xref: /openbsd-src/sys/dev/usb/uchcom.c (revision fabcfecb28af5579f8c0c1cb806278199e88da42)
1*fabcfecbSjsg /*	$OpenBSD: uchcom.c,v 1.37 2024/10/22 21:50:02 jsg Exp $	*/
2ad942b15Sjsg /*	$NetBSD: uchcom.c,v 1.1 2007/09/03 17:57:37 tshiozak Exp $	*/
3ad942b15Sjsg 
4ad942b15Sjsg /*
5ad942b15Sjsg  * Copyright (c) 2007 The NetBSD Foundation, Inc.
6ad942b15Sjsg  * All rights reserved.
7ad942b15Sjsg  *
8ad942b15Sjsg  * This code is derived from software contributed to The NetBSD Foundation
9ad942b15Sjsg  * by Takuya SHIOZAKI (tshiozak@netbsd.org).
10ad942b15Sjsg  *
11ad942b15Sjsg  * Redistribution and use in source and binary forms, with or without
12ad942b15Sjsg  * modification, are permitted provided that the following conditions
13ad942b15Sjsg  * are met:
14ad942b15Sjsg  * 1. Redistributions of source code must retain the above copyright
15ad942b15Sjsg  *    notice, this list of conditions and the following disclaimer.
16ad942b15Sjsg  * 2. Redistributions in binary form must reproduce the above copyright
17ad942b15Sjsg  *    notice, this list of conditions and the following disclaimer in the
18ad942b15Sjsg  *    documentation and/or other materials provided with the distribution.
19ad942b15Sjsg  *
20ad942b15Sjsg  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21ad942b15Sjsg  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22ad942b15Sjsg  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23ad942b15Sjsg  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24ad942b15Sjsg  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25ad942b15Sjsg  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26ad942b15Sjsg  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27ad942b15Sjsg  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28ad942b15Sjsg  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29ad942b15Sjsg  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30ad942b15Sjsg  * POSSIBILITY OF SUCH DAMAGE.
31ad942b15Sjsg  */
32ad942b15Sjsg 
33ad942b15Sjsg /*
34e990f8b2Skevlo  * driver for WinChipHead CH9102/343/341/340.
35ad942b15Sjsg  */
36ad942b15Sjsg 
37ad942b15Sjsg #include <sys/param.h>
38ad942b15Sjsg #include <sys/systm.h>
39ad942b15Sjsg #include <sys/malloc.h>
40ad942b15Sjsg #include <sys/tty.h>
41ad942b15Sjsg #include <sys/device.h>
42ad942b15Sjsg 
4335f382c9Skevlo #include <machine/bus.h>
4435f382c9Skevlo 
45ad942b15Sjsg #include <dev/usb/usb.h>
46ad942b15Sjsg #include <dev/usb/usbdi.h>
4735f382c9Skevlo #include <dev/usb/usbdivar.h>
48ad942b15Sjsg #include <dev/usb/usbdevs.h>
49ad942b15Sjsg #include <dev/usb/ucomvar.h>
50ad942b15Sjsg 
51ad942b15Sjsg #ifdef UCHCOM_DEBUG
52637ea8a0Sjsg #define DPRINTFN(n, x)  do { if (uchcomdebug > (n)) printf x; } while (0)
53ad942b15Sjsg int	uchcomdebug = 0;
54ad942b15Sjsg #else
55ad942b15Sjsg #define DPRINTFN(n, x)
56ad942b15Sjsg #endif
57ad942b15Sjsg #define DPRINTF(x) DPRINTFN(0, x)
58ad942b15Sjsg 
59ad942b15Sjsg #define	UCHCOM_IFACE_INDEX		0
6035f382c9Skevlo #define UCHCOM_SECOND_IFACE_INDEX	1
61ad942b15Sjsg 
62ad942b15Sjsg #define UCHCOM_REV_CH340	0x0250
6335f382c9Skevlo #define UCHCOM_REV_CH343	0x0440
64ad942b15Sjsg #define UCHCOM_INPUT_BUF_SIZE	8
65ad942b15Sjsg 
66ad942b15Sjsg #define UCHCOM_REQ_GET_VERSION		0x5F
67ad942b15Sjsg #define UCHCOM_REQ_READ_REG		0x95
68ad942b15Sjsg #define UCHCOM_REQ_WRITE_REG		0x9A
69ad942b15Sjsg #define UCHCOM_REQ_RESET		0xA1
70ad942b15Sjsg #define UCHCOM_REQ_SET_DTRRTS		0xA4
7135f382c9Skevlo #define UCHCOM_REQ_CH343_WRITE_REG	0xA8
7235f382c9Skevlo #define UCHCOM_REQ_SET_BAUDRATE		UCHCOM_REQ_RESET
73ad942b15Sjsg 
74ad942b15Sjsg #define UCHCOM_REG_STAT1	0x06
75ad942b15Sjsg #define UCHCOM_REG_STAT2	0x07
76ad942b15Sjsg #define UCHCOM_REG_BPS_PRE	0x12
77ad942b15Sjsg #define UCHCOM_REG_BPS_DIV	0x13
78ad942b15Sjsg #define UCHCOM_REG_BPS_MOD	0x14
79ad942b15Sjsg #define UCHCOM_REG_BPS_PAD	0x0F
800b89560cSdlg #define UCHCOM_REG_BREAK	0x05
810b89560cSdlg #define UCHCOM_REG_LCR		0x18
82ad942b15Sjsg #define UCHCOM_REG_LCR2		0x25
83ad942b15Sjsg 
84ad942b15Sjsg #define UCHCOM_VER_20		0x20
85ad942b15Sjsg 
86ad942b15Sjsg #define UCHCOM_BASE_UNKNOWN	0
87ad942b15Sjsg #define UCHCOM_BPS_MOD_BASE	20000000
88ad942b15Sjsg #define UCHCOM_BPS_MOD_BASE_OFS	1100
89ad942b15Sjsg 
900b89560cSdlg #define UCHCOM_BPS_PRE_IMM	0x80	/* CH341: immediate RX forwarding */
910b89560cSdlg 
92ad942b15Sjsg #define UCHCOM_DTR_MASK		0x20
93ad942b15Sjsg #define UCHCOM_RTS_MASK		0x40
94ad942b15Sjsg 
950b89560cSdlg #define UCHCOM_BREAK_MASK	0x01
9635f382c9Skevlo #define UCHCOM_ABREAK_MASK	0x10
9735f382c9Skevlo #define UCHCOM_CH343_BREAK_MASK	0x80
980b89560cSdlg 
990b89560cSdlg #define UCHCOM_LCR_CS5		0x00
1000b89560cSdlg #define UCHCOM_LCR_CS6		0x01
1010b89560cSdlg #define UCHCOM_LCR_CS7		0x02
1020b89560cSdlg #define UCHCOM_LCR_CS8		0x03
1030b89560cSdlg #define UCHCOM_LCR_STOPB	0x04
1040b89560cSdlg #define UCHCOM_LCR_PARENB	0x08
1050b89560cSdlg #define UCHCOM_LCR_PARODD	0x00
1060b89560cSdlg #define UCHCOM_LCR_PAREVEN	0x10
1070b89560cSdlg #define UCHCOM_LCR_PARMARK	0x20
1080b89560cSdlg #define UCHCOM_LCR_PARSPACE	0x30
1090b89560cSdlg #define UCHCOM_LCR_TXE		0x40
1100b89560cSdlg #define UCHCOM_LCR_RXE		0x80
111ad942b15Sjsg 
112ad942b15Sjsg #define UCHCOM_INTR_STAT1	0x02
113ad942b15Sjsg #define UCHCOM_INTR_STAT2	0x03
114ad942b15Sjsg #define UCHCOM_INTR_LEAST	4
115ad942b15Sjsg 
11635f382c9Skevlo #define UCHCOM_T		0x08
11735f382c9Skevlo #define UCHCOM_CL		0x04
11835f382c9Skevlo #define UCHCOM_CT		0x80
1195165e579Ssasano /*
1205165e579Ssasano  * XXX - these magic numbers come from Linux (drivers/usb/serial/ch341.c).
1215165e579Ssasano  * The manufacturer was unresponsive when asked for documentation.
1225165e579Ssasano  */
1235165e579Ssasano #define UCHCOM_RESET_VALUE	0x501F	/* line mode? */
1245165e579Ssasano #define UCHCOM_RESET_INDEX	0xD90A	/* baud rate? */
1255165e579Ssasano 
126ad942b15Sjsg #define UCHCOMOBUFSIZE 256
127ad942b15Sjsg 
12835f382c9Skevlo #define UCHCOM_TYPE_CH343	1
12935f382c9Skevlo 
130*fabcfecbSjsg struct uchcom_softc {
131ad942b15Sjsg 	struct device		 sc_dev;
132ab0b1be7Smglocker 	struct usbd_device	*sc_udev;
133ad942b15Sjsg 	struct device		*sc_subdev;
13435f382c9Skevlo 	struct usbd_interface	*sc_intr_iface;
13535f382c9Skevlo 	struct usbd_interface	*sc_data_iface;
136ad942b15Sjsg 	/* */
137ad942b15Sjsg 	int			 sc_intr_endpoint;
138ab0b1be7Smglocker 	struct usbd_pipe	*sc_intr_pipe;
139ad942b15Sjsg 	u_char			*sc_intr_buf;
140234dfda1Sderaadt 	int			 sc_isize;
141ad942b15Sjsg 	/* */
142e219843cSdlg 	int			 sc_release;
143ad942b15Sjsg 	uint8_t			 sc_version;
14435f382c9Skevlo 	int			 sc_type;
145ad942b15Sjsg 	int			 sc_dtr;
146ad942b15Sjsg 	int			 sc_rts;
147ad942b15Sjsg 	u_char			 sc_lsr;
148ad942b15Sjsg 	u_char			 sc_msr;
149ad942b15Sjsg 	int			 sc_lcr1;
150ad942b15Sjsg 	int			 sc_lcr2;
151ad942b15Sjsg };
152ad942b15Sjsg 
153*fabcfecbSjsg struct uchcom_endpoints {
154ad942b15Sjsg 	int		ep_bulkin;
1557f0d0f7fSuaa 	int		ep_bulkin_size;
156ad942b15Sjsg 	int		ep_bulkout;
157ad942b15Sjsg 	int		ep_intr;
158ad942b15Sjsg 	int		ep_intr_size;
159ad942b15Sjsg };
160ad942b15Sjsg 
161*fabcfecbSjsg struct uchcom_divider {
162ad942b15Sjsg 	uint8_t		dv_prescaler;
163ad942b15Sjsg 	uint8_t		dv_div;
164ad942b15Sjsg 	uint8_t		dv_mod;
165ad942b15Sjsg };
166ad942b15Sjsg 
167*fabcfecbSjsg struct uchcom_divider_record {
168ad942b15Sjsg 	uint32_t		dvr_high;
169ad942b15Sjsg 	uint32_t		dvr_low;
170ad942b15Sjsg 	uint32_t		dvr_base_clock;
171ad942b15Sjsg 	struct uchcom_divider	dvr_divider;
172ad942b15Sjsg };
173ad942b15Sjsg 
174ad942b15Sjsg static const struct uchcom_divider_record dividers[] =
175ad942b15Sjsg {
176ad942b15Sjsg 	{  307200, 307200, UCHCOM_BASE_UNKNOWN, { 7, 0xD9, 0 } },
177ad942b15Sjsg 	{  921600, 921600, UCHCOM_BASE_UNKNOWN, { 7, 0xF3, 0 } },
178ad942b15Sjsg 	{ 2999999,  23530,             6000000, { 3,    0, 0 } },
179ad942b15Sjsg 	{   23529,   2942,              750000, { 2,    0, 0 } },
180ad942b15Sjsg 	{    2941,    368,               93750, { 1,    0, 0 } },
181ad942b15Sjsg 	{     367,      1,               11719, { 0,    0, 0 } },
182ad942b15Sjsg };
183ad942b15Sjsg 
184ad942b15Sjsg void		uchcom_get_status(void *, int, u_char *, u_char *);
185ad942b15Sjsg void		uchcom_set(void *, int, int, int);
186ad942b15Sjsg int		uchcom_param(void *, int, struct termios *);
187ad942b15Sjsg int		uchcom_open(void *, int);
188ad942b15Sjsg void		uchcom_close(void *, int);
189ab0b1be7Smglocker void		uchcom_intr(struct usbd_xfer *, void *, usbd_status);
190ad942b15Sjsg 
191aaf33dadSjsg int		uchcom_find_endpoints(struct uchcom_softc *,
192ad942b15Sjsg 		    struct uchcom_endpoints *);
193aaf33dadSjsg void		uchcom_close_intr_pipe(struct uchcom_softc *);
194ad942b15Sjsg 
195ad942b15Sjsg 
196aaf33dadSjsg usbd_status	uchcom_generic_control_out(struct uchcom_softc *sc,
197aaf33dadSjsg 		    uint8_t reqno, uint16_t value, uint16_t index);
198aaf33dadSjsg usbd_status	uchcom_generic_control_in(struct uchcom_softc *, uint8_t,
199ad942b15Sjsg 		    uint16_t, uint16_t, void *, int, int *);
200aaf33dadSjsg usbd_status	uchcom_write_reg(struct uchcom_softc *, uint8_t, uint8_t,
201aaf33dadSjsg 		    uint8_t, uint8_t);
202aaf33dadSjsg usbd_status	uchcom_read_reg(struct uchcom_softc *, uint8_t, uint8_t *,
203aaf33dadSjsg 		    uint8_t, uint8_t *);
204aaf33dadSjsg usbd_status	uchcom_get_version(struct uchcom_softc *, uint8_t *);
205aaf33dadSjsg usbd_status	uchcom_read_status(struct uchcom_softc *, uint8_t *);
206aaf33dadSjsg usbd_status	uchcom_set_dtrrts_10(struct uchcom_softc *, uint8_t);
207aaf33dadSjsg usbd_status	uchcom_set_dtrrts_20(struct uchcom_softc *, uint8_t);
208aaf33dadSjsg int		uchcom_update_version(struct uchcom_softc *);
209aaf33dadSjsg void		uchcom_convert_status(struct uchcom_softc *, uint8_t);
210aaf33dadSjsg int		uchcom_update_status(struct uchcom_softc *);
211aaf33dadSjsg int		uchcom_set_dtrrts(struct uchcom_softc *, int, int);
212aaf33dadSjsg int		uchcom_set_break(struct uchcom_softc *, int);
21335f382c9Skevlo int		uchcom_set_break_ch343(struct uchcom_softc *, int);
21435f382c9Skevlo void		uchcom_calc_baudrate_ch343(uint32_t, uint8_t *, uint8_t *);
215aaf33dadSjsg int		uchcom_calc_divider_settings(struct uchcom_divider *, uint32_t);
21635f382c9Skevlo int		uchcom_set_dte_rate_ch343(struct uchcom_softc *, uint32_t,
21735f382c9Skevlo 		    uint16_t);
218aaf33dadSjsg int		uchcom_set_dte_rate(struct uchcom_softc *, uint32_t);
21935f382c9Skevlo uint16_t	uchcom_set_line_control(struct uchcom_softc *, tcflag_t,
22035f382c9Skevlo 		    uint16_t *);
221aaf33dadSjsg int		uchcom_clear_chip(struct uchcom_softc *);
222aaf33dadSjsg int		uchcom_reset_chip(struct uchcom_softc *);
223aaf33dadSjsg int		uchcom_setup_comm(struct uchcom_softc *);
224aaf33dadSjsg int		uchcom_setup_intr_pipe(struct uchcom_softc *);
225ad942b15Sjsg 
226ad942b15Sjsg 
227ad942b15Sjsg int		uchcom_match(struct device *, void *, void *);
228ad942b15Sjsg void		uchcom_attach(struct device *, struct device *, void *);
229ad942b15Sjsg int		uchcom_detach(struct device *, int);
230ad942b15Sjsg 
231c520a48cSnaddy const struct ucom_methods uchcom_methods = {
2323ab3d9c7Sjsg 	uchcom_get_status,
2333ab3d9c7Sjsg 	uchcom_set,
2343ab3d9c7Sjsg 	uchcom_param,
2353ab3d9c7Sjsg 	NULL,
2363ab3d9c7Sjsg 	uchcom_open,
2373ab3d9c7Sjsg 	uchcom_close,
2383ab3d9c7Sjsg 	NULL,
2393ab3d9c7Sjsg 	NULL,
240ad942b15Sjsg };
241ad942b15Sjsg 
242ad942b15Sjsg static const struct usb_devno uchcom_devs[] = {
243ad942b15Sjsg 	{ USB_VENDOR_WCH, USB_PRODUCT_WCH_CH341 },
244abb3e393Sjsg 	{ USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH340 },
24535f382c9Skevlo 	{ USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341A },
246e990f8b2Skevlo 	{ USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH343 },
247e990f8b2Skevlo 	{ USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH9102 }
248ad942b15Sjsg };
249ad942b15Sjsg 
250ad942b15Sjsg struct cfdriver uchcom_cd = {
251ad942b15Sjsg 	NULL, "uchcom", DV_DULL
252ad942b15Sjsg };
253ad942b15Sjsg 
254ad942b15Sjsg const struct cfattach uchcom_ca = {
255cf8c8cdaSmpi 	sizeof(struct uchcom_softc), uchcom_match, uchcom_attach, uchcom_detach
256ad942b15Sjsg };
257ad942b15Sjsg 
258ad942b15Sjsg /* ----------------------------------------------------------------------
259ad942b15Sjsg  * driver entry points
260ad942b15Sjsg  */
261ad942b15Sjsg 
262ad942b15Sjsg int
263ad942b15Sjsg uchcom_match(struct device *parent, void *match, void *aux)
264ad942b15Sjsg {
265ad942b15Sjsg 	struct usb_attach_arg *uaa = aux;
266ad942b15Sjsg 
267c0d38480Smpi 	if (uaa->iface == NULL)
268ad942b15Sjsg 		return UMATCH_NONE;
269ad942b15Sjsg 
270df857f07Sjasper 	return (usb_lookup(uchcom_devs, uaa->vendor, uaa->product) != NULL ?
271ad942b15Sjsg 	    UMATCH_VENDOR_PRODUCT : UMATCH_NONE);
272ad942b15Sjsg }
273ad942b15Sjsg 
274ad942b15Sjsg void
275ad942b15Sjsg uchcom_attach(struct device *parent, struct device *self, void *aux)
276ad942b15Sjsg {
277ad942b15Sjsg 	struct uchcom_softc *sc = (struct uchcom_softc *)self;
278ad942b15Sjsg 	struct usb_attach_arg *uaa = aux;
279ad942b15Sjsg 	struct ucom_attach_args uca;
280ad942b15Sjsg 	struct uchcom_endpoints endpoints;
281ad942b15Sjsg 
28235f382c9Skevlo 	sc->sc_udev = uaa->device;
28335f382c9Skevlo 	sc->sc_intr_iface = uaa->iface;
284ad942b15Sjsg 	sc->sc_dtr = sc->sc_rts = -1;
285e219843cSdlg 	sc->sc_release = uaa->release;
286ad942b15Sjsg 
287ad942b15Sjsg 	DPRINTF(("\n\nuchcom attach: sc=%p\n", sc));
288ad942b15Sjsg 
28935f382c9Skevlo 	if (sc->sc_release >= UCHCOM_REV_CH343) {
29035f382c9Skevlo 		printf("%s: CH343\n", sc->sc_dev.dv_xname);
29135f382c9Skevlo 		sc->sc_type = UCHCOM_TYPE_CH343;
29235f382c9Skevlo 	} else if (sc->sc_release == UCHCOM_REV_CH340)
293fc5c6ac5Sderaadt 		printf("%s: CH340\n", sc->sc_dev.dv_xname);
29435f382c9Skevlo 	else
295fc5c6ac5Sderaadt 		printf("%s: CH341\n", sc->sc_dev.dv_xname);
296ad942b15Sjsg 
297aaf33dadSjsg 	if (uchcom_find_endpoints(sc, &endpoints))
298ad942b15Sjsg 		goto failed;
299ad942b15Sjsg 
300ad942b15Sjsg 	sc->sc_intr_endpoint = endpoints.ep_intr;
301234dfda1Sderaadt 	sc->sc_isize = endpoints.ep_intr_size;
302ad942b15Sjsg 
303ad942b15Sjsg 	/* setup ucom layer */
304ad942b15Sjsg 	uca.portno = UCOM_UNK_PORTNO;
305ad942b15Sjsg 	uca.bulkin = endpoints.ep_bulkin;
306ad942b15Sjsg 	uca.bulkout = endpoints.ep_bulkout;
3077f0d0f7fSuaa 	uca.ibufsize = endpoints.ep_bulkin_size;
308ad942b15Sjsg 	uca.obufsize = UCHCOMOBUFSIZE;
3097f0d0f7fSuaa 	uca.ibufsizepad = endpoints.ep_bulkin_size;
310ad942b15Sjsg 	uca.opkthdrlen = 0;
31135f382c9Skevlo 	uca.device = sc->sc_udev;
31235f382c9Skevlo 	uca.iface = sc->sc_data_iface;
313ad942b15Sjsg 	uca.methods = &uchcom_methods;
314ad942b15Sjsg 	uca.arg = sc;
315ad942b15Sjsg 	uca.info = NULL;
316ad942b15Sjsg 
317ad942b15Sjsg 	sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch);
318ad942b15Sjsg 
319ad942b15Sjsg 	return;
320ad942b15Sjsg 
321ad942b15Sjsg failed:
322cbb8e2d4Spirofti 	usbd_deactivate(sc->sc_udev);
323ad942b15Sjsg }
324ad942b15Sjsg 
325ad942b15Sjsg int
326ad942b15Sjsg uchcom_detach(struct device *self, int flags)
327ad942b15Sjsg {
328ad942b15Sjsg 	struct uchcom_softc *sc = (struct uchcom_softc *)self;
329ad942b15Sjsg 	int rv = 0;
330ad942b15Sjsg 
331ad942b15Sjsg 	DPRINTF(("uchcom_detach: sc=%p flags=%d\n", sc, flags));
332ad942b15Sjsg 
333aaf33dadSjsg 	uchcom_close_intr_pipe(sc);
334ad942b15Sjsg 
335ad942b15Sjsg 	if (sc->sc_subdev != NULL) {
336ad942b15Sjsg 		rv = config_detach(sc->sc_subdev, flags);
337ad942b15Sjsg 		sc->sc_subdev = NULL;
338ad942b15Sjsg 	}
339ad942b15Sjsg 
340ad942b15Sjsg 	return rv;
341ad942b15Sjsg }
342ad942b15Sjsg 
343ad942b15Sjsg int
344aaf33dadSjsg uchcom_find_endpoints(struct uchcom_softc *sc,
345aaf33dadSjsg     struct uchcom_endpoints *endpoints)
346ad942b15Sjsg {
3477f0d0f7fSuaa 	int i, bin=-1, bout=-1, intr=-1, binsize=0, isize=0;
34835f382c9Skevlo 	usb_config_descriptor_t *cdesc;
349ad942b15Sjsg 	usb_interface_descriptor_t *id;
350ad942b15Sjsg 	usb_endpoint_descriptor_t *ed;
35135f382c9Skevlo 	usbd_status err;
35235f382c9Skevlo 	uint8_t ifaceno;
353ad942b15Sjsg 
35435f382c9Skevlo 	/* Get the config descriptor. */
35535f382c9Skevlo 	cdesc = usbd_get_config_descriptor(sc->sc_udev);
35635f382c9Skevlo 
35735f382c9Skevlo 	if (cdesc == NULL) {
35835f382c9Skevlo 		printf("%s: failed to get configuration descriptor\n",
35935f382c9Skevlo 		    sc->sc_dev.dv_xname);
36035f382c9Skevlo 		return -1;
36135f382c9Skevlo         }
36235f382c9Skevlo 
36335f382c9Skevlo 	id = usbd_get_interface_descriptor(sc->sc_intr_iface);
364ad942b15Sjsg 
365ad942b15Sjsg 	for (i = 0; i < id->bNumEndpoints; i++) {
36635f382c9Skevlo 		ed = usbd_interface2endpoint_descriptor(sc->sc_intr_iface, i);
367ad942b15Sjsg 		if (ed == NULL) {
368ad942b15Sjsg 			printf("%s: no endpoint descriptor for %d\n",
369ad942b15Sjsg 				sc->sc_dev.dv_xname, i);
370ad942b15Sjsg 			return -1;
371ad942b15Sjsg 		}
372ad942b15Sjsg 
373ad942b15Sjsg 		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
374ad942b15Sjsg 		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) {
375ad942b15Sjsg 			intr = ed->bEndpointAddress;
376ad942b15Sjsg 			isize = UGETW(ed->wMaxPacketSize);
37735f382c9Skevlo 		}
37835f382c9Skevlo 	}
37935f382c9Skevlo 
38035f382c9Skevlo 	ifaceno = (cdesc->bNumInterfaces == 2) ?
38135f382c9Skevlo 	    UCHCOM_SECOND_IFACE_INDEX : UCHCOM_IFACE_INDEX;
38235f382c9Skevlo 
38335f382c9Skevlo 	err = usbd_device2interface_handle(sc->sc_udev, ifaceno,
38435f382c9Skevlo 	    &sc->sc_data_iface);
38535f382c9Skevlo 	if (err) {
38635f382c9Skevlo 		printf("\n%s: failed to get second interface, err=%s\n",
38735f382c9Skevlo 		    sc->sc_dev.dv_xname, usbd_errstr(err));
38835f382c9Skevlo 		return -1;
38935f382c9Skevlo 	}
39035f382c9Skevlo 
39135f382c9Skevlo 	id = usbd_get_interface_descriptor(sc->sc_data_iface);
39235f382c9Skevlo 
39335f382c9Skevlo 	for (i = 0; i < id->bNumEndpoints; i++) {
39435f382c9Skevlo 		ed = usbd_interface2endpoint_descriptor(sc->sc_data_iface, i);
39535f382c9Skevlo 		if (ed == NULL) {
39635f382c9Skevlo 			printf("%s: no endpoint descriptor for %d\n",
39735f382c9Skevlo 				sc->sc_dev.dv_xname, i);
39835f382c9Skevlo 			return -1;
39935f382c9Skevlo 		}
40035f382c9Skevlo 
40135f382c9Skevlo 		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
402ad942b15Sjsg 		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
403ad942b15Sjsg 			bin = ed->bEndpointAddress;
4047f0d0f7fSuaa 			binsize = UGETW(ed->wMaxPacketSize);
405ad942b15Sjsg 		} else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
406ad942b15Sjsg 		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
407ad942b15Sjsg 			bout = ed->bEndpointAddress;
408ad942b15Sjsg 		}
409ad942b15Sjsg 	}
410ad942b15Sjsg 
411ad942b15Sjsg 	if (intr == -1 || bin == -1 || bout == -1) {
412ad942b15Sjsg 		if (intr == -1) {
413ad942b15Sjsg 			printf("%s: no interrupt end point\n",
414ad942b15Sjsg 			       sc->sc_dev.dv_xname);
415ad942b15Sjsg 		}
416ad942b15Sjsg 		if (bin == -1) {
417ad942b15Sjsg 			printf("%s: no data bulk in end point\n",
418ad942b15Sjsg 			       sc->sc_dev.dv_xname);
419ad942b15Sjsg 		}
420ad942b15Sjsg 		if (bout == -1) {
421ad942b15Sjsg 			printf("%s: no data bulk out end point\n",
422ad942b15Sjsg 			       sc->sc_dev.dv_xname);
423ad942b15Sjsg 		}
424ad942b15Sjsg 		return -1;
425ad942b15Sjsg 	}
426ad942b15Sjsg 	if (isize < UCHCOM_INTR_LEAST) {
427ad942b15Sjsg 		printf("%s: intr pipe is too short", sc->sc_dev.dv_xname);
428ad942b15Sjsg 		return -1;
429ad942b15Sjsg 	}
430ad942b15Sjsg 
431ad942b15Sjsg 	DPRINTF(("%s: bulkin=%d, bulkout=%d, intr=%d, isize=%d\n",
432ad942b15Sjsg 		 sc->sc_dev.dv_xname, bin, bout, intr, isize));
433ad942b15Sjsg 
43435f382c9Skevlo 	usbd_claim_iface(sc->sc_udev, ifaceno);
43535f382c9Skevlo 
436ad942b15Sjsg 	endpoints->ep_intr = intr;
437ad942b15Sjsg 	endpoints->ep_intr_size = isize;
438ad942b15Sjsg 	endpoints->ep_bulkin = bin;
4397f0d0f7fSuaa 	endpoints->ep_bulkin_size = binsize;
440ad942b15Sjsg 	endpoints->ep_bulkout = bout;
441ad942b15Sjsg 
442ad942b15Sjsg 	return 0;
443ad942b15Sjsg }
444ad942b15Sjsg 
445ad942b15Sjsg 
446ad942b15Sjsg /* ----------------------------------------------------------------------
447ad942b15Sjsg  * low level i/o
448ad942b15Sjsg  */
449ad942b15Sjsg 
450ad942b15Sjsg usbd_status
451aaf33dadSjsg uchcom_generic_control_out(struct uchcom_softc *sc, uint8_t reqno,
452ad942b15Sjsg     uint16_t value, uint16_t index)
453ad942b15Sjsg {
454ad942b15Sjsg 	usb_device_request_t req;
455ad942b15Sjsg 
456ad942b15Sjsg 	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
457ad942b15Sjsg 	req.bRequest = reqno;
458ad942b15Sjsg 	USETW(req.wValue, value);
459ad942b15Sjsg 	USETW(req.wIndex, index);
460ad942b15Sjsg 	USETW(req.wLength, 0);
461ad942b15Sjsg 
462ad942b15Sjsg 	return usbd_do_request(sc->sc_udev, &req, 0);
463ad942b15Sjsg }
464ad942b15Sjsg 
465ad942b15Sjsg usbd_status
466aaf33dadSjsg uchcom_generic_control_in(struct uchcom_softc *sc, uint8_t reqno,
467ad942b15Sjsg     uint16_t value, uint16_t index, void *buf, int buflen, int *actlen)
468ad942b15Sjsg {
469ad942b15Sjsg 	usb_device_request_t req;
470ad942b15Sjsg 
471ad942b15Sjsg 	req.bmRequestType = UT_READ_VENDOR_DEVICE;
472ad942b15Sjsg 	req.bRequest = reqno;
473ad942b15Sjsg 	USETW(req.wValue, value);
474ad942b15Sjsg 	USETW(req.wIndex, index);
475ad942b15Sjsg 	USETW(req.wLength, (uint16_t)buflen);
476ad942b15Sjsg 
477ad942b15Sjsg 	return usbd_do_request_flags(sc->sc_udev, &req, buf,
478ad942b15Sjsg 				     USBD_SHORT_XFER_OK, actlen,
479ad942b15Sjsg 				     USBD_DEFAULT_TIMEOUT);
480ad942b15Sjsg }
481ad942b15Sjsg 
482ad942b15Sjsg usbd_status
483aaf33dadSjsg uchcom_write_reg(struct uchcom_softc *sc,
484ad942b15Sjsg     uint8_t reg1, uint8_t val1, uint8_t reg2, uint8_t val2)
485ad942b15Sjsg {
486ad942b15Sjsg 	DPRINTF(("uchcom: write reg 0x%02X<-0x%02X, 0x%02X<-0x%02X\n",
487ad942b15Sjsg 		 (unsigned)reg1, (unsigned)val1,
488ad942b15Sjsg 		 (unsigned)reg2, (unsigned)val2));
48935f382c9Skevlo 	return uchcom_generic_control_out(sc,
49035f382c9Skevlo 	    	(sc->sc_type != UCHCOM_TYPE_CH343) ?
49135f382c9Skevlo 		UCHCOM_REQ_WRITE_REG : UCHCOM_REQ_CH343_WRITE_REG,
492ad942b15Sjsg 		reg1|((uint16_t)reg2<<8), val1|((uint16_t)val2<<8));
493ad942b15Sjsg }
494ad942b15Sjsg 
495ad942b15Sjsg usbd_status
496aaf33dadSjsg uchcom_read_reg(struct uchcom_softc *sc,
497ad942b15Sjsg     uint8_t reg1, uint8_t *rval1, uint8_t reg2, uint8_t *rval2)
498ad942b15Sjsg {
499ad942b15Sjsg 	uint8_t buf[UCHCOM_INPUT_BUF_SIZE];
500ad942b15Sjsg 	usbd_status err;
501ad942b15Sjsg 	int actin;
502ad942b15Sjsg 
503aaf33dadSjsg 	err = uchcom_generic_control_in(
504ad942b15Sjsg 		sc, UCHCOM_REQ_READ_REG,
505ad942b15Sjsg 		reg1|((uint16_t)reg2<<8), 0, buf, sizeof buf, &actin);
506ad942b15Sjsg 	if (err)
507ad942b15Sjsg 		return err;
508ad942b15Sjsg 
509ad942b15Sjsg 	DPRINTF(("uchcom: read reg 0x%02X->0x%02X, 0x%02X->0x%02X\n",
510ad942b15Sjsg 		 (unsigned)reg1, (unsigned)buf[0],
511ad942b15Sjsg 		 (unsigned)reg2, (unsigned)buf[1]));
512ad942b15Sjsg 
513ad942b15Sjsg 	if (rval1) *rval1 = buf[0];
514ad942b15Sjsg 	if (rval2) *rval2 = buf[1];
515ad942b15Sjsg 
516ad942b15Sjsg 	return USBD_NORMAL_COMPLETION;
517ad942b15Sjsg }
518ad942b15Sjsg 
519ad942b15Sjsg usbd_status
520aaf33dadSjsg uchcom_get_version(struct uchcom_softc *sc, uint8_t *rver)
521ad942b15Sjsg {
522ad942b15Sjsg 	uint8_t buf[UCHCOM_INPUT_BUF_SIZE];
523ad942b15Sjsg 	usbd_status err;
524ad942b15Sjsg 	int actin;
525ad942b15Sjsg 
526aaf33dadSjsg 	err = uchcom_generic_control_in(
527ad942b15Sjsg 		sc, UCHCOM_REQ_GET_VERSION, 0, 0, buf, sizeof buf, &actin);
528ad942b15Sjsg 	if (err)
529ad942b15Sjsg 		return err;
530ad942b15Sjsg 
531ad942b15Sjsg 	if (rver) *rver = buf[0];
532ad942b15Sjsg 
533ad942b15Sjsg 	return USBD_NORMAL_COMPLETION;
534ad942b15Sjsg }
535ad942b15Sjsg 
536ad942b15Sjsg usbd_status
537aaf33dadSjsg uchcom_read_status(struct uchcom_softc *sc, uint8_t *rval)
538ad942b15Sjsg {
539aaf33dadSjsg 	return uchcom_read_reg(sc, UCHCOM_REG_STAT1, rval, UCHCOM_REG_STAT2,
540aaf33dadSjsg 	    NULL);
541ad942b15Sjsg }
542ad942b15Sjsg 
543ad942b15Sjsg usbd_status
544aaf33dadSjsg uchcom_set_dtrrts_10(struct uchcom_softc *sc, uint8_t val)
545ad942b15Sjsg {
546aaf33dadSjsg 	return uchcom_write_reg(sc, UCHCOM_REG_STAT1, val, UCHCOM_REG_STAT1,
547aaf33dadSjsg 	    val);
548ad942b15Sjsg }
549ad942b15Sjsg 
550ad942b15Sjsg usbd_status
551aaf33dadSjsg uchcom_set_dtrrts_20(struct uchcom_softc *sc, uint8_t val)
552ad942b15Sjsg {
553aaf33dadSjsg 	return uchcom_generic_control_out(sc, UCHCOM_REQ_SET_DTRRTS, val, 0);
554ad942b15Sjsg }
555ad942b15Sjsg 
556ad942b15Sjsg 
557ad942b15Sjsg /* ----------------------------------------------------------------------
558ad942b15Sjsg  * middle layer
559ad942b15Sjsg  */
560ad942b15Sjsg 
561ad942b15Sjsg int
562aaf33dadSjsg uchcom_update_version(struct uchcom_softc *sc)
563ad942b15Sjsg {
564ad942b15Sjsg 	usbd_status err;
565ad942b15Sjsg 
566aaf33dadSjsg 	err = uchcom_get_version(sc, &sc->sc_version);
567ad942b15Sjsg 	if (err) {
568ad942b15Sjsg 		printf("%s: cannot get version: %s\n",
569ad942b15Sjsg 		       sc->sc_dev.dv_xname, usbd_errstr(err));
570ad942b15Sjsg 		return EIO;
571ad942b15Sjsg 	}
572ad942b15Sjsg 
573ad942b15Sjsg 	return 0;
574ad942b15Sjsg }
575ad942b15Sjsg 
576ad942b15Sjsg void
577aaf33dadSjsg uchcom_convert_status(struct uchcom_softc *sc, uint8_t cur)
578ad942b15Sjsg {
579ad942b15Sjsg 	sc->sc_dtr = !(cur & UCHCOM_DTR_MASK);
580ad942b15Sjsg 	sc->sc_rts = !(cur & UCHCOM_RTS_MASK);
581ad942b15Sjsg 
582ad942b15Sjsg 	cur = ~cur & 0x0F;
583ad942b15Sjsg 	sc->sc_msr = (cur << 4) | ((sc->sc_msr >> 4) ^ cur);
584ad942b15Sjsg }
585ad942b15Sjsg 
586ad942b15Sjsg int
587aaf33dadSjsg uchcom_update_status(struct uchcom_softc *sc)
588ad942b15Sjsg {
589ad942b15Sjsg 	usbd_status err;
590ad942b15Sjsg 	uint8_t cur;
591ad942b15Sjsg 
592aaf33dadSjsg 	err = uchcom_read_status(sc, &cur);
593ad942b15Sjsg 	if (err) {
594ad942b15Sjsg 		printf("%s: cannot update status: %s\n",
595ad942b15Sjsg 		       sc->sc_dev.dv_xname, usbd_errstr(err));
596ad942b15Sjsg 		return EIO;
597ad942b15Sjsg 	}
598aaf33dadSjsg 	uchcom_convert_status(sc, cur);
599ad942b15Sjsg 
600ad942b15Sjsg 	return 0;
601ad942b15Sjsg }
602ad942b15Sjsg 
603ad942b15Sjsg 
604ad942b15Sjsg int
605aaf33dadSjsg uchcom_set_dtrrts(struct uchcom_softc *sc, int dtr, int rts)
606ad942b15Sjsg {
607ad942b15Sjsg 	usbd_status err;
608ad942b15Sjsg 	uint8_t val = 0;
609ad942b15Sjsg 
610ad942b15Sjsg 	if (dtr) val |= UCHCOM_DTR_MASK;
611ad942b15Sjsg 	if (rts) val |= UCHCOM_RTS_MASK;
612ad942b15Sjsg 
613ad942b15Sjsg 	if (sc->sc_version < UCHCOM_VER_20)
614aaf33dadSjsg 		err = uchcom_set_dtrrts_10(sc, ~val);
615ad942b15Sjsg 	else
616aaf33dadSjsg 		err = uchcom_set_dtrrts_20(sc, ~val);
617ad942b15Sjsg 
618ad942b15Sjsg 	if (err) {
619ad942b15Sjsg 		printf("%s: cannot set DTR/RTS: %s\n",
620ad942b15Sjsg 		       sc->sc_dev.dv_xname, usbd_errstr(err));
621ad942b15Sjsg 		return EIO;
622ad942b15Sjsg 	}
623ad942b15Sjsg 
624ad942b15Sjsg 	return 0;
625ad942b15Sjsg }
626ad942b15Sjsg 
627ad942b15Sjsg int
628aaf33dadSjsg uchcom_set_break(struct uchcom_softc *sc, int onoff)
629ad942b15Sjsg {
630ad942b15Sjsg 	usbd_status err;
6310b89560cSdlg 	uint8_t brk, lcr;
632ad942b15Sjsg 
6330b89560cSdlg 	err = uchcom_read_reg(sc, UCHCOM_REG_BREAK, &brk, UCHCOM_REG_LCR, &lcr);
634ad942b15Sjsg 	if (err)
635ad942b15Sjsg 		return EIO;
636ad942b15Sjsg 	if (onoff) {
637ad942b15Sjsg 		/* on - clear bits */
6380b89560cSdlg 		brk &= ~UCHCOM_BREAK_MASK;
6390b89560cSdlg 		lcr &= ~UCHCOM_LCR_TXE;
640ad942b15Sjsg 	} else {
641ad942b15Sjsg 		/* off - set bits */
6420b89560cSdlg 		brk |= UCHCOM_BREAK_MASK;
6430b89560cSdlg 		lcr |= UCHCOM_LCR_TXE;
644ad942b15Sjsg 	}
6450b89560cSdlg 	err = uchcom_write_reg(sc, UCHCOM_REG_BREAK, brk, UCHCOM_REG_LCR, lcr);
646ad942b15Sjsg 	if (err)
647ad942b15Sjsg 		return EIO;
648ad942b15Sjsg 
649ad942b15Sjsg 	return 0;
650ad942b15Sjsg }
651ad942b15Sjsg 
652ad942b15Sjsg int
65335f382c9Skevlo uchcom_set_break_ch343(struct uchcom_softc *sc, int onoff)
65435f382c9Skevlo {
65535f382c9Skevlo 	usbd_status err;
65635f382c9Skevlo 	uint8_t brk = UCHCOM_CH343_BREAK_MASK;
65735f382c9Skevlo 
65835f382c9Skevlo 	if (!onoff)
65935f382c9Skevlo 		brk |= UCHCOM_ABREAK_MASK;
66035f382c9Skevlo 
66135f382c9Skevlo 	err = uchcom_write_reg(sc, brk, 0, 0, 0);
66235f382c9Skevlo 	if (err)
66335f382c9Skevlo 		return EIO;
66435f382c9Skevlo 
66535f382c9Skevlo 	return 0;
66635f382c9Skevlo }
66735f382c9Skevlo 
66835f382c9Skevlo void
66935f382c9Skevlo uchcom_calc_baudrate_ch343(uint32_t rate, uint8_t *divisor, uint8_t *factor)
67035f382c9Skevlo {
67135f382c9Skevlo 	uint32_t clk = 12000000;
67235f382c9Skevlo 
67335f382c9Skevlo 	if (rate >= 256000)
67435f382c9Skevlo 		*divisor = 7;
67535f382c9Skevlo 	else if (rate > 23529) {
67635f382c9Skevlo 		clk /= 2;
67735f382c9Skevlo 		*divisor = 3;
67835f382c9Skevlo 	} else if (rate > 2941) {
67935f382c9Skevlo 		clk /=  16;
68035f382c9Skevlo 		*divisor = 2;
68135f382c9Skevlo 	} else if (rate > 367) {
68235f382c9Skevlo 		clk /= 128;
68335f382c9Skevlo 		*divisor = 1;
68435f382c9Skevlo 	} else {
68535f382c9Skevlo 		clk = 11719;
68635f382c9Skevlo 		*divisor = 0;
68735f382c9Skevlo 	}
68835f382c9Skevlo 
68935f382c9Skevlo 	*factor = 256 - clk / rate;
69035f382c9Skevlo }
69135f382c9Skevlo 
69235f382c9Skevlo int
693aaf33dadSjsg uchcom_calc_divider_settings(struct uchcom_divider *dp, uint32_t rate)
694ad942b15Sjsg {
695ad942b15Sjsg 	int i;
696ad942b15Sjsg 	const struct uchcom_divider_record *rp;
697ad942b15Sjsg 	uint32_t div, rem, mod;
698ad942b15Sjsg 
699ad942b15Sjsg 	/* find record */
7007d128c60Sjasper 	for (i=0; i<nitems(dividers); i++) {
701ad942b15Sjsg 		if (dividers[i].dvr_high >= rate &&
702ad942b15Sjsg 		    dividers[i].dvr_low <= rate) {
703ad942b15Sjsg 			rp = &dividers[i];
704ad942b15Sjsg 			goto found;
705ad942b15Sjsg 		}
706ad942b15Sjsg 	}
707ad942b15Sjsg 	return -1;
708ad942b15Sjsg 
709ad942b15Sjsg found:
710ad942b15Sjsg 	dp->dv_prescaler = rp->dvr_divider.dv_prescaler;
711ad942b15Sjsg 	if (rp->dvr_base_clock == UCHCOM_BASE_UNKNOWN)
712ad942b15Sjsg 		dp->dv_div = rp->dvr_divider.dv_div;
713ad942b15Sjsg 	else {
714ad942b15Sjsg 		div = rp->dvr_base_clock / rate;
715ad942b15Sjsg 		rem = rp->dvr_base_clock % rate;
716ad942b15Sjsg 		if (div==0 || div>=0xFF)
717ad942b15Sjsg 			return -1;
718ad942b15Sjsg 		if ((rem<<1) >= rate)
719ad942b15Sjsg 			div += 1;
720ad942b15Sjsg 		dp->dv_div = (uint8_t)-div;
721ad942b15Sjsg 	}
722ad942b15Sjsg 
723ad942b15Sjsg 	mod = UCHCOM_BPS_MOD_BASE/rate + UCHCOM_BPS_MOD_BASE_OFS;
724ad942b15Sjsg 	mod = mod + mod/2;
725ad942b15Sjsg 
726ad942b15Sjsg 	dp->dv_mod = mod / 0x100;
727ad942b15Sjsg 
728ad942b15Sjsg 	return 0;
729ad942b15Sjsg }
730ad942b15Sjsg 
731ad942b15Sjsg int
73235f382c9Skevlo uchcom_set_dte_rate_ch343(struct uchcom_softc *sc, uint32_t rate, uint16_t val)
73335f382c9Skevlo {
73435f382c9Skevlo 	usbd_status err;
73535f382c9Skevlo 	uint16_t idx;
73635f382c9Skevlo 	uint8_t factor, div;
73735f382c9Skevlo 
73835f382c9Skevlo 	uchcom_calc_baudrate_ch343(rate, &div, &factor);
73935f382c9Skevlo 	idx = (factor << 8) | div;
74035f382c9Skevlo 
74135f382c9Skevlo 	err = uchcom_generic_control_out(sc, UCHCOM_REQ_SET_BAUDRATE, val, idx);
74235f382c9Skevlo 	if (err) {
74335f382c9Skevlo 		printf("%s: cannot set DTE rate: %s\n",
74435f382c9Skevlo 		    sc->sc_dev.dv_xname, usbd_errstr(err));
74535f382c9Skevlo 		return EIO;
74635f382c9Skevlo 	}
74735f382c9Skevlo 
74835f382c9Skevlo 	return 0;
74935f382c9Skevlo }
75035f382c9Skevlo 
75135f382c9Skevlo int
752aaf33dadSjsg uchcom_set_dte_rate(struct uchcom_softc *sc, uint32_t rate)
753ad942b15Sjsg {
754ad942b15Sjsg 	usbd_status err;
755ad942b15Sjsg 	struct uchcom_divider dv;
756ad942b15Sjsg 
757aaf33dadSjsg 	if (uchcom_calc_divider_settings(&dv, rate))
758ad942b15Sjsg 		return EINVAL;
759ad942b15Sjsg 
760aaf33dadSjsg 	if ((err = uchcom_write_reg(sc,
761ad942b15Sjsg 			     UCHCOM_REG_BPS_PRE, dv.dv_prescaler,
762ad942b15Sjsg 			     UCHCOM_REG_BPS_DIV, dv.dv_div)) ||
763aaf33dadSjsg 	    (err = uchcom_write_reg(sc,
764ad942b15Sjsg 			     UCHCOM_REG_BPS_MOD, dv.dv_mod,
765ad942b15Sjsg 			     UCHCOM_REG_BPS_PAD, 0))) {
766ad942b15Sjsg 		printf("%s: cannot set DTE rate: %s\n",
767ad942b15Sjsg 		       sc->sc_dev.dv_xname, usbd_errstr(err));
768ad942b15Sjsg 		return EIO;
769ad942b15Sjsg 	}
770ad942b15Sjsg 
771ad942b15Sjsg 	return 0;
772ad942b15Sjsg }
773ad942b15Sjsg 
77435f382c9Skevlo uint16_t
77535f382c9Skevlo uchcom_set_line_control(struct uchcom_softc *sc, tcflag_t cflag, uint16_t *val)
776ad942b15Sjsg {
7770b89560cSdlg 	usbd_status err;
7780b89560cSdlg 	uint8_t lcr = 0, lcr2 = 0;
7790b89560cSdlg 
780e219843cSdlg 	if (sc->sc_release == UCHCOM_REV_CH340) {
781e219843cSdlg 		/*
782e219843cSdlg 		 * XXX: it is difficult to handle the line control
783e219843cSdlg 		 * appropriately on CH340:
784e219843cSdlg 		 *   work as chip default - CS8, no parity, !CSTOPB
785e219843cSdlg 		 *   other modes are not supported.
786e219843cSdlg 		 */
787e219843cSdlg 		switch (ISSET(cflag, CSIZE)) {
788e219843cSdlg 		case CS5:
789e219843cSdlg 		case CS6:
790e219843cSdlg 		case CS7:
791e219843cSdlg 			return EINVAL;
792e219843cSdlg 		case CS8:
793e219843cSdlg 			break;
794e219843cSdlg 		}
795e219843cSdlg 		if (ISSET(cflag, PARENB) || ISSET(cflag, CSTOPB))
796e219843cSdlg 			return EINVAL;
797e219843cSdlg 		return 0;
798e219843cSdlg 	}
799e219843cSdlg 
80035f382c9Skevlo 	if (sc->sc_type != UCHCOM_TYPE_CH343) {
80135f382c9Skevlo 		err = uchcom_read_reg(sc, UCHCOM_REG_LCR, &lcr, UCHCOM_REG_LCR2,
80235f382c9Skevlo 		    &lcr2);
8030b89560cSdlg 		if (err) {
8040b89560cSdlg 			printf("%s: cannot get LCR: %s\n",
8050b89560cSdlg 			    sc->sc_dev.dv_xname, usbd_errstr(err));
8060b89560cSdlg 			return EIO;
8070b89560cSdlg 		}
80835f382c9Skevlo 	}
8090b89560cSdlg 
8100b89560cSdlg 	lcr = UCHCOM_LCR_RXE | UCHCOM_LCR_TXE;
811ad942b15Sjsg 
812ad942b15Sjsg 	switch (ISSET(cflag, CSIZE)) {
813ad942b15Sjsg 	case CS5:
8140b89560cSdlg 		lcr |= UCHCOM_LCR_CS5;
8150b89560cSdlg 		break;
816ad942b15Sjsg 	case CS6:
8170b89560cSdlg 		lcr |= UCHCOM_LCR_CS6;
8180b89560cSdlg 		break;
819ad942b15Sjsg 	case CS7:
8200b89560cSdlg 		lcr |= UCHCOM_LCR_CS7;
8210b89560cSdlg 		break;
822ad942b15Sjsg 	case CS8:
8230b89560cSdlg 		lcr |= UCHCOM_LCR_CS8;
824ad942b15Sjsg 		break;
825ad942b15Sjsg 	}
826ad942b15Sjsg 
8270b89560cSdlg 	if (ISSET(cflag, PARENB)) {
8280b89560cSdlg 		lcr |= UCHCOM_LCR_PARENB;
8290b89560cSdlg 		if (!ISSET(cflag, PARODD))
8300b89560cSdlg 			lcr |= UCHCOM_LCR_PAREVEN;
8310b89560cSdlg 	}
8320b89560cSdlg 
8330b89560cSdlg 	if (ISSET(cflag, CSTOPB)) {
8340b89560cSdlg 		lcr |= UCHCOM_LCR_STOPB;
8350b89560cSdlg 	}
8360b89560cSdlg 
83735f382c9Skevlo 	if (sc->sc_type != UCHCOM_TYPE_CH343) {
83835f382c9Skevlo 		err = uchcom_write_reg(sc, UCHCOM_REG_LCR, lcr, UCHCOM_REG_LCR2,
83935f382c9Skevlo 		    lcr2);
8400b89560cSdlg 		if (err) {
8410b89560cSdlg 			printf("%s: cannot set LCR: %s\n",
8420b89560cSdlg 			    sc->sc_dev.dv_xname, usbd_errstr(err));
8430b89560cSdlg 			return EIO;
8440b89560cSdlg 		}
84535f382c9Skevlo 	} else
84635f382c9Skevlo 		*val = UCHCOM_T | UCHCOM_CL | UCHCOM_CT | lcr << 8;
847ad942b15Sjsg 
848ad942b15Sjsg 	return 0;
849ad942b15Sjsg }
850ad942b15Sjsg 
851ad942b15Sjsg int
852aaf33dadSjsg uchcom_clear_chip(struct uchcom_softc *sc)
853ad942b15Sjsg {
854ad942b15Sjsg 	usbd_status err;
855ad942b15Sjsg 
856ad942b15Sjsg 	DPRINTF(("%s: clear\n", sc->sc_dev.dv_xname));
857aaf33dadSjsg 	err = uchcom_generic_control_out(sc, UCHCOM_REQ_RESET, 0, 0);
858ad942b15Sjsg 	if (err) {
859ad942b15Sjsg 		printf("%s: cannot clear: %s\n",
860ad942b15Sjsg 		       sc->sc_dev.dv_xname, usbd_errstr(err));
861ad942b15Sjsg 		return EIO;
862ad942b15Sjsg 	}
863ad942b15Sjsg 
864ad942b15Sjsg 	return 0;
865ad942b15Sjsg }
866ad942b15Sjsg 
867ad942b15Sjsg int
868aaf33dadSjsg uchcom_reset_chip(struct uchcom_softc *sc)
869ad942b15Sjsg {
870ad942b15Sjsg 	usbd_status err;
871ad942b15Sjsg 
8725165e579Ssasano 	DPRINTF(("%s: reset\n", sc->sc_dev.dv_xname));
873ad942b15Sjsg 
8745165e579Ssasano 	err = uchcom_generic_control_out(sc, UCHCOM_REQ_RESET,
8755165e579Ssasano 					 UCHCOM_RESET_VALUE,
8765165e579Ssasano 					 UCHCOM_RESET_INDEX);
877ad942b15Sjsg 	if (err)
878ad942b15Sjsg 		goto failed;
879ad942b15Sjsg 
880ad942b15Sjsg 	return 0;
881ad942b15Sjsg 
882ad942b15Sjsg failed:
883ad942b15Sjsg 	printf("%s: cannot reset: %s\n",
884ad942b15Sjsg 	       sc->sc_dev.dv_xname, usbd_errstr(err));
885ad942b15Sjsg 	return EIO;
886ad942b15Sjsg }
887ad942b15Sjsg 
888ad942b15Sjsg int
889aaf33dadSjsg uchcom_setup_comm(struct uchcom_softc *sc)
890ad942b15Sjsg {
891ad942b15Sjsg 	int ret;
892ad942b15Sjsg 
893aaf33dadSjsg 	ret = uchcom_clear_chip(sc);
894ad942b15Sjsg 	if (ret)
895ad942b15Sjsg 		return ret;
896ad942b15Sjsg 
897aaf33dadSjsg 	ret = uchcom_set_dte_rate(sc, TTYDEF_SPEED);
898ad942b15Sjsg 	if (ret)
899ad942b15Sjsg 		return ret;
900ad942b15Sjsg 
90135f382c9Skevlo 	ret = uchcom_set_line_control(sc, CS8, 0);
902ad942b15Sjsg 	if (ret)
903ad942b15Sjsg 		return ret;
904ad942b15Sjsg 
905aaf33dadSjsg 	ret = uchcom_update_status(sc);
906ad942b15Sjsg 	if (ret)
907ad942b15Sjsg 		return ret;
908ad942b15Sjsg 
909aaf33dadSjsg 	ret = uchcom_reset_chip(sc);
910ad942b15Sjsg 	if (ret)
911ad942b15Sjsg 		return ret;
912ad942b15Sjsg 
913aaf33dadSjsg 	ret = uchcom_set_dte_rate(sc, TTYDEF_SPEED); /* XXX */
914ad942b15Sjsg 	if (ret)
915ad942b15Sjsg 		return ret;
916ad942b15Sjsg 
917ad942b15Sjsg 	return 0;
918ad942b15Sjsg }
919ad942b15Sjsg 
920ad942b15Sjsg int
921aaf33dadSjsg uchcom_setup_intr_pipe(struct uchcom_softc *sc)
922ad942b15Sjsg {
923ad942b15Sjsg 	usbd_status err;
924ad942b15Sjsg 
925ad942b15Sjsg 	if (sc->sc_intr_endpoint != -1 && sc->sc_intr_pipe == NULL) {
926234dfda1Sderaadt 		sc->sc_intr_buf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK);
92735f382c9Skevlo 		err = usbd_open_pipe_intr(sc->sc_intr_iface,
928ad942b15Sjsg 					  sc->sc_intr_endpoint,
929ad942b15Sjsg 					  USBD_SHORT_XFER_OK,
930ad942b15Sjsg 					  &sc->sc_intr_pipe, sc,
931ad942b15Sjsg 					  sc->sc_intr_buf,
932234dfda1Sderaadt 					  sc->sc_isize,
933ad942b15Sjsg 					  uchcom_intr, USBD_DEFAULT_INTERVAL);
934ad942b15Sjsg 		if (err) {
935ad942b15Sjsg 			printf("%s: cannot open interrupt pipe: %s\n",
936ad942b15Sjsg 			       sc->sc_dev.dv_xname,
937ad942b15Sjsg 			       usbd_errstr(err));
938ad942b15Sjsg 			return EIO;
939ad942b15Sjsg 		}
940ad942b15Sjsg 	}
941ad942b15Sjsg 	return 0;
942ad942b15Sjsg }
943ad942b15Sjsg 
944ad942b15Sjsg void
945aaf33dadSjsg uchcom_close_intr_pipe(struct uchcom_softc *sc)
946ad942b15Sjsg {
947ad942b15Sjsg 	usbd_status err;
948ad942b15Sjsg 
949ad942b15Sjsg 	if (sc->sc_intr_pipe != NULL) {
950ad942b15Sjsg 		err = usbd_close_pipe(sc->sc_intr_pipe);
951ad942b15Sjsg 		if (err)
952ad942b15Sjsg 			printf("%s: close interrupt pipe failed: %s\n",
953ad942b15Sjsg 			       sc->sc_dev.dv_xname, usbd_errstr(err));
954234dfda1Sderaadt 		free(sc->sc_intr_buf, M_USBDEV, sc->sc_isize);
955ad942b15Sjsg 		sc->sc_intr_pipe = NULL;
956ad942b15Sjsg 	}
957ad942b15Sjsg }
958ad942b15Sjsg 
959ad942b15Sjsg 
960ad942b15Sjsg /* ----------------------------------------------------------------------
961ad942b15Sjsg  * methods for ucom
962ad942b15Sjsg  */
963ad942b15Sjsg void
964ad942b15Sjsg uchcom_get_status(void *arg, int portno, u_char *rlsr, u_char *rmsr)
965ad942b15Sjsg {
966ad942b15Sjsg 	struct uchcom_softc *sc = arg;
967ad942b15Sjsg 
968cbb8e2d4Spirofti 	if (usbd_is_dying(sc->sc_udev))
969ad942b15Sjsg 		return;
970ad942b15Sjsg 
971ad942b15Sjsg 	*rlsr = sc->sc_lsr;
972ad942b15Sjsg 	*rmsr = sc->sc_msr;
973ad942b15Sjsg }
974ad942b15Sjsg 
975ad942b15Sjsg void
976ad942b15Sjsg uchcom_set(void *arg, int portno, int reg, int onoff)
977ad942b15Sjsg {
978ad942b15Sjsg 	struct uchcom_softc *sc = arg;
979ad942b15Sjsg 
980cbb8e2d4Spirofti 	if (usbd_is_dying(sc->sc_udev))
981ad942b15Sjsg 		return;
982ad942b15Sjsg 
983ad942b15Sjsg 	switch (reg) {
984ad942b15Sjsg 	case UCOM_SET_DTR:
985ad942b15Sjsg 		sc->sc_dtr = !!onoff;
986aaf33dadSjsg 		uchcom_set_dtrrts(sc, sc->sc_dtr, sc->sc_rts);
987ad942b15Sjsg 		break;
988ad942b15Sjsg 	case UCOM_SET_RTS:
989ad942b15Sjsg 		sc->sc_rts = !!onoff;
990aaf33dadSjsg 		uchcom_set_dtrrts(sc, sc->sc_dtr, sc->sc_rts);
991ad942b15Sjsg 		break;
992ad942b15Sjsg 	case UCOM_SET_BREAK:
99335f382c9Skevlo 		if (sc->sc_type == UCHCOM_TYPE_CH343)
99435f382c9Skevlo 			uchcom_set_break_ch343(sc, onoff);
99535f382c9Skevlo 		else
996aaf33dadSjsg 			uchcom_set_break(sc, onoff);
997ad942b15Sjsg 		break;
998ad942b15Sjsg 	}
999ad942b15Sjsg }
1000ad942b15Sjsg 
1001ad942b15Sjsg int
1002ad942b15Sjsg uchcom_param(void *arg, int portno, struct termios *t)
1003ad942b15Sjsg {
1004ad942b15Sjsg 	struct uchcom_softc *sc = arg;
100535f382c9Skevlo 	uint16_t val = 0;
1006ad942b15Sjsg 	int ret;
1007ad942b15Sjsg 
1008cbb8e2d4Spirofti 	if (usbd_is_dying(sc->sc_udev))
1009ad942b15Sjsg 		return 0;
1010ad942b15Sjsg 
101135f382c9Skevlo 	ret = uchcom_set_line_control(sc, t->c_cflag, &val);
1012ad942b15Sjsg 	if (ret)
1013ad942b15Sjsg 		return ret;
1014ad942b15Sjsg 
101535f382c9Skevlo 	if (sc->sc_type == UCHCOM_TYPE_CH343)
101635f382c9Skevlo 		ret = uchcom_set_dte_rate_ch343(sc, t->c_ospeed, val);
101735f382c9Skevlo 	else
1018aaf33dadSjsg 		ret = uchcom_set_dte_rate(sc, t->c_ospeed);
1019ad942b15Sjsg 	if (ret)
1020ad942b15Sjsg 		return ret;
1021ad942b15Sjsg 
1022ad942b15Sjsg 	return 0;
1023ad942b15Sjsg }
1024ad942b15Sjsg 
1025ad942b15Sjsg int
1026ad942b15Sjsg uchcom_open(void *arg, int portno)
1027ad942b15Sjsg {
1028ad942b15Sjsg 	int ret;
1029ad942b15Sjsg 	struct uchcom_softc *sc = arg;
1030ad942b15Sjsg 
1031cbb8e2d4Spirofti 	if (usbd_is_dying(sc->sc_udev))
1032ad942b15Sjsg 		return EIO;
1033ad942b15Sjsg 
1034aaf33dadSjsg 	ret = uchcom_setup_intr_pipe(sc);
1035ad942b15Sjsg 	if (ret)
1036ad942b15Sjsg 		return ret;
1037ad942b15Sjsg 
103835f382c9Skevlo 	ret = uchcom_update_version(sc);
103935f382c9Skevlo 	if (ret)
104035f382c9Skevlo 		return ret;
104135f382c9Skevlo 
104235f382c9Skevlo 	if (sc->sc_type == UCHCOM_TYPE_CH343)
104335f382c9Skevlo 		ret = uchcom_update_status(sc);
104435f382c9Skevlo 	else
1045aaf33dadSjsg 		ret = uchcom_setup_comm(sc);
1046ad942b15Sjsg 	if (ret)
1047ad942b15Sjsg 		return ret;
1048ad942b15Sjsg 
104935f382c9Skevlo 	sc->sc_dtr = sc->sc_rts = 1;
105035f382c9Skevlo 	ret = uchcom_set_dtrrts(sc, sc->sc_dtr, sc->sc_rts);
105135f382c9Skevlo 	if (ret)
105235f382c9Skevlo 		return ret;
105335f382c9Skevlo 
1054ad942b15Sjsg 	return 0;
1055ad942b15Sjsg }
1056ad942b15Sjsg 
1057ad942b15Sjsg void
1058ad942b15Sjsg uchcom_close(void *arg, int portno)
1059ad942b15Sjsg {
1060ad942b15Sjsg 	struct uchcom_softc *sc = arg;
1061ad942b15Sjsg 
1062aaf33dadSjsg 	uchcom_close_intr_pipe(sc);
1063ad942b15Sjsg }
1064ad942b15Sjsg 
1065ad942b15Sjsg 
1066ad942b15Sjsg /* ----------------------------------------------------------------------
1067ad942b15Sjsg  * callback when the modem status is changed.
1068ad942b15Sjsg  */
1069ad942b15Sjsg void
1070ab0b1be7Smglocker uchcom_intr(struct usbd_xfer *xfer, void *priv, usbd_status status)
1071ad942b15Sjsg {
1072ad942b15Sjsg 	struct uchcom_softc *sc = priv;
1073ad942b15Sjsg 	u_char *buf = sc->sc_intr_buf;
107435f382c9Skevlo 	uint32_t intrstat;
1075ad942b15Sjsg 
1076cbb8e2d4Spirofti 	if (usbd_is_dying(sc->sc_udev))
1077ad942b15Sjsg 		return;
1078ad942b15Sjsg 
1079ad942b15Sjsg 	if (status != USBD_NORMAL_COMPLETION) {
1080ad942b15Sjsg 		if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
1081ad942b15Sjsg 			return;
1082ad942b15Sjsg 
1083ad942b15Sjsg 		DPRINTF(("%s: abnormal status: %s\n",
1084ad942b15Sjsg 			 sc->sc_dev.dv_xname, usbd_errstr(status)));
1085ad942b15Sjsg 		usbd_clear_endpoint_stall_async(sc->sc_intr_pipe);
1086ad942b15Sjsg 		return;
1087ad942b15Sjsg 	}
1088ad942b15Sjsg 	DPRINTF(("%s: intr: 0x%02X 0x%02X 0x%02X 0x%02X "
1089ad942b15Sjsg 		 "0x%02X 0x%02X 0x%02X 0x%02X\n",
1090ad942b15Sjsg 		 sc->sc_dev.dv_xname,
1091ad942b15Sjsg 		 (unsigned)buf[0], (unsigned)buf[1],
1092ad942b15Sjsg 		 (unsigned)buf[2], (unsigned)buf[3],
1093ad942b15Sjsg 		 (unsigned)buf[4], (unsigned)buf[5],
1094ad942b15Sjsg 		 (unsigned)buf[6], (unsigned)buf[7]));
1095ad942b15Sjsg 
109635f382c9Skevlo 	intrstat = (sc->sc_type == UCHCOM_TYPE_CH343) ?
109735f382c9Skevlo 	    xfer->actlen - 1 : UCHCOM_INTR_STAT1;
109835f382c9Skevlo 
109935f382c9Skevlo 	uchcom_convert_status(sc, buf[intrstat]);
1100ad942b15Sjsg 	ucom_status_change((struct ucom_softc *) sc->sc_subdev);
1101ad942b15Sjsg }
1102