diff options
author | Kevin Kirspel <kevin-kirspel@idexx.com> | 2017-05-12 08:16:22 -0400 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2017-05-12 14:18:38 +0200 |
commit | 286c391e39287b0615462de4d6c4ec9e183e99cc (patch) | |
tree | 2e4a928725de817cf47b4d5aaea4f11e1886b8f4 /freebsd/sys/dev/usb | |
parent | Updating scripts for FREEBSD TTY support (diff) | |
download | rtems-libbsd-286c391e39287b0615462de4d6c4ec9e183e99cc.tar.bz2 |
Adding FREEBSD USB Serial Drivers
Diffstat (limited to 'freebsd/sys/dev/usb')
23 files changed, 20104 insertions, 0 deletions
diff --git a/freebsd/sys/dev/usb/serial/u3g.c b/freebsd/sys/dev/usb/serial/u3g.c new file mode 100644 index 00000000..2f3289f0 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/u3g.c @@ -0,0 +1,1234 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* + * Copyright (c) 2008 AnyWi Technologies + * Author: Andrea Guzzo <aguzzo@anywi.com> + * * based on uark.c 1.1 2006/08/14 08:30:22 jsg * + * * parts from ubsa.c 183348 2008-09-25 12:00:56Z phk * + * + * 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. + * + * $FreeBSD$ + */ + +/* + * NOTE: + * + * - The detour through the tty layer is ridiculously expensive wrt + * buffering due to the high speeds. + * + * We should consider adding a simple r/w device which allows + * attaching of PPP in a more efficient way. + * + */ + + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_cdc.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR u3g_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> +#include <dev/usb/usb_msctest.h> + +#include <dev/usb/serial/usb_serial.h> +#include <dev/usb/quirk/usb_quirk.h> + +#ifdef USB_DEBUG +static int u3g_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, u3g, CTLFLAG_RW, 0, "USB 3g"); +SYSCTL_INT(_hw_usb_u3g, OID_AUTO, debug, CTLFLAG_RWTUN, + &u3g_debug, 0, "Debug level"); +#endif + +#define U3G_MAXPORTS 12 +#define U3G_CONFIG_INDEX 0 +#define U3G_BSIZE 2048 +#define U3G_TXSIZE (U3G_BSIZE / U3G_TXFRAMES) +#define U3G_TXFRAMES 4 + +/* Eject methods; See also usb_quirks.h:UQ_MSC_EJECT_* */ +#define U3GINIT_HUAWEI 1 /* Requires Huawei init command */ +#define U3GINIT_SIERRA 2 /* Requires Sierra init command */ +#define U3GINIT_SCSIEJECT 3 /* Requires SCSI eject command */ +#define U3GINIT_REZERO 4 /* Requires SCSI rezero command */ +#define U3GINIT_ZTESTOR 5 /* Requires ZTE SCSI command */ +#define U3GINIT_CMOTECH 6 /* Requires CMOTECH SCSI command */ +#define U3GINIT_WAIT 7 /* Device reappears after a delay */ +#define U3GINIT_SAEL_M460 8 /* Requires vendor init */ +#define U3GINIT_HUAWEISCSI 9 /* Requires Huawei SCSI init command */ +#define U3GINIT_HUAWEISCSI2 10 /* Requires Huawei SCSI init command (2) */ +#define U3GINIT_TCT 11 /* Requires TCT Mobile init command */ + +enum { + U3G_BULK_WR, + U3G_BULK_RD, + U3G_INTR, + U3G_N_TRANSFER, +}; + +struct u3g_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom[U3G_MAXPORTS]; + + struct usb_xfer *sc_xfer[U3G_MAXPORTS][U3G_N_TRANSFER]; + uint8_t sc_iface[U3G_MAXPORTS]; /* local status register */ + uint8_t sc_lsr[U3G_MAXPORTS]; /* local status register */ + uint8_t sc_msr[U3G_MAXPORTS]; /* u3g status register */ + uint16_t sc_line[U3G_MAXPORTS]; /* line status */ + + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_numports; +}; + +static device_probe_t u3g_probe; +static device_attach_t u3g_attach; +static device_detach_t u3g_detach; +static void u3g_free_softc(struct u3g_softc *); + +static usb_callback_t u3g_write_callback; +static usb_callback_t u3g_read_callback; +static usb_callback_t u3g_intr_callback; + +static void u3g_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); +static void u3g_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void u3g_cfg_set_rts(struct ucom_softc *, uint8_t); +static void u3g_start_read(struct ucom_softc *ucom); +static void u3g_stop_read(struct ucom_softc *ucom); +static void u3g_start_write(struct ucom_softc *ucom); +static void u3g_stop_write(struct ucom_softc *ucom); +static void u3g_poll(struct ucom_softc *ucom); +static void u3g_free(struct ucom_softc *ucom); + + +static void u3g_test_autoinst(void *, struct usb_device *, + struct usb_attach_arg *); +static int u3g_driver_loaded(struct module *mod, int what, void *arg); + +static eventhandler_tag u3g_etag; + +static const struct usb_config u3g_config[U3G_N_TRANSFER] = { + + [U3G_BULK_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = U3G_BSIZE,/* bytes */ + .frames = U3G_TXFRAMES, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &u3g_write_callback, + }, + + [U3G_BULK_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = U3G_BSIZE,/* bytes */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &u3g_read_callback, + }, + + [U3G_INTR] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &u3g_intr_callback, + }, +}; + +static const struct ucom_callback u3g_callback = { + .ucom_cfg_get_status = &u3g_cfg_get_status, + .ucom_cfg_set_dtr = &u3g_cfg_set_dtr, + .ucom_cfg_set_rts = &u3g_cfg_set_rts, + .ucom_start_read = &u3g_start_read, + .ucom_stop_read = &u3g_stop_read, + .ucom_start_write = &u3g_start_write, + .ucom_stop_write = &u3g_stop_write, + .ucom_poll = &u3g_poll, + .ucom_free = &u3g_free, +}; + +static device_method_t u3g_methods[] = { + DEVMETHOD(device_probe, u3g_probe), + DEVMETHOD(device_attach, u3g_attach), + DEVMETHOD(device_detach, u3g_detach), + DEVMETHOD_END +}; + +static devclass_t u3g_devclass; + +static driver_t u3g_driver = { + .name = "u3g", + .methods = u3g_methods, + .size = sizeof(struct u3g_softc), +}; + +static const STRUCT_USB_HOST_ID u3g_devs[] = { +#define U3G_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } + U3G_DEV(ACERP, H10, 0), + U3G_DEV(AIRPLUS, MCD650, 0), + U3G_DEV(AIRPRIME, PC5220, 0), + U3G_DEV(AIRPRIME, AC313U, 0), + U3G_DEV(ALINK, 3G, 0), + U3G_DEV(ALINK, 3GU, 0), + U3G_DEV(ALINK, DWM652U5, 0), + U3G_DEV(AMOI, H01, 0), + U3G_DEV(AMOI, H01A, 0), + U3G_DEV(AMOI, H02, 0), + U3G_DEV(ANYDATA, ADU_500A, 0), + U3G_DEV(ANYDATA, ADU_620UW, 0), + U3G_DEV(ANYDATA, ADU_E100X, 0), + U3G_DEV(AXESSTEL, DATAMODEM, 0), + U3G_DEV(CMOTECH, CDMA_MODEM1, 0), + U3G_DEV(CMOTECH, CGU628, U3GINIT_CMOTECH), + U3G_DEV(DELL, U5500, 0), + U3G_DEV(DELL, U5505, 0), + U3G_DEV(DELL, U5510, 0), + U3G_DEV(DELL, U5520, 0), + U3G_DEV(DELL, U5520_2, 0), + U3G_DEV(DELL, U5520_3, 0), + U3G_DEV(DELL, U5700, 0), + U3G_DEV(DELL, U5700_2, 0), + U3G_DEV(DELL, U5700_3, 0), + U3G_DEV(DELL, U5700_4, 0), + U3G_DEV(DELL, U5720, 0), + U3G_DEV(DELL, U5720_2, 0), + U3G_DEV(DELL, U5730, 0), + U3G_DEV(DELL, U5730_2, 0), + U3G_DEV(DELL, U5730_3, 0), + U3G_DEV(DELL, U740, 0), + U3G_DEV(DLINK, DWR510_CD, U3GINIT_SCSIEJECT), + U3G_DEV(DLINK, DWR510, 0), + U3G_DEV(DLINK, DWM157_CD, U3GINIT_SCSIEJECT), + U3G_DEV(DLINK, DWM157, 0), + U3G_DEV(DLINK3, DWM652, 0), + U3G_DEV(HP, EV2200, 0), + U3G_DEV(HP, HS2300, 0), + U3G_DEV(HP, UN2420_QDL, 0), + U3G_DEV(HP, UN2420, 0), + U3G_DEV(HUAWEI, E1401, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1402, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1403, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1404, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1405, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1406, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1407, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1408, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1409, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E140A, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E140B, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E140D, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E140E, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E140F, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1410, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1411, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1412, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1413, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1414, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1415, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1416, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1417, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1418, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1419, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141A, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141B, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141C, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141D, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141E, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E141F, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1420, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1421, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1422, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1423, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1424, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1425, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1426, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1427, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1428, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1429, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142A, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142B, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142C, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142D, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142E, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E142F, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1430, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1431, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1432, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1433, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1434, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1435, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1436, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1437, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1438, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1439, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143A, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143B, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143C, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143D, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143E, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E143F, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E173, 0), + U3G_DEV(HUAWEI, E173_INIT, U3GINIT_HUAWEISCSI), + U3G_DEV(HUAWEI, E3131, 0), + U3G_DEV(HUAWEI, E3131_INIT, U3GINIT_HUAWEISCSI), + U3G_DEV(HUAWEI, E180V, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E220, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E220BIS, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E392, U3GINIT_HUAWEISCSI), + U3G_DEV(HUAWEI, ME909U, U3GINIT_HUAWEISCSI2), + U3G_DEV(HUAWEI, ME909S, U3GINIT_HUAWEISCSI2), + U3G_DEV(HUAWEI, MOBILE, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, E1752, U3GINIT_HUAWEISCSI), + U3G_DEV(HUAWEI, E1820, U3GINIT_HUAWEISCSI), + U3G_DEV(HUAWEI, K3771, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, K3771_INIT, U3GINIT_HUAWEISCSI2), + U3G_DEV(HUAWEI, K3772, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, K3772_INIT, U3GINIT_HUAWEISCSI2), + U3G_DEV(HUAWEI, K3765, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, K3765_INIT, U3GINIT_HUAWEISCSI), + U3G_DEV(HUAWEI, K3770, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, K3770_INIT, U3GINIT_HUAWEISCSI), + U3G_DEV(HUAWEI, K4505, U3GINIT_HUAWEI), + U3G_DEV(HUAWEI, K4505_INIT, U3GINIT_HUAWEISCSI), + U3G_DEV(HUAWEI, ETS2055, U3GINIT_HUAWEI), + U3G_DEV(KYOCERA2, CDMA_MSM_K, 0), + U3G_DEV(KYOCERA2, KPC680, 0), + U3G_DEV(LONGCHEER, WM66, U3GINIT_HUAWEI), + U3G_DEV(LONGCHEER, DISK, U3GINIT_TCT), + U3G_DEV(LONGCHEER, W14, 0), + U3G_DEV(LONGCHEER, XSSTICK, 0), + U3G_DEV(MERLIN, V620, 0), + U3G_DEV(NEOTEL, PRIME, 0), + U3G_DEV(NOVATEL, E725, 0), + U3G_DEV(NOVATEL, ES620, 0), + U3G_DEV(NOVATEL, ES620_2, 0), + U3G_DEV(NOVATEL, EU730, 0), + U3G_DEV(NOVATEL, EU740, 0), + U3G_DEV(NOVATEL, EU870D, 0), + U3G_DEV(NOVATEL, MC760, 0), + U3G_DEV(NOVATEL, MC547, 0), + U3G_DEV(NOVATEL, MC679, 0), + U3G_DEV(NOVATEL, MC950D, 0), + U3G_DEV(NOVATEL, MC990D, 0), + U3G_DEV(NOVATEL, MIFI2200, U3GINIT_SCSIEJECT), + U3G_DEV(NOVATEL, MIFI2200V, U3GINIT_SCSIEJECT), + U3G_DEV(NOVATEL, U720, 0), + U3G_DEV(NOVATEL, U727, 0), + U3G_DEV(NOVATEL, U727_2, 0), + U3G_DEV(NOVATEL, U740, 0), + U3G_DEV(NOVATEL, U740_2, 0), + U3G_DEV(NOVATEL, U760, U3GINIT_SCSIEJECT), + U3G_DEV(NOVATEL, U870, 0), + U3G_DEV(NOVATEL, V620, 0), + U3G_DEV(NOVATEL, V640, 0), + U3G_DEV(NOVATEL, V720, 0), + U3G_DEV(NOVATEL, V740, 0), + U3G_DEV(NOVATEL, X950D, 0), + U3G_DEV(NOVATEL, XU870, 0), + U3G_DEV(MOTOROLA2, MB886, U3GINIT_SCSIEJECT), + U3G_DEV(OPTION, E6500, 0), + U3G_DEV(OPTION, E6501, 0), + U3G_DEV(OPTION, E6601, 0), + U3G_DEV(OPTION, E6721, 0), + U3G_DEV(OPTION, E6741, 0), + U3G_DEV(OPTION, E6761, 0), + U3G_DEV(OPTION, E6800, 0), + U3G_DEV(OPTION, E7021, 0), + U3G_DEV(OPTION, E7041, 0), + U3G_DEV(OPTION, E7061, 0), + U3G_DEV(OPTION, E7100, 0), + U3G_DEV(OPTION, GE40X, 0), + U3G_DEV(OPTION, GT3G, 0), + U3G_DEV(OPTION, GT3GPLUS, 0), + U3G_DEV(OPTION, GT3GQUAD, 0), + U3G_DEV(OPTION, GT3G_1, 0), + U3G_DEV(OPTION, GT3G_2, 0), + U3G_DEV(OPTION, GT3G_3, 0), + U3G_DEV(OPTION, GT3G_4, 0), + U3G_DEV(OPTION, GT3G_5, 0), + U3G_DEV(OPTION, GT3G_6, 0), + U3G_DEV(OPTION, GTHSDPA, 0), + U3G_DEV(OPTION, GTM380, 0), + U3G_DEV(OPTION, GTMAX36, 0), + U3G_DEV(OPTION, GTMAX380HSUPAE, 0), + U3G_DEV(OPTION, GTMAXHSUPA, 0), + U3G_DEV(OPTION, GTMAXHSUPAE, 0), + U3G_DEV(OPTION, VODAFONEMC3G, 0), + U3G_DEV(QISDA, H20_1, 0), + U3G_DEV(QISDA, H20_2, 0), + U3G_DEV(QISDA, H21_1, 0), + U3G_DEV(QISDA, H21_2, 0), + U3G_DEV(QUALCOMM, NTT_L02C_MODEM, U3GINIT_SCSIEJECT), + U3G_DEV(QUALCOMM2, AC8700, 0), + U3G_DEV(QUALCOMM2, MF330, 0), + U3G_DEV(QUALCOMM2, SIM5218, 0), + U3G_DEV(QUALCOMM2, WM620, 0), + U3G_DEV(QUALCOMM2, VW110L, U3GINIT_SCSIEJECT), + U3G_DEV(QUALCOMM2, GOBI2000_QDL, 0), + U3G_DEV(QUALCOMM2, GOBI2000, 0), + U3G_DEV(QUALCOMM2, VT80N, 0), + U3G_DEV(QUALCOMM3, VFAST2, 0), + U3G_DEV(QUALCOMMINC, AC2726, 0), + U3G_DEV(QUALCOMMINC, AC682_INIT, U3GINIT_SCSIEJECT), + U3G_DEV(QUALCOMMINC, AC682, 0), + U3G_DEV(QUALCOMMINC, AC8700, 0), + U3G_DEV(QUALCOMMINC, AC8710, 0), + U3G_DEV(QUALCOMMINC, CDMA_MSM, U3GINIT_SCSIEJECT), + U3G_DEV(QUALCOMMINC, E0002, 0), + U3G_DEV(QUALCOMMINC, E0003, 0), + U3G_DEV(QUALCOMMINC, E0004, 0), + U3G_DEV(QUALCOMMINC, E0005, 0), + U3G_DEV(QUALCOMMINC, E0006, 0), + U3G_DEV(QUALCOMMINC, E0007, 0), + U3G_DEV(QUALCOMMINC, E0008, 0), + U3G_DEV(QUALCOMMINC, E0009, 0), + U3G_DEV(QUALCOMMINC, E000A, 0), + U3G_DEV(QUALCOMMINC, E000B, 0), + U3G_DEV(QUALCOMMINC, E000C, 0), + U3G_DEV(QUALCOMMINC, E000D, 0), + U3G_DEV(QUALCOMMINC, E000E, 0), + U3G_DEV(QUALCOMMINC, E000F, 0), + U3G_DEV(QUALCOMMINC, E0010, 0), + U3G_DEV(QUALCOMMINC, E0011, 0), + U3G_DEV(QUALCOMMINC, E0012, 0), + U3G_DEV(QUALCOMMINC, E0013, 0), + U3G_DEV(QUALCOMMINC, E0014, 0), + U3G_DEV(QUALCOMMINC, E0017, 0), + U3G_DEV(QUALCOMMINC, E0018, 0), + U3G_DEV(QUALCOMMINC, E0019, 0), + U3G_DEV(QUALCOMMINC, E0020, 0), + U3G_DEV(QUALCOMMINC, E0021, 0), + U3G_DEV(QUALCOMMINC, E0022, 0), + U3G_DEV(QUALCOMMINC, E0023, 0), + U3G_DEV(QUALCOMMINC, E0024, 0), + U3G_DEV(QUALCOMMINC, E0025, 0), + U3G_DEV(QUALCOMMINC, E0026, 0), + U3G_DEV(QUALCOMMINC, E0027, 0), + U3G_DEV(QUALCOMMINC, E0028, 0), + U3G_DEV(QUALCOMMINC, E0029, 0), + U3G_DEV(QUALCOMMINC, E0030, 0), + U3G_DEV(QUALCOMMINC, E0032, 0), + U3G_DEV(QUALCOMMINC, E0033, 0), + U3G_DEV(QUALCOMMINC, E0037, 0), + U3G_DEV(QUALCOMMINC, E0039, 0), + U3G_DEV(QUALCOMMINC, E0042, 0), + U3G_DEV(QUALCOMMINC, E0043, 0), + U3G_DEV(QUALCOMMINC, E0048, 0), + U3G_DEV(QUALCOMMINC, E0049, 0), + U3G_DEV(QUALCOMMINC, E0051, 0), + U3G_DEV(QUALCOMMINC, E0052, 0), + U3G_DEV(QUALCOMMINC, E0054, 0), + U3G_DEV(QUALCOMMINC, E0055, 0), + U3G_DEV(QUALCOMMINC, E0057, 0), + U3G_DEV(QUALCOMMINC, E0058, 0), + U3G_DEV(QUALCOMMINC, E0059, 0), + U3G_DEV(QUALCOMMINC, E0060, 0), + U3G_DEV(QUALCOMMINC, E0061, 0), + U3G_DEV(QUALCOMMINC, E0062, 0), + U3G_DEV(QUALCOMMINC, E0063, 0), + U3G_DEV(QUALCOMMINC, E0064, 0), + U3G_DEV(QUALCOMMINC, E0066, 0), + U3G_DEV(QUALCOMMINC, E0069, 0), + U3G_DEV(QUALCOMMINC, E0070, 0), + U3G_DEV(QUALCOMMINC, E0073, 0), + U3G_DEV(QUALCOMMINC, E0076, 0), + U3G_DEV(QUALCOMMINC, E0078, 0), + U3G_DEV(QUALCOMMINC, E0082, 0), + U3G_DEV(QUALCOMMINC, E0086, 0), + U3G_DEV(QUALCOMMINC, SURFSTICK, 0), + U3G_DEV(QUALCOMMINC, E2002, 0), + U3G_DEV(QUALCOMMINC, E2003, 0), + U3G_DEV(QUALCOMMINC, K3772_Z, 0), + U3G_DEV(QUALCOMMINC, K3772_Z_INIT, U3GINIT_SCSIEJECT), + U3G_DEV(QUALCOMMINC, MF112, U3GINIT_ZTESTOR), + U3G_DEV(QUALCOMMINC, MF195E, 0), + U3G_DEV(QUALCOMMINC, MF195E_INIT, U3GINIT_SCSIEJECT), + U3G_DEV(QUALCOMMINC, MF626, 0), + U3G_DEV(QUALCOMMINC, MF628, 0), + U3G_DEV(QUALCOMMINC, MF633R, 0), + /* the following is a RNDIS device, no modem features */ + U3G_DEV(QUALCOMMINC, ZTE_MF730M, U3GINIT_SCSIEJECT), + U3G_DEV(QUANTA, GKE, 0), + U3G_DEV(QUANTA, GLE, 0), + U3G_DEV(QUANTA, GLX, 0), + U3G_DEV(QUANTA, Q101, 0), + U3G_DEV(QUANTA, Q111, 0), + U3G_DEV(SIERRA, AC402, 0), + U3G_DEV(SIERRA, AC595U, 0), + U3G_DEV(SIERRA, AC313U, 0), + U3G_DEV(SIERRA, AC597E, 0), + U3G_DEV(SIERRA, AC875, 0), + U3G_DEV(SIERRA, AC875E, 0), + U3G_DEV(SIERRA, AC875U, 0), + U3G_DEV(SIERRA, AC875U_2, 0), + U3G_DEV(SIERRA, AC880, 0), + U3G_DEV(SIERRA, AC880E, 0), + U3G_DEV(SIERRA, AC880U, 0), + U3G_DEV(SIERRA, AC881, 0), + U3G_DEV(SIERRA, AC881E, 0), + U3G_DEV(SIERRA, AC881U, 0), + U3G_DEV(SIERRA, AC885E, 0), + U3G_DEV(SIERRA, AC885E_2, 0), + U3G_DEV(SIERRA, AC885U, 0), + U3G_DEV(SIERRA, AIRCARD580, 0), + U3G_DEV(SIERRA, AIRCARD595, 0), + U3G_DEV(SIERRA, C22, 0), + U3G_DEV(SIERRA, C597, 0), + U3G_DEV(SIERRA, C888, 0), + U3G_DEV(SIERRA, E0029, 0), + U3G_DEV(SIERRA, E6892, 0), + U3G_DEV(SIERRA, E6893, 0), + U3G_DEV(SIERRA, EM5625, 0), + U3G_DEV(SIERRA, EM5725, 0), + U3G_DEV(SIERRA, MC5720, 0), + U3G_DEV(SIERRA, MC5720_2, 0), + U3G_DEV(SIERRA, MC5725, 0), + U3G_DEV(SIERRA, MC5727, 0), + U3G_DEV(SIERRA, MC5727_2, 0), + U3G_DEV(SIERRA, MC5728, 0), + U3G_DEV(SIERRA, MC7354, 0), + U3G_DEV(SIERRA, MC7355, 0), + U3G_DEV(SIERRA, MC7430, 0), + U3G_DEV(SIERRA, MC8700, 0), + U3G_DEV(SIERRA, MC8755, 0), + U3G_DEV(SIERRA, MC8755_2, 0), + U3G_DEV(SIERRA, MC8755_3, 0), + U3G_DEV(SIERRA, MC8755_4, 0), + U3G_DEV(SIERRA, MC8765, 0), + U3G_DEV(SIERRA, MC8765_2, 0), + U3G_DEV(SIERRA, MC8765_3, 0), + U3G_DEV(SIERRA, MC8775, 0), + U3G_DEV(SIERRA, MC8775_2, 0), + U3G_DEV(SIERRA, MC8780, 0), + U3G_DEV(SIERRA, MC8780_2, 0), + U3G_DEV(SIERRA, MC8780_3, 0), + U3G_DEV(SIERRA, MC8781, 0), + U3G_DEV(SIERRA, MC8781_2, 0), + U3G_DEV(SIERRA, MC8781_3, 0), + U3G_DEV(SIERRA, MC8785, 0), + U3G_DEV(SIERRA, MC8785_2, 0), + U3G_DEV(SIERRA, MC8790, 0), + U3G_DEV(SIERRA, MC8791, 0), + U3G_DEV(SIERRA, MC8792, 0), + U3G_DEV(SIERRA, MINI5725, 0), + U3G_DEV(SIERRA, T11, 0), + U3G_DEV(SIERRA, T598, 0), + U3G_DEV(SILABS, SAEL, U3GINIT_SAEL_M460), + U3G_DEV(STELERA, C105, 0), + U3G_DEV(STELERA, E1003, 0), + U3G_DEV(STELERA, E1004, 0), + U3G_DEV(STELERA, E1005, 0), + U3G_DEV(STELERA, E1006, 0), + U3G_DEV(STELERA, E1007, 0), + U3G_DEV(STELERA, E1008, 0), + U3G_DEV(STELERA, E1009, 0), + U3G_DEV(STELERA, E100A, 0), + U3G_DEV(STELERA, E100B, 0), + U3G_DEV(STELERA, E100C, 0), + U3G_DEV(STELERA, E100D, 0), + U3G_DEV(STELERA, E100E, 0), + U3G_DEV(STELERA, E100F, 0), + U3G_DEV(STELERA, E1010, 0), + U3G_DEV(STELERA, E1011, 0), + U3G_DEV(STELERA, E1012, 0), + U3G_DEV(TCTMOBILE, X060S, 0), + U3G_DEV(TCTMOBILE, X080S, U3GINIT_TCT), + U3G_DEV(TELIT, UC864E, 0), + U3G_DEV(TELIT, UC864G, 0), + U3G_DEV(TLAYTECH, TEU800, 0), + U3G_DEV(TOSHIBA, G450, 0), + U3G_DEV(TOSHIBA, HSDPA, 0), + U3G_DEV(YISO, C893, 0), + U3G_DEV(WETELECOM, WM_D200, 0), + /* Autoinstallers */ + U3G_DEV(NOVATEL, ZEROCD, U3GINIT_SCSIEJECT), + U3G_DEV(OPTION, GTICON322, U3GINIT_REZERO), + U3G_DEV(QUALCOMMINC, ZTE_STOR, U3GINIT_ZTESTOR), + U3G_DEV(QUALCOMMINC, ZTE_STOR2, U3GINIT_SCSIEJECT), + U3G_DEV(QUANTA, Q101_STOR, U3GINIT_SCSIEJECT), + U3G_DEV(SIERRA, TRUINSTALL, U3GINIT_SIERRA), +#undef U3G_DEV +}; + +DRIVER_MODULE(u3g, uhub, u3g_driver, u3g_devclass, u3g_driver_loaded, 0); +MODULE_DEPEND(u3g, ucom, 1, 1, 1); +MODULE_DEPEND(u3g, usb, 1, 1, 1); +MODULE_VERSION(u3g, 1); +USB_PNP_HOST_INFO(u3g_devs); + +static int +u3g_sierra_init(struct usb_device *udev) +{ + struct usb_device_request req; + + req.bmRequestType = UT_VENDOR; + req.bRequest = UR_SET_INTERFACE; + USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); + USETW(req.wIndex, UHF_PORT_CONNECTION); + USETW(req.wLength, 0); + + if (usbd_do_request_flags(udev, NULL, &req, + NULL, 0, NULL, USB_MS_HZ)) { + /* ignore any errors */ + } + return (0); +} + +static int +u3g_huawei_init(struct usb_device *udev) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); + USETW(req.wIndex, UHF_PORT_SUSPEND); + USETW(req.wLength, 0); + + if (usbd_do_request_flags(udev, NULL, &req, + NULL, 0, NULL, USB_MS_HZ)) { + /* ignore any errors */ + } + return (0); +} + +static void +u3g_sael_m460_init(struct usb_device *udev) +{ + static const uint8_t setup[][24] = { + { 0x41, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0xc1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02 }, + { 0xc1, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }, + { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, + { 0xc1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }, + { 0x41, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x19, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x13 }, + { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00 }, + { 0x41, 0x12, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00 }, + { 0x41, 0x19, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x13 }, + { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00 }, + { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 }, + }; + + struct usb_device_request req; + usb_error_t err; + uint16_t len; + uint8_t buf[0x300]; + uint8_t n; + + DPRINTFN(1, "\n"); + + if (usbd_req_set_alt_interface_no(udev, NULL, 0, 0)) { + DPRINTFN(0, "Alt setting 0 failed\n"); + return; + } + + for (n = 0; n != nitems(setup); n++) { + + memcpy(&req, setup[n], sizeof(req)); + + len = UGETW(req.wLength); + if (req.bmRequestType & UE_DIR_IN) { + if (len > sizeof(buf)) { + DPRINTFN(0, "too small buffer\n"); + continue; + } + err = usbd_do_request(udev, NULL, &req, buf); + } else { + if (len > (sizeof(setup[0]) - 8)) { + DPRINTFN(0, "too small buffer\n"); + continue; + } + err = usbd_do_request(udev, NULL, &req, + __DECONST(uint8_t *, &setup[n][8])); + } + if (err) { + DPRINTFN(1, "request %u failed\n", + (unsigned int)n); + /* + * Some of the requests will fail. Stop doing + * requests when we are getting timeouts so + * that we don't block the explore/attach + * thread forever. + */ + if (err == USB_ERR_TIMEOUT) + break; + } + } +} + +/* + * The following function handles 3G modem devices (E220, Mobile, + * etc.) with auto-install flash disks for Windows/MacOSX on the first + * interface. After some command or some delay they change appearance + * to a modem. + */ +static void +u3g_test_autoinst(void *arg, struct usb_device *udev, + struct usb_attach_arg *uaa) +{ + struct usb_interface *iface; + struct usb_interface_descriptor *id; + int error; + unsigned long method; + + if (uaa->dev_state != UAA_DEV_READY) + return; + + iface = usbd_get_iface(udev, 0); + if (iface == NULL) + return; + id = iface->idesc; + if (id == NULL || id->bInterfaceClass != UICLASS_MASS) + return; + + if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEI)) + method = U3GINIT_HUAWEI; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_SIERRA)) + method = U3GINIT_SIERRA; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_SCSIEJECT)) + method = U3GINIT_SCSIEJECT; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_REZERO)) + method = U3GINIT_REZERO; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_ZTESTOR)) + method = U3GINIT_ZTESTOR; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_CMOTECH)) + method = U3GINIT_CMOTECH; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_WAIT)) + method = U3GINIT_WAIT; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEISCSI)) + method = U3GINIT_HUAWEISCSI; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEISCSI2)) + method = U3GINIT_HUAWEISCSI2; + else if (usb_test_quirk(uaa, UQ_MSC_EJECT_TCT)) + method = U3GINIT_TCT; + else if (usbd_lookup_id_by_uaa(u3g_devs, sizeof(u3g_devs), uaa) == 0) + method = USB_GET_DRIVER_INFO(uaa); + else + return; /* no device match */ + + if (bootverbose) { + printf("Ejecting %s %s using method %ld\n", + usb_get_manufacturer(udev), + usb_get_product(udev), method); + } + + switch (method) { + case U3GINIT_HUAWEI: + error = u3g_huawei_init(udev); + break; + case U3GINIT_HUAWEISCSI: + error = usb_msc_eject(udev, 0, MSC_EJECT_HUAWEI); + break; + case U3GINIT_HUAWEISCSI2: + error = usb_msc_eject(udev, 0, MSC_EJECT_HUAWEI2); + break; + case U3GINIT_SCSIEJECT: + error = usb_msc_eject(udev, 0, MSC_EJECT_STOPUNIT); + break; + case U3GINIT_REZERO: + error = usb_msc_eject(udev, 0, MSC_EJECT_REZERO); + break; + case U3GINIT_ZTESTOR: + error = usb_msc_eject(udev, 0, MSC_EJECT_STOPUNIT); + if (error == 0) + error = usb_msc_eject(udev, 0, MSC_EJECT_ZTESTOR); + break; + case U3GINIT_CMOTECH: + error = usb_msc_eject(udev, 0, MSC_EJECT_CMOTECH); + break; + case U3GINIT_TCT: + error = usb_msc_eject(udev, 0, MSC_EJECT_TCT); + break; + case U3GINIT_SIERRA: + error = u3g_sierra_init(udev); + break; + case U3GINIT_WAIT: + /* Just pretend we ejected, the card will timeout */ + error = 0; + break; + default: + /* no 3G eject quirks */ + error = EOPNOTSUPP; + break; + } + if (error == 0) { + /* success, mark the udev as disappearing */ + uaa->dev_state = UAA_DEV_EJECTING; + } +} + +static int +u3g_driver_loaded(struct module *mod, int what, void *arg) +{ + switch (what) { + case MOD_LOAD: + /* register our autoinstall handler */ + u3g_etag = EVENTHANDLER_REGISTER(usb_dev_configured, + u3g_test_autoinst, NULL, EVENTHANDLER_PRI_ANY); + break; + case MOD_UNLOAD: + EVENTHANDLER_DEREGISTER(usb_dev_configured, u3g_etag); + break; + default: + return (EOPNOTSUPP); + } + return (0); +} + +static int +u3g_probe(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != U3G_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bInterfaceClass != UICLASS_VENDOR) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(u3g_devs, sizeof(u3g_devs), uaa)); +} + +static int +u3g_attach(device_t dev) +{ + struct usb_config u3g_config_tmp[U3G_N_TRANSFER]; + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct u3g_softc *sc = device_get_softc(dev); + struct usb_interface *iface; + struct usb_interface_descriptor *id; + uint32_t iface_valid; + int error, type, nports; + int ep, n; + uint8_t i; + + DPRINTF("sc=%p\n", sc); + + type = USB_GET_DRIVER_INFO(uaa); + if (type == U3GINIT_SAEL_M460 + || usb_test_quirk(uaa, UQ_MSC_EJECT_SAEL_M460)) { + u3g_sael_m460_init(uaa->device); + } + + /* copy in USB config */ + for (n = 0; n != U3G_N_TRANSFER; n++) + u3g_config_tmp[n] = u3g_config[n]; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "u3g", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + sc->sc_udev = uaa->device; + + /* Claim all interfaces on the device */ + iface_valid = 0; + for (i = uaa->info.bIfaceIndex; i < USB_IFACE_MAX; i++) { + iface = usbd_get_iface(uaa->device, i); + if (iface == NULL) + break; + id = usbd_get_interface_descriptor(iface); + if (id == NULL || id->bInterfaceClass != UICLASS_VENDOR) + continue; + usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); + iface_valid |= (1<<i); + } + + i = 0; /* interface index */ + ep = 0; /* endpoint index */ + nports = 0; /* number of ports */ + while (i < USB_IFACE_MAX) { + if ((iface_valid & (1<<i)) == 0) { + i++; + continue; + } + + /* update BULK endpoint index */ + for (n = 0; n < U3G_N_TRANSFER; n++) + u3g_config_tmp[n].ep_index = ep; + + /* try to allocate a set of BULK endpoints */ + error = usbd_transfer_setup(uaa->device, &i, + sc->sc_xfer[nports], u3g_config_tmp, U3G_N_TRANSFER, + &sc->sc_ucom[nports], &sc->sc_mtx); + if (error) { + /* next interface */ + i++; + ep = 0; + continue; + } + + iface = usbd_get_iface(uaa->device, i); + id = usbd_get_interface_descriptor(iface); + sc->sc_iface[nports] = id->bInterfaceNumber; + + if (bootverbose && sc->sc_xfer[nports][U3G_INTR]) { + device_printf(dev, "port %d supports modem control\n", + nports); + } + + /* set stall by default */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[nports][U3G_BULK_WR]); + usbd_xfer_set_stall(sc->sc_xfer[nports][U3G_BULK_RD]); + mtx_unlock(&sc->sc_mtx); + + nports++; /* found one port */ + ep++; + if (nports == U3G_MAXPORTS) + break; + } + if (nports == 0) { + device_printf(dev, "no ports found\n"); + goto detach; + } + sc->sc_numports = nports; + + error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, + sc->sc_numports, sc, &u3g_callback, &sc->sc_mtx); + if (error) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + device_printf(dev, "Found %u port%s.\n", sc->sc_numports, + sc->sc_numports > 1 ? "s":""); + + return (0); + +detach: + u3g_detach(dev); + return (ENXIO); +} + +static int +u3g_detach(device_t dev) +{ + struct u3g_softc *sc = device_get_softc(dev); + uint8_t subunit; + + DPRINTF("sc=%p\n", sc); + + /* NOTE: It is not dangerous to detach more ports than attached! */ + ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); + + for (subunit = 0; subunit != U3G_MAXPORTS; subunit++) + usbd_transfer_unsetup(sc->sc_xfer[subunit], U3G_N_TRANSFER); + + device_claim_softc(dev); + + u3g_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(u3g); + +static void +u3g_free_softc(struct u3g_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +u3g_free(struct ucom_softc *ucom) +{ + u3g_free_softc(ucom->sc_parent); +} + +static void +u3g_start_read(struct ucom_softc *ucom) +{ + struct u3g_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint (if configured) */ + usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_INTR]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_RD]); +} + +static void +u3g_stop_read(struct ucom_softc *ucom) +{ + struct u3g_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint (if configured) */ + usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_INTR]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_RD]); +} + +static void +u3g_start_write(struct ucom_softc *ucom) +{ + struct u3g_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_WR]); +} + +static void +u3g_stop_write(struct ucom_softc *ucom) +{ + struct u3g_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_WR]); +} + +static void +u3g_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucom_softc *ucom = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + uint32_t frame; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + for (frame = 0; frame != U3G_TXFRAMES; frame++) { + usbd_xfer_set_frame_offset(xfer, frame * U3G_TXSIZE, frame); + + pc = usbd_xfer_get_frame(xfer, frame); + if (ucom_get_data(ucom, pc, 0, U3G_TXSIZE, &actlen) == 0) + break; + usbd_xfer_set_frame_len(xfer, frame, actlen); + } + if (frame != 0) { + usbd_xfer_set_frames(xfer, frame); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* do a builtin clear-stall */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +u3g_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucom_softc *ucom = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* do a builtin clear-stall */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +u3g_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct u3g_softc *sc = ucom->sc_parent; + + /* XXX Note: sc_lsr is always zero */ + *lsr = sc->sc_lsr[ucom->sc_subunit]; + *msr = sc->sc_msr[ucom->sc_subunit]; +} + +static void +u3g_cfg_set_line(struct ucom_softc *ucom) +{ + struct u3g_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line[ucom->sc_subunit]); + req.wIndex[0] = sc->sc_iface[ucom->sc_subunit]; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, ucom, + &req, NULL, 0, 1000); +} + +static void +u3g_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct u3g_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line[ucom->sc_subunit] |= UCDC_LINE_DTR; + else + sc->sc_line[ucom->sc_subunit] &= ~UCDC_LINE_DTR; + + u3g_cfg_set_line(ucom); +} + +static void +u3g_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct u3g_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line[ucom->sc_subunit] |= UCDC_LINE_RTS; + else + sc->sc_line[ucom->sc_subunit] &= ~UCDC_LINE_RTS; + + u3g_cfg_set_line(ucom); +} + +static void +u3g_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucom_softc *ucom = usbd_xfer_softc(xfer); + struct u3g_softc *sc = ucom->sc_parent; + struct usb_page_cache *pc; + struct usb_cdc_notification pkt; + int actlen; + uint16_t wLen; + uint8_t mstatus; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen < 8) { /* usb_cdc_notification with 2 data bytes */ + DPRINTF("message too short (expected 8, received %d)\n", actlen); + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &pkt, actlen); + + wLen = UGETW(pkt.wLength); + if (wLen < 2) { + DPRINTF("message too short (expected 2 data bytes, received %d)\n", wLen); + goto tr_setup; + } + + if (pkt.bmRequestType == UCDC_NOTIFICATION + && pkt.bNotification == UCDC_N_SERIAL_STATE) { + /* + * Set the serial state in ucom driver based on + * the bits from the notify message + */ + DPRINTF("notify bytes = 0x%02x, 0x%02x\n", + pkt.data[0], pkt.data[1]); + + /* currently, lsr is always zero. */ + sc->sc_lsr[ucom->sc_subunit] = 0; + sc->sc_msr[ucom->sc_subunit] = 0; + + mstatus = pkt.data[0]; + + if (mstatus & UCDC_N_SERIAL_RI) + sc->sc_msr[ucom->sc_subunit] |= SER_RI; + if (mstatus & UCDC_N_SERIAL_DSR) + sc->sc_msr[ucom->sc_subunit] |= SER_DSR; + if (mstatus & UCDC_N_SERIAL_DCD) + sc->sc_msr[ucom->sc_subunit] |= SER_DCD; + ucom_status_change(ucom); + } + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +u3g_poll(struct ucom_softc *ucom) +{ + struct u3g_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer[ucom->sc_subunit], U3G_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/uark.c b/freebsd/sys/dev/usb/serial/uark.c new file mode 100644 index 00000000..c04516ac --- /dev/null +++ b/freebsd/sys/dev/usb/serial/uark.c @@ -0,0 +1,472 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $OpenBSD: uark.c,v 1.1 2006/08/14 08:30:22 jsg Exp $ */ + +/* + * Copyright (c) 2006 Jonathan Gray <jsg@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. + * + * $FreeBSD$ + */ + +/* + * NOTE: all function names beginning like "uark_cfg_" can only + * be called from within the config thread function ! + */ + + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbhid.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR usb_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#define UARK_BUF_SIZE 1024 /* bytes */ + +#define UARK_SET_DATA_BITS(x) ((x) - 5) + +#define UARK_PARITY_NONE 0x00 +#define UARK_PARITY_ODD 0x08 +#define UARK_PARITY_EVEN 0x18 + +#define UARK_STOP_BITS_1 0x00 +#define UARK_STOP_BITS_2 0x04 + +#define UARK_BAUD_REF 3000000 + +#define UARK_WRITE 0x40 +#define UARK_READ 0xc0 + +#define UARK_REQUEST 0xfe + +#define UARK_CONFIG_INDEX 0 +#define UARK_IFACE_INDEX 0 + +enum { + UARK_BULK_DT_WR, + UARK_BULK_DT_RD, + UARK_N_TRANSFER, +}; + +struct uark_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UARK_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_msr; + uint8_t sc_lsr; +}; + +/* prototypes */ + +static device_probe_t uark_probe; +static device_attach_t uark_attach; +static device_detach_t uark_detach; +static void uark_free_softc(struct uark_softc *); + +static usb_callback_t uark_bulk_write_callback; +static usb_callback_t uark_bulk_read_callback; + +static void uark_free(struct ucom_softc *); +static void uark_start_read(struct ucom_softc *); +static void uark_stop_read(struct ucom_softc *); +static void uark_start_write(struct ucom_softc *); +static void uark_stop_write(struct ucom_softc *); +static int uark_pre_param(struct ucom_softc *, struct termios *); +static void uark_cfg_param(struct ucom_softc *, struct termios *); +static void uark_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void uark_cfg_set_break(struct ucom_softc *, uint8_t); +static void uark_cfg_write(struct uark_softc *, uint16_t, uint16_t); +static void uark_poll(struct ucom_softc *ucom); + +static const struct usb_config + uark_xfer_config[UARK_N_TRANSFER] = { + + [UARK_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UARK_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uark_bulk_write_callback, + }, + + [UARK_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UARK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uark_bulk_read_callback, + }, +}; + +static const struct ucom_callback uark_callback = { + .ucom_cfg_get_status = &uark_cfg_get_status, + .ucom_cfg_set_break = &uark_cfg_set_break, + .ucom_cfg_param = &uark_cfg_param, + .ucom_pre_param = &uark_pre_param, + .ucom_start_read = &uark_start_read, + .ucom_stop_read = &uark_stop_read, + .ucom_start_write = &uark_start_write, + .ucom_stop_write = &uark_stop_write, + .ucom_poll = &uark_poll, + .ucom_free = &uark_free, +}; + +static device_method_t uark_methods[] = { + /* Device methods */ + DEVMETHOD(device_probe, uark_probe), + DEVMETHOD(device_attach, uark_attach), + DEVMETHOD(device_detach, uark_detach), + DEVMETHOD_END +}; + +static devclass_t uark_devclass; + +static driver_t uark_driver = { + .name = "uark", + .methods = uark_methods, + .size = sizeof(struct uark_softc), +}; + +static const STRUCT_USB_HOST_ID uark_devs[] = { + {USB_VPI(USB_VENDOR_ARKMICRO, USB_PRODUCT_ARKMICRO_ARK3116, 0)}, +}; + +DRIVER_MODULE(uark, uhub, uark_driver, uark_devclass, NULL, 0); +MODULE_DEPEND(uark, ucom, 1, 1, 1); +MODULE_DEPEND(uark, usb, 1, 1, 1); +MODULE_VERSION(uark, 1); +USB_PNP_HOST_INFO(uark_devs); + +static int +uark_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != 0) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UARK_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uark_devs, sizeof(uark_devs), uaa)); +} + +static int +uark_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uark_softc *sc = device_get_softc(dev); + int32_t error; + uint8_t iface_index; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uark", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + sc->sc_udev = uaa->device; + + iface_index = UARK_IFACE_INDEX; + error = usbd_transfer_setup + (uaa->device, &iface_index, sc->sc_xfer, + uark_xfer_config, UARK_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating control USB " + "transfers failed\n"); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UARK_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UARK_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uark_callback, &sc->sc_mtx); + if (error) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); /* success */ + +detach: + uark_detach(dev); + return (ENXIO); /* failure */ +} + +static int +uark_detach(device_t dev) +{ + struct uark_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UARK_N_TRANSFER); + + device_claim_softc(dev); + + uark_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(uark); + +static void +uark_free_softc(struct uark_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +uark_free(struct ucom_softc *ucom) +{ + uark_free_softc(ucom->sc_parent); +} + +static void +uark_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uark_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UARK_BUF_SIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +uark_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uark_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uark_start_read(struct ucom_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UARK_BULK_DT_RD]); +} + +static void +uark_stop_read(struct ucom_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UARK_BULK_DT_RD]); +} + +static void +uark_start_write(struct ucom_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UARK_BULK_DT_WR]); +} + +static void +uark_stop_write(struct ucom_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UARK_BULK_DT_WR]); +} + +static int +uark_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + if ((t->c_ospeed < 300) || (t->c_ospeed > 115200)) + return (EINVAL); + return (0); +} + +static void +uark_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uark_softc *sc = ucom->sc_parent; + uint32_t speed = t->c_ospeed; + uint16_t data; + + /* + * NOTE: When reverse computing the baud rate from the "data" all + * allowed baud rates are within 3% of the initial baud rate. + */ + data = (UARK_BAUD_REF + (speed / 2)) / speed; + + uark_cfg_write(sc, 3, 0x83); + uark_cfg_write(sc, 0, data & 0xFF); + uark_cfg_write(sc, 1, data >> 8); + uark_cfg_write(sc, 3, 0x03); + + if (t->c_cflag & CSTOPB) + data = UARK_STOP_BITS_2; + else + data = UARK_STOP_BITS_1; + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) + data |= UARK_PARITY_ODD; + else + data |= UARK_PARITY_EVEN; + } else + data |= UARK_PARITY_NONE; + + switch (t->c_cflag & CSIZE) { + case CS5: + data |= UARK_SET_DATA_BITS(5); + break; + case CS6: + data |= UARK_SET_DATA_BITS(6); + break; + case CS7: + data |= UARK_SET_DATA_BITS(7); + break; + default: + case CS8: + data |= UARK_SET_DATA_BITS(8); + break; + } + uark_cfg_write(sc, 3, 0x00); + uark_cfg_write(sc, 3, data); +} + +static void +uark_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uark_softc *sc = ucom->sc_parent; + + /* XXX Note: sc_lsr is always zero */ + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uark_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uark_softc *sc = ucom->sc_parent; + + DPRINTF("onoff=%d\n", onoff); + + uark_cfg_write(sc, 4, onoff ? 0x01 : 0x00); +} + +static void +uark_cfg_write(struct uark_softc *sc, uint16_t index, uint16_t value) +{ + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UARK_WRITE; + req.bRequest = UARK_REQUEST; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, 0); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } +} + +static void +uark_poll(struct ucom_softc *ucom) +{ + struct uark_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UARK_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/ubsa.c b/freebsd/sys/dev/usb/serial/ubsa.c new file mode 100644 index 00000000..a83163a2 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/ubsa.c @@ -0,0 +1,700 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/*- + * Copyright (c) 2002, Alexander Kabaev <kan.FreeBSD.org>. + * 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. + * 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/cdefs.h> +__FBSDID("$FreeBSD$"); +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ichiro FUKUHARA (ichiro@ichiro.org). + * + * 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 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/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR ubsa_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#ifdef USB_DEBUG +static int ubsa_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ubsa, CTLFLAG_RW, 0, "USB ubsa"); +SYSCTL_INT(_hw_usb_ubsa, OID_AUTO, debug, CTLFLAG_RWTUN, + &ubsa_debug, 0, "ubsa debug level"); +#endif + +#define UBSA_BSIZE 1024 /* bytes */ + +#define UBSA_CONFIG_INDEX 0 +#define UBSA_IFACE_INDEX 0 + +#define UBSA_REG_BAUDRATE 0x00 +#define UBSA_REG_STOP_BITS 0x01 +#define UBSA_REG_DATA_BITS 0x02 +#define UBSA_REG_PARITY 0x03 +#define UBSA_REG_DTR 0x0A +#define UBSA_REG_RTS 0x0B +#define UBSA_REG_BREAK 0x0C +#define UBSA_REG_FLOW_CTRL 0x10 + +#define UBSA_PARITY_NONE 0x00 +#define UBSA_PARITY_EVEN 0x01 +#define UBSA_PARITY_ODD 0x02 +#define UBSA_PARITY_MARK 0x03 +#define UBSA_PARITY_SPACE 0x04 + +#define UBSA_FLOW_NONE 0x0000 +#define UBSA_FLOW_OCTS 0x0001 +#define UBSA_FLOW_ODSR 0x0002 +#define UBSA_FLOW_IDSR 0x0004 +#define UBSA_FLOW_IDTR 0x0008 +#define UBSA_FLOW_IRTS 0x0010 +#define UBSA_FLOW_ORTS 0x0020 +#define UBSA_FLOW_UNKNOWN 0x0040 +#define UBSA_FLOW_OXON 0x0080 +#define UBSA_FLOW_IXON 0x0100 + +/* line status register */ +#define UBSA_LSR_TSRE 0x40 /* Transmitter empty: byte sent */ +#define UBSA_LSR_TXRDY 0x20 /* Transmitter buffer empty */ +#define UBSA_LSR_BI 0x10 /* Break detected */ +#define UBSA_LSR_FE 0x08 /* Framing error: bad stop bit */ +#define UBSA_LSR_PE 0x04 /* Parity error */ +#define UBSA_LSR_OE 0x02 /* Overrun, lost incoming byte */ +#define UBSA_LSR_RXRDY 0x01 /* Byte ready in Receive Buffer */ +#define UBSA_LSR_RCV_MASK 0x1f /* Mask for incoming data or error */ + +/* modem status register */ +/* All deltas are from the last read of the MSR. */ +#define UBSA_MSR_DCD 0x80 /* Current Data Carrier Detect */ +#define UBSA_MSR_RI 0x40 /* Current Ring Indicator */ +#define UBSA_MSR_DSR 0x20 /* Current Data Set Ready */ +#define UBSA_MSR_CTS 0x10 /* Current Clear to Send */ +#define UBSA_MSR_DDCD 0x08 /* DCD has changed state */ +#define UBSA_MSR_TERI 0x04 /* RI has toggled low to high */ +#define UBSA_MSR_DDSR 0x02 /* DSR has changed state */ +#define UBSA_MSR_DCTS 0x01 /* CTS has changed state */ + +enum { + UBSA_BULK_DT_WR, + UBSA_BULK_DT_RD, + UBSA_INTR_DT_RD, + UBSA_N_TRANSFER, +}; + +struct ubsa_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UBSA_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_iface_no; /* interface number */ + uint8_t sc_iface_index; /* interface index */ + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* UBSA status register */ +}; + +static device_probe_t ubsa_probe; +static device_attach_t ubsa_attach; +static device_detach_t ubsa_detach; +static void ubsa_free_softc(struct ubsa_softc *); + +static usb_callback_t ubsa_write_callback; +static usb_callback_t ubsa_read_callback; +static usb_callback_t ubsa_intr_callback; + +static void ubsa_cfg_request(struct ubsa_softc *, uint8_t, uint16_t); +static void ubsa_free(struct ucom_softc *); +static void ubsa_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void ubsa_cfg_set_rts(struct ucom_softc *, uint8_t); +static void ubsa_cfg_set_break(struct ucom_softc *, uint8_t); +static int ubsa_pre_param(struct ucom_softc *, struct termios *); +static void ubsa_cfg_param(struct ucom_softc *, struct termios *); +static void ubsa_start_read(struct ucom_softc *); +static void ubsa_stop_read(struct ucom_softc *); +static void ubsa_start_write(struct ucom_softc *); +static void ubsa_stop_write(struct ucom_softc *); +static void ubsa_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void ubsa_poll(struct ucom_softc *ucom); + +static const struct usb_config ubsa_config[UBSA_N_TRANSFER] = { + + [UBSA_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UBSA_BSIZE, /* bytes */ + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &ubsa_write_callback, + }, + + [UBSA_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UBSA_BSIZE, /* bytes */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &ubsa_read_callback, + }, + + [UBSA_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &ubsa_intr_callback, + }, +}; + +static const struct ucom_callback ubsa_callback = { + .ucom_cfg_get_status = &ubsa_cfg_get_status, + .ucom_cfg_set_dtr = &ubsa_cfg_set_dtr, + .ucom_cfg_set_rts = &ubsa_cfg_set_rts, + .ucom_cfg_set_break = &ubsa_cfg_set_break, + .ucom_cfg_param = &ubsa_cfg_param, + .ucom_pre_param = &ubsa_pre_param, + .ucom_start_read = &ubsa_start_read, + .ucom_stop_read = &ubsa_stop_read, + .ucom_start_write = &ubsa_start_write, + .ucom_stop_write = &ubsa_stop_write, + .ucom_poll = &ubsa_poll, + .ucom_free = &ubsa_free, +}; + +static const STRUCT_USB_HOST_ID ubsa_devs[] = { + /* AnyData ADU-500A */ + {USB_VPI(USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_500A, 0)}, + /* AnyData ADU-E100A/H */ + {USB_VPI(USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_E100X, 0)}, + /* Axesstel MV100H */ + {USB_VPI(USB_VENDOR_AXESSTEL, USB_PRODUCT_AXESSTEL_DATAMODEM, 0)}, + /* BELKIN F5U103 */ + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U103, 0)}, + /* BELKIN F5U120 */ + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U120, 0)}, + /* GoHubs GO-COM232 */ + {USB_VPI(USB_VENDOR_ETEK, USB_PRODUCT_ETEK_1COM, 0)}, + /* GoHubs GO-COM232 */ + {USB_VPI(USB_VENDOR_GOHUBS, USB_PRODUCT_GOHUBS_GOCOM232, 0)}, + /* Peracom */ + {USB_VPI(USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_SERIAL1, 0)}, +}; + +static device_method_t ubsa_methods[] = { + DEVMETHOD(device_probe, ubsa_probe), + DEVMETHOD(device_attach, ubsa_attach), + DEVMETHOD(device_detach, ubsa_detach), + DEVMETHOD_END +}; + +static devclass_t ubsa_devclass; + +static driver_t ubsa_driver = { + .name = "ubsa", + .methods = ubsa_methods, + .size = sizeof(struct ubsa_softc), +}; + +DRIVER_MODULE(ubsa, uhub, ubsa_driver, ubsa_devclass, NULL, 0); +MODULE_DEPEND(ubsa, ucom, 1, 1, 1); +MODULE_DEPEND(ubsa, usb, 1, 1, 1); +MODULE_VERSION(ubsa, 1); +USB_PNP_HOST_INFO(ubsa_devs); + +static int +ubsa_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UBSA_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UBSA_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(ubsa_devs, sizeof(ubsa_devs), uaa)); +} + +static int +ubsa_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ubsa_softc *sc = device_get_softc(dev); + int error; + + DPRINTF("sc=%p\n", sc); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "ubsa", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + sc->sc_udev = uaa->device; + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = UBSA_IFACE_INDEX; + + error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, ubsa_config, UBSA_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + DPRINTF("could not allocate all pipes\n"); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UBSA_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UBSA_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &ubsa_callback, &sc->sc_mtx); + if (error) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + ubsa_detach(dev); + return (ENXIO); +} + +static int +ubsa_detach(device_t dev) +{ + struct ubsa_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UBSA_N_TRANSFER); + + device_claim_softc(dev); + + ubsa_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(ubsa); + +static void +ubsa_free_softc(struct ubsa_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +ubsa_free(struct ucom_softc *ucom) +{ + ubsa_free_softc(ucom->sc_parent); +} + +static void +ubsa_cfg_request(struct ubsa_softc *sc, uint8_t index, uint16_t value) +{ + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = index; + USETW(req.wValue, value); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } +} + +static void +ubsa_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + ubsa_cfg_request(sc, UBSA_REG_DTR, onoff ? 1 : 0); +} + +static void +ubsa_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + ubsa_cfg_request(sc, UBSA_REG_RTS, onoff ? 1 : 0); +} + +static void +ubsa_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + ubsa_cfg_request(sc, UBSA_REG_BREAK, onoff ? 1 : 0); +} + +static int +ubsa_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + + DPRINTF("sc = %p\n", ucom->sc_parent); + + switch (t->c_ospeed) { + case B0: + case B300: + case B600: + case B1200: + case B2400: + case B4800: + case B9600: + case B19200: + case B38400: + case B57600: + case B115200: + case B230400: + break; + default: + return (EINVAL); + } + return (0); +} + +static void +ubsa_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct ubsa_softc *sc = ucom->sc_parent; + uint16_t value = 0; + + DPRINTF("sc = %p\n", sc); + + switch (t->c_ospeed) { + case B0: + ubsa_cfg_request(sc, UBSA_REG_FLOW_CTRL, 0); + ubsa_cfg_set_dtr(&sc->sc_ucom, 0); + ubsa_cfg_set_rts(&sc->sc_ucom, 0); + break; + case B300: + case B600: + case B1200: + case B2400: + case B4800: + case B9600: + case B19200: + case B38400: + case B57600: + case B115200: + case B230400: + value = B230400 / t->c_ospeed; + ubsa_cfg_request(sc, UBSA_REG_BAUDRATE, value); + break; + default: + return; + } + + if (t->c_cflag & PARENB) + value = (t->c_cflag & PARODD) ? UBSA_PARITY_ODD : UBSA_PARITY_EVEN; + else + value = UBSA_PARITY_NONE; + + ubsa_cfg_request(sc, UBSA_REG_PARITY, value); + + switch (t->c_cflag & CSIZE) { + case CS5: + value = 0; + break; + case CS6: + value = 1; + break; + case CS7: + value = 2; + break; + default: + case CS8: + value = 3; + break; + } + + ubsa_cfg_request(sc, UBSA_REG_DATA_BITS, value); + + value = (t->c_cflag & CSTOPB) ? 1 : 0; + + ubsa_cfg_request(sc, UBSA_REG_STOP_BITS, value); + + value = 0; + if (t->c_cflag & CRTSCTS) + value |= UBSA_FLOW_OCTS | UBSA_FLOW_IRTS; + + if (t->c_iflag & (IXON | IXOFF)) + value |= UBSA_FLOW_OXON | UBSA_FLOW_IXON; + + ubsa_cfg_request(sc, UBSA_REG_FLOW_CTRL, value); +} + +static void +ubsa_start_read(struct ucom_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usbd_transfer_start(sc->sc_xfer[UBSA_INTR_DT_RD]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UBSA_BULK_DT_RD]); +} + +static void +ubsa_stop_read(struct ucom_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usbd_transfer_stop(sc->sc_xfer[UBSA_INTR_DT_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UBSA_BULK_DT_RD]); +} + +static void +ubsa_start_write(struct ucom_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UBSA_BULK_DT_WR]); +} + +static void +ubsa_stop_write(struct ucom_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UBSA_BULK_DT_WR]); +} + +static void +ubsa_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct ubsa_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +ubsa_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ubsa_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UBSA_BSIZE, &actlen)) { + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ubsa_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ubsa_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ubsa_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ubsa_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[4]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen >= (int)sizeof(buf)) { + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, sizeof(buf)); + + /* + * MSR bits need translation from ns16550 to SER_* values. + * LSR bits are ns16550 in hardware and ucom. + */ + sc->sc_msr = 0; + if (buf[3] & UBSA_MSR_CTS) + sc->sc_msr |= SER_CTS; + if (buf[3] & UBSA_MSR_DCD) + sc->sc_msr |= SER_DCD; + if (buf[3] & UBSA_MSR_RI) + sc->sc_msr |= SER_RI; + if (buf[3] & UBSA_MSR_DSR) + sc->sc_msr |= SER_DSR; + sc->sc_lsr = buf[2]; + + DPRINTF("lsr = 0x%02x, msr = 0x%02x\n", + sc->sc_lsr, sc->sc_msr); + + ucom_status_change(&sc->sc_ucom); + } else { + DPRINTF("ignoring short packet, %d bytes\n", actlen); + } + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ubsa_poll(struct ucom_softc *ucom) +{ + struct ubsa_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UBSA_N_TRANSFER); + +} diff --git a/freebsd/sys/dev/usb/serial/ubser.c b/freebsd/sys/dev/usb/serial/ubser.c new file mode 100644 index 00000000..fbac4c76 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/ubser.c @@ -0,0 +1,561 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/*- + * Copyright (c) 2004 Bernd Walter <ticso@FreeBSD.org> + * + * $URL: https://devel.bwct.de/svn/projects/ubser/ubser.c $ + * $Date: 2004-02-29 01:53:10 +0100 (Sun, 29 Feb 2004) $ + * $Author: ticso $ + * $Rev: 1127 $ + */ + +/*- + * Copyright (c) 2001-2002, Shunsuke Akiyama <akiyama@jp.FreeBSD.org>. + * 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. + * 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. + */ + +/*- + * 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. + * + * 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$"); + +/* + * BWCT serial adapter driver + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR ubser_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#define UBSER_UNIT_MAX 32 + +/* Vendor Interface Requests */ +#define VENDOR_GET_NUMSER 0x01 +#define VENDOR_SET_BREAK 0x02 +#define VENDOR_CLEAR_BREAK 0x03 + +#ifdef USB_DEBUG +static int ubser_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ubser, CTLFLAG_RW, 0, "USB ubser"); +SYSCTL_INT(_hw_usb_ubser, OID_AUTO, debug, CTLFLAG_RWTUN, + &ubser_debug, 0, "ubser debug level"); +#endif + +enum { + UBSER_BULK_DT_WR, + UBSER_BULK_DT_RD, + UBSER_N_TRANSFER, +}; + +struct ubser_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom[UBSER_UNIT_MAX]; + + struct usb_xfer *sc_xfer[UBSER_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_tx_size; + + uint8_t sc_numser; + uint8_t sc_iface_no; + uint8_t sc_iface_index; + uint8_t sc_curr_tx_unit; +}; + +/* prototypes */ + +static device_probe_t ubser_probe; +static device_attach_t ubser_attach; +static device_detach_t ubser_detach; +static void ubser_free_softc(struct ubser_softc *); + +static usb_callback_t ubser_write_callback; +static usb_callback_t ubser_read_callback; + +static void ubser_free(struct ucom_softc *); +static int ubser_pre_param(struct ucom_softc *, struct termios *); +static void ubser_cfg_set_break(struct ucom_softc *, uint8_t); +static void ubser_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void ubser_start_read(struct ucom_softc *); +static void ubser_stop_read(struct ucom_softc *); +static void ubser_start_write(struct ucom_softc *); +static void ubser_stop_write(struct ucom_softc *); +static void ubser_poll(struct ucom_softc *ucom); + +static const struct usb_config ubser_config[UBSER_N_TRANSFER] = { + + [UBSER_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &ubser_write_callback, + }, + + [UBSER_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &ubser_read_callback, + }, +}; + +static const struct ucom_callback ubser_callback = { + .ucom_cfg_set_break = &ubser_cfg_set_break, + .ucom_cfg_get_status = &ubser_cfg_get_status, + .ucom_pre_param = &ubser_pre_param, + .ucom_start_read = &ubser_start_read, + .ucom_stop_read = &ubser_stop_read, + .ucom_start_write = &ubser_start_write, + .ucom_stop_write = &ubser_stop_write, + .ucom_poll = &ubser_poll, + .ucom_free = &ubser_free, +}; + +static device_method_t ubser_methods[] = { + DEVMETHOD(device_probe, ubser_probe), + DEVMETHOD(device_attach, ubser_attach), + DEVMETHOD(device_detach, ubser_detach), + DEVMETHOD_END +}; + +static devclass_t ubser_devclass; + +static driver_t ubser_driver = { + .name = "ubser", + .methods = ubser_methods, + .size = sizeof(struct ubser_softc), +}; + +DRIVER_MODULE(ubser, uhub, ubser_driver, ubser_devclass, NULL, 0); +MODULE_DEPEND(ubser, ucom, 1, 1, 1); +MODULE_DEPEND(ubser, usb, 1, 1, 1); +MODULE_VERSION(ubser, 1); + +static int +ubser_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + /* check if this is a BWCT vendor specific ubser interface */ + if ((strcmp(usb_get_manufacturer(uaa->device), "BWCT") == 0) && + (uaa->info.bInterfaceClass == 0xff) && + (uaa->info.bInterfaceSubClass == 0x00)) + return (0); + + return (ENXIO); +} + +static int +ubser_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ubser_softc *sc = device_get_softc(dev); + struct usb_device_request req; + uint8_t n; + int error; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "ubser", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = uaa->info.bIfaceIndex; + sc->sc_udev = uaa->device; + + /* get number of serials */ + req.bmRequestType = UT_READ_VENDOR_INTERFACE; + req.bRequest = VENDOR_GET_NUMSER; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + error = usbd_do_request_flags(uaa->device, NULL, + &req, &sc->sc_numser, + 0, NULL, USB_DEFAULT_TIMEOUT); + + if (error || (sc->sc_numser == 0)) { + device_printf(dev, "failed to get number " + "of serial ports: %s\n", + usbd_errstr(error)); + goto detach; + } + if (sc->sc_numser > UBSER_UNIT_MAX) + sc->sc_numser = UBSER_UNIT_MAX; + + device_printf(dev, "found %i serials\n", sc->sc_numser); + + error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, ubser_config, UBSER_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + goto detach; + } + sc->sc_tx_size = usbd_xfer_max_len(sc->sc_xfer[UBSER_BULK_DT_WR]); + + if (sc->sc_tx_size == 0) { + DPRINTFN(0, "invalid tx_size\n"); + goto detach; + } + /* initialize port numbers */ + + for (n = 0; n < sc->sc_numser; n++) { + sc->sc_ucom[n].sc_portno = n; + } + + error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, + sc->sc_numser, sc, &ubser_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UBSER_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UBSER_BULK_DT_RD]); + usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + return (0); /* success */ + +detach: + ubser_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ubser_detach(device_t dev) +{ + struct ubser_softc *sc = device_get_softc(dev); + + DPRINTF("\n"); + + ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UBSER_N_TRANSFER); + + device_claim_softc(dev); + + ubser_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(ubser); + +static void +ubser_free_softc(struct ubser_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +ubser_free(struct ucom_softc *ucom) +{ + ubser_free_softc(ucom->sc_parent); +} + +static int +ubser_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + DPRINTF("\n"); + + /* + * The firmware on our devices can only do 8n1@9600bps + * without handshake. + * We refuse to accept other configurations. + */ + + /* ensure 9600bps */ + switch (t->c_ospeed) { + case 9600: + break; + default: + return (EINVAL); + } + + /* 2 stop bits not possible */ + if (t->c_cflag & CSTOPB) + return (EINVAL); + + /* XXX parity handling not possible with current firmware */ + if (t->c_cflag & PARENB) + return (EINVAL); + + /* we can only do 8 data bits */ + switch (t->c_cflag & CSIZE) { + case CS8: + break; + default: + return (EINVAL); + } + + /* we can't do any kind of hardware handshaking */ + if ((t->c_cflag & + (CRTS_IFLOW | CDTR_IFLOW | CDSR_OFLOW | CCAR_OFLOW)) != 0) + return (EINVAL); + + /* + * XXX xon/xoff not supported by the firmware! + * This is handled within FreeBSD only and may overflow buffers + * because of delayed reaction due to device buffering. + */ + + return (0); +} + +static __inline void +ubser_inc_tx_unit(struct ubser_softc *sc) +{ + sc->sc_curr_tx_unit++; + if (sc->sc_curr_tx_unit >= sc->sc_numser) { + sc->sc_curr_tx_unit = 0; + } +} + +static void +ubser_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ubser_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[1]; + uint8_t first_unit = sc->sc_curr_tx_unit; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + do { + if (ucom_get_data(sc->sc_ucom + sc->sc_curr_tx_unit, + pc, 1, sc->sc_tx_size - 1, + &actlen)) { + + buf[0] = sc->sc_curr_tx_unit; + + usbd_copy_in(pc, 0, buf, 1); + + usbd_xfer_set_frame_len(xfer, 0, actlen + 1); + usbd_transfer_submit(xfer); + + ubser_inc_tx_unit(sc); /* round robin */ + + break; + } + ubser_inc_tx_unit(sc); + + } while (sc->sc_curr_tx_unit != first_unit); + + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ubser_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ubser_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[1]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen < 1) { + DPRINTF("invalid actlen=0!\n"); + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, 1); + + if (buf[0] >= sc->sc_numser) { + DPRINTF("invalid serial number!\n"); + goto tr_setup; + } + ucom_put_data(sc->sc_ucom + buf[0], pc, 1, actlen - 1); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ubser_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ubser_softc *sc = ucom->sc_parent; + uint8_t x = ucom->sc_portno; + struct usb_device_request req; + usb_error_t err; + + if (onoff) { + + req.bmRequestType = UT_READ_VENDOR_INTERFACE; + req.bRequest = VENDOR_SET_BREAK; + req.wValue[0] = x; + req.wValue[1] = 0; + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + err = ucom_cfg_do_request(sc->sc_udev, ucom, + &req, NULL, 0, 1000); + if (err) { + DPRINTFN(0, "send break failed, error=%s\n", + usbd_errstr(err)); + } + } +} + +static void +ubser_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + /* fake status bits */ + *lsr = 0; + *msr = SER_DCD; +} + +static void +ubser_start_read(struct ucom_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_RD]); +} + +static void +ubser_stop_read(struct ucom_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UBSER_BULK_DT_RD]); +} + +static void +ubser_start_write(struct ucom_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_WR]); +} + +static void +ubser_stop_write(struct ucom_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UBSER_BULK_DT_WR]); +} + +static void +ubser_poll(struct ucom_softc *ucom) +{ + struct ubser_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UBSER_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/uchcom.c b/freebsd/sys/dev/usb/serial/uchcom.c new file mode 100644 index 00000000..6daef909 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/uchcom.c @@ -0,0 +1,880 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $NetBSD: uchcom.c,v 1.1 2007/09/03 17:57:37 tshiozak Exp $ */ + +/*- + * Copyright (c) 2007, Takanori Watanabe + * 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. + * 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. + */ + +/* + * Copyright (c) 2007 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Takuya SHIOZAKI (tshiozak@netbsd.org). + * + * 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 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$"); + +/* + * Driver for WinChipHead CH341/340, the worst USB-serial chip in the + * world. + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR uchcom_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#ifdef USB_DEBUG +static int uchcom_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uchcom, CTLFLAG_RW, 0, "USB uchcom"); +SYSCTL_INT(_hw_usb_uchcom, OID_AUTO, debug, CTLFLAG_RWTUN, + &uchcom_debug, 0, "uchcom debug level"); +#endif + +#define UCHCOM_IFACE_INDEX 0 +#define UCHCOM_CONFIG_INDEX 0 + +#define UCHCOM_REV_CH340 0x0250 +#define UCHCOM_INPUT_BUF_SIZE 8 + +#define UCHCOM_REQ_GET_VERSION 0x5F +#define UCHCOM_REQ_READ_REG 0x95 +#define UCHCOM_REQ_WRITE_REG 0x9A +#define UCHCOM_REQ_RESET 0xA1 +#define UCHCOM_REQ_SET_DTRRTS 0xA4 + +#define UCHCOM_REG_STAT1 0x06 +#define UCHCOM_REG_STAT2 0x07 +#define UCHCOM_REG_BPS_PRE 0x12 +#define UCHCOM_REG_BPS_DIV 0x13 +#define UCHCOM_REG_BPS_MOD 0x14 +#define UCHCOM_REG_BPS_PAD 0x0F +#define UCHCOM_REG_BREAK1 0x05 +#define UCHCOM_REG_BREAK2 0x18 +#define UCHCOM_REG_LCR1 0x18 +#define UCHCOM_REG_LCR2 0x25 + +#define UCHCOM_VER_20 0x20 + +#define UCHCOM_BASE_UNKNOWN 0 +#define UCHCOM_BPS_MOD_BASE 20000000 +#define UCHCOM_BPS_MOD_BASE_OFS 1100 + +#define UCHCOM_DTR_MASK 0x20 +#define UCHCOM_RTS_MASK 0x40 + +#define UCHCOM_BRK1_MASK 0x01 +#define UCHCOM_BRK2_MASK 0x40 + +#define UCHCOM_LCR1_MASK 0xAF +#define UCHCOM_LCR2_MASK 0x07 +#define UCHCOM_LCR1_PARENB 0x80 +#define UCHCOM_LCR2_PAREVEN 0x07 +#define UCHCOM_LCR2_PARODD 0x06 +#define UCHCOM_LCR2_PARMARK 0x05 +#define UCHCOM_LCR2_PARSPACE 0x04 + +#define UCHCOM_INTR_STAT1 0x02 +#define UCHCOM_INTR_STAT2 0x03 +#define UCHCOM_INTR_LEAST 4 + +#define UCHCOM_BULK_BUF_SIZE 1024 /* bytes */ + +enum { + UCHCOM_BULK_DT_WR, + UCHCOM_BULK_DT_RD, + UCHCOM_INTR_DT_RD, + UCHCOM_N_TRANSFER, +}; + +struct uchcom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UCHCOM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_dtr; /* local copy */ + uint8_t sc_rts; /* local copy */ + uint8_t sc_version; + uint8_t sc_msr; + uint8_t sc_lsr; /* local status register */ +}; + +struct uchcom_divider { + uint8_t dv_prescaler; + uint8_t dv_div; + uint8_t dv_mod; +}; + +struct uchcom_divider_record { + uint32_t dvr_high; + uint32_t dvr_low; + uint32_t dvr_base_clock; + struct uchcom_divider dvr_divider; +}; + +static const struct uchcom_divider_record dividers[] = +{ + {307200, 307200, UCHCOM_BASE_UNKNOWN, {7, 0xD9, 0}}, + {921600, 921600, UCHCOM_BASE_UNKNOWN, {7, 0xF3, 0}}, + {2999999, 23530, 6000000, {3, 0, 0}}, + {23529, 2942, 750000, {2, 0, 0}}, + {2941, 368, 93750, {1, 0, 0}}, + {367, 1, 11719, {0, 0, 0}}, +}; + +#define NUM_DIVIDERS nitems(dividers) + +static const STRUCT_USB_HOST_ID uchcom_devs[] = { + {USB_VPI(USB_VENDOR_WCH, USB_PRODUCT_WCH_CH341SER, 0)}, + {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341SER, 0)}, + {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341SER_2, 0)}, +}; + +/* protypes */ + +static void uchcom_free(struct ucom_softc *); +static int uchcom_pre_param(struct ucom_softc *, struct termios *); +static void uchcom_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void uchcom_cfg_open(struct ucom_softc *ucom); +static void uchcom_cfg_param(struct ucom_softc *, struct termios *); +static void uchcom_cfg_set_break(struct ucom_softc *, uint8_t); +static void uchcom_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uchcom_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uchcom_start_read(struct ucom_softc *); +static void uchcom_start_write(struct ucom_softc *); +static void uchcom_stop_read(struct ucom_softc *); +static void uchcom_stop_write(struct ucom_softc *); +static void uchcom_update_version(struct uchcom_softc *); +static void uchcom_convert_status(struct uchcom_softc *, uint8_t); +static void uchcom_update_status(struct uchcom_softc *); +static void uchcom_set_dtr_rts(struct uchcom_softc *); +static int uchcom_calc_divider_settings(struct uchcom_divider *, uint32_t); +static void uchcom_set_baudrate(struct uchcom_softc *, uint32_t); +static void uchcom_poll(struct ucom_softc *ucom); + +static device_probe_t uchcom_probe; +static device_attach_t uchcom_attach; +static device_detach_t uchcom_detach; +static void uchcom_free_softc(struct uchcom_softc *); + +static usb_callback_t uchcom_intr_callback; +static usb_callback_t uchcom_write_callback; +static usb_callback_t uchcom_read_callback; + +static const struct usb_config uchcom_config_data[UCHCOM_N_TRANSFER] = { + + [UCHCOM_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UCHCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uchcom_write_callback, + }, + + [UCHCOM_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UCHCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uchcom_read_callback, + }, + + [UCHCOM_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &uchcom_intr_callback, + }, +}; + +static struct ucom_callback uchcom_callback = { + .ucom_cfg_get_status = &uchcom_cfg_get_status, + .ucom_cfg_set_dtr = &uchcom_cfg_set_dtr, + .ucom_cfg_set_rts = &uchcom_cfg_set_rts, + .ucom_cfg_set_break = &uchcom_cfg_set_break, + .ucom_cfg_open = &uchcom_cfg_open, + .ucom_cfg_param = &uchcom_cfg_param, + .ucom_pre_param = &uchcom_pre_param, + .ucom_start_read = &uchcom_start_read, + .ucom_stop_read = &uchcom_stop_read, + .ucom_start_write = &uchcom_start_write, + .ucom_stop_write = &uchcom_stop_write, + .ucom_poll = &uchcom_poll, + .ucom_free = &uchcom_free, +}; + +/* ---------------------------------------------------------------------- + * driver entry points + */ + +static int +uchcom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UCHCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UCHCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uchcom_devs, sizeof(uchcom_devs), uaa)); +} + +static int +uchcom_attach(device_t dev) +{ + struct uchcom_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + int error; + uint8_t iface_index; + + DPRINTFN(11, "\n"); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uchcom", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + sc->sc_udev = uaa->device; + + switch (uaa->info.bcdDevice) { + case UCHCOM_REV_CH340: + device_printf(dev, "CH340 detected\n"); + break; + default: + device_printf(dev, "CH341 detected\n"); + break; + } + + iface_index = UCHCOM_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, + &iface_index, sc->sc_xfer, uchcom_config_data, + UCHCOM_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + DPRINTF("one or more missing USB endpoints, " + "error=%s\n", usbd_errstr(error)); + goto detach; + } + + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UCHCOM_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UCHCOM_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uchcom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + uchcom_detach(dev); + return (ENXIO); +} + +static int +uchcom_detach(device_t dev) +{ + struct uchcom_softc *sc = device_get_softc(dev); + + DPRINTFN(11, "\n"); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UCHCOM_N_TRANSFER); + + device_claim_softc(dev); + + uchcom_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(uchcom); + +static void +uchcom_free_softc(struct uchcom_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +uchcom_free(struct ucom_softc *ucom) +{ + uchcom_free_softc(ucom->sc_parent); +} + +/* ---------------------------------------------------------------------- + * low level i/o + */ + +static void +uchcom_ctrl_write(struct uchcom_softc *sc, uint8_t reqno, + uint16_t value, uint16_t index) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = reqno; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, + &sc->sc_ucom, &req, NULL, 0, 1000); +} + +static void +uchcom_ctrl_read(struct uchcom_softc *sc, uint8_t reqno, + uint16_t value, uint16_t index, void *buf, uint16_t buflen) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = reqno; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, buflen); + + ucom_cfg_do_request(sc->sc_udev, + &sc->sc_ucom, &req, buf, USB_SHORT_XFER_OK, 1000); +} + +static void +uchcom_write_reg(struct uchcom_softc *sc, + uint8_t reg1, uint8_t val1, uint8_t reg2, uint8_t val2) +{ + DPRINTF("0x%02X<-0x%02X, 0x%02X<-0x%02X\n", + (unsigned)reg1, (unsigned)val1, + (unsigned)reg2, (unsigned)val2); + uchcom_ctrl_write( + sc, UCHCOM_REQ_WRITE_REG, + reg1 | ((uint16_t)reg2 << 8), val1 | ((uint16_t)val2 << 8)); +} + +static void +uchcom_read_reg(struct uchcom_softc *sc, + uint8_t reg1, uint8_t *rval1, uint8_t reg2, uint8_t *rval2) +{ + uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; + + uchcom_ctrl_read( + sc, UCHCOM_REQ_READ_REG, + reg1 | ((uint16_t)reg2 << 8), 0, buf, sizeof(buf)); + + DPRINTF("0x%02X->0x%02X, 0x%02X->0x%02X\n", + (unsigned)reg1, (unsigned)buf[0], + (unsigned)reg2, (unsigned)buf[1]); + + if (rval1) + *rval1 = buf[0]; + if (rval2) + *rval2 = buf[1]; +} + +static void +uchcom_get_version(struct uchcom_softc *sc, uint8_t *rver) +{ + uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; + + uchcom_ctrl_read(sc, UCHCOM_REQ_GET_VERSION, 0, 0, buf, sizeof(buf)); + + if (rver) + *rver = buf[0]; +} + +static void +uchcom_get_status(struct uchcom_softc *sc, uint8_t *rval) +{ + uchcom_read_reg(sc, UCHCOM_REG_STAT1, rval, UCHCOM_REG_STAT2, NULL); +} + +static void +uchcom_set_dtr_rts_10(struct uchcom_softc *sc, uint8_t val) +{ + uchcom_write_reg(sc, UCHCOM_REG_STAT1, val, UCHCOM_REG_STAT1, val); +} + +static void +uchcom_set_dtr_rts_20(struct uchcom_softc *sc, uint8_t val) +{ + uchcom_ctrl_write(sc, UCHCOM_REQ_SET_DTRRTS, val, 0); +} + + +/* ---------------------------------------------------------------------- + * middle layer + */ + +static void +uchcom_update_version(struct uchcom_softc *sc) +{ + uchcom_get_version(sc, &sc->sc_version); +} + +static void +uchcom_convert_status(struct uchcom_softc *sc, uint8_t cur) +{ + sc->sc_dtr = !(cur & UCHCOM_DTR_MASK); + sc->sc_rts = !(cur & UCHCOM_RTS_MASK); + + cur = ~cur & 0x0F; + sc->sc_msr = (cur << 4) | ((sc->sc_msr >> 4) ^ cur); +} + +static void +uchcom_update_status(struct uchcom_softc *sc) +{ + uint8_t cur; + + uchcom_get_status(sc, &cur); + uchcom_convert_status(sc, cur); +} + + +static void +uchcom_set_dtr_rts(struct uchcom_softc *sc) +{ + uint8_t val = 0; + + if (sc->sc_dtr) + val |= UCHCOM_DTR_MASK; + if (sc->sc_rts) + val |= UCHCOM_RTS_MASK; + + if (sc->sc_version < UCHCOM_VER_20) + uchcom_set_dtr_rts_10(sc, ~val); + else + uchcom_set_dtr_rts_20(sc, ~val); +} + +static void +uchcom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uchcom_softc *sc = ucom->sc_parent; + uint8_t brk1; + uint8_t brk2; + + uchcom_read_reg(sc, UCHCOM_REG_BREAK1, &brk1, UCHCOM_REG_BREAK2, &brk2); + if (onoff) { + /* on - clear bits */ + brk1 &= ~UCHCOM_BRK1_MASK; + brk2 &= ~UCHCOM_BRK2_MASK; + } else { + /* off - set bits */ + brk1 |= UCHCOM_BRK1_MASK; + brk2 |= UCHCOM_BRK2_MASK; + } + uchcom_write_reg(sc, UCHCOM_REG_BREAK1, brk1, UCHCOM_REG_BREAK2, brk2); +} + +static int +uchcom_calc_divider_settings(struct uchcom_divider *dp, uint32_t rate) +{ + const struct uchcom_divider_record *rp; + uint32_t div; + uint32_t rem; + uint32_t mod; + uint8_t i; + + /* find record */ + for (i = 0; i != NUM_DIVIDERS; i++) { + if (dividers[i].dvr_high >= rate && + dividers[i].dvr_low <= rate) { + rp = ÷rs[i]; + goto found; + } + } + return (-1); + +found: + dp->dv_prescaler = rp->dvr_divider.dv_prescaler; + if (rp->dvr_base_clock == UCHCOM_BASE_UNKNOWN) + dp->dv_div = rp->dvr_divider.dv_div; + else { + div = rp->dvr_base_clock / rate; + rem = rp->dvr_base_clock % rate; + if (div == 0 || div >= 0xFF) + return (-1); + if ((rem << 1) >= rate) + div += 1; + dp->dv_div = (uint8_t)-div; + } + + mod = (UCHCOM_BPS_MOD_BASE / rate) + UCHCOM_BPS_MOD_BASE_OFS; + mod = mod + (mod / 2); + + dp->dv_mod = (mod + 0xFF) / 0x100; + + return (0); +} + +static void +uchcom_set_baudrate(struct uchcom_softc *sc, uint32_t rate) +{ + struct uchcom_divider dv; + + if (uchcom_calc_divider_settings(&dv, rate)) + return; + + uchcom_write_reg(sc, + UCHCOM_REG_BPS_PRE, dv.dv_prescaler, + UCHCOM_REG_BPS_DIV, dv.dv_div); + uchcom_write_reg(sc, + UCHCOM_REG_BPS_MOD, dv.dv_mod, + UCHCOM_REG_BPS_PAD, 0); +} + +/* ---------------------------------------------------------------------- + * methods for ucom + */ +static void +uchcom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + /* XXX Note: sc_lsr is always zero */ + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uchcom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + sc->sc_dtr = onoff; + uchcom_set_dtr_rts(sc); +} + +static void +uchcom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + sc->sc_rts = onoff; + uchcom_set_dtr_rts(sc); +} + +static void +uchcom_cfg_open(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + uchcom_update_version(sc); + uchcom_update_status(sc); +} + +static int +uchcom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uchcom_divider dv; + + switch (t->c_cflag & CSIZE) { + case CS8: + break; + default: + return (EIO); + } + + if (uchcom_calc_divider_settings(&dv, t->c_ospeed)) { + return (EIO); + } + return (0); /* success */ +} + +static void +uchcom_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + uchcom_get_version(sc, 0); + uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, 0, 0); + uchcom_set_baudrate(sc, t->c_ospeed); + uchcom_read_reg(sc, 0x18, 0, 0x25, 0); + uchcom_write_reg(sc, 0x18, 0x50, 0x25, 0x00); + uchcom_update_status(sc); + uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, 0x501f, 0xd90a); + uchcom_set_baudrate(sc, t->c_ospeed); + uchcom_set_dtr_rts(sc); + uchcom_update_status(sc); +} + +static void +uchcom_start_read(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usbd_transfer_start(sc->sc_xfer[UCHCOM_INTR_DT_RD]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UCHCOM_BULK_DT_RD]); +} + +static void +uchcom_stop_read(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usbd_transfer_stop(sc->sc_xfer[UCHCOM_INTR_DT_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UCHCOM_BULK_DT_RD]); +} + +static void +uchcom_start_write(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UCHCOM_BULK_DT_WR]); +} + +static void +uchcom_stop_write(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UCHCOM_BULK_DT_WR]); +} + +/* ---------------------------------------------------------------------- + * callback when the modem status is changed. + */ +static void +uchcom_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uchcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[UCHCOM_INTR_LEAST]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("actlen = %u\n", actlen); + + if (actlen >= UCHCOM_INTR_LEAST) { + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, UCHCOM_INTR_LEAST); + + DPRINTF("data = 0x%02X 0x%02X 0x%02X 0x%02X\n", + (unsigned)buf[0], (unsigned)buf[1], + (unsigned)buf[2], (unsigned)buf[3]); + + uchcom_convert_status(sc, buf[UCHCOM_INTR_STAT1]); + ucom_status_change(&sc->sc_ucom); + } + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +uchcom_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uchcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + usbd_xfer_max_len(xfer), &actlen)) { + + DPRINTF("actlen = %d\n", actlen); + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +uchcom_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uchcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen > 0) { + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + } + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +uchcom_poll(struct ucom_softc *ucom) +{ + struct uchcom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UCHCOM_N_TRANSFER); +} + +static device_method_t uchcom_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uchcom_probe), + DEVMETHOD(device_attach, uchcom_attach), + DEVMETHOD(device_detach, uchcom_detach), + DEVMETHOD_END +}; + +static driver_t uchcom_driver = { + .name = "uchcom", + .methods = uchcom_methods, + .size = sizeof(struct uchcom_softc) +}; + +static devclass_t uchcom_devclass; + +DRIVER_MODULE(uchcom, uhub, uchcom_driver, uchcom_devclass, NULL, 0); +MODULE_DEPEND(uchcom, ucom, 1, 1, 1); +MODULE_DEPEND(uchcom, usb, 1, 1, 1); +MODULE_VERSION(uchcom, 1); +USB_PNP_HOST_INFO(uchcom_devs); diff --git a/freebsd/sys/dev/usb/serial/ucycom.c b/freebsd/sys/dev/usb/serial/ucycom.c new file mode 100644 index 00000000..09f90ab3 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/ucycom.c @@ -0,0 +1,614 @@ +#include <machine/rtems-bsd-kernel-space.h> + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * 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. + */ + +/* + * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to + * RS232 bridges. + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbhid.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR usb_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#define UCYCOM_MAX_IOLEN (1024 + 2) /* bytes */ + +#define UCYCOM_IFACE_INDEX 0 + +enum { + UCYCOM_CTRL_RD, + UCYCOM_INTR_RD, + UCYCOM_N_TRANSFER, +}; + +struct ucycom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_device *sc_udev; + struct usb_xfer *sc_xfer[UCYCOM_N_TRANSFER]; + struct mtx sc_mtx; + + uint32_t sc_model; +#define MODEL_CY7C63743 0x63743 +#define MODEL_CY7C64013 0x64013 + + uint16_t sc_flen; /* feature report length */ + uint16_t sc_ilen; /* input report length */ + uint16_t sc_olen; /* output report length */ + + uint8_t sc_fid; /* feature report id */ + uint8_t sc_iid; /* input report id */ + uint8_t sc_oid; /* output report id */ + 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_iface_no; + uint8_t sc_temp_cfg[32]; +}; + +/* prototypes */ + +static device_probe_t ucycom_probe; +static device_attach_t ucycom_attach; +static device_detach_t ucycom_detach; +static void ucycom_free_softc(struct ucycom_softc *); + +static usb_callback_t ucycom_ctrl_write_callback; +static usb_callback_t ucycom_intr_read_callback; + +static void ucycom_free(struct ucom_softc *); +static void ucycom_cfg_open(struct ucom_softc *); +static void ucycom_start_read(struct ucom_softc *); +static void ucycom_stop_read(struct ucom_softc *); +static void ucycom_start_write(struct ucom_softc *); +static void ucycom_stop_write(struct ucom_softc *); +static void ucycom_cfg_write(struct ucycom_softc *, uint32_t, uint8_t); +static int ucycom_pre_param(struct ucom_softc *, struct termios *); +static void ucycom_cfg_param(struct ucom_softc *, struct termios *); +static void ucycom_poll(struct ucom_softc *ucom); + +static const struct usb_config ucycom_config[UCYCOM_N_TRANSFER] = { + + [UCYCOM_CTRL_RD] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = (sizeof(struct usb_device_request) + UCYCOM_MAX_IOLEN), + .callback = &ucycom_ctrl_write_callback, + .timeout = 1000, /* 1 second */ + }, + + [UCYCOM_INTR_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = UCYCOM_MAX_IOLEN, + .callback = &ucycom_intr_read_callback, + }, +}; + +static const struct ucom_callback ucycom_callback = { + .ucom_cfg_param = &ucycom_cfg_param, + .ucom_cfg_open = &ucycom_cfg_open, + .ucom_pre_param = &ucycom_pre_param, + .ucom_start_read = &ucycom_start_read, + .ucom_stop_read = &ucycom_stop_read, + .ucom_start_write = &ucycom_start_write, + .ucom_stop_write = &ucycom_stop_write, + .ucom_poll = &ucycom_poll, + .ucom_free = &ucycom_free, +}; + +static device_method_t ucycom_methods[] = { + DEVMETHOD(device_probe, ucycom_probe), + DEVMETHOD(device_attach, ucycom_attach), + DEVMETHOD(device_detach, ucycom_detach), + DEVMETHOD_END +}; + +static devclass_t ucycom_devclass; + +static driver_t ucycom_driver = { + .name = "ucycom", + .methods = ucycom_methods, + .size = sizeof(struct ucycom_softc), +}; + +/* + * Supported devices + */ +static const STRUCT_USB_HOST_ID ucycom_devs[] = { + {USB_VPI(USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013)}, +}; + +DRIVER_MODULE(ucycom, uhub, ucycom_driver, ucycom_devclass, NULL, 0); +MODULE_DEPEND(ucycom, ucom, 1, 1, 1); +MODULE_DEPEND(ucycom, usb, 1, 1, 1); +MODULE_VERSION(ucycom, 1); +USB_PNP_HOST_INFO(ucycom_devs); + +#define UCYCOM_DEFAULT_RATE 4800 +#define UCYCOM_DEFAULT_CFG 0x03 /* N-8-1 */ + +static int +ucycom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != 0) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UCYCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(ucycom_devs, sizeof(ucycom_devs), uaa)); +} + +static int +ucycom_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ucycom_softc *sc = device_get_softc(dev); + void *urd_ptr = NULL; + int32_t error; + uint16_t urd_len; + uint8_t iface_index; + + sc->sc_udev = uaa->device; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "ucycom", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + DPRINTF("\n"); + + /* get chip model */ + sc->sc_model = USB_GET_DRIVER_INFO(uaa); + if (sc->sc_model == 0) { + device_printf(dev, "unsupported device\n"); + goto detach; + } + device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model); + + /* get report descriptor */ + + error = usbd_req_get_hid_desc(uaa->device, NULL, + &urd_ptr, &urd_len, M_USBDEV, + UCYCOM_IFACE_INDEX); + + if (error) { + device_printf(dev, "failed to get report " + "descriptor: %s\n", + usbd_errstr(error)); + goto detach; + } + /* get report sizes */ + + sc->sc_flen = hid_report_size(urd_ptr, urd_len, hid_feature, &sc->sc_fid); + sc->sc_ilen = hid_report_size(urd_ptr, urd_len, hid_input, &sc->sc_iid); + sc->sc_olen = hid_report_size(urd_ptr, urd_len, hid_output, &sc->sc_oid); + + if ((sc->sc_ilen > UCYCOM_MAX_IOLEN) || (sc->sc_ilen < 1) || + (sc->sc_olen > UCYCOM_MAX_IOLEN) || (sc->sc_olen < 2) || + (sc->sc_flen > UCYCOM_MAX_IOLEN) || (sc->sc_flen < 5)) { + device_printf(dev, "invalid report size i=%d, o=%d, f=%d, max=%d\n", + sc->sc_ilen, sc->sc_olen, sc->sc_flen, + UCYCOM_MAX_IOLEN); + goto detach; + } + sc->sc_iface_no = uaa->info.bIfaceNum; + + iface_index = UCYCOM_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, ucycom_config, UCYCOM_N_TRANSFER, + sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB " + "transfers failed\n"); + goto detach; + } + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &ucycom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + if (urd_ptr) { + free(urd_ptr, M_USBDEV); + } + + return (0); /* success */ + +detach: + if (urd_ptr) { + free(urd_ptr, M_USBDEV); + } + ucycom_detach(dev); + return (ENXIO); +} + +static int +ucycom_detach(device_t dev) +{ + struct ucycom_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UCYCOM_N_TRANSFER); + + device_claim_softc(dev); + + ucycom_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(ucycom); + +static void +ucycom_free_softc(struct ucycom_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +ucycom_free(struct ucom_softc *ucom) +{ + ucycom_free_softc(ucom->sc_parent); +} + +static void +ucycom_cfg_open(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + /* set default configuration */ + ucycom_cfg_write(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG); +} + +static void +ucycom_start_read(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UCYCOM_INTR_RD]); +} + +static void +ucycom_stop_read(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UCYCOM_INTR_RD]); +} + +static void +ucycom_start_write(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UCYCOM_CTRL_RD]); +} + +static void +ucycom_stop_write(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UCYCOM_CTRL_RD]); +} + +static void +ucycom_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucycom_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc0, *pc1; + uint8_t data[2]; + uint8_t offset; + uint32_t actlen; + + pc0 = usbd_xfer_get_frame(xfer, 0); + pc1 = usbd_xfer_get_frame(xfer, 1); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + case USB_ST_SETUP: + + switch (sc->sc_model) { + case MODEL_CY7C63743: + offset = 1; + break; + case MODEL_CY7C64013: + offset = 2; + break; + default: + offset = 0; + break; + } + + if (ucom_get_data(&sc->sc_ucom, pc1, offset, + sc->sc_olen - offset, &actlen)) { + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, UHID_OUTPUT_REPORT, sc->sc_oid); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, sc->sc_olen); + + switch (sc->sc_model) { + case MODEL_CY7C63743: + data[0] = actlen; + break; + case MODEL_CY7C64013: + data[0] = 0; + data[1] = actlen; + break; + default: + break; + } + + usbd_copy_in(pc0, 0, &req, sizeof(req)); + usbd_copy_in(pc1, 0, data, offset); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, sc->sc_olen); + usbd_xfer_set_frames(xfer, sc->sc_olen ? 2 : 1); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + return; + } + DPRINTF("error=%s\n", + usbd_errstr(error)); + goto tr_transferred; + } +} + +static void +ucycom_cfg_write(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg) +{ + struct usb_device_request req; + uint16_t len; + usb_error_t err; + + len = sc->sc_flen; + if (len > sizeof(sc->sc_temp_cfg)) { + len = sizeof(sc->sc_temp_cfg); + } + sc->sc_cfg = cfg; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, UHID_FEATURE_REPORT, sc->sc_fid); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, len); + + sc->sc_temp_cfg[0] = (baud & 0xff); + sc->sc_temp_cfg[1] = (baud >> 8) & 0xff; + sc->sc_temp_cfg[2] = (baud >> 16) & 0xff; + sc->sc_temp_cfg[3] = (baud >> 24) & 0xff; + sc->sc_temp_cfg[4] = cfg; + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, sc->sc_temp_cfg, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } +} + +static int +ucycom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + switch (t->c_ospeed) { + 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); + } + return (0); +} + +static void +ucycom_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct ucycom_softc *sc = ucom->sc_parent; + uint8_t cfg; + + DPRINTF("\n"); + + if (t->c_cflag & CIGNORE) { + cfg = sc->sc_cfg; + } else { + cfg = 0; + switch (t->c_cflag & CSIZE) { + default: + case CS8: + ++cfg; + case CS7: + ++cfg; + case CS6: + ++cfg; + case CS5: + break; + } + + 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; + } + + ucycom_cfg_write(sc, t->c_ospeed, cfg); +} + +static void +ucycom_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ucycom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[2]; + uint32_t offset; + int len; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + pc = usbd_xfer_get_frame(xfer, 0); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + switch (sc->sc_model) { + case MODEL_CY7C63743: + if (actlen < 1) { + goto tr_setup; + } + usbd_copy_out(pc, 0, buf, 1); + + sc->sc_ist = buf[0] & ~0x07; + len = buf[0] & 0x07; + + actlen--; + offset = 1; + + break; + + case MODEL_CY7C64013: + if (actlen < 2) { + goto tr_setup; + } + usbd_copy_out(pc, 0, buf, 2); + + sc->sc_ist = buf[0] & ~0x07; + len = buf[1]; + + actlen -= 2; + offset = 2; + + break; + + default: + DPRINTFN(0, "unsupported model number\n"); + goto tr_setup; + } + + if (len > actlen) + len = actlen; + if (len) + ucom_put_data(&sc->sc_ucom, pc, offset, len); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, sc->sc_ilen); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +ucycom_poll(struct ucom_softc *ucom) +{ + struct ucycom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UCYCOM_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/ufoma.c b/freebsd/sys/dev/usb/serial/ufoma.c new file mode 100644 index 00000000..6a481add --- /dev/null +++ b/freebsd/sys/dev/usb/serial/ufoma.c @@ -0,0 +1,1270 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); +#define UFOMA_HANDSFREE +/*- + * Copyright (c) 2005, Takanori Watanabe + * Copyright (c) 2003, M. Warner Losh <imp@FreeBSD.org>. + * 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. + * 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. + */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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 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. + */ + +/* + * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf + * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf + */ + +/* + * TODO: + * - Implement a Call Device for modems without multiplexed commands. + */ + +/* + * NOTE: all function names beginning like "ufoma_cfg_" can only + * be called from within the config thread function ! + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> +#include <sys/sbuf.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_cdc.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR usb_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +typedef struct ufoma_mobile_acm_descriptor { + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bType; + uint8_t bMode[1]; +} __packed usb_mcpc_acm_descriptor; + +#define UISUBCLASS_MCPC 0x88 + +#define UDESC_VS_INTERFACE 0x44 +#define UDESCSUB_MCPC_ACM 0x11 + +#define UMCPC_ACM_TYPE_AB1 0x1 +#define UMCPC_ACM_TYPE_AB2 0x2 +#define UMCPC_ACM_TYPE_AB5 0x5 +#define UMCPC_ACM_TYPE_AB6 0x6 + +#define UMCPC_ACM_MODE_DEACTIVATED 0x0 +#define UMCPC_ACM_MODE_MODEM 0x1 +#define UMCPC_ACM_MODE_ATCOMMAND 0x2 +#define UMCPC_ACM_MODE_OBEX 0x60 +#define UMCPC_ACM_MODE_VENDOR1 0xc0 +#define UMCPC_ACM_MODE_VENDOR2 0xfe +#define UMCPC_ACM_MODE_UNLINKED 0xff + +#define UMCPC_CM_MOBILE_ACM 0x0 + +#define UMCPC_ACTIVATE_MODE 0x60 +#define UMCPC_GET_MODETABLE 0x61 +#define UMCPC_SET_LINK 0x62 +#define UMCPC_CLEAR_LINK 0x63 + +#define UMCPC_REQUEST_ACKNOWLEDGE 0x31 + +#define UFOMA_MAX_TIMEOUT 15 /* standard says 10 seconds */ +#define UFOMA_CMD_BUF_SIZE 64 /* bytes */ + +#define UFOMA_BULK_BUF_SIZE 1024 /* bytes */ + +enum { + UFOMA_CTRL_ENDPT_INTR, + UFOMA_CTRL_ENDPT_READ, + UFOMA_CTRL_ENDPT_WRITE, + UFOMA_CTRL_ENDPT_MAX, +}; + +enum { + UFOMA_BULK_ENDPT_WRITE, + UFOMA_BULK_ENDPT_READ, + UFOMA_BULK_ENDPT_MAX, +}; + +struct ufoma_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + struct cv sc_cv; + struct mtx sc_mtx; + + struct usb_xfer *sc_ctrl_xfer[UFOMA_CTRL_ENDPT_MAX]; + struct usb_xfer *sc_bulk_xfer[UFOMA_BULK_ENDPT_MAX]; + uint8_t *sc_modetable; + device_t sc_dev; + struct usb_device *sc_udev; + + uint32_t sc_unit; + + uint16_t sc_line; + + uint8_t sc_num_msg; + uint8_t sc_nobulk; + uint8_t sc_ctrl_iface_no; + uint8_t sc_ctrl_iface_index; + uint8_t sc_data_iface_no; + uint8_t sc_data_iface_index; + uint8_t sc_cm_cap; + uint8_t sc_acm_cap; + uint8_t sc_lsr; + uint8_t sc_msr; + uint8_t sc_modetoactivate; + uint8_t sc_currentmode; +}; + +/* prototypes */ + +static device_probe_t ufoma_probe; +static device_attach_t ufoma_attach; +static device_detach_t ufoma_detach; +static void ufoma_free_softc(struct ufoma_softc *); + +static usb_callback_t ufoma_ctrl_read_callback; +static usb_callback_t ufoma_ctrl_write_callback; +static usb_callback_t ufoma_intr_callback; +static usb_callback_t ufoma_bulk_write_callback; +static usb_callback_t ufoma_bulk_read_callback; + +static void *ufoma_get_intconf(struct usb_config_descriptor *, + struct usb_interface_descriptor *, uint8_t, uint8_t); +static void ufoma_cfg_link_state(struct ufoma_softc *); +static void ufoma_cfg_activate_state(struct ufoma_softc *, uint16_t); +static void ufoma_free(struct ucom_softc *); +static void ufoma_cfg_open(struct ucom_softc *); +static void ufoma_cfg_close(struct ucom_softc *); +static void ufoma_cfg_set_break(struct ucom_softc *, uint8_t); +static void ufoma_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void ufoma_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void ufoma_cfg_set_rts(struct ucom_softc *, uint8_t); +static int ufoma_pre_param(struct ucom_softc *, struct termios *); +static void ufoma_cfg_param(struct ucom_softc *, struct termios *); +static int ufoma_modem_setup(device_t, struct ufoma_softc *, + struct usb_attach_arg *); +static void ufoma_start_read(struct ucom_softc *); +static void ufoma_stop_read(struct ucom_softc *); +static void ufoma_start_write(struct ucom_softc *); +static void ufoma_stop_write(struct ucom_softc *); +static void ufoma_poll(struct ucom_softc *ucom); + +/*sysctl stuff*/ +static int ufoma_sysctl_support(SYSCTL_HANDLER_ARGS); +static int ufoma_sysctl_current(SYSCTL_HANDLER_ARGS); +static int ufoma_sysctl_open(SYSCTL_HANDLER_ARGS); + +static const struct usb_config + ufoma_ctrl_config[UFOMA_CTRL_ENDPT_MAX] = { + + [UFOMA_CTRL_ENDPT_INTR] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = sizeof(struct usb_cdc_notification), + .callback = &ufoma_intr_callback, + }, + + [UFOMA_CTRL_ENDPT_READ] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = (sizeof(struct usb_device_request) + UFOMA_CMD_BUF_SIZE), + .flags = {.short_xfer_ok = 1,}, + .callback = &ufoma_ctrl_read_callback, + .timeout = 1000, /* 1 second */ + }, + + [UFOMA_CTRL_ENDPT_WRITE] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = (sizeof(struct usb_device_request) + 1), + .callback = &ufoma_ctrl_write_callback, + .timeout = 1000, /* 1 second */ + }, +}; + +static const struct usb_config + ufoma_bulk_config[UFOMA_BULK_ENDPT_MAX] = { + + [UFOMA_BULK_ENDPT_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UFOMA_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &ufoma_bulk_write_callback, + }, + + [UFOMA_BULK_ENDPT_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UFOMA_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &ufoma_bulk_read_callback, + }, +}; + +static const struct ucom_callback ufoma_callback = { + .ucom_cfg_get_status = &ufoma_cfg_get_status, + .ucom_cfg_set_dtr = &ufoma_cfg_set_dtr, + .ucom_cfg_set_rts = &ufoma_cfg_set_rts, + .ucom_cfg_set_break = &ufoma_cfg_set_break, + .ucom_cfg_param = &ufoma_cfg_param, + .ucom_cfg_open = &ufoma_cfg_open, + .ucom_cfg_close = &ufoma_cfg_close, + .ucom_pre_param = &ufoma_pre_param, + .ucom_start_read = &ufoma_start_read, + .ucom_stop_read = &ufoma_stop_read, + .ucom_start_write = &ufoma_start_write, + .ucom_stop_write = &ufoma_stop_write, + .ucom_poll = &ufoma_poll, + .ucom_free = &ufoma_free, +}; + +static device_method_t ufoma_methods[] = { + /* Device methods */ + DEVMETHOD(device_probe, ufoma_probe), + DEVMETHOD(device_attach, ufoma_attach), + DEVMETHOD(device_detach, ufoma_detach), + DEVMETHOD_END +}; + +static devclass_t ufoma_devclass; + +static driver_t ufoma_driver = { + .name = "ufoma", + .methods = ufoma_methods, + .size = sizeof(struct ufoma_softc), +}; + +static const STRUCT_USB_HOST_ID ufoma_devs[] = { + {USB_IFACE_CLASS(UICLASS_CDC), + USB_IFACE_SUBCLASS(UISUBCLASS_MCPC),}, +}; + +DRIVER_MODULE(ufoma, uhub, ufoma_driver, ufoma_devclass, NULL, 0); +MODULE_DEPEND(ufoma, ucom, 1, 1, 1); +MODULE_DEPEND(ufoma, usb, 1, 1, 1); +MODULE_VERSION(ufoma, 1); +USB_PNP_HOST_INFO(ufoma_devs); + +static int +ufoma_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usb_interface_descriptor *id; + struct usb_config_descriptor *cd; + usb_mcpc_acm_descriptor *mad; + int error; + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + error = usbd_lookup_id_by_uaa(ufoma_devs, sizeof(ufoma_devs), uaa); + if (error) + return (error); + + id = usbd_get_interface_descriptor(uaa->iface); + cd = usbd_get_config_descriptor(uaa->device); + + if (id == NULL || cd == NULL) + return (ENXIO); + + mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM); + if (mad == NULL) + return (ENXIO); + +#ifndef UFOMA_HANDSFREE + if ((mad->bType == UMCPC_ACM_TYPE_AB5) || + (mad->bType == UMCPC_ACM_TYPE_AB6)) + return (ENXIO); +#endif + return (BUS_PROBE_GENERIC); +} + +static int +ufoma_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ufoma_softc *sc = device_get_softc(dev); + struct usb_config_descriptor *cd; + struct usb_interface_descriptor *id; + struct sysctl_ctx_list *sctx; + struct sysctl_oid *soid; + + usb_mcpc_acm_descriptor *mad; + uint8_t elements; + int32_t error; + + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + + mtx_init(&sc->sc_mtx, "ufoma", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + cv_init(&sc->sc_cv, "CWAIT"); + + device_set_usb_desc(dev); + + DPRINTF("\n"); + + /* setup control transfers */ + + cd = usbd_get_config_descriptor(uaa->device); + id = usbd_get_interface_descriptor(uaa->iface); + sc->sc_ctrl_iface_no = id->bInterfaceNumber; + sc->sc_ctrl_iface_index = uaa->info.bIfaceIndex; + + error = usbd_transfer_setup(uaa->device, + &sc->sc_ctrl_iface_index, sc->sc_ctrl_xfer, + ufoma_ctrl_config, UFOMA_CTRL_ENDPT_MAX, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating control USB " + "transfers failed\n"); + goto detach; + } + mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM); + if (mad == NULL) { + goto detach; + } + if (mad->bFunctionLength < sizeof(*mad)) { + device_printf(dev, "invalid MAD descriptor\n"); + goto detach; + } + if ((mad->bType == UMCPC_ACM_TYPE_AB5) || + (mad->bType == UMCPC_ACM_TYPE_AB6)) { + sc->sc_nobulk = 1; + } else { + sc->sc_nobulk = 0; + if (ufoma_modem_setup(dev, sc, uaa)) { + goto detach; + } + } + + elements = (mad->bFunctionLength - sizeof(*mad) + 1); + + /* initialize mode variables */ + + sc->sc_modetable = malloc(elements + 1, M_USBDEV, M_WAITOK); + + if (sc->sc_modetable == NULL) { + goto detach; + } + sc->sc_modetable[0] = (elements + 1); + memcpy(&sc->sc_modetable[1], mad->bMode, elements); + + sc->sc_currentmode = UMCPC_ACM_MODE_UNLINKED; + sc->sc_modetoactivate = mad->bMode[0]; + + /* clear stall at first run, if any */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]); + usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &ufoma_callback, &sc->sc_mtx); + if (error) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + /*Sysctls*/ + sctx = device_get_sysctl_ctx(dev); + soid = device_get_sysctl_tree(dev); + + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "supportmode", + CTLFLAG_RD|CTLTYPE_STRING, sc, 0, ufoma_sysctl_support, + "A", "Supporting port role"); + + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "currentmode", + CTLFLAG_RD|CTLTYPE_STRING, sc, 0, ufoma_sysctl_current, + "A", "Current port role"); + + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "openmode", + CTLFLAG_RW|CTLTYPE_STRING, sc, 0, ufoma_sysctl_open, + "A", "Mode to transit when port is opened"); + SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "comunit", + CTLFLAG_RD, &(sc->sc_super_ucom.sc_unit), 0, + "Unit number as USB serial"); + + return (0); /* success */ + +detach: + ufoma_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ufoma_detach(device_t dev) +{ + struct ufoma_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_ctrl_xfer, UFOMA_CTRL_ENDPT_MAX); + usbd_transfer_unsetup(sc->sc_bulk_xfer, UFOMA_BULK_ENDPT_MAX); + + if (sc->sc_modetable) { + free(sc->sc_modetable, M_USBDEV); + } + cv_destroy(&sc->sc_cv); + + device_claim_softc(dev); + + ufoma_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(ufoma); + +static void +ufoma_free_softc(struct ufoma_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +ufoma_free(struct ucom_softc *ucom) +{ + ufoma_free_softc(ucom->sc_parent); +} + +static void * +ufoma_get_intconf(struct usb_config_descriptor *cd, struct usb_interface_descriptor *id, + uint8_t type, uint8_t subtype) +{ + struct usb_descriptor *desc = (void *)id; + + while ((desc = usb_desc_foreach(cd, desc))) { + + if (desc->bDescriptorType == UDESC_INTERFACE) { + return (NULL); + } + if ((desc->bDescriptorType == type) && + (desc->bDescriptorSubtype == subtype)) { + break; + } + } + return (desc); +} + +static void +ufoma_cfg_link_state(struct ufoma_softc *sc) +{ + struct usb_device_request req; + int32_t error; + + req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; + req.bRequest = UMCPC_SET_LINK; + USETW(req.wValue, UMCPC_CM_MOBILE_ACM); + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wLength, sc->sc_modetable[0]); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, sc->sc_modetable, 0, 1000); + + error = cv_timedwait(&sc->sc_cv, &sc->sc_mtx, hz); + + if (error) { + DPRINTF("NO response\n"); + } +} + +static void +ufoma_cfg_activate_state(struct ufoma_softc *sc, uint16_t state) +{ + struct usb_device_request req; + int32_t error; + + req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; + req.bRequest = UMCPC_ACTIVATE_MODE; + USETW(req.wValue, state); + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + + error = cv_timedwait(&sc->sc_cv, &sc->sc_mtx, + (UFOMA_MAX_TIMEOUT * hz)); + if (error) { + DPRINTF("No response\n"); + } +} + +static void +ufoma_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ufoma_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc0, *pc1; + int len, aframes, nframes; + + usbd_xfer_status(xfer, NULL, NULL, &aframes, &nframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + if (aframes != nframes) + goto tr_setup; + pc1 = usbd_xfer_get_frame(xfer, 1); + len = usbd_xfer_frame_len(xfer, 1); + if (len > 0) + ucom_put_data(&sc->sc_ucom, pc1, 0, len); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + if (sc->sc_num_msg) { + sc->sc_num_msg--; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wValue, 0); + USETW(req.wLength, UFOMA_CMD_BUF_SIZE); + + pc0 = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc0, 0, &req, sizeof(req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, UFOMA_CMD_BUF_SIZE); + usbd_xfer_set_frames(xfer, 2); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + DPRINTF("error = %s\n", + usbd_errstr(error)); + + if (error == USB_ERR_CANCELLED) { + return; + } + goto tr_transferred; + } +} + +static void +ufoma_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ufoma_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + case USB_ST_SETUP: + pc = usbd_xfer_get_frame(xfer, 1); + if (ucom_get_data(&sc->sc_ucom, pc, 0, 1, &actlen)) { + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; + USETW(req.wIndex, sc->sc_ctrl_iface_no); + USETW(req.wValue, 0); + USETW(req.wLength, 1); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, 1); + usbd_xfer_set_frames(xfer, 2); + + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + DPRINTF("error = %s\n", usbd_errstr(error)); + + if (error == USB_ERR_CANCELLED) { + return; + } + goto tr_transferred; + } +} + +static void +ufoma_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ufoma_softc *sc = usbd_xfer_softc(xfer); + struct usb_cdc_notification pkt; + struct usb_page_cache *pc; + uint16_t wLen; + uint16_t temp; + uint8_t mstatus; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen < 8) { + DPRINTF("too short message\n"); + goto tr_setup; + } + if (actlen > (int)sizeof(pkt)) { + DPRINTF("truncating message\n"); + actlen = sizeof(pkt); + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &pkt, actlen); + + actlen -= 8; + + wLen = UGETW(pkt.wLength); + if (actlen > wLen) { + actlen = wLen; + } + if ((pkt.bmRequestType == UT_READ_VENDOR_INTERFACE) && + (pkt.bNotification == UMCPC_REQUEST_ACKNOWLEDGE)) { + temp = UGETW(pkt.wValue); + sc->sc_currentmode = (temp >> 8); + if (!(temp & 0xff)) { + DPRINTF("Mode change failed!\n"); + } + cv_signal(&sc->sc_cv); + } + if (pkt.bmRequestType != UCDC_NOTIFICATION) { + goto tr_setup; + } + switch (pkt.bNotification) { + case UCDC_N_RESPONSE_AVAILABLE: + if (!(sc->sc_nobulk)) { + DPRINTF("Wrong serial state!\n"); + break; + } + if (sc->sc_num_msg != 0xFF) { + sc->sc_num_msg++; + } + usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]); + break; + + case UCDC_N_SERIAL_STATE: + if (sc->sc_nobulk) { + DPRINTF("Wrong serial state!\n"); + break; + } + /* + * Set the serial state in ucom driver based on + * the bits from the notify message + */ + if (actlen < 2) { + DPRINTF("invalid notification " + "length, %d bytes!\n", actlen); + break; + } + DPRINTF("notify bytes = 0x%02x, 0x%02x\n", + pkt.data[0], pkt.data[1]); + + /* currently, lsr is always zero. */ + sc->sc_lsr = 0; + sc->sc_msr = 0; + + mstatus = pkt.data[0]; + + if (mstatus & UCDC_N_SERIAL_RI) { + sc->sc_msr |= SER_RI; + } + if (mstatus & UCDC_N_SERIAL_DSR) { + sc->sc_msr |= SER_DSR; + } + if (mstatus & UCDC_N_SERIAL_DCD) { + sc->sc_msr |= SER_DCD; + } + ucom_status_change(&sc->sc_ucom); + break; + + default: + break; + } + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +ufoma_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ufoma_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UFOMA_BULK_BUF_SIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +ufoma_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ufoma_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +ufoma_cfg_open(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + /* empty input queue */ + + if (sc->sc_num_msg != 0xFF) { + sc->sc_num_msg++; + } + if (sc->sc_currentmode == UMCPC_ACM_MODE_UNLINKED) { + ufoma_cfg_link_state(sc); + } + if (sc->sc_currentmode == UMCPC_ACM_MODE_DEACTIVATED) { + ufoma_cfg_activate_state(sc, sc->sc_modetoactivate); + } +} + +static void +ufoma_cfg_close(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + ufoma_cfg_activate_state(sc, UMCPC_ACM_MODE_DEACTIVATED); +} + +static void +ufoma_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ufoma_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t wValue; + + if (sc->sc_nobulk || + (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX)) { + return; + } + if (!(sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK)) { + return; + } + wValue = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, wValue); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +ufoma_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + /* XXX Note: sc_lsr is always zero */ + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +ufoma_cfg_set_line_state(struct ufoma_softc *sc) +{ + struct usb_device_request req; + + /* Don't send line state emulation request for OBEX port */ + if (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX) { + return; + } + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +ufoma_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_nobulk) { + return; + } + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + ufoma_cfg_set_line_state(sc); +} + +static void +ufoma_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_nobulk) { + return; + } + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + ufoma_cfg_set_line_state(sc); +} + +static int +ufoma_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + return (0); /* we accept anything */ +} + +static void +ufoma_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct ufoma_softc *sc = ucom->sc_parent; + struct usb_device_request req; + struct usb_cdc_line_state ls; + + if (sc->sc_nobulk || + (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX)) { + return; + } + DPRINTF("\n"); + + memset(&ls, 0, sizeof(ls)); + + USETDW(ls.dwDTERate, t->c_ospeed); + + if (t->c_cflag & CSTOPB) { + ls.bCharFormat = UCDC_STOP_BIT_2; + } else { + ls.bCharFormat = UCDC_STOP_BIT_1; + } + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + ls.bParityType = UCDC_PARITY_ODD; + } else { + ls.bParityType = UCDC_PARITY_EVEN; + } + } else { + ls.bParityType = UCDC_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UCDC_LINE_STATE_LENGTH); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &ls, 0, 1000); +} + +static int +ufoma_modem_setup(device_t dev, struct ufoma_softc *sc, + struct usb_attach_arg *uaa) +{ + struct usb_config_descriptor *cd; + struct usb_cdc_acm_descriptor *acm; + struct usb_cdc_cm_descriptor *cmd; + struct usb_interface_descriptor *id; + struct usb_interface *iface; + uint8_t i; + int32_t error; + + cd = usbd_get_config_descriptor(uaa->device); + id = usbd_get_interface_descriptor(uaa->iface); + + cmd = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + + if ((cmd == NULL) || + (cmd->bLength < sizeof(*cmd))) { + return (EINVAL); + } + sc->sc_cm_cap = cmd->bmCapabilities; + sc->sc_data_iface_no = cmd->bDataInterface; + + acm = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM); + + if ((acm == NULL) || + (acm->bLength < sizeof(*acm))) { + return (EINVAL); + } + sc->sc_acm_cap = acm->bmCapabilities; + + device_printf(dev, "data interface %d, has %sCM over data, " + "has %sbreak\n", + sc->sc_data_iface_no, + sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ", + sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no "); + + /* get the data interface too */ + + for (i = 0;; i++) { + + iface = usbd_get_iface(uaa->device, i); + + if (iface) { + + id = usbd_get_interface_descriptor(iface); + + if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) { + sc->sc_data_iface_index = i; + usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); + break; + } + } else { + device_printf(dev, "no data interface\n"); + return (EINVAL); + } + } + + error = usbd_transfer_setup(uaa->device, + &sc->sc_data_iface_index, sc->sc_bulk_xfer, + ufoma_bulk_config, UFOMA_BULK_ENDPT_MAX, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating BULK USB " + "transfers failed\n"); + return (EINVAL); + } + return (0); +} + +static void +ufoma_start_read(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + /* start interrupt transfer */ + usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_INTR]); + + /* start data transfer */ + if (sc->sc_nobulk) { + usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]); + } else { + usbd_transfer_start(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]); + } +} + +static void +ufoma_stop_read(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + /* stop interrupt transfer */ + usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_INTR]); + + /* stop data transfer */ + if (sc->sc_nobulk) { + usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]); + } else { + usbd_transfer_stop(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]); + } +} + +static void +ufoma_start_write(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_nobulk) { + usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_WRITE]); + } else { + usbd_transfer_start(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]); + } +} + +static void +ufoma_stop_write(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + + if (sc->sc_nobulk) { + usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_WRITE]); + } else { + usbd_transfer_stop(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]); + } +} + +static struct umcpc_modetostr_tab{ + int mode; + char *str; +}umcpc_modetostr_tab[]={ + {UMCPC_ACM_MODE_DEACTIVATED, "deactivated"}, + {UMCPC_ACM_MODE_MODEM, "modem"}, + {UMCPC_ACM_MODE_ATCOMMAND, "handsfree"}, + {UMCPC_ACM_MODE_OBEX, "obex"}, + {UMCPC_ACM_MODE_VENDOR1, "vendor1"}, + {UMCPC_ACM_MODE_VENDOR2, "vendor2"}, + {UMCPC_ACM_MODE_UNLINKED, "unlinked"}, + {0, NULL} +}; + +static char *ufoma_mode_to_str(int mode) +{ + int i; + for(i = 0 ;umcpc_modetostr_tab[i].str != NULL; i++){ + if(umcpc_modetostr_tab[i].mode == mode){ + return umcpc_modetostr_tab[i].str; + } + } + return NULL; +} + +static int ufoma_str_to_mode(char *str) +{ + int i; + for(i = 0 ;umcpc_modetostr_tab[i].str != NULL; i++){ + if(strcmp(str, umcpc_modetostr_tab[i].str)==0){ + return umcpc_modetostr_tab[i].mode; + } + } + return -1; +} + +static int ufoma_sysctl_support(SYSCTL_HANDLER_ARGS) +{ + struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; + struct sbuf sb; + int i; + char *mode; + + sbuf_new(&sb, NULL, 1, SBUF_AUTOEXTEND); + for(i = 1; i < sc->sc_modetable[0]; i++){ + mode = ufoma_mode_to_str(sc->sc_modetable[i]); + if(mode !=NULL){ + sbuf_cat(&sb, mode); + }else{ + sbuf_printf(&sb, "(%02x)", sc->sc_modetable[i]); + } + if(i < (sc->sc_modetable[0]-1)) + sbuf_cat(&sb, ","); + } + sbuf_trim(&sb); + sbuf_finish(&sb); + sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); + sbuf_delete(&sb); + + return 0; +} +static int ufoma_sysctl_current(SYSCTL_HANDLER_ARGS) +{ + struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; + char *mode; + char subbuf[]="(XXX)"; + mode = ufoma_mode_to_str(sc->sc_currentmode); + if(!mode){ + mode = subbuf; + snprintf(subbuf, sizeof(subbuf), "(%02x)", sc->sc_currentmode); + } + sysctl_handle_string(oidp, mode, strlen(mode), req); + + return 0; + +} +static int ufoma_sysctl_open(SYSCTL_HANDLER_ARGS) +{ + struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; + char *mode; + char subbuf[40]; + int newmode; + int error; + int i; + + mode = ufoma_mode_to_str(sc->sc_modetoactivate); + if(mode){ + strncpy(subbuf, mode, sizeof(subbuf)); + }else{ + snprintf(subbuf, sizeof(subbuf), "(%02x)", sc->sc_modetoactivate); + } + error = sysctl_handle_string(oidp, subbuf, sizeof(subbuf), req); + if(error != 0 || req->newptr == NULL){ + return error; + } + + if((newmode = ufoma_str_to_mode(subbuf)) == -1){ + return EINVAL; + } + + for(i = 1 ; i < sc->sc_modetable[0] ; i++){ + if(sc->sc_modetable[i] == newmode){ + sc->sc_modetoactivate = newmode; + return 0; + } + } + + return EINVAL; +} + +static void +ufoma_poll(struct ucom_softc *ucom) +{ + struct ufoma_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_ctrl_xfer, UFOMA_CTRL_ENDPT_MAX); + usbd_transfer_poll(sc->sc_bulk_xfer, UFOMA_BULK_ENDPT_MAX); +} diff --git a/freebsd/sys/dev/usb/serial/uftdi.c b/freebsd/sys/dev/usb/serial/uftdi.c new file mode 100644 index 00000000..3445b4c7 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/uftdi.c @@ -0,0 +1,2011 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $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. + * + * 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 FT232x, FT2232x, FT4232x, FT8U100AX and FT8U232xM serial adapters. + * + * Note that we specifically do not do a reset or otherwise alter the state of + * the chip during attach, detach, open, and close, because it could be + * pre-initialized (via an attached serial eeprom) to power-on into a mode such + * as bitbang in which the pins are being driven to a specific state which we + * must not perturb. The device gets reset at power-on, and doesn't need to be + * reset again after that to function, except as directed by ioctl() calls. + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_core.h> +#include <dev/usb/usb_ioctl.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR uftdi_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> +#include <dev/usb/serial/uftdi_reg.h> +#include <dev/usb/uftdiio.h> + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uftdi, CTLFLAG_RW, 0, "USB uftdi"); + +#ifdef USB_DEBUG +static int uftdi_debug = 0; +SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, debug, CTLFLAG_RWTUN, + &uftdi_debug, 0, "Debug level"); +#endif + +#define UFTDI_CONFIG_INDEX 0 + +/* + * IO buffer sizes and FTDI device procotol sizes. + * + * Note that the output packet size in the following defines is not the usb + * protocol packet size based on bus speed, it is the size dictated by the FTDI + * device itself, and is used only on older chips. + * + * We allocate buffers bigger than the hardware's packet size, and process + * multiple packets within each buffer. This allows the controller to make + * optimal use of the usb bus by conducting multiple transfers with the device + * during a single bus timeslice to fill or drain the chip's fifos. + * + * The output data on newer chips has no packet header, and we are able to pack + * any number of output bytes into a buffer. On some older chips, each output + * packet contains a 1-byte header and up to 63 bytes of payload. The size is + * encoded in 6 bits of the header, hence the 64-byte limit on packet size. We + * loop to fill the buffer with many of these header+payload packets. + * + * The input data on all chips consists of packets which contain a 2-byte header + * followed by data payload. The total size of the packet is wMaxPacketSize + * which can change based on the bus speed (e.g., 64 for full speed, 512 for + * high speed). We loop to extract the headers and payloads from the packets + * packed into an input buffer. + */ +#define UFTDI_IBUFSIZE 2048 +#define UFTDI_IHDRSIZE 2 +#define UFTDI_OBUFSIZE 2048 +#define UFTDI_OPKTSIZE 64 + +enum { + UFTDI_BULK_DT_WR, + UFTDI_BULK_DT_RD, + UFTDI_N_TRANSFER, +}; + +enum { + DEVT_SIO, + DEVT_232A, + DEVT_232B, + DEVT_2232D, /* Includes 2232C */ + DEVT_232R, + DEVT_2232H, + DEVT_4232H, + DEVT_232H, + DEVT_230X, +}; + +#define DEVF_BAUDBITS_HINDEX 0x01 /* Baud bits in high byte of index. */ +#define DEVF_BAUDCLK_12M 0X02 /* Base baud clock is 12MHz. */ + +struct uftdi_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_device *sc_udev; + struct usb_xfer *sc_xfer[UFTDI_N_TRANSFER]; + device_t sc_dev; + struct mtx sc_mtx; + + uint32_t sc_unit; + + uint16_t sc_last_lcr; + uint16_t sc_bcdDevice; + + uint8_t sc_devtype; + uint8_t sc_devflags; + uint8_t sc_hdrlen; + uint8_t sc_msr; + uint8_t sc_lsr; + uint8_t sc_bitmode; +}; + +struct uftdi_param_config { + uint16_t baud_lobits; + uint16_t baud_hibits; + 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 void uftdi_free_softc(struct uftdi_softc *); + +static usb_callback_t uftdi_write_callback; +static usb_callback_t uftdi_read_callback; + +static void uftdi_free(struct ucom_softc *); +static void uftdi_cfg_open(struct ucom_softc *); +static void uftdi_cfg_close(struct ucom_softc *); +static void uftdi_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uftdi_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uftdi_cfg_set_break(struct ucom_softc *, uint8_t); +static int uftdi_set_parm_soft(struct ucom_softc *, struct termios *, + struct uftdi_param_config *); +static int uftdi_pre_param(struct ucom_softc *, struct termios *); +static void uftdi_cfg_param(struct ucom_softc *, struct termios *); +static void uftdi_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static int uftdi_reset(struct ucom_softc *, int); +static int uftdi_set_bitmode(struct ucom_softc *, uint8_t, uint8_t); +static int uftdi_get_bitmode(struct ucom_softc *, uint8_t *, uint8_t *); +static int uftdi_set_latency(struct ucom_softc *, int); +static int uftdi_get_latency(struct ucom_softc *, int *); +static int uftdi_set_event_char(struct ucom_softc *, int); +static int uftdi_set_error_char(struct ucom_softc *, int); +static int uftdi_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, + struct thread *); +static void uftdi_start_read(struct ucom_softc *); +static void uftdi_stop_read(struct ucom_softc *); +static void uftdi_start_write(struct ucom_softc *); +static void uftdi_stop_write(struct ucom_softc *); +static void uftdi_poll(struct ucom_softc *ucom); + +static const struct usb_config uftdi_config[UFTDI_N_TRANSFER] = { + + [UFTDI_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UFTDI_OBUFSIZE, + .flags = {.pipe_bof = 1,}, + .callback = &uftdi_write_callback, + }, + + [UFTDI_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UFTDI_IBUFSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uftdi_read_callback, + }, +}; + +static const struct ucom_callback uftdi_callback = { + .ucom_cfg_get_status = &uftdi_cfg_get_status, + .ucom_cfg_set_dtr = &uftdi_cfg_set_dtr, + .ucom_cfg_set_rts = &uftdi_cfg_set_rts, + .ucom_cfg_set_break = &uftdi_cfg_set_break, + .ucom_cfg_param = &uftdi_cfg_param, + .ucom_cfg_open = &uftdi_cfg_open, + .ucom_cfg_close = &uftdi_cfg_close, + .ucom_pre_param = &uftdi_pre_param, + .ucom_ioctl = &uftdi_ioctl, + .ucom_start_read = &uftdi_start_read, + .ucom_stop_read = &uftdi_stop_read, + .ucom_start_write = &uftdi_start_write, + .ucom_stop_write = &uftdi_stop_write, + .ucom_poll = &uftdi_poll, + .ucom_free = &uftdi_free, +}; + +static device_method_t uftdi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uftdi_probe), + DEVMETHOD(device_attach, uftdi_attach), + DEVMETHOD(device_detach, uftdi_detach), + DEVMETHOD_END +}; + +static devclass_t uftdi_devclass; + +static driver_t uftdi_driver = { + .name = "uftdi", + .methods = uftdi_methods, + .size = sizeof(struct uftdi_softc), +}; + +static const STRUCT_USB_HOST_ID uftdi_devs[] = { +#define UFTDI_DEV(v, p, i) \ + { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } + UFTDI_DEV(ACTON, SPECTRAPRO, 0), + UFTDI_DEV(ALTI2, N3, 0), + UFTDI_DEV(ANALOGDEVICES, GNICE, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(ANALOGDEVICES, GNICEPLUS, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(ATMEL, STK541, 0), + UFTDI_DEV(BAYER, CONTOUR_CABLE, 0), + UFTDI_DEV(BBELECTRONICS, 232USB9M, 0), + UFTDI_DEV(BBELECTRONICS, 485USB9F_2W, 0), + UFTDI_DEV(BBELECTRONICS, 485USB9F_4W, 0), + UFTDI_DEV(BBELECTRONICS, 485USBTB_2W, 0), + UFTDI_DEV(BBELECTRONICS, 485USBTB_4W, 0), + UFTDI_DEV(BBELECTRONICS, TTL3USB9M, 0), + UFTDI_DEV(BBELECTRONICS, TTL5USB9M, 0), + UFTDI_DEV(BBELECTRONICS, USO9ML2, 0), + UFTDI_DEV(BBELECTRONICS, USO9ML2DR, 0), + UFTDI_DEV(BBELECTRONICS, USO9ML2DR_2, 0), + UFTDI_DEV(BBELECTRONICS, USOPTL4, 0), + UFTDI_DEV(BBELECTRONICS, USOPTL4DR, 0), + UFTDI_DEV(BBELECTRONICS, USOPTL4DR2, 0), + UFTDI_DEV(BBELECTRONICS, USOTL4, 0), + UFTDI_DEV(BBELECTRONICS, USPTL4, 0), + UFTDI_DEV(BBELECTRONICS, USTL4, 0), + UFTDI_DEV(BBELECTRONICS, ZZ_PROG1_USB, 0), + UFTDI_DEV(CONTEC, COM1USBH, 0), + UFTDI_DEV(DRESDENELEKTRONIK, SENSORTERMINALBOARD, 0), + UFTDI_DEV(DRESDENELEKTRONIK, WIRELESSHANDHELDTERMINAL, 0), + UFTDI_DEV(DRESDENELEKTRONIK, DE_RFNODE, 0), + UFTDI_DEV(DRESDENELEKTRONIK, LEVELSHIFTERSTICKLOWCOST, 0), + UFTDI_DEV(ELEKTOR, FT323R, 0), + UFTDI_DEV(EVOLUTION, ER1, 0), + UFTDI_DEV(EVOLUTION, HYBRID, 0), + UFTDI_DEV(EVOLUTION, RCM4, 0), + UFTDI_DEV(FALCOM, SAMBA, 0), + UFTDI_DEV(FALCOM, TWIST, 0), + UFTDI_DEV(FIC, NEO1973_DEBUG, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FIC, NEO1973_DEBUG, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, 232EX, 0), + UFTDI_DEV(FTDI, 232H, 0), + UFTDI_DEV(FTDI, 232RL, 0), + UFTDI_DEV(FTDI, 4N_GALAXY_DE_1, 0), + UFTDI_DEV(FTDI, 4N_GALAXY_DE_2, 0), + UFTDI_DEV(FTDI, 4N_GALAXY_DE_3, 0), + UFTDI_DEV(FTDI, 8U232AM_ALT, 0), + UFTDI_DEV(FTDI, ACCESSO, 0), + UFTDI_DEV(FTDI, ACG_HFDUAL, 0), + UFTDI_DEV(FTDI, ACTIVE_ROBOTS, 0), + UFTDI_DEV(FTDI, ACTZWAVE, 0), + UFTDI_DEV(FTDI, AMC232, 0), + UFTDI_DEV(FTDI, ARTEMIS, 0), + UFTDI_DEV(FTDI, ASK_RDR400, 0), + UFTDI_DEV(FTDI, ATIK_ATK16, 0), + UFTDI_DEV(FTDI, ATIK_ATK16C, 0), + UFTDI_DEV(FTDI, ATIK_ATK16HR, 0), + UFTDI_DEV(FTDI, ATIK_ATK16HRC, 0), + UFTDI_DEV(FTDI, ATIK_ATK16IC, 0), + UFTDI_DEV(FTDI, BCS_SE923, 0), + UFTDI_DEV(FTDI, CANDAPTER, 0), + UFTDI_DEV(FTDI, CANUSB, 0), + UFTDI_DEV(FTDI, CCSICDU20_0, 0), + UFTDI_DEV(FTDI, CCSICDU40_1, 0), + UFTDI_DEV(FTDI, CCSICDU64_4, 0), + UFTDI_DEV(FTDI, CCSLOAD_N_GO_3, 0), + UFTDI_DEV(FTDI, CCSMACHX_2, 0), + UFTDI_DEV(FTDI, CCSPRIME8_5, 0), + UFTDI_DEV(FTDI, CFA_631, 0), + UFTDI_DEV(FTDI, CFA_632, 0), + UFTDI_DEV(FTDI, CFA_633, 0), + UFTDI_DEV(FTDI, CFA_634, 0), + UFTDI_DEV(FTDI, CFA_635, 0), + UFTDI_DEV(FTDI, CHAMSYS_24_MASTER_WING, 0), + UFTDI_DEV(FTDI, CHAMSYS_MAXI_WING, 0), + UFTDI_DEV(FTDI, CHAMSYS_MEDIA_WING, 0), + UFTDI_DEV(FTDI, CHAMSYS_MIDI_TIMECODE, 0), + UFTDI_DEV(FTDI, CHAMSYS_MINI_WING, 0), + UFTDI_DEV(FTDI, CHAMSYS_PC_WING, 0), + UFTDI_DEV(FTDI, CHAMSYS_USB_DMX, 0), + UFTDI_DEV(FTDI, CHAMSYS_WING, 0), + UFTDI_DEV(FTDI, COM4SM, 0), + UFTDI_DEV(FTDI, CONVERTER_0, 0), + UFTDI_DEV(FTDI, CONVERTER_1, 0), + UFTDI_DEV(FTDI, CONVERTER_2, 0), + UFTDI_DEV(FTDI, CONVERTER_3, 0), + UFTDI_DEV(FTDI, CONVERTER_4, 0), + UFTDI_DEV(FTDI, CONVERTER_5, 0), + UFTDI_DEV(FTDI, CONVERTER_6, 0), + UFTDI_DEV(FTDI, CONVERTER_7, 0), + UFTDI_DEV(FTDI, CTI_USB_MINI_485, 0), + UFTDI_DEV(FTDI, CTI_USB_NANO_485, 0), + UFTDI_DEV(FTDI, DMX4ALL, 0), + UFTDI_DEV(FTDI, DOMINTELL_DGQG, 0), + UFTDI_DEV(FTDI, DOMINTELL_DUSB, 0), + UFTDI_DEV(FTDI, DOTEC, 0), + UFTDI_DEV(FTDI, ECLO_COM_1WIRE, 0), + UFTDI_DEV(FTDI, ECO_PRO_CDS, 0), + UFTDI_DEV(FTDI, EISCOU, 0), + UFTDI_DEV(FTDI, ELSTER_UNICOM, 0), + UFTDI_DEV(FTDI, ELV_ALC8500, 0), + UFTDI_DEV(FTDI, ELV_CLI7000, 0), + UFTDI_DEV(FTDI, ELV_CSI8, 0), + UFTDI_DEV(FTDI, ELV_EC3000, 0), + UFTDI_DEV(FTDI, ELV_EM1000DL, 0), + UFTDI_DEV(FTDI, ELV_EM1010PC, 0), + UFTDI_DEV(FTDI, ELV_FEM, 0), + UFTDI_DEV(FTDI, ELV_FHZ1000PC, 0), + UFTDI_DEV(FTDI, ELV_FHZ1300PC, 0), + UFTDI_DEV(FTDI, ELV_FM3RX, 0), + UFTDI_DEV(FTDI, ELV_FS20SIG, 0), + UFTDI_DEV(FTDI, ELV_HS485, 0), + UFTDI_DEV(FTDI, ELV_KL100, 0), + UFTDI_DEV(FTDI, ELV_MSM1, 0), + UFTDI_DEV(FTDI, ELV_PCD200, 0), + UFTDI_DEV(FTDI, ELV_PCK100, 0), + UFTDI_DEV(FTDI, ELV_PPS7330, 0), + UFTDI_DEV(FTDI, ELV_RFP500, 0), + UFTDI_DEV(FTDI, ELV_T1100, 0), + UFTDI_DEV(FTDI, ELV_TFD128, 0), + UFTDI_DEV(FTDI, ELV_TFM100, 0), + UFTDI_DEV(FTDI, ELV_TWS550, 0), + UFTDI_DEV(FTDI, ELV_UAD8, 0), + UFTDI_DEV(FTDI, ELV_UDA7, 0), + UFTDI_DEV(FTDI, ELV_UDF77, 0), + UFTDI_DEV(FTDI, ELV_UIO88, 0), + UFTDI_DEV(FTDI, ELV_ULA200, 0), + UFTDI_DEV(FTDI, ELV_UM100, 0), + UFTDI_DEV(FTDI, ELV_UMS100, 0), + UFTDI_DEV(FTDI, ELV_UO100, 0), + UFTDI_DEV(FTDI, ELV_UR100, 0), + UFTDI_DEV(FTDI, ELV_USI2, 0), + UFTDI_DEV(FTDI, ELV_USR, 0), + UFTDI_DEV(FTDI, ELV_UTP8, 0), + UFTDI_DEV(FTDI, ELV_WS300PC, 0), + UFTDI_DEV(FTDI, ELV_WS444PC, 0), + UFTDI_DEV(FTDI, ELV_WS500, 0), + UFTDI_DEV(FTDI, ELV_WS550, 0), + UFTDI_DEV(FTDI, ELV_WS777, 0), + UFTDI_DEV(FTDI, ELV_WS888, 0), + UFTDI_DEV(FTDI, EMCU2D, 0), + UFTDI_DEV(FTDI, EMCU2H, 0), + UFTDI_DEV(FTDI, FUTURE_0, 0), + UFTDI_DEV(FTDI, FUTURE_1, 0), + UFTDI_DEV(FTDI, FUTURE_2, 0), + UFTDI_DEV(FTDI, GAMMASCOUT, 0), + UFTDI_DEV(FTDI, GENERIC, 0), + UFTDI_DEV(FTDI, GUDEADS_E808, 0), + UFTDI_DEV(FTDI, GUDEADS_E809, 0), + UFTDI_DEV(FTDI, GUDEADS_E80A, 0), + UFTDI_DEV(FTDI, GUDEADS_E80B, 0), + UFTDI_DEV(FTDI, GUDEADS_E80C, 0), + UFTDI_DEV(FTDI, GUDEADS_E80D, 0), + UFTDI_DEV(FTDI, GUDEADS_E80E, 0), + UFTDI_DEV(FTDI, GUDEADS_E80F, 0), + UFTDI_DEV(FTDI, GUDEADS_E88D, 0), + UFTDI_DEV(FTDI, GUDEADS_E88E, 0), + UFTDI_DEV(FTDI, GUDEADS_E88F, 0), + UFTDI_DEV(FTDI, HD_RADIO, 0), + UFTDI_DEV(FTDI, HO720, 0), + UFTDI_DEV(FTDI, HO730, 0), + UFTDI_DEV(FTDI, HO820, 0), + UFTDI_DEV(FTDI, HO870, 0), + UFTDI_DEV(FTDI, IBS_APP70, 0), + UFTDI_DEV(FTDI, IBS_PCMCIA, 0), + UFTDI_DEV(FTDI, IBS_PEDO, 0), + UFTDI_DEV(FTDI, IBS_PICPRO, 0), + UFTDI_DEV(FTDI, IBS_PK1, 0), + UFTDI_DEV(FTDI, IBS_PROD, 0), + UFTDI_DEV(FTDI, IBS_RS232MON, 0), + UFTDI_DEV(FTDI, IBS_US485, 0), + UFTDI_DEV(FTDI, IPLUS, 0), + UFTDI_DEV(FTDI, IPLUS2, 0), + UFTDI_DEV(FTDI, IRTRANS, 0), + UFTDI_DEV(FTDI, KBS, 0), + UFTDI_DEV(FTDI, KTLINK, 0), + UFTDI_DEV(FTDI, LENZ_LIUSB, 0), + UFTDI_DEV(FTDI, LK202, 0), + UFTDI_DEV(FTDI, LK204, 0), + UFTDI_DEV(FTDI, LM3S_DEVEL_BOARD, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, LM3S_EVAL_BOARD, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, LM3S_ICDI_B_BOARD, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, MASTERDEVEL2, 0), + UFTDI_DEV(FTDI, MAXSTREAM, 0), + UFTDI_DEV(FTDI, MHAM_DB9, 0), + UFTDI_DEV(FTDI, MHAM_IC, 0), + UFTDI_DEV(FTDI, MHAM_KW, 0), + UFTDI_DEV(FTDI, MHAM_RS232, 0), + UFTDI_DEV(FTDI, MHAM_Y6, 0), + UFTDI_DEV(FTDI, MHAM_Y8, 0), + UFTDI_DEV(FTDI, MHAM_Y9, 0), + UFTDI_DEV(FTDI, MHAM_YS, 0), + UFTDI_DEV(FTDI, MICRO_CHAMELEON, 0), + UFTDI_DEV(FTDI, MTXORB_5, 0), + UFTDI_DEV(FTDI, MTXORB_6, 0), + UFTDI_DEV(FTDI, MX2_3, 0), + UFTDI_DEV(FTDI, MX4_5, 0), + UFTDI_DEV(FTDI, NXTCAM, 0), + UFTDI_DEV(FTDI, OCEANIC, 0), + UFTDI_DEV(FTDI, OOCDLINK, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, OPENDCC, 0), + UFTDI_DEV(FTDI, OPENDCC_GATEWAY, 0), + UFTDI_DEV(FTDI, OPENDCC_GBM, 0), + UFTDI_DEV(FTDI, OPENDCC_SNIFFER, 0), + UFTDI_DEV(FTDI, OPENDCC_THROTTLE, 0), + UFTDI_DEV(FTDI, PCDJ_DAC2, 0), + UFTDI_DEV(FTDI, PCMSFU, 0), + UFTDI_DEV(FTDI, PERLE_ULTRAPORT, 0), + UFTDI_DEV(FTDI, PHI_FISCO, 0), + UFTDI_DEV(FTDI, PIEGROUP, 0), + UFTDI_DEV(FTDI, PROPOX_JTAGCABLEII, 0), + UFTDI_DEV(FTDI, R2000KU_TRUE_RNG, 0), + UFTDI_DEV(FTDI, R2X0, 0), + UFTDI_DEV(FTDI, RELAIS, 0), + UFTDI_DEV(FTDI, REU_TINY, 0), + UFTDI_DEV(FTDI, RMP200, 0), + UFTDI_DEV(FTDI, RM_CANVIEW, 0), + UFTDI_DEV(FTDI, RRCIRKITS_LOCOBUFFER, 0), + UFTDI_DEV(FTDI, SCIENCESCOPE_HS_LOGBOOK, 0), + UFTDI_DEV(FTDI, SCIENCESCOPE_LOGBOOKML, 0), + UFTDI_DEV(FTDI, SCIENCESCOPE_LS_LOGBOOK, 0), + UFTDI_DEV(FTDI, SCS_DEVICE_0, 0), + UFTDI_DEV(FTDI, SCS_DEVICE_1, 0), + UFTDI_DEV(FTDI, SCS_DEVICE_2, 0), + UFTDI_DEV(FTDI, SCS_DEVICE_3, 0), + UFTDI_DEV(FTDI, SCS_DEVICE_4, 0), + UFTDI_DEV(FTDI, SCS_DEVICE_5, 0), + UFTDI_DEV(FTDI, SCS_DEVICE_6, 0), + UFTDI_DEV(FTDI, SCS_DEVICE_7, 0), + UFTDI_DEV(FTDI, SCX8_USB_PHOENIX, 0), + UFTDI_DEV(FTDI, SDMUSBQSS, 0), + UFTDI_DEV(FTDI, SEMC_DSS20, 0), + UFTDI_DEV(FTDI, SERIAL_2232C, UFTDI_JTAG_CHECK_STRING), + UFTDI_DEV(FTDI, SERIAL_2232D, 0), + UFTDI_DEV(FTDI, SERIAL_232RL, 0), + UFTDI_DEV(FTDI, SERIAL_4232H, 0), + UFTDI_DEV(FTDI, SERIAL_8U100AX, 0), + UFTDI_DEV(FTDI, SERIAL_8U232AM, 0), + UFTDI_DEV(FTDI, SERIAL_8U232AM4, 0), + UFTDI_DEV(FTDI, SIGNALYZER_SH2, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, SIGNALYZER_SH4, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, SIGNALYZER_SLITE, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, SIGNALYZER_ST, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, SPECIAL_1, 0), + UFTDI_DEV(FTDI, SPECIAL_3, 0), + UFTDI_DEV(FTDI, SPECIAL_4, 0), + UFTDI_DEV(FTDI, SPROG_II, 0), + UFTDI_DEV(FTDI, SR_RADIO, 0), + UFTDI_DEV(FTDI, SUUNTO_SPORTS, 0), + UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13M, 0), + UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13S, 0), + UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13U, 0), + UFTDI_DEV(FTDI, TAVIR_STK500, 0), + UFTDI_DEV(FTDI, TERATRONIK_D2XX, 0), + UFTDI_DEV(FTDI, TERATRONIK_VCP, 0), + UFTDI_DEV(FTDI, THORLABS, 0), + UFTDI_DEV(FTDI, TNC_X, 0), + UFTDI_DEV(FTDI, TTUSB, 0), + UFTDI_DEV(FTDI, TURTELIZER2, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, UOPTBR, 0), + UFTDI_DEV(FTDI, USBSERIAL, 0), + UFTDI_DEV(FTDI, USBX_707, 0), + UFTDI_DEV(FTDI, USB_UIRT, 0), + UFTDI_DEV(FTDI, USINT_CAT, 0), + UFTDI_DEV(FTDI, USINT_RS232, 0), + UFTDI_DEV(FTDI, USINT_WKEY, 0), + UFTDI_DEV(FTDI, VARDAAN, 0), + UFTDI_DEV(FTDI, VNHCPCUSB_D, 0), + UFTDI_DEV(FTDI, WESTREX_MODEL_777, 0), + UFTDI_DEV(FTDI, WESTREX_MODEL_8900F, 0), + UFTDI_DEV(FTDI, XDS100V2, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, XDS100V3, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(FTDI, XF_547, 0), + UFTDI_DEV(FTDI, XF_640, 0), + UFTDI_DEV(FTDI, XF_642, 0), + UFTDI_DEV(FTDI, XM_RADIO, 0), + UFTDI_DEV(FTDI, YEI_SERVOCENTER31, 0), + UFTDI_DEV(GNOTOMETRICS, USB, 0), + UFTDI_DEV(ICOM, SP1, 0), + UFTDI_DEV(ICOM, OPC_U_UC, 0), + UFTDI_DEV(ICOM, RP2C1, 0), + UFTDI_DEV(ICOM, RP2C2, 0), + UFTDI_DEV(ICOM, RP2D, 0), + UFTDI_DEV(ICOM, RP2KVR, 0), + UFTDI_DEV(ICOM, RP2KVT, 0), + UFTDI_DEV(ICOM, RP2VR, 0), + UFTDI_DEV(ICOM, RP2VT, 0), + UFTDI_DEV(ICOM, RP4KVR, 0), + UFTDI_DEV(ICOM, RP4KVT, 0), + UFTDI_DEV(IDTECH, IDT1221U, 0), + UFTDI_DEV(INTERBIOMETRICS, IOBOARD, 0), + UFTDI_DEV(INTERBIOMETRICS, MINI_IOBOARD, 0), + UFTDI_DEV(INTREPIDCS, NEOVI, 0), + UFTDI_DEV(INTREPIDCS, VALUECAN, 0), + UFTDI_DEV(IONICS, PLUGCOMPUTER, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(JETI, SPC1201, 0), + UFTDI_DEV(KOBIL, CONV_B1, 0), + UFTDI_DEV(KOBIL, CONV_KAAN, 0), + UFTDI_DEV(LARSENBRUSGAARD, ALTITRACK, 0), + UFTDI_DEV(MARVELL, SHEEVAPLUG, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0100, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0101, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0102, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0103, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0104, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0105, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0106, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0107, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0108, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0109, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010A, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010B, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010C, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010D, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010E, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010F, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0110, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0111, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0112, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0113, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0114, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0115, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0116, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0117, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0118, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0119, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011A, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011B, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011C, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011D, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011E, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011F, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0120, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0121, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0122, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0123, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0124, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0125, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0126, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0128, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0129, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012A, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012B, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012D, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012E, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012F, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0130, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0131, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0132, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0133, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0134, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0135, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0136, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0137, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0138, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0139, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013A, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013B, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013C, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013D, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013E, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013F, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0140, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0141, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0142, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0143, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0144, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0145, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0146, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0147, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0148, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0149, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014A, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014B, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014C, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014D, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014E, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014F, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0150, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0151, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0152, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0159, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015A, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015B, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015C, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015D, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015E, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015F, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0160, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0161, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0162, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0163, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0164, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0165, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0166, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0167, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0168, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0169, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016A, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016B, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016C, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016D, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016E, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016F, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0170, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0171, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0172, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0173, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0174, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0175, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0176, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0177, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0178, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0179, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017A, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017B, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017C, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017D, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017E, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017F, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0180, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0181, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0182, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0183, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0184, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0185, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0186, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0187, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0188, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0189, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018A, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018B, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018C, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018D, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018E, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018F, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0190, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0191, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0192, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0193, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0194, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0195, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0196, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0197, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0198, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0199, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019A, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019B, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019C, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019D, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019E, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019F, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A0, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A1, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A2, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A3, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A4, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A5, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A6, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A7, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A8, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A9, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AA, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AB, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AC, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AD, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AE, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AF, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B0, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B1, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B2, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B3, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B4, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B5, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B6, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B7, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B8, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B9, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BA, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BB, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BC, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BD, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BE, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BF, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C0, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C1, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C2, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C3, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C4, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C5, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C6, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C7, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C8, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C9, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CA, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CB, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CC, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CD, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CE, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CF, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D0, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D1, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D2, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D3, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D4, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D5, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D6, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D7, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D8, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D9, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DA, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DB, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DC, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DD, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DE, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DF, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E0, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E1, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E2, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E3, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E4, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E5, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E6, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E7, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E8, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E9, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EA, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EB, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EC, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01ED, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EE, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EF, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F0, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F1, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F2, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F3, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F4, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F5, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F6, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F7, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F8, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F9, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FA, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FB, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FC, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FD, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FE, 0), + UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FF, 0), + UFTDI_DEV(MATRIXORBITAL, MOUA, 0), + UFTDI_DEV(MELCO, PCOPRS1, 0), + UFTDI_DEV(METAGEEK, TELLSTICK, 0), + UFTDI_DEV(MOBILITY, USB_SERIAL, 0), + UFTDI_DEV(OLIMEX, ARM_USB_OCD, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(OLIMEX, ARM_USB_OCD_H, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(OPTO, CRD7734, 0), + UFTDI_DEV(OPTO, CRD7734_1, 0), + UFTDI_DEV(PAPOUCH, AD4USB, 0), + UFTDI_DEV(PAPOUCH, AP485, 0), + UFTDI_DEV(PAPOUCH, AP485_2, 0), + UFTDI_DEV(PAPOUCH, DRAK5, 0), + UFTDI_DEV(PAPOUCH, DRAK6, 0), + UFTDI_DEV(PAPOUCH, GMSR, 0), + UFTDI_DEV(PAPOUCH, GMUX, 0), + UFTDI_DEV(PAPOUCH, IRAMP, 0), + UFTDI_DEV(PAPOUCH, LEC, 0), + UFTDI_DEV(PAPOUCH, MU, 0), + UFTDI_DEV(PAPOUCH, QUIDO10X1, 0), + UFTDI_DEV(PAPOUCH, QUIDO2X16, 0), + UFTDI_DEV(PAPOUCH, QUIDO2X2, 0), + UFTDI_DEV(PAPOUCH, QUIDO30X3, 0), + UFTDI_DEV(PAPOUCH, QUIDO3X32, 0), + UFTDI_DEV(PAPOUCH, QUIDO4X4, 0), + UFTDI_DEV(PAPOUCH, QUIDO60X3, 0), + UFTDI_DEV(PAPOUCH, QUIDO8X8, 0), + UFTDI_DEV(PAPOUCH, SB232, 0), + UFTDI_DEV(PAPOUCH, SB422, 0), + UFTDI_DEV(PAPOUCH, SB422_2, 0), + UFTDI_DEV(PAPOUCH, SB485, 0), + UFTDI_DEV(PAPOUCH, SB485C, 0), + UFTDI_DEV(PAPOUCH, SB485S, 0), + UFTDI_DEV(PAPOUCH, SB485_2, 0), + UFTDI_DEV(PAPOUCH, SIMUKEY, 0), + UFTDI_DEV(PAPOUCH, TMU, 0), + UFTDI_DEV(PAPOUCH, UPSUSB, 0), + UFTDI_DEV(POSIFLEX, PP7000, 0), + UFTDI_DEV(QIHARDWARE, JTAGSERIAL, UFTDI_JTAG_IFACE(0)), + UFTDI_DEV(RATOC, REXUSB60F, 0), + UFTDI_DEV(RTSYSTEMS, CT29B, 0), + UFTDI_DEV(RTSYSTEMS, SERIAL_VX7, 0), + UFTDI_DEV(SEALEVEL, 2101, 0), + UFTDI_DEV(SEALEVEL, 2102, 0), + UFTDI_DEV(SEALEVEL, 2103, 0), + UFTDI_DEV(SEALEVEL, 2104, 0), + UFTDI_DEV(SEALEVEL, 2106, 0), + UFTDI_DEV(SEALEVEL, 2201_1, 0), + UFTDI_DEV(SEALEVEL, 2201_2, 0), + UFTDI_DEV(SEALEVEL, 2202_1, 0), + UFTDI_DEV(SEALEVEL, 2202_2, 0), + UFTDI_DEV(SEALEVEL, 2203_1, 0), + UFTDI_DEV(SEALEVEL, 2203_2, 0), + UFTDI_DEV(SEALEVEL, 2401_1, 0), + UFTDI_DEV(SEALEVEL, 2401_2, 0), + UFTDI_DEV(SEALEVEL, 2401_3, 0), + UFTDI_DEV(SEALEVEL, 2401_4, 0), + UFTDI_DEV(SEALEVEL, 2402_1, 0), + UFTDI_DEV(SEALEVEL, 2402_2, 0), + UFTDI_DEV(SEALEVEL, 2402_3, 0), + UFTDI_DEV(SEALEVEL, 2402_4, 0), + UFTDI_DEV(SEALEVEL, 2403_1, 0), + UFTDI_DEV(SEALEVEL, 2403_2, 0), + UFTDI_DEV(SEALEVEL, 2403_3, 0), + UFTDI_DEV(SEALEVEL, 2403_4, 0), + UFTDI_DEV(SEALEVEL, 2801_1, 0), + UFTDI_DEV(SEALEVEL, 2801_2, 0), + UFTDI_DEV(SEALEVEL, 2801_3, 0), + UFTDI_DEV(SEALEVEL, 2801_4, 0), + UFTDI_DEV(SEALEVEL, 2801_5, 0), + UFTDI_DEV(SEALEVEL, 2801_6, 0), + UFTDI_DEV(SEALEVEL, 2801_7, 0), + UFTDI_DEV(SEALEVEL, 2801_8, 0), + UFTDI_DEV(SEALEVEL, 2802_1, 0), + UFTDI_DEV(SEALEVEL, 2802_2, 0), + UFTDI_DEV(SEALEVEL, 2802_3, 0), + UFTDI_DEV(SEALEVEL, 2802_4, 0), + UFTDI_DEV(SEALEVEL, 2802_5, 0), + UFTDI_DEV(SEALEVEL, 2802_6, 0), + UFTDI_DEV(SEALEVEL, 2802_7, 0), + UFTDI_DEV(SEALEVEL, 2802_8, 0), + UFTDI_DEV(SEALEVEL, 2803_1, 0), + UFTDI_DEV(SEALEVEL, 2803_2, 0), + UFTDI_DEV(SEALEVEL, 2803_3, 0), + UFTDI_DEV(SEALEVEL, 2803_4, 0), + UFTDI_DEV(SEALEVEL, 2803_5, 0), + UFTDI_DEV(SEALEVEL, 2803_6, 0), + UFTDI_DEV(SEALEVEL, 2803_7, 0), + UFTDI_DEV(SEALEVEL, 2803_8, 0), + UFTDI_DEV(SIIG2, DK201, 0), + UFTDI_DEV(SIIG2, US2308, 0), + UFTDI_DEV(TESTO, USB_INTERFACE, 0), + UFTDI_DEV(TML, USB_SERIAL, 0), + UFTDI_DEV(TTI, QL355P, 0), + UFTDI_DEV(UNKNOWN4, NF_RIC, 0), +#undef UFTDI_DEV +}; + +DRIVER_MODULE(uftdi, uhub, uftdi_driver, uftdi_devclass, NULL, NULL); +MODULE_DEPEND(uftdi, ucom, 1, 1, 1); +MODULE_DEPEND(uftdi, usb, 1, 1, 1); +MODULE_VERSION(uftdi, 1); +USB_PNP_HOST_INFO(uftdi_devs); + +/* + * Jtag product name strings table. Some products have one or more interfaces + * dedicated to jtag or gpio, but use a product ID that's the same as other + * products which don't. They are marked with a flag in the table above, and + * the following string table is checked for flagged products. The string check + * is done with strstr(); in effect there is an implicit wildcard at the + * beginning and end of each product name string in table. + */ +static const struct jtag_by_name { + const char * product_name; + uint32_t jtag_interfaces; +} jtag_products_by_name[] = { + /* TI Beaglebone and TI XDS100Vn jtag product line. */ + {"XDS100V", UFTDI_JTAG_IFACE(0)}, +}; + +/* + * Set up a sysctl and tunable to en/disable the feature of skipping the + * creation of tty devices for jtag interfaces. Enabled by default. + */ +static int skip_jtag_interfaces = 1; +SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, skip_jtag_interfaces, CTLFLAG_RWTUN, + &skip_jtag_interfaces, 1, "Skip creating tty devices for jtag interfaces"); + +static boolean_t +is_jtag_interface(struct usb_attach_arg *uaa, const struct usb_device_id *id) +{ + int i, iface_bit; + const char * product_name; + const struct jtag_by_name *jbn; + + /* We only allocate 8 flag bits for jtag interface flags. */ + if (uaa->info.bIfaceIndex >= UFTDI_JTAG_IFACES_MAX) + return (0); + iface_bit = UFTDI_JTAG_IFACE(uaa->info.bIfaceIndex); + + /* + * If requested, search the name strings table and use the interface + * bits from that table when the product name string matches, else use + * the jtag interface bits from the main ID table. + */ + if ((id->driver_info & UFTDI_JTAG_MASK) == UFTDI_JTAG_CHECK_STRING) { + product_name = usb_get_product(uaa->device); + for (i = 0; i < nitems(jtag_products_by_name); i++) { + jbn = &jtag_products_by_name[i]; + if (strstr(product_name, jbn->product_name) != NULL && + (jbn->jtag_interfaces & iface_bit) != 0) + return (1); + } + } else if ((id->driver_info & iface_bit) != 0) + return (1); + + return (0); +} + +/* + * Set up softc fields whose value depends on the device type. + * + * Note that the 2232C and 2232D devices are the same for our purposes. In the + * silicon the difference is that the D series has CPU FIFO mode and C doesn't. + * I haven't found any way of determining the C/D difference from info provided + * by the chip other than trying to set CPU FIFO mode and having it work or not. + * + * Due to a hardware bug, a 232B chip without an eeprom reports itself as a + * 232A, but if the serial number is also zero we know it's really a 232B. + */ +static void +uftdi_devtype_setup(struct uftdi_softc *sc, struct usb_attach_arg *uaa) +{ + struct usb_device_descriptor *dd; + + sc->sc_bcdDevice = uaa->info.bcdDevice; + + switch (uaa->info.bcdDevice) { + case 0x200: + dd = usbd_get_device_descriptor(sc->sc_udev); + if (dd->iSerialNumber == 0) { + sc->sc_devtype = DEVT_232B; + } else { + sc->sc_devtype = DEVT_232A; + } + sc->sc_ucom.sc_portno = 0; + break; + case 0x400: + sc->sc_devtype = DEVT_232B; + sc->sc_ucom.sc_portno = 0; + break; + case 0x500: + sc->sc_devtype = DEVT_2232D; + sc->sc_devflags |= DEVF_BAUDBITS_HINDEX; + sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; + break; + case 0x600: + sc->sc_devtype = DEVT_232R; + sc->sc_ucom.sc_portno = 0; + break; + case 0x700: + sc->sc_devtype = DEVT_2232H; + sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; + sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; + break; + case 0x800: + sc->sc_devtype = DEVT_4232H; + sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; + sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; + break; + case 0x900: + sc->sc_devtype = DEVT_232H; + sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M; + sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; + break; + case 0x1000: + sc->sc_devtype = DEVT_230X; + sc->sc_devflags |= DEVF_BAUDBITS_HINDEX; + sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum; + break; + default: + if (uaa->info.bcdDevice < 0x200) { + sc->sc_devtype = DEVT_SIO; + sc->sc_hdrlen = 1; + } else { + sc->sc_devtype = DEVT_232R; + device_printf(sc->sc_dev, "Warning: unknown FTDI " + "device type, bcdDevice=0x%04x, assuming 232R\n", + uaa->info.bcdDevice); + } + sc->sc_ucom.sc_portno = 0; + break; + } +} + +static int +uftdi_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + const struct usb_device_id *id; + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UFTDI_CONFIG_INDEX) { + return (ENXIO); + } + + /* + * Attach to all present interfaces unless this is a JTAG one, which + * we leave for userland. + */ + id = usbd_lookup_id_by_info(uftdi_devs, sizeof(uftdi_devs), + &uaa->info); + if (id == NULL) + return (ENXIO); + if (skip_jtag_interfaces && is_jtag_interface(uaa, id)) { + printf("%s: skipping JTAG interface #%d for '%s' at %u.%u\n", + device_get_name(dev), + uaa->info.bIfaceIndex, + usb_get_product(uaa->device), + usbd_get_bus_index(uaa->device), + usbd_get_device_index(uaa->device)); + return (ENXIO); + } + uaa->driver_info = id->driver_info; + return (BUS_PROBE_SPECIFIC); +} + +static int +uftdi_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uftdi_softc *sc = device_get_softc(dev); + int error; + + DPRINTF("\n"); + + sc->sc_udev = uaa->device; + sc->sc_dev = dev; + sc->sc_unit = device_get_unit(dev); + sc->sc_bitmode = UFTDI_BITMODE_NONE; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uftdi", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + + uftdi_devtype_setup(sc, uaa); + + error = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, uftdi_config, + UFTDI_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating USB " + "transfers failed\n"); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + /* 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)); + + /* Indicate tx bits in sc_lsr can be used to determine busy vs idle. */ + ucom_use_lsr_txbits(&sc->sc_ucom); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uftdi_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); /* success */ + +detach: + uftdi_detach(dev); + return (ENXIO); +} + +static int +uftdi_detach(device_t dev) +{ + struct uftdi_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UFTDI_N_TRANSFER); + + device_claim_softc(dev); + + uftdi_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(uftdi); + +static void +uftdi_free_softc(struct uftdi_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +uftdi_free(struct ucom_softc *ucom) +{ + uftdi_free_softc(ucom->sc_parent); +} + +static void +uftdi_cfg_open(struct ucom_softc *ucom) +{ + + /* + * This do-nothing open routine exists for the sole purpose of this + * DPRINTF() so that you can see the point at which open gets called + * when debugging is enabled. + */ + DPRINTF("\n"); +} + +static void +uftdi_cfg_close(struct ucom_softc *ucom) +{ + + /* + * This do-nothing close routine exists for the sole purpose of this + * DPRINTF() so that you can see the point at which close gets called + * when debugging is enabled. + */ + DPRINTF("\n"); +} + +static void +uftdi_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uftdi_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t pktlen; + uint32_t buflen; + uint8_t buf[1]; + + DPRINTFN(3, "\n"); + + switch (USB_GET_STATE(xfer)) { + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + } + /* FALLTHROUGH */ + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + /* + * If output packets don't require headers (the common case) we + * can just load the buffer up with payload bytes all at once. + * Otherwise, loop to format packets into the buffer while there + * is data available, and room for a packet header and at least + * one byte of payload. + * + * NOTE: The FTDI chip doesn't accept zero length + * packets. This cannot happen because the "pktlen" + * will always be non-zero when "ucom_get_data()" + * returns non-zero which we check below. + */ + pc = usbd_xfer_get_frame(xfer, 0); + if (sc->sc_hdrlen == 0) { + if (ucom_get_data(&sc->sc_ucom, pc, 0, UFTDI_OBUFSIZE, + &buflen) == 0) + break; + } else { + buflen = 0; + while (buflen < UFTDI_OBUFSIZE - sc->sc_hdrlen - 1 && + ucom_get_data(&sc->sc_ucom, pc, buflen + + sc->sc_hdrlen, UFTDI_OPKTSIZE - sc->sc_hdrlen, + &pktlen) != 0) { + buf[0] = FTDI_OUT_TAG(pktlen, + sc->sc_ucom.sc_portno); + usbd_copy_in(pc, buflen, buf, 1); + buflen += pktlen + sc->sc_hdrlen; + } + } + if (buflen != 0) { + usbd_xfer_set_frame_len(xfer, 0, buflen); + usbd_transfer_submit(xfer); + } + break; + } +} + +static void +uftdi_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uftdi_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[2]; + uint8_t ftdi_msr; + uint8_t msr; + uint8_t lsr; + int buflen; + int pktlen; + int pktmax; + int offset; + + DPRINTFN(3, "\n"); + + usbd_xfer_status(xfer, &buflen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (buflen < UFTDI_IHDRSIZE) + goto tr_setup; + pc = usbd_xfer_get_frame(xfer, 0); + pktmax = xfer->max_packet_size - UFTDI_IHDRSIZE; + lsr = 0; + msr = 0; + offset = 0; + /* + * Extract packet headers and payload bytes from the buffer. + * Feed payload bytes to ucom/tty layer; OR-accumulate the + * receiver-related header status bits which are transient and + * could toggle with each packet, but for transmitter-related + * bits keep only the ones from the last packet. + * + * After processing all packets in the buffer, process the + * accumulated transient MSR and LSR values along with the + * non-transient bits from the last packet header. + */ + while (buflen >= UFTDI_IHDRSIZE) { + usbd_copy_out(pc, offset, buf, UFTDI_IHDRSIZE); + offset += UFTDI_IHDRSIZE; + buflen -= UFTDI_IHDRSIZE; + lsr &= ~(ULSR_TXRDY | ULSR_TSRE); + lsr |= FTDI_GET_LSR(buf); + if (FTDI_GET_MSR(buf) & FTDI_SIO_RI_MASK) + msr |= SER_RI; + pktlen = min(buflen, pktmax); + if (pktlen != 0) { + ucom_put_data(&sc->sc_ucom, pc, offset, + pktlen); + offset += pktlen; + buflen -= pktlen; + } + } + ftdi_msr = FTDI_GET_MSR(buf); + + 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 != lsr) { + 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; + + ucom_status_change(&sc->sc_ucom); + } + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uftdi_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb_device_request req; + + DPRINTFN(2, "DTR=%u\n", onoff); + + 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); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uftdi_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb_device_request req; + + DPRINTFN(2, "RTS=%u\n", onoff); + + 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); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uftdi_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + uint16_t wValue; + struct usb_device_request req; + + DPRINTFN(2, "BREAK=%u\n", onoff); + + 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); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +/* + * Return true if the given speed is within operational tolerance of the target + * speed. FTDI recommends that the hardware speed be within 3% of nominal. + */ +static inline boolean_t +uftdi_baud_within_tolerance(uint64_t speed, uint64_t target) +{ + return ((speed >= (target * 100) / 103) && + (speed <= (target * 100) / 97)); +} + +static int +uftdi_sio_encode_baudrate(struct uftdi_softc *sc, speed_t speed, + struct uftdi_param_config *cfg) +{ + u_int i; + const speed_t sio_speeds[] = { + 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 + }; + + /* + * The original SIO chips were limited to a small choice of speeds + * listed in an internal table of speeds chosen by an index value. + */ + for (i = 0; i < nitems(sio_speeds); ++i) { + if (speed == sio_speeds[i]) { + cfg->baud_lobits = i; + cfg->baud_hibits = 0; + return (0); + } + } + return (ERANGE); +} + +static int +uftdi_encode_baudrate(struct uftdi_softc *sc, speed_t speed, + struct uftdi_param_config *cfg) +{ + static const uint8_t encoded_fraction[8] = {0, 3, 2, 4, 1, 5, 6, 7}; + static const uint8_t roundoff_232a[16] = { + 0, 1, 0, 1, 0, -1, 2, 1, + 0, -1, -2, -3, 4, 3, 2, 1, + }; + uint32_t clk, divisor, fastclk_flag, frac, hwspeed; + + /* + * If this chip has the fast clock capability and the speed is within + * range, use the 12MHz clock, otherwise the standard clock is 3MHz. + */ + if ((sc->sc_devflags & DEVF_BAUDCLK_12M) && speed >= 1200) { + clk = 12000000; + fastclk_flag = (1 << 17); + } else { + clk = 3000000; + fastclk_flag = 0; + } + + /* + * Make sure the requested speed is reachable with the available clock + * and a 14-bit divisor. + */ + if (speed < (clk >> 14) || speed > clk) + return (ERANGE); + + /* + * Calculate the divisor, initially yielding a fixed point number with a + * 4-bit (1/16ths) fraction, then round it to the nearest fraction the + * hardware can handle. When the integral part of the divisor is + * greater than one, the fractional part is in 1/8ths of the base clock. + * The FT8U232AM chips can handle only 0.125, 0.250, and 0.5 fractions. + * Later chips can handle all 1/8th fractions. + * + * If the integral part of the divisor is 1, a special rule applies: the + * fractional part can only be .0 or .5 (this is a limitation of the + * hardware). We handle this by truncating the fraction rather than + * rounding, because this only applies to the two fastest speeds the + * chip can achieve and rounding doesn't matter, either you've asked for + * that exact speed or you've asked for something the chip can't do. + * + * For the FT8U232AM chips, use a roundoff table to adjust the result + * to the nearest 1/8th fraction that is supported by the hardware, + * leaving a fixed-point number with a 3-bit fraction which exactly + * represents the math the hardware divider will do. For later-series + * chips that support all 8 fractional divisors, just round 16ths to + * 8ths by adding 1 and dividing by 2. + */ + divisor = (clk << 4) / speed; + if ((divisor & 0xf) == 1) + divisor &= 0xfffffff8; + else if (sc->sc_devtype == DEVT_232A) + divisor += roundoff_232a[divisor & 0x0f]; + else + divisor += 1; /* Rounds odd 16ths up to next 8th. */ + divisor >>= 1; + + /* + * Ensure the resulting hardware speed will be within operational + * tolerance (within 3% of nominal). + */ + hwspeed = (clk << 3) / divisor; + if (!uftdi_baud_within_tolerance(hwspeed, speed)) + return (ERANGE); + + /* + * Re-pack the divisor into hardware format. The lower 14-bits hold the + * integral part, while the upper bits specify the fraction by indexing + * a table of fractions within the hardware which is laid out as: + * {0.0, 0.5, 0.25, 0.125, 0.325, 0.625, 0.725, 0.875} + * The A-series chips only have the first four table entries; the + * roundoff table logic above ensures that the fractional part for those + * chips will be one of the first four values. + * + * When the divisor is 1 a special encoding applies: 1.0 is encoded as + * 0.0, and 1.5 is encoded as 1.0. The rounding logic above has already + * ensured that the fraction is either .0 or .5 if the integral is 1. + */ + frac = divisor & 0x07; + divisor >>= 3; + if (divisor == 1) { + if (frac == 0) + divisor = 0; /* 1.0 becomes 0.0 */ + else + frac = 0; /* 1.5 becomes 1.0 */ + } + divisor |= (encoded_fraction[frac] << 14) | fastclk_flag; + + cfg->baud_lobits = (uint16_t)divisor; + cfg->baud_hibits = (uint16_t)(divisor >> 16); + + /* + * If this chip requires the baud bits to be in the high byte of the + * index word, move the bits up to that location. + */ + if (sc->sc_devflags & DEVF_BAUDBITS_HINDEX) { + cfg->baud_hibits <<= 8; + } + + return (0); +} + +static int +uftdi_set_parm_soft(struct ucom_softc *ucom, struct termios *t, + struct uftdi_param_config *cfg) +{ + struct uftdi_softc *sc = ucom->sc_parent; + int err; + + memset(cfg, 0, sizeof(*cfg)); + + if (sc->sc_devtype == DEVT_SIO) + err = uftdi_sio_encode_baudrate(sc, t->c_ospeed, cfg); + else + err = uftdi_encode_baudrate(sc, t->c_ospeed, cfg); + if (err != 0) + return (err); + + 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 ucom_softc *ucom, struct termios *t) +{ + struct uftdi_param_config cfg; + + DPRINTF("\n"); + + return (uftdi_set_parm_soft(ucom, t, &cfg)); +} + +static void +uftdi_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uftdi_softc *sc = ucom->sc_parent; + uint16_t wIndex = ucom->sc_portno; + struct uftdi_param_config cfg; + struct usb_device_request req; + + DPRINTF("\n"); + + if (uftdi_set_parm_soft(ucom, t, &cfg)) { + /* should not happen */ + return; + } + sc->sc_last_lcr = cfg.lcr; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_BAUD_RATE; + USETW(req.wValue, cfg.baud_lobits); + USETW(req.wIndex, cfg.baud_hibits | wIndex); + USETW(req.wLength, 0); + ucom_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); + ucom_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); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uftdi_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + DPRINTFN(3, "msr=0x%02x lsr=0x%02x\n", sc->sc_msr, sc->sc_lsr); + + *msr = sc->sc_msr; + *lsr = sc->sc_lsr; +} + +static int +uftdi_reset(struct ucom_softc *ucom, int reset_type) +{ + struct uftdi_softc *sc = ucom->sc_parent; + usb_device_request_t req; + + DPRINTFN(2, "\n"); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_RESET; + + USETW(req.wIndex, sc->sc_ucom.sc_portno); + USETW(req.wLength, 0); + USETW(req.wValue, reset_type); + + return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); +} + +static int +uftdi_set_bitmode(struct ucom_softc *ucom, uint8_t bitmode, uint8_t iomask) +{ + struct uftdi_softc *sc = ucom->sc_parent; + usb_device_request_t req; + int rv; + + DPRINTFN(2, "\n"); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_BITMODE; + + USETW(req.wIndex, sc->sc_ucom.sc_portno); + USETW(req.wLength, 0); + + if (bitmode == UFTDI_BITMODE_NONE) + USETW2(req.wValue, 0, 0); + else + USETW2(req.wValue, (1 << bitmode), iomask); + + rv = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); + if (rv == USB_ERR_NORMAL_COMPLETION) + sc->sc_bitmode = bitmode; + + return (rv); +} + +static int +uftdi_get_bitmode(struct ucom_softc *ucom, uint8_t *bitmode, uint8_t *iomask) +{ + struct uftdi_softc *sc = ucom->sc_parent; + usb_device_request_t req; + + DPRINTFN(2, "\n"); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_GET_BITMODE; + + USETW(req.wIndex, sc->sc_ucom.sc_portno); + USETW(req.wLength, 1); + USETW(req.wValue, 0); + + *bitmode = sc->sc_bitmode; + return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, iomask)); +} + +static int +uftdi_set_latency(struct ucom_softc *ucom, int latency) +{ + struct uftdi_softc *sc = ucom->sc_parent; + usb_device_request_t req; + + DPRINTFN(2, "\n"); + + if (latency < 0 || latency > 255) + return (USB_ERR_INVAL); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_LATENCY; + + USETW(req.wIndex, sc->sc_ucom.sc_portno); + USETW(req.wLength, 0); + USETW2(req.wValue, 0, latency); + + return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); +} + +static int +uftdi_get_latency(struct ucom_softc *ucom, int *latency) +{ + struct uftdi_softc *sc = ucom->sc_parent; + usb_device_request_t req; + usb_error_t err; + uint8_t buf; + + DPRINTFN(2, "\n"); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_GET_LATENCY; + + USETW(req.wIndex, sc->sc_ucom.sc_portno); + USETW(req.wLength, 1); + USETW(req.wValue, 0); + + err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &buf); + *latency = buf; + + return (err); +} + +static int +uftdi_set_event_char(struct ucom_softc *ucom, int echar) +{ + struct uftdi_softc *sc = ucom->sc_parent; + usb_device_request_t req; + uint8_t enable; + + DPRINTFN(2, "\n"); + + enable = (echar == -1) ? 0 : 1; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_EVENT_CHAR; + + USETW(req.wIndex, sc->sc_ucom.sc_portno); + USETW(req.wLength, 0); + USETW2(req.wValue, enable, echar & 0xff); + + return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); +} + +static int +uftdi_set_error_char(struct ucom_softc *ucom, int echar) +{ + struct uftdi_softc *sc = ucom->sc_parent; + usb_device_request_t req; + uint8_t enable; + + DPRINTFN(2, "\n"); + + enable = (echar == -1) ? 0 : 1; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_ERROR_CHAR; + + USETW(req.wIndex, sc->sc_ucom.sc_portno); + USETW(req.wLength, 0); + USETW2(req.wValue, enable, echar & 0xff); + + return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL)); +} + +static int +uftdi_read_eeprom(struct ucom_softc *ucom, struct uftdi_eeio *eeio) +{ + struct uftdi_softc *sc = ucom->sc_parent; + usb_device_request_t req; + usb_error_t err; + uint16_t widx, wlength, woffset; + + DPRINTFN(3, "\n"); + + /* Offset and length must both be evenly divisible by two. */ + if ((eeio->offset | eeio->length) & 0x01) + return (EINVAL); + + woffset = eeio->offset / 2U; + wlength = eeio->length / 2U; + for (widx = 0; widx < wlength; widx++) { + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_READ_EEPROM; + USETW(req.wIndex, widx + woffset); + USETW(req.wLength, 2); + USETW(req.wValue, 0); + err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, + &eeio->data[widx]); + if (err != USB_ERR_NORMAL_COMPLETION) + return (err); + } + return (USB_ERR_NORMAL_COMPLETION); +} + +static int +uftdi_write_eeprom(struct ucom_softc *ucom, struct uftdi_eeio *eeio) +{ + struct uftdi_softc *sc = ucom->sc_parent; + usb_device_request_t req; + usb_error_t err; + uint16_t widx, wlength, woffset; + + DPRINTFN(3, "\n"); + + /* Offset and length must both be evenly divisible by two. */ + if ((eeio->offset | eeio->length) & 0x01) + return (EINVAL); + + woffset = eeio->offset / 2U; + wlength = eeio->length / 2U; + for (widx = 0; widx < wlength; widx++) { + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_WRITE_EEPROM; + USETW(req.wIndex, widx + woffset); + USETW(req.wLength, 0); + USETW(req.wValue, eeio->data[widx]); + err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); + if (err != USB_ERR_NORMAL_COMPLETION) + return (err); + } + return (USB_ERR_NORMAL_COMPLETION); +} + +static int +uftdi_erase_eeprom(struct ucom_softc *ucom, int confirmation) +{ + struct uftdi_softc *sc = ucom->sc_parent; + usb_device_request_t req; + usb_error_t err; + + DPRINTFN(2, "\n"); + + /* Small effort to prevent accidental erasure. */ + if (confirmation != UFTDI_CONFIRM_ERASE) + return (EINVAL); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_ERASE_EEPROM; + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + USETW(req.wValue, 0); + err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL); + + return (err); +} + +static int +uftdi_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, + int flag, struct thread *td) +{ + struct uftdi_softc *sc = ucom->sc_parent; + int err; + struct uftdi_bitmode * mode; + + switch (cmd) { + case UFTDIIOC_RESET_IO: + case UFTDIIOC_RESET_RX: + case UFTDIIOC_RESET_TX: + err = uftdi_reset(ucom, + cmd == UFTDIIOC_RESET_IO ? FTDI_SIO_RESET_SIO : + (cmd == UFTDIIOC_RESET_RX ? FTDI_SIO_RESET_PURGE_RX : + FTDI_SIO_RESET_PURGE_TX)); + break; + case UFTDIIOC_SET_BITMODE: + mode = (struct uftdi_bitmode *)data; + err = uftdi_set_bitmode(ucom, mode->mode, mode->iomask); + break; + case UFTDIIOC_GET_BITMODE: + mode = (struct uftdi_bitmode *)data; + err = uftdi_get_bitmode(ucom, &mode->mode, &mode->iomask); + break; + case UFTDIIOC_SET_LATENCY: + err = uftdi_set_latency(ucom, *((int *)data)); + break; + case UFTDIIOC_GET_LATENCY: + err = uftdi_get_latency(ucom, (int *)data); + break; + case UFTDIIOC_SET_ERROR_CHAR: + err = uftdi_set_error_char(ucom, *(int *)data); + break; + case UFTDIIOC_SET_EVENT_CHAR: + err = uftdi_set_event_char(ucom, *(int *)data); + break; + case UFTDIIOC_GET_HWREV: + *(int *)data = sc->sc_bcdDevice; + err = 0; + break; + case UFTDIIOC_READ_EEPROM: + err = uftdi_read_eeprom(ucom, (struct uftdi_eeio *)data); + break; + case UFTDIIOC_WRITE_EEPROM: + err = uftdi_write_eeprom(ucom, (struct uftdi_eeio *)data); + break; + case UFTDIIOC_ERASE_EEPROM: + err = uftdi_erase_eeprom(ucom, *(int *)data); + break; + default: + return (ENOIOCTL); + } + if (err != USB_ERR_NORMAL_COMPLETION) + return (EIO); + return (0); +} + +static void +uftdi_start_read(struct ucom_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_RD]); +} + +static void +uftdi_stop_read(struct ucom_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_RD]); +} + +static void +uftdi_start_write(struct ucom_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_WR]); +} + +static void +uftdi_stop_write(struct ucom_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_WR]); +} + +static void +uftdi_poll(struct ucom_softc *ucom) +{ + struct uftdi_softc *sc = ucom->sc_parent; + + usbd_transfer_poll(sc->sc_xfer, UFTDI_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/uftdi_reg.h b/freebsd/sys/dev/usb/serial/uftdi_reg.h new file mode 100644 index 00000000..a1ea325b --- /dev/null +++ b/freebsd/sys/dev/usb/serial/uftdi_reg.h @@ -0,0 +1,314 @@ +/* $NetBSD: uftdireg.h,v 1.6 2002/07/11 21:14:28 augustss Exp $ */ +/* $FreeBSD$ */ + +/* + * Definitions for the FTDI USB Single Port Serial Converter - + * known as FTDI_SIO (Serial Input/Output application of the chipset) + * + * The device is based on the FTDI FT8U100AX chip. It has a DB25 on one side, + * USB on the other. + * + * Thanx to FTDI (http://www.ftdi.co.uk) for so kindly providing details + * of the protocol required to talk to the device and ongoing assistence + * during development. + * + * Bill Ryder - bryder@sgi.com of Silicon Graphics, Inc. is the original + * author of this file. + */ +/* Modified by Lennart Augustsson */ + +/* Vendor Request Interface */ +#define FTDI_SIO_RESET 0 /* Reset the port */ +#define FTDI_SIO_MODEM_CTRL 1 /* Set the modem control register */ +#define FTDI_SIO_SET_FLOW_CTRL 2 /* Set flow control register */ +#define FTDI_SIO_SET_BAUD_RATE 3 /* Set baud rate */ +#define FTDI_SIO_SET_DATA 4 /* Set the data characteristics of the + * port */ +#define FTDI_SIO_GET_STATUS 5 /* Retrieve current value of status + * reg */ +#define FTDI_SIO_SET_EVENT_CHAR 6 /* Set the event character */ +#define FTDI_SIO_SET_ERROR_CHAR 7 /* Set the error character */ +#define FTDI_SIO_SET_LATENCY 9 /* Set the latency timer */ +#define FTDI_SIO_GET_LATENCY 10 /* Read the latency timer */ +#define FTDI_SIO_SET_BITMODE 11 /* Set the bit bang I/O mode */ +#define FTDI_SIO_GET_BITMODE 12 /* Read pin states from any mode */ +#define FTDI_SIO_READ_EEPROM 144 /* Read eeprom word */ +#define FTDI_SIO_WRITE_EEPROM 145 /* Write eeprom word */ +#define FTDI_SIO_ERASE_EEPROM 146 /* Erase entire eeprom */ + +/* Port Identifier Table */ +#define FTDI_PIT_DEFAULT 0 /* SIOA */ +#define FTDI_PIT_SIOA 1 /* SIOA */ +#define FTDI_PIT_SIOB 2 /* SIOB */ +#define FTDI_PIT_PARALLEL 3 /* Parallel */ + +/* Values for driver_info */ +#define UFTDI_JTAG_IFACE(i) (1 << i) /* Flag interface as jtag */ +#define UFTDI_JTAG_IFACES_MAX 8 /* Allow up to 8 jtag intfs */ +#define UFTDI_JTAG_CHECK_STRING 0xff /* Check product names table */ +#define UFTDI_JTAG_MASK 0xff + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_RESET + * wValue: Control Value + * 0 = Reset SIO + * 1 = Purge RX buffer + * 2 = Purge TX buffer + * wIndex: Port + * wLength: 0 + * Data: None + * + * The Reset SIO command has this effect: + * + * Sets flow control set to 'none' + * Event char = 0x0d + * Event trigger = disabled + * Purge RX buffer + * Purge TX buffer + * Clear DTR + * Clear RTS + * baud and data format not reset + * + * The Purge RX and TX buffer commands affect nothing except the buffers + */ +/* FTDI_SIO_RESET */ +#define FTDI_SIO_RESET_SIO 0 +#define FTDI_SIO_RESET_PURGE_RX 1 +#define FTDI_SIO_RESET_PURGE_TX 2 + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_SET_BAUDRATE + * wValue: BaudRate low bits + * wIndex: Port and BaudRate high bits + * wLength: 0 + * Data: None + */ +/* FTDI_SIO_SET_BAUDRATE */ + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_SET_DATA + * wValue: Data characteristics (see below) + * wIndex: Port + * wLength: 0 + * Data: No + * + * Data characteristics + * + * B0..7 Number of data bits + * B8..10 Parity + * 0 = None + * 1 = Odd + * 2 = Even + * 3 = Mark + * 4 = Space + * B11..13 Stop Bits + * 0 = 1 + * 1 = 1.5 + * 2 = 2 + * B14..15 Reserved + * + */ +/* FTDI_SIO_SET_DATA */ +#define FTDI_SIO_SET_DATA_BITS(n) (n) +#define FTDI_SIO_SET_DATA_PARITY_NONE (0x0 << 8) +#define FTDI_SIO_SET_DATA_PARITY_ODD (0x1 << 8) +#define FTDI_SIO_SET_DATA_PARITY_EVEN (0x2 << 8) +#define FTDI_SIO_SET_DATA_PARITY_MARK (0x3 << 8) +#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8) +#define FTDI_SIO_SET_DATA_STOP_BITS_1 (0x0 << 11) +#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11) +#define FTDI_SIO_SET_DATA_STOP_BITS_2 (0x2 << 11) +#define FTDI_SIO_SET_BREAK (0x1 << 14) + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_MODEM_CTRL + * wValue: ControlValue (see below) + * wIndex: Port + * wLength: 0 + * Data: None + * + * NOTE: If the device is in RTS/CTS flow control, the RTS set by this + * command will be IGNORED without an error being returned + * Also - you can not set DTR and RTS with one control message + * + * ControlValue + * B0 DTR state + * 0 = reset + * 1 = set + * B1 RTS state + * 0 = reset + * 1 = set + * B2..7 Reserved + * B8 DTR state enable + * 0 = ignore + * 1 = use DTR state + * B9 RTS state enable + * 0 = ignore + * 1 = use RTS state + * B10..15 Reserved + */ +/* FTDI_SIO_MODEM_CTRL */ +#define FTDI_SIO_SET_DTR_MASK 0x1 +#define FTDI_SIO_SET_DTR_HIGH (1 | ( FTDI_SIO_SET_DTR_MASK << 8)) +#define FTDI_SIO_SET_DTR_LOW (0 | ( FTDI_SIO_SET_DTR_MASK << 8)) +#define FTDI_SIO_SET_RTS_MASK 0x2 +#define FTDI_SIO_SET_RTS_HIGH (2 | ( FTDI_SIO_SET_RTS_MASK << 8)) +#define FTDI_SIO_SET_RTS_LOW (0 | ( FTDI_SIO_SET_RTS_MASK << 8)) + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_FLOW_CTRL + * wValue: Xoff/Xon + * wIndex: Protocol/Port - hIndex is protocol / lIndex is port + * wLength: 0 + * Data: None + * + * hIndex protocol is: + * B0 Output handshaking using RTS/CTS + * 0 = disabled + * 1 = enabled + * B1 Output handshaking using DTR/DSR + * 0 = disabled + * 1 = enabled + * B2 Xon/Xoff handshaking + * 0 = disabled + * 1 = enabled + * + * A value of zero in the hIndex field disables handshaking + * + * If Xon/Xoff handshaking is specified, the hValue field should contain the + * XOFF character and the lValue field contains the XON character. + */ +/* FTDI_SIO_SET_FLOW_CTRL */ +#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0 +#define FTDI_SIO_RTS_CTS_HS 0x1 +#define FTDI_SIO_DTR_DSR_HS 0x2 +#define FTDI_SIO_XON_XOFF_HS 0x4 + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_EVENT_CHAR + * wValue: Event Char + * wIndex: Port + * wLength: 0 + * Data: None + * + * wValue: + * B0..7 Event Character + * B8 Event Character Processing + * 0 = disabled + * 1 = enabled + * B9..15 Reserved + * + * FTDI_SIO_SET_EVENT_CHAR + * + * Set the special event character for the specified communications port. + * If the device sees this character it will immediately return the + * data read so far - rather than wait 40ms or until 62 bytes are read + * which is what normally happens. + */ + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_ERROR_CHAR + * wValue: Error Char + * wIndex: Port + * wLength: 0 + * Data: None + * + * Error Char + * B0..7 Error Character + * B8 Error Character Processing + * 0 = disabled + * 1 = enabled + * B9..15 Reserved + * FTDI_SIO_SET_ERROR_CHAR + * Set the parity error replacement character for the specified communications + * port. + */ + +/* + * BmRequestType: 1100 0000b + * bRequest: FTDI_SIO_GET_MODEM_STATUS + * wValue: zero + * wIndex: Port + * wLength: 1 + * Data: Status + * + * One byte of data is returned + * B0..3 0 + * B4 CTS + * 0 = inactive + * 1 = active + * B5 DSR + * 0 = inactive + * 1 = active + * B6 Ring Indicator (RI) + * 0 = inactive + * 1 = active + * B7 Receive Line Signal Detect (RLSD) + * 0 = inactive + * 1 = active + * + * FTDI_SIO_GET_MODEM_STATUS + * Retrieve the current value of the modem status register. + */ +#define FTDI_SIO_CTS_MASK 0x10 +#define FTDI_SIO_DSR_MASK 0x20 +#define FTDI_SIO_RI_MASK 0x40 +#define FTDI_SIO_RLSD_MASK 0x80 + +/* + * DATA FORMAT + * + * IN Endpoint + * + * The device reserves the first two bytes of data on this endpoint to contain + * the current values of the modem and line status registers. In the absence of + * data, the device generates a message consisting of these two status bytes + * every 40 ms. + * + * Byte 0: Modem Status + * NOTE: 4 upper bits have same layout as the MSR register in a 16550 + * + * Offset Description + * B0..3 Port + * B4 Clear to Send (CTS) + * B5 Data Set Ready (DSR) + * B6 Ring Indicator (RI) + * B7 Receive Line Signal Detect (RLSD) + * + * Byte 1: Line Status + * NOTE: same layout as the LSR register in a 16550 + * + * Offset Description + * B0 Data Ready (DR) + * B1 Overrun Error (OE) + * B2 Parity Error (PE) + * B3 Framing Error (FE) + * B4 Break Interrupt (BI) + * B5 Transmitter Holding Register (THRE) + * B6 Transmitter Empty (TEMT) + * B7 Error in RCVR FIFO + * OUT Endpoint + * + * This device reserves the first bytes of data on this endpoint contain the + * length and port identifier of the message. For the FTDI USB Serial converter + * the port identifier is always 1. + * + * Byte 0: Port & length + * + * Offset Description + * B0..1 Port + * B2..7 Length of message - (not including Byte 0) + */ +#define FTDI_PORT_MASK 0x0f +#define FTDI_MSR_MASK 0xf0 +#define FTDI_GET_MSR(p) (((p)[0]) & FTDI_MSR_MASK) +#define FTDI_GET_LSR(p) ((p)[1]) +#define FTDI_LSR_MASK (~0x60) /* interesting bits */ +#define FTDI_OUT_TAG(len, port) (((len) << 2) | (port)) diff --git a/freebsd/sys/dev/usb/serial/ugensa.c b/freebsd/sys/dev/usb/serial/ugensa.c new file mode 100644 index 00000000..3e6680dc --- /dev/null +++ b/freebsd/sys/dev/usb/serial/ugensa.c @@ -0,0 +1,403 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $FreeBSD$ */ +/* $NetBSD: ugensa.c,v 1.9.2.1 2007/03/24 14:55:50 yamt Exp $ */ + +/* + * Copyright (c) 2004, 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Roland C. Dowdeswell <elric@netbsd.org>. + * + * 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 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. + */ + +/* + * NOTE: all function names beginning like "ugensa_cfg_" can only + * be called from within the config thread function ! + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR usb_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#define UGENSA_BUF_SIZE 2048 /* bytes */ +#define UGENSA_CONFIG_INDEX 0 +#define UGENSA_IFACE_INDEX 0 +#define UGENSA_IFACE_MAX 8 /* exclusivly */ + +enum { + UGENSA_BULK_DT_WR, + UGENSA_BULK_DT_RD, + UGENSA_N_TRANSFER, +}; + +struct ugensa_sub_softc { + struct ucom_softc *sc_ucom_ptr; + struct usb_xfer *sc_xfer[UGENSA_N_TRANSFER]; +}; + +struct ugensa_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom[UGENSA_IFACE_MAX]; + struct ugensa_sub_softc sc_sub[UGENSA_IFACE_MAX]; + + struct mtx sc_mtx; + uint8_t sc_niface; +}; + +/* prototypes */ + +static device_probe_t ugensa_probe; +static device_attach_t ugensa_attach; +static device_detach_t ugensa_detach; +static void ugensa_free_softc(struct ugensa_softc *); + +static usb_callback_t ugensa_bulk_write_callback; +static usb_callback_t ugensa_bulk_read_callback; + +static void ugensa_free(struct ucom_softc *); +static void ugensa_start_read(struct ucom_softc *); +static void ugensa_stop_read(struct ucom_softc *); +static void ugensa_start_write(struct ucom_softc *); +static void ugensa_stop_write(struct ucom_softc *); +static void ugensa_poll(struct ucom_softc *ucom); + +static const struct usb_config ugensa_xfer_config[UGENSA_N_TRANSFER] = { + + [UGENSA_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UGENSA_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &ugensa_bulk_write_callback, + }, + + [UGENSA_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UGENSA_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &ugensa_bulk_read_callback, + }, +}; + +static const struct ucom_callback ugensa_callback = { + .ucom_start_read = &ugensa_start_read, + .ucom_stop_read = &ugensa_stop_read, + .ucom_start_write = &ugensa_start_write, + .ucom_stop_write = &ugensa_stop_write, + .ucom_poll = &ugensa_poll, + .ucom_free = &ugensa_free, +}; + +static device_method_t ugensa_methods[] = { + /* Device methods */ + DEVMETHOD(device_probe, ugensa_probe), + DEVMETHOD(device_attach, ugensa_attach), + DEVMETHOD(device_detach, ugensa_detach), + DEVMETHOD_END +}; + +static devclass_t ugensa_devclass; + +static driver_t ugensa_driver = { + .name = "ugensa", + .methods = ugensa_methods, + .size = sizeof(struct ugensa_softc), +}; + +static const STRUCT_USB_HOST_ID ugensa_devs[] = { + {USB_VPI(USB_VENDOR_AIRPRIME, USB_PRODUCT_AIRPRIME_PC5220, 0)}, + {USB_VPI(USB_VENDOR_CMOTECH, USB_PRODUCT_CMOTECH_CDMA_MODEM1, 0)}, + {USB_VPI(USB_VENDOR_KYOCERA2, USB_PRODUCT_KYOCERA2_CDMA_MSM_K, 0)}, + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_49GPLUS, 0)}, + {USB_VPI(USB_VENDOR_NOVATEL2, USB_PRODUCT_NOVATEL2_FLEXPACKGPS, 0)}, +}; + +DRIVER_MODULE(ugensa, uhub, ugensa_driver, ugensa_devclass, NULL, 0); +MODULE_DEPEND(ugensa, ucom, 1, 1, 1); +MODULE_DEPEND(ugensa, usb, 1, 1, 1); +MODULE_VERSION(ugensa, 1); +USB_PNP_HOST_INFO(ugensa_devs); + +static int +ugensa_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UGENSA_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != 0) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(ugensa_devs, sizeof(ugensa_devs), uaa)); +} + +static int +ugensa_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ugensa_softc *sc = device_get_softc(dev); + struct ugensa_sub_softc *ssc; + struct usb_interface *iface; + int32_t error; + uint8_t iface_index; + int x, cnt; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "ugensa", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + /* Figure out how many interfaces this device has got */ + for (cnt = 0; cnt < UGENSA_IFACE_MAX; cnt++) { + if ((usbd_get_endpoint(uaa->device, cnt, ugensa_xfer_config + 0) == NULL) || + (usbd_get_endpoint(uaa->device, cnt, ugensa_xfer_config + 1) == NULL)) { + /* we have reached the end */ + break; + } + } + + if (cnt == 0) { + device_printf(dev, "No interfaces\n"); + goto detach; + } + for (x = 0; x < cnt; x++) { + iface = usbd_get_iface(uaa->device, x); + if (iface->idesc->bInterfaceClass != UICLASS_VENDOR) + /* Not a serial port, most likely a SD reader */ + continue; + + ssc = sc->sc_sub + sc->sc_niface; + ssc->sc_ucom_ptr = sc->sc_ucom + sc->sc_niface; + + iface_index = (UGENSA_IFACE_INDEX + x); + error = usbd_transfer_setup(uaa->device, + &iface_index, ssc->sc_xfer, ugensa_xfer_config, + UGENSA_N_TRANSFER, ssc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating USB " + "transfers failed\n"); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(ssc->sc_xfer[UGENSA_BULK_DT_WR]); + usbd_xfer_set_stall(ssc->sc_xfer[UGENSA_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + /* initialize port number */ + ssc->sc_ucom_ptr->sc_portno = sc->sc_niface; + sc->sc_niface++; + if (x != uaa->info.bIfaceIndex) + usbd_set_parent_iface(uaa->device, x, + uaa->info.bIfaceIndex); + } + device_printf(dev, "Found %d interfaces.\n", sc->sc_niface); + + error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_niface, sc, + &ugensa_callback, &sc->sc_mtx); + if (error) { + DPRINTF("attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); /* success */ + +detach: + ugensa_detach(dev); + return (ENXIO); /* failure */ +} + +static int +ugensa_detach(device_t dev) +{ + struct ugensa_softc *sc = device_get_softc(dev); + uint8_t x; + + ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); + + for (x = 0; x < sc->sc_niface; x++) { + usbd_transfer_unsetup(sc->sc_sub[x].sc_xfer, UGENSA_N_TRANSFER); + } + + device_claim_softc(dev); + + ugensa_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(ugensa); + +static void +ugensa_free_softc(struct ugensa_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +ugensa_free(struct ucom_softc *ucom) +{ + ugensa_free_softc(ucom->sc_parent); +} + +static void +ugensa_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ugensa_sub_softc *ssc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(ssc->sc_ucom_ptr, pc, 0, + UGENSA_BUF_SIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +ugensa_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ugensa_sub_softc *ssc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(ssc->sc_ucom_ptr, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +ugensa_start_read(struct ucom_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usbd_transfer_start(ssc->sc_xfer[UGENSA_BULK_DT_RD]); +} + +static void +ugensa_stop_read(struct ucom_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usbd_transfer_stop(ssc->sc_xfer[UGENSA_BULK_DT_RD]); +} + +static void +ugensa_start_write(struct ucom_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usbd_transfer_start(ssc->sc_xfer[UGENSA_BULK_DT_WR]); +} + +static void +ugensa_stop_write(struct ucom_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usbd_transfer_stop(ssc->sc_xfer[UGENSA_BULK_DT_WR]); +} + +static void +ugensa_poll(struct ucom_softc *ucom) +{ + struct ugensa_softc *sc = ucom->sc_parent; + struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno; + + usbd_transfer_poll(ssc->sc_xfer, UGENSA_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/uipaq.c b/freebsd/sys/dev/usb/serial/uipaq.c new file mode 100644 index 00000000..e4eb0dab --- /dev/null +++ b/freebsd/sys/dev/usb/serial/uipaq.c @@ -0,0 +1,1377 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $NetBSD: uipaq.c,v 1.4 2006/11/16 01:33:27 christos Exp $ */ +/* $OpenBSD: uipaq.c,v 1.1 2005/06/17 23:50:33 deraadt Exp $ */ + +/* + * Copyright (c) 2000-2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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 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. + */ + +/* + * iPAQ driver + * + * 19 July 2003: Incorporated changes suggested by Sam Lawrance from + * the uppc module + * + * + * Contact isis@cs.umd.edu if you have any questions/comments about this driver + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_cdc.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR usb_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#define UIPAQ_CONFIG_INDEX 0 /* config number 1 */ +#define UIPAQ_IFACE_INDEX 0 + +#define UIPAQ_BUF_SIZE 1024 + +enum { + UIPAQ_BULK_DT_WR, + UIPAQ_BULK_DT_RD, + UIPAQ_N_TRANSFER, +}; + +struct uipaq_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UIPAQ_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_line; + + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* modem status register */ +}; + +static device_probe_t uipaq_probe; +static device_attach_t uipaq_attach; +static device_detach_t uipaq_detach; +static void uipaq_free_softc(struct uipaq_softc *); + +static usb_callback_t uipaq_write_callback; +static usb_callback_t uipaq_read_callback; + +static void uipaq_free(struct ucom_softc *); +static void uipaq_start_read(struct ucom_softc *); +static void uipaq_stop_read(struct ucom_softc *); +static void uipaq_start_write(struct ucom_softc *); +static void uipaq_stop_write(struct ucom_softc *); +static void uipaq_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uipaq_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uipaq_cfg_set_break(struct ucom_softc *, uint8_t); +static void uipaq_poll(struct ucom_softc *ucom); + +static const struct usb_config uipaq_config_data[UIPAQ_N_TRANSFER] = { + + [UIPAQ_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UIPAQ_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uipaq_write_callback, + }, + + [UIPAQ_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UIPAQ_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uipaq_read_callback, + }, +}; + +static const struct ucom_callback uipaq_callback = { + .ucom_cfg_set_dtr = &uipaq_cfg_set_dtr, + .ucom_cfg_set_rts = &uipaq_cfg_set_rts, + .ucom_cfg_set_break = &uipaq_cfg_set_break, + .ucom_start_read = &uipaq_start_read, + .ucom_stop_read = &uipaq_stop_read, + .ucom_start_write = &uipaq_start_write, + .ucom_stop_write = &uipaq_stop_write, + .ucom_poll = &uipaq_poll, + .ucom_free = &uipaq_free, +}; + +/* + * Much of this list is generated from lists of other drivers that + * support the same hardware. Numeric values are used where no usbdevs + * entries exist. + */ +static const STRUCT_USB_HOST_ID uipaq_devs[] = { + /* Socket USB Sync */ + {USB_VPI(0x0104, 0x00be, 0)}, + /* USB Sync 0301 */ + {USB_VPI(0x04ad, 0x0301, 0)}, + /* USB Sync 0302 */ + {USB_VPI(0x04ad, 0x0302, 0)}, + /* USB Sync 0303 */ + {USB_VPI(0x04ad, 0x0303, 0)}, + /* GPS Pocket PC USB Sync */ + {USB_VPI(0x04ad, 0x0306, 0)}, + /* HHP PDT */ + {USB_VPI(0x0536, 0x01a0, 0)}, + /* Intermec Mobile Computer */ + {USB_VPI(0x067e, 0x1001, 0)}, + /* Linkup Systems USB Sync */ + {USB_VPI(0x094b, 0x0001, 0)}, + /* BCOM USB Sync 0065 */ + {USB_VPI(0x0960, 0x0065, 0)}, + /* BCOM USB Sync 0066 */ + {USB_VPI(0x0960, 0x0066, 0)}, + /* BCOM USB Sync 0067 */ + {USB_VPI(0x0960, 0x0067, 0)}, + /* Portatec USB Sync */ + {USB_VPI(0x0961, 0x0010, 0)}, + /* Trimble GeoExplorer */ + {USB_VPI(0x099e, 0x0052, 0)}, + /* TDS Data Collector */ + {USB_VPI(0x099e, 0x4000, 0)}, + /* Motorola iDEN Smartphone */ + {USB_VPI(0x0c44, 0x03a2, 0)}, + /* Cesscom Luxian Series */ + {USB_VPI(0x0c8e, 0x6000, 0)}, + /* Motorola PowerPad Pocket PCDevice */ + {USB_VPI(0x0cad, 0x9001, 0)}, + /* Freedom Scientific USB Sync */ + {USB_VPI(0x0f4e, 0x0200, 0)}, + /* Cyberbank USB Sync */ + {USB_VPI(0x0f98, 0x0201, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x3001, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x3002, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x3003, 0)}, + /* Wistron USB Sync */ + {USB_VPI(0x0fb8, 0x4001, 0)}, + /* E-TEN USB Sync */ + {USB_VPI(0x1066, 0x00ce, 0)}, + /* E-TEN P3XX Pocket PC */ + {USB_VPI(0x1066, 0x0300, 0)}, + /* E-TEN P5XX Pocket PC */ + {USB_VPI(0x1066, 0x0500, 0)}, + /* E-TEN P6XX Pocket PC */ + {USB_VPI(0x1066, 0x0600, 0)}, + /* E-TEN P7XX Pocket PC */ + {USB_VPI(0x1066, 0x0700, 0)}, + /* Psion Teklogix Sync 753x */ + {USB_VPI(0x1114, 0x0001, 0)}, + /* Psion Teklogix Sync netBookPro */ + {USB_VPI(0x1114, 0x0004, 0)}, + /* Psion Teklogix Sync 7525 */ + {USB_VPI(0x1114, 0x0006, 0)}, + /* VES USB Sync */ + {USB_VPI(0x1182, 0x1388, 0)}, + /* Rugged Pocket PC 2003 */ + {USB_VPI(0x11d9, 0x1002, 0)}, + /* Rugged Pocket PC 2003 */ + {USB_VPI(0x11d9, 0x1003, 0)}, + /* USB Sync 03 */ + {USB_VPI(0x1231, 0xce01, 0)}, + /* USB Sync 03 */ + {USB_VPI(0x1231, 0xce02, 0)}, + /* Mio DigiWalker PPC StrongARM */ + {USB_VPI(0x3340, 0x011c, 0)}, + /* Mio DigiWalker 338 */ + {USB_VPI(0x3340, 0x0326, 0)}, + /* Mio DigiWalker 338 */ + {USB_VPI(0x3340, 0x0426, 0)}, + /* Mio DigiWalker USB Sync */ + {USB_VPI(0x3340, 0x043a, 0)}, + /* MiTAC USB Sync 528 */ + {USB_VPI(0x3340, 0x051c, 0)}, + /* Mio DigiWalker SmartPhone USB Sync */ + {USB_VPI(0x3340, 0x053a, 0)}, + /* MiTAC USB Sync */ + {USB_VPI(0x3340, 0x071c, 0)}, + /* Generic PPC StrongARM */ + {USB_VPI(0x3340, 0x0b1c, 0)}, + /* Generic PPC USB Sync */ + {USB_VPI(0x3340, 0x0e3a, 0)}, + /* Itautec USB Sync */ + {USB_VPI(0x3340, 0x0f1c, 0)}, + /* Generic SmartPhone USB Sync */ + {USB_VPI(0x3340, 0x0f3a, 0)}, + /* Itautec USB Sync */ + {USB_VPI(0x3340, 0x1326, 0)}, + /* YAKUMO USB Sync */ + {USB_VPI(0x3340, 0x191c, 0)}, + /* Vobis USB Sync */ + {USB_VPI(0x3340, 0x2326, 0)}, + /* MEDION Winodws Moble USB Sync */ + {USB_VPI(0x3340, 0x3326, 0)}, + /* Legend USB Sync */ + {USB_VPI(0x3708, 0x20ce, 0)}, + /* Lenovo USB Sync */ + {USB_VPI(0x3708, 0x21ce, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0210, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0211, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0400, 0)}, + /* Mobile Media Technology USB Sync */ + {USB_VPI(0x4113, 0x0410, 0)}, + /* Smartphone */ + {USB_VPI(0x4505, 0x0010, 0)}, + /* SAGEM Wireless Assistant */ + {USB_VPI(0x5e04, 0xce00, 0)}, + /* c10 Series */ + {USB_VPI(USB_VENDOR_ACER, 0x1631, 0)}, + /* c20 Series */ + {USB_VPI(USB_VENDOR_ACER, 0x1632, 0)}, + /* Acer n10 Handheld USB Sync */ + {USB_VPI(USB_VENDOR_ACER, 0x16e1, 0)}, + /* Acer n20 Handheld USB Sync */ + {USB_VPI(USB_VENDOR_ACER, 0x16e2, 0)}, + /* Acer n30 Handheld USB Sync */ + {USB_VPI(USB_VENDOR_ACER, 0x16e3, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x4200, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x4201, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x4202, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x9200, 0)}, + /* ASUS USB Sync */ + {USB_VPI(USB_VENDOR_ASUS, 0x9202, 0)}, + /**/ + {USB_VPI(USB_VENDOR_ASUS, USB_PRODUCT_ASUS_P535, 0)}, + /* CASIO USB Sync 2001 */ + {USB_VPI(USB_VENDOR_CASIO, 0x2001, 0)}, + /* CASIO USB Sync 2003 */ + {USB_VPI(USB_VENDOR_CASIO, 0x2003, 0)}, + /**/ + {USB_VPI(USB_VENDOR_CASIO, USB_PRODUCT_CASIO_BE300, 0)}, + /* MyGuide 7000 XL USB Sync */ + {USB_VPI(USB_VENDOR_COMPAL, 0x0531, 0)}, + /* Compaq iPAQ USB Sync */ + {USB_VPI(USB_VENDOR_COMPAQ, 0x0032, 0)}, + /**/ + {USB_VPI(USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQPOCKETPC, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4001, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4002, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4003, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4004, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4005, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4006, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4007, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4008, 0)}, + /* Dell Axim USB Sync */ + {USB_VPI(USB_VENDOR_DELL, 0x4009, 0)}, + /* Fujitsu Siemens Computers USB Sync */ + {USB_VPI(USB_VENDOR_FSC, 0x1001, 0)}, + /* FUJITSU USB Sync */ + {USB_VPI(USB_VENDOR_FUJITSU, 0x1058, 0)}, + /* FUJITSU USB Sync */ + {USB_VPI(USB_VENDOR_FUJITSU, 0x1079, 0)}, + /* Askey USB Sync */ + {USB_VPI(USB_VENDOR_GIGASET, 0x0601, 0)}, + /* Hitachi USB Sync */ + {USB_VPI(USB_VENDOR_HITACHI, 0x0014, 0)}, + /* HP USB Sync 1612 */ + {USB_VPI(USB_VENDOR_HP, 0x1216, 0)}, + /* HP USB Sync 1620 */ + {USB_VPI(USB_VENDOR_HP, 0x2016, 0)}, + /* HP USB Sync 1621 */ + {USB_VPI(USB_VENDOR_HP, 0x2116, 0)}, + /* HP USB Sync 1622 */ + {USB_VPI(USB_VENDOR_HP, 0x2216, 0)}, + /* HP USB Sync 1630 */ + {USB_VPI(USB_VENDOR_HP, 0x3016, 0)}, + /* HP USB Sync 1631 */ + {USB_VPI(USB_VENDOR_HP, 0x3116, 0)}, + /* HP USB Sync 1632 */ + {USB_VPI(USB_VENDOR_HP, 0x3216, 0)}, + /* HP USB Sync 1640 */ + {USB_VPI(USB_VENDOR_HP, 0x4016, 0)}, + /* HP USB Sync 1641 */ + {USB_VPI(USB_VENDOR_HP, 0x4116, 0)}, + /* HP USB Sync 1642 */ + {USB_VPI(USB_VENDOR_HP, 0x4216, 0)}, + /* HP USB Sync 1650 */ + {USB_VPI(USB_VENDOR_HP, 0x5016, 0)}, + /* HP USB Sync 1651 */ + {USB_VPI(USB_VENDOR_HP, 0x5116, 0)}, + /* HP USB Sync 1652 */ + {USB_VPI(USB_VENDOR_HP, 0x5216, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_2215, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_568J, 0)}, + /* HTC USB Modem */ + {USB_VPI(USB_VENDOR_HTC, 0x00cf, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a01, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a02, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a03, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a04, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a05, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a06, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a07, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a08, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a09, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a0f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a10, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a11, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a12, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a13, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a14, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a15, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a16, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a17, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a18, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a19, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a1f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a20, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a21, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a22, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a23, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a24, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a25, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a26, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a27, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a28, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a29, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a2f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a30, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a31, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a32, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a33, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a34, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a35, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a36, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a37, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a38, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a39, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a3f, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a40, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a41, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a42, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a43, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a44, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a45, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a46, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a47, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a48, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a49, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4a, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4b, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4c, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4d, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4e, 0)}, + /* PocketPC USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a4f, 0)}, + /* HTC SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a50, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a52, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a53, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a54, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a55, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a56, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a57, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a58, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a59, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a5f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a60, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a61, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a62, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a63, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a64, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a65, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a66, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a67, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a68, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a69, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a6f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a70, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a71, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a72, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a73, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a74, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a75, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a76, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a77, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a78, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a79, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a7f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a80, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a81, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a82, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a83, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a84, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a85, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a86, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a87, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a88, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a89, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a8f, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a90, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a91, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a92, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a93, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a94, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a95, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a96, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a97, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a98, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a99, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9a, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9b, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9c, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9d, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9e, 0)}, + /* SmartPhone USB Sync */ + {USB_VPI(USB_VENDOR_HTC, 0x0a9f, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_PPC6700MODEM, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_SMARTPHONE, 0)}, + /**/ + {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_WINMOBILE, 0)}, + /* High Tech Computer Wizard Smartphone */ + {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_WIZARD, 0)}, + /* JVC USB Sync */ + {USB_VPI(USB_VENDOR_JVC, 0x3011, 0)}, + /* JVC USB Sync */ + {USB_VPI(USB_VENDOR_JVC, 0x3012, 0)}, + /* LGE USB Sync */ + {USB_VPI(USB_VENDOR_LG, 0x9c01, 0)}, + /* Microsoft USB Sync */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x00ce, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0400, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0401, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0402, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0403, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0404, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0405, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0406, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0407, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0408, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0409, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040a, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040b, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040c, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040d, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040e, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x040f, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0410, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0411, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0412, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0413, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0414, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0415, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0416, 0)}, + /* Windows Pocket PC 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0417, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0432, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0433, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0434, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0435, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0436, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0437, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0438, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0439, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x043f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0440, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0441, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0442, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0443, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0444, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0445, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0446, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0447, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0448, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0449, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x044f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0450, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0451, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0452, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0453, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0454, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0455, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0456, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0457, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0458, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0459, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x045f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0460, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0461, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0462, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0463, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0464, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0465, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0466, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0467, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0468, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0469, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046b, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046c, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046d, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046e, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x046f, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0470, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0471, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0472, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0473, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0474, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0475, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0476, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0477, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0478, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x0479, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x047a, 0)}, + /* Windows Pocket PC 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x047b, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04c8, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04c9, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ca, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cb, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cc, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cd, 0)}, + /* Windows Smartphone 2002 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ce, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d7, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d8, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d9, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04da, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04db, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04dc, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04dd, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04de, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04df, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e0, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e1, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e2, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e3, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e4, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e5, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e6, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e7, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e8, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e9, 0)}, + /* Windows Smartphone 2003 */ + {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ea, 0)}, + /* Motorola MPx200 Smartphone */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4204, 0)}, + /* Motorola MPc GSM */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4214, 0)}, + /* Motorola MPx220 Smartphone */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4224, 0)}, + /* Motorola MPc CDMA */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4234, 0)}, + /* Motorola MPx100 Smartphone */ + {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4244, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x00d5, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x00d6, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x00d7, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x8024, 0)}, + /* NEC USB Sync */ + {USB_VPI(USB_VENDOR_NEC, 0x8025, 0)}, + /* Panasonic USB Sync */ + {USB_VPI(USB_VENDOR_PANASONIC, 0x2500, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f00, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f01, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f02, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f03, 0)}, + /* Samsung NEXiO USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f04, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6611, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6613, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6615, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6617, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6619, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x661b, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x662e, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6630, 0)}, + /* Samsung MITs USB Sync */ + {USB_VPI(USB_VENDOR_SAMSUNG, 0x6632, 0)}, + /* SHARP WS003SH USB Modem */ + {USB_VPI(USB_VENDOR_SHARP, 0x9102, 0)}, + /* SHARP WS004SH USB Modem */ + {USB_VPI(USB_VENDOR_SHARP, 0x9121, 0)}, + /* SHARP S01SH USB Modem */ + {USB_VPI(USB_VENDOR_SHARP, 0x9151, 0)}, + /**/ + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WZERO3ES, 0)}, + /**/ + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WZERO3ADES, 0)}, + /**/ + {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WILLCOM03, 0)}, + /* Symbol USB Sync */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2000, 0)}, + /* Symbol USB Sync 0x2001 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2001, 0)}, + /* Symbol USB Sync 0x2002 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2002, 0)}, + /* Symbol USB Sync 0x2003 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2003, 0)}, + /* Symbol USB Sync 0x2004 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2004, 0)}, + /* Symbol USB Sync 0x2005 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2005, 0)}, + /* Symbol USB Sync 0x2006 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2006, 0)}, + /* Symbol USB Sync 0x2007 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2007, 0)}, + /* Symbol USB Sync 0x2008 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2008, 0)}, + /* Symbol USB Sync 0x2009 */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x2009, 0)}, + /* Symbol USB Sync 0x200a */ + {USB_VPI(USB_VENDOR_SYMBOL, 0x200a, 0)}, + /* TOSHIBA USB Sync 0700 */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0700, 0)}, + /* TOSHIBA Pocket PC e310 */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0705, 0)}, + /* TOSHIBA Pocket PC e330 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0707, 0)}, + /* TOSHIBA Pocket PC e350Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0708, 0)}, + /* TOSHIBA Pocket PC e750 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x0709, 0)}, + /* TOSHIBA Pocket PC e400 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x070a, 0)}, + /* TOSHIBA Pocket PC e800 Series */ + {USB_VPI(USB_VENDOR_TOSHIBA, 0x070b, 0)}, + /* TOSHIBA Pocket PC e740 */ + {USB_VPI(USB_VENDOR_TOSHIBA, USB_PRODUCT_TOSHIBA_POCKETPC_E740, 0)}, + /* ViewSonic Color Pocket PC V35 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x0ed9, 0)}, + /* ViewSonic Color Pocket PC V36 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1527, 0)}, + /* ViewSonic Color Pocket PC V37 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1529, 0)}, + /* ViewSonic Color Pocket PC V38 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x152b, 0)}, + /* ViewSonic Pocket PC */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x152e, 0)}, + /* ViewSonic Communicator Pocket PC */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1921, 0)}, + /* ViewSonic Smartphone */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1922, 0)}, + /* ViewSonic Pocket PC V30 */ + {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1923, 0)}, +}; + +static device_method_t uipaq_methods[] = { + DEVMETHOD(device_probe, uipaq_probe), + DEVMETHOD(device_attach, uipaq_attach), + DEVMETHOD(device_detach, uipaq_detach), + DEVMETHOD_END +}; + +static devclass_t uipaq_devclass; + +static driver_t uipaq_driver = { + .name = "uipaq", + .methods = uipaq_methods, + .size = sizeof(struct uipaq_softc), +}; + +DRIVER_MODULE(uipaq, uhub, uipaq_driver, uipaq_devclass, NULL, 0); +MODULE_DEPEND(uipaq, ucom, 1, 1, 1); +MODULE_DEPEND(uipaq, usb, 1, 1, 1); +MODULE_VERSION(uipaq, 1); +USB_PNP_HOST_INFO(uipaq_devs); + +static int +uipaq_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UIPAQ_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UIPAQ_IFACE_INDEX) { + return (ENXIO); + } + if (uaa->info.bInterfaceClass == UICLASS_IAD) { + DPRINTF("IAD detected - not UIPAQ serial device\n"); + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uipaq_devs, sizeof(uipaq_devs), uaa)); +} + +static int +uipaq_attach(device_t dev) +{ + struct usb_device_request req; + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uipaq_softc *sc = device_get_softc(dev); + int error; + uint8_t iface_index; + uint8_t i; + + sc->sc_udev = uaa->device; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uipaq", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + /* + * Send magic bytes, cribbed from Linux ipaq driver that + * claims to have sniffed them from Win98. Wait for driver to + * become ready on device side? + */ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, UCDC_LINE_DTR); + USETW(req.wIndex, 0x0); + USETW(req.wLength, 0); + for (i = 0; i != 64; i++) { + error = + usbd_do_request_flags(uaa->device, NULL, &req, + NULL, 0, NULL, 100); + if (error == 0) + break; + usb_pause_mtx(NULL, hz / 10); + } + + iface_index = UIPAQ_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, uipaq_config_data, + UIPAQ_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UIPAQ_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UIPAQ_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uipaq_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + uipaq_detach(dev); + return (ENXIO); +} + +int +uipaq_detach(device_t dev) +{ + struct uipaq_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UIPAQ_N_TRANSFER); + + device_claim_softc(dev); + + uipaq_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(uipaq); + +static void +uipaq_free_softc(struct uipaq_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +uipaq_free(struct ucom_softc *ucom) +{ + uipaq_free_softc(ucom->sc_parent); +} + +static void +uipaq_start_read(struct ucom_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UIPAQ_BULK_DT_RD]); +} + +static void +uipaq_stop_read(struct ucom_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UIPAQ_BULK_DT_RD]); +} + +static void +uipaq_start_write(struct ucom_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UIPAQ_BULK_DT_WR]); +} + +static void +uipaq_stop_write(struct ucom_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UIPAQ_BULK_DT_WR]); +} + +static void +uipaq_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uipaq_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = UIPAQ_IFACE_INDEX; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uipaq_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uipaq_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = UIPAQ_IFACE_INDEX; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uipaq_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uipaq_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t temp; + + temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, temp); + req.wIndex[0] = UIPAQ_IFACE_INDEX; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uipaq_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uipaq_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UIPAQ_BUF_SIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uipaq_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uipaq_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uipaq_poll(struct ucom_softc *ucom) +{ + struct uipaq_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UIPAQ_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/ulpt.c b/freebsd/sys/dev/usb/serial/ulpt.c new file mode 100644 index 00000000..77600bf7 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/ulpt.c @@ -0,0 +1,764 @@ +#include <machine/rtems-bsd-kernel-space.h> + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* $NetBSD: ulpt.c,v 1.60 2003/10/04 21:19:50 augustss Exp $ */ + +/*- + * Copyright (c) 1998, 2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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 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. + */ + +/* + * Printer Class spec: http://www.usb.org/developers/data/devclass/usbprint109.PDF + * Printer Class spec: http://www.usb.org/developers/devclass_docs/usbprint11.pdf + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> +#include <sys/syslog.h> +#include <sys/selinfo.h> +#include <sys/conf.h> +#include <sys/fcntl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbhid.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR ulpt_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#ifdef USB_DEBUG +static int ulpt_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ulpt, CTLFLAG_RW, 0, "USB ulpt"); +SYSCTL_INT(_hw_usb_ulpt, OID_AUTO, debug, CTLFLAG_RWTUN, + &ulpt_debug, 0, "Debug level"); +#endif + +#define ULPT_BSIZE (1<<15) /* bytes */ +#define ULPT_IFQ_MAXLEN 2 /* units */ + +#define UR_GET_DEVICE_ID 0x00 +#define UR_GET_PORT_STATUS 0x01 +#define UR_SOFT_RESET 0x02 + +#define LPS_NERR 0x08 /* printer no error */ +#define LPS_SELECT 0x10 /* printer selected */ +#define LPS_NOPAPER 0x20 /* printer out of paper */ +#define LPS_INVERT (LPS_SELECT|LPS_NERR) +#define LPS_MASK (LPS_SELECT|LPS_NERR|LPS_NOPAPER) + +enum { + ULPT_BULK_DT_WR, + ULPT_BULK_DT_RD, + ULPT_INTR_DT_RD, + ULPT_N_TRANSFER, +}; + +struct ulpt_softc { + struct usb_fifo_sc sc_fifo; + struct usb_fifo_sc sc_fifo_noreset; + struct mtx sc_mtx; + struct usb_callout sc_watchdog; + + device_t sc_dev; + struct usb_device *sc_udev; + struct usb_fifo *sc_fifo_open[2]; + struct usb_xfer *sc_xfer[ULPT_N_TRANSFER]; + + int sc_fflags; /* current open flags, FREAD and + * FWRITE */ + uint8_t sc_iface_no; + uint8_t sc_last_status; + uint8_t sc_zlps; /* number of consequtive zero length + * packets received */ +}; + +/* prototypes */ + +static device_probe_t ulpt_probe; +static device_attach_t ulpt_attach; +static device_detach_t ulpt_detach; + +static usb_callback_t ulpt_write_callback; +static usb_callback_t ulpt_read_callback; +static usb_callback_t ulpt_status_callback; + +static void ulpt_reset(struct ulpt_softc *); +static void ulpt_watchdog(void *); + +static usb_fifo_close_t ulpt_close; +static usb_fifo_cmd_t ulpt_start_read; +static usb_fifo_cmd_t ulpt_start_write; +static usb_fifo_cmd_t ulpt_stop_read; +static usb_fifo_cmd_t ulpt_stop_write; +static usb_fifo_ioctl_t ulpt_ioctl; +static usb_fifo_open_t ulpt_open; +static usb_fifo_open_t unlpt_open; + +static struct usb_fifo_methods ulpt_fifo_methods = { + .f_close = &ulpt_close, + .f_ioctl = &ulpt_ioctl, + .f_open = &ulpt_open, + .f_start_read = &ulpt_start_read, + .f_start_write = &ulpt_start_write, + .f_stop_read = &ulpt_stop_read, + .f_stop_write = &ulpt_stop_write, + .basename[0] = "ulpt", +}; + +static struct usb_fifo_methods unlpt_fifo_methods = { + .f_close = &ulpt_close, + .f_ioctl = &ulpt_ioctl, + .f_open = &unlpt_open, + .f_start_read = &ulpt_start_read, + .f_start_write = &ulpt_start_write, + .f_stop_read = &ulpt_stop_read, + .f_stop_write = &ulpt_stop_write, + .basename[0] = "unlpt", +}; + +static void +ulpt_reset(struct ulpt_softc *sc) +{ + struct usb_device_request req; + + DPRINTFN(2, "\n"); + + req.bRequest = UR_SOFT_RESET; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, 0); + + /* + * There was a mistake in the USB printer 1.0 spec that gave the + * request type as UT_WRITE_CLASS_OTHER; it should have been + * UT_WRITE_CLASS_INTERFACE. Many printers use the old one, + * so we try both. + */ + + mtx_lock(&sc->sc_mtx); + req.bmRequestType = UT_WRITE_CLASS_OTHER; + if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, + &req, NULL, 0, NULL, 2 * USB_MS_HZ)) { /* 1.0 */ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, + &req, NULL, 0, NULL, 2 * USB_MS_HZ)) { /* 1.1 */ + /* ignore error */ + } + } + mtx_unlock(&sc->sc_mtx); +} + +static void +ulpt_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ulpt_softc *sc = usbd_xfer_softc(xfer); + struct usb_fifo *f = sc->sc_fifo_open[USB_FIFO_TX]; + struct usb_page_cache *pc; + int actlen, max; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + if (f == NULL) { + /* should not happen */ + DPRINTF("no FIFO\n"); + return; + } + DPRINTF("state=0x%x actlen=%d\n", USB_GET_STATE(xfer), actlen); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + max = usbd_xfer_max_len(xfer); + if (usb_fifo_get_data(f, pc, 0, max, &actlen, 0)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +ulpt_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ulpt_softc *sc = usbd_xfer_softc(xfer); + struct usb_fifo *f = sc->sc_fifo_open[USB_FIFO_RX]; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + if (f == NULL) { + /* should not happen */ + DPRINTF("no FIFO\n"); + return; + } + DPRINTF("state=0x%x\n", USB_GET_STATE(xfer)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen == 0) { + + if (sc->sc_zlps == 4) { + /* enable BULK throttle */ + usbd_xfer_set_interval(xfer, 500); /* ms */ + } else { + sc->sc_zlps++; + } + } else { + /* disable BULK throttle */ + + usbd_xfer_set_interval(xfer, 0); + sc->sc_zlps = 0; + } + + pc = usbd_xfer_get_frame(xfer, 0); + usb_fifo_put_data(f, pc, 0, actlen, 1); + + case USB_ST_SETUP: +tr_setup: + if (usb_fifo_put_bytes_max(f) != 0) { + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + /* disable BULK throttle */ + usbd_xfer_set_interval(xfer, 0); + sc->sc_zlps = 0; + + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +ulpt_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct ulpt_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc; + uint8_t cur_status; + uint8_t new_status; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + pc = usbd_xfer_get_frame(xfer, 1); + usbd_copy_out(pc, 0, &cur_status, 1); + + cur_status = (cur_status ^ LPS_INVERT) & LPS_MASK; + new_status = cur_status & ~sc->sc_last_status; + sc->sc_last_status = cur_status; + + if (new_status & LPS_SELECT) + log(LOG_NOTICE, "%s: offline\n", + device_get_nameunit(sc->sc_dev)); + else if (new_status & LPS_NOPAPER) + log(LOG_NOTICE, "%s: out of paper\n", + device_get_nameunit(sc->sc_dev)); + else if (new_status & LPS_NERR) + log(LOG_NOTICE, "%s: output error\n", + device_get_nameunit(sc->sc_dev)); + break; + + case USB_ST_SETUP: + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_PORT_STATUS; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, 1); + usbd_xfer_set_frames(xfer, 2); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + DPRINTF("error=%s\n", usbd_errstr(error)); + if (error != USB_ERR_CANCELLED) { + /* wait for next watchdog timeout */ + } + break; + } +} + +static const struct usb_config ulpt_config[ULPT_N_TRANSFER] = { + [ULPT_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = ULPT_BSIZE, + .flags = {.pipe_bof = 1,.proxy_buffer = 1}, + .callback = &ulpt_write_callback, + }, + + [ULPT_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = ULPT_BSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1}, + .callback = &ulpt_read_callback, + }, + + [ULPT_INTR_DT_RD] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request) + 1, + .callback = &ulpt_status_callback, + .timeout = 1000, /* 1 second */ + }, +}; + +static void +ulpt_start_read(struct usb_fifo *fifo) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_start(sc->sc_xfer[ULPT_BULK_DT_RD]); +} + +static void +ulpt_stop_read(struct usb_fifo *fifo) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->sc_xfer[ULPT_BULK_DT_RD]); +} + +static void +ulpt_start_write(struct usb_fifo *fifo) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_start(sc->sc_xfer[ULPT_BULK_DT_WR]); +} + +static void +ulpt_stop_write(struct usb_fifo *fifo) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + usbd_transfer_stop(sc->sc_xfer[ULPT_BULK_DT_WR]); +} + +static int +ulpt_open(struct usb_fifo *fifo, int fflags) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + /* we assume that open is a serial process */ + + if (sc->sc_fflags == 0) { + + /* reset USB parallel port */ + + ulpt_reset(sc); + } + return (unlpt_open(fifo, fflags)); +} + +static int +unlpt_open(struct usb_fifo *fifo, int fflags) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + if (sc->sc_fflags & fflags) { + return (EBUSY); + } + if (fflags & FREAD) { + /* clear stall first */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[ULPT_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + if (usb_fifo_alloc_buffer(fifo, + usbd_xfer_max_len(sc->sc_xfer[ULPT_BULK_DT_RD]), + ULPT_IFQ_MAXLEN)) { + return (ENOMEM); + } + /* set which FIFO is opened */ + sc->sc_fifo_open[USB_FIFO_RX] = fifo; + } + if (fflags & FWRITE) { + /* clear stall first */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[ULPT_BULK_DT_WR]); + mtx_unlock(&sc->sc_mtx); + if (usb_fifo_alloc_buffer(fifo, + usbd_xfer_max_len(sc->sc_xfer[ULPT_BULK_DT_WR]), + ULPT_IFQ_MAXLEN)) { + return (ENOMEM); + } + /* set which FIFO is opened */ + sc->sc_fifo_open[USB_FIFO_TX] = fifo; + } + sc->sc_fflags |= fflags & (FREAD | FWRITE); + return (0); +} + +static void +ulpt_close(struct usb_fifo *fifo, int fflags) +{ + struct ulpt_softc *sc = usb_fifo_softc(fifo); + + sc->sc_fflags &= ~(fflags & (FREAD | FWRITE)); + + if (fflags & (FREAD | FWRITE)) { + usb_fifo_free_buffer(fifo); + } +} + +static int +ulpt_ioctl(struct usb_fifo *fifo, u_long cmd, void *data, + int fflags) +{ + return (ENODEV); +} + +static const STRUCT_USB_HOST_ID ulpt_devs[] = { + /* Uni-directional USB printer */ + {USB_IFACE_CLASS(UICLASS_PRINTER), + USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER), + USB_IFACE_PROTOCOL(UIPROTO_PRINTER_UNI)}, + + /* Bi-directional USB printer */ + {USB_IFACE_CLASS(UICLASS_PRINTER), + USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER), + USB_IFACE_PROTOCOL(UIPROTO_PRINTER_BI)}, + + /* 1284 USB printer */ + {USB_IFACE_CLASS(UICLASS_PRINTER), + USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER), + USB_IFACE_PROTOCOL(UIPROTO_PRINTER_1284)}, +}; + +static int +ulpt_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + int error; + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + error = usbd_lookup_id_by_uaa(ulpt_devs, sizeof(ulpt_devs), uaa); + if (error) + return (error); + + return (BUS_PROBE_GENERIC); +} + +static int +ulpt_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ulpt_softc *sc = device_get_softc(dev); + struct usb_interface_descriptor *id; + int unit = device_get_unit(dev); + int error; + uint8_t iface_index = uaa->info.bIfaceIndex; + uint8_t alt_index; + + DPRINTFN(11, "sc=%p\n", sc); + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, "ulpt lock", NULL, MTX_DEF | MTX_RECURSE); + + usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0); + + /* search through all the descriptors looking for bidir mode */ + + id = usbd_get_interface_descriptor(uaa->iface); + alt_index = 0xFF; + while (1) { + if (id == NULL) { + break; + } + if ((id->bDescriptorType == UDESC_INTERFACE) && + (id->bLength >= sizeof(*id))) { + if (id->bInterfaceNumber != uaa->info.bIfaceNum) { + break; + } else { + alt_index++; + if ((id->bInterfaceClass == UICLASS_PRINTER) && + (id->bInterfaceSubClass == UISUBCLASS_PRINTER) && + (id->bInterfaceProtocol == UIPROTO_PRINTER_BI)) { + goto found; + } + } + } + id = (void *)usb_desc_foreach( + usbd_get_config_descriptor(uaa->device), (void *)id); + } + goto detach; + +found: + + DPRINTF("setting alternate " + "config number: %d\n", alt_index); + + if (alt_index) { + + error = usbd_set_alt_interface_index + (uaa->device, iface_index, alt_index); + + if (error) { + DPRINTF("could not set alternate " + "config, error=%s\n", usbd_errstr(error)); + goto detach; + } + } + sc->sc_iface_no = id->bInterfaceNumber; + + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, ulpt_config, ULPT_N_TRANSFER, + sc, &sc->sc_mtx); + if (error) { + DPRINTF("error=%s\n", usbd_errstr(error)); + goto detach; + } + device_printf(sc->sc_dev, "using bi-directional mode\n"); + +#if 0 +/* + * This code is disabled because for some mysterious reason it causes + * printing not to work. But only sometimes, and mostly with + * UHCI and less often with OHCI. *sigh* + */ + { + struct usb_config_descriptor *cd = usbd_get_config_descriptor(dev); + struct usb_device_request req; + int len, alen; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_DEVICE_ID; + USETW(req.wValue, cd->bConfigurationValue); + USETW2(req.wIndex, id->bInterfaceNumber, id->bAlternateSetting); + USETW(req.wLength, sizeof devinfo - 1); + error = usbd_do_request_flags(dev, &req, devinfo, USB_SHORT_XFER_OK, + &alen, USB_DEFAULT_TIMEOUT); + if (error) { + device_printf(sc->sc_dev, "cannot get device id\n"); + } else if (alen <= 2) { + device_printf(sc->sc_dev, "empty device id, no " + "printer connected?\n"); + } else { + /* devinfo now contains an IEEE-1284 device ID */ + len = ((devinfo[0] & 0xff) << 8) | (devinfo[1] & 0xff); + if (len > sizeof devinfo - 3) + len = sizeof devinfo - 3; + devinfo[len] = 0; + printf("%s: device id <", device_get_nameunit(sc->sc_dev)); + ieee1284_print_id(devinfo + 2); + printf(">\n"); + } + } +#endif + + error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &ulpt_fifo_methods, &sc->sc_fifo, + unit, -1, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + if (error) { + goto detach; + } + error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx, + &unlpt_fifo_methods, &sc->sc_fifo_noreset, + unit, -1, uaa->info.bIfaceIndex, + UID_ROOT, GID_OPERATOR, 0644); + if (error) { + goto detach; + } + /* start reading of status */ + + mtx_lock(&sc->sc_mtx); + ulpt_watchdog(sc); + mtx_unlock(&sc->sc_mtx); + return (0); + +detach: + ulpt_detach(dev); + return (ENOMEM); +} + +static int +ulpt_detach(device_t dev) +{ + struct ulpt_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + usb_fifo_detach(&sc->sc_fifo); + usb_fifo_detach(&sc->sc_fifo_noreset); + + mtx_lock(&sc->sc_mtx); + usb_callout_stop(&sc->sc_watchdog); + mtx_unlock(&sc->sc_mtx); + + usbd_transfer_unsetup(sc->sc_xfer, ULPT_N_TRANSFER); + usb_callout_drain(&sc->sc_watchdog); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +#if 0 +/* XXX This does not belong here. */ + +/* + * Compare two strings until the second ends. + */ + +static uint8_t +ieee1284_compare(const char *a, const char *b) +{ + while (1) { + + if (*b == 0) { + break; + } + if (*a != *b) { + return 1; + } + b++; + a++; + } + return 0; +} + +/* + * Print select parts of an IEEE 1284 device ID. + */ +void +ieee1284_print_id(char *str) +{ + char *p, *q; + + for (p = str - 1; p; p = strchr(p, ';')) { + p++; /* skip ';' */ + if (ieee1284_compare(p, "MFG:") == 0 || + ieee1284_compare(p, "MANUFACTURER:") == 0 || + ieee1284_compare(p, "MDL:") == 0 || + ieee1284_compare(p, "MODEL:") == 0) { + q = strchr(p, ';'); + if (q) + printf("%.*s", (int)(q - p + 1), p); + } + } +} + +#endif + +static void +ulpt_watchdog(void *arg) +{ + struct ulpt_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + /* + * Only read status while the device is not opened, due to + * possible hardware or firmware bug in some printers. + */ + if (sc->sc_fflags == 0) + usbd_transfer_start(sc->sc_xfer[ULPT_INTR_DT_RD]); + + usb_callout_reset(&sc->sc_watchdog, + hz, &ulpt_watchdog, sc); +} + +static devclass_t ulpt_devclass; + +static device_method_t ulpt_methods[] = { + DEVMETHOD(device_probe, ulpt_probe), + DEVMETHOD(device_attach, ulpt_attach), + DEVMETHOD(device_detach, ulpt_detach), + DEVMETHOD_END +}; + +static driver_t ulpt_driver = { + .name = "ulpt", + .methods = ulpt_methods, + .size = sizeof(struct ulpt_softc), +}; + +DRIVER_MODULE(ulpt, uhub, ulpt_driver, ulpt_devclass, NULL, 0); +MODULE_DEPEND(ulpt, usb, 1, 1, 1); +MODULE_VERSION(ulpt, 1); +USB_PNP_HOST_INFO(ulpt_devs); diff --git a/freebsd/sys/dev/usb/serial/umcs.c b/freebsd/sys/dev/usb/serial/umcs.c new file mode 100644 index 00000000..97712ed6 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/umcs.c @@ -0,0 +1,1107 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/*- + * Copyright (c) 2010 Lev Serebryakov <lev@FreeBSD.org>. + * 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. + * 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. + */ + +/* + * This driver supports several multiport USB-to-RS232 serial adapters driven + * by MosChip mos7820 and mos7840, bridge chips. + * The adapters are sold under many different brand names. + * + * Datasheets are available at MosChip www site at + * http://www.moschip.com. The datasheets don't contain full + * programming information for the chip. + * + * It is nornal to have only two enabled ports in devices, based on + * quad-port mos7840. + * + */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/linker_set.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_cdc.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR umcs_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#include <dev/usb/serial/umcs.h> + +#define UMCS7840_MODVER 1 + +#ifdef USB_DEBUG +static int umcs_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, umcs, CTLFLAG_RW, 0, "USB umcs quadport serial adapter"); +SYSCTL_INT(_hw_usb_umcs, OID_AUTO, debug, CTLFLAG_RWTUN, &umcs_debug, 0, "Debug level"); +#endif /* USB_DEBUG */ + + +/* + * Two-port devices (both with 7820 chip and 7840 chip configured as two-port) + * have ports 0 and 2, with ports 1 and 3 omitted. + * So,PHYSICAL port numbers (indexes) on two-port device will be 0 and 2. + * This driver trys to use physical numbers as much as possible. + */ + +/* + * Indexed by PHYSICAL port number. + * Pack non-regular registers to array to easier if-less access. + */ +struct umcs7840_port_registers { + uint8_t reg_sp; /* SP register. */ + uint8_t reg_control; /* CONTROL register. */ + uint8_t reg_dcr; /* DCR0 register. DCR1 & DCR2 can be + * calculated */ +}; + +static const struct umcs7840_port_registers umcs7840_port_registers[UMCS7840_MAX_PORTS] = { + {.reg_sp = MCS7840_DEV_REG_SP1,.reg_control = MCS7840_DEV_REG_CONTROL1,.reg_dcr = MCS7840_DEV_REG_DCR0_1}, + {.reg_sp = MCS7840_DEV_REG_SP2,.reg_control = MCS7840_DEV_REG_CONTROL2,.reg_dcr = MCS7840_DEV_REG_DCR0_2}, + {.reg_sp = MCS7840_DEV_REG_SP3,.reg_control = MCS7840_DEV_REG_CONTROL3,.reg_dcr = MCS7840_DEV_REG_DCR0_3}, + {.reg_sp = MCS7840_DEV_REG_SP4,.reg_control = MCS7840_DEV_REG_CONTROL4,.reg_dcr = MCS7840_DEV_REG_DCR0_4}, +}; + +enum { + UMCS7840_BULK_RD_EP, + UMCS7840_BULK_WR_EP, + UMCS7840_N_TRANSFERS +}; + +struct umcs7840_softc_oneport { + struct usb_xfer *sc_xfer[UMCS7840_N_TRANSFERS]; /* Control structures + * for two transfers */ + + uint8_t sc_lcr; /* local line control register */ + uint8_t sc_mcr; /* local modem control register */ +}; + +struct umcs7840_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom[UMCS7840_MAX_PORTS]; /* Need to be continuous + * array, so indexed by + * LOGICAL port + * (subunit) number */ + + struct usb_xfer *sc_intr_xfer; /* Interrupt endpoint */ + + device_t sc_dev; /* Device for error prints */ + struct usb_device *sc_udev; /* USB Device for all operations */ + struct mtx sc_mtx; /* ucom requires this */ + + uint8_t sc_driver_done; /* Flag when enumeration is finished */ + + uint8_t sc_numports; /* Number of ports (subunits) */ + struct umcs7840_softc_oneport sc_ports[UMCS7840_MAX_PORTS]; /* Indexed by PHYSICAL + * port number. */ +}; + +/* prototypes */ +static usb_error_t umcs7840_get_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t *); +static usb_error_t umcs7840_set_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t); +static usb_error_t umcs7840_get_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t *); +static usb_error_t umcs7840_set_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t); + +static usb_error_t umcs7840_set_baudrate(struct umcs7840_softc *, uint8_t, uint32_t); +static usb_error_t umcs7840_calc_baudrate(uint32_t rate, uint16_t *, uint8_t *); + +static void umcs7840_free(struct ucom_softc *); +static void umcs7840_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *); +static void umcs7840_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void umcs7840_cfg_set_rts(struct ucom_softc *, uint8_t); +static void umcs7840_cfg_set_break(struct ucom_softc *, uint8_t); +static void umcs7840_cfg_param(struct ucom_softc *, struct termios *); +static void umcs7840_cfg_open(struct ucom_softc *); +static void umcs7840_cfg_close(struct ucom_softc *); + +static int umcs7840_pre_param(struct ucom_softc *, struct termios *); + +static void umcs7840_start_read(struct ucom_softc *); +static void umcs7840_stop_read(struct ucom_softc *); + +static void umcs7840_start_write(struct ucom_softc *); +static void umcs7840_stop_write(struct ucom_softc *); + +static void umcs7840_poll(struct ucom_softc *ucom); + +static device_probe_t umcs7840_probe; +static device_attach_t umcs7840_attach; +static device_detach_t umcs7840_detach; +static void umcs7840_free_softc(struct umcs7840_softc *); + +static usb_callback_t umcs7840_intr_callback; +static usb_callback_t umcs7840_read_callback1; +static usb_callback_t umcs7840_read_callback2; +static usb_callback_t umcs7840_read_callback3; +static usb_callback_t umcs7840_read_callback4; +static usb_callback_t umcs7840_write_callback1; +static usb_callback_t umcs7840_write_callback2; +static usb_callback_t umcs7840_write_callback3; +static usb_callback_t umcs7840_write_callback4; + +static void umcs7840_read_callbackN(struct usb_xfer *, usb_error_t, uint8_t); +static void umcs7840_write_callbackN(struct usb_xfer *, usb_error_t, uint8_t); + +/* Indexed by LOGICAL port number (subunit), so two-port device uses 0 & 1 */ +static usb_callback_t *umcs7840_rw_callbacks[UMCS7840_MAX_PORTS][UMCS7840_N_TRANSFERS] = { + {&umcs7840_read_callback1, &umcs7840_write_callback1}, + {&umcs7840_read_callback2, &umcs7840_write_callback2}, + {&umcs7840_read_callback3, &umcs7840_write_callback3}, + {&umcs7840_read_callback4, &umcs7840_write_callback4}, +}; + +static const struct usb_config umcs7840_bulk_config_data[UMCS7840_N_TRANSFERS] = { + [UMCS7840_BULK_RD_EP] = { + .type = UE_BULK, + .endpoint = 0x01, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umcs7840_read_callback1, + .if_index = 0, + }, + + [UMCS7840_BULK_WR_EP] = { + .type = UE_BULK, + .endpoint = 0x02, + .direction = UE_DIR_OUT, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umcs7840_write_callback1, + .if_index = 0, + }, +}; + +static const struct usb_config umcs7840_intr_config_data[1] = { + [0] = { + .type = UE_INTERRUPT, + .endpoint = 0x09, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umcs7840_intr_callback, + .if_index = 0, + }, +}; + +static struct ucom_callback umcs7840_callback = { + .ucom_cfg_get_status = &umcs7840_cfg_get_status, + + .ucom_cfg_set_dtr = &umcs7840_cfg_set_dtr, + .ucom_cfg_set_rts = &umcs7840_cfg_set_rts, + .ucom_cfg_set_break = &umcs7840_cfg_set_break, + + .ucom_cfg_param = &umcs7840_cfg_param, + .ucom_cfg_open = &umcs7840_cfg_open, + .ucom_cfg_close = &umcs7840_cfg_close, + + .ucom_pre_param = &umcs7840_pre_param, + + .ucom_start_read = &umcs7840_start_read, + .ucom_stop_read = &umcs7840_stop_read, + + .ucom_start_write = &umcs7840_start_write, + .ucom_stop_write = &umcs7840_stop_write, + + .ucom_poll = &umcs7840_poll, + .ucom_free = &umcs7840_free, +}; + +static const STRUCT_USB_HOST_ID umcs7840_devs[] = { + {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7820, 0)}, + {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7840, 0)}, +}; + +static device_method_t umcs7840_methods[] = { + DEVMETHOD(device_probe, umcs7840_probe), + DEVMETHOD(device_attach, umcs7840_attach), + DEVMETHOD(device_detach, umcs7840_detach), + DEVMETHOD_END +}; + +static devclass_t umcs7840_devclass; + +static driver_t umcs7840_driver = { + .name = "umcs7840", + .methods = umcs7840_methods, + .size = sizeof(struct umcs7840_softc), +}; + +DRIVER_MODULE(umcs7840, uhub, umcs7840_driver, umcs7840_devclass, 0, 0); +MODULE_DEPEND(umcs7840, ucom, 1, 1, 1); +MODULE_DEPEND(umcs7840, usb, 1, 1, 1); +MODULE_VERSION(umcs7840, UMCS7840_MODVER); +USB_PNP_HOST_INFO(umcs7840_devs); + +static int +umcs7840_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != MCS7840_CONFIG_INDEX) + return (ENXIO); + if (uaa->info.bIfaceIndex != MCS7840_IFACE_INDEX) + return (ENXIO); + return (usbd_lookup_id_by_uaa(umcs7840_devs, sizeof(umcs7840_devs), uaa)); +} + +static int +umcs7840_attach(device_t dev) +{ + struct usb_config umcs7840_config_tmp[UMCS7840_N_TRANSFERS]; + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umcs7840_softc *sc = device_get_softc(dev); + + uint8_t iface_index = MCS7840_IFACE_INDEX; + int error; + int subunit; + int n; + uint8_t data; + + for (n = 0; n < UMCS7840_N_TRANSFERS; ++n) + umcs7840_config_tmp[n] = umcs7840_bulk_config_data[n]; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "umcs7840", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + + /* + * Get number of ports + * Documentation (full datasheet) says, that number of ports is + * set as MCS7840_DEV_MODE_SELECT24S bit in MODE R/Only + * register. But vendor driver uses these undocumented + * register & bit. + * + * Experiments show, that MODE register can have `0' + * (4 ports) bit on 2-port device, so use vendor driver's way. + * + * Also, see notes in header file for these constants. + */ + umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_GPIO, &data); + if (data & MCS7840_DEV_GPIO_4PORTS) { + sc->sc_numports = 4; + /* Store physical port numbers in sc_portno */ + sc->sc_ucom[0].sc_portno = 0; + sc->sc_ucom[1].sc_portno = 1; + sc->sc_ucom[2].sc_portno = 2; + sc->sc_ucom[3].sc_portno = 3; + } else { + sc->sc_numports = 2; + /* Store physical port numbers in sc_portno */ + sc->sc_ucom[0].sc_portno = 0; + sc->sc_ucom[1].sc_portno = 2; /* '1' is skipped */ + } + device_printf(dev, "Chip mcs%04x, found %d active ports\n", uaa->info.idProduct, sc->sc_numports); + if (!umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_MODE, &data)) { + device_printf(dev, "On-die confguration: RST: active %s, HRD: %s, PLL: %s, POR: %s, Ports: %s, EEPROM write %s, IrDA is %savailable\n", + (data & MCS7840_DEV_MODE_RESET) ? "low" : "high", + (data & MCS7840_DEV_MODE_SER_PRSNT) ? "yes" : "no", + (data & MCS7840_DEV_MODE_PLLBYPASS) ? "bypassed" : "avail", + (data & MCS7840_DEV_MODE_PORBYPASS) ? "bypassed" : "avail", + (data & MCS7840_DEV_MODE_SELECT24S) ? "2" : "4", + (data & MCS7840_DEV_MODE_EEPROMWR) ? "enabled" : "disabled", + (data & MCS7840_DEV_MODE_IRDA) ? "" : "not "); + } + /* Setup all transfers */ + for (subunit = 0; subunit < sc->sc_numports; ++subunit) { + for (n = 0; n < UMCS7840_N_TRANSFERS; ++n) { + /* Set endpoint address */ + umcs7840_config_tmp[n].endpoint = umcs7840_bulk_config_data[n].endpoint + 2 * sc->sc_ucom[subunit].sc_portno; + umcs7840_config_tmp[n].callback = umcs7840_rw_callbacks[subunit][n]; + } + error = usbd_transfer_setup(uaa->device, + &iface_index, sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, umcs7840_config_tmp, + UMCS7840_N_TRANSFERS, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB transfers failed for subunit %d of %d\n", + subunit + 1, sc->sc_numports); + goto detach; + } + } + error = usbd_transfer_setup(uaa->device, + &iface_index, &sc->sc_intr_xfer, umcs7840_intr_config_data, + 1, sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "allocating USB transfers failed for interrupt\n"); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + for (subunit = 0; subunit < sc->sc_numports; ++subunit) { + usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_RD_EP]); + usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_WR_EP]); + } + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_numports, sc, + &umcs7840_callback, &sc->sc_mtx); + if (error) + goto detach; + + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + umcs7840_detach(dev); + return (ENXIO); +} + +static int +umcs7840_detach(device_t dev) +{ + struct umcs7840_softc *sc = device_get_softc(dev); + int subunit; + + ucom_detach(&sc->sc_super_ucom, sc->sc_ucom); + + for (subunit = 0; subunit < sc->sc_numports; ++subunit) + usbd_transfer_unsetup(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, UMCS7840_N_TRANSFERS); + usbd_transfer_unsetup(&sc->sc_intr_xfer, 1); + + device_claim_softc(dev); + + umcs7840_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(umcs7840); + +static void +umcs7840_free_softc(struct umcs7840_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +umcs7840_free(struct ucom_softc *ucom) +{ + umcs7840_free_softc(ucom->sc_parent); +} + +static void +umcs7840_cfg_open(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint16_t pn = ucom->sc_portno; + uint8_t data; + + /* If it very first open, finish global configuration */ + if (!sc->sc_driver_done) { + /* + * USB enumeration is finished, pass internal memory to FIFOs + * If it is done in the end of "attach", kernel panics. + */ + if (umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, &data)) + return; + data |= MCS7840_DEV_CONTROL1_DRIVER_DONE; + if (umcs7840_set_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, data)) + return; + sc->sc_driver_done = 1; + } + /* Toggle reset bit on-off */ + if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data)) + return; + data |= MCS7840_DEV_SPx_UART_RESET; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) + return; + data &= ~MCS7840_DEV_SPx_UART_RESET; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) + return; + + /* Set RS-232 mode */ + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_SCRATCHPAD, MCS7840_UART_SCRATCHPAD_RS232)) + return; + + /* Disable RX on time of initialization */ + if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data)) + return; + data |= MCS7840_DEV_CONTROLx_RX_DISABLE; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data)) + return; + + /* Disable all interrupts */ + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0)) + return; + + /* Reset FIFO -- documented */ + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR, 0)) + return; + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR, + MCS7840_UART_FCR_ENABLE | MCS7840_UART_FCR_FLUSHRHR | + MCS7840_UART_FCR_FLUSHTHR | MCS7840_UART_FCR_RTL_1_14)) + return; + + /* Set 8 bit, no parity, 1 stop bit -- documented */ + sc->sc_ports[pn].sc_lcr = MCS7840_UART_LCR_DATALEN8 | MCS7840_UART_LCR_STOPB1; + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr)) + return; + + /* + * Enable DTR/RTS on modem control, enable modem interrupts -- + * documented + */ + sc->sc_ports[pn].sc_mcr = MCS7840_UART_MCR_DTR | MCS7840_UART_MCR_RTS | MCS7840_UART_MCR_IE; + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr)) + return; + + /* Clearing Bulkin and Bulkout FIFO */ + if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data)) + return; + data |= MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) + return; + data &= ~(MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO); + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data)) + return; + + /* Set speed 9600 */ + if (umcs7840_set_baudrate(sc, pn, 9600)) + return; + + + /* Finally enable all interrupts -- documented */ + /* + * Copied from vendor driver, I don't know why we should read LCR + * here + */ + if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, &sc->sc_ports[pn].sc_lcr)) + return; + if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, + MCS7840_UART_IER_RXSTAT | MCS7840_UART_IER_MODEM)) + return; + + /* Enable RX */ + if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data)) + return; + data &= ~MCS7840_DEV_CONTROLx_RX_DISABLE; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data)) + return; + + DPRINTF("Port %d has been opened\n", pn); +} + +static void +umcs7840_cfg_close(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint16_t pn = ucom->sc_portno; + uint8_t data; + + umcs7840_stop_read(ucom); + umcs7840_stop_write(ucom); + + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, 0); + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0); + + /* Disable RX */ + if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data)) + return; + data |= MCS7840_DEV_CONTROLx_RX_DISABLE; + if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data)) + return; + DPRINTF("Port %d has been closed\n", pn); +} + +static void +umcs7840_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + if (onoff) + sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_DTR; + else + sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_DTR; + + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr); + DPRINTF("Port %d DTR set to: %s\n", pn, onoff ? "on" : "off"); +} + +static void +umcs7840_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + if (onoff) + sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_RTS; + else + sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_RTS; + + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr); + DPRINTF("Port %d RTS set to: %s\n", pn, onoff ? "on" : "off"); +} + +static void +umcs7840_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + if (onoff) + sc->sc_ports[pn].sc_lcr |= MCS7840_UART_LCR_BREAK; + else + sc->sc_ports[pn].sc_lcr &= ~MCS7840_UART_LCR_BREAK; + + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr); + DPRINTF("Port %d BREAK set to: %s\n", pn, onoff ? "on" : "off"); +} + + +static void +umcs7840_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + uint8_t lcr = sc->sc_ports[pn].sc_lcr; + uint8_t mcr = sc->sc_ports[pn].sc_mcr; + + DPRINTF("Port %d config:\n", pn); + if (t->c_cflag & CSTOPB) { + DPRINTF(" 2 stop bits\n"); + lcr |= MCS7840_UART_LCR_STOPB2; + } else { + lcr |= MCS7840_UART_LCR_STOPB1; + DPRINTF(" 1 stop bit\n"); + } + + lcr &= ~MCS7840_UART_LCR_PARITYMASK; + if (t->c_cflag & PARENB) { + lcr |= MCS7840_UART_LCR_PARITYON; + if (t->c_cflag & PARODD) { + lcr = MCS7840_UART_LCR_PARITYODD; + DPRINTF(" parity on - odd\n"); + } else { + lcr = MCS7840_UART_LCR_PARITYEVEN; + DPRINTF(" parity on - even\n"); + } + } else { + lcr &= ~MCS7840_UART_LCR_PARITYON; + DPRINTF(" parity off\n"); + } + + lcr &= ~MCS7840_UART_LCR_DATALENMASK; + switch (t->c_cflag & CSIZE) { + case CS5: + lcr |= MCS7840_UART_LCR_DATALEN5; + DPRINTF(" 5 bit\n"); + break; + case CS6: + lcr |= MCS7840_UART_LCR_DATALEN6; + DPRINTF(" 6 bit\n"); + break; + case CS7: + lcr |= MCS7840_UART_LCR_DATALEN7; + DPRINTF(" 7 bit\n"); + break; + case CS8: + lcr |= MCS7840_UART_LCR_DATALEN8; + DPRINTF(" 8 bit\n"); + break; + } + + if (t->c_cflag & CRTSCTS) { + mcr |= MCS7840_UART_MCR_CTSRTS; + DPRINTF(" CTS/RTS\n"); + } else + mcr &= ~MCS7840_UART_MCR_CTSRTS; + + if (t->c_cflag & (CDTR_IFLOW | CDSR_OFLOW)) { + mcr |= MCS7840_UART_MCR_DTRDSR; + DPRINTF(" DTR/DSR\n"); + } else + mcr &= ~MCS7840_UART_MCR_DTRDSR; + + sc->sc_ports[pn].sc_lcr = lcr; + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr); + DPRINTF("Port %d LCR=%02x\n", pn, sc->sc_ports[pn].sc_lcr); + + sc->sc_ports[pn].sc_mcr = mcr; + umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr); + DPRINTF("Port %d MCR=%02x\n", pn, sc->sc_ports[pn].sc_mcr); + + umcs7840_set_baudrate(sc, pn, t->c_ospeed); +} + + +static int +umcs7840_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + uint8_t clk; + uint16_t divisor; + + if (umcs7840_calc_baudrate(t->c_ospeed, &divisor, &clk) || !divisor) + return (EINVAL); + return (0); +} + +static void +umcs7840_start_read(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + /* Start interrupt transfer */ + usbd_transfer_start(sc->sc_intr_xfer); + + /* Start read transfer */ + usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]); +} + +static void +umcs7840_stop_read(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + /* Stop read transfer */ + usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]); +} + +static void +umcs7840_start_write(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + /* Start interrupt transfer */ + usbd_transfer_start(sc->sc_intr_xfer); + + /* Start write transfer */ + usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]); +} + +static void +umcs7840_stop_write(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + + /* Stop write transfer */ + usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]); +} + +static void +umcs7840_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + uint8_t pn = ucom->sc_portno; + uint8_t hw_msr = 0; /* local modem status register */ + + /* + * Read status registers. MSR bits need translation from ns16550 to + * SER_* values. LSR bits are ns16550 in hardware and ucom. + */ + umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LSR, lsr); + umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_MSR, &hw_msr); + + if (hw_msr & MCS7840_UART_MSR_NEGCTS) + *msr |= SER_CTS; + + if (hw_msr & MCS7840_UART_MSR_NEGDCD) + *msr |= SER_DCD; + + if (hw_msr & MCS7840_UART_MSR_NEGRI) + *msr |= SER_RI; + + if (hw_msr & MCS7840_UART_MSR_NEGDSR) + *msr |= SER_DSR; + + DPRINTF("Port %d status: LSR=%02x MSR=%02x\n", ucom->sc_portno, *lsr, *msr); +} + +static void +umcs7840_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umcs7840_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[13]; + int actlen; + int subunit; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen == 5 || actlen == 13) { + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, actlen); + /* Check status of all ports */ + for (subunit = 0; subunit < sc->sc_numports; ++subunit) { + uint8_t pn = sc->sc_ucom[subunit].sc_portno; + + if (buf[pn] & MCS7840_UART_ISR_NOPENDING) + continue; + DPRINTF("Port %d has pending interrupt: %02x (FIFO: %02x)\n", pn, buf[pn] & MCS7840_UART_ISR_INTMASK, buf[pn] & (~MCS7840_UART_ISR_INTMASK)); + switch (buf[pn] & MCS7840_UART_ISR_INTMASK) { + case MCS7840_UART_ISR_RXERR: + case MCS7840_UART_ISR_RXHASDATA: + case MCS7840_UART_ISR_RXTIMEOUT: + case MCS7840_UART_ISR_MSCHANGE: + ucom_status_change(&sc->sc_ucom[subunit]); + break; + default: + /* Do nothing */ + break; + } + } + } else + device_printf(sc->sc_dev, "Invalid interrupt data length %d", actlen); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umcs7840_read_callback1(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_read_callbackN(xfer, error, 0); +} + +static void +umcs7840_read_callback2(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_read_callbackN(xfer, error, 1); +} +static void +umcs7840_read_callback3(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_read_callbackN(xfer, error, 2); +} + +static void +umcs7840_read_callback4(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_read_callbackN(xfer, error, 3); +} + +static void +umcs7840_read_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit) +{ + struct umcs7840_softc *sc = usbd_xfer_softc(xfer); + struct ucom_softc *ucom = &sc->sc_ucom[subunit]; + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + DPRINTF("Port %d read, state = %d, data length = %d\n", ucom->sc_portno, USB_GET_STATE(xfer), actlen); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(ucom, pc, 0, actlen); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umcs7840_write_callback1(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_write_callbackN(xfer, error, 0); +} + +static void +umcs7840_write_callback2(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_write_callbackN(xfer, error, 1); +} + +static void +umcs7840_write_callback3(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_write_callbackN(xfer, error, 2); +} + +static void +umcs7840_write_callback4(struct usb_xfer *xfer, usb_error_t error) +{ + umcs7840_write_callbackN(xfer, error, 3); +} + +static void +umcs7840_write_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit) +{ + struct umcs7840_softc *sc = usbd_xfer_softc(xfer); + struct ucom_softc *ucom = &sc->sc_ucom[subunit]; + struct usb_page_cache *pc; + uint32_t actlen; + + DPRINTF("Port %d write, state = %d\n", ucom->sc_portno, USB_GET_STATE(xfer)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(ucom, pc, 0, usbd_xfer_max_len(xfer), &actlen)) { + DPRINTF("Port %d write, has %d bytes\n", ucom->sc_portno, actlen); + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umcs7840_poll(struct ucom_softc *ucom) +{ + struct umcs7840_softc *sc = ucom->sc_parent; + + DPRINTF("Port %d poll\n", ucom->sc_portno); + usbd_transfer_poll(sc->sc_ports[ucom->sc_portno].sc_xfer, UMCS7840_N_TRANSFERS); + usbd_transfer_poll(&sc->sc_intr_xfer, 1); +} + +static usb_error_t +umcs7840_get_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t *data) +{ + struct usb_device_request req; + usb_error_t err; + uint16_t len; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = MCS7840_RDREQ; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, UMCS7840_READ_LENGTH); + + err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT); + if (err == USB_ERR_NORMAL_COMPLETION && len != 1) { + device_printf(sc->sc_dev, "Reading register %d failed: invalid length %d\n", reg, len); + return (USB_ERR_INVAL); + } else if (err) + device_printf(sc->sc_dev, "Reading register %d failed: %s\n", reg, usbd_errstr(err)); + return (err); +} + +static usb_error_t +umcs7840_set_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t data) +{ + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = MCS7840_WRREQ; + USETW(req.wValue, data); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT); + if (err) + device_printf(sc->sc_dev, "Writing register %d failed: %s\n", reg, usbd_errstr(err)); + + return (err); +} + +static usb_error_t +umcs7840_get_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t *data) +{ + struct usb_device_request req; + uint16_t wVal; + usb_error_t err; + uint16_t len; + + /* portno is port number */ + wVal = ((uint16_t)(portno + 1)) << 8; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = MCS7840_RDREQ; + USETW(req.wValue, wVal); + USETW(req.wIndex, reg); + USETW(req.wLength, UMCS7840_READ_LENGTH); + + err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT); + if (err == USB_ERR_NORMAL_COMPLETION && len != 1) { + device_printf(sc->sc_dev, "Reading UART%d register %d failed: invalid length %d\n", portno, reg, len); + return (USB_ERR_INVAL); + } else if (err) + device_printf(sc->sc_dev, "Reading UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err)); + return (err); +} + +static usb_error_t +umcs7840_set_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t data) +{ + struct usb_device_request req; + usb_error_t err; + uint16_t wVal; + + /* portno is port number */ + wVal = ((uint16_t)(portno + 1)) << 8 | data; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = MCS7840_WRREQ; + USETW(req.wValue, wVal); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT); + if (err) + device_printf(sc->sc_dev, "Writing UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err)); + return (err); +} + +static usb_error_t +umcs7840_set_baudrate(struct umcs7840_softc *sc, uint8_t portno, uint32_t rate) +{ + usb_error_t err; + uint16_t divisor; + uint8_t clk; + uint8_t data; + + if (umcs7840_calc_baudrate(rate, &divisor, &clk)) { + DPRINTF("Port %d bad speed: %d\n", portno, rate); + return (-1); + } + if (divisor == 0 || (clk & MCS7840_DEV_SPx_CLOCK_MASK) != clk) { + DPRINTF("Port %d bad speed calculation: %d\n", portno, rate); + return (-1); + } + DPRINTF("Port %d set speed: %d (%02x / %d)\n", portno, rate, clk, divisor); + + /* Set clock source for standard BAUD frequences */ + err = umcs7840_get_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, &data); + if (err) + return (err); + data &= MCS7840_DEV_SPx_CLOCK_MASK; + data |= clk; + err = umcs7840_set_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, data); + if (err) + return (err); + + /* Set divider */ + sc->sc_ports[portno].sc_lcr |= MCS7840_UART_LCR_DIVISORS; + err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr); + if (err) + return (err); + + err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLL, (uint8_t)(divisor & 0xff)); + if (err) + return (err); + err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLM, (uint8_t)((divisor >> 8) & 0xff)); + if (err) + return (err); + + /* Turn off access to DLL/DLM registers of UART */ + sc->sc_ports[portno].sc_lcr &= ~MCS7840_UART_LCR_DIVISORS; + err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr); + if (err) + return (err); + return (0); +} + +/* Maximum speeds for standard frequences, when PLL is not used */ +static const uint32_t umcs7840_baudrate_divisors[] = {0, 115200, 230400, 403200, 460800, 806400, 921600, 1572864, 3145728,}; +static const uint8_t umcs7840_baudrate_divisors_len = nitems(umcs7840_baudrate_divisors); + +static usb_error_t +umcs7840_calc_baudrate(uint32_t rate, uint16_t *divisor, uint8_t *clk) +{ + uint8_t i = 0; + + if (rate > umcs7840_baudrate_divisors[umcs7840_baudrate_divisors_len - 1]) + return (-1); + + for (i = 0; i < umcs7840_baudrate_divisors_len - 1 && + !(rate > umcs7840_baudrate_divisors[i] && rate <= umcs7840_baudrate_divisors[i + 1]); ++i); + if (rate == 0) + *divisor = 1; /* XXX */ + else + *divisor = umcs7840_baudrate_divisors[i + 1] / rate; + /* 0x00 .. 0x70 */ + *clk = i << MCS7840_DEV_SPx_CLOCK_SHIFT; + return (0); +} diff --git a/freebsd/sys/dev/usb/serial/umcs.h b/freebsd/sys/dev/usb/serial/umcs.h new file mode 100644 index 00000000..8ba57c13 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/umcs.h @@ -0,0 +1,644 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2010 Lev Serebryakov <lev@FreeBSD.org>. + * 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. + * 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. + */ +#ifndef _UMCS7840_H_ +#define _UMCS7840_H_ + +#define UMCS7840_MAX_PORTS 4 + +#define UMCS7840_READ_LENGTH 1 /* bytes */ +#define UMCS7840_CTRL_TIMEOUT 500 /* ms */ + +/* Read/Wrtire registers vendor commands */ +#define MCS7840_RDREQ 0x0d +#define MCS7840_WRREQ 0x0e + +/* Read/Wrtie EEPROM values */ +#define MCS7840_EEPROM_RW_WVALUE 0x0900 + +/* + * All these registers are documented only in full datasheet, + * which can be requested from MosChip tech support. + */ +#define MCS7840_DEV_REG_SP1 0x00 /* Options for for UART 1, R/W */ +#define MCS7840_DEV_REG_CONTROL1 0x01 /* Control bits for UART 1, + * R/W */ +#define MCS7840_DEV_REG_PINPONGHIGH 0x02 /* High bits of ping-pong + * register, R/W */ +#define MCS7840_DEV_REG_PINPONGLOW 0x03 /* Low bits of ping-pong + * register, R/W */ +/* DCRx_1 Registers goes here (see below, they are documented) */ +#define MCS7840_DEV_REG_GPIO 0x07 /* GPIO_0 and GPIO_1 bits, + * undocumented, see notes + * below R/W */ +#define MCS7840_DEV_REG_SP2 0x08 /* Options for for UART 2, R/W */ +#define MCS7840_DEV_REG_CONTROL2 0x09 /* Control bits for UART 2, + * R/W */ +#define MCS7840_DEV_REG_SP3 0x0a /* Options for for UART 3, R/W */ +#define MCS7840_DEV_REG_CONTROL3 0x0b /* Control bits for UART 3, + * R/W */ +#define MCS7840_DEV_REG_SP4 0x0c /* Options for for UART 4, R/W */ +#define MCS7840_DEV_REG_CONTROL4 0x0d /* Control bits for UART 4, + * R/W */ +#define MCS7840_DEV_REG_PLL_DIV_M 0x0e /* Pre-diviedr for PLL, R/W */ +#define MCS7840_DEV_REG_UNKNOWN1 0x0f /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_PLL_DIV_N 0x10 /* Loop divider for PLL, R/W */ +#define MCS7840_DEV_REG_CLOCK_MUX 0x12 /* PLL input clock & Interrupt + * endpoint control, R/W */ +#define MCS7840_DEV_REG_UNKNOWN2 0x11 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_CLOCK_SELECT12 0x13 /* Clock source for ports 1 & + * 2, R/W */ +#define MCS7840_DEV_REG_CLOCK_SELECT34 0x14 /* Clock source for ports 3 & + * 4, R/W */ +#define MCS7840_DEV_REG_UNKNOWN3 0x15 /* NOT MENTIONED AND NOT USED */ +/* DCRx_2-DCRx_4 Registers goes here (see below, they are documented) */ +#define MCS7840_DEV_REG_UNKNOWN4 0x1f /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWN5 0x20 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWN6 0x21 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWN7 0x22 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWN8 0x23 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWN9 0x24 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWNA 0x25 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWNB 0x26 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWNC 0x27 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWND 0x28 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWNE 0x29 /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_UNKNOWNF 0x2a /* NOT MENTIONED AND NOT USED */ +#define MCS7840_DEV_REG_MODE 0x2b /* Hardware configuration, + * R/Only */ +#define MCS7840_DEV_REG_SP1_ICG 0x2c /* Inter character gap + * configuration for Port 1, + * R/W */ +#define MCS7840_DEV_REG_SP2_ICG 0x2d /* Inter character gap + * configuration for Port 2, + * R/W */ +#define MCS7840_DEV_REG_SP3_ICG 0x2e /* Inter character gap + * configuration for Port 3, + * R/W */ +#define MCS7840_DEV_REG_SP4_ICG 0x2f /* Inter character gap + * configuration for Port 4, + * R/W */ +#define MCS7840_DEV_REG_RX_SAMPLING12 0x30 /* RX sampling for ports 1 & + * 2, R/W */ +#define MCS7840_DEV_REG_RX_SAMPLING34 0x31 /* RX sampling for ports 3 & + * 4, R/W */ +#define MCS7840_DEV_REG_BI_FIFO_STAT1 0x32 /* Bulk-In FIFO Stat for Port + * 1, contains number of + * available bytes, R/Only */ +#define MCS7840_DEV_REG_BO_FIFO_STAT1 0x33 /* Bulk-out FIFO Stat for Port + * 1, contains number of + * available bytes, R/Only */ +#define MCS7840_DEV_REG_BI_FIFO_STAT2 0x34 /* Bulk-In FIFO Stat for Port + * 2, contains number of + * available bytes, R/Only */ +#define MCS7840_DEV_REG_BO_FIFO_STAT2 0x35 /* Bulk-out FIFO Stat for Port + * 2, contains number of + * available bytes, R/Only */ +#define MCS7840_DEV_REG_BI_FIFO_STAT3 0x36 /* Bulk-In FIFO Stat for Port + * 3, contains number of + * available bytes, R/Only */ +#define MCS7840_DEV_REG_BO_FIFO_STAT3 0x37 /* Bulk-out FIFO Stat for Port + * 3, contains number of + * available bytes, R/Only */ +#define MCS7840_DEV_REG_BI_FIFO_STAT4 0x38 /* Bulk-In FIFO Stat for Port + * 4, contains number of + * available bytes, R/Only */ +#define MCS7840_DEV_REG_BO_FIFO_STAT4 0x39 /* Bulk-out FIFO Stat for Port + * 4, contains number of + * available bytes, R/Only */ +#define MCS7840_DEV_REG_ZERO_PERIOD1 0x3a /* Period between zero out + * frames for Port 1, R/W */ +#define MCS7840_DEV_REG_ZERO_PERIOD2 0x3b /* Period between zero out + * frames for Port 1, R/W */ +#define MCS7840_DEV_REG_ZERO_PERIOD3 0x3c /* Period between zero out + * frames for Port 1, R/W */ +#define MCS7840_DEV_REG_ZERO_PERIOD4 0x3d /* Period between zero out + * frames for Port 1, R/W */ +#define MCS7840_DEV_REG_ZERO_ENABLE 0x3e /* Enable/disable of zero out + * frames, R/W */ +#define MCS7840_DEV_REG_THR_VAL_LOW1 0x3f /* Low 8 bits of threshold + * value for Bulk-Out for Port + * 1, R/W */ +#define MCS7840_DEV_REG_THR_VAL_HIGH1 0x40 /* High 1 bit of threshold + * value for Bulk-Out and + * enable flag for Port 1, R/W */ +#define MCS7840_DEV_REG_THR_VAL_LOW2 0x41 /* Low 8 bits of threshold + * value for Bulk-Out for Port + * 2, R/W */ +#define MCS7840_DEV_REG_THR_VAL_HIGH2 0x42 /* High 1 bit of threshold + * value for Bulk-Out and + * enable flag for Port 2, R/W */ +#define MCS7840_DEV_REG_THR_VAL_LOW3 0x43 /* Low 8 bits of threshold + * value for Bulk-Out for Port + * 3, R/W */ +#define MCS7840_DEV_REG_THR_VAL_HIGH3 0x44 /* High 1 bit of threshold + * value for Bulk-Out and + * enable flag for Port 3, R/W */ +#define MCS7840_DEV_REG_THR_VAL_LOW4 0x45 /* Low 8 bits of threshold + * value for Bulk-Out for Port + * 4, R/W */ +#define MCS7840_DEV_REG_THR_VAL_HIGH4 0x46 /* High 1 bit of threshold + * value for Bulk-Out and + * enable flag for Port 4, R/W */ + +/* Bits for SPx registers */ +#define MCS7840_DEV_SPx_LOOP_PIPES 0x01 /* Loop Bulk-Out FIFO to the + * Bulk-In FIFO, default = 0 */ +#define MCS7840_DEV_SPx_SKIP_ERR_DATA 0x02 /* Drop data bytes from UART, + * which were recevied with + * errors, default = 0 */ +#define MCS7840_DEV_SPx_RESET_OUT_FIFO 0x04 /* Reset Bulk-Out FIFO */ +#define MCS7840_DEV_SPx_RESET_IN_FIFO 0x08 /* Reset Bulk-In FIFO */ +#define MCS7840_DEV_SPx_CLOCK_MASK 0x70 /* Mask to extract Baud CLK + * source */ +#define MCS7840_DEV_SPx_CLOCK_X1 0x00 /* CLK = 1.8432Mhz, max speed + * = 115200 bps, default */ +#define MCS7840_DEV_SPx_CLOCK_X2 0x10 /* CLK = 3.6864Mhz, max speed + * = 230400 bps */ +#define MCS7840_DEV_SPx_CLOCK_X35 0x20 /* CLK = 6.4512Mhz, max speed + * = 403200 bps */ +#define MCS7840_DEV_SPx_CLOCK_X4 0x30 /* CLK = 7.3728Mhz, max speed + * = 460800 bps */ +#define MCS7840_DEV_SPx_CLOCK_X7 0x40 /* CLK = 12.9024Mhz, max speed + * = 806400 bps */ +#define MCS7840_DEV_SPx_CLOCK_X8 0x50 /* CLK = 14.7456Mhz, max speed + * = 921600 bps */ +#define MCS7840_DEV_SPx_CLOCK_24MHZ 0x60 /* CLK = 24.0000Mhz, max speed + * = 1.5 Mbps */ +#define MCS7840_DEV_SPx_CLOCK_48MHZ 0x70 /* CLK = 48.0000Mhz, max speed + * = 3.0 Mbps */ +#define MCS7840_DEV_SPx_CLOCK_SHIFT 4 /* Value 0..7 can be shifted + * to get clock value */ +#define MCS7840_DEV_SPx_UART_RESET 0x80 /* Reset UART */ + +/* Bits for CONTROLx registers */ +#define MCS7840_DEV_CONTROLx_HWFC 0x01 /* Enable hardware flow + * control (when power + * down? It is unclear + * in documents), + * default = 0 */ +#define MCS7840_DEV_CONTROLx_UNUNSED1 0x02 /* Reserved */ +#define MCS7840_DEV_CONTROLx_CTS_ENABLE 0x04 /* CTS changes are + * translated to MSR, + * default = 0 */ +#define MCS7840_DEV_CONTROLx_UNUSED2 0x08 /* Reserved for ports + * 2,3,4 */ +#define MCS7840_DEV_CONTROL1_DRIVER_DONE 0x08 /* USB enumerating is + * finished, USB + * enumeration memory + * can be used as FIFOs */ +#define MCS7840_DEV_CONTROLx_RX_NEGATE 0x10 /* Negate RX input, + * works for IrDA mode + * only, default = 0 */ +#define MCS7840_DEV_CONTROLx_RX_DISABLE 0x20 /* Disable RX logic, + * works only for + * RS-232/RS-485 mode, + * default = 0 */ +#define MCS7840_DEV_CONTROLx_FSM_CONTROL 0x40 /* Disable RX FSM when + * TX is in progress, + * works for IrDA mode + * only, default = 0 */ +#define MCS7840_DEV_CONTROLx_UNUSED3 0x80 /* Reserved */ + +/* + * Bits for PINPONGx registers + * These registers control how often two input buffers + * for Bulk-In FIFOs are swapped. One of buffers is used + * for USB trnasfer, other for receiving data from UART. + * Exact meaning of 15 bit value in these registers is unknown + */ +#define MCS7840_DEV_PINPONGHIGH_MULT 128 /* Only 7 bits in PINPONGLOW + * register */ +#define MCS7840_DEV_PINPONGLOW_BITS 7 /* Only 7 bits in PINPONGLOW + * register */ + +/* + * THIS ONE IS UNDOCUMENTED IN FULL DATASHEET, but e-mail from tech support + * confirms, that it is register for GPIO_0 and GPIO_1 data input/output. + * Chips has 2 GPIO, but first one (lower bit) MUST be used by device + * authors as "number of port" indicator, grounded (0) for two-port + * devices and pulled-up to 1 for 4-port devices. + */ +#define MCS7840_DEV_GPIO_4PORTS 0x01 /* Device has 4 ports + * configured */ +#define MCS7840_DEV_GPIO_GPIO_0 0x01 /* The same as above */ +#define MCS7840_DEV_GPIO_GPIO_1 0x02 /* GPIO_1 data */ + +/* + * Constants for PLL dividers + * Ouptut frequency of PLL is: + * Fout = (N/M) * Fin. + * Default PLL input frequency Fin is 12Mhz (on-chip). + */ +#define MCS7840_DEV_PLL_DIV_M_BITS 6 /* Number of useful bits for M + * divider */ +#define MCS7840_DEV_PLL_DIV_M_MASK 0x3f /* Mask for M divider */ +#define MCS7840_DEV_PLL_DIV_M_MIN 1 /* Minimum value for M, 0 is + * forbidden */ +#define MCS7840_DEV_PLL_DIV_M_DEF 1 /* Default value for M */ +#define MCS7840_DEV_PLL_DIV_M_MAX 63 /* Maximum value for M */ +#define MCS7840_DEV_PLL_DIV_N_BITS 6 /* Number of useful bits for N + * divider */ +#define MCS7840_DEV_PLL_DIV_N_MASK 0x3f /* Mask for N divider */ +#define MCS7840_DEV_PLL_DIV_N_MIN 1 /* Minimum value for N, 0 is + * forbidden */ +#define MCS7840_DEV_PLL_DIV_N_DEF 8 /* Default value for N */ +#define MCS7840_DEV_PLL_DIV_N_MAX 63 /* Maximum value for N */ + +/* Bits for CLOCK_MUX register */ +#define MCS7840_DEV_CLOCK_MUX_INPUTMASK 0x03 /* Mask to extract PLL clock + * input */ +#define MCS7840_DEV_CLOCK_MUX_IN12MHZ 0x00 /* 12Mhz PLL input, default */ +#define MCS7840_DEV_CLOCK_MUX_INEXTRN 0x01 /* External (device-depended) + * PLL input */ +#define MCS7840_DEV_CLOCK_MUX_INRSV1 0x02 /* Reserved */ +#define MCS7840_DEV_CLOCK_MUX_INRSV2 0x03 /* Reserved */ +#define MCS7840_DEV_CLOCK_MUX_PLLHIGH 0x04 /* 0 = PLL Output is + * 20MHz-100MHz (default), 1 = + * 100MHz-300MHz range */ +#define MCS7840_DEV_CLOCK_MUX_INTRFIFOS 0x08 /* Enable additional 8 bytes + * fro Interrupt USB pipe with + * USB FIFOs statuses, default + * = 0 */ +#define MCS7840_DEV_CLOCK_MUX_RESERVED1 0x10 /* Unused */ +#define MCS7840_DEV_CLOCK_MUX_RESERVED2 0x20 /* Unused */ +#define MCS7840_DEV_CLOCK_MUX_RESERVED3 0x40 /* Unused */ +#define MCS7840_DEV_CLOCK_MUX_RESERVED4 0x80 /* Unused */ + +/* Bits for CLOCK_SELECTxx registers */ +#define MCS7840_DEV_CLOCK_SELECT1_MASK 0x07 /* Bits for port 1 in + * CLOCK_SELECT12 */ +#define MCS7840_DEV_CLOCK_SELECT1_SHIFT 0 /* Shift for port 1in + * CLOCK_SELECT12 */ +#define MCS7840_DEV_CLOCK_SELECT2_MASK 0x38 /* Bits for port 2 in + * CLOCK_SELECT12 */ +#define MCS7840_DEV_CLOCK_SELECT2_SHIFT 3 /* Shift for port 2 in + * CLOCK_SELECT12 */ +#define MCS7840_DEV_CLOCK_SELECT3_MASK 0x07 /* Bits for port 3 in + * CLOCK_SELECT23 */ +#define MCS7840_DEV_CLOCK_SELECT3_SHIFT 0 /* Shift for port 3 in + * CLOCK_SELECT23 */ +#define MCS7840_DEV_CLOCK_SELECT4_MASK 0x38 /* Bits for port 4 in + * CLOCK_SELECT23 */ +#define MCS7840_DEV_CLOCK_SELECT4_SHIFT 3 /* Shift for port 4 in + * CLOCK_SELECT23 */ +#define MCS7840_DEV_CLOCK_SELECT_STD 0x00 /* STANDARD baudrate derived + * from 96Mhz, default for all + * ports */ +#define MCS7840_DEV_CLOCK_SELECT_30MHZ 0x01 /* 30Mhz */ +#define MCS7840_DEV_CLOCK_SELECT_96MHZ 0x02 /* 96Mhz direct */ +#define MCS7840_DEV_CLOCK_SELECT_120MHZ 0x03 /* 120Mhz */ +#define MCS7840_DEV_CLOCK_SELECT_PLL 0x04 /* PLL output (see for M and N + * dividers) */ +#define MCS7840_DEV_CLOCK_SELECT_EXT 0x05 /* External clock input + * (device-dependend) */ +#define MCS7840_DEV_CLOCK_SELECT_RES1 0x06 /* Unused */ +#define MCS7840_DEV_CLOCK_SELECT_RES2 0x07 /* Unused */ + +/* Bits for MODE register */ +#define MCS7840_DEV_MODE_RESERVED1 0x01 /* Unused */ +#define MCS7840_DEV_MODE_RESET 0x02 /* 0: RESET = Active High + * (default), 1: Reserved (?) */ +#define MCS7840_DEV_MODE_SER_PRSNT 0x04 /* 0: Reserved, 1: Do not use + * hardocded values (default) + * (?) */ +#define MCS7840_DEV_MODE_PLLBYPASS 0x08 /* 1: PLL output is bypassed, + * default = 0 */ +#define MCS7840_DEV_MODE_PORBYPASS 0x10 /* 1: Power-On Reset is + * bypassed, default = 0 */ +#define MCS7840_DEV_MODE_SELECT24S 0x20 /* 0: 4 Serial Ports / IrDA + * active, 1: 2 Serial Ports / + * IrDA active */ +#define MCS7840_DEV_MODE_EEPROMWR 0x40 /* EEPROM write is enabled, + * default */ +#define MCS7840_DEV_MODE_IRDA 0x80 /* IrDA mode is activated + * (could be turned on), + * default */ + +/* Bits for SPx ICG */ +#define MCS7840_DEV_SPx_ICG_DEF 0x24 /* All 8 bits is used as + * number of BAUD clocks of + * pause */ + +/* + * Bits for RX_SAMPLINGxx registers + * These registers control when bit value will be sampled within + * the baud period. + * 0 is very beginning of period, 15 is very end, 7 is the middle. + */ +#define MCS7840_DEV_RX_SAMPLING1_MASK 0x0f /* Bits for port 1 in + * RX_SAMPLING12 */ +#define MCS7840_DEV_RX_SAMPLING1_SHIFT 0 /* Shift for port 1in + * RX_SAMPLING12 */ +#define MCS7840_DEV_RX_SAMPLING2_MASK 0xf0 /* Bits for port 2 in + * RX_SAMPLING12 */ +#define MCS7840_DEV_RX_SAMPLING2_SHIFT 4 /* Shift for port 2 in + * RX_SAMPLING12 */ +#define MCS7840_DEV_RX_SAMPLING3_MASK 0x0f /* Bits for port 3 in + * RX_SAMPLING23 */ +#define MCS7840_DEV_RX_SAMPLING3_SHIFT 0 /* Shift for port 3 in + * RX_SAMPLING23 */ +#define MCS7840_DEV_RX_SAMPLING4_MASK 0xf0 /* Bits for port 4 in + * RX_SAMPLING23 */ +#define MCS7840_DEV_RX_SAMPLING4_SHIFT 4 /* Shift for port 4 in + * RX_SAMPLING23 */ +#define MCS7840_DEV_RX_SAMPLINGx_MIN 0 /* Max for any RX Sampling */ +#define MCS7840_DEV_RX_SAMPLINGx_DEF 7 /* Default for any RX + * Sampling, center of period */ +#define MCS7840_DEV_RX_SAMPLINGx_MAX 15 /* Min for any RX Sampling */ + +/* Bits for ZERO_PERIODx */ +#define MCS7840_DEV_ZERO_PERIODx_DEF 20 /* Number of Bulk-in requests + * befor sending zero-sized + * reply */ + +/* Bits for ZERO_ENABLE */ +#define MCS7840_DEV_ZERO_ENABLE_PORT1 0x01 /* Enable of sending + * zero-sized replies for port + * 1, default */ +#define MCS7840_DEV_ZERO_ENABLE_PORT2 0x02 /* Enable of sending + * zero-sized replies for port + * 2, default */ +#define MCS7840_DEV_ZERO_ENABLE_PORT3 0x04 /* Enable of sending + * zero-sized replies for port + * 3, default */ +#define MCS7840_DEV_ZERO_ENABLE_PORT4 0x08 /* Enable of sending + * zero-sized replies for port + * 4, default */ + +/* Bits for THR_VAL_HIGHx */ +#define MCS7840_DEV_THR_VAL_HIGH_MASK 0x01 /* Only one bit is used */ +#define MCS7840_DEV_THR_VAL_HIGH_MUL 256 /* This one bit is means "256" */ +#define MCS7840_DEV_THR_VAL_HIGH_SHIFT 8 /* This one bit is means "256" */ +#define MCS7840_DEV_THR_VAL_HIGH_ENABLE 0x80 /* Enable threshold */ + +/* These are documented in "public" datasheet */ +#define MCS7840_DEV_REG_DCR0_1 0x04 /* Device contol register 0 for Port + * 1, R/W */ +#define MCS7840_DEV_REG_DCR1_1 0x05 /* Device contol register 1 for Port + * 1, R/W */ +#define MCS7840_DEV_REG_DCR2_1 0x06 /* Device contol register 2 for Port + * 1, R/W */ +#define MCS7840_DEV_REG_DCR0_2 0x16 /* Device contol register 0 for Port + * 2, R/W */ +#define MCS7840_DEV_REG_DCR1_2 0x17 /* Device contol register 1 for Port + * 2, R/W */ +#define MCS7840_DEV_REG_DCR2_2 0x18 /* Device contol register 2 for Port + * 2, R/W */ +#define MCS7840_DEV_REG_DCR0_3 0x19 /* Device contol register 0 for Port + * 3, R/W */ +#define MCS7840_DEV_REG_DCR1_3 0x1a /* Device contol register 1 for Port + * 3, R/W */ +#define MCS7840_DEV_REG_DCR2_3 0x1b /* Device contol register 2 for Port + * 3, R/W */ +#define MCS7840_DEV_REG_DCR0_4 0x1c /* Device contol register 0 for Port + * 4, R/W */ +#define MCS7840_DEV_REG_DCR1_4 0x1d /* Device contol register 1 for Port + * 4, R/W */ +#define MCS7840_DEV_REG_DCR2_4 0x1e /* Device contol register 2 for Port + * 4, R/W */ + +/* Bits of DCR0 registers, documented in datasheet */ +#define MCS7840_DEV_DCR0_PWRSAVE 0x01 /* Shutdown transiver + * when USB Suspend is + * engaged, default = 1 */ +#define MCS7840_DEV_DCR0_RESERVED1 0x02 /* Unused */ +#define MCS7840_DEV_DCR0_GPIO_MODE_MASK 0x0c /* GPIO Mode bits, WORKS + * ONLY FOR PORT 1 */ +#define MCS7840_DEV_DCR0_GPIO_MODE_IN 0x00 /* GPIO Mode - Input + * (0b00), WORKS ONLY + * FOR PORT 1 */ +#define MCS7840_DEV_DCR0_GPIO_MODE_OUT 0x08 /* GPIO Mode - Input + * (0b10), WORKS ONLY + * FOR PORT 1 */ +#define MCS7840_DEV_DCR0_RTS_ACTIVE_HIGH 0x10 /* RTS Active is HIGH, + * default = 0 (low) */ +#define MCS7840_DEV_DCR0_RTS_AUTO 0x20 /* RTS is controlled by + * state of TX buffer, + * default = 0 + * (controlled by MCR) */ +#define MCS7840_DEV_DCR0_IRDA 0x40 /* IrDA mode */ +#define MCS7840_DEV_DCR0_RESERVED2 0x80 /* Unused */ + +/* Bits of DCR1 registers, documented in datasheet */ +#define MCS7840_DEV_DCR1_GPIO_CURRENT_MASK 0x03 /* Mask to extract GPIO + * current value, WORKS + * ONLY FOR PORT 1 */ +#define MCS7840_DEV_DCR1_GPIO_CURRENT_6MA 0x00 /* GPIO output current + * 6mA, WORKS ONLY FOR + * PORT 1 */ +#define MCS7840_DEV_DCR1_GPIO_CURRENT_8MA 0x01 /* GPIO output current + * 8mA, defauilt, WORKS + * ONLY FOR PORT 1 */ +#define MCS7840_DEV_DCR1_GPIO_CURRENT_10MA 0x02 /* GPIO output current + * 10mA, WORKS ONLY FOR + * PORT 1 */ +#define MCS7840_DEV_DCR1_GPIO_CURRENT_12MA 0x03 /* GPIO output current + * 12mA, WORKS ONLY FOR + * PORT 1 */ +#define MCS7840_DEV_DCR1_UART_CURRENT_MASK 0x0c /* Mask to extract UART + * signals current value */ +#define MCS7840_DEV_DCR1_UART_CURRENT_6MA 0x00 /* UART output current + * 6mA */ +#define MCS7840_DEV_DCR1_UART_CURRENT_8MA 0x04 /* UART output current + * 8mA, defauilt */ +#define MCS7840_DEV_DCR1_UART_CURRENT_10MA 0x08 /* UART output current + * 10mA */ +#define MCS7840_DEV_DCR1_UART_CURRENT_12MA 0x0c /* UART output current + * 12mA */ +#define MCS7840_DEV_DCR1_WAKEUP_DISABLE 0x10 /* Disable Remote USB + * Wakeup */ +#define MCS7840_DEV_DCR1_PLLPWRDOWN_DISABLE 0x20 /* Disable PLL power + * down when not needed, + * WORKS ONLY FOR PORT 1 */ +#define MCS7840_DEV_DCR1_LONG_INTERRUPT 0x40 /* Enable 13 bytes of + * interrupt data, with + * FIFO statistics, + * WORKS ONLY FOR PORT 1 */ +#define MCS7840_DEV_DCR1_RESERVED1 0x80 /* Unused */ + +/* + * Bits of DCR2 registers, documented in datasheet + * Wakeup will work only if DCR0_IRDA = 0 (RS-xxx mode) and + * DCR1_WAKEUP_DISABLE = 0 (wakeup enabled). + */ +#define MCS7840_DEV_DCR2_WAKEUP_CTS 0x01 /* Wakeup on CTS change, + * default = 0 */ +#define MCS7840_DEV_DCR2_WAKEUP_DCD 0x02 /* Wakeup on DCD change, + * default = 0 */ +#define MCS7840_DEV_DCR2_WAKEUP_RI 0x04 /* Wakeup on RI change, + * default = 1 */ +#define MCS7840_DEV_DCR2_WAKEUP_DSR 0x08 /* Wakeup on DSR change, + * default = 0 */ +#define MCS7840_DEV_DCR2_WAKEUP_RXD 0x10 /* Wakeup on RX Data change, + * default = 0 */ +#define MCS7840_DEV_DCR2_WAKEUP_RESUME 0x20 /* Wakeup issues RESUME + * signal, DISCONNECT + * otherwise, default = 1 */ +#define MCS7840_DEV_DCR2_RESERVED1 0x40 /* Unused */ +#define MCS7840_DEV_DCR2_SHDN_POLARITY 0x80 /* 0: Pin 12 Active Low, 1: + * Pin 12 Active High, default + * = 0 */ + +/* Interrupt endpoint bytes & bits */ +#define MCS7840_IEP_FIFO_STATUS_INDEX 5 +/* + * Thesse can be calculated as "1 << portnumber" for Bulk-out and + * "1 << (portnumber+1)" for Bulk-in + */ +#define MCS7840_IEP_BO_PORT1_HASDATA 0x01 +#define MCS7840_IEP_BI_PORT1_HASDATA 0x02 +#define MCS7840_IEP_BO_PORT2_HASDATA 0x04 +#define MCS7840_IEP_BI_PORT2_HASDATA 0x08 +#define MCS7840_IEP_BO_PORT3_HASDATA 0x10 +#define MCS7840_IEP_BI_PORT3_HASDATA 0x20 +#define MCS7840_IEP_BO_PORT4_HASDATA 0x40 +#define MCS7840_IEP_BI_PORT4_HASDATA 0x80 + +/* Documented UART registers (fully compatible with 16550 UART) */ +#define MCS7840_UART_REG_THR 0x00 /* Transmitter Holding + * Register W/Only */ +#define MCS7840_UART_REG_RHR 0x00 /* Receiver Holding Register + * R/Only */ +#define MCS7840_UART_REG_IER 0x01 /* Interrupt enable register - + * R/W */ +#define MCS7840_UART_REG_FCR 0x02 /* FIFO Control register - + * W/Only */ +#define MCS7840_UART_REG_ISR 0x02 /* Interrupt Status Registter + * R/Only */ +#define MCS7840_UART_REG_LCR 0x03 /* Line control register R/W */ +#define MCS7840_UART_REG_MCR 0x04 /* Modem control register R/W */ +#define MCS7840_UART_REG_LSR 0x05 /* Line status register R/Only */ +#define MCS7840_UART_REG_MSR 0x06 /* Modem status register + * R/Only */ +#define MCS7840_UART_REG_SCRATCHPAD 0x07 /* Scratch pad register */ + +#define MCS7840_UART_REG_DLL 0x00 /* Low bits of BAUD divider */ +#define MCS7840_UART_REG_DLM 0x01 /* High bits of BAUD divider */ + +/* IER bits */ +#define MCS7840_UART_IER_RXREADY 0x01 /* RX Ready interrumpt mask */ +#define MCS7840_UART_IER_TXREADY 0x02 /* TX Ready interrumpt mask */ +#define MCS7840_UART_IER_RXSTAT 0x04 /* RX Status interrumpt mask */ +#define MCS7840_UART_IER_MODEM 0x08 /* Modem status change + * interrumpt mask */ +#define MCS7840_UART_IER_SLEEP 0x10 /* SLEEP enable */ + +/* FCR bits */ +#define MCS7840_UART_FCR_ENABLE 0x01 /* Enable FIFO */ +#define MCS7840_UART_FCR_FLUSHRHR 0x02 /* Flush RHR and FIFO */ +#define MCS7840_UART_FCR_FLUSHTHR 0x04 /* Flush THR and FIFO */ +#define MCS7840_UART_FCR_RTLMASK 0xa0 /* Mask to select RHR + * Interrupt Trigger level */ +#define MCS7840_UART_FCR_RTL_1_1 0x00 /* L1 = 1, L2 = 1 */ +#define MCS7840_UART_FCR_RTL_1_4 0x40 /* L1 = 1, L2 = 4 */ +#define MCS7840_UART_FCR_RTL_1_8 0x80 /* L1 = 1, L2 = 8 */ +#define MCS7840_UART_FCR_RTL_1_14 0xa0 /* L1 = 1, L2 = 14 */ + +/* ISR bits */ +#define MCS7840_UART_ISR_NOPENDING 0x01 /* No interrupt pending */ +#define MCS7840_UART_ISR_INTMASK 0x3f /* Mask to select interrupt + * source */ +#define MCS7840_UART_ISR_RXERR 0x06 /* Recevir error */ +#define MCS7840_UART_ISR_RXHASDATA 0x04 /* Recevier has data */ +#define MCS7840_UART_ISR_RXTIMEOUT 0x0c /* Recevier timeout */ +#define MCS7840_UART_ISR_TXEMPTY 0x02 /* Transmitter empty */ +#define MCS7840_UART_ISR_MSCHANGE 0x00 /* Modem status change */ + +/* LCR bits */ +#define MCS7840_UART_LCR_DATALENMASK 0x03 /* Mask for data length */ +#define MCS7840_UART_LCR_DATALEN5 0x00 /* 5 data bits */ +#define MCS7840_UART_LCR_DATALEN6 0x01 /* 6 data bits */ +#define MCS7840_UART_LCR_DATALEN7 0x02 /* 7 data bits */ +#define MCS7840_UART_LCR_DATALEN8 0x03 /* 8 data bits */ + +#define MCS7840_UART_LCR_STOPBMASK 0x04 /* Mask for stop bits */ +#define MCS7840_UART_LCR_STOPB1 0x00 /* 1 stop bit in any case */ +#define MCS7840_UART_LCR_STOPB2 0x04 /* 1.5-2 stop bits depends on + * data length */ + +#define MCS7840_UART_LCR_PARITYMASK 0x38 /* Mask for all parity data */ +#define MCS7840_UART_LCR_PARITYON 0x08 /* Parity ON/OFF - ON */ +#define MCS7840_UART_LCR_PARITYODD 0x00 /* Parity Odd */ +#define MCS7840_UART_LCR_PARITYEVEN 0x10 /* Parity Even */ +#define MCS7840_UART_LCR_PARITYODD 0x00 /* Parity Odd */ +#define MCS7840_UART_LCR_PARITYFORCE 0x20 /* Force parity odd/even */ + +#define MCS7840_UART_LCR_BREAK 0x40 /* Send BREAK */ +#define MCS7840_UART_LCR_DIVISORS 0x80 /* Map DLL/DLM instead of + * xHR/IER */ + +/* LSR bits */ +#define MCS7840_UART_LSR_RHRAVAIL 0x01 /* Data available for read */ +#define MCS7840_UART_LSR_RHROVERRUN 0x02 /* Data FIFO/register overflow */ +#define MCS7840_UART_LSR_PARITYERR 0x04 /* Parity error */ +#define MCS7840_UART_LSR_FRAMEERR 0x10 /* Framing error */ +#define MCS7840_UART_LSR_BREAKERR 0x20 /* BREAK signal received */ +#define MCS7840_UART_LSR_THREMPTY 0x40 /* THR register is empty, + * ready for transmit */ +#define MCS7840_UART_LSR_HASERR 0x80 /* Has error in receiver FIFO */ + +/* MCR bits */ +#define MCS7840_UART_MCR_DTR 0x01 /* Force DTR to be active + * (low) */ +#define MCS7840_UART_MCR_RTS 0x02 /* Force RTS to be active + * (low) */ +#define MCS7840_UART_MCR_IE 0x04 /* Enable interrupts (from + * code, not documented) */ +#define MCS7840_UART_MCR_LOOPBACK 0x10 /* Enable local loopback test + * mode */ +#define MCS7840_UART_MCR_CTSRTS 0x20 /* Enable CTS/RTS flow control + * in 550 (FIFO) mode */ +#define MCS7840_UART_MCR_DTRDSR 0x40 /* Enable DTR/DSR flow control + * in 550 (FIFO) mode */ +#define MCS7840_UART_MCR_DCD 0x80 /* Enable DCD flow control in + * 550 (FIFO) mode */ + +/* MSR bits */ +#define MCS7840_UART_MSR_DELTACTS 0x01 /* CTS was changed since last + * read */ +#define MCS7840_UART_MSR_DELTADSR 0x02 /* DSR was changed since last + * read */ +#define MCS7840_UART_MSR_DELTARI 0x04 /* RI was changed from low to + * high since last read */ +#define MCS7840_UART_MSR_DELTADCD 0x08 /* DCD was changed since last + * read */ +#define MCS7840_UART_MSR_NEGCTS 0x10 /* Negated CTS signal */ +#define MCS7840_UART_MSR_NEGDSR 0x20 /* Negated DSR signal */ +#define MCS7840_UART_MSR_NEGRI 0x40 /* Negated RI signal */ +#define MCS7840_UART_MSR_NEGDCD 0x80 /* Negated DCD signal */ + +/* SCRATCHPAD bits */ +#define MCS7840_UART_SCRATCHPAD_RS232 0x00 /* RS-485 disabled */ +#define MCS7840_UART_SCRATCHPAD_RS485_DTRRX 0x80 /* RS-485 mode, DTR High + * = RX */ +#define MCS7840_UART_SCRATCHPAD_RS485_DTRTX 0xc0 /* RS-485 mode, DTR High + * = TX */ + +#define MCS7840_CONFIG_INDEX 0 +#define MCS7840_IFACE_INDEX 0 + +#endif diff --git a/freebsd/sys/dev/usb/serial/umct.c b/freebsd/sys/dev/usb/serial/umct.c new file mode 100644 index 00000000..6b41fa94 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/umct.c @@ -0,0 +1,683 @@ +#include <machine/rtems-bsd-kernel-space.h> + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2003 Scott Long + * 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. + * 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. + * + */ + +/* + * Driver for the MCT (Magic Control Technology) USB-RS232 Converter. + * Based on the superb documentation from the linux mct_u232 driver by + * Wolfgang Grandeggar <wolfgang@cec.ch>. + * This device smells a lot like the Belkin F5U103, except that it has + * suffered some mild brain-damage. This driver is based off of the ubsa.c + * driver from Alexander Kabaev <kan@FreeBSD.org>. Merging the two together + * might be useful, though the subtle differences might lead to lots of + * #ifdef's. + */ + +/* + * NOTE: all function names beginning like "umct_cfg_" can only + * be called from within the config thread function ! + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR usb_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +/* The UMCT advertises the standard 8250 UART registers */ +#define UMCT_GET_MSR 2 /* Get Modem Status Register */ +#define UMCT_GET_MSR_SIZE 1 +#define UMCT_GET_LCR 6 /* Get Line Control Register */ +#define UMCT_GET_LCR_SIZE 1 +#define UMCT_SET_BAUD 5 /* Set the Baud Rate Divisor */ +#define UMCT_SET_BAUD_SIZE 4 +#define UMCT_SET_LCR 7 /* Set Line Control Register */ +#define UMCT_SET_LCR_SIZE 1 +#define UMCT_SET_MCR 10 /* Set Modem Control Register */ +#define UMCT_SET_MCR_SIZE 1 + +#define UMCT_MSR_CTS_CHG 0x01 +#define UMCT_MSR_DSR_CHG 0x02 +#define UMCT_MSR_RI_CHG 0x04 +#define UMCT_MSR_CD_CHG 0x08 +#define UMCT_MSR_CTS 0x10 +#define UMCT_MSR_RTS 0x20 +#define UMCT_MSR_RI 0x40 +#define UMCT_MSR_CD 0x80 + +#define UMCT_INTR_INTERVAL 100 +#define UMCT_IFACE_INDEX 0 +#define UMCT_CONFIG_INDEX 0 + +enum { + UMCT_BULK_DT_WR, + UMCT_BULK_DT_RD, + UMCT_INTR_DT_RD, + UMCT_N_TRANSFER, +}; + +struct umct_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_device *sc_udev; + struct usb_xfer *sc_xfer[UMCT_N_TRANSFER]; + struct mtx sc_mtx; + + uint32_t sc_unit; + + uint16_t sc_obufsize; + + uint8_t sc_lsr; + uint8_t sc_msr; + uint8_t sc_lcr; + uint8_t sc_mcr; + uint8_t sc_iface_no; + uint8_t sc_swap_cb; +}; + +/* prototypes */ + +static device_probe_t umct_probe; +static device_attach_t umct_attach; +static device_detach_t umct_detach; +static void umct_free_softc(struct umct_softc *); + +static usb_callback_t umct_intr_callback; +static usb_callback_t umct_intr_callback_sub; +static usb_callback_t umct_read_callback; +static usb_callback_t umct_read_callback_sub; +static usb_callback_t umct_write_callback; + +static void umct_cfg_do_request(struct umct_softc *sc, uint8_t request, + uint16_t len, uint32_t value); +static void umct_free(struct ucom_softc *); +static void umct_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void umct_cfg_set_break(struct ucom_softc *, uint8_t); +static void umct_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void umct_cfg_set_rts(struct ucom_softc *, uint8_t); +static uint8_t umct_calc_baud(uint32_t); +static int umct_pre_param(struct ucom_softc *, struct termios *); +static void umct_cfg_param(struct ucom_softc *, struct termios *); +static void umct_start_read(struct ucom_softc *); +static void umct_stop_read(struct ucom_softc *); +static void umct_start_write(struct ucom_softc *); +static void umct_stop_write(struct ucom_softc *); +static void umct_poll(struct ucom_softc *ucom); + +static const struct usb_config umct_config[UMCT_N_TRANSFER] = { + + [UMCT_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = 0, /* use wMaxPacketSize */ + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &umct_write_callback, + }, + + [UMCT_BULK_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umct_read_callback, + .ep_index = 0, /* first interrupt endpoint */ + }, + + [UMCT_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umct_intr_callback, + .ep_index = 1, /* second interrupt endpoint */ + }, +}; + +static const struct ucom_callback umct_callback = { + .ucom_cfg_get_status = &umct_cfg_get_status, + .ucom_cfg_set_dtr = &umct_cfg_set_dtr, + .ucom_cfg_set_rts = &umct_cfg_set_rts, + .ucom_cfg_set_break = &umct_cfg_set_break, + .ucom_cfg_param = &umct_cfg_param, + .ucom_pre_param = &umct_pre_param, + .ucom_start_read = &umct_start_read, + .ucom_stop_read = &umct_stop_read, + .ucom_start_write = &umct_start_write, + .ucom_stop_write = &umct_stop_write, + .ucom_poll = &umct_poll, + .ucom_free = &umct_free, +}; + +static const STRUCT_USB_HOST_ID umct_devs[] = { + {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_USB232, 0)}, + {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_SITECOM_USB232, 0)}, + {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_DU_H3SP_USB232, 0)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U109, 0)}, + {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U409, 0)}, +}; + +static device_method_t umct_methods[] = { + DEVMETHOD(device_probe, umct_probe), + DEVMETHOD(device_attach, umct_attach), + DEVMETHOD(device_detach, umct_detach), + DEVMETHOD_END +}; + +static devclass_t umct_devclass; + +static driver_t umct_driver = { + .name = "umct", + .methods = umct_methods, + .size = sizeof(struct umct_softc), +}; + +DRIVER_MODULE(umct, uhub, umct_driver, umct_devclass, NULL, 0); +MODULE_DEPEND(umct, ucom, 1, 1, 1); +MODULE_DEPEND(umct, usb, 1, 1, 1); +MODULE_VERSION(umct, 1); +USB_PNP_HOST_INFO(umct_devs); + +static int +umct_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UMCT_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UMCT_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(umct_devs, sizeof(umct_devs), uaa)); +} + +static int +umct_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umct_softc *sc = device_get_softc(dev); + int32_t error; + uint16_t maxp; + uint8_t iface_index; + + sc->sc_udev = uaa->device; + sc->sc_unit = device_get_unit(dev); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "umct", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + sc->sc_iface_no = uaa->info.bIfaceNum; + + iface_index = UMCT_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, umct_config, UMCT_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + device_printf(dev, "allocating USB " + "transfers failed\n"); + goto detach; + } + + /* + * The real bulk-in endpoint is also marked as an interrupt. + * The only way to differentiate it from the real interrupt + * endpoint is to look at the wMaxPacketSize field. + */ + maxp = usbd_xfer_max_framelen(sc->sc_xfer[UMCT_BULK_DT_RD]); + if (maxp == 0x2) { + + /* guessed wrong - switch around endpoints */ + + struct usb_xfer *temp = sc->sc_xfer[UMCT_INTR_DT_RD]; + + sc->sc_xfer[UMCT_INTR_DT_RD] = sc->sc_xfer[UMCT_BULK_DT_RD]; + sc->sc_xfer[UMCT_BULK_DT_RD] = temp; + sc->sc_swap_cb = 1; + } + + sc->sc_obufsize = usbd_xfer_max_len(sc->sc_xfer[UMCT_BULK_DT_WR]); + + if (uaa->info.idProduct == USB_PRODUCT_MCT_SITECOM_USB232) { + if (sc->sc_obufsize > 16) { + sc->sc_obufsize = 16; + } + } + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &umct_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); /* success */ + +detach: + umct_detach(dev); + return (ENXIO); /* failure */ +} + +static int +umct_detach(device_t dev) +{ + struct umct_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UMCT_N_TRANSFER); + + device_claim_softc(dev); + + umct_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(umct); + +static void +umct_free_softc(struct umct_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +umct_free(struct ucom_softc *ucom) +{ + umct_free_softc(ucom->sc_parent); +} + +static void +umct_cfg_do_request(struct umct_softc *sc, uint8_t request, + uint16_t len, uint32_t value) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t temp[4]; + + if (len > 4) + len = 4; + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = request; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, len); + USETDW(temp, value); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, temp, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } + return; +} + +static void +umct_intr_callback_sub(struct usb_xfer *xfer, usb_error_t error) +{ + struct umct_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[2]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen < 2) { + DPRINTF("too short message\n"); + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, sizeof(buf)); + + /* + * MSR bits need translation from ns16550 to SER_* values. + * LSR bits are ns16550 in hardware and ucom. + */ + sc->sc_msr = 0; + if (buf[0] & UMCT_MSR_CTS) + sc->sc_msr |= SER_CTS; + if (buf[0] & UMCT_MSR_CD) + sc->sc_msr |= SER_DCD; + if (buf[0] & UMCT_MSR_RI) + sc->sc_msr |= SER_RI; + if (buf[0] & UMCT_MSR_RTS) + sc->sc_msr |= SER_DSR; + sc->sc_lsr = buf[1]; + + ucom_status_change(&sc->sc_ucom); + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umct_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct umct_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +umct_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umct_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_lcr |= 0x40; + else + sc->sc_lcr &= ~0x40; + + umct_cfg_do_request(sc, UMCT_SET_LCR, UMCT_SET_LCR_SIZE, sc->sc_lcr); +} + +static void +umct_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umct_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= 0x01; + else + sc->sc_mcr &= ~0x01; + + umct_cfg_do_request(sc, UMCT_SET_MCR, UMCT_SET_MCR_SIZE, sc->sc_mcr); +} + +static void +umct_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umct_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= 0x02; + else + sc->sc_mcr &= ~0x02; + + umct_cfg_do_request(sc, UMCT_SET_MCR, UMCT_SET_MCR_SIZE, sc->sc_mcr); +} + +static uint8_t +umct_calc_baud(uint32_t baud) +{ + switch (baud) { + case B300:return (0x1); + case B600: + return (0x2); + case B1200: + return (0x3); + case B2400: + return (0x4); + case B4800: + return (0x6); + case B9600: + return (0x8); + case B19200: + return (0x9); + case B38400: + return (0xa); + case B57600: + return (0xb); + case 115200: + return (0xc); + case B0: + default: + break; + } + return (0x0); +} + +static int +umct_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + return (0); /* we accept anything */ +} + +static void +umct_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct umct_softc *sc = ucom->sc_parent; + uint32_t value; + + value = umct_calc_baud(t->c_ospeed); + umct_cfg_do_request(sc, UMCT_SET_BAUD, UMCT_SET_BAUD_SIZE, value); + + value = (sc->sc_lcr & 0x40); + + switch (t->c_cflag & CSIZE) { + case CS5: + value |= 0x0; + break; + case CS6: + value |= 0x1; + break; + case CS7: + value |= 0x2; + break; + default: + case CS8: + value |= 0x3; + break; + } + + value |= (t->c_cflag & CSTOPB) ? 0x4 : 0; + if (t->c_cflag & PARENB) { + value |= 0x8; + value |= (t->c_cflag & PARODD) ? 0x0 : 0x10; + } + /* + * XXX There doesn't seem to be a way to tell the device + * to use flow control. + */ + + sc->sc_lcr = value; + umct_cfg_do_request(sc, UMCT_SET_LCR, UMCT_SET_LCR_SIZE, value); +} + +static void +umct_start_read(struct ucom_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usbd_transfer_start(sc->sc_xfer[UMCT_INTR_DT_RD]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UMCT_BULK_DT_RD]); +} + +static void +umct_stop_read(struct ucom_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usbd_transfer_stop(sc->sc_xfer[UMCT_INTR_DT_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UMCT_BULK_DT_RD]); +} + +static void +umct_start_write(struct ucom_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UMCT_BULK_DT_WR]); +} + +static void +umct_stop_write(struct ucom_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UMCT_BULK_DT_WR]); +} + +static void +umct_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umct_softc *sc = usbd_xfer_softc(xfer); + + if (sc->sc_swap_cb) + umct_intr_callback_sub(xfer, error); + else + umct_read_callback_sub(xfer, error); +} + +static void +umct_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umct_softc *sc = usbd_xfer_softc(xfer); + + if (sc->sc_swap_cb) + umct_read_callback_sub(xfer, error); + else + umct_intr_callback_sub(xfer, error); +} + +static void +umct_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umct_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + sc->sc_obufsize, &actlen)) { + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umct_read_callback_sub(struct usb_xfer *xfer, usb_error_t error) +{ + struct umct_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umct_poll(struct ucom_softc *ucom) +{ + struct umct_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UMCT_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/umodem.c b/freebsd/sys/dev/usb/serial/umodem.c new file mode 100644 index 00000000..f0d14914 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/umodem.c @@ -0,0 +1,1028 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2003, M. Warner Losh <imp@FreeBSD.org>. + * 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. + * 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. + */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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 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. + */ + +/* + * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf + * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf + * http://www.usb.org/developers/devclass_docs/cdc_wmc10.zip + */ + +/* + * TODO: + * - Add error recovery in various places; the big problem is what + * to do in a callback if there is an error. + * - Implement a Call Device for modems without multiplexed commands. + * + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbhid.h> +#include <dev/usb/usb_cdc.h> +#include <rtems/bsd/local/usbdevs.h> +#include <rtems/bsd/local/usb_if.h> + +#include <dev/usb/usb_ioctl.h> + +#define USB_DEBUG_VAR umodem_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> +#include <dev/usb/quirk/usb_quirk.h> + +#include <dev/usb/serial/usb_serial.h> + +#ifdef USB_DEBUG +static int umodem_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, umodem, CTLFLAG_RW, 0, "USB umodem"); +SYSCTL_INT(_hw_usb_umodem, OID_AUTO, debug, CTLFLAG_RWTUN, + &umodem_debug, 0, "Debug level"); +#endif + +static const STRUCT_USB_DUAL_ID umodem_dual_devs[] = { + /* Generic Modem class match */ + {USB_IFACE_CLASS(UICLASS_CDC), + USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), + USB_IFACE_PROTOCOL(UIPROTO_CDC_AT)}, + {USB_IFACE_CLASS(UICLASS_CDC), + USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), + USB_IFACE_PROTOCOL(UIPROTO_CDC_NONE)}, +}; + +static const STRUCT_USB_HOST_ID umodem_host_devs[] = { + /* Huawei Modem class match */ + {USB_VENDOR(USB_VENDOR_HUAWEI),USB_IFACE_CLASS(UICLASS_CDC), + USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL), + USB_IFACE_PROTOCOL(0xFF)}, + /* Kyocera AH-K3001V */ + {USB_VPI(USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_AHK3001V, 1)}, + {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, 1)}, + {USB_VPI(USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_PC5740, 1)}, +}; + +/* + * As speeds for umodem devices increase, these numbers will need to + * be increased. They should be good for G3 speeds and below. + * + * TODO: The TTY buffers should be increased! + */ +#define UMODEM_BUF_SIZE 1024 + +enum { + UMODEM_BULK_WR, + UMODEM_BULK_RD, + UMODEM_INTR_WR, + UMODEM_INTR_RD, + UMODEM_N_TRANSFER, +}; + +#define UMODEM_MODVER 1 /* module version */ + +struct umodem_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UMODEM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_line; + + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* modem status register */ + uint8_t sc_ctrl_iface_no; + uint8_t sc_data_iface_no; + uint8_t sc_iface_index[2]; + uint8_t sc_cm_over_data; + uint8_t sc_cm_cap; /* CM capabilities */ + uint8_t sc_acm_cap; /* ACM capabilities */ + uint8_t sc_line_coding[32]; /* used in USB device mode */ + uint8_t sc_abstract_state[32]; /* used in USB device mode */ +}; + +static device_probe_t umodem_probe; +static device_attach_t umodem_attach; +static device_detach_t umodem_detach; +static usb_handle_request_t umodem_handle_request; + +static void umodem_free_softc(struct umodem_softc *); + +static usb_callback_t umodem_intr_read_callback; +static usb_callback_t umodem_intr_write_callback; +static usb_callback_t umodem_write_callback; +static usb_callback_t umodem_read_callback; + +static void umodem_free(struct ucom_softc *); +static void umodem_start_read(struct ucom_softc *); +static void umodem_stop_read(struct ucom_softc *); +static void umodem_start_write(struct ucom_softc *); +static void umodem_stop_write(struct ucom_softc *); +static void umodem_get_caps(struct usb_attach_arg *, uint8_t *, uint8_t *); +static void umodem_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static int umodem_pre_param(struct ucom_softc *, struct termios *); +static void umodem_cfg_param(struct ucom_softc *, struct termios *); +static int umodem_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, + struct thread *); +static void umodem_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void umodem_cfg_set_rts(struct ucom_softc *, uint8_t); +static void umodem_cfg_set_break(struct ucom_softc *, uint8_t); +static void *umodem_get_desc(struct usb_attach_arg *, uint8_t, uint8_t); +static usb_error_t umodem_set_comm_feature(struct usb_device *, uint8_t, + uint16_t, uint16_t); +static void umodem_poll(struct ucom_softc *ucom); +static void umodem_find_data_iface(struct usb_attach_arg *uaa, + uint8_t, uint8_t *, uint8_t *); + +static const struct usb_config umodem_config[UMODEM_N_TRANSFER] = { + + [UMODEM_BULK_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_TX, + .if_index = 0, + .bufsize = UMODEM_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &umodem_write_callback, + .usb_mode = USB_MODE_DUAL, + }, + + [UMODEM_BULK_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_RX, + .if_index = 0, + .bufsize = UMODEM_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &umodem_read_callback, + .usb_mode = USB_MODE_DUAL, + }, + + [UMODEM_INTR_WR] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_TX, + .if_index = 1, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umodem_intr_write_callback, + .usb_mode = USB_MODE_DEVICE, + }, + + [UMODEM_INTR_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_RX, + .if_index = 1, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umodem_intr_read_callback, + .usb_mode = USB_MODE_HOST, + }, +}; + +static const struct ucom_callback umodem_callback = { + .ucom_cfg_get_status = &umodem_cfg_get_status, + .ucom_cfg_set_dtr = &umodem_cfg_set_dtr, + .ucom_cfg_set_rts = &umodem_cfg_set_rts, + .ucom_cfg_set_break = &umodem_cfg_set_break, + .ucom_cfg_param = &umodem_cfg_param, + .ucom_pre_param = &umodem_pre_param, + .ucom_ioctl = &umodem_ioctl, + .ucom_start_read = &umodem_start_read, + .ucom_stop_read = &umodem_stop_read, + .ucom_start_write = &umodem_start_write, + .ucom_stop_write = &umodem_stop_write, + .ucom_poll = &umodem_poll, + .ucom_free = &umodem_free, +}; + +static device_method_t umodem_methods[] = { + /* USB interface */ + DEVMETHOD(usb_handle_request, umodem_handle_request), + + /* Device interface */ + DEVMETHOD(device_probe, umodem_probe), + DEVMETHOD(device_attach, umodem_attach), + DEVMETHOD(device_detach, umodem_detach), + DEVMETHOD_END +}; + +static devclass_t umodem_devclass; + +static driver_t umodem_driver = { + .name = "umodem", + .methods = umodem_methods, + .size = sizeof(struct umodem_softc), +}; + +DRIVER_MODULE(umodem, uhub, umodem_driver, umodem_devclass, NULL, 0); +MODULE_DEPEND(umodem, ucom, 1, 1, 1); +MODULE_DEPEND(umodem, usb, 1, 1, 1); +MODULE_VERSION(umodem, UMODEM_MODVER); +USB_PNP_DUAL_INFO(umodem_dual_devs); +USB_PNP_HOST_INFO(umodem_host_devs); + +static int +umodem_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + int error; + + DPRINTFN(11, "\n"); + + error = usbd_lookup_id_by_uaa(umodem_host_devs, + sizeof(umodem_host_devs), uaa); + if (error) { + error = usbd_lookup_id_by_uaa(umodem_dual_devs, + sizeof(umodem_dual_devs), uaa); + if (error) + return (error); + } + return (BUS_PROBE_GENERIC); +} + +static int +umodem_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umodem_softc *sc = device_get_softc(dev); + struct usb_cdc_cm_descriptor *cmd; + struct usb_cdc_union_descriptor *cud; + uint8_t i; + int error; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "umodem", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + sc->sc_ctrl_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index[1] = uaa->info.bIfaceIndex; + sc->sc_udev = uaa->device; + + umodem_get_caps(uaa, &sc->sc_cm_cap, &sc->sc_acm_cap); + + /* get the data interface number */ + + cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + + if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) { + + cud = usbd_find_descriptor(uaa->device, NULL, + uaa->info.bIfaceIndex, UDESC_CS_INTERFACE, + 0xFF, UDESCSUB_CDC_UNION, 0xFF); + + if ((cud == NULL) || (cud->bLength < sizeof(*cud))) { + DPRINTF("Missing descriptor. " + "Assuming data interface is next.\n"); + if (sc->sc_ctrl_iface_no == 0xFF) { + goto detach; + } else { + uint8_t class_match = 0; + + /* set default interface number */ + sc->sc_data_iface_no = 0xFF; + + /* try to find the data interface backwards */ + umodem_find_data_iface(uaa, + uaa->info.bIfaceIndex - 1, + &sc->sc_data_iface_no, &class_match); + + /* try to find the data interface forwards */ + umodem_find_data_iface(uaa, + uaa->info.bIfaceIndex + 1, + &sc->sc_data_iface_no, &class_match); + + /* check if nothing was found */ + if (sc->sc_data_iface_no == 0xFF) + goto detach; + } + } else { + sc->sc_data_iface_no = cud->bSlaveInterface[0]; + } + } else { + sc->sc_data_iface_no = cmd->bDataInterface; + } + + device_printf(dev, "data interface %d, has %sCM over " + "data, has %sbreak\n", + sc->sc_data_iface_no, + sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ", + sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no "); + + /* get the data interface too */ + + for (i = 0;; i++) { + struct usb_interface *iface; + struct usb_interface_descriptor *id; + + iface = usbd_get_iface(uaa->device, i); + + if (iface) { + + id = usbd_get_interface_descriptor(iface); + + if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) { + sc->sc_iface_index[0] = i; + usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); + break; + } + } else { + device_printf(dev, "no data interface\n"); + goto detach; + } + } + + if (usb_test_quirk(uaa, UQ_ASSUME_CM_OVER_DATA)) { + sc->sc_cm_over_data = 1; + } else { + if (sc->sc_cm_cap & USB_CDC_CM_OVER_DATA) { + if (sc->sc_acm_cap & USB_CDC_ACM_HAS_FEATURE) { + + error = umodem_set_comm_feature + (uaa->device, sc->sc_ctrl_iface_no, + UCDC_ABSTRACT_STATE, UCDC_DATA_MULTIPLEXED); + + /* ignore any errors */ + } + sc->sc_cm_over_data = 1; + } + } + error = usbd_transfer_setup(uaa->device, + sc->sc_iface_index, sc->sc_xfer, + umodem_config, UMODEM_N_TRANSFER, + sc, &sc->sc_mtx); + if (error) { + device_printf(dev, "Can't setup transfer\n"); + goto detach; + } + + /* clear stall at first run, if USB host mode */ + if (uaa->usb_mode == USB_MODE_HOST) { + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_RD]); + mtx_unlock(&sc->sc_mtx); + } + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &umodem_callback, &sc->sc_mtx); + if (error) { + device_printf(dev, "Can't attach com\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + umodem_detach(dev); + return (ENXIO); +} + +static void +umodem_find_data_iface(struct usb_attach_arg *uaa, + uint8_t iface_index, uint8_t *p_data_no, uint8_t *p_match_class) +{ + struct usb_interface_descriptor *id; + struct usb_interface *iface; + + iface = usbd_get_iface(uaa->device, iface_index); + + /* check for end of interfaces */ + if (iface == NULL) + return; + + id = usbd_get_interface_descriptor(iface); + + /* check for non-matching interface class */ + if (id->bInterfaceClass != UICLASS_CDC_DATA || + id->bInterfaceSubClass != UISUBCLASS_DATA) { + /* if we got a class match then return */ + if (*p_match_class) + return; + } else { + *p_match_class = 1; + } + + DPRINTFN(11, "Match at index %u\n", iface_index); + + *p_data_no = id->bInterfaceNumber; +} + +static void +umodem_start_read(struct ucom_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint, if any */ + usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_RD]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_RD]); +} + +static void +umodem_stop_read(struct ucom_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint, if any */ + usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_RD]); +} + +static void +umodem_start_write(struct ucom_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_WR]); + usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_WR]); +} + +static void +umodem_stop_write(struct ucom_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_WR]); + usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_WR]); +} + +static void +umodem_get_caps(struct usb_attach_arg *uaa, uint8_t *cm, uint8_t *acm) +{ + struct usb_cdc_cm_descriptor *cmd; + struct usb_cdc_acm_descriptor *cad; + + cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) { + DPRINTF("no CM desc (faking one)\n"); + *cm = USB_CDC_CM_DOES_CM | USB_CDC_CM_OVER_DATA; + } else + *cm = cmd->bmCapabilities; + + cad = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM); + if ((cad == NULL) || (cad->bLength < sizeof(*cad))) { + DPRINTF("no ACM desc\n"); + *acm = 0; + } else + *acm = cad->bmCapabilities; +} + +static void +umodem_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct umodem_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + /* XXX Note: sc_lsr is always zero */ + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static int +umodem_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + return (0); /* we accept anything */ +} + +static void +umodem_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb_cdc_line_state ls; + struct usb_device_request req; + + DPRINTF("sc=%p\n", sc); + + memset(&ls, 0, sizeof(ls)); + + USETDW(ls.dwDTERate, t->c_ospeed); + + ls.bCharFormat = (t->c_cflag & CSTOPB) ? + UCDC_STOP_BIT_2 : UCDC_STOP_BIT_1; + + ls.bParityType = (t->c_cflag & PARENB) ? + ((t->c_cflag & PARODD) ? + UCDC_PARITY_ODD : UCDC_PARITY_EVEN) : UCDC_PARITY_NONE; + + switch (t->c_cflag & CSIZE) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n", + UGETDW(ls.dwDTERate), ls.bCharFormat, + ls.bParityType, ls.bDataBits); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof(ls)); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &ls, 0, 1000); +} + +static int +umodem_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, + int flag, struct thread *td) +{ + struct umodem_softc *sc = ucom->sc_parent; + int error = 0; + + DPRINTF("cmd=0x%08x\n", cmd); + + switch (cmd) { + case USB_GET_CM_OVER_DATA: + *(int *)data = sc->sc_cm_over_data; + break; + + case USB_SET_CM_OVER_DATA: + if (*(int *)data != sc->sc_cm_over_data) { + /* XXX change it */ + } + break; + + default: + DPRINTF("unknown\n"); + error = ENOIOCTL; + break; + } + + return (error); +} + +static void +umodem_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +umodem_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff=%d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +umodem_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umodem_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t temp; + + DPRINTF("onoff=%d\n", onoff); + + if (sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK) { + + temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, temp); + req.wIndex[0] = sc->sc_ctrl_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + } +} + +static void +umodem_intr_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("Transferred %d bytes\n", actlen); + + /* FALLTHROUGH */ + case USB_ST_SETUP: +tr_setup: + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* start clear stall */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +umodem_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_cdc_notification pkt; + struct umodem_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint16_t wLen; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen < 8) { + DPRINTF("received short packet, " + "%d bytes\n", actlen); + goto tr_setup; + } + if (actlen > (int)sizeof(pkt)) { + DPRINTF("truncating message\n"); + actlen = sizeof(pkt); + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &pkt, actlen); + + actlen -= 8; + + wLen = UGETW(pkt.wLength); + if (actlen > wLen) { + actlen = wLen; + } + if (pkt.bmRequestType != UCDC_NOTIFICATION) { + DPRINTF("unknown message type, " + "0x%02x, on notify pipe!\n", + pkt.bmRequestType); + goto tr_setup; + } + switch (pkt.bNotification) { + case UCDC_N_SERIAL_STATE: + /* + * Set the serial state in ucom driver based on + * the bits from the notify message + */ + if (actlen < 2) { + DPRINTF("invalid notification " + "length, %d bytes!\n", actlen); + break; + } + DPRINTF("notify bytes = %02x%02x\n", + pkt.data[0], + pkt.data[1]); + + /* Currently, lsr is always zero. */ + sc->sc_lsr = 0; + sc->sc_msr = 0; + + if (pkt.data[0] & UCDC_N_SERIAL_RI) { + sc->sc_msr |= SER_RI; + } + if (pkt.data[0] & UCDC_N_SERIAL_DSR) { + sc->sc_msr |= SER_DSR; + } + if (pkt.data[0] & UCDC_N_SERIAL_DCD) { + sc->sc_msr |= SER_DCD; + } + ucom_status_change(&sc->sc_ucom); + break; + + default: + DPRINTF("unknown notify message: 0x%02x\n", + pkt.bNotification); + break; + } + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + + } +} + +static void +umodem_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umodem_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UMODEM_BUF_SIZE, &actlen)) { + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umodem_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umodem_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("actlen=%d\n", actlen); + + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void * +umodem_get_desc(struct usb_attach_arg *uaa, uint8_t type, uint8_t subtype) +{ + return (usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex, + type, 0xFF, subtype, 0xFF)); +} + +static usb_error_t +umodem_set_comm_feature(struct usb_device *udev, uint8_t iface_no, + uint16_t feature, uint16_t state) +{ + struct usb_device_request req; + struct usb_cdc_abstract_state ast; + + DPRINTF("feature=%d state=%d\n", + feature, state); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_COMM_FEATURE; + USETW(req.wValue, feature); + req.wIndex[0] = iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UCDC_ABSTRACT_STATE_LENGTH); + USETW(ast.wState, state); + + return (usbd_do_request(udev, NULL, &req, &ast)); +} + +static int +umodem_detach(device_t dev) +{ + struct umodem_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UMODEM_N_TRANSFER); + + device_claim_softc(dev); + + umodem_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(umodem); + +static void +umodem_free_softc(struct umodem_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +umodem_free(struct ucom_softc *ucom) +{ + umodem_free_softc(ucom->sc_parent); +} + +static void +umodem_poll(struct ucom_softc *ucom) +{ + struct umodem_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UMODEM_N_TRANSFER); +} + +static int +umodem_handle_request(device_t dev, + const void *preq, void **pptr, uint16_t *plen, + uint16_t offset, uint8_t *pstate) +{ + struct umodem_softc *sc = device_get_softc(dev); + const struct usb_device_request *req = preq; + uint8_t is_complete = *pstate; + + DPRINTF("sc=%p\n", sc); + + if (!is_complete) { + if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && + (req->bRequest == UCDC_SET_LINE_CODING) && + (req->wIndex[0] == sc->sc_ctrl_iface_no) && + (req->wIndex[1] == 0x00) && + (req->wValue[0] == 0x00) && + (req->wValue[1] == 0x00)) { + if (offset == 0) { + *plen = sizeof(sc->sc_line_coding); + *pptr = &sc->sc_line_coding; + } else { + *plen = 0; + } + return (0); + } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && + (req->wIndex[0] == sc->sc_ctrl_iface_no) && + (req->wIndex[1] == 0x00) && + (req->bRequest == UCDC_SET_COMM_FEATURE)) { + if (offset == 0) { + *plen = sizeof(sc->sc_abstract_state); + *pptr = &sc->sc_abstract_state; + } else { + *plen = 0; + } + return (0); + } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && + (req->wIndex[0] == sc->sc_ctrl_iface_no) && + (req->wIndex[1] == 0x00) && + (req->bRequest == UCDC_SET_CONTROL_LINE_STATE)) { + *plen = 0; + return (0); + } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) && + (req->wIndex[0] == sc->sc_ctrl_iface_no) && + (req->wIndex[1] == 0x00) && + (req->bRequest == UCDC_SEND_BREAK)) { + *plen = 0; + return (0); + } + } + return (ENXIO); /* use builtin handler */ +} diff --git a/freebsd/sys/dev/usb/serial/umoscom.c b/freebsd/sys/dev/usb/serial/umoscom.c new file mode 100644 index 00000000..87a47c15 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/umoscom.c @@ -0,0 +1,736 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $FreeBSD$ */ +/* $OpenBSD: umoscom.c,v 1.2 2006/10/26 06:02:43 jsg Exp $ */ + +/* + * Copyright (c) 2006 Jonathan Gray <jsg@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. + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR umoscom_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#ifdef USB_DEBUG +static int umoscom_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, umoscom, CTLFLAG_RW, 0, "USB umoscom"); +SYSCTL_INT(_hw_usb_umoscom, OID_AUTO, debug, CTLFLAG_RWTUN, + &umoscom_debug, 0, "Debug level"); +#endif + +#define UMOSCOM_BUFSIZE 1024 /* bytes */ + +#define UMOSCOM_CONFIG_INDEX 0 +#define UMOSCOM_IFACE_INDEX 0 + +/* interrupt packet */ +#define UMOSCOM_IIR_RLS 0x06 +#define UMOSCOM_IIR_RDA 0x04 +#define UMOSCOM_IIR_CTI 0x0c +#define UMOSCOM_IIR_THR 0x02 +#define UMOSCOM_IIR_MS 0x00 + +/* registers */ +#define UMOSCOM_READ 0x0d +#define UMOSCOM_WRITE 0x0e +#define UMOSCOM_UART_REG 0x0300 +#define UMOSCOM_VEND_REG 0x0000 + +#define UMOSCOM_TXBUF 0x00 /* Write */ +#define UMOSCOM_RXBUF 0x00 /* Read */ +#define UMOSCOM_INT 0x01 +#define UMOSCOM_FIFO 0x02 /* Write */ +#define UMOSCOM_ISR 0x02 /* Read */ +#define UMOSCOM_LCR 0x03 +#define UMOSCOM_MCR 0x04 +#define UMOSCOM_LSR 0x05 +#define UMOSCOM_MSR 0x06 +#define UMOSCOM_SCRATCH 0x07 +#define UMOSCOM_DIV_LO 0x08 +#define UMOSCOM_DIV_HI 0x09 +#define UMOSCOM_EFR 0x0a +#define UMOSCOM_XON1 0x0b +#define UMOSCOM_XON2 0x0c +#define UMOSCOM_XOFF1 0x0d +#define UMOSCOM_XOFF2 0x0e + +#define UMOSCOM_BAUDLO 0x00 +#define UMOSCOM_BAUDHI 0x01 + +#define UMOSCOM_INT_RXEN 0x01 +#define UMOSCOM_INT_TXEN 0x02 +#define UMOSCOM_INT_RSEN 0x04 +#define UMOSCOM_INT_MDMEM 0x08 +#define UMOSCOM_INT_SLEEP 0x10 +#define UMOSCOM_INT_XOFF 0x20 +#define UMOSCOM_INT_RTS 0x40 + +#define UMOSCOM_FIFO_EN 0x01 +#define UMOSCOM_FIFO_RXCLR 0x02 +#define UMOSCOM_FIFO_TXCLR 0x04 +#define UMOSCOM_FIFO_DMA_BLK 0x08 +#define UMOSCOM_FIFO_TXLVL_MASK 0x30 +#define UMOSCOM_FIFO_TXLVL_8 0x00 +#define UMOSCOM_FIFO_TXLVL_16 0x10 +#define UMOSCOM_FIFO_TXLVL_32 0x20 +#define UMOSCOM_FIFO_TXLVL_56 0x30 +#define UMOSCOM_FIFO_RXLVL_MASK 0xc0 +#define UMOSCOM_FIFO_RXLVL_8 0x00 +#define UMOSCOM_FIFO_RXLVL_16 0x40 +#define UMOSCOM_FIFO_RXLVL_56 0x80 +#define UMOSCOM_FIFO_RXLVL_80 0xc0 + +#define UMOSCOM_ISR_MDM 0x00 +#define UMOSCOM_ISR_NONE 0x01 +#define UMOSCOM_ISR_TX 0x02 +#define UMOSCOM_ISR_RX 0x04 +#define UMOSCOM_ISR_LINE 0x06 +#define UMOSCOM_ISR_RXTIMEOUT 0x0c +#define UMOSCOM_ISR_RX_XOFF 0x10 +#define UMOSCOM_ISR_RTSCTS 0x20 +#define UMOSCOM_ISR_FIFOEN 0xc0 + +#define UMOSCOM_LCR_DBITS(x) ((x) - 5) +#define UMOSCOM_LCR_STOP_BITS_1 0x00 +#define UMOSCOM_LCR_STOP_BITS_2 0x04 /* 2 if 6-8 bits/char or 1.5 if 5 */ +#define UMOSCOM_LCR_PARITY_NONE 0x00 +#define UMOSCOM_LCR_PARITY_ODD 0x08 +#define UMOSCOM_LCR_PARITY_EVEN 0x18 +#define UMOSCOM_LCR_BREAK 0x40 +#define UMOSCOM_LCR_DIVLATCH_EN 0x80 + +#define UMOSCOM_MCR_DTR 0x01 +#define UMOSCOM_MCR_RTS 0x02 +#define UMOSCOM_MCR_LOOP 0x04 +#define UMOSCOM_MCR_INTEN 0x08 +#define UMOSCOM_MCR_LOOPBACK 0x10 +#define UMOSCOM_MCR_XONANY 0x20 +#define UMOSCOM_MCR_IRDA_EN 0x40 +#define UMOSCOM_MCR_BAUD_DIV4 0x80 + +#define UMOSCOM_LSR_RXDATA 0x01 +#define UMOSCOM_LSR_RXOVER 0x02 +#define UMOSCOM_LSR_RXPAR_ERR 0x04 +#define UMOSCOM_LSR_RXFRM_ERR 0x08 +#define UMOSCOM_LSR_RXBREAK 0x10 +#define UMOSCOM_LSR_TXEMPTY 0x20 +#define UMOSCOM_LSR_TXALLEMPTY 0x40 +#define UMOSCOM_LSR_TXFIFO_ERR 0x80 + +#define UMOSCOM_MSR_CTS_CHG 0x01 +#define UMOSCOM_MSR_DSR_CHG 0x02 +#define UMOSCOM_MSR_RI_CHG 0x04 +#define UMOSCOM_MSR_CD_CHG 0x08 +#define UMOSCOM_MSR_CTS 0x10 +#define UMOSCOM_MSR_RTS 0x20 +#define UMOSCOM_MSR_RI 0x40 +#define UMOSCOM_MSR_CD 0x80 + +#define UMOSCOM_BAUD_REF 115200 + +enum { + UMOSCOM_BULK_DT_WR, + UMOSCOM_BULK_DT_RD, + UMOSCOM_INTR_DT_RD, + UMOSCOM_N_TRANSFER, +}; + +struct umoscom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UMOSCOM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_mcr; + uint8_t sc_lcr; +}; + +/* prototypes */ + +static device_probe_t umoscom_probe; +static device_attach_t umoscom_attach; +static device_detach_t umoscom_detach; +static void umoscom_free_softc(struct umoscom_softc *); + +static usb_callback_t umoscom_write_callback; +static usb_callback_t umoscom_read_callback; +static usb_callback_t umoscom_intr_callback; + +static void umoscom_free(struct ucom_softc *); +static void umoscom_cfg_open(struct ucom_softc *); +static void umoscom_cfg_close(struct ucom_softc *); +static void umoscom_cfg_set_break(struct ucom_softc *, uint8_t); +static void umoscom_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void umoscom_cfg_set_rts(struct ucom_softc *, uint8_t); +static int umoscom_pre_param(struct ucom_softc *, struct termios *); +static void umoscom_cfg_param(struct ucom_softc *, struct termios *); +static void umoscom_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void umoscom_cfg_write(struct umoscom_softc *, uint16_t, uint16_t); +static uint8_t umoscom_cfg_read(struct umoscom_softc *, uint16_t); +static void umoscom_start_read(struct ucom_softc *); +static void umoscom_stop_read(struct ucom_softc *); +static void umoscom_start_write(struct ucom_softc *); +static void umoscom_stop_write(struct ucom_softc *); +static void umoscom_poll(struct ucom_softc *ucom); + +static const struct usb_config umoscom_config_data[UMOSCOM_N_TRANSFER] = { + + [UMOSCOM_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UMOSCOM_BUFSIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &umoscom_write_callback, + }, + + [UMOSCOM_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UMOSCOM_BUFSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &umoscom_read_callback, + }, + + [UMOSCOM_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &umoscom_intr_callback, + }, +}; + +static const struct ucom_callback umoscom_callback = { + /* configuration callbacks */ + .ucom_cfg_get_status = &umoscom_cfg_get_status, + .ucom_cfg_set_dtr = &umoscom_cfg_set_dtr, + .ucom_cfg_set_rts = &umoscom_cfg_set_rts, + .ucom_cfg_set_break = &umoscom_cfg_set_break, + .ucom_cfg_param = &umoscom_cfg_param, + .ucom_cfg_open = &umoscom_cfg_open, + .ucom_cfg_close = &umoscom_cfg_close, + + /* other callbacks */ + .ucom_pre_param = &umoscom_pre_param, + .ucom_start_read = &umoscom_start_read, + .ucom_stop_read = &umoscom_stop_read, + .ucom_start_write = &umoscom_start_write, + .ucom_stop_write = &umoscom_stop_write, + .ucom_poll = &umoscom_poll, + .ucom_free = &umoscom_free, +}; + +static device_method_t umoscom_methods[] = { + DEVMETHOD(device_probe, umoscom_probe), + DEVMETHOD(device_attach, umoscom_attach), + DEVMETHOD(device_detach, umoscom_detach), + DEVMETHOD_END +}; + +static devclass_t umoscom_devclass; + +static driver_t umoscom_driver = { + .name = "umoscom", + .methods = umoscom_methods, + .size = sizeof(struct umoscom_softc), +}; + +static const STRUCT_USB_HOST_ID umoscom_devs[] = { + {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7703, 0)} +}; + +DRIVER_MODULE(umoscom, uhub, umoscom_driver, umoscom_devclass, NULL, 0); +MODULE_DEPEND(umoscom, ucom, 1, 1, 1); +MODULE_DEPEND(umoscom, usb, 1, 1, 1); +MODULE_VERSION(umoscom, 1); +USB_PNP_HOST_INFO(umoscom_devs); + +static int +umoscom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UMOSCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UMOSCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(umoscom_devs, sizeof(umoscom_devs), uaa)); +} + +static int +umoscom_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umoscom_softc *sc = device_get_softc(dev); + int error; + uint8_t iface_index; + + sc->sc_udev = uaa->device; + sc->sc_mcr = 0x08; /* enable interrupts */ + + /* XXX the device doesn't provide any ID string, so set a static one */ + device_set_desc(dev, "MOSCHIP USB Serial Port Adapter"); + device_printf(dev, "<MOSCHIP USB Serial Port Adapter>\n"); + + mtx_init(&sc->sc_mtx, "umoscom", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + iface_index = UMOSCOM_IFACE_INDEX; + error = usbd_transfer_setup(uaa->device, &iface_index, + sc->sc_xfer, umoscom_config_data, + UMOSCOM_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UMOSCOM_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UMOSCOM_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &umoscom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + device_printf(dev, "attach error: %s\n", usbd_errstr(error)); + umoscom_detach(dev); + return (ENXIO); +} + +static int +umoscom_detach(device_t dev) +{ + struct umoscom_softc *sc = device_get_softc(dev); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UMOSCOM_N_TRANSFER); + + device_claim_softc(dev); + + umoscom_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(umoscom); + +static void +umoscom_free_softc(struct umoscom_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +umoscom_free(struct ucom_softc *ucom) +{ + umoscom_free_softc(ucom->sc_parent); +} + +static void +umoscom_cfg_open(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + /* Purge FIFOs or odd things happen */ + umoscom_cfg_write(sc, UMOSCOM_FIFO, 0x00 | UMOSCOM_UART_REG); + + /* Enable FIFO */ + umoscom_cfg_write(sc, UMOSCOM_FIFO, UMOSCOM_FIFO_EN | + UMOSCOM_FIFO_RXCLR | UMOSCOM_FIFO_TXCLR | + UMOSCOM_FIFO_DMA_BLK | UMOSCOM_FIFO_RXLVL_MASK | + UMOSCOM_UART_REG); + + /* Enable Interrupt Registers */ + umoscom_cfg_write(sc, UMOSCOM_INT, 0x0C | UMOSCOM_UART_REG); + + /* Magic */ + umoscom_cfg_write(sc, 0x01, 0x08); + + /* Magic */ + umoscom_cfg_write(sc, 0x00, 0x02); +} + +static void +umoscom_cfg_close(struct ucom_softc *ucom) +{ + return; +} + +static void +umoscom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umoscom_softc *sc = ucom->sc_parent; + uint16_t val; + + val = sc->sc_lcr; + if (onoff) + val |= UMOSCOM_LCR_BREAK; + + umoscom_cfg_write(sc, UMOSCOM_LCR, val | UMOSCOM_UART_REG); +} + +static void +umoscom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= UMOSCOM_MCR_DTR; + else + sc->sc_mcr &= ~UMOSCOM_MCR_DTR; + + umoscom_cfg_write(sc, UMOSCOM_MCR, sc->sc_mcr | UMOSCOM_UART_REG); +} + +static void +umoscom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + if (onoff) + sc->sc_mcr |= UMOSCOM_MCR_RTS; + else + sc->sc_mcr &= ~UMOSCOM_MCR_RTS; + + umoscom_cfg_write(sc, UMOSCOM_MCR, sc->sc_mcr | UMOSCOM_UART_REG); +} + +static int +umoscom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + if ((t->c_ospeed <= 1) || (t->c_ospeed > 115200)) + return (EINVAL); + + return (0); +} + +static void +umoscom_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct umoscom_softc *sc = ucom->sc_parent; + uint16_t data; + + DPRINTF("speed=%d\n", t->c_ospeed); + + data = ((uint32_t)UMOSCOM_BAUD_REF) / ((uint32_t)t->c_ospeed); + + if (data == 0) { + DPRINTF("invalid baud rate!\n"); + return; + } + umoscom_cfg_write(sc, UMOSCOM_LCR, + UMOSCOM_LCR_DIVLATCH_EN | UMOSCOM_UART_REG); + + umoscom_cfg_write(sc, UMOSCOM_BAUDLO, + (data & 0xFF) | UMOSCOM_UART_REG); + + umoscom_cfg_write(sc, UMOSCOM_BAUDHI, + ((data >> 8) & 0xFF) | UMOSCOM_UART_REG); + + if (t->c_cflag & CSTOPB) + data = UMOSCOM_LCR_STOP_BITS_2; + else + data = UMOSCOM_LCR_STOP_BITS_1; + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) + data |= UMOSCOM_LCR_PARITY_ODD; + else + data |= UMOSCOM_LCR_PARITY_EVEN; + } else + data |= UMOSCOM_LCR_PARITY_NONE; + + switch (t->c_cflag & CSIZE) { + case CS5: + data |= UMOSCOM_LCR_DBITS(5); + break; + case CS6: + data |= UMOSCOM_LCR_DBITS(6); + break; + case CS7: + data |= UMOSCOM_LCR_DBITS(7); + break; + case CS8: + data |= UMOSCOM_LCR_DBITS(8); + break; + } + + sc->sc_lcr = data; + umoscom_cfg_write(sc, UMOSCOM_LCR, data | UMOSCOM_UART_REG); +} + +static void +umoscom_cfg_get_status(struct ucom_softc *ucom, uint8_t *p_lsr, uint8_t *p_msr) +{ + struct umoscom_softc *sc = ucom->sc_parent; + uint8_t msr; + + DPRINTFN(5, "\n"); + + /* + * Read status registers. MSR bits need translation from ns16550 to + * SER_* values. LSR bits are ns16550 in hardware and ucom. + */ + + *p_lsr = umoscom_cfg_read(sc, UMOSCOM_LSR); + msr = umoscom_cfg_read(sc, UMOSCOM_MSR); + + /* translate bits */ + + if (msr & UMOSCOM_MSR_CTS) + *p_msr |= SER_CTS; + + if (msr & UMOSCOM_MSR_CD) + *p_msr |= SER_DCD; + + if (msr & UMOSCOM_MSR_RI) + *p_msr |= SER_RI; + + if (msr & UMOSCOM_MSR_RTS) + *p_msr |= SER_DSR; +} + +static void +umoscom_cfg_write(struct umoscom_softc *sc, uint16_t reg, uint16_t val) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UMOSCOM_WRITE; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static uint8_t +umoscom_cfg_read(struct umoscom_softc *sc, uint16_t reg) +{ + struct usb_device_request req; + uint8_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UMOSCOM_READ; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &val, 0, 1000); + + DPRINTF("reg=0x%04x, val=0x%02x\n", reg, val); + + return (val); +} + +static void +umoscom_start_read(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + +#if 0 + /* start interrupt endpoint */ + usbd_transfer_start(sc->sc_xfer[UMOSCOM_INTR_DT_RD]); +#endif + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UMOSCOM_BULK_DT_RD]); +} + +static void +umoscom_stop_read(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + /* stop interrupt transfer */ + usbd_transfer_stop(sc->sc_xfer[UMOSCOM_INTR_DT_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UMOSCOM_BULK_DT_RD]); +} + +static void +umoscom_start_write(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UMOSCOM_BULK_DT_WR]); +} + +static void +umoscom_stop_write(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UMOSCOM_BULK_DT_WR]); +} + +static void +umoscom_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umoscom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + DPRINTF("\n"); + + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UMOSCOM_BUFSIZE, &actlen)) { + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + DPRINTFN(0, "transfer failed\n"); + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umoscom_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umoscom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF("got %d bytes\n", actlen); + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + DPRINTF("\n"); + + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + DPRINTFN(0, "transfer failed\n"); + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umoscom_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umoscom_softc *sc = usbd_xfer_softc(xfer); + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen < 2) { + DPRINTF("too short message\n"); + goto tr_setup; + } + ucom_status_change(&sc->sc_ucom); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + DPRINTFN(0, "transfer failed\n"); + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +umoscom_poll(struct ucom_softc *ucom) +{ + struct umoscom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UMOSCOM_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/uplcom.c b/freebsd/sys/dev/usb/serial/uplcom.c new file mode 100644 index 00000000..31b58670 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/uplcom.c @@ -0,0 +1,936 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $NetBSD: uplcom.c,v 1.21 2001/11/13 06:24:56 lukem Exp $ */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2001-2003, 2005 Shunsuke Akiyama <akiyama@jp.FreeBSD.org>. + * 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. + * 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. + */ + +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ichiro FUKUHARA (ichiro@ichiro.org). + * + * 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 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. + */ + +/* + * This driver supports several USB-to-RS232 serial adapters driven by + * Prolific PL-2303, PL-2303X and probably PL-2303HX USB-to-RS232 + * bridge chip. The adapters are sold under many different brand + * names. + * + * Datasheets are available at Prolific www site at + * http://www.prolific.com.tw. The datasheets don't contain full + * programming information for the chip. + * + * PL-2303HX is probably programmed the same as PL-2303X. + * + * There are several differences between PL-2303 and PL-2303(H)X. + * PL-2303(H)X can do higher bitrate in bulk mode, has _probably_ + * different command for controlling CRTSCTS and needs special + * sequence of commands for initialization which aren't also + * documented in the datasheet. + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_cdc.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR uplcom_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#ifdef USB_DEBUG +static int uplcom_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uplcom, CTLFLAG_RW, 0, "USB uplcom"); +SYSCTL_INT(_hw_usb_uplcom, OID_AUTO, debug, CTLFLAG_RWTUN, + &uplcom_debug, 0, "Debug level"); +#endif + +#define UPLCOM_MODVER 1 /* module version */ + +#define UPLCOM_CONFIG_INDEX 0 +#define UPLCOM_IFACE_INDEX 0 +#define UPLCOM_SECOND_IFACE_INDEX 1 + +#ifndef UPLCOM_INTR_INTERVAL +#define UPLCOM_INTR_INTERVAL 0 /* default */ +#endif + +#define UPLCOM_BULK_BUF_SIZE 1024 /* bytes */ + +#define UPLCOM_SET_REQUEST 0x01 +#define UPLCOM_SET_CRTSCTS 0x41 +#define UPLCOM_SET_CRTSCTS_PL2303X 0x61 +#define RSAQ_STATUS_CTS 0x80 +#define RSAQ_STATUS_DSR 0x02 +#define RSAQ_STATUS_DCD 0x01 + +#define TYPE_PL2303 0 +#define TYPE_PL2303HX 1 + +enum { + UPLCOM_BULK_DT_WR, + UPLCOM_BULK_DT_RD, + UPLCOM_INTR_DT_RD, + UPLCOM_N_TRANSFER, +}; + +struct uplcom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UPLCOM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_line; + + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* uplcom status register */ + uint8_t sc_chiptype; /* type of chip */ + uint8_t sc_ctrl_iface_no; + uint8_t sc_data_iface_no; + uint8_t sc_iface_index[2]; +}; + +/* prototypes */ + +static usb_error_t uplcom_reset(struct uplcom_softc *, struct usb_device *); +static usb_error_t uplcom_pl2303_do(struct usb_device *, uint8_t, uint8_t, + uint16_t, uint16_t, uint16_t); +static int uplcom_pl2303_init(struct usb_device *, uint8_t); +static void uplcom_free(struct ucom_softc *); +static void uplcom_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uplcom_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uplcom_cfg_set_break(struct ucom_softc *, uint8_t); +static int uplcom_pre_param(struct ucom_softc *, struct termios *); +static void uplcom_cfg_param(struct ucom_softc *, struct termios *); +static void uplcom_start_read(struct ucom_softc *); +static void uplcom_stop_read(struct ucom_softc *); +static void uplcom_start_write(struct ucom_softc *); +static void uplcom_stop_write(struct ucom_softc *); +static void uplcom_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void uplcom_poll(struct ucom_softc *ucom); + +static device_probe_t uplcom_probe; +static device_attach_t uplcom_attach; +static device_detach_t uplcom_detach; +static void uplcom_free_softc(struct uplcom_softc *); + +static usb_callback_t uplcom_intr_callback; +static usb_callback_t uplcom_write_callback; +static usb_callback_t uplcom_read_callback; + +static const struct usb_config uplcom_config_data[UPLCOM_N_TRANSFER] = { + + [UPLCOM_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UPLCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uplcom_write_callback, + .if_index = 0, + }, + + [UPLCOM_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UPLCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uplcom_read_callback, + .if_index = 0, + }, + + [UPLCOM_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &uplcom_intr_callback, + .if_index = 1, + }, +}; + +static struct ucom_callback uplcom_callback = { + .ucom_cfg_get_status = &uplcom_cfg_get_status, + .ucom_cfg_set_dtr = &uplcom_cfg_set_dtr, + .ucom_cfg_set_rts = &uplcom_cfg_set_rts, + .ucom_cfg_set_break = &uplcom_cfg_set_break, + .ucom_cfg_param = &uplcom_cfg_param, + .ucom_pre_param = &uplcom_pre_param, + .ucom_start_read = &uplcom_start_read, + .ucom_stop_read = &uplcom_stop_read, + .ucom_start_write = &uplcom_start_write, + .ucom_stop_write = &uplcom_stop_write, + .ucom_poll = &uplcom_poll, + .ucom_free = &uplcom_free, +}; + +#define UPLCOM_DEV(v,p) \ + { USB_VENDOR(USB_VENDOR_##v), USB_PRODUCT(USB_PRODUCT_##v##_##p) } + +static const STRUCT_USB_HOST_ID uplcom_devs[] = { + UPLCOM_DEV(ACERP, S81), /* BenQ S81 phone */ + UPLCOM_DEV(ADLINK, ND6530), /* ADLINK ND-6530 USB-Serial */ + UPLCOM_DEV(ALCATEL, OT535), /* Alcatel One Touch 535/735 */ + UPLCOM_DEV(ALCOR, AU9720), /* Alcor AU9720 USB 2.0-RS232 */ + UPLCOM_DEV(ANCHOR, SERIAL), /* Anchor Serial adapter */ + UPLCOM_DEV(ATEN, UC232A), /* PLANEX USB-RS232 URS-03 */ + UPLCOM_DEV(BELKIN, F5U257), /* Belkin F5U257 USB to Serial */ + UPLCOM_DEV(COREGA, CGUSBRS232R), /* Corega CG-USBRS232R */ + UPLCOM_DEV(EPSON, CRESSI_EDY), /* Cressi Edy diving computer */ + UPLCOM_DEV(EPSON, N2ITION3), /* Zeagle N2iTion3 diving computer */ + UPLCOM_DEV(ELECOM, UCSGT), /* ELECOM UC-SGT Serial Adapter */ + UPLCOM_DEV(ELECOM, UCSGT0), /* ELECOM UC-SGT Serial Adapter */ + UPLCOM_DEV(HAL, IMR001), /* HAL Corporation Crossam2+USB */ + UPLCOM_DEV(HP, LD220), /* HP LD220 POS Display */ + UPLCOM_DEV(IODATA, USBRSAQ), /* I/O DATA USB-RSAQ */ + UPLCOM_DEV(IODATA, USBRSAQ5), /* I/O DATA USB-RSAQ5 */ + UPLCOM_DEV(ITEGNO, WM1080A), /* iTegno WM1080A GSM/GFPRS modem */ + UPLCOM_DEV(ITEGNO, WM2080A), /* iTegno WM2080A CDMA modem */ + UPLCOM_DEV(LEADTEK, 9531), /* Leadtek 9531 GPS */ + UPLCOM_DEV(MICROSOFT, 700WX), /* Microsoft Palm 700WX */ + UPLCOM_DEV(MOBILEACTION, MA620), /* Mobile Action MA-620 Infrared Adapter */ + UPLCOM_DEV(NETINDEX, WS002IN), /* Willcom W-S002IN */ + UPLCOM_DEV(NOKIA2, CA42), /* Nokia CA-42 cable */ + UPLCOM_DEV(OTI, DKU5), /* OTI DKU-5 cable */ + UPLCOM_DEV(PANASONIC, TYTP50P6S), /* Panasonic TY-TP50P6-S flat screen */ + UPLCOM_DEV(PLX, CA42), /* PLX CA-42 clone cable */ + UPLCOM_DEV(PROLIFIC, ALLTRONIX_GPRS), /* Alltronix ACM003U00 modem */ + UPLCOM_DEV(PROLIFIC, ALDIGA_AL11U), /* AlDiga AL-11U modem */ + UPLCOM_DEV(PROLIFIC, DCU11), /* DCU-11 Phone Cable */ + UPLCOM_DEV(PROLIFIC, HCR331), /* HCR331 Card Reader */ + UPLCOM_DEV(PROLIFIC, MICROMAX_610U), /* Micromax 610U modem */ + UPLCOM_DEV(PROLIFIC, MOTOROLA), /* Motorola cable */ + UPLCOM_DEV(PROLIFIC, PHAROS), /* Prolific Pharos */ + UPLCOM_DEV(PROLIFIC, PL2303), /* Generic adapter */ + UPLCOM_DEV(PROLIFIC, RSAQ2), /* I/O DATA USB-RSAQ2 */ + UPLCOM_DEV(PROLIFIC, RSAQ3), /* I/O DATA USB-RSAQ3 */ + UPLCOM_DEV(PROLIFIC, UIC_MSR206), /* UIC MSR206 Card Reader */ + UPLCOM_DEV(PROLIFIC2, PL2303), /* Prolific adapter */ + UPLCOM_DEV(RADIOSHACK, USBCABLE), /* Radio Shack USB Adapter */ + UPLCOM_DEV(RATOC, REXUSB60), /* RATOC REX-USB60 */ + UPLCOM_DEV(SAGEM, USBSERIAL), /* Sagem USB-Serial Controller */ + UPLCOM_DEV(SAMSUNG, I330), /* Samsung I330 phone cradle */ + UPLCOM_DEV(SANWA, KB_USB2), /* Sanwa KB-USB2 Multimeter cable */ + UPLCOM_DEV(SIEMENS3, EF81), /* Siemens EF81 */ + UPLCOM_DEV(SIEMENS3, SX1), /* Siemens SX1 */ + UPLCOM_DEV(SIEMENS3, X65), /* Siemens X65 */ + UPLCOM_DEV(SIEMENS3, X75), /* Siemens X75 */ + UPLCOM_DEV(SITECOM, SERIAL), /* Sitecom USB to Serial */ + UPLCOM_DEV(SMART, PL2303), /* SMART Technologies USB to Serial */ + UPLCOM_DEV(SONY, QN3), /* Sony QN3 phone cable */ + UPLCOM_DEV(SONYERICSSON, DATAPILOT), /* Sony Ericsson Datapilot */ + UPLCOM_DEV(SONYERICSSON, DCU10), /* Sony Ericsson DCU-10 Cable */ + UPLCOM_DEV(SOURCENEXT, KEIKAI8), /* SOURCENEXT KeikaiDenwa 8 */ + UPLCOM_DEV(SOURCENEXT, KEIKAI8_CHG), /* SOURCENEXT KeikaiDenwa 8 with charger */ + UPLCOM_DEV(SPEEDDRAGON, MS3303H), /* Speed Dragon USB-Serial */ + UPLCOM_DEV(SYNTECH, CPT8001C), /* Syntech CPT-8001C Barcode scanner */ + UPLCOM_DEV(TDK, UHA6400), /* TDK USB-PHS Adapter UHA6400 */ + UPLCOM_DEV(TDK, UPA9664), /* TDK USB-PHS Adapter UPA9664 */ + UPLCOM_DEV(TRIPPLITE, U209), /* Tripp-Lite U209-000-R USB to Serial */ + UPLCOM_DEV(YCCABLE, PL2303), /* YC Cable USB-Serial */ +}; +#undef UPLCOM_DEV + +static device_method_t uplcom_methods[] = { + DEVMETHOD(device_probe, uplcom_probe), + DEVMETHOD(device_attach, uplcom_attach), + DEVMETHOD(device_detach, uplcom_detach), + DEVMETHOD_END +}; + +static devclass_t uplcom_devclass; + +static driver_t uplcom_driver = { + .name = "uplcom", + .methods = uplcom_methods, + .size = sizeof(struct uplcom_softc), +}; + +DRIVER_MODULE(uplcom, uhub, uplcom_driver, uplcom_devclass, NULL, 0); +MODULE_DEPEND(uplcom, ucom, 1, 1, 1); +MODULE_DEPEND(uplcom, usb, 1, 1, 1); +MODULE_VERSION(uplcom, UPLCOM_MODVER); +USB_PNP_HOST_INFO(uplcom_devs); + +static int +uplcom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UPLCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UPLCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uplcom_devs, sizeof(uplcom_devs), uaa)); +} + +static int +uplcom_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uplcom_softc *sc = device_get_softc(dev); + struct usb_interface *iface; + struct usb_interface_descriptor *id; + struct usb_device_descriptor *dd; + int error; + + DPRINTFN(11, "\n"); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uplcom", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + DPRINTF("sc = %p\n", sc); + + sc->sc_udev = uaa->device; + + /* Determine the chip type. This algorithm is taken from Linux. */ + dd = usbd_get_device_descriptor(sc->sc_udev); + if (dd->bDeviceClass == 0x02) + sc->sc_chiptype = TYPE_PL2303; + else if (dd->bMaxPacketSize == 0x40) + sc->sc_chiptype = TYPE_PL2303HX; + else + sc->sc_chiptype = TYPE_PL2303; + + DPRINTF("chiptype: %s\n", + (sc->sc_chiptype == TYPE_PL2303HX) ? + "2303X" : "2303"); + + /* + * USB-RSAQ1 has two interface + * + * USB-RSAQ1 | USB-RSAQ2 + * -----------------+----------------- + * Interface 0 |Interface 0 + * Interrupt(0x81) | Interrupt(0x81) + * -----------------+ BulkIN(0x02) + * Interface 1 | BulkOUT(0x83) + * BulkIN(0x02) | + * BulkOUT(0x83) | + */ + + sc->sc_ctrl_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index[1] = UPLCOM_IFACE_INDEX; + + iface = usbd_get_iface(uaa->device, UPLCOM_SECOND_IFACE_INDEX); + if (iface) { + id = usbd_get_interface_descriptor(iface); + if (id == NULL) { + device_printf(dev, "no interface descriptor (2)\n"); + goto detach; + } + sc->sc_data_iface_no = id->bInterfaceNumber; + sc->sc_iface_index[0] = UPLCOM_SECOND_IFACE_INDEX; + usbd_set_parent_iface(uaa->device, + UPLCOM_SECOND_IFACE_INDEX, uaa->info.bIfaceIndex); + } else { + sc->sc_data_iface_no = sc->sc_ctrl_iface_no; + sc->sc_iface_index[0] = UPLCOM_IFACE_INDEX; + } + + error = usbd_transfer_setup(uaa->device, + sc->sc_iface_index, sc->sc_xfer, uplcom_config_data, + UPLCOM_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + DPRINTF("one or more missing USB endpoints, " + "error=%s\n", usbd_errstr(error)); + goto detach; + } + error = uplcom_reset(sc, uaa->device); + if (error) { + device_printf(dev, "reset failed, error=%s\n", + usbd_errstr(error)); + goto detach; + } + + if (sc->sc_chiptype != TYPE_PL2303HX) { + /* HX variants seem to lock up after a clear stall request. */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + } else { + if (uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE, + UPLCOM_SET_REQUEST, 8, 0, 0) || + uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE, + UPLCOM_SET_REQUEST, 9, 0, 0)) { + goto detach; + } + } + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uplcom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + /* + * do the initialization during attach so that the system does not + * sleep during open: + */ + if (uplcom_pl2303_init(uaa->device, sc->sc_chiptype)) { + device_printf(dev, "init failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + uplcom_detach(dev); + return (ENXIO); +} + +static int +uplcom_detach(device_t dev) +{ + struct uplcom_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UPLCOM_N_TRANSFER); + + device_claim_softc(dev); + + uplcom_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(uplcom); + +static void +uplcom_free_softc(struct uplcom_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +uplcom_free(struct ucom_softc *ucom) +{ + uplcom_free_softc(ucom->sc_parent); +} + +static usb_error_t +uplcom_reset(struct uplcom_softc *sc, struct usb_device *udev) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + return (usbd_do_request(udev, NULL, &req, NULL)); +} + +static usb_error_t +uplcom_pl2303_do(struct usb_device *udev, uint8_t req_type, uint8_t request, + uint16_t value, uint16_t index, uint16_t length) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t buf[4]; + + req.bmRequestType = req_type; + req.bRequest = request; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, length); + + err = usbd_do_request(udev, NULL, &req, buf); + if (err) { + DPRINTF("error=%s\n", usbd_errstr(err)); + return (1); + } + return (0); +} + +static int +uplcom_pl2303_init(struct usb_device *udev, uint8_t chiptype) +{ + int err; + + if (uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 0, 0) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 1, 0) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1) + || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0, 1, 0) + || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 1, 0, 0)) + return (EIO); + + if (chiptype == TYPE_PL2303HX) + err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x44, 0); + else + err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x24, 0); + if (err) + return (EIO); + + return (0); +} + +static void +uplcom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_DTR; + else + sc->sc_line &= ~UCDC_LINE_DTR; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uplcom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UCDC_LINE_RTS; + else + sc->sc_line &= ~UCDC_LINE_RTS; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, sc->sc_line); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static void +uplcom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t temp; + + DPRINTF("onoff = %d\n", onoff); + + temp = (onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, temp); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); +} + +static const uint32_t uplcom_rates[] = { + 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, + 19200, 28800, 38400, 57600, 115200, + /* + * Higher speeds are probably possible. PL2303X supports up to + * 6Mb and can set any rate + */ + 230400, 460800, 614400, 921600, 1228800 +}; + +#define N_UPLCOM_RATES nitems(uplcom_rates) + +static int +uplcom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uplcom_softc *sc = ucom->sc_parent; + uint8_t i; + + DPRINTF("\n"); + + /** + * Check requested baud rate. + * + * The PL2303 can only set specific baud rates, up to 1228800 baud. + * The PL2303X can set any baud rate up to 6Mb. + * The PL2303HX rev. D can set any baud rate up to 12Mb. + * + * XXX: We currently cannot identify the PL2303HX rev. D, so treat + * it the same as the PL2303X. + */ + if (sc->sc_chiptype != TYPE_PL2303HX) { + for (i = 0; i < N_UPLCOM_RATES; i++) { + if (uplcom_rates[i] == t->c_ospeed) + return (0); + } + } else { + if (t->c_ospeed <= 6000000) + return (0); + } + + DPRINTF("uplcom_param: bad baud rate (%d)\n", t->c_ospeed); + return (EIO); +} + +static void +uplcom_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uplcom_softc *sc = ucom->sc_parent; + struct usb_cdc_line_state ls; + struct usb_device_request req; + + DPRINTF("sc = %p\n", sc); + + memset(&ls, 0, sizeof(ls)); + + USETDW(ls.dwDTERate, t->c_ospeed); + + if (t->c_cflag & CSTOPB) { + ls.bCharFormat = UCDC_STOP_BIT_2; + } else { + ls.bCharFormat = UCDC_STOP_BIT_1; + } + + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + ls.bParityType = UCDC_PARITY_ODD; + } else { + ls.bParityType = UCDC_PARITY_EVEN; + } + } else { + ls.bParityType = UCDC_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n", + UGETDW(ls.dwDTERate), ls.bCharFormat, + ls.bParityType, ls.bDataBits); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_data_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UCDC_LINE_STATE_LENGTH); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &ls, 0, 1000); + + if (t->c_cflag & CRTSCTS) { + + DPRINTF("crtscts = on\n"); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + if (sc->sc_chiptype == TYPE_PL2303HX) + USETW(req.wIndex, UPLCOM_SET_CRTSCTS_PL2303X); + else + USETW(req.wIndex, UPLCOM_SET_CRTSCTS); + USETW(req.wLength, 0); + + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + } else { + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + } +} + +static void +uplcom_start_read(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + /* start interrupt endpoint */ + usbd_transfer_start(sc->sc_xfer[UPLCOM_INTR_DT_RD]); + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[UPLCOM_BULK_DT_RD]); +} + +static void +uplcom_stop_read(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + /* stop interrupt endpoint */ + usbd_transfer_stop(sc->sc_xfer[UPLCOM_INTR_DT_RD]); + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[UPLCOM_BULK_DT_RD]); +} + +static void +uplcom_start_write(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UPLCOM_BULK_DT_WR]); +} + +static void +uplcom_stop_write(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UPLCOM_BULK_DT_WR]); +} + +static void +uplcom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uplcom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + /* XXX Note: sc_lsr is always zero */ + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uplcom_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uplcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[9]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTF("actlen = %u\n", actlen); + + if (actlen >= 9) { + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, sizeof(buf)); + + DPRINTF("status = 0x%02x\n", buf[8]); + + sc->sc_lsr = 0; + sc->sc_msr = 0; + + if (buf[8] & RSAQ_STATUS_CTS) { + sc->sc_msr |= SER_CTS; + } + if (buf[8] & RSAQ_STATUS_DSR) { + sc->sc_msr |= SER_DSR; + } + if (buf[8] & RSAQ_STATUS_DCD) { + sc->sc_msr |= SER_DCD; + } + ucom_status_change(&sc->sc_ucom); + } + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uplcom_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uplcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UPLCOM_BULK_BUF_SIZE, &actlen)) { + + DPRINTF("actlen = %d\n", actlen); + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uplcom_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uplcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uplcom_poll(struct ucom_softc *ucom) +{ + struct uplcom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UPLCOM_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/usb_serial.c b/freebsd/sys/dev/usb/serial/usb_serial.c new file mode 100644 index 00000000..b5d7ef7a --- /dev/null +++ b/freebsd/sys/dev/usb/serial/usb_serial.c @@ -0,0 +1,1742 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */ + +/*- + * Copyright (c) 2001-2003, 2005, 2008 + * Shunsuke Akiyama <akiyama@jp.FreeBSD.org>. + * 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. + * 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/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 1998, 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) at + * Carlstedt Research & Technology. + * + * 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 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/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> +#include <sys/cons.h> + +#include <dev/uart/uart_ppstypes.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> + +#define USB_DEBUG_VAR ucom_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_busdma.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#include <rtems/bsd/local/opt_gdb.h> + +static SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom"); + +static int ucom_pps_mode; + +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, pps_mode, CTLFLAG_RWTUN, + &ucom_pps_mode, 0, + "pulse capture mode: 0/1/2=disabled/CTS/DCD; add 0x10 to invert"); + +#ifdef USB_DEBUG +static int ucom_debug = 0; + +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RWTUN, + &ucom_debug, 0, "ucom debug level"); +#endif + +#define UCOM_CONS_BUFSIZE 1024 + +static uint8_t ucom_cons_rx_buf[UCOM_CONS_BUFSIZE]; +static uint8_t ucom_cons_tx_buf[UCOM_CONS_BUFSIZE]; + +static unsigned int ucom_cons_rx_low = 0; +static unsigned int ucom_cons_rx_high = 0; + +static unsigned int ucom_cons_tx_low = 0; +static unsigned int ucom_cons_tx_high = 0; + +static int ucom_cons_unit = -1; +static int ucom_cons_subunit = 0; +static int ucom_cons_baud = 9600; +static struct ucom_softc *ucom_cons_softc = NULL; + +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_unit, CTLFLAG_RWTUN, + &ucom_cons_unit, 0, "console unit number"); +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_subunit, CTLFLAG_RWTUN, + &ucom_cons_subunit, 0, "console subunit number"); +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_baud, CTLFLAG_RWTUN, + &ucom_cons_baud, 0, "console baud rate"); + +static usb_proc_callback_t ucom_cfg_start_transfers; +static usb_proc_callback_t ucom_cfg_open; +static usb_proc_callback_t ucom_cfg_close; +static usb_proc_callback_t ucom_cfg_line_state; +static usb_proc_callback_t ucom_cfg_status_change; +static usb_proc_callback_t ucom_cfg_param; + +static int ucom_unit_alloc(void); +static void ucom_unit_free(int); +static int ucom_attach_tty(struct ucom_super_softc *, struct ucom_softc *); +static void ucom_detach_tty(struct ucom_super_softc *, struct ucom_softc *); +static void ucom_queue_command(struct ucom_softc *, + usb_proc_callback_t *, struct termios *pt, + struct usb_proc_msg *t0, struct usb_proc_msg *t1); +static void ucom_shutdown(struct ucom_softc *); +static void ucom_ring(struct ucom_softc *, uint8_t); +static void ucom_break(struct ucom_softc *, uint8_t); +static void ucom_dtr(struct ucom_softc *, uint8_t); +static void ucom_rts(struct ucom_softc *, uint8_t); + +static tsw_open_t ucom_open; +static tsw_close_t ucom_close; +static tsw_ioctl_t ucom_ioctl; +static tsw_modem_t ucom_modem; +static tsw_param_t ucom_param; +static tsw_outwakeup_t ucom_outwakeup; +static tsw_inwakeup_t ucom_inwakeup; +static tsw_free_t ucom_free; +static tsw_busy_t ucom_busy; + +static struct ttydevsw ucom_class = { + .tsw_flags = TF_INITLOCK | TF_CALLOUT, + .tsw_open = ucom_open, + .tsw_close = ucom_close, + .tsw_outwakeup = ucom_outwakeup, + .tsw_inwakeup = ucom_inwakeup, + .tsw_ioctl = ucom_ioctl, + .tsw_param = ucom_param, + .tsw_modem = ucom_modem, + .tsw_free = ucom_free, + .tsw_busy = ucom_busy, +}; + +MODULE_DEPEND(ucom, usb, 1, 1, 1); +MODULE_VERSION(ucom, 1); + +#define UCOM_UNIT_MAX 128 /* maximum number of units */ +#define UCOM_TTY_PREFIX "U" + +static struct unrhdr *ucom_unrhdr; +static struct mtx ucom_mtx; +static int ucom_close_refs; + +static void +ucom_init(void *arg) +{ + DPRINTF("\n"); + ucom_unrhdr = new_unrhdr(0, UCOM_UNIT_MAX - 1, NULL); + mtx_init(&ucom_mtx, "UCOM MTX", NULL, MTX_DEF); +} +SYSINIT(ucom_init, SI_SUB_KLD - 1, SI_ORDER_ANY, ucom_init, NULL); + +static void +ucom_uninit(void *arg) +{ + struct unrhdr *hdr; + hdr = ucom_unrhdr; + ucom_unrhdr = NULL; + + DPRINTF("\n"); + + if (hdr != NULL) + delete_unrhdr(hdr); + + mtx_destroy(&ucom_mtx); +} +SYSUNINIT(ucom_uninit, SI_SUB_KLD - 3, SI_ORDER_ANY, ucom_uninit, NULL); + +/* + * Mark a unit number (the X in cuaUX) as in use. + * + * Note that devices using a different naming scheme (see ucom_tty_name() + * callback) still use this unit allocation. + */ +static int +ucom_unit_alloc(void) +{ + int unit; + + /* sanity checks */ + if (ucom_unrhdr == NULL) { + DPRINTF("ucom_unrhdr is NULL\n"); + return (-1); + } + unit = alloc_unr(ucom_unrhdr); + DPRINTF("unit %d is allocated\n", unit); + return (unit); +} + +/* + * Mark the unit number as not in use. + */ +static void +ucom_unit_free(int unit) +{ + /* sanity checks */ + if (unit < 0 || unit >= UCOM_UNIT_MAX || ucom_unrhdr == NULL) { + DPRINTF("cannot free unit number\n"); + return; + } + DPRINTF("unit %d is freed\n", unit); + free_unr(ucom_unrhdr, unit); +} + +/* + * Setup a group of one or more serial ports. + * + * The mutex pointed to by "mtx" is applied before all + * callbacks are called back. Also "mtx" must be applied + * before calling into the ucom-layer! + */ +int +ucom_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc, + int subunits, void *parent, + const struct ucom_callback *callback, struct mtx *mtx) +{ + int subunit; + int error = 0; + + if ((sc == NULL) || + (subunits <= 0) || + (callback == NULL) || + (mtx == NULL)) { + return (EINVAL); + } + + /* allocate a uniq unit number */ + ssc->sc_unit = ucom_unit_alloc(); + if (ssc->sc_unit == -1) + return (ENOMEM); + + /* generate TTY name string */ + snprintf(ssc->sc_ttyname, sizeof(ssc->sc_ttyname), + UCOM_TTY_PREFIX "%d", ssc->sc_unit); + + /* create USB request handling process */ + error = usb_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED); + if (error) { + ucom_unit_free(ssc->sc_unit); + return (error); + } + ssc->sc_subunits = subunits; + ssc->sc_flag = UCOM_FLAG_ATTACHED | + UCOM_FLAG_FREE_UNIT; + + if (callback->ucom_free == NULL) + ssc->sc_flag |= UCOM_FLAG_WAIT_REFS; + + /* increment reference count */ + ucom_ref(ssc); + + for (subunit = 0; subunit < ssc->sc_subunits; subunit++) { + sc[subunit].sc_subunit = subunit; + sc[subunit].sc_super = ssc; + sc[subunit].sc_mtx = mtx; + sc[subunit].sc_parent = parent; + sc[subunit].sc_callback = callback; + + error = ucom_attach_tty(ssc, &sc[subunit]); + if (error) { + ucom_detach(ssc, &sc[0]); + return (error); + } + /* increment reference count */ + ucom_ref(ssc); + + /* set subunit attached */ + sc[subunit].sc_flag |= UCOM_FLAG_ATTACHED; + } + + DPRINTF("tp = %p, unit = %d, subunits = %d\n", + sc->sc_tty, ssc->sc_unit, ssc->sc_subunits); + + return (0); +} + +/* + * The following function will do nothing if the structure pointed to + * by "ssc" and "sc" is zero or has already been detached. + */ +void +ucom_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc) +{ + int subunit; + + if (!(ssc->sc_flag & UCOM_FLAG_ATTACHED)) + return; /* not initialized */ + + if (ssc->sc_sysctl_ttyname != NULL) { + sysctl_remove_oid(ssc->sc_sysctl_ttyname, 1, 0); + ssc->sc_sysctl_ttyname = NULL; + } + + if (ssc->sc_sysctl_ttyports != NULL) { + sysctl_remove_oid(ssc->sc_sysctl_ttyports, 1, 0); + ssc->sc_sysctl_ttyports = NULL; + } + + usb_proc_drain(&ssc->sc_tq); + + for (subunit = 0; subunit < ssc->sc_subunits; subunit++) { + if (sc[subunit].sc_flag & UCOM_FLAG_ATTACHED) { + + ucom_detach_tty(ssc, &sc[subunit]); + + /* avoid duplicate detach */ + sc[subunit].sc_flag &= ~UCOM_FLAG_ATTACHED; + } + } + usb_proc_free(&ssc->sc_tq); + + ucom_unref(ssc); + + if (ssc->sc_flag & UCOM_FLAG_WAIT_REFS) + ucom_drain(ssc); + + /* make sure we don't detach twice */ + ssc->sc_flag &= ~UCOM_FLAG_ATTACHED; +} + +void +ucom_drain(struct ucom_super_softc *ssc) +{ + mtx_lock(&ucom_mtx); + while (ssc->sc_refs > 0) { + printf("ucom: Waiting for a TTY device to close.\n"); + usb_pause_mtx(&ucom_mtx, hz); + } + mtx_unlock(&ucom_mtx); +} + +void +ucom_drain_all(void *arg) +{ + mtx_lock(&ucom_mtx); + while (ucom_close_refs > 0) { + printf("ucom: Waiting for all detached TTY " + "devices to have open fds closed.\n"); + usb_pause_mtx(&ucom_mtx, hz); + } + mtx_unlock(&ucom_mtx); +} + +static int +ucom_attach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc) +{ + struct tty *tp; + char buf[32]; /* temporary TTY device name buffer */ + + tp = tty_alloc_mutex(&ucom_class, sc, sc->sc_mtx); + if (tp == NULL) + return (ENOMEM); + + /* Check if the client has a custom TTY name */ + buf[0] = '\0'; + if (sc->sc_callback->ucom_tty_name) { + sc->sc_callback->ucom_tty_name(sc, buf, + sizeof(buf), ssc->sc_unit, sc->sc_subunit); + } + if (buf[0] == 0) { + /* Use default TTY name */ + if (ssc->sc_subunits > 1) { + /* multiple modems in one */ + snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u.%u", + ssc->sc_unit, sc->sc_subunit); + } else { + /* single modem */ + snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u", + ssc->sc_unit); + } + } + tty_makedev(tp, NULL, "%s", buf); + + sc->sc_tty = tp; + + sc->sc_pps.ppscap = PPS_CAPTUREBOTH; + sc->sc_pps.driver_abi = PPS_ABI_VERSION; + sc->sc_pps.driver_mtx = sc->sc_mtx; + pps_init_abi(&sc->sc_pps); + + DPRINTF("ttycreate: %s\n", buf); + + /* Check if this device should be a console */ + if ((ucom_cons_softc == NULL) && + (ssc->sc_unit == ucom_cons_unit) && + (sc->sc_subunit == ucom_cons_subunit)) { + + DPRINTF("unit %d subunit %d is console", + ssc->sc_unit, sc->sc_subunit); + + ucom_cons_softc = sc; + + tty_init_console(tp, ucom_cons_baud); + + UCOM_MTX_LOCK(ucom_cons_softc); + ucom_cons_rx_low = 0; + ucom_cons_rx_high = 0; + ucom_cons_tx_low = 0; + ucom_cons_tx_high = 0; + sc->sc_flag |= UCOM_FLAG_CONSOLE; + ucom_open(ucom_cons_softc->sc_tty); + ucom_param(ucom_cons_softc->sc_tty, &tp->t_termios_init_in); + UCOM_MTX_UNLOCK(ucom_cons_softc); + } + + return (0); +} + +static void +ucom_detach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc) +{ + struct tty *tp = sc->sc_tty; + + DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) { + UCOM_MTX_LOCK(ucom_cons_softc); + ucom_close(ucom_cons_softc->sc_tty); + sc->sc_flag &= ~UCOM_FLAG_CONSOLE; + UCOM_MTX_UNLOCK(ucom_cons_softc); + ucom_cons_softc = NULL; + } + + /* the config thread has been stopped when we get here */ + + UCOM_MTX_LOCK(sc); + sc->sc_flag |= UCOM_FLAG_GONE; + sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY); + UCOM_MTX_UNLOCK(sc); + + if (tp) { + mtx_lock(&ucom_mtx); + ucom_close_refs++; + mtx_unlock(&ucom_mtx); + + tty_lock(tp); + + ucom_close(tp); /* close, if any */ + + tty_rel_gone(tp); + + UCOM_MTX_LOCK(sc); + /* + * make sure that read and write transfers are stopped + */ + if (sc->sc_callback->ucom_stop_read) + (sc->sc_callback->ucom_stop_read) (sc); + if (sc->sc_callback->ucom_stop_write) + (sc->sc_callback->ucom_stop_write) (sc); + UCOM_MTX_UNLOCK(sc); + } +} + +void +ucom_set_pnpinfo_usb(struct ucom_super_softc *ssc, device_t dev) +{ + char buf[64]; + uint8_t iface_index; + struct usb_attach_arg *uaa; + + snprintf(buf, sizeof(buf), "ttyname=" UCOM_TTY_PREFIX + "%d ttyports=%d", ssc->sc_unit, ssc->sc_subunits); + + /* Store the PNP info in the first interface for the device */ + uaa = device_get_ivars(dev); + iface_index = uaa->info.bIfaceIndex; + + if (usbd_set_pnpinfo(uaa->device, iface_index, buf) != 0) + device_printf(dev, "Could not set PNP info\n"); + + /* + * The following information is also replicated in the PNP-info + * string which is registered above: + */ + if (ssc->sc_sysctl_ttyname == NULL) { + ssc->sc_sysctl_ttyname = SYSCTL_ADD_STRING(NULL, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "ttyname", CTLFLAG_RD, ssc->sc_ttyname, 0, + "TTY device basename"); + } + if (ssc->sc_sysctl_ttyports == NULL) { + ssc->sc_sysctl_ttyports = SYSCTL_ADD_INT(NULL, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "ttyports", CTLFLAG_RD, + NULL, ssc->sc_subunits, "Number of ports"); + } +} + +static void +ucom_queue_command(struct ucom_softc *sc, + usb_proc_callback_t *fn, struct termios *pt, + struct usb_proc_msg *t0, struct usb_proc_msg *t1) +{ + struct ucom_super_softc *ssc = sc->sc_super; + struct ucom_param_task *task; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (usb_proc_is_gone(&ssc->sc_tq)) { + DPRINTF("proc is gone\n"); + return; /* nothing to do */ + } + /* + * NOTE: The task cannot get executed before we drop the + * "sc_mtx" mutex. It is safe to update fields in the message + * structure after that the message got queued. + */ + task = (struct ucom_param_task *) + usb_proc_msignal(&ssc->sc_tq, t0, t1); + + /* Setup callback and softc pointers */ + task->hdr.pm_callback = fn; + task->sc = sc; + + /* + * Make a copy of the termios. This field is only present if + * the "pt" field is not NULL. + */ + if (pt != NULL) + task->termios_copy = *pt; + + /* + * Closing the device should be synchronous. + */ + if (fn == ucom_cfg_close) + usb_proc_mwait(&ssc->sc_tq, t0, t1); + + /* + * In case of multiple configure requests, + * keep track of the last one! + */ + if (fn == ucom_cfg_start_transfers) + sc->sc_last_start_xfer = &task->hdr; +} + +static void +ucom_shutdown(struct ucom_softc *sc) +{ + struct tty *tp = sc->sc_tty; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + DPRINTF("\n"); + + /* + * Hang up if necessary: + */ + if (tp->t_termios.c_cflag & HUPCL) { + ucom_modem(tp, 0, SER_DTR); + } +} + +/* + * Return values: + * 0: normal + * else: taskqueue is draining or gone + */ +uint8_t +ucom_cfg_is_gone(struct ucom_softc *sc) +{ + struct ucom_super_softc *ssc = sc->sc_super; + + return (usb_proc_is_gone(&ssc->sc_tq)); +} + +static void +ucom_cfg_start_transfers(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* TTY device closed */ + return; + } + + if (_task == sc->sc_last_start_xfer) + sc->sc_flag |= UCOM_FLAG_GP_DATA; + + if (sc->sc_callback->ucom_start_read) { + (sc->sc_callback->ucom_start_read) (sc); + } + if (sc->sc_callback->ucom_start_write) { + (sc->sc_callback->ucom_start_write) (sc); + } +} + +static void +ucom_start_transfers(struct ucom_softc *sc) +{ + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + /* + * Make sure that data transfers are started in both + * directions: + */ + if (sc->sc_callback->ucom_start_read) { + (sc->sc_callback->ucom_start_read) (sc); + } + if (sc->sc_callback->ucom_start_write) { + (sc->sc_callback->ucom_start_write) (sc); + } +} + +static void +ucom_cfg_open(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + + DPRINTF("\n"); + + if (sc->sc_flag & UCOM_FLAG_LL_READY) { + + /* already opened */ + + } else { + + sc->sc_flag |= UCOM_FLAG_LL_READY; + + if (sc->sc_callback->ucom_cfg_open) { + (sc->sc_callback->ucom_cfg_open) (sc); + + /* wait a little */ + usb_pause_mtx(sc->sc_mtx, hz / 10); + } + } +} + +static int +ucom_open(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + int error; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_GONE) { + return (ENXIO); + } + if (sc->sc_flag & UCOM_FLAG_HL_READY) { + /* already opened */ + return (0); + } + DPRINTF("tp = %p\n", tp); + + if (sc->sc_callback->ucom_pre_open) { + /* + * give the lower layer a chance to disallow TTY open, for + * example if the device is not present: + */ + error = (sc->sc_callback->ucom_pre_open) (sc); + if (error) { + return (error); + } + } + sc->sc_flag |= UCOM_FLAG_HL_READY; + + /* Disable transfers */ + sc->sc_flag &= ~UCOM_FLAG_GP_DATA; + + sc->sc_lsr = 0; + sc->sc_msr = 0; + sc->sc_mcr = 0; + + /* reset programmed line state */ + sc->sc_pls_curr = 0; + sc->sc_pls_set = 0; + sc->sc_pls_clr = 0; + + /* reset jitter buffer */ + sc->sc_jitterbuf_in = 0; + sc->sc_jitterbuf_out = 0; + + ucom_queue_command(sc, ucom_cfg_open, NULL, + &sc->sc_open_task[0].hdr, + &sc->sc_open_task[1].hdr); + + /* Queue transfer enable command last */ + ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, + &sc->sc_start_task[0].hdr, + &sc->sc_start_task[1].hdr); + + ucom_modem(tp, SER_DTR | SER_RTS, 0); + + ucom_ring(sc, 0); + + ucom_break(sc, 0); + + ucom_status_change(sc); + + return (0); +} + +static void +ucom_cfg_close(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + + DPRINTF("\n"); + + if (sc->sc_flag & UCOM_FLAG_LL_READY) { + sc->sc_flag &= ~UCOM_FLAG_LL_READY; + if (sc->sc_callback->ucom_cfg_close) + (sc->sc_callback->ucom_cfg_close) (sc); + } else { + /* already closed */ + } +} + +static void +ucom_close(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + DPRINTF("tp=%p\n", tp); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + DPRINTF("tp=%p already closed\n", tp); + return; + } + ucom_shutdown(sc); + + ucom_queue_command(sc, ucom_cfg_close, NULL, + &sc->sc_close_task[0].hdr, + &sc->sc_close_task[1].hdr); + + sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW); + + if (sc->sc_callback->ucom_stop_read) { + (sc->sc_callback->ucom_stop_read) (sc); + } +} + +static void +ucom_inwakeup(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + uint16_t pos; + + if (sc == NULL) + return; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + DPRINTF("tp=%p\n", tp); + + if (ttydisc_can_bypass(tp) != 0 || + (sc->sc_flag & UCOM_FLAG_HL_READY) == 0 || + (sc->sc_flag & UCOM_FLAG_INWAKEUP) != 0) { + return; + } + + /* prevent recursion */ + sc->sc_flag |= UCOM_FLAG_INWAKEUP; + + pos = sc->sc_jitterbuf_out; + + while (sc->sc_jitterbuf_in != pos) { + int c; + + c = (char)sc->sc_jitterbuf[pos]; + + if (ttydisc_rint(tp, c, 0) == -1) + break; + pos++; + if (pos >= UCOM_JITTERBUF_SIZE) + pos -= UCOM_JITTERBUF_SIZE; + } + + sc->sc_jitterbuf_out = pos; + + /* clear RTS in async fashion */ + if ((sc->sc_jitterbuf_in == pos) && + (sc->sc_flag & UCOM_FLAG_RTS_IFLOW)) + ucom_rts(sc, 0); + + sc->sc_flag &= ~UCOM_FLAG_INWAKEUP; +} + +static int +ucom_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) +{ + struct ucom_softc *sc = tty_softc(tp); + int error; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return (EIO); + } + DPRINTF("cmd = 0x%08lx\n", cmd); + + switch (cmd) { +#if 0 + case TIOCSRING: + ucom_ring(sc, 1); + error = 0; + break; + case TIOCCRING: + ucom_ring(sc, 0); + error = 0; + break; +#endif + case TIOCSBRK: + ucom_break(sc, 1); + error = 0; + break; + case TIOCCBRK: + ucom_break(sc, 0); + error = 0; + break; + default: + if (sc->sc_callback->ucom_ioctl) { + error = (sc->sc_callback->ucom_ioctl) + (sc, cmd, data, 0, td); + } else { + error = ENOIOCTL; + } + if (error == ENOIOCTL) + error = pps_ioctl(cmd, data, &sc->sc_pps); + break; + } + return (error); +} + +static int +ucom_modem(struct tty *tp, int sigon, int sigoff) +{ + struct ucom_softc *sc = tty_softc(tp); + uint8_t onoff; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return (0); + } + if ((sigon == 0) && (sigoff == 0)) { + + if (sc->sc_mcr & SER_DTR) { + sigon |= SER_DTR; + } + if (sc->sc_mcr & SER_RTS) { + sigon |= SER_RTS; + } + if (sc->sc_msr & SER_CTS) { + sigon |= SER_CTS; + } + if (sc->sc_msr & SER_DCD) { + sigon |= SER_DCD; + } + if (sc->sc_msr & SER_DSR) { + sigon |= SER_DSR; + } + if (sc->sc_msr & SER_RI) { + sigon |= SER_RI; + } + return (sigon); + } + if (sigon & SER_DTR) { + sc->sc_mcr |= SER_DTR; + } + if (sigoff & SER_DTR) { + sc->sc_mcr &= ~SER_DTR; + } + if (sigon & SER_RTS) { + sc->sc_mcr |= SER_RTS; + } + if (sigoff & SER_RTS) { + sc->sc_mcr &= ~SER_RTS; + } + onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0; + ucom_dtr(sc, onoff); + + onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0; + ucom_rts(sc, onoff); + + return (0); +} + +static void +ucom_cfg_line_state(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + uint8_t notch_bits; + uint8_t any_bits; + uint8_t prev_value; + uint8_t last_value; + uint8_t mask; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + + mask = 0; + /* compute callback mask */ + if (sc->sc_callback->ucom_cfg_set_dtr) + mask |= UCOM_LS_DTR; + if (sc->sc_callback->ucom_cfg_set_rts) + mask |= UCOM_LS_RTS; + if (sc->sc_callback->ucom_cfg_set_break) + mask |= UCOM_LS_BREAK; + if (sc->sc_callback->ucom_cfg_set_ring) + mask |= UCOM_LS_RING; + + /* compute the bits we are to program */ + notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask; + any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask; + prev_value = sc->sc_pls_curr ^ notch_bits; + last_value = sc->sc_pls_curr; + + /* reset programmed line state */ + sc->sc_pls_curr = 0; + sc->sc_pls_set = 0; + sc->sc_pls_clr = 0; + + /* ensure that we don't lose any levels */ + if (notch_bits & UCOM_LS_DTR) + sc->sc_callback->ucom_cfg_set_dtr(sc, + (prev_value & UCOM_LS_DTR) ? 1 : 0); + if (notch_bits & UCOM_LS_RTS) + sc->sc_callback->ucom_cfg_set_rts(sc, + (prev_value & UCOM_LS_RTS) ? 1 : 0); + if (notch_bits & UCOM_LS_BREAK) + sc->sc_callback->ucom_cfg_set_break(sc, + (prev_value & UCOM_LS_BREAK) ? 1 : 0); + if (notch_bits & UCOM_LS_RING) + sc->sc_callback->ucom_cfg_set_ring(sc, + (prev_value & UCOM_LS_RING) ? 1 : 0); + + /* set last value */ + if (any_bits & UCOM_LS_DTR) + sc->sc_callback->ucom_cfg_set_dtr(sc, + (last_value & UCOM_LS_DTR) ? 1 : 0); + if (any_bits & UCOM_LS_RTS) + sc->sc_callback->ucom_cfg_set_rts(sc, + (last_value & UCOM_LS_RTS) ? 1 : 0); + if (any_bits & UCOM_LS_BREAK) + sc->sc_callback->ucom_cfg_set_break(sc, + (last_value & UCOM_LS_BREAK) ? 1 : 0); + if (any_bits & UCOM_LS_RING) + sc->sc_callback->ucom_cfg_set_ring(sc, + (last_value & UCOM_LS_RING) ? 1 : 0); +} + +static void +ucom_line_state(struct ucom_softc *sc, + uint8_t set_bits, uint8_t clear_bits) +{ + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + + DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits); + + /* update current programmed line state */ + sc->sc_pls_curr |= set_bits; + sc->sc_pls_curr &= ~clear_bits; + sc->sc_pls_set |= set_bits; + sc->sc_pls_clr |= clear_bits; + + /* defer driver programming */ + ucom_queue_command(sc, ucom_cfg_line_state, NULL, + &sc->sc_line_state_task[0].hdr, + &sc->sc_line_state_task[1].hdr); +} + +static void +ucom_ring(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_RING, 0); + else + ucom_line_state(sc, 0, UCOM_LS_RING); +} + +static void +ucom_break(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_BREAK, 0); + else + ucom_line_state(sc, 0, UCOM_LS_BREAK); +} + +static void +ucom_dtr(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_DTR, 0); + else + ucom_line_state(sc, 0, UCOM_LS_DTR); +} + +static void +ucom_rts(struct ucom_softc *sc, uint8_t onoff) +{ + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + ucom_line_state(sc, UCOM_LS_RTS, 0); + else + ucom_line_state(sc, 0, UCOM_LS_RTS); +} + +static void +ucom_cfg_status_change(struct usb_proc_msg *_task) +{ + struct ucom_cfg_task *task = + (struct ucom_cfg_task *)_task; + struct ucom_softc *sc = task->sc; + struct tty *tp; + int onoff; + uint8_t new_msr; + uint8_t new_lsr; + uint8_t msr_delta; + uint8_t lsr_delta; + uint8_t pps_signal; + + tp = sc->sc_tty; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (sc->sc_callback->ucom_cfg_get_status == NULL) { + return; + } + /* get status */ + + new_msr = 0; + new_lsr = 0; + + (sc->sc_callback->ucom_cfg_get_status) (sc, &new_lsr, &new_msr); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* TTY device closed */ + return; + } + msr_delta = (sc->sc_msr ^ new_msr); + lsr_delta = (sc->sc_lsr ^ new_lsr); + + sc->sc_msr = new_msr; + sc->sc_lsr = new_lsr; + + /* + * Time pulse counting support. + */ + switch(ucom_pps_mode & UART_PPS_SIGNAL_MASK) { + case UART_PPS_CTS: + pps_signal = SER_CTS; + break; + case UART_PPS_DCD: + pps_signal = SER_DCD; + break; + default: + pps_signal = 0; + break; + } + + if ((sc->sc_pps.ppsparam.mode & PPS_CAPTUREBOTH) && + (msr_delta & pps_signal)) { + pps_capture(&sc->sc_pps); + onoff = (sc->sc_msr & pps_signal) ? 1 : 0; + if (ucom_pps_mode & UART_PPS_INVERT_PULSE) + onoff = !onoff; + pps_event(&sc->sc_pps, onoff ? PPS_CAPTUREASSERT : + PPS_CAPTURECLEAR); + } + + if (msr_delta & SER_DCD) { + + onoff = (sc->sc_msr & SER_DCD) ? 1 : 0; + + DPRINTF("DCD changed to %d\n", onoff); + + ttydisc_modem(tp, onoff); + } + + if ((lsr_delta & ULSR_BI) && (sc->sc_lsr & ULSR_BI)) { + + DPRINTF("BREAK detected\n"); + + ttydisc_rint(tp, 0, TRE_BREAK); + ttydisc_rint_done(tp); + } + + if ((lsr_delta & ULSR_FE) && (sc->sc_lsr & ULSR_FE)) { + + DPRINTF("Frame error detected\n"); + + ttydisc_rint(tp, 0, TRE_FRAMING); + ttydisc_rint_done(tp); + } + + if ((lsr_delta & ULSR_PE) && (sc->sc_lsr & ULSR_PE)) { + + DPRINTF("Parity error detected\n"); + + ttydisc_rint(tp, 0, TRE_PARITY); + ttydisc_rint_done(tp); + } +} + +void +ucom_status_change(struct ucom_softc *sc) +{ + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) + return; /* not supported */ + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + return; + } + DPRINTF("\n"); + + ucom_queue_command(sc, ucom_cfg_status_change, NULL, + &sc->sc_status_task[0].hdr, + &sc->sc_status_task[1].hdr); +} + +static void +ucom_cfg_param(struct usb_proc_msg *_task) +{ + struct ucom_param_task *task = + (struct ucom_param_task *)_task; + struct ucom_softc *sc = task->sc; + + if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) { + return; + } + if (sc->sc_callback->ucom_cfg_param == NULL) { + return; + } + + (sc->sc_callback->ucom_cfg_param) (sc, &task->termios_copy); + + /* wait a little */ + usb_pause_mtx(sc->sc_mtx, hz / 10); +} + +static int +ucom_param(struct tty *tp, struct termios *t) +{ + struct ucom_softc *sc = tty_softc(tp); + uint8_t opened; + int error; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + opened = 0; + error = 0; + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + + /* XXX the TTY layer should call "open()" first! */ + /* + * Not quite: Its ordering is partly backwards, but + * some parameters must be set early in ttydev_open(), + * possibly before calling ttydevsw_open(). + */ + error = ucom_open(tp); + if (error) + goto done; + + opened = 1; + } + DPRINTF("sc = %p\n", sc); + + /* Check requested parameters. */ + if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) { + /* XXX c_ospeed == 0 is perfectly valid. */ + DPRINTF("mismatch ispeed and ospeed\n"); + error = EINVAL; + goto done; + } + t->c_ispeed = t->c_ospeed; + + if (sc->sc_callback->ucom_pre_param) { + /* Let the lower layer verify the parameters */ + error = (sc->sc_callback->ucom_pre_param) (sc, t); + if (error) { + DPRINTF("callback error = %d\n", error); + goto done; + } + } + + /* Disable transfers */ + sc->sc_flag &= ~UCOM_FLAG_GP_DATA; + + /* Queue baud rate programming command first */ + ucom_queue_command(sc, ucom_cfg_param, t, + &sc->sc_param_task[0].hdr, + &sc->sc_param_task[1].hdr); + + /* Queue transfer enable command last */ + ucom_queue_command(sc, ucom_cfg_start_transfers, NULL, + &sc->sc_start_task[0].hdr, + &sc->sc_start_task[1].hdr); + + if (t->c_cflag & CRTS_IFLOW) { + sc->sc_flag |= UCOM_FLAG_RTS_IFLOW; + } else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) { + sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW; + ucom_modem(tp, SER_RTS, 0); + } +done: + if (error) { + if (opened) { + ucom_close(tp); + } + } + return (error); +} + +static void +ucom_outwakeup(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + DPRINTF("sc = %p\n", sc); + + if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) { + /* The higher layer is not ready */ + return; + } + ucom_start_transfers(sc); +} + +static bool +ucom_busy(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + const uint8_t txidle = ULSR_TXRDY | ULSR_TSRE; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + DPRINTFN(3, "sc = %p lsr 0x%02x\n", sc, sc->sc_lsr); + + /* + * If the driver maintains the txidle bits in LSR, we can use them to + * determine whether the transmitter is busy or idle. Otherwise we have + * to assume it is idle to avoid hanging forever on tcdrain(3). + */ + if (sc->sc_flag & UCOM_FLAG_LSRTXIDLE) + return ((sc->sc_lsr & txidle) != txidle); + else + return (false); +} + +/*------------------------------------------------------------------------* + * ucom_get_data + * + * Return values: + * 0: No data is available. + * Else: Data is available. + *------------------------------------------------------------------------*/ +uint8_t +ucom_get_data(struct ucom_softc *sc, struct usb_page_cache *pc, + uint32_t offset, uint32_t len, uint32_t *actlen) +{ + struct usb_page_search res; + struct tty *tp = sc->sc_tty; + uint32_t cnt; + uint32_t offset_orig; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) { + unsigned int temp; + + /* get total TX length */ + + temp = ucom_cons_tx_high - ucom_cons_tx_low; + temp %= UCOM_CONS_BUFSIZE; + + /* limit TX length */ + + if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_tx_low)) + temp = (UCOM_CONS_BUFSIZE - ucom_cons_tx_low); + + if (temp > len) + temp = len; + + /* copy in data */ + + usbd_copy_in(pc, offset, ucom_cons_tx_buf + ucom_cons_tx_low, temp); + + /* update counters */ + + ucom_cons_tx_low += temp; + ucom_cons_tx_low %= UCOM_CONS_BUFSIZE; + + /* store actual length */ + + *actlen = temp; + + return (temp ? 1 : 0); + } + + if (tty_gone(tp) || + !(sc->sc_flag & UCOM_FLAG_GP_DATA)) { + actlen[0] = 0; + return (0); /* multiport device polling */ + } + offset_orig = offset; + + while (len != 0) { + + usbd_get_page(pc, offset, &res); + + if (res.length > len) { + res.length = len; + } + /* copy data directly into USB buffer */ + cnt = ttydisc_getc(tp, res.buffer, res.length); + + offset += cnt; + len -= cnt; + + if (cnt < res.length) { + /* end of buffer */ + break; + } + } + + actlen[0] = offset - offset_orig; + + DPRINTF("cnt=%d\n", actlen[0]); + + if (actlen[0] == 0) { + return (0); + } + return (1); +} + +void +ucom_put_data(struct ucom_softc *sc, struct usb_page_cache *pc, + uint32_t offset, uint32_t len) +{ + struct usb_page_search res; + struct tty *tp = sc->sc_tty; + char *buf; + uint32_t cnt; + + UCOM_MTX_ASSERT(sc, MA_OWNED); + + if (sc->sc_flag & UCOM_FLAG_CONSOLE) { + unsigned int temp; + + /* get maximum RX length */ + + temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_rx_high + ucom_cons_rx_low; + temp %= UCOM_CONS_BUFSIZE; + + /* limit RX length */ + + if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_rx_high)) + temp = (UCOM_CONS_BUFSIZE - ucom_cons_rx_high); + + if (temp > len) + temp = len; + + /* copy out data */ + + usbd_copy_out(pc, offset, ucom_cons_rx_buf + ucom_cons_rx_high, temp); + + /* update counters */ + + ucom_cons_rx_high += temp; + ucom_cons_rx_high %= UCOM_CONS_BUFSIZE; + + return; + } + + if (tty_gone(tp)) + return; /* multiport device polling */ + + if (len == 0) + return; /* no data */ + + /* set a flag to prevent recursation ? */ + + while (len > 0) { + + usbd_get_page(pc, offset, &res); + + if (res.length > len) { + res.length = len; + } + len -= res.length; + offset += res.length; + + /* pass characters to tty layer */ + + buf = res.buffer; + cnt = res.length; + + /* first check if we can pass the buffer directly */ + + if (ttydisc_can_bypass(tp)) { + + /* clear any jitter buffer */ + sc->sc_jitterbuf_in = 0; + sc->sc_jitterbuf_out = 0; + + if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) { + DPRINTF("tp=%p, data lost\n", tp); + } + continue; + } + /* need to loop */ + + for (cnt = 0; cnt != res.length; cnt++) { + if (sc->sc_jitterbuf_in != sc->sc_jitterbuf_out || + ttydisc_rint(tp, buf[cnt], 0) == -1) { + uint16_t end; + uint16_t pos; + + pos = sc->sc_jitterbuf_in; + end = sc->sc_jitterbuf_out + + UCOM_JITTERBUF_SIZE - 1; + if (end >= UCOM_JITTERBUF_SIZE) + end -= UCOM_JITTERBUF_SIZE; + + for (; cnt != res.length; cnt++) { + if (pos == end) + break; + sc->sc_jitterbuf[pos] = buf[cnt]; + pos++; + if (pos >= UCOM_JITTERBUF_SIZE) + pos -= UCOM_JITTERBUF_SIZE; + } + + sc->sc_jitterbuf_in = pos; + + /* set RTS in async fashion */ + if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) + ucom_rts(sc, 1); + + DPRINTF("tp=%p, lost %d " + "chars\n", tp, res.length - cnt); + break; + } + } + } + ttydisc_rint_done(tp); +} + +static void +ucom_free(void *xsc) +{ + struct ucom_softc *sc = xsc; + + if (sc->sc_callback->ucom_free != NULL) + sc->sc_callback->ucom_free(sc); + else + ucom_unref(sc->sc_super); + + mtx_lock(&ucom_mtx); + ucom_close_refs--; + mtx_unlock(&ucom_mtx); +} + +static cn_probe_t ucom_cnprobe; +static cn_init_t ucom_cninit; +static cn_term_t ucom_cnterm; +static cn_getc_t ucom_cngetc; +static cn_putc_t ucom_cnputc; +static cn_grab_t ucom_cngrab; +static cn_ungrab_t ucom_cnungrab; + +CONSOLE_DRIVER(ucom); + +static void +ucom_cnprobe(struct consdev *cp) +{ + if (ucom_cons_unit != -1) + cp->cn_pri = CN_NORMAL; + else + cp->cn_pri = CN_DEAD; + + strlcpy(cp->cn_name, "ucom", sizeof(cp->cn_name)); +} + +static void +ucom_cninit(struct consdev *cp) +{ +} + +static void +ucom_cnterm(struct consdev *cp) +{ +} + +static void +ucom_cngrab(struct consdev *cp) +{ +} + +static void +ucom_cnungrab(struct consdev *cp) +{ +} + +static int +ucom_cngetc(struct consdev *cd) +{ + struct ucom_softc *sc = ucom_cons_softc; + int c; + + if (sc == NULL) + return (-1); + + UCOM_MTX_LOCK(sc); + + if (ucom_cons_rx_low != ucom_cons_rx_high) { + c = ucom_cons_rx_buf[ucom_cons_rx_low]; + ucom_cons_rx_low ++; + ucom_cons_rx_low %= UCOM_CONS_BUFSIZE; + } else { + c = -1; + } + + /* start USB transfers */ + ucom_outwakeup(sc->sc_tty); + + UCOM_MTX_UNLOCK(sc); + + /* poll if necessary */ + if (USB_IN_POLLING_MODE_FUNC() && sc->sc_callback->ucom_poll) + (sc->sc_callback->ucom_poll) (sc); + + return (c); +} + +static void +ucom_cnputc(struct consdev *cd, int c) +{ + struct ucom_softc *sc = ucom_cons_softc; + unsigned int temp; + + if (sc == NULL) + return; + + repeat: + + UCOM_MTX_LOCK(sc); + + /* compute maximum TX length */ + + temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_tx_high + ucom_cons_tx_low; + temp %= UCOM_CONS_BUFSIZE; + + if (temp) { + ucom_cons_tx_buf[ucom_cons_tx_high] = c; + ucom_cons_tx_high ++; + ucom_cons_tx_high %= UCOM_CONS_BUFSIZE; + } + + /* start USB transfers */ + ucom_outwakeup(sc->sc_tty); + + UCOM_MTX_UNLOCK(sc); + + /* poll if necessary */ + if (USB_IN_POLLING_MODE_FUNC() && sc->sc_callback->ucom_poll) { + (sc->sc_callback->ucom_poll) (sc); + /* simple flow control */ + if (temp == 0) + goto repeat; + } +} + +/*------------------------------------------------------------------------* + * ucom_ref + * + * This function will increment the super UCOM reference count. + *------------------------------------------------------------------------*/ +void +ucom_ref(struct ucom_super_softc *ssc) +{ + mtx_lock(&ucom_mtx); + ssc->sc_refs++; + mtx_unlock(&ucom_mtx); +} + +/*------------------------------------------------------------------------* + * ucom_free_unit + * + * This function will free the super UCOM's allocated unit + * number. This function can be called on a zero-initialized + * structure. This function can be called multiple times. + *------------------------------------------------------------------------*/ +static void +ucom_free_unit(struct ucom_super_softc *ssc) +{ + if (!(ssc->sc_flag & UCOM_FLAG_FREE_UNIT)) + return; + + ucom_unit_free(ssc->sc_unit); + + ssc->sc_flag &= ~UCOM_FLAG_FREE_UNIT; +} + +/*------------------------------------------------------------------------* + * ucom_unref + * + * This function will decrement the super UCOM reference count. + * + * Return values: + * 0: UCOM structures are still referenced. + * Else: UCOM structures are no longer referenced. + *------------------------------------------------------------------------*/ +int +ucom_unref(struct ucom_super_softc *ssc) +{ + int retval; + + mtx_lock(&ucom_mtx); + retval = (ssc->sc_refs < 2); + ssc->sc_refs--; + mtx_unlock(&ucom_mtx); + + if (retval) + ucom_free_unit(ssc); + + return (retval); +} + +#if defined(GDB) + +#include <gdb/gdb.h> + +static gdb_probe_f ucom_gdbprobe; +static gdb_init_f ucom_gdbinit; +static gdb_term_f ucom_gdbterm; +static gdb_getc_f ucom_gdbgetc; +static gdb_putc_f ucom_gdbputc; + +GDB_DBGPORT(sio, ucom_gdbprobe, ucom_gdbinit, ucom_gdbterm, ucom_gdbgetc, ucom_gdbputc); + +static int +ucom_gdbprobe(void) +{ + return ((ucom_cons_softc != NULL) ? 0 : -1); +} + +static void +ucom_gdbinit(void) +{ +} + +static void +ucom_gdbterm(void) +{ +} + +static void +ucom_gdbputc(int c) +{ + ucom_cnputc(NULL, c); +} + +static int +ucom_gdbgetc(void) +{ + return (ucom_cngetc(NULL)); +} + +#endif diff --git a/freebsd/sys/dev/usb/serial/usb_serial.h b/freebsd/sys/dev/usb/serial/usb_serial.h new file mode 100644 index 00000000..2c53f445 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/usb_serial.h @@ -0,0 +1,230 @@ +/* $NetBSD: ucomvar.h,v 1.9 2001/01/23 21:56:17 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2001-2002, Shunsuke Akiyama <akiyama@jp.FreeBSD.org>. + * 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. + * 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. + */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * 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 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. + */ + +#ifndef _USB_SERIAL_H_ +#define _USB_SERIAL_H_ + +#include <sys/tty.h> +#include <sys/serial.h> +#include <sys/fcntl.h> +#include <sys/sysctl.h> +#include <sys/timepps.h> + +/* Module interface related macros */ +#define UCOM_MODVER 1 + +#define UCOM_MINVER 1 +#define UCOM_PREFVER UCOM_MODVER +#define UCOM_MAXVER 1 +#define UCOM_JITTERBUF_SIZE 128 /* bytes */ + +struct usb_device; +struct ucom_softc; +struct usb_device_request; +struct thread; + +/* + * NOTE: There is no guarantee that "ucom_cfg_close()" will + * be called after "ucom_cfg_open()" if the device is detached + * while it is open! + */ +struct ucom_callback { + void (*ucom_cfg_get_status) (struct ucom_softc *, uint8_t *plsr, uint8_t *pmsr); + void (*ucom_cfg_set_dtr) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_set_rts) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_set_break) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_set_ring) (struct ucom_softc *, uint8_t); + void (*ucom_cfg_param) (struct ucom_softc *, struct termios *); + void (*ucom_cfg_open) (struct ucom_softc *); + void (*ucom_cfg_close) (struct ucom_softc *); + int (*ucom_pre_open) (struct ucom_softc *); + int (*ucom_pre_param) (struct ucom_softc *, struct termios *); + int (*ucom_ioctl) (struct ucom_softc *, uint32_t, caddr_t, int, struct thread *); + void (*ucom_start_read) (struct ucom_softc *); + void (*ucom_stop_read) (struct ucom_softc *); + void (*ucom_start_write) (struct ucom_softc *); + void (*ucom_stop_write) (struct ucom_softc *); + void (*ucom_tty_name) (struct ucom_softc *, char *pbuf, uint16_t buflen, uint16_t unit, uint16_t subunit); + void (*ucom_poll) (struct ucom_softc *); + void (*ucom_free) (struct ucom_softc *); +}; + +/* Line status register */ +#define ULSR_RCV_FIFO 0x80 +#define ULSR_TSRE 0x40 /* Transmitter empty: byte sent */ +#define ULSR_TXRDY 0x20 /* Transmitter buffer empty */ +#define ULSR_BI 0x10 /* Break detected */ +#define ULSR_FE 0x08 /* Framing error: bad stop bit */ +#define ULSR_PE 0x04 /* Parity error */ +#define ULSR_OE 0x02 /* Overrun, lost incoming byte */ +#define ULSR_RXRDY 0x01 /* Byte ready in Receive Buffer */ +#define ULSR_RCV_MASK 0x1f /* Mask for incoming data or error */ + +struct ucom_cfg_task { + struct usb_proc_msg hdr; + struct ucom_softc *sc; +}; + +struct ucom_param_task { + struct usb_proc_msg hdr; + struct ucom_softc *sc; + struct termios termios_copy; +}; + +struct ucom_super_softc { + struct usb_process sc_tq; + int sc_unit; + int sc_subunits; + int sc_refs; + int sc_flag; /* see UCOM_FLAG_XXX */ + struct sysctl_oid *sc_sysctl_ttyname; + struct sysctl_oid *sc_sysctl_ttyports; + char sc_ttyname[16]; +}; + +struct ucom_softc { + /* + * NOTE: To avoid losing level change information we use two + * tasks instead of one for all commands. + * + * Level changes are transitions like: + * + * ON->OFF + * OFF->ON + * OPEN->CLOSE + * CLOSE->OPEN + */ + struct ucom_cfg_task sc_start_task[2]; + struct ucom_cfg_task sc_open_task[2]; + struct ucom_cfg_task sc_close_task[2]; + struct ucom_cfg_task sc_line_state_task[2]; + struct ucom_cfg_task sc_status_task[2]; + struct ucom_param_task sc_param_task[2]; + /* pulse capturing support, PPS */ + struct pps_state sc_pps; + /* Used to set "UCOM_FLAG_GP_DATA" flag: */ + struct usb_proc_msg *sc_last_start_xfer; + const struct ucom_callback *sc_callback; + struct ucom_super_softc *sc_super; + struct tty *sc_tty; + struct mtx *sc_mtx; + void *sc_parent; + int sc_subunit; + uint16_t sc_jitterbuf_in; + uint16_t sc_jitterbuf_out; + uint16_t sc_portno; + uint16_t sc_flag; +#define UCOM_FLAG_RTS_IFLOW 0x01 /* use RTS input flow control */ +#define UCOM_FLAG_GONE 0x02 /* the device is gone */ +#define UCOM_FLAG_ATTACHED 0x04 /* set if attached */ +#define UCOM_FLAG_GP_DATA 0x08 /* set if get and put data is possible */ +#define UCOM_FLAG_LL_READY 0x20 /* set if low layer is ready */ +#define UCOM_FLAG_HL_READY 0x40 /* set if high layer is ready */ +#define UCOM_FLAG_CONSOLE 0x80 /* set if device is a console */ +#define UCOM_FLAG_WAIT_REFS 0x0100 /* set if we must wait for refs */ +#define UCOM_FLAG_FREE_UNIT 0x0200 /* set if we must free the unit */ +#define UCOM_FLAG_INWAKEUP 0x0400 /* set if we are in the tsw_inwakeup callback */ +#define UCOM_FLAG_LSRTXIDLE 0x0800 /* set if sc_lsr bits ULSR_TSRE+TXRDY work */ + uint8_t sc_lsr; + uint8_t sc_msr; + uint8_t sc_mcr; + /* programmed line state bits */ + uint8_t sc_pls_set; /* set bits */ + uint8_t sc_pls_clr; /* cleared bits */ + uint8_t sc_pls_curr; /* last state */ +#define UCOM_LS_DTR 0x01 +#define UCOM_LS_RTS 0x02 +#define UCOM_LS_BREAK 0x04 +#define UCOM_LS_RING 0x08 + uint8_t sc_jitterbuf[UCOM_JITTERBUF_SIZE]; +}; + +#define UCOM_MTX_ASSERT(sc, what) USB_MTX_ASSERT((sc)->sc_mtx, what) +#define UCOM_MTX_LOCK(sc) USB_MTX_LOCK((sc)->sc_mtx) +#define UCOM_MTX_UNLOCK(sc) USB_MTX_UNLOCK((sc)->sc_mtx) +#define UCOM_UNLOAD_DRAIN(x) \ +SYSUNINIT(var, SI_SUB_KLD - 2, SI_ORDER_ANY, ucom_drain_all, 0) + +#define ucom_cfg_do_request(udev,com,req,ptr,flags,timo) \ + usbd_do_request_proc(udev,&(com)->sc_super->sc_tq,req,ptr,flags,NULL,timo) + +int ucom_attach(struct ucom_super_softc *, + struct ucom_softc *, int, void *, + const struct ucom_callback *callback, struct mtx *); +void ucom_detach(struct ucom_super_softc *, struct ucom_softc *); +void ucom_set_pnpinfo_usb(struct ucom_super_softc *, device_t); +void ucom_status_change(struct ucom_softc *); +uint8_t ucom_get_data(struct ucom_softc *, struct usb_page_cache *, + uint32_t, uint32_t, uint32_t *); +void ucom_put_data(struct ucom_softc *, struct usb_page_cache *, + uint32_t, uint32_t); +uint8_t ucom_cfg_is_gone(struct ucom_softc *); +void ucom_drain(struct ucom_super_softc *); +void ucom_drain_all(void *); +void ucom_ref(struct ucom_super_softc *); +int ucom_unref(struct ucom_super_softc *); + +static inline void +ucom_use_lsr_txbits(struct ucom_softc *sc) +{ + + sc->sc_flag |= UCOM_FLAG_LSRTXIDLE; +} + +#endif /* _USB_SERIAL_H_ */ diff --git a/freebsd/sys/dev/usb/serial/uslcom.c b/freebsd/sys/dev/usb/serial/uslcom.c new file mode 100644 index 00000000..03cc96bc --- /dev/null +++ b/freebsd/sys/dev/usb/serial/uslcom.c @@ -0,0 +1,946 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $OpenBSD: uslcom.c,v 1.17 2007/11/24 10:52:12 jsg Exp $ */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Copyright (c) 2006 Jonathan Gray <jsg@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 Silicon Laboratories CP2101/CP2102/CP2103/CP2104/CP2105 + * USB-Serial adapters. Based on datasheet AN571, publicly available from + * http://www.silabs.com/Support%20Documents/TechnicalDocs/AN571.pdf + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_ioctl.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR uslcom_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#ifdef USB_DEBUG +static int uslcom_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uslcom, CTLFLAG_RW, 0, "USB uslcom"); +SYSCTL_INT(_hw_usb_uslcom, OID_AUTO, debug, CTLFLAG_RWTUN, + &uslcom_debug, 0, "Debug level"); +#endif + +#define USLCOM_BULK_BUF_SIZE 1024 +#define USLCOM_CONFIG_INDEX 0 + +/* Request types */ +#define USLCOM_WRITE 0x41 +#define USLCOM_READ 0xc1 + +/* Request codes */ +#define USLCOM_IFC_ENABLE 0x00 +#define USLCOM_SET_BAUDDIV 0x01 +#define USLCOM_SET_LINE_CTL 0x03 +#define USLCOM_SET_BREAK 0x05 +#define USLCOM_SET_MHS 0x07 +#define USLCOM_GET_MDMSTS 0x08 +#define USLCOM_SET_FLOW 0x13 +#define USLCOM_SET_BAUDRATE 0x1e +#define USLCOM_VENDOR_SPECIFIC 0xff + +/* USLCOM_IFC_ENABLE values */ +#define USLCOM_IFC_ENABLE_DIS 0x00 +#define USLCOM_IFC_ENABLE_EN 0x01 + +/* USLCOM_SET_MHS/USLCOM_GET_MDMSTS values */ +#define USLCOM_MHS_DTR_ON 0x0001 +#define USLCOM_MHS_DTR_SET 0x0100 +#define USLCOM_MHS_RTS_ON 0x0002 +#define USLCOM_MHS_RTS_SET 0x0200 +#define USLCOM_MHS_CTS 0x0010 +#define USLCOM_MHS_DSR 0x0020 +#define USLCOM_MHS_RI 0x0040 +#define USLCOM_MHS_DCD 0x0080 + +/* USLCOM_SET_BAUDDIV values */ +#define USLCOM_BAUDDIV_REF 3686400 /* 3.6864 MHz */ + +/* USLCOM_SET_LINE_CTL values */ +#define USLCOM_STOP_BITS_1 0x00 +#define USLCOM_STOP_BITS_2 0x02 +#define USLCOM_PARITY_NONE 0x00 +#define USLCOM_PARITY_ODD 0x10 +#define USLCOM_PARITY_EVEN 0x20 +#define USLCOM_SET_DATA_BITS(x) ((x) << 8) + +/* USLCOM_SET_BREAK values */ +#define USLCOM_SET_BREAK_OFF 0x00 +#define USLCOM_SET_BREAK_ON 0x01 + +/* USLCOM_SET_FLOW values - 1st word */ +#define USLCOM_FLOW_DTR_ON 0x00000001 /* DTR static active */ +#define USLCOM_FLOW_CTS_HS 0x00000008 /* CTS handshake */ +/* USLCOM_SET_FLOW values - 2nd word */ +#define USLCOM_FLOW_RTS_ON 0x00000040 /* RTS static active */ +#define USLCOM_FLOW_RTS_HS 0x00000080 /* RTS handshake */ + +/* USLCOM_VENDOR_SPECIFIC values */ +#define USLCOM_GET_PARTNUM 0x370B +#define USLCOM_WRITE_LATCH 0x37E1 +#define USLCOM_READ_LATCH 0x00C2 + +/* USLCOM_GET_PARTNUM values from hardware */ +#define USLCOM_PARTNUM_CP2101 1 +#define USLCOM_PARTNUM_CP2102 2 +#define USLCOM_PARTNUM_CP2103 3 +#define USLCOM_PARTNUM_CP2104 4 +#define USLCOM_PARTNUM_CP2105 5 + +enum { + USLCOM_BULK_DT_WR, + USLCOM_BULK_DT_RD, + USLCOM_CTRL_DT_RD, + USLCOM_N_TRANSFER, +}; + +struct uslcom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + struct usb_callout sc_watchdog; + + struct usb_xfer *sc_xfer[USLCOM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint8_t sc_msr; + uint8_t sc_lsr; + uint8_t sc_iface_no; + uint8_t sc_partnum; +}; + +static device_probe_t uslcom_probe; +static device_attach_t uslcom_attach; +static device_detach_t uslcom_detach; +static void uslcom_free_softc(struct uslcom_softc *); + +static usb_callback_t uslcom_write_callback; +static usb_callback_t uslcom_read_callback; +static usb_callback_t uslcom_control_callback; + +static void uslcom_free(struct ucom_softc *); +static void uslcom_open(struct ucom_softc *); +static void uslcom_close(struct ucom_softc *); +static uint8_t uslcom_get_partnum(struct uslcom_softc *); +static void uslcom_set_dtr(struct ucom_softc *, uint8_t); +static void uslcom_set_rts(struct ucom_softc *, uint8_t); +static void uslcom_set_break(struct ucom_softc *, uint8_t); +static int uslcom_ioctl(struct ucom_softc *, uint32_t, caddr_t, int, + struct thread *); +static int uslcom_pre_param(struct ucom_softc *, struct termios *); +static void uslcom_param(struct ucom_softc *, struct termios *); +static void uslcom_get_status(struct ucom_softc *, uint8_t *, uint8_t *); +static void uslcom_start_read(struct ucom_softc *); +static void uslcom_stop_read(struct ucom_softc *); +static void uslcom_start_write(struct ucom_softc *); +static void uslcom_stop_write(struct ucom_softc *); +static void uslcom_poll(struct ucom_softc *ucom); + +static const struct usb_config uslcom_config[USLCOM_N_TRANSFER] = { + + [USLCOM_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = USLCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,}, + .callback = &uslcom_write_callback, + }, + + [USLCOM_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = USLCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uslcom_read_callback, + }, + [USLCOM_CTRL_DT_RD] = { + .type = UE_CONTROL, + .endpoint = 0x00, + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request) + 8, + .flags = {.pipe_bof = 1,}, + .callback = &uslcom_control_callback, + .timeout = 1000, /* 1 second timeout */ + }, +}; + +static struct ucom_callback uslcom_callback = { + .ucom_cfg_open = &uslcom_open, + .ucom_cfg_close = &uslcom_close, + .ucom_cfg_get_status = &uslcom_get_status, + .ucom_cfg_set_dtr = &uslcom_set_dtr, + .ucom_cfg_set_rts = &uslcom_set_rts, + .ucom_cfg_set_break = &uslcom_set_break, + .ucom_ioctl = &uslcom_ioctl, + .ucom_cfg_param = &uslcom_param, + .ucom_pre_param = &uslcom_pre_param, + .ucom_start_read = &uslcom_start_read, + .ucom_stop_read = &uslcom_stop_read, + .ucom_start_write = &uslcom_start_write, + .ucom_stop_write = &uslcom_stop_write, + .ucom_poll = &uslcom_poll, + .ucom_free = &uslcom_free, +}; + +static const STRUCT_USB_HOST_ID uslcom_devs[] = { +#define USLCOM_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) } + USLCOM_DEV(BALTECH, CARDREADER), + USLCOM_DEV(CLIPSAL, 5000CT2), + USLCOM_DEV(CLIPSAL, 5500PACA), + USLCOM_DEV(CLIPSAL, 5500PCU), + USLCOM_DEV(CLIPSAL, 560884), + USLCOM_DEV(CLIPSAL, 5800PC), + USLCOM_DEV(CLIPSAL, C5000CT2), + USLCOM_DEV(CLIPSAL, L51xx), + USLCOM_DEV(DATAAPEX, MULTICOM), + USLCOM_DEV(DELL, DW700), + USLCOM_DEV(DIGIANSWER, ZIGBEE802154), + USLCOM_DEV(DYNASTREAM, ANTDEVBOARD), + USLCOM_DEV(DYNASTREAM, ANTDEVBOARD2), + USLCOM_DEV(DYNASTREAM, ANT2USB), + USLCOM_DEV(ELV, USBI2C), + USLCOM_DEV(FESTO, CMSP), + USLCOM_DEV(FESTO, CPX_USB), + USLCOM_DEV(FOXCONN, PIRELLI_DP_L10), + USLCOM_DEV(FOXCONN, TCOM_TC_300), + USLCOM_DEV(GEMALTO, PROXPU), + USLCOM_DEV(JABLOTRON, PC60B), + USLCOM_DEV(KAMSTRUP, OPTICALEYE), + USLCOM_DEV(KAMSTRUP, MBUS_250D), + USLCOM_DEV(LAKESHORE, 121), + USLCOM_DEV(LAKESHORE, 218A), + USLCOM_DEV(LAKESHORE, 219), + USLCOM_DEV(LAKESHORE, 233), + USLCOM_DEV(LAKESHORE, 235), + USLCOM_DEV(LAKESHORE, 335), + USLCOM_DEV(LAKESHORE, 336), + USLCOM_DEV(LAKESHORE, 350), + USLCOM_DEV(LAKESHORE, 371), + USLCOM_DEV(LAKESHORE, 411), + USLCOM_DEV(LAKESHORE, 425), + USLCOM_DEV(LAKESHORE, 455A), + USLCOM_DEV(LAKESHORE, 465), + USLCOM_DEV(LAKESHORE, 475A), + USLCOM_DEV(LAKESHORE, 625A), + USLCOM_DEV(LAKESHORE, 642A), + USLCOM_DEV(LAKESHORE, 648), + USLCOM_DEV(LAKESHORE, 737), + USLCOM_DEV(LAKESHORE, 776), + USLCOM_DEV(LINKINSTRUMENTS, MSO19), + USLCOM_DEV(LINKINSTRUMENTS, MSO28), + USLCOM_DEV(LINKINSTRUMENTS, MSO28_2), + USLCOM_DEV(MEI, CASHFLOW_SC), + USLCOM_DEV(MEI, S2000), + USLCOM_DEV(NETGEAR, M4100), + USLCOM_DEV(OWEN, AC4), + USLCOM_DEV(OWL, CM_160), + USLCOM_DEV(PHILIPS, ACE1001), + USLCOM_DEV(PLX, CA42), + USLCOM_DEV(RENESAS, RX610), + USLCOM_DEV(SEL, C662), + USLCOM_DEV(SILABS, AC_SERV_CAN), + USLCOM_DEV(SILABS, AC_SERV_CIS), + USLCOM_DEV(SILABS, AC_SERV_IBUS), + USLCOM_DEV(SILABS, AC_SERV_OBD), + USLCOM_DEV(SILABS, AEROCOMM), + USLCOM_DEV(SILABS, AMBER_AMB2560), + USLCOM_DEV(SILABS, ARGUSISP), + USLCOM_DEV(SILABS, ARKHAM_DS101_A), + USLCOM_DEV(SILABS, ARKHAM_DS101_M), + USLCOM_DEV(SILABS, ARYGON_MIFARE), + USLCOM_DEV(SILABS, AVIT_USB_TTL), + USLCOM_DEV(SILABS, B_G_H3000), + USLCOM_DEV(SILABS, BALLUFF_RFID), + USLCOM_DEV(SILABS, BEI_VCP), + USLCOM_DEV(SILABS, BSM7DUSB), + USLCOM_DEV(SILABS, BURNSIDE), + USLCOM_DEV(SILABS, C2_EDGE_MODEM), + USLCOM_DEV(SILABS, CP2102), + USLCOM_DEV(SILABS, CP210X_2), + USLCOM_DEV(SILABS, CP210X_3), + USLCOM_DEV(SILABS, CP210X_4), + USLCOM_DEV(SILABS, CRUMB128), + USLCOM_DEV(SILABS, CYGNAL), + USLCOM_DEV(SILABS, CYGNAL_DEBUG), + USLCOM_DEV(SILABS, CYGNAL_GPS), + USLCOM_DEV(SILABS, DEGREE), + USLCOM_DEV(SILABS, DEKTEK_DTAPLUS), + USLCOM_DEV(SILABS, EMS_C1007), + USLCOM_DEV(SILABS, HAMLINKUSB), + USLCOM_DEV(SILABS, HELICOM), + USLCOM_DEV(SILABS, IMS_USB_RS422), + USLCOM_DEV(SILABS, INFINITY_MIC), + USLCOM_DEV(SILABS, INGENI_ZIGBEE), + USLCOM_DEV(SILABS, INSYS_MODEM), + USLCOM_DEV(SILABS, IRZ_SG10), + USLCOM_DEV(SILABS, KYOCERA_GPS), + USLCOM_DEV(SILABS, LIPOWSKY_HARP), + USLCOM_DEV(SILABS, LIPOWSKY_JTAG), + USLCOM_DEV(SILABS, LIPOWSKY_LIN), + USLCOM_DEV(SILABS, MC35PU), + USLCOM_DEV(SILABS, MMB_ZIGBEE), + USLCOM_DEV(SILABS, MJS_TOSLINK), + USLCOM_DEV(SILABS, MSD_DASHHAWK), + USLCOM_DEV(SILABS, MULTIPLEX_RC), + USLCOM_DEV(SILABS, OPTRIS_MSPRO), + USLCOM_DEV(SILABS, POLOLU), + USLCOM_DEV(SILABS, PROCYON_AVS), + USLCOM_DEV(SILABS, SB_PARAMOUNT_ME), + USLCOM_DEV(SILABS, SUUNTO), + USLCOM_DEV(SILABS, TAMSMASTER), + USLCOM_DEV(SILABS, TELEGESIS_ETRX2), + USLCOM_DEV(SILABS, TRACIENT), + USLCOM_DEV(SILABS, TRAQMATE), + USLCOM_DEV(SILABS, USBCOUNT50), + USLCOM_DEV(SILABS, USBPULSE100), + USLCOM_DEV(SILABS, USBSCOPE50), + USLCOM_DEV(SILABS, USBWAVE12), + USLCOM_DEV(SILABS, V_PREON32), + USLCOM_DEV(SILABS, VSTABI), + USLCOM_DEV(SILABS, WAVIT), + USLCOM_DEV(SILABS, WMRBATT), + USLCOM_DEV(SILABS, WMRRIGBLASTER), + USLCOM_DEV(SILABS, WMRRIGTALK), + USLCOM_DEV(SILABS, ZEPHYR_BIO), + USLCOM_DEV(SILABS2, DCU11CLONE), + USLCOM_DEV(SILABS3, GPRS_MODEM), + USLCOM_DEV(SILABS4, 100EU_MODEM), + USLCOM_DEV(SYNTECH, CYPHERLAB100), + USLCOM_DEV(USI, MC60), + USLCOM_DEV(VAISALA, CABLE), + USLCOM_DEV(WAGO, SERVICECABLE), + USLCOM_DEV(WAVESENSE, JAZZ), + USLCOM_DEV(WESTMOUNTAIN, RIGBLASTER_ADVANTAGE), + USLCOM_DEV(WIENERPLEINBAUS, PL512), + USLCOM_DEV(WIENERPLEINBAUS, RCM), + USLCOM_DEV(WIENERPLEINBAUS, MPOD), + USLCOM_DEV(WIENERPLEINBAUS, CML), +#undef USLCOM_DEV +}; + +static device_method_t uslcom_methods[] = { + DEVMETHOD(device_probe, uslcom_probe), + DEVMETHOD(device_attach, uslcom_attach), + DEVMETHOD(device_detach, uslcom_detach), + DEVMETHOD_END +}; + +static devclass_t uslcom_devclass; + +static driver_t uslcom_driver = { + .name = "uslcom", + .methods = uslcom_methods, + .size = sizeof(struct uslcom_softc), +}; + +DRIVER_MODULE(uslcom, uhub, uslcom_driver, uslcom_devclass, NULL, 0); +MODULE_DEPEND(uslcom, ucom, 1, 1, 1); +MODULE_DEPEND(uslcom, usb, 1, 1, 1); +MODULE_VERSION(uslcom, 1); +USB_PNP_HOST_INFO(uslcom_devs); + +static void +uslcom_watchdog(void *arg) +{ + struct uslcom_softc *sc = arg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + usbd_transfer_start(sc->sc_xfer[USLCOM_CTRL_DT_RD]); + + usb_callout_reset(&sc->sc_watchdog, + hz / 4, &uslcom_watchdog, sc); +} + +static int +uslcom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != USLCOM_CONFIG_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uslcom_devs, sizeof(uslcom_devs), uaa)); +} + +static int +uslcom_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uslcom_softc *sc = device_get_softc(dev); + int error; + + DPRINTFN(11, "\n"); + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uslcom", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0); + + sc->sc_udev = uaa->device; + /* use the interface number from the USB interface descriptor */ + sc->sc_iface_no = uaa->info.bIfaceNum; + + error = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, uslcom_config, + USLCOM_N_TRANSFER, sc, &sc->sc_mtx); + if (error) { + DPRINTF("one or more missing USB endpoints, " + "error=%s\n", usbd_errstr(error)); + goto detach; + } + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[USLCOM_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[USLCOM_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + sc->sc_partnum = uslcom_get_partnum(sc); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uslcom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + uslcom_detach(dev); + return (ENXIO); +} + +static int +uslcom_detach(device_t dev) +{ + struct uslcom_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, USLCOM_N_TRANSFER); + + usb_callout_drain(&sc->sc_watchdog); + + device_claim_softc(dev); + + uslcom_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(uslcom); + +static void +uslcom_free_softc(struct uslcom_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +uslcom_free(struct ucom_softc *ucom) +{ + uslcom_free_softc(ucom->sc_parent); +} + +static void +uslcom_open(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_IFC_ENABLE; + USETW(req.wValue, USLCOM_IFC_ENABLE_EN); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("UART enable failed (ignored)\n"); + } + + /* start polling status */ + uslcom_watchdog(sc); +} + +static void +uslcom_close(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + + /* stop polling status */ + usb_callout_stop(&sc->sc_watchdog); + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_IFC_ENABLE; + USETW(req.wValue, USLCOM_IFC_ENABLE_DIS); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("UART disable failed (ignored)\n"); + } +} + +static uint8_t +uslcom_get_partnum(struct uslcom_softc *sc) +{ + struct usb_device_request req; + uint8_t partnum; + + /* Find specific chip type */ + partnum = 0; + req.bmRequestType = USLCOM_READ; + req.bRequest = USLCOM_VENDOR_SPECIFIC; + USETW(req.wValue, USLCOM_GET_PARTNUM); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, sizeof(partnum)); + + if (usbd_do_request_flags(sc->sc_udev, NULL, + &req, &partnum, 0, NULL, 1000)) { + DPRINTF("GET_PARTNUM failed\n"); + } + + return(partnum); +} + +static void +uslcom_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t ctl; + + DPRINTF("onoff = %d\n", onoff); + + ctl = onoff ? USLCOM_MHS_DTR_ON : 0; + ctl |= USLCOM_MHS_DTR_SET; + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_SET_MHS; + USETW(req.wValue, ctl); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("Setting DTR failed (ignored)\n"); + } +} + +static void +uslcom_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t ctl; + + DPRINTF("onoff = %d\n", onoff); + + ctl = onoff ? USLCOM_MHS_RTS_ON : 0; + ctl |= USLCOM_MHS_RTS_SET; + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_SET_MHS; + USETW(req.wValue, ctl); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("Setting DTR failed (ignored)\n"); + } +} + +static int +uslcom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + if (t->c_ospeed <= 0 || t->c_ospeed > 921600) + return (EINVAL); + return (0); +} + +static void +uslcom_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint32_t baudrate, flowctrl[4]; + uint16_t data; + + DPRINTF("\n"); + + baudrate = t->c_ospeed; + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_SET_BAUDRATE; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, sizeof(baudrate)); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &baudrate, 0, 1000)) { + DPRINTF("Set baudrate failed (ignored)\n"); + } + + if (t->c_cflag & CSTOPB) + data = USLCOM_STOP_BITS_2; + else + data = USLCOM_STOP_BITS_1; + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) + data |= USLCOM_PARITY_ODD; + else + data |= USLCOM_PARITY_EVEN; + } else + data |= USLCOM_PARITY_NONE; + switch (t->c_cflag & CSIZE) { + case CS5: + data |= USLCOM_SET_DATA_BITS(5); + break; + case CS6: + data |= USLCOM_SET_DATA_BITS(6); + break; + case CS7: + data |= USLCOM_SET_DATA_BITS(7); + break; + case CS8: + data |= USLCOM_SET_DATA_BITS(8); + break; + } + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_SET_LINE_CTL; + USETW(req.wValue, data); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("Set format failed (ignored)\n"); + } + + if (t->c_cflag & CRTSCTS) { + flowctrl[0] = htole32(USLCOM_FLOW_DTR_ON | USLCOM_FLOW_CTS_HS); + flowctrl[1] = htole32(USLCOM_FLOW_RTS_HS); + } else { + flowctrl[0] = htole32(USLCOM_FLOW_DTR_ON); + flowctrl[1] = htole32(USLCOM_FLOW_RTS_ON); + } + flowctrl[2] = 0; + flowctrl[3] = 0; + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_SET_FLOW; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, sizeof(flowctrl)); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, flowctrl, 0, 1000)) { + DPRINTF("Set flowcontrol failed (ignored)\n"); + } +} + +static void +uslcom_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uslcom_softc *sc = ucom->sc_parent; + + DPRINTF("\n"); + + /* XXX Note: sc_lsr is always zero */ + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uslcom_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + uint16_t brk = onoff ? USLCOM_SET_BREAK_ON : USLCOM_SET_BREAK_OFF; + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_SET_BREAK; + USETW(req.wValue, brk); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("Set BREAK failed (ignored)\n"); + } +} + +static int +uslcom_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data, + int flag, struct thread *td) +{ + struct uslcom_softc *sc = ucom->sc_parent; + struct usb_device_request req; + int error = 0; + uint8_t latch; + + DPRINTF("cmd=0x%08x\n", cmd); + + switch (cmd) { + case USB_GET_GPIO: + if (sc->sc_partnum < USLCOM_PARTNUM_CP2103) { + error = ENODEV; + break; + } + req.bmRequestType = USLCOM_READ; + req.bRequest = USLCOM_VENDOR_SPECIFIC; + USETW(req.wValue, USLCOM_READ_LATCH); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, sizeof(latch)); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, &latch, 0, 1000)) { + DPRINTF("Get LATCH failed\n"); + error = EIO; + } + *(int *)data = latch; + break; + + case USB_SET_GPIO: + if (sc->sc_partnum < USLCOM_PARTNUM_CP2103) + error = ENODEV; + else if ((sc->sc_partnum == USLCOM_PARTNUM_CP2103) || + (sc->sc_partnum == USLCOM_PARTNUM_CP2104)) { + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_VENDOR_SPECIFIC; + USETW(req.wValue, USLCOM_WRITE_LATCH); + USETW(req.wIndex, (*(int *)data)); + USETW(req.wLength, 0); + + if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000)) { + DPRINTF("Set LATCH failed\n"); + error = EIO; + } + } else + error = ENODEV; /* Not yet */ + break; + + default: + DPRINTF("Unknown IOCTL\n"); + error = ENOIOCTL; + break; + } + return (error); +} + +static void +uslcom_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uslcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + USLCOM_BULK_BUF_SIZE, &actlen)) { + + DPRINTF("actlen = %d\n", actlen); + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uslcom_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uslcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uslcom_control_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uslcom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + struct usb_device_request req; + uint8_t msr = 0; + uint8_t buf; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 1); + usbd_copy_out(pc, 0, &buf, sizeof(buf)); + if (buf & USLCOM_MHS_CTS) + msr |= SER_CTS; + if (buf & USLCOM_MHS_DSR) + msr |= SER_DSR; + if (buf & USLCOM_MHS_RI) + msr |= SER_RI; + if (buf & USLCOM_MHS_DCD) + msr |= SER_DCD; + + if (msr != sc->sc_msr) { + DPRINTF("status change msr=0x%02x " + "(was 0x%02x)\n", msr, sc->sc_msr); + sc->sc_msr = msr; + ucom_status_change(&sc->sc_ucom); + } + break; + + case USB_ST_SETUP: + req.bmRequestType = USLCOM_READ; + req.bRequest = USLCOM_GET_MDMSTS; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_iface_no); + USETW(req.wLength, sizeof(buf)); + + usbd_xfer_set_frames(xfer, 2); + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, sizeof(buf)); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + usbd_transfer_submit(xfer); + break; + + default: /* error */ + if (error != USB_ERR_CANCELLED) + DPRINTF("error=%s\n", usbd_errstr(error)); + break; + } +} + +static void +uslcom_start_read(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + + /* start read endpoint */ + usbd_transfer_start(sc->sc_xfer[USLCOM_BULK_DT_RD]); +} + +static void +uslcom_stop_read(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + + /* stop read endpoint */ + usbd_transfer_stop(sc->sc_xfer[USLCOM_BULK_DT_RD]); +} + +static void +uslcom_start_write(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[USLCOM_BULK_DT_WR]); +} + +static void +uslcom_stop_write(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[USLCOM_BULK_DT_WR]); +} + +static void +uslcom_poll(struct ucom_softc *ucom) +{ + struct uslcom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, USLCOM_N_TRANSFER); +} diff --git a/freebsd/sys/dev/usb/serial/uvisor.c b/freebsd/sys/dev/usb/serial/uvisor.c new file mode 100644 index 00000000..e26c596e --- /dev/null +++ b/freebsd/sys/dev/usb/serial/uvisor.c @@ -0,0 +1,679 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $NetBSD: uvisor.c,v 1.9 2001/01/23 14:04:14 augustss Exp $ */ +/* $FreeBSD$ */ + +/* Also already merged from NetBSD: + * $NetBSD: uvisor.c,v 1.12 2001/11/13 06:24:57 lukem Exp $ + * $NetBSD: uvisor.c,v 1.13 2002/02/11 15:11:49 augustss Exp $ + * $NetBSD: uvisor.c,v 1.14 2002/02/27 23:00:03 augustss Exp $ + * $NetBSD: uvisor.c,v 1.15 2002/06/16 15:01:31 augustss Exp $ + * $NetBSD: uvisor.c,v 1.16 2002/07/11 21:14:36 augustss Exp $ + * $NetBSD: uvisor.c,v 1.17 2002/08/13 11:38:15 augustss Exp $ + * $NetBSD: uvisor.c,v 1.18 2003/02/05 00:50:14 augustss Exp $ + * $NetBSD: uvisor.c,v 1.19 2003/02/07 18:12:37 augustss Exp $ + * $NetBSD: uvisor.c,v 1.20 2003/04/11 01:30:10 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) at + * Carlstedt Research & Technology. + * + * 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 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. + */ + +/* + * Handspring Visor (Palmpilot compatible PDA) driver + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR uvisor_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#ifdef USB_DEBUG +static int uvisor_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uvisor, CTLFLAG_RW, 0, "USB uvisor"); +SYSCTL_INT(_hw_usb_uvisor, OID_AUTO, debug, CTLFLAG_RWTUN, + &uvisor_debug, 0, "Debug level"); +#endif + +#define UVISOR_CONFIG_INDEX 0 +#define UVISOR_IFACE_INDEX 0 + +/* + * The following buffer sizes are hardcoded due to the way the Palm + * firmware works. It looks like the device is not short terminating + * the data transferred. + */ +#define UVISORIBUFSIZE 0 /* Use wMaxPacketSize */ +#define UVISOROBUFSIZE 32 /* bytes */ +#define UVISOROFRAMES 32 /* units */ + +/* From the Linux driver */ +/* + * UVISOR_REQUEST_BYTES_AVAILABLE asks the visor for the number of bytes that + * are available to be transferred to the host for the specified endpoint. + * Currently this is not used, and always returns 0x0001 + */ +#define UVISOR_REQUEST_BYTES_AVAILABLE 0x01 + +/* + * UVISOR_CLOSE_NOTIFICATION is set to the device to notify it that the host + * is now closing the pipe. An empty packet is sent in response. + */ +#define UVISOR_CLOSE_NOTIFICATION 0x02 + +/* + * UVISOR_GET_CONNECTION_INFORMATION is sent by the host during enumeration to + * get the endpoints used by the connection. + */ +#define UVISOR_GET_CONNECTION_INFORMATION 0x03 + +/* + * UVISOR_GET_CONNECTION_INFORMATION returns data in the following format + */ +#define UVISOR_MAX_CONN 8 +struct uvisor_connection_info { + uWord num_ports; + struct { + uByte port_function_id; + uByte port; + } __packed connections[UVISOR_MAX_CONN]; +} __packed; + +#define UVISOR_CONNECTION_INFO_SIZE 18 + +/* struct uvisor_connection_info.connection[x].port defines: */ +#define UVISOR_ENDPOINT_1 0x01 +#define UVISOR_ENDPOINT_2 0x02 + +/* struct uvisor_connection_info.connection[x].port_function_id defines: */ +#define UVISOR_FUNCTION_GENERIC 0x00 +#define UVISOR_FUNCTION_DEBUGGER 0x01 +#define UVISOR_FUNCTION_HOTSYNC 0x02 +#define UVISOR_FUNCTION_CONSOLE 0x03 +#define UVISOR_FUNCTION_REMOTE_FILE_SYS 0x04 + +/* + * Unknown PalmOS stuff. + */ +#define UVISOR_GET_PALM_INFORMATION 0x04 +#define UVISOR_GET_PALM_INFORMATION_LEN 0x44 + +struct uvisor_palm_connection_info { + uByte num_ports; + uByte endpoint_numbers_different; + uWord reserved1; + struct { + uDWord port_function_id; + uByte port; + uByte end_point_info; + uWord reserved; + } __packed connections[UVISOR_MAX_CONN]; +} __packed; + +enum { + UVISOR_BULK_DT_WR, + UVISOR_BULK_DT_RD, + UVISOR_N_TRANSFER, +}; + +struct uvisor_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UVISOR_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_flag; +#define UVISOR_FLAG_PALM4 0x0001 +#define UVISOR_FLAG_VISOR 0x0002 +#define UVISOR_FLAG_PALM35 0x0004 +#define UVISOR_FLAG_SEND_NOTIFY 0x0008 + + uint8_t sc_iface_no; + uint8_t sc_iface_index; +}; + +/* prototypes */ + +static device_probe_t uvisor_probe; +static device_attach_t uvisor_attach; +static device_detach_t uvisor_detach; +static void uvisor_free_softc(struct uvisor_softc *); + +static usb_callback_t uvisor_write_callback; +static usb_callback_t uvisor_read_callback; + +static usb_error_t uvisor_init(struct uvisor_softc *, struct usb_device *, + struct usb_config *); +static void uvisor_free(struct ucom_softc *); +static void uvisor_cfg_open(struct ucom_softc *); +static void uvisor_cfg_close(struct ucom_softc *); +static void uvisor_start_read(struct ucom_softc *); +static void uvisor_stop_read(struct ucom_softc *); +static void uvisor_start_write(struct ucom_softc *); +static void uvisor_stop_write(struct ucom_softc *); + +static const struct usb_config uvisor_config[UVISOR_N_TRANSFER] = { + + [UVISOR_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UVISOROBUFSIZE * UVISOROFRAMES, + .frames = UVISOROFRAMES, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uvisor_write_callback, + }, + + [UVISOR_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UVISORIBUFSIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uvisor_read_callback, + }, +}; + +static const struct ucom_callback uvisor_callback = { + .ucom_cfg_open = &uvisor_cfg_open, + .ucom_cfg_close = &uvisor_cfg_close, + .ucom_start_read = &uvisor_start_read, + .ucom_stop_read = &uvisor_stop_read, + .ucom_start_write = &uvisor_start_write, + .ucom_stop_write = &uvisor_stop_write, + .ucom_free = &uvisor_free, +}; + +static device_method_t uvisor_methods[] = { + DEVMETHOD(device_probe, uvisor_probe), + DEVMETHOD(device_attach, uvisor_attach), + DEVMETHOD(device_detach, uvisor_detach), + DEVMETHOD_END +}; + +static devclass_t uvisor_devclass; + +static driver_t uvisor_driver = { + .name = "uvisor", + .methods = uvisor_methods, + .size = sizeof(struct uvisor_softc), +}; + +static const STRUCT_USB_HOST_ID uvisor_devs[] = { +#define UVISOR_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } + UVISOR_DEV(ACEECA, MEZ1000, UVISOR_FLAG_PALM4), + UVISOR_DEV(ALPHASMART, DANA_SYNC, UVISOR_FLAG_PALM4), + UVISOR_DEV(GARMIN, IQUE_3600, UVISOR_FLAG_PALM4), + UVISOR_DEV(FOSSIL, WRISTPDA, UVISOR_FLAG_PALM4), + UVISOR_DEV(HANDSPRING, VISOR, UVISOR_FLAG_VISOR), + UVISOR_DEV(HANDSPRING, TREO, UVISOR_FLAG_PALM4), + UVISOR_DEV(HANDSPRING, TREO600, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, M500, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, M505, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, M515, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, I705, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, M125, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, M130, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, TUNGSTEN_Z, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, TUNGSTEN_T, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, ZIRE, UVISOR_FLAG_PALM4), + UVISOR_DEV(PALM, ZIRE31, UVISOR_FLAG_PALM4), + UVISOR_DEV(SAMSUNG, I500, UVISOR_FLAG_PALM4), + UVISOR_DEV(SONY, CLIE_40, 0), + UVISOR_DEV(SONY, CLIE_41, 0), + UVISOR_DEV(SONY, CLIE_S360, UVISOR_FLAG_PALM4), + UVISOR_DEV(SONY, CLIE_NX60, UVISOR_FLAG_PALM4), + UVISOR_DEV(SONY, CLIE_35, UVISOR_FLAG_PALM35), +/* UVISOR_DEV(SONY, CLIE_25, UVISOR_FLAG_PALM4 ), */ + UVISOR_DEV(SONY, CLIE_TJ37, UVISOR_FLAG_PALM4), +/* UVISOR_DEV(SONY, CLIE_TH55, UVISOR_FLAG_PALM4 ), See PR 80935 */ + UVISOR_DEV(TAPWAVE, ZODIAC, UVISOR_FLAG_PALM4), +#undef UVISOR_DEV +}; + +DRIVER_MODULE(uvisor, uhub, uvisor_driver, uvisor_devclass, NULL, 0); +MODULE_DEPEND(uvisor, ucom, 1, 1, 1); +MODULE_DEPEND(uvisor, usb, 1, 1, 1); +MODULE_VERSION(uvisor, 1); +USB_PNP_HOST_INFO(uvisor_devs); + +static int +uvisor_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UVISOR_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UVISOR_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uvisor_devs, sizeof(uvisor_devs), uaa)); +} + +static int +uvisor_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uvisor_softc *sc = device_get_softc(dev); + struct usb_config uvisor_config_copy[UVISOR_N_TRANSFER]; + int error; + + DPRINTF("sc=%p\n", sc); + memcpy(uvisor_config_copy, uvisor_config, + sizeof(uvisor_config_copy)); + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, "uvisor", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + sc->sc_udev = uaa->device; + + /* configure the device */ + + sc->sc_flag = USB_GET_DRIVER_INFO(uaa); + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = UVISOR_IFACE_INDEX; + + error = uvisor_init(sc, uaa->device, uvisor_config_copy); + + if (error) { + DPRINTF("init failed, error=%s\n", + usbd_errstr(error)); + goto detach; + } + error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, uvisor_config_copy, UVISOR_N_TRANSFER, + sc, &sc->sc_mtx); + if (error) { + DPRINTF("could not allocate all pipes\n"); + goto detach; + } + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uvisor_callback, &sc->sc_mtx); + if (error) { + DPRINTF("ucom_attach failed\n"); + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + return (0); + +detach: + uvisor_detach(dev); + return (ENXIO); +} + +static int +uvisor_detach(device_t dev) +{ + struct uvisor_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UVISOR_N_TRANSFER); + + device_claim_softc(dev); + + uvisor_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(uvisor); + +static void +uvisor_free_softc(struct uvisor_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +uvisor_free(struct ucom_softc *ucom) +{ + uvisor_free_softc(ucom->sc_parent); +} + +static usb_error_t +uvisor_init(struct uvisor_softc *sc, struct usb_device *udev, struct usb_config *config) +{ + usb_error_t err = 0; + struct usb_device_request req; + struct uvisor_connection_info coninfo; + struct uvisor_palm_connection_info pconinfo; + uint16_t actlen; + uint8_t buffer[256]; + + if (sc->sc_flag & UVISOR_FLAG_VISOR) { + DPRINTF("getting connection info\n"); + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_CONNECTION_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_CONNECTION_INFO_SIZE); + err = usbd_do_request_flags(udev, NULL, + &req, &coninfo, USB_SHORT_XFER_OK, + &actlen, USB_DEFAULT_TIMEOUT); + + if (err) { + goto done; + } + } +#ifdef USB_DEBUG + if (sc->sc_flag & UVISOR_FLAG_VISOR) { + uint16_t i, np; + const char *desc; + + np = UGETW(coninfo.num_ports); + if (np > UVISOR_MAX_CONN) { + np = UVISOR_MAX_CONN; + } + DPRINTF("Number of ports: %d\n", np); + + for (i = 0; i < np; ++i) { + switch (coninfo.connections[i].port_function_id) { + case UVISOR_FUNCTION_GENERIC: + desc = "Generic"; + break; + case UVISOR_FUNCTION_DEBUGGER: + desc = "Debugger"; + break; + case UVISOR_FUNCTION_HOTSYNC: + desc = "HotSync"; + break; + case UVISOR_FUNCTION_REMOTE_FILE_SYS: + desc = "Remote File System"; + break; + default: + desc = "unknown"; + break; + } + DPRINTF("Port %d is for %s\n", + coninfo.connections[i].port, desc); + } + } +#endif + + if (sc->sc_flag & UVISOR_FLAG_PALM4) { + uint8_t port; + + /* Palm OS 4.0 Hack */ + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_PALM_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_GET_PALM_INFORMATION_LEN); + + err = usbd_do_request_flags + (udev, NULL, &req, &pconinfo, USB_SHORT_XFER_OK, + &actlen, USB_DEFAULT_TIMEOUT); + + if (err) { + goto done; + } + if (actlen < 12) { + DPRINTF("too little data\n"); + err = USB_ERR_INVAL; + goto done; + } + if (pconinfo.endpoint_numbers_different) { + port = pconinfo.connections[0].end_point_info; + config[0].endpoint = (port & 0xF); /* output */ + config[1].endpoint = (port >> 4); /* input */ + } else { + port = pconinfo.connections[0].port; + config[0].endpoint = (port & 0xF); /* output */ + config[1].endpoint = (port & 0xF); /* input */ + } +#if 0 + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_PALM_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_GET_PALM_INFORMATION_LEN); + err = usbd_do_request(udev, &req, buffer); + if (err) { + goto done; + } +#endif + } + if (sc->sc_flag & UVISOR_FLAG_PALM35) { + /* get the config number */ + DPRINTF("getting config info\n"); + req.bmRequestType = UT_READ; + req.bRequest = UR_GET_CONFIG; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + + err = usbd_do_request(udev, NULL, &req, buffer); + if (err) { + goto done; + } + /* get the interface number */ + DPRINTF("get the interface number\n"); + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_INTERFACE; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + err = usbd_do_request(udev, NULL, &req, buffer); + if (err) { + goto done; + } + } +#if 0 + uWord wAvail; + + DPRINTF("getting available bytes\n"); + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_REQUEST_BYTES_AVAILABLE; + USETW(req.wValue, 0); + USETW(req.wIndex, 5); + USETW(req.wLength, sizeof(wAvail)); + err = usbd_do_request(udev, NULL, &req, &wAvail); + if (err) { + goto done; + } + DPRINTF("avail=%d\n", UGETW(wAvail)); +#endif + + DPRINTF("done\n"); +done: + return (err); +} + +static void +uvisor_cfg_open(struct ucom_softc *ucom) +{ + return; +} + +static void +uvisor_cfg_close(struct ucom_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + uint8_t buffer[UVISOR_CONNECTION_INFO_SIZE]; + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; /* XXX read? */ + req.bRequest = UVISOR_CLOSE_NOTIFICATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_CONNECTION_INFO_SIZE); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, buffer, 0, 1000); + if (err) { + DPRINTFN(0, "close notification failed, error=%s\n", + usbd_errstr(err)); + } +} + +static void +uvisor_start_read(struct ucom_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UVISOR_BULK_DT_RD]); +} + +static void +uvisor_stop_read(struct ucom_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UVISOR_BULK_DT_RD]); +} + +static void +uvisor_start_write(struct ucom_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UVISOR_BULK_DT_WR]); +} + +static void +uvisor_stop_write(struct ucom_softc *ucom) +{ + struct uvisor_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UVISOR_BULK_DT_WR]); +} + +static void +uvisor_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uvisor_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + uint8_t x; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + for (x = 0; x != UVISOROFRAMES; x++) { + + usbd_xfer_set_frame_offset(xfer, + x * UVISOROBUFSIZE, x); + + pc = usbd_xfer_get_frame(xfer, x); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UVISOROBUFSIZE, &actlen)) { + usbd_xfer_set_frame_len(xfer, x, actlen); + } else { + break; + } + } + /* check for data */ + if (x != 0) { + usbd_xfer_set_frames(xfer, x); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +uvisor_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uvisor_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} diff --git a/freebsd/sys/dev/usb/serial/uvscom.c b/freebsd/sys/dev/usb/serial/uvscom.c new file mode 100644 index 00000000..deb80011 --- /dev/null +++ b/freebsd/sys/dev/usb/serial/uvscom.c @@ -0,0 +1,773 @@ +#include <machine/rtems-bsd-kernel-space.h> + +/* $NetBSD: usb/uvscom.c,v 1.1 2002/03/19 15:08:42 augustss Exp $ */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2001-2003, 2005 Shunsuke Akiyama <akiyama@jp.FreeBSD.org>. + * 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. + * 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. + * + */ + +/* + * uvscom: SUNTAC Slipper U VS-10U driver. + * Slipper U is a PC Card to USB converter for data communication card + * adapter. It supports DDI Pocket's Air H" C@rd, C@rd H" 64, NTT's P-in, + * P-in m@ater and various data communication card adapters. + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <rtems/bsd/sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <rtems/bsd/sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <rtems/bsd/sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <rtems/bsd/local/usbdevs.h> + +#define USB_DEBUG_VAR uvscom_debug +#include <dev/usb/usb_debug.h> +#include <dev/usb/usb_process.h> + +#include <dev/usb/serial/usb_serial.h> + +#ifdef USB_DEBUG +static int uvscom_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, uvscom, CTLFLAG_RW, 0, "USB uvscom"); +SYSCTL_INT(_hw_usb_uvscom, OID_AUTO, debug, CTLFLAG_RWTUN, + &uvscom_debug, 0, "Debug level"); +#endif + +#define UVSCOM_MODVER 1 /* module version */ + +#define UVSCOM_CONFIG_INDEX 0 +#define UVSCOM_IFACE_INDEX 0 + +/* Request */ +#define UVSCOM_SET_SPEED 0x10 +#define UVSCOM_LINE_CTL 0x11 +#define UVSCOM_SET_PARAM 0x12 +#define UVSCOM_READ_STATUS 0xd0 +#define UVSCOM_SHUTDOWN 0xe0 + +/* UVSCOM_SET_SPEED parameters */ +#define UVSCOM_SPEED_150BPS 0x00 +#define UVSCOM_SPEED_300BPS 0x01 +#define UVSCOM_SPEED_600BPS 0x02 +#define UVSCOM_SPEED_1200BPS 0x03 +#define UVSCOM_SPEED_2400BPS 0x04 +#define UVSCOM_SPEED_4800BPS 0x05 +#define UVSCOM_SPEED_9600BPS 0x06 +#define UVSCOM_SPEED_19200BPS 0x07 +#define UVSCOM_SPEED_38400BPS 0x08 +#define UVSCOM_SPEED_57600BPS 0x09 +#define UVSCOM_SPEED_115200BPS 0x0a + +/* UVSCOM_LINE_CTL parameters */ +#define UVSCOM_BREAK 0x40 +#define UVSCOM_RTS 0x02 +#define UVSCOM_DTR 0x01 +#define UVSCOM_LINE_INIT 0x08 + +/* UVSCOM_SET_PARAM parameters */ +#define UVSCOM_DATA_MASK 0x03 +#define UVSCOM_DATA_BIT_8 0x03 +#define UVSCOM_DATA_BIT_7 0x02 +#define UVSCOM_DATA_BIT_6 0x01 +#define UVSCOM_DATA_BIT_5 0x00 + +#define UVSCOM_STOP_MASK 0x04 +#define UVSCOM_STOP_BIT_2 0x04 +#define UVSCOM_STOP_BIT_1 0x00 + +#define UVSCOM_PARITY_MASK 0x18 +#define UVSCOM_PARITY_EVEN 0x18 +#define UVSCOM_PARITY_ODD 0x08 +#define UVSCOM_PARITY_NONE 0x00 + +/* Status bits */ +#define UVSCOM_TXRDY 0x04 +#define UVSCOM_RXRDY 0x01 + +#define UVSCOM_DCD 0x08 +#define UVSCOM_NOCARD 0x04 +#define UVSCOM_DSR 0x02 +#define UVSCOM_CTS 0x01 +#define UVSCOM_USTAT_MASK (UVSCOM_NOCARD | UVSCOM_DSR | UVSCOM_CTS) + +#define UVSCOM_BULK_BUF_SIZE 1024 /* bytes */ + +enum { + UVSCOM_BULK_DT_WR, + UVSCOM_BULK_DT_RD, + UVSCOM_INTR_DT_RD, + UVSCOM_N_TRANSFER, +}; + +struct uvscom_softc { + struct ucom_super_softc sc_super_ucom; + struct ucom_softc sc_ucom; + + struct usb_xfer *sc_xfer[UVSCOM_N_TRANSFER]; + struct usb_device *sc_udev; + struct mtx sc_mtx; + + uint16_t sc_line; /* line control register */ + + uint8_t sc_iface_no; /* interface number */ + uint8_t sc_iface_index; /* interface index */ + uint8_t sc_lsr; /* local status register */ + uint8_t sc_msr; /* uvscom status register */ + uint8_t sc_unit_status; /* unit status */ +}; + +/* prototypes */ + +static device_probe_t uvscom_probe; +static device_attach_t uvscom_attach; +static device_detach_t uvscom_detach; +static void uvscom_free_softc(struct uvscom_softc *); + +static usb_callback_t uvscom_write_callback; +static usb_callback_t uvscom_read_callback; +static usb_callback_t uvscom_intr_callback; + +static void uvscom_free(struct ucom_softc *); +static void uvscom_cfg_set_dtr(struct ucom_softc *, uint8_t); +static void uvscom_cfg_set_rts(struct ucom_softc *, uint8_t); +static void uvscom_cfg_set_break(struct ucom_softc *, uint8_t); +static int uvscom_pre_param(struct ucom_softc *, struct termios *); +static void uvscom_cfg_param(struct ucom_softc *, struct termios *); +static int uvscom_pre_open(struct ucom_softc *); +static void uvscom_cfg_open(struct ucom_softc *); +static void uvscom_cfg_close(struct ucom_softc *); +static void uvscom_start_read(struct ucom_softc *); +static void uvscom_stop_read(struct ucom_softc *); +static void uvscom_start_write(struct ucom_softc *); +static void uvscom_stop_write(struct ucom_softc *); +static void uvscom_cfg_get_status(struct ucom_softc *, uint8_t *, + uint8_t *); +static void uvscom_cfg_write(struct uvscom_softc *, uint8_t, uint16_t); +static uint16_t uvscom_cfg_read_status(struct uvscom_softc *); +static void uvscom_poll(struct ucom_softc *ucom); + +static const struct usb_config uvscom_config[UVSCOM_N_TRANSFER] = { + + [UVSCOM_BULK_DT_WR] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UVSCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, + .callback = &uvscom_write_callback, + }, + + [UVSCOM_BULK_DT_RD] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UVSCOM_BULK_BUF_SIZE, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = &uvscom_read_callback, + }, + + [UVSCOM_INTR_DT_RD] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &uvscom_intr_callback, + }, +}; + +static const struct ucom_callback uvscom_callback = { + .ucom_cfg_get_status = &uvscom_cfg_get_status, + .ucom_cfg_set_dtr = &uvscom_cfg_set_dtr, + .ucom_cfg_set_rts = &uvscom_cfg_set_rts, + .ucom_cfg_set_break = &uvscom_cfg_set_break, + .ucom_cfg_param = &uvscom_cfg_param, + .ucom_cfg_open = &uvscom_cfg_open, + .ucom_cfg_close = &uvscom_cfg_close, + .ucom_pre_open = &uvscom_pre_open, + .ucom_pre_param = &uvscom_pre_param, + .ucom_start_read = &uvscom_start_read, + .ucom_stop_read = &uvscom_stop_read, + .ucom_start_write = &uvscom_start_write, + .ucom_stop_write = &uvscom_stop_write, + .ucom_poll = &uvscom_poll, + .ucom_free = &uvscom_free, +}; + +static const STRUCT_USB_HOST_ID uvscom_devs[] = { + /* SUNTAC U-Cable type A4 */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_AS144L4, 0)}, + /* SUNTAC U-Cable type D2 */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_DS96L, 0)}, + /* SUNTAC Ir-Trinity */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_IS96U, 0)}, + /* SUNTAC U-Cable type P1 */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_PS64P1, 0)}, + /* SUNTAC Slipper U */ + {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_VS10U, 0)}, +}; + +static device_method_t uvscom_methods[] = { + DEVMETHOD(device_probe, uvscom_probe), + DEVMETHOD(device_attach, uvscom_attach), + DEVMETHOD(device_detach, uvscom_detach), + DEVMETHOD_END +}; + +static devclass_t uvscom_devclass; + +static driver_t uvscom_driver = { + .name = "uvscom", + .methods = uvscom_methods, + .size = sizeof(struct uvscom_softc), +}; + +DRIVER_MODULE(uvscom, uhub, uvscom_driver, uvscom_devclass, NULL, 0); +MODULE_DEPEND(uvscom, ucom, 1, 1, 1); +MODULE_DEPEND(uvscom, usb, 1, 1, 1); +MODULE_VERSION(uvscom, UVSCOM_MODVER); +USB_PNP_HOST_INFO(uvscom_devs); + +static int +uvscom_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->info.bConfigIndex != UVSCOM_CONFIG_INDEX) { + return (ENXIO); + } + if (uaa->info.bIfaceIndex != UVSCOM_IFACE_INDEX) { + return (ENXIO); + } + return (usbd_lookup_id_by_uaa(uvscom_devs, sizeof(uvscom_devs), uaa)); +} + +static int +uvscom_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct uvscom_softc *sc = device_get_softc(dev); + int error; + + device_set_usb_desc(dev); + mtx_init(&sc->sc_mtx, "uvscom", NULL, MTX_DEF); + ucom_ref(&sc->sc_super_ucom); + + sc->sc_udev = uaa->device; + + DPRINTF("sc=%p\n", sc); + + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = UVSCOM_IFACE_INDEX; + + error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, uvscom_config, UVSCOM_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + DPRINTF("could not allocate all USB transfers!\n"); + goto detach; + } + sc->sc_line = UVSCOM_LINE_INIT; + + /* clear stall at first run */ + mtx_lock(&sc->sc_mtx); + usbd_xfer_set_stall(sc->sc_xfer[UVSCOM_BULK_DT_WR]); + usbd_xfer_set_stall(sc->sc_xfer[UVSCOM_BULK_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc, + &uvscom_callback, &sc->sc_mtx); + if (error) { + goto detach; + } + ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev); + + /* start interrupt pipe */ + mtx_lock(&sc->sc_mtx); + usbd_transfer_start(sc->sc_xfer[UVSCOM_INTR_DT_RD]); + mtx_unlock(&sc->sc_mtx); + + return (0); + +detach: + uvscom_detach(dev); + return (ENXIO); +} + +static int +uvscom_detach(device_t dev) +{ + struct uvscom_softc *sc = device_get_softc(dev); + + DPRINTF("sc=%p\n", sc); + + /* stop interrupt pipe */ + + if (sc->sc_xfer[UVSCOM_INTR_DT_RD]) + usbd_transfer_stop(sc->sc_xfer[UVSCOM_INTR_DT_RD]); + + ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom); + usbd_transfer_unsetup(sc->sc_xfer, UVSCOM_N_TRANSFER); + + device_claim_softc(dev); + + uvscom_free_softc(sc); + + return (0); +} + +UCOM_UNLOAD_DRAIN(uvscom); + +static void +uvscom_free_softc(struct uvscom_softc *sc) +{ + if (ucom_unref(&sc->sc_super_ucom)) { + mtx_destroy(&sc->sc_mtx); + device_free_softc(sc); + } +} + +static void +uvscom_free(struct ucom_softc *ucom) +{ + uvscom_free_softc(ucom->sc_parent); +} + +static void +uvscom_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uvscom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint32_t actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: +tr_setup: + pc = usbd_xfer_get_frame(xfer, 0); + if (ucom_get_data(&sc->sc_ucom, pc, 0, + UVSCOM_BULK_BUF_SIZE, &actlen)) { + + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uvscom_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uvscom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + pc = usbd_xfer_get_frame(xfer, 0); + ucom_put_data(&sc->sc_ucom, pc, 0, actlen); + + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uvscom_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uvscom_softc *sc = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + uint8_t buf[2]; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (actlen >= 2) { + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, buf, sizeof(buf)); + + sc->sc_lsr = 0; + sc->sc_msr = 0; + sc->sc_unit_status = buf[1]; + + if (buf[0] & UVSCOM_TXRDY) { + sc->sc_lsr |= ULSR_TXRDY; + } + if (buf[0] & UVSCOM_RXRDY) { + sc->sc_lsr |= ULSR_RXRDY; + } + if (buf[1] & UVSCOM_CTS) { + sc->sc_msr |= SER_CTS; + } + if (buf[1] & UVSCOM_DSR) { + sc->sc_msr |= SER_DSR; + } + if (buf[1] & UVSCOM_DCD) { + sc->sc_msr |= SER_DCD; + } + /* + * the UCOM layer will ignore this call if the TTY + * device is closed! + */ + ucom_status_change(&sc->sc_ucom); + } + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + return; + } +} + +static void +uvscom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UVSCOM_DTR; + else + sc->sc_line &= ~UVSCOM_DTR; + + uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); +} + +static void +uvscom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UVSCOM_RTS; + else + sc->sc_line &= ~UVSCOM_RTS; + + uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); +} + +static void +uvscom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("onoff = %d\n", onoff); + + if (onoff) + sc->sc_line |= UVSCOM_BREAK; + else + sc->sc_line &= ~UVSCOM_BREAK; + + uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line); +} + +static int +uvscom_pre_param(struct ucom_softc *ucom, struct termios *t) +{ + switch (t->c_ospeed) { + case B150: + case B300: + case B600: + case B1200: + case B2400: + case B4800: + case B9600: + case B19200: + case B38400: + case B57600: + case B115200: + default: + return (EINVAL); + } + return (0); +} + +static void +uvscom_cfg_param(struct ucom_softc *ucom, struct termios *t) +{ + struct uvscom_softc *sc = ucom->sc_parent; + uint16_t value; + + DPRINTF("\n"); + + switch (t->c_ospeed) { + case B150: + value = UVSCOM_SPEED_150BPS; + break; + case B300: + value = UVSCOM_SPEED_300BPS; + break; + case B600: + value = UVSCOM_SPEED_600BPS; + break; + case B1200: + value = UVSCOM_SPEED_1200BPS; + break; + case B2400: + value = UVSCOM_SPEED_2400BPS; + break; + case B4800: + value = UVSCOM_SPEED_4800BPS; + break; + case B9600: + value = UVSCOM_SPEED_9600BPS; + break; + case B19200: + value = UVSCOM_SPEED_19200BPS; + break; + case B38400: + value = UVSCOM_SPEED_38400BPS; + break; + case B57600: + value = UVSCOM_SPEED_57600BPS; + break; + case B115200: + value = UVSCOM_SPEED_115200BPS; + break; + default: + return; + } + + uvscom_cfg_write(sc, UVSCOM_SET_SPEED, value); + + value = 0; + + if (t->c_cflag & CSTOPB) { + value |= UVSCOM_STOP_BIT_2; + } + if (t->c_cflag & PARENB) { + if (t->c_cflag & PARODD) { + value |= UVSCOM_PARITY_ODD; + } else { + value |= UVSCOM_PARITY_EVEN; + } + } else { + value |= UVSCOM_PARITY_NONE; + } + + switch (t->c_cflag & CSIZE) { + case CS5: + value |= UVSCOM_DATA_BIT_5; + break; + case CS6: + value |= UVSCOM_DATA_BIT_6; + break; + case CS7: + value |= UVSCOM_DATA_BIT_7; + break; + default: + case CS8: + value |= UVSCOM_DATA_BIT_8; + break; + } + + uvscom_cfg_write(sc, UVSCOM_SET_PARAM, value); +} + +static int +uvscom_pre_open(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("sc = %p\n", sc); + + /* check if PC card was inserted */ + + if (sc->sc_unit_status & UVSCOM_NOCARD) { + DPRINTF("no PC card!\n"); + return (ENXIO); + } + return (0); +} + +static void +uvscom_cfg_open(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("sc = %p\n", sc); + + uvscom_cfg_read_status(sc); +} + +static void +uvscom_cfg_close(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + DPRINTF("sc=%p\n", sc); + + uvscom_cfg_write(sc, UVSCOM_SHUTDOWN, 0); +} + +static void +uvscom_start_read(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UVSCOM_BULK_DT_RD]); +} + +static void +uvscom_stop_read(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UVSCOM_BULK_DT_RD]); +} + +static void +uvscom_start_write(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usbd_transfer_start(sc->sc_xfer[UVSCOM_BULK_DT_WR]); +} + +static void +uvscom_stop_write(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + usbd_transfer_stop(sc->sc_xfer[UVSCOM_BULK_DT_WR]); +} + +static void +uvscom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr) +{ + struct uvscom_softc *sc = ucom->sc_parent; + + *lsr = sc->sc_lsr; + *msr = sc->sc_msr; +} + +static void +uvscom_cfg_write(struct uvscom_softc *sc, uint8_t index, uint16_t value) +{ + struct usb_device_request req; + usb_error_t err; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = index; + USETW(req.wValue, value); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, NULL, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } +} + +static uint16_t +uvscom_cfg_read_status(struct uvscom_softc *sc) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t data[2]; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UVSCOM_READ_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 2); + + err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, + &req, data, 0, 1000); + if (err) { + DPRINTFN(0, "device request failed, err=%s " + "(ignored)\n", usbd_errstr(err)); + } + return (data[0] | (data[1] << 8)); +} + +static void +uvscom_poll(struct ucom_softc *ucom) +{ + struct uvscom_softc *sc = ucom->sc_parent; + usbd_transfer_poll(sc->sc_xfer, UVSCOM_N_TRANSFER); +} |