From owner-svn-src-head@FreeBSD.ORG Wed Jun 1 17:58:27 2011 Return-Path: Delivered-To: svn-src-head@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 9599D106564A; Wed, 1 Jun 2011 17:58:27 +0000 (UTC) (envelope-from hselasky@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id 819158FC14; Wed, 1 Jun 2011 17:58:27 +0000 (UTC) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.4/8.14.4) with ESMTP id p51HwR1R080855; Wed, 1 Jun 2011 17:58:27 GMT (envelope-from hselasky@svn.freebsd.org) Received: (from hselasky@localhost) by svn.freebsd.org (8.14.4/8.14.4/Submit) id p51HwRI2080848; Wed, 1 Jun 2011 17:58:27 GMT (envelope-from hselasky@svn.freebsd.org) Message-Id: <201106011758.p51HwRI2080848@svn.freebsd.org> From: Hans Petter Selasky Date: Wed, 1 Jun 2011 17:58:27 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Subject: svn commit: r222578 - in head: share/man/man4 sys/conf sys/dev/usb sys/dev/usb/serial sys/modules/usb sys/modules/usb/umcs7840 X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 01 Jun 2011 17:58:27 -0000 Author: hselasky Date: Wed Jun 1 17:58:27 2011 New Revision: 222578 URL: http://svn.freebsd.org/changeset/base/222578 Log: Add support for new USB serial driver. Submitted by: Lev Serebryakov, lev @ MFC after: 14 days Added: head/share/man/man4/umcs7840.4 (contents, props changed) head/sys/dev/usb/serial/umcs7840.c (contents, props changed) head/sys/dev/usb/serial/umcs7840.h (contents, props changed) head/sys/modules/usb/umcs7840/ head/sys/modules/usb/umcs7840/Makefile (contents, props changed) Modified: head/share/man/man4/Makefile head/share/man/man4/ucom.4 head/sys/conf/files head/sys/dev/usb/usb_process.c head/sys/dev/usb/usbdevs head/sys/modules/usb/Makefile Modified: head/share/man/man4/Makefile ============================================================================== --- head/share/man/man4/Makefile Wed Jun 1 17:51:27 2011 (r222577) +++ head/share/man/man4/Makefile Wed Jun 1 17:58:27 2011 (r222578) @@ -475,6 +475,7 @@ MAN= aac.4 \ ukbd.4 \ ulpt.4 \ umass.4 \ + umcs7840.4 \ umct.4 \ umodem.4 \ ums.4 \ Modified: head/share/man/man4/ucom.4 ============================================================================== --- head/share/man/man4/ucom.4 Wed Jun 1 17:51:27 2011 (r222577) +++ head/share/man/man4/ucom.4 Wed Jun 1 17:58:27 2011 (r222578) @@ -78,6 +78,7 @@ multiple external ports. .Xr uark 4 , .Xr uchcom 4 , .Xr uftdi 4 , +.Xr umcs7840 4 , .Xr umct 4 , .Xr umodem 4 , .Xr uplcom 4 , Added: head/share/man/man4/umcs7840.4 ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/share/man/man4/umcs7840.4 Wed Jun 1 17:58:27 2011 (r222578) @@ -0,0 +1,97 @@ +.\" +.\" Copyright (c) 2010 Lev Serebryakov . +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Lennart Augustsson. +.\" +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd December 10, 2010 +.Dt UMCS7840 4 +.Os +.Sh NAME +.Nm umcs7840 +.Nd USB support for serial adapters based on the MCS7820 and MCS7840 chips +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device umcs7840" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +umcs7840_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for various multiport serial adapters based on the MosCom +MCS7820 and MCS7840 chips. They are 2- or 4-port adapters with full-featured +16550-compatible UARTs and very flexible baud generators. Also, these chips +support RS422/RS485 and IrDA oprations. +.Pp +The device is accessed through the +.Xr ucom 4 +driver which makes it behave like a +.Xr tty 4 . +.Pp +Different ports on device are presented as sub-units, like +.Pa /dev/ttyU0.1 +and +.Pa /dev/ttyU0.2 +.Sh HARDWARE +The +.Nm +driver was tested on the following adapters: +.Pp +.Bl -bullet -compact +.It +ST Lab U-360 two-port serial USB adapter +.It +ST Lab U-400 four-port serial USB adapter +.El +.Sh SEE ALSO +.Xr tty 4 , +.Xr ucom 4 , +.Xr usb 4 +.Sh BUGS +This driver doesn't support access to any fine tunes of +chip, like RS522/RS485 mode, non-standard baudrates, etc. +.Sh HISTORY +The +.Nm +driver +appeared in ports since December of 2010. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Lev Serebryakov +.Aq lev@FreeBSD.org . Modified: head/sys/conf/files ============================================================================== --- head/sys/conf/files Wed Jun 1 17:51:27 2011 (r222577) +++ head/sys/conf/files Wed Jun 1 17:58:27 2011 (r222578) @@ -1955,6 +1955,7 @@ dev/usb/serial/uftdi.c optional uftdi dev/usb/serial/ugensa.c optional ugensa dev/usb/serial/uipaq.c optional uipaq dev/usb/serial/ulpt.c optional ulpt +dev/usb/serial/umcs7840.c optional umcs7840 dev/usb/serial/umct.c optional umct dev/usb/serial/umodem.c optional umodem dev/usb/serial/umoscom.c optional umoscom @@ -1964,7 +1965,7 @@ dev/usb/serial/uvisor.c optional uvisor dev/usb/serial/uvscom.c optional uvscom dev/usb/serial/usb_serial.c optional ucom | u3g | uark | ubsa | ubser | \ uchcom | ucycom | ufoma | uftdi | \ - ugensa | uipaq | umct | \ + ugensa | uipaq | umcs7840 | umct | \ umodem | umoscom | uplcom | uslcom | \ uvisor | uvscom # Added: head/sys/dev/usb/serial/umcs7840.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/dev/usb/serial/umcs7840.c Wed Jun 1 17:58:27 2011 (r222578) @@ -0,0 +1,1075 @@ +/*- + * Copyright (c) 2010 Lev Serebryakov . + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "usbdevs.h" + +#define USB_DEBUG_VAR umcs7840_debug +#include +#include + +#include + +#include + +#define UMCS7840_MODVER 1 + +#ifdef USB_DEBUG +static int umcs7840_debug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, umcs7840, CTLFLAG_RW, 0, "USB umcs7840 quadport serial adapter"); +SYSCTL_INT(_hw_usb_umcs7840, OID_AUTO, debug, CTLFLAG_RW, &umcs7840_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 */ + uint8_t sc_lsr; /* local line status register */ + uint8_t sc_msr; /* local modem status 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_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 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, +}; + +static const struct usb_device_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), + {0, 0} +}; + +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); + +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); + + 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); + + mtx_destroy(&sc->sc_mtx); + return (0); +} + +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; + + /* Read LSR & MSR */ + if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LSR, &sc->sc_ports[pn].sc_lsr)) + return; + if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_MSR, &sc->sc_ports[pn].sc_msr)) + return; + DPRINTF("Port %d has been opened, LSR=%02x MSR=%02x\n", pn, sc->sc_ports[pn].sc_lsr, sc->sc_ports[pn].sc_msr); +} + +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; + + *lsr = sc->sc_ports[ucom->sc_portno].sc_lsr; + *msr = sc->sc_ports[ucom->sc_portno].sc_msr; + 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: + /* Read new LSR */ + if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LSR, &sc->sc_ports[pn].sc_lsr)) + break; /* Inner switch */ + ucom_status_change(&sc->sc_ucom[subunit]); + /* Inner switch */ + break; + case MCS7840_UART_ISR_TXEMPTY: + /* Do nothing */ + break; /* Inner switch */ + case MCS7840_UART_ISR_MSCHANGE: + /* Read new MSR */ + if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_MSR, &sc->sc_ports[pn].sc_msr)) + break; /* Inner switch */ + DPRINTF("Port %d: new MSR %02x\n", pn, sc->sc_ports[pn].sc_msr); + ucom_status_change(&sc->sc_ucom[subunit]); + 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); +} + *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***