18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * PING: ultrasonic sensor for distance measuring by using only one GPIOs 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2019 Andreas Klinger <ak@it-klinger.de> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * For details about the devices see: 88c2ecf20Sopenharmony_ci * http://parallax.com/sites/default/files/downloads/28041-LaserPING-2m-Rangefinder-Guide.pdf 98c2ecf20Sopenharmony_ci * http://parallax.com/sites/default/files/downloads/28015-PING-Documentation-v1.6.pdf 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * the measurement cycle as timing diagram looks like: 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * GPIO ___ ________________________ 148c2ecf20Sopenharmony_ci * ping: __/ \____________/ \________________ 158c2ecf20Sopenharmony_ci * ^ ^ ^ ^ 168c2ecf20Sopenharmony_ci * |<->| interrupt interrupt 178c2ecf20Sopenharmony_ci * udelay(5) (ts_rising) (ts_falling) 188c2ecf20Sopenharmony_ci * |<---------------------->| 198c2ecf20Sopenharmony_ci * . pulse time measured . 208c2ecf20Sopenharmony_ci * . --> one round trip of ultra sonic waves 218c2ecf20Sopenharmony_ci * ultra . . 228c2ecf20Sopenharmony_ci * sonic _ _ _. . 238c2ecf20Sopenharmony_ci * burst: _________/ \_/ \_/ \_________________________________________ 248c2ecf20Sopenharmony_ci * . 258c2ecf20Sopenharmony_ci * ultra . 268c2ecf20Sopenharmony_ci * sonic _ _ _. 278c2ecf20Sopenharmony_ci * echo: __________________________________/ \_/ \_/ \________________ 288c2ecf20Sopenharmony_ci */ 298c2ecf20Sopenharmony_ci#include <linux/err.h> 308c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 318c2ecf20Sopenharmony_ci#include <linux/kernel.h> 328c2ecf20Sopenharmony_ci#include <linux/module.h> 338c2ecf20Sopenharmony_ci#include <linux/of.h> 348c2ecf20Sopenharmony_ci#include <linux/of_device.h> 358c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 368c2ecf20Sopenharmony_ci#include <linux/property.h> 378c2ecf20Sopenharmony_ci#include <linux/sched.h> 388c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 398c2ecf20Sopenharmony_ci#include <linux/delay.h> 408c2ecf20Sopenharmony_ci#include <linux/iio/iio.h> 418c2ecf20Sopenharmony_ci#include <linux/iio/sysfs.h> 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistruct ping_cfg { 448c2ecf20Sopenharmony_ci unsigned long trigger_pulse_us; /* length of trigger pulse */ 458c2ecf20Sopenharmony_ci int laserping_error; /* support error code in */ 468c2ecf20Sopenharmony_ci /* pulse width of laser */ 478c2ecf20Sopenharmony_ci /* ping sensors */ 488c2ecf20Sopenharmony_ci s64 timeout_ns; /* timeout in ns */ 498c2ecf20Sopenharmony_ci}; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistruct ping_data { 528c2ecf20Sopenharmony_ci struct device *dev; 538c2ecf20Sopenharmony_ci struct gpio_desc *gpiod_ping; 548c2ecf20Sopenharmony_ci struct mutex lock; 558c2ecf20Sopenharmony_ci int irqnr; 568c2ecf20Sopenharmony_ci ktime_t ts_rising; 578c2ecf20Sopenharmony_ci ktime_t ts_falling; 588c2ecf20Sopenharmony_ci struct completion rising; 598c2ecf20Sopenharmony_ci struct completion falling; 608c2ecf20Sopenharmony_ci const struct ping_cfg *cfg; 618c2ecf20Sopenharmony_ci}; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistatic const struct ping_cfg pa_ping_cfg = { 648c2ecf20Sopenharmony_ci .trigger_pulse_us = 5, 658c2ecf20Sopenharmony_ci .laserping_error = 0, 668c2ecf20Sopenharmony_ci .timeout_ns = 18500000, /* 3 meters */ 678c2ecf20Sopenharmony_ci}; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic const struct ping_cfg pa_laser_ping_cfg = { 708c2ecf20Sopenharmony_ci .trigger_pulse_us = 5, 718c2ecf20Sopenharmony_ci .laserping_error = 1, 728c2ecf20Sopenharmony_ci .timeout_ns = 15500000, /* 2 meters plus error codes */ 738c2ecf20Sopenharmony_ci}; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic irqreturn_t ping_handle_irq(int irq, void *dev_id) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci struct iio_dev *indio_dev = dev_id; 788c2ecf20Sopenharmony_ci struct ping_data *data = iio_priv(indio_dev); 798c2ecf20Sopenharmony_ci ktime_t now = ktime_get(); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci if (gpiod_get_value(data->gpiod_ping)) { 828c2ecf20Sopenharmony_ci data->ts_rising = now; 838c2ecf20Sopenharmony_ci complete(&data->rising); 848c2ecf20Sopenharmony_ci } else { 858c2ecf20Sopenharmony_ci data->ts_falling = now; 868c2ecf20Sopenharmony_ci complete(&data->falling); 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci return IRQ_HANDLED; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic int ping_read(struct iio_dev *indio_dev) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci struct ping_data *data = iio_priv(indio_dev); 958c2ecf20Sopenharmony_ci int ret; 968c2ecf20Sopenharmony_ci ktime_t ktime_dt; 978c2ecf20Sopenharmony_ci s64 dt_ns; 988c2ecf20Sopenharmony_ci u32 time_ns, distance_mm; 998c2ecf20Sopenharmony_ci struct platform_device *pdev = to_platform_device(data->dev); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci /* 1028c2ecf20Sopenharmony_ci * just one read-echo-cycle can take place at a time 1038c2ecf20Sopenharmony_ci * ==> lock against concurrent reading calls 1048c2ecf20Sopenharmony_ci */ 1058c2ecf20Sopenharmony_ci mutex_lock(&data->lock); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci reinit_completion(&data->rising); 1088c2ecf20Sopenharmony_ci reinit_completion(&data->falling); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci gpiod_set_value(data->gpiod_ping, 1); 1118c2ecf20Sopenharmony_ci udelay(data->cfg->trigger_pulse_us); 1128c2ecf20Sopenharmony_ci gpiod_set_value(data->gpiod_ping, 0); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci ret = gpiod_direction_input(data->gpiod_ping); 1158c2ecf20Sopenharmony_ci if (ret < 0) { 1168c2ecf20Sopenharmony_ci mutex_unlock(&data->lock); 1178c2ecf20Sopenharmony_ci return ret; 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci data->irqnr = gpiod_to_irq(data->gpiod_ping); 1218c2ecf20Sopenharmony_ci if (data->irqnr < 0) { 1228c2ecf20Sopenharmony_ci dev_err(data->dev, "gpiod_to_irq: %d\n", data->irqnr); 1238c2ecf20Sopenharmony_ci mutex_unlock(&data->lock); 1248c2ecf20Sopenharmony_ci return data->irqnr; 1258c2ecf20Sopenharmony_ci } 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci ret = request_irq(data->irqnr, ping_handle_irq, 1288c2ecf20Sopenharmony_ci IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 1298c2ecf20Sopenharmony_ci pdev->name, indio_dev); 1308c2ecf20Sopenharmony_ci if (ret < 0) { 1318c2ecf20Sopenharmony_ci dev_err(data->dev, "request_irq: %d\n", ret); 1328c2ecf20Sopenharmony_ci mutex_unlock(&data->lock); 1338c2ecf20Sopenharmony_ci return ret; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci /* it should not take more than 20 ms until echo is rising */ 1378c2ecf20Sopenharmony_ci ret = wait_for_completion_killable_timeout(&data->rising, HZ/50); 1388c2ecf20Sopenharmony_ci if (ret < 0) 1398c2ecf20Sopenharmony_ci goto err_reset_direction; 1408c2ecf20Sopenharmony_ci else if (ret == 0) { 1418c2ecf20Sopenharmony_ci ret = -ETIMEDOUT; 1428c2ecf20Sopenharmony_ci goto err_reset_direction; 1438c2ecf20Sopenharmony_ci } 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci /* it cannot take more than 50 ms until echo is falling */ 1468c2ecf20Sopenharmony_ci ret = wait_for_completion_killable_timeout(&data->falling, HZ/20); 1478c2ecf20Sopenharmony_ci if (ret < 0) 1488c2ecf20Sopenharmony_ci goto err_reset_direction; 1498c2ecf20Sopenharmony_ci else if (ret == 0) { 1508c2ecf20Sopenharmony_ci ret = -ETIMEDOUT; 1518c2ecf20Sopenharmony_ci goto err_reset_direction; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci ktime_dt = ktime_sub(data->ts_falling, data->ts_rising); 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci free_irq(data->irqnr, indio_dev); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci ret = gpiod_direction_output(data->gpiod_ping, GPIOD_OUT_LOW); 1598c2ecf20Sopenharmony_ci if (ret < 0) { 1608c2ecf20Sopenharmony_ci mutex_unlock(&data->lock); 1618c2ecf20Sopenharmony_ci return ret; 1628c2ecf20Sopenharmony_ci } 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci mutex_unlock(&data->lock); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci dt_ns = ktime_to_ns(ktime_dt); 1678c2ecf20Sopenharmony_ci if (dt_ns > data->cfg->timeout_ns) { 1688c2ecf20Sopenharmony_ci dev_dbg(data->dev, "distance out of range: dt=%lldns\n", 1698c2ecf20Sopenharmony_ci dt_ns); 1708c2ecf20Sopenharmony_ci return -EIO; 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci time_ns = dt_ns; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci /* 1768c2ecf20Sopenharmony_ci * read error code of laser ping sensor and give users chance to 1778c2ecf20Sopenharmony_ci * figure out error by using dynamic debuggging 1788c2ecf20Sopenharmony_ci */ 1798c2ecf20Sopenharmony_ci if (data->cfg->laserping_error) { 1808c2ecf20Sopenharmony_ci if ((time_ns > 12500000) && (time_ns <= 13500000)) { 1818c2ecf20Sopenharmony_ci dev_dbg(data->dev, "target too close or to far\n"); 1828c2ecf20Sopenharmony_ci return -EIO; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci if ((time_ns > 13500000) && (time_ns <= 14500000)) { 1858c2ecf20Sopenharmony_ci dev_dbg(data->dev, "internal sensor error\n"); 1868c2ecf20Sopenharmony_ci return -EIO; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci if ((time_ns > 14500000) && (time_ns <= 15500000)) { 1898c2ecf20Sopenharmony_ci dev_dbg(data->dev, "internal sensor timeout\n"); 1908c2ecf20Sopenharmony_ci return -EIO; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci /* 1958c2ecf20Sopenharmony_ci * the speed as function of the temperature is approximately: 1968c2ecf20Sopenharmony_ci * 1978c2ecf20Sopenharmony_ci * speed = 331,5 + 0,6 * Temp 1988c2ecf20Sopenharmony_ci * with Temp in °C 1998c2ecf20Sopenharmony_ci * and speed in m/s 2008c2ecf20Sopenharmony_ci * 2018c2ecf20Sopenharmony_ci * use 343,5 m/s as ultrasonic speed at 20 °C here in absence of the 2028c2ecf20Sopenharmony_ci * temperature 2038c2ecf20Sopenharmony_ci * 2048c2ecf20Sopenharmony_ci * therefore: 2058c2ecf20Sopenharmony_ci * time 343,5 time * 232 2068c2ecf20Sopenharmony_ci * distance = ------ * ------- = ------------ 2078c2ecf20Sopenharmony_ci * 10^6 2 1350800 2088c2ecf20Sopenharmony_ci * with time in ns 2098c2ecf20Sopenharmony_ci * and distance in mm (one way) 2108c2ecf20Sopenharmony_ci * 2118c2ecf20Sopenharmony_ci * because we limit to 3 meters the multiplication with 232 just 2128c2ecf20Sopenharmony_ci * fits into 32 bit 2138c2ecf20Sopenharmony_ci */ 2148c2ecf20Sopenharmony_ci distance_mm = time_ns * 232 / 1350800; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci return distance_mm; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_cierr_reset_direction: 2198c2ecf20Sopenharmony_ci free_irq(data->irqnr, indio_dev); 2208c2ecf20Sopenharmony_ci mutex_unlock(&data->lock); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci if (gpiod_direction_output(data->gpiod_ping, GPIOD_OUT_LOW)) 2238c2ecf20Sopenharmony_ci dev_dbg(data->dev, "error in gpiod_direction_output\n"); 2248c2ecf20Sopenharmony_ci return ret; 2258c2ecf20Sopenharmony_ci} 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cistatic int ping_read_raw(struct iio_dev *indio_dev, 2288c2ecf20Sopenharmony_ci struct iio_chan_spec const *channel, int *val, 2298c2ecf20Sopenharmony_ci int *val2, long info) 2308c2ecf20Sopenharmony_ci{ 2318c2ecf20Sopenharmony_ci int ret; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci if (channel->type != IIO_DISTANCE) 2348c2ecf20Sopenharmony_ci return -EINVAL; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci switch (info) { 2378c2ecf20Sopenharmony_ci case IIO_CHAN_INFO_RAW: 2388c2ecf20Sopenharmony_ci ret = ping_read(indio_dev); 2398c2ecf20Sopenharmony_ci if (ret < 0) 2408c2ecf20Sopenharmony_ci return ret; 2418c2ecf20Sopenharmony_ci *val = ret; 2428c2ecf20Sopenharmony_ci return IIO_VAL_INT; 2438c2ecf20Sopenharmony_ci case IIO_CHAN_INFO_SCALE: 2448c2ecf20Sopenharmony_ci /* 2458c2ecf20Sopenharmony_ci * maximum resolution in datasheet is 1 mm 2468c2ecf20Sopenharmony_ci * 1 LSB is 1 mm 2478c2ecf20Sopenharmony_ci */ 2488c2ecf20Sopenharmony_ci *val = 0; 2498c2ecf20Sopenharmony_ci *val2 = 1000; 2508c2ecf20Sopenharmony_ci return IIO_VAL_INT_PLUS_MICRO; 2518c2ecf20Sopenharmony_ci default: 2528c2ecf20Sopenharmony_ci return -EINVAL; 2538c2ecf20Sopenharmony_ci } 2548c2ecf20Sopenharmony_ci} 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_cistatic const struct iio_info ping_iio_info = { 2578c2ecf20Sopenharmony_ci .read_raw = ping_read_raw, 2588c2ecf20Sopenharmony_ci}; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cistatic const struct iio_chan_spec ping_chan_spec[] = { 2618c2ecf20Sopenharmony_ci { 2628c2ecf20Sopenharmony_ci .type = IIO_DISTANCE, 2638c2ecf20Sopenharmony_ci .info_mask_separate = 2648c2ecf20Sopenharmony_ci BIT(IIO_CHAN_INFO_RAW) | 2658c2ecf20Sopenharmony_ci BIT(IIO_CHAN_INFO_SCALE), 2668c2ecf20Sopenharmony_ci }, 2678c2ecf20Sopenharmony_ci}; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic const struct of_device_id of_ping_match[] = { 2708c2ecf20Sopenharmony_ci { .compatible = "parallax,ping", .data = &pa_ping_cfg}, 2718c2ecf20Sopenharmony_ci { .compatible = "parallax,laserping", .data = &pa_laser_ping_cfg}, 2728c2ecf20Sopenharmony_ci {}, 2738c2ecf20Sopenharmony_ci}; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, of_ping_match); 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_cistatic int ping_probe(struct platform_device *pdev) 2788c2ecf20Sopenharmony_ci{ 2798c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 2808c2ecf20Sopenharmony_ci struct ping_data *data; 2818c2ecf20Sopenharmony_ci struct iio_dev *indio_dev; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci indio_dev = devm_iio_device_alloc(dev, sizeof(struct ping_data)); 2848c2ecf20Sopenharmony_ci if (!indio_dev) { 2858c2ecf20Sopenharmony_ci dev_err(dev, "failed to allocate IIO device\n"); 2868c2ecf20Sopenharmony_ci return -ENOMEM; 2878c2ecf20Sopenharmony_ci } 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci data = iio_priv(indio_dev); 2908c2ecf20Sopenharmony_ci data->dev = dev; 2918c2ecf20Sopenharmony_ci data->cfg = of_device_get_match_data(dev); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci mutex_init(&data->lock); 2948c2ecf20Sopenharmony_ci init_completion(&data->rising); 2958c2ecf20Sopenharmony_ci init_completion(&data->falling); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci data->gpiod_ping = devm_gpiod_get(dev, "ping", GPIOD_OUT_LOW); 2988c2ecf20Sopenharmony_ci if (IS_ERR(data->gpiod_ping)) { 2998c2ecf20Sopenharmony_ci dev_err(dev, "failed to get ping-gpios: err=%ld\n", 3008c2ecf20Sopenharmony_ci PTR_ERR(data->gpiod_ping)); 3018c2ecf20Sopenharmony_ci return PTR_ERR(data->gpiod_ping); 3028c2ecf20Sopenharmony_ci } 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci if (gpiod_cansleep(data->gpiod_ping)) { 3058c2ecf20Sopenharmony_ci dev_err(data->dev, "cansleep-GPIOs not supported\n"); 3068c2ecf20Sopenharmony_ci return -ENODEV; 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, indio_dev); 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci indio_dev->name = "ping"; 3128c2ecf20Sopenharmony_ci indio_dev->info = &ping_iio_info; 3138c2ecf20Sopenharmony_ci indio_dev->modes = INDIO_DIRECT_MODE; 3148c2ecf20Sopenharmony_ci indio_dev->channels = ping_chan_spec; 3158c2ecf20Sopenharmony_ci indio_dev->num_channels = ARRAY_SIZE(ping_chan_spec); 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci return devm_iio_device_register(dev, indio_dev); 3188c2ecf20Sopenharmony_ci} 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_cistatic struct platform_driver ping_driver = { 3218c2ecf20Sopenharmony_ci .probe = ping_probe, 3228c2ecf20Sopenharmony_ci .driver = { 3238c2ecf20Sopenharmony_ci .name = "ping-gpio", 3248c2ecf20Sopenharmony_ci .of_match_table = of_ping_match, 3258c2ecf20Sopenharmony_ci }, 3268c2ecf20Sopenharmony_ci}; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_cimodule_platform_driver(ping_driver); 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>"); 3318c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PING sensors for distance measuring using one GPIOs"); 3328c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3338c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:ping"); 334