diff --git a/Makefile b/Makefile index af61eb40..da699cc3 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ else LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF' endif -.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr +.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target @@ -107,7 +107,7 @@ fmt-check: @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 -gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp +gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp ifneq ($(STM32), 0) gen-device: gen-device-stm32 endif @@ -150,6 +150,9 @@ gen-device-stm32: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/ GO111MODULE=off $(GO) fmt ./src/device/stm32 +gen-device-rp: build/gen-device-svd + ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/ + GO111MODULE=off $(GO) fmt ./src/device/rp # Get LLVM sources. $(LLVM_PROJECTDIR)/llvm: @@ -351,6 +354,8 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pico examples/blinky1 + @$(MD5SUM) test.hex # test pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm @$(MD5SUM) test.hex diff --git a/src/machine/board_pico.go b/src/machine/board_pico.go new file mode 100644 index 00000000..b1be4b91 --- /dev/null +++ b/src/machine/board_pico.go @@ -0,0 +1,43 @@ +// +build pico + +package machine + +// GPIO pins +const ( + GP0 Pin = 0 + GP1 Pin = 1 + GP2 Pin = 2 + GP3 Pin = 3 + GP4 Pin = 4 + GP5 Pin = 5 + GP6 Pin = 6 + GP7 Pin = 7 + GP8 Pin = 8 + GP9 Pin = 9 + GP10 Pin = 10 + GP11 Pin = 11 + GP12 Pin = 12 + GP13 Pin = 13 + GP14 Pin = 14 + GP15 Pin = 15 + GP16 Pin = 16 + GP17 Pin = 17 + GP18 Pin = 18 + GP19 Pin = 19 + GP20 Pin = 20 + GP21 Pin = 21 + GP22 Pin = 22 + GP23 Pin = 23 + GP24 Pin = 24 + GP25 Pin = 25 + GP26 Pin = 26 + GP27 Pin = 27 + GP28 Pin = 28 + GP29 Pin = 29 + + // Onboard LED + LED Pin = GP25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) diff --git a/src/machine/machine_rp2040.go b/src/machine/machine_rp2040.go new file mode 100644 index 00000000..ecab6688 --- /dev/null +++ b/src/machine/machine_rp2040.go @@ -0,0 +1,44 @@ +// +build rp2040 + +package machine + +import ( + "device/rp" + _ "unsafe" +) + +//go:linkname machineInit runtime.machineInit +func machineInit() { + // Reset all peripherals to put system into a known state, + // except for QSPI pads and the XIP IO bank, as this is fatal if running from flash + // and the PLLs, as this is fatal if clock muxing has not been reset on this boot + // and USB, syscfg, as this disturbs USB-to-SWD on core 1 + bits := ^uint32(rp.RESETS_RESET_IO_QSPI | + rp.RESETS_RESET_PADS_QSPI | + rp.RESETS_RESET_PLL_USB | + rp.RESETS_RESET_USBCTRL | + rp.RESETS_RESET_SYSCFG | + rp.RESETS_RESET_PLL_SYS) + resetBlock(bits) + + // Remove reset from peripherals which are clocked only by clkSys and + // clkRef. Other peripherals stay in reset until we've configured clocks. + bits = ^uint32(rp.RESETS_RESET_ADC | + rp.RESETS_RESET_RTC | + rp.RESETS_RESET_SPI0 | + rp.RESETS_RESET_SPI1 | + rp.RESETS_RESET_UART0 | + rp.RESETS_RESET_UART1 | + rp.RESETS_RESET_USBCTRL) + unresetBlockWait(bits) + + clocks.init() + + // Peripheral clocks should now all be running + unresetBlockWait(RESETS_RESET_Msk) +} + +//go:linkname ticks runtime.machineTicks +func ticks() uint64 { + return timer.timeElapsed() +} diff --git a/src/machine/machine_rp2040_clocks.go b/src/machine/machine_rp2040_clocks.go new file mode 100644 index 00000000..9edaa2ed --- /dev/null +++ b/src/machine/machine_rp2040_clocks.go @@ -0,0 +1,251 @@ +// +build rp2040 + +package machine + +import ( + "device/arm" + "device/rp" + "runtime/volatile" + "unsafe" +) + +const ( + KHz = 1000 + MHz = 1000000 +) + +// clockIndex identifies a hardware clock +type clockIndex uint8 + +const ( + clkGPOUT0 clockIndex = iota // GPIO Muxing 0 + clkGPOUT1 // GPIO Muxing 1 + clkGPOUT2 // GPIO Muxing 2 + clkGPOUT3 // GPIO Muxing 3 + clkRef // Watchdog and timers reference clock + clkSys // Processors, bus fabric, memory, memory mapped registers + clkPeri // Peripheral clock for UART and SPI + clkUSB // USB clock + clkADC // ADC clock + clkRTC // Real time clock + numClocks +) + +type clockType struct { + ctrl volatile.Register32 + div volatile.Register32 + selected volatile.Register32 +} + +type fc struct { + refKHz volatile.Register32 + minKHz volatile.Register32 + maxKHz volatile.Register32 + delay volatile.Register32 + interval volatile.Register32 + src volatile.Register32 + status volatile.Register32 + result volatile.Register32 +} + +type clocksType struct { + clk [numClocks]clockType + resus struct { + ctrl volatile.Register32 + status volatile.Register32 + } + fc0 fc + wakeEN0 volatile.Register32 + wakeEN1 volatile.Register32 + sleepEN0 volatile.Register32 + sleepEN1 volatile.Register32 + enabled0 volatile.Register32 + enabled1 volatile.Register32 + intR volatile.Register32 + intE volatile.Register32 + intF volatile.Register32 + intS volatile.Register32 +} + +var clocks = (*clocksType)(unsafe.Pointer(rp.CLOCKS)) + +var configuredFreq [numClocks]uint32 + +type clock struct { + *clockType + cix clockIndex +} + +// clock returns the clock identified by cix. +func (clks *clocksType) clock(cix clockIndex) *clock { + return &clock{ + &clks.clk[cix], + cix, + } +} + +// hasGlitchlessMux returns true if clock contains a glitchless multiplexer. +// +// Clock muxing consists of two components: +// +// A glitchless mux, which can be switched freely, but whose inputs must be +// free-running. +// +// An auxiliary (glitchy) mux, whose output glitches when switched, but has +// no constraints on its inputs. +// +// Not all clocks have both types of mux. +func (clk *clock) hasGlitchlessMux() bool { + return clk.cix == clkSys || clk.cix == clkRef +} + +// configure configures the clock by selecting the main clock source src +// and the auxiliary clock source auxsrc +// and finally setting the clock frequency to freq +// given the input clock source frequency srcFreq. +func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { + if freq > srcFreq { + panic("clock frequency cannot be greater than source frequency") + } + + // Div register is 24.8 int.frac divider so multiply by 2^8 (left shift by 8) + div := uint32((uint64(srcFreq) << 8) / uint64(freq)) + + // If increasing divisor, set divisor before source. Otherwise set source + // before divisor. This avoids a momentary overspeed when e.g. switching + // to a faster source and increasing divisor to compensate. + if div > clk.div.Get() { + clk.div.Set(div) + } + + // If switching a glitchless slice (ref or sys) to an aux source, switch + // away from aux *first* to avoid passing glitches when changing aux mux. + // Assume (!!!) glitchless source 0 is no faster than the aux source. + if clk.hasGlitchlessMux() && src == rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX { + clk.ctrl.ClearBits(rp.CLOCKS_CLK_REF_CTRL_SRC_Msk) + for !clk.selected.HasBits(1) { + } + } else + // If no glitchless mux, cleanly stop the clock to avoid glitches + // propagating when changing aux mux. Note it would be a really bad idea + // to do this on one of the glitchless clocks (clkSys, clkRef). + { + // Disable clock. On clkRef and clkSys this does nothing, + // all other clocks have the ENABLE bit in the same position. + clk.ctrl.ClearBits(rp.CLOCKS_CLK_GPOUT0_CTRL_ENABLE_Msk) + if configuredFreq[clk.cix] > 0 { + // Delay for 3 cycles of the target clock, for ENABLE propagation. + // Note XOSC_COUNT is not helpful here because XOSC is not + // necessarily running, nor is timer... so, 3 cycles per loop: + delayCyc := configuredFreq[clkSys]/configuredFreq[clk.cix] + 1 + arm.AsmFull( + ` + ldr r0, {cyc} + 1: + subs r0, #1 + bne 1b + `, + map[string]interface{}{ + "cyc": &delayCyc, + }) + } + } + + // Set aux mux first, and then glitchless mux if this clock has one. + clk.ctrl.ReplaceBits(auxsrc<= postDiv2. +// +// Post Divider 2, postDiv2 with range 1-7. +func (pll *pll) init(refdiv, vcoFreq, postDiv1, postDiv2 uint32) { + refFreq := xoscFreq / refdiv + + // What are we multiplying the reference clock by to get the vco freq + // (The regs are called div, because you divide the vco output and compare it to the refclk) + fbdiv := vcoFreq / (refFreq * MHz) + + // Check fbdiv range + if !(fbdiv >= 16 && fbdiv <= 320) { + panic("fbdiv should be in the range [16,320]") + } + + // Check divider ranges + if !((postDiv1 >= 1 && postDiv1 <= 7) && (postDiv2 >= 1 && postDiv2 <= 7)) { + panic("postdiv1, postdiv1 should be in the range [1,7]") + } + + // postDiv1 should be >= postDiv2 + // from appnote page 11 + // postdiv1 is designed to operate with a higher input frequency + // than postdiv2 + if postDiv1 < postDiv2 { + panic("postdiv1 should be greater than or equal to postdiv2") + } + + // Check that reference frequency is no greater than vco / 16 + if refFreq > vcoFreq/16 { + panic("reference frequency should not be greater than vco frequency divided by 16") + } + + // div1 feeds into div2 so if div1 is 5 and div2 is 2 then you get a divide by 10 + pdiv := postDiv1< 15 { + panic("xosc frequency cannot be greater than 15MHz") + } + osc.ctrl.Set(rp.XOSC_CTRL_FREQ_RANGE_1_15MHZ) + + // Set xosc startup delay + delay := (((xoscFreq * MHz) / 1000) + 128) / 256 + osc.startup.Set(uint32(delay)) + + // Set the enable bit now that we have set freq range and startup delay + osc.ctrl.SetBits(rp.XOSC_CTRL_ENABLE_ENABLE << rp.XOSC_CTRL_ENABLE_Pos) + + // Wait for xosc to be stable + for !osc.status.HasBits(rp.XOSC_STATUS_STABLE) { + } +} diff --git a/src/runtime/runtime_rp2040.go b/src/runtime/runtime_rp2040.go new file mode 100644 index 00000000..162f18f1 --- /dev/null +++ b/src/runtime/runtime_rp2040.go @@ -0,0 +1,58 @@ +// +build rp2040 + +package runtime + +import ( + "device/arm" +) + +// machineTicks is provided by package machine. +func machineTicks() uint64 + +type timeUnit uint64 + +// ticks returns the number of ticks (microseconds) elapsed since power up. +func ticks() timeUnit { + t := machineTicks() + return timeUnit(t) +} + +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * 1000 +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / 1000) +} + +func sleepTicks(d timeUnit) { + if d == 0 { + return + } + sleepUntil := ticks() + d + for ticks() < sleepUntil { + } +} + +func waitForEvents() { + arm.Asm("wfe") +} + +func putchar(c byte) { +} + +// machineInit is provided by package machine. +func machineInit() + +func init() { + machineInit() +} + +func postinit() {} + +//export Reset_Handler +func main() { + preinit() + run() + abort() +} diff --git a/targets/pico.json b/targets/pico.json new file mode 100644 index 00000000..e8e5a06b --- /dev/null +++ b/targets/pico.json @@ -0,0 +1,10 @@ +{ + "inherits": [ + "rp2040" + ], + "build-tags": ["pico"], + "linkerscript": "targets/pico.ld", + "extra-files": [ + "targets/pico_boot_stage2.S" + ] +} diff --git a/targets/pico.ld b/targets/pico.ld new file mode 100644 index 00000000..267fbc4c --- /dev/null +++ b/targets/pico.ld @@ -0,0 +1,31 @@ + +MEMORY +{ + FLASH_TEXT (rx) : ORIGIN = 0x10000000, LENGTH = 2048k +} + +SECTIONS +{ + /* Second stage bootloader is prepended to the image. It must be 256 bytes big + and checksummed. It is usually built by the boot_stage2 target + in the Raspberry Pi Pico SDK + */ + + .boot2 : { + __boot2_start__ = .; + KEEP (*(.boot2)) + __boot2_end__ = .; + } > FLASH_TEXT + + ASSERT(__boot2_end__ - __boot2_start__ == 256, + "ERROR: Pico second stage bootloader must be 256 bytes in size") + + /* The second stage will always enter the image at the start of .text. + The debugger will use the ELF entry point, which is the _entry_point + symbol if present, otherwise defaults to start of .text. + This can be used to transfer control back to the bootrom on debugger + launches only, to perform proper flash setup. + */ +} + +INCLUDE "targets/rp2040.ld" diff --git a/targets/pico_boot_stage2.S b/targets/pico_boot_stage2.S new file mode 100644 index 00000000..4902d3bb --- /dev/null +++ b/targets/pico_boot_stage2.S @@ -0,0 +1,23 @@ +// Padded and checksummed version of: /home/rkanchan/src/pico-sdk/build/src/rp2_common/boot_stage2/bs2_default.bin + +.cpu cortex-m0plus +.thumb + +.section .boot2, "ax" + +.byte 0x00, 0xb5, 0x32, 0x4b, 0x21, 0x20, 0x58, 0x60, 0x98, 0x68, 0x02, 0x21, 0x88, 0x43, 0x98, 0x60 +.byte 0xd8, 0x60, 0x18, 0x61, 0x58, 0x61, 0x2e, 0x4b, 0x00, 0x21, 0x99, 0x60, 0x02, 0x21, 0x59, 0x61 +.byte 0x01, 0x21, 0xf0, 0x22, 0x99, 0x50, 0x2b, 0x49, 0x19, 0x60, 0x01, 0x21, 0x99, 0x60, 0x35, 0x20 +.byte 0x00, 0xf0, 0x44, 0xf8, 0x02, 0x22, 0x90, 0x42, 0x14, 0xd0, 0x06, 0x21, 0x19, 0x66, 0x00, 0xf0 +.byte 0x34, 0xf8, 0x19, 0x6e, 0x01, 0x21, 0x19, 0x66, 0x00, 0x20, 0x18, 0x66, 0x1a, 0x66, 0x00, 0xf0 +.byte 0x2c, 0xf8, 0x19, 0x6e, 0x19, 0x6e, 0x19, 0x6e, 0x05, 0x20, 0x00, 0xf0, 0x2f, 0xf8, 0x01, 0x21 +.byte 0x08, 0x42, 0xf9, 0xd1, 0x00, 0x21, 0x99, 0x60, 0x1b, 0x49, 0x19, 0x60, 0x00, 0x21, 0x59, 0x60 +.byte 0x1a, 0x49, 0x1b, 0x48, 0x01, 0x60, 0x01, 0x21, 0x99, 0x60, 0xeb, 0x21, 0x19, 0x66, 0xa0, 0x21 +.byte 0x19, 0x66, 0x00, 0xf0, 0x12, 0xf8, 0x00, 0x21, 0x99, 0x60, 0x16, 0x49, 0x14, 0x48, 0x01, 0x60 +.byte 0x01, 0x21, 0x99, 0x60, 0x01, 0xbc, 0x00, 0x28, 0x00, 0xd0, 0x00, 0x47, 0x12, 0x48, 0x13, 0x49 +.byte 0x08, 0x60, 0x03, 0xc8, 0x80, 0xf3, 0x08, 0x88, 0x08, 0x47, 0x03, 0xb5, 0x99, 0x6a, 0x04, 0x20 +.byte 0x01, 0x42, 0xfb, 0xd0, 0x01, 0x20, 0x01, 0x42, 0xf8, 0xd1, 0x03, 0xbd, 0x02, 0xb5, 0x18, 0x66 +.byte 0x18, 0x66, 0xff, 0xf7, 0xf2, 0xff, 0x18, 0x6e, 0x18, 0x6e, 0x02, 0xbd, 0x00, 0x00, 0x02, 0x40 +.byte 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x07, 0x00, 0x00, 0x03, 0x5f, 0x00, 0x21, 0x22, 0x00, 0x00 +.byte 0xf4, 0x00, 0x00, 0x18, 0x22, 0x20, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x10, 0x08, 0xed, 0x00, 0xe0 +.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xb2, 0x4e, 0x7a diff --git a/targets/rp2040.json b/targets/rp2040.json new file mode 100644 index 00000000..ce49ba35 --- /dev/null +++ b/targets/rp2040.json @@ -0,0 +1,12 @@ +{ + "inherits": ["cortex-m0plus"], + "build-tags": ["rp2040", "rp"], + "flash-method": "msd", + "msd-volume-name": "RPI-RP2", + "msd-firmware-name": "firmware.uf2", + "binary-format": "uf2", + "uf2-family-id": "0xe48bff56", + "extra-files": [ + "src/device/rp/rp2040.s" + ] +} diff --git a/targets/rp2040.ld b/targets/rp2040.ld new file mode 100644 index 00000000..186db68f --- /dev/null +++ b/targets/rp2040.ld @@ -0,0 +1,9 @@ + +MEMORY +{ + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 256k +} + +_stack_size = 2K; + +INCLUDE "targets/arm.ld"