diff --git a/configure.ac b/configure.ac
index 1fa5efc..9d23272 100644
--- a/configure.ac
+++ b/configure.ac
@@ -50,6 +50,7 @@ AC_CONFIG_FILES([
examples/1rmtmusic/Makefile
examples/1rmttm1637/Makefile
examples/1rmtws2812/Makefile
+ examples/2cpuclock/Makefile
examples/3prog1/Makefile
ld/Makefile
])
diff --git a/examples/2cpuclock/Makefile.am b/examples/2cpuclock/Makefile.am
new file mode 100644
index 0000000..23a5672
--- /dev/null
+++ b/examples/2cpuclock/Makefile.am
@@ -0,0 +1,38 @@
+include $(top_srcdir)/scripts/elf2bin.mk
+include $(top_srcdir)/ld/flags.mk
+
+noinst_HEADERS = defines.h
+
+if WITH_BINARIES
+AM_LDFLAGS += \
+ -T $(top_srcdir)/ld/esp32.rom.ld \
+ -T $(top_srcdir)/ld/esp32.rom.api.ld \
+ -T $(top_srcdir)/ld/esp32.rom.libgcc.ld \
+ -T $(top_srcdir)/ld/esp32.rom.newlib-data.ld \
+ -T $(top_srcdir)/ld/esp32.rom.newlib-locale.ld \
+ -T $(top_srcdir)/ld/esp32.rom.newlib-nano.ld \
+ -T $(top_srcdir)/ld/esp32.rom.newlib-time.ld \
+ -T $(top_srcdir)/ld/esp32.rom.redefined.ld \
+ -T $(top_srcdir)/ld/esp32.rom.syscalls.ld
+else
+AM_LDFLAGS += \
+ -T $(top_srcdir)/ld/esp32.rom.ld \
+ -T $(top_srcdir)/ld/esp32.rom.api.ld \
+ -T $(top_srcdir)/ld/esp32.rom.libgcc.ld \
+ -T $(top_srcdir)/ld/esp32.rom.redefined.ld \
+ -T $(top_srcdir)/ld/esp32.rom.syscalls.ld
+endif
+
+AM_CFLAGS = -std=c11 -flto
+AM_CPPFLAGS = -I$(top_srcdir)/src -I$(srcdir)
+LDADD = $(top_builddir)/src/libesp32basic.a
+
+bin_PROGRAMS = \
+ cpuclock.elf
+
+if WITH_BINARIES
+CLEANFILES = \
+ cpuclock.bin
+endif
+
+BUILT_SOURCES = $(CLEANFILES)
diff --git a/examples/2cpuclock/README.md b/examples/2cpuclock/README.md
new file mode 100644
index 0000000..976895c
--- /dev/null
+++ b/examples/2cpuclock/README.md
@@ -0,0 +1,48 @@
+### Example setting CPU clock source and related things
+
+CPU clock can have the following sources:
+
+* `XTL_CLK`
+* `PLL_CLK`
+* `RC_FAST_CLK`
+* `APLL_CLK`
+
+Currently we support `XTL_CLK` and `PLL_CLK` with the following frequencies:
+
+* `XTL_CLK`
+ * 40 MHz
+ * 20 MHz
+ * 10 MHz
+ * 8 MHz
+ * 5 MHz
+* `PLL_CLK`
+ * 80 MHz
+ * **160 MHz** (default)
+ * 240 MHz
+
+In this example you can select the cpu clock source,
+set the clock frequency,
+tune the digital voltage regulator (dig_dbias) and
+select the UART0 baud rate (115200, 57600, or 9600).
+
+The NodeMCU onboard LED (GPIO2) blinks at 1 Hz rate based on APB clock.
+This feature is useful to see wether the APB clock frequency matches the
+expected value.
+
+#### Control
+
+* `p` (OK) select `PLL_CLK` as clock source;
+* `x` (OK) select `XTL_CLK` as clock source;
+* `]` (OK) set higher PLL-based clock frequency;
+* `[` (OK) set lower PLL-based clock frequency;
+* `-` (OK) set higher XTL_CLK divisor (lower clock frequency);
+* `+` (OK) set lower XTL_CLK divisor (higher clock frequency);
+* `<` (OK) set lower dig vreg dbias;
+* `>` (OK) set higher dig vreg dbias;
+* `u` (OK) set lower UART0 baud;
+* `U` (OK) set higher UART0 baud;
+* `a` (OK) select UART0 clock source (`APB_CLK` or `REF_TICK`);
+* `i` (OK) Information about registers;
+* `I` (OK) display current settings;
+* `r` (OK) when re-entering a CPU freq state, use its last known vreg dbias setting;
+* `R` (OK) always use the default vreg dbias setting for any CPU freq state.
diff --git a/examples/2cpuclock/cpuclock.c b/examples/2cpuclock/cpuclock.c
new file mode 100644
index 0000000..6f0e9b9
--- /dev/null
+++ b/examples/2cpuclock/cpuclock.c
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2024 - 2026 SZIGETI János
+ *
+ * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3.
+ * See LICENSE or for full license details.
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include "clock.h"
+#include "esp_attr.h"
+#include "dport.h"
+#include "gpio.h"
+#include "iomux.h"
+#include "main.h"
+#include "defines.h"
+#include "romfunctions.h"
+#include "rtc.h"
+#include "typeaux.h"
+#include "uart.h"
+#include "utils/uartutils.h"
+#include "timg.h"
+
+// =================== Hard constants =================
+// #0: Timings
+#define UART_PERIOD_MS 100U ///< for polling UART0 RX for incoming commands
+#define LED_PERIOD_MS 1000U
+#define LED_HIGH_MS 100U
+
+#define REF_TICK_FREQ_HZ 1000000U
+#define XTL_CLK_FREQ_HZ 40000000U
+// derived
+#define XTL_CLK_FREQ_MHZ (XTL_CLK_FREQ_HZ / 1000000U)
+#define APB_FREQ_MHZ (APB_FREQ_HZ / 1000000U)
+#define TIMG00_XTLBASE_FREQ_KHZ (XTL_CLK_FREQ_HZ / (TIM0_0_DIVISOR * 1000))
+
+// #1: Limits
+
+// #2: Connections
+#define LED_GPIO 2U
+
+// #3: Sizes
+
+// ============= Local types ===============
+
+
+// ================ Local function declarations =================
+static uint32_t _ms_to_tick(uint32_t u32msValue);
+static void _led_init();
+static void _led_cycle(uint64_t u64tckNow);
+static void _switch_to_any_clk(ECpuClockSource eClk);
+static void _uart0_autoconf();
+static void _uart_config(bool bApbBased, bool bPllCpuClkSrc, uint32_t u32Baud);
+static void _uart_init();
+static void _uart_cycle(uint64_t u64tckNow);
+static void _vregdbias_init();
+static uint8_t _xtal_ref_tick_div(uint8_t u8Idx);
+
+// =================== Global constants ================
+const bool gbStartAppCpu = START_APP_CPU;
+const uint16_t gu16Tim00Divisor = TIM0_0_DIVISOR;
+const uint64_t gu64tckSchedulePeriod = (CLK_FREQ_HZ / SCHEDULE_FREQ_HZ);
+
+// ==================== Local Data ================
+/// Predefined set of XTL_CLK divisors
+const static uint32_t gau32XtalDiv[] = {
+ 1, 2, 4, 5, 8, 10
+};
+static uint8_t gu8XtalClkIdx = 0;
+static EPllCpuClockFreq gePllCpuClkFreq = PLLCPUFREQ_160MHZ;
+static ECpuClockSource geCpuClkSrc = CLK_PLL;
+
+/// Predefined UART baud rates
+const static uint32_t gau32UartBaud[] = {
+ 9600, 19200, 38400, 57600, 115200, 230400, 460800
+};
+static uint8_t gu8UartBaudIdx = 4;
+
+/// Possible CPU voltage levels (mV)
+const static uint32_t gau32Vreg[] = {
+ 900, 950, 1000, 1050, 1100, 1150, 1200, 1250
+};
+
+static bool gbUart0ApbBased = true;
+
+static uint8_t gau8XtalVregDBiasUser[ARRAY_SIZE(gau32XtalDiv)];
+static uint8_t gau8PllVregDBiasUser[3];
+static bool gbLoadVregDBias;
+
+// ============== Implementation ==============
+// -------------- Internal functions --------------
+
+static uint32_t _ms_to_tick(uint32_t u32msValue) {
+ if (geCpuClkSrc == CLK_PLL) return MS2TICKS(u32msValue);
+ return (TIMG00_XTLBASE_FREQ_KHZ / gau32XtalDiv[gu8XtalClkIdx]) * u32msValue;
+}
+
+static void _led_init() {
+ gpio_pin_enable(LED_GPIO);
+}
+
+static void _led_cycle(uint64_t u64tckNow) {
+ static const RegAddr aprGpioOut[] = {&gsGPIO.OUT_W1TC, &gsGPIO.OUT_W1TS};
+ static uint64_t u64tckNext = 0;
+ static bool gbLedOn = false;
+
+ if (u64tckNext <= u64tckNow) {
+ gbLedOn = !gbLedOn;
+ gpio_reg_setbit(aprGpioOut[gbLedOn], LED_GPIO);
+ u64tckNext += _ms_to_tick(gbLedOn ? LED_HIGH_MS : (LED_PERIOD_MS - LED_HIGH_MS));
+ }
+}
+
+static void _uart_config(bool bApbBased, bool bPllCpuClkSrc, uint32_t u32Baud) {
+ gsUART0.CONF0 = FIELD_REPLACE(gsUART0.CONF0, bApbBased ? 1 : 0, UART_TICK_REF_ALWAYS_ON);
+ uint32_t u32ClkFreqHz = bApbBased ? (bPllCpuClkSrc ? APB_FREQ_HZ : XTL_CLK_FREQ_HZ / gau32XtalDiv[gu8XtalClkIdx]) : REF_TICK_FREQ_HZ;
+ gsUART0.CLKDIV.raw = UART_HZ2CLKDIV(u32Baud, u32ClkFreqHz);
+}
+
+static void _uart0_autoconf() {
+ _uart_config(gbUart0ApbBased, geCpuClkSrc == CLK_PLL, gau32UartBaud[gu8UartBaudIdx]);
+}
+
+static void _uart_init() {
+ _uart0_autoconf();
+}
+
+static void _vregdbias_init() {
+ for (int i = 0; i < ARRAY_SIZE(gau8PllVregDBiasUser); ++i) {
+ gau8PllVregDBiasUser[i] = -1; // not set yet
+ }
+ for (int i = 0; i < ARRAY_SIZE(gau8XtalVregDBiasUser); ++i) {
+ gau8XtalVregDBiasUser[i] = -1; // not set yet
+ }
+ gbLoadVregDBias = true;
+}
+
+static uint8_t _xtal_ref_tick_div(uint8_t u8Idx) {
+ return (XTL_CLK_FREQ_HZ / REF_TICK_FREQ_HZ) / gau32XtalDiv[u8Idx];
+};
+
+static inline void _print_xtl_params() {
+ uint32_t u32CpuFreqKHz = (XTL_CLK_FREQ_HZ / 1000) / gau32XtalDiv[gu8XtalClkIdx];
+ uint32_t u32RefFreqKHz = u32CpuFreqKHz / _xtal_ref_tick_div(gu8XtalClkIdx);
+ uart_printf(&gsUART0, "XTAL div#%u: %u, %u: %uKHz (ref: %uKHz)\r\n",
+ gu8XtalClkIdx, gau32XtalDiv[gu8XtalClkIdx], _xtal_ref_tick_div(gu8XtalClkIdx),
+ u32CpuFreqKHz, u32RefFreqKHz);
+}
+
+static bool _modify_idx(uint8_t *pu8Idx, int8_t i8Diff, uint8_t u8Size, const uint32_t *pu32Values, const char *strText) {
+ bool bRet = false;
+ uint8_t u8IdxOrig = *pu8Idx;
+ if (i8Diff < 0) {
+ if (-i8Diff < *pu8Idx) {
+ *pu8Idx += i8Diff;
+ } else {
+ *pu8Idx = 0;
+ }
+ } else {
+ if (*pu8Idx + i8Diff < u8Size) {
+ *pu8Idx += i8Diff;
+ } else {
+ *pu8Idx = u8Size - 1;
+ }
+ }
+ if (*pu8Idx != u8IdxOrig) {
+ if (pu32Values) {
+ uart_printf(&gsUART0, "%s changed: %u -> %u\r\n", strText, pu32Values[u8IdxOrig], pu32Values[*pu8Idx]);
+ } else {
+ uart_printf(&gsUART0, "%s idx changed: %u -> %u\r\n", strText, u8IdxOrig, *pu8Idx);
+ }
+ bRet = true;
+ } else {
+ if (pu32Values) {
+ uart_printf(&gsUART0, "%s (%u) not changed\r\n", strText, pu32Values[u8IdxOrig]);
+ } else {
+ uart_printf(&gsUART0, "%s idx (%u) not changed\r\n", strText, u8IdxOrig);
+ }
+ }
+ return bRet;
+}
+
+static void _switch_to_any_clk(ECpuClockSource eClk) {
+ ECpuClockSource eCpuClkSrcOrig = geCpuClkSrc;
+ if (eClk == CLK_XTL) {
+ clock_switch_to_xtl(gau32XtalDiv[gu8XtalClkIdx], _xtal_ref_tick_div(gu8XtalClkIdx));
+ } else if (eClk == CLK_PLL) {
+ clock_switch_to_pll(gePllCpuClkFreq);
+ }
+ geCpuClkSrc = eClk;
+ uint8_t u8FreqIdx = ((eClk == CLK_XTL) ? gu8XtalClkIdx : gePllCpuClkFreq);
+ uint8_t *pu8VregDBiasUser = ((eClk == CLK_XTL) ? gau8XtalVregDBiasUser : gau8PllVregDBiasUser);
+
+ if (gbLoadVregDBias && pu8VregDBiasUser[u8FreqIdx] < 8) {
+ clock_set_dig_vreg_dbias_wak(pu8VregDBiasUser[u8FreqIdx]);
+ uart_printf(&gsUART0, "Dig Vreg DBias set to %u mV\r\n", gau32Vreg[pu8VregDBiasUser[u8FreqIdx]]);
+ }
+ // resolve UART baud issue in case of APB freq change
+ if (gbUart0ApbBased && !(eCpuClkSrcOrig == CLK_PLL && eClk == CLK_PLL)) {
+ _uart0_autoconf();
+ }
+}
+
+static void _uart_cycle(uint64_t u64tckNow) {
+ static uint64_t u64tckNext = MS2TICKS(1000);
+
+ if (u64tckNext <= u64tckNow) {
+ while (0 < (gsUART0.STATUS & 0xff)) {
+ char cCtrl = gsUART0Mapped.FIFO & 0xff;
+ int8_t i8UartBaudDiff = 0;
+ int8_t i8XtlDivDiff = 0;
+ int8_t i8PllFreqSelDiff = 0;
+ int8_t i8DigVregDBiasDiff = 0;
+
+ switch (cCtrl) {
+ case '-':
+ --i8XtlDivDiff;
+ break;
+ case '+':
+ ++i8XtlDivDiff;
+ break;
+
+ case 'r':
+ gbLoadVregDBias = true;
+ uart_printf(&gsUART0, "Load stored vreg dbias when CPU CLK is changed.\r\n");
+ break;
+ case 'R':
+ gbLoadVregDBias = false;
+ uart_printf(&gsUART0, "Load default vreg dbias when CPU CLK is changed.\r\n");
+ break;
+
+ // modify UART0 baud
+ case 'u':
+ --i8UartBaudDiff;
+ break;
+ case 'U':
+ ++i8UartBaudDiff;
+ break;
+ // modify PLL-based CPU freq
+ case '[':
+ --i8PllFreqSelDiff;
+ break;
+ case ']':
+ ++i8PllFreqSelDiff;
+ break;
+ // toggle UART0 clock source
+ case 'a':
+ gbUart0ApbBased = !gbUart0ApbBased;
+ _uart0_autoconf();
+ uart_printf(&gsUART0, "UART0 based on %s\r\n", gbUart0ApbBased ? "APB_CLK" : "REF_TICK");
+ break;
+ // print register information
+ case 'i':
+ uart_printf(&gsUART0, "UART\tCONF0: %08X\tAUTOBAUD: %08X\tCLKDIV: %08X\r\n", gsUART0.CONF0, gsUART0.AUTOBAUD, gsUART0.CLKDIV);
+ uart_printf(&gsUART0, "SYSCON\tSYSCLK: %08X\tXTALTICK: %08X\tPLLTICK: %08X\r\n", gprSYSCON[0], gprSYSCON[1], gprSYSCON[2]);
+ break;
+ case 'I':
+ uart_printf(&gsUART0, "ClkSrc: %u, XTL freq: #%u (%u MHz), PLL freq: #%u (%u MHz), UART_APB: %c, LoadDBias: %c, dbias: #%u\r\n",
+ geCpuClkSrc,
+ gu8XtalClkIdx, XTL_CLK_FREQ_MHZ / gau32XtalDiv[gu8XtalClkIdx],
+ gePllCpuClkFreq, APB_FREQ_MHZ * (1 + gePllCpuClkFreq),
+ gbUart0ApbBased ? 'Y' : 'N', gbLoadVregDBias ? 'Y' : 'N', clock_get_dig_vreg_dbias_wak());
+ break;
+ case 'x': // switch to xtal
+ {
+ uart_printf(&gsUART0, "Switch to XTL CPU clock source...");
+ _switch_to_any_clk(CLK_XTL);
+ uart_printf(&gsUART0, "done.\r\n");
+ }
+ break;
+ case 'p': // switch to xtal
+ {
+ uart_printf(&gsUART0, "Switch to PLL CPU clock source...");
+ _switch_to_any_clk(CLK_PLL);
+ uart_printf(&gsUART0, "done.\r\n");
+ }
+ break;
+
+ case '<':
+ --i8DigVregDBiasDiff;
+ break;
+ case '>':
+ ++i8DigVregDBiasDiff;
+ break;
+ default:
+ uart_printf(&gsUART0, "_ _ _ [%c] _ _ _\r\n", cCtrl);
+ }
+
+ // change uart0 baud
+ if (i8UartBaudDiff != 0) {
+ if (_modify_idx(&gu8UartBaudIdx, i8UartBaudDiff, ARRAY_SIZE(gau32UartBaud), gau32UartBaud, "UART0 baud")) {
+ _uart0_autoconf();
+ }
+ }
+
+ // set XTL divisor
+ if (i8XtlDivDiff != 0) {
+ if (_modify_idx(&gu8XtalClkIdx, i8XtlDivDiff, ARRAY_SIZE(gau32XtalDiv), gau32XtalDiv, "XTAL DIV")) {
+ if (geCpuClkSrc == CLK_XTL) {
+ _switch_to_any_clk(CLK_XTL);
+ } else {
+ uart_printf(&gsUART0, "But CPU clock not changed as current clock source is not XTAL.\r\n");
+ }
+ }
+ }
+
+ // set PLL frequency
+ if (i8PllFreqSelDiff != 0) {
+ uint8_t u8PllFreqSel = gePllCpuClkFreq;
+ if (_modify_idx(&u8PllFreqSel, i8PllFreqSelDiff, 3, NULL, "PLL Freq. selector")) {
+ gePllCpuClkFreq = u8PllFreqSel;
+ if (geCpuClkSrc == CLK_PLL) {
+ _switch_to_any_clk(CLK_PLL);
+ } else {
+ uart_printf(&gsUART0, "But CPU clock not changed as current clock source is not PLL.\r\n");
+ }
+ }
+ }
+
+ // set digital voltage regulator dbias
+ if (i8DigVregDBiasDiff != 0) {
+ uint8_t u8dbias = clock_get_dig_vreg_dbias_wak();
+ if (_modify_idx(&u8dbias, i8DigVregDBiasDiff, ARRAY_SIZE(gau32Vreg), gau32Vreg, "dig vreg dbias [mV]")) {
+ clock_set_dig_vreg_dbias_wak(u8dbias);
+ uart_printf(&gsUART0, "DBIAS: %u\r\n", clock_get_dig_vreg_dbias_wak());
+ } else {
+ uart_printf(&gsUART0, "Dig vreg dbias not modified\r\n");
+ }
+ if (geCpuClkSrc == CLK_XTL) {
+ gau8XtalVregDBiasUser[gu8XtalClkIdx] = u8dbias;
+ } else if (geCpuClkSrc == CLK_PLL) {
+ gau8PllVregDBiasUser[gePllCpuClkFreq] = u8dbias;
+ }
+ }
+ }
+
+ u64tckNext += _ms_to_tick(UART_PERIOD_MS);
+ }
+}
+
+// -------------- Interface functions --------------
+
+void prog_init_pro_pre() {
+ _led_init();
+ _uart_init();
+ _vregdbias_init();
+}
+
+void prog_init_app() {
+}
+
+void prog_init_pro_post() {
+}
+
+void prog_cycle_app(uint64_t u64tckNow) {
+}
+
+void prog_cycle_pro(uint64_t u64tckNow) {
+ _led_cycle(u64tckNow);
+ _uart_cycle(u64tckNow);
+}
diff --git a/examples/2cpuclock/defines.h b/examples/2cpuclock/defines.h
new file mode 100644
index 0000000..d61d4d7
--- /dev/null
+++ b/examples/2cpuclock/defines.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 SZIGETI János
+ *
+ * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3.
+ * See LICENSE or for full license details.
+ */
+#ifndef DEFINES_H
+#define DEFINES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ // TIMINGS
+ // const -- do not change this value
+#define APB_FREQ_HZ 80000000U // 80 MHz
+
+ // variables
+#define TIM0_0_DIVISOR 2U
+#define START_APP_CPU 0U
+#define SCHEDULE_FREQ_HZ 1000U // 10KHz
+
+ // derived invariants
+#define CLK_FREQ_HZ (APB_FREQ_HZ / TIM0_0_DIVISOR) // 40 MHz
+#define TICKS_PER_MS (CLK_FREQ_HZ / 1000U) // 40000
+#define TICKS_PER_US (CLK_FREQ_HZ / 1000000U) // 40
+
+#define MS2TICKS(X) ((X) * TICKS_PER_MS)
+#define HZ2APBTICKS(X) (APB_FREQ_HZ / (X))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DEFINES_H */
+
diff --git a/examples/Makefile.am b/examples/Makefile.am
index 30fed6c..949b423 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -1,4 +1,4 @@
AUTOMAKE_OPTIONS =
SUBDIRS=0blink 0button 0hello 0ledctrl 0timerint 0timgalarm 0uart 0udma \
1bme280 1i2cssd1306 1rmtblink 1rmtdht 1rmtmorse 1rmtmusic 1rmttm1637 \
-3prog1 1rmtws2812
+2cpuclock 3prog1 1rmtws2812
diff --git a/ld/esp32peripheral.ld b/ld/esp32peripheral.ld
index a453233..5d8d7d0 100644
--- a/ld/esp32peripheral.ld
+++ b/ld/esp32peripheral.ld
@@ -1,20 +1,22 @@
-PROVIDE ( gsGPIO = 0x3ff44000 );
-PROVIDE ( grIOMUX = 0x3ff49000 );
-PROVIDE ( gsDPORT = 0x3ff00000);
+PROVIDE ( gsDPORT = 0x3ff00000);
PROVIDE ( gsPIDCTRL = 0x3ff1f000);
-PROVIDE ( gsRTC = 0x3ff48000);
-PROVIDE ( gsRMT = 0x3ff56000 );
-PROVIDE ( grRMTRAM = 0x3ff56800 );
-PROVIDE ( gsTIMG0 = 0x3ff5f000);
-PROVIDE ( gsTIMG1 = 0x3ff60000);
-PROVIDE ( gsI2C0 = 0x3ff53000);
-PROVIDE ( gsI2C1 = 0x3ff67000);
-PROVIDE ( gsUART0 = 0x60000000);
-PROVIDE ( gsUART1 = 0x60010000);
-PROVIDE ( gsUART2 = 0x602E0000);
+PROVIDE ( gsGPIO = 0x3ff44000);
+PROVIDE ( gsRTC = 0x3ff48000);
+PROVIDE ( grIOMUX = 0x3ff49000);
+PROVIDE ( gsI2C0 = 0x3ff53000);
+PROVIDE ( gsRMT = 0x3ff56000);
+PROVIDE ( grRMTRAM = 0x3ff56800);
+PROVIDE ( gsEFUSE = 0x3FF5A000);
+PROVIDE ( gsTIMG0 = 0x3ff5f000);
+PROVIDE ( gsTIMG1 = 0x3ff60000);
+PROVIDE ( gprSYSCON = 0x3FF66000);
+PROVIDE ( gsI2C1 = 0x3ff67000);
+PROVIDE ( gsUART0 = 0x60000000);
+PROVIDE ( gsUART1 = 0x60010000);
+PROVIDE ( gsUART2 = 0x602E0000);
PROVIDE ( gsUART0Mapped = 0x3ff40000);
PROVIDE ( gsUART1Mapped = 0x3ff50000);
PROVIDE ( gsUART2Mapped = 0x3ff6e000);
-PROVIDE ( gsUHCI0 = 0x3FF54000);
-PROVIDE ( gsUHCI1 = 0x3FF4C000);
+PROVIDE ( gsUHCI0 = 0x3FF54000);
+PROVIDE ( gsUHCI1 = 0x3FF4C000);
PROVIDE ( _impure_ptr = 0x3ffae0b0);
diff --git a/src/Makefile.am b/src/Makefile.am
index ba8e99b..2b1ea6c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,13 +4,13 @@ libesp32basic_a_AR=$(AR) rcs
lib_LIBRARIES = libesp32basic.a
-nobase_include_HEADERS = dport.h e32b-config.h esp32types.h esp_attr.h gpio.h i2c.h \
+nobase_include_HEADERS = clock.h dport.h e32b-config.h efuse.h esp32types.h esp_attr.h gpio.h i2c.h \
iomux.h lockmgr.h main.h pidctrl.h print.h rmt.h romfunctions.h rtc.h timg.h \
typeaux.h uart.h xtutils.h \
utils/crc.h utils/i2cutils.h utils/i2ciface.h utils/rmtutils.h utils/uartutils.h utils/generators.h
nodist_include_HEADERS =
-libesp32basic_a_SOURCES = i2c.c lockmgr.c main.c rmt.c timg.c uart.c utils/crc.c utils/i2cutils.c utils/rmtutils.c utils/uartutils.c utils/generators.c
+libesp32basic_a_SOURCES = clock.c i2c.c lockmgr.c main.c rmt.c timg.c uart.c utils/crc.c utils/i2cutils.c utils/rmtutils.c utils/uartutils.c utils/generators.c
nodist_libesp32basic_a_SOURCES =
CLEANFILES =
diff --git a/src/clock.c b/src/clock.c
new file mode 100644
index 0000000..f57d4b8
--- /dev/null
+++ b/src/clock.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2026 SZIGETI János
+ *
+ * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3.
+ * See LICENSE or for full license details.
+ */
+
+#include
+#include
+
+#include "clock.h"
+#include "efuse.h"
+#include "typeaux.h"
+#include "xtutils.h"
+#include "rtc.h"
+#include "dport.h"
+#include "romfunctions.h"
+
+// ============== Defines ==============
+#define SYSCON_PRE_DIV_CNT 0
+#define SYSCON_PRE_DIV_CNT_OFS 0U
+#define SYSCON_PRE_DIV_CNT_BITS 10U
+
+#define SYSCON_XTAL_TICK_NUM 0
+#define SYSCON_XTAL_TICK_NUM_OFS 0
+#define SYSCON_XTAL_TICK_NUM_BITS 8
+
+#define RTC_CNTL_SOC_CLK_SEL 0 // use as PFX
+#define RTC_CNTL_SOC_CLK_SEL_OFS 27U
+#define RTC_CNTL_SOC_CLK_SEL_BITS 2U
+
+#define DPORT_CPU_CPUPERIOD_SEL 0 // use as PFX
+#define DPORT_CPU_CPUPERIOD_SEL_OFS 0U
+#define DPORT_CPU_CPUPERIOD_SEL_BITS 2U
+
+#define RTC_CNTL_DIG_VREG_DBIAS_WAK 0 // use as PFX
+#define RTC_CNTL_DIG_VREG_DBIAS_WAK_OFS 11
+#define RTC_CNTL_DIG_VREG_DBIAS_WAK_BITS 3
+
+#define DBIAS_1V00 2U
+#define DBIAS_1V10 4U
+#define DBIAS_1V25 7U
+
+#define I2C_BBPLL_ADDR_LREF 2 // div_ref, div10_8, lref
+#define I2C_BBPLL_ADDR_DIV7_0 3 // div7_0
+#define I2C_BBPLL_ADDR_DCUR 5 // dcur, bw
+#define I2C_BBPLL_ADDR_ENDIV5 11
+#define I2C_BBPLL_ADDR_BBADC_DSNP 9
+
+#define I2CBBPLL_DIVREF_ADDR I2C_BBPLL_ADDR_LREF
+#define I2CBBPLL_DIVREF_OFS 0
+#define I2CBBPLL_DIVREF_BITS 4
+#define I2CBBPLL_DIV7_0_ADDR I2C_BBPLL_ADDR_DIV7_0
+#define I2CBBPLL_DIV7_0_OFS 0
+#define I2CBBPLL_DIV7_0_BITS 8
+#define I2CBBPLL_DIV10_8_ADDR I2C_BBPLL_ADDR_LREF
+#define I2CBBPLL_DIV10_8_OFS 4
+#define I2CBBPLL_DIV10_8_BITS 3
+#define I2CBBPLL_LREF_ADDR I2C_BBPLL_ADDR_LREF
+#define I2CBBPLL_LREF_OFS 7
+#define I2CBBPLL_LREF_BITS 1
+#define I2CBBPLL_DCUR_ADDR I2C_BBPLL_ADDR_DCUR
+#define I2CBBPLL_DCUR_OFS 0
+#define I2CBBPLL_DCUR_BITS 3
+#define I2CBBPLL_BW_ADDR I2C_BBPLL_ADDR_DCUR
+#define I2CBBPLL_BW_OFS 6
+#define I2CBBPLL_BW_BITS 2
+
+// ============== Local types ==============
+
+typedef struct {
+ uint8_t divRef; // 4bits
+ uint8_t div7_0;
+ uint8_t div10_8;
+ uint8_t lref;
+ uint8_t dcur;
+ uint8_t bw;
+ uint8_t endiv5;
+ uint8_t bbadcDsmpVal;
+} SPllValues;
+
+// ============== Local module-wide variables ==============
+static const SPllValues gasBbPllValues[] = {
+ {0, 32, 0, 0, 6, 3, 0x43, 0x84}, // 320/160MHz
+ {0, 28, 0, 0, 6, 3, 0xC3, 0x74} // 480MHz
+};
+
+// ============== Internal function declarations ==============
+// efuse register should be read only once (as it does not change its value)
+
+static uint8_t _dbias240mhz() {
+ static bool bFirst = true;
+ static uint8_t u8Ret;
+ if (bFirst) {
+ bFirst = false;
+ u8Ret = DBIAS_1V25 - FIELD_GET(gsEFUSE.BLK0RDATA[EFUSE_VOL_LVL_HP_INV_BLK0REG], EFUSE_VOL_LVL_HP_INV);
+ }
+ return u8Ret;
+}
+// ============== Implementation ==============
+// -------------- Internal functions --------------
+
+// -------------- Interface functions --------------
+
+uint8_t clock_get_dig_vreg_dbias_wak() {
+ return FIELD_GET(gsRTC.VREG, RTC_CNTL_DIG_VREG_DBIAS_WAK);
+}
+
+void clock_set_dig_vreg_dbias_wak(uint8_t u8DigDbias) {
+ gsRTC.VREG = FIELD_REPLACE(gsRTC.VREG, u8DigDbias, RTC_CNTL_DIG_VREG_DBIAS_WAK);
+}
+
+ECpuClockSource clock_get_cpuclksrc() {
+ return FIELD_GET(gsRTC.CLK_CONF, RTC_CNTL_SOC_CLK_SEL);
+}
+
+EPllCpuClockFreq clock_get_pllcpufreq() {
+ return FIELD_GET(gsDPORT.CPU_PER_CONF, DPORT_CPU_CPUPERIOD_SEL);
+};
+
+void clock_switch_to_xtl(uint16_t u16Divisor, uint8_t u8RefDivisor) {
+ gprSYSCON[0] = FIELD_REPLACE(gprSYSCON[0], u16Divisor - 1, SYSCON_PRE_DIV_CNT);
+ gprSYSCON[1] = FIELD_REPLACE(gprSYSCON[1], u8RefDivisor - 1, SYSCON_XTAL_TICK_NUM);
+ gsRTC.CLK_CONF = FIELD_REPLACE(gsRTC.CLK_CONF, CLK_XTL, RTC_CNTL_SOC_CLK_SEL);
+ uint8_t u8DigDBias = (1 < u16Divisor) ? DBIAS_1V00 : DBIAS_1V10;
+ clock_set_dig_vreg_dbias_wak(u8DigDBias);
+}
+
+void clock_switch_to_pll(EPllCpuClockFreq eFreq) {
+ // get current clk src
+ ECpuClockSource eClkSrc = clock_get_cpuclksrc();
+
+ // set XTAL as clk src
+ if (eClkSrc != CLK_XTL) {
+ gsRTC.CLK_CONF = FIELD_REPLACE(gsRTC.CLK_CONF, CLK_XTL, RTC_CNTL_SOC_CLK_SEL);
+ esp_rom_delay_us(1);
+ }
+
+ // set voltage
+ {
+ uint8_t u8DigDBias = (eFreq < PLLCPUFREQ_240MHZ) ? DBIAS_1V10 : _dbias240mhz();
+ clock_set_dig_vreg_dbias_wak(u8DigDBias);
+ }
+
+ esp_rom_delay_us(1);
+
+ // bbpll configuration
+ {
+ const SPllValues *psVals = &gasBbPllValues[(eFreq < PLLCPUFREQ_240MHZ) ? 0 : 1];
+ // div_ref, div10_8, lref
+ uint8_t lref = FIELD_MASKNSHIFT(psVals->divRef, I2CBBPLL_DIVREF);
+ lref |= FIELD_MASKNSHIFT(psVals->div10_8, I2CBBPLL_DIV10_8);
+ lref |= FIELD_MASKNSHIFT(psVals->lref, I2CBBPLL_LREF);
+ esp_rom_regi2c_write(0x66, 4, I2C_BBPLL_ADDR_LREF, lref);
+
+ uint8_t div7_0 = FIELD_MASKNSHIFT(psVals->div7_0, I2CBBPLL_DIV7_0);
+ esp_rom_regi2c_write(0x66, 4, I2C_BBPLL_ADDR_DIV7_0, div7_0);
+
+ uint8_t dcur = FIELD_MASKNSHIFT(psVals->dcur, I2CBBPLL_DCUR);
+ dcur |= FIELD_MASKNSHIFT(psVals->bw, I2CBBPLL_BW);
+ esp_rom_regi2c_write(0x66, 4, I2C_BBPLL_ADDR_DCUR, dcur);
+
+ esp_rom_regi2c_write(0x66, 4, I2C_BBPLL_ADDR_ENDIV5, psVals->endiv5);
+ esp_rom_regi2c_write(0x66, 4, I2C_BBPLL_ADDR_BBADC_DSNP, psVals->bbadcDsmpVal);
+ }
+ esp_rom_delay_us(1);
+
+ // set frequency
+ gsDPORT.CPU_PER_CONF = FIELD_REPLACE(gsDPORT.CPU_PER_CONF, eFreq, DPORT_CPU_CPUPERIOD_SEL);
+
+ esp_rom_delay_us(1);
+
+ // set PLL as clk src
+ gsRTC.CLK_CONF = FIELD_REPLACE(gsRTC.CLK_CONF, CLK_PLL, RTC_CNTL_SOC_CLK_SEL);
+}
diff --git a/src/clock.h b/src/clock.h
new file mode 100644
index 0000000..7ae7453
--- /dev/null
+++ b/src/clock.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2026 SZIGETI János
+ *
+ * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3.
+ * See LICENSE or for full license details.
+ */
+
+#ifndef CLOCK_H
+#define CLOCK_H
+
+#include "esp32types.h"
+
+/**
+ * @file
+ * Clock-related types and functions.
+ * Note, not to confuse with General-purpose Timers, @ref timg.h "TIMG"!
+ * @see timg.h
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ // ============== Defines ==============
+
+
+ // ============== Types ==============
+
+ typedef enum {
+ CLK_XTL = 0,
+ CLK_PLL,
+ CLK_RC_FAST,
+ CLK_APLL
+ } ECpuClockSource; ///< Clock source of the CPU.
+
+ typedef enum {
+ PLLCPUFREQ_80MHZ = 0,
+ PLLCPUFREQ_160MHZ,
+ PLLCPUFREQ_240MHZ,
+ PLLCPUFREQ_INVALID
+ } EPllCpuClockFreq; ///< PLL based CPU clock frequencies.
+
+
+ // ============== Global values / References ==============
+ extern Reg gprSYSCON[32];
+
+ // ============== Inline interface functions ==============
+
+ // ============== Interface functions ==============
+ uint8_t clock_get_dig_vreg_dbias_wak();
+ void clock_set_dig_vreg_dbias_wak(uint8_t u8DigDbias);
+ ECpuClockSource clock_get_cpuclksrc();
+ EPllCpuClockFreq clock_get_pllcpufreq();
+ void clock_switch_to_xtl(uint16_t u16Divisor, uint8_t u8RefDivisor);
+ void clock_switch_to_pll(EPllCpuClockFreq eFreq);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CLOCK_H */
+
diff --git a/src/efuse.h b/src/efuse.h
new file mode 100644
index 0000000..d8feb4b
--- /dev/null
+++ b/src/efuse.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2026 SZIGETI János
+ *
+ * This file is part of Bilis ESP32 Basic, which is released under GNU General Public License.version 3.
+ * See LICENSE or for full license details.
+ */
+
+#ifndef EFUSE_H
+#define EFUSE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include "esp32types.h"
+
+ // ============== Defines ==============
+#define EFUSE_VOL_LVL_HP_INV 0 // use as PFX
+#define EFUSE_VOL_LVL_HP_INV_BLK0REG 5U
+#define EFUSE_VOL_LVL_HP_INV_OFS 22U
+#define EFUSE_VOL_LVL_HP_INV_BITS 2U
+
+#define EFUSE_DIG_VOL_L6 0 // use as PFX
+#define EFUSE_DIG_VOL_L6_BLK0REG 5U
+#define EFUSE_DIG_VOL_L6_OFS 24U
+#define EFUSE_DIG_VOL_L6_BITS 4
+
+ // ============== Types ==============
+ typedef struct {
+ Reg BLK0RDATA[7];
+ Reg rsvd_x20[5];
+ Reg BLK1RDATA[8];
+ Reg BLK2RDATA[8];
+ Reg BLK3RDATA[8];
+ Reg rsvd_x98[0x60];
+ Reg CLK;
+ Reg CONF;
+ Reg CMD;
+ Reg INT_RAW;
+ Reg INT_ST;
+ Reg INT_ENA;
+ Reg INT_CLR;
+ Reg DAC_CONF;
+ Reg DEC_STATUS;
+ } EFUSE_Type;
+
+ // ============== Global values / References ==============
+ extern EFUSE_Type gsEFUSE;
+
+ // ============== Inline interface functions ==============
+
+ // ============== Interface functions ==============
+
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EFUSE_H */
+
diff --git a/src/esp32types.h b/src/esp32types.h
index f3bfa6a..392964e 100644
--- a/src/esp32types.h
+++ b/src/esp32types.h
@@ -14,6 +14,16 @@ extern "C" {
#include
// ============== Defines ==============
+/// Transforms number of bits into bitmask, e.g., BITS2MASK(3): 7, BITS2MASK(5): 31
+#define BITS2MASK(X) ( ( 1 << ( X ) ) - 1)
+/// Get a field (shifted to LSB) form a (register) value, e.g., with FOO_BITS=3 and FOO_OFS=2, FIELD_GET(0x2C, FOO): 3 (bits2..4)
+#define FIELD_GET(VAL,FIELD) ( ( (VAL) >> FIELD##_OFS ) & BITS2MASK( FIELD##_BITS ) )
+/// Get the bitmask of a given field.
+#define FIELD_MASK(PFX) ( BITS2MASK( PFX##_BITS ) << PFX##_OFS )
+/// Take a field value (X), first crop it to the given bitsize, then shift it to its position (offset).
+#define FIELD_MASKNSHIFT(X,PFX) ( ( X ) & BITS2MASK( PFX##_BITS ) << PFX##_OFS )
+/// Take a register value (VAL), and replace its field (PFX) with a given value (X).
+#define FIELD_REPLACE(VAL,X,PFX) ( ( ( VAL ) & ~( BITS2MASK( PFX##_BITS ) << PFX##_OFS ) ) | ( ( ( X ) & BITS2MASK( PFX##_BITS )) << PFX##_OFS ) )
// ============== Types ==============
diff --git a/src/romfunctions.h b/src/romfunctions.h
index 4e7dfb4..a145ed8 100644
--- a/src/romfunctions.h
+++ b/src/romfunctions.h
@@ -24,6 +24,7 @@ extern "C" {
void ets_isr_mask(uint32_t mask);
void ets_isr_unmask(uint32_t mask);
+ void esp_rom_delay_us(uint32_t u32Delay);
void *_xtos_set_interrupt_handler(int irq_number, void* function);
void *_xtos_set_interrupt_handler_arg(int irq_number, void* function, int argument);
@@ -38,6 +39,13 @@ extern "C" {
extern uint32_t xthal_get_ccompare(int iReg);
void xthal_set_intclear(uint32_t u32IntMask);
+// taken from ESP-IDF (v5.5.2) esp_rom_regi2c.h
+ // (block, hostid) pair is (0x66, 4) for BBPLL and (0x6D, 3) for audio PLL
+uint8_t esp_rom_regi2c_read(uint8_t block, uint8_t host_id, uint8_t reg_add);
+uint8_t esp_rom_regi2c_read_mask(uint8_t block, uint8_t host_id, uint8_t reg_add, uint8_t msb, uint8_t lsb);
+void esp_rom_regi2c_write(uint8_t block, uint8_t host_id, uint8_t reg_add, uint8_t data);
+void esp_rom_regi2c_write_mask(uint8_t block, uint8_t host_id, uint8_t reg_add, uint8_t msb, uint8_t lsb, uint8_t data);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/uart.h b/src/uart.h
index 04a9425..6a6e2a1 100644
--- a/src/uart.h
+++ b/src/uart.h
@@ -20,6 +20,10 @@ extern "C" {
// Y: source CLK frequency (APB or REF)
#define UART_HZ2CLKDIV(X, Y) UART_TCK2CLKDIV(((Y) << 4) / (X))
+#define UART_TICK_REF_ALWAYS_ON 0
+#define UART_TICK_REF_ALWAYS_ON_OFS 27U
+#define UART_TICK_REF_ALWAYS_ON_BITS 1U
+
// ============== Types ==============
typedef enum {