1*c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2b09cd163SJoonyoung Shim /* 3b09cd163SJoonyoung Shim * drivers/media/radio/si470x/radio-si470x-common.c 4b09cd163SJoonyoung Shim * 5b09cd163SJoonyoung Shim * Driver for radios with Silicon Labs Si470x FM Radio Receivers 6b09cd163SJoonyoung Shim * 7b09cd163SJoonyoung Shim * Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net> 8f140612dSHans de Goede * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> 9b09cd163SJoonyoung Shim */ 10b09cd163SJoonyoung Shim 11b09cd163SJoonyoung Shim 12b09cd163SJoonyoung Shim /* 13b09cd163SJoonyoung Shim * History: 14b09cd163SJoonyoung Shim * 2008-01-12 Tobias Lorenz <tobias.lorenz@gmx.net> 15b09cd163SJoonyoung Shim * Version 1.0.0 16b09cd163SJoonyoung Shim * - First working version 17b09cd163SJoonyoung Shim * 2008-01-13 Tobias Lorenz <tobias.lorenz@gmx.net> 18b09cd163SJoonyoung Shim * Version 1.0.1 19b09cd163SJoonyoung Shim * - Improved error handling, every function now returns errno 20b09cd163SJoonyoung Shim * - Improved multi user access (start/mute/stop) 21b09cd163SJoonyoung Shim * - Channel doesn't get lost anymore after start/mute/stop 22b09cd163SJoonyoung Shim * - RDS support added (polling mode via interrupt EP 1) 23b09cd163SJoonyoung Shim * - marked default module parameters with *value* 24b09cd163SJoonyoung Shim * - switched from bit structs to bit masks 25b09cd163SJoonyoung Shim * - header file cleaned and integrated 26b09cd163SJoonyoung Shim * 2008-01-14 Tobias Lorenz <tobias.lorenz@gmx.net> 27b09cd163SJoonyoung Shim * Version 1.0.2 28b09cd163SJoonyoung Shim * - hex values are now lower case 29b09cd163SJoonyoung Shim * - commented USB ID for ADS/Tech moved on todo list 30b09cd163SJoonyoung Shim * - blacklisted si470x in hid-quirks.c 31b09cd163SJoonyoung Shim * - rds buffer handling functions integrated into *_work, *_read 32b09cd163SJoonyoung Shim * - rds_command in si470x_poll exchanged against simple retval 33b09cd163SJoonyoung Shim * - check for firmware version 15 34b09cd163SJoonyoung Shim * - code order and prototypes still remain the same 35b09cd163SJoonyoung Shim * - spacing and bottom of band codes remain the same 36b09cd163SJoonyoung Shim * 2008-01-16 Tobias Lorenz <tobias.lorenz@gmx.net> 37b09cd163SJoonyoung Shim * Version 1.0.3 38b09cd163SJoonyoung Shim * - code reordered to avoid function prototypes 39b09cd163SJoonyoung Shim * - switch/case defaults are now more user-friendly 40b09cd163SJoonyoung Shim * - unified comment style 41b09cd163SJoonyoung Shim * - applied all checkpatch.pl v1.12 suggestions 42b09cd163SJoonyoung Shim * except the warning about the too long lines with bit comments 43b09cd163SJoonyoung Shim * - renamed FMRADIO to RADIO to cut line length (checkpatch.pl) 44b09cd163SJoonyoung Shim * 2008-01-22 Tobias Lorenz <tobias.lorenz@gmx.net> 45b09cd163SJoonyoung Shim * Version 1.0.4 46b09cd163SJoonyoung Shim * - avoid poss. locking when doing copy_to_user which may sleep 47b09cd163SJoonyoung Shim * - RDS is automatically activated on read now 48b09cd163SJoonyoung Shim * - code cleaned of unnecessary rds_commands 49b09cd163SJoonyoung Shim * - USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified 50b09cd163SJoonyoung Shim * (thanks to Guillaume RAMOUSSE) 51b09cd163SJoonyoung Shim * 2008-01-27 Tobias Lorenz <tobias.lorenz@gmx.net> 52b09cd163SJoonyoung Shim * Version 1.0.5 53b09cd163SJoonyoung Shim * - number of seek_retries changed to tune_timeout 54b09cd163SJoonyoung Shim * - fixed problem with incomplete tune operations by own buffers 55b09cd163SJoonyoung Shim * - optimization of variables and printf types 56b09cd163SJoonyoung Shim * - improved error logging 57b09cd163SJoonyoung Shim * 2008-01-31 Tobias Lorenz <tobias.lorenz@gmx.net> 58b09cd163SJoonyoung Shim * Oliver Neukum <oliver@neukum.org> 59b09cd163SJoonyoung Shim * Version 1.0.6 60b09cd163SJoonyoung Shim * - fixed coverity checker warnings in *_usb_driver_disconnect 61b09cd163SJoonyoung Shim * - probe()/open() race by correct ordering in probe() 62b09cd163SJoonyoung Shim * - DMA coherency rules by separate allocation of all buffers 63b09cd163SJoonyoung Shim * - use of endianness macros 64b09cd163SJoonyoung Shim * - abuse of spinlock, replaced by mutex 65b09cd163SJoonyoung Shim * - racy handling of timer in disconnect, 66b09cd163SJoonyoung Shim * replaced by delayed_work 67b09cd163SJoonyoung Shim * - racy interruptible_sleep_on(), 68b09cd163SJoonyoung Shim * replaced with wait_event_interruptible() 69b09cd163SJoonyoung Shim * - handle signals in read() 70b09cd163SJoonyoung Shim * 2008-02-08 Tobias Lorenz <tobias.lorenz@gmx.net> 71b09cd163SJoonyoung Shim * Oliver Neukum <oliver@neukum.org> 72b09cd163SJoonyoung Shim * Version 1.0.7 73b09cd163SJoonyoung Shim * - usb autosuspend support 74b09cd163SJoonyoung Shim * - unplugging fixed 75b09cd163SJoonyoung Shim * 2008-05-07 Tobias Lorenz <tobias.lorenz@gmx.net> 76b09cd163SJoonyoung Shim * Version 1.0.8 77b09cd163SJoonyoung Shim * - hardware frequency seek support 78b09cd163SJoonyoung Shim * - afc indication 79b09cd163SJoonyoung Shim * - more safety checks, let si470x_get_freq return errno 80b09cd163SJoonyoung Shim * - vidioc behavior corrected according to v4l2 spec 81b09cd163SJoonyoung Shim * 2008-10-20 Alexey Klimov <klimov.linux@gmail.com> 82b09cd163SJoonyoung Shim * - add support for KWorld USB FM Radio FM700 83b09cd163SJoonyoung Shim * - blacklisted KWorld radio in hid-core.c and hid-ids.h 84b09cd163SJoonyoung Shim * 2008-12-03 Mark Lord <mlord@pobox.com> 85b09cd163SJoonyoung Shim * - add support for DealExtreme USB Radio 86b09cd163SJoonyoung Shim * 2009-01-31 Bob Ross <pigiron@gmx.com> 87b09cd163SJoonyoung Shim * - correction of stereo detection/setting 88b09cd163SJoonyoung Shim * - correction of signal strength indicator scaling 89b09cd163SJoonyoung Shim * 2009-01-31 Rick Bronson <rick@efn.org> 90b09cd163SJoonyoung Shim * Tobias Lorenz <tobias.lorenz@gmx.net> 91b09cd163SJoonyoung Shim * - add LED status output 92b09cd163SJoonyoung Shim * - get HW/SW version from scratchpad 93b09cd163SJoonyoung Shim * 2009-06-16 Edouard Lafargue <edouard@lafargue.name> 94b09cd163SJoonyoung Shim * Version 1.0.10 95b09cd163SJoonyoung Shim * - add support for interrupt mode for RDS endpoint, 96b09cd163SJoonyoung Shim * instead of polling. 97b09cd163SJoonyoung Shim * Improves RDS reception significantly 98b09cd163SJoonyoung Shim */ 99b09cd163SJoonyoung Shim 100b09cd163SJoonyoung Shim 101b09cd163SJoonyoung Shim /* kernel includes */ 102b09cd163SJoonyoung Shim #include "radio-si470x.h" 103b09cd163SJoonyoung Shim 104b09cd163SJoonyoung Shim /************************************************************************** 105b09cd163SJoonyoung Shim * Module Parameters 106b09cd163SJoonyoung Shim **************************************************************************/ 107b09cd163SJoonyoung Shim 108b09cd163SJoonyoung Shim /* Spacing (kHz) */ 109b09cd163SJoonyoung Shim /* 0: 200 kHz (USA, Australia) */ 110b09cd163SJoonyoung Shim /* 1: 100 kHz (Europe, Japan) */ 111b09cd163SJoonyoung Shim /* 2: 50 kHz */ 112b09cd163SJoonyoung Shim static unsigned short space = 2; 113b09cd163SJoonyoung Shim module_param(space, ushort, 0444); 114b09cd163SJoonyoung Shim MODULE_PARM_DESC(space, "Spacing: 0=200kHz 1=100kHz *2=50kHz*"); 115b09cd163SJoonyoung Shim 116b09cd163SJoonyoung Shim /* De-emphasis */ 117b09cd163SJoonyoung Shim /* 0: 75 us (USA) */ 118b09cd163SJoonyoung Shim /* 1: 50 us (Europe, Australia, Japan) */ 119b09cd163SJoonyoung Shim static unsigned short de = 1; 120b09cd163SJoonyoung Shim module_param(de, ushort, 0444); 121b09cd163SJoonyoung Shim MODULE_PARM_DESC(de, "De-emphasis: 0=75us *1=50us*"); 122b09cd163SJoonyoung Shim 123b09cd163SJoonyoung Shim /* Tune timeout */ 124b09cd163SJoonyoung Shim static unsigned int tune_timeout = 3000; 125b09cd163SJoonyoung Shim module_param(tune_timeout, uint, 0644); 126b09cd163SJoonyoung Shim MODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*"); 127b09cd163SJoonyoung Shim 128b09cd163SJoonyoung Shim /* Seek timeout */ 129b09cd163SJoonyoung Shim static unsigned int seek_timeout = 5000; 130b09cd163SJoonyoung Shim module_param(seek_timeout, uint, 0644); 131b09cd163SJoonyoung Shim MODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*"); 132b09cd163SJoonyoung Shim 133f140612dSHans de Goede static const struct v4l2_frequency_band bands[] = { 134f140612dSHans de Goede { 135f140612dSHans de Goede .type = V4L2_TUNER_RADIO, 136f140612dSHans de Goede .index = 0, 137f140612dSHans de Goede .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 138f140612dSHans de Goede V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 13932737810SHans Verkuil V4L2_TUNER_CAP_FREQ_BANDS | 140f140612dSHans de Goede V4L2_TUNER_CAP_HWSEEK_BOUNDED | 141f140612dSHans de Goede V4L2_TUNER_CAP_HWSEEK_WRAP, 142f140612dSHans de Goede .rangelow = 87500 * 16, 143f140612dSHans de Goede .rangehigh = 108000 * 16, 144f140612dSHans de Goede .modulation = V4L2_BAND_MODULATION_FM, 145f140612dSHans de Goede }, 146f140612dSHans de Goede { 147f140612dSHans de Goede .type = V4L2_TUNER_RADIO, 148f140612dSHans de Goede .index = 1, 149f140612dSHans de Goede .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 150f140612dSHans de Goede V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 15132737810SHans Verkuil V4L2_TUNER_CAP_FREQ_BANDS | 152f140612dSHans de Goede V4L2_TUNER_CAP_HWSEEK_BOUNDED | 153f140612dSHans de Goede V4L2_TUNER_CAP_HWSEEK_WRAP, 154f140612dSHans de Goede .rangelow = 76000 * 16, 155f140612dSHans de Goede .rangehigh = 108000 * 16, 156f140612dSHans de Goede .modulation = V4L2_BAND_MODULATION_FM, 157f140612dSHans de Goede }, 158f140612dSHans de Goede { 159f140612dSHans de Goede .type = V4L2_TUNER_RADIO, 160f140612dSHans de Goede .index = 2, 161f140612dSHans de Goede .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 162f140612dSHans de Goede V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 16332737810SHans Verkuil V4L2_TUNER_CAP_FREQ_BANDS | 164f140612dSHans de Goede V4L2_TUNER_CAP_HWSEEK_BOUNDED | 165f140612dSHans de Goede V4L2_TUNER_CAP_HWSEEK_WRAP, 166f140612dSHans de Goede .rangelow = 76000 * 16, 167f140612dSHans de Goede .rangehigh = 90000 * 16, 168f140612dSHans de Goede .modulation = V4L2_BAND_MODULATION_FM, 169f140612dSHans de Goede }, 170f140612dSHans de Goede }; 171b09cd163SJoonyoung Shim 172b09cd163SJoonyoung Shim /************************************************************************** 173b09cd163SJoonyoung Shim * Generic Functions 174b09cd163SJoonyoung Shim **************************************************************************/ 175b09cd163SJoonyoung Shim 176b09cd163SJoonyoung Shim /* 177f140612dSHans de Goede * si470x_set_band - set the band 178f140612dSHans de Goede */ 179f140612dSHans de Goede static int si470x_set_band(struct si470x_device *radio, int band) 180f140612dSHans de Goede { 181f140612dSHans de Goede if (radio->band == band) 182f140612dSHans de Goede return 0; 183f140612dSHans de Goede 184f140612dSHans de Goede radio->band = band; 185f140612dSHans de Goede radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_BAND; 186f140612dSHans de Goede radio->registers[SYSCONFIG2] |= radio->band << 6; 18758757984SMauro Carvalho Chehab return radio->set_register(radio, SYSCONFIG2); 188f140612dSHans de Goede } 189f140612dSHans de Goede 190f140612dSHans de Goede /* 191b09cd163SJoonyoung Shim * si470x_set_chan - set the channel 192b09cd163SJoonyoung Shim */ 193b09cd163SJoonyoung Shim static int si470x_set_chan(struct si470x_device *radio, unsigned short chan) 194b09cd163SJoonyoung Shim { 195b09cd163SJoonyoung Shim int retval; 19661765a50SNicholas Mc Guire unsigned long time_left; 1978b4b6818SMauro Carvalho Chehab bool timed_out = false; 198b09cd163SJoonyoung Shim 19958757984SMauro Carvalho Chehab retval = radio->get_register(radio, POWERCFG); 20036d6bf89SDouglas Fischer if (retval) 20136d6bf89SDouglas Fischer return retval; 20236d6bf89SDouglas Fischer 20336d6bf89SDouglas Fischer if ((radio->registers[POWERCFG] & (POWERCFG_ENABLE|POWERCFG_DMUTE)) 20436d6bf89SDouglas Fischer != (POWERCFG_ENABLE|POWERCFG_DMUTE)) { 20536d6bf89SDouglas Fischer return 0; 20636d6bf89SDouglas Fischer } 20736d6bf89SDouglas Fischer 208b09cd163SJoonyoung Shim /* start tuning */ 209b09cd163SJoonyoung Shim radio->registers[CHANNEL] &= ~CHANNEL_CHAN; 210b09cd163SJoonyoung Shim radio->registers[CHANNEL] |= CHANNEL_TUNE | chan; 21158757984SMauro Carvalho Chehab retval = radio->set_register(radio, CHANNEL); 212b09cd163SJoonyoung Shim if (retval < 0) 213b09cd163SJoonyoung Shim goto done; 214b09cd163SJoonyoung Shim 2150830be3fSJoonyoung Shim /* wait till tune operation has completed */ 21616735d02SWolfram Sang reinit_completion(&radio->completion); 21761765a50SNicholas Mc Guire time_left = wait_for_completion_timeout(&radio->completion, 2180830be3fSJoonyoung Shim msecs_to_jiffies(tune_timeout)); 21961765a50SNicholas Mc Guire if (time_left == 0) 2200830be3fSJoonyoung Shim timed_out = true; 2210830be3fSJoonyoung Shim 222b09cd163SJoonyoung Shim if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) 2234967d53dSHans Verkuil dev_warn(&radio->videodev.dev, "tune does not complete\n"); 224b09cd163SJoonyoung Shim if (timed_out) 2254967d53dSHans Verkuil dev_warn(&radio->videodev.dev, 226a9d6fd5eSJoonyoung Shim "tune timed out after %u ms\n", tune_timeout); 227b09cd163SJoonyoung Shim 228b09cd163SJoonyoung Shim /* stop tuning */ 229b09cd163SJoonyoung Shim radio->registers[CHANNEL] &= ~CHANNEL_TUNE; 23058757984SMauro Carvalho Chehab retval = radio->set_register(radio, CHANNEL); 231b09cd163SJoonyoung Shim 232b09cd163SJoonyoung Shim done: 233b09cd163SJoonyoung Shim return retval; 234b09cd163SJoonyoung Shim } 235b09cd163SJoonyoung Shim 236f140612dSHans de Goede /* 237f140612dSHans de Goede * si470x_get_step - get channel spacing 238f140612dSHans de Goede */ 239f140612dSHans de Goede static unsigned int si470x_get_step(struct si470x_device *radio) 240f140612dSHans de Goede { 241f140612dSHans de Goede /* Spacing (kHz) */ 242f140612dSHans de Goede switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) { 243f140612dSHans de Goede /* 0: 200 kHz (USA, Australia) */ 244f140612dSHans de Goede case 0: 245f140612dSHans de Goede return 200 * 16; 246f140612dSHans de Goede /* 1: 100 kHz (Europe, Japan) */ 247f140612dSHans de Goede case 1: 248f140612dSHans de Goede return 100 * 16; 249f140612dSHans de Goede /* 2: 50 kHz */ 250f140612dSHans de Goede default: 251f140612dSHans de Goede return 50 * 16; 2522028c71dSJoe Perches } 253f140612dSHans de Goede } 254f140612dSHans de Goede 255b09cd163SJoonyoung Shim 256b09cd163SJoonyoung Shim /* 257b09cd163SJoonyoung Shim * si470x_get_freq - get the frequency 258b09cd163SJoonyoung Shim */ 259b09cd163SJoonyoung Shim static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq) 260b09cd163SJoonyoung Shim { 261f140612dSHans de Goede int chan, retval; 262b09cd163SJoonyoung Shim 263b09cd163SJoonyoung Shim /* read channel */ 26458757984SMauro Carvalho Chehab retval = radio->get_register(radio, READCHAN); 265b09cd163SJoonyoung Shim chan = radio->registers[READCHAN] & READCHAN_READCHAN; 266b09cd163SJoonyoung Shim 267b09cd163SJoonyoung Shim /* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */ 268f140612dSHans de Goede *freq = chan * si470x_get_step(radio) + bands[radio->band].rangelow; 269b09cd163SJoonyoung Shim 270b09cd163SJoonyoung Shim return retval; 271b09cd163SJoonyoung Shim } 272b09cd163SJoonyoung Shim 273b09cd163SJoonyoung Shim 274b09cd163SJoonyoung Shim /* 275b09cd163SJoonyoung Shim * si470x_set_freq - set the frequency 276b09cd163SJoonyoung Shim */ 277b09cd163SJoonyoung Shim int si470x_set_freq(struct si470x_device *radio, unsigned int freq) 278b09cd163SJoonyoung Shim { 279b09cd163SJoonyoung Shim unsigned short chan; 280b09cd163SJoonyoung Shim 281f140612dSHans de Goede freq = clamp(freq, bands[radio->band].rangelow, 282f140612dSHans de Goede bands[radio->band].rangehigh); 283b09cd163SJoonyoung Shim /* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */ 284f140612dSHans de Goede chan = (freq - bands[radio->band].rangelow) / si470x_get_step(radio); 285b09cd163SJoonyoung Shim 286b09cd163SJoonyoung Shim return si470x_set_chan(radio, chan); 287b09cd163SJoonyoung Shim } 28858757984SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(si470x_set_freq); 289b09cd163SJoonyoung Shim 290b09cd163SJoonyoung Shim 291b09cd163SJoonyoung Shim /* 292b09cd163SJoonyoung Shim * si470x_set_seek - set seek 293b09cd163SJoonyoung Shim */ 294b09cd163SJoonyoung Shim static int si470x_set_seek(struct si470x_device *radio, 295ec6f4328SHans Verkuil const struct v4l2_hw_freq_seek *seek) 296b09cd163SJoonyoung Shim { 297f140612dSHans de Goede int band, retval; 298f140612dSHans de Goede unsigned int freq; 2998b4b6818SMauro Carvalho Chehab bool timed_out = false; 30061765a50SNicholas Mc Guire unsigned long time_left; 301b09cd163SJoonyoung Shim 302f140612dSHans de Goede /* set band */ 303f140612dSHans de Goede if (seek->rangelow || seek->rangehigh) { 304f140612dSHans de Goede for (band = 0; band < ARRAY_SIZE(bands); band++) { 305f140612dSHans de Goede if (bands[band].rangelow == seek->rangelow && 306f140612dSHans de Goede bands[band].rangehigh == seek->rangehigh) 307f140612dSHans de Goede break; 308f140612dSHans de Goede } 309f140612dSHans de Goede if (band == ARRAY_SIZE(bands)) 310f140612dSHans de Goede return -EINVAL; /* No matching band found */ 311f140612dSHans de Goede } else 312f140612dSHans de Goede band = 1; /* If nothing is specified seek 76 - 108 Mhz */ 313f140612dSHans de Goede 314f140612dSHans de Goede if (radio->band != band) { 315f140612dSHans de Goede retval = si470x_get_freq(radio, &freq); 316f140612dSHans de Goede if (retval) 317f140612dSHans de Goede return retval; 318f140612dSHans de Goede retval = si470x_set_band(radio, band); 319f140612dSHans de Goede if (retval) 320f140612dSHans de Goede return retval; 321f140612dSHans de Goede retval = si470x_set_freq(radio, freq); 322f140612dSHans de Goede if (retval) 323f140612dSHans de Goede return retval; 324f140612dSHans de Goede } 325f140612dSHans de Goede 326b09cd163SJoonyoung Shim /* start seeking */ 327b09cd163SJoonyoung Shim radio->registers[POWERCFG] |= POWERCFG_SEEK; 328f140612dSHans de Goede if (seek->wrap_around) 329b09cd163SJoonyoung Shim radio->registers[POWERCFG] &= ~POWERCFG_SKMODE; 330b09cd163SJoonyoung Shim else 331b09cd163SJoonyoung Shim radio->registers[POWERCFG] |= POWERCFG_SKMODE; 332f140612dSHans de Goede if (seek->seek_upward) 333b09cd163SJoonyoung Shim radio->registers[POWERCFG] |= POWERCFG_SEEKUP; 334b09cd163SJoonyoung Shim else 335b09cd163SJoonyoung Shim radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP; 33658757984SMauro Carvalho Chehab retval = radio->set_register(radio, POWERCFG); 337b09cd163SJoonyoung Shim if (retval < 0) 338340bd4c1SHans Verkuil return retval; 339b09cd163SJoonyoung Shim 34077947111SHans de Goede /* wait till tune operation has completed */ 34116735d02SWolfram Sang reinit_completion(&radio->completion); 34261765a50SNicholas Mc Guire time_left = wait_for_completion_timeout(&radio->completion, 3430830be3fSJoonyoung Shim msecs_to_jiffies(seek_timeout)); 34461765a50SNicholas Mc Guire if (time_left == 0) 3450830be3fSJoonyoung Shim timed_out = true; 3460830be3fSJoonyoung Shim 347b09cd163SJoonyoung Shim if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) 3484967d53dSHans Verkuil dev_warn(&radio->videodev.dev, "seek does not complete\n"); 349b09cd163SJoonyoung Shim if (radio->registers[STATUSRSSI] & STATUSRSSI_SF) 3504967d53dSHans Verkuil dev_warn(&radio->videodev.dev, 351a9d6fd5eSJoonyoung Shim "seek failed / band limit reached\n"); 352b09cd163SJoonyoung Shim 353b09cd163SJoonyoung Shim /* stop seeking */ 354b09cd163SJoonyoung Shim radio->registers[POWERCFG] &= ~POWERCFG_SEEK; 35558757984SMauro Carvalho Chehab retval = radio->set_register(radio, POWERCFG); 356b09cd163SJoonyoung Shim 357b09cd163SJoonyoung Shim /* try again, if timed out */ 358340bd4c1SHans Verkuil if (retval == 0 && timed_out) 35954f6019bSHans Verkuil return -ENODATA; 360b09cd163SJoonyoung Shim return retval; 361b09cd163SJoonyoung Shim } 362b09cd163SJoonyoung Shim 363b09cd163SJoonyoung Shim 364b09cd163SJoonyoung Shim /* 365b09cd163SJoonyoung Shim * si470x_start - switch on radio 366b09cd163SJoonyoung Shim */ 367b09cd163SJoonyoung Shim int si470x_start(struct si470x_device *radio) 368b09cd163SJoonyoung Shim { 369b09cd163SJoonyoung Shim int retval; 370b09cd163SJoonyoung Shim 371b09cd163SJoonyoung Shim /* powercfg */ 372b09cd163SJoonyoung Shim radio->registers[POWERCFG] = 373b09cd163SJoonyoung Shim POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM; 37458757984SMauro Carvalho Chehab retval = radio->set_register(radio, POWERCFG); 375b09cd163SJoonyoung Shim if (retval < 0) 376b09cd163SJoonyoung Shim goto done; 377b09cd163SJoonyoung Shim 378b09cd163SJoonyoung Shim /* sysconfig 1 */ 379dd328275SDouglas Fischer radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDSIEN | SYSCONFIG1_STCIEN | 380dd328275SDouglas Fischer SYSCONFIG1_RDS; 381dd328275SDouglas Fischer radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_GPIO2; 382dd328275SDouglas Fischer radio->registers[SYSCONFIG1] |= SYSCONFIG1_GPIO2_INT; 383dd328275SDouglas Fischer if (de) 384dd328275SDouglas Fischer radio->registers[SYSCONFIG1] |= SYSCONFIG1_DE; 38558757984SMauro Carvalho Chehab retval = radio->set_register(radio, SYSCONFIG1); 386b09cd163SJoonyoung Shim if (retval < 0) 387b09cd163SJoonyoung Shim goto done; 388b09cd163SJoonyoung Shim 389b09cd163SJoonyoung Shim /* sysconfig 2 */ 390b09cd163SJoonyoung Shim radio->registers[SYSCONFIG2] = 391b9664259SHans de Goede (0x1f << 8) | /* SEEKTH */ 392f140612dSHans de Goede ((radio->band << 6) & SYSCONFIG2_BAND) |/* BAND */ 393b09cd163SJoonyoung Shim ((space << 4) & SYSCONFIG2_SPACE) | /* SPACE */ 394b09cd163SJoonyoung Shim 15; /* VOLUME (max) */ 39558757984SMauro Carvalho Chehab retval = radio->set_register(radio, SYSCONFIG2); 396b09cd163SJoonyoung Shim if (retval < 0) 397b09cd163SJoonyoung Shim goto done; 398b09cd163SJoonyoung Shim 399b09cd163SJoonyoung Shim /* reset last channel */ 400b09cd163SJoonyoung Shim retval = si470x_set_chan(radio, 401b09cd163SJoonyoung Shim radio->registers[CHANNEL] & CHANNEL_CHAN); 402b09cd163SJoonyoung Shim 403b09cd163SJoonyoung Shim done: 404b09cd163SJoonyoung Shim return retval; 405b09cd163SJoonyoung Shim } 40658757984SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(si470x_start); 407b09cd163SJoonyoung Shim 408b09cd163SJoonyoung Shim 409b09cd163SJoonyoung Shim /* 410b09cd163SJoonyoung Shim * si470x_stop - switch off radio 411b09cd163SJoonyoung Shim */ 412b09cd163SJoonyoung Shim int si470x_stop(struct si470x_device *radio) 413b09cd163SJoonyoung Shim { 414b09cd163SJoonyoung Shim int retval; 415b09cd163SJoonyoung Shim 416b09cd163SJoonyoung Shim /* sysconfig 1 */ 417b09cd163SJoonyoung Shim radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS; 41858757984SMauro Carvalho Chehab retval = radio->set_register(radio, SYSCONFIG1); 419b09cd163SJoonyoung Shim if (retval < 0) 420b09cd163SJoonyoung Shim goto done; 421b09cd163SJoonyoung Shim 422b09cd163SJoonyoung Shim /* powercfg */ 423b09cd163SJoonyoung Shim radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; 424b09cd163SJoonyoung Shim /* POWERCFG_ENABLE has to automatically go low */ 425b09cd163SJoonyoung Shim radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE; 42658757984SMauro Carvalho Chehab retval = radio->set_register(radio, POWERCFG); 427b09cd163SJoonyoung Shim 428b09cd163SJoonyoung Shim done: 429b09cd163SJoonyoung Shim return retval; 430b09cd163SJoonyoung Shim } 43158757984SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(si470x_stop); 432b09cd163SJoonyoung Shim 433b09cd163SJoonyoung Shim 434b09cd163SJoonyoung Shim /* 435b09cd163SJoonyoung Shim * si470x_rds_on - switch on rds reception 436b09cd163SJoonyoung Shim */ 437f2f8e850SMauro Carvalho Chehab static int si470x_rds_on(struct si470x_device *radio) 438b09cd163SJoonyoung Shim { 439b09cd163SJoonyoung Shim int retval; 440b09cd163SJoonyoung Shim 441b09cd163SJoonyoung Shim /* sysconfig 1 */ 442b09cd163SJoonyoung Shim radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS; 44358757984SMauro Carvalho Chehab retval = radio->set_register(radio, SYSCONFIG1); 444b09cd163SJoonyoung Shim if (retval < 0) 445b09cd163SJoonyoung Shim radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS; 446b09cd163SJoonyoung Shim 447b09cd163SJoonyoung Shim return retval; 448b09cd163SJoonyoung Shim } 449b09cd163SJoonyoung Shim 450b09cd163SJoonyoung Shim 451b09cd163SJoonyoung Shim 452b09cd163SJoonyoung Shim /************************************************************************** 4531aa925c9SJoonyoung Shim * File Operations Interface 4541aa925c9SJoonyoung Shim **************************************************************************/ 4551aa925c9SJoonyoung Shim 4561aa925c9SJoonyoung Shim /* 4571aa925c9SJoonyoung Shim * si470x_fops_read - read RDS data 4581aa925c9SJoonyoung Shim */ 4591aa925c9SJoonyoung Shim static ssize_t si470x_fops_read(struct file *file, char __user *buf, 4601aa925c9SJoonyoung Shim size_t count, loff_t *ppos) 4611aa925c9SJoonyoung Shim { 4621aa925c9SJoonyoung Shim struct si470x_device *radio = video_drvdata(file); 4631aa925c9SJoonyoung Shim int retval = 0; 4641aa925c9SJoonyoung Shim unsigned int block_count = 0; 4651aa925c9SJoonyoung Shim 4661aa925c9SJoonyoung Shim /* switch on rds reception */ 4671aa925c9SJoonyoung Shim if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) 4681aa925c9SJoonyoung Shim si470x_rds_on(radio); 4691aa925c9SJoonyoung Shim 4701aa925c9SJoonyoung Shim /* block if no new data available */ 4711aa925c9SJoonyoung Shim while (radio->wr_index == radio->rd_index) { 4721aa925c9SJoonyoung Shim if (file->f_flags & O_NONBLOCK) { 4731aa925c9SJoonyoung Shim retval = -EWOULDBLOCK; 4741aa925c9SJoonyoung Shim goto done; 4751aa925c9SJoonyoung Shim } 4761aa925c9SJoonyoung Shim if (wait_event_interruptible(radio->read_queue, 4771aa925c9SJoonyoung Shim radio->wr_index != radio->rd_index) < 0) { 4781aa925c9SJoonyoung Shim retval = -EINTR; 4791aa925c9SJoonyoung Shim goto done; 4801aa925c9SJoonyoung Shim } 4811aa925c9SJoonyoung Shim } 4821aa925c9SJoonyoung Shim 4831aa925c9SJoonyoung Shim /* calculate block count from byte count */ 4841aa925c9SJoonyoung Shim count /= 3; 4851aa925c9SJoonyoung Shim 4861aa925c9SJoonyoung Shim /* copy RDS block out of internal buffer and to user buffer */ 4871aa925c9SJoonyoung Shim while (block_count < count) { 4881aa925c9SJoonyoung Shim if (radio->rd_index == radio->wr_index) 4891aa925c9SJoonyoung Shim break; 4901aa925c9SJoonyoung Shim 4911aa925c9SJoonyoung Shim /* always transfer rds complete blocks */ 4921aa925c9SJoonyoung Shim if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3)) 4931aa925c9SJoonyoung Shim /* retval = -EFAULT; */ 4941aa925c9SJoonyoung Shim break; 4951aa925c9SJoonyoung Shim 4961aa925c9SJoonyoung Shim /* increment and wrap read pointer */ 4971aa925c9SJoonyoung Shim radio->rd_index += 3; 4981aa925c9SJoonyoung Shim if (radio->rd_index >= radio->buf_size) 4991aa925c9SJoonyoung Shim radio->rd_index = 0; 5001aa925c9SJoonyoung Shim 5011aa925c9SJoonyoung Shim /* increment counters */ 5021aa925c9SJoonyoung Shim block_count++; 5031aa925c9SJoonyoung Shim buf += 3; 5041aa925c9SJoonyoung Shim retval += 3; 5051aa925c9SJoonyoung Shim } 5061aa925c9SJoonyoung Shim 5071aa925c9SJoonyoung Shim done: 5081aa925c9SJoonyoung Shim return retval; 5091aa925c9SJoonyoung Shim } 5101aa925c9SJoonyoung Shim 5111aa925c9SJoonyoung Shim 5121aa925c9SJoonyoung Shim /* 5131aa925c9SJoonyoung Shim * si470x_fops_poll - poll RDS data 5141aa925c9SJoonyoung Shim */ 515c23e0cb8SAl Viro static __poll_t si470x_fops_poll(struct file *file, 5161aa925c9SJoonyoung Shim struct poll_table_struct *pts) 5171aa925c9SJoonyoung Shim { 5181aa925c9SJoonyoung Shim struct si470x_device *radio = video_drvdata(file); 51901699437SAl Viro __poll_t req_events = poll_requested_events(pts); 520c23e0cb8SAl Viro __poll_t retval = v4l2_ctrl_poll(file, pts); 5211aa925c9SJoonyoung Shim 522a9a08845SLinus Torvalds if (req_events & (EPOLLIN | EPOLLRDNORM)) { 5231aa925c9SJoonyoung Shim /* switch on rds reception */ 5241aa925c9SJoonyoung Shim if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) 5251aa925c9SJoonyoung Shim si470x_rds_on(radio); 5261aa925c9SJoonyoung Shim 5271aa925c9SJoonyoung Shim poll_wait(file, &radio->read_queue, pts); 5281aa925c9SJoonyoung Shim 5291aa925c9SJoonyoung Shim if (radio->rd_index != radio->wr_index) 530a9a08845SLinus Torvalds retval |= EPOLLIN | EPOLLRDNORM; 531eae63ae0SHans Verkuil } 5321aa925c9SJoonyoung Shim 5331aa925c9SJoonyoung Shim return retval; 5341aa925c9SJoonyoung Shim } 5351aa925c9SJoonyoung Shim 5361aa925c9SJoonyoung Shim 53758757984SMauro Carvalho Chehab static int si470x_fops_open(struct file *file) 53858757984SMauro Carvalho Chehab { 53958757984SMauro Carvalho Chehab struct si470x_device *radio = video_drvdata(file); 54058757984SMauro Carvalho Chehab 54158757984SMauro Carvalho Chehab return radio->fops_open(file); 54258757984SMauro Carvalho Chehab } 54358757984SMauro Carvalho Chehab 54458757984SMauro Carvalho Chehab 54558757984SMauro Carvalho Chehab /* 54658757984SMauro Carvalho Chehab * si470x_fops_release - file release 54758757984SMauro Carvalho Chehab */ 54858757984SMauro Carvalho Chehab static int si470x_fops_release(struct file *file) 54958757984SMauro Carvalho Chehab { 55058757984SMauro Carvalho Chehab struct si470x_device *radio = video_drvdata(file); 55158757984SMauro Carvalho Chehab 55258757984SMauro Carvalho Chehab return radio->fops_release(file); 55358757984SMauro Carvalho Chehab } 55458757984SMauro Carvalho Chehab 55558757984SMauro Carvalho Chehab 5561aa925c9SJoonyoung Shim /* 5571aa925c9SJoonyoung Shim * si470x_fops - file operations interface 5581aa925c9SJoonyoung Shim */ 5591aa925c9SJoonyoung Shim static const struct v4l2_file_operations si470x_fops = { 5601aa925c9SJoonyoung Shim .owner = THIS_MODULE, 5611aa925c9SJoonyoung Shim .read = si470x_fops_read, 5621aa925c9SJoonyoung Shim .poll = si470x_fops_poll, 563f2f8e850SMauro Carvalho Chehab .unlocked_ioctl = video_ioctl2, 5641aa925c9SJoonyoung Shim .open = si470x_fops_open, 5651aa925c9SJoonyoung Shim .release = si470x_fops_release, 5661aa925c9SJoonyoung Shim }; 5671aa925c9SJoonyoung Shim 5681aa925c9SJoonyoung Shim 5691aa925c9SJoonyoung Shim 5701aa925c9SJoonyoung Shim /************************************************************************** 571b09cd163SJoonyoung Shim * Video4Linux Interface 572b09cd163SJoonyoung Shim **************************************************************************/ 573b09cd163SJoonyoung Shim 5744967d53dSHans Verkuil 5754967d53dSHans Verkuil static int si470x_s_ctrl(struct v4l2_ctrl *ctrl) 576b09cd163SJoonyoung Shim { 5774967d53dSHans Verkuil struct si470x_device *radio = 5784967d53dSHans Verkuil container_of(ctrl->handler, struct si470x_device, hdl); 579b09cd163SJoonyoung Shim 580b09cd163SJoonyoung Shim switch (ctrl->id) { 581b09cd163SJoonyoung Shim case V4L2_CID_AUDIO_VOLUME: 582b09cd163SJoonyoung Shim radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME; 5834967d53dSHans Verkuil radio->registers[SYSCONFIG2] |= ctrl->val; 58458757984SMauro Carvalho Chehab return radio->set_register(radio, SYSCONFIG2); 585b09cd163SJoonyoung Shim case V4L2_CID_AUDIO_MUTE: 5864967d53dSHans Verkuil if (ctrl->val) 587b09cd163SJoonyoung Shim radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; 588b09cd163SJoonyoung Shim else 589b09cd163SJoonyoung Shim radio->registers[POWERCFG] |= POWERCFG_DMUTE; 59058757984SMauro Carvalho Chehab return radio->set_register(radio, POWERCFG); 591b09cd163SJoonyoung Shim default: 5924967d53dSHans Verkuil return -EINVAL; 593b09cd163SJoonyoung Shim } 594b09cd163SJoonyoung Shim } 595b09cd163SJoonyoung Shim 596b09cd163SJoonyoung Shim 597b09cd163SJoonyoung Shim /* 598b09cd163SJoonyoung Shim * si470x_vidioc_g_tuner - get tuner attributes 599b09cd163SJoonyoung Shim */ 600b09cd163SJoonyoung Shim static int si470x_vidioc_g_tuner(struct file *file, void *priv, 601b09cd163SJoonyoung Shim struct v4l2_tuner *tuner) 602b09cd163SJoonyoung Shim { 603b09cd163SJoonyoung Shim struct si470x_device *radio = video_drvdata(file); 60486ef3f78SHans de Goede int retval = 0; 605b09cd163SJoonyoung Shim 606340bd4c1SHans Verkuil if (tuner->index != 0) 607340bd4c1SHans Verkuil return -EINVAL; 608b09cd163SJoonyoung Shim 60986ef3f78SHans de Goede if (!radio->status_rssi_auto_update) { 61058757984SMauro Carvalho Chehab retval = radio->get_register(radio, STATUSRSSI); 611b09cd163SJoonyoung Shim if (retval < 0) 612340bd4c1SHans Verkuil return retval; 61386ef3f78SHans de Goede } 614b09cd163SJoonyoung Shim 615b09cd163SJoonyoung Shim /* driver constants */ 616cc1e6315SMauro Carvalho Chehab strscpy(tuner->name, "FM", sizeof(tuner->name)); 617b09cd163SJoonyoung Shim tuner->type = V4L2_TUNER_RADIO; 618b09cd163SJoonyoung Shim tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 61954f6019bSHans Verkuil V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 62054f6019bSHans Verkuil V4L2_TUNER_CAP_HWSEEK_BOUNDED | 62154f6019bSHans Verkuil V4L2_TUNER_CAP_HWSEEK_WRAP; 622b09cd163SJoonyoung Shim tuner->rangelow = 76 * FREQ_MUL; 623b09cd163SJoonyoung Shim tuner->rangehigh = 108 * FREQ_MUL; 624b09cd163SJoonyoung Shim 625b09cd163SJoonyoung Shim /* stereo indicator == stereo (instead of mono) */ 626b09cd163SJoonyoung Shim if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0) 627b09cd163SJoonyoung Shim tuner->rxsubchans = V4L2_TUNER_SUB_MONO; 628b09cd163SJoonyoung Shim else 6294967d53dSHans Verkuil tuner->rxsubchans = V4L2_TUNER_SUB_STEREO; 630b09cd163SJoonyoung Shim /* If there is a reliable method of detecting an RDS channel, 631b09cd163SJoonyoung Shim then this code should check for that before setting this 632b09cd163SJoonyoung Shim RDS subchannel. */ 633b09cd163SJoonyoung Shim tuner->rxsubchans |= V4L2_TUNER_SUB_RDS; 634b09cd163SJoonyoung Shim 635b09cd163SJoonyoung Shim /* mono/stereo selector */ 636b09cd163SJoonyoung Shim if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 0) 637b09cd163SJoonyoung Shim tuner->audmode = V4L2_TUNER_MODE_STEREO; 638b09cd163SJoonyoung Shim else 639b09cd163SJoonyoung Shim tuner->audmode = V4L2_TUNER_MODE_MONO; 640b09cd163SJoonyoung Shim 641b09cd163SJoonyoung Shim /* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */ 642144dcdceSTobias Lorenz /* measured in units of dbµV in 1 db increments (max at ~75 dbµV) */ 643b09cd163SJoonyoung Shim tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI); 644b09cd163SJoonyoung Shim /* the ideal factor is 0xffff/75 = 873,8 */ 645b09cd163SJoonyoung Shim tuner->signal = (tuner->signal * 873) + (8 * tuner->signal / 10); 646eae63ae0SHans Verkuil if (tuner->signal > 0xffff) 647eae63ae0SHans Verkuil tuner->signal = 0xffff; 648b09cd163SJoonyoung Shim 649b09cd163SJoonyoung Shim /* automatic frequency control: -1: freq to low, 1 freq to high */ 650b09cd163SJoonyoung Shim /* AFCRL does only indicate that freq. differs, not if too low/high */ 651b09cd163SJoonyoung Shim tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0; 652b09cd163SJoonyoung Shim 653b09cd163SJoonyoung Shim return retval; 654b09cd163SJoonyoung Shim } 655b09cd163SJoonyoung Shim 656b09cd163SJoonyoung Shim 657b09cd163SJoonyoung Shim /* 658b09cd163SJoonyoung Shim * si470x_vidioc_s_tuner - set tuner attributes 659b09cd163SJoonyoung Shim */ 660b09cd163SJoonyoung Shim static int si470x_vidioc_s_tuner(struct file *file, void *priv, 6612f73c7c5SHans Verkuil const struct v4l2_tuner *tuner) 662b09cd163SJoonyoung Shim { 663b09cd163SJoonyoung Shim struct si470x_device *radio = video_drvdata(file); 664b09cd163SJoonyoung Shim 665b09cd163SJoonyoung Shim if (tuner->index != 0) 666eae63ae0SHans Verkuil return -EINVAL; 667b09cd163SJoonyoung Shim 668b09cd163SJoonyoung Shim /* mono/stereo selector */ 669b09cd163SJoonyoung Shim switch (tuner->audmode) { 670b09cd163SJoonyoung Shim case V4L2_TUNER_MODE_MONO: 671b09cd163SJoonyoung Shim radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */ 672b09cd163SJoonyoung Shim break; 673b09cd163SJoonyoung Shim case V4L2_TUNER_MODE_STEREO: 674eae63ae0SHans Verkuil default: 675b09cd163SJoonyoung Shim radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */ 676b09cd163SJoonyoung Shim break; 677b09cd163SJoonyoung Shim } 678b09cd163SJoonyoung Shim 67958757984SMauro Carvalho Chehab return radio->set_register(radio, POWERCFG); 680b09cd163SJoonyoung Shim } 681b09cd163SJoonyoung Shim 682b09cd163SJoonyoung Shim 683b09cd163SJoonyoung Shim /* 684b09cd163SJoonyoung Shim * si470x_vidioc_g_frequency - get tuner or modulator radio frequency 685b09cd163SJoonyoung Shim */ 686b09cd163SJoonyoung Shim static int si470x_vidioc_g_frequency(struct file *file, void *priv, 687b09cd163SJoonyoung Shim struct v4l2_frequency *freq) 688b09cd163SJoonyoung Shim { 689b09cd163SJoonyoung Shim struct si470x_device *radio = video_drvdata(file); 690b09cd163SJoonyoung Shim 691340bd4c1SHans Verkuil if (freq->tuner != 0) 692340bd4c1SHans Verkuil return -EINVAL; 693b09cd163SJoonyoung Shim 694b09cd163SJoonyoung Shim freq->type = V4L2_TUNER_RADIO; 695340bd4c1SHans Verkuil return si470x_get_freq(radio, &freq->frequency); 696b09cd163SJoonyoung Shim } 697b09cd163SJoonyoung Shim 698b09cd163SJoonyoung Shim 699b09cd163SJoonyoung Shim /* 700b09cd163SJoonyoung Shim * si470x_vidioc_s_frequency - set tuner or modulator radio frequency 701b09cd163SJoonyoung Shim */ 702b09cd163SJoonyoung Shim static int si470x_vidioc_s_frequency(struct file *file, void *priv, 703b530a447SHans Verkuil const struct v4l2_frequency *freq) 704b09cd163SJoonyoung Shim { 705b09cd163SJoonyoung Shim struct si470x_device *radio = video_drvdata(file); 706f140612dSHans de Goede int retval; 707b09cd163SJoonyoung Shim 708340bd4c1SHans Verkuil if (freq->tuner != 0) 709340bd4c1SHans Verkuil return -EINVAL; 710b09cd163SJoonyoung Shim 711f140612dSHans de Goede if (freq->frequency < bands[radio->band].rangelow || 712f140612dSHans de Goede freq->frequency > bands[radio->band].rangehigh) { 713f140612dSHans de Goede /* Switch to band 1 which covers everything we support */ 714f140612dSHans de Goede retval = si470x_set_band(radio, 1); 715f140612dSHans de Goede if (retval) 716f140612dSHans de Goede return retval; 717f140612dSHans de Goede } 718340bd4c1SHans Verkuil return si470x_set_freq(radio, freq->frequency); 719b09cd163SJoonyoung Shim } 720b09cd163SJoonyoung Shim 721b09cd163SJoonyoung Shim 722b09cd163SJoonyoung Shim /* 723b09cd163SJoonyoung Shim * si470x_vidioc_s_hw_freq_seek - set hardware frequency seek 724b09cd163SJoonyoung Shim */ 725b09cd163SJoonyoung Shim static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv, 726ec6f4328SHans Verkuil const struct v4l2_hw_freq_seek *seek) 727b09cd163SJoonyoung Shim { 728b09cd163SJoonyoung Shim struct si470x_device *radio = video_drvdata(file); 729b09cd163SJoonyoung Shim 730340bd4c1SHans Verkuil if (seek->tuner != 0) 731340bd4c1SHans Verkuil return -EINVAL; 732b09cd163SJoonyoung Shim 733617ade61SHans Verkuil if (file->f_flags & O_NONBLOCK) 734617ade61SHans Verkuil return -EWOULDBLOCK; 735617ade61SHans Verkuil 736f140612dSHans de Goede return si470x_set_seek(radio, seek); 737f140612dSHans de Goede } 738f140612dSHans de Goede 739f140612dSHans de Goede /* 740f140612dSHans de Goede * si470x_vidioc_enum_freq_bands - enumerate supported bands 741f140612dSHans de Goede */ 742f140612dSHans de Goede static int si470x_vidioc_enum_freq_bands(struct file *file, void *priv, 743f140612dSHans de Goede struct v4l2_frequency_band *band) 744f140612dSHans de Goede { 745f140612dSHans de Goede if (band->tuner != 0) 746f140612dSHans de Goede return -EINVAL; 747f140612dSHans de Goede if (band->index >= ARRAY_SIZE(bands)) 748f140612dSHans de Goede return -EINVAL; 749f140612dSHans de Goede *band = bands[band->index]; 750f140612dSHans de Goede return 0; 751b09cd163SJoonyoung Shim } 752b09cd163SJoonyoung Shim 7534967d53dSHans Verkuil const struct v4l2_ctrl_ops si470x_ctrl_ops = { 7544967d53dSHans Verkuil .s_ctrl = si470x_s_ctrl, 7554967d53dSHans Verkuil }; 75658757984SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(si470x_ctrl_ops); 75758757984SMauro Carvalho Chehab 75858757984SMauro Carvalho Chehab static int si470x_vidioc_querycap(struct file *file, void *priv, 75958757984SMauro Carvalho Chehab struct v4l2_capability *capability) 76058757984SMauro Carvalho Chehab { 76158757984SMauro Carvalho Chehab struct si470x_device *radio = video_drvdata(file); 76258757984SMauro Carvalho Chehab 76358757984SMauro Carvalho Chehab return radio->vidioc_querycap(file, priv, capability); 76458757984SMauro Carvalho Chehab }; 765b09cd163SJoonyoung Shim 766b09cd163SJoonyoung Shim /* 767b09cd163SJoonyoung Shim * si470x_ioctl_ops - video device ioctl operations 768b09cd163SJoonyoung Shim */ 769b09cd163SJoonyoung Shim static const struct v4l2_ioctl_ops si470x_ioctl_ops = { 770b09cd163SJoonyoung Shim .vidioc_querycap = si470x_vidioc_querycap, 771b09cd163SJoonyoung Shim .vidioc_g_tuner = si470x_vidioc_g_tuner, 772b09cd163SJoonyoung Shim .vidioc_s_tuner = si470x_vidioc_s_tuner, 773b09cd163SJoonyoung Shim .vidioc_g_frequency = si470x_vidioc_g_frequency, 774b09cd163SJoonyoung Shim .vidioc_s_frequency = si470x_vidioc_s_frequency, 775b09cd163SJoonyoung Shim .vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek, 776f140612dSHans de Goede .vidioc_enum_freq_bands = si470x_vidioc_enum_freq_bands, 777eae63ae0SHans Verkuil .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 778eae63ae0SHans Verkuil .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 779b09cd163SJoonyoung Shim }; 780b09cd163SJoonyoung Shim 781b09cd163SJoonyoung Shim 782b09cd163SJoonyoung Shim /* 783b09cd163SJoonyoung Shim * si470x_viddev_template - video device interface 784b09cd163SJoonyoung Shim */ 78582fad476SBhumika Goyal const struct video_device si470x_viddev_template = { 786b09cd163SJoonyoung Shim .fops = &si470x_fops, 787b09cd163SJoonyoung Shim .name = DRIVER_NAME, 7884967d53dSHans Verkuil .release = video_device_release_empty, 789b09cd163SJoonyoung Shim .ioctl_ops = &si470x_ioctl_ops, 790b09cd163SJoonyoung Shim }; 79158757984SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(si470x_viddev_template); 79258757984SMauro Carvalho Chehab 79358757984SMauro Carvalho Chehab MODULE_LICENSE("GPL"); 794