1 /* $OpenBSD: udsbr.c,v 1.7 2004/07/08 22:18:44 deraadt Exp $ */
2 /* $NetBSD: udsbr.c,v 1.7 2002/07/11 21:14:27 augustss Exp $ */
3
4 /*
5 * Copyright (c) 2002 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 *
8 * This code is derived from software contributed to The NetBSD Foundation
9 * by Lennart Augustsson (lennart@augustsson.net).
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the NetBSD
22 * Foundation, Inc. and its contributors.
23 * 4. Neither the name of The NetBSD Foundation nor the names of its
24 * contributors may be used to endorse or promote products derived
25 * from this software without specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39
40 /*
41 * Driver for the D-Link DSB-R100 FM radio.
42 * I apologize for the magic hex constants, but this is what happens
43 * when you have to reverse engineer the driver.
44 * Parts of the code borrowed from Linux and parts from Warner Losh's
45 * FreeBSD driver.
46 */
47
48 #include <sys/param.h>
49 #include <sys/systm.h>
50 #include <sys/kernel.h>
51 #include <sys/device.h>
52
53 #include <sys/radioio.h>
54 #include <dev/radio_if.h>
55
56 #include <dev/usb/usb.h>
57 #include <dev/usb/usbdi.h>
58 #include <dev/usb/usbdi_util.h>
59
60 #include <dev/usb/usbdevs.h>
61
62 #ifdef UDSBR_DEBUG
63 #define DPRINTF(x) do { if (udsbrdebug) logprintf x; } while (0)
64 #define DPRINTFN(n,x) do { if (udsbrdebug>(n)) logprintf x; } while (0)
65 int udsbrdebug = 0;
66 #else
67 #define DPRINTF(x)
68 #define DPRINTFN(n,x)
69 #endif
70
71 #define UDSBR_CONFIG_NO 1
72
73 Static int udsbr_get_info(void *, struct radio_info *);
74 Static int udsbr_set_info(void *, struct radio_info *);
75
76 struct radio_hw_if udsbr_hw_if = {
77 NULL, /* open */
78 NULL, /* close */
79 udsbr_get_info,
80 udsbr_set_info,
81 NULL
82 };
83
84 struct udsbr_softc {
85 USBBASEDEVICE sc_dev;
86 usbd_device_handle sc_udev;
87
88 char sc_mute;
89 char sc_vol;
90 u_int32_t sc_freq;
91
92 struct device *sc_child;
93
94 char sc_dying;
95 };
96
97 Static int udsbr_req(struct udsbr_softc *sc, int ureq, int value,
98 int index);
99 Static void udsbr_start(struct udsbr_softc *sc);
100 Static void udsbr_stop(struct udsbr_softc *sc);
101 Static void udsbr_setfreq(struct udsbr_softc *sc, int freq);
102 Static int udsbr_status(struct udsbr_softc *sc);
103
104 USB_DECLARE_DRIVER(udsbr);
105
USB_MATCH(udsbr)106 USB_MATCH(udsbr)
107 {
108 USB_MATCH_START(udsbr, uaa);
109
110 DPRINTFN(50,("udsbr_match\n"));
111
112 if (uaa->iface != NULL)
113 return (UMATCH_NONE);
114
115 if (uaa->vendor != USB_VENDOR_CYPRESS ||
116 uaa->product != USB_PRODUCT_CYPRESS_FMRADIO)
117 return (UMATCH_NONE);
118 return (UMATCH_VENDOR_PRODUCT);
119 }
120
USB_ATTACH(udsbr)121 USB_ATTACH(udsbr)
122 {
123 USB_ATTACH_START(udsbr, sc, uaa);
124 usbd_device_handle dev = uaa->device;
125 char devinfo[1024];
126 usbd_status err;
127
128 DPRINTFN(10,("udsbr_attach: sc=%p\n", sc));
129
130 usbd_devinfo(dev, 0, devinfo, sizeof devinfo);
131 USB_ATTACH_SETUP;
132 printf("%s: %s\n", USBDEVNAME(sc->sc_dev), devinfo);
133
134 err = usbd_set_config_no(dev, UDSBR_CONFIG_NO, 1);
135 if (err) {
136 printf("%s: setting config no failed\n",
137 USBDEVNAME(sc->sc_dev));
138 USB_ATTACH_ERROR_RETURN;
139 }
140
141 sc->sc_udev = dev;
142
143 DPRINTFN(10, ("udsbr_attach: %p\n", sc->sc_udev));
144
145 usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev,
146 USBDEV(sc->sc_dev));
147
148 sc->sc_child = radio_attach_mi(&udsbr_hw_if, sc, USBDEV(sc->sc_dev));
149
150 USB_ATTACH_SUCCESS_RETURN;
151 }
152
USB_DETACH(udsbr)153 USB_DETACH(udsbr)
154 {
155 USB_DETACH_START(udsbr, sc);
156 int rv = 0;
157
158 if (sc->sc_child != NULL)
159 rv = config_detach(sc->sc_child, flags);
160
161 usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev,
162 USBDEV(sc->sc_dev));
163
164 return (rv);
165 }
166
167 int
udsbr_activate(device_ptr_t self,enum devact act)168 udsbr_activate(device_ptr_t self, enum devact act)
169 {
170 struct udsbr_softc *sc = (struct udsbr_softc *)self;
171 int rv = 0;
172
173 switch (act) {
174 case DVACT_ACTIVATE:
175 return (EOPNOTSUPP);
176 break;
177
178 case DVACT_DEACTIVATE:
179 sc->sc_dying = 1;
180 if (sc->sc_child != NULL)
181 rv = config_deactivate(sc->sc_child);
182 break;
183 }
184 return (rv);
185 }
186
187 int
udsbr_req(struct udsbr_softc * sc,int ureq,int value,int index)188 udsbr_req(struct udsbr_softc *sc, int ureq, int value, int index)
189 {
190 usb_device_request_t req;
191 usbd_status err;
192 u_char data;
193
194 DPRINTFN(1,("udsbr_req: ureq=0x%02x value=0x%04x index=0x%04x\n",
195 ureq, value, index));
196 req.bmRequestType = UT_READ_VENDOR_DEVICE;
197 req.bRequest = ureq;
198 USETW(req.wValue, value);
199 USETW(req.wIndex, index);
200 USETW(req.wLength, 1);
201 err = usbd_do_request(sc->sc_udev, &req, &data);
202 if (err) {
203 printf("%s: request failed err=%d\n", USBDEVNAME(sc->sc_dev),
204 err);
205 }
206 return !(data & 1);
207 }
208
209 void
udsbr_start(struct udsbr_softc * sc)210 udsbr_start(struct udsbr_softc *sc)
211 {
212 (void)udsbr_req(sc, 0x00, 0x0000, 0x00c7);
213 (void)udsbr_req(sc, 0x02, 0x0001, 0x0000);
214 }
215
216 void
udsbr_stop(struct udsbr_softc * sc)217 udsbr_stop(struct udsbr_softc *sc)
218 {
219 (void)udsbr_req(sc, 0x00, 0x0016, 0x001c);
220 (void)udsbr_req(sc, 0x02, 0x0000, 0x0000);
221 }
222
223 void
udsbr_setfreq(struct udsbr_softc * sc,int freq)224 udsbr_setfreq(struct udsbr_softc *sc, int freq)
225 {
226 DPRINTF(("udsbr_setfreq: setfreq=%d\n", freq));
227 /*
228 * Freq now is in Hz. We need to convert it to the frequency
229 * that the radio wants. This frequency is 10.7MHz above
230 * the actual frequency. We then need to convert to
231 * units of 12.5kHz. We add one to the IFM to make rounding
232 * easier.
233 */
234 freq = (freq * 1000 + 10700001) / 12500;
235 (void)udsbr_req(sc, 0x01, (freq >> 8) & 0xff, freq & 0xff);
236 (void)udsbr_req(sc, 0x00, 0x0096, 0x00b7);
237 usbd_delay_ms(sc->sc_udev, 240); /* wait for signal to settle */
238 }
239
240 int
udsbr_status(struct udsbr_softc * sc)241 udsbr_status(struct udsbr_softc *sc)
242 {
243 return (udsbr_req(sc, 0x00, 0x0000, 0x0024));
244 }
245
246
247 int
udsbr_get_info(void * v,struct radio_info * ri)248 udsbr_get_info(void *v, struct radio_info *ri)
249 {
250 struct udsbr_softc *sc = v;
251
252 ri->mute = sc->sc_mute;
253 ri->volume = sc->sc_vol ? 255 : 0;
254 ri->caps = RADIO_CAPS_DETECT_STEREO;
255 ri->rfreq = 0;
256 ri->lock = 0;
257 ri->freq = sc->sc_freq;
258 ri->info = udsbr_status(sc) ? RADIO_INFO_STEREO : 0;
259
260 return (0);
261 }
262
263 int
udsbr_set_info(void * v,struct radio_info * ri)264 udsbr_set_info(void *v, struct radio_info *ri)
265 {
266 struct udsbr_softc *sc = v;
267
268 sc->sc_mute = ri->mute != 0;
269 sc->sc_vol = ri->volume != 0;
270 sc->sc_freq = ri->freq;
271 udsbr_setfreq(sc, sc->sc_freq);
272
273 if (sc->sc_mute || sc->sc_vol == 0)
274 udsbr_stop(sc);
275 else
276 udsbr_start(sc);
277
278 return (0);
279 }
280