On 11/20/2012 01:36 PM, Lars-Peter Clausen wrote: > 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@xxxxxxxxxx> A nice driver. The only thing I could pick up on was the 'interesting' comments alongside the 3db divisors. Still I just wanted to comment on something really ;) added to togreg branch of iio.git > --- > drivers/iio/gyro/Kconfig | 9 + > drivers/iio/gyro/Makefile | 1 + > drivers/iio/gyro/adis16136.c | 581 +++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 591 insertions(+) > create mode 100644 drivers/iio/gyro/adis16136.c > > diff --git a/drivers/iio/gyro/Kconfig b/drivers/iio/gyro/Kconfig > index 21e27e2..48ed148 100644 > --- a/drivers/iio/gyro/Kconfig > +++ b/drivers/iio/gyro/Kconfig > @@ -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 > diff --git a/drivers/iio/gyro/Makefile b/drivers/iio/gyro/Makefile > index 8a895d9..702a058 100644 > --- a/drivers/iio/gyro/Makefile > +++ b/drivers/iio/gyro/Makefile > @@ -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 > index 0000000..277aa74 > --- /dev/null > +++ b/drivers/iio/gyro/adis16136.c > @@ -0,0 +1,581 @@ > +/* > + * ADIS16133/ADIS16135/ADIS16136 gyroscope driver > + * > + * Copyright 2012 Analog Devices Inc. > + * Author: Lars-Peter Clausen <lars@xxxxxxxxxx> > + * > + * 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); > + I love the comments. Useful sounding but I have no idea what they actually mean! > +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 __devinit 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 __devexit 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 = __devexit_p(adis16136_remove), > +}; > +module_spi_driver(adis16136_driver); > + > +MODULE_AUTHOR("Lars-Peter Clausen <lars@xxxxxxxxxx>"); > +MODULE_DESCRIPTION("Analog Devices ADIS16133/ADIS16135/ADIS16136 gyroscope driver"); > +MODULE_LICENSE("GPL v2"); > -- 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