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

[media] v4l2: Add a V4L2 driver for SI476X MFD

This commit adds a driver that exposes all the radio related
functionality of the Si476x series of chips via the V4L2 subsystem.

[mchehab@redhat.com: change it to depends on MFD_SI476X_CORE instead of
selecting it; vidioc_s_register now uses const struct]
Acked-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>

authored by

Andrey Smirnov and committed by
Mauro Carvalho Chehab
b879a9c2 6695be68

+1840
+187
Documentation/video4linux/si476x.txt
··· 1 + SI476x Driver Readme 2 + ------------------------------------------------ 3 + Copyright (C) 2013 Andrey Smirnov <andrew.smirnov@gmail.com> 4 + 5 + TODO for the driver 6 + ------------------------------ 7 + 8 + - According to the SiLabs' datasheet it is possible to update the 9 + firmware of the radio chip in the run-time, thus bringing it to the 10 + most recent version. Unfortunately I couldn't find any mentioning of 11 + the said firmware update for the old chips that I tested the driver 12 + against, so for chips like that the driver only exposes the old 13 + functionality. 14 + 15 + 16 + Parameters exposed over debugfs 17 + ------------------------------- 18 + SI476x allow user to get multiple characteristics that can be very 19 + useful for EoL testing/RF performance estimation, parameters that have 20 + very little to do with V4L2 subsystem. Such parameters are exposed via 21 + debugfs and can be accessed via regular file I/O operations. 22 + 23 + The drivers exposes following files: 24 + 25 + * /sys/kernel/debug/<device-name>/acf 26 + This file contains ACF(Automatically Controlled Features) status 27 + information. The contents of the file is binary data of the 28 + following layout: 29 + 30 + Offset | Name | Description 31 + ==================================================================== 32 + 0x00 | blend_int | Flag, set when stereo separation has 33 + | | crossed below the blend threshold 34 + -------------------------------------------------------------------- 35 + 0x01 | hblend_int | Flag, set when HiBlend cutoff 36 + | | frequency is lower than threshold 37 + -------------------------------------------------------------------- 38 + 0x02 | hicut_int | Flag, set when HiCut cutoff 39 + | | frequency is lower than threshold 40 + -------------------------------------------------------------------- 41 + 0x03 | chbw_int | Flag, set when channel filter 42 + | | bandwidth is less than threshold 43 + -------------------------------------------------------------------- 44 + 0x04 | softmute_int | Flag indicating that softmute 45 + | | attenuation has increased above 46 + | | softmute threshold 47 + -------------------------------------------------------------------- 48 + 0x05 | smute | 0 - Audio is not soft muted 49 + | | 1 - Audio is soft muted 50 + -------------------------------------------------------------------- 51 + 0x06 | smattn | Soft mute attenuation level in dB 52 + -------------------------------------------------------------------- 53 + 0x07 | chbw | Channel filter bandwidth in kHz 54 + -------------------------------------------------------------------- 55 + 0x08 | hicut | HiCut cutoff frequency in units of 56 + | | 100Hz 57 + -------------------------------------------------------------------- 58 + 0x09 | hiblend | HiBlend cutoff frequency in units 59 + | | of 100 Hz 60 + -------------------------------------------------------------------- 61 + 0x10 | pilot | 0 - Stereo pilot is not present 62 + | | 1 - Stereo pilot is present 63 + -------------------------------------------------------------------- 64 + 0x11 | stblend | Stereo blend in % 65 + -------------------------------------------------------------------- 66 + 67 + 68 + * /sys/kernel/debug/<device-name>/rds_blckcnt 69 + This file contains statistics about RDS receptions. It's binary data 70 + has the following layout: 71 + 72 + Offset | Name | Description 73 + ==================================================================== 74 + 0x00 | expected | Number of expected RDS blocks 75 + -------------------------------------------------------------------- 76 + 0x02 | received | Number of received RDS blocks 77 + -------------------------------------------------------------------- 78 + 0x04 | uncorrectable | Number of uncorrectable RDS blocks 79 + -------------------------------------------------------------------- 80 + 81 + * /sys/kernel/debug/<device-name>/agc 82 + This file contains information about parameters pertaining to 83 + AGC(Automatic Gain Control) 84 + 85 + The layout is: 86 + Offset | Name | Description 87 + ==================================================================== 88 + 0x00 | mxhi | 0 - FM Mixer PD high threshold is 89 + | | not tripped 90 + | | 1 - FM Mixer PD high threshold is 91 + | | tripped 92 + -------------------------------------------------------------------- 93 + 0x01 | mxlo | ditto for FM Mixer PD low 94 + -------------------------------------------------------------------- 95 + 0x02 | lnahi | ditto for FM LNA PD high 96 + -------------------------------------------------------------------- 97 + 0x03 | lnalo | ditto for FM LNA PD low 98 + -------------------------------------------------------------------- 99 + 0x04 | fmagc1 | FMAGC1 attenuator resistance 100 + | | (see datasheet for more detail) 101 + -------------------------------------------------------------------- 102 + 0x05 | fmagc2 | ditto for FMAGC2 103 + -------------------------------------------------------------------- 104 + 0x06 | pgagain | PGA gain in dB 105 + -------------------------------------------------------------------- 106 + 0x07 | fmwblang | FM/WB LNA Gain in dB 107 + -------------------------------------------------------------------- 108 + 109 + * /sys/kernel/debug/<device-name>/rsq 110 + This file contains information about parameters pertaining to 111 + RSQ(Received Signal Quality) 112 + 113 + The layout is: 114 + Offset | Name | Description 115 + ==================================================================== 116 + 0x00 | multhint | 0 - multipath value has not crossed 117 + | | the Multipath high threshold 118 + | | 1 - multipath value has crossed 119 + | | the Multipath high threshold 120 + -------------------------------------------------------------------- 121 + 0x01 | multlint | ditto for Multipath low threshold 122 + -------------------------------------------------------------------- 123 + 0x02 | snrhint | 0 - received signal's SNR has not 124 + | | crossed high threshold 125 + | | 1 - received signal's SNR has 126 + | | crossed high threshold 127 + -------------------------------------------------------------------- 128 + 0x03 | snrlint | ditto for low threshold 129 + -------------------------------------------------------------------- 130 + 0x04 | rssihint | ditto for RSSI high threshold 131 + -------------------------------------------------------------------- 132 + 0x05 | rssilint | ditto for RSSI low threshold 133 + -------------------------------------------------------------------- 134 + 0x06 | bltf | Flag indicating if seek command 135 + | | reached/wrapped seek band limit 136 + -------------------------------------------------------------------- 137 + 0x07 | snr_ready | Indicates that SNR metrics is ready 138 + -------------------------------------------------------------------- 139 + 0x08 | rssiready | ditto for RSSI metrics 140 + -------------------------------------------------------------------- 141 + 0x09 | injside | 0 - Low-side injection is being used 142 + | | 1 - High-side injection is used 143 + -------------------------------------------------------------------- 144 + 0x10 | afcrl | Flag indicating if AFC rails 145 + -------------------------------------------------------------------- 146 + 0x11 | valid | Flag indicating if channel is valid 147 + -------------------------------------------------------------------- 148 + 0x12 | readfreq | Current tuned frequency 149 + -------------------------------------------------------------------- 150 + 0x14 | freqoff | Singed frequency offset in units of 151 + | | 2ppm 152 + -------------------------------------------------------------------- 153 + 0x15 | rssi | Signed value of RSSI in dBuV 154 + -------------------------------------------------------------------- 155 + 0x16 | snr | Signed RF SNR in dB 156 + -------------------------------------------------------------------- 157 + 0x17 | issi | Signed Image Strength Signal 158 + | | indicator 159 + -------------------------------------------------------------------- 160 + 0x18 | lassi | Signed Low side adjacent Channel 161 + | | Strength indicator 162 + -------------------------------------------------------------------- 163 + 0x19 | hassi | ditto fpr High side 164 + -------------------------------------------------------------------- 165 + 0x20 | mult | Multipath indicator 166 + -------------------------------------------------------------------- 167 + 0x21 | dev | Frequency deviation 168 + -------------------------------------------------------------------- 169 + 0x24 | assi | Adjascent channel SSI 170 + -------------------------------------------------------------------- 171 + 0x25 | usn | Ultrasonic noise indicator 172 + -------------------------------------------------------------------- 173 + 0x26 | pilotdev | Pilot deviation in units of 100 Hz 174 + -------------------------------------------------------------------- 175 + 0x27 | rdsdev | ditto for RDS 176 + -------------------------------------------------------------------- 177 + 0x28 | assidev | ditto for ASSI 178 + -------------------------------------------------------------------- 179 + 0x29 | strongdev | Frequency deviation 180 + -------------------------------------------------------------------- 181 + 0x30 | rdspi | RDS PI code 182 + -------------------------------------------------------------------- 183 + 184 + * /sys/kernel/debug/<device-name>/rsq_primary 185 + This file contains information about parameters pertaining to 186 + RSQ(Received Signal Quality) for primary tuner only. Layout is as 187 + the one above.
+16
drivers/media/radio/Kconfig
··· 18 18 19 19 source "drivers/media/radio/si470x/Kconfig" 20 20 21 + config RADIO_SI476X 22 + tristate "Silicon Laboratories Si476x I2C FM Radio" 23 + depends on I2C && VIDEO_V4L2 24 + depends on MFD_SI476X_CORE 25 + select SND_SOC_SI476X 26 + ---help--- 27 + Choose Y here if you have this FM radio chip. 28 + 29 + In order to control your radio card, you will need to use programs 30 + that are compatible with the Video For Linux 2 API. Information on 31 + this API and pointers to "v4l2" programs may be found at 32 + <file:Documentation/video4linux/API.html>. 33 + 34 + To compile this driver as a module, choose M here: the 35 + module will be called radio-si476x. 36 + 21 37 config USB_MR800 22 38 tristate "AverMedia MR 800 USB FM radio support" 23 39 depends on USB && VIDEO_V4L2
+1
drivers/media/radio/Makefile
··· 19 19 obj-$(CONFIG_RADIO_TRUST) += radio-trust.o 20 20 obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o 21 21 obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o 22 + obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o 22 23 obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o 23 24 obj-$(CONFIG_USB_DSBR) += dsbr100.o 24 25 obj-$(CONFIG_RADIO_SI470X) += si470x/
+1599
drivers/media/radio/radio-si476x.c
··· 1 + /* 2 + * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips 3 + * 4 + * Copyright (C) 2012 Innovative Converged Devices(ICD) 5 + * Copyright (C) 2013 Andrey Smirnov 6 + * 7 + * Author: Andrey Smirnov <andrew.smirnov@gmail.com> 8 + * 9 + * This program is free software; you can redistribute it and/or modify 10 + * it under the terms of the GNU General Public License as published by 11 + * the Free Software Foundation; version 2 of the License. 12 + * 13 + * This program is distributed in the hope that it will be useful, but 14 + * WITHOUT ANY WARRANTY; without even the implied warranty of 15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 + * General Public License for more details. 17 + * 18 + */ 19 + 20 + #include <linux/module.h> 21 + #include <linux/delay.h> 22 + #include <linux/interrupt.h> 23 + #include <linux/slab.h> 24 + #include <linux/atomic.h> 25 + #include <linux/videodev2.h> 26 + #include <linux/mutex.h> 27 + #include <linux/debugfs.h> 28 + #include <media/v4l2-common.h> 29 + #include <media/v4l2-ioctl.h> 30 + #include <media/v4l2-ctrls.h> 31 + #include <media/v4l2-event.h> 32 + #include <media/v4l2-device.h> 33 + 34 + #include <media/si476x.h> 35 + #include <linux/mfd/si476x-core.h> 36 + 37 + #define FM_FREQ_RANGE_LOW 64000000 38 + #define FM_FREQ_RANGE_HIGH 108000000 39 + 40 + #define AM_FREQ_RANGE_LOW 520000 41 + #define AM_FREQ_RANGE_HIGH 30000000 42 + 43 + #define PWRLINEFLTR (1 << 8) 44 + 45 + #define FREQ_MUL (10000000 / 625) 46 + 47 + #define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0b10000000 & (status)) 48 + 49 + #define DRIVER_NAME "si476x-radio" 50 + #define DRIVER_CARD "SI476x AM/FM Receiver" 51 + 52 + enum si476x_freq_bands { 53 + SI476X_BAND_FM, 54 + SI476X_BAND_AM, 55 + }; 56 + 57 + static const struct v4l2_frequency_band si476x_bands[] = { 58 + [SI476X_BAND_FM] = { 59 + .type = V4L2_TUNER_RADIO, 60 + .index = SI476X_BAND_FM, 61 + .capability = V4L2_TUNER_CAP_LOW 62 + | V4L2_TUNER_CAP_STEREO 63 + | V4L2_TUNER_CAP_RDS 64 + | V4L2_TUNER_CAP_RDS_BLOCK_IO 65 + | V4L2_TUNER_CAP_FREQ_BANDS, 66 + .rangelow = 64 * FREQ_MUL, 67 + .rangehigh = 108 * FREQ_MUL, 68 + .modulation = V4L2_BAND_MODULATION_FM, 69 + }, 70 + [SI476X_BAND_AM] = { 71 + .type = V4L2_TUNER_RADIO, 72 + .index = SI476X_BAND_AM, 73 + .capability = V4L2_TUNER_CAP_LOW 74 + | V4L2_TUNER_CAP_FREQ_BANDS, 75 + .rangelow = 0.52 * FREQ_MUL, 76 + .rangehigh = 30 * FREQ_MUL, 77 + .modulation = V4L2_BAND_MODULATION_AM, 78 + }, 79 + }; 80 + 81 + static inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq, int band) 82 + { 83 + return freq >= si476x_bands[band].rangelow && 84 + freq <= si476x_bands[band].rangehigh; 85 + } 86 + 87 + static inline bool si476x_radio_range_is_inside_of_the_band(u32 low, u32 high, 88 + int band) 89 + { 90 + return low >= si476x_bands[band].rangelow && 91 + high <= si476x_bands[band].rangehigh; 92 + } 93 + 94 + static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl); 95 + static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl); 96 + 97 + enum phase_diversity_modes_idx { 98 + SI476X_IDX_PHDIV_DISABLED, 99 + SI476X_IDX_PHDIV_PRIMARY_COMBINING, 100 + SI476X_IDX_PHDIV_PRIMARY_ANTENNA, 101 + SI476X_IDX_PHDIV_SECONDARY_ANTENNA, 102 + SI476X_IDX_PHDIV_SECONDARY_COMBINING, 103 + }; 104 + 105 + static const char * const phase_diversity_modes[] = { 106 + [SI476X_IDX_PHDIV_DISABLED] = "Disabled", 107 + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary with Secondary", 108 + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary Antenna", 109 + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Secondary Antenna", 110 + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary with Primary", 111 + }; 112 + 113 + static inline enum phase_diversity_modes_idx 114 + si476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode) 115 + { 116 + switch (mode) { 117 + default: /* FALLTHROUGH */ 118 + case SI476X_PHDIV_DISABLED: 119 + return SI476X_IDX_PHDIV_DISABLED; 120 + case SI476X_PHDIV_PRIMARY_COMBINING: 121 + return SI476X_IDX_PHDIV_PRIMARY_COMBINING; 122 + case SI476X_PHDIV_PRIMARY_ANTENNA: 123 + return SI476X_IDX_PHDIV_PRIMARY_ANTENNA; 124 + case SI476X_PHDIV_SECONDARY_ANTENNA: 125 + return SI476X_IDX_PHDIV_SECONDARY_ANTENNA; 126 + case SI476X_PHDIV_SECONDARY_COMBINING: 127 + return SI476X_IDX_PHDIV_SECONDARY_COMBINING; 128 + } 129 + } 130 + 131 + static inline enum si476x_phase_diversity_mode 132 + si476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx) 133 + { 134 + static const int idx_to_value[] = { 135 + [SI476X_IDX_PHDIV_DISABLED] = SI476X_PHDIV_DISABLED, 136 + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = SI476X_PHDIV_PRIMARY_COMBINING, 137 + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = SI476X_PHDIV_PRIMARY_ANTENNA, 138 + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = SI476X_PHDIV_SECONDARY_ANTENNA, 139 + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = SI476X_PHDIV_SECONDARY_COMBINING, 140 + }; 141 + 142 + return idx_to_value[idx]; 143 + } 144 + 145 + static const struct v4l2_ctrl_ops si476x_ctrl_ops = { 146 + .g_volatile_ctrl = si476x_radio_g_volatile_ctrl, 147 + .s_ctrl = si476x_radio_s_ctrl, 148 + }; 149 + 150 + 151 + enum si476x_ctrl_idx { 152 + SI476X_IDX_RSSI_THRESHOLD, 153 + SI476X_IDX_SNR_THRESHOLD, 154 + SI476X_IDX_MAX_TUNE_ERROR, 155 + SI476X_IDX_HARMONICS_COUNT, 156 + SI476X_IDX_DIVERSITY_MODE, 157 + SI476X_IDX_INTERCHIP_LINK, 158 + }; 159 + static struct v4l2_ctrl_config si476x_ctrls[] = { 160 + 161 + /** 162 + * SI476X during its station seeking(or tuning) process uses several 163 + * parameters to detrmine if "the station" is valid: 164 + * 165 + * - Signal's SNR(in dBuV) must be lower than 166 + * #V4L2_CID_SI476X_SNR_THRESHOLD 167 + * - Signal's RSSI(in dBuV) must be greater than 168 + * #V4L2_CID_SI476X_RSSI_THRESHOLD 169 + * - Signal's frequency deviation(in units of 2ppm) must not be 170 + * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR 171 + */ 172 + [SI476X_IDX_RSSI_THRESHOLD] = { 173 + .ops = &si476x_ctrl_ops, 174 + .id = V4L2_CID_SI476X_RSSI_THRESHOLD, 175 + .name = "Valid RSSI Threshold", 176 + .type = V4L2_CTRL_TYPE_INTEGER, 177 + .min = -128, 178 + .max = 127, 179 + .step = 1, 180 + }, 181 + [SI476X_IDX_SNR_THRESHOLD] = { 182 + .ops = &si476x_ctrl_ops, 183 + .id = V4L2_CID_SI476X_SNR_THRESHOLD, 184 + .type = V4L2_CTRL_TYPE_INTEGER, 185 + .name = "Valid SNR Threshold", 186 + .min = -128, 187 + .max = 127, 188 + .step = 1, 189 + }, 190 + [SI476X_IDX_MAX_TUNE_ERROR] = { 191 + .ops = &si476x_ctrl_ops, 192 + .id = V4L2_CID_SI476X_MAX_TUNE_ERROR, 193 + .type = V4L2_CTRL_TYPE_INTEGER, 194 + .name = "Max Tune Errors", 195 + .min = 0, 196 + .max = 126 * 2, 197 + .step = 2, 198 + }, 199 + 200 + /** 201 + * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics 202 + * built-in power-line noise supression filter is to reject 203 + * during AM-mode operation. 204 + */ 205 + [SI476X_IDX_HARMONICS_COUNT] = { 206 + .ops = &si476x_ctrl_ops, 207 + .id = V4L2_CID_SI476X_HARMONICS_COUNT, 208 + .type = V4L2_CTRL_TYPE_INTEGER, 209 + 210 + .name = "Count of Harmonics to Reject", 211 + .min = 0, 212 + .max = 20, 213 + .step = 1, 214 + }, 215 + 216 + /** 217 + * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which 218 + * two tuners working in diversity mode are to work in. 219 + * 220 + * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled 221 + * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is 222 + * on, primary tuner's antenna is the main one. 223 + * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is 224 + * off, primary tuner's antenna is the main one. 225 + * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is 226 + * off, secondary tuner's antenna is the main one. 227 + * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is 228 + * on, secondary tuner's antenna is the main one. 229 + */ 230 + [SI476X_IDX_DIVERSITY_MODE] = { 231 + .ops = &si476x_ctrl_ops, 232 + .id = V4L2_CID_SI476X_DIVERSITY_MODE, 233 + .type = V4L2_CTRL_TYPE_MENU, 234 + .name = "Phase Diversity Mode", 235 + .qmenu = phase_diversity_modes, 236 + .min = 0, 237 + .max = ARRAY_SIZE(phase_diversity_modes) - 1, 238 + }, 239 + 240 + /** 241 + * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in 242 + * diversity mode indicator. Allows user to determine if two 243 + * chips working in diversity mode have established a link 244 + * between each other and if the system as a whole uses 245 + * signals from both antennas to receive FM radio. 246 + */ 247 + [SI476X_IDX_INTERCHIP_LINK] = { 248 + .ops = &si476x_ctrl_ops, 249 + .id = V4L2_CID_SI476X_INTERCHIP_LINK, 250 + .type = V4L2_CTRL_TYPE_BOOLEAN, 251 + .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, 252 + .name = "Inter-Chip Link", 253 + .min = 0, 254 + .max = 1, 255 + .step = 1, 256 + }, 257 + }; 258 + 259 + struct si476x_radio; 260 + 261 + /** 262 + * struct si476x_radio_ops - vtable of tuner functions 263 + * 264 + * This table holds pointers to functions implementing particular 265 + * operations depending on the mode in which the tuner chip was 266 + * configured to start in. If the function is not supported 267 + * corresponding element is set to #NULL. 268 + * 269 + * @tune_freq: Tune chip to a specific frequency 270 + * @seek_start: Star station seeking 271 + * @rsq_status: Get Recieved Signal Quality(RSQ) status 272 + * @rds_blckcnt: Get recived RDS blocks count 273 + * @phase_diversity: Change phase diversity mode of the tuner 274 + * @phase_div_status: Get phase diversity mode status 275 + * @acf_status: Get the status of Automatically Controlled 276 + * Features(ACF) 277 + * @agc_status: Get Automatic Gain Control(AGC) status 278 + */ 279 + struct si476x_radio_ops { 280 + int (*tune_freq)(struct si476x_core *, struct si476x_tune_freq_args *); 281 + int (*seek_start)(struct si476x_core *, bool, bool); 282 + int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *, 283 + struct si476x_rsq_status_report *); 284 + int (*rds_blckcnt)(struct si476x_core *, bool, 285 + struct si476x_rds_blockcount_report *); 286 + 287 + int (*phase_diversity)(struct si476x_core *, 288 + enum si476x_phase_diversity_mode); 289 + int (*phase_div_status)(struct si476x_core *); 290 + int (*acf_status)(struct si476x_core *, 291 + struct si476x_acf_status_report *); 292 + int (*agc_status)(struct si476x_core *, 293 + struct si476x_agc_status_report *); 294 + }; 295 + 296 + /** 297 + * struct si476x_radio - radio device 298 + * 299 + * @core: Pointer to underlying core device 300 + * @videodev: Pointer to video device created by V4L2 subsystem 301 + * @ops: Vtable of functions. See struct si476x_radio_ops for details 302 + * @kref: Reference counter 303 + * @core_lock: An r/w semaphore to brebvent the deletion of underlying 304 + * core structure is the radio device is being used 305 + */ 306 + struct si476x_radio { 307 + struct v4l2_device v4l2dev; 308 + struct video_device videodev; 309 + struct v4l2_ctrl_handler ctrl_handler; 310 + 311 + struct si476x_core *core; 312 + /* This field should not be accesses unless core lock is held */ 313 + const struct si476x_radio_ops *ops; 314 + 315 + struct dentry *debugfs; 316 + u32 audmode; 317 + }; 318 + 319 + static inline struct si476x_radio * 320 + v4l2_dev_to_radio(struct v4l2_device *d) 321 + { 322 + return container_of(d, struct si476x_radio, v4l2dev); 323 + } 324 + 325 + static inline struct si476x_radio * 326 + v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d) 327 + { 328 + return container_of(d, struct si476x_radio, ctrl_handler); 329 + } 330 + 331 + /* 332 + * si476x_vidioc_querycap - query device capabilities 333 + */ 334 + static int si476x_radio_querycap(struct file *file, void *priv, 335 + struct v4l2_capability *capability) 336 + { 337 + struct si476x_radio *radio = video_drvdata(file); 338 + 339 + strlcpy(capability->driver, radio->v4l2dev.name, 340 + sizeof(capability->driver)); 341 + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); 342 + snprintf(capability->bus_info, sizeof(capability->bus_info), 343 + "platform:%s", radio->v4l2dev.name); 344 + 345 + capability->device_caps = V4L2_CAP_TUNER 346 + | V4L2_CAP_RADIO 347 + | V4L2_CAP_HW_FREQ_SEEK; 348 + 349 + si476x_core_lock(radio->core); 350 + if (!si476x_core_is_a_secondary_tuner(radio->core)) 351 + capability->device_caps |= V4L2_CAP_RDS_CAPTURE 352 + | V4L2_CAP_READWRITE; 353 + si476x_core_unlock(radio->core); 354 + 355 + capability->capabilities = capability->device_caps 356 + | V4L2_CAP_DEVICE_CAPS; 357 + return 0; 358 + } 359 + 360 + static int si476x_radio_enum_freq_bands(struct file *file, void *priv, 361 + struct v4l2_frequency_band *band) 362 + { 363 + int err; 364 + struct si476x_radio *radio = video_drvdata(file); 365 + 366 + if (band->tuner != 0) 367 + return -EINVAL; 368 + 369 + switch (radio->core->chip_id) { 370 + /* AM/FM tuners -- all bands are supported */ 371 + case SI476X_CHIP_SI4761: 372 + case SI476X_CHIP_SI4764: 373 + if (band->index < ARRAY_SIZE(si476x_bands)) { 374 + *band = si476x_bands[band->index]; 375 + err = 0; 376 + } else { 377 + err = -EINVAL; 378 + } 379 + break; 380 + /* FM companion tuner chips -- only FM bands are 381 + * supported */ 382 + case SI476X_CHIP_SI4768: 383 + if (band->index == SI476X_BAND_FM) { 384 + *band = si476x_bands[band->index]; 385 + err = 0; 386 + } else { 387 + err = -EINVAL; 388 + } 389 + break; 390 + default: 391 + err = -EINVAL; 392 + } 393 + 394 + return err; 395 + } 396 + 397 + static int si476x_radio_g_tuner(struct file *file, void *priv, 398 + struct v4l2_tuner *tuner) 399 + { 400 + int err; 401 + struct si476x_rsq_status_report report; 402 + struct si476x_radio *radio = video_drvdata(file); 403 + 404 + struct si476x_rsq_status_args args = { 405 + .primary = false, 406 + .rsqack = false, 407 + .attune = false, 408 + .cancel = false, 409 + .stcack = false, 410 + }; 411 + 412 + if (tuner->index != 0) 413 + return -EINVAL; 414 + 415 + tuner->type = V4L2_TUNER_RADIO; 416 + tuner->capability = V4L2_TUNER_CAP_LOW /* Measure frequencies 417 + * in multiples of 418 + * 62.5 Hz */ 419 + | V4L2_TUNER_CAP_STEREO 420 + | V4L2_TUNER_CAP_HWSEEK_BOUNDED 421 + | V4L2_TUNER_CAP_HWSEEK_WRAP 422 + | V4L2_TUNER_CAP_HWSEEK_PROG_LIM; 423 + 424 + si476x_core_lock(radio->core); 425 + 426 + if (si476x_core_is_a_secondary_tuner(radio->core)) { 427 + strlcpy(tuner->name, "FM (secondary)", sizeof(tuner->name)); 428 + tuner->rxsubchans = 0; 429 + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; 430 + } else if (si476x_core_has_am(radio->core)) { 431 + if (si476x_core_is_a_primary_tuner(radio->core)) 432 + strlcpy(tuner->name, "AM/FM (primary)", 433 + sizeof(tuner->name)); 434 + else 435 + strlcpy(tuner->name, "AM/FM", sizeof(tuner->name)); 436 + 437 + tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO 438 + | V4L2_TUNER_SUB_RDS; 439 + tuner->capability |= V4L2_TUNER_CAP_RDS 440 + | V4L2_TUNER_CAP_RDS_BLOCK_IO 441 + | V4L2_TUNER_CAP_FREQ_BANDS; 442 + 443 + tuner->rangelow = si476x_bands[SI476X_BAND_AM].rangelow; 444 + } else { 445 + strlcpy(tuner->name, "FM", sizeof(tuner->name)); 446 + tuner->rxsubchans = V4L2_TUNER_SUB_RDS; 447 + tuner->capability |= V4L2_TUNER_CAP_RDS 448 + | V4L2_TUNER_CAP_RDS_BLOCK_IO 449 + | V4L2_TUNER_CAP_FREQ_BANDS; 450 + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; 451 + } 452 + 453 + tuner->audmode = radio->audmode; 454 + 455 + tuner->afc = 1; 456 + tuner->rangehigh = si476x_bands[SI476X_BAND_FM].rangehigh; 457 + 458 + err = radio->ops->rsq_status(radio->core, 459 + &args, &report); 460 + if (err < 0) { 461 + tuner->signal = 0; 462 + } else { 463 + /* 464 + * tuner->signal value range: 0x0000 .. 0xFFFF, 465 + * report.rssi: -128 .. 127 466 + */ 467 + tuner->signal = (report.rssi + 128) * 257; 468 + } 469 + si476x_core_unlock(radio->core); 470 + 471 + return err; 472 + } 473 + 474 + static int si476x_radio_s_tuner(struct file *file, void *priv, 475 + const struct v4l2_tuner *tuner) 476 + { 477 + struct si476x_radio *radio = video_drvdata(file); 478 + 479 + if (tuner->index != 0) 480 + return -EINVAL; 481 + 482 + if (tuner->audmode == V4L2_TUNER_MODE_MONO || 483 + tuner->audmode == V4L2_TUNER_MODE_STEREO) 484 + radio->audmode = tuner->audmode; 485 + else 486 + radio->audmode = V4L2_TUNER_MODE_STEREO; 487 + 488 + return 0; 489 + } 490 + 491 + static int si476x_radio_init_vtable(struct si476x_radio *radio, 492 + enum si476x_func func) 493 + { 494 + static const struct si476x_radio_ops fm_ops = { 495 + .tune_freq = si476x_core_cmd_fm_tune_freq, 496 + .seek_start = si476x_core_cmd_fm_seek_start, 497 + .rsq_status = si476x_core_cmd_fm_rsq_status, 498 + .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount, 499 + .phase_diversity = si476x_core_cmd_fm_phase_diversity, 500 + .phase_div_status = si476x_core_cmd_fm_phase_div_status, 501 + .acf_status = si476x_core_cmd_fm_acf_status, 502 + .agc_status = si476x_core_cmd_agc_status, 503 + }; 504 + 505 + static const struct si476x_radio_ops am_ops = { 506 + .tune_freq = si476x_core_cmd_am_tune_freq, 507 + .seek_start = si476x_core_cmd_am_seek_start, 508 + .rsq_status = si476x_core_cmd_am_rsq_status, 509 + .rds_blckcnt = NULL, 510 + .phase_diversity = NULL, 511 + .phase_div_status = NULL, 512 + .acf_status = si476x_core_cmd_am_acf_status, 513 + .agc_status = NULL, 514 + }; 515 + 516 + switch (func) { 517 + case SI476X_FUNC_FM_RECEIVER: 518 + radio->ops = &fm_ops; 519 + return 0; 520 + 521 + case SI476X_FUNC_AM_RECEIVER: 522 + radio->ops = &am_ops; 523 + return 0; 524 + default: 525 + WARN(1, "Unexpected tuner function value\n"); 526 + return -EINVAL; 527 + } 528 + } 529 + 530 + static int si476x_radio_pretune(struct si476x_radio *radio, 531 + enum si476x_func func) 532 + { 533 + int retval; 534 + 535 + struct si476x_tune_freq_args args = { 536 + .zifsr = false, 537 + .hd = false, 538 + .injside = SI476X_INJSIDE_AUTO, 539 + .tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE, 540 + .smoothmetrics = SI476X_SM_INITIALIZE_AUDIO, 541 + .antcap = 0, 542 + }; 543 + 544 + switch (func) { 545 + case SI476X_FUNC_FM_RECEIVER: 546 + args.freq = v4l2_to_si476x(radio->core, 547 + 92 * FREQ_MUL); 548 + retval = radio->ops->tune_freq(radio->core, &args); 549 + break; 550 + case SI476X_FUNC_AM_RECEIVER: 551 + args.freq = v4l2_to_si476x(radio->core, 552 + 0.6 * FREQ_MUL); 553 + retval = radio->ops->tune_freq(radio->core, &args); 554 + break; 555 + default: 556 + WARN(1, "Unexpected tuner function value\n"); 557 + retval = -EINVAL; 558 + } 559 + 560 + return retval; 561 + } 562 + static int si476x_radio_do_post_powerup_init(struct si476x_radio *radio, 563 + enum si476x_func func) 564 + { 565 + int err; 566 + 567 + /* regcache_mark_dirty(radio->core->regmap); */ 568 + err = regcache_sync_region(radio->core->regmap, 569 + SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE, 570 + SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT); 571 + if (err < 0) 572 + return err; 573 + 574 + err = regcache_sync_region(radio->core->regmap, 575 + SI476X_PROP_AUDIO_DEEMPHASIS, 576 + SI476X_PROP_AUDIO_PWR_LINE_FILTER); 577 + if (err < 0) 578 + return err; 579 + 580 + err = regcache_sync_region(radio->core->regmap, 581 + SI476X_PROP_INT_CTL_ENABLE, 582 + SI476X_PROP_INT_CTL_ENABLE); 583 + if (err < 0) 584 + return err; 585 + 586 + /* 587 + * Is there any point in restoring SNR and the like 588 + * when switching between AM/FM? 589 + */ 590 + err = regcache_sync_region(radio->core->regmap, 591 + SI476X_PROP_VALID_MAX_TUNE_ERROR, 592 + SI476X_PROP_VALID_MAX_TUNE_ERROR); 593 + if (err < 0) 594 + return err; 595 + 596 + err = regcache_sync_region(radio->core->regmap, 597 + SI476X_PROP_VALID_SNR_THRESHOLD, 598 + SI476X_PROP_VALID_RSSI_THRESHOLD); 599 + if (err < 0) 600 + return err; 601 + 602 + if (func == SI476X_FUNC_FM_RECEIVER) { 603 + if (si476x_core_has_diversity(radio->core)) { 604 + err = si476x_core_cmd_fm_phase_diversity(radio->core, 605 + radio->core->diversity_mode); 606 + if (err < 0) 607 + return err; 608 + } 609 + 610 + err = regcache_sync_region(radio->core->regmap, 611 + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, 612 + SI476X_PROP_FM_RDS_CONFIG); 613 + if (err < 0) 614 + return err; 615 + } 616 + 617 + return si476x_radio_init_vtable(radio, func); 618 + 619 + } 620 + 621 + static int si476x_radio_change_func(struct si476x_radio *radio, 622 + enum si476x_func func) 623 + { 624 + int err; 625 + bool soft; 626 + /* 627 + * Since power/up down is a very time consuming operation, 628 + * try to avoid doing it if the requested mode matches the one 629 + * the tuner is in 630 + */ 631 + if (func == radio->core->power_up_parameters.func) 632 + return 0; 633 + 634 + soft = true; 635 + err = si476x_core_stop(radio->core, soft); 636 + if (err < 0) { 637 + /* 638 + * OK, if the chip does not want to play nice let's 639 + * try to reset it in more brutal way 640 + */ 641 + soft = false; 642 + err = si476x_core_stop(radio->core, soft); 643 + if (err < 0) 644 + return err; 645 + } 646 + /* 647 + Set the desired radio tuner function 648 + */ 649 + radio->core->power_up_parameters.func = func; 650 + 651 + err = si476x_core_start(radio->core, soft); 652 + if (err < 0) 653 + return err; 654 + 655 + /* 656 + * No need to do the rest of manipulations for the bootlader 657 + * mode 658 + */ 659 + if (func != SI476X_FUNC_FM_RECEIVER && 660 + func != SI476X_FUNC_AM_RECEIVER) 661 + return err; 662 + 663 + return si476x_radio_do_post_powerup_init(radio, func); 664 + } 665 + 666 + static int si476x_radio_g_frequency(struct file *file, void *priv, 667 + struct v4l2_frequency *f) 668 + { 669 + int err; 670 + struct si476x_radio *radio = video_drvdata(file); 671 + 672 + if (f->tuner != 0 || 673 + f->type != V4L2_TUNER_RADIO) 674 + return -EINVAL; 675 + 676 + si476x_core_lock(radio->core); 677 + 678 + if (radio->ops->rsq_status) { 679 + struct si476x_rsq_status_report report; 680 + struct si476x_rsq_status_args args = { 681 + .primary = false, 682 + .rsqack = false, 683 + .attune = true, 684 + .cancel = false, 685 + .stcack = false, 686 + }; 687 + 688 + err = radio->ops->rsq_status(radio->core, &args, &report); 689 + if (!err) 690 + f->frequency = si476x_to_v4l2(radio->core, 691 + report.readfreq); 692 + } else { 693 + err = -EINVAL; 694 + } 695 + 696 + si476x_core_unlock(radio->core); 697 + 698 + return err; 699 + } 700 + 701 + static int si476x_radio_s_frequency(struct file *file, void *priv, 702 + const struct v4l2_frequency *f) 703 + { 704 + int err; 705 + u32 freq = f->frequency; 706 + struct si476x_tune_freq_args args; 707 + struct si476x_radio *radio = video_drvdata(file); 708 + 709 + const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh + 710 + si476x_bands[SI476X_BAND_FM].rangelow) / 2; 711 + const int band = (freq > midrange) ? 712 + SI476X_BAND_FM : SI476X_BAND_AM; 713 + const enum si476x_func func = (band == SI476X_BAND_AM) ? 714 + SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER; 715 + 716 + if (f->tuner != 0 || 717 + f->type != V4L2_TUNER_RADIO) 718 + return -EINVAL; 719 + 720 + si476x_core_lock(radio->core); 721 + 722 + freq = clamp(freq, 723 + si476x_bands[band].rangelow, 724 + si476x_bands[band].rangehigh); 725 + 726 + if (si476x_radio_freq_is_inside_of_the_band(freq, 727 + SI476X_BAND_AM) && 728 + (!si476x_core_has_am(radio->core) || 729 + si476x_core_is_a_secondary_tuner(radio->core))) { 730 + err = -EINVAL; 731 + goto unlock; 732 + } 733 + 734 + err = si476x_radio_change_func(radio, func); 735 + if (err < 0) 736 + goto unlock; 737 + 738 + args.zifsr = false; 739 + args.hd = false; 740 + args.injside = SI476X_INJSIDE_AUTO; 741 + args.freq = v4l2_to_si476x(radio->core, freq); 742 + args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE; 743 + args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO; 744 + args.antcap = 0; 745 + 746 + err = radio->ops->tune_freq(radio->core, &args); 747 + 748 + unlock: 749 + si476x_core_unlock(radio->core); 750 + return err; 751 + } 752 + 753 + static int si476x_radio_s_hw_freq_seek(struct file *file, void *priv, 754 + const struct v4l2_hw_freq_seek *seek) 755 + { 756 + int err; 757 + enum si476x_func func; 758 + u32 rangelow, rangehigh; 759 + struct si476x_radio *radio = video_drvdata(file); 760 + 761 + if (file->f_flags & O_NONBLOCK) 762 + return -EAGAIN; 763 + 764 + if (seek->tuner != 0 || 765 + seek->type != V4L2_TUNER_RADIO) 766 + return -EINVAL; 767 + 768 + si476x_core_lock(radio->core); 769 + 770 + if (!seek->rangelow) { 771 + err = regmap_read(radio->core->regmap, 772 + SI476X_PROP_SEEK_BAND_BOTTOM, 773 + &rangelow); 774 + if (!err) 775 + rangelow = si476x_to_v4l2(radio->core, rangelow); 776 + else 777 + goto unlock; 778 + } 779 + if (!seek->rangehigh) { 780 + err = regmap_read(radio->core->regmap, 781 + SI476X_PROP_SEEK_BAND_TOP, 782 + &rangehigh); 783 + if (!err) 784 + rangehigh = si476x_to_v4l2(radio->core, rangehigh); 785 + else 786 + goto unlock; 787 + } 788 + 789 + if (rangelow > rangehigh) { 790 + err = -EINVAL; 791 + goto unlock; 792 + } 793 + 794 + if (si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, 795 + SI476X_BAND_FM)) { 796 + func = SI476X_FUNC_FM_RECEIVER; 797 + 798 + } else if (si476x_core_has_am(radio->core) && 799 + si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, 800 + SI476X_BAND_AM)) { 801 + func = SI476X_FUNC_AM_RECEIVER; 802 + } else { 803 + err = -EINVAL; 804 + goto unlock; 805 + } 806 + 807 + err = si476x_radio_change_func(radio, func); 808 + if (err < 0) 809 + goto unlock; 810 + 811 + if (seek->rangehigh) { 812 + err = regmap_write(radio->core->regmap, 813 + SI476X_PROP_SEEK_BAND_TOP, 814 + v4l2_to_si476x(radio->core, 815 + seek->rangehigh)); 816 + if (err) 817 + goto unlock; 818 + } 819 + if (seek->rangelow) { 820 + err = regmap_write(radio->core->regmap, 821 + SI476X_PROP_SEEK_BAND_BOTTOM, 822 + v4l2_to_si476x(radio->core, 823 + seek->rangelow)); 824 + if (err) 825 + goto unlock; 826 + } 827 + if (seek->spacing) { 828 + err = regmap_write(radio->core->regmap, 829 + SI476X_PROP_SEEK_FREQUENCY_SPACING, 830 + v4l2_to_si476x(radio->core, 831 + seek->spacing)); 832 + if (err) 833 + goto unlock; 834 + } 835 + 836 + err = radio->ops->seek_start(radio->core, 837 + seek->seek_upward, 838 + seek->wrap_around); 839 + unlock: 840 + si476x_core_unlock(radio->core); 841 + 842 + 843 + 844 + return err; 845 + } 846 + 847 + static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl) 848 + { 849 + int retval; 850 + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); 851 + 852 + si476x_core_lock(radio->core); 853 + 854 + switch (ctrl->id) { 855 + case V4L2_CID_SI476X_INTERCHIP_LINK: 856 + if (si476x_core_has_diversity(radio->core)) { 857 + if (radio->ops->phase_diversity) { 858 + retval = radio->ops->phase_div_status(radio->core); 859 + if (retval < 0) 860 + break; 861 + 862 + ctrl->val = !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval); 863 + retval = 0; 864 + break; 865 + } else { 866 + retval = -ENOTTY; 867 + break; 868 + } 869 + } 870 + retval = -EINVAL; 871 + break; 872 + default: 873 + retval = -EINVAL; 874 + break; 875 + } 876 + si476x_core_unlock(radio->core); 877 + return retval; 878 + 879 + } 880 + 881 + static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl) 882 + { 883 + int retval; 884 + enum si476x_phase_diversity_mode mode; 885 + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); 886 + 887 + si476x_core_lock(radio->core); 888 + 889 + switch (ctrl->id) { 890 + case V4L2_CID_SI476X_HARMONICS_COUNT: 891 + retval = regmap_update_bits(radio->core->regmap, 892 + SI476X_PROP_AUDIO_PWR_LINE_FILTER, 893 + SI476X_PROP_PWR_HARMONICS_MASK, 894 + ctrl->val); 895 + break; 896 + case V4L2_CID_POWER_LINE_FREQUENCY: 897 + switch (ctrl->val) { 898 + case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: 899 + retval = regmap_update_bits(radio->core->regmap, 900 + SI476X_PROP_AUDIO_PWR_LINE_FILTER, 901 + SI476X_PROP_PWR_ENABLE_MASK, 902 + 0); 903 + break; 904 + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: 905 + retval = regmap_update_bits(radio->core->regmap, 906 + SI476X_PROP_AUDIO_PWR_LINE_FILTER, 907 + SI476X_PROP_PWR_GRID_MASK, 908 + SI476X_PROP_PWR_GRID_50HZ); 909 + break; 910 + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: 911 + retval = regmap_update_bits(radio->core->regmap, 912 + SI476X_PROP_AUDIO_PWR_LINE_FILTER, 913 + SI476X_PROP_PWR_GRID_MASK, 914 + SI476X_PROP_PWR_GRID_60HZ); 915 + break; 916 + default: 917 + retval = -EINVAL; 918 + break; 919 + } 920 + break; 921 + case V4L2_CID_SI476X_RSSI_THRESHOLD: 922 + retval = regmap_write(radio->core->regmap, 923 + SI476X_PROP_VALID_RSSI_THRESHOLD, 924 + ctrl->val); 925 + break; 926 + case V4L2_CID_SI476X_SNR_THRESHOLD: 927 + retval = regmap_write(radio->core->regmap, 928 + SI476X_PROP_VALID_SNR_THRESHOLD, 929 + ctrl->val); 930 + break; 931 + case V4L2_CID_SI476X_MAX_TUNE_ERROR: 932 + retval = regmap_write(radio->core->regmap, 933 + SI476X_PROP_VALID_MAX_TUNE_ERROR, 934 + ctrl->val); 935 + break; 936 + case V4L2_CID_RDS_RECEPTION: 937 + /* 938 + * It looks like RDS related properties are 939 + * inaccesable when tuner is in AM mode, so cache the 940 + * changes 941 + */ 942 + if (si476x_core_is_in_am_receiver_mode(radio->core)) 943 + regcache_cache_only(radio->core->regmap, true); 944 + 945 + if (ctrl->val) { 946 + retval = regmap_write(radio->core->regmap, 947 + SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT, 948 + radio->core->rds_fifo_depth); 949 + if (retval < 0) 950 + break; 951 + 952 + if (radio->core->client->irq) { 953 + retval = regmap_write(radio->core->regmap, 954 + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, 955 + SI476X_RDSRECV); 956 + if (retval < 0) 957 + break; 958 + } 959 + 960 + /* Drain RDS FIFO before enabling RDS processing */ 961 + retval = si476x_core_cmd_fm_rds_status(radio->core, 962 + false, 963 + true, 964 + true, 965 + NULL); 966 + if (retval < 0) 967 + break; 968 + 969 + retval = regmap_update_bits(radio->core->regmap, 970 + SI476X_PROP_FM_RDS_CONFIG, 971 + SI476X_PROP_RDSEN_MASK, 972 + SI476X_PROP_RDSEN); 973 + } else { 974 + retval = regmap_update_bits(radio->core->regmap, 975 + SI476X_PROP_FM_RDS_CONFIG, 976 + SI476X_PROP_RDSEN_MASK, 977 + !SI476X_PROP_RDSEN); 978 + } 979 + 980 + if (si476x_core_is_in_am_receiver_mode(radio->core)) 981 + regcache_cache_only(radio->core->regmap, false); 982 + break; 983 + case V4L2_CID_TUNE_DEEMPHASIS: 984 + retval = regmap_write(radio->core->regmap, 985 + SI476X_PROP_AUDIO_DEEMPHASIS, 986 + ctrl->val); 987 + break; 988 + 989 + case V4L2_CID_SI476X_DIVERSITY_MODE: 990 + mode = si476x_phase_diversity_idx_to_mode(ctrl->val); 991 + 992 + if (mode == radio->core->diversity_mode) { 993 + retval = 0; 994 + break; 995 + } 996 + 997 + if (si476x_core_is_in_am_receiver_mode(radio->core)) { 998 + /* 999 + * Diversity cannot be configured while tuner 1000 + * is in AM mode so save the changes and carry on. 1001 + */ 1002 + radio->core->diversity_mode = mode; 1003 + retval = 0; 1004 + } else { 1005 + retval = radio->ops->phase_diversity(radio->core, mode); 1006 + if (!retval) 1007 + radio->core->diversity_mode = mode; 1008 + } 1009 + break; 1010 + 1011 + default: 1012 + retval = -EINVAL; 1013 + break; 1014 + } 1015 + 1016 + si476x_core_unlock(radio->core); 1017 + 1018 + return retval; 1019 + } 1020 + 1021 + static int si476x_radio_g_chip_ident(struct file *file, void *fh, 1022 + struct v4l2_dbg_chip_ident *chip) 1023 + { 1024 + if (chip->match.type == V4L2_CHIP_MATCH_HOST && 1025 + v4l2_chip_match_host(&chip->match)) 1026 + return 0; 1027 + return -EINVAL; 1028 + } 1029 + 1030 + 1031 + #ifdef CONFIG_VIDEO_ADV_DEBUG 1032 + static int si476x_radio_g_register(struct file *file, void *fh, 1033 + struct v4l2_dbg_register *reg) 1034 + { 1035 + int err; 1036 + unsigned int value; 1037 + struct si476x_radio *radio = video_drvdata(file); 1038 + 1039 + si476x_core_lock(radio->core); 1040 + reg->size = 2; 1041 + err = regmap_read(radio->core->regmap, 1042 + (unsigned int)reg->reg, &value); 1043 + reg->val = value; 1044 + si476x_core_unlock(radio->core); 1045 + 1046 + return err; 1047 + } 1048 + static int si476x_radio_s_register(struct file *file, void *fh, 1049 + const struct v4l2_dbg_register *reg) 1050 + { 1051 + 1052 + int err; 1053 + struct si476x_radio *radio = video_drvdata(file); 1054 + 1055 + si476x_core_lock(radio->core); 1056 + err = regmap_write(radio->core->regmap, 1057 + (unsigned int)reg->reg, 1058 + (unsigned int)reg->val); 1059 + si476x_core_unlock(radio->core); 1060 + 1061 + return err; 1062 + } 1063 + #endif 1064 + 1065 + static int si476x_radio_fops_open(struct file *file) 1066 + { 1067 + struct si476x_radio *radio = video_drvdata(file); 1068 + int err; 1069 + 1070 + err = v4l2_fh_open(file); 1071 + if (err) 1072 + return err; 1073 + 1074 + if (v4l2_fh_is_singular_file(file)) { 1075 + si476x_core_lock(radio->core); 1076 + err = si476x_core_set_power_state(radio->core, 1077 + SI476X_POWER_UP_FULL); 1078 + if (err < 0) 1079 + goto done; 1080 + 1081 + err = si476x_radio_do_post_powerup_init(radio, 1082 + radio->core->power_up_parameters.func); 1083 + if (err < 0) 1084 + goto power_down; 1085 + 1086 + err = si476x_radio_pretune(radio, 1087 + radio->core->power_up_parameters.func); 1088 + if (err < 0) 1089 + goto power_down; 1090 + 1091 + si476x_core_unlock(radio->core); 1092 + /*Must be done after si476x_core_unlock to prevent a deadlock*/ 1093 + v4l2_ctrl_handler_setup(&radio->ctrl_handler); 1094 + } 1095 + 1096 + return err; 1097 + 1098 + power_down: 1099 + si476x_core_set_power_state(radio->core, 1100 + SI476X_POWER_DOWN); 1101 + done: 1102 + si476x_core_unlock(radio->core); 1103 + v4l2_fh_release(file); 1104 + 1105 + return err; 1106 + } 1107 + 1108 + static int si476x_radio_fops_release(struct file *file) 1109 + { 1110 + int err; 1111 + struct si476x_radio *radio = video_drvdata(file); 1112 + 1113 + if (v4l2_fh_is_singular_file(file) && 1114 + atomic_read(&radio->core->is_alive)) 1115 + si476x_core_set_power_state(radio->core, 1116 + SI476X_POWER_DOWN); 1117 + 1118 + err = v4l2_fh_release(file); 1119 + 1120 + return err; 1121 + } 1122 + 1123 + static ssize_t si476x_radio_fops_read(struct file *file, char __user *buf, 1124 + size_t count, loff_t *ppos) 1125 + { 1126 + ssize_t rval; 1127 + size_t fifo_len; 1128 + unsigned int copied; 1129 + 1130 + struct si476x_radio *radio = video_drvdata(file); 1131 + 1132 + /* block if no new data available */ 1133 + if (kfifo_is_empty(&radio->core->rds_fifo)) { 1134 + if (file->f_flags & O_NONBLOCK) 1135 + return -EWOULDBLOCK; 1136 + 1137 + rval = wait_event_interruptible(radio->core->rds_read_queue, 1138 + (!kfifo_is_empty(&radio->core->rds_fifo) || 1139 + !atomic_read(&radio->core->is_alive))); 1140 + if (rval < 0) 1141 + return -EINTR; 1142 + 1143 + if (!atomic_read(&radio->core->is_alive)) 1144 + return -ENODEV; 1145 + } 1146 + 1147 + fifo_len = kfifo_len(&radio->core->rds_fifo); 1148 + 1149 + if (kfifo_to_user(&radio->core->rds_fifo, buf, 1150 + min(fifo_len, count), 1151 + &copied) != 0) { 1152 + dev_warn(&radio->videodev.dev, 1153 + "Error during FIFO to userspace copy\n"); 1154 + rval = -EIO; 1155 + } else { 1156 + rval = (ssize_t)copied; 1157 + } 1158 + 1159 + return rval; 1160 + } 1161 + 1162 + static unsigned int si476x_radio_fops_poll(struct file *file, 1163 + struct poll_table_struct *pts) 1164 + { 1165 + struct si476x_radio *radio = video_drvdata(file); 1166 + unsigned long req_events = poll_requested_events(pts); 1167 + unsigned int err = v4l2_ctrl_poll(file, pts); 1168 + 1169 + if (req_events & (POLLIN | POLLRDNORM)) { 1170 + if (atomic_read(&radio->core->is_alive)) 1171 + poll_wait(file, &radio->core->rds_read_queue, pts); 1172 + 1173 + if (!atomic_read(&radio->core->is_alive)) 1174 + err = POLLHUP; 1175 + 1176 + if (!kfifo_is_empty(&radio->core->rds_fifo)) 1177 + err = POLLIN | POLLRDNORM; 1178 + } 1179 + 1180 + return err; 1181 + } 1182 + 1183 + static const struct v4l2_file_operations si476x_fops = { 1184 + .owner = THIS_MODULE, 1185 + .read = si476x_radio_fops_read, 1186 + .poll = si476x_radio_fops_poll, 1187 + .unlocked_ioctl = video_ioctl2, 1188 + .open = si476x_radio_fops_open, 1189 + .release = si476x_radio_fops_release, 1190 + }; 1191 + 1192 + 1193 + static const struct v4l2_ioctl_ops si4761_ioctl_ops = { 1194 + .vidioc_querycap = si476x_radio_querycap, 1195 + .vidioc_g_tuner = si476x_radio_g_tuner, 1196 + .vidioc_s_tuner = si476x_radio_s_tuner, 1197 + 1198 + .vidioc_g_frequency = si476x_radio_g_frequency, 1199 + .vidioc_s_frequency = si476x_radio_s_frequency, 1200 + .vidioc_s_hw_freq_seek = si476x_radio_s_hw_freq_seek, 1201 + .vidioc_enum_freq_bands = si476x_radio_enum_freq_bands, 1202 + 1203 + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 1204 + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 1205 + 1206 + .vidioc_g_chip_ident = si476x_radio_g_chip_ident, 1207 + #ifdef CONFIG_VIDEO_ADV_DEBUG 1208 + .vidioc_g_register = si476x_radio_g_register, 1209 + .vidioc_s_register = si476x_radio_s_register, 1210 + #endif 1211 + }; 1212 + 1213 + 1214 + static const struct video_device si476x_viddev_template = { 1215 + .fops = &si476x_fops, 1216 + .name = DRIVER_NAME, 1217 + .release = video_device_release_empty, 1218 + }; 1219 + 1220 + 1221 + 1222 + static ssize_t si476x_radio_read_acf_blob(struct file *file, 1223 + char __user *user_buf, 1224 + size_t count, loff_t *ppos) 1225 + { 1226 + int err; 1227 + struct si476x_radio *radio = file->private_data; 1228 + struct si476x_acf_status_report report; 1229 + 1230 + si476x_core_lock(radio->core); 1231 + if (radio->ops->acf_status) 1232 + err = radio->ops->acf_status(radio->core, &report); 1233 + else 1234 + err = -ENOENT; 1235 + si476x_core_unlock(radio->core); 1236 + 1237 + if (err < 0) 1238 + return err; 1239 + 1240 + return simple_read_from_buffer(user_buf, count, ppos, &report, 1241 + sizeof(report)); 1242 + } 1243 + 1244 + static const struct file_operations radio_acf_fops = { 1245 + .open = simple_open, 1246 + .llseek = default_llseek, 1247 + .read = si476x_radio_read_acf_blob, 1248 + }; 1249 + 1250 + static ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file, 1251 + char __user *user_buf, 1252 + size_t count, loff_t *ppos) 1253 + { 1254 + int err; 1255 + struct si476x_radio *radio = file->private_data; 1256 + struct si476x_rds_blockcount_report report; 1257 + 1258 + si476x_core_lock(radio->core); 1259 + if (radio->ops->rds_blckcnt) 1260 + err = radio->ops->rds_blckcnt(radio->core, true, 1261 + &report); 1262 + else 1263 + err = -ENOENT; 1264 + si476x_core_unlock(radio->core); 1265 + 1266 + if (err < 0) 1267 + return err; 1268 + 1269 + return simple_read_from_buffer(user_buf, count, ppos, &report, 1270 + sizeof(report)); 1271 + } 1272 + 1273 + static const struct file_operations radio_rds_blckcnt_fops = { 1274 + .open = simple_open, 1275 + .llseek = default_llseek, 1276 + .read = si476x_radio_read_rds_blckcnt_blob, 1277 + }; 1278 + 1279 + static ssize_t si476x_radio_read_agc_blob(struct file *file, 1280 + char __user *user_buf, 1281 + size_t count, loff_t *ppos) 1282 + { 1283 + int err; 1284 + struct si476x_radio *radio = file->private_data; 1285 + struct si476x_agc_status_report report; 1286 + 1287 + si476x_core_lock(radio->core); 1288 + if (radio->ops->rds_blckcnt) 1289 + err = radio->ops->agc_status(radio->core, &report); 1290 + else 1291 + err = -ENOENT; 1292 + si476x_core_unlock(radio->core); 1293 + 1294 + if (err < 0) 1295 + return err; 1296 + 1297 + return simple_read_from_buffer(user_buf, count, ppos, &report, 1298 + sizeof(report)); 1299 + } 1300 + 1301 + static const struct file_operations radio_agc_fops = { 1302 + .open = simple_open, 1303 + .llseek = default_llseek, 1304 + .read = si476x_radio_read_agc_blob, 1305 + }; 1306 + 1307 + static ssize_t si476x_radio_read_rsq_blob(struct file *file, 1308 + char __user *user_buf, 1309 + size_t count, loff_t *ppos) 1310 + { 1311 + int err; 1312 + struct si476x_radio *radio = file->private_data; 1313 + struct si476x_rsq_status_report report; 1314 + struct si476x_rsq_status_args args = { 1315 + .primary = false, 1316 + .rsqack = false, 1317 + .attune = false, 1318 + .cancel = false, 1319 + .stcack = false, 1320 + }; 1321 + 1322 + si476x_core_lock(radio->core); 1323 + if (radio->ops->rds_blckcnt) 1324 + err = radio->ops->rsq_status(radio->core, &args, &report); 1325 + else 1326 + err = -ENOENT; 1327 + si476x_core_unlock(radio->core); 1328 + 1329 + if (err < 0) 1330 + return err; 1331 + 1332 + return simple_read_from_buffer(user_buf, count, ppos, &report, 1333 + sizeof(report)); 1334 + } 1335 + 1336 + static const struct file_operations radio_rsq_fops = { 1337 + .open = simple_open, 1338 + .llseek = default_llseek, 1339 + .read = si476x_radio_read_rsq_blob, 1340 + }; 1341 + 1342 + static ssize_t si476x_radio_read_rsq_primary_blob(struct file *file, 1343 + char __user *user_buf, 1344 + size_t count, loff_t *ppos) 1345 + { 1346 + int err; 1347 + struct si476x_radio *radio = file->private_data; 1348 + struct si476x_rsq_status_report report; 1349 + struct si476x_rsq_status_args args = { 1350 + .primary = true, 1351 + .rsqack = false, 1352 + .attune = false, 1353 + .cancel = false, 1354 + .stcack = false, 1355 + }; 1356 + 1357 + si476x_core_lock(radio->core); 1358 + if (radio->ops->rds_blckcnt) 1359 + err = radio->ops->rsq_status(radio->core, &args, &report); 1360 + else 1361 + err = -ENOENT; 1362 + si476x_core_unlock(radio->core); 1363 + 1364 + if (err < 0) 1365 + return err; 1366 + 1367 + return simple_read_from_buffer(user_buf, count, ppos, &report, 1368 + sizeof(report)); 1369 + } 1370 + 1371 + static const struct file_operations radio_rsq_primary_fops = { 1372 + .open = simple_open, 1373 + .llseek = default_llseek, 1374 + .read = si476x_radio_read_rsq_primary_blob, 1375 + }; 1376 + 1377 + 1378 + static int si476x_radio_init_debugfs(struct si476x_radio *radio) 1379 + { 1380 + struct dentry *dentry; 1381 + int ret; 1382 + 1383 + dentry = debugfs_create_dir(dev_name(radio->v4l2dev.dev), NULL); 1384 + if (IS_ERR(dentry)) { 1385 + ret = PTR_ERR(dentry); 1386 + goto exit; 1387 + } 1388 + radio->debugfs = dentry; 1389 + 1390 + dentry = debugfs_create_file("acf", S_IRUGO, 1391 + radio->debugfs, radio, &radio_acf_fops); 1392 + if (IS_ERR(dentry)) { 1393 + ret = PTR_ERR(dentry); 1394 + goto cleanup; 1395 + } 1396 + 1397 + dentry = debugfs_create_file("rds_blckcnt", S_IRUGO, 1398 + radio->debugfs, radio, 1399 + &radio_rds_blckcnt_fops); 1400 + if (IS_ERR(dentry)) { 1401 + ret = PTR_ERR(dentry); 1402 + goto cleanup; 1403 + } 1404 + 1405 + dentry = debugfs_create_file("agc", S_IRUGO, 1406 + radio->debugfs, radio, &radio_agc_fops); 1407 + if (IS_ERR(dentry)) { 1408 + ret = PTR_ERR(dentry); 1409 + goto cleanup; 1410 + } 1411 + 1412 + dentry = debugfs_create_file("rsq", S_IRUGO, 1413 + radio->debugfs, radio, &radio_rsq_fops); 1414 + if (IS_ERR(dentry)) { 1415 + ret = PTR_ERR(dentry); 1416 + goto cleanup; 1417 + } 1418 + 1419 + dentry = debugfs_create_file("rsq_primary", S_IRUGO, 1420 + radio->debugfs, radio, 1421 + &radio_rsq_primary_fops); 1422 + if (IS_ERR(dentry)) { 1423 + ret = PTR_ERR(dentry); 1424 + goto cleanup; 1425 + } 1426 + 1427 + return 0; 1428 + cleanup: 1429 + debugfs_remove_recursive(radio->debugfs); 1430 + exit: 1431 + return ret; 1432 + } 1433 + 1434 + 1435 + static int si476x_radio_add_new_custom(struct si476x_radio *radio, 1436 + enum si476x_ctrl_idx idx) 1437 + { 1438 + int rval; 1439 + struct v4l2_ctrl *ctrl; 1440 + 1441 + ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler, 1442 + &si476x_ctrls[idx], 1443 + NULL); 1444 + rval = radio->ctrl_handler.error; 1445 + if (ctrl == NULL && rval) 1446 + dev_err(radio->v4l2dev.dev, 1447 + "Could not initialize '%s' control %d\n", 1448 + si476x_ctrls[idx].name, rval); 1449 + 1450 + return rval; 1451 + } 1452 + 1453 + static int si476x_radio_probe(struct platform_device *pdev) 1454 + { 1455 + int rval; 1456 + struct si476x_radio *radio; 1457 + struct v4l2_ctrl *ctrl; 1458 + 1459 + static atomic_t instance = ATOMIC_INIT(0); 1460 + 1461 + radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); 1462 + if (!radio) 1463 + return -ENOMEM; 1464 + 1465 + radio->core = i2c_mfd_cell_to_core(&pdev->dev); 1466 + 1467 + v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance); 1468 + 1469 + rval = v4l2_device_register(&pdev->dev, &radio->v4l2dev); 1470 + if (rval) { 1471 + dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); 1472 + return rval; 1473 + } 1474 + 1475 + memcpy(&radio->videodev, &si476x_viddev_template, 1476 + sizeof(struct video_device)); 1477 + 1478 + radio->videodev.v4l2_dev = &radio->v4l2dev; 1479 + radio->videodev.ioctl_ops = &si4761_ioctl_ops; 1480 + 1481 + video_set_drvdata(&radio->videodev, radio); 1482 + platform_set_drvdata(pdev, radio); 1483 + 1484 + set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags); 1485 + 1486 + radio->v4l2dev.ctrl_handler = &radio->ctrl_handler; 1487 + v4l2_ctrl_handler_init(&radio->ctrl_handler, 1488 + 1 + ARRAY_SIZE(si476x_ctrls)); 1489 + 1490 + if (si476x_core_has_am(radio->core)) { 1491 + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, 1492 + &si476x_ctrl_ops, 1493 + V4L2_CID_POWER_LINE_FREQUENCY, 1494 + V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 1495 + 0, 0); 1496 + rval = radio->ctrl_handler.error; 1497 + if (ctrl == NULL && rval) { 1498 + dev_err(&pdev->dev, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n", 1499 + rval); 1500 + goto exit; 1501 + } 1502 + 1503 + rval = si476x_radio_add_new_custom(radio, 1504 + SI476X_IDX_HARMONICS_COUNT); 1505 + if (rval < 0) 1506 + goto exit; 1507 + } 1508 + 1509 + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_RSSI_THRESHOLD); 1510 + if (rval < 0) 1511 + goto exit; 1512 + 1513 + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_SNR_THRESHOLD); 1514 + if (rval < 0) 1515 + goto exit; 1516 + 1517 + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_MAX_TUNE_ERROR); 1518 + if (rval < 0) 1519 + goto exit; 1520 + 1521 + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, 1522 + &si476x_ctrl_ops, 1523 + V4L2_CID_TUNE_DEEMPHASIS, 1524 + V4L2_DEEMPHASIS_75_uS, 0, 0); 1525 + rval = radio->ctrl_handler.error; 1526 + if (ctrl == NULL && rval) { 1527 + dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n", 1528 + rval); 1529 + goto exit; 1530 + } 1531 + 1532 + ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops, 1533 + V4L2_CID_RDS_RECEPTION, 1534 + 0, 1, 1, 1); 1535 + rval = radio->ctrl_handler.error; 1536 + if (ctrl == NULL && rval) { 1537 + dev_err(&pdev->dev, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n", 1538 + rval); 1539 + goto exit; 1540 + } 1541 + 1542 + if (si476x_core_has_diversity(radio->core)) { 1543 + si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def = 1544 + si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode); 1545 + si476x_radio_add_new_custom(radio, SI476X_IDX_DIVERSITY_MODE); 1546 + if (rval < 0) 1547 + goto exit; 1548 + 1549 + si476x_radio_add_new_custom(radio, SI476X_IDX_INTERCHIP_LINK); 1550 + if (rval < 0) 1551 + goto exit; 1552 + } 1553 + 1554 + /* register video device */ 1555 + rval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1); 1556 + if (rval < 0) { 1557 + dev_err(&pdev->dev, "Could not register video device\n"); 1558 + goto exit; 1559 + } 1560 + 1561 + rval = si476x_radio_init_debugfs(radio); 1562 + if (rval < 0) { 1563 + dev_err(&pdev->dev, "Could not creat debugfs interface\n"); 1564 + goto exit; 1565 + } 1566 + 1567 + return 0; 1568 + exit: 1569 + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); 1570 + return rval; 1571 + } 1572 + 1573 + static int si476x_radio_remove(struct platform_device *pdev) 1574 + { 1575 + struct si476x_radio *radio = platform_get_drvdata(pdev); 1576 + 1577 + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); 1578 + video_unregister_device(&radio->videodev); 1579 + v4l2_device_unregister(&radio->v4l2dev); 1580 + debugfs_remove_recursive(radio->debugfs); 1581 + 1582 + return 0; 1583 + } 1584 + 1585 + MODULE_ALIAS("platform:si476x-radio"); 1586 + 1587 + static struct platform_driver si476x_radio_driver = { 1588 + .driver = { 1589 + .name = DRIVER_NAME, 1590 + .owner = THIS_MODULE, 1591 + }, 1592 + .probe = si476x_radio_probe, 1593 + .remove = si476x_radio_remove, 1594 + }; 1595 + module_platform_driver(si476x_radio_driver); 1596 + 1597 + MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); 1598 + MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell"); 1599 + MODULE_LICENSE("GPL");
+37
include/media/si476x.h
··· 1 + /* 2 + * include/media/si476x.h -- Common definitions for si476x driver 3 + * 4 + * Copyright (C) 2012 Innovative Converged Devices(ICD) 5 + * Copyright (C) 2013 Andrey Smirnov 6 + * 7 + * Author: Andrey Smirnov <andrew.smirnov@gmail.com> 8 + * 9 + * This program is free software; you can redistribute it and/or modify 10 + * it under the terms of the GNU General Public License as published by 11 + * the Free Software Foundation; version 2 of the License. 12 + * 13 + * This program is distributed in the hope that it will be useful, but 14 + * WITHOUT ANY WARRANTY; without even the implied warranty of 15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 + * General Public License for more details. 17 + * 18 + */ 19 + 20 + #ifndef SI476X_H 21 + #define SI476X_H 22 + 23 + #include <linux/types.h> 24 + #include <linux/videodev2.h> 25 + 26 + #include <linux/mfd/si476x-reports.h> 27 + 28 + enum si476x_ctrl_id { 29 + V4L2_CID_SI476X_RSSI_THRESHOLD = (V4L2_CID_USER_SI476X_BASE + 1), 30 + V4L2_CID_SI476X_SNR_THRESHOLD = (V4L2_CID_USER_SI476X_BASE + 2), 31 + V4L2_CID_SI476X_MAX_TUNE_ERROR = (V4L2_CID_USER_SI476X_BASE + 3), 32 + V4L2_CID_SI476X_HARMONICS_COUNT = (V4L2_CID_USER_SI476X_BASE + 4), 33 + V4L2_CID_SI476X_DIVERSITY_MODE = (V4L2_CID_USER_SI476X_BASE + 5), 34 + V4L2_CID_SI476X_INTERCHIP_LINK = (V4L2_CID_USER_SI476X_BASE + 6), 35 + }; 36 + 37 + #endif /* SI476X_H*/