linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH RFC v2 0/4] new bus type SIOX
@ 2016-03-21 10:05 Uwe Kleine-König
  2016-03-21 10:05 ` [PATCH RFC v2 1/4] siox: new driver framework for eckelmann SIOX Uwe Kleine-König
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2016-03-21 10:05 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman; +Cc: kernel

Hello,

this is a patch set for a new bus type called SIOX created by the German
company Eckelmann. This is a bus with four data lines. The signals DIN (from
devices to master) and DOUT (from master to devices) are daisy chained through
all devices. DLD and DCLK are shared. A pulse on DCLK pushs bits on the DIN and
DOUT chains in the respective direction. A pulse on DLD makes the devices
sample their input and output stated from/to DIN and DOUT.

A master is the bus controller (i.e. it drives DOUT, DCLK and DLD). A
device is a consumer and provides DIN to the master.

The topology looks as follows:

      ,------->--DCLK-->---------------+----------------------.
      ^                                v                      v
 ,--------.                ,----------------------.       ,------
 |        |                |   ,--------------.   |       |
 |        |--->--DOUT-->---|->-|shift register|->-|--->---|
 |        |                |   `--------------'   |       |
 | master |                |        device        |       |  device
 |        |                |   ,--------------.   |       |
 |        |---<--DIN---<---|-<-|shift register|-<-|---<---|
 |        |                |   `--------------'   |       |
 `--------'                `----------------------'       `------
      v                                ^                      ^
      `----------DLD-------------------+----------------------'

So there are two chains of shift registers, one for letting the master
write to the devices, and the other to let the master read from the
devices. DCLK is the clock to shift both chains by a single bit. An edge
on DLD is used to make the devices load the inputs and sample the
outputs.

There are still some open issues, at least:

 - no device tree binding doc for bus master driver
   I will clean this up eventually when (and if) patch 1 gets in.

 - the devices should be polled each 25 ms, otherwise a per-device
   watchdog resets the device. I'm currently using a workqueue for that,
   and on a loaded machine sometimes the watchdog triggers. So maybe I
   need a different approach here? But I didn't debug that yet, so this
   is just FYI. This can be resolved later.

Since (implicit) v1 I cleaned up the code a bit, the reference counting
for kobjects should be ok now, I'm not confident that I got the locking
right though: I have a single mutex per bus master that also protects
the bus topology, but when holding the lock while removing a device
(which is triggered via sysfs) I get a lockdep splat. I "fixed" that by
releasing the mutex before calling device_unregister for the device.

IMHO the only interesting patch for now is the first one, the others
strictly depend on the first. I only included them here to demonstrate
how I expect the first patch to be used and didn't add the respective
Cc:s for them.

Thanks for your time and interest,
Uwe

Uwe Kleine-König (4):
  siox: new driver framework for eckelmann SIOX
  siox: add gpio bus driver
  gpio: new driver to work with a 8x12 siox
  siox: add support for tracing

 drivers/Kconfig              |   2 +
 drivers/Makefile             |   1 +
 drivers/gpio/Kconfig         |   5 +
 drivers/gpio/Makefile        |   1 +
 drivers/gpio/gpio-siox.c     | 290 ++++++++++++++++++
 drivers/siox/Kconfig         |   9 +
 drivers/siox/Makefile        |   2 +
 drivers/siox/siox-bus-gpio.c | 175 +++++++++++
 drivers/siox/siox-core.c     | 686 +++++++++++++++++++++++++++++++++++++++++++
 drivers/siox/siox.h          |  52 ++++
 include/linux/siox.h         |  61 ++++
 include/trace/events/siox.h  |  60 ++++
 12 files changed, 1344 insertions(+)
 create mode 100644 drivers/gpio/gpio-siox.c
 create mode 100644 drivers/siox/Kconfig
 create mode 100644 drivers/siox/Makefile
 create mode 100644 drivers/siox/siox-bus-gpio.c
 create mode 100644 drivers/siox/siox-core.c
 create mode 100644 drivers/siox/siox.h
 create mode 100644 include/linux/siox.h
 create mode 100644 include/trace/events/siox.h

-- 
2.7.0

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

* [PATCH RFC v2 1/4] siox: new driver framework for eckelmann SIOX
  2016-03-21 10:05 [PATCH RFC v2 0/4] new bus type SIOX Uwe Kleine-König
@ 2016-03-21 10:05 ` Uwe Kleine-König
  2016-05-01 21:27   ` Greg Kroah-Hartman
  2016-03-21 10:05 ` [PATCH RFC v2 2/4] siox: add gpio bus driver Uwe Kleine-König
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 7+ messages in thread
From: Uwe Kleine-König @ 2016-03-21 10:05 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman; +Cc: kernel

---
 drivers/Kconfig          |   2 +
 drivers/Makefile         |   1 +
 drivers/siox/Kconfig     |   2 +
 drivers/siox/Makefile    |   1 +
 drivers/siox/siox-core.c | 674 +++++++++++++++++++++++++++++++++++++++++++++++
 drivers/siox/siox.h      |  52 ++++
 include/linux/siox.h     |  61 +++++
 7 files changed, 793 insertions(+)
 create mode 100644 drivers/siox/Kconfig
 create mode 100644 drivers/siox/Makefile
 create mode 100644 drivers/siox/siox-core.c
 create mode 100644 drivers/siox/siox.h
 create mode 100644 include/linux/siox.h

diff --git a/drivers/Kconfig b/drivers/Kconfig
index 6e973b8e3a3b..c2fbf214f8a3 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -184,4 +184,6 @@ source "drivers/android/Kconfig"
 
 source "drivers/nvdimm/Kconfig"
 
+source "drivers/siox/Kconfig"
+
 endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index b64b49f6e01b..dd7b3cd1fd72 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -165,3 +165,4 @@ obj-$(CONFIG_RAS)		+= ras/
 obj-$(CONFIG_THUNDERBOLT)	+= thunderbolt/
 obj-$(CONFIG_CORESIGHT)		+= hwtracing/coresight/
 obj-$(CONFIG_ANDROID)		+= android/
+obj-$(CONFIG_SIOX)		+= siox/
diff --git a/drivers/siox/Kconfig b/drivers/siox/Kconfig
new file mode 100644
index 000000000000..471fbc009521
--- /dev/null
+++ b/drivers/siox/Kconfig
@@ -0,0 +1,2 @@
+menuconfig SIOX
+	tristate "Eckelmann SIOX Support"
diff --git a/drivers/siox/Makefile b/drivers/siox/Makefile
new file mode 100644
index 000000000000..d55cb5e08868
--- /dev/null
+++ b/drivers/siox/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SIOX) += siox-core.o
diff --git a/drivers/siox/siox-core.c b/drivers/siox/siox-core.c
new file mode 100644
index 000000000000..b4a128218d7d
--- /dev/null
+++ b/drivers/siox/siox-core.c
@@ -0,0 +1,674 @@
+/*
+ * Copyright (C) 2015 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#include "siox.h"
+
+struct workqueue_struct *wqueue;
+static bool siox_is_registered;
+
+void siox_master_lock(struct siox_master *smaster)
+{
+	mutex_lock(&smaster->lock);
+}
+
+void siox_master_unlock(struct siox_master *smaster)
+{
+	mutex_unlock(&smaster->lock);
+}
+
+struct siox_device *siox_device_add(struct siox_master *smaster);
+void siox_device_remove(struct siox_master *smaster);
+
+static void __siox_poll(struct siox_master *smaster)
+{
+	struct siox_device *sdevice;
+	size_t i = 0;
+	u8 prevstatus = smaster->status;
+
+	if (++smaster->status > 0x0d)
+		smaster->status = 0;
+
+	memset(smaster->buf, 0, smaster->setbuf_len);
+
+	list_for_each_entry_reverse(sdevice, &smaster->devices, node) {
+		struct siox_driver *sdriver =
+			sdevice->dev.driver ? to_siox_driver(sdevice->dev.driver) : NULL;
+
+		if (sdriver)
+			sdriver->set_data(sdevice, smaster->status,
+					  &smaster->buf[i + 1]);
+
+		smaster->buf[i] = smaster->status;
+
+		i += sdevice->inbytes;
+	}
+
+	BUG_ON(i != smaster->setbuf_len);
+
+	smaster->pushpull(smaster, smaster->setbuf_len, smaster->buf,
+			  smaster->getbuf_len,
+			  smaster->buf + smaster->setbuf_len);
+
+	list_for_each_entry(sdevice, &smaster->devices, node) {
+		struct siox_driver *sdriver =
+			sdevice->dev.driver ? to_siox_driver(sdevice->dev.driver) : NULL;
+		u8 sdev_status = smaster->buf[i + sdevice->outbytes - 1];
+
+		/*
+		 * bits 4:2 of status sample the respective bit in the status
+		 * byte written in the previous cycle. Mask them out
+		 * accordingly such that a set bit there indicates an error.
+		 */
+		sdev_status ^= ~prevstatus & 0xe;
+
+		if ((sdevice->status ^ sdev_status) & 1)
+			sysfs_notify_dirent(sdevice->watchdog_kn);
+
+		if (!(sdev_status & 1)) {
+			sdevice->watchdog_errors++;
+			sysfs_notify_dirent(sdevice->watchdog_errors_kn);
+		}
+
+		if (sdev_status & 0xe) {
+			sdevice->status_errors++;
+			sysfs_notify_dirent(sdevice->status_errors_kn);
+		}
+
+		sdevice->status = sdev_status;
+
+		/*
+		 * XXX trigger events for watchdog, changed jumper and misread
+		 * counter. Should the bus stop to poll in these cases?
+		 */
+
+		if (sdriver)
+			sdriver->get_data(sdevice, &smaster->buf[i]);
+
+		i += sdevice->outbytes;
+	}
+
+	if (smaster->active)
+		queue_delayed_work(wqueue, &smaster->poll,
+				   smaster->poll_interval);
+}
+
+static void siox_poll(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct siox_master *smaster =
+		container_of(dwork, struct siox_master, poll);
+
+	get_device(&smaster->dev);
+	siox_master_lock(smaster);
+	if (likely(smaster->active))
+		__siox_poll(smaster);
+	siox_master_unlock(smaster);
+	put_device(&smaster->dev);
+}
+
+static int __siox_start(struct siox_master *smaster)
+{
+	if (!(smaster->setbuf_len + smaster->getbuf_len))
+		return -ENODEV;
+
+	if (!smaster->buf)
+		return -ENOMEM;
+
+	smaster->active = 1;
+
+	__siox_poll(smaster);
+
+	return 0;
+}
+
+static int siox_start(struct siox_master *smaster)
+{
+	int ret;
+
+	siox_master_lock(smaster);
+	ret = __siox_start(smaster);
+	siox_master_unlock(smaster);
+
+	return ret;
+}
+
+static int __siox_stop(struct siox_master *smaster)
+{
+	smaster->active = 0;
+	cancel_delayed_work(&smaster->poll);
+	return 0;
+}
+
+static int siox_stop(struct siox_master *smaster)
+{
+	int ret;
+
+	siox_master_lock(smaster);
+	ret = __siox_stop(smaster);
+	siox_master_unlock(smaster);
+
+	return ret;
+}
+
+static ssize_t type_show(struct device *dev,
+			 struct device_attribute *attr, char *buf)
+{
+	struct siox_device *sdev = to_siox_device(dev);
+
+	return sprintf(buf, "%s\n", sdev->type);
+}
+
+static DEVICE_ATTR_RO(type);
+
+static ssize_t inbytes_show(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct siox_device *sdev = to_siox_device(dev);
+
+	return sprintf(buf, "%zu\n", sdev->inbytes);
+}
+
+static DEVICE_ATTR_RO(inbytes);
+
+static ssize_t outbytes_show(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct siox_device *sdev = to_siox_device(dev);
+
+	return sprintf(buf, "%zu\n", sdev->outbytes);
+}
+
+static DEVICE_ATTR_RO(outbytes);
+
+static ssize_t status_errors_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct siox_device *sdev = to_siox_device(dev);
+	unsigned status_errors;
+
+	siox_master_lock(sdev->smaster);
+
+	status_errors = sdev->status_errors;
+
+	siox_master_unlock(sdev->smaster);
+
+	return sprintf(buf, "%u\n", status_errors);
+}
+
+static DEVICE_ATTR_RO(status_errors);
+
+static ssize_t watchdog_show(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct siox_device *sdev = to_siox_device(dev);
+	u8 status;
+
+	siox_master_lock(sdev->smaster);
+
+	status = sdev->status;
+
+	siox_master_unlock(sdev->smaster);
+
+	return sprintf(buf, "%d\n", status & 1);
+}
+
+static DEVICE_ATTR_RO(watchdog);
+
+static ssize_t watchdog_errors_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct siox_device *sdev = to_siox_device(dev);
+	unsigned int watchdog_errors;
+
+	siox_master_lock(sdev->smaster);
+
+	watchdog_errors = sdev->watchdog_errors;
+
+	siox_master_unlock(sdev->smaster);
+
+	return sprintf(buf, "%u\n", watchdog_errors);
+}
+
+static DEVICE_ATTR_RO(watchdog_errors);
+
+static struct attribute *siox_device_attrs[] = {
+	&dev_attr_type.attr,
+	&dev_attr_inbytes.attr,
+	&dev_attr_outbytes.attr,
+	&dev_attr_status_errors.attr,
+	&dev_attr_watchdog.attr,
+	&dev_attr_watchdog_errors.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(siox_device);
+
+static void siox_device_release(struct device *dev)
+{
+	struct siox_device *sdevice = to_siox_device(dev);
+
+	kfree(sdevice);
+}
+
+static struct device_type siox_device_type = {
+	.groups = siox_device_groups,
+	.release = siox_device_release,
+};
+
+static int siox_match(struct device *dev, struct device_driver *drv)
+{
+	if (dev->type != &siox_device_type)
+		return 0;
+
+	/* up to now there is only a single driver so keeping this simple */
+	return 1;
+}
+
+static struct bus_type siox_bus_type = {
+	.name = "siox",
+	.match = siox_match,
+};
+
+static int siox_driver_probe(struct device *dev)
+{
+	struct siox_driver *sdriver = to_siox_driver(dev->driver);
+	struct siox_device *sdevice = to_siox_device(dev);
+	int ret;
+
+	ret = sdriver->probe(sdevice);
+	return ret;
+}
+
+static int siox_driver_remove(struct device *dev)
+{
+	struct siox_driver *sdriver =
+		container_of(dev->driver, struct siox_driver, driver);
+	struct siox_device *sdevice = to_siox_device(dev);
+	int ret;
+
+	ret = sdriver->remove(sdevice);
+	return ret;
+}
+
+static void siox_driver_shutdown(struct device *dev)
+{
+	struct siox_driver *sdriver =
+		container_of(dev->driver, struct siox_driver, driver);
+	struct siox_device *sdevice = to_siox_device(dev);
+
+	sdriver->shutdown(sdevice);
+}
+
+static ssize_t active_show(struct device *dev,
+			   struct device_attribute *attr, char *buf)
+{
+	struct siox_master *smaster = to_siox_master(dev);
+
+	return sprintf(buf, "%d\n", smaster->active);
+}
+
+static ssize_t active_store(struct device *dev,
+			    struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	struct siox_master *smaster = to_siox_master(dev);
+	int ret;
+	int active;
+
+	ret = kstrtoint(buf, 0, &active);
+	if (ret < 0)
+		return ret;
+
+	if (!active == !smaster->active)
+		/* no change */
+		return count;
+
+	if (active)
+		ret = siox_start(smaster);
+	else
+		ret = siox_stop(smaster);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(active);
+
+static ssize_t device_add_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct siox_master *smaster = to_siox_master(dev);
+	int ret;
+	char type[20] = "";
+	size_t inbytes = 0, outbytes = 0;
+
+	ret = sscanf(buf, "%20s %zu %zu", type, &inbytes, &outbytes);
+	if (ret != 3 || strcmp(type, "siox-12x8") ||
+	    inbytes != 2 || outbytes != 4)
+		return -EINVAL;
+
+	siox_device_add(smaster);
+
+	return count;
+}
+
+static DEVICE_ATTR_WO(device_add);
+
+static ssize_t device_remove_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct siox_master *smaster = to_siox_master(dev);
+
+	/* XXX? require to write <type> <inbytes> <outbytes> */
+	siox_device_remove(smaster);
+
+	return count;
+}
+
+static DEVICE_ATTR_WO(device_remove);
+
+static ssize_t poll_interval_ns_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct siox_master *smaster = to_siox_master(dev);
+
+	return sprintf(buf, "%lld\n", jiffies_to_nsecs(smaster->poll_interval));
+}
+
+static ssize_t poll_interval_ns_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct siox_master *smaster = to_siox_master(dev);
+	int ret;
+	u64 val;
+
+	siox_master_lock(smaster);
+
+	ret = kstrtou64(buf, 0, &val);
+	if (ret)
+		goto out_unlock;
+
+	smaster->poll_interval = nsecs_to_jiffies(val);
+
+out_unlock:
+	siox_master_unlock(smaster);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(poll_interval_ns);
+
+static struct attribute *siox_master_attrs[] = {
+	&dev_attr_active.attr,
+	&dev_attr_device_add.attr,
+	&dev_attr_device_remove.attr,
+	&dev_attr_poll_interval_ns.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(siox_master);
+
+static void siox_master_release(struct device *dev)
+{
+	struct siox_master *smaster = to_siox_master(dev);
+
+	kfree(smaster);
+}
+
+static struct device_type siox_master_type = {
+	.groups = siox_master_groups,
+	.release = siox_master_release,
+};
+
+struct siox_master *siox_master_alloc(struct device *dev,
+				      size_t size)
+{
+	struct siox_master *smaster;
+
+	if (!dev)
+		return NULL;
+
+	smaster = kzalloc(sizeof(*smaster) + size, GFP_KERNEL);
+	if (!smaster)
+		return NULL;
+
+	device_initialize(&smaster->dev);
+
+	smaster->busno = -1;
+	smaster->dev.bus = &siox_bus_type;
+	smaster->dev.type = &siox_master_type;
+	smaster->dev.parent = dev;
+	smaster->poll_interval = DIV_ROUND_UP(HZ, 40);
+
+	dev_set_drvdata(&smaster->dev, &smaster[1]);
+
+	return smaster;
+}
+EXPORT_SYMBOL_GPL(siox_master_alloc);
+
+int siox_master_register(struct siox_master *smaster)
+{
+	if (!siox_is_registered)
+		return -EPROBE_DEFER;
+
+	if (!smaster->pushpull)
+		return -EINVAL;
+
+	dev_set_name(&smaster->dev, "siox-%d", smaster->busno);
+
+	mutex_init(&smaster->lock);
+	INIT_LIST_HEAD(&smaster->devices);
+	INIT_DELAYED_WORK(&smaster->poll, siox_poll);
+
+	return device_add(&smaster->dev);
+}
+EXPORT_SYMBOL_GPL(siox_master_register);
+
+void siox_master_unregister(struct siox_master *smaster)
+{
+	/* remove device */
+	device_del(&smaster->dev);
+
+	siox_master_lock(smaster);
+
+	__siox_stop(smaster);
+
+	while (smaster->num_devices) {
+		struct siox_device *sdevice;
+
+		sdevice = container_of(smaster->devices.prev, struct siox_device, node);
+		list_del(&sdevice->node);
+		smaster->num_devices--;
+
+		siox_master_unlock(smaster);
+
+		device_unregister(&sdevice->dev);
+
+		siox_master_lock(smaster);
+	}
+
+	siox_master_unlock(smaster);
+
+	put_device(&smaster->dev);
+}
+EXPORT_SYMBOL_GPL(siox_master_unregister);
+
+struct siox_device *siox_device_add(struct siox_master *smaster)
+{
+	struct siox_device *sdevice;
+	int ret;
+
+	sdevice = kzalloc(sizeof(*sdevice), GFP_KERNEL);
+	if (!sdevice)
+		return NULL;
+
+	sdevice->type = "siox-12x8";
+	sdevice->inbytes = 2;
+	sdevice->outbytes = 4;
+
+	sdevice->smaster = smaster;
+	sdevice->dev.parent = &smaster->dev;
+	sdevice->dev.bus = &siox_bus_type;
+	sdevice->dev.type = &siox_device_type;
+
+	siox_master_lock(smaster);
+
+	dev_set_name(&sdevice->dev, "siox-%d-%d",
+		     smaster->busno, smaster->num_devices);
+
+	ret = device_register(&sdevice->dev);
+	if (ret) {
+		dev_err(&smaster->dev, "failed to register device: %d\n", ret);
+
+		goto err_device_register;
+	}
+
+	smaster->num_devices++;
+	list_add_tail(&sdevice->node, &smaster->devices);
+
+	smaster->setbuf_len += sdevice->inbytes;
+	smaster->getbuf_len += sdevice->outbytes;
+
+	if (smaster->buf_len < smaster->setbuf_len + smaster->getbuf_len) {
+		smaster->buf_len = smaster->setbuf_len + smaster->getbuf_len;
+		smaster->buf = krealloc(smaster->buf,
+					smaster->buf_len, GFP_KERNEL);
+		if (!smaster->buf) {
+			dev_err(&smaster->dev,
+				"failed to realloc buffer to %zu\n",
+				smaster->buf_len);
+			if (smaster->active)
+				__siox_stop(smaster);
+		}
+	}
+
+	siox_master_unlock(smaster);
+
+	sdevice->status_errors_kn = sysfs_get_dirent(sdevice->dev.kobj.sd,
+						     "status_errors");
+	sdevice->watchdog_kn = sysfs_get_dirent(sdevice->dev.kobj.sd,
+						"watchdog");
+	sdevice->watchdog_errors_kn = sysfs_get_dirent(sdevice->dev.kobj.sd,
+						       "watchdog_errors");
+
+	return sdevice;
+
+err_device_register:
+	siox_master_unlock(smaster);
+
+	kfree(sdevice);
+
+	return ERR_PTR(ret);
+}
+
+void siox_device_remove(struct siox_master *smaster)
+{
+	struct siox_device *sdevice;
+
+	siox_master_lock(smaster);
+
+	if (!smaster->num_devices)
+		return;
+
+	sdevice = container_of(smaster->devices.prev, struct siox_device, node);
+	list_del(&sdevice->node);
+	smaster->num_devices--;
+
+	smaster->setbuf_len -= sdevice->inbytes;
+	smaster->getbuf_len -= sdevice->outbytes;
+
+	if (!smaster->num_devices)
+		__siox_stop(smaster);
+
+	siox_master_unlock(smaster);
+
+	/*
+	 * This must be done without holding the master lock because we're
+	 * called from device_remove_store which also holds a sysfs mutex.
+	 * device_unregister tries to aquire the same lock.
+	 */
+	device_unregister(&sdevice->dev);
+}
+
+int __siox_driver_register(struct siox_driver *sdriver, struct module *owner)
+{
+	int ret;
+
+	if (unlikely(!siox_is_registered))
+		return -EPROBE_DEFER;
+
+	if (!sdriver->set_data && !sdriver->get_data) {
+		pr_err("Driver %s doesn't provide needed callbacks\n",
+		       sdriver->driver.name);
+		return -EINVAL;
+	}
+
+	sdriver->driver.owner = owner;
+	sdriver->driver.bus = &siox_bus_type;
+
+	if (sdriver->probe)
+		sdriver->driver.probe = siox_driver_probe;
+	if (sdriver->remove)
+		sdriver->driver.remove = siox_driver_remove;
+	if (sdriver->shutdown)
+		sdriver->driver.shutdown = siox_driver_shutdown;
+
+	ret = driver_register(&sdriver->driver);
+	if (ret)
+		pr_err("Failed to register siox driver %s (%d)\n",
+		       sdriver->driver.name, ret);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(__siox_driver_register);
+
+static int __init siox_init(void)
+{
+	int ret;
+
+	ret = bus_register(&siox_bus_type);
+	if (ret) {
+		pr_err("Registration of SIOX bus type failed: %d\n", ret);
+		return ret;
+	}
+
+	wqueue = create_singlethread_workqueue("siox");
+	if (!wqueue) {
+		pr_err("Creation of siox workqueue failed\n");
+		bus_unregister(&siox_bus_type);
+		return -ENOMEM;
+	}
+
+	siox_is_registered = true;
+
+	return 0;
+}
+subsys_initcall(siox_init);
+
+static void __exit siox_exit(void)
+{
+	flush_workqueue(wqueue);
+	destroy_workqueue(wqueue);
+	bus_unregister(&siox_bus_type);
+}
+module_exit(siox_exit);
+
+MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>");
+MODULE_DESCRIPTION("Eckelmann SIOX driver core");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/siox/siox.h b/drivers/siox/siox.h
new file mode 100644
index 000000000000..1c51ccc7628f
--- /dev/null
+++ b/drivers/siox/siox.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+#include <linux/kernel.h>
+#include <linux/workqueue.h>
+
+#include <linux/siox.h>
+
+#define to_siox_master(_dev)	container_of((_dev), struct siox_master, dev)
+struct siox_master {
+	/* these fields should be initialized by the driver */
+	int busno;
+	int (*pushpull)(struct siox_master *smaster,
+			size_t setbuf_len, const u8 setbuf[],
+			size_t getbuf_len, u8 getbuf[]);
+
+	/* might be initialized by the driver, if 0 it is set to HZ / 40 */
+	unsigned long poll_interval; /* in jiffies */
+
+	/* framework private stuff */
+	struct mutex lock;
+	bool active;
+	struct module *owner;
+	struct device dev;
+	unsigned num_devices;
+	struct list_head devices;
+
+	size_t setbuf_len, getbuf_len;
+	size_t buf_len;
+	u8 *buf;
+	u8 status;
+
+	struct delayed_work poll;
+};
+
+static inline void *siox_master_get_devdata(struct siox_master *smaster)
+{
+	return dev_get_drvdata(&smaster->dev);
+}
+
+struct siox_master *siox_master_alloc(struct device *dev, size_t size);
+static inline void siox_master_put(struct siox_master *smaster)
+{
+	put_device(&smaster->dev);
+}
+
+int siox_master_register(struct siox_master *smaster);
+void siox_master_unregister(struct siox_master *smaster);
diff --git a/include/linux/siox.h b/include/linux/siox.h
new file mode 100644
index 000000000000..0bd57dcc6f6b
--- /dev/null
+++ b/include/linux/siox.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+
+#include <linux/device.h>
+
+#define to_siox_device(_dev)	container_of((_dev), struct siox_device, dev)
+struct siox_device {
+	struct list_head node; /* node in smaster->devices */
+	struct siox_master *smaster;
+	struct device dev;
+
+	char *type;
+	size_t inbytes;
+	size_t outbytes;
+
+	u8 status;
+
+	/* statistics */
+	unsigned int watchdog_errors;
+	unsigned int status_errors;
+
+	struct kernfs_node *status_errors_kn;
+	struct kernfs_node *watchdog_kn;
+	struct kernfs_node *watchdog_errors_kn;
+};
+
+#define to_siox_driver(_drv)	container_of((_drv), struct siox_driver, driver)
+struct siox_driver {
+	int (*probe)(struct siox_device *);
+	int (*remove)(struct siox_device *);
+	void (*shutdown)(struct siox_device *);
+
+	/*
+	 * buf is big enough to hold sdev->inbytes - 1 bytes, the status byte
+	 * is in the scope of the framework.
+	 */
+	int (*set_data)(struct siox_device *, u8 status, u8 buf[]);
+	/*
+	 * buf is big enough to hold sdev->outbytes - 1 bytes, the status byte
+	 * is in the scope of the framework
+	 */
+	int (*get_data)(struct siox_device *, const u8 buf[]);
+
+	struct device_driver driver;
+};
+
+int __siox_driver_register(struct siox_driver *sdriver, struct module *owner);
+static inline int siox_driver_register(struct siox_driver *sdriver)
+{
+	return __siox_driver_register(sdriver, THIS_MODULE);
+}
+
+static inline void siox_driver_unregister(struct siox_driver *sdriver)
+{
+	return driver_unregister(&sdriver->driver);
+}
-- 
2.7.0

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

* [PATCH RFC v2 2/4] siox: add gpio bus driver
  2016-03-21 10:05 [PATCH RFC v2 0/4] new bus type SIOX Uwe Kleine-König
  2016-03-21 10:05 ` [PATCH RFC v2 1/4] siox: new driver framework for eckelmann SIOX Uwe Kleine-König
@ 2016-03-21 10:05 ` Uwe Kleine-König
  2016-03-21 10:05 ` [PATCH RFC v2 3/4] gpio: new driver to work with a 8x12 siox Uwe Kleine-König
  2016-03-21 10:05 ` [PATCH RFC v2 4/4] siox: add support for tracing Uwe Kleine-König
  3 siblings, 0 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2016-03-21 10:05 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman; +Cc: kernel

---
 drivers/siox/Kconfig         |   7 ++
 drivers/siox/Makefile        |   1 +
 drivers/siox/siox-bus-gpio.c | 175 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 183 insertions(+)
 create mode 100644 drivers/siox/siox-bus-gpio.c

diff --git a/drivers/siox/Kconfig b/drivers/siox/Kconfig
index 471fbc009521..7bd28527203f 100644
--- a/drivers/siox/Kconfig
+++ b/drivers/siox/Kconfig
@@ -1,2 +1,9 @@
 menuconfig SIOX
 	tristate "Eckelmann SIOX Support"
+
+if SIOX
+
+config SIOX_BUS_GPIO
+	tristate "SIOX GPIO bus driver"
+
+endif
diff --git a/drivers/siox/Makefile b/drivers/siox/Makefile
index d55cb5e08868..a956f65206d5 100644
--- a/drivers/siox/Makefile
+++ b/drivers/siox/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_SIOX) += siox-core.o
+obj-$(CONFIG_SIOX_BUS_GPIO) += siox-bus-gpio.o
diff --git a/drivers/siox/siox-bus-gpio.c b/drivers/siox/siox-bus-gpio.c
new file mode 100644
index 000000000000..6ac4b70a4362
--- /dev/null
+++ b/drivers/siox/siox-bus-gpio.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <linux/delay.h>
+
+#include "siox.h"
+
+#define DRIVER_NAME "siox-gpio"
+
+struct siox_gpio_ddata {
+	struct gpio_desc *din;
+	struct gpio_desc *dout;
+	struct gpio_desc *dclk;
+	struct gpio_desc *dld;
+};
+
+unsigned int siox_clkhigh_ns = 1000;
+unsigned int siox_loadhigh_ns;
+unsigned int siox_bytegap_ns;
+
+static int siox_gpio_pushpull(struct siox_master *smaster,
+			      size_t setbuf_len, const u8 setbuf[],
+			      size_t getbuf_len, u8 getbuf[])
+{
+	struct siox_gpio_ddata *ddata = siox_master_get_devdata(smaster);
+	size_t i;
+	size_t cycles = max(setbuf_len, getbuf_len);
+
+	/* reset data and clock */
+	gpiod_set_value_cansleep(ddata->dout, 0);
+	gpiod_set_value_cansleep(ddata->dclk, 0);
+
+	gpiod_set_value_cansleep(ddata->dld, 1);
+	ndelay(siox_loadhigh_ns);
+	gpiod_set_value_cansleep(ddata->dld, 0);
+
+	for (i = 0; i < cycles; ++i) {
+		u8 set = 0, get = 0;
+		size_t j;
+
+		if (i >= cycles - setbuf_len)
+			set = setbuf[i - (cycles - setbuf_len)];
+
+		for (j = 0; j < 8; ++j) {
+			get <<= 1;
+			if (gpiod_get_value_cansleep(ddata->din))
+				get |= 1;
+
+			/* DOUT is logically inverted */
+			gpiod_set_value_cansleep(ddata->dout, !(set & 0x80));
+			set <<= 1;
+
+			gpiod_set_value_cansleep(ddata->dclk, 1);
+			ndelay(siox_clkhigh_ns);
+			gpiod_set_value_cansleep(ddata->dclk, 0);
+		}
+
+		if (i < getbuf_len)
+			getbuf[i] = get;
+
+		ndelay(siox_bytegap_ns);
+	}
+
+	gpiod_set_value_cansleep(ddata->dld, 1);
+	ndelay(siox_loadhigh_ns);
+	gpiod_set_value_cansleep(ddata->dld, 0);
+
+	/*
+	 * Resetting dout isn't necessary protocol wise, but it makes the
+	 * signals more pretty because the dout level is deterministic between
+	 * cycles. Note that this only affects dout between the master and the
+	 * first siox device. dout for the later devices depend on the output of
+	 * the previous siox device.
+	 */
+	gpiod_set_value_cansleep(ddata->dout, 0);
+
+	return 0;
+}
+
+static int siox_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct siox_gpio_ddata *ddata;
+	int ret;
+	struct siox_master *smaster;
+
+	smaster = siox_master_alloc(&pdev->dev, sizeof(*ddata));
+	if (!smaster) {
+		dev_err(dev, "failed to allocate siox master\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(pdev, smaster);
+	ddata = siox_master_get_devdata(smaster);
+
+	ddata->din = devm_gpiod_get(dev, "din", GPIOD_IN);
+	if (IS_ERR(ddata->din)) {
+		ret = PTR_ERR(ddata->din);
+		dev_err(dev, "Failed to get %s GPIO: %d\n", "din", ret);
+		goto err;
+	}
+
+	ddata->dout = devm_gpiod_get(dev, "dout", GPIOD_OUT_LOW);
+	if (IS_ERR(ddata->dout)) {
+		ret = PTR_ERR(ddata->dout);
+		dev_err(dev, "Failed to get %s GPIO: %d\n", "dout", ret);
+		goto err;
+	}
+
+	ddata->dclk = devm_gpiod_get(dev, "dclk", GPIOD_OUT_LOW);
+	if (IS_ERR(ddata->dclk)) {
+		ret = PTR_ERR(ddata->dclk);
+		dev_err(dev, "Failed to get %s GPIO: %d\n", "dclk", ret);
+		goto err;
+	}
+
+	ddata->dld = devm_gpiod_get(dev, "dld", GPIOD_OUT_LOW);
+	if (IS_ERR(ddata->dld)) {
+		ret = PTR_ERR(ddata->dld);
+		dev_err(dev, "Failed to get %s GPIO: %d\n", "dld", ret);
+		goto err;
+	}
+
+	smaster->pushpull = siox_gpio_pushpull;
+	/* XXX: determine automatically like spi does */
+	smaster->busno = 0;
+
+	ret = siox_master_register(smaster);
+	if (ret) {
+		dev_err(dev, "Failed to register siox master: %d\n", ret);
+err:
+		siox_master_put(smaster);
+	}
+
+	return ret;
+}
+
+static int siox_gpio_remove(struct platform_device *pdev)
+{
+	struct siox_master *master = platform_get_drvdata(pdev);
+
+	siox_master_unregister(master);
+
+	return 0;
+}
+
+static const struct of_device_id siox_gpio_dt_ids[] = {
+	{ .compatible = "eckelmann,siox-gpio", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, siox_gpio_dt_ids);
+
+static struct platform_driver siox_gpio_driver = {
+	.probe = siox_gpio_probe,
+	.remove = siox_gpio_remove,
+
+	.driver = {
+		.name = DRIVER_NAME,
+		.of_match_table = siox_gpio_dt_ids,
+	},
+};
+module_platform_driver(siox_gpio_driver);
+
+MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
-- 
2.7.0

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

* [PATCH RFC v2 3/4] gpio: new driver to work with a 8x12 siox
  2016-03-21 10:05 [PATCH RFC v2 0/4] new bus type SIOX Uwe Kleine-König
  2016-03-21 10:05 ` [PATCH RFC v2 1/4] siox: new driver framework for eckelmann SIOX Uwe Kleine-König
  2016-03-21 10:05 ` [PATCH RFC v2 2/4] siox: add gpio bus driver Uwe Kleine-König
@ 2016-03-21 10:05 ` Uwe Kleine-König
  2016-03-21 10:05 ` [PATCH RFC v2 4/4] siox: add support for tracing Uwe Kleine-König
  3 siblings, 0 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2016-03-21 10:05 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman; +Cc: kernel

---
 drivers/gpio/Kconfig     |   5 +
 drivers/gpio/Makefile    |   1 +
 drivers/gpio/gpio-siox.c | 290 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 296 insertions(+)
 create mode 100644 drivers/gpio/gpio-siox.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 8f1fe739c985..c9cee1fb60aa 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -404,6 +404,11 @@ config GPIO_SCH311X
 	  To compile this driver as a module, choose M here: the module will
 	  be called gpio-sch311x.
 
+config GPIO_SIOX
+	tristate "SIOX GPIO support"
+	depends on SIOX
+	select GPIOLIB_IRQCHIP
+
 config GPIO_SPEAR_SPICS
 	bool "ST SPEAr13xx SPI Chip Select as GPIO support"
 	depends on PLAT_SPEAR
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index f82cd678ce08..29d79f858003 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -93,6 +93,7 @@ obj-$(CONFIG_GPIO_TC3589X)	+= gpio-tc3589x.o
 obj-$(CONFIG_ARCH_TEGRA)	+= gpio-tegra.o
 obj-$(CONFIG_GPIO_TIMBERDALE)	+= gpio-timberdale.o
 obj-$(CONFIG_GPIO_PALMAS)	+= gpio-palmas.o
+obj-$(CONFIG_GPIO_SIOX)		+= gpio-siox.o
 obj-$(CONFIG_GPIO_TPS6586X)	+= gpio-tps6586x.o
 obj-$(CONFIG_GPIO_TPS65910)	+= gpio-tps65910.o
 obj-$(CONFIG_GPIO_TPS65912)	+= gpio-tps65912.o
diff --git a/drivers/gpio/gpio-siox.c b/drivers/gpio/gpio-siox.c
new file mode 100644
index 000000000000..c03022f0c2ad
--- /dev/null
+++ b/drivers/gpio/gpio-siox.c
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2015 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License version 2 as published by the
+ * Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/siox.h>
+#include <linux/gpio/driver.h>
+#include <linux/of.h>
+
+struct gpio_siox_ddata {
+	struct gpio_chip gchip;
+	struct irq_chip ichip;
+	struct mutex lock;
+	u8 setdata[1];
+	u8 getdata[3];
+
+	spinlock_t irqlock;
+	u32 irq_enable;
+	u32 irq_status;
+	u32 irq_type[20];
+};
+
+/* XXX
+ * should set block until data is shifted out?
+ * should get block until new data is shifted in?
+ */
+static int gpio_siox_set_data(struct siox_device *sdevice, u8 status, u8 buf[])
+{
+	struct gpio_siox_ddata *ddata = dev_get_drvdata(&sdevice->dev);
+
+	mutex_lock(&ddata->lock);
+	buf[0] = ddata->setdata[0];
+	mutex_unlock(&ddata->lock);
+
+	return 0;
+}
+
+static int gpio_siox_get_data(struct siox_device *sdevice, const u8 buf[])
+{
+	struct gpio_siox_ddata *ddata = dev_get_drvdata(&sdevice->dev);
+	size_t offset;
+	u32 trigger;
+
+	mutex_lock(&ddata->lock);
+
+	spin_lock_irq(&ddata->irqlock);
+
+	for (offset = 0; offset < 12; ++offset) {
+		unsigned bitpos = 11 - offset;
+		unsigned gpiolevel = buf[bitpos / 8] & (1 << bitpos % 8);
+		unsigned prev_gpiolevel = ddata->getdata[bitpos / 8] & (1 << (bitpos % 8));
+
+		if (gpiolevel) {
+			if ((ddata->irq_type[offset] & IRQ_TYPE_LEVEL_HIGH) ||
+			    ((ddata->irq_type[offset] & IRQ_TYPE_EDGE_RISING) && !prev_gpiolevel))
+				ddata->irq_status |= 1 << offset;
+		} else {
+			if ((ddata->irq_type[offset] & IRQ_TYPE_LEVEL_LOW) ||
+			    ((ddata->irq_type[offset] & IRQ_TYPE_EDGE_FALLING) && prev_gpiolevel))
+				ddata->irq_status |= 1 << offset;
+		}
+	}
+
+	trigger = ddata->irq_status & ddata->irq_enable;
+
+	spin_unlock_irq(&ddata->irqlock);
+
+	for (offset = 0; offset < 12; ++offset) {
+		if (trigger & (1 << offset)) {
+			struct irq_domain *irqdomain = ddata->gchip.irqdomain;
+			unsigned int irq = irq_find_mapping(irqdomain, offset);
+
+			/*
+			 * Conceptually handle_nested_irq should call the flow
+			 * handler of the irq chip. But it doesn't, so we have
+			 * to clean the irq_status here.
+			 */
+			spin_lock_irq(&ddata->irqlock);
+			ddata->irq_status &= ~(1 << offset);
+			spin_unlock_irq(&ddata->irqlock);
+
+			handle_nested_irq(irq);
+		}
+	}
+
+	ddata->getdata[0] = buf[0];
+	ddata->getdata[1] = buf[1];
+	ddata->getdata[2] = buf[2];
+
+	mutex_unlock(&ddata->lock);
+
+	return 0;
+}
+
+static void gpio_siox_irq_ack(struct irq_data *d)
+{
+	struct irq_chip *ic = irq_data_get_irq_chip(d);
+	struct gpio_siox_ddata *ddata =
+		container_of(ic, struct gpio_siox_ddata, ichip);
+
+	spin_lock_irq(&ddata->irqlock);
+	ddata->irq_status &= ~(1 << d->hwirq);
+	spin_unlock_irq(&ddata->irqlock);
+}
+
+static void gpio_siox_irq_mask(struct irq_data *d)
+{
+	struct irq_chip *ic = irq_data_get_irq_chip(d);
+	struct gpio_siox_ddata *ddata =
+		container_of(ic, struct gpio_siox_ddata, ichip);
+
+	spin_lock_irq(&ddata->irqlock);
+	ddata->irq_enable &= ~(1 << d->hwirq);
+	spin_unlock_irq(&ddata->irqlock);
+}
+
+static void gpio_siox_irq_unmask(struct irq_data *d)
+{
+	struct irq_chip *ic = irq_data_get_irq_chip(d);
+	struct gpio_siox_ddata *ddata =
+		container_of(ic, struct gpio_siox_ddata, ichip);
+
+	spin_lock_irq(&ddata->irqlock);
+	ddata->irq_enable |= 1 << d->hwirq;
+	spin_unlock_irq(&ddata->irqlock);
+}
+
+static int gpio_siox_irq_set_type(struct irq_data *d, u32 type)
+{
+	struct irq_chip *ic = irq_data_get_irq_chip(d);
+	struct gpio_siox_ddata *ddata =
+		container_of(ic, struct gpio_siox_ddata, ichip);
+
+	spin_lock_irq(&ddata->irqlock);
+	ddata->irq_type[d->hwirq] = type;
+	spin_unlock_irq(&ddata->irqlock);
+
+	return 0;
+}
+
+static int gpio_siox_get(struct gpio_chip *chip, unsigned offset)
+{
+	struct gpio_siox_ddata *ddata =
+		container_of(chip, struct gpio_siox_ddata, gchip);
+	int ret;
+
+	mutex_lock(&ddata->lock);
+
+	if (offset >= 12) {
+		unsigned bitpos = 19 - offset;
+		ret = ddata->setdata[0] & (1 << bitpos);
+	} else {
+		unsigned bitpos = 11 - offset;
+		ret = ddata->getdata[bitpos / 8] & (1 << (bitpos % 8));
+	}
+
+	mutex_unlock(&ddata->lock);
+
+	return ret;
+}
+
+static void gpio_siox_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+	struct gpio_siox_ddata *ddata =
+		container_of(chip, struct gpio_siox_ddata, gchip);
+	u8 mask = 1 << (19 - offset);
+
+	mutex_lock(&ddata->lock);
+
+	if (value)
+		ddata->setdata[0] |= mask;
+	else
+		ddata->setdata[0] &= ~mask;
+
+	mutex_unlock(&ddata->lock);
+}
+
+static int gpio_siox_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+	if (offset >= 12)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int gpio_siox_direction_output(struct gpio_chip *chip,
+				      unsigned offset, int value)
+{
+	if (offset < 12)
+		return -EINVAL;
+
+	gpio_siox_set(chip, offset, value);
+	return 0;
+}
+
+static int gpio_siox_get_direction(struct gpio_chip *chip, unsigned offset)
+{
+	if (offset < 12)
+		return 1; /* input */
+	else
+		return 0; /* output */
+}
+
+static int gpio_siox_probe(struct siox_device *sdevice)
+{
+	struct gpio_siox_ddata *ddata;
+	int ret;
+
+	ddata = devm_kzalloc(&sdevice->dev, sizeof(*ddata), GFP_KERNEL);
+	if (!ddata) {
+		dev_info(&sdevice->dev, "failed to allocate driver data\n");
+		return -ENOMEM;
+	}
+
+	dev_set_drvdata(&sdevice->dev, ddata);
+
+	mutex_init(&ddata->lock);
+	spin_lock_init(&ddata->irqlock);
+
+	ddata->gchip.base = -1;
+	ddata->gchip.can_sleep = 1;
+	ddata->gchip.dev = &sdevice->dev;
+	ddata->gchip.owner = THIS_MODULE;
+	ddata->gchip.get = gpio_siox_get;
+	ddata->gchip.set = gpio_siox_set;
+	ddata->gchip.direction_input = gpio_siox_direction_input;
+	ddata->gchip.direction_output = gpio_siox_direction_output;
+	ddata->gchip.get_direction = gpio_siox_get_direction;
+	ddata->gchip.ngpio = 20;
+
+	ddata->ichip.name = "siox-gpio";
+	ddata->ichip.irq_ack = gpio_siox_irq_ack;
+	ddata->ichip.irq_mask = gpio_siox_irq_mask;
+	ddata->ichip.irq_unmask = gpio_siox_irq_unmask;
+	ddata->ichip.irq_set_type = gpio_siox_irq_set_type;
+
+	ret = gpiochip_add(&ddata->gchip);
+	if (ret) {
+		dev_err(&sdevice->dev, "Failed to register gpio chip (%d)\n", ret);
+		goto err_gpiochip;
+	}
+
+	ret = gpiochip_irqchip_add(&ddata->gchip, &ddata->ichip,
+				   0, handle_level_irq, IRQ_TYPE_EDGE_RISING);
+	if (ret) {
+		dev_err(&sdevice->dev, "Failed to register irq chip (%d)\n", ret);
+err_gpiochip:
+		gpiochip_remove(&ddata->gchip);
+	}
+
+	return ret;
+}
+
+static int gpio_siox_remove(struct siox_device *sdevice)
+{
+	struct gpio_siox_ddata *ddata = dev_get_drvdata(&sdevice->dev);
+
+	gpiochip_remove(&ddata->gchip);
+	return 0;
+}
+
+static struct siox_driver gpio_siox_driver = {
+	.probe = gpio_siox_probe,
+	.remove = gpio_siox_remove,
+	.set_data = gpio_siox_set_data,
+	.get_data = gpio_siox_get_data,
+	.driver = {
+		.name = "gpio-siox",
+	},
+};
+
+static int __init gpio_siox_init(void)
+{
+	return siox_driver_register(&gpio_siox_driver);
+}
+module_init(gpio_siox_init);
+
+static void __exit gpio_siox_exit(void)
+{
+	siox_driver_unregister(&gpio_siox_driver);
+}
+module_exit(gpio_siox_exit);
+
+MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>");
+MODULE_DESCRIPTION("SIOX gpio driver");
+MODULE_LICENSE("GPL v2");
-- 
2.7.0

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

* [PATCH RFC v2 4/4] siox: add support for tracing
  2016-03-21 10:05 [PATCH RFC v2 0/4] new bus type SIOX Uwe Kleine-König
                   ` (2 preceding siblings ...)
  2016-03-21 10:05 ` [PATCH RFC v2 3/4] gpio: new driver to work with a 8x12 siox Uwe Kleine-König
@ 2016-03-21 10:05 ` Uwe Kleine-König
  3 siblings, 0 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2016-03-21 10:05 UTC (permalink / raw)
  To: linux-kernel, Greg Kroah-Hartman; +Cc: kernel

---
 drivers/siox/siox-core.c    | 12 +++++++++
 include/trace/events/siox.h | 60 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 72 insertions(+)
 create mode 100644 include/trace/events/siox.h

diff --git a/drivers/siox/siox-core.c b/drivers/siox/siox-core.c
index b4a128218d7d..9d9c04c5de59 100644
--- a/drivers/siox/siox-core.c
+++ b/drivers/siox/siox-core.c
@@ -13,6 +13,9 @@
 
 #include "siox.h"
 
+#define CREATE_TRACE_POINTS
+#include <trace/events/siox.h>
+
 struct workqueue_struct *wqueue;
 static bool siox_is_registered;
 
@@ -33,6 +36,7 @@ static void __siox_poll(struct siox_master *smaster)
 {
 	struct siox_device *sdevice;
 	size_t i = 0;
+	unsigned int devno = smaster->num_devices;
 	u8 prevstatus = smaster->status;
 
 	if (++smaster->status > 0x0d)
@@ -44,16 +48,21 @@ static void __siox_poll(struct siox_master *smaster)
 		struct siox_driver *sdriver =
 			sdevice->dev.driver ? to_siox_driver(sdevice->dev.driver) : NULL;
 
+		devno--;
+
 		if (sdriver)
 			sdriver->set_data(sdevice, smaster->status,
 					  &smaster->buf[i + 1]);
 
 		smaster->buf[i] = smaster->status;
 
+		trace_siox_set_data(smaster, sdevice, devno, i);
+
 		i += sdevice->inbytes;
 	}
 
 	BUG_ON(i != smaster->setbuf_len);
+	BUG_ON(devno);
 
 	smaster->pushpull(smaster, smaster->setbuf_len, smaster->buf,
 			  smaster->getbuf_len,
@@ -91,9 +100,12 @@ static void __siox_poll(struct siox_master *smaster)
 		 * counter. Should the bus stop to poll in these cases?
 		 */
 
+		trace_siox_get_data(smaster, sdevice, devno, i);
+
 		if (sdriver)
 			sdriver->get_data(sdevice, &smaster->buf[i]);
 
+		devno++;
 		i += sdevice->outbytes;
 	}
 
diff --git a/include/trace/events/siox.h b/include/trace/events/siox.h
new file mode 100644
index 000000000000..1cc1a0eea12b
--- /dev/null
+++ b/include/trace/events/siox.h
@@ -0,0 +1,60 @@
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM siox
+
+#if !defined(_TRACE_SIOX_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_SIOX_H
+
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(siox_set_data,
+	    TP_PROTO(const struct siox_master *smaster,
+		     const struct siox_device *sdevice,
+		     unsigned int devno, size_t bufoffset),
+	    TP_ARGS(smaster, sdevice, devno, bufoffset),
+	    TP_STRUCT__entry(
+			     __field(int, busno)
+			     __field(unsigned int, devno)
+			     __field(size_t, inbytes)
+			     __dynamic_array(u8, buf, sdevice->inbytes)
+			    ),
+	    TP_fast_assign(
+			   __entry->busno = smaster->busno;
+			   __entry->devno = devno;
+			   __entry->inbytes = sdevice->inbytes;
+			   memcpy(__get_dynamic_array(buf), smaster->buf + bufoffset, sdevice->inbytes);
+			  ),
+	    TP_printk("siox-%d-%u [%*phD]",
+		      __entry->busno,
+		      __entry->devno,
+		      __entry->inbytes, __get_dynamic_array(buf)
+		     )
+);
+
+TRACE_EVENT(siox_get_data,
+	    TP_PROTO(const struct siox_master *smaster,
+		     const struct siox_device *sdevice,
+		     unsigned int devno, size_t bufoffset),
+	    TP_ARGS(smaster, sdevice, devno, bufoffset),
+	    TP_STRUCT__entry(
+			     __field(int, busno)
+			     __field(unsigned int, devno)
+			     __field(size_t, outbytes)
+			     __dynamic_array(u8, buf, sdevice->outbytes)
+			    ),
+	    TP_fast_assign(
+			   __entry->busno = smaster->busno;
+			   __entry->devno = devno;
+			   __entry->outbytes = sdevice->outbytes;
+			   memcpy(__get_dynamic_array(buf), smaster->buf + bufoffset, sdevice->outbytes);
+			  ),
+	    TP_printk("siox-%d-%u [%*phD]",
+		      __entry->busno,
+		      __entry->devno,
+		      __entry->outbytes, __get_dynamic_array(buf)
+		     )
+);
+
+#endif /* if !defined(_TRACE_SIOX_H) || defined(TRACE_HEADER_MULTI_READ) */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
-- 
2.7.0

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

* Re: [PATCH RFC v2 1/4] siox: new driver framework for eckelmann SIOX
  2016-03-21 10:05 ` [PATCH RFC v2 1/4] siox: new driver framework for eckelmann SIOX Uwe Kleine-König
@ 2016-05-01 21:27   ` Greg Kroah-Hartman
  2016-05-02  7:10     ` Uwe Kleine-König
  0 siblings, 1 reply; 7+ messages in thread
From: Greg Kroah-Hartman @ 2016-05-01 21:27 UTC (permalink / raw)
  To: Uwe Kleine-König; +Cc: linux-kernel, kernel

On Mon, Mar 21, 2016 at 11:05:19AM +0100, Uwe Kleine-König wrote:
> ---

no changelog text or signed-off-by?  Oh well :(

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

* Re: [PATCH RFC v2 1/4] siox: new driver framework for eckelmann SIOX
  2016-05-01 21:27   ` Greg Kroah-Hartman
@ 2016-05-02  7:10     ` Uwe Kleine-König
  0 siblings, 0 replies; 7+ messages in thread
From: Uwe Kleine-König @ 2016-05-02  7:10 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: linux-kernel, kernel

On Sun, May 01, 2016 at 02:27:32PM -0700, Greg Kroah-Hartman wrote:
> On Mon, Mar 21, 2016 at 11:05:19AM +0100, Uwe Kleine-König wrote:
> > ---
> 
> no changelog text or signed-off-by?  Oh well :(

Right. I didn't expect the patches to be ready yet for merging andyhow,
but adding some text and a signoff should not be a problem. Assuming the
patches are not in your mailbox any more, I will resend accordingly.

Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

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

end of thread, other threads:[~2016-05-02  7:10 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-03-21 10:05 [PATCH RFC v2 0/4] new bus type SIOX Uwe Kleine-König
2016-03-21 10:05 ` [PATCH RFC v2 1/4] siox: new driver framework for eckelmann SIOX Uwe Kleine-König
2016-05-01 21:27   ` Greg Kroah-Hartman
2016-05-02  7:10     ` Uwe Kleine-König
2016-03-21 10:05 ` [PATCH RFC v2 2/4] siox: add gpio bus driver Uwe Kleine-König
2016-03-21 10:05 ` [PATCH RFC v2 3/4] gpio: new driver to work with a 8x12 siox Uwe Kleine-König
2016-03-21 10:05 ` [PATCH RFC v2 4/4] siox: add support for tracing Uwe Kleine-König

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).