brintos

brintos / linux-shallow public Read only

0
0
Text · 21.4 KiB · af0a709 Raw
795 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 *  drivers/media/radio/si470x/radio-si470x-common.c4 *5 *  Driver for radios with Silicon Labs Si470x FM Radio Receivers6 *7 *  Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>8 *  Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com>9 */10 11 12/*13 * History:14 * 2008-01-12	Tobias Lorenz <tobias.lorenz@gmx.net>15 *		Version 1.0.016 *		- First working version17 * 2008-01-13	Tobias Lorenz <tobias.lorenz@gmx.net>18 *		Version 1.0.119 *		- Improved error handling, every function now returns errno20 *		- Improved multi user access (start/mute/stop)21 *		- Channel doesn't get lost anymore after start/mute/stop22 *		- RDS support added (polling mode via interrupt EP 1)23 *		- marked default module parameters with *value*24 *		- switched from bit structs to bit masks25 *		- header file cleaned and integrated26 * 2008-01-14	Tobias Lorenz <tobias.lorenz@gmx.net>27 *		Version 1.0.228 *		- hex values are now lower case29 *		- commented USB ID for ADS/Tech moved on todo list30 *		- blacklisted si470x in hid-quirks.c31 *		- rds buffer handling functions integrated into *_work, *_read32 *		- rds_command in si470x_poll exchanged against simple retval33 *		- check for firmware version 1534 *		- code order and prototypes still remain the same35 *		- spacing and bottom of band codes remain the same36 * 2008-01-16	Tobias Lorenz <tobias.lorenz@gmx.net>37 *		Version 1.0.338 *		- code reordered to avoid function prototypes39 *		- switch/case defaults are now more user-friendly40 *		- unified comment style41 *		- applied all checkpatch.pl v1.12 suggestions42 *		  except the warning about the too long lines with bit comments43 *		- renamed FMRADIO to RADIO to cut line length (checkpatch.pl)44 * 2008-01-22	Tobias Lorenz <tobias.lorenz@gmx.net>45 *		Version 1.0.446 *		- avoid poss. locking when doing copy_to_user which may sleep47 *		- RDS is automatically activated on read now48 *		- code cleaned of unnecessary rds_commands49 *		- USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified50 *		  (thanks to Guillaume RAMOUSSE)51 * 2008-01-27	Tobias Lorenz <tobias.lorenz@gmx.net>52 *		Version 1.0.553 *		- number of seek_retries changed to tune_timeout54 *		- fixed problem with incomplete tune operations by own buffers55 *		- optimization of variables and printf types56 *		- improved error logging57 * 2008-01-31	Tobias Lorenz <tobias.lorenz@gmx.net>58 *		Oliver Neukum <oliver@neukum.org>59 *		Version 1.0.660 *		- fixed coverity checker warnings in *_usb_driver_disconnect61 *		- probe()/open() race by correct ordering in probe()62 *		- DMA coherency rules by separate allocation of all buffers63 *		- use of endianness macros64 *		- abuse of spinlock, replaced by mutex65 *		- racy handling of timer in disconnect,66 *		  replaced by delayed_work67 *		- racy interruptible_sleep_on(),68 *		  replaced with wait_event_interruptible()69 *		- handle signals in read()70 * 2008-02-08	Tobias Lorenz <tobias.lorenz@gmx.net>71 *		Oliver Neukum <oliver@neukum.org>72 *		Version 1.0.773 *		- usb autosuspend support74 *		- unplugging fixed75 * 2008-05-07	Tobias Lorenz <tobias.lorenz@gmx.net>76 *		Version 1.0.877 *		- hardware frequency seek support78 *		- afc indication79 *		- more safety checks, let si470x_get_freq return errno80 *		- vidioc behavior corrected according to v4l2 spec81 * 2008-10-20	Alexey Klimov <klimov.linux@gmail.com>82 *		- add support for KWorld USB FM Radio FM70083 *		- blacklisted KWorld radio in hid-core.c and hid-ids.h84 * 2008-12-03	Mark Lord <mlord@pobox.com>85 *		- add support for DealExtreme USB Radio86 * 2009-01-31	Bob Ross <pigiron@gmx.com>87 *		- correction of stereo detection/setting88 *		- correction of signal strength indicator scaling89 * 2009-01-31	Rick Bronson <rick@efn.org>90 *		Tobias Lorenz <tobias.lorenz@gmx.net>91 *		- add LED status output92 *		- get HW/SW version from scratchpad93 * 2009-06-16   Edouard Lafargue <edouard@lafargue.name>94 *		Version 1.0.1095 *		- add support for interrupt mode for RDS endpoint,96 *                instead of polling.97 *                Improves RDS reception significantly98 */99 100 101/* kernel includes */102#include "radio-si470x.h"103 104/**************************************************************************105 * Module Parameters106 **************************************************************************/107 108/* Spacing (kHz) */109/* 0: 200 kHz (USA, Australia) */110/* 1: 100 kHz (Europe, Japan) */111/* 2:  50 kHz */112static unsigned short space = 2;113module_param(space, ushort, 0444);114MODULE_PARM_DESC(space, "Spacing: 0=200kHz 1=100kHz *2=50kHz*");115 116/* De-emphasis */117/* 0: 75 us (USA) */118/* 1: 50 us (Europe, Australia, Japan) */119static unsigned short de = 1;120module_param(de, ushort, 0444);121MODULE_PARM_DESC(de, "De-emphasis: 0=75us *1=50us*");122 123/* Tune timeout */124static unsigned int tune_timeout = 3000;125module_param(tune_timeout, uint, 0644);126MODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*");127 128/* Seek timeout */129static unsigned int seek_timeout = 5000;130module_param(seek_timeout, uint, 0644);131MODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*");132 133static const struct v4l2_frequency_band bands[] = {134	{135		.type = V4L2_TUNER_RADIO,136		.index = 0,137		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |138			    V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |139			    V4L2_TUNER_CAP_FREQ_BANDS |140			    V4L2_TUNER_CAP_HWSEEK_BOUNDED |141			    V4L2_TUNER_CAP_HWSEEK_WRAP,142		.rangelow   =  87500 * 16,143		.rangehigh  = 108000 * 16,144		.modulation = V4L2_BAND_MODULATION_FM,145	},146	{147		.type = V4L2_TUNER_RADIO,148		.index = 1,149		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |150			    V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |151			    V4L2_TUNER_CAP_FREQ_BANDS |152			    V4L2_TUNER_CAP_HWSEEK_BOUNDED |153			    V4L2_TUNER_CAP_HWSEEK_WRAP,154		.rangelow   =  76000 * 16,155		.rangehigh  = 108000 * 16,156		.modulation = V4L2_BAND_MODULATION_FM,157	},158	{159		.type = V4L2_TUNER_RADIO,160		.index = 2,161		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |162			    V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |163			    V4L2_TUNER_CAP_FREQ_BANDS |164			    V4L2_TUNER_CAP_HWSEEK_BOUNDED |165			    V4L2_TUNER_CAP_HWSEEK_WRAP,166		.rangelow   =  76000 * 16,167		.rangehigh  =  90000 * 16,168		.modulation = V4L2_BAND_MODULATION_FM,169	},170};171 172/**************************************************************************173 * Generic Functions174 **************************************************************************/175 176/*177 * si470x_set_band - set the band178 */179static int si470x_set_band(struct si470x_device *radio, int band)180{181	if (radio->band == band)182		return 0;183 184	radio->band = band;185	radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_BAND;186	radio->registers[SYSCONFIG2] |= radio->band << 6;187	return radio->set_register(radio, SYSCONFIG2);188}189 190/*191 * si470x_set_chan - set the channel192 */193static int si470x_set_chan(struct si470x_device *radio, unsigned short chan)194{195	int retval;196	unsigned long time_left;197	bool timed_out = false;198 199	retval = radio->get_register(radio, POWERCFG);200	if (retval)201		return retval;202 203	if ((radio->registers[POWERCFG] & (POWERCFG_ENABLE|POWERCFG_DMUTE))204		!= (POWERCFG_ENABLE|POWERCFG_DMUTE)) {205		return 0;206	}207 208	/* start tuning */209	radio->registers[CHANNEL] &= ~CHANNEL_CHAN;210	radio->registers[CHANNEL] |= CHANNEL_TUNE | chan;211	retval = radio->set_register(radio, CHANNEL);212	if (retval < 0)213		goto done;214 215	/* wait till tune operation has completed */216	reinit_completion(&radio->completion);217	time_left = wait_for_completion_timeout(&radio->completion,218						msecs_to_jiffies(tune_timeout));219	if (time_left == 0)220		timed_out = true;221 222	if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)223		dev_warn(&radio->videodev.dev, "tune does not complete\n");224	if (timed_out)225		dev_warn(&radio->videodev.dev,226			"tune timed out after %u ms\n", tune_timeout);227 228	/* stop tuning */229	radio->registers[CHANNEL] &= ~CHANNEL_TUNE;230	retval = radio->set_register(radio, CHANNEL);231 232done:233	return retval;234}235 236/*237 * si470x_get_step - get channel spacing238 */239static unsigned int si470x_get_step(struct si470x_device *radio)240{241	/* Spacing (kHz) */242	switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {243	/* 0: 200 kHz (USA, Australia) */244	case 0:245		return 200 * 16;246	/* 1: 100 kHz (Europe, Japan) */247	case 1:248		return 100 * 16;249	/* 2:  50 kHz */250	default:251		return 50 * 16;252	}253}254 255 256/*257 * si470x_get_freq - get the frequency258 */259static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq)260{261	int chan, retval;262 263	/* read channel */264	retval = radio->get_register(radio, READCHAN);265	chan = radio->registers[READCHAN] & READCHAN_READCHAN;266 267	/* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */268	*freq = chan * si470x_get_step(radio) + bands[radio->band].rangelow;269 270	return retval;271}272 273 274/*275 * si470x_set_freq - set the frequency276 */277int si470x_set_freq(struct si470x_device *radio, unsigned int freq)278{279	unsigned short chan;280 281	freq = clamp(freq, bands[radio->band].rangelow,282			   bands[radio->band].rangehigh);283	/* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */284	chan = (freq - bands[radio->band].rangelow) / si470x_get_step(radio);285 286	return si470x_set_chan(radio, chan);287}288EXPORT_SYMBOL_GPL(si470x_set_freq);289 290 291/*292 * si470x_set_seek - set seek293 */294static int si470x_set_seek(struct si470x_device *radio,295			   const struct v4l2_hw_freq_seek *seek)296{297	int band, retval;298	unsigned int freq;299	bool timed_out = false;300	unsigned long time_left;301 302	/* set band */303	if (seek->rangelow || seek->rangehigh) {304		for (band = 0; band < ARRAY_SIZE(bands); band++) {305			if (bands[band].rangelow  == seek->rangelow &&306			    bands[band].rangehigh == seek->rangehigh)307				break;308		}309		if (band == ARRAY_SIZE(bands))310			return -EINVAL; /* No matching band found */311	} else312		band = 1; /* If nothing is specified seek 76 - 108 Mhz */313 314	if (radio->band != band) {315		retval = si470x_get_freq(radio, &freq);316		if (retval)317			return retval;318		retval = si470x_set_band(radio, band);319		if (retval)320			return retval;321		retval = si470x_set_freq(radio, freq);322		if (retval)323			return retval;324	}325 326	/* start seeking */327	radio->registers[POWERCFG] |= POWERCFG_SEEK;328	if (seek->wrap_around)329		radio->registers[POWERCFG] &= ~POWERCFG_SKMODE;330	else331		radio->registers[POWERCFG] |= POWERCFG_SKMODE;332	if (seek->seek_upward)333		radio->registers[POWERCFG] |= POWERCFG_SEEKUP;334	else335		radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP;336	retval = radio->set_register(radio, POWERCFG);337	if (retval < 0)338		return retval;339 340	/* wait till tune operation has completed */341	reinit_completion(&radio->completion);342	time_left = wait_for_completion_timeout(&radio->completion,343						msecs_to_jiffies(seek_timeout));344	if (time_left == 0)345		timed_out = true;346 347	if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)348		dev_warn(&radio->videodev.dev, "seek does not complete\n");349	if (radio->registers[STATUSRSSI] & STATUSRSSI_SF)350		dev_warn(&radio->videodev.dev,351			"seek failed / band limit reached\n");352 353	/* stop seeking */354	radio->registers[POWERCFG] &= ~POWERCFG_SEEK;355	retval = radio->set_register(radio, POWERCFG);356 357	/* try again, if timed out */358	if (retval == 0 && timed_out)359		return -ENODATA;360	return retval;361}362 363 364/*365 * si470x_start - switch on radio366 */367int si470x_start(struct si470x_device *radio)368{369	int retval;370 371	/* powercfg */372	radio->registers[POWERCFG] =373		POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM;374	retval = radio->set_register(radio, POWERCFG);375	if (retval < 0)376		goto done;377 378	/* sysconfig 1 */379	radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDSIEN | SYSCONFIG1_STCIEN |380					SYSCONFIG1_RDS;381	radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_GPIO2;382	radio->registers[SYSCONFIG1] |= SYSCONFIG1_GPIO2_INT;383	if (de)384		radio->registers[SYSCONFIG1] |= SYSCONFIG1_DE;385	retval = radio->set_register(radio, SYSCONFIG1);386	if (retval < 0)387		goto done;388 389	/* sysconfig 2 */390	radio->registers[SYSCONFIG2] =391		(0x1f  << 8) |				/* SEEKTH */392		((radio->band << 6) & SYSCONFIG2_BAND) |/* BAND */393		((space << 4) & SYSCONFIG2_SPACE) |	/* SPACE */394		15;					/* VOLUME (max) */395	retval = radio->set_register(radio, SYSCONFIG2);396	if (retval < 0)397		goto done;398 399	/* reset last channel */400	retval = si470x_set_chan(radio,401		radio->registers[CHANNEL] & CHANNEL_CHAN);402 403done:404	return retval;405}406EXPORT_SYMBOL_GPL(si470x_start);407 408 409/*410 * si470x_stop - switch off radio411 */412int si470x_stop(struct si470x_device *radio)413{414	int retval;415 416	/* sysconfig 1 */417	radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;418	retval = radio->set_register(radio, SYSCONFIG1);419	if (retval < 0)420		goto done;421 422	/* powercfg */423	radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;424	/* POWERCFG_ENABLE has to automatically go low */425	radio->registers[POWERCFG] |= POWERCFG_ENABLE |	POWERCFG_DISABLE;426	retval = radio->set_register(radio, POWERCFG);427 428done:429	return retval;430}431EXPORT_SYMBOL_GPL(si470x_stop);432 433 434/*435 * si470x_rds_on - switch on rds reception436 */437static int si470x_rds_on(struct si470x_device *radio)438{439	int retval;440 441	/* sysconfig 1 */442	radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS;443	retval = radio->set_register(radio, SYSCONFIG1);444	if (retval < 0)445		radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;446 447	return retval;448}449 450 451 452/**************************************************************************453 * File Operations Interface454 **************************************************************************/455 456/*457 * si470x_fops_read - read RDS data458 */459static ssize_t si470x_fops_read(struct file *file, char __user *buf,460		size_t count, loff_t *ppos)461{462	struct si470x_device *radio = video_drvdata(file);463	int retval = 0;464	unsigned int block_count = 0;465 466	/* switch on rds reception */467	if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)468		si470x_rds_on(radio);469 470	/* block if no new data available */471	while (radio->wr_index == radio->rd_index) {472		if (file->f_flags & O_NONBLOCK) {473			retval = -EWOULDBLOCK;474			goto done;475		}476		if (wait_event_interruptible(radio->read_queue,477			radio->wr_index != radio->rd_index) < 0) {478			retval = -EINTR;479			goto done;480		}481	}482 483	/* calculate block count from byte count */484	count /= 3;485 486	/* copy RDS block out of internal buffer and to user buffer */487	while (block_count < count) {488		if (radio->rd_index == radio->wr_index)489			break;490 491		/* always transfer rds complete blocks */492		if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3))493			/* retval = -EFAULT; */494			break;495 496		/* increment and wrap read pointer */497		radio->rd_index += 3;498		if (radio->rd_index >= radio->buf_size)499			radio->rd_index = 0;500 501		/* increment counters */502		block_count++;503		buf += 3;504		retval += 3;505	}506 507done:508	return retval;509}510 511 512/*513 * si470x_fops_poll - poll RDS data514 */515static __poll_t si470x_fops_poll(struct file *file,516		struct poll_table_struct *pts)517{518	struct si470x_device *radio = video_drvdata(file);519	__poll_t req_events = poll_requested_events(pts);520	__poll_t retval = v4l2_ctrl_poll(file, pts);521 522	if (req_events & (EPOLLIN | EPOLLRDNORM)) {523		/* switch on rds reception */524		if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)525			si470x_rds_on(radio);526 527		poll_wait(file, &radio->read_queue, pts);528 529		if (radio->rd_index != radio->wr_index)530			retval |= EPOLLIN | EPOLLRDNORM;531	}532 533	return retval;534}535 536 537static int si470x_fops_open(struct file *file)538{539	struct si470x_device *radio = video_drvdata(file);540 541	return radio->fops_open(file);542}543 544 545/*546 * si470x_fops_release - file release547 */548static int si470x_fops_release(struct file *file)549{550	struct si470x_device *radio = video_drvdata(file);551 552	return radio->fops_release(file);553}554 555 556/*557 * si470x_fops - file operations interface558 */559static const struct v4l2_file_operations si470x_fops = {560	.owner			= THIS_MODULE,561	.read			= si470x_fops_read,562	.poll			= si470x_fops_poll,563	.unlocked_ioctl		= video_ioctl2,564	.open			= si470x_fops_open,565	.release		= si470x_fops_release,566};567 568 569 570/**************************************************************************571 * Video4Linux Interface572 **************************************************************************/573 574 575static int si470x_s_ctrl(struct v4l2_ctrl *ctrl)576{577	struct si470x_device *radio =578		container_of(ctrl->handler, struct si470x_device, hdl);579 580	switch (ctrl->id) {581	case V4L2_CID_AUDIO_VOLUME:582		radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME;583		radio->registers[SYSCONFIG2] |= ctrl->val;584		return radio->set_register(radio, SYSCONFIG2);585	case V4L2_CID_AUDIO_MUTE:586		if (ctrl->val)587			radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;588		else589			radio->registers[POWERCFG] |= POWERCFG_DMUTE;590		return radio->set_register(radio, POWERCFG);591	default:592		return -EINVAL;593	}594}595 596 597/*598 * si470x_vidioc_g_tuner - get tuner attributes599 */600static int si470x_vidioc_g_tuner(struct file *file, void *priv,601		struct v4l2_tuner *tuner)602{603	struct si470x_device *radio = video_drvdata(file);604	int retval = 0;605 606	if (tuner->index != 0)607		return -EINVAL;608 609	if (!radio->status_rssi_auto_update) {610		retval = radio->get_register(radio, STATUSRSSI);611		if (retval < 0)612			return retval;613	}614 615	/* driver constants */616	strscpy(tuner->name, "FM", sizeof(tuner->name));617	tuner->type = V4L2_TUNER_RADIO;618	tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |619			    V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |620			    V4L2_TUNER_CAP_HWSEEK_BOUNDED |621			    V4L2_TUNER_CAP_HWSEEK_WRAP;622	tuner->rangelow  =  76 * FREQ_MUL;623	tuner->rangehigh = 108 * FREQ_MUL;624 625	/* stereo indicator == stereo (instead of mono) */626	if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0)627		tuner->rxsubchans = V4L2_TUNER_SUB_MONO;628	else629		tuner->rxsubchans = V4L2_TUNER_SUB_STEREO;630	/* If there is a reliable method of detecting an RDS channel,631	   then this code should check for that before setting this632	   RDS subchannel. */633	tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;634 635	/* mono/stereo selector */636	if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 0)637		tuner->audmode = V4L2_TUNER_MODE_STEREO;638	else639		tuner->audmode = V4L2_TUNER_MODE_MONO;640 641	/* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */642	/* measured in units of dbµV in 1 db increments (max at ~75 dbµV) */643	tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI);644	/* the ideal factor is 0xffff/75 = 873,8 */645	tuner->signal = (tuner->signal * 873) + (8 * tuner->signal / 10);646	if (tuner->signal > 0xffff)647		tuner->signal = 0xffff;648 649	/* automatic frequency control: -1: freq to low, 1 freq to high */650	/* AFCRL does only indicate that freq. differs, not if too low/high */651	tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0;652 653	return retval;654}655 656 657/*658 * si470x_vidioc_s_tuner - set tuner attributes659 */660static int si470x_vidioc_s_tuner(struct file *file, void *priv,661		const struct v4l2_tuner *tuner)662{663	struct si470x_device *radio = video_drvdata(file);664 665	if (tuner->index != 0)666		return -EINVAL;667 668	/* mono/stereo selector */669	switch (tuner->audmode) {670	case V4L2_TUNER_MODE_MONO:671		radio->registers[POWERCFG] |= POWERCFG_MONO;  /* force mono */672		break;673	case V4L2_TUNER_MODE_STEREO:674	default:675		radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */676		break;677	}678 679	return radio->set_register(radio, POWERCFG);680}681 682 683/*684 * si470x_vidioc_g_frequency - get tuner or modulator radio frequency685 */686static int si470x_vidioc_g_frequency(struct file *file, void *priv,687		struct v4l2_frequency *freq)688{689	struct si470x_device *radio = video_drvdata(file);690 691	if (freq->tuner != 0)692		return -EINVAL;693 694	freq->type = V4L2_TUNER_RADIO;695	return si470x_get_freq(radio, &freq->frequency);696}697 698 699/*700 * si470x_vidioc_s_frequency - set tuner or modulator radio frequency701 */702static int si470x_vidioc_s_frequency(struct file *file, void *priv,703		const struct v4l2_frequency *freq)704{705	struct si470x_device *radio = video_drvdata(file);706	int retval;707 708	if (freq->tuner != 0)709		return -EINVAL;710 711	if (freq->frequency < bands[radio->band].rangelow ||712	    freq->frequency > bands[radio->band].rangehigh) {713		/* Switch to band 1 which covers everything we support */714		retval = si470x_set_band(radio, 1);715		if (retval)716			return retval;717	}718	return si470x_set_freq(radio, freq->frequency);719}720 721 722/*723 * si470x_vidioc_s_hw_freq_seek - set hardware frequency seek724 */725static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv,726		const struct v4l2_hw_freq_seek *seek)727{728	struct si470x_device *radio = video_drvdata(file);729 730	if (seek->tuner != 0)731		return -EINVAL;732 733	if (file->f_flags & O_NONBLOCK)734		return -EWOULDBLOCK;735 736	return si470x_set_seek(radio, seek);737}738 739/*740 * si470x_vidioc_enum_freq_bands - enumerate supported bands741 */742static int si470x_vidioc_enum_freq_bands(struct file *file, void *priv,743					 struct v4l2_frequency_band *band)744{745	if (band->tuner != 0)746		return -EINVAL;747	if (band->index >= ARRAY_SIZE(bands))748		return -EINVAL;749	*band = bands[band->index];750	return 0;751}752 753const struct v4l2_ctrl_ops si470x_ctrl_ops = {754	.s_ctrl = si470x_s_ctrl,755};756EXPORT_SYMBOL_GPL(si470x_ctrl_ops);757 758static int si470x_vidioc_querycap(struct file *file, void *priv,759		struct v4l2_capability *capability)760{761	struct si470x_device *radio = video_drvdata(file);762 763	return radio->vidioc_querycap(file, priv, capability);764};765 766/*767 * si470x_ioctl_ops - video device ioctl operations768 */769static const struct v4l2_ioctl_ops si470x_ioctl_ops = {770	.vidioc_querycap	= si470x_vidioc_querycap,771	.vidioc_g_tuner		= si470x_vidioc_g_tuner,772	.vidioc_s_tuner		= si470x_vidioc_s_tuner,773	.vidioc_g_frequency	= si470x_vidioc_g_frequency,774	.vidioc_s_frequency	= si470x_vidioc_s_frequency,775	.vidioc_s_hw_freq_seek	= si470x_vidioc_s_hw_freq_seek,776	.vidioc_enum_freq_bands = si470x_vidioc_enum_freq_bands,777	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,778	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,779};780 781 782/*783 * si470x_viddev_template - video device interface784 */785const struct video_device si470x_viddev_template = {786	.fops			= &si470x_fops,787	.name			= DRIVER_NAME,788	.release		= video_device_release_empty,789	.ioctl_ops		= &si470x_ioctl_ops,790};791EXPORT_SYMBOL_GPL(si470x_viddev_template);792 793MODULE_DESCRIPTION("Core radio driver for Si470x FM Radio Receivers");794MODULE_LICENSE("GPL");795