162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PING: ultrasonic sensor for distance measuring by using only one GPIOs 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2019 Andreas Klinger <ak@it-klinger.de> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * For details about the devices see: 862306a36Sopenharmony_ci * http://parallax.com/sites/default/files/downloads/28041-LaserPING-2m-Rangefinder-Guide.pdf 962306a36Sopenharmony_ci * http://parallax.com/sites/default/files/downloads/28015-PING-Documentation-v1.6.pdf 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * the measurement cycle as timing diagram looks like: 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * GPIO ___ ________________________ 1462306a36Sopenharmony_ci * ping: __/ \____________/ \________________ 1562306a36Sopenharmony_ci * ^ ^ ^ ^ 1662306a36Sopenharmony_ci * |<->| interrupt interrupt 1762306a36Sopenharmony_ci * udelay(5) (ts_rising) (ts_falling) 1862306a36Sopenharmony_ci * |<---------------------->| 1962306a36Sopenharmony_ci * . pulse time measured . 2062306a36Sopenharmony_ci * . --> one round trip of ultra sonic waves 2162306a36Sopenharmony_ci * ultra . . 2262306a36Sopenharmony_ci * sonic _ _ _. . 2362306a36Sopenharmony_ci * burst: _________/ \_/ \_/ \_________________________________________ 2462306a36Sopenharmony_ci * . 2562306a36Sopenharmony_ci * ultra . 2662306a36Sopenharmony_ci * sonic _ _ _. 2762306a36Sopenharmony_ci * echo: __________________________________/ \_/ \_/ \________________ 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_ci#include <linux/err.h> 3062306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 3162306a36Sopenharmony_ci#include <linux/kernel.h> 3262306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 3362306a36Sopenharmony_ci#include <linux/module.h> 3462306a36Sopenharmony_ci#include <linux/platform_device.h> 3562306a36Sopenharmony_ci#include <linux/property.h> 3662306a36Sopenharmony_ci#include <linux/sched.h> 3762306a36Sopenharmony_ci#include <linux/interrupt.h> 3862306a36Sopenharmony_ci#include <linux/delay.h> 3962306a36Sopenharmony_ci#include <linux/iio/iio.h> 4062306a36Sopenharmony_ci#include <linux/iio/sysfs.h> 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistruct ping_cfg { 4362306a36Sopenharmony_ci unsigned long trigger_pulse_us; /* length of trigger pulse */ 4462306a36Sopenharmony_ci int laserping_error; /* support error code in */ 4562306a36Sopenharmony_ci /* pulse width of laser */ 4662306a36Sopenharmony_ci /* ping sensors */ 4762306a36Sopenharmony_ci s64 timeout_ns; /* timeout in ns */ 4862306a36Sopenharmony_ci}; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistruct ping_data { 5162306a36Sopenharmony_ci struct device *dev; 5262306a36Sopenharmony_ci struct gpio_desc *gpiod_ping; 5362306a36Sopenharmony_ci struct mutex lock; 5462306a36Sopenharmony_ci int irqnr; 5562306a36Sopenharmony_ci ktime_t ts_rising; 5662306a36Sopenharmony_ci ktime_t ts_falling; 5762306a36Sopenharmony_ci struct completion rising; 5862306a36Sopenharmony_ci struct completion falling; 5962306a36Sopenharmony_ci const struct ping_cfg *cfg; 6062306a36Sopenharmony_ci}; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic const struct ping_cfg pa_ping_cfg = { 6362306a36Sopenharmony_ci .trigger_pulse_us = 5, 6462306a36Sopenharmony_ci .laserping_error = 0, 6562306a36Sopenharmony_ci .timeout_ns = 18500000, /* 3 meters */ 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic const struct ping_cfg pa_laser_ping_cfg = { 6962306a36Sopenharmony_ci .trigger_pulse_us = 5, 7062306a36Sopenharmony_ci .laserping_error = 1, 7162306a36Sopenharmony_ci .timeout_ns = 15500000, /* 2 meters plus error codes */ 7262306a36Sopenharmony_ci}; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic irqreturn_t ping_handle_irq(int irq, void *dev_id) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci struct iio_dev *indio_dev = dev_id; 7762306a36Sopenharmony_ci struct ping_data *data = iio_priv(indio_dev); 7862306a36Sopenharmony_ci ktime_t now = ktime_get(); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (gpiod_get_value(data->gpiod_ping)) { 8162306a36Sopenharmony_ci data->ts_rising = now; 8262306a36Sopenharmony_ci complete(&data->rising); 8362306a36Sopenharmony_ci } else { 8462306a36Sopenharmony_ci data->ts_falling = now; 8562306a36Sopenharmony_ci complete(&data->falling); 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci return IRQ_HANDLED; 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic int ping_read(struct iio_dev *indio_dev) 9262306a36Sopenharmony_ci{ 9362306a36Sopenharmony_ci struct ping_data *data = iio_priv(indio_dev); 9462306a36Sopenharmony_ci int ret; 9562306a36Sopenharmony_ci ktime_t ktime_dt; 9662306a36Sopenharmony_ci s64 dt_ns; 9762306a36Sopenharmony_ci u32 time_ns, distance_mm; 9862306a36Sopenharmony_ci struct platform_device *pdev = to_platform_device(data->dev); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci /* 10162306a36Sopenharmony_ci * just one read-echo-cycle can take place at a time 10262306a36Sopenharmony_ci * ==> lock against concurrent reading calls 10362306a36Sopenharmony_ci */ 10462306a36Sopenharmony_ci mutex_lock(&data->lock); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci reinit_completion(&data->rising); 10762306a36Sopenharmony_ci reinit_completion(&data->falling); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci gpiod_set_value(data->gpiod_ping, 1); 11062306a36Sopenharmony_ci udelay(data->cfg->trigger_pulse_us); 11162306a36Sopenharmony_ci gpiod_set_value(data->gpiod_ping, 0); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci ret = gpiod_direction_input(data->gpiod_ping); 11462306a36Sopenharmony_ci if (ret < 0) { 11562306a36Sopenharmony_ci mutex_unlock(&data->lock); 11662306a36Sopenharmony_ci return ret; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci data->irqnr = gpiod_to_irq(data->gpiod_ping); 12062306a36Sopenharmony_ci if (data->irqnr < 0) { 12162306a36Sopenharmony_ci dev_err(data->dev, "gpiod_to_irq: %d\n", data->irqnr); 12262306a36Sopenharmony_ci mutex_unlock(&data->lock); 12362306a36Sopenharmony_ci return data->irqnr; 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci ret = request_irq(data->irqnr, ping_handle_irq, 12762306a36Sopenharmony_ci IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 12862306a36Sopenharmony_ci pdev->name, indio_dev); 12962306a36Sopenharmony_ci if (ret < 0) { 13062306a36Sopenharmony_ci dev_err(data->dev, "request_irq: %d\n", ret); 13162306a36Sopenharmony_ci mutex_unlock(&data->lock); 13262306a36Sopenharmony_ci return ret; 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci /* it should not take more than 20 ms until echo is rising */ 13662306a36Sopenharmony_ci ret = wait_for_completion_killable_timeout(&data->rising, HZ/50); 13762306a36Sopenharmony_ci if (ret < 0) 13862306a36Sopenharmony_ci goto err_reset_direction; 13962306a36Sopenharmony_ci else if (ret == 0) { 14062306a36Sopenharmony_ci ret = -ETIMEDOUT; 14162306a36Sopenharmony_ci goto err_reset_direction; 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci /* it cannot take more than 50 ms until echo is falling */ 14562306a36Sopenharmony_ci ret = wait_for_completion_killable_timeout(&data->falling, HZ/20); 14662306a36Sopenharmony_ci if (ret < 0) 14762306a36Sopenharmony_ci goto err_reset_direction; 14862306a36Sopenharmony_ci else if (ret == 0) { 14962306a36Sopenharmony_ci ret = -ETIMEDOUT; 15062306a36Sopenharmony_ci goto err_reset_direction; 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci ktime_dt = ktime_sub(data->ts_falling, data->ts_rising); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci free_irq(data->irqnr, indio_dev); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci ret = gpiod_direction_output(data->gpiod_ping, GPIOD_OUT_LOW); 15862306a36Sopenharmony_ci if (ret < 0) { 15962306a36Sopenharmony_ci mutex_unlock(&data->lock); 16062306a36Sopenharmony_ci return ret; 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci mutex_unlock(&data->lock); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci dt_ns = ktime_to_ns(ktime_dt); 16662306a36Sopenharmony_ci if (dt_ns > data->cfg->timeout_ns) { 16762306a36Sopenharmony_ci dev_dbg(data->dev, "distance out of range: dt=%lldns\n", 16862306a36Sopenharmony_ci dt_ns); 16962306a36Sopenharmony_ci return -EIO; 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci time_ns = dt_ns; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci /* 17562306a36Sopenharmony_ci * read error code of laser ping sensor and give users chance to 17662306a36Sopenharmony_ci * figure out error by using dynamic debugging 17762306a36Sopenharmony_ci */ 17862306a36Sopenharmony_ci if (data->cfg->laserping_error) { 17962306a36Sopenharmony_ci if ((time_ns > 12500000) && (time_ns <= 13500000)) { 18062306a36Sopenharmony_ci dev_dbg(data->dev, "target too close or to far\n"); 18162306a36Sopenharmony_ci return -EIO; 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci if ((time_ns > 13500000) && (time_ns <= 14500000)) { 18462306a36Sopenharmony_ci dev_dbg(data->dev, "internal sensor error\n"); 18562306a36Sopenharmony_ci return -EIO; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci if ((time_ns > 14500000) && (time_ns <= 15500000)) { 18862306a36Sopenharmony_ci dev_dbg(data->dev, "internal sensor timeout\n"); 18962306a36Sopenharmony_ci return -EIO; 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci /* 19462306a36Sopenharmony_ci * the speed as function of the temperature is approximately: 19562306a36Sopenharmony_ci * 19662306a36Sopenharmony_ci * speed = 331,5 + 0,6 * Temp 19762306a36Sopenharmony_ci * with Temp in °C 19862306a36Sopenharmony_ci * and speed in m/s 19962306a36Sopenharmony_ci * 20062306a36Sopenharmony_ci * use 343,5 m/s as ultrasonic speed at 20 °C here in absence of the 20162306a36Sopenharmony_ci * temperature 20262306a36Sopenharmony_ci * 20362306a36Sopenharmony_ci * therefore: 20462306a36Sopenharmony_ci * time 343,5 time * 232 20562306a36Sopenharmony_ci * distance = ------ * ------- = ------------ 20662306a36Sopenharmony_ci * 10^6 2 1350800 20762306a36Sopenharmony_ci * with time in ns 20862306a36Sopenharmony_ci * and distance in mm (one way) 20962306a36Sopenharmony_ci * 21062306a36Sopenharmony_ci * because we limit to 3 meters the multiplication with 232 just 21162306a36Sopenharmony_ci * fits into 32 bit 21262306a36Sopenharmony_ci */ 21362306a36Sopenharmony_ci distance_mm = time_ns * 232 / 1350800; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci return distance_mm; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cierr_reset_direction: 21862306a36Sopenharmony_ci free_irq(data->irqnr, indio_dev); 21962306a36Sopenharmony_ci mutex_unlock(&data->lock); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci if (gpiod_direction_output(data->gpiod_ping, GPIOD_OUT_LOW)) 22262306a36Sopenharmony_ci dev_dbg(data->dev, "error in gpiod_direction_output\n"); 22362306a36Sopenharmony_ci return ret; 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cistatic int ping_read_raw(struct iio_dev *indio_dev, 22762306a36Sopenharmony_ci struct iio_chan_spec const *channel, int *val, 22862306a36Sopenharmony_ci int *val2, long info) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci int ret; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if (channel->type != IIO_DISTANCE) 23362306a36Sopenharmony_ci return -EINVAL; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci switch (info) { 23662306a36Sopenharmony_ci case IIO_CHAN_INFO_RAW: 23762306a36Sopenharmony_ci ret = ping_read(indio_dev); 23862306a36Sopenharmony_ci if (ret < 0) 23962306a36Sopenharmony_ci return ret; 24062306a36Sopenharmony_ci *val = ret; 24162306a36Sopenharmony_ci return IIO_VAL_INT; 24262306a36Sopenharmony_ci case IIO_CHAN_INFO_SCALE: 24362306a36Sopenharmony_ci /* 24462306a36Sopenharmony_ci * maximum resolution in datasheet is 1 mm 24562306a36Sopenharmony_ci * 1 LSB is 1 mm 24662306a36Sopenharmony_ci */ 24762306a36Sopenharmony_ci *val = 0; 24862306a36Sopenharmony_ci *val2 = 1000; 24962306a36Sopenharmony_ci return IIO_VAL_INT_PLUS_MICRO; 25062306a36Sopenharmony_ci default: 25162306a36Sopenharmony_ci return -EINVAL; 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_cistatic const struct iio_info ping_iio_info = { 25662306a36Sopenharmony_ci .read_raw = ping_read_raw, 25762306a36Sopenharmony_ci}; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cistatic const struct iio_chan_spec ping_chan_spec[] = { 26062306a36Sopenharmony_ci { 26162306a36Sopenharmony_ci .type = IIO_DISTANCE, 26262306a36Sopenharmony_ci .info_mask_separate = 26362306a36Sopenharmony_ci BIT(IIO_CHAN_INFO_RAW) | 26462306a36Sopenharmony_ci BIT(IIO_CHAN_INFO_SCALE), 26562306a36Sopenharmony_ci }, 26662306a36Sopenharmony_ci}; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_cistatic const struct of_device_id of_ping_match[] = { 26962306a36Sopenharmony_ci { .compatible = "parallax,ping", .data = &pa_ping_cfg }, 27062306a36Sopenharmony_ci { .compatible = "parallax,laserping", .data = &pa_laser_ping_cfg }, 27162306a36Sopenharmony_ci {}, 27262306a36Sopenharmony_ci}; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, of_ping_match); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_cistatic int ping_probe(struct platform_device *pdev) 27762306a36Sopenharmony_ci{ 27862306a36Sopenharmony_ci struct device *dev = &pdev->dev; 27962306a36Sopenharmony_ci struct ping_data *data; 28062306a36Sopenharmony_ci struct iio_dev *indio_dev; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci indio_dev = devm_iio_device_alloc(dev, sizeof(struct ping_data)); 28362306a36Sopenharmony_ci if (!indio_dev) { 28462306a36Sopenharmony_ci dev_err(dev, "failed to allocate IIO device\n"); 28562306a36Sopenharmony_ci return -ENOMEM; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci data = iio_priv(indio_dev); 28962306a36Sopenharmony_ci data->dev = dev; 29062306a36Sopenharmony_ci data->cfg = device_get_match_data(dev); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci mutex_init(&data->lock); 29362306a36Sopenharmony_ci init_completion(&data->rising); 29462306a36Sopenharmony_ci init_completion(&data->falling); 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci data->gpiod_ping = devm_gpiod_get(dev, "ping", GPIOD_OUT_LOW); 29762306a36Sopenharmony_ci if (IS_ERR(data->gpiod_ping)) { 29862306a36Sopenharmony_ci dev_err(dev, "failed to get ping-gpios: err=%ld\n", 29962306a36Sopenharmony_ci PTR_ERR(data->gpiod_ping)); 30062306a36Sopenharmony_ci return PTR_ERR(data->gpiod_ping); 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci if (gpiod_cansleep(data->gpiod_ping)) { 30462306a36Sopenharmony_ci dev_err(data->dev, "cansleep-GPIOs not supported\n"); 30562306a36Sopenharmony_ci return -ENODEV; 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci platform_set_drvdata(pdev, indio_dev); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci indio_dev->name = "ping"; 31162306a36Sopenharmony_ci indio_dev->info = &ping_iio_info; 31262306a36Sopenharmony_ci indio_dev->modes = INDIO_DIRECT_MODE; 31362306a36Sopenharmony_ci indio_dev->channels = ping_chan_spec; 31462306a36Sopenharmony_ci indio_dev->num_channels = ARRAY_SIZE(ping_chan_spec); 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci return devm_iio_device_register(dev, indio_dev); 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_cistatic struct platform_driver ping_driver = { 32062306a36Sopenharmony_ci .probe = ping_probe, 32162306a36Sopenharmony_ci .driver = { 32262306a36Sopenharmony_ci .name = "ping-gpio", 32362306a36Sopenharmony_ci .of_match_table = of_ping_match, 32462306a36Sopenharmony_ci }, 32562306a36Sopenharmony_ci}; 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_cimodule_platform_driver(ping_driver); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ciMODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>"); 33062306a36Sopenharmony_ciMODULE_DESCRIPTION("PING sensors for distance measuring using one GPIOs"); 33162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 33262306a36Sopenharmony_ciMODULE_ALIAS("platform:ping"); 333