Commit | Line | Data |
---|---|---|
2c834b4d BS |
1 | /* |
2 | * ADIS16240 Programmable Impact Sensor and Recorder driver | |
3 | * | |
4 | * Copyright 2010 Analog Devices Inc. | |
5 | * | |
6 | * Licensed under the GPL-2 or later. | |
7 | */ | |
8 | ||
9 | #include <linux/interrupt.h> | |
10 | #include <linux/irq.h> | |
11 | #include <linux/gpio.h> | |
12 | #include <linux/delay.h> | |
13 | #include <linux/mutex.h> | |
14 | #include <linux/device.h> | |
15 | #include <linux/kernel.h> | |
16 | #include <linux/spi/spi.h> | |
1cb6c1f5 | 17 | #include <linux/slab.h> |
2c834b4d BS |
18 | #include <linux/sysfs.h> |
19 | #include <linux/list.h> | |
99c97852 | 20 | #include <linux/module.h> |
2c834b4d | 21 | |
06458e27 JC |
22 | #include <linux/iio/iio.h> |
23 | #include <linux/iio/sysfs.h> | |
24 | #include <linux/iio/buffer.h> | |
ec04cb04 | 25 | #include <linux/iio/imu/adis.h> |
2c834b4d BS |
26 | |
27 | #include "adis16240.h" | |
2c834b4d BS |
28 | |
29 | static ssize_t adis16240_spi_read_signed(struct device *dev, | |
30 | struct device_attribute *attr, | |
31 | char *buf, | |
32 | unsigned bits) | |
33 | { | |
4b522ce7 | 34 | struct iio_dev *indio_dev = dev_to_iio_dev(dev); |
5cb7cb11 | 35 | struct adis *st = iio_priv(indio_dev); |
2c834b4d BS |
36 | int ret; |
37 | s16 val = 0; | |
38 | unsigned shift = 16 - bits; | |
39 | struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); | |
40 | ||
5cb7cb11 | 41 | ret = adis_read_reg_16(st, |
cd69f57d | 42 | this_attr->address, (u16 *)&val); |
2c834b4d BS |
43 | if (ret) |
44 | return ret; | |
45 | ||
46 | if (val & ADIS16240_ERROR_ACTIVE) | |
5cb7cb11 | 47 | adis_check_status(st); |
2c834b4d BS |
48 | |
49 | val = ((s16)(val << shift) >> shift); | |
50 | return sprintf(buf, "%d\n", val); | |
51 | } | |
52 | ||
2c834b4d BS |
53 | static ssize_t adis16240_read_12bit_signed(struct device *dev, |
54 | struct device_attribute *attr, | |
55 | char *buf) | |
56 | { | |
2c834b4d | 57 | ssize_t ret; |
4b522ce7 | 58 | struct iio_dev *indio_dev = dev_to_iio_dev(dev); |
2c834b4d BS |
59 | |
60 | /* Take the iio_dev status lock */ | |
61 | mutex_lock(&indio_dev->mlock); | |
62 | ret = adis16240_spi_read_signed(dev, attr, buf, 12); | |
63 | mutex_unlock(&indio_dev->mlock); | |
64 | ||
65 | return ret; | |
66 | } | |
67 | ||
322c9563 | 68 | static IIO_DEVICE_ATTR(in_accel_xyz_squared_peak_raw, S_IRUGO, |
2c834b4d BS |
69 | adis16240_read_12bit_signed, NULL, |
70 | ADIS16240_XYZPEAK_OUT); | |
2c834b4d | 71 | |
51a0a5b0 | 72 | static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("4096"); |
2c834b4d | 73 | |
5cb7cb11 LPC |
74 | static const u8 adis16240_addresses[][2] = { |
75 | [ADIS16240_SCAN_ACC_X] = { ADIS16240_XACCL_OFF, ADIS16240_XPEAK_OUT }, | |
76 | [ADIS16240_SCAN_ACC_Y] = { ADIS16240_YACCL_OFF, ADIS16240_YPEAK_OUT }, | |
77 | [ADIS16240_SCAN_ACC_Z] = { ADIS16240_ZACCL_OFF, ADIS16240_ZPEAK_OUT }, | |
cd69f57d JC |
78 | }; |
79 | ||
80 | static int adis16240_read_raw(struct iio_dev *indio_dev, | |
81 | struct iio_chan_spec const *chan, | |
82 | int *val, int *val2, | |
83 | long mask) | |
84 | { | |
5cb7cb11 | 85 | struct adis *st = iio_priv(indio_dev); |
cd69f57d JC |
86 | int ret; |
87 | int bits; | |
88 | u8 addr; | |
89 | s16 val16; | |
90 | ||
91 | switch (mask) { | |
31313fc6 | 92 | case IIO_CHAN_INFO_RAW: |
5cb7cb11 LPC |
93 | return adis_single_conversion(indio_dev, chan, |
94 | ADIS16240_ERROR_ACTIVE, val); | |
c8a9f805 | 95 | case IIO_CHAN_INFO_SCALE: |
cd69f57d | 96 | switch (chan->type) { |
6835cb6b | 97 | case IIO_VOLTAGE: |
acba41f8 LPC |
98 | if (chan->channel == 0) { |
99 | *val = 4; | |
100 | *val2 = 880000; /* 4.88 mV */ | |
101 | return IIO_VAL_INT_PLUS_MICRO; | |
102 | } else { | |
cd69f57d | 103 | return -EINVAL; |
acba41f8 | 104 | } |
cd69f57d | 105 | case IIO_TEMP: |
acba41f8 LPC |
106 | *val = 244; /* 0.244 C */ |
107 | *val2 = 0; | |
cd69f57d JC |
108 | return IIO_VAL_INT_PLUS_MICRO; |
109 | case IIO_ACCEL: | |
110 | *val = 0; | |
acba41f8 | 111 | *val2 = IIO_G_TO_M_S_2(51400); /* 51.4 mg */ |
cd69f57d JC |
112 | return IIO_VAL_INT_PLUS_MICRO; |
113 | default: | |
114 | return -EINVAL; | |
115 | } | |
116 | break; | |
c8a9f805 | 117 | case IIO_CHAN_INFO_PEAK_SCALE: |
acba41f8 LPC |
118 | *val = 0; |
119 | *val2 = IIO_G_TO_M_S_2(51400); /* 51.4 mg */ | |
cd69f57d | 120 | return IIO_VAL_INT_PLUS_MICRO; |
c8a9f805 | 121 | case IIO_CHAN_INFO_OFFSET: |
acba41f8 | 122 | *val = 25000 / 244 - 0x133; /* 25 C = 0x133 */ |
cd69f57d | 123 | return IIO_VAL_INT; |
c8a9f805 | 124 | case IIO_CHAN_INFO_CALIBBIAS: |
cd69f57d JC |
125 | bits = 10; |
126 | mutex_lock(&indio_dev->mlock); | |
5cb7cb11 LPC |
127 | addr = adis16240_addresses[chan->scan_index][0]; |
128 | ret = adis_read_reg_16(st, addr, &val16); | |
cd69f57d JC |
129 | if (ret) { |
130 | mutex_unlock(&indio_dev->mlock); | |
131 | return ret; | |
132 | } | |
133 | val16 &= (1 << bits) - 1; | |
134 | val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); | |
135 | *val = val16; | |
136 | mutex_unlock(&indio_dev->mlock); | |
137 | return IIO_VAL_INT; | |
c8a9f805 | 138 | case IIO_CHAN_INFO_PEAK: |
cd69f57d JC |
139 | bits = 10; |
140 | mutex_lock(&indio_dev->mlock); | |
5cb7cb11 LPC |
141 | addr = adis16240_addresses[chan->scan_index][1]; |
142 | ret = adis_read_reg_16(st, addr, &val16); | |
cd69f57d JC |
143 | if (ret) { |
144 | mutex_unlock(&indio_dev->mlock); | |
145 | return ret; | |
146 | } | |
147 | val16 &= (1 << bits) - 1; | |
148 | val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); | |
149 | *val = val16; | |
150 | mutex_unlock(&indio_dev->mlock); | |
151 | return IIO_VAL_INT; | |
152 | } | |
153 | return -EINVAL; | |
154 | } | |
155 | ||
156 | static int adis16240_write_raw(struct iio_dev *indio_dev, | |
157 | struct iio_chan_spec const *chan, | |
158 | int val, | |
159 | int val2, | |
160 | long mask) | |
161 | { | |
5cb7cb11 | 162 | struct adis *st = iio_priv(indio_dev); |
cd69f57d JC |
163 | int bits = 10; |
164 | s16 val16; | |
165 | u8 addr; | |
166 | switch (mask) { | |
c8a9f805 | 167 | case IIO_CHAN_INFO_CALIBBIAS: |
cd69f57d | 168 | val16 = val & ((1 << bits) - 1); |
5cb7cb11 LPC |
169 | addr = adis16240_addresses[chan->scan_index][0]; |
170 | return adis_write_reg_16(st, addr, val16); | |
cd69f57d JC |
171 | } |
172 | return -EINVAL; | |
173 | } | |
174 | ||
f4e4b955 | 175 | static const struct iio_chan_spec adis16240_channels[] = { |
5cb7cb11 LPC |
176 | ADIS_SUPPLY_CHAN(ADIS16240_SUPPLY_OUT, ADIS16240_SCAN_SUPPLY, 10), |
177 | ADIS_AUX_ADC_CHAN(ADIS16240_AUX_ADC, ADIS16240_SCAN_AUX_ADC, 10), | |
178 | ADIS_ACCEL_CHAN(X, ADIS16240_XACCL_OUT, ADIS16240_SCAN_ACC_X, | |
06d5199d | 179 | IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | |
5cb7cb11 LPC |
180 | IIO_CHAN_INFO_PEAK_SEPARATE_BIT, 10), |
181 | ADIS_ACCEL_CHAN(Y, ADIS16240_YACCL_OUT, ADIS16240_SCAN_ACC_Y, | |
06d5199d | 182 | IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | |
5cb7cb11 LPC |
183 | IIO_CHAN_INFO_PEAK_SEPARATE_BIT, 10), |
184 | ADIS_ACCEL_CHAN(Z, ADIS16240_ZACCL_OUT, ADIS16240_SCAN_ACC_Z, | |
06d5199d | 185 | IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | |
5cb7cb11 LPC |
186 | IIO_CHAN_INFO_PEAK_SEPARATE_BIT, 10), |
187 | ADIS_TEMP_CHAN(ADIS16240_TEMP_OUT, ADIS16240_SCAN_TEMP, 10), | |
cd69f57d JC |
188 | IIO_CHAN_SOFT_TIMESTAMP(6) |
189 | }; | |
190 | ||
2c834b4d | 191 | static struct attribute *adis16240_attributes[] = { |
322c9563 | 192 | &iio_dev_attr_in_accel_xyz_squared_peak_raw.dev_attr.attr, |
51a0a5b0 | 193 | &iio_const_attr_sampling_frequency_available.dev_attr.attr, |
2c834b4d BS |
194 | NULL |
195 | }; | |
196 | ||
197 | static const struct attribute_group adis16240_attribute_group = { | |
198 | .attrs = adis16240_attributes, | |
199 | }; | |
200 | ||
6fe8135f JC |
201 | static const struct iio_info adis16240_info = { |
202 | .attrs = &adis16240_attribute_group, | |
203 | .read_raw = &adis16240_read_raw, | |
204 | .write_raw = &adis16240_write_raw, | |
aacff892 | 205 | .update_scan_mode = adis_update_scan_mode, |
6fe8135f JC |
206 | .driver_module = THIS_MODULE, |
207 | }; | |
208 | ||
5cb7cb11 LPC |
209 | static const char * const adis16240_status_error_msgs[] = { |
210 | [ADIS16240_DIAG_STAT_PWRON_FAIL_BIT] = "Power on, self-test failed", | |
211 | [ADIS16240_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", | |
212 | [ADIS16240_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed", | |
213 | [ADIS16240_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", | |
214 | [ADIS16240_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 2.225V", | |
215 | }; | |
216 | ||
217 | static const struct adis_data adis16240_data = { | |
218 | .write_delay = 35, | |
219 | .read_delay = 35, | |
220 | .msc_ctrl_reg = ADIS16240_MSC_CTRL, | |
221 | .glob_cmd_reg = ADIS16240_GLOB_CMD, | |
222 | .diag_stat_reg = ADIS16240_DIAG_STAT, | |
223 | ||
224 | .self_test_mask = ADIS16240_MSC_CTRL_SELF_TEST_EN, | |
225 | .startup_delay = ADIS16240_STARTUP_DELAY, | |
226 | ||
227 | .status_error_msgs = adis16240_status_error_msgs, | |
228 | .status_error_mask = BIT(ADIS16240_DIAG_STAT_PWRON_FAIL_BIT) | | |
229 | BIT(ADIS16240_DIAG_STAT_SPI_FAIL_BIT) | | |
230 | BIT(ADIS16240_DIAG_STAT_FLASH_UPT_BIT) | | |
231 | BIT(ADIS16240_DIAG_STAT_POWER_HIGH_BIT) | | |
232 | BIT(ADIS16240_DIAG_STAT_POWER_LOW_BIT), | |
233 | }; | |
234 | ||
4ae1c61f | 235 | static int adis16240_probe(struct spi_device *spi) |
2c834b4d | 236 | { |
26d25ae3 | 237 | int ret; |
5cb7cb11 | 238 | struct adis *st; |
a22ff706 JC |
239 | struct iio_dev *indio_dev; |
240 | ||
241 | /* setup the industrialio driver allocated elements */ | |
7cbb7537 | 242 | indio_dev = iio_device_alloc(sizeof(*st)); |
a22ff706 JC |
243 | if (indio_dev == NULL) { |
244 | ret = -ENOMEM; | |
2c834b4d BS |
245 | goto error_ret; |
246 | } | |
a22ff706 | 247 | st = iio_priv(indio_dev); |
2c834b4d | 248 | /* this is only used for removal purposes */ |
a22ff706 | 249 | spi_set_drvdata(spi, indio_dev); |
2c834b4d | 250 | |
a22ff706 JC |
251 | indio_dev->name = spi->dev.driver->name; |
252 | indio_dev->dev.parent = &spi->dev; | |
253 | indio_dev->info = &adis16240_info; | |
254 | indio_dev->channels = adis16240_channels; | |
255 | indio_dev->num_channels = ARRAY_SIZE(adis16240_channels); | |
256 | indio_dev->modes = INDIO_DIRECT_MODE; | |
2c834b4d | 257 | |
5cb7cb11 LPC |
258 | ret = adis_init(st, indio_dev, spi, &adis16240_data); |
259 | if (ret) | |
260 | goto error_free_dev; | |
261 | ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL); | |
2c834b4d BS |
262 | if (ret) |
263 | goto error_free_dev; | |
2c834b4d BS |
264 | |
265 | /* Get the device into a sane initial state */ | |
5cb7cb11 | 266 | ret = adis_initial_startup(st); |
26d25ae3 | 267 | if (ret) |
5cb7cb11 | 268 | goto error_cleanup_buffer_trigger; |
26d25ae3 | 269 | ret = iio_device_register(indio_dev); |
2c834b4d | 270 | if (ret) |
5cb7cb11 | 271 | goto error_cleanup_buffer_trigger; |
2c834b4d BS |
272 | return 0; |
273 | ||
5cb7cb11 LPC |
274 | error_cleanup_buffer_trigger: |
275 | adis_cleanup_buffer_and_trigger(st, indio_dev); | |
2c834b4d | 276 | error_free_dev: |
7cbb7537 | 277 | iio_device_free(indio_dev); |
2c834b4d BS |
278 | error_ret: |
279 | return ret; | |
280 | } | |
281 | ||
8e828752 | 282 | static int __devexit adis16240_remove(struct spi_device *spi) |
2c834b4d | 283 | { |
a22ff706 | 284 | struct iio_dev *indio_dev = spi_get_drvdata(spi); |
5cb7cb11 | 285 | struct adis *st = iio_priv(indio_dev); |
2c834b4d | 286 | |
d2fffd6c | 287 | iio_device_unregister(indio_dev); |
5cb7cb11 | 288 | adis_cleanup_buffer_and_trigger(st, indio_dev); |
7cbb7537 | 289 | iio_device_free(indio_dev); |
2c834b4d BS |
290 | |
291 | return 0; | |
292 | } | |
293 | ||
294 | static struct spi_driver adis16240_driver = { | |
295 | .driver = { | |
296 | .name = "adis16240", | |
297 | .owner = THIS_MODULE, | |
298 | }, | |
299 | .probe = adis16240_probe, | |
300 | .remove = __devexit_p(adis16240_remove), | |
301 | }; | |
ae6ae6fe | 302 | module_spi_driver(adis16240_driver); |
2c834b4d BS |
303 | |
304 | MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); | |
305 | MODULE_DESCRIPTION("Analog Devices Programmable Impact Sensor and Recorder"); | |
306 | MODULE_LICENSE("GPL v2"); | |
55e4390c | 307 | MODULE_ALIAS("spi:adis16240"); |