SCIF module reference

Header :
#include <scif.h>
Makefile : scif is already included by default

Description

The SCIF (System Control Interface) manages the generation of the system and generic clocks. It features :

  • the default RC oscillator used by the CPU at startup, RCSYS
  • a general-purpose 4/8/12MHz RC oscillator, RCFAST
  • an external crystal oscillator (between 0.6MHz and 30MHz), OSC0
  • a Digital Frequency Locked Loop (between 20MHz and 150MHz), DFLL
  • a Phase Locked Loop (between 48 and 240MHz), PLL
  • a 80MHz oscillator, RC80M

Generic clocks are clocks for which the input can be connected to any system clock, that provide an optional divider, and that can be routed to different outputs. There are 12 generic clocks. Each of them is dedicated to a few peripherals, that can use them for internal purposes. The first four generic clocks outputs can be mapped to GPIO pins using the SCIF_GCLKn signal lines, which can be useful in order to generate clock signals for external components. Generic clocks can use up to two different extern clock inputs as clock sources, SCIF_GCLK_IN0 and SCIF_GCLK_IN1.

API

unsigned long getRCSYSFrequency()
Return the frequency of the RCSYS oscillator : 115000.
void enableRCFAST(RCFASTFrequency frequency)
Enable the RCFAST oscillator to either RCFAST_4MHZ, RCFAST_8MHZ or RCFAST_12MHZ. This oscillator will operate in closed-loop mode based on the 32kHz clock generated by the BPM, and is therefore quite precise.
void disableRCFAST()
Disable the RCFAST oscillator.
unsigned long getRCFASTFrequency()
Return the frequency of the external crystal oscillator.
void enableOSC0(unsigned long frequency)
Enable the OSC0 external crystal oscillator running at the specified frequency. This requires that this crystal is connected. See datasheet §42.7.1 Oscillator 0 (OSC0) Characteristics for more details on the required characteristics for this oscillator and its load capacitors.
void disableOSC0()
Disable the OSC0 external crystal oscillator.
unsigned long getOSC0Frequency()
Return the frequency of the external crystal oscillator, specified in enableOSC0().
void enablePLL(int mul, int div, GCLKSource referenceClock=GCLKSource::RCSYS, unsigned long referenceFrequency=115000UL)

Enable the PLL, which is able to generate a high-frequency clock based on a lower-frequency clock. The low-frequency input clock is provided using the generic clock GCLK9, which will be connected to the specified referenceClock running at the specified referenceFrequency. The ratio between the input frequency and the output frequency is specified using mul and div according to the following formulas :

  • if div > 0 : \(f_{out}=\frac{mul+1}{div}.f_{in}\)
  • if div = 0 : \(f_{out}=2(mul+1).f_{in}\)

The PLL is used by the USB module to generate the 48MHz USB clock.

void disablePLL()

Disable the PLL.

unsigned long getPLLFrequency()
Return the output frequency of the PLL.
void enableDFLL(unsigned long frequency, GCLKSource referenceClock=GCLKSource::OSC32K, unsigned long referenceFrequency=32768)

Enable the DFLL, which is able to generate a high-frequency clock based on a lower-frequency clock. The low-frequency input clock is provided using the generic clock GCLK0, which will be connected to the specified referenceClock running at the specified referenceFrequency. The multiplication ratio is calculated from the output frequency.

void disableDFLL()

Disable the DFLL.

unsigned long getDFLLFrequency()
Return the output frequency of the DFLL.
void enableRC80M()
Enable the RC80M oscillator, operating at 80MHz. It can power the main clock if downscaled to at most 48MHz, or be used as a generic clock.
void disableRC80M()
Disable the RC80M oscillator.
unsigned long getRC80MFrequency()
Return the frequency of the RC80M oscillator: 80000000.
void enableGenericClock(GCLKChannel channel, GCLKSource source, bool output=false, uint32_t divider=0)
Enable a generic clock. See the GCLKChannel and GCLKSource enums in the header below for the full list of channels and sources available, but the most important channels are :
  • GCLK0_DFLL : GCLK0 output on pin PB10 by default on Carbide
  • GCLK1_DFLLDITHER : GCLK1 output on pin PB11 by default on Carbide
  • GCLK2_AST : GCLK2 output on pin PB12 by default on Carbide
  • GCLK3_CATB : GCLK3 output on pin PB13 by default on Carbide
and the most important sources are :
  • RC32K : 32KHz oscillator (locked to the external 32KHz crystal)
  • RCFAST : 4MHz, 8MHz or 12MHz oscillator, enabled using enableRCFAST()
  • RC80M : 80MHz oscillator, enabled using enableRC80M() (note that the microcontroller can't output clock speeds higher than 48MHz, you need to use a divider)
  • CLKCPU : clock currently used by the CPU
The first four channels can be mapped to the SCIF_GCLKn GPIO output signals if output is set.
A custom clock divider can be specified in order to reduce the clock speed. Use 0 to disable the divider, otherwise you should only use even values. The maximum divider value is 512 for most generic clocks, except for GCLK11_MASTER which can be divided up to 131072.
void disableGenericClock(GCLKChannel channel)
Disable the given generic clock.
void setPin(PinFunction function, int channel, GPIO::Pin pin)
Set the GPIO pin to use for the specified function and channel. See the Pin muxing tutorial for more details.

Hacking

The module covers a large part of the SCIF's feature set, but there are advanced aspects that can be customized for specific applications. Interrupts can be generated on some clock-related events. The DFLL has more complex features and options, such as a spread-spectrum generator. There are also fractional prescalers that can be used to tune the output frequency of the generic clocks.

Code

Header

#ifndef _SCIF_H_
#define _SCIF_H_

#include <stdint.h>
#include "gpio.h"

// System Control Interface
// This module manages most of the clock sources : external crystal 
// oscillator, DFLL/PLL, low-power default RCSYS oscillator, the faster
// RC80M and RCFAST oscillators, and the Generic Clocks system.
namespace SCIF {

    // Peripheral memory space base address
    const uint32_t SCIF_BASE = 0x400E0800;

    // Registers addresses
    const uint32_t OFFSET_IER =        0x0000; // Interrupt Enable Register
    const uint32_t OFFSET_IDR =        0x0004; // Interrupt Disable Register
    const uint32_t OFFSET_IMR =        0x0008; // Interrupt Mask Register
    const uint32_t OFFSET_ISR =        0x000C; // Interrupt Status Register
    const uint32_t OFFSET_ICR =        0x0010; // Interrupt Clear Register
    const uint32_t OFFSET_PCLKSR =     0x0014; // Power and Clocks Status Register
    const uint32_t OFFSET_UNLOCK =     0x0018; // Unlock Register
    const uint32_t OFFSET_CSCR =       0x001C; // Chip Specific Configuration Register
    const uint32_t OFFSET_OSCCTRL0 =   0x0020; // Oscillator Control Register
    const uint32_t OFFSET_PLL0 =       0x0024; // PLL0 Control Register
    const uint32_t OFFSET_DFLL0CONF =  0x0028; // DFLL0 Config Register
    const uint32_t OFFSET_DFLL0VAL =   0x002C; // DFLL Value Register
    const uint32_t OFFSET_DFLL0MUL =   0x0030; // DFLL0 Multiplier Register
    const uint32_t OFFSET_DFLL0STEP =  0x0034; // DFLL0 Step Register
    const uint32_t OFFSET_DFLL0SSG =   0x0038; // DFLL0 Spread Spectrum Generator Control Register
    const uint32_t OFFSET_DFLL0RATIO = 0x003C; // DFLL0 Ratio Register
    const uint32_t OFFSET_DFLL0SYNC =  0x0040; // DFLL0 Synchronization Register
    const uint32_t OFFSET_RCCR =       0x0044; // System RC Oscillator Calibration Register
    const uint32_t OFFSET_RCFASTCFG =  0x0048; // 4/8/12MHz RC Oscillator Configuration Register
    const uint32_t OFFSET_RCFASTSR =   0x004C; // 4/8/12MHz RC Oscillator Status Register
    const uint32_t OFFSET_RC80MCR =    0x0050; // 80MHz RC Oscillator Register
    const uint32_t OFFSET_HRPCR =      0x0064; // High Resolution Prescaler Control Register
    const uint32_t OFFSET_FPCR =       0x0068; // Fractional Prescaler Control Register
    const uint32_t OFFSET_FPMUL =      0x006C; // Fractional Prescaler Multiplier Register
    const uint32_t OFFSET_FPDIV =      0x0070; // Fractional Prescaler DIVIDER Register
    const uint32_t OFFSET_GCCTRL0 =    0x0074; // Generic Clock Control 0
    const uint32_t OFFSET_GCCTRL1 =    0x0078; // Generic Clock Control 1
    const uint32_t OFFSET_GCCTRL2 =    0x007C; // Generic Clock Control 2
    const uint32_t OFFSET_GCCTRL3 =    0x0080; // Generic Clock Control 3
    const uint32_t OFFSET_GCCTRL4 =    0x0084; // Generic Clock Control 4
    const uint32_t OFFSET_GCCTRL5 =    0x0088; // Generic Clock Control 5
    const uint32_t OFFSET_GCCTRL6 =    0x008C; // Generic Clock Control 6
    const uint32_t OFFSET_GCCTRL7 =    0x0090; // Generic Clock Control 7
    const uint32_t OFFSET_GCCTRL8 =    0x0094; // Generic Clock Control 8
    const uint32_t OFFSET_GCCTRL9 =    0x0098; // Generic Clock Control 9
    const uint32_t OFFSET_GCCTRL10 =   0x009C; // Generic Clock Control 10
    const uint32_t OFFSET_GCCTRL11 =   0x00A0; // Generic Clock Control 11


    // Subregisters
    const uint32_t PCLKSR_OSC0RDY = 0;
    const uint32_t PCLKSR_DFLL0RDY = 3;
    const uint32_t PCLKSR_PLL0LOCK = 6;
    const uint32_t OSCCTRL0_MODE = 0;
    const uint32_t OSCCTRL0_GAIN = 1;
    const uint32_t OSCCTRL0_AGC = 3;
    const uint32_t OSCCTRL0_STARTUP = 8;
    const uint32_t OSCCTRL0_OSCEN = 16;
    const uint32_t PLL0_EN = 0;
    const uint32_t PLL0_PLLOSC = 1;
    const uint32_t PLL0_PLLOPT = 3;
    const uint32_t PLL0_PLLDIV = 8;
    const uint32_t PLL0_PLLMUL = 16;
    const uint32_t PLL0_PLLCOUNT = 24;
    const uint32_t DFLL0CONF_EN = 0;
    const uint32_t DFLL0CONF_MODE = 1;
    const uint32_t DFLL0CONF_RANGE = 16;
    const uint32_t DFLL0STEP_FSTEP = 0;
    const uint32_t DFLL0STEP_CSTEP = 16;
    const uint32_t RCFASTCFG_EN = 0;
    const uint32_t RCFASTCFG_TUNEEN = 1;
    const uint32_t RCFASTCFG_JITMODE = 2;
    const uint32_t RCFASTCFG_NBPERIODS = 4;
    const uint32_t RCFASTCFG_FRANGE = 8;
    const uint32_t RCFASTCFG_LOCKMARGIN = 12;
    const uint32_t RC80MCR_EN = 0;
    const uint32_t GCCTRL_CEN = 0;
    const uint32_t GCCTRL_DIVEN = 1;
    const uint32_t GCCTRL_OSCSEL = 8;
    const uint32_t GCCTRL_DIV = 16;

    // Constants
    const uint32_t UNLOCK_KEY = 0xAA << 24;
    
    // Error codes
    const uint16_t ERR_PLL_OUT_OF_RANGE = 0x0001;
    const uint16_t ERR_DFLL_OUT_OF_RANGE = 0x0002;
    const uint16_t WARN_RCFAST_ALREADY_ENABLED = 0x0003;

    // RCFAST can be configured to operate in any of these frequencies
    enum class RCFASTFrequency {
        RCFAST_4MHZ = 0b00,
        RCFAST_8MHZ = 0b01,
        RCFAST_12MHZ = 0b10
    };

    // Each generic clock channel has one or two specific allocations
    enum class GCLKChannel {
        GCLK0_DFLL,         // Also GCLK0 pin
        GCLK1_DFLLDITHER,   // Also GCLK1 pin
        GCLK2_AST,          // Also GCLK2 pin
        GCLK3_CATB,         // Also GCLK3 pin
        GCLK4_AES,
        GCLK5_GLOC_TC0,
        GCLK6_ABDAC_IIS,
        GCLK7_USB,
        GCLK8_TC1_PEVC0,
        GCLK9_PLL0_PEVC1,
        GCLK10_ADC,
        GCLK11_MASTER
    };

    // Each generic clock can be mapped to any of these clock sources
    enum class GCLKSource {
        RCSYS = 0,
        OSC32K = 1,
        DFLL = 2,
        OSC0 = 3,
        RC80M = 4,
        RCFAST = 5,
        RC1M = 6,
        CLKCPU = 7,
        CLKHSB = 8,
        CLKPBA = 9,
        CLKPBB = 10,
        CLKPBC = 11,
        CLKPBD = 12,
        RC32K = 13,
        CLK1K = 15,
        PLL = 16,
        HRP = 17,
        FP = 18,
        GCLKIN0 = 19,
        GCLKIN1 = 20,
        GCLK11 = 21
    };

    enum class PinFunction {
        GCLK,
        GCLK_IN
    };


    // Module API

    // RCSYS (115kHz) is the default RC oscillator on which the chip operates after reset
    unsigned long getRCSYSFrequency();

    // RCFAST is a faster RC oscillator than RCSYS, which can operate at 4MHz, 8MHz or 12MHz
    void enableRCFAST(RCFASTFrequency frequency);
    void disableRCFAST();
    unsigned long getRCFASTFrequency();

    // OSC0 is an external crystal oscillator, which can operate from 0.6MHz to 30MHz.
    // See datasheet §42.7.1 Oscillator 0 (OSC0) Characteristics for more details on
    // the required characteristics for this oscillator and its load capacitors.
    void enableOSC0(unsigned long frequency);
    void disableOSC0();
    unsigned long getOSC0Frequency();

    // PLL (Phase Locked Loop) is able to generate a high-frequency clock based on a
    // lower-frequency one. It is used by the USB module.
    void enablePLL(int mul, int div, GCLKSource referenceClock=GCLKSource::RCSYS, unsigned long referenceFrequency=115000UL);
    void disablePLL();
    unsigned long getPLLFrequency();

    // DFLL (Digital Frequency Locked Loop) is similar to the PLL
    void enableDFLL(unsigned long frequency, GCLKSource referenceClock=GCLKSource::OSC32K, unsigned long referenceFrequency=32768);
    void disableDFLL();
    unsigned long getDFLLFrequency();

    // RC80M is the faster RC oscillator available, operating at 80MHz. It can power the
    // main clock if downscaled to at most 48MHz, or be used as a generic clock.
    void enableRC80M();
    void disableRC80M();
    unsigned long getRC810MFrequency();

    // Generic clocks
    void enableGenericClock(GCLKChannel channel, GCLKSource source, bool output=false, uint32_t divider=0);
    void disableGenericClock(GCLKChannel channel);

    // Set the pins used for signal lines
    void setPin(PinFunction function, int channel, GPIO::Pin pin);

}


#endif

Module

#include "scif.h"
#include "bscif.h"
#include "error.h"

namespace SCIF {

    // Package-dependant, defined in pins_sam4l_XX.cpp
    extern struct GPIO::Pin PINS_GCLK[];
    extern struct GPIO::Pin PINS_GCLK_IN[];

    // Clocks frequencies
    unsigned long _rcsysFrequency = 115000UL;
    unsigned long _osc0Frequency = 0;
    unsigned long _pllFrequency = 0;
    unsigned long _dfllFrequency = 0;
    unsigned long _rcfastFrequency = 0;
    RCFASTFrequency _rcfastFrequencySelected = RCFASTFrequency::RCFAST_4MHZ;
    unsigned long _rc80mFrequency = 80000000UL;

    // Output mask of the generic clocks
    bool _genericClockOutputEnabled[4] = {false, false, false, false};


    // RCSYS frequency is fixed
    unsigned long getRCSYSFrequency() {
        return _rcsysFrequency;
    }


    // RCFAST

    void enableRCFAST(RCFASTFrequency frequency) {
        // If RCFAST is already enabled
        if (_rcfastFrequency > 0) {
            // If the correct frequency is already selected, do nothing
            if (frequency == _rcfastFrequencySelected) {
                return;
            } else {
                // The current RCFAST setting will be overridden, issue a warning
                Error::happened(Error::Module::SCIF, WARN_RCFAST_ALREADY_ENABLED, Error::Severity::WARNING);
                disableRCFAST();
            }
        }

        // Save the frequency for future use
        switch (frequency) {
            case RCFASTFrequency::RCFAST_4MHZ:
                _rcfastFrequency = 4000000UL;
                break;

            case RCFASTFrequency::RCFAST_8MHZ:
                _rcfastFrequency = 8000000UL;
                break;

            case RCFASTFrequency::RCFAST_12MHZ:
                _rcfastFrequency = 12000000UL;
                break;

            default:
                return;
        }
        _rcfastFrequencySelected = frequency;

        // Unlock the RCFASTCFG register, which is locked by default as a safety mesure
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
                = UNLOCK_KEY            // KEY : Magic word (see datasheet)
                | OFFSET_RCFASTCFG;     // ADDR : unlock RCFASTCFG

        // Configure RCFAST
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_RCFASTCFG))
                = 1 << RCFASTCFG_EN                                // EN : enable the oscillator
                | 1 << RCFASTCFG_TUNEEN                            // TUNEEN : enable the tuner (closed-loop mode)
                | 0 << RCFASTCFG_JITMODE                           // JITMODE : update trim value when lock lost
                | 5 << RCFASTCFG_NBPERIODS                         // NBPERIODS : number of 32kHz periods (max : 7)
                | static_cast<int>(frequency) << RCFASTCFG_FRANGE  // FRANGE : desired frequency
                | 5 << RCFASTCFG_LOCKMARGIN;                       // LOCKMARGIN : error tolerance of the tuner

        // Wait for RCFAST to be ready
        while (!((*(volatile uint32_t*)(SCIF_BASE + OFFSET_RCFASTCFG)) & (1 << RCFASTCFG_EN)));
    }

    void disableRCFAST() {
        // Reset the saved frequency
        _rcfastFrequency = 0;

        // Unlock the RCFASTCFG register, which is locked by default as a safety mesure
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
                = UNLOCK_KEY            // KEY : Magic word (see datasheet)
                | OFFSET_RCFASTCFG;     // ADDR : unlock RCFASTCFG

        // Disable RCFAST
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_RCFASTCFG)) = 0;
    }

    unsigned long getRCFASTFrequency() {
        return _rcfastFrequency;
    }


    // OSC0

    void enableOSC0(unsigned long frequency) {
        // Unlock the OSCCTRL0 register, which is locked by default as a safety mesure
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
                = UNLOCK_KEY            // KEY : Magic word (see datasheet)
                | OFFSET_OSCCTRL0;      // ADDR : unlock OSCCTRL0

        // Save the indicated frequency for future use
        _osc0Frequency = frequency;

        // Configure OSC0
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_OSCCTRL0))
                = 1 << OSCCTRL0_MODE     // MODE : crystal
                | 3 << OSCCTRL0_GAIN     // GAIN : G3 (8MHz to 16MHz)
                | 3 << OSCCTRL0_STARTUP  // STARTUP : ~18ms
                | 1 << OSCCTRL0_OSCEN;   // OSCEN : enabled

        // Wait for OSC0 to be ready
        while (!((*(volatile uint32_t*)(SCIF_BASE + OFFSET_PCLKSR)) & (1 << PCLKSR_OSC0RDY)));
    }

    void disableOSC0() {
        // Unlock the OSCCTRL0 register, which is locked by default as a safety mesure
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
                = UNLOCK_KEY            // KEY : Magic word (see datasheet)
                | OFFSET_OSCCTRL0;      // ADDR : unlock OSCCTRL0

        // Reset the saved frequency
        _osc0Frequency = 0;

        // Disable OSC0
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_OSCCTRL0)) = 0;
    }

    unsigned long getOSC0Frequency() {
        return _osc0Frequency;
    }


    // PLL

    void enablePLL(int mul, int div, GCLKSource referenceClock, unsigned long referenceFrequency) {
        // Enable reference clock
        SCIF::enableGenericClock(SCIF::GCLKChannel::GCLK9_PLL0_PEVC1, referenceClock);

        // Check frequency-related parameters
        if (mul == 0 || mul > 15 || div > 15) {
            Error::happened(Error::Module::SCIF, ERR_PLL_OUT_OF_RANGE, Error::Severity::CRITICAL);
            return;
        }

        // Save the frequency for future use
        if (div == 0) {
            _pllFrequency = 2 * (mul + 1) * referenceFrequency;
        } else {
            _pllFrequency = ((mul + 1) * referenceFrequency) / div;
        }

        // Disable PLL
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
            = UNLOCK_KEY       // KEY : Magic word (see datasheet)
            | OFFSET_PLL0;     // ADDR : unlock PLL0
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_PLL0)) = 0;

        // Configure PLL
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
            = UNLOCK_KEY       // KEY : Magic word (see datasheet)
            | OFFSET_PLL0;     // ADDR : unlock PLL0
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_PLL0))
            = 1 << PLL0_PLLOSC     // PLLOSC : select GCLK9 as a reference
            | div << PLL0_PLLDIV   // PLLDIV : configure division factor
            | mul << PLL0_PLLMUL;  // PLLMUL : configure multiplication factor

        // Enable PLL
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
            = UNLOCK_KEY       // KEY : Magic word (see datasheet)
            | OFFSET_PLL0;     // ADDR : unlock PLL0
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_PLL0))
            |= 1 << PLL0_EN;   // EN : enable the oscillator

        // Wait for PLL to be ready
        while (!((*(volatile uint32_t*)(SCIF_BASE + OFFSET_PCLKSR)) & (1 << PCLKSR_PLL0LOCK)));
    }

    void disablePLL() {
        // Reset the saved frequency
        _pllFrequency = 0;

        // Disable PLL
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
            = UNLOCK_KEY            // KEY : Magic word (see datasheet)
            | OFFSET_PLL0;          // ADDR : unlock PLL0

        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_PLL0)) = 0;
    }

    unsigned long getPLLFrequency() {
        return _pllFrequency;
    }


    // DFLL

    void enableDFLL(unsigned long frequency, GCLKSource referenceClock, unsigned long referenceFrequency) {
        // Enable reference 32 KHz clock
        SCIF::enableGenericClock(SCIF::GCLKChannel::GCLK0_DFLL, referenceClock);

        // Compute frequency-related parameters
        uint8_t range = 0;
        if (frequency >= 96000000 && frequency <= 150000000) {
            range = 0;
        } else if (frequency >= 50000000) {
            range = 1;
        } else if (frequency >= 25000000) {
            range = 2;
        } else if (frequency >= 20000000) {
            range = 3;
        } else {
            Error::happened(Error::Module::SCIF, ERR_DFLL_OUT_OF_RANGE, Error::Severity::CRITICAL);
            return;
        }
        unsigned long mul = frequency / referenceFrequency;
        if (mul > 65535) {
            Error::happened(Error::Module::SCIF, ERR_DFLL_OUT_OF_RANGE, Error::Severity::CRITICAL);
            return;
        }

        // Save the frequency for future use
        _dfllFrequency = frequency;

        // Enable DFLL
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
            = UNLOCK_KEY            // KEY : Magic word (see datasheet)
            | OFFSET_DFLL0CONF;     // ADDR : unlock DFLL0CONF
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_DFLL0CONF))
                |= 1 << DFLL0CONF_EN;            // EN : enable the oscillator

        // Wait for DFLL to be ready
        while (!((*(volatile uint32_t*)(SCIF_BASE + OFFSET_PCLKSR)) & (1 << PCLKSR_DFLL0RDY)));

        // Configure frequency range
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
            = UNLOCK_KEY            // KEY : Magic word (see datasheet)
            | OFFSET_DFLL0CONF;     // ADDR : unlock DFLL0CONF
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_DFLL0CONF))
                |= range << DFLL0CONF_RANGE; // RANGE : Frequency range value

        // Configure frequency multiplier
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
            = UNLOCK_KEY            // KEY : Magic word (see datasheet)
            | OFFSET_DFLL0MUL;     // ADDR : unlock DFLL0CONF
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_DFLL0MUL)) = mul;

        // Configure maximum steps (used to adjust the speed at which the DFLL locks)
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
            = UNLOCK_KEY            // KEY : Magic word (see datasheet)
            | OFFSET_DFLL0STEP;     // ADDR : unlock DFLL0CONF
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_DFLL0STEP))
                = 10 << DFLL0STEP_FSTEP          // FSTEP : Fine maximum step
                | 10 << DFLL0STEP_CSTEP;         // CSTEP : Coarse maximum step

        // Enable Closed Loop mode
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
            = UNLOCK_KEY            // KEY : Magic word (see datasheet)
            | OFFSET_DFLL0CONF;     // ADDR : unlock DFLL0CONF
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_DFLL0CONF))
                |= 1 << DFLL0CONF_MODE;          // MODE : closed loop mode

        // Wait for DFLL to be ready
        while (!((*(volatile uint32_t*)(SCIF_BASE + OFFSET_PCLKSR)) & (1 << PCLKSR_DFLL0RDY)));
    }

    void disableDFLL() {
        // Reset the saved frequency
        _dfllFrequency = 0;

        // Disable DFLL
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_UNLOCK))
            = UNLOCK_KEY            // KEY : Magic word (see datasheet)
            | OFFSET_DFLL0CONF;     // ADDR : unlock DFLL0CONF

        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_DFLL0CONF)) = 0;
    }

    unsigned long getDFLLFrequency() {
        return _dfllFrequency;
    }


    // RC80M

    void enableRC80M() {
        // Configure RC80M
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_RC80MCR))
                = 1 << RCFASTCFG_EN;             // EN : enable the oscillator
    }

    void disableRC80M() {
        // Configure RC80M
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_RC80MCR))
                = 0 << RCFASTCFG_EN;             // EN : enable the oscillator
    }

    unsigned long getRC80MFrequency() {
        return _rc80mFrequency;
    }


    // Generic clocks

    void enableGenericClock(GCLKChannel channel, GCLKSource source, bool output, uint32_t divider) {
        // If this clock has an output, set the corresponding pin in peripheral mode
        if (output && channel <= GCLKChannel::GCLK3_CATB) {
            GPIO::enablePeripheral(PINS_GCLK[static_cast<int>(channel)]);
            _genericClockOutputEnabled[static_cast<int>(channel)] = true;
        }

        uint16_t d = 0;
        if (divider >= 2) {
            d = divider / 2 - 1;
        }

        // Configure clock
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_GCCTRL0 + static_cast<int>(channel) * 0x04))
                = 1 << GCCTRL_CEN           // CEN : enable the clock
                | (d > 0) << GCCTRL_DIVEN   // DIVEN : enable the clock divider if desired
                | static_cast<int>(source) << GCCTRL_OSCSEL   // OSCSEL : select desired clock
                | d << GCCTRL_DIV;          // DIV : desired division factor
    }

    void disableGenericClock(GCLKChannel channel) {
        // Disable the output if it was enabled
        if (_genericClockOutputEnabled[static_cast<int>(channel)]) {
            GPIO::disablePeripheral(PINS_GCLK[static_cast<int>(channel)]);
            _genericClockOutputEnabled[static_cast<int>(channel)] = false;
        }

        // Disable the clock
        (*(volatile uint32_t*)(SCIF_BASE + OFFSET_GCCTRL0 + static_cast<int>(channel) * 0x04)) = 0;
    }


    void setPin(PinFunction function, int channel, GPIO::Pin pin) {
        switch (function) {
            case PinFunction::GCLK:
                PINS_GCLK[static_cast<int>(channel)] = pin;
                break;

            case PinFunction::GCLK_IN:
                PINS_GCLK_IN[static_cast<int>(channel)] = pin;
                break;
        }
    }

}