Device driver for the Cypress CY7C637xx and CY7C640/1xx families of USB

to RS232 bridges, such as the one found in the DeLorme Earthmate USB GPS
receiver (which is the only device currently supported by this driver).

While other USB to serial drivers in the tree rely heavily on ucom, this
one is self-contained.  The reason for that is that ucom assumes that
the bridge uses bulk pipes for I/O, while the Cypress parts actually
register as human interface devices and use HID reports for configuration
and I/O.

The driver is not entirely complete: there is no support yet for flow
control, and output doesn't seem to work, though I don't know if that is
because of a bug in the code, or simply because the Earthmate is a read-
only device.
This commit is contained in:
Dag-Erling Smørgrav 2004-09-05 09:43:47 +00:00
parent 597451b50f
commit 8985c52bb6
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=134804
4 changed files with 645 additions and 0 deletions

View File

@ -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

634
sys/dev/usb/ucycom.c Normal file
View File

@ -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;
}

View File

@ -215,6 +215,7 @@ SUBDIR= ${_3dfx} \
ubsec \
ubser \
ucom \
ucycom \
udav \
udbp \
udf \

View File

@ -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>