]>
Commit | Line | Data |
---|---|---|
3c034cce | 1 | /* |
2 | * Analog Devices ADIS16250/ADIS16255 Low Power Gyroscope | |
3 | * | |
4 | * Written by: Matthias Brugger <m_brugger@web.de> | |
5 | * | |
6 | * Copyright (C) 2010 Fraunhofer Institute for Integrated Circuits | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the | |
20 | * Free Software Foundation, Inc., | |
21 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
22 | */ | |
23 | ||
e390b07b MB |
24 | /* |
25 | * The driver just has a bare interface to the sysfs (sample rate in Hz, | |
26 | * orientation (x, y, z) and gyroscope data in °/sec. | |
27 | * | |
28 | * It should be added to iio subsystem when this has left staging. | |
29 | * | |
30 | */ | |
31 | ||
3c034cce | 32 | #include <linux/init.h> |
33 | #include <linux/module.h> | |
34 | #include <linux/device.h> | |
35 | #include <linux/list.h> | |
36 | #include <linux/errno.h> | |
37 | #include <linux/mutex.h> | |
38 | #include <linux/slab.h> | |
39 | ||
40 | #include <linux/interrupt.h> | |
41 | #include <linux/sysfs.h> | |
42 | #include <linux/stat.h> | |
43 | #include <linux/delay.h> | |
44 | ||
45 | #include <linux/gpio.h> | |
46 | ||
47 | #include <linux/spi/spi.h> | |
48 | #include <linux/workqueue.h> | |
49 | ||
50 | #include "adis16255.h" | |
51 | ||
52 | #define ADIS_STATUS 0x3d | |
53 | #define ADIS_SMPL_PRD_MSB 0x37 | |
54 | #define ADIS_SMPL_PRD_LSB 0x36 | |
55 | #define ADIS_MSC_CTRL_MSB 0x35 | |
56 | #define ADIS_MSC_CTRL_LSB 0x34 | |
57 | #define ADIS_GPIO_CTRL 0x33 | |
58 | #define ADIS_ALM_SMPL1 0x25 | |
59 | #define ADIS_ALM_MAG1 0x21 | |
60 | #define ADIS_GYRO_SCALE 0x17 | |
61 | #define ADIS_GYRO_OUT 0x05 | |
62 | #define ADIS_SUPPLY_OUT 0x03 | |
63 | #define ADIS_ENDURANCE 0x01 | |
64 | ||
65 | /* | |
66 | * data structure for every sensor | |
67 | * | |
68 | * @dev: Driver model representation of the device. | |
69 | * @spi: Pointer to the spi device which will manage i/o to spi bus. | |
70 | * @data: Last read data from device. | |
71 | * @irq_adis: GPIO Number of IRQ signal | |
72 | * @irq: irq line manage by kernel | |
e390b07b | 73 | * @negative: indicates if sensor is upside down (negative == 1) |
3c034cce | 74 | * @direction: indicates axis (x, y, z) the sensor is meassuring |
75 | */ | |
76 | struct spi_adis16255_data { | |
77 | struct device dev; | |
78 | struct spi_device *spi; | |
79 | s16 data; | |
3c034cce | 80 | int irq; |
81 | u8 negative; | |
82 | char direction; | |
83 | }; | |
84 | ||
85 | /*-------------------------------------------------------------------------*/ | |
86 | ||
87 | static int spi_adis16255_read_data(struct spi_adis16255_data *spiadis, | |
88 | u8 adr, | |
89 | u8 *rbuf) | |
90 | { | |
e390b07b | 91 | struct spi_device *spi = spiadis->spi; |
3c034cce | 92 | struct spi_message msg; |
93 | struct spi_transfer xfer1, xfer2; | |
94 | u8 *buf, *rx; | |
95 | int ret; | |
96 | ||
e390b07b MB |
97 | buf = kzalloc(4, GFP_KERNEL); |
98 | if (buf == NULL) | |
3c034cce | 99 | return -ENOMEM; |
100 | ||
e390b07b MB |
101 | rx = kzalloc(4, GFP_KERNEL); |
102 | if (rx == NULL) { | |
103 | ret = -ENOMEM; | |
3c034cce | 104 | goto err_buf; |
105 | } | |
106 | ||
e390b07b | 107 | buf[0] = adr; |
3c034cce | 108 | |
109 | spi_message_init(&msg); | |
110 | memset(&xfer1, 0, sizeof(xfer1)); | |
111 | memset(&xfer2, 0, sizeof(xfer2)); | |
112 | ||
e390b07b MB |
113 | xfer1.tx_buf = buf; |
114 | xfer1.rx_buf = buf + 2; | |
115 | xfer1.len = 2; | |
116 | xfer1.delay_usecs = 9; | |
3c034cce | 117 | |
e390b07b MB |
118 | xfer2.tx_buf = rx + 2; |
119 | xfer2.rx_buf = rx; | |
120 | xfer2.len = 2; | |
3c034cce | 121 | |
122 | spi_message_add_tail(&xfer1, &msg); | |
123 | spi_message_add_tail(&xfer2, &msg); | |
124 | ||
e390b07b MB |
125 | ret = spi_sync(spi, &msg); |
126 | if (ret == 0) { | |
127 | rbuf[0] = rx[0]; | |
128 | rbuf[1] = rx[1]; | |
3c034cce | 129 | } |
130 | ||
131 | kfree(rx); | |
132 | err_buf: | |
133 | kfree(buf); | |
134 | ||
135 | return ret; | |
136 | } | |
137 | ||
138 | static int spi_adis16255_write_data(struct spi_adis16255_data *spiadis, | |
139 | u8 adr1, | |
140 | u8 adr2, | |
141 | u8 *wbuf) | |
142 | { | |
e390b07b | 143 | struct spi_device *spi = spiadis->spi; |
3c034cce | 144 | struct spi_message msg; |
145 | struct spi_transfer xfer1, xfer2; | |
146 | u8 *buf, *rx; | |
147 | int ret; | |
148 | ||
e390b07b MB |
149 | buf = kmalloc(4, GFP_KERNEL); |
150 | if (buf == NULL) | |
3c034cce | 151 | return -ENOMEM; |
152 | ||
e390b07b MB |
153 | rx = kzalloc(4, GFP_KERNEL); |
154 | if (rx == NULL) { | |
155 | ret = -ENOMEM; | |
3c034cce | 156 | goto err_buf; |
157 | } | |
158 | ||
159 | spi_message_init(&msg); | |
160 | memset(&xfer1, 0, sizeof(xfer1)); | |
161 | memset(&xfer2, 0, sizeof(xfer2)); | |
162 | ||
e390b07b MB |
163 | buf[0] = adr1 | 0x80; |
164 | buf[1] = *wbuf; | |
3c034cce | 165 | |
e390b07b MB |
166 | buf[2] = adr2 | 0x80; |
167 | buf[3] = *(wbuf + 1); | |
3c034cce | 168 | |
e390b07b MB |
169 | xfer1.tx_buf = buf; |
170 | xfer1.rx_buf = rx; | |
171 | xfer1.len = 2; | |
172 | xfer1.delay_usecs = 9; | |
3c034cce | 173 | |
e390b07b MB |
174 | xfer2.tx_buf = buf+2; |
175 | xfer2.rx_buf = rx+2; | |
176 | xfer2.len = 2; | |
3c034cce | 177 | |
178 | spi_message_add_tail(&xfer1, &msg); | |
179 | spi_message_add_tail(&xfer2, &msg); | |
180 | ||
e390b07b MB |
181 | ret = spi_sync(spi, &msg); |
182 | if (ret != 0) | |
183 | dev_warn(&spi->dev, "write data to %#x %#x failed\n", | |
3c034cce | 184 | buf[0], buf[2]); |
185 | ||
186 | kfree(rx); | |
187 | err_buf: | |
188 | kfree(buf); | |
189 | return ret; | |
190 | } | |
191 | ||
192 | /*-------------------------------------------------------------------------*/ | |
193 | ||
194 | static irqreturn_t adis_irq_thread(int irq, void *dev_id) | |
195 | { | |
e390b07b | 196 | struct spi_adis16255_data *spiadis = dev_id; |
3c034cce | 197 | int status; |
e390b07b MB |
198 | u16 value = 0; |
199 | ||
200 | status = spi_adis16255_read_data(spiadis, ADIS_GYRO_OUT, (u8 *)&value); | |
201 | if (status != 0) { | |
3c034cce | 202 | dev_warn(&spiadis->spi->dev, "SPI FAILED\n"); |
e390b07b | 203 | goto exit; |
3c034cce | 204 | } |
205 | ||
e390b07b MB |
206 | /* perform on new data only... */ |
207 | if (value & 0x8000) { | |
208 | /* delete error and new data bit */ | |
209 | value = value & 0x3fff; | |
210 | /* set negative value */ | |
211 | if (value & 0x2000) | |
212 | value = value | 0xe000; | |
213 | ||
214 | if (likely(spiadis->negative)) | |
215 | value = -value; | |
216 | ||
217 | spiadis->data = (s16) value; | |
218 | } | |
219 | ||
220 | exit: | |
3c034cce | 221 | return IRQ_HANDLED; |
222 | } | |
223 | ||
224 | /*-------------------------------------------------------------------------*/ | |
225 | ||
226 | ssize_t adis16255_show_data(struct device *device, | |
227 | struct device_attribute *da, | |
228 | char *buf) | |
229 | { | |
e390b07b | 230 | struct spi_adis16255_data *spiadis = dev_get_drvdata(device); |
3c034cce | 231 | return snprintf(buf, PAGE_SIZE, "%d\n", spiadis->data); |
232 | } | |
233 | DEVICE_ATTR(data, S_IRUGO , adis16255_show_data, NULL); | |
234 | ||
235 | ssize_t adis16255_show_direction(struct device *device, | |
236 | struct device_attribute *da, | |
237 | char *buf) | |
238 | { | |
e390b07b | 239 | struct spi_adis16255_data *spiadis = dev_get_drvdata(device); |
3c034cce | 240 | return snprintf(buf, PAGE_SIZE, "%c\n", spiadis->direction); |
241 | } | |
242 | DEVICE_ATTR(direction, S_IRUGO , adis16255_show_direction, NULL); | |
243 | ||
e390b07b MB |
244 | ssize_t adis16255_show_sample_rate(struct device *device, |
245 | struct device_attribute *da, | |
246 | char *buf) | |
247 | { | |
248 | struct spi_adis16255_data *spiadis = dev_get_drvdata(device); | |
249 | int status = 0; | |
250 | u16 value = 0; | |
251 | int ts = 0; | |
252 | ||
253 | status = spi_adis16255_read_data(spiadis, ADIS_SMPL_PRD_MSB, | |
254 | (u8 *)&value); | |
255 | if (status != 0) | |
256 | return -EINVAL; | |
257 | ||
258 | if (value & 0x80) { | |
259 | /* timebase = 60.54 ms */ | |
260 | ts = 60540 * ((0x7f & value) + 1); | |
261 | } else { | |
262 | /* timebase = 1.953 ms */ | |
263 | ts = 1953 * ((0x7f & value) + 1); | |
264 | } | |
265 | ||
266 | return snprintf(buf, PAGE_SIZE, "%d\n", (1000*1000)/ts); | |
267 | } | |
268 | DEVICE_ATTR(sample_rate, S_IRUGO , adis16255_show_sample_rate, NULL); | |
269 | ||
270 | static struct attribute *adis16255_attributes[] = { | |
3c034cce | 271 | &dev_attr_data.attr, |
272 | &dev_attr_direction.attr, | |
e390b07b | 273 | &dev_attr_sample_rate.attr, |
3c034cce | 274 | NULL |
275 | }; | |
276 | ||
e390b07b MB |
277 | static const struct attribute_group adis16255_attr_group = { |
278 | .attrs = adis16255_attributes, | |
3c034cce | 279 | }; |
280 | ||
281 | /*-------------------------------------------------------------------------*/ | |
282 | ||
e390b07b MB |
283 | static int spi_adis16255_shutdown(struct spi_adis16255_data *spiadis) |
284 | { | |
285 | u16 value = 0; | |
286 | /* turn sensor off */ | |
287 | spi_adis16255_write_data(spiadis, | |
288 | ADIS_SMPL_PRD_MSB, ADIS_SMPL_PRD_LSB, | |
289 | (u8 *)&value); | |
290 | spi_adis16255_write_data(spiadis, | |
291 | ADIS_MSC_CTRL_MSB, ADIS_MSC_CTRL_LSB, | |
292 | (u8 *)&value); | |
293 | return 0; | |
294 | } | |
295 | ||
296 | static int spi_adis16255_bringup(struct spi_adis16255_data *spiadis) | |
3c034cce | 297 | { |
e390b07b MB |
298 | int status = 0; |
299 | u16 value = 0; | |
300 | ||
301 | status = spi_adis16255_read_data(spiadis, ADIS_GYRO_SCALE, | |
302 | (u8 *)&value); | |
303 | if (status != 0) | |
304 | goto err; | |
305 | if (value != 0x0800) { | |
306 | dev_warn(&spiadis->spi->dev, "Scale factor is none default" | |
307 | "value (%.4x)\n", value); | |
308 | } | |
309 | ||
310 | /* timebase = 1.953 ms, Ns = 0 -> 512 Hz sample rate */ | |
311 | value = 0x0001; | |
312 | status = spi_adis16255_write_data(spiadis, | |
313 | ADIS_SMPL_PRD_MSB, ADIS_SMPL_PRD_LSB, | |
314 | (u8 *)&value); | |
315 | if (status != 0) | |
316 | goto err; | |
317 | ||
318 | /* start internal self-test */ | |
319 | value = 0x0400; | |
320 | status = spi_adis16255_write_data(spiadis, | |
321 | ADIS_MSC_CTRL_MSB, ADIS_MSC_CTRL_LSB, | |
322 | (u8 *)&value); | |
323 | if (status != 0) | |
324 | goto err; | |
325 | ||
326 | /* wait 35 ms to finish self-test */ | |
327 | msleep(35); | |
328 | ||
329 | value = 0x0000; | |
330 | status = spi_adis16255_read_data(spiadis, ADIS_STATUS, | |
331 | (u8 *)&value); | |
332 | if (status != 0) | |
333 | goto err; | |
334 | ||
335 | if (value & 0x23) { | |
336 | if (value & 0x20) { | |
337 | dev_warn(&spiadis->spi->dev, "self-test error\n"); | |
338 | status = -ENODEV; | |
339 | goto err; | |
340 | } else if (value & 0x3) { | |
341 | dev_warn(&spiadis->spi->dev, "Sensor voltage" | |
342 | "out of range.\n"); | |
343 | status = -ENODEV; | |
344 | goto err; | |
345 | } | |
346 | } | |
3c034cce | 347 | |
e390b07b MB |
348 | /* set interrupt to active high on DIO0 when data ready */ |
349 | value = 0x0006; | |
350 | status = spi_adis16255_write_data(spiadis, | |
351 | ADIS_MSC_CTRL_MSB, ADIS_MSC_CTRL_LSB, | |
352 | (u8 *)&value); | |
353 | if (status != 0) | |
354 | goto err; | |
355 | return status; | |
356 | ||
357 | err: | |
358 | spi_adis16255_shutdown(spiadis); | |
359 | return status; | |
360 | } | |
361 | ||
362 | /*-------------------------------------------------------------------------*/ | |
3c034cce | 363 | |
c3dee74f | 364 | static int __devinit spi_adis16255_probe(struct spi_device *spi) |
e390b07b MB |
365 | { |
366 | ||
367 | struct adis16255_init_data *init_data = spi->dev.platform_data; | |
3c034cce | 368 | struct spi_adis16255_data *spiadis; |
e390b07b | 369 | int status = 0; |
3c034cce | 370 | |
e390b07b | 371 | spiadis = kzalloc(sizeof(*spiadis), GFP_KERNEL); |
3c034cce | 372 | if (!spiadis) |
373 | return -ENOMEM; | |
374 | ||
e390b07b MB |
375 | spiadis->spi = spi; |
376 | spiadis->direction = init_data->direction; | |
3c034cce | 377 | |
378 | if (init_data->negative) | |
e390b07b | 379 | spiadis->negative = 1; |
3c034cce | 380 | |
e390b07b MB |
381 | status = gpio_request(init_data->irq, "adis16255"); |
382 | if (status != 0) | |
3c034cce | 383 | goto err; |
384 | ||
e390b07b MB |
385 | status = gpio_direction_input(init_data->irq); |
386 | if (status != 0) | |
3c034cce | 387 | goto gpio_err; |
388 | ||
e390b07b | 389 | spiadis->irq = gpio_to_irq(init_data->irq); |
3c034cce | 390 | |
e390b07b | 391 | status = request_threaded_irq(spiadis->irq, |
3c034cce | 392 | NULL, adis_irq_thread, |
393 | IRQF_DISABLED, "adis-driver", spiadis); | |
394 | ||
e390b07b | 395 | if (status != 0) { |
3c034cce | 396 | dev_err(&spi->dev, "IRQ request failed\n"); |
397 | goto gpio_err; | |
398 | } | |
399 | ||
e390b07b | 400 | dev_dbg(&spi->dev, "GPIO %d IRQ %d\n", init_data->irq, spiadis->irq); |
3c034cce | 401 | |
402 | dev_set_drvdata(&spi->dev, spiadis); | |
e390b07b MB |
403 | status = sysfs_create_group(&spi->dev.kobj, &adis16255_attr_group); |
404 | if (status != 0) | |
405 | goto irq_err; | |
3c034cce | 406 | |
e390b07b MB |
407 | status = spi_adis16255_bringup(spiadis); |
408 | if (status != 0) | |
409 | goto irq_err; | |
3c034cce | 410 | |
e390b07b | 411 | dev_info(&spi->dev, "spi_adis16255 driver added!\n"); |
3c034cce | 412 | |
413 | return status; | |
414 | ||
415 | irq_err: | |
416 | free_irq(spiadis->irq, spiadis); | |
417 | gpio_err: | |
e390b07b | 418 | gpio_free(init_data->irq); |
3c034cce | 419 | err: |
420 | kfree(spiadis); | |
421 | return status; | |
422 | } | |
423 | ||
c3dee74f | 424 | static int __devexit spi_adis16255_remove(struct spi_device *spi) |
3c034cce | 425 | { |
e390b07b | 426 | struct spi_adis16255_data *spiadis = dev_get_drvdata(&spi->dev); |
3c034cce | 427 | |
e390b07b | 428 | spi_adis16255_shutdown(spiadis); |
3c034cce | 429 | |
430 | free_irq(spiadis->irq, spiadis); | |
e390b07b | 431 | gpio_free(irq_to_gpio(spiadis->irq)); |
3c034cce | 432 | |
433 | sysfs_remove_group(&spiadis->spi->dev.kobj, &adis16255_attr_group); | |
434 | ||
435 | kfree(spiadis); | |
436 | ||
437 | dev_info(&spi->dev, "spi_adis16255 driver removed!\n"); | |
438 | return 0; | |
439 | } | |
440 | ||
e390b07b MB |
441 | static struct spi_driver spi_adis16255_drv = { |
442 | .driver = { | |
443 | .name = "spi_adis16255", | |
444 | .owner = THIS_MODULE, | |
3c034cce | 445 | }, |
e390b07b MB |
446 | .probe = spi_adis16255_probe, |
447 | .remove = __devexit_p(spi_adis16255_remove), | |
3c034cce | 448 | }; |
449 | ||
450 | /*-------------------------------------------------------------------------*/ | |
451 | ||
452 | static int __init spi_adis16255_init(void) | |
453 | { | |
454 | return spi_register_driver(&spi_adis16255_drv); | |
455 | } | |
456 | module_init(spi_adis16255_init); | |
457 | ||
458 | static void __exit spi_adis16255_exit(void) | |
459 | { | |
460 | spi_unregister_driver(&spi_adis16255_drv); | |
461 | } | |
462 | module_exit(spi_adis16255_exit); | |
463 | ||
464 | MODULE_AUTHOR("Matthias Brugger"); | |
465 | MODULE_DESCRIPTION("SPI device driver for ADIS16255 sensor"); | |
466 | MODULE_LICENSE("GPL"); |