[PATCH] spi: Added driver for CP2130 USB-to-SPI bridge

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

 



This is a new driver for the Silicon Labs CP2130 USB-to-SPI bridge. It
is meant to be used for hotplug devices, e. g. the CP2130 combined with
the MCP251x CAN controllers.

Configuration happens when plugged (e. g. from udev), the CP2130 driver
provides sysfs files to control the bridge setup. From there you can
configured SPI bus settings, the interrupt pin, the attached
devices. You can also provide platform data in binary format if the
attached chip requires this.

The OTP ROM can be used to setup a specific product/vendor id to
simplify different device setup recognition.

Signed-off-by: Jochen Henneberg <jh@xxxxxxxxxxxxxxxxxxxxxxxxxx>
---
 drivers/spi/Kconfig      |    6 +
 drivers/spi/Makefile     |    1 +
 drivers/spi/spi-cp2130.c | 1672 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1679 insertions(+)
 create mode 100644 drivers/spi/spi-cp2130.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index f761655..a58684c 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -205,6 +205,12 @@ config SPI_COLDFIRE_QSPI
 	  This enables support for the Coldfire QSPI controller in master
 	  mode.
 
+config SPI_CP2130
+	tristate "CP2130 USB-SPI bridge"
+	depends on USB
+	help
+	  This enables support for Silicon Labs CP2130 USB-to-SPI bridge.
+
 config SPI_DAVINCI
 	tristate "Texas Instruments DaVinci/DA8x/OMAP-L/AM1x SoC SPI controller"
 	depends on ARCH_DAVINCI || ARCH_KEYSTONE
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index d8fc03c..7ec9204 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_SPI_BUTTERFLY)		+= spi-butterfly.o
 obj-$(CONFIG_SPI_CADENCE)		+= spi-cadence.o
 obj-$(CONFIG_SPI_CLPS711X)		+= spi-clps711x.o
 obj-$(CONFIG_SPI_COLDFIRE_QSPI)		+= spi-coldfire-qspi.o
+obj-$(CONFIG_SPI_CP2130)		+= spi-cp2130.o
 obj-$(CONFIG_SPI_DAVINCI)		+= spi-davinci.o
 obj-$(CONFIG_SPI_DLN2)			+= spi-dln2.o
 obj-$(CONFIG_SPI_DESIGNWARE)		+= spi-dw.o
diff --git a/drivers/spi/spi-cp2130.c b/drivers/spi/spi-cp2130.c
new file mode 100644
index 0000000..99a0254
--- /dev/null
+++ b/drivers/spi/spi-cp2130.c
@@ -0,0 +1,1672 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Kernel driver for Silicon Labs CP2130 USB-to-SPI bridge.
+ *
+ * Copyright (C) 2019 Jochen Henneberg (jh@xxxxxxxxxxxxxxxxxxxxxxxxxx)
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/spi/spi.h>
+#include <linux/workqueue.h>
+#include <linux/string.h>
+#include <asm/byteorder.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/version.h>
+
+#define USB_DEVICE_ID_CP2130         0x87a0
+#define USB_VENDOR_ID_CYGNAL         0x10c4
+
+#define CP2130_NUM_GPIOS             11
+#define CP2130_IRQ_POLL_INTERVAL     (1 * 1000 * 1000) /* us */
+
+#define CP2130_MAX_USB_PACKET_SIZE   64
+
+#define CP2130_CMD_READ              0x00
+#define CP2130_CMD_WRITE             0x01
+#define CP2130_CMD_WRITEREAD         0x02
+#define CP2130_BULK_OFFSET_CMD       2
+#define CP2130_BULK_OFFSET_LENGTH    4
+#define CP2130_BULK_OFFSET_DATA      8
+#define CP2130_BULK_HEADER_SIZE      (CP2130_BULK_OFFSET_DATA)
+
+#define CP2130_BREQ_GET_GPIO_VALUES  0x20
+#define CP2130_BREQ_SET_GPIO_MODE    0x23
+#define CP2130_BREQ_GET_GPIO_CS      0x24
+#define CP2130_BREQ_SET_GPIO_CS      0x25
+#define CP2130_BREQ_GET_SPI_WORD     0x30
+#define CP2130_BREQ_SET_SPI_WORD     0x31
+#define CP2130_BREQ_GET_SPI_DELAY    0x32
+#define CP2130_BREQ_SET_SPI_DELAY    0x33
+#define CP2130_BREQ_GET_LOCK_BYTE    0x6E
+#define CP2130_BREQ_GET_PIN_CONFIG   0x6C
+#define CP2130_BREQ_SET_PIN_CONFIG   0x6D
+
+#define CP2130_BREQ_MEMORY_KEY       0xA5F1
+
+/* cp2130 attached chip */
+struct cp2130_channel {
+	int updated;
+	int cs_en; /* chip-select enable mode */
+	int irq_pin;
+	int clock_phase;
+	int polarity;
+	int cs_pin_mode;
+	int clock_freq; /* spi clock frequency */
+	int delay_mask; /* cs enable, pre-deassert,
+			 * post assert and inter-byte delay enable
+			 */
+	int inter_byte_delay;
+	int pre_deassert_delay;
+	int post_assert_delay;
+	char *modalias;
+	void *pdata;
+	struct spi_device *chip;
+};
+
+/* cp2130 OTP ROM */
+struct cp2130_otprom {
+	int lock_byte;
+	int pin_config[CP2130_NUM_GPIOS];
+	int suspend_level;
+	int suspend_mode;
+	int wakeup_mask;
+	int wakeup_match;
+	int divider;
+};
+
+struct cp2130_gpio_irq {
+	struct irq_domain *irq_domain;
+	struct mutex      irq_lock;
+	int               virq[CP2130_NUM_GPIOS];
+	u16               irq_mask;
+};
+
+/* cp2130 device structure */
+struct cp2130_device {
+	struct usb_device *udev;
+	struct usb_interface *intf;
+	struct spi_master *spi_master;
+
+	struct mutex chn_config_lock;
+	struct mutex otprom_lock;
+	struct mutex usb_bus_lock;
+
+	struct work_struct update_chn_config;
+	struct work_struct read_chn_config;
+	struct work_struct update_otprom;
+	struct work_struct read_otprom;
+	struct cp2130_channel chn_configs[CP2130_NUM_GPIOS];
+	struct cp2130_otprom otprom_config;
+
+	struct cp2130_gpio_irq irq_chip;
+	struct work_struct irq_work;
+
+	struct gpio_chip gpio_chip;
+	char *gpio_names[CP2130_NUM_GPIOS];
+	u8 gpio_states[2];
+
+	int current_channel;
+
+	int irq_poll_interval;
+
+	u8 *usb_xfer;
+};
+
+/* Prototypes */
+static int cp2130_probe(struct usb_interface *intf,
+			const struct usb_device_id *id);
+static void cp2130_disconnect(struct usb_interface *intf);
+
+static const struct usb_device_id cp2130_devices[] = {
+	{ USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CP2130) },
+	{ }
+};
+
+/* USB device functions */
+static struct usb_driver cp2130_driver = {
+	.name                 = "cp2130",
+	.probe                = cp2130_probe,
+	.disconnect           = cp2130_disconnect,
+	.suspend              = NULL,
+	.resume               = NULL,
+	.reset_resume         = NULL,
+	.id_table             = cp2130_devices,
+	.supports_autosuspend = 0,
+};
+
+static int __init cp2130_init(void)
+{
+	int ret;
+
+	pr_debug("%s", __func__);
+	ret = usb_register_driver(&cp2130_driver, THIS_MODULE, "cp2130");
+	if (ret)
+		pr_err("can't register cp2130 driver");
+
+	return ret;
+}
+
+static void __exit cp2130_exit(void)
+{
+	pr_debug("%s", __func__);
+	usb_deregister(&cp2130_driver);
+}
+
+static int cp2130_spi_setup(struct spi_device *spi)
+{
+	return 0;
+}
+
+static void cp2130_spi_cleanup(struct spi_device *spi)
+{
+}
+
+static char *cp2130_spi_speed_to_string(int reg_val)
+{
+	switch (reg_val) {
+	case 0: return "12 MHz   ";
+	case 1: return "6 MHz    ";
+	case 2: return "3 MHz    ";
+	case 3: return "1.5 MHz  ";
+	case 4: return "750 kHz  ";
+	case 5: return "375 kHz  ";
+	case 6: return "187.5 kHz";
+	case 7: return "93.8 kHz ";
+	}
+	return "";
+}
+
+static unsigned int cp2130_spi_speed_to_nsec(int reg_val)
+{
+#define USEC (1 * 1000 * 1000)
+#define FREQ_MHZ(n) (n * 1000)
+#define FREQ_KHZ(n) (n)
+
+	switch (reg_val) {
+	case 0: return USEC / FREQ_MHZ(12);
+	case 1: return USEC / FREQ_MHZ(6);
+	case 2: return USEC / FREQ_MHZ(3);
+	case 3: return USEC / FREQ_KHZ(1500);
+	case 4: return USEC / FREQ_KHZ(750);
+	case 5: return USEC / FREQ_KHZ(375);
+	case 6: return USEC / FREQ_KHZ(187);
+	case 7: return USEC / FREQ_KHZ(93);
+	}
+	return USEC / FREQ_KHZ(93);
+}
+
+static ssize_t channel_config_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct usb_interface *intf;
+	struct usb_device *udev;
+	struct cp2130_device *chip;
+	struct cp2130_channel *chn;
+	int i = 0;
+	char out[256];
+	ssize_t ret;
+
+	intf = to_usb_interface(dev);
+	if (!intf)
+		return -EFAULT;
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return -EFAULT;
+
+	udev = usb_get_dev(interface_to_usbdev(intf));
+	if (!udev)
+		return -EFAULT;
+
+	ret = sprintf(out, "channel\tcs_mode\tirq_pin\tclock_phase\tpolarity"
+		"\tcs_pin_mode\tclock_freq\tdelay_mask"
+		"\tinter_byte_delay\tpre_delay\tpost_delay"
+		"\tmod_alias\n");
+	strcat(buf, out);
+	mutex_lock(&chip->chn_config_lock);
+	for (i = 0; i < CP2130_NUM_GPIOS; i++) {
+		chn = &chip->chn_configs[i];
+		ret += sprintf(out, "%d\t%d\t%d\t%d\t\t%d\t\t%d\t\t%s\t%d"
+			"\t\t%d\t\t\t%d\t\t%d\t\t'%s'\n",
+			i, chn->cs_en, chn->irq_pin, chn->clock_phase,
+			chn->polarity, chn->cs_pin_mode,
+			cp2130_spi_speed_to_string(chn->clock_freq),
+			chn->delay_mask, chn->inter_byte_delay,
+			chn->pre_deassert_delay, chn->post_assert_delay,
+			chn->modalias);
+		strcat(buf, out);
+	}
+	mutex_unlock(&chip->chn_config_lock);
+
+	return ret;
+}
+
+static ssize_t channel_config_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct cp2130_channel chn;
+	struct usb_interface *intf;
+	struct usb_device *udev;
+	struct cp2130_device *chip;
+	char *lbuf;
+	char *del; /* delimiter ',' */
+	char *pos; /* current item position in buffer */
+	int i, ret, eos, chn_id;
+
+	intf = to_usb_interface(dev);
+	if (!intf)
+		return -EFAULT;
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return -EFAULT;
+
+	udev = usb_get_dev(interface_to_usbdev(intf));
+	if (!udev)
+		return -EFAULT;
+
+	dev_dbg(&udev->dev, "received '%s' from 'channel_config'", buf);
+
+	if (!count)
+		return -EINVAL;
+
+	lbuf = kstrdup(buf, GFP_KERNEL);
+	if (!lbuf)
+		return -ENOMEM;
+
+	pos = lbuf;
+	eos = i = 0;
+	do {
+		/* search for next separator or end-of-string */
+		del = strchr(pos, ',');
+		if (!del)
+			del = strchr(pos, '\0');
+		if (!del) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (*del == '\0')
+			eos = 1;
+		*del = '\0';
+
+		dev_dbg(&udev->dev, "parsing: %s(%d)", pos, i);
+
+		/* parse current item */
+		switch (i) {
+		case 0: /* channel id */
+			ret = kstrtoint(pos, 10, &chn_id);
+			ret |= (chn_id < 0 || chn_id > 10) ? -EINVAL : 0;
+			if (ret)
+				goto out;
+			break;
+		case 1: /* chip-select enable */
+			ret = kstrtoint(pos, 10, &chn.cs_en);
+			ret |= (chn.cs_en < 0 || chn.cs_en > 2) ? -EINVAL : 0;
+			if (ret)
+				goto out;
+			break;
+		case 2: /* irq pin */
+			ret = kstrtoint(pos, 10, &chn.irq_pin);
+			ret |= (chn.irq_pin > 10) ? -EINVAL : 0;
+			if (ret)
+				goto out;
+			break;
+		case 3: /* clock phase */
+			ret = kstrtoint(pos, 10, &chn.clock_phase);
+			if (ret)
+				goto out;
+			chn.clock_phase = !!chn.clock_phase;
+			break;
+		case 4: /* polarity */
+			ret = kstrtoint(pos, 10, &chn.polarity);
+			if (ret)
+				goto out;
+			chn.polarity = !!chn.polarity;
+			break;
+		case 5: /* chip-select pin mode */
+			ret = kstrtoint(pos, 10, &chn.cs_pin_mode);
+			if (ret)
+				goto out;
+			chn.cs_pin_mode = !!chn.cs_pin_mode;
+			break;
+		case 6: /* spi clock frequency */
+			ret = kstrtoint(pos, 10, &chn.clock_freq);
+			ret |= (chn.clock_freq < 0 || chn.clock_freq > 7) ?
+				-EINVAL : 0;
+			if (ret)
+				goto out;
+			break;
+		case 7: /* delay mask */
+			ret = kstrtoint(pos, 10, &chn.delay_mask);
+			ret |= (chn.delay_mask < 0 || chn.delay_mask > 15) ?
+				-EINVAL : 0;
+			if (ret)
+				goto out;
+			break;
+		case 8: /* inter-byte delay */
+			ret = kstrtoint(pos, 10, &chn.inter_byte_delay);
+			ret |= (chn.inter_byte_delay < 0 ||
+				chn.inter_byte_delay > 0xffff) ?
+				-EINVAL : 0;
+			if (ret)
+				goto out;
+			break;
+		case 9: /* pre-deassert delay */
+			ret = kstrtoint(pos, 10, &chn.pre_deassert_delay);
+			ret |= (chn.pre_deassert_delay < 0 ||
+				chn.pre_deassert_delay > 0xffff) ?
+				-EINVAL : 0;
+			if (ret)
+				goto out;
+			break;
+		case 10: /* post-assert delay */
+			ret = kstrtoint(pos, 10, &chn.post_assert_delay);
+			ret |= (chn.post_assert_delay < 0 ||
+				chn.post_assert_delay > 0xffff) ?
+				-EINVAL : 0;
+			if (ret)
+				goto out;
+			break;
+		case 11: /* modalias */
+			chn.modalias = kstrdup(pos, GFP_KERNEL);
+			if (!chn.modalias) {
+				ret = -EINVAL;
+				goto out;
+			}
+			break;
+		default:
+			ret = -EINVAL;
+			goto out;
+		}
+
+		/* move the parser position forward */
+		pos = ++del;
+		i++;
+	} while (!eos);
+
+	if (i < 11) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	chn.updated = 1;
+	mutex_lock(&chip->chn_config_lock);
+	if (chip->chn_configs[chn_id].updated) {
+		ret = -EINVAL;
+		goto unlock;
+	}
+	/* preserve pdata */
+	chn.pdata = chip->chn_configs[chn_id].pdata;
+
+	memcpy(&chip->chn_configs[chn_id], &chn, sizeof(chn));
+	schedule_work(&chip->update_chn_config);
+	ret = count;
+
+unlock:
+	mutex_unlock(&chip->chn_config_lock);
+
+out:
+	kfree(lbuf);
+	return ret;
+}
+static DEVICE_ATTR_RW(channel_config);
+
+
+static ssize_t channel_pdata_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct usb_interface *intf;
+	struct usb_device *udev;
+	struct cp2130_device *chip;
+	struct cp2130_channel *chn;
+	int chn_id;
+	int ret;
+
+	intf = to_usb_interface(dev);
+	if (!intf)
+		return -EFAULT;
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return -EFAULT;
+
+	udev = usb_get_dev(interface_to_usbdev(intf));
+	if (!udev)
+		return -EFAULT;
+
+	/* first byte is channel id,
+	 * then follows binary platform data
+	 */
+	if (count < 2)
+		return -EINVAL;
+
+	chn_id = buf[0];
+	dev_dbg(&udev->dev, "received pdata for channel %u", chn_id);
+
+	if (chn_id < 0 || chn_id >= CP2130_NUM_GPIOS)
+		return -EINVAL;
+
+	chn = &chip->chn_configs[chn_id];
+
+	/* pdata can be set only once */
+	if (chn->pdata) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	dev_dbg(&udev->dev, "set pdata for channel %u", chn_id);
+
+	mutex_lock(&chip->chn_config_lock);
+	chn->pdata = kzalloc(count - 1, GFP_KERNEL);
+	if (!chn->pdata) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if (!memcpy(chn->pdata, buf + 1, count - 1)) {
+		kfree(chn->pdata);
+		chn->pdata = NULL;
+		ret = -EIO;
+		goto out;
+	}
+
+	ret = count;
+out:
+	mutex_unlock(&chip->chn_config_lock);
+	return ret;
+}
+static DEVICE_ATTR_WO(channel_pdata);
+
+static char *cp2130_pin_mode_to_string(int val)
+{
+	switch (val) {
+	case 0:  return "in        ";
+	case 1:  return "open-drain";
+	case 2:  return "push-pull ";
+	case 3:  return "nCS       ";
+	default: return "special   ";
+	}
+	return "";
+}
+
+static ssize_t otp_rom_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct usb_interface *intf;
+	struct usb_device *udev;
+	struct cp2130_device *chip;
+	int pin;
+	int i = 0;
+	char out[256];
+	ssize_t ret;
+
+	intf = to_usb_interface(dev);
+	if (!intf)
+		return -EFAULT;
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return -EFAULT;
+
+	udev = usb_get_dev(interface_to_usbdev(intf));
+	if (!udev)
+		return -EFAULT;
+
+	mutex_lock(&chip->otprom_lock);
+
+	ret = sprintf(out, "OTP lock status (ro):"
+		"\nvid\t\tpid\t\tmax_power\tpower_mode"
+		"\tversion\t\tmanu_2\t\tmanu_1"
+		"\t\tpriority\tproduct_1\tproduct_2\tserial"
+		"\t\tpin_config\n");
+	strcat(buf, out);
+	for (i = 0; i < 12; i++) {
+		if (!!((chip->otprom_config.lock_byte >> i) & 1))
+			ret += sprintf(out, "unlocked\t");
+		else
+			ret += sprintf(out, "locked\t\t");
+		strcat(buf, out);
+	}
+	ret += sprintf(out, "\n\nOTP pin configuration (rw):\n");
+	strcat(buf, out);
+
+	for (i = 0; i < CP2130_NUM_GPIOS; i++) {
+		ret += sprintf(out, "pin %d\t\t", i);
+		strcat(buf, out);
+	}
+	strcat(buf, "\n");
+	ret++;
+	for (i = 0; i < CP2130_NUM_GPIOS; i++) {
+		pin = chip->otprom_config.pin_config[i];
+		ret += sprintf(out, "%s\t", cp2130_pin_mode_to_string(pin));
+		strcat(buf, out);
+	}
+
+	ret += sprintf(out, "\n\nOTP pin configuration extra data(ro):"
+		"\nsuspend_level\tsuspend_mode\twakeup_mask\twakeup_match"
+		"\tdivider\n");
+	strcat(buf, out);
+	ret += sprintf(out, "%d\t\t%d\t\t%d\t\t%d\t\t%d\n",
+		chip->otprom_config.suspend_level,
+		chip->otprom_config.suspend_mode,
+		chip->otprom_config.wakeup_mask,
+		chip->otprom_config.wakeup_match,
+		chip->otprom_config.divider);
+	strcat(buf, out);
+
+	mutex_unlock(&chip->otprom_lock);
+
+	return ret;
+}
+
+static ssize_t otp_rom_store(struct device *dev,
+			struct device_attribute *attr,
+			const char *buf, size_t count)
+{
+	int pin_config[CP2130_NUM_GPIOS];
+	struct usb_interface *intf;
+	struct usb_device *udev;
+	struct cp2130_device *chip;
+	char *lbuf;
+	char *del; /* delimiter ',' */
+	char *pos; /* current item position in buffer */
+	int i, ret, eos;
+
+	intf = to_usb_interface(dev);
+	if (!intf)
+		return -EFAULT;
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return -EFAULT;
+
+	udev = usb_get_dev(interface_to_usbdev(intf));
+	if (!udev)
+		return -EFAULT;
+
+	dev_dbg(&udev->dev, "received '%s' from 'otp_rom'", buf);
+
+	if (!count)
+		return -EINVAL;
+
+	lbuf = kstrdup(buf, GFP_KERNEL);
+	if (!lbuf)
+		return -ENOMEM;
+
+	pos = lbuf;
+	eos = i = 0;
+	do {
+		/* search for next separator or end-of-string */
+		del = strchr(pos, ',');
+		if (!del)
+			del = strchr(pos, '\0');
+		if (!del) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (*del == '\0')
+			eos = 1;
+		*del = '\0';
+
+		dev_dbg(&udev->dev, "parsing: %s(%d)", pos, i);
+
+		if (i == CP2130_NUM_GPIOS) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		/* parse current item */
+		if (*pos == 'x') {
+			pin_config[i] = -1;
+		} else {
+			ret = kstrtoint(pos, 10, &(pin_config[i]));
+			ret |= (pin_config[i] < 0 ||
+				pin_config[i] > 3) ?
+				-EINVAL : 0;
+			if (ret)
+				goto out;
+		}
+
+		/* move the parser position forward */
+		pos = ++del;
+		i++;
+	} while (!eos);
+
+	if (i < 11) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	mutex_lock(&chip->otprom_lock);
+
+	for (i = 0; i < CP2130_NUM_GPIOS; i++) {
+		if (pin_config[i] < 0)
+			continue;
+		pr_info("cp2130 read OTP: %d", pin_config[i]);
+		chip->otprom_config.pin_config[i] = pin_config[i];
+	}
+	schedule_work(&chip->update_otprom);
+	ret = count;
+
+	mutex_unlock(&chip->otprom_lock);
+
+out:
+	kfree(lbuf);
+	return ret;
+}
+static DEVICE_ATTR_RW(otp_rom);
+
+static ssize_t irq_poll_interval_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct usb_interface *intf;
+	struct usb_device *udev;
+	struct cp2130_device *chip;
+	ssize_t ret;
+
+	intf = to_usb_interface(dev);
+	if (!intf)
+		return -EFAULT;
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return -EFAULT;
+
+	udev = usb_get_dev(interface_to_usbdev(intf));
+	if (!udev)
+		return -EFAULT;
+
+	ret = sprintf(buf, "%d us\n", chip->irq_poll_interval);
+
+	return ret;
+}
+
+static ssize_t irq_poll_interval_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct usb_interface *intf;
+	struct usb_device *udev;
+	struct cp2130_device *chip;
+	int ret, interval;
+
+	intf = to_usb_interface(dev);
+	if (!intf)
+		return -EFAULT;
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return -EFAULT;
+
+	udev = usb_get_dev(interface_to_usbdev(intf));
+	if (!udev)
+		return -EFAULT;
+
+	ret = kstrtoint(buf, 10, &interval);
+
+	/* we only allow numbers and nothing below 10us */
+	if (ret || interval < 10)
+		return -EINVAL;
+
+	chip->irq_poll_interval = interval;
+
+	return count;
+}
+static DEVICE_ATTR_RW(irq_poll_interval);
+
+static int cp2130_transfer_bulk_message(struct spi_master *master,
+					struct spi_transfer *xfer,
+					unsigned int pos)
+{
+	int header_size, data_size, len, tx_len;
+	int ret = -EINVAL;
+	unsigned int limit;
+	unsigned int recv_pipe, xmit_pipe;
+	struct cp2130_device *dev =
+		(struct cp2130_device *) spi_master_get_devdata(master);
+	bool first_frame = !pos;
+
+	xmit_pipe = usb_sndbulkpipe(dev->udev, 0x01);
+	recv_pipe = usb_rcvbulkpipe(dev->udev, 0x82);
+
+	dev_dbg(&master->dev, "recv/xmit pipes: %u / %u",
+		recv_pipe, xmit_pipe);
+
+	/* if this is the first packet we need to prepare the header,
+	 * if not we can use all available space in the packet
+	 */
+	if (first_frame) {
+		/* zero the transfer buffer header, this implicitely
+		 * initializes the reserved fields
+		 */
+		memset(dev->usb_xfer, 0, CP2130_BULK_HEADER_SIZE);
+
+		/* init length field */
+		*((u32 *) (dev->usb_xfer + CP2130_BULK_OFFSET_LENGTH)) =
+			__cpu_to_le32(xfer->len);
+
+		header_size = CP2130_BULK_HEADER_SIZE;
+
+		/* now set the command type according to the SPI
+		 * message setup
+		 */
+		if (xfer->tx_buf && xfer->rx_buf) {
+			/* Simultaneous SPI write and read */
+			limit = CP2130_MAX_USB_PACKET_SIZE -
+				CP2130_BULK_HEADER_SIZE;
+			data_size = min(limit, xfer->len - pos);
+			tx_len = header_size + data_size;
+			dev->usb_xfer[CP2130_BULK_OFFSET_CMD] =
+				CP2130_CMD_WRITEREAD;
+		} else if (xfer->tx_buf) {
+			/* SPI write only */
+			limit = CP2130_MAX_USB_PACKET_SIZE -
+				CP2130_BULK_HEADER_SIZE;
+			data_size = min(limit, xfer->len - pos);
+			tx_len = header_size + data_size;
+			dev->usb_xfer[CP2130_BULK_OFFSET_CMD] =
+				CP2130_CMD_WRITE;
+		} else if (xfer->rx_buf) {
+			/* SPI read only */
+			limit = CP2130_MAX_USB_PACKET_SIZE;
+			data_size = min(limit, xfer->len - pos);
+			tx_len = CP2130_BULK_HEADER_SIZE;
+			dev->usb_xfer[CP2130_BULK_OFFSET_CMD] =
+				CP2130_CMD_READ;
+		}
+	} else {
+		header_size = 0;
+		limit = CP2130_MAX_USB_PACKET_SIZE;
+		data_size = min(limit, xfer->len - pos);
+		tx_len = header_size + data_size;
+	}
+
+	/* copy chunk of SPI tx data */
+	if (xfer->tx_buf)
+		memcpy(dev->usb_xfer + header_size, xfer->tx_buf + pos,
+			data_size);
+
+	/* prepare URB and submit sync CP2130 / AN792 p.7: 'any
+	 * previous data transfer command must complete before another
+	 * data transfer command is issued', so there is no advantage
+	 * from using the async USB API
+	 */
+	if (xfer->tx_buf && xfer->rx_buf) {
+		/* simultaneous SPI write and read */
+		ret = usb_bulk_msg(dev->udev, xmit_pipe, dev->usb_xfer,
+				tx_len, &len, 200);
+		dev_dbg(&master->dev,
+			"write-read - usb tx phase: ret=%d, wrote %d/%d",
+			ret, len, tx_len);
+		if (ret)
+			goto out;
+		ret = usb_bulk_msg(dev->udev, recv_pipe, xfer->rx_buf + pos,
+				data_size, &len, 200);
+		dev_dbg(&master->dev,
+			"write-read - usb rx phase: ret=%d, read %d/%d",
+			ret, len, data_size);
+	} else if (xfer->tx_buf) {
+		/* SPI write only */
+		ret = usb_bulk_msg(dev->udev, xmit_pipe, dev->usb_xfer,
+				tx_len, &len, 200);
+		dev_dbg(&master->dev,
+			"write - usb tx phase: ret=%d, wrote %d/%d",
+			ret, len, tx_len);
+		len -= header_size;
+	} else if (xfer->rx_buf) {
+		/* SPI read only */
+		if (first_frame) { /* write read request only for first frame */
+			ret = usb_bulk_msg(dev->udev, xmit_pipe, dev->usb_xfer,
+					tx_len, &len, 200);
+			dev_dbg(&master->dev,
+				"read - usb tx phase: ret=%d, wrote %d/%d",
+				ret, len, tx_len);
+			if (ret)
+				goto out;
+		}
+		ret = usb_bulk_msg(dev->udev, recv_pipe, xfer->rx_buf + pos,
+				data_size, &len, 200);
+		dev_dbg(&master->dev,
+			"read - usb rx phase: ret=%d, read %d/%d",
+			ret, len, data_size);
+	}
+
+out:
+	return (!ret ? len : ret);
+}
+
+static int cp2130_spi_transfer_one_message(struct spi_master *master,
+					struct spi_message *mesg)
+{
+	struct spi_transfer *xfer;
+	struct cp2130_device *dev =
+		(struct cp2130_device *) spi_master_get_devdata(master);
+	int ret = 0, chn_id;
+	struct cp2130_channel *chn;
+	unsigned int xmit_ctrl_pipe;
+	unsigned int pos, transfer_delay;
+
+	dev_dbg(&master->dev, "spi transfer one message");
+
+	/* search for spi setup of this device */
+	for (chn_id = 0; chn_id < CP2130_NUM_GPIOS; chn_id++) {
+		chn = &dev->chn_configs[chn_id];
+		if (chn->chip == mesg->spi)
+			break;
+	}
+
+	if (chn_id == CP2130_NUM_GPIOS) {
+		ret = -ENODEV;
+		goto out_notfound;
+	}
+
+	mutex_lock(&dev->usb_bus_lock);
+
+	xmit_ctrl_pipe = usb_sndctrlpipe(dev->udev, 0);
+	if (chn_id != dev->current_channel) {
+		dev_dbg(&dev->udev->dev, "load setup for channel %d", chn_id);
+		dev->usb_xfer[0] = chn_id;
+		dev->usb_xfer[1] = chn->cs_en;
+		ret = usb_control_msg(
+			dev->udev, xmit_ctrl_pipe,
+			CP2130_BREQ_SET_GPIO_CS,
+			USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+			0, 0,
+			dev->usb_xfer, 2, 200);
+		if (ret != 2)
+			goto out;
+
+		dev->current_channel = chn_id;
+	}
+
+	/* iterate through all transfers */
+	list_for_each_entry(xfer, &mesg->transfers, transfer_list) {
+		dev_dbg(&master->dev, "spi transfer stats: %p, %p, %d",
+			xfer->tx_buf, xfer->rx_buf, xfer->len);
+
+		/* empty transfer */
+		if (!xfer->tx_buf && !xfer->rx_buf) {
+			udelay(xfer->delay_usecs);
+			continue;
+		}
+
+		/* be prepared for messages > 64 byte */
+		pos = 0;
+
+		/* split the xfer data into chunks for 64 bytes and submit */
+		while (pos < xfer->len) {
+			ret = cp2130_transfer_bulk_message(master, xfer, pos);
+
+			/* wait until all bytes are sent on the SPI bus */
+			transfer_delay = cp2130_spi_speed_to_nsec(
+				chn->clock_freq) /* nsec per bit */
+				* ret * 8 /* number of bits */
+				* 2 /* headroom */
+				/ 1000 /* to usec */;
+			udelay(transfer_delay);
+
+			if (ret < 0)
+				break;
+			pos += ret;
+		}
+		udelay(xfer->delay_usecs);
+		mesg->actual_length += xfer->len;
+	}
+
+out:
+	mutex_unlock(&dev->usb_bus_lock);
+out_notfound:
+	mesg->status = (ret < 0) ? ret : 0;
+	if (mesg->status)
+		dev_err(&master->dev, "USB transfer failed with %d", ret);
+	spi_finalize_current_message(master); /* signal done to queue */
+	return mesg->status;
+}
+
+static int cp2130_irq_from_pin(struct cp2130_device *dev, int pin)
+{
+	return dev->irq_chip.virq[pin];
+}
+
+static void cp2130_update_channel_config(struct work_struct *work)
+{
+	int i;
+	int ret;
+	unsigned int xmit_pipe;
+	struct cp2130_device *dev = container_of(work,
+						struct cp2130_device,
+						update_chn_config);
+	struct cp2130_channel *chn;
+
+	dev_dbg(&dev->udev->dev, "control pipes: %u, %u",
+		usb_sndctrlpipe(dev->udev, 0),
+		usb_rcvctrlpipe(dev->udev, 0));
+
+	xmit_pipe = usb_sndctrlpipe(dev->udev, 0);
+
+	mutex_lock(&dev->chn_config_lock);
+	for (i = 0; i < CP2130_NUM_GPIOS; i++) {
+		chn = &dev->chn_configs[i];
+		if (!chn->updated)
+			continue;
+
+		if (chn->updated > 1)
+			continue;
+
+		chn->updated++;
+
+		dev_dbg(&dev->udev->dev, "update config of channel %d", i);
+		dev->usb_xfer[0] = i;
+
+		mutex_lock(&dev->usb_bus_lock);
+
+		dev_dbg(&dev->udev->dev, "set spi word");
+		dev->usb_xfer[1] = (chn->clock_freq  << 0) |
+			(chn->cs_pin_mode << 3) |
+			(chn->polarity    << 4) |
+			(chn->clock_phase << 5);
+
+		ret = usb_control_msg(
+			dev->udev, xmit_pipe,
+			CP2130_BREQ_SET_SPI_WORD,
+			USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+			0, 0,
+			dev->usb_xfer, 2, 200);
+		if (ret < 2)
+			goto error_unlock;
+
+		dev_dbg(&dev->udev->dev, "set spi delay");
+		dev->usb_xfer[1] = chn->delay_mask;
+
+		dev->usb_xfer[2] = (chn->inter_byte_delay & 0xff00) >> 8;
+		dev->usb_xfer[3] = (chn->inter_byte_delay & 0x00ff) >> 0;
+
+		dev->usb_xfer[4] = (chn->post_assert_delay & 0xff00) >> 8;
+		dev->usb_xfer[5] = (chn->post_assert_delay & 0x00ff) >> 0;
+
+		dev->usb_xfer[6] = (chn->pre_deassert_delay & 0xff00) >> 8;
+		dev->usb_xfer[7] = (chn->pre_deassert_delay & 0x00ff) >> 0;
+		ret = usb_control_msg(
+			dev->udev, xmit_pipe,
+			CP2130_BREQ_SET_SPI_DELAY,
+			USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+			0, 0,
+			dev->usb_xfer, 8, 200);
+		if (ret < 8)
+			goto error_unlock;
+
+		/* configure irq pin as input if required */
+		if (chn->irq_pin >= 0) {
+			dev->usb_xfer[0] = chn->irq_pin;
+			dev->usb_xfer[1] = 0; /* input */
+			dev->usb_xfer[2] = 0; /* value, ignored for input */
+			ret = usb_control_msg(
+				dev->udev, xmit_pipe,
+				CP2130_BREQ_SET_GPIO_MODE,
+				USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+				0, 0,
+				dev->usb_xfer, 3, 200);
+			if (ret < 3)
+				goto error_unlock;
+		}
+
+		mutex_unlock(&dev->usb_bus_lock);
+
+		dev_dbg(&dev->udev->dev, "try register %s", chn->modalias);
+
+		chn->chip = spi_alloc_device(dev->spi_master);
+		if (!chn->chip)
+			goto error;
+
+		chn->chip->max_speed_hz = dev->spi_master->max_speed_hz;
+		chn->chip->chip_select = i;
+		chn->chip->mode = (chn->polarity << 1) | chn->clock_phase;
+		chn->chip->bits_per_word = 8; /* we can only do this */
+		chn->chip->irq = cp2130_irq_from_pin(dev, chn->irq_pin);
+
+		chn->chip->dev.platform_data = chn->pdata;
+
+		dev_dbg(&dev->udev->dev, "initialized %s chip", chn->modalias);
+		strncpy(chn->chip->modalias, chn->modalias,
+			sizeof(chn->chip->modalias));
+
+		ret = spi_add_device(chn->chip);
+		if (ret)
+			goto error;
+
+		dev_dbg(&dev->udev->dev, "%s probe complete", chn->modalias);
+	}
+	mutex_unlock(&dev->chn_config_lock);
+
+	schedule_work(&dev->read_chn_config);
+	return;
+
+error_unlock:
+	mutex_unlock(&dev->usb_bus_lock);
+
+error:
+	mutex_unlock(&dev->chn_config_lock);
+	dev_err(&dev->udev->dev, "failed to configure channel %d", i);
+}
+
+static void cp2130_read_channel_config(struct work_struct *work)
+{
+	int i;
+	int ret;
+	unsigned int recv_pipe;
+	struct cp2130_device *dev = container_of(work,
+						struct cp2130_device,
+						read_chn_config);
+	struct cp2130_channel *chn;
+
+	dev_dbg(&dev->udev->dev, "control pipes: %u, %u",
+		usb_sndctrlpipe(dev->udev, 0),
+		usb_rcvctrlpipe(dev->udev, 0));
+
+	recv_pipe = usb_rcvctrlpipe(dev->udev, 0);
+
+	mutex_lock(&dev->chn_config_lock);
+	mutex_lock(&dev->usb_bus_lock);
+	dev_dbg(&dev->udev->dev, "read channel configs");
+
+	dev_dbg(&dev->udev->dev, "get spi word");
+	ret = usb_control_msg(
+		dev->udev, recv_pipe,
+		CP2130_BREQ_GET_SPI_WORD,
+		USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+		0, 0,
+		dev->usb_xfer, 11, 200);
+
+	for (i = 0; i < CP2130_NUM_GPIOS; i++) {
+		chn = &dev->chn_configs[i];
+
+		chn->clock_freq = dev->usb_xfer[i] & 7;
+		chn->cs_pin_mode = !!(dev->usb_xfer[i] & 8);
+		chn->polarity = !!(dev->usb_xfer[i] & 16);
+		chn->clock_phase = !!(dev->usb_xfer[i] & 32);
+	}
+
+	dev_dbg(&dev->udev->dev, "get delays");
+	for (i = 0; i < CP2130_NUM_GPIOS; i++) {
+		ret = usb_control_msg(
+			dev->udev, recv_pipe,
+			CP2130_BREQ_GET_SPI_DELAY,
+			USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+			0, i,
+			dev->usb_xfer, 8, 200);
+
+		chn = &dev->chn_configs[i];
+
+		chn->delay_mask = dev->usb_xfer[1];
+		chn->inter_byte_delay = dev->usb_xfer[2] << 8;
+		chn->inter_byte_delay |= dev->usb_xfer[3] & 0xff;
+		chn->post_assert_delay = dev->usb_xfer[4] << 8;
+		chn->post_assert_delay |= dev->usb_xfer[5] & 0xff;
+		chn->pre_deassert_delay = dev->usb_xfer[6] << 8;
+		chn->pre_deassert_delay |= dev->usb_xfer[7] & 0xff;
+	}
+
+	mutex_unlock(&dev->usb_bus_lock);
+	mutex_unlock(&dev->chn_config_lock);
+}
+
+static void cp2130_update_otprom(struct work_struct *work)
+{
+	int i;
+	struct cp2130_device *dev = container_of(work,
+						struct cp2130_device,
+						update_otprom);
+	int ret;
+	unsigned int xmit_pipe;
+
+	xmit_pipe = usb_sndctrlpipe(dev->udev, 0);
+
+	mutex_lock(&dev->otprom_lock);
+
+	for (i = 0; i < CP2130_NUM_GPIOS; i++)
+		dev->usb_xfer[i] = dev->otprom_config.pin_config[i];
+
+	mutex_lock(&dev->usb_bus_lock);
+
+	ret = usb_control_msg(
+		dev->udev, xmit_pipe,
+		CP2130_BREQ_SET_PIN_CONFIG,
+		USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+		CP2130_BREQ_MEMORY_KEY, 0,
+		dev->usb_xfer, 20, 200);
+
+	if (ret)
+		dev_err(&dev->udev->dev, "error writing OTP ROM pin config");
+
+	mutex_unlock(&dev->usb_bus_lock);
+	mutex_unlock(&dev->otprom_lock);
+
+	schedule_work(&dev->read_otprom);
+}
+
+static void cp2130_read_otprom(struct work_struct *work)
+{
+	int i;
+	int ret;
+	unsigned int recv_pipe;
+	struct cp2130_device *dev = container_of(work,
+						struct cp2130_device,
+						read_otprom);
+
+	dev_dbg(&dev->udev->dev, "control pipes: %u, %u",
+		usb_sndctrlpipe(dev->udev, 0),
+		usb_rcvctrlpipe(dev->udev, 0));
+
+	recv_pipe = usb_rcvctrlpipe(dev->udev, 0);
+
+	mutex_lock(&dev->otprom_lock);
+	mutex_lock(&dev->usb_bus_lock);
+	dev_dbg(&dev->udev->dev, "read OTP ROM");
+
+	dev_dbg(&dev->udev->dev, "get lock byte");
+	ret = usb_control_msg(
+		dev->udev, recv_pipe,
+		CP2130_BREQ_GET_LOCK_BYTE,
+		USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+		0, 0,
+		dev->usb_xfer, 2, 200);
+	dev_info(&dev->udev->dev, "lock byte %02X %02X",
+		dev->usb_xfer[0] & 0xff, dev->usb_xfer[1] & 0x0f);
+	dev->otprom_config.lock_byte = dev->usb_xfer[0] & 0xff;
+	dev->otprom_config.lock_byte |= (dev->usb_xfer[1] & 0x0f) << 8;
+
+	dev_dbg(&dev->udev->dev, "get pin config");
+	ret = usb_control_msg(
+		dev->udev, recv_pipe,
+		CP2130_BREQ_GET_PIN_CONFIG,
+		USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+		0, 0,
+		dev->usb_xfer, 20, 200);
+
+	for (i = 0; i < CP2130_NUM_GPIOS; ++i) {
+		dev_info(&dev->udev->dev, "pin %d: %02X",
+			i, dev->usb_xfer[i]);
+		dev->otprom_config.pin_config[i] = dev->usb_xfer[i];
+	}
+	dev->otprom_config.suspend_level =
+		(dev->usb_xfer[i] << 8) | dev->usb_xfer[i + 1];
+	i += 2;
+	dev->otprom_config.suspend_mode =
+		(dev->usb_xfer[i] << 8) | dev->usb_xfer[i + 1];
+	i += 2;
+	dev->otprom_config.wakeup_mask =
+		(dev->usb_xfer[i] << 8) | dev->usb_xfer[i + 1];
+	i += 2;
+	dev->otprom_config.wakeup_match =
+		(dev->usb_xfer[i] << 8) | dev->usb_xfer[i + 1];
+	i += 2;
+	dev->otprom_config.divider = dev->usb_xfer[i];
+
+	mutex_unlock(&dev->usb_bus_lock);
+	mutex_unlock(&dev->otprom_lock);
+}
+
+static void cp2130_read_gpios(struct work_struct *work)
+{
+	unsigned int recv_pipe;
+	struct cp2130_device *dev = container_of(work,
+						struct cp2130_device,
+						irq_work);
+	int i, set;
+	unsigned long flags;
+
+loop:
+	recv_pipe = usb_rcvctrlpipe(dev->udev, 0);
+
+	dev_dbg(&dev->udev->dev, "start read gpios");
+
+	mutex_lock(&dev->usb_bus_lock);
+
+	i = usb_control_msg(
+		dev->udev, recv_pipe,
+		CP2130_BREQ_GET_GPIO_VALUES,
+		USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+		0, 0,
+		dev->usb_xfer, 2, 200);
+
+	dev->gpio_states[0] = dev->usb_xfer[0];
+	dev->gpio_states[1] = dev->usb_xfer[1];
+
+	mutex_unlock(&dev->usb_bus_lock);
+
+	if (i < 2) {
+		dev_err(&dev->udev->dev, "failed to read gpios");
+		goto next;
+	}
+
+	dev_dbg(&dev->udev->dev, "read gpios 1: %02X",
+		dev->gpio_states[1] & 0xff & ~(1 | 2 | 4));
+	dev_dbg(&dev->udev->dev, "read gpios 2: %02X",
+		dev->gpio_states[0] & 0xff & ~(2 | 128));
+
+	for (i = 0; i < CP2130_NUM_GPIOS; ++i) {
+		if (!(dev->irq_chip.irq_mask & (1 << i)))
+			continue;
+
+		switch (i) {
+		case 0:
+		case 1:
+		case 2:
+		case 3:
+		case 4:
+			set = dev->gpio_states[1] & (8 << i);
+			break;
+		case 5:
+			set = dev->gpio_states[0] & (1 << 0);
+			break;
+		case 6:
+		case 7:
+		case 8:
+		case 9:
+		case 10:
+			set = dev->gpio_states[0] & (4 << (i - 6));
+			break;
+		}
+
+		if (!set) {
+			dev_dbg(&dev->udev->dev, "issue irq on %d", i);
+			local_irq_save(flags);
+			generic_handle_irq(dev->irq_chip.virq[i]);
+			local_irq_restore(flags);
+		}
+	}
+
+next:
+	if (dev->irq_poll_interval < 0)
+		return;
+
+	usleep_range(dev->irq_poll_interval, dev->irq_poll_interval + 20);
+	goto loop;
+}
+
+static void cp2130_gpio_irq_mask(struct irq_data *data)
+{
+	struct cp2130_gpio_irq *irq_dev = irq_data_get_irq_chip_data(data);
+	struct cp2130_device *dev
+		= container_of(irq_dev, struct cp2130_device, irq_chip);
+
+	dev_dbg(&dev->udev->dev, "irq mask %lu", data->hwirq);
+	irq_dev->irq_mask &= ~(1 << data->hwirq);
+}
+
+static void cp2130_gpio_irq_unmask(struct irq_data *data)
+{
+	struct cp2130_gpio_irq *irq_dev = irq_data_get_irq_chip_data(data);
+	struct cp2130_device *dev
+		= container_of(irq_dev, struct cp2130_device, irq_chip);
+
+	dev_dbg(&dev->udev->dev, "irq unmask %lu", data->hwirq);
+	irq_dev->irq_mask |= 1 << data->hwirq;
+}
+
+static int cp2130_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	return 0;
+}
+
+static void cp2130_gpio_irq_bus_lock(struct irq_data *data)
+{
+	struct cp2130_gpio_irq *irq_dev = irq_data_get_irq_chip_data(data);
+
+	mutex_lock(&irq_dev->irq_lock);
+}
+
+static void cp2130_gpio_irq_bus_unlock(struct irq_data *data)
+{
+	struct cp2130_gpio_irq *irq_dev = irq_data_get_irq_chip_data(data);
+
+	mutex_unlock(&irq_dev->irq_lock);
+}
+
+static struct irq_chip cp2130_gpio_irq_chip = {
+	.name                = "gpio-cp2130",
+	.irq_mask            = cp2130_gpio_irq_mask,
+	.irq_unmask          = cp2130_gpio_irq_unmask,
+	.irq_set_type        = cp2130_gpio_irq_set_type,
+	.irq_bus_lock        = cp2130_gpio_irq_bus_lock,
+	.irq_bus_sync_unlock = cp2130_gpio_irq_bus_unlock,
+};
+
+static int cp2130_gpio_irq_map(struct irq_domain *domain, unsigned int irq,
+			irq_hw_number_t hwirq)
+{
+	irq_set_chip_data(irq, domain->host_data);
+	irq_set_chip(irq, &cp2130_gpio_irq_chip);
+	irq_set_chip_and_handler(irq, &cp2130_gpio_irq_chip, handle_simple_irq);
+	irq_set_noprobe(irq);
+	return 0;
+}
+
+static const struct irq_domain_ops cp2130_gpio_irq_domain_ops = {
+	.map = cp2130_gpio_irq_map,
+};
+
+static void cp2130_gpio_irq_remove(struct cp2130_device *dev);
+
+static int cp2130_gpio_irq_probe(struct cp2130_device *dev)
+{
+	struct cp2130_gpio_irq *irq_dev = &dev->irq_chip;
+	int i;
+
+	mutex_init(&irq_dev->irq_lock);
+
+	irq_dev->irq_domain = irq_domain_add_linear(
+		dev->udev->dev.of_node, CP2130_NUM_GPIOS,
+		&cp2130_gpio_irq_domain_ops, irq_dev);
+
+	if (!irq_dev->irq_domain) {
+		dev_err(&dev->udev->dev, "failed to register IRQ domain");
+		return -ENOMEM;
+	}
+
+	irq_dev->irq_mask = 0;
+	for (i = 0; i < CP2130_NUM_GPIOS; ++i) {
+		irq_dev->virq[i] =
+			irq_create_mapping(irq_dev->irq_domain, i);
+		dev_dbg(&dev->udev->dev, "created virtual irq %d",
+			irq_dev->virq[i]);
+	}
+
+	INIT_WORK(&dev->irq_work, cp2130_read_gpios);
+	schedule_work(&dev->irq_work);
+
+	return 0;
+}
+
+static void cp2130_gpio_irq_remove(struct cp2130_device *dev)
+{
+	int i = 0;
+
+	if (!dev->irq_chip.irq_domain)
+		return;
+
+	for (i = 0; i < CP2130_NUM_GPIOS; ++i)
+		irq_dispose_mapping(dev->irq_chip.virq[i]);
+
+	irq_domain_remove(dev->irq_chip.irq_domain);
+}
+
+static int cp2130_gpio_direction_input(struct gpio_chip *gc, unsigned int off)
+{
+	struct cp2130_device *dev = gpiochip_get_data(gc);
+	int ret;
+	unsigned int xmit_pipe;
+
+	xmit_pipe = usb_sndctrlpipe(dev->udev, 0);
+
+	mutex_lock(&dev->usb_bus_lock);
+
+	dev->usb_xfer[0] = off;
+	dev->usb_xfer[1] = 0; /* input */
+	dev->usb_xfer[2] = 0; /* value, ignored for input */
+	ret = usb_control_msg(
+		dev->udev, xmit_pipe,
+		CP2130_BREQ_SET_GPIO_MODE,
+		USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+		0, 0,
+		dev->usb_xfer, 3, 200);
+
+	mutex_unlock(&dev->usb_bus_lock);
+
+	return (ret == 3) ? 0 : -EIO;
+}
+
+static int cp2130_gpio_direction_output(struct gpio_chip *gc, unsigned int off,
+					int val)
+{
+	struct cp2130_device *dev = gpiochip_get_data(gc);
+	int ret;
+	unsigned int xmit_pipe;
+
+	xmit_pipe = usb_sndctrlpipe(dev->udev, 0);
+
+	mutex_lock(&dev->usb_bus_lock);
+
+	dev->usb_xfer[0] = off;
+	dev->usb_xfer[1] = 2; /* push-pull output */
+	dev->usb_xfer[2] = val; /* state */
+	ret = usb_control_msg(
+		dev->udev, xmit_pipe,
+		CP2130_BREQ_SET_GPIO_MODE,
+		USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
+		0, 0,
+		dev->usb_xfer, 3, 200);
+
+	mutex_unlock(&dev->usb_bus_lock);
+
+	return (ret == 3) ? 0 : -EIO;
+}
+
+static int cp2130_gpio_get_value(struct gpio_chip *gc, unsigned int off)
+{
+	struct cp2130_device *dev = gpiochip_get_data(gc);
+	int set;
+
+	switch (off) {
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+	case 4:
+		set = !!(dev->gpio_states[1] & (8 << off));
+		break;
+	case 5:
+		set = !!(dev->gpio_states[0] & (1 << 0));
+		break;
+	case 6:
+	case 7:
+	case 8:
+	case 9:
+	case 10:
+		set = !!(dev->gpio_states[0] & (4 << (off - 6)));
+		break;
+	default:
+		set = -EINVAL;
+		break;
+	}
+	return set;
+}
+
+static void cp2130_gpio_set_value(struct gpio_chip *gc, unsigned int off,
+				int val)
+{
+	cp2130_gpio_direction_output(gc, off, val);
+}
+
+static const char *const cp2130_gpio_names[] = {
+	"........-_cs0",
+	"........-_cs1",
+	"........-_cs2",
+	"........-_rtr",
+	"........-event_counter",
+	"........-clk_out",
+	"........-gpi",
+	"........-gpo",
+	"........-activity",
+	"........-suspend",
+	"........-_suspend",
+};
+
+static struct attribute *cp2130_sysfs_attributes[] = {
+	&dev_attr_channel_config.attr,
+	&dev_attr_channel_pdata.attr,
+	&dev_attr_otp_rom.attr,
+	&dev_attr_irq_poll_interval.attr,
+	NULL
+};
+
+static const struct attribute_group cp2130_sysfs_atributes_group = {
+	.name = "cp2130",
+	.attrs = cp2130_sysfs_attributes,
+};
+
+static int cp2130_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	struct usb_device *udev = usb_get_dev(interface_to_usbdev(intf));
+	struct cp2130_device *dev;
+	struct spi_master *spi_master = NULL;
+	struct gpio_chip *gc;
+	struct usb_host_interface *iface_desc = intf->cur_altsetting;
+	struct usb_endpoint_descriptor *endpoint;
+	int i, ret;
+
+	ret = -ENOMEM;
+
+	pr_debug("%s", __func__);
+
+	if (!udev)
+		return -ENODEV;
+
+	dev = devm_kzalloc(&udev->dev, sizeof(struct cp2130_device),
+			GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	dev->usb_xfer = devm_kmalloc(&udev->dev, CP2130_MAX_USB_PACKET_SIZE,
+				GFP_KERNEL);
+	if (!dev->usb_xfer)
+		return -ENOMEM;
+
+	dev->udev = udev;
+	dev->intf = intf;
+
+	mutex_init(&dev->usb_bus_lock);
+	mutex_init(&dev->chn_config_lock);
+	mutex_init(&dev->otprom_lock);
+
+	dev->current_channel = -1;
+
+	usb_set_intfdata(intf, dev);
+
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+		endpoint = &iface_desc->endpoint[i].desc;
+		dev_dbg(&udev->dev, "ep addr: 0x%02x",
+			endpoint->bEndpointAddress);
+		if (usb_endpoint_is_bulk_in(endpoint))
+			dev_dbg(&udev->dev, "bulk in ep addr: 0x%02x",
+				endpoint->bEndpointAddress);
+		if (usb_endpoint_is_bulk_out(endpoint))
+			dev_dbg(&udev->dev, "bulk out ep addr: 0x%02x",
+				endpoint->bEndpointAddress);
+	}
+
+	spi_master = spi_alloc_master(&udev->dev, sizeof(void *));
+	if (!spi_master)
+		return -ENOMEM;
+
+	spi_master_set_devdata(spi_master, (void *) dev);
+
+	spi_master->min_speed_hz = 93 * 1000 + 800; /* 93.8kHz */
+	spi_master->max_speed_hz = 12 * 1000 * 1000; /* 12 MHz */
+
+	spi_master->bus_num = -1; /* dynamically assigned */
+	spi_master->num_chipselect = CP2130_NUM_GPIOS;
+	spi_master->mode_bits =
+		SPI_MODE_0 | SPI_MODE_1 | SPI_MODE_2 | SPI_MODE_3;
+
+	spi_master->flags = 0;
+	spi_master->setup = cp2130_spi_setup;
+	spi_master->cleanup = cp2130_spi_cleanup;
+	spi_master->transfer_one_message = cp2130_spi_transfer_one_message;
+
+	ret = devm_spi_register_master(&udev->dev, spi_master);
+
+	if (ret) {
+		dev_err(&udev->dev, "failed to register SPI master");
+		return -ENOMEM;
+	}
+
+	dev->spi_master = spi_master;
+	dev_dbg(&udev->dev, "registered SPI master");
+
+	/* now enable the gpio chip */
+	gc = &dev->gpio_chip;
+	gc->direction_input = cp2130_gpio_direction_input;
+	gc->direction_output = cp2130_gpio_direction_output;
+	gc->get = cp2130_gpio_get_value;
+	gc->set = cp2130_gpio_set_value;
+	gc->can_sleep = true;
+
+	gc->base = -1; /* auto */
+	gc->ngpio = CP2130_NUM_GPIOS;
+	for (i = 0; i < CP2130_NUM_GPIOS; i++) {
+		dev->gpio_names[i] = kstrdup(cp2130_gpio_names[i], GFP_KERNEL);
+		memcpy(dev->gpio_names[i],
+			dev_name(&spi_master->dev), 3 + 5 /* spixxxxx */);
+	}
+	gc->names = (const char **) dev->gpio_names;
+	gc->label = dev_name(&spi_master->dev);
+	gc->owner = THIS_MODULE;
+
+	ret = devm_gpiochip_add_data(&udev->dev, &dev->gpio_chip, dev);
+	if (ret)
+		dev_err(&udev->dev, "failed to register gpio chip");
+
+	/* start irq polling */
+	dev->irq_poll_interval = CP2130_IRQ_POLL_INTERVAL;
+	cp2130_gpio_irq_probe(dev);
+	dev_dbg(&udev->dev, "registered irq chip");
+
+	/* create sysfs files */
+	ret = sysfs_create_group(&intf->dev.kobj,
+				&cp2130_sysfs_atributes_group);
+	if (ret)
+		dev_err(&udev->dev, "device_create_file failed");
+
+	INIT_WORK(&dev->update_chn_config, cp2130_update_channel_config);
+	INIT_WORK(&dev->read_chn_config, cp2130_read_channel_config);
+	INIT_WORK(&dev->update_otprom, cp2130_update_otprom);
+	INIT_WORK(&dev->read_otprom, cp2130_read_otprom);
+	schedule_work(&dev->read_chn_config);
+	schedule_work(&dev->read_otprom);
+
+	return 0;
+}
+
+static void cp2130_disconnect(struct usb_interface *intf)
+{
+	struct cp2130_device *dev = usb_get_intfdata(intf);
+	int i = dev->irq_poll_interval;
+
+	dev->irq_poll_interval = -1;
+	usleep_range(i, i + 100); /* wait for worker to complete */
+
+	/* Remove all devices (if any) attached to the SPI bus */
+	for (i = 0; i < CP2130_NUM_GPIOS; i++)
+		if (dev->chn_configs[i].chip)
+			spi_unregister_device(dev->chn_configs[i].chip);
+
+	cp2130_gpio_irq_remove(dev);
+	for (i = 0; i < CP2130_NUM_GPIOS; i++) {
+		struct cp2130_channel *chn_cfg = &dev->chn_configs[i];
+
+		kfree(dev->gpio_names[i]);
+		kfree(chn_cfg->pdata);
+	}
+
+	/* remove sysfs files */
+	sysfs_remove_group(&intf->dev.kobj, &cp2130_sysfs_atributes_group);
+}
+
+module_init(cp2130_init);
+module_exit(cp2130_exit);
+MODULE_AUTHOR("Jochen Henneberg <jh@xxxxxxxxxxxxxxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Silicon Labs CP2130 USB-to-SPI bridge");
+MODULE_LICENSE("GPL");
+
+MODULE_DEVICE_TABLE(usb, cp2130_devices);
-- 
2.7.4




[Index of Archives]     [Linux Kernel]     [Linux ARM (vger)]     [Linux ARM MSM]     [Linux Omap]     [Linux Arm]     [Linux Tegra]     [Fedora ARM]     [Linux for Samsung SOC]     [eCos]     [Linux Fastboot]     [Gcc Help]     [Git]     [DCCP]     [IETF Announce]     [Security]     [Linux MIPS]     [Yosemite Campsites]

  Powered by Linux