diff options
author | Matt Ranostay <mranostay@gmail.com> | 2014-12-03 07:53:00 -0500 |
---|---|---|
committer | Jonathan Cameron <jic23@kernel.org> | 2014-03-16 14:00:32 -0400 |
commit | 24ddb0e4bba4e98d3f3a783846789520e796b164 (patch) | |
tree | 71d53df60fe5ebab5455c6526c5bff71414718aa /drivers | |
parent | 43c7ecb1fa6b45633747773f055b8deb1c3e52be (diff) |
iio: Add AS3935 lightning sensor support
AS3935 chipset can detect lightning strikes and reports those back as
events and the estimated distance to the storm.
Signed-off-by: Matt Ranostay <mranostay@gmail.com>
Reviewed-by: Marek Vasut <marex@denx.de>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/iio/Kconfig | 1 | ||||
-rw-r--r-- | drivers/iio/Makefile | 1 | ||||
-rw-r--r-- | drivers/iio/proximity/Kconfig | 19 | ||||
-rw-r--r-- | drivers/iio/proximity/Makefile | 6 | ||||
-rw-r--r-- | drivers/iio/proximity/as3935.c | 456 |
5 files changed, 483 insertions, 0 deletions
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 5dd0e120a504..743485e4d6f8 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig | |||
@@ -74,6 +74,7 @@ if IIO_TRIGGER | |||
74 | source "drivers/iio/trigger/Kconfig" | 74 | source "drivers/iio/trigger/Kconfig" |
75 | endif #IIO_TRIGGER | 75 | endif #IIO_TRIGGER |
76 | source "drivers/iio/pressure/Kconfig" | 76 | source "drivers/iio/pressure/Kconfig" |
77 | source "drivers/iio/proximity/Kconfig" | ||
77 | source "drivers/iio/temperature/Kconfig" | 78 | source "drivers/iio/temperature/Kconfig" |
78 | 79 | ||
79 | endif # IIO | 80 | endif # IIO |
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 887d39090d75..698afc2d17ce 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile | |||
@@ -24,5 +24,6 @@ obj-y += light/ | |||
24 | obj-y += magnetometer/ | 24 | obj-y += magnetometer/ |
25 | obj-y += orientation/ | 25 | obj-y += orientation/ |
26 | obj-y += pressure/ | 26 | obj-y += pressure/ |
27 | obj-y += proximity/ | ||
27 | obj-y += temperature/ | 28 | obj-y += temperature/ |
28 | obj-y += trigger/ | 29 | obj-y += trigger/ |
diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig new file mode 100644 index 000000000000..0c8cdf58f6a1 --- /dev/null +++ b/drivers/iio/proximity/Kconfig | |||
@@ -0,0 +1,19 @@ | |||
1 | # | ||
2 | # Proximity sensors | ||
3 | # | ||
4 | |||
5 | menu "Lightning sensors" | ||
6 | |||
7 | config AS3935 | ||
8 | tristate "AS3935 Franklin lightning sensor" | ||
9 | select IIO_BUFFER | ||
10 | select IIO_TRIGGERED_BUFFER | ||
11 | depends on SPI | ||
12 | help | ||
13 | Say Y here to build SPI interface support for the Austrian | ||
14 | Microsystems AS3935 lightning detection sensor. | ||
15 | |||
16 | To compile this driver as a module, choose M here: the | ||
17 | module will be called as3935 | ||
18 | |||
19 | endmenu | ||
diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile new file mode 100644 index 000000000000..743adee1c8bf --- /dev/null +++ b/drivers/iio/proximity/Makefile | |||
@@ -0,0 +1,6 @@ | |||
1 | # | ||
2 | # Makefile for IIO proximity sensors | ||
3 | # | ||
4 | |||
5 | # When adding new entries keep the list in alphabetical order | ||
6 | obj-$(CONFIG_AS3935) += as3935.o | ||
diff --git a/drivers/iio/proximity/as3935.c b/drivers/iio/proximity/as3935.c new file mode 100644 index 000000000000..bf677bfe8eb2 --- /dev/null +++ b/drivers/iio/proximity/as3935.c | |||
@@ -0,0 +1,456 @@ | |||
1 | /* | ||
2 | * as3935.c - Support for AS3935 Franklin lightning sensor | ||
3 | * | ||
4 | * Copyright (C) 2014 Matt Ranostay <mranostay@gmail.com> | ||
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 | */ | ||
17 | |||
18 | #include <linux/module.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/interrupt.h> | ||
21 | #include <linux/delay.h> | ||
22 | #include <linux/workqueue.h> | ||
23 | #include <linux/mutex.h> | ||
24 | #include <linux/err.h> | ||
25 | #include <linux/irq.h> | ||
26 | #include <linux/gpio.h> | ||
27 | #include <linux/spi/spi.h> | ||
28 | #include <linux/iio/iio.h> | ||
29 | #include <linux/iio/sysfs.h> | ||
30 | #include <linux/iio/trigger.h> | ||
31 | #include <linux/iio/trigger_consumer.h> | ||
32 | #include <linux/iio/buffer.h> | ||
33 | #include <linux/iio/triggered_buffer.h> | ||
34 | #include <linux/of_gpio.h> | ||
35 | |||
36 | |||
37 | #define AS3935_AFE_GAIN 0x00 | ||
38 | #define AS3935_AFE_MASK 0x3F | ||
39 | #define AS3935_AFE_GAIN_MAX 0x1F | ||
40 | #define AS3935_AFE_PWR_BIT BIT(0) | ||
41 | |||
42 | #define AS3935_INT 0x03 | ||
43 | #define AS3935_INT_MASK 0x07 | ||
44 | #define AS3935_EVENT_INT BIT(3) | ||
45 | #define AS3935_NOISE_INT BIT(1) | ||
46 | |||
47 | #define AS3935_DATA 0x07 | ||
48 | #define AS3935_DATA_MASK 0x3F | ||
49 | |||
50 | #define AS3935_TUNE_CAP 0x08 | ||
51 | #define AS3935_CALIBRATE 0x3D | ||
52 | |||
53 | #define AS3935_WRITE_DATA BIT(15) | ||
54 | #define AS3935_READ_DATA BIT(14) | ||
55 | #define AS3935_ADDRESS(x) ((x) << 8) | ||
56 | |||
57 | #define MAX_PF_CAP 120 | ||
58 | #define TUNE_CAP_DIV 8 | ||
59 | |||
60 | struct as3935_state { | ||
61 | struct spi_device *spi; | ||
62 | struct iio_trigger *trig; | ||
63 | struct mutex lock; | ||
64 | struct delayed_work work; | ||
65 | |||
66 | u32 tune_cap; | ||
67 | u8 buf[2] ____cacheline_aligned; | ||
68 | }; | ||
69 | |||
70 | static const struct iio_chan_spec as3935_channels[] = { | ||
71 | { | ||
72 | .type = IIO_PROXIMITY, | ||
73 | .info_mask_separate = | ||
74 | BIT(IIO_CHAN_INFO_RAW) | | ||
75 | BIT(IIO_CHAN_INFO_PROCESSED), | ||
76 | .scan_index = 0, | ||
77 | .scan_type = { | ||
78 | .sign = 'u', | ||
79 | .realbits = 6, | ||
80 | .storagebits = 8, | ||
81 | }, | ||
82 | }, | ||
83 | IIO_CHAN_SOFT_TIMESTAMP(1), | ||
84 | }; | ||
85 | |||
86 | static int as3935_read(struct as3935_state *st, unsigned int reg, int *val) | ||
87 | { | ||
88 | u8 cmd; | ||
89 | int ret; | ||
90 | |||
91 | cmd = (AS3935_READ_DATA | AS3935_ADDRESS(reg)) >> 8; | ||
92 | ret = spi_w8r8(st->spi, cmd); | ||
93 | if (ret < 0) | ||
94 | return ret; | ||
95 | *val = ret; | ||
96 | |||
97 | return 0; | ||
98 | }; | ||
99 | |||
100 | static int as3935_write(struct as3935_state *st, | ||
101 | unsigned int reg, | ||
102 | unsigned int val) | ||
103 | { | ||
104 | u8 *buf = st->buf; | ||
105 | |||
106 | buf[0] = (AS3935_WRITE_DATA | AS3935_ADDRESS(reg)) >> 8; | ||
107 | buf[1] = val; | ||
108 | |||
109 | return spi_write(st->spi, buf, 2); | ||
110 | }; | ||
111 | |||
112 | static ssize_t as3935_sensor_sensitivity_show(struct device *dev, | ||
113 | struct device_attribute *attr, | ||
114 | char *buf) | ||
115 | { | ||
116 | struct as3935_state *st = iio_priv(dev_to_iio_dev(dev)); | ||
117 | int val, ret; | ||
118 | |||
119 | ret = as3935_read(st, AS3935_AFE_GAIN, &val); | ||
120 | if (ret) | ||
121 | return ret; | ||
122 | val = (val & AS3935_AFE_MASK) >> 1; | ||
123 | |||
124 | return sprintf(buf, "%d\n", val); | ||
125 | }; | ||
126 | |||
127 | static ssize_t as3935_sensor_sensitivity_store(struct device *dev, | ||
128 | struct device_attribute *attr, | ||
129 | const char *buf, size_t len) | ||
130 | { | ||
131 | struct as3935_state *st = iio_priv(dev_to_iio_dev(dev)); | ||
132 | unsigned long val; | ||
133 | int ret; | ||
134 | |||
135 | ret = kstrtoul((const char *) buf, 10, &val); | ||
136 | if (ret) | ||
137 | return -EINVAL; | ||
138 | |||
139 | if (val > AS3935_AFE_GAIN_MAX) | ||
140 | return -EINVAL; | ||
141 | |||
142 | as3935_write(st, AS3935_AFE_GAIN, val << 1); | ||
143 | |||
144 | return len; | ||
145 | }; | ||
146 | |||
147 | static IIO_DEVICE_ATTR(sensor_sensitivity, S_IRUGO | S_IWUSR, | ||
148 | as3935_sensor_sensitivity_show, as3935_sensor_sensitivity_store, 0); | ||
149 | |||
150 | |||
151 | static struct attribute *as3935_attributes[] = { | ||
152 | &iio_dev_attr_sensor_sensitivity.dev_attr.attr, | ||
153 | NULL, | ||
154 | }; | ||
155 | |||
156 | static struct attribute_group as3935_attribute_group = { | ||
157 | .attrs = as3935_attributes, | ||
158 | }; | ||
159 | |||
160 | static int as3935_read_raw(struct iio_dev *indio_dev, | ||
161 | struct iio_chan_spec const *chan, | ||
162 | int *val, | ||
163 | int *val2, | ||
164 | long m) | ||
165 | { | ||
166 | struct as3935_state *st = iio_priv(indio_dev); | ||
167 | int ret; | ||
168 | |||
169 | |||
170 | switch (m) { | ||
171 | case IIO_CHAN_INFO_PROCESSED: | ||
172 | case IIO_CHAN_INFO_RAW: | ||
173 | *val2 = 0; | ||
174 | ret = as3935_read(st, AS3935_DATA, val); | ||
175 | if (ret) | ||
176 | return ret; | ||
177 | |||
178 | if (m == IIO_CHAN_INFO_RAW) | ||
179 | return IIO_VAL_INT; | ||
180 | |||
181 | /* storm out of range */ | ||
182 | if (*val == AS3935_DATA_MASK) | ||
183 | return -EINVAL; | ||
184 | *val *= 1000; | ||
185 | break; | ||
186 | default: | ||
187 | return -EINVAL; | ||
188 | } | ||
189 | |||
190 | return IIO_VAL_INT; | ||
191 | } | ||
192 | |||
193 | static const struct iio_info as3935_info = { | ||
194 | .driver_module = THIS_MODULE, | ||
195 | .attrs = &as3935_attribute_group, | ||
196 | .read_raw = &as3935_read_raw, | ||
197 | }; | ||
198 | |||
199 | static irqreturn_t as3935_trigger_handler(int irq, void *private) | ||
200 | { | ||
201 | struct iio_poll_func *pf = private; | ||
202 | struct iio_dev *indio_dev = pf->indio_dev; | ||
203 | struct as3935_state *st = iio_priv(indio_dev); | ||
204 | int val, ret; | ||
205 | |||
206 | ret = as3935_read(st, AS3935_DATA, &val); | ||
207 | if (ret) | ||
208 | goto err_read; | ||
209 | val &= AS3935_DATA_MASK; | ||
210 | val *= 1000; | ||
211 | |||
212 | iio_push_to_buffers_with_timestamp(indio_dev, &val, pf->timestamp); | ||
213 | err_read: | ||
214 | iio_trigger_notify_done(indio_dev->trig); | ||
215 | |||
216 | return IRQ_HANDLED; | ||
217 | }; | ||
218 | |||
219 | static const struct iio_trigger_ops iio_interrupt_trigger_ops = { | ||
220 | .owner = THIS_MODULE, | ||
221 | }; | ||
222 | |||
223 | static void as3935_event_work(struct work_struct *work) | ||
224 | { | ||
225 | struct as3935_state *st; | ||
226 | int val; | ||
227 | |||
228 | st = container_of(work, struct as3935_state, work.work); | ||
229 | |||
230 | as3935_read(st, AS3935_INT, &val); | ||
231 | val &= AS3935_INT_MASK; | ||
232 | |||
233 | switch (val) { | ||
234 | case AS3935_EVENT_INT: | ||
235 | iio_trigger_poll(st->trig, iio_get_time_ns()); | ||
236 | break; | ||
237 | case AS3935_NOISE_INT: | ||
238 | dev_warn(&st->spi->dev, "noise level is too high"); | ||
239 | break; | ||
240 | } | ||
241 | }; | ||
242 | |||
243 | static irqreturn_t as3935_interrupt_handler(int irq, void *private) | ||
244 | { | ||
245 | struct iio_dev *indio_dev = private; | ||
246 | struct as3935_state *st = iio_priv(indio_dev); | ||
247 | |||
248 | /* | ||
249 | * Delay work for >2 milliseconds after an interrupt to allow | ||
250 | * estimated distance to recalculated. | ||
251 | */ | ||
252 | |||
253 | schedule_delayed_work(&st->work, msecs_to_jiffies(3)); | ||
254 | |||
255 | return IRQ_HANDLED; | ||
256 | } | ||
257 | |||
258 | static void calibrate_as3935(struct as3935_state *st) | ||
259 | { | ||
260 | mutex_lock(&st->lock); | ||
261 | |||
262 | /* mask disturber interrupt bit */ | ||
263 | as3935_write(st, AS3935_INT, BIT(5)); | ||
264 | |||
265 | as3935_write(st, AS3935_CALIBRATE, 0x96); | ||
266 | as3935_write(st, AS3935_TUNE_CAP, | ||
267 | BIT(5) | (st->tune_cap / TUNE_CAP_DIV)); | ||
268 | |||
269 | mdelay(2); | ||
270 | as3935_write(st, AS3935_TUNE_CAP, (st->tune_cap / TUNE_CAP_DIV)); | ||
271 | |||
272 | mutex_unlock(&st->lock); | ||
273 | } | ||
274 | |||
275 | #ifdef CONFIG_PM_SLEEP | ||
276 | static int as3935_suspend(struct spi_device *spi, pm_message_t msg) | ||
277 | { | ||
278 | struct iio_dev *indio_dev = spi_get_drvdata(spi); | ||
279 | struct as3935_state *st = iio_priv(indio_dev); | ||
280 | int val, ret; | ||
281 | |||
282 | mutex_lock(&st->lock); | ||
283 | ret = as3935_read(st, AS3935_AFE_GAIN, &val); | ||
284 | if (ret) | ||
285 | goto err_suspend; | ||
286 | val |= AS3935_AFE_PWR_BIT; | ||
287 | |||
288 | ret = as3935_write(st, AS3935_AFE_GAIN, val); | ||
289 | |||
290 | err_suspend: | ||
291 | mutex_unlock(&st->lock); | ||
292 | |||
293 | return ret; | ||
294 | } | ||
295 | |||
296 | static int as3935_resume(struct spi_device *spi) | ||
297 | { | ||
298 | struct iio_dev *indio_dev = spi_get_drvdata(spi); | ||
299 | struct as3935_state *st = iio_priv(indio_dev); | ||
300 | int val, ret; | ||
301 | |||
302 | mutex_lock(&st->lock); | ||
303 | ret = as3935_read(st, AS3935_AFE_GAIN, &val); | ||
304 | if (ret) | ||
305 | goto err_resume; | ||
306 | val &= ~AS3935_AFE_PWR_BIT; | ||
307 | ret = as3935_write(st, AS3935_AFE_GAIN, val); | ||
308 | |||
309 | err_resume: | ||
310 | mutex_unlock(&st->lock); | ||
311 | |||
312 | return ret; | ||
313 | } | ||
314 | #else | ||
315 | #define as3935_suspend NULL | ||
316 | #define as3935_resume NULL | ||
317 | #endif | ||
318 | |||
319 | static int as3935_probe(struct spi_device *spi) | ||
320 | { | ||
321 | struct iio_dev *indio_dev; | ||
322 | struct iio_trigger *trig; | ||
323 | struct as3935_state *st; | ||
324 | struct device_node *np = spi->dev.of_node; | ||
325 | int ret; | ||
326 | |||
327 | /* Be sure lightning event interrupt is specified */ | ||
328 | if (!spi->irq) { | ||
329 | dev_err(&spi->dev, "unable to get event interrupt\n"); | ||
330 | return -EINVAL; | ||
331 | } | ||
332 | |||
333 | indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(st)); | ||
334 | if (!indio_dev) | ||
335 | return -ENOMEM; | ||
336 | |||
337 | st = iio_priv(indio_dev); | ||
338 | st->spi = spi; | ||
339 | st->tune_cap = 0; | ||
340 | |||
341 | spi_set_drvdata(spi, indio_dev); | ||
342 | mutex_init(&st->lock); | ||
343 | INIT_DELAYED_WORK(&st->work, as3935_event_work); | ||
344 | |||
345 | ret = of_property_read_u32(np, | ||
346 | "ams,tuning-capacitor-pf", &st->tune_cap); | ||
347 | if (ret) { | ||
348 | st->tune_cap = 0; | ||
349 | dev_warn(&spi->dev, | ||
350 | "no tuning-capacitor-pf set, defaulting to %d", | ||
351 | st->tune_cap); | ||
352 | } | ||
353 | |||
354 | if (st->tune_cap > MAX_PF_CAP) { | ||
355 | dev_err(&spi->dev, | ||
356 | "wrong tuning-capacitor-pf setting of %d\n", | ||
357 | st->tune_cap); | ||
358 | return -EINVAL; | ||
359 | } | ||
360 | |||
361 | indio_dev->dev.parent = &spi->dev; | ||
362 | indio_dev->name = spi_get_device_id(spi)->name; | ||
363 | indio_dev->channels = as3935_channels; | ||
364 | indio_dev->num_channels = ARRAY_SIZE(as3935_channels); | ||
365 | indio_dev->modes = INDIO_DIRECT_MODE; | ||
366 | indio_dev->info = &as3935_info; | ||
367 | |||
368 | trig = devm_iio_trigger_alloc(&spi->dev, "%s-dev%d", | ||
369 | indio_dev->name, indio_dev->id); | ||
370 | |||
371 | if (!trig) | ||
372 | return -ENOMEM; | ||
373 | |||
374 | st->trig = trig; | ||
375 | trig->dev.parent = indio_dev->dev.parent; | ||
376 | iio_trigger_set_drvdata(trig, indio_dev); | ||
377 | trig->ops = &iio_interrupt_trigger_ops; | ||
378 | |||
379 | ret = iio_trigger_register(trig); | ||
380 | if (ret) { | ||
381 | dev_err(&spi->dev, "failed to register trigger\n"); | ||
382 | return ret; | ||
383 | } | ||
384 | |||
385 | ret = iio_triggered_buffer_setup(indio_dev, NULL, | ||
386 | &as3935_trigger_handler, NULL); | ||
387 | |||
388 | if (ret) { | ||
389 | dev_err(&spi->dev, "cannot setup iio trigger\n"); | ||
390 | goto unregister_trigger; | ||
391 | } | ||
392 | |||
393 | calibrate_as3935(st); | ||
394 | |||
395 | ret = devm_request_irq(&spi->dev, spi->irq, | ||
396 | &as3935_interrupt_handler, | ||
397 | IRQF_TRIGGER_RISING, | ||
398 | dev_name(&spi->dev), | ||
399 | indio_dev); | ||
400 | |||
401 | if (ret) { | ||
402 | dev_err(&spi->dev, "unable to request irq\n"); | ||
403 | goto unregister_buffer; | ||
404 | } | ||
405 | |||
406 | ret = iio_device_register(indio_dev); | ||
407 | if (ret < 0) { | ||
408 | dev_err(&spi->dev, "unable to register device\n"); | ||
409 | goto unregister_buffer; | ||
410 | } | ||
411 | return 0; | ||
412 | |||
413 | unregister_buffer: | ||
414 | iio_triggered_buffer_cleanup(indio_dev); | ||
415 | |||
416 | unregister_trigger: | ||
417 | iio_trigger_unregister(st->trig); | ||
418 | |||
419 | return ret; | ||
420 | }; | ||
421 | |||
422 | static int as3935_remove(struct spi_device *spi) | ||
423 | { | ||
424 | struct iio_dev *indio_dev = spi_get_drvdata(spi); | ||
425 | struct as3935_state *st = iio_priv(indio_dev); | ||
426 | |||
427 | iio_device_unregister(indio_dev); | ||
428 | iio_triggered_buffer_cleanup(indio_dev); | ||
429 | iio_trigger_unregister(st->trig); | ||
430 | |||
431 | return 0; | ||
432 | }; | ||
433 | |||
434 | static const struct spi_device_id as3935_id[] = { | ||
435 | {"as3935", 0}, | ||
436 | {}, | ||
437 | }; | ||
438 | MODULE_DEVICE_TABLE(spi, as3935_id); | ||
439 | |||
440 | static struct spi_driver as3935_driver = { | ||
441 | .driver = { | ||
442 | .name = "as3935", | ||
443 | .owner = THIS_MODULE, | ||
444 | }, | ||
445 | .probe = as3935_probe, | ||
446 | .remove = as3935_remove, | ||
447 | .id_table = as3935_id, | ||
448 | .suspend = as3935_suspend, | ||
449 | .resume = as3935_resume, | ||
450 | }; | ||
451 | module_spi_driver(as3935_driver); | ||
452 | |||
453 | MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>"); | ||
454 | MODULE_DESCRIPTION("AS3935 lightning sensor"); | ||
455 | MODULE_LICENSE("GPL"); | ||
456 | MODULE_ALIAS("spi:as3935"); | ||