drivers: entropy: cc13xx_cc26xx: add power management support

This commit adds a dependency on the TRNG resource in the TI Power
module, prevents the system policy from entering standby when TRNG is
active, and adds support for device PM.

Signed-off-by: Vincent Wan <vincent.wan@linaro.org>
diff --git a/drivers/entropy/entropy_cc13xx_cc26xx.c b/drivers/entropy/entropy_cc13xx_cc26xx.c
index 9431509..72d34e9 100644
--- a/drivers/entropy/entropy_cc13xx_cc26xx.c
+++ b/drivers/entropy/entropy_cc13xx_cc26xx.c
@@ -10,12 +10,17 @@
 #include <device.h>
 #include <drivers/entropy.h>
 #include <irq.h>
+#include <power/power.h>
+
 #include <sys/ring_buffer.h>
 #include <sys/sys_io.h>
 
 #include <driverlib/prcm.h>
 #include <driverlib/trng.h>
 
+#include <ti/drivers/Power.h>
+#include <ti/drivers/power/PowerCC26X2.h>
+
 #define CPU_FREQ DT_PROP(DT_PATH(cpus, cpu_0), clock_frequency)
 
 #define US_PER_SAMPLE (1000000ULL * \
@@ -26,6 +31,13 @@
 	struct k_sem sync;
 	struct ring_buf pool;
 	uint8_t data[CONFIG_ENTROPY_CC13XX_CC26XX_POOL_SIZE];
+#ifdef CONFIG_SYS_POWER_MANAGEMENT
+	Power_NotifyObj post_notify;
+	bool constrained;
+#endif
+#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
+	uint32_t pm_state;
+#endif
 };
 
 DEVICE_DECLARE(entropy_cc13xx_cc26xx);
@@ -36,6 +48,41 @@
 	return dev->driver_data;
 }
 
+static void start_trng(struct entropy_cc13xx_cc26xx_data *data)
+{
+	/* Initialization as described in TRM section 18.6.1.2 */
+	TRNGReset();
+	while (sys_read32(TRNG_BASE + TRNG_O_SWRESET)) {
+		continue;
+	}
+
+	/* Set samples per cycle */
+	TRNGConfigure(0, CONFIG_ENTROPY_CC13XX_CC26XX_SAMPLES_PER_CYCLE,
+		0);
+	/* De-tune FROs */
+	sys_write32(TRNG_FRODETUNE_FRO_MASK_M, TRNG_BASE +
+		TRNG_O_FRODETUNE);
+	/* Enable FROs */
+	sys_write32(TRNG_FROEN_FRO_MASK_M, TRNG_BASE + TRNG_O_FROEN);
+	/* Set shutdown and alarm thresholds */
+	sys_write32((CONFIG_ENTROPY_CC13XX_CC26XX_SHUTDOWN_THRESHOLD
+		<< 16) | CONFIG_ENTROPY_CC13XX_CC26XX_ALARM_THRESHOLD,
+		TRNG_BASE + TRNG_O_ALARMCNT);
+
+	TRNGEnable();
+	TRNGIntEnable(TRNG_NUMBER_READY | TRNG_FRO_SHUTDOWN);
+}
+
+#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
+static void stop_trng(struct entropy_cc13xx_cc26xx_data *data)
+{
+	TRNGDisable();
+
+	TRNGIntClear(TRNG_NUMBER_READY | TRNG_FRO_SHUTDOWN);
+	TRNGIntDisable(TRNG_NUMBER_READY | TRNG_FRO_SHUTDOWN);
+}
+#endif
+
 static void handle_shutdown_ovf(void)
 {
 	uint32_t off;
@@ -59,6 +106,17 @@
 	struct entropy_cc13xx_cc26xx_data *data = get_dev_data(dev);
 	uint32_t cnt;
 
+#if defined(CONFIG_SYS_POWER_MANAGEMENT) && \
+	defined(CONFIG_SYS_POWER_SLEEP_STATES)
+	unsigned int key = irq_lock();
+
+	if (!data->constrained) {
+		sys_pm_ctrl_disable_state(SYS_POWER_STATE_SLEEP_2);
+		data->constrained = true;
+	}
+	irq_unlock(key);
+#endif
+
 	TRNGIntEnable(TRNG_NUMBER_READY);
 
 	while (len) {
@@ -80,7 +138,8 @@
 static void entropy_cc13xx_cc26xx_isr(void *arg)
 {
 	struct entropy_cc13xx_cc26xx_data *data = get_dev_data(arg);
-	uint32_t src, cnt;
+	uint32_t src = 0;
+	uint32_t cnt;
 	uint32_t num[2];
 
 	/* Interrupt service routine as described in TRM section 18.6.1.3.2 */
@@ -95,6 +154,14 @@
 
 		/* When pool is full disable interrupt and stop reading numbers */
 		if (cnt != sizeof(num)) {
+#if defined(CONFIG_SYS_POWER_MANAGEMENT) && \
+	defined(CONFIG_SYS_POWER_SLEEP_STATES)
+			if (data->constrained) {
+				sys_pm_ctrl_enable_state(
+					SYS_POWER_STATE_SLEEP_2);
+				data->constrained = false;
+			}
+#endif
 			TRNGIntDisable(TRNG_NUMBER_READY);
 		}
 
@@ -123,7 +190,9 @@
 	cnt = ring_buf_get(&data->pool, buf, len);
 	irq_unlock(key);
 
-	if ((cnt != len) && ((flags & ENTROPY_BUSYWAIT) != 0U)) {
+	if ((cnt == len) || ((flags & ENTROPY_BUSYWAIT) == 0U)) {
+		read = cnt;
+	} else {
 		buf += cnt;
 		len -= cnt;
 
@@ -165,17 +234,114 @@
 			}
 		}
 
-	} else {
-		read = cnt;
 	}
 
 	return read;
 }
 
+#ifdef CONFIG_SYS_POWER_MANAGEMENT
+/*
+ *  ======== post_notify_fxn ========
+ *  Called by Power module when waking up the CPU from Standby. The TRNG needs
+ *  to be reconfigured afterwards, unless Zephyr's device PM turned it off, in
+ *  which case it'd be responsible for turning it back on and reconfiguring it.
+ */
+static int post_notify_fxn(unsigned int eventType, uintptr_t eventArg,
+	uintptr_t clientArg)
+{
+	struct device *dev = (struct device *)clientArg;
+	int ret = Power_NOTIFYDONE;
+	int16_t res_id;
+
+	/* Reconfigure the hardware if returning from sleep */
+	if (eventType == PowerCC26XX_AWAKE_STANDBY) {
+		res_id = PowerCC26XX_PERIPH_TRNG;
+
+		if (Power_getDependencyCount(res_id) != 0) {
+			/* Reconfigure and enable TRNG only if powered */
+			start_trng(get_dev_data(dev));
+		}
+	}
+
+	return (ret);
+}
+#endif
+
+#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
+static int entropy_cc13xx_cc26xx_set_power_state(struct device *dev,
+	uint32_t new_state)
+{
+	struct entropy_cc13xx_cc26xx_data *data = get_dev_data(dev);
+	int ret = 0;
+
+	if ((new_state == DEVICE_PM_ACTIVE_STATE) &&
+		(new_state != data->pm_state)) {
+		Power_setDependency(PowerCC26XX_PERIPH_TRNG);
+		start_trng(data);
+	} else {
+		__ASSERT_NO_MSG(new_state == DEVICE_PM_LOW_POWER_STATE ||
+			new_state == DEVICE_PM_SUSPEND_STATE ||
+			new_state == DEVICE_PM_OFF_STATE);
+
+		if (data->pm_state == DEVICE_PM_ACTIVE_STATE) {
+			stop_trng(data);
+			Power_releaseDependency(PowerCC26XX_PERIPH_TRNG);
+		}
+	}
+	data->pm_state = new_state;
+
+	return ret;
+}
+
+static int entropy_cc13xx_cc26xx_pm_control(struct device *dev,
+	uint32_t ctrl_command, void *context, device_pm_cb cb, void *arg)
+{
+	struct entropy_cc13xx_cc26xx_data *data = get_dev_data(dev);
+	int ret = 0;
+
+	if (ctrl_command == DEVICE_PM_SET_POWER_STATE) {
+		uint32_t new_state = *((const uint32_t *)context);
+
+		if (new_state != data->pm_state) {
+			ret = entropy_cc13xx_cc26xx_set_power_state(dev,
+				new_state);
+		}
+	} else {
+		__ASSERT_NO_MSG(ctrl_command == DEVICE_PM_GET_POWER_STATE);
+		*((uint32_t *)context) = data->pm_state;
+	}
+
+	if (cb) {
+		cb(dev, ret, context, arg);
+	}
+
+	return ret;
+}
+#endif /* CONFIG_DEVICE_POWER_MANAGEMENT */
+
 static int entropy_cc13xx_cc26xx_init(struct device *dev)
 {
 	struct entropy_cc13xx_cc26xx_data *data = get_dev_data(dev);
 
+#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
+	get_dev_data(dev)->pm_state = DEVICE_PM_ACTIVE_STATE;
+#endif
+
+	/* Initialize driver data */
+	ring_buf_init(&data->pool, sizeof(data->data), data->data);
+
+#if defined(CONFIG_SYS_POWER_MANAGEMENT)
+	Power_setDependency(PowerCC26XX_PERIPH_TRNG);
+#if defined(CONFIG_SYS_POWER_SLEEP_STATES)
+	/* Stay out of standby until buffer is filled with entropy */
+	sys_pm_ctrl_disable_state(SYS_POWER_STATE_SLEEP_2);
+	data->constrained = true;
+#endif
+	/* Register notification function */
+	Power_registerNotify(&data->post_notify,
+		PowerCC26XX_AWAKE_STANDBY,
+		post_notify_fxn, (uintptr_t)dev);
+#else
 	/* Power TRNG domain */
 	PRCMPowerDomainOn(PRCM_DOMAIN_PERIPH);
 
@@ -199,29 +365,9 @@
 	       PRCM_DOMAIN_POWER_ON) {
 		continue;
 	}
+#endif
 
-	/* Initialize driver data */
-	ring_buf_init(&data->pool, sizeof(data->data), data->data);
-
-	/* Initialization as described in TRM section 18.6.1.2 */
-	TRNGReset();
-	while (sys_read32(TRNG_BASE + TRNG_O_SWRESET)) {
-		continue;
-	}
-
-	/* Set samples per cycle */
-	TRNGConfigure(0, CONFIG_ENTROPY_CC13XX_CC26XX_SAMPLES_PER_CYCLE, 0);
-	/* De-tune FROs */
-	sys_write32(TRNG_FRODETUNE_FRO_MASK_M, TRNG_BASE + TRNG_O_FRODETUNE);
-	/* Enable FROs */
-	sys_write32(TRNG_FROEN_FRO_MASK_M, TRNG_BASE + TRNG_O_FROEN);
-	/* Set shutdown and alarm thresholds */
-	sys_write32((CONFIG_ENTROPY_CC13XX_CC26XX_SHUTDOWN_THRESHOLD << 16) |
-			    CONFIG_ENTROPY_CC13XX_CC26XX_ALARM_THRESHOLD,
-		    TRNG_BASE + TRNG_O_ALARMCNT);
-
-	TRNGEnable();
-	TRNGIntEnable(TRNG_NUMBER_READY | TRNG_FRO_SHUTDOWN);
+	start_trng(data);
 
 	IRQ_CONNECT(DT_INST_IRQN(0),
 		    DT_INST_IRQ(0, priority),
@@ -242,7 +388,16 @@
 	.sync = Z_SEM_INITIALIZER(entropy_cc13xx_cc26xx_data.sync, 0, 1),
 };
 
+#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
+DEVICE_DEFINE(entropy_cc13xx_cc26xx, DT_INST_LABEL(0),
+		entropy_cc13xx_cc26xx_init,
+		entropy_cc13xx_cc26xx_pm_control,
+		&entropy_cc13xx_cc26xx_data, NULL,
+		PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
+		&entropy_cc13xx_cc26xx_driver_api);
+#else
 DEVICE_AND_API_INIT(entropy_cc13xx_cc26xx, DT_INST_LABEL(0),
 		    entropy_cc13xx_cc26xx_init, &entropy_cc13xx_cc26xx_data,
 		    NULL, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
 		    &entropy_cc13xx_cc26xx_driver_api);
+#endif