diff options
Diffstat (limited to 'sys')
-rw-r--r-- | sys/conf/files | 1 | ||||
-rw-r--r-- | sys/dev/usb/ucycom.c | 634 | ||||
-rw-r--r-- | sys/modules/Makefile | 1 | ||||
-rw-r--r-- | sys/modules/ucycom/Makefile | 9 |
4 files changed, 645 insertions, 0 deletions
diff --git a/sys/conf/files b/sys/conf/files index cea8f88..0e20689 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -800,6 +800,7 @@ dev/usb/ohci_pci.c optional ohci pci dev/usb/ubsa.c optional ubsa ucom dev/usb/ubser.c optional ubser dev/usb/ucom.c optional ucom +dev/usb/ucycom.c optional ucycom ucom dev/usb/udbp.c optional udbp dev/usb/ufm.c optional ufm dev/usb/uftdi.c optional uftdi ucom diff --git a/sys/dev/usb/ucycom.c b/sys/dev/usb/ucycom.c new file mode 100644 index 0000000..ee05ae4 --- /dev/null +++ b/sys/dev/usb/ucycom.c @@ -0,0 +1,634 @@ +/*- + * Copyright (c) 2004 Dag-Erling Coïdan Smørgrav + * All rights reserved. + * + * 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 + * in this position and unchanged. + * 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. + * + * $FreeBSD$ + */ + +/* + * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to + * RS232 bridges. + * + * Normally, a driver for a USB-to-serial chip would hang off the ucom(4) + * driver, but ucom(4) was written under the assumption that all USB-to- + * serial chips use bulk pipes for I/O, while the Cypress parts use HID + * reports. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/sysctl.h> +#include <sys/bus.h> +#include <sys/tty.h> + +#include "usbdevs.h" +#include <dev/usb/usb.h> +#include <dev/usb/usb_port.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbhid.h> +#include <dev/usb/hid.h> + +#define UCYCOM_EP_INPUT 0 +#define UCYCOM_EP_OUTPUT 1 + +#define UCYCOM_MAX_IOLEN 32 + +struct ucycom_softc { + device_t sc_dev; + struct cdev *sc_cdev; + struct tty *sc_tty; + int sc_error; + unsigned long sc_cintr; + unsigned long sc_cin; + unsigned long sc_clost; + unsigned long sc_cout; + + /* usb parameters */ + usbd_device_handle sc_usbdev; + usbd_interface_handle sc_iface; + usbd_pipe_handle sc_pipe; + uint8_t sc_iep; /* input endpoint */ + uint8_t sc_fid; /* feature report id*/ + uint8_t sc_iid; /* input report id */ + uint8_t sc_oid; /* output report id */ + size_t sc_flen; /* feature report length */ + size_t sc_ilen; /* input report length */ + size_t sc_olen; /* output report length */ + uint8_t sc_ibuf[UCYCOM_MAX_IOLEN]; + + /* model and settings */ + uint32_t sc_model; +#define MODEL_CY7C63743 0x63743 +#define MODEL_CY7C64013 0x64013 + uint32_t sc_baud; + uint8_t sc_cfg; +#define UCYCOM_CFG_RESET 0x80 +#define UCYCOM_CFG_PARODD 0x20 +#define UCYCOM_CFG_PAREN 0x10 +#define UCYCOM_CFG_STOPB 0x08 +#define UCYCOM_CFG_DATAB 0x03 + uint8_t sc_ist; /* status flags from last input */ + uint8_t sc_ost; /* status flags for next output */ + + /* flags */ + char sc_open; + char sc_dying; +}; + +static int ucycom_probe(device_t); +static int ucycom_attach(device_t); +static int ucycom_detach(device_t); +static int ucycom_open(struct cdev *, int, int, struct thread *); +static int ucycom_close(struct cdev *, int, int, struct thread *); +static int ucycom_ioctl(struct cdev *, unsigned long, caddr_t, int, struct thread *); +static int ucycom_read(struct cdev *, struct uio *, int); +static int ucycom_write(struct cdev *, struct uio *, int); +static void ucycom_start(struct tty *); +static void ucycom_stop(struct tty *, int); +static int ucycom_param(struct tty *, struct termios *); +static int ucycom_configure(struct ucycom_softc *, uint32_t, uint8_t); +static void ucycom_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); + +static struct cdevsw ucycom_cdevsw = { + .d_version = D_VERSION, + .d_open = ucycom_open, + .d_close = ucycom_close, + .d_ioctl = ucycom_ioctl, + .d_read = ucycom_read, + .d_write = ucycom_write, + .d_name = "ucycom", + .d_flags = D_TTY | D_NEEDGIANT, +}; + +static device_method_t ucycom_methods[] = { + DEVMETHOD(device_probe, ucycom_probe), + DEVMETHOD(device_attach, ucycom_attach), + DEVMETHOD(device_detach, ucycom_detach), + { 0, 0 } +}; + +static driver_t ucycom_driver = { + "ucycom", + ucycom_methods, + sizeof(struct ucycom_softc), +}; + +static devclass_t ucycom_devclass; + +DRIVER_MODULE(ucycom, uhub, ucycom_driver, ucycom_devclass, usbd_driver_load, 0); +MODULE_VERSION(ucycom, 1); +MODULE_DEPEND(ucycom, usb, 1, 1, 1); + +/* + * Supported devices + */ + +static struct ucycom_device { + uint16_t vendor; + uint16_t product; + uint32_t model; +} ucycom_devices[] = { + { USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013 }, + { 0, 0, 0 }, +}; + +#define UCYCOM_DEFAULT_RATE 4800 +#define UCYCOM_DEFAULT_CFG 0x03 /* N-8-1 */ + +/***************************************************************************** + * + * Driver interface + * + */ + +static int +ucycom_probe(device_t dev) +{ + struct usb_attach_arg *uaa; + struct ucycom_device *ud; + + uaa = device_get_ivars(dev); + if (uaa->iface != NULL) + return (UMATCH_NONE); + for (ud = ucycom_devices; ud->model != 0; ++ud) + if (ud->vendor == uaa->vendor && ud->product == uaa->product) + return (UMATCH_VENDOR_PRODUCT); + return (UMATCH_NONE); +} + +static int +ucycom_attach(device_t dev) +{ + struct usb_attach_arg *uaa; + struct ucycom_softc *sc; + struct ucycom_device *ud; + usb_endpoint_descriptor_t *ued; + char *devinfo; + void *urd; + int error, urdlen; + + /* get arguments and softc */ + uaa = device_get_ivars(dev); + sc = device_get_softc(dev); + bzero(sc, sizeof *sc); + sc->sc_dev = dev; + sc->sc_usbdev = uaa->device; + + /* get device description */ + /* XXX usb_devinfo() has little or no overflow protection */ + devinfo = malloc(1024, M_USBDEV, M_WAITOK); + usbd_devinfo(sc->sc_usbdev, 0, devinfo); + device_set_desc_copy(dev, devinfo); + device_printf(dev, "%s\n", devinfo); + free(devinfo, M_USBDEV); + + /* get chip model */ + for (ud = ucycom_devices; ud->model != 0; ++ud) + if (ud->vendor == uaa->vendor && ud->product == uaa->product) + sc->sc_model = ud->model; + if (sc->sc_model == 0) { + device_printf(dev, "unsupported device\n"); + return (ENXIO); + } + device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model); + + /* select configuration */ + error = usbd_set_config_index(sc->sc_usbdev, 0, 1 /* verbose */); + if (error != 0) { + device_printf(dev, "failed to select configuration: %s\n", + usbd_errstr(error)); + return (ENXIO); + } + + /* get first interface handle */ + error = usbd_device2interface_handle(sc->sc_usbdev, 0, &sc->sc_iface); + if (error != 0) { + device_printf(dev, "failed to get interface handle: %s\n", + usbd_errstr(error)); + return (ENXIO); + } + + /* get report descriptor */ + error = usbd_read_report_desc(sc->sc_iface, &urd, &urdlen, M_USBDEV); + if (error != 0) { + device_printf(dev, "failed to get report descriptor: %s\n", + usbd_errstr(error)); + return (ENXIO); + } + + /* get report sizes */ + sc->sc_flen = hid_report_size(urd, urdlen, hid_feature, &sc->sc_fid); + sc->sc_ilen = hid_report_size(urd, urdlen, hid_input, &sc->sc_iid); + sc->sc_olen = hid_report_size(urd, urdlen, hid_output, &sc->sc_oid); + + if (sc->sc_ilen > UCYCOM_MAX_IOLEN || sc->sc_olen > UCYCOM_MAX_IOLEN) { + device_printf(dev, "I/O report size too big (%d, %d, %d)\n", + sc->sc_ilen, sc->sc_olen, UCYCOM_MAX_IOLEN); + return (ENXIO); + } + + /* get and verify input endpoint descriptor */ + ued = usbd_interface2endpoint_descriptor(sc->sc_iface, UCYCOM_EP_INPUT); + if (ued == NULL) { + device_printf(dev, "failed to get input endpoint descriptor\n"); + return (ENXIO); + } + if (UE_GET_DIR(ued->bEndpointAddress) != UE_DIR_IN) { + device_printf(dev, "not an input endpoint\n"); + return (ENXIO); + } + if ((ued->bmAttributes & UE_XFERTYPE) != UE_INTERRUPT) { + device_printf(dev, "not an interrupt endpoint\n"); + return (ENXIO); + } + sc->sc_iep = ued->bEndpointAddress; + + /* set up tty */ + sc->sc_tty = ttymalloc(sc->sc_tty); + sc->sc_tty->t_sc = sc; + sc->sc_tty->t_oproc = ucycom_start; + sc->sc_tty->t_stop = ucycom_stop; + sc->sc_tty->t_param = ucycom_param; + + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "intr", CTLFLAG_RD, &sc->sc_cintr, 0, + "interrupt count"); + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "in", CTLFLAG_RD, &sc->sc_cin, 0, + "input bytes read"); + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "lost", CTLFLAG_RD, &sc->sc_clost, 0, + "input bytes lost"); + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "out", CTLFLAG_RD, &sc->sc_cout, 0, + "output bytes"); + + /* create character device node */ + sc->sc_cdev = make_dev(&ucycom_cdevsw, device_get_unit(sc->sc_dev), + UID_ROOT, GID_WHEEL, 0640, "%s", device_get_nameunit(sc->sc_dev)); + sc->sc_cdev->si_drv1 = sc; + + return (0); +} + +static int +ucycom_detach(device_t dev) +{ + struct ucycom_softc *sc; + + sc = device_get_softc(dev); + + destroy_dev(sc->sc_cdev); + + return (0); +} + +/***************************************************************************** + * + * Device interface + * + */ + +static int +ucycom_open(struct cdev *cdev, int flags, int type, struct thread *td) +{ + struct ucycom_softc *sc = cdev->si_drv1; + int error; + + if (sc->sc_open != 0) + return (EBUSY); + + /* set default configuration */ + ucycom_configure(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG); + + /* open tty and line discipline */ + error = tty_open(cdev, sc->sc_tty); + if (error != 0) + return (error); + error = ttyld_open(sc->sc_tty, cdev); + if (error != 0) { + tty_close(sc->sc_tty); + return (error); + } + sc->sc_cdev->si_tty = sc->sc_tty; + + /* open interrupt pipe */ + error = usbd_open_pipe_intr(sc->sc_iface, sc->sc_iep, 0, + &sc->sc_pipe, sc, sc->sc_ibuf, sc->sc_ilen, + ucycom_intr, USBD_DEFAULT_INTERVAL); + if (error != 0) { + device_printf(sc->sc_dev, "failed to open interrupt pipe: %s\n", + usbd_errstr(error)); + ttyld_close(sc->sc_tty, 0); + tty_close(sc->sc_tty); + return (ENXIO); + } + + if (bootverbose) + device_printf(sc->sc_dev, "%s bypass l_rint()\n", + (sc->sc_tty->t_state & TS_CAN_BYPASS_L_RINT) ? + "can" : "can't"); + + /* done! */ + sc->sc_open = 1; + return (0); +} + +static int +ucycom_close(struct cdev *cdev, int flags, int type, struct thread *td) +{ + struct ucycom_softc *sc = cdev->si_drv1; + + if (sc->sc_open == 0) + return (0); + + /* stop interrupts and close the interrupt pipe */ + usbd_abort_pipe(sc->sc_pipe); + usbd_close_pipe(sc->sc_pipe); + sc->sc_pipe = 0; + + /* close line discipline and tty */ + ttyld_close(sc->sc_tty, flags); + tty_close(sc->sc_tty); + + /* done! */ + sc->sc_open = 0; + return (0); +} + +static int +ucycom_ioctl(struct cdev *cdev, unsigned long cmd, caddr_t data, + int flags, struct thread *td) +{ + struct ucycom_softc *sc = cdev->si_drv1; + int error; + + (void)sc; + error = ttyioctl(cdev, cmd, data, flags, td); + + return (error); +} + +static int +ucycom_read(struct cdev *cdev, struct uio *uio, int flags) +{ + struct ucycom_softc *sc = cdev->si_drv1; + int error; + + if (sc->sc_error) + return (EIO); + + error = ttyld_read(sc->sc_tty, uio, flags); + return (error); +} + +static int +ucycom_write(struct cdev *cdev, struct uio *uio, int flags) +{ + struct ucycom_softc *sc = cdev->si_drv1; + int error; + + if (sc->sc_error) + return (EIO); + + error = ttyld_write(sc->sc_tty, uio, flags); + return (error); +} + +/***************************************************************************** + * + * TTY interface + * + */ + +static void +ucycom_start(struct tty *tty) +{ + struct ucycom_softc *sc = tty->t_sc; + uint8_t report[sc->sc_olen]; + int error, len; + + while (sc->sc_error == 0 && sc->sc_tty->t_outq.c_cc > 0) { + switch (sc->sc_model) { + case MODEL_CY7C63743: + len = q_to_b(&sc->sc_tty->t_outq, + report + 1, sc->sc_olen - 1); + sc->sc_cout += len; + report[0] = len; + len += 1; + break; + case MODEL_CY7C64013: + len = q_to_b(&sc->sc_tty->t_outq, + report + 2, sc->sc_olen - 2); + sc->sc_cout += len; + report[0] = 0; + report[1] = len; + len += 2; + break; + default: + panic("unsupported model (driver error)"); + } + + while (len < sc->sc_olen) + report[len++] = 0; + error = usbd_set_report(sc->sc_iface, UHID_OUTPUT_REPORT, + sc->sc_oid, report, sc->sc_olen); +#if 0 + if (error != 0) { + device_printf(sc->sc_dev, + "failed to set output report: %s\n", + usbd_errstr(error)); + sc->sc_error = error; + } +#endif + } +} + +static void +ucycom_stop(struct tty *tty, int flags) +{ + struct ucycom_softc *sc; + + sc = tty->t_sc; + if (bootverbose) + device_printf(sc->sc_dev, "%s()\n", __func__); +} + +static int +ucycom_param(struct tty *tty, struct termios *t) +{ + struct ucycom_softc *sc; + uint32_t baud; + uint8_t cfg; + int error; + + sc = tty->t_sc; + + if (t->c_ispeed != t->c_ospeed) + return (EINVAL); + baud = t->c_ispeed; + + if (t->c_cflag & CIGNORE) { + cfg = sc->sc_cfg; + } else { + cfg = 0; + switch (t->c_cflag & CSIZE) { + case CS8: + ++cfg; + case CS7: + ++cfg; + case CS6: + ++cfg; + case CS5: + break; + default: + return (EINVAL); + } + if (t->c_cflag & CSTOPB) + cfg |= UCYCOM_CFG_STOPB; + if (t->c_cflag & PARENB) + cfg |= UCYCOM_CFG_PAREN; + if (t->c_cflag & PARODD) + cfg |= UCYCOM_CFG_PARODD; + } + + error = ucycom_configure(sc, baud, cfg); + return (error); +} + +/***************************************************************************** + * + * Hardware interface + * + */ + +static int +ucycom_configure(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg) +{ + uint8_t report[sc->sc_flen]; + int error; + + switch (baud) { + case 600: + case 1200: + case 2400: + case 4800: + case 9600: + case 19200: + case 38400: + case 57600: +#if 0 + /* + * Stock chips only support standard baud rates in the 600 - 57600 + * range, but higher rates can be achieved using custom firmware. + */ + case 115200: + case 153600: + case 192000: +#endif + break; + default: + return (EINVAL); + } + + if (bootverbose) + device_printf(sc->sc_dev, "%d baud, %c-%d-%d\n", baud, + (cfg & UCYCOM_CFG_PAREN) ? + ((cfg & UCYCOM_CFG_PARODD) ? 'O' : 'E') : 'N', + 5 + (cfg & UCYCOM_CFG_DATAB), + (cfg & UCYCOM_CFG_STOPB) ? 2 : 1); + report[0] = baud & 0xff; + report[1] = (baud >> 8) & 0xff; + report[2] = (baud >> 16) & 0xff; + report[3] = (baud >> 24) & 0xff; + report[4] = cfg; + error = usbd_set_report(sc->sc_iface, UHID_FEATURE_REPORT, + sc->sc_fid, report, sc->sc_flen); + if (error != 0) { + device_printf(sc->sc_dev, "%s\n", usbd_errstr(error)); + return (EIO); + } + sc->sc_baud = baud; + sc->sc_cfg = cfg; + return (0); +} + +static void +ucycom_intr(usbd_xfer_handle xfer, usbd_private_handle scp, usbd_status status) +{ + struct ucycom_softc *sc = scp; + uint8_t *data; + int i, len, lost; + + sc->sc_cintr++; + + switch (sc->sc_model) { + case MODEL_CY7C63743: + sc->sc_ist = sc->sc_ibuf[0] & ~0x07; + len = sc->sc_ibuf[0] & 0x07; + data = sc->sc_ibuf + 1; + break; + case MODEL_CY7C64013: + sc->sc_ist = sc->sc_ibuf[0] & ~0x07; + len = sc->sc_ibuf[1]; + data = sc->sc_ibuf + 2; + break; + default: + panic("unsupported model (driver error)"); + } + + switch (status) { + case USBD_NORMAL_COMPLETION: + break; + default: + /* XXX */ + return; + } + + if (sc->sc_tty->t_state & TS_CAN_BYPASS_L_RINT) { + /* XXX flow control! */ + lost = b_to_q(data, len, &sc->sc_tty->t_rawq); + sc->sc_tty->t_rawcc += len - lost; + ttwakeup(sc->sc_tty); + } else { + for (i = 0, lost = len; i < len; ++i, --lost) + if (ttyld_rint(sc->sc_tty, data[i]) != 0) + break; + } + sc->sc_cin += len - lost; + sc->sc_clost += lost; +} diff --git a/sys/modules/Makefile b/sys/modules/Makefile index a502b5c..207ca28 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -215,6 +215,7 @@ SUBDIR= ${_3dfx} \ ubsec \ ubser \ ucom \ + ucycom \ udav \ udbp \ udf \ diff --git a/sys/modules/ucycom/Makefile b/sys/modules/ucycom/Makefile new file mode 100644 index 0000000..7d7fc88 --- /dev/null +++ b/sys/modules/ucycom/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +S= ${.CURDIR}/../.. +.PATH: $S/dev/usb + +KMOD= ucycom +SRCS= ucycom.c opt_usb.h device_if.h bus_if.h vnode_if.h usbdevs.h + +.include <bsd.kmod.mk> |