1 // SPDX-License-Identifier: GPL-2.0-only
3 * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips
5 * Copyright (C) 2012 Innovative Converged Devices(ICD)
6 * Copyright (C) 2013 Andrey Smirnov
8 * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
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>
25 #include <media/drv-intf/si476x.h>
26 #include <linux/mfd/si476x-core.h>
28 #define FM_FREQ_RANGE_LOW 64000000
29 #define FM_FREQ_RANGE_HIGH 108000000
31 #define AM_FREQ_RANGE_LOW 520000
32 #define AM_FREQ_RANGE_HIGH 30000000
34 #define PWRLINEFLTR (1 << 8)
36 #define FREQ_MUL (10000000 / 625)
38 #define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0x80 & (status))
40 #define DRIVER_NAME "si476x-radio"
41 #define DRIVER_CARD "SI476x AM/FM Receiver"
43 enum si476x_freq_bands
{
48 static const struct v4l2_frequency_band si476x_bands
[] = {
50 .type
= V4L2_TUNER_RADIO
,
51 .index
= SI476X_BAND_FM
,
52 .capability
= V4L2_TUNER_CAP_LOW
53 | V4L2_TUNER_CAP_STEREO
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
,
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
,
72 static inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq
, int band
)
74 return freq
>= si476x_bands
[band
].rangelow
&&
75 freq
<= si476x_bands
[band
].rangehigh
;
78 static inline bool si476x_radio_range_is_inside_of_the_band(u32 low
, u32 high
,
81 return low
>= si476x_bands
[band
].rangelow
&&
82 high
<= si476x_bands
[band
].rangehigh
;
85 static int si476x_radio_s_ctrl(struct v4l2_ctrl
*ctrl
);
86 static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl
*ctrl
);
88 enum 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
,
96 static 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",
104 static inline enum phase_diversity_modes_idx
105 si476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode
)
108 default: /* FALLTHROUGH */
109 case SI476X_PHDIV_DISABLED
:
110 return SI476X_IDX_PHDIV_DISABLED
;
111 case SI476X_PHDIV_PRIMARY_COMBINING
:
112 return SI476X_IDX_PHDIV_PRIMARY_COMBINING
;
113 case SI476X_PHDIV_PRIMARY_ANTENNA
:
114 return SI476X_IDX_PHDIV_PRIMARY_ANTENNA
;
115 case SI476X_PHDIV_SECONDARY_ANTENNA
:
116 return SI476X_IDX_PHDIV_SECONDARY_ANTENNA
;
117 case SI476X_PHDIV_SECONDARY_COMBINING
:
118 return SI476X_IDX_PHDIV_SECONDARY_COMBINING
;
122 static inline enum si476x_phase_diversity_mode
123 si476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx
)
125 static const int idx_to_value
[] = {
126 [SI476X_IDX_PHDIV_DISABLED
] = SI476X_PHDIV_DISABLED
,
127 [SI476X_IDX_PHDIV_PRIMARY_COMBINING
] = SI476X_PHDIV_PRIMARY_COMBINING
,
128 [SI476X_IDX_PHDIV_PRIMARY_ANTENNA
] = SI476X_PHDIV_PRIMARY_ANTENNA
,
129 [SI476X_IDX_PHDIV_SECONDARY_ANTENNA
] = SI476X_PHDIV_SECONDARY_ANTENNA
,
130 [SI476X_IDX_PHDIV_SECONDARY_COMBINING
] = SI476X_PHDIV_SECONDARY_COMBINING
,
133 return idx_to_value
[idx
];
136 static const struct v4l2_ctrl_ops si476x_ctrl_ops
= {
137 .g_volatile_ctrl
= si476x_radio_g_volatile_ctrl
,
138 .s_ctrl
= si476x_radio_s_ctrl
,
142 enum si476x_ctrl_idx
{
143 SI476X_IDX_RSSI_THRESHOLD
,
144 SI476X_IDX_SNR_THRESHOLD
,
145 SI476X_IDX_MAX_TUNE_ERROR
,
146 SI476X_IDX_HARMONICS_COUNT
,
147 SI476X_IDX_DIVERSITY_MODE
,
148 SI476X_IDX_INTERCHIP_LINK
,
150 static struct v4l2_ctrl_config si476x_ctrls
[] = {
153 * SI476X during its station seeking(or tuning) process uses several
154 * parameters to detrmine if "the station" is valid:
156 * - Signal's SNR(in dBuV) must be lower than
157 * #V4L2_CID_SI476X_SNR_THRESHOLD
158 * - Signal's RSSI(in dBuV) must be greater than
159 * #V4L2_CID_SI476X_RSSI_THRESHOLD
160 * - Signal's frequency deviation(in units of 2ppm) must not be
161 * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR
163 [SI476X_IDX_RSSI_THRESHOLD
] = {
164 .ops
= &si476x_ctrl_ops
,
165 .id
= V4L2_CID_SI476X_RSSI_THRESHOLD
,
166 .name
= "Valid RSSI Threshold",
167 .type
= V4L2_CTRL_TYPE_INTEGER
,
172 [SI476X_IDX_SNR_THRESHOLD
] = {
173 .ops
= &si476x_ctrl_ops
,
174 .id
= V4L2_CID_SI476X_SNR_THRESHOLD
,
175 .type
= V4L2_CTRL_TYPE_INTEGER
,
176 .name
= "Valid SNR Threshold",
181 [SI476X_IDX_MAX_TUNE_ERROR
] = {
182 .ops
= &si476x_ctrl_ops
,
183 .id
= V4L2_CID_SI476X_MAX_TUNE_ERROR
,
184 .type
= V4L2_CTRL_TYPE_INTEGER
,
185 .name
= "Max Tune Errors",
192 * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics
193 * built-in power-line noise supression filter is to reject
194 * during AM-mode operation.
196 [SI476X_IDX_HARMONICS_COUNT
] = {
197 .ops
= &si476x_ctrl_ops
,
198 .id
= V4L2_CID_SI476X_HARMONICS_COUNT
,
199 .type
= V4L2_CTRL_TYPE_INTEGER
,
201 .name
= "Count of Harmonics to Reject",
208 * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which
209 * two tuners working in diversity mode are to work in.
211 * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled
212 * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is
213 * on, primary tuner's antenna is the main one.
214 * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is
215 * off, primary tuner's antenna is the main one.
216 * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is
217 * off, secondary tuner's antenna is the main one.
218 * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is
219 * on, secondary tuner's antenna is the main one.
221 [SI476X_IDX_DIVERSITY_MODE
] = {
222 .ops
= &si476x_ctrl_ops
,
223 .id
= V4L2_CID_SI476X_DIVERSITY_MODE
,
224 .type
= V4L2_CTRL_TYPE_MENU
,
225 .name
= "Phase Diversity Mode",
226 .qmenu
= phase_diversity_modes
,
228 .max
= ARRAY_SIZE(phase_diversity_modes
) - 1,
232 * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in
233 * diversity mode indicator. Allows user to determine if two
234 * chips working in diversity mode have established a link
235 * between each other and if the system as a whole uses
236 * signals from both antennas to receive FM radio.
238 [SI476X_IDX_INTERCHIP_LINK
] = {
239 .ops
= &si476x_ctrl_ops
,
240 .id
= V4L2_CID_SI476X_INTERCHIP_LINK
,
241 .type
= V4L2_CTRL_TYPE_BOOLEAN
,
242 .flags
= V4L2_CTRL_FLAG_READ_ONLY
| V4L2_CTRL_FLAG_VOLATILE
,
243 .name
= "Inter-Chip Link",
253 * struct si476x_radio_ops - vtable of tuner functions
255 * This table holds pointers to functions implementing particular
256 * operations depending on the mode in which the tuner chip was
257 * configured to start in. If the function is not supported
258 * corresponding element is set to #NULL.
260 * @tune_freq: Tune chip to a specific frequency
261 * @seek_start: Star station seeking
262 * @rsq_status: Get Received Signal Quality(RSQ) status
263 * @rds_blckcnt: Get received RDS blocks count
264 * @phase_diversity: Change phase diversity mode of the tuner
265 * @phase_div_status: Get phase diversity mode status
266 * @acf_status: Get the status of Automatically Controlled
268 * @agc_status: Get Automatic Gain Control(AGC) status
270 struct si476x_radio_ops
{
271 int (*tune_freq
)(struct si476x_core
*, struct si476x_tune_freq_args
*);
272 int (*seek_start
)(struct si476x_core
*, bool, bool);
273 int (*rsq_status
)(struct si476x_core
*, struct si476x_rsq_status_args
*,
274 struct si476x_rsq_status_report
*);
275 int (*rds_blckcnt
)(struct si476x_core
*, bool,
276 struct si476x_rds_blockcount_report
*);
278 int (*phase_diversity
)(struct si476x_core
*,
279 enum si476x_phase_diversity_mode
);
280 int (*phase_div_status
)(struct si476x_core
*);
281 int (*acf_status
)(struct si476x_core
*,
282 struct si476x_acf_status_report
*);
283 int (*agc_status
)(struct si476x_core
*,
284 struct si476x_agc_status_report
*);
288 * struct si476x_radio - radio device
290 * @v4l2dev: Pointer to V4L2 device created by V4L2 subsystem
291 * @videodev: Pointer to video device created by V4L2 subsystem
292 * @ctrl_handler: V4L2 controls handler
293 * @core: Pointer to underlying core device
294 * @ops: Vtable of functions. See struct si476x_radio_ops for details
295 * @debugfs: pointer to &strucd dentry for debugfs
296 * @audmode: audio mode, as defined for the rxsubchans field
299 * core structure is the radio device is being used
301 struct si476x_radio
{
302 struct v4l2_device v4l2dev
;
303 struct video_device videodev
;
304 struct v4l2_ctrl_handler ctrl_handler
;
306 struct si476x_core
*core
;
307 /* This field should not be accesses unless core lock is held */
308 const struct si476x_radio_ops
*ops
;
310 struct dentry
*debugfs
;
314 static inline struct si476x_radio
*
315 v4l2_dev_to_radio(struct v4l2_device
*d
)
317 return container_of(d
, struct si476x_radio
, v4l2dev
);
320 static inline struct si476x_radio
*
321 v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler
*d
)
323 return container_of(d
, struct si476x_radio
, ctrl_handler
);
327 * si476x_vidioc_querycap - query device capabilities
329 static int si476x_radio_querycap(struct file
*file
, void *priv
,
330 struct v4l2_capability
*capability
)
332 struct si476x_radio
*radio
= video_drvdata(file
);
334 strscpy(capability
->driver
, radio
->v4l2dev
.name
,
335 sizeof(capability
->driver
));
336 strscpy(capability
->card
, DRIVER_CARD
, sizeof(capability
->card
));
337 snprintf(capability
->bus_info
, sizeof(capability
->bus_info
),
338 "platform:%s", radio
->v4l2dev
.name
);
342 static int si476x_radio_enum_freq_bands(struct file
*file
, void *priv
,
343 struct v4l2_frequency_band
*band
)
346 struct si476x_radio
*radio
= video_drvdata(file
);
348 if (band
->tuner
!= 0)
351 switch (radio
->core
->chip_id
) {
352 /* AM/FM tuners -- all bands are supported */
353 case SI476X_CHIP_SI4761
:
354 case SI476X_CHIP_SI4764
:
355 if (band
->index
< ARRAY_SIZE(si476x_bands
)) {
356 *band
= si476x_bands
[band
->index
];
362 /* FM companion tuner chips -- only FM bands are
364 case SI476X_CHIP_SI4768
:
365 if (band
->index
== SI476X_BAND_FM
) {
366 *band
= si476x_bands
[band
->index
];
379 static int si476x_radio_g_tuner(struct file
*file
, void *priv
,
380 struct v4l2_tuner
*tuner
)
383 struct si476x_rsq_status_report report
;
384 struct si476x_radio
*radio
= video_drvdata(file
);
386 struct si476x_rsq_status_args args
= {
394 if (tuner
->index
!= 0)
397 tuner
->type
= V4L2_TUNER_RADIO
;
398 tuner
->capability
= V4L2_TUNER_CAP_LOW
/* Measure frequencies
401 | V4L2_TUNER_CAP_STEREO
402 | V4L2_TUNER_CAP_HWSEEK_BOUNDED
403 | V4L2_TUNER_CAP_HWSEEK_WRAP
404 | V4L2_TUNER_CAP_HWSEEK_PROG_LIM
;
406 si476x_core_lock(radio
->core
);
408 if (si476x_core_is_a_secondary_tuner(radio
->core
)) {
409 strscpy(tuner
->name
, "FM (secondary)", sizeof(tuner
->name
));
410 tuner
->rxsubchans
= 0;
411 tuner
->rangelow
= si476x_bands
[SI476X_BAND_FM
].rangelow
;
412 } else if (si476x_core_has_am(radio
->core
)) {
413 if (si476x_core_is_a_primary_tuner(radio
->core
))
414 strscpy(tuner
->name
, "AM/FM (primary)",
415 sizeof(tuner
->name
));
417 strscpy(tuner
->name
, "AM/FM", sizeof(tuner
->name
));
419 tuner
->rxsubchans
= V4L2_TUNER_SUB_MONO
| V4L2_TUNER_SUB_STEREO
420 | V4L2_TUNER_SUB_RDS
;
421 tuner
->capability
|= V4L2_TUNER_CAP_RDS
422 | V4L2_TUNER_CAP_RDS_BLOCK_IO
423 | V4L2_TUNER_CAP_FREQ_BANDS
;
425 tuner
->rangelow
= si476x_bands
[SI476X_BAND_AM
].rangelow
;
427 strscpy(tuner
->name
, "FM", sizeof(tuner
->name
));
428 tuner
->rxsubchans
= V4L2_TUNER_SUB_RDS
;
429 tuner
->capability
|= V4L2_TUNER_CAP_RDS
430 | V4L2_TUNER_CAP_RDS_BLOCK_IO
431 | V4L2_TUNER_CAP_FREQ_BANDS
;
432 tuner
->rangelow
= si476x_bands
[SI476X_BAND_FM
].rangelow
;
435 tuner
->audmode
= radio
->audmode
;
438 tuner
->rangehigh
= si476x_bands
[SI476X_BAND_FM
].rangehigh
;
440 err
= radio
->ops
->rsq_status(radio
->core
,
446 * tuner->signal value range: 0x0000 .. 0xFFFF,
447 * report.rssi: -128 .. 127
449 tuner
->signal
= (report
.rssi
+ 128) * 257;
451 si476x_core_unlock(radio
->core
);
456 static int si476x_radio_s_tuner(struct file
*file
, void *priv
,
457 const struct v4l2_tuner
*tuner
)
459 struct si476x_radio
*radio
= video_drvdata(file
);
461 if (tuner
->index
!= 0)
464 if (tuner
->audmode
== V4L2_TUNER_MODE_MONO
||
465 tuner
->audmode
== V4L2_TUNER_MODE_STEREO
)
466 radio
->audmode
= tuner
->audmode
;
468 radio
->audmode
= V4L2_TUNER_MODE_STEREO
;
473 static int si476x_radio_init_vtable(struct si476x_radio
*radio
,
474 enum si476x_func func
)
476 static const struct si476x_radio_ops fm_ops
= {
477 .tune_freq
= si476x_core_cmd_fm_tune_freq
,
478 .seek_start
= si476x_core_cmd_fm_seek_start
,
479 .rsq_status
= si476x_core_cmd_fm_rsq_status
,
480 .rds_blckcnt
= si476x_core_cmd_fm_rds_blockcount
,
481 .phase_diversity
= si476x_core_cmd_fm_phase_diversity
,
482 .phase_div_status
= si476x_core_cmd_fm_phase_div_status
,
483 .acf_status
= si476x_core_cmd_fm_acf_status
,
484 .agc_status
= si476x_core_cmd_agc_status
,
487 static const struct si476x_radio_ops am_ops
= {
488 .tune_freq
= si476x_core_cmd_am_tune_freq
,
489 .seek_start
= si476x_core_cmd_am_seek_start
,
490 .rsq_status
= si476x_core_cmd_am_rsq_status
,
492 .phase_diversity
= NULL
,
493 .phase_div_status
= NULL
,
494 .acf_status
= si476x_core_cmd_am_acf_status
,
499 case SI476X_FUNC_FM_RECEIVER
:
500 radio
->ops
= &fm_ops
;
503 case SI476X_FUNC_AM_RECEIVER
:
504 radio
->ops
= &am_ops
;
507 WARN(1, "Unexpected tuner function value\n");
512 static int si476x_radio_pretune(struct si476x_radio
*radio
,
513 enum si476x_func func
)
517 struct si476x_tune_freq_args args
= {
520 .injside
= SI476X_INJSIDE_AUTO
,
521 .tunemode
= SI476X_TM_VALIDATED_NORMAL_TUNE
,
522 .smoothmetrics
= SI476X_SM_INITIALIZE_AUDIO
,
527 case SI476X_FUNC_FM_RECEIVER
:
528 args
.freq
= v4l2_to_si476x(radio
->core
,
530 retval
= radio
->ops
->tune_freq(radio
->core
, &args
);
532 case SI476X_FUNC_AM_RECEIVER
:
533 args
.freq
= v4l2_to_si476x(radio
->core
,
535 retval
= radio
->ops
->tune_freq(radio
->core
, &args
);
538 WARN(1, "Unexpected tuner function value\n");
544 static int si476x_radio_do_post_powerup_init(struct si476x_radio
*radio
,
545 enum si476x_func func
)
549 /* regcache_mark_dirty(radio->core->regmap); */
550 err
= regcache_sync_region(radio
->core
->regmap
,
551 SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE
,
552 SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT
);
556 err
= regcache_sync_region(radio
->core
->regmap
,
557 SI476X_PROP_AUDIO_DEEMPHASIS
,
558 SI476X_PROP_AUDIO_PWR_LINE_FILTER
);
562 err
= regcache_sync_region(radio
->core
->regmap
,
563 SI476X_PROP_INT_CTL_ENABLE
,
564 SI476X_PROP_INT_CTL_ENABLE
);
569 * Is there any point in restoring SNR and the like
570 * when switching between AM/FM?
572 err
= regcache_sync_region(radio
->core
->regmap
,
573 SI476X_PROP_VALID_MAX_TUNE_ERROR
,
574 SI476X_PROP_VALID_MAX_TUNE_ERROR
);
578 err
= regcache_sync_region(radio
->core
->regmap
,
579 SI476X_PROP_VALID_SNR_THRESHOLD
,
580 SI476X_PROP_VALID_RSSI_THRESHOLD
);
584 if (func
== SI476X_FUNC_FM_RECEIVER
) {
585 if (si476x_core_has_diversity(radio
->core
)) {
586 err
= si476x_core_cmd_fm_phase_diversity(radio
->core
,
587 radio
->core
->diversity_mode
);
592 err
= regcache_sync_region(radio
->core
->regmap
,
593 SI476X_PROP_FM_RDS_INTERRUPT_SOURCE
,
594 SI476X_PROP_FM_RDS_CONFIG
);
599 return si476x_radio_init_vtable(radio
, func
);
603 static int si476x_radio_change_func(struct si476x_radio
*radio
,
604 enum si476x_func func
)
609 * Since power/up down is a very time consuming operation,
610 * try to avoid doing it if the requested mode matches the one
613 if (func
== radio
->core
->power_up_parameters
.func
)
617 err
= si476x_core_stop(radio
->core
, soft
);
620 * OK, if the chip does not want to play nice let's
621 * try to reset it in more brutal way
624 err
= si476x_core_stop(radio
->core
, soft
);
629 Set the desired radio tuner function
631 radio
->core
->power_up_parameters
.func
= func
;
633 err
= si476x_core_start(radio
->core
, soft
);
638 * No need to do the rest of manipulations for the bootlader
641 if (func
!= SI476X_FUNC_FM_RECEIVER
&&
642 func
!= SI476X_FUNC_AM_RECEIVER
)
645 return si476x_radio_do_post_powerup_init(radio
, func
);
648 static int si476x_radio_g_frequency(struct file
*file
, void *priv
,
649 struct v4l2_frequency
*f
)
652 struct si476x_radio
*radio
= video_drvdata(file
);
655 f
->type
!= V4L2_TUNER_RADIO
)
658 si476x_core_lock(radio
->core
);
660 if (radio
->ops
->rsq_status
) {
661 struct si476x_rsq_status_report report
;
662 struct si476x_rsq_status_args args
= {
670 err
= radio
->ops
->rsq_status(radio
->core
, &args
, &report
);
672 f
->frequency
= si476x_to_v4l2(radio
->core
,
678 si476x_core_unlock(radio
->core
);
683 static int si476x_radio_s_frequency(struct file
*file
, void *priv
,
684 const struct v4l2_frequency
*f
)
687 u32 freq
= f
->frequency
;
688 struct si476x_tune_freq_args args
;
689 struct si476x_radio
*radio
= video_drvdata(file
);
691 const u32 midrange
= (si476x_bands
[SI476X_BAND_AM
].rangehigh
+
692 si476x_bands
[SI476X_BAND_FM
].rangelow
) / 2;
693 const int band
= (freq
> midrange
) ?
694 SI476X_BAND_FM
: SI476X_BAND_AM
;
695 const enum si476x_func func
= (band
== SI476X_BAND_AM
) ?
696 SI476X_FUNC_AM_RECEIVER
: SI476X_FUNC_FM_RECEIVER
;
699 f
->type
!= V4L2_TUNER_RADIO
)
702 si476x_core_lock(radio
->core
);
705 si476x_bands
[band
].rangelow
,
706 si476x_bands
[band
].rangehigh
);
708 if (si476x_radio_freq_is_inside_of_the_band(freq
,
710 (!si476x_core_has_am(radio
->core
) ||
711 si476x_core_is_a_secondary_tuner(radio
->core
))) {
716 err
= si476x_radio_change_func(radio
, func
);
722 args
.injside
= SI476X_INJSIDE_AUTO
;
723 args
.freq
= v4l2_to_si476x(radio
->core
, freq
);
724 args
.tunemode
= SI476X_TM_VALIDATED_NORMAL_TUNE
;
725 args
.smoothmetrics
= SI476X_SM_INITIALIZE_AUDIO
;
728 err
= radio
->ops
->tune_freq(radio
->core
, &args
);
731 si476x_core_unlock(radio
->core
);
735 static int si476x_radio_s_hw_freq_seek(struct file
*file
, void *priv
,
736 const struct v4l2_hw_freq_seek
*seek
)
739 enum si476x_func func
;
740 u32 rangelow
= seek
->rangelow
, rangehigh
= seek
->rangehigh
;
741 struct si476x_radio
*radio
= video_drvdata(file
);
743 if (file
->f_flags
& O_NONBLOCK
)
746 if (seek
->tuner
!= 0 ||
747 seek
->type
!= V4L2_TUNER_RADIO
)
750 si476x_core_lock(radio
->core
);
753 err
= regmap_read(radio
->core
->regmap
,
754 SI476X_PROP_SEEK_BAND_BOTTOM
,
758 rangelow
= si476x_to_v4l2(radio
->core
, rangelow
);
761 err
= regmap_read(radio
->core
->regmap
,
762 SI476X_PROP_SEEK_BAND_TOP
,
766 rangehigh
= si476x_to_v4l2(radio
->core
, rangehigh
);
769 if (rangelow
> rangehigh
) {
774 if (si476x_radio_range_is_inside_of_the_band(rangelow
, rangehigh
,
776 func
= SI476X_FUNC_FM_RECEIVER
;
778 } else if (si476x_core_has_am(radio
->core
) &&
779 si476x_radio_range_is_inside_of_the_band(rangelow
, rangehigh
,
781 func
= SI476X_FUNC_AM_RECEIVER
;
787 err
= si476x_radio_change_func(radio
, func
);
791 if (seek
->rangehigh
) {
792 err
= regmap_write(radio
->core
->regmap
,
793 SI476X_PROP_SEEK_BAND_TOP
,
794 v4l2_to_si476x(radio
->core
,
799 if (seek
->rangelow
) {
800 err
= regmap_write(radio
->core
->regmap
,
801 SI476X_PROP_SEEK_BAND_BOTTOM
,
802 v4l2_to_si476x(radio
->core
,
808 err
= regmap_write(radio
->core
->regmap
,
809 SI476X_PROP_SEEK_FREQUENCY_SPACING
,
810 v4l2_to_si476x(radio
->core
,
816 err
= radio
->ops
->seek_start(radio
->core
,
820 si476x_core_unlock(radio
->core
);
827 static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl
*ctrl
)
830 struct si476x_radio
*radio
= v4l2_ctrl_handler_to_radio(ctrl
->handler
);
832 si476x_core_lock(radio
->core
);
835 case V4L2_CID_SI476X_INTERCHIP_LINK
:
836 if (si476x_core_has_diversity(radio
->core
)) {
837 if (radio
->ops
->phase_diversity
) {
838 retval
= radio
->ops
->phase_div_status(radio
->core
);
842 ctrl
->val
= !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval
);
856 si476x_core_unlock(radio
->core
);
861 static int si476x_radio_s_ctrl(struct v4l2_ctrl
*ctrl
)
864 enum si476x_phase_diversity_mode mode
;
865 struct si476x_radio
*radio
= v4l2_ctrl_handler_to_radio(ctrl
->handler
);
867 si476x_core_lock(radio
->core
);
870 case V4L2_CID_SI476X_HARMONICS_COUNT
:
871 retval
= regmap_update_bits(radio
->core
->regmap
,
872 SI476X_PROP_AUDIO_PWR_LINE_FILTER
,
873 SI476X_PROP_PWR_HARMONICS_MASK
,
876 case V4L2_CID_POWER_LINE_FREQUENCY
:
878 case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED
:
879 retval
= regmap_update_bits(radio
->core
->regmap
,
880 SI476X_PROP_AUDIO_PWR_LINE_FILTER
,
881 SI476X_PROP_PWR_ENABLE_MASK
,
884 case V4L2_CID_POWER_LINE_FREQUENCY_50HZ
:
885 retval
= regmap_update_bits(radio
->core
->regmap
,
886 SI476X_PROP_AUDIO_PWR_LINE_FILTER
,
887 SI476X_PROP_PWR_GRID_MASK
,
888 SI476X_PROP_PWR_GRID_50HZ
);
890 case V4L2_CID_POWER_LINE_FREQUENCY_60HZ
:
891 retval
= regmap_update_bits(radio
->core
->regmap
,
892 SI476X_PROP_AUDIO_PWR_LINE_FILTER
,
893 SI476X_PROP_PWR_GRID_MASK
,
894 SI476X_PROP_PWR_GRID_60HZ
);
901 case V4L2_CID_SI476X_RSSI_THRESHOLD
:
902 retval
= regmap_write(radio
->core
->regmap
,
903 SI476X_PROP_VALID_RSSI_THRESHOLD
,
906 case V4L2_CID_SI476X_SNR_THRESHOLD
:
907 retval
= regmap_write(radio
->core
->regmap
,
908 SI476X_PROP_VALID_SNR_THRESHOLD
,
911 case V4L2_CID_SI476X_MAX_TUNE_ERROR
:
912 retval
= regmap_write(radio
->core
->regmap
,
913 SI476X_PROP_VALID_MAX_TUNE_ERROR
,
916 case V4L2_CID_RDS_RECEPTION
:
918 * It looks like RDS related properties are
919 * inaccesable when tuner is in AM mode, so cache the
922 if (si476x_core_is_in_am_receiver_mode(radio
->core
))
923 regcache_cache_only(radio
->core
->regmap
, true);
926 retval
= regmap_write(radio
->core
->regmap
,
927 SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT
,
928 radio
->core
->rds_fifo_depth
);
932 if (radio
->core
->client
->irq
) {
933 retval
= regmap_write(radio
->core
->regmap
,
934 SI476X_PROP_FM_RDS_INTERRUPT_SOURCE
,
940 /* Drain RDS FIFO before enabling RDS processing */
941 retval
= si476x_core_cmd_fm_rds_status(radio
->core
,
949 retval
= regmap_update_bits(radio
->core
->regmap
,
950 SI476X_PROP_FM_RDS_CONFIG
,
951 SI476X_PROP_RDSEN_MASK
,
954 retval
= regmap_update_bits(radio
->core
->regmap
,
955 SI476X_PROP_FM_RDS_CONFIG
,
956 SI476X_PROP_RDSEN_MASK
,
960 if (si476x_core_is_in_am_receiver_mode(radio
->core
))
961 regcache_cache_only(radio
->core
->regmap
, false);
963 case V4L2_CID_TUNE_DEEMPHASIS
:
964 retval
= regmap_write(radio
->core
->regmap
,
965 SI476X_PROP_AUDIO_DEEMPHASIS
,
969 case V4L2_CID_SI476X_DIVERSITY_MODE
:
970 mode
= si476x_phase_diversity_idx_to_mode(ctrl
->val
);
972 if (mode
== radio
->core
->diversity_mode
) {
977 if (si476x_core_is_in_am_receiver_mode(radio
->core
)) {
979 * Diversity cannot be configured while tuner
980 * is in AM mode so save the changes and carry on.
982 radio
->core
->diversity_mode
= mode
;
985 retval
= radio
->ops
->phase_diversity(radio
->core
, mode
);
987 radio
->core
->diversity_mode
= mode
;
996 si476x_core_unlock(radio
->core
);
1001 #ifdef CONFIG_VIDEO_ADV_DEBUG
1002 static int si476x_radio_g_register(struct file
*file
, void *fh
,
1003 struct v4l2_dbg_register
*reg
)
1007 struct si476x_radio
*radio
= video_drvdata(file
);
1009 si476x_core_lock(radio
->core
);
1011 err
= regmap_read(radio
->core
->regmap
,
1012 (unsigned int)reg
->reg
, &value
);
1014 si476x_core_unlock(radio
->core
);
1018 static int si476x_radio_s_register(struct file
*file
, void *fh
,
1019 const struct v4l2_dbg_register
*reg
)
1023 struct si476x_radio
*radio
= video_drvdata(file
);
1025 si476x_core_lock(radio
->core
);
1026 err
= regmap_write(radio
->core
->regmap
,
1027 (unsigned int)reg
->reg
,
1028 (unsigned int)reg
->val
);
1029 si476x_core_unlock(radio
->core
);
1035 static int si476x_radio_fops_open(struct file
*file
)
1037 struct si476x_radio
*radio
= video_drvdata(file
);
1040 err
= v4l2_fh_open(file
);
1044 if (v4l2_fh_is_singular_file(file
)) {
1045 si476x_core_lock(radio
->core
);
1046 err
= si476x_core_set_power_state(radio
->core
,
1047 SI476X_POWER_UP_FULL
);
1051 err
= si476x_radio_do_post_powerup_init(radio
,
1052 radio
->core
->power_up_parameters
.func
);
1056 err
= si476x_radio_pretune(radio
,
1057 radio
->core
->power_up_parameters
.func
);
1061 si476x_core_unlock(radio
->core
);
1062 /*Must be done after si476x_core_unlock to prevent a deadlock*/
1063 v4l2_ctrl_handler_setup(&radio
->ctrl_handler
);
1069 si476x_core_set_power_state(radio
->core
,
1072 si476x_core_unlock(radio
->core
);
1073 v4l2_fh_release(file
);
1078 static int si476x_radio_fops_release(struct file
*file
)
1081 struct si476x_radio
*radio
= video_drvdata(file
);
1083 if (v4l2_fh_is_singular_file(file
) &&
1084 atomic_read(&radio
->core
->is_alive
))
1085 si476x_core_set_power_state(radio
->core
,
1088 err
= v4l2_fh_release(file
);
1093 static ssize_t
si476x_radio_fops_read(struct file
*file
, char __user
*buf
,
1094 size_t count
, loff_t
*ppos
)
1098 unsigned int copied
;
1100 struct si476x_radio
*radio
= video_drvdata(file
);
1102 /* block if no new data available */
1103 if (kfifo_is_empty(&radio
->core
->rds_fifo
)) {
1104 if (file
->f_flags
& O_NONBLOCK
)
1105 return -EWOULDBLOCK
;
1107 rval
= wait_event_interruptible(radio
->core
->rds_read_queue
,
1108 (!kfifo_is_empty(&radio
->core
->rds_fifo
) ||
1109 !atomic_read(&radio
->core
->is_alive
)));
1113 if (!atomic_read(&radio
->core
->is_alive
))
1117 fifo_len
= kfifo_len(&radio
->core
->rds_fifo
);
1119 if (kfifo_to_user(&radio
->core
->rds_fifo
, buf
,
1120 min(fifo_len
, count
),
1122 dev_warn(&radio
->videodev
.dev
,
1123 "Error during FIFO to userspace copy\n");
1126 rval
= (ssize_t
)copied
;
1132 static __poll_t
si476x_radio_fops_poll(struct file
*file
,
1133 struct poll_table_struct
*pts
)
1135 struct si476x_radio
*radio
= video_drvdata(file
);
1136 __poll_t req_events
= poll_requested_events(pts
);
1137 __poll_t err
= v4l2_ctrl_poll(file
, pts
);
1139 if (req_events
& (EPOLLIN
| EPOLLRDNORM
)) {
1140 if (atomic_read(&radio
->core
->is_alive
))
1141 poll_wait(file
, &radio
->core
->rds_read_queue
, pts
);
1143 if (!atomic_read(&radio
->core
->is_alive
))
1146 if (!kfifo_is_empty(&radio
->core
->rds_fifo
))
1147 err
= EPOLLIN
| EPOLLRDNORM
;
1153 static const struct v4l2_file_operations si476x_fops
= {
1154 .owner
= THIS_MODULE
,
1155 .read
= si476x_radio_fops_read
,
1156 .poll
= si476x_radio_fops_poll
,
1157 .unlocked_ioctl
= video_ioctl2
,
1158 .open
= si476x_radio_fops_open
,
1159 .release
= si476x_radio_fops_release
,
1163 static const struct v4l2_ioctl_ops si4761_ioctl_ops
= {
1164 .vidioc_querycap
= si476x_radio_querycap
,
1165 .vidioc_g_tuner
= si476x_radio_g_tuner
,
1166 .vidioc_s_tuner
= si476x_radio_s_tuner
,
1168 .vidioc_g_frequency
= si476x_radio_g_frequency
,
1169 .vidioc_s_frequency
= si476x_radio_s_frequency
,
1170 .vidioc_s_hw_freq_seek
= si476x_radio_s_hw_freq_seek
,
1171 .vidioc_enum_freq_bands
= si476x_radio_enum_freq_bands
,
1173 .vidioc_subscribe_event
= v4l2_ctrl_subscribe_event
,
1174 .vidioc_unsubscribe_event
= v4l2_event_unsubscribe
,
1176 #ifdef CONFIG_VIDEO_ADV_DEBUG
1177 .vidioc_g_register
= si476x_radio_g_register
,
1178 .vidioc_s_register
= si476x_radio_s_register
,
1183 static const struct video_device si476x_viddev_template
= {
1184 .fops
= &si476x_fops
,
1185 .name
= DRIVER_NAME
,
1186 .release
= video_device_release_empty
,
1191 static ssize_t
si476x_radio_read_acf_blob(struct file
*file
,
1192 char __user
*user_buf
,
1193 size_t count
, loff_t
*ppos
)
1196 struct si476x_radio
*radio
= file
->private_data
;
1197 struct si476x_acf_status_report report
;
1199 si476x_core_lock(radio
->core
);
1200 if (radio
->ops
->acf_status
)
1201 err
= radio
->ops
->acf_status(radio
->core
, &report
);
1204 si476x_core_unlock(radio
->core
);
1209 return simple_read_from_buffer(user_buf
, count
, ppos
, &report
,
1213 static const struct file_operations radio_acf_fops
= {
1214 .open
= simple_open
,
1215 .llseek
= default_llseek
,
1216 .read
= si476x_radio_read_acf_blob
,
1219 static ssize_t
si476x_radio_read_rds_blckcnt_blob(struct file
*file
,
1220 char __user
*user_buf
,
1221 size_t count
, loff_t
*ppos
)
1224 struct si476x_radio
*radio
= file
->private_data
;
1225 struct si476x_rds_blockcount_report report
;
1227 si476x_core_lock(radio
->core
);
1228 if (radio
->ops
->rds_blckcnt
)
1229 err
= radio
->ops
->rds_blckcnt(radio
->core
, true,
1233 si476x_core_unlock(radio
->core
);
1238 return simple_read_from_buffer(user_buf
, count
, ppos
, &report
,
1242 static const struct file_operations radio_rds_blckcnt_fops
= {
1243 .open
= simple_open
,
1244 .llseek
= default_llseek
,
1245 .read
= si476x_radio_read_rds_blckcnt_blob
,
1248 static ssize_t
si476x_radio_read_agc_blob(struct file
*file
,
1249 char __user
*user_buf
,
1250 size_t count
, loff_t
*ppos
)
1253 struct si476x_radio
*radio
= file
->private_data
;
1254 struct si476x_agc_status_report report
;
1256 si476x_core_lock(radio
->core
);
1257 if (radio
->ops
->rds_blckcnt
)
1258 err
= radio
->ops
->agc_status(radio
->core
, &report
);
1261 si476x_core_unlock(radio
->core
);
1266 return simple_read_from_buffer(user_buf
, count
, ppos
, &report
,
1270 static const struct file_operations radio_agc_fops
= {
1271 .open
= simple_open
,
1272 .llseek
= default_llseek
,
1273 .read
= si476x_radio_read_agc_blob
,
1276 static ssize_t
si476x_radio_read_rsq_blob(struct file
*file
,
1277 char __user
*user_buf
,
1278 size_t count
, loff_t
*ppos
)
1281 struct si476x_radio
*radio
= file
->private_data
;
1282 struct si476x_rsq_status_report report
;
1283 struct si476x_rsq_status_args args
= {
1291 si476x_core_lock(radio
->core
);
1292 if (radio
->ops
->rds_blckcnt
)
1293 err
= radio
->ops
->rsq_status(radio
->core
, &args
, &report
);
1296 si476x_core_unlock(radio
->core
);
1301 return simple_read_from_buffer(user_buf
, count
, ppos
, &report
,
1305 static const struct file_operations radio_rsq_fops
= {
1306 .open
= simple_open
,
1307 .llseek
= default_llseek
,
1308 .read
= si476x_radio_read_rsq_blob
,
1311 static ssize_t
si476x_radio_read_rsq_primary_blob(struct file
*file
,
1312 char __user
*user_buf
,
1313 size_t count
, loff_t
*ppos
)
1316 struct si476x_radio
*radio
= file
->private_data
;
1317 struct si476x_rsq_status_report report
;
1318 struct si476x_rsq_status_args args
= {
1326 si476x_core_lock(radio
->core
);
1327 if (radio
->ops
->rds_blckcnt
)
1328 err
= radio
->ops
->rsq_status(radio
->core
, &args
, &report
);
1331 si476x_core_unlock(radio
->core
);
1336 return simple_read_from_buffer(user_buf
, count
, ppos
, &report
,
1340 static const struct file_operations radio_rsq_primary_fops
= {
1341 .open
= simple_open
,
1342 .llseek
= default_llseek
,
1343 .read
= si476x_radio_read_rsq_primary_blob
,
1347 static int si476x_radio_init_debugfs(struct si476x_radio
*radio
)
1349 struct dentry
*dentry
;
1352 dentry
= debugfs_create_dir(dev_name(radio
->v4l2dev
.dev
), NULL
);
1353 if (IS_ERR(dentry
)) {
1354 ret
= PTR_ERR(dentry
);
1357 radio
->debugfs
= dentry
;
1359 dentry
= debugfs_create_file("acf", S_IRUGO
,
1360 radio
->debugfs
, radio
, &radio_acf_fops
);
1361 if (IS_ERR(dentry
)) {
1362 ret
= PTR_ERR(dentry
);
1366 dentry
= debugfs_create_file("rds_blckcnt", S_IRUGO
,
1367 radio
->debugfs
, radio
,
1368 &radio_rds_blckcnt_fops
);
1369 if (IS_ERR(dentry
)) {
1370 ret
= PTR_ERR(dentry
);
1374 dentry
= debugfs_create_file("agc", S_IRUGO
,
1375 radio
->debugfs
, radio
, &radio_agc_fops
);
1376 if (IS_ERR(dentry
)) {
1377 ret
= PTR_ERR(dentry
);
1381 dentry
= debugfs_create_file("rsq", S_IRUGO
,
1382 radio
->debugfs
, radio
, &radio_rsq_fops
);
1383 if (IS_ERR(dentry
)) {
1384 ret
= PTR_ERR(dentry
);
1388 dentry
= debugfs_create_file("rsq_primary", S_IRUGO
,
1389 radio
->debugfs
, radio
,
1390 &radio_rsq_primary_fops
);
1391 if (IS_ERR(dentry
)) {
1392 ret
= PTR_ERR(dentry
);
1398 debugfs_remove_recursive(radio
->debugfs
);
1404 static int si476x_radio_add_new_custom(struct si476x_radio
*radio
,
1405 enum si476x_ctrl_idx idx
)
1408 struct v4l2_ctrl
*ctrl
;
1410 ctrl
= v4l2_ctrl_new_custom(&radio
->ctrl_handler
,
1413 rval
= radio
->ctrl_handler
.error
;
1414 if (ctrl
== NULL
&& rval
)
1415 dev_err(radio
->v4l2dev
.dev
,
1416 "Could not initialize '%s' control %d\n",
1417 si476x_ctrls
[idx
].name
, rval
);
1422 static int si476x_radio_probe(struct platform_device
*pdev
)
1425 struct si476x_radio
*radio
;
1426 struct v4l2_ctrl
*ctrl
;
1428 static atomic_t instance
= ATOMIC_INIT(0);
1430 radio
= devm_kzalloc(&pdev
->dev
, sizeof(*radio
), GFP_KERNEL
);
1434 radio
->core
= i2c_mfd_cell_to_core(&pdev
->dev
);
1436 v4l2_device_set_name(&radio
->v4l2dev
, DRIVER_NAME
, &instance
);
1438 rval
= v4l2_device_register(&pdev
->dev
, &radio
->v4l2dev
);
1440 dev_err(&pdev
->dev
, "Cannot register v4l2_device.\n");
1444 memcpy(&radio
->videodev
, &si476x_viddev_template
,
1445 sizeof(struct video_device
));
1447 radio
->videodev
.v4l2_dev
= &radio
->v4l2dev
;
1448 radio
->videodev
.ioctl_ops
= &si4761_ioctl_ops
;
1449 radio
->videodev
.device_caps
= V4L2_CAP_TUNER
| V4L2_CAP_RADIO
|
1450 V4L2_CAP_HW_FREQ_SEEK
;
1452 si476x_core_lock(radio
->core
);
1453 if (!si476x_core_is_a_secondary_tuner(radio
->core
))
1454 radio
->videodev
.device_caps
|= V4L2_CAP_RDS_CAPTURE
|
1456 si476x_core_unlock(radio
->core
);
1458 video_set_drvdata(&radio
->videodev
, radio
);
1459 platform_set_drvdata(pdev
, radio
);
1462 radio
->v4l2dev
.ctrl_handler
= &radio
->ctrl_handler
;
1463 v4l2_ctrl_handler_init(&radio
->ctrl_handler
,
1464 1 + ARRAY_SIZE(si476x_ctrls
));
1466 if (si476x_core_has_am(radio
->core
)) {
1467 ctrl
= v4l2_ctrl_new_std_menu(&radio
->ctrl_handler
,
1469 V4L2_CID_POWER_LINE_FREQUENCY
,
1470 V4L2_CID_POWER_LINE_FREQUENCY_60HZ
,
1472 rval
= radio
->ctrl_handler
.error
;
1473 if (ctrl
== NULL
&& rval
) {
1474 dev_err(&pdev
->dev
, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n",
1479 rval
= si476x_radio_add_new_custom(radio
,
1480 SI476X_IDX_HARMONICS_COUNT
);
1485 rval
= si476x_radio_add_new_custom(radio
, SI476X_IDX_RSSI_THRESHOLD
);
1489 rval
= si476x_radio_add_new_custom(radio
, SI476X_IDX_SNR_THRESHOLD
);
1493 rval
= si476x_radio_add_new_custom(radio
, SI476X_IDX_MAX_TUNE_ERROR
);
1497 ctrl
= v4l2_ctrl_new_std_menu(&radio
->ctrl_handler
,
1499 V4L2_CID_TUNE_DEEMPHASIS
,
1500 V4L2_DEEMPHASIS_75_uS
, 0, 0);
1501 rval
= radio
->ctrl_handler
.error
;
1502 if (ctrl
== NULL
&& rval
) {
1503 dev_err(&pdev
->dev
, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n",
1508 ctrl
= v4l2_ctrl_new_std(&radio
->ctrl_handler
, &si476x_ctrl_ops
,
1509 V4L2_CID_RDS_RECEPTION
,
1511 rval
= radio
->ctrl_handler
.error
;
1512 if (ctrl
== NULL
&& rval
) {
1513 dev_err(&pdev
->dev
, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n",
1518 if (si476x_core_has_diversity(radio
->core
)) {
1519 si476x_ctrls
[SI476X_IDX_DIVERSITY_MODE
].def
=
1520 si476x_phase_diversity_mode_to_idx(radio
->core
->diversity_mode
);
1521 rval
= si476x_radio_add_new_custom(radio
, SI476X_IDX_DIVERSITY_MODE
);
1525 rval
= si476x_radio_add_new_custom(radio
, SI476X_IDX_INTERCHIP_LINK
);
1530 /* register video device */
1531 rval
= video_register_device(&radio
->videodev
, VFL_TYPE_RADIO
, -1);
1533 dev_err(&pdev
->dev
, "Could not register video device\n");
1537 rval
= si476x_radio_init_debugfs(radio
);
1539 dev_err(&pdev
->dev
, "Could not create debugfs interface\n");
1545 v4l2_ctrl_handler_free(radio
->videodev
.ctrl_handler
);
1549 static int si476x_radio_remove(struct platform_device
*pdev
)
1551 struct si476x_radio
*radio
= platform_get_drvdata(pdev
);
1553 v4l2_ctrl_handler_free(radio
->videodev
.ctrl_handler
);
1554 video_unregister_device(&radio
->videodev
);
1555 v4l2_device_unregister(&radio
->v4l2dev
);
1556 debugfs_remove_recursive(radio
->debugfs
);
1561 MODULE_ALIAS("platform:si476x-radio");
1563 static struct platform_driver si476x_radio_driver
= {
1565 .name
= DRIVER_NAME
,
1567 .probe
= si476x_radio_probe
,
1568 .remove
= si476x_radio_remove
,
1570 module_platform_driver(si476x_radio_driver
);
1572 MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
1573 MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell");
1574 MODULE_LICENSE("GPL");