From: Johannes Thoma <[email protected]>

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 <[email protected]>
---
 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 <[email protected]>
+S:     Maintained
+F:     drivers/iio/ultrasonic-distance/hc-sr04.c
+
+
 HDPVR USB VIDEO ENCODER DRIVER
 M:     Hans Verkuil <[email protected]>
 L:     [email protected]
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 <[email protected]>
+ *
+ * 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


_______________________________________________
Kernelnewbies mailing list
[email protected]
http://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies

Reply via email to