Skip to content

Commit 631dbd9

Browse files
rerickson1cfriedt
authored andcommitted
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 <[email protected]>
1 parent 9ca3cad commit 631dbd9

File tree

2 files changed

+197
-46
lines changed

2 files changed

+197
-46
lines changed

drivers/can/can_mcan.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <zephyr/drivers/can/transceiver.h>
1111
#include <zephyr/kernel.h>
1212
#include <zephyr/logging/log.h>
13+
#include <zephyr/pm/device.h>
1314
#include <zephyr/sys/sys_io.h>
1415
#include <zephyr/sys/util.h>
1516

@@ -322,10 +323,32 @@ int can_mcan_start(const struct device *dev)
322323
}
323324

324325
data->common.started = true;
326+
pm_device_busy_set(dev);
325327

326328
return err;
327329
}
328330

331+
static bool can_mcan_rx_filters_exist(const struct device *dev)
332+
{
333+
const struct can_mcan_config *config = dev->config;
334+
const struct can_mcan_callbacks *cbs = config->callbacks;
335+
int i;
336+
337+
for (i = 0; i < cbs->num_std; i++) {
338+
if (cbs->std[i].function != NULL) {
339+
return true;
340+
}
341+
}
342+
343+
for (i = 0; i < cbs->num_ext; i++) {
344+
if (cbs->ext[i].function != NULL) {
345+
return true;
346+
}
347+
}
348+
349+
return false;
350+
}
351+
329352
int can_mcan_stop(const struct device *dev)
330353
{
331354
const struct can_mcan_config *config = dev->config;
@@ -368,6 +391,12 @@ int can_mcan_stop(const struct device *dev)
368391
}
369392
}
370393

394+
k_mutex_lock(&data->lock, K_FOREVER);
395+
if (!can_mcan_rx_filters_exist(dev)) {
396+
pm_device_busy_clear(dev);
397+
}
398+
k_mutex_unlock(&data->lock);
399+
371400
return 0;
372401
}
373402

@@ -1184,6 +1213,8 @@ int can_mcan_add_rx_filter(const struct device *dev, can_rx_callback_t callback,
11841213
filter_id = can_mcan_add_rx_filter_std(dev, callback, user_data, filter);
11851214
}
11861215

1216+
pm_device_busy_set(dev);
1217+
11871218
return filter_id;
11881219
}
11891220

@@ -1230,6 +1261,10 @@ void can_mcan_remove_rx_filter(const struct device *dev, int filter_id)
12301261
}
12311262
}
12321263

1264+
if (!can_mcan_rx_filters_exist(dev) && !data->common.started) {
1265+
pm_device_busy_clear(dev);
1266+
}
1267+
12331268
k_mutex_unlock(&data->lock);
12341269
}
12351270

drivers/can/can_tcan4x5x.c

Lines changed: 162 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include <zephyr/logging/log.h>
1313
#include <zephyr/sys/byteorder.h>
1414
#include <zephyr/sys/util.h>
15+
#include <zephyr/pm/device.h>
16+
#include <zephyr/pm/device_runtime.h>
1517

1618
LOG_MODULE_REGISTER(can_tcan4x5x, CONFIG_CAN_LOG_LEVEL);
1719

@@ -100,6 +102,10 @@ LOG_MODULE_REGISTER(can_tcan4x5x, CONFIG_CAN_LOG_LEVEL);
100102
#define CAN_TCAN4X5X_MODE_CONFIG_SWE_DIS BIT(1)
101103
#define CAN_TCAN4X5X_MODE_CONFIG_TEST_MODE_CONFIG BIT(0)
102104

105+
#define CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP 0
106+
#define CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY 1
107+
#define CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL 2
108+
103109
/* Timestamp Prescaler register */
104110
#define CAN_TCAN4X5X_TIMESTAMP_PRESCALER 0x0804
105111
#define CAN_TCAN4X5X_TIMESTAMP_PRESCALER_MASK GENMASK(7, 0)
@@ -201,6 +207,9 @@ LOG_MODULE_REGISTER(can_tcan4x5x, CONFIG_CAN_LOG_LEVEL);
201207

202208
/* TCAN4x5x timing requirements */
203209
#define CAN_TCAN4X5X_T_MODE_STBY_NOM_US 70
210+
#define CAN_TCAN4X5X_T_MODE_NOM_SLP_US 200
211+
#define CAN_TCAN4X5X_T_MODE_NOM_STBY_US 200
212+
#define CAN_TCAN4X5X_T_MODE_SLP_STBY_US 200
204213
#define CAN_TCAN4X5X_T_WAKE_US 50
205214
#define CAN_TCAN4X5X_T_PULSE_WIDTH_US 30
206215
#define CAN_TCAN4X5X_T_RESET_US 1000
@@ -551,14 +560,161 @@ static int tcan4x5x_reset(const struct device *dev)
551560
return 0;
552561
}
553562

563+
static int tcan4x5x_set_config_mode_sel(const struct device *dev, uint8_t mode, uint32_t *reg)
564+
{
565+
int err;
566+
uint8_t current_mode;
567+
568+
switch (mode) {
569+
case CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP:
570+
case CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY:
571+
case CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL:
572+
break;
573+
default:
574+
LOG_ERR("invalid mode %u", mode);
575+
return -EINVAL;
576+
}
577+
578+
err = tcan4x5x_read_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, reg);
579+
if (err != 0) {
580+
LOG_ERR("failed to read configuration register (err %d)", err);
581+
return -EIO;
582+
}
583+
584+
current_mode = FIELD_GET(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL, *reg);
585+
LOG_DBG("current mode %u, new mode %u", current_mode, mode);
586+
587+
*reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL);
588+
*reg |= FIELD_PREP(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL, mode);
589+
590+
err = tcan4x5x_write_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, *reg);
591+
if (err != 0) {
592+
LOG_ERR("failed to write configuration register (err %d)", err);
593+
return -EIO;
594+
}
595+
596+
if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY &&
597+
mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL) {
598+
/* Wait for standby to normal mode switch */
599+
k_busy_wait(CAN_TCAN4X5X_T_MODE_STBY_NOM_US);
600+
} else if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL &&
601+
mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP) {
602+
/* Wait for normal to sleep mode switch */
603+
k_busy_wait(CAN_TCAN4X5X_T_MODE_NOM_SLP_US);
604+
} else if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL &&
605+
mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY) {
606+
/* Wait for normal to standby mode switch */
607+
k_busy_wait(CAN_TCAN4X5X_T_MODE_NOM_STBY_US);
608+
} else if (current_mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP &&
609+
mode == CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_STANDBY) {
610+
/* Wait for sleep to standby mode switch */
611+
k_busy_wait(CAN_TCAN4X5X_T_MODE_SLP_STBY_US);
612+
}
613+
614+
return 0;
615+
}
616+
617+
static int tcan4x5x_init_normal_mode(const struct device *dev)
618+
{
619+
const struct can_mcan_config *mcan_config = dev->config;
620+
const struct tcan4x5x_config *tcan_config = mcan_config->custom;
621+
int err = 0;
622+
uint32_t reg;
623+
624+
/* Set TCAN4x5x mode normal */
625+
err = tcan4x5x_set_config_mode_sel(dev, CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_NORMAL, &reg);
626+
if (err != 0) {
627+
return -ENODEV;
628+
}
629+
630+
/* Configure the frequency reference */
631+
if (tcan_config->clk_freq == MHZ(20)) {
632+
/* 20 MHz frequency reference */
633+
reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_CLK_REF);
634+
} else {
635+
/* 40 MHz frequency reference */
636+
reg |= CAN_TCAN4X5X_MODE_CONFIG_CLK_REF;
637+
}
638+
639+
/* Set nWKRQ voltage to VIO */
640+
reg |= CAN_TCAN4X5X_MODE_CONFIG_NWKRQ_VOLTAGE;
641+
642+
/* Write remaining configuration to the device */
643+
err = tcan4x5x_write_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, reg);
644+
if (err != 0) {
645+
LOG_ERR("failed to write configuration register (err %d)", err);
646+
return -EIO;
647+
}
648+
649+
/* Configure Message RAM */
650+
err = can_mcan_configure_mram(dev, CAN_TCAN4X5X_MRAM_BASE, CAN_TCAN4X5X_MRAM_BASE);
651+
if (err != 0) {
652+
return -EIO;
653+
}
654+
655+
/* Initialize M_CAN */
656+
err = can_mcan_init(dev);
657+
if (err != 0) {
658+
LOG_ERR("failed to initialize mcan (err %d)", err);
659+
return err;
660+
}
661+
662+
return err;
663+
}
664+
665+
#ifdef CONFIG_PM_DEVICE
666+
static int tcan4x5x_pm_control(const struct device *dev, enum pm_device_action action)
667+
{
668+
int err = 0;
669+
uint32_t reg;
670+
671+
switch (action) {
672+
case PM_DEVICE_ACTION_SUSPEND:
673+
if (pm_device_is_busy(dev)) {
674+
LOG_DBG("Cannot suspend while device is busy");
675+
return -EBUSY;
676+
}
677+
678+
/*
679+
* Enter sleep mode.
680+
* NOTE: All RX filters are cleared when entering sleep mode.
681+
* User must remove and re-add filters at the application layer.
682+
*/
683+
err = tcan4x5x_set_config_mode_sel(dev, CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL_SLEEP,
684+
&reg);
685+
return err;
686+
case PM_DEVICE_ACTION_RESUME:
687+
/* Wake up the device */
688+
#if TCAN4X5X_WAKE_GPIO_SUPPORT
689+
LOG_DBG("Waking up TCAN4x5x via WAKE GPIO");
690+
err = tcan4x5x_wake(dev);
691+
if (err != 0) {
692+
return err;
693+
}
694+
#else
695+
LOG_DBG("Waking up TCAN4x5x via reset");
696+
err = tcan4x5x_reset(dev);
697+
if (err != 0) {
698+
return err;
699+
}
700+
#endif
701+
/* Enter normal mode */
702+
return tcan4x5x_init_normal_mode(dev);
703+
default:
704+
break;
705+
}
706+
707+
return -ENOTSUP;
708+
}
709+
#endif /* CONFIG_PM_DEVICE */
710+
554711
static int tcan4x5x_init(const struct device *dev)
555712
{
556713
const struct can_mcan_config *mcan_config = dev->config;
557714
const struct tcan4x5x_config *tcan_config = mcan_config->custom;
558715
struct can_mcan_data *mcan_data = dev->data;
559716
struct tcan4x5x_data *tcan_data = mcan_data->custom;
560717
k_tid_t tid;
561-
uint32_t reg;
562718
int err;
563719

564720
/* Initialize int_sem to 1 to ensure any pending IRQ is serviced */
@@ -671,48 +827,7 @@ static int tcan4x5x_init(const struct device *dev)
671827
FIELD_GET(GENMASK(15, 8), info[2]), FIELD_GET(GENMASK(7, 0), info[2]));
672828
#endif /* CONFIG_CAN_LOG_LEVEL >= LOG_LEVEL_DBG */
673829

674-
/* Set TCAN4x5x mode normal */
675-
err = tcan4x5x_read_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, &reg);
676-
if (err != 0) {
677-
LOG_ERR("failed to read configuration register (err %d)", err);
678-
return -ENODEV;
679-
}
680-
681-
reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL);
682-
reg |= FIELD_PREP(CAN_TCAN4X5X_MODE_CONFIG_MODE_SEL, 0x02);
683-
reg |= CAN_TCAN4X5X_MODE_CONFIG_WAKE_CONFIG;
684-
685-
if (tcan_config->clk_freq == MHZ(20)) {
686-
/* 20 MHz frequency reference */
687-
reg &= ~(CAN_TCAN4X5X_MODE_CONFIG_CLK_REF);
688-
} else {
689-
/* 40 MHz frequency reference */
690-
reg |= CAN_TCAN4X5X_MODE_CONFIG_CLK_REF;
691-
}
692-
693-
err = tcan4x5x_write_tcan_reg(dev, CAN_TCAN4X5X_MODE_CONFIG, reg);
694-
if (err != 0) {
695-
LOG_ERR("failed to write configuration register (err %d)", err);
696-
return -ENODEV;
697-
}
698-
699-
/* Wait for standby to normal mode switch */
700-
k_busy_wait(CAN_TCAN4X5X_T_MODE_STBY_NOM_US);
701-
702-
/* Configure Message RAM */
703-
err = can_mcan_configure_mram(dev, CAN_TCAN4X5X_MRAM_BASE, CAN_TCAN4X5X_MRAM_BASE);
704-
if (err != 0) {
705-
return -EIO;
706-
}
707-
708-
/* Initialize M_CAN */
709-
err = can_mcan_init(dev);
710-
if (err != 0) {
711-
LOG_ERR("failed to initialize mcan (err %d)", err);
712-
return err;
713-
}
714-
715-
return 0;
830+
return tcan4x5x_init_normal_mode(dev);
716831
}
717832

718833
static DEVICE_API(can, tcan4x5x_driver_api) = {
@@ -794,8 +909,9 @@ static const struct can_mcan_ops tcan4x5x_ops = {
794909
static struct can_mcan_data can_mcan_data_##inst = \
795910
CAN_MCAN_DATA_INITIALIZER(&tcan4x5x_data_##inst); \
796911
\
797-
CAN_DEVICE_DT_INST_DEFINE(inst, tcan4x5x_init, NULL, &can_mcan_data_##inst, \
798-
&can_mcan_config_##inst, POST_KERNEL, CONFIG_CAN_INIT_PRIORITY, \
799-
&tcan4x5x_driver_api);
912+
PM_DEVICE_DT_INST_DEFINE(inst, tcan4x5x_pm_control); \
913+
CAN_DEVICE_DT_INST_DEFINE(inst, tcan4x5x_init, PM_DEVICE_DT_INST_GET(inst), \
914+
&can_mcan_data_##inst, &can_mcan_config_##inst, POST_KERNEL, \
915+
CONFIG_CAN_INIT_PRIORITY, &tcan4x5x_driver_api);
800916

801917
DT_INST_FOREACH_STATUS_OKAY(TCAN4X5X_INIT)

0 commit comments

Comments
 (0)