diff --git a/.gitignore b/.gitignore index a2bc8dc444..2761d1fcc7 100644 --- a/.gitignore +++ b/.gitignore @@ -37,5 +37,9 @@ test.exe test.gba test.hex test.nro +test.uf2 test.wasm wasm.wasm + +*.uf2 +*.elf \ No newline at end of file diff --git a/builder/build.go b/builder/build.go index 4c49fe13cd..51c62009e7 100644 --- a/builder/build.go +++ b/builder/build.go @@ -25,6 +25,7 @@ import ( "strings" "github.com/gofrs/flock" + "github.com/soypat/tinyboot/boot/picobin" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compiler" "github.com/tinygo-org/tinygo/goenv" @@ -812,6 +813,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return fmt.Errorf("could not modify stack sizes: %w", err) } } + + // Apply patches of bootloader in the order they appear. + if len(config.Target.BootPatches) > 0 { + err = applyPatches(result.Executable, config.Target.BootPatches) + } + if config.RP2040BootPatch() { // Patch the second stage bootloader CRC into the .boot2 section err = patchRP2040BootCRC(result.Executable) @@ -1428,6 +1435,23 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS } } +func applyPatches(executable string, bootPatches []string) (err error) { + for _, patch := range bootPatches { + switch patch { + case "rp2040": + err = patchRP2040BootCRC(executable) + case "rp2350": + err = patchRP2350BootIMAGE_DEF(executable) + default: + err = errors.New("undefined boot patch name") + } + if err != nil { + return fmt.Errorf("apply boot patch %q: %w", patch, err) + } + } + return nil +} + // RP2040 second stage bootloader CRC32 calculation // // Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf @@ -1439,7 +1463,7 @@ func patchRP2040BootCRC(executable string) error { } if len(bytes) != 256 { - return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes") + return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes, got %d", len(bytes)) } // From the 'official' RP2040 checksum script: @@ -1466,6 +1490,21 @@ func patchRP2040BootCRC(executable string) error { return replaceElfSection(executable, ".boot2", bytes) } +// RP2350 block patching. +func patchRP2350BootIMAGE_DEF(executable string) error { + boot2, _, err := getElfSectionData(executable, ".boot2") + if err != nil { + return err + } + item0 := picobin.MakeImageDef(picobin.ImageTypeExecutable, picobin.ExeSecSecure, picobin.ExeCPUARM, picobin.ExeChipRP2350, false) + newBoot := make([]byte, 256) + newBoot, _, err = picobin.AppendBlockFromItems(newBoot[:0], []picobin.Item{item0.Item}, boot2, 0) + off := len(newBoot) + newBoot, _, err = picobin.AppendFinalBlock(newBoot, -off) + // Update the .boot2 section to included the CRC + return replaceElfSection(executable, ".boot2", newBoot) +} + // lock may acquire a lock at the specified path. // It returns a function to release the lock. // If flock is not supported, it does nothing. @@ -1478,3 +1517,10 @@ func lock(path string) func() { return func() { flock.Close() } } + +func b2u8(b bool) uint8 { + if b { + return 1 + } + return 0 +} diff --git a/builder/objcopy.go b/builder/objcopy.go index cf10547b4d..8561ef5793 100644 --- a/builder/objcopy.go +++ b/builder/objcopy.go @@ -2,11 +2,11 @@ package builder import ( "debug/elf" - "io" + "fmt" "os" - "sort" "github.com/marcinbor85/gohex" + "github.com/soypat/tinyboot/build/elfutil" ) // maxPadBytes is the maximum allowed bytes to be padded in a rom extraction @@ -26,18 +26,12 @@ func (e objcopyError) Error() string { return e.Op + ": " + e.Err.Error() } -type progSlice []*elf.Prog - -func (s progSlice) Len() int { return len(s) } -func (s progSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr } -func (s progSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - // extractROM extracts a firmware image and the first load address from the // given ELF file. It tries to emulate the behavior of objcopy. func extractROM(path string) (uint64, []byte, error) { f, err := elf.Open(path) if err != nil { - return 0, nil, objcopyError{"failed to open ELF file to extract text segment", err} + return 0, nil, objcopyError{Op: "failed to open ELF file to extract text segment", Err: err} } defer f.Close() @@ -47,62 +41,33 @@ func extractROM(path string) (uint64, []byte, error) { // > memory dump of the contents of the input object file. All symbols and // > relocation information will be discarded. The memory dump will start at // > the load address of the lowest section copied into the output file. - - // Find the lowest section address. - startAddr := ^uint64(0) - for _, section := range f.Sections { - if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 { - continue - } - if section.Addr < startAddr { - startAddr = section.Addr - } + start, end, err := elfutil.ROMAddr(f) + if err != nil { + return 0, nil, objcopyError{Op: "failed to calculate ELF ROM addresses", Err: err} } - - progs := make(progSlice, 0, 2) - for _, prog := range f.Progs { - if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 0 { - continue - } - progs = append(progs, prog) + err = elfutil.EnsureROMContiguous(f, start, end, maxPadBytes) + if err != nil { + return 0, nil, objcopyError{Op: "checking if ELF ROM contiguous", Err: err} } - if len(progs) == 0 { - return 0, nil, objcopyError{"file does not contain ROM segments: " + path, nil} + const ( + _ = 1 << (iota * 10) + kB + MB + GB + ) + const maxSize = 1 * GB + if end-start > maxSize { + return 0, nil, objcopyError{Op: fmt.Sprintf("obj size exceeds max %d/%d, bad ELF address calculation?", end-start, maxSize)} } - sort.Sort(progs) - var rom []byte - for _, prog := range progs { - romEnd := progs[0].Paddr + uint64(len(rom)) - if prog.Paddr > romEnd && prog.Paddr < romEnd+16 { - // Sometimes, the linker seems to insert a bit of padding between - // segments. Simply zero-fill these parts. - rom = append(rom, make([]byte, prog.Paddr-romEnd)...) - } - if prog.Paddr != progs[0].Paddr+uint64(len(rom)) { - diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom))) - if diff > maxPadBytes { - return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil} - } - // Pad the difference - rom = append(rom, make([]byte, diff)...) - } - data, err := io.ReadAll(prog.Open()) - if err != nil { - return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err} - } - rom = append(rom, data...) - } - if progs[0].Paddr < startAddr { - // The lowest memory address is before the first section. This means - // that there is some extra data loaded at the start of the image that - // should be discarded. - // Example: ELF files where .text doesn't start at address 0 because - // there is a bootloader at the start. - return startAddr, rom[startAddr-progs[0].Paddr:], nil - } else { - return progs[0].Paddr, rom, nil + ROM := make([]byte, end-start) + n, err := elfutil.ReadROMAt(f, ROM, start) + if err != nil { + return 0, nil, objcopyError{Op: "reading ELF ROM", Err: err} + } else if n != len(ROM) { + return 0, nil, objcopyError{Op: "short ELF ROM read"} } + return start, ROM, nil } // objcopy converts an ELF file to a different (simpler) output file format: diff --git a/builder/uf2.go b/builder/uf2.go index 62bae5c66a..676acee09c 100644 --- a/builder/uf2.go +++ b/builder/uf2.go @@ -8,146 +8,34 @@ package builder // import ( - "bytes" - "encoding/binary" + "math" "os" - "strconv" + + "github.com/soypat/tinyboot/build/uf2" ) // convertELFFileToUF2File converts an ELF file to a UF2 file. func convertELFFileToUF2File(infile, outfile string, uf2FamilyID string) error { - // Read the .text segment. - targetAddress, data, err := extractROM(infile) - if err != nil { - return err + uf2Formatter := uf2.Formatter{ChunkSize: 256} + if uf2FamilyID != "" { + err := uf2Formatter.SetFamilyID(uf2FamilyID) + if err != nil { + return err + } } - - output, _, err := convertBinToUF2(data, uint32(targetAddress), uf2FamilyID) + start, ROM, err := extractROM(infile) if err != nil { return err + } else if start > math.MaxUint32 { + return objcopyError{Op: "ELF start ROM address overflows uint32"} } - return os.WriteFile(outfile, output, 0644) -} -// convertBinToUF2 converts the binary bytes in input to UF2 formatted data. -func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byte, int, error) { - blocks := split(input, 256) - output := make([]byte, 0) - - bl, err := newUF2Block(targetAddr, uf2FamilyID) + // Write raw ROM contents in UF2 format to outfile. + expectBlocks := len(ROM)/int(uf2Formatter.ChunkSize) + 1 + uf2data := make([]byte, expectBlocks*uf2.BlockSize) + uf2data, _, err = uf2Formatter.AppendTo(uf2data[:0], ROM, uint32(start)) if err != nil { - return nil, 0, err - } - bl.SetNumBlocks(len(blocks)) - - for i := 0; i < len(blocks); i++ { - bl.SetBlockNo(i) - bl.SetData(blocks[i]) - - output = append(output, bl.Bytes()...) - bl.IncrementAddress(bl.payloadSize) - } - - return output, len(blocks), nil -} - -const ( - uf2MagicStart0 = 0x0A324655 // "UF2\n" - uf2MagicStart1 = 0x9E5D5157 // Randomly selected - uf2MagicEnd = 0x0AB16F30 // Ditto -) - -// uf2Block is the structure used for each UF2 code block sent to device. -type uf2Block struct { - magicStart0 uint32 - magicStart1 uint32 - flags uint32 - targetAddr uint32 - payloadSize uint32 - blockNo uint32 - numBlocks uint32 - familyID uint32 - data []uint8 - magicEnd uint32 -} - -// newUF2Block returns a new uf2Block struct that has been correctly populated -func newUF2Block(targetAddr uint32, uf2FamilyID string) (*uf2Block, error) { - var flags uint32 - var familyID uint32 - if uf2FamilyID != "" { - flags |= flagFamilyIDPresent - v, err := strconv.ParseUint(uf2FamilyID, 0, 32) - if err != nil { - return nil, err - } - familyID = uint32(v) - } - return &uf2Block{magicStart0: uf2MagicStart0, - magicStart1: uf2MagicStart1, - magicEnd: uf2MagicEnd, - targetAddr: targetAddr, - flags: flags, - familyID: familyID, - payloadSize: 256, - data: make([]byte, 476), - }, nil -} - -const ( - flagFamilyIDPresent = 0x00002000 -) - -// Bytes converts the uf2Block to a slice of bytes that can be written to file. -func (b *uf2Block) Bytes() []byte { - buf := bytes.NewBuffer(make([]byte, 0, 512)) - binary.Write(buf, binary.LittleEndian, b.magicStart0) - binary.Write(buf, binary.LittleEndian, b.magicStart1) - binary.Write(buf, binary.LittleEndian, b.flags) - binary.Write(buf, binary.LittleEndian, b.targetAddr) - binary.Write(buf, binary.LittleEndian, b.payloadSize) - binary.Write(buf, binary.LittleEndian, b.blockNo) - binary.Write(buf, binary.LittleEndian, b.numBlocks) - binary.Write(buf, binary.LittleEndian, b.familyID) - binary.Write(buf, binary.LittleEndian, b.data) - binary.Write(buf, binary.LittleEndian, b.magicEnd) - - return buf.Bytes() -} - -// IncrementAddress moves the target address pointer forward by count bytes. -func (b *uf2Block) IncrementAddress(count uint32) { - b.targetAddr += b.payloadSize -} - -// SetData sets the data to be used for the current block. -func (b *uf2Block) SetData(d []byte) { - b.data = make([]byte, 476) - copy(b.data[:], d) -} - -// SetBlockNo sets the current block number to be used. -func (b *uf2Block) SetBlockNo(bn int) { - b.blockNo = uint32(bn) -} - -// SetNumBlocks sets the total number of blocks for this UF2 file. -func (b *uf2Block) SetNumBlocks(total int) { - b.numBlocks = uint32(total) -} - -// split splits a slice of bytes into a slice of byte slices of a specific size limit. -func split(input []byte, limit int) [][]byte { - var block []byte - output := make([][]byte, 0, len(input)/limit+1) - for len(input) >= limit { - // add all blocks - block, input = input[:limit], input[limit:] - output = append(output, block) - } - if len(input) > 0 { - // add remaining block (that isn't full sized) - output = append(output, input) + return objcopyError{Op: "writing UF2 blocks", Err: err} } - return output + return os.WriteFile(outfile, uf2data, 0644) } diff --git a/compileopts/target.go b/compileopts/target.go index f60ee30972..fe864fe9db 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -46,6 +46,7 @@ type TargetSpec struct { LinkerScript string `json:"linkerscript,omitempty"` ExtraFiles []string `json:"extra-files,omitempty"` RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum + BootPatches []string `json:"boot-patches,omitempty"` // Bootloader patches to be applied in the order they appear. Emulator string `json:"emulator,omitempty"` FlashCommand string `json:"flash-command,omitempty"` GDB []string `json:"gdb,omitempty"` diff --git a/go.mod b/go.mod index a4de141365..a0ea3c444b 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/soypat/tinyboot v0.0.0-20240922191516-a0ab175013d3 // indirect github.com/stretchr/testify v1.8.4 // indirect golang.org/x/text v0.16.0 // indirect ) diff --git a/go.sum b/go.sum index d3c1bd310c..50cf3ff82b 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzb github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= +github.com/soypat/tinyboot v0.0.0-20240922191516-a0ab175013d3 h1:qTeQ5xG5DQUW01KWdoMJ/XbZp8InTm1Mz7nR7Qfv4D0= +github.com/soypat/tinyboot v0.0.0-20240922191516-a0ab175013d3/go.mod h1:OsayN6hjGmz9VPyFHqVH6OSAUvUjXAcMviO3YS336hA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= diff --git a/src/machine/board_pico2.go b/src/machine/board_pico2.go new file mode 100644 index 0000000000..327c542fbc --- /dev/null +++ b/src/machine/board_pico2.go @@ -0,0 +1,88 @@ +//go:build pico2 + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico2" + usb_STRING_MANUFACTURER = "Raspberry Pi" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/src/machine/machine_rp2040.go b/src/machine/machine_rp2.go similarity index 71% rename from src/machine/machine_rp2040.go rename to src/machine/machine_rp2.go index 45f9f510f5..1d01866bd0 100644 --- a/src/machine/machine_rp2040.go +++ b/src/machine/machine_rp2.go @@ -1,15 +1,44 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine import ( "device/rp" + "runtime/interrupt" "runtime/volatile" "unsafe" ) const deviceName = rp.Device +const ( + // Number of spin locks available + _NUMSPINLOCKS = 32 + // Number of interrupt handlers available + _NUMIRQ = 32 + _PICO_SPINLOCK_ID_IRQ = 9 +) + +// UART on the RP2040 +var ( + UART0 = &_UART0 + _UART0 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART0, + } + + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART1, + } +) + +func init() { + UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) + UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) +} + //go:linkname machineInit runtime.machineInit func machineInit() { // Reset all peripherals to put system into a known state, @@ -26,13 +55,7 @@ func machineInit() { // 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) + bits = ^uint32(initUnreset) unresetBlockWait(bits) clocks.init() @@ -94,4 +117,25 @@ const ( ) // DMA channels usable on the RP2040. -var dmaChannels = (*[12]dmaChannel)(unsafe.Pointer(rp.DMA)) +var dmaChannels = (*[12 + 4*rp2350ExtraReg]dmaChannel)(unsafe.Pointer(rp.DMA)) + +//go:inline +func boolToBit(a bool) uint32 { + if a { + return 1 + } + return 0 +} + +//go:inline +func u32max(a, b uint32) uint32 { + if a > b { + return a + } + return b +} + +//go:inline +func isReservedI2CAddr(addr uint8) bool { + return (addr&0x78) == 0 || (addr&0x78) == 0x78 +} diff --git a/src/machine/machine_rp2040_i2c.go b/src/machine/machine_rp2040_i2c.go index b7dc63d2b2..f34aa259f5 100644 --- a/src/machine/machine_rp2040_i2c.go +++ b/src/machine/machine_rp2040_i2c.go @@ -631,24 +631,3 @@ func (b i2cAbortError) Reasons() (reasons []string) { } return reasons } - -//go:inline -func boolToBit(a bool) uint32 { - if a { - return 1 - } - return 0 -} - -//go:inline -func u32max(a, b uint32) uint32 { - if a > b { - return a - } - return b -} - -//go:inline -func isReservedI2CAddr(addr uint8) bool { - return (addr&0x78) == 0 || (addr&0x78) == 0x78 -} diff --git a/src/machine/machine_rp2350_resets.go b/src/machine/machine_rp2350_resets.go new file mode 100644 index 0000000000..830fc13468 --- /dev/null +++ b/src/machine/machine_rp2350_resets.go @@ -0,0 +1,44 @@ +//go:build rp2350 + +package machine + +import ( + "device/rp" + "runtime/volatile" + "unsafe" +) + +// RESETS_RESET_Msk is bitmask to reset all peripherals +// +// TODO: This field is not available in the device file. +const RESETS_RESET_Msk = 0x01ffffff + +type resetsType struct { + frceOn volatile.Register32 + frceOff volatile.Register32 + wdSel volatile.Register32 + resetDone volatile.Register32 +} + +var resets = (*resetsType)(unsafe.Pointer(rp.RESETS)) + +// resetBlock resets hardware blocks specified +// by the bit pattern in bits. +func resetBlock(bits uint32) { + resets.frceOff.Set(bits) +} + +// unresetBlock brings hardware blocks specified by the +// bit pattern in bits out of reset. +func unresetBlock(bits uint32) { + resets.frceOn.Set(bits) +} + +// unresetBlockWait brings specified hardware blocks +// specified by the bit pattern in bits +// out of reset and wait for completion. +func unresetBlockWait(bits uint32) { + unresetBlock(bits) + for !resets.resetDone.HasBits(bits) { + } +} diff --git a/src/machine/machine_rp2_2040.go b/src/machine/machine_rp2_2040.go new file mode 100644 index 0000000000..f42a23c7ee --- /dev/null +++ b/src/machine/machine_rp2_2040.go @@ -0,0 +1,148 @@ +//go:build rp2040 + +package machine + +import ( + "device/rp" + "unsafe" +) + +const ( + _NUMBANK0_GPIOS = 30 + _NUMBANK0_IRQS = 4 + rp2350ExtraReg = 0 + initUnreset = 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 +) + +const ( + PinOutput PinMode = iota + PinInput + PinInputPulldown + PinInputPullup + PinAnalog + PinUART + PinPWM + PinI2C + PinSPI + PinPIO0 + PinPIO1 +) + +// GPIO function selectors +const ( + fnJTAG pinFunc = 0 + fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO + fnUART pinFunc = 2 + fnI2C pinFunc = 3 + // Connect a PWM slice to GPIO. There are eight PWM slices, + // each with two outputchannels (A/B). The B pin can also be used as an input, + // for frequency and duty cyclemeasurement + fnPWM pinFunc = 4 + // Software control of GPIO, from the single-cycle IO (SIO) block. + // The SIO function (F5)must be selected for the processors to drive a GPIO, + // but the input is always connected,so software can check the state of GPIOs at any time. + fnSIO pinFunc = 5 + // Connect one of the programmable IO blocks (PIO) to GPIO. PIO can implement a widevariety of interfaces, + // and has its own internal pin mapping hardware, allowing flexibleplacement of digital interfaces on bank 0 GPIOs. + // The PIO function (F6, F7) must beselected for PIO to drive a GPIO, but the input is always connected, + // so the PIOs canalways see the state of all pins. + fnPIO0, fnPIO1 pinFunc = 6, 7 + // General purpose clock inputs/outputs. Can be routed to a number of internal clock domains onRP2040, + // e.g. Input: to provide a 1 Hz clock for the RTC, or can be connected to an internalfrequency counter. + // e.g. Output: optional integer divide + fnGPCK pinFunc = 8 + // USB power control signals to/from the internal USB controller + fnUSB pinFunc = 9 + fnNULL pinFunc = 0x1f + + fnXIP pinFunc = 0 +) + +// Configure configures the gpio pin as per mode. +func (p Pin) Configure(config PinConfig) { + if p == NoPin { + return + } + p.init() + mask := uint32(1) << p + switch config.Mode { + case PinOutput: + p.setFunc(fnSIO) + rp.SIO.GPIO_OE_SET.Set(mask) + case PinInput: + p.setFunc(fnSIO) + p.pulloff() + case PinInputPulldown: + p.setFunc(fnSIO) + p.pulldown() + case PinInputPullup: + p.setFunc(fnSIO) + p.pullup() + case PinAnalog: + p.setFunc(fnNULL) + p.pulloff() + case PinUART: + p.setFunc(fnUART) + case PinPWM: + p.setFunc(fnPWM) + case PinI2C: + // IO config according to 4.3.1.3 of rp2040 datasheet. + p.setFunc(fnI2C) + p.pullup() + p.setSchmitt(true) + p.setSlew(false) + case PinSPI: + p.setFunc(fnSPI) + case PinPIO0: + p.setFunc(fnPIO0) + case PinPIO1: + p.setFunc(fnPIO1) + } +} + +var ( + timer = (*timerType)(unsafe.Pointer(rp.TIMER)) +) + +// Enable or disable a specific interrupt on the executing core. +// num is the interrupt number which must be in [0,31]. +func irqSet(num uint32, enabled bool) { + if num >= _NUMIRQ { + return + } + irqSetMask(1<= _NUMIRQ { + return + } + irqSetMask(1<>(4*(p%8))) & 0xf } - -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} diff --git a/src/machine/machine_rp2040_pins.go b/src/machine/machine_rp2_pins.go similarity index 85% rename from src/machine/machine_rp2040_pins.go rename to src/machine/machine_rp2_pins.go index 9abbdb002e..93f2d50a01 100644 --- a/src/machine/machine_rp2040_pins.go +++ b/src/machine/machine_rp2_pins.go @@ -1,4 +1,4 @@ -//go:build rp2040 || ae_rp2040 || badger2040 || challenger_rp2040 || feather_rp2040 || gopher_badge || kb2040 || macropad_rp2040 || nano_rp2040 || pico || qtpy_rp2040 || thingplus_rp2040 || thumby || tufty2040 || waveshare_rp2040_zero || xiao_rp2040 +//go:build rp2040 || rp2350 || ae_rp2040 || badger2040 || challenger_rp2040 || feather_rp2040 || gopher_badge || kb2040 || macropad_rp2040 || nano_rp2040 || pico || qtpy_rp2040 || thingplus_rp2040 || thumby || tufty2040 || waveshare_rp2040_zero || xiao_rp2040 package machine diff --git a/src/machine/machine_rp2040_pll.go b/src/machine/machine_rp2_pll.go similarity index 98% rename from src/machine/machine_rp2040_pll.go rename to src/machine/machine_rp2_pll.go index d611f2924d..f409768ab3 100644 --- a/src/machine/machine_rp2040_pll.go +++ b/src/machine/machine_rp2_pll.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine diff --git a/src/machine/machine_rp2040_sync.go b/src/machine/machine_rp2_sync.go similarity index 62% rename from src/machine/machine_rp2040_sync.go rename to src/machine/machine_rp2_sync.go index 4c9b443237..5019552afd 100644 --- a/src/machine/machine_rp2040_sync.go +++ b/src/machine/machine_rp2_sync.go @@ -1,24 +1,11 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine -import ( - "device/rp" -) - // machine_rp2040_sync.go contains interrupt and // lock primitives similar to those found in Pico SDK's // irq.c -const ( - // Number of spin locks available - _NUMSPINLOCKS = 32 - // Number of interrupt handlers available - _NUMIRQ = 32 - _PICO_SPINLOCK_ID_IRQ = 9 - _NUMBANK0_GPIOS = 30 -) - // Clears interrupt flag on a pin func (p Pin) acknowledgeInterrupt(change PinChange) { ioBank0.intR[p>>3].Set(p.ioIntBit(change)) @@ -50,23 +37,3 @@ func (p Pin) ctrlSetInterrupt(change PinChange, enabled bool, base *irqCtrl) { enReg.ClearBits(p.ioIntBit(change)) } } - -// Enable or disable a specific interrupt on the executing core. -// num is the interrupt number which must be in [0,31]. -func irqSet(num uint32, enabled bool) { - if num >= _NUMIRQ { - return - } - irqSetMask(1<