Signed-off-by: Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx> --- drivers/staging/iio/accel/Kconfig | 24 ++ drivers/staging/iio/accel/Makefile | 4 + drivers/staging/iio/accel/lis331dlh.h | 224 +++++++++++++++ drivers/staging/iio/accel/lis331dlh_core.c | 406 ++++++++++++++++++++++++++++ drivers/staging/iio/accel/lis331dlh_i2c.c | 176 ++++++++++++ drivers/staging/iio/accel/lis331dlh_spi.c | 231 ++++++++++++++++ 6 files changed, 1065 insertions(+), 0 deletions(-) create mode 100644 drivers/staging/iio/accel/lis331dlh.h create mode 100644 drivers/staging/iio/accel/lis331dlh_core.c create mode 100644 drivers/staging/iio/accel/lis331dlh_i2c.c create mode 100644 drivers/staging/iio/accel/lis331dlh_spi.c diff --git a/drivers/staging/iio/accel/Kconfig b/drivers/staging/iio/accel/Kconfig index 5ab7167..7473393 100644 --- a/drivers/staging/iio/accel/Kconfig +++ b/drivers/staging/iio/accel/Kconfig @@ -94,6 +94,30 @@ config LIS3L02DQ_BUF_RING_SW endchoice +config LIS331DLH + bool "ST Microelectronics LIS331DLH Accelerometer Driver" + help + Say yes here to build support for the ST microelectronics LIS331DLH + accelerometer. The driver supplies direct access via sysfs files. + +if LIS331DLH + +config LIS331DLH_SPI + tristate "ST Microelectronics LIS331DLH Accelerometer Driver (SPI)" + depends on SPI + help + Say yes here to build SPI interface support for the + ST microelectronics LIS331DLH accelerometer. + +config LIS331DLH_I2C + tristate "ST Microelectronics LIS331DLH Accelerometer Driver (I2C)" + depends on I2C + help + Say yes here to build I2C interface support for the + ST microelectronics LIS331DLH accelerometer. + +endif # LIS331DLH + config SCA3000 depends on IIO_BUFFER depends on SPI diff --git a/drivers/staging/iio/accel/Makefile b/drivers/staging/iio/accel/Makefile index 95c6666..22986b7 100644 --- a/drivers/staging/iio/accel/Makefile +++ b/drivers/staging/iio/accel/Makefile @@ -27,6 +27,10 @@ obj-$(CONFIG_ADIS16240) += adis16240.o obj-$(CONFIG_KXSD9) += kxsd9.o +lis331dlh-y := lis331dlh_core.o +obj-$(CONFIG_LIS331DLH_SPI) += lis331dlh.o lis331dlh_spi.o +obj-$(CONFIG_LIS331DLH_I2C) += lis331dlh.o lis331dlh_i2c.o + lis3l02dq-y := lis3l02dq_core.o lis3l02dq-$(CONFIG_IIO_BUFFER) += lis3l02dq_ring.o obj-$(CONFIG_LIS3L02DQ) += lis3l02dq.o diff --git a/drivers/staging/iio/accel/lis331dlh.h b/drivers/staging/iio/accel/lis331dlh.h new file mode 100644 index 0000000..1c5a39d --- /dev/null +++ b/drivers/staging/iio/accel/lis331dlh.h @@ -0,0 +1,224 @@ +/* + * lis331dlh.h -- support STMicroelectronics LIS331DLH + * 3d 2g/4g/8g Linear Accelerometers + * + * Copyright (c) 2007 Jonathan Cameron <jic23@xxxxxxxxx> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx> + * + * 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. + */ + +#ifndef SPI_LIS331DLH_H_ +#define SPI_LIS331DLH_H_ + +#include <asm/byteorder.h> + +#define LIS331DLH "lis331dlh" + +#define LIS331DLH_READ_REG(a) ((a) | 0x80) +#define LIS331DLH_WRITE_REG(a) a +#define LIS331DLH_I2C_AUTO_INC(a) ((a) | 0x80) + +#define LIS331DLH_REG_WHO_AM_I_ADDR 0x0F +#define LIS331DLH_REG_WHO_AM_I_DEFAULT 0x32 + +/* Control Register (1 of 5) */ +#define LIS331DLH_REG_CTRL_1_ADDR 0x20 +/* Power ctrl - either bit set corresponds to on */ +#define LIS331DLH_REG_CTRL_1_POWER_OFF 0x00 +#define LIS331DLH_REG_CTRL_1_POWER_ON 0x20 +#define LIS331DLH_REG_CTRL_1_LOW_POWER_05 0x40 + +/* Data Rate */ +#define LIS331DLH_REG_CTRL_1_DR_50 0x00 +#define LIS331DLH_REG_CTRL_1_DR_100 0x08 +#define LIS331DLH_REG_CTRL_1_DR_400 0x10 +#define LIS331DLH_REG_CTRL_1_DR_1000 0x18 +#define LIS331DLH_REG_CTRL_1_DR_MASK 0x18 + +/* Axes enable ctrls */ +#define LIS331DLH_REG_CTRL_1_AXES_Z_ENABLE 0x04 +#define LIS331DLH_REG_CTRL_1_AXES_Y_ENABLE 0x02 +#define LIS331DLH_REG_CTRL_1_AXES_X_ENABLE 0x01 + +/* Control Register (2 of 5) */ +#define LIS331DLH_REG_CTRL_2_ADDR 0x21 + +#define LIS331DLH_REG_CTRL_2_BOOT 0x80 + +#define LIS331DLH_REG_CTRL_2_HP_NORM 0x00 +#define LIS331DLH_REG_CTRL_2_HP_REF 0x40 + +#define LIS331DLH_REG_CTRL_2_FDS_INT 0x10 +#define LIS331DLH_REG_CTRL_2_HPEN2 0x08 +#define LIS331DLH_REG_CTRL_2_HPEN1 0x04 +#define LIS331DLH_REG_CTRL_2_HPCF1 0x02 +#define LIS331DLH_REG_CTRL_2_HPCF0 0x01 + +/* Control Register (3 of 5) */ +#define LIS331DLH_REG_CTRL_3_ADDR 0x22 + +#define LIS331DLH_REG_CTRL_3_IHL 0x80 +#define LIS331DLH_REG_CTRL_3_PP_OD 0x40 +#define LIS331DLH_REG_CTRL_3_LIR2 0x20 +#define LIS331DLH_REG_CTRL_3_I2_I 0x00 +#define LIS331DLH_REG_CTRL_3_I2_1OR2 0x08 +#define LIS331DLH_REG_CTRL_3_I2_DRDY 0x10 +#define LIS331DLH_REG_CTRL_3_I2_RUN 0x18 +#define LIS331DLH_REG_CTRL_3_I2_MASK 0x18 +#define LIS331DLH_REG_CTRL_3_LIR1 0x04 +#define LIS331DLH_REG_CTRL_3_I1_I 0x00 +#define LIS331DLH_REG_CTRL_3_I1_1OR2 0x01 +#define LIS331DLH_REG_CTRL_3_I1_DRDY 0x02 +#define LIS331DLH_REG_CTRL_3_I1_RUN 0x03 +#define LIS331DLH_REG_CTRL_3_I1_MASK 0x03 + +/* Control Register (4 of 5) */ +#define LIS331DLH_REG_CTRL_4_ADDR 0x23 + +/* Block Data Update only after MSB and LSB read */ +#define LIS331DLH_REG_CTRL_4_BLOCK_UPDATE 0x80 + +/* Set to big endian output */ +#define LIS331DLH_REG_CTRL_4_BIG_ENDIAN 0x40 + +/* Full scale selection */ +#define LIS331DLH_REG_CTRL_4_FS_2G 0x00 +#define LIS331DLH_REG_CTRL_4_FS_4G 0x10 +#define LIS331DLH_REG_CTRL_4_FS_8G 0x30 +#define LIS331DLH_REG_CTRL_4_FS_MASK 0x30 + +/* Self Test Sign */ +#define LIS331DLH_REG_CTRL_4_ST_SIGN 0x08 + +/* Self Test Enable */ +#define LIS331DLH_REG_CTRL_4_ST_ON 0x02 + +/* SPI 3 wire mode */ +#define LIS331DLH_REG_CTRL_4_THREE_WIRE_SPI_MODE 0x01 + +/* Control Register (5 of 5) */ +#define LIS331DLH_REG_CTRL_5_ADDR 0x24 + +#define LIS331DLH_REG_CTRL_5_SLEEP_WAKE_OFF 0x00 +#define LIS331DLH_REG_CTRL_5_LOW_POWER 0x03 + +/* Dummy register */ +#define LIS331DLH_REG_HP_FILTER_RESET 0x25 + +/* Reference register */ +#define LIS331DLH_REG_REFERENCE 0x26 + +/* Status register */ +#define LIS331DLH_REG_STATUS_ADDR 0x27 +/* XYZ axis data overrun - first is all overrun? */ +#define LIS331DLH_REG_STATUS_XYZ_OVERRUN 0x80 +#define LIS331DLH_REG_STATUS_Z_OVERRUN 0x40 +#define LIS331DLH_REG_STATUS_Y_OVERRUN 0x20 +#define LIS331DLH_REG_STATUS_X_OVERRUN 0x10 +/* XYZ new data available - first is all 3 available? */ +#define LIS331DLH_REG_STATUS_XYZ_NEW_DATA 0x08 +#define LIS331DLH_REG_STATUS_Z_NEW_DATA 0x04 +#define LIS331DLH_REG_STATUS_Y_NEW_DATA 0x02 +#define LIS331DLH_REG_STATUS_X_NEW_DATA 0x01 + +/* The accelerometer readings - low and high bytes. +Form of high byte dependant on justification set in ctrl reg */ +#define LIS331DLH_REG_OUT_X_L_ADDR 0x28 +#define LIS331DLH_REG_OUT_X_H_ADDR 0x29 +#define LIS331DLH_REG_OUT_Y_L_ADDR 0x2A +#define LIS331DLH_REG_OUT_Y_H_ADDR 0x2B +#define LIS331DLH_REG_OUT_Z_L_ADDR 0x2C +#define LIS331DLH_REG_OUT_Z_H_ADDR 0x2D + +/* Interrupt 1 config register */ +#define LIS331DLH_REG_INT1_CFG_ADDR 0x30 +#define LIS331DLH_REG_INT1_SRC_ADDR 0x31 +#define LIS331DLH_REG_INT1_THS_ADDR 0x32 +#define LIS331DLH_REG_INT1_DURATION_ADDR 0x33 + +/* Interrupt 2 config register */ +#define LIS331DLH_REG_INT2_CFG_ADDR 0x34 +#define LIS331DLH_REG_INT2_SRC_ADDR 0x35 +#define LIS331DLH_REG_INT2_THS_ADDR 0x36 +#define LIS331DLH_REG_INT2_DURATION_ADDR 0x37 + +#define LIS331DLH_REG_INT_AOI 0x80 +#define LIS331DLH_REG_INT_6D 0x40 +#define LIS331DLH_REG_INT_Z_HIGH 0x20 +#define LIS331DLH_REG_INT_Z_LOW 0x10 +#define LIS331DLH_REG_INT_Y_HIGH 0x08 +#define LIS331DLH_REG_INT_Y_LOW 0x04 +#define LIS331DLH_REG_INT_X_HIGH 0x02 +#define LIS331DLH_REG_INT_X_LOW 0x01 + + +/* Default control settings */ +#define LIS331DLH_DEFAULT_CTRL1 (LIS331DLH_REG_CTRL_1_POWER_ON \ + | LIS331DLH_REG_CTRL_1_DR_50 \ + | LIS331DLH_REG_CTRL_1_AXES_Z_ENABLE \ + | LIS331DLH_REG_CTRL_1_AXES_Y_ENABLE \ + | LIS331DLH_REG_CTRL_1_AXES_X_ENABLE) + +#define LIS331DLH_DEFAULT_CTRL2 LIS331DLH_REG_CTRL_2_HP_NORM + +#define LIS331DLH_DEFAULT_CTRL3 LIS331DLH_REG_CTRL_3_I1_DRDY + +#ifdef __BIG_ENDIAN +#define LIS331DLH_REG_CTRL_4_CPU_ENDIAN LIS331DLH_REG_CTRL_4_BIG_ENDIAN +#else +#define LIS331DLH_REG_CTRL_4_CPU_ENDIAN 0 +#endif + +#define LIS331DLH_DEFAULT_CTRL4 (LIS331DLH_REG_CTRL_4_BLOCK_UPDATE \ + | LIS331DLH_REG_CTRL_4_CPU_ENDIAN \ + | LIS331DLH_REG_CTRL_4_FS_2G) + +#define LIS331DLH_MAX_TX 12 +#define LIS331DLH_MAX_RX 12 + +static const u8 read_all_tx_array[] = { + LIS331DLH_READ_REG(LIS331DLH_REG_OUT_X_L_ADDR), 0, + LIS331DLH_READ_REG(LIS331DLH_REG_OUT_X_H_ADDR), 0, + LIS331DLH_READ_REG(LIS331DLH_REG_OUT_Y_L_ADDR), 0, + LIS331DLH_READ_REG(LIS331DLH_REG_OUT_Y_H_ADDR), 0, + LIS331DLH_READ_REG(LIS331DLH_REG_OUT_Z_L_ADDR), 0, + LIS331DLH_READ_REG(LIS331DLH_REG_OUT_Z_H_ADDR), 0, +}; + +/** + * struct lis331dlh_state - device instance specific data + * @us: pointer to actual bus (spi/i2c) + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: receive buffer (16bit aligned) + * @read/write bus specific functions to access registers + **/ +struct lis331dlh_state { + void *us; + struct mutex buf_lock; + + u8 tx[LIS331DLH_MAX_TX] ____cacheline_aligned; + u16 rx[LIS331DLH_MAX_RX/2] ____cacheline_aligned; + + int (*read_reg_8) (struct lis331dlh_state *st, u8 addr, u8 *val); + int (*write_reg_8) (struct lis331dlh_state *st, u8 addr, u8 val); + int (*read_reg_16) (struct lis331dlh_state *st, u8 low_addr, u16 *val); + int (*read_all) (struct lis331dlh_state *st, u8 *rx_array); +}; + +int lis331dlh_probe(struct iio_dev *indio_dev, struct device *dev); +int lis331dlh_remove(struct iio_dev *indio_dev); + +int lis331dlh_stop_device(struct iio_dev *indio_dev); + +enum LIS331DLH_CHANNELS { + LIS331DLH_ACCEL_X, + LIS331DLH_ACCEL_Y, + LIS331DLH_ACCEL_Z, + LIS331DLH_TIMESTAMP, +}; + +#endif /* SPI_LIS331DLH_H_ */ diff --git a/drivers/staging/iio/accel/lis331dlh_core.c b/drivers/staging/iio/accel/lis331dlh_core.c new file mode 100644 index 0000000..0d06f20 --- /dev/null +++ b/drivers/staging/iio/accel/lis331dlh_core.c @@ -0,0 +1,406 @@ +/* + * lis331dlh_core.c support STMicroelectronics LIS331DLH + * 3d 2g/4g/8g Linear Accelerometers + * + * Copyright (c) 2007 Jonathan Cameron <jic23@xxxxxxxxx> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx> + * + * 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. + */ + +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/sysfs.h> +#include <linux/module.h> + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" + +#include "lis331dlh.h" + +static int lis331dlh_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct lis331dlh_state *st = iio_priv(indio_dev); + s16 value; + u8 utemp; + ssize_t ret = 0; + u8 reg; + + switch (mask) { + case 0: + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + ret = st->read_reg_16(st, chan->address, (u16 *)&value); + *val = value; + mutex_unlock(&indio_dev->mlock); + return ret ? ret : IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = st->read_reg_8(st, LIS331DLH_REG_CTRL_4_ADDR, &utemp); + if (ret) + return ret; + + utemp &= LIS331DLH_REG_CTRL_4_FS_MASK; + switch (utemp) { + case LIS331DLH_REG_CTRL_4_FS_2G: + *val2 = 569; + break; + case LIS331DLH_REG_CTRL_4_FS_4G: + *val2 = 1135; + break; + case LIS331DLH_REG_CTRL_4_FS_8G: + *val2 = 2270; + break; + default: + *val2 = 0; + break; + } + *val = 0; + return IIO_VAL_INT_PLUS_MICRO; + } + return -1; +} + +static ssize_t lis331dlh_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis331dlh_state *st = iio_priv(indio_dev); + int ret, len = 0; + u8 val; + + ret = st->read_reg_8(st, LIS331DLH_REG_CTRL_1_ADDR, &val); + if (ret) + return ret; + + val &= LIS331DLH_REG_CTRL_1_DR_MASK; + switch (val) { + case LIS331DLH_REG_CTRL_1_DR_50: + len = sprintf(buf, "50\n"); + break; + case LIS331DLH_REG_CTRL_1_DR_100: + len = sprintf(buf, "100\n"); + break; + case LIS331DLH_REG_CTRL_1_DR_400: + len = sprintf(buf, "400\n"); + break; + case LIS331DLH_REG_CTRL_1_DR_1000: + len = sprintf(buf, "1000\n"); + break; + } + return len; +} + +static ssize_t lis331dlh_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis331dlh_state *st = iio_priv(indio_dev); + unsigned long freq; + int ret; + u8 val; + + ret = strict_strtoul(buf, 10, &freq); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + ret = st->read_reg_8(st, LIS331DLH_REG_CTRL_1_ADDR, &val); + if (ret) + goto error_ret_mutex; + + val &= ~LIS331DLH_REG_CTRL_1_DR_MASK; + switch (freq) { + case 50: + val |= LIS331DLH_REG_CTRL_1_DR_50; + break; + case 100: + val |= LIS331DLH_REG_CTRL_1_DR_100; + break; + case 400: + val |= LIS331DLH_REG_CTRL_1_DR_400; + break; + case 1000: + val |= LIS331DLH_REG_CTRL_1_DR_1000; + break; + default: + ret = -EINVAL; + goto error_ret_mutex; + }; + + ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_1_ADDR, val); + +error_ret_mutex: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static ssize_t lis331dlh_read_range(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis331dlh_state *st = iio_priv(indio_dev); + int ret, len = 0; + u8 val; + + ret = st->read_reg_8(st, LIS331DLH_REG_CTRL_4_ADDR, &val); + if (ret) + return ret; + + val &= LIS331DLH_REG_CTRL_4_FS_MASK; + switch (val) { + case LIS331DLH_REG_CTRL_4_FS_2G: + len = sprintf(buf, "2G\n"); + break; + case LIS331DLH_REG_CTRL_4_FS_4G: + len = sprintf(buf, "4G\n"); + break; + case LIS331DLH_REG_CTRL_4_FS_8G: + len = sprintf(buf, "8G\n"); + break; + } + return len; +} + +static ssize_t lis331dlh_write_range(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis331dlh_state *st = iio_priv(indio_dev); + unsigned long freq; + int ret; + u8 val; + + ret = strict_strtoul(buf, 10, &freq); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + ret = st->read_reg_8(st, LIS331DLH_REG_CTRL_4_ADDR, &val); + if (ret) + goto error_ret_mutex; + + val &= ~LIS331DLH_REG_CTRL_4_FS_MASK; + switch (freq) { + case 2: + val |= LIS331DLH_REG_CTRL_4_FS_2G; + break; + case 4: + val |= LIS331DLH_REG_CTRL_4_FS_4G; + break; + case 8: + val |= LIS331DLH_REG_CTRL_4_FS_8G; + break; + default: + ret = -EINVAL; + goto error_ret_mutex; + }; + + ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_4_ADDR, val); + +error_ret_mutex: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static int lis331dlh_init(struct iio_dev *indio_dev) +{ + struct lis331dlh_state *st = iio_priv(indio_dev); + int ret; + u8 val; + + ret = st->read_reg_8(st, LIS331DLH_REG_WHO_AM_I_ADDR, &val); + if (ret || (val != LIS331DLH_REG_WHO_AM_I_DEFAULT)) { + dev_err(indio_dev->dev.parent, + "reading WHO_AM_I register failed: 0x%02X", val); + goto err_ret; + } + + /* Write suitable defaults to ctrl1 */ + ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_1_ADDR, + LIS331DLH_DEFAULT_CTRL1); + if (ret) { + dev_err(indio_dev->dev.parent, + "problem with setup control register 1"); + goto err_ret; + } + + /* Write suitable defaults to ctrl2 */ + ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_2_ADDR, + LIS331DLH_DEFAULT_CTRL2); + if (ret) { + dev_err(indio_dev->dev.parent, + "problem with setup control register 2"); + goto err_ret; + } + + /* Write suitable defaults to ctrl3 */ + ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_3_ADDR, + LIS331DLH_DEFAULT_CTRL3); + if (ret) { + dev_err(indio_dev->dev.parent, + "problem with setup control register 3"); + goto err_ret; + } + + /* Write suitable defaults to ctrl4 */ + ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_4_ADDR, + LIS331DLH_DEFAULT_CTRL4); + if (ret) { + dev_err(indio_dev->dev.parent, + "problem with setup control register 4"); + goto err_ret; + } + + /* Read int1 src reg to clear pending irqs */ + ret = st->read_reg_8(st, LIS331DLH_REG_INT1_SRC_ADDR, &val); + if (ret) { + dev_err(indio_dev->dev.parent, + "problem with int1 src register"); + goto err_ret; + } + + /* Read int2 src reg to clear pending irqs */ + ret = st->read_reg_8(st, LIS331DLH_REG_INT2_SRC_ADDR, &val); + if (ret) { + dev_err(indio_dev->dev.parent, + "problem with int2 src register"); + goto err_ret; + } + +err_ret: + return ret; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + lis331dlh_read_frequency, + lis331dlh_write_frequency); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("50 100 400 1000"); + +static IIO_DEVICE_ATTR(range, S_IWUSR | S_IRUGO, + lis331dlh_read_range, + lis331dlh_write_range, 0); + +#define LIS331DLH_EVENT_MASK \ + (IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) | \ + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING)) + +#define LIS331DLH_ACCEL_CHAN_SPEC(_mod, _si, _addr) \ + { .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = _mod, \ + .address = _addr, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .scan_index = _si, \ + .scan_type = IIO_ST('s', 16, 16, 0), \ + .event_mask = LIS331DLH_EVENT_MASK, \ + } + +static struct iio_chan_spec lis331dlh_channels[] = { + LIS331DLH_ACCEL_CHAN_SPEC(IIO_MOD_X, LIS331DLH_ACCEL_X, + LIS331DLH_REG_OUT_X_L_ADDR), + LIS331DLH_ACCEL_CHAN_SPEC(IIO_MOD_Y, LIS331DLH_ACCEL_Y, + LIS331DLH_REG_OUT_Y_L_ADDR), + LIS331DLH_ACCEL_CHAN_SPEC(IIO_MOD_Z, LIS331DLH_ACCEL_Z, + LIS331DLH_REG_OUT_Z_L_ADDR), + IIO_CHAN_SOFT_TIMESTAMP(LIS331DLH_TIMESTAMP) +}; + +static struct attribute *lis331dlh_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_range.dev_attr.attr, + NULL +}; + +static const struct attribute_group lis331dlh_attribute_group = { + .attrs = lis331dlh_attributes, +}; + +static const struct iio_info lis331dlh_info = { + .read_raw = &lis331dlh_read_raw, + .driver_module = THIS_MODULE, + .attrs = &lis331dlh_attribute_group, +}; + +int lis331dlh_probe(struct iio_dev *indio_dev, struct device *dev) +{ + struct lis331dlh_state *st = iio_priv(indio_dev); + int ret; + + mutex_init(&st->buf_lock); + + indio_dev->name = LIS331DLH; + indio_dev->dev.parent = dev; + indio_dev->info = &lis331dlh_info; + indio_dev->channels = lis331dlh_channels; + indio_dev->num_channels = ARRAY_SIZE(lis331dlh_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Get the device into a sane initial state */ + ret = lis331dlh_init(indio_dev); + if (ret) + return ret; + + ret = iio_device_register(indio_dev); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(lis331dlh_probe); + +/* Power down the device */ +int lis331dlh_power_down(struct iio_dev *indio_dev) +{ + struct lis331dlh_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&indio_dev->mlock); + ret = st->write_reg_8(st, LIS331DLH_REG_CTRL_1_ADDR, + LIS331DLH_REG_CTRL_1_POWER_OFF); + if (ret) { + dev_err(&indio_dev->dev, "problem with turning device off"); + goto err_ret; + } + +err_ret: + mutex_unlock(&indio_dev->mlock); + return ret; +} +EXPORT_SYMBOL_GPL(lis331dlh_power_down); + +int lis331dlh_remove(struct iio_dev *indio_dev) +{ + int ret; + + ret = lis331dlh_power_down(indio_dev); + if (ret) + return ret; + + iio_device_unregister(indio_dev); + iio_free_device(indio_dev); + + return 0; +} +EXPORT_SYMBOL_GPL(lis331dlh_remove); + +MODULE_AUTHOR("Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("ST LIS331DLH Accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/accel/lis331dlh_i2c.c b/drivers/staging/iio/accel/lis331dlh_i2c.c new file mode 100644 index 0000000..35c9c98 --- /dev/null +++ b/drivers/staging/iio/accel/lis331dlh_i2c.c @@ -0,0 +1,176 @@ +/* + * lis331dlh_i2c.c support STMicroelectronics LISD02DQ + * 3d 2g/4g/8g Linear Accelerometers + * + * Copyright (c) 2007 Jonathan Cameron <jic23@xxxxxxxxx> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx> + * + * 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. + */ + +#include <linux/i2c.h> + +#include "../iio.h" + +#include "lis331dlh.h" + +static int lis331dlh_i2c_read_reg_8(struct lis331dlh_state *st, + u8 reg_address, u8 *val) +{ + struct i2c_client *client = st->us; + int ret; + + struct i2c_msg msg[] = { { + .addr = client->addr, + .flags = client->flags & I2C_M_TEN, + .len = 1, + .buf = ®_address, + }, { + .addr = client->addr, + .flags = (client->flags & I2C_M_TEN) | I2C_M_RD, + .len = 1, + .buf = val, + } }; + + reg_address = LIS331DLH_WRITE_REG(reg_address); + ret = i2c_transfer(client->adapter, msg, 2); + return (ret == 2) ? 0 : ret; +} + +static int lis331dlh_i2c_write_reg_8(struct lis331dlh_state *st, + u8 reg_address, u8 val) +{ + struct i2c_client *client = st->us; + u8 buf[2]; + int ret; + + buf[0] = reg_address; + buf[1] = val; + + ret = i2c_master_send(client, buf, sizeof(buf)); + return (ret == sizeof(buf)) ? 0 : ret; +} + +static int lis331dlh_i2c_read_reg_16(struct lis331dlh_state *st, + u8 lower_reg_address, u16 *val) +{ + struct i2c_client *client = st->us; + int ret; + + struct i2c_msg msg[] = { { + .addr = client->addr, + .flags = client->flags & I2C_M_TEN, + .len = 1, + .buf = &lower_reg_address, + }, { + .addr = client->addr, + .flags = (client->flags & I2C_M_TEN) | I2C_M_RD, + .len = 2, + .buf = (u8 *)val, + } }; + + lower_reg_address = LIS331DLH_I2C_AUTO_INC(lower_reg_address); + ret = i2c_transfer(client->adapter, msg, 2); + le16_to_cpus(val); + return (ret == 2) ? 0 : ret; +} + +static int lis331dlh_i2c_read_all(struct lis331dlh_state *st, u8 *rx_array) +{ + struct i2c_client *client = st->us; + int ret; + u8 reg = LIS331DLH_I2C_AUTO_INC(LIS331DLH_REG_OUT_X_L_ADDR); + + struct i2c_msg msg[] = { { + .addr = client->addr, + .flags = client->flags & I2C_M_TEN, + .len = 1, + .buf = ®, + }, { + .addr = client->addr, + .flags = (client->flags & I2C_M_TEN) | I2C_M_RD, + .len = 6, + .buf = rx_array, + } }; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret != 2) + return ret; + + return 0; +} + +static int lis331dlh_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + int ret; + struct lis331dlh_state *st; + struct iio_dev *indio_dev; + + indio_dev = iio_allocate_device(sizeof *st); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->read_reg_8 = lis331dlh_i2c_read_reg_8; + st->write_reg_8 = lis331dlh_i2c_write_reg_8; + st->read_reg_16 = lis331dlh_i2c_read_reg_16; + st->read_all = lis331dlh_i2c_read_all; + + st->us = i2c; + + /* this is only used for removal purposes */ + i2c_set_clientdata(i2c, indio_dev); + + ret = lis331dlh_probe(indio_dev, &i2c->dev); + if (ret) + goto error_free_dev; + + return 0; + +error_free_dev: + iio_free_device(indio_dev); + return ret; +} + +static int lis331dlh_i2c_remove(struct i2c_client *i2c) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(i2c); + return lis331dlh_remove(indio_dev); +} + +static const struct i2c_device_id lis331dlh_id[] = { + {"lis331dlh", 0x19 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lis331dlh_id); + +static struct i2c_driver lis331dlh_i2c_driver = { + .driver = { + .name = "lis331dlh_i2c", + .owner = THIS_MODULE, + }, + .probe = lis331dlh_i2c_probe, + .remove = __devexit_p(lis331dlh_i2c_remove), + .id_table = lis331dlh_id, +}; + +static int __init lis331dlh_i2c_module_init(void) +{ + return i2c_add_driver(&lis331dlh_i2c_driver); +} + +static void __exit lis331dlh_i2c_module_exit(void) +{ + i2c_del_driver(&lis331dlh_i2c_driver); +} + +MODULE_AUTHOR("Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("ST LIS331DLH Accelerometer I2C driver"); +MODULE_LICENSE("GPL v2"); + +module_init(lis331dlh_i2c_module_init); +module_exit(lis331dlh_i2c_module_exit); diff --git a/drivers/staging/iio/accel/lis331dlh_spi.c b/drivers/staging/iio/accel/lis331dlh_spi.c new file mode 100644 index 0000000..0aee730 --- /dev/null +++ b/drivers/staging/iio/accel/lis331dlh_spi.c @@ -0,0 +1,231 @@ +/* + * lis331dlh_spi.c support STMicroelectronics LISD02DQ + * 3d 2g/4g/8g Linear Accelerometers via SPI + * + * Copyright (c) 2007 Jonathan Cameron <jic23@xxxxxxxxx> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx> + * + * 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. + */ + +#include <linux/spi/spi.h> + +#include "../iio.h" + +#include "lis331dlh.h" + +/* At the moment the spi framework doesn't allow global setting of cs_change. + * It's in the likely to be added comment at the top of spi.h. + * This means that use cannot be made of spi_write etc. + */ + +/** + * lis331dlh_spi_read_reg_8() - read single byte from a single register + * @dev: device asosciated with child of actual device (iio_dev or iio_trig) + * @reg_address: the address of the register to be read + * @val: pass back the resulting value + **/ +static int lis331dlh_spi_read_reg_8(struct lis331dlh_state *st, + u8 reg_address, u8 *val) +{ + struct spi_device *spi = st->us; + struct spi_message msg; + int ret; + + struct spi_transfer xfer = { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS331DLH_READ_REG(reg_address); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(spi, &msg); + *val = st->rx[1]; + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * lis331dlh_spi_write_reg_8() - write single byte to a register + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @reg_address: the address of the register to be writen + * @val: the value to write + **/ +static int lis331dlh_spi_write_reg_8(struct lis331dlh_state *st, + u8 reg_address, u8 val) +{ + int ret; + struct spi_message msg; + struct spi_device *spi = st->us; + struct spi_transfer xfer = { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS331DLH_WRITE_REG(reg_address); + st->tx[1] = val; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(spi, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * lisl302dq_spi_read_reg_16() - read 2 bytes from a pair of registers + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: somewhere to pass back the value read + **/ +static int lis331dlh_spi_read_reg_16(struct lis331dlh_state *st, + u8 lower_reg_address, + u16 *val) +{ + struct spi_message msg; + struct spi_device *spi = st->us; + int ret; + struct spi_transfer xfers[] = { { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .rx_buf = st->rx + 2, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS331DLH_READ_REG(lower_reg_address); + st->tx[1] = 0; + st->tx[2] = LIS331DLH_READ_REG(lower_reg_address+1); + st->tx[3] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(spi, &msg); + if (ret) { + dev_err(&spi->dev, "problem when reading 16 bit register"); + goto error_ret; + } + *val = (u16)(st->rx[1]) | ((u16)(st->rx[3]) << 8); + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int lis331dlh_spi_read_all(struct lis331dlh_state *st, u8 *rx_array) +{ + struct spi_transfer xfers[2]; + struct spi_message msg; + struct spi_device *spi = st->us; + + st->tx[0] = read_all_tx_array[0]; + xfers[0].tx_buf = st->tx; + xfers[0].rx_buf = NULL; + xfers[0].bits_per_word = 8; + xfers[0].len = 1; + xfers[0].cs_change = 1; + + xfers[1].tx_buf = NULL; + xfers[1].rx_buf = rx_array; + xfers[1].bits_per_word = 8; + xfers[1].len = LIS331DLH_SCAN_ELEMENTS * 2; + xfers[0].cs_change = 1; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + + return spi_sync(spi, &msg); +} + +static int __devinit lis331dlh_spi_probe(struct spi_device *spi) +{ + int ret; + struct lis331dlh_state *st; + struct iio_dev *indio_dev; + + indio_dev = iio_allocate_device(sizeof *st); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->read_reg_8 = lis331dlh_spi_read_reg_8; + st->write_reg_8 = lis331dlh_spi_write_reg_8; + st->read_reg_16 = lis331dlh_spi_read_reg_16; + st->read_all = lis331dlh_spi_read_all; + + st->us = spi; + + spi->mode = SPI_MODE_3; + spi_setup(spi); + + /* this is only used tor removal purposes */ + spi_set_drvdata(spi, indio_dev); + + ret = lis331dlh_probe(indio_dev, &spi->dev); + if (ret) + goto error_free_st; + + return 0; + +error_free_st: + kfree(st); + return ret; +} + +static int lis331dlh_spi_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + return lis331dlh_remove(indio_dev); +} + +static struct spi_driver lis331dlh_spi_driver = { + .driver = { + .name = "lis331dlh_spi", + .owner = THIS_MODULE, + }, + .probe = lis331dlh_spi_probe, + .remove = __devexit_p(lis331dlh_spi_remove), +}; + +static __init int lis331dlh_spi_module_init(void) +{ + return spi_register_driver(&lis331dlh_spi_driver); +} + +static __exit void lis331dlh_spi_module_exit(void) +{ + spi_unregister_driver(&lis331dlh_spi_driver); +} + +MODULE_AUTHOR("Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("ST LIS331DLH Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); + +module_init(lis331dlh_spi_module_init); +module_exit(lis331dlh_spi_module_exit); -- 1.7.2.3 -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html