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