Date: | 2011-10-02 18:35:59 (12 years 2 months ago) |
---|---|
Author: | Maarten ter Huurne |
Commit: | 6f400ef5f964d9f517fcbe9b36695b8a7118c070 |
Message: | media: radio: RDA5807: Added driver. Driver has enough functionality to make fmtools 2.x work, but is far from complete. Many thanks to Jérôme VERES for his command line radio application that demonstrates how the chip can be controlled via I2C. |
Files: |
drivers/media/radio/Kconfig (1 diff) drivers/media/radio/Makefile (1 diff) drivers/media/radio/radio-rda5807.c (1 diff) |
Change Details
drivers/media/radio/Kconfig | ||
---|---|---|
369 | 369 | To compile this driver as a module, choose M here: the |
370 | 370 | module will be called radio-mr800. |
371 | 371 | |
372 | config RADIO_RDA5807 | |
373 | tristate "RDA5807 I2C FM radio support" | |
374 | depends on I2C && VIDEO_V4L2 | |
375 | ---help--- | |
376 | Say Y here if you want to use the RDA5807 FM receiver connected to | |
377 | an I2C bus. It is used in for example the Dingoo A320 portable | |
378 | media player. | |
379 | ||
380 | To compile this driver as a module, choose M here: the | |
381 | module will be called radio-rda5807. | |
382 | ||
372 | 383 | config RADIO_TEA5764 |
373 | 384 | tristate "TEA5764 I2C FM radio support" |
374 | 385 | depends on I2C && VIDEO_V4L2 |
drivers/media/radio/Makefile | ||
---|---|---|
20 | 20 | obj-$(CONFIG_USB_DSBR) += dsbr100.o |
21 | 21 | obj-$(CONFIG_RADIO_SI470X) += si470x/ |
22 | 22 | obj-$(CONFIG_USB_MR800) += radio-mr800.o |
23 | obj-$(CONFIG_RADIO_RDA5807) += radio-rda5807.o | |
23 | 24 | obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o |
24 | 25 | obj-$(CONFIG_RADIO_SAA7706H) += saa7706h.o |
25 | 26 | obj-$(CONFIG_RADIO_TEF6862) += tef6862.o |
drivers/media/radio/radio-rda5807.c | ||
---|---|---|
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. | |
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/slab.h> | |
40 | #include <linux/types.h> | |
41 | #include <linux/videodev2.h> | |
42 | #include <media/v4l2-ctrls.h> | |
43 | #include <media/v4l2-dev.h> | |
44 | #include <media/v4l2-ioctl.h> | |
45 | ||
46 | ||
47 | enum rda5807_reg { | |
48 | RDA5807_REG_CHIPID = 0x00, | |
49 | RDA5807_REG_CTRL = 0x02, | |
50 | RDA5807_REG_CHAN = 0x03, | |
51 | RDA5807_REG_IOCFG = 0x04, | |
52 | RDA5807_REG_INTM_THRESH_VOL = 0x05, | |
53 | RDA5807_REG_SEEK_RESULT = 0x0A, | |
54 | RDA5807_REG_SIGNAL = 0x0B, | |
55 | }; | |
56 | ||
57 | #define RDA5807_MASK_CTRL_DHIZ BIT(15) | |
58 | #define RDA5807_MASK_CTRL_DMUTE BIT(14) | |
59 | #define RDA5807_MASK_CTRL_MONO BIT(13) | |
60 | #define RDA5807_MASK_CTRL_BASS BIT(12) | |
61 | #define RDA5807_MASK_CTRL_SEEKUP BIT(9) | |
62 | #define RDA5807_MASK_CTRL_SEEK BIT(8) | |
63 | #define RDA5807_MASK_CTRL_SKMODE BIT(7) | |
64 | #define RDA5807_MASK_CTRL_CLKMODE (7 << 4) | |
65 | #define RDA5807_MASK_CTRL_SOFTRESET BIT(1) | |
66 | #define RDA5807_MASK_CTRL_ENABLE BIT(0) | |
67 | ||
68 | #define RDA5807_SHIFT_CHAN_WRCHAN 6 | |
69 | #define RDA5807_MASK_CHAN_WRCHAN (0x3FF << RDA5807_SHIFT_CHAN_WRCHAN) | |
70 | #define RDA5807_MASK_CHAN_TUNE BIT(4) | |
71 | #define RDA5807_SHIFT_CHAN_BAND 2 | |
72 | #define RDA5807_MASK_CHAN_BAND (0x3 << RDA5807_SHIFT_CHAN_BAND) | |
73 | #define RDA5807_SHIFT_CHAN_SPACE 0 | |
74 | #define RDA5807_MASK_CHAN_SPACE (0x3 << RDA5807_SHIFT_CHAN_SPACE) | |
75 | ||
76 | #define RDA5807_MASK_SEEKRES_COMPLETE BIT(14) | |
77 | #define RDA5807_MASK_SEEKRES_FAIL BIT(13) | |
78 | #define RDA5807_MASK_SEEKRES_STEREO BIT(10) | |
79 | ||
80 | #define RDA5807_MASK_DEEMPHASIS BIT(11) | |
81 | ||
82 | #define RDA5807_SHIFT_VOLUME_DAC 0 | |
83 | #define RDA5807_MASK_VOLUME_DAC (0xF << RDA5807_SHIFT_VOLUME_DAC) | |
84 | ||
85 | #define RDA5807_SHIFT_RSSI 9 | |
86 | #define RDA5807_MASK_RSSI (0x7F << RDA5807_SHIFT_RSSI) | |
87 | ||
88 | #define RDA5807_FREQ_MIN_KHZ 76000 | |
89 | #define RDA5807_FREQ_MAX_KHZ 108000 | |
90 | ||
91 | static int rda5807_i2c_read(struct i2c_client *client, enum rda5807_reg reg) | |
92 | { | |
93 | __u8 reg_buf = reg; | |
94 | __u16 val_buf; | |
95 | struct i2c_msg msgs[] = { | |
96 | { /* write register number */ | |
97 | .addr = client->addr, | |
98 | .flags = 0, | |
99 | .len = sizeof(reg_buf), | |
100 | .buf = ®_buf, | |
101 | }, | |
102 | { /* read register contents */ | |
103 | .addr = client->addr, | |
104 | .flags = I2C_M_RD, | |
105 | .len = sizeof(val_buf), | |
106 | .buf = (__u8 *)&val_buf, | |
107 | }, | |
108 | }; | |
109 | int err; | |
110 | ||
111 | err = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); | |
112 | if (err < 0) return err; | |
113 | if (err < ARRAY_SIZE(msgs)) return -EIO; | |
114 | ||
115 | dev_info(&client->dev, "reg[%02X] = %04X\n", reg, be16_to_cpu(val_buf)); | |
116 | return be16_to_cpu(val_buf); | |
117 | } | |
118 | ||
119 | static int rda5807_i2c_write(struct i2c_client *client, enum rda5807_reg reg, | |
120 | u16 val) | |
121 | { | |
122 | __u8 buf[] = { reg, val >> 8, val & 0xFF }; | |
123 | struct i2c_msg msgs[] = { | |
124 | { /* write register number and contents */ | |
125 | .addr = client->addr, | |
126 | .flags = 0, | |
127 | .len = sizeof(buf), | |
128 | .buf = buf, | |
129 | }, | |
130 | }; | |
131 | int err; | |
132 | ||
133 | err = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); | |
134 | if (err < 0) return err; | |
135 | if (err < ARRAY_SIZE(msgs)) return -EIO; | |
136 | ||
137 | dev_info(&client->dev, "reg[%02X] := %04X\n", reg, val); | |
138 | return 0; | |
139 | } | |
140 | ||
141 | struct rda5807_driver { | |
142 | struct v4l2_ctrl_handler ctrl_handler; | |
143 | struct video_device video_dev; | |
144 | struct i2c_client *i2c_client; | |
145 | }; | |
146 | ||
147 | static const struct v4l2_file_operations rda5807_fops = { | |
148 | .owner = THIS_MODULE, | |
149 | .unlocked_ioctl = video_ioctl2, | |
150 | }; | |
151 | ||
152 | static int rda5807_update_reg(struct rda5807_driver *radio, | |
153 | enum rda5807_reg reg, u16 mask, u16 val) | |
154 | { | |
155 | int err = 0; | |
156 | // TODO: Locking. | |
157 | // Or do locking in the caller, in case we ever need to update | |
158 | // two registers in one operation? | |
159 | err = rda5807_i2c_read(radio->i2c_client, reg); | |
160 | if (err >= 0) { | |
161 | val |= ((u16)err & ~mask); | |
162 | err = rda5807_i2c_write(radio->i2c_client, reg, val); | |
163 | } | |
164 | return err; | |
165 | } | |
166 | ||
167 | static int rda5807_set_enable(struct rda5807_driver *radio, int enabled) | |
168 | { | |
169 | // TODO: What should control power up/down? | |
170 | // Mute would be a candidate. | |
171 | u16 val = enabled ? RDA5807_MASK_CTRL_ENABLE : 0; | |
172 | return rda5807_update_reg(radio, RDA5807_REG_CTRL, | |
173 | RDA5807_MASK_CTRL_ENABLE, val); | |
174 | } | |
175 | ||
176 | static int rda5807_set_mute(struct rda5807_driver *radio, int muted) | |
177 | { | |
178 | u16 val = muted ? 0 : RDA5807_MASK_CTRL_DMUTE /* disable mute */; | |
179 | return rda5807_update_reg(radio, RDA5807_REG_CTRL, | |
180 | RDA5807_MASK_CTRL_DMUTE, val); | |
181 | } | |
182 | ||
183 | static int rda5807_set_volume(struct rda5807_driver *radio, int volume) | |
184 | { | |
185 | dev_info(&radio->i2c_client->dev, "set volume to %d\n", volume); | |
186 | return rda5807_update_reg(radio, RDA5807_REG_INTM_THRESH_VOL, | |
187 | RDA5807_MASK_VOLUME_DAC, | |
188 | volume << RDA5807_SHIFT_VOLUME_DAC); | |
189 | } | |
190 | ||
191 | static int rda5807_set_preemphasis(struct rda5807_driver *radio, | |
192 | enum v4l2_preemphasis preemp) | |
193 | { | |
194 | dev_info(&radio->i2c_client->dev, "set preemphasis to %d\n", preemp); | |
195 | return rda5807_update_reg(radio, RDA5807_REG_IOCFG, | |
196 | RDA5807_MASK_DEEMPHASIS, | |
197 | preemp == V4L2_PREEMPHASIS_50_uS | |
198 | ? RDA5807_MASK_DEEMPHASIS : 0); | |
199 | } | |
200 | ||
201 | static int rda5807_set_frequency(struct rda5807_driver *radio, u32 freq_khz) | |
202 | { | |
203 | u16 mask = 0; | |
204 | u16 val = 0; | |
205 | ||
206 | dev_info(&radio->i2c_client->dev, "set freq to %u kHz\n", freq_khz); | |
207 | ||
208 | if (freq_khz < RDA5807_FREQ_MIN_KHZ) | |
209 | return -ERANGE; | |
210 | if (freq_khz > RDA5807_FREQ_MAX_KHZ) | |
211 | return -ERANGE; | |
212 | ||
213 | /* select widest band */ | |
214 | mask |= RDA5807_MASK_CHAN_BAND; | |
215 | val |= 2 << RDA5807_SHIFT_CHAN_BAND; | |
216 | /* select 50 kHz channel spacing */ | |
217 | mask |= RDA5807_MASK_CHAN_SPACE; | |
218 | val |= 2 << RDA5807_SHIFT_CHAN_SPACE; | |
219 | /* select frequency */ | |
220 | mask |= RDA5807_MASK_CHAN_WRCHAN; | |
221 | val |= ((freq_khz - RDA5807_FREQ_MIN_KHZ + 25) / 50) | |
222 | << RDA5807_SHIFT_CHAN_WRCHAN; | |
223 | /* start tune operation */ | |
224 | mask |= RDA5807_MASK_CHAN_TUNE; | |
225 | val |= RDA5807_MASK_CHAN_TUNE; | |
226 | ||
227 | return rda5807_update_reg(radio, RDA5807_REG_CHAN, mask, val); | |
228 | } | |
229 | ||
230 | static inline struct rda5807_driver *ctrl_to_radio(struct v4l2_ctrl *ctrl) | |
231 | { | |
232 | return container_of(ctrl->handler, struct rda5807_driver, ctrl_handler); | |
233 | } | |
234 | ||
235 | static int rda5807_s_ctrl(struct v4l2_ctrl *ctrl) | |
236 | { | |
237 | struct rda5807_driver *radio = ctrl_to_radio(ctrl); | |
238 | ||
239 | switch (ctrl->id) { | |
240 | case V4L2_CID_AUDIO_MUTE: | |
241 | return rda5807_set_mute(radio, ctrl->val); | |
242 | case V4L2_CID_AUDIO_VOLUME: | |
243 | return rda5807_set_volume(radio, ctrl->val); | |
244 | case V4L2_CID_TUNE_PREEMPHASIS: | |
245 | return rda5807_set_preemphasis(radio, ctrl->val); | |
246 | default: | |
247 | return -EINVAL; | |
248 | } | |
249 | } | |
250 | ||
251 | static const struct v4l2_ctrl_ops rda5807_ctrl_ops = { | |
252 | .s_ctrl = rda5807_s_ctrl, | |
253 | }; | |
254 | ||
255 | static int rda5807_vidioc_g_audio(struct file *file, void *fh, | |
256 | struct v4l2_audio *a) | |
257 | { | |
258 | if (a->index != 0) | |
259 | return -EINVAL; | |
260 | ||
261 | *a = (struct v4l2_audio) { | |
262 | .name = "Radio", | |
263 | .capability = V4L2_AUDCAP_STEREO, | |
264 | .mode = 0, | |
265 | }; | |
266 | ||
267 | return 0; | |
268 | } | |
269 | ||
270 | static int rda5807_vidioc_g_tuner(struct file *file, void *fh, | |
271 | struct v4l2_tuner *a) | |
272 | { | |
273 | struct rda5807_driver *radio = video_drvdata(file); | |
274 | int err; | |
275 | u16 seekres, signal; | |
276 | __u32 rxsubchans; | |
277 | ||
278 | if (a->index != 0) | |
279 | return -EINVAL; | |
280 | ||
281 | err = rda5807_i2c_read(radio->i2c_client, RDA5807_REG_SEEK_RESULT); | |
282 | if (err < 0) | |
283 | return err; | |
284 | seekres = (u16)err; | |
285 | if ((seekres & (RDA5807_MASK_SEEKRES_COMPLETE | |
286 | | RDA5807_MASK_SEEKRES_FAIL)) | |
287 | == RDA5807_MASK_SEEKRES_COMPLETE) | |
288 | /* mono/stereo known */ | |
289 | rxsubchans = seekres & RDA5807_MASK_SEEKRES_STEREO | |
290 | ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; | |
291 | else | |
292 | /* mono/stereo unknown */ | |
293 | rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; | |
294 | ||
295 | err = rda5807_i2c_read(radio->i2c_client, RDA5807_REG_SIGNAL); | |
296 | if (err < 0) | |
297 | return err; | |
298 | signal = ((u16)err & RDA5807_MASK_RSSI) >> RDA5807_SHIFT_RSSI; | |
299 | ||
300 | *a = (struct v4l2_tuner) { | |
301 | .name = "FM", | |
302 | .type = V4L2_TUNER_RADIO, | |
303 | .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO, | |
304 | /* unit is 1/16 kHz */ | |
305 | .rangelow = RDA5807_FREQ_MIN_KHZ * 16, | |
306 | .rangehigh = RDA5807_FREQ_MAX_KHZ * 16, | |
307 | .rxsubchans = rxsubchans, | |
308 | /* TODO: Implement forced mono (RDA5807_MASK_CTRL_MONO). */ | |
309 | .audmode = V4L2_TUNER_MODE_STEREO, | |
310 | /* TODO: Is the signal strength 6 or 7 bits wide? | |
311 | Good reception is about 56, which would suggest | |
312 | that 63 is max, so 6 bits. */ | |
313 | /* TODO: RSSI is logarithmic, does V4L2 expect log or linear? */ | |
314 | .signal = signal < 0x40 ? signal << 10 : 0xFFFF, | |
315 | .afc = 0, /* automatic frequency control */ | |
316 | }; | |
317 | ||
318 | return 0; | |
319 | } | |
320 | ||
321 | static int rda5807_vidioc_s_frequency(struct file *file, void *fh, | |
322 | struct v4l2_frequency *a) | |
323 | { | |
324 | struct rda5807_driver *radio = video_drvdata(file); | |
325 | ||
326 | if (a->tuner != 0) | |
327 | return -EINVAL; | |
328 | if (a->type != V4L2_TUNER_RADIO) | |
329 | return -EINVAL; | |
330 | ||
331 | return rda5807_set_frequency(radio, (a->frequency * 625) / 10000); | |
332 | } | |
333 | ||
334 | static const struct v4l2_ioctl_ops rda5807_ioctl_ops = { | |
335 | .vidioc_g_audio = rda5807_vidioc_g_audio, | |
336 | .vidioc_g_tuner = rda5807_vidioc_g_tuner, | |
337 | .vidioc_s_frequency = rda5807_vidioc_s_frequency, | |
338 | }; | |
339 | ||
340 | static int __devinit rda5807_i2c_probe(struct i2c_client *client, | |
341 | const struct i2c_device_id *id) | |
342 | { | |
343 | struct rda5807_driver *radio; | |
344 | int chipid; | |
345 | int err; | |
346 | ||
347 | chipid = rda5807_i2c_read(client, RDA5807_REG_CHIPID); | |
348 | if (chipid < 0) { | |
349 | dev_warn(&client->dev, "Failed to read chip ID (%d)\n", chipid); | |
350 | return chipid; | |
351 | } | |
352 | if ((chipid & 0xFF00) != 0x5800) { | |
353 | dev_warn(&client->dev, "Chip ID mismatch: " | |
354 | "expected 58xx, got %04X\n", chipid); | |
355 | return -ENODEV; | |
356 | } | |
357 | dev_info(&client->dev, "Found FM radio receiver\n"); | |
358 | ||
359 | // TODO: Resetting the chip would be good. | |
360 | ||
361 | radio = kzalloc(sizeof(*radio), GFP_KERNEL); | |
362 | if (!radio) { | |
363 | dev_warn(&client->dev, "Failed to allocate driver data\n"); | |
364 | return -ENOMEM; | |
365 | } | |
366 | ||
367 | radio->i2c_client = client; | |
368 | ||
369 | /* Initialize controls. */ | |
370 | v4l2_ctrl_handler_init(&radio->ctrl_handler, 3); | |
371 | v4l2_ctrl_new_std(&radio->ctrl_handler, &rda5807_ctrl_ops, | |
372 | V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); | |
373 | v4l2_ctrl_new_std(&radio->ctrl_handler, &rda5807_ctrl_ops, | |
374 | V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 8); | |
375 | v4l2_ctrl_new_std_menu(&radio->ctrl_handler, &rda5807_ctrl_ops, | |
376 | V4L2_CID_TUNE_PREEMPHASIS, | |
377 | V4L2_PREEMPHASIS_75_uS, | |
378 | BIT(V4L2_PREEMPHASIS_DISABLED), | |
379 | V4L2_PREEMPHASIS_50_uS); | |
380 | err = radio->ctrl_handler.error; | |
381 | if (err) { | |
382 | dev_warn(&client->dev, "Failed to init controls handler" | |
383 | " (%d)\n", err); | |
384 | goto err_ctrl_free; | |
385 | } | |
386 | ||
387 | radio->video_dev = (struct video_device) { | |
388 | .name = "RDA5807 FM receiver", | |
389 | .flags = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG, | |
390 | .ctrl_handler = &radio->ctrl_handler, | |
391 | .fops = &rda5807_fops, | |
392 | .ioctl_ops = &rda5807_ioctl_ops, | |
393 | .release = video_device_release_empty, | |
394 | //.lock = &radio->lock, | |
395 | }; | |
396 | i2c_set_clientdata(client, radio); | |
397 | video_set_drvdata(&radio->video_dev, radio); | |
398 | ||
399 | err = video_register_device(&radio->video_dev, VFL_TYPE_RADIO, -1); | |
400 | if (err < 0) { | |
401 | dev_warn(&client->dev, "Failed to register video device (%d)\n", | |
402 | err); | |
403 | goto err_ctrl_free; | |
404 | } | |
405 | ||
406 | err = v4l2_ctrl_handler_setup(&radio->ctrl_handler); | |
407 | if (err < 0) { | |
408 | dev_warn(&client->dev, "Failed to set default control values" | |
409 | " (%d)\n", err); | |
410 | goto err_video_unreg; | |
411 | } | |
412 | // TODO: Disable on startup and enable on demand. | |
413 | rda5807_set_enable(radio, 1); | |
414 | ||
415 | return 0; | |
416 | ||
417 | err_video_unreg: | |
418 | video_unregister_device(&radio->video_dev); | |
419 | ||
420 | err_ctrl_free: | |
421 | v4l2_ctrl_handler_free(&radio->ctrl_handler); | |
422 | ||
423 | /*err_radio_rel:*/ | |
424 | video_device_release_empty(&radio->video_dev); | |
425 | kfree(radio); | |
426 | ||
427 | return err; | |
428 | } | |
429 | ||
430 | static int __devexit rda5807_i2c_remove(struct i2c_client *client) | |
431 | { | |
432 | struct rda5807_driver *radio = i2c_get_clientdata(client); | |
433 | ||
434 | video_unregister_device(&radio->video_dev); | |
435 | v4l2_ctrl_handler_free(&radio->ctrl_handler); | |
436 | video_device_release_empty(&radio->video_dev); | |
437 | kfree(radio); | |
438 | ||
439 | return 0; | |
440 | } | |
441 | ||
442 | static const struct i2c_device_id rda5807_id[] = { | |
443 | { "radio-rda5807", 0 }, | |
444 | { } | |
445 | }; | |
446 | MODULE_DEVICE_TABLE(i2c, rda5807_id); | |
447 | ||
448 | static struct i2c_driver rda5807_i2c_driver = { | |
449 | .probe = rda5807_i2c_probe, | |
450 | .remove = __devexit_p(rda5807_i2c_remove), | |
451 | .id_table = rda5807_id, | |
452 | .driver = { | |
453 | .name = "radio-rda5807", | |
454 | .owner = THIS_MODULE, | |
455 | }, | |
456 | }; | |
457 | ||
458 | static int __init rda5807_init(void) | |
459 | { | |
460 | return i2c_add_driver(&rda5807_i2c_driver); | |
461 | } | |
462 | ||
463 | static void __exit rda5807_exit(void) | |
464 | { | |
465 | i2c_del_driver(&rda5807_i2c_driver); | |
466 | } | |
467 | ||
468 | module_init(rda5807_init); | |
469 | module_exit(rda5807_exit); | |
470 | ||
471 | MODULE_AUTHOR("Maarten ter Huurne <maarten@treewalker.org>"); | |
472 | MODULE_DESCRIPTION("RDA5807 FM tuner driver"); | |
473 | MODULE_LICENSE("GPL"); |
Branches:
ben-wpan
ben-wpan-stefan
5396a9238205f20f811ea57898980d3ca82df0b6
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