All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] misc devices: HC-SRO4 ultrasonic distance driver
       [not found] <56F3FFF2.40906@johannesthoma.com>
@ 2016-03-24 15:02 ` Johannes Thoma
  2016-03-24 15:24   ` Greg KH
  2016-03-24 15:52   ` [PATCH] misc devices: HC-SRO4 ultrasonic distance driver Daniel Baluta
  0 siblings, 2 replies; 16+ messages in thread
From: Johannes Thoma @ 2016-03-24 15:02 UTC (permalink / raw)
  To: kernelnewbies

 From 56e8f71c990b92c28a8cb03d859880eab8d06a3d Mon Sep 17 00:00:00 2001
From: Johannes Thoma <johannes@johannesthoma.com>
Date: Mon, 21 Mar 2016 22:11:01 +0100
Subject: [PATCH] HC-SRO4 ultrasonic distance sensor driver

The HC-SRO4 is an ultrasonic distance sensor attached to two GPIO
pins. The driver is controlled via sysfs and supports an (in theory)
unlimited number of HC-SRO4 devices.

Unlike user land solutions this driver produces precise results
even when there is high load on the system. It uses a non-blocking
interrupt triggered mechanism to record the length of the echo
signal.

This patch is against the raspberry pi kernel from
https://github.com/raspberrypi/linux.git hash
e481b5ceae6c94c7e60f8bb8591cbb362806246e

Note that this patch isn't meant for lkml (yet) see:
TODO's:

.) Patch against mainline (or whatever kernel it belongs to)
.) Use IIO layer instead of creating random sysfs entries.
.) Fill in GPIO device as parent to device_create_with_groups().
.) Test it with two or more HC-SRO4 devices.
.) Test it on other hardware than raspberry pi.
.) Test it with kernel debugging enabled.

Anyway, comments are highly appreciated.

Signed-off-by: Johannes Thoma <johannes@johannesthoma.com>
---
  MAINTAINERS            |   5 +
  drivers/misc/Kconfig   |  11 ++
  drivers/misc/Makefile  |   1 +
  drivers/misc/hc-sro4.c | 360
+++++++++++++++++++++++++++++++++++++++++++++++++
  4 files changed, 377 insertions(+)
  create mode 100644 drivers/misc/hc-sro4.c

diff --git a/MAINTAINERS b/MAINTAINERS
index da3e4d8..f819d66 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4910,6 +4910,11 @@ W:
http://www.kernel.org/pub/linux/kernel/people/fseidel/hdaps/
  S:	Maintained
  F:	drivers/platform/x86/hdaps.c

+HC-SRO4 ULTRASONIC DISTANCE SENSOR DRIVER
+M:	Johannes Thoma <johannes@johannesthoma.com>
+S:	Maintained
+F:	drivers/misc/hc-sro4.c
+
  HDPVR USB VIDEO ENCODER DRIVER
  M:	Hans Verkuil <hverkuil@xs4all.nl>
  L:	linux-media at vger.kernel.org
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 4c499de..1be88ad 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -475,6 +475,17 @@ config BMP085_SPI
  	  To compile this driver as a module, choose M here: the
  	  module will be called bmp085-spi.

+config SENSORS_HC_SRO4
+	tristate "HC-SRO4 ultrasonic distance sensor on GPIO"
+	depends on GPIOLIB && SYSFS
+	help
+	  Say Y here if you want to support the HC-SRO4
+	  ultrasonic distance sensor to be hooked on two runtime-configurable
+	  GPIO pins.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called hc-sro4.
+
  config PCH_PHUB
  	tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB"
  	select GENERIC_NET_UTILS
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 1acff5b..9a8e508 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_QCOM_COINCELL)	+= qcom-coincell.o
  obj-$(CONFIG_SENSORS_BH1780)	+= bh1780gli.o
  obj-$(CONFIG_SENSORS_BH1770)	+= bh1770glc.o
  obj-$(CONFIG_SENSORS_APDS990X)	+= apds990x.o
+obj-$(CONFIG_SENSORS_HC_SRO4)	+= hc-sro4.o
  obj-$(CONFIG_SGI_IOC4)		+= ioc4.o
  obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o
  obj-$(CONFIG_KGDB_TESTS)	+= kgdbts.o
diff --git a/drivers/misc/hc-sro4.c b/drivers/misc/hc-sro4.c
new file mode 100644
index 0000000..9925b7e
--- /dev/null
+++ b/drivers/misc/hc-sro4.c
@@ -0,0 +1,360 @@
+/* Precise measurements of time delta between sending a trigger signal
+ * to the HC-SRO4 distance sensor and receiving the echo signal from
+ * the sensor back. This has to be precise in the usecs range. We
+ * use trigger interrupts to measure the signal, so no busy wait :)
+ *
+ * This supports an (in theory) unlimited number of HC-SRO4 devices.
+ * To add a device, do a (as root):
+ *
+ *	# echo 23 24 1000 > /sys/class/distance-sensor/configure
+ *
+ * (23 is the trigger GPIO, 24 is the echo GPIO and 1000 is a timeout in
+ *  milliseconds)
+ *
+ * Then a directory appears with a file measure in it. To measure, do a
+ *
+ *	# cat /sys/class/distance-sensor/distance_23_24/measure
+ *
+ * You'll receive the length of the echo signal in usecs. To convert
(roughly)
+ * to centimeters multiply by 17150 and divide by 1e6.
+ *
+ * To deconfigure the device, do a
+ *
+ *	# echo -23 24 > /sys/class/distance-sensor/configure
+ *
+ * (normally not needed).
+ *
+ * DO NOT attach your HC-SRO4's echo pin directly to the raspberry, since
+ * it runs with 5V while raspberry expects 3V on the GPIO inputs.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/timekeeping.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/gpio/driver.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+struct hc_sro4 {
+	int gpio_trig;
+	int gpio_echo;
+	struct gpio_desc *trig_desc;
+	struct gpio_desc *echo_desc;
+	struct timeval time_triggered;
+	struct timeval time_echoed;
+	int echo_received;
+	int device_triggered;
+	struct mutex measurement_mutex;
+	wait_queue_head_t wait_for_echo;
+	unsigned long timeout;
+	struct list_head list;
+};
+
+static LIST_HEAD(hc_sro4_devices);
+static DEFINE_MUTEX(devices_mutex);
+
+static struct hc_sro4 *create_hc_sro4(int trig, int echo, unsigned long
timeout)
+		/* must be called with devices_mutex held */
+{
+	struct hc_sro4 *new;
+	int err;
+
+	new = kmalloc(sizeof(*new), GFP_KERNEL);
+	if (new == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	new->gpio_echo = echo;
+	new->gpio_trig = trig;
+	new->echo_desc = gpio_to_desc(echo);
+	if (new->echo_desc == NULL) {
+		kfree(new);
+		return ERR_PTR(-EINVAL);
+	}
+	new->trig_desc = gpio_to_desc(trig);
+	if (new->trig_desc == NULL) {
+		kfree(new);
+		return ERR_PTR(-EINVAL);
+	}
+
+	err = gpiod_direction_input(new->echo_desc);
+	if (err < 0) {
+		kfree(new);
+		return ERR_PTR(err);
+	}
+	err = gpiod_direction_output(new->trig_desc, 0);
+	if (err < 0) {
+		kfree(new);
+		return ERR_PTR(err);
+	}
+	gpiod_set_value(new->trig_desc, 0);
+
+	mutex_init(&new->measurement_mutex);
+	init_waitqueue_head(&new->wait_for_echo);
+	new->timeout = timeout;
+
+	list_add_tail(&new->list, &hc_sro4_devices);
+
+	return new;
+}
+
+static irqreturn_t echo_received_irq(int irq, void *data)
+{
+	struct hc_sro4 *device = (struct hc_sro4 *) data;
+	int val;
+	struct timeval irq_tv;
+
+	do_gettimeofday(&irq_tv);
+
+	if (!device->device_triggered)
+		return IRQ_HANDLED;
+	if (device->echo_received)
+		return IRQ_HANDLED;
+
+	val = gpiod_get_value(device->echo_desc);
+	if (val == 1) {
+		device->time_triggered = irq_tv;
+	} else {
+		device->time_echoed = irq_tv;
+		device->echo_received = 1;
+		wake_up_interruptible(&device->wait_for_echo);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/* devices_mutex must be held by caller, so nobody deletes the device
+ * before we lock it.
+ */
+
+static int do_measurement(struct hc_sro4 *device,
+			  unsigned long long *usecs_elapsed)
+{
+	long timeout;
+	int irq;
+	int ret;
+
+	if (!mutex_trylock(&device->measurement_mutex)) {
+		mutex_unlock(&devices_mutex);
+		return -EBUSY;
+	}
+	mutex_unlock(&devices_mutex);
+
+	msleep(60);
+		/* wait 60 ms between measurements.
+		 * now, a while true ; do cat measure ; done should work
+		 */
+
+	irq = gpiod_to_irq(device->echo_desc);
+	if (irq < 0)
+		return -EIO;
+
+	device->echo_received = 0;
+	device->device_triggered = 0;
+
+	ret = request_any_context_irq(irq, echo_received_irq,
+		IRQF_SHARED | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+		"hc_sro4", device);
+
+	if (ret < 0)
+		goto out_mutex;
+
+	gpiod_set_value(device->trig_desc, 1);
+	udelay(10);
+	device->device_triggered = 1;
+	gpiod_set_value(device->trig_desc, 0);
+
+	ret = gpiochip_lock_as_irq(gpiod_to_chip(device->echo_desc),
+				   device->gpio_echo);
+	if (ret < 0)
+		goto out_irq;
+
+	timeout = wait_event_interruptible_timeout(device->wait_for_echo,
+				device->echo_received, device->timeout);
+
+	if (timeout == 0)
+		ret = -ETIMEDOUT;
+	else if (timeout < 0)
+		ret = timeout;
+	else {
+		*usecs_elapsed =
+	(device->time_echoed.tv_sec - device->time_triggered.tv_sec) * 1000000 +
+	(device->time_echoed.tv_usec - device->time_triggered.tv_usec);
+		ret = 0;
+	}
+out_irq:
+	free_irq(irq, device);
+out_mutex:
+	mutex_unlock(&device->measurement_mutex);
+
+	return ret;
+}
+
+static ssize_t sysfs_do_measurement(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	struct hc_sro4 *sensor = dev_get_drvdata(dev);
+	unsigned long long usecs_elapsed;
+	int status;
+
+	mutex_lock(&devices_mutex);
+	status = do_measurement(sensor, &usecs_elapsed);
+
+	if (status < 0)
+		return status;
+
+	return sprintf(buf, "%lld\n", usecs_elapsed);
+}
+
+DEVICE_ATTR(measure, 0444, sysfs_do_measurement, NULL);
+
+
+static struct attribute *sensor_attrs[] = {
+	&dev_attr_measure.attr,
+	NULL,
+};
+
+static const struct attribute_group sensor_group = {
+	.attrs = sensor_attrs
+};
+
+static const struct attribute_group *sensor_groups[] = {
+	&sensor_group,
+	NULL
+};
+
+static ssize_t sysfs_configure_store(struct class *class,
+				struct class_attribute *attr,
+				const char *buf, size_t len);
+
+static struct class_attribute hc_sro4_class_attrs[] = {
+	__ATTR(configure, 0200, NULL, sysfs_configure_store),
+	__ATTR_NULL,
+};
+
+static struct class hc_sro4_class = {
+	.name = "distance-sensor",
+	.owner = THIS_MODULE,
+	.class_attrs = hc_sro4_class_attrs
+};
+
+
+static struct hc_sro4 *find_sensor(int trig, int echo)
+{
+	struct hc_sro4 *sensor;
+
+	list_for_each_entry(sensor, &hc_sro4_devices, list) {
+		if (sensor->gpio_trig == trig &&
+		    sensor->gpio_echo == echo)
+			return sensor;
+	}
+	return NULL;
+}
+
+static int match_device(struct device *dev, const void *data)
+{
+	return dev_get_drvdata(dev) == data;
+}
+
+static int remove_sensor(struct hc_sro4 *rip_sensor)
+	/* must be called with devices_mutex held. */
+{
+	struct device *dev;
+
+	dev = class_find_device(&hc_sro4_class, NULL, rip_sensor, match_device);
+	if (dev == NULL)
+		return -ENODEV;
+
+	mutex_lock(&rip_sensor->measurement_mutex);
+			/* wait until measurement has finished */
+	list_del(&rip_sensor->list);
+	kfree(rip_sensor);   /* ?? double free ?? */
+
+	device_unregister(dev);
+	put_device(dev);
+
+	return 0;
+}
+
+static ssize_t sysfs_configure_store(struct class *class,
+				struct class_attribute *attr,
+				const char *buf, size_t len)
+{
+	int add = buf[0] != '-';
+	const char *s = buf;
+	int trig, echo, timeout;
+	struct hc_sro4 *new_sensor, *rip_sensor;
+	int err;
+
+	if (buf[0] == '-' || buf[0] == '+')
+		s++;
+
+	if (add) {
+		if (sscanf(s, "%d %d %d", &trig, &echo, &timeout) != 3)
+			return -EINVAL;
+
+		mutex_lock(&devices_mutex);
+		if (find_sensor(trig, echo)) {
+			mutex_unlock(&devices_mutex);
+			return -EEXIST;
+		}
+
+		new_sensor = create_hc_sro4(trig, echo, timeout);
+		mutex_unlock(&devices_mutex);
+		if (IS_ERR(new_sensor))
+			return PTR_ERR(new_sensor);
+
+		device_create_with_groups(class, NULL, MKDEV(0, 0), new_sensor,
+				sensor_groups, "distance_%d_%d", trig, echo);
+	} else {
+		if (sscanf(s, "%d %d", &trig, &echo) != 2)
+			return -EINVAL;
+
+		mutex_lock(&devices_mutex);
+		rip_sensor = find_sensor(trig, echo);
+		if (rip_sensor == NULL) {
+			mutex_unlock(&devices_mutex);
+			return -ENODEV;
+		}
+		err = remove_sensor(rip_sensor);
+		mutex_unlock(&devices_mutex);
+		if (err < 0)
+			return err;
+	}
+	return len;
+}
+
+static int __init init_hc_sro4(void)
+{
+	return class_register(&hc_sro4_class);
+}
+
+static void exit_hc_sro4(void)
+{
+	struct hc_sro4 *rip_sensor, *tmp;
+
+	mutex_lock(&devices_mutex);
+	list_for_each_entry_safe(rip_sensor, tmp, &hc_sro4_devices, list) {
+		remove_sensor(rip_sensor);   /* ignore errors */
+	}
+	mutex_unlock(&devices_mutex);
+
+	class_unregister(&hc_sro4_class);
+}
+
+module_init(init_hc_sro4);
+module_exit(exit_hc_sro4);
+
+MODULE_AUTHOR("Johannes Thoma");
+MODULE_DESCRIPTION("Distance measurement for the HC-SRO4 ultrasonic
distance sensor for the raspberry pi");
+MODULE_LICENSE("GPL");
+
-- 
1.9.1

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

* [PATCH] misc devices: HC-SRO4 ultrasonic distance driver
  2016-03-24 15:02 ` [PATCH] misc devices: HC-SRO4 ultrasonic distance driver Johannes Thoma
@ 2016-03-24 15:24   ` Greg KH
  2016-05-31 21:05     ` [PATCH] HC-SR04 ultrasonic ranger IIO driver johannes at johannesthoma.com
  2016-03-24 15:52   ` [PATCH] misc devices: HC-SRO4 ultrasonic distance driver Daniel Baluta
  1 sibling, 1 reply; 16+ messages in thread
From: Greg KH @ 2016-03-24 15:24 UTC (permalink / raw)
  To: kernelnewbies

On Thu, Mar 24, 2016 at 04:02:03PM +0100, Johannes Thoma wrote:
>  From 56e8f71c990b92c28a8cb03d859880eab8d06a3d Mon Sep 17 00:00:00 2001
> From: Johannes Thoma <johannes@johannesthoma.com>
> Date: Mon, 21 Mar 2016 22:11:01 +0100
> Subject: [PATCH] HC-SRO4 ultrasonic distance sensor driver
> 
> The HC-SRO4 is an ultrasonic distance sensor attached to two GPIO
> pins. The driver is controlled via sysfs and supports an (in theory)
> unlimited number of HC-SRO4 devices.
> 
> Unlike user land solutions this driver produces precise results
> even when there is high load on the system. It uses a non-blocking
> interrupt triggered mechanism to record the length of the echo
> signal.
> 
> This patch is against the raspberry pi kernel from
> https://github.com/raspberrypi/linux.git hash
> e481b5ceae6c94c7e60f8bb8591cbb362806246e
> 
> Note that this patch isn't meant for lkml (yet) see:
> TODO's:
> 
> .) Patch against mainline (or whatever kernel it belongs to)
> .) Use IIO layer instead of creating random sysfs entries.
> .) Fill in GPIO device as parent to device_create_with_groups().
> .) Test it with two or more HC-SRO4 devices.
> .) Test it on other hardware than raspberry pi.
> .) Test it with kernel debugging enabled.
> 
> Anyway, comments are highly appreciated.
> 
> Signed-off-by: Johannes Thoma <johannes@johannesthoma.com>
> ---
>   MAINTAINERS            |   5 +
>   drivers/misc/Kconfig   |  11 ++
>   drivers/misc/Makefile  |   1 +
>   drivers/misc/hc-sro4.c | 360
> +++++++++++++++++++++++++++++++++++++++++++++++++

Basic comment, your email client corrupted the patch, wrapping the lines
and putting odd spaces at the front of the patch, making it impossible
to apply.  Try using 'git send-email' to fix this up, or fix up your
email client.

Also, your patch should not have the email header up there in it, that's
the sign that something went wrong.

try it again?

thanks

greg k-h

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

* [PATCH] misc devices: HC-SRO4 ultrasonic distance driver
  2016-03-24 15:02 ` [PATCH] misc devices: HC-SRO4 ultrasonic distance driver Johannes Thoma
  2016-03-24 15:24   ` Greg KH
@ 2016-03-24 15:52   ` Daniel Baluta
  2016-03-24 18:15     ` Johannes Thoma
  1 sibling, 1 reply; 16+ messages in thread
From: Daniel Baluta @ 2016-03-24 15:52 UTC (permalink / raw)
  To: kernelnewbies

On Thu, Mar 24, 2016 at 5:02 PM, Johannes Thoma
<johannes@johannesthoma.com> wrote:
>  From 56e8f71c990b92c28a8cb03d859880eab8d06a3d Mon Sep 17 00:00:00 2001
> From: Johannes Thoma <johannes@johannesthoma.com>
> Date: Mon, 21 Mar 2016 22:11:01 +0100
> Subject: [PATCH] HC-SRO4 ultrasonic distance sensor driver
>
> The HC-SRO4 is an ultrasonic distance sensor attached to two GPIO
> pins. The driver is controlled via sysfs and supports an (in theory)
> unlimited number of HC-SRO4 devices.
>
> Unlike user land solutions this driver produces precise results
> even when there is high load on the system. It uses a non-blocking
> interrupt triggered mechanism to record the length of the echo
> signal.

Add link to datasheet.
>
> This patch is against the raspberry pi kernel from
> https://github.com/raspberrypi/linux.git hash
> e481b5ceae6c94c7e60f8bb8591cbb362806246e
>
> Note that this patch isn't meant for lkml (yet) see:
> TODO's:
>
> .) Patch against mainline (or whatever kernel it belongs to)
> .) Use IIO layer instead of creating random sysfs entries.

So, why aren't you directly using IIO? :)

Also run ./scripts/checkpatch.pl --strict your.patch.

thanks,
Daniel.

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

* [PATCH] misc devices: HC-SRO4 ultrasonic distance driver
  2016-03-24 15:52   ` [PATCH] misc devices: HC-SRO4 ultrasonic distance driver Daniel Baluta
@ 2016-03-24 18:15     ` Johannes Thoma
  0 siblings, 0 replies; 16+ messages in thread
From: Johannes Thoma @ 2016-03-24 18:15 UTC (permalink / raw)
  To: kernelnewbies

Dear Greg, dear Daniel,

Thank you for your suggestions,  I will port the driver to IIO layer as
you suggested and then get back (may take some days).  I will also use 
git-send-mail, since that seems to be the best way to do it.

Am 24.03.16 um 16:52 schrieb Daniel Baluta:
> On Thu, Mar 24, 2016 at 5:02 PM, Johannes Thoma
> <johannes@johannesthoma.com> wrote:
>>   From 56e8f71c990b92c28a8cb03d859880eab8d06a3d Mon Sep 17 00:00:00 2001
>> From: Johannes Thoma <johannes@johannesthoma.com>
>> Date: Mon, 21 Mar 2016 22:11:01 +0100
>> Subject: [PATCH] HC-SRO4 ultrasonic distance sensor driver
>>
>> The HC-SRO4 is an ultrasonic distance sensor attached to two GPIO
>> pins. The driver is controlled via sysfs and supports an (in theory)
>> unlimited number of HC-SRO4 devices.
>>
>> Unlike user land solutions this driver produces precise results
>> even when there is high load on the system. It uses a non-blocking
>> interrupt triggered mechanism to record the length of the echo
>> signal.
>
> Add link to datasheet.

One location is:

http://www.micropik.com/PDF/HCSR04.pdf

>>
>> This patch is against the raspberry pi kernel from
>> https://github.com/raspberrypi/linux.git hash
>> e481b5ceae6c94c7e60f8bb8591cbb362806246e
>>
>> Note that this patch isn't meant for lkml (yet) see:
>> TODO's:
>>
>> .) Patch against mainline (or whatever kernel it belongs to)
>> .) Use IIO layer instead of creating random sysfs entries.
>
> So, why aren't you directly using IIO? :)
>
I will but I just got the hint from greg that I should do
so (didn't know about it ;).

all the best,

- Johannes

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

* [PATCH] HC-SR04 ultrasonic ranger IIO driver
  2016-03-24 15:24   ` Greg KH
@ 2016-05-31 21:05     ` johannes at johannesthoma.com
  2016-05-31 21:10       ` Johannes Thoma
                         ` (3 more replies)
  0 siblings, 4 replies; 16+ messages in thread
From: johannes at johannesthoma.com @ 2016-05-31 21:05 UTC (permalink / raw)
  To: kernelnewbies

From: Johannes Thoma <johannes@johannesthoma.com>

The HC-SR04 is an ultrasonic distance sensor attached to two GPIO
pins. The driver based on Industrial I/O (iio) subsystem and is
controlled via configfs and sysfs. It supports an (in theory) unlimited
number of HC-SR04 devices.

Datasheet to the device can be found at:

http://www.micropik.com/PDF/HCSR04.pdf
Signed-off-by: Johannes Thoma <johannes@johannesthoma.com>
---
 MAINTAINERS                               |   7 +
 drivers/iio/Kconfig                       |   1 +
 drivers/iio/Makefile                      |   1 +
 drivers/iio/ultrasonic-distance/Kconfig   |  16 ++
 drivers/iio/ultrasonic-distance/Makefile  |   6 +
 drivers/iio/ultrasonic-distance/hc-sr04.c | 460 ++++++++++++++++++++++++++++++
 6 files changed, 491 insertions(+)
 create mode 100644 drivers/iio/ultrasonic-distance/Kconfig
 create mode 100644 drivers/iio/ultrasonic-distance/Makefile
 create mode 100644 drivers/iio/ultrasonic-distance/hc-sr04.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 9c567a4..fabb338 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4988,6 +4988,13 @@ W:	http://www.kernel.org/pub/linux/kernel/people/fseidel/hdaps/
 S:	Maintained
 F:	drivers/platform/x86/hdaps.c
 
+
+HC-SR04 ULTRASONIC DISTANCE SENSOR DRIVER
+M:	Johannes Thoma <johannes@johannesthoma.com>
+S:	Maintained
+F:	drivers/iio/ultrasonic-distance/hc-sr04.c
+
+
 HDPVR USB VIDEO ENCODER DRIVER
 M:	Hans Verkuil <hverkuil@xs4all.nl>
 L:	linux-media at vger.kernel.org
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 505e921..3c82aad 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -82,5 +82,6 @@ source "drivers/iio/potentiometer/Kconfig"
 source "drivers/iio/pressure/Kconfig"
 source "drivers/iio/proximity/Kconfig"
 source "drivers/iio/temperature/Kconfig"
+source "drivers/iio/ultrasonic-distance/Kconfig"
 
 endif # IIO
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index 20f6490..0f1c00c 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -32,3 +32,4 @@ obj-y += pressure/
 obj-y += proximity/
 obj-y += temperature/
 obj-y += trigger/
+obj-y += ultrasonic-distance/
diff --git a/drivers/iio/ultrasonic-distance/Kconfig b/drivers/iio/ultrasonic-distance/Kconfig
new file mode 100644
index 0000000..9d442dd
--- /dev/null
+++ b/drivers/iio/ultrasonic-distance/Kconfig
@@ -0,0 +1,16 @@
+#
+# Ultrasonic range sensors
+#
+
+menu "Ultrasonic ranger devices"
+
+config HC_SR04
+	tristate "HC-SR04 ultrasonic distance sensor on GPIO"
+	depends on GPIOLIB && SYSFS
+	help
+	  Say Y here if you want to support the HC-SR04 ultrasonic distance
+          sensor which is attached on two runtime-configurable GPIO pins.
+
+  	  To compile this driver as a module, choose M here: the
+	  module will be called hc-sr04.
+endmenu
diff --git a/drivers/iio/ultrasonic-distance/Makefile b/drivers/iio/ultrasonic-distance/Makefile
new file mode 100644
index 0000000..1f01d50c
--- /dev/null
+++ b/drivers/iio/ultrasonic-distance/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for IIO proximity sensors
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_HC_SR04)		+= hc-sr04.o
diff --git a/drivers/iio/ultrasonic-distance/hc-sr04.c b/drivers/iio/ultrasonic-distance/hc-sr04.c
new file mode 100644
index 0000000..e5af647
--- /dev/null
+++ b/drivers/iio/ultrasonic-distance/hc-sr04.c
@@ -0,0 +1,460 @@
+/*
+ * hc-sr04.c - Support for HC-SR04 ultrasonic range sensor
+ *
+ * Copyright (C) 2016 Johannes Thoma <johannes@johannesthoma.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/* Precise measurements of time delta between sending a trigger signal
+ * to the HC-SR04 distance sensor and receiving the echo signal from
+ * the sensor back. This has to be precise in the usecs range. We
+ * use trigger interrupts to measure the signal, so no busy wait :)
+ *
+ * This supports an (in theory) unlimited number of HC-SR04 devices.
+ * It uses IIO software triggers to interface with userland.
+ *
+ * To configure a device do a
+ *
+ *    mkdir /config/iio/triggers/hc-sr04/sensor0
+ *
+ * (you need to mount configfs to /config first)
+ *
+ * Then configure the ECHO and TRIG pins (this also accepts symbolic names
+ * configured in the device tree)
+ *
+ *    echo 23 > /config/iio/triggers/hc-sr04/sensor0/trig_pin
+ *    echo 24 > /config/iio/triggers/hc-sr04/sensor0/echo_pin
+ *
+ * Then you can measure distance with:
+ *
+ *    cat /sys/devices/trigger0/measure
+ *
+ * (trigger0 is the device name as reported by
+ *  /config/iio/triggers/hc-sr04/sensor0/dev_name
+ *
+ * To convert to centimeters, multiply by 17150 and divide by 1000000 (air)
+ *
+ * DO NOT attach your HC-SR04's echo pin directly to the raspberry, since
+ * it runs with 5V while raspberry expects 3V on the GPIO inputs.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/timekeeping.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/gpio/driver.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/sw_trigger.h>
+
+#define DEFAULT_TIMEOUT 1000
+
+enum hc_sr04_state {
+	DEVICE_IDLE,
+	DEVICE_TRIGGERED,
+	DEVICE_ECHO_RECEIVED
+};
+
+struct hc_sr04 {
+		/* the GPIOs of ECHO and TRIG */
+	struct gpio_desc *trig_desc;
+	struct gpio_desc *echo_desc;
+		/* Used to measure length of ECHO signal */
+	struct timeval time_triggered;
+	struct timeval time_echoed;
+		/* protects against starting multiple measurements */
+	struct mutex measurement_mutex;
+		/* Current state of measurement */
+	enum hc_sr04_state state;
+		/* Used by interrupt to wake measurement routine up */
+	wait_queue_head_t wait_for_echo;
+		/* timeout in ms, fail when no echo received within that time */
+	unsigned long timeout;
+		/* Our IIO interface */
+	struct iio_sw_trigger swt;
+		/* Used to compute device settle time */
+	struct timeval last_measurement;
+};
+
+static inline struct hc_sr04 *to_hc_sr04(struct config_item *item)
+{
+	struct iio_sw_trigger *trig = to_iio_sw_trigger(item);
+
+	return container_of(trig, struct hc_sr04, swt);
+}
+
+static irqreturn_t echo_received_irq(int irq, void *data)
+{
+	struct hc_sr04 *device = (struct hc_sr04 *)data;
+	int val;
+	struct timeval irq_tv;
+
+	do_gettimeofday(&irq_tv);
+
+	if (device->state != DEVICE_TRIGGERED)
+		return IRQ_HANDLED;
+
+	val = gpiod_get_value(device->echo_desc);
+	if (val == 1) {
+		device->time_triggered = irq_tv;
+	} else {
+		device->time_echoed = irq_tv;
+		device->state = DEVICE_ECHO_RECEIVED;
+		wake_up_interruptible(&device->wait_for_echo);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int do_measurement(struct hc_sr04 *device,
+			  long long *usecs_elapsed)
+{
+	long timeout;
+	int irq;
+	int ret;
+	struct timeval now;
+	long long time_since_last_measurement;
+
+	*usecs_elapsed = -1;
+
+	if (!device->echo_desc || !device->trig_desc) {
+		dev_dbg(&device->swt.trigger->dev, "Please configure GPIO pins first.\n");
+		return -EINVAL;
+	}
+	if (!mutex_trylock(&device->measurement_mutex))
+		return -EBUSY;
+
+	do_gettimeofday(&now);
+	if (device->last_measurement.tv_sec || device->last_measurement.tv_usec)
+		time_since_last_measurement =
+	(now.tv_sec - device->last_measurement.tv_sec) * 1000000 +
+	(now.tv_usec - device->last_measurement.tv_usec);
+	else
+		time_since_last_measurement = 60000;
+
+		/* wait 60 ms between measurements.
+		 * now, a while true ; do cat measure ; done should work
+		 */
+
+	if (time_since_last_measurement < 60000 &&
+	    time_since_last_measurement >= 0)
+		msleep(60 - (int)time_since_last_measurement / 1000);
+
+	irq = gpiod_to_irq(device->echo_desc);
+	if (irq < 0)
+		return -EIO;
+
+	ret = request_any_context_irq(irq, echo_received_irq,
+				      IRQF_SHARED | IRQF_TRIGGER_FALLING |
+				      IRQF_TRIGGER_RISING,
+				      "hc_sr04", device);
+
+	if (ret < 0)
+		goto out_mutex;
+
+	gpiod_set_value(device->trig_desc, 1);
+	usleep_range(10, 20);
+	device->state = DEVICE_TRIGGERED;
+	gpiod_set_value(device->trig_desc, 0);
+
+	ret = gpiochip_lock_as_irq(gpiod_to_chip(device->echo_desc),
+				   desc_to_gpio(device->echo_desc));
+	if (ret < 0)
+		goto out_irq;
+
+	timeout = wait_event_interruptible_timeout(
+			device->wait_for_echo,
+			device->state == DEVICE_ECHO_RECEIVED,
+			device->timeout * HZ / 1000);
+
+	device->state = DEVICE_IDLE;
+
+	if (timeout == 0) {
+		ret = -ETIMEDOUT;
+	} else if (timeout < 0) {
+		ret = timeout;
+	} else {
+		*usecs_elapsed =
+	(device->time_echoed.tv_sec - device->time_triggered.tv_sec) * 1000000 +
+	(device->time_echoed.tv_usec - device->time_triggered.tv_usec);
+		ret = 0;
+		do_gettimeofday(&device->last_measurement);
+	}
+	gpiochip_unlock_as_irq(gpiod_to_chip(device->echo_desc),
+			       desc_to_gpio(device->echo_desc));
+out_irq:
+	free_irq(irq, device);
+out_mutex:
+	mutex_unlock(&device->measurement_mutex);
+
+	return ret;
+}
+
+static ssize_t sysfs_do_measurement(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	struct hc_sr04 *sensor = dev_get_drvdata(dev);
+	long long usecs_elapsed;
+	int status;
+
+	status = do_measurement(sensor, &usecs_elapsed);
+
+	if (status < 0)
+		return status;
+
+	return sprintf(buf, "%lld\n", usecs_elapsed);
+}
+
+DEVICE_ATTR(measure, 0444, sysfs_do_measurement, NULL);
+
+static struct attribute *sensor_attrs[] = {
+	&dev_attr_measure.attr,
+	NULL,
+};
+
+static const struct attribute_group sensor_group = {
+	.attrs = sensor_attrs
+};
+
+static const struct attribute_group *sensor_groups[] = {
+	&sensor_group,
+	NULL
+};
+
+static ssize_t configure_pin(struct gpio_desc **desc, struct config_item *item,
+			     const char *buf, size_t len, struct device *dev)
+{
+	int err;
+	int echo;
+
+	if (*desc)
+		gpiod_put(*desc);
+
+	*desc = gpiod_get(dev, buf, GPIOD_ASIS);
+	if (IS_ERR(*desc)) {
+		err = PTR_ERR(*desc);
+		*desc = NULL;
+
+		if (err == -ENOENT) {	/* fallback: use GPIO numbers */
+			err = kstrtoint(buf, 10, &echo);
+			if (err < 0)
+				return -ENOENT;
+			*desc = gpio_to_desc(echo);
+			if (*desc)
+				return len;
+			return -ENOENT;
+		}
+
+		return err;
+	}
+	return len;
+}
+
+static ssize_t hc_sr04_echo_pin_store(struct config_item *item,
+				      const char *buf, size_t len)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+	ssize_t ret;
+	int err;
+
+	ret = configure_pin(&sensor->echo_desc, item, buf, len,
+			    &sensor->swt.trigger->dev);
+
+	if (ret >= 0 && sensor->echo_desc) {
+		err = gpiod_direction_input(sensor->echo_desc);
+		if (err < 0)
+			return err;
+	}
+	return ret;
+}
+
+static ssize_t hc_sr04_echo_pin_show(struct config_item *item,
+				     char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	if (sensor->echo_desc)
+		return sprintf(buf, "%d\n", desc_to_gpio(sensor->echo_desc));
+	return 0;
+}
+
+static ssize_t hc_sr04_trig_pin_store(struct config_item *item,
+				      const char *buf, size_t len)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+	ssize_t ret;
+	int err;
+
+	ret = configure_pin(&sensor->trig_desc, item, buf, len,
+			    &sensor->swt.trigger->dev);
+
+	if (ret >= 0 && sensor->trig_desc) {
+		err = gpiod_direction_output(sensor->trig_desc, 0);
+		if (err >= 0)
+			gpiod_set_value(sensor->trig_desc, 0);
+		else
+			return err;
+	}
+	return ret;
+}
+
+static ssize_t hc_sr04_trig_pin_show(struct config_item *item,
+				     char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	if (sensor->trig_desc)
+		return sprintf(buf, "%d\n", desc_to_gpio(sensor->trig_desc));
+	return 0;
+}
+
+static ssize_t hc_sr04_timeout_store(struct config_item *item,
+				     const char *buf, size_t len)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+	unsigned long t;
+	int ret;
+
+	ret = kstrtol(buf, 10, &t);
+	if (ret < 0)
+		return ret;
+
+	sensor->timeout = t;
+	return len;
+}
+
+static ssize_t hc_sr04_timeout_show(struct config_item *item,
+				    char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	return sprintf(buf, "%ld\n", sensor->timeout);
+}
+
+static ssize_t hc_sr04_dev_name_show(struct config_item *item,
+				     char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	return sprintf(buf, "%s", dev_name(&sensor->swt.trigger->dev));
+}
+
+CONFIGFS_ATTR(hc_sr04_, echo_pin);
+CONFIGFS_ATTR(hc_sr04_, trig_pin);
+CONFIGFS_ATTR(hc_sr04_, timeout);
+CONFIGFS_ATTR_RO(hc_sr04_, dev_name);
+
+static struct configfs_attribute *hc_sr04_config_attrs[] = {
+	&hc_sr04_attr_echo_pin,
+	&hc_sr04_attr_trig_pin,
+	&hc_sr04_attr_timeout,
+	&hc_sr04_attr_dev_name,
+	NULL
+};
+
+static struct config_item_type iio_hc_sr04_type = {
+	.ct_owner = THIS_MODULE,
+	.ct_attrs = hc_sr04_config_attrs
+};
+
+static int iio_trig_hc_sr04_set_state(struct iio_trigger *trig, bool state)
+{
+	return 0;
+}
+
+static const struct iio_trigger_ops iio_hc_sr04_trigger_ops = {
+	.owner = THIS_MODULE,
+	.set_trigger_state = iio_trig_hc_sr04_set_state,
+};
+
+static struct iio_sw_trigger *iio_trig_hc_sr04_probe(const char *name)
+{
+	struct hc_sr04 *sensor;
+	int ret;
+
+	sensor = kzalloc(sizeof(*sensor), GFP_KERNEL);
+	if (!sensor)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&sensor->measurement_mutex);
+	init_waitqueue_head(&sensor->wait_for_echo);
+	sensor->timeout = DEFAULT_TIMEOUT;
+
+	sensor->swt.trigger = iio_trigger_alloc("%s", name);
+	if (!sensor->swt.trigger) {
+		ret = -ENOMEM;
+		goto err_free_sensor;
+	}
+	iio_trigger_set_drvdata(sensor->swt.trigger, sensor);
+	sensor->swt.trigger->ops = &iio_hc_sr04_trigger_ops;
+	sensor->swt.trigger->dev.groups = sensor_groups;
+
+	ret = iio_trigger_register(sensor->swt.trigger);
+	if (ret)
+		goto err_free_trigger;
+
+	iio_swt_group_init_type_name(&sensor->swt, name, &iio_hc_sr04_type);
+	return &sensor->swt;
+
+err_free_trigger:
+	iio_trigger_free(sensor->swt.trigger);
+err_free_sensor:
+	kfree(sensor);
+
+	return ERR_PTR(ret);
+}
+
+static int iio_trig_hc_sr04_remove(struct iio_sw_trigger *swt)
+{
+	struct hc_sr04 *rip_sensor;
+
+	rip_sensor = iio_trigger_get_drvdata(swt->trigger);
+
+	iio_trigger_unregister(swt->trigger);
+
+	/* Wait for measurement to be finished. */
+	mutex_lock(&rip_sensor->measurement_mutex);
+
+	iio_trigger_free(swt->trigger);
+	kfree(rip_sensor);
+
+	return 0;
+}
+
+static const struct iio_sw_trigger_ops iio_trig_hc_sr04_ops = {
+	.probe          = iio_trig_hc_sr04_probe,
+	.remove         = iio_trig_hc_sr04_remove,
+};
+
+static struct iio_sw_trigger_type iio_trig_hc_sr04 = {
+	.name = "hc-sr04",
+	.owner = THIS_MODULE,
+	.ops = &iio_trig_hc_sr04_ops,
+};
+
+module_iio_sw_trigger_driver(iio_trig_hc_sr04);
+
+MODULE_AUTHOR("Johannes Thoma");
+MODULE_DESCRIPTION("Distance measurement for the HC-SR04 ultrasonic distance sensor");
+MODULE_LICENSE("GPL");
+
-- 
2.8.0-rc4

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

* [PATCH] HC-SR04 ultrasonic ranger IIO driver
  2016-05-31 21:05     ` [PATCH] HC-SR04 ultrasonic ranger IIO driver johannes at johannesthoma.com
@ 2016-05-31 21:10       ` Johannes Thoma
  2016-05-31 21:35       ` Greg KH
                         ` (2 subsequent siblings)
  3 siblings, 0 replies; 16+ messages in thread
From: Johannes Thoma @ 2016-05-31 21:10 UTC (permalink / raw)
  To: kernelnewbies

Dear List,

I've ported my hc-sr04 driver to IIO now and wanted to ask if it is ok 
to post it to the
main kernel list (or some other list?) like this (see original mail, 
sent with git send-mail).

Thanks a lot,

- Johannes

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

* [PATCH] HC-SR04 ultrasonic ranger IIO driver
  2016-05-31 21:05     ` [PATCH] HC-SR04 ultrasonic ranger IIO driver johannes at johannesthoma.com
  2016-05-31 21:10       ` Johannes Thoma
@ 2016-05-31 21:35       ` Greg KH
  2016-06-01  1:02       ` Valdis.Kletnieks at vt.edu
  2016-06-01 14:56       ` Daniel Baluta
  3 siblings, 0 replies; 16+ messages in thread
From: Greg KH @ 2016-05-31 21:35 UTC (permalink / raw)
  To: kernelnewbies

On Tue, May 31, 2016 at 11:05:57PM +0200, johannes at johannesthoma.com wrote:
> From: Johannes Thoma <johannes@johannesthoma.com>
> 
> The HC-SR04 is an ultrasonic distance sensor attached to two GPIO
> pins. The driver based on Industrial I/O (iio) subsystem and is
> controlled via configfs and sysfs. It supports an (in theory) unlimited
> number of HC-SR04 devices.
> 
> Datasheet to the device can be found at:
> 
> http://www.micropik.com/PDF/HCSR04.pdf
> Signed-off-by: Johannes Thoma <johannes@johannesthoma.com>
> ---
>  MAINTAINERS                               |   7 +
>  drivers/iio/Kconfig                       |   1 +
>  drivers/iio/Makefile                      |   1 +
>  drivers/iio/ultrasonic-distance/Kconfig   |  16 ++
>  drivers/iio/ultrasonic-distance/Makefile  |   6 +
>  drivers/iio/ultrasonic-distance/hc-sr04.c | 460 ++++++++++++++++++++++++++++++
>  6 files changed, 491 insertions(+)
>  create mode 100644 drivers/iio/ultrasonic-distance/Kconfig
>  create mode 100644 drivers/iio/ultrasonic-distance/Makefile
>  create mode 100644 drivers/iio/ultrasonic-distance/hc-sr04.c

Hint, use scripts/get_maintainer.pl to find the correct mailing list and
developers to send this patch to, in order to ensure the correct people
who can apply it, receive it.

thanks,

greg k-h

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

* [PATCH] HC-SR04 ultrasonic ranger IIO driver
  2016-05-31 21:05     ` [PATCH] HC-SR04 ultrasonic ranger IIO driver johannes at johannesthoma.com
  2016-05-31 21:10       ` Johannes Thoma
  2016-05-31 21:35       ` Greg KH
@ 2016-06-01  1:02       ` Valdis.Kletnieks at vt.edu
  2016-06-01 14:56       ` Daniel Baluta
  3 siblings, 0 replies; 16+ messages in thread
From: Valdis.Kletnieks at vt.edu @ 2016-06-01  1:02 UTC (permalink / raw)
  To: kernelnewbies

On Tue, 31 May 2016 23:05:57 +0200, johannes at johannesthoma.com said:

Looks good overall, far from the ugliest driver I've seen.  I spotted one
locking bug, and a few small typos etc, noted inline...

> From: Johannes Thoma <johannes@johannesthoma.com>
>
> The HC-SR04 is an ultrasonic distance sensor attached to two GPIO
> pins. The driver based on Industrial I/O (iio) subsystem and is

        The driver is based on the Industrial...


> + * To configure a device do a
> + *
> + *    mkdir /config/iio/triggers/hc-sr04/sensor0
> + *
> + * (you need to mount configfs to /config first)

Most distros seem to be using /sys/kernel/config as the mount point for this...


> + * Then you can measure distance with:
> + *
> + *    cat /sys/devices/trigger0/measure

What are the units of the returned value? Inches? Hundredths of an inch?
inches.hundredths?   Other?

(Yes, I looked at the datasheet.. and your driver source is more helpful
than the sheed :)


> +	struct gpio_desc *echo_desc;
> +		/* Used to measure length of ECHO signal */

I was going to say "comments on same line", but that would result in *long*
lines, this is better....


> +static int do_measurement(struct hc_sr04 *device,
> +			  long long *usecs_elapsed)
> +{
(...)
> +	if (!mutex_trylock(&device->measurement_mutex))
> +		return -EBUSY;

OK... this is a potential problem, because...

> +	irq = gpiod_to_irq(device->echo_desc);
> +	if (irq < 0)
> +		return -EIO;

Here you do a 'return' without unlocking.  This should probably be:

         if (irq < 0) {
		ret = -EIO;
		goto out_mutex;
	}

I admit not knowing the GPIO or IIO stuff well enough to comment on those
details, but I didn't see anything obviously insane either....

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

* [PATCH] HC-SR04 ultrasonic ranger IIO driver
  2016-05-31 21:05     ` [PATCH] HC-SR04 ultrasonic ranger IIO driver johannes at johannesthoma.com
                         ` (2 preceding siblings ...)
  2016-06-01  1:02       ` Valdis.Kletnieks at vt.edu
@ 2016-06-01 14:56       ` Daniel Baluta
  3 siblings, 0 replies; 16+ messages in thread
From: Daniel Baluta @ 2016-06-01 14:56 UTC (permalink / raw)
  To: kernelnewbies

On Wed, Jun 1, 2016 at 12:05 AM,  <johannes@johannesthoma.com> wrote:
> From: Johannes Thoma <johannes@johannesthoma.com>
>
> The HC-SR04 is an ultrasonic distance sensor attached to two GPIO
> pins. The driver based on Industrial I/O (iio) subsystem and is
> controlled via configfs and sysfs. It supports an (in theory) unlimited
> number of HC-SR04 devices.
>
> Datasheet to the device can be found at:
>
> http://www.micropik.com/PDF/HCSR04.pdf
> Signed-off-by: Johannes Thoma <johannes@johannesthoma.com>

Please send this to the linux-iio mailing list (linux-iio at vger.kernel.org )

thanks,
Daniel.

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

* Re: [PATCH] HC-SR04 ultrasonic ranger IIO driver
  2016-06-06 18:40 [PATCH] HC-SR04 ultrasonic ranger IIO driver johannes
                   ` (2 preceding siblings ...)
  2016-06-07 15:19 ` Lars-Peter Clausen
@ 2016-06-11 15:20 ` Jonathan Cameron
  3 siblings, 0 replies; 16+ messages in thread
From: Jonathan Cameron @ 2016-06-11 15:20 UTC (permalink / raw)
  To: johannes, linux-iio

On 06/06/16 19:40, johannes@johannesthoma.com wrote:
> From: Johannes Thoma <johannes@johannesthoma.com>
> 
> The HC-SR04 is an ultrasonic distance sensor attached to two GPIO
> pins. The driver is based on the Industrial I/O (iio) subsystem and is
> controlled via configfs and sysfs. It supports an (in theory) unlimited
> number of HC-SR04 devices.
> 
> A datasheet to the device can be found at:
> 
> http://www.micropik.com/PDF/HCSR04.pdf
> Signed-off-by: Johannes Thoma <johannes@johannesthoma.com>
Why is this device registered as a trigger?  It looks like a device
to me in IIO terms.

The measure sysfs attribute is effectively a distance channel I think...

> ---
>  MAINTAINERS                               |   7 +
>  drivers/iio/Kconfig                       |   1 +
>  drivers/iio/Makefile                      |   1 +
>  drivers/iio/ultrasonic-distance/Kconfig   |  17 ++
>  drivers/iio/ultrasonic-distance/Makefile  |   6 +
>  drivers/iio/ultrasonic-distance/hc-sr04.c | 466 ++++++++++++++++++++++++++++++
>  6 files changed, 498 insertions(+)
>  create mode 100644 drivers/iio/ultrasonic-distance/Kconfig
>  create mode 100644 drivers/iio/ultrasonic-distance/Makefile
>  create mode 100644 drivers/iio/ultrasonic-distance/hc-sr04.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 7304d2e..3bd640e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5211,6 +5211,13 @@ W:	http://www.kernel.org/pub/linux/kernel/people/fseidel/hdaps/
>  S:	Maintained
>  F:	drivers/platform/x86/hdaps.c
>  
> +
> +HC-SR04 ULTRASONIC DISTANCE SENSOR DRIVER
> +M:	Johannes Thoma <johannes@johannesthoma.com>
> +S:	Maintained
> +F:	drivers/iio/ultrasonic-distance/hc-sr04.c
> +
> +
>  HDPVR USB VIDEO ENCODER DRIVER
>  M:	Hans Verkuil <hverkuil@xs4all.nl>
>  L:	linux-media@vger.kernel.org
> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> index 505e921..3c82aad 100644
> --- a/drivers/iio/Kconfig
> +++ b/drivers/iio/Kconfig
> @@ -82,5 +82,6 @@ source "drivers/iio/potentiometer/Kconfig"
>  source "drivers/iio/pressure/Kconfig"
>  source "drivers/iio/proximity/Kconfig"
>  source "drivers/iio/temperature/Kconfig"
> +source "drivers/iio/ultrasonic-distance/Kconfig"
>  
>  endif # IIO
> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> index 20f6490..0f1c00c 100644
> --- a/drivers/iio/Makefile
> +++ b/drivers/iio/Makefile
> @@ -32,3 +32,4 @@ obj-y += pressure/
>  obj-y += proximity/
>  obj-y += temperature/
>  obj-y += trigger/
> +obj-y += ultrasonic-distance/
> diff --git a/drivers/iio/ultrasonic-distance/Kconfig b/drivers/iio/ultrasonic-distance/Kconfig
> new file mode 100644
> index 0000000..46e848d
> --- /dev/null
> +++ b/drivers/iio/ultrasonic-distance/Kconfig
> @@ -0,0 +1,17 @@
> +#
> +# Ultrasonic range sensors
> +#
> +
> +menu "Ultrasonic ranger devices"
> +
> +config HC_SR04
> +	tristate "HC-SR04 ultrasonic distance sensor on GPIO"
> +	select IIO_SW_TRIGGER
> +	depends on GPIOLIB
> +	help
> +	  Say Y here if you want to support the HC-SR04 ultrasonic distance
> +	  sensor which is attached on two runtime-configurable GPIO pins.
> +
> +  	  To compile this driver as a module, choose M here: the
> +	  module will be called hc-sr04.
> +endmenu
> diff --git a/drivers/iio/ultrasonic-distance/Makefile b/drivers/iio/ultrasonic-distance/Makefile
> new file mode 100644
> index 0000000..1f01d50c
> --- /dev/null
> +++ b/drivers/iio/ultrasonic-distance/Makefile
> @@ -0,0 +1,6 @@
> +#
> +# Makefile for IIO proximity sensors
> +#
> +
> +# When adding new entries keep the list in alphabetical order
> +obj-$(CONFIG_HC_SR04)		+= hc-sr04.o
> diff --git a/drivers/iio/ultrasonic-distance/hc-sr04.c b/drivers/iio/ultrasonic-distance/hc-sr04.c
> new file mode 100644
> index 0000000..e7da37b
> --- /dev/null
> +++ b/drivers/iio/ultrasonic-distance/hc-sr04.c
> @@ -0,0 +1,466 @@
> +/*
> + * hc-sr04.c - Support for HC-SR04 ultrasonic range sensor
> + *
> + * Copyright (C) 2016 Johannes Thoma <johannes@johannesthoma.com>
> + *
> + * 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; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +/* Precise measurements of time delta between sending a trigger signal
> + * to the HC-SR04 distance sensor and receiving the echo signal from
> + * the sensor back. This has to be precise in the usecs range. We
> + * use trigger interrupts to measure the signal, so no busy wait :)
> + *
> + * This supports an (in theory) unlimited number of HC-SR04 devices.
> + * It uses IIO software triggers to interface with userland.
> + *
> + * To configure a device do a
> + *
> + *    mkdir /sys/kernel/config/iio/triggers/hc-sr04/sensor0
> + *
> + * (you need to mount configfs to /sys/kernel/config first unless it isn't
> + * mounted already)
> + *
> + * Then configure the ECHO and TRIG pins (this also accepts symbolic names
> + * configured in the device tree)
> + *
> + *    echo 23 > /config/iio/triggers/hc-sr04/sensor0/trig_pin
> + *    echo 24 > /config/iio/triggers/hc-sr04/sensor0/echo_pin
> + *
> + * Then you can measure distance with:
> + *
> + *    cat /sys/devices/trigger0/measure
> + *
> + * (trigger0 is the device name as reported by
> + *  /config/iio/triggers/hc-sr04/sensor0/dev_name
> + *
> + * This reports the length of the ECHO signal in microseconds, which is
> + * related linearily to the distance measured.
> + *
> + * To convert to centimeters, multiply by 17150 and divide by 1000000 (air)
> + *
> + * DO NOT attach your HC-SR04's echo pin directly to the raspberry, since
> + * it runs with 5V while raspberry expects 3V on the GPIO inputs.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/timekeeping.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/mutex.h>
> +#include <linux/device.h>
> +#include <linux/sysfs.h>
> +#include <linux/interrupt.h>
> +#include <linux/kdev_t.h>
> +#include <linux/list.h>
> +#include <linux/slab.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/delay.h>
> +#include <linux/sched.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/sw_trigger.h>
> +
> +#define DEFAULT_TIMEOUT 1000
> +
> +enum hc_sr04_state {
> +	DEVICE_IDLE,
> +	DEVICE_TRIGGERED,
> +	DEVICE_ECHO_RECEIVED
> +};
> +
> +struct hc_sr04 {
> +		/* the GPIOs of ECHO and TRIG */
> +	struct gpio_desc *trig_desc;
> +	struct gpio_desc *echo_desc;
> +		/* Used to measure length of ECHO signal */
> +	struct timeval time_triggered;
> +	struct timeval time_echoed;
> +		/* protects against starting multiple measurements */
> +	struct mutex measurement_mutex;
> +		/* Current state of measurement */
> +	enum hc_sr04_state state;
> +		/* Used by interrupt to wake measurement routine up */
> +	wait_queue_head_t wait_for_echo;
> +		/* timeout in ms, fail when no echo received within that time */
> +	unsigned long timeout;
> +		/* Our IIO interface */
> +	struct iio_sw_trigger swt;
> +		/* Used to compute device settle time */
> +	struct timeval last_measurement;
> +};
> +
> +static inline struct hc_sr04 *to_hc_sr04(struct config_item *item)
> +{
> +	struct iio_sw_trigger *trig = to_iio_sw_trigger(item);
> +
> +	return container_of(trig, struct hc_sr04, swt);
> +}
> +
> +static irqreturn_t echo_received_irq(int irq, void *data)
> +{
> +	struct hc_sr04 *device = (struct hc_sr04 *)data;
> +	int val;
> +	struct timeval irq_tv;
> +
> +	do_gettimeofday(&irq_tv);
> +
> +	if (device->state != DEVICE_TRIGGERED)
> +		return IRQ_HANDLED;
> +
> +	val = gpiod_get_value(device->echo_desc);
> +	if (val == 1) {
> +		device->time_triggered = irq_tv;
> +	} else {
> +		device->time_echoed = irq_tv;
> +		device->state = DEVICE_ECHO_RECEIVED;
> +		wake_up_interruptible(&device->wait_for_echo);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int do_measurement(struct hc_sr04 *device,
> +			  long long *usecs_elapsed)
> +{
> +	long timeout;
> +	int irq;
> +	int ret;
> +	struct timeval now;
> +	long long time_since_last_measurement;
> +
> +	*usecs_elapsed = -1;
> +
> +	if (!device->echo_desc || !device->trig_desc) {
> +		dev_dbg(&device->swt.trigger->dev, "Please configure GPIO pins first.\n");
> +		return -EINVAL;
> +	}
> +	if (!mutex_trylock(&device->measurement_mutex))
> +		return -EBUSY;
> +
> +	do_gettimeofday(&now);
> +	if (device->last_measurement.tv_sec || device->last_measurement.tv_usec)
> +		time_since_last_measurement =
> +	(now.tv_sec - device->last_measurement.tv_sec) * 1000000 +
> +	(now.tv_usec - device->last_measurement.tv_usec);
> +	else
> +		time_since_last_measurement = 60000;
> +
> +		/* wait 60 ms between measurements.
> +		 * now, a while true ; do cat measure ; done should work
> +		 */
> +
> +	if (time_since_last_measurement < 60000 &&
> +	    time_since_last_measurement >= 0)
> +		msleep(60 - (int)time_since_last_measurement / 1000);
> +
> +	irq = gpiod_to_irq(device->echo_desc);
> +	if (irq < 0) {
> +		ret = -EIO;
> +		goto out_mutex;
> +	}
> +
> +	ret = request_any_context_irq(irq, echo_received_irq,
> +				      IRQF_SHARED | IRQF_TRIGGER_FALLING |
> +				      IRQF_TRIGGER_RISING,
> +				      "hc_sr04", device);
> +
> +	if (ret < 0)
> +		goto out_mutex;
> +
> +	gpiod_set_value(device->trig_desc, 1);
> +	usleep_range(10, 20);
> +	device->state = DEVICE_TRIGGERED;
> +	gpiod_set_value(device->trig_desc, 0);
> +
> +	ret = gpiochip_lock_as_irq(gpiod_to_chip(device->echo_desc),
> +				   desc_to_gpio(device->echo_desc));
> +	if (ret < 0)
> +		goto out_irq;
> +
> +	timeout = wait_event_interruptible_timeout(
> +			device->wait_for_echo,
> +			device->state == DEVICE_ECHO_RECEIVED,
> +			device->timeout * HZ / 1000);
> +
> +	device->state = DEVICE_IDLE;
> +
> +	if (timeout == 0) {
> +		ret = -ETIMEDOUT;
> +	} else if (timeout < 0) {
> +		ret = timeout;
> +	} else {
> +		*usecs_elapsed =
> +	(device->time_echoed.tv_sec - device->time_triggered.tv_sec) * 1000000 +
> +	(device->time_echoed.tv_usec - device->time_triggered.tv_usec);
> +		ret = 0;
> +		do_gettimeofday(&device->last_measurement);
> +	}
> +	gpiochip_unlock_as_irq(gpiod_to_chip(device->echo_desc),
> +			       desc_to_gpio(device->echo_desc));
> +out_irq:
> +	free_irq(irq, device);
> +out_mutex:
> +	mutex_unlock(&device->measurement_mutex);
> +
> +	return ret;
> +}
> +
> +static ssize_t sysfs_do_measurement(struct device *dev,
> +				    struct device_attribute *attr,
> +				    char *buf)
> +{
> +	struct hc_sr04 *sensor = dev_get_drvdata(dev);
> +	long long usecs_elapsed;
> +	int status;
> +
> +	status = do_measurement(sensor, &usecs_elapsed);
> +
> +	if (status < 0)
> +		return status;
> +
> +	return sprintf(buf, "%lld\n", usecs_elapsed);
> +}
> +
> +DEVICE_ATTR(measure, 0444, sysfs_do_measurement, NULL);
> +
> +static struct attribute *sensor_attrs[] = {
> +	&dev_attr_measure.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group sensor_group = {
> +	.attrs = sensor_attrs
> +};
> +
> +static const struct attribute_group *sensor_groups[] = {
> +	&sensor_group,
> +	NULL
> +};
> +
> +static ssize_t configure_pin(struct gpio_desc **desc, struct config_item *item,
> +			     const char *buf, size_t len, struct device *dev)
> +{
> +	int err;
> +	int echo;
> +
> +	if (*desc)
> +		gpiod_put(*desc);
> +
> +	*desc = gpiod_get(dev, buf, GPIOD_ASIS);
> +	if (IS_ERR(*desc)) {
> +		err = PTR_ERR(*desc);
> +		*desc = NULL;
> +
> +		if (err == -ENOENT) {	/* fallback: use GPIO numbers */
> +			err = kstrtoint(buf, 10, &echo);
> +			if (err < 0)
> +				return -ENOENT;
> +			*desc = gpio_to_desc(echo);
> +			if (*desc)
> +				return len;
> +			return -ENOENT;
> +		}
> +
> +		return err;
> +	}
> +	return len;
> +}
> +
> +static ssize_t hc_sr04_echo_pin_store(struct config_item *item,
> +				      const char *buf, size_t len)
> +{
> +	struct hc_sr04 *sensor = to_hc_sr04(item);
> +	ssize_t ret;
> +	int err;
> +
> +	ret = configure_pin(&sensor->echo_desc, item, buf, len,
> +			    &sensor->swt.trigger->dev);
> +
> +	if (ret >= 0 && sensor->echo_desc) {
> +		err = gpiod_direction_input(sensor->echo_desc);
> +		if (err < 0)
> +			return err;
> +	}
> +	return ret;
> +}
> +
> +static ssize_t hc_sr04_echo_pin_show(struct config_item *item,
> +				     char *buf)
> +{
> +	struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +	if (sensor->echo_desc)
> +		return sprintf(buf, "%d\n", desc_to_gpio(sensor->echo_desc));
> +	return 0;
> +}
> +
> +static ssize_t hc_sr04_trig_pin_store(struct config_item *item,
> +				      const char *buf, size_t len)
> +{
> +	struct hc_sr04 *sensor = to_hc_sr04(item);
> +	ssize_t ret;
> +	int err;
> +
> +	ret = configure_pin(&sensor->trig_desc, item, buf, len,
> +			    &sensor->swt.trigger->dev);
> +
> +	if (ret >= 0 && sensor->trig_desc) {
> +		err = gpiod_direction_output(sensor->trig_desc, 0);
> +		if (err >= 0)
> +			gpiod_set_value(sensor->trig_desc, 0);
> +		else
> +			return err;
> +	}
> +	return ret;
> +}
> +
> +static ssize_t hc_sr04_trig_pin_show(struct config_item *item,
> +				     char *buf)
> +{
> +	struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +	if (sensor->trig_desc)
> +		return sprintf(buf, "%d\n", desc_to_gpio(sensor->trig_desc));
> +	return 0;
> +}
> +
> +static ssize_t hc_sr04_timeout_store(struct config_item *item,
> +				     const char *buf, size_t len)
> +{
> +	struct hc_sr04 *sensor = to_hc_sr04(item);
> +	unsigned long t;
> +	int ret;
> +
> +	ret = kstrtol(buf, 10, &t);
> +	if (ret < 0)
> +		return ret;
> +
> +	sensor->timeout = t;
> +	return len;
> +}
> +
> +static ssize_t hc_sr04_timeout_show(struct config_item *item,
> +				    char *buf)
> +{
> +	struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +	return sprintf(buf, "%ld\n", sensor->timeout);
> +}
> +
> +static ssize_t hc_sr04_dev_name_show(struct config_item *item,
> +				     char *buf)
> +{
> +	struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +	return sprintf(buf, "%s", dev_name(&sensor->swt.trigger->dev));
> +}
> +
> +CONFIGFS_ATTR(hc_sr04_, echo_pin);
> +CONFIGFS_ATTR(hc_sr04_, trig_pin);
> +CONFIGFS_ATTR(hc_sr04_, timeout);
> +CONFIGFS_ATTR_RO(hc_sr04_, dev_name);
> +
> +static struct configfs_attribute *hc_sr04_config_attrs[] = {
> +	&hc_sr04_attr_echo_pin,
> +	&hc_sr04_attr_trig_pin,
> +	&hc_sr04_attr_timeout,
> +	&hc_sr04_attr_dev_name,
> +	NULL
> +};
> +
> +static struct config_item_type iio_hc_sr04_type = {
> +	.ct_owner = THIS_MODULE,
> +	.ct_attrs = hc_sr04_config_attrs
> +};
> +
> +static int iio_trig_hc_sr04_set_state(struct iio_trigger *trig, bool state)
> +{
> +	return 0;
> +}
> +
> +static const struct iio_trigger_ops iio_hc_sr04_trigger_ops = {
> +	.owner = THIS_MODULE,
> +	.set_trigger_state = iio_trig_hc_sr04_set_state,
> +};
> +
> +static struct iio_sw_trigger *iio_trig_hc_sr04_probe(const char *name)
> +{
> +	struct hc_sr04 *sensor;
> +	int ret;
> +
> +	sensor = kzalloc(sizeof(*sensor), GFP_KERNEL);
> +	if (!sensor)
> +		return ERR_PTR(-ENOMEM);
> +
> +	mutex_init(&sensor->measurement_mutex);
> +	init_waitqueue_head(&sensor->wait_for_echo);
> +	sensor->timeout = DEFAULT_TIMEOUT;
> +
> +	sensor->swt.trigger = iio_trigger_alloc("%s", name);
> +	if (!sensor->swt.trigger) {
> +		ret = -ENOMEM;
> +		goto err_free_sensor;
> +	}
> +	iio_trigger_set_drvdata(sensor->swt.trigger, sensor);
> +	sensor->swt.trigger->ops = &iio_hc_sr04_trigger_ops;
> +	sensor->swt.trigger->dev.groups = sensor_groups;
> +
> +	ret = iio_trigger_register(sensor->swt.trigger);
> +	if (ret)
> +		goto err_free_trigger;
> +
> +	iio_swt_group_init_type_name(&sensor->swt, name, &iio_hc_sr04_type);
> +	return &sensor->swt;
> +
> +err_free_trigger:
> +	iio_trigger_free(sensor->swt.trigger);
> +err_free_sensor:
> +	kfree(sensor);
> +
> +	return ERR_PTR(ret);
> +}
> +
> +static int iio_trig_hc_sr04_remove(struct iio_sw_trigger *swt)
> +{
> +	struct hc_sr04 *rip_sensor;
> +
> +	rip_sensor = iio_trigger_get_drvdata(swt->trigger);
> +
> +	iio_trigger_unregister(swt->trigger);
> +
> +	/* Wait for measurement to be finished. */
> +	mutex_lock(&rip_sensor->measurement_mutex);
> +
> +	iio_trigger_free(swt->trigger);
> +	kfree(rip_sensor);
> +
> +	return 0;
> +}
> +
> +static const struct iio_sw_trigger_ops iio_trig_hc_sr04_ops = {
> +	.probe          = iio_trig_hc_sr04_probe,
> +	.remove         = iio_trig_hc_sr04_remove,
> +};
> +
> +static struct iio_sw_trigger_type iio_trig_hc_sr04 = {
> +	.name = "hc-sr04",
> +	.owner = THIS_MODULE,
> +	.ops = &iio_trig_hc_sr04_ops,
> +};
> +
> +module_iio_sw_trigger_driver(iio_trig_hc_sr04);
> +
> +MODULE_AUTHOR("Johannes Thoma");
> +MODULE_DESCRIPTION("Distance measurement for the HC-SR04 ultrasonic distance sensor");
> +MODULE_LICENSE("GPL");
> +
> 


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

* Re: [PATCH] HC-SR04 ultrasonic ranger IIO driver
  2016-06-10  5:35   ` Johannes Thoma
@ 2016-06-10  7:50     ` Lars-Peter Clausen
  0 siblings, 0 replies; 16+ messages in thread
From: Lars-Peter Clausen @ 2016-06-10  7:50 UTC (permalink / raw)
  To: Johannes Thoma, linux-iio

On 06/10/2016 07:35 AM, Johannes Thoma wrote:
> Hi,
> 
> Am 07.06.16 um 17:19 schrieb Lars-Peter Clausen:
>>
>> Any new ABI needs to be documented in Documentation/ABI/...
>>
> I will move it there thanks for pointing that out.
> 
>> But to take one thing ahead passing the GPIO number (or a label) from
>> userspace is not really a good ABI in my opinion. The GPIO numbers should
>> come from the hardware description (DTS, ACPI, board file, ...)
>>
> I wanted to offer the possibility to configure the device at run time. Since
> it is not part of the board but a periphial device it could also be attached
> while the system is running. I would like to keep this interface but will
> add support for also configue it via device tree, as you suggested.

The right interface for handling re-configurable logic is devicetree
overlays. It allows you to describe the hardware using standard devicetree
descriptions but it can be loaded and unloaded on-demand at runtime.


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

* Re: [PATCH] HC-SR04 ultrasonic ranger IIO driver
  2016-06-07 15:19 ` Lars-Peter Clausen
@ 2016-06-10  5:35   ` Johannes Thoma
  2016-06-10  7:50     ` Lars-Peter Clausen
  0 siblings, 1 reply; 16+ messages in thread
From: Johannes Thoma @ 2016-06-10  5:35 UTC (permalink / raw)
  To: Lars-Peter Clausen, linux-iio

Hi,

Am 07.06.16 um 17:19 schrieb Lars-Peter Clausen:
>
> Any new ABI needs to be documented in Documentation/ABI/...
>
I will move it there thanks for pointing that out.

> But to take one thing ahead passing the GPIO number (or a label) from
> userspace is not really a good ABI in my opinion. The GPIO numbers should
> come from the hardware description (DTS, ACPI, board file, ...)
>
I wanted to offer the possibility to configure the device at run time. 
Since it is not part of the board but a periphial device it could also 
be attached while the system is running. I would like to keep this 
interface but will add support for also configue it via device tree, as 
you suggested.
>> +	do_gettimeofday(&now);
>
> gettimeofday is not a good function if you want to measure elapsed time
> since the returned value jumps around when the system time is changed. Try
> to use one of the monotonic time sources, those are guaranteed not to jump
> around.
Thanks I didn't think about that.

Kind regards,

- Johannes

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

* Re: [PATCH] HC-SR04 ultrasonic ranger IIO driver
  2016-06-06 18:40 [PATCH] HC-SR04 ultrasonic ranger IIO driver johannes
  2016-06-07  5:42 ` Matt Ranostay
  2016-06-07 11:44 ` Crt Mori
@ 2016-06-07 15:19 ` Lars-Peter Clausen
  2016-06-10  5:35   ` Johannes Thoma
  2016-06-11 15:20 ` Jonathan Cameron
  3 siblings, 1 reply; 16+ messages in thread
From: Lars-Peter Clausen @ 2016-06-07 15:19 UTC (permalink / raw)
  To: johannes, linux-iio

Hi,

Thanks for the patch.

On 06/06/2016 08:40 PM, johannes@johannesthoma.com wrote:
[...]
> + * To configure a device do a
> + *
> + *    mkdir /sys/kernel/config/iio/triggers/hc-sr04/sensor0
> + *
> + * (you need to mount configfs to /sys/kernel/config first unless it isn't
> + * mounted already)
> + *
> + * Then configure the ECHO and TRIG pins (this also accepts symbolic names
> + * configured in the device tree)
> + *
> + *    echo 23 > /config/iio/triggers/hc-sr04/sensor0/trig_pin
> + *    echo 24 > /config/iio/triggers/hc-sr04/sensor0/echo_pin
> + *
> + * Then you can measure distance with:
> + *
> + *    cat /sys/devices/trigger0/measure
> + *
> + * (trigger0 is the device name as reported by
> + *  /config/iio/triggers/hc-sr04/sensor0/dev_name

Any new ABI needs to be documented in Documentation/ABI/...

But to take one thing ahead passing the GPIO number (or a label) from
userspace is not really a good ABI in my opinion. The GPIO numbers should
come from the hardware description (DTS, ACPI, board file, ...)

> +	do_gettimeofday(&now);

gettimeofday is not a good function if you want to measure elapsed time
since the returned value jumps around when the system time is changed. Try
to use one of the monotonic time sources, those are guaranteed not to jump
around.

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

* Re: [PATCH] HC-SR04 ultrasonic ranger IIO driver
  2016-06-06 18:40 [PATCH] HC-SR04 ultrasonic ranger IIO driver johannes
  2016-06-07  5:42 ` Matt Ranostay
@ 2016-06-07 11:44 ` Crt Mori
  2016-06-07 15:19 ` Lars-Peter Clausen
  2016-06-11 15:20 ` Jonathan Cameron
  3 siblings, 0 replies; 16+ messages in thread
From: Crt Mori @ 2016-06-07 11:44 UTC (permalink / raw)
  To: johannes; +Cc: linux-iio

Minor picks inside after first read.

On 6 June 2016 at 20:40,  <johannes@johannesthoma.com> wrote:
> From: Johannes Thoma <johannes@johannesthoma.com>
>
> The HC-SR04 is an ultrasonic distance sensor attached to two GPIO
> pins. The driver is based on the Industrial I/O (iio) subsystem and is
> controlled via configfs and sysfs. It supports an (in theory) unlimited
> number of HC-SR04 devices.
>
> A datasheet to the device can be found at:
>
> http://www.micropik.com/PDF/HCSR04.pdf
> Signed-off-by: Johannes Thoma <johannes@johannesthoma.com>
> ---
>  MAINTAINERS                               |   7 +
>  drivers/iio/Kconfig                       |   1 +
>  drivers/iio/Makefile                      |   1 +
>  drivers/iio/ultrasonic-distance/Kconfig   |  17 ++
>  drivers/iio/ultrasonic-distance/Makefile  |   6 +
>  drivers/iio/ultrasonic-distance/hc-sr04.c | 466 ++++++++++++++++++++++++++++++
>  6 files changed, 498 insertions(+)
>  create mode 100644 drivers/iio/ultrasonic-distance/Kconfig
>  create mode 100644 drivers/iio/ultrasonic-distance/Makefile
>  create mode 100644 drivers/iio/ultrasonic-distance/hc-sr04.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 7304d2e..3bd640e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5211,6 +5211,13 @@ W:       http://www.kernel.org/pub/linux/kernel/people/fseidel/hdaps/
>  S:     Maintained
>  F:     drivers/platform/x86/hdaps.c
>
> +
> +HC-SR04 ULTRASONIC DISTANCE SENSOR DRIVER
> +M:     Johannes Thoma <johannes@johannesthoma.com>
> +S:     Maintained
> +F:     drivers/iio/ultrasonic-distance/hc-sr04.c
> +
> +

drop adding this last line and the top one as well

>  HDPVR USB VIDEO ENCODER DRIVER
>  M:     Hans Verkuil <hverkuil@xs4all.nl>
>  L:     linux-media@vger.kernel.org
> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> index 505e921..3c82aad 100644
> --- a/drivers/iio/Kconfig
> +++ b/drivers/iio/Kconfig
> @@ -82,5 +82,6 @@ source "drivers/iio/potentiometer/Kconfig"
>  source "drivers/iio/pressure/Kconfig"
>  source "drivers/iio/proximity/Kconfig"
>  source "drivers/iio/temperature/Kconfig"
> +source "drivers/iio/ultrasonic-distance/Kconfig"
>
>  endif # IIO
> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> index 20f6490..0f1c00c 100644
> --- a/drivers/iio/Makefile
> +++ b/drivers/iio/Makefile
> @@ -32,3 +32,4 @@ obj-y += pressure/
>  obj-y += proximity/
>  obj-y += temperature/
>  obj-y += trigger/
> +obj-y += ultrasonic-distance/
> diff --git a/drivers/iio/ultrasonic-distance/Kconfig b/drivers/iio/ultrasonic-distance/Kconfig
> new file mode 100644
> index 0000000..46e848d
> --- /dev/null
> +++ b/drivers/iio/ultrasonic-distance/Kconfig
> @@ -0,0 +1,17 @@
> +#
> +# Ultrasonic range sensors
> +#
> +
> +menu "Ultrasonic ranger devices"
> +
> +config HC_SR04
> +       tristate "HC-SR04 ultrasonic distance sensor on GPIO"
> +       select IIO_SW_TRIGGER
> +       depends on GPIOLIB
> +       help
> +         Say Y here if you want to support the HC-SR04 ultrasonic distance
> +         sensor which is attached on two runtime-configurable GPIO pins.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called hc-sr04.
> +endmenu
> diff --git a/drivers/iio/ultrasonic-distance/Makefile b/drivers/iio/ultrasonic-distance/Makefile
> new file mode 100644
> index 0000000..1f01d50c
> --- /dev/null
> +++ b/drivers/iio/ultrasonic-distance/Makefile
> @@ -0,0 +1,6 @@
> +#
> +# Makefile for IIO proximity sensors

While my first thought would be to put driver into proximity, if you
do not intend to then at least modify everything. At this point I
would rather have distance than ultrasonic-distance, as that way we
can put more drivers (lidar, radar etc.)? Lidar is otherwise in
proximity and that is where I would put this on as well.

> +#
> +
> +# When adding new entries keep the list in alphabetical order
> +obj-$(CONFIG_HC_SR04)          += hc-sr04.o
> diff --git a/drivers/iio/ultrasonic-distance/hc-sr04.c b/drivers/iio/ultrasonic-distance/hc-sr04.c
> new file mode 100644
> index 0000000..e7da37b
> --- /dev/null
> +++ b/drivers/iio/ultrasonic-distance/hc-sr04.c
> @@ -0,0 +1,466 @@
> +/*
> + * hc-sr04.c - Support for HC-SR04 ultrasonic range sensor
> + *
> + * Copyright (C) 2016 Johannes Thoma <johannes@johannesthoma.com>
> + *
> + * 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; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +/* Precise measurements of time delta between sending a trigger signal
> + * to the HC-SR04 distance sensor and receiving the echo signal from
> + * the sensor back. This has to be precise in the usecs range. We
> + * use trigger interrupts to measure the signal, so no busy wait :)
> + *
> + * This supports an (in theory) unlimited number of HC-SR04 devices.
> + * It uses IIO software triggers to interface with userland.
> + *
> + * To configure a device do a
> + *
> + *    mkdir /sys/kernel/config/iio/triggers/hc-sr04/sensor0
> + *
> + * (you need to mount configfs to /sys/kernel/config first unless it isn't
> + * mounted already)
> + *
> + * Then configure the ECHO and TRIG pins (this also accepts symbolic names
> + * configured in the device tree)
> + *
> + *    echo 23 > /config/iio/triggers/hc-sr04/sensor0/trig_pin
> + *    echo 24 > /config/iio/triggers/hc-sr04/sensor0/echo_pin
> + *
> + * Then you can measure distance with:
> + *
> + *    cat /sys/devices/trigger0/measure
> + *
> + * (trigger0 is the device name as reported by
> + *  /config/iio/triggers/hc-sr04/sensor0/dev_name
> + *
> + * This reports the length of the ECHO signal in microseconds, which is
> + * related linearily to the distance measured.
> + *
> + * To convert to centimeters, multiply by 17150 and divide by 1000000 (air)
> + *
> + * DO NOT attach your HC-SR04's echo pin directly to the raspberry, since
> + * it runs with 5V while raspberry expects 3V on the GPIO inputs.

While it is nice that we put a specifics in comments this can be a bit
more general

> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/timekeeping.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/mutex.h>
> +#include <linux/device.h>
> +#include <linux/sysfs.h>
> +#include <linux/interrupt.h>
> +#include <linux/kdev_t.h>
> +#include <linux/list.h>
> +#include <linux/slab.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/delay.h>
> +#include <linux/sched.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/sw_trigger.h>
> +
> +#define DEFAULT_TIMEOUT 1000
> +
> +enum hc_sr04_state {
> +       DEVICE_IDLE,
> +       DEVICE_TRIGGERED,
> +       DEVICE_ECHO_RECEIVED
> +};
> +
> +struct hc_sr04 {
> +               /* the GPIOs of ECHO and TRIG */
> +       struct gpio_desc *trig_desc;
> +       struct gpio_desc *echo_desc;
> +               /* Used to measure length of ECHO signal */
> +       struct timeval time_triggered;
> +       struct timeval time_echoed;
> +               /* protects against starting multiple measurements */
> +       struct mutex measurement_mutex;
> +               /* Current state of measurement */
> +       enum hc_sr04_state state;
> +               /* Used by interrupt to wake measurement routine up */
> +       wait_queue_head_t wait_for_echo;
> +               /* timeout in ms, fail when no echo received within that time */
> +       unsigned long timeout;
> +               /* Our IIO interface */
> +       struct iio_sw_trigger swt;
> +               /* Used to compute device settle time */
> +       struct timeval last_measurement;
> +};
> +
> +static inline struct hc_sr04 *to_hc_sr04(struct config_item *item)
> +{
> +       struct iio_sw_trigger *trig = to_iio_sw_trigger(item);
> +
> +       return container_of(trig, struct hc_sr04, swt);
> +}
> +
> +static irqreturn_t echo_received_irq(int irq, void *data)
> +{
> +       struct hc_sr04 *device = (struct hc_sr04 *)data;
> +       int val;
> +       struct timeval irq_tv;
> +
> +       do_gettimeofday(&irq_tv);
> +
> +       if (device->state != DEVICE_TRIGGERED)
> +               return IRQ_HANDLED;
> +
> +       val = gpiod_get_value(device->echo_desc);
> +       if (val == 1) {
> +               device->time_triggered = irq_tv;
> +       } else {
> +               device->time_echoed = irq_tv;
> +               device->state = DEVICE_ECHO_RECEIVED;
> +               wake_up_interruptible(&device->wait_for_echo);
> +       }
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int do_measurement(struct hc_sr04 *device,
> +                         long long *usecs_elapsed)
> +{
> +       long timeout;
> +       int irq;
> +       int ret;
> +       struct timeval now;
> +       long long time_since_last_measurement;
> +
> +       *usecs_elapsed = -1;
> +
> +       if (!device->echo_desc || !device->trig_desc) {
> +               dev_dbg(&device->swt.trigger->dev, "Please configure GPIO pins first.\n");
> +               return -EINVAL;
> +       }
> +       if (!mutex_trylock(&device->measurement_mutex))
> +               return -EBUSY;
> +
> +       do_gettimeofday(&now);
> +       if (device->last_measurement.tv_sec || device->last_measurement.tv_usec)
> +               time_since_last_measurement =
> +       (now.tv_sec - device->last_measurement.tv_sec) * 1000000 +
> +       (now.tv_usec - device->last_measurement.tv_usec);
> +       else
> +               time_since_last_measurement = 60000;
> +
> +               /* wait 60 ms between measurements.
> +                * now, a while true ; do cat measure ; done should work
> +                */
> +
> +       if (time_since_last_measurement < 60000 &&
> +           time_since_last_measurement >= 0)
> +               msleep(60 - (int)time_since_last_measurement / 1000);
> +
> +       irq = gpiod_to_irq(device->echo_desc);
> +       if (irq < 0) {
> +               ret = -EIO;
> +               goto out_mutex;
> +       }
> +
> +       ret = request_any_context_irq(irq, echo_received_irq,
> +                                     IRQF_SHARED | IRQF_TRIGGER_FALLING |
> +                                     IRQF_TRIGGER_RISING,
> +                                     "hc_sr04", device);
> +
> +       if (ret < 0)
> +               goto out_mutex;
> +
> +       gpiod_set_value(device->trig_desc, 1);
> +       usleep_range(10, 20);
> +       device->state = DEVICE_TRIGGERED;
> +       gpiod_set_value(device->trig_desc, 0);
> +
> +       ret = gpiochip_lock_as_irq(gpiod_to_chip(device->echo_desc),
> +                                  desc_to_gpio(device->echo_desc));
> +       if (ret < 0)
> +               goto out_irq;
> +
> +       timeout = wait_event_interruptible_timeout(
> +                       device->wait_for_echo,
> +                       device->state == DEVICE_ECHO_RECEIVED,
> +                       device->timeout * HZ / 1000);
> +
> +       device->state = DEVICE_IDLE;
> +
> +       if (timeout == 0) {
> +               ret = -ETIMEDOUT;
> +       } else if (timeout < 0) {
> +               ret = timeout;
> +       } else {
> +               *usecs_elapsed =
> +       (device->time_echoed.tv_sec - device->time_triggered.tv_sec) * 1000000 +
> +       (device->time_echoed.tv_usec - device->time_triggered.tv_usec);
> +               ret = 0;
> +               do_gettimeofday(&device->last_measurement);
> +       }
> +       gpiochip_unlock_as_irq(gpiod_to_chip(device->echo_desc),
> +                              desc_to_gpio(device->echo_desc));
> +out_irq:
> +       free_irq(irq, device);
> +out_mutex:
> +       mutex_unlock(&device->measurement_mutex);
> +
> +       return ret;
> +}
> +
> +static ssize_t sysfs_do_measurement(struct device *dev,
> +                                   struct device_attribute *attr,
> +                                   char *buf)
> +{
> +       struct hc_sr04 *sensor = dev_get_drvdata(dev);
> +       long long usecs_elapsed;
> +       int status;
> +
> +       status = do_measurement(sensor, &usecs_elapsed);
> +
> +       if (status < 0)
> +               return status;
> +
> +       return sprintf(buf, "%lld\n", usecs_elapsed);
> +}
> +
> +DEVICE_ATTR(measure, 0444, sysfs_do_measurement, NULL);
> +
> +static struct attribute *sensor_attrs[] = {
> +       &dev_attr_measure.attr,
> +       NULL,
> +};
> +
> +static const struct attribute_group sensor_group = {
> +       .attrs = sensor_attrs
> +};
> +
> +static const struct attribute_group *sensor_groups[] = {
> +       &sensor_group,
> +       NULL
> +};
> +
> +static ssize_t configure_pin(struct gpio_desc **desc, struct config_item *item,
> +                            const char *buf, size_t len, struct device *dev)
> +{
> +       int err;
> +       int echo;
> +
> +       if (*desc)
> +               gpiod_put(*desc);
> +
> +       *desc = gpiod_get(dev, buf, GPIOD_ASIS);
> +       if (IS_ERR(*desc)) {
> +               err = PTR_ERR(*desc);
> +               *desc = NULL;
> +
> +               if (err == -ENOENT) {   /* fallback: use GPIO numbers */
> +                       err = kstrtoint(buf, 10, &echo);
> +                       if (err < 0)
> +                               return -ENOENT;
> +                       *desc = gpio_to_desc(echo);
> +                       if (*desc)
> +                               return len;
> +                       return -ENOENT;
> +               }
> +
> +               return err;
> +       }
> +       return len;
> +}
> +
> +static ssize_t hc_sr04_echo_pin_store(struct config_item *item,
> +                                     const char *buf, size_t len)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +       ssize_t ret;
> +       int err;
> +
> +       ret = configure_pin(&sensor->echo_desc, item, buf, len,
> +                           &sensor->swt.trigger->dev);
> +
> +       if (ret >= 0 && sensor->echo_desc) {
> +               err = gpiod_direction_input(sensor->echo_desc);
> +               if (err < 0)
> +                       return err;
> +       }
> +       return ret;
> +}
> +
> +static ssize_t hc_sr04_echo_pin_show(struct config_item *item,
> +                                    char *buf)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +       if (sensor->echo_desc)
> +               return sprintf(buf, "%d\n", desc_to_gpio(sensor->echo_desc));
> +       return 0;
> +}
> +
> +static ssize_t hc_sr04_trig_pin_store(struct config_item *item,
> +                                     const char *buf, size_t len)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +       ssize_t ret;
> +       int err;
> +
> +       ret = configure_pin(&sensor->trig_desc, item, buf, len,
> +                           &sensor->swt.trigger->dev);
> +
> +       if (ret >= 0 && sensor->trig_desc) {
> +               err = gpiod_direction_output(sensor->trig_desc, 0);
> +               if (err >= 0)
> +                       gpiod_set_value(sensor->trig_desc, 0);
> +               else
> +                       return err;
> +       }
> +       return ret;
> +}
> +
> +static ssize_t hc_sr04_trig_pin_show(struct config_item *item,
> +                                    char *buf)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +       if (sensor->trig_desc)
> +               return sprintf(buf, "%d\n", desc_to_gpio(sensor->trig_desc));
> +       return 0;
> +}
> +
> +static ssize_t hc_sr04_timeout_store(struct config_item *item,
> +                                    const char *buf, size_t len)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +       unsigned long t;
> +       int ret;
> +
> +       ret = kstrtol(buf, 10, &t);
> +       if (ret < 0)
> +               return ret;
> +
> +       sensor->timeout = t;
> +       return len;
> +}
> +
> +static ssize_t hc_sr04_timeout_show(struct config_item *item,
> +                                   char *buf)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +       return sprintf(buf, "%ld\n", sensor->timeout);
> +}
> +
> +static ssize_t hc_sr04_dev_name_show(struct config_item *item,
> +                                    char *buf)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +       return sprintf(buf, "%s", dev_name(&sensor->swt.trigger->dev));
> +}
> +
> +CONFIGFS_ATTR(hc_sr04_, echo_pin);
> +CONFIGFS_ATTR(hc_sr04_, trig_pin);
> +CONFIGFS_ATTR(hc_sr04_, timeout);
> +CONFIGFS_ATTR_RO(hc_sr04_, dev_name);
> +
> +static struct configfs_attribute *hc_sr04_config_attrs[] = {
> +       &hc_sr04_attr_echo_pin,
> +       &hc_sr04_attr_trig_pin,
> +       &hc_sr04_attr_timeout,
> +       &hc_sr04_attr_dev_name,
> +       NULL
> +};
> +
> +static struct config_item_type iio_hc_sr04_type = {
> +       .ct_owner = THIS_MODULE,
> +       .ct_attrs = hc_sr04_config_attrs
> +};
> +
> +static int iio_trig_hc_sr04_set_state(struct iio_trigger *trig, bool state)
> +{
> +       return 0;
> +}
> +
> +static const struct iio_trigger_ops iio_hc_sr04_trigger_ops = {
> +       .owner = THIS_MODULE,
> +       .set_trigger_state = iio_trig_hc_sr04_set_state,
> +};
> +
> +static struct iio_sw_trigger *iio_trig_hc_sr04_probe(const char *name)
> +{
> +       struct hc_sr04 *sensor;
> +       int ret;
> +
> +       sensor = kzalloc(sizeof(*sensor), GFP_KERNEL);
> +       if (!sensor)
> +               return ERR_PTR(-ENOMEM);
> +
> +       mutex_init(&sensor->measurement_mutex);
> +       init_waitqueue_head(&sensor->wait_for_echo);
> +       sensor->timeout = DEFAULT_TIMEOUT;
> +
> +       sensor->swt.trigger = iio_trigger_alloc("%s", name);
> +       if (!sensor->swt.trigger) {
> +               ret = -ENOMEM;
> +               goto err_free_sensor;
> +       }
> +       iio_trigger_set_drvdata(sensor->swt.trigger, sensor);
> +       sensor->swt.trigger->ops = &iio_hc_sr04_trigger_ops;
> +       sensor->swt.trigger->dev.groups = sensor_groups;
> +
> +       ret = iio_trigger_register(sensor->swt.trigger);
> +       if (ret)
> +               goto err_free_trigger;
> +
> +       iio_swt_group_init_type_name(&sensor->swt, name, &iio_hc_sr04_type);
> +       return &sensor->swt;
> +
> +err_free_trigger:
> +       iio_trigger_free(sensor->swt.trigger);
> +err_free_sensor:
> +       kfree(sensor);
> +
> +       return ERR_PTR(ret);
> +}
> +
> +static int iio_trig_hc_sr04_remove(struct iio_sw_trigger *swt)
> +{
> +       struct hc_sr04 *rip_sensor;
> +
> +       rip_sensor = iio_trigger_get_drvdata(swt->trigger);
> +
> +       iio_trigger_unregister(swt->trigger);
> +
> +       /* Wait for measurement to be finished. */
> +       mutex_lock(&rip_sensor->measurement_mutex);
> +
> +       iio_trigger_free(swt->trigger);
> +       kfree(rip_sensor);
> +
> +       return 0;
> +}
> +
> +static const struct iio_sw_trigger_ops iio_trig_hc_sr04_ops = {
> +       .probe          = iio_trig_hc_sr04_probe,
> +       .remove         = iio_trig_hc_sr04_remove,
> +};
> +
> +static struct iio_sw_trigger_type iio_trig_hc_sr04 = {
> +       .name = "hc-sr04",
> +       .owner = THIS_MODULE,
> +       .ops = &iio_trig_hc_sr04_ops,
> +};
> +
> +module_iio_sw_trigger_driver(iio_trig_hc_sr04);
> +
> +MODULE_AUTHOR("Johannes Thoma");
> +MODULE_DESCRIPTION("Distance measurement for the HC-SR04 ultrasonic distance sensor");
> +MODULE_LICENSE("GPL");
> +
> --
> 2.8.0-rc4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH] HC-SR04 ultrasonic ranger IIO driver
  2016-06-06 18:40 [PATCH] HC-SR04 ultrasonic ranger IIO driver johannes
@ 2016-06-07  5:42 ` Matt Ranostay
  2016-06-07 11:44 ` Crt Mori
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 16+ messages in thread
From: Matt Ranostay @ 2016-06-07  5:42 UTC (permalink / raw)
  To: johannes; +Cc: linux-iio

On Mon, Jun 6, 2016 at 11:40 AM,  <johannes@johannesthoma.com> wrote:
> From: Johannes Thoma <johannes@johannesthoma.com>
>
> The HC-SR04 is an ultrasonic distance sensor attached to two GPIO
> pins. The driver is based on the Industrial I/O (iio) subsystem and is
> controlled via configfs and sysfs. It supports an (in theory) unlimited
> number of HC-SR04 devices.
>
> A datasheet to the device can be found at:
>
> http://www.micropik.com/PDF/HCSR04.pdf
> Signed-off-by: Johannes Thoma <johannes@johannesthoma.com>
> ---
>  MAINTAINERS                               |   7 +
>  drivers/iio/Kconfig                       |   1 +
>  drivers/iio/Makefile                      |   1 +
>  drivers/iio/ultrasonic-distance/Kconfig   |  17 ++
>  drivers/iio/ultrasonic-distance/Makefile  |   6 +
>  drivers/iio/ultrasonic-distance/hc-sr04.c | 466 ++++++++++++++++++++++++++++++
>  6 files changed, 498 insertions(+)
>  create mode 100644 drivers/iio/ultrasonic-distance/Kconfig
>  create mode 100644 drivers/iio/ultrasonic-distance/Makefile
>  create mode 100644 drivers/iio/ultrasonic-distance/hc-sr04.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 7304d2e..3bd640e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5211,6 +5211,13 @@ W:       http://www.kernel.org/pub/linux/kernel/people/fseidel/hdaps/
>  S:     Maintained
>  F:     drivers/platform/x86/hdaps.c
>
> +
> +HC-SR04 ULTRASONIC DISTANCE SENSOR DRIVER
> +M:     Johannes Thoma <johannes@johannesthoma.com>
> +S:     Maintained
> +F:     drivers/iio/ultrasonic-distance/hc-sr04.c
> +
> +
>  HDPVR USB VIDEO ENCODER DRIVER
>  M:     Hans Verkuil <hverkuil@xs4all.nl>
>  L:     linux-media@vger.kernel.org
> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> index 505e921..3c82aad 100644
> --- a/drivers/iio/Kconfig
> +++ b/drivers/iio/Kconfig
> @@ -82,5 +82,6 @@ source "drivers/iio/potentiometer/Kconfig"
>  source "drivers/iio/pressure/Kconfig"
>  source "drivers/iio/proximity/Kconfig"
>  source "drivers/iio/temperature/Kconfig"
> +source "drivers/iio/ultrasonic-distance/Kconfig"

No reason to start another path for this driver. Please use
drivers/iio/proximity

>
>  endif # IIO
> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> index 20f6490..0f1c00c 100644
> --- a/drivers/iio/Makefile
> +++ b/drivers/iio/Makefile
> @@ -32,3 +32,4 @@ obj-y += pressure/
>  obj-y += proximity/
>  obj-y += temperature/
>  obj-y += trigger/
> +obj-y += ultrasonic-distance/
> diff --git a/drivers/iio/ultrasonic-distance/Kconfig b/drivers/iio/ultrasonic-distance/Kconfig
> new file mode 100644
> index 0000000..46e848d
> --- /dev/null
> +++ b/drivers/iio/ultrasonic-distance/Kconfig
> @@ -0,0 +1,17 @@
> +#
> +# Ultrasonic range sensors
> +#
> +
> +menu "Ultrasonic ranger devices"
> +
> +config HC_SR04
> +       tristate "HC-SR04 ultrasonic distance sensor on GPIO"
> +       select IIO_SW_TRIGGER
> +       depends on GPIOLIB
> +       help
> +         Say Y here if you want to support the HC-SR04 ultrasonic distance
> +         sensor which is attached on two runtime-configurable GPIO pins.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called hc-sr04.
> +endmenu
> diff --git a/drivers/iio/ultrasonic-distance/Makefile b/drivers/iio/ultrasonic-distance/Makefile
> new file mode 100644
> index 0000000..1f01d50c
> --- /dev/null
> +++ b/drivers/iio/ultrasonic-distance/Makefile
> @@ -0,0 +1,6 @@
> +#
> +# Makefile for IIO proximity sensors
> +#
> +
> +# When adding new entries keep the list in alphabetical order
> +obj-$(CONFIG_HC_SR04)          += hc-sr04.o
> diff --git a/drivers/iio/ultrasonic-distance/hc-sr04.c b/drivers/iio/ultrasonic-distance/hc-sr04.c
> new file mode 100644
> index 0000000..e7da37b
> --- /dev/null
> +++ b/drivers/iio/ultrasonic-distance/hc-sr04.c
> @@ -0,0 +1,466 @@
> +/*
> + * hc-sr04.c - Support for HC-SR04 ultrasonic range sensor
> + *
> + * Copyright (C) 2016 Johannes Thoma <johannes@johannesthoma.com>
> + *
> + * 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; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +/* Precise measurements of time delta between sending a trigger signal
> + * to the HC-SR04 distance sensor and receiving the echo signal from
> + * the sensor back. This has to be precise in the usecs range. We
> + * use trigger interrupts to measure the signal, so no busy wait :)
> + *
> + * This supports an (in theory) unlimited number of HC-SR04 devices.
> + * It uses IIO software triggers to interface with userland.
> + *
> + * To configure a device do a
> + *
> + *    mkdir /sys/kernel/config/iio/triggers/hc-sr04/sensor0
> + *
> + * (you need to mount configfs to /sys/kernel/config first unless it isn't
> + * mounted already)
> + *
> + * Then configure the ECHO and TRIG pins (this also accepts symbolic names
> + * configured in the device tree)
> + *
> + *    echo 23 > /config/iio/triggers/hc-sr04/sensor0/trig_pin
> + *    echo 24 > /config/iio/triggers/hc-sr04/sensor0/echo_pin
> + *
> + * Then you can measure distance with:
> + *
> + *    cat /sys/devices/trigger0/measure
> + *
> + * (trigger0 is the device name as reported by
> + *  /config/iio/triggers/hc-sr04/sensor0/dev_name
> + *
> + * This reports the length of the ECHO signal in microseconds, which is
> + * related linearily to the distance measured.
> + *
> + * To convert to centimeters, multiply by 17150 and divide by 1000000 (air)
> + *
> + * DO NOT attach your HC-SR04's echo pin directly to the raspberry, since
> + * it runs with 5V while raspberry expects 3V on the GPIO inputs.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/timekeeping.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/mutex.h>
> +#include <linux/device.h>
> +#include <linux/sysfs.h>
> +#include <linux/interrupt.h>
> +#include <linux/kdev_t.h>
> +#include <linux/list.h>
> +#include <linux/slab.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/delay.h>
> +#include <linux/sched.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/sw_trigger.h>
> +
> +#define DEFAULT_TIMEOUT 1000
> +
> +enum hc_sr04_state {
> +       DEVICE_IDLE,
> +       DEVICE_TRIGGERED,
> +       DEVICE_ECHO_RECEIVED
> +};
> +
> +struct hc_sr04 {
> +               /* the GPIOs of ECHO and TRIG */
> +       struct gpio_desc *trig_desc;
> +       struct gpio_desc *echo_desc;
> +               /* Used to measure length of ECHO signal */
> +       struct timeval time_triggered;
> +       struct timeval time_echoed;
> +               /* protects against starting multiple measurements */
> +       struct mutex measurement_mutex;
> +               /* Current state of measurement */
> +       enum hc_sr04_state state;
> +               /* Used by interrupt to wake measurement routine up */
> +       wait_queue_head_t wait_for_echo;
> +               /* timeout in ms, fail when no echo received within that time */
> +       unsigned long timeout;
> +               /* Our IIO interface */
> +       struct iio_sw_trigger swt;
> +               /* Used to compute device settle time */
> +       struct timeval last_measurement;
> +};
> +
> +static inline struct hc_sr04 *to_hc_sr04(struct config_item *item)
> +{
> +       struct iio_sw_trigger *trig = to_iio_sw_trigger(item);
> +
> +       return container_of(trig, struct hc_sr04, swt);
> +}
> +
> +static irqreturn_t echo_received_irq(int irq, void *data)
> +{
> +       struct hc_sr04 *device = (struct hc_sr04 *)data;
> +       int val;
> +       struct timeval irq_tv;
> +
> +       do_gettimeofday(&irq_tv);
> +
> +       if (device->state != DEVICE_TRIGGERED)
> +               return IRQ_HANDLED;
> +
> +       val = gpiod_get_value(device->echo_desc);
> +       if (val == 1) {
> +               device->time_triggered = irq_tv;
> +       } else {
> +               device->time_echoed = irq_tv;
> +               device->state = DEVICE_ECHO_RECEIVED;
> +               wake_up_interruptible(&device->wait_for_echo);
> +       }
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int do_measurement(struct hc_sr04 *device,
> +                         long long *usecs_elapsed)
> +{
> +       long timeout;
> +       int irq;
> +       int ret;
> +       struct timeval now;
> +       long long time_since_last_measurement;
> +
> +       *usecs_elapsed = -1;
> +
> +       if (!device->echo_desc || !device->trig_desc) {
> +               dev_dbg(&device->swt.trigger->dev, "Please configure GPIO pins first.\n");
> +               return -EINVAL;
> +       }
> +       if (!mutex_trylock(&device->measurement_mutex))
> +               return -EBUSY;
> +
> +       do_gettimeofday(&now);
> +       if (device->last_measurement.tv_sec || device->last_measurement.tv_usec)
> +               time_since_last_measurement =
> +       (now.tv_sec - device->last_measurement.tv_sec) * 1000000 +
> +       (now.tv_usec - device->last_measurement.tv_usec);
> +       else
> +               time_since_last_measurement = 60000;
> +
> +               /* wait 60 ms between measurements.
> +                * now, a while true ; do cat measure ; done should work
> +                */
> +
> +       if (time_since_last_measurement < 60000 &&
> +           time_since_last_measurement >= 0)
> +               msleep(60 - (int)time_since_last_measurement / 1000);
> +
> +       irq = gpiod_to_irq(device->echo_desc);
> +       if (irq < 0) {
> +               ret = -EIO;
> +               goto out_mutex;
> +       }
> +
> +       ret = request_any_context_irq(irq, echo_received_irq,
> +                                     IRQF_SHARED | IRQF_TRIGGER_FALLING |
> +                                     IRQF_TRIGGER_RISING,
> +                                     "hc_sr04", device);
> +
> +       if (ret < 0)
> +               goto out_mutex;
> +
> +       gpiod_set_value(device->trig_desc, 1);
> +       usleep_range(10, 20);
> +       device->state = DEVICE_TRIGGERED;
> +       gpiod_set_value(device->trig_desc, 0);
> +
> +       ret = gpiochip_lock_as_irq(gpiod_to_chip(device->echo_desc),
> +                                  desc_to_gpio(device->echo_desc));
> +       if (ret < 0)
> +               goto out_irq;
> +
> +       timeout = wait_event_interruptible_timeout(
> +                       device->wait_for_echo,
> +                       device->state == DEVICE_ECHO_RECEIVED,
> +                       device->timeout * HZ / 1000);
> +
> +       device->state = DEVICE_IDLE;
> +
> +       if (timeout == 0) {
> +               ret = -ETIMEDOUT;
> +       } else if (timeout < 0) {
> +               ret = timeout;
> +       } else {
> +               *usecs_elapsed =
> +       (device->time_echoed.tv_sec - device->time_triggered.tv_sec) * 1000000 +
> +       (device->time_echoed.tv_usec - device->time_triggered.tv_usec);
> +               ret = 0;
> +               do_gettimeofday(&device->last_measurement);
> +       }
> +       gpiochip_unlock_as_irq(gpiod_to_chip(device->echo_desc),
> +                              desc_to_gpio(device->echo_desc));
> +out_irq:
> +       free_irq(irq, device);
> +out_mutex:
> +       mutex_unlock(&device->measurement_mutex);
> +
> +       return ret;
> +}
> +
> +static ssize_t sysfs_do_measurement(struct device *dev,
> +                                   struct device_attribute *attr,
> +                                   char *buf)
> +{
> +       struct hc_sr04 *sensor = dev_get_drvdata(dev);
> +       long long usecs_elapsed;
> +       int status;
> +
> +       status = do_measurement(sensor, &usecs_elapsed);
> +
> +       if (status < 0)
> +               return status;
> +
> +       return sprintf(buf, "%lld\n", usecs_elapsed);
> +}
> +
> +DEVICE_ATTR(measure, 0444, sysfs_do_measurement, NULL);
> +
> +static struct attribute *sensor_attrs[] = {
> +       &dev_attr_measure.attr,
> +       NULL,
> +};
> +
> +static const struct attribute_group sensor_group = {
> +       .attrs = sensor_attrs
> +};
> +
> +static const struct attribute_group *sensor_groups[] = {
> +       &sensor_group,
> +       NULL
> +};
> +
> +static ssize_t configure_pin(struct gpio_desc **desc, struct config_item *item,
> +                            const char *buf, size_t len, struct device *dev)
> +{
> +       int err;
> +       int echo;
> +
> +       if (*desc)
> +               gpiod_put(*desc);
> +
> +       *desc = gpiod_get(dev, buf, GPIOD_ASIS);
> +       if (IS_ERR(*desc)) {
> +               err = PTR_ERR(*desc);
> +               *desc = NULL;
> +
> +               if (err == -ENOENT) {   /* fallback: use GPIO numbers */
> +                       err = kstrtoint(buf, 10, &echo);
> +                       if (err < 0)
> +                               return -ENOENT;
> +                       *desc = gpio_to_desc(echo);
> +                       if (*desc)
> +                               return len;
> +                       return -ENOENT;
> +               }
> +
> +               return err;
> +       }
> +       return len;
> +}
> +
> +static ssize_t hc_sr04_echo_pin_store(struct config_item *item,
> +                                     const char *buf, size_t len)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +       ssize_t ret;
> +       int err;
> +
> +       ret = configure_pin(&sensor->echo_desc, item, buf, len,
> +                           &sensor->swt.trigger->dev);
> +
> +       if (ret >= 0 && sensor->echo_desc) {
> +               err = gpiod_direction_input(sensor->echo_desc);
> +               if (err < 0)
> +                       return err;
> +       }
> +       return ret;
> +}
> +
> +static ssize_t hc_sr04_echo_pin_show(struct config_item *item,
> +                                    char *buf)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +       if (sensor->echo_desc)
> +               return sprintf(buf, "%d\n", desc_to_gpio(sensor->echo_desc));
> +       return 0;
> +}
> +
> +static ssize_t hc_sr04_trig_pin_store(struct config_item *item,
> +                                     const char *buf, size_t len)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +       ssize_t ret;
> +       int err;
> +
> +       ret = configure_pin(&sensor->trig_desc, item, buf, len,
> +                           &sensor->swt.trigger->dev);
> +
> +       if (ret >= 0 && sensor->trig_desc) {
> +               err = gpiod_direction_output(sensor->trig_desc, 0);
> +               if (err >= 0)
> +                       gpiod_set_value(sensor->trig_desc, 0);
> +               else
> +                       return err;
> +       }
> +       return ret;
> +}
> +
> +static ssize_t hc_sr04_trig_pin_show(struct config_item *item,
> +                                    char *buf)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +       if (sensor->trig_desc)
> +               return sprintf(buf, "%d\n", desc_to_gpio(sensor->trig_desc));
> +       return 0;
> +}
> +
> +static ssize_t hc_sr04_timeout_store(struct config_item *item,
> +                                    const char *buf, size_t len)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +       unsigned long t;
> +       int ret;
> +
> +       ret = kstrtol(buf, 10, &t);
> +       if (ret < 0)
> +               return ret;
> +
> +       sensor->timeout = t;
> +       return len;
> +}
> +
> +static ssize_t hc_sr04_timeout_show(struct config_item *item,
> +                                   char *buf)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +       return sprintf(buf, "%ld\n", sensor->timeout);
> +}
> +
> +static ssize_t hc_sr04_dev_name_show(struct config_item *item,
> +                                    char *buf)
> +{
> +       struct hc_sr04 *sensor = to_hc_sr04(item);
> +
> +       return sprintf(buf, "%s", dev_name(&sensor->swt.trigger->dev));
> +}
> +
> +CONFIGFS_ATTR(hc_sr04_, echo_pin);
> +CONFIGFS_ATTR(hc_sr04_, trig_pin);
> +CONFIGFS_ATTR(hc_sr04_, timeout);
> +CONFIGFS_ATTR_RO(hc_sr04_, dev_name);
> +
> +static struct configfs_attribute *hc_sr04_config_attrs[] = {
> +       &hc_sr04_attr_echo_pin,
> +       &hc_sr04_attr_trig_pin,
> +       &hc_sr04_attr_timeout,
> +       &hc_sr04_attr_dev_name,
> +       NULL
> +};
> +
> +static struct config_item_type iio_hc_sr04_type = {
> +       .ct_owner = THIS_MODULE,
> +       .ct_attrs = hc_sr04_config_attrs
> +};
> +
> +static int iio_trig_hc_sr04_set_state(struct iio_trigger *trig, bool state)
> +{
> +       return 0;
> +}
> +
> +static const struct iio_trigger_ops iio_hc_sr04_trigger_ops = {
> +       .owner = THIS_MODULE,
> +       .set_trigger_state = iio_trig_hc_sr04_set_state,
> +};
> +
> +static struct iio_sw_trigger *iio_trig_hc_sr04_probe(const char *name)
> +{
> +       struct hc_sr04 *sensor;
> +       int ret;
> +
> +       sensor = kzalloc(sizeof(*sensor), GFP_KERNEL);
> +       if (!sensor)
> +               return ERR_PTR(-ENOMEM);
> +
> +       mutex_init(&sensor->measurement_mutex);
> +       init_waitqueue_head(&sensor->wait_for_echo);
> +       sensor->timeout = DEFAULT_TIMEOUT;
> +
> +       sensor->swt.trigger = iio_trigger_alloc("%s", name);
> +       if (!sensor->swt.trigger) {
> +               ret = -ENOMEM;
> +               goto err_free_sensor;
> +       }
> +       iio_trigger_set_drvdata(sensor->swt.trigger, sensor);
> +       sensor->swt.trigger->ops = &iio_hc_sr04_trigger_ops;
> +       sensor->swt.trigger->dev.groups = sensor_groups;
> +
> +       ret = iio_trigger_register(sensor->swt.trigger);
> +       if (ret)
> +               goto err_free_trigger;
> +
> +       iio_swt_group_init_type_name(&sensor->swt, name, &iio_hc_sr04_type);
> +       return &sensor->swt;
> +
> +err_free_trigger:
> +       iio_trigger_free(sensor->swt.trigger);
> +err_free_sensor:
> +       kfree(sensor);
> +
> +       return ERR_PTR(ret);
> +}
> +
> +static int iio_trig_hc_sr04_remove(struct iio_sw_trigger *swt)
> +{
> +       struct hc_sr04 *rip_sensor;
> +
> +       rip_sensor = iio_trigger_get_drvdata(swt->trigger);
> +
> +       iio_trigger_unregister(swt->trigger);
> +
> +       /* Wait for measurement to be finished. */
> +       mutex_lock(&rip_sensor->measurement_mutex);
> +
> +       iio_trigger_free(swt->trigger);
> +       kfree(rip_sensor);
> +
> +       return 0;
> +}
> +
> +static const struct iio_sw_trigger_ops iio_trig_hc_sr04_ops = {
> +       .probe          = iio_trig_hc_sr04_probe,
> +       .remove         = iio_trig_hc_sr04_remove,
> +};
> +
> +static struct iio_sw_trigger_type iio_trig_hc_sr04 = {
> +       .name = "hc-sr04",
> +       .owner = THIS_MODULE,
> +       .ops = &iio_trig_hc_sr04_ops,
> +};
> +
> +module_iio_sw_trigger_driver(iio_trig_hc_sr04);
> +
> +MODULE_AUTHOR("Johannes Thoma");
> +MODULE_DESCRIPTION("Distance measurement for the HC-SR04 ultrasonic distance sensor");
> +MODULE_LICENSE("GPL");
> +
> --
> 2.8.0-rc4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH] HC-SR04 ultrasonic ranger IIO driver
@ 2016-06-06 18:40 johannes
  2016-06-07  5:42 ` Matt Ranostay
                   ` (3 more replies)
  0 siblings, 4 replies; 16+ messages in thread
From: johannes @ 2016-06-06 18:40 UTC (permalink / raw)
  To: linux-iio; +Cc: Johannes Thoma

From: Johannes Thoma <johannes@johannesthoma.com>

The HC-SR04 is an ultrasonic distance sensor attached to two GPIO
pins. The driver is based on the Industrial I/O (iio) subsystem and is
controlled via configfs and sysfs. It supports an (in theory) unlimited
number of HC-SR04 devices.

A datasheet to the device can be found at:

http://www.micropik.com/PDF/HCSR04.pdf
Signed-off-by: Johannes Thoma <johannes@johannesthoma.com>
---
 MAINTAINERS                               |   7 +
 drivers/iio/Kconfig                       |   1 +
 drivers/iio/Makefile                      |   1 +
 drivers/iio/ultrasonic-distance/Kconfig   |  17 ++
 drivers/iio/ultrasonic-distance/Makefile  |   6 +
 drivers/iio/ultrasonic-distance/hc-sr04.c | 466 ++++++++++++++++++++++++++++++
 6 files changed, 498 insertions(+)
 create mode 100644 drivers/iio/ultrasonic-distance/Kconfig
 create mode 100644 drivers/iio/ultrasonic-distance/Makefile
 create mode 100644 drivers/iio/ultrasonic-distance/hc-sr04.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 7304d2e..3bd640e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5211,6 +5211,13 @@ W:	http://www.kernel.org/pub/linux/kernel/people/fseidel/hdaps/
 S:	Maintained
 F:	drivers/platform/x86/hdaps.c
 
+
+HC-SR04 ULTRASONIC DISTANCE SENSOR DRIVER
+M:	Johannes Thoma <johannes@johannesthoma.com>
+S:	Maintained
+F:	drivers/iio/ultrasonic-distance/hc-sr04.c
+
+
 HDPVR USB VIDEO ENCODER DRIVER
 M:	Hans Verkuil <hverkuil@xs4all.nl>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 505e921..3c82aad 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -82,5 +82,6 @@ source "drivers/iio/potentiometer/Kconfig"
 source "drivers/iio/pressure/Kconfig"
 source "drivers/iio/proximity/Kconfig"
 source "drivers/iio/temperature/Kconfig"
+source "drivers/iio/ultrasonic-distance/Kconfig"
 
 endif # IIO
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index 20f6490..0f1c00c 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -32,3 +32,4 @@ obj-y += pressure/
 obj-y += proximity/
 obj-y += temperature/
 obj-y += trigger/
+obj-y += ultrasonic-distance/
diff --git a/drivers/iio/ultrasonic-distance/Kconfig b/drivers/iio/ultrasonic-distance/Kconfig
new file mode 100644
index 0000000..46e848d
--- /dev/null
+++ b/drivers/iio/ultrasonic-distance/Kconfig
@@ -0,0 +1,17 @@
+#
+# Ultrasonic range sensors
+#
+
+menu "Ultrasonic ranger devices"
+
+config HC_SR04
+	tristate "HC-SR04 ultrasonic distance sensor on GPIO"
+	select IIO_SW_TRIGGER
+	depends on GPIOLIB
+	help
+	  Say Y here if you want to support the HC-SR04 ultrasonic distance
+	  sensor which is attached on two runtime-configurable GPIO pins.
+
+  	  To compile this driver as a module, choose M here: the
+	  module will be called hc-sr04.
+endmenu
diff --git a/drivers/iio/ultrasonic-distance/Makefile b/drivers/iio/ultrasonic-distance/Makefile
new file mode 100644
index 0000000..1f01d50c
--- /dev/null
+++ b/drivers/iio/ultrasonic-distance/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for IIO proximity sensors
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_HC_SR04)		+= hc-sr04.o
diff --git a/drivers/iio/ultrasonic-distance/hc-sr04.c b/drivers/iio/ultrasonic-distance/hc-sr04.c
new file mode 100644
index 0000000..e7da37b
--- /dev/null
+++ b/drivers/iio/ultrasonic-distance/hc-sr04.c
@@ -0,0 +1,466 @@
+/*
+ * hc-sr04.c - Support for HC-SR04 ultrasonic range sensor
+ *
+ * Copyright (C) 2016 Johannes Thoma <johannes@johannesthoma.com>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/* Precise measurements of time delta between sending a trigger signal
+ * to the HC-SR04 distance sensor and receiving the echo signal from
+ * the sensor back. This has to be precise in the usecs range. We
+ * use trigger interrupts to measure the signal, so no busy wait :)
+ *
+ * This supports an (in theory) unlimited number of HC-SR04 devices.
+ * It uses IIO software triggers to interface with userland.
+ *
+ * To configure a device do a
+ *
+ *    mkdir /sys/kernel/config/iio/triggers/hc-sr04/sensor0
+ *
+ * (you need to mount configfs to /sys/kernel/config first unless it isn't
+ * mounted already)
+ *
+ * Then configure the ECHO and TRIG pins (this also accepts symbolic names
+ * configured in the device tree)
+ *
+ *    echo 23 > /config/iio/triggers/hc-sr04/sensor0/trig_pin
+ *    echo 24 > /config/iio/triggers/hc-sr04/sensor0/echo_pin
+ *
+ * Then you can measure distance with:
+ *
+ *    cat /sys/devices/trigger0/measure
+ *
+ * (trigger0 is the device name as reported by
+ *  /config/iio/triggers/hc-sr04/sensor0/dev_name
+ *
+ * This reports the length of the ECHO signal in microseconds, which is
+ * related linearily to the distance measured.
+ *
+ * To convert to centimeters, multiply by 17150 and divide by 1000000 (air)
+ *
+ * DO NOT attach your HC-SR04's echo pin directly to the raspberry, since
+ * it runs with 5V while raspberry expects 3V on the GPIO inputs.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/timekeeping.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/gpio/driver.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/sw_trigger.h>
+
+#define DEFAULT_TIMEOUT 1000
+
+enum hc_sr04_state {
+	DEVICE_IDLE,
+	DEVICE_TRIGGERED,
+	DEVICE_ECHO_RECEIVED
+};
+
+struct hc_sr04 {
+		/* the GPIOs of ECHO and TRIG */
+	struct gpio_desc *trig_desc;
+	struct gpio_desc *echo_desc;
+		/* Used to measure length of ECHO signal */
+	struct timeval time_triggered;
+	struct timeval time_echoed;
+		/* protects against starting multiple measurements */
+	struct mutex measurement_mutex;
+		/* Current state of measurement */
+	enum hc_sr04_state state;
+		/* Used by interrupt to wake measurement routine up */
+	wait_queue_head_t wait_for_echo;
+		/* timeout in ms, fail when no echo received within that time */
+	unsigned long timeout;
+		/* Our IIO interface */
+	struct iio_sw_trigger swt;
+		/* Used to compute device settle time */
+	struct timeval last_measurement;
+};
+
+static inline struct hc_sr04 *to_hc_sr04(struct config_item *item)
+{
+	struct iio_sw_trigger *trig = to_iio_sw_trigger(item);
+
+	return container_of(trig, struct hc_sr04, swt);
+}
+
+static irqreturn_t echo_received_irq(int irq, void *data)
+{
+	struct hc_sr04 *device = (struct hc_sr04 *)data;
+	int val;
+	struct timeval irq_tv;
+
+	do_gettimeofday(&irq_tv);
+
+	if (device->state != DEVICE_TRIGGERED)
+		return IRQ_HANDLED;
+
+	val = gpiod_get_value(device->echo_desc);
+	if (val == 1) {
+		device->time_triggered = irq_tv;
+	} else {
+		device->time_echoed = irq_tv;
+		device->state = DEVICE_ECHO_RECEIVED;
+		wake_up_interruptible(&device->wait_for_echo);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int do_measurement(struct hc_sr04 *device,
+			  long long *usecs_elapsed)
+{
+	long timeout;
+	int irq;
+	int ret;
+	struct timeval now;
+	long long time_since_last_measurement;
+
+	*usecs_elapsed = -1;
+
+	if (!device->echo_desc || !device->trig_desc) {
+		dev_dbg(&device->swt.trigger->dev, "Please configure GPIO pins first.\n");
+		return -EINVAL;
+	}
+	if (!mutex_trylock(&device->measurement_mutex))
+		return -EBUSY;
+
+	do_gettimeofday(&now);
+	if (device->last_measurement.tv_sec || device->last_measurement.tv_usec)
+		time_since_last_measurement =
+	(now.tv_sec - device->last_measurement.tv_sec) * 1000000 +
+	(now.tv_usec - device->last_measurement.tv_usec);
+	else
+		time_since_last_measurement = 60000;
+
+		/* wait 60 ms between measurements.
+		 * now, a while true ; do cat measure ; done should work
+		 */
+
+	if (time_since_last_measurement < 60000 &&
+	    time_since_last_measurement >= 0)
+		msleep(60 - (int)time_since_last_measurement / 1000);
+
+	irq = gpiod_to_irq(device->echo_desc);
+	if (irq < 0) {
+		ret = -EIO;
+		goto out_mutex;
+	}
+
+	ret = request_any_context_irq(irq, echo_received_irq,
+				      IRQF_SHARED | IRQF_TRIGGER_FALLING |
+				      IRQF_TRIGGER_RISING,
+				      "hc_sr04", device);
+
+	if (ret < 0)
+		goto out_mutex;
+
+	gpiod_set_value(device->trig_desc, 1);
+	usleep_range(10, 20);
+	device->state = DEVICE_TRIGGERED;
+	gpiod_set_value(device->trig_desc, 0);
+
+	ret = gpiochip_lock_as_irq(gpiod_to_chip(device->echo_desc),
+				   desc_to_gpio(device->echo_desc));
+	if (ret < 0)
+		goto out_irq;
+
+	timeout = wait_event_interruptible_timeout(
+			device->wait_for_echo,
+			device->state == DEVICE_ECHO_RECEIVED,
+			device->timeout * HZ / 1000);
+
+	device->state = DEVICE_IDLE;
+
+	if (timeout == 0) {
+		ret = -ETIMEDOUT;
+	} else if (timeout < 0) {
+		ret = timeout;
+	} else {
+		*usecs_elapsed =
+	(device->time_echoed.tv_sec - device->time_triggered.tv_sec) * 1000000 +
+	(device->time_echoed.tv_usec - device->time_triggered.tv_usec);
+		ret = 0;
+		do_gettimeofday(&device->last_measurement);
+	}
+	gpiochip_unlock_as_irq(gpiod_to_chip(device->echo_desc),
+			       desc_to_gpio(device->echo_desc));
+out_irq:
+	free_irq(irq, device);
+out_mutex:
+	mutex_unlock(&device->measurement_mutex);
+
+	return ret;
+}
+
+static ssize_t sysfs_do_measurement(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	struct hc_sr04 *sensor = dev_get_drvdata(dev);
+	long long usecs_elapsed;
+	int status;
+
+	status = do_measurement(sensor, &usecs_elapsed);
+
+	if (status < 0)
+		return status;
+
+	return sprintf(buf, "%lld\n", usecs_elapsed);
+}
+
+DEVICE_ATTR(measure, 0444, sysfs_do_measurement, NULL);
+
+static struct attribute *sensor_attrs[] = {
+	&dev_attr_measure.attr,
+	NULL,
+};
+
+static const struct attribute_group sensor_group = {
+	.attrs = sensor_attrs
+};
+
+static const struct attribute_group *sensor_groups[] = {
+	&sensor_group,
+	NULL
+};
+
+static ssize_t configure_pin(struct gpio_desc **desc, struct config_item *item,
+			     const char *buf, size_t len, struct device *dev)
+{
+	int err;
+	int echo;
+
+	if (*desc)
+		gpiod_put(*desc);
+
+	*desc = gpiod_get(dev, buf, GPIOD_ASIS);
+	if (IS_ERR(*desc)) {
+		err = PTR_ERR(*desc);
+		*desc = NULL;
+
+		if (err == -ENOENT) {	/* fallback: use GPIO numbers */
+			err = kstrtoint(buf, 10, &echo);
+			if (err < 0)
+				return -ENOENT;
+			*desc = gpio_to_desc(echo);
+			if (*desc)
+				return len;
+			return -ENOENT;
+		}
+
+		return err;
+	}
+	return len;
+}
+
+static ssize_t hc_sr04_echo_pin_store(struct config_item *item,
+				      const char *buf, size_t len)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+	ssize_t ret;
+	int err;
+
+	ret = configure_pin(&sensor->echo_desc, item, buf, len,
+			    &sensor->swt.trigger->dev);
+
+	if (ret >= 0 && sensor->echo_desc) {
+		err = gpiod_direction_input(sensor->echo_desc);
+		if (err < 0)
+			return err;
+	}
+	return ret;
+}
+
+static ssize_t hc_sr04_echo_pin_show(struct config_item *item,
+				     char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	if (sensor->echo_desc)
+		return sprintf(buf, "%d\n", desc_to_gpio(sensor->echo_desc));
+	return 0;
+}
+
+static ssize_t hc_sr04_trig_pin_store(struct config_item *item,
+				      const char *buf, size_t len)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+	ssize_t ret;
+	int err;
+
+	ret = configure_pin(&sensor->trig_desc, item, buf, len,
+			    &sensor->swt.trigger->dev);
+
+	if (ret >= 0 && sensor->trig_desc) {
+		err = gpiod_direction_output(sensor->trig_desc, 0);
+		if (err >= 0)
+			gpiod_set_value(sensor->trig_desc, 0);
+		else
+			return err;
+	}
+	return ret;
+}
+
+static ssize_t hc_sr04_trig_pin_show(struct config_item *item,
+				     char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	if (sensor->trig_desc)
+		return sprintf(buf, "%d\n", desc_to_gpio(sensor->trig_desc));
+	return 0;
+}
+
+static ssize_t hc_sr04_timeout_store(struct config_item *item,
+				     const char *buf, size_t len)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+	unsigned long t;
+	int ret;
+
+	ret = kstrtol(buf, 10, &t);
+	if (ret < 0)
+		return ret;
+
+	sensor->timeout = t;
+	return len;
+}
+
+static ssize_t hc_sr04_timeout_show(struct config_item *item,
+				    char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	return sprintf(buf, "%ld\n", sensor->timeout);
+}
+
+static ssize_t hc_sr04_dev_name_show(struct config_item *item,
+				     char *buf)
+{
+	struct hc_sr04 *sensor = to_hc_sr04(item);
+
+	return sprintf(buf, "%s", dev_name(&sensor->swt.trigger->dev));
+}
+
+CONFIGFS_ATTR(hc_sr04_, echo_pin);
+CONFIGFS_ATTR(hc_sr04_, trig_pin);
+CONFIGFS_ATTR(hc_sr04_, timeout);
+CONFIGFS_ATTR_RO(hc_sr04_, dev_name);
+
+static struct configfs_attribute *hc_sr04_config_attrs[] = {
+	&hc_sr04_attr_echo_pin,
+	&hc_sr04_attr_trig_pin,
+	&hc_sr04_attr_timeout,
+	&hc_sr04_attr_dev_name,
+	NULL
+};
+
+static struct config_item_type iio_hc_sr04_type = {
+	.ct_owner = THIS_MODULE,
+	.ct_attrs = hc_sr04_config_attrs
+};
+
+static int iio_trig_hc_sr04_set_state(struct iio_trigger *trig, bool state)
+{
+	return 0;
+}
+
+static const struct iio_trigger_ops iio_hc_sr04_trigger_ops = {
+	.owner = THIS_MODULE,
+	.set_trigger_state = iio_trig_hc_sr04_set_state,
+};
+
+static struct iio_sw_trigger *iio_trig_hc_sr04_probe(const char *name)
+{
+	struct hc_sr04 *sensor;
+	int ret;
+
+	sensor = kzalloc(sizeof(*sensor), GFP_KERNEL);
+	if (!sensor)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&sensor->measurement_mutex);
+	init_waitqueue_head(&sensor->wait_for_echo);
+	sensor->timeout = DEFAULT_TIMEOUT;
+
+	sensor->swt.trigger = iio_trigger_alloc("%s", name);
+	if (!sensor->swt.trigger) {
+		ret = -ENOMEM;
+		goto err_free_sensor;
+	}
+	iio_trigger_set_drvdata(sensor->swt.trigger, sensor);
+	sensor->swt.trigger->ops = &iio_hc_sr04_trigger_ops;
+	sensor->swt.trigger->dev.groups = sensor_groups;
+
+	ret = iio_trigger_register(sensor->swt.trigger);
+	if (ret)
+		goto err_free_trigger;
+
+	iio_swt_group_init_type_name(&sensor->swt, name, &iio_hc_sr04_type);
+	return &sensor->swt;
+
+err_free_trigger:
+	iio_trigger_free(sensor->swt.trigger);
+err_free_sensor:
+	kfree(sensor);
+
+	return ERR_PTR(ret);
+}
+
+static int iio_trig_hc_sr04_remove(struct iio_sw_trigger *swt)
+{
+	struct hc_sr04 *rip_sensor;
+
+	rip_sensor = iio_trigger_get_drvdata(swt->trigger);
+
+	iio_trigger_unregister(swt->trigger);
+
+	/* Wait for measurement to be finished. */
+	mutex_lock(&rip_sensor->measurement_mutex);
+
+	iio_trigger_free(swt->trigger);
+	kfree(rip_sensor);
+
+	return 0;
+}
+
+static const struct iio_sw_trigger_ops iio_trig_hc_sr04_ops = {
+	.probe          = iio_trig_hc_sr04_probe,
+	.remove         = iio_trig_hc_sr04_remove,
+};
+
+static struct iio_sw_trigger_type iio_trig_hc_sr04 = {
+	.name = "hc-sr04",
+	.owner = THIS_MODULE,
+	.ops = &iio_trig_hc_sr04_ops,
+};
+
+module_iio_sw_trigger_driver(iio_trig_hc_sr04);
+
+MODULE_AUTHOR("Johannes Thoma");
+MODULE_DESCRIPTION("Distance measurement for the HC-SR04 ultrasonic distance sensor");
+MODULE_LICENSE("GPL");
+
-- 
2.8.0-rc4


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

end of thread, other threads:[~2016-06-11 15:20 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <56F3FFF2.40906@johannesthoma.com>
2016-03-24 15:02 ` [PATCH] misc devices: HC-SRO4 ultrasonic distance driver Johannes Thoma
2016-03-24 15:24   ` Greg KH
2016-05-31 21:05     ` [PATCH] HC-SR04 ultrasonic ranger IIO driver johannes at johannesthoma.com
2016-05-31 21:10       ` Johannes Thoma
2016-05-31 21:35       ` Greg KH
2016-06-01  1:02       ` Valdis.Kletnieks at vt.edu
2016-06-01 14:56       ` Daniel Baluta
2016-03-24 15:52   ` [PATCH] misc devices: HC-SRO4 ultrasonic distance driver Daniel Baluta
2016-03-24 18:15     ` Johannes Thoma
2016-06-06 18:40 [PATCH] HC-SR04 ultrasonic ranger IIO driver johannes
2016-06-07  5:42 ` Matt Ranostay
2016-06-07 11:44 ` Crt Mori
2016-06-07 15:19 ` Lars-Peter Clausen
2016-06-10  5:35   ` Johannes Thoma
2016-06-10  7:50     ` Lars-Peter Clausen
2016-06-11 15:20 ` Jonathan Cameron

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.