diff options
author | Richard Röjfors <richard.rojfors@mocean-labs.com> | 2009-09-22 09:14:39 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2009-12-05 15:40:43 -0500 |
commit | eea85b0a629970d462481a80e1d45f4d71fe797f (patch) | |
tree | 6b305c5fb254f6afc623094b07a501df2e305805 /drivers/media/radio/tef6862.c | |
parent | 42752f7a3f4afbabb513d5769c590e9abe2d0cd6 (diff) |
V4L/DVB (13177): radio: Add support for TEF6862 tuner
This patch adds support for TEF6862 Car Radio Enhanced Selectivity Tuner.
It's implemented as a subdev, supporting checking signal strength
and setting and getting frequency.
Signed-off-by: Richard Röjfors <richard.rojfors@mocean-labs.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/radio/tef6862.c')
-rw-r--r-- | drivers/media/radio/tef6862.c | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/drivers/media/radio/tef6862.c b/drivers/media/radio/tef6862.c new file mode 100644 index 000000000000..6e607ff0c169 --- /dev/null +++ b/drivers/media/radio/tef6862.c | |||
@@ -0,0 +1,232 @@ | |||
1 | /* | ||
2 | * tef6862.c Philips TEF6862 Car Radio Enhanced Selectivity Tuner | ||
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/errno.h> | ||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/interrupt.h> | ||
24 | #include <linux/i2c.h> | ||
25 | #include <linux/i2c-id.h> | ||
26 | #include <media/v4l2-ioctl.h> | ||
27 | #include <media/v4l2-device.h> | ||
28 | #include <media/v4l2-chip-ident.h> | ||
29 | |||
30 | #define DRIVER_NAME "tef6862" | ||
31 | |||
32 | #define FREQ_MUL 16000 | ||
33 | |||
34 | #define TEF6862_LO_FREQ (875 * FREQ_MUL / 10) | ||
35 | #define TEF6862_HI_FREQ (108 * FREQ_MUL) | ||
36 | |||
37 | /* Write mode sub addresses */ | ||
38 | #define WM_SUB_BANDWIDTH 0x0 | ||
39 | #define WM_SUB_PLLM 0x1 | ||
40 | #define WM_SUB_PLLL 0x2 | ||
41 | #define WM_SUB_DAA 0x3 | ||
42 | #define WM_SUB_AGC 0x4 | ||
43 | #define WM_SUB_BAND 0x5 | ||
44 | #define WM_SUB_CONTROL 0x6 | ||
45 | #define WM_SUB_LEVEL 0x7 | ||
46 | #define WM_SUB_IFCF 0x8 | ||
47 | #define WM_SUB_IFCAP 0x9 | ||
48 | #define WM_SUB_ACD 0xA | ||
49 | #define WM_SUB_TEST 0xF | ||
50 | |||
51 | /* Different modes of the MSA register */ | ||
52 | #define MODE_BUFFER 0x0 | ||
53 | #define MODE_PRESET 0x1 | ||
54 | #define MODE_SEARCH 0x2 | ||
55 | #define MODE_AF_UPDATE 0x3 | ||
56 | #define MODE_JUMP 0x4 | ||
57 | #define MODE_CHECK 0x5 | ||
58 | #define MODE_LOAD 0x6 | ||
59 | #define MODE_END 0x7 | ||
60 | #define MODE_SHIFT 5 | ||
61 | |||
62 | struct tef6862_state { | ||
63 | struct v4l2_subdev sd; | ||
64 | unsigned long freq; | ||
65 | }; | ||
66 | |||
67 | static inline struct tef6862_state *to_state(struct v4l2_subdev *sd) | ||
68 | { | ||
69 | return container_of(sd, struct tef6862_state, sd); | ||
70 | } | ||
71 | |||
72 | static u16 tef6862_sigstr(struct i2c_client *client) | ||
73 | { | ||
74 | u8 buf[4]; | ||
75 | int err = i2c_master_recv(client, buf, sizeof(buf)); | ||
76 | if (err == sizeof(buf)) | ||
77 | return buf[3] << 8; | ||
78 | return 0; | ||
79 | } | ||
80 | |||
81 | static int tef6862_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v) | ||
82 | { | ||
83 | if (v->index > 0) | ||
84 | return -EINVAL; | ||
85 | |||
86 | /* only support FM for now */ | ||
87 | strlcpy(v->name, "FM", sizeof(v->name)); | ||
88 | v->type = V4L2_TUNER_RADIO; | ||
89 | v->rangelow = TEF6862_LO_FREQ; | ||
90 | v->rangehigh = TEF6862_HI_FREQ; | ||
91 | v->rxsubchans = V4L2_TUNER_SUB_MONO; | ||
92 | v->capability = V4L2_TUNER_CAP_LOW; | ||
93 | v->audmode = V4L2_TUNER_MODE_STEREO; | ||
94 | v->signal = tef6862_sigstr(v4l2_get_subdevdata(sd)); | ||
95 | |||
96 | return 0; | ||
97 | } | ||
98 | |||
99 | static int tef6862_s_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v) | ||
100 | { | ||
101 | return v->index ? -EINVAL : 0; | ||
102 | } | ||
103 | |||
104 | static int tef6862_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) | ||
105 | { | ||
106 | struct tef6862_state *state = to_state(sd); | ||
107 | struct i2c_client *client = v4l2_get_subdevdata(sd); | ||
108 | u16 pll; | ||
109 | u8 i2cmsg[3]; | ||
110 | int err; | ||
111 | |||
112 | if (f->tuner != 0) | ||
113 | return -EINVAL; | ||
114 | |||
115 | pll = 1964 + ((f->frequency - TEF6862_LO_FREQ) * 20) / FREQ_MUL; | ||
116 | i2cmsg[0] = (MODE_PRESET << MODE_SHIFT) | WM_SUB_PLLM; | ||
117 | i2cmsg[1] = (pll >> 8) & 0xff; | ||
118 | i2cmsg[2] = pll & 0xff; | ||
119 | |||
120 | err = i2c_master_send(client, i2cmsg, sizeof(i2cmsg)); | ||
121 | if (!err) | ||
122 | state->freq = f->frequency; | ||
123 | return err; | ||
124 | } | ||
125 | |||
126 | static int tef6862_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) | ||
127 | { | ||
128 | struct tef6862_state *state = to_state(sd); | ||
129 | |||
130 | if (f->tuner != 0) | ||
131 | return -EINVAL; | ||
132 | f->type = V4L2_TUNER_RADIO; | ||
133 | f->frequency = state->freq; | ||
134 | return 0; | ||
135 | } | ||
136 | |||
137 | static int tef6862_g_chip_ident(struct v4l2_subdev *sd, | ||
138 | struct v4l2_dbg_chip_ident *chip) | ||
139 | { | ||
140 | struct i2c_client *client = v4l2_get_subdevdata(sd); | ||
141 | |||
142 | return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_TEF6862, 0); | ||
143 | } | ||
144 | |||
145 | static const struct v4l2_subdev_tuner_ops tef6862_tuner_ops = { | ||
146 | .g_tuner = tef6862_g_tuner, | ||
147 | .s_tuner = tef6862_s_tuner, | ||
148 | .s_frequency = tef6862_s_frequency, | ||
149 | .g_frequency = tef6862_g_frequency, | ||
150 | }; | ||
151 | |||
152 | static const struct v4l2_subdev_core_ops tef6862_core_ops = { | ||
153 | .g_chip_ident = tef6862_g_chip_ident, | ||
154 | }; | ||
155 | |||
156 | static const struct v4l2_subdev_ops tef6862_ops = { | ||
157 | .core = &tef6862_core_ops, | ||
158 | .tuner = &tef6862_tuner_ops, | ||
159 | }; | ||
160 | |||
161 | /* | ||
162 | * Generic i2c probe | ||
163 | * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' | ||
164 | */ | ||
165 | |||
166 | static int __devinit tef6862_probe(struct i2c_client *client, | ||
167 | const struct i2c_device_id *id) | ||
168 | { | ||
169 | struct tef6862_state *state; | ||
170 | struct v4l2_subdev *sd; | ||
171 | |||
172 | /* Check if the adapter supports the needed features */ | ||
173 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) | ||
174 | return -EIO; | ||
175 | |||
176 | v4l_info(client, "chip found @ 0x%02x (%s)\n", | ||
177 | client->addr << 1, client->adapter->name); | ||
178 | |||
179 | state = kmalloc(sizeof(struct tef6862_state), GFP_KERNEL); | ||
180 | if (state == NULL) | ||
181 | return -ENOMEM; | ||
182 | state->freq = TEF6862_LO_FREQ; | ||
183 | |||
184 | sd = &state->sd; | ||
185 | v4l2_i2c_subdev_init(sd, client, &tef6862_ops); | ||
186 | |||
187 | return 0; | ||
188 | } | ||
189 | |||
190 | static int __devexit tef6862_remove(struct i2c_client *client) | ||
191 | { | ||
192 | struct v4l2_subdev *sd = i2c_get_clientdata(client); | ||
193 | |||
194 | v4l2_device_unregister_subdev(sd); | ||
195 | kfree(to_state(sd)); | ||
196 | return 0; | ||
197 | } | ||
198 | |||
199 | static const struct i2c_device_id tef6862_id[] = { | ||
200 | {DRIVER_NAME, 0}, | ||
201 | {}, | ||
202 | }; | ||
203 | |||
204 | MODULE_DEVICE_TABLE(i2c, tef6862_id); | ||
205 | |||
206 | static struct i2c_driver tef6862_driver = { | ||
207 | .driver = { | ||
208 | .owner = THIS_MODULE, | ||
209 | .name = DRIVER_NAME, | ||
210 | }, | ||
211 | .probe = tef6862_probe, | ||
212 | .remove = tef6862_remove, | ||
213 | .id_table = tef6862_id, | ||
214 | }; | ||
215 | |||
216 | static __init int tef6862_init(void) | ||
217 | { | ||
218 | return i2c_add_driver(&tef6862_driver); | ||
219 | } | ||
220 | |||
221 | static __exit void tef6862_exit(void) | ||
222 | { | ||
223 | i2c_del_driver(&tef6862_driver); | ||
224 | } | ||
225 | |||
226 | module_init(tef6862_init); | ||
227 | module_exit(tef6862_exit); | ||
228 | |||
229 | MODULE_DESCRIPTION("TEF6862 Car Radio Enhanced Selectivity Tuner"); | ||
230 | MODULE_AUTHOR("Mocean Laboratories"); | ||
231 | MODULE_LICENSE("GPL v2"); | ||
232 | |||