162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * drivers/media/radio/si470x/radio-si470x-usb.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * USB driver for radios with Silicon Labs Si470x FM Radio Receivers 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci/* 1262306a36Sopenharmony_ci * ToDo: 1362306a36Sopenharmony_ci * - add firmware download/update support 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci/* driver definitions */ 1862306a36Sopenharmony_ci#define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>" 1962306a36Sopenharmony_ci#define DRIVER_CARD "Silicon Labs Si470x FM Radio" 2062306a36Sopenharmony_ci#define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers" 2162306a36Sopenharmony_ci#define DRIVER_VERSION "1.0.10" 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci/* kernel includes */ 2462306a36Sopenharmony_ci#include <linux/usb.h> 2562306a36Sopenharmony_ci#include <linux/hid.h> 2662306a36Sopenharmony_ci#include <linux/slab.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#include "radio-si470x.h" 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/* USB Device ID List */ 3262306a36Sopenharmony_cistatic const struct usb_device_id si470x_usb_driver_id_table[] = { 3362306a36Sopenharmony_ci /* Silicon Labs USB FM Radio Reference Design */ 3462306a36Sopenharmony_ci { USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) }, 3562306a36Sopenharmony_ci /* ADS/Tech FM Radio Receiver (formerly Instant FM Music) */ 3662306a36Sopenharmony_ci { USB_DEVICE_AND_INTERFACE_INFO(0x06e1, 0xa155, USB_CLASS_HID, 0, 0) }, 3762306a36Sopenharmony_ci /* KWorld USB FM Radio SnapMusic Mobile 700 (FM700) */ 3862306a36Sopenharmony_ci { USB_DEVICE_AND_INTERFACE_INFO(0x1b80, 0xd700, USB_CLASS_HID, 0, 0) }, 3962306a36Sopenharmony_ci /* Sanei Electric, Inc. FM USB Radio (sold as DealExtreme.com PCear) */ 4062306a36Sopenharmony_ci { USB_DEVICE_AND_INTERFACE_INFO(0x10c5, 0x819a, USB_CLASS_HID, 0, 0) }, 4162306a36Sopenharmony_ci /* Axentia ALERT FM USB Receiver */ 4262306a36Sopenharmony_ci { USB_DEVICE_AND_INTERFACE_INFO(0x12cf, 0x7111, USB_CLASS_HID, 0, 0) }, 4362306a36Sopenharmony_ci /* Terminating entry */ 4462306a36Sopenharmony_ci { } 4562306a36Sopenharmony_ci}; 4662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, si470x_usb_driver_id_table); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci/************************************************************************** 5162306a36Sopenharmony_ci * Module Parameters 5262306a36Sopenharmony_ci **************************************************************************/ 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci/* Radio Nr */ 5562306a36Sopenharmony_cistatic int radio_nr = -1; 5662306a36Sopenharmony_cimodule_param(radio_nr, int, 0444); 5762306a36Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio Nr"); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/* USB timeout */ 6062306a36Sopenharmony_cistatic unsigned int usb_timeout = 500; 6162306a36Sopenharmony_cimodule_param(usb_timeout, uint, 0644); 6262306a36Sopenharmony_ciMODULE_PARM_DESC(usb_timeout, "USB timeout (ms): *500*"); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci/* RDS buffer blocks */ 6562306a36Sopenharmony_cistatic unsigned int rds_buf = 100; 6662306a36Sopenharmony_cimodule_param(rds_buf, uint, 0444); 6762306a36Sopenharmony_ciMODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*"); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci/* RDS maximum block errors */ 7062306a36Sopenharmony_cistatic unsigned short max_rds_errors = 1; 7162306a36Sopenharmony_ci/* 0 means 0 errors requiring correction */ 7262306a36Sopenharmony_ci/* 1 means 1-2 errors requiring correction (used by original USBRadio.exe) */ 7362306a36Sopenharmony_ci/* 2 means 3-5 errors requiring correction */ 7462306a36Sopenharmony_ci/* 3 means 6+ errors or errors in checkword, correction not possible */ 7562306a36Sopenharmony_cimodule_param(max_rds_errors, ushort, 0644); 7662306a36Sopenharmony_ciMODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*"); 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci/************************************************************************** 8162306a36Sopenharmony_ci * USB HID Reports 8262306a36Sopenharmony_ci **************************************************************************/ 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci/* Reports 1-16 give direct read/write access to the 16 Si470x registers */ 8562306a36Sopenharmony_ci/* with the (REPORT_ID - 1) corresponding to the register address across USB */ 8662306a36Sopenharmony_ci/* endpoint 0 using GET_REPORT and SET_REPORT */ 8762306a36Sopenharmony_ci#define REGISTER_REPORT_SIZE (RADIO_REGISTER_SIZE + 1) 8862306a36Sopenharmony_ci#define REGISTER_REPORT(reg) ((reg) + 1) 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci/* Report 17 gives direct read/write access to the entire Si470x register */ 9162306a36Sopenharmony_ci/* map across endpoint 0 using GET_REPORT and SET_REPORT */ 9262306a36Sopenharmony_ci#define ENTIRE_REPORT_SIZE (RADIO_REGISTER_NUM * RADIO_REGISTER_SIZE + 1) 9362306a36Sopenharmony_ci#define ENTIRE_REPORT 17 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci/* Report 18 is used to send the lowest 6 Si470x registers up the HID */ 9662306a36Sopenharmony_ci/* interrupt endpoint 1 to Windows every 20 milliseconds for status */ 9762306a36Sopenharmony_ci#define RDS_REPORT_SIZE (RDS_REGISTER_NUM * RADIO_REGISTER_SIZE + 1) 9862306a36Sopenharmony_ci#define RDS_REPORT 18 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci/* Report 19: LED state */ 10162306a36Sopenharmony_ci#define LED_REPORT_SIZE 3 10262306a36Sopenharmony_ci#define LED_REPORT 19 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci/* Report 19: stream */ 10562306a36Sopenharmony_ci#define STREAM_REPORT_SIZE 3 10662306a36Sopenharmony_ci#define STREAM_REPORT 19 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci/* Report 20: scratch */ 10962306a36Sopenharmony_ci#define SCRATCH_PAGE_SIZE 63 11062306a36Sopenharmony_ci#define SCRATCH_REPORT_SIZE (SCRATCH_PAGE_SIZE + 1) 11162306a36Sopenharmony_ci#define SCRATCH_REPORT 20 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci/* Reports 19-22: flash upgrade of the C8051F321 */ 11462306a36Sopenharmony_ci#define WRITE_REPORT_SIZE 4 11562306a36Sopenharmony_ci#define WRITE_REPORT 19 11662306a36Sopenharmony_ci#define FLASH_REPORT_SIZE 64 11762306a36Sopenharmony_ci#define FLASH_REPORT 20 11862306a36Sopenharmony_ci#define CRC_REPORT_SIZE 3 11962306a36Sopenharmony_ci#define CRC_REPORT 21 12062306a36Sopenharmony_ci#define RESPONSE_REPORT_SIZE 2 12162306a36Sopenharmony_ci#define RESPONSE_REPORT 22 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/* Report 23: currently unused, but can accept 60 byte reports on the HID */ 12462306a36Sopenharmony_ci/* interrupt out endpoint 2 every 1 millisecond */ 12562306a36Sopenharmony_ci#define UNUSED_REPORT 23 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci#define MAX_REPORT_SIZE 64 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci/************************************************************************** 13262306a36Sopenharmony_ci * Software/Hardware Versions from Scratch Page 13362306a36Sopenharmony_ci **************************************************************************/ 13462306a36Sopenharmony_ci#define RADIO_HW_VERSION 1 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci/************************************************************************** 13962306a36Sopenharmony_ci * LED State Definitions 14062306a36Sopenharmony_ci **************************************************************************/ 14162306a36Sopenharmony_ci#define LED_COMMAND 0x35 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci#define NO_CHANGE_LED 0x00 14462306a36Sopenharmony_ci#define ALL_COLOR_LED 0x01 /* streaming state */ 14562306a36Sopenharmony_ci#define BLINK_GREEN_LED 0x02 /* connect state */ 14662306a36Sopenharmony_ci#define BLINK_RED_LED 0x04 14762306a36Sopenharmony_ci#define BLINK_ORANGE_LED 0x10 /* disconnect state */ 14862306a36Sopenharmony_ci#define SOLID_GREEN_LED 0x20 /* tuning/seeking state */ 14962306a36Sopenharmony_ci#define SOLID_RED_LED 0x40 /* bootload state */ 15062306a36Sopenharmony_ci#define SOLID_ORANGE_LED 0x80 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci/************************************************************************** 15562306a36Sopenharmony_ci * Stream State Definitions 15662306a36Sopenharmony_ci **************************************************************************/ 15762306a36Sopenharmony_ci#define STREAM_COMMAND 0x36 15862306a36Sopenharmony_ci#define STREAM_VIDPID 0x00 15962306a36Sopenharmony_ci#define STREAM_AUDIO 0xff 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci/************************************************************************** 16462306a36Sopenharmony_ci * Bootloader / Flash Commands 16562306a36Sopenharmony_ci **************************************************************************/ 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci/* unique id sent to bootloader and required to put into a bootload state */ 16862306a36Sopenharmony_ci#define UNIQUE_BL_ID 0x34 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci/* mask for the flash data */ 17162306a36Sopenharmony_ci#define FLASH_DATA_MASK 0x55 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci/* bootloader commands */ 17462306a36Sopenharmony_ci#define GET_SW_VERSION_COMMAND 0x00 17562306a36Sopenharmony_ci#define SET_PAGE_COMMAND 0x01 17662306a36Sopenharmony_ci#define ERASE_PAGE_COMMAND 0x02 17762306a36Sopenharmony_ci#define WRITE_PAGE_COMMAND 0x03 17862306a36Sopenharmony_ci#define CRC_ON_PAGE_COMMAND 0x04 17962306a36Sopenharmony_ci#define READ_FLASH_BYTE_COMMAND 0x05 18062306a36Sopenharmony_ci#define RESET_DEVICE_COMMAND 0x06 18162306a36Sopenharmony_ci#define GET_HW_VERSION_COMMAND 0x07 18262306a36Sopenharmony_ci#define BLANK 0xff 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci/* bootloader command responses */ 18562306a36Sopenharmony_ci#define COMMAND_OK 0x01 18662306a36Sopenharmony_ci#define COMMAND_FAILED 0x02 18762306a36Sopenharmony_ci#define COMMAND_PENDING 0x03 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci/************************************************************************** 19262306a36Sopenharmony_ci * General Driver Functions - REGISTER_REPORTs 19362306a36Sopenharmony_ci **************************************************************************/ 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci/* 19662306a36Sopenharmony_ci * si470x_get_report - receive a HID report 19762306a36Sopenharmony_ci */ 19862306a36Sopenharmony_cistatic int si470x_get_report(struct si470x_device *radio, void *buf, int size) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci unsigned char *report = buf; 20162306a36Sopenharmony_ci int retval; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci retval = usb_control_msg(radio->usbdev, 20462306a36Sopenharmony_ci usb_rcvctrlpipe(radio->usbdev, 0), 20562306a36Sopenharmony_ci HID_REQ_GET_REPORT, 20662306a36Sopenharmony_ci USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, 20762306a36Sopenharmony_ci report[0], 2, 20862306a36Sopenharmony_ci buf, size, usb_timeout); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci if (retval < 0) 21162306a36Sopenharmony_ci dev_warn(&radio->intf->dev, 21262306a36Sopenharmony_ci "si470x_get_report: usb_control_msg returned %d\n", 21362306a36Sopenharmony_ci retval); 21462306a36Sopenharmony_ci return retval; 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci/* 21962306a36Sopenharmony_ci * si470x_set_report - send a HID report 22062306a36Sopenharmony_ci */ 22162306a36Sopenharmony_cistatic int si470x_set_report(struct si470x_device *radio, void *buf, int size) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci unsigned char *report = buf; 22462306a36Sopenharmony_ci int retval; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci retval = usb_control_msg(radio->usbdev, 22762306a36Sopenharmony_ci usb_sndctrlpipe(radio->usbdev, 0), 22862306a36Sopenharmony_ci HID_REQ_SET_REPORT, 22962306a36Sopenharmony_ci USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, 23062306a36Sopenharmony_ci report[0], 2, 23162306a36Sopenharmony_ci buf, size, usb_timeout); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci if (retval < 0) 23462306a36Sopenharmony_ci dev_warn(&radio->intf->dev, 23562306a36Sopenharmony_ci "si470x_set_report: usb_control_msg returned %d\n", 23662306a36Sopenharmony_ci retval); 23762306a36Sopenharmony_ci return retval; 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci/* 24262306a36Sopenharmony_ci * si470x_get_register - read register 24362306a36Sopenharmony_ci */ 24462306a36Sopenharmony_cistatic int si470x_get_register(struct si470x_device *radio, int regnr) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci int retval; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci radio->usb_buf[0] = REGISTER_REPORT(regnr); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci retval = si470x_get_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci if (retval >= 0) 25362306a36Sopenharmony_ci radio->registers[regnr] = get_unaligned_be16(&radio->usb_buf[1]); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci return (retval < 0) ? -EINVAL : 0; 25662306a36Sopenharmony_ci} 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci/* 26062306a36Sopenharmony_ci * si470x_set_register - write register 26162306a36Sopenharmony_ci */ 26262306a36Sopenharmony_cistatic int si470x_set_register(struct si470x_device *radio, int regnr) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci int retval; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci radio->usb_buf[0] = REGISTER_REPORT(regnr); 26762306a36Sopenharmony_ci put_unaligned_be16(radio->registers[regnr], &radio->usb_buf[1]); 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci retval = si470x_set_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci return (retval < 0) ? -EINVAL : 0; 27262306a36Sopenharmony_ci} 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci/************************************************************************** 27762306a36Sopenharmony_ci * General Driver Functions - ENTIRE_REPORT 27862306a36Sopenharmony_ci **************************************************************************/ 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci/* 28162306a36Sopenharmony_ci * si470x_get_all_registers - read entire registers 28262306a36Sopenharmony_ci */ 28362306a36Sopenharmony_cistatic int si470x_get_all_registers(struct si470x_device *radio) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci int retval; 28662306a36Sopenharmony_ci unsigned char regnr; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci radio->usb_buf[0] = ENTIRE_REPORT; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci retval = si470x_get_report(radio, radio->usb_buf, ENTIRE_REPORT_SIZE); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci if (retval >= 0) 29362306a36Sopenharmony_ci for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++) 29462306a36Sopenharmony_ci radio->registers[regnr] = get_unaligned_be16( 29562306a36Sopenharmony_ci &radio->usb_buf[regnr * RADIO_REGISTER_SIZE + 1]); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci return (retval < 0) ? -EINVAL : 0; 29862306a36Sopenharmony_ci} 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci/************************************************************************** 30362306a36Sopenharmony_ci * General Driver Functions - LED_REPORT 30462306a36Sopenharmony_ci **************************************************************************/ 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci/* 30762306a36Sopenharmony_ci * si470x_set_led_state - sets the led state 30862306a36Sopenharmony_ci */ 30962306a36Sopenharmony_cistatic int si470x_set_led_state(struct si470x_device *radio, 31062306a36Sopenharmony_ci unsigned char led_state) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci int retval; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci radio->usb_buf[0] = LED_REPORT; 31562306a36Sopenharmony_ci radio->usb_buf[1] = LED_COMMAND; 31662306a36Sopenharmony_ci radio->usb_buf[2] = led_state; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci retval = si470x_set_report(radio, radio->usb_buf, LED_REPORT_SIZE); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci return (retval < 0) ? -EINVAL : 0; 32162306a36Sopenharmony_ci} 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci/************************************************************************** 32662306a36Sopenharmony_ci * General Driver Functions - SCRATCH_REPORT 32762306a36Sopenharmony_ci **************************************************************************/ 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci/* 33062306a36Sopenharmony_ci * si470x_get_scratch_versions - gets the scratch page and version infos 33162306a36Sopenharmony_ci */ 33262306a36Sopenharmony_cistatic int si470x_get_scratch_page_versions(struct si470x_device *radio) 33362306a36Sopenharmony_ci{ 33462306a36Sopenharmony_ci int retval; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci radio->usb_buf[0] = SCRATCH_REPORT; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci retval = si470x_get_report(radio, radio->usb_buf, SCRATCH_REPORT_SIZE); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci if (retval < 0) 34162306a36Sopenharmony_ci dev_warn(&radio->intf->dev, "si470x_get_scratch: si470x_get_report returned %d\n", 34262306a36Sopenharmony_ci retval); 34362306a36Sopenharmony_ci else { 34462306a36Sopenharmony_ci radio->software_version = radio->usb_buf[1]; 34562306a36Sopenharmony_ci radio->hardware_version = radio->usb_buf[2]; 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci return (retval < 0) ? -EINVAL : 0; 34962306a36Sopenharmony_ci} 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci/************************************************************************** 35462306a36Sopenharmony_ci * RDS Driver Functions 35562306a36Sopenharmony_ci **************************************************************************/ 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci/* 35862306a36Sopenharmony_ci * si470x_int_in_callback - rds callback and processing function 35962306a36Sopenharmony_ci * 36062306a36Sopenharmony_ci * TODO: do we need to use mutex locks in some sections? 36162306a36Sopenharmony_ci */ 36262306a36Sopenharmony_cistatic void si470x_int_in_callback(struct urb *urb) 36362306a36Sopenharmony_ci{ 36462306a36Sopenharmony_ci struct si470x_device *radio = urb->context; 36562306a36Sopenharmony_ci int retval; 36662306a36Sopenharmony_ci unsigned char regnr; 36762306a36Sopenharmony_ci unsigned char blocknum; 36862306a36Sopenharmony_ci unsigned short bler; /* rds block errors */ 36962306a36Sopenharmony_ci unsigned short rds; 37062306a36Sopenharmony_ci unsigned char tmpbuf[3]; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci if (urb->status) { 37362306a36Sopenharmony_ci if (urb->status == -ENOENT || 37462306a36Sopenharmony_ci urb->status == -ECONNRESET || 37562306a36Sopenharmony_ci urb->status == -ESHUTDOWN) { 37662306a36Sopenharmony_ci return; 37762306a36Sopenharmony_ci } else { 37862306a36Sopenharmony_ci dev_warn(&radio->intf->dev, 37962306a36Sopenharmony_ci "non-zero urb status (%d)\n", urb->status); 38062306a36Sopenharmony_ci goto resubmit; /* Maybe we can recover. */ 38162306a36Sopenharmony_ci } 38262306a36Sopenharmony_ci } 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci /* Sometimes the device returns len 0 packets */ 38562306a36Sopenharmony_ci if (urb->actual_length != RDS_REPORT_SIZE) 38662306a36Sopenharmony_ci goto resubmit; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci radio->registers[STATUSRSSI] = 38962306a36Sopenharmony_ci get_unaligned_be16(&radio->int_in_buffer[1]); 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci if (radio->registers[STATUSRSSI] & STATUSRSSI_STC) 39262306a36Sopenharmony_ci complete(&radio->completion); 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS)) { 39562306a36Sopenharmony_ci /* Update RDS registers with URB data */ 39662306a36Sopenharmony_ci for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++) 39762306a36Sopenharmony_ci radio->registers[STATUSRSSI + regnr] = 39862306a36Sopenharmony_ci get_unaligned_be16(&radio->int_in_buffer[ 39962306a36Sopenharmony_ci regnr * RADIO_REGISTER_SIZE + 1]); 40062306a36Sopenharmony_ci /* get rds blocks */ 40162306a36Sopenharmony_ci if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) { 40262306a36Sopenharmony_ci /* No RDS group ready, better luck next time */ 40362306a36Sopenharmony_ci goto resubmit; 40462306a36Sopenharmony_ci } 40562306a36Sopenharmony_ci if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) { 40662306a36Sopenharmony_ci /* RDS decoder not synchronized */ 40762306a36Sopenharmony_ci goto resubmit; 40862306a36Sopenharmony_ci } 40962306a36Sopenharmony_ci for (blocknum = 0; blocknum < 4; blocknum++) { 41062306a36Sopenharmony_ci switch (blocknum) { 41162306a36Sopenharmony_ci default: 41262306a36Sopenharmony_ci bler = (radio->registers[STATUSRSSI] & 41362306a36Sopenharmony_ci STATUSRSSI_BLERA) >> 9; 41462306a36Sopenharmony_ci rds = radio->registers[RDSA]; 41562306a36Sopenharmony_ci break; 41662306a36Sopenharmony_ci case 1: 41762306a36Sopenharmony_ci bler = (radio->registers[READCHAN] & 41862306a36Sopenharmony_ci READCHAN_BLERB) >> 14; 41962306a36Sopenharmony_ci rds = radio->registers[RDSB]; 42062306a36Sopenharmony_ci break; 42162306a36Sopenharmony_ci case 2: 42262306a36Sopenharmony_ci bler = (radio->registers[READCHAN] & 42362306a36Sopenharmony_ci READCHAN_BLERC) >> 12; 42462306a36Sopenharmony_ci rds = radio->registers[RDSC]; 42562306a36Sopenharmony_ci break; 42662306a36Sopenharmony_ci case 3: 42762306a36Sopenharmony_ci bler = (radio->registers[READCHAN] & 42862306a36Sopenharmony_ci READCHAN_BLERD) >> 10; 42962306a36Sopenharmony_ci rds = radio->registers[RDSD]; 43062306a36Sopenharmony_ci break; 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci /* Fill the V4L2 RDS buffer */ 43462306a36Sopenharmony_ci put_unaligned_le16(rds, &tmpbuf); 43562306a36Sopenharmony_ci tmpbuf[2] = blocknum; /* offset name */ 43662306a36Sopenharmony_ci tmpbuf[2] |= blocknum << 3; /* received offset */ 43762306a36Sopenharmony_ci if (bler > max_rds_errors) 43862306a36Sopenharmony_ci tmpbuf[2] |= 0x80; /* uncorrectable errors */ 43962306a36Sopenharmony_ci else if (bler > 0) 44062306a36Sopenharmony_ci tmpbuf[2] |= 0x40; /* corrected error(s) */ 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci /* copy RDS block to internal buffer */ 44362306a36Sopenharmony_ci memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3); 44462306a36Sopenharmony_ci radio->wr_index += 3; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci /* wrap write pointer */ 44762306a36Sopenharmony_ci if (radio->wr_index >= radio->buf_size) 44862306a36Sopenharmony_ci radio->wr_index = 0; 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci /* check for overflow */ 45162306a36Sopenharmony_ci if (radio->wr_index == radio->rd_index) { 45262306a36Sopenharmony_ci /* increment and wrap read pointer */ 45362306a36Sopenharmony_ci radio->rd_index += 3; 45462306a36Sopenharmony_ci if (radio->rd_index >= radio->buf_size) 45562306a36Sopenharmony_ci radio->rd_index = 0; 45662306a36Sopenharmony_ci } 45762306a36Sopenharmony_ci } 45862306a36Sopenharmony_ci if (radio->wr_index != radio->rd_index) 45962306a36Sopenharmony_ci wake_up_interruptible(&radio->read_queue); 46062306a36Sopenharmony_ci } 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ciresubmit: 46362306a36Sopenharmony_ci /* Resubmit if we're still running. */ 46462306a36Sopenharmony_ci if (radio->int_in_running && radio->usbdev) { 46562306a36Sopenharmony_ci retval = usb_submit_urb(radio->int_in_urb, GFP_ATOMIC); 46662306a36Sopenharmony_ci if (retval) { 46762306a36Sopenharmony_ci dev_warn(&radio->intf->dev, 46862306a36Sopenharmony_ci "resubmitting urb failed (%d)", retval); 46962306a36Sopenharmony_ci radio->int_in_running = 0; 47062306a36Sopenharmony_ci } 47162306a36Sopenharmony_ci } 47262306a36Sopenharmony_ci radio->status_rssi_auto_update = radio->int_in_running; 47362306a36Sopenharmony_ci} 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_cistatic int si470x_fops_open(struct file *file) 47762306a36Sopenharmony_ci{ 47862306a36Sopenharmony_ci return v4l2_fh_open(file); 47962306a36Sopenharmony_ci} 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_cistatic int si470x_fops_release(struct file *file) 48262306a36Sopenharmony_ci{ 48362306a36Sopenharmony_ci return v4l2_fh_release(file); 48462306a36Sopenharmony_ci} 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_cistatic void si470x_usb_release(struct v4l2_device *v4l2_dev) 48762306a36Sopenharmony_ci{ 48862306a36Sopenharmony_ci struct si470x_device *radio = 48962306a36Sopenharmony_ci container_of(v4l2_dev, struct si470x_device, v4l2_dev); 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci usb_free_urb(radio->int_in_urb); 49262306a36Sopenharmony_ci v4l2_ctrl_handler_free(&radio->hdl); 49362306a36Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 49462306a36Sopenharmony_ci kfree(radio->int_in_buffer); 49562306a36Sopenharmony_ci kfree(radio->buffer); 49662306a36Sopenharmony_ci kfree(radio->usb_buf); 49762306a36Sopenharmony_ci kfree(radio); 49862306a36Sopenharmony_ci} 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci/************************************************************************** 50262306a36Sopenharmony_ci * Video4Linux Interface 50362306a36Sopenharmony_ci **************************************************************************/ 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci/* 50662306a36Sopenharmony_ci * si470x_vidioc_querycap - query device capabilities 50762306a36Sopenharmony_ci */ 50862306a36Sopenharmony_cistatic int si470x_vidioc_querycap(struct file *file, void *priv, 50962306a36Sopenharmony_ci struct v4l2_capability *capability) 51062306a36Sopenharmony_ci{ 51162306a36Sopenharmony_ci struct si470x_device *radio = video_drvdata(file); 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci strscpy(capability->driver, DRIVER_NAME, sizeof(capability->driver)); 51462306a36Sopenharmony_ci strscpy(capability->card, DRIVER_CARD, sizeof(capability->card)); 51562306a36Sopenharmony_ci usb_make_path(radio->usbdev, capability->bus_info, 51662306a36Sopenharmony_ci sizeof(capability->bus_info)); 51762306a36Sopenharmony_ci return 0; 51862306a36Sopenharmony_ci} 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_cistatic int si470x_start_usb(struct si470x_device *radio) 52262306a36Sopenharmony_ci{ 52362306a36Sopenharmony_ci int retval; 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci /* initialize interrupt urb */ 52662306a36Sopenharmony_ci usb_fill_int_urb(radio->int_in_urb, radio->usbdev, 52762306a36Sopenharmony_ci usb_rcvintpipe(radio->usbdev, 52862306a36Sopenharmony_ci radio->int_in_endpoint->bEndpointAddress), 52962306a36Sopenharmony_ci radio->int_in_buffer, 53062306a36Sopenharmony_ci le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize), 53162306a36Sopenharmony_ci si470x_int_in_callback, 53262306a36Sopenharmony_ci radio, 53362306a36Sopenharmony_ci radio->int_in_endpoint->bInterval); 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci radio->int_in_running = 1; 53662306a36Sopenharmony_ci mb(); 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci retval = usb_submit_urb(radio->int_in_urb, GFP_KERNEL); 53962306a36Sopenharmony_ci if (retval) { 54062306a36Sopenharmony_ci dev_info(&radio->intf->dev, 54162306a36Sopenharmony_ci "submitting int urb failed (%d)\n", retval); 54262306a36Sopenharmony_ci radio->int_in_running = 0; 54362306a36Sopenharmony_ci } 54462306a36Sopenharmony_ci radio->status_rssi_auto_update = radio->int_in_running; 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci /* start radio */ 54762306a36Sopenharmony_ci retval = si470x_start(radio); 54862306a36Sopenharmony_ci if (retval < 0) 54962306a36Sopenharmony_ci return retval; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci v4l2_ctrl_handler_setup(&radio->hdl); 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci return retval; 55462306a36Sopenharmony_ci} 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci/************************************************************************** 55762306a36Sopenharmony_ci * USB Interface 55862306a36Sopenharmony_ci **************************************************************************/ 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_ci/* 56162306a36Sopenharmony_ci * si470x_usb_driver_probe - probe for the device 56262306a36Sopenharmony_ci */ 56362306a36Sopenharmony_cistatic int si470x_usb_driver_probe(struct usb_interface *intf, 56462306a36Sopenharmony_ci const struct usb_device_id *id) 56562306a36Sopenharmony_ci{ 56662306a36Sopenharmony_ci struct si470x_device *radio; 56762306a36Sopenharmony_ci struct usb_host_interface *iface_desc; 56862306a36Sopenharmony_ci struct usb_endpoint_descriptor *endpoint; 56962306a36Sopenharmony_ci int i, int_end_size, retval; 57062306a36Sopenharmony_ci unsigned char version_warning = 0; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci /* private data allocation and initialization */ 57362306a36Sopenharmony_ci radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL); 57462306a36Sopenharmony_ci if (!radio) { 57562306a36Sopenharmony_ci retval = -ENOMEM; 57662306a36Sopenharmony_ci goto err_initial; 57762306a36Sopenharmony_ci } 57862306a36Sopenharmony_ci radio->usb_buf = kmalloc(MAX_REPORT_SIZE, GFP_KERNEL); 57962306a36Sopenharmony_ci if (radio->usb_buf == NULL) { 58062306a36Sopenharmony_ci retval = -ENOMEM; 58162306a36Sopenharmony_ci goto err_radio; 58262306a36Sopenharmony_ci } 58362306a36Sopenharmony_ci radio->usbdev = interface_to_usbdev(intf); 58462306a36Sopenharmony_ci radio->intf = intf; 58562306a36Sopenharmony_ci radio->band = 1; /* Default to 76 - 108 MHz */ 58662306a36Sopenharmony_ci mutex_init(&radio->lock); 58762306a36Sopenharmony_ci init_completion(&radio->completion); 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci radio->get_register = si470x_get_register; 59062306a36Sopenharmony_ci radio->set_register = si470x_set_register; 59162306a36Sopenharmony_ci radio->fops_open = si470x_fops_open; 59262306a36Sopenharmony_ci radio->fops_release = si470x_fops_release; 59362306a36Sopenharmony_ci radio->vidioc_querycap = si470x_vidioc_querycap; 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci iface_desc = intf->cur_altsetting; 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci /* Set up interrupt endpoint information. */ 59862306a36Sopenharmony_ci for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { 59962306a36Sopenharmony_ci endpoint = &iface_desc->endpoint[i].desc; 60062306a36Sopenharmony_ci if (usb_endpoint_is_int_in(endpoint)) 60162306a36Sopenharmony_ci radio->int_in_endpoint = endpoint; 60262306a36Sopenharmony_ci } 60362306a36Sopenharmony_ci if (!radio->int_in_endpoint) { 60462306a36Sopenharmony_ci dev_info(&intf->dev, "could not find interrupt in endpoint\n"); 60562306a36Sopenharmony_ci retval = -EIO; 60662306a36Sopenharmony_ci goto err_usbbuf; 60762306a36Sopenharmony_ci } 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci int_end_size = le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize); 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci radio->int_in_buffer = kmalloc(int_end_size, GFP_KERNEL); 61262306a36Sopenharmony_ci if (!radio->int_in_buffer) { 61362306a36Sopenharmony_ci dev_info(&intf->dev, "could not allocate int_in_buffer"); 61462306a36Sopenharmony_ci retval = -ENOMEM; 61562306a36Sopenharmony_ci goto err_usbbuf; 61662306a36Sopenharmony_ci } 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci radio->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); 61962306a36Sopenharmony_ci if (!radio->int_in_urb) { 62062306a36Sopenharmony_ci retval = -ENOMEM; 62162306a36Sopenharmony_ci goto err_intbuffer; 62262306a36Sopenharmony_ci } 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci radio->v4l2_dev.release = si470x_usb_release; 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci /* 62762306a36Sopenharmony_ci * The si470x SiLabs reference design uses the same USB IDs as 62862306a36Sopenharmony_ci * 'Thanko's Raremono' si4734 based receiver. So check here which we 62962306a36Sopenharmony_ci * have: attempt to read the device ID from the si470x: the lower 12 63062306a36Sopenharmony_ci * bits should be 0x0242 for the si470x. 63162306a36Sopenharmony_ci * 63262306a36Sopenharmony_ci * We use this check to determine which device we are dealing with. 63362306a36Sopenharmony_ci */ 63462306a36Sopenharmony_ci if (id->idVendor == 0x10c4 && id->idProduct == 0x818a) { 63562306a36Sopenharmony_ci retval = usb_control_msg(radio->usbdev, 63662306a36Sopenharmony_ci usb_rcvctrlpipe(radio->usbdev, 0), 63762306a36Sopenharmony_ci HID_REQ_GET_REPORT, 63862306a36Sopenharmony_ci USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, 63962306a36Sopenharmony_ci 1, 2, 64062306a36Sopenharmony_ci radio->usb_buf, 3, 500); 64162306a36Sopenharmony_ci if (retval != 3 || 64262306a36Sopenharmony_ci (get_unaligned_be16(&radio->usb_buf[1]) & 0xfff) != 0x0242) { 64362306a36Sopenharmony_ci dev_info(&intf->dev, "this is not a si470x device.\n"); 64462306a36Sopenharmony_ci retval = -ENODEV; 64562306a36Sopenharmony_ci goto err_urb; 64662306a36Sopenharmony_ci } 64762306a36Sopenharmony_ci } 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); 65062306a36Sopenharmony_ci if (retval < 0) { 65162306a36Sopenharmony_ci dev_err(&intf->dev, "couldn't register v4l2_device\n"); 65262306a36Sopenharmony_ci goto err_urb; 65362306a36Sopenharmony_ci } 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci v4l2_ctrl_handler_init(&radio->hdl, 2); 65662306a36Sopenharmony_ci v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops, 65762306a36Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 65862306a36Sopenharmony_ci v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops, 65962306a36Sopenharmony_ci V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 15); 66062306a36Sopenharmony_ci if (radio->hdl.error) { 66162306a36Sopenharmony_ci retval = radio->hdl.error; 66262306a36Sopenharmony_ci dev_err(&intf->dev, "couldn't register control\n"); 66362306a36Sopenharmony_ci goto err_dev; 66462306a36Sopenharmony_ci } 66562306a36Sopenharmony_ci radio->videodev = si470x_viddev_template; 66662306a36Sopenharmony_ci radio->videodev.ctrl_handler = &radio->hdl; 66762306a36Sopenharmony_ci radio->videodev.lock = &radio->lock; 66862306a36Sopenharmony_ci radio->videodev.v4l2_dev = &radio->v4l2_dev; 66962306a36Sopenharmony_ci radio->videodev.release = video_device_release_empty; 67062306a36Sopenharmony_ci radio->videodev.device_caps = 67162306a36Sopenharmony_ci V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE | V4L2_CAP_TUNER | 67262306a36Sopenharmony_ci V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE; 67362306a36Sopenharmony_ci video_set_drvdata(&radio->videodev, radio); 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci /* get device and chip versions */ 67662306a36Sopenharmony_ci if (si470x_get_all_registers(radio) < 0) { 67762306a36Sopenharmony_ci retval = -EIO; 67862306a36Sopenharmony_ci goto err_ctrl; 67962306a36Sopenharmony_ci } 68062306a36Sopenharmony_ci dev_info(&intf->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n", 68162306a36Sopenharmony_ci radio->registers[DEVICEID], radio->registers[SI_CHIPID]); 68262306a36Sopenharmony_ci if ((radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE) < RADIO_FW_VERSION) { 68362306a36Sopenharmony_ci dev_warn(&intf->dev, 68462306a36Sopenharmony_ci "This driver is known to work with firmware version %u, but the device has firmware version %u.\n", 68562306a36Sopenharmony_ci RADIO_FW_VERSION, 68662306a36Sopenharmony_ci radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE); 68762306a36Sopenharmony_ci version_warning = 1; 68862306a36Sopenharmony_ci } 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci /* get software and hardware versions */ 69162306a36Sopenharmony_ci if (si470x_get_scratch_page_versions(radio) < 0) { 69262306a36Sopenharmony_ci retval = -EIO; 69362306a36Sopenharmony_ci goto err_ctrl; 69462306a36Sopenharmony_ci } 69562306a36Sopenharmony_ci dev_info(&intf->dev, "software version %d, hardware version %d\n", 69662306a36Sopenharmony_ci radio->software_version, radio->hardware_version); 69762306a36Sopenharmony_ci if (radio->hardware_version < RADIO_HW_VERSION) { 69862306a36Sopenharmony_ci dev_warn(&intf->dev, 69962306a36Sopenharmony_ci "This driver is known to work with hardware version %u, but the device has hardware version %u.\n", 70062306a36Sopenharmony_ci RADIO_HW_VERSION, 70162306a36Sopenharmony_ci radio->hardware_version); 70262306a36Sopenharmony_ci version_warning = 1; 70362306a36Sopenharmony_ci } 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci /* give out version warning */ 70662306a36Sopenharmony_ci if (version_warning == 1) { 70762306a36Sopenharmony_ci dev_warn(&intf->dev, 70862306a36Sopenharmony_ci "If you have some trouble using this driver, please report to V4L ML at linux-media@vger.kernel.org\n"); 70962306a36Sopenharmony_ci } 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci /* set led to connect state */ 71262306a36Sopenharmony_ci si470x_set_led_state(radio, BLINK_GREEN_LED); 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci /* rds buffer allocation */ 71562306a36Sopenharmony_ci radio->buf_size = rds_buf * 3; 71662306a36Sopenharmony_ci radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL); 71762306a36Sopenharmony_ci if (!radio->buffer) { 71862306a36Sopenharmony_ci retval = -EIO; 71962306a36Sopenharmony_ci goto err_ctrl; 72062306a36Sopenharmony_ci } 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci /* rds buffer configuration */ 72362306a36Sopenharmony_ci radio->wr_index = 0; 72462306a36Sopenharmony_ci radio->rd_index = 0; 72562306a36Sopenharmony_ci init_waitqueue_head(&radio->read_queue); 72662306a36Sopenharmony_ci usb_set_intfdata(intf, radio); 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci /* start radio */ 72962306a36Sopenharmony_ci retval = si470x_start_usb(radio); 73062306a36Sopenharmony_ci if (retval < 0 && !radio->int_in_running) 73162306a36Sopenharmony_ci goto err_buf; 73262306a36Sopenharmony_ci else if (retval < 0) /* in case of radio->int_in_running == 1 */ 73362306a36Sopenharmony_ci goto err_all; 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci /* set initial frequency */ 73662306a36Sopenharmony_ci si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */ 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci /* register video device */ 73962306a36Sopenharmony_ci retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, 74062306a36Sopenharmony_ci radio_nr); 74162306a36Sopenharmony_ci if (retval) { 74262306a36Sopenharmony_ci dev_err(&intf->dev, "Could not register video device\n"); 74362306a36Sopenharmony_ci goto err_all; 74462306a36Sopenharmony_ci } 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci return 0; 74762306a36Sopenharmony_cierr_all: 74862306a36Sopenharmony_ci usb_kill_urb(radio->int_in_urb); 74962306a36Sopenharmony_cierr_buf: 75062306a36Sopenharmony_ci kfree(radio->buffer); 75162306a36Sopenharmony_cierr_ctrl: 75262306a36Sopenharmony_ci v4l2_ctrl_handler_free(&radio->hdl); 75362306a36Sopenharmony_cierr_dev: 75462306a36Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 75562306a36Sopenharmony_cierr_urb: 75662306a36Sopenharmony_ci usb_free_urb(radio->int_in_urb); 75762306a36Sopenharmony_cierr_intbuffer: 75862306a36Sopenharmony_ci kfree(radio->int_in_buffer); 75962306a36Sopenharmony_cierr_usbbuf: 76062306a36Sopenharmony_ci kfree(radio->usb_buf); 76162306a36Sopenharmony_cierr_radio: 76262306a36Sopenharmony_ci kfree(radio); 76362306a36Sopenharmony_cierr_initial: 76462306a36Sopenharmony_ci return retval; 76562306a36Sopenharmony_ci} 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci/* 76962306a36Sopenharmony_ci * si470x_usb_driver_suspend - suspend the device 77062306a36Sopenharmony_ci */ 77162306a36Sopenharmony_cistatic int si470x_usb_driver_suspend(struct usb_interface *intf, 77262306a36Sopenharmony_ci pm_message_t message) 77362306a36Sopenharmony_ci{ 77462306a36Sopenharmony_ci struct si470x_device *radio = usb_get_intfdata(intf); 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_ci dev_info(&intf->dev, "suspending now...\n"); 77762306a36Sopenharmony_ci 77862306a36Sopenharmony_ci /* shutdown interrupt handler */ 77962306a36Sopenharmony_ci if (radio->int_in_running) { 78062306a36Sopenharmony_ci radio->int_in_running = 0; 78162306a36Sopenharmony_ci if (radio->int_in_urb) 78262306a36Sopenharmony_ci usb_kill_urb(radio->int_in_urb); 78362306a36Sopenharmony_ci } 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_ci /* cancel read processes */ 78662306a36Sopenharmony_ci wake_up_interruptible(&radio->read_queue); 78762306a36Sopenharmony_ci 78862306a36Sopenharmony_ci /* stop radio */ 78962306a36Sopenharmony_ci si470x_stop(radio); 79062306a36Sopenharmony_ci return 0; 79162306a36Sopenharmony_ci} 79262306a36Sopenharmony_ci 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci/* 79562306a36Sopenharmony_ci * si470x_usb_driver_resume - resume the device 79662306a36Sopenharmony_ci */ 79762306a36Sopenharmony_cistatic int si470x_usb_driver_resume(struct usb_interface *intf) 79862306a36Sopenharmony_ci{ 79962306a36Sopenharmony_ci struct si470x_device *radio = usb_get_intfdata(intf); 80062306a36Sopenharmony_ci int ret; 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_ci dev_info(&intf->dev, "resuming now...\n"); 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_ci /* start radio */ 80562306a36Sopenharmony_ci ret = si470x_start_usb(radio); 80662306a36Sopenharmony_ci if (ret == 0) 80762306a36Sopenharmony_ci v4l2_ctrl_handler_setup(&radio->hdl); 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci return ret; 81062306a36Sopenharmony_ci} 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci 81362306a36Sopenharmony_ci/* 81462306a36Sopenharmony_ci * si470x_usb_driver_disconnect - disconnect the device 81562306a36Sopenharmony_ci */ 81662306a36Sopenharmony_cistatic void si470x_usb_driver_disconnect(struct usb_interface *intf) 81762306a36Sopenharmony_ci{ 81862306a36Sopenharmony_ci struct si470x_device *radio = usb_get_intfdata(intf); 81962306a36Sopenharmony_ci 82062306a36Sopenharmony_ci mutex_lock(&radio->lock); 82162306a36Sopenharmony_ci v4l2_device_disconnect(&radio->v4l2_dev); 82262306a36Sopenharmony_ci video_unregister_device(&radio->videodev); 82362306a36Sopenharmony_ci usb_kill_urb(radio->int_in_urb); 82462306a36Sopenharmony_ci usb_set_intfdata(intf, NULL); 82562306a36Sopenharmony_ci mutex_unlock(&radio->lock); 82662306a36Sopenharmony_ci v4l2_device_put(&radio->v4l2_dev); 82762306a36Sopenharmony_ci} 82862306a36Sopenharmony_ci 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_ci/* 83162306a36Sopenharmony_ci * si470x_usb_driver - usb driver interface 83262306a36Sopenharmony_ci * 83362306a36Sopenharmony_ci * A note on suspend/resume: this driver had only empty suspend/resume 83462306a36Sopenharmony_ci * functions, and when I tried to test suspend/resume it always disconnected 83562306a36Sopenharmony_ci * instead of resuming (using my ADS InstantFM stick). So I've decided to 83662306a36Sopenharmony_ci * remove these callbacks until someone else with better hardware can 83762306a36Sopenharmony_ci * implement and test this. 83862306a36Sopenharmony_ci */ 83962306a36Sopenharmony_cistatic struct usb_driver si470x_usb_driver = { 84062306a36Sopenharmony_ci .name = DRIVER_NAME, 84162306a36Sopenharmony_ci .probe = si470x_usb_driver_probe, 84262306a36Sopenharmony_ci .disconnect = si470x_usb_driver_disconnect, 84362306a36Sopenharmony_ci .suspend = si470x_usb_driver_suspend, 84462306a36Sopenharmony_ci .resume = si470x_usb_driver_resume, 84562306a36Sopenharmony_ci .reset_resume = si470x_usb_driver_resume, 84662306a36Sopenharmony_ci .id_table = si470x_usb_driver_id_table, 84762306a36Sopenharmony_ci}; 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_cimodule_usb_driver(si470x_usb_driver); 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 85262306a36Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR); 85362306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 85462306a36Sopenharmony_ciMODULE_VERSION(DRIVER_VERSION); 855