diff options
Diffstat (limited to 'drivers/iio/imu')
38 files changed, 5873 insertions, 116 deletions
diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig index 60bb1029e759..f02883b08480 100644 --- a/drivers/iio/imu/Kconfig +++ b/drivers/iio/imu/Kconfig @@ -29,6 +29,19 @@ config ADIS16460 To compile this driver as a module, choose M here: the module will be called adis16460. +config ADIS16475 + tristate "Analog Devices ADIS16475 and similar IMU 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 ADIS16470, ADIS16475, + ADIS16477, ADIS16465, ADIS16467, ADIS16500, ADIS16505, ADIS16507 inertial + sensors. + + To compile this driver as a module, choose M here: the module will be + called adis16475. + config ADIS16480 tristate "Analog Devices ADIS16480 and similar IMU driver" depends on SPI @@ -78,6 +91,7 @@ config KMX61 To compile this driver as module, choose M here: the module will be called kmx61. +source "drivers/iio/imu/inv_icm42600/Kconfig" source "drivers/iio/imu/inv_mpu6050/Kconfig" source "drivers/iio/imu/st_lsm6dsx/Kconfig" diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile index 5237fd4bc384..13e9ff442b11 100644 --- a/drivers/iio/imu/Makefile +++ b/drivers/iio/imu/Makefile @@ -6,6 +6,7 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_ADIS16400) += adis16400.o obj-$(CONFIG_ADIS16460) += adis16460.o +obj-$(CONFIG_ADIS16475) += adis16475.o obj-$(CONFIG_ADIS16480) += adis16480.o adis_lib-y += adis.o @@ -19,6 +20,7 @@ obj-$(CONFIG_FXOS8700) += fxos8700_core.o obj-$(CONFIG_FXOS8700_I2C) += fxos8700_i2c.o obj-$(CONFIG_FXOS8700_SPI) += fxos8700_spi.o +obj-y += inv_icm42600/ obj-y += inv_mpu6050/ obj-$(CONFIG_KMX61) += kmx61.o diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c index a8afd01de4f3..319b64b2fd88 100644 --- a/drivers/iio/imu/adis.c +++ b/drivers/iio/imu/adis.c @@ -97,11 +97,11 @@ int __adis_write_reg(struct adis *adis, unsigned int reg, adis->tx[9] = (value >> 24) & 0xff; adis->tx[6] = ADIS_WRITE_REG(reg + 2); adis->tx[7] = (value >> 16) & 0xff; - /* fall through */ + fallthrough; case 2: adis->tx[4] = ADIS_WRITE_REG(reg + 1); adis->tx[5] = (value >> 8) & 0xff; - /* fall through */ + fallthrough; case 1: adis->tx[2] = ADIS_WRITE_REG(reg); adis->tx[3] = value & 0xff; @@ -191,7 +191,7 @@ int __adis_read_reg(struct adis *adis, unsigned int reg, adis->tx[2] = ADIS_READ_REG(reg + 2); adis->tx[3] = 0; spi_message_add_tail(&xfers[1], &msg); - /* fall through */ + fallthrough; case 2: adis->tx[4] = ADIS_READ_REG(reg); adis->tx[5] = 0; @@ -223,6 +223,31 @@ int __adis_read_reg(struct adis *adis, unsigned int reg, return ret; } EXPORT_SYMBOL_GPL(__adis_read_reg); +/** + * __adis_update_bits_base() - ADIS Update bits function - Unlocked version + * @adis: The adis device + * @reg: The address of the lower of the two registers + * @mask: Bitmask to change + * @val: Value to be written + * @size: Size of the register to update + * + * Updates the desired bits of @reg in accordance with @mask and @val. + */ +int __adis_update_bits_base(struct adis *adis, unsigned int reg, const u32 mask, + const u32 val, u8 size) +{ + int ret; + u32 __val; + + ret = __adis_read_reg(adis, reg, &__val, size); + if (ret) + return ret; + + __val = (__val & ~mask) | (val & mask); + + return __adis_write_reg(adis, reg, __val, size); +} +EXPORT_SYMBOL_GPL(__adis_update_bits_base); #ifdef CONFIG_DEBUG_FS @@ -419,7 +444,7 @@ int __adis_initial_startup(struct adis *adis) if (prod_id != adis->data->prod_id) dev_warn(&adis->spi->dev, - "Device ID(%u) and product ID(%u) do not match.", + "Device ID(%u) and product ID(%u) do not match.\n", adis->data->prod_id, prod_id); return 0; diff --git a/drivers/iio/imu/adis16400.c b/drivers/iio/imu/adis16400.c index 05e70c1c4835..1ebe3e50d3e6 100644 --- a/drivers/iio/imu/adis16400.c +++ b/drivers/iio/imu/adis16400.c @@ -258,7 +258,7 @@ static int adis16400_show_product_id(void *arg, u64 *val) return 0; } -DEFINE_SIMPLE_ATTRIBUTE(adis16400_product_id_fops, +DEFINE_DEBUGFS_ATTRIBUTE(adis16400_product_id_fops, adis16400_show_product_id, NULL, "%lld\n"); static int adis16400_show_flash_count(void *arg, u64 *val) @@ -275,23 +275,22 @@ static int adis16400_show_flash_count(void *arg, u64 *val) return 0; } -DEFINE_SIMPLE_ATTRIBUTE(adis16400_flash_count_fops, +DEFINE_DEBUGFS_ATTRIBUTE(adis16400_flash_count_fops, adis16400_show_flash_count, NULL, "%lld\n"); static int adis16400_debugfs_init(struct iio_dev *indio_dev) { struct adis16400_state *st = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); if (st->variant->flags & ADIS16400_HAS_SERIAL_NUMBER) - debugfs_create_file("serial_number", 0400, - indio_dev->debugfs_dentry, st, - &adis16400_serial_number_fops); + debugfs_create_file_unsafe("serial_number", 0400, + d, st, &adis16400_serial_number_fops); if (st->variant->flags & ADIS16400_HAS_PROD_ID) - debugfs_create_file("product_id", 0400, - indio_dev->debugfs_dentry, st, - &adis16400_product_id_fops); - debugfs_create_file("flash_count", 0400, indio_dev->debugfs_dentry, - st, &adis16400_flash_count_fops); + debugfs_create_file_unsafe("product_id", 0400, + d, st, &adis16400_product_id_fops); + debugfs_create_file_unsafe("flash_count", 0400, + d, st, &adis16400_flash_count_fops); return 0; } @@ -1182,7 +1181,6 @@ static int adis16400_probe(struct spi_device *spi) /* 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; @@ -1194,7 +1192,7 @@ static int adis16400_probe(struct spi_device *spi) indio_dev->available_scan_masks = st->avail_scan_mask; st->adis.burst = &adis16400_burst; if (st->variant->flags & ADIS16400_BURST_DIAG_STAT) - st->adis.burst->extra_len = sizeof(u16); + st->adis.burst_extra_len = sizeof(u16); } adis16400_data = &st->variant->adis_data; diff --git a/drivers/iio/imu/adis16460.c b/drivers/iio/imu/adis16460.c index 0027683d0256..b26a5f1bc51a 100644 --- a/drivers/iio/imu/adis16460.c +++ b/drivers/iio/imu/adis16460.c @@ -87,8 +87,8 @@ static int adis16460_show_serial_number(void *arg, u64 *val) return 0; } -DEFINE_SIMPLE_ATTRIBUTE(adis16460_serial_number_fops, - adis16460_show_serial_number, NULL, "0x%.4llx\n"); +DEFINE_DEBUGFS_ATTRIBUTE(adis16460_serial_number_fops, + adis16460_show_serial_number, NULL, "0x%.4llx\n"); static int adis16460_show_product_id(void *arg, u64 *val) { @@ -105,8 +105,8 @@ static int adis16460_show_product_id(void *arg, u64 *val) return 0; } -DEFINE_SIMPLE_ATTRIBUTE(adis16460_product_id_fops, - adis16460_show_product_id, NULL, "%llu\n"); +DEFINE_DEBUGFS_ATTRIBUTE(adis16460_product_id_fops, + adis16460_show_product_id, NULL, "%llu\n"); static int adis16460_show_flash_count(void *arg, u64 *val) { @@ -123,19 +123,20 @@ static int adis16460_show_flash_count(void *arg, u64 *val) return 0; } -DEFINE_SIMPLE_ATTRIBUTE(adis16460_flash_count_fops, - adis16460_show_flash_count, NULL, "%lld\n"); +DEFINE_DEBUGFS_ATTRIBUTE(adis16460_flash_count_fops, + adis16460_show_flash_count, NULL, "%lld\n"); static int adis16460_debugfs_init(struct iio_dev *indio_dev) { struct adis16460 *adis16460 = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); - debugfs_create_file("serial_number", 0400, indio_dev->debugfs_dentry, - adis16460, &adis16460_serial_number_fops); - debugfs_create_file("product_id", 0400, indio_dev->debugfs_dentry, - adis16460, &adis16460_product_id_fops); - debugfs_create_file("flash_count", 0400, indio_dev->debugfs_dentry, - adis16460, &adis16460_flash_count_fops); + debugfs_create_file_unsafe("serial_number", 0400, + d, adis16460, &adis16460_serial_number_fops); + debugfs_create_file_unsafe("product_id", 0400, + d, adis16460, &adis16460_product_id_fops); + debugfs_create_file_unsafe("flash_count", 0400, + d, adis16460, &adis16460_flash_count_fops); return 0; } @@ -392,7 +393,6 @@ static int adis16460_probe(struct spi_device *spi) st = iio_priv(indio_dev); st->chip_info = &adis16460_chip_info; - indio_dev->dev.parent = &spi->dev; indio_dev->name = spi_get_device_id(spi)->name; indio_dev->channels = st->chip_info->channels; indio_dev->num_channels = st->chip_info->num_channels; diff --git a/drivers/iio/imu/adis16475.c b/drivers/iio/imu/adis16475.c new file mode 100644 index 000000000000..35d10ccb66c2 --- /dev/null +++ b/drivers/iio/imu/adis16475.c @@ -0,0 +1,1337 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADIS16475 IMU driver + * + * Copyright 2019 Analog Devices Inc. + */ +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/imu/adis.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/property.h> +#include <linux/spi/spi.h> + +#define ADIS16475_REG_DIAG_STAT 0x02 +#define ADIS16475_REG_X_GYRO_L 0x04 +#define ADIS16475_REG_Y_GYRO_L 0x08 +#define ADIS16475_REG_Z_GYRO_L 0x0C +#define ADIS16475_REG_X_ACCEL_L 0x10 +#define ADIS16475_REG_Y_ACCEL_L 0x14 +#define ADIS16475_REG_Z_ACCEL_L 0x18 +#define ADIS16475_REG_TEMP_OUT 0x1c +#define ADIS16475_REG_X_GYRO_BIAS_L 0x40 +#define ADIS16475_REG_Y_GYRO_BIAS_L 0x44 +#define ADIS16475_REG_Z_GYRO_BIAS_L 0x48 +#define ADIS16475_REG_X_ACCEL_BIAS_L 0x4c +#define ADIS16475_REG_Y_ACCEL_BIAS_L 0x50 +#define ADIS16475_REG_Z_ACCEL_BIAS_L 0x54 +#define ADIS16475_REG_FILT_CTRL 0x5c +#define ADIS16475_FILT_CTRL_MASK GENMASK(2, 0) +#define ADIS16475_FILT_CTRL(x) FIELD_PREP(ADIS16475_FILT_CTRL_MASK, x) +#define ADIS16475_REG_MSG_CTRL 0x60 +#define ADIS16475_MSG_CTRL_DR_POL_MASK BIT(0) +#define ADIS16475_MSG_CTRL_DR_POL(x) \ + FIELD_PREP(ADIS16475_MSG_CTRL_DR_POL_MASK, x) +#define ADIS16475_SYNC_MODE_MASK GENMASK(4, 2) +#define ADIS16475_SYNC_MODE(x) FIELD_PREP(ADIS16475_SYNC_MODE_MASK, x) +#define ADIS16475_REG_UP_SCALE 0x62 +#define ADIS16475_REG_DEC_RATE 0x64 +#define ADIS16475_REG_GLOB_CMD 0x68 +#define ADIS16475_REG_FIRM_REV 0x6c +#define ADIS16475_REG_FIRM_DM 0x6e +#define ADIS16475_REG_FIRM_Y 0x70 +#define ADIS16475_REG_PROD_ID 0x72 +#define ADIS16475_REG_SERIAL_NUM 0x74 +#define ADIS16475_REG_FLASH_CNT 0x7c +#define ADIS16500_BURST32_MASK BIT(9) +#define ADIS16500_BURST32(x) FIELD_PREP(ADIS16500_BURST32_MASK, x) +/* number of data elements in burst mode */ +#define ADIS16475_BURST32_MAX_DATA 32 +#define ADIS16475_BURST_MAX_DATA 20 +#define ADIS16475_MAX_SCAN_DATA 20 +/* spi max speed in brust mode */ +#define ADIS16475_BURST_MAX_SPEED 1000000 +#define ADIS16475_LSB_DEC_MASK BIT(0) +#define ADIS16475_LSB_FIR_MASK BIT(1) + +enum { + ADIS16475_SYNC_DIRECT = 1, + ADIS16475_SYNC_SCALED, + ADIS16475_SYNC_OUTPUT, + ADIS16475_SYNC_PULSE = 5, +}; + +struct adis16475_sync { + u16 sync_mode; + u16 min_rate; + u16 max_rate; +}; + +struct adis16475_chip_info { + const struct iio_chan_spec *channels; + const struct adis16475_sync *sync; + const struct adis_data adis_data; + const char *name; + u32 num_channels; + u32 gyro_max_val; + u32 gyro_max_scale; + u32 accel_max_val; + u32 accel_max_scale; + u32 temp_scale; + u32 int_clk; + u16 max_dec; + u8 num_sync; + bool has_burst32; +}; + +struct adis16475 { + const struct adis16475_chip_info *info; + struct adis adis; + u32 clk_freq; + bool burst32; + unsigned long lsb_flag; + /* Alignment needed for the timestamp */ + __be16 data[ADIS16475_MAX_SCAN_DATA] __aligned(8); +}; + +enum { + ADIS16475_SCAN_GYRO_X, + ADIS16475_SCAN_GYRO_Y, + ADIS16475_SCAN_GYRO_Z, + ADIS16475_SCAN_ACCEL_X, + ADIS16475_SCAN_ACCEL_Y, + ADIS16475_SCAN_ACCEL_Z, + ADIS16475_SCAN_TEMP, + ADIS16475_SCAN_DIAG_S_FLAGS, + ADIS16475_SCAN_CRC_FAILURE, +}; + +#ifdef CONFIG_DEBUG_FS +static ssize_t adis16475_show_firmware_revision(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct adis16475 *st = file->private_data; + char buf[7]; + size_t len; + u16 rev; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_FIRM_REV, &rev); + if (ret) + return ret; + + len = scnprintf(buf, sizeof(buf), "%x.%x\n", rev >> 8, rev & 0xff); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16475_firmware_revision_fops = { + .open = simple_open, + .read = adis16475_show_firmware_revision, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static ssize_t adis16475_show_firmware_date(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct adis16475 *st = file->private_data; + u16 md, year; + char buf[12]; + size_t len; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_FIRM_Y, &year); + if (ret) + return ret; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_FIRM_DM, &md); + if (ret) + return ret; + + len = snprintf(buf, sizeof(buf), "%.2x-%.2x-%.4x\n", md >> 8, md & 0xff, + year); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16475_firmware_date_fops = { + .open = simple_open, + .read = adis16475_show_firmware_date, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static int adis16475_show_serial_number(void *arg, u64 *val) +{ + struct adis16475 *st = arg; + u16 serial; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_SERIAL_NUM, &serial); + if (ret) + return ret; + + *val = serial; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16475_serial_number_fops, + adis16475_show_serial_number, NULL, "0x%.4llx\n"); + +static int adis16475_show_product_id(void *arg, u64 *val) +{ + struct adis16475 *st = arg; + u16 prod_id; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_PROD_ID, &prod_id); + if (ret) + return ret; + + *val = prod_id; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16475_product_id_fops, + adis16475_show_product_id, NULL, "%llu\n"); + +static int adis16475_show_flash_count(void *arg, u64 *val) +{ + struct adis16475 *st = arg; + u32 flash_count; + int ret; + + ret = adis_read_reg_32(&st->adis, ADIS16475_REG_FLASH_CNT, + &flash_count); + if (ret) + return ret; + + *val = flash_count; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16475_flash_count_fops, + adis16475_show_flash_count, NULL, "%lld\n"); + +static void adis16475_debugfs_init(struct iio_dev *indio_dev) +{ + struct adis16475 *st = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + + debugfs_create_file_unsafe("serial_number", 0400, + d, st, &adis16475_serial_number_fops); + debugfs_create_file_unsafe("product_id", 0400, + d, st, &adis16475_product_id_fops); + debugfs_create_file_unsafe("flash_count", 0400, + d, st, &adis16475_flash_count_fops); + debugfs_create_file("firmware_revision", 0400, + d, st, &adis16475_firmware_revision_fops); + debugfs_create_file("firmware_date", 0400, d, + st, &adis16475_firmware_date_fops); +} +#else +static void adis16475_debugfs_init(struct iio_dev *indio_dev) +{ +} +#endif + +static int adis16475_get_freq(struct adis16475 *st, u32 *freq) +{ + int ret; + u16 dec; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_DEC_RATE, &dec); + if (ret) + return -EINVAL; + + *freq = DIV_ROUND_CLOSEST(st->clk_freq, dec + 1); + + return 0; +} + +static int adis16475_set_freq(struct adis16475 *st, const u32 freq) +{ + u16 dec; + int ret; + + if (!freq) + return -EINVAL; + + dec = DIV_ROUND_CLOSEST(st->clk_freq, freq); + + if (dec) + dec--; + + if (dec > st->info->max_dec) + dec = st->info->max_dec; + + ret = adis_write_reg_16(&st->adis, ADIS16475_REG_DEC_RATE, dec); + if (ret) + return ret; + + /* + * If decimation is used, then gyro and accel data will have meaningful + * bits on the LSB registers. This info is used on the trigger handler. + */ + assign_bit(ADIS16475_LSB_DEC_MASK, &st->lsb_flag, dec); + + return 0; +} + +/* The values are approximated. */ +static const u32 adis16475_3db_freqs[] = { + [0] = 720, /* Filter disabled, full BW (~720Hz) */ + [1] = 360, + [2] = 164, + [3] = 80, + [4] = 40, + [5] = 20, + [6] = 10, +}; + +static int adis16475_get_filter(struct adis16475 *st, u32 *filter) +{ + u16 filter_sz; + int ret; + const int mask = ADIS16475_FILT_CTRL_MASK; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_FILT_CTRL, &filter_sz); + if (ret) + return ret; + + *filter = adis16475_3db_freqs[filter_sz & mask]; + + return 0; +} + +static int adis16475_set_filter(struct adis16475 *st, const u32 filter) +{ + int i = ARRAY_SIZE(adis16475_3db_freqs); + int ret; + + while (--i) { + if (adis16475_3db_freqs[i] >= filter) + break; + } + + ret = adis_write_reg_16(&st->adis, ADIS16475_REG_FILT_CTRL, + ADIS16475_FILT_CTRL(i)); + if (ret) + return ret; + + /* + * If FIR is used, then gyro and accel data will have meaningful + * bits on the LSB registers. This info is used on the trigger handler. + */ + assign_bit(ADIS16475_LSB_FIR_MASK, &st->lsb_flag, i); + + return 0; +} + +static const u32 adis16475_calib_regs[] = { + [ADIS16475_SCAN_GYRO_X] = ADIS16475_REG_X_GYRO_BIAS_L, + [ADIS16475_SCAN_GYRO_Y] = ADIS16475_REG_Y_GYRO_BIAS_L, + [ADIS16475_SCAN_GYRO_Z] = ADIS16475_REG_Z_GYRO_BIAS_L, + [ADIS16475_SCAN_ACCEL_X] = ADIS16475_REG_X_ACCEL_BIAS_L, + [ADIS16475_SCAN_ACCEL_Y] = ADIS16475_REG_Y_ACCEL_BIAS_L, + [ADIS16475_SCAN_ACCEL_Z] = ADIS16475_REG_Z_ACCEL_BIAS_L, +}; + +static int adis16475_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct adis16475 *st = iio_priv(indio_dev); + int ret; + u32 tmp; + + 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 = st->info->gyro_max_val; + *val2 = st->info->gyro_max_scale; + return IIO_VAL_FRACTIONAL; + case IIO_ACCEL: + *val = st->info->accel_max_val; + *val2 = st->info->accel_max_scale; + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + *val = st->info->temp_scale; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + ret = adis_read_reg_32(&st->adis, + adis16475_calib_regs[chan->scan_index], + val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + ret = adis16475_get_filter(st, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = adis16475_get_freq(st, &tmp); + if (ret) + return ret; + + *val = tmp / 1000; + *val2 = (tmp % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int adis16475_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long info) +{ + struct adis16475 *st = iio_priv(indio_dev); + u32 tmp; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + tmp = val * 1000 + val2 / 1000; + return adis16475_set_freq(st, tmp); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return adis16475_set_filter(st, val); + case IIO_CHAN_INFO_CALIBBIAS: + return adis_write_reg_32(&st->adis, + adis16475_calib_regs[chan->scan_index], + val); + default: + return -EINVAL; + } +} + +#define ADIS16475_MOD_CHAN(_type, _mod, _address, _si, _r_bits, _s_bits) \ + { \ + .type = (_type), \ + .modified = 1, \ + .channel2 = (_mod), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .address = (_address), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 's', \ + .realbits = (_r_bits), \ + .storagebits = (_s_bits), \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16475_GYRO_CHANNEL(_mod) \ + ADIS16475_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_ ## _mod, \ + ADIS16475_REG_ ## _mod ## _GYRO_L, \ + ADIS16475_SCAN_GYRO_ ## _mod, 32, 32) + +#define ADIS16475_ACCEL_CHANNEL(_mod) \ + ADIS16475_MOD_CHAN(IIO_ACCEL, IIO_MOD_ ## _mod, \ + ADIS16475_REG_ ## _mod ## _ACCEL_L, \ + ADIS16475_SCAN_ACCEL_ ## _mod, 32, 32) + +#define ADIS16475_TEMP_CHANNEL() { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .address = ADIS16475_REG_TEMP_OUT, \ + .scan_index = ADIS16475_SCAN_TEMP, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec adis16475_channels[] = { + ADIS16475_GYRO_CHANNEL(X), + ADIS16475_GYRO_CHANNEL(Y), + ADIS16475_GYRO_CHANNEL(Z), + ADIS16475_ACCEL_CHANNEL(X), + ADIS16475_ACCEL_CHANNEL(Y), + ADIS16475_ACCEL_CHANNEL(Z), + ADIS16475_TEMP_CHANNEL(), + IIO_CHAN_SOFT_TIMESTAMP(7) +}; + +enum adis16475_variant { + ADIS16470, + ADIS16475_1, + ADIS16475_2, + ADIS16475_3, + ADIS16477_1, + ADIS16477_2, + ADIS16477_3, + ADIS16465_1, + ADIS16465_2, + ADIS16465_3, + ADIS16467_1, + ADIS16467_2, + ADIS16467_3, + ADIS16500, + ADIS16505_1, + ADIS16505_2, + ADIS16505_3, + ADIS16507_1, + ADIS16507_2, + ADIS16507_3, +}; + +enum { + ADIS16475_DIAG_STAT_DATA_PATH = 1, + ADIS16475_DIAG_STAT_FLASH_MEM, + ADIS16475_DIAG_STAT_SPI, + ADIS16475_DIAG_STAT_STANDBY, + ADIS16475_DIAG_STAT_SENSOR, + ADIS16475_DIAG_STAT_MEMORY, + ADIS16475_DIAG_STAT_CLK, +}; + +static const char * const adis16475_status_error_msgs[] = { + [ADIS16475_DIAG_STAT_DATA_PATH] = "Data Path Overrun", + [ADIS16475_DIAG_STAT_FLASH_MEM] = "Flash memory update failure", + [ADIS16475_DIAG_STAT_SPI] = "SPI communication error", + [ADIS16475_DIAG_STAT_STANDBY] = "Standby mode", + [ADIS16475_DIAG_STAT_SENSOR] = "Sensor failure", + [ADIS16475_DIAG_STAT_MEMORY] = "Memory failure", + [ADIS16475_DIAG_STAT_CLK] = "Clock error", +}; + +static int adis16475_enable_irq(struct adis *adis, bool enable) +{ + /* + * There is no way to gate the data-ready signal internally inside the + * ADIS16475. We can only control it's polarity... + */ + if (enable) + enable_irq(adis->spi->irq); + else + disable_irq(adis->spi->irq); + + return 0; +} + +#define ADIS16475_DATA(_prod_id, _timeouts) \ +{ \ + .msc_ctrl_reg = ADIS16475_REG_MSG_CTRL, \ + .glob_cmd_reg = ADIS16475_REG_GLOB_CMD, \ + .diag_stat_reg = ADIS16475_REG_DIAG_STAT, \ + .prod_id_reg = ADIS16475_REG_PROD_ID, \ + .prod_id = (_prod_id), \ + .self_test_mask = BIT(2), \ + .self_test_reg = ADIS16475_REG_GLOB_CMD, \ + .cs_change_delay = 16, \ + .read_delay = 5, \ + .write_delay = 5, \ + .status_error_msgs = adis16475_status_error_msgs, \ + .status_error_mask = BIT(ADIS16475_DIAG_STAT_DATA_PATH) | \ + BIT(ADIS16475_DIAG_STAT_FLASH_MEM) | \ + BIT(ADIS16475_DIAG_STAT_SPI) | \ + BIT(ADIS16475_DIAG_STAT_STANDBY) | \ + BIT(ADIS16475_DIAG_STAT_SENSOR) | \ + BIT(ADIS16475_DIAG_STAT_MEMORY) | \ + BIT(ADIS16475_DIAG_STAT_CLK), \ + .enable_irq = adis16475_enable_irq, \ + .timeouts = (_timeouts), \ +} + +static const struct adis16475_sync adis16475_sync_mode[] = { + { ADIS16475_SYNC_OUTPUT }, + { ADIS16475_SYNC_DIRECT, 1900, 2100 }, + { ADIS16475_SYNC_SCALED, 1, 128 }, + { ADIS16475_SYNC_PULSE, 1000, 2100 }, +}; + +static const struct adis_timeout adis16475_timeouts = { + .reset_ms = 200, + .sw_reset_ms = 200, + .self_test_ms = 20, +}; + +static const struct adis_timeout adis1650x_timeouts = { + .reset_ms = 260, + .sw_reset_ms = 260, + .self_test_ms = 30, +}; + +static const struct adis16475_chip_info adis16475_chip_info[] = { + [ADIS16470] = { + .name = "adis16470", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16470, &adis16475_timeouts), + }, + [ADIS16475_1] = { + .name = "adis16475-1", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16475, &adis16475_timeouts), + }, + [ADIS16475_2] = { + .name = "adis16475-2", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16475, &adis16475_timeouts), + }, + [ADIS16475_3] = { + .name = "adis16475-3", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16475, &adis16475_timeouts), + }, + [ADIS16477_1] = { + .name = "adis16477-1", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16477, &adis16475_timeouts), + }, + [ADIS16477_2] = { + .name = "adis16477-2", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16477, &adis16475_timeouts), + }, + [ADIS16477_3] = { + .name = "adis16477-3", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16477, &adis16475_timeouts), + }, + [ADIS16465_1] = { + .name = "adis16465-1", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16465, &adis16475_timeouts), + }, + [ADIS16465_2] = { + .name = "adis16465-2", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16465, &adis16475_timeouts), + }, + [ADIS16465_3] = { + .name = "adis16465-3", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16465, &adis16475_timeouts), + }, + [ADIS16467_1] = { + .name = "adis16467-1", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16467, &adis16475_timeouts), + }, + [ADIS16467_2] = { + .name = "adis16467-2", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16467, &adis16475_timeouts), + }, + [ADIS16467_3] = { + .name = "adis16467-3", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16467, &adis16475_timeouts), + }, + [ADIS16500] = { + .name = "adis16500", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 392, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .has_burst32 = true, + .adis_data = ADIS16475_DATA(16500, &adis1650x_timeouts), + }, + [ADIS16505_1] = { + .name = "adis16505-1", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 78, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .has_burst32 = true, + .adis_data = ADIS16475_DATA(16505, &adis1650x_timeouts), + }, + [ADIS16505_2] = { + .name = "adis16505-2", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 78, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .has_burst32 = true, + .adis_data = ADIS16475_DATA(16505, &adis1650x_timeouts), + }, + [ADIS16505_3] = { + .name = "adis16505-3", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 78, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .has_burst32 = true, + .adis_data = ADIS16475_DATA(16505, &adis1650x_timeouts), + }, + [ADIS16507_1] = { + .name = "adis16507-1", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 392, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .has_burst32 = true, + .adis_data = ADIS16475_DATA(16507, &adis1650x_timeouts), + }, + [ADIS16507_2] = { + .name = "adis16507-2", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 392, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .has_burst32 = true, + .adis_data = ADIS16475_DATA(16507, &adis1650x_timeouts), + }, + [ADIS16507_3] = { + .name = "adis16507-3", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 392, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .has_burst32 = true, + .adis_data = ADIS16475_DATA(16507, &adis1650x_timeouts), + }, +}; + +static const struct iio_info adis16475_info = { + .read_raw = &adis16475_read_raw, + .write_raw = &adis16475_write_raw, + .update_scan_mode = adis_update_scan_mode, + .debugfs_reg_access = adis_debugfs_reg_access, +}; + +static struct adis_burst adis16475_burst = { + .en = true, + .reg_cmd = ADIS16475_REG_GLOB_CMD, + /* + * adis_update_scan_mode_burst() sets the burst length in respect with + * the number of channels and allocates 16 bits for each. However, + * adis1647x devices also need space for DIAG_STAT, DATA_CNTR or + * TIME_STAMP (depending on the clock mode but for us these bytes are + * don't care...) and CRC. + */ + .extra_len = 3 * sizeof(u16), + .burst_max_len = ADIS16475_BURST32_MAX_DATA, +}; + +static bool adis16475_validate_crc(const u8 *buffer, u16 crc, + const bool burst32) +{ + int i; + /* extra 6 elements for low gyro and accel */ + const u16 sz = burst32 ? ADIS16475_BURST32_MAX_DATA : + ADIS16475_BURST_MAX_DATA; + + for (i = 0; i < sz - 2; i++) + crc -= buffer[i]; + + return crc == 0; +} + +static void adis16475_burst32_check(struct adis16475 *st) +{ + int ret; + struct adis *adis = &st->adis; + + if (!st->info->has_burst32) + return; + + if (st->lsb_flag && !st->burst32) { + const u16 en = ADIS16500_BURST32(1); + + ret = __adis_update_bits(&st->adis, ADIS16475_REG_MSG_CTRL, + ADIS16500_BURST32_MASK, en); + if (ret) + return; + + st->burst32 = true; + + /* + * In 32-bit mode we need extra 2 bytes for all gyro + * and accel channels. + */ + adis->burst_extra_len = 6 * sizeof(u16); + adis->xfer[1].len += 6 * sizeof(u16); + dev_dbg(&adis->spi->dev, "Enable burst32 mode, xfer:%d", + adis->xfer[1].len); + + } else if (!st->lsb_flag && st->burst32) { + const u16 en = ADIS16500_BURST32(0); + + ret = __adis_update_bits(&st->adis, ADIS16475_REG_MSG_CTRL, + ADIS16500_BURST32_MASK, en); + if (ret) + return; + + st->burst32 = false; + + /* Remove the extra bits */ + adis->burst_extra_len = 0; + adis->xfer[1].len -= 6 * sizeof(u16); + dev_dbg(&adis->spi->dev, "Disable burst32 mode, xfer:%d\n", + adis->xfer[1].len); + } +} + +static irqreturn_t adis16475_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16475 *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + int ret, bit, i = 0; + __be16 *buffer; + u16 crc; + bool valid; + /* offset until the first element after gyro and accel */ + const u8 offset = st->burst32 ? 13 : 7; + const u32 cached_spi_speed_hz = adis->spi->max_speed_hz; + + adis->spi->max_speed_hz = ADIS16475_BURST_MAX_SPEED; + + ret = spi_sync(adis->spi, &adis->msg); + if (ret) + return ret; + + adis->spi->max_speed_hz = cached_spi_speed_hz; + buffer = adis->buffer; + + crc = be16_to_cpu(buffer[offset + 2]); + valid = adis16475_validate_crc(adis->buffer, crc, st->burst32); + if (!valid) { + dev_err(&adis->spi->dev, "Invalid crc\n"); + goto check_burst32; + } + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + /* + * When burst mode is used, system flags is the first data + * channel in the sequence, but the scan index is 7. + */ + switch (bit) { + case ADIS16475_SCAN_TEMP: + st->data[i++] = buffer[offset]; + break; + case ADIS16475_SCAN_GYRO_X ... ADIS16475_SCAN_ACCEL_Z: + /* + * The first 2 bytes on the received data are the + * DIAG_STAT reg, hence the +1 offset here... + */ + if (st->burst32) { + /* upper 16 */ + st->data[i++] = buffer[bit * 2 + 2]; + /* lower 16 */ + st->data[i++] = buffer[bit * 2 + 1]; + } else { + st->data[i++] = buffer[bit + 1]; + /* + * Don't bother in doing the manual read if the + * device supports burst32. burst32 will be + * enabled in the next call to + * adis16475_burst32_check()... + */ + if (st->lsb_flag && !st->info->has_burst32) { + u16 val = 0; + const u32 reg = ADIS16475_REG_X_GYRO_L + + bit * 4; + + adis_read_reg_16(adis, reg, &val); + st->data[i++] = cpu_to_be16(val); + } else { + /* lower not used */ + st->data[i++] = 0; + } + } + break; + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, st->data, pf->timestamp); +check_burst32: + /* + * We only check the burst mode at the end of the current capture since + * it takes a full data ready cycle for the device to update the burst + * array. + */ + adis16475_burst32_check(st); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void adis16475_disable_clk(void *data) +{ + clk_disable_unprepare((struct clk *)data); +} + +static int adis16475_config_sync_mode(struct adis16475 *st) +{ + int ret; + struct device *dev = &st->adis.spi->dev; + const struct adis16475_sync *sync; + u32 sync_mode; + + /* default to internal clk */ + st->clk_freq = st->info->int_clk * 1000; + + ret = device_property_read_u32(dev, "adi,sync-mode", &sync_mode); + if (ret) + return 0; + + if (sync_mode >= st->info->num_sync) { + dev_err(dev, "Invalid sync mode: %u for %s\n", sync_mode, + st->info->name); + return -EINVAL; + } + + sync = &st->info->sync[sync_mode]; + + /* All the other modes require external input signal */ + if (sync->sync_mode != ADIS16475_SYNC_OUTPUT) { + struct clk *clk = devm_clk_get(dev, NULL); + + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = clk_prepare_enable(clk); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, adis16475_disable_clk, clk); + if (ret) + return ret; + + st->clk_freq = clk_get_rate(clk); + if (st->clk_freq < sync->min_rate || + st->clk_freq > sync->max_rate) { + dev_err(dev, + "Clk rate:%u not in a valid range:[%u %u]\n", + st->clk_freq, sync->min_rate, sync->max_rate); + return -EINVAL; + } + + if (sync->sync_mode == ADIS16475_SYNC_SCALED) { + u16 up_scale; + u32 scaled_out_freq = 0; + /* + * If we are in scaled mode, we must have an up_scale. + * In scaled mode the allowable input clock range is + * 1 Hz to 128 Hz, and the allowable output range is + * 1900 to 2100 Hz. Hence, a scale must be given to + * get the allowable output. + */ + ret = device_property_read_u32(dev, + "adi,scaled-output-hz", + &scaled_out_freq); + if (ret) { + dev_err(dev, "adi,scaled-output-hz must be given when in scaled sync mode"); + return -EINVAL; + } else if (scaled_out_freq < 1900 || + scaled_out_freq > 2100) { + dev_err(dev, "Invalid value: %u for adi,scaled-output-hz", + scaled_out_freq); + return -EINVAL; + } + + up_scale = DIV_ROUND_CLOSEST(scaled_out_freq, + st->clk_freq); + + ret = __adis_write_reg_16(&st->adis, + ADIS16475_REG_UP_SCALE, + up_scale); + if (ret) + return ret; + + st->clk_freq = scaled_out_freq; + } + + st->clk_freq *= 1000; + } + /* + * Keep in mind that the mask for the clk modes in adis1650* + * chips is different (1100 instead of 11100). However, we + * are not configuring BIT(4) in these chips and the default + * value is 0, so we are fine in doing the below operations. + * I'm keeping this for simplicity and avoiding extra variables + * in chip_info. + */ + ret = __adis_update_bits(&st->adis, ADIS16475_REG_MSG_CTRL, + ADIS16475_SYNC_MODE_MASK, sync->sync_mode); + if (ret) + return ret; + + usleep_range(250, 260); + + return 0; +} + +static int adis16475_config_irq_pin(struct adis16475 *st) +{ + int ret; + struct irq_data *desc; + u32 irq_type; + u16 val = 0; + u8 polarity; + struct spi_device *spi = st->adis.spi; + + desc = irq_get_irq_data(spi->irq); + if (!desc) { + dev_err(&spi->dev, "Could not find IRQ %d\n", spi->irq); + return -EINVAL; + } + /* + * It is possible to configure the data ready polarity. Furthermore, we + * need to update the adis struct if we want data ready as active low. + */ + irq_type = irqd_get_trigger_type(desc); + if (irq_type == IRQ_TYPE_EDGE_RISING) { + polarity = 1; + st->adis.irq_flag = IRQF_TRIGGER_RISING; + } else if (irq_type == IRQ_TYPE_EDGE_FALLING) { + polarity = 0; + st->adis.irq_flag = IRQF_TRIGGER_FALLING; + } else { + dev_err(&spi->dev, "Invalid interrupt type 0x%x specified\n", + irq_type); + return -EINVAL; + } + + val = ADIS16475_MSG_CTRL_DR_POL(polarity); + ret = __adis_update_bits(&st->adis, ADIS16475_REG_MSG_CTRL, + ADIS16475_MSG_CTRL_DR_POL_MASK, val); + if (ret) + return ret; + /* + * There is a delay writing to any bits written to the MSC_CTRL + * register. It should not be bigger than 200us, so 250 should be more + * than enough! + */ + usleep_range(250, 260); + + return 0; +} + +static const struct of_device_id adis16475_of_match[] = { + { .compatible = "adi,adis16470", + .data = &adis16475_chip_info[ADIS16470] }, + { .compatible = "adi,adis16475-1", + .data = &adis16475_chip_info[ADIS16475_1] }, + { .compatible = "adi,adis16475-2", + .data = &adis16475_chip_info[ADIS16475_2] }, + { .compatible = "adi,adis16475-3", + .data = &adis16475_chip_info[ADIS16475_3] }, + { .compatible = "adi,adis16477-1", + .data = &adis16475_chip_info[ADIS16477_1] }, + { .compatible = "adi,adis16477-2", + .data = &adis16475_chip_info[ADIS16477_2] }, + { .compatible = "adi,adis16477-3", + .data = &adis16475_chip_info[ADIS16477_3] }, + { .compatible = "adi,adis16465-1", + .data = &adis16475_chip_info[ADIS16465_1] }, + { .compatible = "adi,adis16465-2", + .data = &adis16475_chip_info[ADIS16465_2] }, + { .compatible = "adi,adis16465-3", + .data = &adis16475_chip_info[ADIS16465_3] }, + { .compatible = "adi,adis16467-1", + .data = &adis16475_chip_info[ADIS16467_1] }, + { .compatible = "adi,adis16467-2", + .data = &adis16475_chip_info[ADIS16467_2] }, + { .compatible = "adi,adis16467-3", + .data = &adis16475_chip_info[ADIS16467_3] }, + { .compatible = "adi,adis16500", + .data = &adis16475_chip_info[ADIS16500] }, + { .compatible = "adi,adis16505-1", + .data = &adis16475_chip_info[ADIS16505_1] }, + { .compatible = "adi,adis16505-2", + .data = &adis16475_chip_info[ADIS16505_2] }, + { .compatible = "adi,adis16505-3", + .data = &adis16475_chip_info[ADIS16505_3] }, + { .compatible = "adi,adis16507-1", + .data = &adis16475_chip_info[ADIS16507_1] }, + { .compatible = "adi,adis16507-2", + .data = &adis16475_chip_info[ADIS16507_2] }, + { .compatible = "adi,adis16507-3", + .data = &adis16475_chip_info[ADIS16507_3] }, + { }, +}; +MODULE_DEVICE_TABLE(of, adis16475_of_match); + +static int adis16475_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adis16475 *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + st->adis.burst = &adis16475_burst; + + st->info = device_get_match_data(&spi->dev); + if (!st->info) + return -EINVAL; + + ret = adis_init(&st->adis, indio_dev, spi, &st->info->adis_data); + if (ret) + return ret; + + indio_dev->name = st->info->name; + indio_dev->channels = st->info->channels; + indio_dev->num_channels = st->info->num_channels; + indio_dev->info = &adis16475_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = __adis_initial_startup(&st->adis); + if (ret) + return ret; + + ret = adis16475_config_irq_pin(st); + if (ret) + return ret; + + ret = adis16475_config_sync_mode(st); + if (ret) + return ret; + + ret = devm_adis_setup_buffer_and_trigger(&st->adis, indio_dev, + adis16475_trigger_handler); + if (ret) + return ret; + + adis16475_enable_irq(&st->adis, false); + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + adis16475_debugfs_init(indio_dev); + + return 0; +} + +static struct spi_driver adis16475_driver = { + .driver = { + .name = "adis16475", + .of_match_table = adis16475_of_match, + }, + .probe = adis16475_probe, +}; +module_spi_driver(adis16475_driver); + +MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16475 IMU driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/adis16480.c b/drivers/iio/imu/adis16480.c index cfae0e4476e7..1eb4f98076f1 100644 --- a/drivers/iio/imu/adis16480.c +++ b/drivers/iio/imu/adis16480.c @@ -284,22 +284,18 @@ DEFINE_DEBUGFS_ATTRIBUTE(adis16480_flash_count_fops, static int adis16480_debugfs_init(struct iio_dev *indio_dev) { struct adis16480 *adis16480 = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); debugfs_create_file_unsafe("firmware_revision", 0400, - indio_dev->debugfs_dentry, adis16480, - &adis16480_firmware_revision_fops); + d, adis16480, &adis16480_firmware_revision_fops); debugfs_create_file_unsafe("firmware_date", 0400, - indio_dev->debugfs_dentry, adis16480, - &adis16480_firmware_date_fops); + d, adis16480, &adis16480_firmware_date_fops); debugfs_create_file_unsafe("serial_number", 0400, - indio_dev->debugfs_dentry, adis16480, - &adis16480_serial_number_fops); + d, adis16480, &adis16480_serial_number_fops); debugfs_create_file_unsafe("product_id", 0400, - indio_dev->debugfs_dentry, adis16480, - &adis16480_product_id_fops); + d, adis16480, &adis16480_product_id_fops); debugfs_create_file_unsafe("flash_count", 0400, - indio_dev->debugfs_dentry, adis16480, - &adis16480_flash_count_fops); + d, adis16480, &adis16480_flash_count_fops); return 0; } @@ -1106,12 +1102,12 @@ static int adis16480_config_irq_pin(struct device_node *of_node, /* * Get the interrupt line behaviour. The data ready polarity can be * configured as positive or negative, corresponding to - * IRQF_TRIGGER_RISING or IRQF_TRIGGER_FALLING respectively. + * IRQ_TYPE_EDGE_RISING or IRQ_TYPE_EDGE_FALLING respectively. */ irq_type = irqd_get_trigger_type(desc); - if (irq_type == IRQF_TRIGGER_RISING) { /* Default */ + if (irq_type == IRQ_TYPE_EDGE_RISING) { /* Default */ val |= ADIS16480_DRDY_POL(1); - } else if (irq_type == IRQF_TRIGGER_FALLING) { + } else if (irq_type == IRQ_TYPE_EDGE_FALLING) { val |= ADIS16480_DRDY_POL(0); } else { dev_err(&st->adis.spi->dev, @@ -1233,7 +1229,6 @@ static int adis16480_probe(struct spi_device *spi) st = iio_priv(indio_dev); st->chip_info = &adis16480_chip_info[id->driver_data]; - indio_dev->dev.parent = &spi->dev; indio_dev->name = spi_get_device_id(spi)->name; indio_dev->channels = st->chip_info->channels; indio_dev->num_channels = st->chip_info->num_channels; diff --git a/drivers/iio/imu/adis_buffer.c b/drivers/iio/imu/adis_buffer.c index 04e5e2a0fd6b..5b4225ee09b9 100644 --- a/drivers/iio/imu/adis_buffer.c +++ b/drivers/iio/imu/adis_buffer.c @@ -23,25 +23,30 @@ static int adis_update_scan_mode_burst(struct iio_dev *indio_dev, const unsigned long *scan_mask) { struct adis *adis = iio_device_get_drvdata(indio_dev); - unsigned int burst_length; + unsigned int burst_length, burst_max_length; u8 *tx; /* All but the timestamp channel */ burst_length = (indio_dev->num_channels - 1) * sizeof(u16); - burst_length += adis->burst->extra_len; + burst_length += adis->burst->extra_len + adis->burst_extra_len; + + if (adis->burst->burst_max_len) + burst_max_length = adis->burst->burst_max_len; + else + burst_max_length = burst_length; adis->xfer = kcalloc(2, sizeof(*adis->xfer), GFP_KERNEL); if (!adis->xfer) return -ENOMEM; - adis->buffer = kzalloc(burst_length + sizeof(u16), GFP_KERNEL); + adis->buffer = kzalloc(burst_max_length + sizeof(u16), GFP_KERNEL); if (!adis->buffer) { kfree(adis->xfer); adis->xfer = NULL; return -ENOMEM; } - tx = adis->buffer + burst_length; + tx = adis->buffer + burst_max_length; tx[0] = ADIS_READ_REG(adis->burst->reg_cmd); tx[1] = 0; @@ -156,6 +161,14 @@ static irqreturn_t adis_trigger_handler(int irq, void *p) return IRQ_HANDLED; } +static void adis_buffer_cleanup(void *arg) +{ + struct adis *adis = arg; + + kfree(adis->buffer); + kfree(adis->xfer); +} + /** * adis_setup_buffer_and_trigger() - Sets up buffer and trigger for the adis device * @adis: The adis device. @@ -199,6 +212,43 @@ error_buffer_cleanup: EXPORT_SYMBOL_GPL(adis_setup_buffer_and_trigger); /** + * devm_adis_setup_buffer_and_trigger() - Sets up buffer and trigger for + * the managed adis device + * @adis: The adis device + * @indio_dev: The IIO device + * @trigger_handler: Optional trigger handler, may be NULL. + * + * Returns 0 on success, a negative error code otherwise. + * + * This function perfoms exactly the same as adis_setup_buffer_and_trigger() + */ +int +devm_adis_setup_buffer_and_trigger(struct adis *adis, struct iio_dev *indio_dev, + irq_handler_t trigger_handler) +{ + int ret; + + if (!trigger_handler) + trigger_handler = adis_trigger_handler; + + ret = devm_iio_triggered_buffer_setup(&adis->spi->dev, indio_dev, + &iio_pollfunc_store_time, + trigger_handler, NULL); + if (ret) + return ret; + + if (adis->spi->irq) { + ret = devm_adis_probe_trigger(adis, indio_dev); + if (ret) + return ret; + } + + return devm_add_action_or_reset(&adis->spi->dev, adis_buffer_cleanup, + adis); +} +EXPORT_SYMBOL_GPL(devm_adis_setup_buffer_and_trigger); + +/** * adis_cleanup_buffer_and_trigger() - Free buffer and trigger resources * @adis: The adis device. * @indio_dev: The IIO device. diff --git a/drivers/iio/imu/adis_trigger.c b/drivers/iio/imu/adis_trigger.c index 8b9cd02c0f9f..8afe71947c00 100644 --- a/drivers/iio/imu/adis_trigger.c +++ b/drivers/iio/imu/adis_trigger.c @@ -27,6 +27,34 @@ static const struct iio_trigger_ops adis_trigger_ops = { .set_trigger_state = &adis_data_rdy_trigger_set_state, }; +static void adis_trigger_setup(struct adis *adis) +{ + adis->trig->dev.parent = &adis->spi->dev; + adis->trig->ops = &adis_trigger_ops; + iio_trigger_set_drvdata(adis->trig, adis); +} + +static int adis_validate_irq_flag(struct adis *adis) +{ + /* + * Typically this devices have data ready either on the rising edge or + * on the falling edge of the data ready pin. This checks enforces that + * one of those is set in the drivers... It defaults to + * IRQF_TRIGGER_RISING for backward compatibility wiht devices that + * don't support changing the pin polarity. + */ + if (!adis->irq_flag) { + adis->irq_flag = IRQF_TRIGGER_RISING; + return 0; + } else if (adis->irq_flag != IRQF_TRIGGER_RISING && + adis->irq_flag != IRQF_TRIGGER_FALLING) { + dev_err(&adis->spi->dev, "Invalid IRQ mask: %08lx\n", + adis->irq_flag); + return -EINVAL; + } + + return 0; +} /** * adis_probe_trigger() - Sets up trigger for a adis device * @adis: The adis device @@ -45,13 +73,15 @@ int adis_probe_trigger(struct adis *adis, struct iio_dev *indio_dev) if (adis->trig == NULL) return -ENOMEM; - adis->trig->dev.parent = &adis->spi->dev; - adis->trig->ops = &adis_trigger_ops; - iio_trigger_set_drvdata(adis->trig, adis); + adis_trigger_setup(adis); + + ret = adis_validate_irq_flag(adis); + if (ret) + return ret; ret = request_irq(adis->spi->irq, &iio_trigger_generic_data_rdy_poll, - IRQF_TRIGGER_RISING, + adis->irq_flag, indio_dev->name, adis->trig); if (ret) @@ -74,6 +104,40 @@ error_free_trig: EXPORT_SYMBOL_GPL(adis_probe_trigger); /** + * devm_adis_probe_trigger() - Sets up trigger for a managed adis device + * @adis: The adis device + * @indio_dev: The IIO device + * + * Returns 0 on success or a negative error code + */ +int devm_adis_probe_trigger(struct adis *adis, struct iio_dev *indio_dev) +{ + int ret; + + adis->trig = devm_iio_trigger_alloc(&adis->spi->dev, "%s-dev%d", + indio_dev->name, indio_dev->id); + if (!adis->trig) + return -ENOMEM; + + adis_trigger_setup(adis); + + ret = adis_validate_irq_flag(adis); + if (ret) + return ret; + + ret = devm_request_irq(&adis->spi->dev, adis->spi->irq, + &iio_trigger_generic_data_rdy_poll, + adis->irq_flag, + indio_dev->name, + adis->trig); + if (ret) + return ret; + + return devm_iio_trigger_register(&adis->spi->dev, adis->trig); +} +EXPORT_SYMBOL_GPL(devm_adis_probe_trigger); + +/** * adis_remove_trigger() - Remove trigger for a adis devices * @adis: The adis device * diff --git a/drivers/iio/imu/bmi160/bmi160.h b/drivers/iio/imu/bmi160/bmi160.h index 621f5309d735..a82e040bd109 100644 --- a/drivers/iio/imu/bmi160/bmi160.h +++ b/drivers/iio/imu/bmi160/bmi160.h @@ -3,10 +3,13 @@ #define BMI160_H_ #include <linux/iio/iio.h> +#include <linux/regulator/consumer.h> struct bmi160_data { struct regmap *regmap; struct iio_trigger *trig; + struct regulator_bulk_data supplies[2]; + struct iio_mount_matrix orientation; }; extern const struct regmap_config bmi160_regmap_config; diff --git a/drivers/iio/imu/bmi160/bmi160_core.c b/drivers/iio/imu/bmi160/bmi160_core.c index 6af65d6f1d28..222ebb26f013 100644 --- a/drivers/iio/imu/bmi160/bmi160_core.c +++ b/drivers/iio/imu/bmi160/bmi160_core.c @@ -15,6 +15,7 @@ #include <linux/delay.h> #include <linux/irq.h> #include <linux/of_irq.h> +#include <linux/regulator/consumer.h> #include <linux/iio/iio.h> #include <linux/iio/triggered_buffer.h> @@ -109,6 +110,7 @@ .storagebits = 16, \ .endianness = IIO_LE, \ }, \ + .ext_info = bmi160_ext_info, \ } /* scan indexes follow DATA register order */ @@ -264,6 +266,20 @@ static const struct bmi160_odr_item bmi160_odr_table[] = { }, }; +static const struct iio_mount_matrix * +bmi160_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct bmi160_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info bmi160_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bmi160_get_mount_matrix), + { } +}; + static const struct iio_chan_spec bmi160_channels[] = { BMI160_CHANNEL(IIO_ACCEL, X, BMI160_SCAN_ACCEL_X), BMI160_CHANNEL(IIO_ACCEL, Y, BMI160_SCAN_ACCEL_Y), @@ -709,6 +725,12 @@ static int bmi160_chip_init(struct bmi160_data *data, bool use_spi) unsigned int val; struct device *dev = regmap_get_device(data->regmap); + ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies), data->supplies); + if (ret) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + ret = regmap_write(data->regmap, BMI160_REG_CMD, BMI160_CMD_SOFTRESET); if (ret) return ret; @@ -793,9 +815,16 @@ int bmi160_probe_trigger(struct iio_dev *indio_dev, int irq, u32 irq_type) static void bmi160_chip_uninit(void *data) { struct bmi160_data *bmi_data = data; + struct device *dev = regmap_get_device(bmi_data->regmap); + int ret; bmi160_set_mode(bmi_data, BMI160_GYRO, false); bmi160_set_mode(bmi_data, BMI160_ACCEL, false); + + ret = regulator_bulk_disable(ARRAY_SIZE(bmi_data->supplies), + bmi_data->supplies); + if (ret) + dev_err(dev, "Failed to disable regulators: %d\n", ret); } int bmi160_core_probe(struct device *dev, struct regmap *regmap, @@ -815,6 +844,21 @@ int bmi160_core_probe(struct device *dev, struct regmap *regmap, dev_set_drvdata(dev, indio_dev); data->regmap = regmap; + data->supplies[0].supply = "vdd"; + data->supplies[1].supply = "vddio"; + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(data->supplies), + data->supplies); + if (ret) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + ret = iio_read_mount_matrix(dev, "mount-matrix", + &data->orientation); + if (ret) + return ret; + ret = bmi160_chip_init(data, use_spi); if (ret) return ret; @@ -826,7 +870,6 @@ int bmi160_core_probe(struct device *dev, struct regmap *regmap, if (!name && ACPI_HANDLE(dev)) name = bmi160_match_acpi_device(dev); - indio_dev->dev.parent = dev; indio_dev->channels = bmi160_channels; indio_dev->num_channels = ARRAY_SIZE(bmi160_channels); indio_dev->name = name; @@ -853,6 +896,6 @@ int bmi160_core_probe(struct device *dev, struct regmap *regmap, } EXPORT_SYMBOL_GPL(bmi160_core_probe); -MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com"); +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); MODULE_DESCRIPTION("Bosch BMI160 driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/bmi160/bmi160_i2c.c b/drivers/iio/imu/bmi160/bmi160_i2c.c index e36f5e82d400..26398614eddf 100644 --- a/drivers/iio/imu/bmi160/bmi160_i2c.c +++ b/drivers/iio/imu/bmi160/bmi160_i2c.c @@ -24,8 +24,8 @@ static int bmi160_i2c_probe(struct i2c_client *client, regmap = devm_regmap_init_i2c(client, &bmi160_regmap_config); if (IS_ERR(regmap)) { - dev_err(&client->dev, "Failed to register i2c regmap %d\n", - (int)PTR_ERR(regmap)); + dev_err(&client->dev, "Failed to register i2c regmap: %pe\n", + regmap); return PTR_ERR(regmap); } diff --git a/drivers/iio/imu/bmi160/bmi160_spi.c b/drivers/iio/imu/bmi160/bmi160_spi.c index c19e3df35559..61389b41c6d9 100644 --- a/drivers/iio/imu/bmi160/bmi160_spi.c +++ b/drivers/iio/imu/bmi160/bmi160_spi.c @@ -20,8 +20,8 @@ static int bmi160_spi_probe(struct spi_device *spi) regmap = devm_regmap_init_spi(spi, &bmi160_regmap_config); if (IS_ERR(regmap)) { - dev_err(&spi->dev, "Failed to register spi regmap %d\n", - (int)PTR_ERR(regmap)); + dev_err(&spi->dev, "Failed to register spi regmap: %pe\n", + regmap); return PTR_ERR(regmap); } return bmi160_core_probe(&spi->dev, regmap, id->name, true); diff --git a/drivers/iio/imu/fxos8700_core.c b/drivers/iio/imu/fxos8700_core.c index 7b47be44ea59..ab288186f36e 100644 --- a/drivers/iio/imu/fxos8700_core.c +++ b/drivers/iio/imu/fxos8700_core.c @@ -633,7 +633,6 @@ int fxos8700_core_probe(struct device *dev, struct regmap *regmap, if (ret) return ret; - indio_dev->dev.parent = dev; indio_dev->channels = fxos8700_channels; indio_dev->num_channels = ARRAY_SIZE(fxos8700_channels); indio_dev->name = name ? name : "fxos8700"; diff --git a/drivers/iio/imu/inv_icm42600/Kconfig b/drivers/iio/imu/inv_icm42600/Kconfig new file mode 100644 index 000000000000..50cbcfcb6cf1 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/Kconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config INV_ICM42600 + tristate + select IIO_BUFFER + +config INV_ICM42600_I2C + tristate "InvenSense ICM-426xx I2C driver" + depends on I2C + select INV_ICM42600 + select REGMAP_I2C + help + This driver supports the InvenSense ICM-426xx motion tracking + devices over I2C. + + This driver can be built as a module. The module will be called + inv-icm42600-i2c. + +config INV_ICM42600_SPI + tristate "InvenSense ICM-426xx SPI driver" + depends on SPI_MASTER + select INV_ICM42600 + select REGMAP_SPI + help + This driver supports the InvenSense ICM-426xx motion tracking + devices over SPI. + + This driver can be built as a module. The module will be called + inv-icm42600-spi. diff --git a/drivers/iio/imu/inv_icm42600/Makefile b/drivers/iio/imu/inv_icm42600/Makefile new file mode 100644 index 000000000000..291714d9aa54 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +obj-$(CONFIG_INV_ICM42600) += inv-icm42600.o +inv-icm42600-y += inv_icm42600_core.o +inv-icm42600-y += inv_icm42600_gyro.o +inv-icm42600-y += inv_icm42600_accel.o +inv-icm42600-y += inv_icm42600_temp.o +inv-icm42600-y += inv_icm42600_buffer.o +inv-icm42600-y += inv_icm42600_timestamp.o + +obj-$(CONFIG_INV_ICM42600_I2C) += inv-icm42600-i2c.o +inv-icm42600-i2c-y += inv_icm42600_i2c.o + +obj-$(CONFIG_INV_ICM42600_SPI) += inv-icm42600-spi.o +inv-icm42600-spi-y += inv_icm42600_spi.o diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h new file mode 100644 index 000000000000..c0f5059b13b3 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h @@ -0,0 +1,395 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#ifndef INV_ICM42600_H_ +#define INV_ICM42600_H_ + +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/regmap.h> +#include <linux/mutex.h> +#include <linux/regulator/consumer.h> +#include <linux/pm.h> +#include <linux/iio/iio.h> + +#include "inv_icm42600_buffer.h" + +enum inv_icm42600_chip { + INV_CHIP_ICM42600, + INV_CHIP_ICM42602, + INV_CHIP_ICM42605, + INV_CHIP_ICM42622, + INV_CHIP_NB, +}; + +/* serial bus slew rates */ +enum inv_icm42600_slew_rate { + INV_ICM42600_SLEW_RATE_20_60NS, + INV_ICM42600_SLEW_RATE_12_36NS, + INV_ICM42600_SLEW_RATE_6_18NS, + INV_ICM42600_SLEW_RATE_4_12NS, + INV_ICM42600_SLEW_RATE_2_6NS, + INV_ICM42600_SLEW_RATE_INF_2NS, +}; + +enum inv_icm42600_sensor_mode { + INV_ICM42600_SENSOR_MODE_OFF, + INV_ICM42600_SENSOR_MODE_STANDBY, + INV_ICM42600_SENSOR_MODE_LOW_POWER, + INV_ICM42600_SENSOR_MODE_LOW_NOISE, + INV_ICM42600_SENSOR_MODE_NB, +}; + +/* gyroscope fullscale values */ +enum inv_icm42600_gyro_fs { + INV_ICM42600_GYRO_FS_2000DPS, + INV_ICM42600_GYRO_FS_1000DPS, + INV_ICM42600_GYRO_FS_500DPS, + INV_ICM42600_GYRO_FS_250DPS, + INV_ICM42600_GYRO_FS_125DPS, + INV_ICM42600_GYRO_FS_62_5DPS, + INV_ICM42600_GYRO_FS_31_25DPS, + INV_ICM42600_GYRO_FS_15_625DPS, + INV_ICM42600_GYRO_FS_NB, +}; + +/* accelerometer fullscale values */ +enum inv_icm42600_accel_fs { + INV_ICM42600_ACCEL_FS_16G, + INV_ICM42600_ACCEL_FS_8G, + INV_ICM42600_ACCEL_FS_4G, + INV_ICM42600_ACCEL_FS_2G, + INV_ICM42600_ACCEL_FS_NB, +}; + +/* ODR suffixed by LN or LP are Low-Noise or Low-Power mode only */ +enum inv_icm42600_odr { + INV_ICM42600_ODR_8KHZ_LN = 3, + INV_ICM42600_ODR_4KHZ_LN, + INV_ICM42600_ODR_2KHZ_LN, + INV_ICM42600_ODR_1KHZ_LN, + INV_ICM42600_ODR_200HZ, + INV_ICM42600_ODR_100HZ, + INV_ICM42600_ODR_50HZ, + INV_ICM42600_ODR_25HZ, + INV_ICM42600_ODR_12_5HZ, + INV_ICM42600_ODR_6_25HZ_LP, + INV_ICM42600_ODR_3_125HZ_LP, + INV_ICM42600_ODR_1_5625HZ_LP, + INV_ICM42600_ODR_500HZ, + INV_ICM42600_ODR_NB, +}; + +enum inv_icm42600_filter { + /* Low-Noise mode sensor data filter (3rd order filter by default) */ + INV_ICM42600_FILTER_BW_ODR_DIV_2, + + /* Low-Power mode sensor data filter (averaging) */ + INV_ICM42600_FILTER_AVG_1X = 1, + INV_ICM42600_FILTER_AVG_16X = 6, +}; + +struct inv_icm42600_sensor_conf { + int mode; + int fs; + int odr; + int filter; +}; +#define INV_ICM42600_SENSOR_CONF_INIT {-1, -1, -1, -1} + +struct inv_icm42600_conf { + struct inv_icm42600_sensor_conf gyro; + struct inv_icm42600_sensor_conf accel; + bool temp_en; +}; + +struct inv_icm42600_suspended { + enum inv_icm42600_sensor_mode gyro; + enum inv_icm42600_sensor_mode accel; + bool temp; +}; + +/** + * struct inv_icm42600_state - driver state variables + * @lock: lock for serializing multiple registers access. + * @chip: chip identifier. + * @name: chip name. + * @map: regmap pointer. + * @vdd_supply: VDD voltage regulator for the chip. + * @vddio_supply: I/O voltage regulator for the chip. + * @orientation: sensor chip orientation relative to main hardware. + * @conf: chip sensors configurations. + * @suspended: suspended sensors configuration. + * @indio_gyro: gyroscope IIO device. + * @indio_accel: accelerometer IIO device. + * @buffer: data transfer buffer aligned for DMA. + * @fifo: FIFO management structure. + * @timestamp: interrupt timestamps. + */ +struct inv_icm42600_state { + struct mutex lock; + enum inv_icm42600_chip chip; + const char *name; + struct regmap *map; + struct regulator *vdd_supply; + struct regulator *vddio_supply; + struct iio_mount_matrix orientation; + struct inv_icm42600_conf conf; + struct inv_icm42600_suspended suspended; + struct iio_dev *indio_gyro; + struct iio_dev *indio_accel; + uint8_t buffer[2] ____cacheline_aligned; + struct inv_icm42600_fifo fifo; + struct { + int64_t gyro; + int64_t accel; + } timestamp; +}; + +/* Virtual register addresses: @bank on MSB (4 upper bits), @address on LSB */ + +/* Bank selection register, available in all banks */ +#define INV_ICM42600_REG_BANK_SEL 0x76 +#define INV_ICM42600_BANK_SEL_MASK GENMASK(2, 0) + +/* User bank 0 (MSB 0x00) */ +#define INV_ICM42600_REG_DEVICE_CONFIG 0x0011 +#define INV_ICM42600_DEVICE_CONFIG_SOFT_RESET BIT(0) + +#define INV_ICM42600_REG_DRIVE_CONFIG 0x0013 +#define INV_ICM42600_DRIVE_CONFIG_I2C_MASK GENMASK(5, 3) +#define INV_ICM42600_DRIVE_CONFIG_I2C(_rate) \ + FIELD_PREP(INV_ICM42600_DRIVE_CONFIG_I2C_MASK, (_rate)) +#define INV_ICM42600_DRIVE_CONFIG_SPI_MASK GENMASK(2, 0) +#define INV_ICM42600_DRIVE_CONFIG_SPI(_rate) \ + FIELD_PREP(INV_ICM42600_DRIVE_CONFIG_SPI_MASK, (_rate)) + +#define INV_ICM42600_REG_INT_CONFIG 0x0014 +#define INV_ICM42600_INT_CONFIG_INT2_LATCHED BIT(5) +#define INV_ICM42600_INT_CONFIG_INT2_PUSH_PULL BIT(4) +#define INV_ICM42600_INT_CONFIG_INT2_ACTIVE_HIGH BIT(3) +#define INV_ICM42600_INT_CONFIG_INT2_ACTIVE_LOW 0x00 +#define INV_ICM42600_INT_CONFIG_INT1_LATCHED BIT(2) +#define INV_ICM42600_INT_CONFIG_INT1_PUSH_PULL BIT(1) +#define INV_ICM42600_INT_CONFIG_INT1_ACTIVE_HIGH BIT(0) +#define INV_ICM42600_INT_CONFIG_INT1_ACTIVE_LOW 0x00 + +#define INV_ICM42600_REG_FIFO_CONFIG 0x0016 +#define INV_ICM42600_FIFO_CONFIG_MASK GENMASK(7, 6) +#define INV_ICM42600_FIFO_CONFIG_BYPASS \ + FIELD_PREP(INV_ICM42600_FIFO_CONFIG_MASK, 0) +#define INV_ICM42600_FIFO_CONFIG_STREAM \ + FIELD_PREP(INV_ICM42600_FIFO_CONFIG_MASK, 1) +#define INV_ICM42600_FIFO_CONFIG_STOP_ON_FULL \ + FIELD_PREP(INV_ICM42600_FIFO_CONFIG_MASK, 2) + +/* all sensor data are 16 bits (2 registers wide) in big-endian */ +#define INV_ICM42600_REG_TEMP_DATA 0x001D +#define INV_ICM42600_REG_ACCEL_DATA_X 0x001F +#define INV_ICM42600_REG_ACCEL_DATA_Y 0x0021 +#define INV_ICM42600_REG_ACCEL_DATA_Z 0x0023 +#define INV_ICM42600_REG_GYRO_DATA_X 0x0025 +#define INV_ICM42600_REG_GYRO_DATA_Y 0x0027 +#define INV_ICM42600_REG_GYRO_DATA_Z 0x0029 +#define INV_ICM42600_DATA_INVALID -32768 + +#define INV_ICM42600_REG_INT_STATUS 0x002D +#define INV_ICM42600_INT_STATUS_UI_FSYNC BIT(6) +#define INV_ICM42600_INT_STATUS_PLL_RDY BIT(5) +#define INV_ICM42600_INT_STATUS_RESET_DONE BIT(4) +#define INV_ICM42600_INT_STATUS_DATA_RDY BIT(3) +#define INV_ICM42600_INT_STATUS_FIFO_THS BIT(2) +#define INV_ICM42600_INT_STATUS_FIFO_FULL BIT(1) +#define INV_ICM42600_INT_STATUS_AGC_RDY BIT(0) + +/* + * FIFO access registers + * FIFO count is 16 bits (2 registers) big-endian + * FIFO data is a continuous read register to read FIFO content + */ +#define INV_ICM42600_REG_FIFO_COUNT 0x002E +#define INV_ICM42600_REG_FIFO_DATA 0x0030 + +#define INV_ICM42600_REG_SIGNAL_PATH_RESET 0x004B +#define INV_ICM42600_SIGNAL_PATH_RESET_DMP_INIT_EN BIT(6) +#define INV_ICM42600_SIGNAL_PATH_RESET_DMP_MEM_RESET BIT(5) +#define INV_ICM42600_SIGNAL_PATH_RESET_RESET BIT(3) +#define INV_ICM42600_SIGNAL_PATH_RESET_TMST_STROBE BIT(2) +#define INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH BIT(1) + +/* default configuration: all data big-endian and fifo count in bytes */ +#define INV_ICM42600_REG_INTF_CONFIG0 0x004C +#define INV_ICM42600_INTF_CONFIG0_FIFO_HOLD_LAST_DATA BIT(7) +#define INV_ICM42600_INTF_CONFIG0_FIFO_COUNT_REC BIT(6) +#define INV_ICM42600_INTF_CONFIG0_FIFO_COUNT_ENDIAN BIT(5) +#define INV_ICM42600_INTF_CONFIG0_SENSOR_DATA_ENDIAN BIT(4) +#define INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_MASK GENMASK(1, 0) +#define INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_SPI_DIS \ + FIELD_PREP(INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_MASK, 2) +#define INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_I2C_DIS \ + FIELD_PREP(INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_MASK, 3) + +#define INV_ICM42600_REG_INTF_CONFIG1 0x004D +#define INV_ICM42600_INTF_CONFIG1_ACCEL_LP_CLK_RC BIT(3) + +#define INV_ICM42600_REG_PWR_MGMT0 0x004E +#define INV_ICM42600_PWR_MGMT0_TEMP_DIS BIT(5) +#define INV_ICM42600_PWR_MGMT0_IDLE BIT(4) +#define INV_ICM42600_PWR_MGMT0_GYRO(_mode) \ + FIELD_PREP(GENMASK(3, 2), (_mode)) +#define INV_ICM42600_PWR_MGMT0_ACCEL(_mode) \ + FIELD_PREP(GENMASK(1, 0), (_mode)) + +#define INV_ICM42600_REG_GYRO_CONFIG0 0x004F +#define INV_ICM42600_GYRO_CONFIG0_FS(_fs) \ + FIELD_PREP(GENMASK(7, 5), (_fs)) +#define INV_ICM42600_GYRO_CONFIG0_ODR(_odr) \ + FIELD_PREP(GENMASK(3, 0), (_odr)) + +#define INV_ICM42600_REG_ACCEL_CONFIG0 0x0050 +#define INV_ICM42600_ACCEL_CONFIG0_FS(_fs) \ + FIELD_PREP(GENMASK(7, 5), (_fs)) +#define INV_ICM42600_ACCEL_CONFIG0_ODR(_odr) \ + FIELD_PREP(GENMASK(3, 0), (_odr)) + +#define INV_ICM42600_REG_GYRO_ACCEL_CONFIG0 0x0052 +#define INV_ICM42600_GYRO_ACCEL_CONFIG0_ACCEL_FILT(_f) \ + FIELD_PREP(GENMASK(7, 4), (_f)) +#define INV_ICM42600_GYRO_ACCEL_CONFIG0_GYRO_FILT(_f) \ + FIELD_PREP(GENMASK(3, 0), (_f)) + +#define INV_ICM42600_REG_TMST_CONFIG 0x0054 +#define INV_ICM42600_TMST_CONFIG_MASK GENMASK(4, 0) +#define INV_ICM42600_TMST_CONFIG_TMST_TO_REGS_EN BIT(4) +#define INV_ICM42600_TMST_CONFIG_TMST_RES_16US BIT(3) +#define INV_ICM42600_TMST_CONFIG_TMST_DELTA_EN BIT(2) +#define INV_ICM42600_TMST_CONFIG_TMST_FSYNC_EN BIT(1) +#define INV_ICM42600_TMST_CONFIG_TMST_EN BIT(0) + +#define INV_ICM42600_REG_FIFO_CONFIG1 0x005F +#define INV_ICM42600_FIFO_CONFIG1_RESUME_PARTIAL_RD BIT(6) +#define INV_ICM42600_FIFO_CONFIG1_WM_GT_TH BIT(5) +#define INV_ICM42600_FIFO_CONFIG1_TMST_FSYNC_EN BIT(3) +#define INV_ICM42600_FIFO_CONFIG1_TEMP_EN BIT(2) +#define INV_ICM42600_FIFO_CONFIG1_GYRO_EN BIT(1) +#define INV_ICM42600_FIFO_CONFIG1_ACCEL_EN BIT(0) + +/* FIFO watermark is 16 bits (2 registers wide) in little-endian */ +#define INV_ICM42600_REG_FIFO_WATERMARK 0x0060 +#define INV_ICM42600_FIFO_WATERMARK_VAL(_wm) \ + cpu_to_le16((_wm) & GENMASK(11, 0)) +/* FIFO is 2048 bytes, let 12 samples for reading latency */ +#define INV_ICM42600_FIFO_WATERMARK_MAX (2048 - 12 * 16) + +#define INV_ICM42600_REG_INT_CONFIG1 0x0064 +#define INV_ICM42600_INT_CONFIG1_TPULSE_DURATION BIT(6) +#define INV_ICM42600_INT_CONFIG1_TDEASSERT_DISABLE BIT(5) +#define INV_ICM42600_INT_CONFIG1_ASYNC_RESET BIT(4) + +#define INV_ICM42600_REG_INT_SOURCE0 0x0065 +#define INV_ICM42600_INT_SOURCE0_UI_FSYNC_INT1_EN BIT(6) +#define INV_ICM42600_INT_SOURCE0_PLL_RDY_INT1_EN BIT(5) +#define INV_ICM42600_INT_SOURCE0_RESET_DONE_INT1_EN BIT(4) +#define INV_ICM42600_INT_SOURCE0_UI_DRDY_INT1_EN BIT(3) +#define INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN BIT(2) +#define INV_ICM42600_INT_SOURCE0_FIFO_FULL_INT1_EN BIT(1) +#define INV_ICM42600_INT_SOURCE0_UI_AGC_RDY_INT1_EN BIT(0) + +#define INV_ICM42600_REG_WHOAMI 0x0075 +#define INV_ICM42600_WHOAMI_ICM42600 0x40 +#define INV_ICM42600_WHOAMI_ICM42602 0x41 +#define INV_ICM42600_WHOAMI_ICM42605 0x42 +#define INV_ICM42600_WHOAMI_ICM42622 0x46 + +/* User bank 1 (MSB 0x10) */ +#define INV_ICM42600_REG_SENSOR_CONFIG0 0x1003 +#define INV_ICM42600_SENSOR_CONFIG0_ZG_DISABLE BIT(5) +#define INV_ICM42600_SENSOR_CONFIG0_YG_DISABLE BIT(4) +#define INV_ICM42600_SENSOR_CONFIG0_XG_DISABLE BIT(3) +#define INV_ICM42600_SENSOR_CONFIG0_ZA_DISABLE BIT(2) +#define INV_ICM42600_SENSOR_CONFIG0_YA_DISABLE BIT(1) +#define INV_ICM42600_SENSOR_CONFIG0_XA_DISABLE BIT(0) + +/* Timestamp value is 20 bits (3 registers) in little-endian */ +#define INV_ICM42600_REG_TMSTVAL 0x1062 +#define INV_ICM42600_TMSTVAL_MASK GENMASK(19, 0) + +#define INV_ICM42600_REG_INTF_CONFIG4 0x107A +#define INV_ICM42600_INTF_CONFIG4_I3C_BUS_ONLY BIT(6) +#define INV_ICM42600_INTF_CONFIG4_SPI_AP_4WIRE BIT(1) + +#define INV_ICM42600_REG_INTF_CONFIG6 0x107C +#define INV_ICM42600_INTF_CONFIG6_MASK GENMASK(4, 0) +#define INV_ICM42600_INTF_CONFIG6_I3C_EN BIT(4) +#define INV_ICM42600_INTF_CONFIG6_I3C_IBI_BYTE_EN BIT(3) +#define INV_ICM42600_INTF_CONFIG6_I3C_IBI_EN BIT(2) +#define INV_ICM42600_INTF_CONFIG6_I3C_DDR_EN BIT(1) +#define INV_ICM42600_INTF_CONFIG6_I3C_SDR_EN BIT(0) + +/* User bank 4 (MSB 0x40) */ +#define INV_ICM42600_REG_INT_SOURCE8 0x404F +#define INV_ICM42600_INT_SOURCE8_FSYNC_IBI_EN BIT(5) +#define INV_ICM42600_INT_SOURCE8_PLL_RDY_IBI_EN BIT(4) +#define INV_ICM42600_INT_SOURCE8_UI_DRDY_IBI_EN BIT(3) +#define INV_ICM42600_INT_SOURCE8_FIFO_THS_IBI_EN BIT(2) +#define INV_ICM42600_INT_SOURCE8_FIFO_FULL_IBI_EN BIT(1) +#define INV_ICM42600_INT_SOURCE8_AGC_RDY_IBI_EN BIT(0) + +#define INV_ICM42600_REG_OFFSET_USER0 0x4077 +#define INV_ICM42600_REG_OFFSET_USER1 0x4078 +#define INV_ICM42600_REG_OFFSET_USER2 0x4079 +#define INV_ICM42600_REG_OFFSET_USER3 0x407A +#define INV_ICM42600_REG_OFFSET_USER4 0x407B +#define INV_ICM42600_REG_OFFSET_USER5 0x407C +#define INV_ICM42600_REG_OFFSET_USER6 0x407D +#define INV_ICM42600_REG_OFFSET_USER7 0x407E +#define INV_ICM42600_REG_OFFSET_USER8 0x407F + +/* Sleep times required by the driver */ +#define INV_ICM42600_POWER_UP_TIME_MS 100 +#define INV_ICM42600_RESET_TIME_MS 1 +#define INV_ICM42600_ACCEL_STARTUP_TIME_MS 20 +#define INV_ICM42600_GYRO_STARTUP_TIME_MS 60 +#define INV_ICM42600_GYRO_STOP_TIME_MS 150 +#define INV_ICM42600_TEMP_STARTUP_TIME_MS 14 +#define INV_ICM42600_SUSPEND_DELAY_MS 2000 + +typedef int (*inv_icm42600_bus_setup)(struct inv_icm42600_state *); + +extern const struct regmap_config inv_icm42600_regmap_config; +extern const struct dev_pm_ops inv_icm42600_pm_ops; + +const struct iio_mount_matrix * +inv_icm42600_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan); + +uint32_t inv_icm42600_odr_to_period(enum inv_icm42600_odr odr); + +int inv_icm42600_set_accel_conf(struct inv_icm42600_state *st, + struct inv_icm42600_sensor_conf *conf, + unsigned int *sleep_ms); + +int inv_icm42600_set_gyro_conf(struct inv_icm42600_state *st, + struct inv_icm42600_sensor_conf *conf, + unsigned int *sleep_ms); + +int inv_icm42600_set_temp_conf(struct inv_icm42600_state *st, bool enable, + unsigned int *sleep_ms); + +int inv_icm42600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval); + +int inv_icm42600_core_probe(struct regmap *regmap, int chip, int irq, + inv_icm42600_bus_setup bus_setup); + +struct iio_dev *inv_icm42600_gyro_init(struct inv_icm42600_state *st); + +int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev); + +struct iio_dev *inv_icm42600_accel_init(struct inv_icm42600_state *st); + +int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev); + +#endif diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c new file mode 100644 index 000000000000..3441b0d61c5d --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c @@ -0,0 +1,787 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/delay.h> +#include <linux/math64.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/kfifo_buf.h> + +#include "inv_icm42600.h" +#include "inv_icm42600_temp.h" +#include "inv_icm42600_buffer.h" +#include "inv_icm42600_timestamp.h" + +#define INV_ICM42600_ACCEL_CHAN(_modifier, _index, _ext_info) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = _ext_info, \ + } + +enum inv_icm42600_accel_scan { + INV_ICM42600_ACCEL_SCAN_X, + INV_ICM42600_ACCEL_SCAN_Y, + INV_ICM42600_ACCEL_SCAN_Z, + INV_ICM42600_ACCEL_SCAN_TEMP, + INV_ICM42600_ACCEL_SCAN_TIMESTAMP, +}; + +static const struct iio_chan_spec_ext_info inv_icm42600_accel_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm42600_get_mount_matrix), + {}, +}; + +static const struct iio_chan_spec inv_icm42600_accel_channels[] = { + INV_ICM42600_ACCEL_CHAN(IIO_MOD_X, INV_ICM42600_ACCEL_SCAN_X, + inv_icm42600_accel_ext_infos), + INV_ICM42600_ACCEL_CHAN(IIO_MOD_Y, INV_ICM42600_ACCEL_SCAN_Y, + inv_icm42600_accel_ext_infos), + INV_ICM42600_ACCEL_CHAN(IIO_MOD_Z, INV_ICM42600_ACCEL_SCAN_Z, + inv_icm42600_accel_ext_infos), + INV_ICM42600_TEMP_CHAN(INV_ICM42600_ACCEL_SCAN_TEMP), + IIO_CHAN_SOFT_TIMESTAMP(INV_ICM42600_ACCEL_SCAN_TIMESTAMP), +}; + +/* + * IIO buffer data: size must be a power of 2 and timestamp aligned + * 16 bytes: 6 bytes acceleration, 2 bytes temperature, 8 bytes timestamp + */ +struct inv_icm42600_accel_buffer { + struct inv_icm42600_fifo_sensor_data accel; + int16_t temp; + int64_t timestamp __aligned(8); +}; + +#define INV_ICM42600_SCAN_MASK_ACCEL_3AXIS \ + (BIT(INV_ICM42600_ACCEL_SCAN_X) | \ + BIT(INV_ICM42600_ACCEL_SCAN_Y) | \ + BIT(INV_ICM42600_ACCEL_SCAN_Z)) + +#define INV_ICM42600_SCAN_MASK_TEMP BIT(INV_ICM42600_ACCEL_SCAN_TEMP) + +static const unsigned long inv_icm42600_accel_scan_masks[] = { + /* 3-axis accel + temperature */ + INV_ICM42600_SCAN_MASK_ACCEL_3AXIS | INV_ICM42600_SCAN_MASK_TEMP, + 0, +}; + +/* enable accelerometer sensor and FIFO write */ +static int inv_icm42600_accel_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int fifo_en = 0; + unsigned int sleep_temp = 0; + unsigned int sleep_accel = 0; + unsigned int sleep; + int ret; + + mutex_lock(&st->lock); + + if (*scan_mask & INV_ICM42600_SCAN_MASK_TEMP) { + /* enable temp sensor */ + ret = inv_icm42600_set_temp_conf(st, true, &sleep_temp); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42600_SENSOR_TEMP; + } + + if (*scan_mask & INV_ICM42600_SCAN_MASK_ACCEL_3AXIS) { + /* enable accel sensor */ + conf.mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE; + ret = inv_icm42600_set_accel_conf(st, &conf, &sleep_accel); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42600_SENSOR_ACCEL; + } + + /* update data FIFO write */ + inv_icm42600_timestamp_apply_odr(ts, 0, 0, 0); + ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + if (ret) + goto out_unlock; + + ret = inv_icm42600_buffer_update_watermark(st); + +out_unlock: + mutex_unlock(&st->lock); + /* sleep maximum required time */ + if (sleep_accel > sleep_temp) + sleep = sleep_accel; + else + sleep = sleep_temp; + if (sleep) + msleep(sleep); + return ret; +} + +static int inv_icm42600_accel_read_sensor(struct inv_icm42600_state *st, + struct iio_chan_spec const *chan, + int16_t *val) +{ + struct device *dev = regmap_get_device(st->map); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int reg; + __be16 *data; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_ACCEL_DATA_X; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_ACCEL_DATA_Y; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_ACCEL_DATA_Z; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + /* enable accel sensor */ + conf.mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE; + ret = inv_icm42600_set_accel_conf(st, &conf, NULL); + if (ret) + goto exit; + + /* read accel register data */ + data = (__be16 *)&st->buffer[0]; + ret = regmap_bulk_read(st->map, reg, data, sizeof(*data)); + if (ret) + goto exit; + + *val = (int16_t)be16_to_cpup(data); + if (*val == INV_ICM42600_DATA_INVALID) + ret = -EINVAL; +exit: + mutex_unlock(&st->lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +/* IIO format int + nano */ +static const int inv_icm42600_accel_scale[] = { + /* +/- 16G => 0.004788403 m/s-2 */ + [2 * INV_ICM42600_ACCEL_FS_16G] = 0, + [2 * INV_ICM42600_ACCEL_FS_16G + 1] = 4788403, + /* +/- 8G => 0.002394202 m/s-2 */ + [2 * INV_ICM42600_ACCEL_FS_8G] = 0, + [2 * INV_ICM42600_ACCEL_FS_8G + 1] = 2394202, + /* +/- 4G => 0.001197101 m/s-2 */ + [2 * INV_ICM42600_ACCEL_FS_4G] = 0, + [2 * INV_ICM42600_ACCEL_FS_4G + 1] = 1197101, + /* +/- 2G => 0.000598550 m/s-2 */ + [2 * INV_ICM42600_ACCEL_FS_2G] = 0, + [2 * INV_ICM42600_ACCEL_FS_2G + 1] = 598550, +}; + +static int inv_icm42600_accel_read_scale(struct inv_icm42600_state *st, + int *val, int *val2) +{ + unsigned int idx; + + idx = st->conf.accel.fs; + + *val = inv_icm42600_accel_scale[2 * idx]; + *val2 = inv_icm42600_accel_scale[2 * idx + 1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm42600_accel_write_scale(struct inv_icm42600_state *st, + int val, int val2) +{ + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_accel_scale); idx += 2) { + if (val == inv_icm42600_accel_scale[idx] && + val2 == inv_icm42600_accel_scale[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm42600_accel_scale)) + return -EINVAL; + + conf.fs = idx / 2; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = inv_icm42600_set_accel_conf(st, &conf, NULL); + + mutex_unlock(&st->lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + micro */ +static const int inv_icm42600_accel_odr[] = { + /* 12.5Hz */ + 12, 500000, + /* 25Hz */ + 25, 0, + /* 50Hz */ + 50, 0, + /* 100Hz */ + 100, 0, + /* 200Hz */ + 200, 0, + /* 1kHz */ + 1000, 0, + /* 2kHz */ + 2000, 0, + /* 4kHz */ + 4000, 0, +}; + +static const int inv_icm42600_accel_odr_conv[] = { + INV_ICM42600_ODR_12_5HZ, + INV_ICM42600_ODR_25HZ, + INV_ICM42600_ODR_50HZ, + INV_ICM42600_ODR_100HZ, + INV_ICM42600_ODR_200HZ, + INV_ICM42600_ODR_1KHZ_LN, + INV_ICM42600_ODR_2KHZ_LN, + INV_ICM42600_ODR_4KHZ_LN, +}; + +static int inv_icm42600_accel_read_odr(struct inv_icm42600_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + odr = st->conf.accel.odr; + + for (i = 0; i < ARRAY_SIZE(inv_icm42600_accel_odr_conv); ++i) { + if (inv_icm42600_accel_odr_conv[i] == odr) + break; + } + if (i >= ARRAY_SIZE(inv_icm42600_accel_odr_conv)) + return -EINVAL; + + *val = inv_icm42600_accel_odr[2 * i]; + *val2 = inv_icm42600_accel_odr[2 * i + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm42600_accel_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_accel_odr); idx += 2) { + if (val == inv_icm42600_accel_odr[idx] && + val2 == inv_icm42600_accel_odr[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm42600_accel_odr)) + return -EINVAL; + + conf.odr = inv_icm42600_accel_odr_conv[idx / 2]; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = inv_icm42600_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr), + iio_buffer_enabled(indio_dev)); + if (ret) + goto out_unlock; + + ret = inv_icm42600_set_accel_conf(st, &conf, NULL); + if (ret) + goto out_unlock; + inv_icm42600_buffer_update_fifo_period(st); + inv_icm42600_buffer_update_watermark(st); + +out_unlock: + mutex_unlock(&st->lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* + * Calibration bias values, IIO range format int + micro. + * Value is limited to +/-1g coded on 12 bits signed. Step is 0.5mg. + */ +static int inv_icm42600_accel_calibbias[] = { + -10, 42010, /* min: -10.042010 m/s² */ + 0, 4903, /* step: 0.004903 m/s² */ + 10, 37106, /* max: 10.037106 m/s² */ +}; + +static int inv_icm42600_accel_read_offset(struct inv_icm42600_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct device *dev = regmap_get_device(st->map); + int64_t val64; + int32_t bias; + unsigned int reg; + int16_t offset; + uint8_t data[2]; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_OFFSET_USER4; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_OFFSET_USER6; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_OFFSET_USER7; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = regmap_bulk_read(st->map, reg, st->buffer, sizeof(data)); + memcpy(data, st->buffer, sizeof(data)); + + mutex_unlock(&st->lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + if (ret) + return ret; + + /* 12 bits signed value */ + switch (chan->channel2) { + case IIO_MOD_X: + offset = sign_extend32(((data[0] & 0xF0) << 4) | data[1], 11); + break; + case IIO_MOD_Y: + offset = sign_extend32(((data[1] & 0x0F) << 8) | data[0], 11); + break; + case IIO_MOD_Z: + offset = sign_extend32(((data[0] & 0xF0) << 4) | data[1], 11); + break; + default: + return -EINVAL; + } + + /* + * convert raw offset to g then to m/s² + * 12 bits signed raw step 0.5mg to g: 5 / 10000 + * g to m/s²: 9.806650 + * result in micro (1000000) + * (offset * 5 * 9.806650 * 1000000) / 10000 + */ + val64 = (int64_t)offset * 5LL * 9806650LL; + /* for rounding, add + or - divisor (10000) divided by 2 */ + if (val64 >= 0) + val64 += 10000LL / 2LL; + else + val64 -= 10000LL / 2LL; + bias = div_s64(val64, 10000L); + *val = bias / 1000000L; + *val2 = bias % 1000000L; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm42600_accel_write_offset(struct inv_icm42600_state *st, + struct iio_chan_spec const *chan, + int val, int val2) +{ + struct device *dev = regmap_get_device(st->map); + int64_t val64; + int32_t min, max; + unsigned int reg, regval; + int16_t offset; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_OFFSET_USER4; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_OFFSET_USER6; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_OFFSET_USER7; + break; + default: + return -EINVAL; + } + + /* inv_icm42600_accel_calibbias: min - step - max in micro */ + min = inv_icm42600_accel_calibbias[0] * 1000000L + + inv_icm42600_accel_calibbias[1]; + max = inv_icm42600_accel_calibbias[4] * 1000000L + + inv_icm42600_accel_calibbias[5]; + val64 = (int64_t)val * 1000000LL + (int64_t)val2; + if (val64 < min || val64 > max) + return -EINVAL; + + /* + * convert m/s² to g then to raw value + * m/s² to g: 1 / 9.806650 + * g to raw 12 bits signed, step 0.5mg: 10000 / 5 + * val in micro (1000000) + * val * 10000 / (9.806650 * 1000000 * 5) + */ + val64 = val64 * 10000LL; + /* for rounding, add + or - divisor (9806650 * 5) divided by 2 */ + if (val64 >= 0) + val64 += 9806650 * 5 / 2; + else + val64 -= 9806650 * 5 / 2; + offset = div_s64(val64, 9806650 * 5); + + /* clamp value limited to 12 bits signed */ + if (offset < -2048) + offset = -2048; + else if (offset > 2047) + offset = 2047; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + switch (chan->channel2) { + case IIO_MOD_X: + /* OFFSET_USER4 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER4, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = ((offset & 0xF00) >> 4) | (regval & 0x0F); + st->buffer[1] = offset & 0xFF; + break; + case IIO_MOD_Y: + /* OFFSET_USER7 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER7, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = offset & 0xFF; + st->buffer[1] = ((offset & 0xF00) >> 8) | (regval & 0xF0); + break; + case IIO_MOD_Z: + /* OFFSET_USER7 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER7, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = ((offset & 0xF00) >> 4) | (regval & 0x0F); + st->buffer[1] = offset & 0xFF; + break; + default: + ret = -EINVAL; + goto out_unlock; + } + + ret = regmap_bulk_write(st->map, reg, st->buffer, 2); + +out_unlock: + mutex_unlock(&st->lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int inv_icm42600_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int16_t data; + int ret; + + switch (chan->type) { + case IIO_ACCEL: + break; + case IIO_TEMP: + return inv_icm42600_temp_read_raw(indio_dev, chan, val, val2, mask); + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = inv_icm42600_accel_read_sensor(st, chan, &data); + iio_device_release_direct_mode(indio_dev); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm42600_accel_read_scale(st, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42600_accel_read_odr(st, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return inv_icm42600_accel_read_offset(st, chan, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42600_accel_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = inv_icm42600_accel_scale; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(inv_icm42600_accel_scale); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = inv_icm42600_accel_odr; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(inv_icm42600_accel_odr); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_CALIBBIAS: + *vals = inv_icm42600_accel_calibbias; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int inv_icm42600_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = inv_icm42600_accel_write_scale(st, val, val2); + iio_device_release_direct_mode(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42600_accel_write_odr(indio_dev, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = inv_icm42600_accel_write_offset(st, chan, val, val2); + iio_device_release_direct_mode(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int inv_icm42600_accel_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int inv_icm42600_accel_hwfifo_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + mutex_lock(&st->lock); + + st->fifo.watermark.accel = val; + ret = inv_icm42600_buffer_update_watermark(st); + + mutex_unlock(&st->lock); + + return ret; +} + +static int inv_icm42600_accel_hwfifo_flush(struct iio_dev *indio_dev, + unsigned int count) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (count == 0) + return 0; + + mutex_lock(&st->lock); + + ret = inv_icm42600_buffer_hwfifo_flush(st, count); + if (!ret) + ret = st->fifo.nb.accel; + + mutex_unlock(&st->lock); + + return ret; +} + +static const struct iio_info inv_icm42600_accel_info = { + .read_raw = inv_icm42600_accel_read_raw, + .read_avail = inv_icm42600_accel_read_avail, + .write_raw = inv_icm42600_accel_write_raw, + .write_raw_get_fmt = inv_icm42600_accel_write_raw_get_fmt, + .debugfs_reg_access = inv_icm42600_debugfs_reg, + .update_scan_mode = inv_icm42600_accel_update_scan_mode, + .hwfifo_set_watermark = inv_icm42600_accel_hwfifo_set_watermark, + .hwfifo_flush_to_buffer = inv_icm42600_accel_hwfifo_flush, +}; + +struct iio_dev *inv_icm42600_accel_init(struct inv_icm42600_state *st) +{ + struct device *dev = regmap_get_device(st->map); + const char *name; + struct inv_icm42600_timestamp *ts; + struct iio_dev *indio_dev; + struct iio_buffer *buffer; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-accel", st->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*ts)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + + buffer = devm_iio_kfifo_allocate(dev); + if (!buffer) + return ERR_PTR(-ENOMEM); + + ts = iio_priv(indio_dev); + inv_icm42600_timestamp_init(ts, inv_icm42600_odr_to_period(st->conf.accel.odr)); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; + indio_dev->info = &inv_icm42600_accel_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->channels = inv_icm42600_accel_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm42600_accel_channels); + indio_dev->available_scan_masks = inv_icm42600_accel_scan_masks; + indio_dev->setup_ops = &inv_icm42600_buffer_ops; + + iio_device_attach_buffer(indio_dev, buffer); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} + +int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); + ssize_t i, size; + unsigned int no; + const void *accel, *gyro, *timestamp; + const int8_t *temp; + unsigned int odr; + int64_t ts_val; + struct inv_icm42600_accel_buffer buffer; + + /* parse all fifo packets */ + for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) { + size = inv_icm42600_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + /* quit if error or FIFO is empty */ + if (size <= 0) + return size; + + /* skip packet if no accel data or data is invalid */ + if (accel == NULL || !inv_icm42600_fifo_is_data_valid(accel)) + continue; + + /* update odr */ + if (odr & INV_ICM42600_SENSOR_ACCEL) + inv_icm42600_timestamp_apply_odr(ts, st->fifo.period, + st->fifo.nb.total, no); + + /* buffer is copied to userspace, zeroing it to avoid any data leak */ + memset(&buffer, 0, sizeof(buffer)); + memcpy(&buffer.accel, accel, sizeof(buffer.accel)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; + ts_val = inv_icm42600_timestamp_pop(ts); + iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val); + } + + return 0; +} diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c new file mode 100644 index 000000000000..99576b2c171f --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> + +#include "inv_icm42600.h" +#include "inv_icm42600_timestamp.h" +#include "inv_icm42600_buffer.h" + +/* FIFO header: 1 byte */ +#define INV_ICM42600_FIFO_HEADER_MSG BIT(7) +#define INV_ICM42600_FIFO_HEADER_ACCEL BIT(6) +#define INV_ICM42600_FIFO_HEADER_GYRO BIT(5) +#define INV_ICM42600_FIFO_HEADER_TMST_FSYNC GENMASK(3, 2) +#define INV_ICM42600_FIFO_HEADER_ODR_ACCEL BIT(1) +#define INV_ICM42600_FIFO_HEADER_ODR_GYRO BIT(0) + +struct inv_icm42600_fifo_1sensor_packet { + uint8_t header; + struct inv_icm42600_fifo_sensor_data data; + int8_t temp; +} __packed; +#define INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE 8 + +struct inv_icm42600_fifo_2sensors_packet { + uint8_t header; + struct inv_icm42600_fifo_sensor_data accel; + struct inv_icm42600_fifo_sensor_data gyro; + int8_t temp; + __be16 timestamp; +} __packed; +#define INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE 16 + +ssize_t inv_icm42600_fifo_decode_packet(const void *packet, const void **accel, + const void **gyro, const int8_t **temp, + const void **timestamp, unsigned int *odr) +{ + const struct inv_icm42600_fifo_1sensor_packet *pack1 = packet; + const struct inv_icm42600_fifo_2sensors_packet *pack2 = packet; + uint8_t header = *((const uint8_t *)packet); + + /* FIFO empty */ + if (header & INV_ICM42600_FIFO_HEADER_MSG) { + *accel = NULL; + *gyro = NULL; + *temp = NULL; + *timestamp = NULL; + *odr = 0; + return 0; + } + + /* handle odr flags */ + *odr = 0; + if (header & INV_ICM42600_FIFO_HEADER_ODR_GYRO) + *odr |= INV_ICM42600_SENSOR_GYRO; + if (header & INV_ICM42600_FIFO_HEADER_ODR_ACCEL) + *odr |= INV_ICM42600_SENSOR_ACCEL; + + /* accel + gyro */ + if ((header & INV_ICM42600_FIFO_HEADER_ACCEL) && + (header & INV_ICM42600_FIFO_HEADER_GYRO)) { + *accel = &pack2->accel; + *gyro = &pack2->gyro; + *temp = &pack2->temp; + *timestamp = &pack2->timestamp; + return INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; + } + + /* accel only */ + if (header & INV_ICM42600_FIFO_HEADER_ACCEL) { + *accel = &pack1->data; + *gyro = NULL; + *temp = &pack1->temp; + *timestamp = NULL; + return INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE; + } + + /* gyro only */ + if (header & INV_ICM42600_FIFO_HEADER_GYRO) { + *accel = NULL; + *gyro = &pack1->data; + *temp = &pack1->temp; + *timestamp = NULL; + return INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE; + } + + /* invalid packet if here */ + return -EINVAL; +} + +void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st) +{ + uint32_t period_gyro, period_accel, period; + + if (st->fifo.en & INV_ICM42600_SENSOR_GYRO) + period_gyro = inv_icm42600_odr_to_period(st->conf.gyro.odr); + else + period_gyro = U32_MAX; + + if (st->fifo.en & INV_ICM42600_SENSOR_ACCEL) + period_accel = inv_icm42600_odr_to_period(st->conf.accel.odr); + else + period_accel = U32_MAX; + + if (period_gyro <= period_accel) + period = period_gyro; + else + period = period_accel; + + st->fifo.period = period; +} + +int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st, + unsigned int fifo_en) +{ + unsigned int mask, val; + int ret; + + /* update only FIFO EN bits */ + mask = INV_ICM42600_FIFO_CONFIG1_TMST_FSYNC_EN | + INV_ICM42600_FIFO_CONFIG1_TEMP_EN | + INV_ICM42600_FIFO_CONFIG1_GYRO_EN | + INV_ICM42600_FIFO_CONFIG1_ACCEL_EN; + + val = 0; + if (fifo_en & INV_ICM42600_SENSOR_GYRO) + val |= INV_ICM42600_FIFO_CONFIG1_GYRO_EN; + if (fifo_en & INV_ICM42600_SENSOR_ACCEL) + val |= INV_ICM42600_FIFO_CONFIG1_ACCEL_EN; + if (fifo_en & INV_ICM42600_SENSOR_TEMP) + val |= INV_ICM42600_FIFO_CONFIG1_TEMP_EN; + + ret = regmap_update_bits(st->map, INV_ICM42600_REG_FIFO_CONFIG1, mask, val); + if (ret) + return ret; + + st->fifo.en = fifo_en; + inv_icm42600_buffer_update_fifo_period(st); + + return 0; +} + +static size_t inv_icm42600_get_packet_size(unsigned int fifo_en) +{ + size_t packet_size; + + if ((fifo_en & INV_ICM42600_SENSOR_GYRO) && + (fifo_en & INV_ICM42600_SENSOR_ACCEL)) + packet_size = INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; + else + packet_size = INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE; + + return packet_size; +} + +static unsigned int inv_icm42600_wm_truncate(unsigned int watermark, + size_t packet_size) +{ + size_t wm_size; + unsigned int wm; + + wm_size = watermark * packet_size; + if (wm_size > INV_ICM42600_FIFO_WATERMARK_MAX) + wm_size = INV_ICM42600_FIFO_WATERMARK_MAX; + + wm = wm_size / packet_size; + + return wm; +} + +/** + * inv_icm42600_buffer_update_watermark - update watermark FIFO threshold + * @st: driver internal state + * + * Returns 0 on success, a negative error code otherwise. + * + * FIFO watermark threshold is computed based on the required watermark values + * set for gyro and accel sensors. Since watermark is all about acceptable data + * latency, use the smallest setting between the 2. It means choosing the + * smallest latency but this is not as simple as choosing the smallest watermark + * value. Latency depends on watermark and ODR. It requires several steps: + * 1) compute gyro and accel latencies and choose the smallest value. + * 2) adapt the choosen latency so that it is a multiple of both gyro and accel + * ones. Otherwise it is possible that you don't meet a requirement. (for + * example with gyro @100Hz wm 4 and accel @100Hz with wm 6, choosing the + * value of 4 will not meet accel latency requirement because 6 is not a + * multiple of 4. You need to use the value 2.) + * 3) Since all periods are multiple of each others, watermark is computed by + * dividing this computed latency by the smallest period, which corresponds + * to the FIFO frequency. Beware that this is only true because we are not + * using 500Hz frequency which is not a multiple of the others. + */ +int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st) +{ + size_t packet_size, wm_size; + unsigned int wm_gyro, wm_accel, watermark; + uint32_t period_gyro, period_accel, period; + uint32_t latency_gyro, latency_accel, latency; + bool restore; + __le16 raw_wm; + int ret; + + packet_size = inv_icm42600_get_packet_size(st->fifo.en); + + /* compute sensors latency, depending on sensor watermark and odr */ + wm_gyro = inv_icm42600_wm_truncate(st->fifo.watermark.gyro, packet_size); + wm_accel = inv_icm42600_wm_truncate(st->fifo.watermark.accel, packet_size); + /* use us for odr to avoid overflow using 32 bits values */ + period_gyro = inv_icm42600_odr_to_period(st->conf.gyro.odr) / 1000UL; + period_accel = inv_icm42600_odr_to_period(st->conf.accel.odr) / 1000UL; + latency_gyro = period_gyro * wm_gyro; + latency_accel = period_accel * wm_accel; + + /* 0 value for watermark means that the sensor is turned off */ + if (latency_gyro == 0) { + watermark = wm_accel; + } else if (latency_accel == 0) { + watermark = wm_gyro; + } else { + /* compute the smallest latency that is a multiple of both */ + if (latency_gyro <= latency_accel) + latency = latency_gyro - (latency_accel % latency_gyro); + else + latency = latency_accel - (latency_gyro % latency_accel); + /* use the shortest period */ + if (period_gyro <= period_accel) + period = period_gyro; + else + period = period_accel; + /* all this works because periods are multiple of each others */ + watermark = latency / period; + if (watermark < 1) + watermark = 1; + } + + /* compute watermark value in bytes */ + wm_size = watermark * packet_size; + + /* changing FIFO watermark requires to turn off watermark interrupt */ + ret = regmap_update_bits_check(st->map, INV_ICM42600_REG_INT_SOURCE0, + INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN, + 0, &restore); + if (ret) + return ret; + + raw_wm = INV_ICM42600_FIFO_WATERMARK_VAL(wm_size); + memcpy(st->buffer, &raw_wm, sizeof(raw_wm)); + ret = regmap_bulk_write(st->map, INV_ICM42600_REG_FIFO_WATERMARK, + st->buffer, sizeof(raw_wm)); + if (ret) + return ret; + + /* restore watermark interrupt */ + if (restore) { + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INT_SOURCE0, + INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN, + INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN); + if (ret) + return ret; + } + + return 0; +} + +static int inv_icm42600_buffer_preenable(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + + pm_runtime_get_sync(dev); + + return 0; +} + +/* + * update_scan_mode callback is turning sensors on and setting data FIFO enable + * bits. + */ +static int inv_icm42600_buffer_postenable(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + mutex_lock(&st->lock); + + /* exit if FIFO is already on */ + if (st->fifo.on) { + ret = 0; + goto out_on; + } + + /* set FIFO threshold interrupt */ + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INT_SOURCE0, + INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN, + INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN); + if (ret) + goto out_unlock; + + /* flush FIFO data */ + ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET, + INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH); + if (ret) + goto out_unlock; + + /* set FIFO in streaming mode */ + ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, + INV_ICM42600_FIFO_CONFIG_STREAM); + if (ret) + goto out_unlock; + + /* workaround: first read of FIFO count after reset is always 0 */ + ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, st->buffer, 2); + if (ret) + goto out_unlock; + +out_on: + /* increase FIFO on counter */ + st->fifo.on++; +out_unlock: + mutex_unlock(&st->lock); + return ret; +} + +static int inv_icm42600_buffer_predisable(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + mutex_lock(&st->lock); + + /* exit if there are several sensors using the FIFO */ + if (st->fifo.on > 1) { + ret = 0; + goto out_off; + } + + /* set FIFO in bypass mode */ + ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, + INV_ICM42600_FIFO_CONFIG_BYPASS); + if (ret) + goto out_unlock; + + /* flush FIFO data */ + ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET, + INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH); + if (ret) + goto out_unlock; + + /* disable FIFO threshold interrupt */ + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INT_SOURCE0, + INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN, 0); + if (ret) + goto out_unlock; + +out_off: + /* decrease FIFO on counter */ + st->fifo.on--; +out_unlock: + mutex_unlock(&st->lock); + return ret; +} + +static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int sensor; + unsigned int *watermark; + struct inv_icm42600_timestamp *ts; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int sleep_temp = 0; + unsigned int sleep_sensor = 0; + unsigned int sleep; + int ret; + + if (indio_dev == st->indio_gyro) { + sensor = INV_ICM42600_SENSOR_GYRO; + watermark = &st->fifo.watermark.gyro; + ts = iio_priv(st->indio_gyro); + } else if (indio_dev == st->indio_accel) { + sensor = INV_ICM42600_SENSOR_ACCEL; + watermark = &st->fifo.watermark.accel; + ts = iio_priv(st->indio_accel); + } else { + return -EINVAL; + } + + mutex_lock(&st->lock); + + ret = inv_icm42600_buffer_set_fifo_en(st, st->fifo.en & ~sensor); + if (ret) + goto out_unlock; + + *watermark = 0; + ret = inv_icm42600_buffer_update_watermark(st); + if (ret) + goto out_unlock; + + conf.mode = INV_ICM42600_SENSOR_MODE_OFF; + if (sensor == INV_ICM42600_SENSOR_GYRO) + ret = inv_icm42600_set_gyro_conf(st, &conf, &sleep_sensor); + else + ret = inv_icm42600_set_accel_conf(st, &conf, &sleep_sensor); + if (ret) + goto out_unlock; + + /* if FIFO is off, turn temperature off */ + if (!st->fifo.on) + ret = inv_icm42600_set_temp_conf(st, false, &sleep_temp); + + inv_icm42600_timestamp_reset(ts); + +out_unlock: + mutex_unlock(&st->lock); + + /* sleep maximum required time */ + if (sleep_sensor > sleep_temp) + sleep = sleep_sensor; + else + sleep = sleep_temp; + if (sleep) + msleep(sleep); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +const struct iio_buffer_setup_ops inv_icm42600_buffer_ops = { + .preenable = inv_icm42600_buffer_preenable, + .postenable = inv_icm42600_buffer_postenable, + .predisable = inv_icm42600_buffer_predisable, + .postdisable = inv_icm42600_buffer_postdisable, +}; + +int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st, + unsigned int max) +{ + size_t max_count; + __be16 *raw_fifo_count; + ssize_t i, size; + const void *accel, *gyro, *timestamp; + const int8_t *temp; + unsigned int odr; + int ret; + + /* reset all samples counters */ + st->fifo.count = 0; + st->fifo.nb.gyro = 0; + st->fifo.nb.accel = 0; + st->fifo.nb.total = 0; + + /* compute maximum FIFO read size */ + if (max == 0) + max_count = sizeof(st->fifo.data); + else + max_count = max * inv_icm42600_get_packet_size(st->fifo.en); + + /* read FIFO count value */ + raw_fifo_count = (__be16 *)st->buffer; + ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, + raw_fifo_count, sizeof(*raw_fifo_count)); + if (ret) + return ret; + st->fifo.count = be16_to_cpup(raw_fifo_count); + + /* check and clamp FIFO count value */ + if (st->fifo.count == 0) + return 0; + if (st->fifo.count > max_count) + st->fifo.count = max_count; + + /* read all FIFO data in internal buffer */ + ret = regmap_noinc_read(st->map, INV_ICM42600_REG_FIFO_DATA, + st->fifo.data, st->fifo.count); + if (ret) + return ret; + + /* compute number of samples for each sensor */ + for (i = 0; i < st->fifo.count; i += size) { + size = inv_icm42600_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + if (size <= 0) + break; + if (gyro != NULL && inv_icm42600_fifo_is_data_valid(gyro)) + st->fifo.nb.gyro++; + if (accel != NULL && inv_icm42600_fifo_is_data_valid(accel)) + st->fifo.nb.accel++; + st->fifo.nb.total++; + } + + return 0; +} + +int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st) +{ + struct inv_icm42600_timestamp *ts; + int ret; + + if (st->fifo.nb.total == 0) + return 0; + + /* handle gyroscope timestamp and FIFO data parsing */ + ts = iio_priv(st->indio_gyro); + inv_icm42600_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, + st->fifo.nb.gyro, st->timestamp.gyro); + if (st->fifo.nb.gyro > 0) { + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + + /* handle accelerometer timestamp and FIFO data parsing */ + ts = iio_priv(st->indio_accel); + inv_icm42600_timestamp_interrupt(ts, st->fifo.period, st->fifo.nb.total, + st->fifo.nb.accel, st->timestamp.accel); + if (st->fifo.nb.accel > 0) { + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; + } + + return 0; +} + +int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st, + unsigned int count) +{ + struct inv_icm42600_timestamp *ts; + int64_t gyro_ts, accel_ts; + int ret; + + gyro_ts = iio_get_time_ns(st->indio_gyro); + accel_ts = iio_get_time_ns(st->indio_accel); + + ret = inv_icm42600_buffer_fifo_read(st, count); + if (ret) + return ret; + + if (st->fifo.nb.total == 0) + return 0; + + if (st->fifo.nb.gyro > 0) { + ts = iio_priv(st->indio_gyro); + inv_icm42600_timestamp_interrupt(ts, st->fifo.period, + st->fifo.nb.total, st->fifo.nb.gyro, + gyro_ts); + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + + if (st->fifo.nb.accel > 0) { + ts = iio_priv(st->indio_accel); + inv_icm42600_timestamp_interrupt(ts, st->fifo.period, + st->fifo.nb.total, st->fifo.nb.accel, + accel_ts); + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; + } + + return 0; +} + +int inv_icm42600_buffer_init(struct inv_icm42600_state *st) +{ + unsigned int val; + int ret; + + /* + * Default FIFO configuration (bits 7 to 5) + * - use invalid value + * - FIFO count in bytes + * - FIFO count in big endian + */ + val = INV_ICM42600_INTF_CONFIG0_FIFO_COUNT_ENDIAN; + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG0, + GENMASK(7, 5), val); + if (ret) + return ret; + + /* + * Enable FIFO partial read and continuous watermark interrupt. + * Disable all FIFO EN bits. + */ + val = INV_ICM42600_FIFO_CONFIG1_RESUME_PARTIAL_RD | + INV_ICM42600_FIFO_CONFIG1_WM_GT_TH; + return regmap_update_bits(st->map, INV_ICM42600_REG_FIFO_CONFIG1, + GENMASK(6, 5) | GENMASK(3, 0), val); +} diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h new file mode 100644 index 000000000000..de2a3949dcc7 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#ifndef INV_ICM42600_BUFFER_H_ +#define INV_ICM42600_BUFFER_H_ + +#include <linux/kernel.h> +#include <linux/bits.h> + +struct inv_icm42600_state; + +#define INV_ICM42600_SENSOR_GYRO BIT(0) +#define INV_ICM42600_SENSOR_ACCEL BIT(1) +#define INV_ICM42600_SENSOR_TEMP BIT(2) + +/** + * struct inv_icm42600_fifo - FIFO state variables + * @on: reference counter for FIFO on. + * @en: bits field of INV_ICM42600_SENSOR_* for FIFO EN bits. + * @period: FIFO internal period. + * @watermark: watermark configuration values for accel and gyro. + * @count: number of bytes in the FIFO data buffer. + * @nb: gyro, accel and total samples in the FIFO data buffer. + * @data: FIFO data buffer aligned for DMA (2kB + 32 bytes of read cache). + */ +struct inv_icm42600_fifo { + unsigned int on; + unsigned int en; + uint32_t period; + struct { + unsigned int gyro; + unsigned int accel; + } watermark; + size_t count; + struct { + size_t gyro; + size_t accel; + size_t total; + } nb; + uint8_t data[2080] ____cacheline_aligned; +}; + +/* FIFO data packet */ +struct inv_icm42600_fifo_sensor_data { + __be16 x; + __be16 y; + __be16 z; +} __packed; +#define INV_ICM42600_FIFO_DATA_INVALID -32768 + +static inline int16_t inv_icm42600_fifo_get_sensor_data(__be16 d) +{ + return be16_to_cpu(d); +} + +static inline bool +inv_icm42600_fifo_is_data_valid(const struct inv_icm42600_fifo_sensor_data *s) +{ + int16_t x, y, z; + + x = inv_icm42600_fifo_get_sensor_data(s->x); + y = inv_icm42600_fifo_get_sensor_data(s->y); + z = inv_icm42600_fifo_get_sensor_data(s->z); + + if (x == INV_ICM42600_FIFO_DATA_INVALID && + y == INV_ICM42600_FIFO_DATA_INVALID && + z == INV_ICM42600_FIFO_DATA_INVALID) + return false; + + return true; +} + +ssize_t inv_icm42600_fifo_decode_packet(const void *packet, const void **accel, + const void **gyro, const int8_t **temp, + const void **timestamp, unsigned int *odr); + +extern const struct iio_buffer_setup_ops inv_icm42600_buffer_ops; + +int inv_icm42600_buffer_init(struct inv_icm42600_state *st); + +void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st); + +int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st, + unsigned int fifo_en); + +int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st); + +int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st, + unsigned int max); + +int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st); + +int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st, + unsigned int count); + +#endif diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c new file mode 100644 index 000000000000..8bd77185ccb7 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c @@ -0,0 +1,786 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> + +#include "inv_icm42600.h" +#include "inv_icm42600_buffer.h" +#include "inv_icm42600_timestamp.h" + +static const struct regmap_range_cfg inv_icm42600_regmap_ranges[] = { + { + .name = "user banks", + .range_min = 0x0000, + .range_max = 0x4FFF, + .selector_reg = INV_ICM42600_REG_BANK_SEL, + .selector_mask = INV_ICM42600_BANK_SEL_MASK, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x1000, + }, +}; + +const struct regmap_config inv_icm42600_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x4FFF, + .ranges = inv_icm42600_regmap_ranges, + .num_ranges = ARRAY_SIZE(inv_icm42600_regmap_ranges), +}; +EXPORT_SYMBOL_GPL(inv_icm42600_regmap_config); + +struct inv_icm42600_hw { + uint8_t whoami; + const char *name; + const struct inv_icm42600_conf *conf; +}; + +/* chip initial default configuration */ +static const struct inv_icm42600_conf inv_icm42600_default_conf = { + .gyro = { + .mode = INV_ICM42600_SENSOR_MODE_OFF, + .fs = INV_ICM42600_GYRO_FS_2000DPS, + .odr = INV_ICM42600_ODR_50HZ, + .filter = INV_ICM42600_FILTER_BW_ODR_DIV_2, + }, + .accel = { + .mode = INV_ICM42600_SENSOR_MODE_OFF, + .fs = INV_ICM42600_ACCEL_FS_16G, + .odr = INV_ICM42600_ODR_50HZ, + .filter = INV_ICM42600_FILTER_BW_ODR_DIV_2, + }, + .temp_en = false, +}; + +static const struct inv_icm42600_hw inv_icm42600_hw[INV_CHIP_NB] = { + [INV_CHIP_ICM42600] = { + .whoami = INV_ICM42600_WHOAMI_ICM42600, + .name = "icm42600", + .conf = &inv_icm42600_default_conf, + }, + [INV_CHIP_ICM42602] = { + .whoami = INV_ICM42600_WHOAMI_ICM42602, + .name = "icm42602", + .conf = &inv_icm42600_default_conf, + }, + [INV_CHIP_ICM42605] = { + .whoami = INV_ICM42600_WHOAMI_ICM42605, + .name = "icm42605", + .conf = &inv_icm42600_default_conf, + }, + [INV_CHIP_ICM42622] = { + .whoami = INV_ICM42600_WHOAMI_ICM42622, + .name = "icm42622", + .conf = &inv_icm42600_default_conf, + }, +}; + +const struct iio_mount_matrix * +inv_icm42600_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + + return &st->orientation; +} + +uint32_t inv_icm42600_odr_to_period(enum inv_icm42600_odr odr) +{ + static uint32_t odr_periods[INV_ICM42600_ODR_NB] = { + /* reserved values */ + 0, 0, 0, + /* 8kHz */ + 125000, + /* 4kHz */ + 250000, + /* 2kHz */ + 500000, + /* 1kHz */ + 1000000, + /* 200Hz */ + 5000000, + /* 100Hz */ + 10000000, + /* 50Hz */ + 20000000, + /* 25Hz */ + 40000000, + /* 12.5Hz */ + 80000000, + /* 6.25Hz */ + 160000000, + /* 3.125Hz */ + 320000000, + /* 1.5625Hz */ + 640000000, + /* 500Hz */ + 2000000, + }; + + return odr_periods[odr]; +} + +static int inv_icm42600_set_pwr_mgmt0(struct inv_icm42600_state *st, + enum inv_icm42600_sensor_mode gyro, + enum inv_icm42600_sensor_mode accel, + bool temp, unsigned int *sleep_ms) +{ + enum inv_icm42600_sensor_mode oldgyro = st->conf.gyro.mode; + enum inv_icm42600_sensor_mode oldaccel = st->conf.accel.mode; + bool oldtemp = st->conf.temp_en; + unsigned int sleepval; + unsigned int val; + int ret; + + /* if nothing changed, exit */ + if (gyro == oldgyro && accel == oldaccel && temp == oldtemp) + return 0; + + val = INV_ICM42600_PWR_MGMT0_GYRO(gyro) | + INV_ICM42600_PWR_MGMT0_ACCEL(accel); + if (!temp) + val |= INV_ICM42600_PWR_MGMT0_TEMP_DIS; + ret = regmap_write(st->map, INV_ICM42600_REG_PWR_MGMT0, val); + if (ret) + return ret; + + st->conf.gyro.mode = gyro; + st->conf.accel.mode = accel; + st->conf.temp_en = temp; + + /* compute required wait time for sensors to stabilize */ + sleepval = 0; + /* temperature stabilization time */ + if (temp && !oldtemp) { + if (sleepval < INV_ICM42600_TEMP_STARTUP_TIME_MS) + sleepval = INV_ICM42600_TEMP_STARTUP_TIME_MS; + } + /* accel startup time */ + if (accel != oldaccel && oldaccel == INV_ICM42600_SENSOR_MODE_OFF) { + /* block any register write for at least 200 µs */ + usleep_range(200, 300); + if (sleepval < INV_ICM42600_ACCEL_STARTUP_TIME_MS) + sleepval = INV_ICM42600_ACCEL_STARTUP_TIME_MS; + } + if (gyro != oldgyro) { + /* gyro startup time */ + if (oldgyro == INV_ICM42600_SENSOR_MODE_OFF) { + /* block any register write for at least 200 µs */ + usleep_range(200, 300); + if (sleepval < INV_ICM42600_GYRO_STARTUP_TIME_MS) + sleepval = INV_ICM42600_GYRO_STARTUP_TIME_MS; + /* gyro stop time */ + } else if (gyro == INV_ICM42600_SENSOR_MODE_OFF) { + if (sleepval < INV_ICM42600_GYRO_STOP_TIME_MS) + sleepval = INV_ICM42600_GYRO_STOP_TIME_MS; + } + } + + /* deferred sleep value if sleep pointer is provided or direct sleep */ + if (sleep_ms) + *sleep_ms = sleepval; + else if (sleepval) + msleep(sleepval); + + return 0; +} + +int inv_icm42600_set_accel_conf(struct inv_icm42600_state *st, + struct inv_icm42600_sensor_conf *conf, + unsigned int *sleep_ms) +{ + struct inv_icm42600_sensor_conf *oldconf = &st->conf.accel; + unsigned int val; + int ret; + + /* Sanitize missing values with current values */ + if (conf->mode < 0) + conf->mode = oldconf->mode; + if (conf->fs < 0) + conf->fs = oldconf->fs; + if (conf->odr < 0) + conf->odr = oldconf->odr; + if (conf->filter < 0) + conf->filter = oldconf->filter; + + /* set ACCEL_CONFIG0 register (accel fullscale & odr) */ + if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { + val = INV_ICM42600_ACCEL_CONFIG0_FS(conf->fs) | + INV_ICM42600_ACCEL_CONFIG0_ODR(conf->odr); + ret = regmap_write(st->map, INV_ICM42600_REG_ACCEL_CONFIG0, val); + if (ret) + return ret; + oldconf->fs = conf->fs; + oldconf->odr = conf->odr; + } + + /* set GYRO_ACCEL_CONFIG0 register (accel filter) */ + if (conf->filter != oldconf->filter) { + val = INV_ICM42600_GYRO_ACCEL_CONFIG0_ACCEL_FILT(conf->filter) | + INV_ICM42600_GYRO_ACCEL_CONFIG0_GYRO_FILT(st->conf.gyro.filter); + ret = regmap_write(st->map, INV_ICM42600_REG_GYRO_ACCEL_CONFIG0, val); + if (ret) + return ret; + oldconf->filter = conf->filter; + } + + /* set PWR_MGMT0 register (accel sensor mode) */ + return inv_icm42600_set_pwr_mgmt0(st, st->conf.gyro.mode, conf->mode, + st->conf.temp_en, sleep_ms); +} + +int inv_icm42600_set_gyro_conf(struct inv_icm42600_state *st, + struct inv_icm42600_sensor_conf *conf, + unsigned int *sleep_ms) +{ + struct inv_icm42600_sensor_conf *oldconf = &st->conf.gyro; + unsigned int val; + int ret; + + /* sanitize missing values with current values */ + if (conf->mode < 0) + conf->mode = oldconf->mode; + if (conf->fs < 0) + conf->fs = oldconf->fs; + if (conf->odr < 0) + conf->odr = oldconf->odr; + if (conf->filter < 0) + conf->filter = oldconf->filter; + + /* set GYRO_CONFIG0 register (gyro fullscale & odr) */ + if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { + val = INV_ICM42600_GYRO_CONFIG0_FS(conf->fs) | + INV_ICM42600_GYRO_CONFIG0_ODR(conf->odr); + ret = regmap_write(st->map, INV_ICM42600_REG_GYRO_CONFIG0, val); + if (ret) + return ret; + oldconf->fs = conf->fs; + oldconf->odr = conf->odr; + } + + /* set GYRO_ACCEL_CONFIG0 register (gyro filter) */ + if (conf->filter != oldconf->filter) { + val = INV_ICM42600_GYRO_ACCEL_CONFIG0_ACCEL_FILT(st->conf.accel.filter) | + INV_ICM42600_GYRO_ACCEL_CONFIG0_GYRO_FILT(conf->filter); + ret = regmap_write(st->map, INV_ICM42600_REG_GYRO_ACCEL_CONFIG0, val); + if (ret) + return ret; + oldconf->filter = conf->filter; + } + + /* set PWR_MGMT0 register (gyro sensor mode) */ + return inv_icm42600_set_pwr_mgmt0(st, conf->mode, st->conf.accel.mode, + st->conf.temp_en, sleep_ms); + + return 0; +} + +int inv_icm42600_set_temp_conf(struct inv_icm42600_state *st, bool enable, + unsigned int *sleep_ms) +{ + return inv_icm42600_set_pwr_mgmt0(st, st->conf.gyro.mode, + st->conf.accel.mode, enable, + sleep_ms); +} + +int inv_icm42600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + mutex_lock(&st->lock); + + if (readval) + ret = regmap_read(st->map, reg, readval); + else + ret = regmap_write(st->map, reg, writeval); + + mutex_unlock(&st->lock); + + return ret; +} + +static int inv_icm42600_set_conf(struct inv_icm42600_state *st, + const struct inv_icm42600_conf *conf) +{ + unsigned int val; + int ret; + + /* set PWR_MGMT0 register (gyro & accel sensor mode, temp enabled) */ + val = INV_ICM42600_PWR_MGMT0_GYRO(conf->gyro.mode) | + INV_ICM42600_PWR_MGMT0_ACCEL(conf->accel.mode); + if (!conf->temp_en) + val |= INV_ICM42600_PWR_MGMT0_TEMP_DIS; + ret = regmap_write(st->map, INV_ICM42600_REG_PWR_MGMT0, val); + if (ret) + return ret; + + /* set GYRO_CONFIG0 register (gyro fullscale & odr) */ + val = INV_ICM42600_GYRO_CONFIG0_FS(conf->gyro.fs) | + INV_ICM42600_GYRO_CONFIG0_ODR(conf->gyro.odr); + ret = regmap_write(st->map, INV_ICM42600_REG_GYRO_CONFIG0, val); + if (ret) + return ret; + + /* set ACCEL_CONFIG0 register (accel fullscale & odr) */ + val = INV_ICM42600_ACCEL_CONFIG0_FS(conf->accel.fs) | + INV_ICM42600_ACCEL_CONFIG0_ODR(conf->accel.odr); + ret = regmap_write(st->map, INV_ICM42600_REG_ACCEL_CONFIG0, val); + if (ret) + return ret; + + /* set GYRO_ACCEL_CONFIG0 register (gyro & accel filters) */ + val = INV_ICM42600_GYRO_ACCEL_CONFIG0_ACCEL_FILT(conf->accel.filter) | + INV_ICM42600_GYRO_ACCEL_CONFIG0_GYRO_FILT(conf->gyro.filter); + ret = regmap_write(st->map, INV_ICM42600_REG_GYRO_ACCEL_CONFIG0, val); + if (ret) + return ret; + + /* update internal conf */ + st->conf = *conf; + + return 0; +} + +/** + * inv_icm42600_setup() - check and setup chip + * @st: driver internal state + * @bus_setup: callback for setting up bus specific registers + * + * Returns 0 on success, a negative error code otherwise. + */ +static int inv_icm42600_setup(struct inv_icm42600_state *st, + inv_icm42600_bus_setup bus_setup) +{ + const struct inv_icm42600_hw *hw = &inv_icm42600_hw[st->chip]; + const struct device *dev = regmap_get_device(st->map); + unsigned int val; + int ret; + + /* check chip self-identification value */ + ret = regmap_read(st->map, INV_ICM42600_REG_WHOAMI, &val); + if (ret) + return ret; + if (val != hw->whoami) { + dev_err(dev, "invalid whoami %#02x expected %#02x (%s)\n", + val, hw->whoami, hw->name); + return -ENODEV; + } + st->name = hw->name; + + /* reset to make sure previous state are not there */ + ret = regmap_write(st->map, INV_ICM42600_REG_DEVICE_CONFIG, + INV_ICM42600_DEVICE_CONFIG_SOFT_RESET); + if (ret) + return ret; + msleep(INV_ICM42600_RESET_TIME_MS); + + ret = regmap_read(st->map, INV_ICM42600_REG_INT_STATUS, &val); + if (ret) + return ret; + if (!(val & INV_ICM42600_INT_STATUS_RESET_DONE)) { + dev_err(dev, "reset error, reset done bit not set\n"); + return -ENODEV; + } + + /* set chip bus configuration */ + ret = bus_setup(st); + if (ret) + return ret; + + /* sensor data in big-endian (default) */ + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG0, + INV_ICM42600_INTF_CONFIG0_SENSOR_DATA_ENDIAN, + INV_ICM42600_INTF_CONFIG0_SENSOR_DATA_ENDIAN); + if (ret) + return ret; + + return inv_icm42600_set_conf(st, hw->conf); +} + +static irqreturn_t inv_icm42600_irq_timestamp(int irq, void *_data) +{ + struct inv_icm42600_state *st = _data; + + st->timestamp.gyro = iio_get_time_ns(st->indio_gyro); + st->timestamp.accel = iio_get_time_ns(st->indio_accel); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t inv_icm42600_irq_handler(int irq, void *_data) +{ + struct inv_icm42600_state *st = _data; + struct device *dev = regmap_get_device(st->map); + unsigned int status; + int ret; + + mutex_lock(&st->lock); + + ret = regmap_read(st->map, INV_ICM42600_REG_INT_STATUS, &status); + if (ret) + goto out_unlock; + + /* FIFO full */ + if (status & INV_ICM42600_INT_STATUS_FIFO_FULL) + dev_warn(dev, "FIFO full data lost!\n"); + + /* FIFO threshold reached */ + if (status & INV_ICM42600_INT_STATUS_FIFO_THS) { + ret = inv_icm42600_buffer_fifo_read(st, 0); + if (ret) { + dev_err(dev, "FIFO read error %d\n", ret); + goto out_unlock; + } + ret = inv_icm42600_buffer_fifo_parse(st); + if (ret) + dev_err(dev, "FIFO parsing error %d\n", ret); + } + +out_unlock: + mutex_unlock(&st->lock); + return IRQ_HANDLED; +} + +/** + * inv_icm42600_irq_init() - initialize int pin and interrupt handler + * @st: driver internal state + * @irq: irq number + * @irq_type: irq trigger type + * @open_drain: true if irq is open drain, false for push-pull + * + * Returns 0 on success, a negative error code otherwise. + */ +static int inv_icm42600_irq_init(struct inv_icm42600_state *st, int irq, + int irq_type, bool open_drain) +{ + struct device *dev = regmap_get_device(st->map); + unsigned int val; + int ret; + + /* configure INT1 interrupt: default is active low on edge */ + switch (irq_type) { + case IRQF_TRIGGER_RISING: + case IRQF_TRIGGER_HIGH: + val = INV_ICM42600_INT_CONFIG_INT1_ACTIVE_HIGH; + break; + default: + val = INV_ICM42600_INT_CONFIG_INT1_ACTIVE_LOW; + break; + } + + switch (irq_type) { + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_HIGH: + val |= INV_ICM42600_INT_CONFIG_INT1_LATCHED; + break; + default: + break; + } + + if (!open_drain) + val |= INV_ICM42600_INT_CONFIG_INT1_PUSH_PULL; + + ret = regmap_write(st->map, INV_ICM42600_REG_INT_CONFIG, val); + if (ret) + return ret; + + /* Deassert async reset for proper INT pin operation (cf datasheet) */ + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INT_CONFIG1, + INV_ICM42600_INT_CONFIG1_ASYNC_RESET, 0); + if (ret) + return ret; + + return devm_request_threaded_irq(dev, irq, inv_icm42600_irq_timestamp, + inv_icm42600_irq_handler, irq_type, + "inv_icm42600", st); +} + +static int inv_icm42600_enable_regulator_vddio(struct inv_icm42600_state *st) +{ + int ret; + + ret = regulator_enable(st->vddio_supply); + if (ret) + return ret; + + /* wait a little for supply ramp */ + usleep_range(3000, 4000); + + return 0; +} + +static void inv_icm42600_disable_vdd_reg(void *_data) +{ + struct inv_icm42600_state *st = _data; + const struct device *dev = regmap_get_device(st->map); + int ret; + + ret = regulator_disable(st->vdd_supply); + if (ret) + dev_err(dev, "failed to disable vdd error %d\n", ret); +} + +static void inv_icm42600_disable_vddio_reg(void *_data) +{ + struct inv_icm42600_state *st = _data; + const struct device *dev = regmap_get_device(st->map); + int ret; + + ret = regulator_disable(st->vddio_supply); + if (ret) + dev_err(dev, "failed to disable vddio error %d\n", ret); +} + +static void inv_icm42600_disable_pm(void *_data) +{ + struct device *dev = _data; + + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); +} + +int inv_icm42600_core_probe(struct regmap *regmap, int chip, int irq, + inv_icm42600_bus_setup bus_setup) +{ + struct device *dev = regmap_get_device(regmap); + struct inv_icm42600_state *st; + struct irq_data *irq_desc; + int irq_type; + bool open_drain; + int ret; + + if (chip < 0 || chip >= INV_CHIP_NB) { + dev_err(dev, "invalid chip = %d\n", chip); + return -ENODEV; + } + + /* get irq properties, set trigger falling by default */ + irq_desc = irq_get_irq_data(irq); + if (!irq_desc) { + dev_err(dev, "could not find IRQ %d\n", irq); + return -EINVAL; + } + + irq_type = irqd_get_trigger_type(irq_desc); + if (!irq_type) + irq_type = IRQF_TRIGGER_FALLING; + + open_drain = device_property_read_bool(dev, "drive-open-drain"); + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + dev_set_drvdata(dev, st); + mutex_init(&st->lock); + st->chip = chip; + st->map = regmap; + + ret = iio_read_mount_matrix(dev, "mount-matrix", &st->orientation); + if (ret) { + dev_err(dev, "failed to retrieve mounting matrix %d\n", ret); + return ret; + } + + st->vdd_supply = devm_regulator_get(dev, "vdd"); + if (IS_ERR(st->vdd_supply)) + return PTR_ERR(st->vdd_supply); + + st->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(st->vddio_supply)) + return PTR_ERR(st->vddio_supply); + + ret = regulator_enable(st->vdd_supply); + if (ret) + return ret; + msleep(INV_ICM42600_POWER_UP_TIME_MS); + + ret = devm_add_action_or_reset(dev, inv_icm42600_disable_vdd_reg, st); + if (ret) + return ret; + + ret = inv_icm42600_enable_regulator_vddio(st); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, inv_icm42600_disable_vddio_reg, st); + if (ret) + return ret; + + /* setup chip registers */ + ret = inv_icm42600_setup(st, bus_setup); + if (ret) + return ret; + + ret = inv_icm42600_timestamp_setup(st); + if (ret) + return ret; + + ret = inv_icm42600_buffer_init(st); + if (ret) + return ret; + + st->indio_gyro = inv_icm42600_gyro_init(st); + if (IS_ERR(st->indio_gyro)) + return PTR_ERR(st->indio_gyro); + + st->indio_accel = inv_icm42600_accel_init(st); + if (IS_ERR(st->indio_accel)) + return PTR_ERR(st->indio_accel); + + ret = inv_icm42600_irq_init(st, irq, irq_type, open_drain); + if (ret) + return ret; + + /* setup runtime power management */ + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, INV_ICM42600_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + + return devm_add_action_or_reset(dev, inv_icm42600_disable_pm, dev); +} +EXPORT_SYMBOL_GPL(inv_icm42600_core_probe); + +/* + * Suspend saves sensors state and turns everything off. + * Check first if runtime suspend has not already done the job. + */ +static int __maybe_unused inv_icm42600_suspend(struct device *dev) +{ + struct inv_icm42600_state *st = dev_get_drvdata(dev); + int ret; + + mutex_lock(&st->lock); + + st->suspended.gyro = st->conf.gyro.mode; + st->suspended.accel = st->conf.accel.mode; + st->suspended.temp = st->conf.temp_en; + if (pm_runtime_suspended(dev)) { + ret = 0; + goto out_unlock; + } + + /* disable FIFO data streaming */ + if (st->fifo.on) { + ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, + INV_ICM42600_FIFO_CONFIG_BYPASS); + if (ret) + goto out_unlock; + } + + ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF, + INV_ICM42600_SENSOR_MODE_OFF, false, + NULL); + if (ret) + goto out_unlock; + + regulator_disable(st->vddio_supply); + +out_unlock: + mutex_unlock(&st->lock); + return ret; +} + +/* + * System resume gets the system back on and restores the sensors state. + * Manually put runtime power management in system active state. + */ +static int __maybe_unused inv_icm42600_resume(struct device *dev) +{ + struct inv_icm42600_state *st = dev_get_drvdata(dev); + int ret; + + mutex_lock(&st->lock); + + ret = inv_icm42600_enable_regulator_vddio(st); + if (ret) + goto out_unlock; + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + /* restore sensors state */ + ret = inv_icm42600_set_pwr_mgmt0(st, st->suspended.gyro, + st->suspended.accel, + st->suspended.temp, NULL); + if (ret) + goto out_unlock; + + /* restore FIFO data streaming */ + if (st->fifo.on) + ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, + INV_ICM42600_FIFO_CONFIG_STREAM); + +out_unlock: + mutex_unlock(&st->lock); + return ret; +} + +/* Runtime suspend will turn off sensors that are enabled by iio devices. */ +static int __maybe_unused inv_icm42600_runtime_suspend(struct device *dev) +{ + struct inv_icm42600_state *st = dev_get_drvdata(dev); + int ret; + + mutex_lock(&st->lock); + + /* disable all sensors */ + ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF, + INV_ICM42600_SENSOR_MODE_OFF, false, + NULL); + if (ret) + goto error_unlock; + + regulator_disable(st->vddio_supply); + +error_unlock: + mutex_unlock(&st->lock); + return ret; +} + +/* Sensors are enabled by iio devices, no need to turn them back on here. */ +static int __maybe_unused inv_icm42600_runtime_resume(struct device *dev) +{ + struct inv_icm42600_state *st = dev_get_drvdata(dev); + int ret; + + mutex_lock(&st->lock); + + ret = inv_icm42600_enable_regulator_vddio(st); + + mutex_unlock(&st->lock); + return ret; +} + +const struct dev_pm_ops inv_icm42600_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(inv_icm42600_suspend, inv_icm42600_resume) + SET_RUNTIME_PM_OPS(inv_icm42600_runtime_suspend, + inv_icm42600_runtime_resume, NULL) +}; +EXPORT_SYMBOL_GPL(inv_icm42600_pm_ops); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-426xx device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c new file mode 100644 index 000000000000..aee7b9ff4bf4 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c @@ -0,0 +1,798 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/delay.h> +#include <linux/math64.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/kfifo_buf.h> + +#include "inv_icm42600.h" +#include "inv_icm42600_temp.h" +#include "inv_icm42600_buffer.h" +#include "inv_icm42600_timestamp.h" + +#define INV_ICM42600_GYRO_CHAN(_modifier, _index, _ext_info) \ + { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = _ext_info, \ + } + +enum inv_icm42600_gyro_scan { + INV_ICM42600_GYRO_SCAN_X, + INV_ICM42600_GYRO_SCAN_Y, + INV_ICM42600_GYRO_SCAN_Z, + INV_ICM42600_GYRO_SCAN_TEMP, + INV_ICM42600_GYRO_SCAN_TIMESTAMP, +}; + +static const struct iio_chan_spec_ext_info inv_icm42600_gyro_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm42600_get_mount_matrix), + {}, +}; + +static const struct iio_chan_spec inv_icm42600_gyro_channels[] = { + INV_ICM42600_GYRO_CHAN(IIO_MOD_X, INV_ICM42600_GYRO_SCAN_X, + inv_icm42600_gyro_ext_infos), + INV_ICM42600_GYRO_CHAN(IIO_MOD_Y, INV_ICM42600_GYRO_SCAN_Y, + inv_icm42600_gyro_ext_infos), + INV_ICM42600_GYRO_CHAN(IIO_MOD_Z, INV_ICM42600_GYRO_SCAN_Z, + inv_icm42600_gyro_ext_infos), + INV_ICM42600_TEMP_CHAN(INV_ICM42600_GYRO_SCAN_TEMP), + IIO_CHAN_SOFT_TIMESTAMP(INV_ICM42600_GYRO_SCAN_TIMESTAMP), +}; + +/* + * IIO buffer data: size must be a power of 2 and timestamp aligned + * 16 bytes: 6 bytes angular velocity, 2 bytes temperature, 8 bytes timestamp + */ +struct inv_icm42600_gyro_buffer { + struct inv_icm42600_fifo_sensor_data gyro; + int16_t temp; + int64_t timestamp __aligned(8); +}; + +#define INV_ICM42600_SCAN_MASK_GYRO_3AXIS \ + (BIT(INV_ICM42600_GYRO_SCAN_X) | \ + BIT(INV_ICM42600_GYRO_SCAN_Y) | \ + BIT(INV_ICM42600_GYRO_SCAN_Z)) + +#define INV_ICM42600_SCAN_MASK_TEMP BIT(INV_ICM42600_GYRO_SCAN_TEMP) + +static const unsigned long inv_icm42600_gyro_scan_masks[] = { + /* 3-axis gyro + temperature */ + INV_ICM42600_SCAN_MASK_GYRO_3AXIS | INV_ICM42600_SCAN_MASK_TEMP, + 0, +}; + +/* enable gyroscope sensor and FIFO write */ +static int inv_icm42600_gyro_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int fifo_en = 0; + unsigned int sleep_gyro = 0; + unsigned int sleep_temp = 0; + unsigned int sleep; + int ret; + + mutex_lock(&st->lock); + + if (*scan_mask & INV_ICM42600_SCAN_MASK_TEMP) { + /* enable temp sensor */ + ret = inv_icm42600_set_temp_conf(st, true, &sleep_temp); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42600_SENSOR_TEMP; + } + + if (*scan_mask & INV_ICM42600_SCAN_MASK_GYRO_3AXIS) { + /* enable gyro sensor */ + conf.mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE; + ret = inv_icm42600_set_gyro_conf(st, &conf, &sleep_gyro); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42600_SENSOR_GYRO; + } + + /* update data FIFO write */ + inv_icm42600_timestamp_apply_odr(ts, 0, 0, 0); + ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + if (ret) + goto out_unlock; + + ret = inv_icm42600_buffer_update_watermark(st); + +out_unlock: + mutex_unlock(&st->lock); + /* sleep maximum required time */ + if (sleep_gyro > sleep_temp) + sleep = sleep_gyro; + else + sleep = sleep_temp; + if (sleep) + msleep(sleep); + return ret; +} + +static int inv_icm42600_gyro_read_sensor(struct inv_icm42600_state *st, + struct iio_chan_spec const *chan, + int16_t *val) +{ + struct device *dev = regmap_get_device(st->map); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int reg; + __be16 *data; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_GYRO_DATA_X; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_GYRO_DATA_Y; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_GYRO_DATA_Z; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + /* enable gyro sensor */ + conf.mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE; + ret = inv_icm42600_set_gyro_conf(st, &conf, NULL); + if (ret) + goto exit; + + /* read gyro register data */ + data = (__be16 *)&st->buffer[0]; + ret = regmap_bulk_read(st->map, reg, data, sizeof(*data)); + if (ret) + goto exit; + + *val = (int16_t)be16_to_cpup(data); + if (*val == INV_ICM42600_DATA_INVALID) + ret = -EINVAL; +exit: + mutex_unlock(&st->lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +/* IIO format int + nano */ +static const int inv_icm42600_gyro_scale[] = { + /* +/- 2000dps => 0.001065264 rad/s */ + [2 * INV_ICM42600_GYRO_FS_2000DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_2000DPS + 1] = 1065264, + /* +/- 1000dps => 0.000532632 rad/s */ + [2 * INV_ICM42600_GYRO_FS_1000DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_1000DPS + 1] = 532632, + /* +/- 500dps => 0.000266316 rad/s */ + [2 * INV_ICM42600_GYRO_FS_500DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_500DPS + 1] = 266316, + /* +/- 250dps => 0.000133158 rad/s */ + [2 * INV_ICM42600_GYRO_FS_250DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_250DPS + 1] = 133158, + /* +/- 125dps => 0.000066579 rad/s */ + [2 * INV_ICM42600_GYRO_FS_125DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_125DPS + 1] = 66579, + /* +/- 62.5dps => 0.000033290 rad/s */ + [2 * INV_ICM42600_GYRO_FS_62_5DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_62_5DPS + 1] = 33290, + /* +/- 31.25dps => 0.000016645 rad/s */ + [2 * INV_ICM42600_GYRO_FS_31_25DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_31_25DPS + 1] = 16645, + /* +/- 15.625dps => 0.000008322 rad/s */ + [2 * INV_ICM42600_GYRO_FS_15_625DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_15_625DPS + 1] = 8322, +}; + +static int inv_icm42600_gyro_read_scale(struct inv_icm42600_state *st, + int *val, int *val2) +{ + unsigned int idx; + + idx = st->conf.gyro.fs; + + *val = inv_icm42600_gyro_scale[2 * idx]; + *val2 = inv_icm42600_gyro_scale[2 * idx + 1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm42600_gyro_write_scale(struct inv_icm42600_state *st, + int val, int val2) +{ + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_gyro_scale); idx += 2) { + if (val == inv_icm42600_gyro_scale[idx] && + val2 == inv_icm42600_gyro_scale[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm42600_gyro_scale)) + return -EINVAL; + + conf.fs = idx / 2; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = inv_icm42600_set_gyro_conf(st, &conf, NULL); + + mutex_unlock(&st->lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + micro */ +static const int inv_icm42600_gyro_odr[] = { + /* 12.5Hz */ + 12, 500000, + /* 25Hz */ + 25, 0, + /* 50Hz */ + 50, 0, + /* 100Hz */ + 100, 0, + /* 200Hz */ + 200, 0, + /* 1kHz */ + 1000, 0, + /* 2kHz */ + 2000, 0, + /* 4kHz */ + 4000, 0, +}; + +static const int inv_icm42600_gyro_odr_conv[] = { + INV_ICM42600_ODR_12_5HZ, + INV_ICM42600_ODR_25HZ, + INV_ICM42600_ODR_50HZ, + INV_ICM42600_ODR_100HZ, + INV_ICM42600_ODR_200HZ, + INV_ICM42600_ODR_1KHZ_LN, + INV_ICM42600_ODR_2KHZ_LN, + INV_ICM42600_ODR_4KHZ_LN, +}; + +static int inv_icm42600_gyro_read_odr(struct inv_icm42600_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + odr = st->conf.gyro.odr; + + for (i = 0; i < ARRAY_SIZE(inv_icm42600_gyro_odr_conv); ++i) { + if (inv_icm42600_gyro_odr_conv[i] == odr) + break; + } + if (i >= ARRAY_SIZE(inv_icm42600_gyro_odr_conv)) + return -EINVAL; + + *val = inv_icm42600_gyro_odr[2 * i]; + *val2 = inv_icm42600_gyro_odr[2 * i + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm42600_gyro_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_gyro_odr); idx += 2) { + if (val == inv_icm42600_gyro_odr[idx] && + val2 == inv_icm42600_gyro_odr[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm42600_gyro_odr)) + return -EINVAL; + + conf.odr = inv_icm42600_gyro_odr_conv[idx / 2]; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = inv_icm42600_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr), + iio_buffer_enabled(indio_dev)); + if (ret) + goto out_unlock; + + ret = inv_icm42600_set_gyro_conf(st, &conf, NULL); + if (ret) + goto out_unlock; + inv_icm42600_buffer_update_fifo_period(st); + inv_icm42600_buffer_update_watermark(st); + +out_unlock: + mutex_unlock(&st->lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* + * Calibration bias values, IIO range format int + nano. + * Value is limited to +/-64dps coded on 12 bits signed. Step is 1/32 dps. + */ +static int inv_icm42600_gyro_calibbias[] = { + -1, 117010721, /* min: -1.117010721 rad/s */ + 0, 545415, /* step: 0.000545415 rad/s */ + 1, 116465306, /* max: 1.116465306 rad/s */ +}; + +static int inv_icm42600_gyro_read_offset(struct inv_icm42600_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct device *dev = regmap_get_device(st->map); + int64_t val64; + int32_t bias; + unsigned int reg; + int16_t offset; + uint8_t data[2]; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_OFFSET_USER0; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_OFFSET_USER1; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_OFFSET_USER3; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = regmap_bulk_read(st->map, reg, st->buffer, sizeof(data)); + memcpy(data, st->buffer, sizeof(data)); + + mutex_unlock(&st->lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + if (ret) + return ret; + + /* 12 bits signed value */ + switch (chan->channel2) { + case IIO_MOD_X: + offset = sign_extend32(((data[1] & 0x0F) << 8) | data[0], 11); + break; + case IIO_MOD_Y: + offset = sign_extend32(((data[0] & 0xF0) << 4) | data[1], 11); + break; + case IIO_MOD_Z: + offset = sign_extend32(((data[1] & 0x0F) << 8) | data[0], 11); + break; + default: + return -EINVAL; + } + + /* + * convert raw offset to dps then to rad/s + * 12 bits signed raw max 64 to dps: 64 / 2048 + * dps to rad: Pi / 180 + * result in nano (1000000000) + * (offset * 64 * Pi * 1000000000) / (2048 * 180) + */ + val64 = (int64_t)offset * 64LL * 3141592653LL; + /* for rounding, add + or - divisor (2048 * 180) divided by 2 */ + if (val64 >= 0) + val64 += 2048 * 180 / 2; + else + val64 -= 2048 * 180 / 2; + bias = div_s64(val64, 2048 * 180); + *val = bias / 1000000000L; + *val2 = bias % 1000000000L; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm42600_gyro_write_offset(struct inv_icm42600_state *st, + struct iio_chan_spec const *chan, + int val, int val2) +{ + struct device *dev = regmap_get_device(st->map); + int64_t val64, min, max; + unsigned int reg, regval; + int16_t offset; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_OFFSET_USER0; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_OFFSET_USER1; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_OFFSET_USER3; + break; + default: + return -EINVAL; + } + + /* inv_icm42600_gyro_calibbias: min - step - max in nano */ + min = (int64_t)inv_icm42600_gyro_calibbias[0] * 1000000000LL + + (int64_t)inv_icm42600_gyro_calibbias[1]; + max = (int64_t)inv_icm42600_gyro_calibbias[4] * 1000000000LL + + (int64_t)inv_icm42600_gyro_calibbias[5]; + val64 = (int64_t)val * 1000000000LL + (int64_t)val2; + if (val64 < min || val64 > max) + return -EINVAL; + + /* + * convert rad/s to dps then to raw value + * rad to dps: 180 / Pi + * dps to raw 12 bits signed, max 64: 2048 / 64 + * val in nano (1000000000) + * val * 180 * 2048 / (Pi * 1000000000 * 64) + */ + val64 = val64 * 180LL * 2048LL; + /* for rounding, add + or - divisor (3141592653 * 64) divided by 2 */ + if (val64 >= 0) + val64 += 3141592653LL * 64LL / 2LL; + else + val64 -= 3141592653LL * 64LL / 2LL; + offset = div64_s64(val64, 3141592653LL * 64LL); + + /* clamp value limited to 12 bits signed */ + if (offset < -2048) + offset = -2048; + else if (offset > 2047) + offset = 2047; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + switch (chan->channel2) { + case IIO_MOD_X: + /* OFFSET_USER1 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER1, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = offset & 0xFF; + st->buffer[1] = (regval & 0xF0) | ((offset & 0xF00) >> 8); + break; + case IIO_MOD_Y: + /* OFFSET_USER1 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER1, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = ((offset & 0xF00) >> 4) | (regval & 0x0F); + st->buffer[1] = offset & 0xFF; + break; + case IIO_MOD_Z: + /* OFFSET_USER4 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER4, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = offset & 0xFF; + st->buffer[1] = (regval & 0xF0) | ((offset & 0xF00) >> 8); + break; + default: + ret = -EINVAL; + goto out_unlock; + } + + ret = regmap_bulk_write(st->map, reg, st->buffer, 2); + +out_unlock: + mutex_unlock(&st->lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int inv_icm42600_gyro_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int16_t data; + int ret; + + switch (chan->type) { + case IIO_ANGL_VEL: + break; + case IIO_TEMP: + return inv_icm42600_temp_read_raw(indio_dev, chan, val, val2, mask); + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = inv_icm42600_gyro_read_sensor(st, chan, &data); + iio_device_release_direct_mode(indio_dev); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm42600_gyro_read_scale(st, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42600_gyro_read_odr(st, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return inv_icm42600_gyro_read_offset(st, chan, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42600_gyro_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = inv_icm42600_gyro_scale; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(inv_icm42600_gyro_scale); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = inv_icm42600_gyro_odr; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(inv_icm42600_gyro_odr); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_CALIBBIAS: + *vals = inv_icm42600_gyro_calibbias; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int inv_icm42600_gyro_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = inv_icm42600_gyro_write_scale(st, val, val2); + iio_device_release_direct_mode(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42600_gyro_write_odr(indio_dev, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = inv_icm42600_gyro_write_offset(st, chan, val, val2); + iio_device_release_direct_mode(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int inv_icm42600_gyro_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int inv_icm42600_gyro_hwfifo_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + mutex_lock(&st->lock); + + st->fifo.watermark.gyro = val; + ret = inv_icm42600_buffer_update_watermark(st); + + mutex_unlock(&st->lock); + + return ret; +} + +static int inv_icm42600_gyro_hwfifo_flush(struct iio_dev *indio_dev, + unsigned int count) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (count == 0) + return 0; + + mutex_lock(&st->lock); + + ret = inv_icm42600_buffer_hwfifo_flush(st, count); + if (!ret) + ret = st->fifo.nb.gyro; + + mutex_unlock(&st->lock); + + return ret; +} + +static const struct iio_info inv_icm42600_gyro_info = { + .read_raw = inv_icm42600_gyro_read_raw, + .read_avail = inv_icm42600_gyro_read_avail, + .write_raw = inv_icm42600_gyro_write_raw, + .write_raw_get_fmt = inv_icm42600_gyro_write_raw_get_fmt, + .debugfs_reg_access = inv_icm42600_debugfs_reg, + .update_scan_mode = inv_icm42600_gyro_update_scan_mode, + .hwfifo_set_watermark = inv_icm42600_gyro_hwfifo_set_watermark, + .hwfifo_flush_to_buffer = inv_icm42600_gyro_hwfifo_flush, +}; + +struct iio_dev *inv_icm42600_gyro_init(struct inv_icm42600_state *st) +{ + struct device *dev = regmap_get_device(st->map); + const char *name; + struct inv_icm42600_timestamp *ts; + struct iio_dev *indio_dev; + struct iio_buffer *buffer; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-gyro", st->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*ts)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + + buffer = devm_iio_kfifo_allocate(dev); + if (!buffer) + return ERR_PTR(-ENOMEM); + + ts = iio_priv(indio_dev); + inv_icm42600_timestamp_init(ts, inv_icm42600_odr_to_period(st->conf.gyro.odr)); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; + indio_dev->info = &inv_icm42600_gyro_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->channels = inv_icm42600_gyro_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm42600_gyro_channels); + indio_dev->available_scan_masks = inv_icm42600_gyro_scan_masks; + indio_dev->setup_ops = &inv_icm42600_buffer_ops; + + iio_device_attach_buffer(indio_dev, buffer); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} + +int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_timestamp *ts = iio_priv(indio_dev); + ssize_t i, size; + unsigned int no; + const void *accel, *gyro, *timestamp; + const int8_t *temp; + unsigned int odr; + int64_t ts_val; + struct inv_icm42600_gyro_buffer buffer; + + /* parse all fifo packets */ + for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) { + size = inv_icm42600_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + /* quit if error or FIFO is empty */ + if (size <= 0) + return size; + + /* skip packet if no gyro data or data is invalid */ + if (gyro == NULL || !inv_icm42600_fifo_is_data_valid(gyro)) + continue; + + /* update odr */ + if (odr & INV_ICM42600_SENSOR_GYRO) + inv_icm42600_timestamp_apply_odr(ts, st->fifo.period, + st->fifo.nb.total, no); + + /* buffer is copied to userspace, zeroing it to avoid any data leak */ + memset(&buffer, 0, sizeof(buffer)); + memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; + ts_val = inv_icm42600_timestamp_pop(ts); + iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val); + } + + return 0; +} diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c new file mode 100644 index 000000000000..85b1934cec60 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 InvenSense, Inc. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/property.h> + +#include "inv_icm42600.h" + +static int inv_icm42600_i2c_bus_setup(struct inv_icm42600_state *st) +{ + unsigned int mask, val; + int ret; + + /* setup interface registers */ + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG6, + INV_ICM42600_INTF_CONFIG6_MASK, + INV_ICM42600_INTF_CONFIG6_I3C_EN); + if (ret) + return ret; + + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG4, + INV_ICM42600_INTF_CONFIG4_I3C_BUS_ONLY, 0); + if (ret) + return ret; + + /* set slew rates for I2C and SPI */ + mask = INV_ICM42600_DRIVE_CONFIG_I2C_MASK | + INV_ICM42600_DRIVE_CONFIG_SPI_MASK; + val = INV_ICM42600_DRIVE_CONFIG_I2C(INV_ICM42600_SLEW_RATE_12_36NS) | + INV_ICM42600_DRIVE_CONFIG_SPI(INV_ICM42600_SLEW_RATE_12_36NS); + ret = regmap_update_bits(st->map, INV_ICM42600_REG_DRIVE_CONFIG, + mask, val); + if (ret) + return ret; + + /* disable SPI bus */ + return regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG0, + INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_MASK, + INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_SPI_DIS); +} + +static int inv_icm42600_probe(struct i2c_client *client) +{ + const void *match; + enum inv_icm42600_chip chip; + struct regmap *regmap; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENOTSUPP; + + match = device_get_match_data(&client->dev); + if (!match) + return -EINVAL; + chip = (enum inv_icm42600_chip)match; + + regmap = devm_regmap_init_i2c(client, &inv_icm42600_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return inv_icm42600_core_probe(regmap, chip, client->irq, + inv_icm42600_i2c_bus_setup); +} + +static const struct of_device_id inv_icm42600_of_matches[] = { + { + .compatible = "invensense,icm42600", + .data = (void *)INV_CHIP_ICM42600, + }, { + .compatible = "invensense,icm42602", + .data = (void *)INV_CHIP_ICM42602, + }, { + .compatible = "invensense,icm42605", + .data = (void *)INV_CHIP_ICM42605, + }, { + .compatible = "invensense,icm42622", + .data = (void *)INV_CHIP_ICM42622, + }, + {} +}; +MODULE_DEVICE_TABLE(of, inv_icm42600_of_matches); + +static struct i2c_driver inv_icm42600_driver = { + .driver = { + .name = "inv-icm42600-i2c", + .of_match_table = inv_icm42600_of_matches, + .pm = &inv_icm42600_pm_ops, + }, + .probe_new = inv_icm42600_probe, +}; +module_i2c_driver(inv_icm42600_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-426xx I2C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c new file mode 100644 index 000000000000..323789697a08 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 InvenSense, Inc. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/spi/spi.h> +#include <linux/regmap.h> +#include <linux/property.h> + +#include "inv_icm42600.h" + +static int inv_icm42600_spi_bus_setup(struct inv_icm42600_state *st) +{ + unsigned int mask, val; + int ret; + + /* setup interface registers */ + val = INV_ICM42600_INTF_CONFIG6_I3C_EN | + INV_ICM42600_INTF_CONFIG6_I3C_SDR_EN | + INV_ICM42600_INTF_CONFIG6_I3C_DDR_EN; + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG6, + INV_ICM42600_INTF_CONFIG6_MASK, val); + if (ret) + return ret; + + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG4, + INV_ICM42600_INTF_CONFIG4_I3C_BUS_ONLY, 0); + if (ret) + return ret; + + /* set slew rates for I2C and SPI */ + mask = INV_ICM42600_DRIVE_CONFIG_I2C_MASK | + INV_ICM42600_DRIVE_CONFIG_SPI_MASK; + val = INV_ICM42600_DRIVE_CONFIG_I2C(INV_ICM42600_SLEW_RATE_20_60NS) | + INV_ICM42600_DRIVE_CONFIG_SPI(INV_ICM42600_SLEW_RATE_INF_2NS); + ret = regmap_update_bits(st->map, INV_ICM42600_REG_DRIVE_CONFIG, + mask, val); + if (ret) + return ret; + + /* disable i2c bus */ + return regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG0, + INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_MASK, + INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_I2C_DIS); +} + +static int inv_icm42600_probe(struct spi_device *spi) +{ + const void *match; + enum inv_icm42600_chip chip; + struct regmap *regmap; + + match = device_get_match_data(&spi->dev); + if (!match) + return -EINVAL; + chip = (enum inv_icm42600_chip)match; + + regmap = devm_regmap_init_spi(spi, &inv_icm42600_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return inv_icm42600_core_probe(regmap, chip, spi->irq, + inv_icm42600_spi_bus_setup); +} + +static const struct of_device_id inv_icm42600_of_matches[] = { + { + .compatible = "invensense,icm42600", + .data = (void *)INV_CHIP_ICM42600, + }, { + .compatible = "invensense,icm42602", + .data = (void *)INV_CHIP_ICM42602, + }, { + .compatible = "invensense,icm42605", + .data = (void *)INV_CHIP_ICM42605, + }, { + .compatible = "invensense,icm42622", + .data = (void *)INV_CHIP_ICM42622, + }, + {} +}; +MODULE_DEVICE_TABLE(of, inv_icm42600_of_matches); + +static struct spi_driver inv_icm42600_driver = { + .driver = { + .name = "inv-icm42600-spi", + .of_match_table = inv_icm42600_of_matches, + .pm = &inv_icm42600_pm_ops, + }, + .probe = inv_icm42600_probe, +}; +module_spi_driver(inv_icm42600_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-426xx SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c new file mode 100644 index 000000000000..213cce1c3111 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> + +#include "inv_icm42600.h" +#include "inv_icm42600_temp.h" + +static int inv_icm42600_temp_read(struct inv_icm42600_state *st, int16_t *temp) +{ + struct device *dev = regmap_get_device(st->map); + __be16 *raw; + int ret; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = inv_icm42600_set_temp_conf(st, true, NULL); + if (ret) + goto exit; + + raw = (__be16 *)&st->buffer[0]; + ret = regmap_bulk_read(st->map, INV_ICM42600_REG_TEMP_DATA, raw, sizeof(*raw)); + if (ret) + goto exit; + + *temp = (int16_t)be16_to_cpup(raw); + if (*temp == INV_ICM42600_DATA_INVALID) + ret = -EINVAL; + +exit: + mutex_unlock(&st->lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +int inv_icm42600_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int16_t temp; + int ret; + + if (chan->type != IIO_TEMP) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = inv_icm42600_temp_read(st, &temp); + iio_device_release_direct_mode(indio_dev); + if (ret) + return ret; + *val = temp; + return IIO_VAL_INT; + /* + * T°C = (temp / 132.48) + 25 + * Tm°C = 1000 * ((temp * 100 / 13248) + 25) + * scale: 100000 / 13248 ~= 7.548309 + * offset: 25000 + */ + case IIO_CHAN_INFO_SCALE: + *val = 7; + *val2 = 548309; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = 25000; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.h b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.h new file mode 100644 index 000000000000..3941186512fb --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#ifndef INV_ICM42600_TEMP_H_ +#define INV_ICM42600_TEMP_H_ + +#include <linux/iio/iio.h> + +#define INV_ICM42600_TEMP_CHAN(_index) \ + { \ + .type = IIO_TEMP, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + }, \ + } + +int inv_icm42600_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask); + +#endif diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c new file mode 100644 index 000000000000..7f2dc41f807b --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include <linux/kernel.h> +#include <linux/regmap.h> +#include <linux/math64.h> + +#include "inv_icm42600.h" +#include "inv_icm42600_timestamp.h" + +/* internal chip period is 32kHz, 31250ns */ +#define INV_ICM42600_TIMESTAMP_PERIOD 31250 +/* allow a jitter of +/- 2% */ +#define INV_ICM42600_TIMESTAMP_JITTER 2 +/* compute min and max periods accepted */ +#define INV_ICM42600_TIMESTAMP_MIN_PERIOD(_p) \ + (((_p) * (100 - INV_ICM42600_TIMESTAMP_JITTER)) / 100) +#define INV_ICM42600_TIMESTAMP_MAX_PERIOD(_p) \ + (((_p) * (100 + INV_ICM42600_TIMESTAMP_JITTER)) / 100) + +/* Add a new value inside an accumulator and update the estimate value */ +static void inv_update_acc(struct inv_icm42600_timestamp_acc *acc, uint32_t val) +{ + uint64_t sum = 0; + size_t i; + + acc->values[acc->idx++] = val; + if (acc->idx >= ARRAY_SIZE(acc->values)) + acc->idx = 0; + + /* compute the mean of all stored values, use 0 as empty slot */ + for (i = 0; i < ARRAY_SIZE(acc->values); ++i) { + if (acc->values[i] == 0) + break; + sum += acc->values[i]; + } + + acc->val = div_u64(sum, i); +} + +void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts, + uint32_t period) +{ + /* initial odr for sensor after reset is 1kHz */ + const uint32_t default_period = 1000000; + + /* current multiplier and period values after reset */ + ts->mult = default_period / INV_ICM42600_TIMESTAMP_PERIOD; + ts->period = default_period; + /* new set multiplier is the one from chip initialization */ + ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD; + + /* use theoretical value for chip period */ + inv_update_acc(&ts->chip_period, INV_ICM42600_TIMESTAMP_PERIOD); +} + +int inv_icm42600_timestamp_setup(struct inv_icm42600_state *st) +{ + unsigned int val; + + /* enable timestamp register */ + val = INV_ICM42600_TMST_CONFIG_TMST_TO_REGS_EN | + INV_ICM42600_TMST_CONFIG_TMST_EN; + return regmap_update_bits(st->map, INV_ICM42600_REG_TMST_CONFIG, + INV_ICM42600_TMST_CONFIG_MASK, val); +} + +int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts, + uint32_t period, bool fifo) +{ + /* when FIFO is on, prevent odr change if one is already pending */ + if (fifo && ts->new_mult != 0) + return -EAGAIN; + + ts->new_mult = period / INV_ICM42600_TIMESTAMP_PERIOD; + + return 0; +} + +static bool inv_validate_period(uint32_t period, uint32_t mult) +{ + const uint32_t chip_period = INV_ICM42600_TIMESTAMP_PERIOD; + uint32_t period_min, period_max; + + /* check that period is acceptable */ + period_min = INV_ICM42600_TIMESTAMP_MIN_PERIOD(chip_period) * mult; + period_max = INV_ICM42600_TIMESTAMP_MAX_PERIOD(chip_period) * mult; + if (period > period_min && period < period_max) + return true; + else + return false; +} + +static bool inv_compute_chip_period(struct inv_icm42600_timestamp *ts, + uint32_t mult, uint32_t period) +{ + uint32_t new_chip_period; + + if (!inv_validate_period(period, mult)) + return false; + + /* update chip internal period estimation */ + new_chip_period = period / mult; + inv_update_acc(&ts->chip_period, new_chip_period); + + return true; +} + +void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts, + uint32_t fifo_period, size_t fifo_nb, + size_t sensor_nb, int64_t timestamp) +{ + struct inv_icm42600_timestamp_interval *it; + int64_t delta, interval; + const uint32_t fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD; + uint32_t period = ts->period; + int32_t m; + bool valid = false; + + if (fifo_nb == 0) + return; + + /* update interrupt timestamp and compute chip and sensor periods */ + it = &ts->it; + it->lo = it->up; + it->up = timestamp; + delta = it->up - it->lo; + if (it->lo != 0) { + /* compute period: delta time divided by number of samples */ + period = div_s64(delta, fifo_nb); + valid = inv_compute_chip_period(ts, fifo_mult, period); + /* update sensor period if chip internal period is updated */ + if (valid) + ts->period = ts->mult * ts->chip_period.val; + } + + /* no previous data, compute theoritical value from interrupt */ + if (ts->timestamp == 0) { + /* elapsed time: sensor period * sensor samples number */ + interval = (int64_t)ts->period * (int64_t)sensor_nb; + ts->timestamp = it->up - interval; + return; + } + + /* if interrupt interval is valid, sync with interrupt timestamp */ + if (valid) { + /* compute measured fifo_period */ + fifo_period = fifo_mult * ts->chip_period.val; + /* delta time between last sample and last interrupt */ + delta = it->lo - ts->timestamp; + /* if there are multiple samples, go back to first one */ + while (delta >= (fifo_period * 3 / 2)) + delta -= fifo_period; + /* compute maximal adjustment value */ + m = INV_ICM42600_TIMESTAMP_MAX_PERIOD(ts->period) - ts->period; + if (delta > m) + delta = m; + else if (delta < -m) + delta = -m; + ts->timestamp += delta; + } +} + +void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts, + uint32_t fifo_period, size_t fifo_nb, + unsigned int fifo_no) +{ + int64_t interval; + uint32_t fifo_mult; + + if (ts->new_mult == 0) + return; + + /* update to new multiplier and update period */ + ts->mult = ts->new_mult; + ts->new_mult = 0; + ts->period = ts->mult * ts->chip_period.val; + + /* + * After ODR change the time interval with the previous sample is + * undertermined (depends when the change occures). So we compute the + * timestamp from the current interrupt using the new FIFO period, the + * total number of samples and the current sample numero. + */ + if (ts->timestamp != 0) { + /* compute measured fifo period */ + fifo_mult = fifo_period / INV_ICM42600_TIMESTAMP_PERIOD; + fifo_period = fifo_mult * ts->chip_period.val; + /* computes time interval between interrupt and this sample */ + interval = (int64_t)(fifo_nb - fifo_no) * (int64_t)fifo_period; + ts->timestamp = ts->it.up - interval; + } +} diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h new file mode 100644 index 000000000000..4e4f331d4fe4 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_timestamp.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#ifndef INV_ICM42600_TIMESTAMP_H_ +#define INV_ICM42600_TIMESTAMP_H_ + +#include <linux/kernel.h> + +struct inv_icm42600_state; + +/** + * struct inv_icm42600_timestamp_interval - timestamps interval + * @lo: interval lower bound + * @up: interval upper bound + */ +struct inv_icm42600_timestamp_interval { + int64_t lo; + int64_t up; +}; + +/** + * struct inv_icm42600_timestamp_acc - accumulator for computing an estimation + * @val: current estimation of the value, the mean of all values + * @idx: current index of the next free place in values table + * @values: table of all measured values, use for computing the mean + */ +struct inv_icm42600_timestamp_acc { + uint32_t val; + size_t idx; + uint32_t values[32]; +}; + +/** + * struct inv_icm42600_timestamp - timestamp management states + * @it: interrupts interval timestamps + * @timestamp: store last timestamp for computing next data timestamp + * @mult: current internal period multiplier + * @new_mult: new set internal period multiplier (not yet effective) + * @period: measured current period of the sensor + * @chip_period: accumulator for computing internal chip period + */ +struct inv_icm42600_timestamp { + struct inv_icm42600_timestamp_interval it; + int64_t timestamp; + uint32_t mult; + uint32_t new_mult; + uint32_t period; + struct inv_icm42600_timestamp_acc chip_period; +}; + +void inv_icm42600_timestamp_init(struct inv_icm42600_timestamp *ts, + uint32_t period); + +int inv_icm42600_timestamp_setup(struct inv_icm42600_state *st); + +int inv_icm42600_timestamp_update_odr(struct inv_icm42600_timestamp *ts, + uint32_t period, bool fifo); + +void inv_icm42600_timestamp_interrupt(struct inv_icm42600_timestamp *ts, + uint32_t fifo_period, size_t fifo_nb, + size_t sensor_nb, int64_t timestamp); + +static inline int64_t +inv_icm42600_timestamp_pop(struct inv_icm42600_timestamp *ts) +{ + ts->timestamp += ts->period; + return ts->timestamp; +} + +void inv_icm42600_timestamp_apply_odr(struct inv_icm42600_timestamp *ts, + uint32_t fifo_period, size_t fifo_nb, + unsigned int fifo_no); + +static inline void +inv_icm42600_timestamp_reset(struct inv_icm42600_timestamp *ts) +{ + const struct inv_icm42600_timestamp_interval interval_init = {0LL, 0LL}; + + ts->it = interval_init; + ts->timestamp = 0; +} + +#endif diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c index 2f8560ba4572..f8f0cf716bc6 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c @@ -101,8 +101,8 @@ static int inv_mpu_process_acpi_config(struct i2c_client *client, unsigned short *primary_addr, unsigned short *secondary_addr) { + struct acpi_device *adev = ACPI_COMPANION(&client->dev); const struct acpi_device_id *id; - struct acpi_device *adev; u32 i2c_addr = 0; LIST_HEAD(resources); int ret; @@ -112,10 +112,6 @@ static int inv_mpu_process_acpi_config(struct i2c_client *client, if (!id) return -ENODEV; - adev = ACPI_COMPANION(&client->dev); - if (!adev) - return -ENODEV; - ret = acpi_dev_get_resources(adev, &resources, acpi_i2c_check_resource, &i2c_addr); if (ret < 0) @@ -135,6 +131,7 @@ int inv_mpu_acpi_create_mux_client(struct i2c_client *client) st->mux_client = NULL; if (ACPI_HANDLE(&client->dev)) { struct i2c_board_info info; + struct i2c_client *mux_client; struct acpi_device *adev; int ret = -1; @@ -172,9 +169,10 @@ int inv_mpu_acpi_create_mux_client(struct i2c_client *client) } else return 0; /* no secondary addr, which is OK */ } - st->mux_client = i2c_new_device(st->muxc->adapter[0], &info); - if (!st->mux_client) - return -ENODEV; + mux_client = i2c_new_client_device(st->muxc->adapter[0], &info); + if (IS_ERR(mux_client)) + return PTR_ERR(mux_client); + st->mux_client = mux_client; } return 0; diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index 0b8d2f7a0165..3fee3947f772 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -433,7 +433,7 @@ static int inv_mpu6050_set_gyro_fsr(struct inv_mpu6050_state *st, return regmap_write(st->map, st->reg->gyro_config, data); } -/** +/* * inv_mpu6050_set_lpf_regs() - set low pass filter registers, chip dependent * * MPU60xx/MPU9150 use only 1 register for accelerometer + gyroscope @@ -467,7 +467,7 @@ static int inv_mpu6050_set_lpf_regs(struct inv_mpu6050_state *st, return regmap_write(st->map, st->reg->accel_lpf, val); } -/** +/* * inv_mpu6050_init_config() - Initialize hardware, disable FIFO. * * Initial configuration: @@ -526,7 +526,7 @@ static int inv_mpu6050_sensor_set(struct inv_mpu6050_state *st, int reg, __be16 d = cpu_to_be16(val); ind = (axis - IIO_MOD_X) * 2; - result = regmap_bulk_write(st->map, reg + ind, (u8 *)&d, 2); + result = regmap_bulk_write(st->map, reg + ind, &d, sizeof(d)); if (result) return -EINVAL; @@ -540,7 +540,7 @@ static int inv_mpu6050_sensor_show(struct inv_mpu6050_state *st, int reg, __be16 d; ind = (axis - IIO_MOD_X) * 2; - result = regmap_bulk_read(st->map, reg + ind, (u8 *)&d, 2); + result = regmap_bulk_read(st->map, reg + ind, &d, sizeof(d)); if (result) return -EINVAL; *val = (short)be16_to_cpup(&d); @@ -847,7 +847,7 @@ error_write_raw_unlock: return result; } -/** +/* * inv_mpu6050_set_lpf() - set low pass filer based on fifo rate. * * Based on the Nyquist principle, the bandwidth of the low @@ -884,7 +884,7 @@ static int inv_mpu6050_set_lpf(struct inv_mpu6050_state *st, int rate) return 0; } -/** +/* * inv_mpu6050_fifo_rate_store() - Set fifo rate. */ static ssize_t @@ -945,7 +945,7 @@ fifo_rate_fail_unlock: return count; } -/** +/* * inv_fifo_rate_show() - Get the current sampling rate. */ static ssize_t @@ -962,7 +962,7 @@ inv_fifo_rate_show(struct device *dev, struct device_attribute *attr, return scnprintf(buf, PAGE_SIZE, "%u\n", fifo_rate); } -/** +/* * inv_attr_show() - calling this function will show current * parameters. * @@ -1248,15 +1248,34 @@ static const struct attribute_group inv_attribute_group = { .attrs = inv_attributes }; +static int inv_mpu6050_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + if (readval) + ret = regmap_read(st->map, reg, readval); + else + ret = regmap_write(st->map, reg, writeval); + mutex_unlock(&st->lock); + + return ret; +} + static const struct iio_info mpu_info = { .read_raw = &inv_mpu6050_read_raw, .write_raw = &inv_mpu6050_write_raw, .write_raw_get_fmt = &inv_write_raw_get_fmt, .attrs = &inv_attribute_group, .validate_trigger = inv_mpu6050_validate_trigger, + .debugfs_reg_access = &inv_mpu6050_reg_access, }; -/** +/* * inv_check_and_setup_chip() - check and setup chip. */ static int inv_check_and_setup_chip(struct inv_mpu6050_state *st) @@ -1511,7 +1530,6 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, } dev_set_drvdata(dev, indio_dev); - indio_dev->dev.parent = dev; /* name will be NULL when enumerated via ACPI */ if (name) indio_dev->name = name; diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c index 6993d3b87bb0..28cfae1e61cf 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c @@ -122,8 +122,8 @@ static int inv_mpu_probe(struct i2c_client *client, regmap = devm_regmap_init_i2c(client, &inv_mpu_regmap_config); if (IS_ERR(regmap)) { - dev_err(&client->dev, "Failed to register i2c regmap %d\n", - (int)PTR_ERR(regmap)); + dev_err(&client->dev, "Failed to register i2c regmap: %pe\n", + regmap); return PTR_ERR(regmap); } diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c index 9511e4715e2c..b533fa2dad0a 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -111,7 +111,7 @@ reset_fifo_fail: return result; } -/** +/* * inv_mpu6050_read_fifo() - Transfer data from hardware FIFO to KFIFO. */ irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c index 673b198e6368..6f968ce687e1 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c @@ -53,8 +53,8 @@ static int inv_mpu_probe(struct spi_device *spi) regmap = devm_regmap_init_spi(spi, &inv_mpu_regmap_config); if (IS_ERR(regmap)) { - dev_err(&spi->dev, "Failed to register spi regmap %d\n", - (int)PTR_ERR(regmap)); + dev_err(&spi->dev, "Failed to register spi regmap: %pe\n", + regmap); return PTR_ERR(regmap); } diff --git a/drivers/iio/imu/kmx61.c b/drivers/iio/imu/kmx61.c index e67466100aff..61885e99d3fc 100644 --- a/drivers/iio/imu/kmx61.c +++ b/drivers/iio/imu/kmx61.c @@ -312,10 +312,10 @@ static int kmx61_convert_wake_up_odr_to_bit(int val, int val2) /** * kmx61_set_mode() - set KMX61 device operating mode - * @data - kmx61 device private data pointer - * @mode - bitmask, indicating operating mode for @device - * @device - bitmask, indicating device for which @mode needs to be set - * @update - update stby bits stored in device's private @data + * @data: kmx61 device private data pointer + * @mode: bitmask, indicating operating mode for @device + * @device: bitmask, indicating device for which @mode needs to be set + * @update: update stby bits stored in device's private @data * * For each sensor (accelerometer/magnetometer) there are two operating modes * STANDBY and OPERATION. Neither accel nor magn can be disabled independently @@ -718,9 +718,9 @@ static int kmx61_setup_any_motion_interrupt(struct kmx61_data *data, /** * kmx61_set_power_state() - set power state for kmx61 @device - * @data - kmx61 device private pointer - * @on - power state to be set for @device - * @device - bitmask indicating device for which @on state needs to be set + * @data: kmx61 device private pointer + * @on: power state to be set for @device + * @device: bitmask indicating device for which @on state needs to be set * * Notice that when ACC power state needs to be set to ON and MAG is in * OPERATION then we know that kmx61_runtime_resume was already called @@ -1248,7 +1248,6 @@ static struct iio_dev *kmx61_indiodev_setup(struct kmx61_data *data, kmx61_set_data(indio_dev, data); - indio_dev->dev.parent = &data->client->dev; indio_dev->channels = chan; indio_dev->num_channels = num_channels; indio_dev->name = name; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 41cb20cb3809..d80ba2e688ed 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -111,7 +111,7 @@ struct st_lsm6dsx_odr { u8 val; }; -#define ST_LSM6DSX_ODR_LIST_SIZE 6 +#define ST_LSM6DSX_ODR_LIST_SIZE 8 struct st_lsm6dsx_odr_table_entry { struct st_lsm6dsx_reg reg; @@ -436,8 +436,7 @@ int st_lsm6dsx_update_watermark(struct st_lsm6dsx_sensor *sensor, u16 watermark); int st_lsm6dsx_update_fifo(struct st_lsm6dsx_sensor *sensor, bool enable); int st_lsm6dsx_flush_fifo(struct st_lsm6dsx_hw *hw); -int st_lsm6dsx_set_fifo_mode(struct st_lsm6dsx_hw *hw, - enum st_lsm6dsx_fifo_mode fifo_mode); +int st_lsm6dsx_resume_fifo(struct st_lsm6dsx_hw *hw); int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw); int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw); int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u32 odr, u8 *val); @@ -484,7 +483,7 @@ st_lsm6dsx_write_locked(struct st_lsm6dsx_hw *hw, unsigned int addr, return err; } -static const inline struct iio_mount_matrix * +static inline const struct iio_mount_matrix * st_lsm6dsx_get_mount_matrix(const struct iio_dev *iio_dev, const struct iio_chan_spec *chan) { @@ -494,7 +493,8 @@ st_lsm6dsx_get_mount_matrix(const struct iio_dev *iio_dev, return &hw->orientation; } -static const struct iio_chan_spec_ext_info st_lsm6dsx_accel_ext_info[] = { +static const +struct iio_chan_spec_ext_info __maybe_unused st_lsm6dsx_accel_ext_info[] = { IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, st_lsm6dsx_get_mount_matrix), { } }; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c index afd00daeefb2..7de10bd636ea 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c @@ -184,8 +184,8 @@ static int st_lsm6dsx_update_decimators(struct st_lsm6dsx_hw *hw) return err; } -int st_lsm6dsx_set_fifo_mode(struct st_lsm6dsx_hw *hw, - enum st_lsm6dsx_fifo_mode fifo_mode) +static int st_lsm6dsx_set_fifo_mode(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_fifo_mode fifo_mode) { unsigned int data; @@ -302,6 +302,18 @@ static int st_lsm6dsx_reset_hw_ts(struct st_lsm6dsx_hw *hw) return 0; } +int st_lsm6dsx_resume_fifo(struct st_lsm6dsx_hw *hw) +{ + int err; + + /* reset hw ts counter */ + err = st_lsm6dsx_reset_hw_ts(hw); + if (err < 0) + return err; + + return st_lsm6dsx_set_fifo_mode(hw, ST_LSM6DSX_FIFO_CONT); +} + /* * Set max bulk read to ST_LSM6DSX_MAX_WORD_LEN/ST_LSM6DSX_MAX_TAGGED_WORD_LEN * in order to avoid a kmalloc for each bus access @@ -675,12 +687,7 @@ int st_lsm6dsx_update_fifo(struct st_lsm6dsx_sensor *sensor, bool enable) goto out; if (fifo_mask) { - /* reset hw ts counter */ - err = st_lsm6dsx_reset_hw_ts(hw); - if (err < 0) - goto out; - - err = st_lsm6dsx_set_fifo_mode(hw, ST_LSM6DSX_FIFO_CONT); + err = st_lsm6dsx_resume_fifo(hw); if (err < 0) goto out; } diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 4426524b59f2..346c24281d26 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -27,7 +27,8 @@ * - FIFO size: 4KB * * - LSM6DSO/LSM6DSOX/ASM330LHH/LSM6DSR/ISM330DHCX: - * - Accelerometer/Gyroscope supported ODR [Hz]: 13, 26, 52, 104, 208, 416 + * - Accelerometer/Gyroscope supported ODR [Hz]: 13, 26, 52, 104, 208, 416, + * 833 * - Accelerometer supported full-scale [g]: +-2/+-4/+-8/+-16 * - Gyroscope supported full-scale [dps]: +-125/+-245/+-500/+-1000/+-2000 * - FIFO size: 3KB @@ -791,7 +792,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .odr_avl[3] = { 104000, 0x04 }, .odr_avl[4] = { 208000, 0x05 }, .odr_avl[5] = { 416000, 0x06 }, - .odr_len = 6, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, }, [ST_LSM6DSX_ID_GYRO] = { .reg = { @@ -804,7 +806,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .odr_avl[3] = { 104000, 0x04 }, .odr_avl[4] = { 208000, 0x05 }, .odr_avl[5] = { 416000, 0x06 }, - .odr_len = 6, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, }, }, .fs_table = { @@ -994,7 +997,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .odr_avl[3] = { 104000, 0x04 }, .odr_avl[4] = { 208000, 0x05 }, .odr_avl[5] = { 416000, 0x06 }, - .odr_len = 6, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, }, [ST_LSM6DSX_ID_GYRO] = { .reg = { @@ -1007,7 +1011,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .odr_avl[3] = { 104000, 0x04 }, .odr_avl[4] = { 208000, 0x05 }, .odr_avl[5] = { 416000, 0x06 }, - .odr_len = 6, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, }, }, .fs_table = { @@ -1171,7 +1176,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .odr_avl[3] = { 104000, 0x04 }, .odr_avl[4] = { 208000, 0x05 }, .odr_avl[5] = { 416000, 0x06 }, - .odr_len = 6, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, }, [ST_LSM6DSX_ID_GYRO] = { .reg = { @@ -1184,7 +1190,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .odr_avl[3] = { 104000, 0x04 }, .odr_avl[4] = { 208000, 0x05 }, .odr_avl[5] = { 416000, 0x06 }, - .odr_len = 6, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, }, }, .fs_table = { @@ -2145,7 +2152,6 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw, return NULL; iio_dev->modes = INDIO_DIRECT_MODE; - iio_dev->dev.parent = hw->dev; iio_dev->available_scan_masks = st_lsm6dsx_available_scan_masks; iio_dev->channels = hw->settings->channels[id].chan; iio_dev->num_channels = hw->settings->channels[id].len; @@ -2451,7 +2457,7 @@ static int __maybe_unused st_lsm6dsx_resume(struct device *dev) } if (hw->fifo_mask) - err = st_lsm6dsx_set_fifo_mode(hw, ST_LSM6DSX_FIFO_CONT); + err = st_lsm6dsx_resume_fifo(hw); return err; } diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c index 1cf98195f84d..ed83471dc7dd 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c @@ -88,6 +88,69 @@ static const struct st_lsm6dsx_ext_dev_settings st_lsm6dsx_ext_dev_table[] = { .len = 6, }, }, + /* LIS3MDL */ + { + .i2c_addr = { 0x1e }, + .wai = { + .addr = 0x0f, + .val = 0x3d, + }, + .id = ST_LSM6DSX_ID_MAGN, + .odr_table = { + .reg = { + .addr = 0x20, + .mask = GENMASK(4, 2), + }, + .odr_avl[0] = { 1000, 0x0 }, + .odr_avl[1] = { 2000, 0x1 }, + .odr_avl[2] = { 3000, 0x2 }, + .odr_avl[3] = { 5000, 0x3 }, + .odr_avl[4] = { 10000, 0x4 }, + .odr_avl[5] = { 20000, 0x5 }, + .odr_avl[6] = { 40000, 0x6 }, + .odr_avl[7] = { 80000, 0x7 }, + .odr_len = 8, + }, + .fs_table = { + .reg = { + .addr = 0x21, + .mask = GENMASK(6, 5), + }, + .fs_avl[0] = { + .gain = 146, + .val = 0x00, + }, /* 4000 uG/LSB */ + .fs_avl[1] = { + .gain = 292, + .val = 0x01, + }, /* 8000 uG/LSB */ + .fs_avl[2] = { + .gain = 438, + .val = 0x02, + }, /* 12000 uG/LSB */ + .fs_avl[3] = { + .gain = 584, + .val = 0x03, + }, /* 16000 uG/LSB */ + .fs_len = 4, + }, + .pwr_table = { + .reg = { + .addr = 0x22, + .mask = GENMASK(1, 0), + }, + .off_val = 0x2, + .on_val = 0x0, + }, + .bdu = { + .addr = 0x24, + .mask = BIT(6), + }, + .out = { + .addr = 0x28, + .len = 6, + }, + }, }; static void st_lsm6dsx_shub_wait_complete(struct st_lsm6dsx_hw *hw) @@ -100,7 +163,7 @@ static void st_lsm6dsx_shub_wait_complete(struct st_lsm6dsx_hw *hw) msleep((2000000U / odr) + 1); } -/** +/* * st_lsm6dsx_shub_read_output - read i2c controller register * * Read st_lsm6dsx i2c controller register @@ -132,7 +195,7 @@ out: return err; } -/** +/* * st_lsm6dsx_shub_write_reg - write i2c controller register * * Write st_lsm6dsx i2c controller register @@ -210,7 +273,7 @@ out: return err; } -/** +/* * st_lsm6dsx_shub_read - read data from slave device register * * Read data from slave device register. SLV0 is used for @@ -260,7 +323,7 @@ st_lsm6dsx_shub_read(struct st_lsm6dsx_sensor *sensor, u8 addr, sizeof(config)); } -/** +/* * st_lsm6dsx_shub_write - write data to slave device register * * Write data from slave device register. SLV0 is used for @@ -519,6 +582,36 @@ st_lsm6dsx_shub_read_raw(struct iio_dev *iio_dev, } static int +st_lsm6dsx_shub_set_full_scale(struct st_lsm6dsx_sensor *sensor, + u32 gain) +{ + const struct st_lsm6dsx_fs_table_entry *fs_table; + int i, err; + + fs_table = &sensor->ext_info.settings->fs_table; + if (!fs_table->reg.addr) + return -ENOTSUPP; + + for (i = 0; i < fs_table->fs_len; i++) { + if (fs_table->fs_avl[i].gain == gain) + break; + } + + if (i == fs_table->fs_len) + return -EINVAL; + + err = st_lsm6dsx_shub_write_with_mask(sensor, fs_table->reg.addr, + fs_table->reg.mask, + fs_table->fs_avl[i].val); + if (err < 0) + return err; + + sensor->gain = gain; + + return 0; +} + +static int st_lsm6dsx_shub_write_raw(struct iio_dev *iio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) @@ -554,6 +647,9 @@ st_lsm6dsx_shub_write_raw(struct iio_dev *iio_dev, } break; } + case IIO_CHAN_INFO_SCALE: + err = st_lsm6dsx_shub_set_full_scale(sensor, val2); + break; default: err = -EINVAL; break; @@ -639,7 +735,6 @@ st_lsm6dsx_shub_alloc_iiodev(struct st_lsm6dsx_hw *hw, return NULL; iio_dev->modes = INDIO_DIRECT_MODE; - iio_dev->dev.parent = hw->dev; iio_dev->info = &st_lsm6dsx_ext_info; sensor = iio_priv(iio_dev); |