1222578Shselasky/*-
2222578Shselasky * Copyright (c) 2010 Lev Serebryakov <lev@FreeBSD.org>.
3222578Shselasky * All rights reserved.
4222578Shselasky *
5222578Shselasky * Redistribution and use in source and binary forms, with or without
6222578Shselasky * modification, are permitted provided that the following conditions
7222578Shselasky * are met:
8222578Shselasky * 1. Redistributions of source code must retain the above copyright
9222578Shselasky *    notice, this list of conditions and the following disclaimer.
10222578Shselasky * 2. Redistributions in binary form must reproduce the above copyright
11222578Shselasky *    notice, this list of conditions and the following disclaimer in the
12222578Shselasky *    documentation and/or other materials provided with the distribution.
13222578Shselasky *
14222578Shselasky * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15222578Shselasky * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16222578Shselasky * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17222578Shselasky * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18222578Shselasky * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19222578Shselasky * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20222578Shselasky * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21222578Shselasky * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22222578Shselasky * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23222578Shselasky * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24222578Shselasky * SUCH DAMAGE.
25222578Shselasky */
26222578Shselasky
27222578Shselasky/*
28222578Shselasky * This driver supports several multiport USB-to-RS232 serial adapters driven
29222578Shselasky * by MosChip mos7820 and mos7840, bridge chips.
30222578Shselasky * The adapters are sold under many different brand names.
31222578Shselasky *
32222578Shselasky * Datasheets are available at MosChip www site at
33222578Shselasky * http://www.moschip.com.  The datasheets don't contain full
34222578Shselasky * programming information for the chip.
35222578Shselasky *
36222578Shselasky * It is nornal to have only two enabled ports in devices, based on
37222578Shselasky * quad-port mos7840.
38222578Shselasky *
39222578Shselasky */
40222578Shselasky#include <sys/cdefs.h>
41222578Shselasky__FBSDID("$FreeBSD: stable/11/sys/dev/usb/serial/umcs.c 314501 2017-03-01 18:23:30Z ian $");
42222578Shselasky
43222578Shselasky#include <sys/stdint.h>
44222578Shselasky#include <sys/stddef.h>
45222578Shselasky#include <sys/param.h>
46222578Shselasky#include <sys/queue.h>
47222578Shselasky#include <sys/types.h>
48222578Shselasky#include <sys/systm.h>
49222578Shselasky#include <sys/kernel.h>
50222578Shselasky#include <sys/bus.h>
51222578Shselasky#include <sys/linker_set.h>
52222578Shselasky#include <sys/module.h>
53222578Shselasky#include <sys/lock.h>
54222578Shselasky#include <sys/mutex.h>
55222578Shselasky#include <sys/condvar.h>
56222578Shselasky#include <sys/sysctl.h>
57222578Shselasky#include <sys/sx.h>
58222578Shselasky#include <sys/unistd.h>
59222578Shselasky#include <sys/callout.h>
60222578Shselasky#include <sys/malloc.h>
61222578Shselasky#include <sys/priv.h>
62222578Shselasky
63222578Shselasky#include <dev/usb/usb.h>
64222578Shselasky#include <dev/usb/usbdi.h>
65222578Shselasky#include <dev/usb/usbdi_util.h>
66222578Shselasky#include <dev/usb/usb_cdc.h>
67222578Shselasky#include "usbdevs.h"
68222578Shselasky
69222696Shselasky#define	USB_DEBUG_VAR umcs_debug
70222578Shselasky#include <dev/usb/usb_debug.h>
71222578Shselasky#include <dev/usb/usb_process.h>
72222578Shselasky
73222578Shselasky#include <dev/usb/serial/usb_serial.h>
74222578Shselasky
75222696Shselasky#include <dev/usb/serial/umcs.h>
76222578Shselasky
77222578Shselasky#define	UMCS7840_MODVER	1
78222578Shselasky
79222578Shselasky#ifdef USB_DEBUG
80222696Shselaskystatic int umcs_debug = 0;
81222578Shselasky
82227309Sedstatic SYSCTL_NODE(_hw_usb, OID_AUTO, umcs, CTLFLAG_RW, 0, "USB umcs quadport serial adapter");
83276701ShselaskySYSCTL_INT(_hw_usb_umcs, OID_AUTO, debug, CTLFLAG_RWTUN, &umcs_debug, 0, "Debug level");
84222578Shselasky#endif					/* USB_DEBUG */
85222578Shselasky
86222578Shselasky
87222578Shselasky/*
88222578Shselasky * Two-port devices (both with 7820 chip and 7840 chip configured as two-port)
89222578Shselasky * have ports 0 and 2, with ports 1 and 3 omitted.
90222578Shselasky * So,PHYSICAL port numbers (indexes) on two-port device will be 0 and 2.
91222578Shselasky * This driver trys to use physical numbers as much as possible.
92222578Shselasky */
93222578Shselasky
94222578Shselasky/*
95222578Shselasky * Indexed by PHYSICAL port number.
96222578Shselasky * Pack non-regular registers to array to easier if-less access.
97222578Shselasky */
98222578Shselaskystruct umcs7840_port_registers {
99222578Shselasky	uint8_t	reg_sp;			/* SP register. */
100222578Shselasky	uint8_t	reg_control;		/* CONTROL register. */
101222578Shselasky	uint8_t	reg_dcr;		/* DCR0 register. DCR1 & DCR2 can be
102222578Shselasky					 * calculated */
103222578Shselasky};
104222578Shselasky
105222578Shselaskystatic const struct umcs7840_port_registers umcs7840_port_registers[UMCS7840_MAX_PORTS] = {
106222578Shselasky	{.reg_sp = MCS7840_DEV_REG_SP1,.reg_control = MCS7840_DEV_REG_CONTROL1,.reg_dcr = MCS7840_DEV_REG_DCR0_1},
107222578Shselasky	{.reg_sp = MCS7840_DEV_REG_SP2,.reg_control = MCS7840_DEV_REG_CONTROL2,.reg_dcr = MCS7840_DEV_REG_DCR0_2},
108222578Shselasky	{.reg_sp = MCS7840_DEV_REG_SP3,.reg_control = MCS7840_DEV_REG_CONTROL3,.reg_dcr = MCS7840_DEV_REG_DCR0_3},
109222578Shselasky	{.reg_sp = MCS7840_DEV_REG_SP4,.reg_control = MCS7840_DEV_REG_CONTROL4,.reg_dcr = MCS7840_DEV_REG_DCR0_4},
110222578Shselasky};
111222578Shselasky
112222578Shselaskyenum {
113222578Shselasky	UMCS7840_BULK_RD_EP,
114222578Shselasky	UMCS7840_BULK_WR_EP,
115222578Shselasky	UMCS7840_N_TRANSFERS
116222578Shselasky};
117222578Shselasky
118222578Shselaskystruct umcs7840_softc_oneport {
119222578Shselasky	struct usb_xfer *sc_xfer[UMCS7840_N_TRANSFERS];	/* Control structures
120222578Shselasky							 * for two transfers */
121222578Shselasky
122222578Shselasky	uint8_t	sc_lcr;			/* local line control register */
123222578Shselasky	uint8_t	sc_mcr;			/* local modem control register */
124222578Shselasky};
125222578Shselasky
126222578Shselaskystruct umcs7840_softc {
127222578Shselasky	struct ucom_super_softc sc_super_ucom;
128222578Shselasky	struct ucom_softc sc_ucom[UMCS7840_MAX_PORTS];	/* Need to be continuous
129222578Shselasky							 * array, so indexed by
130222578Shselasky							 * LOGICAL port
131222578Shselasky							 * (subunit) number */
132222578Shselasky
133222578Shselasky	struct usb_xfer *sc_intr_xfer;	/* Interrupt endpoint */
134222578Shselasky
135222578Shselasky	device_t sc_dev;		/* Device for error prints */
136222578Shselasky	struct usb_device *sc_udev;	/* USB Device for all operations */
137222578Shselasky	struct mtx sc_mtx;		/* ucom requires this */
138222578Shselasky
139222578Shselasky	uint8_t	sc_driver_done;		/* Flag when enumeration is finished */
140222578Shselasky
141222578Shselasky	uint8_t	sc_numports;		/* Number of ports (subunits) */
142222578Shselasky	struct umcs7840_softc_oneport sc_ports[UMCS7840_MAX_PORTS];	/* Indexed by PHYSICAL
143222578Shselasky									 * port number. */
144222578Shselasky};
145222578Shselasky
146222578Shselasky/* prototypes */
147222578Shselaskystatic usb_error_t umcs7840_get_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t *);
148222578Shselaskystatic usb_error_t umcs7840_set_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t);
149222578Shselaskystatic usb_error_t umcs7840_get_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t *);
150222578Shselaskystatic usb_error_t umcs7840_set_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t);
151222578Shselasky
152222578Shselaskystatic usb_error_t umcs7840_set_baudrate(struct umcs7840_softc *, uint8_t, uint32_t);
153222578Shselaskystatic usb_error_t umcs7840_calc_baudrate(uint32_t rate, uint16_t *, uint8_t *);
154222578Shselasky
155239180Shselaskystatic void	umcs7840_free(struct ucom_softc *);
156222578Shselaskystatic void umcs7840_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *);
157222578Shselaskystatic void umcs7840_cfg_set_dtr(struct ucom_softc *, uint8_t);
158222578Shselaskystatic void umcs7840_cfg_set_rts(struct ucom_softc *, uint8_t);
159222578Shselaskystatic void umcs7840_cfg_set_break(struct ucom_softc *, uint8_t);
160222578Shselaskystatic void umcs7840_cfg_param(struct ucom_softc *, struct termios *);
161222578Shselaskystatic void umcs7840_cfg_open(struct ucom_softc *);
162222578Shselaskystatic void umcs7840_cfg_close(struct ucom_softc *);
163222578Shselasky
164222578Shselaskystatic int umcs7840_pre_param(struct ucom_softc *, struct termios *);
165222578Shselasky
166222578Shselaskystatic void umcs7840_start_read(struct ucom_softc *);
167222578Shselaskystatic void umcs7840_stop_read(struct ucom_softc *);
168222578Shselasky
169222578Shselaskystatic void umcs7840_start_write(struct ucom_softc *);
170222578Shselaskystatic void umcs7840_stop_write(struct ucom_softc *);
171222578Shselasky
172222578Shselaskystatic void umcs7840_poll(struct ucom_softc *ucom);
173222578Shselasky
174222578Shselaskystatic device_probe_t umcs7840_probe;
175222578Shselaskystatic device_attach_t umcs7840_attach;
176222578Shselaskystatic device_detach_t umcs7840_detach;
177239299Shselaskystatic void umcs7840_free_softc(struct umcs7840_softc *);
178222578Shselasky
179222578Shselaskystatic usb_callback_t umcs7840_intr_callback;
180222578Shselaskystatic usb_callback_t umcs7840_read_callback1;
181222578Shselaskystatic usb_callback_t umcs7840_read_callback2;
182222578Shselaskystatic usb_callback_t umcs7840_read_callback3;
183222578Shselaskystatic usb_callback_t umcs7840_read_callback4;
184222578Shselaskystatic usb_callback_t umcs7840_write_callback1;
185222578Shselaskystatic usb_callback_t umcs7840_write_callback2;
186222578Shselaskystatic usb_callback_t umcs7840_write_callback3;
187222578Shselaskystatic usb_callback_t umcs7840_write_callback4;
188222578Shselasky
189222578Shselaskystatic void umcs7840_read_callbackN(struct usb_xfer *, usb_error_t, uint8_t);
190222578Shselaskystatic void umcs7840_write_callbackN(struct usb_xfer *, usb_error_t, uint8_t);
191222578Shselasky
192222578Shselasky/* Indexed by LOGICAL port number (subunit), so two-port device uses 0 & 1 */
193222578Shselaskystatic usb_callback_t *umcs7840_rw_callbacks[UMCS7840_MAX_PORTS][UMCS7840_N_TRANSFERS] = {
194222578Shselasky	{&umcs7840_read_callback1, &umcs7840_write_callback1},
195222578Shselasky	{&umcs7840_read_callback2, &umcs7840_write_callback2},
196222578Shselasky	{&umcs7840_read_callback3, &umcs7840_write_callback3},
197222578Shselasky	{&umcs7840_read_callback4, &umcs7840_write_callback4},
198222578Shselasky};
199222578Shselasky
200222578Shselaskystatic const struct usb_config umcs7840_bulk_config_data[UMCS7840_N_TRANSFERS] = {
201222578Shselasky	[UMCS7840_BULK_RD_EP] = {
202222578Shselasky		.type = UE_BULK,
203222578Shselasky		.endpoint = 0x01,
204222578Shselasky		.direction = UE_DIR_IN,
205222578Shselasky		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
206222578Shselasky		.bufsize = 0,		/* use wMaxPacketSize */
207222578Shselasky		.callback = &umcs7840_read_callback1,
208222578Shselasky		.if_index = 0,
209222578Shselasky	},
210222578Shselasky
211222578Shselasky	[UMCS7840_BULK_WR_EP] = {
212222578Shselasky		.type = UE_BULK,
213222578Shselasky		.endpoint = 0x02,
214222578Shselasky		.direction = UE_DIR_OUT,
215222578Shselasky		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
216222578Shselasky		.bufsize = 0,		/* use wMaxPacketSize */
217222578Shselasky		.callback = &umcs7840_write_callback1,
218222578Shselasky		.if_index = 0,
219222578Shselasky	},
220222578Shselasky};
221222578Shselasky
222222578Shselaskystatic const struct usb_config umcs7840_intr_config_data[1] = {
223222578Shselasky	[0] = {
224222578Shselasky		.type = UE_INTERRUPT,
225222578Shselasky		.endpoint = 0x09,
226222578Shselasky		.direction = UE_DIR_IN,
227222578Shselasky		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
228222578Shselasky		.bufsize = 0,		/* use wMaxPacketSize */
229222578Shselasky		.callback = &umcs7840_intr_callback,
230222578Shselasky		.if_index = 0,
231222578Shselasky	},
232222578Shselasky};
233222578Shselasky
234222578Shselaskystatic struct ucom_callback umcs7840_callback = {
235222578Shselasky	.ucom_cfg_get_status = &umcs7840_cfg_get_status,
236222578Shselasky
237222578Shselasky	.ucom_cfg_set_dtr = &umcs7840_cfg_set_dtr,
238222578Shselasky	.ucom_cfg_set_rts = &umcs7840_cfg_set_rts,
239222578Shselasky	.ucom_cfg_set_break = &umcs7840_cfg_set_break,
240222578Shselasky
241222578Shselasky	.ucom_cfg_param = &umcs7840_cfg_param,
242222578Shselasky	.ucom_cfg_open = &umcs7840_cfg_open,
243222578Shselasky	.ucom_cfg_close = &umcs7840_cfg_close,
244222578Shselasky
245222578Shselasky	.ucom_pre_param = &umcs7840_pre_param,
246222578Shselasky
247222578Shselasky	.ucom_start_read = &umcs7840_start_read,
248222578Shselasky	.ucom_stop_read = &umcs7840_stop_read,
249222578Shselasky
250222578Shselasky	.ucom_start_write = &umcs7840_start_write,
251222578Shselasky	.ucom_stop_write = &umcs7840_stop_write,
252222578Shselasky
253222578Shselasky	.ucom_poll = &umcs7840_poll,
254239180Shselasky	.ucom_free = &umcs7840_free,
255222578Shselasky};
256222578Shselasky
257223486Shselaskystatic const STRUCT_USB_HOST_ID umcs7840_devs[] = {
258222578Shselasky	{USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7820, 0)},
259222578Shselasky	{USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7840, 0)},
260222578Shselasky};
261222578Shselasky
262222578Shselaskystatic device_method_t umcs7840_methods[] = {
263222578Shselasky	DEVMETHOD(device_probe, umcs7840_probe),
264222578Shselasky	DEVMETHOD(device_attach, umcs7840_attach),
265222578Shselasky	DEVMETHOD(device_detach, umcs7840_detach),
266239180Shselasky	DEVMETHOD_END
267222578Shselasky};
268222578Shselasky
269222578Shselaskystatic devclass_t umcs7840_devclass;
270222578Shselasky
271222578Shselaskystatic driver_t umcs7840_driver = {
272222578Shselasky	.name = "umcs7840",
273222578Shselasky	.methods = umcs7840_methods,
274222578Shselasky	.size = sizeof(struct umcs7840_softc),
275222578Shselasky};
276222578Shselasky
277222578ShselaskyDRIVER_MODULE(umcs7840, uhub, umcs7840_driver, umcs7840_devclass, 0, 0);
278222578ShselaskyMODULE_DEPEND(umcs7840, ucom, 1, 1, 1);
279222578ShselaskyMODULE_DEPEND(umcs7840, usb, 1, 1, 1);
280222578ShselaskyMODULE_VERSION(umcs7840, UMCS7840_MODVER);
281292080SimpUSB_PNP_HOST_INFO(umcs7840_devs);
282222578Shselasky
283222578Shselaskystatic int
284222578Shselaskyumcs7840_probe(device_t dev)
285222578Shselasky{
286222578Shselasky	struct usb_attach_arg *uaa = device_get_ivars(dev);
287222578Shselasky
288222578Shselasky	if (uaa->usb_mode != USB_MODE_HOST)
289222578Shselasky		return (ENXIO);
290222578Shselasky	if (uaa->info.bConfigIndex != MCS7840_CONFIG_INDEX)
291222578Shselasky		return (ENXIO);
292222578Shselasky	if (uaa->info.bIfaceIndex != MCS7840_IFACE_INDEX)
293222578Shselasky		return (ENXIO);
294222578Shselasky	return (usbd_lookup_id_by_uaa(umcs7840_devs, sizeof(umcs7840_devs), uaa));
295222578Shselasky}
296222578Shselasky
297222578Shselaskystatic int
298222578Shselaskyumcs7840_attach(device_t dev)
299222578Shselasky{
300222578Shselasky	struct usb_config umcs7840_config_tmp[UMCS7840_N_TRANSFERS];
301222578Shselasky	struct usb_attach_arg *uaa = device_get_ivars(dev);
302222578Shselasky	struct umcs7840_softc *sc = device_get_softc(dev);
303222578Shselasky
304222578Shselasky	uint8_t iface_index = MCS7840_IFACE_INDEX;
305222578Shselasky	int error;
306222578Shselasky	int subunit;
307222578Shselasky	int n;
308222578Shselasky	uint8_t data;
309222578Shselasky
310222578Shselasky	for (n = 0; n < UMCS7840_N_TRANSFERS; ++n)
311222578Shselasky		umcs7840_config_tmp[n] = umcs7840_bulk_config_data[n];
312222578Shselasky
313222578Shselasky	device_set_usb_desc(dev);
314222578Shselasky	mtx_init(&sc->sc_mtx, "umcs7840", NULL, MTX_DEF);
315239180Shselasky	ucom_ref(&sc->sc_super_ucom);
316222578Shselasky
317222578Shselasky	sc->sc_dev = dev;
318222578Shselasky	sc->sc_udev = uaa->device;
319222578Shselasky
320222578Shselasky	/*
321222578Shselasky	 * Get number of ports
322222578Shselasky	 * Documentation (full datasheet) says, that number of ports is
323222578Shselasky	 * set as MCS7840_DEV_MODE_SELECT24S bit in MODE R/Only
324222578Shselasky	 * register. But vendor driver uses these undocumented
325222578Shselasky	 * register & bit.
326222578Shselasky	 *
327222578Shselasky	 * Experiments show, that MODE register can have `0'
328222578Shselasky	 * (4 ports) bit on 2-port device, so use vendor driver's way.
329222578Shselasky	 *
330222578Shselasky	 * Also, see notes in header file for these constants.
331222578Shselasky	 */
332222578Shselasky	umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_GPIO, &data);
333222578Shselasky	if (data & MCS7840_DEV_GPIO_4PORTS) {
334222578Shselasky		sc->sc_numports = 4;
335222578Shselasky		/* Store physical port numbers in sc_portno */
336222578Shselasky		sc->sc_ucom[0].sc_portno = 0;
337222578Shselasky		sc->sc_ucom[1].sc_portno = 1;
338222578Shselasky		sc->sc_ucom[2].sc_portno = 2;
339222578Shselasky		sc->sc_ucom[3].sc_portno = 3;
340222578Shselasky	} else {
341222578Shselasky		sc->sc_numports = 2;
342222578Shselasky		/* Store physical port numbers in sc_portno */
343222578Shselasky		sc->sc_ucom[0].sc_portno = 0;
344222578Shselasky		sc->sc_ucom[1].sc_portno = 2;	/* '1' is skipped */
345222578Shselasky	}
346222578Shselasky	device_printf(dev, "Chip mcs%04x, found %d active ports\n", uaa->info.idProduct, sc->sc_numports);
347222578Shselasky	if (!umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_MODE, &data)) {
348222578Shselasky		device_printf(dev, "On-die confguration: RST: active %s, HRD: %s, PLL: %s, POR: %s, Ports: %s, EEPROM write %s, IrDA is %savailable\n",
349222578Shselasky		    (data & MCS7840_DEV_MODE_RESET) ? "low" : "high",
350222578Shselasky		    (data & MCS7840_DEV_MODE_SER_PRSNT) ? "yes" : "no",
351222578Shselasky		    (data & MCS7840_DEV_MODE_PLLBYPASS) ? "bypassed" : "avail",
352222578Shselasky		    (data & MCS7840_DEV_MODE_PORBYPASS) ? "bypassed" : "avail",
353222578Shselasky		    (data & MCS7840_DEV_MODE_SELECT24S) ? "2" : "4",
354222578Shselasky		    (data & MCS7840_DEV_MODE_EEPROMWR) ? "enabled" : "disabled",
355222578Shselasky		    (data & MCS7840_DEV_MODE_IRDA) ? "" : "not ");
356222578Shselasky	}
357222578Shselasky	/* Setup all transfers */
358222578Shselasky	for (subunit = 0; subunit < sc->sc_numports; ++subunit) {
359222578Shselasky		for (n = 0; n < UMCS7840_N_TRANSFERS; ++n) {
360222578Shselasky			/* Set endpoint address */
361222578Shselasky			umcs7840_config_tmp[n].endpoint = umcs7840_bulk_config_data[n].endpoint + 2 * sc->sc_ucom[subunit].sc_portno;
362222578Shselasky			umcs7840_config_tmp[n].callback = umcs7840_rw_callbacks[subunit][n];
363222578Shselasky		}
364222578Shselasky		error = usbd_transfer_setup(uaa->device,
365222578Shselasky		    &iface_index, sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, umcs7840_config_tmp,
366222578Shselasky		    UMCS7840_N_TRANSFERS, sc, &sc->sc_mtx);
367222578Shselasky		if (error) {
368222578Shselasky			device_printf(dev, "allocating USB transfers failed for subunit %d of %d\n",
369222578Shselasky			    subunit + 1, sc->sc_numports);
370222578Shselasky			goto detach;
371222578Shselasky		}
372222578Shselasky	}
373222578Shselasky	error = usbd_transfer_setup(uaa->device,
374222578Shselasky	    &iface_index, &sc->sc_intr_xfer, umcs7840_intr_config_data,
375222578Shselasky	    1, sc, &sc->sc_mtx);
376222578Shselasky	if (error) {
377222578Shselasky		device_printf(dev, "allocating USB transfers failed for interrupt\n");
378222578Shselasky		goto detach;
379222578Shselasky	}
380222578Shselasky	/* clear stall at first run */
381222578Shselasky	mtx_lock(&sc->sc_mtx);
382222578Shselasky	for (subunit = 0; subunit < sc->sc_numports; ++subunit) {
383222578Shselasky		usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_RD_EP]);
384222578Shselasky		usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_WR_EP]);
385222578Shselasky	}
386222578Shselasky	mtx_unlock(&sc->sc_mtx);
387222578Shselasky
388222578Shselasky	error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_numports, sc,
389222578Shselasky	    &umcs7840_callback, &sc->sc_mtx);
390222578Shselasky	if (error)
391222578Shselasky		goto detach;
392222578Shselasky
393222578Shselasky	ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
394222578Shselasky
395222578Shselasky	return (0);
396222578Shselasky
397222578Shselaskydetach:
398222578Shselasky	umcs7840_detach(dev);
399222578Shselasky	return (ENXIO);
400222578Shselasky}
401222578Shselasky
402222578Shselaskystatic int
403222578Shselaskyumcs7840_detach(device_t dev)
404222578Shselasky{
405222578Shselasky	struct umcs7840_softc *sc = device_get_softc(dev);
406222578Shselasky	int subunit;
407222578Shselasky
408222578Shselasky	ucom_detach(&sc->sc_super_ucom, sc->sc_ucom);
409222578Shselasky
410222578Shselasky	for (subunit = 0; subunit < sc->sc_numports; ++subunit)
411222578Shselasky		usbd_transfer_unsetup(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, UMCS7840_N_TRANSFERS);
412222578Shselasky	usbd_transfer_unsetup(&sc->sc_intr_xfer, 1);
413222578Shselasky
414239299Shselasky	device_claim_softc(dev);
415239299Shselasky
416239299Shselasky	umcs7840_free_softc(sc);
417239299Shselasky
418222578Shselasky	return (0);
419222578Shselasky}
420222578Shselasky
421239180ShselaskyUCOM_UNLOAD_DRAIN(umcs7840);
422239180Shselasky
423222578Shselaskystatic void
424239299Shselaskyumcs7840_free_softc(struct umcs7840_softc *sc)
425239180Shselasky{
426239180Shselasky	if (ucom_unref(&sc->sc_super_ucom)) {
427239299Shselasky		mtx_destroy(&sc->sc_mtx);
428239299Shselasky		device_free_softc(sc);
429239180Shselasky	}
430239180Shselasky}
431239180Shselasky
432239180Shselaskystatic void
433239180Shselaskyumcs7840_free(struct ucom_softc *ucom)
434239180Shselasky{
435239299Shselasky	umcs7840_free_softc(ucom->sc_parent);
436239180Shselasky}
437239180Shselasky
438239180Shselaskystatic void
439222578Shselaskyumcs7840_cfg_open(struct ucom_softc *ucom)
440222578Shselasky{
441222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
442222578Shselasky	uint16_t pn = ucom->sc_portno;
443222578Shselasky	uint8_t data;
444222578Shselasky
445222578Shselasky	/* If it very first open, finish global configuration */
446222578Shselasky	if (!sc->sc_driver_done) {
447222578Shselasky		/*
448222578Shselasky		 * USB enumeration is finished, pass internal memory to FIFOs
449222578Shselasky		 * If it is done in the end of "attach", kernel panics.
450222578Shselasky		 */
451222578Shselasky		if (umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, &data))
452222578Shselasky			return;
453222578Shselasky		data |= MCS7840_DEV_CONTROL1_DRIVER_DONE;
454222578Shselasky		if (umcs7840_set_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, data))
455222578Shselasky			return;
456222578Shselasky		sc->sc_driver_done = 1;
457222578Shselasky	}
458222578Shselasky	/* Toggle reset bit on-off */
459222578Shselasky	if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data))
460222578Shselasky		return;
461222578Shselasky	data |= MCS7840_DEV_SPx_UART_RESET;
462222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
463222578Shselasky		return;
464222578Shselasky	data &= ~MCS7840_DEV_SPx_UART_RESET;
465222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
466222578Shselasky		return;
467222578Shselasky
468222578Shselasky	/* Set RS-232 mode */
469222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_SCRATCHPAD, MCS7840_UART_SCRATCHPAD_RS232))
470222578Shselasky		return;
471222578Shselasky
472222578Shselasky	/* Disable RX on time of initialization */
473222578Shselasky	if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data))
474222578Shselasky		return;
475222578Shselasky	data |= MCS7840_DEV_CONTROLx_RX_DISABLE;
476222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data))
477222578Shselasky		return;
478222578Shselasky
479222578Shselasky	/* Disable all interrupts */
480222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0))
481222578Shselasky		return;
482222578Shselasky
483222578Shselasky	/* Reset FIFO -- documented */
484222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR, 0))
485222578Shselasky		return;
486222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR,
487222578Shselasky	    MCS7840_UART_FCR_ENABLE | MCS7840_UART_FCR_FLUSHRHR |
488222578Shselasky	    MCS7840_UART_FCR_FLUSHTHR | MCS7840_UART_FCR_RTL_1_14))
489222578Shselasky		return;
490222578Shselasky
491222578Shselasky	/* Set 8 bit, no parity, 1 stop bit -- documented */
492222578Shselasky	sc->sc_ports[pn].sc_lcr = MCS7840_UART_LCR_DATALEN8 | MCS7840_UART_LCR_STOPB1;
493222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr))
494222578Shselasky		return;
495222578Shselasky
496222578Shselasky	/*
497222578Shselasky	 * Enable DTR/RTS on modem control, enable modem interrupts --
498222578Shselasky	 * documented
499222578Shselasky	 */
500222578Shselasky	sc->sc_ports[pn].sc_mcr = MCS7840_UART_MCR_DTR | MCS7840_UART_MCR_RTS | MCS7840_UART_MCR_IE;
501222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr))
502222578Shselasky		return;
503222578Shselasky
504222578Shselasky	/* Clearing Bulkin and Bulkout FIFO */
505222578Shselasky	if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data))
506222578Shselasky		return;
507222578Shselasky	data |= MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO;
508222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
509222578Shselasky		return;
510222578Shselasky	data &= ~(MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO);
511222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
512222578Shselasky		return;
513222578Shselasky
514222578Shselasky	/* Set speed 9600 */
515222578Shselasky	if (umcs7840_set_baudrate(sc, pn, 9600))
516222578Shselasky		return;
517222578Shselasky
518222578Shselasky
519222578Shselasky	/* Finally enable all interrupts -- documented */
520222578Shselasky	/*
521222578Shselasky	 * Copied from vendor driver, I don't know why we should read LCR
522222578Shselasky	 * here
523222578Shselasky	 */
524222578Shselasky	if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, &sc->sc_ports[pn].sc_lcr))
525222578Shselasky		return;
526222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER,
527222578Shselasky	    MCS7840_UART_IER_RXSTAT | MCS7840_UART_IER_MODEM))
528222578Shselasky		return;
529222578Shselasky
530222578Shselasky	/* Enable RX */
531222578Shselasky	if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data))
532222578Shselasky		return;
533222578Shselasky	data &= ~MCS7840_DEV_CONTROLx_RX_DISABLE;
534222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data))
535222578Shselasky		return;
536222578Shselasky
537260559Shselasky	DPRINTF("Port %d has been opened\n", pn);
538222578Shselasky}
539222578Shselasky
540222578Shselaskystatic void
541222578Shselaskyumcs7840_cfg_close(struct ucom_softc *ucom)
542222578Shselasky{
543222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
544222578Shselasky	uint16_t pn = ucom->sc_portno;
545222578Shselasky	uint8_t data;
546222578Shselasky
547222578Shselasky	umcs7840_stop_read(ucom);
548222578Shselasky	umcs7840_stop_write(ucom);
549222578Shselasky
550222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, 0);
551222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0);
552222578Shselasky
553222578Shselasky	/* Disable RX */
554222578Shselasky	if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data))
555222578Shselasky		return;
556222578Shselasky	data |= MCS7840_DEV_CONTROLx_RX_DISABLE;
557222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data))
558222578Shselasky		return;
559222578Shselasky	DPRINTF("Port %d has been closed\n", pn);
560222578Shselasky}
561222578Shselasky
562222578Shselaskystatic void
563222578Shselaskyumcs7840_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
564222578Shselasky{
565222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
566222578Shselasky	uint8_t pn = ucom->sc_portno;
567222578Shselasky
568222578Shselasky	if (onoff)
569222578Shselasky		sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_DTR;
570222578Shselasky	else
571222578Shselasky		sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_DTR;
572222578Shselasky
573222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr);
574222578Shselasky	DPRINTF("Port %d DTR set to: %s\n", pn, onoff ? "on" : "off");
575222578Shselasky}
576222578Shselasky
577222578Shselaskystatic void
578222578Shselaskyumcs7840_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
579222578Shselasky{
580222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
581222578Shselasky	uint8_t pn = ucom->sc_portno;
582222578Shselasky
583222578Shselasky	if (onoff)
584222578Shselasky		sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_RTS;
585222578Shselasky	else
586222578Shselasky		sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_RTS;
587222578Shselasky
588222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr);
589222578Shselasky	DPRINTF("Port %d RTS set to: %s\n", pn, onoff ? "on" : "off");
590222578Shselasky}
591222578Shselasky
592222578Shselaskystatic void
593222578Shselaskyumcs7840_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
594222578Shselasky{
595222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
596222578Shselasky	uint8_t pn = ucom->sc_portno;
597222578Shselasky
598222578Shselasky	if (onoff)
599222578Shselasky		sc->sc_ports[pn].sc_lcr |= MCS7840_UART_LCR_BREAK;
600222578Shselasky	else
601222578Shselasky		sc->sc_ports[pn].sc_lcr &= ~MCS7840_UART_LCR_BREAK;
602222578Shselasky
603222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr);
604222578Shselasky	DPRINTF("Port %d BREAK set to: %s\n", pn, onoff ? "on" : "off");
605222578Shselasky}
606222578Shselasky
607222578Shselasky
608222578Shselaskystatic void
609222578Shselaskyumcs7840_cfg_param(struct ucom_softc *ucom, struct termios *t)
610222578Shselasky{
611222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
612222578Shselasky	uint8_t pn = ucom->sc_portno;
613222578Shselasky	uint8_t lcr = sc->sc_ports[pn].sc_lcr;
614222578Shselasky	uint8_t mcr = sc->sc_ports[pn].sc_mcr;
615222578Shselasky
616222578Shselasky	DPRINTF("Port %d config:\n", pn);
617222578Shselasky	if (t->c_cflag & CSTOPB) {
618222578Shselasky		DPRINTF("  2 stop bits\n");
619222578Shselasky		lcr |= MCS7840_UART_LCR_STOPB2;
620222578Shselasky	} else {
621222578Shselasky		lcr |= MCS7840_UART_LCR_STOPB1;
622222578Shselasky		DPRINTF("  1 stop bit\n");
623222578Shselasky	}
624222578Shselasky
625222578Shselasky	lcr &= ~MCS7840_UART_LCR_PARITYMASK;
626222578Shselasky	if (t->c_cflag & PARENB) {
627222578Shselasky		lcr |= MCS7840_UART_LCR_PARITYON;
628222578Shselasky		if (t->c_cflag & PARODD) {
629222578Shselasky			lcr = MCS7840_UART_LCR_PARITYODD;
630222578Shselasky			DPRINTF("  parity on - odd\n");
631222578Shselasky		} else {
632222578Shselasky			lcr = MCS7840_UART_LCR_PARITYEVEN;
633222578Shselasky			DPRINTF("  parity on - even\n");
634222578Shselasky		}
635222578Shselasky	} else {
636222578Shselasky		lcr &= ~MCS7840_UART_LCR_PARITYON;
637222578Shselasky		DPRINTF("  parity off\n");
638222578Shselasky	}
639222578Shselasky
640222578Shselasky	lcr &= ~MCS7840_UART_LCR_DATALENMASK;
641222578Shselasky	switch (t->c_cflag & CSIZE) {
642222578Shselasky	case CS5:
643222578Shselasky		lcr |= MCS7840_UART_LCR_DATALEN5;
644222578Shselasky		DPRINTF("  5 bit\n");
645222578Shselasky		break;
646222578Shselasky	case CS6:
647222578Shselasky		lcr |= MCS7840_UART_LCR_DATALEN6;
648222578Shselasky		DPRINTF("  6 bit\n");
649222578Shselasky		break;
650222578Shselasky	case CS7:
651222578Shselasky		lcr |= MCS7840_UART_LCR_DATALEN7;
652222578Shselasky		DPRINTF("  7 bit\n");
653222578Shselasky		break;
654222578Shselasky	case CS8:
655222578Shselasky		lcr |= MCS7840_UART_LCR_DATALEN8;
656222578Shselasky		DPRINTF("  8 bit\n");
657222578Shselasky		break;
658222578Shselasky	}
659222578Shselasky
660222578Shselasky	if (t->c_cflag & CRTSCTS) {
661222578Shselasky		mcr |= MCS7840_UART_MCR_CTSRTS;
662222578Shselasky		DPRINTF("  CTS/RTS\n");
663222578Shselasky	} else
664222578Shselasky		mcr &= ~MCS7840_UART_MCR_CTSRTS;
665222578Shselasky
666222578Shselasky	if (t->c_cflag & (CDTR_IFLOW | CDSR_OFLOW)) {
667222578Shselasky		mcr |= MCS7840_UART_MCR_DTRDSR;
668222578Shselasky		DPRINTF("  DTR/DSR\n");
669222578Shselasky	} else
670222578Shselasky		mcr &= ~MCS7840_UART_MCR_DTRDSR;
671222578Shselasky
672222578Shselasky	sc->sc_ports[pn].sc_lcr = lcr;
673222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr);
674222578Shselasky	DPRINTF("Port %d LCR=%02x\n", pn, sc->sc_ports[pn].sc_lcr);
675222578Shselasky
676222578Shselasky	sc->sc_ports[pn].sc_mcr = mcr;
677222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr);
678222578Shselasky	DPRINTF("Port %d MCR=%02x\n", pn, sc->sc_ports[pn].sc_mcr);
679222578Shselasky
680222578Shselasky	umcs7840_set_baudrate(sc, pn, t->c_ospeed);
681222578Shselasky}
682222578Shselasky
683222578Shselasky
684222578Shselaskystatic int
685222578Shselaskyumcs7840_pre_param(struct ucom_softc *ucom, struct termios *t)
686222578Shselasky{
687222578Shselasky	uint8_t clk;
688222578Shselasky	uint16_t divisor;
689222578Shselasky
690222578Shselasky	if (umcs7840_calc_baudrate(t->c_ospeed, &divisor, &clk) || !divisor)
691222578Shselasky		return (EINVAL);
692222578Shselasky	return (0);
693222578Shselasky}
694222578Shselasky
695222578Shselaskystatic void
696222578Shselaskyumcs7840_start_read(struct ucom_softc *ucom)
697222578Shselasky{
698222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
699222578Shselasky	uint8_t pn = ucom->sc_portno;
700222578Shselasky
701222578Shselasky	/* Start interrupt transfer */
702222578Shselasky	usbd_transfer_start(sc->sc_intr_xfer);
703222578Shselasky
704222578Shselasky	/* Start read transfer */
705222578Shselasky	usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]);
706222578Shselasky}
707222578Shselasky
708222578Shselaskystatic void
709222578Shselaskyumcs7840_stop_read(struct ucom_softc *ucom)
710222578Shselasky{
711222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
712222578Shselasky	uint8_t pn = ucom->sc_portno;
713222578Shselasky
714222578Shselasky	/* Stop read transfer */
715222578Shselasky	usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]);
716222578Shselasky}
717222578Shselasky
718222578Shselaskystatic void
719222578Shselaskyumcs7840_start_write(struct ucom_softc *ucom)
720222578Shselasky{
721222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
722222578Shselasky	uint8_t pn = ucom->sc_portno;
723222578Shselasky
724222578Shselasky	/* Start interrupt transfer */
725222578Shselasky	usbd_transfer_start(sc->sc_intr_xfer);
726222578Shselasky
727222578Shselasky	/* Start write transfer */
728222578Shselasky	usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]);
729222578Shselasky}
730222578Shselasky
731222578Shselaskystatic void
732222578Shselaskyumcs7840_stop_write(struct ucom_softc *ucom)
733222578Shselasky{
734222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
735222578Shselasky	uint8_t pn = ucom->sc_portno;
736222578Shselasky
737222578Shselasky	/* Stop write transfer */
738222578Shselasky	usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]);
739222578Shselasky}
740222578Shselasky
741222578Shselaskystatic void
742222578Shselaskyumcs7840_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
743222578Shselasky{
744222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
745260559Shselasky	uint8_t pn = ucom->sc_portno;
746260559Shselasky	uint8_t	hw_msr = 0;	/* local modem status register */
747222578Shselasky
748314501Sian	/*
749314501Sian	 * Read status registers.  MSR bits need translation from ns16550 to
750314501Sian	 * SER_* values.  LSR bits are ns16550 in hardware and ucom.
751314501Sian	 */
752314501Sian	umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LSR, lsr);
753260559Shselasky	umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_MSR, &hw_msr);
754260559Shselasky
755314501Sian	if (hw_msr & MCS7840_UART_MSR_NEGCTS)
756314501Sian		*msr |= SER_CTS;
757260559Shselasky
758314501Sian	if (hw_msr & MCS7840_UART_MSR_NEGDCD)
759314501Sian		*msr |= SER_DCD;
760314501Sian
761314501Sian	if (hw_msr & MCS7840_UART_MSR_NEGRI)
762314501Sian		*msr |= SER_RI;
763314501Sian
764314501Sian	if (hw_msr & MCS7840_UART_MSR_NEGDSR)
765314501Sian		*msr |= SER_DSR;
766314501Sian
767222578Shselasky	DPRINTF("Port %d status: LSR=%02x MSR=%02x\n", ucom->sc_portno, *lsr, *msr);
768222578Shselasky}
769222578Shselasky
770222578Shselaskystatic void
771222578Shselaskyumcs7840_intr_callback(struct usb_xfer *xfer, usb_error_t error)
772222578Shselasky{
773222578Shselasky	struct umcs7840_softc *sc = usbd_xfer_softc(xfer);
774222578Shselasky	struct usb_page_cache *pc;
775222578Shselasky	uint8_t buf[13];
776222578Shselasky	int actlen;
777222578Shselasky	int subunit;
778222578Shselasky
779222578Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
780222578Shselasky
781222578Shselasky	switch (USB_GET_STATE(xfer)) {
782222578Shselasky	case USB_ST_TRANSFERRED:
783222578Shselasky		if (actlen == 5 || actlen == 13) {
784222578Shselasky			pc = usbd_xfer_get_frame(xfer, 0);
785222578Shselasky			usbd_copy_out(pc, 0, buf, actlen);
786222578Shselasky			/* Check status of all ports */
787222578Shselasky			for (subunit = 0; subunit < sc->sc_numports; ++subunit) {
788222578Shselasky				uint8_t pn = sc->sc_ucom[subunit].sc_portno;
789222578Shselasky
790222578Shselasky				if (buf[pn] & MCS7840_UART_ISR_NOPENDING)
791222578Shselasky					continue;
792222578Shselasky				DPRINTF("Port %d has pending interrupt: %02x (FIFO: %02x)\n", pn, buf[pn] & MCS7840_UART_ISR_INTMASK, buf[pn] & (~MCS7840_UART_ISR_INTMASK));
793222578Shselasky				switch (buf[pn] & MCS7840_UART_ISR_INTMASK) {
794222578Shselasky				case MCS7840_UART_ISR_RXERR:
795222578Shselasky				case MCS7840_UART_ISR_RXHASDATA:
796222578Shselasky				case MCS7840_UART_ISR_RXTIMEOUT:
797260559Shselasky				case MCS7840_UART_ISR_MSCHANGE:
798222578Shselasky					ucom_status_change(&sc->sc_ucom[subunit]);
799222578Shselasky					break;
800260559Shselasky				default:
801222578Shselasky					/* Do nothing */
802222578Shselasky					break;
803222578Shselasky				}
804222578Shselasky			}
805222578Shselasky		} else
806222578Shselasky			device_printf(sc->sc_dev, "Invalid interrupt data length %d", actlen);
807222578Shselasky		/* FALLTHROUGH */
808222578Shselasky	case USB_ST_SETUP:
809222578Shselaskytr_setup:
810222578Shselasky		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
811222578Shselasky		usbd_transfer_submit(xfer);
812222578Shselasky		return;
813222578Shselasky
814222578Shselasky	default:			/* Error */
815222578Shselasky		if (error != USB_ERR_CANCELLED) {
816222578Shselasky			/* try to clear stall first */
817222578Shselasky			usbd_xfer_set_stall(xfer);
818222578Shselasky			goto tr_setup;
819222578Shselasky		}
820222578Shselasky		return;
821222578Shselasky	}
822222578Shselasky}
823222578Shselasky
824222578Shselaskystatic void
825222578Shselaskyumcs7840_read_callback1(struct usb_xfer *xfer, usb_error_t error)
826222578Shselasky{
827222578Shselasky	umcs7840_read_callbackN(xfer, error, 0);
828222578Shselasky}
829222578Shselasky
830222578Shselaskystatic void
831222578Shselaskyumcs7840_read_callback2(struct usb_xfer *xfer, usb_error_t error)
832222578Shselasky{
833222578Shselasky	umcs7840_read_callbackN(xfer, error, 1);
834222578Shselasky}
835222578Shselaskystatic void
836222578Shselaskyumcs7840_read_callback3(struct usb_xfer *xfer, usb_error_t error)
837222578Shselasky{
838222578Shselasky	umcs7840_read_callbackN(xfer, error, 2);
839222578Shselasky}
840222578Shselasky
841222578Shselaskystatic void
842222578Shselaskyumcs7840_read_callback4(struct usb_xfer *xfer, usb_error_t error)
843222578Shselasky{
844222578Shselasky	umcs7840_read_callbackN(xfer, error, 3);
845222578Shselasky}
846222578Shselasky
847222578Shselaskystatic void
848222578Shselaskyumcs7840_read_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit)
849222578Shselasky{
850222578Shselasky	struct umcs7840_softc *sc = usbd_xfer_softc(xfer);
851222578Shselasky	struct ucom_softc *ucom = &sc->sc_ucom[subunit];
852222578Shselasky	struct usb_page_cache *pc;
853222578Shselasky	int actlen;
854222578Shselasky
855222578Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
856222578Shselasky
857222578Shselasky	DPRINTF("Port %d read, state = %d, data length = %d\n", ucom->sc_portno, USB_GET_STATE(xfer), actlen);
858222578Shselasky
859222578Shselasky	switch (USB_GET_STATE(xfer)) {
860222578Shselasky	case USB_ST_TRANSFERRED:
861222578Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
862222578Shselasky		ucom_put_data(ucom, pc, 0, actlen);
863222578Shselasky		/* FALLTHROUGH */
864222578Shselasky	case USB_ST_SETUP:
865222578Shselaskytr_setup:
866222578Shselasky		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
867222578Shselasky		usbd_transfer_submit(xfer);
868222578Shselasky		return;
869222578Shselasky
870222578Shselasky	default:			/* Error */
871222578Shselasky		if (error != USB_ERR_CANCELLED) {
872222578Shselasky			/* try to clear stall first */
873222578Shselasky			usbd_xfer_set_stall(xfer);
874222578Shselasky			goto tr_setup;
875222578Shselasky		}
876222578Shselasky		return;
877222578Shselasky	}
878222578Shselasky}
879222578Shselasky
880222578Shselaskystatic void
881222578Shselaskyumcs7840_write_callback1(struct usb_xfer *xfer, usb_error_t error)
882222578Shselasky{
883222578Shselasky	umcs7840_write_callbackN(xfer, error, 0);
884222578Shselasky}
885222578Shselasky
886222578Shselaskystatic void
887222578Shselaskyumcs7840_write_callback2(struct usb_xfer *xfer, usb_error_t error)
888222578Shselasky{
889222578Shselasky	umcs7840_write_callbackN(xfer, error, 1);
890222578Shselasky}
891222578Shselasky
892222578Shselaskystatic void
893222578Shselaskyumcs7840_write_callback3(struct usb_xfer *xfer, usb_error_t error)
894222578Shselasky{
895222578Shselasky	umcs7840_write_callbackN(xfer, error, 2);
896222578Shselasky}
897222578Shselasky
898222578Shselaskystatic void
899222578Shselaskyumcs7840_write_callback4(struct usb_xfer *xfer, usb_error_t error)
900222578Shselasky{
901222578Shselasky	umcs7840_write_callbackN(xfer, error, 3);
902222578Shselasky}
903222578Shselasky
904222578Shselaskystatic void
905222578Shselaskyumcs7840_write_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit)
906222578Shselasky{
907222578Shselasky	struct umcs7840_softc *sc = usbd_xfer_softc(xfer);
908222578Shselasky	struct ucom_softc *ucom = &sc->sc_ucom[subunit];
909222578Shselasky	struct usb_page_cache *pc;
910222578Shselasky	uint32_t actlen;
911222578Shselasky
912222578Shselasky	DPRINTF("Port %d write, state = %d\n", ucom->sc_portno, USB_GET_STATE(xfer));
913222578Shselasky
914222578Shselasky	switch (USB_GET_STATE(xfer)) {
915222578Shselasky	case USB_ST_SETUP:
916222578Shselasky	case USB_ST_TRANSFERRED:
917222578Shselaskytr_setup:
918222578Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
919222578Shselasky		if (ucom_get_data(ucom, pc, 0, usbd_xfer_max_len(xfer), &actlen)) {
920222578Shselasky			DPRINTF("Port %d write, has %d bytes\n", ucom->sc_portno, actlen);
921222578Shselasky			usbd_xfer_set_frame_len(xfer, 0, actlen);
922222578Shselasky			usbd_transfer_submit(xfer);
923222578Shselasky		}
924222578Shselasky		return;
925222578Shselasky
926222578Shselasky	default:			/* Error */
927222578Shselasky		if (error != USB_ERR_CANCELLED) {
928222578Shselasky			/* try to clear stall first */
929222578Shselasky			usbd_xfer_set_stall(xfer);
930222578Shselasky			goto tr_setup;
931222578Shselasky		}
932222578Shselasky		return;
933222578Shselasky	}
934222578Shselasky}
935222578Shselasky
936222578Shselaskystatic void
937222578Shselaskyumcs7840_poll(struct ucom_softc *ucom)
938222578Shselasky{
939222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
940222578Shselasky
941222578Shselasky	DPRINTF("Port %d poll\n", ucom->sc_portno);
942222578Shselasky	usbd_transfer_poll(sc->sc_ports[ucom->sc_portno].sc_xfer, UMCS7840_N_TRANSFERS);
943222578Shselasky	usbd_transfer_poll(&sc->sc_intr_xfer, 1);
944222578Shselasky}
945222578Shselasky
946222578Shselaskystatic usb_error_t
947222578Shselaskyumcs7840_get_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t *data)
948222578Shselasky{
949222578Shselasky	struct usb_device_request req;
950222578Shselasky	usb_error_t err;
951222578Shselasky	uint16_t len;
952222578Shselasky
953222578Shselasky	req.bmRequestType = UT_READ_VENDOR_DEVICE;
954222578Shselasky	req.bRequest = MCS7840_RDREQ;
955222578Shselasky	USETW(req.wValue, 0);
956222578Shselasky	USETW(req.wIndex, reg);
957222578Shselasky	USETW(req.wLength, UMCS7840_READ_LENGTH);
958222578Shselasky
959222578Shselasky	err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT);
960222578Shselasky	if (err == USB_ERR_NORMAL_COMPLETION && len != 1) {
961222578Shselasky		device_printf(sc->sc_dev, "Reading register %d failed: invalid length %d\n", reg, len);
962222578Shselasky		return (USB_ERR_INVAL);
963222578Shselasky	} else if (err)
964222578Shselasky		device_printf(sc->sc_dev, "Reading register %d failed: %s\n", reg, usbd_errstr(err));
965222578Shselasky	return (err);
966222578Shselasky}
967222578Shselasky
968222578Shselaskystatic usb_error_t
969222578Shselaskyumcs7840_set_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t data)
970222578Shselasky{
971222578Shselasky	struct usb_device_request req;
972222578Shselasky	usb_error_t err;
973222578Shselasky
974222578Shselasky	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
975222578Shselasky	req.bRequest = MCS7840_WRREQ;
976222578Shselasky	USETW(req.wValue, data);
977222578Shselasky	USETW(req.wIndex, reg);
978222578Shselasky	USETW(req.wLength, 0);
979222578Shselasky
980222578Shselasky	err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT);
981222578Shselasky	if (err)
982222578Shselasky		device_printf(sc->sc_dev, "Writing register %d failed: %s\n", reg, usbd_errstr(err));
983222578Shselasky
984222578Shselasky	return (err);
985222578Shselasky}
986222578Shselasky
987222578Shselaskystatic usb_error_t
988222578Shselaskyumcs7840_get_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t *data)
989222578Shselasky{
990222578Shselasky	struct usb_device_request req;
991222578Shselasky	uint16_t wVal;
992222578Shselasky	usb_error_t err;
993222578Shselasky	uint16_t len;
994222578Shselasky
995222578Shselasky	/* portno is port number */
996222578Shselasky	wVal = ((uint16_t)(portno + 1)) << 8;
997222578Shselasky
998222578Shselasky	req.bmRequestType = UT_READ_VENDOR_DEVICE;
999222578Shselasky	req.bRequest = MCS7840_RDREQ;
1000222578Shselasky	USETW(req.wValue, wVal);
1001222578Shselasky	USETW(req.wIndex, reg);
1002222578Shselasky	USETW(req.wLength, UMCS7840_READ_LENGTH);
1003222578Shselasky
1004222578Shselasky	err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT);
1005222578Shselasky	if (err == USB_ERR_NORMAL_COMPLETION && len != 1) {
1006222578Shselasky		device_printf(sc->sc_dev, "Reading UART%d register %d failed: invalid length %d\n", portno, reg, len);
1007222578Shselasky		return (USB_ERR_INVAL);
1008222578Shselasky	} else if (err)
1009222578Shselasky		device_printf(sc->sc_dev, "Reading UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err));
1010222578Shselasky	return (err);
1011222578Shselasky}
1012222578Shselasky
1013222578Shselaskystatic usb_error_t
1014222578Shselaskyumcs7840_set_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t data)
1015222578Shselasky{
1016222578Shselasky	struct usb_device_request req;
1017222578Shselasky	usb_error_t err;
1018222578Shselasky	uint16_t wVal;
1019222578Shselasky
1020222578Shselasky	/* portno is port number */
1021222578Shselasky	wVal = ((uint16_t)(portno + 1)) << 8 | data;
1022222578Shselasky
1023222578Shselasky	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
1024222578Shselasky	req.bRequest = MCS7840_WRREQ;
1025222578Shselasky	USETW(req.wValue, wVal);
1026222578Shselasky	USETW(req.wIndex, reg);
1027222578Shselasky	USETW(req.wLength, 0);
1028222578Shselasky
1029222578Shselasky	err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT);
1030222578Shselasky	if (err)
1031222578Shselasky		device_printf(sc->sc_dev, "Writing UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err));
1032222578Shselasky	return (err);
1033222578Shselasky}
1034222578Shselasky
1035222578Shselaskystatic usb_error_t
1036222578Shselaskyumcs7840_set_baudrate(struct umcs7840_softc *sc, uint8_t portno, uint32_t rate)
1037222578Shselasky{
1038222578Shselasky	usb_error_t err;
1039222578Shselasky	uint16_t divisor;
1040222578Shselasky	uint8_t clk;
1041222578Shselasky	uint8_t data;
1042222578Shselasky
1043222578Shselasky	if (umcs7840_calc_baudrate(rate, &divisor, &clk)) {
1044222578Shselasky		DPRINTF("Port %d bad speed: %d\n", portno, rate);
1045222578Shselasky		return (-1);
1046222578Shselasky	}
1047222578Shselasky	if (divisor == 0 || (clk & MCS7840_DEV_SPx_CLOCK_MASK) != clk) {
1048222578Shselasky		DPRINTF("Port %d bad speed calculation: %d\n", portno, rate);
1049222578Shselasky		return (-1);
1050222578Shselasky	}
1051222578Shselasky	DPRINTF("Port %d set speed: %d (%02x / %d)\n", portno, rate, clk, divisor);
1052222578Shselasky
1053222578Shselasky	/* Set clock source for standard BAUD frequences */
1054222578Shselasky	err = umcs7840_get_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, &data);
1055222578Shselasky	if (err)
1056222578Shselasky		return (err);
1057222578Shselasky	data &= MCS7840_DEV_SPx_CLOCK_MASK;
1058222578Shselasky	data |= clk;
1059222578Shselasky	err = umcs7840_set_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, data);
1060222578Shselasky	if (err)
1061222578Shselasky		return (err);
1062222578Shselasky
1063222578Shselasky	/* Set divider */
1064222578Shselasky	sc->sc_ports[portno].sc_lcr |= MCS7840_UART_LCR_DIVISORS;
1065222578Shselasky	err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr);
1066222578Shselasky	if (err)
1067222578Shselasky		return (err);
1068222578Shselasky
1069222578Shselasky	err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLL, (uint8_t)(divisor & 0xff));
1070222578Shselasky	if (err)
1071222578Shselasky		return (err);
1072222578Shselasky	err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLM, (uint8_t)((divisor >> 8) & 0xff));
1073222578Shselasky	if (err)
1074222578Shselasky		return (err);
1075222578Shselasky
1076222578Shselasky	/* Turn off access to DLL/DLM registers of UART */
1077222578Shselasky	sc->sc_ports[portno].sc_lcr &= ~MCS7840_UART_LCR_DIVISORS;
1078222578Shselasky	err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr);
1079222578Shselasky	if (err)
1080222578Shselasky		return (err);
1081222578Shselasky	return (0);
1082222578Shselasky}
1083222578Shselasky
1084222578Shselasky/* Maximum speeds for standard frequences, when PLL is not used */
1085222578Shselaskystatic const uint32_t umcs7840_baudrate_divisors[] = {0, 115200, 230400, 403200, 460800, 806400, 921600, 1572864, 3145728,};
1086298300Spfgstatic const uint8_t umcs7840_baudrate_divisors_len = nitems(umcs7840_baudrate_divisors);
1087222578Shselasky
1088222578Shselaskystatic usb_error_t
1089222578Shselaskyumcs7840_calc_baudrate(uint32_t rate, uint16_t *divisor, uint8_t *clk)
1090222578Shselasky{
1091222578Shselasky	uint8_t i = 0;
1092222578Shselasky
1093222578Shselasky	if (rate > umcs7840_baudrate_divisors[umcs7840_baudrate_divisors_len - 1])
1094222578Shselasky		return (-1);
1095222578Shselasky
1096222578Shselasky	for (i = 0; i < umcs7840_baudrate_divisors_len - 1 &&
1097222578Shselasky	    !(rate > umcs7840_baudrate_divisors[i] && rate <= umcs7840_baudrate_divisors[i + 1]); ++i);
1098269470Sjoerg	if (rate == 0)
1099269470Sjoerg		*divisor = 1;	/* XXX */
1100269470Sjoerg	else
1101269470Sjoerg		*divisor = umcs7840_baudrate_divisors[i + 1] / rate;
1102222578Shselasky	/* 0x00 .. 0x70 */
1103222578Shselasky	*clk = i << MCS7840_DEV_SPx_CLOCK_SHIFT;
1104222578Shselasky	return (0);
1105222578Shselasky}
1106