Root/drivers/media/radio/radio-rda5807.c

Source at commit 144e9c2530f863e32a3538b06c63484401bbe314 created 9 years 20 days ago.
By Maarten ter Huurne, media: radio: RDA5807: Mute at startup; power down when muted.
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    u16 val = enabled ? RDA5807_MASK_CTRL_ENABLE : 0;
170    dev_info(&radio->i2c_client->dev, "set enabled to %d\n", enabled);
171    return rda5807_update_reg(radio, RDA5807_REG_CTRL,
172                  RDA5807_MASK_CTRL_ENABLE, val);
173}
174
175static int rda5807_set_mute(struct rda5807_driver *radio, int muted)
176{
177    u16 val = muted ? 0 : RDA5807_MASK_CTRL_DMUTE /* disable mute */;
178    dev_info(&radio->i2c_client->dev, "set mute to %d\n", muted);
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        /* Disable the radio while muted, to save power.
242         * TODO: We can't seek while the radio is disabled;
243         * is that a problem?
244         */
245        int err1 = rda5807_set_enable(radio, !ctrl->val);
246        int err2 = rda5807_set_mute(radio, ctrl->val);
247        return err1 ? err1 : err2;
248    }
249    case V4L2_CID_AUDIO_VOLUME:
250        return rda5807_set_volume(radio, ctrl->val);
251    case V4L2_CID_TUNE_PREEMPHASIS:
252        return rda5807_set_preemphasis(radio, ctrl->val);
253    default:
254        return -EINVAL;
255    }
256}
257
258static const struct v4l2_ctrl_ops rda5807_ctrl_ops = {
259    .s_ctrl = rda5807_s_ctrl,
260};
261
262static int rda5807_vidioc_g_audio(struct file *file, void *fh,
263                  struct v4l2_audio *a)
264{
265    if (a->index != 0)
266        return -EINVAL;
267
268    *a = (struct v4l2_audio) {
269        .name = "Radio",
270        .capability = V4L2_AUDCAP_STEREO,
271        .mode = 0,
272    };
273
274    return 0;
275}
276
277static int rda5807_vidioc_g_tuner(struct file *file, void *fh,
278                  struct v4l2_tuner *a)
279{
280    struct rda5807_driver *radio = video_drvdata(file);
281    int err;
282    u16 seekres, signal;
283    __u32 rxsubchans;
284
285    if (a->index != 0)
286        return -EINVAL;
287
288    err = rda5807_i2c_read(radio->i2c_client, RDA5807_REG_SEEK_RESULT);
289    if (err < 0)
290        return err;
291    seekres = (u16)err;
292    if ((seekres & (RDA5807_MASK_SEEKRES_COMPLETE
293                        | RDA5807_MASK_SEEKRES_FAIL))
294                == RDA5807_MASK_SEEKRES_COMPLETE)
295        /* mono/stereo known */
296        rxsubchans = seekres & RDA5807_MASK_SEEKRES_STEREO
297                ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
298    else
299        /* mono/stereo unknown */
300        rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
301
302    err = rda5807_i2c_read(radio->i2c_client, RDA5807_REG_SIGNAL);
303    if (err < 0)
304        return err;
305    signal = ((u16)err & RDA5807_MASK_RSSI) >> RDA5807_SHIFT_RSSI;
306
307    *a = (struct v4l2_tuner) {
308        .name = "FM",
309        .type = V4L2_TUNER_RADIO,
310        .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO,
311        /* unit is 1/16 kHz */
312        .rangelow = RDA5807_FREQ_MIN_KHZ * 16,
313        .rangehigh = RDA5807_FREQ_MAX_KHZ * 16,
314        .rxsubchans = rxsubchans,
315        /* TODO: Implement forced mono (RDA5807_MASK_CTRL_MONO). */
316        .audmode = V4L2_TUNER_MODE_STEREO,
317        /* TODO: Is the signal strength 6 or 7 bits wide?
318                 Good reception is about 56, which would suggest
319                 that 63 is max, so 6 bits. */
320        /* TODO: RSSI is logarithmic, does V4L2 expect log or linear? */
321        .signal = signal < 0x40 ? signal << 10 : 0xFFFF,
322        .afc = 0, /* automatic frequency control */
323    };
324
325    return 0;
326}
327
328static int rda5807_vidioc_s_frequency(struct file *file, void *fh,
329                      struct v4l2_frequency *a)
330{
331    struct rda5807_driver *radio = video_drvdata(file);
332
333    if (a->tuner != 0)
334        return -EINVAL;
335    if (a->type != V4L2_TUNER_RADIO)
336        return -EINVAL;
337
338    return rda5807_set_frequency(radio, (a->frequency * 625) / 10000);
339}
340
341static const struct v4l2_ioctl_ops rda5807_ioctl_ops = {
342    .vidioc_g_audio = rda5807_vidioc_g_audio,
343    .vidioc_g_tuner = rda5807_vidioc_g_tuner,
344    .vidioc_s_frequency = rda5807_vidioc_s_frequency,
345};
346
347static int __devinit rda5807_i2c_probe(struct i2c_client *client,
348                       const struct i2c_device_id *id)
349{
350    struct rda5807_driver *radio;
351    int chipid;
352    int err;
353
354    chipid = rda5807_i2c_read(client, RDA5807_REG_CHIPID);
355    if (chipid < 0) {
356        dev_warn(&client->dev, "Failed to read chip ID (%d)\n", chipid);
357        return chipid;
358    }
359    if ((chipid & 0xFF00) != 0x5800) {
360        dev_warn(&client->dev, "Chip ID mismatch: "
361                       "expected 58xx, got %04X\n", chipid);
362        return -ENODEV;
363    }
364    dev_info(&client->dev, "Found FM radio receiver\n");
365
366    // TODO: Resetting the chip would be good.
367
368    radio = kzalloc(sizeof(*radio), GFP_KERNEL);
369    if (!radio) {
370        dev_warn(&client->dev, "Failed to allocate driver data\n");
371        return -ENOMEM;
372    }
373
374    radio->i2c_client = client;
375
376    /* Initialize controls. */
377    v4l2_ctrl_handler_init(&radio->ctrl_handler, 3);
378    v4l2_ctrl_new_std(&radio->ctrl_handler, &rda5807_ctrl_ops,
379              V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
380    v4l2_ctrl_new_std(&radio->ctrl_handler, &rda5807_ctrl_ops,
381              V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 8);
382    v4l2_ctrl_new_std_menu(&radio->ctrl_handler, &rda5807_ctrl_ops,
383                   V4L2_CID_TUNE_PREEMPHASIS,
384                   V4L2_PREEMPHASIS_75_uS,
385                   BIT(V4L2_PREEMPHASIS_DISABLED),
386                   V4L2_PREEMPHASIS_50_uS);
387    err = radio->ctrl_handler.error;
388    if (err) {
389        dev_warn(&client->dev, "Failed to init controls handler"
390             " (%d)\n", err);
391        goto err_ctrl_free;
392    }
393
394    radio->video_dev = (struct video_device) {
395        .name = "RDA5807 FM receiver",
396        .flags = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG,
397        .ctrl_handler = &radio->ctrl_handler,
398        .fops = &rda5807_fops,
399        .ioctl_ops = &rda5807_ioctl_ops,
400        .release = video_device_release_empty,
401        //.lock = &radio->lock,
402    };
403    i2c_set_clientdata(client, radio);
404    video_set_drvdata(&radio->video_dev, radio);
405
406    err = video_register_device(&radio->video_dev, VFL_TYPE_RADIO, -1);
407    if (err < 0) {
408        dev_warn(&client->dev, "Failed to register video device (%d)\n",
409                       err);
410        goto err_ctrl_free;
411    }
412
413    err = v4l2_ctrl_handler_setup(&radio->ctrl_handler);
414    if (err < 0) {
415        dev_warn(&client->dev, "Failed to set default control values"
416                       " (%d)\n", err);
417        goto err_video_unreg;
418    }
419
420    return 0;
421
422err_video_unreg:
423    video_unregister_device(&radio->video_dev);
424
425err_ctrl_free:
426    v4l2_ctrl_handler_free(&radio->ctrl_handler);
427
428/*err_radio_rel:*/
429    video_device_release_empty(&radio->video_dev);
430    kfree(radio);
431
432    return err;
433}
434
435static int __devexit rda5807_i2c_remove(struct i2c_client *client)
436{
437    struct rda5807_driver *radio = i2c_get_clientdata(client);
438
439    video_unregister_device(&radio->video_dev);
440    v4l2_ctrl_handler_free(&radio->ctrl_handler);
441    video_device_release_empty(&radio->video_dev);
442    kfree(radio);
443
444    return 0;
445}
446
447static const struct i2c_device_id rda5807_id[] = {
448    { "radio-rda5807", 0 },
449    { }
450};
451MODULE_DEVICE_TABLE(i2c, rda5807_id);
452
453static struct i2c_driver rda5807_i2c_driver = {
454    .probe = rda5807_i2c_probe,
455    .remove = __devexit_p(rda5807_i2c_remove),
456    .id_table = rda5807_id,
457    .driver = {
458        .name = "radio-rda5807",
459        .owner = THIS_MODULE,
460    },
461};
462
463static int __init rda5807_init(void)
464{
465    return i2c_add_driver(&rda5807_i2c_driver);
466}
467
468static void __exit rda5807_exit(void)
469{
470    i2c_del_driver(&rda5807_i2c_driver);
471}
472
473module_init(rda5807_init);
474module_exit(rda5807_exit);
475
476MODULE_AUTHOR("Maarten ter Huurne <maarten@treewalker.org>");
477MODULE_DESCRIPTION("RDA5807 FM tuner driver");
478MODULE_LICENSE("GPL");
479

Archive Download this file



interactive