This patch adds support for the I2C interface of the Devantech USB-ISS
Multifunction adapter.

Signed-off-by: Guenter Roeck <[email protected]>
---
The driver has one problem: It competes with the cdc_acm driver for device
access. Copying the usb mailing list in the hope that someone can tell me
if there is a way to prevent this from happening.

 Documentation/i2c/busses/i2c-devantech-iss |   31 ++
 drivers/i2c/busses/Kconfig                 |   10 +
 drivers/i2c/busses/Makefile                |    1 +
 drivers/i2c/busses/i2c-devantech-iss.c     |  495 ++++++++++++++++++++++++++++
 4 files changed, 537 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/i2c/busses/i2c-devantech-iss
 create mode 100644 drivers/i2c/busses/i2c-devantech-iss.c

diff --git a/Documentation/i2c/busses/i2c-devantech-iss 
b/Documentation/i2c/busses/i2c-devantech-iss
new file mode 100644
index 0000000..f8199fd
--- /dev/null
+++ b/Documentation/i2c/busses/i2c-devantech-iss
@@ -0,0 +1,31 @@
+Kernel driver i2c-devantech-iss
+
+Supported adapters:
+  * Devantech USB-ISS Multifunction USB Communications Module
+    Documentation:
+        http://www.robot-electronics.co.uk/htm/usb_iss_tech.htm
+
+Author: Guenter Roeck <[email protected]>
+
+
+Description
+-----------
+
+This is the driver for the Devantech USB-ISS Multifunction USB Communications
+Module.
+
+The USB-ISS Multifunction USB Communications Module provides an interface
+to I2C, SPI, Serial port, and general purpose Analogue Input or Digital I/O.
+The module is powered from USB.
+
+The driver only supports the I2C interface of USB-ISS. The driver does not use
+interrupts.
+
+Clock stretching is supported for bus frequencies of 100,000 Hz and above.
+
+
+Module parameters
+-----------------
+
+* frequency: I2C bus frequency in Hz
+  Supported frequencies are 20,000, 50,000, 100,000, 400,000, and 1,000,000 Hz.
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 230601e..0bffacd 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -676,6 +676,16 @@ config I2C_EG20T
 
 comment "External I2C/SMBus adapter drivers"
 
+config I2C_DEVANTECH
+       tristate "Devantech USB-ISS"
+       select I2C_SMBUS
+       help
+         This supports the I2C interface of the Devantech USB-ISS Multifunction
+         Communications Module.
+
+         This driver can also be built as a module.  If so, the module
+         will be called i2c-devantech-iss.
+
 config I2C_PARPORT
        tristate "Parallel port adapter"
        depends on PARPORT
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 3878c95..5ecec28 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_I2C_XILINX)      += i2c-xiic.o
 obj-$(CONFIG_I2C_EG20T)         += i2c-eg20t.o
 
 # External I2C/SMBus adapter drivers
+obj-$(CONFIG_I2C_DEVANTECH)    += i2c-devantech-iss.o
 obj-$(CONFIG_I2C_PARPORT)      += i2c-parport.o
 obj-$(CONFIG_I2C_PARPORT_LIGHT)        += i2c-parport-light.o
 obj-$(CONFIG_I2C_TAOS_EVM)     += i2c-taos-evm.o
diff --git a/drivers/i2c/busses/i2c-devantech-iss.c 
b/drivers/i2c/busses/i2c-devantech-iss.c
new file mode 100644
index 0000000..b14daa6
--- /dev/null
+++ b/drivers/i2c/busses/i2c-devantech-iss.c
@@ -0,0 +1,495 @@
+/*
+ * Driver for the Devantech USB-ISS Multifunction Communications Module
+ *
+ * Copyright (c) 2011 Ericsson AB
+ *
+ * Derived from:
+ *  i2c-diolan-u2c.c
+ *  Copyright (c) 2010-2011 Ericsson AB
+ *
+ * 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/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+
+#define DRIVER_NAME            "i2c-devantech-iss"
+
+#define USB_VENDOR_ID_DEVANTECH        0x04d8
+#define USB_DEVICE_ID_ISS      0xffee
+
+/* commands */
+#define ISS_CMD_I2C_DIRECT     0x57    /* Custom I2C sequence */
+#define ISS_CMD_I2C_INTERNAL   0x5a    /* internal commands */
+
+/* direct commands */
+#define ISS_I2C_START          0x01
+#define ISS_I2C_RESTART                0x02
+#define ISS_I2C_STOP           0x03
+#define ISS_I2C_NACK           0x04
+#define ISS_I2C_READ(n)                (0x20 + (n) - 1)
+#define ISS_I2C_WRITE(n)       (0x30 + (n) - 1)
+
+/* internal commands */
+#define ISS_GET_VERSION                0x01
+#define ISS_MODE               0x02
+#define ISS_GET_SERIAL         0x03
+
+/* modes */
+#define ISS_MODE_I2C_S_20KHZ   0x20    /* I2C, SW (bitbang), 20 kHz clock */
+#define ISS_MODE_I2C_S_50KHZ   0x30    /* I2C, SW (bitbang), 50 kHz clock */
+#define ISS_MODE_I2C_S_100KHZ  0x40    /* I2C, SW (bitbang), 100 kHz clock */
+#define ISS_MODE_I2C_S_400KHZ  0x50    /* I2C, SW (bitbang), 400 kHz clock */
+#define ISS_MODE_I2C_H_100KHZ  0x60    /* I2C, HW (PIC), 100 kHz clock */
+#define ISS_MODE_I2C_H_400KHZ  0x70    /* I2C, HW (PIC), 400 kHz clock */
+#define ISS_MODE_I2C_H_1000KHZ 0x80    /* I2C, HW (PIC), 1000 kHz clock */
+
+/* response codes */
+
+#define ISS_RESP_ACK           0xff
+#define ISS_RESP_NACK          0x00
+
+#define ISS_RESP_OK            0x00
+#define ISS_RESP_DEV_ERROR     0x01
+#define ISS_RESP_OVERFLOW      0x02
+#define ISS_RESP_UNDERFLOW     0x03
+#define ISS_RESP_UNKNOWN_CMD   0x04
+
+#define ISS_USB_TIMEOUT                100     /* in ms */
+
+#define ISS_MAX_TRANSFER_LEN   60
+
+/* Structure to hold all of our device specific stuff */
+struct i2c_devantech_iss {
+       u8 buffer[ISS_MAX_TRANSFER_LEN];/* rx and tx buffer */
+       struct usb_interface *usb_if;   /* device data interface */
+       struct usb_interface *interface;/* device control interface */
+       struct usb_device *usb_dev;     /* the usb device for this interface */
+       struct i2c_adapter adapter;     /* i2c related things */
+       int ep_out, ep_in;
+};
+
+static uint frequency = 100000;        /* I2C clock frequency in Hz */
+module_param(frequency, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(frequency, "I2C clock frequency in hertz");
+
+static struct usb_driver devantech_iss_driver;
+
+/* usb layer */
+
+/* Send command to device, and get response. */
+static int devantech_usb_transfer(struct i2c_devantech_iss *dev, int len)
+{
+       int ret, actual;
+
+       if (len <= 0 || len > ISS_MAX_TRANSFER_LEN)
+               return -EINVAL;
+
+       ret = usb_bulk_msg(dev->usb_dev,
+                          usb_sndbulkpipe(dev->usb_dev, dev->ep_out),
+                          dev->buffer, len, &actual,
+                          ISS_USB_TIMEOUT);
+
+       if (!ret) {
+               ret = usb_bulk_msg(dev->usb_dev,
+                                  usb_rcvbulkpipe(dev->usb_dev, dev->ep_in),
+                                  dev->buffer, sizeof(dev->buffer),
+                                  &actual, ISS_USB_TIMEOUT);
+               if (!ret && actual > 0)
+                       ret = actual;
+       }
+       return ret;
+}
+
+static int devantech_set_speed(struct i2c_devantech_iss *dev, int speed)
+{
+       int ret;
+
+       dev->buffer[0] = ISS_CMD_I2C_INTERNAL;
+       dev->buffer[1] = ISS_MODE;
+       dev->buffer[2] = speed;
+       dev->buffer[3] = 0x04;
+
+       ret = devantech_usb_transfer(dev, 4);
+       if (ret < 0)
+               return ret;
+       if (!dev->buffer[0])
+               return -EINVAL;
+       return 0;
+}
+
+static void devantech_fw_version(struct i2c_devantech_iss *dev)
+{
+       int ret;
+
+       dev->buffer[0] = ISS_CMD_I2C_INTERNAL;
+       dev->buffer[1] = ISS_GET_VERSION;
+
+       ret = devantech_usb_transfer(dev, 2);
+       if (ret >= 3)
+               dev_info(&dev->interface->dev,
+                        "Devantech USB-ISS firmware version %u.%u\n",
+                        dev->buffer[0], dev->buffer[1]);
+       else
+               dev_err(&dev->interface->dev,
+                       "Failed to get firmware version, error %d\n", ret);
+}
+
+static void devantech_get_serial(struct i2c_devantech_iss *dev)
+{
+       int ret;
+
+       dev->buffer[0] = ISS_CMD_I2C_INTERNAL;
+       dev->buffer[1] = ISS_GET_SERIAL;
+
+       ret = devantech_usb_transfer(dev, 2);
+       if (ret >= 8) {
+               dev->buffer[ret] = '\0';
+               dev_info(&dev->interface->dev,
+                        "Devantech USS-ISS serial number %s\n", dev->buffer);
+       } else {
+               dev_err(&dev->interface->dev,
+                       "Failed to get serial number, error %d\n", ret);
+       }
+}
+
+static int devantech_init(struct i2c_devantech_iss *dev)
+{
+       int speed;
+
+       /*
+        * Software (bit-bang) I2C implementation does not support I2C clock
+        * stretching, at least not with firmware version 7.2. The PIC hardware
+        * implementation supports it, so select the hardware protocol if
+        * available for a given bus speed.
+        * If the bus speed is not configured or set to 0, select 100kHz.
+        */
+       if (frequency >= 1000000) {
+               speed = ISS_MODE_I2C_H_1000KHZ;
+               frequency = 1000000;
+       } else if (frequency >= 400000) {
+               speed = ISS_MODE_I2C_H_400KHZ;
+               frequency = 400000;
+       } else if (frequency >= 100000 || frequency == 0) {
+               speed = ISS_MODE_I2C_H_100KHZ;
+               frequency = 100000;
+       } else if (frequency >= 50000) {
+               speed = ISS_MODE_I2C_S_50KHZ;
+               frequency = 50000;
+       } else {
+               speed = ISS_MODE_I2C_S_20KHZ;
+               frequency = 20000;
+       }
+
+       dev_info(&dev->interface->dev,
+                "Devantech USB-ISS at USB bus %03d address %03d speed %d Hz\n",
+                dev->usb_dev->bus->busnum, dev->usb_dev->devnum, frequency);
+
+       devantech_fw_version(dev);
+       devantech_get_serial(dev);
+
+       return devantech_set_speed(dev, speed);
+}
+
+/* i2c layer */
+
+static int devantech_usb_xfer(struct i2c_adapter *adapter, struct i2c_msg 
*msgs,
+                             int num)
+{
+       struct i2c_devantech_iss *dev = i2c_get_adapdata(adapter);
+       struct i2c_msg *pmsg = NULL;
+       int i, j, len, rlen;
+       int ret;
+
+       len = 0;
+       dev->buffer[len++] = ISS_CMD_I2C_DIRECT;
+       dev->buffer[len++] = ISS_I2C_START;
+
+       for (i = 0; i < num; i++) {
+               pmsg = &msgs[i];
+               if (i)
+                       dev->buffer[len++] = ISS_I2C_RESTART;
+
+               if (pmsg->flags & I2C_M_RD) {
+                       dev->buffer[len++] = ISS_I2C_WRITE(1);
+                       dev->buffer[len++] = (pmsg->addr << 1) | 1;
+                       if (pmsg->flags & I2C_M_RECV_LEN) {
+                               /*
+                                * Block read returns receive length in 1st
+                                * byte. We must read up to 33 bytes of data
+                                * (len + 32) to avoid adapter hangup.
+                                */
+                               dev->buffer[len++] = ISS_I2C_READ(16);
+                               dev->buffer[len++] = ISS_I2C_READ(16);
+                               dev->buffer[len++] = ISS_I2C_NACK;
+                               dev->buffer[len++] = ISS_I2C_READ(1);
+                       } else {
+                               rlen = pmsg->len;
+                               /*
+                                * repeat read sequence until done or out of
+                                * buffer
+                                */
+                               if (rlen > ISS_MAX_TRANSFER_LEN - 8)
+                                       rlen = ISS_MAX_TRANSFER_LEN - 8;
+                               while (rlen > 16) {
+                                       dev->buffer[len++] = ISS_I2C_READ(16);
+                                       rlen -= 16;
+                               }
+                               if (rlen > 1)
+                                       dev->buffer[len++]
+                                         = ISS_I2C_READ(rlen - 1);
+                               dev->buffer[len++] = ISS_I2C_NACK;
+                               dev->buffer[len++] = ISS_I2C_READ(1);
+                       }
+               } else {
+                       dev->buffer[len++] = ISS_I2C_WRITE(1);
+                       dev->buffer[len++] = pmsg->addr << 1;
+                       /* Repeat write sequence until done */
+                       for (rlen = 0; rlen < pmsg->len; rlen += 16) {
+                               int clen = min(pmsg->len - rlen, 16);
+                               /*
+                                * Make sure we have enough buffer space. We
+                                * need two additional bytes, one for the write
+                                * command itself and one for the STOP command.
+                                */
+                               if (len + clen > ISS_MAX_TRANSFER_LEN - 2) {
+                                       ret = -EINVAL;
+                                       goto error;
+                               }
+                               dev->buffer[len++] = ISS_I2C_WRITE(clen);
+                               for (j = 0; j < clen; j++)
+                                       dev->buffer[len++]
+                                         = pmsg->buf[rlen + j];
+                       }
+               }
+       }
+       dev->buffer[len++] = ISS_I2C_STOP;
+
+       ret = devantech_usb_transfer(dev, len);
+       if (ret < 0)
+               goto error;
+
+       if (ret < 2) {
+               ret = -EIO;
+               goto error;
+       }
+
+       if (dev->buffer[0] == ISS_RESP_NACK) {
+               ret = dev->buffer[1] == ISS_RESP_DEV_ERROR ? -ENXIO : -EIO;
+               goto error;
+       }
+
+       /*
+        * Copy received data back into receive buffer. pmsg points to it.
+        */
+       if (pmsg->flags & I2C_M_RD) {
+               if (pmsg->flags & I2C_M_RECV_LEN) {
+                       rlen = dev->buffer[2];
+                       if (rlen == 0 || rlen > I2C_SMBUS_BLOCK_MAX ||
+                           rlen > ret - 2) {
+                               ret = -EPROTO;
+                               goto error;
+                       }
+                       pmsg->len += rlen;
+                       len = pmsg->len;
+               } else {
+                       len = ret - 2;
+               }
+               for (i = 0; i < len; i++)
+                       pmsg->buf[i] = dev->buffer[i + 2];
+       }
+       ret = 0;
+error:
+       return ret;
+}
+
+/*
+ * Return list of supported functionality.
+ */
+static u32 devantech_usb_func(struct i2c_adapter *a)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |
+              I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL;
+}
+
+static const struct i2c_algorithm devantech_usb_algorithm = {
+       .master_xfer = devantech_usb_xfer,
+       .functionality = devantech_usb_func,
+};
+
+/* device layer */
+
+static const struct usb_device_id devantech_iss_table[] = {
+       { USB_DEVICE(USB_VENDOR_ID_DEVANTECH, USB_DEVICE_ID_ISS) },
+       { }
+};
+
+MODULE_DEVICE_TABLE(usb, devantech_iss_table);
+
+static void devantech_iss_free(struct i2c_devantech_iss *dev)
+{
+       usb_put_dev(dev->usb_dev);
+       kfree(dev);
+}
+
+static int devantech_iss_probe(struct usb_interface *interface,
+                              const struct usb_device_id *id)
+{
+       struct i2c_devantech_iss *dev;
+       int ret;
+       struct usb_interface *usb_if;
+       struct usb_host_endpoint *ep_out, *ep_in;
+
+       /* allocate memory for our device state and initialize it */
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (dev == NULL) {
+               dev_err(&interface->dev, "no memory for device state\n");
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
+       dev->interface = interface;
+
+       /* setup i2c adapter description */
+       dev->adapter.owner = THIS_MODULE;
+       dev->adapter.class = I2C_CLASS_HWMON;
+       dev->adapter.algo = &devantech_usb_algorithm;
+       i2c_set_adapdata(&dev->adapter, dev);
+
+       snprintf(dev->adapter.name, sizeof(dev->adapter.name),
+                DRIVER_NAME " at bus %03d device %03d",
+                dev->usb_dev->bus->busnum, dev->usb_dev->devnum);
+
+       dev->adapter.dev.parent = &dev->interface->dev;
+
+       /* Only accept probe on control interface */
+       usb_if = usb_ifnum_to_if(dev->usb_dev, 0);
+       if (usb_if != interface) {
+               dev_err(&interface->dev, "not on control interface\n");
+               ret = -ENODEV;
+               goto error_free;
+       }
+
+       usb_if = usb_ifnum_to_if(dev->usb_dev, 1);
+       if (!usb_if) {
+               dev_err(&interface->dev, "no USB interface\n");
+               ret = -ENODEV;
+               goto error_free;
+       }
+       if (usb_interface_claimed(usb_if)) {
+               dev_err(&interface->dev, "USB interface busy\n");
+               ret = -EBUSY;
+               goto error_free;
+       }
+       if (usb_if->cur_altsetting->desc.bNumEndpoints < 2) {
+               dev_err(&interface->dev,
+                       "insufficient number of USB endpoints\n");
+               ret = -EINVAL;
+               goto error_free;
+       }
+
+       ep_out = &usb_if->cur_altsetting->endpoint[0];
+       ep_in = &usb_if->cur_altsetting->endpoint[1];
+       if (!ep_out || !ep_in) {
+               dev_err(&interface->dev, "missing USB endpoints\n");
+               ret = -EINVAL;
+               goto error_free;
+       }
+       dev->ep_out = ep_out->desc.bEndpointAddress;
+       dev->ep_in = ep_in->desc.bEndpointAddress;
+       dev->usb_if = usb_get_intf(usb_if);
+
+       /*
+        * We need to claim the data interface to prevent other drivers
+        * from accessing and registering it.
+        */
+       ret = usb_driver_claim_interface(&devantech_iss_driver,
+                                        dev->usb_if, dev);
+       if (ret < 0) {
+               dev_err(&usb_if->dev, "failed to claim interface\n");
+               goto error_put;
+       }
+
+       /* save our data pointer in this interface device */
+       usb_set_intfdata(interface, dev);
+
+       /* initialize devantech i2c interface */
+       ret = devantech_init(dev);
+       if (ret < 0) {
+               dev_err(&interface->dev, "failed to initialize adapter\n");
+               goto error_release;
+       }
+
+       /* and finally attach to i2c layer */
+       ret = i2c_add_adapter(&dev->adapter);
+       if (ret < 0) {
+               dev_err(&interface->dev, "failed to add I2C adapter\n");
+               goto error_release;
+       }
+
+       dev_info(&interface->dev, "connected\n");
+       return 0;
+
+error_release:
+       usb_set_intfdata(interface, NULL);
+       usb_set_intfdata(dev->usb_if, NULL);
+       usb_driver_release_interface(&devantech_iss_driver, dev->usb_if);
+error_put:
+       usb_put_intf(dev->usb_if);
+error_free:
+       devantech_iss_free(dev);
+error:
+       return ret;
+}
+
+static void devantech_iss_disconnect(struct usb_interface *interface)
+{
+       struct i2c_devantech_iss *dev = usb_get_intfdata(interface);
+       struct usb_host_interface *alt = interface->cur_altsetting;
+
+       if (alt->desc.bInterfaceNumber)
+               return;
+
+       i2c_del_adapter(&dev->adapter);
+       usb_set_intfdata(interface, NULL);
+       usb_set_intfdata(dev->usb_if, NULL);
+       usb_driver_release_interface(&devantech_iss_driver, dev->usb_if);
+       usb_put_intf(dev->usb_if);
+       devantech_iss_free(dev);
+
+       dev_info(&interface->dev, "disconnected\n");
+}
+
+static struct usb_driver devantech_iss_driver = {
+       .name = DRIVER_NAME,
+       .probe = devantech_iss_probe,
+       .disconnect = devantech_iss_disconnect,
+       .id_table = devantech_iss_table,
+};
+
+static int __init devantech_iss_init(void)
+{
+       /* register this driver with the USB subsystem */
+       return usb_register(&devantech_iss_driver);
+}
+
+static void __exit devantech_iss_exit(void)
+{
+       /* deregister this driver with the USB subsystem */
+       usb_deregister(&devantech_iss_driver);
+}
+
+module_init(devantech_iss_init);
+module_exit(devantech_iss_exit);
+
+MODULE_AUTHOR("Guenter Roeck <[email protected]>");
+MODULE_DESCRIPTION(DRIVER_NAME " driver");
+MODULE_LICENSE("GPL");
-- 
1.7.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to