162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * drivers/media/radio/si470x/radio-si470x-common.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Driver for radios with Silicon Labs Si470x FM Radio Receivers 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net> 862306a36Sopenharmony_ci * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci/* 1362306a36Sopenharmony_ci * History: 1462306a36Sopenharmony_ci * 2008-01-12 Tobias Lorenz <tobias.lorenz@gmx.net> 1562306a36Sopenharmony_ci * Version 1.0.0 1662306a36Sopenharmony_ci * - First working version 1762306a36Sopenharmony_ci * 2008-01-13 Tobias Lorenz <tobias.lorenz@gmx.net> 1862306a36Sopenharmony_ci * Version 1.0.1 1962306a36Sopenharmony_ci * - Improved error handling, every function now returns errno 2062306a36Sopenharmony_ci * - Improved multi user access (start/mute/stop) 2162306a36Sopenharmony_ci * - Channel doesn't get lost anymore after start/mute/stop 2262306a36Sopenharmony_ci * - RDS support added (polling mode via interrupt EP 1) 2362306a36Sopenharmony_ci * - marked default module parameters with *value* 2462306a36Sopenharmony_ci * - switched from bit structs to bit masks 2562306a36Sopenharmony_ci * - header file cleaned and integrated 2662306a36Sopenharmony_ci * 2008-01-14 Tobias Lorenz <tobias.lorenz@gmx.net> 2762306a36Sopenharmony_ci * Version 1.0.2 2862306a36Sopenharmony_ci * - hex values are now lower case 2962306a36Sopenharmony_ci * - commented USB ID for ADS/Tech moved on todo list 3062306a36Sopenharmony_ci * - blacklisted si470x in hid-quirks.c 3162306a36Sopenharmony_ci * - rds buffer handling functions integrated into *_work, *_read 3262306a36Sopenharmony_ci * - rds_command in si470x_poll exchanged against simple retval 3362306a36Sopenharmony_ci * - check for firmware version 15 3462306a36Sopenharmony_ci * - code order and prototypes still remain the same 3562306a36Sopenharmony_ci * - spacing and bottom of band codes remain the same 3662306a36Sopenharmony_ci * 2008-01-16 Tobias Lorenz <tobias.lorenz@gmx.net> 3762306a36Sopenharmony_ci * Version 1.0.3 3862306a36Sopenharmony_ci * - code reordered to avoid function prototypes 3962306a36Sopenharmony_ci * - switch/case defaults are now more user-friendly 4062306a36Sopenharmony_ci * - unified comment style 4162306a36Sopenharmony_ci * - applied all checkpatch.pl v1.12 suggestions 4262306a36Sopenharmony_ci * except the warning about the too long lines with bit comments 4362306a36Sopenharmony_ci * - renamed FMRADIO to RADIO to cut line length (checkpatch.pl) 4462306a36Sopenharmony_ci * 2008-01-22 Tobias Lorenz <tobias.lorenz@gmx.net> 4562306a36Sopenharmony_ci * Version 1.0.4 4662306a36Sopenharmony_ci * - avoid poss. locking when doing copy_to_user which may sleep 4762306a36Sopenharmony_ci * - RDS is automatically activated on read now 4862306a36Sopenharmony_ci * - code cleaned of unnecessary rds_commands 4962306a36Sopenharmony_ci * - USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified 5062306a36Sopenharmony_ci * (thanks to Guillaume RAMOUSSE) 5162306a36Sopenharmony_ci * 2008-01-27 Tobias Lorenz <tobias.lorenz@gmx.net> 5262306a36Sopenharmony_ci * Version 1.0.5 5362306a36Sopenharmony_ci * - number of seek_retries changed to tune_timeout 5462306a36Sopenharmony_ci * - fixed problem with incomplete tune operations by own buffers 5562306a36Sopenharmony_ci * - optimization of variables and printf types 5662306a36Sopenharmony_ci * - improved error logging 5762306a36Sopenharmony_ci * 2008-01-31 Tobias Lorenz <tobias.lorenz@gmx.net> 5862306a36Sopenharmony_ci * Oliver Neukum <oliver@neukum.org> 5962306a36Sopenharmony_ci * Version 1.0.6 6062306a36Sopenharmony_ci * - fixed coverity checker warnings in *_usb_driver_disconnect 6162306a36Sopenharmony_ci * - probe()/open() race by correct ordering in probe() 6262306a36Sopenharmony_ci * - DMA coherency rules by separate allocation of all buffers 6362306a36Sopenharmony_ci * - use of endianness macros 6462306a36Sopenharmony_ci * - abuse of spinlock, replaced by mutex 6562306a36Sopenharmony_ci * - racy handling of timer in disconnect, 6662306a36Sopenharmony_ci * replaced by delayed_work 6762306a36Sopenharmony_ci * - racy interruptible_sleep_on(), 6862306a36Sopenharmony_ci * replaced with wait_event_interruptible() 6962306a36Sopenharmony_ci * - handle signals in read() 7062306a36Sopenharmony_ci * 2008-02-08 Tobias Lorenz <tobias.lorenz@gmx.net> 7162306a36Sopenharmony_ci * Oliver Neukum <oliver@neukum.org> 7262306a36Sopenharmony_ci * Version 1.0.7 7362306a36Sopenharmony_ci * - usb autosuspend support 7462306a36Sopenharmony_ci * - unplugging fixed 7562306a36Sopenharmony_ci * 2008-05-07 Tobias Lorenz <tobias.lorenz@gmx.net> 7662306a36Sopenharmony_ci * Version 1.0.8 7762306a36Sopenharmony_ci * - hardware frequency seek support 7862306a36Sopenharmony_ci * - afc indication 7962306a36Sopenharmony_ci * - more safety checks, let si470x_get_freq return errno 8062306a36Sopenharmony_ci * - vidioc behavior corrected according to v4l2 spec 8162306a36Sopenharmony_ci * 2008-10-20 Alexey Klimov <klimov.linux@gmail.com> 8262306a36Sopenharmony_ci * - add support for KWorld USB FM Radio FM700 8362306a36Sopenharmony_ci * - blacklisted KWorld radio in hid-core.c and hid-ids.h 8462306a36Sopenharmony_ci * 2008-12-03 Mark Lord <mlord@pobox.com> 8562306a36Sopenharmony_ci * - add support for DealExtreme USB Radio 8662306a36Sopenharmony_ci * 2009-01-31 Bob Ross <pigiron@gmx.com> 8762306a36Sopenharmony_ci * - correction of stereo detection/setting 8862306a36Sopenharmony_ci * - correction of signal strength indicator scaling 8962306a36Sopenharmony_ci * 2009-01-31 Rick Bronson <rick@efn.org> 9062306a36Sopenharmony_ci * Tobias Lorenz <tobias.lorenz@gmx.net> 9162306a36Sopenharmony_ci * - add LED status output 9262306a36Sopenharmony_ci * - get HW/SW version from scratchpad 9362306a36Sopenharmony_ci * 2009-06-16 Edouard Lafargue <edouard@lafargue.name> 9462306a36Sopenharmony_ci * Version 1.0.10 9562306a36Sopenharmony_ci * - add support for interrupt mode for RDS endpoint, 9662306a36Sopenharmony_ci * instead of polling. 9762306a36Sopenharmony_ci * Improves RDS reception significantly 9862306a36Sopenharmony_ci */ 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci/* kernel includes */ 10262306a36Sopenharmony_ci#include "radio-si470x.h" 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci/************************************************************************** 10562306a36Sopenharmony_ci * Module Parameters 10662306a36Sopenharmony_ci **************************************************************************/ 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci/* Spacing (kHz) */ 10962306a36Sopenharmony_ci/* 0: 200 kHz (USA, Australia) */ 11062306a36Sopenharmony_ci/* 1: 100 kHz (Europe, Japan) */ 11162306a36Sopenharmony_ci/* 2: 50 kHz */ 11262306a36Sopenharmony_cistatic unsigned short space = 2; 11362306a36Sopenharmony_cimodule_param(space, ushort, 0444); 11462306a36Sopenharmony_ciMODULE_PARM_DESC(space, "Spacing: 0=200kHz 1=100kHz *2=50kHz*"); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci/* De-emphasis */ 11762306a36Sopenharmony_ci/* 0: 75 us (USA) */ 11862306a36Sopenharmony_ci/* 1: 50 us (Europe, Australia, Japan) */ 11962306a36Sopenharmony_cistatic unsigned short de = 1; 12062306a36Sopenharmony_cimodule_param(de, ushort, 0444); 12162306a36Sopenharmony_ciMODULE_PARM_DESC(de, "De-emphasis: 0=75us *1=50us*"); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/* Tune timeout */ 12462306a36Sopenharmony_cistatic unsigned int tune_timeout = 3000; 12562306a36Sopenharmony_cimodule_param(tune_timeout, uint, 0644); 12662306a36Sopenharmony_ciMODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*"); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci/* Seek timeout */ 12962306a36Sopenharmony_cistatic unsigned int seek_timeout = 5000; 13062306a36Sopenharmony_cimodule_param(seek_timeout, uint, 0644); 13162306a36Sopenharmony_ciMODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*"); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic const struct v4l2_frequency_band bands[] = { 13462306a36Sopenharmony_ci { 13562306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 13662306a36Sopenharmony_ci .index = 0, 13762306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 13862306a36Sopenharmony_ci V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 13962306a36Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS | 14062306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 14162306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_WRAP, 14262306a36Sopenharmony_ci .rangelow = 87500 * 16, 14362306a36Sopenharmony_ci .rangehigh = 108000 * 16, 14462306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 14562306a36Sopenharmony_ci }, 14662306a36Sopenharmony_ci { 14762306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 14862306a36Sopenharmony_ci .index = 1, 14962306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 15062306a36Sopenharmony_ci V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 15162306a36Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS | 15262306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 15362306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_WRAP, 15462306a36Sopenharmony_ci .rangelow = 76000 * 16, 15562306a36Sopenharmony_ci .rangehigh = 108000 * 16, 15662306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 15762306a36Sopenharmony_ci }, 15862306a36Sopenharmony_ci { 15962306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 16062306a36Sopenharmony_ci .index = 2, 16162306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 16262306a36Sopenharmony_ci V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 16362306a36Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS | 16462306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 16562306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_WRAP, 16662306a36Sopenharmony_ci .rangelow = 76000 * 16, 16762306a36Sopenharmony_ci .rangehigh = 90000 * 16, 16862306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 16962306a36Sopenharmony_ci }, 17062306a36Sopenharmony_ci}; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci/************************************************************************** 17362306a36Sopenharmony_ci * Generic Functions 17462306a36Sopenharmony_ci **************************************************************************/ 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci/* 17762306a36Sopenharmony_ci * si470x_set_band - set the band 17862306a36Sopenharmony_ci */ 17962306a36Sopenharmony_cistatic int si470x_set_band(struct si470x_device *radio, int band) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci if (radio->band == band) 18262306a36Sopenharmony_ci return 0; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci radio->band = band; 18562306a36Sopenharmony_ci radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_BAND; 18662306a36Sopenharmony_ci radio->registers[SYSCONFIG2] |= radio->band << 6; 18762306a36Sopenharmony_ci return radio->set_register(radio, SYSCONFIG2); 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci/* 19162306a36Sopenharmony_ci * si470x_set_chan - set the channel 19262306a36Sopenharmony_ci */ 19362306a36Sopenharmony_cistatic int si470x_set_chan(struct si470x_device *radio, unsigned short chan) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci int retval; 19662306a36Sopenharmony_ci unsigned long time_left; 19762306a36Sopenharmony_ci bool timed_out = false; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci retval = radio->get_register(radio, POWERCFG); 20062306a36Sopenharmony_ci if (retval) 20162306a36Sopenharmony_ci return retval; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci if ((radio->registers[POWERCFG] & (POWERCFG_ENABLE|POWERCFG_DMUTE)) 20462306a36Sopenharmony_ci != (POWERCFG_ENABLE|POWERCFG_DMUTE)) { 20562306a36Sopenharmony_ci return 0; 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci /* start tuning */ 20962306a36Sopenharmony_ci radio->registers[CHANNEL] &= ~CHANNEL_CHAN; 21062306a36Sopenharmony_ci radio->registers[CHANNEL] |= CHANNEL_TUNE | chan; 21162306a36Sopenharmony_ci retval = radio->set_register(radio, CHANNEL); 21262306a36Sopenharmony_ci if (retval < 0) 21362306a36Sopenharmony_ci goto done; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci /* wait till tune operation has completed */ 21662306a36Sopenharmony_ci reinit_completion(&radio->completion); 21762306a36Sopenharmony_ci time_left = wait_for_completion_timeout(&radio->completion, 21862306a36Sopenharmony_ci msecs_to_jiffies(tune_timeout)); 21962306a36Sopenharmony_ci if (time_left == 0) 22062306a36Sopenharmony_ci timed_out = true; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) 22362306a36Sopenharmony_ci dev_warn(&radio->videodev.dev, "tune does not complete\n"); 22462306a36Sopenharmony_ci if (timed_out) 22562306a36Sopenharmony_ci dev_warn(&radio->videodev.dev, 22662306a36Sopenharmony_ci "tune timed out after %u ms\n", tune_timeout); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci /* stop tuning */ 22962306a36Sopenharmony_ci radio->registers[CHANNEL] &= ~CHANNEL_TUNE; 23062306a36Sopenharmony_ci retval = radio->set_register(radio, CHANNEL); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cidone: 23362306a36Sopenharmony_ci return retval; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci/* 23762306a36Sopenharmony_ci * si470x_get_step - get channel spacing 23862306a36Sopenharmony_ci */ 23962306a36Sopenharmony_cistatic unsigned int si470x_get_step(struct si470x_device *radio) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci /* Spacing (kHz) */ 24262306a36Sopenharmony_ci switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) { 24362306a36Sopenharmony_ci /* 0: 200 kHz (USA, Australia) */ 24462306a36Sopenharmony_ci case 0: 24562306a36Sopenharmony_ci return 200 * 16; 24662306a36Sopenharmony_ci /* 1: 100 kHz (Europe, Japan) */ 24762306a36Sopenharmony_ci case 1: 24862306a36Sopenharmony_ci return 100 * 16; 24962306a36Sopenharmony_ci /* 2: 50 kHz */ 25062306a36Sopenharmony_ci default: 25162306a36Sopenharmony_ci return 50 * 16; 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci/* 25762306a36Sopenharmony_ci * si470x_get_freq - get the frequency 25862306a36Sopenharmony_ci */ 25962306a36Sopenharmony_cistatic int si470x_get_freq(struct si470x_device *radio, unsigned int *freq) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci int chan, retval; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci /* read channel */ 26462306a36Sopenharmony_ci retval = radio->get_register(radio, READCHAN); 26562306a36Sopenharmony_ci chan = radio->registers[READCHAN] & READCHAN_READCHAN; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci /* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */ 26862306a36Sopenharmony_ci *freq = chan * si470x_get_step(radio) + bands[radio->band].rangelow; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci return retval; 27162306a36Sopenharmony_ci} 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci/* 27562306a36Sopenharmony_ci * si470x_set_freq - set the frequency 27662306a36Sopenharmony_ci */ 27762306a36Sopenharmony_ciint si470x_set_freq(struct si470x_device *radio, unsigned int freq) 27862306a36Sopenharmony_ci{ 27962306a36Sopenharmony_ci unsigned short chan; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci freq = clamp(freq, bands[radio->band].rangelow, 28262306a36Sopenharmony_ci bands[radio->band].rangehigh); 28362306a36Sopenharmony_ci /* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */ 28462306a36Sopenharmony_ci chan = (freq - bands[radio->band].rangelow) / si470x_get_step(radio); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci return si470x_set_chan(radio, chan); 28762306a36Sopenharmony_ci} 28862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(si470x_set_freq); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci/* 29262306a36Sopenharmony_ci * si470x_set_seek - set seek 29362306a36Sopenharmony_ci */ 29462306a36Sopenharmony_cistatic int si470x_set_seek(struct si470x_device *radio, 29562306a36Sopenharmony_ci const struct v4l2_hw_freq_seek *seek) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci int band, retval; 29862306a36Sopenharmony_ci unsigned int freq; 29962306a36Sopenharmony_ci bool timed_out = false; 30062306a36Sopenharmony_ci unsigned long time_left; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci /* set band */ 30362306a36Sopenharmony_ci if (seek->rangelow || seek->rangehigh) { 30462306a36Sopenharmony_ci for (band = 0; band < ARRAY_SIZE(bands); band++) { 30562306a36Sopenharmony_ci if (bands[band].rangelow == seek->rangelow && 30662306a36Sopenharmony_ci bands[band].rangehigh == seek->rangehigh) 30762306a36Sopenharmony_ci break; 30862306a36Sopenharmony_ci } 30962306a36Sopenharmony_ci if (band == ARRAY_SIZE(bands)) 31062306a36Sopenharmony_ci return -EINVAL; /* No matching band found */ 31162306a36Sopenharmony_ci } else 31262306a36Sopenharmony_ci band = 1; /* If nothing is specified seek 76 - 108 Mhz */ 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci if (radio->band != band) { 31562306a36Sopenharmony_ci retval = si470x_get_freq(radio, &freq); 31662306a36Sopenharmony_ci if (retval) 31762306a36Sopenharmony_ci return retval; 31862306a36Sopenharmony_ci retval = si470x_set_band(radio, band); 31962306a36Sopenharmony_ci if (retval) 32062306a36Sopenharmony_ci return retval; 32162306a36Sopenharmony_ci retval = si470x_set_freq(radio, freq); 32262306a36Sopenharmony_ci if (retval) 32362306a36Sopenharmony_ci return retval; 32462306a36Sopenharmony_ci } 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci /* start seeking */ 32762306a36Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_SEEK; 32862306a36Sopenharmony_ci if (seek->wrap_around) 32962306a36Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_SKMODE; 33062306a36Sopenharmony_ci else 33162306a36Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_SKMODE; 33262306a36Sopenharmony_ci if (seek->seek_upward) 33362306a36Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_SEEKUP; 33462306a36Sopenharmony_ci else 33562306a36Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP; 33662306a36Sopenharmony_ci retval = radio->set_register(radio, POWERCFG); 33762306a36Sopenharmony_ci if (retval < 0) 33862306a36Sopenharmony_ci return retval; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci /* wait till tune operation has completed */ 34162306a36Sopenharmony_ci reinit_completion(&radio->completion); 34262306a36Sopenharmony_ci time_left = wait_for_completion_timeout(&radio->completion, 34362306a36Sopenharmony_ci msecs_to_jiffies(seek_timeout)); 34462306a36Sopenharmony_ci if (time_left == 0) 34562306a36Sopenharmony_ci timed_out = true; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) 34862306a36Sopenharmony_ci dev_warn(&radio->videodev.dev, "seek does not complete\n"); 34962306a36Sopenharmony_ci if (radio->registers[STATUSRSSI] & STATUSRSSI_SF) 35062306a36Sopenharmony_ci dev_warn(&radio->videodev.dev, 35162306a36Sopenharmony_ci "seek failed / band limit reached\n"); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci /* stop seeking */ 35462306a36Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_SEEK; 35562306a36Sopenharmony_ci retval = radio->set_register(radio, POWERCFG); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci /* try again, if timed out */ 35862306a36Sopenharmony_ci if (retval == 0 && timed_out) 35962306a36Sopenharmony_ci return -ENODATA; 36062306a36Sopenharmony_ci return retval; 36162306a36Sopenharmony_ci} 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci/* 36562306a36Sopenharmony_ci * si470x_start - switch on radio 36662306a36Sopenharmony_ci */ 36762306a36Sopenharmony_ciint si470x_start(struct si470x_device *radio) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci int retval; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci /* powercfg */ 37262306a36Sopenharmony_ci radio->registers[POWERCFG] = 37362306a36Sopenharmony_ci POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM; 37462306a36Sopenharmony_ci retval = radio->set_register(radio, POWERCFG); 37562306a36Sopenharmony_ci if (retval < 0) 37662306a36Sopenharmony_ci goto done; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci /* sysconfig 1 */ 37962306a36Sopenharmony_ci radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDSIEN | SYSCONFIG1_STCIEN | 38062306a36Sopenharmony_ci SYSCONFIG1_RDS; 38162306a36Sopenharmony_ci radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_GPIO2; 38262306a36Sopenharmony_ci radio->registers[SYSCONFIG1] |= SYSCONFIG1_GPIO2_INT; 38362306a36Sopenharmony_ci if (de) 38462306a36Sopenharmony_ci radio->registers[SYSCONFIG1] |= SYSCONFIG1_DE; 38562306a36Sopenharmony_ci retval = radio->set_register(radio, SYSCONFIG1); 38662306a36Sopenharmony_ci if (retval < 0) 38762306a36Sopenharmony_ci goto done; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci /* sysconfig 2 */ 39062306a36Sopenharmony_ci radio->registers[SYSCONFIG2] = 39162306a36Sopenharmony_ci (0x1f << 8) | /* SEEKTH */ 39262306a36Sopenharmony_ci ((radio->band << 6) & SYSCONFIG2_BAND) |/* BAND */ 39362306a36Sopenharmony_ci ((space << 4) & SYSCONFIG2_SPACE) | /* SPACE */ 39462306a36Sopenharmony_ci 15; /* VOLUME (max) */ 39562306a36Sopenharmony_ci retval = radio->set_register(radio, SYSCONFIG2); 39662306a36Sopenharmony_ci if (retval < 0) 39762306a36Sopenharmony_ci goto done; 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci /* reset last channel */ 40062306a36Sopenharmony_ci retval = si470x_set_chan(radio, 40162306a36Sopenharmony_ci radio->registers[CHANNEL] & CHANNEL_CHAN); 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_cidone: 40462306a36Sopenharmony_ci return retval; 40562306a36Sopenharmony_ci} 40662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(si470x_start); 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci/* 41062306a36Sopenharmony_ci * si470x_stop - switch off radio 41162306a36Sopenharmony_ci */ 41262306a36Sopenharmony_ciint si470x_stop(struct si470x_device *radio) 41362306a36Sopenharmony_ci{ 41462306a36Sopenharmony_ci int retval; 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci /* sysconfig 1 */ 41762306a36Sopenharmony_ci radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS; 41862306a36Sopenharmony_ci retval = radio->set_register(radio, SYSCONFIG1); 41962306a36Sopenharmony_ci if (retval < 0) 42062306a36Sopenharmony_ci goto done; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci /* powercfg */ 42362306a36Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; 42462306a36Sopenharmony_ci /* POWERCFG_ENABLE has to automatically go low */ 42562306a36Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE; 42662306a36Sopenharmony_ci retval = radio->set_register(radio, POWERCFG); 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_cidone: 42962306a36Sopenharmony_ci return retval; 43062306a36Sopenharmony_ci} 43162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(si470x_stop); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci/* 43562306a36Sopenharmony_ci * si470x_rds_on - switch on rds reception 43662306a36Sopenharmony_ci */ 43762306a36Sopenharmony_cistatic int si470x_rds_on(struct si470x_device *radio) 43862306a36Sopenharmony_ci{ 43962306a36Sopenharmony_ci int retval; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci /* sysconfig 1 */ 44262306a36Sopenharmony_ci radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS; 44362306a36Sopenharmony_ci retval = radio->set_register(radio, SYSCONFIG1); 44462306a36Sopenharmony_ci if (retval < 0) 44562306a36Sopenharmony_ci radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS; 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci return retval; 44862306a36Sopenharmony_ci} 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci/************************************************************************** 45362306a36Sopenharmony_ci * File Operations Interface 45462306a36Sopenharmony_ci **************************************************************************/ 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci/* 45762306a36Sopenharmony_ci * si470x_fops_read - read RDS data 45862306a36Sopenharmony_ci */ 45962306a36Sopenharmony_cistatic ssize_t si470x_fops_read(struct file *file, char __user *buf, 46062306a36Sopenharmony_ci size_t count, loff_t *ppos) 46162306a36Sopenharmony_ci{ 46262306a36Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 46362306a36Sopenharmony_ci int retval = 0; 46462306a36Sopenharmony_ci unsigned int block_count = 0; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci /* switch on rds reception */ 46762306a36Sopenharmony_ci if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) 46862306a36Sopenharmony_ci si470x_rds_on(radio); 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci /* block if no new data available */ 47162306a36Sopenharmony_ci while (radio->wr_index == radio->rd_index) { 47262306a36Sopenharmony_ci if (file->f_flags & O_NONBLOCK) { 47362306a36Sopenharmony_ci retval = -EWOULDBLOCK; 47462306a36Sopenharmony_ci goto done; 47562306a36Sopenharmony_ci } 47662306a36Sopenharmony_ci if (wait_event_interruptible(radio->read_queue, 47762306a36Sopenharmony_ci radio->wr_index != radio->rd_index) < 0) { 47862306a36Sopenharmony_ci retval = -EINTR; 47962306a36Sopenharmony_ci goto done; 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci } 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci /* calculate block count from byte count */ 48462306a36Sopenharmony_ci count /= 3; 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci /* copy RDS block out of internal buffer and to user buffer */ 48762306a36Sopenharmony_ci while (block_count < count) { 48862306a36Sopenharmony_ci if (radio->rd_index == radio->wr_index) 48962306a36Sopenharmony_ci break; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci /* always transfer rds complete blocks */ 49262306a36Sopenharmony_ci if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3)) 49362306a36Sopenharmony_ci /* retval = -EFAULT; */ 49462306a36Sopenharmony_ci break; 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci /* increment and wrap read pointer */ 49762306a36Sopenharmony_ci radio->rd_index += 3; 49862306a36Sopenharmony_ci if (radio->rd_index >= radio->buf_size) 49962306a36Sopenharmony_ci radio->rd_index = 0; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci /* increment counters */ 50262306a36Sopenharmony_ci block_count++; 50362306a36Sopenharmony_ci buf += 3; 50462306a36Sopenharmony_ci retval += 3; 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_cidone: 50862306a36Sopenharmony_ci return retval; 50962306a36Sopenharmony_ci} 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci/* 51362306a36Sopenharmony_ci * si470x_fops_poll - poll RDS data 51462306a36Sopenharmony_ci */ 51562306a36Sopenharmony_cistatic __poll_t si470x_fops_poll(struct file *file, 51662306a36Sopenharmony_ci struct poll_table_struct *pts) 51762306a36Sopenharmony_ci{ 51862306a36Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 51962306a36Sopenharmony_ci __poll_t req_events = poll_requested_events(pts); 52062306a36Sopenharmony_ci __poll_t retval = v4l2_ctrl_poll(file, pts); 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci if (req_events & (EPOLLIN | EPOLLRDNORM)) { 52362306a36Sopenharmony_ci /* switch on rds reception */ 52462306a36Sopenharmony_ci if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) 52562306a36Sopenharmony_ci si470x_rds_on(radio); 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci poll_wait(file, &radio->read_queue, pts); 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci if (radio->rd_index != radio->wr_index) 53062306a36Sopenharmony_ci retval |= EPOLLIN | EPOLLRDNORM; 53162306a36Sopenharmony_ci } 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci return retval; 53462306a36Sopenharmony_ci} 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_cistatic int si470x_fops_open(struct file *file) 53862306a36Sopenharmony_ci{ 53962306a36Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci return radio->fops_open(file); 54262306a36Sopenharmony_ci} 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci/* 54662306a36Sopenharmony_ci * si470x_fops_release - file release 54762306a36Sopenharmony_ci */ 54862306a36Sopenharmony_cistatic int si470x_fops_release(struct file *file) 54962306a36Sopenharmony_ci{ 55062306a36Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ci return radio->fops_release(file); 55362306a36Sopenharmony_ci} 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci/* 55762306a36Sopenharmony_ci * si470x_fops - file operations interface 55862306a36Sopenharmony_ci */ 55962306a36Sopenharmony_cistatic const struct v4l2_file_operations si470x_fops = { 56062306a36Sopenharmony_ci .owner = THIS_MODULE, 56162306a36Sopenharmony_ci .read = si470x_fops_read, 56262306a36Sopenharmony_ci .poll = si470x_fops_poll, 56362306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 56462306a36Sopenharmony_ci .open = si470x_fops_open, 56562306a36Sopenharmony_ci .release = si470x_fops_release, 56662306a36Sopenharmony_ci}; 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci/************************************************************************** 57162306a36Sopenharmony_ci * Video4Linux Interface 57262306a36Sopenharmony_ci **************************************************************************/ 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_cistatic int si470x_s_ctrl(struct v4l2_ctrl *ctrl) 57662306a36Sopenharmony_ci{ 57762306a36Sopenharmony_ci struct si470x_device *radio = 57862306a36Sopenharmony_ci container_of(ctrl->handler, struct si470x_device, hdl); 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_ci switch (ctrl->id) { 58162306a36Sopenharmony_ci case V4L2_CID_AUDIO_VOLUME: 58262306a36Sopenharmony_ci radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME; 58362306a36Sopenharmony_ci radio->registers[SYSCONFIG2] |= ctrl->val; 58462306a36Sopenharmony_ci return radio->set_register(radio, SYSCONFIG2); 58562306a36Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 58662306a36Sopenharmony_ci if (ctrl->val) 58762306a36Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; 58862306a36Sopenharmony_ci else 58962306a36Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_DMUTE; 59062306a36Sopenharmony_ci return radio->set_register(radio, POWERCFG); 59162306a36Sopenharmony_ci default: 59262306a36Sopenharmony_ci return -EINVAL; 59362306a36Sopenharmony_ci } 59462306a36Sopenharmony_ci} 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci/* 59862306a36Sopenharmony_ci * si470x_vidioc_g_tuner - get tuner attributes 59962306a36Sopenharmony_ci */ 60062306a36Sopenharmony_cistatic int si470x_vidioc_g_tuner(struct file *file, void *priv, 60162306a36Sopenharmony_ci struct v4l2_tuner *tuner) 60262306a36Sopenharmony_ci{ 60362306a36Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 60462306a36Sopenharmony_ci int retval = 0; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci if (tuner->index != 0) 60762306a36Sopenharmony_ci return -EINVAL; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci if (!radio->status_rssi_auto_update) { 61062306a36Sopenharmony_ci retval = radio->get_register(radio, STATUSRSSI); 61162306a36Sopenharmony_ci if (retval < 0) 61262306a36Sopenharmony_ci return retval; 61362306a36Sopenharmony_ci } 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci /* driver constants */ 61662306a36Sopenharmony_ci strscpy(tuner->name, "FM", sizeof(tuner->name)); 61762306a36Sopenharmony_ci tuner->type = V4L2_TUNER_RADIO; 61862306a36Sopenharmony_ci tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 61962306a36Sopenharmony_ci V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 62062306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 62162306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_WRAP; 62262306a36Sopenharmony_ci tuner->rangelow = 76 * FREQ_MUL; 62362306a36Sopenharmony_ci tuner->rangehigh = 108 * FREQ_MUL; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci /* stereo indicator == stereo (instead of mono) */ 62662306a36Sopenharmony_ci if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0) 62762306a36Sopenharmony_ci tuner->rxsubchans = V4L2_TUNER_SUB_MONO; 62862306a36Sopenharmony_ci else 62962306a36Sopenharmony_ci tuner->rxsubchans = V4L2_TUNER_SUB_STEREO; 63062306a36Sopenharmony_ci /* If there is a reliable method of detecting an RDS channel, 63162306a36Sopenharmony_ci then this code should check for that before setting this 63262306a36Sopenharmony_ci RDS subchannel. */ 63362306a36Sopenharmony_ci tuner->rxsubchans |= V4L2_TUNER_SUB_RDS; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci /* mono/stereo selector */ 63662306a36Sopenharmony_ci if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 0) 63762306a36Sopenharmony_ci tuner->audmode = V4L2_TUNER_MODE_STEREO; 63862306a36Sopenharmony_ci else 63962306a36Sopenharmony_ci tuner->audmode = V4L2_TUNER_MODE_MONO; 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci /* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */ 64262306a36Sopenharmony_ci /* measured in units of dbµV in 1 db increments (max at ~75 dbµV) */ 64362306a36Sopenharmony_ci tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI); 64462306a36Sopenharmony_ci /* the ideal factor is 0xffff/75 = 873,8 */ 64562306a36Sopenharmony_ci tuner->signal = (tuner->signal * 873) + (8 * tuner->signal / 10); 64662306a36Sopenharmony_ci if (tuner->signal > 0xffff) 64762306a36Sopenharmony_ci tuner->signal = 0xffff; 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci /* automatic frequency control: -1: freq to low, 1 freq to high */ 65062306a36Sopenharmony_ci /* AFCRL does only indicate that freq. differs, not if too low/high */ 65162306a36Sopenharmony_ci tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0; 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci return retval; 65462306a36Sopenharmony_ci} 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci/* 65862306a36Sopenharmony_ci * si470x_vidioc_s_tuner - set tuner attributes 65962306a36Sopenharmony_ci */ 66062306a36Sopenharmony_cistatic int si470x_vidioc_s_tuner(struct file *file, void *priv, 66162306a36Sopenharmony_ci const struct v4l2_tuner *tuner) 66262306a36Sopenharmony_ci{ 66362306a36Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci if (tuner->index != 0) 66662306a36Sopenharmony_ci return -EINVAL; 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci /* mono/stereo selector */ 66962306a36Sopenharmony_ci switch (tuner->audmode) { 67062306a36Sopenharmony_ci case V4L2_TUNER_MODE_MONO: 67162306a36Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */ 67262306a36Sopenharmony_ci break; 67362306a36Sopenharmony_ci case V4L2_TUNER_MODE_STEREO: 67462306a36Sopenharmony_ci default: 67562306a36Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */ 67662306a36Sopenharmony_ci break; 67762306a36Sopenharmony_ci } 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci return radio->set_register(radio, POWERCFG); 68062306a36Sopenharmony_ci} 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_ci/* 68462306a36Sopenharmony_ci * si470x_vidioc_g_frequency - get tuner or modulator radio frequency 68562306a36Sopenharmony_ci */ 68662306a36Sopenharmony_cistatic int si470x_vidioc_g_frequency(struct file *file, void *priv, 68762306a36Sopenharmony_ci struct v4l2_frequency *freq) 68862306a36Sopenharmony_ci{ 68962306a36Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_ci if (freq->tuner != 0) 69262306a36Sopenharmony_ci return -EINVAL; 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci freq->type = V4L2_TUNER_RADIO; 69562306a36Sopenharmony_ci return si470x_get_freq(radio, &freq->frequency); 69662306a36Sopenharmony_ci} 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci/* 70062306a36Sopenharmony_ci * si470x_vidioc_s_frequency - set tuner or modulator radio frequency 70162306a36Sopenharmony_ci */ 70262306a36Sopenharmony_cistatic int si470x_vidioc_s_frequency(struct file *file, void *priv, 70362306a36Sopenharmony_ci const struct v4l2_frequency *freq) 70462306a36Sopenharmony_ci{ 70562306a36Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 70662306a36Sopenharmony_ci int retval; 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci if (freq->tuner != 0) 70962306a36Sopenharmony_ci return -EINVAL; 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci if (freq->frequency < bands[radio->band].rangelow || 71262306a36Sopenharmony_ci freq->frequency > bands[radio->band].rangehigh) { 71362306a36Sopenharmony_ci /* Switch to band 1 which covers everything we support */ 71462306a36Sopenharmony_ci retval = si470x_set_band(radio, 1); 71562306a36Sopenharmony_ci if (retval) 71662306a36Sopenharmony_ci return retval; 71762306a36Sopenharmony_ci } 71862306a36Sopenharmony_ci return si470x_set_freq(radio, freq->frequency); 71962306a36Sopenharmony_ci} 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci/* 72362306a36Sopenharmony_ci * si470x_vidioc_s_hw_freq_seek - set hardware frequency seek 72462306a36Sopenharmony_ci */ 72562306a36Sopenharmony_cistatic int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv, 72662306a36Sopenharmony_ci const struct v4l2_hw_freq_seek *seek) 72762306a36Sopenharmony_ci{ 72862306a36Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_ci if (seek->tuner != 0) 73162306a36Sopenharmony_ci return -EINVAL; 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 73462306a36Sopenharmony_ci return -EWOULDBLOCK; 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ci return si470x_set_seek(radio, seek); 73762306a36Sopenharmony_ci} 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_ci/* 74062306a36Sopenharmony_ci * si470x_vidioc_enum_freq_bands - enumerate supported bands 74162306a36Sopenharmony_ci */ 74262306a36Sopenharmony_cistatic int si470x_vidioc_enum_freq_bands(struct file *file, void *priv, 74362306a36Sopenharmony_ci struct v4l2_frequency_band *band) 74462306a36Sopenharmony_ci{ 74562306a36Sopenharmony_ci if (band->tuner != 0) 74662306a36Sopenharmony_ci return -EINVAL; 74762306a36Sopenharmony_ci if (band->index >= ARRAY_SIZE(bands)) 74862306a36Sopenharmony_ci return -EINVAL; 74962306a36Sopenharmony_ci *band = bands[band->index]; 75062306a36Sopenharmony_ci return 0; 75162306a36Sopenharmony_ci} 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ciconst struct v4l2_ctrl_ops si470x_ctrl_ops = { 75462306a36Sopenharmony_ci .s_ctrl = si470x_s_ctrl, 75562306a36Sopenharmony_ci}; 75662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(si470x_ctrl_ops); 75762306a36Sopenharmony_ci 75862306a36Sopenharmony_cistatic int si470x_vidioc_querycap(struct file *file, void *priv, 75962306a36Sopenharmony_ci struct v4l2_capability *capability) 76062306a36Sopenharmony_ci{ 76162306a36Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 76262306a36Sopenharmony_ci 76362306a36Sopenharmony_ci return radio->vidioc_querycap(file, priv, capability); 76462306a36Sopenharmony_ci}; 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_ci/* 76762306a36Sopenharmony_ci * si470x_ioctl_ops - video device ioctl operations 76862306a36Sopenharmony_ci */ 76962306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops si470x_ioctl_ops = { 77062306a36Sopenharmony_ci .vidioc_querycap = si470x_vidioc_querycap, 77162306a36Sopenharmony_ci .vidioc_g_tuner = si470x_vidioc_g_tuner, 77262306a36Sopenharmony_ci .vidioc_s_tuner = si470x_vidioc_s_tuner, 77362306a36Sopenharmony_ci .vidioc_g_frequency = si470x_vidioc_g_frequency, 77462306a36Sopenharmony_ci .vidioc_s_frequency = si470x_vidioc_s_frequency, 77562306a36Sopenharmony_ci .vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek, 77662306a36Sopenharmony_ci .vidioc_enum_freq_bands = si470x_vidioc_enum_freq_bands, 77762306a36Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 77862306a36Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 77962306a36Sopenharmony_ci}; 78062306a36Sopenharmony_ci 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci/* 78362306a36Sopenharmony_ci * si470x_viddev_template - video device interface 78462306a36Sopenharmony_ci */ 78562306a36Sopenharmony_ciconst struct video_device si470x_viddev_template = { 78662306a36Sopenharmony_ci .fops = &si470x_fops, 78762306a36Sopenharmony_ci .name = DRIVER_NAME, 78862306a36Sopenharmony_ci .release = video_device_release_empty, 78962306a36Sopenharmony_ci .ioctl_ops = &si470x_ioctl_ops, 79062306a36Sopenharmony_ci}; 79162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(si470x_viddev_template); 79262306a36Sopenharmony_ci 79362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 794