/* * Copyright (c) 2019 Brett Witherspoon * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT ti_cc13xx_cc26xx_trng #include #include #include #include #include #include #include #include #include #include #include #include #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 inline struct entropy_cc13xx_cc26xx_data * get_dev_data(const struct device *dev) { return dev->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_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 = get_dev_data(dev); 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) { struct entropy_cc13xx_cc26xx_data *data = get_dev_data(arg); 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 = get_dev_data(dev); 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(get_dev_data(dev)); } } 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 = get_dev_data(dev); 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 = get_dev_data(dev); /* 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_REF(0), &entropy_cc13xx_cc26xx_data, NULL, PRE_KERNEL_1, CONFIG_ENTROPY_INIT_PRIORITY, &entropy_cc13xx_cc26xx_driver_api);