1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * drivers/media/radio/si470x/radio-si470x-common.c
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Driver for radios with Silicon Labs Si470x FM Radio Receivers
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
8*4882a593Smuzhiyun * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com>
9*4882a593Smuzhiyun */
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun
12*4882a593Smuzhiyun /*
13*4882a593Smuzhiyun * History:
14*4882a593Smuzhiyun * 2008-01-12 Tobias Lorenz <tobias.lorenz@gmx.net>
15*4882a593Smuzhiyun * Version 1.0.0
16*4882a593Smuzhiyun * - First working version
17*4882a593Smuzhiyun * 2008-01-13 Tobias Lorenz <tobias.lorenz@gmx.net>
18*4882a593Smuzhiyun * Version 1.0.1
19*4882a593Smuzhiyun * - Improved error handling, every function now returns errno
20*4882a593Smuzhiyun * - Improved multi user access (start/mute/stop)
21*4882a593Smuzhiyun * - Channel doesn't get lost anymore after start/mute/stop
22*4882a593Smuzhiyun * - RDS support added (polling mode via interrupt EP 1)
23*4882a593Smuzhiyun * - marked default module parameters with *value*
24*4882a593Smuzhiyun * - switched from bit structs to bit masks
25*4882a593Smuzhiyun * - header file cleaned and integrated
26*4882a593Smuzhiyun * 2008-01-14 Tobias Lorenz <tobias.lorenz@gmx.net>
27*4882a593Smuzhiyun * Version 1.0.2
28*4882a593Smuzhiyun * - hex values are now lower case
29*4882a593Smuzhiyun * - commented USB ID for ADS/Tech moved on todo list
30*4882a593Smuzhiyun * - blacklisted si470x in hid-quirks.c
31*4882a593Smuzhiyun * - rds buffer handling functions integrated into *_work, *_read
32*4882a593Smuzhiyun * - rds_command in si470x_poll exchanged against simple retval
33*4882a593Smuzhiyun * - check for firmware version 15
34*4882a593Smuzhiyun * - code order and prototypes still remain the same
35*4882a593Smuzhiyun * - spacing and bottom of band codes remain the same
36*4882a593Smuzhiyun * 2008-01-16 Tobias Lorenz <tobias.lorenz@gmx.net>
37*4882a593Smuzhiyun * Version 1.0.3
38*4882a593Smuzhiyun * - code reordered to avoid function prototypes
39*4882a593Smuzhiyun * - switch/case defaults are now more user-friendly
40*4882a593Smuzhiyun * - unified comment style
41*4882a593Smuzhiyun * - applied all checkpatch.pl v1.12 suggestions
42*4882a593Smuzhiyun * except the warning about the too long lines with bit comments
43*4882a593Smuzhiyun * - renamed FMRADIO to RADIO to cut line length (checkpatch.pl)
44*4882a593Smuzhiyun * 2008-01-22 Tobias Lorenz <tobias.lorenz@gmx.net>
45*4882a593Smuzhiyun * Version 1.0.4
46*4882a593Smuzhiyun * - avoid poss. locking when doing copy_to_user which may sleep
47*4882a593Smuzhiyun * - RDS is automatically activated on read now
48*4882a593Smuzhiyun * - code cleaned of unnecessary rds_commands
49*4882a593Smuzhiyun * - USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified
50*4882a593Smuzhiyun * (thanks to Guillaume RAMOUSSE)
51*4882a593Smuzhiyun * 2008-01-27 Tobias Lorenz <tobias.lorenz@gmx.net>
52*4882a593Smuzhiyun * Version 1.0.5
53*4882a593Smuzhiyun * - number of seek_retries changed to tune_timeout
54*4882a593Smuzhiyun * - fixed problem with incomplete tune operations by own buffers
55*4882a593Smuzhiyun * - optimization of variables and printf types
56*4882a593Smuzhiyun * - improved error logging
57*4882a593Smuzhiyun * 2008-01-31 Tobias Lorenz <tobias.lorenz@gmx.net>
58*4882a593Smuzhiyun * Oliver Neukum <oliver@neukum.org>
59*4882a593Smuzhiyun * Version 1.0.6
60*4882a593Smuzhiyun * - fixed coverity checker warnings in *_usb_driver_disconnect
61*4882a593Smuzhiyun * - probe()/open() race by correct ordering in probe()
62*4882a593Smuzhiyun * - DMA coherency rules by separate allocation of all buffers
63*4882a593Smuzhiyun * - use of endianness macros
64*4882a593Smuzhiyun * - abuse of spinlock, replaced by mutex
65*4882a593Smuzhiyun * - racy handling of timer in disconnect,
66*4882a593Smuzhiyun * replaced by delayed_work
67*4882a593Smuzhiyun * - racy interruptible_sleep_on(),
68*4882a593Smuzhiyun * replaced with wait_event_interruptible()
69*4882a593Smuzhiyun * - handle signals in read()
70*4882a593Smuzhiyun * 2008-02-08 Tobias Lorenz <tobias.lorenz@gmx.net>
71*4882a593Smuzhiyun * Oliver Neukum <oliver@neukum.org>
72*4882a593Smuzhiyun * Version 1.0.7
73*4882a593Smuzhiyun * - usb autosuspend support
74*4882a593Smuzhiyun * - unplugging fixed
75*4882a593Smuzhiyun * 2008-05-07 Tobias Lorenz <tobias.lorenz@gmx.net>
76*4882a593Smuzhiyun * Version 1.0.8
77*4882a593Smuzhiyun * - hardware frequency seek support
78*4882a593Smuzhiyun * - afc indication
79*4882a593Smuzhiyun * - more safety checks, let si470x_get_freq return errno
80*4882a593Smuzhiyun * - vidioc behavior corrected according to v4l2 spec
81*4882a593Smuzhiyun * 2008-10-20 Alexey Klimov <klimov.linux@gmail.com>
82*4882a593Smuzhiyun * - add support for KWorld USB FM Radio FM700
83*4882a593Smuzhiyun * - blacklisted KWorld radio in hid-core.c and hid-ids.h
84*4882a593Smuzhiyun * 2008-12-03 Mark Lord <mlord@pobox.com>
85*4882a593Smuzhiyun * - add support for DealExtreme USB Radio
86*4882a593Smuzhiyun * 2009-01-31 Bob Ross <pigiron@gmx.com>
87*4882a593Smuzhiyun * - correction of stereo detection/setting
88*4882a593Smuzhiyun * - correction of signal strength indicator scaling
89*4882a593Smuzhiyun * 2009-01-31 Rick Bronson <rick@efn.org>
90*4882a593Smuzhiyun * Tobias Lorenz <tobias.lorenz@gmx.net>
91*4882a593Smuzhiyun * - add LED status output
92*4882a593Smuzhiyun * - get HW/SW version from scratchpad
93*4882a593Smuzhiyun * 2009-06-16 Edouard Lafargue <edouard@lafargue.name>
94*4882a593Smuzhiyun * Version 1.0.10
95*4882a593Smuzhiyun * - add support for interrupt mode for RDS endpoint,
96*4882a593Smuzhiyun * instead of polling.
97*4882a593Smuzhiyun * Improves RDS reception significantly
98*4882a593Smuzhiyun */
99*4882a593Smuzhiyun
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun /* kernel includes */
102*4882a593Smuzhiyun #include "radio-si470x.h"
103*4882a593Smuzhiyun
104*4882a593Smuzhiyun /**************************************************************************
105*4882a593Smuzhiyun * Module Parameters
106*4882a593Smuzhiyun **************************************************************************/
107*4882a593Smuzhiyun
108*4882a593Smuzhiyun /* Spacing (kHz) */
109*4882a593Smuzhiyun /* 0: 200 kHz (USA, Australia) */
110*4882a593Smuzhiyun /* 1: 100 kHz (Europe, Japan) */
111*4882a593Smuzhiyun /* 2: 50 kHz */
112*4882a593Smuzhiyun static unsigned short space = 2;
113*4882a593Smuzhiyun module_param(space, ushort, 0444);
114*4882a593Smuzhiyun MODULE_PARM_DESC(space, "Spacing: 0=200kHz 1=100kHz *2=50kHz*");
115*4882a593Smuzhiyun
116*4882a593Smuzhiyun /* De-emphasis */
117*4882a593Smuzhiyun /* 0: 75 us (USA) */
118*4882a593Smuzhiyun /* 1: 50 us (Europe, Australia, Japan) */
119*4882a593Smuzhiyun static unsigned short de = 1;
120*4882a593Smuzhiyun module_param(de, ushort, 0444);
121*4882a593Smuzhiyun MODULE_PARM_DESC(de, "De-emphasis: 0=75us *1=50us*");
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun /* Tune timeout */
124*4882a593Smuzhiyun static unsigned int tune_timeout = 3000;
125*4882a593Smuzhiyun module_param(tune_timeout, uint, 0644);
126*4882a593Smuzhiyun MODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*");
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun /* Seek timeout */
129*4882a593Smuzhiyun static unsigned int seek_timeout = 5000;
130*4882a593Smuzhiyun module_param(seek_timeout, uint, 0644);
131*4882a593Smuzhiyun MODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*");
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun static const struct v4l2_frequency_band bands[] = {
134*4882a593Smuzhiyun {
135*4882a593Smuzhiyun .type = V4L2_TUNER_RADIO,
136*4882a593Smuzhiyun .index = 0,
137*4882a593Smuzhiyun .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
138*4882a593Smuzhiyun V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
139*4882a593Smuzhiyun V4L2_TUNER_CAP_FREQ_BANDS |
140*4882a593Smuzhiyun V4L2_TUNER_CAP_HWSEEK_BOUNDED |
141*4882a593Smuzhiyun V4L2_TUNER_CAP_HWSEEK_WRAP,
142*4882a593Smuzhiyun .rangelow = 87500 * 16,
143*4882a593Smuzhiyun .rangehigh = 108000 * 16,
144*4882a593Smuzhiyun .modulation = V4L2_BAND_MODULATION_FM,
145*4882a593Smuzhiyun },
146*4882a593Smuzhiyun {
147*4882a593Smuzhiyun .type = V4L2_TUNER_RADIO,
148*4882a593Smuzhiyun .index = 1,
149*4882a593Smuzhiyun .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
150*4882a593Smuzhiyun V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
151*4882a593Smuzhiyun V4L2_TUNER_CAP_FREQ_BANDS |
152*4882a593Smuzhiyun V4L2_TUNER_CAP_HWSEEK_BOUNDED |
153*4882a593Smuzhiyun V4L2_TUNER_CAP_HWSEEK_WRAP,
154*4882a593Smuzhiyun .rangelow = 76000 * 16,
155*4882a593Smuzhiyun .rangehigh = 108000 * 16,
156*4882a593Smuzhiyun .modulation = V4L2_BAND_MODULATION_FM,
157*4882a593Smuzhiyun },
158*4882a593Smuzhiyun {
159*4882a593Smuzhiyun .type = V4L2_TUNER_RADIO,
160*4882a593Smuzhiyun .index = 2,
161*4882a593Smuzhiyun .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
162*4882a593Smuzhiyun V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
163*4882a593Smuzhiyun V4L2_TUNER_CAP_FREQ_BANDS |
164*4882a593Smuzhiyun V4L2_TUNER_CAP_HWSEEK_BOUNDED |
165*4882a593Smuzhiyun V4L2_TUNER_CAP_HWSEEK_WRAP,
166*4882a593Smuzhiyun .rangelow = 76000 * 16,
167*4882a593Smuzhiyun .rangehigh = 90000 * 16,
168*4882a593Smuzhiyun .modulation = V4L2_BAND_MODULATION_FM,
169*4882a593Smuzhiyun },
170*4882a593Smuzhiyun };
171*4882a593Smuzhiyun
172*4882a593Smuzhiyun /**************************************************************************
173*4882a593Smuzhiyun * Generic Functions
174*4882a593Smuzhiyun **************************************************************************/
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun /*
177*4882a593Smuzhiyun * si470x_set_band - set the band
178*4882a593Smuzhiyun */
si470x_set_band(struct si470x_device * radio,int band)179*4882a593Smuzhiyun static int si470x_set_band(struct si470x_device *radio, int band)
180*4882a593Smuzhiyun {
181*4882a593Smuzhiyun if (radio->band == band)
182*4882a593Smuzhiyun return 0;
183*4882a593Smuzhiyun
184*4882a593Smuzhiyun radio->band = band;
185*4882a593Smuzhiyun radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_BAND;
186*4882a593Smuzhiyun radio->registers[SYSCONFIG2] |= radio->band << 6;
187*4882a593Smuzhiyun return radio->set_register(radio, SYSCONFIG2);
188*4882a593Smuzhiyun }
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun /*
191*4882a593Smuzhiyun * si470x_set_chan - set the channel
192*4882a593Smuzhiyun */
si470x_set_chan(struct si470x_device * radio,unsigned short chan)193*4882a593Smuzhiyun static int si470x_set_chan(struct si470x_device *radio, unsigned short chan)
194*4882a593Smuzhiyun {
195*4882a593Smuzhiyun int retval;
196*4882a593Smuzhiyun unsigned long time_left;
197*4882a593Smuzhiyun bool timed_out = false;
198*4882a593Smuzhiyun
199*4882a593Smuzhiyun retval = radio->get_register(radio, POWERCFG);
200*4882a593Smuzhiyun if (retval)
201*4882a593Smuzhiyun return retval;
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun if ((radio->registers[POWERCFG] & (POWERCFG_ENABLE|POWERCFG_DMUTE))
204*4882a593Smuzhiyun != (POWERCFG_ENABLE|POWERCFG_DMUTE)) {
205*4882a593Smuzhiyun return 0;
206*4882a593Smuzhiyun }
207*4882a593Smuzhiyun
208*4882a593Smuzhiyun /* start tuning */
209*4882a593Smuzhiyun radio->registers[CHANNEL] &= ~CHANNEL_CHAN;
210*4882a593Smuzhiyun radio->registers[CHANNEL] |= CHANNEL_TUNE | chan;
211*4882a593Smuzhiyun retval = radio->set_register(radio, CHANNEL);
212*4882a593Smuzhiyun if (retval < 0)
213*4882a593Smuzhiyun goto done;
214*4882a593Smuzhiyun
215*4882a593Smuzhiyun /* wait till tune operation has completed */
216*4882a593Smuzhiyun reinit_completion(&radio->completion);
217*4882a593Smuzhiyun time_left = wait_for_completion_timeout(&radio->completion,
218*4882a593Smuzhiyun msecs_to_jiffies(tune_timeout));
219*4882a593Smuzhiyun if (time_left == 0)
220*4882a593Smuzhiyun timed_out = true;
221*4882a593Smuzhiyun
222*4882a593Smuzhiyun if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
223*4882a593Smuzhiyun dev_warn(&radio->videodev.dev, "tune does not complete\n");
224*4882a593Smuzhiyun if (timed_out)
225*4882a593Smuzhiyun dev_warn(&radio->videodev.dev,
226*4882a593Smuzhiyun "tune timed out after %u ms\n", tune_timeout);
227*4882a593Smuzhiyun
228*4882a593Smuzhiyun /* stop tuning */
229*4882a593Smuzhiyun radio->registers[CHANNEL] &= ~CHANNEL_TUNE;
230*4882a593Smuzhiyun retval = radio->set_register(radio, CHANNEL);
231*4882a593Smuzhiyun
232*4882a593Smuzhiyun done:
233*4882a593Smuzhiyun return retval;
234*4882a593Smuzhiyun }
235*4882a593Smuzhiyun
236*4882a593Smuzhiyun /*
237*4882a593Smuzhiyun * si470x_get_step - get channel spacing
238*4882a593Smuzhiyun */
si470x_get_step(struct si470x_device * radio)239*4882a593Smuzhiyun static unsigned int si470x_get_step(struct si470x_device *radio)
240*4882a593Smuzhiyun {
241*4882a593Smuzhiyun /* Spacing (kHz) */
242*4882a593Smuzhiyun switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {
243*4882a593Smuzhiyun /* 0: 200 kHz (USA, Australia) */
244*4882a593Smuzhiyun case 0:
245*4882a593Smuzhiyun return 200 * 16;
246*4882a593Smuzhiyun /* 1: 100 kHz (Europe, Japan) */
247*4882a593Smuzhiyun case 1:
248*4882a593Smuzhiyun return 100 * 16;
249*4882a593Smuzhiyun /* 2: 50 kHz */
250*4882a593Smuzhiyun default:
251*4882a593Smuzhiyun return 50 * 16;
252*4882a593Smuzhiyun }
253*4882a593Smuzhiyun }
254*4882a593Smuzhiyun
255*4882a593Smuzhiyun
256*4882a593Smuzhiyun /*
257*4882a593Smuzhiyun * si470x_get_freq - get the frequency
258*4882a593Smuzhiyun */
si470x_get_freq(struct si470x_device * radio,unsigned int * freq)259*4882a593Smuzhiyun static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq)
260*4882a593Smuzhiyun {
261*4882a593Smuzhiyun int chan, retval;
262*4882a593Smuzhiyun
263*4882a593Smuzhiyun /* read channel */
264*4882a593Smuzhiyun retval = radio->get_register(radio, READCHAN);
265*4882a593Smuzhiyun chan = radio->registers[READCHAN] & READCHAN_READCHAN;
266*4882a593Smuzhiyun
267*4882a593Smuzhiyun /* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */
268*4882a593Smuzhiyun *freq = chan * si470x_get_step(radio) + bands[radio->band].rangelow;
269*4882a593Smuzhiyun
270*4882a593Smuzhiyun return retval;
271*4882a593Smuzhiyun }
272*4882a593Smuzhiyun
273*4882a593Smuzhiyun
274*4882a593Smuzhiyun /*
275*4882a593Smuzhiyun * si470x_set_freq - set the frequency
276*4882a593Smuzhiyun */
si470x_set_freq(struct si470x_device * radio,unsigned int freq)277*4882a593Smuzhiyun int si470x_set_freq(struct si470x_device *radio, unsigned int freq)
278*4882a593Smuzhiyun {
279*4882a593Smuzhiyun unsigned short chan;
280*4882a593Smuzhiyun
281*4882a593Smuzhiyun freq = clamp(freq, bands[radio->band].rangelow,
282*4882a593Smuzhiyun bands[radio->band].rangehigh);
283*4882a593Smuzhiyun /* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */
284*4882a593Smuzhiyun chan = (freq - bands[radio->band].rangelow) / si470x_get_step(radio);
285*4882a593Smuzhiyun
286*4882a593Smuzhiyun return si470x_set_chan(radio, chan);
287*4882a593Smuzhiyun }
288*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(si470x_set_freq);
289*4882a593Smuzhiyun
290*4882a593Smuzhiyun
291*4882a593Smuzhiyun /*
292*4882a593Smuzhiyun * si470x_set_seek - set seek
293*4882a593Smuzhiyun */
si470x_set_seek(struct si470x_device * radio,const struct v4l2_hw_freq_seek * seek)294*4882a593Smuzhiyun static int si470x_set_seek(struct si470x_device *radio,
295*4882a593Smuzhiyun const struct v4l2_hw_freq_seek *seek)
296*4882a593Smuzhiyun {
297*4882a593Smuzhiyun int band, retval;
298*4882a593Smuzhiyun unsigned int freq;
299*4882a593Smuzhiyun bool timed_out = false;
300*4882a593Smuzhiyun unsigned long time_left;
301*4882a593Smuzhiyun
302*4882a593Smuzhiyun /* set band */
303*4882a593Smuzhiyun if (seek->rangelow || seek->rangehigh) {
304*4882a593Smuzhiyun for (band = 0; band < ARRAY_SIZE(bands); band++) {
305*4882a593Smuzhiyun if (bands[band].rangelow == seek->rangelow &&
306*4882a593Smuzhiyun bands[band].rangehigh == seek->rangehigh)
307*4882a593Smuzhiyun break;
308*4882a593Smuzhiyun }
309*4882a593Smuzhiyun if (band == ARRAY_SIZE(bands))
310*4882a593Smuzhiyun return -EINVAL; /* No matching band found */
311*4882a593Smuzhiyun } else
312*4882a593Smuzhiyun band = 1; /* If nothing is specified seek 76 - 108 Mhz */
313*4882a593Smuzhiyun
314*4882a593Smuzhiyun if (radio->band != band) {
315*4882a593Smuzhiyun retval = si470x_get_freq(radio, &freq);
316*4882a593Smuzhiyun if (retval)
317*4882a593Smuzhiyun return retval;
318*4882a593Smuzhiyun retval = si470x_set_band(radio, band);
319*4882a593Smuzhiyun if (retval)
320*4882a593Smuzhiyun return retval;
321*4882a593Smuzhiyun retval = si470x_set_freq(radio, freq);
322*4882a593Smuzhiyun if (retval)
323*4882a593Smuzhiyun return retval;
324*4882a593Smuzhiyun }
325*4882a593Smuzhiyun
326*4882a593Smuzhiyun /* start seeking */
327*4882a593Smuzhiyun radio->registers[POWERCFG] |= POWERCFG_SEEK;
328*4882a593Smuzhiyun if (seek->wrap_around)
329*4882a593Smuzhiyun radio->registers[POWERCFG] &= ~POWERCFG_SKMODE;
330*4882a593Smuzhiyun else
331*4882a593Smuzhiyun radio->registers[POWERCFG] |= POWERCFG_SKMODE;
332*4882a593Smuzhiyun if (seek->seek_upward)
333*4882a593Smuzhiyun radio->registers[POWERCFG] |= POWERCFG_SEEKUP;
334*4882a593Smuzhiyun else
335*4882a593Smuzhiyun radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP;
336*4882a593Smuzhiyun retval = radio->set_register(radio, POWERCFG);
337*4882a593Smuzhiyun if (retval < 0)
338*4882a593Smuzhiyun return retval;
339*4882a593Smuzhiyun
340*4882a593Smuzhiyun /* wait till tune operation has completed */
341*4882a593Smuzhiyun reinit_completion(&radio->completion);
342*4882a593Smuzhiyun time_left = wait_for_completion_timeout(&radio->completion,
343*4882a593Smuzhiyun msecs_to_jiffies(seek_timeout));
344*4882a593Smuzhiyun if (time_left == 0)
345*4882a593Smuzhiyun timed_out = true;
346*4882a593Smuzhiyun
347*4882a593Smuzhiyun if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
348*4882a593Smuzhiyun dev_warn(&radio->videodev.dev, "seek does not complete\n");
349*4882a593Smuzhiyun if (radio->registers[STATUSRSSI] & STATUSRSSI_SF)
350*4882a593Smuzhiyun dev_warn(&radio->videodev.dev,
351*4882a593Smuzhiyun "seek failed / band limit reached\n");
352*4882a593Smuzhiyun
353*4882a593Smuzhiyun /* stop seeking */
354*4882a593Smuzhiyun radio->registers[POWERCFG] &= ~POWERCFG_SEEK;
355*4882a593Smuzhiyun retval = radio->set_register(radio, POWERCFG);
356*4882a593Smuzhiyun
357*4882a593Smuzhiyun /* try again, if timed out */
358*4882a593Smuzhiyun if (retval == 0 && timed_out)
359*4882a593Smuzhiyun return -ENODATA;
360*4882a593Smuzhiyun return retval;
361*4882a593Smuzhiyun }
362*4882a593Smuzhiyun
363*4882a593Smuzhiyun
364*4882a593Smuzhiyun /*
365*4882a593Smuzhiyun * si470x_start - switch on radio
366*4882a593Smuzhiyun */
si470x_start(struct si470x_device * radio)367*4882a593Smuzhiyun int si470x_start(struct si470x_device *radio)
368*4882a593Smuzhiyun {
369*4882a593Smuzhiyun int retval;
370*4882a593Smuzhiyun
371*4882a593Smuzhiyun /* powercfg */
372*4882a593Smuzhiyun radio->registers[POWERCFG] =
373*4882a593Smuzhiyun POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM;
374*4882a593Smuzhiyun retval = radio->set_register(radio, POWERCFG);
375*4882a593Smuzhiyun if (retval < 0)
376*4882a593Smuzhiyun goto done;
377*4882a593Smuzhiyun
378*4882a593Smuzhiyun /* sysconfig 1 */
379*4882a593Smuzhiyun radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDSIEN | SYSCONFIG1_STCIEN |
380*4882a593Smuzhiyun SYSCONFIG1_RDS;
381*4882a593Smuzhiyun radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_GPIO2;
382*4882a593Smuzhiyun radio->registers[SYSCONFIG1] |= SYSCONFIG1_GPIO2_INT;
383*4882a593Smuzhiyun if (de)
384*4882a593Smuzhiyun radio->registers[SYSCONFIG1] |= SYSCONFIG1_DE;
385*4882a593Smuzhiyun retval = radio->set_register(radio, SYSCONFIG1);
386*4882a593Smuzhiyun if (retval < 0)
387*4882a593Smuzhiyun goto done;
388*4882a593Smuzhiyun
389*4882a593Smuzhiyun /* sysconfig 2 */
390*4882a593Smuzhiyun radio->registers[SYSCONFIG2] =
391*4882a593Smuzhiyun (0x1f << 8) | /* SEEKTH */
392*4882a593Smuzhiyun ((radio->band << 6) & SYSCONFIG2_BAND) |/* BAND */
393*4882a593Smuzhiyun ((space << 4) & SYSCONFIG2_SPACE) | /* SPACE */
394*4882a593Smuzhiyun 15; /* VOLUME (max) */
395*4882a593Smuzhiyun retval = radio->set_register(radio, SYSCONFIG2);
396*4882a593Smuzhiyun if (retval < 0)
397*4882a593Smuzhiyun goto done;
398*4882a593Smuzhiyun
399*4882a593Smuzhiyun /* reset last channel */
400*4882a593Smuzhiyun retval = si470x_set_chan(radio,
401*4882a593Smuzhiyun radio->registers[CHANNEL] & CHANNEL_CHAN);
402*4882a593Smuzhiyun
403*4882a593Smuzhiyun done:
404*4882a593Smuzhiyun return retval;
405*4882a593Smuzhiyun }
406*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(si470x_start);
407*4882a593Smuzhiyun
408*4882a593Smuzhiyun
409*4882a593Smuzhiyun /*
410*4882a593Smuzhiyun * si470x_stop - switch off radio
411*4882a593Smuzhiyun */
si470x_stop(struct si470x_device * radio)412*4882a593Smuzhiyun int si470x_stop(struct si470x_device *radio)
413*4882a593Smuzhiyun {
414*4882a593Smuzhiyun int retval;
415*4882a593Smuzhiyun
416*4882a593Smuzhiyun /* sysconfig 1 */
417*4882a593Smuzhiyun radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
418*4882a593Smuzhiyun retval = radio->set_register(radio, SYSCONFIG1);
419*4882a593Smuzhiyun if (retval < 0)
420*4882a593Smuzhiyun goto done;
421*4882a593Smuzhiyun
422*4882a593Smuzhiyun /* powercfg */
423*4882a593Smuzhiyun radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
424*4882a593Smuzhiyun /* POWERCFG_ENABLE has to automatically go low */
425*4882a593Smuzhiyun radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE;
426*4882a593Smuzhiyun retval = radio->set_register(radio, POWERCFG);
427*4882a593Smuzhiyun
428*4882a593Smuzhiyun done:
429*4882a593Smuzhiyun return retval;
430*4882a593Smuzhiyun }
431*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(si470x_stop);
432*4882a593Smuzhiyun
433*4882a593Smuzhiyun
434*4882a593Smuzhiyun /*
435*4882a593Smuzhiyun * si470x_rds_on - switch on rds reception
436*4882a593Smuzhiyun */
si470x_rds_on(struct si470x_device * radio)437*4882a593Smuzhiyun static int si470x_rds_on(struct si470x_device *radio)
438*4882a593Smuzhiyun {
439*4882a593Smuzhiyun int retval;
440*4882a593Smuzhiyun
441*4882a593Smuzhiyun /* sysconfig 1 */
442*4882a593Smuzhiyun radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS;
443*4882a593Smuzhiyun retval = radio->set_register(radio, SYSCONFIG1);
444*4882a593Smuzhiyun if (retval < 0)
445*4882a593Smuzhiyun radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
446*4882a593Smuzhiyun
447*4882a593Smuzhiyun return retval;
448*4882a593Smuzhiyun }
449*4882a593Smuzhiyun
450*4882a593Smuzhiyun
451*4882a593Smuzhiyun
452*4882a593Smuzhiyun /**************************************************************************
453*4882a593Smuzhiyun * File Operations Interface
454*4882a593Smuzhiyun **************************************************************************/
455*4882a593Smuzhiyun
456*4882a593Smuzhiyun /*
457*4882a593Smuzhiyun * si470x_fops_read - read RDS data
458*4882a593Smuzhiyun */
si470x_fops_read(struct file * file,char __user * buf,size_t count,loff_t * ppos)459*4882a593Smuzhiyun static ssize_t si470x_fops_read(struct file *file, char __user *buf,
460*4882a593Smuzhiyun size_t count, loff_t *ppos)
461*4882a593Smuzhiyun {
462*4882a593Smuzhiyun struct si470x_device *radio = video_drvdata(file);
463*4882a593Smuzhiyun int retval = 0;
464*4882a593Smuzhiyun unsigned int block_count = 0;
465*4882a593Smuzhiyun
466*4882a593Smuzhiyun /* switch on rds reception */
467*4882a593Smuzhiyun if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
468*4882a593Smuzhiyun si470x_rds_on(radio);
469*4882a593Smuzhiyun
470*4882a593Smuzhiyun /* block if no new data available */
471*4882a593Smuzhiyun while (radio->wr_index == radio->rd_index) {
472*4882a593Smuzhiyun if (file->f_flags & O_NONBLOCK) {
473*4882a593Smuzhiyun retval = -EWOULDBLOCK;
474*4882a593Smuzhiyun goto done;
475*4882a593Smuzhiyun }
476*4882a593Smuzhiyun if (wait_event_interruptible(radio->read_queue,
477*4882a593Smuzhiyun radio->wr_index != radio->rd_index) < 0) {
478*4882a593Smuzhiyun retval = -EINTR;
479*4882a593Smuzhiyun goto done;
480*4882a593Smuzhiyun }
481*4882a593Smuzhiyun }
482*4882a593Smuzhiyun
483*4882a593Smuzhiyun /* calculate block count from byte count */
484*4882a593Smuzhiyun count /= 3;
485*4882a593Smuzhiyun
486*4882a593Smuzhiyun /* copy RDS block out of internal buffer and to user buffer */
487*4882a593Smuzhiyun while (block_count < count) {
488*4882a593Smuzhiyun if (radio->rd_index == radio->wr_index)
489*4882a593Smuzhiyun break;
490*4882a593Smuzhiyun
491*4882a593Smuzhiyun /* always transfer rds complete blocks */
492*4882a593Smuzhiyun if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3))
493*4882a593Smuzhiyun /* retval = -EFAULT; */
494*4882a593Smuzhiyun break;
495*4882a593Smuzhiyun
496*4882a593Smuzhiyun /* increment and wrap read pointer */
497*4882a593Smuzhiyun radio->rd_index += 3;
498*4882a593Smuzhiyun if (radio->rd_index >= radio->buf_size)
499*4882a593Smuzhiyun radio->rd_index = 0;
500*4882a593Smuzhiyun
501*4882a593Smuzhiyun /* increment counters */
502*4882a593Smuzhiyun block_count++;
503*4882a593Smuzhiyun buf += 3;
504*4882a593Smuzhiyun retval += 3;
505*4882a593Smuzhiyun }
506*4882a593Smuzhiyun
507*4882a593Smuzhiyun done:
508*4882a593Smuzhiyun return retval;
509*4882a593Smuzhiyun }
510*4882a593Smuzhiyun
511*4882a593Smuzhiyun
512*4882a593Smuzhiyun /*
513*4882a593Smuzhiyun * si470x_fops_poll - poll RDS data
514*4882a593Smuzhiyun */
si470x_fops_poll(struct file * file,struct poll_table_struct * pts)515*4882a593Smuzhiyun static __poll_t si470x_fops_poll(struct file *file,
516*4882a593Smuzhiyun struct poll_table_struct *pts)
517*4882a593Smuzhiyun {
518*4882a593Smuzhiyun struct si470x_device *radio = video_drvdata(file);
519*4882a593Smuzhiyun __poll_t req_events = poll_requested_events(pts);
520*4882a593Smuzhiyun __poll_t retval = v4l2_ctrl_poll(file, pts);
521*4882a593Smuzhiyun
522*4882a593Smuzhiyun if (req_events & (EPOLLIN | EPOLLRDNORM)) {
523*4882a593Smuzhiyun /* switch on rds reception */
524*4882a593Smuzhiyun if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
525*4882a593Smuzhiyun si470x_rds_on(radio);
526*4882a593Smuzhiyun
527*4882a593Smuzhiyun poll_wait(file, &radio->read_queue, pts);
528*4882a593Smuzhiyun
529*4882a593Smuzhiyun if (radio->rd_index != radio->wr_index)
530*4882a593Smuzhiyun retval |= EPOLLIN | EPOLLRDNORM;
531*4882a593Smuzhiyun }
532*4882a593Smuzhiyun
533*4882a593Smuzhiyun return retval;
534*4882a593Smuzhiyun }
535*4882a593Smuzhiyun
536*4882a593Smuzhiyun
si470x_fops_open(struct file * file)537*4882a593Smuzhiyun static int si470x_fops_open(struct file *file)
538*4882a593Smuzhiyun {
539*4882a593Smuzhiyun struct si470x_device *radio = video_drvdata(file);
540*4882a593Smuzhiyun
541*4882a593Smuzhiyun return radio->fops_open(file);
542*4882a593Smuzhiyun }
543*4882a593Smuzhiyun
544*4882a593Smuzhiyun
545*4882a593Smuzhiyun /*
546*4882a593Smuzhiyun * si470x_fops_release - file release
547*4882a593Smuzhiyun */
si470x_fops_release(struct file * file)548*4882a593Smuzhiyun static int si470x_fops_release(struct file *file)
549*4882a593Smuzhiyun {
550*4882a593Smuzhiyun struct si470x_device *radio = video_drvdata(file);
551*4882a593Smuzhiyun
552*4882a593Smuzhiyun return radio->fops_release(file);
553*4882a593Smuzhiyun }
554*4882a593Smuzhiyun
555*4882a593Smuzhiyun
556*4882a593Smuzhiyun /*
557*4882a593Smuzhiyun * si470x_fops - file operations interface
558*4882a593Smuzhiyun */
559*4882a593Smuzhiyun static const struct v4l2_file_operations si470x_fops = {
560*4882a593Smuzhiyun .owner = THIS_MODULE,
561*4882a593Smuzhiyun .read = si470x_fops_read,
562*4882a593Smuzhiyun .poll = si470x_fops_poll,
563*4882a593Smuzhiyun .unlocked_ioctl = video_ioctl2,
564*4882a593Smuzhiyun .open = si470x_fops_open,
565*4882a593Smuzhiyun .release = si470x_fops_release,
566*4882a593Smuzhiyun };
567*4882a593Smuzhiyun
568*4882a593Smuzhiyun
569*4882a593Smuzhiyun
570*4882a593Smuzhiyun /**************************************************************************
571*4882a593Smuzhiyun * Video4Linux Interface
572*4882a593Smuzhiyun **************************************************************************/
573*4882a593Smuzhiyun
574*4882a593Smuzhiyun
si470x_s_ctrl(struct v4l2_ctrl * ctrl)575*4882a593Smuzhiyun static int si470x_s_ctrl(struct v4l2_ctrl *ctrl)
576*4882a593Smuzhiyun {
577*4882a593Smuzhiyun struct si470x_device *radio =
578*4882a593Smuzhiyun container_of(ctrl->handler, struct si470x_device, hdl);
579*4882a593Smuzhiyun
580*4882a593Smuzhiyun switch (ctrl->id) {
581*4882a593Smuzhiyun case V4L2_CID_AUDIO_VOLUME:
582*4882a593Smuzhiyun radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME;
583*4882a593Smuzhiyun radio->registers[SYSCONFIG2] |= ctrl->val;
584*4882a593Smuzhiyun return radio->set_register(radio, SYSCONFIG2);
585*4882a593Smuzhiyun case V4L2_CID_AUDIO_MUTE:
586*4882a593Smuzhiyun if (ctrl->val)
587*4882a593Smuzhiyun radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
588*4882a593Smuzhiyun else
589*4882a593Smuzhiyun radio->registers[POWERCFG] |= POWERCFG_DMUTE;
590*4882a593Smuzhiyun return radio->set_register(radio, POWERCFG);
591*4882a593Smuzhiyun default:
592*4882a593Smuzhiyun return -EINVAL;
593*4882a593Smuzhiyun }
594*4882a593Smuzhiyun }
595*4882a593Smuzhiyun
596*4882a593Smuzhiyun
597*4882a593Smuzhiyun /*
598*4882a593Smuzhiyun * si470x_vidioc_g_tuner - get tuner attributes
599*4882a593Smuzhiyun */
si470x_vidioc_g_tuner(struct file * file,void * priv,struct v4l2_tuner * tuner)600*4882a593Smuzhiyun static int si470x_vidioc_g_tuner(struct file *file, void *priv,
601*4882a593Smuzhiyun struct v4l2_tuner *tuner)
602*4882a593Smuzhiyun {
603*4882a593Smuzhiyun struct si470x_device *radio = video_drvdata(file);
604*4882a593Smuzhiyun int retval = 0;
605*4882a593Smuzhiyun
606*4882a593Smuzhiyun if (tuner->index != 0)
607*4882a593Smuzhiyun return -EINVAL;
608*4882a593Smuzhiyun
609*4882a593Smuzhiyun if (!radio->status_rssi_auto_update) {
610*4882a593Smuzhiyun retval = radio->get_register(radio, STATUSRSSI);
611*4882a593Smuzhiyun if (retval < 0)
612*4882a593Smuzhiyun return retval;
613*4882a593Smuzhiyun }
614*4882a593Smuzhiyun
615*4882a593Smuzhiyun /* driver constants */
616*4882a593Smuzhiyun strscpy(tuner->name, "FM", sizeof(tuner->name));
617*4882a593Smuzhiyun tuner->type = V4L2_TUNER_RADIO;
618*4882a593Smuzhiyun tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
619*4882a593Smuzhiyun V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
620*4882a593Smuzhiyun V4L2_TUNER_CAP_HWSEEK_BOUNDED |
621*4882a593Smuzhiyun V4L2_TUNER_CAP_HWSEEK_WRAP;
622*4882a593Smuzhiyun tuner->rangelow = 76 * FREQ_MUL;
623*4882a593Smuzhiyun tuner->rangehigh = 108 * FREQ_MUL;
624*4882a593Smuzhiyun
625*4882a593Smuzhiyun /* stereo indicator == stereo (instead of mono) */
626*4882a593Smuzhiyun if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0)
627*4882a593Smuzhiyun tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
628*4882a593Smuzhiyun else
629*4882a593Smuzhiyun tuner->rxsubchans = V4L2_TUNER_SUB_STEREO;
630*4882a593Smuzhiyun /* If there is a reliable method of detecting an RDS channel,
631*4882a593Smuzhiyun then this code should check for that before setting this
632*4882a593Smuzhiyun RDS subchannel. */
633*4882a593Smuzhiyun tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;
634*4882a593Smuzhiyun
635*4882a593Smuzhiyun /* mono/stereo selector */
636*4882a593Smuzhiyun if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 0)
637*4882a593Smuzhiyun tuner->audmode = V4L2_TUNER_MODE_STEREO;
638*4882a593Smuzhiyun else
639*4882a593Smuzhiyun tuner->audmode = V4L2_TUNER_MODE_MONO;
640*4882a593Smuzhiyun
641*4882a593Smuzhiyun /* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */
642*4882a593Smuzhiyun /* measured in units of dbµV in 1 db increments (max at ~75 dbµV) */
643*4882a593Smuzhiyun tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI);
644*4882a593Smuzhiyun /* the ideal factor is 0xffff/75 = 873,8 */
645*4882a593Smuzhiyun tuner->signal = (tuner->signal * 873) + (8 * tuner->signal / 10);
646*4882a593Smuzhiyun if (tuner->signal > 0xffff)
647*4882a593Smuzhiyun tuner->signal = 0xffff;
648*4882a593Smuzhiyun
649*4882a593Smuzhiyun /* automatic frequency control: -1: freq to low, 1 freq to high */
650*4882a593Smuzhiyun /* AFCRL does only indicate that freq. differs, not if too low/high */
651*4882a593Smuzhiyun tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0;
652*4882a593Smuzhiyun
653*4882a593Smuzhiyun return retval;
654*4882a593Smuzhiyun }
655*4882a593Smuzhiyun
656*4882a593Smuzhiyun
657*4882a593Smuzhiyun /*
658*4882a593Smuzhiyun * si470x_vidioc_s_tuner - set tuner attributes
659*4882a593Smuzhiyun */
si470x_vidioc_s_tuner(struct file * file,void * priv,const struct v4l2_tuner * tuner)660*4882a593Smuzhiyun static int si470x_vidioc_s_tuner(struct file *file, void *priv,
661*4882a593Smuzhiyun const struct v4l2_tuner *tuner)
662*4882a593Smuzhiyun {
663*4882a593Smuzhiyun struct si470x_device *radio = video_drvdata(file);
664*4882a593Smuzhiyun
665*4882a593Smuzhiyun if (tuner->index != 0)
666*4882a593Smuzhiyun return -EINVAL;
667*4882a593Smuzhiyun
668*4882a593Smuzhiyun /* mono/stereo selector */
669*4882a593Smuzhiyun switch (tuner->audmode) {
670*4882a593Smuzhiyun case V4L2_TUNER_MODE_MONO:
671*4882a593Smuzhiyun radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */
672*4882a593Smuzhiyun break;
673*4882a593Smuzhiyun case V4L2_TUNER_MODE_STEREO:
674*4882a593Smuzhiyun default:
675*4882a593Smuzhiyun radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */
676*4882a593Smuzhiyun break;
677*4882a593Smuzhiyun }
678*4882a593Smuzhiyun
679*4882a593Smuzhiyun return radio->set_register(radio, POWERCFG);
680*4882a593Smuzhiyun }
681*4882a593Smuzhiyun
682*4882a593Smuzhiyun
683*4882a593Smuzhiyun /*
684*4882a593Smuzhiyun * si470x_vidioc_g_frequency - get tuner or modulator radio frequency
685*4882a593Smuzhiyun */
si470x_vidioc_g_frequency(struct file * file,void * priv,struct v4l2_frequency * freq)686*4882a593Smuzhiyun static int si470x_vidioc_g_frequency(struct file *file, void *priv,
687*4882a593Smuzhiyun struct v4l2_frequency *freq)
688*4882a593Smuzhiyun {
689*4882a593Smuzhiyun struct si470x_device *radio = video_drvdata(file);
690*4882a593Smuzhiyun
691*4882a593Smuzhiyun if (freq->tuner != 0)
692*4882a593Smuzhiyun return -EINVAL;
693*4882a593Smuzhiyun
694*4882a593Smuzhiyun freq->type = V4L2_TUNER_RADIO;
695*4882a593Smuzhiyun return si470x_get_freq(radio, &freq->frequency);
696*4882a593Smuzhiyun }
697*4882a593Smuzhiyun
698*4882a593Smuzhiyun
699*4882a593Smuzhiyun /*
700*4882a593Smuzhiyun * si470x_vidioc_s_frequency - set tuner or modulator radio frequency
701*4882a593Smuzhiyun */
si470x_vidioc_s_frequency(struct file * file,void * priv,const struct v4l2_frequency * freq)702*4882a593Smuzhiyun static int si470x_vidioc_s_frequency(struct file *file, void *priv,
703*4882a593Smuzhiyun const struct v4l2_frequency *freq)
704*4882a593Smuzhiyun {
705*4882a593Smuzhiyun struct si470x_device *radio = video_drvdata(file);
706*4882a593Smuzhiyun int retval;
707*4882a593Smuzhiyun
708*4882a593Smuzhiyun if (freq->tuner != 0)
709*4882a593Smuzhiyun return -EINVAL;
710*4882a593Smuzhiyun
711*4882a593Smuzhiyun if (freq->frequency < bands[radio->band].rangelow ||
712*4882a593Smuzhiyun freq->frequency > bands[radio->band].rangehigh) {
713*4882a593Smuzhiyun /* Switch to band 1 which covers everything we support */
714*4882a593Smuzhiyun retval = si470x_set_band(radio, 1);
715*4882a593Smuzhiyun if (retval)
716*4882a593Smuzhiyun return retval;
717*4882a593Smuzhiyun }
718*4882a593Smuzhiyun return si470x_set_freq(radio, freq->frequency);
719*4882a593Smuzhiyun }
720*4882a593Smuzhiyun
721*4882a593Smuzhiyun
722*4882a593Smuzhiyun /*
723*4882a593Smuzhiyun * si470x_vidioc_s_hw_freq_seek - set hardware frequency seek
724*4882a593Smuzhiyun */
si470x_vidioc_s_hw_freq_seek(struct file * file,void * priv,const struct v4l2_hw_freq_seek * seek)725*4882a593Smuzhiyun static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv,
726*4882a593Smuzhiyun const struct v4l2_hw_freq_seek *seek)
727*4882a593Smuzhiyun {
728*4882a593Smuzhiyun struct si470x_device *radio = video_drvdata(file);
729*4882a593Smuzhiyun
730*4882a593Smuzhiyun if (seek->tuner != 0)
731*4882a593Smuzhiyun return -EINVAL;
732*4882a593Smuzhiyun
733*4882a593Smuzhiyun if (file->f_flags & O_NONBLOCK)
734*4882a593Smuzhiyun return -EWOULDBLOCK;
735*4882a593Smuzhiyun
736*4882a593Smuzhiyun return si470x_set_seek(radio, seek);
737*4882a593Smuzhiyun }
738*4882a593Smuzhiyun
739*4882a593Smuzhiyun /*
740*4882a593Smuzhiyun * si470x_vidioc_enum_freq_bands - enumerate supported bands
741*4882a593Smuzhiyun */
si470x_vidioc_enum_freq_bands(struct file * file,void * priv,struct v4l2_frequency_band * band)742*4882a593Smuzhiyun static int si470x_vidioc_enum_freq_bands(struct file *file, void *priv,
743*4882a593Smuzhiyun struct v4l2_frequency_band *band)
744*4882a593Smuzhiyun {
745*4882a593Smuzhiyun if (band->tuner != 0)
746*4882a593Smuzhiyun return -EINVAL;
747*4882a593Smuzhiyun if (band->index >= ARRAY_SIZE(bands))
748*4882a593Smuzhiyun return -EINVAL;
749*4882a593Smuzhiyun *band = bands[band->index];
750*4882a593Smuzhiyun return 0;
751*4882a593Smuzhiyun }
752*4882a593Smuzhiyun
753*4882a593Smuzhiyun const struct v4l2_ctrl_ops si470x_ctrl_ops = {
754*4882a593Smuzhiyun .s_ctrl = si470x_s_ctrl,
755*4882a593Smuzhiyun };
756*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(si470x_ctrl_ops);
757*4882a593Smuzhiyun
si470x_vidioc_querycap(struct file * file,void * priv,struct v4l2_capability * capability)758*4882a593Smuzhiyun static int si470x_vidioc_querycap(struct file *file, void *priv,
759*4882a593Smuzhiyun struct v4l2_capability *capability)
760*4882a593Smuzhiyun {
761*4882a593Smuzhiyun struct si470x_device *radio = video_drvdata(file);
762*4882a593Smuzhiyun
763*4882a593Smuzhiyun return radio->vidioc_querycap(file, priv, capability);
764*4882a593Smuzhiyun };
765*4882a593Smuzhiyun
766*4882a593Smuzhiyun /*
767*4882a593Smuzhiyun * si470x_ioctl_ops - video device ioctl operations
768*4882a593Smuzhiyun */
769*4882a593Smuzhiyun static const struct v4l2_ioctl_ops si470x_ioctl_ops = {
770*4882a593Smuzhiyun .vidioc_querycap = si470x_vidioc_querycap,
771*4882a593Smuzhiyun .vidioc_g_tuner = si470x_vidioc_g_tuner,
772*4882a593Smuzhiyun .vidioc_s_tuner = si470x_vidioc_s_tuner,
773*4882a593Smuzhiyun .vidioc_g_frequency = si470x_vidioc_g_frequency,
774*4882a593Smuzhiyun .vidioc_s_frequency = si470x_vidioc_s_frequency,
775*4882a593Smuzhiyun .vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek,
776*4882a593Smuzhiyun .vidioc_enum_freq_bands = si470x_vidioc_enum_freq_bands,
777*4882a593Smuzhiyun .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
778*4882a593Smuzhiyun .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
779*4882a593Smuzhiyun };
780*4882a593Smuzhiyun
781*4882a593Smuzhiyun
782*4882a593Smuzhiyun /*
783*4882a593Smuzhiyun * si470x_viddev_template - video device interface
784*4882a593Smuzhiyun */
785*4882a593Smuzhiyun const struct video_device si470x_viddev_template = {
786*4882a593Smuzhiyun .fops = &si470x_fops,
787*4882a593Smuzhiyun .name = DRIVER_NAME,
788*4882a593Smuzhiyun .release = video_device_release_empty,
789*4882a593Smuzhiyun .ioctl_ops = &si470x_ioctl_ops,
790*4882a593Smuzhiyun };
791*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(si470x_viddev_template);
792*4882a593Smuzhiyun
793*4882a593Smuzhiyun MODULE_LICENSE("GPL");
794