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