Commit | Line | Data |
---|---|---|
66533b48 JC |
1 | /* |
2 | * lis3l02dq.c support STMicroelectronics LISD02DQ | |
3 | * 3d 2g Linear Accelerometers via SPI | |
4 | * | |
5 | * Copyright (c) 2007 Jonathan Cameron <jic23@cam.ac.uk> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | * | |
11 | * Settings: | |
12 | * 16 bit left justified mode used. | |
13 | */ | |
14 | ||
15 | #include <linux/interrupt.h> | |
16 | #include <linux/irq.h> | |
17 | #include <linux/gpio.h> | |
18 | #include <linux/workqueue.h> | |
19 | #include <linux/mutex.h> | |
20 | #include <linux/device.h> | |
21 | #include <linux/kernel.h> | |
22 | #include <linux/spi/spi.h> | |
5a0e3ad6 | 23 | #include <linux/slab.h> |
66533b48 JC |
24 | |
25 | #include <linux/sysfs.h> | |
26 | #include <linux/list.h> | |
27 | ||
28 | #include "../iio.h" | |
29 | #include "../sysfs.h" | |
2662051e | 30 | #include "../ring_generic.h" |
73bce12e JC |
31 | #include "../ring_sw.h" |
32 | ||
66533b48 JC |
33 | #include "accel.h" |
34 | ||
35 | #include "lis3l02dq.h" | |
36 | ||
37 | /* At the moment the spi framework doesn't allow global setting of cs_change. | |
38 | * It's in the likely to be added comment at the top of spi.h. | |
39 | * This means that use cannot be made of spi_write etc. | |
40 | */ | |
41 | ||
1b076b52 JC |
42 | /** |
43 | * lis3l02dq_spi_read_reg_8() - read single byte from a single register | |
44 | * @indio_dev: iio_dev for this actual device | |
45 | * @reg_address: the address of the register to be read | |
46 | * @val: pass back the resulting value | |
47 | **/ | |
48 | int lis3l02dq_spi_read_reg_8(struct iio_dev *indio_dev, | |
49 | u8 reg_address, u8 *val) | |
66533b48 | 50 | { |
1b076b52 JC |
51 | struct iio_sw_ring_helper_state *h = iio_dev_get_devdata(indio_dev); |
52 | struct lis3l02dq_state *st = lis3l02dq_h_to_s(h); | |
66533b48 | 53 | struct spi_message msg; |
f3736416 | 54 | int ret; |
66533b48 JC |
55 | struct spi_transfer xfer = { |
56 | .tx_buf = st->tx, | |
57 | .rx_buf = st->rx, | |
58 | .bits_per_word = 8, | |
59 | .len = 2, | |
66533b48 JC |
60 | }; |
61 | ||
62 | mutex_lock(&st->buf_lock); | |
63 | st->tx[0] = LIS3L02DQ_READ_REG(reg_address); | |
64 | st->tx[1] = 0; | |
65 | ||
66 | spi_message_init(&msg); | |
67 | spi_message_add_tail(&xfer, &msg); | |
68 | ret = spi_sync(st->us, &msg); | |
69 | *val = st->rx[1]; | |
70 | mutex_unlock(&st->buf_lock); | |
71 | ||
72 | return ret; | |
73 | } | |
74 | ||
75 | /** | |
76 | * lis3l02dq_spi_write_reg_8() - write single byte to a register | |
1b076b52 | 77 | * @indio_dev: iio_dev for this device |
25985edc | 78 | * @reg_address: the address of the register to be written |
66533b48 JC |
79 | * @val: the value to write |
80 | **/ | |
1b076b52 | 81 | int lis3l02dq_spi_write_reg_8(struct iio_dev *indio_dev, |
66533b48 JC |
82 | u8 reg_address, |
83 | u8 *val) | |
84 | { | |
85 | int ret; | |
73bce12e JC |
86 | struct iio_sw_ring_helper_state *h |
87 | = iio_dev_get_devdata(indio_dev); | |
88 | struct lis3l02dq_state *st = lis3l02dq_h_to_s(h); | |
66533b48 JC |
89 | |
90 | mutex_lock(&st->buf_lock); | |
91 | st->tx[0] = LIS3L02DQ_WRITE_REG(reg_address); | |
92 | st->tx[1] = *val; | |
1b076b52 | 93 | ret = spi_write(st->us, st->tx, 2); |
66533b48 JC |
94 | mutex_unlock(&st->buf_lock); |
95 | ||
96 | return ret; | |
97 | } | |
98 | ||
99 | /** | |
100 | * lisl302dq_spi_write_reg_s16() - write 2 bytes to a pair of registers | |
1b076b52 JC |
101 | * @indio_dev: iio_dev for this device |
102 | * @lower_reg_address: the address of the lower of the two registers. | |
103 | * Second register is assumed to have address one greater. | |
104 | * @value: value to be written | |
66533b48 | 105 | **/ |
1b076b52 | 106 | static int lis3l02dq_spi_write_reg_s16(struct iio_dev *indio_dev, |
66533b48 JC |
107 | u8 lower_reg_address, |
108 | s16 value) | |
109 | { | |
110 | int ret; | |
111 | struct spi_message msg; | |
73bce12e JC |
112 | struct iio_sw_ring_helper_state *h |
113 | = iio_dev_get_devdata(indio_dev); | |
114 | struct lis3l02dq_state *st = lis3l02dq_h_to_s(h); | |
66533b48 JC |
115 | struct spi_transfer xfers[] = { { |
116 | .tx_buf = st->tx, | |
117 | .bits_per_word = 8, | |
118 | .len = 2, | |
119 | .cs_change = 1, | |
120 | }, { | |
121 | .tx_buf = st->tx + 2, | |
122 | .bits_per_word = 8, | |
123 | .len = 2, | |
66533b48 JC |
124 | }, |
125 | }; | |
126 | ||
127 | mutex_lock(&st->buf_lock); | |
128 | st->tx[0] = LIS3L02DQ_WRITE_REG(lower_reg_address); | |
129 | st->tx[1] = value & 0xFF; | |
130 | st->tx[2] = LIS3L02DQ_WRITE_REG(lower_reg_address + 1); | |
131 | st->tx[3] = (value >> 8) & 0xFF; | |
132 | ||
133 | spi_message_init(&msg); | |
134 | spi_message_add_tail(&xfers[0], &msg); | |
135 | spi_message_add_tail(&xfers[1], &msg); | |
136 | ret = spi_sync(st->us, &msg); | |
137 | mutex_unlock(&st->buf_lock); | |
138 | ||
139 | return ret; | |
140 | } | |
141 | ||
1b076b52 | 142 | static int lis3l02dq_read_reg_s16(struct iio_dev *indio_dev, |
f3736416 JC |
143 | u8 lower_reg_address, |
144 | int *val) | |
66533b48 | 145 | { |
1b076b52 JC |
146 | struct iio_sw_ring_helper_state *h |
147 | = iio_dev_get_devdata(indio_dev); | |
148 | struct lis3l02dq_state *st = lis3l02dq_h_to_s(h); | |
149 | ||
66533b48 | 150 | struct spi_message msg; |
66533b48 | 151 | int ret; |
f3736416 | 152 | s16 tempval; |
66533b48 JC |
153 | struct spi_transfer xfers[] = { { |
154 | .tx_buf = st->tx, | |
155 | .rx_buf = st->rx, | |
156 | .bits_per_word = 8, | |
157 | .len = 2, | |
158 | .cs_change = 1, | |
159 | }, { | |
160 | .tx_buf = st->tx + 2, | |
161 | .rx_buf = st->rx + 2, | |
162 | .bits_per_word = 8, | |
163 | .len = 2, | |
66533b48 JC |
164 | }, |
165 | }; | |
166 | ||
167 | mutex_lock(&st->buf_lock); | |
168 | st->tx[0] = LIS3L02DQ_READ_REG(lower_reg_address); | |
169 | st->tx[1] = 0; | |
f3736416 | 170 | st->tx[2] = LIS3L02DQ_READ_REG(lower_reg_address + 1); |
66533b48 JC |
171 | st->tx[3] = 0; |
172 | ||
173 | spi_message_init(&msg); | |
174 | spi_message_add_tail(&xfers[0], &msg); | |
175 | spi_message_add_tail(&xfers[1], &msg); | |
176 | ret = spi_sync(st->us, &msg); | |
177 | if (ret) { | |
178 | dev_err(&st->us->dev, "problem when reading 16 bit register"); | |
179 | goto error_ret; | |
180 | } | |
f3736416 | 181 | tempval = (s16)(st->rx[1]) | ((s16)(st->rx[3]) << 8); |
66533b48 | 182 | |
f3736416 | 183 | *val = tempval; |
66533b48 JC |
184 | error_ret: |
185 | mutex_unlock(&st->buf_lock); | |
186 | return ret; | |
187 | } | |
188 | ||
f3736416 JC |
189 | enum lis3l02dq_rm_ind { |
190 | LIS3L02DQ_ACCEL, | |
191 | LIS3L02DQ_GAIN, | |
192 | LIS3L02DQ_BIAS, | |
193 | }; | |
66533b48 | 194 | |
f3736416 JC |
195 | static u8 lis3l02dq_axis_map[3][3] = { |
196 | [LIS3L02DQ_ACCEL] = { LIS3L02DQ_REG_OUT_X_L_ADDR, | |
197 | LIS3L02DQ_REG_OUT_Y_L_ADDR, | |
198 | LIS3L02DQ_REG_OUT_Z_L_ADDR }, | |
199 | [LIS3L02DQ_GAIN] = { LIS3L02DQ_REG_GAIN_X_ADDR, | |
200 | LIS3L02DQ_REG_GAIN_Y_ADDR, | |
201 | LIS3L02DQ_REG_GAIN_Z_ADDR }, | |
202 | [LIS3L02DQ_BIAS] = { LIS3L02DQ_REG_OFFSET_X_ADDR, | |
203 | LIS3L02DQ_REG_OFFSET_Y_ADDR, | |
204 | LIS3L02DQ_REG_OFFSET_Z_ADDR } | |
205 | }; | |
66533b48 | 206 | |
f3736416 JC |
207 | static int lis3l02dq_read_thresh(struct iio_dev *indio_dev, |
208 | int e, | |
209 | int *val) | |
66533b48 | 210 | { |
1b076b52 | 211 | return lis3l02dq_read_reg_s16(indio_dev, LIS3L02DQ_REG_THS_L_ADDR, val); |
66533b48 JC |
212 | } |
213 | ||
f3736416 JC |
214 | static int lis3l02dq_write_thresh(struct iio_dev *indio_dev, |
215 | int event_code, | |
216 | int val) | |
66533b48 | 217 | { |
f3736416 | 218 | u16 value = val; |
1b076b52 | 219 | return lis3l02dq_spi_write_reg_s16(indio_dev, |
f3736416 JC |
220 | LIS3L02DQ_REG_THS_L_ADDR, |
221 | value); | |
66533b48 JC |
222 | } |
223 | ||
f3736416 JC |
224 | static int lis3l02dq_read_raw(struct iio_dev *indio_dev, |
225 | struct iio_chan_spec const *chan, | |
226 | int *val, | |
227 | int *val2, | |
228 | long mask) | |
66533b48 | 229 | { |
f3736416 JC |
230 | u8 utemp; |
231 | s8 stemp; | |
232 | ssize_t ret = 0; | |
f3736416 | 233 | u8 reg; |
1b076b52 | 234 | |
f3736416 JC |
235 | switch (mask) { |
236 | case 0: | |
237 | /* Take the iio_dev status lock */ | |
238 | mutex_lock(&indio_dev->mlock); | |
239 | if (indio_dev->currentmode == INDIO_RING_TRIGGERED) | |
240 | ret = lis3l02dq_read_accel_from_ring(indio_dev->ring, | |
241 | chan->scan_index, | |
242 | val); | |
243 | else { | |
244 | reg = lis3l02dq_axis_map | |
245 | [LIS3L02DQ_ACCEL][chan->address]; | |
1b076b52 | 246 | ret = lis3l02dq_read_reg_s16(indio_dev, reg, val); |
f3736416 JC |
247 | } |
248 | mutex_unlock(&indio_dev->mlock); | |
249 | return IIO_VAL_INT; | |
250 | case (1 << IIO_CHAN_INFO_SCALE_SHARED): | |
251 | *val = 0; | |
252 | *val2 = 9580; | |
253 | return IIO_VAL_INT_PLUS_MICRO; | |
254 | case (1 << IIO_CHAN_INFO_CALIBSCALE_SEPARATE): | |
255 | reg = lis3l02dq_axis_map[LIS3L02DQ_GAIN][chan->address]; | |
1b076b52 | 256 | ret = lis3l02dq_spi_read_reg_8(indio_dev, reg, &utemp); |
f3736416 JC |
257 | if (ret) |
258 | goto error_ret; | |
259 | /* to match with what previous code does */ | |
260 | *val = utemp; | |
261 | return IIO_VAL_INT; | |
262 | ||
263 | case (1 << IIO_CHAN_INFO_CALIBBIAS_SEPARATE): | |
264 | reg = lis3l02dq_axis_map[LIS3L02DQ_BIAS][chan->address]; | |
1b076b52 | 265 | ret = lis3l02dq_spi_read_reg_8(indio_dev, reg, (u8 *)&stemp); |
f3736416 JC |
266 | /* to match with what previous code does */ |
267 | *val = stemp; | |
268 | return IIO_VAL_INT; | |
269 | } | |
66533b48 | 270 | error_ret: |
f3736416 | 271 | return ret; |
66533b48 JC |
272 | } |
273 | ||
274 | static ssize_t lis3l02dq_read_frequency(struct device *dev, | |
275 | struct device_attribute *attr, | |
276 | char *buf) | |
277 | { | |
1b076b52 | 278 | struct iio_dev *indio_dev = dev_get_drvdata(dev); |
66533b48 JC |
279 | int ret, len = 0; |
280 | s8 t; | |
1b076b52 | 281 | ret = lis3l02dq_spi_read_reg_8(indio_dev, |
66533b48 JC |
282 | LIS3L02DQ_REG_CTRL_1_ADDR, |
283 | (u8 *)&t); | |
284 | if (ret) | |
285 | return ret; | |
286 | t &= LIS3L02DQ_DEC_MASK; | |
287 | switch (t) { | |
288 | case LIS3L02DQ_REG_CTRL_1_DF_128: | |
289 | len = sprintf(buf, "280\n"); | |
290 | break; | |
291 | case LIS3L02DQ_REG_CTRL_1_DF_64: | |
292 | len = sprintf(buf, "560\n"); | |
293 | break; | |
294 | case LIS3L02DQ_REG_CTRL_1_DF_32: | |
295 | len = sprintf(buf, "1120\n"); | |
296 | break; | |
297 | case LIS3L02DQ_REG_CTRL_1_DF_8: | |
298 | len = sprintf(buf, "4480\n"); | |
299 | break; | |
300 | } | |
301 | return len; | |
302 | } | |
303 | ||
304 | static ssize_t lis3l02dq_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_get_drvdata(dev); | |
310 | long val; | |
311 | int ret; | |
312 | u8 t; | |
313 | ||
314 | ret = strict_strtol(buf, 10, &val); | |
315 | if (ret) | |
316 | return ret; | |
317 | ||
318 | mutex_lock(&indio_dev->mlock); | |
1b076b52 | 319 | ret = lis3l02dq_spi_read_reg_8(indio_dev, |
66533b48 JC |
320 | LIS3L02DQ_REG_CTRL_1_ADDR, |
321 | &t); | |
322 | if (ret) | |
323 | goto error_ret_mutex; | |
324 | /* Wipe the bits clean */ | |
325 | t &= ~LIS3L02DQ_DEC_MASK; | |
326 | switch (val) { | |
327 | case 280: | |
328 | t |= LIS3L02DQ_REG_CTRL_1_DF_128; | |
329 | break; | |
330 | case 560: | |
331 | t |= LIS3L02DQ_REG_CTRL_1_DF_64; | |
332 | break; | |
333 | case 1120: | |
334 | t |= LIS3L02DQ_REG_CTRL_1_DF_32; | |
335 | break; | |
336 | case 4480: | |
337 | t |= LIS3L02DQ_REG_CTRL_1_DF_8; | |
338 | break; | |
339 | default: | |
340 | ret = -EINVAL; | |
341 | goto error_ret_mutex; | |
95cd17c9 | 342 | } |
66533b48 | 343 | |
1b076b52 | 344 | ret = lis3l02dq_spi_write_reg_8(indio_dev, |
66533b48 JC |
345 | LIS3L02DQ_REG_CTRL_1_ADDR, |
346 | &t); | |
347 | ||
348 | error_ret_mutex: | |
349 | mutex_unlock(&indio_dev->mlock); | |
350 | ||
351 | return ret ? ret : len; | |
352 | } | |
353 | ||
354 | static int lis3l02dq_initial_setup(struct lis3l02dq_state *st) | |
355 | { | |
356 | int ret; | |
357 | u8 val, valtest; | |
358 | ||
359 | st->us->mode = SPI_MODE_3; | |
360 | ||
361 | spi_setup(st->us); | |
362 | ||
363 | val = LIS3L02DQ_DEFAULT_CTRL1; | |
364 | /* Write suitable defaults to ctrl1 */ | |
1b076b52 | 365 | ret = lis3l02dq_spi_write_reg_8(st->help.indio_dev, |
66533b48 JC |
366 | LIS3L02DQ_REG_CTRL_1_ADDR, |
367 | &val); | |
368 | if (ret) { | |
369 | dev_err(&st->us->dev, "problem with setup control register 1"); | |
370 | goto err_ret; | |
371 | } | |
372 | /* Repeat as sometimes doesn't work first time?*/ | |
1b076b52 | 373 | ret = lis3l02dq_spi_write_reg_8(st->help.indio_dev, |
66533b48 JC |
374 | LIS3L02DQ_REG_CTRL_1_ADDR, |
375 | &val); | |
376 | if (ret) { | |
377 | dev_err(&st->us->dev, "problem with setup control register 1"); | |
378 | goto err_ret; | |
379 | } | |
380 | ||
381 | /* Read back to check this has worked acts as loose test of correct | |
382 | * chip */ | |
1b076b52 | 383 | ret = lis3l02dq_spi_read_reg_8(st->help.indio_dev, |
66533b48 JC |
384 | LIS3L02DQ_REG_CTRL_1_ADDR, |
385 | &valtest); | |
386 | if (ret || (valtest != val)) { | |
1b076b52 JC |
387 | dev_err(&st->help.indio_dev->dev, |
388 | "device not playing ball %d %d\n", valtest, val); | |
66533b48 JC |
389 | ret = -EINVAL; |
390 | goto err_ret; | |
391 | } | |
392 | ||
393 | val = LIS3L02DQ_DEFAULT_CTRL2; | |
1b076b52 | 394 | ret = lis3l02dq_spi_write_reg_8(st->help.indio_dev, |
66533b48 JC |
395 | LIS3L02DQ_REG_CTRL_2_ADDR, |
396 | &val); | |
397 | if (ret) { | |
398 | dev_err(&st->us->dev, "problem with setup control register 2"); | |
399 | goto err_ret; | |
400 | } | |
401 | ||
402 | val = LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC; | |
1b076b52 | 403 | ret = lis3l02dq_spi_write_reg_8(st->help.indio_dev, |
66533b48 JC |
404 | LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, |
405 | &val); | |
406 | if (ret) | |
407 | dev_err(&st->us->dev, "problem with interrupt cfg register"); | |
408 | err_ret: | |
409 | ||
410 | return ret; | |
411 | } | |
412 | ||
66533b48 JC |
413 | static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, |
414 | lis3l02dq_read_frequency, | |
415 | lis3l02dq_write_frequency); | |
416 | ||
f3fb0011 | 417 | static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("280 560 1120 4480"); |
66533b48 | 418 | |
aaf370db | 419 | static irqreturn_t lis3l02dq_event_handler(int irq, void *private) |
66533b48 | 420 | { |
aaf370db | 421 | struct iio_dev *indio_dev = private; |
f3736416 JC |
422 | struct iio_sw_ring_helper_state *h |
423 | = iio_dev_get_devdata(indio_dev); | |
424 | struct lis3l02dq_state *st = lis3l02dq_h_to_s(h); | |
66533b48 | 425 | |
1e3345bc JC |
426 | disable_irq_nosync(irq); |
427 | st->thresh_timestamp = iio_get_time_ns(); | |
f3736416 | 428 | schedule_work(&st->work_thresh); |
66533b48 | 429 | |
1e3345bc | 430 | return IRQ_HANDLED; |
66533b48 JC |
431 | } |
432 | ||
f3736416 JC |
433 | #define LIS3L02DQ_INFO_MASK \ |
434 | ((1 << IIO_CHAN_INFO_SCALE_SHARED) | \ | |
435 | (1 << IIO_CHAN_INFO_CALIBSCALE_SEPARATE) | \ | |
436 | (1 << IIO_CHAN_INFO_CALIBBIAS_SEPARATE)) | |
437 | ||
438 | #define LIS3L02DQ_EVENT_MASK \ | |
439 | (IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) | \ | |
440 | IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING)) | |
441 | ||
442 | static struct iio_chan_spec lis3l02dq_channels[] = { | |
443 | IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_X, LIS3L02DQ_INFO_MASK, | |
aaf370db | 444 | 0, 0, IIO_ST('s', 12, 16, 0), LIS3L02DQ_EVENT_MASK), |
f3736416 | 445 | IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_Y, LIS3L02DQ_INFO_MASK, |
aaf370db | 446 | 1, 1, IIO_ST('s', 12, 16, 0), LIS3L02DQ_EVENT_MASK), |
f3736416 | 447 | IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_Z, LIS3L02DQ_INFO_MASK, |
aaf370db | 448 | 2, 2, IIO_ST('s', 12, 16, 0), LIS3L02DQ_EVENT_MASK), |
f3736416 JC |
449 | IIO_CHAN_SOFT_TIMESTAMP(3) |
450 | }; | |
451 | ||
452 | ||
453 | static ssize_t lis3l02dq_read_event_config(struct iio_dev *indio_dev, | |
454 | int event_code) | |
455 | { | |
456 | ||
457 | u8 val; | |
458 | int ret; | |
459 | u8 mask = (1 << (IIO_EVENT_CODE_EXTRACT_MODIFIER(event_code)*2 + | |
460 | (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == | |
461 | IIO_EV_DIR_RISING))); | |
1b076b52 | 462 | ret = lis3l02dq_spi_read_reg_8(indio_dev, |
66533b48 | 463 | LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, |
f3736416 JC |
464 | &val); |
465 | if (ret < 0) | |
466 | return ret; | |
467 | ||
468 | return !!(val & mask); | |
469 | } | |
66533b48 | 470 | |
1e3345bc JC |
471 | int lis3l02dq_disable_all_events(struct iio_dev *indio_dev) |
472 | { | |
473 | struct iio_sw_ring_helper_state *h | |
474 | = iio_dev_get_devdata(indio_dev); | |
475 | struct lis3l02dq_state *st = lis3l02dq_h_to_s(h); | |
476 | int ret; | |
477 | u8 control, val; | |
478 | bool irqtofree; | |
479 | ||
480 | ret = lis3l02dq_spi_read_reg_8(indio_dev, | |
481 | LIS3L02DQ_REG_CTRL_2_ADDR, | |
482 | &control); | |
483 | ||
484 | irqtofree = !!(control & LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT); | |
485 | ||
486 | control &= ~LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT; | |
487 | ret = lis3l02dq_spi_write_reg_8(indio_dev, | |
488 | LIS3L02DQ_REG_CTRL_2_ADDR, | |
489 | &control); | |
490 | if (ret) | |
491 | goto error_ret; | |
492 | /* Also for consistency clear the mask */ | |
493 | ret = lis3l02dq_spi_read_reg_8(indio_dev, | |
494 | LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, | |
495 | &val); | |
496 | if (ret) | |
497 | goto error_ret; | |
498 | val &= ~0x3f; | |
499 | ||
500 | ret = lis3l02dq_spi_write_reg_8(indio_dev, | |
501 | LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, | |
502 | &val); | |
503 | if (ret) | |
504 | goto error_ret; | |
505 | ||
506 | if (irqtofree) | |
aaf370db | 507 | free_irq(st->us->irq, indio_dev); |
1e3345bc JC |
508 | |
509 | ret = control; | |
510 | error_ret: | |
511 | return ret; | |
512 | } | |
513 | ||
f3736416 JC |
514 | static int lis3l02dq_write_event_config(struct iio_dev *indio_dev, |
515 | int event_code, | |
f3736416 JC |
516 | int state) |
517 | { | |
1e3345bc JC |
518 | struct iio_sw_ring_helper_state *h |
519 | = iio_dev_get_devdata(indio_dev); | |
520 | struct lis3l02dq_state *st = lis3l02dq_h_to_s(h); | |
f3736416 JC |
521 | int ret = 0; |
522 | u8 val, control; | |
523 | u8 currentlyset; | |
524 | bool changed = false; | |
525 | u8 mask = (1 << (IIO_EVENT_CODE_EXTRACT_MODIFIER(event_code)*2 + | |
526 | (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == | |
527 | IIO_EV_DIR_RISING))); | |
528 | ||
529 | mutex_lock(&indio_dev->mlock); | |
66533b48 | 530 | /* read current control */ |
1b076b52 | 531 | ret = lis3l02dq_spi_read_reg_8(indio_dev, |
66533b48 | 532 | LIS3L02DQ_REG_CTRL_2_ADDR, |
f3736416 | 533 | &control); |
66533b48 | 534 | if (ret) |
f3736416 | 535 | goto error_ret; |
1b076b52 | 536 | ret = lis3l02dq_spi_read_reg_8(indio_dev, |
f3736416 JC |
537 | LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, |
538 | &val); | |
539 | if (ret < 0) | |
540 | goto error_ret; | |
541 | currentlyset = val & mask; | |
542 | ||
543 | if (!currentlyset && state) { | |
544 | changed = true; | |
545 | val |= mask; | |
f3736416 JC |
546 | } else if (currentlyset && !state) { |
547 | changed = true; | |
548 | val &= ~mask; | |
66533b48 | 549 | } |
1e3345bc | 550 | |
66533b48 | 551 | if (changed) { |
1e3345bc JC |
552 | if (!(control & LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT)) { |
553 | ret = request_irq(st->us->irq, | |
554 | &lis3l02dq_event_handler, | |
555 | IRQF_TRIGGER_RISING, | |
556 | "lis3l02dq_event", | |
aaf370db | 557 | indio_dev); |
1e3345bc JC |
558 | if (ret) |
559 | goto error_ret; | |
560 | } | |
561 | ||
1b076b52 | 562 | ret = lis3l02dq_spi_write_reg_8(indio_dev, |
66533b48 | 563 | LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, |
f3736416 | 564 | &val); |
66533b48 | 565 | if (ret) |
f3736416 | 566 | goto error_ret; |
1e3345bc | 567 | control = val & 0x3f ? |
f3736416 JC |
568 | (control | LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT) : |
569 | (control & ~LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT); | |
1b076b52 | 570 | ret = lis3l02dq_spi_write_reg_8(indio_dev, |
f3736416 JC |
571 | LIS3L02DQ_REG_CTRL_2_ADDR, |
572 | &control); | |
1e3345bc JC |
573 | if (ret) |
574 | goto error_ret; | |
575 | ||
576 | /* remove interrupt handler if nothing is still on */ | |
577 | if (!(val & 0x3f)) | |
aaf370db | 578 | free_irq(st->us->irq, indio_dev); |
66533b48 | 579 | } |
66533b48 | 580 | |
f3736416 JC |
581 | error_ret: |
582 | mutex_unlock(&indio_dev->mlock); | |
583 | return ret; | |
66533b48 JC |
584 | } |
585 | ||
66533b48 JC |
586 | /* Unforunately it appears the interrupt won't clear unless you read from the |
587 | * src register. | |
588 | */ | |
589 | static void lis3l02dq_thresh_handler_bh_no_check(struct work_struct *work_s) | |
590 | { | |
f3736416 JC |
591 | struct lis3l02dq_state *st |
592 | = container_of(work_s, | |
593 | struct lis3l02dq_state, work_thresh); | |
66533b48 JC |
594 | u8 t; |
595 | ||
1b076b52 | 596 | lis3l02dq_spi_read_reg_8(st->help.indio_dev, |
66533b48 JC |
597 | LIS3L02DQ_REG_WAKE_UP_SRC_ADDR, |
598 | &t); | |
599 | ||
600 | if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_HIGH) | |
73bce12e | 601 | iio_push_event(st->help.indio_dev, 0, |
18e69a99 JC |
602 | IIO_MOD_EVENT_CODE(IIO_EV_CLASS_ACCEL, |
603 | 0, | |
604 | IIO_EV_MOD_Z, | |
605 | IIO_EV_TYPE_THRESH, | |
606 | IIO_EV_DIR_RISING), | |
b98c9e60 | 607 | st->thresh_timestamp); |
66533b48 JC |
608 | |
609 | if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_LOW) | |
73bce12e | 610 | iio_push_event(st->help.indio_dev, 0, |
18e69a99 JC |
611 | IIO_MOD_EVENT_CODE(IIO_EV_CLASS_ACCEL, |
612 | 0, | |
613 | IIO_EV_MOD_Z, | |
614 | IIO_EV_TYPE_THRESH, | |
615 | IIO_EV_DIR_FALLING), | |
b98c9e60 | 616 | st->thresh_timestamp); |
66533b48 JC |
617 | |
618 | if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_HIGH) | |
73bce12e | 619 | iio_push_event(st->help.indio_dev, 0, |
18e69a99 JC |
620 | IIO_MOD_EVENT_CODE(IIO_EV_CLASS_ACCEL, |
621 | 0, | |
622 | IIO_EV_MOD_Y, | |
623 | IIO_EV_TYPE_THRESH, | |
624 | IIO_EV_DIR_RISING), | |
b98c9e60 | 625 | st->thresh_timestamp); |
66533b48 JC |
626 | |
627 | if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_LOW) | |
73bce12e | 628 | iio_push_event(st->help.indio_dev, 0, |
18e69a99 JC |
629 | IIO_MOD_EVENT_CODE(IIO_EV_CLASS_ACCEL, |
630 | 0, | |
631 | IIO_EV_MOD_Y, | |
632 | IIO_EV_TYPE_THRESH, | |
633 | IIO_EV_DIR_FALLING), | |
b98c9e60 | 634 | st->thresh_timestamp); |
66533b48 JC |
635 | |
636 | if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_HIGH) | |
73bce12e | 637 | iio_push_event(st->help.indio_dev, 0, |
18e69a99 JC |
638 | IIO_MOD_EVENT_CODE(IIO_EV_CLASS_ACCEL, |
639 | 0, | |
640 | IIO_EV_MOD_X, | |
641 | IIO_EV_TYPE_THRESH, | |
642 | IIO_EV_DIR_RISING), | |
b98c9e60 | 643 | st->thresh_timestamp); |
66533b48 JC |
644 | |
645 | if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_LOW) | |
73bce12e | 646 | iio_push_event(st->help.indio_dev, 0, |
18e69a99 JC |
647 | IIO_MOD_EVENT_CODE(IIO_EV_CLASS_ACCEL, |
648 | 0, | |
649 | IIO_EV_MOD_X, | |
650 | IIO_EV_TYPE_THRESH, | |
651 | IIO_EV_DIR_FALLING), | |
b98c9e60 | 652 | st->thresh_timestamp); |
66533b48 JC |
653 | /* reenable the irq */ |
654 | enable_irq(st->us->irq); | |
655 | /* Ack and allow for new interrupts */ | |
1b076b52 | 656 | lis3l02dq_spi_read_reg_8(st->help.indio_dev, |
66533b48 JC |
657 | LIS3L02DQ_REG_WAKE_UP_ACK_ADDR, |
658 | &t); | |
659 | ||
660 | return; | |
661 | } | |
662 | ||
51a0a5b0 | 663 | static IIO_CONST_ATTR_NAME("lis3l02dq"); |
66533b48 JC |
664 | |
665 | static struct attribute *lis3l02dq_attributes[] = { | |
66533b48 | 666 | &iio_dev_attr_sampling_frequency.dev_attr.attr, |
f3fb0011 | 667 | &iio_const_attr_sampling_frequency_available.dev_attr.attr, |
66533b48 JC |
668 | &iio_const_attr_name.dev_attr.attr, |
669 | NULL | |
670 | }; | |
671 | ||
672 | static const struct attribute_group lis3l02dq_attribute_group = { | |
673 | .attrs = lis3l02dq_attributes, | |
674 | }; | |
675 | ||
676 | static int __devinit lis3l02dq_probe(struct spi_device *spi) | |
677 | { | |
678 | int ret, regdone = 0; | |
679 | struct lis3l02dq_state *st = kzalloc(sizeof *st, GFP_KERNEL); | |
680 | if (!st) { | |
681 | ret = -ENOMEM; | |
682 | goto error_ret; | |
683 | } | |
d0348e50 | 684 | INIT_WORK(&st->work_thresh, lis3l02dq_thresh_handler_bh_no_check); |
66533b48 JC |
685 | /* this is only used tor removal purposes */ |
686 | spi_set_drvdata(spi, st); | |
687 | ||
688 | /* Allocate the comms buffers */ | |
689 | st->rx = kzalloc(sizeof(*st->rx)*LIS3L02DQ_MAX_RX, GFP_KERNEL); | |
690 | if (st->rx == NULL) { | |
691 | ret = -ENOMEM; | |
692 | goto error_free_st; | |
693 | } | |
694 | st->tx = kzalloc(sizeof(*st->tx)*LIS3L02DQ_MAX_TX, GFP_KERNEL); | |
695 | if (st->tx == NULL) { | |
696 | ret = -ENOMEM; | |
697 | goto error_free_rx; | |
698 | } | |
699 | st->us = spi; | |
700 | mutex_init(&st->buf_lock); | |
701 | /* setup the industrialio driver allocated elements */ | |
6f7c8ee5 | 702 | st->help.indio_dev = iio_allocate_device(0); |
73bce12e | 703 | if (st->help.indio_dev == NULL) { |
66533b48 JC |
704 | ret = -ENOMEM; |
705 | goto error_free_tx; | |
706 | } | |
707 | ||
73bce12e JC |
708 | st->help.indio_dev->dev.parent = &spi->dev; |
709 | st->help.indio_dev->num_interrupt_lines = 1; | |
f3736416 JC |
710 | st->help.indio_dev->channels = lis3l02dq_channels; |
711 | st->help.indio_dev->num_channels = ARRAY_SIZE(lis3l02dq_channels); | |
712 | st->help.indio_dev->read_raw = &lis3l02dq_read_raw; | |
713 | st->help.indio_dev->read_event_value = &lis3l02dq_read_thresh; | |
714 | st->help.indio_dev->write_event_value = &lis3l02dq_write_thresh; | |
715 | st->help.indio_dev->write_event_config = &lis3l02dq_write_event_config; | |
716 | st->help.indio_dev->read_event_config = &lis3l02dq_read_event_config; | |
73bce12e JC |
717 | st->help.indio_dev->attrs = &lis3l02dq_attribute_group; |
718 | st->help.indio_dev->dev_data = (void *)(&st->help); | |
719 | st->help.indio_dev->driver_module = THIS_MODULE; | |
720 | st->help.indio_dev->modes = INDIO_DIRECT_MODE; | |
66533b48 | 721 | |
73bce12e | 722 | ret = lis3l02dq_configure_ring(st->help.indio_dev); |
66533b48 JC |
723 | if (ret) |
724 | goto error_free_dev; | |
725 | ||
73bce12e | 726 | ret = iio_device_register(st->help.indio_dev); |
66533b48 JC |
727 | if (ret) |
728 | goto error_unreg_ring_funcs; | |
729 | regdone = 1; | |
730 | ||
f3736416 JC |
731 | ret = iio_ring_buffer_register_ex(st->help.indio_dev->ring, 0, |
732 | lis3l02dq_channels, | |
733 | ARRAY_SIZE(lis3l02dq_channels)); | |
66533b48 JC |
734 | if (ret) { |
735 | printk(KERN_ERR "failed to initialize the ring\n"); | |
736 | goto error_unreg_ring_funcs; | |
737 | } | |
738 | ||
739 | if (spi->irq && gpio_is_valid(irq_to_gpio(spi->irq)) > 0) { | |
66533b48 | 740 | st->inter = 0; |
73bce12e | 741 | ret = lis3l02dq_probe_trigger(st->help.indio_dev); |
66533b48 | 742 | if (ret) |
aaf370db | 743 | goto error_uninitialize_ring; |
66533b48 JC |
744 | } |
745 | ||
746 | /* Get the device into a sane initial state */ | |
747 | ret = lis3l02dq_initial_setup(st); | |
748 | if (ret) | |
749 | goto error_remove_trigger; | |
750 | return 0; | |
751 | ||
752 | error_remove_trigger: | |
73bce12e JC |
753 | if (st->help.indio_dev->modes & INDIO_RING_TRIGGERED) |
754 | lis3l02dq_remove_trigger(st->help.indio_dev); | |
66533b48 | 755 | error_uninitialize_ring: |
73bce12e | 756 | iio_ring_buffer_unregister(st->help.indio_dev->ring); |
66533b48 | 757 | error_unreg_ring_funcs: |
73bce12e | 758 | lis3l02dq_unconfigure_ring(st->help.indio_dev); |
66533b48 JC |
759 | error_free_dev: |
760 | if (regdone) | |
73bce12e | 761 | iio_device_unregister(st->help.indio_dev); |
66533b48 | 762 | else |
73bce12e | 763 | iio_free_device(st->help.indio_dev); |
66533b48 JC |
764 | error_free_tx: |
765 | kfree(st->tx); | |
766 | error_free_rx: | |
767 | kfree(st->rx); | |
768 | error_free_st: | |
769 | kfree(st); | |
770 | error_ret: | |
771 | return ret; | |
772 | } | |
773 | ||
774 | /* Power down the device */ | |
775 | static int lis3l02dq_stop_device(struct iio_dev *indio_dev) | |
776 | { | |
777 | int ret; | |
73bce12e JC |
778 | struct iio_sw_ring_helper_state *h |
779 | = iio_dev_get_devdata(indio_dev); | |
780 | struct lis3l02dq_state *st = lis3l02dq_h_to_s(h); | |
66533b48 JC |
781 | u8 val = 0; |
782 | ||
783 | mutex_lock(&indio_dev->mlock); | |
1b076b52 | 784 | ret = lis3l02dq_spi_write_reg_8(indio_dev, |
66533b48 JC |
785 | LIS3L02DQ_REG_CTRL_1_ADDR, |
786 | &val); | |
787 | if (ret) { | |
788 | dev_err(&st->us->dev, "problem with turning device off: ctrl1"); | |
789 | goto err_ret; | |
790 | } | |
791 | ||
1b076b52 | 792 | ret = lis3l02dq_spi_write_reg_8(indio_dev, |
66533b48 JC |
793 | LIS3L02DQ_REG_CTRL_2_ADDR, |
794 | &val); | |
795 | if (ret) | |
796 | dev_err(&st->us->dev, "problem with turning device off: ctrl2"); | |
797 | err_ret: | |
798 | mutex_unlock(&indio_dev->mlock); | |
799 | return ret; | |
800 | } | |
801 | ||
802 | /* fixme, confirm ordering in this function */ | |
803 | static int lis3l02dq_remove(struct spi_device *spi) | |
804 | { | |
805 | int ret; | |
806 | struct lis3l02dq_state *st = spi_get_drvdata(spi); | |
73bce12e | 807 | struct iio_dev *indio_dev = st->help.indio_dev; |
1e3345bc JC |
808 | ret = lis3l02dq_disable_all_events(indio_dev); |
809 | if (ret) | |
810 | goto err_ret; | |
66533b48 JC |
811 | |
812 | ret = lis3l02dq_stop_device(indio_dev); | |
813 | if (ret) | |
814 | goto err_ret; | |
815 | ||
816 | flush_scheduled_work(); | |
817 | ||
818 | lis3l02dq_remove_trigger(indio_dev); | |
2662051e | 819 | iio_ring_buffer_unregister(indio_dev->ring); |
66533b48 JC |
820 | lis3l02dq_unconfigure_ring(indio_dev); |
821 | iio_device_unregister(indio_dev); | |
822 | kfree(st->tx); | |
823 | kfree(st->rx); | |
824 | kfree(st); | |
825 | ||
826 | return 0; | |
827 | ||
828 | err_ret: | |
829 | return ret; | |
830 | } | |
831 | ||
832 | static struct spi_driver lis3l02dq_driver = { | |
833 | .driver = { | |
834 | .name = "lis3l02dq", | |
835 | .owner = THIS_MODULE, | |
836 | }, | |
837 | .probe = lis3l02dq_probe, | |
838 | .remove = __devexit_p(lis3l02dq_remove), | |
839 | }; | |
840 | ||
841 | static __init int lis3l02dq_init(void) | |
842 | { | |
843 | return spi_register_driver(&lis3l02dq_driver); | |
844 | } | |
845 | module_init(lis3l02dq_init); | |
846 | ||
847 | static __exit void lis3l02dq_exit(void) | |
848 | { | |
849 | spi_unregister_driver(&lis3l02dq_driver); | |
850 | } | |
851 | module_exit(lis3l02dq_exit); | |
852 | ||
853 | MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); | |
854 | MODULE_DESCRIPTION("ST LIS3L02DQ Accelerometer SPI driver"); | |
855 | MODULE_LICENSE("GPL v2"); |