OpenBSD-4.6/sys/dev/usb/udl.c

Compare this file to the similar file:
Show the results in this format:

/*	$OpenBSD: udl.c,v 1.19 2009/06/06 16:56:56 yuo Exp $ */

/*
 * Copyright (c) 2009 Marcus Glocker <mglocker@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Driver for the ``DisplayLink DL-120 / DL-160'' graphic chips based
 * on the reversed engineered specifications of Florian Echtler
 * <floe@butterbrot.org>:
 *
 * 	http://floe.butterbrot.org/displaylink/doku.php
 *
 * This driver has been inspired by the cfxga(4) driver because we have
 * to deal with similar challenges, like no direct access to the video
 * memory.
 */

#include <sys/param.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <uvm/uvm.h>

#include <machine/bus.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>
#include <dev/usb/usbdevs.h>

#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wsdisplayvar.h>
#include <dev/rasops/rasops.h>

#include <dev/usb/udl.h>

/*
 * Defines.
 */
#if 0
#define UDL_DEBUG
#endif
#ifdef UDL_DEBUG
int udl_debug = 1;
#define DPRINTF(l, x...) do { if ((l) <= udl_debug) printf(x); } while (0)
#else
#define DPRINTF(l, x...)
#endif

#define DN(sc)		((sc)->sc_dev.dv_xname)
#define FUNC		__func__

/*
 * Prototypes.
 */
int		udl_match(struct device *, void *, void *);
void		udl_attach(struct device *, struct device *, void *);
int		udl_detach(struct device *, int);
int		udl_activate(struct device *, enum devact);

int		udl_ioctl(void *, u_long, caddr_t, int, struct proc *);
paddr_t		udl_mmap(void *, off_t, int);
int		udl_alloc_screen(void *, const struct wsscreen_descr *,
		    void **, int *, int *, long *);
void		udl_free_screen(void *, void *);
int		udl_show_screen(void *, void *, int,
		    void (*)(void *, int, int), void *);
void		udl_burner(void *, u_int, u_int);

void		udl_copycols(void *, int, int, int, int);
void		udl_copyrows(void *, int, int, int);
void		udl_erasecols(void *, int, int, int, long);
void		udl_eraserows(void *, int, int, long);
void		udl_putchar(void *, int, int, u_int, long);
void		udl_do_cursor(struct rasops_info *);

usbd_status	udl_ctrl_msg(struct udl_softc *, uint8_t, uint8_t,
		    uint16_t, uint16_t, uint8_t *, size_t);
usbd_status	udl_poll(struct udl_softc *, uint32_t *);
usbd_status	udl_read_1(struct udl_softc *, uint16_t, uint8_t *);
usbd_status	udl_write_1(struct udl_softc *, uint16_t, uint8_t);
usbd_status	udl_read_edid(struct udl_softc *, uint8_t *);
usbd_status	udl_set_enc_key(struct udl_softc *, uint8_t *, uint8_t);
usbd_status	udl_set_decomp_table(struct udl_softc *, uint8_t *, uint16_t);

usbd_status	udl_cmd_alloc_xfer(struct udl_softc *);
void		udl_cmd_free_xfer(struct udl_softc *);
int		udl_cmd_alloc_buf(struct udl_softc *);
void		udl_cmd_free_buf(struct udl_softc *);
void		udl_cmd_insert_int_1(struct udl_softc *, uint8_t);
void		udl_cmd_insert_int_2(struct udl_softc *, uint16_t);
void		udl_cmd_insert_int_3(struct udl_softc *, uint32_t);
void		udl_cmd_insert_int_4(struct udl_softc *, uint32_t);
void		udl_cmd_insert_buf(struct udl_softc *, uint8_t *, uint32_t);
void		udl_cmd_insert_check(struct udl_cmd_buf *, int);
void		udl_cmd_write_reg_1(struct udl_softc *, uint8_t, uint8_t);
void		udl_cmd_write_reg_3(struct udl_softc *, uint8_t, uint32_t);
usbd_status	udl_cmd_send(struct udl_softc *);
usbd_status	udl_cmd_send_async(struct udl_softc *);
void		udl_cmd_send_async_cb(usbd_xfer_handle, usbd_private_handle,
		    usbd_status);

usbd_status	udl_init_chip(struct udl_softc *);
void		udl_init_fb_offsets(struct udl_softc *, uint32_t, uint32_t,
		    uint32_t, uint32_t);
usbd_status	udl_init_resolution(struct udl_softc *, uint8_t *, uint8_t);
void		udl_fb_off_write(struct udl_softc *, uint16_t, uint32_t,
		    uint16_t);
void		udl_fb_line_write(struct udl_softc *, uint16_t, uint32_t,
		    uint32_t, uint32_t);
void		udl_fb_block_write(struct udl_softc *, uint16_t, uint32_t,
		    uint32_t, uint32_t, uint32_t);
void		udl_fb_buf_write(struct udl_softc *, uint8_t *, uint32_t,
		    uint32_t, uint16_t);
void		udl_fb_off_copy(struct udl_softc *, uint32_t, uint32_t,
		    uint16_t);
void		udl_fb_line_copy(struct udl_softc *, uint32_t, uint32_t,
		    uint32_t, uint32_t, uint32_t);
void		udl_fb_block_copy(struct udl_softc *, uint32_t, uint32_t,
		    uint32_t, uint32_t, uint32_t, uint32_t);
void		udl_draw_char(struct udl_softc *, uint16_t, uint16_t, u_int,
		    uint32_t, uint32_t);
#ifdef UDL_DEBUG
void		udl_hexdump(void *, int, int);
usbd_status	udl_init_test(struct udl_softc *);
#endif

/*
 * Driver glue.
 */
struct cfdriver udl_cd = {
	NULL, "udl", DV_DULL
};

const struct cfattach udl_ca = {
	sizeof(struct udl_softc),
	udl_match,
	udl_attach,
	udl_detach,
	udl_activate
};

/*
 * wsdisplay glue.
 */
struct wsscreen_descr udl_stdscreen = {
	"std",			/* name */
	0, 0,			/* ncols, nrows */
	NULL,			/* textops */
	0, 0,			/* fontwidth, fontheight */
	WSSCREEN_WSCOLORS	/* capabilities */
};

const struct wsscreen_descr *udl_scrlist[] = {
	&udl_stdscreen
};

struct wsscreen_list udl_screenlist = {
	sizeof(udl_scrlist) / sizeof(struct wsscreen_descr *), udl_scrlist
};

struct wsdisplay_accessops udl_accessops = {
	udl_ioctl,
	udl_mmap,
	udl_alloc_screen,
	udl_free_screen,
	udl_show_screen,
	NULL,
	NULL,
	NULL,
	udl_burner
};

/*
 * Matching devices.
 */
static const struct usb_devno udl_devs[] = {
	{ USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LCD4300U },
	{ USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LCD8000U },
	{ USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_DLDVI },
	{ USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_VGA10 },
	{ USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_WSDVI },
	{ USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_EC008 }
};

int
udl_match(struct device *parent, void *match, void *aux)
{
	struct usb_attach_arg *uaa = aux;

	if (uaa->iface != NULL)
		return (UMATCH_NONE);

	if (usb_lookup(udl_devs, uaa->vendor, uaa->product) != NULL)
		return (UMATCH_VENDOR_PRODUCT);

	return (UMATCH_NONE);
}

void
udl_attach(struct device *parent, struct device *self, void *aux)
{
	struct udl_softc *sc = (struct udl_softc *)self;
	struct usb_attach_arg *uaa = aux;
	struct wsemuldisplaydev_attach_args aa;
	usbd_status error;
	int err;

	sc->sc_udev = uaa->device;

	/*
	 * Set device configuration descriptor number.
	 */
	error = usbd_set_config_no(sc->sc_udev, 1, 0);
	if (error != USBD_NORMAL_COMPLETION)
		return;

	/*
	 * Create device handle to interface descriptor.
	 */
	error = usbd_device2interface_handle(sc->sc_udev, 0, &sc->sc_iface);
	if (error != USBD_NORMAL_COMPLETION)
		return;

	/*
	 * Allocate bulk command xfer.
	 */
	error = udl_cmd_alloc_xfer(sc);
	if (error != USBD_NORMAL_COMPLETION)
		return;

	/*
	 * Allocate command buffer.
	 */
	err = udl_cmd_alloc_buf(sc);
	if (err != 0)
		return;

	/*
	 * Open bulk TX pipe.
	 */
	error = usbd_open_pipe(sc->sc_iface, 0x01, USBD_EXCLUSIVE_USE,
	    &sc->sc_tx_pipeh);
	if (error != USBD_NORMAL_COMPLETION)
		return;

	/*
	 * Initialize chip.
	 */
	error = udl_init_chip(sc);
	if (error != USBD_NORMAL_COMPLETION)
		return;

	/*
	 * Initialize resolution.
	 */
	sc->sc_width = 800;	/* XXX shouldn't we do this somewhere else? */
	sc->sc_height = 600;
	sc->sc_depth = 16;

	error = udl_init_resolution(sc, udl_reg_vals_800,
	    sizeof(udl_reg_vals_800));
	if (error != USBD_NORMAL_COMPLETION)
		return;

	/*
	 * Attach wsdisplay.
	 */
	aa.console = 0;
	aa.scrdata = &udl_screenlist;
	aa.accessops = &udl_accessops;
	aa.accesscookie = sc;
	aa.defaultscreens = 0;

	sc->sc_wsdisplay = config_found(self, &aa, wsemuldisplaydevprint);

	usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, &sc->sc_dev);
}

int
udl_detach(struct device *self, int flags)
{
	struct udl_softc *sc = (struct udl_softc *)self;

	/*
	 * Close bulk TX pipe.
	 */
	if (sc->sc_tx_pipeh != NULL) {
		usbd_abort_pipe(sc->sc_tx_pipeh);
		usbd_close_pipe(sc->sc_tx_pipeh);
	}

	/*
	 * Free command buffer.
	 */
	udl_cmd_free_buf(sc);

	/*
	 * Free command xfer.
	 */
	udl_cmd_free_xfer(sc);

	/*
	 * Detach wsdisplay.
	 */
	if (sc->sc_wsdisplay != NULL)
		config_detach(sc->sc_wsdisplay, DETACH_FORCE);

	usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, &sc->sc_dev);

	return (0);
}

int
udl_activate(struct device *self, enum devact act)
{
	switch (act) {
	case DVACT_ACTIVATE:
		return (EOPNOTSUPP);
	case DVACT_DEACTIVATE:
		break;
	}

	return (0);
}

/* ---------- */

int
udl_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
{
	struct udl_softc *sc;

	sc = v;

	DPRINTF(1, "%s: %s: ('%c', %d, %d)\n",
	    DN(sc), FUNC, IOCGROUP(cmd), cmd & 0xff, IOCPARM_LEN(cmd));

	/* TODO */
	switch (cmd) {
	case WSDISPLAYIO_GTYPE:
		*(u_int *)data = WSDISPLAY_TYPE_DL;
		break;
	default:
		return (-1);
	}

	return (0);
}

paddr_t
udl_mmap(void *v, off_t off, int prot)
{
	struct udl_softc *sc;

	sc = v;

	DPRINTF(1, "%s: %s\n", DN(sc), FUNC);

	return (-1);
}

int
udl_alloc_screen(void *v, const struct wsscreen_descr *type,
    void **cookiep, int *curxp, int *curyp, long *attrp)
{
	struct udl_softc *sc = v;
	struct wsdisplay_font *font;

	DPRINTF(1, "%s: %s\n", DN(sc), FUNC);

	if (sc->sc_nscreens > 0)
		return (ENOMEM);

	/*
	 * Initialize rasops.
	 */
	sc->sc_ri.ri_depth = sc->sc_depth;
	sc->sc_ri.ri_bits = NULL;
	sc->sc_ri.ri_width = sc->sc_width;
	sc->sc_ri.ri_height = sc->sc_height;
	sc->sc_ri.ri_stride = sc->sc_width * sc->sc_height / 8;
	sc->sc_ri.ri_hw = (void *)sc;
	sc->sc_ri.ri_flg = 0;

	/* swap B and R at 16 bpp */
	if (sc->sc_depth == 16) {
		sc->sc_ri.ri_rnum = 5;
		sc->sc_ri.ri_rpos = 11;
		sc->sc_ri.ri_gnum = 6;
		sc->sc_ri.ri_gpos = 5;
		sc->sc_ri.ri_bnum = 5;
		sc->sc_ri.ri_bpos = 0;
	}

	rasops_init(&sc->sc_ri, 100, 100);

	sc->sc_ri.ri_ops.copycols = udl_copycols;
	sc->sc_ri.ri_ops.copyrows = udl_copyrows;
	sc->sc_ri.ri_ops.erasecols = udl_erasecols;
	sc->sc_ri.ri_ops.eraserows = udl_eraserows;
	sc->sc_ri.ri_ops.putchar = udl_putchar;
	sc->sc_ri.ri_do_cursor = udl_do_cursor;

	sc->sc_ri.ri_ops.alloc_attr(&sc->sc_ri, 0, 0, 0, attrp);

	udl_stdscreen.nrows = sc->sc_ri.ri_rows;
	udl_stdscreen.ncols = sc->sc_ri.ri_cols;
	udl_stdscreen.textops = &sc->sc_ri.ri_ops;
	udl_stdscreen.fontwidth = sc->sc_ri.ri_font->fontwidth;
	udl_stdscreen.fontheight = sc->sc_ri.ri_font->fontheight;
	udl_stdscreen.capabilities = sc->sc_ri.ri_caps;

	*cookiep = &sc->sc_ri;
	*curxp = 0;
	*curyp = 0;

	sc->sc_nscreens++;

	font = sc->sc_ri.ri_font;
	DPRINTF(1, "%s: %s: using font %s (%dx%d)\n",
	    DN(sc), FUNC, font->name, sc->sc_ri.ri_cols, sc->sc_ri.ri_rows);

	return (0);
}

void
udl_free_screen(void *v, void *cookie)
{
	struct udl_softc *sc;

	sc = v;

	DPRINTF(1, "%s: %s\n", DN(sc), FUNC);
}

int
udl_show_screen(void *v, void *cookie, int waitok,
    void (*cb)(void *, int, int), void *cbarg)
{
	struct udl_softc *sc;

	sc = v;

	DPRINTF(1, "%s: %s\n", DN(sc), FUNC);

	return (0);
}

void
udl_burner(void *v, u_int on, u_int flags)
{
	struct udl_softc *sc;

	sc = v;

	DPRINTF(1, "%s: %s: screen %s\n", DN(sc), FUNC, on ? "ON" : "OFF");

	if (on)
		udl_cmd_write_reg_1(sc, UDL_REG_SCREEN, UDL_REG_SCREEN_ON);
	else
		udl_cmd_write_reg_1(sc, UDL_REG_SCREEN, UDL_REG_SCREEN_OFF);

	udl_cmd_write_reg_1(sc, UDL_REG_SYNC, 0xff);

	(void)udl_cmd_send_async(sc);
}

/* ---------- */

void
udl_copycols(void *cookie, int row, int src, int dst, int num)
{
	struct rasops_info *ri = cookie;
	struct udl_softc *sc;
	int sx, sy, dx, dy, cx, cy;

	sc = ri->ri_hw;

	DPRINTF(2, "%s: %s: row=%d, src=%d, dst=%d, num=%d\n",
	    DN(sc), FUNC, row, src, dst, num);

	sx = src * ri->ri_font->fontwidth;
	sy = row * ri->ri_font->fontheight;
	dx = dst * ri->ri_font->fontwidth;
	dy = row * ri->ri_font->fontheight;
	cx = num * ri->ri_font->fontwidth;
	cy = ri->ri_font->fontheight;

	udl_fb_block_copy(sc, sx, sy, dx, dy, cx, cy);

	(void)udl_cmd_send_async(sc);
}

void
udl_copyrows(void *cookie, int src, int dst, int num)
{
	struct rasops_info *ri = cookie;
	struct udl_softc *sc;
	int sy, dy, cx, cy;

	sc = ri->ri_hw;

	DPRINTF(2, "%s: %s: src=%d, dst=%d, num=%d\n",
	    DN(sc), FUNC, src, dst, num);

	sy = src * sc->sc_ri.ri_font->fontheight;
	dy = dst * sc->sc_ri.ri_font->fontheight;
	cx = sc->sc_ri.ri_emuwidth;
	cy = num * sc->sc_ri.ri_font->fontheight;

	/* copy row block to off-screen first to fix overlay-copy problem */
	udl_fb_block_copy(sc, 0, sy, 0, sc->sc_ri.ri_emuheight, cx, cy);

	/* copy row block back from off-screen now */
	udl_fb_block_copy(sc, 0, sc->sc_ri.ri_emuheight, 0, dy, cx, cy);

	(void)udl_cmd_send_async(sc);
}

void
udl_erasecols(void *cookie, int row, int col, int num, long attr)
{
	struct rasops_info *ri = cookie;
	struct udl_softc *sc = ri->ri_hw;
	uint16_t bgc;
	int fg, bg;
	int x, y, cx, cy;

	sc = ri->ri_hw;

	DPRINTF(2, "%s: %s: row=%d, col=%d, num=%d\n",
	    DN(sc), FUNC, row, col, num);

	sc->sc_ri.ri_ops.unpack_attr(cookie, attr, &fg, &bg, NULL);
	bgc = (uint16_t)sc->sc_ri.ri_devcmap[bg];

	x = col * sc->sc_ri.ri_font->fontwidth;
	y = row * sc->sc_ri.ri_font->fontheight;
	cx = num * sc->sc_ri.ri_font->fontwidth;
	cy = sc->sc_ri.ri_font->fontheight;

	udl_fb_block_write(sc, bgc, x, y, cx, cy);

	(void)udl_cmd_send_async(sc);
}

void
udl_eraserows(void *cookie, int row, int num, long attr)
{
	struct rasops_info *ri = cookie;
	struct udl_softc *sc;
	uint16_t bgc;
	int fg, bg;
	int x, y, cx, cy;

	sc = ri->ri_hw;

	DPRINTF(2, "%s: %s: row=%d, num=%d\n", DN(sc), FUNC, row, num);

	sc->sc_ri.ri_ops.unpack_attr(cookie, attr, &fg, &bg, NULL);
	bgc = (uint16_t)sc->sc_ri.ri_devcmap[bg];

	x = 0;
	y = row * sc->sc_ri.ri_font->fontheight;
	cx = sc->sc_ri.ri_emuwidth;
	cy = num * sc->sc_ri.ri_font->fontheight;

	udl_fb_block_write(sc, bgc, x, y, cx, cy);

	(void)udl_cmd_send_async(sc);
}

void
udl_putchar(void *cookie, int row, int col, u_int uc, long attr)
{
	struct rasops_info *ri = cookie;
	struct udl_softc *sc = ri->ri_hw;
	uint16_t fgc, bgc;
	uint32_t x, y, fg, bg;

	DPRINTF(2, "%s: %s\n", DN(sc), FUNC);

	sc->sc_ri.ri_ops.unpack_attr(cookie, attr, &fg, &bg, NULL);
	fgc = (uint16_t)sc->sc_ri.ri_devcmap[fg];
	bgc = (uint16_t)sc->sc_ri.ri_devcmap[bg];

	x = col * ri->ri_font->fontwidth;
	y = row * ri->ri_font->fontheight;

	if (uc == ' ') {
		/*
		 * Writting a block for the space character instead rendering
		 * it from font bits is more slim.
		 */
		udl_fb_block_write(sc, bgc, x, y,
		    ri->ri_font->fontwidth, ri->ri_font->fontheight);
	} else {
		/* render a character from font bits */
		udl_draw_char(sc, fgc, bgc, uc, x, y);
	}

	/*
	 * We don't call udl_cmd_send_async() here, since sending each
	 * character by itself gets the performance down bad.  Instead the
	 * character will be buffered until another rasops function flush
	 * the buffer.
	 */
}

void
udl_do_cursor(struct rasops_info *ri)
{
	struct udl_softc *sc = ri->ri_hw;
	uint32_t x, y;

	/*
	 * XXX
	 * We can't draw a transparent cursor yet because the chip
	 * doesn't offer an XOR command nor a read command for screen
	 * regions.  Maybe this gets fixed once when wscons(4) is able
	 * to remember the on-screen characters.
	 */

	DPRINTF(2, "%s: %s: ccol=%d, crow=%d\n",
	    DN(sc), FUNC, ri->ri_ccol, ri->ri_crow);

	x = ri->ri_ccol * ri->ri_font->fontwidth;
	y = ri->ri_crow * ri->ri_font->fontheight;

	if (sc->sc_cursor_on == 0) {
		/* safe the last character block to off-screen */
		udl_fb_block_copy(sc, x, y, 0, sc->sc_ri.ri_emuheight,
		    ri->ri_font->fontwidth, ri->ri_font->fontheight);

		/* draw cursor */
		udl_fb_block_write(sc, 0xffff, x, y,
		    ri->ri_font->fontwidth, ri->ri_font->fontheight);

		sc->sc_cursor_on = 1;
	} else {
		/* restore the last safed character from off-screen */
		udl_fb_block_copy(sc, 0, sc->sc_ri.ri_emuheight, x, y,
		    ri->ri_font->fontwidth, ri->ri_font->fontheight);

		sc->sc_cursor_on = 0;
	}

	(void)udl_cmd_send_async(sc);
}

/* ---------- */

usbd_status
udl_ctrl_msg(struct udl_softc *sc, uint8_t rt, uint8_t r,
    uint16_t index, uint16_t value, uint8_t *buf, size_t len)
{
	usb_device_request_t req;
	usbd_status error;

	req.bmRequestType = rt;
	req.bRequest = r;
	USETW(req.wIndex, index);
	USETW(req.wValue, value);
	USETW(req.wLength, len);

	error = usbd_do_request(sc->sc_udev, &req, buf);
	if (error != USBD_NORMAL_COMPLETION) {
		printf("%s: %s: %s!\n", DN(sc), FUNC, usbd_errstr(error));
		return (error);
	}

	return (USBD_NORMAL_COMPLETION);
}

usbd_status
udl_poll(struct udl_softc *sc, uint32_t *buf)
{
	uint8_t lbuf[4];
	usbd_status error;

	error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE,
	    UDL_CTRL_CMD_POLL, 0x0000, 0x0000, lbuf, 4);
	if (error != USBD_NORMAL_COMPLETION) {
		printf("%s: %s: %s!\n", DN(sc), FUNC, usbd_errstr(error));
		return (error);
	}
	*buf = *(uint32_t *)lbuf;

	return (USBD_NORMAL_COMPLETION);
}

usbd_status
udl_read_1(struct udl_softc *sc, uint16_t addr, uint8_t *buf)
{
	uint8_t lbuf[1];
	usbd_status error;

	error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE,
	    UDL_CTRL_CMD_READ_1, addr, 0x0000, lbuf, 1);
	if (error != USBD_NORMAL_COMPLETION) {
		printf("%s: %s: %s!\n", DN(sc), FUNC, usbd_errstr(error));
		return (error);
	}
	*buf = *(uint8_t *)lbuf;

	return (USBD_NORMAL_COMPLETION);
}

usbd_status
udl_write_1(struct udl_softc *sc, uint16_t addr, uint8_t buf)
{
	usbd_status error;

	error = udl_ctrl_msg(sc, UT_WRITE_VENDOR_DEVICE,
	    UDL_CTRL_CMD_WRITE_1, addr, 0x0000, &buf, 1);
	if (error != USBD_NORMAL_COMPLETION) {
		printf("%s: %s: %s!\n", DN(sc), FUNC, usbd_errstr(error));
		return (error);
	}

	return (USBD_NORMAL_COMPLETION);
}

usbd_status
udl_read_edid(struct udl_softc *sc, uint8_t *buf)
{
	uint8_t lbuf[64];
	uint16_t offset;
	usbd_status error;

	offset = 0;

	error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE,
	    UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 64);
	if (error != USBD_NORMAL_COMPLETION)
		goto fail;
	bcopy(lbuf + 1, buf + offset, 63);
	offset += 63;

	error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE,
	    UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 64);
	if (error != USBD_NORMAL_COMPLETION)
		goto fail;
	bcopy(lbuf + 1, buf + offset, 63);
	offset += 63;

	error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE,
	    UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 3);
	if (error != USBD_NORMAL_COMPLETION)
		goto fail;
	bcopy(lbuf + 1, buf + offset, 2);

	return (USBD_NORMAL_COMPLETION);
fail:
	printf("%s: %s: %s!\n", DN(sc), FUNC, usbd_errstr(error));
	return (error);
}

usbd_status
udl_set_enc_key(struct udl_softc *sc, uint8_t *buf, uint8_t len)
{
	usbd_status error;

	error = udl_ctrl_msg(sc, UT_WRITE_VENDOR_DEVICE,
	    UDL_CTRL_CMD_SET_KEY, 0x0000, 0x0000, buf, len);
	if (error != USBD_NORMAL_COMPLETION) {
		printf("%s: %s: %s!\n", DN(sc), FUNC, usbd_errstr(error));
		return (error);
	}
	
	return (USBD_NORMAL_COMPLETION);
}

usbd_status
udl_set_decomp_table(struct udl_softc *sc, uint8_t *buf, uint16_t len)
{
	int err;

	udl_cmd_insert_int_1(sc, UDL_BULK_SOC);
	udl_cmd_insert_int_1(sc, UDL_BULK_CMD_DECOMP);
	udl_cmd_insert_int_4(sc, 0x263871cd);	/* magic number */
	udl_cmd_insert_int_4(sc, 0x00000200);	/* 512 byte chunks */
	udl_cmd_insert_buf(sc, buf, len);

	err = udl_cmd_send(sc);
	if (err != 0)
		return (USBD_INVAL);

	return (USBD_NORMAL_COMPLETION);
}

/* ---------- */

usbd_status
udl_cmd_alloc_xfer(struct udl_softc *sc)
{
	int i;

	for (i = 0; i < UDL_CMD_XFER_COUNT; i++) {
		struct udl_cmd_xfer *cx = &sc->sc_cmd_xfer[i];

		cx->sc = sc;

		cx->xfer = usbd_alloc_xfer(sc->sc_udev);
		if (cx->xfer == NULL) {
			printf("%s: %s: can't allocate xfer handle!\n",
			    DN(sc), FUNC);
			return (USBD_NOMEM);
		}

		cx->buf = usbd_alloc_buffer(cx->xfer, UDL_CMD_MAX_XFER_SIZE);
		if (cx->buf == NULL) {
			printf("%s: %s: can't allocate xfer buffer!\n",
			    DN(sc), FUNC);
			return (USBD_NOMEM);
		}
	}

	return (USBD_NORMAL_COMPLETION);
}

void
udl_cmd_free_xfer(struct udl_softc *sc)
{
	int i;

	for (i = 0; i < UDL_CMD_XFER_COUNT; i++) {
		struct udl_cmd_xfer *cx = &sc->sc_cmd_xfer[i];

		if (cx->xfer != NULL) {
			usbd_free_xfer(cx->xfer);
			cx->xfer = NULL;
		}
	}
}

int
udl_cmd_alloc_buf(struct udl_softc *sc)
{
	struct udl_cmd_buf *cb = &sc->sc_cmd_buf;

	cb->buf = malloc(UDL_CMD_MAX_XFER_SIZE, M_DEVBUF, 0);
	if (cb->buf == NULL) {
		printf("%s: %s: can't allocate buffer!\n",
		    DN(sc), FUNC);
		return (ENOMEM);
	}
	cb->off = 0;

	return (0);
}

void
udl_cmd_free_buf(struct udl_softc *sc)
{
	struct udl_cmd_buf *cb = &sc->sc_cmd_buf;

	free(cb->buf, M_DEVBUF);
	cb->off = 0;
}

void
udl_cmd_insert_int_1(struct udl_softc *sc, uint8_t value)
{
	struct udl_cmd_buf *cb = &sc->sc_cmd_buf;

	udl_cmd_insert_check(cb, 1);

	cb->buf[cb->off] = value;

	cb->off += 1;
}

void
udl_cmd_insert_int_2(struct udl_softc *sc, uint16_t value)
{
	uint16_t lvalue;
	struct udl_cmd_buf *cb = &sc->sc_cmd_buf;

	udl_cmd_insert_check(cb, 2);

	lvalue = htobe16(value);
	bcopy(&lvalue, cb->buf + cb->off, 2);

	cb->off += 2;
}

void
udl_cmd_insert_int_3(struct udl_softc *sc, uint32_t value)
{
	uint32_t lvalue;
	struct udl_cmd_buf *cb = &sc->sc_cmd_buf;

	udl_cmd_insert_check(cb, 3);
#if BYTE_ORDER == BIG_ENDIAN
	lvalue = htobe32(value) << 8;
#else
	lvalue = htobe32(value) >> 8;
#endif
	bcopy(&lvalue, cb->buf + cb->off, 3);

	cb->off += 3;
}

void
udl_cmd_insert_int_4(struct udl_softc *sc, uint32_t value)
{
	uint32_t lvalue;
	struct udl_cmd_buf *cb = &sc->sc_cmd_buf;

	udl_cmd_insert_check(cb, 4);

	lvalue = htobe32(value);
	bcopy(&lvalue, cb->buf + cb->off, 4);

	cb->off += 4;
}

void
udl_cmd_insert_buf(struct udl_softc *sc, uint8_t *buf, uint32_t len)
{
	struct udl_cmd_buf *cb = &sc->sc_cmd_buf;

	udl_cmd_insert_check(cb, len);

	bcopy(buf, cb->buf + cb->off, len);

	cb->off += len;
}

void
udl_cmd_insert_check(struct udl_cmd_buf *cb, int len)
{
	int total;

	total = cb->off + len;

	if (total >=  UDL_CMD_MAX_XFER_SIZE) {
		/* XXX */
		panic("udl_cmd_insert_check: command buffer is full");
	}
}

void
udl_cmd_write_reg_1(struct udl_softc *sc, uint8_t reg, uint8_t val)
{
	udl_cmd_insert_int_1(sc, UDL_BULK_SOC);
	udl_cmd_insert_int_1(sc, UDL_BULK_CMD_REG_WRITE_1);
	udl_cmd_insert_int_1(sc, reg);
	udl_cmd_insert_int_1(sc, val);
}

void
udl_cmd_write_reg_3(struct udl_softc *sc, uint8_t reg, uint32_t val)
{
	udl_cmd_write_reg_1(sc, reg + 0, (val >> 16) & 0xff);
	udl_cmd_write_reg_1(sc, reg + 1, (val >> 8) & 0xff);
	udl_cmd_write_reg_1(sc, reg + 2, (val >> 0) & 0xff);
}

usbd_status
udl_cmd_send(struct udl_softc *sc)
{
	struct udl_cmd_buf *cb = &sc->sc_cmd_buf;
	struct udl_cmd_xfer *cx = &sc->sc_cmd_xfer[0];
	int len;
	usbd_status error;

	/* mark end of command stack */
	udl_cmd_insert_int_1(sc, UDL_BULK_SOC);
	udl_cmd_insert_int_1(sc, UDL_BULK_CMD_EOC);

	bcopy(cb->buf, cx->buf, cb->off);

	len = cb->off;
	error = usbd_bulk_transfer(cx->xfer, sc->sc_tx_pipeh,
	    USBD_NO_COPY | USBD_SHORT_XFER_OK, 1000, cx->buf, &len,
	    "udl_bulk_xmit");
	if (error != USBD_NORMAL_COMPLETION) {
		printf("%s: %s: %s!\n", DN(sc), FUNC, usbd_errstr(error));
		/* we clear our buffer now to avoid growing out of bounds */
		goto fail;
	}
	DPRINTF(1, "%s: %s: sent %d of %d bytes\n",
	    DN(sc), FUNC, len, cb->off);
fail:
	cb->off = 0;

	return (error);
}

usbd_status
udl_cmd_send_async(struct udl_softc *sc)
{
	struct udl_cmd_buf *cb = &sc->sc_cmd_buf;
	struct udl_cmd_xfer *cx;
	usbd_status error;
	int i, s;

	/* if the ring buffer is full, wait until it's flushed completely */
	if (sc->sc_cmd_xfer_cnt == UDL_CMD_XFER_COUNT) {
		DPRINTF(2, "%s: %s: ring buffer full, wait until flushed\n",
		    DN(sc), FUNC);
		/*
		 * XXX
		 * Yes, this is ugly.  But since we can't tsleep() here, I
		 * have no better idea how we can delay rasops so it doesn't
		 * blow up our command buffer.
		 */
		while (sc->sc_cmd_xfer_cnt > 0)
			delay(100);
	}

	s = splusb();	/* no callbacks please until accounting is done */

	/* find a free ring buffer */
	for (i = 0; i < UDL_CMD_XFER_COUNT; i++) {
		if (sc->sc_cmd_xfer[i].busy == 0)
			break;
	}
	if (i == UDL_CMD_XFER_COUNT) {
		/* XXX this shouldn't happen */
		panic("udl_cmd_send_async: buffer full");
	}
	cx = &sc->sc_cmd_xfer[i];

	/* mark end of command stack */
	udl_cmd_insert_int_1(sc, UDL_BULK_SOC);
	udl_cmd_insert_int_1(sc, UDL_BULK_CMD_EOC);

	/* copy command buffer to xfer buffer */
	bcopy(cb->buf, cx->buf, cb->off);

	/* do xfer */
	usbd_setup_xfer(cx->xfer, sc->sc_tx_pipeh, cx, cx->buf, cb->off,
	     USBD_NO_COPY, 1000, udl_cmd_send_async_cb);
	error = usbd_transfer(cx->xfer);
	if (error != 0 && error != USBD_IN_PROGRESS) {
		printf("%s: %s: %s!\n", DN(sc), FUNC, usbd_errstr(error));
		return (error);
	}
	DPRINTF(2, "%s: %s: sending %d bytes from buffer no. %d\n",
	    DN(sc), FUNC, cb->off, i);

	/* free command buffer, lock xfer buffer */
	cb->off = 0;
	cx->busy = 1;
	sc->sc_cmd_xfer_cnt++;

	splx(s);

	return (USBD_NORMAL_COMPLETION);
}

void
udl_cmd_send_async_cb(usbd_xfer_handle xfer, usbd_private_handle priv,
    usbd_status status)
{
	struct udl_cmd_xfer *cx = priv;
	struct udl_softc *sc = cx->sc;
	int len;

	if (status != USBD_NORMAL_COMPLETION) {
		printf("%s: %s: %s!\n", DN(sc), FUNC, usbd_errstr(status));

		if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
			return;
		if (status == USBD_STALLED)
			usbd_clear_endpoint_stall_async(sc->sc_tx_pipeh);
		goto skip;
	}
	usbd_get_xfer_status(xfer, NULL, NULL, &len, NULL);

	DPRINTF(2, "%s: %s: sent %d bytes\n", DN(sc), FUNC, len);
skip:
	/* free xfer buffer */
	cx->busy = 0;
	sc->sc_cmd_xfer_cnt--;
}

/* ---------- */

usbd_status
udl_init_chip(struct udl_softc *sc)
{
	uint8_t ui8;
	uint32_t ui32;
	int8_t edid[128];
	usbd_status error;

	error = udl_poll(sc, &ui32);
	if (error != USBD_NORMAL_COMPLETION)
		return (error);
	DPRINTF(1, "%s: %s: poll=0x%08x\n", DN(sc), FUNC, ui32);

	error = udl_read_1(sc, 0xc484, &ui8);
	if (error != USBD_NORMAL_COMPLETION)
		return (error);
	DPRINTF(1, "%s: %s: read 0x%02x from 0xc484\n", DN(sc), FUNC, ui8);

	error = udl_write_1(sc, 0xc41f, 0x01);
	if (error != USBD_NORMAL_COMPLETION)
		return (error);
	DPRINTF(1, "%s: %s: write 0x01 to 0xc41f\n", DN(sc), FUNC);

	error = udl_read_edid(sc, edid);
	if (error != USBD_NORMAL_COMPLETION)
		return (error);
	DPRINTF(1, "%s: %s: read EDID=\n", DN(sc), FUNC);
#ifdef UDL_DEBUG
	udl_hexdump(edid, sizeof(edid), 0);
#endif
	error = udl_set_enc_key(sc, udl_null_key_1, sizeof(udl_null_key_1));
	if (error != USBD_NORMAL_COMPLETION)
		return (error);
	DPRINTF(1, "%s: %s: set encryption key\n", DN(sc), FUNC);

	error = udl_write_1(sc, 0xc40b, 0x00);
	if (error != USBD_NORMAL_COMPLETION)
		return (error);
	DPRINTF(1, "%s: %s: write 0x00 to 0xc40b\n", DN(sc), FUNC, ui8);

	error = udl_set_decomp_table(sc, udl_decomp_table,
	    sizeof(udl_decomp_table));
	if (error != USBD_NORMAL_COMPLETION)
		return (error);
	DPRINTF(1, "%s: %s: set decompression table\n", DN(sc), FUNC);

	return (USBD_NORMAL_COMPLETION);
}

void
udl_init_fb_offsets(struct udl_softc *sc, uint32_t start16, uint32_t stride16,
    uint32_t start8, uint32_t stride8)
{
	udl_cmd_write_reg_1(sc, UDL_REG_SYNC, 0x00);
	udl_cmd_write_reg_3(sc, UDL_REG_ADDR_START16, start16);
	udl_cmd_write_reg_3(sc, UDL_REG_ADDR_STRIDE16, stride16);
	udl_cmd_write_reg_3(sc, UDL_REG_ADDR_START8, start8);
	udl_cmd_write_reg_3(sc, UDL_REG_ADDR_STRIDE8, stride8);
	udl_cmd_write_reg_1(sc, UDL_REG_SYNC, 0xff);
}

usbd_status
udl_init_resolution(struct udl_softc *sc, uint8_t *buf, uint8_t len)
{
	int i;
	usbd_status error;

	/* write resolution values and set video memory offsets */
	udl_cmd_write_reg_1(sc, UDL_REG_SYNC, 0x00);
	for (i = 0; i < len; i++)
		udl_cmd_write_reg_1(sc, i, buf[i]);
	udl_cmd_write_reg_1(sc, UDL_REG_SYNC, 0xff);

	udl_init_fb_offsets(sc, 0x000000, 0x000a00, 0x555555, 0x000500);
	error = udl_cmd_send(sc);
	if (error != USBD_NORMAL_COMPLETION)
		return (error);

	/* clear screen */
	udl_fb_block_write(sc, 0x0000, 0, 0, sc->sc_width, sc->sc_height);
	error = udl_cmd_send(sc);
	if (error != USBD_NORMAL_COMPLETION)
		return (error);

	/* show framebuffer content */
	udl_cmd_write_reg_1(sc, UDL_REG_SCREEN, UDL_REG_SCREEN_ON);
	udl_cmd_write_reg_1(sc, UDL_REG_SYNC, 0xff);
	error = udl_cmd_send(sc);
	if (error != USBD_NORMAL_COMPLETION)
		return (error);

	return (USBD_NORMAL_COMPLETION);
}

void
udl_fb_off_write(struct udl_softc *sc, uint16_t rgb16, uint32_t off,
    uint16_t width)
{
	uint8_t buf[UDL_CMD_MAX_DATA_SIZE];
	uint16_t lwidth, lrgb16;
	uint32_t loff;
	int i;

	loff = off * 2;
	lwidth = width * 2;

	udl_cmd_insert_int_1(sc, UDL_BULK_SOC);
	udl_cmd_insert_int_1(sc, UDL_BULK_CMD_FB_WRITE | UDL_BULK_CMD_FB_WORD);
	udl_cmd_insert_int_3(sc, loff);
	udl_cmd_insert_int_1(sc, width >= UDL_CMD_MAX_PIXEL_COUNT ? 0 : width);

	for (i = 0; i < lwidth; i += 2) {
		lrgb16 = htobe16(rgb16);
		bcopy(&lrgb16, buf + i, 2);
	}

	udl_cmd_insert_buf(sc, buf, lwidth);
}

void
udl_fb_line_write(struct udl_softc *sc, uint16_t rgb16, uint32_t x,
    uint32_t y, uint32_t width)
{
	uint32_t off, block;

	off = (y * sc->sc_width) + x;

	while (width) {
		if (width > UDL_CMD_MAX_PIXEL_COUNT)	
			block = UDL_CMD_MAX_PIXEL_COUNT;
		else
			block = width;

		udl_fb_off_write(sc, rgb16, off, block);

		off += block;
		width -= block;
	}
}

void
udl_fb_block_write(struct udl_softc *sc, uint16_t rgb16, uint32_t x,
    uint32_t y, uint32_t width, uint32_t height)
{
	uint32_t i;

	for (i = 0; i < height; i++)
		udl_fb_line_write(sc, rgb16, x, y + i, width);
}

void
udl_fb_buf_write(struct udl_softc *sc, uint8_t *buf, uint32_t x,
    uint32_t y, uint16_t width)
{
	uint16_t lwidth;
	uint32_t off;

	off = ((y * sc->sc_width) + x) * 2;
	lwidth = width * 2;

	udl_cmd_insert_int_1(sc, UDL_BULK_SOC);
	udl_cmd_insert_int_1(sc, UDL_BULK_CMD_FB_WRITE | UDL_BULK_CMD_FB_WORD);
	udl_cmd_insert_int_3(sc, off);
	udl_cmd_insert_int_1(sc, width >= UDL_CMD_MAX_PIXEL_COUNT ? 0 : width);

	udl_cmd_insert_buf(sc, buf, lwidth);
}

void
udl_fb_off_copy(struct udl_softc *sc, uint32_t src_off, uint32_t dst_off,
    uint16_t width)
{
	uint32_t ldst_off, lsrc_off;

	ldst_off = dst_off * 2;
	lsrc_off = src_off * 2;

	udl_cmd_insert_int_1(sc, UDL_BULK_SOC);
	udl_cmd_insert_int_1(sc, UDL_BULK_CMD_FB_COPY | UDL_BULK_CMD_FB_WORD);
	udl_cmd_insert_int_3(sc, ldst_off);
	udl_cmd_insert_int_1(sc, width >= UDL_CMD_MAX_PIXEL_COUNT ? 0 : width);
	udl_cmd_insert_int_3(sc, lsrc_off);
}

void
udl_fb_line_copy(struct udl_softc *sc, uint32_t src_x, uint32_t src_y,
    uint32_t dst_x, uint32_t dst_y, uint32_t width)
{
	uint32_t src_off, dst_off, block;

	src_off = (src_y * sc->sc_width) + src_x;
	dst_off = (dst_y * sc->sc_width) + dst_x;

	while (width) {
		if (width > UDL_CMD_MAX_PIXEL_COUNT)
			block = UDL_CMD_MAX_PIXEL_COUNT;
		else
			block = width;

		udl_fb_off_copy(sc, src_off, dst_off, block);

		src_off += block;
		dst_off += block;
		width -= block;
	}
}

void
udl_fb_block_copy(struct udl_softc *sc, uint32_t src_x, uint32_t src_y,
    uint32_t dst_x, uint32_t dst_y, uint32_t width, uint32_t height)
{
	int i;

	for (i = 0; i < height; i++)
		udl_fb_line_copy(sc, src_x, src_y + i, dst_x, dst_y + i, width);
}

void
udl_draw_char(struct udl_softc *sc, uint16_t fg, uint16_t bg, u_int uc,
    uint32_t x, uint32_t y)
{
	int i, j, ly;
	uint8_t *fontchar, fontbits, luc;
	uint8_t buf[UDL_CMD_MAX_DATA_SIZE];
	uint16_t *line, lrgb16;
	struct wsdisplay_font *font = sc->sc_ri.ri_font;

	fontchar = (uint8_t *)(font->data + (uc - font->firstchar) *
	    sc->sc_ri.ri_fontscale);

	ly = y;
	for (i = 0; i < font->fontheight; i++) {
		fontbits = *fontchar;
		line = (uint16_t *)buf;

		for (j = (font->fontwidth - 1); j != -1; j--) {
			luc = 1 << j;
			if (fontbits & luc)
				lrgb16 = htobe16(fg);
			else
				lrgb16 = htobe16(bg);
			bcopy(&lrgb16, line, 2);
			line++;
		}
		udl_fb_buf_write(sc, buf, x, ly, font->fontwidth);
		ly++;

		fontchar += font->stride;
	}
}

/* ---------- */
#ifdef UDL_DEBUG
void
udl_hexdump(void *buf, int len, int quiet)
{
	int i;

	for (i = 0; i < len; i++) {
		if (quiet == 0) {
			if (i % 16 == 0)
				printf("%s%5i:", i ? "\n" : "", i);
			if (i % 4 == 0)
				printf(" ");
		}
		printf("%02x", (int)*((u_char *)buf + i));
	}
	printf("\n");
}

usbd_status
udl_init_test(struct udl_softc *sc)
{
	int i, j, parts, loops;
	uint16_t color;
	uint16_t rgb24[3] = { 0xf800, 0x07e0, 0x001f };

	loops = (sc->sc_width * sc->sc_height) / UDL_CMD_MAX_PIXEL_COUNT;
	parts = loops / 3;
	color = rgb24[0];

	j = 1;
	for (i = 0; i < loops; i++) {
		if (i == parts) {
			color = rgb24[j];
			parts += parts;
			j++;
		}
		udl_fb_off_write(sc, color, i * UDL_CMD_MAX_PIXEL_COUNT,
		    UDL_CMD_MAX_PIXEL_COUNT);
	}
	(void)udl_cmd_send(sc);

	return (USBD_NORMAL_COMPLETION);
}
#endif