diff options
Diffstat (limited to 'sys/dev/usb/serial/uftdi.c')
-rw-r--r-- | sys/dev/usb/serial/uftdi.c | 784 |
1 files changed, 784 insertions, 0 deletions
diff --git a/sys/dev/usb/serial/uftdi.c b/sys/dev/usb/serial/uftdi.c new file mode 100644 index 0000000..8ff7250 --- /dev/null +++ b/sys/dev/usb/serial/uftdi.c @@ -0,0 +1,784 @@ +/* $NetBSD: uftdi.c,v 1.13 2002/09/23 05:51:23 simonb Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * NOTE: all function names beginning like "uftdi_cfg_" can only + * be called from within the config thread function ! + */ + +/* + * FTDI FT8U100AX serial adapter driver + */ + +#include "usbdevs.h" +#include <dev/usb/usb.h> +#include <dev/usb/usb_mfunc.h> +#include <dev/usb/usb_error.h> +#include <dev/usb/usb_cdc.h> + +#define USB_DEBUG_VAR uftdi_debug + +#include <dev/usb/usb_core.h> +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> +#include <dev/usb/usb_request.h> +#include <dev/usb/usb_lookup.h> +#include <dev/usb/usb_util.h> +#include <dev/usb/usb_busdma.h> + +#include <dev/usb/serial/usb_serial.h> +#include <dev/usb/serial/uftdi_reg.h> + +#if USB_DEBUG +static int uftdi_debug = 0; + +SYSCTL_NODE(_hw_usb2, OID_AUTO, uftdi, CTLFLAG_RW, 0, "USB uftdi"); +SYSCTL_INT(_hw_usb2_uftdi, OID_AUTO, debug, CTLFLAG_RW, + &uftdi_debug, 0, "Debug level"); +#endif + +#define UFTDI_CONFIG_INDEX 0 +#define UFTDI_IFACE_INDEX 0 + +#define UFTDI_IBUFSIZE 64 /* bytes, maximum number of bytes per + * frame */ +#define UFTDI_OBUFSIZE 64 /* bytes, cannot be increased due to + * do size encoding */ + +enum { + UFTDI_BULK_DT_WR, + UFTDI_BULK_DT_RD, + UFTDI_N_TRANSFER, +}; + +struct uftdi_softc { + struct usb2_com_super_softc sc_super_ucom; + struct usb2_com_softc sc_ucom; + + struct usb2_device *sc_udev; + struct usb2_xfer *sc_xfer[UFTDI_N_TRANSFER]; + device_t sc_dev; + + uint32_t sc_unit; + enum uftdi_type sc_type; + + uint16_t sc_last_lcr; + + uint8_t sc_iface_index; + uint8_t sc_hdrlen; + uint8_t sc_msr; + uint8_t sc_lsr; + + uint8_t sc_name[16]; +}; + +struct uftdi_param_config { + uint16_t rate; + uint16_t lcr; + uint8_t v_start; + uint8_t v_stop; + uint8_t v_flow; +}; + +/* prototypes */ + +static device_probe_t uftdi_probe; +static device_attach_t uftdi_attach; +static device_detach_t uftdi_detach; + +static usb2_callback_t uftdi_write_callback; +static usb2_callback_t uftdi_read_callback; + +static void uftdi_cfg_open(struct usb2_com_softc *); +static void uftdi_cfg_set_dtr(struct usb2_com_softc *, uint8_t); +static void uftdi_cfg_set_rts(struct usb2_com_softc *, uint8_t); +static void uftdi_cfg_set_break(struct usb2_com_softc *, uint8_t); +static int uftdi_set_parm_soft(struct termios *, + struct uftdi_param_config *, uint8_t); +static int uftdi_pre_param(struct usb2_com_softc *, struct termios *); +static void uftdi_cfg_param(struct usb2_com_softc *, struct termios *); +static void uftdi_cfg_get_status(struct usb2_com_softc *, uint8_t *, + uint8_t *); +static void uftdi_start_read(struct usb2_com_softc *); +static void uftdi_stop_read(struct usb2_com_softc *); +static void uftdi_start_write(struct usb2_com_softc *); +static void uftdi_stop_write(struct usb2_com_softc *); +static uint8_t uftdi_8u232am_getrate(uint32_t, uint16_t *); + +static const struct usb2_config uftdi_config[UFTDI_N_TRANSFER] = { + + [UFTDI_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .mh.bufsize = UFTDI_OBUFSIZE, + .mh.flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .mh.callback = &uftdi_write_callback, + }, + + [UFTDI_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .mh.bufsize = UFTDI_IBUFSIZE, + .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .mh.callback = &uftdi_read_callback, + }, +}; + +static const struct usb2_com_callback uftdi_callback = { + .usb2_com_cfg_get_status = &uftdi_cfg_get_status, + .usb2_com_cfg_set_dtr = &uftdi_cfg_set_dtr, + .usb2_com_cfg_set_rts = &uftdi_cfg_set_rts, + .usb2_com_cfg_set_break = &uftdi_cfg_set_break, + .usb2_com_cfg_param = &uftdi_cfg_param, + .usb2_com_cfg_open = &uftdi_cfg_open, + .usb2_com_pre_param = &uftdi_pre_param, + .usb2_com_start_read = &uftdi_start_read, + .usb2_com_stop_read = &uftdi_stop_read, + .usb2_com_start_write = &uftdi_start_write, + .usb2_com_stop_write = &uftdi_stop_write, +}; + +static device_method_t uftdi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uftdi_probe), + DEVMETHOD(device_attach, uftdi_attach), + DEVMETHOD(device_detach, uftdi_detach), + + {0, 0} +}; + +static devclass_t uftdi_devclass; + +static driver_t uftdi_driver = { + .name = "uftdi", + .methods = uftdi_methods, + .size = sizeof(struct uftdi_softc), +}; + +DRIVER_MODULE(uftdi, ushub, uftdi_driver, uftdi_devclass, NULL, 0); +MODULE_DEPEND(uftdi, ucom, 1, 1, 1); +MODULE_DEPEND(uftdi, usb, 1, 1, 1); + +static struct usb2_device_id uftdi_devs[] = { + {USB_VPI(USB_VENDOR_DRESDENELEKTRONIK, USB_PRODUCT_DRESDENELEKTRONIK_SENSORTERMINALBOARD, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SERIAL_8U100AX, UFTDI_TYPE_SIO)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SERIAL_2232C, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SERIAL_8U232AM, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_SEMC_DSS20, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_631, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_632, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_633, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_634, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_CFA_635, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_USBSERIAL, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_MX2_3, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_MX4_5, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_LK202, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_LK204, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13M, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13S, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13U, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_EISCOU, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_UOPTBR, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_EMCU2D, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_PCMSFU, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_EMCU2H, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_FTDI, USB_PRODUCT_FTDI_MAXSTREAM, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_SIIG2, USB_PRODUCT_SIIG2_US2308, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_INTREPIDCS, USB_PRODUCT_INTREPIDCS_VALUECAN, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_INTREPIDCS, USB_PRODUCT_INTREPIDCS_NEOVI, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_BBELECTRONICS, USB_PRODUCT_BBELECTRONICS_USOTL4, UFTDI_TYPE_8U232AM)}, + {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_PCOPRS1, UFTDI_TYPE_8U232AM)}, +}; + +static int +uftdi_probe(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb2_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UFTDI_CONFIG_INDEX) { + return (ENXIO); + } + /* attach to all present interfaces */ + + return (usb2_lookup_id_by_uaa(uftdi_devs, sizeof(uftdi_devs), uaa)); +} + +static int +uftdi_attach(device_t dev) +{ + struct usb2_attach_arg *uaa = device_get_ivars(dev); + struct uftdi_softc *sc = device_get_softc(dev); + int error; + + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + + device_set_usb2_desc(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + DPRINTF("\n"); + + sc->sc_iface_index = uaa->info.bIfaceIndex; + sc->sc_type = USB_GET_DRIVER_INFO(uaa); + + switch (sc->sc_type) { + case UFTDI_TYPE_SIO: + sc->sc_hdrlen = 1; + break; + case UFTDI_TYPE_8U232AM: + default: + sc->sc_hdrlen = 0; + break; + } + + error = usb2_transfer_setup(uaa->device, + &sc->sc_iface_index, sc->sc_xfer, uftdi_config, + UFTDI_N_TRANSFER, sc, &Giant); + + if (error) { + device_printf(dev, "allocating USB " + "transfers failed!\n"); + goto detach; + } + sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; + + /* clear stall at first run */ + usb2_transfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_WR]); + usb2_transfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_RD]); + + /* set a valid "lcr" value */ + + sc->sc_last_lcr = + (FTDI_SIO_SET_DATA_STOP_BITS_2 | + FTDI_SIO_SET_DATA_PARITY_NONE | + FTDI_SIO_SET_DATA_BITS(8)); + + error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uftdi_callback, &Giant); + if (error) { + goto detach; + } + return (0); /* success */ + +detach: + uftdi_detach(dev); + return (ENXIO); +} + +static int +uftdi_detach(device_t dev) +{ + struct uftdi_softc *sc = device_get_softc(dev); + + usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1); + + usb2_transfer_unsetup(sc->sc_xfer, UFTDI_N_TRANSFER); + + return (0); +} + +static void +uftdi_cfg_open(struct usb2_com_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + struct usb2_device_request req; + + DPRINTF(""); + + /* perform a full reset on the device */ + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_RESET; + USETW(req.wValue, FTDI_SIO_RESET_SIO); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + usb2_com_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + + /* turn on RTS/CTS flow control */ + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_FLOW_CTRL; + USETW(req.wValue, 0); + USETW2(req.wIndex, FTDI_SIO_RTS_CTS_HS, wIndex); + USETW(req.wLength, 0); + usb2_com_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + + /* + * NOTE: with the new UCOM layer there will always be a + * "uftdi_cfg_param()" call after "open()", so there is no need for + * "open()" to configure anything + */ +} + +static void +uftdi_write_callback(struct usb2_xfer *xfer) +{ + struct uftdi_softc *sc = xfer->priv_sc; + uint32_t actlen; + uint8_t buf[1]; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + if (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers, + sc->sc_hdrlen, UFTDI_OBUFSIZE - sc->sc_hdrlen, + &actlen)) { + + if (sc->sc_hdrlen > 0) { + buf[0] = + FTDI_OUT_TAG(actlen, sc->sc_ucom.sc_portno); + usb2_copy_in(xfer->frbuffers, 0, buf, 1); + } + xfer->frlengths[0] = actlen + sc->sc_hdrlen; + usb2_start_hardware(xfer); + } + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + return; + } +} + +static void +uftdi_read_callback(struct usb2_xfer *xfer) +{ + struct uftdi_softc *sc = xfer->priv_sc; + uint8_t buf[2]; + uint8_t ftdi_msr; + uint8_t msr; + uint8_t lsr; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (xfer->actlen < 2) { + goto tr_setup; + } + usb2_copy_out(xfer->frbuffers, 0, buf, 2); + + ftdi_msr = FTDI_GET_MSR(buf); + lsr = FTDI_GET_LSR(buf); + + msr = 0; + if (ftdi_msr & FTDI_SIO_CTS_MASK) + msr |= SER_CTS; + if (ftdi_msr & FTDI_SIO_DSR_MASK) + msr |= SER_DSR; + if (ftdi_msr & FTDI_SIO_RI_MASK) + msr |= SER_RI; + if (ftdi_msr & FTDI_SIO_RLSD_MASK) + msr |= SER_DCD; + + if ((sc->sc_msr != msr) || + ((sc->sc_lsr & FTDI_LSR_MASK) != (lsr & FTDI_LSR_MASK))) { + DPRINTF("status change msr=0x%02x (0x%02x) " + "lsr=0x%02x (0x%02x)\n", msr, sc->sc_msr, + lsr, sc->sc_lsr); + + sc->sc_msr = msr; + sc->sc_lsr = lsr; + + usb2_com_status_change(&sc->sc_ucom); + } + xfer->actlen -= 2; + + if (xfer->actlen > 0) { + usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers, 2, + xfer->actlen); + } + case USB_ST_SETUP: +tr_setup: + xfer->frlengths[0] = xfer->max_data_length; + usb2_start_hardware(xfer); + return; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + xfer->flags.stall_pipe = 1; + goto tr_setup; + } + return; + } +} + +static void +uftdi_cfg_set_dtr(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb2_device_request req; + + wValue = onoff ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_MODEM_CTRL; + USETW(req.wValue, wValue); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + usb2_com_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uftdi_cfg_set_rts(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb2_device_request req; + + wValue = onoff ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_MODEM_CTRL; + USETW(req.wValue, wValue); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + usb2_com_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uftdi_cfg_set_break(struct usb2_com_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb2_device_request req; + + if (onoff) { + sc->sc_last_lcr |= FTDI_SIO_SET_BREAK; + } else { + sc->sc_last_lcr &= ~FTDI_SIO_SET_BREAK; + } + + wValue = sc->sc_last_lcr; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_DATA; + USETW(req.wValue, wValue); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + usb2_com_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static int +uftdi_set_parm_soft(struct termios *t, + struct uftdi_param_config *cfg, uint8_t type) +{ + bzero(cfg, sizeof(*cfg)); + + switch (type) { + case UFTDI_TYPE_SIO: + switch (t->c_ospeed) { + case 300: + cfg->rate = ftdi_sio_b300; + break; + case 600: + cfg->rate = ftdi_sio_b600; + break; + case 1200: + cfg->rate = ftdi_sio_b1200; + break; + case 2400: + cfg->rate = ftdi_sio_b2400; + break; + case 4800: + cfg->rate = ftdi_sio_b4800; + break; + case 9600: + cfg->rate = ftdi_sio_b9600; + break; + case 19200: + cfg->rate = ftdi_sio_b19200; + break; + case 38400: + cfg->rate = ftdi_sio_b38400; + break; + case 57600: + cfg->rate = ftdi_sio_b57600; + break; + case 115200: + cfg->rate = ftdi_sio_b115200; + break; + default: + return (EINVAL); + } + break; + + case UFTDI_TYPE_8U232AM: + if (uftdi_8u232am_getrate(t->c_ospeed, &cfg->rate)) { + return (EINVAL); + } + break; + } + + if (t->c_cflag & CSTOPB) + cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_2; + else + cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_1; + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_ODD; + } else { + cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_EVEN; + } + } else { + cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(5); + break; + + case CS6: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(6); + break; + + case CS7: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(7); + break; + + case CS8: + cfg->lcr |= FTDI_SIO_SET_DATA_BITS(8); + break; + } + + if (t->c_cflag & CRTSCTS) { + cfg->v_flow = FTDI_SIO_RTS_CTS_HS; + } else if (t->c_iflag & (IXON | IXOFF)) { + cfg->v_flow = FTDI_SIO_XON_XOFF_HS; + cfg->v_start = t->c_cc[VSTART]; + cfg->v_stop = t->c_cc[VSTOP]; + } else { + cfg->v_flow = FTDI_SIO_DISABLE_FLOW_CTRL; + } + + return (0); +} + +static int +uftdi_pre_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct uftdi_softc *sc = ucom->sc_parent; + struct uftdi_param_config cfg; + + DPRINTF("\n"); + + return (uftdi_set_parm_soft(t, &cfg, sc->sc_type)); +} + +static void +uftdi_cfg_param(struct usb2_com_softc *ucom, struct termios *t) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + struct uftdi_param_config cfg; + struct usb2_device_request req; + + if (uftdi_set_parm_soft(t, &cfg, sc->sc_type)) { + /* should not happen */ + return; + } + sc->sc_last_lcr = cfg.lcr; + + DPRINTF("\n"); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_BAUD_RATE; + USETW(req.wValue, cfg.rate); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + usb2_com_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_DATA; + USETW(req.wValue, cfg.lcr); + USETW(req.wIndex, wIndex); + USETW(req.wLength, 0); + usb2_com_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_FLOW_CTRL; + USETW2(req.wValue, cfg.v_stop, cfg.v_start); + USETW2(req.wIndex, cfg.v_flow, wIndex); + USETW(req.wLength, 0); + usb2_com_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uftdi_cfg_get_status(struct usb2_com_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + DPRINTF("msr=0x%02x lsr=0x%02x\n", + sc->sc_msr, sc->sc_lsr); + + *msr = sc->sc_msr; + *lsr = sc->sc_lsr; +} + +static void +uftdi_start_read(struct usb2_com_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_RD]); +} + +static void +uftdi_stop_read(struct usb2_com_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_RD]); +} + +static void +uftdi_start_write(struct usb2_com_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usb2_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_WR]); +} + +static void +uftdi_stop_write(struct usb2_com_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usb2_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_WR]); +} + +/*------------------------------------------------------------------------* + * uftdi_8u232am_getrate + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +uftdi_8u232am_getrate(uint32_t speed, uint16_t *rate) +{ + /* Table of the nearest even powers-of-2 for values 0..15. */ + static const uint8_t roundoff[16] = { + 0, 2, 2, 4, 4, 4, 8, 8, + 8, 8, 8, 8, 16, 16, 16, 16, + }; + uint32_t d; + uint32_t freq; + uint16_t result; + + if ((speed < 178) || (speed > ((3000000 * 100) / 97))) + return (1); /* prevent numerical overflow */ + + /* Special cases for 2M and 3M. */ + if ((speed >= ((3000000 * 100) / 103)) && + (speed <= ((3000000 * 100) / 97))) { + result = 0; + goto done; + } + if ((speed >= ((2000000 * 100) / 103)) && + (speed <= ((2000000 * 100) / 97))) { + result = 1; + goto done; + } + d = (FTDI_8U232AM_FREQ << 4) / speed; + d = (d & ~15) + roundoff[d & 15]; + + if (d < FTDI_8U232AM_MIN_DIV) + d = FTDI_8U232AM_MIN_DIV; + else if (d > FTDI_8U232AM_MAX_DIV) + d = FTDI_8U232AM_MAX_DIV; + + /* + * Calculate the frequency needed for "d" to exactly divide down to + * our target "speed", and check that the actual frequency is within + * 3% of this. + */ + freq = (speed * d); + if ((freq < ((FTDI_8U232AM_FREQ * 1600ULL) / 103)) || + (freq > ((FTDI_8U232AM_FREQ * 1600ULL) / 97))) + return (1); + + /* + * Pack the divisor into the resultant value. The lower 14-bits + * hold the integral part, while the upper 2 bits encode the + * fractional component: either 0, 0.5, 0.25, or 0.125. + */ + result = (d >> 4); + if (d & 8) + result |= 0x4000; + else if (d & 4) + result |= 0x8000; + else if (d & 2) + result |= 0xc000; + +done: + *rate = result; + return (0); +} |