summaryrefslogtreecommitdiff
path: root/src/oracle/gpio_ref.c
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-25 12:40:53 -0300
committerGabriel Schneider <[email protected]>2026-08-25 12:46:51 -0300
commitf5f8068fac59b4f16046c2022c2fc7c7e447ef4c (patch)
tree2731a3ed4e51cae09e184e25778eded5fc37d1f5 /src/oracle/gpio_ref.c
downloadesp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.tar.gz
esp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.zip
zig-p4: pure-Zig ESP32-P4 toolchain
build.zig generates the linker script and drives Zig's own LLD; tools/image.zig turns the ELF into a flashable image and tools/{rom,serial}.zig speak the mask ROM loader over the UART. No CMake, ninja, idf.py, esptool, or external linker. src/soc.zig is a comptime register model over ESP-IDF's own *_reg.h headers; src/hal/ adds peripheral sequences; src/io/ implements std.Io for the chip; src/oracle/ diffs this HAL against ESP-IDF's on the die.
Diffstat (limited to 'src/oracle/gpio_ref.c')
-rw-r--r--src/oracle/gpio_ref.c116
1 files changed, 116 insertions, 0 deletions
diff --git a/src/oracle/gpio_ref.c b/src/oracle/gpio_ref.c
new file mode 100644
index 0000000..b4070a2
--- /dev/null
+++ b/src/oracle/gpio_ref.c
@@ -0,0 +1,116 @@
+/* The reference implementation, which is ESP-IDF's own.
+ *
+ * ESP-IDF's `*_ll.h` headers are `static inline` functions over the same registers this project's
+ * Zig HAL drives. Compiled by Zig's clang for riscv32-freestanding they link into the same image as
+ * the Zig code, which is what makes a differential test possible at all: one binary, one boot, one
+ * set of clocks, both implementations, and the diff taken on the die.
+ *
+ * These wrappers exist only to give the inline functions external linkage so Zig can call them.
+ * There is no logic here - anything clever in this file would be a third implementation to doubt.
+ */
+
+/* IDF's clock and reset LL functions are shadowed by a wrapper macro that references
+ * `__DECLARE_RCC_ATOMIC_ENV`, an identifier IDF never defines anywhere; its purpose is to make an
+ * unguarded call fail to compile, because the only legal caller holds a spinlock. There is no
+ * FreeRTOS here, and core 1 is held in reset at power-on, so declaring the name is exactly as safe
+ * as the spinlock would be - and it is what IDF's own bootloader does
+ * (bootloader_support/src/bootloader_console.c:53 declares a dummy local for the same reason). */
+static int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused));
+
+#include "hal/gpio_ll.h"
+#include "soc/gpio_struct.h"
+#include "soc/io_mux_struct.h"
+
+/* Whether this translation unit was built with the ROM path switched on. The harness prints it, so
+ * that a differential run can never silently be "my registers versus the mask ROM". */
+int oracle_gpio_uses_rom_api(void)
+{
+#if HAL_CONFIG(GPIO_USE_ROM_API)
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+void oracle_gpio_set_level(unsigned pin, unsigned level)
+{
+ gpio_ll_set_level(&GPIO, pin, level);
+}
+
+int oracle_gpio_get_level(unsigned pin)
+{
+ return gpio_ll_get_level(&GPIO, pin);
+}
+
+void oracle_gpio_output_enable(unsigned pin)
+{
+ gpio_ll_output_enable(&GPIO, pin);
+}
+
+void oracle_gpio_output_disable(unsigned pin)
+{
+ gpio_ll_output_disable(&GPIO, pin);
+}
+
+void oracle_gpio_input_enable(unsigned pin)
+{
+ gpio_ll_input_enable(&GPIO, pin);
+}
+
+void oracle_gpio_input_disable(unsigned pin)
+{
+ gpio_ll_input_disable(&GPIO, pin);
+}
+
+void oracle_gpio_func_sel(unsigned pin, unsigned func)
+{
+ gpio_ll_func_sel(&GPIO, pin, func);
+}
+
+void oracle_gpio_set_drive(unsigned pin, unsigned strength)
+{
+ gpio_ll_set_drive_capability(&GPIO, pin, (gpio_drive_cap_t)strength);
+}
+
+void oracle_gpio_pullup_en(unsigned pin)
+{
+ gpio_ll_pullup_en(&GPIO, pin);
+}
+
+void oracle_gpio_pullup_dis(unsigned pin)
+{
+ gpio_ll_pullup_dis(&GPIO, pin);
+}
+
+void oracle_gpio_pulldown_en(unsigned pin)
+{
+ gpio_ll_pulldown_en(&GPIO, pin);
+}
+
+void oracle_gpio_pulldown_dis(unsigned pin)
+{
+ gpio_ll_pulldown_dis(&GPIO, pin);
+}
+
+/* Open drain, which lives in the GPIO block's own per-pin register (GPIO_PINn_PAD_DRIVER) rather
+ * than in the IO MUX pad register - a different register file for the same pad. The I2C HAL needs it
+ * because that bus is wired-AND, and a pin left push-pull shorts a shared bus against another
+ * device's driver. One bit, and expensive to get wrong. */
+void oracle_gpio_od_enable(unsigned pin)
+{
+ gpio_ll_od_enable(&GPIO, pin);
+}
+
+void oracle_gpio_od_disable(unsigned pin)
+{
+ gpio_ll_od_disable(&GPIO, pin);
+}
+
+/* Route a peripheral signal to a pad through the GPIO matrix. This is the one GPIO operation with a
+ * real sequence rather than a single field write, and therefore the one where a write-trace
+ * comparison can find something a state comparison cannot. */
+void oracle_gpio_matrix_out(unsigned pin, unsigned signal)
+{
+ gpio_ll_set_output_signal_matrix_source(&GPIO, pin, signal, false);
+ gpio_ll_set_output_enable_ctrl(&GPIO, pin, true, false);
+}