All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3] dln-2: Add support for Diolan DLN-2 devices
@ 2014-08-20 11:24 Daniel Baluta
  2014-08-20 11:24 ` [PATCH 1/3] usb: add " Daniel Baluta
                   ` (2 more replies)
  0 siblings, 3 replies; 10+ messages in thread
From: Daniel Baluta @ 2014-08-20 11:24 UTC (permalink / raw)
  To: gregkh, linux-usb, wsa, linux-i2c, linus.walleij, gnurou, linux-gpio
  Cc: pratyush.anand, pebolle, stern, octavian.purdila, matthew,
	linux-kernel, laurentiu.palcu, daniel.baluta, jdelvare, arnd,
	sjg

This patch series adds support for Diolan USB-I2C/GPIO Master Adapter DLN-2.
Details about device can be found here:

https://www.diolan.com/i2c/i2c_interface.html.

There is no support for SPI part yet.

Daniel Baluta (1):
  gpio: add support for the Diolan DLN-2 USB-GPIO driver

Laurentiu Palcu (1):
  i2c: add support for Diolan DLN-2 USB-I2C adapter

Octavian Purdila (1):
  usb: add support for Diolan DLN-2 devices

 drivers/gpio/Kconfig          |  13 +
 drivers/gpio/Makefile         |   1 +
 drivers/gpio/gpio-dln2.c      | 571 +++++++++++++++++++++++++++++++++
 drivers/i2c/busses/Kconfig    |  11 +
 drivers/i2c/busses/Makefile   |   1 +
 drivers/i2c/busses/i2c-dln2.c | 328 +++++++++++++++++++
 drivers/usb/misc/Kconfig      |   6 +
 drivers/usb/misc/Makefile     |   1 +
 drivers/usb/misc/dln2.c       | 719 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/usb/dln2.h      | 146 +++++++++
 10 files changed, 1797 insertions(+)
 create mode 100644 drivers/gpio/gpio-dln2.c
 create mode 100644 drivers/i2c/busses/i2c-dln2.c
 create mode 100644 drivers/usb/misc/dln2.c
 create mode 100644 include/linux/usb/dln2.h

-- 
1.9.1

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/3] usb: add support for Diolan DLN-2 devices
  2014-08-20 11:24 [PATCH 0/3] dln-2: Add support for Diolan DLN-2 devices Daniel Baluta
@ 2014-08-20 11:24 ` Daniel Baluta
  2014-08-20 19:53   ` Arnd Bergmann
  2014-08-21  8:07   ` Johan Hovold
  2014-08-20 11:24 ` [PATCH 2/3] i2c: add support for Diolan DLN-2 USB-I2C adapter Daniel Baluta
       [not found] ` <1408533887-22727-1-git-send-email-daniel.baluta-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
  2 siblings, 2 replies; 10+ messages in thread
From: Daniel Baluta @ 2014-08-20 11:24 UTC (permalink / raw)
  To: gregkh, linux-usb, wsa, linux-i2c, linus.walleij, gnurou, linux-gpio
  Cc: pratyush.anand, pebolle, stern, octavian.purdila, matthew,
	linux-kernel, laurentiu.palcu, daniel.baluta, jdelvare, arnd,
	sjg

From: Octavian Purdila <octavian.purdila@intel.com>

This patch implements the USB part of the Diolan USB-I2C/SPI/GPIO
Master Adapter DLN-2. Details about the device can be found here:

https://www.diolan.com/i2c/i2c_interface.html.

Information about the USB protocol can be found in the Programmer's
Reference Manual [1], see section 1.7.

Because the hardware has a single transmit endpoint and a single
receive endpoint the communication between the various DLN2 drivers
and the hardware will be muxed/demuxed by this driver.

The functional DLN2 drivers (i2c, GPIO, etc.) will have to register
themselves as DLN2 modules in order to send or receive data.

Each DLN2 module will be identified by the handle field within the DLN2
message header. If a DLN2 module issues multiple commands in parallel
they will be identified by the echo counter field in the message header.

The DLN2 modules can use the dln2_transfer() function to issue a
command and wait for its response. They can also use an asynchronous
mode of operation, in which case a receive callback function is going
to be notified when messages for a specific handle are received.

Because the hardware reserves handle 0 for GPIO events, the driver
also reserves handle 0. It will be allocated to a DLN2 module only if
it is explicitly requested.

[1] https://www.diolan.com/downloads/dln-api-manual.pdf

Signed-off-by: Octavian Purdila <octavian.purdila@intel.com>
---
 drivers/usb/misc/Kconfig  |   6 +
 drivers/usb/misc/Makefile |   1 +
 drivers/usb/misc/dln2.c   | 719 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/usb/dln2.h  | 146 ++++++++++
 4 files changed, 872 insertions(+)
 create mode 100644 drivers/usb/misc/dln2.c
 create mode 100644 include/linux/usb/dln2.h

diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 76d7720..953f521 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -255,3 +255,9 @@ config USB_LINK_LAYER_TEST
 	  This driver is for generating specific traffic for Super Speed Link
 	  Layer Test Device. Say Y only when you want to conduct USB Super Speed
 	  Link Layer Test for host controllers.
+
+config USB_DLN2
+	tristate "Diolan DLN2 USB Driver"
+	help
+	  This adds USB support for Diolan  USB-I2C/SPI/GPIO
+	  Master Adapter DLN-2.
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 65b0402..767264e 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_USB_USS720)		+= uss720.o
 obj-$(CONFIG_USB_SEVSEG)		+= usbsevseg.o
 obj-$(CONFIG_USB_YUREX)			+= yurex.o
 obj-$(CONFIG_USB_HSIC_USB3503)		+= usb3503.o
+obj-$(CONFIG_USB_DLN2)			+= dln2.o
 
 obj-$(CONFIG_USB_SISUSBVGA)		+= sisusbvga/
 obj-$(CONFIG_USB_LINK_LAYER_TEST)	+= lvstest.o
diff --git a/drivers/usb/misc/dln2.c b/drivers/usb/misc/dln2.c
new file mode 100644
index 0000000..5bfa850
--- /dev/null
+++ b/drivers/usb/misc/dln2.c
@@ -0,0 +1,719 @@
+/*
+ * Driver for the Diolan DLN-2 USB adapter
+ *
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * 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>
+#include <linux/mutex.h>
+
+#define DRIVER_NAME			"usb-dln2"
+
+#define DLN2_GENERIC_MODULE_ID		0x00
+#define DLN2_GENERIC_CMD(cmd)		DLN2_CMD(cmd, DLN2_GENERIC_MODULE_ID)
+
+/* generic commands */
+#define CMD_GET_DEVICE_VER		DLN2_GENERIC_CMD(0x30)
+#define CMD_GET_DEVICE_SN		DLN2_GENERIC_CMD(0x31)
+
+#define DLN2_HW_ID			0x200
+
+#define DLN2_USB_TIMEOUT		100	/* in ms */
+
+#define DLN2_MAX_RX_SLOTS 16
+
+#include <linux/usb/dln2.h>
+
+struct dln2_rx_context {
+	struct completion done;
+	struct urb *urb;
+	bool connected;
+};
+
+struct dln2_rx_slots {
+	/* RX slots bitmap */
+	DECLARE_BITMAP(bmap, DLN2_MAX_RX_SLOTS);
+
+	/* used to wait for a free RX slot */
+	wait_queue_head_t wq;
+
+	/* used to wait for an RX operation to complete */
+	struct dln2_rx_context slots[DLN2_MAX_RX_SLOTS];
+
+	/* avoid races between free_rx_slot and dln2_transfer_rx_cb */
+	spinlock_t lock;
+};
+
+static int find_free_slot(struct dln2_rx_slots *rxs, int *slot)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&rxs->lock, flags);
+
+	*slot = bitmap_find_free_region(rxs->bmap, DLN2_MAX_RX_SLOTS, 0) - 1;
+
+	if (*slot >= 0) {
+		struct dln2_rx_context *rxc = &rxs->slots[*slot];
+
+		init_completion(&rxc->done);
+		rxc->connected = true;
+	}
+
+	spin_unlock_irqrestore(&rxs->lock, flags);
+
+	return *slot >= 0;
+}
+
+static int alloc_rx_slot(struct dln2_rx_slots *rxs)
+{
+	int slot, ret;
+
+	ret = wait_event_interruptible(rxs->wq, find_free_slot(rxs, &slot));
+	if (ret < 0)
+		return ret;
+
+	return slot;
+}
+
+static void free_rx_slot(struct dln2_rx_slots *rxs, int slot)
+{
+	unsigned long flags;
+	struct dln2_rx_context *rxc;
+
+	spin_lock_irqsave(&rxs->lock, flags);
+
+	bitmap_clear(rxs->bmap, slot + 1, 1);
+
+	rxc = &rxs->slots[slot];
+	rxc->connected = false;
+
+	if (rxc->urb) {
+		usb_submit_urb(rxc->urb, GFP_KERNEL);
+		rxc->urb = NULL;
+	}
+
+	spin_unlock_irqrestore(&rxs->lock, flags);
+
+	wake_up_interruptible(&rxs->wq);
+}
+
+
+#define DLN2_MAX_MODULES	5
+#define DLN2_MAX_URBS		16
+
+struct dln2_dev {
+	struct urb *rx_urb[DLN2_MAX_URBS];
+	void *rx_buf[DLN2_MAX_URBS];
+	int ep_in, ep_out;              /* Endpoints    */
+	struct usb_device *usb_dev;	/* the usb device for this device */
+	struct usb_interface *interface;/* the interface for this device */
+
+	struct dln2_rx_slots mod_rx_slots[DLN2_MAX_MODULES];
+	void *context[DLN2_MAX_MODULES];
+	bool mod_initialized[DLN2_MAX_MODULES];
+
+	struct list_head list;
+};
+
+struct dln2_mod {
+	struct dln2_mod_ops *ops;
+	void *context;
+};
+
+/* registered modules (e.g i2c, GPIO, GPIO interrupts, etc.) */
+static struct dln2_mod dln2_modules[DLN2_MAX_MODULES];
+static DEFINE_MUTEX(dln2_modules_mutex);
+/* module used internally for control messages */
+static struct dln2_mod *dln2_ctrl_mod;
+
+static inline int dln2_get_handle(struct dln2_mod *mod)
+{
+	int handle = mod - dln2_modules;
+
+	BUG_ON(handle < 0 || handle > DLN2_MAX_MODULES);
+
+	return handle;
+}
+
+static void dln2_connect_do_work(struct work_struct *w);
+static DECLARE_DELAYED_WORK(dln2_connect_work, dln2_connect_do_work);
+
+struct dln2_mod *dln2_register_module(int handle, struct dln2_mod_ops *mod_ops)
+{
+	int i;
+
+	if (!mod_ops)
+		return ERR_PTR(-EINVAL);
+
+	mutex_lock(&dln2_modules_mutex);
+
+	if (handle < 0) {
+		for (i = 0; i < DLN2_MAX_MODULES; i++) {
+			/* reserve handle 0 for GPIO interrupts */
+			if (!dln2_modules[i].ops && i > 0) {
+				handle = i;
+				break;
+			}
+		}
+	}
+
+	if (handle >= DLN2_MAX_MODULES) {
+		mutex_unlock(&dln2_modules_mutex);
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (dln2_modules[handle].ops) {
+		mutex_unlock(&dln2_modules_mutex);
+		return ERR_PTR(-EBUSY);
+	}
+
+	dln2_modules[handle].ops = mod_ops;
+
+	mutex_unlock(&dln2_modules_mutex);
+
+	schedule_delayed_work(&dln2_connect_work, HZ/10);
+
+	return &dln2_modules[handle];
+}
+EXPORT_SYMBOL(dln2_register_module);
+
+void dln2_unregister_module(struct dln2_mod *mod)
+{
+	mutex_lock(&dln2_modules_mutex);
+	mod->ops = NULL;
+	mutex_unlock(&dln2_modules_mutex);
+}
+EXPORT_SYMBOL(dln2_unregister_module);
+
+static void dln2_transfer_rx_cb(struct dln2_dev *dev, struct urb *urb,
+				struct dln2_response *rsp, void *data, int len)
+{
+	int handle = le16_to_cpu(rsp->hdr.handle);
+	int rx_slot = le16_to_cpu(rsp->hdr.echo);
+	struct dln2_rx_slots *rxs = &dev->mod_rx_slots[handle];
+	struct dln2_rx_context *rxc;
+
+	spin_lock(&rxs->lock);
+	rxc = &rxs->slots[rx_slot];
+	if (rxc->connected) {
+		rxc->urb = urb;
+		complete(&rxc->done);
+	} else {
+		dev_dbg(&dev->interface->dev, "drop response for handle/slot %d/%d",
+			 handle, rx_slot);
+		usb_submit_urb(urb, GFP_ATOMIC);
+	}
+	spin_unlock(&rxs->lock);
+}
+
+static void dln2_rx(struct urb *urb)
+{
+	struct dln2_dev *dev = urb->context;
+	struct dln2_response *rsp = urb->transfer_buffer;
+	struct dln2_mod *mod;
+	u16 id, echo, handle, size;
+	u8 *user_data;
+	int user_len;
+
+	handle = le16_to_cpu(rsp->hdr.handle);
+	id = le16_to_cpu(rsp->hdr.id);
+	echo = le16_to_cpu(rsp->hdr.echo);
+	size = le16_to_cpu(rsp->hdr.size);
+
+	if (handle > DLN2_MAX_MODULES)
+		goto out_invalid_handle;
+	mod = &dln2_modules[handle];
+	if (!mod->ops)
+		goto out_invalid_handle;
+
+	if (size != urb->actual_length)
+		dev_warn(&dev->interface->dev, "RX len mismatch: handle %x cmd %x echo %x size %d actual %d\n",
+			 handle, id, echo, size, urb->actual_length);
+
+	user_data = urb->transfer_buffer + sizeof(struct dln2_header);
+	user_len = urb->actual_length - sizeof(struct dln2_header);
+
+	if (mod->ops->receive)
+		mod->ops->receive(dev, urb, rsp, user_data, user_len);
+	else
+		dln2_transfer_rx_cb(dev, urb, rsp, user_data, user_len);
+
+	return;
+
+out_invalid_handle:
+	dev_warn(&dev->interface->dev, "RX: invalid handle %d\n", handle);
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static void *dln2_prep_buf(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd,
+			   u16 echo, void *obuf, int *obuf_len, gfp_t gfp)
+{
+	void *buf;
+	int len;
+	struct dln2_header *hdr;
+	u16 handle = dln2_get_handle(mod);
+
+	len = *obuf_len + sizeof(*hdr);
+	buf = kmalloc(len, gfp);
+	if (!buf)
+		return NULL;
+
+	hdr = (struct dln2_header *)buf;
+	hdr->id = cpu_to_le16(cmd);
+	hdr->size = cpu_to_le16(len);
+	hdr->echo = cpu_to_le16(echo);
+	hdr->handle = cpu_to_le16(handle);
+
+	memcpy(buf + sizeof(*hdr), obuf, *obuf_len);
+	*obuf_len = len;
+
+	return buf;
+}
+
+static int dln2_send_wait(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd,
+			  u16 echo, void *obuf, int obuf_len)
+{
+	int len = obuf_len, ret = 0, actual;
+	void *buf;
+
+	if (!dev)
+		return -ENODEV;
+
+	buf = dln2_prep_buf(dev, mod, cmd, echo, obuf, &len, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = usb_bulk_msg(dev->usb_dev,
+			   usb_sndbulkpipe(dev->usb_dev, dev->ep_out),
+			   buf, len, &actual, DLN2_USB_TIMEOUT);
+
+	kfree(buf);
+
+	return ret;
+}
+
+static void dln2_send_complete(struct urb *urb)
+{
+	kfree(urb->context);
+	usb_free_urb(urb);
+}
+
+int dln2_send(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd,
+	      u16 echo, void *obuf, int obuf_len)
+{
+	int len = obuf_len, ret;
+	struct urb *urb;
+	void *buf;
+
+	if (!dev)
+		return -ENODEV;
+
+	buf = dln2_prep_buf(dev, mod, cmd, echo, obuf, &len, GFP_ATOMIC);
+	if (!buf)
+		return -ENOMEM;
+
+	urb = usb_alloc_urb(0, GFP_ATOMIC);
+	if (!urb) {
+		kfree(buf);
+		return -ENOMEM;
+	}
+
+	usb_fill_bulk_urb(urb, dev->usb_dev,
+			  usb_sndbulkpipe(dev->usb_dev, dev->ep_out),
+			  buf, len, dln2_send_complete, buf);
+
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret < 0) {
+		usb_free_urb(urb);
+		kfree(buf);
+		return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(dln2_send);
+
+int dln2_transfer(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd,
+		  void *obuf, int obuf_len, void *ibuf, int *ibuf_len)
+{
+	u16 result, rx_slot;
+	struct dln2_response *rsp;
+	int ret = 0, handle = dln2_get_handle(mod);
+	const int timeout = DLN2_USB_TIMEOUT * HZ / 1000;
+	struct dln2_rx_slots *rxs;
+	struct dln2_rx_context *rxc;
+	struct device *d;
+
+	if (!dev)
+		return -ENODEV;
+
+	d = &dev->interface->dev;
+
+	if (mod->ops->receive) {
+		dev_warn(&dev->interface->dev,
+			 "module %s calls dln2_transfer w/ rx_callback set\n",
+			 mod->ops->name);
+		return -EINVAL;
+	}
+
+	rxs = &dev->mod_rx_slots[handle];
+
+	rx_slot = alloc_rx_slot(rxs);
+	if (rx_slot < 0) {
+		dev_err(d, "alloc_rx_slot failed: %d", ret);
+		return rx_slot;
+	}
+
+	ret = dln2_send_wait(dev, mod, cmd, rx_slot, obuf, obuf_len);
+	if (ret < 0) {
+		free_rx_slot(rxs, rx_slot);
+		dev_err(d, "USB write failed: %d", ret);
+		return ret;
+	}
+
+	rxc = &rxs->slots[rx_slot];
+
+	ret = wait_for_completion_interruptible_timeout(&rxc->done, timeout);
+	if (ret <= 0) {
+		ret = !ret ? -ETIMEDOUT : ret;
+		goto out_free_rx_slot;
+	}
+
+	rsp = rxc->urb->transfer_buffer;
+	result = le16_to_cpu(rsp->result);
+
+	if (result) {
+		dev_warn(d, "%d received response with error %d\n",
+			 handle, result);
+		ret = -EREMOTEIO;
+		goto out_free_rx_slot;
+	}
+
+	if (!ibuf) {
+		ret = 0;
+		goto out_free_rx_slot;
+	}
+
+	if (*ibuf_len > rxc->urb->actual_length - sizeof(*rsp))
+		*ibuf_len = rxc->urb->actual_length - sizeof(*rsp);
+
+	memcpy(ibuf, rsp + 1, *ibuf_len);
+
+out_free_rx_slot:
+	free_rx_slot(rxs, rx_slot);
+
+	return ret;
+}
+EXPORT_SYMBOL(dln2_transfer);
+
+static int dln2_check_hw(struct dln2_dev *dev)
+{
+	__le32 hw_type;
+	int ret, len = sizeof(hw_type);
+
+
+	ret = dln2_transfer(dev, dln2_ctrl_mod, CMD_GET_DEVICE_VER, NULL, 0,
+			    &hw_type, &len);
+	if (ret < 0)
+		return ret;
+
+	if (le32_to_cpu(hw_type) != DLN2_HW_ID) {
+		dev_err(&dev->interface->dev, "Device ID 0x%x not supported!",
+			le32_to_cpu(hw_type));
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int dln2_print_serialno(struct dln2_dev *dev)
+{
+	__le32 serial_no;
+	int ret, len = sizeof(serial_no);
+
+
+	ret = dln2_transfer(dev, dln2_ctrl_mod, CMD_GET_DEVICE_SN, NULL, 0,
+			    &serial_no, &len);
+	if (ret < 0)
+		return ret;
+
+	dev_info(&dev->interface->dev,
+		 "Diolan DLN2 device serial number is: 0x%x\n",
+		 le32_to_cpu(serial_no));
+
+	return 0;
+}
+
+static int dln2_hw_init(struct dln2_dev *dev)
+{
+	int ret;
+
+	dev_info(&dev->interface->dev,
+		 "Diolan DLN2 at USB bus %03d address %03d\n",
+		 dev->usb_dev->bus->busnum, dev->usb_dev->devnum);
+
+	ret = dln2_check_hw(dev);
+	if (ret < 0)
+		return ret;
+
+	dln2_print_serialno(dev);
+
+	return ret;
+}
+
+/* device layer */
+
+
+static LIST_HEAD(dln2_dev_list);
+
+static void dln2_connect_modules(struct dln2_dev *dev)
+{
+	int i;
+
+	for (i = 0; i < DLN2_MAX_MODULES; i++)
+		if (dln2_modules[i].ops && dln2_modules[i].ops->connect &&
+		    !dev->mod_initialized[i] &&
+		    !dln2_modules[i].ops->connect(dev))
+			dev->mod_initialized[i] = true;
+}
+
+static void dln2_connect_do_work(struct work_struct *w)
+{
+	struct dln2_dev *dev;
+
+	mutex_lock(&dln2_modules_mutex);
+	list_for_each_entry(dev, &dln2_dev_list, list)
+		dln2_connect_modules(dev);
+	mutex_unlock(&dln2_modules_mutex);
+}
+
+static void dln2_free_rx_urbs(struct dln2_dev *dev)
+{
+	int i;
+
+	for (i = 0; i < DLN2_MAX_URBS; i++) {
+		usb_unlink_urb(dev->rx_urb[i]);
+		usb_free_urb(dev->rx_urb[i]);
+		kfree(dev->rx_buf[i]);
+	}
+}
+
+static void dln2_free(struct dln2_dev *dev)
+{
+	dln2_free_rx_urbs(dev);
+	usb_put_dev(dev->usb_dev);
+	kfree(dev);
+}
+
+static int dln2_setup_rx_urbs(struct dln2_dev *dev,
+			      struct usb_host_interface *hostif)
+{
+	int i, ret;
+	int rx_max_size = le16_to_cpu(hostif->endpoint[1].desc.wMaxPacketSize);
+	struct device *d = &dev->interface->dev;
+
+	for (i = 0; i < DLN2_MAX_URBS; i++) {
+		dev->rx_buf[i] = kmalloc(rx_max_size, GFP_KERNEL);
+		if (!dev->rx_buf[i]) {
+			dev_err(d, "no memory for RX buffers\n");
+			return -ENOMEM;
+		}
+
+		dev->rx_urb[i] = usb_alloc_urb(0, GFP_KERNEL);
+		if (!dev->rx_urb[i]) {
+			dev_err(d, "no memory for RX URBs\n");
+			return -ENOMEM;
+		}
+
+		usb_fill_bulk_urb(dev->rx_urb[i], dev->usb_dev,
+				  usb_rcvbulkpipe(dev->usb_dev, dev->ep_in),
+				  dev->rx_buf[i], rx_max_size, dln2_rx, dev);
+
+		ret = usb_submit_urb(dev->rx_urb[i], GFP_KERNEL);
+		if (ret < 0) {
+			dev_err(d, "failed to submit RX URB: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void dln2_disconnect(struct usb_interface *interface)
+{
+	struct dln2_dev *dev = usb_get_intfdata(interface);
+	int i;
+
+	mutex_lock(&dln2_modules_mutex);
+	list_del(&dev->list);
+	for (i = 0; i < DLN2_MAX_MODULES; i++)
+		if (dln2_modules[i].ops && dln2_modules[i].ops->disconnect &&
+		    dev->mod_initialized[i])
+			dln2_modules[i].ops->disconnect(dev);
+	mutex_unlock(&dln2_modules_mutex);
+
+	usb_set_intfdata(interface, NULL);
+	dln2_free(dev);
+
+	dev_dbg(&interface->dev, "disconnected\n");
+}
+
+static int dln2_probe(struct usb_interface *interface,
+		      const struct usb_device_id *id)
+{
+	struct usb_host_interface *hostif = interface->cur_altsetting;
+	struct dln2_dev *dev;
+	int ret, i;
+
+	if (hostif->desc.bInterfaceNumber != 0 ||
+	    hostif->desc.bNumEndpoints < 2)
+		return -ENODEV;
+
+	/* allocate memory for our device state and initialize it */
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		dev_err(&interface->dev, "no memory for device state\n");
+		ret = -ENOMEM;
+		goto out;
+	}
+	dev->ep_out = hostif->endpoint[0].desc.bEndpointAddress;
+	dev->ep_in = hostif->endpoint[1].desc.bEndpointAddress;
+
+	dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
+	dev->interface = interface;
+
+	/* save our data pointer in this interface device */
+	usb_set_intfdata(interface, dev);
+
+	for (i = 0; i < DLN2_MAX_MODULES; i++) {
+		init_waitqueue_head(&dev->mod_rx_slots[i].wq);
+		spin_lock_init(&dev->mod_rx_slots[i].lock);
+	}
+
+	ret = dln2_setup_rx_urbs(dev, hostif);
+	if (ret) {
+		dln2_disconnect(interface);
+		return ret;
+	}
+
+	ret = dln2_hw_init(dev);
+	if (ret < 0) {
+		dev_err(&interface->dev, "failed to initialize hardware\n");
+		goto out_cleanup;
+	}
+
+	dev_dbg(&interface->dev, "connected " DRIVER_NAME "\n");
+
+	mutex_lock(&dln2_modules_mutex);
+	dln2_connect_modules(dev);
+	list_add(&dev->list, &dln2_dev_list);
+	mutex_unlock(&dln2_modules_mutex);
+
+	return 0;
+
+out_cleanup:
+	usb_set_intfdata(interface, NULL);
+	dln2_free(dev);
+out:
+	return ret;
+}
+
+void dln2_set_mod_context(struct dln2_mod *mod, void *context)
+{
+	mod->context = context;
+}
+EXPORT_SYMBOL(dln2_set_mod_context);
+
+void *dln2_get_mod_context(struct dln2_mod *mod)
+{
+	return mod->context;
+}
+EXPORT_SYMBOL(dln2_get_mod_context);
+
+void dln2_set_dev_context(struct dln2_dev *dev, struct dln2_mod *mod,
+			  void *context)
+{
+	int handle = dln2_get_handle(mod);
+
+	dev->context[handle] = context;
+}
+EXPORT_SYMBOL(dln2_set_dev_context);
+
+void *dln2_get_dev_context(struct dln2_dev *dev, struct dln2_mod *mod)
+{
+	int handle = dln2_get_handle(mod);
+
+	return dev->context[handle];
+}
+EXPORT_SYMBOL(dln2_get_dev_context);
+
+struct device *dln2_get_device(struct dln2_dev *dev)
+{
+	return &dev->interface->dev;
+}
+EXPORT_SYMBOL(dln2_get_device);
+
+static const struct usb_device_id dln2_table[] = {
+	{ USB_DEVICE(0xa257, 0x2013) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, dln2_table);
+
+static struct usb_driver dln2_driver = {
+	.name = DRIVER_NAME,
+	.probe = dln2_probe,
+	.disconnect = dln2_disconnect,
+	.id_table = dln2_table,
+};
+
+static struct dln2_mod_ops dln2_ctrl_mod_ops = {
+	.name = "dln2-ctrl",
+	.receive = NULL,
+	.connect = NULL,
+	.disconnect = NULL,
+};
+
+static int __init dln2_init(void)
+{
+	int err = 0;
+
+	dln2_ctrl_mod = dln2_register_module(-1, &dln2_ctrl_mod_ops);
+
+	if (IS_ERR(dln2_ctrl_mod)) {
+		err = PTR_ERR(dln2_ctrl_mod);
+		pr_err(DRIVER_NAME "dln2_register_module failed: %d\n", err);
+		return err;
+	}
+
+	err = usb_register_driver(&dln2_driver, THIS_MODULE, KBUILD_MODNAME);
+	if (err < 0)
+		pr_err(DRIVER_NAME "failed to register usb driver: %d\n", err);
+
+	return err;
+}
+module_init(dln2_init);
+
+static void __exit dln2_exit(void)
+{
+	usb_deregister(&dln2_driver);
+}
+module_exit(dln2_exit);
+
+MODULE_AUTHOR("Octavian Purdila <octavian.purdila@intel.com>");
+MODULE_DESCRIPTION(DRIVER_NAME " driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/usb/dln2.h b/include/linux/usb/dln2.h
new file mode 100644
index 0000000..3f7f8c6
--- /dev/null
+++ b/include/linux/usb/dln2.h
@@ -0,0 +1,146 @@
+#ifndef __LINUX_USB_DLN2_H
+#define __LINUX_USB_DLN2_H
+
+#include <linux/usb.h>
+
+struct dln2_header {
+	__le16 size;
+	__le16 id;
+	__le16 echo;
+	__le16 handle;
+} __packed;
+
+struct dln2_response {
+	struct dln2_header hdr;
+	__le16 result;
+} __packed;
+
+
+struct dln2_mod;
+struct dln2_dev;
+
+#define DLN2_CMD(cmd, id)		((cmd) | ((id) << 8))
+
+/**
+ * dln2_mod_ops - DLN2 module callbacks
+ *
+ * @name - name of the module
+ *
+ * @receive - called when a message is received for the handle
+ * associated with this module. It can be NULL, and in this case
+ * dln_transfer() can be use for this module and the receive path will
+ * be handled internally. If the receive callback is used the user
+ * must call usb_sumit_urb() after finishing reading the data.
+ *
+ * @connect - called when a new DLN2 device is connected. The module
+ * should perform any needed initialization and associated the module
+ * context to this device with dln2_set_dev_context().
+ *
+ * @disconnect - called when a DLN2 device is disconnected. The module
+ * should free any resources associated with this device.
+ */
+struct dln2_mod_ops {
+	const char *name;
+
+	void (*receive)(struct dln2_dev *dev, struct urb *urb,
+			struct dln2_response *res, void *data, int len);
+	int (*connect)(struct dln2_dev *dev);
+	void (*disconnect)(struct dln2_dev *dev);
+};
+
+/**
+ * dl2n_register_module - register a DLN2 module
+ *
+ * @handle - the requested handle for this module that is going to be
+ * passed to the hardware; a positive value to request a specific
+ * value, or -1 to automatically allocate one
+ *
+ * @mod_ops - see dln2_mod_ops()
+ *
+ * @return an ERR_PTR is return in case of error. In case of success a
+ * dln2_mod handle is returned. The module should use
+ * dln2_set_mod_context() to associate any private context to this
+ * module.
+ */
+struct dln2_mod *dln2_register_module(int handle, struct dln2_mod_ops *mod_ops);
+
+/**
+ * dl2n_register_module - unregister a DLN2 module
+ */
+void dln2_unregister_module(struct dln2_mod *mod);
+
+/**
+ * dln2_transfer - issue a DLN2 command and wait for a response and
+ * the associated data
+ *
+ * Only modules that did *NOT* register a receive callback will be
+ * able to use this function.
+ *
+ * @dev - the DLN2 device on which to issue the command
+ * @mod - the DLN2 module issuing the command
+ * @cmd - the command to be sent to the device
+ * @obuf - the buffer to be sent to the device; can be NULL if the
+ * user doesn't need to transmit data with this command
+ * @obuf_len - the size of the buffer to be sent to the device
+ * @ibuf - any data associated with the response will be copied here;
+ * it can be NULL if the user doesn't need the response data
+ * @ibuf_len - must be initialized to the input buffer size; it will
+ * be modified to indicate the actual data transfered
+ *
+ * @returns 0 for success, negative value for errors
+ */
+int dln2_transfer(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd,
+		  void *obuf, int obuf_len, void *ibuf, int *ibuf_len);
+
+/**
+ * dln2_send - issue a DLN2 command without waiting for a response
+ *
+ * @dev - the DLN2 device on which to issue the command
+ * @mod - the DLN2 module issuing the command
+ * @cmd - the command to be sent to the device
+ * @echo - this value will be echoed back in the response
+ * @obuf - the buffer to be sent to the device; can be NULL if the
+ * user doesn't need to transmit data with this command
+ * @obuf_len - the size of the buffer to be sent to the device
+ *
+ * @returns 0 for success, negative value for errors
+ */
+int dln2_send(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd, u16 echo,
+	      void *obuf, int obuf_len);
+
+/**
+ * dln2_set_mod_context - store a private pointer into dln2_mod
+ *
+ * @mod - the DLN2 module
+ * @context - the private pointer to be set
+ */
+void dln2_set_mod_context(struct dln2_mod *mod, void *context);
+
+/**
+ * dln2_set_mod_context - get the module private pointer from dln2_mod
+ *
+ * @mod - the DLN2 module
+ * @returns the module private pointer
+ */
+void *dln2_get_mod_context(struct dln2_mod *mod);
+
+
+/**
+ * dln2_set_mod_context - store a private pointer into dln2_dev
+ *
+ * @dev - the DLN2 device
+ * @context - the private pointer to be set
+ */
+void dln2_set_dev_context(struct dln2_dev *dev, struct dln2_mod *mod,
+			  void *context);
+/**
+ * dln2_set_mod_context - get the module private pointer from dln2_dev
+ *
+ * @dev - the DLN2 device
+ * @returns the device private pointer
+ */
+void *dln2_get_dev_context(struct dln2_dev *dev, struct dln2_mod *mod);
+
+struct device *dln2_get_device(struct dln2_dev *dev);
+
+#endif
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/3] i2c: add support for Diolan DLN-2 USB-I2C adapter
  2014-08-20 11:24 [PATCH 0/3] dln-2: Add support for Diolan DLN-2 devices Daniel Baluta
  2014-08-20 11:24 ` [PATCH 1/3] usb: add " Daniel Baluta
@ 2014-08-20 11:24 ` Daniel Baluta
       [not found] ` <1408533887-22727-1-git-send-email-daniel.baluta-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
  2 siblings, 0 replies; 10+ messages in thread
From: Daniel Baluta @ 2014-08-20 11:24 UTC (permalink / raw)
  To: gregkh, linux-usb, wsa, linux-i2c, linus.walleij, gnurou, linux-gpio
  Cc: pratyush.anand, pebolle, stern, octavian.purdila, matthew,
	linux-kernel, laurentiu.palcu, daniel.baluta, jdelvare, arnd,
	sjg

From: Laurentiu Palcu <laurentiu.palcu@intel.com>

This patch adds support for the Diolan DLN-2 I2C master module. Due
to hardware limitations it does not support SMBUS quick commands.

Information about the USB protocol interface can be found in the
Programmer's Reference Manual [1], see section 6.2.2 for the I2C
master module commands and responses.

[1] https://www.diolan.com/downloads/dln-api-manual.pdf

Signed-off-by: Laurentiu Palcu <laurentiu.palcu@intel.com>
---
 drivers/i2c/busses/Kconfig    |  11 ++
 drivers/i2c/busses/Makefile   |   1 +
 drivers/i2c/busses/i2c-dln2.c | 328 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 340 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-dln2.c

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 2ac87fa..06b1e89 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1021,4 +1021,15 @@ config SCx200_ACB
 	  This support is also available as a module.  If so, the module
 	  will be called scx200_acb.
 
+config I2C_DLN2
+       tristate "Diolan DLN-2 USB I2C adapter"
+       depends on USB
+       select USB_DLN2
+       help
+         If you say yes to this option, support will be included for Diolan
+         DLN2, a USB to I2C interface.
+
+         This driver can also be built as a module.  If so, the module
+         will be called i2c-dln2.
+
 endmenu
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 49bf07e..3118fea 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -100,5 +100,6 @@ obj-$(CONFIG_I2C_ELEKTOR)	+= i2c-elektor.o
 obj-$(CONFIG_I2C_PCA_ISA)	+= i2c-pca-isa.o
 obj-$(CONFIG_I2C_SIBYTE)	+= i2c-sibyte.o
 obj-$(CONFIG_SCx200_ACB)	+= scx200_acb.o
+obj-$(CONFIG_I2C_DLN2)		+= i2c-dln2.o
 
 ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG
diff --git a/drivers/i2c/busses/i2c-dln2.c b/drivers/i2c/busses/i2c-dln2.c
new file mode 100644
index 0000000..7befbb0
--- /dev/null
+++ b/drivers/i2c/busses/i2c-dln2.c
@@ -0,0 +1,328 @@
+/*
+ * Driver for the Diolan DLN-2 USB-I2C adapter
+ *
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * 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/dln2.h>
+#include <linux/i2c.h>
+
+#define DRIVER_NAME			"i2c-dln2"
+
+#define DLN2_I2C_MODULE_ID		0x03
+#define DLN2_I2C_CMD(cmd)		DLN2_CMD(cmd, DLN2_I2C_MODULE_ID)
+
+/* I2C commands */
+#define CMD_I2C_GET_PORT_COUNT		DLN2_I2C_CMD(0x00)
+#define CMD_I2C_ENABLE			DLN2_I2C_CMD(0x01)
+#define CMD_I2C_DISABLE			DLN2_I2C_CMD(0x02)
+#define CMD_I2C_IS_ENABLED		DLN2_I2C_CMD(0x03)
+#define CMD_I2C_SET_FREQUENCY		DLN2_I2C_CMD(0x04)
+#define CMD_I2C_GET_FREQUENCY		DLN2_I2C_CMD(0x05)
+#define CMD_I2C_WRITE			DLN2_I2C_CMD(0x06)
+#define CMD_I2C_READ			DLN2_I2C_CMD(0x07)
+#define CMD_I2C_SCAN_DEVICES		DLN2_I2C_CMD(0x08)
+#define CMD_I2C_PULLUP_ENABLE		DLN2_I2C_CMD(0x09)
+#define CMD_I2C_PULLUP_DISABLE		DLN2_I2C_CMD(0x0A)
+#define CMD_I2C_PULLUP_IS_ENABLED	DLN2_I2C_CMD(0x0B)
+#define CMD_I2C_TRANSFER		DLN2_I2C_CMD(0x0C)
+#define CMD_I2C_SET_MAX_REPLY_COUNT	DLN2_I2C_CMD(0x0D)
+#define CMD_I2C_GET_MAX_REPLY_COUNT	DLN2_I2C_CMD(0x0E)
+#define CMD_I2C_GET_MIN_FREQUENCY	DLN2_I2C_CMD(0x40)
+#define CMD_I2C_GET_MAX_FREQUENCY	DLN2_I2C_CMD(0x41)
+
+#define DLN2_I2C_FREQ_FAST		400000
+#define DLN2_I2C_FREQ_STD		100000
+
+#define DLN2_I2C_MAX_XFER_SIZE		256
+
+static struct dln2_mod *dln2_i2c_mod;
+
+/* Structure to hold all of our device specific stuff */
+struct dln2_i2c {
+	struct i2c_adapter adapter;	/* i2c related things */
+	struct dln2_dev *dln2;
+};
+
+static uint frequency = DLN2_I2C_FREQ_STD;	/* I2C clock frequency in Hz */
+
+module_param(frequency, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(frequency, "I2C clock frequency in hertz");
+
+static int dln2_i2c_set_state(struct dln2_i2c *dev, u8 state)
+{
+	int ret;
+	u8 port = 0;
+
+	ret = dln2_transfer(dev->dln2, dln2_i2c_mod,
+			    state ? CMD_I2C_ENABLE : CMD_I2C_DISABLE,
+			    &port, sizeof(port), NULL, NULL);
+
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+#define dln2_i2c_enable(dev)	dln2_i2c_set_state(dev, 1)
+#define dln2_i2c_disable(dev)	dln2_i2c_set_state(dev, 0)
+
+static int dln2_i2c_set_frequency(struct dln2_i2c *dev, u32 freq)
+{
+	int ret;
+	struct tx_data {
+		u8 port;
+		__le32 freq;
+	} __packed tx;
+
+	tx.port = 0;
+	tx.freq = cpu_to_le32(freq);
+
+	ret = dln2_transfer(dev->dln2, dln2_i2c_mod, CMD_I2C_SET_FREQUENCY,
+			    &tx, sizeof(tx), NULL, NULL);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int dln2_i2c_write(struct dln2_i2c *dev, u8 addr,
+			  u8 *data, u16 data_len)
+{
+	int ret, len;
+	struct tx_data {
+		u8 port;
+		u8 addr;
+		u8 mem_addr_len;
+		__le32 mem_addr;
+		__le16 buf_len;
+		u8 buf[DLN2_I2C_MAX_XFER_SIZE];
+	} __packed tx;
+
+	if (data_len > DLN2_I2C_MAX_XFER_SIZE)
+		return -ENOSPC;
+
+	tx.port = 0;
+	tx.addr = addr;
+	tx.mem_addr_len = 0;
+	tx.mem_addr = 0;
+	tx.buf_len = cpu_to_le16(data_len);
+	memcpy(tx.buf, data, data_len);
+
+	len = sizeof(tx) + data_len - DLN2_I2C_MAX_XFER_SIZE;
+	ret = dln2_transfer(dev->dln2, dln2_i2c_mod, CMD_I2C_WRITE,
+			    &tx, len, NULL, NULL);
+	if (ret < 0)
+		return ret;
+
+	return data_len;
+}
+
+static int dln2_i2c_read(struct dln2_i2c *dev, u16 addr, u8 *data,
+			 u16 data_len)
+{
+	struct tx_data {
+		u8 port;
+		u8 addr;
+		u8 mem_addr_len;
+		__le32 mem_addr;
+		__le16 buf_len;
+	} __packed tx;
+	struct rx_data {
+		__le16 buf_len;
+		u8 buf[DLN2_I2C_MAX_XFER_SIZE];
+	} __packed rx;
+	int ret, rx_len = sizeof(rx), buf_len;
+
+
+	tx.port = 0;
+	tx.addr = addr;
+	tx.mem_addr_len = 0;
+	tx.mem_addr = 0;
+	tx.buf_len = cpu_to_le16(data_len);
+
+	ret = dln2_transfer(dev->dln2, dln2_i2c_mod, CMD_I2C_READ, &tx,
+			    sizeof(tx), &rx, &rx_len);
+	if (ret < 0)
+		return ret;
+
+	buf_len = le16_to_cpu(rx.buf_len);
+
+	if (rx_len < buf_len) {
+		dev_warn(&dev->adapter.dev, "RX len mismatch: %d %d\n",
+			 rx_len, buf_len);
+		buf_len = rx_len;
+	}
+
+	dev_dbg(&dev->adapter.dev, "Rcvd: %d bytes", buf_len);
+
+	memcpy(data, rx.buf, min(rx_len, buf_len));
+
+	return rx_len;
+}
+
+static int dln2_i2c_setup(struct dln2_i2c *dev)
+{
+	int ret;
+
+	ret = dln2_i2c_disable(dev);
+	if (ret < 0)
+		return ret;
+
+	/* Set I2C frequency */
+	ret = dln2_i2c_set_frequency(dev, frequency);
+	if (ret < 0)
+		return ret;
+
+	ret = dln2_i2c_enable(dev);
+
+	return ret;
+}
+
+static int dln2_i2c_xfer(struct i2c_adapter *adapter,
+			 struct i2c_msg *msgs, int num)
+{
+	struct dln2_i2c *dev = i2c_get_adapdata(adapter);
+	struct i2c_msg *pmsg;
+	int i;
+	int ret = 0;
+
+	for (i = 0; i < num; i++) {
+		pmsg = &msgs[i];
+
+		if (pmsg->len > DLN2_I2C_MAX_XFER_SIZE)
+			return -EINVAL;
+
+		if (pmsg->flags & I2C_M_RD) {
+			ret = dln2_i2c_read(dev, pmsg->addr, pmsg->buf,
+					    pmsg->len);
+			if (ret < 0)
+				return ret;
+
+			pmsg->len = ret;
+		} else {
+			ret = dln2_i2c_write(dev, pmsg->addr, pmsg->buf,
+					     pmsg->len);
+			if (ret != pmsg->len)
+				return -EPROTO;
+		}
+	}
+
+	return num;
+}
+
+/*
+ * Return list of supported functionality.
+ */
+static u32 dln2_i2c_func(struct i2c_adapter *a)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
+		I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL;
+}
+
+static const struct i2c_algorithm dln2_i2c_usb_algorithm = {
+	.master_xfer = dln2_i2c_xfer,
+	.functionality = dln2_i2c_func,
+};
+
+/* device layer */
+
+static int dln2_i2c_connect(struct dln2_dev *dln2)
+{
+	int ret;
+	struct dln2_i2c *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+
+	if (!dev)
+		return -ENOMEM;
+
+	/* setup i2c adapter description */
+	dev->adapter.owner = THIS_MODULE;
+	dev->adapter.class = I2C_CLASS_HWMON;
+	dev->adapter.algo = &dln2_i2c_usb_algorithm;
+	i2c_set_adapdata(&dev->adapter, dev);
+	snprintf(dev->adapter.name, sizeof(dev->adapter.name), DRIVER_NAME);
+
+	dev->adapter.dev.parent = dln2_get_device(dln2);
+
+	dev->dln2 = dln2;
+	dln2_set_dev_context(dln2, dln2_i2c_mod, dev);
+
+	/* initialize the i2c interface */
+	ret = dln2_i2c_setup(dev);
+	if (ret < 0) {
+		dev_err(&dev->adapter.dev, "failed to initialize adapter\n");
+		goto error_free;
+	}
+
+	/* and finally attach to i2c layer */
+	ret = i2c_add_adapter(&dev->adapter);
+	if (ret < 0) {
+		dev_err(&dev->adapter.dev, "failed to add I2C adapter\n");
+		goto error_free;
+	}
+
+	dev_dbg(&dev->adapter.dev, "connected " DRIVER_NAME "\n");
+
+	return 0;
+
+error_free:
+	kfree(dev);
+
+	return ret;
+}
+
+static void dln2_i2c_disconnect(struct dln2_dev *dln2)
+{
+	struct dln2_i2c *dev = dln2_get_dev_context(dln2, dln2_i2c_mod);
+
+	i2c_del_adapter(&dev->adapter);
+
+	dev_dbg(&dev->adapter.dev, "disconnected" DRIVER_NAME "\n");
+}
+
+static struct dln2_mod_ops dln2_i2c_ops = {
+	.name = DRIVER_NAME,
+	.connect = dln2_i2c_connect,
+	.disconnect = dln2_i2c_disconnect,
+};
+
+static int __init dln2_i2c_init(void)
+{
+	int err;
+
+	dln2_i2c_mod = dln2_register_module(-1, &dln2_i2c_ops);
+
+	if (IS_ERR(dln2_i2c_mod)) {
+		err = PTR_ERR(dln2_i2c_mod);
+		dln2_i2c_mod = NULL;
+		pr_err(DRIVER_NAME "dln2_register_module failed: %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+module_init(dln2_i2c_init);
+
+static void __exit dln2_i2c_exit(void)
+{
+	if (dln2_i2c_mod)
+		dln2_unregister_module(dln2_i2c_mod);
+}
+module_exit(dln2_i2c_exit);
+
+MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@intel.com>");
+MODULE_DESCRIPTION(DRIVER_NAME " driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/3] gpio: add support for the Diolan DLN-2 USB-GPIO driver
  2014-08-20 11:24 [PATCH 0/3] dln-2: Add support for Diolan DLN-2 devices Daniel Baluta
@ 2014-08-20 11:24     ` Daniel Baluta
  2014-08-20 11:24 ` [PATCH 2/3] i2c: add support for Diolan DLN-2 USB-I2C adapter Daniel Baluta
       [not found] ` <1408533887-22727-1-git-send-email-daniel.baluta-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
  2 siblings, 0 replies; 10+ messages in thread
From: Daniel Baluta @ 2014-08-20 11:24 UTC (permalink / raw)
  To: gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r,
	linux-usb-u79uwXL29TY76Z2rM5mHXA, wsa-z923LK4zBo2bacvFa/9K2g,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linus.walleij-QSEj5FYQhm4dnm+yROfE0A,
	gnurou-Re5JQEeQqe8AvxtiuMwx3w, linux-gpio-u79uwXL29TY76Z2rM5mHXA
  Cc: pratyush.anand-qxv4g6HH51o, pebolle-IWqWACnzNjzz+pZb47iToQ,
	stern-nwvwT67g6+6dFdvTe/nMLpVzexx5G7lz,
	octavian.purdila-ral2JQCrhuEAvxtiuMwx3w,
	matthew-AIaitxs59ERIo2TaICnI/Q,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	laurentiu.palcu-ral2JQCrhuEAvxtiuMwx3w,
	daniel.baluta-ral2JQCrhuEAvxtiuMwx3w, jdelvare-l3A5Bk7waGM,
	arnd-r2nGTMty4D4, sjg-F7+t8E8rja9g9hUCZPvPmw

This patch adds GPIO and IRQ support for the Diolan DLN-2 GPIO module.

Information about the USB protocol interface can be found in the
Programmer's Reference Manual [1], see section 2.9 for the GPIO
module commands and responses.

[1] https://www.diolan.com/downloads/dln-api-manual.pdf

Signed-off-by: Daniel Baluta <daniel.baluta-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
---
 drivers/gpio/Kconfig     |  13 ++
 drivers/gpio/Makefile    |   1 +
 drivers/gpio/gpio-dln2.c | 571 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 585 insertions(+)
 create mode 100644 drivers/gpio/gpio-dln2.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 9de1515..1da9857 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -897,4 +897,17 @@ config GPIO_VIPERBOARD
           River Tech's viperboard.h for detailed meaning
           of the module parameters.
 
+config GPIO_DLN2
+	tristate "Diolan DLN2 GPIO support"
+	depends on USB
+	select USB_DLN2
+	select IRQ_DOMAIN
+
+	help
+	  Select this option to enable GPIO driver for the Diolan DLN2
+	  board.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called gpio-dln2.
+
 endif
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 5d024e3..eaa97a0 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_GPIO_CRYSTAL_COVE)	+= gpio-crystalcove.o
 obj-$(CONFIG_GPIO_DA9052)	+= gpio-da9052.o
 obj-$(CONFIG_GPIO_DA9055)	+= gpio-da9055.o
 obj-$(CONFIG_GPIO_DAVINCI)	+= gpio-davinci.o
+obj-$(CONFIG_GPIO_DLN2)		+= gpio-dln2.o
 obj-$(CONFIG_GPIO_DWAPB)	+= gpio-dwapb.o
 obj-$(CONFIG_GPIO_EM)		+= gpio-em.o
 obj-$(CONFIG_GPIO_EP93XX)	+= gpio-ep93xx.o
diff --git a/drivers/gpio/gpio-dln2.c b/drivers/gpio/gpio-dln2.c
new file mode 100644
index 0000000..20d2e07
--- /dev/null
+++ b/drivers/gpio/gpio-dln2.c
@@ -0,0 +1,571 @@
+/*
+ * Driver for the Diolan DLN-2 USB-GPIO adapter
+ *
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * 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/slab.h>
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/irqdomain.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/usb.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/ptrace.h>
+#include <linux/wait.h>
+#include <linux/usb/dln2.h>
+
+#define DRIVER_NAME "gpio-dln2"
+
+#define DLN2_GPIO_ID			0x01
+
+#define CMD_GPIO_GET_PORT_COUNT		DLN2_CMD(0x00, DLN2_GPIO_ID)
+#define CMD_GPIO_GET_PIN_COUNT		DLN2_CMD(0x01, DLN2_GPIO_ID)
+#define CMD_GPIO_SET_DEBOUNCE		DLN2_CMD(0x04, DLN2_GPIO_ID)
+#define CMD_GPIO_GET_DEBOUNCE		DLN2_CMD(0x05, DLN2_GPIO_ID)
+#define CMD_GPIO_PORT_GET_VAL		DLN2_CMD(0x06, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_GET_VAL		DLN2_CMD(0x0B, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_SET_OUT_VAL	DLN2_CMD(0x0C, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_GET_OUT_VAL	DLN2_CMD(0x0D, DLN2_GPIO_ID)
+#define CMD_GPIO_CONDITION_MET_EV	DLN2_CMD(0x0F, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_ENABLE		DLN2_CMD(0x10, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_DISABLE		DLN2_CMD(0x11, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_SET_DIRECTION	DLN2_CMD(0x13, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_GET_DIRECTION	DLN2_CMD(0x14, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_SET_EVENT_CFG	DLN2_CMD(0x1E, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_GET_EVENT_CFG	DLN2_CMD(0x1F, DLN2_GPIO_ID)
+
+#define DLN2_GPIO_EVENT_NONE		0
+#define DLN2_GPIO_EVENT_CHANGE		1
+#define DLN2_GPIO_EVENT_LVL_HIGH	2
+#define DLN2_GPIO_EVENT_LVL_LOW		3
+#define DLN2_GPIO_EVENT_CHANGE_RISING	0x11
+#define DLN2_GPIO_EVENT_CHANGE_FALLING  0x21
+#define DLN2_GPIO_EVENT_MASK		0x0F
+
+struct dln2_mod *dln2_gpio_mod, *dln2_irq_mod;
+
+#define DLN2_GPIO_MAX_PINS 32
+
+struct dln2_irq_work {
+	struct work_struct work;
+	struct dln2_gpio *dev;
+	int pin, type;
+};
+
+struct dln2_gpio {
+	struct dln2_dev *dln2;
+
+	struct gpio_chip gpio;
+
+	struct irq_domain *irq_domain;
+	DECLARE_BITMAP(irqs_masked, DLN2_GPIO_MAX_PINS);
+	DECLARE_BITMAP(irqs_enabled, DLN2_GPIO_MAX_PINS);
+	DECLARE_BITMAP(irqs_pending, DLN2_GPIO_MAX_PINS);
+	struct dln2_irq_work irq_work[DLN2_GPIO_MAX_PINS];
+};
+
+struct dln2_gpio_pin {
+	__le16 pin;
+} __packed;
+
+struct dln2_gpio_pin_val {
+	__le16 pin;
+	u8 value;
+} __packed;
+
+int dln2_gpio_get_pin_count(struct dln2_dev *dln2)
+{
+	__le16 count;
+	int ret, len = sizeof(count);
+
+	ret = dln2_transfer(dln2, dln2_gpio_mod, CMD_GPIO_GET_PIN_COUNT,
+			    NULL, 0, &count, &len);
+	if (ret < 0)
+		return ret;
+
+	return le16_to_cpu(count);
+}
+
+static int dln2_gpio_pin_cmd(struct dln2_dev *dln2, int cmd, unsigned pin)
+{
+	struct dln2_gpio_pin req = {
+		.pin = cpu_to_le16(pin),
+	};
+
+	return dln2_transfer(dln2, dln2_gpio_mod, cmd, &req, sizeof(req),
+			     NULL, NULL);
+}
+
+
+static int dln2_gpio_pin_val(struct dln2_gpio *dev, int cmd, unsigned int pin)
+{
+	struct dln2_gpio_pin req = {
+		.pin = cpu_to_le16(pin),
+	};
+	struct dln2_gpio_pin_val rsp;
+	int ret, len = sizeof(rsp);
+
+	ret = dln2_transfer(dev->dln2, dln2_gpio_mod, cmd, &req, sizeof(req),
+			    &rsp, &len);
+	if (ret < 0)
+		return ret;
+
+	if (req.pin != rsp.pin) {
+		dev_err(dev->gpio.dev, "bad pin response: %d %d\n",
+			pin, le16_to_cpu(rsp.pin));
+		return -EPROTO;
+	}
+
+	return rsp.value;
+}
+
+static int dln2_gpio_pin_get_in_val(struct dln2_gpio *dev, unsigned int pin)
+{
+	int ret = dln2_gpio_pin_val(dev, CMD_GPIO_PIN_GET_VAL, pin);
+
+	if (ret < 0)
+		return ret;
+	return !!ret;
+}
+
+static int dln2_gpio_pin_get_out_val(struct dln2_gpio *dev, unsigned int pin)
+{
+	int ret = dln2_gpio_pin_val(dev, CMD_GPIO_PIN_GET_OUT_VAL, pin);
+
+	if (ret < 0)
+		return ret;
+	return !!ret;
+}
+
+static void dln2_gpio_pin_set_out_val(struct dln2_dev *dln2, unsigned int pin,
+				      int value)
+{
+	struct dln2_gpio_pin_val req = {
+		.pin = cpu_to_le16(pin),
+		.value = cpu_to_le16(value),
+	};
+
+	dln2_transfer(dln2, dln2_gpio_mod, CMD_GPIO_PIN_SET_OUT_VAL,
+		      &req, sizeof(req), NULL, NULL);
+}
+
+static int dln2_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+
+	return dln2_gpio_pin_cmd(dev->dln2, CMD_GPIO_PIN_ENABLE, offset);
+}
+
+static void dln2_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+
+	dln2_gpio_pin_cmd(dev->dln2, CMD_GPIO_PIN_DISABLE, offset);
+}
+
+static int dln2_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+	struct dln2_gpio_pin req = {
+		.pin = cpu_to_le16(offset),
+	};
+	struct dln2_gpio_pin_val rsp;
+	int ret, len = sizeof(rsp);
+
+	ret = dln2_transfer(dev->dln2, dln2_gpio_mod,
+			    CMD_GPIO_PIN_GET_DIRECTION,
+			    &req, sizeof(req), &rsp, &len);
+	if (ret < 0)
+		return ret;
+
+	if (req.pin != rsp.pin) {
+		dev_err(dev->gpio.dev, "bad pin response: %d %d\n",
+			offset, le16_to_cpu(rsp.pin));
+		return -EPROTO;
+	}
+
+	/* DLN2 GPIO direction is 0 for input and 1 for output */
+	return !rsp.value;
+}
+
+static int dln2_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+	int dir = dln2_gpio_get_direction(chip, offset);
+
+	if (dir < 0)
+		return dir;
+
+	if (dir)
+		return dln2_gpio_pin_get_in_val(dev, offset);
+	else
+		return dln2_gpio_pin_get_out_val(dev, offset);
+}
+
+static void dln2_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+
+	dln2_gpio_pin_set_out_val(dev->dln2, offset, value);
+}
+
+static int dln2_gpio_set_direction(struct gpio_chip *chip, unsigned offset,
+				   unsigned dir)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+	struct dln2_gpio_pin_val req = {
+		.pin = cpu_to_le16(offset),
+		.value = cpu_to_le16(dir),
+	};
+
+	return dln2_transfer(dev->dln2, dln2_gpio_mod,
+			     CMD_GPIO_PIN_SET_DIRECTION,
+			     &req, sizeof(req), NULL, NULL);
+}
+
+#define DLN2_GPIO_DIRECTION_IN		0
+#define DLN2_GPIO_DIRECTION_OUT		1
+
+static int dln2_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+	return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_IN);
+}
+
+static int dln2_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+				      int value)
+{
+	return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_OUT);
+}
+
+static int dln2_gpio_set_debounce(struct gpio_chip *chip, unsigned offset,
+				  unsigned debounce)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+	struct {
+		__le32 duration;
+	} __packed req = {
+		.duration = cpu_to_le32(debounce),
+	};
+
+	return dln2_transfer(dev->dln2, dln2_gpio_mod, CMD_GPIO_SET_DEBOUNCE,
+			     &req, sizeof(req), NULL, NULL);
+}
+
+static int dln2_gpio_set_event_cfg(struct dln2_dev *dln2, unsigned pin,
+				   unsigned type, unsigned period)
+{
+	struct {
+		__le16 pin;
+		u8 type;
+		__le16 period;
+	} __packed req = {
+		.pin = cpu_to_le16(pin),
+		.type = type,
+		.period = cpu_to_le16(period),
+	};
+
+	return dln2_transfer(dln2, dln2_gpio_mod, CMD_GPIO_PIN_SET_EVENT_CFG,
+			     &req, sizeof(req), NULL, NULL);
+}
+
+static int dln2_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+
+	return irq_create_mapping(dev->irq_domain, offset);
+}
+
+
+static void dln2_irq_work(struct work_struct *w)
+{
+	struct dln2_irq_work *iw = container_of(w, struct dln2_irq_work, work);
+	struct dln2_gpio *dev = iw->dev;
+	struct dln2_dev *dln2 = dev->dln2;
+	u8 type = iw->type & DLN2_GPIO_EVENT_MASK;
+
+	/* USB device has been disconnected, bail out */
+	if (!dln2)
+		return;
+
+	if (test_bit(iw->pin, dev->irqs_enabled))
+		dln2_gpio_set_event_cfg(dln2, iw->pin, type, 0);
+	else
+		dln2_gpio_set_event_cfg(dln2, iw->pin, DLN2_GPIO_EVENT_NONE, 0);
+}
+
+static void dln2_irq_enable(struct irq_data *irqd)
+{
+	struct dln2_gpio *dev = irq_data_get_irq_chip_data(irqd);
+	int pin = irqd_to_hwirq(irqd);
+
+	set_bit(pin, dev->irqs_enabled);
+	schedule_work(&dev->irq_work[pin].work);
+}
+
+static void dln2_irq_disable(struct irq_data *irqd)
+{
+	struct dln2_gpio *dev = irq_data_get_irq_chip_data(irqd);
+	int pin = irqd_to_hwirq(irqd);
+
+	clear_bit(pin, dev->irqs_enabled);
+	schedule_work(&dev->irq_work[pin].work);
+}
+
+static void dln2_irq_mask(struct irq_data *irqd)
+{
+	struct dln2_gpio *dev = irq_data_get_irq_chip_data(irqd);
+	int pin = irqd_to_hwirq(irqd);
+
+	set_bit(pin, dev->irqs_masked);
+}
+
+static void dln2_irq_unmask(struct irq_data *irqd)
+{
+	struct dln2_gpio *dev = irq_data_get_irq_chip_data(irqd);
+	int pin = irqd_to_hwirq(irqd);
+
+	if (test_and_clear_bit(pin, dev->irqs_masked))
+		generic_handle_irq(pin);
+}
+
+static int dln2_irq_set_type(struct irq_data *irqd, unsigned type)
+{
+	struct dln2_gpio *dev = irq_data_get_irq_chip_data(irqd);
+	int pin = irqd_to_hwirq(irqd);
+
+	switch (type) {
+	case IRQ_TYPE_LEVEL_HIGH:
+		dev->irq_work[pin].type = DLN2_GPIO_EVENT_LVL_HIGH;
+		break;
+	case IRQ_TYPE_LEVEL_LOW:
+		dev->irq_work[pin].type = DLN2_GPIO_EVENT_LVL_LOW;
+		break;
+	case IRQ_TYPE_EDGE_BOTH:
+		dev->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE;
+		break;
+	case IRQ_TYPE_EDGE_RISING:
+		dev->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE_RISING;
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		dev->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE_FALLING;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static struct irq_chip dln2_gpio_irqchip = {
+	.name = "dln2-irq",
+	.irq_enable = dln2_irq_enable,
+	.irq_disable = dln2_irq_disable,
+	.irq_mask = dln2_irq_mask,
+	.irq_unmask = dln2_irq_unmask,
+	.irq_set_type = dln2_irq_set_type,
+};
+
+static int dln2_gpio_irq_map(struct irq_domain *irqd, unsigned int irq,
+			     irq_hw_number_t hwirq)
+{
+	irq_set_chip_data(irq, irqd->host_data);
+	irq_set_chip_and_handler(irq, &dln2_gpio_irqchip, handle_simple_irq);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops dln2_domain_irq_ops = {
+	.map = dln2_gpio_irq_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+
+static int dln2_gpio_connect(struct dln2_dev *dln2)
+{
+	struct dln2_gpio *dev;
+	struct device *d = dln2_get_device(dln2);
+	int pins = dln2_gpio_get_pin_count(dln2), i, ret;
+
+	if (pins < 0) {
+		dev_err(d, "failed to get pin count: %d\n", pins);
+		return pins;
+	}
+	if (pins > DLN2_GPIO_MAX_PINS)
+		dev_warn(d, "clamping pins to %d\n", DLN2_GPIO_MAX_PINS);
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		dev_err(d, "fail to allocate for device state\n");
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < DLN2_GPIO_MAX_PINS; i++) {
+		INIT_WORK(&dev->irq_work[i].work, dln2_irq_work);
+		dev->irq_work[i].pin = i;
+		dev->irq_work[i].dev = dev;
+	}
+
+	dev->irq_domain = irq_domain_add_simple(NULL, pins, 0,
+						&dln2_domain_irq_ops, dev);
+	if (!dev->irq_domain) {
+		kfree(dev);
+		return -ENOMEM;
+	}
+
+	dev->dln2 = dln2;
+
+	dev->gpio.label = "dln2";
+	dev->gpio.dev = d;
+	dev->gpio.owner = THIS_MODULE;
+	dev->gpio.base = -1;
+	dev->gpio.ngpio = pins;
+	dev->gpio.exported = 1;
+
+	dev->gpio.set = dln2_gpio_set;
+	dev->gpio.get = dln2_gpio_get;
+	dev->gpio.request = dln2_gpio_request;
+	dev->gpio.free = dln2_gpio_free;
+	dev->gpio.get_direction = dln2_gpio_get_direction;
+	dev->gpio.direction_input = dln2_gpio_direction_input;
+	dev->gpio.direction_output = dln2_gpio_direction_output;
+	dev->gpio.set_debounce = dln2_gpio_set_debounce;
+
+	dev->gpio.to_irq = dln2_gpio_to_irq;
+
+	dln2_set_dev_context(dln2, dln2_gpio_mod, dev);
+
+	ret = gpiochip_add(&dev->gpio);
+	if (ret < 0) {
+		irq_domain_remove(dev->irq_domain);
+		kfree(dev);
+		dev_err(dev->gpio.dev, "error adding gpio chip: %d\n", ret);
+		return ret;
+	}
+
+	dev_dbg(dev->gpio.dev, "connected " DRIVER_NAME "\n");
+	return 0;
+}
+
+static void dln2_gpio_disconnect(struct dln2_dev *dln2)
+{
+	struct dln2_gpio *dev = dln2_get_dev_context(dln2, dln2_gpio_mod);
+	int ret;
+
+	irq_domain_remove(dev->irq_domain);
+	ret = gpiochip_remove(&dev->gpio);
+	if (ret < 0) {
+		dev_warn(dev->gpio.dev, "error removing gpio chip: %d\n", ret);
+		dev->dln2 = NULL;
+		return;
+	}
+
+	kfree(dev);
+
+	dev_dbg(dev->gpio.dev, "disconnected " DRIVER_NAME "\n");
+}
+
+static struct dln2_mod_ops dln2_gpio_ops = {
+	.name = DRIVER_NAME,
+	.connect = dln2_gpio_connect,
+	.disconnect = dln2_gpio_disconnect,
+};
+
+static void dln2_gpio_event(struct dln2_dev *dln2, struct urb *urb,
+			    struct dln2_response *rsp, void *data, int len)
+{
+	struct dln2_gpio *dev = dln2_get_dev_context(dln2, dln2_gpio_mod);
+	struct {
+		__le16 count;
+		__u8 type;
+		__le16 pin;
+		__u8 value;
+	} __packed *event = data;
+	int pin, irq, id = le16_to_cpu(rsp->hdr.id);
+
+	if (id != CMD_GPIO_CONDITION_MET_EV) {
+		dev_err(dev->gpio.dev, "unexpected cmd %x\n", id);
+		goto out_complete;
+	}
+
+	pin = le16_to_cpu(event->pin);
+	irq = irq_find_mapping(dev->irq_domain, pin);
+
+	if (!irq) {
+		dev_err(dev->gpio.dev, "pin %d not mapped to IRQ\n", pin);
+		goto out_complete;
+	}
+
+	if (!test_bit(pin, dev->irqs_enabled) ||
+	    test_bit(pin, dev->irqs_masked))
+		goto out_complete;
+
+	switch (dev->irq_work[pin].type) {
+	case DLN2_GPIO_EVENT_CHANGE_RISING:
+		if (event->value)
+			generic_handle_irq(irq);
+		break;
+	case DLN2_GPIO_EVENT_CHANGE_FALLING:
+		if (!event->value)
+			generic_handle_irq(irq);
+		break;
+	default:
+		generic_handle_irq(irq);
+	}
+
+out_complete:
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static struct dln2_mod_ops dln2_irq_ops = {
+	.name = "irq-" DRIVER_NAME,
+	.receive = dln2_gpio_event,
+};
+
+static int __init dln2_gpio_init(void)
+{
+	int err;
+
+	dln2_gpio_mod = dln2_register_module(-1, &dln2_gpio_ops);
+
+	if (IS_ERR(dln2_gpio_mod)) {
+		err = PTR_ERR(dln2_gpio_mod);
+		dln2_gpio_mod = NULL;
+		pr_err(DRIVER_NAME "dln2_register_module failed: %d\n", err);
+		return err;
+	}
+
+	dln2_irq_mod = dln2_register_module(0, &dln2_irq_ops);
+	if (IS_ERR(dln2_irq_mod)) {
+		err = PTR_ERR(dln2_irq_mod);
+		dln2_irq_mod = NULL;
+		dln2_unregister_module(dln2_gpio_mod);
+		dln2_gpio_mod = NULL;
+		pr_err(DRIVER_NAME "dln2_register_module failed: %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+module_init(dln2_gpio_init);
+
+static void __exit dln2_gpio_exit(void)
+{
+	if (dln2_gpio_mod)
+		dln2_unregister_module(dln2_gpio_mod);
+}
+module_exit(dln2_gpio_exit);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org");
+MODULE_DESCRIPTION(DRIVER_NAME "driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1

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

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/3] gpio: add support for the Diolan DLN-2 USB-GPIO driver
@ 2014-08-20 11:24     ` Daniel Baluta
  0 siblings, 0 replies; 10+ messages in thread
From: Daniel Baluta @ 2014-08-20 11:24 UTC (permalink / raw)
  To: gregkh, linux-usb, wsa, linux-i2c, linus.walleij, gnurou, linux-gpio
  Cc: pratyush.anand, pebolle, stern, octavian.purdila, matthew,
	linux-kernel, laurentiu.palcu, daniel.baluta, jdelvare, arnd,
	sjg

This patch adds GPIO and IRQ support for the Diolan DLN-2 GPIO module.

Information about the USB protocol interface can be found in the
Programmer's Reference Manual [1], see section 2.9 for the GPIO
module commands and responses.

[1] https://www.diolan.com/downloads/dln-api-manual.pdf

Signed-off-by: Daniel Baluta <daniel.baluta@intel.com>
---
 drivers/gpio/Kconfig     |  13 ++
 drivers/gpio/Makefile    |   1 +
 drivers/gpio/gpio-dln2.c | 571 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 585 insertions(+)
 create mode 100644 drivers/gpio/gpio-dln2.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 9de1515..1da9857 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -897,4 +897,17 @@ config GPIO_VIPERBOARD
           River Tech's viperboard.h for detailed meaning
           of the module parameters.
 
+config GPIO_DLN2
+	tristate "Diolan DLN2 GPIO support"
+	depends on USB
+	select USB_DLN2
+	select IRQ_DOMAIN
+
+	help
+	  Select this option to enable GPIO driver for the Diolan DLN2
+	  board.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called gpio-dln2.
+
 endif
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 5d024e3..eaa97a0 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_GPIO_CRYSTAL_COVE)	+= gpio-crystalcove.o
 obj-$(CONFIG_GPIO_DA9052)	+= gpio-da9052.o
 obj-$(CONFIG_GPIO_DA9055)	+= gpio-da9055.o
 obj-$(CONFIG_GPIO_DAVINCI)	+= gpio-davinci.o
+obj-$(CONFIG_GPIO_DLN2)		+= gpio-dln2.o
 obj-$(CONFIG_GPIO_DWAPB)	+= gpio-dwapb.o
 obj-$(CONFIG_GPIO_EM)		+= gpio-em.o
 obj-$(CONFIG_GPIO_EP93XX)	+= gpio-ep93xx.o
diff --git a/drivers/gpio/gpio-dln2.c b/drivers/gpio/gpio-dln2.c
new file mode 100644
index 0000000..20d2e07
--- /dev/null
+++ b/drivers/gpio/gpio-dln2.c
@@ -0,0 +1,571 @@
+/*
+ * Driver for the Diolan DLN-2 USB-GPIO adapter
+ *
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * 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/slab.h>
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/irqdomain.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/usb.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/ptrace.h>
+#include <linux/wait.h>
+#include <linux/usb/dln2.h>
+
+#define DRIVER_NAME "gpio-dln2"
+
+#define DLN2_GPIO_ID			0x01
+
+#define CMD_GPIO_GET_PORT_COUNT		DLN2_CMD(0x00, DLN2_GPIO_ID)
+#define CMD_GPIO_GET_PIN_COUNT		DLN2_CMD(0x01, DLN2_GPIO_ID)
+#define CMD_GPIO_SET_DEBOUNCE		DLN2_CMD(0x04, DLN2_GPIO_ID)
+#define CMD_GPIO_GET_DEBOUNCE		DLN2_CMD(0x05, DLN2_GPIO_ID)
+#define CMD_GPIO_PORT_GET_VAL		DLN2_CMD(0x06, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_GET_VAL		DLN2_CMD(0x0B, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_SET_OUT_VAL	DLN2_CMD(0x0C, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_GET_OUT_VAL	DLN2_CMD(0x0D, DLN2_GPIO_ID)
+#define CMD_GPIO_CONDITION_MET_EV	DLN2_CMD(0x0F, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_ENABLE		DLN2_CMD(0x10, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_DISABLE		DLN2_CMD(0x11, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_SET_DIRECTION	DLN2_CMD(0x13, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_GET_DIRECTION	DLN2_CMD(0x14, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_SET_EVENT_CFG	DLN2_CMD(0x1E, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_GET_EVENT_CFG	DLN2_CMD(0x1F, DLN2_GPIO_ID)
+
+#define DLN2_GPIO_EVENT_NONE		0
+#define DLN2_GPIO_EVENT_CHANGE		1
+#define DLN2_GPIO_EVENT_LVL_HIGH	2
+#define DLN2_GPIO_EVENT_LVL_LOW		3
+#define DLN2_GPIO_EVENT_CHANGE_RISING	0x11
+#define DLN2_GPIO_EVENT_CHANGE_FALLING  0x21
+#define DLN2_GPIO_EVENT_MASK		0x0F
+
+struct dln2_mod *dln2_gpio_mod, *dln2_irq_mod;
+
+#define DLN2_GPIO_MAX_PINS 32
+
+struct dln2_irq_work {
+	struct work_struct work;
+	struct dln2_gpio *dev;
+	int pin, type;
+};
+
+struct dln2_gpio {
+	struct dln2_dev *dln2;
+
+	struct gpio_chip gpio;
+
+	struct irq_domain *irq_domain;
+	DECLARE_BITMAP(irqs_masked, DLN2_GPIO_MAX_PINS);
+	DECLARE_BITMAP(irqs_enabled, DLN2_GPIO_MAX_PINS);
+	DECLARE_BITMAP(irqs_pending, DLN2_GPIO_MAX_PINS);
+	struct dln2_irq_work irq_work[DLN2_GPIO_MAX_PINS];
+};
+
+struct dln2_gpio_pin {
+	__le16 pin;
+} __packed;
+
+struct dln2_gpio_pin_val {
+	__le16 pin;
+	u8 value;
+} __packed;
+
+int dln2_gpio_get_pin_count(struct dln2_dev *dln2)
+{
+	__le16 count;
+	int ret, len = sizeof(count);
+
+	ret = dln2_transfer(dln2, dln2_gpio_mod, CMD_GPIO_GET_PIN_COUNT,
+			    NULL, 0, &count, &len);
+	if (ret < 0)
+		return ret;
+
+	return le16_to_cpu(count);
+}
+
+static int dln2_gpio_pin_cmd(struct dln2_dev *dln2, int cmd, unsigned pin)
+{
+	struct dln2_gpio_pin req = {
+		.pin = cpu_to_le16(pin),
+	};
+
+	return dln2_transfer(dln2, dln2_gpio_mod, cmd, &req, sizeof(req),
+			     NULL, NULL);
+}
+
+
+static int dln2_gpio_pin_val(struct dln2_gpio *dev, int cmd, unsigned int pin)
+{
+	struct dln2_gpio_pin req = {
+		.pin = cpu_to_le16(pin),
+	};
+	struct dln2_gpio_pin_val rsp;
+	int ret, len = sizeof(rsp);
+
+	ret = dln2_transfer(dev->dln2, dln2_gpio_mod, cmd, &req, sizeof(req),
+			    &rsp, &len);
+	if (ret < 0)
+		return ret;
+
+	if (req.pin != rsp.pin) {
+		dev_err(dev->gpio.dev, "bad pin response: %d %d\n",
+			pin, le16_to_cpu(rsp.pin));
+		return -EPROTO;
+	}
+
+	return rsp.value;
+}
+
+static int dln2_gpio_pin_get_in_val(struct dln2_gpio *dev, unsigned int pin)
+{
+	int ret = dln2_gpio_pin_val(dev, CMD_GPIO_PIN_GET_VAL, pin);
+
+	if (ret < 0)
+		return ret;
+	return !!ret;
+}
+
+static int dln2_gpio_pin_get_out_val(struct dln2_gpio *dev, unsigned int pin)
+{
+	int ret = dln2_gpio_pin_val(dev, CMD_GPIO_PIN_GET_OUT_VAL, pin);
+
+	if (ret < 0)
+		return ret;
+	return !!ret;
+}
+
+static void dln2_gpio_pin_set_out_val(struct dln2_dev *dln2, unsigned int pin,
+				      int value)
+{
+	struct dln2_gpio_pin_val req = {
+		.pin = cpu_to_le16(pin),
+		.value = cpu_to_le16(value),
+	};
+
+	dln2_transfer(dln2, dln2_gpio_mod, CMD_GPIO_PIN_SET_OUT_VAL,
+		      &req, sizeof(req), NULL, NULL);
+}
+
+static int dln2_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+
+	return dln2_gpio_pin_cmd(dev->dln2, CMD_GPIO_PIN_ENABLE, offset);
+}
+
+static void dln2_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+
+	dln2_gpio_pin_cmd(dev->dln2, CMD_GPIO_PIN_DISABLE, offset);
+}
+
+static int dln2_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+	struct dln2_gpio_pin req = {
+		.pin = cpu_to_le16(offset),
+	};
+	struct dln2_gpio_pin_val rsp;
+	int ret, len = sizeof(rsp);
+
+	ret = dln2_transfer(dev->dln2, dln2_gpio_mod,
+			    CMD_GPIO_PIN_GET_DIRECTION,
+			    &req, sizeof(req), &rsp, &len);
+	if (ret < 0)
+		return ret;
+
+	if (req.pin != rsp.pin) {
+		dev_err(dev->gpio.dev, "bad pin response: %d %d\n",
+			offset, le16_to_cpu(rsp.pin));
+		return -EPROTO;
+	}
+
+	/* DLN2 GPIO direction is 0 for input and 1 for output */
+	return !rsp.value;
+}
+
+static int dln2_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+	int dir = dln2_gpio_get_direction(chip, offset);
+
+	if (dir < 0)
+		return dir;
+
+	if (dir)
+		return dln2_gpio_pin_get_in_val(dev, offset);
+	else
+		return dln2_gpio_pin_get_out_val(dev, offset);
+}
+
+static void dln2_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+
+	dln2_gpio_pin_set_out_val(dev->dln2, offset, value);
+}
+
+static int dln2_gpio_set_direction(struct gpio_chip *chip, unsigned offset,
+				   unsigned dir)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+	struct dln2_gpio_pin_val req = {
+		.pin = cpu_to_le16(offset),
+		.value = cpu_to_le16(dir),
+	};
+
+	return dln2_transfer(dev->dln2, dln2_gpio_mod,
+			     CMD_GPIO_PIN_SET_DIRECTION,
+			     &req, sizeof(req), NULL, NULL);
+}
+
+#define DLN2_GPIO_DIRECTION_IN		0
+#define DLN2_GPIO_DIRECTION_OUT		1
+
+static int dln2_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+	return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_IN);
+}
+
+static int dln2_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+				      int value)
+{
+	return dln2_gpio_set_direction(chip, offset, DLN2_GPIO_DIRECTION_OUT);
+}
+
+static int dln2_gpio_set_debounce(struct gpio_chip *chip, unsigned offset,
+				  unsigned debounce)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+	struct {
+		__le32 duration;
+	} __packed req = {
+		.duration = cpu_to_le32(debounce),
+	};
+
+	return dln2_transfer(dev->dln2, dln2_gpio_mod, CMD_GPIO_SET_DEBOUNCE,
+			     &req, sizeof(req), NULL, NULL);
+}
+
+static int dln2_gpio_set_event_cfg(struct dln2_dev *dln2, unsigned pin,
+				   unsigned type, unsigned period)
+{
+	struct {
+		__le16 pin;
+		u8 type;
+		__le16 period;
+	} __packed req = {
+		.pin = cpu_to_le16(pin),
+		.type = type,
+		.period = cpu_to_le16(period),
+	};
+
+	return dln2_transfer(dln2, dln2_gpio_mod, CMD_GPIO_PIN_SET_EVENT_CFG,
+			     &req, sizeof(req), NULL, NULL);
+}
+
+static int dln2_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+	struct dln2_gpio *dev = container_of(chip, struct dln2_gpio, gpio);
+
+	return irq_create_mapping(dev->irq_domain, offset);
+}
+
+
+static void dln2_irq_work(struct work_struct *w)
+{
+	struct dln2_irq_work *iw = container_of(w, struct dln2_irq_work, work);
+	struct dln2_gpio *dev = iw->dev;
+	struct dln2_dev *dln2 = dev->dln2;
+	u8 type = iw->type & DLN2_GPIO_EVENT_MASK;
+
+	/* USB device has been disconnected, bail out */
+	if (!dln2)
+		return;
+
+	if (test_bit(iw->pin, dev->irqs_enabled))
+		dln2_gpio_set_event_cfg(dln2, iw->pin, type, 0);
+	else
+		dln2_gpio_set_event_cfg(dln2, iw->pin, DLN2_GPIO_EVENT_NONE, 0);
+}
+
+static void dln2_irq_enable(struct irq_data *irqd)
+{
+	struct dln2_gpio *dev = irq_data_get_irq_chip_data(irqd);
+	int pin = irqd_to_hwirq(irqd);
+
+	set_bit(pin, dev->irqs_enabled);
+	schedule_work(&dev->irq_work[pin].work);
+}
+
+static void dln2_irq_disable(struct irq_data *irqd)
+{
+	struct dln2_gpio *dev = irq_data_get_irq_chip_data(irqd);
+	int pin = irqd_to_hwirq(irqd);
+
+	clear_bit(pin, dev->irqs_enabled);
+	schedule_work(&dev->irq_work[pin].work);
+}
+
+static void dln2_irq_mask(struct irq_data *irqd)
+{
+	struct dln2_gpio *dev = irq_data_get_irq_chip_data(irqd);
+	int pin = irqd_to_hwirq(irqd);
+
+	set_bit(pin, dev->irqs_masked);
+}
+
+static void dln2_irq_unmask(struct irq_data *irqd)
+{
+	struct dln2_gpio *dev = irq_data_get_irq_chip_data(irqd);
+	int pin = irqd_to_hwirq(irqd);
+
+	if (test_and_clear_bit(pin, dev->irqs_masked))
+		generic_handle_irq(pin);
+}
+
+static int dln2_irq_set_type(struct irq_data *irqd, unsigned type)
+{
+	struct dln2_gpio *dev = irq_data_get_irq_chip_data(irqd);
+	int pin = irqd_to_hwirq(irqd);
+
+	switch (type) {
+	case IRQ_TYPE_LEVEL_HIGH:
+		dev->irq_work[pin].type = DLN2_GPIO_EVENT_LVL_HIGH;
+		break;
+	case IRQ_TYPE_LEVEL_LOW:
+		dev->irq_work[pin].type = DLN2_GPIO_EVENT_LVL_LOW;
+		break;
+	case IRQ_TYPE_EDGE_BOTH:
+		dev->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE;
+		break;
+	case IRQ_TYPE_EDGE_RISING:
+		dev->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE_RISING;
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		dev->irq_work[pin].type = DLN2_GPIO_EVENT_CHANGE_FALLING;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static struct irq_chip dln2_gpio_irqchip = {
+	.name = "dln2-irq",
+	.irq_enable = dln2_irq_enable,
+	.irq_disable = dln2_irq_disable,
+	.irq_mask = dln2_irq_mask,
+	.irq_unmask = dln2_irq_unmask,
+	.irq_set_type = dln2_irq_set_type,
+};
+
+static int dln2_gpio_irq_map(struct irq_domain *irqd, unsigned int irq,
+			     irq_hw_number_t hwirq)
+{
+	irq_set_chip_data(irq, irqd->host_data);
+	irq_set_chip_and_handler(irq, &dln2_gpio_irqchip, handle_simple_irq);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops dln2_domain_irq_ops = {
+	.map = dln2_gpio_irq_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+
+static int dln2_gpio_connect(struct dln2_dev *dln2)
+{
+	struct dln2_gpio *dev;
+	struct device *d = dln2_get_device(dln2);
+	int pins = dln2_gpio_get_pin_count(dln2), i, ret;
+
+	if (pins < 0) {
+		dev_err(d, "failed to get pin count: %d\n", pins);
+		return pins;
+	}
+	if (pins > DLN2_GPIO_MAX_PINS)
+		dev_warn(d, "clamping pins to %d\n", DLN2_GPIO_MAX_PINS);
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		dev_err(d, "fail to allocate for device state\n");
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < DLN2_GPIO_MAX_PINS; i++) {
+		INIT_WORK(&dev->irq_work[i].work, dln2_irq_work);
+		dev->irq_work[i].pin = i;
+		dev->irq_work[i].dev = dev;
+	}
+
+	dev->irq_domain = irq_domain_add_simple(NULL, pins, 0,
+						&dln2_domain_irq_ops, dev);
+	if (!dev->irq_domain) {
+		kfree(dev);
+		return -ENOMEM;
+	}
+
+	dev->dln2 = dln2;
+
+	dev->gpio.label = "dln2";
+	dev->gpio.dev = d;
+	dev->gpio.owner = THIS_MODULE;
+	dev->gpio.base = -1;
+	dev->gpio.ngpio = pins;
+	dev->gpio.exported = 1;
+
+	dev->gpio.set = dln2_gpio_set;
+	dev->gpio.get = dln2_gpio_get;
+	dev->gpio.request = dln2_gpio_request;
+	dev->gpio.free = dln2_gpio_free;
+	dev->gpio.get_direction = dln2_gpio_get_direction;
+	dev->gpio.direction_input = dln2_gpio_direction_input;
+	dev->gpio.direction_output = dln2_gpio_direction_output;
+	dev->gpio.set_debounce = dln2_gpio_set_debounce;
+
+	dev->gpio.to_irq = dln2_gpio_to_irq;
+
+	dln2_set_dev_context(dln2, dln2_gpio_mod, dev);
+
+	ret = gpiochip_add(&dev->gpio);
+	if (ret < 0) {
+		irq_domain_remove(dev->irq_domain);
+		kfree(dev);
+		dev_err(dev->gpio.dev, "error adding gpio chip: %d\n", ret);
+		return ret;
+	}
+
+	dev_dbg(dev->gpio.dev, "connected " DRIVER_NAME "\n");
+	return 0;
+}
+
+static void dln2_gpio_disconnect(struct dln2_dev *dln2)
+{
+	struct dln2_gpio *dev = dln2_get_dev_context(dln2, dln2_gpio_mod);
+	int ret;
+
+	irq_domain_remove(dev->irq_domain);
+	ret = gpiochip_remove(&dev->gpio);
+	if (ret < 0) {
+		dev_warn(dev->gpio.dev, "error removing gpio chip: %d\n", ret);
+		dev->dln2 = NULL;
+		return;
+	}
+
+	kfree(dev);
+
+	dev_dbg(dev->gpio.dev, "disconnected " DRIVER_NAME "\n");
+}
+
+static struct dln2_mod_ops dln2_gpio_ops = {
+	.name = DRIVER_NAME,
+	.connect = dln2_gpio_connect,
+	.disconnect = dln2_gpio_disconnect,
+};
+
+static void dln2_gpio_event(struct dln2_dev *dln2, struct urb *urb,
+			    struct dln2_response *rsp, void *data, int len)
+{
+	struct dln2_gpio *dev = dln2_get_dev_context(dln2, dln2_gpio_mod);
+	struct {
+		__le16 count;
+		__u8 type;
+		__le16 pin;
+		__u8 value;
+	} __packed *event = data;
+	int pin, irq, id = le16_to_cpu(rsp->hdr.id);
+
+	if (id != CMD_GPIO_CONDITION_MET_EV) {
+		dev_err(dev->gpio.dev, "unexpected cmd %x\n", id);
+		goto out_complete;
+	}
+
+	pin = le16_to_cpu(event->pin);
+	irq = irq_find_mapping(dev->irq_domain, pin);
+
+	if (!irq) {
+		dev_err(dev->gpio.dev, "pin %d not mapped to IRQ\n", pin);
+		goto out_complete;
+	}
+
+	if (!test_bit(pin, dev->irqs_enabled) ||
+	    test_bit(pin, dev->irqs_masked))
+		goto out_complete;
+
+	switch (dev->irq_work[pin].type) {
+	case DLN2_GPIO_EVENT_CHANGE_RISING:
+		if (event->value)
+			generic_handle_irq(irq);
+		break;
+	case DLN2_GPIO_EVENT_CHANGE_FALLING:
+		if (!event->value)
+			generic_handle_irq(irq);
+		break;
+	default:
+		generic_handle_irq(irq);
+	}
+
+out_complete:
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static struct dln2_mod_ops dln2_irq_ops = {
+	.name = "irq-" DRIVER_NAME,
+	.receive = dln2_gpio_event,
+};
+
+static int __init dln2_gpio_init(void)
+{
+	int err;
+
+	dln2_gpio_mod = dln2_register_module(-1, &dln2_gpio_ops);
+
+	if (IS_ERR(dln2_gpio_mod)) {
+		err = PTR_ERR(dln2_gpio_mod);
+		dln2_gpio_mod = NULL;
+		pr_err(DRIVER_NAME "dln2_register_module failed: %d\n", err);
+		return err;
+	}
+
+	dln2_irq_mod = dln2_register_module(0, &dln2_irq_ops);
+	if (IS_ERR(dln2_irq_mod)) {
+		err = PTR_ERR(dln2_irq_mod);
+		dln2_irq_mod = NULL;
+		dln2_unregister_module(dln2_gpio_mod);
+		dln2_gpio_mod = NULL;
+		pr_err(DRIVER_NAME "dln2_register_module failed: %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+module_init(dln2_gpio_init);
+
+static void __exit dln2_gpio_exit(void)
+{
+	if (dln2_gpio_mod)
+		dln2_unregister_module(dln2_gpio_mod);
+}
+module_exit(dln2_gpio_exit);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com");
+MODULE_DESCRIPTION(DRIVER_NAME "driver");
+MODULE_LICENSE("GPL");
-- 
1.9.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/3] usb: add support for Diolan DLN-2 devices
  2014-08-20 11:24 ` [PATCH 1/3] usb: add " Daniel Baluta
@ 2014-08-20 19:53   ` Arnd Bergmann
  2014-08-21  8:07   ` Johan Hovold
  1 sibling, 0 replies; 10+ messages in thread
From: Arnd Bergmann @ 2014-08-20 19:53 UTC (permalink / raw)
  To: Daniel Baluta
  Cc: gregkh, linux-usb, wsa, linux-i2c, linus.walleij, gnurou,
	linux-gpio, pratyush.anand, pebolle, stern, octavian.purdila,
	matthew, linux-kernel, laurentiu.palcu, jdelvare, sjg

On Wednesday 20 August 2014, Daniel Baluta wrote:
> From: Octavian Purdila <octavian.purdila@intel.com>
> 
> This patch implements the USB part of the Diolan USB-I2C/SPI/GPIO
> Master Adapter DLN-2. Details about the device can be found here:
> 
> https://www.diolan.com/i2c/i2c_interface.html.
> 
> Information about the USB protocol can be found in the Programmer's
> Reference Manual [1], see section 1.7.
> 
> Because the hardware has a single transmit endpoint and a single
> receive endpoint the communication between the various DLN2 drivers
> and the hardware will be muxed/demuxed by this driver.
> 
> The functional DLN2 drivers (i2c, GPIO, etc.) will have to register
> themselves as DLN2 modules in order to send or receive data.
> 
> Each DLN2 module will be identified by the handle field within the DLN2
> message header. If a DLN2 module issues multiple commands in parallel
> they will be identified by the echo counter field in the message header.
> 
> The DLN2 modules can use the dln2_transfer() function to issue a
> command and wait for its response. They can also use an asynchronous
> mode of operation, in which case a receive callback function is going
> to be notified when messages for a specific handle are received.
> 
> Because the hardware reserves handle 0 for GPIO events, the driver
> also reserves handle 0. It will be allocated to a DLN2 module only if
> it is explicitly requested.
> 
> [1] https://www.diolan.com/downloads/dln-api-manual.pdf
> 
> Signed-off-by: Octavian Purdila <octavian.purdila@intel.com>

After a very brief review of the driver, I think this would be better
handled as an MFD driver in drivers/mfd that creates child devices and
has the high-level drivers get registered as platform_driver.

	Arnd

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/3] usb: add support for Diolan DLN-2 devices
  2014-08-20 11:24 ` [PATCH 1/3] usb: add " Daniel Baluta
  2014-08-20 19:53   ` Arnd Bergmann
@ 2014-08-21  8:07   ` Johan Hovold
  2014-08-21 23:10       ` Octavian Purdila
  1 sibling, 1 reply; 10+ messages in thread
From: Johan Hovold @ 2014-08-21  8:07 UTC (permalink / raw)
  To: Daniel Baluta
  Cc: gregkh, linux-usb, wsa, linux-i2c, linus.walleij, gnurou,
	linux-gpio, pratyush.anand, pebolle, stern, octavian.purdila,
	matthew, linux-kernel, laurentiu.palcu, jdelvare, arnd, sjg

On Wed, Aug 20, 2014 at 02:24:45PM +0300, Daniel Baluta wrote:
> From: Octavian Purdila <octavian.purdila@intel.com>
> 
> This patch implements the USB part of the Diolan USB-I2C/SPI/GPIO
> Master Adapter DLN-2. Details about the device can be found here:
> 
> https://www.diolan.com/i2c/i2c_interface.html.
> 
> Information about the USB protocol can be found in the Programmer's
> Reference Manual [1], see section 1.7.
> 
> Because the hardware has a single transmit endpoint and a single
> receive endpoint the communication between the various DLN2 drivers
> and the hardware will be muxed/demuxed by this driver.
> 
> The functional DLN2 drivers (i2c, GPIO, etc.) will have to register
> themselves as DLN2 modules in order to send or receive data.
> 
> Each DLN2 module will be identified by the handle field within the DLN2
> message header. If a DLN2 module issues multiple commands in parallel
> they will be identified by the echo counter field in the message header.
> 
> The DLN2 modules can use the dln2_transfer() function to issue a
> command and wait for its response. They can also use an asynchronous
> mode of operation, in which case a receive callback function is going
> to be notified when messages for a specific handle are received.
> 
> Because the hardware reserves handle 0 for GPIO events, the driver
> also reserves handle 0. It will be allocated to a DLN2 module only if
> it is explicitly requested.
> 
> [1] https://www.diolan.com/downloads/dln-api-manual.pdf
> 
> Signed-off-by: Octavian Purdila <octavian.purdila@intel.com>
> ---
>  drivers/usb/misc/Kconfig  |   6 +
>  drivers/usb/misc/Makefile |   1 +
>  drivers/usb/misc/dln2.c   | 719 ++++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/usb/dln2.h  | 146 ++++++++++
>  4 files changed, 872 insertions(+)
>  create mode 100644 drivers/usb/misc/dln2.c
>  create mode 100644 include/linux/usb/dln2.h
> 
> diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
> index 76d7720..953f521 100644
> --- a/drivers/usb/misc/Kconfig
> +++ b/drivers/usb/misc/Kconfig
> @@ -255,3 +255,9 @@ config USB_LINK_LAYER_TEST
>  	  This driver is for generating specific traffic for Super Speed Link
>  	  Layer Test Device. Say Y only when you want to conduct USB Super Speed
>  	  Link Layer Test for host controllers.
> +
> +config USB_DLN2
> +	tristate "Diolan DLN2 USB Driver"
> +	help
> +	  This adds USB support for Diolan  USB-I2C/SPI/GPIO
> +	  Master Adapter DLN-2.
> diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
> index 65b0402..767264e 100644
> --- a/drivers/usb/misc/Makefile
> +++ b/drivers/usb/misc/Makefile
> @@ -25,6 +25,7 @@ obj-$(CONFIG_USB_USS720)		+= uss720.o
>  obj-$(CONFIG_USB_SEVSEG)		+= usbsevseg.o
>  obj-$(CONFIG_USB_YUREX)			+= yurex.o
>  obj-$(CONFIG_USB_HSIC_USB3503)		+= usb3503.o
> +obj-$(CONFIG_USB_DLN2)			+= dln2.o
>  
>  obj-$(CONFIG_USB_SISUSBVGA)		+= sisusbvga/
>  obj-$(CONFIG_USB_LINK_LAYER_TEST)	+= lvstest.o
> diff --git a/drivers/usb/misc/dln2.c b/drivers/usb/misc/dln2.c
> new file mode 100644
> index 0000000..5bfa850
> --- /dev/null
> +++ b/drivers/usb/misc/dln2.c
> @@ -0,0 +1,719 @@
> +/*
> + * Driver for the Diolan DLN-2 USB adapter
> + *
> + * Copyright (c) 2014 Intel Corporation
> + *
> + * 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>
> +#include <linux/mutex.h>
> +
> +#define DRIVER_NAME			"usb-dln2"
> +
> +#define DLN2_GENERIC_MODULE_ID		0x00
> +#define DLN2_GENERIC_CMD(cmd)		DLN2_CMD(cmd, DLN2_GENERIC_MODULE_ID)
> +
> +/* generic commands */
> +#define CMD_GET_DEVICE_VER		DLN2_GENERIC_CMD(0x30)
> +#define CMD_GET_DEVICE_SN		DLN2_GENERIC_CMD(0x31)

Have you considered using regmap for register access?

> +
> +#define DLN2_HW_ID			0x200
> +
> +#define DLN2_USB_TIMEOUT		100	/* in ms */
> +
> +#define DLN2_MAX_RX_SLOTS 16
> +
> +#include <linux/usb/dln2.h>

Move to the other includes.

> +
> +struct dln2_rx_context {
> +	struct completion done;
> +	struct urb *urb;
> +	bool connected;
> +};
> +
> +struct dln2_rx_slots {
> +	/* RX slots bitmap */
> +	DECLARE_BITMAP(bmap, DLN2_MAX_RX_SLOTS);

You only have 16 slots and only do single bit manipulations. Just use an
unsigned long and find_first_bit, set_bit, etc, below.

> +
> +	/* used to wait for a free RX slot */
> +	wait_queue_head_t wq;
> +
> +	/* used to wait for an RX operation to complete */
> +	struct dln2_rx_context slots[DLN2_MAX_RX_SLOTS];
> +
> +	/* avoid races between free_rx_slot and dln2_transfer_rx_cb */
> +	spinlock_t lock;
> +};
> +
> +static int find_free_slot(struct dln2_rx_slots *rxs, int *slot)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&rxs->lock, flags);
> +
> +	*slot = bitmap_find_free_region(rxs->bmap, DLN2_MAX_RX_SLOTS, 0) - 1;
> +
> +	if (*slot >= 0) {
> +		struct dln2_rx_context *rxc = &rxs->slots[*slot];
> +
> +		init_completion(&rxc->done);
> +		rxc->connected = true;
> +	}
> +
> +	spin_unlock_irqrestore(&rxs->lock, flags);
> +
> +	return *slot >= 0;
> +}
> +
> +static int alloc_rx_slot(struct dln2_rx_slots *rxs)
> +{
> +	int slot, ret;
> +
> +	ret = wait_event_interruptible(rxs->wq, find_free_slot(rxs, &slot));

You probably want a timeout here as well.

> +	if (ret < 0)
> +		return ret;
> +
> +	return slot;
> +}
> +
> +static void free_rx_slot(struct dln2_rx_slots *rxs, int slot)
> +{
> +	unsigned long flags;
> +	struct dln2_rx_context *rxc;
> +
> +	spin_lock_irqsave(&rxs->lock, flags);
> +
> +	bitmap_clear(rxs->bmap, slot + 1, 1);
> +
> +	rxc = &rxs->slots[slot];
> +	rxc->connected = false;
> +
> +	if (rxc->urb) {
> +		usb_submit_urb(rxc->urb, GFP_KERNEL);

You cannot use GFP_KERNEL in atomic context.

Error handling is missing.

> +		rxc->urb = NULL;
> +	}
> +
> +	spin_unlock_irqrestore(&rxs->lock, flags);
> +
> +	wake_up_interruptible(&rxs->wq);
> +}
> +
> +
> +#define DLN2_MAX_MODULES	5
> +#define DLN2_MAX_URBS		16
> +
> +struct dln2_dev {
> +	struct urb *rx_urb[DLN2_MAX_URBS];
> +	void *rx_buf[DLN2_MAX_URBS];
> +	int ep_in, ep_out;              /* Endpoints    */

One declaration per line, and endpoint addresses are unsigned -- u8 will
do.

Skip the comment.

> +	struct usb_device *usb_dev;	/* the usb device for this device */
> +	struct usb_interface *interface;/* the interface for this device */

Do the comments really add anything here?

> +
> +	struct dln2_rx_slots mod_rx_slots[DLN2_MAX_MODULES];
> +	void *context[DLN2_MAX_MODULES];
> +	bool mod_initialized[DLN2_MAX_MODULES];
> +
> +	struct list_head list;
> +};
> +
> +struct dln2_mod {
> +	struct dln2_mod_ops *ops;
> +	void *context;
> +};
> +
> +/* registered modules (e.g i2c, GPIO, GPIO interrupts, etc.) */
> +static struct dln2_mod dln2_modules[DLN2_MAX_MODULES];
> +static DEFINE_MUTEX(dln2_modules_mutex);
> +/* module used internally for control messages */
> +static struct dln2_mod *dln2_ctrl_mod;
> +
> +static inline int dln2_get_handle(struct dln2_mod *mod)
> +{
> +	int handle = mod - dln2_modules;
> +
> +	BUG_ON(handle < 0 || handle > DLN2_MAX_MODULES);

Don't use BUG(). It's really not nice to kill the user's machine for
this. Handle at the call site instead.

> +
> +	return handle;
> +}
> +
> +static void dln2_connect_do_work(struct work_struct *w);
> +static DECLARE_DELAYED_WORK(dln2_connect_work, dln2_connect_do_work);
> +
> +struct dln2_mod *dln2_register_module(int handle, struct dln2_mod_ops *mod_ops)
> +{
> +	int i;
> +
> +	if (!mod_ops)
> +		return ERR_PTR(-EINVAL);
> +
> +	mutex_lock(&dln2_modules_mutex);
> +
> +	if (handle < 0) {
> +		for (i = 0; i < DLN2_MAX_MODULES; i++) {
> +			/* reserve handle 0 for GPIO interrupts */
> +			if (!dln2_modules[i].ops && i > 0) {
> +				handle = i;
> +				break;
> +			}
> +		}
> +	}
> +
> +	if (handle >= DLN2_MAX_MODULES) {
> +		mutex_unlock(&dln2_modules_mutex);
> +		return ERR_PTR(-EINVAL);
> +	}
> +
> +	if (dln2_modules[handle].ops) {
> +		mutex_unlock(&dln2_modules_mutex);
> +		return ERR_PTR(-EBUSY);
> +	}
> +
> +	dln2_modules[handle].ops = mod_ops;
> +
> +	mutex_unlock(&dln2_modules_mutex);
> +
> +	schedule_delayed_work(&dln2_connect_work, HZ/10);
> +
> +	return &dln2_modules[handle];
> +}
> +EXPORT_SYMBOL(dln2_register_module);
> +
> +void dln2_unregister_module(struct dln2_mod *mod)
> +{
> +	mutex_lock(&dln2_modules_mutex);
> +	mod->ops = NULL;
> +	mutex_unlock(&dln2_modules_mutex);
> +}
> +EXPORT_SYMBOL(dln2_unregister_module);

I think you want to implement this driver as an MFD driver, rather than
invent your own module registration.

> +
> +static void dln2_transfer_rx_cb(struct dln2_dev *dev, struct urb *urb,
> +				struct dln2_response *rsp, void *data, int len)
> +{
> +	int handle = le16_to_cpu(rsp->hdr.handle);
> +	int rx_slot = le16_to_cpu(rsp->hdr.echo);
> +	struct dln2_rx_slots *rxs = &dev->mod_rx_slots[handle];
> +	struct dln2_rx_context *rxc;
> +
> +	spin_lock(&rxs->lock);
> +	rxc = &rxs->slots[rx_slot];
> +	if (rxc->connected) {
> +		rxc->urb = urb;
> +		complete(&rxc->done);
> +	} else {
> +		dev_dbg(&dev->interface->dev, "drop response for handle/slot %d/%d",
> +			 handle, rx_slot);
> +		usb_submit_urb(urb, GFP_ATOMIC);

Error handling? Check all usb_submit_urb calls throughout.

> +	}
> +	spin_unlock(&rxs->lock);
> +}
> +
> +static void dln2_rx(struct urb *urb)
> +{
> +	struct dln2_dev *dev = urb->context;
> +	struct dln2_response *rsp = urb->transfer_buffer;
> +	struct dln2_mod *mod;
> +	u16 id, echo, handle, size;
> +	u8 *user_data;
> +	int user_len;
> +

First of all you have to check the urb status, this could be a callback
after a failed or canceled transfer.

You also need to verify that that the received data is at least
sizeof(struct dln2_response).

> +	handle = le16_to_cpu(rsp->hdr.handle);
> +	id = le16_to_cpu(rsp->hdr.id);
> +	echo = le16_to_cpu(rsp->hdr.echo);
> +	size = le16_to_cpu(rsp->hdr.size);
> +
> +	if (handle > DLN2_MAX_MODULES)
> +		goto out_invalid_handle;
> +	mod = &dln2_modules[handle];
> +	if (!mod->ops)
> +		goto out_invalid_handle;
> +
> +	if (size != urb->actual_length)
> +		dev_warn(&dev->interface->dev, "RX len mismatch: handle %x cmd %x echo %x size %d actual %d\n",
> +			 handle, id, echo, size, urb->actual_length);
> +

Again, verify the received data length before parsing it.

> +	user_data = urb->transfer_buffer + sizeof(struct dln2_header);
> +	user_len = urb->actual_length - sizeof(struct dln2_header);
> +
> +	if (mod->ops->receive)
> +		mod->ops->receive(dev, urb, rsp, user_data, user_len);
> +	else
> +		dln2_transfer_rx_cb(dev, urb, rsp, user_data, user_len);
> +
> +	return;
> +
> +out_invalid_handle:
> +	dev_warn(&dev->interface->dev, "RX: invalid handle %d\n", handle);
> +	usb_submit_urb(urb, GFP_ATOMIC);
> +}
> +
> +static void *dln2_prep_buf(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd,
> +			   u16 echo, void *obuf, int *obuf_len, gfp_t gfp)
> +{
> +	void *buf;
> +	int len;
> +	struct dln2_header *hdr;
> +	u16 handle = dln2_get_handle(mod);
> +
> +	len = *obuf_len + sizeof(*hdr);
> +	buf = kmalloc(len, gfp);
> +	if (!buf)
> +		return NULL;
> +
> +	hdr = (struct dln2_header *)buf;
> +	hdr->id = cpu_to_le16(cmd);
> +	hdr->size = cpu_to_le16(len);
> +	hdr->echo = cpu_to_le16(echo);
> +	hdr->handle = cpu_to_le16(handle);
> +
> +	memcpy(buf + sizeof(*hdr), obuf, *obuf_len);
> +	*obuf_len = len;
> +
> +	return buf;
> +}
> +
> +static int dln2_send_wait(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd,
> +			  u16 echo, void *obuf, int obuf_len)
> +{
> +	int len = obuf_len, ret = 0, actual;
> +	void *buf;
> +
> +	if (!dev)
> +		return -ENODEV;
> +
> +	buf = dln2_prep_buf(dev, mod, cmd, echo, obuf, &len, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
> +	ret = usb_bulk_msg(dev->usb_dev,
> +			   usb_sndbulkpipe(dev->usb_dev, dev->ep_out),
> +			   buf, len, &actual, DLN2_USB_TIMEOUT);
> +
> +	kfree(buf);
> +
> +	return ret;
> +}
> +
> +static void dln2_send_complete(struct urb *urb)
> +{
> +	kfree(urb->context);
> +	usb_free_urb(urb);
> +}
> +
> +int dln2_send(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd,
> +	      u16 echo, void *obuf, int obuf_len)
> +{
> +	int len = obuf_len, ret;

One declaration per line (here and throughout), especially if you're
initialising.

> +	struct urb *urb;
> +	void *buf;
> +
> +	if (!dev)
> +		return -ENODEV;
> +
> +	buf = dln2_prep_buf(dev, mod, cmd, echo, obuf, &len, GFP_ATOMIC);
> +	if (!buf)
> +		return -ENOMEM;
> +
> +	urb = usb_alloc_urb(0, GFP_ATOMIC);
> +	if (!urb) {
> +		kfree(buf);
> +		return -ENOMEM;
> +	}
> +
> +	usb_fill_bulk_urb(urb, dev->usb_dev,
> +			  usb_sndbulkpipe(dev->usb_dev, dev->ep_out),
> +			  buf, len, dln2_send_complete, buf);
> +
> +	ret = usb_submit_urb(urb, GFP_ATOMIC);
> +	if (ret < 0) {
> +		usb_free_urb(urb);
> +		kfree(buf);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(dln2_send);
> +
> +int dln2_transfer(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd,
> +		  void *obuf, int obuf_len, void *ibuf, int *ibuf_len)
> +{
> +	u16 result, rx_slot;
> +	struct dln2_response *rsp;
> +	int ret = 0, handle = dln2_get_handle(mod);
> +	const int timeout = DLN2_USB_TIMEOUT * HZ / 1000;
> +	struct dln2_rx_slots *rxs;
> +	struct dln2_rx_context *rxc;
> +	struct device *d;

You'd usually use dev here, and call your struct dln2_dev dln2.

> +
> +	if (!dev)
> +		return -ENODEV;
> +
> +	d = &dev->interface->dev;
> +
> +	if (mod->ops->receive) {
> +		dev_warn(&dev->interface->dev,
> +			 "module %s calls dln2_transfer w/ rx_callback set\n",
> +			 mod->ops->name);
> +		return -EINVAL;
> +	}
> +
> +	rxs = &dev->mod_rx_slots[handle];
> +
> +	rx_slot = alloc_rx_slot(rxs);
> +	if (rx_slot < 0) {
> +		dev_err(d, "alloc_rx_slot failed: %d", ret);
> +		return rx_slot;
> +	}
> +
> +	ret = dln2_send_wait(dev, mod, cmd, rx_slot, obuf, obuf_len);
> +	if (ret < 0) {
> +		free_rx_slot(rxs, rx_slot);
> +		dev_err(d, "USB write failed: %d", ret);
> +		return ret;
> +	}
> +
> +	rxc = &rxs->slots[rx_slot];
> +
> +	ret = wait_for_completion_interruptible_timeout(&rxc->done, timeout);
> +	if (ret <= 0) {
> +		ret = !ret ? -ETIMEDOUT : ret;
> +		goto out_free_rx_slot;
> +	}
> +
> +	rsp = rxc->urb->transfer_buffer;
> +	result = le16_to_cpu(rsp->result);
> +
> +	if (result) {
> +		dev_warn(d, "%d received response with error %d\n",
> +			 handle, result);
> +		ret = -EREMOTEIO;
> +		goto out_free_rx_slot;
> +	}
> +
> +	if (!ibuf) {
> +		ret = 0;
> +		goto out_free_rx_slot;
> +	}
> +
> +	if (*ibuf_len > rxc->urb->actual_length - sizeof(*rsp))
> +		*ibuf_len = rxc->urb->actual_length - sizeof(*rsp);

Again, verify that actual length is greater than the response header.

> +
> +	memcpy(ibuf, rsp + 1, *ibuf_len);
> +
> +out_free_rx_slot:
> +	free_rx_slot(rxs, rx_slot);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(dln2_transfer);
> +
> +static int dln2_check_hw(struct dln2_dev *dev)
> +{
> +	__le32 hw_type;
> +	int ret, len = sizeof(hw_type);
> +
> +
> +	ret = dln2_transfer(dev, dln2_ctrl_mod, CMD_GET_DEVICE_VER, NULL, 0,
> +			    &hw_type, &len);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (le32_to_cpu(hw_type) != DLN2_HW_ID) {
> +		dev_err(&dev->interface->dev, "Device ID 0x%x not supported!",
> +			le32_to_cpu(hw_type));
> +		return -ENODEV;
> +	}
> +
> +	return 0;
> +}
> +
> +static int dln2_print_serialno(struct dln2_dev *dev)
> +{
> +	__le32 serial_no;
> +	int ret, len = sizeof(serial_no);
> +
> +
> +	ret = dln2_transfer(dev, dln2_ctrl_mod, CMD_GET_DEVICE_SN, NULL, 0,
> +			    &serial_no, &len);
> +	if (ret < 0)
> +		return ret;
> +
> +	dev_info(&dev->interface->dev,
> +		 "Diolan DLN2 device serial number is: 0x%x\n",
> +		 le32_to_cpu(serial_no));
> +
> +	return 0;
> +}
> +
> +static int dln2_hw_init(struct dln2_dev *dev)
> +{
> +	int ret;
> +
> +	dev_info(&dev->interface->dev,
> +		 "Diolan DLN2 at USB bus %03d address %03d\n",
> +		 dev->usb_dev->bus->busnum, dev->usb_dev->devnum);

You could just drop this message, or at least drop the bus and address.
The bus is included in the dev_info message and the bus address is
readily available using lsusb should it ever be needed. 

> +
> +	ret = dln2_check_hw(dev);
> +	if (ret < 0)
> +		return ret;
> +
> +	dln2_print_serialno(dev);
> +
> +	return ret;
> +}
> +
> +/* device layer */
> +
> +
> +static LIST_HEAD(dln2_dev_list);
> +
> +static void dln2_connect_modules(struct dln2_dev *dev)
> +{
> +	int i;
> +
> +	for (i = 0; i < DLN2_MAX_MODULES; i++)
> +		if (dln2_modules[i].ops && dln2_modules[i].ops->connect &&
> +		    !dev->mod_initialized[i] &&
> +		    !dln2_modules[i].ops->connect(dev))
> +			dev->mod_initialized[i] = true;

This is hardly readable. Add braces and inner conditionals.

> +}
> +
> +static void dln2_connect_do_work(struct work_struct *w)
> +{
> +	struct dln2_dev *dev;
> +
> +	mutex_lock(&dln2_modules_mutex);
> +	list_for_each_entry(dev, &dln2_dev_list, list)
> +		dln2_connect_modules(dev);
> +	mutex_unlock(&dln2_modules_mutex);
> +}
> +
> +static void dln2_free_rx_urbs(struct dln2_dev *dev)
> +{
> +	int i;
> +
> +	for (i = 0; i < DLN2_MAX_URBS; i++) {
> +		usb_unlink_urb(dev->rx_urb[i]);

You must use usb_kill_urb here as usb_unlink_urb is asynchronous.

> +		usb_free_urb(dev->rx_urb[i]);
> +		kfree(dev->rx_buf[i]);
> +	}
> +}
> +
> +static void dln2_free(struct dln2_dev *dev)
> +{
> +	dln2_free_rx_urbs(dev);
> +	usb_put_dev(dev->usb_dev);
> +	kfree(dev);
> +}
> +
> +static int dln2_setup_rx_urbs(struct dln2_dev *dev,
> +			      struct usb_host_interface *hostif)
> +{
> +	int i, ret;
> +	int rx_max_size = le16_to_cpu(hostif->endpoint[1].desc.wMaxPacketSize);
> +	struct device *d = &dev->interface->dev;
> +
> +	for (i = 0; i < DLN2_MAX_URBS; i++) {
> +		dev->rx_buf[i] = kmalloc(rx_max_size, GFP_KERNEL);
> +		if (!dev->rx_buf[i]) {
> +			dev_err(d, "no memory for RX buffers\n");

Drop all out-of-memory messages. This has already been logged with a
backtrace.

> +			return -ENOMEM;
> +		}
> +
> +		dev->rx_urb[i] = usb_alloc_urb(0, GFP_KERNEL);
> +		if (!dev->rx_urb[i]) {
> +			dev_err(d, "no memory for RX URBs\n");
> +			return -ENOMEM;
> +		}
> +
> +		usb_fill_bulk_urb(dev->rx_urb[i], dev->usb_dev,
> +				  usb_rcvbulkpipe(dev->usb_dev, dev->ep_in),
> +				  dev->rx_buf[i], rx_max_size, dln2_rx, dev);
> +
> +		ret = usb_submit_urb(dev->rx_urb[i], GFP_KERNEL);
> +		if (ret < 0) {
> +			dev_err(d, "failed to submit RX URB: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void dln2_disconnect(struct usb_interface *interface)
> +{
> +	struct dln2_dev *dev = usb_get_intfdata(interface);
> +	int i;
> +
> +	mutex_lock(&dln2_modules_mutex);
> +	list_del(&dev->list);
> +	for (i = 0; i < DLN2_MAX_MODULES; i++)
> +		if (dln2_modules[i].ops && dln2_modules[i].ops->disconnect &&
> +		    dev->mod_initialized[i])
> +			dln2_modules[i].ops->disconnect(dev);
> +	mutex_unlock(&dln2_modules_mutex);
> +
> +	usb_set_intfdata(interface, NULL);

This isn't needed.

You should probably set a disconnected flag though and check that in
your I/O paths to make sure that no subdriver ever triggers any I/O
after this function returns.

> +	dln2_free(dev);
> +
> +	dev_dbg(&interface->dev, "disconnected\n");
> +}
> +
> +static int dln2_probe(struct usb_interface *interface,
> +		      const struct usb_device_id *id)
> +{
> +	struct usb_host_interface *hostif = interface->cur_altsetting;
> +	struct dln2_dev *dev;
> +	int ret, i;
> +
> +	if (hostif->desc.bInterfaceNumber != 0 ||
> +	    hostif->desc.bNumEndpoints < 2)
> +		return -ENODEV;
> +
> +	/* allocate memory for our device state and initialize it */
> +	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
> +	if (!dev) {
> +		dev_err(&interface->dev, "no memory for device state\n");
> +		ret = -ENOMEM;
> +		goto out;
> +	}
> +	dev->ep_out = hostif->endpoint[0].desc.bEndpointAddress;
> +	dev->ep_in = hostif->endpoint[1].desc.bEndpointAddress;
> +
> +	dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
> +	dev->interface = interface;
> +
> +	/* save our data pointer in this interface device */
> +	usb_set_intfdata(interface, dev);
> +
> +	for (i = 0; i < DLN2_MAX_MODULES; i++) {
> +		init_waitqueue_head(&dev->mod_rx_slots[i].wq);
> +		spin_lock_init(&dev->mod_rx_slots[i].lock);
> +	}
> +
> +	ret = dln2_setup_rx_urbs(dev, hostif);
> +	if (ret) {
> +		dln2_disconnect(interface);
> +		return ret;
> +	}
> +
> +	ret = dln2_hw_init(dev);
> +	if (ret < 0) {
> +		dev_err(&interface->dev, "failed to initialize hardware\n");
> +		goto out_cleanup;
> +	}
> +
> +	dev_dbg(&interface->dev, "connected " DRIVER_NAME "\n");
> +
> +	mutex_lock(&dln2_modules_mutex);
> +	dln2_connect_modules(dev);
> +	list_add(&dev->list, &dln2_dev_list);
> +	mutex_unlock(&dln2_modules_mutex);
> +
> +	return 0;
> +
> +out_cleanup:
> +	usb_set_intfdata(interface, NULL);
> +	dln2_free(dev);
> +out:
> +	return ret;
> +}
> +
> +void dln2_set_mod_context(struct dln2_mod *mod, void *context)
> +{
> +	mod->context = context;
> +}
> +EXPORT_SYMBOL(dln2_set_mod_context);
> +
> +void *dln2_get_mod_context(struct dln2_mod *mod)
> +{
> +	return mod->context;
> +}
> +EXPORT_SYMBOL(dln2_get_mod_context);

Why would you ever want these two? You don't even use them now.

> +
> +void dln2_set_dev_context(struct dln2_dev *dev, struct dln2_mod *mod,
> +			  void *context)
> +{
> +	int handle = dln2_get_handle(mod);
> +
> +	dev->context[handle] = context;
> +}
> +EXPORT_SYMBOL(dln2_set_dev_context);
> +
> +void *dln2_get_dev_context(struct dln2_dev *dev, struct dln2_mod *mod)
> +{
> +	int handle = dln2_get_handle(mod);
> +
> +	return dev->context[handle];
> +}
> +EXPORT_SYMBOL(dln2_get_dev_context);
> +
> +struct device *dln2_get_device(struct dln2_dev *dev)
> +{
> +	return &dev->interface->dev;
> +}
> +EXPORT_SYMBOL(dln2_get_device);
> +
> +static const struct usb_device_id dln2_table[] = {
> +	{ USB_DEVICE(0xa257, 0x2013) },
> +	{ }
> +};
> +
> +MODULE_DEVICE_TABLE(usb, dln2_table);
> +
> +static struct usb_driver dln2_driver = {
> +	.name = DRIVER_NAME,
> +	.probe = dln2_probe,
> +	.disconnect = dln2_disconnect,
> +	.id_table = dln2_table,
> +};
> +
> +static struct dln2_mod_ops dln2_ctrl_mod_ops = {
> +	.name = "dln2-ctrl",
> +	.receive = NULL,
> +	.connect = NULL,
> +	.disconnect = NULL,
> +};
> +
> +static int __init dln2_init(void)
> +{
> +	int err = 0;
> +
> +	dln2_ctrl_mod = dln2_register_module(-1, &dln2_ctrl_mod_ops);

Restructure the driver as an MFD driver, and rethink the I/O interface
and you should be able to get rid of a lot of code above, including this
control module that you only use to fetch two parameters during probe.

> +
> +	if (IS_ERR(dln2_ctrl_mod)) {
> +		err = PTR_ERR(dln2_ctrl_mod);
> +		pr_err(DRIVER_NAME "dln2_register_module failed: %d\n", err);
> +		return err;
> +	}
> +
> +	err = usb_register_driver(&dln2_driver, THIS_MODULE, KBUILD_MODNAME);
> +	if (err < 0)
> +		pr_err(DRIVER_NAME "failed to register usb driver: %d\n", err);
> +
> +	return err;
> +}
> +module_init(dln2_init);
> +
> +static void __exit dln2_exit(void)
> +{
> +	usb_deregister(&dln2_driver);
> +}
> +module_exit(dln2_exit);
> +
> +MODULE_AUTHOR("Octavian Purdila <octavian.purdila@intel.com>");
> +MODULE_DESCRIPTION(DRIVER_NAME " driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/usb/dln2.h b/include/linux/usb/dln2.h
> new file mode 100644
> index 0000000..3f7f8c6
> --- /dev/null
> +++ b/include/linux/usb/dln2.h
> @@ -0,0 +1,146 @@
> +#ifndef __LINUX_USB_DLN2_H
> +#define __LINUX_USB_DLN2_H
> +
> +#include <linux/usb.h>
> +
> +struct dln2_header {
> +	__le16 size;
> +	__le16 id;
> +	__le16 echo;
> +	__le16 handle;
> +} __packed;
> +
> +struct dln2_response {
> +	struct dln2_header hdr;
> +	__le16 result;
> +} __packed;

The subdrivers should never have to worry about these details.

> +
> +
> +struct dln2_mod;
> +struct dln2_dev;
> +
> +#define DLN2_CMD(cmd, id)		((cmd) | ((id) << 8))
> +
> +/**
> + * dln2_mod_ops - DLN2 module callbacks
> + *
> + * @name - name of the module
> + *
> + * @receive - called when a message is received for the handle
> + * associated with this module. It can be NULL, and in this case
> + * dln_transfer() can be use for this module and the receive path will
> + * be handled internally. If the receive callback is used the user
> + * must call usb_sumit_urb() after finishing reading the data.

Again, handle the transport details in your core driver and let it
resubmit any urbs.

> + *
> + * @connect - called when a new DLN2 device is connected. The module
> + * should perform any needed initialization and associated the module
> + * context to this device with dln2_set_dev_context().
> + *
> + * @disconnect - called when a DLN2 device is disconnected. The module
> + * should free any resources associated with this device.
> + */
> +struct dln2_mod_ops {
> +	const char *name;
> +
> +	void (*receive)(struct dln2_dev *dev, struct urb *urb,
> +			struct dln2_response *res, void *data, int len);
> +	int (*connect)(struct dln2_dev *dev);
> +	void (*disconnect)(struct dln2_dev *dev);
> +};
> +
> +/**
> + * dl2n_register_module - register a DLN2 module
> + *
> + * @handle - the requested handle for this module that is going to be
> + * passed to the hardware; a positive value to request a specific
> + * value, or -1 to automatically allocate one
> + *
> + * @mod_ops - see dln2_mod_ops()
> + *
> + * @return an ERR_PTR is return in case of error. In case of success a
> + * dln2_mod handle is returned. The module should use
> + * dln2_set_mod_context() to associate any private context to this
> + * module.
> + */
> +struct dln2_mod *dln2_register_module(int handle, struct dln2_mod_ops *mod_ops);
> +
> +/**
> + * dl2n_register_module - unregister a DLN2 module
> + */
> +void dln2_unregister_module(struct dln2_mod *mod);
> +
> +/**
> + * dln2_transfer - issue a DLN2 command and wait for a response and
> + * the associated data
> + *
> + * Only modules that did *NOT* register a receive callback will be
> + * able to use this function.
> + *
> + * @dev - the DLN2 device on which to issue the command
> + * @mod - the DLN2 module issuing the command
> + * @cmd - the command to be sent to the device
> + * @obuf - the buffer to be sent to the device; can be NULL if the
> + * user doesn't need to transmit data with this command
> + * @obuf_len - the size of the buffer to be sent to the device
> + * @ibuf - any data associated with the response will be copied here;
> + * it can be NULL if the user doesn't need the response data
> + * @ibuf_len - must be initialized to the input buffer size; it will
> + * be modified to indicate the actual data transfered
> + *
> + * @returns 0 for success, negative value for errors
> + */
> +int dln2_transfer(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd,
> +		  void *obuf, int obuf_len, void *ibuf, int *ibuf_len);
> +
> +/**
> + * dln2_send - issue a DLN2 command without waiting for a response
> + *
> + * @dev - the DLN2 device on which to issue the command
> + * @mod - the DLN2 module issuing the command
> + * @cmd - the command to be sent to the device
> + * @echo - this value will be echoed back in the response
> + * @obuf - the buffer to be sent to the device; can be NULL if the
> + * user doesn't need to transmit data with this command
> + * @obuf_len - the size of the buffer to be sent to the device
> + *
> + * @returns 0 for success, negative value for errors
> + */
> +int dln2_send(struct dln2_dev *dev, struct dln2_mod *mod, u16 cmd, u16 echo,
> +	      void *obuf, int obuf_len);
> +
> +/**
> + * dln2_set_mod_context - store a private pointer into dln2_mod
> + *
> + * @mod - the DLN2 module
> + * @context - the private pointer to be set
> + */
> +void dln2_set_mod_context(struct dln2_mod *mod, void *context);
> +
> +/**
> + * dln2_set_mod_context - get the module private pointer from dln2_mod
> + *
> + * @mod - the DLN2 module
> + * @returns the module private pointer
> + */
> +void *dln2_get_mod_context(struct dln2_mod *mod);
> +
> +
> +/**
> + * dln2_set_mod_context - store a private pointer into dln2_dev
> + *
> + * @dev - the DLN2 device
> + * @context - the private pointer to be set
> + */
> +void dln2_set_dev_context(struct dln2_dev *dev, struct dln2_mod *mod,
> +			  void *context);
> +/**
> + * dln2_set_mod_context - get the module private pointer from dln2_dev
> + *
> + * @dev - the DLN2 device
> + * @returns the device private pointer
> + */
> +void *dln2_get_dev_context(struct dln2_dev *dev, struct dln2_mod *mod);
> +
> +struct device *dln2_get_device(struct dln2_dev *dev);
> +
> +#endif

Johan

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/3] usb: add support for Diolan DLN-2 devices
  2014-08-21  8:07   ` Johan Hovold
@ 2014-08-21 23:10       ` Octavian Purdila
  0 siblings, 0 replies; 10+ messages in thread
From: Octavian Purdila @ 2014-08-21 23:10 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Daniel Baluta, Greg Kroah-Hartman,
	linux-usb-u79uwXL29TY76Z2rM5mHXA, wsa-z923LK4zBo2bacvFa/9K2g,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linus.walleij-QSEj5FYQhm4dnm+yROfE0A,
	gnurou-Re5JQEeQqe8AvxtiuMwx3w, linux-gpio-u79uwXL29TY76Z2rM5mHXA,
	pratyush.anand-qxv4g6HH51o, pebolle-IWqWACnzNjzz+pZb47iToQ,
	Alan Stern, matthew-AIaitxs59ERIo2TaICnI/Q, lkml,
	Laurentiu Palcu, jdelvare-l3A5Bk7waGM, arnd-r2nGTMty4D4,
	sjg-F7+t8E8rja9g9hUCZPvPmw

On Thu, Aug 21, 2014 at 11:07 AM, Johan Hovold <johan-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
> On Wed, Aug 20, 2014 at 02:24:45PM +0300, Daniel Baluta wrote:
>> From: Octavian Purdila <octavian.purdila-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
>>
>> This patch implements the USB part of the Diolan USB-I2C/SPI/GPIO
>> Master Adapter DLN-2. Details about the device can be found here:
>>
<snip>
>
> I think you want to implement this driver as an MFD driver, rather than
> invent your own module registration.
>

Hi Johan,

Thank you for the detailed reviewed. We will rewrite the drivers to
use the MFD interface and address the rest of your comments and we
will resubmit them next week.

Thanks,
Tavi

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/3] usb: add support for Diolan DLN-2 devices
@ 2014-08-21 23:10       ` Octavian Purdila
  0 siblings, 0 replies; 10+ messages in thread
From: Octavian Purdila @ 2014-08-21 23:10 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Daniel Baluta, Greg Kroah-Hartman, linux-usb, wsa, linux-i2c,
	linus.walleij, gnurou, linux-gpio, pratyush.anand, pebolle,
	Alan Stern, matthew, lkml, Laurentiu Palcu, jdelvare, arnd, sjg

On Thu, Aug 21, 2014 at 11:07 AM, Johan Hovold <johan@kernel.org> wrote:
> On Wed, Aug 20, 2014 at 02:24:45PM +0300, Daniel Baluta wrote:
>> From: Octavian Purdila <octavian.purdila@intel.com>
>>
>> This patch implements the USB part of the Diolan USB-I2C/SPI/GPIO
>> Master Adapter DLN-2. Details about the device can be found here:
>>
<snip>
>
> I think you want to implement this driver as an MFD driver, rather than
> invent your own module registration.
>

Hi Johan,

Thank you for the detailed reviewed. We will rewrite the drivers to
use the MFD interface and address the rest of your comments and we
will resubmit them next week.

Thanks,
Tavi

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 3/3] gpio: add support for the Diolan DLN-2 USB-GPIO driver
  2014-08-20 11:24     ` Daniel Baluta
  (?)
@ 2014-08-29  7:16     ` Linus Walleij
  -1 siblings, 0 replies; 10+ messages in thread
From: Linus Walleij @ 2014-08-29  7:16 UTC (permalink / raw)
  To: Daniel Baluta
  Cc: Greg KH, linux-usb, Wolfram Sang, linux-i2c, Alexandre Courbot,
	linux-gpio, pratyush.anand, Paul Bolle, Alan Stern,
	octavian.purdila, matthew, linux-kernel, laurentiu.palcu,
	Jean Delvare, Arnd Bergmann, Simon Glass

On Wed, Aug 20, 2014 at 1:24 PM, Daniel Baluta <daniel.baluta@intel.com> wrote:

> This patch adds GPIO and IRQ support for the Diolan DLN-2 GPIO module.
>
> Information about the USB protocol interface can be found in the
> Programmer's Reference Manual [1], see section 2.9 for the GPIO
> module commands and responses.
>
> [1] https://www.diolan.com/downloads/dln-api-manual.pdf
>
> Signed-off-by: Daniel Baluta <daniel.baluta@intel.com>

Major change required: rewrite this driver to not select IRQ_DOMAIN,
instead select GPIOLIB_IRQCHIP and use the shared infrastructure.
See other drivers that select GPIOLIB_IRQCHIP or grep the git
log to see how this works in practice.

You need to use some container_of() operations.

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2014-08-29  7:16 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-08-20 11:24 [PATCH 0/3] dln-2: Add support for Diolan DLN-2 devices Daniel Baluta
2014-08-20 11:24 ` [PATCH 1/3] usb: add " Daniel Baluta
2014-08-20 19:53   ` Arnd Bergmann
2014-08-21  8:07   ` Johan Hovold
2014-08-21 23:10     ` Octavian Purdila
2014-08-21 23:10       ` Octavian Purdila
2014-08-20 11:24 ` [PATCH 2/3] i2c: add support for Diolan DLN-2 USB-I2C adapter Daniel Baluta
     [not found] ` <1408533887-22727-1-git-send-email-daniel.baluta-ral2JQCrhuEAvxtiuMwx3w@public.gmane.org>
2014-08-20 11:24   ` [PATCH 3/3] gpio: add support for the Diolan DLN-2 USB-GPIO driver Daniel Baluta
2014-08-20 11:24     ` Daniel Baluta
2014-08-29  7:16     ` Linus Walleij

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.