1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds * ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips
41da177e4SLinus Torvalds *
5c1017a4cSJaroslav Kysela * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>
61da177e4SLinus Torvalds */
71da177e4SLinus Torvalds
81da177e4SLinus Torvalds #include <linux/delay.h>
9da155d5bSPaul Gortmaker #include <linux/module.h>
101da177e4SLinus Torvalds #include <linux/init.h>
115a0e3ad6STejun Heo #include <linux/slab.h>
12d4ecc83bSHans Verkuil #include <linux/sched.h>
13eab924d0SMauro Carvalho Chehab #include <asm/io.h>
14d4ecc83bSHans Verkuil #include <media/v4l2-device.h>
154522e825SOndrej Zary #include <media/v4l2-dev.h>
16d4ecc83bSHans Verkuil #include <media/v4l2-fh.h>
174522e825SOndrej Zary #include <media/v4l2-ioctl.h>
18d4ecc83bSHans Verkuil #include <media/v4l2-event.h>
19d647f0b7SMauro Carvalho Chehab #include <media/drv-intf/tea575x.h>
201da177e4SLinus Torvalds
21c1017a4cSJaroslav Kysela MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
221da177e4SLinus Torvalds MODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips");
231da177e4SLinus Torvalds MODULE_LICENSE("GPL");
241da177e4SLinus Torvalds
251da177e4SLinus Torvalds /*
261da177e4SLinus Torvalds * definitions
271da177e4SLinus Torvalds */
281da177e4SLinus Torvalds
291da177e4SLinus Torvalds #define TEA575X_BIT_SEARCH (1<<24) /* 1 = search action, 0 = tuned */
301da177e4SLinus Torvalds #define TEA575X_BIT_UPDOWN (1<<23) /* 0 = search down, 1 = search up */
311da177e4SLinus Torvalds #define TEA575X_BIT_MONO (1<<22) /* 0 = stereo, 1 = mono */
321da177e4SLinus Torvalds #define TEA575X_BIT_BAND_MASK (3<<20)
331da177e4SLinus Torvalds #define TEA575X_BIT_BAND_FM (0<<20)
341da177e4SLinus Torvalds #define TEA575X_BIT_BAND_MW (1<<20)
35fc488517SHans de Goede #define TEA575X_BIT_BAND_LW (2<<20)
36fc488517SHans de Goede #define TEA575X_BIT_BAND_SW (3<<20)
371da177e4SLinus Torvalds #define TEA575X_BIT_PORT_0 (1<<19) /* user bit */
381da177e4SLinus Torvalds #define TEA575X_BIT_PORT_1 (1<<18) /* user bit */
391da177e4SLinus Torvalds #define TEA575X_BIT_SEARCH_MASK (3<<16) /* search level */
401da177e4SLinus Torvalds #define TEA575X_BIT_SEARCH_5_28 (0<<16) /* FM >5uV, AM >28uV */
411da177e4SLinus Torvalds #define TEA575X_BIT_SEARCH_10_40 (1<<16) /* FM >10uV, AM > 40uV */
421da177e4SLinus Torvalds #define TEA575X_BIT_SEARCH_30_63 (2<<16) /* FM >30uV, AM > 63uV */
431da177e4SLinus Torvalds #define TEA575X_BIT_SEARCH_150_1000 (3<<16) /* FM > 150uV, AM > 1000uV */
441da177e4SLinus Torvalds #define TEA575X_BIT_DUMMY (1<<15) /* buffer */
451da177e4SLinus Torvalds #define TEA575X_BIT_FREQ_MASK 0x7fff
461da177e4SLinus Torvalds
47fc488517SHans de Goede enum { BAND_FM, BAND_FM_JAPAN, BAND_AM };
48fc488517SHans de Goede
49fc488517SHans de Goede static const struct v4l2_frequency_band bands[] = {
50fc488517SHans de Goede {
51fc488517SHans de Goede .type = V4L2_TUNER_RADIO,
52fc488517SHans de Goede .index = 0,
53fc488517SHans de Goede .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
54fc488517SHans de Goede V4L2_TUNER_CAP_FREQ_BANDS,
55fc488517SHans de Goede .rangelow = 87500 * 16,
56fc488517SHans de Goede .rangehigh = 108000 * 16,
57fc488517SHans de Goede .modulation = V4L2_BAND_MODULATION_FM,
58fc488517SHans de Goede },
59fc488517SHans de Goede {
60fc488517SHans de Goede .type = V4L2_TUNER_RADIO,
61fc488517SHans de Goede .index = 0,
62fc488517SHans de Goede .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
63fc488517SHans de Goede V4L2_TUNER_CAP_FREQ_BANDS,
64fc488517SHans de Goede .rangelow = 76000 * 16,
65fc488517SHans de Goede .rangehigh = 91000 * 16,
66fc488517SHans de Goede .modulation = V4L2_BAND_MODULATION_FM,
67fc488517SHans de Goede },
68fc488517SHans de Goede {
69fc488517SHans de Goede .type = V4L2_TUNER_RADIO,
70fc488517SHans de Goede .index = 1,
71fc488517SHans de Goede .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
72fc488517SHans de Goede .rangelow = 530 * 16,
73fc488517SHans de Goede .rangehigh = 1710 * 16,
74fc488517SHans de Goede .modulation = V4L2_BAND_MODULATION_AM,
75fc488517SHans de Goede },
76fc488517SHans de Goede };
77fc488517SHans de Goede
781da177e4SLinus Torvalds /*
791da177e4SLinus Torvalds * lowlevel part
801da177e4SLinus Torvalds */
811da177e4SLinus Torvalds
snd_tea575x_write(struct snd_tea575x * tea,unsigned int val)8214219d06SOndrej Zary static void snd_tea575x_write(struct snd_tea575x *tea, unsigned int val)
8314219d06SOndrej Zary {
8414219d06SOndrej Zary u16 l;
8514219d06SOndrej Zary u8 data;
8614219d06SOndrej Zary
8731a62d41SHans de Goede if (tea->ops->write_val)
8831a62d41SHans de Goede return tea->ops->write_val(tea, val);
8931a62d41SHans de Goede
9014219d06SOndrej Zary tea->ops->set_direction(tea, 1);
9114219d06SOndrej Zary udelay(16);
9214219d06SOndrej Zary
9314219d06SOndrej Zary for (l = 25; l > 0; l--) {
9414219d06SOndrej Zary data = (val >> 24) & TEA575X_DATA;
9514219d06SOndrej Zary val <<= 1; /* shift data */
9614219d06SOndrej Zary tea->ops->set_pins(tea, data | TEA575X_WREN);
9714219d06SOndrej Zary udelay(2);
9814219d06SOndrej Zary tea->ops->set_pins(tea, data | TEA575X_WREN | TEA575X_CLK);
9914219d06SOndrej Zary udelay(2);
10014219d06SOndrej Zary tea->ops->set_pins(tea, data | TEA575X_WREN);
10114219d06SOndrej Zary udelay(2);
10214219d06SOndrej Zary }
10314219d06SOndrej Zary
10414219d06SOndrej Zary if (!tea->mute)
10514219d06SOndrej Zary tea->ops->set_pins(tea, 0);
10614219d06SOndrej Zary }
10714219d06SOndrej Zary
snd_tea575x_read(struct snd_tea575x * tea)108bc2b395cSHans Verkuil static u32 snd_tea575x_read(struct snd_tea575x *tea)
10914219d06SOndrej Zary {
11014219d06SOndrej Zary u16 l, rdata;
11114219d06SOndrej Zary u32 data = 0;
11214219d06SOndrej Zary
11331a62d41SHans de Goede if (tea->ops->read_val)
11431a62d41SHans de Goede return tea->ops->read_val(tea);
11531a62d41SHans de Goede
11614219d06SOndrej Zary tea->ops->set_direction(tea, 0);
11714219d06SOndrej Zary tea->ops->set_pins(tea, 0);
11814219d06SOndrej Zary udelay(16);
11914219d06SOndrej Zary
12014219d06SOndrej Zary for (l = 24; l--;) {
12114219d06SOndrej Zary tea->ops->set_pins(tea, TEA575X_CLK);
12214219d06SOndrej Zary udelay(2);
12314219d06SOndrej Zary if (!l)
12414219d06SOndrej Zary tea->tuned = tea->ops->get_pins(tea) & TEA575X_MOST ? 0 : 1;
12514219d06SOndrej Zary tea->ops->set_pins(tea, 0);
12614219d06SOndrej Zary udelay(2);
12714219d06SOndrej Zary data <<= 1; /* shift data */
12814219d06SOndrej Zary rdata = tea->ops->get_pins(tea);
12914219d06SOndrej Zary if (!l)
13014219d06SOndrej Zary tea->stereo = (rdata & TEA575X_MOST) ? 0 : 1;
13114219d06SOndrej Zary if (rdata & TEA575X_DATA)
13214219d06SOndrej Zary data++;
13314219d06SOndrej Zary udelay(2);
13414219d06SOndrej Zary }
13514219d06SOndrej Zary
13614219d06SOndrej Zary if (tea->mute)
13714219d06SOndrej Zary tea->ops->set_pins(tea, TEA575X_WREN);
13814219d06SOndrej Zary
13914219d06SOndrej Zary return data;
14014219d06SOndrej Zary }
14114219d06SOndrej Zary
snd_tea575x_val_to_freq(struct snd_tea575x * tea,u32 val)14240e006aeSHans de Goede static u32 snd_tea575x_val_to_freq(struct snd_tea575x *tea, u32 val)
143bc2b395cSHans Verkuil {
14440e006aeSHans de Goede u32 freq = val & TEA575X_BIT_FREQ_MASK;
145bc2b395cSHans Verkuil
146bc2b395cSHans Verkuil if (freq == 0)
147bc2b395cSHans Verkuil return freq;
148bc2b395cSHans Verkuil
149fc488517SHans de Goede switch (tea->band) {
150fc488517SHans de Goede case BAND_FM:
151bc2b395cSHans Verkuil /* freq *= 12.5 */
152bc2b395cSHans Verkuil freq *= 125;
153bc2b395cSHans Verkuil freq /= 10;
154bc2b395cSHans Verkuil /* crystal fixup */
155bc2b395cSHans Verkuil freq -= TEA575X_FMIF;
156fc488517SHans de Goede break;
157fc488517SHans de Goede case BAND_FM_JAPAN:
158fc488517SHans de Goede /* freq *= 12.5 */
159fc488517SHans de Goede freq *= 125;
160fc488517SHans de Goede freq /= 10;
161fc488517SHans de Goede /* crystal fixup */
162fc488517SHans de Goede freq += TEA575X_FMIF;
163fc488517SHans de Goede break;
164fc488517SHans de Goede case BAND_AM:
165fc488517SHans de Goede /* crystal fixup */
166fc488517SHans de Goede freq -= TEA575X_AMIF;
167fc488517SHans de Goede break;
168fc488517SHans de Goede }
169bc2b395cSHans Verkuil
170fc488517SHans de Goede return clamp(freq * 16, bands[tea->band].rangelow,
171fc488517SHans de Goede bands[tea->band].rangehigh); /* from kHz */
172bc2b395cSHans Verkuil }
173bc2b395cSHans Verkuil
snd_tea575x_get_freq(struct snd_tea575x * tea)17440e006aeSHans de Goede static u32 snd_tea575x_get_freq(struct snd_tea575x *tea)
17540e006aeSHans de Goede {
17640e006aeSHans de Goede return snd_tea575x_val_to_freq(tea, snd_tea575x_read(tea));
17740e006aeSHans de Goede }
17840e006aeSHans de Goede
snd_tea575x_set_freq(struct snd_tea575x * tea)179559c2009SHans de Goede void snd_tea575x_set_freq(struct snd_tea575x *tea)
1801da177e4SLinus Torvalds {
181fc488517SHans de Goede u32 freq = tea->freq / 16; /* to kHz */
182fc488517SHans de Goede u32 band = 0;
1831da177e4SLinus Torvalds
184fc488517SHans de Goede switch (tea->band) {
185fc488517SHans de Goede case BAND_FM:
186fc488517SHans de Goede band = TEA575X_BIT_BAND_FM;
1871da177e4SLinus Torvalds /* crystal fixup */
188ea27316eSOndrej Zary freq += TEA575X_FMIF;
1891da177e4SLinus Torvalds /* freq /= 12.5 */
1901da177e4SLinus Torvalds freq *= 10;
1911da177e4SLinus Torvalds freq /= 125;
192fc488517SHans de Goede break;
193fc488517SHans de Goede case BAND_FM_JAPAN:
194fc488517SHans de Goede band = TEA575X_BIT_BAND_FM;
195fc488517SHans de Goede /* crystal fixup */
196fc488517SHans de Goede freq -= TEA575X_FMIF;
197fc488517SHans de Goede /* freq /= 12.5 */
198fc488517SHans de Goede freq *= 10;
199fc488517SHans de Goede freq /= 125;
200fc488517SHans de Goede break;
201fc488517SHans de Goede case BAND_AM:
202fc488517SHans de Goede band = TEA575X_BIT_BAND_MW;
203fc488517SHans de Goede /* crystal fixup */
204fc488517SHans de Goede freq += TEA575X_AMIF;
205fc488517SHans de Goede break;
206fc488517SHans de Goede }
2071da177e4SLinus Torvalds
208fc488517SHans de Goede tea->val &= ~(TEA575X_BIT_FREQ_MASK | TEA575X_BIT_BAND_MASK);
209fc488517SHans de Goede tea->val |= band;
2101da177e4SLinus Torvalds tea->val |= freq & TEA575X_BIT_FREQ_MASK;
21114219d06SOndrej Zary snd_tea575x_write(tea, tea->val);
21240e006aeSHans de Goede tea->freq = snd_tea575x_val_to_freq(tea, tea->val);
2131da177e4SLinus Torvalds }
2140e2a706bSAndy Shevchenko EXPORT_SYMBOL(snd_tea575x_set_freq);
2151da177e4SLinus Torvalds
2161da177e4SLinus Torvalds /*
2171da177e4SLinus Torvalds * Linux Video interface
2181da177e4SLinus Torvalds */
2191da177e4SLinus Torvalds
vidioc_querycap(struct file * file,void * priv,struct v4l2_capability * v)2209b76ede4SMauro Carvalho Chehab static int vidioc_querycap(struct file *file, void *priv,
2219b76ede4SMauro Carvalho Chehab struct v4l2_capability *v)
2221da177e4SLinus Torvalds {
223c170ecf4SHans Verkuil struct snd_tea575x *tea = video_drvdata(file);
2241da177e4SLinus Torvalds
225c0decac1SMauro Carvalho Chehab strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver));
226c0decac1SMauro Carvalho Chehab strscpy(v->card, tea->card, sizeof(v->card));
22710ca7201SOndrej 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));
2291da177e4SLinus Torvalds return 0;
2301da177e4SLinus Torvalds }
2319b76ede4SMauro Carvalho Chehab
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,
233fc488517SHans de Goede struct v4l2_frequency_band *band)
234fc488517SHans de Goede {
235fc488517SHans de Goede int index;
236fc488517SHans de Goede
237fc488517SHans de Goede if (band->tuner != 0)
238fc488517SHans de Goede return -EINVAL;
239fc488517SHans de Goede
240fc488517SHans de Goede switch (band->index) {
241fc488517SHans de Goede case 0:
242fc488517SHans de Goede if (tea->tea5759)
243fc488517SHans de Goede index = BAND_FM_JAPAN;
244fc488517SHans de Goede else
245fc488517SHans de Goede index = BAND_FM;
246fc488517SHans de Goede break;
247fc488517SHans de Goede case 1:
248fc488517SHans de Goede if (tea->has_am) {
249fc488517SHans de Goede index = BAND_AM;
250fc488517SHans de Goede break;
251fc488517SHans de Goede }
252*df561f66SGustavo A. R. Silva fallthrough;
253fc488517SHans de Goede default:
254fc488517SHans de Goede return -EINVAL;
255fc488517SHans de Goede }
256fc488517SHans de Goede
257fc488517SHans de Goede *band = bands[index];
258fc488517SHans de Goede if (!tea->cannot_read_data)
259fc488517SHans de Goede band->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED;
260fc488517SHans de Goede
261fc488517SHans de Goede return 0;
262fc488517SHans de Goede }
2636994ca3dSOndrej Zary EXPORT_SYMBOL(snd_tea575x_enum_freq_bands);
264fc488517SHans de Goede
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)
2671da177e4SLinus Torvalds {
268375d1358SOndrej 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 {
275fc488517SHans de Goede struct v4l2_frequency_band band_fm = { 0, };
276375d1358SOndrej Zary
2779b76ede4SMauro Carvalho Chehab if (v->index > 0)
2781da177e4SLinus Torvalds return -EINVAL;
2799b76ede4SMauro Carvalho Chehab
28014219d06SOndrej Zary snd_tea575x_read(tea);
2816994ca3dSOndrej Zary snd_tea575x_enum_freq_bands(tea, &band_fm);
282375d1358SOndrej Zary
283fc488517SHans de Goede memset(v, 0, sizeof(*v));
284c0decac1SMauro Carvalho Chehab strscpy(v->name, tea->has_am ? "FM/AM" : "FM", sizeof(v->name));
2859b76ede4SMauro Carvalho Chehab v->type = V4L2_TUNER_RADIO;
286fc488517SHans de Goede v->capability = band_fm.capability;
287fc488517SHans de Goede v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : band_fm.rangelow;
288fc488517SHans de Goede v->rangehigh = band_fm.rangehigh;
289d4ecc83bSHans Verkuil v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
290d4ecc83bSHans Verkuil v->audmode = (tea->val & TEA575X_BIT_MONO) ?
291d4ecc83bSHans Verkuil V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO;
292375d1358SOndrej Zary v->signal = tea->tuned ? 0xffff : 0;
2931da177e4SLinus Torvalds return 0;
2941da177e4SLinus Torvalds }
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 }
3049b76ede4SMauro Carvalho Chehab
vidioc_s_tuner(struct file * file,void * priv,const struct v4l2_tuner * v)3059b76ede4SMauro Carvalho Chehab static int vidioc_s_tuner(struct file *file, void *priv,
3062f73c7c5SHans Verkuil const struct v4l2_tuner *v)
3071da177e4SLinus Torvalds {
308d4ecc83bSHans Verkuil struct snd_tea575x *tea = video_drvdata(file);
309fc488517SHans de Goede u32 orig_val = tea->val;
310d4ecc83bSHans Verkuil
311d4ecc83bSHans Verkuil if (v->index)
3121da177e4SLinus Torvalds return -EINVAL;
313d4ecc83bSHans Verkuil tea->val &= ~TEA575X_BIT_MONO;
314d4ecc83bSHans Verkuil if (v->audmode == V4L2_TUNER_MODE_MONO)
315d4ecc83bSHans Verkuil tea->val |= TEA575X_BIT_MONO;
316fc488517SHans de Goede /* Only apply changes if currently tuning FM */
317fc488517SHans de Goede if (tea->band != BAND_AM && tea->val != orig_val)
318fc488517SHans de Goede snd_tea575x_set_freq(tea);
319fc488517SHans de Goede
3201da177e4SLinus Torvalds return 0;
3211da177e4SLinus Torvalds }
3229b76ede4SMauro Carvalho Chehab
vidioc_g_frequency(struct file * file,void * priv,struct v4l2_frequency * f)3239b76ede4SMauro Carvalho Chehab static int vidioc_g_frequency(struct file *file, void *priv,
3249b76ede4SMauro Carvalho Chehab struct v4l2_frequency *f)
3259b76ede4SMauro Carvalho Chehab {
3269b76ede4SMauro Carvalho Chehab struct snd_tea575x *tea = video_drvdata(file);
3279b76ede4SMauro Carvalho Chehab
328375d1358SOndrej Zary if (f->tuner != 0)
329375d1358SOndrej Zary return -EINVAL;
3309b76ede4SMauro Carvalho Chehab f->type = V4L2_TUNER_RADIO;
3319b76ede4SMauro Carvalho Chehab f->frequency = tea->freq;
3321da177e4SLinus Torvalds return 0;
3339b76ede4SMauro Carvalho Chehab }
3349b76ede4SMauro Carvalho Chehab
vidioc_s_frequency(struct file * file,void * priv,const struct v4l2_frequency * f)3359b76ede4SMauro Carvalho Chehab static int vidioc_s_frequency(struct file *file, void *priv,
336b530a447SHans Verkuil const struct v4l2_frequency *f)
3379b76ede4SMauro Carvalho Chehab {
3389b76ede4SMauro Carvalho Chehab struct snd_tea575x *tea = video_drvdata(file);
3399b76ede4SMauro Carvalho Chehab
340375d1358SOndrej Zary if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
341375d1358SOndrej Zary return -EINVAL;
342375d1358SOndrej Zary
343fc488517SHans de Goede if (tea->has_am && f->frequency < (20000 * 16))
344fc488517SHans de Goede tea->band = BAND_AM;
345fc488517SHans de Goede else if (tea->tea5759)
346fc488517SHans de Goede tea->band = BAND_FM_JAPAN;
347fc488517SHans de Goede else
348fc488517SHans de Goede tea->band = BAND_FM;
349fc488517SHans de Goede
350b530a447SHans Verkuil tea->freq = clamp_t(u32, f->frequency, bands[tea->band].rangelow,
351fc488517SHans de Goede bands[tea->band].rangehigh);
3521da177e4SLinus Torvalds snd_tea575x_set_freq(tea);
3531da177e4SLinus Torvalds return 0;
3541da177e4SLinus Torvalds }
3559b76ede4SMauro Carvalho Chehab
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,
357ec6f4328SHans Verkuil const struct v4l2_hw_freq_seek *a)
3581da177e4SLinus Torvalds {
359bc2b395cSHans Verkuil unsigned long timeout;
360fc488517SHans de Goede int i, spacing;
3619b76ede4SMauro Carvalho Chehab
362d4ecc83bSHans Verkuil if (tea->cannot_read_data)
363d4ecc83bSHans Verkuil return -ENOTTY;
364d4ecc83bSHans Verkuil if (a->tuner || a->wrap_around)
365d4ecc83bSHans Verkuil return -EINVAL;
366bc2b395cSHans Verkuil
367617ade61SHans Verkuil if (file->f_flags & O_NONBLOCK)
368617ade61SHans Verkuil return -EWOULDBLOCK;
369617ade61SHans Verkuil
370fc488517SHans de Goede if (a->rangelow || a->rangehigh) {
371fc488517SHans de Goede for (i = 0; i < ARRAY_SIZE(bands); i++) {
372fc488517SHans de Goede if ((i == BAND_FM && tea->tea5759) ||
373fc488517SHans de Goede (i == BAND_FM_JAPAN && !tea->tea5759) ||
374fc488517SHans de Goede (i == BAND_AM && !tea->has_am))
375fc488517SHans de Goede continue;
376fc488517SHans de Goede if (bands[i].rangelow == a->rangelow &&
377fc488517SHans de Goede bands[i].rangehigh == a->rangehigh)
378fc488517SHans de Goede break;
379fc488517SHans de Goede }
380fc488517SHans de Goede if (i == ARRAY_SIZE(bands))
381fc488517SHans de Goede return -EINVAL; /* No matching band found */
382fc488517SHans de Goede if (i != tea->band) {
383fc488517SHans de Goede tea->band = i;
384fc488517SHans de Goede tea->freq = clamp(tea->freq, bands[i].rangelow,
385fc488517SHans de Goede bands[i].rangehigh);
386fc488517SHans de Goede snd_tea575x_set_freq(tea);
387fc488517SHans de Goede }
388fc488517SHans de Goede }
389fc488517SHans de Goede
390fc488517SHans de Goede spacing = (tea->band == BAND_AM) ? 5 : 50; /* kHz */
391fc488517SHans de Goede
392bc2b395cSHans Verkuil /* clear the frequency, HW will fill it in */
393bc2b395cSHans Verkuil tea->val &= ~TEA575X_BIT_FREQ_MASK;
394d4ecc83bSHans Verkuil tea->val |= TEA575X_BIT_SEARCH;
395d4ecc83bSHans Verkuil if (a->seek_upward)
396d4ecc83bSHans Verkuil tea->val |= TEA575X_BIT_UPDOWN;
397d4ecc83bSHans Verkuil else
398bc2b395cSHans Verkuil tea->val &= ~TEA575X_BIT_UPDOWN;
399bc2b395cSHans Verkuil snd_tea575x_write(tea, tea->val);
400bc2b395cSHans Verkuil timeout = jiffies + msecs_to_jiffies(10000);
401bc2b395cSHans Verkuil for (;;) {
402bc2b395cSHans Verkuil if (time_after(jiffies, timeout))
403bc2b395cSHans Verkuil break;
404d4ecc83bSHans Verkuil if (schedule_timeout_interruptible(msecs_to_jiffies(10))) {
405d4ecc83bSHans Verkuil /* some signal arrived, stop search */
406d4ecc83bSHans Verkuil tea->val &= ~TEA575X_BIT_SEARCH;
407bc2b395cSHans Verkuil snd_tea575x_set_freq(tea);
408d4ecc83bSHans Verkuil return -ERESTARTSYS;
409d4ecc83bSHans Verkuil }
410bc2b395cSHans Verkuil if (!(snd_tea575x_read(tea) & TEA575X_BIT_SEARCH)) {
411bc2b395cSHans Verkuil u32 freq;
412bc2b395cSHans Verkuil
413bc2b395cSHans Verkuil /* Found a frequency, wait until it can be read */
414bc2b395cSHans Verkuil for (i = 0; i < 100; i++) {
415bc2b395cSHans Verkuil msleep(10);
416bc2b395cSHans Verkuil freq = snd_tea575x_get_freq(tea);
417bc2b395cSHans Verkuil if (freq) /* available */
418bc2b395cSHans Verkuil break;
419d4ecc83bSHans Verkuil }
420bc2b395cSHans Verkuil if (freq == 0) /* shouldn't happen */
421bc2b395cSHans Verkuil break;
422bc2b395cSHans Verkuil /*
423fc488517SHans de Goede * if we moved by less than the spacing, or in the
424fc488517SHans de Goede * wrong direction, continue seeking
425bc2b395cSHans Verkuil */
426fc488517SHans de Goede if (abs(tea->freq - freq) < 16 * spacing ||
427bc2b395cSHans Verkuil (a->seek_upward && freq < tea->freq) ||
428bc2b395cSHans Verkuil (!a->seek_upward && freq > tea->freq)) {
429bc2b395cSHans Verkuil snd_tea575x_write(tea, tea->val);
430bc2b395cSHans Verkuil continue;
431bc2b395cSHans Verkuil }
432bc2b395cSHans Verkuil tea->freq = freq;
433bc2b395cSHans Verkuil tea->val &= ~TEA575X_BIT_SEARCH;
4341da177e4SLinus Torvalds return 0;
4351da177e4SLinus Torvalds }
436bc2b395cSHans Verkuil }
437bc2b395cSHans Verkuil tea->val &= ~TEA575X_BIT_SEARCH;
438bc2b395cSHans Verkuil snd_tea575x_set_freq(tea);
43954f6019bSHans Verkuil return -ENODATA;
440bc2b395cSHans Verkuil }
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 }
4509b76ede4SMauro Carvalho Chehab
tea575x_s_ctrl(struct v4l2_ctrl * ctrl)4514522e825SOndrej Zary static int tea575x_s_ctrl(struct v4l2_ctrl *ctrl)
4529b76ede4SMauro Carvalho Chehab {
4534522e825SOndrej Zary struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler);
4549b76ede4SMauro Carvalho Chehab
4559b76ede4SMauro Carvalho Chehab switch (ctrl->id) {
4569b76ede4SMauro Carvalho Chehab case V4L2_CID_AUDIO_MUTE:
4574522e825SOndrej Zary tea->mute = ctrl->val;
45814219d06SOndrej Zary snd_tea575x_set_freq(tea);
45914219d06SOndrej Zary return 0;
4609b76ede4SMauro Carvalho Chehab }
4614522e825SOndrej Zary
4629b76ede4SMauro Carvalho Chehab return -EINVAL;
4639b76ede4SMauro Carvalho Chehab }
4649b76ede4SMauro Carvalho Chehab
4659b76ede4SMauro Carvalho Chehab static const struct v4l2_file_operations tea575x_fops = {
4666a529c1aSOndrej Zary .unlocked_ioctl = video_ioctl2,
467d4ecc83bSHans Verkuil .open = v4l2_fh_open,
468d4ecc83bSHans Verkuil .release = v4l2_fh_release,
469d4ecc83bSHans Verkuil .poll = v4l2_ctrl_poll,
4709b76ede4SMauro Carvalho Chehab };
4719b76ede4SMauro Carvalho Chehab
4729b76ede4SMauro Carvalho Chehab static const struct v4l2_ioctl_ops tea575x_ioctl_ops = {
4739b76ede4SMauro Carvalho Chehab .vidioc_querycap = vidioc_querycap,
4749b76ede4SMauro Carvalho Chehab .vidioc_g_tuner = vidioc_g_tuner,
4759b76ede4SMauro Carvalho Chehab .vidioc_s_tuner = vidioc_s_tuner,
4769b76ede4SMauro Carvalho Chehab .vidioc_g_frequency = vidioc_g_frequency,
4779b76ede4SMauro Carvalho Chehab .vidioc_s_frequency = vidioc_s_frequency,
478d4ecc83bSHans Verkuil .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek,
479fc488517SHans de Goede .vidioc_enum_freq_bands = vidioc_enum_freq_bands,
480d4ecc83bSHans Verkuil .vidioc_log_status = v4l2_ctrl_log_status,
481d4ecc83bSHans Verkuil .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
482d4ecc83bSHans Verkuil .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
4839b76ede4SMauro Carvalho Chehab };
4849b76ede4SMauro Carvalho Chehab
485d4ecc83bSHans Verkuil static const struct video_device tea575x_radio = {
4869b76ede4SMauro Carvalho Chehab .ioctl_ops = &tea575x_ioctl_ops,
4874522e825SOndrej Zary .release = video_device_release_empty,
4884522e825SOndrej Zary };
4894522e825SOndrej Zary
4904522e825SOndrej Zary static const struct v4l2_ctrl_ops tea575x_ctrl_ops = {
4914522e825SOndrej Zary .s_ctrl = tea575x_s_ctrl,
4929b76ede4SMauro Carvalho Chehab };
4939b76ede4SMauro Carvalho Chehab
4941da177e4SLinus Torvalds
snd_tea575x_hw_init(struct snd_tea575x * tea)4958fd79579SOndrej Zary int snd_tea575x_hw_init(struct snd_tea575x *tea)
4968fd79579SOndrej Zary {
497d4ecc83bSHans Verkuil tea->mute = true;
49814219d06SOndrej Zary
499d4ecc83bSHans Verkuil /* Not all devices can or know how to read the data back.
500d4ecc83bSHans Verkuil Such devices can set cannot_read_data to true. */
501d4ecc83bSHans Verkuil if (!tea->cannot_read_data) {
50214219d06SOndrej Zary snd_tea575x_write(tea, 0x55AA);
50314219d06SOndrej Zary if (snd_tea575x_read(tea) != 0x55AA)
50414219d06SOndrej Zary return -ENODEV;
505d4ecc83bSHans Verkuil }
5061da177e4SLinus Torvalds
507bc2b395cSHans Verkuil tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_5_28;
5081da177e4SLinus Torvalds tea->freq = 90500 * 16; /* 90.5Mhz default */
5094522e825SOndrej Zary snd_tea575x_set_freq(tea);
5101da177e4SLinus Torvalds
5118fd79579SOndrej Zary return 0;
5128fd79579SOndrej Zary }
5138fd79579SOndrej Zary EXPORT_SYMBOL(snd_tea575x_hw_init);
5148fd79579SOndrej Zary
snd_tea575x_init(struct snd_tea575x * tea,struct module * owner)5158fd79579SOndrej Zary int snd_tea575x_init(struct snd_tea575x *tea, struct module *owner)
5168fd79579SOndrej Zary {
5178fd79579SOndrej Zary int retval = snd_tea575x_hw_init(tea);
5188fd79579SOndrej Zary
5198fd79579SOndrej Zary if (retval)
5208fd79579SOndrej Zary return retval;
5218fd79579SOndrej Zary
5224522e825SOndrej Zary tea->vd = tea575x_radio;
5234522e825SOndrej Zary video_set_drvdata(&tea->vd, tea);
5246a529c1aSOndrej Zary mutex_init(&tea->mutex);
525c0decac1SMauro Carvalho Chehab strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name));
5266a529c1aSOndrej Zary tea->vd.lock = &tea->mutex;
527d4ecc83bSHans Verkuil 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;
5315daf53a6SHans de Goede tea->fops = tea575x_fops;
5325daf53a6SHans de Goede tea->fops.owner = owner;
5335daf53a6SHans de Goede tea->vd.fops = &tea->fops;
53465397995SHans Verkuil /* disable hw_freq_seek if we can't use it */
53565397995SHans Verkuil if (tea->cannot_read_data)
536152a3a73SHans Verkuil v4l2_disable_ioctl(&tea->vd, VIDIOC_S_HW_FREQ_SEEK);
5379b76ede4SMauro Carvalho Chehab
5383d0fe51cSHans de Goede if (!tea->cannot_mute) {
5393d0fe51cSHans de Goede tea->vd.ctrl_handler = &tea->ctrl_handler;
5404522e825SOndrej Zary v4l2_ctrl_handler_init(&tea->ctrl_handler, 1);
5413d0fe51cSHans de Goede v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops,
5423d0fe51cSHans de Goede V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
5434522e825SOndrej Zary retval = tea->ctrl_handler.error;
5449b76ede4SMauro Carvalho Chehab if (retval) {
545d4ecc83bSHans Verkuil v4l2_err(tea->v4l2_dev, "can't initialize controls\n");
5464522e825SOndrej Zary v4l2_ctrl_handler_free(&tea->ctrl_handler);
54714219d06SOndrej Zary return retval;
5489b76ede4SMauro Carvalho Chehab }
5499b76ede4SMauro Carvalho Chehab
5504522e825SOndrej Zary if (tea->ext_init) {
5514522e825SOndrej Zary retval = tea->ext_init(tea);
5524522e825SOndrej Zary if (retval) {
5534522e825SOndrej Zary v4l2_ctrl_handler_free(&tea->ctrl_handler);
5544522e825SOndrej Zary return retval;
5554522e825SOndrej Zary }
5564522e825SOndrej Zary }
5574522e825SOndrej Zary
5584522e825SOndrej Zary v4l2_ctrl_handler_setup(&tea->ctrl_handler);
5593d0fe51cSHans de Goede }
5604522e825SOndrej Zary
561d4ecc83bSHans Verkuil retval = video_register_device(&tea->vd, VFL_TYPE_RADIO, tea->radio_nr);
5624522e825SOndrej Zary if (retval) {
563d4ecc83bSHans Verkuil v4l2_err(tea->v4l2_dev, "can't register video device!\n");
5643d0fe51cSHans de Goede v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
5654522e825SOndrej Zary return retval;
5664522e825SOndrej Zary }
56714219d06SOndrej Zary
56814219d06SOndrej Zary return 0;
5691da177e4SLinus Torvalds }
5700e2a706bSAndy Shevchenko EXPORT_SYMBOL(snd_tea575x_init);
5711da177e4SLinus Torvalds
snd_tea575x_exit(struct snd_tea575x * tea)57297f02e05STakashi Iwai void snd_tea575x_exit(struct snd_tea575x *tea)
5731da177e4SLinus Torvalds {
5744522e825SOndrej Zary video_unregister_device(&tea->vd);
5753d0fe51cSHans de Goede v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
5761da177e4SLinus Torvalds }
5771da177e4SLinus Torvalds EXPORT_SYMBOL(snd_tea575x_exit);
578