From patchwork Tue Jun 7 19:25:24 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Angelo Compagnucci X-Patchwork-Id: 631778 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3rPM3t1Xdrz9t3x for ; Wed, 8 Jun 2016 05:25:46 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=Z4LftWXv; dkim-atps=neutral Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755241AbcFGTZi (ORCPT ); Tue, 7 Jun 2016 15:25:38 -0400 Received: from mail-wm0-f68.google.com ([74.125.82.68]:35201 "EHLO mail-wm0-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755260AbcFGTZf (ORCPT ); Tue, 7 Jun 2016 15:25:35 -0400 Received: by mail-wm0-f68.google.com with SMTP id k184so20223012wme.2 for ; Tue, 07 Jun 2016 12:25:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id; bh=HKKrlbGWR8HXZQl7A0zhJnhUAofFrSoQTuiz79xDdmk=; b=Z4LftWXvRBWAx9ZZnMaD5pdxQfHl03lTTshaeulvXtJUWJxqfLpPwtM3WwIxbWTUUl FBRYwignxCz2l1CY6pbuyQxlqOj6mY4wqirD0oZRgo1hCuTGl2kudTt8t9Hs7XAtjWcb KE2ib9Du5CfM2OLDnZjOFivUmKb2mdL8c/fv7/NrHjVKFJ6h5RWy5ZG1tCveEgDfTTi/ dd4MJXTezJ65ggooMj21hFMvhTQM8cWTIPxqdYw1CZ7Ui4jWuX+bEW1hgY4FY7gIdM5M Rcp6a1Y5+HOgNlXNZDHhKNI4CdcIadVpXYoSBx2Hdw+W0IMc4kCC5/plUglGSMM1iHPF 57Ig== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=HKKrlbGWR8HXZQl7A0zhJnhUAofFrSoQTuiz79xDdmk=; b=iTQ8CifYE0Yl3MIinSFdpT8OA9sf46b2c1kE2prWx1BAj/tmE14HMg2DUjMOqIrK36 cVQ6Aty5c6Y6xg6lFSXOBy0CCoWRtvEwah+bvRxRcbJ0JYyMwPCsjAgVy1EegTaOKUuK M0v0fnuaH4m7IUPDjEV3R59Ic5F4f8Ty/QbNL+tLRZRqenp5nDR/iiJn1DS6NxMwDIMi ZhJ7WbkCQ3vdLcytmK2S2hsUUU5z66h1Romc29vzqyYffPpDA3EbdMRaZj+eojiCiimT QhCBROBO8K83GyCNAV80UZT9M/3rGwiJWluZ1QumjNSTa3JDlEm2piqTlzsTv+vV2Mv5 uuyg== X-Gm-Message-State: ALyK8tLx62HTx3Pk081dXXYOyROMiQvhXWtnq3R6XjTnV1PSy707Yi6IqVSTGMDCThf8gw== X-Received: by 10.195.12.229 with SMTP id et5mr1014226wjd.22.1465327533788; Tue, 07 Jun 2016 12:25:33 -0700 (PDT) Received: from localhost.localdomain ([89.202.204.147]) by smtp.gmail.com with ESMTPSA id o129sm20691257wmb.17.2016.06.07.12.25.32 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 07 Jun 2016 12:25:32 -0700 (PDT) From: Angelo Compagnucci To: linux-i2c@vger.kernel.org Cc: Angelo Compagnucci Subject: [PATCH v2] drivers/i2c/busses: adding ch341a USB adapter Date: Tue, 7 Jun 2016 21:25:24 +0200 Message-Id: <1465327524-22265-1-git-send-email-angelo.compagnucci@gmail.com> X-Mailer: git-send-email 1.9.1 Sender: linux-i2c-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-i2c@vger.kernel.org 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 --- 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 --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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("i2c-ch341a driver"); +MODULE_LICENSE("GPL v2");