From 622ea77bfccd751247b1c08c3126d7ab716f0423 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 25 Sep 2023 03:38:49 +0200 Subject: [PATCH] This commit adds support to the bmi323 device on top of the pre-existing bmc150 kernel module. Some new devices for example the ROG Ally and the Air Plus identify this chip in the ACPI table as a bmc150 so previously the original module was loaded, but was erroring out as it cannot handle such device. The device I own does not allow me to use the interrupt part of the device as the interrupt pin is not connected (or not advertised to be connected) hence I avoided including on this commit anything related to IRQ. This driver has already been proved to work well enough to be used in the switch emulator "yuzu". While designing this module my main focus was not to alter the original driver and not to limit the original author in regard to future mofications, and I was mostly able to achive this, except: 1) I added a new structure on top of the original one and added a field that is responsible for holding information on what type of chip the module is currently managing 2) the previous point required the init function of the original driver to write that field in order to be sure no bmi323 code was executed when the old part of the module is managing the device 3) as the original driver issued an i2c write on some register not really meant to be written in the bmi323 device I have made sure an i2c read to discover the bmi323 is performed prior to that code: such read SHOULD fail in the older bmc150 IC for two reasons: - the i2c address is not reported in the memory map of the bmc150 in its datasheet - the i2c read attempts to get 4 bytes out of a 8-bit device - the fourth bit (the one that cannot be read from a bmc150 device) is initialized to 0 and bmi323 presence is signaled with a 1 in the LSB that is the fourth coming out of the device in temporal order --- drivers/iio/accel/bmc150-accel-core.c | 2307 ++++++++++++++++++++++++- drivers/iio/accel/bmc150-accel-i2c.c | 100 +- drivers/iio/accel/bmc150-accel.h | 94 +- 3 files changed, 2495 insertions(+), 6 deletions(-) diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c index 110591804b4c..9a2c1732c9ef 100644 --- a/drivers/iio/accel/bmc150-accel-core.c +++ b/drivers/iio/accel/bmc150-accel-core.c @@ -130,6 +130,73 @@ #define BMC150_ACCEL_REG_FIFO_DATA 0x3F #define BMC150_ACCEL_FIFO_LENGTH 32 +#define BMC150_BMI323_TEMPER_CENTER_VAL 23 +#define BMC150_BMI323_TEMPER_LSB_PER_KELVIN_VAL 512 + +#define BMC150_BMI323_AUTO_SUSPEND_DELAY_MS 2000 + +#define BMC150_BMI323_CHIP_ID_REG 0x00 +#define BMC150_BMI323_SOFT_RESET_REG 0x7E +#define BMC150_BMI323_SOFT_RESET_VAL 0xDEAFU +#define BMC150_BMI323_DATA_BASE_REG 0x03 +#define BMC150_BMI323_TEMPERATURE_DATA_REG 0x09 +#define BMC150_BMI323_FIFO_FILL_LEVEL_REG 0x15 +#define BMC150_BMI323_FIFO_DATA_REG 0x16 +#define BMC150_BMI323_ACC_CONF_REG 0x20 +#define BMC150_BMI323_GYR_CONF_REG 0x21 +#define BMC150_BMI323_FIFO_CONF_REG 0x36 + +// these are bits [0:3] of ACC_CONF.acc_odr, sample rate in Hz for the accel part of the chip +#define BMC150_BMI323_ACCEL_ODR_0_78123_VAL 0x0001 +#define BMC150_BMI323_ACCEL_ODR_1_5625_VAL 0x0002 +#define BMC150_BMI323_ACCEL_ODR_3_125_VAL 0x0003 +#define BMC150_BMI323_ACCEL_ODR_6_25_VAL 0x0004 +#define BMC150_BMI323_ACCEL_ODR_12_5_VAL 0x0005 +#define BMC150_BMI323_ACCEL_ODR_25_VAL 0x0006 +#define BMC150_BMI323_ACCEL_ODR_50_VAL 0x0007 +#define BMC150_BMI323_ACCEL_ODR_100_VAL 0x0008 +#define BMC150_BMI323_ACCEL_ODR_200_VAL 0x0009 +#define BMC150_BMI323_ACCEL_ODR_400_VAL 0x000A +#define BMC150_BMI323_ACCEL_ODR_800_VAL 0x000B +#define BMC150_BMI323_ACCEL_ODR_1600_VAL 0x000C +#define BMC150_BMI323_ACCEL_ODR_3200_VAL 0x000D +#define BMC150_BMI323_ACCEL_ODR_6400_VAL 0x000E + +#define BMC150_BMI323_ACCEL_BW_ODR_2_VAL 0x0000 +#define BMC150_BMI323_ACCEL_BW_ODR_4_VAL 0x0001 + +// these are bits [4:6] of ACC_CONF.acc_range, full scale resolution +#define BMC150_BMI323_ACCEL_RANGE_2_VAL 0x0000 // +/-2g, 16.38 LSB/mg +#define BMC150_BMI323_ACCEL_RANGE_4_VAL 0x0001 // +/-4g, 8.19 LSB/mg +#define BMC150_BMI323_ACCEL_RANGE_8_VAL 0x0002 // +/-8g, 4.10 LSB/mg +#define BMC150_BMI323_ACCEL_RANGE_16_VAL 0x0003 // +/-4g, 2.05 LSB/mg + +// these are bits [0:3] of GYR_CONF.gyr_odr, sample rate in Hz for the gyro part of the chip +#define BMC150_BMI323_GYRO_ODR_0_78123_VAL 0x0001 +#define BMC150_BMI323_GYRO_ODR_1_5625_VAL 0x0002 +#define BMC150_BMI323_GYRO_ODR_3_125_VAL 0x0003 +#define BMC150_BMI323_GYRO_ODR_6_25_VAL 0x0004 +#define BMC150_BMI323_GYRO_ODR_12_5_VAL 0x0005 +#define BMC150_BMI323_GYRO_ODR_25_VAL 0x0006 +#define BMC150_BMI323_GYRO_ODR_50_VAL 0x0007 +#define BMC150_BMI323_GYRO_ODR_100_VAL 0x0008 +#define BMC150_BMI323_GYRO_ODR_200_VAL 0x0009 +#define BMC150_BMI323_GYRO_ODR_400_VAL 0x000A +#define BMC150_BMI323_GYRO_ODR_800_VAL 0x000B +#define BMC150_BMI323_GYRO_ODR_1600_VAL 0x000C +#define BMC150_BMI323_GYRO_ODR_3200_VAL 0x000D +#define BMC150_BMI323_GYRO_ODR_6400_VAL 0x000E + +#define BMC150_BMI323_GYRO_BW_ODR_2_VAL 0x0000 +#define BMC150_BMI323_GYRO_BW_ODR_4_VAL 0x0001 + +// these are bits [4:6] of GYR_CONF.gyr_range, full scale resolution +#define BMC150_BMI323_GYRO_RANGE_125_VAL 0x0000 // +/-125°/s, 262.144 LSB/°/s +#define BMC150_BMI323_GYRO_RANGE_250_VAL 0x0001 // +/-250°/s, 131.2 LSB/°/s +#define BMC150_BMI323_GYRO_RANGE_500_VAL 0x0002 // +/-500°/s, 65.6 LSB/°/s +#define BMC150_BMI323_GYRO_RANGE_1000_VAL 0x0003 // +/-1000°/s, 32.8 LSB/°/s +#define BMC150_BMI323_GYRO_RANGE_2000_VAL 0x0004 // +/-2000°/s, 16.4 LSB/°/s + enum bmc150_accel_axis { AXIS_X, AXIS_Y, @@ -149,6 +216,654 @@ struct bmc150_scale_info { u8 reg_range; }; +/* + * This enum MUST not be altered as there are parts in the code that + * uses an int conversion to get the correct device register to read. + */ +enum bmi323_axis { + BMI323_ACCEL_AXIS_X = 0, + BMI323_ACCEL_AXIS_Y, + BMI323_ACCEL_AXIS_Z, + BMI323_GYRO_AXIS_X, + BMI323_GYRO_AXIS_Y, + BMI323_GYRO_AXIS_Z, + BMI323_TEMP, + BMI323_AXIS_MAX, +}; + +static const struct bmi323_scale_accel_info { + u8 hw_val; + int val; + int val2; + int ret_type; +} bmi323_accel_scale_map[] = { + { + .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_2_VAL << (u16)4, + .val = 0, + .val2 = 598, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_4_VAL << (u16)4, + .val = 0, + .val2 = 1196, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_8_VAL << (u16)4, + .val = 0, + .val2 = 2392, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .hw_val = (u16)BMC150_BMI323_ACCEL_RANGE_16_VAL << (u16)4, + .val = 0, + .val2 = 4785, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, +}; + +static const struct bmi323_scale_gyro_info { + u8 hw_val; + int val; + int val2; + int ret_type; +} bmi323_gyro_scale_map[] = { + { + .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_125_VAL << (u16)4, + .val = 0, + .val2 = 66545, + .ret_type = IIO_VAL_INT_PLUS_NANO, + }, + { + .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_125_VAL << (u16)4, + .val = 0, + .val2 = 66, + .ret_type = IIO_VAL_INT_PLUS_NANO, + }, + { + .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_250_VAL << (u16)4, + .val = 0, + .val2 = 133090, + .ret_type = IIO_VAL_INT_PLUS_NANO, + }, + { + .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_250_VAL << (u16)4, + .val = 0, + .val2 = 133, + .ret_type = IIO_VAL_INT_PLUS_NANO, + }, + { + .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_500_VAL << (u16)4, + .val = 0, + .val2 = 266181, + .ret_type = IIO_VAL_INT_PLUS_NANO, + }, + { + .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_500_VAL << (u16)4, + .val = 0, + .val2 = 266, + .ret_type = IIO_VAL_INT_PLUS_NANO, + }, + { + .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_1000_VAL << (u16)4, + .val = 0, + .val2 = 532362, + .ret_type = IIO_VAL_INT_PLUS_NANO, + }, + { + .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_1000_VAL << (u16)4, + .val = 0, + .val2 = 532, + .ret_type = IIO_VAL_INT_PLUS_NANO, + }, + { + .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_2000_VAL << (u16)4, + .val = 0, + .val2 = 1064724, + .ret_type = IIO_VAL_INT_PLUS_NANO, + }, + { + // this shouldn't be necessary, but iio seems to have a wrong rounding of this value... + .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_2000_VAL << (u16)4, + .val = 0, + .val2 = 1064, + .ret_type = IIO_VAL_INT_PLUS_NANO, + }, + { + .hw_val = (u16)BMC150_BMI323_GYRO_RANGE_2000_VAL << (u16)4, + .val = 0, + .val2 = 1065, + .ret_type = IIO_VAL_INT_PLUS_NANO, + }, +}; + +/* + * this reflects the frequency map that is following. + * For each index i of that map index i*2 and i*2+1 of of this + * holds ODR/2 and ODR/4 + */ +static const struct bmi323_3db_freq_cutoff_accel_info { + int val; + int val2; + int ret_type; +} bmi323_accel_3db_freq_cutoff[] = { + { + .val = 0, + .val2 = 390615, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 0, + .val2 = 195308, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 0, + .val2 = 781300, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 0, + .val2 = 390650, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 1, + .val2 = 562500, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 0, + .val2 = 78125, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 3, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 1, + .val2 = 500000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 6, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 3, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 12, + .val2 = 500000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 6, + .val2 = 250000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 25, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 12, + .val2 = 500000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 50, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 25, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 100, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 50, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 200, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 100, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 400, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 200, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 800, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 400, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 1600, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 800, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 1600, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 800, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 3200, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 1600, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, +}; + +static const struct bmi323_freq_accel_info { + u8 hw_val; + int val; + int val2; + s64 time_ns; +} bmi323_accel_odr_map[] = { + { + .hw_val = BMC150_BMI323_ACCEL_ODR_0_78123_VAL, + .val = 0, + .val2 = 781230, + .time_ns = 1280032769, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_1_5625_VAL, + .val = 1, + .val2 = 562600, + .time_ns = 886522247, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_3_125_VAL, + .val = 3, + .val2 = 125000, + .time_ns = 320000000, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_6_25_VAL, + .val = 6, + .val2 = 250000, + .time_ns = 160000000, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_12_5_VAL, + .val = 12, + .val2 = 500000, + .time_ns = 80000000, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_25_VAL, + .val = 25, + .val2 = 0, + .time_ns = 40000000, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_50_VAL, + .val = 50, + .val2 = 0, + .time_ns = 20000000, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_100_VAL, + .val = 100, + .val2 = 0, + .time_ns = 10000000, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_200_VAL, + .val = 200, + .val2 = 0, + .time_ns = 5000000, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_400_VAL, + .val = 400, + .val2 = 0, + .time_ns = 2500000, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_800_VAL, + .val = 800, + .val2 = 0, + .time_ns = 1250000, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_1600_VAL, + .val = 1600, + .val2 = 0, + .time_ns = 625000, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_3200_VAL, + .val = 3200, + .val2 = 0, + .time_ns = 312500, + }, + { + .hw_val = BMC150_BMI323_ACCEL_ODR_6400_VAL, + .val = 6400, + .val2 = 0, + .time_ns = 156250, + }, +}; + +static const struct bmi323_freq_gyro_info { + u8 hw_val; + int val; + int val2; + s64 time_ns; +} bmi323_gyro_odr_map[] = { + { + .hw_val = BMC150_BMI323_GYRO_ODR_0_78123_VAL, + .val = 0, + .val2 = 781230, + .time_ns = 1280032769, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_1_5625_VAL, + .val = 1, + .val2 = 562600, + .time_ns = 886522247, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_3_125_VAL, + .val = 3, + .val2 = 125000, + .time_ns = 320000000, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_6_25_VAL, + .val = 6, + .val2 = 250000, + .time_ns = 160000000, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_12_5_VAL, + .val = 12, + .val2 = 500000, + .time_ns = 80000000, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_25_VAL, + .val = 25, + .val2 = 0, + .time_ns = 40000000, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_50_VAL, + .val = 50, + .val2 = 0, + .time_ns = 20000000, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_100_VAL, + .val = 100, + .val2 = 0, + .time_ns = 10000000, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_200_VAL, + .val = 200, + .val2 = 0, + .time_ns = 5000000, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_400_VAL, + .val = 400, + .val2 = 0, + .time_ns = 2500000, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_800_VAL, + .val = 800, + .val2 = 0, + .time_ns = 1250000, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_1600_VAL, + .val = 1600, + .val2 = 0, + .time_ns = 625000, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_3200_VAL, + .val = 3200, + .val2 = 0, + .time_ns = 312500, + }, + { + .hw_val = BMC150_BMI323_GYRO_ODR_6400_VAL, + .val = 6400, + .val2 = 0, + .time_ns = 156250, + }, +}; + +static const struct bmi323_3db_freq_cutoff_gyro_info { + int val; + int val2; + int ret_type; +} bmi323_gyro_3db_freq_cutoff[] = { + { + .val = 0, + .val2 = 390615, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 0, + .val2 = 1953075, // TODO: check if this gets reported correctly... + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 0, + .val2 = 781300, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 0, + .val2 = 390650, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 1, + .val2 = 562500, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 0, + .val2 = 78125, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 3, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 1, + .val2 = 500000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 6, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 3, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 12, + .val2 = 500000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 6, + .val2 = 250000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 25, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 12, + .val2 = 500000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 50, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 25, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 100, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 50, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 200, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 100, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 400, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 200, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 800, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 400, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 1600, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 800, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 1600, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 800, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 3200, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, + { + .val = 1600, + .val2 = 000000, + .ret_type = IIO_VAL_INT_PLUS_MICRO, + }, +}; + +static const int bmi323_accel_scales[] = { + 0, 598, 0, 1196, 0, 2392, 0, 4785, +}; + +static const int bmi323_gyro_scales[] = { + 0, 66545, 0, 133090, 0, 266181, 0, 532362, 0, 1064724, +}; + +static const int bmi323_sample_freqs[] = { + 0, 781230, 1, 562600, 3, 125000, 6, 250000, 12, 500000, + 25, 0, 50, 0, 100, 0, 200, 0, 400, 0, + 800, 0, 1600, 0, 3200, 0, 6400, 0, +}; + +static const struct { + int val; + int val2; // IIO_VAL_INT_PLUS_MICRO + u8 bw_bits; +} bmi323_samp_freq_table[] = { { 15, 620000, 0x08 }, { 31, 260000, 0x09 }, + { 62, 500000, 0x0A }, { 125, 0, 0x0B }, + { 250, 0, 0x0C }, { 500, 0, 0x0D }, + { 1000, 0, 0x0E }, { 2000, 0, 0x0F } }; + struct bmc150_accel_chip_info { const char *name; u8 chip_id; @@ -1113,6 +1828,52 @@ static const struct iio_event_spec bmc150_accel_event = { .num_event_specs = 1 \ } +#define BMI323_ACCEL_CHANNEL(_axis, bits) \ + { \ + .type = IIO_ACCEL, .modified = 1, .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = BMI323_ACCEL_AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 16 - (bits), \ + .endianness = IIO_LE, \ + }, \ + } + +#define BMI323_GYRO_CHANNEL(_axis, bits) \ + { \ + .type = IIO_ANGL_VEL, .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = BMI323_GYRO_AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 16 - (bits), \ + .endianness = IIO_LE, \ + }, \ + /*.ext_info = bmi323_accel_ext_info,*/ \ + /*.event_spec = &bmi323_accel_event,*/ \ + /*.num_event_specs = 1*/ \ + } + #define BMC150_ACCEL_CHANNELS(bits) { \ { \ .type = IIO_TEMP, \ @@ -1595,7 +2356,7 @@ static int bmc150_accel_chip_init(struct bmc150_accel_data *data) struct device *dev = regmap_get_device(data->regmap); int ret, i; unsigned int val; - + /* * Reset chip to get it in a known good state. A delay of 1.8ms after * reset is required according to the data sheets of supported chips. @@ -1677,6 +2438,11 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, data = iio_priv(indio_dev); dev_set_drvdata(dev, indio_dev); + /* + * Setting the dev_type here is necessary to avoid having it left uninitialized + * and therefore potentially executing bmi323 functions for the original bmc150 model. + */ + data->dev_type = BMC150; data->regmap = regmap; data->type = type; @@ -1826,12 +2592,1407 @@ void bmc150_accel_core_remove(struct device *dev) } EXPORT_SYMBOL_NS_GPL(bmc150_accel_core_remove, IIO_BMC150); -#ifdef CONFIG_PM_SLEEP -static int bmc150_accel_suspend(struct device *dev) +struct device *bmi323_get_managed_device(struct bmi323_private_data *bmi323) +{ + if (bmi323->i2c_client != NULL) + return &bmi323->i2c_client->dev; + + return &bmi323->spi_client->dev; +} + +static int bmi323_set_power_state(struct bmi323_private_data *bmi323, bool on) +{ +#ifdef CONFIG_PM + struct device *dev = bmi323_get_managed_device(bmi323); + int ret; + + if (on) + ret = pm_runtime_get_sync(dev); + else { + pm_runtime_mark_last_busy(dev); + ret = pm_runtime_put_autosuspend(dev); + } + + if (ret < 0) { + dev_err(dev, "bmi323_set_power_state failed with %d\n", on); + + if (on) + pm_runtime_put_noidle(dev); + + return ret; + } +#endif + + return 0; +} + +int bmi323_write_u16(struct bmi323_private_data *bmi323, u8 in_reg, + u16 in_value) +{ + s32 ret; + + if (bmi323->i2c_client != NULL) { + ret = i2c_smbus_write_i2c_block_data(bmi323->i2c_client, in_reg, + sizeof(in_value), + (u8 *)(&in_value)); + if (ret != 0) { + return -2; + } + + return 0; + } else if (bmi323->spi_client != NULL) { + /* + * To whoever may need this: implementing this should be straightforward: + * it's specular to the i2c part. + */ + + return -EINVAL; // TODO: change with 0 once implemented + } + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(bmi323_write_u16, IIO_BMC150); + +int bmi323_read_u16(struct bmi323_private_data *bmi323, u8 in_reg, + u16 *out_value) +{ + s32 ret; + u8 read_bytes[4]; + + if (bmi323->i2c_client != NULL) { + ret = i2c_smbus_read_i2c_block_data(bmi323->i2c_client, in_reg, + sizeof(read_bytes), + &read_bytes[0]); + if (ret != 4) { + return ret; + } + + // DUMMY = read_bytes[0] + // DUMMY = read_bytes[1] + // LSB = read_bytes[2] + // MSB = read_bytes[3] + u8 *o = (u8 *)out_value; + o[0] = read_bytes[2]; + o[1] = read_bytes[3]; + + return 0; + } else if (bmi323->spi_client != NULL) { + printk(KERN_CRIT + "bmi323: SPI interface is not yet implemented.\n"); + + /* + * To whoever may need this: implementing this should be straightforward: + * it's specular to the i2c part except that the dummy data is just 1 byte. + */ + + return -EINVAL; // TODO: change with 0 once implemented + } + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(bmi323_read_u16, IIO_BMC150); + +int bmi323_chip_check(struct bmi323_private_data *bmi323) +{ + u16 chip_id; + int ret; + + ret = bmi323_read_u16(bmi323, BMC150_BMI323_CHIP_ID_REG, &chip_id); + if (ret != 0) { + return ret; + } + + if (((chip_id)&0x00FF) != cpu_to_le16((u16)0x0043U)) { + dev_err(bmi323->dev, + "bmi323_chip_check failed with: %d; chip_id = 0x%04x", + ret, chip_id); + + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(bmi323_chip_check, IIO_BMC150); + +static int bmi323_buffer_preenable(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + const int ret = bmi323_set_power_state(&data->bmi323, true); + + if (ret == 0) { + mutex_lock(&data->bmi323.mutex); + data->bmi323.fifo_frame_time_diff_ns = + (data->bmi323.acc_odr_time_ns >= + data->bmi323.gyr_odr_time_ns) ? + data->bmi323.acc_odr_time_ns : + data->bmi323.gyr_odr_time_ns; + mutex_unlock(&data->bmi323.mutex); + } + + return ret; +} + +static int bmi323_buffer_postenable(struct iio_dev *indio_dev) +{ + //struct bmc150_accel_data *data = iio_priv(indio_dev); + + /* + * This code is a placeholder until I can get a way to test it + */ + + return 0; +} + +static int bmi323_buffer_predisable(struct iio_dev *indio_dev) +{ + //struct bmc150_accel_data *data = iio_priv(indio_dev); + + /* + * This code is a placeholder until I can get a way to test it + */ + + return 0; +} + +static int bmi323_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + return bmi323_set_power_state(&data->bmi323, true); +} + +static const struct iio_buffer_setup_ops bmi323_buffer_ops = { + .preenable = bmi323_buffer_preenable, + .postenable = bmi323_buffer_postenable, + .predisable = bmi323_buffer_predisable, + .postdisable = bmi323_buffer_postdisable, +}; + +int bmi323_chip_rst(struct bmi323_private_data *bmi323) +{ + u16 sensor_status = 0x0000, device_status = 0x0000; + int ret; + + ret = bmi323_write_u16(bmi323, BMC150_BMI323_SOFT_RESET_REG, + cpu_to_le16((u16)BMC150_BMI323_SOFT_RESET_VAL)); + if (ret != 0) { + dev_err(bmi323->dev, + "bmi323: error while issuing the soft-reset command: %d", + ret); + return ret; + } + + /* wait the specified amount of time... I agree with the bmc150 module: better safe than sorry. */ + msleep(5); + + // if the device is connected over SPI a dummy read is to be performed once after each reset + if (bmi323->spi_client != NULL) { + dev_info(bmi323->dev, + "issuing the dummy read to switch mode to SPI"); + + // do not even check the result of that... it's just a dummy read + bmi323_chip_check(bmi323); + } + + ret = bmi323_chip_check(bmi323); + if (ret != 0) { + return ret; + } + + /* now check the correct initialization status as per datasheet */ + ret = bmi323_read_u16(bmi323, 0x01, &device_status); + if (ret != 0) { + return -EINVAL; + } + + if ((device_status & cpu_to_le16((u16)0x00FFU)) != + cpu_to_le16((u16)0x0000U)) { + dev_err(bmi323->dev, + "bmi323: device_status incorrect: %d; device_status = 0x%04x", + ret, device_status); + + /* from the datasheet: power error */ + return -EINVAL; + } + + /* from the datasheet: power ok */ + ret = bmi323_read_u16(bmi323, 0x02, &sensor_status); + if (ret != 0) { + return -EINVAL; + } + + if ((sensor_status & cpu_to_le16((u16)0x00FFU)) != + cpu_to_le16((u16)0x0001U)) { + dev_err(bmi323->dev, + "bmi323: sensor_status incorrect: %d; sensor_status = 0x%04x", + ret, sensor_status); + + /* from the datasheet: initialization error */ + return -EINVAL; + } + + /* from the datasheet: initialization ok */ + return 0; +} +EXPORT_SYMBOL_NS_GPL(bmi323_chip_rst, IIO_BMC150); + +static const struct iio_chan_spec bmi323_channels[] = { + BMI323_ACCEL_CHANNEL(X, 16), + BMI323_ACCEL_CHANNEL(Y, 16), + BMI323_ACCEL_CHANNEL(Z, 16), + BMI323_GYRO_CHANNEL(X, 16), + BMI323_GYRO_CHANNEL(Y, 16), + BMI323_GYRO_CHANNEL(Z, 16), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = BMI323_TEMP, + }, + IIO_CHAN_SOFT_TIMESTAMP(BMI323_AXIS_MAX), +}; + +static int bmi323_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret = -EINVAL, was_sleep_modified = -1; + u16 raw_read = 0x8000; + + mutex_lock(&data->bmi323.mutex); + + if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { + dev_err(data->bmi323.dev, + "bmi323 error: device has not being woken up correctly."); + mutex_unlock(&data->bmi323.mutex); + return -EBUSY; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + switch (chan->type) { + case IIO_TEMP: + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto bmi323_read_raw_error; + } + + was_sleep_modified = + bmi323_set_power_state(&data->bmi323, true); + if (was_sleep_modified != 0) { + ret = was_sleep_modified; + goto bmi323_read_raw_error_power; + } + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret != 0) { + printk(KERN_CRIT + "bmc150 bmi323_read_raw IIO_TEMP iio_device_claim_direct_mode returned %d\n", + ret); + goto bmi323_read_raw_error; + } + + ret = bmi323_read_u16( + &data->bmi323, + BMC150_BMI323_TEMPERATURE_DATA_REG, &raw_read); + iio_device_release_direct_mode(indio_dev); + if (ret != 0) { + printk(KERN_CRIT + "bmc150 bmi323_read_raw IIO_TEMP bmi323_read_u16 returned %d\n", + ret); + goto bmi323_read_raw_error; + } + + *val = sign_extend32(le16_to_cpu(raw_read), 15); + bmi323_set_power_state(&data->bmi323, false); + mutex_unlock(&data->bmi323.mutex); + return IIO_VAL_INT; + + case IIO_ACCEL: + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto bmi323_read_raw_error; + } + + was_sleep_modified = + bmi323_set_power_state(&data->bmi323, true); + if (was_sleep_modified != 0) { + ret = was_sleep_modified; + goto bmi323_read_raw_error_power; + } + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret != 0) { + printk(KERN_CRIT + "bmc150 bmi323_read_raw IIO_ACCEL iio_device_claim_direct_mode returned %d\n", + ret); + goto bmi323_read_raw_error; + } + + ret = bmi323_read_u16(&data->bmi323, + BMC150_BMI323_DATA_BASE_REG + + (u8)(chan->scan_index), + &raw_read); + iio_device_release_direct_mode(indio_dev); + if (ret != 0) { + printk(KERN_CRIT + "bmc150 bmi323_read_raw IIO_ACCEL bmi323_read_u16 returned %d\n", + ret); + goto bmi323_read_raw_error; + } + *val = sign_extend32(le16_to_cpu(raw_read), 15); + bmi323_set_power_state(&data->bmi323, false); + mutex_unlock(&data->bmi323.mutex); + return IIO_VAL_INT; + + case IIO_ANGL_VEL: + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto bmi323_read_raw_error; + } + + was_sleep_modified = + bmi323_set_power_state(&data->bmi323, true); + if (was_sleep_modified != 0) { + ret = was_sleep_modified; + goto bmi323_read_raw_error_power; + } + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret != 0) { + printk(KERN_CRIT + "bmc150 bmi323_read_raw IIO_ANGL_VEL iio_device_claim_direct_mode returned %d\n", + ret); + goto bmi323_read_raw_error; + } + + ret = bmi323_read_u16(&data->bmi323, + BMC150_BMI323_DATA_BASE_REG + + (u8)(chan->scan_index), + &raw_read); + iio_device_release_direct_mode(indio_dev); + if (ret != 0) { + printk(KERN_CRIT + "bmc150 bmi323_read_raw IIO_ANGL_VEL bmi323_read_u16 returned %d\n", + ret); + goto bmi323_read_raw_error; + } + + *val = sign_extend32(le16_to_cpu(raw_read), 15); + bmi323_set_power_state(&data->bmi323, false); + mutex_unlock(&data->bmi323.mutex); + return IIO_VAL_INT; + + default: + goto bmi323_read_raw_error; + } + } + case IIO_CHAN_INFO_OFFSET: { + switch (chan->type) { + case IIO_TEMP: + *val = BMC150_BMI323_TEMPER_CENTER_VAL; + *val2 = 0; + mutex_unlock(&data->bmi323.mutex); + return IIO_VAL_INT; + + default: + ret = -EINVAL; + goto bmi323_read_raw_error; + } + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: { + *val = 0; + *val2 = BMC150_BMI323_TEMPER_LSB_PER_KELVIN_VAL; + mutex_unlock(&data->bmi323.mutex); + return IIO_VAL_FRACTIONAL; + } + case IIO_ACCEL: { + u8 *le_raw_read = + (u8 *)&data->bmi323.acc_conf_reg_value; + for (int s = 0; s < ARRAY_SIZE(bmi323_accel_scale_map); + ++s) { + if (((le_raw_read[0]) & ((u16)0b01110000U)) == + (bmi323_accel_scale_map[s].hw_val)) { + *val = bmi323_accel_scale_map[s].val; + *val2 = bmi323_accel_scale_map[s].val2; + + mutex_unlock(&data->bmi323.mutex); + return bmi323_accel_scale_map[s] + .ret_type; + } + } + + ret = -EINVAL; + goto bmi323_read_raw_error; + } + case IIO_ANGL_VEL: { + u8 *le_raw_read = + (u8 *)&data->bmi323.gyr_conf_reg_value; + for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_scale_map); + ++s) { + if (((le_raw_read[0]) & ((u16)0b01110000U)) == + (bmi323_gyro_scale_map[s].hw_val)) { + *val = bmi323_gyro_scale_map[s].val; + *val2 = bmi323_gyro_scale_map[s].val2; + + mutex_unlock(&data->bmi323.mutex); + return bmi323_gyro_scale_map[s].ret_type; + } + } + + ret = -EINVAL; + goto bmi323_read_raw_error; + } + default: + ret = -EINVAL; + goto bmi323_read_raw_error; + } + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + switch (chan->type) { + case IIO_ACCEL: { + u8 *le_raw_read = + (u8 *)&data->bmi323.acc_conf_reg_value; + for (int s = 0; s < ARRAY_SIZE(bmi323_accel_odr_map); + ++s) { + if (((le_raw_read[0]) & ((u16)0x0FU)) == + (bmi323_accel_odr_map[s].hw_val)) { + /* + * from tha datasheed: -3dB cut-off frequency can be configured with the bit 7 of GYR_confm, + * also called acc_bw that can either be 0 or 1, where 1 means odr/4 and 0 means odr/2 + */ + int freq_adj_idx = + (((le_raw_read[0]) & + ((u8)0x80U)) == (u8)0x00U) ? + (s * 2) + 0 : + (s * 2) + 1; + *val = bmi323_accel_3db_freq_cutoff + [freq_adj_idx] + .val; + *val2 = bmi323_accel_3db_freq_cutoff + [freq_adj_idx] + .val2; + + mutex_unlock(&data->bmi323.mutex); + return IIO_VAL_INT_PLUS_MICRO; + } + } + + ret = -EINVAL; + goto bmi323_read_raw_error; + } + case IIO_ANGL_VEL: { + u8 *le_raw_read = + (u8 *)&data->bmi323.gyr_conf_reg_value; + for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_odr_map); + ++s) { + if (((le_raw_read[0]) & ((u16)0x0FU)) == + (bmi323_gyro_odr_map[s].hw_val)) { + /* + * from tha datasheed: -3dB cut-off frequency can be configured with the bit 7 of GYR_confm, + * also called acc_bw that can either be 0 or 1, where 1 means odr/4 and 0 means odr/2 + */ + int freq_adj_idx = + (((le_raw_read[0]) & + ((u8)0x80U)) == (u8)0x0000U) ? + (s * 2) + 0 : + (s * 2) + 1; + *val = bmi323_gyro_3db_freq_cutoff + [freq_adj_idx] + .val; + *val2 = bmi323_gyro_3db_freq_cutoff + [freq_adj_idx] + .val2; + + mutex_unlock(&data->bmi323.mutex); + return bmi323_gyro_3db_freq_cutoff + [freq_adj_idx] + .ret_type; + } + } + + ret = -EINVAL; + goto bmi323_read_raw_error; + } + default: { + ret = -EINVAL; + goto bmi323_read_raw_error; + } + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_TEMP: { + + // while in normal or power mode the temperature sensur has a 50Hz sampling frequency + *val = 50; + *val2 = 0; + + mutex_unlock(&data->bmi323.mutex); + return IIO_VAL_INT_PLUS_MICRO; + } + case IIO_ACCEL: { + u8 *le_raw_read = + (u8 *)&data->bmi323.acc_conf_reg_value; + for (int s = 0; s < ARRAY_SIZE(bmi323_accel_odr_map); + ++s) { + if (((le_raw_read[0]) & ((u16)0x0FU)) == + (bmi323_accel_odr_map[s].hw_val)) { + *val = bmi323_accel_odr_map[s].val; + *val2 = bmi323_accel_odr_map[s].val2; + + mutex_unlock(&data->bmi323.mutex); + return IIO_VAL_INT_PLUS_MICRO; + } + } + + ret = -EINVAL; + goto bmi323_read_raw_error; + } + case IIO_ANGL_VEL: { + u8 *le_raw_read = + (u8 *)&data->bmi323.gyr_conf_reg_value; + for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_odr_map); + ++s) { + if (((le_raw_read[0]) & ((u16)0x0FU)) == + (bmi323_gyro_odr_map[s].hw_val)) { + *val = bmi323_gyro_odr_map[s].val; + *val2 = bmi323_gyro_odr_map[s].val2; + + mutex_unlock(&data->bmi323.mutex); + return IIO_VAL_INT_PLUS_MICRO; + } + } + + ret = -EINVAL; + goto bmi323_read_raw_error; + } + default: + ret = -EINVAL; + goto bmi323_read_raw_error; + } + default: + ret = -EINVAL; + goto bmi323_read_raw_error; + } + +bmi323_read_raw_error: + if (was_sleep_modified == 0) { + bmi323_set_power_state(&data->bmi323, false); + } + +bmi323_read_raw_error_power: + mutex_unlock(&data->bmi323.mutex); + return ret; +} + +static int bmi323_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret = -EINVAL, was_sleep_modified = -1; + + mutex_lock(&data->bmi323.mutex); + + if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { + dev_err(data->bmi323.dev, + "bmi323 error: device has not being woken up correctly."); + mutex_unlock(&data->bmi323.mutex); + return -EBUSY; + } + + switch (mask) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + switch (chan->type) { + default: { + ret = -EINVAL; + goto bmi323_write_raw_error; + } + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_ACCEL: + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto bmi323_write_raw_error; + } + + for (int s = 0; s < ARRAY_SIZE(bmi323_accel_odr_map); + ++s) { + if ((bmi323_accel_odr_map[s].val == val) && + (bmi323_accel_odr_map[s].val2 == val2)) { + const u16 conf_backup = + data->bmi323.acc_conf_reg_value; + u8 *le_raw_read = + (u8 *)&data->bmi323 + .acc_conf_reg_value; + le_raw_read[0] &= (u8)0b11110000U; + le_raw_read[0] |= + ((u8)bmi323_gyro_odr_map[s] + .hw_val); + + was_sleep_modified = + bmi323_set_power_state( + &data->bmi323, true); + if (was_sleep_modified != 0) { + ret = was_sleep_modified; + data->bmi323.acc_conf_reg_value = + conf_backup; + goto bmi323_write_raw_error_power; + } + + ret = bmi323_write_u16( + &data->bmi323, + BMC150_BMI323_ACC_CONF_REG, + data->bmi323.acc_conf_reg_value); + if (ret != 0) { + data->bmi323.acc_conf_reg_value = + conf_backup; + goto bmi323_write_raw_error; + } + + data->bmi323.acc_odr_time_ns = + bmi323_accel_odr_map[s].time_ns; + bmi323_set_power_state(&data->bmi323, + false); + mutex_unlock(&data->bmi323.mutex); + return 0; + } + } + + ret = -EINVAL; + goto bmi323_write_raw_error; + case IIO_ANGL_VEL: + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto bmi323_write_raw_error; + } + + for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_odr_map); + ++s) { + if ((bmi323_gyro_odr_map[s].val == val) && + (bmi323_gyro_odr_map[s].val2 == val2)) { + const u16 conf_backup = + data->bmi323.gyr_conf_reg_value; + u8 *le_raw_read = + (u8 *)&data->bmi323 + .gyr_conf_reg_value; + le_raw_read[0] &= (u8)0b11110000U; + le_raw_read[0] |= + ((u8)bmi323_gyro_odr_map[s] + .hw_val); + + was_sleep_modified = + bmi323_set_power_state( + &data->bmi323, true); + if (was_sleep_modified != 0) { + ret = was_sleep_modified; + data->bmi323.gyr_conf_reg_value = + conf_backup; + goto bmi323_write_raw_error_power; + } + + ret = bmi323_write_u16( + &data->bmi323, + BMC150_BMI323_GYR_CONF_REG, + data->bmi323.gyr_conf_reg_value); + if (ret != 0) { + data->bmi323.gyr_conf_reg_value = + conf_backup; + goto bmi323_write_raw_error; + } + + data->bmi323.gyr_odr_time_ns = + bmi323_gyro_odr_map[s].time_ns; + bmi323_set_power_state(&data->bmi323, + false); + mutex_unlock(&data->bmi323.mutex); + return 0; + } + } + + ret = -EINVAL; + goto bmi323_write_raw_error; + + /* Termometer also ends up here: its sampling frequency depends on the chip configuration and cannot be changed */ + default: + ret = -EINVAL; + goto bmi323_write_raw_error; + } + + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto bmi323_write_raw_error; + } + + for (int s = 0; s < ARRAY_SIZE(bmi323_accel_scale_map); + ++s) { + if ((bmi323_accel_scale_map[s].val == val) && + (bmi323_accel_scale_map[s].val2 == val2)) { + u8 *le_raw_read = + (u8 *)&data->bmi323 + .acc_conf_reg_value; + le_raw_read[0] &= (u8)0b10001111U; + le_raw_read[0] |= + ((u8)bmi323_accel_scale_map[s] + .hw_val); + + was_sleep_modified = + bmi323_set_power_state( + &data->bmi323, true); + if (was_sleep_modified != 0) { + ret = was_sleep_modified; + goto bmi323_write_raw_error_power; + } + + ret = bmi323_write_u16( + &data->bmi323, + BMC150_BMI323_ACC_CONF_REG, + data->bmi323.acc_conf_reg_value); + if (ret != 0) { + goto bmi323_write_raw_error; + } + + bmi323_set_power_state(&data->bmi323, + false); + mutex_unlock(&data->bmi323.mutex); + return 0; + } + } + + dev_warn( + data->bmi323.dev, + "bmi323 error: accel scale val=%d,val2=%d unavailable: ignoring.", + val, val2); + + ret = -EINVAL; + goto bmi323_write_raw_error; + case IIO_ANGL_VEL: + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto bmi323_write_raw_error; + } + + for (int s = 0; s < ARRAY_SIZE(bmi323_gyro_scale_map); + ++s) { + if ((bmi323_gyro_scale_map[s].val == val) && + (bmi323_gyro_scale_map[s].val2 == val2)) { + u8 *le_raw_read = + (u8 *)&data->bmi323 + .gyr_conf_reg_value; + le_raw_read[0] &= (u8)0b10001111U; + le_raw_read[0] |= + ((u8)bmi323_gyro_scale_map[s] + .hw_val); + + was_sleep_modified = + bmi323_set_power_state( + &data->bmi323, true); + if (was_sleep_modified != 0) { + ret = was_sleep_modified; + goto bmi323_write_raw_error_power; + } + + ret = bmi323_write_u16( + &data->bmi323, + BMC150_BMI323_GYR_CONF_REG, + data->bmi323.acc_conf_reg_value); + if (ret != 0) { + goto bmi323_write_raw_error; + } + + bmi323_set_power_state(&data->bmi323, + false); + mutex_unlock(&data->bmi323.mutex); + return 0; + } + } + + dev_warn( + data->bmi323.dev, + "bmi323 error: gyro scale val=%d,val2=%d unavailable: ignoring.", + val, val2); + + ret = -EINVAL; + goto bmi323_write_raw_error; + + default: + ret = -EINVAL; + goto bmi323_write_raw_error; + } + + default: + ret = -EINVAL; + goto bmi323_write_raw_error; + } + +bmi323_write_raw_error: + if (was_sleep_modified == 0) { + bmi323_set_power_state(&data->bmi323, false); + } + +bmi323_write_raw_error_power: + mutex_unlock(&data->bmi323.mutex); + return ret; +} + +static int bmi323_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = bmi323_accel_scales; + *length = ARRAY_SIZE(bmi323_accel_scales); + return IIO_AVAIL_LIST; + case IIO_ANGL_VEL: + *type = IIO_VAL_INT_PLUS_NANO; + *vals = bmi323_gyro_scales; + *length = ARRAY_SIZE(bmi323_gyro_scales); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = bmi323_sample_freqs; + *length = ARRAY_SIZE(bmi323_sample_freqs); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info bmi323_accel_info = { + .read_raw = bmi323_read_raw, + .write_raw = bmi323_write_raw, + .read_avail = bmi323_read_avail, + //.hwfifo_flush_to_buffer = bmi323_fifo_flush, +}; + +static int bmi323_fifo_flush(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + ret = bmi323_write_u16(&data->bmi323, 0x37, cpu_to_le16(0x01)); + + return ret; +} + +static const u16 stub_value = 0x8000; + +#define ADVANCE_AT_REQ_OR_AVAIL(req, avail, dst, dst_offset, src, src_offset) \ + if (req) { \ + if (gyr_avail) { \ + memcpy((void *)(dst + dst_offset), \ + (const void *)(src + src_offset), 2); \ + src_offset += 2; \ + } else { \ + memcpy((void *)(dst + dst_offset), \ + (const void *)((const u8 *)(&stub_value)), 2); \ + } \ + dst_offset += 2; \ + } else { \ + if (avail) { \ + src_offset += 2; \ + } \ + } + +static irqreturn_t iio_bmi323_trigger_h(int irq, void *p) +{ + printk(KERN_WARNING "bmi323 executed iio_bmi323_trigger_h"); + + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmc150_accel_data *indio_data = iio_priv(indio_dev); + + mutex_lock(&indio_data->bmi323.mutex); + + const bool temp_avail = ((indio_data->bmi323.fifo_conf_reg_value & + (cpu_to_le16(0b0000100000000000))) != 0); + const bool gyr_avail = ((indio_data->bmi323.fifo_conf_reg_value & + (cpu_to_le16(0b0000010000000000))) != 0); + const bool acc_avail = ((indio_data->bmi323.fifo_conf_reg_value & + (cpu_to_le16(0b0000001000000000))) != 0); + const bool time_avail = ((indio_data->bmi323.fifo_conf_reg_value & + (cpu_to_le16(0b0000000100000000))) != 0); + + /* Calculate the number of bytes for a frame */ + const u16 frames_aggregate_size_in_words = + /* 2 * */ ((temp_avail ? 1 : 0) + (gyr_avail ? 3 : 0) + + (acc_avail ? 3 : 0) + (time_avail ? 1 : 0)); + + u16 available_words = 0; + const int available_words_read_res = bmi323_read_u16( + &indio_data->bmi323, BMC150_BMI323_FIFO_FILL_LEVEL_REG, + &available_words); + if (available_words_read_res != 0) { + goto bmi323_irq_done; + } + + const u16 available_frame_aggregates = (le16_to_cpu(available_words)) / + (frames_aggregate_size_in_words); + + const s64 current_timestamp_ns = iio_get_time_ns(indio_dev); + const s64 fifo_frame_time_ns = + indio_data->bmi323.fifo_frame_time_diff_ns; + const s64 first_sample_timestamp_ns = + current_timestamp_ns - + (fifo_frame_time_ns * (s64)(available_frame_aggregates)); + + /* This can hold one full block */ + u8 temp_data[16]; + + /* This is fifo data as read from the sensor */ + u8 fifo_data[32]; + + /* + | CHANNEL | scan_index + |============================ + | | | + | ACCEL_X | 0 | + | ACCEL_Y | 1 | + | ACCEL_Y | 2 | + | GYRO_X | 3 | + | GYRO_Y | 4 | + | GYRO_Z | 5 | + | TEMP | 6 | + | TIMESTAMP | ? | + */ + bool accel_x_requested = false; + bool accel_y_requested = false; + bool accel_z_requested = false; + bool gyro_x_requested = false; + bool gyro_y_requested = false; + bool gyro_z_requested = false; + bool temp_requested = false; + + int j = 0; + for_each_set_bit(j, indio_dev->active_scan_mask, + indio_dev->masklength) { + switch (j) { + case 0: + accel_x_requested = true; + break; + case 1: + accel_y_requested = true; + break; + case 2: + accel_z_requested = true; + break; + case 3: + gyro_x_requested = true; + break; + case 4: + gyro_y_requested = true; + break; + case 5: + gyro_z_requested = true; + break; + case 6: + temp_requested = true; + break; + default: + break; + } + } + + u16 current_fifo_buffer_offset_bytes = 0; + for (u16 f = 0; f < available_frame_aggregates; ++f) { + u16 current_sample_buffer_offset = 0; + + /* Read data from the raw device */ + if (indio_data->bmi323.i2c_client != NULL) { + const int bytes_to_read = + 2 + (2 * frames_aggregate_size_in_words); + int read_block_ret = i2c_smbus_read_i2c_block_data( + indio_data->bmi323.i2c_client, + BMC150_BMI323_FIFO_DATA_REG, bytes_to_read, + &fifo_data[0]); + if (read_block_ret < bytes_to_read) { + dev_warn( + &indio_data->bmi323.i2c_client->dev, + "bmi323: i2c_smbus_read_i2c_block_data wrong return: expected %d bytes, %d arrived. Doing what is possible with recovered data.\n", + bytes_to_read, read_block_ret); + + /* at this point FIFO buffer must be flushed to avoid interpreting data incorrectly the next trigger */ + const int flush_res = + bmi323_fifo_flush(indio_dev); + if (flush_res != 0) { + dev_err(&indio_data->bmi323.i2c_client + ->dev, + "bmi323: Could not flush FIFO (%d). Following buffer data might be corrupted.\n", + flush_res); + } + + goto bmi323_irq_done; + } + + /* Discard 2-bytes dummy data from I2C */ + current_fifo_buffer_offset_bytes = 2; + } else if (indio_data->bmi323.spi_client != NULL) { + printk(KERN_CRIT + "bmi323: SPI interface is not yet implemented.\n"); + + /* + * To whoever may need this: implementing this should be straightforward: + * it's specular to the i2c part. + */ + + /* Discard 1-byte dummy data from SPI */ + current_fifo_buffer_offset_bytes = 1; + + goto bmi323_irq_done; + } + + ADVANCE_AT_REQ_OR_AVAIL(accel_x_requested, acc_avail, + (u8 *)&temp_data[0], + current_sample_buffer_offset, + (u8 *)&fifo_data[0], + current_fifo_buffer_offset_bytes); + ADVANCE_AT_REQ_OR_AVAIL(accel_y_requested, acc_avail, + (u8 *)&temp_data[0], + current_sample_buffer_offset, + (u8 *)&fifo_data[0], + current_fifo_buffer_offset_bytes); + ADVANCE_AT_REQ_OR_AVAIL(accel_z_requested, acc_avail, + (u8 *)&temp_data[0], + current_sample_buffer_offset, + (u8 *)&fifo_data[0], + current_fifo_buffer_offset_bytes); + ADVANCE_AT_REQ_OR_AVAIL(gyro_x_requested, gyr_avail, + (u8 *)&temp_data[0], + current_sample_buffer_offset, + (u8 *)&fifo_data[0], + current_fifo_buffer_offset_bytes); + ADVANCE_AT_REQ_OR_AVAIL(gyro_y_requested, gyr_avail, + (u8 *)&temp_data[0], + current_sample_buffer_offset, + (u8 *)&fifo_data[0], + current_fifo_buffer_offset_bytes); + ADVANCE_AT_REQ_OR_AVAIL(gyro_z_requested, gyr_avail, + (u8 *)&temp_data[0], + current_sample_buffer_offset, + (u8 *)&fifo_data[0], + current_fifo_buffer_offset_bytes); + ADVANCE_AT_REQ_OR_AVAIL(temp_requested, temp_avail, + (u8 *)&temp_data[0], + current_sample_buffer_offset, + (u8 *)&fifo_data[0], + current_fifo_buffer_offset_bytes); + +#ifdef BMC150_BMI232_DEBUG_EN + /* The following is code only used for debugging */ + u16 timestamp = 0; + if (time_avail) { + memcpy((u8 *)×tamp, + (const u8 + *)(&fifo_data + [current_fifo_buffer_offset_bytes]), + 2); + current_fifo_buffer_offset_bytes += 2; + } + + u16 *debg = (u16 *)&temp_data[0]; + if (!time_avail) { + printk(KERN_WARNING + "bmi323 pushing to buffer %d/%d -- accel: %d %d %d gyro: %d %d %d", + (int)(f + 1), (int)available_frame_aggregates, + (int)(*((s16 *)&debg[0])), + (int)(*((s16 *)&debg[1])), + (int)(*((s16 *)&debg[2])), + (int)(*((s16 *)&debg[3])), + (int)(*((s16 *)&debg[4])), + (int)(*((s16 *)&debg[5]))); + } else { + printk(KERN_WARNING + "bmi323 pushing to buffer %d/%d -- time: %d accel: %d %d %d gyro: %d %d %d", + (int)(f + 1), (int)available_frame_aggregates, + (int)timestamp, (int)(*((s16 *)&debg[0])), + (int)(*((s16 *)&debg[1])), + (int)(*((s16 *)&debg[2])), + (int)(*((s16 *)&debg[3])), + (int)(*((s16 *)&debg[4])), + (int)(*((s16 *)&debg[5]))); + } +#endif + + iio_push_to_buffers_with_timestamp( + indio_dev, &temp_data[0], + first_sample_timestamp_ns + + (fifo_frame_time_ns * (s64)j)); + } + +bmi323_irq_done: + mutex_unlock(&indio_data->bmi323.mutex); + + /* + * Tell the core we are done with this trigger and ready for the + * next one. + */ + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +int bmi323_set_trigger_state(struct iio_trigger *trig, bool state) +{ + return 0; +} + +/* +// The following is meant to be used in a IRQ-enabled hardware +static const struct iio_trigger_ops time_trigger_ops = { + .set_trigger_state = &bmi323_set_trigger_state, + //.reenable = NULL, + .validate_device = &iio_trigger_validate_own_device, +}; +*/ + +/* + * A very basic scan mask: everything can work in conjunction with everything else so no need to worry about + * managing conbinations of mutually exclusive data sources... + */ +static const unsigned long bmi323_accel_scan_masks[] = { + BIT(BMI323_ACCEL_AXIS_X) | BIT(BMI323_ACCEL_AXIS_Y) | + BIT(BMI323_ACCEL_AXIS_Z) | BIT(BMI323_GYRO_AXIS_X) | + BIT(BMI323_GYRO_AXIS_Y) | + BIT(BMI323_GYRO_AXIS_Z) /*| BIT(BMI323_TEMP)*/, + 0 +}; + +int bmi323_iio_init(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + struct irq_data *irq_desc = NULL; + + if (data->bmi323.i2c_client != NULL) { + data->bmi323.dev = &data->bmi323.i2c_client->dev; + } else if (data->bmi323.spi_client != NULL) { + data->bmi323.dev = &data->bmi323.spi_client->dev; + } else { + return -ENODEV; + } + + int ret = 0; + + /* change to 8 for a default 200Hz sampling rate */ + const int gyr_odr_conf_idx = 7; + const int acc_odr_conf_idx = 7; + + mutex_init(&data->bmi323.mutex); + + data->bmi323.acc_odr_time_ns = + bmi323_accel_odr_map[acc_odr_conf_idx].time_ns; + data->bmi323.gyr_odr_time_ns = + bmi323_gyro_odr_map[gyr_odr_conf_idx].time_ns; + + // FIFO enabled for gyro, accel and temp. Overwrite older samples. + data->bmi323.fifo_conf_reg_value = cpu_to_le16((u16)0x0F00U); + //data->bmi323.fifo_conf_reg_value = cpu_to_le16((u16)0x0E00U); + //data->bmi323.fifo_conf_reg_value = cpu_to_le16((u16)0x0600U); // working + + // now set the (default) normal mode... + // normal mode: 0x4000 + // no averaging: 0x0000 + data->bmi323.acc_conf_reg_value = cpu_to_le16( + 0x4000 | ((u16)BMC150_BMI323_ACCEL_RANGE_2_VAL << (u16)4U) | + ((u16)bmi323_accel_odr_map[acc_odr_conf_idx].hw_val)); + + // now set the (default) normal mode... + // normal mode: 0x4000 + // no averaging: 0x0000 + // filtering to ODR/2: 0x0000 + data->bmi323.gyr_conf_reg_value = cpu_to_le16( + 0x4000 | ((u16)BMC150_BMI323_GYRO_RANGE_125_VAL << (u16)4U) | + ((u16)bmi323_gyro_odr_map[gyr_odr_conf_idx].hw_val)); + + // the datasheet states that FIFO buffer MUST be enabled before enabling any sensor + ret = bmi323_write_u16(&data->bmi323, BMC150_BMI323_FIFO_CONF_REG, + data->bmi323.fifo_conf_reg_value); + if (ret != 0) { + return -1; + } + + ret = bmi323_write_u16(&data->bmi323, BMC150_BMI323_ACC_CONF_REG, + data->bmi323.acc_conf_reg_value); + if (ret != 0) { + return -1; + } + + ret = bmi323_write_u16(&data->bmi323, BMC150_BMI323_GYR_CONF_REG, + data->bmi323.gyr_conf_reg_value); + if (ret != 0) { + return -2; + } + + indio_dev->channels = bmi323_channels; + indio_dev->num_channels = ARRAY_SIZE(bmi323_channels); + indio_dev->name = "bmi323"; + indio_dev->available_scan_masks = bmi323_accel_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmi323_accel_info; + indio_dev->label = "bmi323-accel_base"; + + if (data->bmi323.irq > 0) { + dev_info(data->bmi323.dev, "IRQ pin reported as connected: %d", + data->bmi323.irq); + + irq_desc = irq_get_irq_data(data->bmi323.irq); + if (!irq_desc) { + dev_err(data->bmi323.dev, + "Could not find IRQ %d. ignoring it.\n", + data->bmi323.irq); + goto bmi323_iio_init_missing_irq_pin; + } + + //data->bmi323.trig[0] = devm_iio_trigger_alloc(data->bmi323.dev, "trig-fifo_full-%s-%d", indio_dev->name, iio_device_id(indio_dev)); + //if (data->bmi323.trig[0] == NULL) { + // ret = -ENOMEM; + // goto bmi323_iio_init_err_trigger_unregister; + //} + // + //data->bmi323.trig[0]->ops = &time_trigger_ops; + //iio_trigger_set_drvdata(data->bmi323.trig[0], indio_dev); + //ret = devm_iio_trigger_register(data->bmi323.dev, data->bmi323.trig[0]); + //if (ret) { + // dev_err(data->bmi323.dev, "iio trigger register failed\n"); + // goto bmi323_iio_init_err_trigger_unregister; + //} + + /* + * register triggers BEFORE buffer setup so that they are cleared + * on emergence exit by bmi323_iio_init_err_trigger_unregister. + * + * This is just a placeholder until I can get my hands on a bmi323 + * device that has the IRQ pin actually connected to the CPU. + */ + + /* here resume operation with the module part common to irq and non-irq enabled code. */ + goto bmi323_iio_init_common_irq_and_not_irq; + } + +bmi323_iio_init_missing_irq_pin: + dev_info( + data->bmi323.dev, + "IRQ pin NOT connected (irq=%d). Will continue normally without triggers.", + data->bmi323.irq); + +bmi323_iio_init_common_irq_and_not_irq: + + /* Once orientation matrix is implemented switch this to iio_triggered_buffer_setup_ext. */ + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + iio_bmi323_trigger_h, + &bmi323_buffer_ops); + if (ret < 0) { + dev_err(data->bmi323.dev, + "Failed: iio triggered buffer setup: %d\n", ret); + goto bmi323_iio_init_err_trigger_unregister; + } + + ret = pm_runtime_set_active(data->bmi323.dev); + if (ret) { + dev_err(data->bmi323.dev, + "bmi323 unable to initialize runtime PD: pm_runtime_set_active returned %d\n", + ret); + goto bmi323_iio_init_err_buffer_cleanup; + } + + pm_runtime_enable(data->bmi323.dev); + pm_runtime_set_autosuspend_delay(data->bmi323.dev, + BMC150_BMI323_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(data->bmi323.dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(data->bmi323.dev, + "bmi323 unable to register iio device: %d\n", ret); + goto bmi323_iio_init_err_pm_cleanup; + } + + return 0; + +bmi323_iio_init_err_pm_cleanup: + pm_runtime_dont_use_autosuspend(data->bmi323.dev); + pm_runtime_disable(data->bmi323.dev); +bmi323_iio_init_err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +bmi323_iio_init_err_trigger_unregister: + /* + * unregister triggers if they have been setup already. + * iio_trigger_unregister shall be used in that regard. + * + * This is just a placeholder until I can get my hands on a bmi323 + * device that has the IRQ pin actually connected to the CPU. + */ + //if (data->bmi323.trig[0] != NULL) { + // iio_trigger_unregister(data->bmi323.trig[0]); + //} + + return ret; +} +EXPORT_SYMBOL_NS_GPL(bmi323_iio_init, IIO_BMC150); + +void bmi323_iio_deinit(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + struct device *dev = bmi323_get_managed_device(&data->bmi323); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + + iio_triggered_buffer_cleanup(indio_dev); + + //iio_device_free(indio_dev); // this isn't done in the bmg160 driver nor in other drivers so I guess I shouldn't do it too + + mutex_unlock(&data->bmi323.mutex); + bmi323_chip_rst(&data->bmi323); + mutex_unlock(&data->bmi323.mutex); +} +EXPORT_SYMBOL_NS_GPL(bmi323_iio_deinit, IIO_BMC150); + +#ifdef CONFIG_PM_SLEEP +static int bmc150_accel_suspend(struct device *dev) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmc150_accel_data *data = iio_priv(indio_dev); + if (data->dev_type == BMI323) { + int ret; + + //dev_warn(dev, "bmi323 suspending driver..."); + + // here push the register GYRO & ACCEL configuration and issue a reset so that chip goes to sleep mode (the default one after a reset) + mutex_unlock(&data->bmi323.mutex); + + ret = bmi323_chip_rst(&data->bmi323); + mutex_unlock(&data->bmi323.mutex); + if (ret != 0) { + dev_err(dev, + "bmi323 error in suspend on bmi323_chip_rst: %d\n", + ret); + data->bmi323.flags |= BMI323_FLAGS_RESET_FAILED; + return -EAGAIN; + } + + return 0; + } + mutex_lock(&data->mutex); bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0); mutex_unlock(&data->mutex); @@ -1844,6 +4005,63 @@ static int bmc150_accel_resume(struct device *dev) struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmc150_accel_data *data = iio_priv(indio_dev); + if (data->dev_type == BMI323) { + int ret; + + //dev_warn(dev, "bmi323 resuming driver..."); + + // here pop the register GYRO & ACCEL configuration and issue a reset so that chip goes to sleep mode (the default one after a reset) + mutex_lock(&data->bmi323.mutex); + + // this was done already in runtime_sleep function. + if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { + ret = bmi323_chip_rst(&data->bmi323); + if (ret == 0) { + data->bmi323.flags &= + ~BMI323_FLAGS_RESET_FAILED; + } else { + goto bmi323_bmc150_accel_resume_terminate; + } + } + + ret = bmi323_write_u16(&data->bmi323, + BMC150_BMI323_FIFO_CONF_REG, + data->bmi323.fifo_conf_reg_value); + if (ret != 0) { + goto bmi323_bmc150_accel_resume_terminate; + } + + ret = bmi323_write_u16(&data->bmi323, + BMC150_BMI323_GYR_CONF_REG, + data->bmi323.gyr_conf_reg_value); + if (ret != 0) { + goto bmi323_bmc150_accel_resume_terminate; + } + + ret = bmi323_write_u16(&data->bmi323, + BMC150_BMI323_ACC_CONF_REG, + data->bmi323.acc_conf_reg_value); + if (ret != 0) { + goto bmi323_bmc150_accel_resume_terminate; + } + +bmi323_bmc150_accel_resume_terminate: + mutex_unlock(&data->bmi323.mutex); + if (ret != 0) { + return -EAGAIN; + } + + /* + * datasheet says "Start-up time": suspend to high performance mode is tipically 30ms, + * however when setting this to 32 or even higher the first reading from the gyro (unlike accel part) + * is actually the (wrong) default value 0x8000 so it is better to sleep a bit longer + * to prevent issues and give time to the sensor to pick up first readings... + */ + msleep_interruptible(64); + + return 0; + } + mutex_lock(&data->mutex); bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); bmc150_accel_fifo_set_mode(data); @@ -1863,6 +4081,25 @@ static int bmc150_accel_runtime_suspend(struct device *dev) struct bmc150_accel_data *data = iio_priv(indio_dev); int ret; + if (data->dev_type == BMI323) { + //dev_warn(dev, "bmi323 suspending runtime..."); + + /* + * Every operation requiring this function have the mutex locked already: + * with mutex_lock(&data->bmi323.mutex); + */ + ret = bmi323_chip_rst(&data->bmi323); + if (ret != 0) { + dev_err(dev, + "bmi323 error in runtime_suspend on bmi323_chip_rst: %d\n", + ret); + data->bmi323.flags |= BMI323_FLAGS_RESET_FAILED; + return -EAGAIN; + } + + return 0; + } + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0); if (ret < 0) return -EAGAIN; @@ -1877,6 +4114,70 @@ static int bmc150_accel_runtime_resume(struct device *dev) int ret; int sleep_val; + if (data->dev_type == BMI323) { + //dev_warn(dev, "bmi323 resuming runtime..."); + + /* + * Every operation requiring this function have the mutex locked already: + * with mutex_lock(&data->bmi323.mutex); + */ + + // recover from a bad state if it was left that way on reuntime_suspend + if ((data->bmi323.flags & BMI323_FLAGS_RESET_FAILED) != 0x00U) { + ret = bmi323_chip_rst(&data->bmi323); + if (ret == 0) { + data->bmi323.flags &= + ~BMI323_FLAGS_RESET_FAILED; + } else { + goto bmi323_bmc150_accel_runtime_resume_terminate; + } + } + + ret = bmi323_write_u16(&data->bmi323, + BMC150_BMI323_FIFO_CONF_REG, + data->bmi323.fifo_conf_reg_value); + if (ret != 0) { + dev_err(dev, + "bmi323 writing to GYR_CONF register failed"); + goto bmi323_bmc150_accel_runtime_resume_terminate; + } + + ret = bmi323_write_u16(&data->bmi323, + BMC150_BMI323_GYR_CONF_REG, + data->bmi323.gyr_conf_reg_value); + if (ret != 0) { + dev_err(dev, + "bmi323 writing to GYR_CONF register failed"); + goto bmi323_bmc150_accel_runtime_resume_terminate; + } + + ret = bmi323_write_u16(&data->bmi323, + BMC150_BMI323_ACC_CONF_REG, + data->bmi323.acc_conf_reg_value); + if (ret != 0) { + dev_err(dev, + "bmi323 writing to ACC_CONF register failed"); + goto bmi323_bmc150_accel_runtime_resume_terminate; + } + +bmi323_bmc150_accel_runtime_resume_terminate: + if (ret != 0) { + dev_err(dev, + "bmi323 bmc150_accel_runtime_resume -EAGAIN"); + return -EAGAIN; + } + + /* + * datasheet says "Start-up time": suspend to high performance mode is tipically 30ms, + * however when setting this to 32 or even higher the first reading from the gyro (unlike accel part) + * is actually the (wrong) default value 0x8000 so it is better to sleep a bit longer + * to prevent issues and give time to the sensor to pick up first readings... + */ + msleep_interruptible(64); + + return 0; + } + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); if (ret < 0) return ret; diff --git a/drivers/iio/accel/bmc150-accel-i2c.c b/drivers/iio/accel/bmc150-accel-i2c.c index ee1ba134ad42..0d6ee304b3e7 100644 --- a/drivers/iio/accel/bmc150-accel-i2c.c +++ b/drivers/iio/accel/bmc150-accel-i2c.c @@ -173,15 +173,102 @@ static void bmc150_acpi_dual_accel_remove(struct i2c_client *client) {} static int bmc150_accel_probe(struct i2c_client *client) { + int ret; + u8 chip_id_first[4] = { 0x00, 0x00, 0x00, 0x00 }; + enum bmc150_device_type dev_type = BMC150; const struct i2c_device_id *id = i2c_client_get_device_id(client); struct regmap *regmap; const char *name = NULL; enum bmc150_type type = BOSCH_UNKNOWN; + + /* reads 4 bytes (2 dummy + 2 good) from the i2c CHIP_ID device register */ + ret = i2c_smbus_read_i2c_block_data(client, 0x00, 4, &chip_id_first[0]); + if (ret != 4) { + dev_info( + &client->dev, + "error checking if the bmc150 is in fact a bmi323: i2c_smbus_read_i2c_block_data = %d: reg = 0x%02x.\n\tIt probably is a bmc150 as correctly reported by the ACPI entry.", + (int)ret, 0x00); + goto bmi150_old_probe; + } + + // at this point we have enough data to know what chip we are handling + dev_type = (chip_id_first[2] == 0x43) ? BMI323 : dev_type; + + if (dev_type == BMI323) { + dev_warn( + &client->dev, + "bmc323: what the ACPI table reported as a bmc150 is in fact a bmc323\n"); + + struct iio_dev *indio_dev = devm_iio_device_alloc( + &client->dev, sizeof(struct bmc150_accel_data)); + if (!indio_dev) { + dev_err(&client->dev, + "bmc323 init process failed: out of memory\n"); + + return -ENOMEM; + } + + dev_set_drvdata(&client->dev, indio_dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + data->dev_type = dev_type; + + struct bmi323_private_data *bmi323_data = &data->bmi323; + bmi323_data->i2c_client = client; + bmi323_data->spi_client = NULL; + bmi323_data->irq = client->irq; + + /* + * VDD is the analog and digital domain voltage supply + * VDDIO is the digital I/O voltage supply + */ + bmi323_data->regulators[0].supply = "vdd"; + bmi323_data->regulators[1].supply = "vddio"; + ret = devm_regulator_bulk_get( + &client->dev, ARRAY_SIZE(bmi323_data->regulators), + bmi323_data->regulators); + if (ret) { + return dev_err_probe(&client->dev, ret, + "failed to get regulators\n"); + } + + ret = regulator_bulk_enable(ARRAY_SIZE(bmi323_data->regulators), + bmi323_data->regulators); + if (ret) { + iio_device_free(indio_dev); + + dev_err(&client->dev, + "failed to enable regulators: %d\n", ret); + return ret; + } + + ret = bmi323_chip_rst(bmi323_data); + if (ret != 0) { + dev_err(&client->dev, + "bmc323: error issuing the chip reset: %d\n", + ret); + return ret; + } + + dev_info( + &client->dev, + "bmc323: chip reset success: starting the iio subsystem binding\n"); + + ret = bmi323_iio_init(indio_dev); + if (ret != 0) { + return ret; + } + + return 0; + } + +bmi150_old_probe: + dev_info(&client->dev, + "executing the normal procedure for a bmc150..."); + bool block_supported = i2c_check_functionality(client->adapter, I2C_FUNC_I2C) || i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK); - int ret; regmap = devm_regmap_init_i2c(client, &bmc150_regmap_conf); if (IS_ERR(regmap)) { @@ -198,7 +285,7 @@ static int bmc150_accel_probe(struct i2c_client *client) type, name, block_supported); if (ret) return ret; - + /* * The !id check avoids recursion when probe() gets called * for the second client. @@ -211,6 +298,15 @@ static int bmc150_accel_probe(struct i2c_client *client) static void bmc150_accel_remove(struct i2c_client *client) { + struct iio_dev *indio_dev = dev_get_drvdata(&client->dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + + if (data->dev_type == BMI323) { + bmi323_iio_deinit(indio_dev); + + return; + } + bmc150_acpi_dual_accel_remove(client); bmc150_accel_core_remove(&client->dev); diff --git a/drivers/iio/accel/bmc150-accel.h b/drivers/iio/accel/bmc150-accel.h index 7775c5edaeef..65ec208960df 100644 --- a/drivers/iio/accel/bmc150-accel.h +++ b/drivers/iio/accel/bmc150-accel.h @@ -8,6 +8,14 @@ #include #include +/* + * the bmi323 needs raw access to spi and i2c: I cannot use regmap + * as this device expects i2c writes to be 2 bytes, + * spi reads to be 3 bytes and i2c reads to be 4 bytes. + */ +#include +#include + struct regmap; struct i2c_client; struct bmc150_accel_chip_info; @@ -34,6 +42,11 @@ struct bmc150_accel_interrupt { atomic_t users; }; +enum bmc150_device_type { + BMC150, + BMI323, +}; + struct bmc150_accel_trigger { struct bmc150_accel_data *data; struct iio_trigger *indio_trig; @@ -55,6 +68,25 @@ enum bmc150_accel_trigger_id { BMC150_ACCEL_TRIGGERS, }; +#define BMI323_FLAGS_RESET_FAILED 0x00000001U + +struct bmi323_private_data { + struct regulator_bulk_data regulators[2]; + struct i2c_client *i2c_client; + struct spi_device *spi_client; + struct device *dev; /* pointer at i2c_client->dev or spi_client->dev */ + struct mutex mutex; + int irq; + u32 flags; + u16 acc_conf_reg_value; + u16 gyr_conf_reg_value; + u16 fifo_conf_reg_value; + struct iio_trigger *trig[1]; + s64 fifo_frame_time_diff_ns; + s64 acc_odr_time_ns; + s64 gyr_odr_time_ns; +}; + struct bmc150_accel_data { struct regmap *regmap; struct regulator_bulk_data regulators[2]; @@ -83,7 +115,67 @@ struct bmc150_accel_data { void (*resume_callback)(struct device *dev); struct delayed_work resume_work; struct iio_mount_matrix orientation; -}; + enum bmc150_device_type dev_type; + struct bmi323_private_data bmi323; + }; + +/** + * This function performs a write of a u16 little-endian (regardless of CPU architecture) integer + * to a device register. Returns 0 on success or an error code otherwise. + * + * PRE: in_value holds the data to be sent to the sensor, in little endian format even on big endian + * architectures. + * + * NOTE: bmi323->dev can be NULL (not yet initialized) when this function is called + * therefore it is not needed and is not used inside the function + * + * WARNING: this function does not lock any mutex and synchronization MUST be performed by the caller + */ +int bmi323_write_u16(struct bmi323_private_data *bmi323, u8 in_reg, u16 in_value); + +/** + * This function performs a read of "good" values from the bmi323 discarding what + * in the datasheet is described as "dummy data": additional useles bytes. + * + * PRE: bmi323 has been partially initialized: i2c_device and spi_devices MUST be set to either + * the correct value or NULL + * + * NOTE: bmi323->dev can be NULL (not yet initialized) when this function is called + * therefore it is not needed and is not used inside the function + * + * POST: on success out_value is written with data from the sensor, as it came out, so the + * content is little-endian even on big endian architectures + * + * WARNING: this function does not lock any mutex and synchronization MUST be performed by the caller + */ +int bmi323_read_u16(struct bmi323_private_data *bmi323, u8 in_reg, u16* out_value); + +int bmi323_chip_check(struct bmi323_private_data *bmi323); + +/** + * Reset the chip in a known state that is ready to accept commands, but is not configured therefore after calling this function + * it is required to load a new configuration to start data acquisition. + * + * PRE: bmi323 has been fully identified and partially initialized + * + * NOTE: after issuing a reset the the chip will be in what it is called "suspended mode" and the feature angine is + * ready to be set. This mode has everything disabled and consumes aroud 15uA. + * + * When removing the driver or suspend has been requested it's best to reset the chip so that power consumption + * will be the lowest possible. + */ +int bmi323_chip_rst(struct bmi323_private_data *bmi323); + +/** + * This function MUST be called in probe and is responsible for registering the userspace sysfs. + * + * The indio_dev MUST have been allocated but not registered. This function will perform userspace registration. + * + * @param indio_dev the industrual io device already allocated but not yet registered + */ +int bmi323_iio_init(struct iio_dev *indio_dev); + +void bmi323_iio_deinit(struct iio_dev *indio_dev); int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, enum bmc150_type type, const char *name, -- 2.42.0