From 8847502f437999c4eb73b187b98d18520aeef4ea Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 18 Sep 2026 21:50:59 +0900 Subject: [PATCH 1/5] machine/mimxrt1062: add USB device (CDC) support for Teensy 4.x Connect the USB1 device controller to the machine/usb stack. The controller runs at full speed because the stack uses 64 byte endpoints. The DMA descriptors and the endpoint buffers are in a non cacheable region at the top of OCRAM, because the USB DMA cannot access DTCM. The serial number comes from the unique chip ID. The default serial output is now USB CDC. Use -serial uart for the old behavior. Set serial-port so tinygo monitor can find the port. Enable the 1200 bps touch so tinygo flash can start the bootloader without a button press. Tested on a Teensy 4.0 with CDC, HID keyboard, HID mouse, HID joystick, and MIDI. --- src/machine/board_teensy40.go | 11 + src/machine/board_teensy41.go | 11 + src/machine/machine_mimxrt1062_usb.go | 512 ++++++++++++++++++++++++++ src/machine/usb.go | 2 +- src/runtime/runtime_mimxrt1062.go | 24 +- src/runtime/runtime_mimxrt1062_mpu.go | 5 + targets/mimxrt1062-teensy40.ld | 2 +- targets/teensy40.json | 4 +- targets/teensy41.json | 4 +- 9 files changed, 563 insertions(+), 12 deletions(-) create mode 100644 src/machine/machine_mimxrt1062_usb.go diff --git a/src/machine/board_teensy40.go b/src/machine/board_teensy40.go index 22529a8d75..17f1813406 100644 --- a/src/machine/board_teensy40.go +++ b/src/machine/board_teensy40.go @@ -388,3 +388,14 @@ var ( }, } ) + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Teensy 4.0" + usb_STRING_MANUFACTURER = "PJRC" +) + +var ( + usb_VID uint16 = 0x16C0 + usb_PID uint16 = 0x0483 +) diff --git a/src/machine/board_teensy41.go b/src/machine/board_teensy41.go index 9f168c50d8..0e0843a5fe 100644 --- a/src/machine/board_teensy41.go +++ b/src/machine/board_teensy41.go @@ -405,3 +405,14 @@ var ( }, } ) + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Teensy 4.1" + usb_STRING_MANUFACTURER = "PJRC" +) + +var ( + usb_VID uint16 = 0x16C0 + usb_PID uint16 = 0x0483 +) diff --git a/src/machine/machine_mimxrt1062_usb.go b/src/machine/machine_mimxrt1062_usb.go new file mode 100644 index 0000000000..0bd424e259 --- /dev/null +++ b/src/machine/machine_mimxrt1062_usb.go @@ -0,0 +1,512 @@ +//go:build mimxrt1062 + +package machine + +// USB device driver for the i.MX RT1062 (Teensy 4.x). +// +// The USB1 device controller reads one queue head (dQH) for each endpoint +// direction and transfer descriptors (dTD). See the device data structures +// section of the USB chapter in IMXRT1060RM. +// +// The dQH and dTD structures and the endpoint buffers are in a non cacheable +// region at the top of OCRAM. The USB DMA cannot access DTCM. +// +// The controller runs at full speed (PORTSC1.PFSC). The machine/usb stack +// assumes 64 byte endpoints. + +import ( + "device/arm" + "device/nxp" + "machine/usb" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +const NumberOfUSBEndpoints = 8 + +// Layout of the non-cacheable USB DMA region (4 KiB, see linker script). +const ( + usbRAMBase uintptr = 0x2027F000 + usbDQHBase = usbRAMBase + 0x000 // 16 * 64 B, 2 KiB aligned + usbDTDBase = usbRAMBase + 0x400 // 16 * 32 B + usbOutBufBase = usbRAMBase + 0x600 // 8 * 64 B + usbInBufBase = usbRAMBase + 0x800 // 8 * 64 B + usbEP0InBase = usbRAMBase + 0xA00 // 256 B for EP0 IN control data + usbEP0InLen = 256 +) + +// Endpoint queue head (dQH), 64 bytes. +type usbDQH struct { + config volatile.Register32 + current volatile.Register32 + next volatile.Register32 + token volatile.Register32 + pages [5]volatile.Register32 + _ uint32 + setup [2]volatile.Register32 + _ [4]uint32 +} + +// Endpoint transfer descriptor (dTD), 32 bytes. +type usbDTD struct { + next volatile.Register32 + token volatile.Register32 + pages [5]volatile.Register32 + _ uint32 +} + +const ( + dtdTerminate = 0x1 + dtdTokenActive = 0x80 + dtdTokenIOC = 0x8000 + dqhIOS = 0x8000 // interrupt on setup + dqhZLTDisable = 0x20000000 // disable automatic zero-length packet +) + +func dqh(ep uint32, in bool) *usbDQH { + i := 2 * ep + if in { + i++ + } + return (*usbDQH)(unsafe.Pointer(usbDQHBase + uintptr(i)*64)) +} + +func dtd(ep uint32, in bool) *usbDTD { + i := 2 * ep + if in { + i++ + } + return (*usbDTD)(unsafe.Pointer(usbDTDBase + uintptr(i)*32)) +} + +func epOutBuf(ep uint32) []byte { + return unsafe.Slice((*byte)(unsafe.Pointer(usbOutBufBase+uintptr(ep)*64)), 64) +} + +func epInBuf(ep uint32) []byte { + return unsafe.Slice((*byte)(unsafe.Pointer(usbInBufBase+uintptr(ep)*64)), 64) +} + +func ep0InXferBuf() []byte { + return unsafe.Slice((*byte)(unsafe.Pointer(usbEP0InBase)), usbEP0InLen) +} + +// endptCtrl returns ENDPTCTRL[ep] (the registers are consecutive). +func endptCtrl(ep uint32) *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&nxp.USB1.ENDPTCTRL0), 4*uintptr(ep))) +} + +// ENDPTCTRL bits (same layout for every endpoint). +const ( + epctrlRXS = 0x1 // RX stall + epctrlRXR = 0x40 // RX data toggle reset + epctrlRXE = 0x80 // RX enable + epctrlTXS = 0x10000 // TX stall + epctrlTXR = 0x400000 + epctrlTXE = 0x800000 + epctrlRXTPos = 2 + epctrlTXTPos = 18 +) + +// Bound for hardware wait loops, far longer than any real operation takes. +const usbSpinLimit = 5_000_000 + +// Configure the USB peripheral. The config is here for compatibility with the UART interface. +func (dev *USBDevice) Configure(config UARTConfig) { + if dev.initcomplete { + return + } + + // Set a default serial number from the unique chip ID. The device + // descriptor declares iSerialNumber, so the string must exist. + if usb.Serial == "" { + usb.Serial = mimxrtSerialNumber() + } + + // Ungate the USB clock. PLL3 (480 MHz) is already set up by clock init. + nxp.CCM.SetCCGR6_CG0(3) + + // Reset and power up the PHY. + nxp.USBPHY1.CTRL_SET.Set(nxp.USBPHY_CTRL_SFTRST) + nxp.USBPHY1.CTRL_CLR.Set(nxp.USBPHY_CTRL_SFTRST | nxp.USBPHY_CTRL_CLKGATE) + nxp.USBPHY1.PWD.Set(0) + + // Reset the controller (the bootloader used it). + nxp.USB1.USBCMD.SetBits(nxp.USB_USBCMD_RST) + for nxp.USB1.USBCMD.HasBits(nxp.USB_USBCMD_RST) { + } + + // Device mode, setup lockout disabled (we use the setup tripwire instead). + nxp.USB1.USBMODE.Set(2<>= 4 + } + return string(b[:]) +} + +func usbRAMClear() { + p := unsafe.Slice((*volatile.Register32)(unsafe.Pointer(usbRAMBase)), 0x1000/4) + for i := range p { + p[i].Set(0) + } +} + +// Attach connects the device to the USB bus (run the controller). +func (dev *USBDevice) Attach() { + nxp.USB1.USBCMD.SetBits(nxp.USB_USBCMD_RS) +} + +// Detach disconnects the device from the USB bus. +func (dev *USBDevice) Detach() { + nxp.USB1.USBCMD.ClearBits(nxp.USB_USBCMD_RS) +} + +func handleUSBIRQ(intr interrupt.Interrupt) { + status := nxp.USB1.USBSTS.Get() + nxp.USB1.USBSTS.Set(status) + + if status&nxp.USB_USBSTS_URI != 0 { + handleUSBBusReset() + } + + if status&nxp.USB_USBSTS_UI != 0 { + // SETUP packets land in the dQH's setup area, flagged in ENDPTSETUPSTAT. + for { + ss := nxp.USB1.ENDPTSETUPSTAT.Get() + if ss == 0 { + break + } + nxp.USB1.ENDPTSETUPSTAT.Set(ss) + if ss&1 != 0 { + handleEP0Setup() + } + } + + comp := nxp.USB1.ENDPTCOMPLETE.Get() + if comp != 0 { + nxp.USB1.ENDPTCOMPLETE.Set(comp) + + // OUT completions, bits 0 to 7 + for ep := uint32(1); ep < NumberOfUSBEndpoints; ep++ { + if comp&(1<>16)&0x7FFF) + buf := epOutBuf(ep)[:n] + if usbRxHandler[ep] == nil || usbRxHandler[ep](buf) { + AckUsbOutTransfer(ep) + } + } + } + + // IN completions, bits 16 to 23 + for ep := uint32(1); ep < NumberOfUSBEndpoints; ep++ { + if comp&(1<<(16+ep)) != 0 { + if usbTxHandler[ep] != nil { + usbTxHandler[ep]() + } + } + } + } + } +} + +// IN endpoints whose in-flight transfer was cancelled by a bus reset flush. +// Only touched from the USB interrupt handler. +var usbTxCancelled uint32 + +func handleUSBBusReset() { + // See the Bus Reset section of the USB chapter in IMXRT1060RM. + nxp.USB1.ENDPTSETUPSTAT.Set(nxp.USB1.ENDPTSETUPSTAT.Get()) + nxp.USB1.ENDPTCOMPLETE.Set(nxp.USB1.ENDPTCOMPLETE.Get()) + for i := 0; nxp.USB1.ENDPTPRIME.Get() != 0; i++ { + if i > usbSpinLimit { + break + } + } + // Record IN transfers the flush below cancels. Their completion callbacks + // run in initEndpoint once the host reconfigures the device. + for ep := uint32(1); ep < NumberOfUSBEndpoints; ep++ { + if dtd(ep, true).token.Get()&dtdTokenActive != 0 { + usbTxCancelled |= 1 << ep + } + } + nxp.USB1.ENDPTFLUSH.Set(0xFFFFFFFF) + nxp.USB1.DEVICEADDR.Set(0) + usbConfiguration = 0 +} + +// handleEP0Setup reads the setup packet (guarded by the setup tripwire) and +// dispatches it to the shared stack. +func handleEP0Setup() { + var raw [8]byte + for i := 0; ; i++ { + if i > usbSpinLimit { + return + } + nxp.USB1.USBCMD.SetBits(nxp.USB_USBCMD_SUTW) + s0 := dqh(0, false).setup[0].Get() + s1 := dqh(0, false).setup[1].Get() + if nxp.USB1.USBCMD.HasBits(nxp.USB_USBCMD_SUTW) { + raw[0], raw[1], raw[2], raw[3] = byte(s0), byte(s0>>8), byte(s0>>16), byte(s0>>24) + raw[4], raw[5], raw[6], raw[7] = byte(s1), byte(s1>>8), byte(s1>>16), byte(s1>>24) + break + } + } + nxp.USB1.USBCMD.ClearBits(nxp.USB_USBCMD_SUTW) + + // A new setup cancels any transfer still pending on EP0. + nxp.USB1.ENDPTFLUSH.Set(1<<16 | 1) + for i := 0; nxp.USB1.ENDPTFLUSH.HasBits(1<<16 | 1); i++ { + if i > usbSpinLimit { + break + } + } + + setup := usb.NewSetup(raw[:]) + + // A control write has an OUT data stage. Prime EP0 OUT before the + // dispatch so the handler can read it with ReceiveUSBControlPacket. + if setup.BmRequestType&0x80 == 0 && setup.WLength > 0 { + usbPrime(0, false, usbOutBufBase, 64) + } + + ok := false + if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { + ok = handleStandardSetup(setup) + } else { + if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { + ok = usbSetupHandler[setup.WIndex](setup) + } + } + if !ok { + USBDev.SetStallEPIn(0) + } +} + +// usbPrime arms one dTD for the given buffer and primes the endpoint. +// The hardware splits the transfer into max packet size packets. +func usbPrime(ep uint32, in bool, addr uintptr, size int) { + d := dtd(ep, in) + d.next.Set(dtdTerminate) + d.token.Set(uint32(size)<<16 | dtdTokenIOC | dtdTokenActive) + p := uint32(addr) + d.pages[0].Set(p) + d.pages[1].Set(p&^0xFFF + 0x1000) + d.pages[2].Set(p&^0xFFF + 0x2000) + d.pages[3].Set(p&^0xFFF + 0x3000) + d.pages[4].Set(p&^0xFFF + 0x4000) + + q := dqh(ep, in) + q.next.Set(uint32(uintptr(unsafe.Pointer(d)))) + q.token.Set(0) + + // Descriptor writes must reach memory before the prime register write. + arm.Asm("dsb 0xF") + + mask := uint32(1) << ep + if in { + mask = 1 << (16 + ep) + } + // Clear a stale completion bit from an earlier transfer so it is not + // read as the completion of this transfer. + nxp.USB1.ENDPTCOMPLETE.Set(mask) + nxp.USB1.ENDPTPRIME.SetBits(mask) + for i := 0; nxp.USB1.ENDPTPRIME.HasBits(mask); i++ { + if i > usbSpinLimit { + return + } + } +} + +func initEndpoint(ep, config uint32) { + // The ENDPTCTRL type value is 2 for bulk and 3 for interrupt. + var t uint32 + switch config &^ (usb.EndpointIn | usb.EndpointOut) { + case usb.ENDPOINT_TYPE_BULK: + t = 2 + case usb.ENDPOINT_TYPE_INTERRUPT: + t = 3 + case usb.ENDPOINT_TYPE_CONTROL: + return // EP0 is configured in Configure + default: + t = 2 + } + + in := config&usb.EndpointIn != 0 + + // A repeated SET_CONFIGURATION can find a transfer still active. Stop the + // endpoint before the dQH and dTD writes below, and record a cancelled IN + // transfer so its completion callback runs. + if in && dtd(ep, true).token.Get()&dtdTokenActive != 0 { + usbTxCancelled |= 1 << ep + } + mask := uint32(1) << ep + if in { + mask = 1 << (16 + ep) + } + nxp.USB1.ENDPTFLUSH.Set(mask) + for i := 0; nxp.USB1.ENDPTFLUSH.HasBits(mask); i++ { + if i > usbSpinLimit { + break + } + } + dtd(ep, in).token.Set(0) + + if in { + dqh(ep, true).config.Set(64<<16 | dqhZLTDisable) + endptCtrl(ep).SetBits(t< 64 { + return false + } + if dtd(ep, true).token.Get()&dtdTokenActive != 0 { + return false + } + } + sendUSBPacket(ep, data) + return true +} + +//go:noinline +func sendUSBPacket(ep uint32, data []byte) { + ep &= 0x7F + if ep == 0 { + n := len(data) + if n > usbEP0InLen { + n = usbEP0InLen + } + copy(ep0InXferBuf(), data[:n]) + usbPrime(0, true, usbEP0InBase, n) + if n > 0 { + // A control read has an OUT status stage. Prime for it. + usbPrime(0, false, usbOutBufBase, 64) + } + } else { + n := len(data) + if n > 64 { + n = 64 + } + copy(epInBuf(ep), data[:n]) + usbPrime(ep, true, usbInBufBase+uintptr(ep)*64, n) + } +} + +// ReceiveUSBControlPacket waits for and returns the EP0 OUT data stage that +// was primed when the setup packet was dispatched. +func ReceiveUSBControlPacket() (b [cdcLineInfoSize]byte, err error) { + for i := 0; nxp.USB1.ENDPTCOMPLETE.Get()&1 == 0; i++ { + if i > usbSpinLimit { + return b, ErrUSBReadTimeout + } + } + nxp.USB1.ENDPTCOMPLETE.Set(1) + arm.Asm("dsb 0xF") + + n := 64 - int((dtd(0, false).token.Get()>>16)&0x7FFF) + if n > len(b) { + n = len(b) + } + out := epOutBuf(0) + for i := 0; i < n; i++ { + b[i] = out[i] + } + return b, nil +} + +// AckUsbOutTransfer re-arms an OUT endpoint after its data was consumed. +func AckUsbOutTransfer(ep uint32) { + ep &= 0x7F + usbPrime(ep, false, usbOutBufBase+uintptr(ep)*64, 64) +} + +func SendZlp() { + sendUSBPacket(0, nil) +} + +func handleUSBSetAddress(setup usb.Setup) bool { + // USBADRA defers the address change until after the status stage. + nxp.USB1.DEVICEADDR.Set(uint32(setup.WValueL)<<25 | nxp.USB_DEVICEADDR_USBADRA) + SendZlp() + return true +} + +// Set ENDPOINT_HALT/stall status on a USB IN endpoint. +func (dev *USBDevice) SetStallEPIn(ep uint32) { + endptCtrl(ep & 0x7F).SetBits(epctrlTXS) +} + +// Set ENDPOINT_HALT/stall status on a USB OUT endpoint. +func (dev *USBDevice) SetStallEPOut(ep uint32) { + endptCtrl(ep & 0x7F).SetBits(epctrlRXS) +} + +// Clear the ENDPOINT_HALT/stall on a USB IN endpoint. +func (dev *USBDevice) ClearStallEPIn(ep uint32) { + ep &= 0x7F + endptCtrl(ep).ClearBits(epctrlTXS) + endptCtrl(ep).SetBits(epctrlTXR) // reset data toggle to DATA0 +} + +// Clear the ENDPOINT_HALT/stall on a USB OUT endpoint. +func (dev *USBDevice) ClearStallEPOut(ep uint32) { + ep &= 0x7F + endptCtrl(ep).ClearBits(epctrlRXS) + endptCtrl(ep).SetBits(epctrlRXR) +} + +// EnterBootloader resets into the HalfKay bootloader. The bootloader chip +// watches for this breakpoint, the same as Teensyduino soft reboot. +func EnterBootloader() { + arm.DisableInterrupts() + arm.Asm("bkpt #251") + for { + } +} diff --git a/src/machine/usb.go b/src/machine/usb.go index 9fcc1997d7..9cc6b2e59f 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -1,4 +1,4 @@ -//go:build sam || nrf52840 || rp2040 || rp2350 || stm32f4 || stm32f7 || stm32h7 +//go:build sam || nrf52840 || rp2040 || rp2350 || stm32f4 || stm32f7 || stm32h7 || mimxrt1062 package machine diff --git a/src/runtime/runtime_mimxrt1062.go b/src/runtime/runtime_mimxrt1062.go index a26c9b6e3e..848b9147c8 100644 --- a/src/runtime/runtime_mimxrt1062.go +++ b/src/runtime/runtime_mimxrt1062.go @@ -6,6 +6,8 @@ import ( "device/arm" "device/nxp" "machine" + + _ "machine/usb/cdc" "math/bits" "unsafe" ) @@ -106,7 +108,12 @@ func initPeripherals() { initPins() // configure GPIO enablePeripheralClocks() // activate peripheral clock gates - initUART() // configure UART (initialized first for debugging) +} + +func init() { + // InitSerial must run from a package init function (inside run()), after + // the heap is initialized: with -serial usb it allocates for the USB stack. + machine.InitSerial() } func initPins() { @@ -117,24 +124,25 @@ func initPins() { nxp.IOMUXC_GPR.GPR29.Set(0xFFFFFFFF) } -func initUART() { - machine.InitSerial() -} - func putchar(c byte) { + // Serial is nil until InitSerial runs. Drop early output so a print + // from a fault handler does not cause a second fault. + if machine.Serial == nil { + return + } machine.Serial.WriteByte(c) } func getchar() byte { - for machine.UART1.Buffered() == 0 { + for machine.Serial.Buffered() == 0 { Gosched() } - v, _ := machine.UART1.ReadByte() + v, _ := machine.Serial.ReadByte() return v } func buffered() int { - return machine.UART1.Buffered() + return machine.Serial.Buffered() } func exit(code int) { diff --git a/src/runtime/runtime_mimxrt1062_mpu.go b/src/runtime/runtime_mimxrt1062_mpu.go index 024e4336ee..dba5bc502a 100644 --- a/src/runtime/runtime_mimxrt1062_mpu.go +++ b/src/runtime/runtime_mimxrt1062_mpu.go @@ -47,5 +47,10 @@ func initCache() { nxp.MPU.SetRBAR(7, 0x60000000) nxp.MPU.SetRASR(nxp.RGNSZ_2MB, nxp.PERM_FULL, nxp.EXTN_NORMAL, true, false, true, true, false) + // [8] USB DMA region, top 4 KiB of OCRAM, #NORMAL non cacheable, -EXEC. + // It holds the USB dQH and dTD descriptors and the endpoint buffers. + nxp.MPU.SetRBAR(8, 0x2027F000) + nxp.MPU.SetRASR(nxp.RGNSZ_4KB, nxp.PERM_FULL, nxp.Extension(1), false, false, false, false, false) + nxp.MPU.Enable(true) } diff --git a/targets/mimxrt1062-teensy40.ld b/targets/mimxrt1062-teensy40.ld index 70a717b72f..47ea141e43 100644 --- a/targets/mimxrt1062-teensy40.ld +++ b/targets/mimxrt1062-teensy40.ld @@ -2,7 +2,7 @@ MEMORY { ITCM (rwx): ORIGIN = 0x00000000, LENGTH = 0x00080000 /* 512 Kib */ DTCM (rwx): ORIGIN = 0x20000000, LENGTH = 0x00080000 /* 512 Kib */ - RAM (rwx): ORIGIN = 0x20200000, LENGTH = 0x00080000 /* 512 Kib */ + RAM (rwx): ORIGIN = 0x20200000, LENGTH = 0x0007F000 /* 512 KiB minus 4 KiB reserved for USB DMA descriptors/buffers */ FLASH (rx): ORIGIN = 0x60000000, LENGTH = 0x001FFFF0 /* 1984 Kib */ } diff --git a/targets/teensy40.json b/targets/teensy40.json index 223db6f6f1..7709c3c5f2 100644 --- a/targets/teensy40.json +++ b/targets/teensy40.json @@ -1,7 +1,9 @@ { "inherits": ["cortex-m7"], "build-tags": ["teensy40", "teensy", "mimxrt1062", "nxp"], - "serial": "uart", + "serial": "usb", + "serial-port": ["16c0:0483"], + "flash-1200-bps-reset": "true", "automatic-stack-size": false, "linkerscript": "targets/mimxrt1062-teensy40.ld", "extra-files": [ diff --git a/targets/teensy41.json b/targets/teensy41.json index 8866bc48e3..b5cbcf13fa 100644 --- a/targets/teensy41.json +++ b/targets/teensy41.json @@ -1,7 +1,9 @@ { "inherits": ["cortex-m7"], "build-tags": ["teensy41", "teensy", "mimxrt1062", "nxp"], - "serial": "uart", + "serial": "usb", + "serial-port": ["16c0:0483"], + "flash-1200-bps-reset": "true", "automatic-stack-size": false, "linkerscript": "targets/mimxrt1062-teensy40.ld", "extra-files": [ From b1d107de8b98c42f2ef86c4cd563ec3f6032b7ef Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 18 Sep 2026 22:09:47 +0900 Subject: [PATCH 2/5] machine/mimxrt1062: take the USB DMA region address from the linker script The address 0x2027F000 was in three places, the linker script, the MPU setup, and the USB driver. Define _usb_dma_start in the linker script and use the symbol from the Go code. An ASSERT makes sure the region keeps the 2 KiB alignment that ENDPTLISTADDR requires. --- src/machine/machine_mimxrt1062_usb.go | 20 ++++++++++++-------- src/runtime/runtime_mimxrt1062_mpu.go | 6 +++++- targets/mimxrt1062-teensy40.ld | 4 ++++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/machine/machine_mimxrt1062_usb.go b/src/machine/machine_mimxrt1062_usb.go index 0bd424e259..ae9c3d7b38 100644 --- a/src/machine/machine_mimxrt1062_usb.go +++ b/src/machine/machine_mimxrt1062_usb.go @@ -25,17 +25,21 @@ import ( const NumberOfUSBEndpoints = 8 +//go:extern _usb_dma_start +var _usb_dma_start [0]byte + // Layout of the non-cacheable USB DMA region (4 KiB, see linker script). -const ( - usbRAMBase uintptr = 0x2027F000 - usbDQHBase = usbRAMBase + 0x000 // 16 * 64 B, 2 KiB aligned - usbDTDBase = usbRAMBase + 0x400 // 16 * 32 B - usbOutBufBase = usbRAMBase + 0x600 // 8 * 64 B - usbInBufBase = usbRAMBase + 0x800 // 8 * 64 B - usbEP0InBase = usbRAMBase + 0xA00 // 256 B for EP0 IN control data - usbEP0InLen = 256 +var ( + usbRAMBase = uintptr(unsafe.Pointer(&_usb_dma_start)) + usbDQHBase = usbRAMBase + 0x000 // 16 * 64 B, 2 KiB aligned + usbDTDBase = usbRAMBase + 0x400 // 16 * 32 B + usbOutBufBase = usbRAMBase + 0x600 // 8 * 64 B + usbInBufBase = usbRAMBase + 0x800 // 8 * 64 B + usbEP0InBase = usbRAMBase + 0xA00 // 256 B for EP0 IN control data ) +const usbEP0InLen = 256 + // Endpoint queue head (dQH), 64 bytes. type usbDQH struct { config volatile.Register32 diff --git a/src/runtime/runtime_mimxrt1062_mpu.go b/src/runtime/runtime_mimxrt1062_mpu.go index dba5bc502a..17d291d13b 100644 --- a/src/runtime/runtime_mimxrt1062_mpu.go +++ b/src/runtime/runtime_mimxrt1062_mpu.go @@ -4,8 +4,12 @@ package runtime import ( "device/nxp" + "unsafe" ) +//go:extern _usb_dma_start +var _usb_dma_start [0]byte + func initCache() { nxp.MPU.Enable(false) @@ -49,7 +53,7 @@ func initCache() { // [8] USB DMA region, top 4 KiB of OCRAM, #NORMAL non cacheable, -EXEC. // It holds the USB dQH and dTD descriptors and the endpoint buffers. - nxp.MPU.SetRBAR(8, 0x2027F000) + nxp.MPU.SetRBAR(8, uint32(uintptr(unsafe.Pointer(&_usb_dma_start)))) nxp.MPU.SetRASR(nxp.RGNSZ_4KB, nxp.PERM_FULL, nxp.Extension(1), false, false, false, false, false) nxp.MPU.Enable(true) diff --git a/targets/mimxrt1062-teensy40.ld b/targets/mimxrt1062-teensy40.ld index 47ea141e43..f086f598ed 100644 --- a/targets/mimxrt1062-teensy40.ld +++ b/targets/mimxrt1062-teensy40.ld @@ -96,6 +96,10 @@ SECTIONS _heap_start = ORIGIN(RAM); _heap_end = ORIGIN(RAM) + LENGTH(RAM); + /* USB DMA region, the 4 KiB reserved above RAM. */ + _usb_dma_start = ORIGIN(RAM) + LENGTH(RAM); + ASSERT((_usb_dma_start & 0x7FF) == 0, "_usb_dma_start must be 2 KiB aligned for ENDPTLISTADDR") + _globals_start = _sdata; _globals_end = _ebss; From 5aada33252b8b6e290980cf3b9777ffe22c704d4 Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 18 Sep 2026 22:10:00 +0900 Subject: [PATCH 3/5] mimxrt1062: shorten comments Keep comments no more than 2 lines, as AGENTS.md specifies. Move the notes from the long file header next to the code they describe. --- src/machine/machine_mimxrt1062_usb.go | 26 ++++++++------------------ src/runtime/runtime_mimxrt1062.go | 4 ++-- src/runtime/runtime_mimxrt1062_mpu.go | 3 +-- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/machine/machine_mimxrt1062_usb.go b/src/machine/machine_mimxrt1062_usb.go index ae9c3d7b38..a1b4c2afd7 100644 --- a/src/machine/machine_mimxrt1062_usb.go +++ b/src/machine/machine_mimxrt1062_usb.go @@ -2,17 +2,8 @@ package machine -// USB device driver for the i.MX RT1062 (Teensy 4.x). -// -// The USB1 device controller reads one queue head (dQH) for each endpoint -// direction and transfer descriptors (dTD). See the device data structures -// section of the USB chapter in IMXRT1060RM. -// -// The dQH and dTD structures and the endpoint buffers are in a non cacheable -// region at the top of OCRAM. The USB DMA cannot access DTCM. -// -// The controller runs at full speed (PORTSC1.PFSC). The machine/usb stack -// assumes 64 byte endpoints. +// USB device driver for the i.MX RT1062 (Teensy 4.x). See the device data +// structures section of the USB chapter in IMXRT1060RM. import ( "device/arm" @@ -28,7 +19,8 @@ const NumberOfUSBEndpoints = 8 //go:extern _usb_dma_start var _usb_dma_start [0]byte -// Layout of the non-cacheable USB DMA region (4 KiB, see linker script). +// Layout of the non cacheable USB DMA region (4 KiB, see linker script). +// The USB DMA cannot access DTCM, so the region is at the top of OCRAM. var ( usbRAMBase = uintptr(unsafe.Pointer(&_usb_dma_start)) usbDQHBase = usbRAMBase + 0x000 // 16 * 64 B, 2 KiB aligned @@ -366,9 +358,8 @@ func initEndpoint(ep, config uint32) { in := config&usb.EndpointIn != 0 - // A repeated SET_CONFIGURATION can find a transfer still active. Stop the - // endpoint before the dQH and dTD writes below, and record a cancelled IN - // transfer so its completion callback runs. + // Stop the endpoint before the dQH and dTD writes below. Record a + // cancelled IN transfer so its completion callback runs. if in && dtd(ep, true).token.Get()&dtdTokenActive != 0 { usbTxCancelled |= 1 << ep } @@ -402,9 +393,8 @@ func initEndpoint(ep, config uint32) { } } -// SendUSBInPacket sends a packet for USB (interrupt in / bulk in). -// It reports false when the data does not fit in one packet or when the -// previous transfer on this endpoint is still active. +// SendUSBInPacket sends a packet for USB (interrupt in / bulk in). It reports +// false when the data does not fit or the previous transfer is still active. func SendUSBInPacket(ep uint32, data []byte) bool { ep &= 0x7F if ep != 0 { diff --git a/src/runtime/runtime_mimxrt1062.go b/src/runtime/runtime_mimxrt1062.go index 848b9147c8..65fd4ad5d2 100644 --- a/src/runtime/runtime_mimxrt1062.go +++ b/src/runtime/runtime_mimxrt1062.go @@ -111,8 +111,8 @@ func initPeripherals() { } func init() { - // InitSerial must run from a package init function (inside run()), after - // the heap is initialized: with -serial usb it allocates for the USB stack. + // InitSerial must run from a package init function, after the heap is + // initialized. With -serial usb it allocates for the USB stack. machine.InitSerial() } diff --git a/src/runtime/runtime_mimxrt1062_mpu.go b/src/runtime/runtime_mimxrt1062_mpu.go index 17d291d13b..3d14fd0ab5 100644 --- a/src/runtime/runtime_mimxrt1062_mpu.go +++ b/src/runtime/runtime_mimxrt1062_mpu.go @@ -30,8 +30,7 @@ func initCache() { nxp.MPU.SetRASR(nxp.RGNSZ_1GB, nxp.PERM_FULL, nxp.EXTN_DEVICE, true, false, false, false, false) // [3] ITCM: 512 KiB, +ACCESS, #NORMAL (non-cacheable), +EXEC, -share, -subregion - // TEX 0b001 with C=0 B=0 is Normal non cacheable memory. TEX 0 is Strongly - // Ordered and unaligned accesses fault there. See Arm DDI 0403, PMSAv7. + // TEX 0 is Strongly Ordered and faults on unaligned access, see Arm DDI 0403. nxp.MPU.SetRBAR(3, 0x00000000) nxp.MPU.SetRASR(nxp.RGNSZ_512KB, nxp.PERM_FULL, nxp.Extension(1), true, false, false, false, false) From 3c040e3836428fb8f067e56cfedaa79b82ca1d6c Mon Sep 17 00:00:00 2001 From: sago35 Date: Thu, 17 Sep 2026 21:43:24 +0900 Subject: [PATCH 4/5] machine/mimxrt1062: add flash support (machine.Flash) for Teensy 4.x The CPU runs code from the FlexSPI serial NOR flash (XIP). The flash gives no data while an erase or a program operation is active. Thus the full transaction runs from RAM with interrupts disabled. The code uses the FlexSPI IP commands and LUT sequence 15. This is the same method as Teensyduino cores/teensy4/eeprom.c. The routine is in a new .ramfuncs section. The startup code copies this section to OCRAM2. OCRAM2 does not need a FlexRAM bank change. The data area is a fixed 60 KiB region at the top of the flash. Teensyduino uses the same region for its EEPROM emulation. The Teensy bootloader does not erase this region during an upload. Thus the data stays through firmware updates. Tested on a Teensy 4.0 with erase, non-aligned writes across page boundaries, read back, and data kept through firmware uploads. --- src/machine/flash.go | 2 +- src/machine/machine_mimxrt1062_flash.go | 244 ++++++++++++++++++++++++ src/runtime/runtime_mimxrt1062.go | 23 +++ targets/mimxrt1062-teensy40.ld | 25 ++- 4 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 src/machine/machine_mimxrt1062_flash.go diff --git a/src/machine/flash.go b/src/machine/flash.go index bb3d335f83..bdd630c7b5 100644 --- a/src/machine/flash.go +++ b/src/machine/flash.go @@ -1,4 +1,4 @@ -//go:build esp32c3 || nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32f7 || stm32l0 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350 +//go:build esp32c3 || nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32f7 || stm32l0 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350 || mimxrt1062 package machine diff --git a/src/machine/machine_mimxrt1062_flash.go b/src/machine/machine_mimxrt1062_flash.go new file mode 100644 index 0000000000..d2c2a82e6c --- /dev/null +++ b/src/machine/machine_mimxrt1062_flash.go @@ -0,0 +1,244 @@ +//go:build mimxrt1062 + +package machine + +// Flash driver for the FlexSPI serial NOR flash on Teensy 4.x boards. +// The method follows Teensyduino cores/teensy4/eeprom.c. + +import ( + "device/arm" + "device/nxp" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +const ( + // serial NOR page program granularity + flashPageSize = 256 + flashPageWords = flashPageSize / 4 + + // memory-mapped (AHB) base address of the flash + flashBaseAddr = 0x60000000 + + eraseBlockSizeValue = 4096 // 4 KiB sector erase +) + +// FlexSPI LUT sequence words. The instruction format is in the i.MX RT1060 +// Reference Manual, chapter 27.5.7 "Lookup table". Sequence 15 is free. +const ( + flashLUTWriteEnable = 0x00000406 // CMD 0x06 (write enable) + flashLUTEraseSector = 0x08180420 // CMD 0x20, RADDR 24 bits (4K sector erase) + flashLUTPageProgram0 = 0x08180432 // CMD 0x32, RADDR 24 bits (quad page program) + flashLUTPageProgram1 = 0x00002201 // WRITE on 4 pads + flashLUTReadStatus = 0x24010405 // CMD 0x05, READ 1 byte (status register 1) + + flexspiLUTKey = 0x5AF05AF0 +) + +// compile-time check for ensuring we fulfill BlockDevice interface +var _ BlockDevice = flashBlockDevice{} + +var Flash flashBlockDevice + +type flashBlockDevice struct { +} + +// staging buffer for one flash page, kept in RAM +var flashPageBuf [flashPageWords]uint32 + +// ReadAt reads the given number of bytes from the block device. +func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + if readAddress(off) > FlashDataEnd() { + return 0, errFlashCannotReadPastEOF + } + + data := unsafe.Slice((*byte)(unsafe.Pointer(readAddress(off))), len(p)) + copy(p, data) + + return len(p), nil +} + +// WriteAt writes the given number of bytes to the block device. The driver +// programs full pages padded with 0xFF. The destination must be erased. +func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + start := readAddress(off) + if start+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + for n < len(p) { + pageAddr := start &^ (flashPageSize - 1) + offInPage := start - pageAddr + chunk := flashPageSize - int(offInPage) + if chunk > len(p)-n { + chunk = len(p) - n + } + + for i := range flashPageBuf { + flashPageBuf[i] = 0xFFFFFFFF + } + page := (*[flashPageSize]byte)(unsafe.Pointer(&flashPageBuf)) + copy(page[offInPage:int(offInPage)+chunk], p[n:n+chunk]) + + mask := interrupt.Disable() + flashTransaction(uint32(pageAddr-flashBaseAddr), true) + interrupt.Restore(mask) + flashInvalidateDCache(pageAddr, flashPageSize) + + start += uintptr(chunk) + n += chunk + } + + return n, nil +} + +// Size returns the number of bytes in this block device. +func (f flashBlockDevice) Size() int64 { + return int64(FlashDataEnd() - FlashDataStart()) +} + +const writeBlockSize = flashPageSize + +// WriteBlockSize returns the block size in which data can be written to +// memory. It can be used by a client to optimize writes, non-aligned writes +// should always work correctly. +func (f flashBlockDevice) WriteBlockSize() int64 { + return writeBlockSize +} + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +func (f flashBlockDevice) EraseBlockSize() int64 { + return eraseBlockSize() +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +func (f flashBlockDevice) EraseBlocks(start, length int64) error { + addr := readAddress(start * f.EraseBlockSize()) + if addr+uintptr(length)*eraseBlockSizeValue > FlashDataEnd() { + return errFlashCannotErasePastEOF + } + + for i := int64(0); i < length; i++ { + mask := interrupt.Disable() + flashTransaction(uint32(addr-flashBaseAddr), false) + interrupt.Restore(mask) + flashInvalidateDCache(addr, eraseBlockSizeValue) + addr += eraseBlockSizeValue + } + + return nil +} + +// return the correct address to be used for reads +func readAddress(off int64) uintptr { + return FlashDataStart() + uintptr(off) +} + +// flashInvalidateDCache removes cached copies of the given flash range. +// The operations are no-ops while the data cache is off. +func flashInvalidateDCache(addr, size uintptr) { + // DCIMVAC register, see Arm DDI 0403 (Armv7-M ARM) section B3.2.2 + const dcimvacAddr = 0xE000EF5C + dcimvac := (*volatile.Register32)(unsafe.Pointer(uintptr(dcimvacAddr))) + arm.Asm("dsb") + for a := addr &^ 31; a < addr+size; a += 32 { + dcimvac.Set(uint32(a)) + } + arm.Asm("dsb") + arm.Asm("isb") +} + +// flashTransaction erases the 4 KiB sector at offset, or programs one page +// from flashPageBuf at offset. Call it with interrupts disabled. +// +//go:section .ramfuncs +//go:nobounds +func flashTransaction(offset uint32, program bool) { + // This code runs from RAM. The flash gives no data during the operation. + // Use only volatile loads and stores, the compiler inlines them. + fs := nxp.FLEXSPI + + // unlock the LUT and load sequence 15 with "write enable" + volatile.StoreUint32(&fs.LUTKEY.Reg, flexspiLUTKey) + volatile.StoreUint32(&fs.LUTCR.Reg, nxp.FlexSPI_LUTCR_UNLOCK_Msk) + volatile.StoreUint32(&fs.LUT[60].Reg, flashLUTWriteEnable) + volatile.StoreUint32(&fs.LUT[61].Reg, 0) + volatile.StoreUint32(&fs.LUT[62].Reg, 0) + volatile.StoreUint32(&fs.LUT[63].Reg, 0) + + // issue write enable + volatile.StoreUint32(&fs.IPCR0.Reg, 0) + volatile.StoreUint32(&fs.IPCR1.Reg, 15< FLASH + /* Code that runs from OCRAM2. The flash gives no data during an erase + or a program operation. See machine_mimxrt1062_flash.go. */ + .ramfuncs : ALIGN(8) { + + _sramfuncs = .; + *(.ramfuncs .ramfuncs.*); + . = ALIGN(16); + _eramfuncs = .; + + } > RAM AT > FLASH + + _framfuncs = LOADADDR(.ramfuncs); + .text.padding (NOLOAD) : { . = ALIGN(32768); @@ -93,17 +107,22 @@ SECTIONS _sidata = LOADADDR(.data); - _heap_start = ORIGIN(RAM); + _heap_start = _eramfuncs; _heap_end = ORIGIN(RAM) + LENGTH(RAM); /* USB DMA region, the 4 KiB reserved above RAM. */ _usb_dma_start = ORIGIN(RAM) + LENGTH(RAM); ASSERT((_usb_dma_start & 0x7FF) == 0, "_usb_dma_start must be 2 KiB aligned for ENDPTLISTADDR") + /* Fixed data area for machine.Flash. The Teensy bootloader does not + erase it, see Teensyduino cores/teensy4/eeprom.c FLASH_BASEADDR. */ + __flash_data_start = ORIGIN(FLASH) + LENGTH(FLASH); + __flash_data_end = 0x601FF000; + _globals_start = _sdata; _globals_end = _ebss; - _image_size = SIZEOF(.text) + SIZEOF(.tinygo_stacksizes) + SIZEOF(.data); + _image_size = SIZEOF(.text) + SIZEOF(.tinygo_stacksizes) + SIZEOF(.ramfuncs) + SIZEOF(.data); /* TODO: link .text to ITCM */ _itcm_blocks = (0 + 0x7FFF) >> 15; From 46751106d8b1728177de9ed5abc3aa76c856f0b5 Mon Sep 17 00:00:00 2001 From: sago35 Date: Thu, 17 Sep 2026 21:43:34 +0900 Subject: [PATCH 5/5] targets/teensy41: use the full 8 MiB flash The teensy41 target used the Teensy 4.0 linker script and flash configuration block. That limited the flash to 2 MiB. Add a linker script and an FCB for the Teensy 4.1 with sflashA1Size set to 8 MiB. Also set the MPU region size for the QSPI flash for each board. The size was fixed at 2 MiB before. Access above that range caused a fault. The machine.Flash data area is the fixed 252 KiB region at the top of the 8 MiB flash. The Teensy bootloader keeps this region during an upload. Teensyduino uses the same region for its EEPROM emulation on the Teensy 4.1. Build tested only. Not yet checked on a real Teensy 4.1. --- src/runtime/runtime_mimxrt1062_mpu.go | 4 +- src/runtime/runtime_mimxrt1062_teensy40.go | 8 + src/runtime/runtime_mimxrt1062_teensy41.go | 8 + targets/mimxrt1062-teensy41.ld | 130 ++++++++++++++ targets/teensy41.json | 4 +- targets/teensy41.s | 199 +++++++++++++++++++++ 6 files changed, 349 insertions(+), 4 deletions(-) create mode 100644 src/runtime/runtime_mimxrt1062_teensy40.go create mode 100644 src/runtime/runtime_mimxrt1062_teensy41.go create mode 100644 targets/mimxrt1062-teensy41.ld create mode 100644 targets/teensy41.s diff --git a/src/runtime/runtime_mimxrt1062_mpu.go b/src/runtime/runtime_mimxrt1062_mpu.go index 3d14fd0ab5..96b1583eec 100644 --- a/src/runtime/runtime_mimxrt1062_mpu.go +++ b/src/runtime/runtime_mimxrt1062_mpu.go @@ -46,9 +46,9 @@ func initCache() { nxp.MPU.SetRBAR(6, 0x70000000) nxp.MPU.SetRASR(nxp.RGNSZ_512MB, nxp.PERM_FULL, nxp.EXTN_NORMAL, true, false, true, true, false) - // [7] QSPI flash: 2 MiB, +ACCESS, #NORMAL, +EXEC, -share, +CACHE, +BUFFER, -subregion + // [7] QSPI flash: 2 or 8 MiB, +ACCESS, #NORMAL, +EXEC, -share, +CACHE, +BUFFER, -subregion nxp.MPU.SetRBAR(7, 0x60000000) - nxp.MPU.SetRASR(nxp.RGNSZ_2MB, nxp.PERM_FULL, nxp.EXTN_NORMAL, true, false, true, true, false) + nxp.MPU.SetRASR(qspiFlashMPUSize, nxp.PERM_FULL, nxp.EXTN_NORMAL, true, false, true, true, false) // [8] USB DMA region, top 4 KiB of OCRAM, #NORMAL non cacheable, -EXEC. // It holds the USB dQH and dTD descriptors and the endpoint buffers. diff --git a/src/runtime/runtime_mimxrt1062_teensy40.go b/src/runtime/runtime_mimxrt1062_teensy40.go new file mode 100644 index 0000000000..23b9df4d89 --- /dev/null +++ b/src/runtime/runtime_mimxrt1062_teensy40.go @@ -0,0 +1,8 @@ +//go:build teensy40 + +package runtime + +import "device/nxp" + +// MPU region size for the 2 MiB QSPI flash of the Teensy 4.0. +const qspiFlashMPUSize = nxp.RGNSZ_2MB diff --git a/src/runtime/runtime_mimxrt1062_teensy41.go b/src/runtime/runtime_mimxrt1062_teensy41.go new file mode 100644 index 0000000000..69ea406954 --- /dev/null +++ b/src/runtime/runtime_mimxrt1062_teensy41.go @@ -0,0 +1,8 @@ +//go:build teensy41 + +package runtime + +import "device/nxp" + +// MPU region size for the 8 MiB QSPI flash of the Teensy 4.1. +const qspiFlashMPUSize = nxp.RGNSZ_8MB diff --git a/targets/mimxrt1062-teensy41.ld b/targets/mimxrt1062-teensy41.ld new file mode 100644 index 0000000000..7c2e84f11b --- /dev/null +++ b/targets/mimxrt1062-teensy41.ld @@ -0,0 +1,130 @@ +MEMORY +{ + ITCM (rwx): ORIGIN = 0x00000000, LENGTH = 0x00080000 /* 512 Kib */ + DTCM (rwx): ORIGIN = 0x20000000, LENGTH = 0x00080000 /* 512 Kib */ + RAM (rwx): ORIGIN = 0x20200000, LENGTH = 0x0007F000 /* 512 KiB minus 4 KiB reserved for USB DMA descriptors/buffers */ + /* The last 256 KiB of the flash are reserved for machine.Flash data. */ + FLASH (rx): ORIGIN = 0x60000000, LENGTH = 0x007C0000 /* 7936 KiB */ +} + +ENTRY(Reset_Handler); + +_stack_size = 4K; + +SECTIONS +{ + .text : ALIGN(8) { + + FILL(0xFFFFFFFF); + + /* place flash config at beginning of flash device */ + KEEP(*(.flash_config)); + + /* IVT must be located at +4 Kbyte offset from base address of flash. */ + . = ORIGIN(FLASH) + 0x1000; + KEEP(*(.ivt)); + + . = ORIGIN(FLASH) + 0x1020; + KEEP(*(.boot_data)); + + . = ORIGIN(FLASH) + 0x2000; + + _svectors = ABSOLUTE(.); + KEEP(*(.isr_vector)); + . = ALIGN(8); + + *(.text.Reset_Handler); + . = ALIGN(8); + + _stext = .; + *(.text*); + *(.rodata* .constdata*); + . = ALIGN(8); + _etext = .; + + } > FLASH + + .tinygo_stacksizes : ALIGN(8) { + + *(.tinygo_stacksizes); + . = ALIGN(8); + + } > FLASH + + /* Code that runs from OCRAM2. The flash gives no data during an erase + or a program operation. See machine_mimxrt1062_flash.go. */ + .ramfuncs : ALIGN(8) { + + _sramfuncs = .; + *(.ramfuncs .ramfuncs.*); + . = ALIGN(16); + _eramfuncs = .; + + } > RAM AT > FLASH + + _framfuncs = LOADADDR(.ramfuncs); + + .text.padding (NOLOAD) : { + + . = ALIGN(32768); + + } > ITCM + + .stack (NOLOAD) : { + + . = ALIGN(8); + . += _stack_size; + _stack_top = .; + + } > DTCM + + .data : ALIGN(8) { + + FILL(0xFFFFFFFF); + + _sdata = .; + *(.data*); + . = ALIGN(8); + _edata = .; + + } > DTCM AT > FLASH + + .bss : ALIGN(8) { + + _sbss = .; + *(.bss*); + *(COMMON); + . = ALIGN(8); + _ebss = .; + + } > DTCM AT > DTCM + + /DISCARD/ : { + + *(.ARM.exidx*); /* causes spurious 'undefined reference' errors */ + + } + + _sidata = LOADADDR(.data); + + _heap_start = _eramfuncs; + _heap_end = ORIGIN(RAM) + LENGTH(RAM); + + /* USB DMA region, the 4 KiB reserved above RAM. */ + _usb_dma_start = ORIGIN(RAM) + LENGTH(RAM); + ASSERT((_usb_dma_start & 0x7FF) == 0, "_usb_dma_start must be 2 KiB aligned for ENDPTLISTADDR") + + /* Fixed data area for machine.Flash. The Teensy bootloader does not + erase it, see Teensyduino cores/teensy4/eeprom.c FLASH_BASEADDR. */ + __flash_data_start = ORIGIN(FLASH) + LENGTH(FLASH); + __flash_data_end = 0x607FF000; + + _globals_start = _sdata; + _globals_end = _ebss; + + _image_size = SIZEOF(.text) + SIZEOF(.tinygo_stacksizes) + SIZEOF(.ramfuncs) + SIZEOF(.data); + + /* TODO: link .text to ITCM */ + _itcm_blocks = (0 + 0x7FFF) >> 15; + _flexram_cfg = 0xAAAAAAAA | ((1 << (_itcm_blocks * 2)) - 1); +} diff --git a/targets/teensy41.json b/targets/teensy41.json index b5cbcf13fa..e4a09578e3 100644 --- a/targets/teensy41.json +++ b/targets/teensy41.json @@ -5,10 +5,10 @@ "serial-port": ["16c0:0483"], "flash-1200-bps-reset": "true", "automatic-stack-size": false, - "linkerscript": "targets/mimxrt1062-teensy40.ld", + "linkerscript": "targets/mimxrt1062-teensy41.ld", "extra-files": [ "src/device/nxp/mimxrt1062.s", - "targets/teensy40.s" + "targets/teensy41.s" ], "flash-command": "teensy_loader_cli -mmcu=imxrt1062 -v -w {hex}" } diff --git a/targets/teensy41.s b/targets/teensy41.s new file mode 100644 index 0000000000..b688a18dcb --- /dev/null +++ b/targets/teensy41.s @@ -0,0 +1,199 @@ +// ----------------------------------------------------------------------------- +// file: teensy41.s +// desc: various startup and configuration data for Teensy 4.1. +// ----------------------------------------------------------------------------- +// References +// i.MX RT1060 Processor Reference Manual +// - Section 9.7.1 "Image Vector Table and Boot Data" +// Teensyduino 1.53 by Paul Stoffregen (PJRC) +// - cores/teensy4/bootdata.c +// - cores/teensy4/startup.c +// ----------------------------------------------------------------------------- + +.section .boot_data +.global __boot_data +__boot_data: + .word 0x60000000 // boot start location + .word _image_size // flash size + .word 0 // plugin flag, use 0 to indicate normal (non-plugin) ROM image + +.section .ivt +.global __ivt +__ivt: + .word 0x402000D1 // header (version 4.0) + .word _svectors // image entry function + .word 0 // reserved + .word 0 // DCD info (optional, set to 0|NULL if unused) + .word __boot_data // boot data struct + .word __ivt // self + .word 0 // command sequence file (CSF) not provided in image + .word 0 // reserved + +.section .flash_config +.global __flash_config +__flash_config: + // 448 byte common FlexSPI configuration block, 8.6.3.1 page 223 (RT1060 rev 0) + // MCU_Flashloader_Reference_Manual.pdf, 8.2.1, Table 8-2, page 72-75 + .word 0x42464346 // Tag 0x00 + .word 0x56010000 // Version + .word 0 // reserved + .word 0x00020101 // columnAdressWidth,dataSetupTime,dataHoldTime,readSampleClkSrc + + .word 0x00000000 // waitTimeCfgCommands,-,deviceModeCfgEnable + .word 0 // deviceModeSeq + .word 0 // deviceModeArg + .word 0x00000000 // -,-,-,configCmdEnable + + .word 0 // configCmdSeqs 0x20 + .word 0 + .word 0 + .word 0 + + .word 0 // cfgCmdArgs 0x30 + .word 0 + .word 0 + .word 0 + + .word 0x00000000 // controllerMiscOption 0x40 + .word 0x00030401 // lutCustomSeqEnable,serialClkFreq,sflashPadType,deviceType + .word 0 // reserved + .word 0 // reserved + + //.word 0x00200000 // sflashA1Size (Teensy 4.0) 0x50 + .word 0x00800000 // sflashA1Size (Teensy 4.1) 0x50 + + .word 0 // sflashA2Size + .word 0 // sflashB1Size + .word 0 // sflashB2Size + + .word 0 // csPadSettingOverride 0x60 + .word 0 // sclkPadSettingOverride + .word 0 // dataPadSettingOverride + .word 0 // dqsPadSettingOverride + + .word 0 // timeoutInMs 0x70 + .word 0 // commandInterval + .word 0 // dataValidTime + .word 0x00000000 // busyBitPolarity,busyOffset + + .word 0x0A1804EB // lookupTable[0] 0x80 + .word 0x26043206 // lookupTable[1] + .word 0 // lookupTable[2] + .word 0 // lookupTable[3] + + .word 0x24040405 // lookupTable[4] 0x90 + .word 0 // lookupTable[5] + .word 0 // lookupTable[6] + .word 0 // lookupTable[7] + + .word 0 // lookupTable[8] 0xA0 + .word 0 // lookupTable[9] + .word 0 // lookupTable[10] + .word 0 // lookupTable[11] + + .word 0x00000406 // lookupTable[12] 0xB0 + .word 0 // lookupTable[13] + .word 0 // lookupTable[14] + .word 0 // lookupTable[15] + + .word 0 // lookupTable[16] 0xC0 + .word 0 // lookupTable[17] + .word 0 // lookupTable[18] + .word 0 // lookupTable[19] + + .word 0x08180420 // lookupTable[20] 0xD0 + .word 0 // lookupTable[21] + .word 0 // lookupTable[22] + .word 0 // lookupTable[23] + + .word 0 // lookupTable[24] 0xE0 + .word 0 // lookupTable[25] + .word 0 // lookupTable[26] + .word 0 // lookupTable[27] + + .word 0 // lookupTable[28] 0xF0 + .word 0 // lookupTable[29] + .word 0 // lookupTable[30] + .word 0 // lookupTable[31] + + .word 0x081804D8 // lookupTable[32] 0x100 + .word 0 // lookupTable[33] + .word 0 // lookupTable[34] + .word 0 // lookupTable[35] + + .word 0x08180402 // lookupTable[36] 0x110 + .word 0x00002004 // lookupTable[37] + .word 0 // lookupTable[38] + .word 0 // lookupTable[39] + + .word 0 // lookupTable[40] 0x120 + .word 0 // lookupTable[41] + .word 0 // lookupTable[42] + .word 0 // lookupTable[43] + + .word 0x00000460 // lookupTable[44] 0x130 + .word 0 // lookupTable[45] + .word 0 // lookupTable[46] + .word 0 // lookupTable[47] + + .word 0 // lookupTable[48] 0x140 + .word 0 // lookupTable[49] + .word 0 // lookupTable[50] + .word 0 // lookupTable[51] + + .word 0 // lookupTable[52] 0x150 + .word 0 // lookupTable[53] + .word 0 // lookupTable[54] + .word 0 // lookupTable[55] + + .word 0 // lookupTable[56] 0x160 + .word 0 // lookupTable[57] + .word 0 // lookupTable[58] + .word 0 // lookupTable[59] + + .word 0 // lookupTable[60] 0x170 + .word 0 // lookupTable[61] + .word 0 // lookupTable[62] + .word 0 // lookupTable[63] + + .word 0 // LUT 0: Read 0x180 + .word 0 // LUT 1: ReadStatus + .word 0 // LUT 3: WriteEnable + .word 0 // LUT 5: EraseSector + + .word 0 // LUT 9: PageProgram 0x190 + .word 0 // LUT 11: ChipErase + .word 0 // LUT 15: Dummy + .word 0 // LUT unused? + + .word 0 // LUT unused? 0x1A0 + .word 0 // LUT unused? + .word 0 // LUT unused? + .word 0 // LUT unused? + + .word 0 // reserved 0x1B0 + .word 0 // reserved + .word 0 // reserved + .word 0 // reserved + + // 64 byte Serial NOR configuration block (8.6.3.2, page 346) + + .word 256 // pageSize 0x1C0 + .word 4096 // sectorSize + .word 1 // ipCmdSerialClkFreq + .word 0 // reserved + + .word 0x00010000 // block size 0x1D0 + .word 0 // reserved + .word 0 // reserved + .word 0 // reserved + + .word 0 // reserved 0x1E0 + .word 0 // reserved + .word 0 // reserved + .word 0 // reserved + + .word 0 // reserved 0x1F0 + .word 0 // reserved + .word 0 // reserved + .word 0 // reserved