| /****************************************************************************** |
| * Filename: osc.c |
| * Revised: 2016-05-27 10:06:10 +0200 (Fri, 27 May 2016) |
| * Revision: 46521 |
| * |
| * Description: Driver for setting up the system Oscillators |
| * |
| * Copyright (c) 2015 - 2016, 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: |
| * |
| * 1) Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * 2) 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. |
| * |
| * 3) Neither the name of the ORGANIZATION 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 HOLDER 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. |
| * |
| ******************************************************************************/ |
| |
| #include <inc/hw_types.h> |
| #include <inc/hw_ccfg.h> |
| #include <inc/hw_fcfg1.h> |
| #include <driverlib/aon_batmon.h> |
| #include <driverlib/aon_rtc.h> |
| #include <driverlib/osc.h> |
| |
| //***************************************************************************** |
| // |
| // Handle support for DriverLib in ROM: |
| // This section will undo prototype renaming made in the header file |
| // |
| //***************************************************************************** |
| #if !defined(DOXYGEN) |
| #undef OSCClockSourceSet |
| #define OSCClockSourceSet NOROM_OSCClockSourceSet |
| #undef OSCClockSourceGet |
| #define OSCClockSourceGet NOROM_OSCClockSourceGet |
| #undef OSCHF_GetStartupTime |
| #define OSCHF_GetStartupTime NOROM_OSCHF_GetStartupTime |
| #undef OSCHF_TurnOnXosc |
| #define OSCHF_TurnOnXosc NOROM_OSCHF_TurnOnXosc |
| #undef OSCHF_AttemptToSwitchToXosc |
| #define OSCHF_AttemptToSwitchToXosc NOROM_OSCHF_AttemptToSwitchToXosc |
| #undef OSCHF_SwitchToRcOscTurnOffXosc |
| #define OSCHF_SwitchToRcOscTurnOffXosc NOROM_OSCHF_SwitchToRcOscTurnOffXosc |
| #undef OSCHF_DebugGetCrystalAmplitude |
| #define OSCHF_DebugGetCrystalAmplitude NOROM_OSCHF_DebugGetCrystalAmplitude |
| #undef OSCHF_DebugGetExpectedAverageCrystalAmplitude |
| #define OSCHF_DebugGetExpectedAverageCrystalAmplitude NOROM_OSCHF_DebugGetExpectedAverageCrystalAmplitude |
| #undef OSC_HPOSCRelativeFrequencyOffsetGet |
| #define OSC_HPOSCRelativeFrequencyOffsetGet NOROM_OSC_HPOSCRelativeFrequencyOffsetGet |
| #undef OSC_HPOSCRelativeFrequencyOffsetToRFCoreFormatConvert |
| #define OSC_HPOSCRelativeFrequencyOffsetToRFCoreFormatConvert NOROM_OSC_HPOSCRelativeFrequencyOffsetToRFCoreFormatConvert |
| #endif |
| |
| //***************************************************************************** |
| // |
| // OSCHF switch time calculator defines and globals |
| // |
| //***************************************************************************** |
| |
| #define RTC_CV_TO_MS(x) (( 1000 * ( x )) >> 16 ) |
| #define RTC_CV_TO_US(x) (( 1000000 * ( x )) >> 16 ) |
| |
| typedef struct { |
| uint32_t previousStartupTimeInUs ; |
| uint32_t timeXoscOff_CV ; |
| uint32_t timeXoscOn_CV ; |
| uint32_t timeXoscStable_CV ; |
| int32_t tempXoscOff ; |
| } OscHfGlobals_t; |
| |
| static OscHfGlobals_t oscHfGlobals; |
| |
| |
| //***************************************************************************** |
| // |
| // Configure the oscillator input to the a source clock. |
| // |
| //***************************************************************************** |
| void |
| OSCClockSourceSet(uint32_t ui32SrcClk, uint32_t ui32Osc) |
| { |
| // |
| // Check the arguments. |
| // |
| ASSERT((ui32SrcClk & OSC_SRC_CLK_LF) || |
| (ui32SrcClk & OSC_SRC_CLK_MF) || |
| (ui32SrcClk & OSC_SRC_CLK_HF)); |
| ASSERT((ui32Osc == OSC_RCOSC_HF) || |
| (ui32Osc == OSC_RCOSC_LF) || |
| (ui32Osc == OSC_XOSC_HF) || |
| (ui32Osc == OSC_XOSC_LF)); |
| |
| // |
| // Request the high frequency source clock (using 24 MHz XTAL) |
| // |
| if(ui32SrcClk & OSC_SRC_CLK_HF) |
| { |
| // |
| // Enable the HF XTAL as HF clock source |
| // |
| DDI16BitfieldWrite(AUX_DDI0_OSC_BASE, DDI_0_OSC_O_CTL0, |
| DDI_0_OSC_CTL0_SCLK_HF_SRC_SEL_M, |
| DDI_0_OSC_CTL0_SCLK_HF_SRC_SEL_S, |
| ui32Osc); |
| } |
| |
| // |
| // Configure the medium frequency source clock |
| // |
| if(ui32SrcClk & OSC_SRC_CLK_MF) |
| { |
| DDI16BitfieldWrite(AUX_DDI0_OSC_BASE, DDI_0_OSC_O_CTL0, |
| DDI_0_OSC_CTL0_SCLK_MF_SRC_SEL_M, |
| DDI_0_OSC_CTL0_SCLK_MF_SRC_SEL_S, |
| ui32Osc); |
| } |
| |
| // |
| // Configure the low frequency source clock. |
| // |
| if(ui32SrcClk & OSC_SRC_CLK_LF) |
| { |
| // |
| // Change the clock source. |
| // |
| DDI16BitfieldWrite(AUX_DDI0_OSC_BASE, DDI_0_OSC_O_CTL0, |
| DDI_0_OSC_CTL0_SCLK_LF_SRC_SEL_M, |
| DDI_0_OSC_CTL0_SCLK_LF_SRC_SEL_S, |
| ui32Osc); |
| } |
| } |
| |
| //***************************************************************************** |
| // |
| // Get the source clock settings |
| // |
| //***************************************************************************** |
| uint32_t |
| OSCClockSourceGet(uint32_t ui32SrcClk) |
| { |
| uint32_t ui32ClockSource; |
| |
| // |
| // Check the arguments. |
| // |
| ASSERT((ui32SrcClk & OSC_SRC_CLK_LF) || |
| (ui32SrcClk & OSC_SRC_CLK_HF)); |
| |
| // |
| // Return the source for the selected clock. |
| // |
| if(ui32SrcClk == OSC_SRC_CLK_LF) |
| { |
| ui32ClockSource = DDI16BitfieldRead(AUX_DDI0_OSC_BASE, DDI_0_OSC_O_STAT0, |
| DDI_0_OSC_STAT0_SCLK_LF_SRC_M, |
| DDI_0_OSC_STAT0_SCLK_LF_SRC_S); |
| } |
| else |
| { |
| ui32ClockSource = DDI16BitfieldRead(AUX_DDI0_OSC_BASE, DDI_0_OSC_O_STAT0, |
| DDI_0_OSC_STAT0_SCLK_HF_SRC_M, |
| DDI_0_OSC_STAT0_SCLK_HF_SRC_S); |
| } |
| return (ui32ClockSource); |
| } |
| |
| //***************************************************************************** |
| // |
| // Returns maximum startup time (in microseconds) of XOSC_HF |
| // |
| //***************************************************************************** |
| uint32_t |
| OSCHF_GetStartupTime( uint32_t timeUntilWakeupInMs ) |
| { |
| uint32_t deltaTimeSinceXoscOnInMs ; |
| int32_t deltaTempSinceXoscOn ; |
| uint32_t newStartupTimeInUs ; |
| |
| deltaTimeSinceXoscOnInMs = RTC_CV_TO_MS( AONRTCCurrentCompareValueGet() - oscHfGlobals.timeXoscOn_CV ); |
| deltaTempSinceXoscOn = AONBatMonTemperatureGetDegC() - oscHfGlobals.tempXoscOff; |
| |
| if ( deltaTempSinceXoscOn < 0 ) { |
| deltaTempSinceXoscOn = -deltaTempSinceXoscOn; |
| } |
| |
| if ( (( timeUntilWakeupInMs + deltaTimeSinceXoscOnInMs ) > 3000 ) || |
| ( deltaTempSinceXoscOn > 5 ) || |
| ( oscHfGlobals.timeXoscStable_CV < oscHfGlobals.timeXoscOn_CV ) || |
| ( oscHfGlobals.previousStartupTimeInUs == 0 ) ) |
| { |
| newStartupTimeInUs = 2000; |
| if (( HWREG( CCFG_BASE + CCFG_O_SIZE_AND_DIS_FLAGS ) & CCFG_SIZE_AND_DIS_FLAGS_DIS_XOSC_OVR_M ) == 0 ) { |
| newStartupTimeInUs = (( HWREG( CCFG_BASE + CCFG_O_MODE_CONF_1 ) & |
| CCFG_MODE_CONF_1_XOSC_MAX_START_M ) >> |
| CCFG_MODE_CONF_1_XOSC_MAX_START_S ) * 125; |
| // Note: CCFG startup time is "in units of 100us" adding 25% margin results in *125 |
| } |
| } else { |
| newStartupTimeInUs = RTC_CV_TO_US( oscHfGlobals.timeXoscStable_CV - oscHfGlobals.timeXoscOn_CV ); |
| newStartupTimeInUs += ( newStartupTimeInUs >> 2 ); // Add 25 percent margin |
| if ( newStartupTimeInUs < oscHfGlobals.previousStartupTimeInUs ) { |
| newStartupTimeInUs = oscHfGlobals.previousStartupTimeInUs; |
| } |
| } |
| |
| if ( newStartupTimeInUs < 200 ) { |
| newStartupTimeInUs = 200; |
| } |
| if ( newStartupTimeInUs > 4000 ) { |
| newStartupTimeInUs = 4000; |
| } |
| return ( newStartupTimeInUs ); |
| } |
| |
| |
| //***************************************************************************** |
| // |
| // Turns on XOSC_HF (but without switching to XOSC_HF) |
| // |
| //***************************************************************************** |
| void |
| OSCHF_TurnOnXosc( void ) |
| { |
| OSCClockSourceSet( OSC_SRC_CLK_HF | OSC_SRC_CLK_MF, OSC_XOSC_HF ); |
| oscHfGlobals.timeXoscOn_CV = AONRTCCurrentCompareValueGet(); |
| } |
| |
| |
| //***************************************************************************** |
| // |
| // Switch to XOSC_HF if XOSC_HF is ready. |
| // |
| //***************************************************************************** |
| bool |
| OSCHF_AttemptToSwitchToXosc( void ) |
| { |
| uint32_t startupTimeInUs; |
| uint32_t prevLimmit25InUs; |
| |
| if ( OSCClockSourceGet( OSC_SRC_CLK_HF ) == OSC_XOSC_HF ) { |
| // Already on XOSC - nothing to do |
| return ( 1 ); |
| } |
| if ( OSCHfSourceReady()) { |
| OSCHfSourceSwitch(); |
| |
| // |
| // Store startup time, but limit to 25 percent reduction each time. |
| // |
| oscHfGlobals.timeXoscStable_CV = AONRTCCurrentCompareValueGet(); |
| startupTimeInUs = RTC_CV_TO_US( oscHfGlobals.timeXoscStable_CV - oscHfGlobals.timeXoscOn_CV ); |
| prevLimmit25InUs = oscHfGlobals.previousStartupTimeInUs; |
| prevLimmit25InUs -= ( prevLimmit25InUs >> 2 ); // 25 percent margin |
| oscHfGlobals.previousStartupTimeInUs = startupTimeInUs; |
| if ( prevLimmit25InUs > startupTimeInUs ) { |
| oscHfGlobals.previousStartupTimeInUs = prevLimmit25InUs; |
| } |
| return ( 1 ); |
| } |
| return ( 0 ); |
| } |
| |
| |
| //***************************************************************************** |
| // |
| // Switch to RCOSC_HF and turn off XOSC_HF |
| // |
| //***************************************************************************** |
| void |
| OSCHF_SwitchToRcOscTurnOffXosc( void ) |
| { |
| // |
| // Set SCLK_HF and SCLK_MF to RCOSC_HF without checking |
| // Doing this anyway to keep HF and MF in sync |
| // |
| OSCClockSourceSet( OSC_SRC_CLK_HF | OSC_SRC_CLK_MF, OSC_RCOSC_HF ); |
| |
| // |
| // Do the switching if not already running on RCOSC_HF |
| // |
| if ( OSCClockSourceGet( OSC_SRC_CLK_HF ) != OSC_RCOSC_HF ) { |
| OSCHfSourceSwitch(); |
| } |
| |
| oscHfGlobals.timeXoscOff_CV = AONRTCCurrentCompareValueGet(); |
| oscHfGlobals.tempXoscOff = AONBatMonTemperatureGetDegC(); |
| } |
| |
| //***************************************************************************** |
| // |
| // Calculate the temperature dependent relative frequency offset of HPOSC |
| // |
| //***************************************************************************** |
| int32_t |
| OSC_HPOSCRelativeFrequencyOffsetGet( int32_t tempDegC ) |
| { |
| // Estimate HPOSC frequency, using temperature and curve fitting parameters |
| uint32_t fitParams = HWREG(FCFG1_BASE + FCFG1_O_FREQ_OFFSET); |
| // Extract the P0,P1,P2 params, and sign extend them via shifting up/down |
| int32_t paramP0 = ((((int32_t) fitParams) << (32 - FCFG1_FREQ_OFFSET_HPOSC_COMP_P0_W - FCFG1_FREQ_OFFSET_HPOSC_COMP_P0_S)) |
| >> (32 - FCFG1_FREQ_OFFSET_HPOSC_COMP_P0_W)); |
| int32_t paramP1 = ((((int32_t) fitParams) << (32 - FCFG1_FREQ_OFFSET_HPOSC_COMP_P1_W - FCFG1_FREQ_OFFSET_HPOSC_COMP_P1_S)) |
| >> (32 - FCFG1_FREQ_OFFSET_HPOSC_COMP_P1_W)); |
| int32_t paramP2 = ((((int32_t) fitParams) << (32 - FCFG1_FREQ_OFFSET_HPOSC_COMP_P2_W - FCFG1_FREQ_OFFSET_HPOSC_COMP_P2_S)) |
| >> (32 - FCFG1_FREQ_OFFSET_HPOSC_COMP_P2_W)); |
| int32_t paramP3 = ((((int32_t) HWREG(FCFG1_BASE + FCFG1_O_MISC_CONF_2)) |
| << (32 - FCFG1_MISC_CONF_2_HPOSC_COMP_P3_W - FCFG1_MISC_CONF_2_HPOSC_COMP_P3_S)) |
| >> (32 - FCFG1_MISC_CONF_2_HPOSC_COMP_P3_W)); |
| |
| // Now we can find the HPOSC freq offset, given as a signed variable d, expressed by: |
| // |
| // F_HPOSC = F_nom * (1 + d/(2^22)) , where: F_HPOSC = HPOSC frequency |
| // F_nom = nominal clock source frequency (e.g. 48.000 MHz) |
| // d = describes relative freq offset |
| |
| // We can estimate the d variable, using temperature compensation parameters: |
| // |
| // d = P0 + P1*(t - T0) + P2*(t - T0)^2 + P3*(t - T0)^3, where: P0,P1,P2,P3 are curve fitting parameters from FCFG1 |
| // t = current temperature (from temp sensor) in deg C |
| // T0 = 27 deg C (fixed temperature constant) |
| int32_t tempDelta = (tempDegC - 27); |
| int32_t tempDeltaX2 = tempDelta * tempDelta; |
| int32_t d = paramP0 + ((tempDelta*paramP1)>>3) + ((tempDeltaX2*paramP2)>>10) + ((tempDeltaX2*tempDelta*paramP3)>>18); |
| |
| return ( d ); |
| } |
| |
| //***************************************************************************** |
| // |
| // Converts the relative frequency offset of HPOSC to the RF Core parameter format. |
| // |
| //***************************************************************************** |
| int16_t |
| OSC_HPOSCRelativeFrequencyOffsetToRFCoreFormatConvert( int32_t HPOSC_RelFreqOffset ) |
| { |
| // The input argument, hereby referred to simply as "d", describes the frequency offset |
| // of the HPOSC relative to the nominal frequency in this way: |
| // |
| // F_HPOSC = F_nom * (1 + d/(2^22)) |
| // |
| // But for use by the radio, to compensate the frequency error, we need to find the |
| // frequency offset "rfcFreqOffset" defined in the following format: |
| // |
| // F_nom = F_HPOSC * (1 + rfCoreFreqOffset/(2^22)) |
| // |
| // To derive "rfCoreFreqOffset" from "d" we combine the two above equations and get: |
| // |
| // (1 + rfCoreFreqOffset/(2^22)) = (1 + d/(2^22))^-1 |
| // |
| // Which can be rewritten into: |
| // |
| // rfCoreFreqOffset = -d*(2^22) / ((2^22) + d) |
| // |
| // = -d * [ 1 / (1 + d/(2^22)) ] |
| // |
| // To avoid doing a 64-bit division due to the (1 + d/(2^22))^-1 expression, |
| // we can use Taylor series (Maclaurin series) to approximate it: |
| // |
| // 1 / (1 - x) ~= 1 + x + x^2 + x^3 + x^4 + ... etc (Maclaurin series) |
| // |
| // In our case, we have x = - d/(2^22), and we only include up to the first |
| // order term of the series, as the second order term ((d^2)/(2^44)) is very small: |
| // |
| // freqError ~= -d + d^2/(2^22) (+ small approximation error) |
| // |
| // The approximation error is negligible for our use. |
| |
| int32_t rfCoreFreqOffset = -HPOSC_RelFreqOffset + (( HPOSC_RelFreqOffset * HPOSC_RelFreqOffset ) >> 22 ); |
| |
| return ( rfCoreFreqOffset ); |
| } |
| |
| //***************************************************************************** |
| // |
| // Get crystal amplitude (assuming crystal is running). |
| // |
| //***************************************************************************** |
| uint32_t |
| OSCHF_DebugGetCrystalAmplitude( void ) |
| { |
| uint32_t oscCfgRegCopy ; |
| uint32_t startTime ; |
| uint32_t deltaTime ; |
| uint32_t ampValue ; |
| |
| // |
| // The specified method is as follows: |
| // 1. Set minimum interval between oscillator amplitude calibrations. |
| // (Done by setting PER_M=0 and PER_E=1) |
| // 2. Wait approximately 4 milliseconds in order to measure over a |
| // moderately large number of calibrations. |
| // 3. Read out the crystal amplitude value from the peek detector. |
| // 4. Restore original oscillator amplitude calibrations interval. |
| // 5. Return crystal amplitude value converted to millivolt. |
| // |
| oscCfgRegCopy = HWREG( AON_WUC_BASE + AON_WUC_O_OSCCFG ); |
| HWREG( AON_WUC_BASE + AON_WUC_O_OSCCFG ) = ( 1 << AON_WUC_OSCCFG_PER_E_S ); |
| startTime = AONRTCCurrentCompareValueGet(); |
| do { |
| deltaTime = AONRTCCurrentCompareValueGet() - startTime; |
| } while ( deltaTime < ((uint32_t)( 0.004 * FACTOR_SEC_TO_COMP_VAL_FORMAT ))); |
| ampValue = ( HWREG( AUX_DDI0_OSC_BASE + DDI_0_OSC_O_STAT1 ) & |
| DDI_0_OSC_STAT1_HPM_UPDATE_AMP_M ) >> |
| DDI_0_OSC_STAT1_HPM_UPDATE_AMP_S ; |
| HWREG( AON_WUC_BASE + AON_WUC_O_OSCCFG ) = oscCfgRegCopy; |
| |
| return ( ampValue * 15 ); |
| } |
| |
| //***************************************************************************** |
| // |
| // Get the expected average crystal amplitude. |
| // |
| //***************************************************************************** |
| uint32_t |
| OSCHF_DebugGetExpectedAverageCrystalAmplitude( void ) |
| { |
| uint32_t ampCompTh1 ; |
| uint32_t highThreshold ; |
| uint32_t lowThreshold ; |
| |
| ampCompTh1 = HWREG( AUX_DDI0_OSC_BASE + DDI_0_OSC_O_AMPCOMPTH1 ); |
| highThreshold = ( ampCompTh1 & DDI_0_OSC_AMPCOMPTH1_HPMRAMP3_HTH_M ) >> |
| DDI_0_OSC_AMPCOMPTH1_HPMRAMP3_HTH_S ; |
| lowThreshold = ( ampCompTh1 & DDI_0_OSC_AMPCOMPTH1_HPMRAMP3_LTH_M ) >> |
| DDI_0_OSC_AMPCOMPTH1_HPMRAMP3_LTH_S ; |
| |
| return ((( highThreshold + lowThreshold ) * 15 ) >> 1 ); |
| } |