From 56e8f71c990b92c28a8cb03d859880eab8d06a3d Mon Sep 17 00:00:00 2001
From: Johannes Thoma <johan...@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 <johan...@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 <johan...@johannesthoma.com>
+S:     Maintained
+F:     drivers/misc/hc-sro4.c
+
  HDPVR USB VIDEO ENCODER DRIVER
  M:    Hans Verkuil <hverk...@xs4all.nl>
  L:    linux-me...@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





_______________________________________________
Kernelnewbies mailing list
Kernelnewbies@kernelnewbies.org
http://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies

Reply via email to