umcs.c revision 223486
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: head/sys/dev/usb/serial/umcs.c 223486 2011-06-24 02:30:02Z hselasky $");
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
82222696ShselaskySYSCTL_NODE(_hw_usb, OID_AUTO, umcs, CTLFLAG_RW, 0, "USB umcs quadport serial adapter");
83222696ShselaskySYSCTL_INT(_hw_usb_umcs, OID_AUTO, debug, CTLFLAG_RW, &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	uint8_t	sc_lsr;			/* local line status register */
125222578Shselasky	uint8_t	sc_msr;			/* local modem status register */
126222578Shselasky};
127222578Shselasky
128222578Shselaskystruct umcs7840_softc {
129222578Shselasky	struct ucom_super_softc sc_super_ucom;
130222578Shselasky	struct ucom_softc sc_ucom[UMCS7840_MAX_PORTS];	/* Need to be continuous
131222578Shselasky							 * array, so indexed by
132222578Shselasky							 * LOGICAL port
133222578Shselasky							 * (subunit) number */
134222578Shselasky
135222578Shselasky	struct usb_xfer *sc_intr_xfer;	/* Interrupt endpoint */
136222578Shselasky
137222578Shselasky	device_t sc_dev;		/* Device for error prints */
138222578Shselasky	struct usb_device *sc_udev;	/* USB Device for all operations */
139222578Shselasky	struct mtx sc_mtx;		/* ucom requires this */
140222578Shselasky
141222578Shselasky	uint8_t	sc_driver_done;		/* Flag when enumeration is finished */
142222578Shselasky
143222578Shselasky	uint8_t	sc_numports;		/* Number of ports (subunits) */
144222578Shselasky	struct umcs7840_softc_oneport sc_ports[UMCS7840_MAX_PORTS];	/* Indexed by PHYSICAL
145222578Shselasky									 * port number. */
146222578Shselasky};
147222578Shselasky
148222578Shselasky/* prototypes */
149222578Shselaskystatic usb_error_t umcs7840_get_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t *);
150222578Shselaskystatic usb_error_t umcs7840_set_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t);
151222578Shselaskystatic usb_error_t umcs7840_get_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t *);
152222578Shselaskystatic usb_error_t umcs7840_set_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t);
153222578Shselasky
154222578Shselaskystatic usb_error_t umcs7840_set_baudrate(struct umcs7840_softc *, uint8_t, uint32_t);
155222578Shselaskystatic usb_error_t umcs7840_calc_baudrate(uint32_t rate, uint16_t *, uint8_t *);
156222578Shselasky
157222578Shselaskystatic void umcs7840_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *);
158222578Shselaskystatic void umcs7840_cfg_set_dtr(struct ucom_softc *, uint8_t);
159222578Shselaskystatic void umcs7840_cfg_set_rts(struct ucom_softc *, uint8_t);
160222578Shselaskystatic void umcs7840_cfg_set_break(struct ucom_softc *, uint8_t);
161222578Shselaskystatic void umcs7840_cfg_param(struct ucom_softc *, struct termios *);
162222578Shselaskystatic void umcs7840_cfg_open(struct ucom_softc *);
163222578Shselaskystatic void umcs7840_cfg_close(struct ucom_softc *);
164222578Shselasky
165222578Shselaskystatic int umcs7840_pre_param(struct ucom_softc *, struct termios *);
166222578Shselasky
167222578Shselaskystatic void umcs7840_start_read(struct ucom_softc *);
168222578Shselaskystatic void umcs7840_stop_read(struct ucom_softc *);
169222578Shselasky
170222578Shselaskystatic void umcs7840_start_write(struct ucom_softc *);
171222578Shselaskystatic void umcs7840_stop_write(struct ucom_softc *);
172222578Shselasky
173222578Shselaskystatic void umcs7840_poll(struct ucom_softc *ucom);
174222578Shselasky
175222578Shselaskystatic device_probe_t umcs7840_probe;
176222578Shselaskystatic device_attach_t umcs7840_attach;
177222578Shselaskystatic device_detach_t umcs7840_detach;
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,
254222578Shselasky};
255222578Shselasky
256223486Shselaskystatic const STRUCT_USB_HOST_ID umcs7840_devs[] = {
257222578Shselasky	{USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7820, 0)},
258222578Shselasky	{USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7840, 0)},
259222578Shselasky};
260222578Shselasky
261222578Shselaskystatic device_method_t umcs7840_methods[] = {
262222578Shselasky	DEVMETHOD(device_probe, umcs7840_probe),
263222578Shselasky	DEVMETHOD(device_attach, umcs7840_attach),
264222578Shselasky	DEVMETHOD(device_detach, umcs7840_detach),
265222578Shselasky	{0, 0}
266222578Shselasky};
267222578Shselasky
268222578Shselaskystatic devclass_t umcs7840_devclass;
269222578Shselasky
270222578Shselaskystatic driver_t umcs7840_driver = {
271222578Shselasky	.name = "umcs7840",
272222578Shselasky	.methods = umcs7840_methods,
273222578Shselasky	.size = sizeof(struct umcs7840_softc),
274222578Shselasky};
275222578Shselasky
276222578ShselaskyDRIVER_MODULE(umcs7840, uhub, umcs7840_driver, umcs7840_devclass, 0, 0);
277222578ShselaskyMODULE_DEPEND(umcs7840, ucom, 1, 1, 1);
278222578ShselaskyMODULE_DEPEND(umcs7840, usb, 1, 1, 1);
279222578ShselaskyMODULE_VERSION(umcs7840, UMCS7840_MODVER);
280222578Shselasky
281222578Shselaskystatic int
282222578Shselaskyumcs7840_probe(device_t dev)
283222578Shselasky{
284222578Shselasky	struct usb_attach_arg *uaa = device_get_ivars(dev);
285222578Shselasky
286222578Shselasky	if (uaa->usb_mode != USB_MODE_HOST)
287222578Shselasky		return (ENXIO);
288222578Shselasky	if (uaa->info.bConfigIndex != MCS7840_CONFIG_INDEX)
289222578Shselasky		return (ENXIO);
290222578Shselasky	if (uaa->info.bIfaceIndex != MCS7840_IFACE_INDEX)
291222578Shselasky		return (ENXIO);
292222578Shselasky	return (usbd_lookup_id_by_uaa(umcs7840_devs, sizeof(umcs7840_devs), uaa));
293222578Shselasky}
294222578Shselasky
295222578Shselaskystatic int
296222578Shselaskyumcs7840_attach(device_t dev)
297222578Shselasky{
298222578Shselasky	struct usb_config umcs7840_config_tmp[UMCS7840_N_TRANSFERS];
299222578Shselasky	struct usb_attach_arg *uaa = device_get_ivars(dev);
300222578Shselasky	struct umcs7840_softc *sc = device_get_softc(dev);
301222578Shselasky
302222578Shselasky	uint8_t iface_index = MCS7840_IFACE_INDEX;
303222578Shselasky	int error;
304222578Shselasky	int subunit;
305222578Shselasky	int n;
306222578Shselasky	uint8_t data;
307222578Shselasky
308222578Shselasky	for (n = 0; n < UMCS7840_N_TRANSFERS; ++n)
309222578Shselasky		umcs7840_config_tmp[n] = umcs7840_bulk_config_data[n];
310222578Shselasky
311222578Shselasky	device_set_usb_desc(dev);
312222578Shselasky	mtx_init(&sc->sc_mtx, "umcs7840", NULL, MTX_DEF);
313222578Shselasky
314222578Shselasky	sc->sc_dev = dev;
315222578Shselasky	sc->sc_udev = uaa->device;
316222578Shselasky
317222578Shselasky	/*
318222578Shselasky	 * Get number of ports
319222578Shselasky	 * Documentation (full datasheet) says, that number of ports is
320222578Shselasky	 * set as MCS7840_DEV_MODE_SELECT24S bit in MODE R/Only
321222578Shselasky	 * register. But vendor driver uses these undocumented
322222578Shselasky	 * register & bit.
323222578Shselasky	 *
324222578Shselasky	 * Experiments show, that MODE register can have `0'
325222578Shselasky	 * (4 ports) bit on 2-port device, so use vendor driver's way.
326222578Shselasky	 *
327222578Shselasky	 * Also, see notes in header file for these constants.
328222578Shselasky	 */
329222578Shselasky	umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_GPIO, &data);
330222578Shselasky	if (data & MCS7840_DEV_GPIO_4PORTS) {
331222578Shselasky		sc->sc_numports = 4;
332222578Shselasky		/* Store physical port numbers in sc_portno */
333222578Shselasky		sc->sc_ucom[0].sc_portno = 0;
334222578Shselasky		sc->sc_ucom[1].sc_portno = 1;
335222578Shselasky		sc->sc_ucom[2].sc_portno = 2;
336222578Shselasky		sc->sc_ucom[3].sc_portno = 3;
337222578Shselasky	} else {
338222578Shselasky		sc->sc_numports = 2;
339222578Shselasky		/* Store physical port numbers in sc_portno */
340222578Shselasky		sc->sc_ucom[0].sc_portno = 0;
341222578Shselasky		sc->sc_ucom[1].sc_portno = 2;	/* '1' is skipped */
342222578Shselasky	}
343222578Shselasky	device_printf(dev, "Chip mcs%04x, found %d active ports\n", uaa->info.idProduct, sc->sc_numports);
344222578Shselasky	if (!umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_MODE, &data)) {
345222578Shselasky		device_printf(dev, "On-die confguration: RST: active %s, HRD: %s, PLL: %s, POR: %s, Ports: %s, EEPROM write %s, IrDA is %savailable\n",
346222578Shselasky		    (data & MCS7840_DEV_MODE_RESET) ? "low" : "high",
347222578Shselasky		    (data & MCS7840_DEV_MODE_SER_PRSNT) ? "yes" : "no",
348222578Shselasky		    (data & MCS7840_DEV_MODE_PLLBYPASS) ? "bypassed" : "avail",
349222578Shselasky		    (data & MCS7840_DEV_MODE_PORBYPASS) ? "bypassed" : "avail",
350222578Shselasky		    (data & MCS7840_DEV_MODE_SELECT24S) ? "2" : "4",
351222578Shselasky		    (data & MCS7840_DEV_MODE_EEPROMWR) ? "enabled" : "disabled",
352222578Shselasky		    (data & MCS7840_DEV_MODE_IRDA) ? "" : "not ");
353222578Shselasky	}
354222578Shselasky	/* Setup all transfers */
355222578Shselasky	for (subunit = 0; subunit < sc->sc_numports; ++subunit) {
356222578Shselasky		for (n = 0; n < UMCS7840_N_TRANSFERS; ++n) {
357222578Shselasky			/* Set endpoint address */
358222578Shselasky			umcs7840_config_tmp[n].endpoint = umcs7840_bulk_config_data[n].endpoint + 2 * sc->sc_ucom[subunit].sc_portno;
359222578Shselasky			umcs7840_config_tmp[n].callback = umcs7840_rw_callbacks[subunit][n];
360222578Shselasky		}
361222578Shselasky		error = usbd_transfer_setup(uaa->device,
362222578Shselasky		    &iface_index, sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, umcs7840_config_tmp,
363222578Shselasky		    UMCS7840_N_TRANSFERS, sc, &sc->sc_mtx);
364222578Shselasky		if (error) {
365222578Shselasky			device_printf(dev, "allocating USB transfers failed for subunit %d of %d\n",
366222578Shselasky			    subunit + 1, sc->sc_numports);
367222578Shselasky			goto detach;
368222578Shselasky		}
369222578Shselasky	}
370222578Shselasky	error = usbd_transfer_setup(uaa->device,
371222578Shselasky	    &iface_index, &sc->sc_intr_xfer, umcs7840_intr_config_data,
372222578Shselasky	    1, sc, &sc->sc_mtx);
373222578Shselasky	if (error) {
374222578Shselasky		device_printf(dev, "allocating USB transfers failed for interrupt\n");
375222578Shselasky		goto detach;
376222578Shselasky	}
377222578Shselasky	/* clear stall at first run */
378222578Shselasky	mtx_lock(&sc->sc_mtx);
379222578Shselasky	for (subunit = 0; subunit < sc->sc_numports; ++subunit) {
380222578Shselasky		usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_RD_EP]);
381222578Shselasky		usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_WR_EP]);
382222578Shselasky	}
383222578Shselasky	mtx_unlock(&sc->sc_mtx);
384222578Shselasky
385222578Shselasky	error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_numports, sc,
386222578Shselasky	    &umcs7840_callback, &sc->sc_mtx);
387222578Shselasky	if (error)
388222578Shselasky		goto detach;
389222578Shselasky
390222578Shselasky	ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
391222578Shselasky
392222578Shselasky	return (0);
393222578Shselasky
394222578Shselaskydetach:
395222578Shselasky	umcs7840_detach(dev);
396222578Shselasky	return (ENXIO);
397222578Shselasky}
398222578Shselasky
399222578Shselaskystatic int
400222578Shselaskyumcs7840_detach(device_t dev)
401222578Shselasky{
402222578Shselasky	struct umcs7840_softc *sc = device_get_softc(dev);
403222578Shselasky	int subunit;
404222578Shselasky
405222578Shselasky	ucom_detach(&sc->sc_super_ucom, sc->sc_ucom);
406222578Shselasky
407222578Shselasky	for (subunit = 0; subunit < sc->sc_numports; ++subunit)
408222578Shselasky		usbd_transfer_unsetup(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, UMCS7840_N_TRANSFERS);
409222578Shselasky	usbd_transfer_unsetup(&sc->sc_intr_xfer, 1);
410222578Shselasky
411222578Shselasky	mtx_destroy(&sc->sc_mtx);
412222578Shselasky	return (0);
413222578Shselasky}
414222578Shselasky
415222578Shselaskystatic void
416222578Shselaskyumcs7840_cfg_open(struct ucom_softc *ucom)
417222578Shselasky{
418222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
419222578Shselasky	uint16_t pn = ucom->sc_portno;
420222578Shselasky	uint8_t data;
421222578Shselasky
422222578Shselasky	/* If it very first open, finish global configuration */
423222578Shselasky	if (!sc->sc_driver_done) {
424222578Shselasky		/*
425222578Shselasky		 * USB enumeration is finished, pass internal memory to FIFOs
426222578Shselasky		 * If it is done in the end of "attach", kernel panics.
427222578Shselasky		 */
428222578Shselasky		if (umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, &data))
429222578Shselasky			return;
430222578Shselasky		data |= MCS7840_DEV_CONTROL1_DRIVER_DONE;
431222578Shselasky		if (umcs7840_set_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, data))
432222578Shselasky			return;
433222578Shselasky		sc->sc_driver_done = 1;
434222578Shselasky	}
435222578Shselasky	/* Toggle reset bit on-off */
436222578Shselasky	if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data))
437222578Shselasky		return;
438222578Shselasky	data |= MCS7840_DEV_SPx_UART_RESET;
439222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
440222578Shselasky		return;
441222578Shselasky	data &= ~MCS7840_DEV_SPx_UART_RESET;
442222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
443222578Shselasky		return;
444222578Shselasky
445222578Shselasky	/* Set RS-232 mode */
446222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_SCRATCHPAD, MCS7840_UART_SCRATCHPAD_RS232))
447222578Shselasky		return;
448222578Shselasky
449222578Shselasky	/* Disable RX on time of initialization */
450222578Shselasky	if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data))
451222578Shselasky		return;
452222578Shselasky	data |= MCS7840_DEV_CONTROLx_RX_DISABLE;
453222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data))
454222578Shselasky		return;
455222578Shselasky
456222578Shselasky	/* Disable all interrupts */
457222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0))
458222578Shselasky		return;
459222578Shselasky
460222578Shselasky	/* Reset FIFO -- documented */
461222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR, 0))
462222578Shselasky		return;
463222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR,
464222578Shselasky	    MCS7840_UART_FCR_ENABLE | MCS7840_UART_FCR_FLUSHRHR |
465222578Shselasky	    MCS7840_UART_FCR_FLUSHTHR | MCS7840_UART_FCR_RTL_1_14))
466222578Shselasky		return;
467222578Shselasky
468222578Shselasky	/* Set 8 bit, no parity, 1 stop bit -- documented */
469222578Shselasky	sc->sc_ports[pn].sc_lcr = MCS7840_UART_LCR_DATALEN8 | MCS7840_UART_LCR_STOPB1;
470222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr))
471222578Shselasky		return;
472222578Shselasky
473222578Shselasky	/*
474222578Shselasky	 * Enable DTR/RTS on modem control, enable modem interrupts --
475222578Shselasky	 * documented
476222578Shselasky	 */
477222578Shselasky	sc->sc_ports[pn].sc_mcr = MCS7840_UART_MCR_DTR | MCS7840_UART_MCR_RTS | MCS7840_UART_MCR_IE;
478222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr))
479222578Shselasky		return;
480222578Shselasky
481222578Shselasky	/* Clearing Bulkin and Bulkout FIFO */
482222578Shselasky	if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data))
483222578Shselasky		return;
484222578Shselasky	data |= MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO;
485222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
486222578Shselasky		return;
487222578Shselasky	data &= ~(MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO);
488222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
489222578Shselasky		return;
490222578Shselasky
491222578Shselasky	/* Set speed 9600 */
492222578Shselasky	if (umcs7840_set_baudrate(sc, pn, 9600))
493222578Shselasky		return;
494222578Shselasky
495222578Shselasky
496222578Shselasky	/* Finally enable all interrupts -- documented */
497222578Shselasky	/*
498222578Shselasky	 * Copied from vendor driver, I don't know why we should read LCR
499222578Shselasky	 * here
500222578Shselasky	 */
501222578Shselasky	if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, &sc->sc_ports[pn].sc_lcr))
502222578Shselasky		return;
503222578Shselasky	if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER,
504222578Shselasky	    MCS7840_UART_IER_RXSTAT | MCS7840_UART_IER_MODEM))
505222578Shselasky		return;
506222578Shselasky
507222578Shselasky	/* Enable RX */
508222578Shselasky	if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data))
509222578Shselasky		return;
510222578Shselasky	data &= ~MCS7840_DEV_CONTROLx_RX_DISABLE;
511222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data))
512222578Shselasky		return;
513222578Shselasky
514222578Shselasky	/* Read LSR & MSR */
515222578Shselasky	if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LSR, &sc->sc_ports[pn].sc_lsr))
516222578Shselasky		return;
517222578Shselasky	if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_MSR, &sc->sc_ports[pn].sc_msr))
518222578Shselasky		return;
519222578Shselasky	DPRINTF("Port %d has been opened, LSR=%02x MSR=%02x\n", pn, sc->sc_ports[pn].sc_lsr, sc->sc_ports[pn].sc_msr);
520222578Shselasky}
521222578Shselasky
522222578Shselaskystatic void
523222578Shselaskyumcs7840_cfg_close(struct ucom_softc *ucom)
524222578Shselasky{
525222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
526222578Shselasky	uint16_t pn = ucom->sc_portno;
527222578Shselasky	uint8_t data;
528222578Shselasky
529222578Shselasky	umcs7840_stop_read(ucom);
530222578Shselasky	umcs7840_stop_write(ucom);
531222578Shselasky
532222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, 0);
533222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0);
534222578Shselasky
535222578Shselasky	/* Disable RX */
536222578Shselasky	if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data))
537222578Shselasky		return;
538222578Shselasky	data |= MCS7840_DEV_CONTROLx_RX_DISABLE;
539222578Shselasky	if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data))
540222578Shselasky		return;
541222578Shselasky	DPRINTF("Port %d has been closed\n", pn);
542222578Shselasky}
543222578Shselasky
544222578Shselaskystatic void
545222578Shselaskyumcs7840_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
546222578Shselasky{
547222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
548222578Shselasky	uint8_t pn = ucom->sc_portno;
549222578Shselasky
550222578Shselasky	if (onoff)
551222578Shselasky		sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_DTR;
552222578Shselasky	else
553222578Shselasky		sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_DTR;
554222578Shselasky
555222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr);
556222578Shselasky	DPRINTF("Port %d DTR set to: %s\n", pn, onoff ? "on" : "off");
557222578Shselasky}
558222578Shselasky
559222578Shselaskystatic void
560222578Shselaskyumcs7840_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
561222578Shselasky{
562222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
563222578Shselasky	uint8_t pn = ucom->sc_portno;
564222578Shselasky
565222578Shselasky	if (onoff)
566222578Shselasky		sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_RTS;
567222578Shselasky	else
568222578Shselasky		sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_RTS;
569222578Shselasky
570222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr);
571222578Shselasky	DPRINTF("Port %d RTS set to: %s\n", pn, onoff ? "on" : "off");
572222578Shselasky}
573222578Shselasky
574222578Shselaskystatic void
575222578Shselaskyumcs7840_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
576222578Shselasky{
577222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
578222578Shselasky	uint8_t pn = ucom->sc_portno;
579222578Shselasky
580222578Shselasky	if (onoff)
581222578Shselasky		sc->sc_ports[pn].sc_lcr |= MCS7840_UART_LCR_BREAK;
582222578Shselasky	else
583222578Shselasky		sc->sc_ports[pn].sc_lcr &= ~MCS7840_UART_LCR_BREAK;
584222578Shselasky
585222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr);
586222578Shselasky	DPRINTF("Port %d BREAK set to: %s\n", pn, onoff ? "on" : "off");
587222578Shselasky}
588222578Shselasky
589222578Shselasky
590222578Shselaskystatic void
591222578Shselaskyumcs7840_cfg_param(struct ucom_softc *ucom, struct termios *t)
592222578Shselasky{
593222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
594222578Shselasky	uint8_t pn = ucom->sc_portno;
595222578Shselasky	uint8_t lcr = sc->sc_ports[pn].sc_lcr;
596222578Shselasky	uint8_t mcr = sc->sc_ports[pn].sc_mcr;
597222578Shselasky
598222578Shselasky	DPRINTF("Port %d config:\n", pn);
599222578Shselasky	if (t->c_cflag & CSTOPB) {
600222578Shselasky		DPRINTF("  2 stop bits\n");
601222578Shselasky		lcr |= MCS7840_UART_LCR_STOPB2;
602222578Shselasky	} else {
603222578Shselasky		lcr |= MCS7840_UART_LCR_STOPB1;
604222578Shselasky		DPRINTF("  1 stop bit\n");
605222578Shselasky	}
606222578Shselasky
607222578Shselasky	lcr &= ~MCS7840_UART_LCR_PARITYMASK;
608222578Shselasky	if (t->c_cflag & PARENB) {
609222578Shselasky		lcr |= MCS7840_UART_LCR_PARITYON;
610222578Shselasky		if (t->c_cflag & PARODD) {
611222578Shselasky			lcr = MCS7840_UART_LCR_PARITYODD;
612222578Shselasky			DPRINTF("  parity on - odd\n");
613222578Shselasky		} else {
614222578Shselasky			lcr = MCS7840_UART_LCR_PARITYEVEN;
615222578Shselasky			DPRINTF("  parity on - even\n");
616222578Shselasky		}
617222578Shselasky	} else {
618222578Shselasky		lcr &= ~MCS7840_UART_LCR_PARITYON;
619222578Shselasky		DPRINTF("  parity off\n");
620222578Shselasky	}
621222578Shselasky
622222578Shselasky	lcr &= ~MCS7840_UART_LCR_DATALENMASK;
623222578Shselasky	switch (t->c_cflag & CSIZE) {
624222578Shselasky	case CS5:
625222578Shselasky		lcr |= MCS7840_UART_LCR_DATALEN5;
626222578Shselasky		DPRINTF("  5 bit\n");
627222578Shselasky		break;
628222578Shselasky	case CS6:
629222578Shselasky		lcr |= MCS7840_UART_LCR_DATALEN6;
630222578Shselasky		DPRINTF("  6 bit\n");
631222578Shselasky		break;
632222578Shselasky	case CS7:
633222578Shselasky		lcr |= MCS7840_UART_LCR_DATALEN7;
634222578Shselasky		DPRINTF("  7 bit\n");
635222578Shselasky		break;
636222578Shselasky	case CS8:
637222578Shselasky		lcr |= MCS7840_UART_LCR_DATALEN8;
638222578Shselasky		DPRINTF("  8 bit\n");
639222578Shselasky		break;
640222578Shselasky	}
641222578Shselasky
642222578Shselasky	if (t->c_cflag & CRTSCTS) {
643222578Shselasky		mcr |= MCS7840_UART_MCR_CTSRTS;
644222578Shselasky		DPRINTF("  CTS/RTS\n");
645222578Shselasky	} else
646222578Shselasky		mcr &= ~MCS7840_UART_MCR_CTSRTS;
647222578Shselasky
648222578Shselasky	if (t->c_cflag & (CDTR_IFLOW | CDSR_OFLOW)) {
649222578Shselasky		mcr |= MCS7840_UART_MCR_DTRDSR;
650222578Shselasky		DPRINTF("  DTR/DSR\n");
651222578Shselasky	} else
652222578Shselasky		mcr &= ~MCS7840_UART_MCR_DTRDSR;
653222578Shselasky
654222578Shselasky	sc->sc_ports[pn].sc_lcr = lcr;
655222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr);
656222578Shselasky	DPRINTF("Port %d LCR=%02x\n", pn, sc->sc_ports[pn].sc_lcr);
657222578Shselasky
658222578Shselasky	sc->sc_ports[pn].sc_mcr = mcr;
659222578Shselasky	umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr);
660222578Shselasky	DPRINTF("Port %d MCR=%02x\n", pn, sc->sc_ports[pn].sc_mcr);
661222578Shselasky
662222578Shselasky	umcs7840_set_baudrate(sc, pn, t->c_ospeed);
663222578Shselasky}
664222578Shselasky
665222578Shselasky
666222578Shselaskystatic int
667222578Shselaskyumcs7840_pre_param(struct ucom_softc *ucom, struct termios *t)
668222578Shselasky{
669222578Shselasky	uint8_t clk;
670222578Shselasky	uint16_t divisor;
671222578Shselasky
672222578Shselasky	if (umcs7840_calc_baudrate(t->c_ospeed, &divisor, &clk) || !divisor)
673222578Shselasky		return (EINVAL);
674222578Shselasky	return (0);
675222578Shselasky}
676222578Shselasky
677222578Shselaskystatic void
678222578Shselaskyumcs7840_start_read(struct ucom_softc *ucom)
679222578Shselasky{
680222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
681222578Shselasky	uint8_t pn = ucom->sc_portno;
682222578Shselasky
683222578Shselasky	/* Start interrupt transfer */
684222578Shselasky	usbd_transfer_start(sc->sc_intr_xfer);
685222578Shselasky
686222578Shselasky	/* Start read transfer */
687222578Shselasky	usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]);
688222578Shselasky}
689222578Shselasky
690222578Shselaskystatic void
691222578Shselaskyumcs7840_stop_read(struct ucom_softc *ucom)
692222578Shselasky{
693222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
694222578Shselasky	uint8_t pn = ucom->sc_portno;
695222578Shselasky
696222578Shselasky	/* Stop read transfer */
697222578Shselasky	usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]);
698222578Shselasky}
699222578Shselasky
700222578Shselaskystatic void
701222578Shselaskyumcs7840_start_write(struct ucom_softc *ucom)
702222578Shselasky{
703222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
704222578Shselasky	uint8_t pn = ucom->sc_portno;
705222578Shselasky
706222578Shselasky	/* Start interrupt transfer */
707222578Shselasky	usbd_transfer_start(sc->sc_intr_xfer);
708222578Shselasky
709222578Shselasky	/* Start write transfer */
710222578Shselasky	usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]);
711222578Shselasky}
712222578Shselasky
713222578Shselaskystatic void
714222578Shselaskyumcs7840_stop_write(struct ucom_softc *ucom)
715222578Shselasky{
716222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
717222578Shselasky	uint8_t pn = ucom->sc_portno;
718222578Shselasky
719222578Shselasky	/* Stop write transfer */
720222578Shselasky	usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]);
721222578Shselasky}
722222578Shselasky
723222578Shselaskystatic void
724222578Shselaskyumcs7840_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
725222578Shselasky{
726222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
727222578Shselasky
728222578Shselasky	*lsr = sc->sc_ports[ucom->sc_portno].sc_lsr;
729222578Shselasky	*msr = sc->sc_ports[ucom->sc_portno].sc_msr;
730222578Shselasky	DPRINTF("Port %d status: LSR=%02x MSR=%02x\n", ucom->sc_portno, *lsr, *msr);
731222578Shselasky}
732222578Shselasky
733222578Shselaskystatic void
734222578Shselaskyumcs7840_intr_callback(struct usb_xfer *xfer, usb_error_t error)
735222578Shselasky{
736222578Shselasky	struct umcs7840_softc *sc = usbd_xfer_softc(xfer);
737222578Shselasky	struct usb_page_cache *pc;
738222578Shselasky	uint8_t buf[13];
739222578Shselasky	int actlen;
740222578Shselasky	int subunit;
741222578Shselasky
742222578Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
743222578Shselasky
744222578Shselasky	switch (USB_GET_STATE(xfer)) {
745222578Shselasky	case USB_ST_TRANSFERRED:
746222578Shselasky		if (actlen == 5 || actlen == 13) {
747222578Shselasky			pc = usbd_xfer_get_frame(xfer, 0);
748222578Shselasky			usbd_copy_out(pc, 0, buf, actlen);
749222578Shselasky			/* Check status of all ports */
750222578Shselasky			for (subunit = 0; subunit < sc->sc_numports; ++subunit) {
751222578Shselasky				uint8_t pn = sc->sc_ucom[subunit].sc_portno;
752222578Shselasky
753222578Shselasky				if (buf[pn] & MCS7840_UART_ISR_NOPENDING)
754222578Shselasky					continue;
755222578Shselasky				DPRINTF("Port %d has pending interrupt: %02x (FIFO: %02x)\n", pn, buf[pn] & MCS7840_UART_ISR_INTMASK, buf[pn] & (~MCS7840_UART_ISR_INTMASK));
756222578Shselasky				switch (buf[pn] & MCS7840_UART_ISR_INTMASK) {
757222578Shselasky				case MCS7840_UART_ISR_RXERR:
758222578Shselasky				case MCS7840_UART_ISR_RXHASDATA:
759222578Shselasky				case MCS7840_UART_ISR_RXTIMEOUT:
760222578Shselasky					/* Read new LSR */
761222578Shselasky					if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LSR, &sc->sc_ports[pn].sc_lsr))
762222578Shselasky						break;	/* Inner switch */
763222578Shselasky					ucom_status_change(&sc->sc_ucom[subunit]);
764222578Shselasky					/* Inner switch */
765222578Shselasky					break;
766222578Shselasky				case MCS7840_UART_ISR_TXEMPTY:
767222578Shselasky					/* Do nothing */
768222578Shselasky					break;	/* Inner switch */
769222578Shselasky				case MCS7840_UART_ISR_MSCHANGE:
770222578Shselasky					/* Read new MSR */
771222578Shselasky					if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_MSR, &sc->sc_ports[pn].sc_msr))
772222578Shselasky						break;	/* Inner switch */
773222578Shselasky					DPRINTF("Port %d: new MSR %02x\n", pn, sc->sc_ports[pn].sc_msr);
774222578Shselasky					ucom_status_change(&sc->sc_ucom[subunit]);
775222578Shselasky					break;
776222578Shselasky				}
777222578Shselasky			}
778222578Shselasky		} else
779222578Shselasky			device_printf(sc->sc_dev, "Invalid interrupt data length %d", actlen);
780222578Shselasky		/* FALLTHROUGH */
781222578Shselasky	case USB_ST_SETUP:
782222578Shselaskytr_setup:
783222578Shselasky		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
784222578Shselasky		usbd_transfer_submit(xfer);
785222578Shselasky		return;
786222578Shselasky
787222578Shselasky	default:			/* Error */
788222578Shselasky		if (error != USB_ERR_CANCELLED) {
789222578Shselasky			/* try to clear stall first */
790222578Shselasky			usbd_xfer_set_stall(xfer);
791222578Shselasky			goto tr_setup;
792222578Shselasky		}
793222578Shselasky		return;
794222578Shselasky	}
795222578Shselasky}
796222578Shselasky
797222578Shselaskystatic void
798222578Shselaskyumcs7840_read_callback1(struct usb_xfer *xfer, usb_error_t error)
799222578Shselasky{
800222578Shselasky	umcs7840_read_callbackN(xfer, error, 0);
801222578Shselasky}
802222578Shselasky
803222578Shselaskystatic void
804222578Shselaskyumcs7840_read_callback2(struct usb_xfer *xfer, usb_error_t error)
805222578Shselasky{
806222578Shselasky	umcs7840_read_callbackN(xfer, error, 1);
807222578Shselasky}
808222578Shselaskystatic void
809222578Shselaskyumcs7840_read_callback3(struct usb_xfer *xfer, usb_error_t error)
810222578Shselasky{
811222578Shselasky	umcs7840_read_callbackN(xfer, error, 2);
812222578Shselasky}
813222578Shselasky
814222578Shselaskystatic void
815222578Shselaskyumcs7840_read_callback4(struct usb_xfer *xfer, usb_error_t error)
816222578Shselasky{
817222578Shselasky	umcs7840_read_callbackN(xfer, error, 3);
818222578Shselasky}
819222578Shselasky
820222578Shselaskystatic void
821222578Shselaskyumcs7840_read_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit)
822222578Shselasky{
823222578Shselasky	struct umcs7840_softc *sc = usbd_xfer_softc(xfer);
824222578Shselasky	struct ucom_softc *ucom = &sc->sc_ucom[subunit];
825222578Shselasky	struct usb_page_cache *pc;
826222578Shselasky	int actlen;
827222578Shselasky
828222578Shselasky	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
829222578Shselasky
830222578Shselasky	DPRINTF("Port %d read, state = %d, data length = %d\n", ucom->sc_portno, USB_GET_STATE(xfer), actlen);
831222578Shselasky
832222578Shselasky	switch (USB_GET_STATE(xfer)) {
833222578Shselasky	case USB_ST_TRANSFERRED:
834222578Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
835222578Shselasky		ucom_put_data(ucom, pc, 0, actlen);
836222578Shselasky		/* FALLTHROUGH */
837222578Shselasky	case USB_ST_SETUP:
838222578Shselaskytr_setup:
839222578Shselasky		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
840222578Shselasky		usbd_transfer_submit(xfer);
841222578Shselasky		return;
842222578Shselasky
843222578Shselasky	default:			/* Error */
844222578Shselasky		if (error != USB_ERR_CANCELLED) {
845222578Shselasky			/* try to clear stall first */
846222578Shselasky			usbd_xfer_set_stall(xfer);
847222578Shselasky			goto tr_setup;
848222578Shselasky		}
849222578Shselasky		return;
850222578Shselasky	}
851222578Shselasky}
852222578Shselasky
853222578Shselaskystatic void
854222578Shselaskyumcs7840_write_callback1(struct usb_xfer *xfer, usb_error_t error)
855222578Shselasky{
856222578Shselasky	umcs7840_write_callbackN(xfer, error, 0);
857222578Shselasky}
858222578Shselasky
859222578Shselaskystatic void
860222578Shselaskyumcs7840_write_callback2(struct usb_xfer *xfer, usb_error_t error)
861222578Shselasky{
862222578Shselasky	umcs7840_write_callbackN(xfer, error, 1);
863222578Shselasky}
864222578Shselasky
865222578Shselaskystatic void
866222578Shselaskyumcs7840_write_callback3(struct usb_xfer *xfer, usb_error_t error)
867222578Shselasky{
868222578Shselasky	umcs7840_write_callbackN(xfer, error, 2);
869222578Shselasky}
870222578Shselasky
871222578Shselaskystatic void
872222578Shselaskyumcs7840_write_callback4(struct usb_xfer *xfer, usb_error_t error)
873222578Shselasky{
874222578Shselasky	umcs7840_write_callbackN(xfer, error, 3);
875222578Shselasky}
876222578Shselasky
877222578Shselaskystatic void
878222578Shselaskyumcs7840_write_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit)
879222578Shselasky{
880222578Shselasky	struct umcs7840_softc *sc = usbd_xfer_softc(xfer);
881222578Shselasky	struct ucom_softc *ucom = &sc->sc_ucom[subunit];
882222578Shselasky	struct usb_page_cache *pc;
883222578Shselasky	uint32_t actlen;
884222578Shselasky
885222578Shselasky	DPRINTF("Port %d write, state = %d\n", ucom->sc_portno, USB_GET_STATE(xfer));
886222578Shselasky
887222578Shselasky	switch (USB_GET_STATE(xfer)) {
888222578Shselasky	case USB_ST_SETUP:
889222578Shselasky	case USB_ST_TRANSFERRED:
890222578Shselaskytr_setup:
891222578Shselasky		pc = usbd_xfer_get_frame(xfer, 0);
892222578Shselasky		if (ucom_get_data(ucom, pc, 0, usbd_xfer_max_len(xfer), &actlen)) {
893222578Shselasky			DPRINTF("Port %d write, has %d bytes\n", ucom->sc_portno, actlen);
894222578Shselasky			usbd_xfer_set_frame_len(xfer, 0, actlen);
895222578Shselasky			usbd_transfer_submit(xfer);
896222578Shselasky		}
897222578Shselasky		return;
898222578Shselasky
899222578Shselasky	default:			/* Error */
900222578Shselasky		if (error != USB_ERR_CANCELLED) {
901222578Shselasky			/* try to clear stall first */
902222578Shselasky			usbd_xfer_set_stall(xfer);
903222578Shselasky			goto tr_setup;
904222578Shselasky		}
905222578Shselasky		return;
906222578Shselasky	}
907222578Shselasky}
908222578Shselasky
909222578Shselaskystatic void
910222578Shselaskyumcs7840_poll(struct ucom_softc *ucom)
911222578Shselasky{
912222578Shselasky	struct umcs7840_softc *sc = ucom->sc_parent;
913222578Shselasky
914222578Shselasky	DPRINTF("Port %d poll\n", ucom->sc_portno);
915222578Shselasky	usbd_transfer_poll(sc->sc_ports[ucom->sc_portno].sc_xfer, UMCS7840_N_TRANSFERS);
916222578Shselasky	usbd_transfer_poll(&sc->sc_intr_xfer, 1);
917222578Shselasky}
918222578Shselasky
919222578Shselaskystatic usb_error_t
920222578Shselaskyumcs7840_get_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t *data)
921222578Shselasky{
922222578Shselasky	struct usb_device_request req;
923222578Shselasky	usb_error_t err;
924222578Shselasky	uint16_t len;
925222578Shselasky
926222578Shselasky	req.bmRequestType = UT_READ_VENDOR_DEVICE;
927222578Shselasky	req.bRequest = MCS7840_RDREQ;
928222578Shselasky	USETW(req.wValue, 0);
929222578Shselasky	USETW(req.wIndex, reg);
930222578Shselasky	USETW(req.wLength, UMCS7840_READ_LENGTH);
931222578Shselasky
932222578Shselasky	err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT);
933222578Shselasky	if (err == USB_ERR_NORMAL_COMPLETION && len != 1) {
934222578Shselasky		device_printf(sc->sc_dev, "Reading register %d failed: invalid length %d\n", reg, len);
935222578Shselasky		return (USB_ERR_INVAL);
936222578Shselasky	} else if (err)
937222578Shselasky		device_printf(sc->sc_dev, "Reading register %d failed: %s\n", reg, usbd_errstr(err));
938222578Shselasky	return (err);
939222578Shselasky}
940222578Shselasky
941222578Shselaskystatic usb_error_t
942222578Shselaskyumcs7840_set_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t data)
943222578Shselasky{
944222578Shselasky	struct usb_device_request req;
945222578Shselasky	usb_error_t err;
946222578Shselasky
947222578Shselasky	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
948222578Shselasky	req.bRequest = MCS7840_WRREQ;
949222578Shselasky	USETW(req.wValue, data);
950222578Shselasky	USETW(req.wIndex, reg);
951222578Shselasky	USETW(req.wLength, 0);
952222578Shselasky
953222578Shselasky	err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT);
954222578Shselasky	if (err)
955222578Shselasky		device_printf(sc->sc_dev, "Writing register %d failed: %s\n", reg, usbd_errstr(err));
956222578Shselasky
957222578Shselasky	return (err);
958222578Shselasky}
959222578Shselasky
960222578Shselaskystatic usb_error_t
961222578Shselaskyumcs7840_get_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t *data)
962222578Shselasky{
963222578Shselasky	struct usb_device_request req;
964222578Shselasky	uint16_t wVal;
965222578Shselasky	usb_error_t err;
966222578Shselasky	uint16_t len;
967222578Shselasky
968222578Shselasky	/* portno is port number */
969222578Shselasky	wVal = ((uint16_t)(portno + 1)) << 8;
970222578Shselasky
971222578Shselasky	req.bmRequestType = UT_READ_VENDOR_DEVICE;
972222578Shselasky	req.bRequest = MCS7840_RDREQ;
973222578Shselasky	USETW(req.wValue, wVal);
974222578Shselasky	USETW(req.wIndex, reg);
975222578Shselasky	USETW(req.wLength, UMCS7840_READ_LENGTH);
976222578Shselasky
977222578Shselasky	err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT);
978222578Shselasky	if (err == USB_ERR_NORMAL_COMPLETION && len != 1) {
979222578Shselasky		device_printf(sc->sc_dev, "Reading UART%d register %d failed: invalid length %d\n", portno, reg, len);
980222578Shselasky		return (USB_ERR_INVAL);
981222578Shselasky	} else if (err)
982222578Shselasky		device_printf(sc->sc_dev, "Reading UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err));
983222578Shselasky	return (err);
984222578Shselasky}
985222578Shselasky
986222578Shselaskystatic usb_error_t
987222578Shselaskyumcs7840_set_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t data)
988222578Shselasky{
989222578Shselasky	struct usb_device_request req;
990222578Shselasky	usb_error_t err;
991222578Shselasky	uint16_t wVal;
992222578Shselasky
993222578Shselasky	/* portno is port number */
994222578Shselasky	wVal = ((uint16_t)(portno + 1)) << 8 | data;
995222578Shselasky
996222578Shselasky	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
997222578Shselasky	req.bRequest = MCS7840_WRREQ;
998222578Shselasky	USETW(req.wValue, wVal);
999222578Shselasky	USETW(req.wIndex, reg);
1000222578Shselasky	USETW(req.wLength, 0);
1001222578Shselasky
1002222578Shselasky	err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT);
1003222578Shselasky	if (err)
1004222578Shselasky		device_printf(sc->sc_dev, "Writing UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err));
1005222578Shselasky	return (err);
1006222578Shselasky}
1007222578Shselasky
1008222578Shselaskystatic usb_error_t
1009222578Shselaskyumcs7840_set_baudrate(struct umcs7840_softc *sc, uint8_t portno, uint32_t rate)
1010222578Shselasky{
1011222578Shselasky	usb_error_t err;
1012222578Shselasky	uint16_t divisor;
1013222578Shselasky	uint8_t clk;
1014222578Shselasky	uint8_t data;
1015222578Shselasky
1016222578Shselasky	if (umcs7840_calc_baudrate(rate, &divisor, &clk)) {
1017222578Shselasky		DPRINTF("Port %d bad speed: %d\n", portno, rate);
1018222578Shselasky		return (-1);
1019222578Shselasky	}
1020222578Shselasky	if (divisor == 0 || (clk & MCS7840_DEV_SPx_CLOCK_MASK) != clk) {
1021222578Shselasky		DPRINTF("Port %d bad speed calculation: %d\n", portno, rate);
1022222578Shselasky		return (-1);
1023222578Shselasky	}
1024222578Shselasky	DPRINTF("Port %d set speed: %d (%02x / %d)\n", portno, rate, clk, divisor);
1025222578Shselasky
1026222578Shselasky	/* Set clock source for standard BAUD frequences */
1027222578Shselasky	err = umcs7840_get_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, &data);
1028222578Shselasky	if (err)
1029222578Shselasky		return (err);
1030222578Shselasky	data &= MCS7840_DEV_SPx_CLOCK_MASK;
1031222578Shselasky	data |= clk;
1032222578Shselasky	err = umcs7840_set_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, data);
1033222578Shselasky	if (err)
1034222578Shselasky		return (err);
1035222578Shselasky
1036222578Shselasky	/* Set divider */
1037222578Shselasky	sc->sc_ports[portno].sc_lcr |= MCS7840_UART_LCR_DIVISORS;
1038222578Shselasky	err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr);
1039222578Shselasky	if (err)
1040222578Shselasky		return (err);
1041222578Shselasky
1042222578Shselasky	err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLL, (uint8_t)(divisor & 0xff));
1043222578Shselasky	if (err)
1044222578Shselasky		return (err);
1045222578Shselasky	err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLM, (uint8_t)((divisor >> 8) & 0xff));
1046222578Shselasky	if (err)
1047222578Shselasky		return (err);
1048222578Shselasky
1049222578Shselasky	/* Turn off access to DLL/DLM registers of UART */
1050222578Shselasky	sc->sc_ports[portno].sc_lcr &= ~MCS7840_UART_LCR_DIVISORS;
1051222578Shselasky	err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr);
1052222578Shselasky	if (err)
1053222578Shselasky		return (err);
1054222578Shselasky	return (0);
1055222578Shselasky}
1056222578Shselasky
1057222578Shselasky/* Maximum speeds for standard frequences, when PLL is not used */
1058222578Shselaskystatic const uint32_t umcs7840_baudrate_divisors[] = {0, 115200, 230400, 403200, 460800, 806400, 921600, 1572864, 3145728,};
1059222578Shselaskystatic const uint8_t umcs7840_baudrate_divisors_len = sizeof(umcs7840_baudrate_divisors) / sizeof(umcs7840_baudrate_divisors[0]);
1060222578Shselasky
1061222578Shselaskystatic usb_error_t
1062222578Shselaskyumcs7840_calc_baudrate(uint32_t rate, uint16_t *divisor, uint8_t *clk)
1063222578Shselasky{
1064222578Shselasky	uint8_t i = 0;
1065222578Shselasky
1066222578Shselasky	if (rate > umcs7840_baudrate_divisors[umcs7840_baudrate_divisors_len - 1])
1067222578Shselasky		return (-1);
1068222578Shselasky
1069222578Shselasky	for (i = 0; i < umcs7840_baudrate_divisors_len - 1 &&
1070222578Shselasky	    !(rate > umcs7840_baudrate_divisors[i] && rate <= umcs7840_baudrate_divisors[i + 1]); ++i);
1071222578Shselasky	*divisor = umcs7840_baudrate_divisors[i + 1] / rate;
1072222578Shselasky	/* 0x00 .. 0x70 */
1073222578Shselasky	*clk = i << MCS7840_DEV_SPx_CLOCK_SHIFT;
1074222578Shselasky	return (0);
1075222578Shselasky}
1076