| /* |
| * Copyright (c) 2024 Texas Instruments Incorporated |
| * Copyright (c) 2024 Baylibre, SAS |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/init.h> |
| #include <zephyr/pm/pm.h> |
| #include <zephyr/pm/policy.h> |
| |
| #include <ti/drivers/utils/Math.h> |
| #include <ti/drivers/Power.h> |
| #include <ti/drivers/power/PowerCC23X0.h> |
| |
| #include <inc/hw_types.h> |
| #include <inc/hw_memmap.h> |
| #include <inc/hw_ckmd.h> |
| #include <inc/hw_systim.h> |
| #include <inc/hw_rtc.h> |
| #include <inc/hw_evtsvt.h> |
| #include <inc/hw_ints.h> |
| |
| #include <driverlib/lrfd.h> |
| #include <driverlib/ull.h> |
| #include <driverlib/pmctl.h> |
| |
| /* Configuring TI Power module to not use its policy function (we use Zephyr's |
| * instead), and disable oscillator calibration functionality for now. |
| */ |
| const PowerCC23X0_Config PowerCC23X0_config = { |
| .policyInitFxn = NULL, |
| .policyFxn = NULL, |
| }; |
| |
| #ifdef CONFIG_PM |
| |
| #define MAX_SYSTIMER_DELTA 0xFFBFFFFFU |
| #define RTC_TO_SYSTIM_TICKS 8U |
| #define SYSTIM_CH_STEP 4U |
| #define SYSTIM_CH(idx) (SYSTIM_O_CH0CC + idx * SYSTIM_CH_STEP) |
| #define SYSTIM_TO_RTC_SHIFT 3U |
| #define SYSTIM_CH_CNT 5U |
| #define RTC_NEXT(val, now) (((val - PowerCC23X0_WAKEDELAYSTANDBY) >> SYSTIM_TO_RTC_SHIFT) + now) |
| |
| static void pm_cc23x0_enter_standby(void); |
| static int power_initialize(void); |
| extern int_fast16_t PowerCC23X0_notify(uint_fast16_t eventType); |
| static void pm_cc23x0_systim_standby_restore(void); |
| |
| /* Global to stash the SysTimer timeouts while we enter standby */ |
| static uint32_t systim[SYSTIM_CH_CNT]; |
| static uintptr_t key; |
| static uint32_t systim_mask; |
| |
| /* Shift values to convert between the different resolutions of the SysTimer |
| * channels. Channel 0 can technically support either 1us or 250ns. Until the |
| * channel is actively used, we will hard-code it to 1us resolution to improve |
| * runtime. |
| */ |
| const uint8_t systim_offset[SYSTIM_CH_CNT] = { |
| 0, /* 1us */ |
| 0, /* 1us */ |
| 2, /* 250ns -> 1us */ |
| 2, /* 250ns -> 1us */ |
| 2 /* 250ns -> 1us */ |
| }; |
| |
| static void pm_cc23x0_systim_standby_restore(void) |
| { |
| HWREG(RTC_BASE + RTC_O_ARMCLR) = RTC_ARMCLR_CH0_CLR; |
| HWREG(RTC_BASE + RTC_O_ICLR) = RTC_ICLR_EV0_CLR; |
| |
| ULLSync(); |
| |
| HwiP_clearInterrupt(INT_CPUIRQ16); |
| HWREG(EVTSVT_BASE + EVTSVT_O_CPUIRQ16SEL) = EVTSVT_CPUIRQ16SEL_PUBID_SYSTIM0; |
| |
| while (HWREG(SYSTIM_BASE + SYSTIM_O_STATUS) != SYSTIM_STATUS_VAL_RUN) { |
| ; |
| } |
| |
| for (uint8_t idx = 0; idx < SYSTIM_CH_CNT; idx++) { |
| if (systim_mask & (1 << idx)) { |
| HWREG(SYSTIM_BASE + SYSTIM_CH(idx)) = systim[idx]; |
| } |
| } |
| |
| HWREG(SYSTIM_BASE + SYSTIM_O_IMASK) = systim_mask; |
| LRFDApplyClockDependencies(); |
| PowerCC23X0_notify(PowerLPF3_AWAKE_STANDBY); |
| |
| HwiP_restore(key); |
| } |
| |
| static void pm_cc23x0_enter_standby(void) |
| { |
| uint32_t rtc_now = 0; |
| uint32_t systim_now = 0; |
| uint32_t systim_next = MAX_SYSTIMER_DELTA; |
| uint32_t systim_delta = 0; |
| |
| key = HwiP_disable(); |
| |
| uint32_t constraints = Power_getConstraintMask(); |
| bool standby = (constraints & (1 << PowerLPF3_DISALLOW_STANDBY)) == 0; |
| bool idle = (constraints & (1 << PowerLPF3_DISALLOW_IDLE)) == 0; |
| |
| if (standby && (HWREG(CKMD_BASE + CKMD_O_LFCLKSEL) & CKMD_LFCLKSEL_MAIN_LFOSC) && |
| !(HWREG(CKMD_BASE + CKMD_O_LFCLKSTAT) & CKMD_LFCLKSTAT_FLTSETTLED_M)) { |
| standby = false; |
| idle = false; |
| } |
| |
| if (standby) { |
| systim_mask = HWREG(SYSTIM_BASE + SYSTIM_O_IMASK); |
| if (systim_mask != 0) { |
| systim_next = 0xFFFFFFFF; |
| systim_now = HWREG(SYSTIM_BASE + SYSTIM_O_TIME1U); |
| for (uint8_t idx = 0; idx < SYSTIM_CH_CNT; idx++) { |
| if (systim_mask & (1 << idx)) { |
| systim[idx] = HWREG(SYSTIM_BASE + SYSTIM_CH(idx)); |
| systim_delta = systim[idx]; |
| systim_delta -= systim_now << systim_offset[idx]; |
| |
| if (systim_delta > MAX_SYSTIMER_DELTA) { |
| systim_delta = 0; |
| } |
| |
| systim_delta = systim_delta >> systim_offset[idx]; |
| systim_next = MIN(systim_next, systim_delta); |
| } |
| } |
| } else { |
| systim_next = MAX_SYSTIMER_DELTA; |
| } |
| |
| if (systim_next > PowerCC23X0_TOTALTIMESTANDBY) { |
| HWREG(EVTSVT_BASE + EVTSVT_O_CPUIRQ16SEL) = |
| EVTSVT_CPUIRQ16SEL_PUBID_AON_RTC_COMB; |
| HwiP_clearInterrupt(INT_CPUIRQ16); |
| rtc_now = HWREG(RTC_BASE + RTC_O_TIME8U); |
| HWREG(RTC_BASE + RTC_O_CH0CC8U) = RTC_NEXT(systim_next, rtc_now); |
| |
| Power_sleep(PowerLPF3_STANDBY); |
| pm_cc23x0_systim_standby_restore(); |
| } else if (idle) { |
| __WFI(); |
| } |
| } else if (idle) { |
| __WFI(); |
| } |
| |
| HwiP_restore(key); |
| } |
| |
| void pm_state_set(enum pm_state state, uint8_t substate_id) |
| { |
| ARG_UNUSED(substate_id); |
| |
| switch (state) { |
| case PM_STATE_RUNTIME_IDLE: |
| PowerCC23X0_doWFI(); |
| break; |
| case PM_STATE_STANDBY: |
| pm_cc23x0_enter_standby(); |
| break; |
| case PM_STATE_SOFT_OFF: |
| Power_shutdown(0, 0); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id) |
| { |
| ARG_UNUSED(state); |
| ARG_UNUSED(substate_id); |
| |
| HwiP_restore(0); |
| } |
| |
| #endif /* CONFIG_PM */ |
| |
| static int power_initialize(void) |
| { |
| Power_init(); |
| |
| if (DT_HAS_COMPAT_STATUS_OKAY(ti_cc23x0_lf_xosc)) { |
| PowerLPF3_selectLFXT(); |
| } |
| |
| PMCTLSetVoltageRegulator(PMCTL_VOLTAGE_REGULATOR_DCDC); |
| |
| return 0; |
| } |
| |
| SYS_INIT(power_initialize, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); |