18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * drivers/media/radio/si470x/radio-si470x-common.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Driver for radios with Silicon Labs Si470x FM Radio Receivers 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net> 88c2ecf20Sopenharmony_ci * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci/* 138c2ecf20Sopenharmony_ci * History: 148c2ecf20Sopenharmony_ci * 2008-01-12 Tobias Lorenz <tobias.lorenz@gmx.net> 158c2ecf20Sopenharmony_ci * Version 1.0.0 168c2ecf20Sopenharmony_ci * - First working version 178c2ecf20Sopenharmony_ci * 2008-01-13 Tobias Lorenz <tobias.lorenz@gmx.net> 188c2ecf20Sopenharmony_ci * Version 1.0.1 198c2ecf20Sopenharmony_ci * - Improved error handling, every function now returns errno 208c2ecf20Sopenharmony_ci * - Improved multi user access (start/mute/stop) 218c2ecf20Sopenharmony_ci * - Channel doesn't get lost anymore after start/mute/stop 228c2ecf20Sopenharmony_ci * - RDS support added (polling mode via interrupt EP 1) 238c2ecf20Sopenharmony_ci * - marked default module parameters with *value* 248c2ecf20Sopenharmony_ci * - switched from bit structs to bit masks 258c2ecf20Sopenharmony_ci * - header file cleaned and integrated 268c2ecf20Sopenharmony_ci * 2008-01-14 Tobias Lorenz <tobias.lorenz@gmx.net> 278c2ecf20Sopenharmony_ci * Version 1.0.2 288c2ecf20Sopenharmony_ci * - hex values are now lower case 298c2ecf20Sopenharmony_ci * - commented USB ID for ADS/Tech moved on todo list 308c2ecf20Sopenharmony_ci * - blacklisted si470x in hid-quirks.c 318c2ecf20Sopenharmony_ci * - rds buffer handling functions integrated into *_work, *_read 328c2ecf20Sopenharmony_ci * - rds_command in si470x_poll exchanged against simple retval 338c2ecf20Sopenharmony_ci * - check for firmware version 15 348c2ecf20Sopenharmony_ci * - code order and prototypes still remain the same 358c2ecf20Sopenharmony_ci * - spacing and bottom of band codes remain the same 368c2ecf20Sopenharmony_ci * 2008-01-16 Tobias Lorenz <tobias.lorenz@gmx.net> 378c2ecf20Sopenharmony_ci * Version 1.0.3 388c2ecf20Sopenharmony_ci * - code reordered to avoid function prototypes 398c2ecf20Sopenharmony_ci * - switch/case defaults are now more user-friendly 408c2ecf20Sopenharmony_ci * - unified comment style 418c2ecf20Sopenharmony_ci * - applied all checkpatch.pl v1.12 suggestions 428c2ecf20Sopenharmony_ci * except the warning about the too long lines with bit comments 438c2ecf20Sopenharmony_ci * - renamed FMRADIO to RADIO to cut line length (checkpatch.pl) 448c2ecf20Sopenharmony_ci * 2008-01-22 Tobias Lorenz <tobias.lorenz@gmx.net> 458c2ecf20Sopenharmony_ci * Version 1.0.4 468c2ecf20Sopenharmony_ci * - avoid poss. locking when doing copy_to_user which may sleep 478c2ecf20Sopenharmony_ci * - RDS is automatically activated on read now 488c2ecf20Sopenharmony_ci * - code cleaned of unnecessary rds_commands 498c2ecf20Sopenharmony_ci * - USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified 508c2ecf20Sopenharmony_ci * (thanks to Guillaume RAMOUSSE) 518c2ecf20Sopenharmony_ci * 2008-01-27 Tobias Lorenz <tobias.lorenz@gmx.net> 528c2ecf20Sopenharmony_ci * Version 1.0.5 538c2ecf20Sopenharmony_ci * - number of seek_retries changed to tune_timeout 548c2ecf20Sopenharmony_ci * - fixed problem with incomplete tune operations by own buffers 558c2ecf20Sopenharmony_ci * - optimization of variables and printf types 568c2ecf20Sopenharmony_ci * - improved error logging 578c2ecf20Sopenharmony_ci * 2008-01-31 Tobias Lorenz <tobias.lorenz@gmx.net> 588c2ecf20Sopenharmony_ci * Oliver Neukum <oliver@neukum.org> 598c2ecf20Sopenharmony_ci * Version 1.0.6 608c2ecf20Sopenharmony_ci * - fixed coverity checker warnings in *_usb_driver_disconnect 618c2ecf20Sopenharmony_ci * - probe()/open() race by correct ordering in probe() 628c2ecf20Sopenharmony_ci * - DMA coherency rules by separate allocation of all buffers 638c2ecf20Sopenharmony_ci * - use of endianness macros 648c2ecf20Sopenharmony_ci * - abuse of spinlock, replaced by mutex 658c2ecf20Sopenharmony_ci * - racy handling of timer in disconnect, 668c2ecf20Sopenharmony_ci * replaced by delayed_work 678c2ecf20Sopenharmony_ci * - racy interruptible_sleep_on(), 688c2ecf20Sopenharmony_ci * replaced with wait_event_interruptible() 698c2ecf20Sopenharmony_ci * - handle signals in read() 708c2ecf20Sopenharmony_ci * 2008-02-08 Tobias Lorenz <tobias.lorenz@gmx.net> 718c2ecf20Sopenharmony_ci * Oliver Neukum <oliver@neukum.org> 728c2ecf20Sopenharmony_ci * Version 1.0.7 738c2ecf20Sopenharmony_ci * - usb autosuspend support 748c2ecf20Sopenharmony_ci * - unplugging fixed 758c2ecf20Sopenharmony_ci * 2008-05-07 Tobias Lorenz <tobias.lorenz@gmx.net> 768c2ecf20Sopenharmony_ci * Version 1.0.8 778c2ecf20Sopenharmony_ci * - hardware frequency seek support 788c2ecf20Sopenharmony_ci * - afc indication 798c2ecf20Sopenharmony_ci * - more safety checks, let si470x_get_freq return errno 808c2ecf20Sopenharmony_ci * - vidioc behavior corrected according to v4l2 spec 818c2ecf20Sopenharmony_ci * 2008-10-20 Alexey Klimov <klimov.linux@gmail.com> 828c2ecf20Sopenharmony_ci * - add support for KWorld USB FM Radio FM700 838c2ecf20Sopenharmony_ci * - blacklisted KWorld radio in hid-core.c and hid-ids.h 848c2ecf20Sopenharmony_ci * 2008-12-03 Mark Lord <mlord@pobox.com> 858c2ecf20Sopenharmony_ci * - add support for DealExtreme USB Radio 868c2ecf20Sopenharmony_ci * 2009-01-31 Bob Ross <pigiron@gmx.com> 878c2ecf20Sopenharmony_ci * - correction of stereo detection/setting 888c2ecf20Sopenharmony_ci * - correction of signal strength indicator scaling 898c2ecf20Sopenharmony_ci * 2009-01-31 Rick Bronson <rick@efn.org> 908c2ecf20Sopenharmony_ci * Tobias Lorenz <tobias.lorenz@gmx.net> 918c2ecf20Sopenharmony_ci * - add LED status output 928c2ecf20Sopenharmony_ci * - get HW/SW version from scratchpad 938c2ecf20Sopenharmony_ci * 2009-06-16 Edouard Lafargue <edouard@lafargue.name> 948c2ecf20Sopenharmony_ci * Version 1.0.10 958c2ecf20Sopenharmony_ci * - add support for interrupt mode for RDS endpoint, 968c2ecf20Sopenharmony_ci * instead of polling. 978c2ecf20Sopenharmony_ci * Improves RDS reception significantly 988c2ecf20Sopenharmony_ci */ 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci/* kernel includes */ 1028c2ecf20Sopenharmony_ci#include "radio-si470x.h" 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci/************************************************************************** 1058c2ecf20Sopenharmony_ci * Module Parameters 1068c2ecf20Sopenharmony_ci **************************************************************************/ 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci/* Spacing (kHz) */ 1098c2ecf20Sopenharmony_ci/* 0: 200 kHz (USA, Australia) */ 1108c2ecf20Sopenharmony_ci/* 1: 100 kHz (Europe, Japan) */ 1118c2ecf20Sopenharmony_ci/* 2: 50 kHz */ 1128c2ecf20Sopenharmony_cistatic unsigned short space = 2; 1138c2ecf20Sopenharmony_cimodule_param(space, ushort, 0444); 1148c2ecf20Sopenharmony_ciMODULE_PARM_DESC(space, "Spacing: 0=200kHz 1=100kHz *2=50kHz*"); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci/* De-emphasis */ 1178c2ecf20Sopenharmony_ci/* 0: 75 us (USA) */ 1188c2ecf20Sopenharmony_ci/* 1: 50 us (Europe, Australia, Japan) */ 1198c2ecf20Sopenharmony_cistatic unsigned short de = 1; 1208c2ecf20Sopenharmony_cimodule_param(de, ushort, 0444); 1218c2ecf20Sopenharmony_ciMODULE_PARM_DESC(de, "De-emphasis: 0=75us *1=50us*"); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci/* Tune timeout */ 1248c2ecf20Sopenharmony_cistatic unsigned int tune_timeout = 3000; 1258c2ecf20Sopenharmony_cimodule_param(tune_timeout, uint, 0644); 1268c2ecf20Sopenharmony_ciMODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*"); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci/* Seek timeout */ 1298c2ecf20Sopenharmony_cistatic unsigned int seek_timeout = 5000; 1308c2ecf20Sopenharmony_cimodule_param(seek_timeout, uint, 0644); 1318c2ecf20Sopenharmony_ciMODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*"); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_cistatic const struct v4l2_frequency_band bands[] = { 1348c2ecf20Sopenharmony_ci { 1358c2ecf20Sopenharmony_ci .type = V4L2_TUNER_RADIO, 1368c2ecf20Sopenharmony_ci .index = 0, 1378c2ecf20Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 1388c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 1398c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS | 1408c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 1418c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_WRAP, 1428c2ecf20Sopenharmony_ci .rangelow = 87500 * 16, 1438c2ecf20Sopenharmony_ci .rangehigh = 108000 * 16, 1448c2ecf20Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 1458c2ecf20Sopenharmony_ci }, 1468c2ecf20Sopenharmony_ci { 1478c2ecf20Sopenharmony_ci .type = V4L2_TUNER_RADIO, 1488c2ecf20Sopenharmony_ci .index = 1, 1498c2ecf20Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 1508c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 1518c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS | 1528c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 1538c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_WRAP, 1548c2ecf20Sopenharmony_ci .rangelow = 76000 * 16, 1558c2ecf20Sopenharmony_ci .rangehigh = 108000 * 16, 1568c2ecf20Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 1578c2ecf20Sopenharmony_ci }, 1588c2ecf20Sopenharmony_ci { 1598c2ecf20Sopenharmony_ci .type = V4L2_TUNER_RADIO, 1608c2ecf20Sopenharmony_ci .index = 2, 1618c2ecf20Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 1628c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 1638c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS | 1648c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 1658c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_WRAP, 1668c2ecf20Sopenharmony_ci .rangelow = 76000 * 16, 1678c2ecf20Sopenharmony_ci .rangehigh = 90000 * 16, 1688c2ecf20Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 1698c2ecf20Sopenharmony_ci }, 1708c2ecf20Sopenharmony_ci}; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci/************************************************************************** 1738c2ecf20Sopenharmony_ci * Generic Functions 1748c2ecf20Sopenharmony_ci **************************************************************************/ 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci/* 1778c2ecf20Sopenharmony_ci * si470x_set_band - set the band 1788c2ecf20Sopenharmony_ci */ 1798c2ecf20Sopenharmony_cistatic int si470x_set_band(struct si470x_device *radio, int band) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci if (radio->band == band) 1828c2ecf20Sopenharmony_ci return 0; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci radio->band = band; 1858c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_BAND; 1868c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG2] |= radio->band << 6; 1878c2ecf20Sopenharmony_ci return radio->set_register(radio, SYSCONFIG2); 1888c2ecf20Sopenharmony_ci} 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci/* 1918c2ecf20Sopenharmony_ci * si470x_set_chan - set the channel 1928c2ecf20Sopenharmony_ci */ 1938c2ecf20Sopenharmony_cistatic int si470x_set_chan(struct si470x_device *radio, unsigned short chan) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci int retval; 1968c2ecf20Sopenharmony_ci unsigned long time_left; 1978c2ecf20Sopenharmony_ci bool timed_out = false; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci retval = radio->get_register(radio, POWERCFG); 2008c2ecf20Sopenharmony_ci if (retval) 2018c2ecf20Sopenharmony_ci return retval; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci if ((radio->registers[POWERCFG] & (POWERCFG_ENABLE|POWERCFG_DMUTE)) 2048c2ecf20Sopenharmony_ci != (POWERCFG_ENABLE|POWERCFG_DMUTE)) { 2058c2ecf20Sopenharmony_ci return 0; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci /* start tuning */ 2098c2ecf20Sopenharmony_ci radio->registers[CHANNEL] &= ~CHANNEL_CHAN; 2108c2ecf20Sopenharmony_ci radio->registers[CHANNEL] |= CHANNEL_TUNE | chan; 2118c2ecf20Sopenharmony_ci retval = radio->set_register(radio, CHANNEL); 2128c2ecf20Sopenharmony_ci if (retval < 0) 2138c2ecf20Sopenharmony_ci goto done; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci /* wait till tune operation has completed */ 2168c2ecf20Sopenharmony_ci reinit_completion(&radio->completion); 2178c2ecf20Sopenharmony_ci time_left = wait_for_completion_timeout(&radio->completion, 2188c2ecf20Sopenharmony_ci msecs_to_jiffies(tune_timeout)); 2198c2ecf20Sopenharmony_ci if (time_left == 0) 2208c2ecf20Sopenharmony_ci timed_out = true; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) 2238c2ecf20Sopenharmony_ci dev_warn(&radio->videodev.dev, "tune does not complete\n"); 2248c2ecf20Sopenharmony_ci if (timed_out) 2258c2ecf20Sopenharmony_ci dev_warn(&radio->videodev.dev, 2268c2ecf20Sopenharmony_ci "tune timed out after %u ms\n", tune_timeout); 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci /* stop tuning */ 2298c2ecf20Sopenharmony_ci radio->registers[CHANNEL] &= ~CHANNEL_TUNE; 2308c2ecf20Sopenharmony_ci retval = radio->set_register(radio, CHANNEL); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cidone: 2338c2ecf20Sopenharmony_ci return retval; 2348c2ecf20Sopenharmony_ci} 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci/* 2378c2ecf20Sopenharmony_ci * si470x_get_step - get channel spacing 2388c2ecf20Sopenharmony_ci */ 2398c2ecf20Sopenharmony_cistatic unsigned int si470x_get_step(struct si470x_device *radio) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci /* Spacing (kHz) */ 2428c2ecf20Sopenharmony_ci switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) { 2438c2ecf20Sopenharmony_ci /* 0: 200 kHz (USA, Australia) */ 2448c2ecf20Sopenharmony_ci case 0: 2458c2ecf20Sopenharmony_ci return 200 * 16; 2468c2ecf20Sopenharmony_ci /* 1: 100 kHz (Europe, Japan) */ 2478c2ecf20Sopenharmony_ci case 1: 2488c2ecf20Sopenharmony_ci return 100 * 16; 2498c2ecf20Sopenharmony_ci /* 2: 50 kHz */ 2508c2ecf20Sopenharmony_ci default: 2518c2ecf20Sopenharmony_ci return 50 * 16; 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci} 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci/* 2578c2ecf20Sopenharmony_ci * si470x_get_freq - get the frequency 2588c2ecf20Sopenharmony_ci */ 2598c2ecf20Sopenharmony_cistatic int si470x_get_freq(struct si470x_device *radio, unsigned int *freq) 2608c2ecf20Sopenharmony_ci{ 2618c2ecf20Sopenharmony_ci int chan, retval; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci /* read channel */ 2648c2ecf20Sopenharmony_ci retval = radio->get_register(radio, READCHAN); 2658c2ecf20Sopenharmony_ci chan = radio->registers[READCHAN] & READCHAN_READCHAN; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci /* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */ 2688c2ecf20Sopenharmony_ci *freq = chan * si470x_get_step(radio) + bands[radio->band].rangelow; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci return retval; 2718c2ecf20Sopenharmony_ci} 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci/* 2758c2ecf20Sopenharmony_ci * si470x_set_freq - set the frequency 2768c2ecf20Sopenharmony_ci */ 2778c2ecf20Sopenharmony_ciint si470x_set_freq(struct si470x_device *radio, unsigned int freq) 2788c2ecf20Sopenharmony_ci{ 2798c2ecf20Sopenharmony_ci unsigned short chan; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci freq = clamp(freq, bands[radio->band].rangelow, 2828c2ecf20Sopenharmony_ci bands[radio->band].rangehigh); 2838c2ecf20Sopenharmony_ci /* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */ 2848c2ecf20Sopenharmony_ci chan = (freq - bands[radio->band].rangelow) / si470x_get_step(radio); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci return si470x_set_chan(radio, chan); 2878c2ecf20Sopenharmony_ci} 2888c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(si470x_set_freq); 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci/* 2928c2ecf20Sopenharmony_ci * si470x_set_seek - set seek 2938c2ecf20Sopenharmony_ci */ 2948c2ecf20Sopenharmony_cistatic int si470x_set_seek(struct si470x_device *radio, 2958c2ecf20Sopenharmony_ci const struct v4l2_hw_freq_seek *seek) 2968c2ecf20Sopenharmony_ci{ 2978c2ecf20Sopenharmony_ci int band, retval; 2988c2ecf20Sopenharmony_ci unsigned int freq; 2998c2ecf20Sopenharmony_ci bool timed_out = false; 3008c2ecf20Sopenharmony_ci unsigned long time_left; 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci /* set band */ 3038c2ecf20Sopenharmony_ci if (seek->rangelow || seek->rangehigh) { 3048c2ecf20Sopenharmony_ci for (band = 0; band < ARRAY_SIZE(bands); band++) { 3058c2ecf20Sopenharmony_ci if (bands[band].rangelow == seek->rangelow && 3068c2ecf20Sopenharmony_ci bands[band].rangehigh == seek->rangehigh) 3078c2ecf20Sopenharmony_ci break; 3088c2ecf20Sopenharmony_ci } 3098c2ecf20Sopenharmony_ci if (band == ARRAY_SIZE(bands)) 3108c2ecf20Sopenharmony_ci return -EINVAL; /* No matching band found */ 3118c2ecf20Sopenharmony_ci } else 3128c2ecf20Sopenharmony_ci band = 1; /* If nothing is specified seek 76 - 108 Mhz */ 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci if (radio->band != band) { 3158c2ecf20Sopenharmony_ci retval = si470x_get_freq(radio, &freq); 3168c2ecf20Sopenharmony_ci if (retval) 3178c2ecf20Sopenharmony_ci return retval; 3188c2ecf20Sopenharmony_ci retval = si470x_set_band(radio, band); 3198c2ecf20Sopenharmony_ci if (retval) 3208c2ecf20Sopenharmony_ci return retval; 3218c2ecf20Sopenharmony_ci retval = si470x_set_freq(radio, freq); 3228c2ecf20Sopenharmony_ci if (retval) 3238c2ecf20Sopenharmony_ci return retval; 3248c2ecf20Sopenharmony_ci } 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci /* start seeking */ 3278c2ecf20Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_SEEK; 3288c2ecf20Sopenharmony_ci if (seek->wrap_around) 3298c2ecf20Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_SKMODE; 3308c2ecf20Sopenharmony_ci else 3318c2ecf20Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_SKMODE; 3328c2ecf20Sopenharmony_ci if (seek->seek_upward) 3338c2ecf20Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_SEEKUP; 3348c2ecf20Sopenharmony_ci else 3358c2ecf20Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP; 3368c2ecf20Sopenharmony_ci retval = radio->set_register(radio, POWERCFG); 3378c2ecf20Sopenharmony_ci if (retval < 0) 3388c2ecf20Sopenharmony_ci return retval; 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci /* wait till tune operation has completed */ 3418c2ecf20Sopenharmony_ci reinit_completion(&radio->completion); 3428c2ecf20Sopenharmony_ci time_left = wait_for_completion_timeout(&radio->completion, 3438c2ecf20Sopenharmony_ci msecs_to_jiffies(seek_timeout)); 3448c2ecf20Sopenharmony_ci if (time_left == 0) 3458c2ecf20Sopenharmony_ci timed_out = true; 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) 3488c2ecf20Sopenharmony_ci dev_warn(&radio->videodev.dev, "seek does not complete\n"); 3498c2ecf20Sopenharmony_ci if (radio->registers[STATUSRSSI] & STATUSRSSI_SF) 3508c2ecf20Sopenharmony_ci dev_warn(&radio->videodev.dev, 3518c2ecf20Sopenharmony_ci "seek failed / band limit reached\n"); 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci /* stop seeking */ 3548c2ecf20Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_SEEK; 3558c2ecf20Sopenharmony_ci retval = radio->set_register(radio, POWERCFG); 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci /* try again, if timed out */ 3588c2ecf20Sopenharmony_ci if (retval == 0 && timed_out) 3598c2ecf20Sopenharmony_ci return -ENODATA; 3608c2ecf20Sopenharmony_ci return retval; 3618c2ecf20Sopenharmony_ci} 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci/* 3658c2ecf20Sopenharmony_ci * si470x_start - switch on radio 3668c2ecf20Sopenharmony_ci */ 3678c2ecf20Sopenharmony_ciint si470x_start(struct si470x_device *radio) 3688c2ecf20Sopenharmony_ci{ 3698c2ecf20Sopenharmony_ci int retval; 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci /* powercfg */ 3728c2ecf20Sopenharmony_ci radio->registers[POWERCFG] = 3738c2ecf20Sopenharmony_ci POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM; 3748c2ecf20Sopenharmony_ci retval = radio->set_register(radio, POWERCFG); 3758c2ecf20Sopenharmony_ci if (retval < 0) 3768c2ecf20Sopenharmony_ci goto done; 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_ci /* sysconfig 1 */ 3798c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDSIEN | SYSCONFIG1_STCIEN | 3808c2ecf20Sopenharmony_ci SYSCONFIG1_RDS; 3818c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_GPIO2; 3828c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG1] |= SYSCONFIG1_GPIO2_INT; 3838c2ecf20Sopenharmony_ci if (de) 3848c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG1] |= SYSCONFIG1_DE; 3858c2ecf20Sopenharmony_ci retval = radio->set_register(radio, SYSCONFIG1); 3868c2ecf20Sopenharmony_ci if (retval < 0) 3878c2ecf20Sopenharmony_ci goto done; 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci /* sysconfig 2 */ 3908c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG2] = 3918c2ecf20Sopenharmony_ci (0x1f << 8) | /* SEEKTH */ 3928c2ecf20Sopenharmony_ci ((radio->band << 6) & SYSCONFIG2_BAND) |/* BAND */ 3938c2ecf20Sopenharmony_ci ((space << 4) & SYSCONFIG2_SPACE) | /* SPACE */ 3948c2ecf20Sopenharmony_ci 15; /* VOLUME (max) */ 3958c2ecf20Sopenharmony_ci retval = radio->set_register(radio, SYSCONFIG2); 3968c2ecf20Sopenharmony_ci if (retval < 0) 3978c2ecf20Sopenharmony_ci goto done; 3988c2ecf20Sopenharmony_ci 3998c2ecf20Sopenharmony_ci /* reset last channel */ 4008c2ecf20Sopenharmony_ci retval = si470x_set_chan(radio, 4018c2ecf20Sopenharmony_ci radio->registers[CHANNEL] & CHANNEL_CHAN); 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_cidone: 4048c2ecf20Sopenharmony_ci return retval; 4058c2ecf20Sopenharmony_ci} 4068c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(si470x_start); 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci/* 4108c2ecf20Sopenharmony_ci * si470x_stop - switch off radio 4118c2ecf20Sopenharmony_ci */ 4128c2ecf20Sopenharmony_ciint si470x_stop(struct si470x_device *radio) 4138c2ecf20Sopenharmony_ci{ 4148c2ecf20Sopenharmony_ci int retval; 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_ci /* sysconfig 1 */ 4178c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS; 4188c2ecf20Sopenharmony_ci retval = radio->set_register(radio, SYSCONFIG1); 4198c2ecf20Sopenharmony_ci if (retval < 0) 4208c2ecf20Sopenharmony_ci goto done; 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci /* powercfg */ 4238c2ecf20Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; 4248c2ecf20Sopenharmony_ci /* POWERCFG_ENABLE has to automatically go low */ 4258c2ecf20Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE; 4268c2ecf20Sopenharmony_ci retval = radio->set_register(radio, POWERCFG); 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_cidone: 4298c2ecf20Sopenharmony_ci return retval; 4308c2ecf20Sopenharmony_ci} 4318c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(si470x_stop); 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_ci/* 4358c2ecf20Sopenharmony_ci * si470x_rds_on - switch on rds reception 4368c2ecf20Sopenharmony_ci */ 4378c2ecf20Sopenharmony_cistatic int si470x_rds_on(struct si470x_device *radio) 4388c2ecf20Sopenharmony_ci{ 4398c2ecf20Sopenharmony_ci int retval; 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci /* sysconfig 1 */ 4428c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS; 4438c2ecf20Sopenharmony_ci retval = radio->set_register(radio, SYSCONFIG1); 4448c2ecf20Sopenharmony_ci if (retval < 0) 4458c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS; 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_ci return retval; 4488c2ecf20Sopenharmony_ci} 4498c2ecf20Sopenharmony_ci 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci/************************************************************************** 4538c2ecf20Sopenharmony_ci * File Operations Interface 4548c2ecf20Sopenharmony_ci **************************************************************************/ 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci/* 4578c2ecf20Sopenharmony_ci * si470x_fops_read - read RDS data 4588c2ecf20Sopenharmony_ci */ 4598c2ecf20Sopenharmony_cistatic ssize_t si470x_fops_read(struct file *file, char __user *buf, 4608c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 4618c2ecf20Sopenharmony_ci{ 4628c2ecf20Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 4638c2ecf20Sopenharmony_ci int retval = 0; 4648c2ecf20Sopenharmony_ci unsigned int block_count = 0; 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci /* switch on rds reception */ 4678c2ecf20Sopenharmony_ci if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) 4688c2ecf20Sopenharmony_ci si470x_rds_on(radio); 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci /* block if no new data available */ 4718c2ecf20Sopenharmony_ci while (radio->wr_index == radio->rd_index) { 4728c2ecf20Sopenharmony_ci if (file->f_flags & O_NONBLOCK) { 4738c2ecf20Sopenharmony_ci retval = -EWOULDBLOCK; 4748c2ecf20Sopenharmony_ci goto done; 4758c2ecf20Sopenharmony_ci } 4768c2ecf20Sopenharmony_ci if (wait_event_interruptible(radio->read_queue, 4778c2ecf20Sopenharmony_ci radio->wr_index != radio->rd_index) < 0) { 4788c2ecf20Sopenharmony_ci retval = -EINTR; 4798c2ecf20Sopenharmony_ci goto done; 4808c2ecf20Sopenharmony_ci } 4818c2ecf20Sopenharmony_ci } 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci /* calculate block count from byte count */ 4848c2ecf20Sopenharmony_ci count /= 3; 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ci /* copy RDS block out of internal buffer and to user buffer */ 4878c2ecf20Sopenharmony_ci while (block_count < count) { 4888c2ecf20Sopenharmony_ci if (radio->rd_index == radio->wr_index) 4898c2ecf20Sopenharmony_ci break; 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_ci /* always transfer rds complete blocks */ 4928c2ecf20Sopenharmony_ci if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3)) 4938c2ecf20Sopenharmony_ci /* retval = -EFAULT; */ 4948c2ecf20Sopenharmony_ci break; 4958c2ecf20Sopenharmony_ci 4968c2ecf20Sopenharmony_ci /* increment and wrap read pointer */ 4978c2ecf20Sopenharmony_ci radio->rd_index += 3; 4988c2ecf20Sopenharmony_ci if (radio->rd_index >= radio->buf_size) 4998c2ecf20Sopenharmony_ci radio->rd_index = 0; 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_ci /* increment counters */ 5028c2ecf20Sopenharmony_ci block_count++; 5038c2ecf20Sopenharmony_ci buf += 3; 5048c2ecf20Sopenharmony_ci retval += 3; 5058c2ecf20Sopenharmony_ci } 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_cidone: 5088c2ecf20Sopenharmony_ci return retval; 5098c2ecf20Sopenharmony_ci} 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_ci/* 5138c2ecf20Sopenharmony_ci * si470x_fops_poll - poll RDS data 5148c2ecf20Sopenharmony_ci */ 5158c2ecf20Sopenharmony_cistatic __poll_t si470x_fops_poll(struct file *file, 5168c2ecf20Sopenharmony_ci struct poll_table_struct *pts) 5178c2ecf20Sopenharmony_ci{ 5188c2ecf20Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 5198c2ecf20Sopenharmony_ci __poll_t req_events = poll_requested_events(pts); 5208c2ecf20Sopenharmony_ci __poll_t retval = v4l2_ctrl_poll(file, pts); 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci if (req_events & (EPOLLIN | EPOLLRDNORM)) { 5238c2ecf20Sopenharmony_ci /* switch on rds reception */ 5248c2ecf20Sopenharmony_ci if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) 5258c2ecf20Sopenharmony_ci si470x_rds_on(radio); 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_ci poll_wait(file, &radio->read_queue, pts); 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_ci if (radio->rd_index != radio->wr_index) 5308c2ecf20Sopenharmony_ci retval |= EPOLLIN | EPOLLRDNORM; 5318c2ecf20Sopenharmony_ci } 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci return retval; 5348c2ecf20Sopenharmony_ci} 5358c2ecf20Sopenharmony_ci 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_cistatic int si470x_fops_open(struct file *file) 5388c2ecf20Sopenharmony_ci{ 5398c2ecf20Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 5408c2ecf20Sopenharmony_ci 5418c2ecf20Sopenharmony_ci return radio->fops_open(file); 5428c2ecf20Sopenharmony_ci} 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci/* 5468c2ecf20Sopenharmony_ci * si470x_fops_release - file release 5478c2ecf20Sopenharmony_ci */ 5488c2ecf20Sopenharmony_cistatic int si470x_fops_release(struct file *file) 5498c2ecf20Sopenharmony_ci{ 5508c2ecf20Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_ci return radio->fops_release(file); 5538c2ecf20Sopenharmony_ci} 5548c2ecf20Sopenharmony_ci 5558c2ecf20Sopenharmony_ci 5568c2ecf20Sopenharmony_ci/* 5578c2ecf20Sopenharmony_ci * si470x_fops - file operations interface 5588c2ecf20Sopenharmony_ci */ 5598c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations si470x_fops = { 5608c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 5618c2ecf20Sopenharmony_ci .read = si470x_fops_read, 5628c2ecf20Sopenharmony_ci .poll = si470x_fops_poll, 5638c2ecf20Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 5648c2ecf20Sopenharmony_ci .open = si470x_fops_open, 5658c2ecf20Sopenharmony_ci .release = si470x_fops_release, 5668c2ecf20Sopenharmony_ci}; 5678c2ecf20Sopenharmony_ci 5688c2ecf20Sopenharmony_ci 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci/************************************************************************** 5718c2ecf20Sopenharmony_ci * Video4Linux Interface 5728c2ecf20Sopenharmony_ci **************************************************************************/ 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_ci 5758c2ecf20Sopenharmony_cistatic int si470x_s_ctrl(struct v4l2_ctrl *ctrl) 5768c2ecf20Sopenharmony_ci{ 5778c2ecf20Sopenharmony_ci struct si470x_device *radio = 5788c2ecf20Sopenharmony_ci container_of(ctrl->handler, struct si470x_device, hdl); 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_ci switch (ctrl->id) { 5818c2ecf20Sopenharmony_ci case V4L2_CID_AUDIO_VOLUME: 5828c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME; 5838c2ecf20Sopenharmony_ci radio->registers[SYSCONFIG2] |= ctrl->val; 5848c2ecf20Sopenharmony_ci return radio->set_register(radio, SYSCONFIG2); 5858c2ecf20Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 5868c2ecf20Sopenharmony_ci if (ctrl->val) 5878c2ecf20Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; 5888c2ecf20Sopenharmony_ci else 5898c2ecf20Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_DMUTE; 5908c2ecf20Sopenharmony_ci return radio->set_register(radio, POWERCFG); 5918c2ecf20Sopenharmony_ci default: 5928c2ecf20Sopenharmony_ci return -EINVAL; 5938c2ecf20Sopenharmony_ci } 5948c2ecf20Sopenharmony_ci} 5958c2ecf20Sopenharmony_ci 5968c2ecf20Sopenharmony_ci 5978c2ecf20Sopenharmony_ci/* 5988c2ecf20Sopenharmony_ci * si470x_vidioc_g_tuner - get tuner attributes 5998c2ecf20Sopenharmony_ci */ 6008c2ecf20Sopenharmony_cistatic int si470x_vidioc_g_tuner(struct file *file, void *priv, 6018c2ecf20Sopenharmony_ci struct v4l2_tuner *tuner) 6028c2ecf20Sopenharmony_ci{ 6038c2ecf20Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 6048c2ecf20Sopenharmony_ci int retval = 0; 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci if (tuner->index != 0) 6078c2ecf20Sopenharmony_ci return -EINVAL; 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ci if (!radio->status_rssi_auto_update) { 6108c2ecf20Sopenharmony_ci retval = radio->get_register(radio, STATUSRSSI); 6118c2ecf20Sopenharmony_ci if (retval < 0) 6128c2ecf20Sopenharmony_ci return retval; 6138c2ecf20Sopenharmony_ci } 6148c2ecf20Sopenharmony_ci 6158c2ecf20Sopenharmony_ci /* driver constants */ 6168c2ecf20Sopenharmony_ci strscpy(tuner->name, "FM", sizeof(tuner->name)); 6178c2ecf20Sopenharmony_ci tuner->type = V4L2_TUNER_RADIO; 6188c2ecf20Sopenharmony_ci tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 6198c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | 6208c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_BOUNDED | 6218c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_WRAP; 6228c2ecf20Sopenharmony_ci tuner->rangelow = 76 * FREQ_MUL; 6238c2ecf20Sopenharmony_ci tuner->rangehigh = 108 * FREQ_MUL; 6248c2ecf20Sopenharmony_ci 6258c2ecf20Sopenharmony_ci /* stereo indicator == stereo (instead of mono) */ 6268c2ecf20Sopenharmony_ci if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0) 6278c2ecf20Sopenharmony_ci tuner->rxsubchans = V4L2_TUNER_SUB_MONO; 6288c2ecf20Sopenharmony_ci else 6298c2ecf20Sopenharmony_ci tuner->rxsubchans = V4L2_TUNER_SUB_STEREO; 6308c2ecf20Sopenharmony_ci /* If there is a reliable method of detecting an RDS channel, 6318c2ecf20Sopenharmony_ci then this code should check for that before setting this 6328c2ecf20Sopenharmony_ci RDS subchannel. */ 6338c2ecf20Sopenharmony_ci tuner->rxsubchans |= V4L2_TUNER_SUB_RDS; 6348c2ecf20Sopenharmony_ci 6358c2ecf20Sopenharmony_ci /* mono/stereo selector */ 6368c2ecf20Sopenharmony_ci if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 0) 6378c2ecf20Sopenharmony_ci tuner->audmode = V4L2_TUNER_MODE_STEREO; 6388c2ecf20Sopenharmony_ci else 6398c2ecf20Sopenharmony_ci tuner->audmode = V4L2_TUNER_MODE_MONO; 6408c2ecf20Sopenharmony_ci 6418c2ecf20Sopenharmony_ci /* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */ 6428c2ecf20Sopenharmony_ci /* measured in units of dbµV in 1 db increments (max at ~75 dbµV) */ 6438c2ecf20Sopenharmony_ci tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI); 6448c2ecf20Sopenharmony_ci /* the ideal factor is 0xffff/75 = 873,8 */ 6458c2ecf20Sopenharmony_ci tuner->signal = (tuner->signal * 873) + (8 * tuner->signal / 10); 6468c2ecf20Sopenharmony_ci if (tuner->signal > 0xffff) 6478c2ecf20Sopenharmony_ci tuner->signal = 0xffff; 6488c2ecf20Sopenharmony_ci 6498c2ecf20Sopenharmony_ci /* automatic frequency control: -1: freq to low, 1 freq to high */ 6508c2ecf20Sopenharmony_ci /* AFCRL does only indicate that freq. differs, not if too low/high */ 6518c2ecf20Sopenharmony_ci tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0; 6528c2ecf20Sopenharmony_ci 6538c2ecf20Sopenharmony_ci return retval; 6548c2ecf20Sopenharmony_ci} 6558c2ecf20Sopenharmony_ci 6568c2ecf20Sopenharmony_ci 6578c2ecf20Sopenharmony_ci/* 6588c2ecf20Sopenharmony_ci * si470x_vidioc_s_tuner - set tuner attributes 6598c2ecf20Sopenharmony_ci */ 6608c2ecf20Sopenharmony_cistatic int si470x_vidioc_s_tuner(struct file *file, void *priv, 6618c2ecf20Sopenharmony_ci const struct v4l2_tuner *tuner) 6628c2ecf20Sopenharmony_ci{ 6638c2ecf20Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 6648c2ecf20Sopenharmony_ci 6658c2ecf20Sopenharmony_ci if (tuner->index != 0) 6668c2ecf20Sopenharmony_ci return -EINVAL; 6678c2ecf20Sopenharmony_ci 6688c2ecf20Sopenharmony_ci /* mono/stereo selector */ 6698c2ecf20Sopenharmony_ci switch (tuner->audmode) { 6708c2ecf20Sopenharmony_ci case V4L2_TUNER_MODE_MONO: 6718c2ecf20Sopenharmony_ci radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */ 6728c2ecf20Sopenharmony_ci break; 6738c2ecf20Sopenharmony_ci case V4L2_TUNER_MODE_STEREO: 6748c2ecf20Sopenharmony_ci default: 6758c2ecf20Sopenharmony_ci radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */ 6768c2ecf20Sopenharmony_ci break; 6778c2ecf20Sopenharmony_ci } 6788c2ecf20Sopenharmony_ci 6798c2ecf20Sopenharmony_ci return radio->set_register(radio, POWERCFG); 6808c2ecf20Sopenharmony_ci} 6818c2ecf20Sopenharmony_ci 6828c2ecf20Sopenharmony_ci 6838c2ecf20Sopenharmony_ci/* 6848c2ecf20Sopenharmony_ci * si470x_vidioc_g_frequency - get tuner or modulator radio frequency 6858c2ecf20Sopenharmony_ci */ 6868c2ecf20Sopenharmony_cistatic int si470x_vidioc_g_frequency(struct file *file, void *priv, 6878c2ecf20Sopenharmony_ci struct v4l2_frequency *freq) 6888c2ecf20Sopenharmony_ci{ 6898c2ecf20Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 6908c2ecf20Sopenharmony_ci 6918c2ecf20Sopenharmony_ci if (freq->tuner != 0) 6928c2ecf20Sopenharmony_ci return -EINVAL; 6938c2ecf20Sopenharmony_ci 6948c2ecf20Sopenharmony_ci freq->type = V4L2_TUNER_RADIO; 6958c2ecf20Sopenharmony_ci return si470x_get_freq(radio, &freq->frequency); 6968c2ecf20Sopenharmony_ci} 6978c2ecf20Sopenharmony_ci 6988c2ecf20Sopenharmony_ci 6998c2ecf20Sopenharmony_ci/* 7008c2ecf20Sopenharmony_ci * si470x_vidioc_s_frequency - set tuner or modulator radio frequency 7018c2ecf20Sopenharmony_ci */ 7028c2ecf20Sopenharmony_cistatic int si470x_vidioc_s_frequency(struct file *file, void *priv, 7038c2ecf20Sopenharmony_ci const struct v4l2_frequency *freq) 7048c2ecf20Sopenharmony_ci{ 7058c2ecf20Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 7068c2ecf20Sopenharmony_ci int retval; 7078c2ecf20Sopenharmony_ci 7088c2ecf20Sopenharmony_ci if (freq->tuner != 0) 7098c2ecf20Sopenharmony_ci return -EINVAL; 7108c2ecf20Sopenharmony_ci 7118c2ecf20Sopenharmony_ci if (freq->frequency < bands[radio->band].rangelow || 7128c2ecf20Sopenharmony_ci freq->frequency > bands[radio->band].rangehigh) { 7138c2ecf20Sopenharmony_ci /* Switch to band 1 which covers everything we support */ 7148c2ecf20Sopenharmony_ci retval = si470x_set_band(radio, 1); 7158c2ecf20Sopenharmony_ci if (retval) 7168c2ecf20Sopenharmony_ci return retval; 7178c2ecf20Sopenharmony_ci } 7188c2ecf20Sopenharmony_ci return si470x_set_freq(radio, freq->frequency); 7198c2ecf20Sopenharmony_ci} 7208c2ecf20Sopenharmony_ci 7218c2ecf20Sopenharmony_ci 7228c2ecf20Sopenharmony_ci/* 7238c2ecf20Sopenharmony_ci * si470x_vidioc_s_hw_freq_seek - set hardware frequency seek 7248c2ecf20Sopenharmony_ci */ 7258c2ecf20Sopenharmony_cistatic int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv, 7268c2ecf20Sopenharmony_ci const struct v4l2_hw_freq_seek *seek) 7278c2ecf20Sopenharmony_ci{ 7288c2ecf20Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 7298c2ecf20Sopenharmony_ci 7308c2ecf20Sopenharmony_ci if (seek->tuner != 0) 7318c2ecf20Sopenharmony_ci return -EINVAL; 7328c2ecf20Sopenharmony_ci 7338c2ecf20Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 7348c2ecf20Sopenharmony_ci return -EWOULDBLOCK; 7358c2ecf20Sopenharmony_ci 7368c2ecf20Sopenharmony_ci return si470x_set_seek(radio, seek); 7378c2ecf20Sopenharmony_ci} 7388c2ecf20Sopenharmony_ci 7398c2ecf20Sopenharmony_ci/* 7408c2ecf20Sopenharmony_ci * si470x_vidioc_enum_freq_bands - enumerate supported bands 7418c2ecf20Sopenharmony_ci */ 7428c2ecf20Sopenharmony_cistatic int si470x_vidioc_enum_freq_bands(struct file *file, void *priv, 7438c2ecf20Sopenharmony_ci struct v4l2_frequency_band *band) 7448c2ecf20Sopenharmony_ci{ 7458c2ecf20Sopenharmony_ci if (band->tuner != 0) 7468c2ecf20Sopenharmony_ci return -EINVAL; 7478c2ecf20Sopenharmony_ci if (band->index >= ARRAY_SIZE(bands)) 7488c2ecf20Sopenharmony_ci return -EINVAL; 7498c2ecf20Sopenharmony_ci *band = bands[band->index]; 7508c2ecf20Sopenharmony_ci return 0; 7518c2ecf20Sopenharmony_ci} 7528c2ecf20Sopenharmony_ci 7538c2ecf20Sopenharmony_ciconst struct v4l2_ctrl_ops si470x_ctrl_ops = { 7548c2ecf20Sopenharmony_ci .s_ctrl = si470x_s_ctrl, 7558c2ecf20Sopenharmony_ci}; 7568c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(si470x_ctrl_ops); 7578c2ecf20Sopenharmony_ci 7588c2ecf20Sopenharmony_cistatic int si470x_vidioc_querycap(struct file *file, void *priv, 7598c2ecf20Sopenharmony_ci struct v4l2_capability *capability) 7608c2ecf20Sopenharmony_ci{ 7618c2ecf20Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 7628c2ecf20Sopenharmony_ci 7638c2ecf20Sopenharmony_ci return radio->vidioc_querycap(file, priv, capability); 7648c2ecf20Sopenharmony_ci}; 7658c2ecf20Sopenharmony_ci 7668c2ecf20Sopenharmony_ci/* 7678c2ecf20Sopenharmony_ci * si470x_ioctl_ops - video device ioctl operations 7688c2ecf20Sopenharmony_ci */ 7698c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops si470x_ioctl_ops = { 7708c2ecf20Sopenharmony_ci .vidioc_querycap = si470x_vidioc_querycap, 7718c2ecf20Sopenharmony_ci .vidioc_g_tuner = si470x_vidioc_g_tuner, 7728c2ecf20Sopenharmony_ci .vidioc_s_tuner = si470x_vidioc_s_tuner, 7738c2ecf20Sopenharmony_ci .vidioc_g_frequency = si470x_vidioc_g_frequency, 7748c2ecf20Sopenharmony_ci .vidioc_s_frequency = si470x_vidioc_s_frequency, 7758c2ecf20Sopenharmony_ci .vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek, 7768c2ecf20Sopenharmony_ci .vidioc_enum_freq_bands = si470x_vidioc_enum_freq_bands, 7778c2ecf20Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 7788c2ecf20Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 7798c2ecf20Sopenharmony_ci}; 7808c2ecf20Sopenharmony_ci 7818c2ecf20Sopenharmony_ci 7828c2ecf20Sopenharmony_ci/* 7838c2ecf20Sopenharmony_ci * si470x_viddev_template - video device interface 7848c2ecf20Sopenharmony_ci */ 7858c2ecf20Sopenharmony_ciconst struct video_device si470x_viddev_template = { 7868c2ecf20Sopenharmony_ci .fops = &si470x_fops, 7878c2ecf20Sopenharmony_ci .name = DRIVER_NAME, 7888c2ecf20Sopenharmony_ci .release = video_device_release_empty, 7898c2ecf20Sopenharmony_ci .ioctl_ops = &si470x_ioctl_ops, 7908c2ecf20Sopenharmony_ci}; 7918c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(si470x_viddev_template); 7928c2ecf20Sopenharmony_ci 7938c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 794