xref: /OK3568_Linux_fs/kernel/drivers/media/radio/si470x/radio-si470x-common.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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