diff mbox

[v2] drivers/i2c/busses: adding ch341a USB adapter

Message ID 1465327524-22265-1-git-send-email-angelo.compagnucci@gmail.com
State New
Headers show

Commit Message

Angelo Compagnucci June 7, 2016, 7:25 p.m. UTC
This patch adds support for ch341a USB to I2C bridge, it's
based on a reverse engineering of the official gui client
and several code examples.

Tested with several eeprom models (24c32, 24c08), rtc (ds1307),
adc (mcp3422).

Signed-off-by: Angelo Compagnucci <angelo.compagnucci@gmail.com>
---
Changes v1->v2:

* Better delay in writing function
* Corrected i2c supported speeds (verified also
  with an oscilloscope)

 drivers/i2c/busses/Kconfig      |  10 ++
 drivers/i2c/busses/Makefile     |   1 +
 drivers/i2c/busses/i2c-ch341a.c | 388 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 399 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-ch341a.c
diff mbox

Patch

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index f167021..2a168c2 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -997,6 +997,16 @@  config I2C_RCAR
 
 comment "External I2C/SMBus adapter drivers"
 
+config I2C_CH341A
+        tristate "Winchiphead CH341A USB adapter"
+        depends on USB
+        help
+          If you say yes to this option, support will be included for
+          Winchiphead CH341A, a USB to I2C interface.
+
+          This driver can also be built as a module.  If so, the module
+          will be called i2c-ch341a.
+
 config I2C_DIOLAN_U2C
 	tristate "Diolan U2C-12 USB adapter"
 	depends on USB
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 37f2819..16805b7 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -98,6 +98,7 @@  obj-$(CONFIG_I2C_XLP9XX)	+= i2c-xlp9xx.o
 obj-$(CONFIG_I2C_RCAR)		+= i2c-rcar.o
 
 # External I2C/SMBus adapter drivers
+obj-$(CONFIG_I2C_CH341A)	+= i2c-ch341a.o
 obj-$(CONFIG_I2C_DIOLAN_U2C)	+= i2c-diolan-u2c.o
 obj-$(CONFIG_I2C_DLN2)		+= i2c-dln2.o
 obj-$(CONFIG_I2C_PARPORT)	+= i2c-parport.o
diff --git a/drivers/i2c/busses/i2c-ch341a.c b/drivers/i2c/busses/i2c-ch341a.c
new file mode 100644
index 0000000..ccd2a01
--- /dev/null
+++ b/drivers/i2c/busses/i2c-ch341a.c
@@ -0,0 +1,388 @@ 
+/*
+ * Driver for the Winchiphead CH341A USB to I2C chip
+ *
+ * Datasheet: http://www.winchiphead.com/download/CH341/CH341DS1.PDF
+ *
+ * Copyright (C) 2016 Angelo Compagnucci <angelo.compagnucci@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/string.h>
+#include <linux/platform_device.h>
+
+#define TIMEOUT 1000
+
+#define CH341A_CONTROL_I2C        0xAA
+
+#define CH341A_I2C_CMD_STA        0x74
+#define CH341A_I2C_CMD_STO        0x75
+#define CH341A_I2C_CMD_OUT        0x80
+#define CH341A_I2C_CMD_IN         0xC0
+#define CH341A_I2C_CMD_MAX_LENGTH 32
+#define CH341A_I2C_CMD_SET        0x60
+#define CH341A_I2C_CMD_US         0x40
+#define CH341A_I2C_CMD_MS         0x50
+#define CH341A_I2C_CMD_DLY        0x0f
+#define CH341A_I2C_CMD_END        0x00
+
+#define SEND_PAYLOAD_LENGTH       29
+#define RECV_PAYLOAD_LENGTH       32
+
+struct i2c_ch341a {
+	u8 in_buf[CH341A_I2C_CMD_MAX_LENGTH];
+	u8 out_buf[CH341A_I2C_CMD_MAX_LENGTH];
+	struct usb_device *usb_dev;
+	struct usb_interface *interface;
+	struct i2c_adapter adapter;
+	unsigned int clock_speed;
+	int ep_in, ep_out;
+};
+
+static int ch341a_usb_i2c_command(struct i2c_adapter *adapter, u8 *cmd, u8 len)
+{
+	struct i2c_ch341a *dev = (struct i2c_ch341a *)adapter->algo_data;
+	int sent, ret;
+
+	ret = usb_bulk_msg(dev->usb_dev,
+		usb_sndbulkpipe(dev->usb_dev, dev->ep_out),
+		cmd, len, &sent, TIMEOUT);
+	if (ret != 0) return ret;
+
+	return 0;
+}
+
+static int ch341a_usb_i2c_start(struct i2c_adapter *adapter)
+{
+	u8 I2C_CMD_START[] = {CH341A_CONTROL_I2C, CH341A_I2C_CMD_STA, CH341A_I2C_CMD_END};
+	print_hex_dump_bytes(__func__, DUMP_PREFIX_OFFSET, I2C_CMD_START, sizeof(I2C_CMD_START));
+	return ch341a_usb_i2c_command(adapter, I2C_CMD_START, sizeof(I2C_CMD_START));
+}
+
+static int ch341a_usb_i2c_stop(struct i2c_adapter *adapter)
+{
+	u8 I2C_CMD_STOP[] = {CH341A_CONTROL_I2C, CH341A_I2C_CMD_STO, CH341A_I2C_CMD_END};
+	print_hex_dump_bytes(__func__, DUMP_PREFIX_OFFSET, I2C_CMD_STOP, sizeof(I2C_CMD_STOP));
+	return ch341a_usb_i2c_command(adapter, I2C_CMD_STOP, sizeof(I2C_CMD_STOP));
+}
+
+static int ch341a_usb_i2c_set_speed(struct i2c_adapter *adapter, unsigned int data)
+{
+	struct i2c_ch341a *ch341a_data = (struct i2c_ch341a *)adapter->algo_data;
+	u8 I2C_CMD_SET[] = {CH341A_CONTROL_I2C, CH341A_I2C_CMD_SET , CH341A_I2C_CMD_END};
+	u8 speed;
+
+	switch (data) {
+		case 20:
+			speed = 0;
+			break;
+		case 100:
+			speed = 1;
+			break;
+		case 400:
+			speed = 2;
+			break;
+		case 750:
+			speed = 3;
+			break;
+		default:
+			return -EINVAL;
+	}
+
+	ch341a_data->clock_speed = data;
+
+	I2C_CMD_SET[1] |= speed;
+
+	print_hex_dump_bytes(__func__, DUMP_PREFIX_OFFSET, I2C_CMD_SET, sizeof(I2C_CMD_SET));
+	return ch341a_usb_i2c_command(adapter, I2C_CMD_SET, sizeof(I2C_CMD_SET));
+}
+
+static int ch341a_usb_i2c_write(struct i2c_adapter *adapter, u8 len, u8 *data)
+{
+	struct i2c_ch341a *dev = (struct i2c_ch341a *)adapter->algo_data;
+	int ret, actual;
+
+	dev->out_buf[0] = CH341A_CONTROL_I2C;
+	dev->out_buf[1] = CH341A_I2C_CMD_OUT | len;
+	memcpy(&dev->out_buf[2], data, len);
+	dev->out_buf[2+len] = CH341A_I2C_CMD_END;
+
+	print_hex_dump_bytes(__func__, DUMP_PREFIX_OFFSET, dev->out_buf, len+3);
+
+	ret = usb_bulk_msg(dev->usb_dev,
+		usb_sndbulkpipe(dev->usb_dev, dev->ep_out),
+		dev->out_buf, len+3, &actual, TIMEOUT);
+	if (ret != 0) return ret;
+
+	return 0;
+}
+
+static int ch341a_usb_write_bytes(struct i2c_adapter *adapter, u8 addr, u16 len, u8 *data)
+{
+	int i, ret;
+
+	ret = ch341a_usb_i2c_start(adapter);
+	if (ret != 0) return ret;
+
+	ret = ch341a_usb_i2c_write(adapter, 1, &addr);
+	if (ret != 0) return ret;
+
+	for (i=0; i < len/SEND_PAYLOAD_LENGTH; i++) {
+		ret = ch341a_usb_i2c_write(adapter,
+			SEND_PAYLOAD_LENGTH,
+			&data[i*SEND_PAYLOAD_LENGTH]);
+		if (ret != 0) return ret;
+	}
+	if ( len % SEND_PAYLOAD_LENGTH ) {
+		ret = ch341a_usb_i2c_write(adapter,
+			len-(SEND_PAYLOAD_LENGTH*i),
+			&data[i*SEND_PAYLOAD_LENGTH]);
+		if (ret != 0) return ret;
+	}
+
+	ret = ch341a_usb_i2c_stop(adapter);
+	usleep_range(2500, 2500);
+
+	return ret;
+}
+
+static int ch341a_usb_i2c_read(struct i2c_adapter *adapter, u8 len, u8 *data)
+{
+	struct i2c_ch341a *dev = (struct i2c_ch341a *)adapter->algo_data;
+	u8 I2C_CMD_READ_BYTE[] = {CH341A_CONTROL_I2C, 0, CH341A_I2C_CMD_END};
+	int ret, actual;
+
+	I2C_CMD_READ_BYTE[1] = CH341A_I2C_CMD_IN | (u8) len;
+
+	ret = usb_bulk_msg(dev->usb_dev,
+			usb_sndbulkpipe(dev->usb_dev, dev->ep_out),
+			I2C_CMD_READ_BYTE, sizeof(I2C_CMD_READ_BYTE), &actual, TIMEOUT);
+	if (ret != 0) return ret;
+
+	ret = usb_bulk_msg(dev->usb_dev,
+			usb_rcvbulkpipe(dev->usb_dev, dev->ep_in),
+			dev->in_buf, RECV_PAYLOAD_LENGTH, &actual, TIMEOUT);
+	if (ret != 0) return ret;
+
+	memcpy(data, dev->in_buf, len);
+
+	return 0;
+}
+
+static int ch341a_usb_i2c_read_bytes(struct i2c_adapter *adapter, u8 addr, u16 len, u8 *data)
+{
+	int ret, i;
+
+	ret = ch341a_usb_i2c_start(adapter);
+	if (ret != 0) return ret;
+	ret = ch341a_usb_i2c_write(adapter, 1, &addr);
+	if (ret != 0) return ret;
+
+	for (i=0; i < len/RECV_PAYLOAD_LENGTH; i++) {
+		ret = ch341a_usb_i2c_read(adapter,
+			RECV_PAYLOAD_LENGTH,
+			&data[i*RECV_PAYLOAD_LENGTH]);
+		if (ret != 0) return ret;
+	}
+	if ( len % RECV_PAYLOAD_LENGTH ) {
+		ret = ch341a_usb_i2c_read(adapter,
+			len-(RECV_PAYLOAD_LENGTH*i),
+			&data[i*RECV_PAYLOAD_LENGTH]);
+		if (ret != 0) return ret;
+	}
+
+	print_hex_dump_bytes(__func__, DUMP_PREFIX_OFFSET, data, len);
+
+	ret = ch341a_usb_i2c_stop(adapter);
+	if (ret != 0) return ret;
+	ch341a_usb_i2c_write(adapter, 1, &addr); //should give error so don't check here
+
+	return 0;
+}
+
+static int ch341a_usb_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)
+{
+	struct i2c_msg *pmsg;
+	int i;
+
+	for (i = 0; i < num; i++) {
+		pmsg = &msgs[i];
+		dev_dbg(&adapter->dev, "%s: (addr=%x, flags=%x, len=%d)", __func__,
+			pmsg->addr, pmsg->flags, pmsg->len);
+		if (pmsg->flags & I2C_M_RD) {
+			if (ch341a_usb_i2c_read_bytes(adapter,
+				(pmsg->addr<<1) + 1,
+				pmsg->len, pmsg->buf) != 0)
+					return -EREMOTEIO;
+		} else {
+			if (ch341a_usb_write_bytes(adapter,
+				(pmsg->addr<<1), pmsg->len, pmsg->buf) != 0)
+					return -EREMOTEIO;
+		}
+	}
+	return i;
+}
+
+static u32 ch341a_usb_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static ssize_t ch341a_attr_clock_store_value(struct device *dev,
+			struct device_attribute *attr,
+			const char *buf, size_t count) {
+
+	struct i2c_adapter *adapter = to_i2c_adapter(dev);
+	unsigned int value;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &value);
+
+	ret = ch341a_usb_i2c_set_speed(adapter, value);
+	if (ret != 0) return -EINVAL;
+
+	return count;
+}
+
+static ssize_t ch341a_attr_clock_show_value(struct device *dev,
+				struct device_attribute *attr,
+				char *buf) {
+	struct i2c_adapter *adapter = to_i2c_adapter(dev);
+	struct i2c_ch341a *ch341a_data = (struct i2c_ch341a *)adapter->algo_data;
+	return sprintf(buf, "%d\n", (ch341a_data->clock_speed));
+}
+
+static ssize_t ch341a_attr_clock_show_freqs(struct device *dev,
+				struct device_attribute *attr,
+				char *buf) {
+	return sprintf(buf, "20 100 400 750\n");
+}
+
+static DEVICE_ATTR(clock_frequency, S_IWUSR | S_IRUGO,
+			ch341a_attr_clock_show_value,
+			ch341a_attr_clock_store_value);
+static DEVICE_ATTR(clock_frequency_available, S_IRUGO,
+		ch341a_attr_clock_show_freqs, NULL);
+
+static struct attribute *ch341a_attrs[] = {
+	&dev_attr_clock_frequency.attr,
+	&dev_attr_clock_frequency_available.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(ch341a);
+
+static const struct i2c_algorithm usb_algorithm = {
+	.master_xfer	= ch341a_usb_xfer,
+	.functionality	= ch341a_usb_func,
+};
+
+static const struct usb_device_id ch341a_table[] = {
+	{ USB_DEVICE(0x1a86, 0x5512) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, ch341a_table);
+
+static void ch341a_free(struct i2c_ch341a *dev)
+{
+	usb_put_dev(dev->usb_dev);
+	kfree(dev);
+}
+
+static int ch341a_probe(struct usb_interface *interface,
+			const struct usb_device_id *id)
+{
+	struct usb_host_interface *hostif = interface->cur_altsetting;
+	struct i2c_ch341a *dev;
+	int ret = -ENOMEM;
+
+	dev_dbg(&interface->dev, "probing usb device\n");
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (dev == NULL) {
+		dev_err(&interface->dev, "Out of memory\n");
+		goto error;
+	}
+
+	dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
+	dev->interface = interface;
+
+	dev->ep_out = hostif->endpoint[1].desc.bEndpointAddress;
+	dev->ep_in = hostif->endpoint[0].desc.bEndpointAddress;
+
+	usb_set_intfdata(interface, dev);
+
+	dev->adapter.owner = THIS_MODULE;
+	dev->adapter.class = I2C_CLASS_HWMON;
+	dev->adapter.algo = &usb_algorithm;
+	dev->adapter.algo_data = dev;
+	dev->adapter.dev.groups = ch341a_groups;
+	i2c_set_adapdata(&dev->adapter, dev);
+	snprintf(dev->adapter.name, sizeof(dev->adapter.name),
+		 "i2c-ch341a at bus %03d device %03d",
+		 dev->usb_dev->bus->busnum, dev->usb_dev->devnum);
+
+	dev->adapter.dev.parent = &dev->interface->dev;
+
+	ret = i2c_add_adapter(&dev->adapter);
+	if (ret < 0) {
+		goto error;
+	}
+
+	ret = ch341a_usb_i2c_set_speed(&dev->adapter, 100);
+	if (ret != 0) return ret;
+
+	dev_info(&dev->adapter.dev, "connected i2c-ch341a device\n");
+
+	return 0;
+
+ error:
+	if (dev)
+		usb_set_intfdata(interface, NULL);
+		ch341a_free(dev);
+
+	return ret;
+}
+
+static void ch341a_disconnect(struct usb_interface *interface)
+{
+	struct i2c_ch341a *dev = usb_get_intfdata(interface);
+
+	i2c_del_adapter(&dev->adapter);
+	usb_set_intfdata(interface, NULL);
+	ch341a_free(dev);
+
+	dev_dbg(&interface->dev, "disconnected\n");
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id ch341a_of_match[] = {
+	{ .compatible = "ch341a" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ch341a_of_match);
+#endif
+
+static struct usb_driver ch341a_driver = {
+	.name		= "i2c-ch341a",
+	.probe		= ch341a_probe,
+	.disconnect	= ch341a_disconnect,
+	.id_table	= ch341a_table,
+};
+
+module_usb_driver(ch341a_driver);
+
+MODULE_AUTHOR("Angelo Compagnucci <angelo.compagnucci@gmail.com>");
+MODULE_DESCRIPTION("i2c-ch341a driver");
+MODULE_LICENSE("GPL v2");