iio: imu: inv_icm42600: add support of accel low-power mode

Add ODRs accessible only in low-power mode. Switch automatically to
low-power or low-noise depending on the ODR set.

Add channel attributes "power_mode" and "power_mode_available" for
setting the power mode to use (low-noise or low-power) for ODRs
supporting both mode. Reading "power_mode" when the sensor is on
will return the actual mode and not the requested one. It will be
different when using ODRs not supported by the requested mode.

Use low-power mode by default.

Signed-off-by: Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com>
Link: https://lore.kernel.org/r/20240605195949.766677-3-inv.git-commit@tdk.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
This commit is contained in:
Jean-Baptiste Maneyrol
2024-06-05 19:59:49 +00:00
committed by Jonathan Cameron
parent 3d4d033a8d
commit 07d4d0bb4a
3 changed files with 151 additions and 4 deletions
@@ -177,11 +177,15 @@ struct inv_icm42600_state {
* struct inv_icm42600_sensor_state - sensor state variables
* @scales: table of scales.
* @scales_len: length (nb of items) of the scales table.
* @power_mode: sensor requested power mode (for common frequencies)
* @filter: sensor filter.
* @ts: timestamp module states.
*/
struct inv_icm42600_sensor_state {
const int *scales;
size_t scales_len;
enum inv_icm42600_sensor_mode power_mode;
enum inv_icm42600_filter filter;
struct inv_sensors_timestamp ts;
};
@@ -55,8 +55,108 @@ enum inv_icm42600_accel_scan {
INV_ICM42600_ACCEL_SCAN_TIMESTAMP,
};
static const char * const inv_icm42600_accel_power_mode_items[] = {
"low-noise",
"low-power",
};
static const int inv_icm42600_accel_power_mode_values[] = {
INV_ICM42600_SENSOR_MODE_LOW_NOISE,
INV_ICM42600_SENSOR_MODE_LOW_POWER,
};
static const int inv_icm42600_accel_filter_values[] = {
INV_ICM42600_FILTER_BW_ODR_DIV_2,
INV_ICM42600_FILTER_AVG_16X,
};
static int inv_icm42600_accel_power_mode_set(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
unsigned int idx)
{
struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev);
int power_mode, filter;
if (chan->type != IIO_ACCEL)
return -EINVAL;
if (idx >= ARRAY_SIZE(inv_icm42600_accel_power_mode_values))
return -EINVAL;
if (iio_buffer_enabled(indio_dev))
return -EBUSY;
power_mode = inv_icm42600_accel_power_mode_values[idx];
filter = inv_icm42600_accel_filter_values[idx];
guard(mutex)(&st->lock);
/* prevent change if power mode is not supported by the ODR */
switch (power_mode) {
case INV_ICM42600_SENSOR_MODE_LOW_NOISE:
if (st->conf.accel.odr >= INV_ICM42600_ODR_6_25HZ_LP &&
st->conf.accel.odr <= INV_ICM42600_ODR_1_5625HZ_LP)
return -EPERM;
break;
case INV_ICM42600_SENSOR_MODE_LOW_POWER:
default:
if (st->conf.accel.odr <= INV_ICM42600_ODR_1KHZ_LN)
return -EPERM;
break;
}
accel_st->power_mode = power_mode;
accel_st->filter = filter;
return 0;
}
static int inv_icm42600_accel_power_mode_get(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan)
{
struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev);
unsigned int idx;
int power_mode;
if (chan->type != IIO_ACCEL)
return -EINVAL;
guard(mutex)(&st->lock);
/* if sensor is on, returns actual power mode and not configured one */
switch (st->conf.accel.mode) {
case INV_ICM42600_SENSOR_MODE_LOW_POWER:
case INV_ICM42600_SENSOR_MODE_LOW_NOISE:
power_mode = st->conf.accel.mode;
break;
default:
power_mode = accel_st->power_mode;
break;
}
for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_accel_power_mode_values); ++idx) {
if (power_mode == inv_icm42600_accel_power_mode_values[idx])
break;
}
if (idx >= ARRAY_SIZE(inv_icm42600_accel_power_mode_values))
return -EINVAL;
return idx;
}
static const struct iio_enum inv_icm42600_accel_power_mode_enum = {
.items = inv_icm42600_accel_power_mode_items,
.num_items = ARRAY_SIZE(inv_icm42600_accel_power_mode_items),
.set = inv_icm42600_accel_power_mode_set,
.get = inv_icm42600_accel_power_mode_get,
};
static const struct iio_chan_spec_ext_info inv_icm42600_accel_ext_infos[] = {
IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm42600_get_mount_matrix),
IIO_ENUM_AVAILABLE("power_mode", IIO_SHARED_BY_TYPE,
&inv_icm42600_accel_power_mode_enum),
IIO_ENUM("power_mode", IIO_SHARED_BY_TYPE,
&inv_icm42600_accel_power_mode_enum),
{},
};
@@ -120,7 +220,8 @@ static int inv_icm42600_accel_update_scan_mode(struct iio_dev *indio_dev,
if (*scan_mask & INV_ICM42600_SCAN_MASK_ACCEL_3AXIS) {
/* enable accel sensor */
conf.mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE;
conf.mode = accel_st->power_mode;
conf.filter = accel_st->filter;
ret = inv_icm42600_set_accel_conf(st, &conf, &sleep_accel);
if (ret)
goto out_unlock;
@@ -144,10 +245,12 @@ out_unlock:
return ret;
}
static int inv_icm42600_accel_read_sensor(struct inv_icm42600_state *st,
static int inv_icm42600_accel_read_sensor(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int16_t *val)
{
struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev);
struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev);
struct device *dev = regmap_get_device(st->map);
struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT;
unsigned int reg;
@@ -175,7 +278,8 @@ static int inv_icm42600_accel_read_sensor(struct inv_icm42600_state *st,
mutex_lock(&st->lock);
/* enable accel sensor */
conf.mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE;
conf.mode = accel_st->power_mode;
conf.filter = accel_st->filter;
ret = inv_icm42600_set_accel_conf(st, &conf, NULL);
if (ret)
goto exit;
@@ -277,6 +381,12 @@ static int inv_icm42600_accel_write_scale(struct iio_dev *indio_dev,
/* IIO format int + micro */
static const int inv_icm42600_accel_odr[] = {
/* 1.5625Hz */
1, 562500,
/* 3.125Hz */
3, 125000,
/* 6.25Hz */
6, 250000,
/* 12.5Hz */
12, 500000,
/* 25Hz */
@@ -296,6 +406,9 @@ static const int inv_icm42600_accel_odr[] = {
};
static const int inv_icm42600_accel_odr_conv[] = {
INV_ICM42600_ODR_1_5625HZ_LP,
INV_ICM42600_ODR_3_125HZ_LP,
INV_ICM42600_ODR_6_25HZ_LP,
INV_ICM42600_ODR_12_5HZ,
INV_ICM42600_ODR_25HZ,
INV_ICM42600_ODR_50HZ,
@@ -581,7 +694,7 @@ static int inv_icm42600_accel_read_raw(struct iio_dev *indio_dev,
ret = iio_device_claim_direct_mode(indio_dev);
if (ret)
return ret;
ret = inv_icm42600_accel_read_sensor(st, chan, &data);
ret = inv_icm42600_accel_read_sensor(indio_dev, chan, &data);
iio_device_release_direct_mode(indio_dev);
if (ret)
return ret;
@@ -754,6 +867,9 @@ struct iio_dev *inv_icm42600_accel_init(struct inv_icm42600_state *st)
accel_st->scales_len = ARRAY_SIZE(inv_icm42600_accel_scale);
break;
}
/* low-power by default at init */
accel_st->power_mode = INV_ICM42600_SENSOR_MODE_LOW_POWER;
accel_st->filter = INV_ICM42600_FILTER_AVG_16X;
/*
* clock period is 32kHz (31250ns)
@@ -292,6 +292,23 @@ int inv_icm42600_set_accel_conf(struct inv_icm42600_state *st,
if (conf->filter < 0)
conf->filter = oldconf->filter;
/* force power mode against ODR when sensor is on */
switch (conf->mode) {
case INV_ICM42600_SENSOR_MODE_LOW_POWER:
case INV_ICM42600_SENSOR_MODE_LOW_NOISE:
if (conf->odr <= INV_ICM42600_ODR_1KHZ_LN) {
conf->mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE;
conf->filter = INV_ICM42600_FILTER_BW_ODR_DIV_2;
} else if (conf->odr >= INV_ICM42600_ODR_6_25HZ_LP &&
conf->odr <= INV_ICM42600_ODR_1_5625HZ_LP) {
conf->mode = INV_ICM42600_SENSOR_MODE_LOW_POWER;
conf->filter = INV_ICM42600_FILTER_AVG_16X;
}
break;
default:
break;
}
/* set ACCEL_CONFIG0 register (accel fullscale & odr) */
if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {
val = INV_ICM42600_ACCEL_CONFIG0_FS(conf->fs) |
@@ -485,6 +502,16 @@ static int inv_icm42600_setup(struct inv_icm42600_state *st,
if (ret)
return ret;
/*
* Use RC clock for accel low-power to fix glitches when switching
* gyro on/off while accel low-power is on.
*/
ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG1,
INV_ICM42600_INTF_CONFIG1_ACCEL_LP_CLK_RC,
INV_ICM42600_INTF_CONFIG1_ACCEL_LP_CLK_RC);
if (ret)
return ret;
return inv_icm42600_set_conf(st, hw->conf);
}