From: Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx> This driver made use of a few convenient corners of scan_type so I have lifted those elements into iio_chan_spec structure for now (in initial iio core support patch). Signed-off-by: Jonathan Cameron <jic23@xxxxxxxxx> --- drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/imu/Kconfig | 12 + drivers/iio/imu/Makefile | 6 + drivers/iio/imu/adis16400.h | 180 ++++++ drivers/iio/imu/adis16400_core.c | 1142 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 1342 insertions(+), 0 deletions(-) diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 5dcd9e5..308bc97 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -12,6 +12,7 @@ menuconfig IIO if IIO source "drivers/iio/adc/Kconfig" +source "drivers/iio/imu/Kconfig" source "drivers/iio/light/Kconfig" endif # IIO diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 0e59946..db3c426 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -6,4 +6,5 @@ obj-$(CONFIG_IIO) += iio.o industrialio-y := core.o obj-y += adc/ +obj-y += imu/ obj-y += light/ diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig new file mode 100644 index 0000000..fe3c28c --- /dev/null +++ b/drivers/iio/imu/Kconfig @@ -0,0 +1,12 @@ +menu "Inertial measurement units (IMUs)" + +config IIO_ADIS16400 + tristate "Analog Devices ADIS16400 and similar IMU SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices adis16300, adis16350, + adis16354, adis16355, adis16360, adis16362, adis16364, adis16365, + adis16400 and adis16405 triaxial inertial sensors (adis16400 series + also have magnetometers). + +endmenu diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile new file mode 100644 index 0000000..eb4d31e --- /dev/null +++ b/drivers/iio/imu/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for IIO IMUs +# + +iio_adis16400-y := adis16400_core.o +obj-$(CONFIG_IIO_ADIS16400) += iio_adis16400.o \ No newline at end of file diff --git a/drivers/iio/imu/adis16400.h b/drivers/iio/imu/adis16400.h new file mode 100644 index 0000000..78377ea --- /dev/null +++ b/drivers/iio/imu/adis16400.h @@ -0,0 +1,180 @@ +/* + * adis16400.h support Analog Devices ADIS16400 + * 3d 18g accelerometers, + * 3d gyroscopes, + * 3d 2.5gauss magnetometers via SPI + * + * Copyright (c) 2009 Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx> + * Copyright (c) 2007 Jonathan Cameron <jic23@xxxxxxxxx> + * + * Loosely based upon lis3l02dq.h + * + * 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_ADIS16400_H_ +#define SPI_ADIS16400_H_ + +#define ADIS16400_STARTUP_DELAY 290 /* ms */ +#define ADIS16400_MTEST_DELAY 90 /* ms */ + +#define ADIS16400_READ_REG(a) a +#define ADIS16400_WRITE_REG(a) ((a) | 0x80) + +#define ADIS16400_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16400_SUPPLY_OUT 0x02 /* Power supply measurement */ +#define ADIS16400_XGYRO_OUT 0x04 /* X-axis gyroscope output */ +#define ADIS16400_YGYRO_OUT 0x06 /* Y-axis gyroscope output */ +#define ADIS16400_ZGYRO_OUT 0x08 /* Z-axis gyroscope output */ +#define ADIS16400_XACCL_OUT 0x0A /* X-axis accelerometer output */ +#define ADIS16400_YACCL_OUT 0x0C /* Y-axis accelerometer output */ +#define ADIS16400_ZACCL_OUT 0x0E /* Z-axis accelerometer output */ +#define ADIS16400_XMAGN_OUT 0x10 /* X-axis magnetometer measurement */ +#define ADIS16400_YMAGN_OUT 0x12 /* Y-axis magnetometer measurement */ +#define ADIS16400_ZMAGN_OUT 0x14 /* Z-axis magnetometer measurement */ +#define ADIS16400_TEMP_OUT 0x16 /* Temperature output */ +#define ADIS16400_AUX_ADC 0x18 /* Auxiliary ADC measurement */ + +#define ADIS16350_XTEMP_OUT 0x10 /* X-axis gyroscope temperature measurement */ +#define ADIS16350_YTEMP_OUT 0x12 /* Y-axis gyroscope temperature measurement */ +#define ADIS16350_ZTEMP_OUT 0x14 /* Z-axis gyroscope temperature measurement */ + +#define ADIS16300_PITCH_OUT 0x12 /* X axis inclinometer output measurement */ +#define ADIS16300_ROLL_OUT 0x12 /* Y axis inclinometer output measurement */ + +/* Calibration parameters */ +#define ADIS16400_XGYRO_OFF 0x1A /* X-axis gyroscope bias offset factor */ +#define ADIS16400_YGYRO_OFF 0x1C /* Y-axis gyroscope bias offset factor */ +#define ADIS16400_ZGYRO_OFF 0x1E /* Z-axis gyroscope bias offset factor */ +#define ADIS16400_XACCL_OFF 0x20 /* X-axis acceleration bias offset factor */ +#define ADIS16400_YACCL_OFF 0x22 /* Y-axis acceleration bias offset factor */ +#define ADIS16400_ZACCL_OFF 0x24 /* Z-axis acceleration bias offset factor */ +#define ADIS16400_XMAGN_HIF 0x26 /* X-axis magnetometer, hard-iron factor */ +#define ADIS16400_YMAGN_HIF 0x28 /* Y-axis magnetometer, hard-iron factor */ +#define ADIS16400_ZMAGN_HIF 0x2A /* Z-axis magnetometer, hard-iron factor */ +#define ADIS16400_XMAGN_SIF 0x2C /* X-axis magnetometer, soft-iron factor */ +#define ADIS16400_YMAGN_SIF 0x2E /* Y-axis magnetometer, soft-iron factor */ +#define ADIS16400_ZMAGN_SIF 0x30 /* Z-axis magnetometer, soft-iron factor */ + +#define ADIS16400_GPIO_CTRL 0x32 /* Auxiliary digital input/output control */ +#define ADIS16400_MSC_CTRL 0x34 /* Miscellaneous control */ +#define ADIS16400_SMPL_PRD 0x36 /* Internal sample period (rate) control */ +#define ADIS16400_SENS_AVG 0x38 /* Dynamic range and digital filter control */ +#define ADIS16400_SLP_CNT 0x3A /* Sleep mode control */ +#define ADIS16400_DIAG_STAT 0x3C /* System status */ + +/* Alarm functions */ +#define ADIS16400_GLOB_CMD 0x3E /* System command */ +#define ADIS16400_ALM_MAG1 0x40 /* Alarm 1 amplitude threshold */ +#define ADIS16400_ALM_MAG2 0x42 /* Alarm 2 amplitude threshold */ +#define ADIS16400_ALM_SMPL1 0x44 /* Alarm 1 sample size */ +#define ADIS16400_ALM_SMPL2 0x46 /* Alarm 2 sample size */ +#define ADIS16400_ALM_CTRL 0x48 /* Alarm control */ +#define ADIS16400_AUX_DAC 0x4A /* Auxiliary DAC data */ + +#define ADIS16400_PRODUCT_ID 0x56 /* Product identifier */ + +#define ADIS16400_ERROR_ACTIVE (1<<14) +#define ADIS16400_NEW_DATA (1<<14) + +/* MSC_CTRL */ +#define ADIS16400_MSC_CTRL_MEM_TEST (1<<11) +#define ADIS16400_MSC_CTRL_INT_SELF_TEST (1<<10) +#define ADIS16400_MSC_CTRL_NEG_SELF_TEST (1<<9) +#define ADIS16400_MSC_CTRL_POS_SELF_TEST (1<<8) +#define ADIS16400_MSC_CTRL_GYRO_BIAS (1<<7) +#define ADIS16400_MSC_CTRL_ACCL_ALIGN (1<<6) +#define ADIS16400_MSC_CTRL_DATA_RDY_EN (1<<2) +#define ADIS16400_MSC_CTRL_DATA_RDY_POL_HIGH (1<<1) +#define ADIS16400_MSC_CTRL_DATA_RDY_DIO2 (1<<0) + +/* SMPL_PRD */ +#define ADIS16400_SMPL_PRD_TIME_BASE (1<<7) +#define ADIS16400_SMPL_PRD_DIV_MASK 0x7F + +/* DIAG_STAT */ +#define ADIS16400_DIAG_STAT_ZACCL_FAIL (1<<15) +#define ADIS16400_DIAG_STAT_YACCL_FAIL (1<<14) +#define ADIS16400_DIAG_STAT_XACCL_FAIL (1<<13) +#define ADIS16400_DIAG_STAT_XGYRO_FAIL (1<<12) +#define ADIS16400_DIAG_STAT_YGYRO_FAIL (1<<11) +#define ADIS16400_DIAG_STAT_ZGYRO_FAIL (1<<10) +#define ADIS16400_DIAG_STAT_ALARM2 (1<<9) +#define ADIS16400_DIAG_STAT_ALARM1 (1<<8) +#define ADIS16400_DIAG_STAT_FLASH_CHK (1<<6) +#define ADIS16400_DIAG_STAT_SELF_TEST (1<<5) +#define ADIS16400_DIAG_STAT_OVERFLOW (1<<4) +#define ADIS16400_DIAG_STAT_SPI_FAIL (1<<3) +#define ADIS16400_DIAG_STAT_FLASH_UPT (1<<2) +#define ADIS16400_DIAG_STAT_POWER_HIGH (1<<1) +#define ADIS16400_DIAG_STAT_POWER_LOW (1<<0) + +/* GLOB_CMD */ +#define ADIS16400_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16400_GLOB_CMD_P_AUTO_NULL (1<<4) +#define ADIS16400_GLOB_CMD_FLASH_UPD (1<<3) +#define ADIS16400_GLOB_CMD_DAC_LATCH (1<<2) +#define ADIS16400_GLOB_CMD_FAC_CALIB (1<<1) +#define ADIS16400_GLOB_CMD_AUTO_NULL (1<<0) + +/* SLP_CNT */ +#define ADIS16400_SLP_CNT_POWER_OFF (1<<8) + +#define ADIS16400_MAX_TX 24 +#define ADIS16400_MAX_RX 24 + +#define ADIS16400_SPI_SLOW (u32)(300 * 1000) +#define ADIS16400_SPI_BURST (u32)(1000 * 1000) +#define ADIS16400_SPI_FAST (u32)(2000 * 1000) + +#define ADIS16400_HAS_PROD_ID 1 + +struct adis16400_chip_info { + const struct iio_chan_spec *channels; + const int num_channels; + const int product_id; + const long flags; + unsigned int gyro_scale_micro; + unsigned int accel_scale_micro; +}; + +/** + * struct adis16400_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct adis16400_state { + struct spi_device *us; + struct iio_trigger *trig; + struct mutex buf_lock; + struct adis16400_chip_info *variant; + + u8 tx[ADIS16400_MAX_TX] ____cacheline_aligned; + u8 rx[ADIS16400_MAX_RX] ____cacheline_aligned; +}; + +#define ADIS16400_SCAN_SUPPLY 0 +#define ADIS16400_SCAN_GYRO_X 1 +#define ADIS16400_SCAN_GYRO_Y 2 +#define ADIS16400_SCAN_GYRO_Z 3 +#define ADIS16400_SCAN_ACC_X 4 +#define ADIS16400_SCAN_ACC_Y 5 +#define ADIS16400_SCAN_ACC_Z 6 +#define ADIS16400_SCAN_MAGN_X 7 +#define ADIS16350_SCAN_TEMP_X 7 +#define ADIS16400_SCAN_MAGN_Y 8 +#define ADIS16350_SCAN_TEMP_Y 8 +#define ADIS16400_SCAN_MAGN_Z 9 +#define ADIS16350_SCAN_TEMP_Z 9 +#define ADIS16400_SCAN_TEMP 10 +#define ADIS16350_SCAN_ADC_0 10 +#define ADIS16400_SCAN_ADC_0 11 +#define ADIS16300_SCAN_INCLI_X 12 +#define ADIS16300_SCAN_INCLI_Y 13 + +#endif /* SPI_ADIS16400_H_ */ diff --git a/drivers/iio/imu/adis16400_core.c b/drivers/iio/imu/adis16400_core.c new file mode 100644 index 0000000..5e49742 --- /dev/null +++ b/drivers/iio/imu/adis16400_core.c @@ -0,0 +1,1142 @@ +/* + * adis16400.c support a number of ADI IMUs + * + * Copyright (c) 2009 Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx> + * Copyright (c) 2007-2011 Jonathan Cameron <jic23@xxxxxxxxx> + * Copyright (c) 2011 Analog Devices Inc. + * + * 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/delay.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/list.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include "adis16400.h" + +enum adis16400_chip_variant { + ADIS16300, + ADIS16334, + ADIS16350, + ADIS16360, + ADIS16362, + ADIS16364, + ADIS16365, + ADIS16400, +}; + +/** + * adis16400_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 written + * @val: the value to write + */ +static int adis16400_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val) +{ + int ret; + struct adis16400_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16400_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16400_spi_write_reg_16() - write 2 bytes to 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: value to be written + * + * At the moment the spi framework doesn't allow global setting of cs_change. + * This means that use cannot be made of spi_write. + */ +static int adis16400_spi_write_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 value) +{ + int ret; + struct spi_message msg; + struct adis16400_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16400_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = ADIS16400_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16400_spi_read_reg_16() - read 2 bytes from a 16-bit register + * @indio_dev: iio device + * @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 + * + * At the moment the spi framework doesn't allow global setting of cs_change. + * This means that use cannot be made of spi_read. + **/ +static int adis16400_spi_read_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 *val) +{ + struct spi_message msg; + struct adis16400_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16400_READ_REG(lower_reg_address); + st->tx[1] = 0; + + 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) { + dev_err(&st->us->dev, + "problem when reading 16 bit register 0x%02X", + lower_reg_address); + goto error_ret; + } + *val = (st->rx[0] << 8) | st->rx[1]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t adis16400_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int ret, len = 0; + u16 t; + int sps; + ret = adis16400_spi_read_reg_16(indio_dev, + ADIS16400_SMPL_PRD, + &t); + if (ret) + return ret; + sps = (t & ADIS16400_SMPL_PRD_TIME_BASE) ? 53 : 1638; + sps /= (t & ADIS16400_SMPL_PRD_DIV_MASK) + 1; + len = sprintf(buf, "%d SPS\n", sps); + return len; +} + +static ssize_t adis16400_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 adis16400_state *st = iio_priv(indio_dev); + long val; + int ret; + u8 t; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + + t = (1638 / val); + if (t > 0) + t--; + t &= ADIS16400_SMPL_PRD_DIV_MASK; + if ((t & ADIS16400_SMPL_PRD_DIV_MASK) >= 0x0A) + st->us->max_speed_hz = ADIS16400_SPI_SLOW; + else + st->us->max_speed_hz = ADIS16400_SPI_FAST; + + ret = adis16400_spi_write_reg_8(indio_dev, + ADIS16400_SMPL_PRD, + t); + + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static int adis16400_reset(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16400_spi_write_reg_8(indio_dev, + ADIS16400_GLOB_CMD, + ADIS16400_GLOB_CMD_SW_RESET); + if (ret) + dev_err(&indio_dev->dev, "problem resetting device"); + + return ret; +} + +static ssize_t adis16400_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + bool val; + int ret; + + ret = strtobool(buf, &val); + if (ret < 0) + return ret; + if (val) { + ret = adis16400_reset(dev_get_drvdata(dev)); + if (ret < 0) + return ret; + } + + return len; +} + +/* Power down the device */ +static int adis16400_stop_device(struct iio_dev *indio_dev) +{ + int ret; + u16 val = ADIS16400_SLP_CNT_POWER_OFF; + + ret = adis16400_spi_write_reg_16(indio_dev, ADIS16400_SLP_CNT, val); + if (ret) + dev_err(&indio_dev->dev, + "problem with turning device off: SLP_CNT"); + + return ret; +} + +static int adis16400_check_status(struct iio_dev *indio_dev) +{ + u16 status; + int ret; + struct device *dev = &indio_dev->dev; + + ret = adis16400_spi_read_reg_16(indio_dev, + ADIS16400_DIAG_STAT, &status); + + if (ret < 0) { + dev_err(dev, "Reading status failed\n"); + goto error_ret; + } + ret = status; + if (status & ADIS16400_DIAG_STAT_ZACCL_FAIL) + dev_err(dev, "Z-axis accelerometer self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_YACCL_FAIL) + dev_err(dev, "Y-axis accelerometer self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_XACCL_FAIL) + dev_err(dev, "X-axis accelerometer self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_XGYRO_FAIL) + dev_err(dev, "X-axis gyroscope self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_YGYRO_FAIL) + dev_err(dev, "Y-axis gyroscope self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_ZGYRO_FAIL) + dev_err(dev, "Z-axis gyroscope self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_ALARM2) + dev_err(dev, "Alarm 2 active\n"); + if (status & ADIS16400_DIAG_STAT_ALARM1) + dev_err(dev, "Alarm 1 active\n"); + if (status & ADIS16400_DIAG_STAT_FLASH_CHK) + dev_err(dev, "Flash checksum error\n"); + if (status & ADIS16400_DIAG_STAT_SELF_TEST) + dev_err(dev, "Self test error\n"); + if (status & ADIS16400_DIAG_STAT_OVERFLOW) + dev_err(dev, "Sensor overrange\n"); + if (status & ADIS16400_DIAG_STAT_SPI_FAIL) + dev_err(dev, "SPI failure\n"); + if (status & ADIS16400_DIAG_STAT_FLASH_UPT) + dev_err(dev, "Flash update failed\n"); + if (status & ADIS16400_DIAG_STAT_POWER_HIGH) + dev_err(dev, "Power supply above 5.25V\n"); + if (status & ADIS16400_DIAG_STAT_POWER_LOW) + dev_err(dev, "Power supply below 4.75V\n"); + +error_ret: + return ret; +} + +static int adis16400_self_test(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16400_spi_write_reg_16(indio_dev, + ADIS16400_MSC_CTRL, + ADIS16400_MSC_CTRL_MEM_TEST); + if (ret) { + dev_err(&indio_dev->dev, "problem starting self test"); + goto err_ret; + } + + msleep(ADIS16400_MTEST_DELAY); + adis16400_check_status(indio_dev); + +err_ret: + return ret; +} + +static int adis16400_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + u16 prod_id, smp_prd; + struct adis16400_state *st = iio_priv(indio_dev); + + /* use low spi speed for init */ + st->us->max_speed_hz = ADIS16400_SPI_SLOW; + st->us->mode = SPI_MODE_3; + spi_setup(st->us); + + ret = adis16400_self_test(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "self test failure"); + goto err_ret; + } + + ret = adis16400_check_status(indio_dev); + if (ret) { + adis16400_reset(indio_dev); + dev_err(&indio_dev->dev, "device not playing ball -> reset"); + msleep(ADIS16400_STARTUP_DELAY); + ret = adis16400_check_status(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "giving up"); + goto err_ret; + } + } + if (st->variant->flags & ADIS16400_HAS_PROD_ID) { + ret = adis16400_spi_read_reg_16(indio_dev, + ADIS16400_PRODUCT_ID, &prod_id); + if (ret) + goto err_ret; + + if ((prod_id & 0xF000) != st->variant->product_id) + dev_warn(&indio_dev->dev, "incorrect id"); + + dev_info(&indio_dev->dev, + "%s: prod_id 0x%04x at CS%d (irq %d)\n", + indio_dev->name, prod_id, + st->us->chip_select, st->us->irq); + } + /* use high spi speed if possible */ + ret = adis16400_spi_read_reg_16(indio_dev, + ADIS16400_SMPL_PRD, &smp_prd); + if (!ret && (smp_prd & ADIS16400_SMPL_PRD_DIV_MASK) < 0x0A) { + st->us->max_speed_hz = ADIS16400_SPI_SLOW; + spi_setup(st->us); + } + +err_ret: + return ret; +} + +static IIO_DEVICE_ATTR(sampling_frequency, + S_IWUSR | S_IRUGO, + adis16400_read_frequency, + adis16400_write_frequency, + 0); + +static IIO_DEVICE_ATTR(reset, S_IWUSR, NULL, adis16400_write_reset, 0); + +static IIO_CONST_ATTR(sampling_frequency_available, "409 546 819 1638"); + +enum adis16400_chan { + in_supply, + gyro_x, + gyro_y, + gyro_z, + accel_x, + accel_y, + accel_z, + magn_x, + magn_y, + magn_z, + temp, + temp0, temp1, temp2, + in1, + incli_x, + incli_y, +}; + +static u8 adis16400_addresses[17][2] = { + [in_supply] = { ADIS16400_SUPPLY_OUT }, + [gyro_x] = { ADIS16400_XGYRO_OUT, ADIS16400_XGYRO_OFF }, + [gyro_y] = { ADIS16400_YGYRO_OUT, ADIS16400_YGYRO_OFF }, + [gyro_z] = { ADIS16400_ZGYRO_OUT, ADIS16400_ZGYRO_OFF }, + [accel_x] = { ADIS16400_XACCL_OUT, ADIS16400_XACCL_OFF }, + [accel_y] = { ADIS16400_YACCL_OUT, ADIS16400_YACCL_OFF }, + [accel_z] = { ADIS16400_ZACCL_OUT, ADIS16400_ZACCL_OFF }, + [magn_x] = { ADIS16400_XMAGN_OUT }, + [magn_y] = { ADIS16400_YMAGN_OUT }, + [magn_z] = { ADIS16400_ZMAGN_OUT }, + [temp] = { ADIS16400_TEMP_OUT }, + [temp0] = { ADIS16350_XTEMP_OUT }, + [temp1] = { ADIS16350_YTEMP_OUT }, + [temp2] = { ADIS16350_ZTEMP_OUT }, + [in1] = { ADIS16400_AUX_ADC }, + [incli_x] = { ADIS16300_PITCH_OUT }, + [incli_y] = { ADIS16300_ROLL_OUT } +}; + +static int adis16400_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&indio_dev->mlock); + ret = adis16400_spi_write_reg_16(indio_dev, + adis16400_addresses[chan->address][1], + val); + mutex_unlock(&indio_dev->mlock); + return ret; + default: + return -EINVAL; + } +} + +static int adis16400_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int ret, shift; + s16 val16; + + switch (mask) { + case 0: + mutex_lock(&indio_dev->mlock); + ret = adis16400_spi_read_reg_16(indio_dev, + adis16400_addresses[chan->address][0], + &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << chan->scan_type.realbits) - 1; + if (chan->scan_type.sign == 's') { + shift = 16 - chan->scan_type.realbits; + val16 = (s16)(val16 << shift) >> shift; + } + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = st->variant->gyro_scale_micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_IN: + *val = 0; + if (chan->channel == 0) + *val2 = 2418; + else + *val2 = 806; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = st->variant->accel_scale_micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MAGN: + *val = 0; + *val2 = 500; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = 140000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&indio_dev->mlock); + ret = adis16400_spi_read_reg_16(indio_dev, + adis16400_addresses[chan->address][1], + &val16); + mutex_unlock(&indio_dev->mlock); + if (ret) + return ret; + val16 = ((val16 & 0xFFF) << 4) >> 4; + *val = val16; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + /* currently only temperature */ + *val = 198; + *val2 = 160000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static struct iio_chan_spec adis16400_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .extend_name = "supply", + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in_supply, + .scan_type = { + .realbits = 14, + .sign = 'u', + }, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = gyro_x, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = gyro_y, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = gyro_z, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_x, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_y, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_z, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = magn_x, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = magn_y, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = magn_z, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = temp, + .scan_type = { + .realbits = 12, + .sign = 's', + }, + }, { + .type = IIO_IN, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in1, + .scan_type = { + .realbits = 12, + .sign = 'u', + }, + }, +}; + +static struct iio_chan_spec adis16350_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .extend_name = "supply", + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in_supply, + .scan_type = { + .realbits = 12, + .sign = 'u', + }, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = gyro_x, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = gyro_y, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = gyro_z, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_x, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_y, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_z, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .extend_name = "x", + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = temp0, + .scan_type = { + .realbits = 12, + .sign = 's', + }, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 1, + .extend_name = "y", + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = temp1, + .scan_type = { + .realbits = 12, + .sign = 's', + }, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 2, + .extend_name = "z", + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = temp2, + .scan_type = { + .realbits = 12, + .sign = 's', + }, + }, { + .type = IIO_IN, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in1, + .scan_type = { + .realbits = 12, + .sign = 'u', + }, + }, +}; + +static struct iio_chan_spec adis16300_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .extend_name = "supply", + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in_supply, + .scan_type = { + .realbits = 14, + .sign = 'u', + }, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = gyro_x, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_x, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_y, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_z, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = temp, + .scan_type = { + .realbits = 12, + .sign = 's', + }, + }, { + .type = IIO_IN, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in1, + .scan_type = { + .realbits = 12, + .sign = 'u', + }, + }, { + .type = IIO_INCLI, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = incli_x, + .scan_type = { + .realbits = 13, + .sign = 's', + }, + }, { + .type = IIO_INCLI, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = incli_y, + .scan_type = { + .realbits = 13, + .sign = 's', + }, + }, +}; + +static const struct iio_chan_spec adis16334_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = gyro_x, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = gyro_y, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = gyro_z, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_x, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_y, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_z, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_z, + .scan_type = { + .realbits = 14, + .sign = 's', + }, + }, +}; + +static struct attribute *adis16400_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_reset.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16400_attribute_group = { + .attrs = adis16400_attributes, +}; + +static struct adis16400_chip_info adis16400_chips[] = { + [ADIS16300] = { + .channels = adis16300_channels, + .num_channels = ARRAY_SIZE(adis16300_channels), + .gyro_scale_micro = 873, + .accel_scale_micro = 5884, + }, + [ADIS16334] = { + .channels = adis16334_channels, + .num_channels = ARRAY_SIZE(adis16334_channels), + .gyro_scale_micro = 873, + .accel_scale_micro = 981, + }, + [ADIS16350] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .gyro_scale_micro = 872664, + .accel_scale_micro = 24732, + }, + [ADIS16360] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID, + .product_id = 0x3FE8, + .gyro_scale_micro = 1279, + .accel_scale_micro = 24732, + }, + [ADIS16362] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID, + .product_id = 0x3FEA, + .gyro_scale_micro = 1279, + .accel_scale_micro = 24732, + }, + [ADIS16364] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID, + .product_id = 0x3FEC, + .gyro_scale_micro = 1279, + .accel_scale_micro = 24732, + }, + [ADIS16365] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID, + .product_id = 0x3FED, + .gyro_scale_micro = 1279, + .accel_scale_micro = 24732, + }, + [ADIS16400] = { + .channels = adis16400_channels, + .num_channels = ARRAY_SIZE(adis16400_channels), + .flags = ADIS16400_HAS_PROD_ID, + .product_id = 0x4015, + .gyro_scale_micro = 873, + .accel_scale_micro = 32656, + } +}; + +static const struct iio_info adis16400_info = { + .driver_module = THIS_MODULE, + .read_raw = &adis16400_read_raw, + .write_raw = &adis16400_write_raw, + .attrs = &adis16400_attribute_group, +}; + +static int __devinit adis16400_probe(struct spi_device *spi) +{ + int ret; + struct adis16400_state *st; + struct iio_dev *indio_dev; + + indio_dev = iio_device_allocate(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st->us = spi; + mutex_init(&st->buf_lock); + + /* setup the industrialio driver allocated elements */ + st->variant = &adis16400_chips[spi_get_device_id(spi)->driver_data]; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = st->variant->channels; + indio_dev->num_channels = st->variant->num_channels; + indio_dev->info = &adis16400_info; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + /* Get the device into a sane initial state */ + ret = adis16400_initial_setup(indio_dev); + if (ret) + goto error_unregister_dev; + + return 0; + +error_unregister_dev: + iio_device_unregister(indio_dev); +error_free_dev: + iio_device_free(indio_dev); +error_ret: + return ret; +} + +/* fixme, confirm ordering in this function */ +static int adis16400_remove(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + + ret = adis16400_stop_device(indio_dev); + if (ret) + return ret; + + iio_device_free(indio_dev); + + return 0; +} + +static const struct spi_device_id adis16400_id[] = { + {"adis16300", ADIS16300}, + {"adis16334", ADIS16334}, + {"adis16350", ADIS16350}, + {"adis16354", ADIS16350}, + {"adis16355", ADIS16350}, + {"adis16360", ADIS16360}, + {"adis16362", ADIS16362}, + {"adis16364", ADIS16364}, + {"adis16365", ADIS16365}, + {"adis16400", ADIS16400}, + {"adis16405", ADIS16400}, + {} +}; + +static struct spi_driver adis16400_driver = { + .driver = { + .name = "adis16400", + .owner = THIS_MODULE, + }, + .id_table = adis16400_id, + .probe = adis16400_probe, + .remove = __devexit_p(adis16400_remove), +}; + +static __init int adis16400_init(void) +{ + return spi_register_driver(&adis16400_driver); +} +module_init(adis16400_init); + +static __exit void adis16400_exit(void) +{ + spi_unregister_driver(&adis16400_driver); +} +module_exit(adis16400_exit); + +MODULE_AUTHOR("Manuel Stahl <manuel.stahl@xxxxxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Analog Devices ADIS16400/5 IMU SPI driver"); +MODULE_LICENSE("GPL v2"); -- 1.7.7.2 -- 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