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