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