1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2b09cd163SJoonyoung Shim /*
3b09cd163SJoonyoung Shim * drivers/media/radio/si470x/radio-si470x-usb.c
4b09cd163SJoonyoung Shim *
5b09cd163SJoonyoung Shim * USB driver for radios with Silicon Labs Si470x FM Radio Receivers
6b09cd163SJoonyoung Shim *
7b09cd163SJoonyoung Shim * Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
8b09cd163SJoonyoung Shim */
9b09cd163SJoonyoung Shim
10b09cd163SJoonyoung Shim
11b09cd163SJoonyoung Shim /*
12b09cd163SJoonyoung Shim * ToDo:
13b09cd163SJoonyoung Shim * - add firmware download/update support
14b09cd163SJoonyoung Shim */
15b09cd163SJoonyoung Shim
16b09cd163SJoonyoung Shim
17b09cd163SJoonyoung Shim /* driver definitions */
18b09cd163SJoonyoung Shim #define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>"
192908249fSKees Cook #define DRIVER_CARD "Silicon Labs Si470x FM Radio"
20b09cd163SJoonyoung Shim #define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers"
21b09cd163SJoonyoung Shim #define DRIVER_VERSION "1.0.10"
22b09cd163SJoonyoung Shim
23b09cd163SJoonyoung Shim /* kernel includes */
24b09cd163SJoonyoung Shim #include <linux/usb.h>
25b09cd163SJoonyoung Shim #include <linux/hid.h>
265a0e3ad6STejun Heo #include <linux/slab.h>
27b09cd163SJoonyoung Shim
28b09cd163SJoonyoung Shim #include "radio-si470x.h"
29b09cd163SJoonyoung Shim
30b09cd163SJoonyoung Shim
31b09cd163SJoonyoung Shim /* USB Device ID List */
321ab2234eSArvind Yadav static const struct usb_device_id si470x_usb_driver_id_table[] = {
33b09cd163SJoonyoung Shim /* Silicon Labs USB FM Radio Reference Design */
34b09cd163SJoonyoung Shim { USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) },
35b09cd163SJoonyoung Shim /* ADS/Tech FM Radio Receiver (formerly Instant FM Music) */
36b09cd163SJoonyoung Shim { USB_DEVICE_AND_INTERFACE_INFO(0x06e1, 0xa155, USB_CLASS_HID, 0, 0) },
37b09cd163SJoonyoung Shim /* KWorld USB FM Radio SnapMusic Mobile 700 (FM700) */
38b09cd163SJoonyoung Shim { USB_DEVICE_AND_INTERFACE_INFO(0x1b80, 0xd700, USB_CLASS_HID, 0, 0) },
39b09cd163SJoonyoung Shim /* Sanei Electric, Inc. FM USB Radio (sold as DealExtreme.com PCear) */
40b09cd163SJoonyoung Shim { USB_DEVICE_AND_INTERFACE_INFO(0x10c5, 0x819a, USB_CLASS_HID, 0, 0) },
4111030183SHans de Goede /* Axentia ALERT FM USB Receiver */
4211030183SHans de Goede { USB_DEVICE_AND_INTERFACE_INFO(0x12cf, 0x7111, USB_CLASS_HID, 0, 0) },
43b09cd163SJoonyoung Shim /* Terminating entry */
44b09cd163SJoonyoung Shim { }
45b09cd163SJoonyoung Shim };
46b09cd163SJoonyoung Shim MODULE_DEVICE_TABLE(usb, si470x_usb_driver_id_table);
47b09cd163SJoonyoung Shim
48b09cd163SJoonyoung Shim
49b09cd163SJoonyoung Shim
50b09cd163SJoonyoung Shim /**************************************************************************
51b09cd163SJoonyoung Shim * Module Parameters
52b09cd163SJoonyoung Shim **************************************************************************/
53b09cd163SJoonyoung Shim
54b09cd163SJoonyoung Shim /* Radio Nr */
55b09cd163SJoonyoung Shim static int radio_nr = -1;
56b09cd163SJoonyoung Shim module_param(radio_nr, int, 0444);
57b09cd163SJoonyoung Shim MODULE_PARM_DESC(radio_nr, "Radio Nr");
58b09cd163SJoonyoung Shim
59b09cd163SJoonyoung Shim /* USB timeout */
60b09cd163SJoonyoung Shim static unsigned int usb_timeout = 500;
61b09cd163SJoonyoung Shim module_param(usb_timeout, uint, 0644);
62b09cd163SJoonyoung Shim MODULE_PARM_DESC(usb_timeout, "USB timeout (ms): *500*");
63b09cd163SJoonyoung Shim
64b09cd163SJoonyoung Shim /* RDS buffer blocks */
65b09cd163SJoonyoung Shim static unsigned int rds_buf = 100;
66b09cd163SJoonyoung Shim module_param(rds_buf, uint, 0444);
67b09cd163SJoonyoung Shim MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
68b09cd163SJoonyoung Shim
69b09cd163SJoonyoung Shim /* RDS maximum block errors */
70b09cd163SJoonyoung Shim static unsigned short max_rds_errors = 1;
71b09cd163SJoonyoung Shim /* 0 means 0 errors requiring correction */
72b09cd163SJoonyoung Shim /* 1 means 1-2 errors requiring correction (used by original USBRadio.exe) */
73b09cd163SJoonyoung Shim /* 2 means 3-5 errors requiring correction */
74b09cd163SJoonyoung Shim /* 3 means 6+ errors or errors in checkword, correction not possible */
75b09cd163SJoonyoung Shim module_param(max_rds_errors, ushort, 0644);
76b09cd163SJoonyoung Shim MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
77b09cd163SJoonyoung Shim
78b09cd163SJoonyoung Shim
79b09cd163SJoonyoung Shim
80b09cd163SJoonyoung Shim /**************************************************************************
81b09cd163SJoonyoung Shim * USB HID Reports
82b09cd163SJoonyoung Shim **************************************************************************/
83b09cd163SJoonyoung Shim
84b09cd163SJoonyoung Shim /* Reports 1-16 give direct read/write access to the 16 Si470x registers */
85b09cd163SJoonyoung Shim /* with the (REPORT_ID - 1) corresponding to the register address across USB */
86b09cd163SJoonyoung Shim /* endpoint 0 using GET_REPORT and SET_REPORT */
87b09cd163SJoonyoung Shim #define REGISTER_REPORT_SIZE (RADIO_REGISTER_SIZE + 1)
88b09cd163SJoonyoung Shim #define REGISTER_REPORT(reg) ((reg) + 1)
89b09cd163SJoonyoung Shim
90b09cd163SJoonyoung Shim /* Report 17 gives direct read/write access to the entire Si470x register */
91b09cd163SJoonyoung Shim /* map across endpoint 0 using GET_REPORT and SET_REPORT */
92b09cd163SJoonyoung Shim #define ENTIRE_REPORT_SIZE (RADIO_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
93b09cd163SJoonyoung Shim #define ENTIRE_REPORT 17
94b09cd163SJoonyoung Shim
95b09cd163SJoonyoung Shim /* Report 18 is used to send the lowest 6 Si470x registers up the HID */
96b09cd163SJoonyoung Shim /* interrupt endpoint 1 to Windows every 20 milliseconds for status */
97b09cd163SJoonyoung Shim #define RDS_REPORT_SIZE (RDS_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
98b09cd163SJoonyoung Shim #define RDS_REPORT 18
99b09cd163SJoonyoung Shim
100b09cd163SJoonyoung Shim /* Report 19: LED state */
101b09cd163SJoonyoung Shim #define LED_REPORT_SIZE 3
102b09cd163SJoonyoung Shim #define LED_REPORT 19
103b09cd163SJoonyoung Shim
104b09cd163SJoonyoung Shim /* Report 19: stream */
105b09cd163SJoonyoung Shim #define STREAM_REPORT_SIZE 3
106b09cd163SJoonyoung Shim #define STREAM_REPORT 19
107b09cd163SJoonyoung Shim
108b09cd163SJoonyoung Shim /* Report 20: scratch */
109b09cd163SJoonyoung Shim #define SCRATCH_PAGE_SIZE 63
110b09cd163SJoonyoung Shim #define SCRATCH_REPORT_SIZE (SCRATCH_PAGE_SIZE + 1)
111b09cd163SJoonyoung Shim #define SCRATCH_REPORT 20
112b09cd163SJoonyoung Shim
113b09cd163SJoonyoung Shim /* Reports 19-22: flash upgrade of the C8051F321 */
114b09cd163SJoonyoung Shim #define WRITE_REPORT_SIZE 4
115b09cd163SJoonyoung Shim #define WRITE_REPORT 19
116b09cd163SJoonyoung Shim #define FLASH_REPORT_SIZE 64
117b09cd163SJoonyoung Shim #define FLASH_REPORT 20
118b09cd163SJoonyoung Shim #define CRC_REPORT_SIZE 3
119b09cd163SJoonyoung Shim #define CRC_REPORT 21
120b09cd163SJoonyoung Shim #define RESPONSE_REPORT_SIZE 2
121b09cd163SJoonyoung Shim #define RESPONSE_REPORT 22
122b09cd163SJoonyoung Shim
123b09cd163SJoonyoung Shim /* Report 23: currently unused, but can accept 60 byte reports on the HID */
124b09cd163SJoonyoung Shim /* interrupt out endpoint 2 every 1 millisecond */
125b09cd163SJoonyoung Shim #define UNUSED_REPORT 23
126b09cd163SJoonyoung Shim
127567e2e96SHans Verkuil #define MAX_REPORT_SIZE 64
128567e2e96SHans Verkuil
129b09cd163SJoonyoung Shim
130b09cd163SJoonyoung Shim
131b09cd163SJoonyoung Shim /**************************************************************************
1329dcb79c2STobias Lorenz * Software/Hardware Versions from Scratch Page
133b09cd163SJoonyoung Shim **************************************************************************/
134b09cd163SJoonyoung Shim #define RADIO_HW_VERSION 1
135b09cd163SJoonyoung Shim
136b09cd163SJoonyoung Shim
137b09cd163SJoonyoung Shim
138b09cd163SJoonyoung Shim /**************************************************************************
139b09cd163SJoonyoung Shim * LED State Definitions
140b09cd163SJoonyoung Shim **************************************************************************/
141b09cd163SJoonyoung Shim #define LED_COMMAND 0x35
142b09cd163SJoonyoung Shim
143b09cd163SJoonyoung Shim #define NO_CHANGE_LED 0x00
144b09cd163SJoonyoung Shim #define ALL_COLOR_LED 0x01 /* streaming state */
145b09cd163SJoonyoung Shim #define BLINK_GREEN_LED 0x02 /* connect state */
146b09cd163SJoonyoung Shim #define BLINK_RED_LED 0x04
147b09cd163SJoonyoung Shim #define BLINK_ORANGE_LED 0x10 /* disconnect state */
148b09cd163SJoonyoung Shim #define SOLID_GREEN_LED 0x20 /* tuning/seeking state */
149b09cd163SJoonyoung Shim #define SOLID_RED_LED 0x40 /* bootload state */
150b09cd163SJoonyoung Shim #define SOLID_ORANGE_LED 0x80
151b09cd163SJoonyoung Shim
152b09cd163SJoonyoung Shim
153b09cd163SJoonyoung Shim
154b09cd163SJoonyoung Shim /**************************************************************************
155b09cd163SJoonyoung Shim * Stream State Definitions
156b09cd163SJoonyoung Shim **************************************************************************/
157b09cd163SJoonyoung Shim #define STREAM_COMMAND 0x36
158b09cd163SJoonyoung Shim #define STREAM_VIDPID 0x00
159b09cd163SJoonyoung Shim #define STREAM_AUDIO 0xff
160b09cd163SJoonyoung Shim
161b09cd163SJoonyoung Shim
162b09cd163SJoonyoung Shim
163b09cd163SJoonyoung Shim /**************************************************************************
164b09cd163SJoonyoung Shim * Bootloader / Flash Commands
165b09cd163SJoonyoung Shim **************************************************************************/
166b09cd163SJoonyoung Shim
167b09cd163SJoonyoung Shim /* unique id sent to bootloader and required to put into a bootload state */
168b09cd163SJoonyoung Shim #define UNIQUE_BL_ID 0x34
169b09cd163SJoonyoung Shim
170b09cd163SJoonyoung Shim /* mask for the flash data */
171b09cd163SJoonyoung Shim #define FLASH_DATA_MASK 0x55
172b09cd163SJoonyoung Shim
173b09cd163SJoonyoung Shim /* bootloader commands */
174b09cd163SJoonyoung Shim #define GET_SW_VERSION_COMMAND 0x00
175b09cd163SJoonyoung Shim #define SET_PAGE_COMMAND 0x01
176b09cd163SJoonyoung Shim #define ERASE_PAGE_COMMAND 0x02
177b09cd163SJoonyoung Shim #define WRITE_PAGE_COMMAND 0x03
178b09cd163SJoonyoung Shim #define CRC_ON_PAGE_COMMAND 0x04
179b09cd163SJoonyoung Shim #define READ_FLASH_BYTE_COMMAND 0x05
180b09cd163SJoonyoung Shim #define RESET_DEVICE_COMMAND 0x06
181b09cd163SJoonyoung Shim #define GET_HW_VERSION_COMMAND 0x07
182b09cd163SJoonyoung Shim #define BLANK 0xff
183b09cd163SJoonyoung Shim
184b09cd163SJoonyoung Shim /* bootloader command responses */
185b09cd163SJoonyoung Shim #define COMMAND_OK 0x01
186b09cd163SJoonyoung Shim #define COMMAND_FAILED 0x02
187b09cd163SJoonyoung Shim #define COMMAND_PENDING 0x03
188b09cd163SJoonyoung Shim
189b09cd163SJoonyoung Shim
190b09cd163SJoonyoung Shim
191b09cd163SJoonyoung Shim /**************************************************************************
192b09cd163SJoonyoung Shim * General Driver Functions - REGISTER_REPORTs
193b09cd163SJoonyoung Shim **************************************************************************/
194b09cd163SJoonyoung Shim
195b09cd163SJoonyoung Shim /*
196b09cd163SJoonyoung Shim * si470x_get_report - receive a HID report
197b09cd163SJoonyoung Shim */
si470x_get_report(struct si470x_device * radio,void * buf,int size)198b09cd163SJoonyoung Shim static int si470x_get_report(struct si470x_device *radio, void *buf, int size)
199b09cd163SJoonyoung Shim {
200567e2e96SHans Verkuil unsigned char *report = buf;
201b09cd163SJoonyoung Shim int retval;
202b09cd163SJoonyoung Shim
203b09cd163SJoonyoung Shim retval = usb_control_msg(radio->usbdev,
204b09cd163SJoonyoung Shim usb_rcvctrlpipe(radio->usbdev, 0),
205b09cd163SJoonyoung Shim HID_REQ_GET_REPORT,
206b09cd163SJoonyoung Shim USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
207b09cd163SJoonyoung Shim report[0], 2,
208b09cd163SJoonyoung Shim buf, size, usb_timeout);
209b09cd163SJoonyoung Shim
210b09cd163SJoonyoung Shim if (retval < 0)
211a9d6fd5eSJoonyoung Shim dev_warn(&radio->intf->dev,
212a9d6fd5eSJoonyoung Shim "si470x_get_report: usb_control_msg returned %d\n",
213b09cd163SJoonyoung Shim retval);
214b09cd163SJoonyoung Shim return retval;
215b09cd163SJoonyoung Shim }
216b09cd163SJoonyoung Shim
217b09cd163SJoonyoung Shim
218b09cd163SJoonyoung Shim /*
219b09cd163SJoonyoung Shim * si470x_set_report - send a HID report
220b09cd163SJoonyoung Shim */
si470x_set_report(struct si470x_device * radio,void * buf,int size)221b09cd163SJoonyoung Shim static int si470x_set_report(struct si470x_device *radio, void *buf, int size)
222b09cd163SJoonyoung Shim {
223567e2e96SHans Verkuil unsigned char *report = buf;
224b09cd163SJoonyoung Shim int retval;
225b09cd163SJoonyoung Shim
226b09cd163SJoonyoung Shim retval = usb_control_msg(radio->usbdev,
227b09cd163SJoonyoung Shim usb_sndctrlpipe(radio->usbdev, 0),
228b09cd163SJoonyoung Shim HID_REQ_SET_REPORT,
229b09cd163SJoonyoung Shim USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
230b09cd163SJoonyoung Shim report[0], 2,
231b09cd163SJoonyoung Shim buf, size, usb_timeout);
232b09cd163SJoonyoung Shim
233b09cd163SJoonyoung Shim if (retval < 0)
234a9d6fd5eSJoonyoung Shim dev_warn(&radio->intf->dev,
235a9d6fd5eSJoonyoung Shim "si470x_set_report: usb_control_msg returned %d\n",
236b09cd163SJoonyoung Shim retval);
237b09cd163SJoonyoung Shim return retval;
238b09cd163SJoonyoung Shim }
239b09cd163SJoonyoung Shim
240b09cd163SJoonyoung Shim
241b09cd163SJoonyoung Shim /*
242b09cd163SJoonyoung Shim * si470x_get_register - read register
243b09cd163SJoonyoung Shim */
si470x_get_register(struct si470x_device * radio,int regnr)24458757984SMauro Carvalho Chehab static int si470x_get_register(struct si470x_device *radio, int regnr)
245b09cd163SJoonyoung Shim {
246b09cd163SJoonyoung Shim int retval;
247b09cd163SJoonyoung Shim
248567e2e96SHans Verkuil radio->usb_buf[0] = REGISTER_REPORT(regnr);
249b09cd163SJoonyoung Shim
250567e2e96SHans Verkuil retval = si470x_get_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE);
251b09cd163SJoonyoung Shim
252b09cd163SJoonyoung Shim if (retval >= 0)
253567e2e96SHans Verkuil radio->registers[regnr] = get_unaligned_be16(&radio->usb_buf[1]);
254b09cd163SJoonyoung Shim
255b09cd163SJoonyoung Shim return (retval < 0) ? -EINVAL : 0;
256b09cd163SJoonyoung Shim }
257b09cd163SJoonyoung Shim
258b09cd163SJoonyoung Shim
259b09cd163SJoonyoung Shim /*
260b09cd163SJoonyoung Shim * si470x_set_register - write register
261b09cd163SJoonyoung Shim */
si470x_set_register(struct si470x_device * radio,int regnr)26258757984SMauro Carvalho Chehab static int si470x_set_register(struct si470x_device *radio, int regnr)
263b09cd163SJoonyoung Shim {
264b09cd163SJoonyoung Shim int retval;
265b09cd163SJoonyoung Shim
266567e2e96SHans Verkuil radio->usb_buf[0] = REGISTER_REPORT(regnr);
267567e2e96SHans Verkuil put_unaligned_be16(radio->registers[regnr], &radio->usb_buf[1]);
268b09cd163SJoonyoung Shim
269567e2e96SHans Verkuil retval = si470x_set_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE);
270b09cd163SJoonyoung Shim
271b09cd163SJoonyoung Shim return (retval < 0) ? -EINVAL : 0;
272b09cd163SJoonyoung Shim }
273b09cd163SJoonyoung Shim
274b09cd163SJoonyoung Shim
275b09cd163SJoonyoung Shim
276b09cd163SJoonyoung Shim /**************************************************************************
277b09cd163SJoonyoung Shim * General Driver Functions - ENTIRE_REPORT
278b09cd163SJoonyoung Shim **************************************************************************/
279b09cd163SJoonyoung Shim
280b09cd163SJoonyoung Shim /*
281b09cd163SJoonyoung Shim * si470x_get_all_registers - read entire registers
282b09cd163SJoonyoung Shim */
si470x_get_all_registers(struct si470x_device * radio)283b09cd163SJoonyoung Shim static int si470x_get_all_registers(struct si470x_device *radio)
284b09cd163SJoonyoung Shim {
285b09cd163SJoonyoung Shim int retval;
286b09cd163SJoonyoung Shim unsigned char regnr;
287b09cd163SJoonyoung Shim
288567e2e96SHans Verkuil radio->usb_buf[0] = ENTIRE_REPORT;
289b09cd163SJoonyoung Shim
290567e2e96SHans Verkuil retval = si470x_get_report(radio, radio->usb_buf, ENTIRE_REPORT_SIZE);
291b09cd163SJoonyoung Shim
292b09cd163SJoonyoung Shim if (retval >= 0)
293b09cd163SJoonyoung Shim for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++)
294b09cd163SJoonyoung Shim radio->registers[regnr] = get_unaligned_be16(
295567e2e96SHans Verkuil &radio->usb_buf[regnr * RADIO_REGISTER_SIZE + 1]);
296b09cd163SJoonyoung Shim
297b09cd163SJoonyoung Shim return (retval < 0) ? -EINVAL : 0;
298b09cd163SJoonyoung Shim }
299b09cd163SJoonyoung Shim
300b09cd163SJoonyoung Shim
301b09cd163SJoonyoung Shim
302b09cd163SJoonyoung Shim /**************************************************************************
303b09cd163SJoonyoung Shim * General Driver Functions - LED_REPORT
304b09cd163SJoonyoung Shim **************************************************************************/
305b09cd163SJoonyoung Shim
306b09cd163SJoonyoung Shim /*
307b09cd163SJoonyoung Shim * si470x_set_led_state - sets the led state
308b09cd163SJoonyoung Shim */
si470x_set_led_state(struct si470x_device * radio,unsigned char led_state)309b09cd163SJoonyoung Shim static int si470x_set_led_state(struct si470x_device *radio,
310b09cd163SJoonyoung Shim unsigned char led_state)
311b09cd163SJoonyoung Shim {
312b09cd163SJoonyoung Shim int retval;
313b09cd163SJoonyoung Shim
314567e2e96SHans Verkuil radio->usb_buf[0] = LED_REPORT;
315567e2e96SHans Verkuil radio->usb_buf[1] = LED_COMMAND;
316567e2e96SHans Verkuil radio->usb_buf[2] = led_state;
317b09cd163SJoonyoung Shim
318567e2e96SHans Verkuil retval = si470x_set_report(radio, radio->usb_buf, LED_REPORT_SIZE);
319b09cd163SJoonyoung Shim
320b09cd163SJoonyoung Shim return (retval < 0) ? -EINVAL : 0;
321b09cd163SJoonyoung Shim }
322b09cd163SJoonyoung Shim
323b09cd163SJoonyoung Shim
324b09cd163SJoonyoung Shim
325b09cd163SJoonyoung Shim /**************************************************************************
326b09cd163SJoonyoung Shim * General Driver Functions - SCRATCH_REPORT
327b09cd163SJoonyoung Shim **************************************************************************/
328b09cd163SJoonyoung Shim
329b09cd163SJoonyoung Shim /*
330b09cd163SJoonyoung Shim * si470x_get_scratch_versions - gets the scratch page and version infos
331b09cd163SJoonyoung Shim */
si470x_get_scratch_page_versions(struct si470x_device * radio)332b09cd163SJoonyoung Shim static int si470x_get_scratch_page_versions(struct si470x_device *radio)
333b09cd163SJoonyoung Shim {
334b09cd163SJoonyoung Shim int retval;
335b09cd163SJoonyoung Shim
336567e2e96SHans Verkuil radio->usb_buf[0] = SCRATCH_REPORT;
337b09cd163SJoonyoung Shim
338567e2e96SHans Verkuil retval = si470x_get_report(radio, radio->usb_buf, SCRATCH_REPORT_SIZE);
339b09cd163SJoonyoung Shim
340b09cd163SJoonyoung Shim if (retval < 0)
341cbfc9080SMauro Carvalho Chehab dev_warn(&radio->intf->dev, "si470x_get_scratch: si470x_get_report returned %d\n",
342cbfc9080SMauro Carvalho Chehab retval);
343b09cd163SJoonyoung Shim else {
344567e2e96SHans Verkuil radio->software_version = radio->usb_buf[1];
345567e2e96SHans Verkuil radio->hardware_version = radio->usb_buf[2];
346b09cd163SJoonyoung Shim }
347b09cd163SJoonyoung Shim
348b09cd163SJoonyoung Shim return (retval < 0) ? -EINVAL : 0;
349b09cd163SJoonyoung Shim }
350b09cd163SJoonyoung Shim
351b09cd163SJoonyoung Shim
352b09cd163SJoonyoung Shim
353b09cd163SJoonyoung Shim /**************************************************************************
354b09cd163SJoonyoung Shim * RDS Driver Functions
355b09cd163SJoonyoung Shim **************************************************************************/
356b09cd163SJoonyoung Shim
357b09cd163SJoonyoung Shim /*
358b09cd163SJoonyoung Shim * si470x_int_in_callback - rds callback and processing function
359b09cd163SJoonyoung Shim *
360b09cd163SJoonyoung Shim * TODO: do we need to use mutex locks in some sections?
361b09cd163SJoonyoung Shim */
si470x_int_in_callback(struct urb * urb)362b09cd163SJoonyoung Shim static void si470x_int_in_callback(struct urb *urb)
363b09cd163SJoonyoung Shim {
364b09cd163SJoonyoung Shim struct si470x_device *radio = urb->context;
365b09cd163SJoonyoung Shim int retval;
366b09cd163SJoonyoung Shim unsigned char regnr;
367b09cd163SJoonyoung Shim unsigned char blocknum;
368b09cd163SJoonyoung Shim unsigned short bler; /* rds block errors */
369b09cd163SJoonyoung Shim unsigned short rds;
370b09cd163SJoonyoung Shim unsigned char tmpbuf[3];
371b09cd163SJoonyoung Shim
372b09cd163SJoonyoung Shim if (urb->status) {
373b09cd163SJoonyoung Shim if (urb->status == -ENOENT ||
374b09cd163SJoonyoung Shim urb->status == -ECONNRESET ||
375b09cd163SJoonyoung Shim urb->status == -ESHUTDOWN) {
376b09cd163SJoonyoung Shim return;
377b09cd163SJoonyoung Shim } else {
378a9d6fd5eSJoonyoung Shim dev_warn(&radio->intf->dev,
379a9d6fd5eSJoonyoung Shim "non-zero urb status (%d)\n", urb->status);
380b09cd163SJoonyoung Shim goto resubmit; /* Maybe we can recover. */
381b09cd163SJoonyoung Shim }
382b09cd163SJoonyoung Shim }
383b09cd163SJoonyoung Shim
38486ef3f78SHans de Goede /* Sometimes the device returns len 0 packets */
38586ef3f78SHans de Goede if (urb->actual_length != RDS_REPORT_SIZE)
386b09cd163SJoonyoung Shim goto resubmit;
387b09cd163SJoonyoung Shim
38886ef3f78SHans de Goede radio->registers[STATUSRSSI] =
38986ef3f78SHans de Goede get_unaligned_be16(&radio->int_in_buffer[1]);
39086ef3f78SHans de Goede
39177947111SHans de Goede if (radio->registers[STATUSRSSI] & STATUSRSSI_STC)
39277947111SHans de Goede complete(&radio->completion);
39377947111SHans de Goede
39486ef3f78SHans de Goede if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS)) {
395b09cd163SJoonyoung Shim /* Update RDS registers with URB data */
39686ef3f78SHans de Goede for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++)
397b09cd163SJoonyoung Shim radio->registers[STATUSRSSI + regnr] =
398b09cd163SJoonyoung Shim get_unaligned_be16(&radio->int_in_buffer[
399b09cd163SJoonyoung Shim regnr * RADIO_REGISTER_SIZE + 1]);
400b09cd163SJoonyoung Shim /* get rds blocks */
401b09cd163SJoonyoung Shim if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {
402b09cd163SJoonyoung Shim /* No RDS group ready, better luck next time */
403b09cd163SJoonyoung Shim goto resubmit;
404b09cd163SJoonyoung Shim }
405b09cd163SJoonyoung Shim if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) {
406b09cd163SJoonyoung Shim /* RDS decoder not synchronized */
407b09cd163SJoonyoung Shim goto resubmit;
408b09cd163SJoonyoung Shim }
409b09cd163SJoonyoung Shim for (blocknum = 0; blocknum < 4; blocknum++) {
410b09cd163SJoonyoung Shim switch (blocknum) {
411b09cd163SJoonyoung Shim default:
412b09cd163SJoonyoung Shim bler = (radio->registers[STATUSRSSI] &
413b09cd163SJoonyoung Shim STATUSRSSI_BLERA) >> 9;
414b09cd163SJoonyoung Shim rds = radio->registers[RDSA];
415b09cd163SJoonyoung Shim break;
416b09cd163SJoonyoung Shim case 1:
417b09cd163SJoonyoung Shim bler = (radio->registers[READCHAN] &
418b09cd163SJoonyoung Shim READCHAN_BLERB) >> 14;
419b09cd163SJoonyoung Shim rds = radio->registers[RDSB];
420b09cd163SJoonyoung Shim break;
421b09cd163SJoonyoung Shim case 2:
422b09cd163SJoonyoung Shim bler = (radio->registers[READCHAN] &
423b09cd163SJoonyoung Shim READCHAN_BLERC) >> 12;
424b09cd163SJoonyoung Shim rds = radio->registers[RDSC];
425b09cd163SJoonyoung Shim break;
426b09cd163SJoonyoung Shim case 3:
427b09cd163SJoonyoung Shim bler = (radio->registers[READCHAN] &
428b09cd163SJoonyoung Shim READCHAN_BLERD) >> 10;
429b09cd163SJoonyoung Shim rds = radio->registers[RDSD];
430b09cd163SJoonyoung Shim break;
431c2c1b415SPeter Senna Tschudin }
432b09cd163SJoonyoung Shim
433b09cd163SJoonyoung Shim /* Fill the V4L2 RDS buffer */
434b09cd163SJoonyoung Shim put_unaligned_le16(rds, &tmpbuf);
435b09cd163SJoonyoung Shim tmpbuf[2] = blocknum; /* offset name */
436b09cd163SJoonyoung Shim tmpbuf[2] |= blocknum << 3; /* received offset */
437b09cd163SJoonyoung Shim if (bler > max_rds_errors)
438b09cd163SJoonyoung Shim tmpbuf[2] |= 0x80; /* uncorrectable errors */
439b09cd163SJoonyoung Shim else if (bler > 0)
440b09cd163SJoonyoung Shim tmpbuf[2] |= 0x40; /* corrected error(s) */
441b09cd163SJoonyoung Shim
442b09cd163SJoonyoung Shim /* copy RDS block to internal buffer */
443b09cd163SJoonyoung Shim memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);
444b09cd163SJoonyoung Shim radio->wr_index += 3;
445b09cd163SJoonyoung Shim
446b09cd163SJoonyoung Shim /* wrap write pointer */
447b09cd163SJoonyoung Shim if (radio->wr_index >= radio->buf_size)
448b09cd163SJoonyoung Shim radio->wr_index = 0;
449b09cd163SJoonyoung Shim
450b09cd163SJoonyoung Shim /* check for overflow */
451b09cd163SJoonyoung Shim if (radio->wr_index == radio->rd_index) {
452b09cd163SJoonyoung Shim /* increment and wrap read pointer */
453b09cd163SJoonyoung Shim radio->rd_index += 3;
454b09cd163SJoonyoung Shim if (radio->rd_index >= radio->buf_size)
455b09cd163SJoonyoung Shim radio->rd_index = 0;
456b09cd163SJoonyoung Shim }
457b09cd163SJoonyoung Shim }
458b09cd163SJoonyoung Shim if (radio->wr_index != radio->rd_index)
459b09cd163SJoonyoung Shim wake_up_interruptible(&radio->read_queue);
460b09cd163SJoonyoung Shim }
461b09cd163SJoonyoung Shim
462b09cd163SJoonyoung Shim resubmit:
463b09cd163SJoonyoung Shim /* Resubmit if we're still running. */
464b09cd163SJoonyoung Shim if (radio->int_in_running && radio->usbdev) {
465b09cd163SJoonyoung Shim retval = usb_submit_urb(radio->int_in_urb, GFP_ATOMIC);
466b09cd163SJoonyoung Shim if (retval) {
467a9d6fd5eSJoonyoung Shim dev_warn(&radio->intf->dev,
468a9d6fd5eSJoonyoung Shim "resubmitting urb failed (%d)", retval);
469b09cd163SJoonyoung Shim radio->int_in_running = 0;
470b09cd163SJoonyoung Shim }
471b09cd163SJoonyoung Shim }
47286ef3f78SHans de Goede radio->status_rssi_auto_update = radio->int_in_running;
473b09cd163SJoonyoung Shim }
474b09cd163SJoonyoung Shim
475b09cd163SJoonyoung Shim
si470x_fops_open(struct file * file)47658757984SMauro Carvalho Chehab static int si470x_fops_open(struct file *file)
477b09cd163SJoonyoung Shim {
4786fd522a6SHans Verkuil return v4l2_fh_open(file);
479b09cd163SJoonyoung Shim }
480b09cd163SJoonyoung Shim
si470x_fops_release(struct file * file)48158757984SMauro Carvalho Chehab static int si470x_fops_release(struct file *file)
482b09cd163SJoonyoung Shim {
4834967d53dSHans Verkuil return v4l2_fh_release(file);
484b09cd163SJoonyoung Shim }
485b09cd163SJoonyoung Shim
si470x_usb_release(struct v4l2_device * v4l2_dev)4866fd522a6SHans Verkuil static void si470x_usb_release(struct v4l2_device *v4l2_dev)
4874967d53dSHans Verkuil {
4886fd522a6SHans Verkuil struct si470x_device *radio =
4896fd522a6SHans Verkuil container_of(v4l2_dev, struct si470x_device, v4l2_dev);
4904967d53dSHans Verkuil
4914967d53dSHans Verkuil usb_free_urb(radio->int_in_urb);
4924967d53dSHans Verkuil v4l2_ctrl_handler_free(&radio->hdl);
4934967d53dSHans Verkuil v4l2_device_unregister(&radio->v4l2_dev);
4944967d53dSHans Verkuil kfree(radio->int_in_buffer);
4954967d53dSHans Verkuil kfree(radio->buffer);
496567e2e96SHans Verkuil kfree(radio->usb_buf);
4974967d53dSHans Verkuil kfree(radio);
4984967d53dSHans Verkuil }
499b09cd163SJoonyoung Shim
500b09cd163SJoonyoung Shim
501b09cd163SJoonyoung Shim /**************************************************************************
502b09cd163SJoonyoung Shim * Video4Linux Interface
503b09cd163SJoonyoung Shim **************************************************************************/
504b09cd163SJoonyoung Shim
505b09cd163SJoonyoung Shim /*
506b09cd163SJoonyoung Shim * si470x_vidioc_querycap - query device capabilities
507b09cd163SJoonyoung Shim */
si470x_vidioc_querycap(struct file * file,void * priv,struct v4l2_capability * capability)50858757984SMauro Carvalho Chehab static int si470x_vidioc_querycap(struct file *file, void *priv,
509b09cd163SJoonyoung Shim struct v4l2_capability *capability)
510b09cd163SJoonyoung Shim {
511b09cd163SJoonyoung Shim struct si470x_device *radio = video_drvdata(file);
512b09cd163SJoonyoung Shim
513c0decac1SMauro Carvalho Chehab strscpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
514c0decac1SMauro Carvalho Chehab strscpy(capability->card, DRIVER_CARD, sizeof(capability->card));
515b09cd163SJoonyoung Shim usb_make_path(radio->usbdev, capability->bus_info,
516b09cd163SJoonyoung Shim sizeof(capability->bus_info));
517b09cd163SJoonyoung Shim return 0;
518b09cd163SJoonyoung Shim }
519b09cd163SJoonyoung Shim
520b09cd163SJoonyoung Shim
si470x_start_usb(struct si470x_device * radio)5216fd522a6SHans Verkuil static int si470x_start_usb(struct si470x_device *radio)
5226fd522a6SHans Verkuil {
5236fd522a6SHans Verkuil int retval;
5246fd522a6SHans Verkuil
5256fd522a6SHans Verkuil /* initialize interrupt urb */
5266fd522a6SHans Verkuil usb_fill_int_urb(radio->int_in_urb, radio->usbdev,
5276fd522a6SHans Verkuil usb_rcvintpipe(radio->usbdev,
5286fd522a6SHans Verkuil radio->int_in_endpoint->bEndpointAddress),
5296fd522a6SHans Verkuil radio->int_in_buffer,
5306fd522a6SHans Verkuil le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize),
5316fd522a6SHans Verkuil si470x_int_in_callback,
5326fd522a6SHans Verkuil radio,
5336fd522a6SHans Verkuil radio->int_in_endpoint->bInterval);
5346fd522a6SHans Verkuil
5356fd522a6SHans Verkuil radio->int_in_running = 1;
5366fd522a6SHans Verkuil mb();
5376fd522a6SHans Verkuil
5386fd522a6SHans Verkuil retval = usb_submit_urb(radio->int_in_urb, GFP_KERNEL);
5396fd522a6SHans Verkuil if (retval) {
5406fd522a6SHans Verkuil dev_info(&radio->intf->dev,
5416fd522a6SHans Verkuil "submitting int urb failed (%d)\n", retval);
5426fd522a6SHans Verkuil radio->int_in_running = 0;
5436fd522a6SHans Verkuil }
54486ef3f78SHans de Goede radio->status_rssi_auto_update = radio->int_in_running;
54577947111SHans de Goede
54677947111SHans de Goede /* start radio */
54777947111SHans de Goede retval = si470x_start(radio);
54877947111SHans de Goede if (retval < 0)
54977947111SHans de Goede return retval;
55077947111SHans de Goede
55177947111SHans de Goede v4l2_ctrl_handler_setup(&radio->hdl);
55277947111SHans de Goede
5536fd522a6SHans Verkuil return retval;
5546fd522a6SHans Verkuil }
555b09cd163SJoonyoung Shim
556b09cd163SJoonyoung Shim /**************************************************************************
557b09cd163SJoonyoung Shim * USB Interface
558b09cd163SJoonyoung Shim **************************************************************************/
559b09cd163SJoonyoung Shim
560b09cd163SJoonyoung Shim /*
561b09cd163SJoonyoung Shim * si470x_usb_driver_probe - probe for the device
562b09cd163SJoonyoung Shim */
si470x_usb_driver_probe(struct usb_interface * intf,const struct usb_device_id * id)563b09cd163SJoonyoung Shim static int si470x_usb_driver_probe(struct usb_interface *intf,
564b09cd163SJoonyoung Shim const struct usb_device_id *id)
565b09cd163SJoonyoung Shim {
566b09cd163SJoonyoung Shim struct si470x_device *radio;
567b09cd163SJoonyoung Shim struct usb_host_interface *iface_desc;
568b09cd163SJoonyoung Shim struct usb_endpoint_descriptor *endpoint;
5699aa4d4eaSMarkus Elfring int i, int_end_size, retval;
5709dcb79c2STobias Lorenz unsigned char version_warning = 0;
571b09cd163SJoonyoung Shim
572b09cd163SJoonyoung Shim /* private data allocation and initialization */
573b09cd163SJoonyoung Shim radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
574b09cd163SJoonyoung Shim if (!radio) {
575b09cd163SJoonyoung Shim retval = -ENOMEM;
576b09cd163SJoonyoung Shim goto err_initial;
577b09cd163SJoonyoung Shim }
578567e2e96SHans Verkuil radio->usb_buf = kmalloc(MAX_REPORT_SIZE, GFP_KERNEL);
579567e2e96SHans Verkuil if (radio->usb_buf == NULL) {
580567e2e96SHans Verkuil retval = -ENOMEM;
581567e2e96SHans Verkuil goto err_radio;
582567e2e96SHans Verkuil }
583b09cd163SJoonyoung Shim radio->usbdev = interface_to_usbdev(intf);
584b09cd163SJoonyoung Shim radio->intf = intf;
585f140612dSHans de Goede radio->band = 1; /* Default to 76 - 108 MHz */
586b09cd163SJoonyoung Shim mutex_init(&radio->lock);
58777947111SHans de Goede init_completion(&radio->completion);
588b09cd163SJoonyoung Shim
58958757984SMauro Carvalho Chehab radio->get_register = si470x_get_register;
59058757984SMauro Carvalho Chehab radio->set_register = si470x_set_register;
59158757984SMauro Carvalho Chehab radio->fops_open = si470x_fops_open;
59258757984SMauro Carvalho Chehab radio->fops_release = si470x_fops_release;
59358757984SMauro Carvalho Chehab radio->vidioc_querycap = si470x_vidioc_querycap;
59458757984SMauro Carvalho Chehab
595b09cd163SJoonyoung Shim iface_desc = intf->cur_altsetting;
596b09cd163SJoonyoung Shim
597b09cd163SJoonyoung Shim /* Set up interrupt endpoint information. */
598b09cd163SJoonyoung Shim for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
599b09cd163SJoonyoung Shim endpoint = &iface_desc->endpoint[i].desc;
600a0ffe4c0SHimangi Saraogi if (usb_endpoint_is_int_in(endpoint))
601b09cd163SJoonyoung Shim radio->int_in_endpoint = endpoint;
602b09cd163SJoonyoung Shim }
603b09cd163SJoonyoung Shim if (!radio->int_in_endpoint) {
604a9d6fd5eSJoonyoung Shim dev_info(&intf->dev, "could not find interrupt in endpoint\n");
605b09cd163SJoonyoung Shim retval = -EIO;
606567e2e96SHans Verkuil goto err_usbbuf;
607b09cd163SJoonyoung Shim }
608b09cd163SJoonyoung Shim
609b09cd163SJoonyoung Shim int_end_size = le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize);
610b09cd163SJoonyoung Shim
611b09cd163SJoonyoung Shim radio->int_in_buffer = kmalloc(int_end_size, GFP_KERNEL);
612b09cd163SJoonyoung Shim if (!radio->int_in_buffer) {
613a9d6fd5eSJoonyoung Shim dev_info(&intf->dev, "could not allocate int_in_buffer");
614b09cd163SJoonyoung Shim retval = -ENOMEM;
615567e2e96SHans Verkuil goto err_usbbuf;
616b09cd163SJoonyoung Shim }
617b09cd163SJoonyoung Shim
618b09cd163SJoonyoung Shim radio->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
619b09cd163SJoonyoung Shim if (!radio->int_in_urb) {
620b09cd163SJoonyoung Shim retval = -ENOMEM;
621b09cd163SJoonyoung Shim goto err_intbuffer;
622b09cd163SJoonyoung Shim }
623b09cd163SJoonyoung Shim
6246fd522a6SHans Verkuil radio->v4l2_dev.release = si470x_usb_release;
6255df2def5SHans Verkuil
6265df2def5SHans Verkuil /*
6275df2def5SHans Verkuil * The si470x SiLabs reference design uses the same USB IDs as
6285df2def5SHans Verkuil * 'Thanko's Raremono' si4734 based receiver. So check here which we
6295df2def5SHans Verkuil * have: attempt to read the device ID from the si470x: the lower 12
6305df2def5SHans Verkuil * bits should be 0x0242 for the si470x.
6315df2def5SHans Verkuil *
6325df2def5SHans Verkuil * We use this check to determine which device we are dealing with.
6335df2def5SHans Verkuil */
6345df2def5SHans Verkuil if (id->idVendor == 0x10c4 && id->idProduct == 0x818a) {
6355df2def5SHans Verkuil retval = usb_control_msg(radio->usbdev,
6365df2def5SHans Verkuil usb_rcvctrlpipe(radio->usbdev, 0),
6375df2def5SHans Verkuil HID_REQ_GET_REPORT,
6385df2def5SHans Verkuil USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
6395df2def5SHans Verkuil 1, 2,
6405df2def5SHans Verkuil radio->usb_buf, 3, 500);
6415df2def5SHans Verkuil if (retval != 3 ||
6425df2def5SHans Verkuil (get_unaligned_be16(&radio->usb_buf[1]) & 0xfff) != 0x0242) {
6435df2def5SHans Verkuil dev_info(&intf->dev, "this is not a si470x device.\n");
6445df2def5SHans Verkuil retval = -ENODEV;
6455df2def5SHans Verkuil goto err_urb;
6465df2def5SHans Verkuil }
6475df2def5SHans Verkuil }
6485df2def5SHans Verkuil
6494967d53dSHans Verkuil retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev);
6504967d53dSHans Verkuil if (retval < 0) {
6514967d53dSHans Verkuil dev_err(&intf->dev, "couldn't register v4l2_device\n");
652f54ba7f1SAlexey Khoroshilov goto err_urb;
653b09cd163SJoonyoung Shim }
6544967d53dSHans Verkuil
6554967d53dSHans Verkuil v4l2_ctrl_handler_init(&radio->hdl, 2);
6564967d53dSHans Verkuil v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
6574967d53dSHans Verkuil V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
6584967d53dSHans Verkuil v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
6594967d53dSHans Verkuil V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 15);
6604967d53dSHans Verkuil if (radio->hdl.error) {
6614967d53dSHans Verkuil retval = radio->hdl.error;
6624967d53dSHans Verkuil dev_err(&intf->dev, "couldn't register control\n");
6634967d53dSHans Verkuil goto err_dev;
6644967d53dSHans Verkuil }
6654967d53dSHans Verkuil radio->videodev = si470x_viddev_template;
6664967d53dSHans Verkuil radio->videodev.ctrl_handler = &radio->hdl;
6674967d53dSHans Verkuil radio->videodev.lock = &radio->lock;
6684967d53dSHans Verkuil radio->videodev.v4l2_dev = &radio->v4l2_dev;
6696fd522a6SHans Verkuil radio->videodev.release = video_device_release_empty;
670e83ce300SHans Verkuil radio->videodev.device_caps =
671e83ce300SHans Verkuil V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE | V4L2_CAP_TUNER |
672e83ce300SHans Verkuil V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
6734967d53dSHans Verkuil video_set_drvdata(&radio->videodev, radio);
674b09cd163SJoonyoung Shim
6759dcb79c2STobias Lorenz /* get device and chip versions */
676b09cd163SJoonyoung Shim if (si470x_get_all_registers(radio) < 0) {
677b09cd163SJoonyoung Shim retval = -EIO;
6784967d53dSHans Verkuil goto err_ctrl;
679b09cd163SJoonyoung Shim }
680a9d6fd5eSJoonyoung Shim dev_info(&intf->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
681dd7a2acfSMauro Carvalho Chehab radio->registers[DEVICEID], radio->registers[SI_CHIPID]);
682dd7a2acfSMauro Carvalho Chehab if ((radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE) < RADIO_FW_VERSION) {
6839dcb79c2STobias Lorenz dev_warn(&intf->dev,
684d5aa19c9SMauro Carvalho Chehab "This driver is known to work with firmware version %u, but the device has firmware version %u.\n",
685d5aa19c9SMauro Carvalho Chehab RADIO_FW_VERSION,
686dd7a2acfSMauro Carvalho Chehab radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE);
6879dcb79c2STobias Lorenz version_warning = 1;
6889dcb79c2STobias Lorenz }
689b09cd163SJoonyoung Shim
690b09cd163SJoonyoung Shim /* get software and hardware versions */
691b09cd163SJoonyoung Shim if (si470x_get_scratch_page_versions(radio) < 0) {
692b09cd163SJoonyoung Shim retval = -EIO;
6934967d53dSHans Verkuil goto err_ctrl;
694b09cd163SJoonyoung Shim }
695a9d6fd5eSJoonyoung Shim dev_info(&intf->dev, "software version %d, hardware version %d\n",
696b09cd163SJoonyoung Shim radio->software_version, radio->hardware_version);
6979dcb79c2STobias Lorenz if (radio->hardware_version < RADIO_HW_VERSION) {
6989dcb79c2STobias Lorenz dev_warn(&intf->dev,
699d5aa19c9SMauro Carvalho Chehab "This driver is known to work with hardware version %u, but the device has hardware version %u.\n",
700d5aa19c9SMauro Carvalho Chehab RADIO_HW_VERSION,
7019dcb79c2STobias Lorenz radio->hardware_version);
7029dcb79c2STobias Lorenz version_warning = 1;
7039dcb79c2STobias Lorenz }
7049dcb79c2STobias Lorenz
7059dcb79c2STobias Lorenz /* give out version warning */
7069dcb79c2STobias Lorenz if (version_warning == 1) {
707a9d6fd5eSJoonyoung Shim dev_warn(&intf->dev,
708d5aa19c9SMauro Carvalho Chehab "If you have some trouble using this driver, please report to V4L ML at linux-media@vger.kernel.org\n");
709b09cd163SJoonyoung Shim }
710b09cd163SJoonyoung Shim
711b09cd163SJoonyoung Shim /* set led to connect state */
712b09cd163SJoonyoung Shim si470x_set_led_state(radio, BLINK_GREEN_LED);
713b09cd163SJoonyoung Shim
714b09cd163SJoonyoung Shim /* rds buffer allocation */
715b09cd163SJoonyoung Shim radio->buf_size = rds_buf * 3;
716b09cd163SJoonyoung Shim radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL);
717b09cd163SJoonyoung Shim if (!radio->buffer) {
718b09cd163SJoonyoung Shim retval = -EIO;
7194967d53dSHans Verkuil goto err_ctrl;
720b09cd163SJoonyoung Shim }
721b09cd163SJoonyoung Shim
722b09cd163SJoonyoung Shim /* rds buffer configuration */
723b09cd163SJoonyoung Shim radio->wr_index = 0;
724b09cd163SJoonyoung Shim radio->rd_index = 0;
725b09cd163SJoonyoung Shim init_waitqueue_head(&radio->read_queue);
7264967d53dSHans Verkuil usb_set_intfdata(intf, radio);
727b09cd163SJoonyoung Shim
7286fd522a6SHans Verkuil /* start radio */
7296fd522a6SHans Verkuil retval = si470x_start_usb(radio);
730*7d21e0b1SShigeru Yoshida if (retval < 0 && !radio->int_in_running)
7310d616f2aSHans Verkuil goto err_buf;
732*7d21e0b1SShigeru Yoshida else if (retval < 0) /* in case of radio->int_in_running == 1 */
733*7d21e0b1SShigeru Yoshida goto err_all;
7346fd522a6SHans Verkuil
73577947111SHans de Goede /* set initial frequency */
73677947111SHans de Goede si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */
73777947111SHans de Goede
738b09cd163SJoonyoung Shim /* register video device */
7394967d53dSHans Verkuil retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO,
740b09cd163SJoonyoung Shim radio_nr);
741b09cd163SJoonyoung Shim if (retval) {
7426fd522a6SHans Verkuil dev_err(&intf->dev, "Could not register video device\n");
743b09cd163SJoonyoung Shim goto err_all;
744b09cd163SJoonyoung Shim }
745b09cd163SJoonyoung Shim
746b09cd163SJoonyoung Shim return 0;
747b09cd163SJoonyoung Shim err_all:
7480d616f2aSHans Verkuil usb_kill_urb(radio->int_in_urb);
7490d616f2aSHans Verkuil err_buf:
750b09cd163SJoonyoung Shim kfree(radio->buffer);
7514967d53dSHans Verkuil err_ctrl:
7524967d53dSHans Verkuil v4l2_ctrl_handler_free(&radio->hdl);
7534967d53dSHans Verkuil err_dev:
7544967d53dSHans Verkuil v4l2_device_unregister(&radio->v4l2_dev);
755f54ba7f1SAlexey Khoroshilov err_urb:
756f54ba7f1SAlexey Khoroshilov usb_free_urb(radio->int_in_urb);
757b09cd163SJoonyoung Shim err_intbuffer:
758b09cd163SJoonyoung Shim kfree(radio->int_in_buffer);
759567e2e96SHans Verkuil err_usbbuf:
760567e2e96SHans Verkuil kfree(radio->usb_buf);
761b09cd163SJoonyoung Shim err_radio:
762b09cd163SJoonyoung Shim kfree(radio);
763b09cd163SJoonyoung Shim err_initial:
764b09cd163SJoonyoung Shim return retval;
765b09cd163SJoonyoung Shim }
766b09cd163SJoonyoung Shim
767b09cd163SJoonyoung Shim
768b09cd163SJoonyoung Shim /*
769b09cd163SJoonyoung Shim * si470x_usb_driver_suspend - suspend the device
770b09cd163SJoonyoung Shim */
si470x_usb_driver_suspend(struct usb_interface * intf,pm_message_t message)771b09cd163SJoonyoung Shim static int si470x_usb_driver_suspend(struct usb_interface *intf,
772b09cd163SJoonyoung Shim pm_message_t message)
773b09cd163SJoonyoung Shim {
7746fd522a6SHans Verkuil struct si470x_device *radio = usb_get_intfdata(intf);
7756fd522a6SHans Verkuil
776a9d6fd5eSJoonyoung Shim dev_info(&intf->dev, "suspending now...\n");
777b09cd163SJoonyoung Shim
7786fd522a6SHans Verkuil /* shutdown interrupt handler */
7796fd522a6SHans Verkuil if (radio->int_in_running) {
7806fd522a6SHans Verkuil radio->int_in_running = 0;
7816fd522a6SHans Verkuil if (radio->int_in_urb)
7826fd522a6SHans Verkuil usb_kill_urb(radio->int_in_urb);
7836fd522a6SHans Verkuil }
7846fd522a6SHans Verkuil
7856fd522a6SHans Verkuil /* cancel read processes */
7866fd522a6SHans Verkuil wake_up_interruptible(&radio->read_queue);
7876fd522a6SHans Verkuil
7886fd522a6SHans Verkuil /* stop radio */
7896fd522a6SHans Verkuil si470x_stop(radio);
790b09cd163SJoonyoung Shim return 0;
791b09cd163SJoonyoung Shim }
792b09cd163SJoonyoung Shim
793b09cd163SJoonyoung Shim
794b09cd163SJoonyoung Shim /*
795b09cd163SJoonyoung Shim * si470x_usb_driver_resume - resume the device
796b09cd163SJoonyoung Shim */
si470x_usb_driver_resume(struct usb_interface * intf)797b09cd163SJoonyoung Shim static int si470x_usb_driver_resume(struct usb_interface *intf)
798b09cd163SJoonyoung Shim {
7996fd522a6SHans Verkuil struct si470x_device *radio = usb_get_intfdata(intf);
800c1af23c4SHans de Goede int ret;
8016fd522a6SHans Verkuil
802a9d6fd5eSJoonyoung Shim dev_info(&intf->dev, "resuming now...\n");
803b09cd163SJoonyoung Shim
8046fd522a6SHans Verkuil /* start radio */
805c1af23c4SHans de Goede ret = si470x_start_usb(radio);
806c1af23c4SHans de Goede if (ret == 0)
807c1af23c4SHans de Goede v4l2_ctrl_handler_setup(&radio->hdl);
808c1af23c4SHans de Goede
809c1af23c4SHans de Goede return ret;
810b09cd163SJoonyoung Shim }
811b09cd163SJoonyoung Shim
812b09cd163SJoonyoung Shim
813b09cd163SJoonyoung Shim /*
814b09cd163SJoonyoung Shim * si470x_usb_driver_disconnect - disconnect the device
815b09cd163SJoonyoung Shim */
si470x_usb_driver_disconnect(struct usb_interface * intf)816b09cd163SJoonyoung Shim static void si470x_usb_driver_disconnect(struct usb_interface *intf)
817b09cd163SJoonyoung Shim {
818b09cd163SJoonyoung Shim struct si470x_device *radio = usb_get_intfdata(intf);
819b09cd163SJoonyoung Shim
820cf9b475dSMauro Carvalho Chehab mutex_lock(&radio->lock);
8214967d53dSHans Verkuil v4l2_device_disconnect(&radio->v4l2_dev);
8224967d53dSHans Verkuil video_unregister_device(&radio->videodev);
8230d616f2aSHans Verkuil usb_kill_urb(radio->int_in_urb);
824b09cd163SJoonyoung Shim usb_set_intfdata(intf, NULL);
825cf9b475dSMauro Carvalho Chehab mutex_unlock(&radio->lock);
8266fd522a6SHans Verkuil v4l2_device_put(&radio->v4l2_dev);
827b09cd163SJoonyoung Shim }
828b09cd163SJoonyoung Shim
829b09cd163SJoonyoung Shim
830b09cd163SJoonyoung Shim /*
831b09cd163SJoonyoung Shim * si470x_usb_driver - usb driver interface
8326fd522a6SHans Verkuil *
8336fd522a6SHans Verkuil * A note on suspend/resume: this driver had only empty suspend/resume
8346fd522a6SHans Verkuil * functions, and when I tried to test suspend/resume it always disconnected
8356fd522a6SHans Verkuil * instead of resuming (using my ADS InstantFM stick). So I've decided to
8366fd522a6SHans Verkuil * remove these callbacks until someone else with better hardware can
8376fd522a6SHans Verkuil * implement and test this.
838b09cd163SJoonyoung Shim */
839b09cd163SJoonyoung Shim static struct usb_driver si470x_usb_driver = {
840b09cd163SJoonyoung Shim .name = DRIVER_NAME,
841b09cd163SJoonyoung Shim .probe = si470x_usb_driver_probe,
842b09cd163SJoonyoung Shim .disconnect = si470x_usb_driver_disconnect,
843b09cd163SJoonyoung Shim .suspend = si470x_usb_driver_suspend,
844b09cd163SJoonyoung Shim .resume = si470x_usb_driver_resume,
8456fd522a6SHans Verkuil .reset_resume = si470x_usb_driver_resume,
846b09cd163SJoonyoung Shim .id_table = si470x_usb_driver_id_table,
847b09cd163SJoonyoung Shim };
848b09cd163SJoonyoung Shim
849ecb3b2b3SGreg Kroah-Hartman module_usb_driver(si470x_usb_driver);
850b09cd163SJoonyoung Shim
851b09cd163SJoonyoung Shim MODULE_LICENSE("GPL");
852b09cd163SJoonyoung Shim MODULE_AUTHOR(DRIVER_AUTHOR);
853b09cd163SJoonyoung Shim MODULE_DESCRIPTION(DRIVER_DESC);
854b09cd163SJoonyoung Shim MODULE_VERSION(DRIVER_VERSION);
855