diff --git a/GNUmakefile b/GNUmakefile index ef6af6f2d..c671e3cb7 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -960,6 +960,9 @@ ifneq ($(XTENSA), 0) @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest @$(MD5SUM) test.bin + # xiao-esp32c6 + $(TINYGO) build -size short -o test.bin -target=xiao-esp32c6 examples/blinky1 + @$(MD5SUM) test.bin # xiao-esp32s3 $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1 @$(MD5SUM) test.bin diff --git a/builder/build.go b/builder/build.go index 5fe1429b8..dabbe7544 100644 --- a/builder/build.go +++ b/builder/build.go @@ -1076,7 +1076,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if err != nil { return result, err } - case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266": + case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp32c6", "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 8e62d9183..0e0a156d4 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", + "esp32c6", "esp32s3", "fe310", "gameboy-advance", diff --git a/builder/esp.go b/builder/esp.go index c866a5c31..a480b4d1e 100644 --- a/builder/esp.go +++ b/builder/esp.go @@ -100,12 +100,24 @@ func makeESPFirmwareImage(infile, outfile, format string) error { chip_id := map[string]uint16{ "esp32": 0x0000, "esp32c3": 0x0005, + "esp32c6": 0x000d, "esp32s3": 0x0009, }[chip] + // SPI flash speed/size byte (byte 3 of header): + // Upper nibble = flash size, lower nibble = flash frequency. + // The espflasher auto-detects and patches the flash size (upper nibble), + // but the frequency (lower nibble) must be correct per chip. + spiSpeedSize := map[string]uint8{ + "esp32": 0x1f, // 80MHz=0x0F, 2MB=0x10 + "esp32c3": 0x1f, // 80MHz=0x0F, 2MB=0x10 + "esp32c6": 0x10, // 80MHz=0x00, 2MB=0x10 (C6 uses different freq encoding) + "esp32s3": 0x1f, // 80MHz=0x0F, 2MB=0x10 + }[chip] + // Image header. switch chip { - case "esp32", "esp32c3", "esp32s3": + case "esp32", "esp32c3", "esp32s3", "esp32c6": // 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 @@ -126,8 +138,8 @@ func makeESPFirmwareImage(infile, outfile, format string) error { }{ magic: 0xE9, segment_count: byte(len(segments)), - spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO - spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB + spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO + spi_speed_size: spiSpeedSize, entry_addr: uint32(inf.Entry), wp_pin: 0xEE, // disable WP pin chip_id: chip_id, diff --git a/src/device/esp/esp32c6.S b/src/device/esp/esp32c6.S new file mode 100644 index 000000000..7afd39f68 --- /dev/null +++ b/src/device/esp/esp32c6.S @@ -0,0 +1,66 @@ +// This is a very minimal bootloader for the ESP32-C6. It only initializes the +// flash and then continues with the generic RISC-V initialization code, which +// in turn will call runtime.main. +// It is written in assembly (and not in a higher level language) to make sure +// it is entirely loaded into IRAM and doesn't accidentally call functions +// stored in IROM. +// +// The ESP32-C6 has a unified IRAM/DRAM address space at 0x40800000, and +// separate DROM (0x42800000) / IROM (0x42000000) flash-mapped regions. + +.section .init +.global call_start_cpu0 +.type call_start_cpu0,@function +call_start_cpu0: + // At this point: + // - The ROM bootloader is finished and has jumped to here. + // - We're running from IRAM: both IRAM and DRAM segments have been loaded + // by the ROM bootloader. + // - We have a usable stack (but not the one we would like to use). + // - No flash mappings (MMU) are set up yet. + + // Reset MMU, see bootloader_reset_mmu in the ESP-IDF. + call Cache_Suspend_ICache + mv s0, a0 // autoload value + call Cache_Invalidate_ICache_All + call Cache_MMU_Init + + // Set up flash mapping (both IROM and DROM). + // On ESP32-C6, Cache_Dbus_MMU_Set is replaced by Cache_MSPI_MMU_Set + // which has an extra "sensitive" parameter. + // C equivalent: + // Cache_MSPI_MMU_Set(0, 0, 0x42000000, 0, 64, 256, 0) + // Maps 16MB starting at 0x42000000, covering both IROM and DROM. + li a0, 0 // sensitive: no flash encryption + li a1, 0 // ext_ram: MMU_ACCESS_FLASH + li a2, 0x42000000 // vaddr: start of flash-mapped region + li a3, 0 // paddr: physical address in the flash chip + li a4, 64 // psize: always 64 (kilobytes) + li a5, 256 // num: pages (16MB / 64K = 256, covers IROM+DROM) + li a6, 0 // fixed + call Cache_MSPI_MMU_Set + + // Enable the flash cache. + mv a0, s0 // restore autoload value from Cache_Suspend_ICache call + call Cache_Resume_ICache + + // Jump to generic RISC-V initialization, which initializes the stack + // pointer and globals register. It should not return. + j _start + +.section .text.exception_vectors +.global _vector_table +.type _vector_table,@function + +_vector_table: + + .option push + .option norvc + + .rept 32 + j handleInterruptASM /* interrupt handler */ + .endr + + .option pop + +.size _vector_table, .-_vector_table diff --git a/src/machine/board_xiao-esp32c6.go b/src/machine/board_xiao-esp32c6.go new file mode 100644 index 000000000..f0021c4ca --- /dev/null +++ b/src/machine/board_xiao-esp32c6.go @@ -0,0 +1,57 @@ +//go:build xiao_esp32c6 + +// This file contains the pin mappings for the Seeed XIAO ESP32C6 board. +// +// Seeed Studio XIAO ESP32C6 is an IoT mini development board based on +// the Espressif ESP32-C6 WiFi 6 / Bluetooth 5 / 802.15.4 chip. +// +// - https://www.seeedstudio.com/Seeed-Studio-XIAO-ESP32C6-p-5884.html +// - https://wiki.seeedstudio.com/xiao_esp32c6_getting_started/ + +package machine + +// Digital Pins +const ( + D0 = GPIO0 + D1 = GPIO1 + D2 = GPIO2 + D3 = GPIO21 + D4 = GPIO22 + D5 = GPIO23 + D6 = GPIO16 + D7 = GPIO17 + D8 = GPIO19 + D9 = GPIO20 + D10 = GPIO18 +) + +// Analog pins +const ( + A0 = GPIO0 + A1 = GPIO1 + A2 = GPIO2 +) + +// UART pins +const ( + UART_TX_PIN = GPIO16 + UART_RX_PIN = GPIO17 +) + +// I2C pins +const ( + SDA_PIN = GPIO22 + SCL_PIN = GPIO23 +) + +// SPI pins +const ( + SPI_SCK_PIN = GPIO19 + SPI_SDI_PIN = GPIO20 + SPI_SDO_PIN = GPIO18 +) + +// Onboard LEDs +const ( + LED = GPIO15 +) diff --git a/src/machine/machine_esp32c6.go b/src/machine/machine_esp32c6.go new file mode 100644 index 000000000..50996dd72 --- /dev/null +++ b/src/machine/machine_esp32c6.go @@ -0,0 +1,546 @@ +//go:build esp32c6 + +package machine + +import ( + "device/esp" + "device/riscv" + "errors" + "runtime/interrupt" + "runtime/volatile" + "sync" + "unsafe" +) + +const deviceName = esp.Device +const maxPin = 31 +const cpuInterruptFromPin = 6 + +// CPUFrequency returns the current CPU frequency of the chip. +// Currently it is a fixed frequency but it may allow changing in the future. +func CPUFrequency() uint32 { + return 160e6 // 160MHz +} + +const ( + PinOutput PinMode = iota + PinInput + PinInputPullup + PinInputPulldown + PinAnalog +) + +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 + GPIO22 Pin = 22 + GPIO23 Pin = 23 + GPIO24 Pin = 24 + GPIO25 Pin = 25 + GPIO26 Pin = 26 + GPIO27 Pin = 27 + GPIO28 Pin = 28 + GPIO29 Pin = 29 + GPIO30 Pin = 30 +) + +const ( + ADC0 Pin = GPIO0 + ADC1 Pin = GPIO1 + ADC2 Pin = GPIO2 + ADC3 Pin = GPIO3 + ADC4 Pin = GPIO4 + ADC5 Pin = GPIO5 + ADC6 Pin = GPIO6 +) + +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + PinRising PinChange = iota + 1 + PinFalling + PinToggle +) + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + if p == NoPin { + // This simplifies pin configuration in peripherals such as SPI. + return + } + + var muxConfig uint32 + + // Configure this pin as a GPIO pin. + const function = 1 // function 1 is GPIO for every pin + muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos + + // FUN_IE: disable for PinAnalog (high-Z for ADC) + if config.Mode != PinAnalog { + muxConfig |= esp.IO_MUX_GPIO_FUN_IE + } + + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos + + // Select pull mode (no pulls for PinAnalog). + if config.Mode == PinInputPullup { + muxConfig |= esp.IO_MUX_GPIO_FUN_WPU + } else if config.Mode == PinInputPulldown { + muxConfig |= esp.IO_MUX_GPIO_FUN_WPD + } + + // Configure the pad with the given IO mux configuration. + p.mux().Set(muxConfig) + + // Set the output signal to the simple GPIO output. + p.outFunc().Set(0x80) + + switch config.Mode { + case PinOutput: + // Set the 'output enable' bit. + esp.GPIO.ENABLE_W1TS.Set(1 << p) + case PinInput, PinInputPullup, PinInputPulldown, PinAnalog: + // Clear the 'output enable' bit. + esp.GPIO.ENABLE_W1TC.Set(1 << p) + } +} + +// configure is the same as Configure, but allows setting a specific GPIO matrix signal. +func (p Pin) configure(config PinConfig, signal uint32) { + p.Configure(config) + if signal == 256 { + return + } + if config.Mode == PinOutput { + p.outFunc().Set(signal) + } else { + inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)) + } +} + +func initI2CExt1Clock() {} + +// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the +// output function selection. +func (p Pin) outFunc() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4)) +} + +func (p Pin) pinReg() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4))) +} + +// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input +// function selection. +func inFunc(signal uint32) *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG), uintptr(signal)*4)) +} + +// mux returns the I/O mux configuration register corresponding to the given +// GPIO pin. +func (p Pin) mux() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.IO_MUX.GPIO0), uintptr(p)*4)) +} + +// pin returns the PIN register corresponding to the given GPIO pin. +func (p Pin) pin() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.PIN0), uintptr(p)*4)) +} + +// Set the pin to high or low. +// Warning: only use this on an output pin! +func (p Pin) Set(value bool) { + if value { + reg, mask := p.portMaskSet() + reg.Set(mask) + } else { + reg, mask := p.portMaskClear() + reg.Set(mask) + } +} + +// Get returns the current value of a GPIO pin when configured as an input or as +// an output. +func (p Pin) Get() bool { + reg := &esp.GPIO.IN + return (reg.Get()>>p)&1 > 0 +} + +// Return the register and mask to enable a given GPIO pin. This can be used to +// implement bit-banged drivers. +// +// Warning: only use this on an output pin! +func (p Pin) PortMaskSet() (*uint32, uint32) { + reg, mask := p.portMaskSet() + return ®.Reg, mask +} + +// Return the register and mask to disable a given GPIO pin. This can be used to +// implement bit-banged drivers. +// +// Warning: only use this on an output pin! +func (p Pin) PortMaskClear() (*uint32, uint32) { + reg, mask := p.portMaskClear() + return ®.Reg, mask +} + +func (p Pin) portMaskSet() (*volatile.Register32, uint32) { + return &esp.GPIO.OUT_W1TS, 1 << p +} + +func (p Pin) portMaskClear() (*volatile.Register32, uint32) { + return &esp.GPIO.OUT_W1TC, 1 << p +} + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// You can pass a nil func to unset the pin change interrupt. If you do so, +// the change parameter is ignored and can be set to any value (such as 0). +// If the pin is already configured with a callback, you must first unset +// this pins interrupt before you can set a new callback. +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) { + if p >= maxPin { + return ErrInvalidInputPin + } + + if callback == nil { + // Disable this pin interrupt + p.pin().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk) + + if pinCallbacks[p] != nil { + pinCallbacks[p] = nil + } + return nil + } + + if pinCallbacks[p] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + pinCallbacks[p] = callback + + onceSetupPinInterrupt.Do(func() { + err = setupPinInterrupt() + }) + if err != nil { + return err + } + + p.pin().Set( + (p.pin().Get() & ^uint32(esp.GPIO_PIN_INT_TYPE_Msk|esp.GPIO_PIN_INT_ENA_Msk)) | + uint32(change)< 1 { + return errWrongStopBitSize + } + // - data length + uart.Bus.SetCONF0_BIT_NUM(uint32(dataBits - 5)) + // - stop bit + uart.Bus.SetCONF0_STOP_BIT_NUM(uint32(stopBits)) + // - parity check + switch parity { + case ParityNone: + uart.Bus.SetCONF0_PARITY_EN(0) + case ParityEven: + uart.Bus.SetCONF0_PARITY_EN(1) + uart.Bus.SetCONF0_PARITY(0) + case ParityOdd: + uart.Bus.SetCONF0_PARITY_EN(1) + uart.Bus.SetCONF0_PARITY(1) + } + return nil +} + +func initUARTClock(bus *esp.UART_Type) { + // On ESP32-C6, UART clock is controlled via PCR (Peripheral Clock Reset). + switch bus { + case esp.UART0: + esp.PCR.SetUART0_CONF_UART0_CLK_EN(1) + esp.PCR.SetUART0_CONF_UART0_RST_EN(0) // ensure not in reset + bus.SetCLK_CONF_RST_CORE(1) + esp.PCR.SetUART0_CONF_UART0_RST_EN(1) + esp.PCR.SetUART0_CONF_UART0_RST_EN(0) + bus.SetCLK_CONF_RST_CORE(0) + // Configure SCLK divisor in PCR + esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_NUM(0) + esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_A(0) + esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_B(0) + case esp.UART1: + esp.PCR.SetUART1_CONF_UART1_CLK_EN(1) + esp.PCR.SetUART1_CONF_UART1_RST_EN(0) + bus.SetCLK_CONF_RST_CORE(1) + esp.PCR.SetUART1_CONF_UART1_RST_EN(1) + esp.PCR.SetUART1_CONF_UART1_RST_EN(0) + bus.SetCLK_CONF_RST_CORE(0) + esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_NUM(0) + esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_A(0) + esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_B(0) + } + // wait for Core Clock to ready for configuration + for bus.GetREG_UPDATE() > 0 { + riscv.Asm("nop") + } +} + +func (uart *UART) SetBaudRate(baudRate uint32) { + // based on esp-idf + max_div := uint32((1 << 12) - 1) + sclk_div := (pplClockFreq + (max_div * baudRate) - 1) / (max_div * baudRate) + clk_div := (pplClockFreq << 4) / (baudRate * sclk_div) + uart.Bus.SetCLKDIV(clk_div >> 4) + uart.Bus.SetCLKDIV_FRAG(clk_div & 0xf) + // On C6, the SCLK divisor is in the PCR register. + switch uart.Bus { + case esp.UART0: + esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_NUM(sclk_div - 1) + case esp.UART1: + esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_NUM(sclk_div - 1) + } +} + +func (uart *UART) setupPins(config UARTConfig, regs registerSet) { + config.RX.Configure(PinConfig{Mode: PinInputPullup}) + config.TX.Configure(PinConfig{Mode: PinInputPullup}) + + // link TX with GPIO signal X (technical reference manual, GPIO matrix) + config.TX.outFunc().Set(regs.gpioMatrixSignal) + // link RX with GPIO signal X and route signals via GPIO matrix + inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX)) +} + +func (uart *UART) configureInterrupt(intrMapReg *volatile.Register32) { + // Disable all UART interrupts + uart.Bus.INT_ENA.ClearBits(0x0ffff) + + intrMapReg.Set(7) + onceUart.Do(func() { + _ = interrupt.New(7, func(i interrupt.Interrupt) { + UART0.serveInterrupt(0) + UART1.serveInterrupt(1) + }).Enable() + }) +} + +func (uart *UART) serveInterrupt(num int) { + // get interrupt status + interrutFlag := uart.Bus.INT_ST.Get() + if (interrutFlag & uartInterrupts) == 0 { + return + } + + // block UART interrupts while processing + uart.Bus.INT_ENA.ClearBits(uartInterrupts) + + if interrutFlag&esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA > 0 { + for uart.Bus.GetSTATUS_RXFIFO_CNT() > 0 { + b := uart.Bus.GetFIFO_RXFIFO_RD_BYTE() + if !uart.Buffer.Put(byte(b & 0xff)) { + uart.DataOverflowDetected = true + } + } + } + if interrutFlag&esp.UART_INT_ENA_PARITY_ERR_INT_ENA > 0 { + uart.ParityErrorDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_FRM_ERR_INT_ENA { + uart.DataErrorDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA { + uart.DataOverflowDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_GLITCH_DET_INT_ENA { + uart.DataErrorDetected = true + } + + // Clear the UART interrupt status + uart.Bus.INT_CLR.SetBits(interrutFlag) + uart.Bus.INT_CLR.ClearBits(interrutFlag) + // Enable interrupts + uart.Bus.INT_ENA.Set(uartInterrupts) +} + +const uart_empty_thresh_default = 10 + +func (uart *UART) enableTransmitter() { + uart.Bus.SetCONF0_TXFIFO_RST(1) + uart.Bus.SetCONF0_TXFIFO_RST(0) + uart.Bus.SetCONF1_TXFIFO_EMPTY_THRHD(uart_empty_thresh_default) +} + +func (uart *UART) enableReceiver() { + uart.Bus.SetCONF0_RXFIFO_RST(1) + uart.Bus.SetCONF0_RXFIFO_RST(0) + uart.Bus.SetCONF1_RXFIFO_FULL_THRHD(1) + uart.Bus.SetINT_ENA_RXFIFO_FULL_INT_ENA(1) + uart.Bus.SetINT_ENA_FRM_ERR_INT_ENA(1) + uart.Bus.SetINT_ENA_PARITY_ERR_INT_ENA(1) + uart.Bus.SetINT_ENA_GLITCH_DET_INT_ENA(1) + uart.Bus.SetINT_ENA_RXFIFO_OVF_INT_ENA(1) +} + +func (uart *UART) writeByte(b byte) error { + for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 { + // Wait until there is space in the transmit buffer. + } + uart.Bus.FIFO.Set(uint32(b)) + return nil +} + +func (uart *UART) flush() {} diff --git a/src/machine/machine_esp32c6_usb.go b/src/machine/machine_esp32c6_usb.go new file mode 100644 index 000000000..aa9bc988b --- /dev/null +++ b/src/machine/machine_esp32c6_usb.go @@ -0,0 +1,250 @@ +//go:build esp32c6 + +package machine + +import ( + "device/esp" + "errors" + "machine/usb" + "machine/usb/descriptor" + "runtime/interrupt" +) + +// USB Serial/JTAG Controller +// The ESP32-C6 has the same built-in USB Serial/JTAG hardware IP as the +// ESP32-C3. The only difference at the machine level is how the peripheral +// clock is enabled: C3 uses SYSTEM.PERIP_CLK_EN0, while C6 uses the PCR +// (Peripheral Clock Reset) block. + +const cpuInterruptFromUSB = 10 + +type USB_DEVICE struct { + Bus *esp.USB_DEVICE_Type + Buffer *RingBuffer + txPending bool // unflushed data in the EP1 TX FIFO + txStalled bool // set when flushAndWait fails (no host reading); cleared when FIFO becomes writable +} + +var ( + _USBCDC = &USB_DEVICE{ + Bus: esp.USB_DEVICE, + Buffer: NewRingBuffer(), + } + + USBCDC Serialer = _USBCDC +) + +var ( + errUSBWrongSize = errors.New("USB: invalid write size") + errUSBCouldNotWriteAllData = errors.New("USB: could not write all data") +) + +type Serialer interface { + WriteByte(c byte) error + Write(data []byte) (n int, err error) + Configure(config UARTConfig) error + Buffered() int + ReadByte() (byte, error) + DTR() bool + RTS() bool +} + +var usbConfigured bool + +// USBDevice provides a stub USB device for the ESP32-C6. The hardware +// only supports a fixed-function CDC-ACM serial port, so the programmable +// USB device features are no-ops. +type USBDevice struct { + initcomplete bool + InitEndpointComplete bool +} + +var USBDev = &USBDevice{} + +func (dev *USBDevice) SetStallEPIn(ep uint32) {} +func (dev *USBDevice) SetStallEPOut(ep uint32) {} +func (dev *USBDevice) ClearStallEPIn(ep uint32) {} +func (dev *USBDevice) ClearStallEPOut(ep uint32) {} + +// initUSB is intentionally empty — the interp phase evaluates init() +// functions at compile time and cannot access hardware registers. +// Actual hardware setup is deferred to the first Configure() call. +func initUSB() {} + +// Configure initialises the USB Serial/JTAG controller clock, pads, and +// interrupt so that received data is buffered automatically. +func (usbdev *USB_DEVICE) Configure(config UARTConfig) error { + if usbConfigured { + return nil + } + usbConfigured = true + + // Enable the USB_DEVICE peripheral clock via PCR (C6 uses PCR instead + // of SYSTEM.PERIP_CLK_EN0 that C3 used). + esp.PCR.SetUSB_DEVICE_CONF_USB_DEVICE_CLK_EN(1) + esp.PCR.SetUSB_DEVICE_CONF_USB_DEVICE_RST_EN(0) + + // Ensure internal PHY is selected and USB pads are enabled. + usbdev.Bus.SetCONF0_PHY_SEL(0) + usbdev.Bus.SetCONF0_USB_PAD_ENABLE(1) + usbdev.Bus.SetCONF0_DP_PULLUP(1) + + // Clear any pending interrupts. + usbdev.Bus.INT_CLR.Set(0xFFFFFFFF) + + // Enable the RX-packet-received interrupt. + usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1) + + // Map the USB peripheral interrupt to CPU interrupt cpuInterruptFromUSB. + esp.INTERRUPT_CORE0.SetUSB_INTR_MAP(cpuInterruptFromUSB) + + _ = interrupt.New(cpuInterruptFromUSB, func(interrupt.Interrupt) { + _USBCDC.handleInterrupt() + }).Enable() + + return nil +} + +// ensureConfigured triggers lazy initialization on first use. +func (usbdev *USB_DEVICE) ensureConfigured() { + if !usbConfigured { + usbdev.Configure(UARTConfig{}) + } +} + +// handleInterrupt drains the hardware RX FIFO into the software ring buffer. +func (usbdev *USB_DEVICE) handleInterrupt() { + intStatus := usbdev.Bus.INT_ST.Get() + + // Disable the RX interrupt to prevent re-triggering while we drain. + usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(0) + + if intStatus&esp.USB_DEVICE_INT_ST_SERIAL_OUT_RECV_PKT_INT_ST != 0 { + for usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 { + b := byte(usbdev.Bus.EP1.Get()) + usbdev.Buffer.Put(b) + } + usbdev.Bus.SetINT_CLR_SERIAL_OUT_RECV_PKT_INT_CLR(1) + } + + // Re-enable the RX interrupt. + usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1) +} + +func (usbdev *USB_DEVICE) WriteByte(c byte) error { + usbdev.ensureConfigured() + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + if usbdev.txStalled { + return errUSBCouldNotWriteAllData + } + if !usbdev.flushAndWait() { + usbdev.txStalled = true + return errUSBCouldNotWriteAllData + } + } + usbdev.txStalled = false + + usbdev.Bus.EP1.Set(uint32(c)) + + if c == '\n' { + usbdev.flush() + usbdev.txPending = false + } else { + usbdev.txPending = true + } + + return nil +} + +func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) { + usbdev.ensureConfigured() + if len(data) == 0 { + return 0, nil + } + + for i, c := range data { + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + if usbdev.txStalled { + return i, errUSBCouldNotWriteAllData + } + if !usbdev.flushAndWait() { + usbdev.txStalled = true + return i, errUSBCouldNotWriteAllData + } + } + usbdev.txStalled = false + usbdev.Bus.EP1.Set(uint32(c)) + } + + usbdev.flush() + usbdev.txPending = false + return len(data), nil +} + +// Buffered returns the number of bytes waiting in the receive ring buffer. +func (usbdev *USB_DEVICE) Buffered() int { + usbdev.ensureConfigured() + if usbdev.txPending { + usbdev.flush() + usbdev.txPending = false + } + return int(usbdev.Buffer.Used()) +} + +// ReadByte returns a byte from the receive ring buffer. +func (usbdev *USB_DEVICE) ReadByte() (byte, error) { + b, ok := usbdev.Buffer.Get() + if !ok { + return 0, nil + } + return b, nil +} + +func (usbdev *USB_DEVICE) DTR() bool { + return false +} + +func (usbdev *USB_DEVICE) RTS() bool { + return false +} + +// flush signals WR_DONE to tell the hardware to send the data that has +// been written to the EP1 FIFO. +func (usbdev *USB_DEVICE) flush() { + usbdev.Bus.SetEP1_CONF_WR_DONE(1) +} + +// flushAndWait signals WR_DONE and waits for the EP1 FIFO to become +// writable again. Returns false if the FIFO is still locked after the +// timeout (no host reading). +func (usbdev *USB_DEVICE) flushAndWait() bool { + usbdev.Bus.SetEP1_CONF_WR_DONE(1) + for i := 0; i < 50000; i++ { + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() != 0 { + return true + } + } + return false +} + +// FlushSerial flushes any pending USB serial TX data. +func FlushSerial() { + if _USBCDC.txPending { + _USBCDC.flush() + _USBCDC.txPending = false + } +} + +// ConfigureUSBEndpoint is a no-op on ESP32-C6 — the hardware does not +// support programmable USB endpoints. +func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) { +} + +// SendZlp is a no-op on ESP32-C6. +func SendZlp() { +} + +// SendUSBInPacket is a no-op on ESP32-C6. +func SendUSBInPacket(ep uint32, data []byte) bool { + return false +} diff --git a/src/runtime/interrupt/interrupt_esp32c6.go b/src/runtime/interrupt/interrupt_esp32c6.go new file mode 100644 index 000000000..83cc5e54d --- /dev/null +++ b/src/runtime/interrupt/interrupt_esp32c6.go @@ -0,0 +1,250 @@ +//go:build esp32c6 + +package interrupt + +import ( + "device/riscv" + "errors" + "runtime/volatile" + "unsafe" +) + +//go:extern tinygo_saved_ra +var tinygo_saved_ra uintptr + +// plicType maps the ESP32-C6 PLIC (Platform-Level Interrupt Controller) +// machine-mode registers. The C6 uses the PLIC — not the INTPRI/INTC block +// that the ESP32-C3 uses — as its CPU interrupt controller +// (SOC_INT_PLIC_SUPPORTED). The INTPRI registers at 0x600c5000 are vestigial +// backward-compatibility aliases on the C6 and are not wired to the CPU, so +// writing them never delivers an interrupt. +type plicType struct { + MXINT_ENABLE volatile.Register32 // 0x00 bit N enables CPU interrupt line N + MXINT_TYPE volatile.Register32 // 0x04 bit N: 1=edge, 0=level + MXINT_CLEAR volatile.Register32 // 0x08 edge acknowledge + MXINT_EIP_STATUS volatile.Register32 // 0x0C pending status (read-only) + MXINT_PRI [32]volatile.Register32 // 0x10..0x8C per-line priority (4 bits) + MXINT_THRESH volatile.Register32 // 0x90 priority threshold (8 bits) +} + +// plic points at the PLIC machine-mode register block (DR_REG_PLIC_MX_BASE). +var plic = (*plicType)(unsafe.Pointer(uintptr(0x20001000))) + +// Enable registers a CPU interrupt. +// The ESP32-C6 has 31 CPU independent interrupts (1..31). +func (i Interrupt) Enable() error { + if i.num < 1 || i.num > 31 { + return errors.New("interrupt for ESP32-C6 must be in range of 1 through 31") + } + mask := riscv.DisableInterrupts() + defer riscv.EnableInterrupts(mask) + + // Enable CPU interrupt number i.num via the PLIC. + plic.MXINT_ENABLE.SetBits(1 << i.num) + + // Set pulse interrupt type (rising edge detection). + plic.MXINT_TYPE.SetBits(1 << i.num) + + // Set default priority (must be >= threshold to be delivered). + plic.MXINT_PRI[i.num].Set(defaultThreshold) + + // Reset interrupt before re-enabling. + plic.MXINT_CLEAR.SetBits(1 << i.num) + plic.MXINT_CLEAR.ClearBits(1 << i.num) + + riscv.Asm("fence") + return nil +} + +// Adding pseudo function calls that is replaced by the compiler with the actual +// functions registered through interrupt.New. +// +//go:linkname callHandlers runtime/interrupt.callHandlers +func callHandlers(num int) + +//go:linkname signalInterrupt runtime.signalInterrupt +func signalInterrupt() + +const ( + IRQNUM_1 = 1 + iota + IRQNUM_2 + IRQNUM_3 + IRQNUM_4 + IRQNUM_5 + IRQNUM_6 + IRQNUM_7 + IRQNUM_8 + IRQNUM_9 + IRQNUM_10 + IRQNUM_11 + IRQNUM_12 + IRQNUM_13 + IRQNUM_14 + IRQNUM_15 + IRQNUM_16 + IRQNUM_17 + IRQNUM_18 + IRQNUM_19 + IRQNUM_20 + IRQNUM_21 + IRQNUM_22 + IRQNUM_23 + IRQNUM_24 + IRQNUM_25 + IRQNUM_26 + IRQNUM_27 + IRQNUM_28 + IRQNUM_29 + IRQNUM_30 + IRQNUM_31 +) + +const ( + defaultThreshold = 5 + // Priority 0 disables an interrupt on ESP32-C6. + disableThreshold = 0 +) + +//go:inline +func callHandler(n int) { + switch n { + case IRQNUM_1: + callHandlers(IRQNUM_1) + case IRQNUM_2: + callHandlers(IRQNUM_2) + case IRQNUM_3: + callHandlers(IRQNUM_3) + case IRQNUM_4: + callHandlers(IRQNUM_4) + case IRQNUM_5: + callHandlers(IRQNUM_5) + case IRQNUM_6: + callHandlers(IRQNUM_6) + case IRQNUM_7: + callHandlers(IRQNUM_7) + case IRQNUM_8: + callHandlers(IRQNUM_8) + case IRQNUM_9: + callHandlers(IRQNUM_9) + case IRQNUM_10: + callHandlers(IRQNUM_10) + case IRQNUM_11: + callHandlers(IRQNUM_11) + case IRQNUM_12: + callHandlers(IRQNUM_12) + case IRQNUM_13: + callHandlers(IRQNUM_13) + case IRQNUM_14: + callHandlers(IRQNUM_14) + case IRQNUM_15: + callHandlers(IRQNUM_15) + case IRQNUM_16: + callHandlers(IRQNUM_16) + case IRQNUM_17: + callHandlers(IRQNUM_17) + case IRQNUM_18: + callHandlers(IRQNUM_18) + case IRQNUM_19: + callHandlers(IRQNUM_19) + case IRQNUM_20: + callHandlers(IRQNUM_20) + case IRQNUM_21: + callHandlers(IRQNUM_21) + case IRQNUM_22: + callHandlers(IRQNUM_22) + case IRQNUM_23: + callHandlers(IRQNUM_23) + case IRQNUM_24: + callHandlers(IRQNUM_24) + case IRQNUM_25: + callHandlers(IRQNUM_25) + case IRQNUM_26: + callHandlers(IRQNUM_26) + case IRQNUM_27: + callHandlers(IRQNUM_27) + case IRQNUM_28: + callHandlers(IRQNUM_28) + case IRQNUM_29: + callHandlers(IRQNUM_29) + case IRQNUM_30: + callHandlers(IRQNUM_30) + case IRQNUM_31: + callHandlers(IRQNUM_31) + } +} + +//export handleInterrupt +func handleInterrupt() { + mcause := riscv.MCAUSE.Get() + exception := mcause&(1<<31) == 0 + interruptNumber := uint32(mcause & 0x1f) + + if !exception && interruptNumber > 0 { + // Save MSTATUS & MEPC, which could be overwritten by another CPU interrupt. + mstatus := riscv.MSTATUS.Get() + mepc := riscv.MEPC.Get() + // Temporarily disable this interrupt by lowering its PLIC priority + // below the threshold. + thresholdSave := plic.MXINT_PRI[interruptNumber].Get() + plic.MXINT_PRI[interruptNumber].Set(disableThreshold) + riscv.Asm("fence") + + interruptBit := uint32(1 << interruptNumber) + + // Reset pending status interrupt. + if plic.MXINT_TYPE.Get()&interruptBit != 0 { + // Edge type interrupt. + plic.MXINT_CLEAR.SetBits(interruptBit) + plic.MXINT_CLEAR.ClearBits(interruptBit) + } else { + // Level type interrupt. + plic.MXINT_CLEAR.ClearBits(interruptBit) + } + + // Enable CPU interrupts. + riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE) + + // Call registered interrupt handler(s). + callHandler(int(interruptNumber)) + + // Signal to sleepTicks that an interrupt has occurred. + signalInterrupt() + + // Disable CPU interrupts. + riscv.MSTATUS.ClearBits(riscv.MSTATUS_MIE) + + // Restore interrupt priority to enable interrupt again. + plic.MXINT_PRI[interruptNumber].Set(thresholdSave) + riscv.Asm("fence") + + // Zero MCAUSE so that interrupt.In() returns false once we + // return to normal (non-interrupt) code. + riscv.MCAUSE.Set(0) + + // Restore MSTATUS & MEPC. + riscv.MSTATUS.Set(mstatus) + riscv.MEPC.Set(mepc) + } else { + handleException(mcause) + } +} + +func handleException(mcause uintptr) { + println("*** Exception: pc:", riscv.MEPC.Get()) + println("*** Exception: code:", uint32(mcause&0x1f)) + println("*** Exception: mcause:", mcause) + println("*** Exception: ra:", tinygo_saved_ra) + switch uint32(mcause & 0x1f) { + case riscv.InstructionAccessFault: + println("*** virtual address:", riscv.MTVAL.Get()) + case riscv.IllegalInstruction: + println("*** opcode:", riscv.MTVAL.Get()) + case riscv.LoadAccessFault: + println("*** read address:", riscv.MTVAL.Get()) + case riscv.StoreOrAMOAccessFault: + println("*** write address:", riscv.MTVAL.Get()) + } + for { + riscv.Asm("wfi") + } +} diff --git a/src/runtime/runtime_esp32c6.go b/src/runtime/runtime_esp32c6.go new file mode 100644 index 000000000..5ed751be9 --- /dev/null +++ b/src/runtime/runtime_esp32c6.go @@ -0,0 +1,211 @@ +//go:build esp32c6 + +package runtime + +import ( + "device/esp" + "device/riscv" + "machine" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +// 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 160MHz, 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. + + // Disable Timer Group 0 watchdog (unlock first). + esp.TIMG0.WDTWPROTECT.Set(0x50D83AA1) + esp.TIMG0.WDTCONFIG0.Set(0) + + // Disable Timer Group 1 watchdog (unlock first). + esp.TIMG1.WDTWPROTECT.Set(0x50D83AA1) + esp.TIMG1.WDTCONFIG0.Set(0) + + // Disable LP watchdog (write-protect key first). + esp.LP_WDT.WDTWPROTECT.Set(0x50D83AA1) + esp.LP_WDT.WDTCONFIG0.Set(0) + + // Disable super watchdog. + esp.LP_WDT.SWD_WPROTECT.Set(0x50D83AA1) + esp.LP_WDT.SWD_CONF.SetBits(1 << 30) // SWD_DISABLE bit + + // Change CPU frequency to 160MHz from SPLL (480MHz). + // + // Clock tree: SPLL (480MHz) → HP root → CPU / AHB / APB + // + // Set dividers BEFORE switching the clock source so the first PLL + // cycle already arrives divided: + // HP root = SPLL / (HS_DIV_NUM+1) = 480 / 3 = 160 MHz + // CPU = HP root / (CPU_HS_DIV_NUM+1) = 160 / 1 = 160 MHz + // AHB = HP root / (AHB_HS_DIV_NUM+1) = 160 / 4 = 40 MHz + // APB = AHB / (APB_HS_DIV_NUM+1) = 40 / 1 = 40 MHz + esp.PCR.CPU_FREQ_CONF.Set(0 << 8) // CPU_HS_DIV_NUM = 0 (div1) + esp.PCR.AHB_FREQ_CONF.Set(3 << 8) // AHB_HS_DIV_NUM = 3 (div4) + esp.PCR.APB_FREQ_CONF.Set(0 << 8) // APB_HS_DIV_NUM = 0 (div1) + + // Switch to PLL: SOC_CLK_SEL = 1 (SPLL), HS_DIV_NUM = 2 (div3). + esp.PCR.SYSCLK_CONF.Set(1<<16 | 2<<8) + + clearbss() + + // Configure interrupt handler + interruptInit() + + // Initialize main system timer used for time.Now. + initTimer() + + // Initialize timer alarm interrupt for the scheduler. + initTimerInterrupt() + + // Initialize the heap, call main.main, etc. + run() + + // Fallback: if main ever returns, hang the CPU. + exit(0) +} + +func init() { + machine.InitSerial() +} + +func abort() { + for { + riscv.Asm("wfi") + } +} + +// interruptInit initializes the interrupt controller. +func interruptInit() { + mie := riscv.DisableInterrupts() + + // Reset all interrupt priorities to zero in the PLIC. + for i := 1; i < 32; i++ { + plic.MXINT_PRI[i].Set(0) + } + + // Default threshold for interrupts is 5. + plic.MXINT_THRESH.Set(5) + + // Set the interrupt address. + // Set MODE field to 1 - a vector base address. + // Note that this address must be aligned to 256 bytes. + riscv.MTVEC.Set((uintptr(unsafe.Pointer(&_vector_table))) | 1) + + // On the ESP32-C6 the CPU receives PLIC interrupts through the standard + // RISC-V mie CSR (machine interrupt-enable, 0x304). Unlike the ESP32-C3's + // INTC, the PLIC's per-line interrupts are gated by mie, so it must be + // enabled for any interrupt to reach the CPU. esp-hal does the same thing + // for the PLIC: `csrw mie, 0xffffffff`. + riscv.MIE.Set(0xffffffff) + + // Globally enable machine-mode interrupts (MSTATUS.MIE). + // + // Unlike the ESP32-C3, whose ROM bootloader hands control to the + // application with MSTATUS.MIE already set, the ESP32-C6 ROM leaves it + // cleared. Restoring the previous state (riscv.EnableInterrupts(mie)) + // would therefore leave interrupts globally disabled, so no interrupt is + // ever delivered to the CPU: timer alarms silently fall back to the + // polling loop in sleepTicks and peripheral RX interrupts (such as the + // USB Serial/JTAG controller) never fire. Set the bit explicitly instead. + _ = mie + riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE) +} + +// CPU interrupt number used for the TIMG0 timer alarm. +const timerAlarmCPUInterrupt = 9 + +var interruptPending volatile.Register8 + +func signalInterrupt() { + interruptPending.Set(1) +} + +// initTimerInterrupt routes the TIMG0 timer 0 alarm interrupt to a CPU +// interrupt and registers a handler. +func initTimerInterrupt() { + // Map the TIMG0 T0 peripheral interrupt to a CPU interrupt line. + // On C6, INTERRUPT_CORE0 is for peripheral→CPU interrupt mapping. + esp.INTERRUPT_CORE0.TG0_T0_INTR_MAP.Set(timerAlarmCPUInterrupt) + + // Enable T0 interrupt at the timer group level. + esp.TIMG0.INT_ENA_TIMERS.SetBits(1) + + // Register the interrupt handler. + interrupt.New(timerAlarmCPUInterrupt, func(interrupt.Interrupt) { + esp.TIMG0.INT_CLR_TIMERS.Set(1) + }) + + // Enable the CPU interrupt: + mie := riscv.DisableInterrupts() + + // Clear any stale pending bit. + plic.MXINT_CLEAR.SetBits(1 << timerAlarmCPUInterrupt) + plic.MXINT_CLEAR.ClearBits(1 << timerAlarmCPUInterrupt) + + // Set edge-triggered. + plic.MXINT_TYPE.SetBits(1 << timerAlarmCPUInterrupt) + + // Set priority above threshold. + plic.MXINT_PRI[timerAlarmCPUInterrupt].Set(10) + + riscv.Asm("fence") + + plic.MXINT_ENABLE.SetBits(1 << timerAlarmCPUInterrupt) + + riscv.EnableInterrupts(mie) +} + +// sleepTicks spins until the given number of ticks have elapsed, using the +// TIMG0 alarm interrupt to avoid busy-waiting for the entire duration. +func sleepTicks(d timeUnit) { + machine.FlushSerial() + target := ticks() + d + for ticks() < target { + interruptPending.Set(0) + + esp.TIMG0.T0ALARMLO.Set(uint32(target)) + esp.TIMG0.T0ALARMHI.Set(uint32(target >> 32)) + + // Enable the alarm (auto-clears when alarm fires). + esp.TIMG0.T0CONFIG.SetBits(esp.TIMG_T0CONFIG_ALARM_EN) + + for interruptPending.Get() == 0 { + if ticks() >= target { + return + } + } + } +} + +//go:extern _vector_table +var _vector_table [0]uintptr + +// plicType maps the ESP32-C6 PLIC (Platform-Level Interrupt Controller) +// machine-mode registers. The C6 uses the PLIC — not the INTPRI/INTC block +// that the ESP32-C3 uses — as its CPU interrupt controller +// (SOC_INT_PLIC_SUPPORTED). The INTPRI registers at 0x600c5000 are vestigial +// backward-compatibility aliases on the C6 and are not wired to the CPU, so +// writing them never delivers an interrupt. +type plicType struct { + MXINT_ENABLE volatile.Register32 // 0x00 bit N enables CPU interrupt line N + MXINT_TYPE volatile.Register32 // 0x04 bit N: 1=edge, 0=level + MXINT_CLEAR volatile.Register32 // 0x08 edge acknowledge + MXINT_EIP_STATUS volatile.Register32 // 0x0C pending status (read-only) + MXINT_PRI [32]volatile.Register32 // 0x10..0x8C per-line priority (4 bits) + MXINT_THRESH volatile.Register32 // 0x90 priority threshold (8 bits) +} + +// plic points at the PLIC machine-mode register block (DR_REG_PLIC_MX_BASE). +var plic = (*plicType)(unsafe.Pointer(uintptr(0x20001000))) diff --git a/src/runtime/runtime_esp32xx.go b/src/runtime/runtime_esp32xx.go index cde65b8cb..d94deb0ac 100644 --- a/src/runtime/runtime_esp32xx.go +++ b/src/runtime/runtime_esp32xx.go @@ -1,4 +1,4 @@ -//go:build esp32 || esp32c3 +//go:build esp32 || esp32c3 || esp32c6 package runtime diff --git a/targets/esp32c6.json b/targets/esp32c6.json new file mode 100644 index 000000000..bc9dc1900 --- /dev/null +++ b/targets/esp32c6.json @@ -0,0 +1,21 @@ +{ + "inherits": ["riscv32"], + "inheritable-only": true, + "features": "+32bit,+a,+c,+m,+zaamo,+zalrsc,+zmmul,-b,-d,-e,-experimental-sdext,-experimental-sdtrig,-experimental-smctr,-experimental-ssctr,-experimental-svukte,-experimental-xqcia,-experimental-xqciac,-experimental-xqcicli,-experimental-xqcicm,-experimental-xqcics,-experimental-xqcicsr,-experimental-xqciint,-experimental-xqcilo,-experimental-xqcilsm,-experimental-xqcisls,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-experimental-zvbc32e,-experimental-zvkgs,-f,-h,-relax,-sha,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smdbltrp,-smepmp,-smmpm,-smnpm,-smrnmi,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssdbltrp,-ssnpm,-sspm,-ssqosid,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-supm,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-svvptc,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xmipscmove,-xmipslsp,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zabha,-zacas,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "build-tags": ["esp32c6", "esp"], + "serial": "usb", + "rtlib": "compiler-rt", + "libc": "picolibc", + "default-stack-size": 8192, + "linkerscript": "targets/esp32c6.ld", + "extra-files": [ + "src/device/esp/esp32c6.S" + ], + "binary-format": "esp32c6", + "flash-method": "esp32jtag", + "serial-port": ["303a:1001"], + "openocd-interface": "esp_usb_jtag", + "openocd-target": "esp32c6", + "openocd-commands": ["gdb_memory_map disable"], + "gdb": ["riscv32-esp-elf-gdb"] +} diff --git a/targets/esp32c6.ld b/targets/esp32c6.ld new file mode 100644 index 000000000..d33edb256 --- /dev/null +++ b/targets/esp32c6.ld @@ -0,0 +1,190 @@ +/* Linker script for the ESP32-C6 + * + * The ESP32-C6 has a simpler memory layout than the ESP32-C3: + * - It has 512kB of HP-SRAM. Unlike the C3, IRAM and DRAM share the same + * address space at 0x40800000, so there is no need for separate DRAM/IRAM + * regions. + * - It has 16kB of LP-SRAM at 0x50000000 for low-power / RTC use. + * - DROM and IROM are in separate, non-overlapping 8MB address ranges: + * IROM at 0x42000000 and DROM at 0x42800000. + * - The MMU works in pages of 64kB, which means the bottom 16 bits of the + * address in flash and the address in DROM/IROM need to match. + * - Memory in SRAM is loaded at reset by the ROM bootloader. + * + * The firmware image segments are sorted by virtual address (by esp.go): + * 1. .data (SRAM 0x408...) - loaded by ROM bootloader + * 2. .iram (SRAM 0x408...) - loaded by ROM bootloader + * 3. .text (IROM 0x420...) - flash-mapped via MMU + * 4. .rodata (DROM 0x428...) - flash-mapped via MMU + * + * IMPORTANT: SRAM sections (.data, .iram) are defined BEFORE the dummy + * sections so that SIZEOF() references are backward (not forward). lld may + * not correctly resolve forward SIZEOF() references, which would produce + * wrong dummy sizes and corrupt the firmware image. + */ + +MEMORY +{ + /* HP-SRAM: unified IRAM/DRAM address space (512K). + * Unlike the C3, IRAM and DRAM share the same address space, so we use + * a single memory region to avoid linker overlap errors. + */ + SRAM (rwx) : ORIGIN = 0x40800000, LENGTH = 512K + + /* DROM and IROM are in separate, non-overlapping address ranges. */ + DROM (r) : ORIGIN = 0x42800000, LENGTH = 8M /* Data bus (read-only, flash-mapped) */ + IROM (rx) : ORIGIN = 0x42000000, LENGTH = 8M /* Instruction bus (flash-mapped) */ +} + +/* The entry point. It is set in the image flashed to the chip, so must be + * defined. + */ +ENTRY(call_start_cpu0) + +SECTIONS +{ + /* === SRAM sections (loaded by ROM bootloader) === */ + + /* Put the stack at the bottom of SRAM, so that the application will + * crash on stack overflow instead of silently corrupting memory. + * See: http://blog.japaric.io/stack-overflow-protection/ + */ + .stack (NOLOAD) : + { + . = ALIGN(16); + . += _stack_size; + _stack_top = .; + } >SRAM + + /* 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(.); + *(.sbss) + *(.bss .bss.*) + . = ALIGN (4); + _ebss = ABSOLUTE(.); + } >SRAM + + /* Mutable global variables. This data (in the SRAM segment) is initialized + * by the ROM bootloader. + */ + .data : ALIGN(4) + { + . = ALIGN (4); + _sdata = ABSOLUTE(.); + *(.sdata) + *(.data .data.*) + *(.dram*) + . = ALIGN (4); + _edata = ABSOLUTE(.); + } >SRAM + + /* Code that must run from IRAM (not flash-mapped), for example interrupt + * vectors and code that runs before the flash has been mapped. + * Since IRAM and DRAM share the same address space on ESP32-C6, this is + * placed sequentially after .data in the same SRAM region. + */ + .iram : ALIGN(4) + { + *(.init) /* call_start_cpu0 (before flash is mapped) */ + *(.iram*) /* code that must run from IRAM */ + *(.text.handleInterruptASM) /* must be in SRAM, within JAL range of _vector_table */ + . = ALIGN(256); + *(.text.exception_vectors) + . = ALIGN(4); + } > SRAM + + /* Heap starts after IRAM. */ + . = ALIGN(4); + _heap_start = ABSOLUTE(.); + _heap_end = ORIGIN(SRAM) + LENGTH(SRAM); + + /* === IROM sections (flash-mapped code) === */ + + /* Dummy section to align .text virtual address with its flash offset. + * In the firmware image, .data and .iram (SRAM) come before .text (IROM). + * SIZEOF(.data) and SIZEOF(.iram) are backward references (defined above). + * .data may be empty (esp.go skips empty sections), so use a ternary. + */ + .irom_dummy (NOLOAD) : ALIGN(0x10000) + { + . += 0x18; /* image header */ + . += (SIZEOF(.data) > 0) ? (SIZEOF(.data) + 0x8) : 0; /* data segment + header (if non-empty) */ + . += SIZEOF(.iram) + 0x8; /* iram segment + header */ + . += 0x8; /* text segment header */ + } > IROM + + /* Code stored in IROM (flash-mapped). + * This is the main program code. + */ + .text : ALIGN(4) + { + *(.text .text.*) + } > IROM + + /* === DROM sections (flash-mapped read-only data) === */ + + /* Dummy section to align .rodata virtual address with its flash offset. + * Segments in the image are sorted by address: SRAM (.data, .iram), + * IROM (.text), then DROM (.rodata). So .rodata comes last, and we must + * account for all preceding segments. + * All SIZEOF() references are backward (sections defined above). + */ + .rodata_dummy (NOLOAD): ALIGN(4) + { + . += 0x18; /* image header (24 bytes) */ + . += (SIZEOF(.data) > 0) ? (SIZEOF(.data) + 0x8) : 0; /* data segment + header (if non-empty) */ + . += SIZEOF(.iram) + 0x8; /* iram segment + header */ + . += SIZEOF(.text) + 0x8; /* text segment + header */ + . += 0x8; /* rodata segment header */ + } > DROM + + /* Constant global variables, stored in DROM. */ + .rodata : ALIGN(4) + { + *(.rodata*) + . = ALIGN (4); + } >DROM + + /DISCARD/ : + { + *(.eh_frame) /* we don't do exception handling in C */ + } + + /* For the garbage collector. */ + .tinygo_stacksizes (INFO) : + { + *(.tinygo_stacksizes) + } + .tinygo_arm_entries (INFO) : + { + *(.tinygo_arm_entries) + } +} + +/* For the GC and scheduler. */ +_globals_start = _sbss; +_globals_end = _edata; + +/* Default stack size. */ +_stack_size = 4K; + +/* ROM function addresses for ESP32-C6. + * Source: ESP-IDF components/esp_rom/esp32c6/ld/esp32c6.rom.ld + */ +PROVIDE( ets_delay_us = 0x40000040 ); + +/* Cache ROM functions */ +Cache_Invalidate_ICache_All = 0x4000064c; +Cache_Suspend_ICache = 0x40000698; +Cache_Resume_ICache = 0x4000069c; +Cache_MMU_Init = 0x400006b4; +Cache_MSPI_MMU_Set = 0x400006b8; + +/* PHY ROM data */ +phy_param_rom = 0x4087fce8; diff --git a/targets/xiao-esp32c6.json b/targets/xiao-esp32c6.json new file mode 100644 index 000000000..2a3d85455 --- /dev/null +++ b/targets/xiao-esp32c6.json @@ -0,0 +1,4 @@ +{ + "inherits": ["esp32c6"], + "build-tags": ["xiao_esp32c6"] +}