diff --git a/builder/build.go b/builder/build.go index e1634a3b06..a598f01965 100644 --- a/builder/build.go +++ b/builder/build.go @@ -1042,7 +1042,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if err != nil { return result, err } - case "esp32", "esp32-img", "esp32c3", "esp8266": + case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266": // Special format for the ESP family of chips (parsed by the ROM // bootloader). result.Binary = filepath.Join(tmpdir, "main"+outext) diff --git a/builder/builder_test.go b/builder/builder_test.go index 6b84b10070..8e62d9183c 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -28,6 +28,7 @@ func TestClangAttributes(t *testing.T) { "cortex-m4", "cortex-m7", "esp32c3", + "esp32s3", "fe310", "gameboy-advance", "k210", diff --git a/builder/esp.go b/builder/esp.go index 67f9384d43..c866a5c316 100644 --- a/builder/esp.go +++ b/builder/esp.go @@ -100,11 +100,12 @@ func makeESPFirmwareImage(infile, outfile, format string) error { chip_id := map[string]uint16{ "esp32": 0x0000, "esp32c3": 0x0005, + "esp32s3": 0x0009, }[chip] // Image header. switch chip { - case "esp32", "esp32c3": + case "esp32", "esp32c3", "esp32s3": // Header format: // https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71 // Note: not adding a SHA256 hash as the binary is modified by diff --git a/src/internal/task/task_stack_esp32.go b/src/internal/task/task_stack_esp32.go index e0bfa5b447..e32df88e39 100644 --- a/src/internal/task/task_stack_esp32.go +++ b/src/internal/task/task_stack_esp32.go @@ -1,4 +1,4 @@ -//go:build scheduler.tasks && esp32 +//go:build scheduler.tasks && (esp32 || esp32s3) package task diff --git a/src/machine/machine_esp32s3.go b/src/machine/machine_esp32s3.go new file mode 100644 index 0000000000..65261bd3c8 --- /dev/null +++ b/src/machine/machine_esp32s3.go @@ -0,0 +1,312 @@ +//go:build esp32s3 + +package machine + +import ( + "device/esp" + "errors" + "runtime/volatile" + "unsafe" +) + +const deviceName = esp.Device + +const xtalClock = 40_000000 // 40MHz +const apbClock = 80_000000 // 80MHz +const cryptoPWMClock = 160_000000 // 160MHz + +// GetCPUFrequency returns the current CPU frequency of the chip. +func GetCPUFrequency() (uint32, error) { + switch esp.SYSTEM.GetSYSCLK_CONF_SOC_CLK_SEL() { + case 0: + return xtalClock / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil + case 1: + switch esp.SYSTEM.GetCPU_PER_CONF_CPUPERIOD_SEL() { + case 0: + return 80e6, nil + case 1: + return 160e6, nil + case 2: + // If esp.SYSTEM.GetCPU_PER_CONF_PLL_FREQ_SEL() == 1, this is undefined + return 240e6, nil + } + case 2: + //RC Fast Clock + return (175e5) / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil + } + return 0, errors.New("machine: Unable to determine current cpu frequency") +} + +// SetCPUFrequency sets the frequency of the CPU to one of several targets +func SetCPUFrequency(frequency uint32) error { + // Always assume we are on PLL. Lower frequencies can be set with a different + // clock source, but this will change the behavior of APB clock and Crypto PWM + // clock + //esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1) + + switch frequency { + case 80_000000: + esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(0) + esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0) // Reduce PLL freq when possible + return nil + case 160_000000: + esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(1) + esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0) + return nil + case 240_000000: + esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1) // Increase PLL freq when needed + esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2) + return nil + } + return errors.New("machine: Unsupported CPU frequency selected. Supported: 80, 160, 240 MHz") +} + +var ( + ErrInvalidSPIBus = errors.New("machine: invalid SPI bus") +) + +const ( + PinOutput PinMode = iota + PinInput + PinInputPullup + PinInputPulldown +) + +// Hardware pin numbers +const ( + GPIO0 Pin = 0 + GPIO1 Pin = 1 + GPIO2 Pin = 2 + GPIO3 Pin = 3 + GPIO4 Pin = 4 + GPIO5 Pin = 5 + GPIO6 Pin = 6 + GPIO7 Pin = 7 + GPIO8 Pin = 8 + GPIO9 Pin = 9 + GPIO10 Pin = 10 + GPIO11 Pin = 11 + GPIO12 Pin = 12 + GPIO13 Pin = 13 + GPIO14 Pin = 14 + GPIO15 Pin = 15 + GPIO16 Pin = 16 + GPIO17 Pin = 17 + GPIO18 Pin = 18 + GPIO19 Pin = 19 + GPIO20 Pin = 20 + GPIO21 Pin = 21 + GPIO26 Pin = 26 + GPIO27 Pin = 27 + GPIO28 Pin = 28 + GPIO29 Pin = 29 + GPIO30 Pin = 30 + GPIO31 Pin = 31 + GPIO32 Pin = 32 + GPIO33 Pin = 33 + GPIO34 Pin = 34 + GPIO35 Pin = 35 + GPIO36 Pin = 36 + GPIO37 Pin = 37 + GPIO38 Pin = 38 + GPIO39 Pin = 39 + GPIO40 Pin = 40 + GPIO41 Pin = 41 + GPIO42 Pin = 42 + GPIO43 Pin = 43 + GPIO44 Pin = 44 + GPIO45 Pin = 45 + GPIO46 Pin = 46 + GPIO47 Pin = 47 + GPIO48 Pin = 48 +) + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + // Output function 256 is a special value reserved for use as a regular GPIO + // pin. Peripherals (SPI etc) can set a custom output function by calling + // lowercase configure() instead with a signal name. + p.configure(config, 256) +} + +// configure is the same as Configure, but allows for setting a specific input +// or output signal. +// Signals are always routed through the GPIO matrix for simplicity. Output +// signals are configured in FUNCx_OUT_SEL_CFG which selects a particular signal +// to output on a given pin. Input signals are configured in FUNCy_IN_SEL_CFG, +// which sets the pin to use for a particular input signal. +func (p Pin) configure(config PinConfig, signal uint32) { + if p == NoPin { + // This simplifies pin configuration in peripherals such as SPI. + return + } + + ioConfig := uint32(0) + + // MCU_SEL: Function 1 is always GPIO + ioConfig |= (1 << esp.IO_MUX_GPIO_MCU_SEL_Pos) + + // FUN_IE: Make this pin an input pin (always set for GPIO operation) + ioConfig |= esp.IO_MUX_GPIO_FUN_IE + + // DRV: Set drive strength to 20 mA as a default. Pins 17 and 18 are special + var drive uint32 + if p == GPIO17 || p == GPIO18 { + drive = 1 // 20 mA + } else { + drive = 2 // 20 mA + } + ioConfig |= (drive << esp.IO_MUX_GPIO_FUN_DRV_Pos) + + // WPU/WPD: Select pull mode. + if config.Mode == PinInputPullup { + ioConfig |= esp.IO_MUX_GPIO_FUN_WPU + } else if config.Mode == PinInputPulldown { + ioConfig |= esp.IO_MUX_GPIO_FUN_WPD + } + + // Set configuration + ioRegister := p.ioMuxReg() + ioRegister.Set(ioConfig) + + switch config.Mode { + case PinOutput: + // Set the 'output enable' bit. + if p < 32 { + esp.GPIO.ENABLE_W1TS.Set(1 << p) + } else { + esp.GPIO.ENABLE1_W1TS.Set(1 << (p - 32)) + } + // Set the signal to read the output value from. It can be a peripheral + // output signal, or the special value 256 which indicates regular GPIO + // usage. + p.outFunc().Set(signal) + case PinInput, PinInputPullup, PinInputPulldown: + // Clear the 'output enable' bit. + if p < 32 { + esp.GPIO.ENABLE_W1TC.Set(1 << p) + } else { + esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32)) + } + if signal != 256 { + // Signal is a peripheral function (not a simple GPIO). Connect this + // signal to the pin. + // Note that outFunc and inFunc work in the opposite direction. + // outFunc configures a pin to use a given output signal, while + // inFunc specifies a pin to use to read the signal from. + inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)<>16)&0xff >= 128 { + // Read UART_TXFIFO_CNT from the status register, which indicates how + // many bytes there are in the transmit buffer. Wait until there are + // less than 128 bytes in this buffer (the default buffer size). + } + uart.Bus.FIFO.Set(uint32(b)) + return nil +} + +func (uart *UART) flush() {} + +// TODO: SPI diff --git a/src/runtime/runtime_esp32s3.go b/src/runtime/runtime_esp32s3.go new file mode 100644 index 0000000000..35cd26da85 --- /dev/null +++ b/src/runtime/runtime_esp32s3.go @@ -0,0 +1,82 @@ +//go:build esp32s3 + +package runtime + +import ( + "device/esp" +) + +// This is the function called on startup after the flash (IROM/DROM) is +// initialized and the stack pointer has been set. +// +//export main +func main() { + // This initialization configures the following things: + // * It disables all watchdog timers. They might be useful at some point in + // the future, but will need integration into the scheduler. For now, + // they're all disabled. + // * It sets the CPU frequency to 240MHz, which is the maximum speed allowed + // for this CPU. Lower frequencies might be possible in the future, but + // running fast and sleeping quickly is often also a good strategy to save + // power. + // TODO: protect certain memory regions, especially the area below the stack + // to protect against stack overflows. See + // esp_cpu_configure_region_protection in ESP-IDF. + + // Disable RTC watchdog. + esp.RTC_CNTL.WDTWPROTECT.Set(0x50D83AA1) + esp.RTC_CNTL.WDTCONFIG0.Set(0) + esp.RTC_CNTL.WDTWPROTECT.Set(0x0) // Re-enable write protect + + // Disable Timer 0 watchdog. + esp.TIMG1.WDTWPROTECT.Set(0x50D83AA1) // write protect + esp.TIMG1.WDTCONFIG0.Set(0) // disable TG0 WDT + esp.TIMG1.WDTWPROTECT.Set(0x0) // Re-enable write protect + + esp.TIMG0.WDTWPROTECT.Set(0x50D83AA1) // write protect + esp.TIMG0.WDTCONFIG0.Set(0) // disable TG0 WDT + esp.TIMG0.WDTWPROTECT.Set(0x0) // Re-enable write protect + + // Disable super watchdog. + esp.RTC_CNTL.SWD_WPROTECT.Set(0x8F1D312A) + esp.RTC_CNTL.SWD_CONF.Set(esp.RTC_CNTL_SWD_CONF_SWD_DISABLE) + esp.RTC_CNTL.SWD_WPROTECT.Set(0x0) // Re-enable write protect + + // Change CPU frequency from 20MHz to 80MHz, by switching from the XTAL to the + // PLL clock source (see table "CPU Clock Frequency" in the reference manual). + esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1) + + // Change CPU frequency from 80MHz to 240MHz by setting SYSTEM_PLL_FREQ_SEL to + // 1 and SYSTEM_CPUPERIOD_SEL to 2 (see table "CPU Clock Frequency" in the + // reference manual). + esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1) + esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2) + + // Clear bss. Repeat many times while we wait for cpu/clock to stabilize + for x := 0; x < 30; x++ { + clearbss() + } + + // Initialize main system timer used for time.Now. + initTimer() + + // Initialize the heap, call main.main, etc. + run() + + // Fallback: if main ever returns, hang the CPU. + exit(0) +} + +func abort() { + // lock up forever + print("abort called\n") +} + +//go:extern _vector_table +var _vector_table [0]uintptr + +//go:extern _sbss +var _sbss [0]byte + +//go:extern _ebss +var _ebss [0]byte diff --git a/src/runtime/runtime_esp32sx.go b/src/runtime/runtime_esp32sx.go new file mode 100644 index 0000000000..b30dc37e97 --- /dev/null +++ b/src/runtime/runtime_esp32sx.go @@ -0,0 +1,86 @@ +//go:build esp32s3 + +package runtime + +import ( + "device/esp" + "machine" + "unsafe" +) + +//type timeUnit int64 + +func putchar(c byte) { + machine.Serial.WriteByte(c) +} + +func getchar() byte { + for machine.Serial.Buffered() == 0 { + Gosched() + } + v, _ := machine.Serial.ReadByte() + return v +} + +func buffered() int { + return machine.Serial.Buffered() +} + +// Initialize .bss: zero-initialized global variables. +// The .data section has already been loaded by the ROM bootloader. +func clearbss() { + ptr := unsafe.Pointer(&_sbss) + for ptr != unsafe.Pointer(&_ebss) { + *(*uint32)(ptr) = 0 + ptr = unsafe.Add(ptr, 4) + } +} + +func initTimer() { + // Configure timer 0 in timer group 0, for timekeeping. + // EN: Enable the timer. + // INCREASE: Count up every tick (as opposed to counting down). + // DIVIDER: 16-bit prescaler, set to 2 for dividing the APB clock by two + // (40MHz). + // esp.TIMG0.T0CONFIG.Set(0 << esp.TIMG_T0CONFIG_T0_EN_Pos) + esp.TIMG0.T0CONFIG.Set(esp.TIMG_TCONFIG_EN | esp.TIMG_TCONFIG_INCREASE | 2<DRAM + + /* Constant literals and code. Loaded into IRAM for now. Eventually, most + * code should be executed directly from flash. + * Note that literals must be before code for the l32r instruction to work. + */ +.text.call_start_cpu0 : ALIGN(4) +{ + *(.literal.call_start_cpu0) + *(.text.call_start_cpu0) +} >IRAM AT >DRAM + +/* All other code and literals */ +.text : ALIGN(4) +{ + *(.literal .text) + *(.literal.* .text.*) + *(.text) + *(.text.*) +} >IRAM AT >DRAM + + /* Constant global variables. + * They are loaded in DRAM for ease of use. Eventually they should be stored + * in flash and loaded directly from there but they're kept in RAM to make + * sure they can always be accessed (even in interrupts). + */ + .rodata : ALIGN(4) + { + *(.rodata) + *(.rodata.*) + } >DRAM + + /* Mutable global variables. + */ + .data : ALIGN(4) + { + _sdata = ABSOLUTE(.); + *(.data) + *(.data.*) + _edata = ABSOLUTE(.); + } >DRAM + + /* Check that the boot ROM stack (for the APP CPU) does not overlap with the + * data that is loaded by the boot ROM. There may be ways to avoid this + * issue if it occurs in practice. + * The magic value here is _stack_sentry in the boot ROM ELF file. + */ + ASSERT(_edata < 0x3ffe1320, "the .data section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") + + /* Global variables that are mutable and zero-initialized. + * These must be zeroed at startup (unlike data, which is loaded by the + * bootloader). + */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN (4); + _sbss = ABSOLUTE(.); + *(.bss) + *(.bss.*) + . = ALIGN (4); + _ebss = ABSOLUTE(.); + } >DRAM +} + +/* For the garbage collector. + */ +_globals_start = _sdata; +_globals_end = _ebss; +_heap_start = _ebss; +_heap_end = ORIGIN(DRAM) + LENGTH(DRAM); + +_stack_size = 4K; + +/* From ESP-IDF: + * components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld + * This is the subset that is sometimes used by LLVM during codegen, and thus + * must always be present. + */ +memset = 0x400011e8; +memcpy = 0x400011f4; +memmove = 0x40001200; +memcmp = 0x4000120c; + +/* From ESP-IDF: + * components/esp_rom/esp32/ld/esp32.rom.libgcc.ld + * These are called from LLVM during codegen. The original license is Apache + * 2.0, but I believe that a list of function names and addresses can't really + * be copyrighted. + */ +__absvdi2 = 0x4000216c; +__absvsi2 = 0x40002178; +__adddf3 = 0x40002184; +__addsf3 = 0x40002190; +__addvdi3 = 0x4000219c; +__addvsi3 = 0x400021a8; +__ashldi3 = 0x400021b4; +__ashrdi3 = 0x400021c0; +__bswapdi2 = 0x400021cc; +__bswapsi2 = 0x400021d8; +__clear_cache = 0x400021e4; +__clrsbdi2 = 0x400021f0; +__clrsbsi2 = 0x400021fc; +__clzdi2 = 0x40002208; +__clzsi2 = 0x40002214; +__cmpdi2 = 0x40002220; +__ctzdi2 = 0x4000222c; +__ctzsi2 = 0x40002238; +__divdc3 = 0x40002244; +__divdf3 = 0x40002250; +__divdi3 = 0x4000225c; +__divsc3 = 0x40002268; +__divsf3 = 0x40002274; +__divsi3 = 0x40002280; +__eqdf2 = 0x4000228c; +__eqsf2 = 0x40002298; +__extendsfdf2 = 0x400022a4; +__ffsdi2 = 0x400022b0; +__ffssi2 = 0x400022bc; +__fixdfdi = 0x400022c8; +__fixdfsi = 0x400022d4; +__fixsfdi = 0x400022e0; +__fixsfsi = 0x400022ec; +__fixunsdfsi = 0x400022f8; +__fixunssfdi = 0x40002304; +__fixunssfsi = 0x40002310; +__floatdidf = 0x4000231c; +__floatdisf = 0x40002328; +__floatsidf = 0x40002334; +__floatsisf = 0x40002340; +__floatundidf = 0x4000234c; +__floatundisf = 0x40002358; +__floatunsidf = 0x40002364; +__floatunsisf = 0x40002370; +__gcc_bcmp = 0x4000237c; +__gedf2 = 0x40002388; +__gesf2 = 0x40002394; +__gtdf2 = 0x400023a0; +__gtsf2 = 0x400023ac; +__ledf2 = 0x400023b8; +__lesf2 = 0x400023c4; +__lshrdi3 = 0x400023d0; +__ltdf2 = 0x400023dc; +__ltsf2 = 0x400023e8; +__moddi3 = 0x400023f4; +__modsi3 = 0x40002400; +__muldc3 = 0x4000240c; +__muldf3 = 0x40002418; +__muldi3 = 0x40002424; +__mulsc3 = 0x40002430; +__mulsf3 = 0x4000243c; +__mulsi3 = 0x40002448; +__mulvdi3 = 0x40002454; +__mulvsi3 = 0x40002460; +__nedf2 = 0x4000246c; +__negdf2 = 0x40002478; +__negdi2 = 0x40002484; +__negsf2 = 0x40002490; +__negvdi2 = 0x4000249c; +__negvsi2 = 0x400024a8; +__nesf2 = 0x400024b4; +__paritysi2 = 0x400024c0; +__popcountdi2 = 0x400024cc; +__popcountsi2 = 0x400024d8; +__powidf2 = 0x400024e4; +__powisf2 = 0x400024f0; +__subdf3 = 0x400024fc; +__subsf3 = 0x40002508; +__subvdi3 = 0x40002514; +__subvsi3 = 0x40002520; +__truncdfsf2 = 0x4000252c; +__ucmpdi2 = 0x40002538; +__udivdi3 = 0x40002544; +__udivmoddi4 = 0x40002550; +__udivsi3 = 0x4000255c; +__udiv_w_sdiv = 0x40002568; +__umoddi3 = 0x40002574; +__umodsi3 = 0x40002580; +__unorddf2 = 0x4000258c; +__unordsf2 = 0x40002598;