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