drivers: can: tcan4x5x: Add device PM support
Add support for device runtime power management. Suspending the device will place it into sleep mode, its lowest power state. All config will be lost when suspending the device. Do not allow suspending the device if it is started or has RX filters registered. The application has to reconfigure the CAN device to resume expected operation. Signed-off-by: Ryan Erickson <ryan.erickson@ezurio.com>
This commit is contained in:
committed by
Chris Friedt
parent
9ca3cad57d
commit
631dbd98b4
@@ -10,6 +10,7 @@
|
||||
#include <zephyr/drivers/can/transceiver.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/pm/device.h>
|
||||
#include <zephyr/sys/sys_io.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
@@ -322,10 +323,32 @@ int can_mcan_start(const struct device *dev)
|
||||
}
|
||||
|
||||
data->common.started = true;
|
||||
pm_device_busy_set(dev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static bool can_mcan_rx_filters_exist(const struct device *dev)
|
||||
{
|
||||
const struct can_mcan_config *config = dev->config;
|
||||
const struct can_mcan_callbacks *cbs = config->callbacks;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < cbs->num_std; i++) {
|
||||
if (cbs->std[i].function != NULL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < cbs->num_ext; i++) {
|
||||
if (cbs->ext[i].function != NULL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int can_mcan_stop(const struct device *dev)
|
||||
{
|
||||
const struct can_mcan_config *config = dev->config;
|
||||
@@ -368,6 +391,12 @@ int can_mcan_stop(const struct device *dev)
|
||||
}
|
||||
}
|
||||
|
||||
k_mutex_lock(&data->lock, K_FOREVER);
|
||||
if (!can_mcan_rx_filters_exist(dev)) {
|
||||
pm_device_busy_clear(dev);
|
||||
}
|
||||
k_mutex_unlock(&data->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1184,6 +1213,8 @@ int can_mcan_add_rx_filter(const struct device *dev, can_rx_callback_t callback,
|
||||
filter_id = can_mcan_add_rx_filter_std(dev, callback, user_data, filter);
|
||||
}
|
||||
|
||||
pm_device_busy_set(dev);
|
||||
|
||||
return filter_id;
|
||||
}
|
||||
|
||||
@@ -1230,6 +1261,10 @@ void can_mcan_remove_rx_filter(const struct device *dev, int filter_id)
|
||||
}
|
||||
}
|
||||
|
||||
if (!can_mcan_rx_filters_exist(dev) && !data->common.started) {
|
||||
pm_device_busy_clear(dev);
|
||||
}
|
||||
|
||||
k_mutex_unlock(&data->lock);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include <zephyr/pm/device.h>
|
||||
#include <zephyr/pm/device_runtime.h>
|
||||
|
||||
LOG_MODULE_REGISTER(can_tcan4x5x, CONFIG_CAN_LOG_LEVEL);
|
||||
|
||||
@@ -100,6 +102,10 @@ LOG_MODULE_REGISTER(can_tcan4x5x, CONFIG_CAN_LOG_LEVEL);
|
||||
#define CAN_TCAN4X5X_MODE_CONFIG_SWE_DIS BIT(1)
|
||||
#define CAN_TCAN4X5X_MODE_CONFIG_TEST_MODE_CONFIG BIT(0)
|
||||
|
||||
#define CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP 0
|
||||
#define CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY 1
|
||||
#define CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL 2
|
||||
|
||||
/* Timestamp Prescaler register */
|
||||
#define CAN_TCAN4X5X_TIMESTAMP_PRESCALER 0x0804
|
||||
#define CAN_TCAN4X5X_TIMESTAMP_PRESCALER_MASK GENMASK(7, 0)
|
||||
@@ -201,6 +207,9 @@ LOG_MODULE_REGISTER(can_tcan4x5x, CONFIG_CAN_LOG_LEVEL);
|
||||
|
||||
/* TCAN4x5x timing requirements */
|
||||
#define CAN_TCAN4X5X_T_MODE_STBY_NOM_US 70
|
||||
#define CAN_TCAN4X5X_T_MODE_NOM_SLP_US 200
|
||||
#define CAN_TCAN4X5X_T_MODE_NOM_STBY_US 200
|
||||
#define CAN_TCAN4X5X_T_MODE_SLP_STBY_US 200
|
||||
#define CAN_TCAN4X5X_T_WAKE_US 50
|
||||
#define CAN_TCAN4X5X_T_PULSE_WIDTH_US 30
|
||||
#define CAN_TCAN4X5X_T_RESET_US 1000
|
||||
@@ -551,6 +560,154 @@ static int tcan4x5x_reset(const struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tcan4x5x_set_config_mode_sel(const struct device *dev, uint8_t mode, uint32_t *reg)
|
||||
{
|
||||
int err;
|
||||
uint8_t current_mode;
|
||||
|
||||
switch (mode) {
|
||||
case CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP:
|
||||
case CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY:
|
||||
case CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL:
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("invalid mode %u", mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = tcan4x5x_read_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, reg);
|
||||
if (err != 0) {
|
||||
LOG_ERR("failed to read configuration register (err %d)", err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
current_mode = FIELD_GET(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL, *reg);
|
||||
LOG_DBG("current mode %u, new mode %u", current_mode, mode);
|
||||
|
||||
*reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL);
|
||||
*reg |= FIELD_PREP(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL, mode);
|
||||
|
||||
err = tcan4x5x_write_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, *reg);
|
||||
if (err != 0) {
|
||||
LOG_ERR("failed to write configuration register (err %d)", err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY &&
|
||||
mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL) {
|
||||
/* Wait for standby to normal mode switch */
|
||||
k_busy_wait(CAN_TCAN4X5X_T_MODE_STBY_NOM_US);
|
||||
} else if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL &&
|
||||
mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP) {
|
||||
/* Wait for normal to sleep mode switch */
|
||||
k_busy_wait(CAN_TCAN4X5X_T_MODE_NOM_SLP_US);
|
||||
} else if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL &&
|
||||
mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY) {
|
||||
/* Wait for normal to standby mode switch */
|
||||
k_busy_wait(CAN_TCAN4X5X_T_MODE_NOM_STBY_US);
|
||||
} else if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP &&
|
||||
mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY) {
|
||||
/* Wait for sleep to standby mode switch */
|
||||
k_busy_wait(CAN_TCAN4X5X_T_MODE_SLP_STBY_US);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tcan4x5x_init_normal_mode(const struct device *dev)
|
||||
{
|
||||
const struct can_mcan_config *mcan_config = dev->config;
|
||||
const struct tcan4x5x_config *tcan_config = mcan_config->custom;
|
||||
int err = 0;
|
||||
uint32_t reg;
|
||||
|
||||
/* Set TCAN4x5x mode normal */
|
||||
err = tcan4x5x_set_config_mode_sel(dev, CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL, ®);
|
||||
if (err != 0) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Configure the frequency reference */
|
||||
if (tcan_config->clk_freq == MHZ(20)) {
|
||||
/* 20 MHz frequency reference */
|
||||
reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_CLK_REF);
|
||||
} else {
|
||||
/* 40 MHz frequency reference */
|
||||
reg |= CAN_TCAN4X5X_MODE_CONFIG_CLK_REF;
|
||||
}
|
||||
|
||||
/* Set nWKRQ voltage to VIO */
|
||||
reg |= CAN_TCAN4X5X_MODE_CONFIG_NWKRQ_VOLTAGE;
|
||||
|
||||
/* Write remaining configuration to the device */
|
||||
err = tcan4x5x_write_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, reg);
|
||||
if (err != 0) {
|
||||
LOG_ERR("failed to write configuration register (err %d)", err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Configure Message RAM */
|
||||
err = can_mcan_configure_mram(dev, CAN_TCAN4X5X_MRAM_BASE, CAN_TCAN4X5X_MRAM_BASE);
|
||||
if (err != 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Initialize M_CAN */
|
||||
err = can_mcan_init(dev);
|
||||
if (err != 0) {
|
||||
LOG_ERR("failed to initialize mcan (err %d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
static int tcan4x5x_pm_control(const struct device *dev, enum pm_device_action action)
|
||||
{
|
||||
int err = 0;
|
||||
uint32_t reg;
|
||||
|
||||
switch (action) {
|
||||
case PM_DEVICE_ACTION_SUSPEND:
|
||||
if (pm_device_is_busy(dev)) {
|
||||
LOG_DBG("Cannot suspend while device is busy");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enter sleep mode.
|
||||
* NOTE: All RX filters are cleared when entering sleep mode.
|
||||
* User must remove and re-add filters at the application layer.
|
||||
*/
|
||||
err = tcan4x5x_set_config_mode_sel(dev, CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP,
|
||||
®);
|
||||
return err;
|
||||
case PM_DEVICE_ACTION_RESUME:
|
||||
/* Wake up the device */
|
||||
#if TCAN4X5X_WAKE_GPIO_SUPPORT
|
||||
LOG_DBG("Waking up TCAN4x5x via WAKE GPIO");
|
||||
err = tcan4x5x_wake(dev);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
#else
|
||||
LOG_DBG("Waking up TCAN4x5x via reset");
|
||||
err = tcan4x5x_reset(dev);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
/* Enter normal mode */
|
||||
return tcan4x5x_init_normal_mode(dev);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
|
||||
static int tcan4x5x_init(const struct device *dev)
|
||||
{
|
||||
const struct can_mcan_config *mcan_config = dev->config;
|
||||
@@ -558,7 +715,6 @@ static int tcan4x5x_init(const struct device *dev)
|
||||
struct can_mcan_data *mcan_data = dev->data;
|
||||
struct tcan4x5x_data *tcan_data = mcan_data->custom;
|
||||
k_tid_t tid;
|
||||
uint32_t reg;
|
||||
int err;
|
||||
|
||||
/* Initialize int_sem to 1 to ensure any pending IRQ is serviced */
|
||||
@@ -671,48 +827,7 @@ static int tcan4x5x_init(const struct device *dev)
|
||||
FIELD_GET(GENMASK(15, 8), info[2]), FIELD_GET(GENMASK(7, 0), info[2]));
|
||||
#endif /* CONFIG_CAN_LOG_LEVEL >= LOG_LEVEL_DBG */
|
||||
|
||||
/* Set TCAN4x5x mode normal */
|
||||
err = tcan4x5x_read_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, ®);
|
||||
if (err != 0) {
|
||||
LOG_ERR("failed to read configuration register (err %d)", err);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL);
|
||||
reg |= FIELD_PREP(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL, 0x02);
|
||||
reg |= CAN_TCAN4X5X_MODE_CONFIG_WAKE_CONFIG;
|
||||
|
||||
if (tcan_config->clk_freq == MHZ(20)) {
|
||||
/* 20 MHz frequency reference */
|
||||
reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_CLK_REF);
|
||||
} else {
|
||||
/* 40 MHz frequency reference */
|
||||
reg |= CAN_TCAN4X5X_MODE_CONFIG_CLK_REF;
|
||||
}
|
||||
|
||||
err = tcan4x5x_write_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, reg);
|
||||
if (err != 0) {
|
||||
LOG_ERR("failed to write configuration register (err %d)", err);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Wait for standby to normal mode switch */
|
||||
k_busy_wait(CAN_TCAN4X5X_T_MODE_STBY_NOM_US);
|
||||
|
||||
/* Configure Message RAM */
|
||||
err = can_mcan_configure_mram(dev, CAN_TCAN4X5X_MRAM_BASE, CAN_TCAN4X5X_MRAM_BASE);
|
||||
if (err != 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Initialize M_CAN */
|
||||
err = can_mcan_init(dev);
|
||||
if (err != 0) {
|
||||
LOG_ERR("failed to initialize mcan (err %d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return tcan4x5x_init_normal_mode(dev);
|
||||
}
|
||||
|
||||
static DEVICE_API(can, tcan4x5x_driver_api) = {
|
||||
@@ -794,8 +909,9 @@ static const struct can_mcan_ops tcan4x5x_ops = {
|
||||
static struct can_mcan_data can_mcan_data_##inst = \
|
||||
CAN_MCAN_DATA_INITIALIZER(&tcan4x5x_data_##inst); \
|
||||
\
|
||||
CAN_DEVICE_DT_INST_DEFINE(inst, tcan4x5x_init, NULL, &can_mcan_data_##inst, \
|
||||
&can_mcan_config_##inst, POST_KERNEL, CONFIG_CAN_INIT_PRIORITY, \
|
||||
&tcan4x5x_driver_api);
|
||||
PM_DEVICE_DT_INST_DEFINE(inst, tcan4x5x_pm_control); \
|
||||
CAN_DEVICE_DT_INST_DEFINE(inst, tcan4x5x_init, PM_DEVICE_DT_INST_GET(inst), \
|
||||
&can_mcan_data_##inst, &can_mcan_config_##inst, POST_KERNEL, \
|
||||
CONFIG_CAN_INIT_PRIORITY, &tcan4x5x_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(TCAN4X5X_INIT)
|
||||
|
||||
Reference in New Issue
Block a user