aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/radio
diff options
context:
space:
mode:
authorRichard Röjfors <richard.rojfors@pelagicore.com>2010-02-02 17:40:49 -0500
committerMauro Carvalho Chehab <mchehab@redhat.com>2010-02-26 13:10:52 -0500
commitee4b9dbb83c73c5408d7f479a501551a63ec57d3 (patch)
tree42acf0501526303bcf231a16efea1738aa60905c /drivers/media/radio
parent08c45cd58560f10e08e0ec4686845625b7030e8c (diff)
V4L/DVB: radio: add support for SAA7706H Car Radio DSP
Initial support for the SAA7706H Car Radio DSP. It is a I2C device and currently the mute control is supported. When the device is unmuted it is brought out of reset and initiated using the proposed intialisation sequence. When muted the DSP is brought into reset state. [akpm@linux-foundation.org: include delay.h] Signed-off-by: Richard Röjfors <richard.rojfors@pelagicore.com> Cc: Douglas Schilling Landgraf <dougsland@gmail.com> Cc: Hans Verkuil <hverkuil@xs4all.nl> Cc: Randy Dunlap <randy.dunlap@oracle.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/radio')
-rw-r--r--drivers/media/radio/Kconfig12
-rw-r--r--drivers/media/radio/Makefile1
-rw-r--r--drivers/media/radio/saa7706h.c451
3 files changed, 464 insertions, 0 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
index 3f40f375981b..e5f0d7a8dd0b 100644
--- a/drivers/media/radio/Kconfig
+++ b/drivers/media/radio/Kconfig
@@ -417,6 +417,18 @@ config RADIO_TEA5764_XTAL
417 Say Y here if TEA5764 have a 32768 Hz crystal in circuit, say N 417 Say Y here if TEA5764 have a 32768 Hz crystal in circuit, say N
418 here if TEA5764 reference frequency is connected in FREQIN. 418 here if TEA5764 reference frequency is connected in FREQIN.
419 419
420config RADIO_SAA7706H
421 tristate "SAA7706H Car Radio DSP"
422 depends on I2C && VIDEO_V4L2
423 ---help---
424 Say Y here if you want to use the SAA7706H Car radio Digital
425 Signal Processor, found for instance on the Russellville development
426 board. On the russellville the device is connected to internal
427 timberdale I2C bus.
428
429 To compile this driver as a module, choose M here: the
430 module will be called SAA7706H.
431
420config RADIO_TEF6862 432config RADIO_TEF6862
421 tristate "TEF6862 Car Radio Enhanced Selectivity Tuner" 433 tristate "TEF6862 Car Radio Enhanced Selectivity Tuner"
422 depends on I2C && VIDEO_V4L2 434 depends on I2C && VIDEO_V4L2
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
index 01922ada6914..f681dbfe9ef0 100644
--- a/drivers/media/radio/Makefile
+++ b/drivers/media/radio/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_USB_DSBR) += dsbr100.o
23obj-$(CONFIG_RADIO_SI470X) += si470x/ 23obj-$(CONFIG_RADIO_SI470X) += si470x/
24obj-$(CONFIG_USB_MR800) += radio-mr800.o 24obj-$(CONFIG_USB_MR800) += radio-mr800.o
25obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o 25obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o
26obj-$(CONFIG_RADIO_SAA7706H) += saa7706h.o
26obj-$(CONFIG_RADIO_TEF6862) += tef6862.o 27obj-$(CONFIG_RADIO_TEF6862) += tef6862.o
27 28
28EXTRA_CFLAGS += -Isound 29EXTRA_CFLAGS += -Isound
diff --git a/drivers/media/radio/saa7706h.c b/drivers/media/radio/saa7706h.c
new file mode 100644
index 000000000000..5db5528a8b25
--- /dev/null
+++ b/drivers/media/radio/saa7706h.c
@@ -0,0 +1,451 @@
1/*
2 * saa7706.c Philips SAA7706H Car Radio DSP driver
3 * Copyright (c) 2009 Intel Corporation
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19#include <linux/module.h>
20#include <linux/init.h>
21#include <linux/delay.h>
22#include <linux/errno.h>
23#include <linux/kernel.h>
24#include <linux/interrupt.h>
25#include <linux/i2c.h>
26#include <media/v4l2-device.h>
27#include <media/v4l2-chip-ident.h>
28
29#define DRIVER_NAME "saa7706h"
30
31/* the I2C memory map looks like this
32
33 $1C00 - $FFFF Not Used
34 $2200 - $3FFF Reserved YRAM (DSP2) space
35 $2000 - $21FF YRAM (DSP2)
36 $1FF0 - $1FFF Hardware Registers
37 $1280 - $1FEF Reserved XRAM (DSP2) space
38 $1000 - $127F XRAM (DSP2)
39 $0FFF DSP CONTROL
40 $0A00 - $0FFE Reserved
41 $0980 - $09FF Reserved YRAM (DSP1) space
42 $0800 - $097F YRAM (DSP1)
43 $0200 - $07FF Not Used
44 $0180 - $01FF Reserved XRAM (DSP1) space
45 $0000 - $017F XRAM (DSP1)
46*/
47
48#define SAA7706H_REG_CTRL 0x0fff
49#define SAA7706H_CTRL_BYP_PLL 0x0001
50#define SAA7706H_CTRL_PLL_DIV_MASK 0x003e
51#define SAA7706H_CTRL_PLL3_62975MHZ 0x003e
52#define SAA7706H_CTRL_DSP_TURBO 0x0040
53#define SAA7706H_CTRL_PC_RESET_DSP1 0x0080
54#define SAA7706H_CTRL_PC_RESET_DSP2 0x0100
55#define SAA7706H_CTRL_DSP1_ROM_EN_MASK 0x0600
56#define SAA7706H_CTRL_DSP1_FUNC_PROM 0x0000
57#define SAA7706H_CTRL_DSP2_ROM_EN_MASK 0x1800
58#define SAA7706H_CTRL_DSP2_FUNC_PROM 0x0000
59#define SAA7706H_CTRL_DIG_SIL_INTERPOL 0x8000
60
61#define SAA7706H_REG_EVALUATION 0x1ff0
62#define SAA7706H_EVAL_DISABLE_CHARGE_PUMP 0x000001
63#define SAA7706H_EVAL_DCS_CLOCK 0x000002
64#define SAA7706H_EVAL_GNDRC1_ENABLE 0x000004
65#define SAA7706H_EVAL_GNDRC2_ENABLE 0x000008
66
67#define SAA7706H_REG_CL_GEN1 0x1ff3
68#define SAA7706H_CL_GEN1_MIN_LOOPGAIN_MASK 0x00000f
69#define SAA7706H_CL_GEN1_LOOPGAIN_MASK 0x0000f0
70#define SAA7706H_CL_GEN1_COARSE_RATION 0xffff00
71
72#define SAA7706H_REG_CL_GEN2 0x1ff4
73#define SAA7706H_CL_GEN2_WSEDGE_FALLING 0x000001
74#define SAA7706H_CL_GEN2_STOP_VCO 0x000002
75#define SAA7706H_CL_GEN2_FRERUN 0x000004
76#define SAA7706H_CL_GEN2_ADAPTIVE 0x000008
77#define SAA7706H_CL_GEN2_FINE_RATIO_MASK 0x0ffff0
78
79#define SAA7706H_REG_CL_GEN4 0x1ff6
80#define SAA7706H_CL_GEN4_BYPASS_PLL1 0x001000
81#define SAA7706H_CL_GEN4_PLL1_DIV_MASK 0x03e000
82#define SAA7706H_CL_GEN4_DSP1_TURBO 0x040000
83
84#define SAA7706H_REG_SEL 0x1ff7
85#define SAA7706H_SEL_DSP2_SRCA_MASK 0x000007
86#define SAA7706H_SEL_DSP2_FMTA_MASK 0x000031
87#define SAA7706H_SEL_DSP2_SRCB_MASK 0x0001c0
88#define SAA7706H_SEL_DSP2_FMTB_MASK 0x000e00
89#define SAA7706H_SEL_DSP1_SRC_MASK 0x003000
90#define SAA7706H_SEL_DSP1_FMT_MASK 0x01c003
91#define SAA7706H_SEL_SPDIF2 0x020000
92#define SAA7706H_SEL_HOST_IO_FMT_MASK 0x1c0000
93#define SAA7706H_SEL_EN_HOST_IO 0x200000
94
95#define SAA7706H_REG_IAC 0x1ff8
96#define SAA7706H_REG_CLK_SET 0x1ff9
97#define SAA7706H_REG_CLK_COEFF 0x1ffa
98#define SAA7706H_REG_INPUT_SENS 0x1ffb
99#define SAA7706H_INPUT_SENS_RDS_VOL_MASK 0x0003f
100#define SAA7706H_INPUT_SENS_FM_VOL_MASK 0x00fc0
101#define SAA7706H_INPUT_SENS_FM_MPX 0x01000
102#define SAA7706H_INPUT_SENS_OFF_FILTER_A_EN 0x02000
103#define SAA7706H_INPUT_SENS_OFF_FILTER_B_EN 0x04000
104#define SAA7706H_REG_PHONE_NAV_AUDIO 0x1ffc
105#define SAA7706H_REG_IO_CONF_DSP2 0x1ffd
106#define SAA7706H_REG_STATUS_DSP2 0x1ffe
107#define SAA7706H_REG_PC_DSP2 0x1fff
108
109#define SAA7706H_DSP1_MOD0 0x0800
110#define SAA7706H_DSP1_ROM_VER 0x097f
111#define SAA7706H_DSP2_MPTR0 0x1000
112
113#define SAA7706H_DSP1_MODPNTR 0x0000
114
115#define SAA7706H_DSP2_XMEM_CONTLLCW 0x113e
116#define SAA7706H_DSP2_XMEM_BUSAMP 0x114a
117#define SAA7706H_DSP2_XMEM_FDACPNTR 0x11f9
118#define SAA7706H_DSP2_XMEM_IIS1PNTR 0x11fb
119
120#define SAA7706H_DSP2_YMEM_PVGA 0x212a
121#define SAA7706H_DSP2_YMEM_PVAT1 0x212b
122#define SAA7706H_DSP2_YMEM_PVAT 0x212c
123#define SAA7706H_DSP2_YMEM_ROM_VER 0x21ff
124
125#define SUPPORTED_DSP1_ROM_VER 0x667
126
127struct saa7706h_state {
128 struct v4l2_subdev sd;
129 unsigned muted;
130};
131
132static inline struct saa7706h_state *to_state(struct v4l2_subdev *sd)
133{
134 return container_of(sd, struct saa7706h_state, sd);
135}
136
137static int saa7706h_i2c_send(struct i2c_client *client, const u8 *data, int len)
138{
139 int err = i2c_master_send(client, data, len);
140 if (err == len)
141 return 0;
142 return err > 0 ? -EIO : err;
143}
144
145static int saa7706h_i2c_transfer(struct i2c_client *client,
146 struct i2c_msg *msgs, int num)
147{
148 int err = i2c_transfer(client->adapter, msgs, num);
149 if (err == num)
150 return 0;
151 return err > 0 ? -EIO : err;
152}
153
154static int saa7706h_set_reg24(struct v4l2_subdev *sd, u16 reg, u32 val)
155{
156 struct i2c_client *client = v4l2_get_subdevdata(sd);
157 u8 buf[5];
158 int pos = 0;
159
160 buf[pos++] = reg >> 8;
161 buf[pos++] = reg;
162 buf[pos++] = val >> 16;
163 buf[pos++] = val >> 8;
164 buf[pos++] = val;
165
166 return saa7706h_i2c_send(client, buf, pos);
167}
168
169static int saa7706h_set_reg24_err(struct v4l2_subdev *sd, u16 reg, u32 val,
170 int *err)
171{
172 return *err ? *err : saa7706h_set_reg24(sd, reg, val);
173}
174
175static int saa7706h_set_reg16(struct v4l2_subdev *sd, u16 reg, u16 val)
176{
177 struct i2c_client *client = v4l2_get_subdevdata(sd);
178 u8 buf[4];
179 int pos = 0;
180
181 buf[pos++] = reg >> 8;
182 buf[pos++] = reg;
183 buf[pos++] = val >> 8;
184 buf[pos++] = val;
185
186 return saa7706h_i2c_send(client, buf, pos);
187}
188
189static int saa7706h_set_reg16_err(struct v4l2_subdev *sd, u16 reg, u16 val,
190 int *err)
191{
192 return *err ? *err : saa7706h_set_reg16(sd, reg, val);
193}
194
195static int saa7706h_get_reg16(struct v4l2_subdev *sd, u16 reg)
196{
197 struct i2c_client *client = v4l2_get_subdevdata(sd);
198 u8 buf[2];
199 int err;
200 u8 regaddr[] = {reg >> 8, reg};
201 struct i2c_msg msg[] = { {client->addr, 0, sizeof(regaddr), regaddr},
202 {client->addr, I2C_M_RD, sizeof(buf), buf} };
203
204 err = saa7706h_i2c_transfer(client, msg, ARRAY_SIZE(msg));
205 if (err)
206 return err;
207
208 return buf[0] << 8 | buf[1];
209}
210
211static int saa7706h_unmute(struct v4l2_subdev *sd)
212{
213 struct saa7706h_state *state = to_state(sd);
214 int err = 0;
215
216 err = saa7706h_set_reg16_err(sd, SAA7706H_REG_CTRL,
217 SAA7706H_CTRL_PLL3_62975MHZ | SAA7706H_CTRL_PC_RESET_DSP1 |
218 SAA7706H_CTRL_PC_RESET_DSP2, &err);
219
220 /* newer versions of the chip requires a small sleep after reset */
221 msleep(1);
222
223 err = saa7706h_set_reg16_err(sd, SAA7706H_REG_CTRL,
224 SAA7706H_CTRL_PLL3_62975MHZ, &err);
225
226 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_EVALUATION, 0, &err);
227
228 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN1, 0x040022, &err);
229
230 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN2,
231 SAA7706H_CL_GEN2_WSEDGE_FALLING, &err);
232
233 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN4, 0x024080, &err);
234
235 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_SEL, 0x200080, &err);
236
237 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_IAC, 0xf4caed, &err);
238
239 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CLK_SET, 0x124334, &err);
240
241 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CLK_COEFF, 0x004a1a,
242 &err);
243
244 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_INPUT_SENS, 0x0071c7,
245 &err);
246
247 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_PHONE_NAV_AUDIO,
248 0x0e22ff, &err);
249
250 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_IO_CONF_DSP2, 0x001ff8,
251 &err);
252
253 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_STATUS_DSP2, 0x080003,
254 &err);
255
256 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_PC_DSP2, 0x000004, &err);
257
258 err = saa7706h_set_reg16_err(sd, SAA7706H_DSP1_MOD0, 0x0c6c, &err);
259
260 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_MPTR0, 0x000b4b, &err);
261
262 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP1_MODPNTR, 0x000600, &err);
263
264 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP1_MODPNTR, 0x0000c0, &err);
265
266 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x000819,
267 &err);
268
269 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x00085a,
270 &err);
271
272 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_BUSAMP, 0x7fffff,
273 &err);
274
275 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_FDACPNTR, 0x2000cb,
276 &err);
277
278 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_IIS1PNTR, 0x2000cb,
279 &err);
280
281 err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVGA, 0x0f80, &err);
282
283 err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVAT1, 0x0800,
284 &err);
285
286 err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVAT, 0x0800, &err);
287
288 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x000905,
289 &err);
290 if (!err)
291 state->muted = 0;
292 return err;
293}
294
295static int saa7706h_mute(struct v4l2_subdev *sd)
296{
297 struct saa7706h_state *state = to_state(sd);
298 int err;
299
300 err = saa7706h_set_reg16(sd, SAA7706H_REG_CTRL,
301 SAA7706H_CTRL_PLL3_62975MHZ | SAA7706H_CTRL_PC_RESET_DSP1 |
302 SAA7706H_CTRL_PC_RESET_DSP2);
303 if (!err)
304 state->muted = 1;
305 return err;
306}
307
308static int saa7706h_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc)
309{
310 switch (qc->id) {
311 case V4L2_CID_AUDIO_MUTE:
312 return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
313 }
314 return -EINVAL;
315}
316
317static int saa7706h_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
318{
319 struct saa7706h_state *state = to_state(sd);
320
321 switch (ctrl->id) {
322 case V4L2_CID_AUDIO_MUTE:
323 ctrl->value = state->muted;
324 return 0;
325 }
326 return -EINVAL;
327}
328
329static int saa7706h_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
330{
331 switch (ctrl->id) {
332 case V4L2_CID_AUDIO_MUTE:
333 if (ctrl->value)
334 return saa7706h_mute(sd);
335 return saa7706h_unmute(sd);
336 }
337 return -EINVAL;
338}
339
340static int saa7706h_g_chip_ident(struct v4l2_subdev *sd,
341 struct v4l2_dbg_chip_ident *chip)
342{
343 struct i2c_client *client = v4l2_get_subdevdata(sd);
344
345 return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_SAA7706H, 0);
346}
347
348static const struct v4l2_subdev_core_ops saa7706h_core_ops = {
349 .g_chip_ident = saa7706h_g_chip_ident,
350 .queryctrl = saa7706h_queryctrl,
351 .g_ctrl = saa7706h_g_ctrl,
352 .s_ctrl = saa7706h_s_ctrl,
353};
354
355static const struct v4l2_subdev_ops saa7706h_ops = {
356 .core = &saa7706h_core_ops,
357};
358
359/*
360 * Generic i2c probe
361 * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1'
362 */
363
364static int __devinit saa7706h_probe(struct i2c_client *client,
365 const struct i2c_device_id *id)
366{
367 struct saa7706h_state *state;
368 struct v4l2_subdev *sd;
369 int err;
370
371 /* Check if the adapter supports the needed features */
372 if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
373 return -EIO;
374
375 v4l_info(client, "chip found @ 0x%02x (%s)\n",
376 client->addr << 1, client->adapter->name);
377
378 state = kmalloc(sizeof(struct saa7706h_state), GFP_KERNEL);
379 if (state == NULL)
380 return -ENOMEM;
381 sd = &state->sd;
382 v4l2_i2c_subdev_init(sd, client, &saa7706h_ops);
383
384 /* check the rom versions */
385 err = saa7706h_get_reg16(sd, SAA7706H_DSP1_ROM_VER);
386 if (err < 0)
387 goto err;
388 if (err != SUPPORTED_DSP1_ROM_VER)
389 v4l2_warn(sd, "Unknown DSP1 ROM code version: 0x%x\n", err);
390
391 state->muted = 1;
392
393 /* startup in a muted state */
394 err = saa7706h_mute(sd);
395 if (err)
396 goto err;
397
398 return 0;
399
400err:
401 v4l2_device_unregister_subdev(sd);
402 kfree(to_state(sd));
403
404 printk(KERN_ERR DRIVER_NAME ": Failed to probe: %d\n", err);
405
406 return err;
407}
408
409static int __devexit saa7706h_remove(struct i2c_client *client)
410{
411 struct v4l2_subdev *sd = i2c_get_clientdata(client);
412
413 saa7706h_mute(sd);
414 v4l2_device_unregister_subdev(sd);
415 kfree(to_state(sd));
416 return 0;
417}
418
419static const struct i2c_device_id saa7706h_id[] = {
420 {DRIVER_NAME, 0},
421 {},
422};
423
424MODULE_DEVICE_TABLE(i2c, saa7706h_id);
425
426static struct i2c_driver saa7706h_driver = {
427 .driver = {
428 .owner = THIS_MODULE,
429 .name = DRIVER_NAME,
430 },
431 .probe = saa7706h_probe,
432 .remove = saa7706h_remove,
433 .id_table = saa7706h_id,
434};
435
436static __init int saa7706h_init(void)
437{
438 return i2c_add_driver(&saa7706h_driver);
439}
440
441static __exit void saa7706h_exit(void)
442{
443 i2c_del_driver(&saa7706h_driver);
444}
445
446module_init(saa7706h_init);
447module_exit(saa7706h_exit);
448
449MODULE_DESCRIPTION("SAA7706H Car Radio DSP driver");
450MODULE_AUTHOR("Mocean Laboratories");
451MODULE_LICENSE("GPL v2");