Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 | // SPDX-License-Identifier: GPL-2.0-or-later /* * SRF04: ultrasonic sensor for distance measuring by using GPIOs * * Copyright (c) 2017 Andreas Klinger <ak@it-klinger.de> * * For details about the device see: * https://www.robot-electronics.co.uk/htm/srf04tech.htm * * the measurement cycle as timing diagram looks like: * * +---+ * GPIO | | * trig: --+ +------------------------------------------------------ * ^ ^ * |<->| * udelay(trigger_pulse_us) * * ultra +-+ +-+ +-+ * sonic | | | | | | * burst: ---------+ +-+ +-+ +----------------------------------------- * . * ultra . +-+ +-+ +-+ * sonic . | | | | | | * echo: ----------------------------------+ +-+ +-+ +---------------- * . . * +------------------------+ * GPIO | | * echo: -------------------+ +--------------- * ^ ^ * interrupt interrupt * (ts_rising) (ts_falling) * |<---------------------->| * pulse time measured * --> one round trip of ultra sonic waves */ #include <linux/err.h> #include <linux/gpio/consumer.h> #include <linux/kernel.h> #include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/sched.h> #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/pm_runtime.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> struct srf04_cfg { unsigned long trigger_pulse_us; }; struct srf04_data { struct device *dev; struct gpio_desc *gpiod_trig; struct gpio_desc *gpiod_echo; struct gpio_desc *gpiod_power; struct mutex lock; int irqnr; ktime_t ts_rising; ktime_t ts_falling; struct completion rising; struct completion falling; const struct srf04_cfg *cfg; int startup_time_ms; }; static const struct srf04_cfg srf04_cfg = { .trigger_pulse_us = 10, }; static const struct srf04_cfg mb_lv_cfg = { .trigger_pulse_us = 20, }; static irqreturn_t srf04_handle_irq(int irq, void *dev_id) { struct iio_dev *indio_dev = dev_id; struct srf04_data *data = iio_priv(indio_dev); ktime_t now = ktime_get(); if (gpiod_get_value(data->gpiod_echo)) { data->ts_rising = now; complete(&data->rising); } else { data->ts_falling = now; complete(&data->falling); } return IRQ_HANDLED; } static int srf04_read(struct srf04_data *data) { int ret; ktime_t ktime_dt; u64 dt_ns; u32 time_ns, distance_mm; if (data->gpiod_power) { ret = pm_runtime_resume_and_get(data->dev); if (ret < 0) return ret; } /* * just one read-echo-cycle can take place at a time * ==> lock against concurrent reading calls */ mutex_lock(&data->lock); reinit_completion(&data->rising); reinit_completion(&data->falling); gpiod_set_value(data->gpiod_trig, 1); udelay(data->cfg->trigger_pulse_us); gpiod_set_value(data->gpiod_trig, 0); if (data->gpiod_power) { pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); } /* it should not take more than 20 ms until echo is rising */ ret = wait_for_completion_killable_timeout(&data->rising, HZ/50); if (ret < 0) { mutex_unlock(&data->lock); return ret; } else if (ret == 0) { mutex_unlock(&data->lock); return -ETIMEDOUT; } /* it cannot take more than 50 ms until echo is falling */ ret = wait_for_completion_killable_timeout(&data->falling, HZ/20); if (ret < 0) { mutex_unlock(&data->lock); return ret; } else if (ret == 0) { mutex_unlock(&data->lock); return -ETIMEDOUT; } ktime_dt = ktime_sub(data->ts_falling, data->ts_rising); mutex_unlock(&data->lock); dt_ns = ktime_to_ns(ktime_dt); /* * measuring more than 6,45 meters is beyond the capabilities of * the supported sensors * ==> filter out invalid results for not measuring echos of * another us sensor * * formula: * distance 6,45 * 2 m * time = ---------- = ------------ = 40438871 ns * speed 319 m/s * * using a minimum speed at -20 °C of 319 m/s */ if (dt_ns > 40438871) return -EIO; time_ns = dt_ns; /* * the speed as function of the temperature is approximately: * * speed = 331,5 + 0,6 * Temp * with Temp in °C * and speed in m/s * * use 343,5 m/s as ultrasonic speed at 20 °C here in absence of the * temperature * * therefore: * time 343,5 time * 106 * distance = ------ * ------- = ------------ * 10^6 2 617176 * with time in ns * and distance in mm (one way) * * because we limit to 6,45 meters the multiplication with 106 just * fits into 32 bit */ distance_mm = time_ns * 106 / 617176; return distance_mm; } static int srf04_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *channel, int *val, int *val2, long info) { struct srf04_data *data = iio_priv(indio_dev); int ret; if (channel->type != IIO_DISTANCE) return -EINVAL; switch (info) { case IIO_CHAN_INFO_RAW: ret = srf04_read(data); if (ret < 0) return ret; *val = ret; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: /* * theoretical maximum resolution is 3 mm * 1 LSB is 1 mm */ *val = 0; *val2 = 1000; return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } } static const struct iio_info srf04_iio_info = { .read_raw = srf04_read_raw, }; static const struct iio_chan_spec srf04_chan_spec[] = { { .type = IIO_DISTANCE, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), }, }; static const struct of_device_id of_srf04_match[] = { { .compatible = "devantech,srf04", .data = &srf04_cfg }, { .compatible = "maxbotix,mb1000", .data = &mb_lv_cfg }, { .compatible = "maxbotix,mb1010", .data = &mb_lv_cfg }, { .compatible = "maxbotix,mb1020", .data = &mb_lv_cfg }, { .compatible = "maxbotix,mb1030", .data = &mb_lv_cfg }, { .compatible = "maxbotix,mb1040", .data = &mb_lv_cfg }, {}, }; MODULE_DEVICE_TABLE(of, of_srf04_match); static int srf04_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct srf04_data *data; struct iio_dev *indio_dev; int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(struct srf04_data)); if (!indio_dev) { dev_err(dev, "failed to allocate IIO device\n"); return -ENOMEM; } data = iio_priv(indio_dev); data->dev = dev; data->cfg = device_get_match_data(dev); mutex_init(&data->lock); init_completion(&data->rising); init_completion(&data->falling); data->gpiod_trig = devm_gpiod_get(dev, "trig", GPIOD_OUT_LOW); if (IS_ERR(data->gpiod_trig)) { dev_err(dev, "failed to get trig-gpios: err=%ld\n", PTR_ERR(data->gpiod_trig)); return PTR_ERR(data->gpiod_trig); } data->gpiod_echo = devm_gpiod_get(dev, "echo", GPIOD_IN); if (IS_ERR(data->gpiod_echo)) { dev_err(dev, "failed to get echo-gpios: err=%ld\n", PTR_ERR(data->gpiod_echo)); return PTR_ERR(data->gpiod_echo); } data->gpiod_power = devm_gpiod_get_optional(dev, "power", GPIOD_OUT_LOW); if (IS_ERR(data->gpiod_power)) { dev_err(dev, "failed to get power-gpios: err=%ld\n", PTR_ERR(data->gpiod_power)); return PTR_ERR(data->gpiod_power); } if (data->gpiod_power) { data->startup_time_ms = 100; device_property_read_u32(dev, "startup-time-ms", &data->startup_time_ms); dev_dbg(dev, "using power gpio: startup-time-ms=%d\n", data->startup_time_ms); } if (gpiod_cansleep(data->gpiod_echo)) { dev_err(data->dev, "cansleep-GPIOs not supported\n"); return -ENODEV; } data->irqnr = gpiod_to_irq(data->gpiod_echo); if (data->irqnr < 0) { dev_err(data->dev, "gpiod_to_irq: %d\n", data->irqnr); return data->irqnr; } ret = devm_request_irq(dev, data->irqnr, srf04_handle_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, pdev->name, indio_dev); if (ret < 0) { dev_err(data->dev, "request_irq: %d\n", ret); return ret; } platform_set_drvdata(pdev, indio_dev); indio_dev->name = "srf04"; indio_dev->info = &srf04_iio_info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = srf04_chan_spec; indio_dev->num_channels = ARRAY_SIZE(srf04_chan_spec); ret = iio_device_register(indio_dev); if (ret < 0) { dev_err(data->dev, "iio_device_register: %d\n", ret); return ret; } if (data->gpiod_power) { pm_runtime_set_autosuspend_delay(data->dev, 1000); pm_runtime_use_autosuspend(data->dev); ret = pm_runtime_set_active(data->dev); if (ret) { dev_err(data->dev, "pm_runtime_set_active: %d\n", ret); iio_device_unregister(indio_dev); } pm_runtime_enable(data->dev); pm_runtime_idle(data->dev); } return ret; } static void srf04_remove(struct platform_device *pdev) { struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct srf04_data *data = iio_priv(indio_dev); iio_device_unregister(indio_dev); if (data->gpiod_power) { pm_runtime_disable(data->dev); pm_runtime_set_suspended(data->dev); } } static int srf04_pm_runtime_suspend(struct device *dev) { struct platform_device *pdev = container_of(dev, struct platform_device, dev); struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct srf04_data *data = iio_priv(indio_dev); gpiod_set_value(data->gpiod_power, 0); return 0; } static int srf04_pm_runtime_resume(struct device *dev) { struct platform_device *pdev = container_of(dev, struct platform_device, dev); struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct srf04_data *data = iio_priv(indio_dev); gpiod_set_value(data->gpiod_power, 1); msleep(data->startup_time_ms); return 0; } static const struct dev_pm_ops srf04_pm_ops = { RUNTIME_PM_OPS(srf04_pm_runtime_suspend, srf04_pm_runtime_resume, NULL) }; static struct platform_driver srf04_driver = { .probe = srf04_probe, .remove_new = srf04_remove, .driver = { .name = "srf04-gpio", .of_match_table = of_srf04_match, .pm = pm_ptr(&srf04_pm_ops), }, }; module_platform_driver(srf04_driver); MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>"); MODULE_DESCRIPTION("SRF04 ultrasonic sensor for distance measuring using GPIOs"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:srf04"); |