aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/radio
diff options
context:
space:
mode:
authorAndrey Smirnov <andrew.smirnov@gmail.com>2013-04-18 19:46:08 -0400
committerMauro Carvalho Chehab <mchehab@redhat.com>2013-04-18 20:20:34 -0400
commitb879a9c2a755d4ddf9e685258de6435710fd2f03 (patch)
tree8d4bd6c9df347b7d92dfd2523cb2987e78868108 /drivers/media/radio
parent6695be6863b75620ffa6d422965680ce785cb7c8 (diff)
[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>
Diffstat (limited to 'drivers/media/radio')
-rw-r--r--drivers/media/radio/Kconfig16
-rw-r--r--drivers/media/radio/Makefile1
-rw-r--r--drivers/media/radio/radio-si476x.c1599
3 files changed, 1616 insertions, 0 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
index 24e64a09884c..c0beee2fa37c 100644
--- a/drivers/media/radio/Kconfig
+++ b/drivers/media/radio/Kconfig
@@ -18,6 +18,22 @@ config RADIO_SI470X
18 18
19source "drivers/media/radio/si470x/Kconfig" 19source "drivers/media/radio/si470x/Kconfig"
20 20
21config 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
21config USB_MR800 37config USB_MR800
22 tristate "AverMedia MR 800 USB FM radio support" 38 tristate "AverMedia MR 800 USB FM radio support"
23 depends on USB && VIDEO_V4L2 39 depends on USB && VIDEO_V4L2
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
index 303eaebdb85a..0dcdb320cfc7 100644
--- a/drivers/media/radio/Makefile
+++ b/drivers/media/radio/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o
19obj-$(CONFIG_RADIO_TRUST) += radio-trust.o 19obj-$(CONFIG_RADIO_TRUST) += radio-trust.o
20obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o 20obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o
21obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o 21obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o
22obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o
22obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o 23obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o
23obj-$(CONFIG_USB_DSBR) += dsbr100.o 24obj-$(CONFIG_USB_DSBR) += dsbr100.o
24obj-$(CONFIG_RADIO_SI470X) += si470x/ 25obj-$(CONFIG_RADIO_SI470X) += si470x/
diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c
new file mode 100644
index 000000000000..9430c6a29937
--- /dev/null
+++ b/drivers/media/radio/radio-si476x.c
@@ -0,0 +1,1599 @@
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
52enum si476x_freq_bands {
53 SI476X_BAND_FM,
54 SI476X_BAND_AM,
55};
56
57static 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
81static 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
87static 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
94static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl);
95static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl);
96
97enum 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
105static 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
113static inline enum phase_diversity_modes_idx
114si476x_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
131static inline enum si476x_phase_diversity_mode
132si476x_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
145static 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
151enum 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};
159static 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
259struct 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 */
279struct 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 */
306struct 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
319static inline struct si476x_radio *
320v4l2_dev_to_radio(struct v4l2_device *d)
321{
322 return container_of(d, struct si476x_radio, v4l2dev);
323}
324
325static inline struct si476x_radio *
326v4l2_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 */
334static 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
360static 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
397static 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
474static 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
491static 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
530static 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}
562static 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
621static 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
666static 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
701static 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
748unlock:
749 si476x_core_unlock(radio->core);
750 return err;
751}
752
753static 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);
839unlock:
840 si476x_core_unlock(radio->core);
841
842
843
844 return err;
845}
846
847static 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
881static 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
1021static 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
1032static 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}
1048static 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
1065static 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
1098power_down:
1099 si476x_core_set_power_state(radio->core,
1100 SI476X_POWER_DOWN);
1101done:
1102 si476x_core_unlock(radio->core);
1103 v4l2_fh_release(file);
1104
1105 return err;
1106}
1107
1108static 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
1123static 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
1162static 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
1183static 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
1193static 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
1214static 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
1222static 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
1244static const struct file_operations radio_acf_fops = {
1245 .open = simple_open,
1246 .llseek = default_llseek,
1247 .read = si476x_radio_read_acf_blob,
1248};
1249
1250static 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
1273static 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
1279static 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
1301static const struct file_operations radio_agc_fops = {
1302 .open = simple_open,
1303 .llseek = default_llseek,
1304 .read = si476x_radio_read_agc_blob,
1305};
1306
1307static 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
1336static const struct file_operations radio_rsq_fops = {
1337 .open = simple_open,
1338 .llseek = default_llseek,
1339 .read = si476x_radio_read_rsq_blob,
1340};
1341
1342static 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
1371static 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
1378static 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;
1428cleanup:
1429 debugfs_remove_recursive(radio->debugfs);
1430exit:
1431 return ret;
1432}
1433
1434
1435static 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
1453static 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;
1568exit:
1569 v4l2_ctrl_handler_free(radio->videodev.ctrl_handler);
1570 return rval;
1571}
1572
1573static 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
1585MODULE_ALIAS("platform:si476x-radio");
1586
1587static 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};
1595module_platform_driver(si476x_radio_driver);
1596
1597MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
1598MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell");
1599MODULE_LICENSE("GPL");