588 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * v4l2 driver for TEA5777 Philips AM/FM radio tuner chips4 *5 * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com>6 *7 * Based on the ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips:8 *9 * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>10 */11 12#include <linux/delay.h>13#include <linux/init.h>14#include <linux/module.h>15#include <linux/sched.h>16#include <linux/slab.h>17#include <media/v4l2-device.h>18#include <media/v4l2-dev.h>19#include <media/v4l2-fh.h>20#include <media/v4l2-ioctl.h>21#include <media/v4l2-event.h>22#include "radio-tea5777.h"23 24MODULE_AUTHOR("Hans de Goede <perex@perex.cz>");25MODULE_DESCRIPTION("Routines for control of TEA5777 Philips AM/FM radio tuner chips");26MODULE_LICENSE("GPL");27 28#define TEA5777_FM_IF 150 /* kHz */29#define TEA5777_FM_FREQ_STEP 50 /* kHz */30 31#define TEA5777_AM_IF 21 /* kHz */32#define TEA5777_AM_FREQ_STEP 1 /* kHz */33 34/* Write reg, common bits */35#define TEA5777_W_MUTE_MASK (1LL << 47)36#define TEA5777_W_MUTE_SHIFT 4737#define TEA5777_W_AM_FM_MASK (1LL << 46)38#define TEA5777_W_AM_FM_SHIFT 4639#define TEA5777_W_STB_MASK (1LL << 45)40#define TEA5777_W_STB_SHIFT 4541 42#define TEA5777_W_IFCE_MASK (1LL << 29)43#define TEA5777_W_IFCE_SHIFT 2944#define TEA5777_W_IFW_MASK (1LL << 28)45#define TEA5777_W_IFW_SHIFT 2846#define TEA5777_W_HILO_MASK (1LL << 27)47#define TEA5777_W_HILO_SHIFT 2748#define TEA5777_W_DBUS_MASK (1LL << 26)49#define TEA5777_W_DBUS_SHIFT 2650 51#define TEA5777_W_INTEXT_MASK (1LL << 24)52#define TEA5777_W_INTEXT_SHIFT 2453#define TEA5777_W_P1_MASK (1LL << 23)54#define TEA5777_W_P1_SHIFT 2355#define TEA5777_W_P0_MASK (1LL << 22)56#define TEA5777_W_P0_SHIFT 2257#define TEA5777_W_PEN1_MASK (1LL << 21)58#define TEA5777_W_PEN1_SHIFT 2159#define TEA5777_W_PEN0_MASK (1LL << 20)60#define TEA5777_W_PEN0_SHIFT 2061 62#define TEA5777_W_CHP0_MASK (1LL << 18)63#define TEA5777_W_CHP0_SHIFT 1864#define TEA5777_W_DEEM_MASK (1LL << 17)65#define TEA5777_W_DEEM_SHIFT 1766 67#define TEA5777_W_SEARCH_MASK (1LL << 7)68#define TEA5777_W_SEARCH_SHIFT 769#define TEA5777_W_PROGBLIM_MASK (1LL << 6)70#define TEA5777_W_PROGBLIM_SHIFT 671#define TEA5777_W_UPDWN_MASK (1LL << 5)72#define TEA5777_W_UPDWN_SHIFT 573#define TEA5777_W_SLEV_MASK (3LL << 3)74#define TEA5777_W_SLEV_SHIFT 375 76/* Write reg, FM specific bits */77#define TEA5777_W_FM_PLL_MASK (0x1fffLL << 32)78#define TEA5777_W_FM_PLL_SHIFT 3279#define TEA5777_W_FM_FREF_MASK (0x03LL << 30)80#define TEA5777_W_FM_FREF_SHIFT 3081#define TEA5777_W_FM_FREF_VALUE 0LL /* 50k steps, 150k IF */82 83#define TEA5777_W_FM_FORCEMONO_MASK (1LL << 15)84#define TEA5777_W_FM_FORCEMONO_SHIFT 1585#define TEA5777_W_FM_SDSOFF_MASK (1LL << 14)86#define TEA5777_W_FM_SDSOFF_SHIFT 1487#define TEA5777_W_FM_DOFF_MASK (1LL << 13)88#define TEA5777_W_FM_DOFF_SHIFT 1389 90#define TEA5777_W_FM_STEP_MASK (3LL << 1)91#define TEA5777_W_FM_STEP_SHIFT 192 93/* Write reg, AM specific bits */94#define TEA5777_W_AM_PLL_MASK (0x7ffLL << 34)95#define TEA5777_W_AM_PLL_SHIFT 3496#define TEA5777_W_AM_AGCRF_MASK (1LL << 33)97#define TEA5777_W_AM_AGCRF_SHIFT 3398#define TEA5777_W_AM_AGCIF_MASK (1LL << 32)99#define TEA5777_W_AM_AGCIF_SHIFT 32100#define TEA5777_W_AM_MWLW_MASK (1LL << 31)101#define TEA5777_W_AM_MWLW_SHIFT 31102#define TEA5777_W_AM_LW 0LL103#define TEA5777_W_AM_MW 1LL104#define TEA5777_W_AM_LNA_MASK (1LL << 30)105#define TEA5777_W_AM_LNA_SHIFT 30106 107#define TEA5777_W_AM_PEAK_MASK (1LL << 25)108#define TEA5777_W_AM_PEAK_SHIFT 25109 110#define TEA5777_W_AM_RFB_MASK (1LL << 16)111#define TEA5777_W_AM_RFB_SHIFT 16112#define TEA5777_W_AM_CALLIGN_MASK (1LL << 15)113#define TEA5777_W_AM_CALLIGN_SHIFT 15114#define TEA5777_W_AM_CBANK_MASK (0x7fLL << 8)115#define TEA5777_W_AM_CBANK_SHIFT 8116 117#define TEA5777_W_AM_DELAY_MASK (1LL << 2)118#define TEA5777_W_AM_DELAY_SHIFT 2119#define TEA5777_W_AM_STEP_MASK (1LL << 1)120#define TEA5777_W_AM_STEP_SHIFT 1121 122/* Read reg, common bits */123#define TEA5777_R_LEVEL_MASK (0x0f << 17)124#define TEA5777_R_LEVEL_SHIFT 17125#define TEA5777_R_SFOUND_MASK (0x01 << 16)126#define TEA5777_R_SFOUND_SHIFT 16127#define TEA5777_R_BLIM_MASK (0x01 << 15)128#define TEA5777_R_BLIM_SHIFT 15129 130/* Read reg, FM specific bits */131#define TEA5777_R_FM_STEREO_MASK (0x01 << 21)132#define TEA5777_R_FM_STEREO_SHIFT 21133#define TEA5777_R_FM_PLL_MASK 0x1fff134#define TEA5777_R_FM_PLL_SHIFT 0135 136enum { BAND_FM, BAND_AM };137 138static const struct v4l2_frequency_band bands[] = {139 {140 .type = V4L2_TUNER_RADIO,141 .index = 0,142 .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |143 V4L2_TUNER_CAP_FREQ_BANDS |144 V4L2_TUNER_CAP_HWSEEK_BOUNDED |145 V4L2_TUNER_CAP_HWSEEK_PROG_LIM,146 .rangelow = 76000 * 16,147 .rangehigh = 108000 * 16,148 .modulation = V4L2_BAND_MODULATION_FM,149 },150 {151 .type = V4L2_TUNER_RADIO,152 .index = 1,153 .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS |154 V4L2_TUNER_CAP_HWSEEK_BOUNDED |155 V4L2_TUNER_CAP_HWSEEK_PROG_LIM,156 .rangelow = 530 * 16,157 .rangehigh = 1710 * 16,158 .modulation = V4L2_BAND_MODULATION_AM,159 },160};161 162static u32 tea5777_freq_to_v4l2_freq(struct radio_tea5777 *tea, u32 freq)163{164 switch (tea->band) {165 case BAND_FM:166 return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16;167 case BAND_AM:168 return (freq * TEA5777_AM_FREQ_STEP + TEA5777_AM_IF) * 16;169 }170 return 0; /* Never reached */171}172 173int radio_tea5777_set_freq(struct radio_tea5777 *tea)174{175 u32 freq;176 int res;177 178 freq = clamp(tea->freq, bands[tea->band].rangelow,179 bands[tea->band].rangehigh);180 freq = (freq + 8) / 16; /* to kHz */181 182 switch (tea->band) {183 case BAND_FM:184 tea->write_reg &= ~TEA5777_W_AM_FM_MASK;185 freq = (freq - TEA5777_FM_IF) / TEA5777_FM_FREQ_STEP;186 tea->write_reg &= ~TEA5777_W_FM_PLL_MASK;187 tea->write_reg |= (u64)freq << TEA5777_W_FM_PLL_SHIFT;188 tea->write_reg &= ~TEA5777_W_FM_FREF_MASK;189 tea->write_reg |= TEA5777_W_FM_FREF_VALUE <<190 TEA5777_W_FM_FREF_SHIFT;191 tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK;192 if (tea->audmode == V4L2_TUNER_MODE_MONO)193 tea->write_reg |= 1LL << TEA5777_W_FM_FORCEMONO_SHIFT;194 break;195 case BAND_AM:196 tea->write_reg &= ~TEA5777_W_AM_FM_MASK;197 tea->write_reg |= (1LL << TEA5777_W_AM_FM_SHIFT);198 freq = (freq - TEA5777_AM_IF) / TEA5777_AM_FREQ_STEP;199 tea->write_reg &= ~TEA5777_W_AM_PLL_MASK;200 tea->write_reg |= (u64)freq << TEA5777_W_AM_PLL_SHIFT;201 tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK;202 tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK;203 tea->write_reg &= ~TEA5777_W_AM_MWLW_MASK;204 tea->write_reg |= TEA5777_W_AM_MW << TEA5777_W_AM_MWLW_SHIFT;205 tea->write_reg &= ~TEA5777_W_AM_LNA_MASK;206 tea->write_reg |= 1LL << TEA5777_W_AM_LNA_SHIFT;207 tea->write_reg &= ~TEA5777_W_AM_PEAK_MASK;208 tea->write_reg |= 1LL << TEA5777_W_AM_PEAK_SHIFT;209 tea->write_reg &= ~TEA5777_W_AM_CALLIGN_MASK;210 break;211 }212 213 res = tea->ops->write_reg(tea, tea->write_reg);214 if (res)215 return res;216 217 tea->needs_write = false;218 tea->read_reg = -1;219 tea->freq = tea5777_freq_to_v4l2_freq(tea, freq);220 221 return 0;222}223 224static int radio_tea5777_update_read_reg(struct radio_tea5777 *tea, int wait)225{226 int res;227 228 if (tea->read_reg != -1)229 return 0;230 231 if (tea->write_before_read && tea->needs_write) {232 res = radio_tea5777_set_freq(tea);233 if (res)234 return res;235 }236 237 if (wait) {238 if (schedule_timeout_interruptible(msecs_to_jiffies(wait)))239 return -ERESTARTSYS;240 }241 242 res = tea->ops->read_reg(tea, &tea->read_reg);243 if (res)244 return res;245 246 tea->needs_write = true;247 return 0;248}249 250/*251 * Linux Video interface252 */253 254static int vidioc_querycap(struct file *file, void *priv,255 struct v4l2_capability *v)256{257 struct radio_tea5777 *tea = video_drvdata(file);258 259 strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver));260 strscpy(v->card, tea->card, sizeof(v->card));261 strlcat(v->card, " TEA5777", sizeof(v->card));262 strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info));263 return 0;264}265 266static int vidioc_enum_freq_bands(struct file *file, void *priv,267 struct v4l2_frequency_band *band)268{269 struct radio_tea5777 *tea = video_drvdata(file);270 271 if (band->tuner != 0 || band->index >= ARRAY_SIZE(bands) ||272 (!tea->has_am && band->index == BAND_AM))273 return -EINVAL;274 275 *band = bands[band->index];276 return 0;277}278 279static int vidioc_g_tuner(struct file *file, void *priv,280 struct v4l2_tuner *v)281{282 struct radio_tea5777 *tea = video_drvdata(file);283 int res;284 285 if (v->index > 0)286 return -EINVAL;287 288 res = radio_tea5777_update_read_reg(tea, 0);289 if (res)290 return res;291 292 memset(v, 0, sizeof(*v));293 if (tea->has_am)294 strscpy(v->name, "AM/FM", sizeof(v->name));295 else296 strscpy(v->name, "FM", sizeof(v->name));297 v->type = V4L2_TUNER_RADIO;298 v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |299 V4L2_TUNER_CAP_FREQ_BANDS |300 V4L2_TUNER_CAP_HWSEEK_BOUNDED |301 V4L2_TUNER_CAP_HWSEEK_PROG_LIM;302 v->rangelow = tea->has_am ? bands[BAND_AM].rangelow :303 bands[BAND_FM].rangelow;304 v->rangehigh = bands[BAND_FM].rangehigh;305 if (tea->band == BAND_FM &&306 (tea->read_reg & TEA5777_R_FM_STEREO_MASK))307 v->rxsubchans = V4L2_TUNER_SUB_STEREO;308 else309 v->rxsubchans = V4L2_TUNER_SUB_MONO;310 v->audmode = tea->audmode;311 /* shift - 12 to convert 4-bits (0-15) scale to 16-bits (0-65535) */312 v->signal = (tea->read_reg & TEA5777_R_LEVEL_MASK) >>313 (TEA5777_R_LEVEL_SHIFT - 12);314 315 /* Invalidate read_reg, so that next call we return up2date signal */316 tea->read_reg = -1;317 318 return 0;319}320 321static int vidioc_s_tuner(struct file *file, void *priv,322 const struct v4l2_tuner *v)323{324 struct radio_tea5777 *tea = video_drvdata(file);325 u32 orig_audmode = tea->audmode;326 327 if (v->index)328 return -EINVAL;329 330 tea->audmode = v->audmode;331 if (tea->audmode > V4L2_TUNER_MODE_STEREO)332 tea->audmode = V4L2_TUNER_MODE_STEREO;333 334 if (tea->audmode != orig_audmode && tea->band == BAND_FM)335 return radio_tea5777_set_freq(tea);336 337 return 0;338}339 340static int vidioc_g_frequency(struct file *file, void *priv,341 struct v4l2_frequency *f)342{343 struct radio_tea5777 *tea = video_drvdata(file);344 345 if (f->tuner != 0)346 return -EINVAL;347 f->type = V4L2_TUNER_RADIO;348 f->frequency = tea->freq;349 return 0;350}351 352static int vidioc_s_frequency(struct file *file, void *priv,353 const struct v4l2_frequency *f)354{355 struct radio_tea5777 *tea = video_drvdata(file);356 357 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)358 return -EINVAL;359 360 if (tea->has_am && f->frequency < (20000 * 16))361 tea->band = BAND_AM;362 else363 tea->band = BAND_FM;364 365 tea->freq = f->frequency;366 return radio_tea5777_set_freq(tea);367}368 369static int vidioc_s_hw_freq_seek(struct file *file, void *fh,370 const struct v4l2_hw_freq_seek *a)371{372 struct radio_tea5777 *tea = video_drvdata(file);373 unsigned long timeout;374 u32 rangelow = a->rangelow;375 u32 rangehigh = a->rangehigh;376 int i, res, spacing;377 u32 orig_freq;378 379 if (a->tuner || a->wrap_around)380 return -EINVAL;381 382 if (file->f_flags & O_NONBLOCK)383 return -EWOULDBLOCK;384 385 if (rangelow || rangehigh) {386 for (i = 0; i < ARRAY_SIZE(bands); i++) {387 if (i == BAND_AM && !tea->has_am)388 continue;389 if (bands[i].rangelow >= rangelow &&390 bands[i].rangehigh <= rangehigh)391 break;392 }393 if (i == ARRAY_SIZE(bands))394 return -EINVAL; /* No matching band found */395 396 tea->band = i;397 if (tea->freq < rangelow || tea->freq > rangehigh) {398 tea->freq = clamp(tea->freq, rangelow,399 rangehigh);400 res = radio_tea5777_set_freq(tea);401 if (res)402 return res;403 }404 } else {405 rangelow = bands[tea->band].rangelow;406 rangehigh = bands[tea->band].rangehigh;407 }408 409 spacing = (tea->band == BAND_AM) ? (5 * 16) : (200 * 16); /* kHz */410 orig_freq = tea->freq;411 412 tea->write_reg |= TEA5777_W_PROGBLIM_MASK;413 if (tea->seek_rangelow != rangelow) {414 tea->write_reg &= ~TEA5777_W_UPDWN_MASK;415 tea->freq = rangelow;416 res = radio_tea5777_set_freq(tea);417 if (res)418 goto leave;419 tea->seek_rangelow = rangelow;420 }421 if (tea->seek_rangehigh != rangehigh) {422 tea->write_reg |= TEA5777_W_UPDWN_MASK;423 tea->freq = rangehigh;424 res = radio_tea5777_set_freq(tea);425 if (res)426 goto leave;427 tea->seek_rangehigh = rangehigh;428 }429 tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK;430 431 tea->write_reg |= TEA5777_W_SEARCH_MASK;432 if (a->seek_upward) {433 tea->write_reg |= TEA5777_W_UPDWN_MASK;434 tea->freq = orig_freq + spacing;435 } else {436 tea->write_reg &= ~TEA5777_W_UPDWN_MASK;437 tea->freq = orig_freq - spacing;438 }439 res = radio_tea5777_set_freq(tea);440 if (res)441 goto leave;442 443 timeout = jiffies + msecs_to_jiffies(5000);444 for (;;) {445 if (time_after(jiffies, timeout)) {446 res = -ENODATA;447 break;448 }449 450 res = radio_tea5777_update_read_reg(tea, 100);451 if (res)452 break;453 454 /*455 * Note we use tea->freq to track how far we've searched sofar456 * this is necessary to ensure we continue seeking at the right457 * point, in the write_before_read case.458 */459 tea->freq = (tea->read_reg & TEA5777_R_FM_PLL_MASK);460 tea->freq = tea5777_freq_to_v4l2_freq(tea, tea->freq);461 462 if ((tea->read_reg & TEA5777_R_SFOUND_MASK)) {463 tea->write_reg &= ~TEA5777_W_SEARCH_MASK;464 return 0;465 }466 467 if (tea->read_reg & TEA5777_R_BLIM_MASK) {468 res = -ENODATA;469 break;470 }471 472 /* Force read_reg update */473 tea->read_reg = -1;474 }475leave:476 tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK;477 tea->write_reg &= ~TEA5777_W_SEARCH_MASK;478 tea->freq = orig_freq;479 radio_tea5777_set_freq(tea);480 return res;481}482 483static int tea575x_s_ctrl(struct v4l2_ctrl *c)484{485 struct radio_tea5777 *tea =486 container_of(c->handler, struct radio_tea5777, ctrl_handler);487 488 switch (c->id) {489 case V4L2_CID_AUDIO_MUTE:490 if (c->val)491 tea->write_reg |= TEA5777_W_MUTE_MASK;492 else493 tea->write_reg &= ~TEA5777_W_MUTE_MASK;494 495 return radio_tea5777_set_freq(tea);496 }497 498 return -EINVAL;499}500 501static const struct v4l2_file_operations tea575x_fops = {502 .unlocked_ioctl = video_ioctl2,503 .open = v4l2_fh_open,504 .release = v4l2_fh_release,505 .poll = v4l2_ctrl_poll,506};507 508static const struct v4l2_ioctl_ops tea575x_ioctl_ops = {509 .vidioc_querycap = vidioc_querycap,510 .vidioc_g_tuner = vidioc_g_tuner,511 .vidioc_s_tuner = vidioc_s_tuner,512 .vidioc_g_frequency = vidioc_g_frequency,513 .vidioc_s_frequency = vidioc_s_frequency,514 .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek,515 .vidioc_enum_freq_bands = vidioc_enum_freq_bands,516 .vidioc_log_status = v4l2_ctrl_log_status,517 .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,518 .vidioc_unsubscribe_event = v4l2_event_unsubscribe,519};520 521static const struct video_device tea575x_radio = {522 .ioctl_ops = &tea575x_ioctl_ops,523 .release = video_device_release_empty,524};525 526static const struct v4l2_ctrl_ops tea575x_ctrl_ops = {527 .s_ctrl = tea575x_s_ctrl,528};529 530int radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner)531{532 int res;533 534 tea->write_reg = (1LL << TEA5777_W_IFCE_SHIFT) |535 (1LL << TEA5777_W_IFW_SHIFT) |536 (1LL << TEA5777_W_INTEXT_SHIFT) |537 (1LL << TEA5777_W_CHP0_SHIFT) |538 (1LL << TEA5777_W_SLEV_SHIFT);539 tea->freq = 90500 * 16; /* 90.5Mhz default */540 tea->audmode = V4L2_TUNER_MODE_STEREO;541 res = radio_tea5777_set_freq(tea);542 if (res) {543 v4l2_err(tea->v4l2_dev, "can't set initial freq (%d)\n", res);544 return res;545 }546 547 tea->vd = tea575x_radio;548 video_set_drvdata(&tea->vd, tea);549 mutex_init(&tea->mutex);550 strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name));551 tea->vd.lock = &tea->mutex;552 tea->vd.v4l2_dev = tea->v4l2_dev;553 tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO |554 V4L2_CAP_HW_FREQ_SEEK;555 tea->fops = tea575x_fops;556 tea->fops.owner = owner;557 tea->vd.fops = &tea->fops;558 559 tea->vd.ctrl_handler = &tea->ctrl_handler;560 v4l2_ctrl_handler_init(&tea->ctrl_handler, 1);561 v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops,562 V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);563 res = tea->ctrl_handler.error;564 if (res) {565 v4l2_err(tea->v4l2_dev, "can't initialize controls\n");566 v4l2_ctrl_handler_free(&tea->ctrl_handler);567 return res;568 }569 v4l2_ctrl_handler_setup(&tea->ctrl_handler);570 571 res = video_register_device(&tea->vd, VFL_TYPE_RADIO, -1);572 if (res) {573 v4l2_err(tea->v4l2_dev, "can't register video device!\n");574 v4l2_ctrl_handler_free(tea->vd.ctrl_handler);575 return res;576 }577 578 return 0;579}580EXPORT_SYMBOL_GPL(radio_tea5777_init);581 582void radio_tea5777_exit(struct radio_tea5777 *tea)583{584 video_unregister_device(&tea->vd);585 v4l2_ctrl_handler_free(tea->vd.ctrl_handler);586}587EXPORT_SYMBOL_GPL(radio_tea5777_exit);588