]>
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> | |
5a0e3ad6 | 25 | #include <linux/slab.h> |
eea85b0a RR |
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)); | |
bd93b3ad AL |
121 | if (err != sizeof(i2cmsg)) |
122 | return err < 0 ? err : -EIO; | |
123 | ||
124 | state->freq = f->frequency; | |
125 | return 0; | |
eea85b0a RR |
126 | } |
127 | ||
128 | static int tef6862_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) | |
129 | { | |
130 | struct tef6862_state *state = to_state(sd); | |
131 | ||
132 | if (f->tuner != 0) | |
133 | return -EINVAL; | |
134 | f->type = V4L2_TUNER_RADIO; | |
135 | f->frequency = state->freq; | |
136 | return 0; | |
137 | } | |
138 | ||
139 | static int tef6862_g_chip_ident(struct v4l2_subdev *sd, | |
140 | struct v4l2_dbg_chip_ident *chip) | |
141 | { | |
142 | struct i2c_client *client = v4l2_get_subdevdata(sd); | |
143 | ||
144 | return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_TEF6862, 0); | |
145 | } | |
146 | ||
147 | static const struct v4l2_subdev_tuner_ops tef6862_tuner_ops = { | |
148 | .g_tuner = tef6862_g_tuner, | |
149 | .s_tuner = tef6862_s_tuner, | |
150 | .s_frequency = tef6862_s_frequency, | |
151 | .g_frequency = tef6862_g_frequency, | |
152 | }; | |
153 | ||
154 | static const struct v4l2_subdev_core_ops tef6862_core_ops = { | |
155 | .g_chip_ident = tef6862_g_chip_ident, | |
156 | }; | |
157 | ||
158 | static const struct v4l2_subdev_ops tef6862_ops = { | |
159 | .core = &tef6862_core_ops, | |
160 | .tuner = &tef6862_tuner_ops, | |
161 | }; | |
162 | ||
163 | /* | |
164 | * Generic i2c probe | |
165 | * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' | |
166 | */ | |
167 | ||
168 | static int __devinit tef6862_probe(struct i2c_client *client, | |
169 | const struct i2c_device_id *id) | |
170 | { | |
171 | struct tef6862_state *state; | |
172 | struct v4l2_subdev *sd; | |
173 | ||
174 | /* Check if the adapter supports the needed features */ | |
175 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) | |
176 | return -EIO; | |
177 | ||
178 | v4l_info(client, "chip found @ 0x%02x (%s)\n", | |
179 | client->addr << 1, client->adapter->name); | |
180 | ||
80845a33 | 181 | state = kzalloc(sizeof(struct tef6862_state), GFP_KERNEL); |
eea85b0a RR |
182 | if (state == NULL) |
183 | return -ENOMEM; | |
184 | state->freq = TEF6862_LO_FREQ; | |
185 | ||
186 | sd = &state->sd; | |
187 | v4l2_i2c_subdev_init(sd, client, &tef6862_ops); | |
188 | ||
189 | return 0; | |
190 | } | |
191 | ||
192 | static int __devexit tef6862_remove(struct i2c_client *client) | |
193 | { | |
194 | struct v4l2_subdev *sd = i2c_get_clientdata(client); | |
195 | ||
196 | v4l2_device_unregister_subdev(sd); | |
197 | kfree(to_state(sd)); | |
198 | return 0; | |
199 | } | |
200 | ||
201 | static const struct i2c_device_id tef6862_id[] = { | |
202 | {DRIVER_NAME, 0}, | |
203 | {}, | |
204 | }; | |
205 | ||
206 | MODULE_DEVICE_TABLE(i2c, tef6862_id); | |
207 | ||
208 | static struct i2c_driver tef6862_driver = { | |
209 | .driver = { | |
210 | .owner = THIS_MODULE, | |
211 | .name = DRIVER_NAME, | |
212 | }, | |
213 | .probe = tef6862_probe, | |
cd624c7b | 214 | .remove = __devexit_p(tef6862_remove), |
eea85b0a RR |
215 | .id_table = tef6862_id, |
216 | }; | |
217 | ||
c6e8d86f | 218 | module_i2c_driver(tef6862_driver); |
eea85b0a RR |
219 | |
220 | MODULE_DESCRIPTION("TEF6862 Car Radio Enhanced Selectivity Tuner"); | |
221 | MODULE_AUTHOR("Mocean Laboratories"); | |
222 | MODULE_LICENSE("GPL v2"); |