ufm.c revision 187970
1/*-
2 * Copyright (c) 2001 M. Warner Losh
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions, and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * This code is based on ugen.c and ulpt.c developed by Lennart Augustsson.
27 * This code includes software developed by the NetBSD Foundation, Inc. and
28 * its contributors.
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: head/sys/dev/usb2/misc/ufm2.c 187970 2009-02-01 00:51:25Z thompsa $");
33
34
35#include <dev/usb2/include/usb2_devid.h>
36#include <dev/usb2/include/usb2_standard.h>
37#include <dev/usb2/include/usb2_mfunc.h>
38#include <dev/usb2/include/usb2_error.h>
39#include <dev/usb2/include/ufm2_ioctl.h>
40
41#define	USB_DEBUG_VAR usb2_debug
42
43#include <dev/usb2/core/usb2_core.h>
44#include <dev/usb2/core/usb2_debug.h>
45#include <dev/usb2/core/usb2_process.h>
46#include <dev/usb2/core/usb2_request.h>
47#include <dev/usb2/core/usb2_lookup.h>
48#include <dev/usb2/core/usb2_util.h>
49#include <dev/usb2/core/usb2_busdma.h>
50#include <dev/usb2/core/usb2_mbuf.h>
51#include <dev/usb2/core/usb2_dev.h>
52
53#define	UFM_CMD0		0x00
54#define	UFM_CMD_SET_FREQ	0x01
55#define	UFM_CMD2		0x02
56
57struct ufm_softc {
58	struct usb2_fifo_sc sc_fifo;
59	struct mtx sc_mtx;
60
61	struct usb2_device *sc_udev;
62
63	uint32_t sc_unit;
64	uint32_t sc_freq;
65
66	uint8_t	sc_name[16];
67};
68
69/* prototypes */
70
71static device_probe_t ufm_probe;
72static device_attach_t ufm_attach;
73static device_detach_t ufm_detach;
74
75static usb2_fifo_ioctl_t ufm_ioctl;
76static usb2_fifo_open_t ufm_open;
77
78static struct usb2_fifo_methods ufm_fifo_methods = {
79	.f_ioctl = &ufm_ioctl,
80	.f_open = &ufm_open,
81	.basename[0] = "ufm",
82};
83
84static int	ufm_do_req(struct ufm_softc *, uint8_t, uint16_t, uint16_t,
85		    uint8_t *);
86static int	ufm_set_freq(struct ufm_softc *, void *);
87static int	ufm_get_freq(struct ufm_softc *, void *);
88static int	ufm_start(struct ufm_softc *, void *);
89static int	ufm_stop(struct ufm_softc *, void *);
90static int	ufm_get_stat(struct ufm_softc *, void *);
91
92static devclass_t ufm_devclass;
93
94static device_method_t ufm_methods[] = {
95	DEVMETHOD(device_probe, ufm_probe),
96	DEVMETHOD(device_attach, ufm_attach),
97	DEVMETHOD(device_detach, ufm_detach),
98	{0, 0}
99};
100
101static driver_t ufm_driver = {
102	.name = "ufm",
103	.methods = ufm_methods,
104	.size = sizeof(struct ufm_softc),
105};
106
107MODULE_DEPEND(ufm, usb2_misc, 1, 1, 1);
108DRIVER_MODULE(ufm, ushub, ufm_driver, ufm_devclass, NULL, 0);
109MODULE_DEPEND(ufm, usb2_core, 1, 1, 1);
110
111static int
112ufm_probe(device_t dev)
113{
114	struct usb2_attach_arg *uaa = device_get_ivars(dev);
115
116	if (uaa->usb2_mode != USB_MODE_HOST) {
117		return (ENXIO);
118	}
119	if ((uaa->info.idVendor == USB_VENDOR_CYPRESS) &&
120	    (uaa->info.idProduct == USB_PRODUCT_CYPRESS_FMRADIO)) {
121		return (0);
122	}
123	return (ENXIO);
124}
125
126static int
127ufm_attach(device_t dev)
128{
129	struct usb2_attach_arg *uaa = device_get_ivars(dev);
130	struct ufm_softc *sc = device_get_softc(dev);
131	int error;
132
133	sc->sc_udev = uaa->device;
134	sc->sc_unit = device_get_unit(dev);
135
136	snprintf(sc->sc_name, sizeof(sc->sc_name), "%s",
137	    device_get_nameunit(dev));
138
139	mtx_init(&sc->sc_mtx, "ufm lock", NULL, MTX_DEF | MTX_RECURSE);
140
141	device_set_usb2_desc(dev);
142
143	/* set interface permissions */
144	usb2_set_iface_perm(uaa->device, uaa->info.bIfaceIndex,
145	    UID_ROOT, GID_OPERATOR, 0644);
146
147	error = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx,
148	    &ufm_fifo_methods, &sc->sc_fifo,
149	    device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex);
150	if (error) {
151		goto detach;
152	}
153	return (0);			/* success */
154
155detach:
156	ufm_detach(dev);
157	return (ENXIO);
158}
159
160static int
161ufm_detach(device_t dev)
162{
163	struct ufm_softc *sc = device_get_softc(dev);
164
165	usb2_fifo_detach(&sc->sc_fifo);
166
167	mtx_destroy(&sc->sc_mtx);
168
169	return (0);
170}
171
172static int
173ufm_open(struct usb2_fifo *dev, int fflags, struct thread *td)
174{
175	if ((fflags & (FWRITE | FREAD)) != (FWRITE | FREAD)) {
176		return (EACCES);
177	}
178	return (0);
179}
180
181static int
182ufm_do_req(struct ufm_softc *sc, uint8_t request,
183    uint16_t value, uint16_t index, uint8_t *retbuf)
184{
185	int error;
186
187	struct usb2_device_request req;
188	uint8_t buf[1];
189
190	req.bmRequestType = UT_READ_VENDOR_DEVICE;
191	req.bRequest = request;
192	USETW(req.wValue, value);
193	USETW(req.wIndex, index);
194	USETW(req.wLength, 1);
195
196	error = usb2_do_request(sc->sc_udev, NULL, &req, buf);
197
198	if (retbuf) {
199		*retbuf = buf[0];
200	}
201	if (error) {
202		return (ENXIO);
203	}
204	return (0);
205}
206
207static int
208ufm_set_freq(struct ufm_softc *sc, void *addr)
209{
210	int freq = *(int *)addr;
211
212	/*
213	 * Freq now is in Hz.  We need to convert it to the frequency
214	 * that the radio wants.  This frequency is 10.7MHz above
215	 * the actual frequency.  We then need to convert to
216	 * units of 12.5kHz.  We add one to the IFM to make rounding
217	 * easier.
218	 */
219	mtx_lock(&sc->sc_mtx);
220	sc->sc_freq = freq;
221	mtx_unlock(&sc->sc_mtx);
222
223	freq = (freq + 10700001) / 12500;
224
225	/* This appears to set the frequency */
226	if (ufm_do_req(sc, UFM_CMD_SET_FREQ,
227	    freq >> 8, freq, NULL) != 0) {
228		return (EIO);
229	}
230	/* Not sure what this does */
231	if (ufm_do_req(sc, UFM_CMD0,
232	    0x96, 0xb7, NULL) != 0) {
233		return (EIO);
234	}
235	return (0);
236}
237
238static int
239ufm_get_freq(struct ufm_softc *sc, void *addr)
240{
241	int *valp = (int *)addr;
242
243	mtx_lock(&sc->sc_mtx);
244	*valp = sc->sc_freq;
245	mtx_unlock(&sc->sc_mtx);
246	return (0);
247}
248
249static int
250ufm_start(struct ufm_softc *sc, void *addr)
251{
252	uint8_t ret;
253
254	if (ufm_do_req(sc, UFM_CMD0,
255	    0x00, 0xc7, &ret)) {
256		return (EIO);
257	}
258	if (ufm_do_req(sc, UFM_CMD2,
259	    0x01, 0x00, &ret)) {
260		return (EIO);
261	}
262	if (ret & 0x1) {
263		return (EIO);
264	}
265	return (0);
266}
267
268static int
269ufm_stop(struct ufm_softc *sc, void *addr)
270{
271	if (ufm_do_req(sc, UFM_CMD0,
272	    0x16, 0x1C, NULL)) {
273		return (EIO);
274	}
275	if (ufm_do_req(sc, UFM_CMD2,
276	    0x00, 0x00, NULL)) {
277		return (EIO);
278	}
279	return (0);
280}
281
282static int
283ufm_get_stat(struct ufm_softc *sc, void *addr)
284{
285	uint8_t ret;
286
287	/*
288	 * Note, there's a 240ms settle time before the status
289	 * will be valid, so sleep that amount.
290	 */
291
292	mtx_lock(&sc->sc_mtx);
293	usb2_pause_mtx(&sc->sc_mtx, USB_MS_HZ / 4);
294	mtx_unlock(&sc->sc_mtx);
295
296	if (ufm_do_req(sc, UFM_CMD0,
297	    0x00, 0x24, &ret)) {
298		return (EIO);
299	}
300	*(int *)addr = ret;
301
302	return (0);
303}
304
305static int
306ufm_ioctl(struct usb2_fifo *fifo, u_long cmd, void *addr,
307    int fflags, struct thread *td)
308{
309	struct ufm_softc *sc = fifo->priv_sc0;
310	int error = 0;
311
312	switch (cmd) {
313	case FM_SET_FREQ:
314		error = ufm_set_freq(sc, addr);
315		break;
316	case FM_GET_FREQ:
317		error = ufm_get_freq(sc, addr);
318		break;
319	case FM_START:
320		error = ufm_start(sc, addr);
321		break;
322	case FM_STOP:
323		error = ufm_stop(sc, addr);
324		break;
325	case FM_GET_STAT:
326		error = ufm_get_stat(sc, addr);
327		break;
328	default:
329		error = ENOTTY;
330		break;
331	}
332	return (error);
333}
334