Commit | Line | Data |
---|---|---|
2f3abe6c LPC |
1 | /* |
2 | * ADIS16480 and similar IMUs driver | |
3 | * | |
4 | * Copyright 2012 Analog Devices Inc. | |
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 version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | * | |
10 | */ | |
11 | ||
12 | #include <linux/interrupt.h> | |
13 | #include <linux/delay.h> | |
14 | #include <linux/mutex.h> | |
15 | #include <linux/device.h> | |
16 | #include <linux/kernel.h> | |
17 | #include <linux/spi/spi.h> | |
18 | #include <linux/slab.h> | |
19 | #include <linux/sysfs.h> | |
20 | #include <linux/module.h> | |
21 | ||
22 | #include <linux/iio/iio.h> | |
23 | #include <linux/iio/sysfs.h> | |
24 | #include <linux/iio/buffer.h> | |
25 | #include <linux/iio/imu/adis.h> | |
26 | ||
2f3abe6c LPC |
27 | #include <linux/debugfs.h> |
28 | ||
29 | #define ADIS16480_PAGE_SIZE 0x80 | |
30 | ||
31 | #define ADIS16480_REG(page, reg) ((page) * ADIS16480_PAGE_SIZE + (reg)) | |
32 | ||
33 | #define ADIS16480_REG_PAGE_ID 0x00 /* Same address on each page */ | |
34 | #define ADIS16480_REG_SEQ_CNT ADIS16480_REG(0x00, 0x06) | |
35 | #define ADIS16480_REG_SYS_E_FLA ADIS16480_REG(0x00, 0x08) | |
36 | #define ADIS16480_REG_DIAG_STS ADIS16480_REG(0x00, 0x0A) | |
37 | #define ADIS16480_REG_ALM_STS ADIS16480_REG(0x00, 0x0C) | |
38 | #define ADIS16480_REG_TEMP_OUT ADIS16480_REG(0x00, 0x0E) | |
39 | #define ADIS16480_REG_X_GYRO_OUT ADIS16480_REG(0x00, 0x10) | |
40 | #define ADIS16480_REG_Y_GYRO_OUT ADIS16480_REG(0x00, 0x14) | |
41 | #define ADIS16480_REG_Z_GYRO_OUT ADIS16480_REG(0x00, 0x18) | |
42 | #define ADIS16480_REG_X_ACCEL_OUT ADIS16480_REG(0x00, 0x1C) | |
43 | #define ADIS16480_REG_Y_ACCEL_OUT ADIS16480_REG(0x00, 0x20) | |
44 | #define ADIS16480_REG_Z_ACCEL_OUT ADIS16480_REG(0x00, 0x24) | |
45 | #define ADIS16480_REG_X_MAGN_OUT ADIS16480_REG(0x00, 0x28) | |
46 | #define ADIS16480_REG_Y_MAGN_OUT ADIS16480_REG(0x00, 0x2A) | |
47 | #define ADIS16480_REG_Z_MAGN_OUT ADIS16480_REG(0x00, 0x2C) | |
48 | #define ADIS16480_REG_BAROM_OUT ADIS16480_REG(0x00, 0x2E) | |
49 | #define ADIS16480_REG_X_DELTAANG_OUT ADIS16480_REG(0x00, 0x40) | |
50 | #define ADIS16480_REG_Y_DELTAANG_OUT ADIS16480_REG(0x00, 0x44) | |
51 | #define ADIS16480_REG_Z_DELTAANG_OUT ADIS16480_REG(0x00, 0x48) | |
52 | #define ADIS16480_REG_X_DELTAVEL_OUT ADIS16480_REG(0x00, 0x4C) | |
53 | #define ADIS16480_REG_Y_DELTAVEL_OUT ADIS16480_REG(0x00, 0x50) | |
54 | #define ADIS16480_REG_Z_DELTAVEL_OUT ADIS16480_REG(0x00, 0x54) | |
55 | #define ADIS16480_REG_PROD_ID ADIS16480_REG(0x00, 0x7E) | |
56 | ||
57 | #define ADIS16480_REG_X_GYRO_SCALE ADIS16480_REG(0x02, 0x04) | |
58 | #define ADIS16480_REG_Y_GYRO_SCALE ADIS16480_REG(0x02, 0x06) | |
59 | #define ADIS16480_REG_Z_GYRO_SCALE ADIS16480_REG(0x02, 0x08) | |
60 | #define ADIS16480_REG_X_ACCEL_SCALE ADIS16480_REG(0x02, 0x0A) | |
61 | #define ADIS16480_REG_Y_ACCEL_SCALE ADIS16480_REG(0x02, 0x0C) | |
62 | #define ADIS16480_REG_Z_ACCEL_SCALE ADIS16480_REG(0x02, 0x0E) | |
63 | #define ADIS16480_REG_X_GYRO_BIAS ADIS16480_REG(0x02, 0x10) | |
64 | #define ADIS16480_REG_Y_GYRO_BIAS ADIS16480_REG(0x02, 0x14) | |
65 | #define ADIS16480_REG_Z_GYRO_BIAS ADIS16480_REG(0x02, 0x18) | |
66 | #define ADIS16480_REG_X_ACCEL_BIAS ADIS16480_REG(0x02, 0x1C) | |
67 | #define ADIS16480_REG_Y_ACCEL_BIAS ADIS16480_REG(0x02, 0x20) | |
68 | #define ADIS16480_REG_Z_ACCEL_BIAS ADIS16480_REG(0x02, 0x24) | |
69 | #define ADIS16480_REG_X_HARD_IRON ADIS16480_REG(0x02, 0x28) | |
70 | #define ADIS16480_REG_Y_HARD_IRON ADIS16480_REG(0x02, 0x2A) | |
71 | #define ADIS16480_REG_Z_HARD_IRON ADIS16480_REG(0x02, 0x2C) | |
72 | #define ADIS16480_REG_BAROM_BIAS ADIS16480_REG(0x02, 0x40) | |
73 | #define ADIS16480_REG_FLASH_CNT ADIS16480_REG(0x02, 0x7C) | |
74 | ||
75 | #define ADIS16480_REG_GLOB_CMD ADIS16480_REG(0x03, 0x02) | |
76 | #define ADIS16480_REG_FNCTIO_CTRL ADIS16480_REG(0x03, 0x06) | |
77 | #define ADIS16480_REG_GPIO_CTRL ADIS16480_REG(0x03, 0x08) | |
78 | #define ADIS16480_REG_CONFIG ADIS16480_REG(0x03, 0x0A) | |
79 | #define ADIS16480_REG_DEC_RATE ADIS16480_REG(0x03, 0x0C) | |
80 | #define ADIS16480_REG_SLP_CNT ADIS16480_REG(0x03, 0x10) | |
81 | #define ADIS16480_REG_FILTER_BNK0 ADIS16480_REG(0x03, 0x16) | |
82 | #define ADIS16480_REG_FILTER_BNK1 ADIS16480_REG(0x03, 0x18) | |
83 | #define ADIS16480_REG_ALM_CNFG0 ADIS16480_REG(0x03, 0x20) | |
84 | #define ADIS16480_REG_ALM_CNFG1 ADIS16480_REG(0x03, 0x22) | |
85 | #define ADIS16480_REG_ALM_CNFG2 ADIS16480_REG(0x03, 0x24) | |
86 | #define ADIS16480_REG_XG_ALM_MAGN ADIS16480_REG(0x03, 0x28) | |
87 | #define ADIS16480_REG_YG_ALM_MAGN ADIS16480_REG(0x03, 0x2A) | |
88 | #define ADIS16480_REG_ZG_ALM_MAGN ADIS16480_REG(0x03, 0x2C) | |
89 | #define ADIS16480_REG_XA_ALM_MAGN ADIS16480_REG(0x03, 0x2E) | |
90 | #define ADIS16480_REG_YA_ALM_MAGN ADIS16480_REG(0x03, 0x30) | |
91 | #define ADIS16480_REG_ZA_ALM_MAGN ADIS16480_REG(0x03, 0x32) | |
92 | #define ADIS16480_REG_XM_ALM_MAGN ADIS16480_REG(0x03, 0x34) | |
93 | #define ADIS16480_REG_YM_ALM_MAGN ADIS16480_REG(0x03, 0x36) | |
94 | #define ADIS16480_REG_ZM_ALM_MAGN ADIS16480_REG(0x03, 0x38) | |
95 | #define ADIS16480_REG_BR_ALM_MAGN ADIS16480_REG(0x03, 0x3A) | |
96 | #define ADIS16480_REG_FIRM_REV ADIS16480_REG(0x03, 0x78) | |
97 | #define ADIS16480_REG_FIRM_DM ADIS16480_REG(0x03, 0x7A) | |
98 | #define ADIS16480_REG_FIRM_Y ADIS16480_REG(0x03, 0x7C) | |
99 | ||
100 | #define ADIS16480_REG_SERIAL_NUM ADIS16480_REG(0x04, 0x20) | |
101 | ||
102 | /* Each filter coefficent bank spans two pages */ | |
103 | #define ADIS16480_FIR_COEF(page) (x < 60 ? ADIS16480_REG(page, (x) + 8) : \ | |
104 | ADIS16480_REG((page) + 1, (x) - 60 + 8)) | |
105 | #define ADIS16480_FIR_COEF_A(x) ADIS16480_FIR_COEF(0x05, (x)) | |
106 | #define ADIS16480_FIR_COEF_B(x) ADIS16480_FIR_COEF(0x07, (x)) | |
107 | #define ADIS16480_FIR_COEF_C(x) ADIS16480_FIR_COEF(0x09, (x)) | |
108 | #define ADIS16480_FIR_COEF_D(x) ADIS16480_FIR_COEF(0x0B, (x)) | |
109 | ||
110 | struct adis16480_chip_info { | |
111 | unsigned int num_channels; | |
112 | const struct iio_chan_spec *channels; | |
113 | }; | |
114 | ||
115 | struct adis16480 { | |
116 | const struct adis16480_chip_info *chip_info; | |
117 | ||
118 | struct adis adis; | |
119 | }; | |
120 | ||
121 | #ifdef CONFIG_DEBUG_FS | |
122 | ||
123 | static ssize_t adis16480_show_firmware_revision(struct file *file, | |
124 | char __user *userbuf, size_t count, loff_t *ppos) | |
125 | { | |
126 | struct adis16480 *adis16480 = file->private_data; | |
afc3a57a | 127 | char buf[7]; |
2f3abe6c LPC |
128 | size_t len; |
129 | u16 rev; | |
130 | int ret; | |
131 | ||
132 | ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_FIRM_REV, &rev); | |
133 | if (ret < 0) | |
134 | return ret; | |
135 | ||
afc3a57a | 136 | len = scnprintf(buf, sizeof(buf), "%x.%x\n", rev >> 8, rev & 0xff); |
2f3abe6c LPC |
137 | |
138 | return simple_read_from_buffer(userbuf, count, ppos, buf, len); | |
139 | } | |
140 | ||
141 | static const struct file_operations adis16480_firmware_revision_fops = { | |
142 | .open = simple_open, | |
143 | .read = adis16480_show_firmware_revision, | |
144 | .llseek = default_llseek, | |
145 | .owner = THIS_MODULE, | |
146 | }; | |
147 | ||
148 | static ssize_t adis16480_show_firmware_date(struct file *file, | |
149 | char __user *userbuf, size_t count, loff_t *ppos) | |
150 | { | |
151 | struct adis16480 *adis16480 = file->private_data; | |
152 | u16 md, year; | |
153 | char buf[12]; | |
154 | size_t len; | |
155 | int ret; | |
156 | ||
157 | ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_FIRM_Y, &year); | |
158 | if (ret < 0) | |
159 | return ret; | |
160 | ||
161 | ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_FIRM_DM, &md); | |
162 | if (ret < 0) | |
163 | return ret; | |
164 | ||
165 | len = snprintf(buf, sizeof(buf), "%.2x-%.2x-%.4x\n", | |
166 | md >> 8, md & 0xff, year); | |
167 | ||
168 | return simple_read_from_buffer(userbuf, count, ppos, buf, len); | |
169 | } | |
170 | ||
171 | static const struct file_operations adis16480_firmware_date_fops = { | |
172 | .open = simple_open, | |
173 | .read = adis16480_show_firmware_date, | |
174 | .llseek = default_llseek, | |
175 | .owner = THIS_MODULE, | |
176 | }; | |
177 | ||
178 | static int adis16480_show_serial_number(void *arg, u64 *val) | |
179 | { | |
180 | struct adis16480 *adis16480 = arg; | |
181 | u16 serial; | |
182 | int ret; | |
183 | ||
184 | ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_SERIAL_NUM, | |
185 | &serial); | |
186 | if (ret < 0) | |
187 | return ret; | |
188 | ||
189 | *val = serial; | |
190 | ||
191 | return 0; | |
192 | } | |
193 | DEFINE_SIMPLE_ATTRIBUTE(adis16480_serial_number_fops, | |
194 | adis16480_show_serial_number, NULL, "0x%.4llx\n"); | |
195 | ||
196 | static int adis16480_show_product_id(void *arg, u64 *val) | |
197 | { | |
198 | struct adis16480 *adis16480 = arg; | |
199 | u16 prod_id; | |
200 | int ret; | |
201 | ||
202 | ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_PROD_ID, | |
203 | &prod_id); | |
204 | if (ret < 0) | |
205 | return ret; | |
206 | ||
207 | *val = prod_id; | |
208 | ||
209 | return 0; | |
210 | } | |
211 | DEFINE_SIMPLE_ATTRIBUTE(adis16480_product_id_fops, | |
212 | adis16480_show_product_id, NULL, "%llu\n"); | |
213 | ||
214 | static int adis16480_show_flash_count(void *arg, u64 *val) | |
215 | { | |
216 | struct adis16480 *adis16480 = arg; | |
217 | u32 flash_count; | |
218 | int ret; | |
219 | ||
220 | ret = adis_read_reg_32(&adis16480->adis, ADIS16480_REG_FLASH_CNT, | |
221 | &flash_count); | |
222 | if (ret < 0) | |
223 | return ret; | |
224 | ||
225 | *val = flash_count; | |
226 | ||
227 | return 0; | |
228 | } | |
229 | DEFINE_SIMPLE_ATTRIBUTE(adis16480_flash_count_fops, | |
230 | adis16480_show_flash_count, NULL, "%lld\n"); | |
231 | ||
232 | static int adis16480_debugfs_init(struct iio_dev *indio_dev) | |
233 | { | |
234 | struct adis16480 *adis16480 = iio_priv(indio_dev); | |
235 | ||
236 | debugfs_create_file("firmware_revision", 0400, | |
237 | indio_dev->debugfs_dentry, adis16480, | |
238 | &adis16480_firmware_revision_fops); | |
239 | debugfs_create_file("firmware_date", 0400, indio_dev->debugfs_dentry, | |
240 | adis16480, &adis16480_firmware_date_fops); | |
241 | debugfs_create_file("serial_number", 0400, indio_dev->debugfs_dentry, | |
242 | adis16480, &adis16480_serial_number_fops); | |
243 | debugfs_create_file("product_id", 0400, indio_dev->debugfs_dentry, | |
244 | adis16480, &adis16480_product_id_fops); | |
245 | debugfs_create_file("flash_count", 0400, indio_dev->debugfs_dentry, | |
246 | adis16480, &adis16480_flash_count_fops); | |
247 | ||
248 | return 0; | |
249 | } | |
250 | ||
251 | #else | |
252 | ||
253 | static int adis16480_debugfs_init(struct iio_dev *indio_dev) | |
254 | { | |
255 | return 0; | |
256 | } | |
257 | ||
258 | #endif | |
259 | ||
260 | static int adis16480_set_freq(struct adis16480 *st, unsigned int freq) | |
261 | { | |
262 | unsigned int t; | |
263 | ||
264 | t = 2460000 / freq; | |
265 | if (t > 2048) | |
266 | t = 2048; | |
267 | ||
268 | if (t != 0) | |
269 | t--; | |
270 | ||
271 | return adis_write_reg_16(&st->adis, ADIS16480_REG_DEC_RATE, t); | |
272 | } | |
273 | ||
274 | static int adis16480_get_freq(struct adis16480 *st, unsigned int *freq) | |
275 | { | |
276 | uint16_t t; | |
277 | int ret; | |
278 | ||
279 | ret = adis_read_reg_16(&st->adis, ADIS16480_REG_DEC_RATE, &t); | |
280 | if (ret < 0) | |
281 | return ret; | |
282 | ||
283 | *freq = 2460000 / (t + 1); | |
284 | ||
285 | return 0; | |
286 | } | |
287 | ||
288 | static ssize_t adis16480_read_frequency(struct device *dev, | |
289 | struct device_attribute *attr, | |
290 | char *buf) | |
291 | { | |
292 | struct iio_dev *indio_dev = dev_to_iio_dev(dev); | |
293 | struct adis16480 *st = iio_priv(indio_dev); | |
294 | unsigned int freq; | |
295 | int ret; | |
296 | ||
297 | ret = adis16480_get_freq(st, &freq); | |
298 | if (ret < 0) | |
299 | return ret; | |
300 | ||
301 | return sprintf(buf, "%d.%.3d\n", freq / 1000, freq % 1000); | |
302 | } | |
303 | ||
304 | static ssize_t adis16480_write_frequency(struct device *dev, | |
305 | struct device_attribute *attr, | |
306 | const char *buf, | |
307 | size_t len) | |
308 | { | |
309 | struct iio_dev *indio_dev = dev_to_iio_dev(dev); | |
310 | struct adis16480 *st = iio_priv(indio_dev); | |
311 | int freq_int, freq_fract; | |
312 | long val; | |
313 | int ret; | |
314 | ||
315 | ret = iio_str_to_fixpoint(buf, 100, &freq_int, &freq_fract); | |
316 | if (ret) | |
317 | return ret; | |
318 | ||
319 | val = freq_int * 1000 + freq_fract; | |
320 | ||
321 | if (val <= 0) | |
322 | return -EINVAL; | |
323 | ||
324 | ret = adis16480_set_freq(st, val); | |
325 | ||
326 | return ret ? ret : len; | |
327 | } | |
328 | ||
329 | static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, | |
330 | adis16480_read_frequency, | |
331 | adis16480_write_frequency); | |
332 | ||
333 | enum { | |
334 | ADIS16480_SCAN_GYRO_X, | |
335 | ADIS16480_SCAN_GYRO_Y, | |
336 | ADIS16480_SCAN_GYRO_Z, | |
337 | ADIS16480_SCAN_ACCEL_X, | |
338 | ADIS16480_SCAN_ACCEL_Y, | |
339 | ADIS16480_SCAN_ACCEL_Z, | |
340 | ADIS16480_SCAN_MAGN_X, | |
341 | ADIS16480_SCAN_MAGN_Y, | |
342 | ADIS16480_SCAN_MAGN_Z, | |
343 | ADIS16480_SCAN_BARO, | |
344 | ADIS16480_SCAN_TEMP, | |
345 | }; | |
346 | ||
347 | static const unsigned int adis16480_calibbias_regs[] = { | |
348 | [ADIS16480_SCAN_GYRO_X] = ADIS16480_REG_X_GYRO_BIAS, | |
349 | [ADIS16480_SCAN_GYRO_Y] = ADIS16480_REG_Y_GYRO_BIAS, | |
350 | [ADIS16480_SCAN_GYRO_Z] = ADIS16480_REG_Z_GYRO_BIAS, | |
351 | [ADIS16480_SCAN_ACCEL_X] = ADIS16480_REG_X_ACCEL_BIAS, | |
352 | [ADIS16480_SCAN_ACCEL_Y] = ADIS16480_REG_Y_ACCEL_BIAS, | |
353 | [ADIS16480_SCAN_ACCEL_Z] = ADIS16480_REG_Z_ACCEL_BIAS, | |
354 | [ADIS16480_SCAN_MAGN_X] = ADIS16480_REG_X_HARD_IRON, | |
355 | [ADIS16480_SCAN_MAGN_Y] = ADIS16480_REG_Y_HARD_IRON, | |
356 | [ADIS16480_SCAN_MAGN_Z] = ADIS16480_REG_Z_HARD_IRON, | |
357 | [ADIS16480_SCAN_BARO] = ADIS16480_REG_BAROM_BIAS, | |
358 | }; | |
359 | ||
360 | static const unsigned int adis16480_calibscale_regs[] = { | |
361 | [ADIS16480_SCAN_GYRO_X] = ADIS16480_REG_X_GYRO_SCALE, | |
362 | [ADIS16480_SCAN_GYRO_Y] = ADIS16480_REG_Y_GYRO_SCALE, | |
363 | [ADIS16480_SCAN_GYRO_Z] = ADIS16480_REG_Z_GYRO_SCALE, | |
364 | [ADIS16480_SCAN_ACCEL_X] = ADIS16480_REG_X_ACCEL_SCALE, | |
365 | [ADIS16480_SCAN_ACCEL_Y] = ADIS16480_REG_Y_ACCEL_SCALE, | |
366 | [ADIS16480_SCAN_ACCEL_Z] = ADIS16480_REG_Z_ACCEL_SCALE, | |
367 | }; | |
368 | ||
369 | static int adis16480_set_calibbias(struct iio_dev *indio_dev, | |
370 | const struct iio_chan_spec *chan, int bias) | |
371 | { | |
372 | unsigned int reg = adis16480_calibbias_regs[chan->scan_index]; | |
373 | struct adis16480 *st = iio_priv(indio_dev); | |
374 | ||
375 | switch (chan->type) { | |
376 | case IIO_MAGN: | |
377 | case IIO_PRESSURE: | |
378 | if (bias < -0x8000 || bias >= 0x8000) | |
379 | return -EINVAL; | |
380 | return adis_write_reg_16(&st->adis, reg, bias); | |
381 | case IIO_ANGL_VEL: | |
382 | case IIO_ACCEL: | |
383 | return adis_write_reg_32(&st->adis, reg, bias); | |
384 | default: | |
385 | break; | |
386 | } | |
387 | ||
388 | return -EINVAL; | |
389 | } | |
390 | ||
391 | static int adis16480_get_calibbias(struct iio_dev *indio_dev, | |
392 | const struct iio_chan_spec *chan, int *bias) | |
393 | { | |
394 | unsigned int reg = adis16480_calibbias_regs[chan->scan_index]; | |
395 | struct adis16480 *st = iio_priv(indio_dev); | |
396 | uint16_t val16; | |
397 | uint32_t val32; | |
398 | int ret; | |
399 | ||
400 | switch (chan->type) { | |
401 | case IIO_MAGN: | |
402 | case IIO_PRESSURE: | |
403 | ret = adis_read_reg_16(&st->adis, reg, &val16); | |
404 | *bias = sign_extend32(val16, 15); | |
405 | break; | |
406 | case IIO_ANGL_VEL: | |
407 | case IIO_ACCEL: | |
408 | ret = adis_read_reg_32(&st->adis, reg, &val32); | |
409 | *bias = sign_extend32(val32, 31); | |
410 | break; | |
411 | default: | |
412 | ret = -EINVAL; | |
413 | } | |
414 | ||
415 | if (ret < 0) | |
416 | return ret; | |
417 | ||
418 | return IIO_VAL_INT; | |
419 | } | |
420 | ||
421 | static int adis16480_set_calibscale(struct iio_dev *indio_dev, | |
422 | const struct iio_chan_spec *chan, int scale) | |
423 | { | |
424 | unsigned int reg = adis16480_calibscale_regs[chan->scan_index]; | |
425 | struct adis16480 *st = iio_priv(indio_dev); | |
426 | ||
427 | if (scale < -0x8000 || scale >= 0x8000) | |
428 | return -EINVAL; | |
429 | ||
430 | return adis_write_reg_16(&st->adis, reg, scale); | |
431 | } | |
432 | ||
433 | static int adis16480_get_calibscale(struct iio_dev *indio_dev, | |
434 | const struct iio_chan_spec *chan, int *scale) | |
435 | { | |
436 | unsigned int reg = adis16480_calibscale_regs[chan->scan_index]; | |
437 | struct adis16480 *st = iio_priv(indio_dev); | |
438 | uint16_t val16; | |
439 | int ret; | |
440 | ||
441 | ret = adis_read_reg_16(&st->adis, reg, &val16); | |
442 | if (ret < 0) | |
443 | return ret; | |
444 | ||
445 | *scale = sign_extend32(val16, 15); | |
446 | return IIO_VAL_INT; | |
447 | } | |
448 | ||
449 | static const unsigned int adis16480_def_filter_freqs[] = { | |
450 | 310, | |
451 | 55, | |
452 | 275, | |
453 | 63, | |
454 | }; | |
455 | ||
456 | static const unsigned int ad16480_filter_data[][2] = { | |
457 | [ADIS16480_SCAN_GYRO_X] = { ADIS16480_REG_FILTER_BNK0, 0 }, | |
458 | [ADIS16480_SCAN_GYRO_Y] = { ADIS16480_REG_FILTER_BNK0, 3 }, | |
459 | [ADIS16480_SCAN_GYRO_Z] = { ADIS16480_REG_FILTER_BNK0, 6 }, | |
460 | [ADIS16480_SCAN_ACCEL_X] = { ADIS16480_REG_FILTER_BNK0, 9 }, | |
461 | [ADIS16480_SCAN_ACCEL_Y] = { ADIS16480_REG_FILTER_BNK0, 12 }, | |
462 | [ADIS16480_SCAN_ACCEL_Z] = { ADIS16480_REG_FILTER_BNK1, 0 }, | |
463 | [ADIS16480_SCAN_MAGN_X] = { ADIS16480_REG_FILTER_BNK1, 3 }, | |
464 | [ADIS16480_SCAN_MAGN_Y] = { ADIS16480_REG_FILTER_BNK1, 6 }, | |
465 | [ADIS16480_SCAN_MAGN_Z] = { ADIS16480_REG_FILTER_BNK1, 9 }, | |
466 | }; | |
467 | ||
468 | static int adis16480_get_filter_freq(struct iio_dev *indio_dev, | |
469 | const struct iio_chan_spec *chan, int *freq) | |
470 | { | |
471 | struct adis16480 *st = iio_priv(indio_dev); | |
472 | unsigned int enable_mask, offset, reg; | |
473 | uint16_t val; | |
474 | int ret; | |
475 | ||
476 | reg = ad16480_filter_data[chan->scan_index][0]; | |
477 | offset = ad16480_filter_data[chan->scan_index][1]; | |
478 | enable_mask = BIT(offset + 2); | |
479 | ||
480 | ret = adis_read_reg_16(&st->adis, reg, &val); | |
481 | if (ret < 0) | |
482 | return ret; | |
483 | ||
484 | if (!(val & enable_mask)) | |
485 | *freq = 0; | |
486 | else | |
487 | *freq = adis16480_def_filter_freqs[(val >> offset) & 0x3]; | |
488 | ||
489 | return IIO_VAL_INT; | |
490 | } | |
491 | ||
492 | static int adis16480_set_filter_freq(struct iio_dev *indio_dev, | |
493 | const struct iio_chan_spec *chan, unsigned int freq) | |
494 | { | |
495 | struct adis16480 *st = iio_priv(indio_dev); | |
496 | unsigned int enable_mask, offset, reg; | |
497 | unsigned int diff, best_diff; | |
498 | unsigned int i, best_freq; | |
499 | uint16_t val; | |
500 | int ret; | |
501 | ||
502 | reg = ad16480_filter_data[chan->scan_index][0]; | |
503 | offset = ad16480_filter_data[chan->scan_index][1]; | |
504 | enable_mask = BIT(offset + 2); | |
505 | ||
506 | ret = adis_read_reg_16(&st->adis, reg, &val); | |
507 | if (ret < 0) | |
508 | return ret; | |
509 | ||
510 | if (freq == 0) { | |
511 | val &= ~enable_mask; | |
512 | } else { | |
513 | best_freq = 0; | |
514 | best_diff = 310; | |
515 | for (i = 0; i < ARRAY_SIZE(adis16480_def_filter_freqs); i++) { | |
516 | if (adis16480_def_filter_freqs[i] >= freq) { | |
517 | diff = adis16480_def_filter_freqs[i] - freq; | |
518 | if (diff < best_diff) { | |
519 | best_diff = diff; | |
520 | best_freq = i; | |
521 | } | |
522 | } | |
523 | } | |
524 | ||
525 | val &= ~(0x3 << offset); | |
526 | val |= best_freq << offset; | |
527 | val |= enable_mask; | |
528 | } | |
529 | ||
530 | return adis_write_reg_16(&st->adis, reg, val); | |
531 | } | |
532 | ||
533 | static int adis16480_read_raw(struct iio_dev *indio_dev, | |
534 | const struct iio_chan_spec *chan, int *val, int *val2, long info) | |
535 | { | |
536 | switch (info) { | |
537 | case IIO_CHAN_INFO_RAW: | |
538 | return adis_single_conversion(indio_dev, chan, 0, val); | |
539 | case IIO_CHAN_INFO_SCALE: | |
540 | switch (chan->type) { | |
541 | case IIO_ANGL_VEL: | |
542 | *val = 0; | |
543 | *val2 = IIO_DEGREE_TO_RAD(20000); /* 0.02 degree/sec */ | |
544 | return IIO_VAL_INT_PLUS_MICRO; | |
545 | case IIO_ACCEL: | |
546 | *val = 0; | |
547 | *val2 = IIO_G_TO_M_S_2(800); /* 0.8 mg */ | |
548 | return IIO_VAL_INT_PLUS_MICRO; | |
549 | case IIO_MAGN: | |
550 | *val = 0; | |
551 | *val2 = 100; /* 0.0001 gauss */ | |
552 | return IIO_VAL_INT_PLUS_MICRO; | |
553 | case IIO_TEMP: | |
554 | *val = 5; | |
555 | *val2 = 650000; /* 5.65 milli degree Celsius */ | |
556 | return IIO_VAL_INT_PLUS_MICRO; | |
557 | case IIO_PRESSURE: | |
558 | *val = 0; | |
559 | *val2 = 4000; /* 40ubar = 0.004 kPa */ | |
560 | return IIO_VAL_INT_PLUS_MICRO; | |
561 | default: | |
562 | return -EINVAL; | |
563 | } | |
564 | case IIO_CHAN_INFO_OFFSET: | |
565 | /* Only the temperature channel has a offset */ | |
566 | *val = 4425; /* 25 degree Celsius = 0x0000 */ | |
567 | return IIO_VAL_INT; | |
568 | case IIO_CHAN_INFO_CALIBBIAS: | |
569 | return adis16480_get_calibbias(indio_dev, chan, val); | |
570 | case IIO_CHAN_INFO_CALIBSCALE: | |
571 | return adis16480_get_calibscale(indio_dev, chan, val); | |
572 | case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: | |
573 | return adis16480_get_filter_freq(indio_dev, chan, val); | |
574 | default: | |
575 | return -EINVAL; | |
576 | } | |
577 | } | |
578 | ||
579 | static int adis16480_write_raw(struct iio_dev *indio_dev, | |
580 | const struct iio_chan_spec *chan, int val, int val2, long info) | |
581 | { | |
582 | switch (info) { | |
583 | case IIO_CHAN_INFO_CALIBBIAS: | |
584 | return adis16480_set_calibbias(indio_dev, chan, val); | |
585 | case IIO_CHAN_INFO_CALIBSCALE: | |
586 | return adis16480_set_calibscale(indio_dev, chan, val); | |
587 | case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: | |
588 | return adis16480_set_filter_freq(indio_dev, chan, val); | |
589 | default: | |
590 | return -EINVAL; | |
591 | } | |
592 | } | |
593 | ||
86b64c9d | 594 | #define ADIS16480_MOD_CHANNEL(_type, _mod, _address, _si, _info_sep, _bits) \ |
2f3abe6c LPC |
595 | { \ |
596 | .type = (_type), \ | |
597 | .modified = 1, \ | |
598 | .channel2 = (_mod), \ | |
86b64c9d JC |
599 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ |
600 | BIT(IIO_CHAN_INFO_CALIBBIAS) | \ | |
601 | _info_sep, \ | |
602 | .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ | |
2f3abe6c LPC |
603 | .address = (_address), \ |
604 | .scan_index = (_si), \ | |
605 | .scan_type = { \ | |
606 | .sign = 's', \ | |
607 | .realbits = (_bits), \ | |
608 | .storagebits = (_bits), \ | |
609 | .endianness = IIO_BE, \ | |
610 | }, \ | |
611 | } | |
612 | ||
613 | #define ADIS16480_GYRO_CHANNEL(_mod) \ | |
614 | ADIS16480_MOD_CHANNEL(IIO_ANGL_VEL, IIO_MOD_ ## _mod, \ | |
615 | ADIS16480_REG_ ## _mod ## _GYRO_OUT, ADIS16480_SCAN_GYRO_ ## _mod, \ | |
86b64c9d JC |
616 | BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ |
617 | BIT(IIO_CHAN_INFO_CALIBSCALE), \ | |
2f3abe6c LPC |
618 | 32) |
619 | ||
620 | #define ADIS16480_ACCEL_CHANNEL(_mod) \ | |
621 | ADIS16480_MOD_CHANNEL(IIO_ACCEL, IIO_MOD_ ## _mod, \ | |
622 | ADIS16480_REG_ ## _mod ## _ACCEL_OUT, ADIS16480_SCAN_ACCEL_ ## _mod, \ | |
86b64c9d JC |
623 | BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ |
624 | BIT(IIO_CHAN_INFO_CALIBSCALE), \ | |
2f3abe6c LPC |
625 | 32) |
626 | ||
627 | #define ADIS16480_MAGN_CHANNEL(_mod) \ | |
628 | ADIS16480_MOD_CHANNEL(IIO_MAGN, IIO_MOD_ ## _mod, \ | |
629 | ADIS16480_REG_ ## _mod ## _MAGN_OUT, ADIS16480_SCAN_MAGN_ ## _mod, \ | |
86b64c9d | 630 | BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ |
2f3abe6c LPC |
631 | 16) |
632 | ||
633 | #define ADIS16480_PRESSURE_CHANNEL() \ | |
634 | { \ | |
635 | .type = IIO_PRESSURE, \ | |
636 | .indexed = 1, \ | |
637 | .channel = 0, \ | |
86b64c9d JC |
638 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ |
639 | BIT(IIO_CHAN_INFO_CALIBBIAS) | \ | |
640 | BIT(IIO_CHAN_INFO_SCALE), \ | |
2f3abe6c LPC |
641 | .address = ADIS16480_REG_BAROM_OUT, \ |
642 | .scan_index = ADIS16480_SCAN_BARO, \ | |
643 | .scan_type = { \ | |
644 | .sign = 's', \ | |
645 | .realbits = 32, \ | |
646 | .storagebits = 32, \ | |
647 | .endianness = IIO_BE, \ | |
648 | }, \ | |
649 | } | |
650 | ||
651 | #define ADIS16480_TEMP_CHANNEL() { \ | |
652 | .type = IIO_TEMP, \ | |
653 | .indexed = 1, \ | |
654 | .channel = 0, \ | |
86b64c9d JC |
655 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ |
656 | BIT(IIO_CHAN_INFO_SCALE) | \ | |
657 | BIT(IIO_CHAN_INFO_OFFSET), \ | |
2f3abe6c LPC |
658 | .address = ADIS16480_REG_TEMP_OUT, \ |
659 | .scan_index = ADIS16480_SCAN_TEMP, \ | |
660 | .scan_type = { \ | |
661 | .sign = 's', \ | |
662 | .realbits = 16, \ | |
663 | .storagebits = 16, \ | |
664 | .endianness = IIO_BE, \ | |
665 | }, \ | |
666 | } | |
667 | ||
668 | static const struct iio_chan_spec adis16480_channels[] = { | |
669 | ADIS16480_GYRO_CHANNEL(X), | |
670 | ADIS16480_GYRO_CHANNEL(Y), | |
671 | ADIS16480_GYRO_CHANNEL(Z), | |
672 | ADIS16480_ACCEL_CHANNEL(X), | |
673 | ADIS16480_ACCEL_CHANNEL(Y), | |
674 | ADIS16480_ACCEL_CHANNEL(Z), | |
675 | ADIS16480_MAGN_CHANNEL(X), | |
676 | ADIS16480_MAGN_CHANNEL(Y), | |
677 | ADIS16480_MAGN_CHANNEL(Z), | |
678 | ADIS16480_PRESSURE_CHANNEL(), | |
679 | ADIS16480_TEMP_CHANNEL(), | |
680 | IIO_CHAN_SOFT_TIMESTAMP(11) | |
681 | }; | |
682 | ||
683 | static const struct iio_chan_spec adis16485_channels[] = { | |
684 | ADIS16480_GYRO_CHANNEL(X), | |
685 | ADIS16480_GYRO_CHANNEL(Y), | |
686 | ADIS16480_GYRO_CHANNEL(Z), | |
687 | ADIS16480_ACCEL_CHANNEL(X), | |
688 | ADIS16480_ACCEL_CHANNEL(Y), | |
689 | ADIS16480_ACCEL_CHANNEL(Z), | |
690 | ADIS16480_TEMP_CHANNEL(), | |
691 | IIO_CHAN_SOFT_TIMESTAMP(7) | |
692 | }; | |
693 | ||
694 | enum adis16480_variant { | |
695 | ADIS16375, | |
696 | ADIS16480, | |
697 | ADIS16485, | |
698 | ADIS16488, | |
699 | }; | |
700 | ||
701 | static const struct adis16480_chip_info adis16480_chip_info[] = { | |
702 | [ADIS16375] = { | |
703 | .channels = adis16485_channels, | |
704 | .num_channels = ARRAY_SIZE(adis16485_channels), | |
705 | }, | |
706 | [ADIS16480] = { | |
707 | .channels = adis16480_channels, | |
708 | .num_channels = ARRAY_SIZE(adis16480_channels), | |
709 | }, | |
710 | [ADIS16485] = { | |
711 | .channels = adis16485_channels, | |
712 | .num_channels = ARRAY_SIZE(adis16485_channels), | |
713 | }, | |
714 | [ADIS16488] = { | |
715 | .channels = adis16480_channels, | |
716 | .num_channels = ARRAY_SIZE(adis16480_channels), | |
717 | }, | |
718 | }; | |
719 | ||
720 | static struct attribute *adis16480_attributes[] = { | |
721 | &iio_dev_attr_sampling_frequency.dev_attr.attr, | |
722 | NULL | |
723 | }; | |
724 | ||
725 | static const struct attribute_group adis16480_attribute_group = { | |
726 | .attrs = adis16480_attributes, | |
727 | }; | |
728 | ||
729 | static const struct iio_info adis16480_info = { | |
730 | .attrs = &adis16480_attribute_group, | |
731 | .read_raw = &adis16480_read_raw, | |
732 | .write_raw = &adis16480_write_raw, | |
733 | .update_scan_mode = adis_update_scan_mode, | |
734 | .driver_module = THIS_MODULE, | |
735 | }; | |
736 | ||
737 | static int adis16480_stop_device(struct iio_dev *indio_dev) | |
738 | { | |
739 | struct adis16480 *st = iio_priv(indio_dev); | |
740 | int ret; | |
741 | ||
742 | ret = adis_write_reg_16(&st->adis, ADIS16480_REG_SLP_CNT, BIT(9)); | |
743 | if (ret) | |
744 | dev_err(&indio_dev->dev, | |
745 | "Could not power down device: %d\n", ret); | |
746 | ||
747 | return ret; | |
748 | } | |
749 | ||
750 | static int adis16480_enable_irq(struct adis *adis, bool enable) | |
751 | { | |
752 | return adis_write_reg_16(adis, ADIS16480_REG_FNCTIO_CTRL, | |
753 | enable ? BIT(3) : 0); | |
754 | } | |
755 | ||
756 | static int adis16480_initial_setup(struct iio_dev *indio_dev) | |
757 | { | |
758 | struct adis16480 *st = iio_priv(indio_dev); | |
759 | uint16_t prod_id; | |
760 | unsigned int device_id; | |
761 | int ret; | |
762 | ||
763 | adis_reset(&st->adis); | |
764 | msleep(70); | |
765 | ||
766 | ret = adis_write_reg_16(&st->adis, ADIS16480_REG_GLOB_CMD, BIT(1)); | |
767 | if (ret) | |
768 | return ret; | |
769 | msleep(30); | |
770 | ||
771 | ret = adis_check_status(&st->adis); | |
772 | if (ret) | |
773 | return ret; | |
774 | ||
775 | ret = adis_read_reg_16(&st->adis, ADIS16480_REG_PROD_ID, &prod_id); | |
776 | if (ret) | |
777 | return ret; | |
778 | ||
779 | sscanf(indio_dev->name, "adis%u\n", &device_id); | |
780 | ||
781 | if (prod_id != device_id) | |
782 | dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", | |
783 | device_id, prod_id); | |
784 | ||
785 | return 0; | |
786 | } | |
787 | ||
788 | #define ADIS16480_DIAG_STAT_XGYRO_FAIL 0 | |
789 | #define ADIS16480_DIAG_STAT_YGYRO_FAIL 1 | |
790 | #define ADIS16480_DIAG_STAT_ZGYRO_FAIL 2 | |
791 | #define ADIS16480_DIAG_STAT_XACCL_FAIL 3 | |
792 | #define ADIS16480_DIAG_STAT_YACCL_FAIL 4 | |
793 | #define ADIS16480_DIAG_STAT_ZACCL_FAIL 5 | |
794 | #define ADIS16480_DIAG_STAT_XMAGN_FAIL 8 | |
795 | #define ADIS16480_DIAG_STAT_YMAGN_FAIL 9 | |
796 | #define ADIS16480_DIAG_STAT_ZMAGN_FAIL 10 | |
797 | #define ADIS16480_DIAG_STAT_BARO_FAIL 11 | |
798 | ||
799 | static const char * const adis16480_status_error_msgs[] = { | |
800 | [ADIS16480_DIAG_STAT_XGYRO_FAIL] = "X-axis gyroscope self-test failure", | |
801 | [ADIS16480_DIAG_STAT_YGYRO_FAIL] = "Y-axis gyroscope self-test failure", | |
802 | [ADIS16480_DIAG_STAT_ZGYRO_FAIL] = "Z-axis gyroscope self-test failure", | |
803 | [ADIS16480_DIAG_STAT_XACCL_FAIL] = "X-axis accelerometer self-test failure", | |
804 | [ADIS16480_DIAG_STAT_YACCL_FAIL] = "Y-axis accelerometer self-test failure", | |
805 | [ADIS16480_DIAG_STAT_ZACCL_FAIL] = "Z-axis accelerometer self-test failure", | |
806 | [ADIS16480_DIAG_STAT_XMAGN_FAIL] = "X-axis magnetometer self-test failure", | |
807 | [ADIS16480_DIAG_STAT_YMAGN_FAIL] = "Y-axis magnetometer self-test failure", | |
808 | [ADIS16480_DIAG_STAT_ZMAGN_FAIL] = "Z-axis magnetometer self-test failure", | |
809 | [ADIS16480_DIAG_STAT_BARO_FAIL] = "Barometer self-test failure", | |
810 | }; | |
811 | ||
812 | static const struct adis_data adis16480_data = { | |
813 | .diag_stat_reg = ADIS16480_REG_DIAG_STS, | |
814 | .glob_cmd_reg = ADIS16480_REG_GLOB_CMD, | |
815 | .has_paging = true, | |
816 | ||
817 | .read_delay = 5, | |
818 | .write_delay = 5, | |
819 | ||
820 | .status_error_msgs = adis16480_status_error_msgs, | |
821 | .status_error_mask = BIT(ADIS16480_DIAG_STAT_XGYRO_FAIL) | | |
822 | BIT(ADIS16480_DIAG_STAT_YGYRO_FAIL) | | |
823 | BIT(ADIS16480_DIAG_STAT_ZGYRO_FAIL) | | |
824 | BIT(ADIS16480_DIAG_STAT_XACCL_FAIL) | | |
825 | BIT(ADIS16480_DIAG_STAT_YACCL_FAIL) | | |
826 | BIT(ADIS16480_DIAG_STAT_ZACCL_FAIL) | | |
827 | BIT(ADIS16480_DIAG_STAT_XMAGN_FAIL) | | |
828 | BIT(ADIS16480_DIAG_STAT_YMAGN_FAIL) | | |
829 | BIT(ADIS16480_DIAG_STAT_ZMAGN_FAIL) | | |
830 | BIT(ADIS16480_DIAG_STAT_BARO_FAIL), | |
831 | ||
832 | .enable_irq = adis16480_enable_irq, | |
833 | }; | |
834 | ||
835 | static int adis16480_probe(struct spi_device *spi) | |
836 | { | |
837 | const struct spi_device_id *id = spi_get_device_id(spi); | |
838 | struct iio_dev *indio_dev; | |
839 | struct adis16480 *st; | |
840 | int ret; | |
841 | ||
842 | indio_dev = iio_device_alloc(sizeof(*st)); | |
843 | if (indio_dev == NULL) | |
844 | return -ENOMEM; | |
845 | ||
846 | spi_set_drvdata(spi, indio_dev); | |
847 | ||
848 | st = iio_priv(indio_dev); | |
849 | ||
850 | st->chip_info = &adis16480_chip_info[id->driver_data]; | |
851 | indio_dev->dev.parent = &spi->dev; | |
852 | indio_dev->name = spi_get_device_id(spi)->name; | |
853 | indio_dev->channels = st->chip_info->channels; | |
854 | indio_dev->num_channels = st->chip_info->num_channels; | |
855 | indio_dev->info = &adis16480_info; | |
856 | indio_dev->modes = INDIO_DIRECT_MODE; | |
857 | ||
858 | ret = adis_init(&st->adis, indio_dev, spi, &adis16480_data); | |
859 | if (ret) | |
860 | goto error_free_dev; | |
861 | ||
862 | ret = adis_setup_buffer_and_trigger(&st->adis, indio_dev, NULL); | |
863 | if (ret) | |
864 | goto error_free_dev; | |
865 | ||
866 | ret = adis16480_initial_setup(indio_dev); | |
867 | if (ret) | |
868 | goto error_cleanup_buffer; | |
869 | ||
870 | ret = iio_device_register(indio_dev); | |
871 | if (ret) | |
872 | goto error_stop_device; | |
873 | ||
874 | adis16480_debugfs_init(indio_dev); | |
875 | ||
876 | return 0; | |
877 | ||
878 | error_stop_device: | |
879 | adis16480_stop_device(indio_dev); | |
880 | error_cleanup_buffer: | |
881 | adis_cleanup_buffer_and_trigger(&st->adis, indio_dev); | |
882 | error_free_dev: | |
883 | iio_device_free(indio_dev); | |
884 | return ret; | |
885 | } | |
886 | ||
887 | static int adis16480_remove(struct spi_device *spi) | |
888 | { | |
889 | struct iio_dev *indio_dev = spi_get_drvdata(spi); | |
890 | struct adis16480 *st = iio_priv(indio_dev); | |
891 | ||
892 | iio_device_unregister(indio_dev); | |
893 | adis16480_stop_device(indio_dev); | |
894 | ||
895 | adis_cleanup_buffer_and_trigger(&st->adis, indio_dev); | |
896 | ||
897 | iio_device_free(indio_dev); | |
898 | ||
899 | return 0; | |
900 | } | |
901 | ||
902 | static const struct spi_device_id adis16480_ids[] = { | |
903 | { "adis16375", ADIS16375 }, | |
904 | { "adis16480", ADIS16480 }, | |
905 | { "adis16485", ADIS16485 }, | |
906 | { "adis16488", ADIS16488 }, | |
907 | { } | |
908 | }; | |
909 | MODULE_DEVICE_TABLE(spi, adis16480_ids); | |
910 | ||
911 | static struct spi_driver adis16480_driver = { | |
912 | .driver = { | |
913 | .name = "adis16480", | |
914 | .owner = THIS_MODULE, | |
915 | }, | |
916 | .id_table = adis16480_ids, | |
917 | .probe = adis16480_probe, | |
918 | .remove = adis16480_remove, | |
919 | }; | |
920 | module_spi_driver(adis16480_driver); | |
921 | ||
922 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); | |
923 | MODULE_DESCRIPTION("Analog Devices ADIS16480 IMU driver"); | |
924 | MODULE_LICENSE("GPL v2"); |