/*	$OpenBSD: uticom.c,v 1.27 2015/03/14 03:38:50 jsg Exp $	*/
/*
 * Copyright (c) 2005 Dmitry Komissaroff <dxi@mail.ru>.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/conf.h>
#include <sys/tty.h>

#include <machine/bus.h>

#include <dev/usb/usb.h>

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

#include <dev/usb/ucomvar.h>

#ifdef UTICOM_DEBUG
static int uticomdebug = 0;
#define DPRINTFN(n, x)	do { if (uticomdebug > (n)) printf x; } while (0)
#else
#define DPRINTFN(n, x)
#endif

#define DPRINTF(x) DPRINTFN(0, x)

#define	UTICOM_CONFIG_INDEX	1
#define	UTICOM_ACTIVE_INDEX	2

#define	UTICOM_IFACE_INDEX	0

/*
 * These are the maximum number of bytes transferred per frame.
 * The output buffer size cannot be increased due to the size encoding.
 */
#define UTICOM_IBUFSZ		64
#define UTICOM_OBUFSZ		64

#define UTICOM_FW_BUFSZ		16284

#define UTICOM_INTR_INTERVAL	100	/* ms */

#define UTICOM_RQ_LINE		0
/* Used to sync data0/1-toggle on reopen bulk pipe. */
#define UTICOM_RQ_SOF		1
#define UTICOM_RQ_SON		2

#define UTICOM_RQ_BAUD		3
#define UTICOM_RQ_LCR		4
#define UTICOM_RQ_FCR		5
#define UTICOM_RQ_RTS		6
#define UTICOM_RQ_DTR		7
#define UTICOM_RQ_BREAK		8
#define UTICOM_RQ_CRTSCTS	9

#define UTICOM_BRATE_REF	923077

#define UTICOM_SET_DATA_BITS(x)	(x - 5)

#define UTICOM_STOP_BITS_1	0x00
#define UTICOM_STOP_BITS_2	0x40

#define UTICOM_PARITY_NONE	0x00
#define UTICOM_PARITY_ODD	0x08
#define UTICOM_PARITY_EVEN	0x18

#define UTICOM_LCR_OVR		0x1
#define UTICOM_LCR_PTE		0x2
#define UTICOM_LCR_FRE		0x4
#define UTICOM_LCR_BRK		0x8

#define UTICOM_MCR_CTS		0x1
#define UTICOM_MCR_DSR		0x2
#define UTICOM_MCR_CD		0x4
#define UTICOM_MCR_RI		0x8

/* Structures */
struct uticom_fw_header {
	uint16_t	length;
	uint8_t		checkSum;
} __packed;

struct uticom_buf {
	unsigned int		buf_size;
	char			*buf_buf;
	char			*buf_get;
	char			*buf_put;
};

struct	uticom_softc {
	struct device		 sc_dev;	/* base device */
	struct usbd_device	*sc_udev;	/* device */
	struct usbd_interface	*sc_iface;	/* interface */

	int			sc_iface_number; /* interface number */

	struct usbd_interface	*sc_intr_iface;	/* interrupt interface */
	int			sc_intr_number;	/* interrupt number */
	struct usbd_pipe	*sc_intr_pipe;	/* interrupt pipe */
	u_char			*sc_intr_buf;	/* interrupt buffer */
	int			sc_isize;

	u_char			sc_dtr;		/* current DTR state */
	u_char			sc_rts;		/* current RTS state */
	u_char			sc_status;

	u_char			sc_lsr;		/* Local status register */
	u_char			sc_msr;		/* uticom status register */

	struct device		*sc_subdev;
};

static	usbd_status uticom_reset(struct uticom_softc *);
static	usbd_status uticom_set_crtscts(struct uticom_softc *);
static	void uticom_intr(struct usbd_xfer *, void *, usbd_status);

static	void uticom_set(void *, int, int, int);
static	void uticom_dtr(struct uticom_softc *, int);
static	void uticom_rts(struct uticom_softc *, int);
static	void uticom_break(struct uticom_softc *, int);
static	void uticom_get_status(void *, int, u_char *, u_char *);
#if 0 /* TODO */
static	int  uticom_ioctl(void *, int, u_long, caddr_t, int, usb_proc_ptr);
#endif
static	int  uticom_param(void *, int, struct termios *);
static	int  uticom_open(void *, int);
static	void uticom_close(void *, int);

void uticom_attach_hook(void *arg);

static int uticom_download_fw(struct uticom_softc *sc, int pipeno,
    struct usbd_device *dev);

struct ucom_methods uticom_methods = {
	uticom_get_status,
	uticom_set,
	uticom_param,
	NULL, /* uticom_ioctl, TODO */
	uticom_open,
	uticom_close,
	NULL,
	NULL
};

int	uticom_match(struct device *, void *, void *);
void	uticom_attach(struct device *, struct device *, void *);
int	uticom_detach(struct device *, int);

struct cfdriver uticom_cd = {
	NULL, "uticom", DV_DULL
};

const struct cfattach uticom_ca = {
	sizeof(struct uticom_softc), uticom_match, uticom_attach, uticom_detach
};

static const struct usb_devno uticom_devs[] = {
	{ USB_VENDOR_TI, USB_PRODUCT_TI_TUSB3410 },
	{ USB_VENDOR_TI, USB_PRODUCT_TI_MSP430_JTAG },
	{ USB_VENDOR_STARTECH, USB_PRODUCT_STARTECH_ICUSB232X },
	{ USB_VENDOR_MOXA, USB_PRODUCT_MOXA_UPORT1110 },
	{ USB_VENDOR_ABBOTT, USB_PRODUCT_ABBOTT_STEREO_PLUG }
};

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

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

	return (usb_lookup(uticom_devs, uaa->vendor, uaa->product) != NULL ?
	    UMATCH_VENDOR_PRODUCT : UMATCH_NONE);
}

void
uticom_attach(struct device *parent, struct device *self, void *aux)
{
	struct uticom_softc	*sc = (struct uticom_softc *)self;
	struct usb_attach_arg	*uaa = aux;
	struct usbd_device	*dev = uaa->device;

	sc->sc_udev = dev;
	sc->sc_iface = uaa->iface;

	if (rootvp == NULL)
		mountroothook_establish(uticom_attach_hook, sc);
	else
		uticom_attach_hook(sc);
}

void
uticom_attach_hook(void *arg)
{
	struct uticom_softc		*sc = arg;
	usb_config_descriptor_t		*cdesc;
	usb_interface_descriptor_t	*id;
	usb_endpoint_descriptor_t	*ed;
	usbd_status			 err;
	int				 status, i;
	usb_device_descriptor_t		*dd;
	struct ucom_attach_args		 uca;

	/* Initialize endpoints. */
	uca.bulkin = uca.bulkout = -1;
	sc->sc_intr_number = -1;
	sc->sc_intr_pipe = NULL;

	dd = usbd_get_device_descriptor(sc->sc_udev);
	DPRINTF(("%s: uticom_attach: num of configurations %d\n",
	    sc->sc_dev.dv_xname, dd->bNumConfigurations));

	/* The device without firmware has single configuration with single
	 * bulk out interface. */
	if (dd->bNumConfigurations > 1)
		goto fwload_done;

	/* Loading firmware. */
	DPRINTF(("%s: uticom_attach: starting loading firmware\n",
	    sc->sc_dev.dv_xname));

	err = usbd_set_config_index(sc->sc_udev, UTICOM_CONFIG_INDEX, 1);
	if (err) {
		printf("%s: failed to set configuration: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
		usbd_deactivate(sc->sc_udev);
		return;
	}

	/* Get the config descriptor. */
	cdesc = usbd_get_config_descriptor(sc->sc_udev);

	if (cdesc == NULL) {
		printf("%s: failed to get configuration descriptor\n",
		    sc->sc_dev.dv_xname);
		usbd_deactivate(sc->sc_udev);
		return;
	}

	err = usbd_device2interface_handle(sc->sc_udev, UTICOM_IFACE_INDEX,
	    &sc->sc_iface);
	if (err) {
		printf("%s: failed to get interface: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
		usbd_deactivate(sc->sc_udev);
		return;
	}

	/* Find the bulk out interface used to upload firmware. */
	id = usbd_get_interface_descriptor(sc->sc_iface);
	sc->sc_iface_number = id->bInterfaceNumber;

	for (i = 0; i < id->bNumEndpoints; i++) {
		ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i);
		if (ed == NULL) {
			printf("%s: no endpoint descriptor for %d\n",
			    sc->sc_dev.dv_xname, i);
			usbd_deactivate(sc->sc_udev);
			return;
		}

		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
			uca.bulkout = ed->bEndpointAddress;
			DPRINTF(("%s: uticom_attach: data bulk out num: %d\n",
			    sc->sc_dev.dv_xname, ed->bEndpointAddress));
		}

		if (uca.bulkout == -1) {
			printf("%s: could not find data bulk out\n",
			    sc->sc_dev.dv_xname);
			usbd_deactivate(sc->sc_udev);
			return;
		}
	}

	status = uticom_download_fw(sc, uca.bulkout, sc->sc_udev);

	if (status) {
		printf("%s: firmware download failed\n",
		    sc->sc_dev.dv_xname);
		usbd_deactivate(sc->sc_udev);
		return;
	} else {
		DPRINTF(("%s: firmware download succeeded\n",
		    sc->sc_dev.dv_xname));
	}

	status = usbd_reload_device_desc(sc->sc_udev);
	if (status) {
		printf("%s: error reloading device descriptor\n",
		    sc->sc_dev.dv_xname);
		usbd_deactivate(sc->sc_udev);
		return;
	}

fwload_done:
	dd = usbd_get_device_descriptor(sc->sc_udev);
	DPRINTF(("%s: uticom_attach: num of configurations %d\n",
	    sc->sc_dev.dv_xname, dd->bNumConfigurations));

	err = usbd_set_config_index(sc->sc_udev, UTICOM_ACTIVE_INDEX, 1);
	if (err) {
		printf("%s: failed to set configuration: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
		usbd_deactivate(sc->sc_udev);
		return;
	}

	/* Get the config descriptor. */
	cdesc = usbd_get_config_descriptor(sc->sc_udev);
	if (cdesc == NULL) {
		printf("%s: failed to get configuration descriptor\n",
		    sc->sc_dev.dv_xname);
		usbd_deactivate(sc->sc_udev);
		return;
	}

	/* Get the interface (XXX: multiport chips are not supported yet). */
	err = usbd_device2interface_handle(sc->sc_udev, UTICOM_IFACE_INDEX,
	    &sc->sc_iface);
	if (err) {
		printf("%s: failed to get interface: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
		usbd_deactivate(sc->sc_udev);
		return;
	}

	/* Find the interrupt endpoints. */
	id = usbd_get_interface_descriptor(sc->sc_iface);
	sc->sc_iface_number = id->bInterfaceNumber;

	for (i = 0; i < id->bNumEndpoints; i++) {
		ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i);
		if (ed == NULL) {
			printf("%s: no endpoint descriptor for %d\n",
			    sc->sc_dev.dv_xname, i);
			usbd_deactivate(sc->sc_udev);
			return;
		}

		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) {
			sc->sc_intr_number = ed->bEndpointAddress;
			sc->sc_isize = UGETW(ed->wMaxPacketSize);

		}
	}

	if (sc->sc_intr_number == -1) {
		printf("%s: could not find interrupt in\n",
		    sc->sc_dev.dv_xname);
		usbd_deactivate(sc->sc_udev);
		return;
	}

	/* Keep interface for interrupt. */
	sc->sc_intr_iface = sc->sc_iface;

	/* Find the bulk{in,out} endpoints. */
	id = usbd_get_interface_descriptor(sc->sc_iface);
	sc->sc_iface_number = id->bInterfaceNumber;

	for (i = 0; i < id->bNumEndpoints; i++) {
		ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i);
		if (ed == NULL) {
			printf("%s: no endpoint descriptor for %d\n",
			    sc->sc_dev.dv_xname, i);
			usbd_deactivate(sc->sc_udev);
			return;
		}

		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
			uca.bulkin = ed->bEndpointAddress;
		} else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
			uca.bulkout = ed->bEndpointAddress;
		}
	}

	if (uca.bulkin == -1) {
		printf("%s: could not find data bulk in\n",
		    sc->sc_dev.dv_xname);
		usbd_deactivate(sc->sc_udev);
		return;
	}

	if (uca.bulkout == -1) {
		printf("%s: could not find data bulk out\n",
		    sc->sc_dev.dv_xname);
		usbd_deactivate(sc->sc_udev);
		return;
	}

	sc->sc_dtr = sc->sc_rts = -1;

	uca.portno = UCOM_UNK_PORTNO;
	uca.ibufsize = UTICOM_IBUFSZ;
	uca.obufsize = UTICOM_OBUFSZ;
	uca.ibufsizepad = UTICOM_IBUFSZ;
	uca.device = sc->sc_udev;
	uca.iface = sc->sc_iface;
	uca.opkthdrlen = 0;
	uca.methods = &uticom_methods;
	uca.arg = sc;
	uca.info = NULL;

	err = uticom_reset(sc);
	if (err) {
		printf("%s: reset failed: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
		usbd_deactivate(sc->sc_udev);
		return;
	}

	DPRINTF(("%s: uticom_attach: in = 0x%x, out = 0x%x, intr = 0x%x\n",
	    sc->sc_dev.dv_xname, uca.bulkin,
	    uca.bulkout, sc->sc_intr_number));

	sc->sc_subdev = config_found_sm((struct device *)sc, &uca, ucomprint, ucomsubmatch);
}

int
uticom_detach(struct device *self, int flags)
{
	struct uticom_softc *sc = (struct uticom_softc *)self;

	DPRINTF(("%s: uticom_detach: sc = %p\n",
	    sc->sc_dev.dv_xname, sc));

	if (sc->sc_subdev != NULL) {
		config_detach(sc->sc_subdev, flags);
		sc->sc_subdev = NULL;
	}

	if (sc->sc_intr_pipe != NULL) {
		usbd_abort_pipe(sc->sc_intr_pipe);
		usbd_close_pipe(sc->sc_intr_pipe);
		free(sc->sc_intr_buf, M_USBDEV, 0);
		sc->sc_intr_pipe = NULL;
	}

	return (0);
}

static usbd_status
uticom_reset(struct uticom_softc *sc)
{
	usb_device_request_t req;
	usbd_status err;

	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
	req.bRequest = UTICOM_RQ_SON;
	USETW(req.wValue, 0);
	USETW(req.wIndex, 0);
	USETW(req.wLength, 0);

	err = usbd_do_request(sc->sc_udev, &req, NULL);
	if (err){
		printf("%s: uticom_reset: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
		return (EIO);
	}

	DPRINTF(("%s: uticom_reset: done\n", sc->sc_dev.dv_xname));
	return (0);
}

static void
uticom_set(void *addr, int portno, int reg, int onoff)
{
	struct uticom_softc *sc = addr;

	switch (reg) {
	case UCOM_SET_DTR:
		uticom_dtr(sc, onoff);
		break;
	case UCOM_SET_RTS:
		uticom_rts(sc, onoff);
		break;
	case UCOM_SET_BREAK:
		uticom_break(sc, onoff);
		break;
	default:
		break;
	}
}

static void
uticom_dtr(struct uticom_softc *sc, int onoff)
{
	usb_device_request_t req;
	usbd_status err;

	DPRINTF(("%s: uticom_dtr: onoff = %d\n", sc->sc_dev.dv_xname,
	    onoff));

	if (sc->sc_dtr == onoff)
		return;
	sc->sc_dtr = onoff;

	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
	req.bRequest = UTICOM_RQ_DTR;
	USETW(req.wValue, sc->sc_dtr ? UCDC_LINE_DTR : 0);
	USETW(req.wIndex, 0);
	USETW(req.wLength, 0);

	err = usbd_do_request(sc->sc_udev, &req, NULL);
	if (err)
		printf("%s: uticom_dtr: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
}

static void
uticom_rts(struct uticom_softc *sc, int onoff)
{
	usb_device_request_t req;
	usbd_status err;

	DPRINTF(("%s: uticom_rts: onoff = %d\n", sc->sc_dev.dv_xname,
	    onoff));

	if (sc->sc_rts == onoff)
		return;
	sc->sc_rts = onoff;
	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
	req.bRequest = UTICOM_RQ_RTS;
	USETW(req.wValue, sc->sc_rts ? UCDC_LINE_RTS : 0);
	USETW(req.wIndex, 0);
	USETW(req.wLength, 0);

	err = usbd_do_request(sc->sc_udev, &req, NULL);
	if (err)
		printf("%s: uticom_rts: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
}

static void
uticom_break(struct uticom_softc *sc, int onoff)
{
	usb_device_request_t req;
	usbd_status err;

	DPRINTF(("%s: uticom_break: onoff = %d\n", sc->sc_dev.dv_xname,
	    onoff));

	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
	req.bRequest = UTICOM_RQ_BREAK;
	USETW(req.wValue, onoff ? 1 : 0);
	USETW(req.wIndex, 0);
	USETW(req.wLength, 0);

	err = usbd_do_request(sc->sc_udev, &req, NULL);
	if (err)
		printf("%s: uticom_break: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
}

static usbd_status
uticom_set_crtscts(struct uticom_softc *sc)
{
	usb_device_request_t req;
	usbd_status err;

	DPRINTF(("%s: uticom_set_crtscts: on\n", sc->sc_dev.dv_xname));

	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
	req.bRequest = UTICOM_RQ_CRTSCTS;
	USETW(req.wValue, 1);
	USETW(req.wIndex, 0);
	USETW(req.wLength, 0);

	err = usbd_do_request(sc->sc_udev, &req, NULL);
	if (err) {
		printf("%s: uticom_set_crtscts: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
		return (err);
	}

	return (USBD_NORMAL_COMPLETION);
}

static int
uticom_param(void *vsc, int portno, struct termios *t)
{
	struct uticom_softc *sc = (struct uticom_softc *)vsc;
	usb_device_request_t req;
	usbd_status err;
	uint8_t data;

	DPRINTF(("%s: uticom_param\n", sc->sc_dev.dv_xname));

	switch (t->c_ospeed) {
	case 1200:
	case 2400:
	case 4800:
	case 7200:
	case 9600:
	case 14400:
	case 19200:
	case 38400:
	case 57600:
	case 115200:
	case 230400:
	case 460800:
	case 921600:
		req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
		req.bRequest = UTICOM_RQ_BAUD;
		USETW(req.wValue, (UTICOM_BRATE_REF / t->c_ospeed));
		USETW(req.wIndex, 0);
		USETW(req.wLength, 0);

		err = usbd_do_request(sc->sc_udev, &req, 0);
		if (err) {
			printf("%s: uticom_param: %s\n",
			    sc->sc_dev.dv_xname, usbd_errstr(err));
			return (EIO);
		}
		break;
	default:
		printf("%s: uticom_param: unsupported baud rate %d\n",
		    sc->sc_dev.dv_xname, t->c_ospeed);
		return (EINVAL);
	}

	switch (ISSET(t->c_cflag, CSIZE)) {
	case CS5:
		data = UTICOM_SET_DATA_BITS(5);
		break;
	case CS6:
		data = UTICOM_SET_DATA_BITS(6);
		break;
	case CS7:
		data = UTICOM_SET_DATA_BITS(7);
		break;
	case CS8:
		data = UTICOM_SET_DATA_BITS(8);
		break;
	default:
		return (EIO);
	}

	if (ISSET(t->c_cflag, CSTOPB))
		data |= UTICOM_STOP_BITS_2;
	else
		data |= UTICOM_STOP_BITS_1;

	if (ISSET(t->c_cflag, PARENB)) {
		if (ISSET(t->c_cflag, PARODD))
			data |= UTICOM_PARITY_ODD;
		else
			data |= UTICOM_PARITY_EVEN;
	} else
		data |= UTICOM_PARITY_NONE;

	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
	req.bRequest = UTICOM_RQ_LCR;
	USETW(req.wIndex, 0);
	USETW(req.wLength, 0);
	USETW(req.wValue, data);

	err = usbd_do_request(sc->sc_udev, &req, NULL);
	if (err) {
		printf("%s: uticom_param: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
		return (err);
	}

	if (ISSET(t->c_cflag, CRTSCTS)) {
		err = uticom_set_crtscts(sc);
		if (err)
			return (EIO);
	}

	return (0);
}

static int
uticom_open(void *addr, int portno)
{
	struct uticom_softc *sc = addr;
	usbd_status err;

	if (usbd_is_dying(sc->sc_udev))
		return (ENXIO);

	DPRINTF(("%s: uticom_open\n", sc->sc_dev.dv_xname));

	sc->sc_status = 0; /* clear status bit */

	if (sc->sc_intr_number != -1 && sc->sc_intr_pipe == NULL) {
		sc->sc_intr_buf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK);
		err = usbd_open_pipe_intr(sc->sc_intr_iface, sc->sc_intr_number,
		    USBD_SHORT_XFER_OK, &sc->sc_intr_pipe, sc, sc->sc_intr_buf,
		    sc->sc_isize, uticom_intr, UTICOM_INTR_INTERVAL);
		if (err) {
			printf("%s: cannot open interrupt pipe (addr %d)\n",
			    sc->sc_dev.dv_xname, sc->sc_intr_number);
			return (EIO);
		}
	}

	DPRINTF(("%s: uticom_open: port opened\n", sc->sc_dev.dv_xname));
	return (0);
}

static void
uticom_close(void *addr, int portno)
{
	struct uticom_softc *sc = addr;
	usb_device_request_t req;
	usbd_status err;

	if (usbd_is_dying(sc->sc_udev))
		return;

	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
	req.bRequest = UTICOM_RQ_SON;
	USETW(req.wValue, 0);
	USETW(req.wIndex, 0);
	USETW(req.wLength, 0);

	/* Try to reset UART part of chip. */
	err = usbd_do_request(sc->sc_udev, &req, NULL);
	if (err) {
		printf("%s: uticom_close: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));
		return;
	}

	DPRINTF(("%s: uticom_close: close\n", sc->sc_dev.dv_xname));

	if (sc->sc_intr_pipe != NULL) {
		usbd_abort_pipe(sc->sc_intr_pipe);
		err = usbd_close_pipe(sc->sc_intr_pipe);
		if (err)
			printf("%s: close interrupt pipe failed: %s\n",
			    sc->sc_dev.dv_xname, usbd_errstr(err));
		free(sc->sc_intr_buf, M_USBDEV, 0);
		sc->sc_intr_pipe = NULL;
	}
}

static void
uticom_intr(struct usbd_xfer *xfer, void *priv, usbd_status status)
{
	struct uticom_softc *sc = priv;
	u_char *buf = sc->sc_intr_buf;

	if (usbd_is_dying(sc->sc_udev))
		return;

	if (status != USBD_NORMAL_COMPLETION) {
		if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) {
			DPRINTF(("%s: uticom_intr: int status: %s\n",
			    sc->sc_dev.dv_xname, usbd_errstr(status)));
			return;
		}

		DPRINTF(("%s: uticom_intr: abnormal status: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(status)));
		usbd_clear_endpoint_stall_async(sc->sc_intr_pipe);
		return;
	}

	if (!xfer->actlen)
		return;

	DPRINTF(("%s: xfer_length = %d\n", sc->sc_dev.dv_xname,
	    xfer->actlen));

	sc->sc_lsr = sc->sc_msr = 0;

	if (buf[0] == 0) {
		/* msr registers */
		if (buf[1] & UTICOM_MCR_CTS)
			sc->sc_msr |= UMSR_CTS;
		if (buf[1] & UTICOM_MCR_DSR)
			sc->sc_msr |= UMSR_DSR;
		if (buf[1] & UTICOM_MCR_CD)
			sc->sc_msr |= UMSR_DCD;
		if (buf[1] & UTICOM_MCR_RI)
			sc->sc_msr |= UMSR_RI;
	} else {
		/* lsr registers */
		if (buf[0] & UTICOM_LCR_OVR)
			sc->sc_lsr |= ULSR_OE;
		if (buf[0] & UTICOM_LCR_PTE)
			sc->sc_lsr |= ULSR_PE;
		if (buf[0] & UTICOM_LCR_FRE)
			sc->sc_lsr |= ULSR_FE;
		if (buf[0] & UTICOM_LCR_BRK)
			sc->sc_lsr |= ULSR_BI;
	}

//	if (uticomstickdsr)
//		sc->sc_msr |= UMSR_DSR;

	ucom_status_change((struct ucom_softc *)sc->sc_subdev);
}

static void
uticom_get_status(void *addr, int portno, u_char *lsr, u_char *msr)
{
#if 0 /* TODO */
	struct uticom_softc *sc = addr;

	DPRINTF(("uticom_get_status:\n"));

	if (lsr != NULL)
		*lsr = sc->sc_lsr;
	if (msr != NULL)
		*msr = sc->sc_msr;
#endif
	return;
}

#if 0 /* TODO */
static int
uticom_ioctl(void *addr, int portno, u_long cmd, caddr_t data, int flag,
    usb_proc_ptr p)
{
	struct uticom_softc *sc = addr;
	int error = 0;

	if (usbd_is_dying(sc->sc_udev))
		return (EIO);

	DPRINTF(("uticom_ioctl: cmd = 0x%08lx\n", cmd));

	switch (cmd) {
	case TIOCNOTTY:
	case TIOCMGET:
	case TIOCMSET:
	case USB_GET_CM_OVER_DATA:
	case USB_SET_CM_OVER_DATA:
		break;

	default:
		DPRINTF(("uticom_ioctl: unknown\n"));
		error = ENOTTY;
		break;
	}

	return (error);
}
#endif

static int
uticom_download_fw(struct uticom_softc *sc, int pipeno,
    struct usbd_device *dev)
{
	u_char *obuf, *firmware;
	size_t firmware_size;
	int buffer_size, pos;
	uint8_t cs = 0, *buffer;
	usbd_status err;
	struct uticom_fw_header *header;
	struct usbd_xfer *oxfer = 0;
	usbd_status error = 0;
	struct usbd_pipe *pipe;

	error = loadfirmware("tusb3410", &firmware, &firmware_size);
	if (error)
		return (error);

	buffer_size = UTICOM_FW_BUFSZ + sizeof(struct uticom_fw_header);
	buffer = malloc(buffer_size, M_USBDEV, M_WAITOK | M_CANFAIL);

	if (!buffer) {
		printf("%s: uticom_download_fw: out of memory\n",
		    sc->sc_dev.dv_xname);
		free(firmware, M_DEVBUF, 0);
		return ENOMEM;
	}

	memcpy(buffer, firmware, firmware_size);
	memset(buffer + firmware_size, 0xff, buffer_size - firmware_size);

	for (pos = sizeof(struct uticom_fw_header); pos < buffer_size; pos++)
		cs = (uint8_t)(cs + buffer[pos]);

	header = (struct uticom_fw_header*)buffer;
	header->length = (uint16_t)(buffer_size -
	    sizeof(struct uticom_fw_header));
	header->checkSum = cs;

	DPRINTF(("%s: downloading firmware ...\n",
	    sc->sc_dev.dv_xname));

	err = usbd_open_pipe(sc->sc_iface, pipeno, USBD_EXCLUSIVE_USE,
	    &pipe);
	if (err) {
		printf("%s: open bulk out error (addr %d): %s\n",
		    sc->sc_dev.dv_xname, pipeno, usbd_errstr(err));
		error = EIO;
		goto finish;
	}

	oxfer = usbd_alloc_xfer(dev);
	if (oxfer == NULL) {
		error = ENOMEM;
		goto finish;
	}

	obuf = usbd_alloc_buffer(oxfer, buffer_size);
	if (obuf == NULL) {
		error = ENOMEM;
		goto finish;
	}

	memcpy(obuf, buffer, buffer_size);

	usbd_setup_xfer(oxfer, pipe, (void *)sc, obuf, buffer_size,
	    USBD_NO_COPY | USBD_SYNCHRONOUS, USBD_NO_TIMEOUT, 0);
	err = usbd_transfer(oxfer);

	if (err != USBD_NORMAL_COMPLETION)
		printf("%s: uticom_download_fw: error: %s\n",
		    sc->sc_dev.dv_xname, usbd_errstr(err));

finish:
	free(firmware, M_DEVBUF, 0);
	usbd_free_buffer(oxfer);
	usbd_free_xfer(oxfer);
	oxfer = NULL;
	usbd_abort_pipe(pipe);
	usbd_close_pipe(pipe);
	free(buffer, M_USBDEV, 0);
	return err;
}
