Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[media] radio-tea5777: Add support for tuning AM

This completes my work on the Griffin radioSHARK2 driver, let me use this
opportunity to thank Hisaaki Shibata for his generous donation of a
Griffin radioSHARK2 to me, which has made this driver possible.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>

authored by

Hans de Goede and committed by
Mauro Carvalho Chehab
d7aab0bf 4fad5c47

+157 -45
+155 -45
drivers/media/radio/radio-tea5777.c
··· 39 39 MODULE_DESCRIPTION("Routines for control of TEA5777 Philips AM/FM radio tuner chips"); 40 40 MODULE_LICENSE("GPL"); 41 41 42 - /* Fixed FM only band for now, will implement multi-band support when the 43 - VIDIOC_ENUM_FREQ_BANDS API is upstream */ 44 - #define TEA5777_FM_RANGELOW (76000 * 16) 45 - #define TEA5777_FM_RANGEHIGH (108000 * 16) 46 - 47 42 #define TEA5777_FM_IF 150 /* kHz */ 48 - #define TEA5777_FM_FREQ_STEP 50 /* kHz */ 43 + #define TEA5777_FM_FREQ_STEP 50 /* kHz */ 44 + 45 + #define TEA5777_AM_IF 21 /* kHz */ 46 + #define TEA5777_AM_FREQ_STEP 1 /* kHz */ 49 47 50 48 /* Write reg, common bits */ 51 49 #define TEA5777_W_MUTE_MASK (1LL << 47) ··· 92 94 #define TEA5777_W_FM_PLL_SHIFT 32 93 95 #define TEA5777_W_FM_FREF_MASK (0x03LL << 30) 94 96 #define TEA5777_W_FM_FREF_SHIFT 30 95 - #define TEA5777_W_FM_FREF_VALUE 0 /* 50 kHz tune steps, 150 kHz IF */ 97 + #define TEA5777_W_FM_FREF_VALUE 0LL /* 50k steps, 150k IF */ 96 98 97 99 #define TEA5777_W_FM_FORCEMONO_MASK (1LL << 15) 98 100 #define TEA5777_W_FM_FORCEMONO_SHIFT 15 ··· 113 115 #define TEA5777_W_AM_AGCIF_SHIFT 32 114 116 #define TEA5777_W_AM_MWLW_MASK (1LL << 31) 115 117 #define TEA5777_W_AM_MWLW_SHIFT 31 118 + #define TEA5777_W_AM_LW 0LL 119 + #define TEA5777_W_AM_MW 1LL 116 120 #define TEA5777_W_AM_LNA_MASK (1LL << 30) 117 121 #define TEA5777_W_AM_LNA_SHIFT 30 118 122 ··· 147 147 #define TEA5777_R_FM_PLL_MASK 0x1fff 148 148 #define TEA5777_R_FM_PLL_SHIFT 0 149 149 150 + enum { BAND_FM, BAND_AM }; 151 + 152 + static const struct v4l2_frequency_band bands[] = { 153 + { 154 + .type = V4L2_TUNER_RADIO, 155 + .index = 0, 156 + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 157 + V4L2_TUNER_CAP_FREQ_BANDS | 158 + V4L2_TUNER_CAP_HWSEEK_BOUNDED | 159 + V4L2_TUNER_CAP_HWSEEK_PROG_LIM, 160 + .rangelow = 76000 * 16, 161 + .rangehigh = 108000 * 16, 162 + .modulation = V4L2_BAND_MODULATION_FM, 163 + }, 164 + { 165 + .type = V4L2_TUNER_RADIO, 166 + .index = 1, 167 + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS | 168 + V4L2_TUNER_CAP_HWSEEK_BOUNDED | 169 + V4L2_TUNER_CAP_HWSEEK_PROG_LIM, 170 + .rangelow = 530 * 16, 171 + .rangehigh = 1710 * 16, 172 + .modulation = V4L2_BAND_MODULATION_AM, 173 + }, 174 + }; 175 + 150 176 static u32 tea5777_freq_to_v4l2_freq(struct radio_tea5777 *tea, u32 freq) 151 177 { 152 - return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16; 178 + switch (tea->band) { 179 + case BAND_FM: 180 + return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16; 181 + case BAND_AM: 182 + return (freq * TEA5777_AM_FREQ_STEP + TEA5777_AM_IF) * 16; 183 + } 184 + return 0; /* Never reached */ 153 185 } 154 186 155 187 static int radio_tea5777_set_freq(struct radio_tea5777 *tea) ··· 189 157 u32 freq; 190 158 int res; 191 159 192 - freq = clamp_t(u32, tea->freq, 193 - TEA5777_FM_RANGELOW, TEA5777_FM_RANGEHIGH); 160 + freq = clamp(tea->freq, bands[tea->band].rangelow, 161 + bands[tea->band].rangehigh); 194 162 freq = (freq + 8) / 16; /* to kHz */ 195 163 196 - freq = (freq - TEA5777_FM_IF) / TEA5777_FM_FREQ_STEP; 197 - 198 - tea->write_reg &= ~(TEA5777_W_FM_PLL_MASK | TEA5777_W_FM_FREF_MASK); 199 - tea->write_reg |= (u64)freq << TEA5777_W_FM_PLL_SHIFT; 200 - tea->write_reg |= TEA5777_W_FM_FREF_VALUE << TEA5777_W_FM_FREF_SHIFT; 164 + switch (tea->band) { 165 + case BAND_FM: 166 + tea->write_reg &= ~TEA5777_W_AM_FM_MASK; 167 + freq = (freq - TEA5777_FM_IF) / TEA5777_FM_FREQ_STEP; 168 + tea->write_reg &= ~TEA5777_W_FM_PLL_MASK; 169 + tea->write_reg |= (u64)freq << TEA5777_W_FM_PLL_SHIFT; 170 + tea->write_reg &= ~TEA5777_W_FM_FREF_MASK; 171 + tea->write_reg |= TEA5777_W_FM_FREF_VALUE << 172 + TEA5777_W_FM_FREF_SHIFT; 173 + tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK; 174 + if (tea->audmode == V4L2_TUNER_MODE_MONO) 175 + tea->write_reg |= 1LL << TEA5777_W_FM_FORCEMONO_SHIFT; 176 + break; 177 + case BAND_AM: 178 + tea->write_reg &= ~TEA5777_W_AM_FM_MASK; 179 + tea->write_reg |= (1LL << TEA5777_W_AM_FM_SHIFT); 180 + freq = (freq - TEA5777_AM_IF) / TEA5777_AM_FREQ_STEP; 181 + tea->write_reg &= ~TEA5777_W_AM_PLL_MASK; 182 + tea->write_reg |= (u64)freq << TEA5777_W_AM_PLL_SHIFT; 183 + tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK; 184 + tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK; 185 + tea->write_reg &= ~TEA5777_W_AM_MWLW_MASK; 186 + tea->write_reg |= TEA5777_W_AM_MW << TEA5777_W_AM_MWLW_SHIFT; 187 + tea->write_reg &= ~TEA5777_W_AM_LNA_MASK; 188 + tea->write_reg |= 1LL << TEA5777_W_AM_LNA_SHIFT; 189 + tea->write_reg &= ~TEA5777_W_AM_PEAK_MASK; 190 + tea->write_reg |= 1LL << TEA5777_W_AM_PEAK_SHIFT; 191 + tea->write_reg &= ~TEA5777_W_AM_CALLIGN_MASK; 192 + break; 193 + } 201 194 202 195 res = tea->ops->write_reg(tea, tea->write_reg); 203 196 if (res) ··· 280 223 return 0; 281 224 } 282 225 226 + static int vidioc_enum_freq_bands(struct file *file, void *priv, 227 + struct v4l2_frequency_band *band) 228 + { 229 + struct radio_tea5777 *tea = video_drvdata(file); 230 + 231 + if (band->tuner != 0 || band->index >= ARRAY_SIZE(bands) || 232 + (!tea->has_am && band->index == BAND_AM)) 233 + return -EINVAL; 234 + 235 + *band = bands[band->index]; 236 + return 0; 237 + } 238 + 283 239 static int vidioc_g_tuner(struct file *file, void *priv, 284 240 struct v4l2_tuner *v) 285 241 { ··· 313 243 strlcpy(v->name, "FM", sizeof(v->name)); 314 244 v->type = V4L2_TUNER_RADIO; 315 245 v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 316 - V4L2_TUNER_CAP_HWSEEK_BOUNDED; 317 - v->rangelow = TEA5777_FM_RANGELOW; 318 - v->rangehigh = TEA5777_FM_RANGEHIGH; 319 - v->rxsubchans = (tea->read_reg & TEA5777_R_FM_STEREO_MASK) ? 320 - V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; 321 - v->audmode = (tea->write_reg & TEA5777_W_FM_FORCEMONO_MASK) ? 322 - V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; 246 + V4L2_TUNER_CAP_FREQ_BANDS | 247 + V4L2_TUNER_CAP_HWSEEK_BOUNDED | 248 + V4L2_TUNER_CAP_HWSEEK_PROG_LIM; 249 + v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : 250 + bands[BAND_FM].rangelow; 251 + v->rangehigh = bands[BAND_FM].rangehigh; 252 + if (tea->band == BAND_FM && 253 + (tea->read_reg & TEA5777_R_FM_STEREO_MASK)) 254 + v->rxsubchans = V4L2_TUNER_SUB_STEREO; 255 + else 256 + v->rxsubchans = V4L2_TUNER_SUB_MONO; 257 + v->audmode = tea->audmode; 323 258 /* shift - 12 to convert 4-bits (0-15) scale to 16-bits (0-65535) */ 324 259 v->signal = (tea->read_reg & TEA5777_R_LEVEL_MASK) >> 325 260 (TEA5777_R_LEVEL_SHIFT - 12); ··· 339 264 struct v4l2_tuner *v) 340 265 { 341 266 struct radio_tea5777 *tea = video_drvdata(file); 267 + u32 orig_audmode = tea->audmode; 342 268 343 269 if (v->index) 344 270 return -EINVAL; 345 271 346 - if (v->audmode == V4L2_TUNER_MODE_MONO) 347 - tea->write_reg |= TEA5777_W_FM_FORCEMONO_MASK; 348 - else 349 - tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK; 272 + if (v->audmode > V4L2_TUNER_MODE_STEREO) 273 + v->audmode = V4L2_TUNER_MODE_STEREO; 350 274 351 - return radio_tea5777_set_freq(tea); 275 + tea->audmode = v->audmode; 276 + 277 + if (tea->audmode != orig_audmode && tea->band == BAND_FM) 278 + return radio_tea5777_set_freq(tea); 279 + 280 + return 0; 352 281 } 353 282 354 283 static int vidioc_g_frequency(struct file *file, void *priv, ··· 375 296 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 376 297 return -EINVAL; 377 298 299 + if (tea->has_am && f->frequency < (20000 * 16)) 300 + tea->band = BAND_AM; 301 + else 302 + tea->band = BAND_FM; 303 + 378 304 tea->freq = f->frequency; 379 305 return radio_tea5777_set_freq(tea); 380 306 } ··· 388 304 struct v4l2_hw_freq_seek *a) 389 305 { 390 306 struct radio_tea5777 *tea = video_drvdata(file); 391 - u32 orig_freq = tea->freq; 392 307 unsigned long timeout; 393 - int res, spacing = 200 * 16; /* 200 kHz */ 394 - /* These are fixed *for now* */ 395 - const u32 seek_rangelow = TEA5777_FM_RANGELOW; 396 - const u32 seek_rangehigh = TEA5777_FM_RANGEHIGH; 308 + int i, res, spacing; 309 + u32 orig_freq; 397 310 398 311 if (a->tuner || a->wrap_around) 399 312 return -EINVAL; 400 313 401 - tea->write_reg |= TEA5777_W_PROGBLIM_MASK; 402 - if (seek_rangelow != tea->seek_rangelow) { 403 - tea->write_reg &= ~TEA5777_W_UPDWN_MASK; 404 - tea->freq = seek_rangelow; 405 - res = radio_tea5777_set_freq(tea); 406 - if (res) 407 - goto leave; 408 - tea->seek_rangelow = tea->freq; 314 + if (a->rangelow || a->rangehigh) { 315 + for (i = 0; i < ARRAY_SIZE(bands); i++) { 316 + if (i == BAND_AM && !tea->has_am) 317 + continue; 318 + if (bands[i].rangelow >= a->rangelow && 319 + bands[i].rangehigh <= a->rangehigh) 320 + break; 321 + } 322 + if (i == ARRAY_SIZE(bands)) 323 + return -EINVAL; /* No matching band found */ 324 + 325 + tea->band = i; 326 + if (tea->freq < a->rangelow || tea->freq > a->rangehigh) { 327 + tea->freq = clamp(tea->freq, a->rangelow, 328 + a->rangehigh); 329 + res = radio_tea5777_set_freq(tea); 330 + if (res) 331 + return res; 332 + } 333 + } else { 334 + a->rangelow = bands[tea->band].rangelow; 335 + a->rangehigh = bands[tea->band].rangehigh; 409 336 } 410 - if (seek_rangehigh != tea->seek_rangehigh) { 411 - tea->write_reg |= TEA5777_W_UPDWN_MASK; 412 - tea->freq = seek_rangehigh; 337 + 338 + spacing = (tea->band == BAND_AM) ? (5 * 16) : (200 * 16); /* kHz */ 339 + orig_freq = tea->freq; 340 + 341 + tea->write_reg |= TEA5777_W_PROGBLIM_MASK; 342 + if (tea->seek_rangelow != a->rangelow) { 343 + tea->write_reg &= ~TEA5777_W_UPDWN_MASK; 344 + tea->freq = a->rangelow; 413 345 res = radio_tea5777_set_freq(tea); 414 346 if (res) 415 347 goto leave; 416 - tea->seek_rangehigh = tea->freq; 348 + tea->seek_rangelow = a->rangelow; 349 + } 350 + if (tea->seek_rangehigh != a->rangehigh) { 351 + tea->write_reg |= TEA5777_W_UPDWN_MASK; 352 + tea->freq = a->rangehigh; 353 + res = radio_tea5777_set_freq(tea); 354 + if (res) 355 + goto leave; 356 + tea->seek_rangehigh = a->rangehigh; 417 357 } 418 358 tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK; 419 359 ··· 525 417 .vidioc_g_frequency = vidioc_g_frequency, 526 418 .vidioc_s_frequency = vidioc_s_frequency, 527 419 .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, 420 + .vidioc_enum_freq_bands = vidioc_enum_freq_bands, 528 421 .vidioc_log_status = v4l2_ctrl_log_status, 529 422 .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 530 423 .vidioc_unsubscribe_event = v4l2_event_unsubscribe, ··· 548 439 (1LL << TEA5777_W_IFW_SHIFT) | 549 440 (1LL << TEA5777_W_INTEXT_SHIFT) | 550 441 (1LL << TEA5777_W_CHP0_SHIFT) | 551 - (2LL << TEA5777_W_SLEV_SHIFT); 442 + (1LL << TEA5777_W_SLEV_SHIFT); 552 443 tea->freq = 90500 * 16; /* 90.5Mhz default */ 444 + tea->audmode = V4L2_TUNER_MODE_STEREO; 553 445 res = radio_tea5777_set_freq(tea); 554 446 if (res) { 555 447 v4l2_err(tea->v4l2_dev, "can't set initial freq (%d)\n", res);
+2
drivers/media/radio/radio-tea5777.h
··· 68 68 bool has_am; /* Device can tune to AM freqs */ 69 69 bool write_before_read; /* must write before read quirk */ 70 70 bool needs_write; /* for write before read quirk */ 71 + u32 band; /* current band */ 71 72 u32 freq; /* current frequency */ 73 + u32 audmode; /* last set audmode */ 72 74 u32 seek_rangelow; /* current hwseek limits */ 73 75 u32 seek_rangehigh; 74 76 u32 read_reg;