1b09cd163SJoonyoung Shim /*
2b09cd163SJoonyoung Shim  *  drivers/media/radio/si470x/radio-si470x-usb.c
3b09cd163SJoonyoung Shim  *
4b09cd163SJoonyoung Shim  *  USB driver for radios with Silicon Labs Si470x FM Radio Receivers
5b09cd163SJoonyoung Shim  *
6b09cd163SJoonyoung Shim  *  Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
7b09cd163SJoonyoung Shim  *
8b09cd163SJoonyoung Shim  * This program is free software; you can redistribute it and/or modify
9b09cd163SJoonyoung Shim  * it under the terms of the GNU General Public License as published by
10b09cd163SJoonyoung Shim  * the Free Software Foundation; either version 2 of the License, or
11b09cd163SJoonyoung Shim  * (at your option) any later version.
12b09cd163SJoonyoung Shim  *
13b09cd163SJoonyoung Shim  * This program is distributed in the hope that it will be useful,
14b09cd163SJoonyoung Shim  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15b09cd163SJoonyoung Shim  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16b09cd163SJoonyoung Shim  * GNU General Public License for more details.
17b09cd163SJoonyoung Shim  */
18b09cd163SJoonyoung Shim 
19b09cd163SJoonyoung Shim 
20b09cd163SJoonyoung Shim /*
21b09cd163SJoonyoung Shim  * ToDo:
22b09cd163SJoonyoung Shim  * - add firmware download/update support
23b09cd163SJoonyoung Shim  */
24b09cd163SJoonyoung Shim 
25b09cd163SJoonyoung Shim 
26b09cd163SJoonyoung Shim /* driver definitions */
27b09cd163SJoonyoung Shim #define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>"
28b09cd163SJoonyoung Shim #define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver"
29b09cd163SJoonyoung Shim #define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers"
30b09cd163SJoonyoung Shim #define DRIVER_VERSION "1.0.10"
31b09cd163SJoonyoung Shim 
32b09cd163SJoonyoung Shim /* kernel includes */
33b09cd163SJoonyoung Shim #include <linux/usb.h>
34b09cd163SJoonyoung Shim #include <linux/hid.h>
355a0e3ad6STejun Heo #include <linux/slab.h>
36b09cd163SJoonyoung Shim 
37b09cd163SJoonyoung Shim #include "radio-si470x.h"
38b09cd163SJoonyoung Shim 
39b09cd163SJoonyoung Shim 
40b09cd163SJoonyoung Shim /* USB Device ID List */
411ab2234eSArvind Yadav static const struct usb_device_id si470x_usb_driver_id_table[] = {
42b09cd163SJoonyoung Shim 	/* Silicon Labs USB FM Radio Reference Design */
43b09cd163SJoonyoung Shim 	{ USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) },
44b09cd163SJoonyoung Shim 	/* ADS/Tech FM Radio Receiver (formerly Instant FM Music) */
45b09cd163SJoonyoung Shim 	{ USB_DEVICE_AND_INTERFACE_INFO(0x06e1, 0xa155, USB_CLASS_HID, 0, 0) },
46b09cd163SJoonyoung Shim 	/* KWorld USB FM Radio SnapMusic Mobile 700 (FM700) */
47b09cd163SJoonyoung Shim 	{ USB_DEVICE_AND_INTERFACE_INFO(0x1b80, 0xd700, USB_CLASS_HID, 0, 0) },
48b09cd163SJoonyoung Shim 	/* Sanei Electric, Inc. FM USB Radio (sold as DealExtreme.com PCear) */
49b09cd163SJoonyoung Shim 	{ USB_DEVICE_AND_INTERFACE_INFO(0x10c5, 0x819a, USB_CLASS_HID, 0, 0) },
5011030183SHans de Goede 	/* Axentia ALERT FM USB Receiver */
5111030183SHans de Goede 	{ USB_DEVICE_AND_INTERFACE_INFO(0x12cf, 0x7111, USB_CLASS_HID, 0, 0) },
52b09cd163SJoonyoung Shim 	/* Terminating entry */
53b09cd163SJoonyoung Shim 	{ }
54b09cd163SJoonyoung Shim };
55b09cd163SJoonyoung Shim MODULE_DEVICE_TABLE(usb, si470x_usb_driver_id_table);
56b09cd163SJoonyoung Shim 
57b09cd163SJoonyoung Shim 
58b09cd163SJoonyoung Shim 
59b09cd163SJoonyoung Shim /**************************************************************************
60b09cd163SJoonyoung Shim  * Module Parameters
61b09cd163SJoonyoung Shim  **************************************************************************/
62b09cd163SJoonyoung Shim 
63b09cd163SJoonyoung Shim /* Radio Nr */
64b09cd163SJoonyoung Shim static int radio_nr = -1;
65b09cd163SJoonyoung Shim module_param(radio_nr, int, 0444);
66b09cd163SJoonyoung Shim MODULE_PARM_DESC(radio_nr, "Radio Nr");
67b09cd163SJoonyoung Shim 
68b09cd163SJoonyoung Shim /* USB timeout */
69b09cd163SJoonyoung Shim static unsigned int usb_timeout = 500;
70b09cd163SJoonyoung Shim module_param(usb_timeout, uint, 0644);
71b09cd163SJoonyoung Shim MODULE_PARM_DESC(usb_timeout, "USB timeout (ms): *500*");
72b09cd163SJoonyoung Shim 
73b09cd163SJoonyoung Shim /* RDS buffer blocks */
74b09cd163SJoonyoung Shim static unsigned int rds_buf = 100;
75b09cd163SJoonyoung Shim module_param(rds_buf, uint, 0444);
76b09cd163SJoonyoung Shim MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
77b09cd163SJoonyoung Shim 
78b09cd163SJoonyoung Shim /* RDS maximum block errors */
79b09cd163SJoonyoung Shim static unsigned short max_rds_errors = 1;
80b09cd163SJoonyoung Shim /* 0 means   0  errors requiring correction */
81b09cd163SJoonyoung Shim /* 1 means 1-2  errors requiring correction (used by original USBRadio.exe) */
82b09cd163SJoonyoung Shim /* 2 means 3-5  errors requiring correction */
83b09cd163SJoonyoung Shim /* 3 means   6+ errors or errors in checkword, correction not possible */
84b09cd163SJoonyoung Shim module_param(max_rds_errors, ushort, 0644);
85b09cd163SJoonyoung Shim MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
86b09cd163SJoonyoung Shim 
87b09cd163SJoonyoung Shim 
88b09cd163SJoonyoung Shim 
89b09cd163SJoonyoung Shim /**************************************************************************
90b09cd163SJoonyoung Shim  * USB HID Reports
91b09cd163SJoonyoung Shim  **************************************************************************/
92b09cd163SJoonyoung Shim 
93b09cd163SJoonyoung Shim /* Reports 1-16 give direct read/write access to the 16 Si470x registers */
94b09cd163SJoonyoung Shim /* with the (REPORT_ID - 1) corresponding to the register address across USB */
95b09cd163SJoonyoung Shim /* endpoint 0 using GET_REPORT and SET_REPORT */
96b09cd163SJoonyoung Shim #define REGISTER_REPORT_SIZE	(RADIO_REGISTER_SIZE + 1)
97b09cd163SJoonyoung Shim #define REGISTER_REPORT(reg)	((reg) + 1)
98b09cd163SJoonyoung Shim 
99b09cd163SJoonyoung Shim /* Report 17 gives direct read/write access to the entire Si470x register */
100b09cd163SJoonyoung Shim /* map across endpoint 0 using GET_REPORT and SET_REPORT */
101b09cd163SJoonyoung Shim #define ENTIRE_REPORT_SIZE	(RADIO_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
102b09cd163SJoonyoung Shim #define ENTIRE_REPORT		17
103b09cd163SJoonyoung Shim 
104b09cd163SJoonyoung Shim /* Report 18 is used to send the lowest 6 Si470x registers up the HID */
105b09cd163SJoonyoung Shim /* interrupt endpoint 1 to Windows every 20 milliseconds for status */
106b09cd163SJoonyoung Shim #define RDS_REPORT_SIZE		(RDS_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
107b09cd163SJoonyoung Shim #define RDS_REPORT		18
108b09cd163SJoonyoung Shim 
109b09cd163SJoonyoung Shim /* Report 19: LED state */
110b09cd163SJoonyoung Shim #define LED_REPORT_SIZE		3
111b09cd163SJoonyoung Shim #define LED_REPORT		19
112b09cd163SJoonyoung Shim 
113b09cd163SJoonyoung Shim /* Report 19: stream */
114b09cd163SJoonyoung Shim #define STREAM_REPORT_SIZE	3
115b09cd163SJoonyoung Shim #define STREAM_REPORT		19
116b09cd163SJoonyoung Shim 
117b09cd163SJoonyoung Shim /* Report 20: scratch */
118b09cd163SJoonyoung Shim #define SCRATCH_PAGE_SIZE	63
119b09cd163SJoonyoung Shim #define SCRATCH_REPORT_SIZE	(SCRATCH_PAGE_SIZE + 1)
120b09cd163SJoonyoung Shim #define SCRATCH_REPORT		20
121b09cd163SJoonyoung Shim 
122b09cd163SJoonyoung Shim /* Reports 19-22: flash upgrade of the C8051F321 */
123b09cd163SJoonyoung Shim #define WRITE_REPORT_SIZE	4
124b09cd163SJoonyoung Shim #define WRITE_REPORT		19
125b09cd163SJoonyoung Shim #define FLASH_REPORT_SIZE	64
126b09cd163SJoonyoung Shim #define FLASH_REPORT		20
127b09cd163SJoonyoung Shim #define CRC_REPORT_SIZE		3
128b09cd163SJoonyoung Shim #define CRC_REPORT		21
129b09cd163SJoonyoung Shim #define RESPONSE_REPORT_SIZE	2
130b09cd163SJoonyoung Shim #define RESPONSE_REPORT		22
131b09cd163SJoonyoung Shim 
132b09cd163SJoonyoung Shim /* Report 23: currently unused, but can accept 60 byte reports on the HID */
133b09cd163SJoonyoung Shim /* interrupt out endpoint 2 every 1 millisecond */
134b09cd163SJoonyoung Shim #define UNUSED_REPORT		23
135b09cd163SJoonyoung Shim 
136567e2e96SHans Verkuil #define MAX_REPORT_SIZE		64
137567e2e96SHans Verkuil 
138b09cd163SJoonyoung Shim 
139b09cd163SJoonyoung Shim 
140b09cd163SJoonyoung Shim /**************************************************************************
1419dcb79c2STobias Lorenz  * Software/Hardware Versions from Scratch Page
142b09cd163SJoonyoung Shim  **************************************************************************/
143b09cd163SJoonyoung Shim #define RADIO_HW_VERSION			1
144b09cd163SJoonyoung Shim 
145b09cd163SJoonyoung Shim 
146b09cd163SJoonyoung Shim 
147b09cd163SJoonyoung Shim /**************************************************************************
148b09cd163SJoonyoung Shim  * LED State Definitions
149b09cd163SJoonyoung Shim  **************************************************************************/
150b09cd163SJoonyoung Shim #define LED_COMMAND		0x35
151b09cd163SJoonyoung Shim 
152b09cd163SJoonyoung Shim #define NO_CHANGE_LED		0x00
153b09cd163SJoonyoung Shim #define ALL_COLOR_LED		0x01	/* streaming state */
154b09cd163SJoonyoung Shim #define BLINK_GREEN_LED		0x02	/* connect state */
155b09cd163SJoonyoung Shim #define BLINK_RED_LED		0x04
156b09cd163SJoonyoung Shim #define BLINK_ORANGE_LED	0x10	/* disconnect state */
157b09cd163SJoonyoung Shim #define SOLID_GREEN_LED		0x20	/* tuning/seeking state */
158b09cd163SJoonyoung Shim #define SOLID_RED_LED		0x40	/* bootload state */
159b09cd163SJoonyoung Shim #define SOLID_ORANGE_LED	0x80
160b09cd163SJoonyoung Shim 
161b09cd163SJoonyoung Shim 
162b09cd163SJoonyoung Shim 
163b09cd163SJoonyoung Shim /**************************************************************************
164b09cd163SJoonyoung Shim  * Stream State Definitions
165b09cd163SJoonyoung Shim  **************************************************************************/
166b09cd163SJoonyoung Shim #define STREAM_COMMAND	0x36
167b09cd163SJoonyoung Shim #define STREAM_VIDPID	0x00
168b09cd163SJoonyoung Shim #define STREAM_AUDIO	0xff
169b09cd163SJoonyoung Shim 
170b09cd163SJoonyoung Shim 
171b09cd163SJoonyoung Shim 
172b09cd163SJoonyoung Shim /**************************************************************************
173b09cd163SJoonyoung Shim  * Bootloader / Flash Commands
174b09cd163SJoonyoung Shim  **************************************************************************/
175b09cd163SJoonyoung Shim 
176b09cd163SJoonyoung Shim /* unique id sent to bootloader and required to put into a bootload state */
177b09cd163SJoonyoung Shim #define UNIQUE_BL_ID		0x34
178b09cd163SJoonyoung Shim 
179b09cd163SJoonyoung Shim /* mask for the flash data */
180b09cd163SJoonyoung Shim #define FLASH_DATA_MASK		0x55
181b09cd163SJoonyoung Shim 
182b09cd163SJoonyoung Shim /* bootloader commands */
183b09cd163SJoonyoung Shim #define GET_SW_VERSION_COMMAND	0x00
184b09cd163SJoonyoung Shim #define SET_PAGE_COMMAND	0x01
185b09cd163SJoonyoung Shim #define ERASE_PAGE_COMMAND	0x02
186b09cd163SJoonyoung Shim #define WRITE_PAGE_COMMAND	0x03
187b09cd163SJoonyoung Shim #define CRC_ON_PAGE_COMMAND	0x04
188b09cd163SJoonyoung Shim #define READ_FLASH_BYTE_COMMAND	0x05
189b09cd163SJoonyoung Shim #define RESET_DEVICE_COMMAND	0x06
190b09cd163SJoonyoung Shim #define GET_HW_VERSION_COMMAND	0x07
191b09cd163SJoonyoung Shim #define BLANK			0xff
192b09cd163SJoonyoung Shim 
193b09cd163SJoonyoung Shim /* bootloader command responses */
194b09cd163SJoonyoung Shim #define COMMAND_OK		0x01
195b09cd163SJoonyoung Shim #define COMMAND_FAILED		0x02
196b09cd163SJoonyoung Shim #define COMMAND_PENDING		0x03
197b09cd163SJoonyoung Shim 
198b09cd163SJoonyoung Shim 
199b09cd163SJoonyoung Shim 
200b09cd163SJoonyoung Shim /**************************************************************************
201b09cd163SJoonyoung Shim  * General Driver Functions - REGISTER_REPORTs
202b09cd163SJoonyoung Shim  **************************************************************************/
203b09cd163SJoonyoung Shim 
204b09cd163SJoonyoung Shim /*
205b09cd163SJoonyoung Shim  * si470x_get_report - receive a HID report
206b09cd163SJoonyoung Shim  */
207b09cd163SJoonyoung Shim static int si470x_get_report(struct si470x_device *radio, void *buf, int size)
208b09cd163SJoonyoung Shim {
209567e2e96SHans Verkuil 	unsigned char *report = buf;
210b09cd163SJoonyoung Shim 	int retval;
211b09cd163SJoonyoung Shim 
212b09cd163SJoonyoung Shim 	retval = usb_control_msg(radio->usbdev,
213b09cd163SJoonyoung Shim 		usb_rcvctrlpipe(radio->usbdev, 0),
214b09cd163SJoonyoung Shim 		HID_REQ_GET_REPORT,
215b09cd163SJoonyoung Shim 		USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
216b09cd163SJoonyoung Shim 		report[0], 2,
217b09cd163SJoonyoung Shim 		buf, size, usb_timeout);
218b09cd163SJoonyoung Shim 
219b09cd163SJoonyoung Shim 	if (retval < 0)
220a9d6fd5eSJoonyoung Shim 		dev_warn(&radio->intf->dev,
221a9d6fd5eSJoonyoung Shim 			"si470x_get_report: usb_control_msg returned %d\n",
222b09cd163SJoonyoung Shim 			retval);
223b09cd163SJoonyoung Shim 	return retval;
224b09cd163SJoonyoung Shim }
225b09cd163SJoonyoung Shim 
226b09cd163SJoonyoung Shim 
227b09cd163SJoonyoung Shim /*
228b09cd163SJoonyoung Shim  * si470x_set_report - send a HID report
229b09cd163SJoonyoung Shim  */
230b09cd163SJoonyoung Shim static int si470x_set_report(struct si470x_device *radio, void *buf, int size)
231b09cd163SJoonyoung Shim {
232567e2e96SHans Verkuil 	unsigned char *report = buf;
233b09cd163SJoonyoung Shim 	int retval;
234b09cd163SJoonyoung Shim 
235b09cd163SJoonyoung Shim 	retval = usb_control_msg(radio->usbdev,
236b09cd163SJoonyoung Shim 		usb_sndctrlpipe(radio->usbdev, 0),
237b09cd163SJoonyoung Shim 		HID_REQ_SET_REPORT,
238b09cd163SJoonyoung Shim 		USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
239b09cd163SJoonyoung Shim 		report[0], 2,
240b09cd163SJoonyoung Shim 		buf, size, usb_timeout);
241b09cd163SJoonyoung Shim 
242b09cd163SJoonyoung Shim 	if (retval < 0)
243a9d6fd5eSJoonyoung Shim 		dev_warn(&radio->intf->dev,
244a9d6fd5eSJoonyoung Shim 			"si470x_set_report: usb_control_msg returned %d\n",
245b09cd163SJoonyoung Shim 			retval);
246b09cd163SJoonyoung Shim 	return retval;
247b09cd163SJoonyoung Shim }
248b09cd163SJoonyoung Shim 
249b09cd163SJoonyoung Shim 
250b09cd163SJoonyoung Shim /*
251b09cd163SJoonyoung Shim  * si470x_get_register - read register
252b09cd163SJoonyoung Shim  */
25358757984SMauro Carvalho Chehab static int si470x_get_register(struct si470x_device *radio, int regnr)
254b09cd163SJoonyoung Shim {
255b09cd163SJoonyoung Shim 	int retval;
256b09cd163SJoonyoung Shim 
257567e2e96SHans Verkuil 	radio->usb_buf[0] = REGISTER_REPORT(regnr);
258b09cd163SJoonyoung Shim 
259567e2e96SHans Verkuil 	retval = si470x_get_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE);
260b09cd163SJoonyoung Shim 
261b09cd163SJoonyoung Shim 	if (retval >= 0)
262567e2e96SHans Verkuil 		radio->registers[regnr] = get_unaligned_be16(&radio->usb_buf[1]);
263b09cd163SJoonyoung Shim 
264b09cd163SJoonyoung Shim 	return (retval < 0) ? -EINVAL : 0;
265b09cd163SJoonyoung Shim }
266b09cd163SJoonyoung Shim 
267b09cd163SJoonyoung Shim 
268b09cd163SJoonyoung Shim /*
269b09cd163SJoonyoung Shim  * si470x_set_register - write register
270b09cd163SJoonyoung Shim  */
27158757984SMauro Carvalho Chehab static int si470x_set_register(struct si470x_device *radio, int regnr)
272b09cd163SJoonyoung Shim {
273b09cd163SJoonyoung Shim 	int retval;
274b09cd163SJoonyoung Shim 
275567e2e96SHans Verkuil 	radio->usb_buf[0] = REGISTER_REPORT(regnr);
276567e2e96SHans Verkuil 	put_unaligned_be16(radio->registers[regnr], &radio->usb_buf[1]);
277b09cd163SJoonyoung Shim 
278567e2e96SHans Verkuil 	retval = si470x_set_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE);
279b09cd163SJoonyoung Shim 
280b09cd163SJoonyoung Shim 	return (retval < 0) ? -EINVAL : 0;
281b09cd163SJoonyoung Shim }
282b09cd163SJoonyoung Shim 
283b09cd163SJoonyoung Shim 
284b09cd163SJoonyoung Shim 
285b09cd163SJoonyoung Shim /**************************************************************************
286b09cd163SJoonyoung Shim  * General Driver Functions - ENTIRE_REPORT
287b09cd163SJoonyoung Shim  **************************************************************************/
288b09cd163SJoonyoung Shim 
289b09cd163SJoonyoung Shim /*
290b09cd163SJoonyoung Shim  * si470x_get_all_registers - read entire registers
291b09cd163SJoonyoung Shim  */
292b09cd163SJoonyoung Shim static int si470x_get_all_registers(struct si470x_device *radio)
293b09cd163SJoonyoung Shim {
294b09cd163SJoonyoung Shim 	int retval;
295b09cd163SJoonyoung Shim 	unsigned char regnr;
296b09cd163SJoonyoung Shim 
297567e2e96SHans Verkuil 	radio->usb_buf[0] = ENTIRE_REPORT;
298b09cd163SJoonyoung Shim 
299567e2e96SHans Verkuil 	retval = si470x_get_report(radio, radio->usb_buf, ENTIRE_REPORT_SIZE);
300b09cd163SJoonyoung Shim 
301b09cd163SJoonyoung Shim 	if (retval >= 0)
302b09cd163SJoonyoung Shim 		for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++)
303b09cd163SJoonyoung Shim 			radio->registers[regnr] = get_unaligned_be16(
304567e2e96SHans Verkuil 				&radio->usb_buf[regnr * RADIO_REGISTER_SIZE + 1]);
305b09cd163SJoonyoung Shim 
306b09cd163SJoonyoung Shim 	return (retval < 0) ? -EINVAL : 0;
307b09cd163SJoonyoung Shim }
308b09cd163SJoonyoung Shim 
309b09cd163SJoonyoung Shim 
310b09cd163SJoonyoung Shim 
311b09cd163SJoonyoung Shim /**************************************************************************
312b09cd163SJoonyoung Shim  * General Driver Functions - LED_REPORT
313b09cd163SJoonyoung Shim  **************************************************************************/
314b09cd163SJoonyoung Shim 
315b09cd163SJoonyoung Shim /*
316b09cd163SJoonyoung Shim  * si470x_set_led_state - sets the led state
317b09cd163SJoonyoung Shim  */
318b09cd163SJoonyoung Shim static int si470x_set_led_state(struct si470x_device *radio,
319b09cd163SJoonyoung Shim 		unsigned char led_state)
320b09cd163SJoonyoung Shim {
321b09cd163SJoonyoung Shim 	int retval;
322b09cd163SJoonyoung Shim 
323567e2e96SHans Verkuil 	radio->usb_buf[0] = LED_REPORT;
324567e2e96SHans Verkuil 	radio->usb_buf[1] = LED_COMMAND;
325567e2e96SHans Verkuil 	radio->usb_buf[2] = led_state;
326b09cd163SJoonyoung Shim 
327567e2e96SHans Verkuil 	retval = si470x_set_report(radio, radio->usb_buf, LED_REPORT_SIZE);
328b09cd163SJoonyoung Shim 
329b09cd163SJoonyoung Shim 	return (retval < 0) ? -EINVAL : 0;
330b09cd163SJoonyoung Shim }
331b09cd163SJoonyoung Shim 
332b09cd163SJoonyoung Shim 
333b09cd163SJoonyoung Shim 
334b09cd163SJoonyoung Shim /**************************************************************************
335b09cd163SJoonyoung Shim  * General Driver Functions - SCRATCH_REPORT
336b09cd163SJoonyoung Shim  **************************************************************************/
337b09cd163SJoonyoung Shim 
338b09cd163SJoonyoung Shim /*
339b09cd163SJoonyoung Shim  * si470x_get_scratch_versions - gets the scratch page and version infos
340b09cd163SJoonyoung Shim  */
341b09cd163SJoonyoung Shim static int si470x_get_scratch_page_versions(struct si470x_device *radio)
342b09cd163SJoonyoung Shim {
343b09cd163SJoonyoung Shim 	int retval;
344b09cd163SJoonyoung Shim 
345567e2e96SHans Verkuil 	radio->usb_buf[0] = SCRATCH_REPORT;
346b09cd163SJoonyoung Shim 
347567e2e96SHans Verkuil 	retval = si470x_get_report(radio, radio->usb_buf, SCRATCH_REPORT_SIZE);
348b09cd163SJoonyoung Shim 
349b09cd163SJoonyoung Shim 	if (retval < 0)
350cbfc9080SMauro Carvalho Chehab 		dev_warn(&radio->intf->dev, "si470x_get_scratch: si470x_get_report returned %d\n",
351cbfc9080SMauro Carvalho Chehab 			 retval);
352b09cd163SJoonyoung Shim 	else {
353567e2e96SHans Verkuil 		radio->software_version = radio->usb_buf[1];
354567e2e96SHans Verkuil 		radio->hardware_version = radio->usb_buf[2];
355b09cd163SJoonyoung Shim 	}
356b09cd163SJoonyoung Shim 
357b09cd163SJoonyoung Shim 	return (retval < 0) ? -EINVAL : 0;
358b09cd163SJoonyoung Shim }
359b09cd163SJoonyoung Shim 
360b09cd163SJoonyoung Shim 
361b09cd163SJoonyoung Shim 
362b09cd163SJoonyoung Shim /**************************************************************************
363b09cd163SJoonyoung Shim  * RDS Driver Functions
364b09cd163SJoonyoung Shim  **************************************************************************/
365b09cd163SJoonyoung Shim 
366b09cd163SJoonyoung Shim /*
367b09cd163SJoonyoung Shim  * si470x_int_in_callback - rds callback and processing function
368b09cd163SJoonyoung Shim  *
369b09cd163SJoonyoung Shim  * TODO: do we need to use mutex locks in some sections?
370b09cd163SJoonyoung Shim  */
371b09cd163SJoonyoung Shim static void si470x_int_in_callback(struct urb *urb)
372b09cd163SJoonyoung Shim {
373b09cd163SJoonyoung Shim 	struct si470x_device *radio = urb->context;
374b09cd163SJoonyoung Shim 	int retval;
375b09cd163SJoonyoung Shim 	unsigned char regnr;
376b09cd163SJoonyoung Shim 	unsigned char blocknum;
377b09cd163SJoonyoung Shim 	unsigned short bler; /* rds block errors */
378b09cd163SJoonyoung Shim 	unsigned short rds;
379b09cd163SJoonyoung Shim 	unsigned char tmpbuf[3];
380b09cd163SJoonyoung Shim 
381b09cd163SJoonyoung Shim 	if (urb->status) {
382b09cd163SJoonyoung Shim 		if (urb->status == -ENOENT ||
383b09cd163SJoonyoung Shim 				urb->status == -ECONNRESET ||
384b09cd163SJoonyoung Shim 				urb->status == -ESHUTDOWN) {
385b09cd163SJoonyoung Shim 			return;
386b09cd163SJoonyoung Shim 		} else {
387a9d6fd5eSJoonyoung Shim 			dev_warn(&radio->intf->dev,
388a9d6fd5eSJoonyoung Shim 			 "non-zero urb status (%d)\n", urb->status);
389b09cd163SJoonyoung Shim 			goto resubmit; /* Maybe we can recover. */
390b09cd163SJoonyoung Shim 		}
391b09cd163SJoonyoung Shim 	}
392b09cd163SJoonyoung Shim 
39386ef3f78SHans de Goede 	/* Sometimes the device returns len 0 packets */
39486ef3f78SHans de Goede 	if (urb->actual_length != RDS_REPORT_SIZE)
395b09cd163SJoonyoung Shim 		goto resubmit;
396b09cd163SJoonyoung Shim 
39786ef3f78SHans de Goede 	radio->registers[STATUSRSSI] =
39886ef3f78SHans de Goede 		get_unaligned_be16(&radio->int_in_buffer[1]);
39986ef3f78SHans de Goede 
40077947111SHans de Goede 	if (radio->registers[STATUSRSSI] & STATUSRSSI_STC)
40177947111SHans de Goede 		complete(&radio->completion);
40277947111SHans de Goede 
40386ef3f78SHans de Goede 	if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS)) {
404b09cd163SJoonyoung Shim 		/* Update RDS registers with URB data */
40586ef3f78SHans de Goede 		for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++)
406b09cd163SJoonyoung Shim 			radio->registers[STATUSRSSI + regnr] =
407b09cd163SJoonyoung Shim 			    get_unaligned_be16(&radio->int_in_buffer[
408b09cd163SJoonyoung Shim 				regnr * RADIO_REGISTER_SIZE + 1]);
409b09cd163SJoonyoung Shim 		/* get rds blocks */
410b09cd163SJoonyoung Shim 		if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {
411b09cd163SJoonyoung Shim 			/* No RDS group ready, better luck next time */
412b09cd163SJoonyoung Shim 			goto resubmit;
413b09cd163SJoonyoung Shim 		}
414b09cd163SJoonyoung Shim 		if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) {
415b09cd163SJoonyoung Shim 			/* RDS decoder not synchronized */
416b09cd163SJoonyoung Shim 			goto resubmit;
417b09cd163SJoonyoung Shim 		}
418b09cd163SJoonyoung Shim 		for (blocknum = 0; blocknum < 4; blocknum++) {
419b09cd163SJoonyoung Shim 			switch (blocknum) {
420b09cd163SJoonyoung Shim 			default:
421b09cd163SJoonyoung Shim 				bler = (radio->registers[STATUSRSSI] &
422b09cd163SJoonyoung Shim 						STATUSRSSI_BLERA) >> 9;
423b09cd163SJoonyoung Shim 				rds = radio->registers[RDSA];
424b09cd163SJoonyoung Shim 				break;
425b09cd163SJoonyoung Shim 			case 1:
426b09cd163SJoonyoung Shim 				bler = (radio->registers[READCHAN] &
427b09cd163SJoonyoung Shim 						READCHAN_BLERB) >> 14;
428b09cd163SJoonyoung Shim 				rds = radio->registers[RDSB];
429b09cd163SJoonyoung Shim 				break;
430b09cd163SJoonyoung Shim 			case 2:
431b09cd163SJoonyoung Shim 				bler = (radio->registers[READCHAN] &
432b09cd163SJoonyoung Shim 						READCHAN_BLERC) >> 12;
433b09cd163SJoonyoung Shim 				rds = radio->registers[RDSC];
434b09cd163SJoonyoung Shim 				break;
435b09cd163SJoonyoung Shim 			case 3:
436b09cd163SJoonyoung Shim 				bler = (radio->registers[READCHAN] &
437b09cd163SJoonyoung Shim 						READCHAN_BLERD) >> 10;
438b09cd163SJoonyoung Shim 				rds = radio->registers[RDSD];
439b09cd163SJoonyoung Shim 				break;
440c2c1b415SPeter Senna Tschudin 			}
441b09cd163SJoonyoung Shim 
442b09cd163SJoonyoung Shim 			/* Fill the V4L2 RDS buffer */
443b09cd163SJoonyoung Shim 			put_unaligned_le16(rds, &tmpbuf);
444b09cd163SJoonyoung Shim 			tmpbuf[2] = blocknum;		/* offset name */
445b09cd163SJoonyoung Shim 			tmpbuf[2] |= blocknum << 3;	/* received offset */
446b09cd163SJoonyoung Shim 			if (bler > max_rds_errors)
447b09cd163SJoonyoung Shim 				tmpbuf[2] |= 0x80; /* uncorrectable errors */
448b09cd163SJoonyoung Shim 			else if (bler > 0)
449b09cd163SJoonyoung Shim 				tmpbuf[2] |= 0x40; /* corrected error(s) */
450b09cd163SJoonyoung Shim 
451b09cd163SJoonyoung Shim 			/* copy RDS block to internal buffer */
452b09cd163SJoonyoung Shim 			memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);
453b09cd163SJoonyoung Shim 			radio->wr_index += 3;
454b09cd163SJoonyoung Shim 
455b09cd163SJoonyoung Shim 			/* wrap write pointer */
456b09cd163SJoonyoung Shim 			if (radio->wr_index >= radio->buf_size)
457b09cd163SJoonyoung Shim 				radio->wr_index = 0;
458b09cd163SJoonyoung Shim 
459b09cd163SJoonyoung Shim 			/* check for overflow */
460b09cd163SJoonyoung Shim 			if (radio->wr_index == radio->rd_index) {
461b09cd163SJoonyoung Shim 				/* increment and wrap read pointer */
462b09cd163SJoonyoung Shim 				radio->rd_index += 3;
463b09cd163SJoonyoung Shim 				if (radio->rd_index >= radio->buf_size)
464b09cd163SJoonyoung Shim 					radio->rd_index = 0;
465b09cd163SJoonyoung Shim 			}
466b09cd163SJoonyoung Shim 		}
467b09cd163SJoonyoung Shim 		if (radio->wr_index != radio->rd_index)
468b09cd163SJoonyoung Shim 			wake_up_interruptible(&radio->read_queue);
469b09cd163SJoonyoung Shim 	}
470b09cd163SJoonyoung Shim 
471b09cd163SJoonyoung Shim resubmit:
472b09cd163SJoonyoung Shim 	/* Resubmit if we're still running. */
473b09cd163SJoonyoung Shim 	if (radio->int_in_running && radio->usbdev) {
474b09cd163SJoonyoung Shim 		retval = usb_submit_urb(radio->int_in_urb, GFP_ATOMIC);
475b09cd163SJoonyoung Shim 		if (retval) {
476a9d6fd5eSJoonyoung Shim 			dev_warn(&radio->intf->dev,
477a9d6fd5eSJoonyoung Shim 			       "resubmitting urb failed (%d)", retval);
478b09cd163SJoonyoung Shim 			radio->int_in_running = 0;
479b09cd163SJoonyoung Shim 		}
480b09cd163SJoonyoung Shim 	}
48186ef3f78SHans de Goede 	radio->status_rssi_auto_update = radio->int_in_running;
482b09cd163SJoonyoung Shim }
483b09cd163SJoonyoung Shim 
484b09cd163SJoonyoung Shim 
48558757984SMauro Carvalho Chehab static int si470x_fops_open(struct file *file)
486b09cd163SJoonyoung Shim {
4876fd522a6SHans Verkuil 	return v4l2_fh_open(file);
488b09cd163SJoonyoung Shim }
489b09cd163SJoonyoung Shim 
49058757984SMauro Carvalho Chehab static int si470x_fops_release(struct file *file)
491b09cd163SJoonyoung Shim {
4924967d53dSHans Verkuil 	return v4l2_fh_release(file);
493b09cd163SJoonyoung Shim }
494b09cd163SJoonyoung Shim 
4956fd522a6SHans Verkuil static void si470x_usb_release(struct v4l2_device *v4l2_dev)
4964967d53dSHans Verkuil {
4976fd522a6SHans Verkuil 	struct si470x_device *radio =
4986fd522a6SHans Verkuil 		container_of(v4l2_dev, struct si470x_device, v4l2_dev);
4994967d53dSHans Verkuil 
5004967d53dSHans Verkuil 	usb_free_urb(radio->int_in_urb);
5014967d53dSHans Verkuil 	v4l2_ctrl_handler_free(&radio->hdl);
5024967d53dSHans Verkuil 	v4l2_device_unregister(&radio->v4l2_dev);
5034967d53dSHans Verkuil 	kfree(radio->int_in_buffer);
5044967d53dSHans Verkuil 	kfree(radio->buffer);
505567e2e96SHans Verkuil 	kfree(radio->usb_buf);
5064967d53dSHans Verkuil 	kfree(radio);
5074967d53dSHans Verkuil }
508b09cd163SJoonyoung Shim 
509b09cd163SJoonyoung Shim 
510b09cd163SJoonyoung Shim /**************************************************************************
511b09cd163SJoonyoung Shim  * Video4Linux Interface
512b09cd163SJoonyoung Shim  **************************************************************************/
513b09cd163SJoonyoung Shim 
514b09cd163SJoonyoung Shim /*
515b09cd163SJoonyoung Shim  * si470x_vidioc_querycap - query device capabilities
516b09cd163SJoonyoung Shim  */
51758757984SMauro Carvalho Chehab static int si470x_vidioc_querycap(struct file *file, void *priv,
518b09cd163SJoonyoung Shim 				  struct v4l2_capability *capability)
519b09cd163SJoonyoung Shim {
520b09cd163SJoonyoung Shim 	struct si470x_device *radio = video_drvdata(file);
521b09cd163SJoonyoung Shim 
522c0decac1SMauro Carvalho Chehab 	strscpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
523c0decac1SMauro Carvalho Chehab 	strscpy(capability->card, DRIVER_CARD, sizeof(capability->card));
524b09cd163SJoonyoung Shim 	usb_make_path(radio->usbdev, capability->bus_info,
525b09cd163SJoonyoung Shim 			sizeof(capability->bus_info));
526b09cd163SJoonyoung Shim 	return 0;
527b09cd163SJoonyoung Shim }
528b09cd163SJoonyoung Shim 
529b09cd163SJoonyoung Shim 
5306fd522a6SHans Verkuil static int si470x_start_usb(struct si470x_device *radio)
5316fd522a6SHans Verkuil {
5326fd522a6SHans Verkuil 	int retval;
5336fd522a6SHans Verkuil 
5346fd522a6SHans Verkuil 	/* initialize interrupt urb */
5356fd522a6SHans Verkuil 	usb_fill_int_urb(radio->int_in_urb, radio->usbdev,
5366fd522a6SHans Verkuil 			usb_rcvintpipe(radio->usbdev,
5376fd522a6SHans Verkuil 				radio->int_in_endpoint->bEndpointAddress),
5386fd522a6SHans Verkuil 			radio->int_in_buffer,
5396fd522a6SHans Verkuil 			le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize),
5406fd522a6SHans Verkuil 			si470x_int_in_callback,
5416fd522a6SHans Verkuil 			radio,
5426fd522a6SHans Verkuil 			radio->int_in_endpoint->bInterval);
5436fd522a6SHans Verkuil 
5446fd522a6SHans Verkuil 	radio->int_in_running = 1;
5456fd522a6SHans Verkuil 	mb();
5466fd522a6SHans Verkuil 
5476fd522a6SHans Verkuil 	retval = usb_submit_urb(radio->int_in_urb, GFP_KERNEL);
5486fd522a6SHans Verkuil 	if (retval) {
5496fd522a6SHans Verkuil 		dev_info(&radio->intf->dev,
5506fd522a6SHans Verkuil 				"submitting int urb failed (%d)\n", retval);
5516fd522a6SHans Verkuil 		radio->int_in_running = 0;
5526fd522a6SHans Verkuil 	}
55386ef3f78SHans de Goede 	radio->status_rssi_auto_update = radio->int_in_running;
55477947111SHans de Goede 
55577947111SHans de Goede 	/* start radio */
55677947111SHans de Goede 	retval = si470x_start(radio);
55777947111SHans de Goede 	if (retval < 0)
55877947111SHans de Goede 		return retval;
55977947111SHans de Goede 
56077947111SHans de Goede 	v4l2_ctrl_handler_setup(&radio->hdl);
56177947111SHans de Goede 
5626fd522a6SHans Verkuil 	return retval;
5636fd522a6SHans Verkuil }
564b09cd163SJoonyoung Shim 
565b09cd163SJoonyoung Shim /**************************************************************************
566b09cd163SJoonyoung Shim  * USB Interface
567b09cd163SJoonyoung Shim  **************************************************************************/
568b09cd163SJoonyoung Shim 
569b09cd163SJoonyoung Shim /*
570b09cd163SJoonyoung Shim  * si470x_usb_driver_probe - probe for the device
571b09cd163SJoonyoung Shim  */
572b09cd163SJoonyoung Shim static int si470x_usb_driver_probe(struct usb_interface *intf,
573b09cd163SJoonyoung Shim 		const struct usb_device_id *id)
574b09cd163SJoonyoung Shim {
575b09cd163SJoonyoung Shim 	struct si470x_device *radio;
576b09cd163SJoonyoung Shim 	struct usb_host_interface *iface_desc;
577b09cd163SJoonyoung Shim 	struct usb_endpoint_descriptor *endpoint;
5789aa4d4eaSMarkus Elfring 	int i, int_end_size, retval;
5799dcb79c2STobias Lorenz 	unsigned char version_warning = 0;
580b09cd163SJoonyoung Shim 
581b09cd163SJoonyoung Shim 	/* private data allocation and initialization */
582b09cd163SJoonyoung Shim 	radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
583b09cd163SJoonyoung Shim 	if (!radio) {
584b09cd163SJoonyoung Shim 		retval = -ENOMEM;
585b09cd163SJoonyoung Shim 		goto err_initial;
586b09cd163SJoonyoung Shim 	}
587567e2e96SHans Verkuil 	radio->usb_buf = kmalloc(MAX_REPORT_SIZE, GFP_KERNEL);
588567e2e96SHans Verkuil 	if (radio->usb_buf == NULL) {
589567e2e96SHans Verkuil 		retval = -ENOMEM;
590567e2e96SHans Verkuil 		goto err_radio;
591567e2e96SHans Verkuil 	}
592b09cd163SJoonyoung Shim 	radio->usbdev = interface_to_usbdev(intf);
593b09cd163SJoonyoung Shim 	radio->intf = intf;
594f140612dSHans de Goede 	radio->band = 1; /* Default to 76 - 108 MHz */
595b09cd163SJoonyoung Shim 	mutex_init(&radio->lock);
59677947111SHans de Goede 	init_completion(&radio->completion);
597b09cd163SJoonyoung Shim 
59858757984SMauro Carvalho Chehab 	radio->get_register = si470x_get_register;
59958757984SMauro Carvalho Chehab 	radio->set_register = si470x_set_register;
60058757984SMauro Carvalho Chehab 	radio->fops_open = si470x_fops_open;
60158757984SMauro Carvalho Chehab 	radio->fops_release = si470x_fops_release;
60258757984SMauro Carvalho Chehab 	radio->vidioc_querycap = si470x_vidioc_querycap;
60358757984SMauro Carvalho Chehab 
604b09cd163SJoonyoung Shim 	iface_desc = intf->cur_altsetting;
605b09cd163SJoonyoung Shim 
606b09cd163SJoonyoung Shim 	/* Set up interrupt endpoint information. */
607b09cd163SJoonyoung Shim 	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
608b09cd163SJoonyoung Shim 		endpoint = &iface_desc->endpoint[i].desc;
609a0ffe4c0SHimangi Saraogi 		if (usb_endpoint_is_int_in(endpoint))
610b09cd163SJoonyoung Shim 			radio->int_in_endpoint = endpoint;
611b09cd163SJoonyoung Shim 	}
612b09cd163SJoonyoung Shim 	if (!radio->int_in_endpoint) {
613a9d6fd5eSJoonyoung Shim 		dev_info(&intf->dev, "could not find interrupt in endpoint\n");
614b09cd163SJoonyoung Shim 		retval = -EIO;
615567e2e96SHans Verkuil 		goto err_usbbuf;
616b09cd163SJoonyoung Shim 	}
617b09cd163SJoonyoung Shim 
618b09cd163SJoonyoung Shim 	int_end_size = le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize);
619b09cd163SJoonyoung Shim 
620b09cd163SJoonyoung Shim 	radio->int_in_buffer = kmalloc(int_end_size, GFP_KERNEL);
621b09cd163SJoonyoung Shim 	if (!radio->int_in_buffer) {
622a9d6fd5eSJoonyoung Shim 		dev_info(&intf->dev, "could not allocate int_in_buffer");
623b09cd163SJoonyoung Shim 		retval = -ENOMEM;
624567e2e96SHans Verkuil 		goto err_usbbuf;
625b09cd163SJoonyoung Shim 	}
626b09cd163SJoonyoung Shim 
627b09cd163SJoonyoung Shim 	radio->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
628b09cd163SJoonyoung Shim 	if (!radio->int_in_urb) {
629b09cd163SJoonyoung Shim 		retval = -ENOMEM;
630b09cd163SJoonyoung Shim 		goto err_intbuffer;
631b09cd163SJoonyoung Shim 	}
632b09cd163SJoonyoung Shim 
6336fd522a6SHans Verkuil 	radio->v4l2_dev.release = si470x_usb_release;
6345df2def5SHans Verkuil 
6355df2def5SHans Verkuil 	/*
6365df2def5SHans Verkuil 	 * The si470x SiLabs reference design uses the same USB IDs as
6375df2def5SHans Verkuil 	 * 'Thanko's Raremono' si4734 based receiver. So check here which we
6385df2def5SHans Verkuil 	 * have: attempt to read the device ID from the si470x: the lower 12
6395df2def5SHans Verkuil 	 * bits should be 0x0242 for the si470x.
6405df2def5SHans Verkuil 	 *
6415df2def5SHans Verkuil 	 * We use this check to determine which device we are dealing with.
6425df2def5SHans Verkuil 	 */
6435df2def5SHans Verkuil 	if (id->idVendor == 0x10c4 && id->idProduct == 0x818a) {
6445df2def5SHans Verkuil 		retval = usb_control_msg(radio->usbdev,
6455df2def5SHans Verkuil 				usb_rcvctrlpipe(radio->usbdev, 0),
6465df2def5SHans Verkuil 				HID_REQ_GET_REPORT,
6475df2def5SHans Verkuil 				USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
6485df2def5SHans Verkuil 				1, 2,
6495df2def5SHans Verkuil 				radio->usb_buf, 3, 500);
6505df2def5SHans Verkuil 		if (retval != 3 ||
6515df2def5SHans Verkuil 		    (get_unaligned_be16(&radio->usb_buf[1]) & 0xfff) != 0x0242) {
6525df2def5SHans Verkuil 			dev_info(&intf->dev, "this is not a si470x device.\n");
6535df2def5SHans Verkuil 			retval = -ENODEV;
6545df2def5SHans Verkuil 			goto err_urb;
6555df2def5SHans Verkuil 		}
6565df2def5SHans Verkuil 	}
6575df2def5SHans Verkuil 
6584967d53dSHans Verkuil 	retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev);
6594967d53dSHans Verkuil 	if (retval < 0) {
6604967d53dSHans Verkuil 		dev_err(&intf->dev, "couldn't register v4l2_device\n");
661f54ba7f1SAlexey Khoroshilov 		goto err_urb;
662b09cd163SJoonyoung Shim 	}
6634967d53dSHans Verkuil 
6644967d53dSHans Verkuil 	v4l2_ctrl_handler_init(&radio->hdl, 2);
6654967d53dSHans Verkuil 	v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
6664967d53dSHans Verkuil 			  V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
6674967d53dSHans Verkuil 	v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
6684967d53dSHans Verkuil 			  V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 15);
6694967d53dSHans Verkuil 	if (radio->hdl.error) {
6704967d53dSHans Verkuil 		retval = radio->hdl.error;
6714967d53dSHans Verkuil 		dev_err(&intf->dev, "couldn't register control\n");
6724967d53dSHans Verkuil 		goto err_dev;
6734967d53dSHans Verkuil 	}
6744967d53dSHans Verkuil 	radio->videodev = si470x_viddev_template;
6754967d53dSHans Verkuil 	radio->videodev.ctrl_handler = &radio->hdl;
6764967d53dSHans Verkuil 	radio->videodev.lock = &radio->lock;
6774967d53dSHans Verkuil 	radio->videodev.v4l2_dev = &radio->v4l2_dev;
6786fd522a6SHans Verkuil 	radio->videodev.release = video_device_release_empty;
679e83ce300SHans Verkuil 	radio->videodev.device_caps =
680e83ce300SHans Verkuil 		V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE | V4L2_CAP_TUNER |
681e83ce300SHans Verkuil 		V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
6824967d53dSHans Verkuil 	video_set_drvdata(&radio->videodev, radio);
683b09cd163SJoonyoung Shim 
6849dcb79c2STobias Lorenz 	/* get device and chip versions */
685b09cd163SJoonyoung Shim 	if (si470x_get_all_registers(radio) < 0) {
686b09cd163SJoonyoung Shim 		retval = -EIO;
6874967d53dSHans Verkuil 		goto err_ctrl;
688b09cd163SJoonyoung Shim 	}
689a9d6fd5eSJoonyoung Shim 	dev_info(&intf->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
690dd7a2acfSMauro Carvalho Chehab 			radio->registers[DEVICEID], radio->registers[SI_CHIPID]);
691dd7a2acfSMauro Carvalho Chehab 	if ((radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE) < RADIO_FW_VERSION) {
6929dcb79c2STobias Lorenz 		dev_warn(&intf->dev,
693cbfc9080SMauro Carvalho Chehab 			"This driver is known to work with firmware version %hu,\n",
694cbfc9080SMauro Carvalho Chehab 			RADIO_FW_VERSION);
6959dcb79c2STobias Lorenz 		dev_warn(&intf->dev,
6969dcb79c2STobias Lorenz 			"but the device has firmware version %hu.\n",
697dd7a2acfSMauro Carvalho Chehab 			radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE);
6989dcb79c2STobias Lorenz 		version_warning = 1;
6999dcb79c2STobias Lorenz 	}
700b09cd163SJoonyoung Shim 
701b09cd163SJoonyoung Shim 	/* get software and hardware versions */
702b09cd163SJoonyoung Shim 	if (si470x_get_scratch_page_versions(radio) < 0) {
703b09cd163SJoonyoung Shim 		retval = -EIO;
7044967d53dSHans Verkuil 		goto err_ctrl;
705b09cd163SJoonyoung Shim 	}
706a9d6fd5eSJoonyoung Shim 	dev_info(&intf->dev, "software version %d, hardware version %d\n",
707b09cd163SJoonyoung Shim 			radio->software_version, radio->hardware_version);
7089dcb79c2STobias Lorenz 	if (radio->hardware_version < RADIO_HW_VERSION) {
7099dcb79c2STobias Lorenz 		dev_warn(&intf->dev,
710cbfc9080SMauro Carvalho Chehab 			"This driver is known to work with hardware version %hu,\n",
711cbfc9080SMauro Carvalho Chehab 			RADIO_HW_VERSION);
7129dcb79c2STobias Lorenz 		dev_warn(&intf->dev,
7139dcb79c2STobias Lorenz 			"but the device has hardware version %hu.\n",
7149dcb79c2STobias Lorenz 			radio->hardware_version);
7159dcb79c2STobias Lorenz 		version_warning = 1;
7169dcb79c2STobias Lorenz 	}
7179dcb79c2STobias Lorenz 
7189dcb79c2STobias Lorenz 	/* give out version warning */
7199dcb79c2STobias Lorenz 	if (version_warning == 1) {
720a9d6fd5eSJoonyoung Shim 		dev_warn(&intf->dev,
721a9d6fd5eSJoonyoung Shim 			"If you have some trouble using this driver,\n");
722a9d6fd5eSJoonyoung Shim 		dev_warn(&intf->dev,
723cbfc9080SMauro Carvalho Chehab 			"please report to V4L ML at linux-media@vger.kernel.org\n");
724b09cd163SJoonyoung Shim 	}
725b09cd163SJoonyoung Shim 
726b09cd163SJoonyoung Shim 	/* set led to connect state */
727b09cd163SJoonyoung Shim 	si470x_set_led_state(radio, BLINK_GREEN_LED);
728b09cd163SJoonyoung Shim 
729b09cd163SJoonyoung Shim 	/* rds buffer allocation */
730b09cd163SJoonyoung Shim 	radio->buf_size = rds_buf * 3;
731b09cd163SJoonyoung Shim 	radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL);
732b09cd163SJoonyoung Shim 	if (!radio->buffer) {
733b09cd163SJoonyoung Shim 		retval = -EIO;
7344967d53dSHans Verkuil 		goto err_ctrl;
735b09cd163SJoonyoung Shim 	}
736b09cd163SJoonyoung Shim 
737b09cd163SJoonyoung Shim 	/* rds buffer configuration */
738b09cd163SJoonyoung Shim 	radio->wr_index = 0;
739b09cd163SJoonyoung Shim 	radio->rd_index = 0;
740b09cd163SJoonyoung Shim 	init_waitqueue_head(&radio->read_queue);
7414967d53dSHans Verkuil 	usb_set_intfdata(intf, radio);
742b09cd163SJoonyoung Shim 
7436fd522a6SHans Verkuil 	/* start radio */
7446fd522a6SHans Verkuil 	retval = si470x_start_usb(radio);
7456fd522a6SHans Verkuil 	if (retval < 0)
7466fd522a6SHans Verkuil 		goto err_all;
7476fd522a6SHans Verkuil 
74877947111SHans de Goede 	/* set initial frequency */
74977947111SHans de Goede 	si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */
75077947111SHans de Goede 
751b09cd163SJoonyoung Shim 	/* register video device */
7524967d53dSHans Verkuil 	retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO,
753b09cd163SJoonyoung Shim 			radio_nr);
754b09cd163SJoonyoung Shim 	if (retval) {
7556fd522a6SHans Verkuil 		dev_err(&intf->dev, "Could not register video device\n");
756b09cd163SJoonyoung Shim 		goto err_all;
757b09cd163SJoonyoung Shim 	}
758b09cd163SJoonyoung Shim 
759b09cd163SJoonyoung Shim 	return 0;
760b09cd163SJoonyoung Shim err_all:
761b09cd163SJoonyoung Shim 	kfree(radio->buffer);
7624967d53dSHans Verkuil err_ctrl:
7634967d53dSHans Verkuil 	v4l2_ctrl_handler_free(&radio->hdl);
7644967d53dSHans Verkuil err_dev:
7654967d53dSHans Verkuil 	v4l2_device_unregister(&radio->v4l2_dev);
766f54ba7f1SAlexey Khoroshilov err_urb:
767f54ba7f1SAlexey Khoroshilov 	usb_free_urb(radio->int_in_urb);
768b09cd163SJoonyoung Shim err_intbuffer:
769b09cd163SJoonyoung Shim 	kfree(radio->int_in_buffer);
770567e2e96SHans Verkuil err_usbbuf:
771567e2e96SHans Verkuil 	kfree(radio->usb_buf);
772b09cd163SJoonyoung Shim err_radio:
773b09cd163SJoonyoung Shim 	kfree(radio);
774b09cd163SJoonyoung Shim err_initial:
775b09cd163SJoonyoung Shim 	return retval;
776b09cd163SJoonyoung Shim }
777b09cd163SJoonyoung Shim 
778b09cd163SJoonyoung Shim 
779b09cd163SJoonyoung Shim /*
780b09cd163SJoonyoung Shim  * si470x_usb_driver_suspend - suspend the device
781b09cd163SJoonyoung Shim  */
782b09cd163SJoonyoung Shim static int si470x_usb_driver_suspend(struct usb_interface *intf,
783b09cd163SJoonyoung Shim 		pm_message_t message)
784b09cd163SJoonyoung Shim {
7856fd522a6SHans Verkuil 	struct si470x_device *radio = usb_get_intfdata(intf);
7866fd522a6SHans Verkuil 
787a9d6fd5eSJoonyoung Shim 	dev_info(&intf->dev, "suspending now...\n");
788b09cd163SJoonyoung Shim 
7896fd522a6SHans Verkuil 	/* shutdown interrupt handler */
7906fd522a6SHans Verkuil 	if (radio->int_in_running) {
7916fd522a6SHans Verkuil 		radio->int_in_running = 0;
7926fd522a6SHans Verkuil 		if (radio->int_in_urb)
7936fd522a6SHans Verkuil 			usb_kill_urb(radio->int_in_urb);
7946fd522a6SHans Verkuil 	}
7956fd522a6SHans Verkuil 
7966fd522a6SHans Verkuil 	/* cancel read processes */
7976fd522a6SHans Verkuil 	wake_up_interruptible(&radio->read_queue);
7986fd522a6SHans Verkuil 
7996fd522a6SHans Verkuil 	/* stop radio */
8006fd522a6SHans Verkuil 	si470x_stop(radio);
801b09cd163SJoonyoung Shim 	return 0;
802b09cd163SJoonyoung Shim }
803b09cd163SJoonyoung Shim 
804b09cd163SJoonyoung Shim 
805b09cd163SJoonyoung Shim /*
806b09cd163SJoonyoung Shim  * si470x_usb_driver_resume - resume the device
807b09cd163SJoonyoung Shim  */
808b09cd163SJoonyoung Shim static int si470x_usb_driver_resume(struct usb_interface *intf)
809b09cd163SJoonyoung Shim {
8106fd522a6SHans Verkuil 	struct si470x_device *radio = usb_get_intfdata(intf);
811c1af23c4SHans de Goede 	int ret;
8126fd522a6SHans Verkuil 
813a9d6fd5eSJoonyoung Shim 	dev_info(&intf->dev, "resuming now...\n");
814b09cd163SJoonyoung Shim 
8156fd522a6SHans Verkuil 	/* start radio */
816c1af23c4SHans de Goede 	ret = si470x_start_usb(radio);
817c1af23c4SHans de Goede 	if (ret == 0)
818c1af23c4SHans de Goede 		v4l2_ctrl_handler_setup(&radio->hdl);
819c1af23c4SHans de Goede 
820c1af23c4SHans de Goede 	return ret;
821b09cd163SJoonyoung Shim }
822b09cd163SJoonyoung Shim 
823b09cd163SJoonyoung Shim 
824b09cd163SJoonyoung Shim /*
825b09cd163SJoonyoung Shim  * si470x_usb_driver_disconnect - disconnect the device
826b09cd163SJoonyoung Shim  */
827b09cd163SJoonyoung Shim static void si470x_usb_driver_disconnect(struct usb_interface *intf)
828b09cd163SJoonyoung Shim {
829b09cd163SJoonyoung Shim 	struct si470x_device *radio = usb_get_intfdata(intf);
830b09cd163SJoonyoung Shim 
831cf9b475dSMauro Carvalho Chehab 	mutex_lock(&radio->lock);
8324967d53dSHans Verkuil 	v4l2_device_disconnect(&radio->v4l2_dev);
8334967d53dSHans Verkuil 	video_unregister_device(&radio->videodev);
834b09cd163SJoonyoung Shim 	usb_set_intfdata(intf, NULL);
835cf9b475dSMauro Carvalho Chehab 	mutex_unlock(&radio->lock);
8366fd522a6SHans Verkuil 	v4l2_device_put(&radio->v4l2_dev);
837b09cd163SJoonyoung Shim }
838b09cd163SJoonyoung Shim 
839b09cd163SJoonyoung Shim 
840b09cd163SJoonyoung Shim /*
841b09cd163SJoonyoung Shim  * si470x_usb_driver - usb driver interface
8426fd522a6SHans Verkuil  *
8436fd522a6SHans Verkuil  * A note on suspend/resume: this driver had only empty suspend/resume
8446fd522a6SHans Verkuil  * functions, and when I tried to test suspend/resume it always disconnected
8456fd522a6SHans Verkuil  * instead of resuming (using my ADS InstantFM stick). So I've decided to
8466fd522a6SHans Verkuil  * remove these callbacks until someone else with better hardware can
8476fd522a6SHans Verkuil  * implement and test this.
848b09cd163SJoonyoung Shim  */
849b09cd163SJoonyoung Shim static struct usb_driver si470x_usb_driver = {
850b09cd163SJoonyoung Shim 	.name			= DRIVER_NAME,
851b09cd163SJoonyoung Shim 	.probe			= si470x_usb_driver_probe,
852b09cd163SJoonyoung Shim 	.disconnect		= si470x_usb_driver_disconnect,
853b09cd163SJoonyoung Shim 	.suspend		= si470x_usb_driver_suspend,
854b09cd163SJoonyoung Shim 	.resume			= si470x_usb_driver_resume,
8556fd522a6SHans Verkuil 	.reset_resume		= si470x_usb_driver_resume,
856b09cd163SJoonyoung Shim 	.id_table		= si470x_usb_driver_id_table,
857b09cd163SJoonyoung Shim };
858b09cd163SJoonyoung Shim 
859ecb3b2b3SGreg Kroah-Hartman module_usb_driver(si470x_usb_driver);
860b09cd163SJoonyoung Shim 
861b09cd163SJoonyoung Shim MODULE_LICENSE("GPL");
862b09cd163SJoonyoung Shim MODULE_AUTHOR(DRIVER_AUTHOR);
863b09cd163SJoonyoung Shim MODULE_DESCRIPTION(DRIVER_DESC);
864b09cd163SJoonyoung Shim MODULE_VERSION(DRIVER_VERSION);
865