[PATCH] i2c/busses: Driver for Devantech USB-ISS I2C adapter

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

Signed-off-by: Guenter Roeck <guenter.roeck@xxxxxxxxxxxx>
---
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 <guenter.roeck@xxxxxxxxxxxx>
+
+
+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 <guenter.roeck@xxxxxxxxxxxx>");
+MODULE_DESCRIPTION(DRIVER_NAME " driver");
+MODULE_LICENSE("GPL");
-- 
1.7.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux