Date:2011-10-02 18:35:59 (8 years 11 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
369369      To compile this driver as a module, choose M here: the
370370      module will be called radio-mr800.
371371
372config 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
372383config RADIO_TEA5764
373384    tristate "TEA5764 I2C FM radio support"
374385    depends on I2C && VIDEO_V4L2
drivers/media/radio/Makefile
2020obj-$(CONFIG_USB_DSBR) += dsbr100.o
2121obj-$(CONFIG_RADIO_SI470X) += si470x/
2222obj-$(CONFIG_USB_MR800) += radio-mr800.o
23obj-$(CONFIG_RADIO_RDA5807) += radio-rda5807.o
2324obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o
2425obj-$(CONFIG_RADIO_SAA7706H) += saa7706h.o
2526obj-$(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
47enum 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
91static 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 = &reg_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
119static 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
141struct rda5807_driver {
142    struct v4l2_ctrl_handler ctrl_handler;
143    struct video_device video_dev;
144    struct i2c_client *i2c_client;
145};
146
147static const struct v4l2_file_operations rda5807_fops = {
148    .owner = THIS_MODULE,
149    .unlocked_ioctl = video_ioctl2,
150};
151
152static 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
167static 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
176static 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
183static 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
191static 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
201static 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
230static 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
235static 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
251static const struct v4l2_ctrl_ops rda5807_ctrl_ops = {
252    .s_ctrl = rda5807_s_ctrl,
253};
254
255static 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
270static 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
321static 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
334static 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
340static 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
417err_video_unreg:
418    video_unregister_device(&radio->video_dev);
419
420err_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
430static 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
442static const struct i2c_device_id rda5807_id[] = {
443    { "radio-rda5807", 0 },
444    { }
445};
446MODULE_DEVICE_TABLE(i2c, rda5807_id);
447
448static 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
458static int __init rda5807_init(void)
459{
460    return i2c_add_driver(&rda5807_i2c_driver);
461}
462
463static void __exit rda5807_exit(void)
464{
465    i2c_del_driver(&rda5807_i2c_driver);
466}
467
468module_init(rda5807_init);
469module_exit(rda5807_exit);
470
471MODULE_AUTHOR("Maarten ter Huurne <maarten@treewalker.org>");
472MODULE_DESCRIPTION("RDA5807 FM tuner driver");
473MODULE_LICENSE("GPL");

Archive Download the corresponding diff file



interactive