18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2012 Innovative Converged Devices(ICD) 68c2ecf20Sopenharmony_ci * Copyright (C) 2013 Andrey Smirnov 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Author: Andrey Smirnov <andrew.smirnov@gmail.com> 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/delay.h> 138c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci#include <linux/atomic.h> 168c2ecf20Sopenharmony_ci#include <linux/videodev2.h> 178c2ecf20Sopenharmony_ci#include <linux/mutex.h> 188c2ecf20Sopenharmony_ci#include <linux/debugfs.h> 198c2ecf20Sopenharmony_ci#include <media/v4l2-common.h> 208c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 218c2ecf20Sopenharmony_ci#include <media/v4l2-ctrls.h> 228c2ecf20Sopenharmony_ci#include <media/v4l2-event.h> 238c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#include <media/drv-intf/si476x.h> 268c2ecf20Sopenharmony_ci#include <linux/mfd/si476x-core.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define FM_FREQ_RANGE_LOW 64000000 298c2ecf20Sopenharmony_ci#define FM_FREQ_RANGE_HIGH 108000000 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define AM_FREQ_RANGE_LOW 520000 328c2ecf20Sopenharmony_ci#define AM_FREQ_RANGE_HIGH 30000000 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci#define PWRLINEFLTR (1 << 8) 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#define FREQ_MUL (10000000 / 625) 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0x80 & (status)) 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#define DRIVER_NAME "si476x-radio" 418c2ecf20Sopenharmony_ci#define DRIVER_CARD "SI476x AM/FM Receiver" 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cienum si476x_freq_bands { 448c2ecf20Sopenharmony_ci SI476X_BAND_FM, 458c2ecf20Sopenharmony_ci SI476X_BAND_AM, 468c2ecf20Sopenharmony_ci}; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic const struct v4l2_frequency_band si476x_bands[] = { 498c2ecf20Sopenharmony_ci [SI476X_BAND_FM] = { 508c2ecf20Sopenharmony_ci .type = V4L2_TUNER_RADIO, 518c2ecf20Sopenharmony_ci .index = SI476X_BAND_FM, 528c2ecf20Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW 538c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_STEREO 548c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_RDS 558c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_RDS_BLOCK_IO 568c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_FREQ_BANDS, 578c2ecf20Sopenharmony_ci .rangelow = 64 * FREQ_MUL, 588c2ecf20Sopenharmony_ci .rangehigh = 108 * FREQ_MUL, 598c2ecf20Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 608c2ecf20Sopenharmony_ci }, 618c2ecf20Sopenharmony_ci [SI476X_BAND_AM] = { 628c2ecf20Sopenharmony_ci .type = V4L2_TUNER_RADIO, 638c2ecf20Sopenharmony_ci .index = SI476X_BAND_AM, 648c2ecf20Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW 658c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_FREQ_BANDS, 668c2ecf20Sopenharmony_ci .rangelow = 0.52 * FREQ_MUL, 678c2ecf20Sopenharmony_ci .rangehigh = 30 * FREQ_MUL, 688c2ecf20Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_AM, 698c2ecf20Sopenharmony_ci }, 708c2ecf20Sopenharmony_ci}; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_cistatic inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq, int band) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci return freq >= si476x_bands[band].rangelow && 758c2ecf20Sopenharmony_ci freq <= si476x_bands[band].rangehigh; 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic inline bool si476x_radio_range_is_inside_of_the_band(u32 low, u32 high, 798c2ecf20Sopenharmony_ci int band) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci return low >= si476x_bands[band].rangelow && 828c2ecf20Sopenharmony_ci high <= si476x_bands[band].rangehigh; 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl); 868c2ecf20Sopenharmony_cistatic int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_cienum phase_diversity_modes_idx { 898c2ecf20Sopenharmony_ci SI476X_IDX_PHDIV_DISABLED, 908c2ecf20Sopenharmony_ci SI476X_IDX_PHDIV_PRIMARY_COMBINING, 918c2ecf20Sopenharmony_ci SI476X_IDX_PHDIV_PRIMARY_ANTENNA, 928c2ecf20Sopenharmony_ci SI476X_IDX_PHDIV_SECONDARY_ANTENNA, 938c2ecf20Sopenharmony_ci SI476X_IDX_PHDIV_SECONDARY_COMBINING, 948c2ecf20Sopenharmony_ci}; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic const char * const phase_diversity_modes[] = { 978c2ecf20Sopenharmony_ci [SI476X_IDX_PHDIV_DISABLED] = "Disabled", 988c2ecf20Sopenharmony_ci [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary with Secondary", 998c2ecf20Sopenharmony_ci [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary Antenna", 1008c2ecf20Sopenharmony_ci [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Secondary Antenna", 1018c2ecf20Sopenharmony_ci [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary with Primary", 1028c2ecf20Sopenharmony_ci}; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic inline enum phase_diversity_modes_idx 1058c2ecf20Sopenharmony_cisi476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode) 1068c2ecf20Sopenharmony_ci{ 1078c2ecf20Sopenharmony_ci switch (mode) { 1088c2ecf20Sopenharmony_ci default: 1098c2ecf20Sopenharmony_ci fallthrough; 1108c2ecf20Sopenharmony_ci case SI476X_PHDIV_DISABLED: 1118c2ecf20Sopenharmony_ci return SI476X_IDX_PHDIV_DISABLED; 1128c2ecf20Sopenharmony_ci case SI476X_PHDIV_PRIMARY_COMBINING: 1138c2ecf20Sopenharmony_ci return SI476X_IDX_PHDIV_PRIMARY_COMBINING; 1148c2ecf20Sopenharmony_ci case SI476X_PHDIV_PRIMARY_ANTENNA: 1158c2ecf20Sopenharmony_ci return SI476X_IDX_PHDIV_PRIMARY_ANTENNA; 1168c2ecf20Sopenharmony_ci case SI476X_PHDIV_SECONDARY_ANTENNA: 1178c2ecf20Sopenharmony_ci return SI476X_IDX_PHDIV_SECONDARY_ANTENNA; 1188c2ecf20Sopenharmony_ci case SI476X_PHDIV_SECONDARY_COMBINING: 1198c2ecf20Sopenharmony_ci return SI476X_IDX_PHDIV_SECONDARY_COMBINING; 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic inline enum si476x_phase_diversity_mode 1248c2ecf20Sopenharmony_cisi476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci static const int idx_to_value[] = { 1278c2ecf20Sopenharmony_ci [SI476X_IDX_PHDIV_DISABLED] = SI476X_PHDIV_DISABLED, 1288c2ecf20Sopenharmony_ci [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = SI476X_PHDIV_PRIMARY_COMBINING, 1298c2ecf20Sopenharmony_ci [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = SI476X_PHDIV_PRIMARY_ANTENNA, 1308c2ecf20Sopenharmony_ci [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = SI476X_PHDIV_SECONDARY_ANTENNA, 1318c2ecf20Sopenharmony_ci [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = SI476X_PHDIV_SECONDARY_COMBINING, 1328c2ecf20Sopenharmony_ci }; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci return idx_to_value[idx]; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops si476x_ctrl_ops = { 1388c2ecf20Sopenharmony_ci .g_volatile_ctrl = si476x_radio_g_volatile_ctrl, 1398c2ecf20Sopenharmony_ci .s_ctrl = si476x_radio_s_ctrl, 1408c2ecf20Sopenharmony_ci}; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cienum si476x_ctrl_idx { 1448c2ecf20Sopenharmony_ci SI476X_IDX_RSSI_THRESHOLD, 1458c2ecf20Sopenharmony_ci SI476X_IDX_SNR_THRESHOLD, 1468c2ecf20Sopenharmony_ci SI476X_IDX_MAX_TUNE_ERROR, 1478c2ecf20Sopenharmony_ci SI476X_IDX_HARMONICS_COUNT, 1488c2ecf20Sopenharmony_ci SI476X_IDX_DIVERSITY_MODE, 1498c2ecf20Sopenharmony_ci SI476X_IDX_INTERCHIP_LINK, 1508c2ecf20Sopenharmony_ci}; 1518c2ecf20Sopenharmony_cistatic struct v4l2_ctrl_config si476x_ctrls[] = { 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci /* 1548c2ecf20Sopenharmony_ci * SI476X during its station seeking(or tuning) process uses several 1558c2ecf20Sopenharmony_ci * parameters to detrmine if "the station" is valid: 1568c2ecf20Sopenharmony_ci * 1578c2ecf20Sopenharmony_ci * - Signal's SNR(in dBuV) must be lower than 1588c2ecf20Sopenharmony_ci * #V4L2_CID_SI476X_SNR_THRESHOLD 1598c2ecf20Sopenharmony_ci * - Signal's RSSI(in dBuV) must be greater than 1608c2ecf20Sopenharmony_ci * #V4L2_CID_SI476X_RSSI_THRESHOLD 1618c2ecf20Sopenharmony_ci * - Signal's frequency deviation(in units of 2ppm) must not be 1628c2ecf20Sopenharmony_ci * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR 1638c2ecf20Sopenharmony_ci */ 1648c2ecf20Sopenharmony_ci [SI476X_IDX_RSSI_THRESHOLD] = { 1658c2ecf20Sopenharmony_ci .ops = &si476x_ctrl_ops, 1668c2ecf20Sopenharmony_ci .id = V4L2_CID_SI476X_RSSI_THRESHOLD, 1678c2ecf20Sopenharmony_ci .name = "Valid RSSI Threshold", 1688c2ecf20Sopenharmony_ci .type = V4L2_CTRL_TYPE_INTEGER, 1698c2ecf20Sopenharmony_ci .min = -128, 1708c2ecf20Sopenharmony_ci .max = 127, 1718c2ecf20Sopenharmony_ci .step = 1, 1728c2ecf20Sopenharmony_ci }, 1738c2ecf20Sopenharmony_ci [SI476X_IDX_SNR_THRESHOLD] = { 1748c2ecf20Sopenharmony_ci .ops = &si476x_ctrl_ops, 1758c2ecf20Sopenharmony_ci .id = V4L2_CID_SI476X_SNR_THRESHOLD, 1768c2ecf20Sopenharmony_ci .type = V4L2_CTRL_TYPE_INTEGER, 1778c2ecf20Sopenharmony_ci .name = "Valid SNR Threshold", 1788c2ecf20Sopenharmony_ci .min = -128, 1798c2ecf20Sopenharmony_ci .max = 127, 1808c2ecf20Sopenharmony_ci .step = 1, 1818c2ecf20Sopenharmony_ci }, 1828c2ecf20Sopenharmony_ci [SI476X_IDX_MAX_TUNE_ERROR] = { 1838c2ecf20Sopenharmony_ci .ops = &si476x_ctrl_ops, 1848c2ecf20Sopenharmony_ci .id = V4L2_CID_SI476X_MAX_TUNE_ERROR, 1858c2ecf20Sopenharmony_ci .type = V4L2_CTRL_TYPE_INTEGER, 1868c2ecf20Sopenharmony_ci .name = "Max Tune Errors", 1878c2ecf20Sopenharmony_ci .min = 0, 1888c2ecf20Sopenharmony_ci .max = 126 * 2, 1898c2ecf20Sopenharmony_ci .step = 2, 1908c2ecf20Sopenharmony_ci }, 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci /* 1938c2ecf20Sopenharmony_ci * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics 1948c2ecf20Sopenharmony_ci * built-in power-line noise supression filter is to reject 1958c2ecf20Sopenharmony_ci * during AM-mode operation. 1968c2ecf20Sopenharmony_ci */ 1978c2ecf20Sopenharmony_ci [SI476X_IDX_HARMONICS_COUNT] = { 1988c2ecf20Sopenharmony_ci .ops = &si476x_ctrl_ops, 1998c2ecf20Sopenharmony_ci .id = V4L2_CID_SI476X_HARMONICS_COUNT, 2008c2ecf20Sopenharmony_ci .type = V4L2_CTRL_TYPE_INTEGER, 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci .name = "Count of Harmonics to Reject", 2038c2ecf20Sopenharmony_ci .min = 0, 2048c2ecf20Sopenharmony_ci .max = 20, 2058c2ecf20Sopenharmony_ci .step = 1, 2068c2ecf20Sopenharmony_ci }, 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci /* 2098c2ecf20Sopenharmony_ci * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which 2108c2ecf20Sopenharmony_ci * two tuners working in diversity mode are to work in. 2118c2ecf20Sopenharmony_ci * 2128c2ecf20Sopenharmony_ci * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled 2138c2ecf20Sopenharmony_ci * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is 2148c2ecf20Sopenharmony_ci * on, primary tuner's antenna is the main one. 2158c2ecf20Sopenharmony_ci * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is 2168c2ecf20Sopenharmony_ci * off, primary tuner's antenna is the main one. 2178c2ecf20Sopenharmony_ci * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is 2188c2ecf20Sopenharmony_ci * off, secondary tuner's antenna is the main one. 2198c2ecf20Sopenharmony_ci * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is 2208c2ecf20Sopenharmony_ci * on, secondary tuner's antenna is the main one. 2218c2ecf20Sopenharmony_ci */ 2228c2ecf20Sopenharmony_ci [SI476X_IDX_DIVERSITY_MODE] = { 2238c2ecf20Sopenharmony_ci .ops = &si476x_ctrl_ops, 2248c2ecf20Sopenharmony_ci .id = V4L2_CID_SI476X_DIVERSITY_MODE, 2258c2ecf20Sopenharmony_ci .type = V4L2_CTRL_TYPE_MENU, 2268c2ecf20Sopenharmony_ci .name = "Phase Diversity Mode", 2278c2ecf20Sopenharmony_ci .qmenu = phase_diversity_modes, 2288c2ecf20Sopenharmony_ci .min = 0, 2298c2ecf20Sopenharmony_ci .max = ARRAY_SIZE(phase_diversity_modes) - 1, 2308c2ecf20Sopenharmony_ci }, 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci /* 2338c2ecf20Sopenharmony_ci * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in 2348c2ecf20Sopenharmony_ci * diversity mode indicator. Allows user to determine if two 2358c2ecf20Sopenharmony_ci * chips working in diversity mode have established a link 2368c2ecf20Sopenharmony_ci * between each other and if the system as a whole uses 2378c2ecf20Sopenharmony_ci * signals from both antennas to receive FM radio. 2388c2ecf20Sopenharmony_ci */ 2398c2ecf20Sopenharmony_ci [SI476X_IDX_INTERCHIP_LINK] = { 2408c2ecf20Sopenharmony_ci .ops = &si476x_ctrl_ops, 2418c2ecf20Sopenharmony_ci .id = V4L2_CID_SI476X_INTERCHIP_LINK, 2428c2ecf20Sopenharmony_ci .type = V4L2_CTRL_TYPE_BOOLEAN, 2438c2ecf20Sopenharmony_ci .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, 2448c2ecf20Sopenharmony_ci .name = "Inter-Chip Link", 2458c2ecf20Sopenharmony_ci .min = 0, 2468c2ecf20Sopenharmony_ci .max = 1, 2478c2ecf20Sopenharmony_ci .step = 1, 2488c2ecf20Sopenharmony_ci }, 2498c2ecf20Sopenharmony_ci}; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_cistruct si476x_radio; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci/** 2548c2ecf20Sopenharmony_ci * struct si476x_radio_ops - vtable of tuner functions 2558c2ecf20Sopenharmony_ci * 2568c2ecf20Sopenharmony_ci * This table holds pointers to functions implementing particular 2578c2ecf20Sopenharmony_ci * operations depending on the mode in which the tuner chip was 2588c2ecf20Sopenharmony_ci * configured to start in. If the function is not supported 2598c2ecf20Sopenharmony_ci * corresponding element is set to #NULL. 2608c2ecf20Sopenharmony_ci * 2618c2ecf20Sopenharmony_ci * @tune_freq: Tune chip to a specific frequency 2628c2ecf20Sopenharmony_ci * @seek_start: Star station seeking 2638c2ecf20Sopenharmony_ci * @rsq_status: Get Received Signal Quality(RSQ) status 2648c2ecf20Sopenharmony_ci * @rds_blckcnt: Get received RDS blocks count 2658c2ecf20Sopenharmony_ci * @phase_diversity: Change phase diversity mode of the tuner 2668c2ecf20Sopenharmony_ci * @phase_div_status: Get phase diversity mode status 2678c2ecf20Sopenharmony_ci * @acf_status: Get the status of Automatically Controlled 2688c2ecf20Sopenharmony_ci * Features(ACF) 2698c2ecf20Sopenharmony_ci * @agc_status: Get Automatic Gain Control(AGC) status 2708c2ecf20Sopenharmony_ci */ 2718c2ecf20Sopenharmony_cistruct si476x_radio_ops { 2728c2ecf20Sopenharmony_ci int (*tune_freq)(struct si476x_core *, struct si476x_tune_freq_args *); 2738c2ecf20Sopenharmony_ci int (*seek_start)(struct si476x_core *, bool, bool); 2748c2ecf20Sopenharmony_ci int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *, 2758c2ecf20Sopenharmony_ci struct si476x_rsq_status_report *); 2768c2ecf20Sopenharmony_ci int (*rds_blckcnt)(struct si476x_core *, bool, 2778c2ecf20Sopenharmony_ci struct si476x_rds_blockcount_report *); 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci int (*phase_diversity)(struct si476x_core *, 2808c2ecf20Sopenharmony_ci enum si476x_phase_diversity_mode); 2818c2ecf20Sopenharmony_ci int (*phase_div_status)(struct si476x_core *); 2828c2ecf20Sopenharmony_ci int (*acf_status)(struct si476x_core *, 2838c2ecf20Sopenharmony_ci struct si476x_acf_status_report *); 2848c2ecf20Sopenharmony_ci int (*agc_status)(struct si476x_core *, 2858c2ecf20Sopenharmony_ci struct si476x_agc_status_report *); 2868c2ecf20Sopenharmony_ci}; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci/** 2898c2ecf20Sopenharmony_ci * struct si476x_radio - radio device 2908c2ecf20Sopenharmony_ci * 2918c2ecf20Sopenharmony_ci * @v4l2dev: Pointer to V4L2 device created by V4L2 subsystem 2928c2ecf20Sopenharmony_ci * @videodev: Pointer to video device created by V4L2 subsystem 2938c2ecf20Sopenharmony_ci * @ctrl_handler: V4L2 controls handler 2948c2ecf20Sopenharmony_ci * @core: Pointer to underlying core device 2958c2ecf20Sopenharmony_ci * @ops: Vtable of functions. See struct si476x_radio_ops for details 2968c2ecf20Sopenharmony_ci * @debugfs: pointer to &strucd dentry for debugfs 2978c2ecf20Sopenharmony_ci * @audmode: audio mode, as defined for the rxsubchans field 2988c2ecf20Sopenharmony_ci * at videodev2.h 2998c2ecf20Sopenharmony_ci * 3008c2ecf20Sopenharmony_ci * core structure is the radio device is being used 3018c2ecf20Sopenharmony_ci */ 3028c2ecf20Sopenharmony_cistruct si476x_radio { 3038c2ecf20Sopenharmony_ci struct v4l2_device v4l2dev; 3048c2ecf20Sopenharmony_ci struct video_device videodev; 3058c2ecf20Sopenharmony_ci struct v4l2_ctrl_handler ctrl_handler; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci struct si476x_core *core; 3088c2ecf20Sopenharmony_ci /* This field should not be accesses unless core lock is held */ 3098c2ecf20Sopenharmony_ci const struct si476x_radio_ops *ops; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci struct dentry *debugfs; 3128c2ecf20Sopenharmony_ci u32 audmode; 3138c2ecf20Sopenharmony_ci}; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_cistatic inline struct si476x_radio * 3168c2ecf20Sopenharmony_civ4l2_dev_to_radio(struct v4l2_device *d) 3178c2ecf20Sopenharmony_ci{ 3188c2ecf20Sopenharmony_ci return container_of(d, struct si476x_radio, v4l2dev); 3198c2ecf20Sopenharmony_ci} 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_cistatic inline struct si476x_radio * 3228c2ecf20Sopenharmony_civ4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d) 3238c2ecf20Sopenharmony_ci{ 3248c2ecf20Sopenharmony_ci return container_of(d, struct si476x_radio, ctrl_handler); 3258c2ecf20Sopenharmony_ci} 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci/* 3288c2ecf20Sopenharmony_ci * si476x_vidioc_querycap - query device capabilities 3298c2ecf20Sopenharmony_ci */ 3308c2ecf20Sopenharmony_cistatic int si476x_radio_querycap(struct file *file, void *priv, 3318c2ecf20Sopenharmony_ci struct v4l2_capability *capability) 3328c2ecf20Sopenharmony_ci{ 3338c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci strscpy(capability->driver, radio->v4l2dev.name, 3368c2ecf20Sopenharmony_ci sizeof(capability->driver)); 3378c2ecf20Sopenharmony_ci strscpy(capability->card, DRIVER_CARD, sizeof(capability->card)); 3388c2ecf20Sopenharmony_ci snprintf(capability->bus_info, sizeof(capability->bus_info), 3398c2ecf20Sopenharmony_ci "platform:%s", radio->v4l2dev.name); 3408c2ecf20Sopenharmony_ci return 0; 3418c2ecf20Sopenharmony_ci} 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_cistatic int si476x_radio_enum_freq_bands(struct file *file, void *priv, 3448c2ecf20Sopenharmony_ci struct v4l2_frequency_band *band) 3458c2ecf20Sopenharmony_ci{ 3468c2ecf20Sopenharmony_ci int err; 3478c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci if (band->tuner != 0) 3508c2ecf20Sopenharmony_ci return -EINVAL; 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ci switch (radio->core->chip_id) { 3538c2ecf20Sopenharmony_ci /* AM/FM tuners -- all bands are supported */ 3548c2ecf20Sopenharmony_ci case SI476X_CHIP_SI4761: 3558c2ecf20Sopenharmony_ci case SI476X_CHIP_SI4764: 3568c2ecf20Sopenharmony_ci if (band->index < ARRAY_SIZE(si476x_bands)) { 3578c2ecf20Sopenharmony_ci *band = si476x_bands[band->index]; 3588c2ecf20Sopenharmony_ci err = 0; 3598c2ecf20Sopenharmony_ci } else { 3608c2ecf20Sopenharmony_ci err = -EINVAL; 3618c2ecf20Sopenharmony_ci } 3628c2ecf20Sopenharmony_ci break; 3638c2ecf20Sopenharmony_ci /* FM companion tuner chips -- only FM bands are 3648c2ecf20Sopenharmony_ci * supported */ 3658c2ecf20Sopenharmony_ci case SI476X_CHIP_SI4768: 3668c2ecf20Sopenharmony_ci if (band->index == SI476X_BAND_FM) { 3678c2ecf20Sopenharmony_ci *band = si476x_bands[band->index]; 3688c2ecf20Sopenharmony_ci err = 0; 3698c2ecf20Sopenharmony_ci } else { 3708c2ecf20Sopenharmony_ci err = -EINVAL; 3718c2ecf20Sopenharmony_ci } 3728c2ecf20Sopenharmony_ci break; 3738c2ecf20Sopenharmony_ci default: 3748c2ecf20Sopenharmony_ci err = -EINVAL; 3758c2ecf20Sopenharmony_ci } 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci return err; 3788c2ecf20Sopenharmony_ci} 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_cistatic int si476x_radio_g_tuner(struct file *file, void *priv, 3818c2ecf20Sopenharmony_ci struct v4l2_tuner *tuner) 3828c2ecf20Sopenharmony_ci{ 3838c2ecf20Sopenharmony_ci int err; 3848c2ecf20Sopenharmony_ci struct si476x_rsq_status_report report; 3858c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci struct si476x_rsq_status_args args = { 3888c2ecf20Sopenharmony_ci .primary = false, 3898c2ecf20Sopenharmony_ci .rsqack = false, 3908c2ecf20Sopenharmony_ci .attune = false, 3918c2ecf20Sopenharmony_ci .cancel = false, 3928c2ecf20Sopenharmony_ci .stcack = false, 3938c2ecf20Sopenharmony_ci }; 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci if (tuner->index != 0) 3968c2ecf20Sopenharmony_ci return -EINVAL; 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_ci tuner->type = V4L2_TUNER_RADIO; 3998c2ecf20Sopenharmony_ci tuner->capability = V4L2_TUNER_CAP_LOW /* Measure frequencies 4008c2ecf20Sopenharmony_ci * in multiples of 4018c2ecf20Sopenharmony_ci * 62.5 Hz */ 4028c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_STEREO 4038c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_HWSEEK_BOUNDED 4048c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_HWSEEK_WRAP 4058c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_HWSEEK_PROG_LIM; 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci if (si476x_core_is_a_secondary_tuner(radio->core)) { 4108c2ecf20Sopenharmony_ci strscpy(tuner->name, "FM (secondary)", sizeof(tuner->name)); 4118c2ecf20Sopenharmony_ci tuner->rxsubchans = 0; 4128c2ecf20Sopenharmony_ci tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; 4138c2ecf20Sopenharmony_ci } else if (si476x_core_has_am(radio->core)) { 4148c2ecf20Sopenharmony_ci if (si476x_core_is_a_primary_tuner(radio->core)) 4158c2ecf20Sopenharmony_ci strscpy(tuner->name, "AM/FM (primary)", 4168c2ecf20Sopenharmony_ci sizeof(tuner->name)); 4178c2ecf20Sopenharmony_ci else 4188c2ecf20Sopenharmony_ci strscpy(tuner->name, "AM/FM", sizeof(tuner->name)); 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ci tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO 4218c2ecf20Sopenharmony_ci | V4L2_TUNER_SUB_RDS; 4228c2ecf20Sopenharmony_ci tuner->capability |= V4L2_TUNER_CAP_RDS 4238c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_RDS_BLOCK_IO 4248c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_FREQ_BANDS; 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci tuner->rangelow = si476x_bands[SI476X_BAND_AM].rangelow; 4278c2ecf20Sopenharmony_ci } else { 4288c2ecf20Sopenharmony_ci strscpy(tuner->name, "FM", sizeof(tuner->name)); 4298c2ecf20Sopenharmony_ci tuner->rxsubchans = V4L2_TUNER_SUB_RDS; 4308c2ecf20Sopenharmony_ci tuner->capability |= V4L2_TUNER_CAP_RDS 4318c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_RDS_BLOCK_IO 4328c2ecf20Sopenharmony_ci | V4L2_TUNER_CAP_FREQ_BANDS; 4338c2ecf20Sopenharmony_ci tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; 4348c2ecf20Sopenharmony_ci } 4358c2ecf20Sopenharmony_ci 4368c2ecf20Sopenharmony_ci tuner->audmode = radio->audmode; 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci tuner->afc = 1; 4398c2ecf20Sopenharmony_ci tuner->rangehigh = si476x_bands[SI476X_BAND_FM].rangehigh; 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci err = radio->ops->rsq_status(radio->core, 4428c2ecf20Sopenharmony_ci &args, &report); 4438c2ecf20Sopenharmony_ci if (err < 0) { 4448c2ecf20Sopenharmony_ci tuner->signal = 0; 4458c2ecf20Sopenharmony_ci } else { 4468c2ecf20Sopenharmony_ci /* 4478c2ecf20Sopenharmony_ci * tuner->signal value range: 0x0000 .. 0xFFFF, 4488c2ecf20Sopenharmony_ci * report.rssi: -128 .. 127 4498c2ecf20Sopenharmony_ci */ 4508c2ecf20Sopenharmony_ci tuner->signal = (report.rssi + 128) * 257; 4518c2ecf20Sopenharmony_ci } 4528c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci return err; 4558c2ecf20Sopenharmony_ci} 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_cistatic int si476x_radio_s_tuner(struct file *file, void *priv, 4588c2ecf20Sopenharmony_ci const struct v4l2_tuner *tuner) 4598c2ecf20Sopenharmony_ci{ 4608c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci if (tuner->index != 0) 4638c2ecf20Sopenharmony_ci return -EINVAL; 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci if (tuner->audmode == V4L2_TUNER_MODE_MONO || 4668c2ecf20Sopenharmony_ci tuner->audmode == V4L2_TUNER_MODE_STEREO) 4678c2ecf20Sopenharmony_ci radio->audmode = tuner->audmode; 4688c2ecf20Sopenharmony_ci else 4698c2ecf20Sopenharmony_ci radio->audmode = V4L2_TUNER_MODE_STEREO; 4708c2ecf20Sopenharmony_ci 4718c2ecf20Sopenharmony_ci return 0; 4728c2ecf20Sopenharmony_ci} 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_cistatic int si476x_radio_init_vtable(struct si476x_radio *radio, 4758c2ecf20Sopenharmony_ci enum si476x_func func) 4768c2ecf20Sopenharmony_ci{ 4778c2ecf20Sopenharmony_ci static const struct si476x_radio_ops fm_ops = { 4788c2ecf20Sopenharmony_ci .tune_freq = si476x_core_cmd_fm_tune_freq, 4798c2ecf20Sopenharmony_ci .seek_start = si476x_core_cmd_fm_seek_start, 4808c2ecf20Sopenharmony_ci .rsq_status = si476x_core_cmd_fm_rsq_status, 4818c2ecf20Sopenharmony_ci .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount, 4828c2ecf20Sopenharmony_ci .phase_diversity = si476x_core_cmd_fm_phase_diversity, 4838c2ecf20Sopenharmony_ci .phase_div_status = si476x_core_cmd_fm_phase_div_status, 4848c2ecf20Sopenharmony_ci .acf_status = si476x_core_cmd_fm_acf_status, 4858c2ecf20Sopenharmony_ci .agc_status = si476x_core_cmd_agc_status, 4868c2ecf20Sopenharmony_ci }; 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci static const struct si476x_radio_ops am_ops = { 4898c2ecf20Sopenharmony_ci .tune_freq = si476x_core_cmd_am_tune_freq, 4908c2ecf20Sopenharmony_ci .seek_start = si476x_core_cmd_am_seek_start, 4918c2ecf20Sopenharmony_ci .rsq_status = si476x_core_cmd_am_rsq_status, 4928c2ecf20Sopenharmony_ci .rds_blckcnt = NULL, 4938c2ecf20Sopenharmony_ci .phase_diversity = NULL, 4948c2ecf20Sopenharmony_ci .phase_div_status = NULL, 4958c2ecf20Sopenharmony_ci .acf_status = si476x_core_cmd_am_acf_status, 4968c2ecf20Sopenharmony_ci .agc_status = NULL, 4978c2ecf20Sopenharmony_ci }; 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci switch (func) { 5008c2ecf20Sopenharmony_ci case SI476X_FUNC_FM_RECEIVER: 5018c2ecf20Sopenharmony_ci radio->ops = &fm_ops; 5028c2ecf20Sopenharmony_ci return 0; 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci case SI476X_FUNC_AM_RECEIVER: 5058c2ecf20Sopenharmony_ci radio->ops = &am_ops; 5068c2ecf20Sopenharmony_ci return 0; 5078c2ecf20Sopenharmony_ci default: 5088c2ecf20Sopenharmony_ci WARN(1, "Unexpected tuner function value\n"); 5098c2ecf20Sopenharmony_ci return -EINVAL; 5108c2ecf20Sopenharmony_ci } 5118c2ecf20Sopenharmony_ci} 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_cistatic int si476x_radio_pretune(struct si476x_radio *radio, 5148c2ecf20Sopenharmony_ci enum si476x_func func) 5158c2ecf20Sopenharmony_ci{ 5168c2ecf20Sopenharmony_ci int retval; 5178c2ecf20Sopenharmony_ci 5188c2ecf20Sopenharmony_ci struct si476x_tune_freq_args args = { 5198c2ecf20Sopenharmony_ci .zifsr = false, 5208c2ecf20Sopenharmony_ci .hd = false, 5218c2ecf20Sopenharmony_ci .injside = SI476X_INJSIDE_AUTO, 5228c2ecf20Sopenharmony_ci .tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE, 5238c2ecf20Sopenharmony_ci .smoothmetrics = SI476X_SM_INITIALIZE_AUDIO, 5248c2ecf20Sopenharmony_ci .antcap = 0, 5258c2ecf20Sopenharmony_ci }; 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_ci switch (func) { 5288c2ecf20Sopenharmony_ci case SI476X_FUNC_FM_RECEIVER: 5298c2ecf20Sopenharmony_ci args.freq = v4l2_to_si476x(radio->core, 5308c2ecf20Sopenharmony_ci 92 * FREQ_MUL); 5318c2ecf20Sopenharmony_ci retval = radio->ops->tune_freq(radio->core, &args); 5328c2ecf20Sopenharmony_ci break; 5338c2ecf20Sopenharmony_ci case SI476X_FUNC_AM_RECEIVER: 5348c2ecf20Sopenharmony_ci args.freq = v4l2_to_si476x(radio->core, 5358c2ecf20Sopenharmony_ci 0.6 * FREQ_MUL); 5368c2ecf20Sopenharmony_ci retval = radio->ops->tune_freq(radio->core, &args); 5378c2ecf20Sopenharmony_ci break; 5388c2ecf20Sopenharmony_ci default: 5398c2ecf20Sopenharmony_ci WARN(1, "Unexpected tuner function value\n"); 5408c2ecf20Sopenharmony_ci retval = -EINVAL; 5418c2ecf20Sopenharmony_ci } 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci return retval; 5448c2ecf20Sopenharmony_ci} 5458c2ecf20Sopenharmony_cistatic int si476x_radio_do_post_powerup_init(struct si476x_radio *radio, 5468c2ecf20Sopenharmony_ci enum si476x_func func) 5478c2ecf20Sopenharmony_ci{ 5488c2ecf20Sopenharmony_ci int err; 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci /* regcache_mark_dirty(radio->core->regmap); */ 5518c2ecf20Sopenharmony_ci err = regcache_sync_region(radio->core->regmap, 5528c2ecf20Sopenharmony_ci SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE, 5538c2ecf20Sopenharmony_ci SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT); 5548c2ecf20Sopenharmony_ci if (err < 0) 5558c2ecf20Sopenharmony_ci return err; 5568c2ecf20Sopenharmony_ci 5578c2ecf20Sopenharmony_ci err = regcache_sync_region(radio->core->regmap, 5588c2ecf20Sopenharmony_ci SI476X_PROP_AUDIO_DEEMPHASIS, 5598c2ecf20Sopenharmony_ci SI476X_PROP_AUDIO_PWR_LINE_FILTER); 5608c2ecf20Sopenharmony_ci if (err < 0) 5618c2ecf20Sopenharmony_ci return err; 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_ci err = regcache_sync_region(radio->core->regmap, 5648c2ecf20Sopenharmony_ci SI476X_PROP_INT_CTL_ENABLE, 5658c2ecf20Sopenharmony_ci SI476X_PROP_INT_CTL_ENABLE); 5668c2ecf20Sopenharmony_ci if (err < 0) 5678c2ecf20Sopenharmony_ci return err; 5688c2ecf20Sopenharmony_ci 5698c2ecf20Sopenharmony_ci /* 5708c2ecf20Sopenharmony_ci * Is there any point in restoring SNR and the like 5718c2ecf20Sopenharmony_ci * when switching between AM/FM? 5728c2ecf20Sopenharmony_ci */ 5738c2ecf20Sopenharmony_ci err = regcache_sync_region(radio->core->regmap, 5748c2ecf20Sopenharmony_ci SI476X_PROP_VALID_MAX_TUNE_ERROR, 5758c2ecf20Sopenharmony_ci SI476X_PROP_VALID_MAX_TUNE_ERROR); 5768c2ecf20Sopenharmony_ci if (err < 0) 5778c2ecf20Sopenharmony_ci return err; 5788c2ecf20Sopenharmony_ci 5798c2ecf20Sopenharmony_ci err = regcache_sync_region(radio->core->regmap, 5808c2ecf20Sopenharmony_ci SI476X_PROP_VALID_SNR_THRESHOLD, 5818c2ecf20Sopenharmony_ci SI476X_PROP_VALID_RSSI_THRESHOLD); 5828c2ecf20Sopenharmony_ci if (err < 0) 5838c2ecf20Sopenharmony_ci return err; 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci if (func == SI476X_FUNC_FM_RECEIVER) { 5868c2ecf20Sopenharmony_ci if (si476x_core_has_diversity(radio->core)) { 5878c2ecf20Sopenharmony_ci err = si476x_core_cmd_fm_phase_diversity(radio->core, 5888c2ecf20Sopenharmony_ci radio->core->diversity_mode); 5898c2ecf20Sopenharmony_ci if (err < 0) 5908c2ecf20Sopenharmony_ci return err; 5918c2ecf20Sopenharmony_ci } 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ci err = regcache_sync_region(radio->core->regmap, 5948c2ecf20Sopenharmony_ci SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, 5958c2ecf20Sopenharmony_ci SI476X_PROP_FM_RDS_CONFIG); 5968c2ecf20Sopenharmony_ci if (err < 0) 5978c2ecf20Sopenharmony_ci return err; 5988c2ecf20Sopenharmony_ci } 5998c2ecf20Sopenharmony_ci 6008c2ecf20Sopenharmony_ci return si476x_radio_init_vtable(radio, func); 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_ci} 6038c2ecf20Sopenharmony_ci 6048c2ecf20Sopenharmony_cistatic int si476x_radio_change_func(struct si476x_radio *radio, 6058c2ecf20Sopenharmony_ci enum si476x_func func) 6068c2ecf20Sopenharmony_ci{ 6078c2ecf20Sopenharmony_ci int err; 6088c2ecf20Sopenharmony_ci bool soft; 6098c2ecf20Sopenharmony_ci /* 6108c2ecf20Sopenharmony_ci * Since power/up down is a very time consuming operation, 6118c2ecf20Sopenharmony_ci * try to avoid doing it if the requested mode matches the one 6128c2ecf20Sopenharmony_ci * the tuner is in 6138c2ecf20Sopenharmony_ci */ 6148c2ecf20Sopenharmony_ci if (func == radio->core->power_up_parameters.func) 6158c2ecf20Sopenharmony_ci return 0; 6168c2ecf20Sopenharmony_ci 6178c2ecf20Sopenharmony_ci soft = true; 6188c2ecf20Sopenharmony_ci err = si476x_core_stop(radio->core, soft); 6198c2ecf20Sopenharmony_ci if (err < 0) { 6208c2ecf20Sopenharmony_ci /* 6218c2ecf20Sopenharmony_ci * OK, if the chip does not want to play nice let's 6228c2ecf20Sopenharmony_ci * try to reset it in more brutal way 6238c2ecf20Sopenharmony_ci */ 6248c2ecf20Sopenharmony_ci soft = false; 6258c2ecf20Sopenharmony_ci err = si476x_core_stop(radio->core, soft); 6268c2ecf20Sopenharmony_ci if (err < 0) 6278c2ecf20Sopenharmony_ci return err; 6288c2ecf20Sopenharmony_ci } 6298c2ecf20Sopenharmony_ci /* 6308c2ecf20Sopenharmony_ci Set the desired radio tuner function 6318c2ecf20Sopenharmony_ci */ 6328c2ecf20Sopenharmony_ci radio->core->power_up_parameters.func = func; 6338c2ecf20Sopenharmony_ci 6348c2ecf20Sopenharmony_ci err = si476x_core_start(radio->core, soft); 6358c2ecf20Sopenharmony_ci if (err < 0) 6368c2ecf20Sopenharmony_ci return err; 6378c2ecf20Sopenharmony_ci 6388c2ecf20Sopenharmony_ci /* 6398c2ecf20Sopenharmony_ci * No need to do the rest of manipulations for the bootlader 6408c2ecf20Sopenharmony_ci * mode 6418c2ecf20Sopenharmony_ci */ 6428c2ecf20Sopenharmony_ci if (func != SI476X_FUNC_FM_RECEIVER && 6438c2ecf20Sopenharmony_ci func != SI476X_FUNC_AM_RECEIVER) 6448c2ecf20Sopenharmony_ci return err; 6458c2ecf20Sopenharmony_ci 6468c2ecf20Sopenharmony_ci return si476x_radio_do_post_powerup_init(radio, func); 6478c2ecf20Sopenharmony_ci} 6488c2ecf20Sopenharmony_ci 6498c2ecf20Sopenharmony_cistatic int si476x_radio_g_frequency(struct file *file, void *priv, 6508c2ecf20Sopenharmony_ci struct v4l2_frequency *f) 6518c2ecf20Sopenharmony_ci{ 6528c2ecf20Sopenharmony_ci int err; 6538c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 6548c2ecf20Sopenharmony_ci 6558c2ecf20Sopenharmony_ci if (f->tuner != 0 || 6568c2ecf20Sopenharmony_ci f->type != V4L2_TUNER_RADIO) 6578c2ecf20Sopenharmony_ci return -EINVAL; 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ci if (radio->ops->rsq_status) { 6628c2ecf20Sopenharmony_ci struct si476x_rsq_status_report report; 6638c2ecf20Sopenharmony_ci struct si476x_rsq_status_args args = { 6648c2ecf20Sopenharmony_ci .primary = false, 6658c2ecf20Sopenharmony_ci .rsqack = false, 6668c2ecf20Sopenharmony_ci .attune = true, 6678c2ecf20Sopenharmony_ci .cancel = false, 6688c2ecf20Sopenharmony_ci .stcack = false, 6698c2ecf20Sopenharmony_ci }; 6708c2ecf20Sopenharmony_ci 6718c2ecf20Sopenharmony_ci err = radio->ops->rsq_status(radio->core, &args, &report); 6728c2ecf20Sopenharmony_ci if (!err) 6738c2ecf20Sopenharmony_ci f->frequency = si476x_to_v4l2(radio->core, 6748c2ecf20Sopenharmony_ci report.readfreq); 6758c2ecf20Sopenharmony_ci } else { 6768c2ecf20Sopenharmony_ci err = -EINVAL; 6778c2ecf20Sopenharmony_ci } 6788c2ecf20Sopenharmony_ci 6798c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 6808c2ecf20Sopenharmony_ci 6818c2ecf20Sopenharmony_ci return err; 6828c2ecf20Sopenharmony_ci} 6838c2ecf20Sopenharmony_ci 6848c2ecf20Sopenharmony_cistatic int si476x_radio_s_frequency(struct file *file, void *priv, 6858c2ecf20Sopenharmony_ci const struct v4l2_frequency *f) 6868c2ecf20Sopenharmony_ci{ 6878c2ecf20Sopenharmony_ci int err; 6888c2ecf20Sopenharmony_ci u32 freq = f->frequency; 6898c2ecf20Sopenharmony_ci struct si476x_tune_freq_args args; 6908c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 6918c2ecf20Sopenharmony_ci 6928c2ecf20Sopenharmony_ci const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh + 6938c2ecf20Sopenharmony_ci si476x_bands[SI476X_BAND_FM].rangelow) / 2; 6948c2ecf20Sopenharmony_ci const int band = (freq > midrange) ? 6958c2ecf20Sopenharmony_ci SI476X_BAND_FM : SI476X_BAND_AM; 6968c2ecf20Sopenharmony_ci const enum si476x_func func = (band == SI476X_BAND_AM) ? 6978c2ecf20Sopenharmony_ci SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER; 6988c2ecf20Sopenharmony_ci 6998c2ecf20Sopenharmony_ci if (f->tuner != 0 || 7008c2ecf20Sopenharmony_ci f->type != V4L2_TUNER_RADIO) 7018c2ecf20Sopenharmony_ci return -EINVAL; 7028c2ecf20Sopenharmony_ci 7038c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 7048c2ecf20Sopenharmony_ci 7058c2ecf20Sopenharmony_ci freq = clamp(freq, 7068c2ecf20Sopenharmony_ci si476x_bands[band].rangelow, 7078c2ecf20Sopenharmony_ci si476x_bands[band].rangehigh); 7088c2ecf20Sopenharmony_ci 7098c2ecf20Sopenharmony_ci if (si476x_radio_freq_is_inside_of_the_band(freq, 7108c2ecf20Sopenharmony_ci SI476X_BAND_AM) && 7118c2ecf20Sopenharmony_ci (!si476x_core_has_am(radio->core) || 7128c2ecf20Sopenharmony_ci si476x_core_is_a_secondary_tuner(radio->core))) { 7138c2ecf20Sopenharmony_ci err = -EINVAL; 7148c2ecf20Sopenharmony_ci goto unlock; 7158c2ecf20Sopenharmony_ci } 7168c2ecf20Sopenharmony_ci 7178c2ecf20Sopenharmony_ci err = si476x_radio_change_func(radio, func); 7188c2ecf20Sopenharmony_ci if (err < 0) 7198c2ecf20Sopenharmony_ci goto unlock; 7208c2ecf20Sopenharmony_ci 7218c2ecf20Sopenharmony_ci args.zifsr = false; 7228c2ecf20Sopenharmony_ci args.hd = false; 7238c2ecf20Sopenharmony_ci args.injside = SI476X_INJSIDE_AUTO; 7248c2ecf20Sopenharmony_ci args.freq = v4l2_to_si476x(radio->core, freq); 7258c2ecf20Sopenharmony_ci args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE; 7268c2ecf20Sopenharmony_ci args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO; 7278c2ecf20Sopenharmony_ci args.antcap = 0; 7288c2ecf20Sopenharmony_ci 7298c2ecf20Sopenharmony_ci err = radio->ops->tune_freq(radio->core, &args); 7308c2ecf20Sopenharmony_ci 7318c2ecf20Sopenharmony_ciunlock: 7328c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 7338c2ecf20Sopenharmony_ci return err; 7348c2ecf20Sopenharmony_ci} 7358c2ecf20Sopenharmony_ci 7368c2ecf20Sopenharmony_cistatic int si476x_radio_s_hw_freq_seek(struct file *file, void *priv, 7378c2ecf20Sopenharmony_ci const struct v4l2_hw_freq_seek *seek) 7388c2ecf20Sopenharmony_ci{ 7398c2ecf20Sopenharmony_ci int err; 7408c2ecf20Sopenharmony_ci enum si476x_func func; 7418c2ecf20Sopenharmony_ci u32 rangelow = seek->rangelow, rangehigh = seek->rangehigh; 7428c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 7438c2ecf20Sopenharmony_ci 7448c2ecf20Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 7458c2ecf20Sopenharmony_ci return -EAGAIN; 7468c2ecf20Sopenharmony_ci 7478c2ecf20Sopenharmony_ci if (seek->tuner != 0 || 7488c2ecf20Sopenharmony_ci seek->type != V4L2_TUNER_RADIO) 7498c2ecf20Sopenharmony_ci return -EINVAL; 7508c2ecf20Sopenharmony_ci 7518c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 7528c2ecf20Sopenharmony_ci 7538c2ecf20Sopenharmony_ci if (!rangelow) { 7548c2ecf20Sopenharmony_ci err = regmap_read(radio->core->regmap, 7558c2ecf20Sopenharmony_ci SI476X_PROP_SEEK_BAND_BOTTOM, 7568c2ecf20Sopenharmony_ci &rangelow); 7578c2ecf20Sopenharmony_ci if (err) 7588c2ecf20Sopenharmony_ci goto unlock; 7598c2ecf20Sopenharmony_ci rangelow = si476x_to_v4l2(radio->core, rangelow); 7608c2ecf20Sopenharmony_ci } 7618c2ecf20Sopenharmony_ci if (!rangehigh) { 7628c2ecf20Sopenharmony_ci err = regmap_read(radio->core->regmap, 7638c2ecf20Sopenharmony_ci SI476X_PROP_SEEK_BAND_TOP, 7648c2ecf20Sopenharmony_ci &rangehigh); 7658c2ecf20Sopenharmony_ci if (err) 7668c2ecf20Sopenharmony_ci goto unlock; 7678c2ecf20Sopenharmony_ci rangehigh = si476x_to_v4l2(radio->core, rangehigh); 7688c2ecf20Sopenharmony_ci } 7698c2ecf20Sopenharmony_ci 7708c2ecf20Sopenharmony_ci if (rangelow > rangehigh) { 7718c2ecf20Sopenharmony_ci err = -EINVAL; 7728c2ecf20Sopenharmony_ci goto unlock; 7738c2ecf20Sopenharmony_ci } 7748c2ecf20Sopenharmony_ci 7758c2ecf20Sopenharmony_ci if (si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, 7768c2ecf20Sopenharmony_ci SI476X_BAND_FM)) { 7778c2ecf20Sopenharmony_ci func = SI476X_FUNC_FM_RECEIVER; 7788c2ecf20Sopenharmony_ci 7798c2ecf20Sopenharmony_ci } else if (si476x_core_has_am(radio->core) && 7808c2ecf20Sopenharmony_ci si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, 7818c2ecf20Sopenharmony_ci SI476X_BAND_AM)) { 7828c2ecf20Sopenharmony_ci func = SI476X_FUNC_AM_RECEIVER; 7838c2ecf20Sopenharmony_ci } else { 7848c2ecf20Sopenharmony_ci err = -EINVAL; 7858c2ecf20Sopenharmony_ci goto unlock; 7868c2ecf20Sopenharmony_ci } 7878c2ecf20Sopenharmony_ci 7888c2ecf20Sopenharmony_ci err = si476x_radio_change_func(radio, func); 7898c2ecf20Sopenharmony_ci if (err < 0) 7908c2ecf20Sopenharmony_ci goto unlock; 7918c2ecf20Sopenharmony_ci 7928c2ecf20Sopenharmony_ci if (seek->rangehigh) { 7938c2ecf20Sopenharmony_ci err = regmap_write(radio->core->regmap, 7948c2ecf20Sopenharmony_ci SI476X_PROP_SEEK_BAND_TOP, 7958c2ecf20Sopenharmony_ci v4l2_to_si476x(radio->core, 7968c2ecf20Sopenharmony_ci seek->rangehigh)); 7978c2ecf20Sopenharmony_ci if (err) 7988c2ecf20Sopenharmony_ci goto unlock; 7998c2ecf20Sopenharmony_ci } 8008c2ecf20Sopenharmony_ci if (seek->rangelow) { 8018c2ecf20Sopenharmony_ci err = regmap_write(radio->core->regmap, 8028c2ecf20Sopenharmony_ci SI476X_PROP_SEEK_BAND_BOTTOM, 8038c2ecf20Sopenharmony_ci v4l2_to_si476x(radio->core, 8048c2ecf20Sopenharmony_ci seek->rangelow)); 8058c2ecf20Sopenharmony_ci if (err) 8068c2ecf20Sopenharmony_ci goto unlock; 8078c2ecf20Sopenharmony_ci } 8088c2ecf20Sopenharmony_ci if (seek->spacing) { 8098c2ecf20Sopenharmony_ci err = regmap_write(radio->core->regmap, 8108c2ecf20Sopenharmony_ci SI476X_PROP_SEEK_FREQUENCY_SPACING, 8118c2ecf20Sopenharmony_ci v4l2_to_si476x(radio->core, 8128c2ecf20Sopenharmony_ci seek->spacing)); 8138c2ecf20Sopenharmony_ci if (err) 8148c2ecf20Sopenharmony_ci goto unlock; 8158c2ecf20Sopenharmony_ci } 8168c2ecf20Sopenharmony_ci 8178c2ecf20Sopenharmony_ci err = radio->ops->seek_start(radio->core, 8188c2ecf20Sopenharmony_ci seek->seek_upward, 8198c2ecf20Sopenharmony_ci seek->wrap_around); 8208c2ecf20Sopenharmony_ciunlock: 8218c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 8228c2ecf20Sopenharmony_ci 8238c2ecf20Sopenharmony_ci 8248c2ecf20Sopenharmony_ci 8258c2ecf20Sopenharmony_ci return err; 8268c2ecf20Sopenharmony_ci} 8278c2ecf20Sopenharmony_ci 8288c2ecf20Sopenharmony_cistatic int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl) 8298c2ecf20Sopenharmony_ci{ 8308c2ecf20Sopenharmony_ci int retval; 8318c2ecf20Sopenharmony_ci struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); 8328c2ecf20Sopenharmony_ci 8338c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 8348c2ecf20Sopenharmony_ci 8358c2ecf20Sopenharmony_ci switch (ctrl->id) { 8368c2ecf20Sopenharmony_ci case V4L2_CID_SI476X_INTERCHIP_LINK: 8378c2ecf20Sopenharmony_ci if (si476x_core_has_diversity(radio->core)) { 8388c2ecf20Sopenharmony_ci if (radio->ops->phase_diversity) { 8398c2ecf20Sopenharmony_ci retval = radio->ops->phase_div_status(radio->core); 8408c2ecf20Sopenharmony_ci if (retval < 0) 8418c2ecf20Sopenharmony_ci break; 8428c2ecf20Sopenharmony_ci 8438c2ecf20Sopenharmony_ci ctrl->val = !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval); 8448c2ecf20Sopenharmony_ci retval = 0; 8458c2ecf20Sopenharmony_ci break; 8468c2ecf20Sopenharmony_ci } else { 8478c2ecf20Sopenharmony_ci retval = -ENOTTY; 8488c2ecf20Sopenharmony_ci break; 8498c2ecf20Sopenharmony_ci } 8508c2ecf20Sopenharmony_ci } 8518c2ecf20Sopenharmony_ci retval = -EINVAL; 8528c2ecf20Sopenharmony_ci break; 8538c2ecf20Sopenharmony_ci default: 8548c2ecf20Sopenharmony_ci retval = -EINVAL; 8558c2ecf20Sopenharmony_ci break; 8568c2ecf20Sopenharmony_ci } 8578c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 8588c2ecf20Sopenharmony_ci return retval; 8598c2ecf20Sopenharmony_ci 8608c2ecf20Sopenharmony_ci} 8618c2ecf20Sopenharmony_ci 8628c2ecf20Sopenharmony_cistatic int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl) 8638c2ecf20Sopenharmony_ci{ 8648c2ecf20Sopenharmony_ci int retval; 8658c2ecf20Sopenharmony_ci enum si476x_phase_diversity_mode mode; 8668c2ecf20Sopenharmony_ci struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); 8678c2ecf20Sopenharmony_ci 8688c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 8698c2ecf20Sopenharmony_ci 8708c2ecf20Sopenharmony_ci switch (ctrl->id) { 8718c2ecf20Sopenharmony_ci case V4L2_CID_SI476X_HARMONICS_COUNT: 8728c2ecf20Sopenharmony_ci retval = regmap_update_bits(radio->core->regmap, 8738c2ecf20Sopenharmony_ci SI476X_PROP_AUDIO_PWR_LINE_FILTER, 8748c2ecf20Sopenharmony_ci SI476X_PROP_PWR_HARMONICS_MASK, 8758c2ecf20Sopenharmony_ci ctrl->val); 8768c2ecf20Sopenharmony_ci break; 8778c2ecf20Sopenharmony_ci case V4L2_CID_POWER_LINE_FREQUENCY: 8788c2ecf20Sopenharmony_ci switch (ctrl->val) { 8798c2ecf20Sopenharmony_ci case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: 8808c2ecf20Sopenharmony_ci retval = regmap_update_bits(radio->core->regmap, 8818c2ecf20Sopenharmony_ci SI476X_PROP_AUDIO_PWR_LINE_FILTER, 8828c2ecf20Sopenharmony_ci SI476X_PROP_PWR_ENABLE_MASK, 8838c2ecf20Sopenharmony_ci 0); 8848c2ecf20Sopenharmony_ci break; 8858c2ecf20Sopenharmony_ci case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: 8868c2ecf20Sopenharmony_ci retval = regmap_update_bits(radio->core->regmap, 8878c2ecf20Sopenharmony_ci SI476X_PROP_AUDIO_PWR_LINE_FILTER, 8888c2ecf20Sopenharmony_ci SI476X_PROP_PWR_GRID_MASK, 8898c2ecf20Sopenharmony_ci SI476X_PROP_PWR_GRID_50HZ); 8908c2ecf20Sopenharmony_ci break; 8918c2ecf20Sopenharmony_ci case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: 8928c2ecf20Sopenharmony_ci retval = regmap_update_bits(radio->core->regmap, 8938c2ecf20Sopenharmony_ci SI476X_PROP_AUDIO_PWR_LINE_FILTER, 8948c2ecf20Sopenharmony_ci SI476X_PROP_PWR_GRID_MASK, 8958c2ecf20Sopenharmony_ci SI476X_PROP_PWR_GRID_60HZ); 8968c2ecf20Sopenharmony_ci break; 8978c2ecf20Sopenharmony_ci default: 8988c2ecf20Sopenharmony_ci retval = -EINVAL; 8998c2ecf20Sopenharmony_ci break; 9008c2ecf20Sopenharmony_ci } 9018c2ecf20Sopenharmony_ci break; 9028c2ecf20Sopenharmony_ci case V4L2_CID_SI476X_RSSI_THRESHOLD: 9038c2ecf20Sopenharmony_ci retval = regmap_write(radio->core->regmap, 9048c2ecf20Sopenharmony_ci SI476X_PROP_VALID_RSSI_THRESHOLD, 9058c2ecf20Sopenharmony_ci ctrl->val); 9068c2ecf20Sopenharmony_ci break; 9078c2ecf20Sopenharmony_ci case V4L2_CID_SI476X_SNR_THRESHOLD: 9088c2ecf20Sopenharmony_ci retval = regmap_write(radio->core->regmap, 9098c2ecf20Sopenharmony_ci SI476X_PROP_VALID_SNR_THRESHOLD, 9108c2ecf20Sopenharmony_ci ctrl->val); 9118c2ecf20Sopenharmony_ci break; 9128c2ecf20Sopenharmony_ci case V4L2_CID_SI476X_MAX_TUNE_ERROR: 9138c2ecf20Sopenharmony_ci retval = regmap_write(radio->core->regmap, 9148c2ecf20Sopenharmony_ci SI476X_PROP_VALID_MAX_TUNE_ERROR, 9158c2ecf20Sopenharmony_ci ctrl->val); 9168c2ecf20Sopenharmony_ci break; 9178c2ecf20Sopenharmony_ci case V4L2_CID_RDS_RECEPTION: 9188c2ecf20Sopenharmony_ci /* 9198c2ecf20Sopenharmony_ci * It looks like RDS related properties are 9208c2ecf20Sopenharmony_ci * inaccesable when tuner is in AM mode, so cache the 9218c2ecf20Sopenharmony_ci * changes 9228c2ecf20Sopenharmony_ci */ 9238c2ecf20Sopenharmony_ci if (si476x_core_is_in_am_receiver_mode(radio->core)) 9248c2ecf20Sopenharmony_ci regcache_cache_only(radio->core->regmap, true); 9258c2ecf20Sopenharmony_ci 9268c2ecf20Sopenharmony_ci if (ctrl->val) { 9278c2ecf20Sopenharmony_ci retval = regmap_write(radio->core->regmap, 9288c2ecf20Sopenharmony_ci SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT, 9298c2ecf20Sopenharmony_ci radio->core->rds_fifo_depth); 9308c2ecf20Sopenharmony_ci if (retval < 0) 9318c2ecf20Sopenharmony_ci break; 9328c2ecf20Sopenharmony_ci 9338c2ecf20Sopenharmony_ci if (radio->core->client->irq) { 9348c2ecf20Sopenharmony_ci retval = regmap_write(radio->core->regmap, 9358c2ecf20Sopenharmony_ci SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, 9368c2ecf20Sopenharmony_ci SI476X_RDSRECV); 9378c2ecf20Sopenharmony_ci if (retval < 0) 9388c2ecf20Sopenharmony_ci break; 9398c2ecf20Sopenharmony_ci } 9408c2ecf20Sopenharmony_ci 9418c2ecf20Sopenharmony_ci /* Drain RDS FIFO before enabling RDS processing */ 9428c2ecf20Sopenharmony_ci retval = si476x_core_cmd_fm_rds_status(radio->core, 9438c2ecf20Sopenharmony_ci false, 9448c2ecf20Sopenharmony_ci true, 9458c2ecf20Sopenharmony_ci true, 9468c2ecf20Sopenharmony_ci NULL); 9478c2ecf20Sopenharmony_ci if (retval < 0) 9488c2ecf20Sopenharmony_ci break; 9498c2ecf20Sopenharmony_ci 9508c2ecf20Sopenharmony_ci retval = regmap_update_bits(radio->core->regmap, 9518c2ecf20Sopenharmony_ci SI476X_PROP_FM_RDS_CONFIG, 9528c2ecf20Sopenharmony_ci SI476X_PROP_RDSEN_MASK, 9538c2ecf20Sopenharmony_ci SI476X_PROP_RDSEN); 9548c2ecf20Sopenharmony_ci } else { 9558c2ecf20Sopenharmony_ci retval = regmap_update_bits(radio->core->regmap, 9568c2ecf20Sopenharmony_ci SI476X_PROP_FM_RDS_CONFIG, 9578c2ecf20Sopenharmony_ci SI476X_PROP_RDSEN_MASK, 9588c2ecf20Sopenharmony_ci !SI476X_PROP_RDSEN); 9598c2ecf20Sopenharmony_ci } 9608c2ecf20Sopenharmony_ci 9618c2ecf20Sopenharmony_ci if (si476x_core_is_in_am_receiver_mode(radio->core)) 9628c2ecf20Sopenharmony_ci regcache_cache_only(radio->core->regmap, false); 9638c2ecf20Sopenharmony_ci break; 9648c2ecf20Sopenharmony_ci case V4L2_CID_TUNE_DEEMPHASIS: 9658c2ecf20Sopenharmony_ci retval = regmap_write(radio->core->regmap, 9668c2ecf20Sopenharmony_ci SI476X_PROP_AUDIO_DEEMPHASIS, 9678c2ecf20Sopenharmony_ci ctrl->val); 9688c2ecf20Sopenharmony_ci break; 9698c2ecf20Sopenharmony_ci 9708c2ecf20Sopenharmony_ci case V4L2_CID_SI476X_DIVERSITY_MODE: 9718c2ecf20Sopenharmony_ci mode = si476x_phase_diversity_idx_to_mode(ctrl->val); 9728c2ecf20Sopenharmony_ci 9738c2ecf20Sopenharmony_ci if (mode == radio->core->diversity_mode) { 9748c2ecf20Sopenharmony_ci retval = 0; 9758c2ecf20Sopenharmony_ci break; 9768c2ecf20Sopenharmony_ci } 9778c2ecf20Sopenharmony_ci 9788c2ecf20Sopenharmony_ci if (si476x_core_is_in_am_receiver_mode(radio->core)) { 9798c2ecf20Sopenharmony_ci /* 9808c2ecf20Sopenharmony_ci * Diversity cannot be configured while tuner 9818c2ecf20Sopenharmony_ci * is in AM mode so save the changes and carry on. 9828c2ecf20Sopenharmony_ci */ 9838c2ecf20Sopenharmony_ci radio->core->diversity_mode = mode; 9848c2ecf20Sopenharmony_ci retval = 0; 9858c2ecf20Sopenharmony_ci } else { 9868c2ecf20Sopenharmony_ci retval = radio->ops->phase_diversity(radio->core, mode); 9878c2ecf20Sopenharmony_ci if (!retval) 9888c2ecf20Sopenharmony_ci radio->core->diversity_mode = mode; 9898c2ecf20Sopenharmony_ci } 9908c2ecf20Sopenharmony_ci break; 9918c2ecf20Sopenharmony_ci 9928c2ecf20Sopenharmony_ci default: 9938c2ecf20Sopenharmony_ci retval = -EINVAL; 9948c2ecf20Sopenharmony_ci break; 9958c2ecf20Sopenharmony_ci } 9968c2ecf20Sopenharmony_ci 9978c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 9988c2ecf20Sopenharmony_ci 9998c2ecf20Sopenharmony_ci return retval; 10008c2ecf20Sopenharmony_ci} 10018c2ecf20Sopenharmony_ci 10028c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG 10038c2ecf20Sopenharmony_cistatic int si476x_radio_g_register(struct file *file, void *fh, 10048c2ecf20Sopenharmony_ci struct v4l2_dbg_register *reg) 10058c2ecf20Sopenharmony_ci{ 10068c2ecf20Sopenharmony_ci int err; 10078c2ecf20Sopenharmony_ci unsigned int value; 10088c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 10098c2ecf20Sopenharmony_ci 10108c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 10118c2ecf20Sopenharmony_ci reg->size = 2; 10128c2ecf20Sopenharmony_ci err = regmap_read(radio->core->regmap, 10138c2ecf20Sopenharmony_ci (unsigned int)reg->reg, &value); 10148c2ecf20Sopenharmony_ci reg->val = value; 10158c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 10168c2ecf20Sopenharmony_ci 10178c2ecf20Sopenharmony_ci return err; 10188c2ecf20Sopenharmony_ci} 10198c2ecf20Sopenharmony_cistatic int si476x_radio_s_register(struct file *file, void *fh, 10208c2ecf20Sopenharmony_ci const struct v4l2_dbg_register *reg) 10218c2ecf20Sopenharmony_ci{ 10228c2ecf20Sopenharmony_ci 10238c2ecf20Sopenharmony_ci int err; 10248c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 10258c2ecf20Sopenharmony_ci 10268c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 10278c2ecf20Sopenharmony_ci err = regmap_write(radio->core->regmap, 10288c2ecf20Sopenharmony_ci (unsigned int)reg->reg, 10298c2ecf20Sopenharmony_ci (unsigned int)reg->val); 10308c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 10318c2ecf20Sopenharmony_ci 10328c2ecf20Sopenharmony_ci return err; 10338c2ecf20Sopenharmony_ci} 10348c2ecf20Sopenharmony_ci#endif 10358c2ecf20Sopenharmony_ci 10368c2ecf20Sopenharmony_cistatic int si476x_radio_fops_open(struct file *file) 10378c2ecf20Sopenharmony_ci{ 10388c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 10398c2ecf20Sopenharmony_ci int err; 10408c2ecf20Sopenharmony_ci 10418c2ecf20Sopenharmony_ci err = v4l2_fh_open(file); 10428c2ecf20Sopenharmony_ci if (err) 10438c2ecf20Sopenharmony_ci return err; 10448c2ecf20Sopenharmony_ci 10458c2ecf20Sopenharmony_ci if (v4l2_fh_is_singular_file(file)) { 10468c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 10478c2ecf20Sopenharmony_ci err = si476x_core_set_power_state(radio->core, 10488c2ecf20Sopenharmony_ci SI476X_POWER_UP_FULL); 10498c2ecf20Sopenharmony_ci if (err < 0) 10508c2ecf20Sopenharmony_ci goto done; 10518c2ecf20Sopenharmony_ci 10528c2ecf20Sopenharmony_ci err = si476x_radio_do_post_powerup_init(radio, 10538c2ecf20Sopenharmony_ci radio->core->power_up_parameters.func); 10548c2ecf20Sopenharmony_ci if (err < 0) 10558c2ecf20Sopenharmony_ci goto power_down; 10568c2ecf20Sopenharmony_ci 10578c2ecf20Sopenharmony_ci err = si476x_radio_pretune(radio, 10588c2ecf20Sopenharmony_ci radio->core->power_up_parameters.func); 10598c2ecf20Sopenharmony_ci if (err < 0) 10608c2ecf20Sopenharmony_ci goto power_down; 10618c2ecf20Sopenharmony_ci 10628c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 10638c2ecf20Sopenharmony_ci /*Must be done after si476x_core_unlock to prevent a deadlock*/ 10648c2ecf20Sopenharmony_ci v4l2_ctrl_handler_setup(&radio->ctrl_handler); 10658c2ecf20Sopenharmony_ci } 10668c2ecf20Sopenharmony_ci 10678c2ecf20Sopenharmony_ci return err; 10688c2ecf20Sopenharmony_ci 10698c2ecf20Sopenharmony_cipower_down: 10708c2ecf20Sopenharmony_ci si476x_core_set_power_state(radio->core, 10718c2ecf20Sopenharmony_ci SI476X_POWER_DOWN); 10728c2ecf20Sopenharmony_cidone: 10738c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 10748c2ecf20Sopenharmony_ci v4l2_fh_release(file); 10758c2ecf20Sopenharmony_ci 10768c2ecf20Sopenharmony_ci return err; 10778c2ecf20Sopenharmony_ci} 10788c2ecf20Sopenharmony_ci 10798c2ecf20Sopenharmony_cistatic int si476x_radio_fops_release(struct file *file) 10808c2ecf20Sopenharmony_ci{ 10818c2ecf20Sopenharmony_ci int err; 10828c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 10838c2ecf20Sopenharmony_ci 10848c2ecf20Sopenharmony_ci if (v4l2_fh_is_singular_file(file) && 10858c2ecf20Sopenharmony_ci atomic_read(&radio->core->is_alive)) 10868c2ecf20Sopenharmony_ci si476x_core_set_power_state(radio->core, 10878c2ecf20Sopenharmony_ci SI476X_POWER_DOWN); 10888c2ecf20Sopenharmony_ci 10898c2ecf20Sopenharmony_ci err = v4l2_fh_release(file); 10908c2ecf20Sopenharmony_ci 10918c2ecf20Sopenharmony_ci return err; 10928c2ecf20Sopenharmony_ci} 10938c2ecf20Sopenharmony_ci 10948c2ecf20Sopenharmony_cistatic ssize_t si476x_radio_fops_read(struct file *file, char __user *buf, 10958c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 10968c2ecf20Sopenharmony_ci{ 10978c2ecf20Sopenharmony_ci ssize_t rval; 10988c2ecf20Sopenharmony_ci size_t fifo_len; 10998c2ecf20Sopenharmony_ci unsigned int copied; 11008c2ecf20Sopenharmony_ci 11018c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 11028c2ecf20Sopenharmony_ci 11038c2ecf20Sopenharmony_ci /* block if no new data available */ 11048c2ecf20Sopenharmony_ci if (kfifo_is_empty(&radio->core->rds_fifo)) { 11058c2ecf20Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 11068c2ecf20Sopenharmony_ci return -EWOULDBLOCK; 11078c2ecf20Sopenharmony_ci 11088c2ecf20Sopenharmony_ci rval = wait_event_interruptible(radio->core->rds_read_queue, 11098c2ecf20Sopenharmony_ci (!kfifo_is_empty(&radio->core->rds_fifo) || 11108c2ecf20Sopenharmony_ci !atomic_read(&radio->core->is_alive))); 11118c2ecf20Sopenharmony_ci if (rval < 0) 11128c2ecf20Sopenharmony_ci return -EINTR; 11138c2ecf20Sopenharmony_ci 11148c2ecf20Sopenharmony_ci if (!atomic_read(&radio->core->is_alive)) 11158c2ecf20Sopenharmony_ci return -ENODEV; 11168c2ecf20Sopenharmony_ci } 11178c2ecf20Sopenharmony_ci 11188c2ecf20Sopenharmony_ci fifo_len = kfifo_len(&radio->core->rds_fifo); 11198c2ecf20Sopenharmony_ci 11208c2ecf20Sopenharmony_ci if (kfifo_to_user(&radio->core->rds_fifo, buf, 11218c2ecf20Sopenharmony_ci min(fifo_len, count), 11228c2ecf20Sopenharmony_ci &copied) != 0) { 11238c2ecf20Sopenharmony_ci dev_warn(&radio->videodev.dev, 11248c2ecf20Sopenharmony_ci "Error during FIFO to userspace copy\n"); 11258c2ecf20Sopenharmony_ci rval = -EIO; 11268c2ecf20Sopenharmony_ci } else { 11278c2ecf20Sopenharmony_ci rval = (ssize_t)copied; 11288c2ecf20Sopenharmony_ci } 11298c2ecf20Sopenharmony_ci 11308c2ecf20Sopenharmony_ci return rval; 11318c2ecf20Sopenharmony_ci} 11328c2ecf20Sopenharmony_ci 11338c2ecf20Sopenharmony_cistatic __poll_t si476x_radio_fops_poll(struct file *file, 11348c2ecf20Sopenharmony_ci struct poll_table_struct *pts) 11358c2ecf20Sopenharmony_ci{ 11368c2ecf20Sopenharmony_ci struct si476x_radio *radio = video_drvdata(file); 11378c2ecf20Sopenharmony_ci __poll_t req_events = poll_requested_events(pts); 11388c2ecf20Sopenharmony_ci __poll_t err = v4l2_ctrl_poll(file, pts); 11398c2ecf20Sopenharmony_ci 11408c2ecf20Sopenharmony_ci if (req_events & (EPOLLIN | EPOLLRDNORM)) { 11418c2ecf20Sopenharmony_ci if (atomic_read(&radio->core->is_alive)) 11428c2ecf20Sopenharmony_ci poll_wait(file, &radio->core->rds_read_queue, pts); 11438c2ecf20Sopenharmony_ci 11448c2ecf20Sopenharmony_ci if (!atomic_read(&radio->core->is_alive)) 11458c2ecf20Sopenharmony_ci err = EPOLLHUP; 11468c2ecf20Sopenharmony_ci 11478c2ecf20Sopenharmony_ci if (!kfifo_is_empty(&radio->core->rds_fifo)) 11488c2ecf20Sopenharmony_ci err = EPOLLIN | EPOLLRDNORM; 11498c2ecf20Sopenharmony_ci } 11508c2ecf20Sopenharmony_ci 11518c2ecf20Sopenharmony_ci return err; 11528c2ecf20Sopenharmony_ci} 11538c2ecf20Sopenharmony_ci 11548c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations si476x_fops = { 11558c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 11568c2ecf20Sopenharmony_ci .read = si476x_radio_fops_read, 11578c2ecf20Sopenharmony_ci .poll = si476x_radio_fops_poll, 11588c2ecf20Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 11598c2ecf20Sopenharmony_ci .open = si476x_radio_fops_open, 11608c2ecf20Sopenharmony_ci .release = si476x_radio_fops_release, 11618c2ecf20Sopenharmony_ci}; 11628c2ecf20Sopenharmony_ci 11638c2ecf20Sopenharmony_ci 11648c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops si4761_ioctl_ops = { 11658c2ecf20Sopenharmony_ci .vidioc_querycap = si476x_radio_querycap, 11668c2ecf20Sopenharmony_ci .vidioc_g_tuner = si476x_radio_g_tuner, 11678c2ecf20Sopenharmony_ci .vidioc_s_tuner = si476x_radio_s_tuner, 11688c2ecf20Sopenharmony_ci 11698c2ecf20Sopenharmony_ci .vidioc_g_frequency = si476x_radio_g_frequency, 11708c2ecf20Sopenharmony_ci .vidioc_s_frequency = si476x_radio_s_frequency, 11718c2ecf20Sopenharmony_ci .vidioc_s_hw_freq_seek = si476x_radio_s_hw_freq_seek, 11728c2ecf20Sopenharmony_ci .vidioc_enum_freq_bands = si476x_radio_enum_freq_bands, 11738c2ecf20Sopenharmony_ci 11748c2ecf20Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 11758c2ecf20Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 11768c2ecf20Sopenharmony_ci 11778c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG 11788c2ecf20Sopenharmony_ci .vidioc_g_register = si476x_radio_g_register, 11798c2ecf20Sopenharmony_ci .vidioc_s_register = si476x_radio_s_register, 11808c2ecf20Sopenharmony_ci#endif 11818c2ecf20Sopenharmony_ci}; 11828c2ecf20Sopenharmony_ci 11838c2ecf20Sopenharmony_ci 11848c2ecf20Sopenharmony_cistatic const struct video_device si476x_viddev_template = { 11858c2ecf20Sopenharmony_ci .fops = &si476x_fops, 11868c2ecf20Sopenharmony_ci .name = DRIVER_NAME, 11878c2ecf20Sopenharmony_ci .release = video_device_release_empty, 11888c2ecf20Sopenharmony_ci}; 11898c2ecf20Sopenharmony_ci 11908c2ecf20Sopenharmony_ci 11918c2ecf20Sopenharmony_ci 11928c2ecf20Sopenharmony_cistatic ssize_t si476x_radio_read_acf_blob(struct file *file, 11938c2ecf20Sopenharmony_ci char __user *user_buf, 11948c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 11958c2ecf20Sopenharmony_ci{ 11968c2ecf20Sopenharmony_ci int err; 11978c2ecf20Sopenharmony_ci struct si476x_radio *radio = file->private_data; 11988c2ecf20Sopenharmony_ci struct si476x_acf_status_report report; 11998c2ecf20Sopenharmony_ci 12008c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 12018c2ecf20Sopenharmony_ci if (radio->ops->acf_status) 12028c2ecf20Sopenharmony_ci err = radio->ops->acf_status(radio->core, &report); 12038c2ecf20Sopenharmony_ci else 12048c2ecf20Sopenharmony_ci err = -ENOENT; 12058c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 12068c2ecf20Sopenharmony_ci 12078c2ecf20Sopenharmony_ci if (err < 0) 12088c2ecf20Sopenharmony_ci return err; 12098c2ecf20Sopenharmony_ci 12108c2ecf20Sopenharmony_ci return simple_read_from_buffer(user_buf, count, ppos, &report, 12118c2ecf20Sopenharmony_ci sizeof(report)); 12128c2ecf20Sopenharmony_ci} 12138c2ecf20Sopenharmony_ci 12148c2ecf20Sopenharmony_cistatic const struct file_operations radio_acf_fops = { 12158c2ecf20Sopenharmony_ci .open = simple_open, 12168c2ecf20Sopenharmony_ci .llseek = default_llseek, 12178c2ecf20Sopenharmony_ci .read = si476x_radio_read_acf_blob, 12188c2ecf20Sopenharmony_ci}; 12198c2ecf20Sopenharmony_ci 12208c2ecf20Sopenharmony_cistatic ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file, 12218c2ecf20Sopenharmony_ci char __user *user_buf, 12228c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 12238c2ecf20Sopenharmony_ci{ 12248c2ecf20Sopenharmony_ci int err; 12258c2ecf20Sopenharmony_ci struct si476x_radio *radio = file->private_data; 12268c2ecf20Sopenharmony_ci struct si476x_rds_blockcount_report report; 12278c2ecf20Sopenharmony_ci 12288c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 12298c2ecf20Sopenharmony_ci if (radio->ops->rds_blckcnt) 12308c2ecf20Sopenharmony_ci err = radio->ops->rds_blckcnt(radio->core, true, 12318c2ecf20Sopenharmony_ci &report); 12328c2ecf20Sopenharmony_ci else 12338c2ecf20Sopenharmony_ci err = -ENOENT; 12348c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 12358c2ecf20Sopenharmony_ci 12368c2ecf20Sopenharmony_ci if (err < 0) 12378c2ecf20Sopenharmony_ci return err; 12388c2ecf20Sopenharmony_ci 12398c2ecf20Sopenharmony_ci return simple_read_from_buffer(user_buf, count, ppos, &report, 12408c2ecf20Sopenharmony_ci sizeof(report)); 12418c2ecf20Sopenharmony_ci} 12428c2ecf20Sopenharmony_ci 12438c2ecf20Sopenharmony_cistatic const struct file_operations radio_rds_blckcnt_fops = { 12448c2ecf20Sopenharmony_ci .open = simple_open, 12458c2ecf20Sopenharmony_ci .llseek = default_llseek, 12468c2ecf20Sopenharmony_ci .read = si476x_radio_read_rds_blckcnt_blob, 12478c2ecf20Sopenharmony_ci}; 12488c2ecf20Sopenharmony_ci 12498c2ecf20Sopenharmony_cistatic ssize_t si476x_radio_read_agc_blob(struct file *file, 12508c2ecf20Sopenharmony_ci char __user *user_buf, 12518c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 12528c2ecf20Sopenharmony_ci{ 12538c2ecf20Sopenharmony_ci int err; 12548c2ecf20Sopenharmony_ci struct si476x_radio *radio = file->private_data; 12558c2ecf20Sopenharmony_ci struct si476x_agc_status_report report; 12568c2ecf20Sopenharmony_ci 12578c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 12588c2ecf20Sopenharmony_ci if (radio->ops->rds_blckcnt) 12598c2ecf20Sopenharmony_ci err = radio->ops->agc_status(radio->core, &report); 12608c2ecf20Sopenharmony_ci else 12618c2ecf20Sopenharmony_ci err = -ENOENT; 12628c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 12638c2ecf20Sopenharmony_ci 12648c2ecf20Sopenharmony_ci if (err < 0) 12658c2ecf20Sopenharmony_ci return err; 12668c2ecf20Sopenharmony_ci 12678c2ecf20Sopenharmony_ci return simple_read_from_buffer(user_buf, count, ppos, &report, 12688c2ecf20Sopenharmony_ci sizeof(report)); 12698c2ecf20Sopenharmony_ci} 12708c2ecf20Sopenharmony_ci 12718c2ecf20Sopenharmony_cistatic const struct file_operations radio_agc_fops = { 12728c2ecf20Sopenharmony_ci .open = simple_open, 12738c2ecf20Sopenharmony_ci .llseek = default_llseek, 12748c2ecf20Sopenharmony_ci .read = si476x_radio_read_agc_blob, 12758c2ecf20Sopenharmony_ci}; 12768c2ecf20Sopenharmony_ci 12778c2ecf20Sopenharmony_cistatic ssize_t si476x_radio_read_rsq_blob(struct file *file, 12788c2ecf20Sopenharmony_ci char __user *user_buf, 12798c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 12808c2ecf20Sopenharmony_ci{ 12818c2ecf20Sopenharmony_ci int err; 12828c2ecf20Sopenharmony_ci struct si476x_radio *radio = file->private_data; 12838c2ecf20Sopenharmony_ci struct si476x_rsq_status_report report; 12848c2ecf20Sopenharmony_ci struct si476x_rsq_status_args args = { 12858c2ecf20Sopenharmony_ci .primary = false, 12868c2ecf20Sopenharmony_ci .rsqack = false, 12878c2ecf20Sopenharmony_ci .attune = false, 12888c2ecf20Sopenharmony_ci .cancel = false, 12898c2ecf20Sopenharmony_ci .stcack = false, 12908c2ecf20Sopenharmony_ci }; 12918c2ecf20Sopenharmony_ci 12928c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 12938c2ecf20Sopenharmony_ci if (radio->ops->rds_blckcnt) 12948c2ecf20Sopenharmony_ci err = radio->ops->rsq_status(radio->core, &args, &report); 12958c2ecf20Sopenharmony_ci else 12968c2ecf20Sopenharmony_ci err = -ENOENT; 12978c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 12988c2ecf20Sopenharmony_ci 12998c2ecf20Sopenharmony_ci if (err < 0) 13008c2ecf20Sopenharmony_ci return err; 13018c2ecf20Sopenharmony_ci 13028c2ecf20Sopenharmony_ci return simple_read_from_buffer(user_buf, count, ppos, &report, 13038c2ecf20Sopenharmony_ci sizeof(report)); 13048c2ecf20Sopenharmony_ci} 13058c2ecf20Sopenharmony_ci 13068c2ecf20Sopenharmony_cistatic const struct file_operations radio_rsq_fops = { 13078c2ecf20Sopenharmony_ci .open = simple_open, 13088c2ecf20Sopenharmony_ci .llseek = default_llseek, 13098c2ecf20Sopenharmony_ci .read = si476x_radio_read_rsq_blob, 13108c2ecf20Sopenharmony_ci}; 13118c2ecf20Sopenharmony_ci 13128c2ecf20Sopenharmony_cistatic ssize_t si476x_radio_read_rsq_primary_blob(struct file *file, 13138c2ecf20Sopenharmony_ci char __user *user_buf, 13148c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 13158c2ecf20Sopenharmony_ci{ 13168c2ecf20Sopenharmony_ci int err; 13178c2ecf20Sopenharmony_ci struct si476x_radio *radio = file->private_data; 13188c2ecf20Sopenharmony_ci struct si476x_rsq_status_report report; 13198c2ecf20Sopenharmony_ci struct si476x_rsq_status_args args = { 13208c2ecf20Sopenharmony_ci .primary = true, 13218c2ecf20Sopenharmony_ci .rsqack = false, 13228c2ecf20Sopenharmony_ci .attune = false, 13238c2ecf20Sopenharmony_ci .cancel = false, 13248c2ecf20Sopenharmony_ci .stcack = false, 13258c2ecf20Sopenharmony_ci }; 13268c2ecf20Sopenharmony_ci 13278c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 13288c2ecf20Sopenharmony_ci if (radio->ops->rds_blckcnt) 13298c2ecf20Sopenharmony_ci err = radio->ops->rsq_status(radio->core, &args, &report); 13308c2ecf20Sopenharmony_ci else 13318c2ecf20Sopenharmony_ci err = -ENOENT; 13328c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 13338c2ecf20Sopenharmony_ci 13348c2ecf20Sopenharmony_ci if (err < 0) 13358c2ecf20Sopenharmony_ci return err; 13368c2ecf20Sopenharmony_ci 13378c2ecf20Sopenharmony_ci return simple_read_from_buffer(user_buf, count, ppos, &report, 13388c2ecf20Sopenharmony_ci sizeof(report)); 13398c2ecf20Sopenharmony_ci} 13408c2ecf20Sopenharmony_ci 13418c2ecf20Sopenharmony_cistatic const struct file_operations radio_rsq_primary_fops = { 13428c2ecf20Sopenharmony_ci .open = simple_open, 13438c2ecf20Sopenharmony_ci .llseek = default_llseek, 13448c2ecf20Sopenharmony_ci .read = si476x_radio_read_rsq_primary_blob, 13458c2ecf20Sopenharmony_ci}; 13468c2ecf20Sopenharmony_ci 13478c2ecf20Sopenharmony_ci 13488c2ecf20Sopenharmony_cistatic void si476x_radio_init_debugfs(struct si476x_radio *radio) 13498c2ecf20Sopenharmony_ci{ 13508c2ecf20Sopenharmony_ci radio->debugfs = debugfs_create_dir(dev_name(radio->v4l2dev.dev), NULL); 13518c2ecf20Sopenharmony_ci 13528c2ecf20Sopenharmony_ci debugfs_create_file("acf", S_IRUGO, radio->debugfs, radio, 13538c2ecf20Sopenharmony_ci &radio_acf_fops); 13548c2ecf20Sopenharmony_ci 13558c2ecf20Sopenharmony_ci debugfs_create_file("rds_blckcnt", S_IRUGO, radio->debugfs, radio, 13568c2ecf20Sopenharmony_ci &radio_rds_blckcnt_fops); 13578c2ecf20Sopenharmony_ci 13588c2ecf20Sopenharmony_ci debugfs_create_file("agc", S_IRUGO, radio->debugfs, radio, 13598c2ecf20Sopenharmony_ci &radio_agc_fops); 13608c2ecf20Sopenharmony_ci 13618c2ecf20Sopenharmony_ci debugfs_create_file("rsq", S_IRUGO, radio->debugfs, radio, 13628c2ecf20Sopenharmony_ci &radio_rsq_fops); 13638c2ecf20Sopenharmony_ci 13648c2ecf20Sopenharmony_ci debugfs_create_file("rsq_primary", S_IRUGO, radio->debugfs, radio, 13658c2ecf20Sopenharmony_ci &radio_rsq_primary_fops); 13668c2ecf20Sopenharmony_ci} 13678c2ecf20Sopenharmony_ci 13688c2ecf20Sopenharmony_ci 13698c2ecf20Sopenharmony_cistatic int si476x_radio_add_new_custom(struct si476x_radio *radio, 13708c2ecf20Sopenharmony_ci enum si476x_ctrl_idx idx) 13718c2ecf20Sopenharmony_ci{ 13728c2ecf20Sopenharmony_ci int rval; 13738c2ecf20Sopenharmony_ci struct v4l2_ctrl *ctrl; 13748c2ecf20Sopenharmony_ci 13758c2ecf20Sopenharmony_ci ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler, 13768c2ecf20Sopenharmony_ci &si476x_ctrls[idx], 13778c2ecf20Sopenharmony_ci NULL); 13788c2ecf20Sopenharmony_ci rval = radio->ctrl_handler.error; 13798c2ecf20Sopenharmony_ci if (ctrl == NULL && rval) 13808c2ecf20Sopenharmony_ci dev_err(radio->v4l2dev.dev, 13818c2ecf20Sopenharmony_ci "Could not initialize '%s' control %d\n", 13828c2ecf20Sopenharmony_ci si476x_ctrls[idx].name, rval); 13838c2ecf20Sopenharmony_ci 13848c2ecf20Sopenharmony_ci return rval; 13858c2ecf20Sopenharmony_ci} 13868c2ecf20Sopenharmony_ci 13878c2ecf20Sopenharmony_cistatic int si476x_radio_probe(struct platform_device *pdev) 13888c2ecf20Sopenharmony_ci{ 13898c2ecf20Sopenharmony_ci int rval; 13908c2ecf20Sopenharmony_ci struct si476x_radio *radio; 13918c2ecf20Sopenharmony_ci struct v4l2_ctrl *ctrl; 13928c2ecf20Sopenharmony_ci 13938c2ecf20Sopenharmony_ci static atomic_t instance = ATOMIC_INIT(0); 13948c2ecf20Sopenharmony_ci 13958c2ecf20Sopenharmony_ci radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); 13968c2ecf20Sopenharmony_ci if (!radio) 13978c2ecf20Sopenharmony_ci return -ENOMEM; 13988c2ecf20Sopenharmony_ci 13998c2ecf20Sopenharmony_ci radio->core = i2c_mfd_cell_to_core(&pdev->dev); 14008c2ecf20Sopenharmony_ci 14018c2ecf20Sopenharmony_ci v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance); 14028c2ecf20Sopenharmony_ci 14038c2ecf20Sopenharmony_ci rval = v4l2_device_register(&pdev->dev, &radio->v4l2dev); 14048c2ecf20Sopenharmony_ci if (rval) { 14058c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); 14068c2ecf20Sopenharmony_ci return rval; 14078c2ecf20Sopenharmony_ci } 14088c2ecf20Sopenharmony_ci 14098c2ecf20Sopenharmony_ci memcpy(&radio->videodev, &si476x_viddev_template, 14108c2ecf20Sopenharmony_ci sizeof(struct video_device)); 14118c2ecf20Sopenharmony_ci 14128c2ecf20Sopenharmony_ci radio->videodev.v4l2_dev = &radio->v4l2dev; 14138c2ecf20Sopenharmony_ci radio->videodev.ioctl_ops = &si4761_ioctl_ops; 14148c2ecf20Sopenharmony_ci radio->videodev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO | 14158c2ecf20Sopenharmony_ci V4L2_CAP_HW_FREQ_SEEK; 14168c2ecf20Sopenharmony_ci 14178c2ecf20Sopenharmony_ci si476x_core_lock(radio->core); 14188c2ecf20Sopenharmony_ci if (!si476x_core_is_a_secondary_tuner(radio->core)) 14198c2ecf20Sopenharmony_ci radio->videodev.device_caps |= V4L2_CAP_RDS_CAPTURE | 14208c2ecf20Sopenharmony_ci V4L2_CAP_READWRITE; 14218c2ecf20Sopenharmony_ci si476x_core_unlock(radio->core); 14228c2ecf20Sopenharmony_ci 14238c2ecf20Sopenharmony_ci video_set_drvdata(&radio->videodev, radio); 14248c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, radio); 14258c2ecf20Sopenharmony_ci 14268c2ecf20Sopenharmony_ci 14278c2ecf20Sopenharmony_ci radio->v4l2dev.ctrl_handler = &radio->ctrl_handler; 14288c2ecf20Sopenharmony_ci v4l2_ctrl_handler_init(&radio->ctrl_handler, 14298c2ecf20Sopenharmony_ci 1 + ARRAY_SIZE(si476x_ctrls)); 14308c2ecf20Sopenharmony_ci 14318c2ecf20Sopenharmony_ci if (si476x_core_has_am(radio->core)) { 14328c2ecf20Sopenharmony_ci ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, 14338c2ecf20Sopenharmony_ci &si476x_ctrl_ops, 14348c2ecf20Sopenharmony_ci V4L2_CID_POWER_LINE_FREQUENCY, 14358c2ecf20Sopenharmony_ci V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 14368c2ecf20Sopenharmony_ci 0, 0); 14378c2ecf20Sopenharmony_ci rval = radio->ctrl_handler.error; 14388c2ecf20Sopenharmony_ci if (ctrl == NULL && rval) { 14398c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n", 14408c2ecf20Sopenharmony_ci rval); 14418c2ecf20Sopenharmony_ci goto exit; 14428c2ecf20Sopenharmony_ci } 14438c2ecf20Sopenharmony_ci 14448c2ecf20Sopenharmony_ci rval = si476x_radio_add_new_custom(radio, 14458c2ecf20Sopenharmony_ci SI476X_IDX_HARMONICS_COUNT); 14468c2ecf20Sopenharmony_ci if (rval < 0) 14478c2ecf20Sopenharmony_ci goto exit; 14488c2ecf20Sopenharmony_ci } 14498c2ecf20Sopenharmony_ci 14508c2ecf20Sopenharmony_ci rval = si476x_radio_add_new_custom(radio, SI476X_IDX_RSSI_THRESHOLD); 14518c2ecf20Sopenharmony_ci if (rval < 0) 14528c2ecf20Sopenharmony_ci goto exit; 14538c2ecf20Sopenharmony_ci 14548c2ecf20Sopenharmony_ci rval = si476x_radio_add_new_custom(radio, SI476X_IDX_SNR_THRESHOLD); 14558c2ecf20Sopenharmony_ci if (rval < 0) 14568c2ecf20Sopenharmony_ci goto exit; 14578c2ecf20Sopenharmony_ci 14588c2ecf20Sopenharmony_ci rval = si476x_radio_add_new_custom(radio, SI476X_IDX_MAX_TUNE_ERROR); 14598c2ecf20Sopenharmony_ci if (rval < 0) 14608c2ecf20Sopenharmony_ci goto exit; 14618c2ecf20Sopenharmony_ci 14628c2ecf20Sopenharmony_ci ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, 14638c2ecf20Sopenharmony_ci &si476x_ctrl_ops, 14648c2ecf20Sopenharmony_ci V4L2_CID_TUNE_DEEMPHASIS, 14658c2ecf20Sopenharmony_ci V4L2_DEEMPHASIS_75_uS, 0, 0); 14668c2ecf20Sopenharmony_ci rval = radio->ctrl_handler.error; 14678c2ecf20Sopenharmony_ci if (ctrl == NULL && rval) { 14688c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n", 14698c2ecf20Sopenharmony_ci rval); 14708c2ecf20Sopenharmony_ci goto exit; 14718c2ecf20Sopenharmony_ci } 14728c2ecf20Sopenharmony_ci 14738c2ecf20Sopenharmony_ci ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops, 14748c2ecf20Sopenharmony_ci V4L2_CID_RDS_RECEPTION, 14758c2ecf20Sopenharmony_ci 0, 1, 1, 1); 14768c2ecf20Sopenharmony_ci rval = radio->ctrl_handler.error; 14778c2ecf20Sopenharmony_ci if (ctrl == NULL && rval) { 14788c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n", 14798c2ecf20Sopenharmony_ci rval); 14808c2ecf20Sopenharmony_ci goto exit; 14818c2ecf20Sopenharmony_ci } 14828c2ecf20Sopenharmony_ci 14838c2ecf20Sopenharmony_ci if (si476x_core_has_diversity(radio->core)) { 14848c2ecf20Sopenharmony_ci si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def = 14858c2ecf20Sopenharmony_ci si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode); 14868c2ecf20Sopenharmony_ci rval = si476x_radio_add_new_custom(radio, SI476X_IDX_DIVERSITY_MODE); 14878c2ecf20Sopenharmony_ci if (rval < 0) 14888c2ecf20Sopenharmony_ci goto exit; 14898c2ecf20Sopenharmony_ci 14908c2ecf20Sopenharmony_ci rval = si476x_radio_add_new_custom(radio, SI476X_IDX_INTERCHIP_LINK); 14918c2ecf20Sopenharmony_ci if (rval < 0) 14928c2ecf20Sopenharmony_ci goto exit; 14938c2ecf20Sopenharmony_ci } 14948c2ecf20Sopenharmony_ci 14958c2ecf20Sopenharmony_ci /* register video device */ 14968c2ecf20Sopenharmony_ci rval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1); 14978c2ecf20Sopenharmony_ci if (rval < 0) { 14988c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Could not register video device\n"); 14998c2ecf20Sopenharmony_ci goto exit; 15008c2ecf20Sopenharmony_ci } 15018c2ecf20Sopenharmony_ci 15028c2ecf20Sopenharmony_ci si476x_radio_init_debugfs(radio); 15038c2ecf20Sopenharmony_ci 15048c2ecf20Sopenharmony_ci return 0; 15058c2ecf20Sopenharmony_ciexit: 15068c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); 15078c2ecf20Sopenharmony_ci return rval; 15088c2ecf20Sopenharmony_ci} 15098c2ecf20Sopenharmony_ci 15108c2ecf20Sopenharmony_cistatic int si476x_radio_remove(struct platform_device *pdev) 15118c2ecf20Sopenharmony_ci{ 15128c2ecf20Sopenharmony_ci struct si476x_radio *radio = platform_get_drvdata(pdev); 15138c2ecf20Sopenharmony_ci 15148c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); 15158c2ecf20Sopenharmony_ci video_unregister_device(&radio->videodev); 15168c2ecf20Sopenharmony_ci v4l2_device_unregister(&radio->v4l2dev); 15178c2ecf20Sopenharmony_ci debugfs_remove_recursive(radio->debugfs); 15188c2ecf20Sopenharmony_ci 15198c2ecf20Sopenharmony_ci return 0; 15208c2ecf20Sopenharmony_ci} 15218c2ecf20Sopenharmony_ci 15228c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:si476x-radio"); 15238c2ecf20Sopenharmony_ci 15248c2ecf20Sopenharmony_cistatic struct platform_driver si476x_radio_driver = { 15258c2ecf20Sopenharmony_ci .driver = { 15268c2ecf20Sopenharmony_ci .name = DRIVER_NAME, 15278c2ecf20Sopenharmony_ci }, 15288c2ecf20Sopenharmony_ci .probe = si476x_radio_probe, 15298c2ecf20Sopenharmony_ci .remove = si476x_radio_remove, 15308c2ecf20Sopenharmony_ci}; 15318c2ecf20Sopenharmony_cimodule_platform_driver(si476x_radio_driver); 15328c2ecf20Sopenharmony_ci 15338c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); 15348c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell"); 15358c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 1536