| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * SRF04: ultrasonic sensor for distance measuring by using GPIOs |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (c) 2017 Andreas Klinger <ak@it-klinger.de> |
|---|
| 5 | 6 | * |
|---|
| 6 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 7 | | - * it under the terms of the GNU General Public License as published by |
|---|
| 8 | | - * the Free Software Foundation; either version 2 of the License, or |
|---|
| 9 | | - * (at your option) any later version. |
|---|
| 10 | | - * |
|---|
| 11 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 12 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 13 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 14 | | - * GNU General Public License for more details. |
|---|
| 15 | | - * |
|---|
| 16 | 7 | * For details about the device see: |
|---|
| 17 | | - * http://www.robot-electronics.co.uk/htm/srf04tech.htm |
|---|
| 8 | + * https://www.robot-electronics.co.uk/htm/srf04tech.htm |
|---|
| 18 | 9 | * |
|---|
| 19 | 10 | * the measurement cycle as timing diagram looks like: |
|---|
| 20 | 11 | * |
|---|
| .. | .. |
|---|
| 23 | 14 | * trig: --+ +------------------------------------------------------ |
|---|
| 24 | 15 | * ^ ^ |
|---|
| 25 | 16 | * |<->| |
|---|
| 26 | | - * udelay(10) |
|---|
| 17 | + * udelay(trigger_pulse_us) |
|---|
| 27 | 18 | * |
|---|
| 28 | 19 | * ultra +-+ +-+ +-+ |
|---|
| 29 | 20 | * sonic | | | | | | |
|---|
| .. | .. |
|---|
| 48 | 39 | #include <linux/kernel.h> |
|---|
| 49 | 40 | #include <linux/module.h> |
|---|
| 50 | 41 | #include <linux/of.h> |
|---|
| 42 | +#include <linux/of_device.h> |
|---|
| 51 | 43 | #include <linux/platform_device.h> |
|---|
| 52 | 44 | #include <linux/property.h> |
|---|
| 53 | 45 | #include <linux/sched.h> |
|---|
| 54 | 46 | #include <linux/interrupt.h> |
|---|
| 55 | 47 | #include <linux/delay.h> |
|---|
| 48 | +#include <linux/pm_runtime.h> |
|---|
| 56 | 49 | #include <linux/iio/iio.h> |
|---|
| 57 | 50 | #include <linux/iio/sysfs.h> |
|---|
| 51 | + |
|---|
| 52 | +struct srf04_cfg { |
|---|
| 53 | + unsigned long trigger_pulse_us; |
|---|
| 54 | +}; |
|---|
| 58 | 55 | |
|---|
| 59 | 56 | struct srf04_data { |
|---|
| 60 | 57 | struct device *dev; |
|---|
| 61 | 58 | struct gpio_desc *gpiod_trig; |
|---|
| 62 | 59 | struct gpio_desc *gpiod_echo; |
|---|
| 60 | + struct gpio_desc *gpiod_power; |
|---|
| 63 | 61 | struct mutex lock; |
|---|
| 64 | 62 | int irqnr; |
|---|
| 65 | 63 | ktime_t ts_rising; |
|---|
| 66 | 64 | ktime_t ts_falling; |
|---|
| 67 | 65 | struct completion rising; |
|---|
| 68 | 66 | struct completion falling; |
|---|
| 67 | + const struct srf04_cfg *cfg; |
|---|
| 68 | + int startup_time_ms; |
|---|
| 69 | +}; |
|---|
| 70 | + |
|---|
| 71 | +static const struct srf04_cfg srf04_cfg = { |
|---|
| 72 | + .trigger_pulse_us = 10, |
|---|
| 73 | +}; |
|---|
| 74 | + |
|---|
| 75 | +static const struct srf04_cfg mb_lv_cfg = { |
|---|
| 76 | + .trigger_pulse_us = 20, |
|---|
| 69 | 77 | }; |
|---|
| 70 | 78 | |
|---|
| 71 | 79 | static irqreturn_t srf04_handle_irq(int irq, void *dev_id) |
|---|
| .. | .. |
|---|
| 92 | 100 | u64 dt_ns; |
|---|
| 93 | 101 | u32 time_ns, distance_mm; |
|---|
| 94 | 102 | |
|---|
| 103 | + if (data->gpiod_power) |
|---|
| 104 | + pm_runtime_get_sync(data->dev); |
|---|
| 105 | + |
|---|
| 95 | 106 | /* |
|---|
| 96 | 107 | * just one read-echo-cycle can take place at a time |
|---|
| 97 | 108 | * ==> lock against concurrent reading calls |
|---|
| .. | .. |
|---|
| 102 | 113 | reinit_completion(&data->falling); |
|---|
| 103 | 114 | |
|---|
| 104 | 115 | gpiod_set_value(data->gpiod_trig, 1); |
|---|
| 105 | | - udelay(10); |
|---|
| 116 | + udelay(data->cfg->trigger_pulse_us); |
|---|
| 106 | 117 | gpiod_set_value(data->gpiod_trig, 0); |
|---|
| 118 | + |
|---|
| 119 | + if (data->gpiod_power) { |
|---|
| 120 | + pm_runtime_mark_last_busy(data->dev); |
|---|
| 121 | + pm_runtime_put_autosuspend(data->dev); |
|---|
| 122 | + } |
|---|
| 107 | 123 | |
|---|
| 108 | 124 | /* it should not take more than 20 ms until echo is rising */ |
|---|
| 109 | 125 | ret = wait_for_completion_killable_timeout(&data->rising, HZ/50); |
|---|
| .. | .. |
|---|
| 216 | 232 | }, |
|---|
| 217 | 233 | }; |
|---|
| 218 | 234 | |
|---|
| 235 | +static const struct of_device_id of_srf04_match[] = { |
|---|
| 236 | + { .compatible = "devantech,srf04", .data = &srf04_cfg}, |
|---|
| 237 | + { .compatible = "maxbotix,mb1000", .data = &mb_lv_cfg}, |
|---|
| 238 | + { .compatible = "maxbotix,mb1010", .data = &mb_lv_cfg}, |
|---|
| 239 | + { .compatible = "maxbotix,mb1020", .data = &mb_lv_cfg}, |
|---|
| 240 | + { .compatible = "maxbotix,mb1030", .data = &mb_lv_cfg}, |
|---|
| 241 | + { .compatible = "maxbotix,mb1040", .data = &mb_lv_cfg}, |
|---|
| 242 | + {}, |
|---|
| 243 | +}; |
|---|
| 244 | + |
|---|
| 245 | +MODULE_DEVICE_TABLE(of, of_srf04_match); |
|---|
| 246 | + |
|---|
| 219 | 247 | static int srf04_probe(struct platform_device *pdev) |
|---|
| 220 | 248 | { |
|---|
| 221 | 249 | struct device *dev = &pdev->dev; |
|---|
| .. | .. |
|---|
| 231 | 259 | |
|---|
| 232 | 260 | data = iio_priv(indio_dev); |
|---|
| 233 | 261 | data->dev = dev; |
|---|
| 262 | + data->cfg = of_match_device(of_srf04_match, dev)->data; |
|---|
| 234 | 263 | |
|---|
| 235 | 264 | mutex_init(&data->lock); |
|---|
| 236 | 265 | init_completion(&data->rising); |
|---|
| .. | .. |
|---|
| 248 | 277 | dev_err(dev, "failed to get echo-gpios: err=%ld\n", |
|---|
| 249 | 278 | PTR_ERR(data->gpiod_echo)); |
|---|
| 250 | 279 | return PTR_ERR(data->gpiod_echo); |
|---|
| 280 | + } |
|---|
| 281 | + |
|---|
| 282 | + data->gpiod_power = devm_gpiod_get_optional(dev, "power", |
|---|
| 283 | + GPIOD_OUT_LOW); |
|---|
| 284 | + if (IS_ERR(data->gpiod_power)) { |
|---|
| 285 | + dev_err(dev, "failed to get power-gpios: err=%ld\n", |
|---|
| 286 | + PTR_ERR(data->gpiod_power)); |
|---|
| 287 | + return PTR_ERR(data->gpiod_power); |
|---|
| 288 | + } |
|---|
| 289 | + if (data->gpiod_power) { |
|---|
| 290 | + |
|---|
| 291 | + if (of_property_read_u32(dev->of_node, "startup-time-ms", |
|---|
| 292 | + &data->startup_time_ms)) |
|---|
| 293 | + data->startup_time_ms = 100; |
|---|
| 294 | + dev_dbg(dev, "using power gpio: startup-time-ms=%d\n", |
|---|
| 295 | + data->startup_time_ms); |
|---|
| 251 | 296 | } |
|---|
| 252 | 297 | |
|---|
| 253 | 298 | if (gpiod_cansleep(data->gpiod_echo)) { |
|---|
| .. | .. |
|---|
| 272 | 317 | platform_set_drvdata(pdev, indio_dev); |
|---|
| 273 | 318 | |
|---|
| 274 | 319 | indio_dev->name = "srf04"; |
|---|
| 275 | | - indio_dev->dev.parent = &pdev->dev; |
|---|
| 276 | 320 | indio_dev->info = &srf04_iio_info; |
|---|
| 277 | 321 | indio_dev->modes = INDIO_DIRECT_MODE; |
|---|
| 278 | 322 | indio_dev->channels = srf04_chan_spec; |
|---|
| 279 | 323 | indio_dev->num_channels = ARRAY_SIZE(srf04_chan_spec); |
|---|
| 280 | 324 | |
|---|
| 281 | | - return devm_iio_device_register(dev, indio_dev); |
|---|
| 325 | + ret = iio_device_register(indio_dev); |
|---|
| 326 | + if (ret < 0) { |
|---|
| 327 | + dev_err(data->dev, "iio_device_register: %d\n", ret); |
|---|
| 328 | + return ret; |
|---|
| 329 | + } |
|---|
| 330 | + |
|---|
| 331 | + if (data->gpiod_power) { |
|---|
| 332 | + pm_runtime_set_autosuspend_delay(data->dev, 1000); |
|---|
| 333 | + pm_runtime_use_autosuspend(data->dev); |
|---|
| 334 | + |
|---|
| 335 | + ret = pm_runtime_set_active(data->dev); |
|---|
| 336 | + if (ret) { |
|---|
| 337 | + dev_err(data->dev, "pm_runtime_set_active: %d\n", ret); |
|---|
| 338 | + iio_device_unregister(indio_dev); |
|---|
| 339 | + } |
|---|
| 340 | + |
|---|
| 341 | + pm_runtime_enable(data->dev); |
|---|
| 342 | + pm_runtime_idle(data->dev); |
|---|
| 343 | + } |
|---|
| 344 | + |
|---|
| 345 | + return ret; |
|---|
| 282 | 346 | } |
|---|
| 283 | 347 | |
|---|
| 284 | | -static const struct of_device_id of_srf04_match[] = { |
|---|
| 285 | | - { .compatible = "devantech,srf04", }, |
|---|
| 286 | | - {}, |
|---|
| 287 | | -}; |
|---|
| 348 | +static int srf04_remove(struct platform_device *pdev) |
|---|
| 349 | +{ |
|---|
| 350 | + struct iio_dev *indio_dev = platform_get_drvdata(pdev); |
|---|
| 351 | + struct srf04_data *data = iio_priv(indio_dev); |
|---|
| 288 | 352 | |
|---|
| 289 | | -MODULE_DEVICE_TABLE(of, of_srf04_match); |
|---|
| 353 | + iio_device_unregister(indio_dev); |
|---|
| 354 | + |
|---|
| 355 | + if (data->gpiod_power) { |
|---|
| 356 | + pm_runtime_disable(data->dev); |
|---|
| 357 | + pm_runtime_set_suspended(data->dev); |
|---|
| 358 | + } |
|---|
| 359 | + |
|---|
| 360 | + return 0; |
|---|
| 361 | +} |
|---|
| 362 | + |
|---|
| 363 | +static int __maybe_unused srf04_pm_runtime_suspend(struct device *dev) |
|---|
| 364 | +{ |
|---|
| 365 | + struct platform_device *pdev = container_of(dev, |
|---|
| 366 | + struct platform_device, dev); |
|---|
| 367 | + struct iio_dev *indio_dev = platform_get_drvdata(pdev); |
|---|
| 368 | + struct srf04_data *data = iio_priv(indio_dev); |
|---|
| 369 | + |
|---|
| 370 | + gpiod_set_value(data->gpiod_power, 0); |
|---|
| 371 | + |
|---|
| 372 | + return 0; |
|---|
| 373 | +} |
|---|
| 374 | + |
|---|
| 375 | +static int __maybe_unused srf04_pm_runtime_resume(struct device *dev) |
|---|
| 376 | +{ |
|---|
| 377 | + struct platform_device *pdev = container_of(dev, |
|---|
| 378 | + struct platform_device, dev); |
|---|
| 379 | + struct iio_dev *indio_dev = platform_get_drvdata(pdev); |
|---|
| 380 | + struct srf04_data *data = iio_priv(indio_dev); |
|---|
| 381 | + |
|---|
| 382 | + gpiod_set_value(data->gpiod_power, 1); |
|---|
| 383 | + msleep(data->startup_time_ms); |
|---|
| 384 | + |
|---|
| 385 | + return 0; |
|---|
| 386 | +} |
|---|
| 387 | + |
|---|
| 388 | +static const struct dev_pm_ops srf04_pm_ops = { |
|---|
| 389 | + SET_RUNTIME_PM_OPS(srf04_pm_runtime_suspend, |
|---|
| 390 | + srf04_pm_runtime_resume, NULL) |
|---|
| 391 | +}; |
|---|
| 290 | 392 | |
|---|
| 291 | 393 | static struct platform_driver srf04_driver = { |
|---|
| 292 | 394 | .probe = srf04_probe, |
|---|
| 395 | + .remove = srf04_remove, |
|---|
| 293 | 396 | .driver = { |
|---|
| 294 | 397 | .name = "srf04-gpio", |
|---|
| 295 | 398 | .of_match_table = of_srf04_match, |
|---|
| 399 | + .pm = &srf04_pm_ops, |
|---|
| 296 | 400 | }, |
|---|
| 297 | 401 | }; |
|---|
| 298 | 402 | |
|---|