-
Home
-
doc
-
support
-
driver
-
sysclock
sysclock_dspic33c.c
View on Github
/**
* @file sysclock_dspic33c.c
* @author Sebastien CAUX (sebcaux)
* @copyright UniSwarm 2018-2023
*
* @date May 28, 2018, 02:28 PM
*
* @brief System clock support for udevkit for dsPIC33CH, dsPIC33CK
*
* Implementation based on Microchip documents DS70005255B and DS2070005319B :
* http://ww1.microchip.com/downloads/en/DeviceDoc/dsPIC33-PIC24-FRM-Oscillator-Module-with-High-Speed-PLL-70005255b.pdf
* http://ww1.microchip.com/downloads/en/DeviceDoc/dsPIC33CH128MP5%20Datasheet%2070005319b.pdf
*/
#include "sysclock.h"
#include <archi.h>
#include <board.h>
#include <stdio.h>
static uint32_t _sysclock_sysfreq = 0;
static uint32_t _sysclock_posc = 0;
static uint32_t _sysclock_pllo = 0;
static uint32_t _sysclock_vco = 0;
static uint32_t _sysclock_apllo = 0;
static uint32_t _sysclock_avco = 0;
/**
* @brief Gets the actual frequency on a particular peripheral bus clock
* @param busClock id of the bus clock (1 periph bus clock), 0 for sysclock
* @return bus frequency in Hz, 1 in case of error to not cause divide by 0
*/
uint32_t sysclock_periphFreq(SYSCLOCK_CLOCK busClock)
{
if (_sysclock_sysfreq == 0)
{
_sysclock_sysfreq = sysclock_sourceFreq(sysclock_source());
}
switch (busClock)
{
case SYSCLOCK_CLOCK_FOSC:
return _sysclock_sysfreq;
case SYSCLOCK_CLOCK_PBCLK:
return _sysclock_sysfreq >> 1;
case SYSCLOCK_CLOCK_REFCLK:
{
uint16_t divisor = REFOCONHbits.RODIV << 1;
return (_sysclock_sysfreq >> 1) / divisor;
}
case SYSCLOCK_CLOCK_SYSCLK:
{
uint16_t divisor = 1;
if (CLKDIVbits.DOZEN == 1)
{
divisor = 1 << (CLKDIVbits.DOZE);
}
return (_sysclock_sysfreq >> 1) / divisor;
}
case SYSCLOCK_CLOCK_FRC:
return sysclock_sourceFreq(SYSCLOCK_SRC_FRC);
case SYSCLOCK_CLOCK_FPLLO:
return _sysclock_pllo;
case SYSCLOCK_CLOCK_VCO:
return _sysclock_vco;
case SYSCLOCK_CLOCK_VCO2:
return _sysclock_vco >> 1;
case SYSCLOCK_CLOCK_VCO3:
return _sysclock_vco / 3;
case SYSCLOCK_CLOCK_VCO4:
return _sysclock_vco >> 2;
case SYSCLOCK_CLOCK_AFPLLO:
return _sysclock_apllo;
case SYSCLOCK_CLOCK_AVCO:
return _sysclock_avco;
case SYSCLOCK_CLOCK_AVCO2:
return _sysclock_avco >> 1;
case SYSCLOCK_CLOCK_AVCO3:
return _sysclock_avco / 3;
case SYSCLOCK_CLOCK_AVCO4:
return _sysclock_avco >> 2;
}
return 1;
}
/**
* @brief Change the divisor of the busClock given as argument.
* @param busClock id of the bus clock or ref clock (SYSCLOCK_CLOCK_REFCLK or
* SYSCLOCK_CLOCK_PBCLK, SYSCLOCK_CLOCK_FRC)
* @param div divisor to set
* @return 0 if ok, -1 in case of error
*/
int sysclock_setClockDiv(SYSCLOCK_CLOCK busClock, uint16_t divisor)
{
uint16_t udivisor = 0;
if (busClock == SYSCLOCK_CLOCK_FRC)
{
if (divisor > 256)
{
return -1;
}
if (divisor == 256)
{
udivisor = 8;
}
else
{
for (udivisor = 0; divisor != 0; udivisor++)
{
divisor >>= 1;
}
}
udivisor -= 1;
CLKDIVbits.FRCDIV = udivisor;
return 0;
}
if (busClock == SYSCLOCK_CLOCK_REFCLK)
{
udivisor >>= 1;
REFOCONHbits.RODIV = udivisor;
return 0;
}
if (busClock == SYSCLOCK_CLOCK_PBCLK)
{
if (divisor == 1)
{
CLKDIVbits.DOZEN = 0;
}
else
{
if (divisor > 128)
{
return -1;
}
for (udivisor = 0; divisor != 0; udivisor++)
{
divisor >>= 1;
}
udivisor -= 1;
CLKDIVbits.DOZE = udivisor;
CLKDIVbits.DOZEN = 1;
}
return 0;
}
return -1; // bad index
}
/**
* @brief Return the actual frequency of the clock source
* @param source clock id to request
* @return frequency of 'source' clock, 0 in case of disabled clock, -1 in case of error
*/
int32_t sysclock_sourceFreq(SYSCLOCK_SOURCE source)
{
int32_t freq = -1;
uint16_t divisor;
switch (source)
{
case SYSCLOCK_SRC_LPRC:
freq = LPRC_BASE_FREQ; // 32kHz LPRC
break;
case SYSCLOCK_SRC_POSC:
freq = _sysclock_posc; // external primary oscillator
break;
case SYSCLOCK_SRC_PPLL:
case SYSCLOCK_SRC_FRCPLL:
freq = _sysclock_pllo >> 1; // primary oscillator with PLL
break;
case SYSCLOCK_SRC_BFRC:
freq = BFRC_BASE_FREQ; // Backup FRC
break;
case SYSCLOCK_SRC_FRC:
case SYSCLOCK_SRC_FRCDIV:
#ifdef SYSCLOCK_NO_OSCTUNE
freq = FRC_BASE_FREQ;
#else
{
int32_t osctune = OSCTUN;
if (osctune >= 32)
{
osctune = (osctune | 0xFFFFFFE0);
}
freq = FRC_BASE_FREQ + osctune * OSCTUN_D; // FRC
}
#endif
if (source == SYSCLOCK_SRC_FRCDIV)
{
divisor = CLKDIVbits.FRCDIV;
if (divisor != 0b111)
{
divisor = 1 << divisor;
}
else
{
divisor = 256;
}
freq = freq / divisor; // FRC / divisor
}
break;
}
return freq;
}
/**
* @brief Change a frequency of a source if it can be modified
* @param source clock id to change
* @return 0 in case of success, -1 in case of error
*/
int sysclock_setSourceFreq(SYSCLOCK_SOURCE source, uint32_t freq)
{
if (source == SYSCLOCK_SRC_POSC)
{
_sysclock_posc = freq;
return 0;
}
return -1;
}
/**
* @brief Return the actual clock source for system clock
* @return SYSCLOCK_SOURCE enum corresponding to actual clock source
*/
SYSCLOCK_SOURCE sysclock_source(void)
{
SYSCLOCK_SOURCE source = (SYSCLOCK_SOURCE)OSCCONbits.COSC;
return source;
}
/**
* @brief Switch the source clock of sysclock to another one and wait for the change effective
* @param source id to switch to
* @return 0 if ok, -1 in case of error
*/
int sysclock_switchSourceTo(SYSCLOCK_SOURCE source)
{
if (OSCCONbits.CLKLOCK == 1)
{
return -1; // Clocks and PLL are locked, source cannot be changed
}
// disable interrupts
disable_interrupt();
// unlock clock config (OSCCON is write protected)
// unlockClockConfig();
// select the new source
__builtin_write_OSCCONH(source); // primariry osc input
// trigger change
__builtin_write_OSCCONL(OSCCON | 0x01);
nop();
nop();
// relock clock config
// lockClockConfig();
while (OSCCONbits.OSWEN == 1)
{
nop();
}
if (sysclock_source() != source)
{
return -3; // Error when switch clock source
}
if (source == SYSCLOCK_SRC_FRCPLL || source == SYSCLOCK_SRC_PPLL)
{
// Wait for PLL to lock
while (OSCCONbits.LOCK != 1)
{
;
}
_sysclock_pllo = sysclock_getPLLClock();
}
_sysclock_sysfreq = sysclock_sourceFreq(source);
// enable interrupts
enable_interrupt();
return 0;
}
/**
* @brief Sets the system clock of the CPU, the system clock may be different of CPU
* clock and peripheral clock
* @param fosc desirate system frequency in Hz
* @return 0 if ok, -1 in case of error
*/
int sysclock_setClock(uint32_t fosc)
{
if (_sysclock_posc == 0)
{
#ifndef SYSCLOCK_NO_OSCTUNE
OSCTUN = 0; // ==> Fin = 8 MHz Internal clock
#endif
return sysclock_setPLLClock(fosc, SYSCLOCK_SRC_FRC);
}
else
{
return sysclock_setPLLClock(fosc, SYSCLOCK_SRC_POSC);
}
}
/**
* @brief Internal function to set clock with PLL from Primary oscilator (POSC) or FRC
* @param fosc desirate system frequency in Hz
* @param src input source clock of PLL (SYSCLOCK_SRC_FRC or SYSCLOCK_SRC_POSC)
* @return 0 if ok, -1 in case of error
*/
int sysclock_setPLLClock(uint32_t fosc, uint8_t src)
{
if (src != SYSCLOCK_SRC_FRC && src != SYSCLOCK_SRC_POSC)
{
return -4;
}
if (fosc > SYSCLOCK_FOSC_MAX)
{
return -1; // cannot generate fosc > SYSCLOCK_FOSC_MAX
}
if (fosc < SYSCLOCK_FSYS_MIN / 8)
{
return -1; // cannot generate fosc < SYSCLOCK_FSYS_MIN / 8
}
uint32_t fin = sysclock_sourceFreq(src);
if (fin == 0)
{
return -1;
}
if (fin < SYSCLOCK_FPLLI_MIN)
{
return -2;
}
uint16_t prediv = fin / SYSCLOCK_FPLLI_MIN;
uint32_t fplli = fin / prediv;
if (fplli > SYSCLOCK_FPLLI_MAX)
{
return -2;
}
uint8_t postdiv1 = 0, postdiv2 = 0;
uint16_t multiplier = 0;
int32_t error = 0x7FFFFFFF;
for (uint8_t mpostdiv1 = SYSCLOCK_POSTDIV1_MIN; mpostdiv1 <= SYSCLOCK_POSTDIV1_MAX; mpostdiv1++)
{
for (uint16_t mpostdiv2 = SYSCLOCK_POSTDIV2_MIN; mpostdiv2 <= mpostdiv1; mpostdiv2++)
{
uint16_t mmultiplier = (fosc * mpostdiv1 * mpostdiv2) / fplli;
if (mmultiplier < SYSCLOCK_PLLM_MIN || mmultiplier > SYSCLOCK_PLLM_MAX)
{
continue;
}
uint32_t fvco = fplli * mmultiplier;
if (fvco < SYSCLOCK_FVCO_MIN || fvco > SYSCLOCK_FVCO_MAX)
{
continue;
}
int32_t merror = fvco / (mpostdiv1 * mpostdiv2) - fosc;
if (merror < 0)
{
merror = -merror;
}
if (merror < error && mmultiplier <= SYSCLOCK_PLLM_MAX && mmultiplier >= SYSCLOCK_PLLM_MIN)
{
multiplier = mmultiplier;
error = merror;
postdiv1 = mpostdiv1;
postdiv2 = mpostdiv2;
if (merror == 0)
{
mpostdiv1 = SYSCLOCK_POSTDIV1_MAX + 1;
break;
}
}
}
}
if (multiplier == 0)
{
return -5;
}
CLKDIVbits.PLLPRE = prediv;
PLLFBDbits.PLLFBDIV = multiplier;
PLLDIVbits.POST1DIV = postdiv1;
PLLDIVbits.POST2DIV = postdiv2;
if (src == SYSCLOCK_SRC_FRC)
{
__builtin_write_OSCCONH(SYSCLOCK_SRC_FRCPLL); // frc input
}
else
{
__builtin_write_OSCCONH(SYSCLOCK_SRC_PPLL); // primary osc input
}
__builtin_write_OSCCONL(OSCCON | 0x01);
// Wait for Clock switch to occur
while (OSCCONbits.OSWEN != 0)
{
;
}
// Wait for PLL to lock
while (OSCCONbits.LOCK != 1)
{
;
}
_sysclock_pllo = sysclock_getPLLClock();
_sysclock_sysfreq = _sysclock_pllo >> 1;
return 0;
}
uint32_t sysclock_getPLLClock(void)
{
uint32_t fin;
if (OSCCONbits.COSC == SYSCLOCK_SRC_FRCPLL) // FRC as input
{
fin = sysclock_sourceFreq(SYSCLOCK_SRC_FRC);
}
else if (OSCCONbits.COSC == SYSCLOCK_SRC_PPLL) // POSC as input
{
fin = sysclock_sourceFreq(SYSCLOCK_SRC_POSC);
}
else
{
return 0;
}
uint16_t prediv = CLKDIVbits.PLLPRE;
uint16_t multiplier = PLLFBDbits.PLLFBDIV;
uint16_t postdiv = (PLLDIVbits.POST1DIV) * (PLLDIVbits.POST2DIV);
_sysclock_vco = fin / prediv * multiplier;
uint32_t fpllo = _sysclock_vco / postdiv;
return fpllo;
}
uint32_t sysclock_getAPLLClock(void)
{
uint32_t fin;
if (ACLKCON1bits.FRCSEL == 1) // FRC as input
{
fin = sysclock_sourceFreq(SYSCLOCK_SRC_FRC);
}
else // POSC as input
{
fin = sysclock_sourceFreq(SYSCLOCK_SRC_POSC);
}
uint16_t prediv = ACLKCON1bits.APLLPRE;
uint16_t multiplier = APLLFBD1bits.APLLFBDIV;
uint16_t postdiv = (APLLDIV1bits.APOST1DIV) * (APLLDIV1bits.APOST2DIV);
_sysclock_avco = fin / prediv * multiplier;
uint32_t afpllo = _sysclock_vco / postdiv;
_sysclock_apllo = afpllo;
return afpllo;
}