1 /* $NetBSD: tea5767.c,v 1.2 2019/12/23 20:49:09 thorpej Exp $ */ 2 /*- 3 * Copyright (c) 2018 The NetBSD Foundation, Inc. 4 * All rights reserved. 5 * 6 * This code is derived from software contributed to The NetBSD Foundation 7 * by Karuna Grewal. 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS 19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 22 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include <sys/cdefs.h> 32 __KERNEL_RCSID(0, "$NetBSD: tea5767.c,v 1.2 2019/12/23 20:49:09 thorpej Exp $"); 33 34 #include <sys/proc.h> 35 #include <sys/kernel.h> 36 #include <sys/param.h> 37 #include <sys/device.h> 38 #include <sys/errno.h> 39 #include <sys/systm.h> 40 41 #include <dev/i2c/i2cvar.h> 42 43 #include <sys/ioctl.h> 44 #include <sys/radioio.h> 45 #include <dev/radio_if.h> 46 47 #include "tea5767reg.h" 48 49 struct tea5767_tune { 50 int mute; 51 uint32_t freq; 52 int stereo; 53 bool is_pll_set; 54 bool is_xtal_set; 55 bool is_force_srch; 56 int fm_band; /* Set = JAPAN. */ 57 int lock; 58 int adc_stop_level; 59 }; 60 61 struct tea5767_softc { 62 device_t sc_dev; 63 /* Tunable properties. */ 64 struct tea5767_tune tune; 65 /* I2C bus controller. */ 66 i2c_tag_t sc_i2c_tag; 67 /* Device addr on I2C. */ 68 i2c_addr_t sc_addr; 69 /* Device capabilities. */ 70 uint32_t caps; 71 }; 72 73 static int tea5767_get_info(void *, struct radio_info *); 74 static int tea5767_set_info(void *, struct radio_info *); 75 static int tea5767_search(void *, int); 76 77 static int tea5767_match(device_t, cfdata_t, void *); 78 static void tea5767_attach(device_t, device_t, void *); 79 80 static const struct radio_hw_if tea5767_hw_if = { 81 NULL, 82 NULL, 83 tea5767_get_info, 84 tea5767_set_info, 85 tea5767_search 86 }; 87 88 static const uint32_t tea5767_common_caps = 89 RADIO_CAPS_DETECT_STEREO | 90 RADIO_CAPS_SET_MONO | 91 RADIO_CAPS_HW_SEARCH | 92 RADIO_CAPS_LOCK_SENSITIVITY; 93 94 CFATTACH_DECL_NEW(tea5767radio, sizeof(struct tea5767_softc), tea5767_match, 95 tea5767_attach, NULL, NULL); 96 97 static int 98 tea5767_match(device_t parent, cfdata_t cf, void *aux) 99 { 100 struct i2c_attach_args *ia = aux; 101 102 if (ia->ia_addr == TEA5767_ADDR) 103 return I2C_MATCH_ADDRESS_ONLY; 104 105 return 0; 106 } 107 108 static void 109 tea5767_attach(device_t parent, device_t self, void *aux) 110 { 111 struct tea5767_softc *sc = device_private(self); 112 int tea5767_flags = device_cfdata(self)->cf_flags; 113 struct i2c_attach_args *ia = aux; 114 115 aprint_normal(": Philips/NXP TEA5767 Radio\n"); 116 aprint_naive(": FM radio\n"); 117 118 sc->sc_dev = self; 119 sc->tune.mute = 0; 120 sc->tune.freq = MIN_FM_FREQ; 121 sc->tune.stereo = 1; 122 sc->tune.is_pll_set = false; 123 sc->tune.is_xtal_set = false; 124 sc->tune.is_force_srch = false; 125 sc->sc_i2c_tag = ia->ia_tag; 126 sc->sc_addr = ia->ia_addr; 127 128 if (tea5767_flags & TEA5767_PLL_FLAG) 129 sc->tune.is_pll_set = true; 130 if (tea5767_flags & TEA5767_XTAL_FLAG) 131 sc->tune.is_xtal_set = true; 132 if (tea5767_flags & TEA5767_JAPAN_FM_FLAG) 133 sc->tune.fm_band = 1; 134 if (tea5767_flags & TEA5767_FORCE_SRCH_FLAG) 135 sc->tune.is_force_srch = true; 136 137 sc->caps = tea5767_common_caps; 138 139 /* 140 * Check the quality of crystal and disable hardware search if 141 * necessary. 142 */ 143 if (!tea5767_if_check(sc) && !(sc->tune.is_force_srch)) 144 sc->caps &= ~RADIO_CAPS_HW_SEARCH; 145 146 radio_attach_mi(&tea5767_hw_if, sc, self); 147 } 148 149 static int 150 tea5767_write(struct tea5767_softc *sc, uint8_t *reg) 151 { 152 int exec_result; 153 154 if (iic_acquire_bus(sc->sc_i2c_tag, 0)) { 155 device_printf(sc->sc_dev, "bus acquiration failed.\n"); 156 return 1; 157 } 158 159 exec_result = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 160 sc->sc_addr, NULL, 0, reg, 5, 0); 161 162 if (exec_result) { 163 iic_release_bus(sc->sc_i2c_tag, 0); 164 device_printf(sc->sc_dev, "write operation failed %d.\n", 165 exec_result); 166 return 1; 167 } 168 169 iic_release_bus(sc->sc_i2c_tag, 0); 170 171 return 0; 172 } 173 174 175 static int 176 tea5767_read(struct tea5767_softc *sc, uint8_t *reg) 177 { 178 int exec_result; 179 180 if (iic_acquire_bus(sc->sc_i2c_tag, 0)) { 181 device_printf(sc->sc_dev, "bus acquiration failed.\n"); 182 return 1; 183 } 184 185 exec_result = iic_exec(sc->sc_i2c_tag, I2C_OP_READ_WITH_STOP, 186 sc->sc_addr, NULL, 0, reg, 5, 0); 187 188 if (exec_result) { 189 iic_release_bus(sc->sc_i2c_tag, 0); 190 device_printf(sc->sc_dev, "read operation failed.\n"); 191 return 1; 192 } 193 194 iic_release_bus(sc->sc_i2c_tag, 0); 195 return 0; 196 } 197 198 /* 199 * Calculate the PLL word. 200 * TODO: Extend this calculation to do high side injection as well 201 * (add fields to tea5767_tune). 202 */ 203 static void 204 tea5767_freq_to_pll(struct tea5767_tune tune, uint8_t *buf) 205 { 206 uint16_t pll_word = 0; 207 208 if (!tune.is_pll_set && !tune.is_xtal_set) 209 pll_word = 4 * (tune.freq - 225) * 1000 / 50000; 210 else if (!tune.is_pll_set && tune.is_xtal_set) { 211 pll_word = 4 * (tune.freq - 225) * 1000 / 32768; 212 buf[3] = TEA5767_XTAL; 213 } 214 else { 215 pll_word = 4 * (tune.freq - 225) * 1000 / 50000; 216 buf[4] |= TEA5767_PLLREF; 217 } 218 219 buf[1] = pll_word & 0xff; 220 buf[0] = (pll_word>>8) & 0x3f; 221 } 222 223 static void 224 tea5767_set_properties(struct tea5767_softc *sc, uint8_t *reg) 225 { 226 memset(reg,0,5); 227 228 tea5767_freq_to_pll(sc->tune, reg); 229 230 if (sc->tune.mute) { 231 reg[0] |= TEA5767_MUTE; 232 reg[2] |=TEA5767_MUTE_R | TEA5767_MUTE_L; 233 } 234 235 reg[3] |= TEA5767_SNC; 236 237 if (sc->tune.stereo == 0) 238 reg[2] |= TEA5767_MONO; 239 if(sc->tune.fm_band) 240 reg[3] |= TEA5767_FM_BAND; 241 } 242 243 static bool 244 tea5767_if_check(struct tea5767_soft *sc) 245 { 246 uint8_t reg[5]; 247 248 tea5767_set_properties(sc, reg); 249 tea5767_write(sc, reg); 250 251 memset(reg,0,5); 252 delay(1000); 253 tea5767_read(sc,reg); 254 255 if ((reg[2] & 0x7f) == 0x37) 256 return true; 257 return false; 258 } 259 260 static void 261 tea5767_pll_to_freq(struct tea5767_tune *tune, uint8_t *buf) 262 { 263 int pll_word = buf[1] | (buf[0] & 0x3f) << 8; 264 if (!tune->is_pll_set && !tune->is_xtal_set) 265 tune->freq = pll_word * 50 / 4 + 225; 266 else if (!tune->is_pll_set && tune->is_xtal_set) 267 tune->freq = pll_word * 32768 / 4000 + 225; 268 else 269 tune->freq = pll_word * 50 / 4 + 225; 270 } 271 272 static int 273 tea5767_get_info(void *v, struct radio_info *ri) 274 { 275 struct tea5767_softc *sc = v; 276 uint8_t reg[5]; 277 tea5767_read(sc, reg); 278 279 tea5767_pll_to_freq(&sc->tune, reg); 280 ri->mute = sc->tune.mute; 281 ri->stereo = sc->tune.stereo; 282 ri->freq = sc->tune.freq; 283 ri->lock = sc->tune.lock; 284 285 ri->caps = sc->caps; 286 287 return 0; 288 } 289 290 static int 291 tea5767_set_info(void *v, struct radio_info *ri) 292 { 293 struct tea5767_softc *sc = v; 294 int adc_conversion; 295 uint8_t reg[5]; 296 297 sc->tune.mute = ri->mute; 298 sc->tune.stereo = ri->stereo; 299 sc->tune.freq = ri->freq; 300 sc->tune.lock = ri->lock; 301 302 adc_conversion = (ri->lock-60) * 3 / 30; 303 304 switch(adc_conversion) { 305 case 0: sc->tune.adc_stop_level = TEA5767_SSL_3; 306 break; 307 case 1: sc->tune.adc_stop_level = TEA5767_SSL_2; 308 break; 309 case 2: sc->tune.adc_stop_level = TEA5767_SSL_1; 310 break; 311 } 312 313 tea5767_set_properties(sc, reg); 314 tea5767_write(sc, reg); 315 return 0; 316 } 317 318 static int 319 tea5767_search(void *v, int dir) 320 { 321 struct tea5767_softc *sc = v; 322 uint8_t reg[5]; 323 324 /* Increment frequency to search for the next frequency. */ 325 sc->tune.freq = sc->tune.freq >= 107500 ? 87500 : sc->tune.freq + 100; 326 tea5767_set_properties(sc, reg); 327 328 /* Activate autonomous search. */ 329 reg[0] |= TEA5767_SEARCH; 330 331 /* 332 * If dir 1 => search up, if dir 0 => search down. 333 */ 334 if (dir) 335 reg[2] |= TEA5767_SUD; 336 337 reg[2] |= sc->tune.adc_stop_level; /* Stop level for search. */ 338 339 tea5767_write(sc, reg); 340 341 memset(reg, 0, 5); 342 343 while (tea5767_read(sc, reg), !(reg[0] & TEA5767_READY_FLAG)) { 344 kpause("teasrch", true, hz/100, NULL); 345 } 346 347 return 0; 348 } 349 350