xref: /openbsd-src/sys/dev/usb/udsbr.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: udsbr.c,v 1.25 2013/11/07 12:06:25 pirofti 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  *
20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 /*
34  * Driver for the D-Link DSB-R100 FM radio.
35  * I apologize for the magic hex constants, but this is what happens
36  * when you have to reverse engineer the driver.
37  * Parts of the code borrowed from Linux and parts from Warner Losh's
38  * FreeBSD driver.
39  */
40 
41 #include <sys/param.h>
42 #include <sys/systm.h>
43 #include <sys/kernel.h>
44 #include <sys/device.h>
45 
46 #include <sys/radioio.h>
47 #include <dev/radio_if.h>
48 
49 #include <dev/usb/usb.h>
50 #include <dev/usb/usbdi.h>
51 #include <dev/usb/usbdi_util.h>
52 
53 #include <dev/usb/usbdevs.h>
54 
55 #ifdef UDSBR_DEBUG
56 #define DPRINTF(x)	do { if (udsbrdebug) printf x; } while (0)
57 #define DPRINTFN(n,x)	do { if (udsbrdebug>(n)) printf x; } while (0)
58 int	udsbrdebug = 0;
59 #else
60 #define DPRINTF(x)
61 #define DPRINTFN(n,x)
62 #endif
63 
64 #define UDSBR_CONFIG_NO		1
65 
66 int     udsbr_get_info(void *, struct radio_info *);
67 int     udsbr_set_info(void *, struct radio_info *);
68 
69 struct radio_hw_if udsbr_hw_if = {
70 	NULL, /* open */
71 	NULL, /* close */
72 	udsbr_get_info,
73 	udsbr_set_info,
74 	NULL
75 };
76 
77 struct udsbr_softc {
78  	struct device		 sc_dev;
79 	struct usbd_device	*sc_udev;
80 
81 	char			 sc_mute;
82 	char			 sc_vol;
83 	u_int32_t		 sc_freq;
84 
85 	struct device		*sc_child;
86 };
87 
88 int	udsbr_req(struct udsbr_softc *sc, int ureq, int value, int index);
89 void	udsbr_start(struct udsbr_softc *sc);
90 void	udsbr_stop(struct udsbr_softc *sc);
91 void	udsbr_setfreq(struct udsbr_softc *sc, int freq);
92 int	udsbr_status(struct udsbr_softc *sc);
93 
94 int udsbr_match(struct device *, void *, void *);
95 void udsbr_attach(struct device *, struct device *, void *);
96 int udsbr_detach(struct device *, int);
97 int udsbr_activate(struct device *, int);
98 
99 struct cfdriver udsbr_cd = {
100 	NULL, "udsbr", DV_DULL
101 };
102 
103 const struct cfattach udsbr_ca = {
104 	sizeof(struct udsbr_softc),
105 	udsbr_match,
106 	udsbr_attach,
107 	udsbr_detach,
108 	udsbr_activate,
109 };
110 
111 int
112 udsbr_match(struct device *parent, void *match, void *aux)
113 {
114 	struct usb_attach_arg	*uaa = aux;
115 
116 	DPRINTFN(50,("udsbr_match\n"));
117 
118 	if (uaa->iface != NULL)
119 		return (UMATCH_NONE);
120 
121 	if (uaa->vendor != USB_VENDOR_CYPRESS ||
122 	    uaa->product != USB_PRODUCT_CYPRESS_FMRADIO)
123 		return (UMATCH_NONE);
124 	return (UMATCH_VENDOR_PRODUCT);
125 }
126 
127 void
128 udsbr_attach(struct device *parent, struct device *self, void *aux)
129 {
130 	struct udsbr_softc	*sc = (struct udsbr_softc *)self;
131 	struct usb_attach_arg	*uaa = aux;
132 	struct usbd_device	*dev = uaa->device;
133 	usbd_status		err;
134 
135 	DPRINTFN(10,("udsbr_attach: sc=%p\n", sc));
136 
137 	err = usbd_set_config_no(dev, UDSBR_CONFIG_NO, 1);
138 	if (err) {
139 		printf("%s: setting config no failed\n",
140 		    sc->sc_dev.dv_xname);
141 		return;
142 	}
143 
144 	sc->sc_udev = dev;
145 
146 	DPRINTFN(10, ("udsbr_attach: %p\n", sc->sc_udev));
147 
148 	sc->sc_child = radio_attach_mi(&udsbr_hw_if, sc, &sc->sc_dev);
149 }
150 
151 int
152 udsbr_detach(struct device *self, int flags)
153 {
154 	struct udsbr_softc *sc = (struct udsbr_softc *)self;
155 	int rv = 0;
156 
157 	if (sc->sc_child != NULL)
158 		rv = config_detach(sc->sc_child, flags);
159 
160 	return (rv);
161 }
162 
163 int
164 udsbr_activate(struct device *self, int act)
165 {
166 	struct udsbr_softc *sc = (struct udsbr_softc *)self;
167 	int rv = 0;
168 
169 	switch (act) {
170 	case DVACT_DEACTIVATE:
171 		if (sc->sc_child != NULL)
172 			rv = config_deactivate(sc->sc_child);
173 		break;
174 	}
175 	return (rv);
176 }
177 
178 int
179 udsbr_req(struct udsbr_softc *sc, int ureq, int value, int index)
180 {
181 	usb_device_request_t req;
182 	usbd_status err;
183 	u_char data;
184 
185 	DPRINTFN(1,("udsbr_req: ureq=0x%02x value=0x%04x index=0x%04x\n",
186 		    ureq, value, index));
187 	req.bmRequestType = UT_READ_VENDOR_DEVICE;
188 	req.bRequest = ureq;
189 	USETW(req.wValue, value);
190 	USETW(req.wIndex, index);
191 	USETW(req.wLength, 1);
192 	err = usbd_do_request(sc->sc_udev, &req, &data);
193 	if (err) {
194 		printf("%s: request failed err=%d\n", sc->sc_dev.dv_xname,
195 		       err);
196 	}
197 	return !(data & 1);
198 }
199 
200 void
201 udsbr_start(struct udsbr_softc *sc)
202 {
203 	(void)udsbr_req(sc, 0x00, 0x0000, 0x00c7);
204 	(void)udsbr_req(sc, 0x02, 0x0001, 0x0000);
205 }
206 
207 void
208 udsbr_stop(struct udsbr_softc *sc)
209 {
210 	(void)udsbr_req(sc, 0x00, 0x0016, 0x001c);
211 	(void)udsbr_req(sc, 0x02, 0x0000, 0x0000);
212 }
213 
214 void
215 udsbr_setfreq(struct udsbr_softc *sc, int freq)
216 {
217 	DPRINTF(("udsbr_setfreq: setfreq=%d\n", freq));
218         /*
219          * Freq now is in Hz.  We need to convert it to the frequency
220          * that the radio wants.  This frequency is 10.7MHz above
221          * the actual frequency.  We then need to convert to
222          * units of 12.5kHz.  We add one to the IFM to make rounding
223          * easier.
224          */
225         freq = (freq * 1000 + 10700001) / 12500;
226 	(void)udsbr_req(sc, 0x01, (freq >> 8) & 0xff, freq & 0xff);
227 	(void)udsbr_req(sc, 0x00, 0x0096, 0x00b7);
228 	usbd_delay_ms(sc->sc_udev, 240); /* wait for signal to settle */
229 }
230 
231 int
232 udsbr_status(struct udsbr_softc *sc)
233 {
234 	return (udsbr_req(sc, 0x00, 0x0000, 0x0024));
235 }
236 
237 
238 int
239 udsbr_get_info(void *v, struct radio_info *ri)
240 {
241 	struct udsbr_softc *sc = v;
242 
243 	ri->mute = sc->sc_mute;
244 	ri->volume = sc->sc_vol ? 255 : 0;
245 	ri->caps = RADIO_CAPS_DETECT_STEREO;
246 	ri->rfreq = 0;
247 	ri->lock = 0;
248 	ri->freq = sc->sc_freq;
249 	ri->info = udsbr_status(sc) ? RADIO_INFO_STEREO : 0;
250 
251 	return (0);
252 }
253 
254 int
255 udsbr_set_info(void *v, struct radio_info *ri)
256 {
257 	struct udsbr_softc *sc = v;
258 
259 	sc->sc_mute = ri->mute != 0;
260 	sc->sc_vol = ri->volume != 0;
261 	sc->sc_freq = ri->freq;
262 	udsbr_setfreq(sc, sc->sc_freq);
263 
264 	if (sc->sc_mute || sc->sc_vol == 0)
265 		udsbr_stop(sc);
266 	else
267 		udsbr_start(sc);
268 
269 	return (0);
270 }
271