Path: blob/master/drivers/media/radio/si470x/radio-si470x-common.c
15157 views
/*1* drivers/media/radio/si470x/radio-si470x-common.c2*3* Driver for radios with Silicon Labs Si470x FM Radio Receivers4*5* Copyright (c) 2009 Tobias Lorenz <[email protected]>6*7* This program is free software; you can redistribute it and/or modify8* it under the terms of the GNU General Public License as published by9* the Free Software Foundation; either version 2 of the License, or10* (at your option) any later version.11*12* This program is distributed in the hope that it will be useful,13* but WITHOUT ANY WARRANTY; without even the implied warranty of14* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the15* GNU General Public License for more details.16*17* You should have received a copy of the GNU General Public License18* along with this program; if not, write to the Free Software19* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA20*/212223/*24* History:25* 2008-01-12 Tobias Lorenz <[email protected]>26* Version 1.0.027* - First working version28* 2008-01-13 Tobias Lorenz <[email protected]>29* Version 1.0.130* - Improved error handling, every function now returns errno31* - Improved multi user access (start/mute/stop)32* - Channel doesn't get lost anymore after start/mute/stop33* - RDS support added (polling mode via interrupt EP 1)34* - marked default module parameters with *value*35* - switched from bit structs to bit masks36* - header file cleaned and integrated37* 2008-01-14 Tobias Lorenz <[email protected]>38* Version 1.0.239* - hex values are now lower case40* - commented USB ID for ADS/Tech moved on todo list41* - blacklisted si470x in hid-quirks.c42* - rds buffer handling functions integrated into *_work, *_read43* - rds_command in si470x_poll exchanged against simple retval44* - check for firmware version 1545* - code order and prototypes still remain the same46* - spacing and bottom of band codes remain the same47* 2008-01-16 Tobias Lorenz <[email protected]>48* Version 1.0.349* - code reordered to avoid function prototypes50* - switch/case defaults are now more user-friendly51* - unified comment style52* - applied all checkpatch.pl v1.12 suggestions53* except the warning about the too long lines with bit comments54* - renamed FMRADIO to RADIO to cut line length (checkpatch.pl)55* 2008-01-22 Tobias Lorenz <[email protected]>56* Version 1.0.457* - avoid poss. locking when doing copy_to_user which may sleep58* - RDS is automatically activated on read now59* - code cleaned of unnecessary rds_commands60* - USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified61* (thanks to Guillaume RAMOUSSE)62* 2008-01-27 Tobias Lorenz <[email protected]>63* Version 1.0.564* - number of seek_retries changed to tune_timeout65* - fixed problem with incomplete tune operations by own buffers66* - optimization of variables and printf types67* - improved error logging68* 2008-01-31 Tobias Lorenz <[email protected]>69* Oliver Neukum <[email protected]>70* Version 1.0.671* - fixed coverity checker warnings in *_usb_driver_disconnect72* - probe()/open() race by correct ordering in probe()73* - DMA coherency rules by separate allocation of all buffers74* - use of endianness macros75* - abuse of spinlock, replaced by mutex76* - racy handling of timer in disconnect,77* replaced by delayed_work78* - racy interruptible_sleep_on(),79* replaced with wait_event_interruptible()80* - handle signals in read()81* 2008-02-08 Tobias Lorenz <[email protected]>82* Oliver Neukum <[email protected]>83* Version 1.0.784* - usb autosuspend support85* - unplugging fixed86* 2008-05-07 Tobias Lorenz <[email protected]>87* Version 1.0.888* - hardware frequency seek support89* - afc indication90* - more safety checks, let si470x_get_freq return errno91* - vidioc behavior corrected according to v4l2 spec92* 2008-10-20 Alexey Klimov <[email protected]>93* - add support for KWorld USB FM Radio FM70094* - blacklisted KWorld radio in hid-core.c and hid-ids.h95* 2008-12-03 Mark Lord <[email protected]>96* - add support for DealExtreme USB Radio97* 2009-01-31 Bob Ross <[email protected]>98* - correction of stereo detection/setting99* - correction of signal strength indicator scaling100* 2009-01-31 Rick Bronson <[email protected]>101* Tobias Lorenz <[email protected]>102* - add LED status output103* - get HW/SW version from scratchpad104* 2009-06-16 Edouard Lafargue <[email protected]>105* Version 1.0.10106* - add support for interrupt mode for RDS endpoint,107* instead of polling.108* Improves RDS reception significantly109*/110111112/* kernel includes */113#include "radio-si470x.h"114115116117/**************************************************************************118* Module Parameters119**************************************************************************/120121/* Spacing (kHz) */122/* 0: 200 kHz (USA, Australia) */123/* 1: 100 kHz (Europe, Japan) */124/* 2: 50 kHz */125static unsigned short space = 2;126module_param(space, ushort, 0444);127MODULE_PARM_DESC(space, "Spacing: 0=200kHz 1=100kHz *2=50kHz*");128129/* Bottom of Band (MHz) */130/* 0: 87.5 - 108 MHz (USA, Europe)*/131/* 1: 76 - 108 MHz (Japan wide band) */132/* 2: 76 - 90 MHz (Japan) */133static unsigned short band = 1;134module_param(band, ushort, 0444);135MODULE_PARM_DESC(band, "Band: 0=87.5..108MHz *1=76..108MHz* 2=76..90MHz");136137/* De-emphasis */138/* 0: 75 us (USA) */139/* 1: 50 us (Europe, Australia, Japan) */140static unsigned short de = 1;141module_param(de, ushort, 0444);142MODULE_PARM_DESC(de, "De-emphasis: 0=75us *1=50us*");143144/* Tune timeout */145static unsigned int tune_timeout = 3000;146module_param(tune_timeout, uint, 0644);147MODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*");148149/* Seek timeout */150static unsigned int seek_timeout = 5000;151module_param(seek_timeout, uint, 0644);152MODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*");153154155156/**************************************************************************157* Generic Functions158**************************************************************************/159160/*161* si470x_set_chan - set the channel162*/163static int si470x_set_chan(struct si470x_device *radio, unsigned short chan)164{165int retval;166unsigned long timeout;167bool timed_out = 0;168169/* start tuning */170radio->registers[CHANNEL] &= ~CHANNEL_CHAN;171radio->registers[CHANNEL] |= CHANNEL_TUNE | chan;172retval = si470x_set_register(radio, CHANNEL);173if (retval < 0)174goto done;175176/* currently I2C driver only uses interrupt way to tune */177if (radio->stci_enabled) {178INIT_COMPLETION(radio->completion);179180/* wait till tune operation has completed */181retval = wait_for_completion_timeout(&radio->completion,182msecs_to_jiffies(tune_timeout));183if (!retval)184timed_out = true;185} else {186/* wait till tune operation has completed */187timeout = jiffies + msecs_to_jiffies(tune_timeout);188do {189retval = si470x_get_register(radio, STATUSRSSI);190if (retval < 0)191goto stop;192timed_out = time_after(jiffies, timeout);193} while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)194&& (!timed_out));195}196197if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)198dev_warn(&radio->videodev->dev, "tune does not complete\n");199if (timed_out)200dev_warn(&radio->videodev->dev,201"tune timed out after %u ms\n", tune_timeout);202203stop:204/* stop tuning */205radio->registers[CHANNEL] &= ~CHANNEL_TUNE;206retval = si470x_set_register(radio, CHANNEL);207208done:209return retval;210}211212213/*214* si470x_get_freq - get the frequency215*/216static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq)217{218unsigned int spacing, band_bottom;219unsigned short chan;220int retval;221222/* Spacing (kHz) */223switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {224/* 0: 200 kHz (USA, Australia) */225case 0:226spacing = 0.200 * FREQ_MUL; break;227/* 1: 100 kHz (Europe, Japan) */228case 1:229spacing = 0.100 * FREQ_MUL; break;230/* 2: 50 kHz */231default:232spacing = 0.050 * FREQ_MUL; break;233};234235/* Bottom of Band (MHz) */236switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {237/* 0: 87.5 - 108 MHz (USA, Europe) */238case 0:239band_bottom = 87.5 * FREQ_MUL; break;240/* 1: 76 - 108 MHz (Japan wide band) */241default:242band_bottom = 76 * FREQ_MUL; break;243/* 2: 76 - 90 MHz (Japan) */244case 2:245band_bottom = 76 * FREQ_MUL; break;246};247248/* read channel */249retval = si470x_get_register(radio, READCHAN);250chan = radio->registers[READCHAN] & READCHAN_READCHAN;251252/* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */253*freq = chan * spacing + band_bottom;254255return retval;256}257258259/*260* si470x_set_freq - set the frequency261*/262int si470x_set_freq(struct si470x_device *radio, unsigned int freq)263{264unsigned int spacing, band_bottom;265unsigned short chan;266267/* Spacing (kHz) */268switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {269/* 0: 200 kHz (USA, Australia) */270case 0:271spacing = 0.200 * FREQ_MUL; break;272/* 1: 100 kHz (Europe, Japan) */273case 1:274spacing = 0.100 * FREQ_MUL; break;275/* 2: 50 kHz */276default:277spacing = 0.050 * FREQ_MUL; break;278};279280/* Bottom of Band (MHz) */281switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {282/* 0: 87.5 - 108 MHz (USA, Europe) */283case 0:284band_bottom = 87.5 * FREQ_MUL; break;285/* 1: 76 - 108 MHz (Japan wide band) */286default:287band_bottom = 76 * FREQ_MUL; break;288/* 2: 76 - 90 MHz (Japan) */289case 2:290band_bottom = 76 * FREQ_MUL; break;291};292293/* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */294chan = (freq - band_bottom) / spacing;295296return si470x_set_chan(radio, chan);297}298299300/*301* si470x_set_seek - set seek302*/303static int si470x_set_seek(struct si470x_device *radio,304unsigned int wrap_around, unsigned int seek_upward)305{306int retval = 0;307unsigned long timeout;308bool timed_out = 0;309310/* start seeking */311radio->registers[POWERCFG] |= POWERCFG_SEEK;312if (wrap_around == 1)313radio->registers[POWERCFG] &= ~POWERCFG_SKMODE;314else315radio->registers[POWERCFG] |= POWERCFG_SKMODE;316if (seek_upward == 1)317radio->registers[POWERCFG] |= POWERCFG_SEEKUP;318else319radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP;320retval = si470x_set_register(radio, POWERCFG);321if (retval < 0)322goto done;323324/* currently I2C driver only uses interrupt way to seek */325if (radio->stci_enabled) {326INIT_COMPLETION(radio->completion);327328/* wait till seek operation has completed */329retval = wait_for_completion_timeout(&radio->completion,330msecs_to_jiffies(seek_timeout));331if (!retval)332timed_out = true;333} else {334/* wait till seek operation has completed */335timeout = jiffies + msecs_to_jiffies(seek_timeout);336do {337retval = si470x_get_register(radio, STATUSRSSI);338if (retval < 0)339goto stop;340timed_out = time_after(jiffies, timeout);341} while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)342&& (!timed_out));343}344345if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)346dev_warn(&radio->videodev->dev, "seek does not complete\n");347if (radio->registers[STATUSRSSI] & STATUSRSSI_SF)348dev_warn(&radio->videodev->dev,349"seek failed / band limit reached\n");350if (timed_out)351dev_warn(&radio->videodev->dev,352"seek timed out after %u ms\n", seek_timeout);353354stop:355/* stop seeking */356radio->registers[POWERCFG] &= ~POWERCFG_SEEK;357retval = si470x_set_register(radio, POWERCFG);358359done:360/* try again, if timed out */361if ((retval == 0) && timed_out)362retval = -EAGAIN;363364return retval;365}366367368/*369* si470x_start - switch on radio370*/371int si470x_start(struct si470x_device *radio)372{373int retval;374375/* powercfg */376radio->registers[POWERCFG] =377POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM;378retval = si470x_set_register(radio, POWERCFG);379if (retval < 0)380goto done;381382/* sysconfig 1 */383radio->registers[SYSCONFIG1] =384(de << 11) & SYSCONFIG1_DE; /* DE*/385retval = si470x_set_register(radio, SYSCONFIG1);386if (retval < 0)387goto done;388389/* sysconfig 2 */390radio->registers[SYSCONFIG2] =391(0x3f << 8) | /* SEEKTH */392((band << 6) & SYSCONFIG2_BAND) | /* BAND */393((space << 4) & SYSCONFIG2_SPACE) | /* SPACE */39415; /* VOLUME (max) */395retval = si470x_set_register(radio, SYSCONFIG2);396if (retval < 0)397goto done;398399/* reset last channel */400retval = si470x_set_chan(radio,401radio->registers[CHANNEL] & CHANNEL_CHAN);402403done:404return retval;405}406407408/*409* si470x_stop - switch off radio410*/411int si470x_stop(struct si470x_device *radio)412{413int retval;414415/* sysconfig 1 */416radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;417retval = si470x_set_register(radio, SYSCONFIG1);418if (retval < 0)419goto done;420421/* powercfg */422radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;423/* POWERCFG_ENABLE has to automatically go low */424radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE;425retval = si470x_set_register(radio, POWERCFG);426427done:428return retval;429}430431432/*433* si470x_rds_on - switch on rds reception434*/435static int si470x_rds_on(struct si470x_device *radio)436{437int retval;438439/* sysconfig 1 */440radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS;441retval = si470x_set_register(radio, SYSCONFIG1);442if (retval < 0)443radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;444445return retval;446}447448449450/**************************************************************************451* File Operations Interface452**************************************************************************/453454/*455* si470x_fops_read - read RDS data456*/457static ssize_t si470x_fops_read(struct file *file, char __user *buf,458size_t count, loff_t *ppos)459{460struct si470x_device *radio = video_drvdata(file);461int retval = 0;462unsigned int block_count = 0;463464/* switch on rds reception */465mutex_lock(&radio->lock);466if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)467si470x_rds_on(radio);468469/* block if no new data available */470while (radio->wr_index == radio->rd_index) {471if (file->f_flags & O_NONBLOCK) {472retval = -EWOULDBLOCK;473goto done;474}475if (wait_event_interruptible(radio->read_queue,476radio->wr_index != radio->rd_index) < 0) {477retval = -EINTR;478goto done;479}480}481482/* calculate block count from byte count */483count /= 3;484485/* copy RDS block out of internal buffer and to user buffer */486while (block_count < count) {487if (radio->rd_index == radio->wr_index)488break;489490/* always transfer rds complete blocks */491if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3))492/* retval = -EFAULT; */493break;494495/* increment and wrap read pointer */496radio->rd_index += 3;497if (radio->rd_index >= radio->buf_size)498radio->rd_index = 0;499500/* increment counters */501block_count++;502buf += 3;503retval += 3;504}505506done:507mutex_unlock(&radio->lock);508return retval;509}510511512/*513* si470x_fops_poll - poll RDS data514*/515static unsigned int si470x_fops_poll(struct file *file,516struct poll_table_struct *pts)517{518struct si470x_device *radio = video_drvdata(file);519int retval = 0;520521/* switch on rds reception */522523mutex_lock(&radio->lock);524if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)525si470x_rds_on(radio);526mutex_unlock(&radio->lock);527528poll_wait(file, &radio->read_queue, pts);529530if (radio->rd_index != radio->wr_index)531retval = POLLIN | POLLRDNORM;532533return retval;534}535536537/*538* si470x_fops - file operations interface539*/540static const struct v4l2_file_operations si470x_fops = {541.owner = THIS_MODULE,542.read = si470x_fops_read,543.poll = si470x_fops_poll,544.unlocked_ioctl = video_ioctl2,545.open = si470x_fops_open,546.release = si470x_fops_release,547};548549550551/**************************************************************************552* Video4Linux Interface553**************************************************************************/554555/*556* si470x_vidioc_queryctrl - enumerate control items557*/558static int si470x_vidioc_queryctrl(struct file *file, void *priv,559struct v4l2_queryctrl *qc)560{561struct si470x_device *radio = video_drvdata(file);562int retval = -EINVAL;563564/* abort if qc->id is below V4L2_CID_BASE */565if (qc->id < V4L2_CID_BASE)566goto done;567568/* search video control */569switch (qc->id) {570case V4L2_CID_AUDIO_VOLUME:571return v4l2_ctrl_query_fill(qc, 0, 15, 1, 15);572case V4L2_CID_AUDIO_MUTE:573return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);574}575576/* disable unsupported base controls */577/* to satisfy kradio and such apps */578if ((retval == -EINVAL) && (qc->id < V4L2_CID_LASTP1)) {579qc->flags = V4L2_CTRL_FLAG_DISABLED;580retval = 0;581}582583done:584if (retval < 0)585dev_warn(&radio->videodev->dev,586"query controls failed with %d\n", retval);587return retval;588}589590591/*592* si470x_vidioc_g_ctrl - get the value of a control593*/594static int si470x_vidioc_g_ctrl(struct file *file, void *priv,595struct v4l2_control *ctrl)596{597struct si470x_device *radio = video_drvdata(file);598int retval = 0;599600mutex_lock(&radio->lock);601/* safety checks */602retval = si470x_disconnect_check(radio);603if (retval)604goto done;605606switch (ctrl->id) {607case V4L2_CID_AUDIO_VOLUME:608ctrl->value = radio->registers[SYSCONFIG2] &609SYSCONFIG2_VOLUME;610break;611case V4L2_CID_AUDIO_MUTE:612ctrl->value = ((radio->registers[POWERCFG] &613POWERCFG_DMUTE) == 0) ? 1 : 0;614break;615default:616retval = -EINVAL;617}618619done:620if (retval < 0)621dev_warn(&radio->videodev->dev,622"get control failed with %d\n", retval);623624mutex_unlock(&radio->lock);625return retval;626}627628629/*630* si470x_vidioc_s_ctrl - set the value of a control631*/632static int si470x_vidioc_s_ctrl(struct file *file, void *priv,633struct v4l2_control *ctrl)634{635struct si470x_device *radio = video_drvdata(file);636int retval = 0;637638mutex_lock(&radio->lock);639/* safety checks */640retval = si470x_disconnect_check(radio);641if (retval)642goto done;643644switch (ctrl->id) {645case V4L2_CID_AUDIO_VOLUME:646radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME;647radio->registers[SYSCONFIG2] |= ctrl->value;648retval = si470x_set_register(radio, SYSCONFIG2);649break;650case V4L2_CID_AUDIO_MUTE:651if (ctrl->value == 1)652radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;653else654radio->registers[POWERCFG] |= POWERCFG_DMUTE;655retval = si470x_set_register(radio, POWERCFG);656break;657default:658retval = -EINVAL;659}660661done:662if (retval < 0)663dev_warn(&radio->videodev->dev,664"set control failed with %d\n", retval);665mutex_unlock(&radio->lock);666return retval;667}668669670/*671* si470x_vidioc_g_audio - get audio attributes672*/673static int si470x_vidioc_g_audio(struct file *file, void *priv,674struct v4l2_audio *audio)675{676/* driver constants */677audio->index = 0;678strcpy(audio->name, "Radio");679audio->capability = V4L2_AUDCAP_STEREO;680audio->mode = 0;681682return 0;683}684685686/*687* si470x_vidioc_g_tuner - get tuner attributes688*/689static int si470x_vidioc_g_tuner(struct file *file, void *priv,690struct v4l2_tuner *tuner)691{692struct si470x_device *radio = video_drvdata(file);693int retval = 0;694695mutex_lock(&radio->lock);696/* safety checks */697retval = si470x_disconnect_check(radio);698if (retval)699goto done;700701if (tuner->index != 0) {702retval = -EINVAL;703goto done;704}705706retval = si470x_get_register(radio, STATUSRSSI);707if (retval < 0)708goto done;709710/* driver constants */711strcpy(tuner->name, "FM");712tuner->type = V4L2_TUNER_RADIO;713tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |714V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO;715716/* range limits */717switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {718/* 0: 87.5 - 108 MHz (USA, Europe, default) */719default:720tuner->rangelow = 87.5 * FREQ_MUL;721tuner->rangehigh = 108 * FREQ_MUL;722break;723/* 1: 76 - 108 MHz (Japan wide band) */724case 1:725tuner->rangelow = 76 * FREQ_MUL;726tuner->rangehigh = 108 * FREQ_MUL;727break;728/* 2: 76 - 90 MHz (Japan) */729case 2:730tuner->rangelow = 76 * FREQ_MUL;731tuner->rangehigh = 90 * FREQ_MUL;732break;733};734735/* stereo indicator == stereo (instead of mono) */736if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0)737tuner->rxsubchans = V4L2_TUNER_SUB_MONO;738else739tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;740/* If there is a reliable method of detecting an RDS channel,741then this code should check for that before setting this742RDS subchannel. */743tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;744745/* mono/stereo selector */746if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 0)747tuner->audmode = V4L2_TUNER_MODE_STEREO;748else749tuner->audmode = V4L2_TUNER_MODE_MONO;750751/* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */752/* measured in units of dbµV in 1 db increments (max at ~75 dbµV) */753tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI);754/* the ideal factor is 0xffff/75 = 873,8 */755tuner->signal = (tuner->signal * 873) + (8 * tuner->signal / 10);756757/* automatic frequency control: -1: freq to low, 1 freq to high */758/* AFCRL does only indicate that freq. differs, not if too low/high */759tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0;760761done:762if (retval < 0)763dev_warn(&radio->videodev->dev,764"get tuner failed with %d\n", retval);765mutex_unlock(&radio->lock);766return retval;767}768769770/*771* si470x_vidioc_s_tuner - set tuner attributes772*/773static int si470x_vidioc_s_tuner(struct file *file, void *priv,774struct v4l2_tuner *tuner)775{776struct si470x_device *radio = video_drvdata(file);777int retval = 0;778779mutex_lock(&radio->lock);780/* safety checks */781retval = si470x_disconnect_check(radio);782if (retval)783goto done;784785if (tuner->index != 0)786goto done;787788/* mono/stereo selector */789switch (tuner->audmode) {790case V4L2_TUNER_MODE_MONO:791radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */792break;793case V4L2_TUNER_MODE_STEREO:794radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */795break;796default:797goto done;798}799800retval = si470x_set_register(radio, POWERCFG);801802done:803if (retval < 0)804dev_warn(&radio->videodev->dev,805"set tuner failed with %d\n", retval);806mutex_unlock(&radio->lock);807return retval;808}809810811/*812* si470x_vidioc_g_frequency - get tuner or modulator radio frequency813*/814static int si470x_vidioc_g_frequency(struct file *file, void *priv,815struct v4l2_frequency *freq)816{817struct si470x_device *radio = video_drvdata(file);818int retval = 0;819820/* safety checks */821mutex_lock(&radio->lock);822retval = si470x_disconnect_check(radio);823if (retval)824goto done;825826if (freq->tuner != 0) {827retval = -EINVAL;828goto done;829}830831freq->type = V4L2_TUNER_RADIO;832retval = si470x_get_freq(radio, &freq->frequency);833834done:835if (retval < 0)836dev_warn(&radio->videodev->dev,837"get frequency failed with %d\n", retval);838mutex_unlock(&radio->lock);839return retval;840}841842843/*844* si470x_vidioc_s_frequency - set tuner or modulator radio frequency845*/846static int si470x_vidioc_s_frequency(struct file *file, void *priv,847struct v4l2_frequency *freq)848{849struct si470x_device *radio = video_drvdata(file);850int retval = 0;851852mutex_lock(&radio->lock);853/* safety checks */854retval = si470x_disconnect_check(radio);855if (retval)856goto done;857858if (freq->tuner != 0) {859retval = -EINVAL;860goto done;861}862863retval = si470x_set_freq(radio, freq->frequency);864865done:866if (retval < 0)867dev_warn(&radio->videodev->dev,868"set frequency failed with %d\n", retval);869mutex_unlock(&radio->lock);870return retval;871}872873874/*875* si470x_vidioc_s_hw_freq_seek - set hardware frequency seek876*/877static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv,878struct v4l2_hw_freq_seek *seek)879{880struct si470x_device *radio = video_drvdata(file);881int retval = 0;882883mutex_lock(&radio->lock);884/* safety checks */885retval = si470x_disconnect_check(radio);886if (retval)887goto done;888889if (seek->tuner != 0) {890retval = -EINVAL;891goto done;892}893894retval = si470x_set_seek(radio, seek->wrap_around, seek->seek_upward);895896done:897if (retval < 0)898dev_warn(&radio->videodev->dev,899"set hardware frequency seek failed with %d\n", retval);900mutex_unlock(&radio->lock);901return retval;902}903904905/*906* si470x_ioctl_ops - video device ioctl operations907*/908static const struct v4l2_ioctl_ops si470x_ioctl_ops = {909.vidioc_querycap = si470x_vidioc_querycap,910.vidioc_queryctrl = si470x_vidioc_queryctrl,911.vidioc_g_ctrl = si470x_vidioc_g_ctrl,912.vidioc_s_ctrl = si470x_vidioc_s_ctrl,913.vidioc_g_audio = si470x_vidioc_g_audio,914.vidioc_g_tuner = si470x_vidioc_g_tuner,915.vidioc_s_tuner = si470x_vidioc_s_tuner,916.vidioc_g_frequency = si470x_vidioc_g_frequency,917.vidioc_s_frequency = si470x_vidioc_s_frequency,918.vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek,919};920921922/*923* si470x_viddev_template - video device interface924*/925struct video_device si470x_viddev_template = {926.fops = &si470x_fops,927.name = DRIVER_NAME,928.release = video_device_release,929.ioctl_ops = &si470x_ioctl_ops,930};931932933