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