mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-16 20:42:29 -04:00
iio: imu: inv_icm45600: add new inv_icm45600 driver
Core component of a new driver for InvenSense ICM-45600 devices. It includes registers definition, main probe/setup, and device utility functions. ICM-456xx devices are latest generation of 6-axis IMU, gyroscope+accelerometer and temperature sensor. This device includes a 8K FIFO, supports I2C/I3C/SPI, and provides intelligent motion features like pedometer, tilt detection, and tap detection. Signed-off-by: Remi Buisson <remi.buisson@tdk.com> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
This commit is contained in:
committed by
Jonathan Cameron
parent
7d55d00185
commit
7ff021a3fa
@@ -109,6 +109,7 @@ config KMX61
|
|||||||
be called kmx61.
|
be called kmx61.
|
||||||
|
|
||||||
source "drivers/iio/imu/inv_icm42600/Kconfig"
|
source "drivers/iio/imu/inv_icm42600/Kconfig"
|
||||||
|
source "drivers/iio/imu/inv_icm45600/Kconfig"
|
||||||
source "drivers/iio/imu/inv_mpu6050/Kconfig"
|
source "drivers/iio/imu/inv_mpu6050/Kconfig"
|
||||||
|
|
||||||
config SMI240
|
config SMI240
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ obj-$(CONFIG_FXOS8700_I2C) += fxos8700_i2c.o
|
|||||||
obj-$(CONFIG_FXOS8700_SPI) += fxos8700_spi.o
|
obj-$(CONFIG_FXOS8700_SPI) += fxos8700_spi.o
|
||||||
|
|
||||||
obj-y += inv_icm42600/
|
obj-y += inv_icm42600/
|
||||||
|
obj-y += inv_icm45600/
|
||||||
obj-y += inv_mpu6050/
|
obj-y += inv_mpu6050/
|
||||||
|
|
||||||
obj-$(CONFIG_KMX61) += kmx61.o
|
obj-$(CONFIG_KMX61) += kmx61.o
|
||||||
|
|||||||
5
drivers/iio/imu/inv_icm45600/Kconfig
Normal file
5
drivers/iio/imu/inv_icm45600/Kconfig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
config INV_ICM45600
|
||||||
|
tristate
|
||||||
|
select IIO_INV_SENSORS_TIMESTAMP
|
||||||
4
drivers/iio/imu/inv_icm45600/Makefile
Normal file
4
drivers/iio/imu/inv_icm45600/Makefile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
obj-$(CONFIG_INV_ICM45600) += inv-icm45600.o
|
||||||
|
inv-icm45600-y += inv_icm45600_core.o
|
||||||
331
drivers/iio/imu/inv_icm45600/inv_icm45600.h
Normal file
331
drivers/iio/imu/inv_icm45600/inv_icm45600.h
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
/* Copyright (C) 2025 Invensense, Inc. */
|
||||||
|
|
||||||
|
#ifndef INV_ICM45600_H_
|
||||||
|
#define INV_ICM45600_H_
|
||||||
|
|
||||||
|
#include <linux/bits.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/regulator/consumer.h>
|
||||||
|
#include <linux/sizes.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#include <linux/iio/common/inv_sensors_timestamp.h>
|
||||||
|
#include <linux/iio/iio.h>
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_BANK_MASK GENMASK(15, 8)
|
||||||
|
#define INV_ICM45600_REG_ADDR_MASK GENMASK(7, 0)
|
||||||
|
|
||||||
|
enum inv_icm45600_sensor_mode {
|
||||||
|
INV_ICM45600_SENSOR_MODE_OFF,
|
||||||
|
INV_ICM45600_SENSOR_MODE_STANDBY,
|
||||||
|
INV_ICM45600_SENSOR_MODE_LOW_POWER,
|
||||||
|
INV_ICM45600_SENSOR_MODE_LOW_NOISE,
|
||||||
|
INV_ICM45600_SENSOR_MODE_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
/* gyroscope fullscale values */
|
||||||
|
enum inv_icm45600_gyro_fs {
|
||||||
|
INV_ICM45600_GYRO_FS_2000DPS,
|
||||||
|
INV_ICM45600_GYRO_FS_1000DPS,
|
||||||
|
INV_ICM45600_GYRO_FS_500DPS,
|
||||||
|
INV_ICM45600_GYRO_FS_250DPS,
|
||||||
|
INV_ICM45600_GYRO_FS_125DPS,
|
||||||
|
INV_ICM45600_GYRO_FS_62_5DPS,
|
||||||
|
INV_ICM45600_GYRO_FS_31_25DPS,
|
||||||
|
INV_ICM45600_GYRO_FS_15_625DPS,
|
||||||
|
INV_ICM45600_GYRO_FS_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
enum inv_icm45686_gyro_fs {
|
||||||
|
INV_ICM45686_GYRO_FS_4000DPS,
|
||||||
|
INV_ICM45686_GYRO_FS_2000DPS,
|
||||||
|
INV_ICM45686_GYRO_FS_1000DPS,
|
||||||
|
INV_ICM45686_GYRO_FS_500DPS,
|
||||||
|
INV_ICM45686_GYRO_FS_250DPS,
|
||||||
|
INV_ICM45686_GYRO_FS_125DPS,
|
||||||
|
INV_ICM45686_GYRO_FS_62_5DPS,
|
||||||
|
INV_ICM45686_GYRO_FS_31_25DPS,
|
||||||
|
INV_ICM45686_GYRO_FS_15_625DPS,
|
||||||
|
INV_ICM45686_GYRO_FS_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
/* accelerometer fullscale values */
|
||||||
|
enum inv_icm45600_accel_fs {
|
||||||
|
INV_ICM45600_ACCEL_FS_16G,
|
||||||
|
INV_ICM45600_ACCEL_FS_8G,
|
||||||
|
INV_ICM45600_ACCEL_FS_4G,
|
||||||
|
INV_ICM45600_ACCEL_FS_2G,
|
||||||
|
INV_ICM45600_ACCEL_FS_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
enum inv_icm45686_accel_fs {
|
||||||
|
INV_ICM45686_ACCEL_FS_32G,
|
||||||
|
INV_ICM45686_ACCEL_FS_16G,
|
||||||
|
INV_ICM45686_ACCEL_FS_8G,
|
||||||
|
INV_ICM45686_ACCEL_FS_4G,
|
||||||
|
INV_ICM45686_ACCEL_FS_2G,
|
||||||
|
INV_ICM45686_ACCEL_FS_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ODR suffixed by LN or LP are Low-Noise or Low-Power mode only */
|
||||||
|
enum inv_icm45600_odr {
|
||||||
|
INV_ICM45600_ODR_6400HZ_LN = 0x03,
|
||||||
|
INV_ICM45600_ODR_3200HZ_LN,
|
||||||
|
INV_ICM45600_ODR_1600HZ_LN,
|
||||||
|
INV_ICM45600_ODR_800HZ_LN,
|
||||||
|
INV_ICM45600_ODR_400HZ,
|
||||||
|
INV_ICM45600_ODR_200HZ,
|
||||||
|
INV_ICM45600_ODR_100HZ,
|
||||||
|
INV_ICM45600_ODR_50HZ,
|
||||||
|
INV_ICM45600_ODR_25HZ,
|
||||||
|
INV_ICM45600_ODR_12_5HZ,
|
||||||
|
INV_ICM45600_ODR_6_25HZ_LP,
|
||||||
|
INV_ICM45600_ODR_3_125HZ_LP,
|
||||||
|
INV_ICM45600_ODR_1_5625HZ_LP,
|
||||||
|
INV_ICM45600_ODR_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
struct inv_icm45600_sensor_conf {
|
||||||
|
u8 mode;
|
||||||
|
u8 fs;
|
||||||
|
u8 odr;
|
||||||
|
u8 filter;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct inv_icm45600_conf {
|
||||||
|
struct inv_icm45600_sensor_conf gyro;
|
||||||
|
struct inv_icm45600_sensor_conf accel;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct inv_icm45600_suspended {
|
||||||
|
enum inv_icm45600_sensor_mode gyro;
|
||||||
|
enum inv_icm45600_sensor_mode accel;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct inv_icm45600_chip_info {
|
||||||
|
u8 whoami;
|
||||||
|
const char *name;
|
||||||
|
const struct inv_icm45600_conf *conf;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct inv_icm45600_state - driver state variables
|
||||||
|
* @lock: lock for serializing multiple registers access.
|
||||||
|
* @map: regmap pointer.
|
||||||
|
* @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.
|
||||||
|
* @chip_info: chip driver data.
|
||||||
|
* @timestamp: interrupt timestamps.
|
||||||
|
* @buffer: data transfer buffer aligned for DMA.
|
||||||
|
*/
|
||||||
|
struct inv_icm45600_state {
|
||||||
|
struct mutex lock;
|
||||||
|
struct regmap *map;
|
||||||
|
struct regulator *vddio_supply;
|
||||||
|
struct iio_mount_matrix orientation;
|
||||||
|
struct inv_icm45600_conf conf;
|
||||||
|
struct inv_icm45600_suspended suspended;
|
||||||
|
struct iio_dev *indio_gyro;
|
||||||
|
struct iio_dev *indio_accel;
|
||||||
|
const struct inv_icm45600_chip_info *chip_info;
|
||||||
|
struct {
|
||||||
|
s64 gyro;
|
||||||
|
s64 accel;
|
||||||
|
} timestamp;
|
||||||
|
union {
|
||||||
|
u8 buff[2];
|
||||||
|
__le16 u16;
|
||||||
|
u8 ireg[3];
|
||||||
|
} buffer __aligned(IIO_DMA_MINALIGN);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct inv_icm45600_sensor_state - sensor state variables
|
||||||
|
* @scales: table of scales.
|
||||||
|
* @scales_len: length (nb of items) of the scales table.
|
||||||
|
* @power_mode: sensor requested power mode (for common frequencies)
|
||||||
|
* @ts: timestamp module states.
|
||||||
|
*/
|
||||||
|
struct inv_icm45600_sensor_state {
|
||||||
|
const int *scales;
|
||||||
|
size_t scales_len;
|
||||||
|
enum inv_icm45600_sensor_mode power_mode;
|
||||||
|
struct inv_sensors_timestamp ts;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Virtual register addresses: @bank on MSB (16 bits), @address on LSB */
|
||||||
|
|
||||||
|
/* Indirect register access */
|
||||||
|
#define INV_ICM45600_REG_IREG_ADDR 0x7C
|
||||||
|
#define INV_ICM45600_REG_IREG_DATA 0x7E
|
||||||
|
|
||||||
|
/* Direct acces registers */
|
||||||
|
#define INV_ICM45600_REG_MISC2 0x007F
|
||||||
|
#define INV_ICM45600_MISC2_SOFT_RESET BIT(1)
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_DRIVE_CONFIG0 0x0032
|
||||||
|
#define INV_ICM45600_DRIVE_CONFIG0_SPI_MASK GENMASK(3, 1)
|
||||||
|
#define INV_ICM45600_SPI_SLEW_RATE_0_5NS 6
|
||||||
|
#define INV_ICM45600_SPI_SLEW_RATE_4NS 5
|
||||||
|
#define INV_ICM45600_SPI_SLEW_RATE_5NS 4
|
||||||
|
#define INV_ICM45600_SPI_SLEW_RATE_7NS 3
|
||||||
|
#define INV_ICM45600_SPI_SLEW_RATE_10NS 2
|
||||||
|
#define INV_ICM45600_SPI_SLEW_RATE_14NS 1
|
||||||
|
#define INV_ICM45600_SPI_SLEW_RATE_38NS 0
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_INT1_CONFIG2 0x0018
|
||||||
|
#define INV_ICM45600_INT1_CONFIG2_PUSH_PULL BIT(2)
|
||||||
|
#define INV_ICM45600_INT1_CONFIG2_LATCHED BIT(1)
|
||||||
|
#define INV_ICM45600_INT1_CONFIG2_ACTIVE_HIGH BIT(0)
|
||||||
|
#define INV_ICM45600_INT1_CONFIG2_ACTIVE_LOW 0x00
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_FIFO_CONFIG0 0x001D
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG0_MODE_MASK GENMASK(7, 6)
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS 0
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG0_MODE_STREAM 1
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG0_MODE_STOP_ON_FULL 2
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MAX 0x1F
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_FIFO_CONFIG2 0x0020
|
||||||
|
#define INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH BIT(7)
|
||||||
|
#define INV_ICM45600_REG_FIFO_CONFIG2_WM_GT_TH BIT(3)
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_FIFO_CONFIG3 0x0021
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG3_ES1_EN BIT(5)
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG3_ES0_EN BIT(4)
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG3_HIRES_EN BIT(3)
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG3_GYRO_EN BIT(2)
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG3_ACCEL_EN BIT(1)
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG3_IF_EN BIT(0)
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_FIFO_CONFIG4 0x0022
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG4_COMP_EN BIT(2)
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG4_TMST_FSYNC_EN BIT(1)
|
||||||
|
#define INV_ICM45600_FIFO_CONFIG4_ES0_9B BIT(0)
|
||||||
|
|
||||||
|
/* all sensor data are 16 bits (2 registers wide) in big-endian */
|
||||||
|
#define INV_ICM45600_REG_TEMP_DATA 0x000C
|
||||||
|
#define INV_ICM45600_REG_ACCEL_DATA_X 0x0000
|
||||||
|
#define INV_ICM45600_REG_ACCEL_DATA_Y 0x0002
|
||||||
|
#define INV_ICM45600_REG_ACCEL_DATA_Z 0x0004
|
||||||
|
#define INV_ICM45600_REG_GYRO_DATA_X 0x0006
|
||||||
|
#define INV_ICM45600_REG_GYRO_DATA_Y 0x0008
|
||||||
|
#define INV_ICM45600_REG_GYRO_DATA_Z 0x000A
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_INT_STATUS 0x0019
|
||||||
|
#define INV_ICM45600_INT_STATUS_RESET_DONE BIT(7)
|
||||||
|
#define INV_ICM45600_INT_STATUS_AUX1_AGC_RDY BIT(6)
|
||||||
|
#define INV_ICM45600_INT_STATUS_AP_AGC_RDY BIT(5)
|
||||||
|
#define INV_ICM45600_INT_STATUS_AP_FSYNC BIT(4)
|
||||||
|
#define INV_ICM45600_INT_STATUS_AUX1_DRDY BIT(3)
|
||||||
|
#define INV_ICM45600_INT_STATUS_DATA_RDY BIT(2)
|
||||||
|
#define INV_ICM45600_INT_STATUS_FIFO_THS BIT(1)
|
||||||
|
#define INV_ICM45600_INT_STATUS_FIFO_FULL BIT(0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIFO access registers
|
||||||
|
* FIFO count is 16 bits (2 registers)
|
||||||
|
* FIFO data is a continuous read register to read FIFO content
|
||||||
|
*/
|
||||||
|
#define INV_ICM45600_REG_FIFO_COUNT 0x0012
|
||||||
|
#define INV_ICM45600_REG_FIFO_DATA 0x0014
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_PWR_MGMT0 0x0010
|
||||||
|
#define INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK GENMASK(3, 2)
|
||||||
|
#define INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK GENMASK(1, 0)
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_ACCEL_CONFIG0 0x001B
|
||||||
|
#define INV_ICM45600_ACCEL_CONFIG0_FS_MASK GENMASK(6, 4)
|
||||||
|
#define INV_ICM45600_ACCEL_CONFIG0_ODR_MASK GENMASK(3, 0)
|
||||||
|
#define INV_ICM45600_REG_GYRO_CONFIG0 0x001C
|
||||||
|
#define INV_ICM45600_GYRO_CONFIG0_FS_MASK GENMASK(7, 4)
|
||||||
|
#define INV_ICM45600_GYRO_CONFIG0_ODR_MASK GENMASK(3, 0)
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_SMC_CONTROL_0 0xA258
|
||||||
|
#define INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL BIT(4)
|
||||||
|
#define INV_ICM45600_SMC_CONTROL_0_TMST_EN BIT(0)
|
||||||
|
|
||||||
|
/* FIFO watermark is 16 bits (2 registers wide) in little-endian */
|
||||||
|
#define INV_ICM45600_REG_FIFO_WATERMARK 0x001E
|
||||||
|
|
||||||
|
/* FIFO is configured for 8kb */
|
||||||
|
#define INV_ICM45600_FIFO_SIZE_MAX SZ_8K
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_INT1_CONFIG0 0x0016
|
||||||
|
#define INV_ICM45600_INT1_CONFIG0_RESET_DONE_EN BIT(7)
|
||||||
|
#define INV_ICM45600_INT1_CONFIG0_AUX1_AGC_RDY_EN BIT(6)
|
||||||
|
#define INV_ICM45600_INT1_CONFIG0_AP_AGC_RDY_EN BIT(5)
|
||||||
|
#define INV_ICM45600_INT1_CONFIG0_AP_FSYNC_EN BIT(4)
|
||||||
|
#define INV_ICM45600_INT1_CONFIG0_AUX1_DRDY_EN BIT(3)
|
||||||
|
#define INV_ICM45600_INT1_CONFIG0_DRDY_EN BIT(2)
|
||||||
|
#define INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN BIT(1)
|
||||||
|
#define INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN BIT(0)
|
||||||
|
|
||||||
|
#define INV_ICM45600_REG_WHOAMI 0x0072
|
||||||
|
#define INV_ICM45600_WHOAMI_ICM45605 0xE5
|
||||||
|
#define INV_ICM45600_WHOAMI_ICM45686 0xE9
|
||||||
|
#define INV_ICM45600_WHOAMI_ICM45688P 0xE7
|
||||||
|
#define INV_ICM45600_WHOAMI_ICM45608 0x81
|
||||||
|
#define INV_ICM45600_WHOAMI_ICM45634 0x82
|
||||||
|
#define INV_ICM45600_WHOAMI_ICM45689 0x83
|
||||||
|
#define INV_ICM45600_WHOAMI_ICM45606 0x84
|
||||||
|
#define INV_ICM45600_WHOAMI_ICM45687 0x85
|
||||||
|
|
||||||
|
/* Gyro USER offset */
|
||||||
|
#define INV_ICM45600_IPREG_SYS1_REG_42 0xA42A
|
||||||
|
#define INV_ICM45600_IPREG_SYS1_REG_56 0xA438
|
||||||
|
#define INV_ICM45600_IPREG_SYS1_REG_70 0xA446
|
||||||
|
#define INV_ICM45600_GYRO_OFFUSER_MASK GENMASK(13, 0)
|
||||||
|
/* Gyro Averaging filter */
|
||||||
|
#define INV_ICM45600_IPREG_SYS1_REG_170 0xA4AA
|
||||||
|
#define INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK GENMASK(4, 1)
|
||||||
|
#define INV_ICM45600_GYRO_LP_AVG_SEL_8X 5
|
||||||
|
#define INV_ICM45600_GYRO_LP_AVG_SEL_2X 1
|
||||||
|
/* Accel USER offset */
|
||||||
|
#define INV_ICM45600_IPREG_SYS2_REG_24 0xA518
|
||||||
|
#define INV_ICM45600_IPREG_SYS2_REG_32 0xA520
|
||||||
|
#define INV_ICM45600_IPREG_SYS2_REG_40 0xA528
|
||||||
|
#define INV_ICM45600_ACCEL_OFFUSER_MASK GENMASK(13, 0)
|
||||||
|
/* Accel averaging filter */
|
||||||
|
#define INV_ICM45600_IPREG_SYS2_REG_129 0xA581
|
||||||
|
#define INV_ICM45600_ACCEL_LP_AVG_SEL_1X 0x0000
|
||||||
|
#define INV_ICM45600_ACCEL_LP_AVG_SEL_4X 0x0002
|
||||||
|
|
||||||
|
/* Sleep times required by the driver */
|
||||||
|
#define INV_ICM45600_ACCEL_STARTUP_TIME_MS 60
|
||||||
|
#define INV_ICM45600_GYRO_STARTUP_TIME_MS 60
|
||||||
|
#define INV_ICM45600_GYRO_STOP_TIME_MS 150
|
||||||
|
#define INV_ICM45600_IREG_DELAY_US 4
|
||||||
|
|
||||||
|
typedef int (*inv_icm45600_bus_setup)(struct inv_icm45600_state *);
|
||||||
|
|
||||||
|
extern const struct dev_pm_ops inv_icm45600_pm_ops;
|
||||||
|
|
||||||
|
const struct iio_mount_matrix *
|
||||||
|
inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev,
|
||||||
|
const struct iio_chan_spec *chan);
|
||||||
|
|
||||||
|
u32 inv_icm45600_odr_to_period(enum inv_icm45600_odr odr);
|
||||||
|
|
||||||
|
int inv_icm45600_set_accel_conf(struct inv_icm45600_state *st,
|
||||||
|
struct inv_icm45600_sensor_conf *conf,
|
||||||
|
unsigned int *sleep_ms);
|
||||||
|
|
||||||
|
int inv_icm45600_set_gyro_conf(struct inv_icm45600_state *st,
|
||||||
|
struct inv_icm45600_sensor_conf *conf,
|
||||||
|
unsigned int *sleep_ms);
|
||||||
|
|
||||||
|
int inv_icm45600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
|
||||||
|
unsigned int writeval, unsigned int *readval);
|
||||||
|
|
||||||
|
int inv_icm45600_core_probe(struct regmap *regmap,
|
||||||
|
const struct inv_icm45600_chip_info *chip_info,
|
||||||
|
bool reset, inv_icm45600_bus_setup bus_setup);
|
||||||
|
|
||||||
|
#endif
|
||||||
621
drivers/iio/imu/inv_icm45600/inv_icm45600_core.c
Normal file
621
drivers/iio/imu/inv_icm45600/inv_icm45600_core.c
Normal file
@@ -0,0 +1,621 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/* Copyright (C) 2025 Invensense, Inc. */
|
||||||
|
|
||||||
|
#include <linux/bitfield.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/err.h>
|
||||||
|
#include <linux/limits.h>
|
||||||
|
#include <linux/minmax.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/regulator/consumer.h>
|
||||||
|
#include <linux/time.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#include <asm/byteorder.h>
|
||||||
|
|
||||||
|
#include <linux/iio/iio.h>
|
||||||
|
|
||||||
|
#include "inv_icm45600.h"
|
||||||
|
|
||||||
|
static int inv_icm45600_ireg_read(struct regmap *map, unsigned int reg,
|
||||||
|
u8 *data, size_t count)
|
||||||
|
{
|
||||||
|
const struct device *dev = regmap_get_device(map);
|
||||||
|
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||||
|
unsigned int d;
|
||||||
|
size_t i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
st->buffer.ireg[0] = FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg);
|
||||||
|
st->buffer.ireg[1] = FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg);
|
||||||
|
|
||||||
|
/* Burst write address. */
|
||||||
|
ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, st->buffer.ireg, 2);
|
||||||
|
/*
|
||||||
|
* Wait while the device is busy processing the address.
|
||||||
|
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
|
||||||
|
*/
|
||||||
|
fsleep(INV_ICM45600_IREG_DELAY_US);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Read the data. */
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
ret = regmap_read(map, INV_ICM45600_REG_IREG_DATA, &d);
|
||||||
|
/*
|
||||||
|
* Wait while the device is busy processing the address.
|
||||||
|
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
|
||||||
|
*/
|
||||||
|
fsleep(INV_ICM45600_IREG_DELAY_US);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
data[i] = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int inv_icm45600_ireg_write(struct regmap *map, unsigned int reg,
|
||||||
|
const u8 *data, size_t count)
|
||||||
|
{
|
||||||
|
const struct device *dev = regmap_get_device(map);
|
||||||
|
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||||
|
size_t i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
st->buffer.ireg[0] = FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg);
|
||||||
|
st->buffer.ireg[1] = FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg);
|
||||||
|
st->buffer.ireg[2] = data[0];
|
||||||
|
|
||||||
|
/* Burst write address and first byte. */
|
||||||
|
ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, st->buffer.ireg, 3);
|
||||||
|
/*
|
||||||
|
* Wait while the device is busy processing the address.
|
||||||
|
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
|
||||||
|
*/
|
||||||
|
fsleep(INV_ICM45600_IREG_DELAY_US);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Write the remaining bytes. */
|
||||||
|
for (i = 1; i < count; i++) {
|
||||||
|
ret = regmap_write(map, INV_ICM45600_REG_IREG_DATA, data[i]);
|
||||||
|
/*
|
||||||
|
* Wait while the device is busy processing the address.
|
||||||
|
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
|
||||||
|
*/
|
||||||
|
fsleep(INV_ICM45600_IREG_DELAY_US);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int inv_icm45600_read(void *context, const void *reg_buf, size_t reg_size,
|
||||||
|
void *val_buf, size_t val_size)
|
||||||
|
{
|
||||||
|
unsigned int reg = be16_to_cpup(reg_buf);
|
||||||
|
struct regmap *map = context;
|
||||||
|
|
||||||
|
if (FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg))
|
||||||
|
return inv_icm45600_ireg_read(map, reg, val_buf, val_size);
|
||||||
|
|
||||||
|
return regmap_bulk_read(map, FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg),
|
||||||
|
val_buf, val_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int inv_icm45600_write(void *context, const void *data, size_t count)
|
||||||
|
{
|
||||||
|
const u8 *d = data;
|
||||||
|
unsigned int reg = be16_to_cpup(data);
|
||||||
|
struct regmap *map = context;
|
||||||
|
|
||||||
|
if (FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg))
|
||||||
|
return inv_icm45600_ireg_write(map, reg, d + 2, count - 2);
|
||||||
|
|
||||||
|
return regmap_bulk_write(map, FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg),
|
||||||
|
d + 2, count - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct regmap_bus inv_icm45600_regmap_bus = {
|
||||||
|
.read = inv_icm45600_read,
|
||||||
|
.write = inv_icm45600_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct regmap_config inv_icm45600_regmap_config = {
|
||||||
|
.reg_bits = 16,
|
||||||
|
.val_bits = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct iio_mount_matrix *
|
||||||
|
inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev,
|
||||||
|
const struct iio_chan_spec *chan)
|
||||||
|
{
|
||||||
|
const struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||||
|
|
||||||
|
return &st->orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 inv_icm45600_odr_to_period(enum inv_icm45600_odr odr)
|
||||||
|
{
|
||||||
|
static const u32 odr_periods[INV_ICM45600_ODR_MAX] = {
|
||||||
|
/* 3 first values are reserved, left to 0 */
|
||||||
|
[INV_ICM45600_ODR_6400HZ_LN] = 156250,
|
||||||
|
[INV_ICM45600_ODR_3200HZ_LN] = 312500,
|
||||||
|
[INV_ICM45600_ODR_1600HZ_LN] = 625000,
|
||||||
|
[INV_ICM45600_ODR_800HZ_LN] = 1250000,
|
||||||
|
[INV_ICM45600_ODR_400HZ] = 2500000,
|
||||||
|
[INV_ICM45600_ODR_200HZ] = 5000000,
|
||||||
|
[INV_ICM45600_ODR_100HZ] = 10000000,
|
||||||
|
[INV_ICM45600_ODR_50HZ] = 20000000,
|
||||||
|
[INV_ICM45600_ODR_25HZ] = 40000000,
|
||||||
|
[INV_ICM45600_ODR_12_5HZ] = 80000000,
|
||||||
|
[INV_ICM45600_ODR_6_25HZ_LP] = 160000000,
|
||||||
|
[INV_ICM45600_ODR_3_125HZ_LP] = 320000000,
|
||||||
|
[INV_ICM45600_ODR_1_5625HZ_LP] = 640000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
return odr_periods[odr];
|
||||||
|
}
|
||||||
|
|
||||||
|
static int inv_icm45600_set_pwr_mgmt0(struct inv_icm45600_state *st,
|
||||||
|
enum inv_icm45600_sensor_mode gyro,
|
||||||
|
enum inv_icm45600_sensor_mode accel,
|
||||||
|
unsigned int *sleep_ms)
|
||||||
|
{
|
||||||
|
enum inv_icm45600_sensor_mode oldgyro = st->conf.gyro.mode;
|
||||||
|
enum inv_icm45600_sensor_mode oldaccel = st->conf.accel.mode;
|
||||||
|
unsigned int sleepval;
|
||||||
|
unsigned int val;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* if nothing changed, exit */
|
||||||
|
if (gyro == oldgyro && accel == oldaccel)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
val = FIELD_PREP(INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK, gyro) |
|
||||||
|
FIELD_PREP(INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK, accel);
|
||||||
|
ret = regmap_write(st->map, INV_ICM45600_REG_PWR_MGMT0, val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
st->conf.gyro.mode = gyro;
|
||||||
|
st->conf.accel.mode = accel;
|
||||||
|
|
||||||
|
/* Compute the required wait time for sensors to stabilize. */
|
||||||
|
sleepval = 0;
|
||||||
|
if (accel != oldaccel && oldaccel == INV_ICM45600_SENSOR_MODE_OFF)
|
||||||
|
sleepval = max(sleepval, INV_ICM45600_ACCEL_STARTUP_TIME_MS);
|
||||||
|
|
||||||
|
if (gyro != oldgyro) {
|
||||||
|
if (oldgyro == INV_ICM45600_SENSOR_MODE_OFF)
|
||||||
|
sleepval = max(sleepval, INV_ICM45600_GYRO_STARTUP_TIME_MS);
|
||||||
|
else if (gyro == INV_ICM45600_SENSOR_MODE_OFF)
|
||||||
|
sleepval = max(sleepval, INV_ICM45600_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void inv_icm45600_set_default_conf(struct inv_icm45600_sensor_conf *conf,
|
||||||
|
struct inv_icm45600_sensor_conf *oldconf)
|
||||||
|
{
|
||||||
|
/* Sanitize missing values with current values. */
|
||||||
|
if (conf->mode == U8_MAX)
|
||||||
|
conf->mode = oldconf->mode;
|
||||||
|
if (conf->fs == U8_MAX)
|
||||||
|
conf->fs = oldconf->fs;
|
||||||
|
if (conf->odr == U8_MAX)
|
||||||
|
conf->odr = oldconf->odr;
|
||||||
|
if (conf->filter == U8_MAX)
|
||||||
|
conf->filter = oldconf->filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
int inv_icm45600_set_accel_conf(struct inv_icm45600_state *st,
|
||||||
|
struct inv_icm45600_sensor_conf *conf,
|
||||||
|
unsigned int *sleep_ms)
|
||||||
|
{
|
||||||
|
struct inv_icm45600_sensor_conf *oldconf = &st->conf.accel;
|
||||||
|
unsigned int val;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
inv_icm45600_set_default_conf(conf, oldconf);
|
||||||
|
|
||||||
|
/* Force the power mode against the ODR when sensor is on. */
|
||||||
|
if (conf->mode > INV_ICM45600_SENSOR_MODE_STANDBY) {
|
||||||
|
if (conf->odr <= INV_ICM45600_ODR_800HZ_LN) {
|
||||||
|
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE;
|
||||||
|
} else {
|
||||||
|
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
|
||||||
|
/* sanitize averaging value depending on ODR for low-power mode */
|
||||||
|
/* maximum 1x @400Hz */
|
||||||
|
if (conf->odr == INV_ICM45600_ODR_400HZ)
|
||||||
|
conf->filter = INV_ICM45600_ACCEL_LP_AVG_SEL_1X;
|
||||||
|
else
|
||||||
|
conf->filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set accel fullscale & odr. */
|
||||||
|
if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {
|
||||||
|
val = FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_FS_MASK, conf->fs) |
|
||||||
|
FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_ODR_MASK, conf->odr);
|
||||||
|
ret = regmap_write(st->map, INV_ICM45600_REG_ACCEL_CONFIG0, val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
oldconf->fs = conf->fs;
|
||||||
|
oldconf->odr = conf->odr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set accel low-power average filter. */
|
||||||
|
if (conf->filter != oldconf->filter) {
|
||||||
|
ret = regmap_write(st->map, INV_ICM45600_IPREG_SYS2_REG_129,
|
||||||
|
conf->filter);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
oldconf->filter = conf->filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update the sensor accel mode. */
|
||||||
|
return inv_icm45600_set_pwr_mgmt0(st, st->conf.gyro.mode, conf->mode,
|
||||||
|
sleep_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
int inv_icm45600_set_gyro_conf(struct inv_icm45600_state *st,
|
||||||
|
struct inv_icm45600_sensor_conf *conf,
|
||||||
|
unsigned int *sleep_ms)
|
||||||
|
{
|
||||||
|
struct inv_icm45600_sensor_conf *oldconf = &st->conf.gyro;
|
||||||
|
unsigned int val;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
inv_icm45600_set_default_conf(conf, oldconf);
|
||||||
|
|
||||||
|
/* Force the power mode against ODR when sensor is on. */
|
||||||
|
if (conf->mode > INV_ICM45600_SENSOR_MODE_STANDBY) {
|
||||||
|
if (conf->odr >= INV_ICM45600_ODR_6_25HZ_LP) {
|
||||||
|
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
|
||||||
|
conf->filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X;
|
||||||
|
} else {
|
||||||
|
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set gyro fullscale & odr. */
|
||||||
|
if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {
|
||||||
|
val = FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_FS_MASK, conf->fs) |
|
||||||
|
FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_ODR_MASK, conf->odr);
|
||||||
|
ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
oldconf->fs = conf->fs;
|
||||||
|
oldconf->odr = conf->odr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set gyro low-power average filter. */
|
||||||
|
if (conf->filter != oldconf->filter) {
|
||||||
|
val = FIELD_PREP(INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK, conf->filter);
|
||||||
|
ret = regmap_update_bits(st->map, INV_ICM45600_IPREG_SYS1_REG_170,
|
||||||
|
INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK, val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
oldconf->filter = conf->filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update the sensor gyro mode. */
|
||||||
|
return inv_icm45600_set_pwr_mgmt0(st, conf->mode, st->conf.accel.mode,
|
||||||
|
sleep_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
int inv_icm45600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
|
||||||
|
unsigned int writeval, unsigned int *readval)
|
||||||
|
{
|
||||||
|
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||||
|
|
||||||
|
guard(mutex)(&st->lock);
|
||||||
|
|
||||||
|
if (readval)
|
||||||
|
return regmap_read(st->map, reg, readval);
|
||||||
|
else
|
||||||
|
return regmap_write(st->map, reg, writeval);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int inv_icm45600_set_conf(struct inv_icm45600_state *st,
|
||||||
|
const struct inv_icm45600_conf *conf)
|
||||||
|
{
|
||||||
|
unsigned int val;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
val = FIELD_PREP(INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK, conf->gyro.mode) |
|
||||||
|
FIELD_PREP(INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK, conf->accel.mode);
|
||||||
|
ret = regmap_write(st->map, INV_ICM45600_REG_PWR_MGMT0, val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
val = FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_FS_MASK, conf->gyro.fs) |
|
||||||
|
FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_ODR_MASK, conf->gyro.odr);
|
||||||
|
ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
val = FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_FS_MASK, conf->accel.fs) |
|
||||||
|
FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_ODR_MASK, conf->accel.odr);
|
||||||
|
ret = regmap_write(st->map, INV_ICM45600_REG_ACCEL_CONFIG0, val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Save configuration. */
|
||||||
|
st->conf = *conf;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* inv_icm45600_setup() - check and setup chip
|
||||||
|
* @st: driver internal state
|
||||||
|
* @chip_info: detected chip description
|
||||||
|
* @reset: define whether a reset is required or not
|
||||||
|
* @bus_setup: callback for setting up bus specific registers
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, a negative error code otherwise.
|
||||||
|
*/
|
||||||
|
static int inv_icm45600_setup(struct inv_icm45600_state *st,
|
||||||
|
const struct inv_icm45600_chip_info *chip_info,
|
||||||
|
bool reset, inv_icm45600_bus_setup bus_setup)
|
||||||
|
{
|
||||||
|
const struct device *dev = regmap_get_device(st->map);
|
||||||
|
unsigned int val;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Set chip bus configuration if specified. */
|
||||||
|
if (bus_setup) {
|
||||||
|
ret = bus_setup(st);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check chip self-identification value. */
|
||||||
|
ret = regmap_read(st->map, INV_ICM45600_REG_WHOAMI, &val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
if (val != chip_info->whoami) {
|
||||||
|
/*
|
||||||
|
* SPI interface has no ack mechanism.
|
||||||
|
* 0xFF or 0x00 whoami means no response from the device.
|
||||||
|
*/
|
||||||
|
if (val == U8_MAX || val == 0)
|
||||||
|
return dev_err_probe(dev, -ENODEV,
|
||||||
|
"Invalid whoami %#02x expected %#02x (%s)\n",
|
||||||
|
val, chip_info->whoami, chip_info->name);
|
||||||
|
|
||||||
|
dev_warn(dev, "Unexpected whoami %#02x expected %#02x (%s)\n",
|
||||||
|
val, chip_info->whoami, chip_info->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
st->chip_info = chip_info;
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
/* Reset previous state. */
|
||||||
|
ret = regmap_write(st->map, INV_ICM45600_REG_MISC2,
|
||||||
|
INV_ICM45600_MISC2_SOFT_RESET);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
/*
|
||||||
|
* IMU reset time.
|
||||||
|
* Datasheet: 16.84 REG_MISC2
|
||||||
|
*/
|
||||||
|
fsleep(USEC_PER_MSEC);
|
||||||
|
|
||||||
|
if (bus_setup) {
|
||||||
|
ret = bus_setup(st);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = regmap_read(st->map, INV_ICM45600_REG_INT_STATUS, &val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
if (!(val & INV_ICM45600_INT_STATUS_RESET_DONE)) {
|
||||||
|
dev_err(dev, "reset error, reset done bit not set\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inv_icm45600_set_conf(st, chip_info->conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int inv_icm45600_timestamp_setup(struct inv_icm45600_state *st)
|
||||||
|
{
|
||||||
|
/* Enable timestamps. */
|
||||||
|
return regmap_set_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0,
|
||||||
|
INV_ICM45600_SMC_CONTROL_0_TMST_EN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int inv_icm45600_enable_regulator_vddio(struct inv_icm45600_state *st)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = regulator_enable(st->vddio_supply);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait a little for supply ramp.
|
||||||
|
* Duration is empirically defined.
|
||||||
|
*/
|
||||||
|
fsleep(3 * USEC_PER_MSEC);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void inv_icm45600_disable_vddio_reg(void *_data)
|
||||||
|
{
|
||||||
|
struct inv_icm45600_state *st = _data;
|
||||||
|
struct device *dev = regmap_get_device(st->map);
|
||||||
|
|
||||||
|
if (pm_runtime_status_suspended(dev))
|
||||||
|
return;
|
||||||
|
|
||||||
|
regulator_disable(st->vddio_supply);
|
||||||
|
}
|
||||||
|
|
||||||
|
int inv_icm45600_core_probe(struct regmap *regmap, const struct inv_icm45600_chip_info *chip_info,
|
||||||
|
bool reset, inv_icm45600_bus_setup bus_setup)
|
||||||
|
{
|
||||||
|
struct device *dev = regmap_get_device(regmap);
|
||||||
|
struct inv_icm45600_state *st;
|
||||||
|
struct regmap *regmap_custom;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
regmap_custom = devm_regmap_init(dev, &inv_icm45600_regmap_bus, regmap,
|
||||||
|
&inv_icm45600_regmap_config);
|
||||||
|
if (IS_ERR(regmap_custom))
|
||||||
|
return dev_err_probe(dev, PTR_ERR(regmap_custom), "Failed to register regmap\n");
|
||||||
|
|
||||||
|
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
|
||||||
|
if (!st)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
dev_set_drvdata(dev, st);
|
||||||
|
|
||||||
|
ret = devm_mutex_init(dev, &st->lock);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
st->map = regmap_custom;
|
||||||
|
|
||||||
|
ret = iio_read_mount_matrix(dev, &st->orientation);
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(dev, ret, "Failed to retrieve mounting matrix\n");
|
||||||
|
|
||||||
|
st->vddio_supply = devm_regulator_get(dev, "vddio");
|
||||||
|
if (IS_ERR(st->vddio_supply))
|
||||||
|
return PTR_ERR(st->vddio_supply);
|
||||||
|
|
||||||
|
ret = devm_regulator_get_enable(dev, "vdd");
|
||||||
|
if (ret)
|
||||||
|
return dev_err_probe(dev, ret, "Failed to get vdd regulator\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Supply ramp time + Start-up time.
|
||||||
|
* Datasheet: 3.3.2 A.C. Electrical Characteristics
|
||||||
|
*/
|
||||||
|
fsleep(5 * USEC_PER_MSEC);
|
||||||
|
|
||||||
|
ret = inv_icm45600_enable_regulator_vddio(st);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = devm_add_action_or_reset(dev, inv_icm45600_disable_vddio_reg, st);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = inv_icm45600_setup(st, chip_info, reset, bus_setup);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = inv_icm45600_timestamp_setup(st);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = devm_pm_runtime_set_active_enabled(dev);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
pm_runtime_get_noresume(dev);
|
||||||
|
pm_runtime_set_autosuspend_delay(dev, 2 * USEC_PER_MSEC);
|
||||||
|
pm_runtime_use_autosuspend(dev);
|
||||||
|
pm_runtime_put(dev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_NS_GPL(inv_icm45600_core_probe, "IIO_ICM45600");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Suspend saves sensors state and turns everything off.
|
||||||
|
*/
|
||||||
|
static int inv_icm45600_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
scoped_guard(mutex, &st->lock) {
|
||||||
|
/* Save sensors states */
|
||||||
|
st->suspended.gyro = st->conf.gyro.mode;
|
||||||
|
st->suspended.accel = st->conf.accel.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pm_runtime_force_suspend(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* System resume gets the system back on and restores the sensors state.
|
||||||
|
* Manually put runtime power management in system active state.
|
||||||
|
*/
|
||||||
|
static int inv_icm45600_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = pm_runtime_force_resume(dev);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
scoped_guard(mutex, &st->lock) {
|
||||||
|
/* Restore sensors state. */
|
||||||
|
ret = inv_icm45600_set_pwr_mgmt0(st, st->suspended.gyro,
|
||||||
|
st->suspended.accel, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Runtime suspend will turn off sensors that are enabled by iio devices. */
|
||||||
|
static int inv_icm45600_runtime_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
guard(mutex)(&st->lock);
|
||||||
|
|
||||||
|
/* disable all sensors */
|
||||||
|
ret = inv_icm45600_set_pwr_mgmt0(st, INV_ICM45600_SENSOR_MODE_OFF,
|
||||||
|
INV_ICM45600_SENSOR_MODE_OFF, NULL);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
regulator_disable(st->vddio_supply);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sensors are enabled by iio devices, no need to turn them back on here. */
|
||||||
|
static int inv_icm45600_runtime_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
guard(mutex)(&st->lock);
|
||||||
|
|
||||||
|
return inv_icm45600_enable_regulator_vddio(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPORT_NS_GPL_DEV_PM_OPS(inv_icm45600_pm_ops, IIO_ICM45600) = {
|
||||||
|
SYSTEM_SLEEP_PM_OPS(inv_icm45600_suspend, inv_icm45600_resume)
|
||||||
|
RUNTIME_PM_OPS(inv_icm45600_runtime_suspend,
|
||||||
|
inv_icm45600_runtime_resume, NULL)
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE_AUTHOR("InvenSense, Inc.");
|
||||||
|
MODULE_DESCRIPTION("InvenSense ICM-456xx device driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_IMPORT_NS("IIO_INV_SENSORS_TIMESTAMP");
|
||||||
Reference in New Issue
Block a user