From e435bc191f6a224192b548bed8cb1893b64e7df2 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Tue, 18 Aug 2009 18:06:23 +0100 Subject: [PATCH] Staging: IIO: kxsd9 accelerometer minimal support This provides only very minimal support for this device. Note that an alternate driver has been posted to the input mailing list. When the original LMKL discussion that led to the descision to develop IIO occured, the question on whether the differing requirements of IIO and input drivers made it a good idea to have unified drivers was left as an open question. It still is. All opinions on this question welcome. Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/staging/iio/accel/Kconfig | 7 + drivers/staging/iio/accel/Makefile | 2 + drivers/staging/iio/accel/kxsd9.c | 395 +++++++++++++++++++++++++++++++++++++ drivers/staging/iio/adc/adc.h | 13 ++ 4 files changed, 417 insertions(+) create mode 100644 drivers/staging/iio/accel/kxsd9.c create mode 100644 drivers/staging/iio/adc/adc.h diff --git a/drivers/staging/iio/accel/Kconfig b/drivers/staging/iio/accel/Kconfig index d94da21..afe619d 100644 --- a/drivers/staging/iio/accel/Kconfig +++ b/drivers/staging/iio/accel/Kconfig @@ -3,6 +3,13 @@ # comment "Accelerometers" +config KXSD9 + tristate "Kionix KXSD9 Accelerometer Driver" + depends on SPI + help + Say yes here to build support for the Kionix KXSD9 accelerometer. + Currently this only supports the device via an SPI interface. + config LIS3L02DQ tristate "ST Microelectronics LIS3L02DQ Accelerometer Driver" depends on SPI diff --git a/drivers/staging/iio/accel/Makefile b/drivers/staging/iio/accel/Makefile index 5a7ef9d..cbec688 100644 --- a/drivers/staging/iio/accel/Makefile +++ b/drivers/staging/iio/accel/Makefile @@ -1,5 +1,7 @@ # # Makefile for industrial I/O accelerometer drivers # +obj-$(CONFIG_KXSD9) += kxsd9.o + lis3l02dq-y := lis3l02dq_core.o obj-$(CONFIG_LIS3L02DQ) += lis3l02dq.o 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Kionix KXSD9 SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/adc.h b/drivers/staging/iio/adc/adc.h new file mode 100644 index 0000000..d925b2c --- /dev/null +++ b/drivers/staging/iio/adc/adc.h @@ -0,0 +1,13 @@ +/* + * adc.h - sysfs attributes associated with ADCs + * + * 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. + * + * Copyright (c) 2008 Jonathan Cameron + * + */ + +#define IIO_DEV_ATTR_ADC(_num, _show, _addr) \ + IIO_DEVICE_ATTR(adc_##_num, S_IRUGO, _show, NULL, _addr) -- 2.7.4