V4L/DVB (12416): radio-si470x: add i2c driver for si470x
[safe/jmp/linux-2.6] / drivers / media / radio / si470x / radio-si470x-i2c.c
1 /*
2  * drivers/media/radio/si470x/radio-si470x-i2c.c
3  *
4  * I2C driver for radios with Silicon Labs Si470x FM Radio Receivers
5  *
6  * Copyright (C) 2009 Samsung Electronics Co.Ltd
7  * Author: Joonyoung Shim <jy0922.shim@samsung.com>
8  *
9  *  This program is free software; you can redistribute  it and/or modify it
10  *  under  the terms of  the GNU General  Public License as published by the
11  *  Free Software Foundation;  either version 2 of the  License, or (at your
12  *  option) any later version.
13  *
14  *
15  * TODO:
16  * - RDS support
17  *
18  */
19
20 #include <linux/module.h>
21 #include <linux/init.h>
22 #include <linux/i2c.h>
23 #include <linux/delay.h>
24
25 #include "radio-si470x.h"
26
27 #define DRIVER_KERNEL_VERSION   KERNEL_VERSION(1, 0, 0)
28 #define DRIVER_CARD             "Silicon Labs Si470x FM Radio Receiver"
29 #define DRIVER_VERSION          "1.0.0"
30
31 /* starting with the upper byte of register 0x0a */
32 #define READ_REG_NUM            RADIO_REGISTER_NUM
33 #define READ_INDEX(i)           ((i + RADIO_REGISTER_NUM - 0x0a) % READ_REG_NUM)
34
35 static int si470x_get_all_registers(struct si470x_device *radio)
36 {
37         int i;
38         u16 buf[READ_REG_NUM];
39         struct i2c_msg msgs[1] = {
40                 { radio->client->addr, I2C_M_RD, sizeof(u16) * READ_REG_NUM,
41                         (void *)buf },
42         };
43
44         if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
45                 return -EIO;
46
47         for (i = 0; i < READ_REG_NUM; i++)
48                 radio->registers[i] = __be16_to_cpu(buf[READ_INDEX(i)]);
49
50         return 0;
51 }
52
53 int si470x_get_register(struct si470x_device *radio, int regnr)
54 {
55         u16 buf[READ_REG_NUM];
56         struct i2c_msg msgs[1] = {
57                 { radio->client->addr, I2C_M_RD, sizeof(u16) * READ_REG_NUM,
58                         (void *)buf },
59         };
60
61         if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
62                 return -EIO;
63
64         radio->registers[regnr] = __be16_to_cpu(buf[READ_INDEX(regnr)]);
65
66         return 0;
67 }
68
69 /* starting with the upper byte of register 0x02h */
70 #define WRITE_REG_NUM           8
71 #define WRITE_INDEX(i)          (i + 0x02)
72
73 int si470x_set_register(struct si470x_device *radio, int regnr)
74 {
75         int i;
76         u16 buf[WRITE_REG_NUM];
77         struct i2c_msg msgs[1] = {
78                 { radio->client->addr, 0, sizeof(u16) * WRITE_REG_NUM,
79                         (void *)buf },
80         };
81
82         for (i = 0; i < WRITE_REG_NUM; i++)
83                 buf[i] = __cpu_to_be16(radio->registers[WRITE_INDEX(i)]);
84
85         if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
86                 return -EIO;
87
88         return 0;
89 }
90
91 int si470x_disconnect_check(struct si470x_device *radio)
92 {
93         return 0;
94 }
95
96 static int si470x_fops_open(struct file *file)
97 {
98         struct si470x_device *radio = video_drvdata(file);
99         int retval = 0;
100
101         mutex_lock(&radio->lock);
102         radio->users++;
103
104         if (radio->users == 1)
105                 /* start radio */
106                 retval = si470x_start(radio);
107         mutex_unlock(&radio->lock);
108
109         return retval;
110 }
111
112 static int si470x_fops_release(struct file *file)
113 {
114         struct si470x_device *radio = video_drvdata(file);
115         int retval = 0;
116
117         /* safety check */
118         if (!radio)
119                 return -ENODEV;
120
121         mutex_lock(&radio->lock);
122         radio->users--;
123         if (radio->users == 0)
124                 /* stop radio */
125                 retval = si470x_stop(radio);
126         mutex_unlock(&radio->lock);
127
128         return retval;
129 }
130
131 const struct v4l2_file_operations si470x_fops = {
132         .owner          = THIS_MODULE,
133         .ioctl          = video_ioctl2,
134         .open           = si470x_fops_open,
135         .release        = si470x_fops_release,
136 };
137
138 int si470x_vidioc_querycap(struct file *file, void *priv,
139                 struct v4l2_capability *capability)
140 {
141         strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
142         strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
143         capability->version = DRIVER_KERNEL_VERSION;
144         capability->capabilities = V4L2_CAP_HW_FREQ_SEEK |
145                 V4L2_CAP_TUNER | V4L2_CAP_RADIO;
146
147         return 0;
148 }
149
150 static int __devinit si470x_i2c_probe(struct i2c_client *client,
151                 const struct i2c_device_id *id)
152 {
153         struct si470x_device *radio;
154         int retval = 0;
155
156         /* private data allocation and initialization */
157         radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
158         if (!radio) {
159                 retval = -ENOMEM;
160                 goto err_initial;
161         }
162         radio->client = client;
163         radio->users = 0;
164         mutex_init(&radio->lock);
165
166         /* video device allocation and initialization */
167         radio->videodev = video_device_alloc();
168         if (!radio->videodev) {
169                 retval = -ENOMEM;
170                 goto err_radio;
171         }
172         memcpy(radio->videodev, &si470x_viddev_template,
173                         sizeof(si470x_viddev_template));
174         video_set_drvdata(radio->videodev, radio);
175
176         /* power up : need 110ms */
177         radio->registers[POWERCFG] = POWERCFG_ENABLE;
178         if (si470x_set_register(radio, POWERCFG) < 0) {
179                 retval = -EIO;
180                 goto err_all;
181         }
182         msleep(110);
183
184         /* show some infos about the specific si470x device */
185         if (si470x_get_all_registers(radio) < 0) {
186                 retval = -EIO;
187                 goto err_radio;
188         }
189         dev_info(&client->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
190                         radio->registers[DEVICEID], radio->registers[CHIPID]);
191
192         /* set initial frequency */
193         si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */
194
195         /* register video device */
196         retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, -1);
197         if (retval) {
198                 dev_warn(&client->dev, "Could not register video device\n");
199                 goto err_all;
200         }
201
202         i2c_set_clientdata(client, radio);
203
204         return 0;
205 err_all:
206         video_device_release(radio->videodev);
207 err_radio:
208         kfree(radio);
209 err_initial:
210         return retval;
211 }
212
213 static __devexit int si470x_i2c_remove(struct i2c_client *client)
214 {
215         struct si470x_device *radio = i2c_get_clientdata(client);
216
217         video_unregister_device(radio->videodev);
218         kfree(radio);
219         i2c_set_clientdata(client, NULL);
220
221         return 0;
222 }
223
224 static const struct i2c_device_id si470x_i2c_id[] = {
225         { "si470x", 0 },
226         { }
227 };
228 MODULE_DEVICE_TABLE(i2c, si470x_i2c_id);
229
230 static struct i2c_driver si470x_i2c_driver = {
231         .driver = {
232                 .name = "si470x",
233                 .owner = THIS_MODULE,
234         },
235         .probe = si470x_i2c_probe,
236         .remove = __devexit_p(si470x_i2c_remove),
237         .id_table = si470x_i2c_id,
238 };
239
240 static int __init si470x_i2c_init(void)
241 {
242         return i2c_add_driver(&si470x_i2c_driver);
243 }
244 module_init(si470x_i2c_init);
245
246 static void __exit si470x_i2c_exit(void)
247 {
248         i2c_del_driver(&si470x_i2c_driver);
249 }
250 module_exit(si470x_i2c_exit);
251
252 MODULE_DESCRIPTION("i2c radio driver for si470x fm radio receivers");
253 MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
254 MODULE_LICENSE("GPL");