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

source code of linux/drivers/media/radio/radio-si476x.c