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