diff --git a/Documentation/devicetree/bindings/iio/stm/imu/st_ism330is.txt b/Documentation/devicetree/bindings/iio/stm/imu/st_ism330is.txt new file mode 100644 index 000000000000..11cadeb952e9 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/stm/imu/st_ism330is.txt @@ -0,0 +1,55 @@ +* st_ism330is driver for imu MEMS sensors + +Required properties for all bus drivers: +- compatible: must be one of: + "st,ism330is" + +Required properties for the i2c bindings: +- reg: i2c slave address + +Required properties for the spi bindings: +- reg: the chipselect index + +- spi-max-frequency: maximal bus speed, should be set to 1000000 unless + constrained by external circuitry. + +Optional properties for all bus drivers: +- vdd-supply: an optional regulator that needs to be on to provide VDD + power to the sensor. + +- vddio-supply: an optional regulator that needs to be on to provide the + VDD IO power to the sensor. + +- mount-matrix: mount rotation matrix. + Refer to iio/mount-matrix.txt for details. + +- enable-sensor-hub: enable i2c master interface. Default is disabled. + +- drive-pullup-shub: enable pullup on master i2c line. + +Example for an spi device node on CS0: + +ism330is-imu@0 { + compatible = "st,ism330is"; + reg = <0x0>; + spi-max-frequency = <1000000>; + vddio-supply = <&sensors_vddio>; + vdd-supply = <&sensors_vdd>; + mount-matrix = "1", "0", "0", + "0", "1", "0", + "0", "0", "1"; + drive-pullup-shub; +}; + +Example for an i2c device node when SA0 tied to ground: + +ism330is-imu@6a { + compatible = "st,ism330is"; + reg = <0x6a>; + vddio-supply = <&sensors_vddio>; + vdd-supply = <&sensors_vdd>; + mount-matrix = "1", "0", "0", + "0", "1", "0", + "0", "0", "1"; + drive-pullup-shub; +}; diff --git a/drivers/iio/stm/imu/Kconfig b/drivers/iio/stm/imu/Kconfig index 2dc9e51bae19..6ad1a36e7a8f 100644 --- a/drivers/iio/stm/imu/Kconfig +++ b/drivers/iio/stm/imu/Kconfig @@ -18,5 +18,6 @@ source "drivers/iio/stm/imu/st_lsm6dsm/Kconfig" source "drivers/iio/stm/imu/st_lsm6dsvx/Kconfig" source "drivers/iio/stm/imu/st_lsm6dso16is/Kconfig" source "drivers/iio/stm/imu/st_lsm6dsv16bx/Kconfig" +source "drivers/iio/stm/imu/st_ism330is/Kconfig" endmenu diff --git a/drivers/iio/stm/imu/Makefile b/drivers/iio/stm/imu/Makefile index 96d4383be9e6..de6199a1c47f 100644 --- a/drivers/iio/stm/imu/Makefile +++ b/drivers/iio/stm/imu/Makefile @@ -16,3 +16,4 @@ obj-y += st_lsm6dsm/ obj-y += st_lsm6dsvx/ obj-y += st_lsm6dso16is/ obj-y += st_lsm6dsv16bx/ +obj-y += st_ism330is/ diff --git a/drivers/iio/stm/imu/st_ism330is/Kconfig b/drivers/iio/stm/imu/st_ism330is/Kconfig new file mode 100644 index 000000000000..5fbcc4a57b4f --- /dev/null +++ b/drivers/iio/stm/imu/st_ism330is/Kconfig @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config IIO_ST_ISM330IS + tristate "STMicroelectronics ISM330IS sensor" + depends on (I2C || SPI) + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_ST_ISM330IS_I2C if (I2C) + select IIO_ST_ISM330IS_SPI if (SPI_MASTER) + help + Say yes here to build support for STMicroelectronics + ISM330IS imu sensors. + + To compile this driver as a module, choose M here: the module + will be called st_ism330is. + +config IIO_ST_ISM330IS_I2C + tristate + select REGMAP_I2C + depends on IIO_ST_ISM330IS + +config IIO_ST_ISM330IS_SPI + tristate + select REGMAP_SPI + depends on IIO_ST_ISM330IS + diff --git a/drivers/iio/stm/imu/st_ism330is/Makefile b/drivers/iio/stm/imu/st_ism330is/Makefile new file mode 100644 index 000000000000..be37599921c7 --- /dev/null +++ b/drivers/iio/stm/imu/st_ism330is/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +st_ism330is-y := st_ism330is_core.o \ + st_ism330is_shub.o \ + st_ism330is_triggers.o + +obj-$(CONFIG_IIO_ST_ISM330IS) += st_ism330is.o +obj-$(CONFIG_IIO_ST_ISM330IS_I2C) += st_ism330is_i2c.o +obj-$(CONFIG_IIO_ST_ISM330IS_SPI) += st_ism330is_spi.o diff --git a/drivers/iio/stm/imu/st_ism330is/st_ism330is.h b/drivers/iio/stm/imu/st_ism330is/st_ism330is.h new file mode 100644 index 000000000000..50ca1e1e9d68 --- /dev/null +++ b/drivers/iio/stm/imu/st_ism330is/st_ism330is.h @@ -0,0 +1,344 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * STMicroelectronics st_ism330is sensor driver + * + * MEMS Software Solutions Team + * + * Copyright 2023 STMicroelectronics Inc. + */ + +#ifndef ST_ISM330IS_H +#define ST_ISM330IS_H + +#include +#include +#include +#include +#include +#include + +#define ST_ISM330IS_DEV_NAME "ism330is" + +#define ST_ISM330IS_REG_FUNC_CFG_ACCESS_ADDR 0x01 +#define ST_ISM330IS_SHUB_REG_MASK BIT(6) + +#define ST_ISM330IS_REG_PIN_CTRL_ADDR 0x02 +#define ST_ISM330IS_SDO_PU_EN_MASK BIT(6) + +#define ST_ISM330IS_REG_WHOAMI_ADDR 0x0f +#define ST_ISM330IS_WHOAMI_VAL 0x22 + +#define ST_ISM330IS_REG_CTRL1_XL_ADDR 0x10 +#define ST_ISM330IS_ODR_XL_MASK GENMASK(7, 4) +#define ST_ISM330IS_FS_XL_MASK GENMASK(3, 2) + +#define ST_ISM330IS_REG_CTRL2_G_ADDR 0x11 +#define ST_ISM330IS_ODR_G_MASK GENMASK(7, 4) +#define ST_ISM330IS_FS_G_MASK GENMASK(3, 1) + +#define ST_ISM330IS_REG_CTRL3_C_ADDR 0x12 +#define ST_ISM330IS_BOOT_MASK BIT(7) +#define ST_ISM330IS_BDU_MASK BIT(6) +#define ST_ISM330IS_H_LACTIVE_MASK BIT(5) +#define ST_ISM330IS_PP_OD_MASK BIT(4) +#define ST_ISM330IS_SW_RESET_MASK BIT(0) + +#define ST_ISM330IS_REG_CTRL5_C_ADDR 0x14 +#define ST_ISM330IS_ST_G_MASK GENMASK(3, 2) +#define ST_ISM330IS_ST_XL_MASK GENMASK(1, 0) + +#define ST_ISM330IS_REG_STATUS_ADDR 0x1e +#define ST_ISM330IS_STATUS_TDA BIT(2) +#define ST_ISM330IS_STATUS_XLDA BIT(0) +#define ST_ISM330IS_STATUS_GDA BIT(1) + +#define ST_ISM330IS_REG_OUT_TEMP_L_ADDR 0x20 +#define ST_ISM330IS_REG_OUTX_L_G_ADDR 0x22 +#define ST_ISM330IS_REG_OUTY_L_G_ADDR 0x24 +#define ST_ISM330IS_REG_OUTZ_L_G_ADDR 0x26 +#define ST_ISM330IS_REG_OUTX_L_A_ADDR 0x28 +#define ST_ISM330IS_REG_OUTY_L_A_ADDR 0x2a +#define ST_ISM330IS_REG_OUTZ_L_A_ADDR 0x2c + +#define ST_ISM330IS_ST_ACCEL_MIN 737 +#define ST_ISM330IS_ST_ACCEL_MAX 13934 +#define ST_ISM330IS_ST_GYRO_MIN 2142 +#define ST_ISM330IS_ST_GYRO_MAX 10000 + +#define ST_ISM330IS_ST_DISABLED_VAL 0 +#define ST_ISM330IS_ST_POS_SIGN_VAL 1 +#define ST_ISM330IS_ST_NEG_ACCEL_SIGN_VAL 2 +#define ST_ISM330IS_ST_NEG_GYRO_SIGN_VAL 3 + +/* shub registers */ +#define ST_ISM330IS_REG_SENSOR_HUB_1_ADDR 0x02 + +#define ST_ISM330IS_REG_MASTER_CONFIG_ADDR 0x14 +#define ST_ISM330IS_WRITE_ONCE_MASK BIT(6) +#define ST_ISM330IS_SHUB_PU_EN_MASK BIT(3) +#define ST_ISM330IS_MASTER_ON_MASK BIT(2) +#define ST_ISM330IS_AUX_SENS_ON_MASK GENMASK(1, 0) + +#define ST_ISM330IS_REG_SLV0_ADDR 0x15 +#define ST_ISM330IS_REG_SLV0_CFG 0x17 +#define ST_ISM330IS_REG_SLV1_ADDR 0x18 +#define ST_ISM330IS_REG_SLV2_ADDR 0x1b +#define ST_ISM330IS_REG_SLV3_ADDR 0x1e + +#define ST_ISM330IS_REG_DATAWRITE_SLV0_ADDR 0x21 +#define ST_ISM330IS_SLAVE_NUMOP_MASK GENMASK(2, 0) + +#define ST_ISM330IS_REG_STATUS_MASTER_ADDR 0x22 +#define ST_ISM330IS_SENS_HUB_ENDOP_MASK BIT(0) + +/* Timestamp Tick 25us/LSB */ +#define ST_ISM330IS_TS_DELTA_NS 25000ULL + +/* Temperature in uC */ +#define ST_ISM330IS_TEMP_GAIN 256 +#define ST_ISM330IS_TEMP_OFFSET 6400 + +#define ST_ISM330IS_DATA_CHANNEL(chan_type, addr, mod, ch2, scan_idx, \ + rb, sb, sg, ext_inf) \ +{ \ + .type = chan_type, \ + .address = addr, \ + .modified = mod, \ + .channel2 = ch2, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = scan_idx, \ + .scan_type = { \ + .sign = sg, \ + .realbits = rb, \ + .storagebits = sb, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = ext_inf, \ +} + +#define ST_ISM330IS_SHIFT_VAL(val, mask) (((val) << __ffs(mask)) & (mask)) + +extern const struct dev_pm_ops st_ism330is_pm_ops; + +/** + * struct st_ism330is_reg - Generic sensor register + * description (addr + mask) + * + * @addr: Address of register. + * @mask: Bitmask register for proper usage. + */ +struct st_ism330is_reg { + u8 addr; + u8 mask; +}; + +/** + * struct st_ism330is_odr - Single ODR entry + * @mhz: Sensor ODR (milli Hz). + * @val: ODR register value. + */ +struct st_ism330is_odr { + u32 mhz; + u8 val; +}; + +/** + * struct st_ism330is_odr_table_entry - Sensor ODR table + * @size: Size of ODR table. + * @reg: ODR register. + * @odr_avl: Array of supported ODR value. + */ +struct st_ism330is_odr_table_entry { + u8 size; + struct st_ism330is_reg reg; + struct st_ism330is_odr odr_avl[8]; +}; + +/** + * struct st_ism330is_fs + * brief Full scale entry + * + * @gain: The gain to obtain data value from raw data (LSB). + * @val: Register value. + */ +struct st_ism330is_fs { + u32 gain; + u8 val; +}; + +/** + * struct st_ism330is_fs_table_entry - Full Scale sensor table + * @reg: st_ism330is_reg struct. + * @fs_avl: Full Scale list entries. + * @fs_len: Real size of fs_avl array. + */ +struct st_ism330is_fs_table_entry { + int fs_len; + struct st_ism330is_reg reg; + struct st_ism330is_fs fs_avl[4]; +}; + +enum st_ism330is_sensor_id { + ST_ISM330IS_ID_GYRO = 0, + ST_ISM330IS_ID_ACC, + ST_ISM330IS_ID_TEMP, + ST_ISM330IS_ID_EXT0, + ST_ISM330IS_ID_EXT1, + ST_ISM330IS_ID_MAX, +}; + +/** + * @enum st_ism330is_sensor_id + * @brief Sensor Table Identifier + */ +static const enum st_ism330is_sensor_id st_ism330is_main_sensor_list[] = { + [0] = ST_ISM330IS_ID_GYRO, + [1] = ST_ISM330IS_ID_ACC, + [2] = ST_ISM330IS_ID_TEMP, + [3] = ST_ISM330IS_ID_EXT0, + [4] = ST_ISM330IS_ID_EXT1, +}; + +static const enum st_ism330is_sensor_id +st_ism330is_triggered_main_sensor_list[] = { + [0] = ST_ISM330IS_ID_GYRO, + [1] = ST_ISM330IS_ID_ACC, + [2] = ST_ISM330IS_ID_TEMP, + [3] = ST_ISM330IS_ID_EXT0, + [4] = ST_ISM330IS_ID_EXT1, +}; + +struct st_ism330is_ext_dev_info { + const struct st_ism330is_ext_dev_settings *ext_dev_settings; + u8 ext_dev_i2c_addr; +}; + +/** + * struct st_ism330is_sensor - ST IMU sensor instance + * @ext_dev_info: For sensor hub indicate device info struct. + * @id: Sensor identifier. + * @hw: Pointer to instance of struct st_ism330is_hw. + * @name: Sensor name. + * @offset: Sensor data offset. + * @gain: Configured sensor sensitivity. + * @mhz: Output data rate of the sensor [milli Hz]. + * @selftest_status: Report last self test status. + * @min_st: Min self test raw data value. + * @max_st: Max self test raw data value. + */ +struct st_ism330is_sensor { + struct st_ism330is_ext_dev_info ext_dev_info; + enum st_ism330is_sensor_id id; + struct st_ism330is_hw *hw; + char name[32]; + + u32 offset; + u32 gain; + u32 mhz; + + /* self test */ + int8_t selftest_status; + int min_st; + int max_st; +}; + +/** + * struct st_ism330is_hw - ST IMU MEMS hw instance + * @iio_devs: Pointers to acc/gyro iio_dev instances. + * @orientation: Sensor orientation matrix. + * @vddio_supply: Voltage regulator for VDDIIO. + * @vdd_supply: Voltage regulator for VDD. + * @page_lock: Mutex to prevent concurrent access to the page selector. + * @regmap: Register map of the device. + * @dev: Pointer to instance of struct device (I2C or SPI). + * @i2c_master_pu: I2C master line Pull Up configuration. + * @enable_mask: Enabled sensor bitmask. + * @ext_data_len: Number of i2c slave devices connected to I2C master. + * @irq: Device interrupt line (I2C or SPI). + */ +struct st_ism330is_hw { + struct iio_dev *iio_devs[ST_ISM330IS_ID_MAX]; + struct iio_mount_matrix orientation; + struct regulator *vddio_supply; + struct regulator *vdd_supply; + struct mutex page_lock; + struct regmap *regmap; + struct device *dev; + u8 i2c_master_pu; + u32 enable_mask; + u8 ext_data_len; + int irq; +}; + +static inline int +__st_ism330is_write_with_mask(struct st_ism330is_hw *hw, + unsigned int addr, + unsigned int mask, + unsigned int val) +{ + return regmap_update_bits(hw->regmap, addr, mask, + ST_ISM330IS_SHIFT_VAL(val, mask)); +} + +static inline int +st_ism330is_update_bits_locked(struct st_ism330is_hw *hw, + unsigned int addr, + unsigned int mask, + unsigned int val) +{ + int err; + + mutex_lock(&hw->page_lock); + err = __st_ism330is_write_with_mask(hw, addr, mask, val); + mutex_unlock(&hw->page_lock); + + return err; +} + +static inline int +st_ism330is_read_locked(struct st_ism330is_hw *hw, unsigned int addr, + void *val, unsigned int len) +{ + int err; + + mutex_lock(&hw->page_lock); + err = regmap_bulk_read(hw->regmap, addr, val, len); + mutex_unlock(&hw->page_lock); + + return err; +} + +static inline int +st_ism330is_write_locked(struct st_ism330is_hw *hw, unsigned int addr, + unsigned int val) +{ + int err; + + mutex_lock(&hw->page_lock); + err = regmap_write(hw->regmap, addr, val); + mutex_unlock(&hw->page_lock); + + return err; +} + +static inline int +st_ism330is_set_page_access(struct st_ism330is_hw *hw, unsigned int mask, + unsigned int val) +{ + return __st_ism330is_write_with_mask(hw, + ST_ISM330IS_REG_FUNC_CFG_ACCESS_ADDR, + mask, val); +} + +int st_ism330is_probe(struct device *dev, int irq, struct regmap *regmap); +int st_ism330is_sensor_set_enable(struct st_ism330is_sensor *sensor, + bool enable); +int st_ism330is_shub_probe(struct st_ism330is_hw *hw); +int st_ism330is_shub_set_enable(struct st_ism330is_sensor *sensor, + bool enable); +int st_ism330is_shub_read(struct st_ism330is_sensor *sensor, + u8 addr, u8 *data, int len); +int st_ism330is_allocate_buffers(struct st_ism330is_hw *hw); +#endif /* ST_ISM330IS_H */ diff --git a/drivers/iio/stm/imu/st_ism330is/st_ism330is_core.c b/drivers/iio/stm/imu/st_ism330is/st_ism330is_core.c new file mode 100644 index 000000000000..92edbea11234 --- /dev/null +++ b/drivers/iio/stm/imu/st_ism330is/st_ism330is_core.c @@ -0,0 +1,1210 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics st_ism330is sensor driver + * + * MEMS Software Solutions Team + * + * Copyright 2023 STMicroelectronics Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st_ism330is.h" + +static struct st_ism330is_selftest_table { + char *string_mode; + u8 accel_value; + u8 gyro_value; + u8 gyro_mask; +} st_ism330is_selftest_table[] = { + [0] = { + .string_mode = "disabled", + .accel_value = ST_ISM330IS_ST_DISABLED_VAL, + .gyro_value = ST_ISM330IS_ST_DISABLED_VAL, + }, + [1] = { + .string_mode = "positive-sign", + .accel_value = ST_ISM330IS_ST_POS_SIGN_VAL, + .gyro_value = ST_ISM330IS_ST_POS_SIGN_VAL + }, + [2] = { + .string_mode = "negative-sign", + .accel_value = ST_ISM330IS_ST_NEG_ACCEL_SIGN_VAL, + .gyro_value = ST_ISM330IS_ST_NEG_GYRO_SIGN_VAL + }, +}; + +static const struct st_ism330is_odr_table_entry st_ism330is_odr_table[] = { + [ST_ISM330IS_ID_ACC] = { + .size = 7, + .reg = { + .addr = ST_ISM330IS_REG_CTRL1_XL_ADDR, + .mask = ST_ISM330IS_ODR_XL_MASK, + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_avl[6] = { 833000, 0x07 }, + }, + [ST_ISM330IS_ID_GYRO] = { + .size = 7, + .reg = { + .addr = ST_ISM330IS_REG_CTRL2_G_ADDR, + .mask = ST_ISM330IS_ODR_G_MASK, + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_avl[6] = { 833000, 0x07 }, + }, + [ST_ISM330IS_ID_TEMP] = { + .size = 2, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 52000, 0x03 }, + }, +}; + +static struct st_ism330is_fs_table_entry st_ism330is_fs_table[] = { + [ST_ISM330IS_ID_ACC] = { + .fs_len = 4, + .reg = { + .addr = ST_ISM330IS_REG_CTRL1_XL_ADDR, + .mask = ST_ISM330IS_FS_XL_MASK, + }, + .fs_avl[0] = { IIO_G_TO_M_S_2(61000), 0x0 }, + .fs_avl[1] = { IIO_G_TO_M_S_2(122000), 0x2 }, + .fs_avl[2] = { IIO_G_TO_M_S_2(244000), 0x3 }, + .fs_avl[3] = { IIO_G_TO_M_S_2(488000), 0x1 }, + }, + [ST_ISM330IS_ID_GYRO] = { + .fs_len = 4, + .reg = { + .addr = ST_ISM330IS_REG_CTRL2_G_ADDR, + .mask = ST_ISM330IS_FS_G_MASK, + }, + .fs_avl[0] = { IIO_DEGREE_TO_RAD(8750000), 0x0 }, + .fs_avl[1] = { IIO_DEGREE_TO_RAD(17500000), 0x1 }, + .fs_avl[2] = { IIO_DEGREE_TO_RAD(35000000), 0x2 }, + .fs_avl[3] = { IIO_DEGREE_TO_RAD(70000000), 0x3 }, + }, + [ST_ISM330IS_ID_TEMP] = { + .fs_len = 1, + .fs_avl[0] = { ST_ISM330IS_TEMP_GAIN, 0x0 }, + }, +}; + +static const struct iio_mount_matrix * +st_ism330is_get_mount_matrix(const struct iio_dev *iio_dev, + const struct iio_chan_spec *ch) +{ + struct st_ism330is_sensor *sensor = iio_priv(iio_dev); + struct st_ism330is_hw *hw = sensor->hw; + + return &hw->orientation; +} + +static const struct iio_chan_spec_ext_info st_ism330is_chan_spec_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, st_ism330is_get_mount_matrix), + { } +}; + +static const struct iio_chan_spec st_ism330is_acc_channels[] = { + ST_ISM330IS_DATA_CHANNEL(IIO_ACCEL, ST_ISM330IS_REG_OUTX_L_A_ADDR, + 1, IIO_MOD_X, 0, 16, 16, 's', + st_ism330is_chan_spec_ext_info), + ST_ISM330IS_DATA_CHANNEL(IIO_ACCEL, ST_ISM330IS_REG_OUTY_L_A_ADDR, + 1, IIO_MOD_Y, 1, 16, 16, 's', + st_ism330is_chan_spec_ext_info), + ST_ISM330IS_DATA_CHANNEL(IIO_ACCEL, ST_ISM330IS_REG_OUTZ_L_A_ADDR, + 1, IIO_MOD_Z, 2, 16, 16, 's', + st_ism330is_chan_spec_ext_info), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec st_ism330is_gyro_channels[] = { + ST_ISM330IS_DATA_CHANNEL(IIO_ANGL_VEL, ST_ISM330IS_REG_OUTX_L_G_ADDR, + 1, IIO_MOD_X, 0, 16, 16, 's', + st_ism330is_chan_spec_ext_info), + ST_ISM330IS_DATA_CHANNEL(IIO_ANGL_VEL, ST_ISM330IS_REG_OUTY_L_G_ADDR, + 1, IIO_MOD_Y, 1, 16, 16, 's', + st_ism330is_chan_spec_ext_info), + ST_ISM330IS_DATA_CHANNEL(IIO_ANGL_VEL, ST_ISM330IS_REG_OUTZ_L_G_ADDR, + 1, IIO_MOD_Z, 2, 16, 16, 's', + st_ism330is_chan_spec_ext_info), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec st_ism330is_temp_channels[] = { + { + .type = IIO_TEMP, + .address = ST_ISM330IS_REG_OUT_TEMP_L_ADDR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_OFFSET) + | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + } + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static __maybe_unused int st_ism330is_reg_access(struct iio_dev *iio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct st_ism330is_sensor *sensor = iio_priv(iio_dev); + int ret; + + ret = iio_device_claim_direct_mode(iio_dev); + if (ret) + return ret; + + if (readval == NULL) + ret = regmap_write(sensor->hw->regmap, reg, writeval); + else + ret = regmap_read(sensor->hw->regmap, reg, readval); + + iio_device_release_direct_mode(iio_dev); + + return (ret < 0) ? ret : 0; +} + +/** + * st_ism330is_check_whoami - Detect device HW ID + * + * Check the value of the device HW ID if valid + * + * @param hw: ST IMU MEMS hw instance. + * @return 0 if OK, negative value for ERROR + */ +static int st_ism330is_check_whoami(struct st_ism330is_hw *hw) +{ + int err, data; + + err = regmap_read(hw->regmap, ST_ISM330IS_REG_WHOAMI_ADDR, &data); + if (err < 0) { + dev_err(hw->dev, "failed to read whoami register\n"); + + return err; + } + + if (data != ST_ISM330IS_WHOAMI_VAL) { + dev_err(hw->dev, "unsupported whoami [%02x]\n", data); + + return -ENODEV; + } + + return 0; +} + +/** + * st_ism330is_set_full_scale - Set sensor full scale + * + * Check the value of requested gain, apply it if supported. + * NOTE: support also sensor with only one FS available (Temp gain is fixed). + * + * @param sensor: ST IMU MEMS sensor instance. + * @param gain: Sensor gain. + * @return 0 if OK, negative value for ERROR + */ +static int st_ism330is_set_full_scale(struct st_ism330is_sensor *sensor, + u32 gain) +{ + enum st_ism330is_sensor_id id = sensor->id; + struct st_ism330is_hw *hw = sensor->hw; + int i, err; + u8 val; + + for (i = 0; i < st_ism330is_fs_table[id].fs_len; i++) + if (st_ism330is_fs_table[id].fs_avl[i].gain == gain) + break; + + if (i == st_ism330is_fs_table[id].fs_len) + return -EINVAL; + + val = st_ism330is_fs_table[id].fs_avl[i].val; + err = regmap_update_bits(hw->regmap, + st_ism330is_fs_table[id].reg.addr, + st_ism330is_fs_table[id].reg.mask, + ST_ISM330IS_SHIFT_VAL(val, + st_ism330is_fs_table[id].reg.mask)); + if (err < 0) + return err; + + sensor->gain = gain; + + return 0; +} + +static int st_ism330is_get_odr_val(enum st_ism330is_sensor_id id, u32 mhz, + u32 *val, u32 *modr) +{ + u32 sensor_odr; + int i; + + /* this avoid entry 0mHz to ODR table */ + if (mhz == 0) { + *val = 0; + *modr = 0; + + return 0; + } + + for (i = 0; i < st_ism330is_odr_table[id].size; i++) { + sensor_odr = st_ism330is_odr_table[id].odr_avl[i].mhz; + if (sensor_odr >= mhz) { + *val = st_ism330is_odr_table[id].odr_avl[i].val; + *modr = sensor_odr; + + return 0; + } + } + + return -EINVAL; +} + + +static u32 +st_ism330is_check_odr_dependency(struct st_ism330is_hw *hw, u32 mhz, + enum st_ism330is_sensor_id ref_id) +{ + struct st_ism330is_sensor *ref = iio_priv(hw->iio_devs[ref_id]); + u32 ret = mhz; + + if (mhz > 0) { + if (hw->enable_mask & BIT(ref_id)) + ret = max_t(u32, ref->mhz, mhz); + } else { + ret = (hw->enable_mask & BIT(ref_id)) ? ref->mhz : 0; + } + + return ret; +} + +static int st_ism330is_set_odr(struct st_ism330is_sensor *sensor, u32 mhz) +{ + enum st_ism330is_sensor_id id = sensor->id; + struct st_ism330is_hw *hw = sensor->hw; + int val, err, odr, modr; + + switch (id) { + case ST_ISM330IS_ID_EXT0: + case ST_ISM330IS_ID_EXT1: + case ST_ISM330IS_ID_TEMP: + case ST_ISM330IS_ID_ACC: { + int i; + + id = ST_ISM330IS_ID_ACC; + for (i = ST_ISM330IS_ID_ACC; i < ST_ISM330IS_ID_MAX; i++) { + if (!hw->iio_devs[i] || i == sensor->id) + continue; + + odr = st_ism330is_check_odr_dependency(hw, mhz, i); + if (odr != mhz) + return 0; + } + break; + } + default: + break; + } + + err = st_ism330is_get_odr_val(id, odr, &val, &modr); + if (err < 0) + return err; + + return st_ism330is_update_bits_locked(hw, + st_ism330is_odr_table[id].reg.addr, + st_ism330is_odr_table[id].reg.mask, + val); +} + +int st_ism330is_sensor_set_enable(struct st_ism330is_sensor *sensor, + bool enable) +{ + int mhz = enable ? sensor->mhz : 0; + int err; + + err = st_ism330is_set_odr(sensor, mhz); + if (err < 0) + return err; + + if (enable) + sensor->hw->enable_mask |= BIT(sensor->id); + else + sensor->hw->enable_mask &= ~BIT(sensor->id); + + return 0; +} + +static int st_ism330is_read_oneshot(struct st_ism330is_sensor *sensor, + u8 addr, int *val) +{ + struct st_ism330is_hw *hw = sensor->hw; + int err, delay; + __le16 data; + + /* + * adjust delay for data valid because of turn-on time: + * - Acc, Power-down -> High-performance discard 1 sample + * - Gyro, Power-down -> High-performance wait 70 ms + f(ODR) + * - Temp, 1 ODR + * NOTE: we use conversion 1100000000 to also take into account the + * internal oscillator tolerance of 10% + */ + switch (sensor->id) { + case ST_ISM330IS_ID_GYRO: { + int n = 3; + + if (sensor->mhz > + st_ism330is_odr_table[sensor->id].odr_avl[0].mhz) + n++; + + delay = 70000 + n * (1100000000 / sensor->mhz); + } + break; + case ST_ISM330IS_ID_ACC: + delay = 2 * (1100000000 / sensor->mhz); + break; + case ST_ISM330IS_ID_TEMP: + delay = 1100000000 / sensor->mhz; + break; + default: + return -EINVAL; + } + + err = st_ism330is_sensor_set_enable(sensor, true); + if (err < 0) + return err; + + usleep_range(delay, delay + (delay >> 1)); + err = st_ism330is_read_locked(hw, addr, &data, sizeof(data)); + st_ism330is_sensor_set_enable(sensor, false); + if (err < 0) + return err; + + *val = (s16)le16_to_cpu(data); + + return IIO_VAL_INT; +} + +static int st_ism330is_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + case IIO_ACCEL: + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + default: + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static int st_ism330is_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + struct st_ism330is_sensor *sensor = iio_priv(iio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(iio_dev); + if (ret) + return ret; + + ret = st_ism330is_read_oneshot(sensor, ch->address, val); + iio_device_release_direct_mode(iio_dev); + break; + case IIO_CHAN_INFO_OFFSET: + switch (ch->type) { + case IIO_TEMP: + *val = sensor->offset; + ret = IIO_VAL_INT; + break; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = sensor->mhz / 1000; + *val2 = (sensor->mhz % 1000) * 1000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SCALE: + switch (ch->type) { + case IIO_TEMP: + *val = 1000; + *val2 = ST_ISM330IS_TEMP_GAIN; + ret = IIO_VAL_FRACTIONAL; + break; + case IIO_ACCEL: + case IIO_ANGL_VEL: + *val = 0; + *val2 = sensor->gain; + ret = IIO_VAL_INT_PLUS_NANO; + break; + default: + return -EINVAL; + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int st_ism330is_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct st_ism330is_sensor *sensor = iio_priv(iio_dev); + int err; + + err = iio_device_claim_direct_mode(iio_dev); + if (err) + return err; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + err = st_ism330is_set_full_scale(sensor, val2); + break; + case IIO_CHAN_INFO_SAMP_FREQ: { + u32 reg, modr; + + val = val * 1000 + val2 / 1000; + + err = st_ism330is_get_odr_val(sensor->id, val, ®, &modr); + if (err < 0) + goto release; + + sensor->mhz = modr; + break; + } + default: + err = -EINVAL; + break; + } + +release: + iio_device_release_direct_mode(iio_dev); + + return err; +} + +static ssize_t +st_ism330is_sysfs_sampling_frequency_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct st_ism330is_sensor *sensor = iio_priv(dev_to_iio_dev(dev)); + const struct st_ism330is_odr_table_entry *odr_table; + enum st_ism330is_sensor_id id = sensor->id; + int i, len = 0; + + odr_table = &st_ism330is_odr_table[id]; + for (i = 0; i < st_ism330is_odr_table[id].size; i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%03d ", + odr_table->odr_avl[i].mhz / 1000, + odr_table->odr_avl[i].mhz % 1000); + } + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t st_ism330is_sysfs_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct st_ism330is_sensor *sensor = iio_priv(dev_to_iio_dev(dev)); + enum st_ism330is_sensor_id id = sensor->id; + int i, len = 0; + + for (i = 0; i < st_ism330is_fs_table[id].fs_len; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%09u ", + st_ism330is_fs_table[id].fs_avl[i].gain); + buf[len - 1] = '\n'; + + return len; +} + +static int +st_ism330is_set_selftest(struct st_ism330is_sensor *sensor, int index) +{ + u8 mode, mask; + + switch (sensor->id) { + case ST_ISM330IS_ID_ACC: + mask = ST_ISM330IS_ST_XL_MASK; + mode = st_ism330is_selftest_table[index].accel_value; + break; + case ST_ISM330IS_ID_GYRO: + mask = ST_ISM330IS_ST_G_MASK; + mode = st_ism330is_selftest_table[index].gyro_value; + break; + default: + return -EINVAL; + } + + return st_ism330is_update_bits_locked(sensor->hw, + ST_ISM330IS_REG_CTRL5_C_ADDR, + mask, mode); +} + +static ssize_t +st_ism330is_sysfs_get_selftest_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s, %s\n", + st_ism330is_selftest_table[1].string_mode, + st_ism330is_selftest_table[2].string_mode); +} + +static ssize_t +st_ism330is_sysfs_get_selftest_status(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct st_ism330is_sensor *sensor = iio_priv(dev_to_iio_dev(dev)); + enum st_ism330is_sensor_id id = sensor->id; + char *message = NULL; + int8_t result; + + if (id != ST_ISM330IS_ID_ACC && + id != ST_ISM330IS_ID_GYRO) + return -EINVAL; + + result = sensor->selftest_status; + if (result == 0) + message = "na"; + else if (result < 0) + message = "fail"; + else if (result > 0) + message = "pass"; + + return sprintf(buf, "%s\n", message); +} + +static int +st_ism330is_selftest_sensor(struct st_ism330is_sensor *sensor, int test) +{ + int x_selftest = 0, y_selftest = 0, z_selftest = 0; + int x = 0, y = 0, z = 0, try_count = 0; + u8 i, status, n = 0; + u8 reg, bitmask; + int ret, delay; + u8 raw_data[6]; + + switch (sensor->id) { + case ST_ISM330IS_ID_ACC: + reg = ST_ISM330IS_REG_OUTX_L_A_ADDR; + bitmask = ST_ISM330IS_STATUS_XLDA; + break; + case ST_ISM330IS_ID_GYRO: + reg = ST_ISM330IS_REG_OUTX_L_G_ADDR; + bitmask = ST_ISM330IS_STATUS_GDA; + break; + default: + return -EINVAL; + } + + /* set selftest normal mode */ + ret = st_ism330is_set_selftest(sensor, 0); + if (ret < 0) + return ret; + + ret = st_ism330is_sensor_set_enable(sensor, true); + if (ret < 0) + return ret; + + /* wait at least 2 ODRs to be sure */ + delay = 2 * (1000000000 / sensor->mhz); + + /* power up, wait 100 ms for stable output */ + msleep(100); + + /* for 5 times, after checking status bit, read the output registers */ + for (i = 0; i < 5; i++) { + try_count = 0; + while (try_count < 3) { + usleep_range(delay, 2 * delay); + ret = st_ism330is_read_locked(sensor->hw, + ST_ISM330IS_REG_STATUS_ADDR, + &status, sizeof(status)); + if (ret < 0) + goto selftest_failure; + + if (status & bitmask) { + ret = st_ism330is_read_locked(sensor->hw, + reg, raw_data, + sizeof(raw_data)); + if (ret < 0) + goto selftest_failure; + + /* + * for 5 times, after checking status bit, + * read the output registers + */ + x += ((s16)*(u16 *)&raw_data[0]) / 5; + y += ((s16)*(u16 *)&raw_data[2]) / 5; + z += ((s16)*(u16 *)&raw_data[4]) / 5; + n++; + + break; + } + try_count++; + } + } + + if (i != n) { + dev_err(sensor->hw->dev, + "some acc samples missing (expected %d, read %d)\n", + i, n); + ret = -1; + + goto selftest_failure; + } + + n = 0; + + /* set selftest mode */ + st_ism330is_set_selftest(sensor, test); + + /* wait 100 ms for stable output */ + msleep(100); + + /* for 5 times, after checking status bit, read the output registers */ + for (i = 0; i < 5; i++) { + try_count = 0; + while (try_count < 3) { + usleep_range(delay, 2 * delay); + ret = st_ism330is_read_locked(sensor->hw, + ST_ISM330IS_REG_STATUS_ADDR, + &status, sizeof(status)); + if (ret < 0) + goto selftest_failure; + + if (status & bitmask) { + ret = st_ism330is_read_locked(sensor->hw, + reg, raw_data, + sizeof(raw_data)); + if (ret < 0) + goto selftest_failure; + + x_selftest += ((s16)*(u16 *)&raw_data[0]) / 5; + y_selftest += ((s16)*(u16 *)&raw_data[2]) / 5; + z_selftest += ((s16)*(u16 *)&raw_data[4]) / 5; + n++; + + break; + } + try_count++; + } + } + + if (i != n) { + dev_err(sensor->hw->dev, + "some samples missing (expected %d, read %d)\n", + i, n); + ret = -1; + + goto selftest_failure; + } + + if ((abs(x_selftest - x) < sensor->min_st) || + (abs(x_selftest - x) > sensor->max_st)) { + sensor->selftest_status = -1; + goto selftest_failure; + } + + if ((abs(y_selftest - y) < sensor->min_st) || + (abs(y_selftest - y) > sensor->max_st)) { + sensor->selftest_status = -1; + goto selftest_failure; + } + + if ((abs(z_selftest - z) < sensor->min_st) || + (abs(z_selftest - z) > sensor->max_st)) { + sensor->selftest_status = -1; + goto selftest_failure; + } + + sensor->selftest_status = 1; + +selftest_failure: + /* restore selftest to normal mode */ + st_ism330is_set_selftest(sensor, 0); + + return st_ism330is_sensor_set_enable(sensor, false); +} + +static ssize_t +st_ism330is_sysfs_start_selftest(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct iio_dev *iio_dev = dev_to_iio_dev(dev); + struct st_ism330is_sensor *sensor = iio_priv(iio_dev); + enum st_ism330is_sensor_id id = sensor->id; + struct st_ism330is_hw *hw = sensor->hw; + int ret, test; + u32 gain, mhz; + + if (id != ST_ISM330IS_ID_ACC && + id != ST_ISM330IS_ID_GYRO) + return -EINVAL; + + for (test = 0; test < ARRAY_SIZE(st_ism330is_selftest_table); test++) { + if (strncmp(buf, st_ism330is_selftest_table[test].string_mode, + strlen(st_ism330is_selftest_table[test].string_mode)) == 0) + break; + } + + if (test == ARRAY_SIZE(st_ism330is_selftest_table)) + return -EINVAL; + + ret = iio_device_claim_direct_mode(iio_dev); + if (ret) + return ret; + + /* self test mode unavailable if sensor enabled */ + if (hw->enable_mask & BIT(id)) { + ret = -EBUSY; + + goto out_claim; + } + + gain = sensor->gain; + mhz = sensor->mhz; + if (id == ST_ISM330IS_ID_ACC) { + /* set BDU = 1, FS = 4 g, ODR = 52 Hz */ + st_ism330is_set_full_scale(sensor, IIO_G_TO_M_S_2(122)); + st_ism330is_set_odr(sensor, 52000); + st_ism330is_selftest_sensor(sensor, test); + } else { + /* set BDU = 1, ODR = 208 Hz, FS = 2000 dps */ + st_ism330is_set_full_scale(sensor, IIO_DEGREE_TO_RAD(70000)); + st_ism330is_set_odr(sensor, 208000); + st_ism330is_selftest_sensor(sensor, test); + } + + /* restore full scale after test */ + st_ism330is_set_full_scale(sensor, gain); + st_ism330is_set_odr(sensor, mhz); + +out_claim: + iio_device_release_direct_mode(iio_dev); + + return size; +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(st_ism330is_sysfs_sampling_frequency_avail); +static IIO_DEVICE_ATTR(in_accel_scale_available, 0444, + st_ism330is_sysfs_scale_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_anglvel_scale_available, 0444, + st_ism330is_sysfs_scale_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_temp_scale_available, 0444, + st_ism330is_sysfs_scale_avail, NULL, 0); + +static IIO_DEVICE_ATTR(selftest_available, 0444, + st_ism330is_sysfs_get_selftest_available, + NULL, 0); +static IIO_DEVICE_ATTR(selftest, 0644, + st_ism330is_sysfs_get_selftest_status, + st_ism330is_sysfs_start_selftest, 0); + +static struct attribute *st_ism330is_acc_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_accel_scale_available.dev_attr.attr, + &iio_dev_attr_selftest_available.dev_attr.attr, + &iio_dev_attr_selftest.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_ism330is_acc_attribute_group = { + .attrs = st_ism330is_acc_attributes, +}; + +static const struct iio_info st_ism330is_acc_info = { + .attrs = &st_ism330is_acc_attribute_group, + .read_raw = st_ism330is_read_raw, + .write_raw = st_ism330is_write_raw, + .write_raw_get_fmt = st_ism330is_write_raw_get_fmt, + .debugfs_reg_access = st_ism330is_reg_access, +}; + +static struct attribute *st_ism330is_gyro_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_anglvel_scale_available.dev_attr.attr, + &iio_dev_attr_selftest_available.dev_attr.attr, + &iio_dev_attr_selftest.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_ism330is_gyro_attribute_group = { + .attrs = st_ism330is_gyro_attributes, +}; + +static const struct iio_info st_ism330is_gyro_info = { + .attrs = &st_ism330is_gyro_attribute_group, + .read_raw = st_ism330is_read_raw, + .write_raw = st_ism330is_write_raw, + .write_raw_get_fmt = st_ism330is_write_raw_get_fmt, +}; + +static struct attribute *st_ism330is_temp_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_temp_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_ism330is_temp_attribute_group = { + .attrs = st_ism330is_temp_attributes, +}; + +static const struct iio_info st_ism330is_temp_info = { + .attrs = &st_ism330is_temp_attribute_group, + .read_raw = st_ism330is_read_raw, + .write_raw = st_ism330is_write_raw, + .write_raw_get_fmt = st_ism330is_write_raw_get_fmt, +}; + +static int st_ism330is_reset_device(struct st_ism330is_hw *hw) +{ + int err; + + /* sw reset */ + err = regmap_update_bits(hw->regmap, ST_ISM330IS_REG_CTRL3_C_ADDR, + ST_ISM330IS_SW_RESET_MASK, + FIELD_PREP(ST_ISM330IS_SW_RESET_MASK, 1)); + if (err < 0) + return err; + + /* software reset procedure takes a maximum of 50 µs */ + usleep_range(50, 60); + + return err; +} + +static int st_ism330is_init_device(struct st_ism330is_hw *hw) +{ + /* enable Block Data Update */ + return regmap_update_bits(hw->regmap, ST_ISM330IS_REG_CTRL3_C_ADDR, + ST_ISM330IS_BDU_MASK, + FIELD_PREP(ST_ISM330IS_BDU_MASK, 1)); +} + +static struct iio_dev *st_ism330is_alloc_iiodev(struct st_ism330is_hw *hw, + enum st_ism330is_sensor_id id) +{ + struct st_ism330is_sensor *sensor; + struct iio_dev *iio_dev; + + iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor)); + if (!iio_dev) + return NULL; + + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->dev.parent = hw->dev; + + sensor = iio_priv(iio_dev); + sensor->id = id; + sensor->hw = hw; + + switch (id) { + case ST_ISM330IS_ID_ACC: + iio_dev->channels = st_ism330is_acc_channels; + iio_dev->num_channels = ARRAY_SIZE(st_ism330is_acc_channels); + scnprintf(sensor->name, sizeof(sensor->name), "%s_accel", + ST_ISM330IS_DEV_NAME); + iio_dev->info = &st_ism330is_acc_info; + st_ism330is_set_full_scale(sensor, + st_ism330is_fs_table[id].fs_avl[0].gain); + sensor->offset = 0; + sensor->mhz = st_ism330is_odr_table[id].odr_avl[1].mhz; + sensor->min_st = ST_ISM330IS_ST_ACCEL_MIN; + sensor->max_st = ST_ISM330IS_ST_ACCEL_MAX; + break; + case ST_ISM330IS_ID_GYRO: + iio_dev->channels = st_ism330is_gyro_channels; + iio_dev->num_channels = ARRAY_SIZE(st_ism330is_gyro_channels); + scnprintf(sensor->name, sizeof(sensor->name), "%s_gyro", + ST_ISM330IS_DEV_NAME); + iio_dev->info = &st_ism330is_gyro_info; + st_ism330is_set_full_scale(sensor, + st_ism330is_fs_table[id].fs_avl[0].gain); + sensor->offset = 0; + sensor->mhz = st_ism330is_odr_table[id].odr_avl[1].mhz; + sensor->min_st = ST_ISM330IS_ST_GYRO_MIN; + sensor->max_st = ST_ISM330IS_ST_GYRO_MAX; + break; + case ST_ISM330IS_ID_TEMP: + iio_dev->channels = st_ism330is_temp_channels; + iio_dev->num_channels = ARRAY_SIZE(st_ism330is_temp_channels); + scnprintf(sensor->name, sizeof(sensor->name), "%s_temp", + ST_ISM330IS_DEV_NAME); + iio_dev->info = &st_ism330is_temp_info; + sensor->offset = ST_ISM330IS_TEMP_OFFSET; + sensor->mhz = st_ism330is_odr_table[id].odr_avl[1].mhz; + break; + default: + return NULL; + } + + iio_dev->name = sensor->name; + + return iio_dev; +} + +static void st_ism330is_disable_regulator_action(void *_data) +{ + struct st_ism330is_hw *hw = _data; + + regulator_disable(hw->vddio_supply); + regulator_disable(hw->vdd_supply); +} + +static int st_ism330is_power_enable(struct st_ism330is_hw *hw) +{ + int err; + + hw->vdd_supply = devm_regulator_get(hw->dev, "vdd"); + if (IS_ERR(hw->vdd_supply)) { + if (PTR_ERR(hw->vdd_supply) != -EPROBE_DEFER) + dev_err(hw->dev, "Failed to get vdd regulator %d\n", + (int)PTR_ERR(hw->vdd_supply)); + + return PTR_ERR(hw->vdd_supply); + } + + hw->vddio_supply = devm_regulator_get(hw->dev, "vddio"); + if (IS_ERR(hw->vddio_supply)) { + if (PTR_ERR(hw->vddio_supply) != -EPROBE_DEFER) + dev_err(hw->dev, "Failed to get vddio regulator %d\n", + (int)PTR_ERR(hw->vddio_supply)); + + return PTR_ERR(hw->vddio_supply); + } + + err = regulator_enable(hw->vdd_supply); + if (err) { + dev_err(hw->dev, "Failed to enable vdd regulator: %d\n", err); + return err; + } + + err = regulator_enable(hw->vddio_supply); + if (err) { + regulator_disable(hw->vdd_supply); + return err; + } + + err = devm_add_action_or_reset(hw->dev, + st_ism330is_disable_regulator_action, + hw); + if (err) { + dev_err(hw->dev, + "Failed to setup regulator cleanup action %d\n", + err); + return err; + } + + return 0; +} + +/** + * Probe device function + * Implements [MODULE] feature for Power Management + * + * @param dev: Device pointer. + * @param irq: I2C/SPI/I3C client irq. + * @param hw_id: Sensor HW id. + * @param regmap: Bus Transfer Function pointer. + * @retval 0 if OK, < 0 for error + */ +int st_ism330is_probe(struct device *dev, int irq, struct regmap *regmap) +{ + struct st_ism330is_hw *hw; + int i, err; + + hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL); + if (!hw) + return -ENOMEM; + + dev_set_drvdata(dev, (void *)hw); + + mutex_init(&hw->page_lock); + + hw->regmap = regmap; + hw->dev = dev; + hw->irq = irq; + + err = st_ism330is_power_enable(hw); + if (err != 0) + return err; + + /* + * after the device is powered up, it performs a 10 ms (maximum) boot + * procedure to load the trimming parameters. + * After the boot is completed, both the accelerometer and the gyroscope + * are automatically configured in power-down mode. + */ + usleep_range(10000, 11000); + + err = regmap_write(hw->regmap, + ST_ISM330IS_REG_FUNC_CFG_ACCESS_ADDR, 0); + if (err < 0) + return err; + + err = st_ism330is_check_whoami(hw); + if (err < 0) + return err; + + err = st_ism330is_reset_device(hw); + if (err < 0) + return err; + + err = st_ism330is_init_device(hw); + if (err < 0) + return err; + +#if KERNEL_VERSION(5, 15, 0) <= LINUX_VERSION_CODE + err = iio_read_mount_matrix(hw->dev, &hw->orientation); +#elif KERNEL_VERSION(5, 2, 0) <= LINUX_VERSION_CODE + err = iio_read_mount_matrix(hw->dev, "mount-matrix", &hw->orientation); +#else /* LINUX_VERSION_CODE */ + err = of_iio_read_mount_matrix(hw->dev, "mount-matrix", + &hw->orientation); +#endif /* LINUX_VERSION_CODE */ + + if (err) { + dev_err(dev, "Failed to retrieve mounting matrix %d\n", err); + + return err; + } + + /* register only data sensors */ + for (i = 0; i < ARRAY_SIZE(st_ism330is_main_sensor_list); i++) { + enum st_ism330is_sensor_id id = st_ism330is_main_sensor_list[i]; + + hw->iio_devs[id] = st_ism330is_alloc_iiodev(hw, id); + if (!hw->iio_devs[id]) + continue; + } + + if (!dev_fwnode(dev) || + device_property_read_bool(dev, "enable-sensor-hub")) { + err = st_ism330is_shub_probe(hw); + if (err < 0) + return err; + } + + err = st_ism330is_allocate_buffers(hw); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(st_ism330is_main_sensor_list); i++) { + enum st_ism330is_sensor_id id = st_ism330is_main_sensor_list[i]; + + if (!hw->iio_devs[id]) + continue; + + err = devm_iio_device_register(hw->dev, hw->iio_devs[id]); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL(st_ism330is_probe); + +static int __maybe_unused st_ism330is_suspend(struct device *dev) +{ + struct st_ism330is_hw *hw = dev_get_drvdata(dev); + struct st_ism330is_sensor *sensor; + int i, err = 0; + + for (i = 0; i < ST_ISM330IS_ID_MAX; i++) { + sensor = iio_priv(hw->iio_devs[i]); + if (!hw->iio_devs[i]) + continue; + + if (!(hw->enable_mask & BIT(sensor->id))) + continue; + + err = st_ism330is_set_odr(sensor, 0); + if (err < 0) + return err; + } + + return err < 0 ? err : 0; +} + +static int __maybe_unused st_ism330is_resume(struct device *dev) +{ + struct st_ism330is_hw *hw = dev_get_drvdata(dev); + struct st_ism330is_sensor *sensor; + int i, err = 0; + + for (i = 0; i < ST_ISM330IS_ID_MAX; i++) { + sensor = iio_priv(hw->iio_devs[i]); + if (!hw->iio_devs[i]) + continue; + + if (!(hw->enable_mask & BIT(sensor->id))) + continue; + + err = st_ism330is_set_odr(sensor, sensor->mhz); + if (err < 0) + return err; + } + + return err < 0 ? err : 0; +} + +const struct dev_pm_ops st_ism330is_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(st_ism330is_suspend, st_ism330is_resume) +}; +EXPORT_SYMBOL(st_ism330is_pm_ops); + +MODULE_AUTHOR("MEMS Software Solutions Team"); +MODULE_DESCRIPTION("STMicroelectronics st_ism330is driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/stm/imu/st_ism330is/st_ism330is_i2c.c b/drivers/iio/stm/imu/st_ism330is/st_ism330is_i2c.c new file mode 100644 index 000000000000..28d347457287 --- /dev/null +++ b/drivers/iio/stm/imu/st_ism330is/st_ism330is_i2c.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics st_ism330is i2c driver + * + * MEMS Software Solutions Team + * + * Copyright 2023 STMicroelectronics Inc. + */ + +#include +#include +#include +#include +#include + +#include "st_ism330is.h" + +static const struct regmap_config st_ism330is_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int st_ism330is_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &st_ism330is_i2c_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap %d\n", + (int)PTR_ERR(regmap)); + + return PTR_ERR(regmap); + } + + return st_ism330is_probe(&client->dev, client->irq, regmap); +} + +static const struct of_device_id st_ism330is_i2c_of_match[] = { + { .compatible = "st,ism330is" }, + {}, +}; +MODULE_DEVICE_TABLE(of, st_ism330is_i2c_of_match); + +static const struct i2c_device_id st_ism330is_i2c_id_table[] = { + { ST_ISM330IS_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, st_ism330is_i2c_id_table); + +static struct i2c_driver st_ism330is_driver = { + .driver = { + .name = "st_" ST_ISM330IS_DEV_NAME "_i2c", + .pm = &st_ism330is_pm_ops, + .of_match_table = of_match_ptr(st_ism330is_i2c_of_match), + }, + .probe = st_ism330is_i2c_probe, + .id_table = st_ism330is_i2c_id_table, +}; +module_i2c_driver(st_ism330is_driver); + +MODULE_AUTHOR("MEMS Software Solutions Team"); +MODULE_DESCRIPTION("STMicroelectronics st_ism330is i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/stm/imu/st_ism330is/st_ism330is_shub.c b/drivers/iio/stm/imu/st_ism330is/st_ism330is_shub.c new file mode 100644 index 000000000000..eb822026f4c5 --- /dev/null +++ b/drivers/iio/stm/imu/st_ism330is/st_ism330is_shub.c @@ -0,0 +1,952 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics st_ism330is sensor hub library driver + * + * MEMS Software Solutions Team + * + * Copyright 2023 STMicroelectronics Inc. + */ + +#include +#include +#include +#include +#include + +#include "st_ism330is.h" + +#define ST_ISM330IS_MAX_SLV_NUM 2 + +/** + * @struct st_ism330is_ext_pwr + * @brief External device Power Management description + * reg: Generic sensor register description. + * off_val: Value to write into register to power off external sensor. + * on_val: Value to write into register for power on external sensor. + */ +struct st_ism330is_ext_pwr { + struct st_ism330is_reg reg; + u8 off_val; + u8 on_val; +}; + +/** + * @struct st_ism330is_ext_dev_settings + * @brief External sensor descritor entry + * i2c_addr: External I2C device address (max two). + * wai_addr: Device ID address. + * wai_val: Device ID value. + * odr_table: ODR sensor table. + * fs_table: Full scale table. + * temp_comp_reg: Temperature compensation registers. + * pwr_table: External device Power Management description. + * off_canc_reg: Offset cancellation registers. + * bdu_reg: Block Data Update registers. + * ext_channels:IIO device channel specifications. + * ext_chan_depth: Max number of IIO device channel specifications. + * data_len: Sensor output data len. + */ +struct st_ism330is_ext_dev_settings { + u8 i2c_addr[2]; + u8 wai_addr; + u8 wai_val; + struct st_ism330is_odr_table_entry odr_table; + struct st_ism330is_fs_table_entry fs_table; + struct st_ism330is_reg temp_comp_reg; + struct st_ism330is_ext_pwr pwr_table; + struct st_ism330is_reg off_canc_reg; + struct st_ism330is_reg bdu_reg; + const struct iio_chan_spec ext_channels[5]; + u8 ext_chan_depth; + u8 data_len; +}; + +static const struct st_ism330is_ext_dev_settings st_ism330is_ext_dev_table[] = { + { + /* LIS2MDL */ + .i2c_addr = { 0x1e }, + .wai_addr = 0x4f, + .wai_val = 0x40, + .odr_table = { + .size = 5, + .reg = { + .addr = 0x60, + .mask = GENMASK(3, 2), + }, + .odr_avl[0] = { 5000, 0x0 }, + .odr_avl[1] = { 10000, 0x0 }, + .odr_avl[2] = { 20000, 0x1 }, + .odr_avl[3] = { 50000, 0x2 }, + .odr_avl[4] = { 100000, 0x3 }, + }, + .fs_table = { + .fs_len = 1, + .fs_avl[0] = { + .gain = 1500, + .val = 0x0, + }, /* 1500 uG/LSB */ + }, + .temp_comp_reg = { + .addr = 0x60, + .mask = BIT(7), + }, + .pwr_table = { + .reg = { + .addr = 0x60, + .mask = GENMASK(1, 0), + }, + .off_val = 0x2, + .on_val = 0x0, + }, + .off_canc_reg = { + .addr = 0x61, + .mask = BIT(1), + }, + .bdu_reg = { + .addr = 0x62, + .mask = BIT(4), + }, + .ext_channels[0] = ST_ISM330IS_DATA_CHANNEL(IIO_MAGN, 0x68, + 1, IIO_MOD_X, 0, + 16, 16, 's', NULL), + .ext_channels[1] = ST_ISM330IS_DATA_CHANNEL(IIO_MAGN, 0x6a, + 1, IIO_MOD_Y, 1, + 16, 16, 's', NULL), + .ext_channels[2] = ST_ISM330IS_DATA_CHANNEL(IIO_MAGN, 0x6c, + 1, IIO_MOD_Z, 2, + 16, 16, 's', NULL), + .ext_channels[3] = IIO_CHAN_SOFT_TIMESTAMP(3), + .ext_chan_depth = 4, + .data_len = 6, + }, + { + /* LPS22HH */ + .i2c_addr = { 0x5c, 0x5d }, + .wai_addr = 0x0f, + .wai_val = 0xb3, + .odr_table = { + .size = 5, + .reg = { + .addr = 0x10, + .mask = GENMASK(6, 4), + }, + .odr_avl[0] = { 1000, 0x1 }, + .odr_avl[1] = { 10000, 0x2 }, + .odr_avl[2] = { 25000, 0x3 }, + .odr_avl[3] = { 50000, 0x4 }, + .odr_avl[4] = { 100000, 0x6 }, + }, + .fs_table = { + .fs_len = 1, + /* hPa miscro scale */ + .fs_avl[0] = { + .gain = 1000000UL / 4096UL, + .val = 0x0, + }, + }, + .bdu_reg = { + .addr = 0x10, + .mask = BIT(1), + }, + .ext_channels[0] = ST_ISM330IS_DATA_CHANNEL(IIO_PRESSURE, 0x28, + 0, IIO_NO_MOD, 0, + 24, 32, 'u', NULL), + .ext_channels[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .ext_chan_depth = 2, + .data_len = 3, + }, +}; + +/** + * Wait write trigger [SHUB] + * + * In write on external device register, each operation is triggered + * by accel/gyro data ready, this means that wait time depends on ODR + * plus i2c time + * NOTE: Be sure to enable Acc or Gyro before this operation + * + * @param hw: ST IMU MEMS hw instance. + */ +static inline void st_ism330is_shub_wait_complete(struct st_ism330is_hw *hw) +{ + struct st_ism330is_sensor *sensor; + u32 odr; + + sensor = iio_priv(hw->iio_devs[ST_ISM330IS_ID_ACC]); + + /* check if acc is enabled (it should be) */ + if (hw->enable_mask & BIT(ST_ISM330IS_ID_ACC)) + odr = sensor->mhz; + else + odr = 12500; + + /* odr tollerance is 10 % */ + msleep(1100000 / odr); +} + +/** + * Read from sensor hub bank register [SHUB] + * + * NOTE: uses page_lock + * + * @param hw: ST IMU MEMS hw instance. + * @param addr: Remote address register. + * @param data: Data buffer. + * @param len: Data read len. + * @return 0 if OK, < 0 if ERROR + */ +static int st_ism330is_shub_read_reg(struct st_ism330is_hw *hw, u8 addr, + u8 *data, int len) +{ + int err; + + mutex_lock(&hw->page_lock); + err = st_ism330is_set_page_access(hw, ST_ISM330IS_SHUB_REG_MASK, + true); + if (err < 0) + goto out; + + err = regmap_bulk_read(hw->regmap, (unsigned int)addr, + (unsigned int *)data, len); + st_ism330is_set_page_access(hw, ST_ISM330IS_SHUB_REG_MASK, false); +out: + mutex_unlock(&hw->page_lock); + + return err; +} + +/** + * Write to sensor hub bank register [SHUB] + * + * NOTE: uses page_lock + * + * @param hw: ST IMU MEMS hw instance. + * @param addr: Remote address register. + * @param data: Data buffer. + * @param len: Data read len. + * @return 0 if OK, < 0 if ERROR + */ +static int st_ism330is_shub_write_reg(struct st_ism330is_hw *hw, u8 addr, + u8 *data, int len) +{ + int err; + + mutex_lock(&hw->page_lock); + err = st_ism330is_set_page_access(hw, ST_ISM330IS_SHUB_REG_MASK, true); + if (err < 0) + goto out; + + err = regmap_bulk_write(hw->regmap, (unsigned int)addr, + (unsigned int *)data, len); + st_ism330is_set_page_access(hw, ST_ISM330IS_SHUB_REG_MASK, false); +out: + mutex_unlock(&hw->page_lock); + + return err; +} + +/** + * Enable sensor hub interface [SHUB] + * + * NOTE: uses page_lock + * + * @param sensor: ST IMU sensor instance + * @param enable: Master Enable/Disable. + * @return 0 if OK, < 0 if ERROR + */ +static int +st_ism330is_shub_master_enable(struct st_ism330is_sensor *sensor, bool enable) +{ + struct st_ism330is_hw *hw = sensor->hw; + int err; + + /* enable main sensor as trigger */ + err = st_ism330is_sensor_set_enable(sensor, enable); + if (err < 0) + return err; + + mutex_lock(&hw->page_lock); + err = st_ism330is_set_page_access(hw, ST_ISM330IS_SHUB_REG_MASK, true); + if (err < 0) + goto out; + + err = __st_ism330is_write_with_mask(hw, + ST_ISM330IS_REG_MASTER_CONFIG_ADDR, + ST_ISM330IS_MASTER_ON_MASK, + enable); + + st_ism330is_set_page_access(hw, ST_ISM330IS_SHUB_REG_MASK, false); + +out: + mutex_unlock(&hw->page_lock); + + return err; +} + +/** + * Read sensor data register from shub interface + * + * NOTE: use SLV3 i2c slave for one-shot read operation + * + * @param sensor: ST IMU sensor instance + * @param addr: Remote address register. + * @param data: Data buffer. + * @param len: Data read len. + * @return 0 if OK, < 0 if ERROR + */ +int st_ism330is_shub_read(struct st_ism330is_sensor *sensor, + u8 addr, u8 *data, int len) +{ + struct st_ism330is_ext_dev_info *ext_info = &sensor->ext_dev_info; + struct st_ism330is_hw *hw = sensor->hw; + u8 out_addr = ST_ISM330IS_REG_SENSOR_HUB_1_ADDR + hw->ext_data_len; + u8 config[3]; + int err; + + config[0] = (ext_info->ext_dev_i2c_addr << 1) | 1; + config[1] = addr; + config[2] = len & 0x7; + + err = st_ism330is_shub_write_reg(hw, ST_ISM330IS_REG_SLV3_ADDR, + config, sizeof(config)); + if (err < 0) + return err; + + err = st_ism330is_shub_master_enable(sensor, true); + if (err < 0) + return err; + + st_ism330is_shub_wait_complete(hw); + + err = st_ism330is_shub_read_reg(hw, out_addr, data, len & 0x7); + + st_ism330is_shub_master_enable(sensor, false); + + memset(config, 0, sizeof(config)); + + return st_ism330is_shub_write_reg(hw, ST_ISM330IS_REG_SLV3_ADDR, + config, sizeof(config)); +} + +/** + * Write sensor data register from shub interface + * + * NOTE: use SLV0 i2c slave for write operation + * + * @param sensor: ST IMU sensor instance + * @param addr: Remote address register. + * @param data: Data buffer. + * @param len: Data read len. + * @return 0 if OK, < 0 if ERROR + */ +static int st_ism330is_shub_write(struct st_ism330is_sensor *sensor, + u8 addr, u8 *data, int len) +{ + struct st_ism330is_ext_dev_info *ext_info = &sensor->ext_dev_info; + struct st_ism330is_hw *hw = sensor->hw; + u8 mconfig = ST_ISM330IS_WRITE_ONCE_MASK | 3 | hw->i2c_master_pu; + u8 config[3] = {}; + int err, i; + + /* AuxSens = 3 + wr once + pull up configuration */ + err = st_ism330is_shub_write_reg(hw, + ST_ISM330IS_REG_MASTER_CONFIG_ADDR, + &mconfig, sizeof(mconfig)); + if (err < 0) + return err; + + config[0] = ext_info->ext_dev_i2c_addr << 1; + for (i = 0; i < len; i++) { + config[1] = addr + i; + + err = st_ism330is_shub_write_reg(hw, + ST_ISM330IS_REG_SLV0_ADDR, + config, sizeof(config)); + if (err < 0) + return err; + + err = st_ism330is_shub_write_reg(hw, + ST_ISM330IS_REG_DATAWRITE_SLV0_ADDR, + &data[i], 1); + if (err < 0) + return err; + + err = st_ism330is_shub_master_enable(sensor, true); + if (err < 0) + return err; + + st_ism330is_shub_wait_complete(hw); + + st_ism330is_shub_master_enable(sensor, false); + } + + return st_ism330is_shub_write_reg(hw, ST_ISM330IS_REG_SLV0_ADDR, + config, sizeof(config)); +} + +/** + * Write sensor data register from shub interface using register bitmask + * + * @param sensor: ST IMU sensor instance + * @param addr: Remote address register. + * @param mask: Register bitmask. + * @param val: Data buffer. + * @return 0 if OK, < 0 if ERROR + */ +static int +st_ism330is_shub_write_with_mask(struct st_ism330is_sensor *sensor, + u8 addr, u8 mask, u8 val) +{ + int err; + u8 data; + + err = st_ism330is_shub_read(sensor, addr, &data, sizeof(data)); + if (err < 0) + return err; + + data = (data & ~mask) | ST_ISM330IS_SHIFT_VAL(val, mask); + + return st_ism330is_shub_write(sensor, addr, &data, sizeof(data)); +} + +/** + * Configure external sensor connected on master I2C interface + * + * NOTE: use SLV1/SLV2 i2c slave for FIFO read operation + * + * @param sensor: ST IMU sensor instance + * @param enable: Enable/Disable sensor. + * @return 0 if OK, < 0 if ERROR + */ +static int +st_ism330is_shub_config_channels(struct st_ism330is_sensor *sensor, bool enable) +{ + struct st_ism330is_ext_dev_info *ext_info; + struct st_ism330is_hw *hw = sensor->hw; + struct st_ism330is_sensor *cur_sensor; + u8 config[6] = {}, enable_mask; + int i, j = 0; + + enable_mask = enable ? hw->enable_mask | BIT(sensor->id) + : hw->enable_mask & ~BIT(sensor->id); + + for (i = ST_ISM330IS_ID_EXT0; i <= ST_ISM330IS_ID_EXT1; i++) { + if (!hw->iio_devs[i]) + continue; + + cur_sensor = iio_priv(hw->iio_devs[i]); + if (!(enable_mask & BIT(cur_sensor->id))) + continue; + + ext_info = &cur_sensor->ext_dev_info; + config[j] = (ext_info->ext_dev_i2c_addr << 1) | 1; + config[j + 1] = + ext_info->ext_dev_settings->ext_channels[0].address; + config[j + 2] = (ext_info->ext_dev_settings->data_len & + ST_ISM330IS_SLAVE_NUMOP_MASK); + j += 3; + } + + return st_ism330is_shub_write_reg(hw, ST_ISM330IS_REG_SLV1_ADDR, + config, sizeof(config)); +} + +/** + * Get a valid ODR [SHUB] + * + * Check a valid ODR closest to the passed value + * + * @param sensor: SST IMU sensor instance. + * @param odr: ODR value (in Hz). + * @param val: ODR register value data pointer. + * @return 0 if OK, negative value for ERROR + */ +static int st_ism330is_shub_get_odr_val(struct st_ism330is_sensor *sensor, + u32 odr, u8 *val) +{ + struct st_ism330is_ext_dev_info *ext_info = &sensor->ext_dev_info; + int i; + + for (i = 0; i < ext_info->ext_dev_settings->odr_table.size; i++) + if (ext_info->ext_dev_settings->odr_table.odr_avl[i].mhz >= odr) + break; + + if (i == ext_info->ext_dev_settings->odr_table.size) + return -EINVAL; + + *val = ext_info->ext_dev_settings->odr_table.odr_avl[i].val; + + return 0; +} + +/** + * Set new ODR to sensor [SHUB] + * + * Set a valid ODR closest to the passed value + * + * @param sensor: ST IMU sensor instance + * @param odr: ODR value (in Hz). + * @return 0 if OK, negative value for ERROR + */ +static int st_ism330is_shub_set_odr(struct st_ism330is_sensor *sensor, u32 odr) +{ + struct st_ism330is_ext_dev_info *ext_info = &sensor->ext_dev_info; + struct st_ism330is_hw *hw = sensor->hw; + u8 odr_val; + int err; + + err = st_ism330is_shub_get_odr_val(sensor, odr, &odr_val); + if (err < 0) + return err; + + if (sensor->mhz == odr && (hw->enable_mask & BIT(sensor->id))) + return 0; + + return st_ism330is_shub_write_with_mask(sensor, + ext_info->ext_dev_settings->odr_table.reg.addr, + ext_info->ext_dev_settings->odr_table.reg.mask, + odr_val); +} + +/** + * Enable or Disable sensor [SHUB] + * + * @param sensor: ST IMU sensor instance + * @param enable: Enable or disable the sensor [true,false]. + * @return 0 if OK, negative value for ERROR + */ +int st_ism330is_shub_set_enable(struct st_ism330is_sensor *sensor, bool enable) +{ + struct st_ism330is_ext_dev_info *ext_info = &sensor->ext_dev_info; + int err; + + err = st_ism330is_shub_config_channels(sensor, enable); + if (err < 0) + return err; + + if (enable) { + err = st_ism330is_shub_set_odr(sensor, sensor->mhz); + if (err < 0) + return err; + } else { + err = st_ism330is_shub_write_with_mask(sensor, + ext_info->ext_dev_settings->odr_table.reg.addr, + ext_info->ext_dev_settings->odr_table.reg.mask, + 0); + if (err < 0) + return err; + } + + if (ext_info->ext_dev_settings->pwr_table.reg.addr) { + u8 val; + + val = enable ? ext_info->ext_dev_settings->pwr_table.on_val + : ext_info->ext_dev_settings->pwr_table.off_val; + err = st_ism330is_shub_write_with_mask(sensor, + ext_info->ext_dev_settings->pwr_table.reg.addr, + ext_info->ext_dev_settings->pwr_table.reg.mask, + val); + if (err < 0) + return err; + } + + return st_ism330is_shub_master_enable(sensor, enable); +} + +static inline u32 st_ism330is_get_unaligned_le24(const u8 *p) +{ + return (s32)((p[0] | p[1] << 8 | p[2] << 16) << 8) >> 8; +} + +/** + * Single sensor read operation [SHUB] + * + * @param sensor: ST IMU sensor instance + * @param ch: IIO Channel. + * @param val: Output data register value. + * @return IIO_VAL_INT if OK, negative value for ERROR + */ +static int +st_ism330is_shub_read_oneshot(struct st_ism330is_sensor *sensor, + struct iio_chan_spec const *ch, int *val) +{ + int err, delay, len = ch->scan_type.realbits >> 3; + u8 data[4]; + + err = st_ism330is_shub_set_enable(sensor, true); + if (err < 0) + return err; + + delay = 1100000000 / sensor->mhz; + usleep_range(delay, delay + (delay >> 1)); + + err = st_ism330is_shub_read(sensor, ch->address, data, len); + if (err < 0) + return err; + + st_ism330is_shub_set_enable(sensor, false); + + switch (len) { + case 3: + *val = (s32)st_ism330is_get_unaligned_le24(data); + break; + case 2: + *val = (s16)get_unaligned_le16(data); + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +/** + * Read Sensor data configuration [SHUB] + * + * @param iio_dev: IIO Device. + * @param ch: IIO Channel. + * @param val: Data Buffer (MSB). + * @param val2: Data Buffer (LSB). + * @param mask: Data Mask. + * @return 0 if OK, -EINVAL value for ERROR + */ +static int st_ism330is_shub_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + struct st_ism330is_sensor *sensor = iio_priv(iio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(iio_dev); + if (ret) + return ret; + + ret = st_ism330is_shub_read_oneshot(sensor, ch, val); + iio_device_release_direct_mode(iio_dev); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = sensor->mhz; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = sensor->gain; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/** + * Write Sensor data configuration [SHUB] + * + * @param iio_dev: IIO Device. + * @param chan: IIO Channel. + * @param val: Data Buffer (MSB). + * @param val2: Data Buffer (LSB). + * @param mask: Data Mask. + * @return 0 if OK, -EINVAL value for ERROR + */ +static int st_ism330is_shub_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct st_ism330is_sensor *sensor = iio_priv(iio_dev); + int err; + + mutex_lock(&iio_dev->mlock); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + u8 data; + + val = val * 1000 + val2 / 1000; + err = st_ism330is_shub_get_odr_val(sensor, val, &data); + if (!err) + sensor->mhz = val; + break; + } + case IIO_CHAN_INFO_SCALE: + err = 0; + break; + default: + err = -EINVAL; + break; + } + + mutex_unlock(&iio_dev->mlock); + + return err; +} + +/** + * Get a list of available sensor ODR [SHUB] + * + * List of available ODR returned separated by commas + * + * @param dev: IIO Device. + * @param attr: IIO Channel attribute. + * @param buf: User buffer. + * @return buffer len + */ +static ssize_t +st_ism330is_sysfs_shub_sampling_freq_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct st_ism330is_sensor *sensor = iio_priv(dev_to_iio_dev(dev)); + struct st_ism330is_ext_dev_info *ext_info = &sensor->ext_dev_info; + int i, len = 0; + + for (i = 0; i < ext_info->ext_dev_settings->odr_table.size; i++) { + u16 val = ext_info->ext_dev_settings->odr_table.odr_avl[i].mhz; + + if (val > 0) + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%03d ", + val / 1000, val % 1000); + } + buf[len - 1] = '\n'; + + return len; +} + +/** + * Get a list of available sensor Full Scale [SHUB] + * + * List of available Full Scale returned separated by commas + * + * @param dev: IIO Device. + * @param attr: IIO Channel attribute. + * @param buf: User buffer. + * @return buffer len + */ +static ssize_t st_ism330is_sysfs_shub_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct st_ism330is_sensor *sensor = iio_priv(dev_to_iio_dev(dev)); + struct st_ism330is_ext_dev_info *ext_info = &sensor->ext_dev_info; + int i, len = 0; + + for (i = 0; i < ext_info->ext_dev_settings->fs_table.fs_len; i++) { + u16 val = ext_info->ext_dev_settings->fs_table.fs_avl[i].gain; + + if (val > 0) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ", + val); + } + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(st_ism330is_sysfs_shub_sampling_freq_avail); +static IIO_DEVICE_ATTR(in_ext_scale_available, 0444, + st_ism330is_sysfs_shub_scale_avail, NULL, 0); + +static struct attribute *st_ism330is_ext_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_ext_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_ism330is_ext_attribute_group = { + .attrs = st_ism330is_ext_attributes, +}; + +static const struct iio_info st_ism330is_ext_info = { + .attrs = &st_ism330is_ext_attribute_group, + .read_raw = st_ism330is_shub_read_raw, + .write_raw = st_ism330is_shub_write_raw, +}; + +/** + * Allocate IIO device [SHUB] + * + * @param hw: ST IMU MEMS hw instance. + * @param ext_settings: xternal sensor descritor entry. + * @param id: Sensor Identifier. + * @param i2c_addr: external I2C address on master bus. + * @return struct iio_dev *, NULL if ERROR + */ +static struct iio_dev *st_ism330is_shub_alloc_iio_dev(struct st_ism330is_hw *hw, + const struct st_ism330is_ext_dev_settings *ext_settings, + enum st_ism330is_sensor_id id, u8 i2c_addr) +{ + struct st_ism330is_sensor *sensor; + struct iio_dev *iio_dev; + + iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor)); + if (!iio_dev) + return NULL; + + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->dev.parent = hw->dev; + iio_dev->info = &st_ism330is_ext_info; + iio_dev->channels = ext_settings->ext_channels; + iio_dev->num_channels = ext_settings->ext_chan_depth; + sensor = iio_priv(iio_dev); + sensor->id = id; + sensor->hw = hw; + sensor->mhz = ext_settings->odr_table.odr_avl[0].mhz; + sensor->gain = ext_settings->fs_table.fs_avl[0].gain; + sensor->ext_dev_info.ext_dev_i2c_addr = i2c_addr; + sensor->ext_dev_info.ext_dev_settings = ext_settings; + + switch (iio_dev->channels[0].type) { + case IIO_MAGN: + scnprintf(sensor->name, sizeof(sensor->name), "%s_magn", + ST_ISM330IS_DEV_NAME); + break; + case IIO_PRESSURE: + scnprintf(sensor->name, sizeof(sensor->name), "%s_press", + ST_ISM330IS_DEV_NAME); + break; + default: + scnprintf(sensor->name, sizeof(sensor->name), "%s_ext", + ST_ISM330IS_DEV_NAME); + break; + } + + iio_dev->name = sensor->name; + + return iio_dev; +} + +static int +st_ism330is_shub_init_remote_sensor(struct st_ism330is_sensor *sensor) +{ + struct st_ism330is_ext_dev_info *ext_info = &sensor->ext_dev_info; + int err = 0; + + if (ext_info->ext_dev_settings->bdu_reg.addr) + err = st_ism330is_shub_write_with_mask(sensor, + ext_info->ext_dev_settings->bdu_reg.addr, + ext_info->ext_dev_settings->bdu_reg.mask, 1); + + if (ext_info->ext_dev_settings->temp_comp_reg.addr) + err = st_ism330is_shub_write_with_mask(sensor, + ext_info->ext_dev_settings->temp_comp_reg.addr, + ext_info->ext_dev_settings->temp_comp_reg.mask, 1); + + if (ext_info->ext_dev_settings->off_canc_reg.addr) + err = st_ism330is_shub_write_with_mask(sensor, + ext_info->ext_dev_settings->off_canc_reg.addr, + ext_info->ext_dev_settings->off_canc_reg.mask, 1); + + return err; +} + +/** + * Probe device function [SHUB] + * + * @param hw: ST IMU MEMS hw instance. + * @return 0 if OK, negative for ERROR + */ +int st_ism330is_shub_probe(struct st_ism330is_hw *hw) +{ + const struct st_ism330is_ext_dev_settings *settings; + struct st_ism330is_sensor *acc_sensor, *sensor; + struct device_node *np = hw->dev->of_node; + u8 config[3], data, num_ext_dev = 0; + enum st_ism330is_sensor_id id; + int err, i = 0, j; + + if (np && of_property_read_bool(np, "drive-pullup-shub")) { + dev_info(hw->dev, "enabling pull up on i2c master\n"); + err = st_ism330is_shub_read_reg(hw, + ST_ISM330IS_REG_MASTER_CONFIG_ADDR, + &data, sizeof(data)); + if (err < 0) + return err; + + data |= ST_ISM330IS_SHUB_PU_EN_MASK; + err = st_ism330is_shub_write_reg(hw, + ST_ISM330IS_REG_MASTER_CONFIG_ADDR, + &data, sizeof(data)); + + if (err < 0) + return err; + + hw->i2c_master_pu = ST_ISM330IS_SHUB_PU_EN_MASK; + } + + acc_sensor = iio_priv(hw->iio_devs[ST_ISM330IS_ID_ACC]); + while (i < ARRAY_SIZE(st_ism330is_ext_dev_table) && + num_ext_dev < ST_ISM330IS_MAX_SLV_NUM) { + settings = &st_ism330is_ext_dev_table[i]; + + for (j = 0; j < ARRAY_SIZE(settings->i2c_addr); j++) { + if (!settings->i2c_addr[j]) + continue; + + /* read wai slave register */ + config[0] = (settings->i2c_addr[j] << 1) | 1; + config[1] = settings->wai_addr; + config[2] = 1; + + err = st_ism330is_shub_write_reg(hw, + ST_ISM330IS_REG_SLV0_ADDR, + config, sizeof(config)); + if (err < 0) + return err; + + err = st_ism330is_shub_master_enable(acc_sensor, true); + if (err < 0) + return err; + + st_ism330is_shub_wait_complete(hw); + + err = st_ism330is_shub_read_reg(hw, + ST_ISM330IS_REG_SENSOR_HUB_1_ADDR, + &data, sizeof(data)); + + st_ism330is_shub_master_enable(acc_sensor, false); + + if (err < 0) + return err; + + if (data != settings->wai_val) + continue; + + id = ST_ISM330IS_ID_EXT0 + num_ext_dev; + hw->iio_devs[id] = st_ism330is_shub_alloc_iio_dev(hw, + settings, id, + settings->i2c_addr[j]); + if (!hw->iio_devs[id]) + return -ENOMEM; + + sensor = iio_priv(hw->iio_devs[id]); + err = st_ism330is_shub_init_remote_sensor(sensor); + if (err < 0) + return err; + + num_ext_dev++; + hw->ext_data_len += settings->data_len; + break; + } + + i++; + } + + if (!num_ext_dev) + return 0; + + memset(config, 0, sizeof(config)); + err = st_ism330is_shub_write_reg(hw, ST_ISM330IS_REG_SLV0_ADDR, + config, sizeof(config)); + if (err < 0) + return err; + + /* AuxSens = 3 + wr once */ + data = ST_ISM330IS_WRITE_ONCE_MASK | 3 | hw->i2c_master_pu; + return st_ism330is_shub_write_reg(hw, + ST_ISM330IS_REG_MASTER_CONFIG_ADDR, + &data, sizeof(data)); +} diff --git a/drivers/iio/stm/imu/st_ism330is/st_ism330is_spi.c b/drivers/iio/stm/imu/st_ism330is/st_ism330is_spi.c new file mode 100644 index 000000000000..6e76f5d400ff --- /dev/null +++ b/drivers/iio/stm/imu/st_ism330is/st_ism330is_spi.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics st_ism330is spi driver + * + * MEMS Software Solutions Team + * + * Copyright 2023 STMicroelectronics Inc. + */ + +#include +#include +#include +#include +#include + +#include "st_ism330is.h" + +static const struct regmap_config st_ism330is_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int st_ism330is_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &st_ism330is_spi_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap %d\n", + (int)PTR_ERR(regmap)); + + return PTR_ERR(regmap); + } + + return st_ism330is_probe(&spi->dev, spi->irq, regmap); +} + +static const struct of_device_id st_ism330is_spi_of_match[] = { + { .compatible = "st,ism330is" }, + {}, +}; +MODULE_DEVICE_TABLE(of, st_ism330is_spi_of_match); + +static const struct spi_device_id st_ism330is_spi_id_table[] = { + { ST_ISM330IS_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(spi, st_ism330is_spi_id_table); + +static struct spi_driver st_ism330is_driver = { + .driver = { + .name = "st_" ST_ISM330IS_DEV_NAME "_spi", + .pm = &st_ism330is_pm_ops, + .of_match_table = of_match_ptr(st_ism330is_spi_of_match), + }, + .probe = st_ism330is_spi_probe, + .id_table = st_ism330is_spi_id_table, +}; +module_spi_driver(st_ism330is_driver); + +MODULE_AUTHOR("MEMS Software Solutions Team"); +MODULE_DESCRIPTION("STMicroelectronics st_ism330is spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/stm/imu/st_ism330is/st_ism330is_triggers.c b/drivers/iio/stm/imu/st_ism330is/st_ism330is_triggers.c new file mode 100644 index 000000000000..5cf021eefd0c --- /dev/null +++ b/drivers/iio/stm/imu/st_ism330is/st_ism330is_triggers.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics st_ism330is trigger buffer library driver + * + * MEMS Software Solutions Team + * + * Copyright 2023 STMicroelectronics Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st_ism330is.h" + +#define ST_ISM330IS_AG_SAMPLE_SIZE 6 +#define ST_ISM330IS_PT_SAMPLE_SIZE 2 + +static int st_ism330is_buffer_enable(struct iio_dev *iio_dev, bool enable) +{ + struct st_ism330is_sensor *sensor = iio_priv(iio_dev); + + if (sensor->id == ST_ISM330IS_ID_EXT0 || + sensor->id == ST_ISM330IS_ID_EXT1) + return st_ism330is_shub_set_enable(sensor, enable); + + return st_ism330is_sensor_set_enable(sensor, enable); +} + +static int st_ism330is_fifo_preenable(struct iio_dev *iio_dev) +{ + return st_ism330is_buffer_enable(iio_dev, true); +} + +static int st_ism330is_fifo_postdisable(struct iio_dev *iio_dev) +{ + return st_ism330is_buffer_enable(iio_dev, false); +} + +static const struct iio_buffer_setup_ops st_ism330is_buffer_setup_ops = { + .preenable = st_ism330is_fifo_preenable, + +#if KERNEL_VERSION(5, 10, 0) > LINUX_VERSION_CODE + .postenable = iio_triggered_buffer_postenable, + .predisable = iio_triggered_buffer_predisable, +#endif /* LINUX_VERSION_CODE */ + + .postdisable = st_ism330is_fifo_postdisable, +}; + +static irqreturn_t st_ism330is_buffer_pollfunc(int irq, void *private) +{ + u8 iio_buf[ALIGN(ST_ISM330IS_AG_SAMPLE_SIZE, sizeof(s64)) + + sizeof(s64) + sizeof(s64)]; + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct st_ism330is_sensor *sensor = iio_priv(indio_dev); + struct st_ism330is_hw *hw = sensor->hw; + int addr = indio_dev->channels[0].address; + + switch (indio_dev->channels[0].type) { + case IIO_ACCEL: + case IIO_ANGL_VEL: + st_ism330is_read_locked(hw, addr, &iio_buf, + ST_ISM330IS_AG_SAMPLE_SIZE); + break; + case IIO_TEMP: + st_ism330is_read_locked(hw, addr, &iio_buf, + ST_ISM330IS_PT_SAMPLE_SIZE); + break; + case IIO_PRESSURE: + st_ism330is_shub_read(sensor, addr, (u8 *)&iio_buf, + ST_ISM330IS_PT_SAMPLE_SIZE); + break; + case IIO_MAGN: + st_ism330is_shub_read(sensor, addr, (u8 *)&iio_buf, + ST_ISM330IS_AG_SAMPLE_SIZE); + break; + default: + return -EINVAL; + } + + iio_push_to_buffers_with_timestamp(indio_dev, iio_buf, + iio_get_time_ns(indio_dev)); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int st_ism330is_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct st_ism330is_hw *hw = iio_trigger_get_drvdata(trig); + + dev_dbg(hw->dev, "trigger set %d\n", state); + + return 0; +} + +static const struct iio_trigger_ops st_ism330is_trigger_ops = { + .set_trigger_state = st_ism330is_trig_set_state, +}; + +int st_ism330is_allocate_buffers(struct st_ism330is_hw *hw) +{ + int i; + + for (i = 0; + i < ARRAY_SIZE(st_ism330is_triggered_main_sensor_list); + i++) { + enum st_ism330is_sensor_id id; + int err; + + id = st_ism330is_triggered_main_sensor_list[i]; + if (!hw->iio_devs[id]) + continue; + + err = devm_iio_triggered_buffer_setup(hw->dev, + hw->iio_devs[id], NULL, + st_ism330is_buffer_pollfunc, + &st_ism330is_buffer_setup_ops); + if (err) + return err; + } + + return 0; +} diff --git a/stm_iio_configs/ism330is_defconfig b/stm_iio_configs/ism330is_defconfig new file mode 100644 index 000000000000..154edb935837 --- /dev/null +++ b/stm_iio_configs/ism330is_defconfig @@ -0,0 +1,3 @@ +CONFIG_IIO_ST_ISM330IS=m +CONFIG_IIO_ST_ISM330IS_I2C=m +CONFIG_IIO_ST_ISM330IS_SPI=m