18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *   ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *	Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/delay.h>
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/init.h>
118c2ecf20Sopenharmony_ci#include <linux/slab.h>
128c2ecf20Sopenharmony_ci#include <linux/sched.h>
138c2ecf20Sopenharmony_ci#include <asm/io.h>
148c2ecf20Sopenharmony_ci#include <media/v4l2-device.h>
158c2ecf20Sopenharmony_ci#include <media/v4l2-dev.h>
168c2ecf20Sopenharmony_ci#include <media/v4l2-fh.h>
178c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h>
188c2ecf20Sopenharmony_ci#include <media/v4l2-event.h>
198c2ecf20Sopenharmony_ci#include <media/drv-intf/tea575x.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
228c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips");
238c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci/*
268c2ecf20Sopenharmony_ci * definitions
278c2ecf20Sopenharmony_ci */
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH	(1<<24)		/* 1 = search action, 0 = tuned */
308c2ecf20Sopenharmony_ci#define TEA575X_BIT_UPDOWN	(1<<23)		/* 0 = search down, 1 = search up */
318c2ecf20Sopenharmony_ci#define TEA575X_BIT_MONO	(1<<22)		/* 0 = stereo, 1 = mono */
328c2ecf20Sopenharmony_ci#define TEA575X_BIT_BAND_MASK	(3<<20)
338c2ecf20Sopenharmony_ci#define TEA575X_BIT_BAND_FM	(0<<20)
348c2ecf20Sopenharmony_ci#define TEA575X_BIT_BAND_MW	(1<<20)
358c2ecf20Sopenharmony_ci#define TEA575X_BIT_BAND_LW	(2<<20)
368c2ecf20Sopenharmony_ci#define TEA575X_BIT_BAND_SW	(3<<20)
378c2ecf20Sopenharmony_ci#define TEA575X_BIT_PORT_0	(1<<19)		/* user bit */
388c2ecf20Sopenharmony_ci#define TEA575X_BIT_PORT_1	(1<<18)		/* user bit */
398c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH_MASK	(3<<16)		/* search level */
408c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH_5_28	     (0<<16)	/* FM >5uV, AM >28uV */
418c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH_10_40     (1<<16)	/* FM >10uV, AM > 40uV */
428c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH_30_63     (2<<16)	/* FM >30uV, AM > 63uV */
438c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH_150_1000  (3<<16)	/* FM > 150uV, AM > 1000uV */
448c2ecf20Sopenharmony_ci#define TEA575X_BIT_DUMMY	(1<<15)		/* buffer */
458c2ecf20Sopenharmony_ci#define TEA575X_BIT_FREQ_MASK	0x7fff
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cienum { BAND_FM, BAND_FM_JAPAN, BAND_AM };
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic const struct v4l2_frequency_band bands[] = {
508c2ecf20Sopenharmony_ci	{
518c2ecf20Sopenharmony_ci		.type = V4L2_TUNER_RADIO,
528c2ecf20Sopenharmony_ci		.index = 0,
538c2ecf20Sopenharmony_ci		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
548c2ecf20Sopenharmony_ci			      V4L2_TUNER_CAP_FREQ_BANDS,
558c2ecf20Sopenharmony_ci		.rangelow   =  87500 * 16,
568c2ecf20Sopenharmony_ci		.rangehigh  = 108000 * 16,
578c2ecf20Sopenharmony_ci		.modulation = V4L2_BAND_MODULATION_FM,
588c2ecf20Sopenharmony_ci	},
598c2ecf20Sopenharmony_ci	{
608c2ecf20Sopenharmony_ci		.type = V4L2_TUNER_RADIO,
618c2ecf20Sopenharmony_ci		.index = 0,
628c2ecf20Sopenharmony_ci		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
638c2ecf20Sopenharmony_ci			      V4L2_TUNER_CAP_FREQ_BANDS,
648c2ecf20Sopenharmony_ci		.rangelow   = 76000 * 16,
658c2ecf20Sopenharmony_ci		.rangehigh  = 91000 * 16,
668c2ecf20Sopenharmony_ci		.modulation = V4L2_BAND_MODULATION_FM,
678c2ecf20Sopenharmony_ci	},
688c2ecf20Sopenharmony_ci	{
698c2ecf20Sopenharmony_ci		.type = V4L2_TUNER_RADIO,
708c2ecf20Sopenharmony_ci		.index = 1,
718c2ecf20Sopenharmony_ci		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
728c2ecf20Sopenharmony_ci		.rangelow   =  530 * 16,
738c2ecf20Sopenharmony_ci		.rangehigh  = 1710 * 16,
748c2ecf20Sopenharmony_ci		.modulation = V4L2_BAND_MODULATION_AM,
758c2ecf20Sopenharmony_ci	},
768c2ecf20Sopenharmony_ci};
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci/*
798c2ecf20Sopenharmony_ci * lowlevel part
808c2ecf20Sopenharmony_ci */
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic void snd_tea575x_write(struct snd_tea575x *tea, unsigned int val)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	u16 l;
858c2ecf20Sopenharmony_ci	u8 data;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	if (tea->ops->write_val)
888c2ecf20Sopenharmony_ci		return tea->ops->write_val(tea, val);
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	tea->ops->set_direction(tea, 1);
918c2ecf20Sopenharmony_ci	udelay(16);
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	for (l = 25; l > 0; l--) {
948c2ecf20Sopenharmony_ci		data = (val >> 24) & TEA575X_DATA;
958c2ecf20Sopenharmony_ci		val <<= 1;			/* shift data */
968c2ecf20Sopenharmony_ci		tea->ops->set_pins(tea, data | TEA575X_WREN);
978c2ecf20Sopenharmony_ci		udelay(2);
988c2ecf20Sopenharmony_ci		tea->ops->set_pins(tea, data | TEA575X_WREN | TEA575X_CLK);
998c2ecf20Sopenharmony_ci		udelay(2);
1008c2ecf20Sopenharmony_ci		tea->ops->set_pins(tea, data | TEA575X_WREN);
1018c2ecf20Sopenharmony_ci		udelay(2);
1028c2ecf20Sopenharmony_ci	}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	if (!tea->mute)
1058c2ecf20Sopenharmony_ci		tea->ops->set_pins(tea, 0);
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic u32 snd_tea575x_read(struct snd_tea575x *tea)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	u16 l, rdata;
1118c2ecf20Sopenharmony_ci	u32 data = 0;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	if (tea->ops->read_val)
1148c2ecf20Sopenharmony_ci		return tea->ops->read_val(tea);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	tea->ops->set_direction(tea, 0);
1178c2ecf20Sopenharmony_ci	tea->ops->set_pins(tea, 0);
1188c2ecf20Sopenharmony_ci	udelay(16);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	for (l = 24; l--;) {
1218c2ecf20Sopenharmony_ci		tea->ops->set_pins(tea, TEA575X_CLK);
1228c2ecf20Sopenharmony_ci		udelay(2);
1238c2ecf20Sopenharmony_ci		if (!l)
1248c2ecf20Sopenharmony_ci			tea->tuned = tea->ops->get_pins(tea) & TEA575X_MOST ? 0 : 1;
1258c2ecf20Sopenharmony_ci		tea->ops->set_pins(tea, 0);
1268c2ecf20Sopenharmony_ci		udelay(2);
1278c2ecf20Sopenharmony_ci		data <<= 1;			/* shift data */
1288c2ecf20Sopenharmony_ci		rdata = tea->ops->get_pins(tea);
1298c2ecf20Sopenharmony_ci		if (!l)
1308c2ecf20Sopenharmony_ci			tea->stereo = (rdata & TEA575X_MOST) ?  0 : 1;
1318c2ecf20Sopenharmony_ci		if (rdata & TEA575X_DATA)
1328c2ecf20Sopenharmony_ci			data++;
1338c2ecf20Sopenharmony_ci		udelay(2);
1348c2ecf20Sopenharmony_ci	}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	if (tea->mute)
1378c2ecf20Sopenharmony_ci		tea->ops->set_pins(tea, TEA575X_WREN);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	return data;
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic u32 snd_tea575x_val_to_freq(struct snd_tea575x *tea, u32 val)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	u32 freq = val & TEA575X_BIT_FREQ_MASK;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	if (freq == 0)
1478c2ecf20Sopenharmony_ci		return freq;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	switch (tea->band) {
1508c2ecf20Sopenharmony_ci	case BAND_FM:
1518c2ecf20Sopenharmony_ci		/* freq *= 12.5 */
1528c2ecf20Sopenharmony_ci		freq *= 125;
1538c2ecf20Sopenharmony_ci		freq /= 10;
1548c2ecf20Sopenharmony_ci		/* crystal fixup */
1558c2ecf20Sopenharmony_ci		freq -= TEA575X_FMIF;
1568c2ecf20Sopenharmony_ci		break;
1578c2ecf20Sopenharmony_ci	case BAND_FM_JAPAN:
1588c2ecf20Sopenharmony_ci		/* freq *= 12.5 */
1598c2ecf20Sopenharmony_ci		freq *= 125;
1608c2ecf20Sopenharmony_ci		freq /= 10;
1618c2ecf20Sopenharmony_ci		/* crystal fixup */
1628c2ecf20Sopenharmony_ci		freq += TEA575X_FMIF;
1638c2ecf20Sopenharmony_ci		break;
1648c2ecf20Sopenharmony_ci	case BAND_AM:
1658c2ecf20Sopenharmony_ci		/* crystal fixup */
1668c2ecf20Sopenharmony_ci		freq -= TEA575X_AMIF;
1678c2ecf20Sopenharmony_ci		break;
1688c2ecf20Sopenharmony_ci	}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	return clamp(freq * 16, bands[tea->band].rangelow,
1718c2ecf20Sopenharmony_ci				bands[tea->band].rangehigh); /* from kHz */
1728c2ecf20Sopenharmony_ci}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_cistatic u32 snd_tea575x_get_freq(struct snd_tea575x *tea)
1758c2ecf20Sopenharmony_ci{
1768c2ecf20Sopenharmony_ci	return snd_tea575x_val_to_freq(tea, snd_tea575x_read(tea));
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_civoid snd_tea575x_set_freq(struct snd_tea575x *tea)
1808c2ecf20Sopenharmony_ci{
1818c2ecf20Sopenharmony_ci	u32 freq = tea->freq / 16;	/* to kHz */
1828c2ecf20Sopenharmony_ci	u32 band = 0;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	switch (tea->band) {
1858c2ecf20Sopenharmony_ci	case BAND_FM:
1868c2ecf20Sopenharmony_ci		band = TEA575X_BIT_BAND_FM;
1878c2ecf20Sopenharmony_ci		/* crystal fixup */
1888c2ecf20Sopenharmony_ci		freq += TEA575X_FMIF;
1898c2ecf20Sopenharmony_ci		/* freq /= 12.5 */
1908c2ecf20Sopenharmony_ci		freq *= 10;
1918c2ecf20Sopenharmony_ci		freq /= 125;
1928c2ecf20Sopenharmony_ci		break;
1938c2ecf20Sopenharmony_ci	case BAND_FM_JAPAN:
1948c2ecf20Sopenharmony_ci		band = TEA575X_BIT_BAND_FM;
1958c2ecf20Sopenharmony_ci		/* crystal fixup */
1968c2ecf20Sopenharmony_ci		freq -= TEA575X_FMIF;
1978c2ecf20Sopenharmony_ci		/* freq /= 12.5 */
1988c2ecf20Sopenharmony_ci		freq *= 10;
1998c2ecf20Sopenharmony_ci		freq /= 125;
2008c2ecf20Sopenharmony_ci		break;
2018c2ecf20Sopenharmony_ci	case BAND_AM:
2028c2ecf20Sopenharmony_ci		band = TEA575X_BIT_BAND_MW;
2038c2ecf20Sopenharmony_ci		/* crystal fixup */
2048c2ecf20Sopenharmony_ci		freq += TEA575X_AMIF;
2058c2ecf20Sopenharmony_ci		break;
2068c2ecf20Sopenharmony_ci	}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	tea->val &= ~(TEA575X_BIT_FREQ_MASK | TEA575X_BIT_BAND_MASK);
2098c2ecf20Sopenharmony_ci	tea->val |= band;
2108c2ecf20Sopenharmony_ci	tea->val |= freq & TEA575X_BIT_FREQ_MASK;
2118c2ecf20Sopenharmony_ci	snd_tea575x_write(tea, tea->val);
2128c2ecf20Sopenharmony_ci	tea->freq = snd_tea575x_val_to_freq(tea, tea->val);
2138c2ecf20Sopenharmony_ci}
2148c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_set_freq);
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci/*
2178c2ecf20Sopenharmony_ci * Linux Video interface
2188c2ecf20Sopenharmony_ci */
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_cistatic int vidioc_querycap(struct file *file, void  *priv,
2218c2ecf20Sopenharmony_ci					struct v4l2_capability *v)
2228c2ecf20Sopenharmony_ci{
2238c2ecf20Sopenharmony_ci	struct snd_tea575x *tea = video_drvdata(file);
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver));
2268c2ecf20Sopenharmony_ci	strscpy(v->card, tea->card, sizeof(v->card));
2278c2ecf20Sopenharmony_ci	strlcat(v->card, tea->tea5759 ? " TEA5759" : " TEA5757", sizeof(v->card));
2288c2ecf20Sopenharmony_ci	strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info));
2298c2ecf20Sopenharmony_ci	return 0;
2308c2ecf20Sopenharmony_ci}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ciint snd_tea575x_enum_freq_bands(struct snd_tea575x *tea,
2338c2ecf20Sopenharmony_ci					struct v4l2_frequency_band *band)
2348c2ecf20Sopenharmony_ci{
2358c2ecf20Sopenharmony_ci	int index;
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	if (band->tuner != 0)
2388c2ecf20Sopenharmony_ci		return -EINVAL;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	switch (band->index) {
2418c2ecf20Sopenharmony_ci	case 0:
2428c2ecf20Sopenharmony_ci		if (tea->tea5759)
2438c2ecf20Sopenharmony_ci			index = BAND_FM_JAPAN;
2448c2ecf20Sopenharmony_ci		else
2458c2ecf20Sopenharmony_ci			index = BAND_FM;
2468c2ecf20Sopenharmony_ci		break;
2478c2ecf20Sopenharmony_ci	case 1:
2488c2ecf20Sopenharmony_ci		if (tea->has_am) {
2498c2ecf20Sopenharmony_ci			index = BAND_AM;
2508c2ecf20Sopenharmony_ci			break;
2518c2ecf20Sopenharmony_ci		}
2528c2ecf20Sopenharmony_ci		fallthrough;
2538c2ecf20Sopenharmony_ci	default:
2548c2ecf20Sopenharmony_ci		return -EINVAL;
2558c2ecf20Sopenharmony_ci	}
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	*band = bands[index];
2588c2ecf20Sopenharmony_ci	if (!tea->cannot_read_data)
2598c2ecf20Sopenharmony_ci		band->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	return 0;
2628c2ecf20Sopenharmony_ci}
2638c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_enum_freq_bands);
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_cistatic int vidioc_enum_freq_bands(struct file *file, void *priv,
2668c2ecf20Sopenharmony_ci					 struct v4l2_frequency_band *band)
2678c2ecf20Sopenharmony_ci{
2688c2ecf20Sopenharmony_ci	struct snd_tea575x *tea = video_drvdata(file);
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	return snd_tea575x_enum_freq_bands(tea, band);
2718c2ecf20Sopenharmony_ci}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ciint snd_tea575x_g_tuner(struct snd_tea575x *tea, struct v4l2_tuner *v)
2748c2ecf20Sopenharmony_ci{
2758c2ecf20Sopenharmony_ci	struct v4l2_frequency_band band_fm = { 0, };
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	if (v->index > 0)
2788c2ecf20Sopenharmony_ci		return -EINVAL;
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	snd_tea575x_read(tea);
2818c2ecf20Sopenharmony_ci	snd_tea575x_enum_freq_bands(tea, &band_fm);
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	memset(v, 0, sizeof(*v));
2848c2ecf20Sopenharmony_ci	strscpy(v->name, tea->has_am ? "FM/AM" : "FM", sizeof(v->name));
2858c2ecf20Sopenharmony_ci	v->type = V4L2_TUNER_RADIO;
2868c2ecf20Sopenharmony_ci	v->capability = band_fm.capability;
2878c2ecf20Sopenharmony_ci	v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : band_fm.rangelow;
2888c2ecf20Sopenharmony_ci	v->rangehigh = band_fm.rangehigh;
2898c2ecf20Sopenharmony_ci	v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
2908c2ecf20Sopenharmony_ci	v->audmode = (tea->val & TEA575X_BIT_MONO) ?
2918c2ecf20Sopenharmony_ci		V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO;
2928c2ecf20Sopenharmony_ci	v->signal = tea->tuned ? 0xffff : 0;
2938c2ecf20Sopenharmony_ci	return 0;
2948c2ecf20Sopenharmony_ci}
2958c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_g_tuner);
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv,
2988c2ecf20Sopenharmony_ci					struct v4l2_tuner *v)
2998c2ecf20Sopenharmony_ci{
3008c2ecf20Sopenharmony_ci	struct snd_tea575x *tea = video_drvdata(file);
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci	return snd_tea575x_g_tuner(tea, v);
3038c2ecf20Sopenharmony_ci}
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv,
3068c2ecf20Sopenharmony_ci					const struct v4l2_tuner *v)
3078c2ecf20Sopenharmony_ci{
3088c2ecf20Sopenharmony_ci	struct snd_tea575x *tea = video_drvdata(file);
3098c2ecf20Sopenharmony_ci	u32 orig_val = tea->val;
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci	if (v->index)
3128c2ecf20Sopenharmony_ci		return -EINVAL;
3138c2ecf20Sopenharmony_ci	tea->val &= ~TEA575X_BIT_MONO;
3148c2ecf20Sopenharmony_ci	if (v->audmode == V4L2_TUNER_MODE_MONO)
3158c2ecf20Sopenharmony_ci		tea->val |= TEA575X_BIT_MONO;
3168c2ecf20Sopenharmony_ci	/* Only apply changes if currently tuning FM */
3178c2ecf20Sopenharmony_ci	if (tea->band != BAND_AM && tea->val != orig_val)
3188c2ecf20Sopenharmony_ci		snd_tea575x_set_freq(tea);
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	return 0;
3218c2ecf20Sopenharmony_ci}
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv,
3248c2ecf20Sopenharmony_ci					struct v4l2_frequency *f)
3258c2ecf20Sopenharmony_ci{
3268c2ecf20Sopenharmony_ci	struct snd_tea575x *tea = video_drvdata(file);
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	if (f->tuner != 0)
3298c2ecf20Sopenharmony_ci		return -EINVAL;
3308c2ecf20Sopenharmony_ci	f->type = V4L2_TUNER_RADIO;
3318c2ecf20Sopenharmony_ci	f->frequency = tea->freq;
3328c2ecf20Sopenharmony_ci	return 0;
3338c2ecf20Sopenharmony_ci}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv,
3368c2ecf20Sopenharmony_ci					const struct v4l2_frequency *f)
3378c2ecf20Sopenharmony_ci{
3388c2ecf20Sopenharmony_ci	struct snd_tea575x *tea = video_drvdata(file);
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci	if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
3418c2ecf20Sopenharmony_ci		return -EINVAL;
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	if (tea->has_am && f->frequency < (20000 * 16))
3448c2ecf20Sopenharmony_ci		tea->band = BAND_AM;
3458c2ecf20Sopenharmony_ci	else if (tea->tea5759)
3468c2ecf20Sopenharmony_ci		tea->band = BAND_FM_JAPAN;
3478c2ecf20Sopenharmony_ci	else
3488c2ecf20Sopenharmony_ci		tea->band = BAND_FM;
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	tea->freq = clamp_t(u32, f->frequency, bands[tea->band].rangelow,
3518c2ecf20Sopenharmony_ci					bands[tea->band].rangehigh);
3528c2ecf20Sopenharmony_ci	snd_tea575x_set_freq(tea);
3538c2ecf20Sopenharmony_ci	return 0;
3548c2ecf20Sopenharmony_ci}
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ciint snd_tea575x_s_hw_freq_seek(struct file *file, struct snd_tea575x *tea,
3578c2ecf20Sopenharmony_ci				const struct v4l2_hw_freq_seek *a)
3588c2ecf20Sopenharmony_ci{
3598c2ecf20Sopenharmony_ci	unsigned long timeout;
3608c2ecf20Sopenharmony_ci	int i, spacing;
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	if (tea->cannot_read_data)
3638c2ecf20Sopenharmony_ci		return -ENOTTY;
3648c2ecf20Sopenharmony_ci	if (a->tuner || a->wrap_around)
3658c2ecf20Sopenharmony_ci		return -EINVAL;
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_ci	if (file->f_flags & O_NONBLOCK)
3688c2ecf20Sopenharmony_ci		return -EWOULDBLOCK;
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci	if (a->rangelow || a->rangehigh) {
3718c2ecf20Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(bands); i++) {
3728c2ecf20Sopenharmony_ci			if ((i == BAND_FM && tea->tea5759) ||
3738c2ecf20Sopenharmony_ci			    (i == BAND_FM_JAPAN && !tea->tea5759) ||
3748c2ecf20Sopenharmony_ci			    (i == BAND_AM && !tea->has_am))
3758c2ecf20Sopenharmony_ci				continue;
3768c2ecf20Sopenharmony_ci			if (bands[i].rangelow  == a->rangelow &&
3778c2ecf20Sopenharmony_ci			    bands[i].rangehigh == a->rangehigh)
3788c2ecf20Sopenharmony_ci				break;
3798c2ecf20Sopenharmony_ci		}
3808c2ecf20Sopenharmony_ci		if (i == ARRAY_SIZE(bands))
3818c2ecf20Sopenharmony_ci			return -EINVAL; /* No matching band found */
3828c2ecf20Sopenharmony_ci		if (i != tea->band) {
3838c2ecf20Sopenharmony_ci			tea->band = i;
3848c2ecf20Sopenharmony_ci			tea->freq = clamp(tea->freq, bands[i].rangelow,
3858c2ecf20Sopenharmony_ci						     bands[i].rangehigh);
3868c2ecf20Sopenharmony_ci			snd_tea575x_set_freq(tea);
3878c2ecf20Sopenharmony_ci		}
3888c2ecf20Sopenharmony_ci	}
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci	spacing = (tea->band == BAND_AM) ? 5 : 50; /* kHz */
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_ci	/* clear the frequency, HW will fill it in */
3938c2ecf20Sopenharmony_ci	tea->val &= ~TEA575X_BIT_FREQ_MASK;
3948c2ecf20Sopenharmony_ci	tea->val |= TEA575X_BIT_SEARCH;
3958c2ecf20Sopenharmony_ci	if (a->seek_upward)
3968c2ecf20Sopenharmony_ci		tea->val |= TEA575X_BIT_UPDOWN;
3978c2ecf20Sopenharmony_ci	else
3988c2ecf20Sopenharmony_ci		tea->val &= ~TEA575X_BIT_UPDOWN;
3998c2ecf20Sopenharmony_ci	snd_tea575x_write(tea, tea->val);
4008c2ecf20Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(10000);
4018c2ecf20Sopenharmony_ci	for (;;) {
4028c2ecf20Sopenharmony_ci		if (time_after(jiffies, timeout))
4038c2ecf20Sopenharmony_ci			break;
4048c2ecf20Sopenharmony_ci		if (schedule_timeout_interruptible(msecs_to_jiffies(10))) {
4058c2ecf20Sopenharmony_ci			/* some signal arrived, stop search */
4068c2ecf20Sopenharmony_ci			tea->val &= ~TEA575X_BIT_SEARCH;
4078c2ecf20Sopenharmony_ci			snd_tea575x_set_freq(tea);
4088c2ecf20Sopenharmony_ci			return -ERESTARTSYS;
4098c2ecf20Sopenharmony_ci		}
4108c2ecf20Sopenharmony_ci		if (!(snd_tea575x_read(tea) & TEA575X_BIT_SEARCH)) {
4118c2ecf20Sopenharmony_ci			u32 freq;
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci			/* Found a frequency, wait until it can be read */
4148c2ecf20Sopenharmony_ci			for (i = 0; i < 100; i++) {
4158c2ecf20Sopenharmony_ci				msleep(10);
4168c2ecf20Sopenharmony_ci				freq = snd_tea575x_get_freq(tea);
4178c2ecf20Sopenharmony_ci				if (freq) /* available */
4188c2ecf20Sopenharmony_ci					break;
4198c2ecf20Sopenharmony_ci			}
4208c2ecf20Sopenharmony_ci			if (freq == 0) /* shouldn't happen */
4218c2ecf20Sopenharmony_ci				break;
4228c2ecf20Sopenharmony_ci			/*
4238c2ecf20Sopenharmony_ci			 * if we moved by less than the spacing, or in the
4248c2ecf20Sopenharmony_ci			 * wrong direction, continue seeking
4258c2ecf20Sopenharmony_ci			 */
4268c2ecf20Sopenharmony_ci			if (abs(tea->freq - freq) < 16 * spacing ||
4278c2ecf20Sopenharmony_ci					(a->seek_upward && freq < tea->freq) ||
4288c2ecf20Sopenharmony_ci					(!a->seek_upward && freq > tea->freq)) {
4298c2ecf20Sopenharmony_ci				snd_tea575x_write(tea, tea->val);
4308c2ecf20Sopenharmony_ci				continue;
4318c2ecf20Sopenharmony_ci			}
4328c2ecf20Sopenharmony_ci			tea->freq = freq;
4338c2ecf20Sopenharmony_ci			tea->val &= ~TEA575X_BIT_SEARCH;
4348c2ecf20Sopenharmony_ci			return 0;
4358c2ecf20Sopenharmony_ci		}
4368c2ecf20Sopenharmony_ci	}
4378c2ecf20Sopenharmony_ci	tea->val &= ~TEA575X_BIT_SEARCH;
4388c2ecf20Sopenharmony_ci	snd_tea575x_set_freq(tea);
4398c2ecf20Sopenharmony_ci	return -ENODATA;
4408c2ecf20Sopenharmony_ci}
4418c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_s_hw_freq_seek);
4428c2ecf20Sopenharmony_ci
4438c2ecf20Sopenharmony_cistatic int vidioc_s_hw_freq_seek(struct file *file, void *fh,
4448c2ecf20Sopenharmony_ci					const struct v4l2_hw_freq_seek *a)
4458c2ecf20Sopenharmony_ci{
4468c2ecf20Sopenharmony_ci	struct snd_tea575x *tea = video_drvdata(file);
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci	return snd_tea575x_s_hw_freq_seek(file, tea, a);
4498c2ecf20Sopenharmony_ci}
4508c2ecf20Sopenharmony_ci
4518c2ecf20Sopenharmony_cistatic int tea575x_s_ctrl(struct v4l2_ctrl *ctrl)
4528c2ecf20Sopenharmony_ci{
4538c2ecf20Sopenharmony_ci	struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler);
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_ci	switch (ctrl->id) {
4568c2ecf20Sopenharmony_ci	case V4L2_CID_AUDIO_MUTE:
4578c2ecf20Sopenharmony_ci		tea->mute = ctrl->val;
4588c2ecf20Sopenharmony_ci		snd_tea575x_set_freq(tea);
4598c2ecf20Sopenharmony_ci		return 0;
4608c2ecf20Sopenharmony_ci	}
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ci	return -EINVAL;
4638c2ecf20Sopenharmony_ci}
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations tea575x_fops = {
4668c2ecf20Sopenharmony_ci	.unlocked_ioctl	= video_ioctl2,
4678c2ecf20Sopenharmony_ci	.open           = v4l2_fh_open,
4688c2ecf20Sopenharmony_ci	.release        = v4l2_fh_release,
4698c2ecf20Sopenharmony_ci	.poll           = v4l2_ctrl_poll,
4708c2ecf20Sopenharmony_ci};
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops tea575x_ioctl_ops = {
4738c2ecf20Sopenharmony_ci	.vidioc_querycap    = vidioc_querycap,
4748c2ecf20Sopenharmony_ci	.vidioc_g_tuner     = vidioc_g_tuner,
4758c2ecf20Sopenharmony_ci	.vidioc_s_tuner     = vidioc_s_tuner,
4768c2ecf20Sopenharmony_ci	.vidioc_g_frequency = vidioc_g_frequency,
4778c2ecf20Sopenharmony_ci	.vidioc_s_frequency = vidioc_s_frequency,
4788c2ecf20Sopenharmony_ci	.vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek,
4798c2ecf20Sopenharmony_ci	.vidioc_enum_freq_bands = vidioc_enum_freq_bands,
4808c2ecf20Sopenharmony_ci	.vidioc_log_status  = v4l2_ctrl_log_status,
4818c2ecf20Sopenharmony_ci	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
4828c2ecf20Sopenharmony_ci	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
4838c2ecf20Sopenharmony_ci};
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_cistatic const struct video_device tea575x_radio = {
4868c2ecf20Sopenharmony_ci	.ioctl_ops	= &tea575x_ioctl_ops,
4878c2ecf20Sopenharmony_ci	.release        = video_device_release_empty,
4888c2ecf20Sopenharmony_ci};
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops tea575x_ctrl_ops = {
4918c2ecf20Sopenharmony_ci	.s_ctrl = tea575x_s_ctrl,
4928c2ecf20Sopenharmony_ci};
4938c2ecf20Sopenharmony_ci
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ciint snd_tea575x_hw_init(struct snd_tea575x *tea)
4968c2ecf20Sopenharmony_ci{
4978c2ecf20Sopenharmony_ci	tea->mute = true;
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_ci	/* Not all devices can or know how to read the data back.
5008c2ecf20Sopenharmony_ci	   Such devices can set cannot_read_data to true. */
5018c2ecf20Sopenharmony_ci	if (!tea->cannot_read_data) {
5028c2ecf20Sopenharmony_ci		snd_tea575x_write(tea, 0x55AA);
5038c2ecf20Sopenharmony_ci		if (snd_tea575x_read(tea) != 0x55AA)
5048c2ecf20Sopenharmony_ci			return -ENODEV;
5058c2ecf20Sopenharmony_ci	}
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_ci	tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_5_28;
5088c2ecf20Sopenharmony_ci	tea->freq = 90500 * 16;		/* 90.5Mhz default */
5098c2ecf20Sopenharmony_ci	snd_tea575x_set_freq(tea);
5108c2ecf20Sopenharmony_ci
5118c2ecf20Sopenharmony_ci	return 0;
5128c2ecf20Sopenharmony_ci}
5138c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_hw_init);
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ciint snd_tea575x_init(struct snd_tea575x *tea, struct module *owner)
5168c2ecf20Sopenharmony_ci{
5178c2ecf20Sopenharmony_ci	int retval = snd_tea575x_hw_init(tea);
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci	if (retval)
5208c2ecf20Sopenharmony_ci		return retval;
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ci	tea->vd = tea575x_radio;
5238c2ecf20Sopenharmony_ci	video_set_drvdata(&tea->vd, tea);
5248c2ecf20Sopenharmony_ci	mutex_init(&tea->mutex);
5258c2ecf20Sopenharmony_ci	strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name));
5268c2ecf20Sopenharmony_ci	tea->vd.lock = &tea->mutex;
5278c2ecf20Sopenharmony_ci	tea->vd.v4l2_dev = tea->v4l2_dev;
5288c2ecf20Sopenharmony_ci	tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
5298c2ecf20Sopenharmony_ci	if (!tea->cannot_read_data)
5308c2ecf20Sopenharmony_ci		tea->vd.device_caps |= V4L2_CAP_HW_FREQ_SEEK;
5318c2ecf20Sopenharmony_ci	tea->fops = tea575x_fops;
5328c2ecf20Sopenharmony_ci	tea->fops.owner = owner;
5338c2ecf20Sopenharmony_ci	tea->vd.fops = &tea->fops;
5348c2ecf20Sopenharmony_ci	/* disable hw_freq_seek if we can't use it */
5358c2ecf20Sopenharmony_ci	if (tea->cannot_read_data)
5368c2ecf20Sopenharmony_ci		v4l2_disable_ioctl(&tea->vd, VIDIOC_S_HW_FREQ_SEEK);
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_ci	if (!tea->cannot_mute) {
5398c2ecf20Sopenharmony_ci		tea->vd.ctrl_handler = &tea->ctrl_handler;
5408c2ecf20Sopenharmony_ci		v4l2_ctrl_handler_init(&tea->ctrl_handler, 1);
5418c2ecf20Sopenharmony_ci		v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops,
5428c2ecf20Sopenharmony_ci				  V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
5438c2ecf20Sopenharmony_ci		retval = tea->ctrl_handler.error;
5448c2ecf20Sopenharmony_ci		if (retval) {
5458c2ecf20Sopenharmony_ci			v4l2_err(tea->v4l2_dev, "can't initialize controls\n");
5468c2ecf20Sopenharmony_ci			v4l2_ctrl_handler_free(&tea->ctrl_handler);
5478c2ecf20Sopenharmony_ci			return retval;
5488c2ecf20Sopenharmony_ci		}
5498c2ecf20Sopenharmony_ci
5508c2ecf20Sopenharmony_ci		if (tea->ext_init) {
5518c2ecf20Sopenharmony_ci			retval = tea->ext_init(tea);
5528c2ecf20Sopenharmony_ci			if (retval) {
5538c2ecf20Sopenharmony_ci				v4l2_ctrl_handler_free(&tea->ctrl_handler);
5548c2ecf20Sopenharmony_ci				return retval;
5558c2ecf20Sopenharmony_ci			}
5568c2ecf20Sopenharmony_ci		}
5578c2ecf20Sopenharmony_ci
5588c2ecf20Sopenharmony_ci		v4l2_ctrl_handler_setup(&tea->ctrl_handler);
5598c2ecf20Sopenharmony_ci	}
5608c2ecf20Sopenharmony_ci
5618c2ecf20Sopenharmony_ci	retval = video_register_device(&tea->vd, VFL_TYPE_RADIO, tea->radio_nr);
5628c2ecf20Sopenharmony_ci	if (retval) {
5638c2ecf20Sopenharmony_ci		v4l2_err(tea->v4l2_dev, "can't register video device!\n");
5648c2ecf20Sopenharmony_ci		v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
5658c2ecf20Sopenharmony_ci		return retval;
5668c2ecf20Sopenharmony_ci	}
5678c2ecf20Sopenharmony_ci
5688c2ecf20Sopenharmony_ci	return 0;
5698c2ecf20Sopenharmony_ci}
5708c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_init);
5718c2ecf20Sopenharmony_ci
5728c2ecf20Sopenharmony_civoid snd_tea575x_exit(struct snd_tea575x *tea)
5738c2ecf20Sopenharmony_ci{
5748c2ecf20Sopenharmony_ci	video_unregister_device(&tea->vd);
5758c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
5768c2ecf20Sopenharmony_ci}
5778c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_exit);
578