162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. 462306a36Sopenharmony_ci * All rights reserved. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci/* kernel includes */ 862306a36Sopenharmony_ci#include <linux/kernel.h> 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/usb.h> 1162306a36Sopenharmony_ci#include <linux/init.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include <linux/input.h> 1462306a36Sopenharmony_ci#include <linux/mutex.h> 1562306a36Sopenharmony_ci#include <linux/i2c.h> 1662306a36Sopenharmony_ci/* V4l includes */ 1762306a36Sopenharmony_ci#include <linux/videodev2.h> 1862306a36Sopenharmony_ci#include <media/v4l2-common.h> 1962306a36Sopenharmony_ci#include <media/v4l2-device.h> 2062306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 2162306a36Sopenharmony_ci#include <media/v4l2-event.h> 2262306a36Sopenharmony_ci#include <linux/platform_data/media/si4713.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include "si4713.h" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* driver and module definitions */ 2762306a36Sopenharmony_ciMODULE_AUTHOR("Dinesh Ram <dinesh.ram@cern.ch>"); 2862306a36Sopenharmony_ciMODULE_DESCRIPTION("Si4713 FM Transmitter USB driver"); 2962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/* The Device announces itself as Cygnal Integrated Products, Inc. */ 3262306a36Sopenharmony_ci#define USB_SI4713_VENDOR 0x10c4 3362306a36Sopenharmony_ci#define USB_SI4713_PRODUCT 0x8244 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define BUFFER_LENGTH 64 3662306a36Sopenharmony_ci#define USB_TIMEOUT 1000 3762306a36Sopenharmony_ci#define USB_RESP_TIMEOUT 50000 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci/* USB Device ID List */ 4062306a36Sopenharmony_cistatic const struct usb_device_id usb_si4713_usb_device_table[] = { 4162306a36Sopenharmony_ci {USB_DEVICE_AND_INTERFACE_INFO(USB_SI4713_VENDOR, USB_SI4713_PRODUCT, 4262306a36Sopenharmony_ci USB_CLASS_HID, 0, 0) }, 4362306a36Sopenharmony_ci { } /* Terminating entry */ 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, usb_si4713_usb_device_table); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistruct si4713_usb_device { 4962306a36Sopenharmony_ci struct usb_device *usbdev; 5062306a36Sopenharmony_ci struct usb_interface *intf; 5162306a36Sopenharmony_ci struct video_device vdev; 5262306a36Sopenharmony_ci struct v4l2_device v4l2_dev; 5362306a36Sopenharmony_ci struct v4l2_subdev *v4l2_subdev; 5462306a36Sopenharmony_ci struct mutex lock; 5562306a36Sopenharmony_ci struct i2c_adapter i2c_adapter; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci u8 *buffer; 5862306a36Sopenharmony_ci}; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic inline struct si4713_usb_device *to_si4713_dev(struct v4l2_device *v4l2_dev) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci return container_of(v4l2_dev, struct si4713_usb_device, v4l2_dev); 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 6662306a36Sopenharmony_ci struct v4l2_capability *v) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci struct si4713_usb_device *radio = video_drvdata(file); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci strscpy(v->driver, "radio-usb-si4713", sizeof(v->driver)); 7162306a36Sopenharmony_ci strscpy(v->card, "Si4713 FM Transmitter", sizeof(v->card)); 7262306a36Sopenharmony_ci usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); 7362306a36Sopenharmony_ci return 0; 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic int vidioc_g_modulator(struct file *file, void *priv, 7762306a36Sopenharmony_ci struct v4l2_modulator *vm) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci struct si4713_usb_device *radio = video_drvdata(file); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci return v4l2_subdev_call(radio->v4l2_subdev, tuner, g_modulator, vm); 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic int vidioc_s_modulator(struct file *file, void *priv, 8562306a36Sopenharmony_ci const struct v4l2_modulator *vm) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci struct si4713_usb_device *radio = video_drvdata(file); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci return v4l2_subdev_call(radio->v4l2_subdev, tuner, s_modulator, vm); 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 9362306a36Sopenharmony_ci const struct v4l2_frequency *vf) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci struct si4713_usb_device *radio = video_drvdata(file); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci return v4l2_subdev_call(radio->v4l2_subdev, tuner, s_frequency, vf); 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 10162306a36Sopenharmony_ci struct v4l2_frequency *vf) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci struct si4713_usb_device *radio = video_drvdata(file); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci return v4l2_subdev_call(radio->v4l2_subdev, tuner, g_frequency, vf); 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops usb_si4713_ioctl_ops = { 10962306a36Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 11062306a36Sopenharmony_ci .vidioc_g_modulator = vidioc_g_modulator, 11162306a36Sopenharmony_ci .vidioc_s_modulator = vidioc_s_modulator, 11262306a36Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 11362306a36Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 11462306a36Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 11562306a36Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 11662306a36Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 11762306a36Sopenharmony_ci}; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci/* File system interface */ 12062306a36Sopenharmony_cistatic const struct v4l2_file_operations usb_si4713_fops = { 12162306a36Sopenharmony_ci .owner = THIS_MODULE, 12262306a36Sopenharmony_ci .open = v4l2_fh_open, 12362306a36Sopenharmony_ci .release = v4l2_fh_release, 12462306a36Sopenharmony_ci .poll = v4l2_ctrl_poll, 12562306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 12662306a36Sopenharmony_ci}; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic void usb_si4713_video_device_release(struct v4l2_device *v4l2_dev) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci struct si4713_usb_device *radio = to_si4713_dev(v4l2_dev); 13162306a36Sopenharmony_ci struct i2c_adapter *adapter = &radio->i2c_adapter; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci i2c_del_adapter(adapter); 13462306a36Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 13562306a36Sopenharmony_ci kfree(radio->buffer); 13662306a36Sopenharmony_ci kfree(radio); 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci/* 14062306a36Sopenharmony_ci * This command sequence emulates the behaviour of the Windows driver. 14162306a36Sopenharmony_ci * The structure of these commands was determined by sniffing the 14262306a36Sopenharmony_ci * usb traffic of the device during startup. 14362306a36Sopenharmony_ci * Most likely, these commands make some queries to the device. 14462306a36Sopenharmony_ci * Commands are sent to enquire parameters like the bus mode, 14562306a36Sopenharmony_ci * component revision, boot mode, the device serial number etc. 14662306a36Sopenharmony_ci * 14762306a36Sopenharmony_ci * These commands are necessary to be sent in this order during startup. 14862306a36Sopenharmony_ci * The device fails to powerup if these commands are not sent. 14962306a36Sopenharmony_ci * 15062306a36Sopenharmony_ci * The complete list of startup commands is given in the start_seq table below. 15162306a36Sopenharmony_ci */ 15262306a36Sopenharmony_cistatic int si4713_send_startup_command(struct si4713_usb_device *radio) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci unsigned long until_jiffies = jiffies + usecs_to_jiffies(USB_RESP_TIMEOUT) + 1; 15562306a36Sopenharmony_ci u8 *buffer = radio->buffer; 15662306a36Sopenharmony_ci int retval; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci /* send the command */ 15962306a36Sopenharmony_ci retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), 16062306a36Sopenharmony_ci 0x09, 0x21, 0x033f, 0, radio->buffer, 16162306a36Sopenharmony_ci BUFFER_LENGTH, USB_TIMEOUT); 16262306a36Sopenharmony_ci if (retval < 0) 16362306a36Sopenharmony_ci return retval; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci for (;;) { 16662306a36Sopenharmony_ci /* receive the response */ 16762306a36Sopenharmony_ci retval = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), 16862306a36Sopenharmony_ci 0x01, 0xa1, 0x033f, 0, radio->buffer, 16962306a36Sopenharmony_ci BUFFER_LENGTH, USB_TIMEOUT); 17062306a36Sopenharmony_ci if (retval < 0) 17162306a36Sopenharmony_ci return retval; 17262306a36Sopenharmony_ci if (!radio->buffer[1]) { 17362306a36Sopenharmony_ci /* USB traffic sniffing showed that some commands require 17462306a36Sopenharmony_ci * additional checks. */ 17562306a36Sopenharmony_ci switch (buffer[1]) { 17662306a36Sopenharmony_ci case 0x32: 17762306a36Sopenharmony_ci if (radio->buffer[2] == 0) 17862306a36Sopenharmony_ci return 0; 17962306a36Sopenharmony_ci break; 18062306a36Sopenharmony_ci case 0x14: 18162306a36Sopenharmony_ci case 0x12: 18262306a36Sopenharmony_ci if (radio->buffer[2] & SI4713_CTS) 18362306a36Sopenharmony_ci return 0; 18462306a36Sopenharmony_ci break; 18562306a36Sopenharmony_ci case 0x06: 18662306a36Sopenharmony_ci if ((radio->buffer[2] & SI4713_CTS) && radio->buffer[9] == 0x08) 18762306a36Sopenharmony_ci return 0; 18862306a36Sopenharmony_ci break; 18962306a36Sopenharmony_ci default: 19062306a36Sopenharmony_ci return 0; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci } 19362306a36Sopenharmony_ci if (time_is_before_jiffies(until_jiffies)) 19462306a36Sopenharmony_ci return -EIO; 19562306a36Sopenharmony_ci msleep(3); 19662306a36Sopenharmony_ci } 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci return retval; 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistruct si4713_start_seq_table { 20262306a36Sopenharmony_ci int len; 20362306a36Sopenharmony_ci u8 payload[8]; 20462306a36Sopenharmony_ci}; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci/* 20762306a36Sopenharmony_ci * Some of the startup commands that could be recognized are : 20862306a36Sopenharmony_ci * (0x03): Get serial number of the board (Response : CB000-00-00) 20962306a36Sopenharmony_ci * (0x06, 0x03, 0x03, 0x08, 0x01, 0x0f) : Get Component revision 21062306a36Sopenharmony_ci */ 21162306a36Sopenharmony_cistatic const struct si4713_start_seq_table start_seq[] = { 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci { 1, { 0x03 } }, 21462306a36Sopenharmony_ci { 2, { 0x32, 0x7f } }, 21562306a36Sopenharmony_ci { 6, { 0x06, 0x03, 0x03, 0x08, 0x01, 0x0f } }, 21662306a36Sopenharmony_ci { 2, { 0x14, 0x02 } }, 21762306a36Sopenharmony_ci { 2, { 0x09, 0x90 } }, 21862306a36Sopenharmony_ci { 3, { 0x08, 0x90, 0xfa } }, 21962306a36Sopenharmony_ci { 2, { 0x36, 0x01 } }, 22062306a36Sopenharmony_ci { 2, { 0x05, 0x03 } }, 22162306a36Sopenharmony_ci { 7, { 0x06, 0x00, 0x06, 0x0e, 0x01, 0x0f, 0x05 } }, 22262306a36Sopenharmony_ci { 1, { 0x12 } }, 22362306a36Sopenharmony_ci /* Commands that are sent after pressing the 'Initialize' 22462306a36Sopenharmony_ci button in the windows application */ 22562306a36Sopenharmony_ci { 1, { 0x03 } }, 22662306a36Sopenharmony_ci { 1, { 0x01 } }, 22762306a36Sopenharmony_ci { 2, { 0x09, 0x90 } }, 22862306a36Sopenharmony_ci { 3, { 0x08, 0x90, 0xfa } }, 22962306a36Sopenharmony_ci { 1, { 0x34 } }, 23062306a36Sopenharmony_ci { 2, { 0x35, 0x01 } }, 23162306a36Sopenharmony_ci { 2, { 0x36, 0x01 } }, 23262306a36Sopenharmony_ci { 2, { 0x30, 0x09 } }, 23362306a36Sopenharmony_ci { 4, { 0x30, 0x06, 0x00, 0xe2 } }, 23462306a36Sopenharmony_ci { 3, { 0x31, 0x01, 0x30 } }, 23562306a36Sopenharmony_ci { 3, { 0x31, 0x04, 0x09 } }, 23662306a36Sopenharmony_ci { 2, { 0x05, 0x02 } }, 23762306a36Sopenharmony_ci { 6, { 0x06, 0x03, 0x03, 0x08, 0x01, 0x0f } }, 23862306a36Sopenharmony_ci}; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cistatic int si4713_start_seq(struct si4713_usb_device *radio) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci int retval = 0; 24362306a36Sopenharmony_ci int i; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci radio->buffer[0] = 0x3f; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(start_seq); i++) { 24862306a36Sopenharmony_ci int len = start_seq[i].len; 24962306a36Sopenharmony_ci const u8 *payload = start_seq[i].payload; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci memcpy(radio->buffer + 1, payload, len); 25262306a36Sopenharmony_ci memset(radio->buffer + len + 1, 0, BUFFER_LENGTH - 1 - len); 25362306a36Sopenharmony_ci retval = si4713_send_startup_command(radio); 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci return retval; 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cistatic struct i2c_board_info si4713_board_info = { 26062306a36Sopenharmony_ci I2C_BOARD_INFO("si4713", SI4713_I2C_ADDR_BUSEN_HIGH), 26162306a36Sopenharmony_ci}; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_cistruct si4713_command_table { 26462306a36Sopenharmony_ci int command_id; 26562306a36Sopenharmony_ci u8 payload[8]; 26662306a36Sopenharmony_ci}; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci/* 26962306a36Sopenharmony_ci * Structure of a command : 27062306a36Sopenharmony_ci * Byte 1 : 0x3f (always) 27162306a36Sopenharmony_ci * Byte 2 : 0x06 (send a command) 27262306a36Sopenharmony_ci * Byte 3 : Unknown 27362306a36Sopenharmony_ci * Byte 4 : Number of arguments + 1 (for the command byte) 27462306a36Sopenharmony_ci * Byte 5 : Number of response bytes 27562306a36Sopenharmony_ci */ 27662306a36Sopenharmony_cistatic struct si4713_command_table command_table[] = { 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci { SI4713_CMD_POWER_UP, { 0x00, SI4713_PWUP_NARGS + 1, SI4713_PWUP_NRESP} }, 27962306a36Sopenharmony_ci { SI4713_CMD_GET_REV, { 0x03, 0x01, SI4713_GETREV_NRESP } }, 28062306a36Sopenharmony_ci { SI4713_CMD_POWER_DOWN, { 0x00, 0x01, SI4713_PWDN_NRESP} }, 28162306a36Sopenharmony_ci { SI4713_CMD_SET_PROPERTY, { 0x00, SI4713_SET_PROP_NARGS + 1, SI4713_SET_PROP_NRESP } }, 28262306a36Sopenharmony_ci { SI4713_CMD_GET_PROPERTY, { 0x00, SI4713_GET_PROP_NARGS + 1, SI4713_GET_PROP_NRESP } }, 28362306a36Sopenharmony_ci { SI4713_CMD_TX_TUNE_FREQ, { 0x03, SI4713_TXFREQ_NARGS + 1, SI4713_TXFREQ_NRESP } }, 28462306a36Sopenharmony_ci { SI4713_CMD_TX_TUNE_POWER, { 0x03, SI4713_TXPWR_NARGS + 1, SI4713_TXPWR_NRESP } }, 28562306a36Sopenharmony_ci { SI4713_CMD_TX_TUNE_MEASURE, { 0x03, SI4713_TXMEA_NARGS + 1, SI4713_TXMEA_NRESP } }, 28662306a36Sopenharmony_ci { SI4713_CMD_TX_TUNE_STATUS, { 0x00, SI4713_TXSTATUS_NARGS + 1, SI4713_TXSTATUS_NRESP } }, 28762306a36Sopenharmony_ci { SI4713_CMD_TX_ASQ_STATUS, { 0x03, SI4713_ASQSTATUS_NARGS + 1, SI4713_ASQSTATUS_NRESP } }, 28862306a36Sopenharmony_ci { SI4713_CMD_GET_INT_STATUS, { 0x03, 0x01, SI4713_GET_STATUS_NRESP } }, 28962306a36Sopenharmony_ci { SI4713_CMD_TX_RDS_BUFF, { 0x03, SI4713_RDSBUFF_NARGS + 1, SI4713_RDSBUFF_NRESP } }, 29062306a36Sopenharmony_ci { SI4713_CMD_TX_RDS_PS, { 0x00, SI4713_RDSPS_NARGS + 1, SI4713_RDSPS_NRESP } }, 29162306a36Sopenharmony_ci}; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_cistatic int send_command(struct si4713_usb_device *radio, u8 *payload, char *data, int len) 29462306a36Sopenharmony_ci{ 29562306a36Sopenharmony_ci int retval; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci radio->buffer[0] = 0x3f; 29862306a36Sopenharmony_ci radio->buffer[1] = 0x06; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci memcpy(radio->buffer + 2, payload, 3); 30162306a36Sopenharmony_ci memcpy(radio->buffer + 5, data, len); 30262306a36Sopenharmony_ci memset(radio->buffer + 5 + len, 0, BUFFER_LENGTH - 5 - len); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci /* send the command */ 30562306a36Sopenharmony_ci retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), 30662306a36Sopenharmony_ci 0x09, 0x21, 0x033f, 0, radio->buffer, 30762306a36Sopenharmony_ci BUFFER_LENGTH, USB_TIMEOUT); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci return retval < 0 ? retval : 0; 31062306a36Sopenharmony_ci} 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_cistatic int si4713_i2c_read(struct si4713_usb_device *radio, char *data, int len) 31362306a36Sopenharmony_ci{ 31462306a36Sopenharmony_ci unsigned long until_jiffies = jiffies + usecs_to_jiffies(USB_RESP_TIMEOUT) + 1; 31562306a36Sopenharmony_ci int retval; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci /* receive the response */ 31862306a36Sopenharmony_ci for (;;) { 31962306a36Sopenharmony_ci retval = usb_control_msg(radio->usbdev, 32062306a36Sopenharmony_ci usb_rcvctrlpipe(radio->usbdev, 0), 32162306a36Sopenharmony_ci 0x01, 0xa1, 0x033f, 0, radio->buffer, 32262306a36Sopenharmony_ci BUFFER_LENGTH, USB_TIMEOUT); 32362306a36Sopenharmony_ci if (retval < 0) 32462306a36Sopenharmony_ci return retval; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci /* 32762306a36Sopenharmony_ci * Check that we get a valid reply back (buffer[1] == 0) and 32862306a36Sopenharmony_ci * that CTS is set before returning, otherwise we wait and try 32962306a36Sopenharmony_ci * again. The i2c driver also does the CTS check, but the timeouts 33062306a36Sopenharmony_ci * used there are much too small for this USB driver, so we wait 33162306a36Sopenharmony_ci * for it here. 33262306a36Sopenharmony_ci */ 33362306a36Sopenharmony_ci if (radio->buffer[1] == 0 && (radio->buffer[2] & SI4713_CTS)) { 33462306a36Sopenharmony_ci memcpy(data, radio->buffer + 2, len); 33562306a36Sopenharmony_ci return 0; 33662306a36Sopenharmony_ci } 33762306a36Sopenharmony_ci if (time_is_before_jiffies(until_jiffies)) { 33862306a36Sopenharmony_ci /* Zero the status value, ensuring CTS isn't set */ 33962306a36Sopenharmony_ci data[0] = 0; 34062306a36Sopenharmony_ci return 0; 34162306a36Sopenharmony_ci } 34262306a36Sopenharmony_ci msleep(3); 34362306a36Sopenharmony_ci } 34462306a36Sopenharmony_ci} 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_cistatic int si4713_i2c_write(struct si4713_usb_device *radio, char *data, int len) 34762306a36Sopenharmony_ci{ 34862306a36Sopenharmony_ci int retval = -EINVAL; 34962306a36Sopenharmony_ci int i; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci if (len > BUFFER_LENGTH - 5) 35262306a36Sopenharmony_ci return -EINVAL; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(command_table); i++) { 35562306a36Sopenharmony_ci if (data[0] == command_table[i].command_id) 35662306a36Sopenharmony_ci retval = send_command(radio, command_table[i].payload, 35762306a36Sopenharmony_ci data, len); 35862306a36Sopenharmony_ci } 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci return retval < 0 ? retval : 0; 36162306a36Sopenharmony_ci} 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_cistatic int si4713_transfer(struct i2c_adapter *i2c_adapter, 36462306a36Sopenharmony_ci struct i2c_msg *msgs, int num) 36562306a36Sopenharmony_ci{ 36662306a36Sopenharmony_ci struct si4713_usb_device *radio = i2c_get_adapdata(i2c_adapter); 36762306a36Sopenharmony_ci int retval = -EINVAL; 36862306a36Sopenharmony_ci int i; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci for (i = 0; i < num; i++) { 37162306a36Sopenharmony_ci if (msgs[i].flags & I2C_M_RD) 37262306a36Sopenharmony_ci retval = si4713_i2c_read(radio, msgs[i].buf, msgs[i].len); 37362306a36Sopenharmony_ci else 37462306a36Sopenharmony_ci retval = si4713_i2c_write(radio, msgs[i].buf, msgs[i].len); 37562306a36Sopenharmony_ci if (retval) 37662306a36Sopenharmony_ci break; 37762306a36Sopenharmony_ci } 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci return retval ? retval : num; 38062306a36Sopenharmony_ci} 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_cistatic u32 si4713_functionality(struct i2c_adapter *adapter) 38362306a36Sopenharmony_ci{ 38462306a36Sopenharmony_ci return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; 38562306a36Sopenharmony_ci} 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_cistatic const struct i2c_algorithm si4713_algo = { 38862306a36Sopenharmony_ci .master_xfer = si4713_transfer, 38962306a36Sopenharmony_ci .functionality = si4713_functionality, 39062306a36Sopenharmony_ci}; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci/* This name value shows up in the sysfs filename associated 39362306a36Sopenharmony_ci with this I2C adapter */ 39462306a36Sopenharmony_cistatic const struct i2c_adapter si4713_i2c_adapter_template = { 39562306a36Sopenharmony_ci .name = "si4713-i2c", 39662306a36Sopenharmony_ci .owner = THIS_MODULE, 39762306a36Sopenharmony_ci .algo = &si4713_algo, 39862306a36Sopenharmony_ci}; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_cistatic int si4713_register_i2c_adapter(struct si4713_usb_device *radio) 40162306a36Sopenharmony_ci{ 40262306a36Sopenharmony_ci radio->i2c_adapter = si4713_i2c_adapter_template; 40362306a36Sopenharmony_ci /* set up sysfs linkage to our parent device */ 40462306a36Sopenharmony_ci radio->i2c_adapter.dev.parent = &radio->usbdev->dev; 40562306a36Sopenharmony_ci i2c_set_adapdata(&radio->i2c_adapter, radio); 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci return i2c_add_adapter(&radio->i2c_adapter); 40862306a36Sopenharmony_ci} 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci/* check if the device is present and register with v4l and usb if it is */ 41162306a36Sopenharmony_cistatic int usb_si4713_probe(struct usb_interface *intf, 41262306a36Sopenharmony_ci const struct usb_device_id *id) 41362306a36Sopenharmony_ci{ 41462306a36Sopenharmony_ci struct si4713_usb_device *radio; 41562306a36Sopenharmony_ci struct i2c_adapter *adapter; 41662306a36Sopenharmony_ci struct v4l2_subdev *sd; 41762306a36Sopenharmony_ci int retval; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci dev_info(&intf->dev, "Si4713 development board discovered: (%04X:%04X)\n", 42062306a36Sopenharmony_ci id->idVendor, id->idProduct); 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci /* Initialize local device structure */ 42362306a36Sopenharmony_ci radio = kzalloc(sizeof(struct si4713_usb_device), GFP_KERNEL); 42462306a36Sopenharmony_ci if (radio) 42562306a36Sopenharmony_ci radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci if (!radio || !radio->buffer) { 42862306a36Sopenharmony_ci dev_err(&intf->dev, "kmalloc for si4713_usb_device failed\n"); 42962306a36Sopenharmony_ci kfree(radio); 43062306a36Sopenharmony_ci return -ENOMEM; 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci mutex_init(&radio->lock); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci radio->usbdev = interface_to_usbdev(intf); 43662306a36Sopenharmony_ci radio->intf = intf; 43762306a36Sopenharmony_ci usb_set_intfdata(intf, &radio->v4l2_dev); 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci retval = si4713_start_seq(radio); 44062306a36Sopenharmony_ci if (retval < 0) 44162306a36Sopenharmony_ci goto err_v4l2; 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); 44462306a36Sopenharmony_ci if (retval < 0) { 44562306a36Sopenharmony_ci dev_err(&intf->dev, "couldn't register v4l2_device\n"); 44662306a36Sopenharmony_ci goto err_v4l2; 44762306a36Sopenharmony_ci } 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci retval = si4713_register_i2c_adapter(radio); 45062306a36Sopenharmony_ci if (retval < 0) { 45162306a36Sopenharmony_ci dev_err(&intf->dev, "could not register i2c device\n"); 45262306a36Sopenharmony_ci goto err_i2cdev; 45362306a36Sopenharmony_ci } 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci adapter = &radio->i2c_adapter; 45662306a36Sopenharmony_ci sd = v4l2_i2c_new_subdev_board(&radio->v4l2_dev, adapter, 45762306a36Sopenharmony_ci &si4713_board_info, NULL); 45862306a36Sopenharmony_ci radio->v4l2_subdev = sd; 45962306a36Sopenharmony_ci if (!sd) { 46062306a36Sopenharmony_ci dev_err(&intf->dev, "cannot get v4l2 subdevice\n"); 46162306a36Sopenharmony_ci retval = -ENODEV; 46262306a36Sopenharmony_ci goto del_adapter; 46362306a36Sopenharmony_ci } 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci radio->vdev.ctrl_handler = sd->ctrl_handler; 46662306a36Sopenharmony_ci radio->v4l2_dev.release = usb_si4713_video_device_release; 46762306a36Sopenharmony_ci strscpy(radio->vdev.name, radio->v4l2_dev.name, 46862306a36Sopenharmony_ci sizeof(radio->vdev.name)); 46962306a36Sopenharmony_ci radio->vdev.v4l2_dev = &radio->v4l2_dev; 47062306a36Sopenharmony_ci radio->vdev.fops = &usb_si4713_fops; 47162306a36Sopenharmony_ci radio->vdev.ioctl_ops = &usb_si4713_ioctl_ops; 47262306a36Sopenharmony_ci radio->vdev.lock = &radio->lock; 47362306a36Sopenharmony_ci radio->vdev.release = video_device_release_empty; 47462306a36Sopenharmony_ci radio->vdev.vfl_dir = VFL_DIR_TX; 47562306a36Sopenharmony_ci radio->vdev.device_caps = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci video_set_drvdata(&radio->vdev, radio); 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_ci retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); 48062306a36Sopenharmony_ci if (retval < 0) { 48162306a36Sopenharmony_ci dev_err(&intf->dev, "could not register video device\n"); 48262306a36Sopenharmony_ci goto del_adapter; 48362306a36Sopenharmony_ci } 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci dev_info(&intf->dev, "V4L2 device registered as %s\n", 48662306a36Sopenharmony_ci video_device_node_name(&radio->vdev)); 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci return 0; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_cidel_adapter: 49162306a36Sopenharmony_ci i2c_del_adapter(adapter); 49262306a36Sopenharmony_cierr_i2cdev: 49362306a36Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 49462306a36Sopenharmony_cierr_v4l2: 49562306a36Sopenharmony_ci kfree(radio->buffer); 49662306a36Sopenharmony_ci kfree(radio); 49762306a36Sopenharmony_ci return retval; 49862306a36Sopenharmony_ci} 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_cistatic void usb_si4713_disconnect(struct usb_interface *intf) 50162306a36Sopenharmony_ci{ 50262306a36Sopenharmony_ci struct si4713_usb_device *radio = to_si4713_dev(usb_get_intfdata(intf)); 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci dev_info(&intf->dev, "Si4713 development board now disconnected\n"); 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci mutex_lock(&radio->lock); 50762306a36Sopenharmony_ci usb_set_intfdata(intf, NULL); 50862306a36Sopenharmony_ci video_unregister_device(&radio->vdev); 50962306a36Sopenharmony_ci v4l2_device_disconnect(&radio->v4l2_dev); 51062306a36Sopenharmony_ci mutex_unlock(&radio->lock); 51162306a36Sopenharmony_ci v4l2_device_put(&radio->v4l2_dev); 51262306a36Sopenharmony_ci} 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci/* USB subsystem interface */ 51562306a36Sopenharmony_cistatic struct usb_driver usb_si4713_driver = { 51662306a36Sopenharmony_ci .name = "radio-usb-si4713", 51762306a36Sopenharmony_ci .probe = usb_si4713_probe, 51862306a36Sopenharmony_ci .disconnect = usb_si4713_disconnect, 51962306a36Sopenharmony_ci .id_table = usb_si4713_usb_device_table, 52062306a36Sopenharmony_ci}; 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_cimodule_usb_driver(usb_si4713_driver); 523