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