]>
Commit | Line | Data |
---|---|---|
e435bc19 JC |
1 | /* |
2 | * kxsd9.c simple support for the Kionix KXSD9 3D | |
3 | * accelerometer. | |
4 | * | |
5 | * Copyright (c) 2008-2009 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 | * The i2c interface is very similar, so shouldn't be a problem once | |
12 | * I have a suitable wire made up. | |
13 | * | |
14 | * TODO: Support the motion detector | |
15 | * Uses register address incrementing so could have a | |
16 | * heavily optimized ring buffer access function. | |
17 | */ | |
18 | ||
19 | #include <linux/interrupt.h> | |
20 | #include <linux/gpio.h> | |
21 | #include <linux/fs.h> | |
22 | #include <linux/device.h> | |
23 | #include <linux/kernel.h> | |
24 | #include <linux/spi/spi.h> | |
25 | #include <linux/sysfs.h> | |
26 | #include <linux/rtc.h> | |
27 | #include <linux/delay.h> | |
5a0e3ad6 | 28 | #include <linux/slab.h> |
e435bc19 JC |
29 | |
30 | #include "../iio.h" | |
31 | #include "../sysfs.h" | |
32 | #include "../adc/adc.h" | |
33 | #include "accel.h" | |
34 | ||
35 | #define KXSD9_REG_X 0x00 | |
36 | #define KXSD9_REG_Y 0x02 | |
37 | #define KXSD9_REG_Z 0x04 | |
38 | #define KXSD9_REG_AUX 0x06 | |
39 | #define KXSD9_REG_RESET 0x0a | |
40 | #define KXSD9_REG_CTRL_C 0x0c | |
41 | ||
42 | #define KXSD9_FS_8 0x00 | |
43 | #define KXSD9_FS_6 0x01 | |
44 | #define KXSD9_FS_4 0x02 | |
45 | #define KXSD9_FS_2 0x03 | |
46 | #define KXSD9_FS_MASK 0x03 | |
47 | ||
48 | #define KXSD9_REG_CTRL_B 0x0d | |
49 | #define KXSD9_REG_CTRL_A 0x0e | |
50 | ||
51 | #define KXSD9_READ(a) (0x80 | (a)) | |
52 | #define KXSD9_WRITE(a) (a) | |
53 | ||
54 | #define IIO_DEV_ATTR_ACCEL_SET_RANGE(_mode, _show, _store) \ | |
55 | IIO_DEVICE_ATTR(accel_range, _mode, _show, _store, 0) | |
56 | ||
57 | #define KXSD9_STATE_RX_SIZE 2 | |
58 | #define KXSD9_STATE_TX_SIZE 4 | |
59 | /** | |
60 | * struct kxsd9_state - device related storage | |
61 | * @buf_lock: protect the rx and tx buffers. | |
62 | * @indio_dev: associated industrial IO device | |
63 | * @us: spi device | |
64 | * @rx: single rx buffer storage | |
65 | * @tx: single tx buffer storage | |
66 | **/ | |
67 | struct kxsd9_state { | |
68 | struct mutex buf_lock; | |
69 | struct iio_dev *indio_dev; | |
70 | struct spi_device *us; | |
71 | u8 *rx; | |
72 | u8 *tx; | |
73 | }; | |
74 | ||
75 | /* This may want to move to mili g to allow for non integer ranges */ | |
76 | static ssize_t kxsd9_read_accel_range(struct device *dev, | |
77 | struct device_attribute *attr, | |
78 | char *buf) | |
79 | { | |
80 | int ret; | |
81 | ssize_t len = 0; | |
82 | struct iio_dev *indio_dev = dev_get_drvdata(dev); | |
83 | struct kxsd9_state *st = indio_dev->dev_data; | |
84 | struct spi_transfer xfer = { | |
85 | .bits_per_word = 8, | |
86 | .len = 2, | |
87 | .cs_change = 1, | |
88 | .tx_buf = st->tx, | |
89 | .rx_buf = st->rx, | |
90 | }; | |
91 | struct spi_message msg; | |
92 | ||
93 | mutex_lock(&st->buf_lock); | |
94 | st->tx[0] = KXSD9_READ(KXSD9_REG_CTRL_C); | |
95 | st->tx[1] = 0; | |
96 | spi_message_init(&msg); | |
97 | spi_message_add_tail(&xfer, &msg); | |
98 | ret = spi_sync(st->us, &msg); | |
99 | if (ret) | |
100 | goto error_ret; | |
101 | ||
102 | switch (st->rx[1] & KXSD9_FS_MASK) { | |
103 | case KXSD9_FS_8: | |
104 | len += sprintf(buf, "8\n"); | |
105 | break; | |
106 | case KXSD9_FS_6: | |
107 | len += sprintf(buf, "6\n"); | |
108 | break; | |
109 | case KXSD9_FS_4: | |
110 | len += sprintf(buf, "4\n"); | |
111 | break; | |
112 | case KXSD9_FS_2: | |
113 | len += sprintf(buf, "2\n"); | |
114 | break; | |
115 | } | |
116 | ||
117 | error_ret: | |
118 | mutex_unlock(&st->buf_lock); | |
119 | ||
120 | return ret ? ret : len; | |
121 | } | |
122 | static ssize_t kxsd9_write_accel_range(struct device *dev, | |
123 | struct device_attribute *attr, | |
124 | const char *buf, | |
125 | size_t len) | |
126 | { | |
127 | long readin; | |
128 | struct spi_message msg; | |
129 | int ret; | |
130 | struct iio_dev *indio_dev = dev_get_drvdata(dev); | |
131 | struct kxsd9_state *st = indio_dev->dev_data; | |
132 | u8 val; | |
133 | struct spi_transfer xfers[] = { | |
134 | { | |
135 | .bits_per_word = 8, | |
136 | .len = 2, | |
137 | .cs_change = 1, | |
138 | .tx_buf = st->tx, | |
139 | .rx_buf = st->rx, | |
140 | }, { | |
141 | .bits_per_word = 8, | |
142 | .len = 2, | |
143 | .cs_change = 1, | |
144 | .tx_buf = st->tx, | |
145 | }, | |
146 | }; | |
147 | ||
148 | ret = strict_strtol(buf, 10, &readin); | |
149 | if (ret) | |
150 | return ret; | |
151 | switch (readin) { | |
152 | case 8: | |
153 | val = KXSD9_FS_8; | |
154 | break; | |
155 | case 6: | |
156 | val = KXSD9_FS_6; | |
157 | break; | |
158 | case 4: | |
159 | val = KXSD9_FS_4; | |
160 | break; | |
161 | case 2: | |
162 | val = KXSD9_FS_2; | |
163 | break; | |
164 | default: | |
165 | return -EINVAL; | |
166 | } | |
167 | mutex_lock(&st->buf_lock); | |
168 | st->tx[0] = KXSD9_READ(KXSD9_REG_CTRL_C); | |
169 | st->tx[1] = 0; | |
170 | spi_message_init(&msg); | |
171 | spi_message_add_tail(&xfers[0], &msg); | |
172 | ret = spi_sync(st->us, &msg); | |
173 | if (ret) | |
174 | goto error_ret; | |
175 | st->tx[0] = KXSD9_WRITE(KXSD9_REG_CTRL_C); | |
176 | st->tx[1] = (st->rx[1] & ~KXSD9_FS_MASK) | val; | |
177 | ||
178 | spi_message_init(&msg); | |
179 | spi_message_add_tail(&xfers[1], &msg); | |
180 | ret = spi_sync(st->us, &msg); | |
181 | error_ret: | |
182 | mutex_unlock(&st->buf_lock); | |
183 | return ret ? ret : len; | |
184 | } | |
185 | static ssize_t kxsd9_read_accel(struct device *dev, | |
186 | struct device_attribute *attr, | |
187 | char *buf) | |
188 | { | |
189 | struct spi_message msg; | |
190 | int ret; | |
191 | ssize_t len = 0; | |
192 | u16 val; | |
193 | struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); | |
194 | struct iio_dev *indio_dev = dev_get_drvdata(dev); | |
195 | struct kxsd9_state *st = indio_dev->dev_data; | |
196 | struct spi_transfer xfers[] = { | |
197 | { | |
198 | .bits_per_word = 8, | |
199 | .len = 1, | |
200 | .cs_change = 0, | |
201 | .delay_usecs = 200, | |
202 | .tx_buf = st->tx, | |
203 | }, { | |
204 | .bits_per_word = 8, | |
205 | .len = 2, | |
206 | .cs_change = 1, | |
207 | .rx_buf = st->rx, | |
208 | }, | |
209 | }; | |
210 | ||
211 | mutex_lock(&st->buf_lock); | |
212 | st->tx[0] = KXSD9_READ(this_attr->address); | |
213 | spi_message_init(&msg); | |
214 | spi_message_add_tail(&xfers[0], &msg); | |
215 | spi_message_add_tail(&xfers[1], &msg); | |
216 | ret = spi_sync(st->us, &msg); | |
217 | if (ret) | |
218 | goto error_ret; | |
219 | val = (((u16)(st->rx[0])) << 8) | (st->rx[1] & 0xF0); | |
220 | len = sprintf(buf, "%d\n", val); | |
221 | error_ret: | |
222 | mutex_unlock(&st->buf_lock); | |
223 | ||
224 | return ret ? ret : len; | |
225 | } | |
226 | ||
227 | static IIO_DEV_ATTR_ACCEL_X(kxsd9_read_accel, KXSD9_REG_X); | |
228 | static IIO_DEV_ATTR_ACCEL_Y(kxsd9_read_accel, KXSD9_REG_Y); | |
229 | static IIO_DEV_ATTR_ACCEL_Z(kxsd9_read_accel, KXSD9_REG_Z); | |
230 | static IIO_DEV_ATTR_ADC(0, kxsd9_read_accel, KXSD9_REG_AUX); | |
231 | static IIO_DEV_ATTR_ACCEL_SET_RANGE(S_IRUGO | S_IWUSR, | |
232 | kxsd9_read_accel_range, | |
233 | kxsd9_write_accel_range); | |
234 | ||
235 | static struct attribute *kxsd9_attributes[] = { | |
236 | &iio_dev_attr_accel_x.dev_attr.attr, | |
237 | &iio_dev_attr_accel_y.dev_attr.attr, | |
238 | &iio_dev_attr_accel_z.dev_attr.attr, | |
239 | &iio_dev_attr_adc_0.dev_attr.attr, | |
240 | &iio_dev_attr_accel_range.dev_attr.attr, | |
241 | NULL, | |
242 | }; | |
243 | ||
244 | static const struct attribute_group kxsd9_attribute_group = { | |
245 | .attrs = kxsd9_attributes, | |
246 | }; | |
247 | ||
248 | static int __devinit kxsd9_power_up(struct spi_device *spi) | |
249 | { | |
250 | int ret; | |
251 | struct spi_transfer xfers[2] = { | |
252 | { | |
253 | .bits_per_word = 8, | |
254 | .len = 2, | |
255 | .cs_change = 1, | |
256 | }, { | |
257 | .bits_per_word = 8, | |
258 | .len = 2, | |
259 | .cs_change = 1, | |
260 | }, | |
261 | }; | |
262 | struct spi_message msg; | |
263 | u8 *tx2; | |
264 | u8 *tx = kmalloc(2, GFP_KERNEL); | |
265 | ||
266 | if (tx == NULL) { | |
267 | ret = -ENOMEM; | |
268 | goto error_ret; | |
269 | } | |
270 | tx2 = kmalloc(2, GFP_KERNEL); | |
271 | if (tx2 == NULL) { | |
272 | ret = -ENOMEM; | |
273 | goto error_free_tx; | |
274 | } | |
275 | tx[0] = 0x0d; | |
276 | tx[1] = 0x40; | |
277 | ||
278 | tx2[0] = 0x0c; | |
279 | tx2[1] = 0x9b; | |
280 | ||
281 | xfers[0].tx_buf = tx; | |
282 | xfers[1].tx_buf = tx2; | |
283 | spi_message_init(&msg); | |
284 | spi_message_add_tail(&xfers[0], &msg); | |
285 | spi_message_add_tail(&xfers[1], &msg); | |
286 | ret = spi_sync(spi, &msg); | |
287 | ||
288 | kfree(tx2); | |
289 | error_free_tx: | |
290 | kfree(tx); | |
291 | error_ret: | |
292 | return ret; | |
293 | ||
294 | }; | |
295 | ||
296 | static int __devinit kxsd9_probe(struct spi_device *spi) | |
297 | { | |
298 | ||
299 | struct kxsd9_state *st; | |
300 | int ret = 0; | |
301 | ||
302 | st = kzalloc(sizeof(*st), GFP_KERNEL); | |
303 | if (st == NULL) { | |
304 | ret = -ENOMEM; | |
305 | goto error_ret; | |
306 | } | |
307 | spi_set_drvdata(spi, st); | |
308 | ||
309 | st->rx = kmalloc(sizeof(*st->rx)*KXSD9_STATE_RX_SIZE, | |
310 | GFP_KERNEL); | |
311 | if (st->rx == NULL) { | |
312 | ret = -ENOMEM; | |
313 | goto error_free_st; | |
314 | } | |
315 | st->tx = kmalloc(sizeof(*st->tx)*KXSD9_STATE_TX_SIZE, | |
316 | GFP_KERNEL); | |
317 | if (st->tx == NULL) { | |
318 | ret = -ENOMEM; | |
319 | goto error_free_rx; | |
320 | } | |
321 | ||
322 | st->us = spi; | |
323 | mutex_init(&st->buf_lock); | |
324 | st->indio_dev = iio_allocate_device(); | |
325 | if (st->indio_dev == NULL) { | |
326 | ret = -ENOMEM; | |
327 | goto error_free_tx; | |
328 | } | |
329 | st->indio_dev->dev.parent = &spi->dev; | |
330 | /* for now */ | |
331 | st->indio_dev->num_interrupt_lines = 0; | |
332 | st->indio_dev->event_attrs = NULL; | |
333 | ||
334 | st->indio_dev->attrs = &kxsd9_attribute_group; | |
335 | st->indio_dev->dev_data = (void *)(st); | |
336 | st->indio_dev->driver_module = THIS_MODULE; | |
337 | st->indio_dev->modes = INDIO_DIRECT_MODE; | |
338 | ||
339 | ret = iio_device_register(st->indio_dev); | |
340 | if (ret) | |
341 | goto error_free_dev; | |
342 | ||
343 | spi->mode = SPI_MODE_0; | |
344 | spi_setup(spi); | |
345 | kxsd9_power_up(spi); | |
346 | ||
347 | return 0; | |
348 | ||
349 | error_free_dev: | |
350 | iio_free_device(st->indio_dev); | |
351 | error_free_tx: | |
352 | kfree(st->tx); | |
353 | error_free_rx: | |
354 | kfree(st->rx); | |
355 | error_free_st: | |
356 | kfree(st); | |
357 | error_ret: | |
358 | return ret; | |
359 | } | |
360 | ||
361 | static int __devexit kxsd9_remove(struct spi_device *spi) | |
362 | { | |
363 | struct kxsd9_state *st = spi_get_drvdata(spi); | |
364 | ||
365 | iio_device_unregister(st->indio_dev); | |
366 | kfree(st->tx); | |
367 | kfree(st->rx); | |
368 | kfree(st); | |
369 | ||
370 | return 0; | |
371 | } | |
372 | ||
373 | static struct spi_driver kxsd9_driver = { | |
374 | .driver = { | |
375 | .name = "kxsd9", | |
376 | .owner = THIS_MODULE, | |
377 | }, | |
378 | .probe = kxsd9_probe, | |
379 | .remove = __devexit_p(kxsd9_remove), | |
380 | }; | |
381 | ||
382 | static __init int kxsd9_spi_init(void) | |
383 | { | |
384 | return spi_register_driver(&kxsd9_driver); | |
385 | } | |
386 | module_init(kxsd9_spi_init); | |
387 | ||
388 | static __exit void kxsd9_spi_exit(void) | |
389 | { | |
390 | spi_unregister_driver(&kxsd9_driver); | |
391 | } | |
392 | module_exit(kxsd9_spi_exit); | |
393 | ||
394 | MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); | |
395 | MODULE_DESCRIPTION("Kionix KXSD9 SPI driver"); | |
396 | MODULE_LICENSE("GPL v2"); |