18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. 48c2ecf20Sopenharmony_ci * All rights reserved. 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci/* kernel includes */ 88c2ecf20Sopenharmony_ci#include <linux/kernel.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/usb.h> 118c2ecf20Sopenharmony_ci#include <linux/init.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci#include <linux/input.h> 148c2ecf20Sopenharmony_ci#include <linux/mutex.h> 158c2ecf20Sopenharmony_ci#include <linux/i2c.h> 168c2ecf20Sopenharmony_ci/* V4l includes */ 178c2ecf20Sopenharmony_ci#include <linux/videodev2.h> 188c2ecf20Sopenharmony_ci#include <media/v4l2-common.h> 198c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 208c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 218c2ecf20Sopenharmony_ci#include <media/v4l2-event.h> 228c2ecf20Sopenharmony_ci#include <linux/platform_data/media/si4713.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#include "si4713.h" 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci/* driver and module definitions */ 278c2ecf20Sopenharmony_ciMODULE_AUTHOR("Dinesh Ram <dinesh.ram@cern.ch>"); 288c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Si4713 FM Transmitter USB driver"); 298c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci/* The Device announces itself as Cygnal Integrated Products, Inc. */ 328c2ecf20Sopenharmony_ci#define USB_SI4713_VENDOR 0x10c4 338c2ecf20Sopenharmony_ci#define USB_SI4713_PRODUCT 0x8244 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#define BUFFER_LENGTH 64 368c2ecf20Sopenharmony_ci#define USB_TIMEOUT 1000 378c2ecf20Sopenharmony_ci#define USB_RESP_TIMEOUT 50000 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci/* USB Device ID List */ 408c2ecf20Sopenharmony_cistatic const struct usb_device_id usb_si4713_usb_device_table[] = { 418c2ecf20Sopenharmony_ci {USB_DEVICE_AND_INTERFACE_INFO(USB_SI4713_VENDOR, USB_SI4713_PRODUCT, 428c2ecf20Sopenharmony_ci USB_CLASS_HID, 0, 0) }, 438c2ecf20Sopenharmony_ci { } /* Terminating entry */ 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, usb_si4713_usb_device_table); 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistruct si4713_usb_device { 498c2ecf20Sopenharmony_ci struct usb_device *usbdev; 508c2ecf20Sopenharmony_ci struct usb_interface *intf; 518c2ecf20Sopenharmony_ci struct video_device vdev; 528c2ecf20Sopenharmony_ci struct v4l2_device v4l2_dev; 538c2ecf20Sopenharmony_ci struct v4l2_subdev *v4l2_subdev; 548c2ecf20Sopenharmony_ci struct mutex lock; 558c2ecf20Sopenharmony_ci struct i2c_adapter i2c_adapter; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci u8 *buffer; 588c2ecf20Sopenharmony_ci}; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic inline struct si4713_usb_device *to_si4713_dev(struct v4l2_device *v4l2_dev) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci return container_of(v4l2_dev, struct si4713_usb_device, v4l2_dev); 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 668c2ecf20Sopenharmony_ci struct v4l2_capability *v) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci struct si4713_usb_device *radio = video_drvdata(file); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci strscpy(v->driver, "radio-usb-si4713", sizeof(v->driver)); 718c2ecf20Sopenharmony_ci strscpy(v->card, "Si4713 FM Transmitter", sizeof(v->card)); 728c2ecf20Sopenharmony_ci usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); 738c2ecf20Sopenharmony_ci return 0; 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic int vidioc_g_modulator(struct file *file, void *priv, 778c2ecf20Sopenharmony_ci struct v4l2_modulator *vm) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci struct si4713_usb_device *radio = video_drvdata(file); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci return v4l2_subdev_call(radio->v4l2_subdev, tuner, g_modulator, vm); 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic int vidioc_s_modulator(struct file *file, void *priv, 858c2ecf20Sopenharmony_ci const struct v4l2_modulator *vm) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci struct si4713_usb_device *radio = video_drvdata(file); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci return v4l2_subdev_call(radio->v4l2_subdev, tuner, s_modulator, vm); 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 938c2ecf20Sopenharmony_ci const struct v4l2_frequency *vf) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci struct si4713_usb_device *radio = video_drvdata(file); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci return v4l2_subdev_call(radio->v4l2_subdev, tuner, s_frequency, vf); 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 1018c2ecf20Sopenharmony_ci struct v4l2_frequency *vf) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci struct si4713_usb_device *radio = video_drvdata(file); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci return v4l2_subdev_call(radio->v4l2_subdev, tuner, g_frequency, vf); 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops usb_si4713_ioctl_ops = { 1098c2ecf20Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 1108c2ecf20Sopenharmony_ci .vidioc_g_modulator = vidioc_g_modulator, 1118c2ecf20Sopenharmony_ci .vidioc_s_modulator = vidioc_s_modulator, 1128c2ecf20Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 1138c2ecf20Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 1148c2ecf20Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 1158c2ecf20Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 1168c2ecf20Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 1178c2ecf20Sopenharmony_ci}; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci/* File system interface */ 1208c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations usb_si4713_fops = { 1218c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1228c2ecf20Sopenharmony_ci .open = v4l2_fh_open, 1238c2ecf20Sopenharmony_ci .release = v4l2_fh_release, 1248c2ecf20Sopenharmony_ci .poll = v4l2_ctrl_poll, 1258c2ecf20Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 1268c2ecf20Sopenharmony_ci}; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic void usb_si4713_video_device_release(struct v4l2_device *v4l2_dev) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci struct si4713_usb_device *radio = to_si4713_dev(v4l2_dev); 1318c2ecf20Sopenharmony_ci struct i2c_adapter *adapter = &radio->i2c_adapter; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci i2c_del_adapter(adapter); 1348c2ecf20Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 1358c2ecf20Sopenharmony_ci kfree(radio->buffer); 1368c2ecf20Sopenharmony_ci kfree(radio); 1378c2ecf20Sopenharmony_ci} 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci/* 1408c2ecf20Sopenharmony_ci * This command sequence emulates the behaviour of the Windows driver. 1418c2ecf20Sopenharmony_ci * The structure of these commands was determined by sniffing the 1428c2ecf20Sopenharmony_ci * usb traffic of the device during startup. 1438c2ecf20Sopenharmony_ci * Most likely, these commands make some queries to the device. 1448c2ecf20Sopenharmony_ci * Commands are sent to enquire parameters like the bus mode, 1458c2ecf20Sopenharmony_ci * component revision, boot mode, the device serial number etc. 1468c2ecf20Sopenharmony_ci * 1478c2ecf20Sopenharmony_ci * These commands are necessary to be sent in this order during startup. 1488c2ecf20Sopenharmony_ci * The device fails to powerup if these commands are not sent. 1498c2ecf20Sopenharmony_ci * 1508c2ecf20Sopenharmony_ci * The complete list of startup commands is given in the start_seq table below. 1518c2ecf20Sopenharmony_ci */ 1528c2ecf20Sopenharmony_cistatic int si4713_send_startup_command(struct si4713_usb_device *radio) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci unsigned long until_jiffies = jiffies + usecs_to_jiffies(USB_RESP_TIMEOUT) + 1; 1558c2ecf20Sopenharmony_ci u8 *buffer = radio->buffer; 1568c2ecf20Sopenharmony_ci int retval; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* send the command */ 1598c2ecf20Sopenharmony_ci retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), 1608c2ecf20Sopenharmony_ci 0x09, 0x21, 0x033f, 0, radio->buffer, 1618c2ecf20Sopenharmony_ci BUFFER_LENGTH, USB_TIMEOUT); 1628c2ecf20Sopenharmony_ci if (retval < 0) 1638c2ecf20Sopenharmony_ci return retval; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci for (;;) { 1668c2ecf20Sopenharmony_ci /* receive the response */ 1678c2ecf20Sopenharmony_ci retval = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), 1688c2ecf20Sopenharmony_ci 0x01, 0xa1, 0x033f, 0, radio->buffer, 1698c2ecf20Sopenharmony_ci BUFFER_LENGTH, USB_TIMEOUT); 1708c2ecf20Sopenharmony_ci if (retval < 0) 1718c2ecf20Sopenharmony_ci return retval; 1728c2ecf20Sopenharmony_ci if (!radio->buffer[1]) { 1738c2ecf20Sopenharmony_ci /* USB traffic sniffing showed that some commands require 1748c2ecf20Sopenharmony_ci * additional checks. */ 1758c2ecf20Sopenharmony_ci switch (buffer[1]) { 1768c2ecf20Sopenharmony_ci case 0x32: 1778c2ecf20Sopenharmony_ci if (radio->buffer[2] == 0) 1788c2ecf20Sopenharmony_ci return 0; 1798c2ecf20Sopenharmony_ci break; 1808c2ecf20Sopenharmony_ci case 0x14: 1818c2ecf20Sopenharmony_ci case 0x12: 1828c2ecf20Sopenharmony_ci if (radio->buffer[2] & SI4713_CTS) 1838c2ecf20Sopenharmony_ci return 0; 1848c2ecf20Sopenharmony_ci break; 1858c2ecf20Sopenharmony_ci case 0x06: 1868c2ecf20Sopenharmony_ci if ((radio->buffer[2] & SI4713_CTS) && radio->buffer[9] == 0x08) 1878c2ecf20Sopenharmony_ci return 0; 1888c2ecf20Sopenharmony_ci break; 1898c2ecf20Sopenharmony_ci default: 1908c2ecf20Sopenharmony_ci return 0; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci if (time_is_before_jiffies(until_jiffies)) 1948c2ecf20Sopenharmony_ci return -EIO; 1958c2ecf20Sopenharmony_ci msleep(3); 1968c2ecf20Sopenharmony_ci } 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci return retval; 1998c2ecf20Sopenharmony_ci} 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_cistruct si4713_start_seq_table { 2028c2ecf20Sopenharmony_ci int len; 2038c2ecf20Sopenharmony_ci u8 payload[8]; 2048c2ecf20Sopenharmony_ci}; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci/* 2078c2ecf20Sopenharmony_ci * Some of the startup commands that could be recognized are : 2088c2ecf20Sopenharmony_ci * (0x03): Get serial number of the board (Response : CB000-00-00) 2098c2ecf20Sopenharmony_ci * (0x06, 0x03, 0x03, 0x08, 0x01, 0x0f) : Get Component revision 2108c2ecf20Sopenharmony_ci */ 2118c2ecf20Sopenharmony_cistatic const struct si4713_start_seq_table start_seq[] = { 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci { 1, { 0x03 } }, 2148c2ecf20Sopenharmony_ci { 2, { 0x32, 0x7f } }, 2158c2ecf20Sopenharmony_ci { 6, { 0x06, 0x03, 0x03, 0x08, 0x01, 0x0f } }, 2168c2ecf20Sopenharmony_ci { 2, { 0x14, 0x02 } }, 2178c2ecf20Sopenharmony_ci { 2, { 0x09, 0x90 } }, 2188c2ecf20Sopenharmony_ci { 3, { 0x08, 0x90, 0xfa } }, 2198c2ecf20Sopenharmony_ci { 2, { 0x36, 0x01 } }, 2208c2ecf20Sopenharmony_ci { 2, { 0x05, 0x03 } }, 2218c2ecf20Sopenharmony_ci { 7, { 0x06, 0x00, 0x06, 0x0e, 0x01, 0x0f, 0x05 } }, 2228c2ecf20Sopenharmony_ci { 1, { 0x12 } }, 2238c2ecf20Sopenharmony_ci /* Commands that are sent after pressing the 'Initialize' 2248c2ecf20Sopenharmony_ci button in the windows application */ 2258c2ecf20Sopenharmony_ci { 1, { 0x03 } }, 2268c2ecf20Sopenharmony_ci { 1, { 0x01 } }, 2278c2ecf20Sopenharmony_ci { 2, { 0x09, 0x90 } }, 2288c2ecf20Sopenharmony_ci { 3, { 0x08, 0x90, 0xfa } }, 2298c2ecf20Sopenharmony_ci { 1, { 0x34 } }, 2308c2ecf20Sopenharmony_ci { 2, { 0x35, 0x01 } }, 2318c2ecf20Sopenharmony_ci { 2, { 0x36, 0x01 } }, 2328c2ecf20Sopenharmony_ci { 2, { 0x30, 0x09 } }, 2338c2ecf20Sopenharmony_ci { 4, { 0x30, 0x06, 0x00, 0xe2 } }, 2348c2ecf20Sopenharmony_ci { 3, { 0x31, 0x01, 0x30 } }, 2358c2ecf20Sopenharmony_ci { 3, { 0x31, 0x04, 0x09 } }, 2368c2ecf20Sopenharmony_ci { 2, { 0x05, 0x02 } }, 2378c2ecf20Sopenharmony_ci { 6, { 0x06, 0x03, 0x03, 0x08, 0x01, 0x0f } }, 2388c2ecf20Sopenharmony_ci}; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_cistatic int si4713_start_seq(struct si4713_usb_device *radio) 2418c2ecf20Sopenharmony_ci{ 2428c2ecf20Sopenharmony_ci int retval = 0; 2438c2ecf20Sopenharmony_ci int i; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci radio->buffer[0] = 0x3f; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(start_seq); i++) { 2488c2ecf20Sopenharmony_ci int len = start_seq[i].len; 2498c2ecf20Sopenharmony_ci const u8 *payload = start_seq[i].payload; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci memcpy(radio->buffer + 1, payload, len); 2528c2ecf20Sopenharmony_ci memset(radio->buffer + len + 1, 0, BUFFER_LENGTH - 1 - len); 2538c2ecf20Sopenharmony_ci retval = si4713_send_startup_command(radio); 2548c2ecf20Sopenharmony_ci } 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci return retval; 2578c2ecf20Sopenharmony_ci} 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_cistatic struct i2c_board_info si4713_board_info = { 2608c2ecf20Sopenharmony_ci I2C_BOARD_INFO("si4713", SI4713_I2C_ADDR_BUSEN_HIGH), 2618c2ecf20Sopenharmony_ci}; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_cistruct si4713_command_table { 2648c2ecf20Sopenharmony_ci int command_id; 2658c2ecf20Sopenharmony_ci u8 payload[8]; 2668c2ecf20Sopenharmony_ci}; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci/* 2698c2ecf20Sopenharmony_ci * Structure of a command : 2708c2ecf20Sopenharmony_ci * Byte 1 : 0x3f (always) 2718c2ecf20Sopenharmony_ci * Byte 2 : 0x06 (send a command) 2728c2ecf20Sopenharmony_ci * Byte 3 : Unknown 2738c2ecf20Sopenharmony_ci * Byte 4 : Number of arguments + 1 (for the command byte) 2748c2ecf20Sopenharmony_ci * Byte 5 : Number of response bytes 2758c2ecf20Sopenharmony_ci */ 2768c2ecf20Sopenharmony_cistatic struct si4713_command_table command_table[] = { 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci { SI4713_CMD_POWER_UP, { 0x00, SI4713_PWUP_NARGS + 1, SI4713_PWUP_NRESP} }, 2798c2ecf20Sopenharmony_ci { SI4713_CMD_GET_REV, { 0x03, 0x01, SI4713_GETREV_NRESP } }, 2808c2ecf20Sopenharmony_ci { SI4713_CMD_POWER_DOWN, { 0x00, 0x01, SI4713_PWDN_NRESP} }, 2818c2ecf20Sopenharmony_ci { SI4713_CMD_SET_PROPERTY, { 0x00, SI4713_SET_PROP_NARGS + 1, SI4713_SET_PROP_NRESP } }, 2828c2ecf20Sopenharmony_ci { SI4713_CMD_GET_PROPERTY, { 0x00, SI4713_GET_PROP_NARGS + 1, SI4713_GET_PROP_NRESP } }, 2838c2ecf20Sopenharmony_ci { SI4713_CMD_TX_TUNE_FREQ, { 0x03, SI4713_TXFREQ_NARGS + 1, SI4713_TXFREQ_NRESP } }, 2848c2ecf20Sopenharmony_ci { SI4713_CMD_TX_TUNE_POWER, { 0x03, SI4713_TXPWR_NARGS + 1, SI4713_TXPWR_NRESP } }, 2858c2ecf20Sopenharmony_ci { SI4713_CMD_TX_TUNE_MEASURE, { 0x03, SI4713_TXMEA_NARGS + 1, SI4713_TXMEA_NRESP } }, 2868c2ecf20Sopenharmony_ci { SI4713_CMD_TX_TUNE_STATUS, { 0x00, SI4713_TXSTATUS_NARGS + 1, SI4713_TXSTATUS_NRESP } }, 2878c2ecf20Sopenharmony_ci { SI4713_CMD_TX_ASQ_STATUS, { 0x03, SI4713_ASQSTATUS_NARGS + 1, SI4713_ASQSTATUS_NRESP } }, 2888c2ecf20Sopenharmony_ci { SI4713_CMD_GET_INT_STATUS, { 0x03, 0x01, SI4713_GET_STATUS_NRESP } }, 2898c2ecf20Sopenharmony_ci { SI4713_CMD_TX_RDS_BUFF, { 0x03, SI4713_RDSBUFF_NARGS + 1, SI4713_RDSBUFF_NRESP } }, 2908c2ecf20Sopenharmony_ci { SI4713_CMD_TX_RDS_PS, { 0x00, SI4713_RDSPS_NARGS + 1, SI4713_RDSPS_NRESP } }, 2918c2ecf20Sopenharmony_ci}; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_cistatic int send_command(struct si4713_usb_device *radio, u8 *payload, char *data, int len) 2948c2ecf20Sopenharmony_ci{ 2958c2ecf20Sopenharmony_ci int retval; 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci radio->buffer[0] = 0x3f; 2988c2ecf20Sopenharmony_ci radio->buffer[1] = 0x06; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci memcpy(radio->buffer + 2, payload, 3); 3018c2ecf20Sopenharmony_ci memcpy(radio->buffer + 5, data, len); 3028c2ecf20Sopenharmony_ci memset(radio->buffer + 5 + len, 0, BUFFER_LENGTH - 5 - len); 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci /* send the command */ 3058c2ecf20Sopenharmony_ci retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), 3068c2ecf20Sopenharmony_ci 0x09, 0x21, 0x033f, 0, radio->buffer, 3078c2ecf20Sopenharmony_ci BUFFER_LENGTH, USB_TIMEOUT); 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci return retval < 0 ? retval : 0; 3108c2ecf20Sopenharmony_ci} 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_cistatic int si4713_i2c_read(struct si4713_usb_device *radio, char *data, int len) 3138c2ecf20Sopenharmony_ci{ 3148c2ecf20Sopenharmony_ci unsigned long until_jiffies = jiffies + usecs_to_jiffies(USB_RESP_TIMEOUT) + 1; 3158c2ecf20Sopenharmony_ci int retval; 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci /* receive the response */ 3188c2ecf20Sopenharmony_ci for (;;) { 3198c2ecf20Sopenharmony_ci retval = usb_control_msg(radio->usbdev, 3208c2ecf20Sopenharmony_ci usb_rcvctrlpipe(radio->usbdev, 0), 3218c2ecf20Sopenharmony_ci 0x01, 0xa1, 0x033f, 0, radio->buffer, 3228c2ecf20Sopenharmony_ci BUFFER_LENGTH, USB_TIMEOUT); 3238c2ecf20Sopenharmony_ci if (retval < 0) 3248c2ecf20Sopenharmony_ci return retval; 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci /* 3278c2ecf20Sopenharmony_ci * Check that we get a valid reply back (buffer[1] == 0) and 3288c2ecf20Sopenharmony_ci * that CTS is set before returning, otherwise we wait and try 3298c2ecf20Sopenharmony_ci * again. The i2c driver also does the CTS check, but the timeouts 3308c2ecf20Sopenharmony_ci * used there are much too small for this USB driver, so we wait 3318c2ecf20Sopenharmony_ci * for it here. 3328c2ecf20Sopenharmony_ci */ 3338c2ecf20Sopenharmony_ci if (radio->buffer[1] == 0 && (radio->buffer[2] & SI4713_CTS)) { 3348c2ecf20Sopenharmony_ci memcpy(data, radio->buffer + 2, len); 3358c2ecf20Sopenharmony_ci return 0; 3368c2ecf20Sopenharmony_ci } 3378c2ecf20Sopenharmony_ci if (time_is_before_jiffies(until_jiffies)) { 3388c2ecf20Sopenharmony_ci /* Zero the status value, ensuring CTS isn't set */ 3398c2ecf20Sopenharmony_ci data[0] = 0; 3408c2ecf20Sopenharmony_ci return 0; 3418c2ecf20Sopenharmony_ci } 3428c2ecf20Sopenharmony_ci msleep(3); 3438c2ecf20Sopenharmony_ci } 3448c2ecf20Sopenharmony_ci} 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_cistatic int si4713_i2c_write(struct si4713_usb_device *radio, char *data, int len) 3478c2ecf20Sopenharmony_ci{ 3488c2ecf20Sopenharmony_ci int retval = -EINVAL; 3498c2ecf20Sopenharmony_ci int i; 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci if (len > BUFFER_LENGTH - 5) 3528c2ecf20Sopenharmony_ci return -EINVAL; 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(command_table); i++) { 3558c2ecf20Sopenharmony_ci if (data[0] == command_table[i].command_id) 3568c2ecf20Sopenharmony_ci retval = send_command(radio, command_table[i].payload, 3578c2ecf20Sopenharmony_ci data, len); 3588c2ecf20Sopenharmony_ci } 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci return retval < 0 ? retval : 0; 3618c2ecf20Sopenharmony_ci} 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_cistatic int si4713_transfer(struct i2c_adapter *i2c_adapter, 3648c2ecf20Sopenharmony_ci struct i2c_msg *msgs, int num) 3658c2ecf20Sopenharmony_ci{ 3668c2ecf20Sopenharmony_ci struct si4713_usb_device *radio = i2c_get_adapdata(i2c_adapter); 3678c2ecf20Sopenharmony_ci int retval = -EINVAL; 3688c2ecf20Sopenharmony_ci int i; 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci for (i = 0; i < num; i++) { 3718c2ecf20Sopenharmony_ci if (msgs[i].flags & I2C_M_RD) 3728c2ecf20Sopenharmony_ci retval = si4713_i2c_read(radio, msgs[i].buf, msgs[i].len); 3738c2ecf20Sopenharmony_ci else 3748c2ecf20Sopenharmony_ci retval = si4713_i2c_write(radio, msgs[i].buf, msgs[i].len); 3758c2ecf20Sopenharmony_ci if (retval) 3768c2ecf20Sopenharmony_ci break; 3778c2ecf20Sopenharmony_ci } 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_ci return retval ? retval : num; 3808c2ecf20Sopenharmony_ci} 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_cistatic u32 si4713_functionality(struct i2c_adapter *adapter) 3838c2ecf20Sopenharmony_ci{ 3848c2ecf20Sopenharmony_ci return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; 3858c2ecf20Sopenharmony_ci} 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_cistatic const struct i2c_algorithm si4713_algo = { 3888c2ecf20Sopenharmony_ci .master_xfer = si4713_transfer, 3898c2ecf20Sopenharmony_ci .functionality = si4713_functionality, 3908c2ecf20Sopenharmony_ci}; 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci/* This name value shows up in the sysfs filename associated 3938c2ecf20Sopenharmony_ci with this I2C adapter */ 3948c2ecf20Sopenharmony_cistatic const struct i2c_adapter si4713_i2c_adapter_template = { 3958c2ecf20Sopenharmony_ci .name = "si4713-i2c", 3968c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 3978c2ecf20Sopenharmony_ci .algo = &si4713_algo, 3988c2ecf20Sopenharmony_ci}; 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_cistatic int si4713_register_i2c_adapter(struct si4713_usb_device *radio) 4018c2ecf20Sopenharmony_ci{ 4028c2ecf20Sopenharmony_ci radio->i2c_adapter = si4713_i2c_adapter_template; 4038c2ecf20Sopenharmony_ci /* set up sysfs linkage to our parent device */ 4048c2ecf20Sopenharmony_ci radio->i2c_adapter.dev.parent = &radio->usbdev->dev; 4058c2ecf20Sopenharmony_ci i2c_set_adapdata(&radio->i2c_adapter, radio); 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci return i2c_add_adapter(&radio->i2c_adapter); 4088c2ecf20Sopenharmony_ci} 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_ci/* check if the device is present and register with v4l and usb if it is */ 4118c2ecf20Sopenharmony_cistatic int usb_si4713_probe(struct usb_interface *intf, 4128c2ecf20Sopenharmony_ci const struct usb_device_id *id) 4138c2ecf20Sopenharmony_ci{ 4148c2ecf20Sopenharmony_ci struct si4713_usb_device *radio; 4158c2ecf20Sopenharmony_ci struct i2c_adapter *adapter; 4168c2ecf20Sopenharmony_ci struct v4l2_subdev *sd; 4178c2ecf20Sopenharmony_ci int retval; 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_ci dev_info(&intf->dev, "Si4713 development board discovered: (%04X:%04X)\n", 4208c2ecf20Sopenharmony_ci id->idVendor, id->idProduct); 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci /* Initialize local device structure */ 4238c2ecf20Sopenharmony_ci radio = kzalloc(sizeof(struct si4713_usb_device), GFP_KERNEL); 4248c2ecf20Sopenharmony_ci if (radio) 4258c2ecf20Sopenharmony_ci radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci if (!radio || !radio->buffer) { 4288c2ecf20Sopenharmony_ci dev_err(&intf->dev, "kmalloc for si4713_usb_device failed\n"); 4298c2ecf20Sopenharmony_ci kfree(radio); 4308c2ecf20Sopenharmony_ci return -ENOMEM; 4318c2ecf20Sopenharmony_ci } 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci mutex_init(&radio->lock); 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci radio->usbdev = interface_to_usbdev(intf); 4368c2ecf20Sopenharmony_ci radio->intf = intf; 4378c2ecf20Sopenharmony_ci usb_set_intfdata(intf, &radio->v4l2_dev); 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci retval = si4713_start_seq(radio); 4408c2ecf20Sopenharmony_ci if (retval < 0) 4418c2ecf20Sopenharmony_ci goto err_v4l2; 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); 4448c2ecf20Sopenharmony_ci if (retval < 0) { 4458c2ecf20Sopenharmony_ci dev_err(&intf->dev, "couldn't register v4l2_device\n"); 4468c2ecf20Sopenharmony_ci goto err_v4l2; 4478c2ecf20Sopenharmony_ci } 4488c2ecf20Sopenharmony_ci 4498c2ecf20Sopenharmony_ci retval = si4713_register_i2c_adapter(radio); 4508c2ecf20Sopenharmony_ci if (retval < 0) { 4518c2ecf20Sopenharmony_ci dev_err(&intf->dev, "could not register i2c device\n"); 4528c2ecf20Sopenharmony_ci goto err_i2cdev; 4538c2ecf20Sopenharmony_ci } 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci adapter = &radio->i2c_adapter; 4568c2ecf20Sopenharmony_ci sd = v4l2_i2c_new_subdev_board(&radio->v4l2_dev, adapter, 4578c2ecf20Sopenharmony_ci &si4713_board_info, NULL); 4588c2ecf20Sopenharmony_ci radio->v4l2_subdev = sd; 4598c2ecf20Sopenharmony_ci if (!sd) { 4608c2ecf20Sopenharmony_ci dev_err(&intf->dev, "cannot get v4l2 subdevice\n"); 4618c2ecf20Sopenharmony_ci retval = -ENODEV; 4628c2ecf20Sopenharmony_ci goto del_adapter; 4638c2ecf20Sopenharmony_ci } 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci radio->vdev.ctrl_handler = sd->ctrl_handler; 4668c2ecf20Sopenharmony_ci radio->v4l2_dev.release = usb_si4713_video_device_release; 4678c2ecf20Sopenharmony_ci strscpy(radio->vdev.name, radio->v4l2_dev.name, 4688c2ecf20Sopenharmony_ci sizeof(radio->vdev.name)); 4698c2ecf20Sopenharmony_ci radio->vdev.v4l2_dev = &radio->v4l2_dev; 4708c2ecf20Sopenharmony_ci radio->vdev.fops = &usb_si4713_fops; 4718c2ecf20Sopenharmony_ci radio->vdev.ioctl_ops = &usb_si4713_ioctl_ops; 4728c2ecf20Sopenharmony_ci radio->vdev.lock = &radio->lock; 4738c2ecf20Sopenharmony_ci radio->vdev.release = video_device_release_empty; 4748c2ecf20Sopenharmony_ci radio->vdev.vfl_dir = VFL_DIR_TX; 4758c2ecf20Sopenharmony_ci radio->vdev.device_caps = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT; 4768c2ecf20Sopenharmony_ci 4778c2ecf20Sopenharmony_ci video_set_drvdata(&radio->vdev, radio); 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); 4808c2ecf20Sopenharmony_ci if (retval < 0) { 4818c2ecf20Sopenharmony_ci dev_err(&intf->dev, "could not register video device\n"); 4828c2ecf20Sopenharmony_ci goto del_adapter; 4838c2ecf20Sopenharmony_ci } 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci dev_info(&intf->dev, "V4L2 device registered as %s\n", 4868c2ecf20Sopenharmony_ci video_device_node_name(&radio->vdev)); 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci return 0; 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_cidel_adapter: 4918c2ecf20Sopenharmony_ci i2c_del_adapter(adapter); 4928c2ecf20Sopenharmony_cierr_i2cdev: 4938c2ecf20Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 4948c2ecf20Sopenharmony_cierr_v4l2: 4958c2ecf20Sopenharmony_ci kfree(radio->buffer); 4968c2ecf20Sopenharmony_ci kfree(radio); 4978c2ecf20Sopenharmony_ci return retval; 4988c2ecf20Sopenharmony_ci} 4998c2ecf20Sopenharmony_ci 5008c2ecf20Sopenharmony_cistatic void usb_si4713_disconnect(struct usb_interface *intf) 5018c2ecf20Sopenharmony_ci{ 5028c2ecf20Sopenharmony_ci struct si4713_usb_device *radio = to_si4713_dev(usb_get_intfdata(intf)); 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci dev_info(&intf->dev, "Si4713 development board now disconnected\n"); 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci mutex_lock(&radio->lock); 5078c2ecf20Sopenharmony_ci usb_set_intfdata(intf, NULL); 5088c2ecf20Sopenharmony_ci video_unregister_device(&radio->vdev); 5098c2ecf20Sopenharmony_ci v4l2_device_disconnect(&radio->v4l2_dev); 5108c2ecf20Sopenharmony_ci mutex_unlock(&radio->lock); 5118c2ecf20Sopenharmony_ci v4l2_device_put(&radio->v4l2_dev); 5128c2ecf20Sopenharmony_ci} 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci/* USB subsystem interface */ 5158c2ecf20Sopenharmony_cistatic struct usb_driver usb_si4713_driver = { 5168c2ecf20Sopenharmony_ci .name = "radio-usb-si4713", 5178c2ecf20Sopenharmony_ci .probe = usb_si4713_probe, 5188c2ecf20Sopenharmony_ci .disconnect = usb_si4713_disconnect, 5198c2ecf20Sopenharmony_ci .id_table = usb_si4713_usb_device_table, 5208c2ecf20Sopenharmony_ci}; 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_cimodule_usb_driver(usb_si4713_driver); 523