diff options
author | Hans Verkuil <hans.verkuil@cisco.com> | 2013-12-13 06:51:25 -0500 |
---|---|---|
committer | Mauro Carvalho Chehab <m.chehab@samsung.com> | 2013-12-18 08:30:32 -0500 |
commit | 21326c461e10431767e817e858e66113336d361c (patch) | |
tree | e213e31a44f6d245c17ab8ff615878d4081e20f3 | |
parent | 5df2def55073596f6f50eb3337ece6a4a60dfcf9 (diff) |
[media] radio-raremono: add support for 'Thanko's Raremono' AM/FM/SW USB device
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Cc: Dinesh Ram <dinesh.ram@cern.ch>
Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>
-rw-r--r-- | drivers/media/radio/Kconfig | 14 | ||||
-rw-r--r-- | drivers/media/radio/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/radio/radio-raremono.c | 387 |
3 files changed, 402 insertions, 0 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index e92cec6fc480..192f36f2f4aa 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig | |||
@@ -129,6 +129,20 @@ config USB_KEENE | |||
129 | To compile this driver as a module, choose M here: the | 129 | To compile this driver as a module, choose M here: the |
130 | module will be called radio-keene. | 130 | module will be called radio-keene. |
131 | 131 | ||
132 | config USB_RAREMONO | ||
133 | tristate "Thanko's Raremono AM/FM/SW radio support" | ||
134 | depends on USB && VIDEO_V4L2 | ||
135 | ---help--- | ||
136 | The 'Thanko's Raremono' device contains the Si4734 chip from Silicon Labs Inc. | ||
137 | It is one of the very few or perhaps the only consumer USB radio device | ||
138 | to receive the AM/FM/SW bands. | ||
139 | |||
140 | Say Y here if you want to connect this type of AM/FM/SW receiver | ||
141 | to your computer's USB port. | ||
142 | |||
143 | To compile this driver as a module, choose M here: the | ||
144 | module will be called radio-raremono. | ||
145 | |||
132 | config USB_MA901 | 146 | config USB_MA901 |
133 | tristate "Masterkit MA901 USB FM radio support" | 147 | tristate "Masterkit MA901 USB FM radio support" |
134 | depends on USB && VIDEO_V4L2 | 148 | depends on USB && VIDEO_V4L2 |
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index eb1a3a034622..120e791199b2 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile | |||
@@ -32,6 +32,7 @@ obj-$(CONFIG_RADIO_TIMBERDALE) += radio-timb.o | |||
32 | obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o | 32 | obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o |
33 | obj-$(CONFIG_RADIO_WL128X) += wl128x/ | 33 | obj-$(CONFIG_RADIO_WL128X) += wl128x/ |
34 | obj-$(CONFIG_RADIO_TEA575X) += tea575x.o | 34 | obj-$(CONFIG_RADIO_TEA575X) += tea575x.o |
35 | obj-$(CONFIG_USB_RAREMONO) += radio-raremono.o | ||
35 | 36 | ||
36 | shark2-objs := radio-shark2.o radio-tea5777.o | 37 | shark2-objs := radio-shark2.o radio-tea5777.o |
37 | 38 | ||
diff --git a/drivers/media/radio/radio-raremono.c b/drivers/media/radio/radio-raremono.c new file mode 100644 index 000000000000..7b3bdbb1be73 --- /dev/null +++ b/drivers/media/radio/radio-raremono.c | |||
@@ -0,0 +1,387 @@ | |||
1 | /* | ||
2 | * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved. | ||
3 | * | ||
4 | * This program is free software; you may redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; version 2 of the License. | ||
7 | * | ||
8 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
9 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
10 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
11 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||
12 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||
13 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
14 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
15 | * SOFTWARE. | ||
16 | */ | ||
17 | |||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/init.h> | ||
21 | #include <linux/slab.h> | ||
22 | #include <linux/input.h> | ||
23 | #include <linux/usb.h> | ||
24 | #include <linux/hid.h> | ||
25 | #include <linux/mutex.h> | ||
26 | #include <linux/videodev2.h> | ||
27 | #include <asm/unaligned.h> | ||
28 | #include <media/v4l2-device.h> | ||
29 | #include <media/v4l2-ioctl.h> | ||
30 | #include <media/v4l2-ctrls.h> | ||
31 | #include <media/v4l2-event.h> | ||
32 | |||
33 | /* | ||
34 | * 'Thanko's Raremono' is a Japanese si4734-based AM/FM/SW USB receiver: | ||
35 | * | ||
36 | * http://www.raremono.jp/product/484.html/ | ||
37 | * | ||
38 | * The USB protocol has been reversed engineered using wireshark, initially | ||
39 | * by Dinesh Ram <dinesh.ram@cern.ch> and finished by Hans Verkuil | ||
40 | * <hverkuil@xs4all.nl>. | ||
41 | * | ||
42 | * Sadly the firmware used in this product hides lots of goodies since the | ||
43 | * si4734 has more features than are supported by the firmware. Oh well... | ||
44 | */ | ||
45 | |||
46 | /* driver and module definitions */ | ||
47 | MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>"); | ||
48 | MODULE_DESCRIPTION("Thanko's Raremono AM/FM/SW Receiver USB driver"); | ||
49 | MODULE_LICENSE("GPL v2"); | ||
50 | |||
51 | /* | ||
52 | * The Device announces itself as Cygnal Integrated Products, Inc. | ||
53 | * | ||
54 | * The vendor and product IDs (and in fact all other lsusb information as | ||
55 | * well) are identical to the si470x Silicon Labs USB FM Radio Reference | ||
56 | * Design board, even though this card has a si4734 device. Clearly the | ||
57 | * designer of this product never bothered to change the USB IDs. | ||
58 | */ | ||
59 | |||
60 | /* USB Device ID List */ | ||
61 | static struct usb_device_id usb_raremono_device_table[] = { | ||
62 | {USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) }, | ||
63 | { } /* Terminating entry */ | ||
64 | }; | ||
65 | |||
66 | MODULE_DEVICE_TABLE(usb, usb_raremono_device_table); | ||
67 | |||
68 | #define BUFFER_LENGTH 64 | ||
69 | |||
70 | /* Timeout is set to a high value, could probably be reduced. Need more tests */ | ||
71 | #define USB_TIMEOUT 10000 | ||
72 | |||
73 | /* Frequency limits in KHz */ | ||
74 | #define FM_FREQ_RANGE_LOW 64000 | ||
75 | #define FM_FREQ_RANGE_HIGH 108000 | ||
76 | |||
77 | #define AM_FREQ_RANGE_LOW 520 | ||
78 | #define AM_FREQ_RANGE_HIGH 1710 | ||
79 | |||
80 | #define SW_FREQ_RANGE_LOW 2300 | ||
81 | #define SW_FREQ_RANGE_HIGH 26100 | ||
82 | |||
83 | enum { BAND_FM, BAND_AM, BAND_SW }; | ||
84 | |||
85 | static const struct v4l2_frequency_band bands[] = { | ||
86 | /* Band FM */ | ||
87 | { | ||
88 | .type = V4L2_TUNER_RADIO, | ||
89 | .index = 0, | ||
90 | .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | | ||
91 | V4L2_TUNER_CAP_FREQ_BANDS, | ||
92 | .rangelow = FM_FREQ_RANGE_LOW * 16, | ||
93 | .rangehigh = FM_FREQ_RANGE_HIGH * 16, | ||
94 | .modulation = V4L2_BAND_MODULATION_FM, | ||
95 | }, | ||
96 | /* Band AM */ | ||
97 | { | ||
98 | .type = V4L2_TUNER_RADIO, | ||
99 | .index = 1, | ||
100 | .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, | ||
101 | .rangelow = AM_FREQ_RANGE_LOW * 16, | ||
102 | .rangehigh = AM_FREQ_RANGE_HIGH * 16, | ||
103 | .modulation = V4L2_BAND_MODULATION_AM, | ||
104 | }, | ||
105 | /* Band SW */ | ||
106 | { | ||
107 | .type = V4L2_TUNER_RADIO, | ||
108 | .index = 2, | ||
109 | .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, | ||
110 | .rangelow = SW_FREQ_RANGE_LOW * 16, | ||
111 | .rangehigh = SW_FREQ_RANGE_HIGH * 16, | ||
112 | .modulation = V4L2_BAND_MODULATION_AM, | ||
113 | }, | ||
114 | }; | ||
115 | |||
116 | struct raremono_device { | ||
117 | struct usb_device *usbdev; | ||
118 | struct usb_interface *intf; | ||
119 | struct video_device vdev; | ||
120 | struct v4l2_device v4l2_dev; | ||
121 | struct mutex lock; | ||
122 | |||
123 | u8 *buffer; | ||
124 | u32 band; | ||
125 | unsigned curfreq; | ||
126 | }; | ||
127 | |||
128 | static inline struct raremono_device *to_raremono_dev(struct v4l2_device *v4l2_dev) | ||
129 | { | ||
130 | return container_of(v4l2_dev, struct raremono_device, v4l2_dev); | ||
131 | } | ||
132 | |||
133 | /* Set frequency. */ | ||
134 | static int raremono_cmd_main(struct raremono_device *radio, unsigned band, unsigned freq) | ||
135 | { | ||
136 | unsigned band_offset; | ||
137 | int ret; | ||
138 | |||
139 | switch (band) { | ||
140 | case BAND_FM: | ||
141 | band_offset = 1; | ||
142 | freq /= 10; | ||
143 | break; | ||
144 | case BAND_AM: | ||
145 | band_offset = 0; | ||
146 | break; | ||
147 | default: | ||
148 | band_offset = 2; | ||
149 | break; | ||
150 | } | ||
151 | radio->buffer[0] = 0x04 + band_offset; | ||
152 | radio->buffer[1] = freq >> 8; | ||
153 | radio->buffer[2] = freq & 0xff; | ||
154 | |||
155 | ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), | ||
156 | HID_REQ_SET_REPORT, | ||
157 | USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, | ||
158 | 0x0300 + radio->buffer[0], 2, | ||
159 | radio->buffer, 3, USB_TIMEOUT); | ||
160 | |||
161 | if (ret < 0) { | ||
162 | dev_warn(radio->v4l2_dev.dev, "%s failed (%d)\n", __func__, ret); | ||
163 | return ret; | ||
164 | } | ||
165 | radio->curfreq = (band == BAND_FM) ? freq * 10 : freq; | ||
166 | return 0; | ||
167 | } | ||
168 | |||
169 | /* Handle unplugging the device. | ||
170 | * We call video_unregister_device in any case. | ||
171 | * The last function called in this procedure is | ||
172 | * usb_raremono_device_release. | ||
173 | */ | ||
174 | static void usb_raremono_disconnect(struct usb_interface *intf) | ||
175 | { | ||
176 | struct raremono_device *radio = to_raremono_dev(usb_get_intfdata(intf)); | ||
177 | |||
178 | dev_info(&intf->dev, "Thanko's Raremono disconnected\n"); | ||
179 | |||
180 | mutex_lock(&radio->lock); | ||
181 | usb_set_intfdata(intf, NULL); | ||
182 | video_unregister_device(&radio->vdev); | ||
183 | v4l2_device_disconnect(&radio->v4l2_dev); | ||
184 | mutex_unlock(&radio->lock); | ||
185 | v4l2_device_put(&radio->v4l2_dev); | ||
186 | } | ||
187 | |||
188 | /* | ||
189 | * Linux Video interface | ||
190 | */ | ||
191 | static int vidioc_querycap(struct file *file, void *priv, | ||
192 | struct v4l2_capability *v) | ||
193 | { | ||
194 | struct raremono_device *radio = video_drvdata(file); | ||
195 | |||
196 | strlcpy(v->driver, "radio-raremono", sizeof(v->driver)); | ||
197 | strlcpy(v->card, "Thanko's Raremono", sizeof(v->card)); | ||
198 | usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); | ||
199 | v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; | ||
200 | v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; | ||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | static int vidioc_enum_freq_bands(struct file *file, void *priv, | ||
205 | struct v4l2_frequency_band *band) | ||
206 | { | ||
207 | if (band->tuner != 0) | ||
208 | return -EINVAL; | ||
209 | |||
210 | if (band->index >= ARRAY_SIZE(bands)) | ||
211 | return -EINVAL; | ||
212 | |||
213 | *band = bands[band->index]; | ||
214 | |||
215 | return 0; | ||
216 | } | ||
217 | |||
218 | static int vidioc_g_tuner(struct file *file, void *priv, | ||
219 | struct v4l2_tuner *v) | ||
220 | { | ||
221 | struct raremono_device *radio = video_drvdata(file); | ||
222 | int ret; | ||
223 | |||
224 | if (v->index > 0) | ||
225 | return -EINVAL; | ||
226 | |||
227 | strlcpy(v->name, "AM/FM/SW", sizeof(v->name)); | ||
228 | v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | | ||
229 | V4L2_TUNER_CAP_FREQ_BANDS; | ||
230 | v->rangelow = AM_FREQ_RANGE_LOW * 16; | ||
231 | v->rangehigh = FM_FREQ_RANGE_HIGH * 16; | ||
232 | v->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; | ||
233 | v->audmode = (radio->curfreq < FM_FREQ_RANGE_LOW) ? | ||
234 | V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; | ||
235 | memset(radio->buffer, 1, BUFFER_LENGTH); | ||
236 | ret = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), | ||
237 | 1, 0xa1, 0x030d, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); | ||
238 | |||
239 | if (ret < 0) { | ||
240 | dev_warn(radio->v4l2_dev.dev, "%s failed (%d)\n", __func__, ret); | ||
241 | return ret; | ||
242 | } | ||
243 | v->signal = ((radio->buffer[1] & 0xf) << 8 | radio->buffer[2]) << 4; | ||
244 | return 0; | ||
245 | } | ||
246 | |||
247 | static int vidioc_s_tuner(struct file *file, void *priv, | ||
248 | const struct v4l2_tuner *v) | ||
249 | { | ||
250 | return v->index ? -EINVAL : 0; | ||
251 | } | ||
252 | |||
253 | static int vidioc_s_frequency(struct file *file, void *priv, | ||
254 | const struct v4l2_frequency *f) | ||
255 | { | ||
256 | struct raremono_device *radio = video_drvdata(file); | ||
257 | u32 freq = f->frequency; | ||
258 | unsigned band; | ||
259 | |||
260 | if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) | ||
261 | return -EINVAL; | ||
262 | |||
263 | if (f->frequency >= (FM_FREQ_RANGE_LOW + SW_FREQ_RANGE_HIGH) * 8) | ||
264 | band = BAND_FM; | ||
265 | else if (f->frequency <= (AM_FREQ_RANGE_HIGH + SW_FREQ_RANGE_LOW) * 8) | ||
266 | band = BAND_AM; | ||
267 | else | ||
268 | band = BAND_SW; | ||
269 | |||
270 | freq = clamp_t(u32, f->frequency, bands[band].rangelow, bands[band].rangehigh); | ||
271 | return raremono_cmd_main(radio, band, freq / 16); | ||
272 | } | ||
273 | |||
274 | static int vidioc_g_frequency(struct file *file, void *priv, | ||
275 | struct v4l2_frequency *f) | ||
276 | { | ||
277 | struct raremono_device *radio = video_drvdata(file); | ||
278 | |||
279 | if (f->tuner != 0) | ||
280 | return -EINVAL; | ||
281 | f->type = V4L2_TUNER_RADIO; | ||
282 | f->frequency = radio->curfreq * 16; | ||
283 | return 0; | ||
284 | } | ||
285 | |||
286 | /* File system interface */ | ||
287 | static const struct v4l2_file_operations usb_raremono_fops = { | ||
288 | .owner = THIS_MODULE, | ||
289 | .open = v4l2_fh_open, | ||
290 | .release = v4l2_fh_release, | ||
291 | .unlocked_ioctl = video_ioctl2, | ||
292 | }; | ||
293 | |||
294 | static const struct v4l2_ioctl_ops usb_raremono_ioctl_ops = { | ||
295 | .vidioc_querycap = vidioc_querycap, | ||
296 | .vidioc_g_tuner = vidioc_g_tuner, | ||
297 | .vidioc_s_tuner = vidioc_s_tuner, | ||
298 | .vidioc_g_frequency = vidioc_g_frequency, | ||
299 | .vidioc_s_frequency = vidioc_s_frequency, | ||
300 | .vidioc_enum_freq_bands = vidioc_enum_freq_bands, | ||
301 | }; | ||
302 | |||
303 | /* check if the device is present and register with v4l and usb if it is */ | ||
304 | static int usb_raremono_probe(struct usb_interface *intf, | ||
305 | const struct usb_device_id *id) | ||
306 | { | ||
307 | struct raremono_device *radio; | ||
308 | int retval = 0; | ||
309 | |||
310 | radio = devm_kzalloc(&intf->dev, sizeof(struct raremono_device), GFP_KERNEL); | ||
311 | if (radio) | ||
312 | radio->buffer = devm_kmalloc(&intf->dev, BUFFER_LENGTH, GFP_KERNEL); | ||
313 | |||
314 | if (!radio || !radio->buffer) | ||
315 | return -ENOMEM; | ||
316 | |||
317 | radio->usbdev = interface_to_usbdev(intf); | ||
318 | radio->intf = intf; | ||
319 | |||
320 | /* | ||
321 | * This device uses the same USB IDs as the si470x SiLabs reference | ||
322 | * design. So do an additional check: attempt to read the device ID | ||
323 | * from the si470x: the lower 12 bits are 0x0242 for the si470x. The | ||
324 | * Raremono always returns 0x0800 (the meaning of that is unknown, but | ||
325 | * at least it works). | ||
326 | * | ||
327 | * We use this check to determine which device we are dealing with. | ||
328 | */ | ||
329 | msleep(20); | ||
330 | retval = usb_control_msg(radio->usbdev, | ||
331 | usb_rcvctrlpipe(radio->usbdev, 0), | ||
332 | HID_REQ_GET_REPORT, | ||
333 | USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, | ||
334 | 1, 2, | ||
335 | radio->buffer, 3, 500); | ||
336 | if (retval != 3 || | ||
337 | (get_unaligned_be16(&radio->buffer[1]) & 0xfff) == 0x0242) { | ||
338 | dev_info(&intf->dev, "this is not Thanko's Raremono.\n"); | ||
339 | return -ENODEV; | ||
340 | } | ||
341 | |||
342 | dev_info(&intf->dev, "Thanko's Raremono connected: (%04X:%04X)\n", | ||
343 | id->idVendor, id->idProduct); | ||
344 | |||
345 | retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); | ||
346 | if (retval < 0) { | ||
347 | dev_err(&intf->dev, "couldn't register v4l2_device\n"); | ||
348 | return retval; | ||
349 | } | ||
350 | |||
351 | mutex_init(&radio->lock); | ||
352 | |||
353 | strlcpy(radio->vdev.name, radio->v4l2_dev.name, | ||
354 | sizeof(radio->vdev.name)); | ||
355 | radio->vdev.v4l2_dev = &radio->v4l2_dev; | ||
356 | radio->vdev.fops = &usb_raremono_fops; | ||
357 | radio->vdev.ioctl_ops = &usb_raremono_ioctl_ops; | ||
358 | radio->vdev.lock = &radio->lock; | ||
359 | radio->vdev.release = video_device_release_empty; | ||
360 | |||
361 | usb_set_intfdata(intf, &radio->v4l2_dev); | ||
362 | |||
363 | video_set_drvdata(&radio->vdev, radio); | ||
364 | set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags); | ||
365 | |||
366 | raremono_cmd_main(radio, BAND_FM, 95160); | ||
367 | |||
368 | retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); | ||
369 | if (retval == 0) { | ||
370 | dev_info(&intf->dev, "V4L2 device registered as %s\n", | ||
371 | video_device_node_name(&radio->vdev)); | ||
372 | return 0; | ||
373 | } | ||
374 | dev_err(&intf->dev, "could not register video device\n"); | ||
375 | v4l2_device_unregister(&radio->v4l2_dev); | ||
376 | return retval; | ||
377 | } | ||
378 | |||
379 | /* USB subsystem interface */ | ||
380 | static struct usb_driver usb_raremono_driver = { | ||
381 | .name = "radio-raremono", | ||
382 | .probe = usb_raremono_probe, | ||
383 | .disconnect = usb_raremono_disconnect, | ||
384 | .id_table = usb_raremono_device_table, | ||
385 | }; | ||
386 | |||
387 | module_usb_driver(usb_raremono_driver); | ||