1184610Salfred/*	$NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $	*/
2184610Salfred
3184610Salfred#include <sys/cdefs.h>
4184610Salfred__FBSDID("$FreeBSD$");
5186885Stakawata#define UFOMA_HANDSFREE
6184610Salfred/*-
7184610Salfred * Copyright (c) 2005, Takanori Watanabe
8189002Sed * Copyright (c) 2003, M. Warner Losh <imp@FreeBSD.org>.
9184610Salfred * All rights reserved.
10184610Salfred *
11184610Salfred * Redistribution and use in source and binary forms, with or without
12184610Salfred * modification, are permitted provided that the following conditions
13184610Salfred * are met:
14184610Salfred * 1. Redistributions of source code must retain the above copyright
15184610Salfred *    notice, this list of conditions and the following disclaimer.
16184610Salfred * 2. Redistributions in binary form must reproduce the above copyright
17184610Salfred *    notice, this list of conditions and the following disclaimer in the
18184610Salfred *    documentation and/or other materials provided with the distribution.
19184610Salfred *
20184610Salfred * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21184610Salfred * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22184610Salfred * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23184610Salfred * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24184610Salfred * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25184610Salfred * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26184610Salfred * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27184610Salfred * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28184610Salfred * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29184610Salfred * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30184610Salfred * SUCH DAMAGE.
31184610Salfred */
32184610Salfred
33184610Salfred/*-
34184610Salfred * Copyright (c) 1998 The NetBSD Foundation, Inc.
35184610Salfred * All rights reserved.
36184610Salfred *
37184610Salfred * This code is derived from software contributed to The NetBSD Foundation
38184610Salfred * by Lennart Augustsson (lennart@augustsson.net) at
39184610Salfred * Carlstedt Research & Technology.
40184610Salfred *
41184610Salfred * Redistribution and use in source and binary forms, with or without
42184610Salfred * modification, are permitted provided that the following conditions
43184610Salfred * are met:
44184610Salfred * 1. Redistributions of source code must retain the above copyright
45184610Salfred *    notice, this list of conditions and the following disclaimer.
46184610Salfred * 2. Redistributions in binary form must reproduce the above copyright
47184610Salfred *    notice, this list of conditions and the following disclaimer in the
48184610Salfred *    documentation and/or other materials provided with the distribution.
49184610Salfred *
50184610Salfred * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
51184610Salfred * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
52184610Salfred * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
53184610Salfred * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
54184610Salfred * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
55184610Salfred * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
56184610Salfred * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
57184610Salfred * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
58184610Salfred * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
59184610Salfred * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
60184610Salfred * POSSIBILITY OF SUCH DAMAGE.
61184610Salfred */
62184610Salfred
63184610Salfred/*
64184610Salfred * Comm Class spec:  http://www.usb.org/developers/devclass_docs/usbccs10.pdf
65184610Salfred *                   http://www.usb.org/developers/devclass_docs/usbcdc11.pdf
66184610Salfred */
67184610Salfred
68184610Salfred/*
69184610Salfred * TODO:
70184610Salfred * - Implement a Call Device for modems without multiplexed commands.
71184610Salfred */
72184610Salfred
73184610Salfred/*
74184610Salfred * NOTE: all function names beginning like "ufoma_cfg_" can only
75184610Salfred * be called from within the config thread function !
76184610Salfred */
77184610Salfred
78194677Sthompsa#include <sys/stdint.h>
79194677Sthompsa#include <sys/stddef.h>
80194677Sthompsa#include <sys/param.h>
81194677Sthompsa#include <sys/queue.h>
82194677Sthompsa#include <sys/types.h>
83194677Sthompsa#include <sys/systm.h>
84194677Sthompsa#include <sys/kernel.h>
85194677Sthompsa#include <sys/bus.h>
86194677Sthompsa#include <sys/module.h>
87194677Sthompsa#include <sys/lock.h>
88194677Sthompsa#include <sys/mutex.h>
89194677Sthompsa#include <sys/condvar.h>
90194677Sthompsa#include <sys/sysctl.h>
91194677Sthompsa#include <sys/sx.h>
92194677Sthompsa#include <sys/unistd.h>
93194677Sthompsa#include <sys/callout.h>
94194677Sthompsa#include <sys/malloc.h>
95194677Sthompsa#include <sys/priv.h>
96194677Sthompsa#include <sys/sbuf.h>
97194677Sthompsa
98188942Sthompsa#include <dev/usb/usb.h>
99194677Sthompsa#include <dev/usb/usbdi.h>
100194677Sthompsa#include <dev/usb/usbdi_util.h>
101188942Sthompsa#include <dev/usb/usb_cdc.h>
102194677Sthompsa#include "usbdevs.h"
103184610Salfred
104194228Sthompsa#define	USB_DEBUG_VAR usb_debug
105188942Sthompsa#include <dev/usb/usb_debug.h>
106188942Sthompsa#include <dev/usb/usb_process.h>
107184610Salfred
108188942Sthompsa#include <dev/usb/serial/usb_serial.h>
109184610Salfred
110184610Salfredtypedef struct ufoma_mobile_acm_descriptor {
111184610Salfred	uint8_t	bFunctionLength;
112184610Salfred	uint8_t	bDescriptorType;
113184610Salfred	uint8_t	bDescriptorSubtype;
114184610Salfred	uint8_t	bType;
115184610Salfred	uint8_t	bMode[1];
116194228Sthompsa} __packed usb_mcpc_acm_descriptor;
117184610Salfred
118184610Salfred#define	UISUBCLASS_MCPC 0x88
119184610Salfred
120184610Salfred#define	UDESC_VS_INTERFACE 0x44
121184610Salfred#define	UDESCSUB_MCPC_ACM  0x11
122184610Salfred
123184610Salfred#define	UMCPC_ACM_TYPE_AB1 0x1
124184610Salfred#define	UMCPC_ACM_TYPE_AB2 0x2
125184610Salfred#define	UMCPC_ACM_TYPE_AB5 0x5
126184610Salfred#define	UMCPC_ACM_TYPE_AB6 0x6
127184610Salfred
128184610Salfred#define	UMCPC_ACM_MODE_DEACTIVATED 0x0
129184610Salfred#define	UMCPC_ACM_MODE_MODEM 0x1
130184610Salfred#define	UMCPC_ACM_MODE_ATCOMMAND 0x2
131184610Salfred#define	UMCPC_ACM_MODE_OBEX 0x60
132184610Salfred#define	UMCPC_ACM_MODE_VENDOR1 0xc0
133184610Salfred#define	UMCPC_ACM_MODE_VENDOR2 0xfe
134184610Salfred#define	UMCPC_ACM_MODE_UNLINKED 0xff
135184610Salfred
136184610Salfred#define	UMCPC_CM_MOBILE_ACM 0x0
137184610Salfred
138184610Salfred#define	UMCPC_ACTIVATE_MODE 0x60
139184610Salfred#define	UMCPC_GET_MODETABLE 0x61
140184610Salfred#define	UMCPC_SET_LINK 0x62
141184610Salfred#define	UMCPC_CLEAR_LINK 0x63
142184610Salfred
143184610Salfred#define	UMCPC_REQUEST_ACKNOWLEDGE 0x31
144184610Salfred
145184610Salfred#define	UFOMA_MAX_TIMEOUT 15		/* standard says 10 seconds */
146184610Salfred#define	UFOMA_CMD_BUF_SIZE 64		/* bytes */
147184610Salfred
148184610Salfred#define	UFOMA_BULK_BUF_SIZE 1024	/* bytes */
149184610Salfred
150187299Stakawataenum {
151187299Stakawata	UFOMA_CTRL_ENDPT_INTR,
152187299Stakawata	UFOMA_CTRL_ENDPT_READ,
153187299Stakawata	UFOMA_CTRL_ENDPT_WRITE,
154188413Sthompsa	UFOMA_CTRL_ENDPT_MAX,
155187299Stakawata};
156184610Salfred
157187299Stakawataenum {
158187299Stakawata	UFOMA_BULK_ENDPT_WRITE,
159187299Stakawata	UFOMA_BULK_ENDPT_READ,
160188413Sthompsa	UFOMA_BULK_ENDPT_MAX,
161187299Stakawata};
162187299Stakawata
163184610Salfredstruct ufoma_softc {
164192984Sthompsa	struct ucom_super_softc sc_super_ucom;
165192984Sthompsa	struct ucom_softc sc_ucom;
166184610Salfred	struct cv sc_cv;
167189265Sthompsa	struct mtx sc_mtx;
168184610Salfred
169192984Sthompsa	struct usb_xfer *sc_ctrl_xfer[UFOMA_CTRL_ENDPT_MAX];
170192984Sthompsa	struct usb_xfer *sc_bulk_xfer[UFOMA_BULK_ENDPT_MAX];
171184610Salfred	uint8_t *sc_modetable;
172184610Salfred	device_t sc_dev;
173192984Sthompsa	struct usb_device *sc_udev;
174184610Salfred
175184610Salfred	uint32_t sc_unit;
176184610Salfred
177184610Salfred	uint16_t sc_line;
178184610Salfred
179184610Salfred	uint8_t	sc_num_msg;
180187579Stakawata	uint8_t	sc_nobulk;
181184610Salfred	uint8_t	sc_ctrl_iface_no;
182184610Salfred	uint8_t	sc_ctrl_iface_index;
183184610Salfred	uint8_t	sc_data_iface_no;
184184610Salfred	uint8_t	sc_data_iface_index;
185184610Salfred	uint8_t	sc_cm_cap;
186184610Salfred	uint8_t	sc_acm_cap;
187184610Salfred	uint8_t	sc_lsr;
188184610Salfred	uint8_t	sc_msr;
189184610Salfred	uint8_t	sc_modetoactivate;
190184610Salfred	uint8_t	sc_currentmode;
191184610Salfred};
192184610Salfred
193184610Salfred/* prototypes */
194184610Salfred
195184610Salfredstatic device_probe_t ufoma_probe;
196184610Salfredstatic device_attach_t ufoma_attach;
197184610Salfredstatic device_detach_t ufoma_detach;
198239299Shselaskystatic void ufoma_free_softc(struct ufoma_softc *);
199184610Salfred
200193045Sthompsastatic usb_callback_t ufoma_ctrl_read_callback;
201193045Sthompsastatic usb_callback_t ufoma_ctrl_write_callback;
202193045Sthompsastatic usb_callback_t ufoma_intr_callback;
203193045Sthompsastatic usb_callback_t ufoma_bulk_write_callback;
204193045Sthompsastatic usb_callback_t ufoma_bulk_read_callback;
205184610Salfred
206192984Sthompsastatic void	*ufoma_get_intconf(struct usb_config_descriptor *,
207192984Sthompsa		    struct usb_interface_descriptor *, uint8_t, uint8_t);
208185948Sthompsastatic void	ufoma_cfg_link_state(struct ufoma_softc *);
209185948Sthompsastatic void	ufoma_cfg_activate_state(struct ufoma_softc *, uint16_t);
210239180Shselaskystatic void	ufoma_free(struct ucom_softc *);
211192984Sthompsastatic void	ufoma_cfg_open(struct ucom_softc *);
212192984Sthompsastatic void	ufoma_cfg_close(struct ucom_softc *);
213192984Sthompsastatic void	ufoma_cfg_set_break(struct ucom_softc *, uint8_t);
214192984Sthompsastatic void	ufoma_cfg_get_status(struct ucom_softc *, uint8_t *,
215185948Sthompsa		    uint8_t *);
216192984Sthompsastatic void	ufoma_cfg_set_dtr(struct ucom_softc *, uint8_t);
217192984Sthompsastatic void	ufoma_cfg_set_rts(struct ucom_softc *, uint8_t);
218192984Sthompsastatic int	ufoma_pre_param(struct ucom_softc *, struct termios *);
219192984Sthompsastatic void	ufoma_cfg_param(struct ucom_softc *, struct termios *);
220185948Sthompsastatic int	ufoma_modem_setup(device_t, struct ufoma_softc *,
221192984Sthompsa		    struct usb_attach_arg *);
222192984Sthompsastatic void	ufoma_start_read(struct ucom_softc *);
223192984Sthompsastatic void	ufoma_stop_read(struct ucom_softc *);
224192984Sthompsastatic void	ufoma_start_write(struct ucom_softc *);
225192984Sthompsastatic void	ufoma_stop_write(struct ucom_softc *);
226197570Sthompsastatic void	ufoma_poll(struct ucom_softc *ucom);
227184610Salfred
228186885Stakawata/*sysctl stuff*/
229186885Stakawatastatic int ufoma_sysctl_support(SYSCTL_HANDLER_ARGS);
230186885Stakawatastatic int ufoma_sysctl_current(SYSCTL_HANDLER_ARGS);
231186885Stakawatastatic int ufoma_sysctl_open(SYSCTL_HANDLER_ARGS);
232186885Stakawata
233192984Sthompsastatic const struct usb_config
234184610Salfred	ufoma_ctrl_config[UFOMA_CTRL_ENDPT_MAX] = {
235184610Salfred
236187299Stakawata	[UFOMA_CTRL_ENDPT_INTR] = {
237184610Salfred		.type = UE_INTERRUPT,
238184610Salfred		.endpoint = UE_ADDR_ANY,
239184610Salfred		.direction = UE_DIR_IN,
240190734Sthompsa		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
241192984Sthompsa		.bufsize = sizeof(struct usb_cdc_notification),
242190734Sthompsa		.callback = &ufoma_intr_callback,
243184610Salfred	},
244184610Salfred
245187299Stakawata	[UFOMA_CTRL_ENDPT_READ] = {
246184610Salfred		.type = UE_CONTROL,
247184610Salfred		.endpoint = 0x00,	/* Control pipe */
248184610Salfred		.direction = UE_DIR_ANY,
249192984Sthompsa		.bufsize = (sizeof(struct usb_device_request) + UFOMA_CMD_BUF_SIZE),
250190734Sthompsa		.flags = {.short_xfer_ok = 1,},
251190734Sthompsa		.callback = &ufoma_ctrl_read_callback,
252190734Sthompsa		.timeout = 1000,	/* 1 second */
253184610Salfred	},
254184610Salfred
255187299Stakawata	[UFOMA_CTRL_ENDPT_WRITE] = {
256184610Salfred		.type = UE_CONTROL,
257184610Salfred		.endpoint = 0x00,	/* Control pipe */
258184610Salfred		.direction = UE_DIR_ANY,
259192984Sthompsa		.bufsize = (sizeof(struct usb_device_request) + 1),
260190734Sthompsa		.callback = &ufoma_ctrl_write_callback,
261190734Sthompsa		.timeout = 1000,	/* 1 second */
262184610Salfred	},
263184610Salfred};
264184610Salfred
265192984Sthompsastatic const struct usb_config
266184610Salfred	ufoma_bulk_config[UFOMA_BULK_ENDPT_MAX] = {
267184610Salfred
268187299Stakawata	[UFOMA_BULK_ENDPT_WRITE] = {
269184610Salfred		.type = UE_BULK,
270184610Salfred		.endpoint = UE_ADDR_ANY,
271184610Salfred		.direction = UE_DIR_OUT,
272190734Sthompsa		.bufsize = UFOMA_BULK_BUF_SIZE,
273190734Sthompsa		.flags = {.pipe_bof = 1,.force_short_xfer = 1,},
274190734Sthompsa		.callback = &ufoma_bulk_write_callback,
275184610Salfred	},
276184610Salfred
277187299Stakawata	[UFOMA_BULK_ENDPT_READ] = {
278184610Salfred		.type = UE_BULK,
279184610Salfred		.endpoint = UE_ADDR_ANY,
280184610Salfred		.direction = UE_DIR_IN,
281190734Sthompsa		.bufsize = UFOMA_BULK_BUF_SIZE,
282190734Sthompsa		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
283190734Sthompsa		.callback = &ufoma_bulk_read_callback,
284184610Salfred	},
285184610Salfred};
286184610Salfred
287192984Sthompsastatic const struct ucom_callback ufoma_callback = {
288194228Sthompsa	.ucom_cfg_get_status = &ufoma_cfg_get_status,
289194228Sthompsa	.ucom_cfg_set_dtr = &ufoma_cfg_set_dtr,
290194228Sthompsa	.ucom_cfg_set_rts = &ufoma_cfg_set_rts,
291194228Sthompsa	.ucom_cfg_set_break = &ufoma_cfg_set_break,
292194228Sthompsa	.ucom_cfg_param = &ufoma_cfg_param,
293194228Sthompsa	.ucom_cfg_open = &ufoma_cfg_open,
294194228Sthompsa	.ucom_cfg_close = &ufoma_cfg_close,
295194228Sthompsa	.ucom_pre_param = &ufoma_pre_param,
296194228Sthompsa	.ucom_start_read = &ufoma_start_read,
297194228Sthompsa	.ucom_stop_read = &ufoma_stop_read,
298194228Sthompsa	.ucom_start_write = &ufoma_start_write,
299194228Sthompsa	.ucom_stop_write = &ufoma_stop_write,
300197570Sthompsa	.ucom_poll = &ufoma_poll,
301239180Shselasky	.ucom_free = &ufoma_free,
302184610Salfred};
303184610Salfred
304184610Salfredstatic device_method_t ufoma_methods[] = {
305184610Salfred	/* Device methods */
306184610Salfred	DEVMETHOD(device_probe, ufoma_probe),
307184610Salfred	DEVMETHOD(device_attach, ufoma_attach),
308184610Salfred	DEVMETHOD(device_detach, ufoma_detach),
309239180Shselasky	DEVMETHOD_END
310184610Salfred};
311184610Salfred
312184610Salfredstatic devclass_t ufoma_devclass;
313184610Salfred
314184610Salfredstatic driver_t ufoma_driver = {
315184610Salfred	.name = "ufoma",
316184610Salfred	.methods = ufoma_methods,
317184610Salfred	.size = sizeof(struct ufoma_softc),
318184610Salfred};
319184610Salfred
320189275SthompsaDRIVER_MODULE(ufoma, uhub, ufoma_driver, ufoma_devclass, NULL, 0);
321188942SthompsaMODULE_DEPEND(ufoma, ucom, 1, 1, 1);
322188942SthompsaMODULE_DEPEND(ufoma, usb, 1, 1, 1);
323212122SthompsaMODULE_VERSION(ufoma, 1);
324184610Salfred
325223515Shselaskystatic const STRUCT_USB_HOST_ID ufoma_devs[] = {
326223515Shselasky	{USB_IFACE_CLASS(UICLASS_CDC),
327223515Shselasky	 USB_IFACE_SUBCLASS(UISUBCLASS_MCPC),},
328223515Shselasky};
329223515Shselasky
330184610Salfredstatic int
331184610Salfredufoma_probe(device_t dev)
332184610Salfred{
333192984Sthompsa	struct usb_attach_arg *uaa = device_get_ivars(dev);
334192984Sthompsa	struct usb_interface_descriptor *id;
335192984Sthompsa	struct usb_config_descriptor *cd;
336194228Sthompsa	usb_mcpc_acm_descriptor *mad;
337223515Shselasky	int error;
338184610Salfred
339223515Shselasky	if (uaa->usb_mode != USB_MODE_HOST)
340184610Salfred		return (ENXIO);
341223515Shselasky
342223515Shselasky	error = usbd_lookup_id_by_uaa(ufoma_devs, sizeof(ufoma_devs), uaa);
343223515Shselasky	if (error)
344223515Shselasky		return (error);
345223515Shselasky
346194228Sthompsa	id = usbd_get_interface_descriptor(uaa->iface);
347194228Sthompsa	cd = usbd_get_config_descriptor(uaa->device);
348184610Salfred
349223515Shselasky	if (id == NULL || cd == NULL)
350184610Salfred		return (ENXIO);
351223515Shselasky
352184610Salfred	mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM);
353223515Shselasky	if (mad == NULL)
354184610Salfred		return (ENXIO);
355223515Shselasky
356184610Salfred#ifndef UFOMA_HANDSFREE
357184610Salfred	if ((mad->bType == UMCPC_ACM_TYPE_AB5) ||
358223515Shselasky	    (mad->bType == UMCPC_ACM_TYPE_AB6))
359184610Salfred		return (ENXIO);
360184610Salfred#endif
361223515Shselasky	return (BUS_PROBE_GENERIC);
362184610Salfred}
363184610Salfred
364184610Salfredstatic int
365184610Salfredufoma_attach(device_t dev)
366184610Salfred{
367192984Sthompsa	struct usb_attach_arg *uaa = device_get_ivars(dev);
368184610Salfred	struct ufoma_softc *sc = device_get_softc(dev);
369192984Sthompsa	struct usb_config_descriptor *cd;
370192984Sthompsa	struct usb_interface_descriptor *id;
371186885Stakawata	struct sysctl_ctx_list *sctx;
372186885Stakawata	struct sysctl_oid *soid;
373186885Stakawata
374194228Sthompsa	usb_mcpc_acm_descriptor *mad;
375184610Salfred	uint8_t elements;
376184610Salfred	int32_t error;
377184610Salfred
378184610Salfred	sc->sc_udev = uaa->device;
379184610Salfred	sc->sc_dev = dev;
380184610Salfred	sc->sc_unit = device_get_unit(dev);
381184610Salfred
382189265Sthompsa	mtx_init(&sc->sc_mtx, "ufoma", NULL, MTX_DEF);
383239180Shselasky	ucom_ref(&sc->sc_super_ucom);
384194227Sthompsa	cv_init(&sc->sc_cv, "CWAIT");
385184610Salfred
386194228Sthompsa	device_set_usb_desc(dev);
387184610Salfred
388184610Salfred	DPRINTF("\n");
389184610Salfred
390184610Salfred	/* setup control transfers */
391184610Salfred
392194228Sthompsa	cd = usbd_get_config_descriptor(uaa->device);
393194228Sthompsa	id = usbd_get_interface_descriptor(uaa->iface);
394184610Salfred	sc->sc_ctrl_iface_no = id->bInterfaceNumber;
395184610Salfred	sc->sc_ctrl_iface_index = uaa->info.bIfaceIndex;
396184610Salfred
397194228Sthompsa	error = usbd_transfer_setup(uaa->device,
398184610Salfred	    &sc->sc_ctrl_iface_index, sc->sc_ctrl_xfer,
399189265Sthompsa	    ufoma_ctrl_config, UFOMA_CTRL_ENDPT_MAX, sc, &sc->sc_mtx);
400184610Salfred
401184610Salfred	if (error) {
402184610Salfred		device_printf(dev, "allocating control USB "
403199816Sthompsa		    "transfers failed\n");
404184610Salfred		goto detach;
405184610Salfred	}
406184610Salfred	mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM);
407184610Salfred	if (mad == NULL) {
408184610Salfred		goto detach;
409184610Salfred	}
410184610Salfred	if (mad->bFunctionLength < sizeof(*mad)) {
411184610Salfred		device_printf(dev, "invalid MAD descriptor\n");
412184610Salfred		goto detach;
413184610Salfred	}
414184610Salfred	if ((mad->bType == UMCPC_ACM_TYPE_AB5) ||
415184610Salfred	    (mad->bType == UMCPC_ACM_TYPE_AB6)) {
416187579Stakawata		sc->sc_nobulk = 1;
417184610Salfred	} else {
418187579Stakawata		sc->sc_nobulk = 0;
419184610Salfred		if (ufoma_modem_setup(dev, sc, uaa)) {
420184610Salfred			goto detach;
421184610Salfred		}
422184610Salfred	}
423184610Salfred
424184610Salfred	elements = (mad->bFunctionLength - sizeof(*mad) + 1);
425184610Salfred
426184610Salfred	/* initialize mode variables */
427184610Salfred
428184610Salfred	sc->sc_modetable = malloc(elements + 1, M_USBDEV, M_WAITOK);
429184610Salfred
430184610Salfred	if (sc->sc_modetable == NULL) {
431184610Salfred		goto detach;
432184610Salfred	}
433184610Salfred	sc->sc_modetable[0] = (elements + 1);
434227461Shselasky	memcpy(&sc->sc_modetable[1], mad->bMode, elements);
435184610Salfred
436184610Salfred	sc->sc_currentmode = UMCPC_ACM_MODE_UNLINKED;
437184610Salfred	sc->sc_modetoactivate = mad->bMode[0];
438184610Salfred
439188413Sthompsa	/* clear stall at first run, if any */
440189265Sthompsa	mtx_lock(&sc->sc_mtx);
441194677Sthompsa	usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]);
442194677Sthompsa	usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]);
443189265Sthompsa	mtx_unlock(&sc->sc_mtx);
444184610Salfred
445194228Sthompsa	error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
446189265Sthompsa	    &ufoma_callback, &sc->sc_mtx);
447184610Salfred	if (error) {
448194228Sthompsa		DPRINTF("ucom_attach failed\n");
449184610Salfred		goto detach;
450184610Salfred	}
451214843Sn_hibma	ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
452214843Sn_hibma
453186885Stakawata	/*Sysctls*/
454186885Stakawata	sctx = device_get_sysctl_ctx(dev);
455186885Stakawata	soid = device_get_sysctl_tree(dev);
456186885Stakawata
457186885Stakawata	SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "supportmode",
458186885Stakawata			CTLFLAG_RD|CTLTYPE_STRING, sc, 0, ufoma_sysctl_support,
459186885Stakawata			"A", "Supporting port role");
460186885Stakawata
461186885Stakawata	SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "currentmode",
462186885Stakawata			CTLFLAG_RD|CTLTYPE_STRING, sc, 0, ufoma_sysctl_current,
463186885Stakawata			"A", "Current port role");
464186885Stakawata
465186885Stakawata	SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "openmode",
466186885Stakawata			CTLFLAG_RW|CTLTYPE_STRING, sc, 0, ufoma_sysctl_open,
467186885Stakawata			"A", "Mode to transit when port is opened");
468187113Stakawata	SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "comunit",
469214761Sn_hibma			CTLFLAG_RD, &(sc->sc_super_ucom.sc_unit), 0,
470187113Stakawata			"Unit number as USB serial");
471186885Stakawata
472184610Salfred	return (0);			/* success */
473184610Salfred
474184610Salfreddetach:
475184610Salfred	ufoma_detach(dev);
476184610Salfred	return (ENXIO);			/* failure */
477184610Salfred}
478184610Salfred
479184610Salfredstatic int
480184610Salfredufoma_detach(device_t dev)
481184610Salfred{
482184610Salfred	struct ufoma_softc *sc = device_get_softc(dev);
483184610Salfred
484214761Sn_hibma	ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
485194228Sthompsa	usbd_transfer_unsetup(sc->sc_ctrl_xfer, UFOMA_CTRL_ENDPT_MAX);
486194228Sthompsa	usbd_transfer_unsetup(sc->sc_bulk_xfer, UFOMA_BULK_ENDPT_MAX);
487184610Salfred
488184610Salfred	if (sc->sc_modetable) {
489184610Salfred		free(sc->sc_modetable, M_USBDEV);
490184610Salfred	}
491194227Sthompsa	cv_destroy(&sc->sc_cv);
492184610Salfred
493239299Shselasky	device_claim_softc(dev);
494239299Shselasky
495239299Shselasky	ufoma_free_softc(sc);
496239299Shselasky
497184610Salfred	return (0);
498184610Salfred}
499184610Salfred
500239180ShselaskyUCOM_UNLOAD_DRAIN(ufoma);
501239180Shselasky
502239180Shselaskystatic void
503239299Shselaskyufoma_free_softc(struct ufoma_softc *sc)
504239180Shselasky{
505239180Shselasky	if (ucom_unref(&sc->sc_super_ucom)) {
506239299Shselasky		mtx_destroy(&sc->sc_mtx);
507239299Shselasky		device_free_softc(sc);
508239180Shselasky	}
509239180Shselasky}
510239180Shselasky
511239180Shselaskystatic void
512239180Shselaskyufoma_free(struct ucom_softc *ucom)
513239180Shselasky{
514239299Shselasky	ufoma_free_softc(ucom->sc_parent);
515239180Shselasky}
516239180Shselasky
517184610Salfredstatic void *
518192984Sthompsaufoma_get_intconf(struct usb_config_descriptor *cd, struct usb_interface_descriptor *id,
519184610Salfred    uint8_t type, uint8_t subtype)
520184610Salfred{
521192984Sthompsa	struct usb_descriptor *desc = (void *)id;
522184610Salfred
523194228Sthompsa	while ((desc = usb_desc_foreach(cd, desc))) {
524184610Salfred
525184610Salfred		if (desc->bDescriptorType == UDESC_INTERFACE) {
526184610Salfred			return (NULL);
527184610Salfred		}
528184610Salfred		if ((desc->bDescriptorType == type) &&
529184610Salfred		    (desc->bDescriptorSubtype == subtype)) {
530184610Salfred			break;
531184610Salfred		}
532184610Salfred	}
533184610Salfred	return (desc);
534184610Salfred}
535184610Salfred
536184610Salfredstatic void
537184610Salfredufoma_cfg_link_state(struct ufoma_softc *sc)
538184610Salfred{
539192984Sthompsa	struct usb_device_request req;
540184610Salfred	int32_t error;
541184610Salfred
542184610Salfred	req.bmRequestType = UT_WRITE_VENDOR_INTERFACE;
543184610Salfred	req.bRequest = UMCPC_SET_LINK;
544184610Salfred	USETW(req.wValue, UMCPC_CM_MOBILE_ACM);
545184610Salfred	USETW(req.wIndex, sc->sc_ctrl_iface_no);
546184610Salfred	USETW(req.wLength, sc->sc_modetable[0]);
547184610Salfred
548194228Sthompsa	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
549188413Sthompsa	    &req, sc->sc_modetable, 0, 1000);
550184610Salfred
551194227Sthompsa	error = cv_timedwait(&sc->sc_cv, &sc->sc_mtx, hz);
552184610Salfred
553184610Salfred	if (error) {
554184610Salfred		DPRINTF("NO response\n");
555184610Salfred	}
556184610Salfred}
557184610Salfred
558184610Salfredstatic void
559184610Salfredufoma_cfg_activate_state(struct ufoma_softc *sc, uint16_t state)
560184610Salfred{
561192984Sthompsa	struct usb_device_request req;
562184610Salfred	int32_t error;
563184610Salfred
564184610Salfred	req.bmRequestType = UT_WRITE_VENDOR_INTERFACE;
565184610Salfred	req.bRequest = UMCPC_ACTIVATE_MODE;
566184610Salfred	USETW(req.wValue, state);
567184610Salfred	USETW(req.wIndex, sc->sc_ctrl_iface_no);
568184610Salfred	USETW(req.wLength, 0);
569184610Salfred
570194228Sthompsa	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
571188413Sthompsa	    &req, NULL, 0, 1000);
572184610Salfred
573194227Sthompsa	error = cv_timedwait(&sc->sc_cv, &sc->sc_mtx,
574184610Salfred	    (UFOMA_MAX_TIMEOUT * hz));
575184610Salfred	if (error) {
576184610Salfred		DPRINTF("No response\n");
577184610Salfred	}
578184610Salfred}
579184610Salfred
580184610Salfredstatic void
581194677Sthompsaufoma_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error)
582184610Salfred{
583194677Sthompsa	struct ufoma_softc *sc = usbd_xfer_softc(xfer);
584192984Sthompsa	struct usb_device_request req;
585194677Sthompsa	struct usb_page_cache *pc0, *pc1;
586194677Sthompsa	int len, aframes, nframes;
587184610Salfred
588194677Sthompsa	usbd_xfer_status(xfer, NULL, NULL, &aframes, &nframes);
589194677Sthompsa
590184610Salfred	switch (USB_GET_STATE(xfer)) {
591184610Salfred	case USB_ST_TRANSFERRED:
592184610Salfredtr_transferred:
593194677Sthompsa		if (aframes != nframes)
594184610Salfred			goto tr_setup;
595194677Sthompsa		pc1 = usbd_xfer_get_frame(xfer, 1);
596194682Sthompsa		len = usbd_xfer_frame_len(xfer, 1);
597194677Sthompsa		if (len > 0)
598194677Sthompsa			ucom_put_data(&sc->sc_ucom, pc1, 0, len);
599194677Sthompsa		/* FALLTHROUGH */
600184610Salfred	case USB_ST_SETUP:
601184610Salfredtr_setup:
602184610Salfred		if (sc->sc_num_msg) {
603184610Salfred			sc->sc_num_msg--;
604184610Salfred
605184610Salfred			req.bmRequestType = UT_READ_CLASS_INTERFACE;
606184610Salfred			req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE;
607184610Salfred			USETW(req.wIndex, sc->sc_ctrl_iface_no);
608184610Salfred			USETW(req.wValue, 0);
609184610Salfred			USETW(req.wLength, UFOMA_CMD_BUF_SIZE);
610184610Salfred
611194677Sthompsa			pc0 = usbd_xfer_get_frame(xfer, 0);
612194677Sthompsa			usbd_copy_in(pc0, 0, &req, sizeof(req));
613184610Salfred
614194677Sthompsa			usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
615194677Sthompsa			usbd_xfer_set_frame_len(xfer, 1, UFOMA_CMD_BUF_SIZE);
616194677Sthompsa			usbd_xfer_set_frames(xfer, 2);
617194228Sthompsa			usbd_transfer_submit(xfer);
618184610Salfred		}
619184610Salfred		return;
620184610Salfred
621184610Salfred	default:			/* Error */
622184610Salfred		DPRINTF("error = %s\n",
623194677Sthompsa		    usbd_errstr(error));
624184610Salfred
625194677Sthompsa		if (error == USB_ERR_CANCELLED) {
626184610Salfred			return;
627184610Salfred		}
628184610Salfred		goto tr_transferred;
629184610Salfred	}
630184610Salfred}
631184610Salfred
632184610Salfredstatic void
633194677Sthompsaufoma_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error)
634184610Salfred{
635194677Sthompsa	struct ufoma_softc *sc = usbd_xfer_softc(xfer);
636192984Sthompsa	struct usb_device_request req;
637194677Sthompsa	struct usb_page_cache *pc;
638184610Salfred	uint32_t actlen;
639184610Salfred
640184610Salfred	switch (USB_GET_STATE(xfer)) {
641184610Salfred	case USB_ST_TRANSFERRED:
642184610Salfredtr_transferred:
643184610Salfred	case USB_ST_SETUP:
644194677Sthompsa		pc = usbd_xfer_get_frame(xfer, 1);
645194677Sthompsa		if (ucom_get_data(&sc->sc_ucom, pc, 0, 1, &actlen)) {
646184610Salfred
647184610Salfred			req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
648184610Salfred			req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND;
649184610Salfred			USETW(req.wIndex, sc->sc_ctrl_iface_no);
650184610Salfred			USETW(req.wValue, 0);
651184610Salfred			USETW(req.wLength, 1);
652184610Salfred
653194677Sthompsa			pc = usbd_xfer_get_frame(xfer, 0);
654194677Sthompsa			usbd_copy_in(pc, 0, &req, sizeof(req));
655184610Salfred
656194677Sthompsa			usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
657194677Sthompsa			usbd_xfer_set_frame_len(xfer, 1, 1);
658194677Sthompsa			usbd_xfer_set_frames(xfer, 2);
659184610Salfred
660194228Sthompsa			usbd_transfer_submit(xfer);
661184610Salfred		}
662184610Salfred		return;
663184610Salfred
664184610Salfred	default:			/* Error */
665194677Sthompsa		DPRINTF("error = %s\n", usbd_errstr(error));
666184610Salfred
667194677Sthompsa		if (error == USB_ERR_CANCELLED) {
668184610Salfred			return;
669184610Salfred		}
670184610Salfred		goto tr_transferred;
671184610Salfred	}
672184610Salfred}
673184610Salfred
674184610Salfredstatic void
675194677Sthompsaufoma_intr_callback(struct usb_xfer *xfer, usb_error_t error)
676184610Salfred{
677194677Sthompsa	struct ufoma_softc *sc = usbd_xfer_softc(xfer);
678192984Sthompsa	struct usb_cdc_notification pkt;
679194677Sthompsa	struct usb_page_cache *pc;
680184610Salfred	uint16_t wLen;
681184610Salfred	uint16_t temp;
682184610Salfred	uint8_t mstatus;
683194677Sthompsa	int actlen;
684184610Salfred
685194677Sthompsa	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
686194677Sthompsa
687184610Salfred	switch (USB_GET_STATE(xfer)) {
688184610Salfred	case USB_ST_TRANSFERRED:
689194677Sthompsa		if (actlen < 8) {
690184610Salfred			DPRINTF("too short message\n");
691184610Salfred			goto tr_setup;
692184610Salfred		}
693233774Shselasky		if (actlen > (int)sizeof(pkt)) {
694184610Salfred			DPRINTF("truncating message\n");
695194677Sthompsa			actlen = sizeof(pkt);
696184610Salfred		}
697194677Sthompsa		pc = usbd_xfer_get_frame(xfer, 0);
698194677Sthompsa		usbd_copy_out(pc, 0, &pkt, actlen);
699184610Salfred
700194677Sthompsa		actlen -= 8;
701184610Salfred
702184610Salfred		wLen = UGETW(pkt.wLength);
703194677Sthompsa		if (actlen > wLen) {
704194677Sthompsa			actlen = wLen;
705184610Salfred		}
706184610Salfred		if ((pkt.bmRequestType == UT_READ_VENDOR_INTERFACE) &&
707184610Salfred		    (pkt.bNotification == UMCPC_REQUEST_ACKNOWLEDGE)) {
708184610Salfred			temp = UGETW(pkt.wValue);
709184610Salfred			sc->sc_currentmode = (temp >> 8);
710184610Salfred			if (!(temp & 0xff)) {
711184610Salfred				DPRINTF("Mode change failed!\n");
712184610Salfred			}
713194227Sthompsa			cv_signal(&sc->sc_cv);
714184610Salfred		}
715184610Salfred		if (pkt.bmRequestType != UCDC_NOTIFICATION) {
716184610Salfred			goto tr_setup;
717184610Salfred		}
718184610Salfred		switch (pkt.bNotification) {
719184610Salfred		case UCDC_N_RESPONSE_AVAILABLE:
720187579Stakawata			if (!(sc->sc_nobulk)) {
721184610Salfred				DPRINTF("Wrong serial state!\n");
722184610Salfred				break;
723184610Salfred			}
724184610Salfred			if (sc->sc_num_msg != 0xFF) {
725184610Salfred				sc->sc_num_msg++;
726184610Salfred			}
727194228Sthompsa			usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]);
728184610Salfred			break;
729184610Salfred
730184610Salfred		case UCDC_N_SERIAL_STATE:
731187579Stakawata			if (sc->sc_nobulk) {
732184610Salfred				DPRINTF("Wrong serial state!\n");
733184610Salfred				break;
734184610Salfred			}
735184610Salfred			/*
736184610Salfred		         * Set the serial state in ucom driver based on
737184610Salfred		         * the bits from the notify message
738184610Salfred		         */
739194677Sthompsa			if (actlen < 2) {
740184610Salfred				DPRINTF("invalid notification "
741194677Sthompsa				    "length, %d bytes!\n", actlen);
742184610Salfred				break;
743184610Salfred			}
744184610Salfred			DPRINTF("notify bytes = 0x%02x, 0x%02x\n",
745184610Salfred			    pkt.data[0], pkt.data[1]);
746184610Salfred
747184610Salfred			/* currently, lsr is always zero. */
748184610Salfred			sc->sc_lsr = 0;
749184610Salfred			sc->sc_msr = 0;
750184610Salfred
751184610Salfred			mstatus = pkt.data[0];
752184610Salfred
753184610Salfred			if (mstatus & UCDC_N_SERIAL_RI) {
754184610Salfred				sc->sc_msr |= SER_RI;
755184610Salfred			}
756184610Salfred			if (mstatus & UCDC_N_SERIAL_DSR) {
757184610Salfred				sc->sc_msr |= SER_DSR;
758184610Salfred			}
759184610Salfred			if (mstatus & UCDC_N_SERIAL_DCD) {
760184610Salfred				sc->sc_msr |= SER_DCD;
761184610Salfred			}
762194228Sthompsa			ucom_status_change(&sc->sc_ucom);
763184610Salfred			break;
764184610Salfred
765184610Salfred		default:
766184610Salfred			break;
767184610Salfred		}
768184610Salfred
769184610Salfred	case USB_ST_SETUP:
770184610Salfredtr_setup:
771194677Sthompsa		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
772194228Sthompsa		usbd_transfer_submit(xfer);
773184610Salfred		return;
774184610Salfred
775184610Salfred	default:			/* Error */
776194677Sthompsa		if (error != USB_ERR_CANCELLED) {
777188413Sthompsa			/* try to clear stall first */
778194677Sthompsa			usbd_xfer_set_stall(xfer);
779188413Sthompsa			goto tr_setup;
780184610Salfred		}
781184610Salfred		return;
782184610Salfred	}
783184610Salfred}
784184610Salfred
785184610Salfredstatic void
786194677Sthompsaufoma_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
787184610Salfred{
788194677Sthompsa	struct ufoma_softc *sc = usbd_xfer_softc(xfer);
789194677Sthompsa	struct usb_page_cache *pc;
790184610Salfred	uint32_t actlen;
791184610Salfred
792184610Salfred	switch (USB_GET_STATE(xfer)) {
793184610Salfred	case USB_ST_SETUP:
794184610Salfred	case USB_ST_TRANSFERRED:
795188413Sthompsatr_setup:
796194677Sthompsa		pc = usbd_xfer_get_frame(xfer, 0);
797194677Sthompsa		if (ucom_get_data(&sc->sc_ucom, pc, 0,
798184610Salfred		    UFOMA_BULK_BUF_SIZE, &actlen)) {
799194677Sthompsa			usbd_xfer_set_frame_len(xfer, 0, actlen);
800194228Sthompsa			usbd_transfer_submit(xfer);
801184610Salfred		}
802184610Salfred		return;
803184610Salfred
804184610Salfred	default:			/* Error */
805194677Sthompsa		if (error != USB_ERR_CANCELLED) {
806188413Sthompsa			/* try to clear stall first */
807194677Sthompsa			usbd_xfer_set_stall(xfer);
808188413Sthompsa			goto tr_setup;
809184610Salfred		}
810184610Salfred		return;
811184610Salfred	}
812184610Salfred}
813184610Salfred
814184610Salfredstatic void
815194677Sthompsaufoma_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
816184610Salfred{
817194677Sthompsa	struct ufoma_softc *sc = usbd_xfer_softc(xfer);
818194677Sthompsa	struct usb_page_cache *pc;
819194677Sthompsa	int actlen;
820184610Salfred
821194677Sthompsa	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
822194677Sthompsa
823184610Salfred	switch (USB_GET_STATE(xfer)) {
824184610Salfred	case USB_ST_TRANSFERRED:
825194677Sthompsa		pc = usbd_xfer_get_frame(xfer, 0);
826194677Sthompsa		ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
827184610Salfred
828184610Salfred	case USB_ST_SETUP:
829188413Sthompsatr_setup:
830194677Sthompsa		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
831194228Sthompsa		usbd_transfer_submit(xfer);
832184610Salfred		return;
833184610Salfred
834184610Salfred	default:			/* Error */
835194677Sthompsa		if (error != USB_ERR_CANCELLED) {
836188413Sthompsa			/* try to clear stall first */
837194677Sthompsa			usbd_xfer_set_stall(xfer);
838188413Sthompsa			goto tr_setup;
839184610Salfred		}
840184610Salfred		return;
841184610Salfred	}
842184610Salfred}
843184610Salfred
844184610Salfredstatic void
845192984Sthompsaufoma_cfg_open(struct ucom_softc *ucom)
846184610Salfred{
847184610Salfred	struct ufoma_softc *sc = ucom->sc_parent;
848184610Salfred
849184610Salfred	/* empty input queue */
850184610Salfred
851184610Salfred	if (sc->sc_num_msg != 0xFF) {
852184610Salfred		sc->sc_num_msg++;
853184610Salfred	}
854184610Salfred	if (sc->sc_currentmode == UMCPC_ACM_MODE_UNLINKED) {
855184610Salfred		ufoma_cfg_link_state(sc);
856184610Salfred	}
857184610Salfred	if (sc->sc_currentmode == UMCPC_ACM_MODE_DEACTIVATED) {
858184610Salfred		ufoma_cfg_activate_state(sc, sc->sc_modetoactivate);
859184610Salfred	}
860184610Salfred}
861184610Salfred
862184610Salfredstatic void
863192984Sthompsaufoma_cfg_close(struct ucom_softc *ucom)
864184610Salfred{
865184610Salfred	struct ufoma_softc *sc = ucom->sc_parent;
866184610Salfred
867184610Salfred	ufoma_cfg_activate_state(sc, UMCPC_ACM_MODE_DEACTIVATED);
868184610Salfred}
869184610Salfred
870184610Salfredstatic void
871192984Sthompsaufoma_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
872184610Salfred{
873184610Salfred	struct ufoma_softc *sc = ucom->sc_parent;
874192984Sthompsa	struct usb_device_request req;
875184610Salfred	uint16_t wValue;
876184610Salfred
877187579Stakawata	if (sc->sc_nobulk ||
878186885Stakawata	    (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX)) {
879184610Salfred		return;
880184610Salfred	}
881184610Salfred	if (!(sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK)) {
882184610Salfred		return;
883184610Salfred	}
884184610Salfred	wValue = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF;
885184610Salfred
886184610Salfred	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
887184610Salfred	req.bRequest = UCDC_SEND_BREAK;
888184610Salfred	USETW(req.wValue, wValue);
889184610Salfred	req.wIndex[0] = sc->sc_ctrl_iface_no;
890184610Salfred	req.wIndex[1] = 0;
891184610Salfred	USETW(req.wLength, 0);
892184610Salfred
893194228Sthompsa	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
894188413Sthompsa	    &req, NULL, 0, 1000);
895184610Salfred}
896184610Salfred
897184610Salfredstatic void
898192984Sthompsaufoma_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
899184610Salfred{
900184610Salfred	struct ufoma_softc *sc = ucom->sc_parent;
901184610Salfred
902184610Salfred	*lsr = sc->sc_lsr;
903184610Salfred	*msr = sc->sc_msr;
904184610Salfred}
905184610Salfred
906184610Salfredstatic void
907184610Salfredufoma_cfg_set_line_state(struct ufoma_softc *sc)
908184610Salfred{
909192984Sthompsa	struct usb_device_request req;
910184610Salfred
911184610Salfred	/* Don't send line state emulation request for OBEX port */
912184610Salfred	if (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX) {
913184610Salfred		return;
914184610Salfred	}
915184610Salfred	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
916184610Salfred	req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
917184610Salfred	USETW(req.wValue, sc->sc_line);
918184610Salfred	req.wIndex[0] = sc->sc_ctrl_iface_no;
919184610Salfred	req.wIndex[1] = 0;
920184610Salfred	USETW(req.wLength, 0);
921184610Salfred
922194228Sthompsa	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
923188413Sthompsa	    &req, NULL, 0, 1000);
924184610Salfred}
925184610Salfred
926184610Salfredstatic void
927192984Sthompsaufoma_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
928184610Salfred{
929184610Salfred	struct ufoma_softc *sc = ucom->sc_parent;
930184610Salfred
931187579Stakawata	if (sc->sc_nobulk) {
932184610Salfred		return;
933184610Salfred	}
934184610Salfred	if (onoff)
935184610Salfred		sc->sc_line |= UCDC_LINE_DTR;
936184610Salfred	else
937184610Salfred		sc->sc_line &= ~UCDC_LINE_DTR;
938184610Salfred
939184610Salfred	ufoma_cfg_set_line_state(sc);
940184610Salfred}
941184610Salfred
942184610Salfredstatic void
943192984Sthompsaufoma_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
944184610Salfred{
945184610Salfred	struct ufoma_softc *sc = ucom->sc_parent;
946184610Salfred
947187579Stakawata	if (sc->sc_nobulk) {
948184610Salfred		return;
949184610Salfred	}
950184610Salfred	if (onoff)
951184610Salfred		sc->sc_line |= UCDC_LINE_RTS;
952184610Salfred	else
953184610Salfred		sc->sc_line &= ~UCDC_LINE_RTS;
954184610Salfred
955184610Salfred	ufoma_cfg_set_line_state(sc);
956184610Salfred}
957184610Salfred
958184610Salfredstatic int
959192984Sthompsaufoma_pre_param(struct ucom_softc *ucom, struct termios *t)
960184610Salfred{
961184610Salfred	return (0);			/* we accept anything */
962184610Salfred}
963184610Salfred
964184610Salfredstatic void
965192984Sthompsaufoma_cfg_param(struct ucom_softc *ucom, struct termios *t)
966184610Salfred{
967184610Salfred	struct ufoma_softc *sc = ucom->sc_parent;
968192984Sthompsa	struct usb_device_request req;
969192984Sthompsa	struct usb_cdc_line_state ls;
970184610Salfred
971187579Stakawata	if (sc->sc_nobulk ||
972184610Salfred	    (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX)) {
973184610Salfred		return;
974184610Salfred	}
975184610Salfred	DPRINTF("\n");
976184610Salfred
977227461Shselasky	memset(&ls, 0, sizeof(ls));
978184610Salfred
979184610Salfred	USETDW(ls.dwDTERate, t->c_ospeed);
980184610Salfred
981184610Salfred	if (t->c_cflag & CSTOPB) {
982184610Salfred		ls.bCharFormat = UCDC_STOP_BIT_2;
983184610Salfred	} else {
984184610Salfred		ls.bCharFormat = UCDC_STOP_BIT_1;
985184610Salfred	}
986184610Salfred
987184610Salfred	if (t->c_cflag & PARENB) {
988184610Salfred		if (t->c_cflag & PARODD) {
989184610Salfred			ls.bParityType = UCDC_PARITY_ODD;
990184610Salfred		} else {
991184610Salfred			ls.bParityType = UCDC_PARITY_EVEN;
992184610Salfred		}
993184610Salfred	} else {
994184610Salfred		ls.bParityType = UCDC_PARITY_NONE;
995184610Salfred	}
996184610Salfred
997184610Salfred	switch (t->c_cflag & CSIZE) {
998184610Salfred	case CS5:
999184610Salfred		ls.bDataBits = 5;
1000184610Salfred		break;
1001184610Salfred	case CS6:
1002184610Salfred		ls.bDataBits = 6;
1003184610Salfred		break;
1004184610Salfred	case CS7:
1005184610Salfred		ls.bDataBits = 7;
1006184610Salfred		break;
1007184610Salfred	case CS8:
1008184610Salfred		ls.bDataBits = 8;
1009184610Salfred		break;
1010184610Salfred	}
1011184610Salfred
1012184610Salfred	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
1013184610Salfred	req.bRequest = UCDC_SET_LINE_CODING;
1014184610Salfred	USETW(req.wValue, 0);
1015184610Salfred	req.wIndex[0] = sc->sc_ctrl_iface_no;
1016184610Salfred	req.wIndex[1] = 0;
1017184610Salfred	USETW(req.wLength, UCDC_LINE_STATE_LENGTH);
1018184610Salfred
1019194228Sthompsa	ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
1020188413Sthompsa	    &req, &ls, 0, 1000);
1021184610Salfred}
1022184610Salfred
1023184610Salfredstatic int
1024184610Salfredufoma_modem_setup(device_t dev, struct ufoma_softc *sc,
1025192984Sthompsa    struct usb_attach_arg *uaa)
1026184610Salfred{
1027192984Sthompsa	struct usb_config_descriptor *cd;
1028192984Sthompsa	struct usb_cdc_acm_descriptor *acm;
1029192984Sthompsa	struct usb_cdc_cm_descriptor *cmd;
1030192984Sthompsa	struct usb_interface_descriptor *id;
1031192984Sthompsa	struct usb_interface *iface;
1032184610Salfred	uint8_t i;
1033184610Salfred	int32_t error;
1034184610Salfred
1035194228Sthompsa	cd = usbd_get_config_descriptor(uaa->device);
1036194228Sthompsa	id = usbd_get_interface_descriptor(uaa->iface);
1037184610Salfred
1038184610Salfred	cmd = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
1039184610Salfred
1040184610Salfred	if ((cmd == NULL) ||
1041184610Salfred	    (cmd->bLength < sizeof(*cmd))) {
1042184610Salfred		return (EINVAL);
1043184610Salfred	}
1044184610Salfred	sc->sc_cm_cap = cmd->bmCapabilities;
1045184610Salfred	sc->sc_data_iface_no = cmd->bDataInterface;
1046184610Salfred
1047184610Salfred	acm = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM);
1048184610Salfred
1049184610Salfred	if ((acm == NULL) ||
1050184610Salfred	    (acm->bLength < sizeof(*acm))) {
1051184610Salfred		return (EINVAL);
1052184610Salfred	}
1053184610Salfred	sc->sc_acm_cap = acm->bmCapabilities;
1054184610Salfred
1055184610Salfred	device_printf(dev, "data interface %d, has %sCM over data, "
1056184610Salfred	    "has %sbreak\n",
1057184610Salfred	    sc->sc_data_iface_no,
1058184610Salfred	    sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ",
1059184610Salfred	    sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no ");
1060184610Salfred
1061184610Salfred	/* get the data interface too */
1062184610Salfred
1063184610Salfred	for (i = 0;; i++) {
1064184610Salfred
1065194228Sthompsa		iface = usbd_get_iface(uaa->device, i);
1066184610Salfred
1067184610Salfred		if (iface) {
1068184610Salfred
1069194228Sthompsa			id = usbd_get_interface_descriptor(iface);
1070184610Salfred
1071184610Salfred			if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) {
1072184610Salfred				sc->sc_data_iface_index = i;
1073194228Sthompsa				usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
1074184610Salfred				break;
1075184610Salfred			}
1076184610Salfred		} else {
1077199816Sthompsa			device_printf(dev, "no data interface\n");
1078184610Salfred			return (EINVAL);
1079184610Salfred		}
1080184610Salfred	}
1081184610Salfred
1082194228Sthompsa	error = usbd_transfer_setup(uaa->device,
1083184610Salfred	    &sc->sc_data_iface_index, sc->sc_bulk_xfer,
1084189265Sthompsa	    ufoma_bulk_config, UFOMA_BULK_ENDPT_MAX, sc, &sc->sc_mtx);
1085184610Salfred
1086184610Salfred	if (error) {
1087184610Salfred		device_printf(dev, "allocating BULK USB "
1088199816Sthompsa		    "transfers failed\n");
1089184610Salfred		return (EINVAL);
1090184610Salfred	}
1091184610Salfred	return (0);
1092184610Salfred}
1093184610Salfred
1094184610Salfredstatic void
1095192984Sthompsaufoma_start_read(struct ucom_softc *ucom)
1096184610Salfred{
1097184610Salfred	struct ufoma_softc *sc = ucom->sc_parent;
1098184610Salfred
1099184610Salfred	/* start interrupt transfer */
1100194228Sthompsa	usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_INTR]);
1101184610Salfred
1102184610Salfred	/* start data transfer */
1103187579Stakawata	if (sc->sc_nobulk) {
1104194228Sthompsa		usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]);
1105184610Salfred	} else {
1106194228Sthompsa		usbd_transfer_start(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]);
1107184610Salfred	}
1108184610Salfred}
1109184610Salfred
1110184610Salfredstatic void
1111192984Sthompsaufoma_stop_read(struct ucom_softc *ucom)
1112184610Salfred{
1113184610Salfred	struct ufoma_softc *sc = ucom->sc_parent;
1114184610Salfred
1115184610Salfred	/* stop interrupt transfer */
1116194228Sthompsa	usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_INTR]);
1117184610Salfred
1118184610Salfred	/* stop data transfer */
1119187579Stakawata	if (sc->sc_nobulk) {
1120194228Sthompsa		usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]);
1121184610Salfred	} else {
1122194228Sthompsa		usbd_transfer_stop(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]);
1123184610Salfred	}
1124184610Salfred}
1125184610Salfred
1126184610Salfredstatic void
1127192984Sthompsaufoma_start_write(struct ucom_softc *ucom)
1128184610Salfred{
1129184610Salfred	struct ufoma_softc *sc = ucom->sc_parent;
1130184610Salfred
1131187579Stakawata	if (sc->sc_nobulk) {
1132194228Sthompsa		usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_WRITE]);
1133184610Salfred	} else {
1134194228Sthompsa		usbd_transfer_start(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]);
1135184610Salfred	}
1136184610Salfred}
1137184610Salfred
1138184610Salfredstatic void
1139192984Sthompsaufoma_stop_write(struct ucom_softc *ucom)
1140184610Salfred{
1141184610Salfred	struct ufoma_softc *sc = ucom->sc_parent;
1142184610Salfred
1143187579Stakawata	if (sc->sc_nobulk) {
1144194228Sthompsa		usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_WRITE]);
1145184610Salfred	} else {
1146194228Sthompsa		usbd_transfer_stop(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]);
1147184610Salfred	}
1148184610Salfred}
1149186885Stakawata
1150194099Sthompsastatic struct umcpc_modetostr_tab{
1151186885Stakawata	int mode;
1152186885Stakawata	char *str;
1153186885Stakawata}umcpc_modetostr_tab[]={
1154186885Stakawata	{UMCPC_ACM_MODE_DEACTIVATED, "deactivated"},
1155186885Stakawata	{UMCPC_ACM_MODE_MODEM, "modem"},
1156186885Stakawata	{UMCPC_ACM_MODE_ATCOMMAND, "handsfree"},
1157186885Stakawata	{UMCPC_ACM_MODE_OBEX, "obex"},
1158186885Stakawata	{UMCPC_ACM_MODE_VENDOR1, "vendor1"},
1159186885Stakawata	{UMCPC_ACM_MODE_VENDOR2, "vendor2"},
1160186885Stakawata	{UMCPC_ACM_MODE_UNLINKED, "unlinked"},
1161186885Stakawata	{0, NULL}
1162186885Stakawata};
1163186885Stakawata
1164186885Stakawatastatic char *ufoma_mode_to_str(int mode)
1165186885Stakawata{
1166186885Stakawata	int i;
1167186885Stakawata	for(i = 0 ;umcpc_modetostr_tab[i].str != NULL; i++){
1168186885Stakawata		if(umcpc_modetostr_tab[i].mode == mode){
1169186885Stakawata			return umcpc_modetostr_tab[i].str;
1170186885Stakawata		}
1171186885Stakawata	}
1172186885Stakawata	return NULL;
1173186885Stakawata}
1174186885Stakawata
1175186885Stakawatastatic int ufoma_str_to_mode(char *str)
1176186885Stakawata{
1177186885Stakawata	int i;
1178186885Stakawata	for(i = 0 ;umcpc_modetostr_tab[i].str != NULL; i++){
1179186885Stakawata		if(strcmp(str, umcpc_modetostr_tab[i].str)==0){
1180186885Stakawata			return umcpc_modetostr_tab[i].mode;
1181186885Stakawata		}
1182186885Stakawata	}
1183186885Stakawata	return -1;
1184186885Stakawata}
1185186885Stakawata
1186186885Stakawatastatic int ufoma_sysctl_support(SYSCTL_HANDLER_ARGS)
1187186885Stakawata{
1188186885Stakawata	struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1;
1189186885Stakawata	struct sbuf sb;
1190186885Stakawata	int i;
1191186885Stakawata	char *mode;
1192186885Stakawata
1193186885Stakawata	sbuf_new(&sb, NULL, 1, SBUF_AUTOEXTEND);
1194186885Stakawata	for(i = 1; i < sc->sc_modetable[0]; i++){
1195186885Stakawata		mode = ufoma_mode_to_str(sc->sc_modetable[i]);
1196186885Stakawata		if(mode !=NULL){
1197186885Stakawata			sbuf_cat(&sb, mode);
1198186885Stakawata		}else{
1199186885Stakawata			sbuf_printf(&sb, "(%02x)", sc->sc_modetable[i]);
1200186885Stakawata		}
1201186885Stakawata		if(i < (sc->sc_modetable[0]-1))
1202186885Stakawata			sbuf_cat(&sb, ",");
1203186885Stakawata	}
1204186885Stakawata	sbuf_trim(&sb);
1205186885Stakawata	sbuf_finish(&sb);
1206186885Stakawata	sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req);
1207186885Stakawata	sbuf_delete(&sb);
1208186885Stakawata
1209186885Stakawata	return 0;
1210186885Stakawata}
1211186885Stakawatastatic int ufoma_sysctl_current(SYSCTL_HANDLER_ARGS)
1212186885Stakawata{
1213186885Stakawata	struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1;
1214186885Stakawata	char *mode;
1215186885Stakawata	char subbuf[]="(XXX)";
1216186885Stakawata	mode = ufoma_mode_to_str(sc->sc_currentmode);
1217186885Stakawata	if(!mode){
1218186885Stakawata		mode = subbuf;
1219186885Stakawata		snprintf(subbuf, sizeof(subbuf), "(%02x)", sc->sc_currentmode);
1220186885Stakawata	}
1221186885Stakawata	sysctl_handle_string(oidp, mode, strlen(mode), req);
1222186885Stakawata
1223186885Stakawata	return 0;
1224186885Stakawata
1225186885Stakawata}
1226186885Stakawatastatic int ufoma_sysctl_open(SYSCTL_HANDLER_ARGS)
1227186885Stakawata{
1228186885Stakawata	struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1;
1229186885Stakawata	char *mode;
1230186885Stakawata	char subbuf[40];
1231186885Stakawata	int newmode;
1232186885Stakawata	int error;
1233186885Stakawata	int i;
1234186885Stakawata
1235186885Stakawata	mode = ufoma_mode_to_str(sc->sc_modetoactivate);
1236186885Stakawata	if(mode){
1237186885Stakawata		strncpy(subbuf, mode, sizeof(subbuf));
1238186885Stakawata	}else{
1239186885Stakawata		snprintf(subbuf, sizeof(subbuf), "(%02x)", sc->sc_modetoactivate);
1240186885Stakawata	}
1241186885Stakawata	error = sysctl_handle_string(oidp, subbuf, sizeof(subbuf), req);
1242186885Stakawata	if(error != 0 || req->newptr == NULL){
1243186885Stakawata		return error;
1244186885Stakawata	}
1245186885Stakawata
1246186885Stakawata	if((newmode = ufoma_str_to_mode(subbuf)) == -1){
1247186885Stakawata		return EINVAL;
1248186885Stakawata	}
1249186885Stakawata
1250186885Stakawata	for(i = 1 ; i < sc->sc_modetable[0] ; i++){
1251186885Stakawata		if(sc->sc_modetable[i] == newmode){
1252186885Stakawata			sc->sc_modetoactivate = newmode;
1253186885Stakawata			return 0;
1254186885Stakawata		}
1255186885Stakawata	}
1256186885Stakawata
1257186885Stakawata	return EINVAL;
1258186885Stakawata}
1259197570Sthompsa
1260197570Sthompsastatic void
1261197570Sthompsaufoma_poll(struct ucom_softc *ucom)
1262197570Sthompsa{
1263197570Sthompsa	struct ufoma_softc *sc = ucom->sc_parent;
1264197570Sthompsa	usbd_transfer_poll(sc->sc_ctrl_xfer, UFOMA_CTRL_ENDPT_MAX);
1265197570Sthompsa	usbd_transfer_poll(sc->sc_bulk_xfer, UFOMA_BULK_ENDPT_MAX);
1266197570Sthompsa}
1267