18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * v4l2 driver for TEA5777 Philips AM/FM radio tuner chips 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Based on the ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips: 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz> 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/delay.h> 138c2ecf20Sopenharmony_ci#include <linux/init.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/sched.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 188c2ecf20Sopenharmony_ci#include <media/v4l2-dev.h> 198c2ecf20Sopenharmony_ci#include <media/v4l2-fh.h> 208c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 218c2ecf20Sopenharmony_ci#include <media/v4l2-event.h> 228c2ecf20Sopenharmony_ci#include "radio-tea5777.h" 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <perex@perex.cz>"); 258c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Routines for control of TEA5777 Philips AM/FM radio tuner chips"); 268c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define TEA5777_FM_IF 150 /* kHz */ 298c2ecf20Sopenharmony_ci#define TEA5777_FM_FREQ_STEP 50 /* kHz */ 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define TEA5777_AM_IF 21 /* kHz */ 328c2ecf20Sopenharmony_ci#define TEA5777_AM_FREQ_STEP 1 /* kHz */ 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci/* Write reg, common bits */ 358c2ecf20Sopenharmony_ci#define TEA5777_W_MUTE_MASK (1LL << 47) 368c2ecf20Sopenharmony_ci#define TEA5777_W_MUTE_SHIFT 47 378c2ecf20Sopenharmony_ci#define TEA5777_W_AM_FM_MASK (1LL << 46) 388c2ecf20Sopenharmony_ci#define TEA5777_W_AM_FM_SHIFT 46 398c2ecf20Sopenharmony_ci#define TEA5777_W_STB_MASK (1LL << 45) 408c2ecf20Sopenharmony_ci#define TEA5777_W_STB_SHIFT 45 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define TEA5777_W_IFCE_MASK (1LL << 29) 438c2ecf20Sopenharmony_ci#define TEA5777_W_IFCE_SHIFT 29 448c2ecf20Sopenharmony_ci#define TEA5777_W_IFW_MASK (1LL << 28) 458c2ecf20Sopenharmony_ci#define TEA5777_W_IFW_SHIFT 28 468c2ecf20Sopenharmony_ci#define TEA5777_W_HILO_MASK (1LL << 27) 478c2ecf20Sopenharmony_ci#define TEA5777_W_HILO_SHIFT 27 488c2ecf20Sopenharmony_ci#define TEA5777_W_DBUS_MASK (1LL << 26) 498c2ecf20Sopenharmony_ci#define TEA5777_W_DBUS_SHIFT 26 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci#define TEA5777_W_INTEXT_MASK (1LL << 24) 528c2ecf20Sopenharmony_ci#define TEA5777_W_INTEXT_SHIFT 24 538c2ecf20Sopenharmony_ci#define TEA5777_W_P1_MASK (1LL << 23) 548c2ecf20Sopenharmony_ci#define TEA5777_W_P1_SHIFT 23 558c2ecf20Sopenharmony_ci#define TEA5777_W_P0_MASK (1LL << 22) 568c2ecf20Sopenharmony_ci#define TEA5777_W_P0_SHIFT 22 578c2ecf20Sopenharmony_ci#define TEA5777_W_PEN1_MASK (1LL << 21) 588c2ecf20Sopenharmony_ci#define TEA5777_W_PEN1_SHIFT 21 598c2ecf20Sopenharmony_ci#define TEA5777_W_PEN0_MASK (1LL << 20) 608c2ecf20Sopenharmony_ci#define TEA5777_W_PEN0_SHIFT 20 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci#define TEA5777_W_CHP0_MASK (1LL << 18) 638c2ecf20Sopenharmony_ci#define TEA5777_W_CHP0_SHIFT 18 648c2ecf20Sopenharmony_ci#define TEA5777_W_DEEM_MASK (1LL << 17) 658c2ecf20Sopenharmony_ci#define TEA5777_W_DEEM_SHIFT 17 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci#define TEA5777_W_SEARCH_MASK (1LL << 7) 688c2ecf20Sopenharmony_ci#define TEA5777_W_SEARCH_SHIFT 7 698c2ecf20Sopenharmony_ci#define TEA5777_W_PROGBLIM_MASK (1LL << 6) 708c2ecf20Sopenharmony_ci#define TEA5777_W_PROGBLIM_SHIFT 6 718c2ecf20Sopenharmony_ci#define TEA5777_W_UPDWN_MASK (1LL << 5) 728c2ecf20Sopenharmony_ci#define TEA5777_W_UPDWN_SHIFT 5 738c2ecf20Sopenharmony_ci#define TEA5777_W_SLEV_MASK (3LL << 3) 748c2ecf20Sopenharmony_ci#define TEA5777_W_SLEV_SHIFT 3 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci/* Write reg, FM specific bits */ 778c2ecf20Sopenharmony_ci#define TEA5777_W_FM_PLL_MASK (0x1fffLL << 32) 788c2ecf20Sopenharmony_ci#define TEA5777_W_FM_PLL_SHIFT 32 798c2ecf20Sopenharmony_ci#define TEA5777_W_FM_FREF_MASK (0x03LL << 30) 808c2ecf20Sopenharmony_ci#define TEA5777_W_FM_FREF_SHIFT 30 818c2ecf20Sopenharmony_ci#define TEA5777_W_FM_FREF_VALUE 0LL /* 50k steps, 150k IF */ 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci#define TEA5777_W_FM_FORCEMONO_MASK (1LL << 15) 848c2ecf20Sopenharmony_ci#define TEA5777_W_FM_FORCEMONO_SHIFT 15 858c2ecf20Sopenharmony_ci#define TEA5777_W_FM_SDSOFF_MASK (1LL << 14) 868c2ecf20Sopenharmony_ci#define TEA5777_W_FM_SDSOFF_SHIFT 14 878c2ecf20Sopenharmony_ci#define TEA5777_W_FM_DOFF_MASK (1LL << 13) 888c2ecf20Sopenharmony_ci#define TEA5777_W_FM_DOFF_SHIFT 13 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci#define TEA5777_W_FM_STEP_MASK (3LL << 1) 918c2ecf20Sopenharmony_ci#define TEA5777_W_FM_STEP_SHIFT 1 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci/* Write reg, AM specific bits */ 948c2ecf20Sopenharmony_ci#define TEA5777_W_AM_PLL_MASK (0x7ffLL << 34) 958c2ecf20Sopenharmony_ci#define TEA5777_W_AM_PLL_SHIFT 34 968c2ecf20Sopenharmony_ci#define TEA5777_W_AM_AGCRF_MASK (1LL << 33) 978c2ecf20Sopenharmony_ci#define TEA5777_W_AM_AGCRF_SHIFT 33 988c2ecf20Sopenharmony_ci#define TEA5777_W_AM_AGCIF_MASK (1LL << 32) 998c2ecf20Sopenharmony_ci#define TEA5777_W_AM_AGCIF_SHIFT 32 1008c2ecf20Sopenharmony_ci#define TEA5777_W_AM_MWLW_MASK (1LL << 31) 1018c2ecf20Sopenharmony_ci#define TEA5777_W_AM_MWLW_SHIFT 31 1028c2ecf20Sopenharmony_ci#define TEA5777_W_AM_LW 0LL 1038c2ecf20Sopenharmony_ci#define TEA5777_W_AM_MW 1LL 1048c2ecf20Sopenharmony_ci#define TEA5777_W_AM_LNA_MASK (1LL << 30) 1058c2ecf20Sopenharmony_ci#define TEA5777_W_AM_LNA_SHIFT 30 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci#define TEA5777_W_AM_PEAK_MASK (1LL << 25) 1088c2ecf20Sopenharmony_ci#define TEA5777_W_AM_PEAK_SHIFT 25 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci#define TEA5777_W_AM_RFB_MASK (1LL << 16) 1118c2ecf20Sopenharmony_ci#define TEA5777_W_AM_RFB_SHIFT 16 1128c2ecf20Sopenharmony_ci#define TEA5777_W_AM_CALLIGN_MASK (1LL << 15) 1138c2ecf20Sopenharmony_ci#define TEA5777_W_AM_CALLIGN_SHIFT 15 1148c2ecf20Sopenharmony_ci#define TEA5777_W_AM_CBANK_MASK (0x7fLL << 8) 1158c2ecf20Sopenharmony_ci#define TEA5777_W_AM_CBANK_SHIFT 8 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci#define TEA5777_W_AM_DELAY_MASK (1LL << 2) 1188c2ecf20Sopenharmony_ci#define TEA5777_W_AM_DELAY_SHIFT 2 1198c2ecf20Sopenharmony_ci#define TEA5777_W_AM_STEP_MASK (1LL << 1) 1208c2ecf20Sopenharmony_ci#define TEA5777_W_AM_STEP_SHIFT 1 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci/* Read reg, common bits */ 1238c2ecf20Sopenharmony_ci#define TEA5777_R_LEVEL_MASK (0x0f << 17) 1248c2ecf20Sopenharmony_ci#define TEA5777_R_LEVEL_SHIFT 17 1258c2ecf20Sopenharmony_ci#define TEA5777_R_SFOUND_MASK (0x01 << 16) 1268c2ecf20Sopenharmony_ci#define TEA5777_R_SFOUND_SHIFT 16 1278c2ecf20Sopenharmony_ci#define TEA5777_R_BLIM_MASK (0x01 << 15) 1288c2ecf20Sopenharmony_ci#define TEA5777_R_BLIM_SHIFT 15 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci/* Read reg, FM specific bits */ 1318c2ecf20Sopenharmony_ci#define TEA5777_R_FM_STEREO_MASK (0x01 << 21) 1328c2ecf20Sopenharmony_ci#define TEA5777_R_FM_STEREO_SHIFT 21 1338c2ecf20Sopenharmony_ci#define TEA5777_R_FM_PLL_MASK 0x1fff 1348c2ecf20Sopenharmony_ci#define TEA5777_R_FM_PLL_SHIFT 0 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_cienum { BAND_FM, BAND_AM }; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistatic const struct v4l2_frequency_band bands[] = { 1398c2ecf20Sopenharmony_ci { 1408c2ecf20Sopenharmony_ci .type = V4L2_TUNER_RADIO, 1418c2ecf20Sopenharmony_ci .index = 0, 1428c2ecf20Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 1438c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS | 1448c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 1458c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_PROG_LIM, 1468c2ecf20Sopenharmony_ci .rangelow = 76000 * 16, 1478c2ecf20Sopenharmony_ci .rangehigh = 108000 * 16, 1488c2ecf20Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 1498c2ecf20Sopenharmony_ci }, 1508c2ecf20Sopenharmony_ci { 1518c2ecf20Sopenharmony_ci .type = V4L2_TUNER_RADIO, 1528c2ecf20Sopenharmony_ci .index = 1, 1538c2ecf20Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS | 1548c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 1558c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_PROG_LIM, 1568c2ecf20Sopenharmony_ci .rangelow = 530 * 16, 1578c2ecf20Sopenharmony_ci .rangehigh = 1710 * 16, 1588c2ecf20Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_AM, 1598c2ecf20Sopenharmony_ci }, 1608c2ecf20Sopenharmony_ci}; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cistatic u32 tea5777_freq_to_v4l2_freq(struct radio_tea5777 *tea, u32 freq) 1638c2ecf20Sopenharmony_ci{ 1648c2ecf20Sopenharmony_ci switch (tea->band) { 1658c2ecf20Sopenharmony_ci case BAND_FM: 1668c2ecf20Sopenharmony_ci return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16; 1678c2ecf20Sopenharmony_ci case BAND_AM: 1688c2ecf20Sopenharmony_ci return (freq * TEA5777_AM_FREQ_STEP + TEA5777_AM_IF) * 16; 1698c2ecf20Sopenharmony_ci } 1708c2ecf20Sopenharmony_ci return 0; /* Never reached */ 1718c2ecf20Sopenharmony_ci} 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ciint radio_tea5777_set_freq(struct radio_tea5777 *tea) 1748c2ecf20Sopenharmony_ci{ 1758c2ecf20Sopenharmony_ci u32 freq; 1768c2ecf20Sopenharmony_ci int res; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci freq = clamp(tea->freq, bands[tea->band].rangelow, 1798c2ecf20Sopenharmony_ci bands[tea->band].rangehigh); 1808c2ecf20Sopenharmony_ci freq = (freq + 8) / 16; /* to kHz */ 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci switch (tea->band) { 1838c2ecf20Sopenharmony_ci case BAND_FM: 1848c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_FM_MASK; 1858c2ecf20Sopenharmony_ci freq = (freq - TEA5777_FM_IF) / TEA5777_FM_FREQ_STEP; 1868c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_FM_PLL_MASK; 1878c2ecf20Sopenharmony_ci tea->write_reg |= (u64)freq << TEA5777_W_FM_PLL_SHIFT; 1888c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_FM_FREF_MASK; 1898c2ecf20Sopenharmony_ci tea->write_reg |= TEA5777_W_FM_FREF_VALUE << 1908c2ecf20Sopenharmony_ci TEA5777_W_FM_FREF_SHIFT; 1918c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK; 1928c2ecf20Sopenharmony_ci if (tea->audmode == V4L2_TUNER_MODE_MONO) 1938c2ecf20Sopenharmony_ci tea->write_reg |= 1LL << TEA5777_W_FM_FORCEMONO_SHIFT; 1948c2ecf20Sopenharmony_ci break; 1958c2ecf20Sopenharmony_ci case BAND_AM: 1968c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_FM_MASK; 1978c2ecf20Sopenharmony_ci tea->write_reg |= (1LL << TEA5777_W_AM_FM_SHIFT); 1988c2ecf20Sopenharmony_ci freq = (freq - TEA5777_AM_IF) / TEA5777_AM_FREQ_STEP; 1998c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_PLL_MASK; 2008c2ecf20Sopenharmony_ci tea->write_reg |= (u64)freq << TEA5777_W_AM_PLL_SHIFT; 2018c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK; 2028c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK; 2038c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_MWLW_MASK; 2048c2ecf20Sopenharmony_ci tea->write_reg |= TEA5777_W_AM_MW << TEA5777_W_AM_MWLW_SHIFT; 2058c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_LNA_MASK; 2068c2ecf20Sopenharmony_ci tea->write_reg |= 1LL << TEA5777_W_AM_LNA_SHIFT; 2078c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_PEAK_MASK; 2088c2ecf20Sopenharmony_ci tea->write_reg |= 1LL << TEA5777_W_AM_PEAK_SHIFT; 2098c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_AM_CALLIGN_MASK; 2108c2ecf20Sopenharmony_ci break; 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci res = tea->ops->write_reg(tea, tea->write_reg); 2148c2ecf20Sopenharmony_ci if (res) 2158c2ecf20Sopenharmony_ci return res; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci tea->needs_write = false; 2188c2ecf20Sopenharmony_ci tea->read_reg = -1; 2198c2ecf20Sopenharmony_ci tea->freq = tea5777_freq_to_v4l2_freq(tea, freq); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci return 0; 2228c2ecf20Sopenharmony_ci} 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_cistatic int radio_tea5777_update_read_reg(struct radio_tea5777 *tea, int wait) 2258c2ecf20Sopenharmony_ci{ 2268c2ecf20Sopenharmony_ci int res; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci if (tea->read_reg != -1) 2298c2ecf20Sopenharmony_ci return 0; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci if (tea->write_before_read && tea->needs_write) { 2328c2ecf20Sopenharmony_ci res = radio_tea5777_set_freq(tea); 2338c2ecf20Sopenharmony_ci if (res) 2348c2ecf20Sopenharmony_ci return res; 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci if (wait) { 2388c2ecf20Sopenharmony_ci if (schedule_timeout_interruptible(msecs_to_jiffies(wait))) 2398c2ecf20Sopenharmony_ci return -ERESTARTSYS; 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci res = tea->ops->read_reg(tea, &tea->read_reg); 2438c2ecf20Sopenharmony_ci if (res) 2448c2ecf20Sopenharmony_ci return res; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci tea->needs_write = true; 2478c2ecf20Sopenharmony_ci return 0; 2488c2ecf20Sopenharmony_ci} 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci/* 2518c2ecf20Sopenharmony_ci * Linux Video interface 2528c2ecf20Sopenharmony_ci */ 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 2558c2ecf20Sopenharmony_ci struct v4l2_capability *v) 2568c2ecf20Sopenharmony_ci{ 2578c2ecf20Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver)); 2608c2ecf20Sopenharmony_ci strscpy(v->card, tea->card, sizeof(v->card)); 2618c2ecf20Sopenharmony_ci strlcat(v->card, " TEA5777", sizeof(v->card)); 2628c2ecf20Sopenharmony_ci strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info)); 2638c2ecf20Sopenharmony_ci return 0; 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic int vidioc_enum_freq_bands(struct file *file, void *priv, 2678c2ecf20Sopenharmony_ci struct v4l2_frequency_band *band) 2688c2ecf20Sopenharmony_ci{ 2698c2ecf20Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci if (band->tuner != 0 || band->index >= ARRAY_SIZE(bands) || 2728c2ecf20Sopenharmony_ci (!tea->has_am && band->index == BAND_AM)) 2738c2ecf20Sopenharmony_ci return -EINVAL; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci *band = bands[band->index]; 2768c2ecf20Sopenharmony_ci return 0; 2778c2ecf20Sopenharmony_ci} 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv, 2808c2ecf20Sopenharmony_ci struct v4l2_tuner *v) 2818c2ecf20Sopenharmony_ci{ 2828c2ecf20Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 2838c2ecf20Sopenharmony_ci int res; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci if (v->index > 0) 2868c2ecf20Sopenharmony_ci return -EINVAL; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci res = radio_tea5777_update_read_reg(tea, 0); 2898c2ecf20Sopenharmony_ci if (res) 2908c2ecf20Sopenharmony_ci return res; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci memset(v, 0, sizeof(*v)); 2938c2ecf20Sopenharmony_ci if (tea->has_am) 2948c2ecf20Sopenharmony_ci strscpy(v->name, "AM/FM", sizeof(v->name)); 2958c2ecf20Sopenharmony_ci else 2968c2ecf20Sopenharmony_ci strscpy(v->name, "FM", sizeof(v->name)); 2978c2ecf20Sopenharmony_ci v->type = V4L2_TUNER_RADIO; 2988c2ecf20Sopenharmony_ci v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 2998c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS | 3008c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 3018c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_PROG_LIM; 3028c2ecf20Sopenharmony_ci v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : 3038c2ecf20Sopenharmony_ci bands[BAND_FM].rangelow; 3048c2ecf20Sopenharmony_ci v->rangehigh = bands[BAND_FM].rangehigh; 3058c2ecf20Sopenharmony_ci if (tea->band == BAND_FM && 3068c2ecf20Sopenharmony_ci (tea->read_reg & TEA5777_R_FM_STEREO_MASK)) 3078c2ecf20Sopenharmony_ci v->rxsubchans = V4L2_TUNER_SUB_STEREO; 3088c2ecf20Sopenharmony_ci else 3098c2ecf20Sopenharmony_ci v->rxsubchans = V4L2_TUNER_SUB_MONO; 3108c2ecf20Sopenharmony_ci v->audmode = tea->audmode; 3118c2ecf20Sopenharmony_ci /* shift - 12 to convert 4-bits (0-15) scale to 16-bits (0-65535) */ 3128c2ecf20Sopenharmony_ci v->signal = (tea->read_reg & TEA5777_R_LEVEL_MASK) >> 3138c2ecf20Sopenharmony_ci (TEA5777_R_LEVEL_SHIFT - 12); 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci /* Invalidate read_reg, so that next call we return up2date signal */ 3168c2ecf20Sopenharmony_ci tea->read_reg = -1; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci return 0; 3198c2ecf20Sopenharmony_ci} 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv, 3228c2ecf20Sopenharmony_ci const struct v4l2_tuner *v) 3238c2ecf20Sopenharmony_ci{ 3248c2ecf20Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 3258c2ecf20Sopenharmony_ci u32 orig_audmode = tea->audmode; 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci if (v->index) 3288c2ecf20Sopenharmony_ci return -EINVAL; 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci tea->audmode = v->audmode; 3318c2ecf20Sopenharmony_ci if (tea->audmode > V4L2_TUNER_MODE_STEREO) 3328c2ecf20Sopenharmony_ci tea->audmode = V4L2_TUNER_MODE_STEREO; 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_ci if (tea->audmode != orig_audmode && tea->band == BAND_FM) 3358c2ecf20Sopenharmony_ci return radio_tea5777_set_freq(tea); 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci return 0; 3388c2ecf20Sopenharmony_ci} 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 3418c2ecf20Sopenharmony_ci struct v4l2_frequency *f) 3428c2ecf20Sopenharmony_ci{ 3438c2ecf20Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci if (f->tuner != 0) 3468c2ecf20Sopenharmony_ci return -EINVAL; 3478c2ecf20Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 3488c2ecf20Sopenharmony_ci f->frequency = tea->freq; 3498c2ecf20Sopenharmony_ci return 0; 3508c2ecf20Sopenharmony_ci} 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 3538c2ecf20Sopenharmony_ci const struct v4l2_frequency *f) 3548c2ecf20Sopenharmony_ci{ 3558c2ecf20Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 3588c2ecf20Sopenharmony_ci return -EINVAL; 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci if (tea->has_am && f->frequency < (20000 * 16)) 3618c2ecf20Sopenharmony_ci tea->band = BAND_AM; 3628c2ecf20Sopenharmony_ci else 3638c2ecf20Sopenharmony_ci tea->band = BAND_FM; 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci tea->freq = f->frequency; 3668c2ecf20Sopenharmony_ci return radio_tea5777_set_freq(tea); 3678c2ecf20Sopenharmony_ci} 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_cistatic int vidioc_s_hw_freq_seek(struct file *file, void *fh, 3708c2ecf20Sopenharmony_ci const struct v4l2_hw_freq_seek *a) 3718c2ecf20Sopenharmony_ci{ 3728c2ecf20Sopenharmony_ci struct radio_tea5777 *tea = video_drvdata(file); 3738c2ecf20Sopenharmony_ci unsigned long timeout; 3748c2ecf20Sopenharmony_ci u32 rangelow = a->rangelow; 3758c2ecf20Sopenharmony_ci u32 rangehigh = a->rangehigh; 3768c2ecf20Sopenharmony_ci int i, res, spacing; 3778c2ecf20Sopenharmony_ci u32 orig_freq; 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_ci if (a->tuner || a->wrap_around) 3808c2ecf20Sopenharmony_ci return -EINVAL; 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 3838c2ecf20Sopenharmony_ci return -EWOULDBLOCK; 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci if (rangelow || rangehigh) { 3868c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(bands); i++) { 3878c2ecf20Sopenharmony_ci if (i == BAND_AM && !tea->has_am) 3888c2ecf20Sopenharmony_ci continue; 3898c2ecf20Sopenharmony_ci if (bands[i].rangelow >= rangelow && 3908c2ecf20Sopenharmony_ci bands[i].rangehigh <= rangehigh) 3918c2ecf20Sopenharmony_ci break; 3928c2ecf20Sopenharmony_ci } 3938c2ecf20Sopenharmony_ci if (i == ARRAY_SIZE(bands)) 3948c2ecf20Sopenharmony_ci return -EINVAL; /* No matching band found */ 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci tea->band = i; 3978c2ecf20Sopenharmony_ci if (tea->freq < rangelow || tea->freq > rangehigh) { 3988c2ecf20Sopenharmony_ci tea->freq = clamp(tea->freq, rangelow, 3998c2ecf20Sopenharmony_ci rangehigh); 4008c2ecf20Sopenharmony_ci res = radio_tea5777_set_freq(tea); 4018c2ecf20Sopenharmony_ci if (res) 4028c2ecf20Sopenharmony_ci return res; 4038c2ecf20Sopenharmony_ci } 4048c2ecf20Sopenharmony_ci } else { 4058c2ecf20Sopenharmony_ci rangelow = bands[tea->band].rangelow; 4068c2ecf20Sopenharmony_ci rangehigh = bands[tea->band].rangehigh; 4078c2ecf20Sopenharmony_ci } 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci spacing = (tea->band == BAND_AM) ? (5 * 16) : (200 * 16); /* kHz */ 4108c2ecf20Sopenharmony_ci orig_freq = tea->freq; 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_ci tea->write_reg |= TEA5777_W_PROGBLIM_MASK; 4138c2ecf20Sopenharmony_ci if (tea->seek_rangelow != rangelow) { 4148c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_UPDWN_MASK; 4158c2ecf20Sopenharmony_ci tea->freq = rangelow; 4168c2ecf20Sopenharmony_ci res = radio_tea5777_set_freq(tea); 4178c2ecf20Sopenharmony_ci if (res) 4188c2ecf20Sopenharmony_ci goto leave; 4198c2ecf20Sopenharmony_ci tea->seek_rangelow = rangelow; 4208c2ecf20Sopenharmony_ci } 4218c2ecf20Sopenharmony_ci if (tea->seek_rangehigh != rangehigh) { 4228c2ecf20Sopenharmony_ci tea->write_reg |= TEA5777_W_UPDWN_MASK; 4238c2ecf20Sopenharmony_ci tea->freq = rangehigh; 4248c2ecf20Sopenharmony_ci res = radio_tea5777_set_freq(tea); 4258c2ecf20Sopenharmony_ci if (res) 4268c2ecf20Sopenharmony_ci goto leave; 4278c2ecf20Sopenharmony_ci tea->seek_rangehigh = rangehigh; 4288c2ecf20Sopenharmony_ci } 4298c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK; 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci tea->write_reg |= TEA5777_W_SEARCH_MASK; 4328c2ecf20Sopenharmony_ci if (a->seek_upward) { 4338c2ecf20Sopenharmony_ci tea->write_reg |= TEA5777_W_UPDWN_MASK; 4348c2ecf20Sopenharmony_ci tea->freq = orig_freq + spacing; 4358c2ecf20Sopenharmony_ci } else { 4368c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_UPDWN_MASK; 4378c2ecf20Sopenharmony_ci tea->freq = orig_freq - spacing; 4388c2ecf20Sopenharmony_ci } 4398c2ecf20Sopenharmony_ci res = radio_tea5777_set_freq(tea); 4408c2ecf20Sopenharmony_ci if (res) 4418c2ecf20Sopenharmony_ci goto leave; 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci timeout = jiffies + msecs_to_jiffies(5000); 4448c2ecf20Sopenharmony_ci for (;;) { 4458c2ecf20Sopenharmony_ci if (time_after(jiffies, timeout)) { 4468c2ecf20Sopenharmony_ci res = -ENODATA; 4478c2ecf20Sopenharmony_ci break; 4488c2ecf20Sopenharmony_ci } 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_ci res = radio_tea5777_update_read_reg(tea, 100); 4518c2ecf20Sopenharmony_ci if (res) 4528c2ecf20Sopenharmony_ci break; 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci /* 4558c2ecf20Sopenharmony_ci * Note we use tea->freq to track how far we've searched sofar 4568c2ecf20Sopenharmony_ci * this is necessary to ensure we continue seeking at the right 4578c2ecf20Sopenharmony_ci * point, in the write_before_read case. 4588c2ecf20Sopenharmony_ci */ 4598c2ecf20Sopenharmony_ci tea->freq = (tea->read_reg & TEA5777_R_FM_PLL_MASK); 4608c2ecf20Sopenharmony_ci tea->freq = tea5777_freq_to_v4l2_freq(tea, tea->freq); 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci if ((tea->read_reg & TEA5777_R_SFOUND_MASK)) { 4638c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_SEARCH_MASK; 4648c2ecf20Sopenharmony_ci return 0; 4658c2ecf20Sopenharmony_ci } 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_ci if (tea->read_reg & TEA5777_R_BLIM_MASK) { 4688c2ecf20Sopenharmony_ci res = -ENODATA; 4698c2ecf20Sopenharmony_ci break; 4708c2ecf20Sopenharmony_ci } 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci /* Force read_reg update */ 4738c2ecf20Sopenharmony_ci tea->read_reg = -1; 4748c2ecf20Sopenharmony_ci } 4758c2ecf20Sopenharmony_cileave: 4768c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK; 4778c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_SEARCH_MASK; 4788c2ecf20Sopenharmony_ci tea->freq = orig_freq; 4798c2ecf20Sopenharmony_ci radio_tea5777_set_freq(tea); 4808c2ecf20Sopenharmony_ci return res; 4818c2ecf20Sopenharmony_ci} 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_cistatic int tea575x_s_ctrl(struct v4l2_ctrl *c) 4848c2ecf20Sopenharmony_ci{ 4858c2ecf20Sopenharmony_ci struct radio_tea5777 *tea = 4868c2ecf20Sopenharmony_ci container_of(c->handler, struct radio_tea5777, ctrl_handler); 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci switch (c->id) { 4898c2ecf20Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 4908c2ecf20Sopenharmony_ci if (c->val) 4918c2ecf20Sopenharmony_ci tea->write_reg |= TEA5777_W_MUTE_MASK; 4928c2ecf20Sopenharmony_ci else 4938c2ecf20Sopenharmony_ci tea->write_reg &= ~TEA5777_W_MUTE_MASK; 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci return radio_tea5777_set_freq(tea); 4968c2ecf20Sopenharmony_ci } 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci return -EINVAL; 4998c2ecf20Sopenharmony_ci} 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations tea575x_fops = { 5028c2ecf20Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 5038c2ecf20Sopenharmony_ci .open = v4l2_fh_open, 5048c2ecf20Sopenharmony_ci .release = v4l2_fh_release, 5058c2ecf20Sopenharmony_ci .poll = v4l2_ctrl_poll, 5068c2ecf20Sopenharmony_ci}; 5078c2ecf20Sopenharmony_ci 5088c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops tea575x_ioctl_ops = { 5098c2ecf20Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 5108c2ecf20Sopenharmony_ci .vidioc_g_tuner = vidioc_g_tuner, 5118c2ecf20Sopenharmony_ci .vidioc_s_tuner = vidioc_s_tuner, 5128c2ecf20Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 5138c2ecf20Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 5148c2ecf20Sopenharmony_ci .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, 5158c2ecf20Sopenharmony_ci .vidioc_enum_freq_bands = vidioc_enum_freq_bands, 5168c2ecf20Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 5178c2ecf20Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 5188c2ecf20Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 5198c2ecf20Sopenharmony_ci}; 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_cistatic const struct video_device tea575x_radio = { 5228c2ecf20Sopenharmony_ci .ioctl_ops = &tea575x_ioctl_ops, 5238c2ecf20Sopenharmony_ci .release = video_device_release_empty, 5248c2ecf20Sopenharmony_ci}; 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops tea575x_ctrl_ops = { 5278c2ecf20Sopenharmony_ci .s_ctrl = tea575x_s_ctrl, 5288c2ecf20Sopenharmony_ci}; 5298c2ecf20Sopenharmony_ci 5308c2ecf20Sopenharmony_ciint radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner) 5318c2ecf20Sopenharmony_ci{ 5328c2ecf20Sopenharmony_ci int res; 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci tea->write_reg = (1LL << TEA5777_W_IFCE_SHIFT) | 5358c2ecf20Sopenharmony_ci (1LL << TEA5777_W_IFW_SHIFT) | 5368c2ecf20Sopenharmony_ci (1LL << TEA5777_W_INTEXT_SHIFT) | 5378c2ecf20Sopenharmony_ci (1LL << TEA5777_W_CHP0_SHIFT) | 5388c2ecf20Sopenharmony_ci (1LL << TEA5777_W_SLEV_SHIFT); 5398c2ecf20Sopenharmony_ci tea->freq = 90500 * 16; /* 90.5Mhz default */ 5408c2ecf20Sopenharmony_ci tea->audmode = V4L2_TUNER_MODE_STEREO; 5418c2ecf20Sopenharmony_ci res = radio_tea5777_set_freq(tea); 5428c2ecf20Sopenharmony_ci if (res) { 5438c2ecf20Sopenharmony_ci v4l2_err(tea->v4l2_dev, "can't set initial freq (%d)\n", res); 5448c2ecf20Sopenharmony_ci return res; 5458c2ecf20Sopenharmony_ci } 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_ci tea->vd = tea575x_radio; 5488c2ecf20Sopenharmony_ci video_set_drvdata(&tea->vd, tea); 5498c2ecf20Sopenharmony_ci mutex_init(&tea->mutex); 5508c2ecf20Sopenharmony_ci strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name)); 5518c2ecf20Sopenharmony_ci tea->vd.lock = &tea->mutex; 5528c2ecf20Sopenharmony_ci tea->vd.v4l2_dev = tea->v4l2_dev; 5538c2ecf20Sopenharmony_ci tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO | 5548c2ecf20Sopenharmony_ci V4L2_CAP_HW_FREQ_SEEK; 5558c2ecf20Sopenharmony_ci tea->fops = tea575x_fops; 5568c2ecf20Sopenharmony_ci tea->fops.owner = owner; 5578c2ecf20Sopenharmony_ci tea->vd.fops = &tea->fops; 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci tea->vd.ctrl_handler = &tea->ctrl_handler; 5608c2ecf20Sopenharmony_ci v4l2_ctrl_handler_init(&tea->ctrl_handler, 1); 5618c2ecf20Sopenharmony_ci v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops, 5628c2ecf20Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 5638c2ecf20Sopenharmony_ci res = tea->ctrl_handler.error; 5648c2ecf20Sopenharmony_ci if (res) { 5658c2ecf20Sopenharmony_ci v4l2_err(tea->v4l2_dev, "can't initialize controls\n"); 5668c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(&tea->ctrl_handler); 5678c2ecf20Sopenharmony_ci return res; 5688c2ecf20Sopenharmony_ci } 5698c2ecf20Sopenharmony_ci v4l2_ctrl_handler_setup(&tea->ctrl_handler); 5708c2ecf20Sopenharmony_ci 5718c2ecf20Sopenharmony_ci res = video_register_device(&tea->vd, VFL_TYPE_RADIO, -1); 5728c2ecf20Sopenharmony_ci if (res) { 5738c2ecf20Sopenharmony_ci v4l2_err(tea->v4l2_dev, "can't register video device!\n"); 5748c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 5758c2ecf20Sopenharmony_ci return res; 5768c2ecf20Sopenharmony_ci } 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci return 0; 5798c2ecf20Sopenharmony_ci} 5808c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(radio_tea5777_init); 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_civoid radio_tea5777_exit(struct radio_tea5777 *tea) 5838c2ecf20Sopenharmony_ci{ 5848c2ecf20Sopenharmony_ci video_unregister_device(&tea->vd); 5858c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 5868c2ecf20Sopenharmony_ci} 5878c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(radio_tea5777_exit); 588