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