18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *   v4l2 driver for TEA5777 Philips AM/FM radio tuner chips
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *	Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci *   Based on the ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips:
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci *	Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/delay.h>
138c2ecf20Sopenharmony_ci#include <linux/init.h>
148c2ecf20Sopenharmony_ci#include <linux/module.h>
158c2ecf20Sopenharmony_ci#include <linux/sched.h>
168c2ecf20Sopenharmony_ci#include <linux/slab.h>
178c2ecf20Sopenharmony_ci#include <media/v4l2-device.h>
188c2ecf20Sopenharmony_ci#include <media/v4l2-dev.h>
198c2ecf20Sopenharmony_ci#include <media/v4l2-fh.h>
208c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h>
218c2ecf20Sopenharmony_ci#include <media/v4l2-event.h>
228c2ecf20Sopenharmony_ci#include "radio-tea5777.h"
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <perex@perex.cz>");
258c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Routines for control of TEA5777 Philips AM/FM radio tuner chips");
268c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci#define TEA5777_FM_IF			150 /* kHz */
298c2ecf20Sopenharmony_ci#define TEA5777_FM_FREQ_STEP		50  /* kHz */
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#define TEA5777_AM_IF			21  /* kHz */
328c2ecf20Sopenharmony_ci#define TEA5777_AM_FREQ_STEP		1   /* kHz */
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci/* Write reg, common bits */
358c2ecf20Sopenharmony_ci#define TEA5777_W_MUTE_MASK		(1LL << 47)
368c2ecf20Sopenharmony_ci#define TEA5777_W_MUTE_SHIFT		47
378c2ecf20Sopenharmony_ci#define TEA5777_W_AM_FM_MASK		(1LL << 46)
388c2ecf20Sopenharmony_ci#define TEA5777_W_AM_FM_SHIFT		46
398c2ecf20Sopenharmony_ci#define TEA5777_W_STB_MASK		(1LL << 45)
408c2ecf20Sopenharmony_ci#define TEA5777_W_STB_SHIFT		45
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define TEA5777_W_IFCE_MASK		(1LL << 29)
438c2ecf20Sopenharmony_ci#define TEA5777_W_IFCE_SHIFT		29
448c2ecf20Sopenharmony_ci#define TEA5777_W_IFW_MASK		(1LL << 28)
458c2ecf20Sopenharmony_ci#define TEA5777_W_IFW_SHIFT		28
468c2ecf20Sopenharmony_ci#define TEA5777_W_HILO_MASK		(1LL << 27)
478c2ecf20Sopenharmony_ci#define TEA5777_W_HILO_SHIFT		27
488c2ecf20Sopenharmony_ci#define TEA5777_W_DBUS_MASK		(1LL << 26)
498c2ecf20Sopenharmony_ci#define TEA5777_W_DBUS_SHIFT		26
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci#define TEA5777_W_INTEXT_MASK		(1LL << 24)
528c2ecf20Sopenharmony_ci#define TEA5777_W_INTEXT_SHIFT		24
538c2ecf20Sopenharmony_ci#define TEA5777_W_P1_MASK		(1LL << 23)
548c2ecf20Sopenharmony_ci#define TEA5777_W_P1_SHIFT		23
558c2ecf20Sopenharmony_ci#define TEA5777_W_P0_MASK		(1LL << 22)
568c2ecf20Sopenharmony_ci#define TEA5777_W_P0_SHIFT		22
578c2ecf20Sopenharmony_ci#define TEA5777_W_PEN1_MASK		(1LL << 21)
588c2ecf20Sopenharmony_ci#define TEA5777_W_PEN1_SHIFT		21
598c2ecf20Sopenharmony_ci#define TEA5777_W_PEN0_MASK		(1LL << 20)
608c2ecf20Sopenharmony_ci#define TEA5777_W_PEN0_SHIFT		20
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci#define TEA5777_W_CHP0_MASK		(1LL << 18)
638c2ecf20Sopenharmony_ci#define TEA5777_W_CHP0_SHIFT		18
648c2ecf20Sopenharmony_ci#define TEA5777_W_DEEM_MASK		(1LL << 17)
658c2ecf20Sopenharmony_ci#define TEA5777_W_DEEM_SHIFT		17
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci#define TEA5777_W_SEARCH_MASK		(1LL << 7)
688c2ecf20Sopenharmony_ci#define TEA5777_W_SEARCH_SHIFT		7
698c2ecf20Sopenharmony_ci#define TEA5777_W_PROGBLIM_MASK		(1LL << 6)
708c2ecf20Sopenharmony_ci#define TEA5777_W_PROGBLIM_SHIFT	6
718c2ecf20Sopenharmony_ci#define TEA5777_W_UPDWN_MASK		(1LL << 5)
728c2ecf20Sopenharmony_ci#define TEA5777_W_UPDWN_SHIFT		5
738c2ecf20Sopenharmony_ci#define TEA5777_W_SLEV_MASK		(3LL << 3)
748c2ecf20Sopenharmony_ci#define TEA5777_W_SLEV_SHIFT		3
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci/* Write reg, FM specific bits */
778c2ecf20Sopenharmony_ci#define TEA5777_W_FM_PLL_MASK		(0x1fffLL << 32)
788c2ecf20Sopenharmony_ci#define TEA5777_W_FM_PLL_SHIFT		32
798c2ecf20Sopenharmony_ci#define TEA5777_W_FM_FREF_MASK		(0x03LL << 30)
808c2ecf20Sopenharmony_ci#define TEA5777_W_FM_FREF_SHIFT		30
818c2ecf20Sopenharmony_ci#define TEA5777_W_FM_FREF_VALUE		0LL /* 50k steps, 150k IF */
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci#define TEA5777_W_FM_FORCEMONO_MASK	(1LL << 15)
848c2ecf20Sopenharmony_ci#define TEA5777_W_FM_FORCEMONO_SHIFT	15
858c2ecf20Sopenharmony_ci#define TEA5777_W_FM_SDSOFF_MASK	(1LL << 14)
868c2ecf20Sopenharmony_ci#define TEA5777_W_FM_SDSOFF_SHIFT	14
878c2ecf20Sopenharmony_ci#define TEA5777_W_FM_DOFF_MASK		(1LL << 13)
888c2ecf20Sopenharmony_ci#define TEA5777_W_FM_DOFF_SHIFT		13
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci#define TEA5777_W_FM_STEP_MASK		(3LL << 1)
918c2ecf20Sopenharmony_ci#define TEA5777_W_FM_STEP_SHIFT		1
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci/* Write reg, AM specific bits */
948c2ecf20Sopenharmony_ci#define TEA5777_W_AM_PLL_MASK		(0x7ffLL << 34)
958c2ecf20Sopenharmony_ci#define TEA5777_W_AM_PLL_SHIFT		34
968c2ecf20Sopenharmony_ci#define TEA5777_W_AM_AGCRF_MASK		(1LL << 33)
978c2ecf20Sopenharmony_ci#define TEA5777_W_AM_AGCRF_SHIFT	33
988c2ecf20Sopenharmony_ci#define TEA5777_W_AM_AGCIF_MASK		(1LL << 32)
998c2ecf20Sopenharmony_ci#define TEA5777_W_AM_AGCIF_SHIFT	32
1008c2ecf20Sopenharmony_ci#define TEA5777_W_AM_MWLW_MASK		(1LL << 31)
1018c2ecf20Sopenharmony_ci#define TEA5777_W_AM_MWLW_SHIFT		31
1028c2ecf20Sopenharmony_ci#define TEA5777_W_AM_LW			0LL
1038c2ecf20Sopenharmony_ci#define TEA5777_W_AM_MW			1LL
1048c2ecf20Sopenharmony_ci#define TEA5777_W_AM_LNA_MASK		(1LL << 30)
1058c2ecf20Sopenharmony_ci#define TEA5777_W_AM_LNA_SHIFT		30
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci#define TEA5777_W_AM_PEAK_MASK		(1LL << 25)
1088c2ecf20Sopenharmony_ci#define TEA5777_W_AM_PEAK_SHIFT		25
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci#define TEA5777_W_AM_RFB_MASK		(1LL << 16)
1118c2ecf20Sopenharmony_ci#define TEA5777_W_AM_RFB_SHIFT		16
1128c2ecf20Sopenharmony_ci#define TEA5777_W_AM_CALLIGN_MASK	(1LL << 15)
1138c2ecf20Sopenharmony_ci#define TEA5777_W_AM_CALLIGN_SHIFT	15
1148c2ecf20Sopenharmony_ci#define TEA5777_W_AM_CBANK_MASK		(0x7fLL << 8)
1158c2ecf20Sopenharmony_ci#define TEA5777_W_AM_CBANK_SHIFT	8
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci#define TEA5777_W_AM_DELAY_MASK		(1LL << 2)
1188c2ecf20Sopenharmony_ci#define TEA5777_W_AM_DELAY_SHIFT	2
1198c2ecf20Sopenharmony_ci#define TEA5777_W_AM_STEP_MASK		(1LL << 1)
1208c2ecf20Sopenharmony_ci#define TEA5777_W_AM_STEP_SHIFT		1
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci/* Read reg, common bits */
1238c2ecf20Sopenharmony_ci#define TEA5777_R_LEVEL_MASK		(0x0f << 17)
1248c2ecf20Sopenharmony_ci#define TEA5777_R_LEVEL_SHIFT		17
1258c2ecf20Sopenharmony_ci#define TEA5777_R_SFOUND_MASK		(0x01 << 16)
1268c2ecf20Sopenharmony_ci#define TEA5777_R_SFOUND_SHIFT		16
1278c2ecf20Sopenharmony_ci#define TEA5777_R_BLIM_MASK		(0x01 << 15)
1288c2ecf20Sopenharmony_ci#define TEA5777_R_BLIM_SHIFT		15
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci/* Read reg, FM specific bits */
1318c2ecf20Sopenharmony_ci#define TEA5777_R_FM_STEREO_MASK	(0x01 << 21)
1328c2ecf20Sopenharmony_ci#define TEA5777_R_FM_STEREO_SHIFT	21
1338c2ecf20Sopenharmony_ci#define TEA5777_R_FM_PLL_MASK		0x1fff
1348c2ecf20Sopenharmony_ci#define TEA5777_R_FM_PLL_SHIFT		0
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_cienum { BAND_FM, BAND_AM };
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_cistatic const struct v4l2_frequency_band bands[] = {
1398c2ecf20Sopenharmony_ci	{
1408c2ecf20Sopenharmony_ci		.type = V4L2_TUNER_RADIO,
1418c2ecf20Sopenharmony_ci		.index = 0,
1428c2ecf20Sopenharmony_ci		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
1438c2ecf20Sopenharmony_ci			      V4L2_TUNER_CAP_FREQ_BANDS |
1448c2ecf20Sopenharmony_ci			      V4L2_TUNER_CAP_HWSEEK_BOUNDED |
1458c2ecf20Sopenharmony_ci			      V4L2_TUNER_CAP_HWSEEK_PROG_LIM,
1468c2ecf20Sopenharmony_ci		.rangelow   =  76000 * 16,
1478c2ecf20Sopenharmony_ci		.rangehigh  = 108000 * 16,
1488c2ecf20Sopenharmony_ci		.modulation = V4L2_BAND_MODULATION_FM,
1498c2ecf20Sopenharmony_ci	},
1508c2ecf20Sopenharmony_ci	{
1518c2ecf20Sopenharmony_ci		.type = V4L2_TUNER_RADIO,
1528c2ecf20Sopenharmony_ci		.index = 1,
1538c2ecf20Sopenharmony_ci		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS |
1548c2ecf20Sopenharmony_ci			      V4L2_TUNER_CAP_HWSEEK_BOUNDED |
1558c2ecf20Sopenharmony_ci			      V4L2_TUNER_CAP_HWSEEK_PROG_LIM,
1568c2ecf20Sopenharmony_ci		.rangelow   =  530 * 16,
1578c2ecf20Sopenharmony_ci		.rangehigh  = 1710 * 16,
1588c2ecf20Sopenharmony_ci		.modulation = V4L2_BAND_MODULATION_AM,
1598c2ecf20Sopenharmony_ci	},
1608c2ecf20Sopenharmony_ci};
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_cistatic u32 tea5777_freq_to_v4l2_freq(struct radio_tea5777 *tea, u32 freq)
1638c2ecf20Sopenharmony_ci{
1648c2ecf20Sopenharmony_ci	switch (tea->band) {
1658c2ecf20Sopenharmony_ci	case BAND_FM:
1668c2ecf20Sopenharmony_ci		return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16;
1678c2ecf20Sopenharmony_ci	case BAND_AM:
1688c2ecf20Sopenharmony_ci		return (freq * TEA5777_AM_FREQ_STEP + TEA5777_AM_IF) * 16;
1698c2ecf20Sopenharmony_ci	}
1708c2ecf20Sopenharmony_ci	return 0; /* Never reached */
1718c2ecf20Sopenharmony_ci}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ciint radio_tea5777_set_freq(struct radio_tea5777 *tea)
1748c2ecf20Sopenharmony_ci{
1758c2ecf20Sopenharmony_ci	u32 freq;
1768c2ecf20Sopenharmony_ci	int res;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	freq = clamp(tea->freq, bands[tea->band].rangelow,
1798c2ecf20Sopenharmony_ci				bands[tea->band].rangehigh);
1808c2ecf20Sopenharmony_ci	freq = (freq + 8) / 16; /* to kHz */
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	switch (tea->band) {
1838c2ecf20Sopenharmony_ci	case BAND_FM:
1848c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_AM_FM_MASK;
1858c2ecf20Sopenharmony_ci		freq = (freq - TEA5777_FM_IF) / TEA5777_FM_FREQ_STEP;
1868c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_FM_PLL_MASK;
1878c2ecf20Sopenharmony_ci		tea->write_reg |= (u64)freq << TEA5777_W_FM_PLL_SHIFT;
1888c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_FM_FREF_MASK;
1898c2ecf20Sopenharmony_ci		tea->write_reg |= TEA5777_W_FM_FREF_VALUE <<
1908c2ecf20Sopenharmony_ci				  TEA5777_W_FM_FREF_SHIFT;
1918c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK;
1928c2ecf20Sopenharmony_ci		if (tea->audmode == V4L2_TUNER_MODE_MONO)
1938c2ecf20Sopenharmony_ci			tea->write_reg |= 1LL << TEA5777_W_FM_FORCEMONO_SHIFT;
1948c2ecf20Sopenharmony_ci		break;
1958c2ecf20Sopenharmony_ci	case BAND_AM:
1968c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_AM_FM_MASK;
1978c2ecf20Sopenharmony_ci		tea->write_reg |= (1LL << TEA5777_W_AM_FM_SHIFT);
1988c2ecf20Sopenharmony_ci		freq = (freq - TEA5777_AM_IF) / TEA5777_AM_FREQ_STEP;
1998c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_AM_PLL_MASK;
2008c2ecf20Sopenharmony_ci		tea->write_reg |= (u64)freq << TEA5777_W_AM_PLL_SHIFT;
2018c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK;
2028c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK;
2038c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_AM_MWLW_MASK;
2048c2ecf20Sopenharmony_ci		tea->write_reg |= TEA5777_W_AM_MW << TEA5777_W_AM_MWLW_SHIFT;
2058c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_AM_LNA_MASK;
2068c2ecf20Sopenharmony_ci		tea->write_reg |= 1LL << TEA5777_W_AM_LNA_SHIFT;
2078c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_AM_PEAK_MASK;
2088c2ecf20Sopenharmony_ci		tea->write_reg |= 1LL << TEA5777_W_AM_PEAK_SHIFT;
2098c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_AM_CALLIGN_MASK;
2108c2ecf20Sopenharmony_ci		break;
2118c2ecf20Sopenharmony_ci	}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	res = tea->ops->write_reg(tea, tea->write_reg);
2148c2ecf20Sopenharmony_ci	if (res)
2158c2ecf20Sopenharmony_ci		return res;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	tea->needs_write = false;
2188c2ecf20Sopenharmony_ci	tea->read_reg = -1;
2198c2ecf20Sopenharmony_ci	tea->freq = tea5777_freq_to_v4l2_freq(tea, freq);
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	return 0;
2228c2ecf20Sopenharmony_ci}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_cistatic int radio_tea5777_update_read_reg(struct radio_tea5777 *tea, int wait)
2258c2ecf20Sopenharmony_ci{
2268c2ecf20Sopenharmony_ci	int res;
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	if (tea->read_reg != -1)
2298c2ecf20Sopenharmony_ci		return 0;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	if (tea->write_before_read && tea->needs_write) {
2328c2ecf20Sopenharmony_ci		res = radio_tea5777_set_freq(tea);
2338c2ecf20Sopenharmony_ci		if (res)
2348c2ecf20Sopenharmony_ci			return res;
2358c2ecf20Sopenharmony_ci	}
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	if (wait) {
2388c2ecf20Sopenharmony_ci		if (schedule_timeout_interruptible(msecs_to_jiffies(wait)))
2398c2ecf20Sopenharmony_ci			return -ERESTARTSYS;
2408c2ecf20Sopenharmony_ci	}
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	res = tea->ops->read_reg(tea, &tea->read_reg);
2438c2ecf20Sopenharmony_ci	if (res)
2448c2ecf20Sopenharmony_ci		return res;
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	tea->needs_write = true;
2478c2ecf20Sopenharmony_ci	return 0;
2488c2ecf20Sopenharmony_ci}
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci/*
2518c2ecf20Sopenharmony_ci * Linux Video interface
2528c2ecf20Sopenharmony_ci */
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_cistatic int vidioc_querycap(struct file *file, void  *priv,
2558c2ecf20Sopenharmony_ci					struct v4l2_capability *v)
2568c2ecf20Sopenharmony_ci{
2578c2ecf20Sopenharmony_ci	struct radio_tea5777 *tea = video_drvdata(file);
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver));
2608c2ecf20Sopenharmony_ci	strscpy(v->card, tea->card, sizeof(v->card));
2618c2ecf20Sopenharmony_ci	strlcat(v->card, " TEA5777", sizeof(v->card));
2628c2ecf20Sopenharmony_ci	strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info));
2638c2ecf20Sopenharmony_ci	return 0;
2648c2ecf20Sopenharmony_ci}
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_cistatic int vidioc_enum_freq_bands(struct file *file, void *priv,
2678c2ecf20Sopenharmony_ci					 struct v4l2_frequency_band *band)
2688c2ecf20Sopenharmony_ci{
2698c2ecf20Sopenharmony_ci	struct radio_tea5777 *tea = video_drvdata(file);
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	if (band->tuner != 0 || band->index >= ARRAY_SIZE(bands) ||
2728c2ecf20Sopenharmony_ci	    (!tea->has_am && band->index == BAND_AM))
2738c2ecf20Sopenharmony_ci		return -EINVAL;
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	*band = bands[band->index];
2768c2ecf20Sopenharmony_ci	return 0;
2778c2ecf20Sopenharmony_ci}
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv,
2808c2ecf20Sopenharmony_ci					struct v4l2_tuner *v)
2818c2ecf20Sopenharmony_ci{
2828c2ecf20Sopenharmony_ci	struct radio_tea5777 *tea = video_drvdata(file);
2838c2ecf20Sopenharmony_ci	int res;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	if (v->index > 0)
2868c2ecf20Sopenharmony_ci		return -EINVAL;
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	res = radio_tea5777_update_read_reg(tea, 0);
2898c2ecf20Sopenharmony_ci	if (res)
2908c2ecf20Sopenharmony_ci		return res;
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	memset(v, 0, sizeof(*v));
2938c2ecf20Sopenharmony_ci	if (tea->has_am)
2948c2ecf20Sopenharmony_ci		strscpy(v->name, "AM/FM", sizeof(v->name));
2958c2ecf20Sopenharmony_ci	else
2968c2ecf20Sopenharmony_ci		strscpy(v->name, "FM", sizeof(v->name));
2978c2ecf20Sopenharmony_ci	v->type = V4L2_TUNER_RADIO;
2988c2ecf20Sopenharmony_ci	v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
2998c2ecf20Sopenharmony_ci			V4L2_TUNER_CAP_FREQ_BANDS |
3008c2ecf20Sopenharmony_ci			V4L2_TUNER_CAP_HWSEEK_BOUNDED |
3018c2ecf20Sopenharmony_ci			V4L2_TUNER_CAP_HWSEEK_PROG_LIM;
3028c2ecf20Sopenharmony_ci	v->rangelow   = tea->has_am ? bands[BAND_AM].rangelow :
3038c2ecf20Sopenharmony_ci				      bands[BAND_FM].rangelow;
3048c2ecf20Sopenharmony_ci	v->rangehigh  = bands[BAND_FM].rangehigh;
3058c2ecf20Sopenharmony_ci	if (tea->band == BAND_FM &&
3068c2ecf20Sopenharmony_ci			(tea->read_reg & TEA5777_R_FM_STEREO_MASK))
3078c2ecf20Sopenharmony_ci		v->rxsubchans = V4L2_TUNER_SUB_STEREO;
3088c2ecf20Sopenharmony_ci	else
3098c2ecf20Sopenharmony_ci		v->rxsubchans = V4L2_TUNER_SUB_MONO;
3108c2ecf20Sopenharmony_ci	v->audmode = tea->audmode;
3118c2ecf20Sopenharmony_ci	/* shift - 12 to convert 4-bits (0-15) scale to 16-bits (0-65535) */
3128c2ecf20Sopenharmony_ci	v->signal = (tea->read_reg & TEA5777_R_LEVEL_MASK) >>
3138c2ecf20Sopenharmony_ci		    (TEA5777_R_LEVEL_SHIFT - 12);
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_ci	/* Invalidate read_reg, so that next call we return up2date signal */
3168c2ecf20Sopenharmony_ci	tea->read_reg = -1;
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	return 0;
3198c2ecf20Sopenharmony_ci}
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv,
3228c2ecf20Sopenharmony_ci					const struct v4l2_tuner *v)
3238c2ecf20Sopenharmony_ci{
3248c2ecf20Sopenharmony_ci	struct radio_tea5777 *tea = video_drvdata(file);
3258c2ecf20Sopenharmony_ci	u32 orig_audmode = tea->audmode;
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	if (v->index)
3288c2ecf20Sopenharmony_ci		return -EINVAL;
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	tea->audmode = v->audmode;
3318c2ecf20Sopenharmony_ci	if (tea->audmode > V4L2_TUNER_MODE_STEREO)
3328c2ecf20Sopenharmony_ci		tea->audmode = V4L2_TUNER_MODE_STEREO;
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci	if (tea->audmode != orig_audmode && tea->band == BAND_FM)
3358c2ecf20Sopenharmony_ci		return radio_tea5777_set_freq(tea);
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	return 0;
3388c2ecf20Sopenharmony_ci}
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv,
3418c2ecf20Sopenharmony_ci					struct v4l2_frequency *f)
3428c2ecf20Sopenharmony_ci{
3438c2ecf20Sopenharmony_ci	struct radio_tea5777 *tea = video_drvdata(file);
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	if (f->tuner != 0)
3468c2ecf20Sopenharmony_ci		return -EINVAL;
3478c2ecf20Sopenharmony_ci	f->type = V4L2_TUNER_RADIO;
3488c2ecf20Sopenharmony_ci	f->frequency = tea->freq;
3498c2ecf20Sopenharmony_ci	return 0;
3508c2ecf20Sopenharmony_ci}
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv,
3538c2ecf20Sopenharmony_ci					const struct v4l2_frequency *f)
3548c2ecf20Sopenharmony_ci{
3558c2ecf20Sopenharmony_ci	struct radio_tea5777 *tea = video_drvdata(file);
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
3588c2ecf20Sopenharmony_ci		return -EINVAL;
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	if (tea->has_am && f->frequency < (20000 * 16))
3618c2ecf20Sopenharmony_ci		tea->band = BAND_AM;
3628c2ecf20Sopenharmony_ci	else
3638c2ecf20Sopenharmony_ci		tea->band = BAND_FM;
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ci	tea->freq = f->frequency;
3668c2ecf20Sopenharmony_ci	return radio_tea5777_set_freq(tea);
3678c2ecf20Sopenharmony_ci}
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_cistatic int vidioc_s_hw_freq_seek(struct file *file, void *fh,
3708c2ecf20Sopenharmony_ci					const struct v4l2_hw_freq_seek *a)
3718c2ecf20Sopenharmony_ci{
3728c2ecf20Sopenharmony_ci	struct radio_tea5777 *tea = video_drvdata(file);
3738c2ecf20Sopenharmony_ci	unsigned long timeout;
3748c2ecf20Sopenharmony_ci	u32 rangelow = a->rangelow;
3758c2ecf20Sopenharmony_ci	u32 rangehigh = a->rangehigh;
3768c2ecf20Sopenharmony_ci	int i, res, spacing;
3778c2ecf20Sopenharmony_ci	u32 orig_freq;
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	if (a->tuner || a->wrap_around)
3808c2ecf20Sopenharmony_ci		return -EINVAL;
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	if (file->f_flags & O_NONBLOCK)
3838c2ecf20Sopenharmony_ci		return -EWOULDBLOCK;
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci	if (rangelow || rangehigh) {
3868c2ecf20Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(bands); i++) {
3878c2ecf20Sopenharmony_ci			if (i == BAND_AM && !tea->has_am)
3888c2ecf20Sopenharmony_ci				continue;
3898c2ecf20Sopenharmony_ci			if (bands[i].rangelow  >= rangelow &&
3908c2ecf20Sopenharmony_ci			    bands[i].rangehigh <= rangehigh)
3918c2ecf20Sopenharmony_ci				break;
3928c2ecf20Sopenharmony_ci		}
3938c2ecf20Sopenharmony_ci		if (i == ARRAY_SIZE(bands))
3948c2ecf20Sopenharmony_ci			return -EINVAL; /* No matching band found */
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci		tea->band = i;
3978c2ecf20Sopenharmony_ci		if (tea->freq < rangelow || tea->freq > rangehigh) {
3988c2ecf20Sopenharmony_ci			tea->freq = clamp(tea->freq, rangelow,
3998c2ecf20Sopenharmony_ci						     rangehigh);
4008c2ecf20Sopenharmony_ci			res = radio_tea5777_set_freq(tea);
4018c2ecf20Sopenharmony_ci			if (res)
4028c2ecf20Sopenharmony_ci				return res;
4038c2ecf20Sopenharmony_ci		}
4048c2ecf20Sopenharmony_ci	} else {
4058c2ecf20Sopenharmony_ci		rangelow  = bands[tea->band].rangelow;
4068c2ecf20Sopenharmony_ci		rangehigh = bands[tea->band].rangehigh;
4078c2ecf20Sopenharmony_ci	}
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	spacing   = (tea->band == BAND_AM) ? (5 * 16) : (200 * 16); /* kHz */
4108c2ecf20Sopenharmony_ci	orig_freq = tea->freq;
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	tea->write_reg |= TEA5777_W_PROGBLIM_MASK;
4138c2ecf20Sopenharmony_ci	if (tea->seek_rangelow != rangelow) {
4148c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_UPDWN_MASK;
4158c2ecf20Sopenharmony_ci		tea->freq = rangelow;
4168c2ecf20Sopenharmony_ci		res = radio_tea5777_set_freq(tea);
4178c2ecf20Sopenharmony_ci		if (res)
4188c2ecf20Sopenharmony_ci			goto leave;
4198c2ecf20Sopenharmony_ci		tea->seek_rangelow = rangelow;
4208c2ecf20Sopenharmony_ci	}
4218c2ecf20Sopenharmony_ci	if (tea->seek_rangehigh != rangehigh) {
4228c2ecf20Sopenharmony_ci		tea->write_reg |= TEA5777_W_UPDWN_MASK;
4238c2ecf20Sopenharmony_ci		tea->freq = rangehigh;
4248c2ecf20Sopenharmony_ci		res = radio_tea5777_set_freq(tea);
4258c2ecf20Sopenharmony_ci		if (res)
4268c2ecf20Sopenharmony_ci			goto leave;
4278c2ecf20Sopenharmony_ci		tea->seek_rangehigh = rangehigh;
4288c2ecf20Sopenharmony_ci	}
4298c2ecf20Sopenharmony_ci	tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK;
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci	tea->write_reg |= TEA5777_W_SEARCH_MASK;
4328c2ecf20Sopenharmony_ci	if (a->seek_upward) {
4338c2ecf20Sopenharmony_ci		tea->write_reg |= TEA5777_W_UPDWN_MASK;
4348c2ecf20Sopenharmony_ci		tea->freq = orig_freq + spacing;
4358c2ecf20Sopenharmony_ci	} else {
4368c2ecf20Sopenharmony_ci		tea->write_reg &= ~TEA5777_W_UPDWN_MASK;
4378c2ecf20Sopenharmony_ci		tea->freq = orig_freq - spacing;
4388c2ecf20Sopenharmony_ci	}
4398c2ecf20Sopenharmony_ci	res = radio_tea5777_set_freq(tea);
4408c2ecf20Sopenharmony_ci	if (res)
4418c2ecf20Sopenharmony_ci		goto leave;
4428c2ecf20Sopenharmony_ci
4438c2ecf20Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(5000);
4448c2ecf20Sopenharmony_ci	for (;;) {
4458c2ecf20Sopenharmony_ci		if (time_after(jiffies, timeout)) {
4468c2ecf20Sopenharmony_ci			res = -ENODATA;
4478c2ecf20Sopenharmony_ci			break;
4488c2ecf20Sopenharmony_ci		}
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci		res = radio_tea5777_update_read_reg(tea, 100);
4518c2ecf20Sopenharmony_ci		if (res)
4528c2ecf20Sopenharmony_ci			break;
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci		/*
4558c2ecf20Sopenharmony_ci		 * Note we use tea->freq to track how far we've searched sofar
4568c2ecf20Sopenharmony_ci		 * this is necessary to ensure we continue seeking at the right
4578c2ecf20Sopenharmony_ci		 * point, in the write_before_read case.
4588c2ecf20Sopenharmony_ci		 */
4598c2ecf20Sopenharmony_ci		tea->freq = (tea->read_reg & TEA5777_R_FM_PLL_MASK);
4608c2ecf20Sopenharmony_ci		tea->freq = tea5777_freq_to_v4l2_freq(tea, tea->freq);
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ci		if ((tea->read_reg & TEA5777_R_SFOUND_MASK)) {
4638c2ecf20Sopenharmony_ci			tea->write_reg &= ~TEA5777_W_SEARCH_MASK;
4648c2ecf20Sopenharmony_ci			return 0;
4658c2ecf20Sopenharmony_ci		}
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci		if (tea->read_reg & TEA5777_R_BLIM_MASK) {
4688c2ecf20Sopenharmony_ci			res = -ENODATA;
4698c2ecf20Sopenharmony_ci			break;
4708c2ecf20Sopenharmony_ci		}
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_ci		/* Force read_reg update */
4738c2ecf20Sopenharmony_ci		tea->read_reg = -1;
4748c2ecf20Sopenharmony_ci	}
4758c2ecf20Sopenharmony_cileave:
4768c2ecf20Sopenharmony_ci	tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK;
4778c2ecf20Sopenharmony_ci	tea->write_reg &= ~TEA5777_W_SEARCH_MASK;
4788c2ecf20Sopenharmony_ci	tea->freq = orig_freq;
4798c2ecf20Sopenharmony_ci	radio_tea5777_set_freq(tea);
4808c2ecf20Sopenharmony_ci	return res;
4818c2ecf20Sopenharmony_ci}
4828c2ecf20Sopenharmony_ci
4838c2ecf20Sopenharmony_cistatic int tea575x_s_ctrl(struct v4l2_ctrl *c)
4848c2ecf20Sopenharmony_ci{
4858c2ecf20Sopenharmony_ci	struct radio_tea5777 *tea =
4868c2ecf20Sopenharmony_ci		container_of(c->handler, struct radio_tea5777, ctrl_handler);
4878c2ecf20Sopenharmony_ci
4888c2ecf20Sopenharmony_ci	switch (c->id) {
4898c2ecf20Sopenharmony_ci	case V4L2_CID_AUDIO_MUTE:
4908c2ecf20Sopenharmony_ci		if (c->val)
4918c2ecf20Sopenharmony_ci			tea->write_reg |= TEA5777_W_MUTE_MASK;
4928c2ecf20Sopenharmony_ci		else
4938c2ecf20Sopenharmony_ci			tea->write_reg &= ~TEA5777_W_MUTE_MASK;
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci		return radio_tea5777_set_freq(tea);
4968c2ecf20Sopenharmony_ci	}
4978c2ecf20Sopenharmony_ci
4988c2ecf20Sopenharmony_ci	return -EINVAL;
4998c2ecf20Sopenharmony_ci}
5008c2ecf20Sopenharmony_ci
5018c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations tea575x_fops = {
5028c2ecf20Sopenharmony_ci	.unlocked_ioctl	= video_ioctl2,
5038c2ecf20Sopenharmony_ci	.open		= v4l2_fh_open,
5048c2ecf20Sopenharmony_ci	.release	= v4l2_fh_release,
5058c2ecf20Sopenharmony_ci	.poll		= v4l2_ctrl_poll,
5068c2ecf20Sopenharmony_ci};
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops tea575x_ioctl_ops = {
5098c2ecf20Sopenharmony_ci	.vidioc_querycap    = vidioc_querycap,
5108c2ecf20Sopenharmony_ci	.vidioc_g_tuner     = vidioc_g_tuner,
5118c2ecf20Sopenharmony_ci	.vidioc_s_tuner     = vidioc_s_tuner,
5128c2ecf20Sopenharmony_ci	.vidioc_g_frequency = vidioc_g_frequency,
5138c2ecf20Sopenharmony_ci	.vidioc_s_frequency = vidioc_s_frequency,
5148c2ecf20Sopenharmony_ci	.vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek,
5158c2ecf20Sopenharmony_ci	.vidioc_enum_freq_bands = vidioc_enum_freq_bands,
5168c2ecf20Sopenharmony_ci	.vidioc_log_status  = v4l2_ctrl_log_status,
5178c2ecf20Sopenharmony_ci	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
5188c2ecf20Sopenharmony_ci	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
5198c2ecf20Sopenharmony_ci};
5208c2ecf20Sopenharmony_ci
5218c2ecf20Sopenharmony_cistatic const struct video_device tea575x_radio = {
5228c2ecf20Sopenharmony_ci	.ioctl_ops	= &tea575x_ioctl_ops,
5238c2ecf20Sopenharmony_ci	.release	= video_device_release_empty,
5248c2ecf20Sopenharmony_ci};
5258c2ecf20Sopenharmony_ci
5268c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops tea575x_ctrl_ops = {
5278c2ecf20Sopenharmony_ci	.s_ctrl = tea575x_s_ctrl,
5288c2ecf20Sopenharmony_ci};
5298c2ecf20Sopenharmony_ci
5308c2ecf20Sopenharmony_ciint radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner)
5318c2ecf20Sopenharmony_ci{
5328c2ecf20Sopenharmony_ci	int res;
5338c2ecf20Sopenharmony_ci
5348c2ecf20Sopenharmony_ci	tea->write_reg = (1LL << TEA5777_W_IFCE_SHIFT) |
5358c2ecf20Sopenharmony_ci			 (1LL << TEA5777_W_IFW_SHIFT) |
5368c2ecf20Sopenharmony_ci			 (1LL << TEA5777_W_INTEXT_SHIFT) |
5378c2ecf20Sopenharmony_ci			 (1LL << TEA5777_W_CHP0_SHIFT) |
5388c2ecf20Sopenharmony_ci			 (1LL << TEA5777_W_SLEV_SHIFT);
5398c2ecf20Sopenharmony_ci	tea->freq = 90500 * 16;	/* 90.5Mhz default */
5408c2ecf20Sopenharmony_ci	tea->audmode = V4L2_TUNER_MODE_STEREO;
5418c2ecf20Sopenharmony_ci	res = radio_tea5777_set_freq(tea);
5428c2ecf20Sopenharmony_ci	if (res) {
5438c2ecf20Sopenharmony_ci		v4l2_err(tea->v4l2_dev, "can't set initial freq (%d)\n", res);
5448c2ecf20Sopenharmony_ci		return res;
5458c2ecf20Sopenharmony_ci	}
5468c2ecf20Sopenharmony_ci
5478c2ecf20Sopenharmony_ci	tea->vd = tea575x_radio;
5488c2ecf20Sopenharmony_ci	video_set_drvdata(&tea->vd, tea);
5498c2ecf20Sopenharmony_ci	mutex_init(&tea->mutex);
5508c2ecf20Sopenharmony_ci	strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name));
5518c2ecf20Sopenharmony_ci	tea->vd.lock = &tea->mutex;
5528c2ecf20Sopenharmony_ci	tea->vd.v4l2_dev = tea->v4l2_dev;
5538c2ecf20Sopenharmony_ci	tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
5548c2ecf20Sopenharmony_ci			      V4L2_CAP_HW_FREQ_SEEK;
5558c2ecf20Sopenharmony_ci	tea->fops = tea575x_fops;
5568c2ecf20Sopenharmony_ci	tea->fops.owner = owner;
5578c2ecf20Sopenharmony_ci	tea->vd.fops = &tea->fops;
5588c2ecf20Sopenharmony_ci
5598c2ecf20Sopenharmony_ci	tea->vd.ctrl_handler = &tea->ctrl_handler;
5608c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_init(&tea->ctrl_handler, 1);
5618c2ecf20Sopenharmony_ci	v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops,
5628c2ecf20Sopenharmony_ci			  V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
5638c2ecf20Sopenharmony_ci	res = tea->ctrl_handler.error;
5648c2ecf20Sopenharmony_ci	if (res) {
5658c2ecf20Sopenharmony_ci		v4l2_err(tea->v4l2_dev, "can't initialize controls\n");
5668c2ecf20Sopenharmony_ci		v4l2_ctrl_handler_free(&tea->ctrl_handler);
5678c2ecf20Sopenharmony_ci		return res;
5688c2ecf20Sopenharmony_ci	}
5698c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_setup(&tea->ctrl_handler);
5708c2ecf20Sopenharmony_ci
5718c2ecf20Sopenharmony_ci	res = video_register_device(&tea->vd, VFL_TYPE_RADIO, -1);
5728c2ecf20Sopenharmony_ci	if (res) {
5738c2ecf20Sopenharmony_ci		v4l2_err(tea->v4l2_dev, "can't register video device!\n");
5748c2ecf20Sopenharmony_ci		v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
5758c2ecf20Sopenharmony_ci		return res;
5768c2ecf20Sopenharmony_ci	}
5778c2ecf20Sopenharmony_ci
5788c2ecf20Sopenharmony_ci	return 0;
5798c2ecf20Sopenharmony_ci}
5808c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(radio_tea5777_init);
5818c2ecf20Sopenharmony_ci
5828c2ecf20Sopenharmony_civoid radio_tea5777_exit(struct radio_tea5777 *tea)
5838c2ecf20Sopenharmony_ci{
5848c2ecf20Sopenharmony_ci	video_unregister_device(&tea->vd);
5858c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
5868c2ecf20Sopenharmony_ci}
5878c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(radio_tea5777_exit);
588