/* * Copyright (c) 2015-2019, Texas Instruments Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * ======== PowerCC26X2.c ======== */ #include #include #include #include #include #include #include /* driverlib header files */ #include #include DeviceFamily_constructPath(inc/hw_types.h) #include DeviceFamily_constructPath(inc/hw_prcm.h) #include DeviceFamily_constructPath(inc/hw_nvic.h) #include DeviceFamily_constructPath(inc/hw_aux_sysif.h) #include DeviceFamily_constructPath(inc/hw_aon_rtc.h) #include DeviceFamily_constructPath(inc/hw_memmap.h) #include DeviceFamily_constructPath(inc/hw_ccfg.h) #include DeviceFamily_constructPath(inc/hw_rfc_pwr.h) #include DeviceFamily_constructPath(inc/hw_aon_pmctl.h) #include DeviceFamily_constructPath(inc/hw_fcfg1.h) #include DeviceFamily_constructPath(inc/hw_ints.h) #include DeviceFamily_constructPath(driverlib/sys_ctrl.h) #include DeviceFamily_constructPath(driverlib/pwr_ctrl.h) #include DeviceFamily_constructPath(driverlib/prcm.h) #include DeviceFamily_constructPath(driverlib/aon_ioc.h) #include DeviceFamily_constructPath(driverlib/aon_rtc.h) #include DeviceFamily_constructPath(driverlib/aon_event.h) #include DeviceFamily_constructPath(driverlib/osc.h) #include DeviceFamily_constructPath(driverlib/cpu.h) #include DeviceFamily_constructPath(driverlib/vims.h) #include DeviceFamily_constructPath(driverlib/rfc.h) #include DeviceFamily_constructPath(driverlib/sys_ctrl.h) #include DeviceFamily_constructPath(driverlib/driverlib_release.h) #include DeviceFamily_constructPath(driverlib/setup.h) #include DeviceFamily_constructPath(driverlib/ccfgread.h) static unsigned int configureXOSCHF(unsigned int action); static unsigned int nopResourceHandler(unsigned int action); static unsigned int configureRFCoreClocks(unsigned int action); static void switchXOSCHF(void); static void disableLFClockQualifiers(void); static void emptyClockFunc(uintptr_t arg); static int_fast16_t notify(uint_fast16_t eventType); static void oscillatorISR(uintptr_t arg); /* RCOSC calibration functions functions */ extern void PowerCC26X2_calibrate(void); extern bool PowerCC26X2_initiateCalibration(void); extern void PowerCC26X2_auxISR(uintptr_t arg); extern void PowerCC26X2_RCOSC_clockFunc(uintptr_t arg); /* Externs */ extern const PowerCC26X2_Config PowerCC26X2_config; /* Module_State */ PowerCC26X2_ModuleState PowerCC26X2_module = { .notifyList = {0}, /* list of registered notifications */ .constraintMask = 0, /* the constraint mask */ .clockObj = {0}, /* Clock object for scheduling wakeups */ .tdcHwi = {0}, /* hwi object for calibration */ .oscHwi = {0}, /* hwi object for oscillators */ .nDeltaFreqCurr = 0, /* RCOSC calibration variable */ .nCtrimCurr = 0, /* RCOSC calibration variable */ .nCtrimFractCurr = 0, /* RCOSC calibration variable */ .nCtrimNew = 0, /* RCOSC calibration variable */ .nCtrimFractNew = 0, /* RCOSC calibration variable */ .nRtrimNew = 0, /* RCOSC calibration variable */ .nRtrimCurr = 0, /* RCOSC calibration variable */ .nDeltaFreqNew = 0, /* RCOSC calibration variable */ .bRefine = false, /* RCOSC calibration variable */ .state = Power_ACTIVE, /* current transition state */ .xoscPending = false, /* is XOSC_HF activation in progress? */ .calLF = false, /* calibrate RCOSC_LF? */ .auxHwiState = 0, /* calibration AUX ISR state */ .busyCal = false, /* already busy calibrating */ .calStep = 0, /* current calibration step */ .firstLF = true, /* is this first LF calibration? */ .enablePolicy = false, /* default value is false */ .initialized = false, /* whether Power_init has been called */ .constraintCounts = { 0, 0, 0, 0, 0, 0, 0 }, .resourceCounts = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .resourceHandlers = { configureRFCoreClocks, configureXOSCHF, nopResourceHandler }, /* special resource handler functions */ .policyFxn = 0 /* power policyFxn */ }; /* resource database */ const PowerCC26XX_ResourceRecord resourceDB[PowerCC26X2_NUMRESOURCES] = { {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_PERIPH, PRCM_PERIPH_TIMER0}, /* PERIPH_GPT0 */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_PERIPH, PRCM_PERIPH_TIMER1}, /* PERIPH_GPT1 */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_PERIPH, PRCM_PERIPH_TIMER2}, /* PERIPH_GPT2 */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_PERIPH, PRCM_PERIPH_TIMER3}, /* PERIPH_GPT3 */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_SERIAL, PRCM_PERIPH_SSI0}, /* PERIPH_SSI0 */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_PERIPH, PRCM_PERIPH_SSI1}, /* PERIPH_SSI1 */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_SERIAL, PRCM_PERIPH_UART0}, /* PERIPH_UART0 */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_SERIAL, PRCM_PERIPH_I2C0}, /* PERIPH_I2C0 */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_PERIPH, PRCM_PERIPH_TRNG}, /* PERIPH_TRNG */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_PERIPH, PRCM_PERIPH_GPIO}, /* PERIPH_GPIO */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_PERIPH, PRCM_PERIPH_UDMA}, /* PERIPH_UDMA */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_PERIPH, PRCM_PERIPH_CRYPTO}, /* PERIPH_CRYPTO */ {PowerCC26XX_PERIPH | PowerCC26XX_PERIPH_UDMA, PRCM_PERIPH_I2S}, /* PERIPH_I2S */ {PowerCC26XX_SPECIAL | PowerCC26XX_DOMAIN_RFCORE, 0}, /* PERIPH_RFCORE */ {PowerCC26XX_SPECIAL | PowerCC26XX_NOPARENT, 1}, /* XOSC_HF */ {PowerCC26XX_DOMAIN | PowerCC26XX_NOPARENT, PRCM_DOMAIN_PERIPH}, /* DOMAIN_PERIPH */ {PowerCC26XX_DOMAIN | PowerCC26XX_NOPARENT, PRCM_DOMAIN_SERIAL}, /* DOMAIN_SERIAL */ {PowerCC26XX_DOMAIN | PowerCC26XX_NOPARENT, PRCM_DOMAIN_RFCORE}, /* DOMAIN_RFCORE */ {PowerCC26XX_SPECIAL | PowerCC26XX_NOPARENT, 2}, /* DOMAIN_SYSBUS */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_PERIPH, PRCM_PERIPH_PKA}, /* PERIPH_PKA */ {PowerCC26XX_PERIPH | PowerCC26XX_DOMAIN_PERIPH, PRCM_PERIPH_UART1}, /* PERIPH_UART1 */ }; /* ****************** Power APIs ******************** */ /* * ======== Power_disablePolicy ======== * Do not run the configured policy */ bool Power_disablePolicy(void) { bool enablePolicy = PowerCC26X2_module.enablePolicy; PowerCC26X2_module.enablePolicy = false; return (enablePolicy); } /* * ======== Power_enablePolicy ======== * Run the configured policy */ void Power_enablePolicy(void) { PowerCC26X2_module.enablePolicy = true; } /* * ======== Power_getConstraintMask ======== * Get a bitmask indicating the constraints that have been registered with * Power. */ uint_fast32_t Power_getConstraintMask(void) { return (PowerCC26X2_module.constraintMask); } /* * ======== Power_getDependencyCount ======== * Get the count of dependencies that are currently declared upon a resource. */ int_fast16_t Power_getDependencyCount(uint_fast16_t resourceId) { DebugP_assert(resourceId < PowerCC26X2_NUMRESOURCES); return ((int_fast16_t)PowerCC26X2_module.resourceCounts[resourceId]); } /* * ======== Power_getTransitionLatency ======== * Get the transition latency for a sleep state. The latency is reported * in units of microseconds. */ uint_fast32_t Power_getTransitionLatency(uint_fast16_t sleepState, uint_fast16_t type) { uint32_t latency = 0; if (type == Power_RESUME) { if (sleepState == PowerCC26XX_STANDBY) { latency = PowerCC26X2_RESUMETIMESTANDBY; } } else { if (sleepState == PowerCC26XX_STANDBY) { latency = PowerCC26X2_TOTALTIMESTANDBY; } } return (latency); } /* * ======== Power_getTransitionState ======== * Get the current sleep transition state. */ uint_fast16_t Power_getTransitionState(void) { return (PowerCC26X2_module.state); } /* * ======== Power_idleFunc ======== * Function needs to be plugged into the idle loop. * It calls the configured policy function if the * 'enablePolicy' flag is set. */ void Power_idleFunc() { if (PowerCC26X2_module.enablePolicy) { if (PowerCC26X2_module.policyFxn != NULL) { (*(PowerCC26X2_module.policyFxn))(); } } } /* * ======== Power_init ======== */ int_fast16_t Power_init() { ClockP_Params clockParams; uint32_t ccfgLfClkSrc; /* CC26X2 PG1.0 trap. If we are running on PG1.0, spin forever. * This hardware revision is no longer supported. This trap is * provided to aid in automatically identifying PG1.0 devices * in circulation and will be removed later in the year. */ if (!((HWREG(FCFG1_BASE + FCFG1_O_TFW_FT) % 10000) >= 683)) { while (1); } /* if this function has already been called, just return */ if (PowerCC26X2_module.initialized) { return (Power_SOK); } /* set module state field 'initialized' to true */ PowerCC26X2_module.initialized = true; /* set the module state enablePolicy field */ PowerCC26X2_module.enablePolicy = PowerCC26X2_config.enablePolicy; /* copy the Power policy function to module state */ PowerCC26X2_module.policyFxn = PowerCC26X2_config.policyFxn; /* construct the Clock object for scheduling of wakeups */ /* initiated and started by the power policy */ ClockP_Params_init(&clockParams); clockParams.period = 0; clockParams.startFlag = false; clockParams.arg = 0; ClockP_construct(&PowerCC26X2_module.clockObj, &emptyClockFunc, 0, &clockParams); /* * If RCOSC calibration is enabled, construct a Clock object for * delays. Set timeout to 8 Clock tick periods to get * ceil(8x10us/30.5us/SCLK_LF_period)*30.5us/SCLK_LF_period = ~90us. * The total time we need to wait for AUX_SYSIF_TDCREFCLKCTL_ACK * is about 105us and the ClockP_start() call needs about 21us. * All together, that makes ~111us. A decent approximation of the * ideal wait duration. * In practice, the COMPARE_MARGIN that is currently still in * the kernel Timer.c implementation may make it take longer * than 90us to time out. */ ClockP_Params_init(&clockParams); clockParams.period = 0; clockParams.startFlag = false; clockParams.arg = 0; ClockP_construct(&PowerCC26X2_module.calibrationClock, &PowerCC26X2_RCOSC_clockFunc, 8, &clockParams); HwiP_construct(&PowerCC26X2_module.oscHwi, INT_OSC_COMB, oscillatorISR, NULL); HWREG(PRCM_BASE + PRCM_O_OSCIMSC) = 0; /* construct the TDC hwi */ HwiP_construct(&PowerCC26X2_module.tdcHwi, INT_AUX_COMB, PowerCC26X2_auxISR, NULL); DRIVERLIB_ASSERT_CURR_RELEASE(); /* read the LF clock source from CCFG */ ccfgLfClkSrc = CCFGRead_SCLK_LF_OPTION(); /* check if should calibrate RCOSC_LF */ if (PowerCC26X2_config.calibrateRCOSC_LF) { /* verify RCOSC_LF is the LF clock source */ if (ccfgLfClkSrc == CCFGREAD_SCLK_LF_OPTION_RCOSC_LF) { PowerCC26X2_module.calLF = true; } } /* * if LF source is RCOSC_LF or XOSC_LF: assert DISALLOW_STANDBY constraint * and start a timeout to check for activation */ if ((ccfgLfClkSrc == CCFGREAD_SCLK_LF_OPTION_RCOSC_LF) || (ccfgLfClkSrc == CCFGREAD_SCLK_LF_OPTION_XOSC_LF)) { /* Turn on oscillator interrupt for SCLK_LF switching */ HWREG(PRCM_BASE + PRCM_O_OSCIMSC) |= PRCM_OSCIMSC_LFSRCDONEIM_M; /* disallow STANDBY pending LF clock quailifier disabling */ Power_setConstraint(PowerCC26XX_DISALLOW_STANDBY); } else if (ccfgLfClkSrc == CCFGREAD_SCLK_LF_OPTION_EXTERNAL_LF) { /* * else, if the LF clock source is external, can disable clock qualifiers * now; no need to assert DISALLOW_STANDBY or start the Clock object */ /* yes, disable the LF clock qualifiers */ DDI16BitfieldWrite( AUX_DDI0_OSC_BASE, DDI_0_OSC_O_CTL0, DDI_0_OSC_CTL0_BYPASS_XOSC_LF_CLK_QUAL_M| DDI_0_OSC_CTL0_BYPASS_RCOSC_LF_CLK_QUAL_M, DDI_0_OSC_CTL0_BYPASS_RCOSC_LF_CLK_QUAL_S, 0x3); /* enable clock loss detection */ OSCClockLossEventEnable(); } else if(ccfgLfClkSrc == CCFGREAD_SCLK_LF_OPTION_XOSC_HF_DLF) { /* else, user has requested LF to be derived from XOSC_HF */ /* Turn on oscillator interrupt for SCLK_LF switching. * When using HPOSC, the LF clock will already have switched * and the interrupt will fire once interrupts are enabled * again when the OS starts. * When using a regular HF crystal, it may take a little * time for the crystal to start up */ HWREG(PRCM_BASE + PRCM_O_OSCIMSC) |= PRCM_OSCIMSC_LFSRCDONEIM_M; /* disallow standby since we cannot go into standby with * an HF derived LF clock */ Power_setConstraint(PowerCC26XX_DISALLOW_STANDBY); } /* if VIMS RAM is configured as GPRAM: set retention constraint */ if (!CCFGRead_DIS_GPRAM()) { Power_setConstraint(PowerCC26XX_RETAIN_VIMS_CACHE_IN_STANDBY); } return (Power_SOK); } /* * ======== Power_registerNotify ======== * Register a function to be called on a specific power event. * */ int_fast16_t Power_registerNotify(Power_NotifyObj * pNotifyObj, uint_fast16_t eventTypes, Power_NotifyFxn notifyFxn, uintptr_t clientArg) { int_fast16_t status = Power_SOK; /* check for NULL pointers */ if ((pNotifyObj == NULL) || (notifyFxn == NULL)) { status = Power_EINVALIDPOINTER; } else { /* fill in notify object elements */ pNotifyObj->eventTypes = eventTypes; pNotifyObj->notifyFxn = notifyFxn; pNotifyObj->clientArg = clientArg; /* place notify object on event notification queue */ List_put(&PowerCC26X2_module.notifyList, (List_Elem*)pNotifyObj); } return (status); } /* * ======== Power_releaseConstraint ======== * Release a previously declared constraint. */ int_fast16_t Power_releaseConstraint(uint_fast16_t constraintId) { unsigned int key; uint8_t count; /* assert constraintId is valid */ DebugP_assert(constraintId < PowerCC26X2_NUMCONSTRAINTS); key = HwiP_disable(); /* get the count of the constraint */ count = PowerCC26X2_module.constraintCounts[constraintId]; DebugP_assert(count != 0); count--; /* save the updated count */ PowerCC26X2_module.constraintCounts[constraintId] = count; if (count == 0) { PowerCC26X2_module.constraintMask &= ~(1 << constraintId); } HwiP_restore(key); return (Power_SOK); } /* * ======== Power_releaseDependency ======== * Release a previously declared dependency. */ int_fast16_t Power_releaseDependency(uint_fast16_t resourceId) { uint8_t parent; uint8_t count; uint32_t id; unsigned int key; /* assert resourceId is valid */ DebugP_assert(resourceId < PowerCC26X2_NUMRESOURCES); /* disable interrupts */ key = HwiP_disable(); /* read and decrement the reference count */ count = PowerCC26X2_module.resourceCounts[resourceId]; DebugP_assert(count != 0); count--; /* save the reference count */ PowerCC26X2_module.resourceCounts[resourceId] = count; /* if this was the last dependency being released.., */ if (count == 0) { /* deactivate this resource ... */ id = resourceDB[resourceId].driverlibID; /* is resource a peripheral?... */ if (resourceDB[resourceId].flags & PowerCC26XX_PERIPH) { PRCMPeripheralRunDisable(id); PRCMPeripheralSleepDisable(id); PRCMPeripheralDeepSleepDisable(id); PRCMLoadSet(); while (!PRCMLoadGet()) { ; } } /* else, does resource require a special handler?... */ else if (resourceDB[resourceId].flags & PowerCC26XX_SPECIAL) { /* call the special handler */ PowerCC26X2_module.resourceHandlers[id](PowerCC26XX_DISABLE); } /* else resource is a power domain */ else { PRCMPowerDomainOff(id); while (PRCMPowerDomainStatus(id) != PRCM_DOMAIN_POWER_OFF) { ; } } /* propagate release up the dependency tree ... */ /* check for a first parent */ parent = resourceDB[resourceId].flags & PowerCC26XX_PARENTMASK; /* if 1st parent, make recursive call to release that dependency */ if (parent < PowerCC26X2_NUMRESOURCES) { Power_releaseDependency(parent); } } /* re-enable interrupts */ HwiP_restore(key); return (Power_SOK); } /* * ======== Power_setConstraint ======== * Declare an operational constraint. */ int_fast16_t Power_setConstraint(uint_fast16_t constraintId) { unsigned int key; /* assert constraint id is valid */ DebugP_assert(constraintId < PowerCC26X2_NUMCONSTRAINTS); /* disable interrupts */ key = HwiP_disable(); /* set the specified constraint in the constraintMask */ PowerCC26X2_module.constraintMask |= 1 << constraintId; /* increment the specified constraint count */ PowerCC26X2_module.constraintCounts[constraintId]++; /* re-enable interrupts */ HwiP_restore(key); return (Power_SOK); } /* * ======== Power_setDependency ======== * Declare a dependency upon a resource. */ int_fast16_t Power_setDependency(uint_fast16_t resourceId) { uint8_t parent; uint8_t count; uint32_t id; unsigned int key; /* assert resourceId is valid */ DebugP_assert(resourceId < PowerCC26X2_NUMRESOURCES); /* disable interrupts */ key = HwiP_disable(); /* read and increment reference count */ count = PowerCC26X2_module.resourceCounts[resourceId]++; /* if resource was NOT activated previously ... */ if (count == 0) { /* propagate set up the dependency tree ... */ /* check for a first parent */ parent = resourceDB[resourceId].flags & PowerCC26XX_PARENTMASK; /* if first parent, make recursive call to set that dependency */ if (parent < PowerCC26X2_NUMRESOURCES) { Power_setDependency(parent); } /* now activate this resource ... */ id = resourceDB[resourceId].driverlibID; /* is resource a peripheral?... */ if (resourceDB[resourceId].flags & PowerCC26XX_PERIPH) { PRCMPeripheralRunEnable(id); PRCMPeripheralSleepEnable(id); PRCMPeripheralDeepSleepEnable(id); PRCMLoadSet(); while (!PRCMLoadGet()) { ; } } /* else, does resource require a special handler?... */ else if (resourceDB[resourceId].flags & PowerCC26XX_SPECIAL) { /* call the special handler */ PowerCC26X2_module.resourceHandlers[id](PowerCC26XX_ENABLE); } /* else resource is a power domain */ else { PRCMPowerDomainOn(id); while (PRCMPowerDomainStatus(id) != PRCM_DOMAIN_POWER_ON) { ; } } } /* re-enable interrupts */ HwiP_restore(key); return (Power_SOK); } /* * ======== Power_setPolicy ======== * Set the Power policy function */ void Power_setPolicy(Power_PolicyFxn policy) { PowerCC26X2_module.policyFxn = policy; } /* * ======== Power_shutdown ======== */ int_fast16_t Power_shutdown(uint_fast16_t shutdownState, uint_fast32_t shutdownTime) { int_fast16_t status = Power_EFAIL; unsigned int constraints; unsigned int hwiKey; /* disable interrupts */ hwiKey = HwiP_disable(); /* check if there is a constraint to prohibit shutdown */ constraints = Power_getConstraintMask(); if (constraints & (1 << PowerCC26XX_DISALLOW_SHUTDOWN)) { status = Power_ECHANGE_NOT_ALLOWED; } /* OK to shutdown ... */ else if (PowerCC26X2_module.state == Power_ACTIVE) { /* set new transition state to entering shutdown */ PowerCC26X2_module.state = Power_ENTERING_SHUTDOWN; /* signal all clients registered for pre-shutdown notification */ status = notify(PowerCC26XX_ENTERING_SHUTDOWN); /* check for any error */ if (status != Power_SOK) { PowerCC26X2_module.state = Power_ACTIVE; HwiP_restore(hwiKey); return (status); } /* Ensure the JTAG domain is turned off * otherwise MCU domain can't be turned off. */ HWREG(AON_PMCTL_BASE + AON_PMCTL_O_JTAGCFG) = 0; SysCtrlAonSync(); /* now proceed with shutdown sequence ... */ SysCtrlShutdownWithAbort(); } else { status = Power_EBUSY; } /* NOTE: if shutdown succeeded, should never get here */ /* return failure status */ PowerCC26X2_module.state = Power_ACTIVE; /* re-enable interrupts */ HwiP_restore(hwiKey); /* if get here, failed to shutdown, return error code */ return (status); } /* * ======== Power_sleep ======== */ int_fast16_t Power_sleep(uint_fast16_t sleepState) { int_fast16_t status = Power_SOK; int_fast16_t notifyStatus = Power_SOK; int_fast16_t lateNotifyStatus = Power_SOK; unsigned int xosc_hf_active = false; uint_fast16_t postEventLate; uint32_t poweredDomains = 0; uint_fast16_t preEvent; uint_fast16_t postEvent; unsigned int constraints; bool retainCache = false; uint32_t modeVIMS; unsigned int swiKey; /* first validate the sleep code */ if (sleepState != PowerCC26XX_STANDBY) { status = Power_EINVALIDINPUT; } else { /* check to make sure Power is not busy with another transition */ if (PowerCC26X2_module.state == Power_ACTIVE) { /* set transition state to entering sleep */ PowerCC26X2_module.state = Power_ENTERING_SLEEP; } else { status = Power_EBUSY; } if (status == Power_SOK) { /* setup sleep vars */ preEvent = PowerCC26XX_ENTERING_STANDBY; postEvent = PowerCC26XX_AWAKE_STANDBY; postEventLate = PowerCC26XX_AWAKE_STANDBY_LATE; /* disable Task scheduling */ PowerCC26XX_schedulerDisable(); /* signal all clients registered for pre-sleep notification */ status = notify(preEvent); /* check for any error */ if (status != Power_SOK) { PowerCC26X2_module.state = Power_ACTIVE; PowerCC26XX_schedulerRestore(); return (status); } /* now disable Swi scheduling */ swiKey = SwiP_disable(); /* 1. Query and save domain states before powering them off */ if (Power_getDependencyCount(PowerCC26XX_DOMAIN_RFCORE)) { poweredDomains |= PRCM_DOMAIN_RFCORE; } if (Power_getDependencyCount(PowerCC26XX_DOMAIN_SERIAL)){ poweredDomains |= PRCM_DOMAIN_SERIAL; } if (Power_getDependencyCount(PowerCC26XX_DOMAIN_PERIPH)) { poweredDomains |= PRCM_DOMAIN_PERIPH; } /* 2. If XOSC_HF is active or we are waiting to switch * to it, force it off. Otherwise, the XOSC_HF may be * automatically turned on by the hardware without * a call to configureXOSCHF(PowerCC26XX_ENABLE) * This is not necessarily a problem. However exactly * what the cutoff point is where the hardware considers * the XOSC_HF "on" without having switched to is not * considered by this driver. */ if (OSCClockSourceGet(OSC_SRC_CLK_HF) == OSC_XOSC_HF || PowerCC26X2_module.xoscPending == true) { xosc_hf_active = true; configureXOSCHF(PowerCC26XX_DISABLE); } /* query constraints to determine if cache should be retained */ constraints = Power_getConstraintMask(); if (constraints & (1 << PowerCC26XX_RETAIN_VIMS_CACHE_IN_STANDBY)) { retainCache = true; } else { retainCache = false; // Get the current VIMS mode do { modeVIMS = VIMSModeGet(VIMS_BASE); } while (modeVIMS == VIMS_MODE_CHANGING); } /* 3. * - Freeze the IOs on the boundary between MCU and AON * - Make sure AON writes take effect * - Request power off of every PD in the MCU voltage domain * - Ensure that no clocks are forced on in Crypto, DMA and I2S * - Gate running deep sleep clocks for Crypto, DMA and I2S * - Load the new clock settings * - Configure the VIMS power domain mode to power up flash * again after coming out of standby. * - Request uLDO during standby * - Use recharge comparator * - Ensure all writes have taken effect * - Ensure UDMA, Crypto and I2C clocks are turned off * - Ensure all non-CPU power domains are turned off * - Turn off cache retention if requested * - Invoke deep sleep to go to standby */ SysCtrlStandby(retainCache, VIMS_ON_CPU_ON_MODE, SYSCTRL_PREFERRED_RECHARGE_MODE); /* 4. If didn't retain VIMS in standby, re-enable retention now */ if (retainCache == false) { /* 5.1 If previously in a cache mode, restore the mode now */ if (modeVIMS == VIMS_MODE_ENABLED) { VIMSModeSet(VIMS_BASE, modeVIMS); } /* 5.2 Re-enable retention */ PRCMCacheRetentionEnable(); } /* 6. Start re-powering power domains */ PRCMPowerDomainOn(poweredDomains); /* 7. Restore deep sleep clocks of Crypto and DMA */ if (Power_getDependencyCount(PowerCC26XX_PERIPH_CRYPTO)) { PRCMPeripheralDeepSleepEnable( resourceDB[PowerCC26XX_PERIPH_CRYPTO].driverlibID); } if (Power_getDependencyCount(PowerCC26XX_PERIPH_UDMA)) { PRCMPeripheralDeepSleepEnable( resourceDB[PowerCC26XX_PERIPH_UDMA].driverlibID); } /* 8. Make sure clock settings take effect */ PRCMLoadSet(); /* 9. Release request for uLDO */ PRCMMcuUldoConfigure(false); /* 10. Set transition state to EXITING_SLEEP */ PowerCC26X2_module.state = Power_EXITING_SLEEP; /* 11. Wait until all power domains are back on */ while (PRCMPowerDomainStatus(poweredDomains) != PRCM_DOMAIN_POWER_ON); /* 12. Wait for the RTC shadow values to be updated so that * the early notification callbacks can read out valid RTC values. * This can likely be removed as the 2MHz MF clock will have ticked by now. */ SysCtrlAonSync(); /* * 13. Signal clients registered for early post-sleep notification; * this should be used to initialize any timing critical or IO * dependent hardware */ notifyStatus = notify(postEvent); /* 14. Disable IO freeze and ensure RTC shadow value is updated */ AONIOCFreezeDisable(); SysCtrlAonSync(); /* 15. If XOSC_HF was forced off above, initiate switch back */ if (xosc_hf_active == true) { configureXOSCHF(PowerCC26XX_ENABLE); } /* 16. Re-enable interrupts */ CPUcpsie(); /* * 17. Signal all clients registered for late post-sleep * notification */ lateNotifyStatus = notify(postEventLate); /* * 18. Now clear the transition state before re-enabling * scheduler */ PowerCC26X2_module.state = Power_ACTIVE; /* Re-enable Swi scheduling */ SwiP_restore(swiKey); /* re-enable Task scheduling */ PowerCC26XX_schedulerRestore(); /* if there was a notification error, set return status */ if ((notifyStatus != Power_SOK) || (lateNotifyStatus != Power_SOK)) { status = Power_EFAIL; } } } return (status); } /* * ======== Power_unregisterNotify ======== * Unregister for a power notification. * */ void Power_unregisterNotify(Power_NotifyObj * pNotifyObj) { unsigned int key; /* remove notify object from its event queue */ key = HwiP_disable(); /* remove notify object from its event queue */ List_remove(&PowerCC26X2_module.notifyList, (List_Elem *)pNotifyObj); HwiP_restore(key); } /* ****************** CC26XX specific APIs ******************** */ /* * ======== PowerCC26XX_calibrate ======== * Plug this function into the PowerCC26X2_Config structure * if calibration is needed. */ bool PowerCC26XX_calibrate(unsigned int arg) { bool retVal = false; switch (arg) { case PowerCC26X2_INITIATE_CALIBRATE: retVal = PowerCC26X2_initiateCalibration(); break; case PowerCC26X2_DO_CALIBRATE: PowerCC26X2_calibrate(); break; default: while (1); } return (retVal); } /* * ======== PowerCC26XX_doWFI ======== */ void PowerCC26XX_doWFI(void) { __asm(" wfi"); } /* * ======== PowerCC26X2_getClockHandle ======== */ ClockP_Handle PowerCC26XX_getClockHandle() { return ((ClockP_Handle)&PowerCC26X2_module.clockObj); } /* * ======== PowerCC26XX_noCalibrate ======== * Plug this function into the PowerCC26X2 config structure if calibration * is not needed. */ bool PowerCC26XX_noCalibrate(unsigned int arg) { return (0); } /* * ======== PowerCC26XX_getXoscStartupTime ======== * Get the estimated crystal oscillator startup time */ uint32_t PowerCC26XX_getXoscStartupTime(uint32_t timeUntilWakeupInMs) { return (OSCHF_GetStartupTime(timeUntilWakeupInMs)); } /* * ======== PowerCC26X2_injectCalibration ======== * Explicitly trigger RCOSC calibration */ bool PowerCC26XX_injectCalibration(void) { if ((*(PowerCC26X2_config.calibrateFxn))(PowerCC26X2_INITIATE_CALIBRATE)) { /* here if AUX SMPH was available, start calibration now ... */ (*(PowerCC26X2_config.calibrateFxn))(PowerCC26X2_DO_CALIBRATE); return (true); } return (false); } /* * ======== PowerCC26XX_isStableXOSC_HF ======== * Check if XOSC_HF has stabilized. */ bool PowerCC26XX_isStableXOSC_HF(void) { bool ready = true; unsigned int key; key = HwiP_disable(); /* only query if HF source is ready if there is a pending change */ if (PowerCC26X2_module.xoscPending) { ready = OSCHfSourceReady(); } HwiP_restore(key); return (ready); } /* * ======== PowerCC26XX_switchXOSC_HF ======== * Switch to enable XOSC_HF. * May only be called when using the PowerCC26XX_SWITCH_XOSC_HF_MANUALLY * constraint. * May only be called after ensuring the XOSC_HF is stable by calling * PowerCC26XX_isStableXOSC_HF(). */ void PowerCC26XX_switchXOSC_HF(void) { bool readyToCal; unsigned int key; key = HwiP_disable(); /* Since PowerCC26X2_isStableXOSC_HF() should have been called before this * function, we can just switch without handling the case when the XOSC_HF * is not ready or PowerCC26X2_module.xoscPending is not true. */ OSCHF_AttemptToSwitchToXosc(); /* Since configureXOSCHF() was called prior to this function to turn * on the XOSC_HF, PowerCC26X2_module.xoscPending will be true and * we can safely set it to false. */ PowerCC26X2_module.xoscPending = false; /* Allow going into IDLE again since we sucessfully switched * to XOSC_HF */ Power_releaseConstraint(PowerCC26XX_DISALLOW_IDLE); HwiP_restore(key); /* initiate RCOSC calibration */ readyToCal = (*(PowerCC26X2_config.calibrateFxn))(PowerCC26X2_INITIATE_CALIBRATE); /* now notify clients that were waiting for a switch notification */ notify(PowerCC26XX_XOSC_HF_SWITCHED); /* if ready to start first cal measurment, do it now */ if (readyToCal == true) { (*(PowerCC26X2_config.calibrateFxn))(PowerCC26X2_DO_CALIBRATE); } } /* * * * * * * * * * * internal and support functions * * * * * * * * * * */ /* * ======== oscillatorISR ======== */ static void oscillatorISR(uintptr_t arg) { uint32_t rawStatus = HWREG(PRCM_BASE + PRCM_O_OSCRIS); uint32_t intStatusMask = HWREG(PRCM_BASE + PRCM_O_OSCIMSC); /* Turn off mask for all flags we will handle */ HWREG(PRCM_BASE + PRCM_O_OSCIMSC) = intStatusMask & ~rawStatus; /* XOSC_LF or RCOSC_LF qualified */ if (rawStatus & PRCM_OSCRIS_LFSRCDONERIS_M & intStatusMask) { disableLFClockQualifiers(); } /* XOSC_HF ready to switch to */ if (rawStatus & PRCM_OSCIMSC_HFSRCPENDIM_M & intStatusMask) { switchXOSCHF(); } /* Clear flags we will handle. Does not really work as expected as * the flags seem to level-detect and not edge-detect. Until the * underlying trigger is taken care of, the flag will not deassert * even when cleared. * We're clearing at the end in order to prevent the flag from * immediately asserting again if the underlying trigger was * not handled yet. * SCLK_LF switched can never be cleared after triggering * only masked out. XOSC_HF ready to switch can be cleared * after switching to XOSC_HF. */ HWREG(PRCM_BASE + PRCM_O_OSCICR) = intStatusMask & rawStatus; } /* * ======== emptyClockFunc ======== * Clock function used by power policy to schedule early wakeups. */ static void emptyClockFunc(uintptr_t arg) { } /* * ======== disableLFClockQualifiers ======== * Clock function used for delayed disable of LF clock qualifiers. */ static void disableLFClockQualifiers(void) { uint32_t sourceLF; /* query LF clock source */ sourceLF = OSCClockSourceGet(OSC_SRC_CLK_LF); /* is LF source either RCOSC_LF or XOSC_LF yet? */ if ((sourceLF == OSC_RCOSC_LF) || (sourceLF == OSC_XOSC_LF)) { /* yes, disable the LF clock qualifiers */ DDI16BitfieldWrite( AUX_DDI0_OSC_BASE, DDI_0_OSC_O_CTL0, DDI_0_OSC_CTL0_BYPASS_XOSC_LF_CLK_QUAL_M| DDI_0_OSC_CTL0_BYPASS_RCOSC_LF_CLK_QUAL_M, DDI_0_OSC_CTL0_BYPASS_RCOSC_LF_CLK_QUAL_S, 0x3 ); /* enable clock loss detection */ OSCClockLossEventEnable(); /* now finish by releasing the standby disallow constraint */ Power_releaseConstraint(PowerCC26XX_DISALLOW_STANDBY); } else if(sourceLF == OSC_XOSC_HF) { /* yes, disable the LF clock qualifiers */ DDI16BitfieldWrite( AUX_DDI0_OSC_BASE, DDI_0_OSC_O_CTL0, DDI_0_OSC_CTL0_BYPASS_XOSC_LF_CLK_QUAL_M| DDI_0_OSC_CTL0_BYPASS_RCOSC_LF_CLK_QUAL_M, DDI_0_OSC_CTL0_BYPASS_RCOSC_LF_CLK_QUAL_S, 0x3 ); /* enable clock loss detection */ OSCClockLossEventEnable(); /* do not allow standby since the LF clock is HF derived */ } } /* * ======== nopResourceFunc ======== * special resource handler */ static unsigned int nopResourceHandler(unsigned int action) { return (0); } /* * ======== notify ======== * Send notifications to registered clients. * Note: Task scheduling is disabled when this function is called. */ static int_fast16_t notify(uint_fast16_t eventType) { int_fast16_t notifyStatus; Power_NotifyFxn notifyFxn; uintptr_t clientArg; List_Elem *elem; /* if queue is empty, return immediately */ if (!List_empty(&PowerCC26X2_module.notifyList)) { /* point to first client notify object */ elem = List_head(&PowerCC26X2_module.notifyList); /* walk the queue and notify each registered client of the event */ do { if (((Power_NotifyObj *)elem)->eventTypes & eventType) { /* pull params from notify object */ notifyFxn = ((Power_NotifyObj *)elem)->notifyFxn; clientArg = ((Power_NotifyObj *)elem)->clientArg; /* call the client's notification function */ notifyStatus = (int_fast16_t)(*(Power_NotifyFxn)notifyFxn)( eventType, 0, clientArg); /* if client declared error stop all further notifications */ if (notifyStatus != Power_NOTIFYDONE) { return (Power_EFAIL); } } /* get next element in the notification queue */ elem = List_next(elem); } while (elem != NULL); } return (Power_SOK); } /* * ======== configureRFCoreClocks ======== * Special dependency function for controlling RF core clocks. */ static unsigned int configureRFCoreClocks(unsigned int action) { if (action == PowerCC26XX_ENABLE) { RFCClockEnable(); } else { RFCClockDisable(); } return (0); } /* * ======== switchXOSCHF ======== * Switching to XOSC_HF when it has stabilized. */ static void switchXOSCHF(void) { bool readyToCal; unsigned int key; key = HwiP_disable(); /* Switch to the XOSC_HF. Since this function is only called * after we get an interrupt signifying it is ready to switch, * it should always succeed. * If it does not succeed, try again. It is fine if we spin, * there is no sensible recovery mechanism from such an error. */ while (!OSCHF_AttemptToSwitchToXosc()); /* The only time we should get here is when PowerCC26X2_module.xoscPending == true * holds. * Allow going into IDLE again since we sucessfully switched * to XOSC_HF */ Power_releaseConstraint(PowerCC26XX_DISALLOW_IDLE); PowerCC26X2_module.xoscPending = false; HwiP_restore(key); /* initiate RCOSC calibration */ readyToCal = (*(PowerCC26X2_config.calibrateFxn))(PowerCC26X2_INITIATE_CALIBRATE); /* now notify clients that were waiting for a switch notification */ notify(PowerCC26XX_XOSC_HF_SWITCHED); /* if ready to start first cal measurment, do it now */ if (readyToCal == true) { (*(PowerCC26X2_config.calibrateFxn))(PowerCC26X2_DO_CALIBRATE); } } /* * ======== configureXOSCHF ======== */ static unsigned int configureXOSCHF(unsigned int action) { /* By checking action == PowerCC26XX_ENABLE and PowerCC26X2_module.xoscPending * carefully, the function should be idempotent. Calling it with the same * action more than once will not have any effect until the hardware triggers * a software state change. */ if (action == PowerCC26XX_ENABLE && OSCClockSourceGet(OSC_SRC_CLK_HF) != OSC_XOSC_HF && PowerCC26X2_module.xoscPending == false) { OSCHF_TurnOnXosc(); PowerCC26X2_module.xoscPending = true; /* Unless it is disallowed, unmask the XOSC_HF ready to switch flag */ if (!(Power_getConstraintMask() & (1 << PowerCC26XX_SWITCH_XOSC_HF_MANUALLY))) { /* Clearing the flag in the ISR does not always work. Clear it again just in case */ HWREG(PRCM_BASE + PRCM_O_OSCICR) = PRCM_OSCICR_HFSRCPENDC_M; /* Turn on oscillator interrupt for SCLK_HF switching */ HWREG(PRCM_BASE + PRCM_O_OSCIMSC) |= PRCM_OSCIMSC_HFSRCPENDIM_M; } /* If the device goes into IDLE in between turning on XOSC_HF and * and switching SCLK_HF to XOSC_HF, the INT_OSC_COMB HFSRCPEND * trigger will be suppressed. * The DISALLOW_IDLE constraint should only ever be set whenever * we transition from xoscPending == false to true. */ Power_setConstraint(PowerCC26XX_DISALLOW_IDLE); } /* when release XOSC_HF, auto switch to RCOSC_HF */ else if (action == PowerCC26XX_DISABLE) { OSCHF_SwitchToRcOscTurnOffXosc(); /* If we have not actually switched to XOSC_HF yet, we need to * undo what we did above when turning on XOSC_HF. Otherwise, * we may not balance the constraints correctly or get * unexpected interrupts. */ if (PowerCC26X2_module.xoscPending) { /* Remove HFSRCPEND from the OSC_COMB interrupt mask */ uint32_t oscMask = HWREG(PRCM_BASE + PRCM_O_OSCIMSC); HWREG(PRCM_BASE + PRCM_O_OSCIMSC) = oscMask & ~ PRCM_OSCIMSC_HFSRCPENDIM_M; /* Clear any residual trigger for HFSRCPEND */ HWREG(PRCM_BASE + PRCM_O_OSCICR) = PRCM_OSCICR_HFSRCPENDC; Power_releaseConstraint(PowerCC26XX_DISALLOW_IDLE); PowerCC26X2_module.xoscPending = false; } } return (0); }