soc: ti: cc23x0: Add power management

Add power management capabilities for cc23x0:
- runtime-idle
- standby
- soft-off

Signed-off-by: Stoyan Bogdanov <sbogdanov@baylibre.com>
diff --git a/soc/ti/simplelink/cc23x0/CMakeLists.txt b/soc/ti/simplelink/cc23x0/CMakeLists.txt
index 89a2e8b..e5b8710 100644
--- a/soc/ti/simplelink/cc23x0/CMakeLists.txt
+++ b/soc/ti/simplelink/cc23x0/CMakeLists.txt
@@ -8,6 +8,8 @@
 zephyr_sources(ccfg.c)
 zephyr_include_directories(.)
 
+zephyr_sources_ifdef(CONFIG_PM power.c)
+
 zephyr_linker_sources_ifdef(CONFIG_HAS_TI_CCFG SECTIONS ccfg.ld)
 
 set(SOC_LINKER_SCRIPT ${ZEPHYR_BASE}/include/zephyr/arch/arm/cortex_m/scripts/linker.ld CACHE INTERNAL "")
diff --git a/soc/ti/simplelink/cc23x0/Kconfig b/soc/ti/simplelink/cc23x0/Kconfig
index 473f8fc..a344aef 100644
--- a/soc/ti/simplelink/cc23x0/Kconfig
+++ b/soc/ti/simplelink/cc23x0/Kconfig
@@ -18,6 +18,8 @@
 	select DYNAMIC_THREAD_ALLOC
 	select THREAD_STACK_INFO
 	select BUILD_OUTPUT_HEX
+	select HAS_PM
+	select PM_DEVICE if PM
 
 menu "Bootloader Configuration"
 depends on SOC_SERIES_CC23X0
diff --git a/soc/ti/simplelink/cc23x0/power.c b/soc/ti/simplelink/cc23x0/power.c
new file mode 100644
index 0000000..54512c9
--- /dev/null
+++ b/soc/ti/simplelink/cc23x0/power.c
@@ -0,0 +1,200 @@
+/*
+ * 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);
diff --git a/soc/ti/simplelink/cc23x0/soc.c b/soc/ti/simplelink/cc23x0/soc.c
index cddae58..c687e37 100644
--- a/soc/ti/simplelink/cc23x0/soc.c
+++ b/soc/ti/simplelink/cc23x0/soc.c
@@ -7,6 +7,9 @@
 
 #include <driverlib/setup.h>
 
+const uint_least8_t GPIO_pinLowerBound;
+const uint_least8_t GPIO_pinUpperBound = 25;
+
 void soc_reset_hook(void)
 {
 	/* Perform necessary trim of the device. */