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