mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01: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/bits.h>
|
||||||
#include <linux/iio/iio.h>
|
#include <linux/iio/iio.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/pm.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <linux/regulator/consumer.h>
|
#include <linux/regulator/consumer.h>
|
||||||
#include <linux/time.h>
|
#include <linux/time.h>
|
||||||
|
|
@ -125,6 +126,7 @@ struct inv_icm42607_hw {
|
||||||
* @lock: lock for serializing multiple registers access.
|
* @lock: lock for serializing multiple registers access.
|
||||||
* @map: regmap pointer.
|
* @map: regmap pointer.
|
||||||
* @vddio_supply: I/O voltage regulator for the chip.
|
* @vddio_supply: I/O voltage regulator for the chip.
|
||||||
|
* @vddio_en: I/O voltage status for runtime PM.
|
||||||
* @conf: chip sensors configurations.
|
* @conf: chip sensors configurations.
|
||||||
* @orientation: sensor chip orientation relative to main hardware.
|
* @orientation: sensor chip orientation relative to main hardware.
|
||||||
*/
|
*/
|
||||||
|
|
@ -133,6 +135,7 @@ struct inv_icm42607_state {
|
||||||
struct mutex lock;
|
struct mutex lock;
|
||||||
struct regmap *map;
|
struct regmap *map;
|
||||||
struct regulator *vddio_supply;
|
struct regulator *vddio_supply;
|
||||||
|
bool vddio_en;
|
||||||
struct inv_icm42607_conf conf;
|
struct inv_icm42607_conf conf;
|
||||||
struct iio_mount_matrix orientation;
|
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_GYRO_STOP_TIME_US (45 * USEC_PER_MSEC)
|
||||||
#define INV_ICM42607_TEMP_STARTUP_TIME_US 77
|
#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 *);
|
typedef int (*inv_icm42607_bus_setup)(struct inv_icm42607_state *);
|
||||||
|
|
||||||
extern const struct regmap_config inv_icm42607_regmap_config;
|
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_icm42607_hw_data;
|
||||||
extern const struct inv_icm42607_hw inv_icm42607p_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,
|
int inv_icm42607_core_probe(struct regmap *regmap,
|
||||||
const struct inv_icm42607_hw *hw,
|
const struct inv_icm42607_hw *hw,
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,19 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/bitfield.h>
|
#include <linux/bitfield.h>
|
||||||
|
#include <linux/cleanup.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/iio/iio.h>
|
#include <linux/iio/iio.h>
|
||||||
|
#include <linux/ktime.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <linux/regulator/consumer.h>
|
#include <linux/regulator/consumer.h>
|
||||||
#include <linux/time.h>
|
#include <linux/time.h>
|
||||||
|
#include <linux/timekeeping.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
|
|
||||||
#include "inv_icm42607.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");
|
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,
|
static int inv_icm42607_set_init_conf(struct inv_icm42607_state *st,
|
||||||
const struct inv_icm42607_conf *conf)
|
const struct inv_icm42607_conf *conf)
|
||||||
{
|
{
|
||||||
|
|
@ -212,20 +302,47 @@ static int inv_icm42607_enable_vddio_reg(struct inv_icm42607_state *st)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (st->vddio_en)
|
||||||
|
return 0;
|
||||||
|
|
||||||
ret = regulator_enable(st->vddio_supply);
|
ret = regulator_enable(st->vddio_supply);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
fsleep(INV_ICM42607_POWER_UP_TIME_US);
|
fsleep(INV_ICM42607_POWER_UP_TIME_US);
|
||||||
|
|
||||||
|
st->vddio_en = true;
|
||||||
|
|
||||||
return 0;
|
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)
|
static void inv_icm42607_disable_vddio_reg(void *_data)
|
||||||
{
|
{
|
||||||
struct inv_icm42607_state *st = _data;
|
struct inv_icm42607_state *st = _data;
|
||||||
|
|
||||||
|
if (!st->vddio_en)
|
||||||
|
return;
|
||||||
|
|
||||||
regulator_disable(st->vddio_supply);
|
regulator_disable(st->vddio_supply);
|
||||||
|
|
||||||
|
st->vddio_en = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int inv_icm42607_core_probe(struct regmap *regmap,
|
int inv_icm42607_core_probe(struct regmap *regmap,
|
||||||
|
|
@ -240,6 +357,8 @@ int inv_icm42607_core_probe(struct regmap *regmap,
|
||||||
if (!st)
|
if (!st)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
dev_set_drvdata(dev, st);
|
||||||
|
|
||||||
ret = devm_mutex_init(dev, &st->lock);
|
ret = devm_mutex_init(dev, &st->lock);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -275,10 +394,78 @@ int inv_icm42607_core_probe(struct regmap *regmap,
|
||||||
if (ret)
|
if (ret)
|
||||||
return 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;
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_NS_GPL(inv_icm42607_core_probe, "IIO_ICM42607");
|
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_AUTHOR("InvenSense, Inc.");
|
||||||
MODULE_DESCRIPTION("InvenSense ICM-42607 device driver");
|
MODULE_DESCRIPTION("InvenSense ICM-42607 device driver");
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ static struct i2c_driver inv_icm42607_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "inv-icm42607-i2c",
|
.name = "inv-icm42607-i2c",
|
||||||
.of_match_table = inv_icm42607_of_matches,
|
.of_match_table = inv_icm42607_of_matches,
|
||||||
|
.pm = pm_ptr(&inv_icm42607_pm_ops),
|
||||||
},
|
},
|
||||||
.id_table = inv_icm42607_id,
|
.id_table = inv_icm42607_id,
|
||||||
.probe = inv_icm42607_probe,
|
.probe = inv_icm42607_probe,
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ static struct spi_driver inv_icm42607_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "inv-icm42607-spi",
|
.name = "inv-icm42607-spi",
|
||||||
.of_match_table = inv_icm42607_of_matches,
|
.of_match_table = inv_icm42607_of_matches,
|
||||||
|
.pm = pm_ptr(&inv_icm42607_pm_ops),
|
||||||
},
|
},
|
||||||
.id_table = inv_icm42607_spi_id_table,
|
.id_table = inv_icm42607_spi_id_table,
|
||||||
.probe = inv_icm42607_probe,
|
.probe = inv_icm42607_probe,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user