iio: imu: inv_icm42607: Add PM support for icm42607

Add power management support for the ICM42607 device driver.

Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
Signed-off-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
This commit is contained in:
Chris Morgan 2026-07-28 17:55:36 -05:00 committed by Jonathan Cameron
parent 337cb9a3fa
commit 3007c1530f
4 changed files with 203 additions and 0 deletions

View File

@ -9,6 +9,7 @@
#include <linux/bits.h>
#include <linux/iio/iio.h>
#include <linux/mutex.h>
#include <linux/pm.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/time.h>
@ -125,6 +126,7 @@ struct inv_icm42607_hw {
* @lock: lock for serializing multiple registers access.
* @map: regmap pointer.
* @vddio_supply: I/O voltage regulator for the chip.
* @vddio_en: I/O voltage status for runtime PM.
* @conf: chip sensors configurations.
* @orientation: sensor chip orientation relative to main hardware.
*/
@ -133,6 +135,7 @@ struct inv_icm42607_state {
struct mutex lock;
struct regmap *map;
struct regulator *vddio_supply;
bool vddio_en;
struct inv_icm42607_conf conf;
struct iio_mount_matrix orientation;
};
@ -352,11 +355,22 @@ struct inv_icm42607_state {
#define INV_ICM42607_GYRO_STOP_TIME_US (45 * USEC_PER_MSEC)
#define INV_ICM42607_TEMP_STARTUP_TIME_US 77
/*
* Suspend delay assumed from other icm42600 series device, not
* documented in datasheet.
*/
#define INV_ICM42607_SUSPEND_DELAY_MS (2 * MSEC_PER_SEC)
typedef int (*inv_icm42607_bus_setup)(struct inv_icm42607_state *);
extern const struct regmap_config inv_icm42607_regmap_config;
extern const struct inv_icm42607_hw inv_icm42607_hw_data;
extern const struct inv_icm42607_hw inv_icm42607p_hw_data;
extern const struct dev_pm_ops inv_icm42607_pm_ops;
int inv_icm42607_get_pwr_mgmt0(struct inv_icm42607_state *st,
enum inv_icm42607_sensor_mode *gyro,
enum inv_icm42607_sensor_mode *accel);
int inv_icm42607_core_probe(struct regmap *regmap,
const struct inv_icm42607_hw *hw,

View File

@ -4,15 +4,19 @@
*/
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/iio/iio.h>
#include <linux/ktime.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/time.h>
#include <linux/timekeeping.h>
#include <linux/types.h>
#include "inv_icm42607.h"
@ -100,6 +104,92 @@ const struct inv_icm42607_hw inv_icm42607p_hw_data = {
};
EXPORT_SYMBOL_NS_GPL(inv_icm42607p_hw_data, "IIO_ICM42607");
int inv_icm42607_get_pwr_mgmt0(struct inv_icm42607_state *st,
enum inv_icm42607_sensor_mode *gyro,
enum inv_icm42607_sensor_mode *accel)
{
unsigned int val;
int ret;
ret = regmap_read(st->map, INV_ICM42607_REG_PWR_MGMT0, &val);
if (ret)
return ret;
*gyro = FIELD_GET(INV_ICM42607_PWR_MGMT0_GYRO_MODE_MASK, val);
*accel = FIELD_GET(INV_ICM42607_PWR_MGMT0_ACCEL_MODE_MASK, val);
return 0;
}
static int inv_icm42607_set_pwr_mgmt0(struct inv_icm42607_state *st,
enum inv_icm42607_sensor_mode gyro,
enum inv_icm42607_sensor_mode accel)
{
enum inv_icm42607_sensor_mode oldaccel, oldgyro;
unsigned int sleepval_us;
unsigned int val;
s64 disable_wait;
int ret;
ret = inv_icm42607_get_pwr_mgmt0(st, &oldgyro, &oldaccel);
if (ret)
return ret;
if (gyro == oldgyro && accel == oldaccel)
return 0;
/*
* Datasheet on page 14.26 says we need to ensure the gyro sensor is on
* for a minimum of 45ms. So if we transition from an on state to an
* off state make sure at least 45ms have passed before power off and
* wait if it hasn't. In case some platforms don't respond well to a
* sleep of 0, make sure the fsleep duration is > 0.
*/
if (!gyro && oldgyro) {
disable_wait = clamp(ktime_us_delta(st->conf.gyro_stop, ktime_get()),
0, INV_ICM42607_GYRO_STOP_TIME_US);
if (disable_wait > 0)
fsleep(disable_wait);
}
val = FIELD_PREP(INV_ICM42607_PWR_MGMT0_GYRO_MODE_MASK, gyro) |
FIELD_PREP(INV_ICM42607_PWR_MGMT0_ACCEL_MODE_MASK, accel);
ret = regmap_write(st->map, INV_ICM42607_REG_PWR_MGMT0, val);
if (ret)
return ret;
/*
* If a state change occurs from off to on, sleep for the startup time
* of the sensor. Since more than one sensor can be transitioned from
* off to on, select the maximum time from each of the sensors changing
* from off to on. The startup time for the temp sensor is considerably
* smaller than the startup time for the other sensors and one or more
* are required to be on for the temp sensor to function, so any start
* delay should be enough.
*/
sleepval_us = 0;
if (accel && !oldaccel)
sleepval_us = max(sleepval_us, INV_ICM42607_ACCEL_STARTUP_TIME_US);
if (gyro && !oldgyro) {
sleepval_us = max(sleepval_us, INV_ICM42607_GYRO_STARTUP_TIME_US);
/* Track the earliest we can turn off the gyroscope. */
st->conf.gyro_stop = ktime_add_us(ktime_get(),
INV_ICM42607_GYRO_STOP_TIME_US);
}
/*
* Only sleep if sleepval_us is greater than 0 in case some platforms
* have issues with a 0 delay. The 0 delay can happen if one or both
* sensors is shut down.
*/
if (sleepval_us > 0)
fsleep(sleepval_us);
return 0;
}
static int inv_icm42607_set_init_conf(struct inv_icm42607_state *st,
const struct inv_icm42607_conf *conf)
{
@ -212,20 +302,47 @@ static int inv_icm42607_enable_vddio_reg(struct inv_icm42607_state *st)
{
int ret;
if (st->vddio_en)
return 0;
ret = regulator_enable(st->vddio_supply);
if (ret)
return ret;
fsleep(INV_ICM42607_POWER_UP_TIME_US);
st->vddio_en = true;
return 0;
}
static void inv_icm42607_sensors_off(void *_data)
{
struct inv_icm42607_state *st = _data;
const struct device *dev = regmap_get_device(st->map);
int ret;
guard(mutex)(&st->lock);
st->conf.gyro.mode = INV_ICM42607_SENSOR_MODE_OFF;
st->conf.accel.mode = INV_ICM42607_SENSOR_MODE_OFF;
ret = inv_icm42607_set_pwr_mgmt0(st, st->conf.gyro.mode,
st->conf.accel.mode);
if (ret)
dev_err(dev, "Unable to turn off sensors\n");
}
static void inv_icm42607_disable_vddio_reg(void *_data)
{
struct inv_icm42607_state *st = _data;
if (!st->vddio_en)
return;
regulator_disable(st->vddio_supply);
st->vddio_en = false;
}
int inv_icm42607_core_probe(struct regmap *regmap,
@ -240,6 +357,8 @@ int inv_icm42607_core_probe(struct regmap *regmap,
if (!st)
return -ENOMEM;
dev_set_drvdata(dev, st);
ret = devm_mutex_init(dev, &st->lock);
if (ret)
return ret;
@ -275,10 +394,78 @@ int inv_icm42607_core_probe(struct regmap *regmap,
if (ret)
return ret;
/*
* Ensure if sensors get turned on at some point, they're turned off
* as part of teardown.
*/
ret = devm_add_action_or_reset(dev, inv_icm42607_sensors_off, st);
if (ret)
return ret;
ret = devm_pm_runtime_set_active_enabled(dev);
if (ret)
return ret;
pm_runtime_set_autosuspend_delay(dev, INV_ICM42607_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(dev);
return 0;
}
EXPORT_SYMBOL_NS_GPL(inv_icm42607_core_probe, "IIO_ICM42607");
static int inv_icm42607_suspend(struct device *dev)
{
struct inv_icm42607_state *st = dev_get_drvdata(dev);
int ret;
ret = pm_runtime_force_suspend(dev);
if (ret)
return ret;
inv_icm42607_disable_vddio_reg(st);
return 0;
}
static int inv_icm42607_resume(struct device *dev)
{
struct inv_icm42607_state *st = dev_get_drvdata(dev);
int ret;
ret = inv_icm42607_enable_vddio_reg(st);
if (ret)
return ret;
/* Sync the regcache again after regulator shutdown. */
regcache_mark_dirty(st->map);
ret = regcache_sync(st->map);
if (ret)
return ret;
return pm_runtime_force_resume(dev);
}
static int inv_icm42607_runtime_suspend(struct device *dev)
{
struct inv_icm42607_state *st = dev_get_drvdata(dev);
/*
* Set sensors state to off. Since we only support one-shot
* today we can use runtime PM to turn sensors off when not
* in use, and then when needed the reads/writes will
* re-enable the sensors as needed. This reduces complexity,
* however the tradeoff is that an unused sensor won't be
* turned off until the entire chip is no longer in use.
*/
inv_icm42607_sensors_off(st);
return 0;
}
EXPORT_NS_GPL_DEV_PM_OPS(inv_icm42607_pm_ops, IIO_ICM42607) = {
SYSTEM_SLEEP_PM_OPS(inv_icm42607_suspend, inv_icm42607_resume)
RUNTIME_PM_OPS(inv_icm42607_runtime_suspend, NULL, NULL)
};
MODULE_AUTHOR("InvenSense, Inc.");
MODULE_DESCRIPTION("InvenSense ICM-42607 device driver");
MODULE_LICENSE("GPL");

View File

@ -84,6 +84,7 @@ static struct i2c_driver inv_icm42607_driver = {
.driver = {
.name = "inv-icm42607-i2c",
.of_match_table = inv_icm42607_of_matches,
.pm = pm_ptr(&inv_icm42607_pm_ops),
},
.id_table = inv_icm42607_id,
.probe = inv_icm42607_probe,

View File

@ -94,6 +94,7 @@ static struct spi_driver inv_icm42607_driver = {
.driver = {
.name = "inv-icm42607-spi",
.of_match_table = inv_icm42607_of_matches,
.pm = pm_ptr(&inv_icm42607_pm_ops),
},
.id_table = inv_icm42607_spi_id_table,
.probe = inv_icm42607_probe,