1 /* $OpenBSD: aztech.c,v 1.4 2002/01/23 20:30:38 mickey Exp $ */
2 /* $RuOBSD: aztech.c,v 1.11 2001/10/20 13:23:47 pva Exp $ */
3
4 /*
5 * Copyright (c) 2001 Maxim Tsyplakov <tm@oganer.net>,
6 * Vladimir Popov <jumbo@narod.ru>
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 /* Aztech/PackardBell FM Radio Card device driver */
31
32 /*
33 * Sanyo LM7001J Direct PLL Frequency Synthesizer:
34 * ??? See http://www.redsword.com/tjacobs/geeb/fmcard.htm
35 *
36 * Philips TEA5712T AM/FM Stereo DTS Radio:
37 * http://www.semiconductors.philips.com/pip/TEA5712
38 */
39
40 #include <sys/param.h>
41 #include <sys/systm.h>
42 #include <sys/proc.h>
43 #include <sys/errno.h>
44 #include <sys/ioctl.h>
45 #include <sys/device.h>
46 #include <sys/radioio.h>
47
48 #include <machine/bus.h>
49
50 #include <dev/isa/isavar.h>
51 #include <dev/ic/lm700x.h>
52 #include <dev/radio_if.h>
53
54 #define RF_25K 25
55 #define RF_50K 50
56 #define RF_100K 100
57
58 #define MAX_VOL 3
59 #define VOLUME_RATIO(x) (255 * x / MAX_VOL)
60
61 #define AZ_BASE_VALID(x) ((x == 0x350) || (x == 0x358))
62 #define AZTECH_CAPABILITIES RADIO_CAPS_DETECT_STEREO | \
63 RADIO_CAPS_DETECT_SIGNAL | \
64 RADIO_CAPS_SET_MONO | \
65 RADIO_CAPS_REFERENCE_FREQ
66
67
68 #define AZTECH_STEREO (1 << 0)
69 #define AZTECH_SIGNAL (1 << 1)
70
71 #define AZ_WREN_ON (1 << 1)
72 #define AZ_WREN_OFF (0 << 1)
73
74 #define AZ_CLCK_ON (1 << 6)
75 #define AZ_CLCK_OFF (0 << 6)
76
77 #define AZ_DATA_ON (1 << 7)
78 #define AZ_DATA_OFF (0 << 7)
79
80 int az_probe(struct device *, void *, void *);
81 void az_attach(struct device *, struct device * self, void *);
82
83 int az_get_info(void *, struct radio_info *);
84 int az_set_info(void *, struct radio_info *);
85
86 struct radio_hw_if az_hw_if = {
87 NULL, /* open */
88 NULL, /* close */
89 az_get_info,
90 az_set_info,
91 NULL
92 };
93
94 struct az_softc {
95 struct device sc_dev;
96
97 int mute;
98 u_int8_t vol;
99 u_int32_t freq;
100 u_int32_t rf;
101 u_int32_t stereo;
102
103 struct lm700x_t lm;
104 };
105
106 struct cfattach az_ca = {
107 sizeof(struct az_softc), az_probe, az_attach
108 };
109
110 struct cfdriver az_cd = {
111 NULL, "az", DV_DULL
112 };
113
114 u_int az_find(bus_space_tag_t, bus_space_handle_t);
115 void az_set_mute(struct az_softc *);
116 void az_set_freq(struct az_softc *, u_int32_t);
117 u_int8_t az_state(bus_space_tag_t, bus_space_handle_t);
118
119 void az_lm700x_init(bus_space_tag_t, bus_space_handle_t, bus_size_t, u_int32_t);
120 void az_lm700x_rset(bus_space_tag_t, bus_space_handle_t, bus_size_t, u_int32_t);
121
122 u_int8_t az_conv_vol(u_int8_t);
123 u_int8_t az_unconv_vol(u_int8_t);
124
125 int
az_probe(struct device * parent,void * self,void * aux)126 az_probe(struct device *parent, void *self, void *aux)
127 {
128 struct isa_attach_args *ia = aux;
129 bus_space_tag_t iot = ia->ia_iot;
130 bus_space_handle_t ioh;
131
132 int iosize = 1, iobase = ia->ia_iobase;
133
134 if (!AZ_BASE_VALID(iobase)) {
135 printf("az: configured iobase 0x%x invalid\n", iobase);
136 return (0);
137 }
138
139 if (bus_space_map(iot, iobase, iosize, 0, &ioh))
140 return (0);
141
142 if (!az_find(iot, ioh)) {
143 bus_space_unmap(iot, ioh, iosize);
144 return (0);
145 }
146
147 bus_space_unmap(iot, ioh, iosize);
148 ia->ia_iosize = iosize;
149 return (1);
150 }
151
152 void
az_attach(struct device * parent,struct device * self,void * aux)153 az_attach(struct device *parent, struct device *self, void *aux)
154 {
155 struct az_softc *sc = (void *) self;
156 struct isa_attach_args *ia = aux;
157
158 sc->lm.iot = ia->ia_iot;
159 sc->rf = LM700X_REF_050;
160 sc->stereo = LM700X_STEREO;
161 sc->mute = 0;
162 sc->freq = MIN_FM_FREQ;
163 sc->vol = 0;
164
165 /* remap I/O */
166 if (bus_space_map(sc->lm.iot, ia->ia_iobase, ia->ia_iosize,
167 0, &sc->lm.ioh)) {
168 printf(": bus_space_map() failed\n");
169 return;
170 }
171
172 printf(": Aztech/PackardBell\n");
173
174 /* Configure struct lm700x_t lm */
175 sc->lm.offset = 0;
176 sc->lm.wzcl = AZ_WREN_ON | AZ_CLCK_OFF | AZ_DATA_OFF;
177 sc->lm.wzch = AZ_WREN_ON | AZ_CLCK_ON | AZ_DATA_OFF;
178 sc->lm.wocl = AZ_WREN_ON | AZ_CLCK_OFF | AZ_DATA_ON;
179 sc->lm.woch = AZ_WREN_ON | AZ_CLCK_ON | AZ_DATA_ON;
180 sc->lm.initdata = 0;
181 sc->lm.rsetdata = AZ_DATA_ON | AZ_CLCK_ON | AZ_WREN_OFF;
182 sc->lm.init = az_lm700x_init;
183 sc->lm.rset = az_lm700x_rset;
184
185 az_set_freq(sc, sc->freq);
186
187 radio_attach_mi(&az_hw_if, sc, &sc->sc_dev);
188 }
189
190 /*
191 * Mute the card
192 */
193 void
az_set_mute(struct az_softc * sc)194 az_set_mute(struct az_softc *sc)
195 {
196 bus_space_write_1(sc->lm.iot, sc->lm.ioh, 0,
197 sc->mute ? 0 : sc->vol);
198 DELAY(6);
199 bus_space_write_1(sc->lm.iot, sc->lm.ioh, 0,
200 sc->mute ? 0 : sc->vol);
201 }
202
203 void
az_set_freq(struct az_softc * sc,u_int32_t nfreq)204 az_set_freq(struct az_softc *sc, u_int32_t nfreq)
205 {
206 u_int8_t vol;
207 u_int32_t reg;
208
209 vol = sc->mute ? 0 : sc->vol;
210
211 if (nfreq > MAX_FM_FREQ)
212 nfreq = MAX_FM_FREQ;
213 if (nfreq < MIN_FM_FREQ)
214 nfreq = MIN_FM_FREQ;
215
216 sc->freq = nfreq;
217
218 reg = lm700x_encode_freq(nfreq, sc->rf);
219 reg |= sc->stereo | sc->rf | LM700X_DIVIDER_FM;
220
221 lm700x_hardware_write(&sc->lm, reg, vol);
222
223 az_set_mute(sc);
224 }
225
226 /*
227 * Return state of the card - tuned/not tuned, mono/stereo
228 */
229 u_int8_t
az_state(bus_space_tag_t iot,bus_space_handle_t ioh)230 az_state(bus_space_tag_t iot, bus_space_handle_t ioh)
231 {
232 u_int8_t info = 0, portdata;
233
234 portdata = bus_space_read_1(iot, ioh, 0);
235
236 info |= portdata & AZTECH_STEREO ? 0 : RADIO_INFO_STEREO;
237 info |= portdata & AZTECH_SIGNAL ? 0 : RADIO_INFO_SIGNAL;
238
239 return info;
240 }
241
242 /*
243 * Convert volume to hardware representation.
244 * The card uses bits 00000x0x to set volume.
245 */
246 u_int8_t
az_conv_vol(u_int8_t vol)247 az_conv_vol(u_int8_t vol)
248 {
249 if (vol < VOLUME_RATIO(1))
250 return 0;
251 else if (vol >= VOLUME_RATIO(1) && vol < VOLUME_RATIO(2))
252 return 1;
253 else if (vol >= VOLUME_RATIO(2) && vol < VOLUME_RATIO(3))
254 return 4;
255 else
256 return 5;
257 }
258
259 /*
260 * Convert volume from hardware representation
261 */
262 u_int8_t
az_unconv_vol(u_int8_t vol)263 az_unconv_vol(u_int8_t vol)
264 {
265 switch (vol) {
266 case 0:
267 return VOLUME_RATIO(0);
268 case 1:
269 return VOLUME_RATIO(1);
270 case 4:
271 return VOLUME_RATIO(2);
272 }
273 return VOLUME_RATIO(3);
274 }
275
276 u_int
az_find(bus_space_tag_t iot,bus_space_handle_t ioh)277 az_find(bus_space_tag_t iot, bus_space_handle_t ioh)
278 {
279 struct az_softc sc;
280 u_int i;
281
282 sc.lm.iot = iot;
283 sc.lm.ioh = ioh;
284 sc.lm.offset = 0;
285 sc.lm.wzcl = AZ_WREN_ON | AZ_CLCK_OFF | AZ_DATA_OFF;
286 sc.lm.wzch = AZ_WREN_ON | AZ_CLCK_ON | AZ_DATA_OFF;
287 sc.lm.wocl = AZ_WREN_ON | AZ_CLCK_OFF | AZ_DATA_ON;
288 sc.lm.woch = AZ_WREN_ON | AZ_CLCK_ON | AZ_DATA_ON;
289 sc.lm.initdata = 0;
290 sc.lm.rsetdata = AZ_DATA_ON | AZ_CLCK_ON | AZ_WREN_OFF;
291 sc.lm.init = az_lm700x_init;
292 sc.lm.rset = az_lm700x_rset;
293 sc.rf = LM700X_REF_050;
294 sc.mute = 0;
295 sc.stereo = LM700X_STEREO;
296 sc.vol = 0;
297
298 /*
299 * Scan whole FM range. If there is a card it'll
300 * respond on some frequency.
301 */
302 for (i = MIN_FM_FREQ; i < MAX_FM_FREQ; i += 10) {
303 az_set_freq(&sc, i);
304 if (az_state(iot, ioh))
305 return 1;
306 }
307
308 return 0;
309 }
310
311 void
az_lm700x_init(bus_space_tag_t iot,bus_space_handle_t ioh,bus_size_t off,u_int32_t data)312 az_lm700x_init(bus_space_tag_t iot, bus_space_handle_t ioh, bus_size_t off,
313 u_int32_t data)
314 {
315 /* Do nothing */
316 return;
317 }
318
319 void
az_lm700x_rset(bus_space_tag_t iot,bus_space_handle_t ioh,bus_size_t off,u_int32_t data)320 az_lm700x_rset(bus_space_tag_t iot, bus_space_handle_t ioh, bus_size_t off,
321 u_int32_t data)
322 {
323 bus_space_write_1(iot, ioh, off, data);
324 }
325
326 int
az_get_info(void * v,struct radio_info * ri)327 az_get_info(void *v, struct radio_info *ri)
328 {
329 struct az_softc *sc = v;
330
331 ri->mute = sc->mute;
332 ri->volume = az_unconv_vol(sc->vol);
333 ri->stereo = sc->stereo == LM700X_STEREO ? 1 : 0;
334 ri->caps = AZTECH_CAPABILITIES;
335 ri->rfreq = lm700x_decode_ref(sc->rf);
336 ri->info = az_state(sc->lm.iot, sc->lm.ioh);
337 ri->freq = sc->freq;
338
339 /* UNSUPPORTED */
340 ri->lock = 0;
341
342 return (0);
343 }
344
345 int
az_set_info(void * v,struct radio_info * ri)346 az_set_info(void *v, struct radio_info *ri)
347 {
348 struct az_softc *sc = v;
349
350 sc->mute = ri->mute ? 1 : 0;
351 sc->vol = az_conv_vol(ri->volume);
352 sc->stereo = ri->stereo ? LM700X_STEREO : LM700X_MONO;
353 sc->rf = lm700x_encode_ref(ri->rfreq);
354
355 az_set_freq(sc, ri->freq);
356 az_set_mute(sc);
357
358 return (0);
359 }
360