From d6b09bd85d57752395c6407bd8a9b32eb7b279ff Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Wed, 16 Jan 2013 12:48:00 +0000 Subject: staging:iio: Move adis16400 out of staging This adis16400 driver is in pretty good shape now, so move it out of staging. Signed-off-by: Lars-Peter Clausen Signed-off-by: Jonathan Cameron --- drivers/iio/imu/Kconfig | 11 + drivers/iio/imu/Makefile | 3 + drivers/iio/imu/adis16400.h | 204 ++++++++++ drivers/iio/imu/adis16400_buffer.c | 96 +++++ drivers/iio/imu/adis16400_core.c | 812 +++++++++++++++++++++++++++++++++++++ 5 files changed, 1126 insertions(+) create mode 100644 drivers/iio/imu/adis16400.h create mode 100644 drivers/iio/imu/adis16400_buffer.c create mode 100644 drivers/iio/imu/adis16400_core.c (limited to 'drivers/iio/imu') diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig index 3d79a40e916b..47f66ed9c4ef 100644 --- a/drivers/iio/imu/Kconfig +++ b/drivers/iio/imu/Kconfig @@ -3,6 +3,17 @@ # menu "Inertial measurement units" +config ADIS16400 + tristate "Analog Devices ADIS16400 and similar IMU SPI driver" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say yes here to build support for Analog Devices adis16300, adis16344, + adis16350, adis16354, adis16355, adis16360, adis16362, adis16364, + adis16365, adis16400 and adis16405 triaxial inertial sensors + (adis16400 series also have magnetometers). + config ADIS16480 tristate "Analog Devices ADIS16480 and similar IMU driver" depends on SPI diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile index cfe57638f6f9..019b717c5ff1 100644 --- a/drivers/iio/imu/Makefile +++ b/drivers/iio/imu/Makefile @@ -2,6 +2,9 @@ # Makefile for Inertial Measurement Units # +adis16400-y := adis16400_core.o +adis16400-$(CONFIG_IIO_BUFFER) += adis16400_buffer.o +obj-$(CONFIG_ADIS16400) += adis16400.o obj-$(CONFIG_ADIS16480) += adis16480.o adis_lib-y += adis.o diff --git a/drivers/iio/imu/adis16400.h b/drivers/iio/imu/adis16400.h new file mode 100644 index 000000000000..a3b9e56c5bd1 --- /dev/null +++ b/drivers/iio/imu/adis16400.h @@ -0,0 +1,204 @@ +/* + * adis16400.h support Analog Devices ADIS16400 + * 3d 18g accelerometers, + * 3d gyroscopes, + * 3d 2.5gauss magnetometers via SPI + * + * Copyright (c) 2009 Manuel Stahl + * Copyright (c) 2007 Jonathan Cameron + * + * 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_ + +#include + +#define ADIS16400_STARTUP_DELAY 290 /* ms */ +#define ADIS16400_MTEST_DELAY 90 /* ms */ + +#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 0x14 /* Y axis inclinometer output measurement */ +#define ADIS16300_AUX_ADC 0x16 /* Auxiliary ADC 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 15 +#define ADIS16400_DIAG_STAT_YACCL_FAIL 14 +#define ADIS16400_DIAG_STAT_XACCL_FAIL 13 +#define ADIS16400_DIAG_STAT_XGYRO_FAIL 12 +#define ADIS16400_DIAG_STAT_YGYRO_FAIL 11 +#define ADIS16400_DIAG_STAT_ZGYRO_FAIL 10 +#define ADIS16400_DIAG_STAT_ALARM2 9 +#define ADIS16400_DIAG_STAT_ALARM1 8 +#define ADIS16400_DIAG_STAT_FLASH_CHK 6 +#define ADIS16400_DIAG_STAT_SELF_TEST 5 +#define ADIS16400_DIAG_STAT_OVERFLOW 4 +#define ADIS16400_DIAG_STAT_SPI_FAIL 3 +#define ADIS16400_DIAG_STAT_FLASH_UPT 2 +#define ADIS16400_DIAG_STAT_POWER_HIGH 1 +#define ADIS16400_DIAG_STAT_POWER_LOW 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 ADIS16334_RATE_DIV_SHIFT 8 +#define ADIS16334_RATE_INT_CLK BIT(0) + +#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 BIT(0) +#define ADIS16400_NO_BURST BIT(1) +#define ADIS16400_HAS_SLOW_MODE BIT(2) + +struct adis16400_state; + +struct adis16400_chip_info { + const struct iio_chan_spec *channels; + const int num_channels; + const long flags; + unsigned int gyro_scale_micro; + unsigned int accel_scale_micro; + int temp_scale_nano; + int temp_offset; + int (*set_freq)(struct adis16400_state *st, unsigned int freq); + int (*get_freq)(struct adis16400_state *st); +}; + +/** + * struct adis16400_state - device instance specific data + * @variant: chip variant info + * @filt_int: integer part of requested filter frequency + * @adis: adis device + **/ +struct adis16400_state { + struct adis16400_chip_info *variant; + int filt_int; + + struct adis adis; +}; + +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +enum { + ADIS16400_SCAN_SUPPLY, + ADIS16400_SCAN_GYRO_X, + ADIS16400_SCAN_GYRO_Y, + ADIS16400_SCAN_GYRO_Z, + ADIS16400_SCAN_ACC_X, + ADIS16400_SCAN_ACC_Y, + ADIS16400_SCAN_ACC_Z, + ADIS16400_SCAN_MAGN_X, + ADIS16400_SCAN_MAGN_Y, + ADIS16400_SCAN_MAGN_Z, + ADIS16350_SCAN_TEMP_X, + ADIS16350_SCAN_TEMP_Y, + ADIS16350_SCAN_TEMP_Z, + ADIS16300_SCAN_INCLI_X, + ADIS16300_SCAN_INCLI_Y, + ADIS16400_SCAN_ADC, +}; + +#ifdef CONFIG_IIO_BUFFER + +ssize_t adis16400_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + + +int adis16400_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask); +irqreturn_t adis16400_trigger_handler(int irq, void *p); + +#else /* CONFIG_IIO_BUFFER */ + +#define adis16400_update_scan_mode NULL +#define adis16400_trigger_handler NULL + +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* SPI_ADIS16400_H_ */ diff --git a/drivers/iio/imu/adis16400_buffer.c b/drivers/iio/imu/adis16400_buffer.c new file mode 100644 index 000000000000..054c01d6e73c --- /dev/null +++ b/drivers/iio/imu/adis16400_buffer.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "adis16400.h" + +int adis16400_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct adis16400_state *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + uint16_t *tx, *rx; + + if (st->variant->flags & ADIS16400_NO_BURST) + return adis_update_scan_mode(indio_dev, scan_mask); + + kfree(adis->xfer); + kfree(adis->buffer); + + adis->xfer = kcalloc(2, sizeof(*adis->xfer), GFP_KERNEL); + if (!adis->xfer) + return -ENOMEM; + + adis->buffer = kzalloc(indio_dev->scan_bytes + sizeof(u16), + GFP_KERNEL); + if (!adis->buffer) + return -ENOMEM; + + rx = adis->buffer; + tx = adis->buffer + indio_dev->scan_bytes; + + tx[0] = ADIS_READ_REG(ADIS16400_GLOB_CMD); + tx[1] = 0; + + adis->xfer[0].tx_buf = tx; + adis->xfer[0].bits_per_word = 8; + adis->xfer[0].len = 2; + adis->xfer[1].tx_buf = tx; + adis->xfer[1].bits_per_word = 8; + adis->xfer[1].len = indio_dev->scan_bytes; + + spi_message_init(&adis->msg); + spi_message_add_tail(&adis->xfer[0], &adis->msg); + spi_message_add_tail(&adis->xfer[1], &adis->msg); + + return 0; +} + +irqreturn_t adis16400_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16400_state *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + u32 old_speed_hz = st->adis.spi->max_speed_hz; + int ret; + + if (!adis->buffer) + return -ENOMEM; + + if (!(st->variant->flags & ADIS16400_NO_BURST) && + st->adis.spi->max_speed_hz > ADIS16400_SPI_BURST) { + st->adis.spi->max_speed_hz = ADIS16400_SPI_BURST; + spi_setup(st->adis.spi); + } + + ret = spi_sync(adis->spi, &adis->msg); + if (ret) + dev_err(&adis->spi->dev, "Failed to read data: %d\n", ret); + + if (!(st->variant->flags & ADIS16400_NO_BURST)) { + st->adis.spi->max_speed_hz = old_speed_hz; + spi_setup(st->adis.spi); + } + + /* Guaranteed to be aligned with 8 byte boundary */ + if (indio_dev->scan_timestamp) { + void *b = adis->buffer + indio_dev->scan_bytes - sizeof(s64); + *(s64 *)b = pf->timestamp; + } + + iio_push_to_buffers(indio_dev, adis->buffer); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} diff --git a/drivers/iio/imu/adis16400_core.c b/drivers/iio/imu/adis16400_core.c new file mode 100644 index 000000000000..1bbe5eed0139 --- /dev/null +++ b/drivers/iio/imu/adis16400_core.c @@ -0,0 +1,812 @@ +/* + * adis16400.c support Analog Devices ADIS16400/5 + * 3d 2g Linear Accelerometers, + * 3d Gyroscopes, + * 3d Magnetometers via SPI + * + * Copyright (c) 2009 Manuel Stahl + * Copyright (c) 2007 Jonathan Cameron + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "adis16400.h" + +enum adis16400_chip_variant { + ADIS16300, + ADIS16334, + ADIS16350, + ADIS16360, + ADIS16362, + ADIS16364, + ADIS16400, +}; + +static int adis16334_get_freq(struct adis16400_state *st) +{ + int ret; + uint16_t t; + + ret = adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &t); + if (ret < 0) + return ret; + + t >>= ADIS16334_RATE_DIV_SHIFT; + + return (8192 >> t) / 10; +} + +static int adis16334_set_freq(struct adis16400_state *st, unsigned int freq) +{ + unsigned int t; + + freq *= 10; + if (freq < 8192) + t = ilog2(8192 / freq); + else + t = 0; + + if (t > 0x31) + t = 0x31; + + t <<= ADIS16334_RATE_DIV_SHIFT; + t |= ADIS16334_RATE_INT_CLK; + + return adis_write_reg_16(&st->adis, ADIS16400_SMPL_PRD, t); +} + +static int adis16400_get_freq(struct adis16400_state *st) +{ + int sps, ret; + uint16_t t; + + ret = adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &t); + if (ret < 0) + return ret; + + sps = (t & ADIS16400_SMPL_PRD_TIME_BASE) ? 53 : 1638; + sps /= (t & ADIS16400_SMPL_PRD_DIV_MASK) + 1; + + return sps; +} + +static int adis16400_set_freq(struct adis16400_state *st, unsigned int freq) +{ + unsigned int t; + + t = 1638 / freq; + if (t > 0) + t--; + t &= ADIS16400_SMPL_PRD_DIV_MASK; + + if ((t & ADIS16400_SMPL_PRD_DIV_MASK) >= 0x0A) + st->adis.spi->max_speed_hz = ADIS16400_SPI_SLOW; + else + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + + return adis_write_reg_8(&st->adis, ADIS16400_SMPL_PRD, t); +} + +static ssize_t adis16400_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adis16400_state *st = iio_priv(indio_dev); + int ret; + + ret = st->variant->get_freq(st); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret); +} + +static const unsigned adis16400_3db_divisors[] = { + [0] = 2, /* Special case */ + [1] = 6, + [2] = 12, + [3] = 25, + [4] = 50, + [5] = 100, + [6] = 200, + [7] = 200, /* Not a valid setting */ +}; + +static int adis16400_set_filter(struct iio_dev *indio_dev, int sps, int val) +{ + struct adis16400_state *st = iio_priv(indio_dev); + uint16_t val16; + int i, ret; + + for (i = ARRAY_SIZE(adis16400_3db_divisors) - 1; i >= 1; i--) { + if (sps / adis16400_3db_divisors[i] >= val) + break; + } + + ret = adis_read_reg_16(&st->adis, ADIS16400_SENS_AVG, &val16); + if (ret < 0) + return ret; + + ret = adis_write_reg_16(&st->adis, ADIS16400_SENS_AVG, + (val16 & ~0x07) | i); + return ret; +} + +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_to_iio_dev(dev); + struct adis16400_state *st = iio_priv(indio_dev); + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + if (val == 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + st->variant->set_freq(st, val); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +/* Power down the device */ +static int adis16400_stop_device(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int ret; + + ret = adis_write_reg_16(&st->adis, ADIS16400_SLP_CNT, + ADIS16400_SLP_CNT_POWER_OFF); + if (ret) + dev_err(&indio_dev->dev, + "problem with turning device off: SLP_CNT"); + + return ret; +} + +static int adis16400_initial_setup(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + uint16_t prod_id, smp_prd; + unsigned int device_id; + int ret; + + /* use low spi speed for init if the device has a slow mode */ + if (st->variant->flags & ADIS16400_HAS_SLOW_MODE) + st->adis.spi->max_speed_hz = ADIS16400_SPI_SLOW; + else + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + st->adis.spi->mode = SPI_MODE_3; + spi_setup(st->adis.spi); + + ret = adis_initial_startup(&st->adis); + if (ret) + return ret; + + if (st->variant->flags & ADIS16400_HAS_PROD_ID) { + ret = adis_read_reg_16(&st->adis, + ADIS16400_PRODUCT_ID, &prod_id); + if (ret) + goto err_ret; + + sscanf(indio_dev->name, "adis%u\n", &device_id); + + if (prod_id != device_id) + dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", + device_id, prod_id); + + dev_info(&indio_dev->dev, "%s: prod_id 0x%04x at CS%d (irq %d)\n", + indio_dev->name, prod_id, + st->adis.spi->chip_select, st->adis.spi->irq); + } + /* use high spi speed if possible */ + if (st->variant->flags & ADIS16400_HAS_SLOW_MODE) { + ret = adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &smp_prd); + if (ret) + goto err_ret; + + if ((smp_prd & ADIS16400_SMPL_PRD_DIV_MASK) < 0x0A) { + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + spi_setup(st->adis.spi); + } + } + +err_ret: + return ret; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + adis16400_read_frequency, + adis16400_write_frequency); + +static const uint8_t adis16400_addresses[] = { + [ADIS16400_SCAN_GYRO_X] = ADIS16400_XGYRO_OFF, + [ADIS16400_SCAN_GYRO_Y] = ADIS16400_YGYRO_OFF, + [ADIS16400_SCAN_GYRO_Z] = ADIS16400_ZGYRO_OFF, + [ADIS16400_SCAN_ACC_X] = ADIS16400_XACCL_OFF, + [ADIS16400_SCAN_ACC_Y] = ADIS16400_YACCL_OFF, + [ADIS16400_SCAN_ACC_Z] = ADIS16400_ZACCL_OFF, +}; + +static int adis16400_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int ret, sps; + + switch (info) { + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&indio_dev->mlock); + ret = adis_write_reg_16(&st->adis, + adis16400_addresses[chan->scan_index], val); + mutex_unlock(&indio_dev->mlock); + return ret; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + /* + * Need to cache values so we can update if the frequency + * changes. + */ + mutex_lock(&indio_dev->mlock); + st->filt_int = val; + /* Work out update to current value */ + sps = st->variant->get_freq(st); + if (sps < 0) { + mutex_unlock(&indio_dev->mlock); + return sps; + } + + ret = adis16400_set_filter(indio_dev, sps, 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 info) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int16_t val16; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, 0, val); + 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_VOLTAGE: + *val = 0; + if (chan->channel == 0) { + *val = 2; + *val2 = 418000; /* 2.418 mV */ + } else { + *val = 0; + *val2 = 805800; /* 805.8 uV */ + } + 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; /* 0.5 mgauss */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = st->variant->temp_scale_nano / 1000000; + *val2 = (st->variant->temp_scale_nano % 1000000); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&indio_dev->mlock); + ret = adis_read_reg_16(&st->adis, + adis16400_addresses[chan->scan_index], &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 = st->variant->temp_offset; + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + mutex_lock(&indio_dev->mlock); + /* Need both the number of taps and the sampling frequency */ + ret = adis_read_reg_16(&st->adis, + ADIS16400_SENS_AVG, + &val16); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + ret = st->variant->get_freq(st); + if (ret >= 0) + *val = ret / adis16400_3db_divisors[val16 & 0x07]; + *val2 = 0; + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +#define ADIS16400_VOLTAGE_CHAN(addr, bits, name, si) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = 0, \ + .extend_name = name, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, \ + .address = (addr), \ + .scan_index = (si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_SUPPLY_CHAN(addr, bits) \ + ADIS16400_VOLTAGE_CHAN(addr, bits, "supply", ADIS16400_SCAN_SUPPLY) + +#define ADIS16400_AUX_ADC_CHAN(addr, bits) \ + ADIS16400_VOLTAGE_CHAN(addr, bits, NULL, ADIS16400_SCAN_ADC) + +#define ADIS16400_GYRO_CHAN(mod, addr, bits) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, \ + .address = addr, \ + .scan_index = ADIS16400_SCAN_GYRO_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_ACCEL_CHAN(mod, addr, bits) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, \ + .address = (addr), \ + .scan_index = ADIS16400_SCAN_ACC_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_MAGN_CHAN(mod, addr, bits) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, \ + .address = (addr), \ + .scan_index = ADIS16400_SCAN_MAGN_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_MOD_TEMP_NAME_X "x" +#define ADIS16400_MOD_TEMP_NAME_Y "y" +#define ADIS16400_MOD_TEMP_NAME_Z "z" + +#define ADIS16400_MOD_TEMP_CHAN(mod, addr, bits) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .extend_name = ADIS16400_MOD_TEMP_NAME_ ## mod, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | \ + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, \ + .address = (addr), \ + .scan_index = ADIS16350_SCAN_TEMP_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_TEMP_CHAN(addr, bits) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, \ + .address = (addr), \ + .scan_index = ADIS16350_SCAN_TEMP_X, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_INCLI_CHAN(mod, addr, bits) { \ + .type = IIO_INCLI, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .address = (addr), \ + .scan_index = ADIS16300_SCAN_INCLI_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec adis16400_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 14), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_MAGN_CHAN(X, ADIS16400_XMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Y, ADIS16400_YMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Z, ADIS16400_ZMAGN_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16400_TEMP_OUT, 12), + ADIS16400_AUX_ADC_CHAN(ADIS16400_AUX_ADC, 12), + IIO_CHAN_SOFT_TIMESTAMP(12) +}; + +static const struct iio_chan_spec adis16350_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 12), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_MAGN_CHAN(X, ADIS16400_XMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Y, ADIS16400_YMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Z, ADIS16400_ZMAGN_OUT, 14), + ADIS16400_AUX_ADC_CHAN(ADIS16300_AUX_ADC, 12), + ADIS16400_MOD_TEMP_CHAN(X, ADIS16350_XTEMP_OUT, 12), + ADIS16400_MOD_TEMP_CHAN(Y, ADIS16350_YTEMP_OUT, 12), + ADIS16400_MOD_TEMP_CHAN(Z, ADIS16350_ZTEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(11) +}; + +static const struct iio_chan_spec adis16300_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 12), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16350_XTEMP_OUT, 12), + ADIS16400_AUX_ADC_CHAN(ADIS16300_AUX_ADC, 12), + ADIS16400_INCLI_CHAN(X, ADIS16300_PITCH_OUT, 13), + ADIS16400_INCLI_CHAN(Y, ADIS16300_ROLL_OUT, 13), + IIO_CHAN_SOFT_TIMESTAMP(14) +}; + +static const struct iio_chan_spec adis16334_channels[] = { + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16350_XTEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(8) +}; + +static struct attribute *adis16400_attributes[] = { + &iio_dev_attr_sampling_frequency.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), + .flags = ADIS16400_HAS_SLOW_MODE, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = 5884, + .temp_scale_nano = 140000000, /* 0.14 C */ + .temp_offset = 25000000 / 140000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16334] = { + .channels = adis16334_channels, + .num_channels = ARRAY_SIZE(adis16334_channels), + .flags = ADIS16400_HAS_PROD_ID, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(1000), /* 1 mg */ + .temp_scale_nano = 67850000, /* 0.06785 C */ + .temp_offset = 25000000 / 67850, /* 25 C = 0x00 */ + .set_freq = adis16334_set_freq, + .get_freq = adis16334_get_freq, + }, + [ADIS16350] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .gyro_scale_micro = IIO_DEGREE_TO_RAD(73260), /* 0.07326 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(2522), /* 0.002522 g */ + .temp_scale_nano = 145300000, /* 0.1453 C */ + .temp_offset = 25000000 / 145300, /* 25 C = 0x00 */ + .flags = ADIS16400_NO_BURST | ADIS16400_HAS_SLOW_MODE, + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16360] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(3333), /* 3.333 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16362] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(333), /* 0.333 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16364] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(1000), /* 1 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16400] = { + .channels = adis16400_channels, + .num_channels = ARRAY_SIZE(adis16400_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(3333), /* 3.333 mg */ + .temp_scale_nano = 140000000, /* 0.14 C */ + .temp_offset = 25000000 / 140000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + } +}; + +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, + .update_scan_mode = adis16400_update_scan_mode, +}; + +static const unsigned long adis16400_burst_scan_mask[] = { + ~0UL, + 0, +}; + +static const char * const adis16400_status_error_msgs[] = { + [ADIS16400_DIAG_STAT_ZACCL_FAIL] = "Z-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_YACCL_FAIL] = "Y-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_XACCL_FAIL] = "X-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_XGYRO_FAIL] = "X-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_YGYRO_FAIL] = "Y-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_ZGYRO_FAIL] = "Z-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_ALARM2] = "Alarm 2 active", + [ADIS16400_DIAG_STAT_ALARM1] = "Alarm 1 active", + [ADIS16400_DIAG_STAT_FLASH_CHK] = "Flash checksum error", + [ADIS16400_DIAG_STAT_SELF_TEST] = "Self test error", + [ADIS16400_DIAG_STAT_OVERFLOW] = "Sensor overrange", + [ADIS16400_DIAG_STAT_SPI_FAIL] = "SPI failure", + [ADIS16400_DIAG_STAT_FLASH_UPT] = "Flash update failed", + [ADIS16400_DIAG_STAT_POWER_HIGH] = "Power supply above 5.25V", + [ADIS16400_DIAG_STAT_POWER_LOW] = "Power supply below 4.75V", +}; + +static const struct adis_data adis16400_data = { + .msc_ctrl_reg = ADIS16400_MSC_CTRL, + .glob_cmd_reg = ADIS16400_GLOB_CMD, + .diag_stat_reg = ADIS16400_DIAG_STAT, + + .read_delay = 50, + .write_delay = 50, + + .self_test_mask = ADIS16400_MSC_CTRL_MEM_TEST, + .startup_delay = ADIS16400_STARTUP_DELAY, + + .status_error_msgs = adis16400_status_error_msgs, + .status_error_mask = BIT(ADIS16400_DIAG_STAT_ZACCL_FAIL) | + BIT(ADIS16400_DIAG_STAT_YACCL_FAIL) | + BIT(ADIS16400_DIAG_STAT_XACCL_FAIL) | + BIT(ADIS16400_DIAG_STAT_XGYRO_FAIL) | + BIT(ADIS16400_DIAG_STAT_YGYRO_FAIL) | + BIT(ADIS16400_DIAG_STAT_ZGYRO_FAIL) | + BIT(ADIS16400_DIAG_STAT_ALARM2) | + BIT(ADIS16400_DIAG_STAT_ALARM1) | + BIT(ADIS16400_DIAG_STAT_FLASH_CHK) | + BIT(ADIS16400_DIAG_STAT_SELF_TEST) | + BIT(ADIS16400_DIAG_STAT_OVERFLOW) | + BIT(ADIS16400_DIAG_STAT_SPI_FAIL) | + BIT(ADIS16400_DIAG_STAT_FLASH_UPT) | + BIT(ADIS16400_DIAG_STAT_POWER_HIGH) | + BIT(ADIS16400_DIAG_STAT_POWER_LOW), +}; + +static int adis16400_probe(struct spi_device *spi) +{ + struct adis16400_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + /* 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; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (!(st->variant->flags & ADIS16400_NO_BURST)) + indio_dev->available_scan_masks = adis16400_burst_scan_mask; + + ret = adis_init(&st->adis, indio_dev, spi, &adis16400_data); + if (ret) + goto error_free_dev; + + ret = adis_setup_buffer_and_trigger(&st->adis, indio_dev, + adis16400_trigger_handler); + if (ret) + goto error_free_dev; + + /* Get the device into a sane initial state */ + ret = adis16400_initial_setup(indio_dev); + if (ret) + goto error_cleanup_buffer; + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer; + + return 0; + +error_cleanup_buffer: + adis_cleanup_buffer_and_trigger(&st->adis, indio_dev); +error_free_dev: + iio_device_free(indio_dev); + return ret; +} + +static int adis16400_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adis16400_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + adis16400_stop_device(indio_dev); + + adis_cleanup_buffer_and_trigger(&st->adis, indio_dev); + + 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", ADIS16360}, + {"adis16400", ADIS16400}, + {"adis16405", ADIS16400}, + {} +}; +MODULE_DEVICE_TABLE(spi, adis16400_id); + +static struct spi_driver adis16400_driver = { + .driver = { + .name = "adis16400", + .owner = THIS_MODULE, + }, + .id_table = adis16400_id, + .probe = adis16400_probe, + .remove = adis16400_remove, +}; +module_spi_driver(adis16400_driver); + +MODULE_AUTHOR("Manuel Stahl "); +MODULE_DESCRIPTION("Analog Devices ADIS16400/5 IMU SPI driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3