mirror of
https://github.com/torvalds/linux.git
synced 2026-09-25 09:41:03 +02:00
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:
parent
337cb9a3fa
commit
3007c1530f
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user