aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/radio/radio-tea5777.c
diff options
context:
space:
mode:
authorHans de Goede <hdegoede@redhat.com>2012-06-23 03:39:58 -0400
committerMauro Carvalho Chehab <mchehab@redhat.com>2012-07-30 17:46:17 -0400
commit4faba767c6243b43ad975406fe027be7394e4591 (patch)
tree2c2d6848b37080ed12b554214b655b55d3e83241 /drivers/media/radio/radio-tea5777.c
parent7a3ed2d95e9ef3032700c2e56f3369d8652a6e8b (diff)
[media] shark2: New driver for the Griffin radioSHARK v2 USB radio receiver
This driver consists of 2 parts, a generic tea5777 driver and a driver for the Griffin radioSHARK v2 USB radio receiver, which is the only driver using the generic tea5777 for now. This first version only implements FM support, once the the new VIDIOC_ENUM_FREQ_BANDS API is upstream I'll also add AM support. Signed-off-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/radio/radio-tea5777.c')
-rw-r--r--drivers/media/radio/radio-tea5777.c489
1 files changed, 489 insertions, 0 deletions
diff --git a/drivers/media/radio/radio-tea5777.c b/drivers/media/radio/radio-tea5777.c
new file mode 100644
index 000000000000..3e12179364f8
--- /dev/null
+++ b/drivers/media/radio/radio-tea5777.c
@@ -0,0 +1,489 @@
1/*
2 * v4l2 driver for TEA5777 Philips AM/FM radio tuner chips
3 *
4 * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com>
5 *
6 * Based on the ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips:
7 *
8 * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 *
24 */
25
26#include <linux/delay.h>
27#include <linux/init.h>
28#include <linux/module.h>
29#include <linux/sched.h>
30#include <linux/slab.h>
31#include <media/v4l2-device.h>
32#include <media/v4l2-dev.h>
33#include <media/v4l2-fh.h>
34#include <media/v4l2-ioctl.h>
35#include <media/v4l2-event.h>
36#include "radio-tea5777.h"
37
38MODULE_AUTHOR("Hans de Goede <perex@perex.cz>");
39MODULE_DESCRIPTION("Routines for control of TEA5777 Philips AM/FM radio tuner chips");
40MODULE_LICENSE("GPL");
41
42/* Fixed FM only band for now, will implement multi-band support when the
43 VIDIOC_ENUM_FREQ_BANDS API is upstream */
44#define TEA5777_FM_RANGELOW (76000 * 16)
45#define TEA5777_FM_RANGEHIGH (108000 * 16)
46
47#define TEA5777_FM_IF 150 /* kHz */
48#define TEA5777_FM_FREQ_STEP 50 /* kHz */
49
50/* Write reg, common bits */
51#define TEA5777_W_MUTE_MASK (1LL << 47)
52#define TEA5777_W_MUTE_SHIFT 47
53#define TEA5777_W_AM_FM_MASK (1LL << 46)
54#define TEA5777_W_AM_FM_SHIFT 46
55#define TEA5777_W_STB_MASK (1LL << 45)
56#define TEA5777_W_STB_SHIFT 45
57
58#define TEA5777_W_IFCE_MASK (1LL << 29)
59#define TEA5777_W_IFCE_SHIFT 29
60#define TEA5777_W_IFW_MASK (1LL << 28)
61#define TEA5777_W_IFW_SHIFT 28
62#define TEA5777_W_HILO_MASK (1LL << 27)
63#define TEA5777_W_HILO_SHIFT 27
64#define TEA5777_W_DBUS_MASK (1LL << 26)
65#define TEA5777_W_DBUS_SHIFT 26
66
67#define TEA5777_W_INTEXT_MASK (1LL << 24)
68#define TEA5777_W_INTEXT_SHIFT 24
69#define TEA5777_W_P1_MASK (1LL << 23)
70#define TEA5777_W_P1_SHIFT 23
71#define TEA5777_W_P0_MASK (1LL << 22)
72#define TEA5777_W_P0_SHIFT 22
73#define TEA5777_W_PEN1_MASK (1LL << 21)
74#define TEA5777_W_PEN1_SHIFT 21
75#define TEA5777_W_PEN0_MASK (1LL << 20)
76#define TEA5777_W_PEN0_SHIFT 20
77
78#define TEA5777_W_CHP0_MASK (1LL << 18)
79#define TEA5777_W_CHP0_SHIFT 18
80#define TEA5777_W_DEEM_MASK (1LL << 17)
81#define TEA5777_W_DEEM_SHIFT 17
82
83#define TEA5777_W_SEARCH_MASK (1LL << 7)
84#define TEA5777_W_SEARCH_SHIFT 7
85#define TEA5777_W_PROGBLIM_MASK (1LL << 6)
86#define TEA5777_W_PROGBLIM_SHIFT 6
87#define TEA5777_W_UPDWN_MASK (1LL << 5)
88#define TEA5777_W_UPDWN_SHIFT 5
89#define TEA5777_W_SLEV_MASK (3LL << 3)
90#define TEA5777_W_SLEV_SHIFT 3
91
92/* Write reg, FM specific bits */
93#define TEA5777_W_FM_PLL_MASK (0x1fffLL << 32)
94#define TEA5777_W_FM_PLL_SHIFT 32
95#define TEA5777_W_FM_FREF_MASK (0x03LL << 30)
96#define TEA5777_W_FM_FREF_SHIFT 30
97#define TEA5777_W_FM_FREF_VALUE 0 /* 50 kHz tune steps, 150 kHz IF */
98
99#define TEA5777_W_FM_FORCEMONO_MASK (1LL << 15)
100#define TEA5777_W_FM_FORCEMONO_SHIFT 15
101#define TEA5777_W_FM_SDSOFF_MASK (1LL << 14)
102#define TEA5777_W_FM_SDSOFF_SHIFT 14
103#define TEA5777_W_FM_DOFF_MASK (1LL << 13)
104#define TEA5777_W_FM_DOFF_SHIFT 13
105
106#define TEA5777_W_FM_STEP_MASK (3LL << 1)
107#define TEA5777_W_FM_STEP_SHIFT 1
108
109/* Write reg, AM specific bits */
110#define TEA5777_W_AM_PLL_MASK (0x7ffLL << 34)
111#define TEA5777_W_AM_PLL_SHIFT 34
112#define TEA5777_W_AM_AGCRF_MASK (1LL << 33)
113#define TEA5777_W_AM_AGCRF_SHIFT 33
114#define TEA5777_W_AM_AGCIF_MASK (1LL << 32)
115#define TEA5777_W_AM_AGCIF_SHIFT 32
116#define TEA5777_W_AM_MWLW_MASK (1LL << 31)
117#define TEA5777_W_AM_MWLW_SHIFT 31
118#define TEA5777_W_AM_LNA_MASK (1LL << 30)
119#define TEA5777_W_AM_LNA_SHIFT 30
120
121#define TEA5777_W_AM_PEAK_MASK (1LL << 25)
122#define TEA5777_W_AM_PEAK_SHIFT 25
123
124#define TEA5777_W_AM_RFB_MASK (1LL << 16)
125#define TEA5777_W_AM_RFB_SHIFT 16
126#define TEA5777_W_AM_CALLIGN_MASK (1LL << 15)
127#define TEA5777_W_AM_CALLIGN_SHIFT 15
128#define TEA5777_W_AM_CBANK_MASK (0x7fLL << 8)
129#define TEA5777_W_AM_CBANK_SHIFT 8
130
131#define TEA5777_W_AM_DELAY_MASK (1LL << 2)
132#define TEA5777_W_AM_DELAY_SHIFT 2
133#define TEA5777_W_AM_STEP_MASK (1LL << 1)
134#define TEA5777_W_AM_STEP_SHIFT 1
135
136/* Read reg, common bits */
137#define TEA5777_R_LEVEL_MASK (0x0f << 17)
138#define TEA5777_R_LEVEL_SHIFT 17
139#define TEA5777_R_SFOUND_MASK (0x01 << 16)
140#define TEA5777_R_SFOUND_SHIFT 16
141#define TEA5777_R_BLIM_MASK (0x01 << 15)
142#define TEA5777_R_BLIM_SHIFT 15
143
144/* Read reg, FM specific bits */
145#define TEA5777_R_FM_STEREO_MASK (0x01 << 21)
146#define TEA5777_R_FM_STEREO_SHIFT 21
147#define TEA5777_R_FM_PLL_MASK 0x1fff
148#define TEA5777_R_FM_PLL_SHIFT 0
149
150static u32 tea5777_freq_to_v4l2_freq(struct radio_tea5777 *tea, u32 freq)
151{
152 return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16;
153}
154
155static int radio_tea5777_set_freq(struct radio_tea5777 *tea)
156{
157 u64 freq;
158 int res;
159
160 freq = clamp_t(u32, tea->freq,
161 TEA5777_FM_RANGELOW, TEA5777_FM_RANGEHIGH);
162 freq = (freq + 8) / 16; /* to kHz */
163
164 freq = (freq - TEA5777_FM_IF) / TEA5777_FM_FREQ_STEP;
165
166 tea->write_reg &= ~(TEA5777_W_FM_PLL_MASK | TEA5777_W_FM_FREF_MASK);
167 tea->write_reg |= freq << TEA5777_W_FM_PLL_SHIFT;
168 tea->write_reg |= TEA5777_W_FM_FREF_VALUE << TEA5777_W_FM_FREF_SHIFT;
169
170 res = tea->ops->write_reg(tea, tea->write_reg);
171 if (res)
172 return res;
173
174 tea->needs_write = false;
175 tea->read_reg = -1;
176 tea->freq = tea5777_freq_to_v4l2_freq(tea, freq);
177
178 return 0;
179}
180
181static int radio_tea5777_update_read_reg(struct radio_tea5777 *tea, int wait)
182{
183 int res;
184
185 if (tea->read_reg != -1)
186 return 0;
187
188 if (tea->write_before_read && tea->needs_write) {
189 res = radio_tea5777_set_freq(tea);
190 if (res)
191 return res;
192 }
193
194 if (wait) {
195 if (schedule_timeout_interruptible(msecs_to_jiffies(wait)))
196 return -ERESTARTSYS;
197 }
198
199 res = tea->ops->read_reg(tea, &tea->read_reg);
200 if (res)
201 return res;
202
203 tea->needs_write = true;
204 return 0;
205}
206
207/*
208 * Linux Video interface
209 */
210
211static int vidioc_querycap(struct file *file, void *priv,
212 struct v4l2_capability *v)
213{
214 struct radio_tea5777 *tea = video_drvdata(file);
215
216 strlcpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver));
217 strlcpy(v->card, tea->card, sizeof(v->card));
218 strlcat(v->card, " TEA5777", sizeof(v->card));
219 strlcpy(v->bus_info, tea->bus_info, sizeof(v->bus_info));
220 v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
221 v->device_caps |= V4L2_CAP_HW_FREQ_SEEK;
222 v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
223 return 0;
224}
225
226static int vidioc_g_tuner(struct file *file, void *priv,
227 struct v4l2_tuner *v)
228{
229 struct radio_tea5777 *tea = video_drvdata(file);
230 int res;
231
232 if (v->index > 0)
233 return -EINVAL;
234
235 res = radio_tea5777_update_read_reg(tea, 0);
236 if (res)
237 return res;
238
239 memset(v, 0, sizeof(*v));
240 if (tea->has_am)
241 strlcpy(v->name, "AM/FM", sizeof(v->name));
242 else
243 strlcpy(v->name, "FM", sizeof(v->name));
244 v->type = V4L2_TUNER_RADIO;
245 v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
246 V4L2_TUNER_CAP_HWSEEK_BOUNDED;
247 v->rangelow = TEA5777_FM_RANGELOW;
248 v->rangehigh = TEA5777_FM_RANGEHIGH;
249 v->rxsubchans = (tea->read_reg & TEA5777_R_FM_STEREO_MASK) ?
250 V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
251 v->audmode = (tea->write_reg & TEA5777_W_FM_FORCEMONO_MASK) ?
252 V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO;
253 /* shift - 12 to convert 4-bits (0-15) scale to 16-bits (0-65535) */
254 v->signal = (tea->read_reg & TEA5777_R_LEVEL_MASK) >>
255 (TEA5777_R_LEVEL_SHIFT - 12);
256
257 /* Invalidate read_reg, so that next call we return up2date signal */
258 tea->read_reg = -1;
259
260 return 0;
261}
262
263static int vidioc_s_tuner(struct file *file, void *priv,
264 struct v4l2_tuner *v)
265{
266 struct radio_tea5777 *tea = video_drvdata(file);
267
268 if (v->index)
269 return -EINVAL;
270
271 if (v->audmode == V4L2_TUNER_MODE_MONO)
272 tea->write_reg |= TEA5777_W_FM_FORCEMONO_MASK;
273 else
274 tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK;
275
276 return radio_tea5777_set_freq(tea);
277}
278
279static int vidioc_g_frequency(struct file *file, void *priv,
280 struct v4l2_frequency *f)
281{
282 struct radio_tea5777 *tea = video_drvdata(file);
283
284 if (f->tuner != 0)
285 return -EINVAL;
286 f->type = V4L2_TUNER_RADIO;
287 f->frequency = tea->freq;
288 return 0;
289}
290
291static int vidioc_s_frequency(struct file *file, void *priv,
292 struct v4l2_frequency *f)
293{
294 struct radio_tea5777 *tea = video_drvdata(file);
295
296 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
297 return -EINVAL;
298
299 tea->freq = f->frequency;
300 return radio_tea5777_set_freq(tea);
301}
302
303static int vidioc_s_hw_freq_seek(struct file *file, void *fh,
304 struct v4l2_hw_freq_seek *a)
305{
306 struct radio_tea5777 *tea = video_drvdata(file);
307 u32 orig_freq = tea->freq;
308 unsigned long timeout;
309 int res, spacing = 200 * 16; /* 200 kHz */
310 /* These are fixed *for now* */
311 const u32 seek_rangelow = TEA5777_FM_RANGELOW;
312 const u32 seek_rangehigh = TEA5777_FM_RANGEHIGH;
313
314 if (a->tuner || a->wrap_around)
315 return -EINVAL;
316
317 tea->write_reg |= TEA5777_W_PROGBLIM_MASK;
318 if (seek_rangelow != tea->seek_rangelow) {
319 tea->write_reg &= ~TEA5777_W_UPDWN_MASK;
320 tea->freq = seek_rangelow;
321 res = radio_tea5777_set_freq(tea);
322 if (res)
323 goto leave;
324 tea->seek_rangelow = tea->freq;
325 }
326 if (seek_rangehigh != tea->seek_rangehigh) {
327 tea->write_reg |= TEA5777_W_UPDWN_MASK;
328 tea->freq = seek_rangehigh;
329 res = radio_tea5777_set_freq(tea);
330 if (res)
331 goto leave;
332 tea->seek_rangehigh = tea->freq;
333 }
334 tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK;
335
336 tea->write_reg |= TEA5777_W_SEARCH_MASK;
337 if (a->seek_upward) {
338 tea->write_reg |= TEA5777_W_UPDWN_MASK;
339 tea->freq = orig_freq + spacing;
340 } else {
341 tea->write_reg &= ~TEA5777_W_UPDWN_MASK;
342 tea->freq = orig_freq - spacing;
343 }
344 res = radio_tea5777_set_freq(tea);
345 if (res)
346 goto leave;
347
348 timeout = jiffies + msecs_to_jiffies(5000);
349 for (;;) {
350 if (time_after(jiffies, timeout)) {
351 res = -ENODATA;
352 break;
353 }
354
355 res = radio_tea5777_update_read_reg(tea, 100);
356 if (res)
357 break;
358
359 /*
360 * Note we use tea->freq to track how far we've searched sofar
361 * this is necessary to ensure we continue seeking at the right
362 * point, in the write_before_read case.
363 */
364 tea->freq = (tea->read_reg & TEA5777_R_FM_PLL_MASK);
365 tea->freq = tea5777_freq_to_v4l2_freq(tea, tea->freq);
366
367 if ((tea->read_reg & TEA5777_R_SFOUND_MASK)) {
368 tea->write_reg &= ~TEA5777_W_SEARCH_MASK;
369 return 0;
370 }
371
372 if (tea->read_reg & TEA5777_R_BLIM_MASK) {
373 res = -ENODATA;
374 break;
375 }
376
377 /* Force read_reg update */
378 tea->read_reg = -1;
379 }
380leave:
381 tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK;
382 tea->write_reg &= ~TEA5777_W_SEARCH_MASK;
383 tea->freq = orig_freq;
384 radio_tea5777_set_freq(tea);
385 return res;
386}
387
388static int tea575x_s_ctrl(struct v4l2_ctrl *c)
389{
390 struct radio_tea5777 *tea =
391 container_of(c->handler, struct radio_tea5777, ctrl_handler);
392
393 switch (c->id) {
394 case V4L2_CID_AUDIO_MUTE:
395 if (c->val)
396 tea->write_reg |= TEA5777_W_MUTE_MASK;
397 else
398 tea->write_reg &= ~TEA5777_W_MUTE_MASK;
399
400 return radio_tea5777_set_freq(tea);
401 }
402
403 return -EINVAL;
404}
405
406static const struct v4l2_file_operations tea575x_fops = {
407 .unlocked_ioctl = video_ioctl2,
408 .open = v4l2_fh_open,
409 .release = v4l2_fh_release,
410 .poll = v4l2_ctrl_poll,
411};
412
413static const struct v4l2_ioctl_ops tea575x_ioctl_ops = {
414 .vidioc_querycap = vidioc_querycap,
415 .vidioc_g_tuner = vidioc_g_tuner,
416 .vidioc_s_tuner = vidioc_s_tuner,
417 .vidioc_g_frequency = vidioc_g_frequency,
418 .vidioc_s_frequency = vidioc_s_frequency,
419 .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek,
420 .vidioc_log_status = v4l2_ctrl_log_status,
421 .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
422 .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
423};
424
425static const struct video_device tea575x_radio = {
426 .ioctl_ops = &tea575x_ioctl_ops,
427 .release = video_device_release_empty,
428};
429
430static const struct v4l2_ctrl_ops tea575x_ctrl_ops = {
431 .s_ctrl = tea575x_s_ctrl,
432};
433
434int radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner)
435{
436 int res;
437
438 tea->write_reg = (1LL << TEA5777_W_IFCE_SHIFT) |
439 (1LL << TEA5777_W_IFW_SHIFT) |
440 (1LL << TEA5777_W_INTEXT_SHIFT) |
441 (1LL << TEA5777_W_CHP0_SHIFT) |
442 (2LL << TEA5777_W_SLEV_SHIFT);
443 tea->freq = 90500 * 16; /* 90.5Mhz default */
444 res = radio_tea5777_set_freq(tea);
445 if (res) {
446 v4l2_err(tea->v4l2_dev, "can't set initial freq (%d)\n", res);
447 return res;
448 }
449
450 tea->vd = tea575x_radio;
451 video_set_drvdata(&tea->vd, tea);
452 mutex_init(&tea->mutex);
453 strlcpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name));
454 tea->vd.lock = &tea->mutex;
455 tea->vd.v4l2_dev = tea->v4l2_dev;
456 tea->fops = tea575x_fops;
457 tea->fops.owner = owner;
458 tea->vd.fops = &tea->fops;
459 set_bit(V4L2_FL_USE_FH_PRIO, &tea->vd.flags);
460
461 tea->vd.ctrl_handler = &tea->ctrl_handler;
462 v4l2_ctrl_handler_init(&tea->ctrl_handler, 1);
463 v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops,
464 V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
465 res = tea->ctrl_handler.error;
466 if (res) {
467 v4l2_err(tea->v4l2_dev, "can't initialize controls\n");
468 v4l2_ctrl_handler_free(&tea->ctrl_handler);
469 return res;
470 }
471 v4l2_ctrl_handler_setup(&tea->ctrl_handler);
472
473 res = video_register_device(&tea->vd, VFL_TYPE_RADIO, -1);
474 if (res) {
475 v4l2_err(tea->v4l2_dev, "can't register video device!\n");
476 v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
477 return res;
478 }
479
480 return 0;
481}
482EXPORT_SYMBOL_GPL(radio_tea5777_init);
483
484void radio_tea5777_exit(struct radio_tea5777 *tea)
485{
486 video_unregister_device(&tea->vd);
487 v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
488}
489EXPORT_SYMBOL_GPL(radio_tea5777_exit);