ESP32-S3 Baremetal Support

esp32s3_clock.c

CPU clock.

The ROM leaves the CPU on the crystal divided by two - 20 MHz - which is too slow to bit-bang an addressable LED (see led.h). Anything that measures time in CPU cycles needs to know the real frequency, so this module both switches the clock and reports what it actually reads back from hardware, rather than trusting a compile-time constant.

Typical use, first thing in _start():

set_cpu_160mhz();

API

Declared in esp32s3_clock.h

Functions

uint32_t read_cpu_mhz(void);

Reads the current CPU speed straight from the clock-tree registers.

returns
frequency in MHz (20, 40, 80, 160 or 240)
uint32_t cpu_mhz(void);

The CPU speed that delay and LED timing use. Cached after the first call.

returns
frequency in MHz
void set_cpu_160mhz(void);

Switches the CPU to 160 MHz off the PLL. Needs the PLL running, which it is on the ROM's boot path.

returns
nothing
void periph_enable(uint32_t clk_en_reg, uint32_t rst_en_reg, uint32_t mask);

Ungates a peripheral's clock and pulses its reset, leaving it in the state its own driver expects to start from. Every peripheral powers up gated off and half-configured by the ROM, so each driver does this first.

clk_en_reg
SYSTEM_PERIP_CLK_EN0_REG or ..._EN1_REG
rst_en_reg
the matching SYSTEM_PERIP_RST_EN0_REG or ..._EN1_REG
mask
the peripheral's bit, the same in both registers
returns
nothing

Source

board/esp32s3_clock.c
board/esp32s3_clock.c
62 lines
/*
 * CPU clock: reads the current speed from the clock tree and switches to
 * 160 MHz off the PLL.
 */

#include "esp32s3_clock.h"
#include "esp32s3_regs.h"

static uint32_t cached_cpu_mhz;     // 0 until someone asks

uint32_t read_cpu_mhz(void)
{
    uint32_t sysclk = ESP32S3_REG(SYSTEM_SYSCLK_CONF_REG);

    switch ((sysclk >> SYSTEM_SOC_CLK_SEL_S) & SYSTEM_SOC_CLK_SEL_M) {
    case 0:                                 // straight off the crystal
        return ESP32S3_XTAL_MHZ / ((sysclk & SYSTEM_PRE_DIV_CNT_M) + 1);
    case 1:                                 // PLL, divided down
        switch (ESP32S3_REG(SYSTEM_CPU_PER_CONF_REG) & SYSTEM_CPUPERIOD_SEL_M) {
        case 0:  return 80;
        case 1:  return 160;
        default: return 240;
        }
    default:                                // internal RC oscillator
        return 20;
    }
}

uint32_t cpu_mhz(void)
{
    if (cached_cpu_mhz == 0) {
        cached_cpu_mhz = read_cpu_mhz();
    }
    return cached_cpu_mhz;
}

void set_cpu_160mhz(void)
{
    uint32_t period = ESP32S3_REG(SYSTEM_CPU_PER_CONF_REG);
    period = (period & ~SYSTEM_CPUPERIOD_SEL_M) | 1u;   // CPUPERIOD_SEL = 160 MHz
    ESP32S3_REG(SYSTEM_CPU_PER_CONF_REG) = period;

    uint32_t sysclk = ESP32S3_REG(SYSTEM_SYSCLK_CONF_REG);
    sysclk &= ~SYSTEM_PRE_DIV_CNT_M;                    // divide by 1
    ESP32S3_REG(SYSTEM_SYSCLK_CONF_REG) = sysclk;

    sysclk = (sysclk & ~(SYSTEM_SOC_CLK_SEL_M << SYSTEM_SOC_CLK_SEL_S))
           | (1u << SYSTEM_SOC_CLK_SEL_S);              // source = PLL
    ESP32S3_REG(SYSTEM_SYSCLK_CONF_REG) = sysclk;

    cached_cpu_mhz = read_cpu_mhz();
}

void periph_enable(uint32_t clk_en_reg, uint32_t rst_en_reg, uint32_t mask)
{
    ESP32S3_REG(clk_en_reg) |= mask;

    // Held in reset, then let go: whatever the ROM left in the peripheral's
    // registers is cleared before its driver writes the first one.
    ESP32S3_REG(rst_en_reg) |= mask;
    ESP32S3_REG(rst_en_reg) &= ~mask;
}
include/esp32s3_clock.h— the header, in full
include/esp32s3_clock.h
42 lines
/*
 * CPU clock.
 *
 * The ROM leaves the CPU on the crystal divided by two - 20 MHz - which is
 * too slow to bit-bang an addressable LED (see led.h). Anything that measures
 * time in CPU cycles needs to know the real frequency, so this module both
 * switches the clock and reports what it actually reads back from hardware,
 * rather than trusting a compile-time constant.
 *
 * Typical use, first thing in _start():
 *
 *     set_cpu_160mhz();
 */

#ifndef ESP32S3_CLOCK_H
#define ESP32S3_CLOCK_H

#include <stdint.h>

// Reads the current CPU speed straight from the clock-tree registers.
// returns: frequency in MHz (20, 40, 80, 160 or 240)
uint32_t read_cpu_mhz(void);

// The CPU speed that delay and LED timing use. Cached after the first call.
// returns: frequency in MHz
uint32_t cpu_mhz(void);

// Switches the CPU to 160 MHz off the PLL. Needs the PLL running, which it
// is on the ROM's boot path.
// returns: nothing
void set_cpu_160mhz(void);

// Ungates a peripheral's clock and pulses its reset, leaving it in the state
// its own driver expects to start from. Every peripheral powers up gated off
// and half-configured by the ROM, so each driver does this first.
// - clk_en_reg: SYSTEM_PERIP_CLK_EN0_REG or ..._EN1_REG
// - rst_en_reg: the matching SYSTEM_PERIP_RST_EN0_REG or ..._EN1_REG
// - mask: the peripheral's bit, the same in both registers
// returns: nothing
void periph_enable(uint32_t clk_en_reg, uint32_t rst_en_reg, uint32_t mask);

#endif // ESP32S3_CLOCK_H