| /* |
| * Copyright (c) 2019 Brett Witherspoon |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ti_cc13xx_cc26xx_trng |
| |
| #include <kernel.h> |
| #include <device.h> |
| #include <drivers/entropy.h> |
| #include <irq.h> |
| #include <pm/pm.h> |
| #include <pm/device.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 * \ |
| CONFIG_ENTROPY_CC13XX_CC26XX_SAMPLES_PER_CYCLE / CPU_FREQ + 1ULL) |
| |
| struct entropy_cc13xx_cc26xx_data { |
| struct k_sem lock; |
| struct k_sem sync; |
| struct ring_buf pool; |
| uint8_t data[CONFIG_ENTROPY_CC13XX_CC26XX_POOL_SIZE]; |
| #ifdef CONFIG_PM |
| Power_NotifyObj post_notify; |
| bool constrained; |
| #endif |
| }; |
| |
| 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_PM_DEVICE |
| 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; |
| |
| /* Clear shutdown */ |
| TRNGIntClear(TRNG_FRO_SHUTDOWN); |
| /* Disabled FROs */ |
| off = sys_read32(TRNG_BASE + TRNG_O_ALARMSTOP); |
| /* Clear alarms */ |
| sys_write32(0, TRNG_BASE + TRNG_O_ALARMMASK); |
| sys_write32(0, TRNG_BASE + TRNG_O_ALARMSTOP); |
| /* De-tune FROs */ |
| sys_write32(off, TRNG_BASE + TRNG_O_FRODETUNE); |
| /* Re-enable FROs */ |
| sys_write32(off, TRNG_BASE + TRNG_O_FROEN); |
| } |
| |
| static int entropy_cc13xx_cc26xx_get_entropy(const struct device *dev, |
| uint8_t *buf, |
| uint16_t len) |
| { |
| struct entropy_cc13xx_cc26xx_data *data = dev->data; |
| uint32_t cnt; |
| |
| #ifdef CONFIG_PM |
| unsigned int key = irq_lock(); |
| |
| if (!data->constrained) { |
| pm_constraint_set(PM_STATE_STANDBY); |
| data->constrained = true; |
| } |
| irq_unlock(key); |
| #endif |
| |
| TRNGIntEnable(TRNG_NUMBER_READY); |
| |
| while (len) { |
| k_sem_take(&data->lock, K_FOREVER); |
| cnt = ring_buf_get(&data->pool, buf, len); |
| k_sem_give(&data->lock); |
| |
| if (cnt) { |
| buf += cnt; |
| len -= cnt; |
| } else { |
| k_sem_take(&data->sync, K_FOREVER); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void entropy_cc13xx_cc26xx_isr(const void *arg) |
| { |
| const struct device *dev = arg; |
| struct entropy_cc13xx_cc26xx_data *data = dev->data; |
| uint32_t src = 0; |
| uint32_t cnt; |
| uint32_t num[2]; |
| |
| /* Interrupt service routine as described in TRM section 18.6.1.3.2 */ |
| src = TRNGStatusGet(); |
| |
| if (src & TRNG_NUMBER_READY) { |
| /* This function acknowledges the ready status */ |
| num[1] = TRNGNumberGet(TRNG_HI_WORD); |
| num[0] = TRNGNumberGet(TRNG_LOW_WORD); |
| |
| cnt = ring_buf_put(&data->pool, (uint8_t *)num, sizeof(num)); |
| |
| /* When pool is full disable interrupt and stop reading numbers */ |
| if (cnt != sizeof(num)) { |
| #ifdef CONFIG_PM |
| if (data->constrained) { |
| pm_constraint_release( |
| PM_STATE_STANDBY); |
| data->constrained = false; |
| } |
| #endif |
| TRNGIntDisable(TRNG_NUMBER_READY); |
| } |
| |
| k_sem_give(&data->sync); |
| } |
| |
| /* Change the shutdown FROs' oscillating frequncy in an attempt to |
| * prevent further locking on to the sampling clock frequncy. |
| */ |
| if (src & TRNG_FRO_SHUTDOWN) { |
| handle_shutdown_ovf(); |
| } |
| } |
| |
| static int entropy_cc13xx_cc26xx_get_entropy_isr(const struct device *dev, |
| uint8_t *buf, uint16_t len, |
| uint32_t flags) |
| { |
| struct entropy_cc13xx_cc26xx_data *data = dev->data; |
| uint16_t cnt; |
| uint16_t read = len; |
| uint32_t src; |
| uint32_t num[2]; |
| unsigned int key; |
| |
| key = irq_lock(); |
| cnt = ring_buf_get(&data->pool, buf, len); |
| irq_unlock(key); |
| |
| if ((cnt == len) || ((flags & ENTROPY_BUSYWAIT) == 0U)) { |
| read = cnt; |
| } else { |
| buf += cnt; |
| len -= cnt; |
| |
| /* Allowed to busy-wait. We should use a polling approach */ |
| while (len) { |
| key = irq_lock(); |
| |
| src = TRNGStatusGet(); |
| if (src & TRNG_NUMBER_READY) { |
| /* |
| * This function acknowledges the ready |
| * status |
| */ |
| num[1] = TRNGNumberGet(TRNG_HI_WORD); |
| num[0] = TRNGNumberGet(TRNG_LOW_WORD); |
| |
| ring_buf_put(&data->pool, (uint8_t *)num, |
| sizeof(num)); |
| } |
| |
| /* |
| * If interrupts were enabled during busy wait, this |
| * would allow us to pick up anything that has been put |
| * in by the ISR as well. |
| */ |
| cnt = ring_buf_get(&data->pool, buf, len); |
| |
| if (src & TRNG_FRO_SHUTDOWN) { |
| handle_shutdown_ovf(); |
| } |
| |
| irq_unlock(key); |
| |
| if (cnt) { |
| buf += cnt; |
| len -= cnt; |
| } else { |
| k_busy_wait(US_PER_SAMPLE); |
| } |
| } |
| |
| } |
| |
| return read; |
| } |
| |
| #ifdef CONFIG_PM |
| /* |
| * ======== 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) |
| { |
| const struct device *dev = (const 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(dev->data); |
| } |
| } |
| |
| return (ret); |
| } |
| #endif |
| |
| #ifdef CONFIG_PM_DEVICE |
| static int entropy_cc13xx_cc26xx_pm_action(const struct device *dev, |
| enum pm_device_action action) |
| { |
| struct entropy_cc13xx_cc26xx_data *data = dev->data; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_RESUME: |
| Power_setDependency(PowerCC26XX_PERIPH_TRNG); |
| start_trng(data); |
| break; |
| case PM_DEVICE_ACTION_SUSPEND: |
| stop_trng(data); |
| Power_releaseDependency(PowerCC26XX_PERIPH_TRNG); |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| #endif /* CONFIG_PM_DEVICE */ |
| |
| static int entropy_cc13xx_cc26xx_init(const struct device *dev) |
| { |
| struct entropy_cc13xx_cc26xx_data *data = dev->data; |
| |
| /* Initialize driver data */ |
| ring_buf_init(&data->pool, sizeof(data->data), data->data); |
| |
| #if defined(CONFIG_PM) |
| Power_setDependency(PowerCC26XX_PERIPH_TRNG); |
| /* Stay out of standby until buffer is filled with entropy */ |
| pm_constraint_set(PM_STATE_STANDBY); |
| data->constrained = true; |
| /* 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); |
| |
| /* Enable TRNG peripheral clocks */ |
| PRCMPeripheralRunEnable(PRCM_PERIPH_TRNG); |
| /* Enabled the TRNG while in sleep mode to keep the entropy pool full. After |
| * the pool is full the TRNG will enter idle mode when random numbers are no |
| * longer being read. */ |
| PRCMPeripheralSleepEnable(PRCM_PERIPH_TRNG); |
| PRCMPeripheralDeepSleepEnable(PRCM_PERIPH_TRNG); |
| |
| |
| /* Load PRCM settings */ |
| PRCMLoadSet(); |
| while (!PRCMLoadGet()) { |
| continue; |
| } |
| |
| /* Peripherals should not be accessed until power domain is on. */ |
| while (PRCMPowerDomainStatus(PRCM_DOMAIN_PERIPH) != |
| PRCM_DOMAIN_POWER_ON) { |
| continue; |
| } |
| #endif |
| |
| start_trng(data); |
| |
| IRQ_CONNECT(DT_INST_IRQN(0), |
| DT_INST_IRQ(0, priority), |
| entropy_cc13xx_cc26xx_isr, |
| DEVICE_DT_INST_GET(0), 0); |
| irq_enable(DT_INST_IRQN(0)); |
| |
| return 0; |
| } |
| |
| static const struct entropy_driver_api entropy_cc13xx_cc26xx_driver_api = { |
| .get_entropy = entropy_cc13xx_cc26xx_get_entropy, |
| .get_entropy_isr = entropy_cc13xx_cc26xx_get_entropy_isr, |
| }; |
| |
| static struct entropy_cc13xx_cc26xx_data entropy_cc13xx_cc26xx_data = { |
| .lock = Z_SEM_INITIALIZER(entropy_cc13xx_cc26xx_data.lock, 1, 1), |
| .sync = Z_SEM_INITIALIZER(entropy_cc13xx_cc26xx_data.sync, 0, 1), |
| }; |
| |
| PM_DEVICE_DT_INST_DEFINE(0, entropy_cc13xx_cc26xx_pm_action); |
| |
| DEVICE_DT_INST_DEFINE(0, |
| entropy_cc13xx_cc26xx_init, |
| PM_DEVICE_DT_INST_GET(0), |
| &entropy_cc13xx_cc26xx_data, NULL, |
| PRE_KERNEL_1, CONFIG_ENTROPY_INIT_PRIORITY, |
| &entropy_cc13xx_cc26xx_driver_api); |