ufm.c revision 192984
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/usb/misc/ufm.c 192984 2009-05-28 17:36:36Z thompsa $");
33
34
35#include "usbdevs.h"
36#include <dev/usb/usb.h>
37#include <dev/usb/usb_mfunc.h>
38#include <dev/usb/usb_error.h>
39#include <dev/usb/ufm_ioctl.h>
40
41#define	USB_DEBUG_VAR usb2_debug
42
43#include <dev/usb/usb_core.h>
44#include <dev/usb/usb_debug.h>
45#include <dev/usb/usb_process.h>
46#include <dev/usb/usb_request.h>
47#include <dev/usb/usb_lookup.h>
48#include <dev/usb/usb_util.h>
49#include <dev/usb/usb_busdma.h>
50#include <dev/usb/usb_mbuf.h>
51#include <dev/usb/usb_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 usb_fifo_sc sc_fifo;
59	struct mtx sc_mtx;
60
61	struct usb_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 usb_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
107DRIVER_MODULE(ufm, uhub, ufm_driver, ufm_devclass, NULL, 0);
108MODULE_DEPEND(ufm, usb, 1, 1, 1);
109
110static int
111ufm_probe(device_t dev)
112{
113	struct usb_attach_arg *uaa = device_get_ivars(dev);
114
115	if (uaa->usb_mode != USB_MODE_HOST) {
116		return (ENXIO);
117	}
118	if ((uaa->info.idVendor == USB_VENDOR_CYPRESS) &&
119	    (uaa->info.idProduct == USB_PRODUCT_CYPRESS_FMRADIO)) {
120		return (0);
121	}
122	return (ENXIO);
123}
124
125static int
126ufm_attach(device_t dev)
127{
128	struct usb_attach_arg *uaa = device_get_ivars(dev);
129	struct ufm_softc *sc = device_get_softc(dev);
130	int error;
131
132	sc->sc_udev = uaa->device;
133	sc->sc_unit = device_get_unit(dev);
134
135	snprintf(sc->sc_name, sizeof(sc->sc_name), "%s",
136	    device_get_nameunit(dev));
137
138	mtx_init(&sc->sc_mtx, "ufm lock", NULL, MTX_DEF | MTX_RECURSE);
139
140	device_set_usb2_desc(dev);
141
142	error = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx,
143	    &ufm_fifo_methods, &sc->sc_fifo,
144	    device_get_unit(dev), 0 - 1, uaa->info.bIfaceIndex,
145	    UID_ROOT, GID_OPERATOR, 0644);
146	if (error) {
147		goto detach;
148	}
149	return (0);			/* success */
150
151detach:
152	ufm_detach(dev);
153	return (ENXIO);
154}
155
156static int
157ufm_detach(device_t dev)
158{
159	struct ufm_softc *sc = device_get_softc(dev);
160
161	usb2_fifo_detach(&sc->sc_fifo);
162
163	mtx_destroy(&sc->sc_mtx);
164
165	return (0);
166}
167
168static int
169ufm_open(struct usb_fifo *dev, int fflags)
170{
171	if ((fflags & (FWRITE | FREAD)) != (FWRITE | FREAD)) {
172		return (EACCES);
173	}
174	return (0);
175}
176
177static int
178ufm_do_req(struct ufm_softc *sc, uint8_t request,
179    uint16_t value, uint16_t index, uint8_t *retbuf)
180{
181	int error;
182
183	struct usb_device_request req;
184	uint8_t buf[1];
185
186	req.bmRequestType = UT_READ_VENDOR_DEVICE;
187	req.bRequest = request;
188	USETW(req.wValue, value);
189	USETW(req.wIndex, index);
190	USETW(req.wLength, 1);
191
192	error = usb2_do_request(sc->sc_udev, NULL, &req, buf);
193
194	if (retbuf) {
195		*retbuf = buf[0];
196	}
197	if (error) {
198		return (ENXIO);
199	}
200	return (0);
201}
202
203static int
204ufm_set_freq(struct ufm_softc *sc, void *addr)
205{
206	int freq = *(int *)addr;
207
208	/*
209	 * Freq now is in Hz.  We need to convert it to the frequency
210	 * that the radio wants.  This frequency is 10.7MHz above
211	 * the actual frequency.  We then need to convert to
212	 * units of 12.5kHz.  We add one to the IFM to make rounding
213	 * easier.
214	 */
215	mtx_lock(&sc->sc_mtx);
216	sc->sc_freq = freq;
217	mtx_unlock(&sc->sc_mtx);
218
219	freq = (freq + 10700001) / 12500;
220
221	/* This appears to set the frequency */
222	if (ufm_do_req(sc, UFM_CMD_SET_FREQ,
223	    freq >> 8, freq, NULL) != 0) {
224		return (EIO);
225	}
226	/* Not sure what this does */
227	if (ufm_do_req(sc, UFM_CMD0,
228	    0x96, 0xb7, NULL) != 0) {
229		return (EIO);
230	}
231	return (0);
232}
233
234static int
235ufm_get_freq(struct ufm_softc *sc, void *addr)
236{
237	int *valp = (int *)addr;
238
239	mtx_lock(&sc->sc_mtx);
240	*valp = sc->sc_freq;
241	mtx_unlock(&sc->sc_mtx);
242	return (0);
243}
244
245static int
246ufm_start(struct ufm_softc *sc, void *addr)
247{
248	uint8_t ret;
249
250	if (ufm_do_req(sc, UFM_CMD0,
251	    0x00, 0xc7, &ret)) {
252		return (EIO);
253	}
254	if (ufm_do_req(sc, UFM_CMD2,
255	    0x01, 0x00, &ret)) {
256		return (EIO);
257	}
258	if (ret & 0x1) {
259		return (EIO);
260	}
261	return (0);
262}
263
264static int
265ufm_stop(struct ufm_softc *sc, void *addr)
266{
267	if (ufm_do_req(sc, UFM_CMD0,
268	    0x16, 0x1C, NULL)) {
269		return (EIO);
270	}
271	if (ufm_do_req(sc, UFM_CMD2,
272	    0x00, 0x00, NULL)) {
273		return (EIO);
274	}
275	return (0);
276}
277
278static int
279ufm_get_stat(struct ufm_softc *sc, void *addr)
280{
281	uint8_t ret;
282
283	/*
284	 * Note, there's a 240ms settle time before the status
285	 * will be valid, so sleep that amount.
286	 */
287	usb2_pause_mtx(NULL, hz / 4);
288
289	if (ufm_do_req(sc, UFM_CMD0,
290	    0x00, 0x24, &ret)) {
291		return (EIO);
292	}
293	*(int *)addr = ret;
294
295	return (0);
296}
297
298static int
299ufm_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr,
300    int fflags)
301{
302	struct ufm_softc *sc = fifo->priv_sc0;
303	int error = 0;
304
305	switch (cmd) {
306	case FM_SET_FREQ:
307		error = ufm_set_freq(sc, addr);
308		break;
309	case FM_GET_FREQ:
310		error = ufm_get_freq(sc, addr);
311		break;
312	case FM_START:
313		error = ufm_start(sc, addr);
314		break;
315	case FM_STOP:
316		error = ufm_stop(sc, addr);
317		break;
318	case FM_GET_STAT:
319		error = ufm_get_stat(sc, addr);
320		break;
321	default:
322		error = ENOTTY;
323		break;
324	}
325	return (error);
326}
327