162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * v4l2 driver for TEA5777 Philips AM/FM radio tuner chips 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Based on the ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips: 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/delay.h> 1362306a36Sopenharmony_ci#include <linux/init.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/sched.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <media/v4l2-device.h> 1862306a36Sopenharmony_ci#include <media/v4l2-dev.h> 1962306a36Sopenharmony_ci#include <media/v4l2-fh.h> 2062306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 2162306a36Sopenharmony_ci#include <media/v4l2-event.h> 2262306a36Sopenharmony_ci#include "radio-tea5777.h" 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <perex@perex.cz>"); 2562306a36Sopenharmony_ciMODULE_DESCRIPTION("Routines for control of TEA5777 Philips AM/FM radio tuner chips"); 2662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define TEA5777_FM_IF 150 /* kHz */ 2962306a36Sopenharmony_ci#define TEA5777_FM_FREQ_STEP 50 /* kHz */ 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define TEA5777_AM_IF 21 /* kHz */ 3262306a36Sopenharmony_ci#define TEA5777_AM_FREQ_STEP 1 /* kHz */ 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* Write reg, common bits */ 3562306a36Sopenharmony_ci#define TEA5777_W_MUTE_MASK (1LL << 47) 3662306a36Sopenharmony_ci#define TEA5777_W_MUTE_SHIFT 47 3762306a36Sopenharmony_ci#define TEA5777_W_AM_FM_MASK (1LL << 46) 3862306a36Sopenharmony_ci#define TEA5777_W_AM_FM_SHIFT 46 3962306a36Sopenharmony_ci#define TEA5777_W_STB_MASK (1LL << 45) 4062306a36Sopenharmony_ci#define TEA5777_W_STB_SHIFT 45 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define TEA5777_W_IFCE_MASK (1LL << 29) 4362306a36Sopenharmony_ci#define TEA5777_W_IFCE_SHIFT 29 4462306a36Sopenharmony_ci#define TEA5777_W_IFW_MASK (1LL << 28) 4562306a36Sopenharmony_ci#define TEA5777_W_IFW_SHIFT 28 4662306a36Sopenharmony_ci#define TEA5777_W_HILO_MASK (1LL << 27) 4762306a36Sopenharmony_ci#define TEA5777_W_HILO_SHIFT 27 4862306a36Sopenharmony_ci#define TEA5777_W_DBUS_MASK (1LL << 26) 4962306a36Sopenharmony_ci#define TEA5777_W_DBUS_SHIFT 26 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#define TEA5777_W_INTEXT_MASK (1LL << 24) 5262306a36Sopenharmony_ci#define TEA5777_W_INTEXT_SHIFT 24 5362306a36Sopenharmony_ci#define TEA5777_W_P1_MASK (1LL << 23) 5462306a36Sopenharmony_ci#define TEA5777_W_P1_SHIFT 23 5562306a36Sopenharmony_ci#define TEA5777_W_P0_MASK (1LL << 22) 5662306a36Sopenharmony_ci#define TEA5777_W_P0_SHIFT 22 5762306a36Sopenharmony_ci#define TEA5777_W_PEN1_MASK (1LL << 21) 5862306a36Sopenharmony_ci#define TEA5777_W_PEN1_SHIFT 21 5962306a36Sopenharmony_ci#define TEA5777_W_PEN0_MASK (1LL << 20) 6062306a36Sopenharmony_ci#define TEA5777_W_PEN0_SHIFT 20 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci#define TEA5777_W_CHP0_MASK (1LL << 18) 6362306a36Sopenharmony_ci#define TEA5777_W_CHP0_SHIFT 18 6462306a36Sopenharmony_ci#define TEA5777_W_DEEM_MASK (1LL << 17) 6562306a36Sopenharmony_ci#define TEA5777_W_DEEM_SHIFT 17 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci#define TEA5777_W_SEARCH_MASK (1LL << 7) 6862306a36Sopenharmony_ci#define TEA5777_W_SEARCH_SHIFT 7 6962306a36Sopenharmony_ci#define TEA5777_W_PROGBLIM_MASK (1LL << 6) 7062306a36Sopenharmony_ci#define TEA5777_W_PROGBLIM_SHIFT 6 7162306a36Sopenharmony_ci#define TEA5777_W_UPDWN_MASK (1LL << 5) 7262306a36Sopenharmony_ci#define TEA5777_W_UPDWN_SHIFT 5 7362306a36Sopenharmony_ci#define TEA5777_W_SLEV_MASK (3LL << 3) 7462306a36Sopenharmony_ci#define TEA5777_W_SLEV_SHIFT 3 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/* Write reg, FM specific bits */ 7762306a36Sopenharmony_ci#define TEA5777_W_FM_PLL_MASK (0x1fffLL << 32) 7862306a36Sopenharmony_ci#define TEA5777_W_FM_PLL_SHIFT 32 7962306a36Sopenharmony_ci#define TEA5777_W_FM_FREF_MASK (0x03LL << 30) 8062306a36Sopenharmony_ci#define TEA5777_W_FM_FREF_SHIFT 30 8162306a36Sopenharmony_ci#define TEA5777_W_FM_FREF_VALUE 0LL /* 50k steps, 150k IF */ 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci#define TEA5777_W_FM_FORCEMONO_MASK (1LL << 15) 8462306a36Sopenharmony_ci#define TEA5777_W_FM_FORCEMONO_SHIFT 15 8562306a36Sopenharmony_ci#define TEA5777_W_FM_SDSOFF_MASK (1LL << 14) 8662306a36Sopenharmony_ci#define TEA5777_W_FM_SDSOFF_SHIFT 14 8762306a36Sopenharmony_ci#define TEA5777_W_FM_DOFF_MASK (1LL << 13) 8862306a36Sopenharmony_ci#define TEA5777_W_FM_DOFF_SHIFT 13 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci#define TEA5777_W_FM_STEP_MASK (3LL << 1) 9162306a36Sopenharmony_ci#define TEA5777_W_FM_STEP_SHIFT 1 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci/* Write reg, AM specific bits */ 9462306a36Sopenharmony_ci#define TEA5777_W_AM_PLL_MASK (0x7ffLL << 34) 9562306a36Sopenharmony_ci#define TEA5777_W_AM_PLL_SHIFT 34 9662306a36Sopenharmony_ci#define TEA5777_W_AM_AGCRF_MASK (1LL << 33) 9762306a36Sopenharmony_ci#define TEA5777_W_AM_AGCRF_SHIFT 33 9862306a36Sopenharmony_ci#define TEA5777_W_AM_AGCIF_MASK (1LL << 32) 9962306a36Sopenharmony_ci#define TEA5777_W_AM_AGCIF_SHIFT 32 10062306a36Sopenharmony_ci#define TEA5777_W_AM_MWLW_MASK (1LL << 31) 10162306a36Sopenharmony_ci#define TEA5777_W_AM_MWLW_SHIFT 31 10262306a36Sopenharmony_ci#define TEA5777_W_AM_LW 0LL 10362306a36Sopenharmony_ci#define TEA5777_W_AM_MW 1LL 10462306a36Sopenharmony_ci#define TEA5777_W_AM_LNA_MASK (1LL << 30) 10562306a36Sopenharmony_ci#define TEA5777_W_AM_LNA_SHIFT 30 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci#define TEA5777_W_AM_PEAK_MASK (1LL << 25) 10862306a36Sopenharmony_ci#define TEA5777_W_AM_PEAK_SHIFT 25 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci#define TEA5777_W_AM_RFB_MASK (1LL << 16) 11162306a36Sopenharmony_ci#define TEA5777_W_AM_RFB_SHIFT 16 11262306a36Sopenharmony_ci#define TEA5777_W_AM_CALLIGN_MASK (1LL << 15) 11362306a36Sopenharmony_ci#define TEA5777_W_AM_CALLIGN_SHIFT 15 11462306a36Sopenharmony_ci#define TEA5777_W_AM_CBANK_MASK (0x7fLL << 8) 11562306a36Sopenharmony_ci#define TEA5777_W_AM_CBANK_SHIFT 8 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci#define TEA5777_W_AM_DELAY_MASK (1LL << 2) 11862306a36Sopenharmony_ci#define TEA5777_W_AM_DELAY_SHIFT 2 11962306a36Sopenharmony_ci#define TEA5777_W_AM_STEP_MASK (1LL << 1) 12062306a36Sopenharmony_ci#define TEA5777_W_AM_STEP_SHIFT 1 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci/* Read reg, common bits */ 12362306a36Sopenharmony_ci#define TEA5777_R_LEVEL_MASK (0x0f << 17) 12462306a36Sopenharmony_ci#define TEA5777_R_LEVEL_SHIFT 17 12562306a36Sopenharmony_ci#define TEA5777_R_SFOUND_MASK (0x01 << 16) 12662306a36Sopenharmony_ci#define TEA5777_R_SFOUND_SHIFT 16 12762306a36Sopenharmony_ci#define TEA5777_R_BLIM_MASK (0x01 << 15) 12862306a36Sopenharmony_ci#define TEA5777_R_BLIM_SHIFT 15 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci/* Read reg, FM specific bits */ 13162306a36Sopenharmony_ci#define TEA5777_R_FM_STEREO_MASK (0x01 << 21) 13262306a36Sopenharmony_ci#define TEA5777_R_FM_STEREO_SHIFT 21 13362306a36Sopenharmony_ci#define TEA5777_R_FM_PLL_MASK 0x1fff 13462306a36Sopenharmony_ci#define TEA5777_R_FM_PLL_SHIFT 0 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cienum { BAND_FM, BAND_AM }; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic const struct v4l2_frequency_band bands[] = { 13962306a36Sopenharmony_ci { 14062306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 14162306a36Sopenharmony_ci .index = 0, 14262306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 14362306a36Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS | 14462306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 14562306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_PROG_LIM, 14662306a36Sopenharmony_ci .rangelow = 76000 * 16, 14762306a36Sopenharmony_ci .rangehigh = 108000 * 16, 14862306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 14962306a36Sopenharmony_ci }, 15062306a36Sopenharmony_ci { 15162306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 15262306a36Sopenharmony_ci .index = 1, 15362306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS | 15462306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 15562306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_PROG_LIM, 15662306a36Sopenharmony_ci .rangelow = 530 * 16, 15762306a36Sopenharmony_ci .rangehigh = 1710 * 16, 15862306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_AM, 15962306a36Sopenharmony_ci }, 16062306a36Sopenharmony_ci}; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistatic u32 tea5777_freq_to_v4l2_freq(struct radio_tea5777 *tea, u32 freq) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci switch (tea->band) { 16562306a36Sopenharmony_ci case BAND_FM: 16662306a36Sopenharmony_ci return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16; 16762306a36Sopenharmony_ci case BAND_AM: 16862306a36Sopenharmony_ci return (freq * TEA5777_AM_FREQ_STEP + TEA5777_AM_IF) * 16; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci return 0; /* Never reached */ 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ciint radio_tea5777_set_freq(struct radio_tea5777 *tea) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci u32 freq; 17662306a36Sopenharmony_ci int res; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci freq = clamp(tea->freq, bands[tea->band].rangelow, 17962306a36Sopenharmony_ci bands[tea->band].rangehigh); 18062306a36Sopenharmony_ci freq = (freq + 8) / 16; /* to kHz */ 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci switch (tea->band) { 18362306a36Sopenharmony_ci case BAND_FM: 18462306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_FM_MASK; 18562306a36Sopenharmony_ci freq = (freq - TEA5777_FM_IF) / TEA5777_FM_FREQ_STEP; 18662306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_FM_PLL_MASK; 18762306a36Sopenharmony_ci tea->write_reg |= (u64)freq << TEA5777_W_FM_PLL_SHIFT; 18862306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_FM_FREF_MASK; 18962306a36Sopenharmony_ci tea->write_reg |= TEA5777_W_FM_FREF_VALUE << 19062306a36Sopenharmony_ci TEA5777_W_FM_FREF_SHIFT; 19162306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK; 19262306a36Sopenharmony_ci if (tea->audmode == V4L2_TUNER_MODE_MONO) 19362306a36Sopenharmony_ci tea->write_reg |= 1LL << TEA5777_W_FM_FORCEMONO_SHIFT; 19462306a36Sopenharmony_ci break; 19562306a36Sopenharmony_ci case BAND_AM: 19662306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_FM_MASK; 19762306a36Sopenharmony_ci tea->write_reg |= (1LL << TEA5777_W_AM_FM_SHIFT); 19862306a36Sopenharmony_ci freq = (freq - TEA5777_AM_IF) / TEA5777_AM_FREQ_STEP; 19962306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_PLL_MASK; 20062306a36Sopenharmony_ci tea->write_reg |= (u64)freq << TEA5777_W_AM_PLL_SHIFT; 20162306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK; 20262306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK; 20362306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_MWLW_MASK; 20462306a36Sopenharmony_ci tea->write_reg |= TEA5777_W_AM_MW << TEA5777_W_AM_MWLW_SHIFT; 20562306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_LNA_MASK; 20662306a36Sopenharmony_ci tea->write_reg |= 1LL << TEA5777_W_AM_LNA_SHIFT; 20762306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_PEAK_MASK; 20862306a36Sopenharmony_ci tea->write_reg |= 1LL << TEA5777_W_AM_PEAK_SHIFT; 20962306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_CALLIGN_MASK; 21062306a36Sopenharmony_ci break; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci res = tea->ops->write_reg(tea, tea->write_reg); 21462306a36Sopenharmony_ci if (res) 21562306a36Sopenharmony_ci return res; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci tea->needs_write = false; 21862306a36Sopenharmony_ci tea->read_reg = -1; 21962306a36Sopenharmony_ci tea->freq = tea5777_freq_to_v4l2_freq(tea, freq); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci return 0; 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistatic int radio_tea5777_update_read_reg(struct radio_tea5777 *tea, int wait) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci int res; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci if (tea->read_reg != -1) 22962306a36Sopenharmony_ci return 0; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci if (tea->write_before_read && tea->needs_write) { 23262306a36Sopenharmony_ci res = radio_tea5777_set_freq(tea); 23362306a36Sopenharmony_ci if (res) 23462306a36Sopenharmony_ci return res; 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci if (wait) { 23862306a36Sopenharmony_ci if (schedule_timeout_interruptible(msecs_to_jiffies(wait))) 23962306a36Sopenharmony_ci return -ERESTARTSYS; 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci res = tea->ops->read_reg(tea, &tea->read_reg); 24362306a36Sopenharmony_ci if (res) 24462306a36Sopenharmony_ci return res; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci tea->needs_write = true; 24762306a36Sopenharmony_ci return 0; 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci/* 25162306a36Sopenharmony_ci * Linux Video interface 25262306a36Sopenharmony_ci */ 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 25562306a36Sopenharmony_ci struct v4l2_capability *v) 25662306a36Sopenharmony_ci{ 25762306a36Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver)); 26062306a36Sopenharmony_ci strscpy(v->card, tea->card, sizeof(v->card)); 26162306a36Sopenharmony_ci strlcat(v->card, " TEA5777", sizeof(v->card)); 26262306a36Sopenharmony_ci strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info)); 26362306a36Sopenharmony_ci return 0; 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_cistatic int vidioc_enum_freq_bands(struct file *file, void *priv, 26762306a36Sopenharmony_ci struct v4l2_frequency_band *band) 26862306a36Sopenharmony_ci{ 26962306a36Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci if (band->tuner != 0 || band->index >= ARRAY_SIZE(bands) || 27262306a36Sopenharmony_ci (!tea->has_am && band->index == BAND_AM)) 27362306a36Sopenharmony_ci return -EINVAL; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci *band = bands[band->index]; 27662306a36Sopenharmony_ci return 0; 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv, 28062306a36Sopenharmony_ci struct v4l2_tuner *v) 28162306a36Sopenharmony_ci{ 28262306a36Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 28362306a36Sopenharmony_ci int res; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci if (v->index > 0) 28662306a36Sopenharmony_ci return -EINVAL; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci res = radio_tea5777_update_read_reg(tea, 0); 28962306a36Sopenharmony_ci if (res) 29062306a36Sopenharmony_ci return res; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci memset(v, 0, sizeof(*v)); 29362306a36Sopenharmony_ci if (tea->has_am) 29462306a36Sopenharmony_ci strscpy(v->name, "AM/FM", sizeof(v->name)); 29562306a36Sopenharmony_ci else 29662306a36Sopenharmony_ci strscpy(v->name, "FM", sizeof(v->name)); 29762306a36Sopenharmony_ci v->type = V4L2_TUNER_RADIO; 29862306a36Sopenharmony_ci v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 29962306a36Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS | 30062306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 30162306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_PROG_LIM; 30262306a36Sopenharmony_ci v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : 30362306a36Sopenharmony_ci bands[BAND_FM].rangelow; 30462306a36Sopenharmony_ci v->rangehigh = bands[BAND_FM].rangehigh; 30562306a36Sopenharmony_ci if (tea->band == BAND_FM && 30662306a36Sopenharmony_ci (tea->read_reg & TEA5777_R_FM_STEREO_MASK)) 30762306a36Sopenharmony_ci v->rxsubchans = V4L2_TUNER_SUB_STEREO; 30862306a36Sopenharmony_ci else 30962306a36Sopenharmony_ci v->rxsubchans = V4L2_TUNER_SUB_MONO; 31062306a36Sopenharmony_ci v->audmode = tea->audmode; 31162306a36Sopenharmony_ci /* shift - 12 to convert 4-bits (0-15) scale to 16-bits (0-65535) */ 31262306a36Sopenharmony_ci v->signal = (tea->read_reg & TEA5777_R_LEVEL_MASK) >> 31362306a36Sopenharmony_ci (TEA5777_R_LEVEL_SHIFT - 12); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci /* Invalidate read_reg, so that next call we return up2date signal */ 31662306a36Sopenharmony_ci tea->read_reg = -1; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci return 0; 31962306a36Sopenharmony_ci} 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv, 32262306a36Sopenharmony_ci const struct v4l2_tuner *v) 32362306a36Sopenharmony_ci{ 32462306a36Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 32562306a36Sopenharmony_ci u32 orig_audmode = tea->audmode; 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci if (v->index) 32862306a36Sopenharmony_ci return -EINVAL; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci tea->audmode = v->audmode; 33162306a36Sopenharmony_ci if (tea->audmode > V4L2_TUNER_MODE_STEREO) 33262306a36Sopenharmony_ci tea->audmode = V4L2_TUNER_MODE_STEREO; 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci if (tea->audmode != orig_audmode && tea->band == BAND_FM) 33562306a36Sopenharmony_ci return radio_tea5777_set_freq(tea); 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci return 0; 33862306a36Sopenharmony_ci} 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 34162306a36Sopenharmony_ci struct v4l2_frequency *f) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci if (f->tuner != 0) 34662306a36Sopenharmony_ci return -EINVAL; 34762306a36Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 34862306a36Sopenharmony_ci f->frequency = tea->freq; 34962306a36Sopenharmony_ci return 0; 35062306a36Sopenharmony_ci} 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 35362306a36Sopenharmony_ci const struct v4l2_frequency *f) 35462306a36Sopenharmony_ci{ 35562306a36Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 35862306a36Sopenharmony_ci return -EINVAL; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci if (tea->has_am && f->frequency < (20000 * 16)) 36162306a36Sopenharmony_ci tea->band = BAND_AM; 36262306a36Sopenharmony_ci else 36362306a36Sopenharmony_ci tea->band = BAND_FM; 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci tea->freq = f->frequency; 36662306a36Sopenharmony_ci return radio_tea5777_set_freq(tea); 36762306a36Sopenharmony_ci} 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_cistatic int vidioc_s_hw_freq_seek(struct file *file, void *fh, 37062306a36Sopenharmony_ci const struct v4l2_hw_freq_seek *a) 37162306a36Sopenharmony_ci{ 37262306a36Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 37362306a36Sopenharmony_ci unsigned long timeout; 37462306a36Sopenharmony_ci u32 rangelow = a->rangelow; 37562306a36Sopenharmony_ci u32 rangehigh = a->rangehigh; 37662306a36Sopenharmony_ci int i, res, spacing; 37762306a36Sopenharmony_ci u32 orig_freq; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci if (a->tuner || a->wrap_around) 38062306a36Sopenharmony_ci return -EINVAL; 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 38362306a36Sopenharmony_ci return -EWOULDBLOCK; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci if (rangelow || rangehigh) { 38662306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(bands); i++) { 38762306a36Sopenharmony_ci if (i == BAND_AM && !tea->has_am) 38862306a36Sopenharmony_ci continue; 38962306a36Sopenharmony_ci if (bands[i].rangelow >= rangelow && 39062306a36Sopenharmony_ci bands[i].rangehigh <= rangehigh) 39162306a36Sopenharmony_ci break; 39262306a36Sopenharmony_ci } 39362306a36Sopenharmony_ci if (i == ARRAY_SIZE(bands)) 39462306a36Sopenharmony_ci return -EINVAL; /* No matching band found */ 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci tea->band = i; 39762306a36Sopenharmony_ci if (tea->freq < rangelow || tea->freq > rangehigh) { 39862306a36Sopenharmony_ci tea->freq = clamp(tea->freq, rangelow, 39962306a36Sopenharmony_ci rangehigh); 40062306a36Sopenharmony_ci res = radio_tea5777_set_freq(tea); 40162306a36Sopenharmony_ci if (res) 40262306a36Sopenharmony_ci return res; 40362306a36Sopenharmony_ci } 40462306a36Sopenharmony_ci } else { 40562306a36Sopenharmony_ci rangelow = bands[tea->band].rangelow; 40662306a36Sopenharmony_ci rangehigh = bands[tea->band].rangehigh; 40762306a36Sopenharmony_ci } 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci spacing = (tea->band == BAND_AM) ? (5 * 16) : (200 * 16); /* kHz */ 41062306a36Sopenharmony_ci orig_freq = tea->freq; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci tea->write_reg |= TEA5777_W_PROGBLIM_MASK; 41362306a36Sopenharmony_ci if (tea->seek_rangelow != rangelow) { 41462306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_UPDWN_MASK; 41562306a36Sopenharmony_ci tea->freq = rangelow; 41662306a36Sopenharmony_ci res = radio_tea5777_set_freq(tea); 41762306a36Sopenharmony_ci if (res) 41862306a36Sopenharmony_ci goto leave; 41962306a36Sopenharmony_ci tea->seek_rangelow = rangelow; 42062306a36Sopenharmony_ci } 42162306a36Sopenharmony_ci if (tea->seek_rangehigh != rangehigh) { 42262306a36Sopenharmony_ci tea->write_reg |= TEA5777_W_UPDWN_MASK; 42362306a36Sopenharmony_ci tea->freq = rangehigh; 42462306a36Sopenharmony_ci res = radio_tea5777_set_freq(tea); 42562306a36Sopenharmony_ci if (res) 42662306a36Sopenharmony_ci goto leave; 42762306a36Sopenharmony_ci tea->seek_rangehigh = rangehigh; 42862306a36Sopenharmony_ci } 42962306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK; 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci tea->write_reg |= TEA5777_W_SEARCH_MASK; 43262306a36Sopenharmony_ci if (a->seek_upward) { 43362306a36Sopenharmony_ci tea->write_reg |= TEA5777_W_UPDWN_MASK; 43462306a36Sopenharmony_ci tea->freq = orig_freq + spacing; 43562306a36Sopenharmony_ci } else { 43662306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_UPDWN_MASK; 43762306a36Sopenharmony_ci tea->freq = orig_freq - spacing; 43862306a36Sopenharmony_ci } 43962306a36Sopenharmony_ci res = radio_tea5777_set_freq(tea); 44062306a36Sopenharmony_ci if (res) 44162306a36Sopenharmony_ci goto leave; 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci timeout = jiffies + msecs_to_jiffies(5000); 44462306a36Sopenharmony_ci for (;;) { 44562306a36Sopenharmony_ci if (time_after(jiffies, timeout)) { 44662306a36Sopenharmony_ci res = -ENODATA; 44762306a36Sopenharmony_ci break; 44862306a36Sopenharmony_ci } 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci res = radio_tea5777_update_read_reg(tea, 100); 45162306a36Sopenharmony_ci if (res) 45262306a36Sopenharmony_ci break; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci /* 45562306a36Sopenharmony_ci * Note we use tea->freq to track how far we've searched sofar 45662306a36Sopenharmony_ci * this is necessary to ensure we continue seeking at the right 45762306a36Sopenharmony_ci * point, in the write_before_read case. 45862306a36Sopenharmony_ci */ 45962306a36Sopenharmony_ci tea->freq = (tea->read_reg & TEA5777_R_FM_PLL_MASK); 46062306a36Sopenharmony_ci tea->freq = tea5777_freq_to_v4l2_freq(tea, tea->freq); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci if ((tea->read_reg & TEA5777_R_SFOUND_MASK)) { 46362306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_SEARCH_MASK; 46462306a36Sopenharmony_ci return 0; 46562306a36Sopenharmony_ci } 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci if (tea->read_reg & TEA5777_R_BLIM_MASK) { 46862306a36Sopenharmony_ci res = -ENODATA; 46962306a36Sopenharmony_ci break; 47062306a36Sopenharmony_ci } 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci /* Force read_reg update */ 47362306a36Sopenharmony_ci tea->read_reg = -1; 47462306a36Sopenharmony_ci } 47562306a36Sopenharmony_cileave: 47662306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK; 47762306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_SEARCH_MASK; 47862306a36Sopenharmony_ci tea->freq = orig_freq; 47962306a36Sopenharmony_ci radio_tea5777_set_freq(tea); 48062306a36Sopenharmony_ci return res; 48162306a36Sopenharmony_ci} 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_cistatic int tea575x_s_ctrl(struct v4l2_ctrl *c) 48462306a36Sopenharmony_ci{ 48562306a36Sopenharmony_ci struct radio_tea5777 *tea = 48662306a36Sopenharmony_ci container_of(c->handler, struct radio_tea5777, ctrl_handler); 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci switch (c->id) { 48962306a36Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 49062306a36Sopenharmony_ci if (c->val) 49162306a36Sopenharmony_ci tea->write_reg |= TEA5777_W_MUTE_MASK; 49262306a36Sopenharmony_ci else 49362306a36Sopenharmony_ci tea->write_reg &= ~TEA5777_W_MUTE_MASK; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci return radio_tea5777_set_freq(tea); 49662306a36Sopenharmony_ci } 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci return -EINVAL; 49962306a36Sopenharmony_ci} 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_cistatic const struct v4l2_file_operations tea575x_fops = { 50262306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 50362306a36Sopenharmony_ci .open = v4l2_fh_open, 50462306a36Sopenharmony_ci .release = v4l2_fh_release, 50562306a36Sopenharmony_ci .poll = v4l2_ctrl_poll, 50662306a36Sopenharmony_ci}; 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops tea575x_ioctl_ops = { 50962306a36Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 51062306a36Sopenharmony_ci .vidioc_g_tuner = vidioc_g_tuner, 51162306a36Sopenharmony_ci .vidioc_s_tuner = vidioc_s_tuner, 51262306a36Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 51362306a36Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 51462306a36Sopenharmony_ci .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, 51562306a36Sopenharmony_ci .vidioc_enum_freq_bands = vidioc_enum_freq_bands, 51662306a36Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 51762306a36Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 51862306a36Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 51962306a36Sopenharmony_ci}; 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_cistatic const struct video_device tea575x_radio = { 52262306a36Sopenharmony_ci .ioctl_ops = &tea575x_ioctl_ops, 52362306a36Sopenharmony_ci .release = video_device_release_empty, 52462306a36Sopenharmony_ci}; 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops tea575x_ctrl_ops = { 52762306a36Sopenharmony_ci .s_ctrl = tea575x_s_ctrl, 52862306a36Sopenharmony_ci}; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ciint radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner) 53162306a36Sopenharmony_ci{ 53262306a36Sopenharmony_ci int res; 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci tea->write_reg = (1LL << TEA5777_W_IFCE_SHIFT) | 53562306a36Sopenharmony_ci (1LL << TEA5777_W_IFW_SHIFT) | 53662306a36Sopenharmony_ci (1LL << TEA5777_W_INTEXT_SHIFT) | 53762306a36Sopenharmony_ci (1LL << TEA5777_W_CHP0_SHIFT) | 53862306a36Sopenharmony_ci (1LL << TEA5777_W_SLEV_SHIFT); 53962306a36Sopenharmony_ci tea->freq = 90500 * 16; /* 90.5Mhz default */ 54062306a36Sopenharmony_ci tea->audmode = V4L2_TUNER_MODE_STEREO; 54162306a36Sopenharmony_ci res = radio_tea5777_set_freq(tea); 54262306a36Sopenharmony_ci if (res) { 54362306a36Sopenharmony_ci v4l2_err(tea->v4l2_dev, "can't set initial freq (%d)\n", res); 54462306a36Sopenharmony_ci return res; 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci tea->vd = tea575x_radio; 54862306a36Sopenharmony_ci video_set_drvdata(&tea->vd, tea); 54962306a36Sopenharmony_ci mutex_init(&tea->mutex); 55062306a36Sopenharmony_ci strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name)); 55162306a36Sopenharmony_ci tea->vd.lock = &tea->mutex; 55262306a36Sopenharmony_ci tea->vd.v4l2_dev = tea->v4l2_dev; 55362306a36Sopenharmony_ci tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO | 55462306a36Sopenharmony_ci V4L2_CAP_HW_FREQ_SEEK; 55562306a36Sopenharmony_ci tea->fops = tea575x_fops; 55662306a36Sopenharmony_ci tea->fops.owner = owner; 55762306a36Sopenharmony_ci tea->vd.fops = &tea->fops; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci tea->vd.ctrl_handler = &tea->ctrl_handler; 56062306a36Sopenharmony_ci v4l2_ctrl_handler_init(&tea->ctrl_handler, 1); 56162306a36Sopenharmony_ci v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops, 56262306a36Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 56362306a36Sopenharmony_ci res = tea->ctrl_handler.error; 56462306a36Sopenharmony_ci if (res) { 56562306a36Sopenharmony_ci v4l2_err(tea->v4l2_dev, "can't initialize controls\n"); 56662306a36Sopenharmony_ci v4l2_ctrl_handler_free(&tea->ctrl_handler); 56762306a36Sopenharmony_ci return res; 56862306a36Sopenharmony_ci } 56962306a36Sopenharmony_ci v4l2_ctrl_handler_setup(&tea->ctrl_handler); 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci res = video_register_device(&tea->vd, VFL_TYPE_RADIO, -1); 57262306a36Sopenharmony_ci if (res) { 57362306a36Sopenharmony_ci v4l2_err(tea->v4l2_dev, "can't register video device!\n"); 57462306a36Sopenharmony_ci v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 57562306a36Sopenharmony_ci return res; 57662306a36Sopenharmony_ci } 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci return 0; 57962306a36Sopenharmony_ci} 58062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(radio_tea5777_init); 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_civoid radio_tea5777_exit(struct radio_tea5777 *tea) 58362306a36Sopenharmony_ci{ 58462306a36Sopenharmony_ci video_unregister_device(&tea->vd); 58562306a36Sopenharmony_ci v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 58662306a36Sopenharmony_ci} 58762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(radio_tea5777_exit); 588