Commit ccd2b52f authored by Lars-Peter Clausen's avatar Lars-Peter Clausen Committed by Jonathan Cameron

staging:iio: Add common ADIS library

A lot of the devices from the ADIS family use the same methods for accessing
registers, sampling data and trigger handling. They also have similar register
layout for the control registers.

This patch adds a common library for these devices. The library implements
functions for reading and writing registers as buffer and trigger management. It
also provides a set functions for accessing the control registers and for
running the devices internal self-test. Having this common library code will
allow us to remove a lot of duplicated code.
Signed-off-by: default avatarLars-Peter Clausen <lars@metafoo.de>
Signed-off-by: default avatarJonathan Cameron <jic23@kernel.org>
parent 6dc973d4
......@@ -15,3 +15,16 @@ config ADIS16400
(adis16400 series also have magnetometers).
endmenu
config IIO_ADIS_LIB
tristate
help
A set of IO helper functions for the Analog Devices ADIS* device family.
config IIO_ADIS_LIB_BUFFER
bool
select IIO_TRIGGER
select IIO_SW_RING
help
A set of buffer helper functions for the Analog Devices ADIS* device
family.
......@@ -5,3 +5,8 @@
adis16400-y := adis16400_core.o
adis16400-$(CONFIG_IIO_BUFFER) += adis16400_ring.o adis16400_trigger.o
obj-$(CONFIG_ADIS16400) += adis16400.o
adis_lib-y += adis.o
adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_trigger.o
adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_buffer.o
obj-$(CONFIG_IIO_ADIS_LIB) += adis_lib.o
/*
* Common library for ADIS16XXX devices
*
* Copyright 2012 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2 or later.
*/
#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/module.h>
#include <asm/unaligned.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include "adis.h"
#define ADIS_MSC_CTRL_DATA_RDY_EN BIT(2)
#define ADIS_MSC_CTRL_DATA_RDY_POL_HIGH BIT(1)
#define ADIS_MSC_CTRL_DATA_RDY_DIO2 BIT(0)
#define ADIS_GLOB_CMD_SW_RESET BIT(7)
/**
* adis_write_reg_8() - Write single byte to a register
* @adis: The adis device
* @reg: The address of the register to be written
* @val: The value to write
*/
int adis_write_reg_8(struct adis *adis, unsigned int reg, uint8_t val)
{
int ret;
mutex_lock(&adis->txrx_lock);
adis->tx[0] = ADIS_WRITE_REG(reg);
adis->tx[1] = val;
ret = spi_write(adis->spi, adis->tx, 2);
mutex_unlock(&adis->txrx_lock);
return ret;
}
EXPORT_SYMBOL_GPL(adis_write_reg_8);
/**
* adis_write_reg_16() - Write 2 bytes to a pair of registers
* @adis: The adis device
* @reg: The address of the lower of the two registers
* @val: Value to be written
*/
int adis_write_reg_16(struct adis *adis, unsigned int reg, uint16_t value)
{
int ret;
struct spi_message msg;
struct spi_transfer xfers[] = {
{
.tx_buf = adis->tx,
.bits_per_word = 8,
.len = 2,
.cs_change = 1,
.delay_usecs = adis->data->write_delay,
}, {
.tx_buf = adis->tx + 2,
.bits_per_word = 8,
.len = 2,
.delay_usecs = adis->data->write_delay,
},
};
mutex_lock(&adis->txrx_lock);
adis->tx[0] = ADIS_WRITE_REG(reg);
adis->tx[1] = value & 0xff;
adis->tx[2] = ADIS_WRITE_REG(reg + 1);
adis->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(adis->spi, &msg);
mutex_unlock(&adis->txrx_lock);
return ret;
}
EXPORT_SYMBOL_GPL(adis_write_reg_16);
/**
* adis_read_reg_16() - read 2 bytes from a 16-bit register
* @adis: The adis device
* @reg: The address of the lower of the two registers
* @val: The value read back from the device
*/
int adis_read_reg_16(struct adis *adis, unsigned int reg, uint16_t *val)
{
struct spi_message msg;
int ret;
struct spi_transfer xfers[] = {
{
.tx_buf = adis->tx,
.bits_per_word = 8,
.len = 2,
.cs_change = 1,
.delay_usecs = adis->data->read_delay,
}, {
.rx_buf = adis->rx,
.bits_per_word = 8,
.len = 2,
.delay_usecs = adis->data->read_delay,
},
};
mutex_lock(&adis->txrx_lock);
adis->tx[0] = ADIS_READ_REG(reg);
adis->tx[1] = 0;
spi_message_init(&msg);
spi_message_add_tail(&xfers[0], &msg);
spi_message_add_tail(&xfers[1], &msg);
ret = spi_sync(adis->spi, &msg);
if (ret) {
dev_err(&adis->spi->dev, "Failed to read 16 bit register 0x%02X: %d\n",
reg, ret);
goto error_ret;
}
*val = get_unaligned_be16(adis->rx);
error_ret:
mutex_unlock(&adis->txrx_lock);
return ret;
}
EXPORT_SYMBOL_GPL(adis_read_reg_16);
/**
* adis_enable_irq() - Enable or disable data ready IRQ
* @adis: The adis device
* @enable: Whether to enable the IRQ
*
* Returns 0 on success, negative error code otherwise
*/
int adis_enable_irq(struct adis *adis, bool enable)
{
int ret = 0;
uint16_t msc;
ret = adis_read_reg_16(adis, adis->data->msc_ctrl_reg, &msc);
if (ret)
goto error_ret;
msc |= ADIS_MSC_CTRL_DATA_RDY_POL_HIGH;
msc &= ~ADIS_MSC_CTRL_DATA_RDY_DIO2;
if (enable)
msc |= ADIS_MSC_CTRL_DATA_RDY_EN;
else
msc &= ~ADIS_MSC_CTRL_DATA_RDY_EN;
ret = adis_write_reg_16(adis, adis->data->msc_ctrl_reg, msc);
error_ret:
return ret;
}
EXPORT_SYMBOL(adis_enable_irq);
/**
* adis_check_status() - Check the device for error conditions
* @adis: The adis device
*
* Returns 0 on success, a negative error code otherwise
*/
int adis_check_status(struct adis *adis)
{
uint16_t status;
int ret;
int i;
ret = adis_read_reg_16(adis, adis->data->diag_stat_reg, &status);
if (ret < 0)
return ret;
status &= adis->data->status_error_mask;
if (status == 0)
return 0;
for (i = 0; i < 16; ++i) {
if (status & BIT(i)) {
dev_err(&adis->spi->dev, "%s.\n",
adis->data->status_error_msgs[i]);
}
}
return -EIO;
}
EXPORT_SYMBOL_GPL(adis_check_status);
/**
* adis_reset() - Reset the device
* @adis: The adis device
*
* Returns 0 on success, a negative error code otherwise
*/
int adis_reset(struct adis *adis)
{
int ret;
ret = adis_write_reg_8(adis, adis->data->glob_cmd_reg,
ADIS_GLOB_CMD_SW_RESET);
if (ret)
dev_err(&adis->spi->dev, "Failed to reset device: %d\n", ret);
return ret;
}
EXPORT_SYMBOL_GPL(adis_reset);
static int adis_self_test(struct adis *adis)
{
int ret;
ret = adis_write_reg_16(adis, adis->data->msc_ctrl_reg,
adis->data->self_test_mask);
if (ret) {
dev_err(&adis->spi->dev, "Failed to initiate self test: %d\n",
ret);
return ret;
}
msleep(adis->data->startup_delay);
return adis_check_status(adis);
}
/**
* adis_inital_startup() - Performs device self-test
* @adis: The adis device
*
* Returns 0 if the device is operational, a negative error code otherwise.
*
* This function should be called early on in the device initialization sequence
* to ensure that the device is in a sane and known state and that it is usable.
*/
int adis_initial_startup(struct adis *adis)
{
int ret;
ret = adis_self_test(adis);
if (ret) {
dev_err(&adis->spi->dev, "Self-test failed, trying reset.\n");
adis_reset(adis);
msleep(adis->data->startup_delay);
ret = adis_self_test(adis);
if (ret) {
dev_err(&adis->spi->dev, "Second self-test failed, giving up.\n");
return ret;
}
}
return 0;
}
EXPORT_SYMBOL_GPL(adis_initial_startup);
/**
* adis_single_conversion() - Performs a single sample conversion
* @indio_dev: The IIO device
* @chan: The IIO channel
* @error_mask: Mask for the error bit
* @val: Result of the conversion
*
* Returns IIO_VAL_INT on success, a negative error code otherwise.
*
* The function performs a single conversion on a given channel and post
* processes the value accordingly to the channel spec. If a error_mask is given
* the function will check if the mask is set in the returned raw value. If it
* is set the function will perform a self-check. If the device does not report
* a error bit in the channels raw value set error_mask to 0.
*/
int adis_single_conversion(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan, unsigned int error_mask, int *val)
{
struct adis *adis = iio_device_get_drvdata(indio_dev);
uint16_t val16;
int ret;
mutex_lock(&indio_dev->mlock);
ret = adis_read_reg_16(adis, chan->address, &val16);
if (ret)
goto err_unlock;
if (val16 & error_mask) {
ret = adis_check_status(adis);
if (ret)
goto err_unlock;
}
if (chan->scan_type.sign == 's')
*val = sign_extend32(val16, chan->scan_type.realbits - 1);
else
*val = val16 & ((1 << chan->scan_type.realbits) - 1);
ret = IIO_VAL_INT;
err_unlock:
mutex_unlock(&indio_dev->mlock);
return ret;
}
EXPORT_SYMBOL_GPL(adis_single_conversion);
/**
* adis_init() - Initialize adis device structure
* @adis: The adis device
* @indio_dev: The iio device
* @spi: The spi device
* @data: Chip specific data
*
* Returns 0 on success, a negative error code otherwise.
*
* This function must be called, before any other adis helper function may be
* called.
*/
int adis_init(struct adis *adis, struct iio_dev *indio_dev,
struct spi_device *spi, const struct adis_data *data)
{
mutex_init(&adis->txrx_lock);
adis->spi = spi;
adis->data = data;
iio_device_set_drvdata(indio_dev, adis);
return adis_enable_irq(adis, false);
}
EXPORT_SYMBOL_GPL(adis_init);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("Common library code for ADIS16XXX devices");
/*
* Common library for ADIS16XXX devices
*
* Copyright 2012 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*
* Licensed under the GPL-2 or later.
*/
#ifndef __IIO_ADIS_H__
#define __IIO_ADIS_H__
#include <linux/spi/spi.h>
#include <linux/interrupt.h>
#include <linux/iio/types.h>
#define ADIS_WRITE_REG(reg) (0x80 | (reg))
#define ADIS_READ_REG(reg) (reg)
/**
* struct adis_data - ADIS chip variant specific data
* @read_delay: SPI delay for read operations in us
* @write_delay: SPI delay for write operations in us
* @glob_cmd_reg: Register address of the GLOB_CMD register
* @msc_ctrl_reg: Register address of the MSC_CTRL register
* @diag_stat_reg: Register address of the DIAG_STAT register
* @status_error_msgs: Array of error messgaes
* @status_error_mask:
*/
struct adis_data {
unsigned int read_delay;
unsigned int write_delay;
unsigned int glob_cmd_reg;
unsigned int msc_ctrl_reg;
unsigned int diag_stat_reg;
unsigned int self_test_mask;
unsigned int startup_delay;
const char * const *status_error_msgs;
unsigned int status_error_mask;
};
struct adis {
struct spi_device *spi;
struct iio_trigger *trig;
const struct adis_data *data;
struct mutex txrx_lock;
struct spi_message msg;
struct spi_transfer *xfer;
void *buffer;
uint8_t tx[8] ____cacheline_aligned;
uint8_t rx[4];
};
int adis_init(struct adis *adis, struct iio_dev *indio_dev,
struct spi_device *spi, const struct adis_data *data);
int adis_reset(struct adis *adis);
int adis_write_reg_8(struct adis *adis, unsigned int reg, uint8_t val);
int adis_write_reg_16(struct adis *adis, unsigned int reg, uint16_t val);
int adis_read_reg_16(struct adis *adis, unsigned int reg, uint16_t *val);
int adis_enable_irq(struct adis *adis, bool enable);
int adis_check_status(struct adis *adis);
int adis_initial_startup(struct adis *adis);
int adis_single_conversion(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan, unsigned int error_mask,
int *val);
#define ADIS_VOLTAGE_CHAN(addr, si, chan, name, bits) { \
.type = IIO_VOLTAGE, \
.indexed = 1, \
.channel = (chan), \
.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, \
}, \
}
#define ADIS_SUPPLY_CHAN(addr, si, bits) \
ADIS_VOLTAGE_CHAN(addr, si, 0, "supply", bits)
#define ADIS_AUX_ADC_CHAN(addr, si, bits) \
ADIS_VOLTAGE_CHAN(addr, si, 1, NULL, bits)
#define ADIS_TEMP_CHAN(addr, si, bits) { \
.type = IIO_TEMP, \
.indexed = 1, \
.channel = 0, \
.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \
IIO_CHAN_INFO_SCALE_SEPARATE_BIT | \
IIO_CHAN_INFO_OFFSET_SEPARATE_BIT, \
.address = (addr), \
.scan_index = (si), \
.scan_type = { \
.sign = 'u', \
.realbits = (bits), \
.storagebits = 16, \
}, \
}
#define ADIS_MOD_CHAN(_type, mod, addr, si, info, bits) { \
.type = (_type), \
.modified = 1, \
.channel2 = IIO_MOD_ ## mod, \
.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \
IIO_CHAN_INFO_SCALE_SHARED_BIT | \
info, \
.address = (addr), \
.scan_index = (si), \
.scan_type = { \
.sign = 's', \
.realbits = (bits), \
.storagebits = 16, \
}, \
}
#define ADIS_ACCEL_CHAN(mod, addr, si, info, bits) \
ADIS_MOD_CHAN(IIO_ACCEL, mod, addr, si, info, bits)
#define ADIS_GYRO_CHAN(mod, addr, si, info, bits) \
ADIS_MOD_CHAN(IIO_ANGL_VEL, mod, addr, si, info, bits)
#define ADIS_INCLI_CHAN(mod, addr, si, info, bits) \
ADIS_MOD_CHAN(IIO_INCLI, mod, addr, si, info, bits)
#define ADIS_ROT_CHAN(mod, addr, si, info, bits) \
ADIS_MOD_CHAN(IIO_ROT, mod, addr, si, info, bits)
#ifdef CONFIG_IIO_ADIS_LIB_BUFFER
int adis_setup_buffer_and_trigger(struct adis *adis,
struct iio_dev *indio_dev, irqreturn_t (*trigger_handler)(int, void *));
void adis_cleanup_buffer_and_trigger(struct adis *adis,
struct iio_dev *indio_dev);
int adis_probe_trigger(struct adis *adis, struct iio_dev *indio_dev);
void adis_remove_trigger(struct adis *adis);
#else /* CONFIG_IIO_BUFFER */
static inline int adis_setup_buffer_and_trigger(struct adis *adis,
struct iio_dev *indio_dev, irqreturn_t (*trigger_handler)(int, void *))
{
return 0;
}
static inline void adis_cleanup_buffer_and_trigger(struct adis *adis,
struct iio_dev *indio_dev)
{
}
static inline int adis_probe_trigger(struct adis *adis,
struct iio_dev *indio_dev)
{
return 0;
}
static inline void adis_remove_trigger(struct adis *adis)
{
}
#endif /* CONFIG_IIO_BUFFER */
#endif
#include <linux/export.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/kernel.h>
#include <linux/spi/spi.h>
#include <linux/slab.h>
#include <linux/iio/iio.h>
#include <linux/iio/buffer.h>
#include "../ring_sw.h"
#include <linux/iio/trigger_consumer.h>
#include "adis.h"
#define ADIS_MAX_OUTPUTS 12
static int adis_read_buffer_data(struct adis *adis, struct iio_dev *indio_dev)
{
int n_outputs = indio_dev->num_channels;
struct spi_transfer xfers[ADIS_MAX_OUTPUTS + 1];
struct spi_message msg;
int ret;
int i;
mutex_lock(&adis->txrx_lock);
spi_message_init(&msg);
memset(xfers, 0, sizeof(xfers));
for (i = 0; i <= n_outputs; i++) {
xfers[i].bits_per_word = 8;
xfers[i].cs_change = 1;
xfers[i].len = 2;
xfers[i].delay_usecs = adis->data->read_delay;
if (i < n_outputs) {
xfers[i].tx_buf = adis->tx + 2 * i;
adis->tx[2 * i] = indio_dev->channels[i].address;
adis->tx[2 * i + 1] = 0;
}
if (i >= 1)
xfers[i].rx_buf = adis->rx + 2 * (i - 1);
spi_message_add_tail(&xfers[i], &msg);
}
ret = spi_sync(adis->spi, &msg);
if (ret)
dev_err(&adis->spi->dev, "Failed to read data: %d", ret);
mutex_unlock(&adis->txrx_lock);
return ret;
}
static irqreturn_t adis_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct adis *adis = iio_device_get_drvdata(indio_dev);
u16 *data;
int i = 0;
data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);
if (data == NULL) {
dev_err(&adis->spi->dev, "Failed to allocate memory.");
return -ENOMEM;
}
if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)
&& adis_read_buffer_data(adis, indio_dev) >= 0)
for (; i < bitmap_weight(indio_dev->active_scan_mask,
indio_dev->masklength); i++)
data[i] = be16_to_cpup((__be16 *)&(adis->rx[i*2]));
/* Guaranteed to be aligned with 8 byte boundary */
if (indio_dev->scan_timestamp)
*((s64 *)(PTR_ALIGN(data, sizeof(s64)))) = pf->timestamp;
iio_push_to_buffers(indio_dev, (u8 *)data);
iio_trigger_notify_done(indio_dev->trig);
kfree(data);
return IRQ_HANDLED;
}
static const struct iio_buffer_setup_ops adis_buffer_setup_ops = {
.preenable = &iio_sw_buffer_preenable,
.postenable = &iio_triggered_buffer_postenable,
.predisable = &iio_triggered_buffer_predisable,
};
static int adis_buffer_setup(struct iio_dev *indio_dev,
irqreturn_t (*trigger_handler)(int, void *))
{
int ret = 0;
struct iio_buffer *buffer;
if (!trigger_handler)
trigger_handler = &adis_trigger_handler;
buffer = iio_sw_rb_allocate(indio_dev);
if (!buffer) {
ret = -ENOMEM;
return ret;
}