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