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