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