aboutsummaryrefslogtreecommitdiffstats
path: root/sound/i2c
diff options
context:
space:
mode:
authorHans de Goede <hdegoede@redhat.com>2012-07-12 16:39:18 -0400
committerMauro Carvalho Chehab <mchehab@redhat.com>2012-09-13 16:42:09 -0400
commitfc488517cc0d50bcc9e4ffa90fee5755f9c914fc (patch)
treee32c210cbe62c44bcb7291ddd18821370bef5c20 /sound/i2c
parent355a4d01bd74bdd7b9dd9adeec683b2e3dd9549b (diff)
[media] snd_tea575x: Add support for tuning AM
Add support for tuning AM (on devices with the necessary additional hardware components), and advertise the available bands using the new VIDIOC_ENUM_FREQ_BANDS ioctl. Signed-off-by: Hans de Goede <hdegoede@redhat.com> CC: Ondrej Zary <linux@rainbow-software.org> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'sound/i2c')
-rw-r--r--sound/i2c/other/tea575x-tuner.c197
1 files changed, 161 insertions, 36 deletions
diff --git a/sound/i2c/other/tea575x-tuner.c b/sound/i2c/other/tea575x-tuner.c
index d14edb7d6484..88bbd88c066e 100644
--- a/sound/i2c/other/tea575x-tuner.c
+++ b/sound/i2c/other/tea575x-tuner.c
@@ -37,9 +37,6 @@ MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
37MODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips"); 37MODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips");
38MODULE_LICENSE("GPL"); 38MODULE_LICENSE("GPL");
39 39
40#define FREQ_LO ((tea->tea5759 ? 760 : 875) * 1600U)
41#define FREQ_HI ((tea->tea5759 ? 910 : 1080) * 1600U)
42
43/* 40/*
44 * definitions 41 * definitions
45 */ 42 */
@@ -50,8 +47,8 @@ MODULE_LICENSE("GPL");
50#define TEA575X_BIT_BAND_MASK (3<<20) 47#define TEA575X_BIT_BAND_MASK (3<<20)
51#define TEA575X_BIT_BAND_FM (0<<20) 48#define TEA575X_BIT_BAND_FM (0<<20)
52#define TEA575X_BIT_BAND_MW (1<<20) 49#define TEA575X_BIT_BAND_MW (1<<20)
53#define TEA575X_BIT_BAND_LW (1<<21) 50#define TEA575X_BIT_BAND_LW (2<<20)
54#define TEA575X_BIT_BAND_SW (1<<22) 51#define TEA575X_BIT_BAND_SW (3<<20)
55#define TEA575X_BIT_PORT_0 (1<<19) /* user bit */ 52#define TEA575X_BIT_PORT_0 (1<<19) /* user bit */
56#define TEA575X_BIT_PORT_1 (1<<18) /* user bit */ 53#define TEA575X_BIT_PORT_1 (1<<18) /* user bit */
57#define TEA575X_BIT_SEARCH_MASK (3<<16) /* search level */ 54#define TEA575X_BIT_SEARCH_MASK (3<<16) /* search level */
@@ -62,6 +59,37 @@ MODULE_LICENSE("GPL");
62#define TEA575X_BIT_DUMMY (1<<15) /* buffer */ 59#define TEA575X_BIT_DUMMY (1<<15) /* buffer */
63#define TEA575X_BIT_FREQ_MASK 0x7fff 60#define TEA575X_BIT_FREQ_MASK 0x7fff
64 61
62enum { BAND_FM, BAND_FM_JAPAN, BAND_AM };
63
64static const struct v4l2_frequency_band bands[] = {
65 {
66 .type = V4L2_TUNER_RADIO,
67 .index = 0,
68 .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
69 V4L2_TUNER_CAP_FREQ_BANDS,
70 .rangelow = 87500 * 16,
71 .rangehigh = 108000 * 16,
72 .modulation = V4L2_BAND_MODULATION_FM,
73 },
74 {
75 .type = V4L2_TUNER_RADIO,
76 .index = 0,
77 .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
78 V4L2_TUNER_CAP_FREQ_BANDS,
79 .rangelow = 76000 * 16,
80 .rangehigh = 91000 * 16,
81 .modulation = V4L2_BAND_MODULATION_FM,
82 },
83 {
84 .type = V4L2_TUNER_RADIO,
85 .index = 1,
86 .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
87 .rangelow = 530 * 16,
88 .rangehigh = 1710 * 16,
89 .modulation = V4L2_BAND_MODULATION_AM,
90 },
91};
92
65/* 93/*
66 * lowlevel part 94 * lowlevel part
67 */ 95 */
@@ -133,16 +161,29 @@ static u32 snd_tea575x_val_to_freq(struct snd_tea575x *tea, u32 val)
133 if (freq == 0) 161 if (freq == 0)
134 return freq; 162 return freq;
135 163
136 /* freq *= 12.5 */ 164 switch (tea->band) {
137 freq *= 125; 165 case BAND_FM:
138 freq /= 10; 166 /* freq *= 12.5 */
139 /* crystal fixup */ 167 freq *= 125;
140 if (tea->tea5759) 168 freq /= 10;
141 freq += TEA575X_FMIF; 169 /* crystal fixup */
142 else
143 freq -= TEA575X_FMIF; 170 freq -= TEA575X_FMIF;
171 break;
172 case BAND_FM_JAPAN:
173 /* freq *= 12.5 */
174 freq *= 125;
175 freq /= 10;
176 /* crystal fixup */
177 freq += TEA575X_FMIF;
178 break;
179 case BAND_AM:
180 /* crystal fixup */
181 freq -= TEA575X_AMIF;
182 break;
183 }
144 184
145 return clamp(freq * 16, FREQ_LO, FREQ_HI); /* from kHz */ 185 return clamp(freq * 16, bands[tea->band].rangelow,
186 bands[tea->band].rangehigh); /* from kHz */
146} 187}
147 188
148static u32 snd_tea575x_get_freq(struct snd_tea575x *tea) 189static u32 snd_tea575x_get_freq(struct snd_tea575x *tea)
@@ -152,19 +193,35 @@ static u32 snd_tea575x_get_freq(struct snd_tea575x *tea)
152 193
153static void snd_tea575x_set_freq(struct snd_tea575x *tea) 194static void snd_tea575x_set_freq(struct snd_tea575x *tea)
154{ 195{
155 u32 freq = tea->freq; 196 u32 freq = tea->freq / 16; /* to kHz */
197 u32 band = 0;
156 198
157 freq /= 16; /* to kHz */ 199 switch (tea->band) {
158 /* crystal fixup */ 200 case BAND_FM:
159 if (tea->tea5759) 201 band = TEA575X_BIT_BAND_FM;
160 freq -= TEA575X_FMIF; 202 /* crystal fixup */
161 else
162 freq += TEA575X_FMIF; 203 freq += TEA575X_FMIF;
163 /* freq /= 12.5 */ 204 /* freq /= 12.5 */
164 freq *= 10; 205 freq *= 10;
165 freq /= 125; 206 freq /= 125;
207 break;
208 case BAND_FM_JAPAN:
209 band = TEA575X_BIT_BAND_FM;
210 /* crystal fixup */
211 freq -= TEA575X_FMIF;
212 /* freq /= 12.5 */
213 freq *= 10;
214 freq /= 125;
215 break;
216 case BAND_AM:
217 band = TEA575X_BIT_BAND_MW;
218 /* crystal fixup */
219 freq += TEA575X_AMIF;
220 break;
221 }
166 222
167 tea->val &= ~TEA575X_BIT_FREQ_MASK; 223 tea->val &= ~(TEA575X_BIT_FREQ_MASK | TEA575X_BIT_BAND_MASK);
224 tea->val |= band;
168 tea->val |= freq & TEA575X_BIT_FREQ_MASK; 225 tea->val |= freq & TEA575X_BIT_FREQ_MASK;
169 snd_tea575x_write(tea, tea->val); 226 snd_tea575x_write(tea, tea->val);
170 tea->freq = snd_tea575x_val_to_freq(tea, tea->val); 227 tea->freq = snd_tea575x_val_to_freq(tea, tea->val);
@@ -190,23 +247,57 @@ static int vidioc_querycap(struct file *file, void *priv,
190 return 0; 247 return 0;
191} 248}
192 249
250static int vidioc_enum_freq_bands(struct file *file, void *priv,
251 struct v4l2_frequency_band *band)
252{
253 struct snd_tea575x *tea = video_drvdata(file);
254 int index;
255
256 if (band->tuner != 0)
257 return -EINVAL;
258
259 switch (band->index) {
260 case 0:
261 if (tea->tea5759)
262 index = BAND_FM_JAPAN;
263 else
264 index = BAND_FM;
265 break;
266 case 1:
267 if (tea->has_am) {
268 index = BAND_AM;
269 break;
270 }
271 /* Fall through */
272 default:
273 return -EINVAL;
274 }
275
276 *band = bands[index];
277 if (!tea->cannot_read_data)
278 band->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED;
279
280 return 0;
281}
282
193static int vidioc_g_tuner(struct file *file, void *priv, 283static int vidioc_g_tuner(struct file *file, void *priv,
194 struct v4l2_tuner *v) 284 struct v4l2_tuner *v)
195{ 285{
196 struct snd_tea575x *tea = video_drvdata(file); 286 struct snd_tea575x *tea = video_drvdata(file);
287 struct v4l2_frequency_band band_fm = { 0, };
197 288
198 if (v->index > 0) 289 if (v->index > 0)
199 return -EINVAL; 290 return -EINVAL;
200 291
201 snd_tea575x_read(tea); 292 snd_tea575x_read(tea);
293 vidioc_enum_freq_bands(file, priv, &band_fm);
202 294
203 strcpy(v->name, "FM"); 295 memset(v, 0, sizeof(*v));
296 strlcpy(v->name, tea->has_am ? "FM/AM" : "FM", sizeof(v->name));
204 v->type = V4L2_TUNER_RADIO; 297 v->type = V4L2_TUNER_RADIO;
205 v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; 298 v->capability = band_fm.capability;
206 if (!tea->cannot_read_data) 299 v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : band_fm.rangelow;
207 v->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED; 300 v->rangehigh = band_fm.rangehigh;
208 v->rangelow = FREQ_LO;
209 v->rangehigh = FREQ_HI;
210 v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; 301 v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
211 v->audmode = (tea->val & TEA575X_BIT_MONO) ? 302 v->audmode = (tea->val & TEA575X_BIT_MONO) ?
212 V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; 303 V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO;
@@ -218,13 +309,17 @@ static int vidioc_s_tuner(struct file *file, void *priv,
218 struct v4l2_tuner *v) 309 struct v4l2_tuner *v)
219{ 310{
220 struct snd_tea575x *tea = video_drvdata(file); 311 struct snd_tea575x *tea = video_drvdata(file);
312 u32 orig_val = tea->val;
221 313
222 if (v->index) 314 if (v->index)
223 return -EINVAL; 315 return -EINVAL;
224 tea->val &= ~TEA575X_BIT_MONO; 316 tea->val &= ~TEA575X_BIT_MONO;
225 if (v->audmode == V4L2_TUNER_MODE_MONO) 317 if (v->audmode == V4L2_TUNER_MODE_MONO)
226 tea->val |= TEA575X_BIT_MONO; 318 tea->val |= TEA575X_BIT_MONO;
227 snd_tea575x_write(tea, tea->val); 319 /* Only apply changes if currently tuning FM */
320 if (tea->band != BAND_AM && tea->val != orig_val)
321 snd_tea575x_set_freq(tea);
322
228 return 0; 323 return 0;
229} 324}
230 325
@@ -248,8 +343,15 @@ static int vidioc_s_frequency(struct file *file, void *priv,
248 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 343 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
249 return -EINVAL; 344 return -EINVAL;
250 345
251 tea->val &= ~TEA575X_BIT_SEARCH; 346 if (tea->has_am && f->frequency < (20000 * 16))
252 tea->freq = clamp(f->frequency, FREQ_LO, FREQ_HI); 347 tea->band = BAND_AM;
348 else if (tea->tea5759)
349 tea->band = BAND_FM_JAPAN;
350 else
351 tea->band = BAND_FM;
352
353 tea->freq = clamp(f->frequency, bands[tea->band].rangelow,
354 bands[tea->band].rangehigh);
253 snd_tea575x_set_freq(tea); 355 snd_tea575x_set_freq(tea);
254 return 0; 356 return 0;
255} 357}
@@ -259,13 +361,35 @@ static int vidioc_s_hw_freq_seek(struct file *file, void *fh,
259{ 361{
260 struct snd_tea575x *tea = video_drvdata(file); 362 struct snd_tea575x *tea = video_drvdata(file);
261 unsigned long timeout; 363 unsigned long timeout;
262 int i; 364 int i, spacing;
263 365
264 if (tea->cannot_read_data) 366 if (tea->cannot_read_data)
265 return -ENOTTY; 367 return -ENOTTY;
266 if (a->tuner || a->wrap_around) 368 if (a->tuner || a->wrap_around)
267 return -EINVAL; 369 return -EINVAL;
268 370
371 if (a->rangelow || a->rangehigh) {
372 for (i = 0; i < ARRAY_SIZE(bands); i++) {
373 if ((i == BAND_FM && tea->tea5759) ||
374 (i == BAND_FM_JAPAN && !tea->tea5759) ||
375 (i == BAND_AM && !tea->has_am))
376 continue;
377 if (bands[i].rangelow == a->rangelow &&
378 bands[i].rangehigh == a->rangehigh)
379 break;
380 }
381 if (i == ARRAY_SIZE(bands))
382 return -EINVAL; /* No matching band found */
383 if (i != tea->band) {
384 tea->band = i;
385 tea->freq = clamp(tea->freq, bands[i].rangelow,
386 bands[i].rangehigh);
387 snd_tea575x_set_freq(tea);
388 }
389 }
390
391 spacing = (tea->band == BAND_AM) ? 5 : 50; /* kHz */
392
269 /* clear the frequency, HW will fill it in */ 393 /* clear the frequency, HW will fill it in */
270 tea->val &= ~TEA575X_BIT_FREQ_MASK; 394 tea->val &= ~TEA575X_BIT_FREQ_MASK;
271 tea->val |= TEA575X_BIT_SEARCH; 395 tea->val |= TEA575X_BIT_SEARCH;
@@ -297,10 +421,10 @@ static int vidioc_s_hw_freq_seek(struct file *file, void *fh,
297 if (freq == 0) /* shouldn't happen */ 421 if (freq == 0) /* shouldn't happen */
298 break; 422 break;
299 /* 423 /*
300 * if we moved by less than 50 kHz, or in the wrong 424 * if we moved by less than the spacing, or in the
301 * direction, continue seeking 425 * wrong direction, continue seeking
302 */ 426 */
303 if (abs(tea->freq - freq) < 16 * 50 || 427 if (abs(tea->freq - freq) < 16 * spacing ||
304 (a->seek_upward && freq < tea->freq) || 428 (a->seek_upward && freq < tea->freq) ||
305 (!a->seek_upward && freq > tea->freq)) { 429 (!a->seek_upward && freq > tea->freq)) {
306 snd_tea575x_write(tea, tea->val); 430 snd_tea575x_write(tea, tea->val);
@@ -344,6 +468,7 @@ static const struct v4l2_ioctl_ops tea575x_ioctl_ops = {
344 .vidioc_g_frequency = vidioc_g_frequency, 468 .vidioc_g_frequency = vidioc_g_frequency,
345 .vidioc_s_frequency = vidioc_s_frequency, 469 .vidioc_s_frequency = vidioc_s_frequency,
346 .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, 470 .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek,
471 .vidioc_enum_freq_bands = vidioc_enum_freq_bands,
347 .vidioc_log_status = v4l2_ctrl_log_status, 472 .vidioc_log_status = v4l2_ctrl_log_status,
348 .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 473 .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
349 .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 474 .vidioc_unsubscribe_event = v4l2_event_unsubscribe,