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