xref: /linux/drivers/media/radio/tea575x.c (revision 4b4193256c8d3bc3a5397b5cd9494c2ad386317d)
1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2338c658aSOndrej Zary /*
3338c658aSOndrej Zary  *   ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips
4338c658aSOndrej Zary  *
5338c658aSOndrej Zary  *	Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>
6338c658aSOndrej Zary  */
7338c658aSOndrej Zary 
8338c658aSOndrej Zary #include <linux/delay.h>
9338c658aSOndrej Zary #include <linux/module.h>
10338c658aSOndrej Zary #include <linux/init.h>
11338c658aSOndrej Zary #include <linux/slab.h>
12338c658aSOndrej Zary #include <linux/sched.h>
13eab924d0SMauro Carvalho Chehab #include <asm/io.h>
14338c658aSOndrej Zary #include <media/v4l2-device.h>
15338c658aSOndrej Zary #include <media/v4l2-dev.h>
16338c658aSOndrej Zary #include <media/v4l2-fh.h>
17338c658aSOndrej Zary #include <media/v4l2-ioctl.h>
18338c658aSOndrej Zary #include <media/v4l2-event.h>
19d647f0b7SMauro Carvalho Chehab #include <media/drv-intf/tea575x.h>
20338c658aSOndrej Zary 
21338c658aSOndrej Zary MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
22338c658aSOndrej Zary MODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips");
23338c658aSOndrej Zary MODULE_LICENSE("GPL");
24338c658aSOndrej Zary 
25338c658aSOndrej Zary /*
26338c658aSOndrej Zary  * definitions
27338c658aSOndrej Zary  */
28338c658aSOndrej Zary 
29338c658aSOndrej Zary #define TEA575X_BIT_SEARCH	(1<<24)		/* 1 = search action, 0 = tuned */
30338c658aSOndrej Zary #define TEA575X_BIT_UPDOWN	(1<<23)		/* 0 = search down, 1 = search up */
31338c658aSOndrej Zary #define TEA575X_BIT_MONO	(1<<22)		/* 0 = stereo, 1 = mono */
32338c658aSOndrej Zary #define TEA575X_BIT_BAND_MASK	(3<<20)
33338c658aSOndrej Zary #define TEA575X_BIT_BAND_FM	(0<<20)
34338c658aSOndrej Zary #define TEA575X_BIT_BAND_MW	(1<<20)
35338c658aSOndrej Zary #define TEA575X_BIT_BAND_LW	(2<<20)
36338c658aSOndrej Zary #define TEA575X_BIT_BAND_SW	(3<<20)
37338c658aSOndrej Zary #define TEA575X_BIT_PORT_0	(1<<19)		/* user bit */
38338c658aSOndrej Zary #define TEA575X_BIT_PORT_1	(1<<18)		/* user bit */
39338c658aSOndrej Zary #define TEA575X_BIT_SEARCH_MASK	(3<<16)		/* search level */
40338c658aSOndrej Zary #define TEA575X_BIT_SEARCH_5_28	     (0<<16)	/* FM >5uV, AM >28uV */
41338c658aSOndrej Zary #define TEA575X_BIT_SEARCH_10_40     (1<<16)	/* FM >10uV, AM > 40uV */
42338c658aSOndrej Zary #define TEA575X_BIT_SEARCH_30_63     (2<<16)	/* FM >30uV, AM > 63uV */
43338c658aSOndrej Zary #define TEA575X_BIT_SEARCH_150_1000  (3<<16)	/* FM > 150uV, AM > 1000uV */
44338c658aSOndrej Zary #define TEA575X_BIT_DUMMY	(1<<15)		/* buffer */
45338c658aSOndrej Zary #define TEA575X_BIT_FREQ_MASK	0x7fff
46338c658aSOndrej Zary 
47338c658aSOndrej Zary enum { BAND_FM, BAND_FM_JAPAN, BAND_AM };
48338c658aSOndrej Zary 
49338c658aSOndrej Zary static const struct v4l2_frequency_band bands[] = {
50338c658aSOndrej Zary 	{
51338c658aSOndrej Zary 		.type = V4L2_TUNER_RADIO,
52338c658aSOndrej Zary 		.index = 0,
53338c658aSOndrej Zary 		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
54338c658aSOndrej Zary 			      V4L2_TUNER_CAP_FREQ_BANDS,
55338c658aSOndrej Zary 		.rangelow   =  87500 * 16,
56338c658aSOndrej Zary 		.rangehigh  = 108000 * 16,
57338c658aSOndrej Zary 		.modulation = V4L2_BAND_MODULATION_FM,
58338c658aSOndrej Zary 	},
59338c658aSOndrej Zary 	{
60338c658aSOndrej Zary 		.type = V4L2_TUNER_RADIO,
61338c658aSOndrej Zary 		.index = 0,
62338c658aSOndrej Zary 		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
63338c658aSOndrej Zary 			      V4L2_TUNER_CAP_FREQ_BANDS,
64338c658aSOndrej Zary 		.rangelow   = 76000 * 16,
65338c658aSOndrej Zary 		.rangehigh  = 91000 * 16,
66338c658aSOndrej Zary 		.modulation = V4L2_BAND_MODULATION_FM,
67338c658aSOndrej Zary 	},
68338c658aSOndrej Zary 	{
69338c658aSOndrej Zary 		.type = V4L2_TUNER_RADIO,
70338c658aSOndrej Zary 		.index = 1,
71338c658aSOndrej Zary 		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
72338c658aSOndrej Zary 		.rangelow   =  530 * 16,
73338c658aSOndrej Zary 		.rangehigh  = 1710 * 16,
74338c658aSOndrej Zary 		.modulation = V4L2_BAND_MODULATION_AM,
75338c658aSOndrej Zary 	},
76338c658aSOndrej Zary };
77338c658aSOndrej Zary 
78338c658aSOndrej Zary /*
79338c658aSOndrej Zary  * lowlevel part
80338c658aSOndrej Zary  */
81338c658aSOndrej Zary 
snd_tea575x_write(struct snd_tea575x * tea,unsigned int val)82338c658aSOndrej Zary static void snd_tea575x_write(struct snd_tea575x *tea, unsigned int val)
83338c658aSOndrej Zary {
84338c658aSOndrej Zary 	u16 l;
85338c658aSOndrej Zary 	u8 data;
86338c658aSOndrej Zary 
87338c658aSOndrej Zary 	if (tea->ops->write_val)
88338c658aSOndrej Zary 		return tea->ops->write_val(tea, val);
89338c658aSOndrej Zary 
90338c658aSOndrej Zary 	tea->ops->set_direction(tea, 1);
91338c658aSOndrej Zary 	udelay(16);
92338c658aSOndrej Zary 
93338c658aSOndrej Zary 	for (l = 25; l > 0; l--) {
94338c658aSOndrej Zary 		data = (val >> 24) & TEA575X_DATA;
95338c658aSOndrej Zary 		val <<= 1;			/* shift data */
96338c658aSOndrej Zary 		tea->ops->set_pins(tea, data | TEA575X_WREN);
97338c658aSOndrej Zary 		udelay(2);
98338c658aSOndrej Zary 		tea->ops->set_pins(tea, data | TEA575X_WREN | TEA575X_CLK);
99338c658aSOndrej Zary 		udelay(2);
100338c658aSOndrej Zary 		tea->ops->set_pins(tea, data | TEA575X_WREN);
101338c658aSOndrej Zary 		udelay(2);
102338c658aSOndrej Zary 	}
103338c658aSOndrej Zary 
104338c658aSOndrej Zary 	if (!tea->mute)
105338c658aSOndrej Zary 		tea->ops->set_pins(tea, 0);
106338c658aSOndrej Zary }
107338c658aSOndrej Zary 
snd_tea575x_read(struct snd_tea575x * tea)108338c658aSOndrej Zary static u32 snd_tea575x_read(struct snd_tea575x *tea)
109338c658aSOndrej Zary {
110338c658aSOndrej Zary 	u16 l, rdata;
111338c658aSOndrej Zary 	u32 data = 0;
112338c658aSOndrej Zary 
113338c658aSOndrej Zary 	if (tea->ops->read_val)
114338c658aSOndrej Zary 		return tea->ops->read_val(tea);
115338c658aSOndrej Zary 
116338c658aSOndrej Zary 	tea->ops->set_direction(tea, 0);
117338c658aSOndrej Zary 	tea->ops->set_pins(tea, 0);
118338c658aSOndrej Zary 	udelay(16);
119338c658aSOndrej Zary 
120338c658aSOndrej Zary 	for (l = 24; l--;) {
121338c658aSOndrej Zary 		tea->ops->set_pins(tea, TEA575X_CLK);
122338c658aSOndrej Zary 		udelay(2);
123338c658aSOndrej Zary 		if (!l)
124338c658aSOndrej Zary 			tea->tuned = tea->ops->get_pins(tea) & TEA575X_MOST ? 0 : 1;
125338c658aSOndrej Zary 		tea->ops->set_pins(tea, 0);
126338c658aSOndrej Zary 		udelay(2);
127338c658aSOndrej Zary 		data <<= 1;			/* shift data */
128338c658aSOndrej Zary 		rdata = tea->ops->get_pins(tea);
129338c658aSOndrej Zary 		if (!l)
130338c658aSOndrej Zary 			tea->stereo = (rdata & TEA575X_MOST) ?  0 : 1;
131338c658aSOndrej Zary 		if (rdata & TEA575X_DATA)
132338c658aSOndrej Zary 			data++;
133338c658aSOndrej Zary 		udelay(2);
134338c658aSOndrej Zary 	}
135338c658aSOndrej Zary 
136338c658aSOndrej Zary 	if (tea->mute)
137338c658aSOndrej Zary 		tea->ops->set_pins(tea, TEA575X_WREN);
138338c658aSOndrej Zary 
139338c658aSOndrej Zary 	return data;
140338c658aSOndrej Zary }
141338c658aSOndrej Zary 
snd_tea575x_val_to_freq(struct snd_tea575x * tea,u32 val)142338c658aSOndrej Zary static u32 snd_tea575x_val_to_freq(struct snd_tea575x *tea, u32 val)
143338c658aSOndrej Zary {
144338c658aSOndrej Zary 	u32 freq = val & TEA575X_BIT_FREQ_MASK;
145338c658aSOndrej Zary 
146338c658aSOndrej Zary 	if (freq == 0)
147338c658aSOndrej Zary 		return freq;
148338c658aSOndrej Zary 
149338c658aSOndrej Zary 	switch (tea->band) {
150338c658aSOndrej Zary 	case BAND_FM:
151338c658aSOndrej Zary 		/* freq *= 12.5 */
152338c658aSOndrej Zary 		freq *= 125;
153338c658aSOndrej Zary 		freq /= 10;
154338c658aSOndrej Zary 		/* crystal fixup */
155338c658aSOndrej Zary 		freq -= TEA575X_FMIF;
156338c658aSOndrej Zary 		break;
157338c658aSOndrej Zary 	case BAND_FM_JAPAN:
158338c658aSOndrej Zary 		/* freq *= 12.5 */
159338c658aSOndrej Zary 		freq *= 125;
160338c658aSOndrej Zary 		freq /= 10;
161338c658aSOndrej Zary 		/* crystal fixup */
162338c658aSOndrej Zary 		freq += TEA575X_FMIF;
163338c658aSOndrej Zary 		break;
164338c658aSOndrej Zary 	case BAND_AM:
165338c658aSOndrej Zary 		/* crystal fixup */
166338c658aSOndrej Zary 		freq -= TEA575X_AMIF;
167338c658aSOndrej Zary 		break;
168338c658aSOndrej Zary 	}
169338c658aSOndrej Zary 
170338c658aSOndrej Zary 	return clamp(freq * 16, bands[tea->band].rangelow,
171338c658aSOndrej Zary 				bands[tea->band].rangehigh); /* from kHz */
172338c658aSOndrej Zary }
173338c658aSOndrej Zary 
snd_tea575x_get_freq(struct snd_tea575x * tea)174338c658aSOndrej Zary static u32 snd_tea575x_get_freq(struct snd_tea575x *tea)
175338c658aSOndrej Zary {
176338c658aSOndrej Zary 	return snd_tea575x_val_to_freq(tea, snd_tea575x_read(tea));
177338c658aSOndrej Zary }
178338c658aSOndrej Zary 
snd_tea575x_set_freq(struct snd_tea575x * tea)179338c658aSOndrej Zary void snd_tea575x_set_freq(struct snd_tea575x *tea)
180338c658aSOndrej Zary {
181338c658aSOndrej Zary 	u32 freq = tea->freq / 16;	/* to kHz */
182338c658aSOndrej Zary 	u32 band = 0;
183338c658aSOndrej Zary 
184338c658aSOndrej Zary 	switch (tea->band) {
185338c658aSOndrej Zary 	case BAND_FM:
186338c658aSOndrej Zary 		band = TEA575X_BIT_BAND_FM;
187338c658aSOndrej Zary 		/* crystal fixup */
188338c658aSOndrej Zary 		freq += TEA575X_FMIF;
189338c658aSOndrej Zary 		/* freq /= 12.5 */
190338c658aSOndrej Zary 		freq *= 10;
191338c658aSOndrej Zary 		freq /= 125;
192338c658aSOndrej Zary 		break;
193338c658aSOndrej Zary 	case BAND_FM_JAPAN:
194338c658aSOndrej Zary 		band = TEA575X_BIT_BAND_FM;
195338c658aSOndrej Zary 		/* crystal fixup */
196338c658aSOndrej Zary 		freq -= TEA575X_FMIF;
197338c658aSOndrej Zary 		/* freq /= 12.5 */
198338c658aSOndrej Zary 		freq *= 10;
199338c658aSOndrej Zary 		freq /= 125;
200338c658aSOndrej Zary 		break;
201338c658aSOndrej Zary 	case BAND_AM:
202338c658aSOndrej Zary 		band = TEA575X_BIT_BAND_MW;
203338c658aSOndrej Zary 		/* crystal fixup */
204338c658aSOndrej Zary 		freq += TEA575X_AMIF;
205338c658aSOndrej Zary 		break;
206338c658aSOndrej Zary 	}
207338c658aSOndrej Zary 
208338c658aSOndrej Zary 	tea->val &= ~(TEA575X_BIT_FREQ_MASK | TEA575X_BIT_BAND_MASK);
209338c658aSOndrej Zary 	tea->val |= band;
210338c658aSOndrej Zary 	tea->val |= freq & TEA575X_BIT_FREQ_MASK;
211338c658aSOndrej Zary 	snd_tea575x_write(tea, tea->val);
212338c658aSOndrej Zary 	tea->freq = snd_tea575x_val_to_freq(tea, tea->val);
213338c658aSOndrej Zary }
2140e2a706bSAndy Shevchenko EXPORT_SYMBOL(snd_tea575x_set_freq);
215338c658aSOndrej Zary 
216338c658aSOndrej Zary /*
217338c658aSOndrej Zary  * Linux Video interface
218338c658aSOndrej Zary  */
219338c658aSOndrej Zary 
vidioc_querycap(struct file * file,void * priv,struct v4l2_capability * v)220338c658aSOndrej Zary static int vidioc_querycap(struct file *file, void  *priv,
221338c658aSOndrej Zary 					struct v4l2_capability *v)
222338c658aSOndrej Zary {
223338c658aSOndrej Zary 	struct snd_tea575x *tea = video_drvdata(file);
224338c658aSOndrej Zary 
225c0decac1SMauro Carvalho Chehab 	strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver));
226c0decac1SMauro Carvalho Chehab 	strscpy(v->card, tea->card, sizeof(v->card));
227338c658aSOndrej Zary 	strlcat(v->card, tea->tea5759 ? " TEA5759" : " TEA5757", sizeof(v->card));
228c0decac1SMauro Carvalho Chehab 	strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info));
229338c658aSOndrej Zary 	return 0;
230338c658aSOndrej Zary }
231338c658aSOndrej Zary 
snd_tea575x_enum_freq_bands(struct snd_tea575x * tea,struct v4l2_frequency_band * band)2326994ca3dSOndrej Zary int snd_tea575x_enum_freq_bands(struct snd_tea575x *tea,
233338c658aSOndrej Zary 					struct v4l2_frequency_band *band)
234338c658aSOndrej Zary {
235338c658aSOndrej Zary 	int index;
236338c658aSOndrej Zary 
237338c658aSOndrej Zary 	if (band->tuner != 0)
238338c658aSOndrej Zary 		return -EINVAL;
239338c658aSOndrej Zary 
240338c658aSOndrej Zary 	switch (band->index) {
241338c658aSOndrej Zary 	case 0:
242338c658aSOndrej Zary 		if (tea->tea5759)
243338c658aSOndrej Zary 			index = BAND_FM_JAPAN;
244338c658aSOndrej Zary 		else
245338c658aSOndrej Zary 			index = BAND_FM;
246338c658aSOndrej Zary 		break;
247338c658aSOndrej Zary 	case 1:
248338c658aSOndrej Zary 		if (tea->has_am) {
249338c658aSOndrej Zary 			index = BAND_AM;
250338c658aSOndrej Zary 			break;
251338c658aSOndrej Zary 		}
252*df561f66SGustavo A. R. Silva 		fallthrough;
253338c658aSOndrej Zary 	default:
254338c658aSOndrej Zary 		return -EINVAL;
255338c658aSOndrej Zary 	}
256338c658aSOndrej Zary 
257338c658aSOndrej Zary 	*band = bands[index];
258338c658aSOndrej Zary 	if (!tea->cannot_read_data)
259338c658aSOndrej Zary 		band->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED;
260338c658aSOndrej Zary 
261338c658aSOndrej Zary 	return 0;
262338c658aSOndrej Zary }
2636994ca3dSOndrej Zary EXPORT_SYMBOL(snd_tea575x_enum_freq_bands);
264338c658aSOndrej Zary 
vidioc_enum_freq_bands(struct file * file,void * priv,struct v4l2_frequency_band * band)2656994ca3dSOndrej Zary static int vidioc_enum_freq_bands(struct file *file, void *priv,
2666994ca3dSOndrej Zary 					 struct v4l2_frequency_band *band)
267338c658aSOndrej Zary {
268338c658aSOndrej Zary 	struct snd_tea575x *tea = video_drvdata(file);
2696994ca3dSOndrej Zary 
2706994ca3dSOndrej Zary 	return snd_tea575x_enum_freq_bands(tea, band);
2716994ca3dSOndrej Zary }
2726994ca3dSOndrej Zary 
snd_tea575x_g_tuner(struct snd_tea575x * tea,struct v4l2_tuner * v)2736994ca3dSOndrej Zary int snd_tea575x_g_tuner(struct snd_tea575x *tea, struct v4l2_tuner *v)
2746994ca3dSOndrej Zary {
275338c658aSOndrej Zary 	struct v4l2_frequency_band band_fm = { 0, };
276338c658aSOndrej Zary 
277338c658aSOndrej Zary 	if (v->index > 0)
278338c658aSOndrej Zary 		return -EINVAL;
279338c658aSOndrej Zary 
280338c658aSOndrej Zary 	snd_tea575x_read(tea);
2816994ca3dSOndrej Zary 	snd_tea575x_enum_freq_bands(tea, &band_fm);
282338c658aSOndrej Zary 
283338c658aSOndrej Zary 	memset(v, 0, sizeof(*v));
284c0decac1SMauro Carvalho Chehab 	strscpy(v->name, tea->has_am ? "FM/AM" : "FM", sizeof(v->name));
285338c658aSOndrej Zary 	v->type = V4L2_TUNER_RADIO;
286338c658aSOndrej Zary 	v->capability = band_fm.capability;
287338c658aSOndrej Zary 	v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : band_fm.rangelow;
288338c658aSOndrej Zary 	v->rangehigh = band_fm.rangehigh;
289338c658aSOndrej Zary 	v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
290338c658aSOndrej Zary 	v->audmode = (tea->val & TEA575X_BIT_MONO) ?
291338c658aSOndrej Zary 		V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO;
292338c658aSOndrej Zary 	v->signal = tea->tuned ? 0xffff : 0;
293338c658aSOndrej Zary 	return 0;
294338c658aSOndrej Zary }
2956994ca3dSOndrej Zary EXPORT_SYMBOL(snd_tea575x_g_tuner);
2966994ca3dSOndrej Zary 
vidioc_g_tuner(struct file * file,void * priv,struct v4l2_tuner * v)2976994ca3dSOndrej Zary static int vidioc_g_tuner(struct file *file, void *priv,
2986994ca3dSOndrej Zary 					struct v4l2_tuner *v)
2996994ca3dSOndrej Zary {
3006994ca3dSOndrej Zary 	struct snd_tea575x *tea = video_drvdata(file);
3016994ca3dSOndrej Zary 
3026994ca3dSOndrej Zary 	return snd_tea575x_g_tuner(tea, v);
3036994ca3dSOndrej Zary }
304338c658aSOndrej Zary 
vidioc_s_tuner(struct file * file,void * priv,const struct v4l2_tuner * v)305338c658aSOndrej Zary static int vidioc_s_tuner(struct file *file, void *priv,
306338c658aSOndrej Zary 					const struct v4l2_tuner *v)
307338c658aSOndrej Zary {
308338c658aSOndrej Zary 	struct snd_tea575x *tea = video_drvdata(file);
309338c658aSOndrej Zary 	u32 orig_val = tea->val;
310338c658aSOndrej Zary 
311338c658aSOndrej Zary 	if (v->index)
312338c658aSOndrej Zary 		return -EINVAL;
313338c658aSOndrej Zary 	tea->val &= ~TEA575X_BIT_MONO;
314338c658aSOndrej Zary 	if (v->audmode == V4L2_TUNER_MODE_MONO)
315338c658aSOndrej Zary 		tea->val |= TEA575X_BIT_MONO;
316338c658aSOndrej Zary 	/* Only apply changes if currently tuning FM */
317338c658aSOndrej Zary 	if (tea->band != BAND_AM && tea->val != orig_val)
318338c658aSOndrej Zary 		snd_tea575x_set_freq(tea);
319338c658aSOndrej Zary 
320338c658aSOndrej Zary 	return 0;
321338c658aSOndrej Zary }
322338c658aSOndrej Zary 
vidioc_g_frequency(struct file * file,void * priv,struct v4l2_frequency * f)323338c658aSOndrej Zary static int vidioc_g_frequency(struct file *file, void *priv,
324338c658aSOndrej Zary 					struct v4l2_frequency *f)
325338c658aSOndrej Zary {
326338c658aSOndrej Zary 	struct snd_tea575x *tea = video_drvdata(file);
327338c658aSOndrej Zary 
328338c658aSOndrej Zary 	if (f->tuner != 0)
329338c658aSOndrej Zary 		return -EINVAL;
330338c658aSOndrej Zary 	f->type = V4L2_TUNER_RADIO;
331338c658aSOndrej Zary 	f->frequency = tea->freq;
332338c658aSOndrej Zary 	return 0;
333338c658aSOndrej Zary }
334338c658aSOndrej Zary 
vidioc_s_frequency(struct file * file,void * priv,const struct v4l2_frequency * f)335338c658aSOndrej Zary static int vidioc_s_frequency(struct file *file, void *priv,
336338c658aSOndrej Zary 					const struct v4l2_frequency *f)
337338c658aSOndrej Zary {
338338c658aSOndrej Zary 	struct snd_tea575x *tea = video_drvdata(file);
339338c658aSOndrej Zary 
340338c658aSOndrej Zary 	if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
341338c658aSOndrej Zary 		return -EINVAL;
342338c658aSOndrej Zary 
343338c658aSOndrej Zary 	if (tea->has_am && f->frequency < (20000 * 16))
344338c658aSOndrej Zary 		tea->band = BAND_AM;
345338c658aSOndrej Zary 	else if (tea->tea5759)
346338c658aSOndrej Zary 		tea->band = BAND_FM_JAPAN;
347338c658aSOndrej Zary 	else
348338c658aSOndrej Zary 		tea->band = BAND_FM;
349338c658aSOndrej Zary 
350338c658aSOndrej Zary 	tea->freq = clamp_t(u32, f->frequency, bands[tea->band].rangelow,
351338c658aSOndrej Zary 					bands[tea->band].rangehigh);
352338c658aSOndrej Zary 	snd_tea575x_set_freq(tea);
353338c658aSOndrej Zary 	return 0;
354338c658aSOndrej Zary }
355338c658aSOndrej Zary 
snd_tea575x_s_hw_freq_seek(struct file * file,struct snd_tea575x * tea,const struct v4l2_hw_freq_seek * a)3566994ca3dSOndrej Zary int snd_tea575x_s_hw_freq_seek(struct file *file, struct snd_tea575x *tea,
357338c658aSOndrej Zary 				const struct v4l2_hw_freq_seek *a)
358338c658aSOndrej Zary {
359338c658aSOndrej Zary 	unsigned long timeout;
360338c658aSOndrej Zary 	int i, spacing;
361338c658aSOndrej Zary 
362338c658aSOndrej Zary 	if (tea->cannot_read_data)
363338c658aSOndrej Zary 		return -ENOTTY;
364338c658aSOndrej Zary 	if (a->tuner || a->wrap_around)
365338c658aSOndrej Zary 		return -EINVAL;
366338c658aSOndrej Zary 
367338c658aSOndrej Zary 	if (file->f_flags & O_NONBLOCK)
368338c658aSOndrej Zary 		return -EWOULDBLOCK;
369338c658aSOndrej Zary 
370338c658aSOndrej Zary 	if (a->rangelow || a->rangehigh) {
371338c658aSOndrej Zary 		for (i = 0; i < ARRAY_SIZE(bands); i++) {
372338c658aSOndrej Zary 			if ((i == BAND_FM && tea->tea5759) ||
373338c658aSOndrej Zary 			    (i == BAND_FM_JAPAN && !tea->tea5759) ||
374338c658aSOndrej Zary 			    (i == BAND_AM && !tea->has_am))
375338c658aSOndrej Zary 				continue;
376338c658aSOndrej Zary 			if (bands[i].rangelow  == a->rangelow &&
377338c658aSOndrej Zary 			    bands[i].rangehigh == a->rangehigh)
378338c658aSOndrej Zary 				break;
379338c658aSOndrej Zary 		}
380338c658aSOndrej Zary 		if (i == ARRAY_SIZE(bands))
381338c658aSOndrej Zary 			return -EINVAL; /* No matching band found */
382338c658aSOndrej Zary 		if (i != tea->band) {
383338c658aSOndrej Zary 			tea->band = i;
384338c658aSOndrej Zary 			tea->freq = clamp(tea->freq, bands[i].rangelow,
385338c658aSOndrej Zary 						     bands[i].rangehigh);
386338c658aSOndrej Zary 			snd_tea575x_set_freq(tea);
387338c658aSOndrej Zary 		}
388338c658aSOndrej Zary 	}
389338c658aSOndrej Zary 
390338c658aSOndrej Zary 	spacing = (tea->band == BAND_AM) ? 5 : 50; /* kHz */
391338c658aSOndrej Zary 
392338c658aSOndrej Zary 	/* clear the frequency, HW will fill it in */
393338c658aSOndrej Zary 	tea->val &= ~TEA575X_BIT_FREQ_MASK;
394338c658aSOndrej Zary 	tea->val |= TEA575X_BIT_SEARCH;
395338c658aSOndrej Zary 	if (a->seek_upward)
396338c658aSOndrej Zary 		tea->val |= TEA575X_BIT_UPDOWN;
397338c658aSOndrej Zary 	else
398338c658aSOndrej Zary 		tea->val &= ~TEA575X_BIT_UPDOWN;
399338c658aSOndrej Zary 	snd_tea575x_write(tea, tea->val);
400338c658aSOndrej Zary 	timeout = jiffies + msecs_to_jiffies(10000);
401338c658aSOndrej Zary 	for (;;) {
402338c658aSOndrej Zary 		if (time_after(jiffies, timeout))
403338c658aSOndrej Zary 			break;
404338c658aSOndrej Zary 		if (schedule_timeout_interruptible(msecs_to_jiffies(10))) {
405338c658aSOndrej Zary 			/* some signal arrived, stop search */
406338c658aSOndrej Zary 			tea->val &= ~TEA575X_BIT_SEARCH;
407338c658aSOndrej Zary 			snd_tea575x_set_freq(tea);
408338c658aSOndrej Zary 			return -ERESTARTSYS;
409338c658aSOndrej Zary 		}
410338c658aSOndrej Zary 		if (!(snd_tea575x_read(tea) & TEA575X_BIT_SEARCH)) {
411338c658aSOndrej Zary 			u32 freq;
412338c658aSOndrej Zary 
413338c658aSOndrej Zary 			/* Found a frequency, wait until it can be read */
414338c658aSOndrej Zary 			for (i = 0; i < 100; i++) {
415338c658aSOndrej Zary 				msleep(10);
416338c658aSOndrej Zary 				freq = snd_tea575x_get_freq(tea);
417338c658aSOndrej Zary 				if (freq) /* available */
418338c658aSOndrej Zary 					break;
419338c658aSOndrej Zary 			}
420338c658aSOndrej Zary 			if (freq == 0) /* shouldn't happen */
421338c658aSOndrej Zary 				break;
422338c658aSOndrej Zary 			/*
423338c658aSOndrej Zary 			 * if we moved by less than the spacing, or in the
424338c658aSOndrej Zary 			 * wrong direction, continue seeking
425338c658aSOndrej Zary 			 */
426338c658aSOndrej Zary 			if (abs(tea->freq - freq) < 16 * spacing ||
427338c658aSOndrej Zary 					(a->seek_upward && freq < tea->freq) ||
428338c658aSOndrej Zary 					(!a->seek_upward && freq > tea->freq)) {
429338c658aSOndrej Zary 				snd_tea575x_write(tea, tea->val);
430338c658aSOndrej Zary 				continue;
431338c658aSOndrej Zary 			}
432338c658aSOndrej Zary 			tea->freq = freq;
433338c658aSOndrej Zary 			tea->val &= ~TEA575X_BIT_SEARCH;
434338c658aSOndrej Zary 			return 0;
435338c658aSOndrej Zary 		}
436338c658aSOndrej Zary 	}
437338c658aSOndrej Zary 	tea->val &= ~TEA575X_BIT_SEARCH;
438338c658aSOndrej Zary 	snd_tea575x_set_freq(tea);
439338c658aSOndrej Zary 	return -ENODATA;
440338c658aSOndrej Zary }
4416994ca3dSOndrej Zary EXPORT_SYMBOL(snd_tea575x_s_hw_freq_seek);
4426994ca3dSOndrej Zary 
vidioc_s_hw_freq_seek(struct file * file,void * fh,const struct v4l2_hw_freq_seek * a)4436994ca3dSOndrej Zary static int vidioc_s_hw_freq_seek(struct file *file, void *fh,
4446994ca3dSOndrej Zary 					const struct v4l2_hw_freq_seek *a)
4456994ca3dSOndrej Zary {
4466994ca3dSOndrej Zary 	struct snd_tea575x *tea = video_drvdata(file);
4476994ca3dSOndrej Zary 
4486994ca3dSOndrej Zary 	return snd_tea575x_s_hw_freq_seek(file, tea, a);
4496994ca3dSOndrej Zary }
450338c658aSOndrej Zary 
tea575x_s_ctrl(struct v4l2_ctrl * ctrl)451338c658aSOndrej Zary static int tea575x_s_ctrl(struct v4l2_ctrl *ctrl)
452338c658aSOndrej Zary {
453338c658aSOndrej Zary 	struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler);
454338c658aSOndrej Zary 
455338c658aSOndrej Zary 	switch (ctrl->id) {
456338c658aSOndrej Zary 	case V4L2_CID_AUDIO_MUTE:
457338c658aSOndrej Zary 		tea->mute = ctrl->val;
458338c658aSOndrej Zary 		snd_tea575x_set_freq(tea);
459338c658aSOndrej Zary 		return 0;
460338c658aSOndrej Zary 	}
461338c658aSOndrej Zary 
462338c658aSOndrej Zary 	return -EINVAL;
463338c658aSOndrej Zary }
464338c658aSOndrej Zary 
465338c658aSOndrej Zary static const struct v4l2_file_operations tea575x_fops = {
466338c658aSOndrej Zary 	.unlocked_ioctl	= video_ioctl2,
467338c658aSOndrej Zary 	.open           = v4l2_fh_open,
468338c658aSOndrej Zary 	.release        = v4l2_fh_release,
469338c658aSOndrej Zary 	.poll           = v4l2_ctrl_poll,
470338c658aSOndrej Zary };
471338c658aSOndrej Zary 
472338c658aSOndrej Zary static const struct v4l2_ioctl_ops tea575x_ioctl_ops = {
473338c658aSOndrej Zary 	.vidioc_querycap    = vidioc_querycap,
474338c658aSOndrej Zary 	.vidioc_g_tuner     = vidioc_g_tuner,
475338c658aSOndrej Zary 	.vidioc_s_tuner     = vidioc_s_tuner,
476338c658aSOndrej Zary 	.vidioc_g_frequency = vidioc_g_frequency,
477338c658aSOndrej Zary 	.vidioc_s_frequency = vidioc_s_frequency,
478338c658aSOndrej Zary 	.vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek,
479338c658aSOndrej Zary 	.vidioc_enum_freq_bands = vidioc_enum_freq_bands,
480338c658aSOndrej Zary 	.vidioc_log_status  = v4l2_ctrl_log_status,
481338c658aSOndrej Zary 	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
482338c658aSOndrej Zary 	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
483338c658aSOndrej Zary };
484338c658aSOndrej Zary 
485338c658aSOndrej Zary static const struct video_device tea575x_radio = {
486338c658aSOndrej Zary 	.ioctl_ops	= &tea575x_ioctl_ops,
487338c658aSOndrej Zary 	.release        = video_device_release_empty,
488338c658aSOndrej Zary };
489338c658aSOndrej Zary 
490338c658aSOndrej Zary static const struct v4l2_ctrl_ops tea575x_ctrl_ops = {
491338c658aSOndrej Zary 	.s_ctrl = tea575x_s_ctrl,
492338c658aSOndrej Zary };
493338c658aSOndrej Zary 
494338c658aSOndrej Zary 
snd_tea575x_hw_init(struct snd_tea575x * tea)495338c658aSOndrej Zary int snd_tea575x_hw_init(struct snd_tea575x *tea)
496338c658aSOndrej Zary {
497338c658aSOndrej Zary 	tea->mute = true;
498338c658aSOndrej Zary 
499338c658aSOndrej Zary 	/* Not all devices can or know how to read the data back.
500338c658aSOndrej Zary 	   Such devices can set cannot_read_data to true. */
501338c658aSOndrej Zary 	if (!tea->cannot_read_data) {
502338c658aSOndrej Zary 		snd_tea575x_write(tea, 0x55AA);
503338c658aSOndrej Zary 		if (snd_tea575x_read(tea) != 0x55AA)
504338c658aSOndrej Zary 			return -ENODEV;
505338c658aSOndrej Zary 	}
506338c658aSOndrej Zary 
507338c658aSOndrej Zary 	tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_5_28;
508338c658aSOndrej Zary 	tea->freq = 90500 * 16;		/* 90.5Mhz default */
509338c658aSOndrej Zary 	snd_tea575x_set_freq(tea);
510338c658aSOndrej Zary 
511338c658aSOndrej Zary 	return 0;
512338c658aSOndrej Zary }
513338c658aSOndrej Zary EXPORT_SYMBOL(snd_tea575x_hw_init);
514338c658aSOndrej Zary 
snd_tea575x_init(struct snd_tea575x * tea,struct module * owner)515338c658aSOndrej Zary int snd_tea575x_init(struct snd_tea575x *tea, struct module *owner)
516338c658aSOndrej Zary {
517338c658aSOndrej Zary 	int retval = snd_tea575x_hw_init(tea);
518338c658aSOndrej Zary 
519338c658aSOndrej Zary 	if (retval)
520338c658aSOndrej Zary 		return retval;
521338c658aSOndrej Zary 
522338c658aSOndrej Zary 	tea->vd = tea575x_radio;
523338c658aSOndrej Zary 	video_set_drvdata(&tea->vd, tea);
524338c658aSOndrej Zary 	mutex_init(&tea->mutex);
525c0decac1SMauro Carvalho Chehab 	strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name));
526338c658aSOndrej Zary 	tea->vd.lock = &tea->mutex;
527338c658aSOndrej Zary 	tea->vd.v4l2_dev = tea->v4l2_dev;
528e83ce300SHans Verkuil 	tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
529e83ce300SHans Verkuil 	if (!tea->cannot_read_data)
530e83ce300SHans Verkuil 		tea->vd.device_caps |= V4L2_CAP_HW_FREQ_SEEK;
531338c658aSOndrej Zary 	tea->fops = tea575x_fops;
532338c658aSOndrej Zary 	tea->fops.owner = owner;
533338c658aSOndrej Zary 	tea->vd.fops = &tea->fops;
534338c658aSOndrej Zary 	/* disable hw_freq_seek if we can't use it */
535338c658aSOndrej Zary 	if (tea->cannot_read_data)
536338c658aSOndrej Zary 		v4l2_disable_ioctl(&tea->vd, VIDIOC_S_HW_FREQ_SEEK);
537338c658aSOndrej Zary 
538338c658aSOndrej Zary 	if (!tea->cannot_mute) {
539338c658aSOndrej Zary 		tea->vd.ctrl_handler = &tea->ctrl_handler;
540338c658aSOndrej Zary 		v4l2_ctrl_handler_init(&tea->ctrl_handler, 1);
541338c658aSOndrej Zary 		v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops,
542338c658aSOndrej Zary 				  V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
543338c658aSOndrej Zary 		retval = tea->ctrl_handler.error;
544338c658aSOndrej Zary 		if (retval) {
545338c658aSOndrej Zary 			v4l2_err(tea->v4l2_dev, "can't initialize controls\n");
546338c658aSOndrej Zary 			v4l2_ctrl_handler_free(&tea->ctrl_handler);
547338c658aSOndrej Zary 			return retval;
548338c658aSOndrej Zary 		}
549338c658aSOndrej Zary 
550338c658aSOndrej Zary 		if (tea->ext_init) {
551338c658aSOndrej Zary 			retval = tea->ext_init(tea);
552338c658aSOndrej Zary 			if (retval) {
553338c658aSOndrej Zary 				v4l2_ctrl_handler_free(&tea->ctrl_handler);
554338c658aSOndrej Zary 				return retval;
555338c658aSOndrej Zary 			}
556338c658aSOndrej Zary 		}
557338c658aSOndrej Zary 
558338c658aSOndrej Zary 		v4l2_ctrl_handler_setup(&tea->ctrl_handler);
559338c658aSOndrej Zary 	}
560338c658aSOndrej Zary 
561338c658aSOndrej Zary 	retval = video_register_device(&tea->vd, VFL_TYPE_RADIO, tea->radio_nr);
562338c658aSOndrej Zary 	if (retval) {
563338c658aSOndrej Zary 		v4l2_err(tea->v4l2_dev, "can't register video device!\n");
564338c658aSOndrej Zary 		v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
565338c658aSOndrej Zary 		return retval;
566338c658aSOndrej Zary 	}
567338c658aSOndrej Zary 
568338c658aSOndrej Zary 	return 0;
569338c658aSOndrej Zary }
5700e2a706bSAndy Shevchenko EXPORT_SYMBOL(snd_tea575x_init);
571338c658aSOndrej Zary 
snd_tea575x_exit(struct snd_tea575x * tea)572338c658aSOndrej Zary void snd_tea575x_exit(struct snd_tea575x *tea)
573338c658aSOndrej Zary {
574338c658aSOndrej Zary 	video_unregister_device(&tea->vd);
575338c658aSOndrej Zary 	v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
576338c658aSOndrej Zary }
577338c658aSOndrej Zary EXPORT_SYMBOL(snd_tea575x_exit);
578