summaryrefslogtreecommitdiff
path: root/drivers/iio/imu/adis.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-12-11 21:59:44 (GMT)
committerLinus Torvalds <torvalds@linux-foundation.org>2012-12-11 21:59:44 (GMT)
commit8966961b31c251b854169e9886394c2a20f2cea7 (patch)
tree248a625b23335acbd5ca4b55eb136fe0dc8ba0aa /drivers/iio/imu/adis.c
parent6a5971d8fea1f4a8c33dfe0cec6a1c490f0c9cde (diff)
parent7bcb57cde66c19df378f3468ea342166a8a4504d (diff)
downloadlinux-fsl-qoriq-8966961b31c251b854169e9886394c2a20f2cea7.tar.xz
Merge tag 'staging-3.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
Pull staging driver tree merge from Greg Kroah-Hartman: "Here's the big staging tree merge for 3.8-rc1 There's a lot of patches in here, the majority being the comedi rework/cleanup that has been ongoing and is causing a huge reduction in overall code size, which is amazing to watch. We also removed some older drivers (telephony and rts_pstor), and added a new one (fwserial which also came in through the tty tree due to tty api changes, take that one if you get merge conflicts.) The iio and ipack drivers are moving out of the staging area into their own part of the kernel as they have been cleaned up sufficiently and are working well. Overall, again a reduction of code: 768 files changed, 31887 insertions(+), 82166 deletions(-) All of this has been in the linux-next tree for a while. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>" * tag 'staging-3.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (1298 commits) iio: imu: adis16480: remove duplicated include from adis16480.c iio: gyro: adis16136: remove duplicated include from adis16136.c iio:imu: adis16480: show_firmware() buffer too small iio:gyro: adis16136: divide by zero in write_frequency() iio: adc: Add Texas Instruments ADC081C021/027 support iio:ad7793: Add support for the ad7796 and ad7797 iio:ad7793: Add support for the ad7798 and ad7799 staging:iio: Move ad7793 driver out of staging staging:iio:ad7793: Implement stricter id checking staging:iio:ad7793: Move register definitions from header to source staging:iio:ad7793: Rework regulator handling staging:iio:ad7793: Rework platform data staging:iio:ad7793: Use kstrtol instead of strict_strtol staging:iio:ad7793: Use usleep_range instead of msleep staging:iio:ad7793: Fix temperature scale staging:iio:ad7793: Fix VDD monitor scale staging: gdm72xx: unlock on error in init_usb() staging: panel: pass correct lengths to keypad_send_key() staging: comedi: addi_apci_2032: fix interrupt support staging: comedi: addi_apci_2032: move i_APCI2032_ConfigDigitalOutput() ...
Diffstat (limited to 'drivers/iio/imu/adis.c')
-rw-r--r--drivers/iio/imu/adis.c440
1 files changed, 440 insertions, 0 deletions
diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c
new file mode 100644
index 0000000..911255d
--- /dev/null
+++ b/drivers/iio/imu/adis.c
@@ -0,0 +1,440 @@
+/*
+ * 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 <linux/iio/imu/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)
+
+int adis_write_reg(struct adis *adis, unsigned int reg,
+ unsigned int value, unsigned int size)
+{
+ unsigned int page = reg / ADIS_PAGE_SIZE;
+ int ret, i;
+ 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,
+ .cs_change = 1,
+ .delay_usecs = adis->data->write_delay,
+ }, {
+ .tx_buf = adis->tx + 4,
+ .bits_per_word = 8,
+ .len = 2,
+ .cs_change = 1,
+ .delay_usecs = adis->data->write_delay,
+ }, {
+ .tx_buf = adis->tx + 6,
+ .bits_per_word = 8,
+ .len = 2,
+ .delay_usecs = adis->data->write_delay,
+ }, {
+ .tx_buf = adis->tx + 8,
+ .bits_per_word = 8,
+ .len = 2,
+ .delay_usecs = adis->data->write_delay,
+ },
+ };
+
+ mutex_lock(&adis->txrx_lock);
+
+ spi_message_init(&msg);
+
+ if (adis->current_page != page) {
+ adis->tx[0] = ADIS_WRITE_REG(ADIS_REG_PAGE_ID);
+ adis->tx[1] = page;
+ spi_message_add_tail(&xfers[0], &msg);
+ }
+
+ switch (size) {
+ case 4:
+ adis->tx[8] = ADIS_WRITE_REG(reg + 3);
+ adis->tx[9] = (value >> 24) & 0xff;
+ adis->tx[6] = ADIS_WRITE_REG(reg + 2);
+ adis->tx[7] = (value >> 16) & 0xff;
+ case 2:
+ adis->tx[4] = ADIS_WRITE_REG(reg + 1);
+ adis->tx[5] = (value >> 8) & 0xff;
+ case 1:
+ adis->tx[2] = ADIS_WRITE_REG(reg);
+ adis->tx[3] = value & 0xff;
+ break;
+ default:
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ xfers[size].cs_change = 0;
+
+ for (i = 1; i <= size; i++)
+ spi_message_add_tail(&xfers[i], &msg);
+
+ ret = spi_sync(adis->spi, &msg);
+ if (ret) {
+ dev_err(&adis->spi->dev, "Failed to write register 0x%02X: %d\n",
+ reg, ret);
+ } else {
+ adis->current_page = page;
+ }
+
+out_unlock:
+ mutex_unlock(&adis->txrx_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(adis_write_reg);
+
+/**
+ * adis_read_reg() - 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(struct adis *adis, unsigned int reg,
+ unsigned int *val, unsigned int size)
+{
+ unsigned int page = reg / ADIS_PAGE_SIZE;
+ 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->write_delay,
+ }, {
+ .tx_buf = adis->tx + 2,
+ .bits_per_word = 8,
+ .len = 2,
+ .cs_change = 1,
+ .delay_usecs = adis->data->read_delay,
+ }, {
+ .tx_buf = adis->tx + 4,
+ .rx_buf = adis->rx,
+ .bits_per_word = 8,
+ .len = 2,
+ .cs_change = 1,
+ .delay_usecs = adis->data->read_delay,
+ }, {
+ .rx_buf = adis->rx + 2,
+ .bits_per_word = 8,
+ .len = 2,
+ .delay_usecs = adis->data->read_delay,
+ },
+ };
+
+ mutex_lock(&adis->txrx_lock);
+ spi_message_init(&msg);
+
+ if (adis->current_page != page) {
+ adis->tx[0] = ADIS_WRITE_REG(ADIS_REG_PAGE_ID);
+ adis->tx[1] = page;
+ spi_message_add_tail(&xfers[0], &msg);
+ }
+
+ switch (size) {
+ case 4:
+ adis->tx[2] = ADIS_READ_REG(reg + 2);
+ adis->tx[3] = 0;
+ spi_message_add_tail(&xfers[1], &msg);
+ case 2:
+ adis->tx[4] = ADIS_READ_REG(reg);
+ adis->tx[5] = 0;
+ spi_message_add_tail(&xfers[2], &msg);
+ spi_message_add_tail(&xfers[3], &msg);
+ break;
+ default:
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ ret = spi_sync(adis->spi, &msg);
+ if (ret) {
+ dev_err(&adis->spi->dev, "Failed to read register 0x%02X: %d\n",
+ reg, ret);
+ goto out_unlock;
+ } else {
+ adis->current_page = page;
+ }
+
+ switch (size) {
+ case 4:
+ *val = get_unaligned_be32(adis->rx);
+ break;
+ case 2:
+ *val = get_unaligned_be16(adis->rx + 2);
+ break;
+ }
+
+out_unlock:
+ mutex_unlock(&adis->txrx_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(adis_read_reg);
+
+#ifdef CONFIG_DEBUG_FS
+
+int adis_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, unsigned int writeval, unsigned int *readval)
+{
+ struct adis *adis = iio_device_get_drvdata(indio_dev);
+
+ if (readval) {
+ uint16_t val16;
+ int ret;
+
+ ret = adis_read_reg_16(adis, reg, &val16);
+ *readval = val16;
+
+ return ret;
+ } else {
+ return adis_write_reg_16(adis, reg, writeval);
+ }
+}
+EXPORT_SYMBOL(adis_debugfs_reg_access);
+
+#endif
+
+/**
+ * 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;
+
+ if (adis->data->enable_irq)
+ return adis->data->enable_irq(adis, enable);
+
+ 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);
+ unsigned int uval;
+ int ret;
+
+ mutex_lock(&indio_dev->mlock);
+
+ ret = adis_read_reg(adis, chan->address, &uval,
+ chan->scan_type.storagebits / 8);
+ if (ret)
+ goto err_unlock;
+
+ if (uval & error_mask) {
+ ret = adis_check_status(adis);
+ if (ret)
+ goto err_unlock;
+ }
+
+ if (chan->scan_type.sign == 's')
+ *val = sign_extend32(uval, chan->scan_type.realbits - 1);
+ else
+ *val = uval & ((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);
+
+ if (data->has_paging) {
+ /* Need to set the page before first read/write */
+ adis->current_page = -1;
+ } else {
+ /* Page will always be 0 */
+ adis->current_page = 0;
+ }
+
+ 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");