iio:gyro: Add support for the ADIS16136 gyroscope
authorLars-Peter Clausen <lars@metafoo.de>
Tue, 20 Nov 2012 13:36:00 +0000 (13:36 +0000)
committerJonathan Cameron <jic23@kernel.org>
Tue, 20 Nov 2012 21:26:34 +0000 (21:26 +0000)
This patch adds support for the ADIS16133, ADIS16135, ADIS16136 single channel
gyroscopes. The main difference between them is the sensor precision.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>
drivers/iio/gyro/Kconfig
drivers/iio/gyro/Makefile
drivers/iio/gyro/adis16136.c [new file with mode: 0644]

index 21e27e2fc68cf586e374f1709024c99e0204ba15..48ed1483ff27260e8ac24634cc670acf32f33172 100644 (file)
@@ -3,6 +3,15 @@
 #
 menu "Digital gyroscope sensors"
 
+config ADIS16136
+       tristate "Analog devices ADIS16136 and similar gyroscopes driver"
+       depends on SPI_MASTER
+       select IIO_ADIS_LIB
+       select IIO_ADIS_LIB_BUFFER if IIO_BUFFER
+       help
+         Say yes here to build support for the Analog Devices ADIS16133, ADIS16135,
+         ADIS16136 gyroscope devices.
+
 config HID_SENSOR_GYRO_3D
        depends on HID_SENSOR_HUB
        select IIO_BUFFER
index 8a895d9fcbceb2b38f86c2163feb4143d9068bf4..702a058907e35c96ef66aa42053b6be85eac1834 100644 (file)
@@ -2,4 +2,5 @@
 # Makefile for industrial I/O gyroscope sensor drivers
 #
 
+obj-$(CONFIG_ADIS16136) += adis16136.o
 obj-$(CONFIG_HID_SENSOR_GYRO_3D) += hid-sensor-gyro-3d.o
diff --git a/drivers/iio/gyro/adis16136.c b/drivers/iio/gyro/adis16136.c
new file mode 100644 (file)
index 0000000..05486df
--- /dev/null
@@ -0,0 +1,581 @@
+/*
+ * ADIS16133/ADIS16135/ADIS16136 gyroscope driver
+ *
+ * Copyright 2012 Analog Devices Inc.
+ *   Author: Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/interrupt.h>
+#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 <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/imu/adis.h>
+
+#include <linux/iio/iio.h>
+#include <linux/debugfs.h>
+
+#define ADIS16136_REG_FLASH_CNT                0x00
+#define ADIS16136_REG_TEMP_OUT         0x02
+#define ADIS16136_REG_GYRO_OUT2                0x04
+#define ADIS16136_REG_GYRO_OUT         0x06
+#define ADIS16136_REG_GYRO_OFF2                0x08
+#define ADIS16136_REG_GYRO_OFF         0x0A
+#define ADIS16136_REG_ALM_MAG1         0x10
+#define ADIS16136_REG_ALM_MAG2         0x12
+#define ADIS16136_REG_ALM_SAMPL1       0x14
+#define ADIS16136_REG_ALM_SAMPL2       0x16
+#define ADIS16136_REG_ALM_CTRL         0x18
+#define ADIS16136_REG_GPIO_CTRL                0x1A
+#define ADIS16136_REG_MSC_CTRL         0x1C
+#define ADIS16136_REG_SMPL_PRD         0x1E
+#define ADIS16136_REG_AVG_CNT          0x20
+#define ADIS16136_REG_DEC_RATE         0x22
+#define ADIS16136_REG_SLP_CTRL         0x24
+#define ADIS16136_REG_DIAG_STAT                0x26
+#define ADIS16136_REG_GLOB_CMD         0x28
+#define ADIS16136_REG_LOT1             0x32
+#define ADIS16136_REG_LOT2             0x34
+#define ADIS16136_REG_LOT3             0x36
+#define ADIS16136_REG_PROD_ID          0x38
+#define ADIS16136_REG_SERIAL_NUM       0x3A
+
+#define ADIS16136_DIAG_STAT_FLASH_UPDATE_FAIL  2
+#define ADIS16136_DIAG_STAT_SPI_FAIL           3
+#define ADIS16136_DIAG_STAT_SELF_TEST_FAIL     5
+#define ADIS16136_DIAG_STAT_FLASH_CHKSUM_FAIL  6
+
+#define ADIS16136_MSC_CTRL_MEMORY_TEST BIT(11)
+#define ADIS16136_MSC_CTRL_SELF_TEST BIT(10)
+
+struct adis16136_chip_info {
+       unsigned int precision;
+       unsigned int fullscale;
+};
+
+struct adis16136 {
+       const struct adis16136_chip_info *chip_info;
+
+       struct adis adis;
+};
+
+#ifdef CONFIG_DEBUG_FS
+
+static ssize_t adis16136_show_serial(struct file *file,
+               char __user *userbuf, size_t count, loff_t *ppos)
+{
+       struct adis16136 *adis16136 = file->private_data;
+       uint16_t lot1, lot2, lot3, serial;
+       char buf[20];
+       size_t len;
+       int ret;
+
+       ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_SERIAL_NUM,
+               &serial);
+       if (ret < 0)
+               return ret;
+
+       ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_LOT1, &lot1);
+       if (ret < 0)
+               return ret;
+
+       ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_LOT2, &lot2);
+       if (ret < 0)
+               return ret;
+
+       ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_LOT3, &lot3);
+       if (ret < 0)
+               return ret;
+
+       len = snprintf(buf, sizeof(buf), "%.4x%.4x%.4x-%.4x\n", lot1, lot2,
+               lot3, serial);
+
+       return simple_read_from_buffer(userbuf, count, ppos, buf, len);
+}
+
+static const struct file_operations adis16136_serial_fops = {
+       .open = simple_open,
+       .read = adis16136_show_serial,
+       .llseek = default_llseek,
+       .owner = THIS_MODULE,
+};
+
+static int adis16136_show_product_id(void *arg, u64 *val)
+{
+       struct adis16136 *adis16136 = arg;
+       u16 prod_id;
+       int ret;
+
+       ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_PROD_ID,
+               &prod_id);
+       if (ret < 0)
+               return ret;
+
+       *val = prod_id;
+
+       return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(adis16136_product_id_fops,
+       adis16136_show_product_id, NULL, "%llu\n");
+
+static int adis16136_show_flash_count(void *arg, u64 *val)
+{
+       struct adis16136 *adis16136 = arg;
+       uint16_t flash_count;
+       int ret;
+
+       ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_FLASH_CNT,
+               &flash_count);
+       if (ret < 0)
+               return ret;
+
+       *val = flash_count;
+
+       return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(adis16136_flash_count_fops,
+       adis16136_show_flash_count, NULL, "%lld\n");
+
+static int adis16136_debugfs_init(struct iio_dev *indio_dev)
+{
+       struct adis16136 *adis16136 = iio_priv(indio_dev);
+
+       debugfs_create_file("serial_number", 0400, indio_dev->debugfs_dentry,
+               adis16136, &adis16136_serial_fops);
+       debugfs_create_file("product_id", 0400, indio_dev->debugfs_dentry,
+               adis16136, &adis16136_product_id_fops);
+       debugfs_create_file("flash_count", 0400, indio_dev->debugfs_dentry,
+               adis16136, &adis16136_flash_count_fops);
+
+       return 0;
+}
+
+#else
+
+static int adis16136_debugfs_init(struct iio_dev *indio_dev)
+{
+       return 0;
+}
+
+#endif
+
+static int adis16136_set_freq(struct adis16136 *adis16136, unsigned int freq)
+{
+       unsigned int t;
+
+       t = 32768 / freq;
+       if (t < 0xf)
+               t = 0xf;
+       else if (t > 0xffff)
+               t = 0xffff;
+       else
+               t--;
+
+       return adis_write_reg_16(&adis16136->adis, ADIS16136_REG_SMPL_PRD, t);
+}
+
+static int adis16136_get_freq(struct adis16136 *adis16136, unsigned int *freq)
+{
+       uint16_t t;
+       int ret;
+
+       ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_SMPL_PRD, &t);
+       if (ret < 0)
+               return ret;
+
+       *freq = 32768 / (t + 1);
+
+       return 0;
+}
+
+static ssize_t adis16136_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 adis16136 *adis16136 = iio_priv(indio_dev);
+       long val;
+       int ret;
+
+       ret = kstrtol(buf, 10, &val);
+       if (ret)
+               return ret;
+
+       if (val == 0)
+               return -EINVAL;
+
+       ret = adis16136_set_freq(adis16136, val);
+
+       return ret ? ret : len;
+}
+
+static ssize_t adis16136_read_frequency(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+       struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+       struct adis16136 *adis16136 = iio_priv(indio_dev);
+       unsigned int freq;
+       int ret;
+
+       ret = adis16136_get_freq(adis16136, &freq);
+       if (ret < 0)
+               return ret;
+
+       return sprintf(buf, "%d\n", freq);
+}
+
+static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO,
+                                 adis16136_read_frequency,
+                                 adis16136_write_frequency);
+
+static const unsigned adis16136_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 adis16136_set_filter(struct iio_dev *indio_dev, int val)
+{
+       struct adis16136 *adis16136 = iio_priv(indio_dev);
+       unsigned int freq;
+       int i, ret;
+
+       ret = adis16136_get_freq(adis16136, &freq);
+       if (ret < 0)
+               return ret;
+
+       for (i = ARRAY_SIZE(adis16136_3db_divisors) - 1; i >= 1; i--) {
+               if (freq / adis16136_3db_divisors[i] >= val)
+                       break;
+       }
+
+       return adis_write_reg_16(&adis16136->adis, ADIS16136_REG_AVG_CNT, i);
+}
+
+static int adis16136_get_filter(struct iio_dev *indio_dev, int *val)
+{
+       struct adis16136 *adis16136 = iio_priv(indio_dev);
+       unsigned int freq;
+       uint16_t val16;
+       int ret;
+
+       mutex_lock(&indio_dev->mlock);
+
+       ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_AVG_CNT, &val16);
+       if (ret < 0)
+               goto err_unlock;
+
+       ret = adis16136_get_freq(adis16136, &freq);
+       if (ret < 0)
+               goto err_unlock;
+
+       *val = freq / adis16136_3db_divisors[val16 & 0x07];
+
+err_unlock:
+       mutex_unlock(&indio_dev->mlock);
+
+       return ret ? ret : IIO_VAL_INT;
+}
+
+static int adis16136_read_raw(struct iio_dev *indio_dev,
+       const struct iio_chan_spec *chan, int *val, int *val2, long info)
+{
+       struct adis16136 *adis16136 = iio_priv(indio_dev);
+       uint32_t val32;
+       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 = adis16136->chip_info->precision;
+                       *val2 = (adis16136->chip_info->fullscale << 16);
+                       return IIO_VAL_FRACTIONAL;
+               case IIO_TEMP:
+                       *val = 10;
+                       *val2 = 697000; /* 0.010697 degree Celsius */
+                       return IIO_VAL_INT_PLUS_MICRO;
+               default:
+                       return -EINVAL;
+               }
+       case IIO_CHAN_INFO_CALIBBIAS:
+               ret = adis_read_reg_32(&adis16136->adis,
+                       ADIS16136_REG_GYRO_OFF2, &val32);
+               if (ret < 0)
+                       return ret;
+
+               *val = sign_extend32(val32, 31);
+
+               return IIO_VAL_INT;
+       case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+               return adis16136_get_filter(indio_dev, val);
+       default:
+               return -EINVAL;
+       }
+}
+
+static int adis16136_write_raw(struct iio_dev *indio_dev,
+       const struct iio_chan_spec *chan, int val, int val2, long info)
+{
+       struct adis16136 *adis16136 = iio_priv(indio_dev);
+
+       switch (info) {
+       case IIO_CHAN_INFO_CALIBBIAS:
+               return adis_write_reg_32(&adis16136->adis,
+                       ADIS16136_REG_GYRO_OFF2, val);
+       case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
+               return adis16136_set_filter(indio_dev, val);
+       default:
+               break;
+       }
+
+       return -EINVAL;
+}
+
+enum {
+       ADIS16136_SCAN_GYRO,
+       ADIS16136_SCAN_TEMP,
+};
+
+static const struct iio_chan_spec adis16136_channels[] = {
+       {
+               .type = IIO_ANGL_VEL,
+               .modified = 1,
+               .channel2 = IIO_MOD_X,
+               .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_SEPARATE_BIT,
+               .address = ADIS16136_REG_GYRO_OUT2,
+               .scan_index = ADIS16136_SCAN_GYRO,
+               .scan_type = {
+                       .sign = 's',
+                       .realbits = 32,
+                       .storagebits = 32,
+                       .endianness = IIO_BE,
+               },
+       }, {
+               .type = IIO_TEMP,
+               .indexed = 1,
+               .channel = 0,
+               .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT |
+                       IIO_CHAN_INFO_SCALE_SEPARATE_BIT,
+               .address = ADIS16136_REG_TEMP_OUT,
+               .scan_index = ADIS16136_SCAN_TEMP,
+               .scan_type = {
+                       .sign = 's',
+                       .realbits = 16,
+                       .storagebits = 16,
+                       .endianness = IIO_BE,
+               },
+       },
+       IIO_CHAN_SOFT_TIMESTAMP(2),
+};
+
+static struct attribute *adis16136_attributes[] = {
+       &iio_dev_attr_sampling_frequency.dev_attr.attr,
+       NULL
+};
+
+static const struct attribute_group adis16136_attribute_group = {
+       .attrs = adis16136_attributes,
+};
+
+static const struct iio_info adis16136_info = {
+       .driver_module = THIS_MODULE,
+       .attrs = &adis16136_attribute_group,
+       .read_raw = &adis16136_read_raw,
+       .write_raw = &adis16136_write_raw,
+       .update_scan_mode = adis_update_scan_mode,
+       .debugfs_reg_access = adis_debugfs_reg_access,
+};
+
+static int adis16136_stop_device(struct iio_dev *indio_dev)
+{
+       struct adis16136 *adis16136 = iio_priv(indio_dev);
+       int ret;
+
+       ret = adis_write_reg_16(&adis16136->adis, ADIS16136_REG_SLP_CTRL, 0xff);
+       if (ret)
+               dev_err(&indio_dev->dev,
+                       "Could not power down device: %d\n", ret);
+
+       return ret;
+}
+
+static int adis16136_initial_setup(struct iio_dev *indio_dev)
+{
+       struct adis16136 *adis16136 = iio_priv(indio_dev);
+       unsigned int device_id;
+       uint16_t prod_id;
+       int ret;
+
+       ret = adis_initial_startup(&adis16136->adis);
+       if (ret)
+               return ret;
+
+       ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_PROD_ID,
+               &prod_id);
+       if (ret)
+               return 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);
+
+       return 0;
+}
+
+static const char * const adis16136_status_error_msgs[] = {
+       [ADIS16136_DIAG_STAT_FLASH_UPDATE_FAIL] = "Flash update failed",
+       [ADIS16136_DIAG_STAT_SPI_FAIL] = "SPI failure",
+       [ADIS16136_DIAG_STAT_SELF_TEST_FAIL] = "Self test error",
+       [ADIS16136_DIAG_STAT_FLASH_CHKSUM_FAIL] = "Flash checksum error",
+};
+
+static const struct adis_data adis16136_data = {
+       .diag_stat_reg = ADIS16136_REG_DIAG_STAT,
+       .glob_cmd_reg = ADIS16136_REG_GLOB_CMD,
+       .msc_ctrl_reg = ADIS16136_REG_MSC_CTRL,
+
+       .self_test_mask = ADIS16136_MSC_CTRL_SELF_TEST,
+       .startup_delay = 80,
+
+       .read_delay = 10,
+       .write_delay = 10,
+
+       .status_error_msgs = adis16136_status_error_msgs,
+       .status_error_mask = BIT(ADIS16136_DIAG_STAT_FLASH_UPDATE_FAIL) |
+               BIT(ADIS16136_DIAG_STAT_SPI_FAIL) |
+               BIT(ADIS16136_DIAG_STAT_SELF_TEST_FAIL) |
+               BIT(ADIS16136_DIAG_STAT_FLASH_CHKSUM_FAIL),
+};
+
+enum adis16136_id {
+       ID_ADIS16133,
+       ID_ADIS16135,
+       ID_ADIS16136,
+};
+
+static const struct adis16136_chip_info adis16136_chip_info[] = {
+       [ID_ADIS16133] = {
+               .precision = IIO_DEGREE_TO_RAD(1200),
+               .fullscale = 24000,
+       },
+       [ID_ADIS16135] = {
+               .precision = IIO_DEGREE_TO_RAD(300),
+               .fullscale = 24000,
+       },
+       [ID_ADIS16136] = {
+               .precision = IIO_DEGREE_TO_RAD(450),
+               .fullscale = 24623,
+       },
+};
+
+static int adis16136_probe(struct spi_device *spi)
+{
+       const struct spi_device_id *id = spi_get_device_id(spi);
+       struct adis16136 *adis16136;
+       struct iio_dev *indio_dev;
+       int ret;
+
+       indio_dev = iio_device_alloc(sizeof(*adis16136));
+       if (indio_dev == NULL)
+               return -ENOMEM;
+
+       spi_set_drvdata(spi, indio_dev);
+
+       adis16136 = iio_priv(indio_dev);
+
+       adis16136->chip_info = &adis16136_chip_info[id->driver_data];
+       indio_dev->dev.parent = &spi->dev;
+       indio_dev->name = spi_get_device_id(spi)->name;
+       indio_dev->channels = adis16136_channels;
+       indio_dev->num_channels = ARRAY_SIZE(adis16136_channels);
+       indio_dev->info = &adis16136_info;
+       indio_dev->modes = INDIO_DIRECT_MODE;
+
+       ret = adis_init(&adis16136->adis, indio_dev, spi, &adis16136_data);
+       if (ret)
+               goto error_free_dev;
+
+       ret = adis_setup_buffer_and_trigger(&adis16136->adis, indio_dev, NULL);
+       if (ret)
+               goto error_free_dev;
+
+       ret = adis16136_initial_setup(indio_dev);
+       if (ret)
+               goto error_cleanup_buffer;
+
+       ret = iio_device_register(indio_dev);
+       if (ret)
+               goto error_stop_device;
+
+       adis16136_debugfs_init(indio_dev);
+
+       return 0;
+
+error_stop_device:
+       adis16136_stop_device(indio_dev);
+error_cleanup_buffer:
+       adis_cleanup_buffer_and_trigger(&adis16136->adis, indio_dev);
+error_free_dev:
+       iio_device_free(indio_dev);
+       return ret;
+}
+
+static int adis16136_remove(struct spi_device *spi)
+{
+       struct iio_dev *indio_dev = spi_get_drvdata(spi);
+       struct adis16136 *adis16136 = iio_priv(indio_dev);
+
+       iio_device_unregister(indio_dev);
+       adis16136_stop_device(indio_dev);
+
+       adis_cleanup_buffer_and_trigger(&adis16136->adis, indio_dev);
+
+       iio_device_free(indio_dev);
+
+       return 0;
+}
+
+static const struct spi_device_id adis16136_ids[] = {
+       { "adis16133", ID_ADIS16133 },
+       { "adis16135", ID_ADIS16135 },
+       { "adis16136", ID_ADIS16136 },
+       { }
+};
+MODULE_DEVICE_TABLE(spi, adis16136_ids);
+
+static struct spi_driver adis16136_driver = {
+       .driver = {
+               .name = "adis16136",
+               .owner = THIS_MODULE,
+       },
+       .id_table = adis16136_ids,
+       .probe = adis16136_probe,
+       .remove = adis16136_remove,
+};
+module_spi_driver(adis16136_driver);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("Analog Devices ADIS16133/ADIS16135/ADIS16136 gyroscope driver");
+MODULE_LICENSE("GPL v2");