diff options
author | Russell King <rmk+kernel@arm.linux.org.uk> | 2009-09-22 19:54:53 (GMT) |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2009-09-22 20:01:40 (GMT) |
commit | ae19ffbadc1b2100285a5b5b3d0a4e0a11390904 (patch) | |
tree | 3c2086ab67398a019089a47ca3f362a4bc6db74f /drivers/staging/iio/accel/kxsd9.c | |
parent | 34e84f39a27d059a3e6ec6e8b94aafa702e6f220 (diff) | |
parent | 9173a8ef24a6b1b8031507b35b8ffe5f85a87692 (diff) | |
download | linux-fsl-qoriq-ae19ffbadc1b2100285a5b5b3d0a4e0a11390904.tar.xz |
Merge branch 'master' into for-linus
Diffstat (limited to 'drivers/staging/iio/accel/kxsd9.c')
-rw-r--r-- | drivers/staging/iio/accel/kxsd9.c | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/drivers/staging/iio/accel/kxsd9.c b/drivers/staging/iio/accel/kxsd9.c new file mode 100644 index 0000000..33d16b6 --- /dev/null +++ b/drivers/staging/iio/accel/kxsd9.c @@ -0,0 +1,395 @@ +/* + * kxsd9.c simple support for the Kionix KXSD9 3D + * accelerometer. + * + * Copyright (c) 2008-2009 Jonathan Cameron <jic23@cam.ac.uk> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The i2c interface is very similar, so shouldn't be a problem once + * I have a suitable wire made up. + * + * TODO: Support the motion detector + * Uses register address incrementing so could have a + * heavily optimized ring buffer access function. + */ + +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/rtc.h> +#include <linux/delay.h> + +#include "../iio.h" +#include "../sysfs.h" +#include "../adc/adc.h" +#include "accel.h" + +#define KXSD9_REG_X 0x00 +#define KXSD9_REG_Y 0x02 +#define KXSD9_REG_Z 0x04 +#define KXSD9_REG_AUX 0x06 +#define KXSD9_REG_RESET 0x0a +#define KXSD9_REG_CTRL_C 0x0c + +#define KXSD9_FS_8 0x00 +#define KXSD9_FS_6 0x01 +#define KXSD9_FS_4 0x02 +#define KXSD9_FS_2 0x03 +#define KXSD9_FS_MASK 0x03 + +#define KXSD9_REG_CTRL_B 0x0d +#define KXSD9_REG_CTRL_A 0x0e + +#define KXSD9_READ(a) (0x80 | (a)) +#define KXSD9_WRITE(a) (a) + +#define IIO_DEV_ATTR_ACCEL_SET_RANGE(_mode, _show, _store) \ + IIO_DEVICE_ATTR(accel_range, _mode, _show, _store, 0) + +#define KXSD9_STATE_RX_SIZE 2 +#define KXSD9_STATE_TX_SIZE 4 +/** + * struct kxsd9_state - device related storage + * @buf_lock: protect the rx and tx buffers. + * @indio_dev: associated industrial IO device + * @us: spi device + * @rx: single rx buffer storage + * @tx: single tx buffer storage + **/ +struct kxsd9_state { + struct mutex buf_lock; + struct iio_dev *indio_dev; + struct spi_device *us; + u8 *rx; + u8 *tx; +}; + +/* This may want to move to mili g to allow for non integer ranges */ +static ssize_t kxsd9_read_accel_range(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + ssize_t len = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct kxsd9_state *st = indio_dev->dev_data; + struct spi_transfer xfer = { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .tx_buf = st->tx, + .rx_buf = st->rx, + }; + struct spi_message msg; + + mutex_lock(&st->buf_lock); + st->tx[0] = KXSD9_READ(KXSD9_REG_CTRL_C); + st->tx[1] = 0; + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->us, &msg); + if (ret) + goto error_ret; + + switch (st->rx[1] & KXSD9_FS_MASK) { + case KXSD9_FS_8: + len += sprintf(buf, "8\n"); + break; + case KXSD9_FS_6: + len += sprintf(buf, "6\n"); + break; + case KXSD9_FS_4: + len += sprintf(buf, "4\n"); + break; + case KXSD9_FS_2: + len += sprintf(buf, "2\n"); + break; + } + +error_ret: + mutex_unlock(&st->buf_lock); + + return ret ? ret : len; +} +static ssize_t kxsd9_write_accel_range(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + long readin; + struct spi_message msg; + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct kxsd9_state *st = indio_dev->dev_data; + u8 val; + struct spi_transfer xfers[] = { + { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .tx_buf = st->tx, + .rx_buf = st->rx, + }, { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .tx_buf = st->tx, + }, + }; + + ret = strict_strtol(buf, 10, &readin); + if (ret) + return ret; + switch (readin) { + case 8: + val = KXSD9_FS_8; + break; + case 6: + val = KXSD9_FS_6; + break; + case 4: + val = KXSD9_FS_4; + break; + case 2: + val = KXSD9_FS_2; + break; + default: + return -EINVAL; + } + mutex_lock(&st->buf_lock); + st->tx[0] = KXSD9_READ(KXSD9_REG_CTRL_C); + st->tx[1] = 0; + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + ret = spi_sync(st->us, &msg); + if (ret) + goto error_ret; + st->tx[0] = KXSD9_WRITE(KXSD9_REG_CTRL_C); + st->tx[1] = (st->rx[1] & ~KXSD9_FS_MASK) | val; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); +error_ret: + mutex_unlock(&st->buf_lock); + return ret ? ret : len; +} +static ssize_t kxsd9_read_accel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct spi_message msg; + int ret; + ssize_t len = 0; + u16 val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct kxsd9_state *st = indio_dev->dev_data; + struct spi_transfer xfers[] = { + { + .bits_per_word = 8, + .len = 1, + .cs_change = 0, + .delay_usecs = 200, + .tx_buf = st->tx, + }, { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .rx_buf = st->rx, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = KXSD9_READ(this_attr->address); + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) + goto error_ret; + val = (((u16)(st->rx[0])) << 8) | (st->rx[1] & 0xF0); + len = sprintf(buf, "%d\n", val); +error_ret: + mutex_unlock(&st->buf_lock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_ACCEL_X(kxsd9_read_accel, KXSD9_REG_X); +static IIO_DEV_ATTR_ACCEL_Y(kxsd9_read_accel, KXSD9_REG_Y); +static IIO_DEV_ATTR_ACCEL_Z(kxsd9_read_accel, KXSD9_REG_Z); +static IIO_DEV_ATTR_ADC(0, kxsd9_read_accel, KXSD9_REG_AUX); +static IIO_DEV_ATTR_ACCEL_SET_RANGE(S_IRUGO | S_IWUSR, + kxsd9_read_accel_range, + kxsd9_write_accel_range); + +static struct attribute *kxsd9_attributes[] = { + &iio_dev_attr_accel_x.dev_attr.attr, + &iio_dev_attr_accel_y.dev_attr.attr, + &iio_dev_attr_accel_z.dev_attr.attr, + &iio_dev_attr_adc_0.dev_attr.attr, + &iio_dev_attr_accel_range.dev_attr.attr, + NULL, +}; + +static const struct attribute_group kxsd9_attribute_group = { + .attrs = kxsd9_attributes, +}; + +static int __devinit kxsd9_power_up(struct spi_device *spi) +{ + int ret; + struct spi_transfer xfers[2] = { + { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, + }; + struct spi_message msg; + u8 *tx2; + u8 *tx = kmalloc(2, GFP_KERNEL); + + if (tx == NULL) { + ret = -ENOMEM; + goto error_ret; + } + tx2 = kmalloc(2, GFP_KERNEL); + if (tx2 == NULL) { + ret = -ENOMEM; + goto error_free_tx; + } + tx[0] = 0x0d; + tx[1] = 0x40; + + tx2[0] = 0x0c; + tx2[1] = 0x9b; + + xfers[0].tx_buf = tx; + xfers[1].tx_buf = tx2; + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(spi, &msg); + + kfree(tx2); +error_free_tx: + kfree(tx); +error_ret: + return ret; + +}; + +static int __devinit kxsd9_probe(struct spi_device *spi) +{ + + struct kxsd9_state *st; + int ret = 0; + + st = kzalloc(sizeof(*st), GFP_KERNEL); + if (st == NULL) { + ret = -ENOMEM; + goto error_ret; + } + spi_set_drvdata(spi, st); + + st->rx = kmalloc(sizeof(*st->rx)*KXSD9_STATE_RX_SIZE, + GFP_KERNEL); + if (st->rx == NULL) { + ret = -ENOMEM; + goto error_free_st; + } + st->tx = kmalloc(sizeof(*st->tx)*KXSD9_STATE_TX_SIZE, + GFP_KERNEL); + if (st->tx == NULL) { + ret = -ENOMEM; + goto error_free_rx; + } + + st->us = spi; + mutex_init(&st->buf_lock); + st->indio_dev = iio_allocate_device(); + if (st->indio_dev == NULL) { + ret = -ENOMEM; + goto error_free_tx; + } + st->indio_dev->dev.parent = &spi->dev; + /* for now */ + st->indio_dev->num_interrupt_lines = 0; + st->indio_dev->event_attrs = NULL; + + st->indio_dev->attrs = &kxsd9_attribute_group; + st->indio_dev->dev_data = (void *)(st); + st->indio_dev->driver_module = THIS_MODULE; + st->indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(st->indio_dev); + if (ret) + goto error_free_dev; + + spi->mode = SPI_MODE_0; + spi_setup(spi); + kxsd9_power_up(spi); + + return 0; + +error_free_dev: + iio_free_device(st->indio_dev); +error_free_tx: + kfree(st->tx); +error_free_rx: + kfree(st->rx); +error_free_st: + kfree(st); +error_ret: + return ret; +} + +static int __devexit kxsd9_remove(struct spi_device *spi) +{ + struct kxsd9_state *st = spi_get_drvdata(spi); + + iio_device_unregister(st->indio_dev); + kfree(st->tx); + kfree(st->rx); + kfree(st); + + return 0; +} + +static struct spi_driver kxsd9_driver = { + .driver = { + .name = "kxsd9", + .owner = THIS_MODULE, + }, + .probe = kxsd9_probe, + .remove = __devexit_p(kxsd9_remove), +}; + +static __init int kxsd9_spi_init(void) +{ + return spi_register_driver(&kxsd9_driver); +} +module_init(kxsd9_spi_init); + +static __exit void kxsd9_spi_exit(void) +{ + spi_unregister_driver(&kxsd9_driver); +} +module_exit(kxsd9_spi_exit); + +MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); +MODULE_DESCRIPTION("Kionix KXSD9 SPI driver"); +MODULE_LICENSE("GPL v2"); |