Root/
Source at commit ed96d8b8595461bb55ca0643934fffba04f540b6 created 12 years 5 months ago. By Maarten ter Huurne, media: radio: RDA5807: Implemented VIDIOC_G_FREQUENCY ioctl. | |
---|---|
1 | /* |
2 | * radio-rda5807.c - Driver for using the RDA5807 FM tuner chip via I2C |
3 | * |
4 | * Copyright (c) 2011 Maarten ter Huurne <maarten@treewalker.org> |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License version 2 as |
8 | * published by the Free Software Foundation. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License |
16 | * along with this program; if not, write to the Free Software |
17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
18 | * |
19 | * |
20 | * Many thanks to Jérôme VERES for his command line radio application that |
21 | * demonstrates how the chip can be controlled via I2C. |
22 | * |
23 | * The RDA5807 has three ways of accessing registers: |
24 | * - I2C address 0x10: sequential access, RDA5800 style |
25 | * - I2C address 0x11: random access |
26 | * - I2C address 0x60: sequential access, TEA5767 compatible |
27 | * This driver uses random access and therefore the i2c_board_info should |
28 | * specify address 0x11 using the macro RDA5807_I2C_ADDR. |
29 | * Note that while there are many similarities, the register map of the RDA5807 |
30 | * differs from that of the RDA5800 in several essential places. |
31 | */ |
32 | |
33 | |
34 | #include <asm/byteorder.h> |
35 | #include <linux/bitops.h> |
36 | #include <linux/module.h> |
37 | #include <linux/i2c.h> |
38 | #include <linux/kernel.h> |
39 | #include <linux/pm.h> |
40 | #include <linux/slab.h> |
41 | #include <linux/types.h> |
42 | #include <linux/videodev2.h> |
43 | #include <media/radio-rda5807.h> |
44 | #include <media/v4l2-ctrls.h> |
45 | #include <media/v4l2-dev.h> |
46 | #include <media/v4l2-ioctl.h> |
47 | |
48 | |
49 | enum rda5807_reg { |
50 | RDA5807_REG_CHIPID = 0x00, |
51 | RDA5807_REG_CTRL = 0x02, |
52 | RDA5807_REG_CHAN = 0x03, |
53 | RDA5807_REG_IOCFG = 0x04, |
54 | RDA5807_REG_INTM_THRESH_VOL = 0x05, |
55 | RDA5807_REG_SEEK_RESULT = 0x0A, |
56 | RDA5807_REG_SIGNAL = 0x0B, |
57 | }; |
58 | |
59 | #define RDA5807_MASK_CTRL_DHIZ BIT(15) |
60 | #define RDA5807_MASK_CTRL_DMUTE BIT(14) |
61 | #define RDA5807_MASK_CTRL_MONO BIT(13) |
62 | #define RDA5807_MASK_CTRL_BASS BIT(12) |
63 | #define RDA5807_MASK_CTRL_SEEKUP BIT(9) |
64 | #define RDA5807_MASK_CTRL_SEEK BIT(8) |
65 | #define RDA5807_MASK_CTRL_SKMODE BIT(7) |
66 | #define RDA5807_MASK_CTRL_CLKMODE (7 << 4) |
67 | #define RDA5807_MASK_CTRL_SOFTRESET BIT(1) |
68 | #define RDA5807_MASK_CTRL_ENABLE BIT(0) |
69 | |
70 | #define RDA5807_SHIFT_CHAN_WRCHAN 6 |
71 | #define RDA5807_MASK_CHAN_WRCHAN (0x3FF << RDA5807_SHIFT_CHAN_WRCHAN) |
72 | #define RDA5807_MASK_CHAN_TUNE BIT(4) |
73 | #define RDA5807_SHIFT_CHAN_BAND 2 |
74 | #define RDA5807_MASK_CHAN_BAND (0x3 << RDA5807_SHIFT_CHAN_BAND) |
75 | #define RDA5807_SHIFT_CHAN_SPACE 0 |
76 | #define RDA5807_MASK_CHAN_SPACE (0x3 << RDA5807_SHIFT_CHAN_SPACE) |
77 | |
78 | #define RDA5807_MASK_SEEKRES_COMPLETE BIT(14) |
79 | #define RDA5807_MASK_SEEKRES_FAIL BIT(13) |
80 | #define RDA5807_MASK_SEEKRES_STEREO BIT(10) |
81 | #define RDA5807_MASK_SEEKRES_READCHAN 0x3FF |
82 | |
83 | #define RDA5807_MASK_DEEMPHASIS BIT(11) |
84 | |
85 | #define RDA5807_SHIFT_VOLUME_DAC 0 |
86 | #define RDA5807_MASK_VOLUME_DAC (0xF << RDA5807_SHIFT_VOLUME_DAC) |
87 | |
88 | #define RDA5807_SHIFT_RSSI 9 |
89 | #define RDA5807_MASK_RSSI (0x7F << RDA5807_SHIFT_RSSI) |
90 | |
91 | #define RDA5807_FREQ_MIN_KHZ 76000 |
92 | #define RDA5807_FREQ_MAX_KHZ 108000 |
93 | |
94 | static int rda5807_i2c_read(struct i2c_client *client, enum rda5807_reg reg) |
95 | { |
96 | __u8 reg_buf = reg; |
97 | __u16 val_buf; |
98 | struct i2c_msg msgs[] = { |
99 | { /* write register number */ |
100 | .addr = client->addr, |
101 | .flags = 0, |
102 | .len = sizeof(reg_buf), |
103 | .buf = ®_buf, |
104 | }, |
105 | { /* read register contents */ |
106 | .addr = client->addr, |
107 | .flags = I2C_M_RD, |
108 | .len = sizeof(val_buf), |
109 | .buf = (__u8 *)&val_buf, |
110 | }, |
111 | }; |
112 | int err; |
113 | |
114 | err = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); |
115 | if (err < 0) return err; |
116 | if (err < ARRAY_SIZE(msgs)) return -EIO; |
117 | |
118 | dev_info(&client->dev, "reg[%02X] = %04X\n", reg, be16_to_cpu(val_buf)); |
119 | return be16_to_cpu(val_buf); |
120 | } |
121 | |
122 | static int rda5807_i2c_write(struct i2c_client *client, enum rda5807_reg reg, |
123 | u16 val) |
124 | { |
125 | __u8 buf[] = { reg, val >> 8, val & 0xFF }; |
126 | struct i2c_msg msgs[] = { |
127 | { /* write register number and contents */ |
128 | .addr = client->addr, |
129 | .flags = 0, |
130 | .len = sizeof(buf), |
131 | .buf = buf, |
132 | }, |
133 | }; |
134 | int err; |
135 | |
136 | err = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); |
137 | if (err < 0) return err; |
138 | if (err < ARRAY_SIZE(msgs)) return -EIO; |
139 | |
140 | dev_info(&client->dev, "reg[%02X] := %04X\n", reg, val); |
141 | return 0; |
142 | } |
143 | |
144 | struct rda5807_driver { |
145 | struct v4l2_ctrl_handler ctrl_handler; |
146 | struct video_device video_dev; |
147 | struct i2c_client *i2c_client; |
148 | }; |
149 | |
150 | static const struct v4l2_file_operations rda5807_fops = { |
151 | .owner = THIS_MODULE, |
152 | .unlocked_ioctl = video_ioctl2, |
153 | }; |
154 | |
155 | static int rda5807_update_reg(struct rda5807_driver *radio, |
156 | enum rda5807_reg reg, u16 mask, u16 val) |
157 | { |
158 | int err = 0; |
159 | // TODO: Locking. |
160 | // Or do locking in the caller, in case we ever need to update |
161 | // two registers in one operation? |
162 | err = rda5807_i2c_read(radio->i2c_client, reg); |
163 | if (err >= 0) { |
164 | val |= ((u16)err & ~mask); |
165 | err = rda5807_i2c_write(radio->i2c_client, reg, val); |
166 | } |
167 | return err; |
168 | } |
169 | |
170 | static int rda5807_set_enable(struct rda5807_driver *radio, int enabled) |
171 | { |
172 | u16 val = enabled ? RDA5807_MASK_CTRL_ENABLE : 0; |
173 | int err; |
174 | dev_info(&radio->i2c_client->dev, "set enabled to %d\n", enabled); |
175 | err = rda5807_update_reg(radio, RDA5807_REG_CTRL, |
176 | RDA5807_MASK_CTRL_ENABLE, val); |
177 | if (err < 0) |
178 | return err; |
179 | /* Tuning is lost when the chip is disabled, so re-tune when enabled. */ |
180 | if (enabled) |
181 | return rda5807_update_reg(radio, RDA5807_REG_CHAN, |
182 | RDA5807_MASK_CHAN_TUNE, |
183 | RDA5807_MASK_CHAN_TUNE); |
184 | else |
185 | return 0; |
186 | } |
187 | |
188 | static int rda5807_set_mute(struct rda5807_driver *radio, int muted) |
189 | { |
190 | u16 val = muted ? 0 : RDA5807_MASK_CTRL_DMUTE /* disable mute */; |
191 | dev_info(&radio->i2c_client->dev, "set mute to %d\n", muted); |
192 | return rda5807_update_reg(radio, RDA5807_REG_CTRL, |
193 | RDA5807_MASK_CTRL_DMUTE, val); |
194 | } |
195 | |
196 | static int rda5807_set_volume(struct rda5807_driver *radio, int volume) |
197 | { |
198 | dev_info(&radio->i2c_client->dev, "set volume to %d\n", volume); |
199 | return rda5807_update_reg(radio, RDA5807_REG_INTM_THRESH_VOL, |
200 | RDA5807_MASK_VOLUME_DAC, |
201 | volume << RDA5807_SHIFT_VOLUME_DAC); |
202 | } |
203 | |
204 | static int rda5807_set_preemphasis(struct rda5807_driver *radio, |
205 | enum v4l2_preemphasis preemp) |
206 | { |
207 | dev_info(&radio->i2c_client->dev, "set preemphasis to %d\n", preemp); |
208 | return rda5807_update_reg(radio, RDA5807_REG_IOCFG, |
209 | RDA5807_MASK_DEEMPHASIS, |
210 | preemp == V4L2_PREEMPHASIS_50_uS |
211 | ? RDA5807_MASK_DEEMPHASIS : 0); |
212 | } |
213 | |
214 | static int rda5807_get_frequency(struct rda5807_driver *radio) |
215 | { |
216 | u32 freq_khz; |
217 | u16 val; |
218 | int err; |
219 | |
220 | err = rda5807_i2c_read(radio->i2c_client, RDA5807_REG_SEEK_RESULT); |
221 | if (err < 0) |
222 | return err; |
223 | val = err; |
224 | |
225 | freq_khz = 50 * (val & RDA5807_MASK_SEEKRES_READCHAN) |
226 | + RDA5807_FREQ_MIN_KHZ; |
227 | dev_info(&radio->i2c_client->dev, "get freq of %u kHz\n", freq_khz); |
228 | return freq_khz; |
229 | } |
230 | |
231 | static int rda5807_set_frequency(struct rda5807_driver *radio, u32 freq_khz) |
232 | { |
233 | u16 mask = 0; |
234 | u16 val = 0; |
235 | |
236 | dev_info(&radio->i2c_client->dev, "set freq to %u kHz\n", freq_khz); |
237 | |
238 | if (freq_khz < RDA5807_FREQ_MIN_KHZ) |
239 | return -ERANGE; |
240 | if (freq_khz > RDA5807_FREQ_MAX_KHZ) |
241 | return -ERANGE; |
242 | |
243 | /* select widest band */ |
244 | mask |= RDA5807_MASK_CHAN_BAND; |
245 | val |= 2 << RDA5807_SHIFT_CHAN_BAND; |
246 | /* select 50 kHz channel spacing */ |
247 | mask |= RDA5807_MASK_CHAN_SPACE; |
248 | val |= 2 << RDA5807_SHIFT_CHAN_SPACE; |
249 | /* select frequency */ |
250 | mask |= RDA5807_MASK_CHAN_WRCHAN; |
251 | val |= ((freq_khz - RDA5807_FREQ_MIN_KHZ + 25) / 50) |
252 | << RDA5807_SHIFT_CHAN_WRCHAN; |
253 | /* start tune operation */ |
254 | mask |= RDA5807_MASK_CHAN_TUNE; |
255 | val |= RDA5807_MASK_CHAN_TUNE; |
256 | |
257 | return rda5807_update_reg(radio, RDA5807_REG_CHAN, mask, val); |
258 | } |
259 | |
260 | static inline struct rda5807_driver *ctrl_to_radio(struct v4l2_ctrl *ctrl) |
261 | { |
262 | return container_of(ctrl->handler, struct rda5807_driver, ctrl_handler); |
263 | } |
264 | |
265 | static int rda5807_s_ctrl(struct v4l2_ctrl *ctrl) |
266 | { |
267 | struct rda5807_driver *radio = ctrl_to_radio(ctrl); |
268 | |
269 | switch (ctrl->id) { |
270 | case V4L2_CID_AUDIO_MUTE: { |
271 | /* Disable the radio while muted, to save power. |
272 | * TODO: We can't seek while the radio is disabled; |
273 | * is that a problem? |
274 | */ |
275 | int err1 = rda5807_set_enable(radio, !ctrl->val); |
276 | int err2 = rda5807_set_mute(radio, ctrl->val); |
277 | return err1 ? err1 : err2; |
278 | } |
279 | case V4L2_CID_AUDIO_VOLUME: |
280 | return rda5807_set_volume(radio, ctrl->val); |
281 | case V4L2_CID_TUNE_PREEMPHASIS: |
282 | return rda5807_set_preemphasis(radio, ctrl->val); |
283 | default: |
284 | return -EINVAL; |
285 | } |
286 | } |
287 | |
288 | static const struct v4l2_ctrl_ops rda5807_ctrl_ops = { |
289 | .s_ctrl = rda5807_s_ctrl, |
290 | }; |
291 | |
292 | static int rda5807_vidioc_querycap(struct file *file, void *fh, |
293 | struct v4l2_capability *cap) |
294 | { |
295 | *cap = (struct v4l2_capability) { |
296 | .driver = "rda5807", |
297 | .card = "RDA5807 FM receiver", |
298 | .bus_info = "I2C", |
299 | .capabilities = V4L2_CAP_RADIO | V4L2_CAP_TUNER, |
300 | }; |
301 | |
302 | return 0; |
303 | } |
304 | |
305 | static int rda5807_vidioc_g_audio(struct file *file, void *fh, |
306 | struct v4l2_audio *a) |
307 | { |
308 | if (a->index != 0) |
309 | return -EINVAL; |
310 | |
311 | *a = (struct v4l2_audio) { |
312 | .name = "Radio", |
313 | .capability = V4L2_AUDCAP_STEREO, |
314 | .mode = 0, |
315 | }; |
316 | |
317 | return 0; |
318 | } |
319 | |
320 | static int rda5807_vidioc_g_tuner(struct file *file, void *fh, |
321 | struct v4l2_tuner *a) |
322 | { |
323 | struct rda5807_driver *radio = video_drvdata(file); |
324 | int err; |
325 | u16 seekres, signal; |
326 | __u32 rxsubchans; |
327 | |
328 | if (a->index != 0) |
329 | return -EINVAL; |
330 | |
331 | err = rda5807_i2c_read(radio->i2c_client, RDA5807_REG_SEEK_RESULT); |
332 | if (err < 0) |
333 | return err; |
334 | seekres = (u16)err; |
335 | if ((seekres & (RDA5807_MASK_SEEKRES_COMPLETE |
336 | | RDA5807_MASK_SEEKRES_FAIL)) |
337 | == RDA5807_MASK_SEEKRES_COMPLETE) |
338 | /* mono/stereo known */ |
339 | rxsubchans = seekres & RDA5807_MASK_SEEKRES_STEREO |
340 | ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; |
341 | else |
342 | /* mono/stereo unknown */ |
343 | rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; |
344 | |
345 | err = rda5807_i2c_read(radio->i2c_client, RDA5807_REG_SIGNAL); |
346 | if (err < 0) |
347 | return err; |
348 | signal = ((u16)err & RDA5807_MASK_RSSI) >> RDA5807_SHIFT_RSSI; |
349 | |
350 | *a = (struct v4l2_tuner) { |
351 | .name = "FM", |
352 | .type = V4L2_TUNER_RADIO, |
353 | .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO, |
354 | /* unit is 1/16 kHz */ |
355 | .rangelow = RDA5807_FREQ_MIN_KHZ * 16, |
356 | .rangehigh = RDA5807_FREQ_MAX_KHZ * 16, |
357 | .rxsubchans = rxsubchans, |
358 | /* TODO: Implement forced mono (RDA5807_MASK_CTRL_MONO). */ |
359 | .audmode = V4L2_TUNER_MODE_STEREO, |
360 | .signal = signal << (16 - 7), |
361 | .afc = 0, /* automatic frequency control */ |
362 | }; |
363 | |
364 | return 0; |
365 | } |
366 | |
367 | static int rda5807_vidioc_g_frequency(struct file *file, void *fh, |
368 | struct v4l2_frequency *a) |
369 | { |
370 | struct rda5807_driver *radio = video_drvdata(file); |
371 | int freq_khz; |
372 | |
373 | if (a->tuner != 0) |
374 | return -EINVAL; |
375 | /* This ioctl ignores the type field. */ |
376 | |
377 | freq_khz = rda5807_get_frequency(radio); |
378 | if (freq_khz < 0) |
379 | return freq_khz; |
380 | |
381 | a->frequency = (__u32)freq_khz * 16; |
382 | return 0; |
383 | } |
384 | |
385 | static int rda5807_vidioc_s_frequency(struct file *file, void *fh, |
386 | struct v4l2_frequency *a) |
387 | { |
388 | struct rda5807_driver *radio = video_drvdata(file); |
389 | |
390 | if (a->tuner != 0) |
391 | return -EINVAL; |
392 | if (a->type != V4L2_TUNER_RADIO) |
393 | return -EINVAL; |
394 | |
395 | return rda5807_set_frequency(radio, (a->frequency * 625) / 10000); |
396 | } |
397 | |
398 | static const struct v4l2_ioctl_ops rda5807_ioctl_ops = { |
399 | .vidioc_querycap = rda5807_vidioc_querycap, |
400 | .vidioc_g_audio = rda5807_vidioc_g_audio, |
401 | .vidioc_g_tuner = rda5807_vidioc_g_tuner, |
402 | .vidioc_g_frequency = rda5807_vidioc_g_frequency, |
403 | .vidioc_s_frequency = rda5807_vidioc_s_frequency, |
404 | }; |
405 | |
406 | static int __devinit rda5807_i2c_probe(struct i2c_client *client, |
407 | const struct i2c_device_id *id) |
408 | { |
409 | struct rda5807_platform_data *pdata = client->dev.platform_data; |
410 | struct rda5807_driver *radio; |
411 | int err; |
412 | u16 val; |
413 | |
414 | if (!pdata) { |
415 | dev_err(&client->dev, "Platform data missing\n"); |
416 | return -EINVAL; |
417 | } |
418 | |
419 | err = rda5807_i2c_read(client, RDA5807_REG_CHIPID); |
420 | if (err < 0) { |
421 | dev_err(&client->dev, "Failed to read chip ID (%d)\n", err); |
422 | return err; |
423 | } |
424 | val = err; |
425 | if ((val & 0xFF00) != 0x5800) { |
426 | dev_err(&client->dev, "Chip ID mismatch: " |
427 | "expected 58xx, got %04X\n", val); |
428 | return -ENODEV; |
429 | } |
430 | dev_info(&client->dev, "Found FM radio receiver\n"); |
431 | |
432 | // TODO: Resetting the chip would be good. |
433 | |
434 | radio = kzalloc(sizeof(*radio), GFP_KERNEL); |
435 | if (!radio) { |
436 | dev_err(&client->dev, "Failed to allocate driver data\n"); |
437 | return -ENOMEM; |
438 | } |
439 | |
440 | radio->i2c_client = client; |
441 | |
442 | /* Initialize controls. */ |
443 | v4l2_ctrl_handler_init(&radio->ctrl_handler, 3); |
444 | v4l2_ctrl_new_std(&radio->ctrl_handler, &rda5807_ctrl_ops, |
445 | V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); |
446 | v4l2_ctrl_new_std(&radio->ctrl_handler, &rda5807_ctrl_ops, |
447 | V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 8); |
448 | /* TODO: V4L2_CID_TUNE_PREEMPHASIS is based on V4L2_CID_FM_TX_CLASS_BASE |
449 | * which suggests it is a transmit control rather than a receive |
450 | * control. The register bit we change is called "de-emphasis", |
451 | * but there is no de-emphasis control in V4L2. |
452 | */ |
453 | v4l2_ctrl_new_std_menu(&radio->ctrl_handler, &rda5807_ctrl_ops, |
454 | V4L2_CID_TUNE_PREEMPHASIS, |
455 | V4L2_PREEMPHASIS_75_uS, |
456 | BIT(V4L2_PREEMPHASIS_DISABLED), |
457 | V4L2_PREEMPHASIS_50_uS); |
458 | err = radio->ctrl_handler.error; |
459 | if (err) { |
460 | dev_err(&client->dev, "Failed to init controls handler (%d)\n", |
461 | err); |
462 | goto err_ctrl_free; |
463 | } |
464 | |
465 | radio->video_dev = (struct video_device) { |
466 | .name = "RDA5807 FM receiver", |
467 | .flags = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG, |
468 | .ctrl_handler = &radio->ctrl_handler, |
469 | .fops = &rda5807_fops, |
470 | .ioctl_ops = &rda5807_ioctl_ops, |
471 | .release = video_device_release_empty, |
472 | //.lock = &radio->lock, |
473 | }; |
474 | i2c_set_clientdata(client, radio); |
475 | video_set_drvdata(&radio->video_dev, radio); |
476 | |
477 | err = video_register_device(&radio->video_dev, VFL_TYPE_RADIO, -1); |
478 | if (err < 0) { |
479 | dev_err(&client->dev, "Failed to register video device (%d)\n", |
480 | err); |
481 | goto err_ctrl_free; |
482 | } |
483 | |
484 | /* Configure chip inputs. */ |
485 | err = rda5807_update_reg(radio, RDA5807_REG_INTM_THRESH_VOL, |
486 | 0xF << 4, (pdata->input_flags & 0xF) << 4); |
487 | if (err < 0) { |
488 | dev_warn(&client->dev, "Failed to configure inputs (%d)\n", |
489 | err); |
490 | } |
491 | /* Configure chip outputs. */ |
492 | val = 0; |
493 | if (pdata->output_flags & RDA5807_OUTPUT_AUDIO_I2S) { |
494 | val |= BIT(6); |
495 | } else if (pdata->output_flags & RDA5807_OUTPUT_STEREO_INDICATOR) { |
496 | val |= BIT(4); |
497 | } |
498 | err = rda5807_update_reg(radio, RDA5807_REG_IOCFG, 0x003F, val); |
499 | if (err < 0) { |
500 | dev_warn(&client->dev, "Failed to configure outputs (%d)\n", |
501 | err); |
502 | } |
503 | val = 0; |
504 | if (pdata->output_flags & RDA5807_OUTPUT_AUDIO_ANALOG) { |
505 | val |= BIT(15); |
506 | } |
507 | err = rda5807_update_reg(radio, RDA5807_REG_CTRL, BIT(15), val); |
508 | if (err < 0) { |
509 | dev_warn(&client->dev, "Failed to configure outputs (%d)\n", |
510 | err); |
511 | } |
512 | |
513 | err = v4l2_ctrl_handler_setup(&radio->ctrl_handler); |
514 | if (err < 0) { |
515 | dev_err(&client->dev, "Failed to set default control values" |
516 | " (%d)\n", err); |
517 | goto err_video_unreg; |
518 | } |
519 | |
520 | return 0; |
521 | |
522 | err_video_unreg: |
523 | video_unregister_device(&radio->video_dev); |
524 | |
525 | err_ctrl_free: |
526 | v4l2_ctrl_handler_free(&radio->ctrl_handler); |
527 | |
528 | /*err_radio_rel:*/ |
529 | video_device_release_empty(&radio->video_dev); |
530 | kfree(radio); |
531 | |
532 | return err; |
533 | } |
534 | |
535 | static int __devexit rda5807_i2c_remove(struct i2c_client *client) |
536 | { |
537 | struct rda5807_driver *radio = i2c_get_clientdata(client); |
538 | |
539 | video_unregister_device(&radio->video_dev); |
540 | v4l2_ctrl_handler_free(&radio->ctrl_handler); |
541 | video_device_release_empty(&radio->video_dev); |
542 | kfree(radio); |
543 | |
544 | return 0; |
545 | } |
546 | |
547 | #ifdef CONFIG_PM |
548 | |
549 | static int rda5807_suspend(struct device *dev) |
550 | { |
551 | struct i2c_client *client = to_i2c_client(dev); |
552 | struct rda5807_driver *radio = i2c_get_clientdata(client); |
553 | |
554 | return rda5807_set_enable(radio, 0); |
555 | } |
556 | |
557 | static int rda5807_resume(struct device *dev) |
558 | { |
559 | struct i2c_client *client = to_i2c_client(dev); |
560 | struct rda5807_driver *radio = i2c_get_clientdata(client); |
561 | struct v4l2_ctrl *mute_ctrl = v4l2_ctrl_find(&radio->ctrl_handler, |
562 | V4L2_CID_AUDIO_MUTE); |
563 | s32 mute_val = v4l2_ctrl_g_ctrl(mute_ctrl); |
564 | int enabled = !mute_val; |
565 | |
566 | if (enabled) |
567 | return rda5807_set_enable(radio, enabled); |
568 | else |
569 | return 0; |
570 | } |
571 | |
572 | static SIMPLE_DEV_PM_OPS(rda5807_pm_ops, rda5807_suspend, rda5807_resume); |
573 | #define RDA5807_PM_OPS (&rda5807_pm_ops) |
574 | |
575 | #else |
576 | |
577 | #define RDA5807_PM_OPS NULL |
578 | |
579 | #endif |
580 | |
581 | static const struct i2c_device_id rda5807_id[] = { |
582 | { "radio-rda5807", 0 }, |
583 | { } |
584 | }; |
585 | MODULE_DEVICE_TABLE(i2c, rda5807_id); |
586 | |
587 | static struct i2c_driver rda5807_i2c_driver = { |
588 | .probe = rda5807_i2c_probe, |
589 | .remove = __devexit_p(rda5807_i2c_remove), |
590 | .id_table = rda5807_id, |
591 | .driver = { |
592 | .name = "radio-rda5807", |
593 | .owner = THIS_MODULE, |
594 | .pm = RDA5807_PM_OPS, |
595 | }, |
596 | }; |
597 | |
598 | static int __init rda5807_init(void) |
599 | { |
600 | return i2c_add_driver(&rda5807_i2c_driver); |
601 | } |
602 | |
603 | static void __exit rda5807_exit(void) |
604 | { |
605 | i2c_del_driver(&rda5807_i2c_driver); |
606 | } |
607 | |
608 | module_init(rda5807_init); |
609 | module_exit(rda5807_exit); |
610 | |
611 | MODULE_AUTHOR("Maarten ter Huurne <maarten@treewalker.org>"); |
612 | MODULE_DESCRIPTION("RDA5807 FM tuner driver"); |
613 | MODULE_LICENSE("GPL"); |
614 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9