18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * driver for the i2c-tiny-usb adapter - 1.0
48c2ecf20Sopenharmony_ci * http://www.harbaum.org/till/i2c_tiny_usb
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Copyright (C) 2006-2007 Till Harbaum (Till@Harbaum.org)
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/errno.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/slab.h>
138c2ecf20Sopenharmony_ci#include <linux/types.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci/* include interfaces to usb layer */
168c2ecf20Sopenharmony_ci#include <linux/usb.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci/* include interface to i2c layer */
198c2ecf20Sopenharmony_ci#include <linux/i2c.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci/* commands via USB, must match command ids in the firmware */
228c2ecf20Sopenharmony_ci#define CMD_ECHO		0
238c2ecf20Sopenharmony_ci#define CMD_GET_FUNC		1
248c2ecf20Sopenharmony_ci#define CMD_SET_DELAY		2
258c2ecf20Sopenharmony_ci#define CMD_GET_STATUS		3
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define CMD_I2C_IO		4
288c2ecf20Sopenharmony_ci#define CMD_I2C_IO_BEGIN	(1<<0)
298c2ecf20Sopenharmony_ci#define CMD_I2C_IO_END		(1<<1)
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci/* i2c bit delay, default is 10us -> 100kHz max
328c2ecf20Sopenharmony_ci   (in practice, due to additional delays in the i2c bitbanging
338c2ecf20Sopenharmony_ci   code this results in a i2c clock of about 50kHz) */
348c2ecf20Sopenharmony_cistatic unsigned short delay = 10;
358c2ecf20Sopenharmony_cimodule_param(delay, ushort, 0);
368c2ecf20Sopenharmony_ciMODULE_PARM_DESC(delay, "bit delay in microseconds "
378c2ecf20Sopenharmony_ci		 "(default is 10us for 100kHz max)");
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic int usb_read(struct i2c_adapter *adapter, int cmd,
408c2ecf20Sopenharmony_ci		    int value, int index, void *data, int len);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic int usb_write(struct i2c_adapter *adapter, int cmd,
438c2ecf20Sopenharmony_ci		     int value, int index, void *data, int len);
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/* ----- begin of i2c layer ---------------------------------------------- */
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci#define STATUS_IDLE		0
488c2ecf20Sopenharmony_ci#define STATUS_ADDRESS_ACK	1
498c2ecf20Sopenharmony_ci#define STATUS_ADDRESS_NAK	2
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic int usb_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	unsigned char *pstatus;
548c2ecf20Sopenharmony_ci	struct i2c_msg *pmsg;
558c2ecf20Sopenharmony_ci	int i, ret;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	dev_dbg(&adapter->dev, "master xfer %d messages:\n", num);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	pstatus = kmalloc(sizeof(*pstatus), GFP_KERNEL);
608c2ecf20Sopenharmony_ci	if (!pstatus)
618c2ecf20Sopenharmony_ci		return -ENOMEM;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	for (i = 0 ; i < num ; i++) {
648c2ecf20Sopenharmony_ci		int cmd = CMD_I2C_IO;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci		if (i == 0)
678c2ecf20Sopenharmony_ci			cmd |= CMD_I2C_IO_BEGIN;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci		if (i == num-1)
708c2ecf20Sopenharmony_ci			cmd |= CMD_I2C_IO_END;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci		pmsg = &msgs[i];
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci		dev_dbg(&adapter->dev,
758c2ecf20Sopenharmony_ci			"  %d: %s (flags %d) %d bytes to 0x%02x\n",
768c2ecf20Sopenharmony_ci			i, pmsg->flags & I2C_M_RD ? "read" : "write",
778c2ecf20Sopenharmony_ci			pmsg->flags, pmsg->len, pmsg->addr);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci		/* and directly send the message */
808c2ecf20Sopenharmony_ci		if (pmsg->flags & I2C_M_RD) {
818c2ecf20Sopenharmony_ci			/* read data */
828c2ecf20Sopenharmony_ci			if (usb_read(adapter, cmd,
838c2ecf20Sopenharmony_ci				     pmsg->flags, pmsg->addr,
848c2ecf20Sopenharmony_ci				     pmsg->buf, pmsg->len) != pmsg->len) {
858c2ecf20Sopenharmony_ci				dev_err(&adapter->dev,
868c2ecf20Sopenharmony_ci					"failure reading data\n");
878c2ecf20Sopenharmony_ci				ret = -EIO;
888c2ecf20Sopenharmony_ci				goto out;
898c2ecf20Sopenharmony_ci			}
908c2ecf20Sopenharmony_ci		} else {
918c2ecf20Sopenharmony_ci			/* write data */
928c2ecf20Sopenharmony_ci			if (usb_write(adapter, cmd,
938c2ecf20Sopenharmony_ci				      pmsg->flags, pmsg->addr,
948c2ecf20Sopenharmony_ci				      pmsg->buf, pmsg->len) != pmsg->len) {
958c2ecf20Sopenharmony_ci				dev_err(&adapter->dev,
968c2ecf20Sopenharmony_ci					"failure writing data\n");
978c2ecf20Sopenharmony_ci				ret = -EIO;
988c2ecf20Sopenharmony_ci				goto out;
998c2ecf20Sopenharmony_ci			}
1008c2ecf20Sopenharmony_ci		}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci		/* read status */
1038c2ecf20Sopenharmony_ci		if (usb_read(adapter, CMD_GET_STATUS, 0, 0, pstatus, 1) != 1) {
1048c2ecf20Sopenharmony_ci			dev_err(&adapter->dev, "failure reading status\n");
1058c2ecf20Sopenharmony_ci			ret = -EIO;
1068c2ecf20Sopenharmony_ci			goto out;
1078c2ecf20Sopenharmony_ci		}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci		dev_dbg(&adapter->dev, "  status = %d\n", *pstatus);
1108c2ecf20Sopenharmony_ci		if (*pstatus == STATUS_ADDRESS_NAK) {
1118c2ecf20Sopenharmony_ci			ret = -ENXIO;
1128c2ecf20Sopenharmony_ci			goto out;
1138c2ecf20Sopenharmony_ci		}
1148c2ecf20Sopenharmony_ci	}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	ret = i;
1178c2ecf20Sopenharmony_ciout:
1188c2ecf20Sopenharmony_ci	kfree(pstatus);
1198c2ecf20Sopenharmony_ci	return ret;
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_cistatic u32 usb_func(struct i2c_adapter *adapter)
1238c2ecf20Sopenharmony_ci{
1248c2ecf20Sopenharmony_ci	__le32 *pfunc;
1258c2ecf20Sopenharmony_ci	u32 ret;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	pfunc = kmalloc(sizeof(*pfunc), GFP_KERNEL);
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	/* get functionality from adapter */
1308c2ecf20Sopenharmony_ci	if (!pfunc || usb_read(adapter, CMD_GET_FUNC, 0, 0, pfunc,
1318c2ecf20Sopenharmony_ci			       sizeof(*pfunc)) != sizeof(*pfunc)) {
1328c2ecf20Sopenharmony_ci		dev_err(&adapter->dev, "failure reading functionality\n");
1338c2ecf20Sopenharmony_ci		ret = 0;
1348c2ecf20Sopenharmony_ci		goto out;
1358c2ecf20Sopenharmony_ci	}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	ret = le32_to_cpup(pfunc);
1388c2ecf20Sopenharmony_ciout:
1398c2ecf20Sopenharmony_ci	kfree(pfunc);
1408c2ecf20Sopenharmony_ci	return ret;
1418c2ecf20Sopenharmony_ci}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci/* This is the actual algorithm we define */
1448c2ecf20Sopenharmony_cistatic const struct i2c_algorithm usb_algorithm = {
1458c2ecf20Sopenharmony_ci	.master_xfer	= usb_xfer,
1468c2ecf20Sopenharmony_ci	.functionality	= usb_func,
1478c2ecf20Sopenharmony_ci};
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci/* ----- end of i2c layer ------------------------------------------------ */
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci/* ----- begin of usb layer ---------------------------------------------- */
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci/*
1548c2ecf20Sopenharmony_ci * Initially the usb i2c interface uses a vid/pid pair donated by
1558c2ecf20Sopenharmony_ci * Future Technology Devices International Ltd., later a pair was
1568c2ecf20Sopenharmony_ci * bought from EZPrototypes
1578c2ecf20Sopenharmony_ci */
1588c2ecf20Sopenharmony_cistatic const struct usb_device_id i2c_tiny_usb_table[] = {
1598c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x0403, 0xc631) },   /* FTDI */
1608c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x1c40, 0x0534) },   /* EZPrototypes */
1618c2ecf20Sopenharmony_ci	{ }                               /* Terminating entry */
1628c2ecf20Sopenharmony_ci};
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, i2c_tiny_usb_table);
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci/* Structure to hold all of our device specific stuff */
1678c2ecf20Sopenharmony_cistruct i2c_tiny_usb {
1688c2ecf20Sopenharmony_ci	struct usb_device *usb_dev; /* the usb device for this device */
1698c2ecf20Sopenharmony_ci	struct usb_interface *interface; /* the interface for this device */
1708c2ecf20Sopenharmony_ci	struct i2c_adapter adapter; /* i2c related things */
1718c2ecf20Sopenharmony_ci};
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_cistatic int usb_read(struct i2c_adapter *adapter, int cmd,
1748c2ecf20Sopenharmony_ci		    int value, int index, void *data, int len)
1758c2ecf20Sopenharmony_ci{
1768c2ecf20Sopenharmony_ci	struct i2c_tiny_usb *dev = (struct i2c_tiny_usb *)adapter->algo_data;
1778c2ecf20Sopenharmony_ci	void *dmadata = kmalloc(len, GFP_KERNEL);
1788c2ecf20Sopenharmony_ci	int ret;
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	if (!dmadata)
1818c2ecf20Sopenharmony_ci		return -ENOMEM;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	/* do control transfer */
1848c2ecf20Sopenharmony_ci	ret = usb_control_msg(dev->usb_dev, usb_rcvctrlpipe(dev->usb_dev, 0),
1858c2ecf20Sopenharmony_ci			       cmd, USB_TYPE_VENDOR | USB_RECIP_INTERFACE |
1868c2ecf20Sopenharmony_ci			       USB_DIR_IN, value, index, dmadata, len, 2000);
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	memcpy(data, dmadata, len);
1898c2ecf20Sopenharmony_ci	kfree(dmadata);
1908c2ecf20Sopenharmony_ci	return ret;
1918c2ecf20Sopenharmony_ci}
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_cistatic int usb_write(struct i2c_adapter *adapter, int cmd,
1948c2ecf20Sopenharmony_ci		     int value, int index, void *data, int len)
1958c2ecf20Sopenharmony_ci{
1968c2ecf20Sopenharmony_ci	struct i2c_tiny_usb *dev = (struct i2c_tiny_usb *)adapter->algo_data;
1978c2ecf20Sopenharmony_ci	void *dmadata = kmemdup(data, len, GFP_KERNEL);
1988c2ecf20Sopenharmony_ci	int ret;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	if (!dmadata)
2018c2ecf20Sopenharmony_ci		return -ENOMEM;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	/* do control transfer */
2048c2ecf20Sopenharmony_ci	ret = usb_control_msg(dev->usb_dev, usb_sndctrlpipe(dev->usb_dev, 0),
2058c2ecf20Sopenharmony_ci			       cmd, USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
2068c2ecf20Sopenharmony_ci			       value, index, dmadata, len, 2000);
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	kfree(dmadata);
2098c2ecf20Sopenharmony_ci	return ret;
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_cistatic void i2c_tiny_usb_free(struct i2c_tiny_usb *dev)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	usb_put_dev(dev->usb_dev);
2158c2ecf20Sopenharmony_ci	kfree(dev);
2168c2ecf20Sopenharmony_ci}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_cistatic int i2c_tiny_usb_probe(struct usb_interface *interface,
2198c2ecf20Sopenharmony_ci			      const struct usb_device_id *id)
2208c2ecf20Sopenharmony_ci{
2218c2ecf20Sopenharmony_ci	struct i2c_tiny_usb *dev;
2228c2ecf20Sopenharmony_ci	int retval = -ENOMEM;
2238c2ecf20Sopenharmony_ci	u16 version;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	dev_dbg(&interface->dev, "probing usb device\n");
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	/* allocate memory for our device state and initialize it */
2288c2ecf20Sopenharmony_ci	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
2298c2ecf20Sopenharmony_ci	if (!dev)
2308c2ecf20Sopenharmony_ci		goto error;
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
2338c2ecf20Sopenharmony_ci	dev->interface = interface;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	/* save our data pointer in this interface device */
2368c2ecf20Sopenharmony_ci	usb_set_intfdata(interface, dev);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	version = le16_to_cpu(dev->usb_dev->descriptor.bcdDevice);
2398c2ecf20Sopenharmony_ci	dev_info(&interface->dev,
2408c2ecf20Sopenharmony_ci		 "version %x.%02x found at bus %03d address %03d\n",
2418c2ecf20Sopenharmony_ci		 version >> 8, version & 0xff,
2428c2ecf20Sopenharmony_ci		 dev->usb_dev->bus->busnum, dev->usb_dev->devnum);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	/* setup i2c adapter description */
2458c2ecf20Sopenharmony_ci	dev->adapter.owner = THIS_MODULE;
2468c2ecf20Sopenharmony_ci	dev->adapter.class = I2C_CLASS_HWMON;
2478c2ecf20Sopenharmony_ci	dev->adapter.algo = &usb_algorithm;
2488c2ecf20Sopenharmony_ci	dev->adapter.algo_data = dev;
2498c2ecf20Sopenharmony_ci	snprintf(dev->adapter.name, sizeof(dev->adapter.name),
2508c2ecf20Sopenharmony_ci		 "i2c-tiny-usb at bus %03d device %03d",
2518c2ecf20Sopenharmony_ci		 dev->usb_dev->bus->busnum, dev->usb_dev->devnum);
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	if (usb_write(&dev->adapter, CMD_SET_DELAY, delay, 0, NULL, 0) != 0) {
2548c2ecf20Sopenharmony_ci		dev_err(&dev->adapter.dev,
2558c2ecf20Sopenharmony_ci			"failure setting delay to %dus\n", delay);
2568c2ecf20Sopenharmony_ci		retval = -EIO;
2578c2ecf20Sopenharmony_ci		goto error;
2588c2ecf20Sopenharmony_ci	}
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci	dev->adapter.dev.parent = &dev->interface->dev;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	/* and finally attach to i2c layer */
2638c2ecf20Sopenharmony_ci	i2c_add_adapter(&dev->adapter);
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	/* inform user about successful attachment to i2c layer */
2668c2ecf20Sopenharmony_ci	dev_info(&dev->adapter.dev, "connected i2c-tiny-usb device\n");
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	return 0;
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci error:
2718c2ecf20Sopenharmony_ci	if (dev)
2728c2ecf20Sopenharmony_ci		i2c_tiny_usb_free(dev);
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	return retval;
2758c2ecf20Sopenharmony_ci}
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_cistatic void i2c_tiny_usb_disconnect(struct usb_interface *interface)
2788c2ecf20Sopenharmony_ci{
2798c2ecf20Sopenharmony_ci	struct i2c_tiny_usb *dev = usb_get_intfdata(interface);
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	i2c_del_adapter(&dev->adapter);
2828c2ecf20Sopenharmony_ci	usb_set_intfdata(interface, NULL);
2838c2ecf20Sopenharmony_ci	i2c_tiny_usb_free(dev);
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	dev_dbg(&interface->dev, "disconnected\n");
2868c2ecf20Sopenharmony_ci}
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_cistatic struct usb_driver i2c_tiny_usb_driver = {
2898c2ecf20Sopenharmony_ci	.name		= "i2c-tiny-usb",
2908c2ecf20Sopenharmony_ci	.probe		= i2c_tiny_usb_probe,
2918c2ecf20Sopenharmony_ci	.disconnect	= i2c_tiny_usb_disconnect,
2928c2ecf20Sopenharmony_ci	.id_table	= i2c_tiny_usb_table,
2938c2ecf20Sopenharmony_ci};
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_cimodule_usb_driver(i2c_tiny_usb_driver);
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci/* ----- end of usb layer ------------------------------------------------ */
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ciMODULE_AUTHOR("Till Harbaum <Till@Harbaum.org>");
3008c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("i2c-tiny-usb driver v1.0");
3018c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
302