summaryrefslogtreecommitdiffstats
path: root/sys/dev/usb/serial/ucycom.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/usb/serial/ucycom.c')
-rw-r--r--sys/dev/usb/serial/ucycom.c564
1 files changed, 564 insertions, 0 deletions
diff --git a/sys/dev/usb/serial/ucycom.c b/sys/dev/usb/serial/ucycom.c
new file mode 100644
index 0000000..cabb7fb
--- /dev/null
+++ b/sys/dev/usb/serial/ucycom.c
@@ -0,0 +1,564 @@
+#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 "usbdevs.h"
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_mfunc.h>
+#include <dev/usb/usb_error.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usbhid.h>
+
+#define USB_DEBUG_VAR usb2_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_request.h>
+#include <dev/usb/usb_lookup.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_hid.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 usb2_com_super_softc sc_super_ucom;
+ struct usb2_com_softc sc_ucom;
+
+ struct usb2_device *sc_udev;
+ struct usb2_xfer *sc_xfer[UCYCOM_N_TRANSFER];
+
+ 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_name[16];
+ 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 usb2_callback_t ucycom_ctrl_write_callback;
+static usb2_callback_t ucycom_intr_read_callback;
+
+static void ucycom_cfg_open(struct usb2_com_softc *);
+static void ucycom_start_read(struct usb2_com_softc *);
+static void ucycom_stop_read(struct usb2_com_softc *);
+static void ucycom_start_write(struct usb2_com_softc *);
+static void ucycom_stop_write(struct usb2_com_softc *);
+static void ucycom_cfg_write(struct ucycom_softc *, uint32_t, uint8_t);
+static int ucycom_pre_param(struct usb2_com_softc *, struct termios *);
+static void ucycom_cfg_param(struct usb2_com_softc *, struct termios *);
+
+static const struct usb2_config ucycom_config[UCYCOM_N_TRANSFER] = {
+
+ [UCYCOM_CTRL_RD] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .mh.bufsize = (sizeof(struct usb2_device_request) + UCYCOM_MAX_IOLEN),
+ .mh.flags = {},
+ .mh.callback = &ucycom_ctrl_write_callback,
+ .mh.timeout = 1000, /* 1 second */
+ },
+
+ [UCYCOM_INTR_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .mh.bufsize = UCYCOM_MAX_IOLEN,
+ .mh.callback = &ucycom_intr_read_callback,
+ },
+};
+
+static const struct usb2_com_callback ucycom_callback = {
+ .usb2_com_cfg_param = &ucycom_cfg_param,
+ .usb2_com_cfg_open = &ucycom_cfg_open,
+ .usb2_com_pre_param = &ucycom_pre_param,
+ .usb2_com_start_read = &ucycom_start_read,
+ .usb2_com_stop_read = &ucycom_stop_read,
+ .usb2_com_start_write = &ucycom_start_write,
+ .usb2_com_stop_write = &ucycom_stop_write,
+};
+
+static device_method_t ucycom_methods[] = {
+ DEVMETHOD(device_probe, ucycom_probe),
+ DEVMETHOD(device_attach, ucycom_attach),
+ DEVMETHOD(device_detach, ucycom_detach),
+ {0, 0}
+};
+
+static devclass_t ucycom_devclass;
+
+static driver_t ucycom_driver = {
+ .name = "ucycom",
+ .methods = ucycom_methods,
+ .size = sizeof(struct ucycom_softc),
+};
+
+DRIVER_MODULE(ucycom, ushub, ucycom_driver, ucycom_devclass, NULL, 0);
+MODULE_DEPEND(ucycom, ucom, 1, 1, 1);
+MODULE_DEPEND(ucycom, usb, 1, 1, 1);
+
+/*
+ * Supported devices
+ */
+static const struct usb2_device_id ucycom_devs[] = {
+ {USB_VPI(USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013)},
+};
+
+#define UCYCOM_DEFAULT_RATE 4800
+#define UCYCOM_DEFAULT_CFG 0x03 /* N-8-1 */
+
+static int
+ucycom_probe(device_t dev)
+{
+ struct usb2_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb2_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != 0) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != UCYCOM_IFACE_INDEX) {
+ return (ENXIO);
+ }
+ return (usb2_lookup_id_by_uaa(ucycom_devs, sizeof(ucycom_devs), uaa));
+}
+
+static int
+ucycom_attach(device_t dev)
+{
+ struct usb2_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_usb2_desc(dev);
+
+ snprintf(sc->sc_name, sizeof(sc->sc_name),
+ "%s", device_get_nameunit(dev));
+
+ 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 = usb2_req_get_hid_desc
+ (uaa->device, &Giant,
+ &urd_ptr, &urd_len, M_USBDEV,
+ UCYCOM_IFACE_INDEX);
+
+ if (error) {
+ device_printf(dev, "failed to get report "
+ "descriptor: %s\n",
+ usb2_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 = usb2_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, ucycom_config, UCYCOM_N_TRANSFER,
+ sc, &Giant);
+ if (error) {
+ device_printf(dev, "allocating USB "
+ "transfers failed!\n");
+ goto detach;
+ }
+ error = usb2_com_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &ucycom_callback, &Giant);
+
+ if (error) {
+ goto detach;
+ }
+ 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);
+
+ usb2_com_detach(&sc->sc_super_ucom, &sc->sc_ucom, 1);
+
+ usb2_transfer_unsetup(sc->sc_xfer, UCYCOM_N_TRANSFER);
+
+ return (0);
+}
+
+static void
+ucycom_cfg_open(struct usb2_com_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 usb2_com_softc *ucom)
+{
+ struct ucycom_softc *sc = ucom->sc_parent;
+
+ usb2_transfer_start(sc->sc_xfer[UCYCOM_INTR_RD]);
+}
+
+static void
+ucycom_stop_read(struct usb2_com_softc *ucom)
+{
+ struct ucycom_softc *sc = ucom->sc_parent;
+
+ usb2_transfer_stop(sc->sc_xfer[UCYCOM_INTR_RD]);
+}
+
+static void
+ucycom_start_write(struct usb2_com_softc *ucom)
+{
+ struct ucycom_softc *sc = ucom->sc_parent;
+
+ usb2_transfer_start(sc->sc_xfer[UCYCOM_CTRL_RD]);
+}
+
+static void
+ucycom_stop_write(struct usb2_com_softc *ucom)
+{
+ struct ucycom_softc *sc = ucom->sc_parent;
+
+ usb2_transfer_stop(sc->sc_xfer[UCYCOM_CTRL_RD]);
+}
+
+static void
+ucycom_ctrl_write_callback(struct usb2_xfer *xfer)
+{
+ struct ucycom_softc *sc = xfer->priv_sc;
+ struct usb2_device_request req;
+ uint8_t data[2];
+ uint8_t offset;
+ uint32_t actlen;
+
+ 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 (usb2_com_get_data(&sc->sc_ucom, xfer->frbuffers + 1, 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;
+ }
+
+ usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req));
+ usb2_copy_in(xfer->frbuffers + 1, 0, data, offset);
+
+ xfer->frlengths[0] = sizeof(req);
+ xfer->frlengths[1] = sc->sc_olen;
+ xfer->nframes = xfer->frlengths[1] ? 2 : 1;
+ usb2_start_hardware(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (xfer->error == USB_ERR_CANCELLED) {
+ return;
+ }
+ DPRINTF("error=%s\n",
+ usb2_errstr(xfer->error));
+ goto tr_transferred;
+ }
+}
+
+static void
+ucycom_cfg_write(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg)
+{
+ struct usb2_device_request req;
+ uint16_t len;
+ usb2_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 = usb2_com_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", usb2_errstr(err));
+ }
+}
+
+static int
+ucycom_pre_param(struct usb2_com_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 usb2_com_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 usb2_xfer *xfer)
+{
+ struct ucycom_softc *sc = xfer->priv_sc;
+ uint8_t buf[2];
+ uint32_t offset;
+ uint32_t len;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ switch (sc->sc_model) {
+ case MODEL_CY7C63743:
+ if (xfer->actlen < 1) {
+ goto tr_setup;
+ }
+ usb2_copy_out(xfer->frbuffers, 0, buf, 1);
+
+ sc->sc_ist = buf[0] & ~0x07;
+ len = buf[0] & 0x07;
+
+ (xfer->actlen)--;
+
+ offset = 1;
+
+ break;
+
+ case MODEL_CY7C64013:
+ if (xfer->actlen < 2) {
+ goto tr_setup;
+ }
+ usb2_copy_out(xfer->frbuffers, 0, buf, 2);
+
+ sc->sc_ist = buf[0] & ~0x07;
+ len = buf[1];
+
+ (xfer->actlen) -= 2;
+
+ offset = 2;
+
+ break;
+
+ default:
+ DPRINTFN(0, "unsupported model number!\n");
+ goto tr_setup;
+ }
+
+ if (len > xfer->actlen) {
+ len = xfer->actlen;
+ }
+ if (len) {
+ usb2_com_put_data(&sc->sc_ucom, xfer->frbuffers,
+ offset, len);
+ }
+ case USB_ST_SETUP:
+tr_setup:
+ xfer->frlengths[0] = sc->sc_ilen;
+ usb2_start_hardware(xfer);
+ return;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ xfer->flags.stall_pipe = 1;
+ goto tr_setup;
+ }
+ return;
+
+ }
+}
OpenPOWER on IntegriCloud