rp2040: Add initial support for the rp2040 mcu

Support the rp2040 (as tested on a Raspberry Pi Pico board).  This
adds basic uart, timer, gpio, and watchdog support.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2021-06-19 21:43:26 -04:00
parent 20c5976356
commit 045bfa4e8d
11 changed files with 642 additions and 0 deletions

View File

@ -21,6 +21,8 @@ choice
bool "LPC176x (Smoothieboard)" bool "LPC176x (Smoothieboard)"
config MACH_STM32 config MACH_STM32
bool "STMicroelectronics STM32" bool "STMicroelectronics STM32"
config MACH_RP2040
bool "Raspberry Pi RP2040"
config MACH_PRU config MACH_PRU
bool "Beaglebone PRU" bool "Beaglebone PRU"
config MACH_LINUX config MACH_LINUX
@ -34,6 +36,7 @@ source "src/atsam/Kconfig"
source "src/atsamd/Kconfig" source "src/atsamd/Kconfig"
source "src/lpc176x/Kconfig" source "src/lpc176x/Kconfig"
source "src/stm32/Kconfig" source "src/stm32/Kconfig"
source "src/rp2040/Kconfig"
source "src/pru/Kconfig" source "src/pru/Kconfig"
source "src/linux/Kconfig" source "src/linux/Kconfig"
source "src/simulator/Kconfig" source "src/simulator/Kconfig"

59
src/rp2040/Kconfig Normal file
View File

@ -0,0 +1,59 @@
# Kconfig settings for STM32 processors
if MACH_RP2040
config RP2040_SELECT
bool
default y
select HAVE_GPIO
select HAVE_GPIO_BITBANGING
select HAVE_STRICT_TIMING
config BOARD_DIRECTORY
string
default "rp2040"
config MCU
string
default "rp2040"
config CLOCK_FREQ
int
default 12000000
config FLASH_SIZE
hex
default 0x200000
config RAM_START
hex
default 0x20000000
config RAM_SIZE
hex
default 0x42000
config STACK_SIZE
int
default 512
config FLASH_START
hex
default 0x10000000
######################################################################
# Communication inteface
######################################################################
config USBSERIAL
bool
config SERIAL
bool
choice
prompt "Communication interface"
config RP2040_SERIAL_UART0
bool "Serial (on UART0 GPIO1/GPIO0)"
select SERIAL
endchoice
endif

48
src/rp2040/Makefile Normal file
View File

@ -0,0 +1,48 @@
# Additional RP2040 build rules
# Setup the toolchain
CROSS_PREFIX=arm-none-eabi-
dirs-y += src/rp2040 src/generic lib/rp2040/elf2uf2
CFLAGS += -mcpu=cortex-m0plus -mthumb -Ilib/cmsis-core
CFLAGS += -Ilib/rp2040 -Ilib/rp2040/cmsis_include
CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs
CFLAGS_klipper.elf += -T $(OUT)src/rp2040/rp2040_link.ld
$(OUT)klipper.elf: $(OUT)stage2.o $(OUT)src/rp2040/rp2040_link.ld
# Add source files
src-y += rp2040/main.c rp2040/gpio.c generic/crc16_ccitt.c
src-y += generic/armcm_boot.c generic/armcm_irq.c generic/armcm_reset.c
src-y += generic/timer_irq.c rp2040/timer.c
src-$(CONFIG_SERIAL) += rp2040/serial.c generic/serial_irq.c
# rp2040 stage2 building
$(OUT)stage2.o: lib/rp2040/boot_stage2/boot2_w25q080.S
@echo " Building rp2040 stage2 $@"
$(Q)$(CC) $(CFLAGS) -Ilib/rp2040/boot_stage2 -Ilib/rp2040/boot_stage2/asminclude -DPICO_FLASH_SPI_CLKDIV=2 -c $< -o $(OUT)stage2raw1.o
$(Q)$(LD) $(OUT)stage2raw1.o -nostartfiles --script=lib/rp2040/boot_stage2/boot_stage2.ld -o $(OUT)stage2raw.o
$(Q)$(OBJCOPY) -O binary $(OUT)stage2raw.o $(OUT)stage2raw.bin
$(Q)lib/rp2040/boot_stage2/pad_checksum -s 0xffffffff $(OUT)stage2raw.bin $(OUT)stage2.S
$(Q)$(CC) $(CFLAGS) -c $(OUT)stage2.S -o $(OUT)stage2.o
OBJS_klipper.elf += $(OUT)stage2.o
# Binary output file rules
target-y += $(OUT)klipper.uf2
$(OUT)lib/rp2040/elf2uf2/elf2uf2: lib/rp2040/elf2uf2/main.cpp
@echo " Building $@"
$(Q)g++ -g -O -Ilib/rp2040 $< -o $@
$(OUT)klipper.uf2: $(OUT)klipper.elf $(OUT)lib/rp2040/elf2uf2/elf2uf2
@echo " Creating uf2 file $@"
$(Q)$(OUT)lib/rp2040/elf2uf2/elf2uf2 $< $@
# Flash rules
flash: $(OUT)klipper.uf2
@echo "Error: Flashing not supported on rp2040."
@echo "Place target board in bootloader mode (hold bootsel button"
@echo "during powerup), mount the device as a usb drive, and copy"
@echo "$< to the device."
@exit -1

119
src/rp2040/gpio.c Normal file
View File

@ -0,0 +1,119 @@
// GPIO functions on rp2040
//
// Copyright (C) 2021 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <string.h> // ffs
#include "board/irq.h" // irq_save
#include "command.h" // shutdown
#include "gpio.h" // gpio_out_setup
#include "hardware/structs/iobank0.h" // iobank0_hw
#include "hardware/structs/padsbank0.h" // padsbank0_hw
#include "hardware/structs/sio.h" // sio_hw
#include "internal.h" // gpio_peripheral
#include "sched.h" // sched_shutdown
/****************************************************************
* Pin mappings
****************************************************************/
DECL_ENUMERATION_RANGE("pin", "gpio0", 0, 30);
// Set the mode and extended function of a pin
void
gpio_peripheral(uint32_t gpio, int func, int pull_up)
{
padsbank0_hw->io[gpio] = (
PADS_BANK0_GPIO0_IE_BITS
| (PADS_BANK0_GPIO0_DRIVE_VALUE_4MA << PADS_BANK0_GPIO0_DRIVE_MSB)
| (pull_up > 0 ? PADS_BANK0_GPIO0_PUE_BITS : 0)
| (pull_up < 0 ? PADS_BANK0_GPIO0_PDE_BITS : 0));
iobank0_hw->io[gpio].ctrl = func << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB;
}
// Convert a register and bit location back to an integer pin identifier
static int
mask_to_pin(uint32_t mask)
{
return ffs(mask)-1;
}
/****************************************************************
* General Purpose Input Output (GPIO) pins
****************************************************************/
struct gpio_out
gpio_out_setup(uint8_t pin, uint8_t val)
{
if (pin > 30)
goto fail;
struct gpio_out g = { .bit=1<<pin };
gpio_out_reset(g, val);
return g;
fail:
shutdown("Not an output pin");
}
void
gpio_out_reset(struct gpio_out g, uint8_t val)
{
int pin = mask_to_pin(g.bit);
irqstatus_t flag = irq_save();
gpio_out_write(g, val);
sio_hw->gpio_oe_set = g.bit;
gpio_peripheral(pin, 5, 0);
irq_restore(flag);
}
void
gpio_out_toggle_noirq(struct gpio_out g)
{
sio_hw->gpio_togl = g.bit;
}
void
gpio_out_toggle(struct gpio_out g)
{
gpio_out_toggle_noirq(g);
}
void
gpio_out_write(struct gpio_out g, uint8_t val)
{
if (val)
sio_hw->gpio_set = g.bit;
else
sio_hw->gpio_clr = g.bit;
}
struct gpio_in
gpio_in_setup(uint8_t pin, int8_t pull_up)
{
if (pin > 30)
goto fail;
struct gpio_in g = { .bit=1<<pin };
gpio_in_reset(g, pull_up);
return g;
fail:
shutdown("Not an input pin");
}
void
gpio_in_reset(struct gpio_in g, int8_t pull_up)
{
int pin = mask_to_pin(g.bit);
irqstatus_t flag = irq_save();
gpio_peripheral(pin, 5, pull_up);
sio_hw->gpio_oe_clr = g.bit;
irq_restore(flag);
}
uint8_t
gpio_in_read(struct gpio_in g)
{
return !!(sio_hw->gpio_in & g.bit);
}

22
src/rp2040/gpio.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef __RP2040_GPIO_H
#define __RP2040_GPIO_H
#include <stdint.h> // uint32_t
struct gpio_out {
uint32_t bit;
};
struct gpio_out gpio_out_setup(uint8_t pin, uint8_t val);
void gpio_out_reset(struct gpio_out g, uint8_t val);
void gpio_out_toggle_noirq(struct gpio_out g);
void gpio_out_toggle(struct gpio_out g);
void gpio_out_write(struct gpio_out g, uint8_t val);
struct gpio_in {
uint32_t bit;
};
struct gpio_in gpio_in_setup(uint8_t pin, int8_t pull_up);
void gpio_in_reset(struct gpio_in g, int8_t pull_up);
uint8_t gpio_in_read(struct gpio_in g);
#endif // gpio.h

12
src/rp2040/internal.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef __RP2040_INTERNAL_H
#define __RP2040_INTERNAL_H
// Local definitions for rp2040
#include "RP2040.h"
void enable_pclock(uint32_t reset_bit);
int is_enabled_pclock(uint32_t reset_bit);
uint32_t get_pclock_frequency(uint32_t reset_bit);
void gpio_peripheral(uint32_t gpio, int func, int pull_up);
#endif // internal.h

149
src/rp2040/main.c Normal file
View File

@ -0,0 +1,149 @@
// Startup code on rp2040
//
// Copyright (C) 2021 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <stdint.h> // uint32_t
#include "hardware/structs/clocks.h" // clock_hw_t
#include "hardware/structs/pll.h" // pll_hw_t
#include "hardware/structs/resets.h" // sio_hw
#include "hardware/structs/watchdog.h" // watchdog_hw
#include "hardware/structs/xosc.h" // xosc_hw
#include "sched.h" // sched_main
/****************************************************************
* watchdog handler
****************************************************************/
void
watchdog_reset(void)
{
watchdog_hw->load = 0x800000; // ~350ms
}
DECL_TASK(watchdog_reset);
void
watchdog_init(void)
{
watchdog_reset();
watchdog_hw->ctrl = (WATCHDOG_CTRL_PAUSE_DBG0_BITS
| WATCHDOG_CTRL_PAUSE_DBG1_BITS
| WATCHDOG_CTRL_PAUSE_JTAG_BITS
| WATCHDOG_CTRL_ENABLE_BITS);
}
DECL_INIT(watchdog_init);
/****************************************************************
* Clock setup
****************************************************************/
#define FREQ_XOSC 12000000
#define FREQ_SYS 125000000
void
enable_pclock(uint32_t reset_bit)
{
resets_hw->reset |= reset_bit;
resets_hw->reset &= ~reset_bit;
while (!(resets_hw->reset_done & reset_bit))
;
}
int
is_enabled_pclock(uint32_t reset_bit)
{
return !(resets_hw->reset & reset_bit);
}
uint32_t
get_pclock_frequency(uint32_t reset_bit)
{
return FREQ_SYS;
}
static void
xosc_setup(void)
{
xosc_hw->startup = DIV_ROUND_UP(FREQ_XOSC, 1000 * 256); // 1ms
xosc_hw->ctrl = (XOSC_CTRL_FREQ_RANGE_VALUE_1_15MHZ
| (XOSC_CTRL_ENABLE_VALUE_ENABLE << XOSC_CTRL_ENABLE_LSB));
while(!(xosc_hw->status & XOSC_STATUS_STABLE_BITS))
;
}
static void
pll_setup(pll_hw_t *pll, uint32_t mul, uint32_t postdiv)
{
// Setup pll
uint32_t refdiv = 1, fbdiv = mul, postdiv2 = 2, postdiv1 = postdiv/postdiv2;
pll->cs = refdiv;
pll->fbdiv_int = fbdiv;
pll->pwr = PLL_PWR_DSMPD_BITS | PLL_PWR_POSTDIVPD_BITS;
while (!(pll->cs & PLL_CS_LOCK_BITS))
;
// Setup post divider
pll->prim = ((postdiv1 << PLL_PRIM_POSTDIV1_LSB)
| (postdiv2 << PLL_PRIM_POSTDIV2_LSB));
pll->pwr = PLL_PWR_DSMPD_BITS;
}
static void
clk_aux_setup(uint32_t clk_id, uint32_t aux_id)
{
clock_hw_t *clk = &clocks_hw->clk[clk_id];
clk->ctrl = 0;
clk->ctrl = aux_id | CLOCKS_CLK_PERI_CTRL_ENABLE_BITS;
}
static void
clock_setup(void)
{
// Set clk_sys and clk_ref to use internal clock
clock_hw_t *csys = &clocks_hw->clk[clk_sys];
csys->ctrl &= ~CLOCKS_CLK_SYS_CTRL_SRC_BITS;
while (csys->selected != 0x1)
;
clock_hw_t *cref = &clocks_hw->clk[clk_ref];
cref->ctrl &= ~CLOCKS_CLK_REF_CTRL_SRC_BITS;
while (cref->selected != 0x1)
;
// Reset peripherals (that can be)
resets_hw->reset = ~(RESETS_RESET_IO_QSPI_BITS
| RESETS_RESET_PADS_QSPI_BITS);
// Setup xosc, pll_sys, and switch clk_sys
xosc_setup();
enable_pclock(RESETS_RESET_PLL_SYS_BITS);
pll_setup(pll_sys_hw, 125, 125*FREQ_XOSC/FREQ_SYS);
csys->ctrl = 0;
csys->div = 1<<CLOCKS_CLK_SYS_DIV_INT_LSB;
csys->ctrl = CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX;
while (!(csys->selected & (1 << 1)))
;
// Setup clk_peri
clk_aux_setup(clk_peri, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS);
// Enable watchdog tick (at 12Mhz)
cref->div = 1<<CLOCKS_CLK_REF_DIV_INT_LSB;
cref->ctrl = CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC;
while (!(cref->selected & (1 << 2)))
;
watchdog_hw->tick = 1 | WATCHDOG_TICK_ENABLE_BITS;
// Enable GPIO control
enable_pclock(RESETS_RESET_IO_BANK0_BITS | RESETS_RESET_PADS_BANK0_BITS);
}
// Main entry point - called from armcm_boot.c:ResetHandler()
void
armcm_main(void)
{
clock_setup();
sched_main();
}

View File

@ -0,0 +1,66 @@
// rp2040 linker script (based on armcm_link.lds.S and customized for stage2)
//
// Copyright (C) 2019-2021 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "autoconf.h" // CONFIG_FLASH_START
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
MEMORY
{
rom (rx) : ORIGIN = CONFIG_FLASH_START , LENGTH = CONFIG_FLASH_SIZE
ram (rwx) : ORIGIN = CONFIG_RAM_START , LENGTH = CONFIG_RAM_SIZE
}
SECTIONS
{
.text : {
. = ALIGN(4);
KEEP(*(.boot2))
_text_vectortable_start = .;
KEEP(*(.vector_table))
_text_vectortable_end = .;
*(.text .text.*)
*(.rodata .rodata*)
} > rom
. = ALIGN(4);
_data_flash = .;
.data : AT (_data_flash)
{
. = ALIGN(4);
_data_start = .;
*(.ramfunc .ramfunc.*);
*(.data .data.*);
. = ALIGN(4);
_data_end = .;
} > ram
.bss (NOLOAD) :
{
. = ALIGN(4);
_bss_start = .;
*(.bss .bss.*)
*(COMMON)
. = ALIGN(4);
_bss_end = .;
} > ram
_stack_start = CONFIG_RAM_START + CONFIG_RAM_SIZE - CONFIG_STACK_SIZE ;
.stack _stack_start (NOLOAD) :
{
. = . + CONFIG_STACK_SIZE;
_stack_end = .;
} > ram
/DISCARD/ : {
// The .init/.fini sections are used by __libc_init_array(), but
// that isn't needed so no need to include them in the binary.
*(.init)
*(.fini)
}
}

92
src/rp2040/serial.c Normal file
View File

@ -0,0 +1,92 @@
// rp2040 serial
//
// Copyright (C) 2021 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <stdint.h> // uint32_t
#include "autoconf.h" // CONFIG_SERIAL
#include "board/armcm_boot.h" // armcm_enable_irq
#include "board/irq.h" // irq_save
#include "board/serial_irq.h" // serial_rx_data
#include "hardware/structs/resets.h" // RESETS_RESET_UART0_BITS
#include "hardware/structs/uart.h" // UART0_BASE
#include "internal.h" // UART0_IRQn
#include "sched.h" // DECL_INIT
#define UARTx uart0_hw
#define UARTx_IRQn UART0_IRQ_IRQn
#define GPIO_Rx 1
#define GPIO_Tx 0
// Write tx bytes to the serial port
static void
kick_tx(void)
{
for (;;) {
if (UARTx->fr & UART_UARTFR_TXFF_BITS) {
// Output fifo full - enable tx irq
UARTx->imsc = (UART_UARTIMSC_RXIM_BITS | UART_UARTIMSC_RTIM_BITS
| UART_UARTIMSC_TXIM_BITS);
break;
}
uint8_t data;
int ret = serial_get_tx_byte(&data);
if (ret) {
// No more data to send - disable tx irq
UARTx->imsc = UART_UARTIMSC_RXIM_BITS | UART_UARTIMSC_RTIM_BITS;
break;
}
UARTx->dr = data;
}
}
void
UARTx_IRQHandler(void)
{
uint32_t mis = UARTx->mis;
if (mis & (UART_UARTMIS_RXMIS_BITS | UART_UARTMIS_RTMIS_BITS)) {
do {
serial_rx_byte(UARTx->dr);
} while (!(UARTx->fr & UART_UARTFR_RXFE_BITS));
} else if (mis & UART_UARTMIS_TXMIS_BITS) {
kick_tx();
}
}
void
serial_enable_tx_irq(void)
{
if (!(UARTx->fr & UART_UARTFR_TXFF_BITS)) {
irqstatus_t flag = irq_save();
kick_tx();
irq_restore(flag);
}
}
void
serial_init(void)
{
enable_pclock(RESETS_RESET_UART0_BITS);
// Setup baud
uint32_t pclk = get_pclock_frequency(RESETS_RESET_UART0_BITS);
uint32_t div = DIV_ROUND_CLOSEST(pclk * 4, CONFIG_SERIAL_BAUD);
UARTx->ibrd = div >> 6;
UARTx->fbrd = div & 0x3f;
// Enable fifo, set 8N1
UARTx->lcr_h = UART_UARTLCR_H_FEN_BITS | UART_UARTLCR_H_WLEN_BITS;
UARTx->ifls = 0;
UARTx->cr = (UART_UARTCR_RXE_BITS | UART_UARTCR_TXE_BITS
| UART_UARTCR_UARTEN_BITS);
// Setup pins
gpio_peripheral(GPIO_Rx, 2, 1);
gpio_peripheral(GPIO_Tx, 2, 0);
// Enable receive irq
armcm_enable_irq(UARTx_IRQHandler, UARTx_IRQn, 0);
UARTx->imsc = UART_UARTIMSC_RXIM_BITS | UART_UARTIMSC_RTIM_BITS;
}
DECL_INIT(serial_init);

70
src/rp2040/timer.c Normal file
View File

@ -0,0 +1,70 @@
// rp2040 timer support
//
// Copyright (C) 2021 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "board/armcm_boot.h" // armcm_enable_irq
#include "board/irq.h" // irq_disable
#include "board/misc.h" // timer_read_time
#include "board/timer_irq.h" // timer_dispatch_many
#include "command.h" // DECL_SHUTDOWN
#include "hardware/structs/resets.h" // RESETS_RESET_UART0_BITS
#include "hardware/structs/timer.h" // RESETS_RESET_UART0_BITS
#include "internal.h" // enable_pclock
#include "sched.h" // DECL_INIT
/****************************************************************
* Low level timer code
****************************************************************/
// Return the current time (in absolute clock ticks).
uint32_t
timer_read_time(void)
{
return timer_hw->timerawl;
}
static inline void
timer_set(uint32_t next)
{
timer_hw->alarm[0] = next;
}
// Activate timer dispatch as soon as possible
void
timer_kick(void)
{
timer_set(timer_read_time() + 50);
}
/****************************************************************
* Setup and irqs
****************************************************************/
// Hardware timer IRQ handler - dispatch software timers
void __aligned(16)
TIMER0_IRQHandler(void)
{
irq_disable();
timer_hw->intr = 1;
uint32_t next = timer_dispatch_many();
timer_set(next);
irq_enable();
}
void
timer_init(void)
{
irq_disable();
enable_pclock(RESETS_RESET_TIMER_BITS);
timer_hw->timelw = 0;
timer_hw->timehw = 0;
armcm_enable_irq(TIMER0_IRQHandler, TIMER_IRQ_0_IRQn, 2);
timer_hw->inte = 1;
timer_kick();
irq_enable();
}
DECL_INIT(timer_init);

View File

@ -0,0 +1,2 @@
# Base config file for rp2040 boards
CONFIG_MACH_RP2040=y