[PATCH] misc devices: HC-SRO4 ultrasonic distance driver
Johannes Thoma
johannes at johannesthoma.com
Thu Mar 24 11:02:03 EDT 2016
From 56e8f71c990b92c28a8cb03d859880eab8d06a3d Mon Sep 17 00:00:00 2001
From: Johannes Thoma <johannes at 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 at 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 at johannesthoma.com>
+S: Maintained
+F: drivers/misc/hc-sro4.c
+
HDPVR USB VIDEO ENCODER DRIVER
M: Hans Verkuil <hverkuil at 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
More information about the Kernelnewbies
mailing list