Initial Release
This commit is contained in:
70
src/rp2_common/CMakeLists.txt
Normal file
70
src/rp2_common/CMakeLists.txt
Normal file
@ -0,0 +1,70 @@
|
||||
option(PICO_NO_FLASH "Default binaries to not not use flash")
|
||||
option(PICO_COPY_TO_RAM "Default binaries to Copy code to RAM when booting from flash")
|
||||
|
||||
set(CMAKE_EXECUTABLE_SUFFIX .elf)
|
||||
|
||||
pico_add_subdirectory(hardware_base)
|
||||
pico_add_subdirectory(hardware_claim)
|
||||
# HAL items which expose a public (inline) functions/macro API above the raw hardware
|
||||
pico_add_subdirectory(hardware_adc)
|
||||
pico_add_subdirectory(hardware_clocks)
|
||||
pico_add_subdirectory(hardware_dma)
|
||||
pico_add_subdirectory(hardware_divider)
|
||||
pico_add_subdirectory(hardware_flash)
|
||||
pico_add_subdirectory(hardware_gpio)
|
||||
pico_add_subdirectory(hardware_i2c)
|
||||
pico_add_subdirectory(hardware_interp)
|
||||
pico_add_subdirectory(hardware_irq)
|
||||
pico_add_subdirectory(hardware_pio)
|
||||
pico_add_subdirectory(hardware_pll)
|
||||
pico_add_subdirectory(hardware_pwm)
|
||||
pico_add_subdirectory(hardware_resets)
|
||||
pico_add_subdirectory(hardware_rtc)
|
||||
pico_add_subdirectory(hardware_spi)
|
||||
pico_add_subdirectory(hardware_sync)
|
||||
pico_add_subdirectory(hardware_timer)
|
||||
pico_add_subdirectory(hardware_uart)
|
||||
pico_add_subdirectory(hardware_vreg)
|
||||
pico_add_subdirectory(hardware_watchdog)
|
||||
pico_add_subdirectory(hardware_xosc)
|
||||
|
||||
# Helper functions to connect to data/functions in the bootrom
|
||||
pico_add_subdirectory(pico_bootrom)
|
||||
pico_add_subdirectory(pico_platform)
|
||||
|
||||
if (NOT PICO_BARE_METAL)
|
||||
# NOTE THE ORDERING HERE IS IMPORTANT AS SOME TARGETS CHECK ON EXISTENCE OF OTHER TARGETS
|
||||
pico_add_subdirectory(boot_stage2)
|
||||
|
||||
pico_add_subdirectory(pico_multicore)
|
||||
|
||||
pico_add_subdirectory(pico_bit_ops)
|
||||
pico_add_subdirectory(pico_divider)
|
||||
pico_add_subdirectory(pico_double)
|
||||
pico_add_subdirectory(pico_int64_ops)
|
||||
pico_add_subdirectory(pico_float)
|
||||
pico_add_subdirectory(pico_mem_ops)
|
||||
pico_add_subdirectory(pico_malloc)
|
||||
pico_add_subdirectory(pico_printf)
|
||||
|
||||
pico_add_subdirectory(pico_stdio)
|
||||
pico_add_subdirectory(pico_stdio_semihosting)
|
||||
pico_add_subdirectory(pico_stdio_uart)
|
||||
|
||||
pico_add_subdirectory(tinyusb)
|
||||
pico_add_subdirectory(pico_stdio_usb)
|
||||
|
||||
pico_add_subdirectory(pico_stdlib)
|
||||
|
||||
pico_add_subdirectory(pico_cxx_options)
|
||||
pico_add_subdirectory(pico_standard_link)
|
||||
|
||||
pico_add_subdirectory(pico_fix)
|
||||
|
||||
pico_add_subdirectory(pico_runtime)
|
||||
|
||||
endif()
|
||||
|
||||
set(CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}" PARENT_SCOPE)
|
||||
|
||||
pico_add_doxygen(${CMAKE_CURRENT_LIST_DIR})
|
8
src/rp2_common/README.md
Normal file
8
src/rp2_common/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
This directory contains libraries specifically targeting the RP2040 or possible future related devices. It is selected when
|
||||
`PICO_PLATFORM=rp2040` (the default) is specified for the build
|
||||
|
||||
`hardware_` libraries exist for individual hardware components to provide a simple API
|
||||
providing a thin abstraction hiding the details of accessing the hardware registers directly.
|
||||
|
||||
`pico_` provides higher level functionality you might generally find in say an OS kernel, as well
|
||||
as runtime support familiar to most C programmers.
|
67
src/rp2_common/boot_stage2/CMakeLists.txt
Normal file
67
src/rp2_common/boot_stage2/CMakeLists.txt
Normal file
@ -0,0 +1,67 @@
|
||||
# PICO_CMAKE_CONFIG: PICO_DEFAULT_BOOT_STAGE2_FILE, Default stage2 file to use unless overridden by pico_set_boot_stage2 on the TARGET, type=bool, default=.../boot2_w25q080.S, group=build
|
||||
if (NOT PICO_DEFAULT_BOOT_STAGE2_FILE)
|
||||
set(PICO_DEFAULT_BOOT_STAGE2_FILE "${CMAKE_CURRENT_LIST_DIR}/boot2_w25q080.S")
|
||||
endif()
|
||||
|
||||
set(PICO_DEFAULT_BOOT_STAGE2_FILE "${PICO_DEFAULT_BOOT_STAGE2_FILE}" CACHE STRING "boot_stage2 source file" FORCE)
|
||||
|
||||
if (NOT EXISTS ${PICO_DEFAULT_BOOT_STAGE2_FILE})
|
||||
message(FATAL_ERROR "Specified boot_stage2 source '${PICO_BOOT_STAGE2_FILE}' does not exist.")
|
||||
endif()
|
||||
|
||||
# needed by function below
|
||||
set(PICO_BOOT_STAGE2_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "")
|
||||
|
||||
function(pico_define_boot_stage2 NAME SOURCES)
|
||||
add_executable(${NAME}
|
||||
${SOURCES}
|
||||
)
|
||||
|
||||
# todo bit of an abstraction failure - revisit for Clang support anyway
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||
target_link_options(${NAME} PRIVATE "-nostdlib")
|
||||
else ()
|
||||
target_link_options(${NAME} PRIVATE "--specs=nosys.specs")
|
||||
target_link_options(${NAME} PRIVATE "-nostartfiles")
|
||||
endif ()
|
||||
|
||||
target_link_libraries(${NAME} hardware_regs)
|
||||
target_link_options(${NAME} PRIVATE "LINKER:--script=${PICO_BOOT_STAGE2_DIR}/boot_stage2.ld")
|
||||
set_target_properties(${NAME} PROPERTIES LINK_DEPENDS ${PICO_BOOT_STAGE2_DIR}/boot_stage2.ld)
|
||||
|
||||
pico_add_dis_output(${NAME})
|
||||
pico_add_map_output(${NAME})
|
||||
|
||||
set(ORIGINAL_BIN ${CMAKE_CURRENT_BINARY_DIR}/${NAME}.bin)
|
||||
set(PADDED_CHECKSUMMED_ASM ${CMAKE_CURRENT_BINARY_DIR}/${NAME}_padded_checksummed.S)
|
||||
|
||||
add_custom_target(${NAME}_bin DEPENDS ${ORIGINAL_BIN})
|
||||
add_dependencies(${NAME}_bin ${NAME})
|
||||
|
||||
add_custom_command(OUTPUT ${ORIGINAL_BIN} COMMAND ${CMAKE_OBJCOPY} -Obinary $<TARGET_FILE:${NAME}> ${ORIGINAL_BIN})
|
||||
|
||||
find_package (Python3 REQUIRED COMPONENTS Interpreter)
|
||||
add_custom_command(OUTPUT ${PADDED_CHECKSUMMED_ASM}
|
||||
COMMAND ${Python3_EXECUTABLE} ${PICO_BOOT_STAGE2_DIR}/pad_checksum -s 0xffffffff ${ORIGINAL_BIN} ${PADDED_CHECKSUMMED_ASM}
|
||||
)
|
||||
|
||||
add_custom_target(${NAME}_padded_checksummed_asm DEPENDS ${PADDED_CHECKSUMMED_ASM})
|
||||
add_dependencies(${NAME}_padded_checksummed_asm ${NAME}_bin)
|
||||
|
||||
add_library(${NAME}_library INTERFACE)
|
||||
add_dependencies(${NAME}_library ${NAME}_padded_checksummed_asm)
|
||||
# not strictly (or indeed actually) a link library, but this avoids dependency cycle
|
||||
target_link_libraries(${NAME}_library INTERFACE ${PADDED_CHECKSUMMED_ASM})
|
||||
endfunction()
|
||||
|
||||
macro(pico_set_boot_stage2 TARGET NAME)
|
||||
get_target_property(target_type ${TARGET} TYPE)
|
||||
if ("EXECUTABLE" STREQUAL "${target_type}")
|
||||
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_BOOT_STAGE2 "${NAME}")
|
||||
else()
|
||||
message(FATAL_ERROR "boot stage2 implementation must be set on executable not library")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
pico_define_boot_stage2(bs2_default ${PICO_DEFAULT_BOOT_STAGE2_FILE})
|
||||
|
152
src/rp2_common/boot_stage2/boot2_generic_03h.S
Normal file
152
src/rp2_common/boot_stage2/boot2_generic_03h.S
Normal file
@ -0,0 +1,152 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Second stage boot code
|
||||
// Copyright (c) 2019 Raspberry Pi (Trading) Ltd.
|
||||
//
|
||||
// Device: Anything which responds to 03h serial read command
|
||||
//
|
||||
// Details: * Configure SSI to translate each APB read into a 03h command
|
||||
// * 8 command clocks, 24 address clocks and 32 data clocks
|
||||
// * This enables you to boot from almost anything: you can pretty
|
||||
// much solder a potato to your PCB, or a piece of cheese
|
||||
// * The tradeoff is performance around 3x worse than QSPI XIP
|
||||
//
|
||||
// Building: * This code must be position-independent, and use stack only
|
||||
// * The code will be padded to a size of 256 bytes, including a
|
||||
// 4-byte checksum. Therefore code size cannot exceed 252 bytes.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include "pico/asm_helper.S"
|
||||
#include "hardware/regs/addressmap.h"
|
||||
#include "ssi.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Config section
|
||||
// ----------------------------------------------------------------------------
|
||||
// It should be possible to support most flash devices by modifying this section
|
||||
|
||||
// The serial flash interface will run at clk_sys/PICO_FLASH_SPI_CLKDIV.
|
||||
// This must be a positive, even integer.
|
||||
// The bootrom is very conservative with SPI frequency, but here we should be
|
||||
// as aggressive as possible.
|
||||
#ifndef PICO_FLASH_SPI_CLKDIV
|
||||
#define PICO_FLASH_SPI_CLKDIV 4
|
||||
#endif
|
||||
|
||||
#define CMD_READ 0x03
|
||||
|
||||
// Value is number of address bits divided by 4
|
||||
#define ADDR_L 6
|
||||
|
||||
#define CTRLR0_XIP \
|
||||
(SSI_CTRLR0_SPI_FRF_VALUE_STD << SSI_CTRLR0_SPI_FRF_LSB) | /* Standard 1-bit SPI serial frames */ \
|
||||
(31 << SSI_CTRLR0_DFS_32_LSB) | /* 32 clocks per data frame */ \
|
||||
(SSI_CTRLR0_TMOD_VALUE_EEPROM_READ << SSI_CTRLR0_TMOD_LSB) /* Send instr + addr, receive data */
|
||||
|
||||
#define SPI_CTRLR0_XIP \
|
||||
(CMD_READ << SSI_SPI_CTRLR0_XIP_CMD_LSB) | /* Value of instruction prefix */ \
|
||||
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Total number of address + mode bits */ \
|
||||
(2 << SSI_SPI_CTRLR0_INST_L_LSB) | /* 8 bit command prefix (field value is bits divided by 4) */ \
|
||||
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C1A << SSI_SPI_CTRLR0_TRANS_TYPE_LSB) /* command and address both in serial format */
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Start of 2nd Stage Boot Code
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
.cpu cortex-m0
|
||||
.thumb
|
||||
|
||||
.section .text
|
||||
|
||||
.global _stage2_boot
|
||||
.type _stage2_boot,%function
|
||||
.thumb_func
|
||||
_stage2_boot:
|
||||
push {lr}
|
||||
|
||||
ldr r3, =XIP_SSI_BASE // Use as base address where possible
|
||||
|
||||
// Disable SSI to allow further config
|
||||
mov r1, #0
|
||||
str r1, [r3, #SSI_SSIENR_OFFSET]
|
||||
|
||||
// Set baud rate
|
||||
mov r1, #PICO_FLASH_SPI_CLKDIV
|
||||
str r1, [r3, #SSI_BAUDR_OFFSET]
|
||||
|
||||
ldr r1, =(CTRLR0_XIP)
|
||||
str r1, [r3, #SSI_CTRLR0_OFFSET]
|
||||
|
||||
ldr r1, =(SPI_CTRLR0_XIP)
|
||||
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET)
|
||||
str r1, [r0]
|
||||
|
||||
// NDF=0 (single 32b read)
|
||||
mov r1, #0x0
|
||||
str r1, [r3, #SSI_CTRLR1_OFFSET]
|
||||
|
||||
// Re-enable SSI
|
||||
mov r1, #1
|
||||
str r1, [r3, #SSI_SSIENR_OFFSET]
|
||||
|
||||
// We are now in XIP mode. Any bus accesses to the XIP address window will be
|
||||
// translated by the SSI into 03h read commands to the external flash (if cache is missed),
|
||||
// and the data will be returned to the bus.
|
||||
|
||||
soft_reset:
|
||||
// Jump to exit point provided in lr. Bootrom will pass null, in which
|
||||
// case we use default exit target at a 256 byte offset into XIP region
|
||||
// (immediately after this second stage's flash location)
|
||||
pop {r0}
|
||||
cmp r0, #0
|
||||
bne 1f
|
||||
ldr r0, =(XIP_BASE + 0x101)
|
||||
1:
|
||||
bx r0
|
||||
|
||||
// Common functions
|
||||
|
||||
wait_ssi_ready:
|
||||
push {r0, r1, lr}
|
||||
|
||||
// Command is complete when there is nothing left to send
|
||||
// (TX FIFO empty) and SSI is no longer busy (CSn deasserted)
|
||||
1:
|
||||
ldr r1, [r3, #SSI_SR_OFFSET]
|
||||
mov r0, #SSI_SR_TFE_BITS
|
||||
tst r1, r0
|
||||
beq 1b
|
||||
mov r0, #SSI_SR_BUSY_BITS
|
||||
tst r1, r0
|
||||
bne 1b
|
||||
|
||||
pop {r0, r1, pc}
|
||||
|
||||
#ifdef PROGRAM_STATUS_REG
|
||||
// Pass status read cmd into r0.
|
||||
// Returns status value in r0.
|
||||
.global read_flash_sreg
|
||||
.type read_flash_sreg,%function
|
||||
.thumb_func
|
||||
read_flash_sreg:
|
||||
push {r1, lr}
|
||||
str r0, [r3, #SSI_DR0_OFFSET]
|
||||
// Dummy byte:
|
||||
str r0, [r3, #SSI_DR0_OFFSET]
|
||||
|
||||
bl wait_ssi_ready
|
||||
// Discard first byte and combine the next two
|
||||
ldr r0, [r3, #SSI_DR0_OFFSET]
|
||||
ldr r0, [r3, #SSI_DR0_OFFSET]
|
||||
|
||||
pop {r1, pc}
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Literal Table
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
.global literals
|
||||
literals:
|
||||
.ltorg
|
||||
|
||||
.end
|
299
src/rp2_common/boot_stage2/boot2_is25lp080.S
Normal file
299
src/rp2_common/boot_stage2/boot2_is25lp080.S
Normal file
@ -0,0 +1,299 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Second stage boot code
|
||||
// Copyright (c) 2019 Raspberry Pi (Trading) Ltd.
|
||||
//
|
||||
// Device: ISSI IS25LP080D
|
||||
// Based on W25Q080 code: main difference is the QE bit being in
|
||||
// SR1 instead of SR2.
|
||||
//
|
||||
// Description: Configures IS25LP080D to run in Quad I/O continuous read XIP mode
|
||||
//
|
||||
// Details: * Check status register to determine if QSPI mode is enabled,
|
||||
// and perform an SR programming cycle if necessary.
|
||||
// * Use SSI to perform a dummy 0xEB read command, with the mode
|
||||
// continuation bits set, so that the flash will not require
|
||||
// 0xEB instruction prefix on subsequent reads.
|
||||
// * Configure SSI to write address, mode bits, but no instruction.
|
||||
// SSI + flash are now jointly in a state where continuous reads
|
||||
// can take place.
|
||||
// * Set VTOR = 0x10000100 (user vector table immediately after
|
||||
// this boot2 image).
|
||||
// * Read stack pointer (MSP) and reset vector from the flash
|
||||
// vector table; set SP and jump, as though the processor had
|
||||
// booted directly from flash.
|
||||
//
|
||||
// Building: * This code must be linked to run at 0x20027f00
|
||||
// * The code will be padded to a size of 256 bytes, including a
|
||||
// 4-byte checksum. Therefore code size cannot exceed 252 bytes.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include "pico/asm_helper.S"
|
||||
#include "hardware/regs/addressmap.h"
|
||||
#include "ssi.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Config section
|
||||
// ----------------------------------------------------------------------------
|
||||
// It should be possible to support most flash devices by modifying this section
|
||||
|
||||
// The serial flash interface will run at clk_sys/PICO_FLASH_SPI_CLKDIV.
|
||||
// This must be a positive, even integer.
|
||||
// The bootrom is very conservative with SPI frequency, but here we should be
|
||||
// as aggressive as possible.
|
||||
#ifndef PICO_FLASH_SPI_CLKDIV
|
||||
#define PICO_FLASH_SPI_CLKDIV 4
|
||||
#endif
|
||||
|
||||
|
||||
// Define interface width: single/dual/quad IO
|
||||
#define FRAME_FORMAT SSI_CTRLR0_SPI_FRF_VALUE_QUAD
|
||||
|
||||
// For W25Q080 this is the "Read data fast quad IO" instruction:
|
||||
#define CMD_READ 0xeb
|
||||
|
||||
// "Mode bits" are 8 special bits sent immediately after
|
||||
// the address bits in a "Read Data Fast Quad I/O" command sequence.
|
||||
// On W25Q080, the four LSBs are don't care, and if MSBs == 0xa, the
|
||||
// next read does not require the 0xeb instruction prefix.
|
||||
#define MODE_CONTINUOUS_READ 0xa0
|
||||
|
||||
// The number of address + mode bits, divided by 4 (always 4, not function of
|
||||
// interface width).
|
||||
#define ADDR_L 8
|
||||
|
||||
// How many clocks of Hi-Z following the mode bits. For W25Q080, 4 dummy cycles
|
||||
// are required.
|
||||
#define WAIT_CYCLES 4
|
||||
|
||||
// If defined, we will read status reg, compare to SREG_DATA, and overwrite
|
||||
// with our value if the SR doesn't match.
|
||||
// This isn't great because it will remove block protections.
|
||||
// A better solution is to use a volatile SR write if your device supports it.
|
||||
#define PROGRAM_STATUS_REG
|
||||
|
||||
#define CMD_WRITE_ENABLE 0x06
|
||||
#define CMD_READ_STATUS 0x05
|
||||
#define CMD_WRITE_STATUS 0x01
|
||||
#define SREG_DATA 0x40 // Enable quad-SPI mode
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Start of 2nd Stage Boot Code
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
.cpu cortex-m0
|
||||
.thumb
|
||||
|
||||
.section .text
|
||||
|
||||
.global _stage2_boot
|
||||
.type _stage2_boot,%function
|
||||
.thumb_func
|
||||
_stage2_boot:
|
||||
|
||||
ldr r5, =XIP_SSI_BASE // Use as base address where possible
|
||||
|
||||
// Disable SSI to allow further config
|
||||
mov r1, #0
|
||||
str r1, [r5, #SSI_SSIENR_OFFSET]
|
||||
|
||||
// Set baud rate
|
||||
mov r1, #PICO_FLASH_SPI_CLKDIV
|
||||
str r1, [r5, #SSI_BAUDR_OFFSET]
|
||||
|
||||
// On QSPI parts we usually need a 01h SR-write command to enable QSPI mode
|
||||
// (i.e. turn WPn and HOLDn into IO2/IO3)
|
||||
#ifdef PROGRAM_STATUS_REG
|
||||
program_sregs:
|
||||
#define CTRL0_SPI_TXRX \
|
||||
(7 << SSI_CTRLR0_DFS_32_LSB) | /* 8 bits per data frame */ \
|
||||
(SSI_CTRLR0_TMOD_VALUE_TX_AND_RX << SSI_CTRLR0_TMOD_LSB)
|
||||
|
||||
ldr r1, =(CTRL0_SPI_TXRX)
|
||||
str r1, [r5, #SSI_CTRLR0_OFFSET]
|
||||
|
||||
// Enable SSI and select slave 0
|
||||
mov r1, #1
|
||||
str r1, [r5, #SSI_SSIENR_OFFSET]
|
||||
|
||||
// Check whether SR needs updating
|
||||
ldr r0, =CMD_READ_STATUS
|
||||
bl read_flash_sreg
|
||||
ldr r2, =SREG_DATA
|
||||
cmp r0, r2
|
||||
beq skip_sreg_programming
|
||||
|
||||
// Send write enable command
|
||||
mov r1, #CMD_WRITE_ENABLE
|
||||
str r1, [r5, #SSI_DR0_OFFSET]
|
||||
|
||||
// Poll for completion and discard RX
|
||||
bl wait_ssi_ready
|
||||
ldr r1, [r5, #SSI_DR0_OFFSET]
|
||||
|
||||
// Send status write command followed by data bytes
|
||||
mov r1, #CMD_WRITE_STATUS
|
||||
str r1, [r5, #SSI_DR0_OFFSET]
|
||||
mov r0, #0
|
||||
str r2, [r5, #SSI_DR0_OFFSET]
|
||||
|
||||
bl wait_ssi_ready
|
||||
ldr r1, [r5, #SSI_DR0_OFFSET]
|
||||
ldr r1, [r5, #SSI_DR0_OFFSET]
|
||||
|
||||
// Poll status register for write completion
|
||||
1:
|
||||
ldr r0, =CMD_READ_STATUS
|
||||
bl read_flash_sreg
|
||||
mov r1, #1
|
||||
tst r0, r1
|
||||
bne 1b
|
||||
|
||||
skip_sreg_programming:
|
||||
|
||||
// Send a 0xA3 high-performance-mode instruction
|
||||
// ldr r1, =0xa3
|
||||
// str r1, [r5, #SSI_DR0_OFFSET]
|
||||
// bl wait_ssi_ready
|
||||
|
||||
// Disable SSI again so that it can be reconfigured
|
||||
mov r1, #0
|
||||
str r1, [r5, #SSI_SSIENR_OFFSET]
|
||||
#endif
|
||||
|
||||
|
||||
// First we need to send the initial command to get us in to Fast Read Quad I/O
|
||||
// mode. As this transaction requires a command, we can't send it in XIP mode.
|
||||
// To enter Continuous Read mode as well we need to append 4'b0010 to the address
|
||||
// bits and then add a further 4 don't care bits. We will construct this by
|
||||
// specifying a 28-bit address, with the least significant bits being 4'b0010.
|
||||
// This is just a dummy transaction so we'll perform a read from address zero
|
||||
// and then discard what comes back. All we really care about is that at the
|
||||
// end of the transaction, the flash device is in Continuous Read mode
|
||||
// and from then on will only expect to receive addresses.
|
||||
dummy_read:
|
||||
#define CTRLR0_ENTER_XIP \
|
||||
(FRAME_FORMAT /* Quad I/O mode */ \
|
||||
<< SSI_CTRLR0_SPI_FRF_LSB) | \
|
||||
(31 << SSI_CTRLR0_DFS_32_LSB) | /* 32 data bits */ \
|
||||
(SSI_CTRLR0_TMOD_VALUE_EEPROM_READ /* Send INST/ADDR, Receive Data */ \
|
||||
<< SSI_CTRLR0_TMOD_LSB)
|
||||
|
||||
ldr r1, =(CTRLR0_ENTER_XIP)
|
||||
str r1, [r5, #SSI_CTRLR0_OFFSET]
|
||||
|
||||
mov r1, #0x0 // NDF=0 (single 32b read)
|
||||
str r1, [r5, #SSI_CTRLR1_OFFSET]
|
||||
|
||||
#define SPI_CTRLR0_ENTER_XIP \
|
||||
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Address + mode bits */ \
|
||||
(WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \
|
||||
(SSI_SPI_CTRLR0_INST_L_VALUE_8B \
|
||||
<< SSI_SPI_CTRLR0_INST_L_LSB) | /* 8-bit instruction */ \
|
||||
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A /* Send Command in serial mode then address in Quad I/O mode */ \
|
||||
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
|
||||
|
||||
ldr r1, =(SPI_CTRLR0_ENTER_XIP)
|
||||
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET) // SPI_CTRL0 Register
|
||||
str r1, [r0]
|
||||
|
||||
mov r1, #1 // Re-enable SSI
|
||||
str r1, [r5, #SSI_SSIENR_OFFSET]
|
||||
|
||||
mov r1, #CMD_READ
|
||||
str r1, [r5, #SSI_DR0_OFFSET] // Push SPI command into TX FIFO
|
||||
mov r1, #MODE_CONTINUOUS_READ // 32-bit: 24 address bits (we don't care, so 0) and M[7:4]=1010
|
||||
str r1, [r5, #SSI_DR0_OFFSET] // Push Address into TX FIFO - this will trigger the transaction
|
||||
|
||||
// Poll for completion
|
||||
bl wait_ssi_ready
|
||||
|
||||
// At this point CN# will be deasserted and the SPI clock will not be running.
|
||||
// The Winbond WX25X10CL device will be in continuous read, dual I/O mode and
|
||||
// only expecting address bits after the next CN# assertion. So long as we
|
||||
// send 4'b0010 (and 4 more dummy HiZ bits) after every subsequent 24b address
|
||||
// then the Winbond device will remain in continuous read mode. This is the
|
||||
// ideal mode for Execute-In-Place.
|
||||
// (If we want to exit continuous read mode then we will need to switch back
|
||||
// to APM mode and generate a 28-bit address phase with the extra nibble set
|
||||
// to 4'b0000).
|
||||
|
||||
mov r1, #0
|
||||
str r1, [r5, #SSI_SSIENR_OFFSET] // Disable SSI (and clear FIFO) to allow further config
|
||||
|
||||
// Note that the INST_L field is used to select what XIP data gets pushed into
|
||||
// the TX FIFO:
|
||||
// INST_L_0_BITS {ADDR[23:0],XIP_CMD[7:0]} Load "mode bits" into XIP_CMD
|
||||
// Anything else {XIP_CMD[7:0],ADDR[23:0]} Load SPI command into XIP_CMD
|
||||
configure_ssi:
|
||||
#define SPI_CTRLR0_XIP \
|
||||
(MODE_CONTINUOUS_READ /* Mode bits to keep flash in continuous read mode */ \
|
||||
<< SSI_SPI_CTRLR0_XIP_CMD_LSB) | \
|
||||
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Total number of address + mode bits */ \
|
||||
(WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \
|
||||
(SSI_SPI_CTRLR0_INST_L_VALUE_NONE /* Do not send a command, instead send XIP_CMD as mode bits after address */ \
|
||||
<< SSI_SPI_CTRLR0_INST_L_LSB) | \
|
||||
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A /* Send Address in Quad I/O mode (and Command but that is zero bits long) */ \
|
||||
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
|
||||
|
||||
ldr r1, =(SPI_CTRLR0_XIP)
|
||||
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET)
|
||||
str r1, [r0]
|
||||
|
||||
mov r1, #1
|
||||
str r1, [r5, #SSI_SSIENR_OFFSET] // Re-enable SSI
|
||||
|
||||
// We are now in XIP mode, with all transactions using Dual I/O and only
|
||||
// needing to send 24-bit addresses (plus mode bits) for each read transaction.
|
||||
|
||||
soft_reset:
|
||||
// Just jump to start of image after this 256-byte boot stage2 (with thumb bit set)
|
||||
ldr r0, =(XIP_BASE + 256 + 1)
|
||||
bx r0
|
||||
|
||||
// Common functions
|
||||
|
||||
wait_ssi_ready:
|
||||
push {r0, r1, lr}
|
||||
|
||||
// Command is complete when there is nothing left to send
|
||||
// (TX FIFO empty) and SSI is no longer busy (CSn deasserted)
|
||||
1:
|
||||
ldr r1, [r5, #SSI_SR_OFFSET]
|
||||
mov r0, #SSI_SR_TFE_BITS
|
||||
tst r1, r0
|
||||
beq 1b
|
||||
mov r0, #SSI_SR_BUSY_BITS
|
||||
tst r1, r0
|
||||
bne 1b
|
||||
|
||||
pop {r0, r1, pc}
|
||||
|
||||
#ifdef PROGRAM_STATUS_REG
|
||||
// Pass status read cmd into r0.
|
||||
// Returns status value in r0.
|
||||
.global read_flash_sreg
|
||||
.type read_flash_sreg,%function
|
||||
.thumb_func
|
||||
read_flash_sreg:
|
||||
push {r1, lr}
|
||||
str r0, [r5, #SSI_DR0_OFFSET]
|
||||
// Dummy byte:
|
||||
str r0, [r5, #SSI_DR0_OFFSET]
|
||||
|
||||
bl wait_ssi_ready
|
||||
// Discard first byte and combine the next two
|
||||
ldr r0, [r5, #SSI_DR0_OFFSET]
|
||||
ldr r0, [r5, #SSI_DR0_OFFSET]
|
||||
|
||||
pop {r1, pc}
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Literal Table
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
.global literals
|
||||
literals:
|
||||
.ltorg
|
||||
|
||||
.end
|
47
src/rp2_common/boot_stage2/boot2_usb_blinky.S
Normal file
47
src/rp2_common/boot_stage2/boot2_usb_blinky.S
Normal file
@ -0,0 +1,47 @@
|
||||
// Stub second stage which calls into USB bootcode, with parameters.
|
||||
// USB boot takes two parameters:
|
||||
// - A GPIO mask for activity LED -- if mask is 0, don't touch GPIOs at all
|
||||
// - A mask of interfaces to disable. Bit 0 disables MSC, bit 1 disables PICOBoot
|
||||
// The bootrom passes 0 for both of these parameters, but user code (or this
|
||||
// second stage) can pass anything.
|
||||
|
||||
#define USB_BOOT_MSD_AND_PICOBOOT 0x0
|
||||
#define USB_BOOT_MSD_ONLY 0x2
|
||||
#define USB_BOOT_PICOBOOT_ONLY 0x1
|
||||
|
||||
// Config
|
||||
#define ACTIVITY_LED 0
|
||||
#define BOOT_MODE USB_BOOT_MSD_AND_PICOBOOT
|
||||
|
||||
.cpu cortex-m0
|
||||
.thumb
|
||||
|
||||
.section .text
|
||||
|
||||
.global _stage2_boot
|
||||
.type _stage2_boot,%function
|
||||
|
||||
.thumb_func
|
||||
_stage2_boot:
|
||||
mov r7, #0x14 // Pointer to _well_known pointer table in ROM
|
||||
ldrh r0, [r7, #0] // Offset 0 is 16 bit pointer to function table
|
||||
ldrh r7, [r7, #4] // Offset 4 is 16 bit pointer to table lookup routine
|
||||
ldr r1, =('U' | ('B' << 8)) // Symbol for USB Boot
|
||||
blx r7
|
||||
cmp r0, #0
|
||||
beq dead
|
||||
|
||||
mov r7, r0
|
||||
ldr r0, =(1u << ACTIVITY_LED) // Mask of which GPIO (or GPIOs) to use
|
||||
mov r1, #BOOT_MODE
|
||||
blx r7
|
||||
|
||||
dead:
|
||||
wfi
|
||||
b dead
|
||||
|
||||
.global literals
|
||||
literals:
|
||||
.ltorg
|
||||
|
||||
.end
|
330
src/rp2_common/boot_stage2/boot2_w25q080.S
Normal file
330
src/rp2_common/boot_stage2/boot2_w25q080.S
Normal file
@ -0,0 +1,330 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Second stage boot code
|
||||
// Copyright (c) 2019 Raspberry Pi (Trading) Ltd.
|
||||
//
|
||||
// Device: Winbond W25Q080
|
||||
// Also supports W25Q16JV (which has some different SR instructions)
|
||||
// Also supports AT25SF081
|
||||
// Also supports S25FL132K0
|
||||
//
|
||||
// Description: Configures W25Q080 to run in Quad I/O continuous read XIP mode
|
||||
//
|
||||
// Details: * Check status register 2 to determine if QSPI mode is enabled,
|
||||
// and perform an SR2 programming cycle if necessary.
|
||||
// * Use SSI to perform a dummy 0xEB read command, with the mode
|
||||
// continuation bits set, so that the flash will not require
|
||||
// 0xEB instruction prefix on subsequent reads.
|
||||
// * Configure SSI to write address, mode bits, but no instruction.
|
||||
// SSI + flash are now jointly in a state where continuous reads
|
||||
// can take place.
|
||||
// * Jump to exit pointer passed in via lr. Bootrom passes null,
|
||||
// in which case this code uses a default 256 byte flash offset
|
||||
//
|
||||
// Building: * This code must be position-independent, and use stack only
|
||||
// * The code will be padded to a size of 256 bytes, including a
|
||||
// 4-byte checksum. Therefore code size cannot exceed 252 bytes.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include "pico/asm_helper.S"
|
||||
#include "hardware/regs/addressmap.h"
|
||||
#include "hardware/regs/ssi.h"
|
||||
#include "hardware/regs/pads_qspi.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Config section
|
||||
// ----------------------------------------------------------------------------
|
||||
// It should be possible to support most flash devices by modifying this section
|
||||
|
||||
// The serial flash interface will run at clk_sys/PICO_FLASH_SPI_CLKDIV.
|
||||
// This must be a positive, even integer.
|
||||
// The bootrom is very conservative with SPI frequency, but here we should be
|
||||
// as aggressive as possible.
|
||||
|
||||
#ifndef PICO_FLASH_SPI_CLKDIV
|
||||
#define PICO_FLASH_SPI_CLKDIV 4
|
||||
#endif
|
||||
#if PICO_FLASH_SPI_CLKDIV & 1
|
||||
#error PICO_FLASH_SPI_CLKDIV must be even
|
||||
#endif
|
||||
|
||||
// Define interface width: single/dual/quad IO
|
||||
#define FRAME_FORMAT SSI_CTRLR0_SPI_FRF_VALUE_QUAD
|
||||
|
||||
// For W25Q080 this is the "Read data fast quad IO" instruction:
|
||||
#define CMD_READ 0xeb
|
||||
|
||||
// "Mode bits" are 8 special bits sent immediately after
|
||||
// the address bits in a "Read Data Fast Quad I/O" command sequence.
|
||||
// On W25Q080, the four LSBs are don't care, and if MSBs == 0xa, the
|
||||
// next read does not require the 0xeb instruction prefix.
|
||||
#define MODE_CONTINUOUS_READ 0xa0
|
||||
|
||||
// The number of address + mode bits, divided by 4 (always 4, not function of
|
||||
// interface width).
|
||||
#define ADDR_L 8
|
||||
|
||||
// How many clocks of Hi-Z following the mode bits. For W25Q080, 4 dummy cycles
|
||||
// are required.
|
||||
#define WAIT_CYCLES 4
|
||||
|
||||
// If defined, we will read status reg, compare to SREG_DATA, and overwrite
|
||||
// with our value if the SR doesn't match.
|
||||
// We do a two-byte write to SR1 (01h cmd) rather than a one-byte write to
|
||||
// SR2 (31h cmd) as the latter command isn't supported by WX25Q080.
|
||||
// This isn't great because it will remove block protections.
|
||||
// A better solution is to use a volatile SR write if your device supports it.
|
||||
#define PROGRAM_STATUS_REG
|
||||
|
||||
#define CMD_WRITE_ENABLE 0x06
|
||||
#define CMD_READ_STATUS 0x05
|
||||
#define CMD_READ_STATUS2 0x35
|
||||
#define CMD_WRITE_STATUS 0x01
|
||||
#define SREG_DATA 0x02 // Enable quad-SPI mode
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Start of 2nd Stage Boot Code
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
.syntax unified
|
||||
.cpu cortex-m0plus
|
||||
.thumb
|
||||
|
||||
.section .text
|
||||
|
||||
// The exit point is passed in lr. If entered from bootrom, this will be the
|
||||
// flash address immediately following this second stage (0x10000100).
|
||||
// Otherwise it will be a return address -- second stage being called as a
|
||||
// function by user code, after copying out of XIP region. r3 holds SSI base,
|
||||
// r0...2 used as temporaries. Other GPRs not used.
|
||||
.global _stage2_boot
|
||||
.type _stage2_boot,%function
|
||||
.thumb_func
|
||||
_stage2_boot:
|
||||
push {lr}
|
||||
|
||||
// Set pad configuration:
|
||||
// - SCLK 8mA drive, no slew limiting
|
||||
// - SDx disable input Schmitt to reduce delay
|
||||
|
||||
ldr r3, =PADS_QSPI_BASE
|
||||
movs r0, #(2 << PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_LSB | PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_BITS)
|
||||
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SCLK_OFFSET]
|
||||
ldr r0, [r3, #PADS_QSPI_GPIO_QSPI_SD0_OFFSET]
|
||||
movs r1, #PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_BITS
|
||||
bics r0, r1
|
||||
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD0_OFFSET]
|
||||
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD1_OFFSET]
|
||||
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD2_OFFSET]
|
||||
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD3_OFFSET]
|
||||
|
||||
ldr r3, =XIP_SSI_BASE
|
||||
|
||||
// Disable SSI to allow further config
|
||||
movs r1, #0
|
||||
str r1, [r3, #SSI_SSIENR_OFFSET]
|
||||
|
||||
// Set baud rate
|
||||
movs r1, #PICO_FLASH_SPI_CLKDIV
|
||||
str r1, [r3, #SSI_BAUDR_OFFSET]
|
||||
|
||||
// Set 1-cycle sample delay. If PICO_FLASH_SPI_CLKDIV == 2 then this means,
|
||||
// if the flash launches data on SCLK posedge, we capture it at the time that
|
||||
// the next SCLK posedge is launched. This is shortly before that posedge
|
||||
// arrives at the flash, so data hold time should be ok. For
|
||||
// PICO_FLASH_SPI_CLKDIV > 2 this pretty much has no effect.
|
||||
|
||||
movs r1, #1
|
||||
movs r2, #SSI_RX_SAMPLE_DLY_OFFSET // == 0xf0 so need 8 bits of offset significance
|
||||
str r1, [r3, r2]
|
||||
|
||||
|
||||
// On QSPI parts we usually need a 01h SR-write command to enable QSPI mode
|
||||
// (i.e. turn WPn and HOLDn into IO2/IO3)
|
||||
#ifdef PROGRAM_STATUS_REG
|
||||
program_sregs:
|
||||
#define CTRL0_SPI_TXRX \
|
||||
(7 << SSI_CTRLR0_DFS_32_LSB) | /* 8 bits per data frame */ \
|
||||
(SSI_CTRLR0_TMOD_VALUE_TX_AND_RX << SSI_CTRLR0_TMOD_LSB)
|
||||
|
||||
ldr r1, =(CTRL0_SPI_TXRX)
|
||||
str r1, [r3, #SSI_CTRLR0_OFFSET]
|
||||
|
||||
// Enable SSI and select slave 0
|
||||
movs r1, #1
|
||||
str r1, [r3, #SSI_SSIENR_OFFSET]
|
||||
|
||||
// Check whether SR needs updating
|
||||
movs r0, #CMD_READ_STATUS2
|
||||
bl read_flash_sreg
|
||||
movs r2, #SREG_DATA
|
||||
cmp r0, r2
|
||||
beq skip_sreg_programming
|
||||
|
||||
// Send write enable command
|
||||
movs r1, #CMD_WRITE_ENABLE
|
||||
str r1, [r3, #SSI_DR0_OFFSET]
|
||||
|
||||
// Poll for completion and discard RX
|
||||
bl wait_ssi_ready
|
||||
ldr r1, [r3, #SSI_DR0_OFFSET]
|
||||
|
||||
// Send status write command followed by data bytes
|
||||
movs r1, #CMD_WRITE_STATUS
|
||||
str r1, [r3, #SSI_DR0_OFFSET]
|
||||
movs r0, #0
|
||||
str r0, [r3, #SSI_DR0_OFFSET]
|
||||
str r2, [r3, #SSI_DR0_OFFSET]
|
||||
|
||||
bl wait_ssi_ready
|
||||
ldr r1, [r3, #SSI_DR0_OFFSET]
|
||||
ldr r1, [r3, #SSI_DR0_OFFSET]
|
||||
ldr r1, [r3, #SSI_DR0_OFFSET]
|
||||
|
||||
// Poll status register for write completion
|
||||
1:
|
||||
movs r0, #CMD_READ_STATUS
|
||||
bl read_flash_sreg
|
||||
movs r1, #1
|
||||
tst r0, r1
|
||||
bne 1b
|
||||
|
||||
skip_sreg_programming:
|
||||
|
||||
// Disable SSI again so that it can be reconfigured
|
||||
movs r1, #0
|
||||
str r1, [r3, #SSI_SSIENR_OFFSET]
|
||||
#endif
|
||||
|
||||
// Currently the flash expects an 8 bit serial command prefix on every
|
||||
// transfer, which is a waste of cycles. Perform a dummy Fast Read Quad I/O
|
||||
// command, with mode bits set such that the flash will not expect a serial
|
||||
// command prefix on *subsequent* transfers. We don't care about the results
|
||||
// of the read, the important part is the mode bits.
|
||||
|
||||
dummy_read:
|
||||
#define CTRLR0_ENTER_XIP \
|
||||
(FRAME_FORMAT /* Quad I/O mode */ \
|
||||
<< SSI_CTRLR0_SPI_FRF_LSB) | \
|
||||
(31 << SSI_CTRLR0_DFS_32_LSB) | /* 32 data bits */ \
|
||||
(SSI_CTRLR0_TMOD_VALUE_EEPROM_READ /* Send INST/ADDR, Receive Data */ \
|
||||
<< SSI_CTRLR0_TMOD_LSB)
|
||||
|
||||
ldr r1, =(CTRLR0_ENTER_XIP)
|
||||
str r1, [r3, #SSI_CTRLR0_OFFSET]
|
||||
|
||||
movs r1, #0x0 // NDF=0 (single 32b read)
|
||||
str r1, [r3, #SSI_CTRLR1_OFFSET]
|
||||
|
||||
#define SPI_CTRLR0_ENTER_XIP \
|
||||
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Address + mode bits */ \
|
||||
(WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \
|
||||
(SSI_SPI_CTRLR0_INST_L_VALUE_8B \
|
||||
<< SSI_SPI_CTRLR0_INST_L_LSB) | /* 8-bit instruction */ \
|
||||
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A /* Send Command in serial mode then address in Quad I/O mode */ \
|
||||
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
|
||||
|
||||
ldr r1, =(SPI_CTRLR0_ENTER_XIP)
|
||||
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET) // SPI_CTRL0 Register
|
||||
str r1, [r0]
|
||||
|
||||
movs r1, #1 // Re-enable SSI
|
||||
str r1, [r3, #SSI_SSIENR_OFFSET]
|
||||
|
||||
movs r1, #CMD_READ
|
||||
str r1, [r3, #SSI_DR0_OFFSET] // Push SPI command into TX FIFO
|
||||
movs r1, #MODE_CONTINUOUS_READ // 32-bit: 24 address bits (we don't care, so 0) and M[7:4]=1010
|
||||
str r1, [r3, #SSI_DR0_OFFSET] // Push Address into TX FIFO - this will trigger the transaction
|
||||
|
||||
// Poll for completion
|
||||
bl wait_ssi_ready
|
||||
|
||||
// The flash is in a state where we can blast addresses in parallel, and get
|
||||
// parallel data back. Now configure the SSI to translate XIP bus accesses
|
||||
// into QSPI transfers of this form.
|
||||
|
||||
movs r1, #0
|
||||
str r1, [r3, #SSI_SSIENR_OFFSET] // Disable SSI (and clear FIFO) to allow further config
|
||||
|
||||
// Note that the INST_L field is used to select what XIP data gets pushed into
|
||||
// the TX FIFO:
|
||||
// INST_L_0_BITS {ADDR[23:0],XIP_CMD[7:0]} Load "mode bits" into XIP_CMD
|
||||
// Anything else {XIP_CMD[7:0],ADDR[23:0]} Load SPI command into XIP_CMD
|
||||
configure_ssi:
|
||||
#define SPI_CTRLR0_XIP \
|
||||
(MODE_CONTINUOUS_READ /* Mode bits to keep flash in continuous read mode */ \
|
||||
<< SSI_SPI_CTRLR0_XIP_CMD_LSB) | \
|
||||
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Total number of address + mode bits */ \
|
||||
(WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \
|
||||
(SSI_SPI_CTRLR0_INST_L_VALUE_NONE /* Do not send a command, instead send XIP_CMD as mode bits after address */ \
|
||||
<< SSI_SPI_CTRLR0_INST_L_LSB) | \
|
||||
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A /* Send Address in Quad I/O mode (and Command but that is zero bits long) */ \
|
||||
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
|
||||
|
||||
ldr r1, =(SPI_CTRLR0_XIP)
|
||||
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET)
|
||||
str r1, [r0]
|
||||
|
||||
movs r1, #1
|
||||
str r1, [r3, #SSI_SSIENR_OFFSET] // Re-enable SSI
|
||||
|
||||
// Bus accesses to the XIP window will now be transparently serviced by the
|
||||
// external flash on cache miss. We are ready to run code from flash.
|
||||
|
||||
soft_reset:
|
||||
// Jump to exit point provided in lr. Bootrom will pass null, in which
|
||||
// case we use default exit target at a 256 byte offset into XIP region
|
||||
// (immediately after this second stage's flash location)
|
||||
pop {r0}
|
||||
cmp r0, #0
|
||||
bne 1f
|
||||
ldr r0, =(XIP_BASE + 0x101)
|
||||
1:
|
||||
bx r0
|
||||
|
||||
// Common functions
|
||||
|
||||
wait_ssi_ready:
|
||||
push {r0, r1, lr}
|
||||
|
||||
// Command is complete when there is nothing left to send
|
||||
// (TX FIFO empty) and SSI is no longer busy (CSn deasserted)
|
||||
1:
|
||||
ldr r1, [r3, #SSI_SR_OFFSET]
|
||||
movs r0, #SSI_SR_TFE_BITS
|
||||
tst r1, r0
|
||||
beq 1b
|
||||
movs r0, #SSI_SR_BUSY_BITS
|
||||
tst r1, r0
|
||||
bne 1b
|
||||
|
||||
pop {r0, r1, pc}
|
||||
|
||||
#ifdef PROGRAM_STATUS_REG
|
||||
// Pass status read cmd into r0.
|
||||
// Returns status value in r0.
|
||||
.global read_flash_sreg
|
||||
.type read_flash_sreg,%function
|
||||
.thumb_func
|
||||
read_flash_sreg:
|
||||
push {r1, lr}
|
||||
str r0, [r3, #SSI_DR0_OFFSET]
|
||||
// Dummy byte:
|
||||
str r0, [r3, #SSI_DR0_OFFSET]
|
||||
|
||||
bl wait_ssi_ready
|
||||
// Discard first byte and combine the next two
|
||||
ldr r0, [r3, #SSI_DR0_OFFSET]
|
||||
ldr r0, [r3, #SSI_DR0_OFFSET]
|
||||
|
||||
pop {r1, pc}
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Literal Table
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
.global literals
|
||||
literals:
|
||||
.ltorg
|
||||
|
||||
.end
|
219
src/rp2_common/boot_stage2/boot2_w25x10cl.S
Normal file
219
src/rp2_common/boot_stage2/boot2_w25x10cl.S
Normal file
@ -0,0 +1,219 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// .d8888b. 888 .d8888b. 888
|
||||
// d88P Y88b 888 d88P Y88b 888
|
||||
// 888 888 Y88b. 888
|
||||
// .d88P 88888b. .d88888 "Y888b. 888888 8888b. .d88b. .d88b.
|
||||
// .od888P" 888 "88b d88" 888 "Y88b. 888 "88b d88P"88b d8P Y8b
|
||||
// d88P" 888 888 888 888 "888 888 .d888888 888 888 88888888
|
||||
// 888" 888 888 Y88b 888 Y88b d88P Y88b. 888 888 Y88b 888 Y8b.
|
||||
// 888888888 888 888 "Y88888 "Y8888P" "Y888 "Y888888 "Y88888 "Y8888
|
||||
// 888
|
||||
// Y8b d88P
|
||||
// "Y88P"
|
||||
// 888888b. 888 .d8888b. 888
|
||||
// 888 "88b 888 d88P Y88b 888
|
||||
// 888 .88P 888 888 888 888
|
||||
// 8888888K. .d88b. .d88b. 888888 888 .d88b. .d88888 .d88b.
|
||||
// 888 "Y88b d88""88b d88""88b 888 888 d88""88b d88" 888 d8P Y8b
|
||||
// 888 888 888 888 888 888 888 888 888 888 888 888 888 88888888
|
||||
// 888 d88P Y88..88P Y88..88P Y88b. Y88b d88P Y88..88P Y88b 888 Y8b.
|
||||
// 8888888P" "Y88P" "Y88P" "Y888 "Y8888P" "Y88P" "Y88888 "Y8888
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Device: Winbond W25X10CL
|
||||
//
|
||||
// Description: Configures W25X10CL to run in Dual I/O continuous read XIP mode
|
||||
//
|
||||
// Details: * Disable SSI
|
||||
// * Configure SSI to generate 8b command + 28b address + 2 wait,
|
||||
// with address and data using dual SPI mode
|
||||
// * Enable SSI
|
||||
// * Generate dummy read with command = 0xBB, top 24b of address
|
||||
// of 0x000000 followed by M[7:0]=0010zzzz (with the HiZ being
|
||||
// generated by 2 wait cycles). This leaves the W25X10CL in
|
||||
// continuous read mode
|
||||
// * Disable SSI
|
||||
// * Configure SSI to generate 0b command + 28b address + 2 wait,
|
||||
// with the extra 4 bits of address LSB being 0x2 to keep the
|
||||
// W25X10CL in continuous read mode forever
|
||||
// * Enable SSI
|
||||
// * Set VTOR = 0x10000100
|
||||
// * Read MSP reset vector from 0x10000100 and write to MSP (this
|
||||
// will also enable XIP mode in the SSI wrapper)
|
||||
// * Read PC reset vector from 0x10000104 and jump to it
|
||||
//
|
||||
// Building: * This code must be linked to run at 0x20000000
|
||||
// * The code will be padded to a size of 256 bytes, including a
|
||||
// 4-byte checksum. Therefore code size cannot exceed 252 bytes.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#include "pico/asm_helper.S"
|
||||
#include "hardware/regs/addressmap.h"
|
||||
#include "ssi.h"
|
||||
|
||||
// The serial flash interface will run at clk_sys/PICO_FLASH_SPI_CLKDIV.
|
||||
// This must be an even number.
|
||||
#ifndef PICO_FLASH_SPI_CLKDIV
|
||||
#define PICO_FLASH_SPI_CLKDIV 4
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// The "System Control Block" is a set of internal Cortex-M0+ control registers
|
||||
// that are memory mapped and accessed like any other H/W register. They have
|
||||
// fixed addresses in the address map of every Cortex-M0+ system.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
.equ SCB_VTOR, 0xE000ED08 // RW Vector Table Offset Register
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Winbond W25X10CL Supported Commands
|
||||
// Taken from "w25x10cl_reg_021714.pdf"
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
.equ W25X10CL_CMD_READ_DATA_FAST_DUAL_IO, 0xbb
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Winbond W25X10CL "Mode bits" are 8 special bits sent immediately after
|
||||
// the address bits in a "Read Data Fast Dual I/O" command sequence.
|
||||
// Of M[7:4], they say M[7:6] are reserved (set to zero), and bits M[3:0]
|
||||
// are don't care (we HiZ). Only M[5:4] are used, and they must be set
|
||||
// to M[5:4] = 2'b10 to enable continuous read mode.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
.equ W25X10CL_MODE_CONTINUOUS_READ, 0x20
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Start of 2nd Stage Boot Code
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
.cpu cortex-m0
|
||||
.thumb
|
||||
|
||||
.org 0
|
||||
|
||||
.section .text
|
||||
|
||||
// This code will get copied to 0x20000000 and then executed
|
||||
|
||||
.global _stage2_boot
|
||||
.type _stage2_boot,%function
|
||||
.thumb_func
|
||||
_stage2_boot:
|
||||
|
||||
ldr r5, =XIP_SSI_BASE // Use as base address where possible
|
||||
|
||||
// We are primarily interested in setting up Flash for DSPI XIP w/ continuous read
|
||||
|
||||
mov r1, #0
|
||||
str r1, [r5, #SSI_SSIENR_OFFSET] // Disable SSI to allow further config
|
||||
|
||||
// The Boot ROM sets a very conservative SPI clock frequency to be sure it can
|
||||
// read the initial 256 bytes from any device. Here we can be more aggressive.
|
||||
|
||||
mov r1, #PICO_FLASH_SPI_CLKDIV
|
||||
str r1, [r5, #SSI_BAUDR_OFFSET] // Set SSI Clock
|
||||
|
||||
// First we need to send the initial command to get us in to Fast Read Dual I/O
|
||||
// mode. As this transaction requires a command, we can't send it in XIP mode.
|
||||
// To enter Continuous Read mode as well we need to append 4'b0010 to the address
|
||||
// bits and then add a further 4 don't care bits. We will construct this by
|
||||
// specifying a 28-bit address, with the least significant bits being 4'b0010.
|
||||
// This is just a dummy transaction so we'll perform a read from address zero
|
||||
// and then discard what comes back. All we really care about is that at the
|
||||
// end of the transaction, the Winbond W25X10CL device is in Continuous Read mode
|
||||
// and from then on will only expect to receive addresses.
|
||||
|
||||
#define CTRLR0_ENTER_XIP \
|
||||
(SSI_CTRLR0_SPI_FRF_VALUE_DUAL /* Dual I/O mode */ \
|
||||
<< SSI_CTRLR0_SPI_FRF_LSB) | \
|
||||
(31 << SSI_CTRLR0_DFS_32_LSB) | /* 32 data bits */ \
|
||||
(SSI_CTRLR0_TMOD_VALUE_EEPROM_READ /* Send INST/ADDR, Receive Data */ \
|
||||
<< SSI_CTRLR0_TMOD_LSB)
|
||||
|
||||
ldr r1, =(CTRLR0_ENTER_XIP)
|
||||
str r1, [r5, #SSI_CTRLR0_OFFSET]
|
||||
|
||||
mov r1, #0x0 // NDF=0 (single 32b read)
|
||||
str r1, [r5, #SSI_CTRLR1_OFFSET]
|
||||
|
||||
#define SPI_CTRLR0_ENTER_XIP \
|
||||
(7 << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Send 28 bits (24 address + 4 mode) */ \
|
||||
(2 << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z the other 4 mode bits (2 cycles @ dual I/O = 4 bits) */ \
|
||||
(SSI_SPI_CTRLR0_INST_L_VALUE_8B \
|
||||
<< SSI_SPI_CTRLR0_INST_L_LSB) | /* 8-bit instruction */ \
|
||||
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A /* Send Command in serial mode then address in Dual I/O mode */ \
|
||||
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
|
||||
|
||||
ldr r1, =(SPI_CTRLR0_ENTER_XIP)
|
||||
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET) // SPI_CTRL0 Register
|
||||
str r1, [r0]
|
||||
|
||||
mov r1, #1 // Re-enable SSI
|
||||
str r1, [r5, #SSI_SSIENR_OFFSET]
|
||||
|
||||
mov r1, #W25X10CL_CMD_READ_DATA_FAST_DUAL_IO // 8b command = 0xBB
|
||||
str r1, [r5, #SSI_DR0_OFFSET] // Push SPI command into TX FIFO
|
||||
mov r1, #0x0000002 // 28-bit Address for dummy read = 0x000000 + 0x2 Mode bits to set M[5:4]=10
|
||||
str r1, [r5, #SSI_DR0_OFFSET] // Push Address into TX FIFO - this will trigger the transaction
|
||||
|
||||
// Now we wait for the read transaction to complete by monitoring the SSI
|
||||
// status register and checking for the "RX FIFO Not Empty" flag to assert.
|
||||
|
||||
mov r1, #SSI_SR_RFNE_BITS
|
||||
00:
|
||||
ldr r0, [r5, #SSI_SR_OFFSET] // Read status register
|
||||
tst r0, r1 // RFNE status flag set?
|
||||
beq 00b // If not then wait
|
||||
|
||||
// At this point CN# will be deasserted and the SPI clock will not be running.
|
||||
// The Winbond WX25X10CL device will be in continuous read, dual I/O mode and
|
||||
// only expecting address bits after the next CN# assertion. So long as we
|
||||
// send 4'b0010 (and 4 more dummy HiZ bits) after every subsequent 24b address
|
||||
// then the Winbond device will remain in continuous read mode. This is the
|
||||
// ideal mode for Execute-In-Place.
|
||||
// (If we want to exit continuous read mode then we will need to switch back
|
||||
// to APM mode and generate a 28-bit address phase with the extra nibble set
|
||||
// to 4'b0000).
|
||||
|
||||
mov r1, #0
|
||||
str r1, [r5, #SSI_SSIENR_OFFSET] // Disable SSI (and clear FIFO) to allow further config
|
||||
|
||||
// Note that the INST_L field is used to select what XIP data gets pushed into
|
||||
// the TX FIFO:
|
||||
// INST_L_0_BITS {ADDR[23:0],XIP_CMD[7:0]} Load "mode bits" into XIP_CMD
|
||||
// Anything else {XIP_CMD[7:0],ADDR[23:0]} Load SPI command into XIP_CMD
|
||||
|
||||
#define SPI_CTRLR0_XIP \
|
||||
(W25X10CL_MODE_CONTINUOUS_READ /* Mode bits to keep Winbond in continuous read mode */ \
|
||||
<< SSI_SPI_CTRLR0_XIP_CMD_LSB) | \
|
||||
(7 << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Send 28 bits (24 address + 4 mode) */ \
|
||||
(2 << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z the other 4 mode bits (2 cycles @ dual I/O = 4 bits) */ \
|
||||
(SSI_SPI_CTRLR0_INST_L_VALUE_NONE /* Do not send a command, instead send XIP_CMD as mode bits after address */ \
|
||||
<< SSI_SPI_CTRLR0_INST_L_LSB) | \
|
||||
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A /* Send Address in Dual I/O mode (and Command but that is zero bits long) */ \
|
||||
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
|
||||
|
||||
ldr r1, =(SPI_CTRLR0_XIP)
|
||||
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET)
|
||||
str r1, [r0]
|
||||
|
||||
mov r1, #1
|
||||
str r1, [r5, #SSI_SSIENR_OFFSET] // Re-enable SSI
|
||||
|
||||
// We are now in XIP mode, with all transactions using Dual I/O and only
|
||||
// needing to send 24-bit addresses (plus mode bits) for each read transaction.
|
||||
|
||||
soft_reset:
|
||||
// Just jump to start of image after this 256-byte boot stage2 (with thumb bit set)
|
||||
ldr r0, =(XIP_BASE + 256 + 1)
|
||||
bx r0
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Literal Table
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
.ltorg
|
||||
|
||||
.end
|
13
src/rp2_common/boot_stage2/boot_stage2.ld
Normal file
13
src/rp2_common/boot_stage2/boot_stage2.ld
Normal file
@ -0,0 +1,13 @@
|
||||
MEMORY {
|
||||
/* We are loaded to the top 256 bytes of SRAM, which is above the bootrom
|
||||
stack. Note 4 bytes occupied by checksum. */
|
||||
SRAM(rx) : ORIGIN = 0x20041f00, LENGTH = 252
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
. = ORIGIN(SRAM);
|
||||
.text : {
|
||||
*(.entry)
|
||||
*(.text)
|
||||
} >SRAM
|
||||
}
|
4
src/rp2_common/boot_stage2/doc.h
Normal file
4
src/rp2_common/boot_stage2/doc.h
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* \defgroup boot_stage2 boot_stage2
|
||||
* \brief Second stage boot loaders responsible for setting up external flash
|
||||
*/
|
53
src/rp2_common/boot_stage2/pad_checksum
Executable file
53
src/rp2_common/boot_stage2/pad_checksum
Executable file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import binascii
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def any_int(x):
|
||||
try:
|
||||
return int(x, 0)
|
||||
except:
|
||||
raise argparse.ArgumentTypeError("expected an integer, not '{!r}'".format(x))
|
||||
|
||||
|
||||
def bitrev(x, width):
|
||||
return int("{:0{w}b}".format(x, w=width)[::-1], 2)
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("ifile", help="Input file (binary)")
|
||||
parser.add_argument("ofile", help="Output file (assembly)")
|
||||
parser.add_argument("-p", "--pad", help="Padded size (bytes), including 4-byte checksum, default 256",
|
||||
type=any_int, default=256)
|
||||
parser.add_argument("-s", "--seed", help="Checksum seed value, default 0",
|
||||
type=any_int, default=0)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
idata = open(args.ifile, "rb").read()
|
||||
except:
|
||||
sys.exit("Could not open input file '{}'".format(args.ifile))
|
||||
|
||||
if len(idata) >= args.pad - 4:
|
||||
sys.exit("Input file size ({} bytes) too large for final size ({} bytes)".format(len(idata), args.pad))
|
||||
|
||||
idata_padded = idata + bytes(args.pad - 4 - len(idata))
|
||||
|
||||
# Our bootrom CRC32 is slightly bass-ackward but it's best to work around for now (FIXME)
|
||||
# 100% worth it to save two Thumb instructions
|
||||
checksum = bitrev(
|
||||
(binascii.crc32(bytes(bitrev(b, 8) for b in idata_padded), args.seed ^ 0xffffffff) ^ 0xffffffff) & 0xffffffff, 32)
|
||||
odata = idata_padded + struct.pack("<L", checksum)
|
||||
|
||||
try:
|
||||
with open(args.ofile, "w") as ofile:
|
||||
ofile.write("// Padded and checksummed version of: {}\n\n".format(args.ifile))
|
||||
ofile.write(".section .boot2, \"a\"\n\n")
|
||||
for offs in range(0, len(odata), 16):
|
||||
chunk = odata[offs:min(offs + 16, len(odata))]
|
||||
ofile.write(".byte {}\n".format(", ".join("0x{:02x}".format(b) for b in chunk)))
|
||||
except:
|
||||
sys.exit("Could not open output file '{}'".format(args.ofile))
|
4
src/rp2_common/hardware_adc/CMakeLists.txt
Normal file
4
src/rp2_common/hardware_adc/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
pico_simple_hardware_target(adc)
|
||||
|
||||
# additional library
|
||||
target_link_libraries(hardware_adc INTERFACE hardware_resets)
|
23
src/rp2_common/hardware_adc/adc.c
Normal file
23
src/rp2_common/hardware_adc/adc.c
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/adc.h"
|
||||
#include "hardware/resets.h"
|
||||
|
||||
void adc_init(void) {
|
||||
// ADC is in an unknown state. We should start by resetting it
|
||||
reset_block(RESETS_RESET_ADC_BITS);
|
||||
unreset_block_wait(RESETS_RESET_ADC_BITS);
|
||||
|
||||
// Now turn it back on. Staging of clock etc is handled internally
|
||||
adc_hw->cs = ADC_CS_EN_BITS;
|
||||
|
||||
// Internal staging completes in a few cycles, but poll to be sure
|
||||
while (!(adc_hw->cs & ADC_CS_READY_BITS)) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
}
|
248
src/rp2_common/hardware_adc/include/hardware/adc.h
Normal file
248
src/rp2_common/hardware_adc/include/hardware/adc.h
Normal file
@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_ADC_H_
|
||||
#define _HARDWARE_ADC_H_
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/adc.h"
|
||||
#include "hardware/gpio.h"
|
||||
|
||||
/** \file hardware/adc.h
|
||||
* \defgroup hardware_adc hardware_adc
|
||||
*
|
||||
* Analog to Digital Converter (ADC) API
|
||||
*
|
||||
* The RP2040 has an internal analogue-digital converter (ADC) with the following features:
|
||||
* - SAR ADC
|
||||
* - 500 kS/s (Using an independent 48MHz clock)
|
||||
* - 12 bit (9.5 ENOB)
|
||||
* - 5 input mux:
|
||||
* - 4 inputs that are available on package pins shared with GPIO[29:26]
|
||||
* - 1 input is dedicated to the internal temperature sensor
|
||||
* - 4 element receive sample FIFO
|
||||
* - Interrupt generation
|
||||
* - DMA interface
|
||||
*
|
||||
* Although there is only one ADC you can specify the input to it using the adc_select_input() function.
|
||||
* In round robin mode (adc_rrobin()) will use that input and move to the next one after a read.
|
||||
*
|
||||
* User ADC inputs are on 0-3 (GPIO 26-29), the temperature sensor is on input 4.
|
||||
*
|
||||
* Temperature sensor values can be approximated in centigrade as:
|
||||
*
|
||||
* T = 27 - (ADC_Voltage - 0.706)/0.001721
|
||||
*
|
||||
* The FIFO, if used, can contain up to 4 entries.
|
||||
*
|
||||
* \subsection adc_example Example
|
||||
* \addtogroup hardware_adc
|
||||
*
|
||||
* \include hello_adc.c
|
||||
*/
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_ADC, Enable/disable assertions in the ADC module, type=bool, default=0, group=hardware_adc
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_ADC
|
||||
#define PARAM_ASSERTIONS_ENABLED_ADC 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*! \brief Initialise the ADC HW
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
*/
|
||||
void adc_init(void);
|
||||
|
||||
/*! \brief Initialise the gpio for use as an ADC pin
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* Prepare a GPIO for use with ADC, by disabling all digital functions.
|
||||
*
|
||||
* \param gpio The GPIO number to use. Allowable GPIO numbers are 26 to 29 inclusive.
|
||||
*/
|
||||
static inline void adc_gpio_init(uint gpio) {
|
||||
invalid_params_if(ADC, gpio < 26 || gpio > 29);
|
||||
// Select NULL function to make output driver hi-Z
|
||||
gpio_set_function(gpio, GPIO_FUNC_NULL);
|
||||
// Also disable digital pulls and digital receiver
|
||||
gpio_disable_pulls(gpio);
|
||||
gpio_set_input_enabled(gpio, false);
|
||||
}
|
||||
|
||||
/*! \brief ADC input select
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* Select an ADC input. 0...3 are GPIOs 26...29 respectively.
|
||||
* Input 4 is the onboard temperature sensor.
|
||||
*
|
||||
* \param input Input to select.
|
||||
*/
|
||||
static inline void adc_select_input(uint input) {
|
||||
invalid_params_if(ADC, input > 4);
|
||||
hw_write_masked(&adc_hw->cs, input << ADC_CS_AINSEL_LSB, ADC_CS_AINSEL_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Round Robin sampling selector
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* This function sets which inputs are to be run through in round robin mode.
|
||||
* Value between 0 and 0x1f (bit 0 to bit 4 for GPIO 26 to 29 and temperature sensor input respectively)
|
||||
*
|
||||
* \param input_mask A bit pattern indicating which of the 5 inputs are to be sampled. Write a value of 0 to disable round robin sampling.
|
||||
*/
|
||||
static inline void adc_set_round_robin(uint input_mask) {
|
||||
invalid_params_if(ADC, input_mask & ~ADC_CS_RROBIN_BITS);
|
||||
hw_write_masked(&adc_hw->cs, input_mask << ADC_CS_RROBIN_LSB, ADC_CS_RROBIN_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Enable the onboard temperature sensor
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* \param enable Set true to power on the onboard temperature sensor, false to power off.
|
||||
*
|
||||
*/
|
||||
static inline void adc_set_temp_sensor_enabled(bool enable) {
|
||||
if (enable)
|
||||
hw_set_bits(&adc_hw->cs, ADC_CS_TS_EN_BITS);
|
||||
else
|
||||
hw_clear_bits(&adc_hw->cs, ADC_CS_TS_EN_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Perform a single conversion
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* Performs an ADC conversion, waits for the result, and then returns it.
|
||||
*
|
||||
* \return Result of the conversion.
|
||||
*/
|
||||
static inline uint16_t adc_read(void) {
|
||||
hw_set_bits(&adc_hw->cs, ADC_CS_START_ONCE_BITS);
|
||||
|
||||
while (!(adc_hw->cs & ADC_CS_READY_BITS))
|
||||
tight_loop_contents();
|
||||
|
||||
return adc_hw->result;
|
||||
}
|
||||
|
||||
/*! \brief Enable or disable free-running sampling mode
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* \param run false to disable, true to enable free running conversion mode.
|
||||
*/
|
||||
static inline void adc_run(bool run) {
|
||||
if (run)
|
||||
hw_set_bits(&adc_hw->cs, ADC_CS_START_MANY_BITS);
|
||||
else
|
||||
hw_clear_bits(&adc_hw->cs, ADC_CS_START_MANY_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Set the ADC Clock divisor
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* Period of samples will be (1 + div) cycles on average. Note it takes 96 cycles to perform a conversion,
|
||||
* so any period less than that will be clamped to 96.
|
||||
*
|
||||
* \param clkdiv If non-zero, conversion will be started at intervals rather than back to back.
|
||||
*/
|
||||
static inline void adc_set_clkdiv(float clkdiv) {
|
||||
invalid_params_if(ADC, clkdiv >= 1 << (ADC_DIV_INT_MSB - ADC_DIV_INT_LSB + 1));
|
||||
adc_hw->div = (uint32_t)(clkdiv * (float) (1 << ADC_DIV_INT_LSB));
|
||||
}
|
||||
|
||||
/*! \brief Setup the ADC FIFO
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* FIFO is 4 samples long, if a conversion is completed and the FIFO is full the result is dropped.
|
||||
*
|
||||
* \param en Enables write each conversion result to the FIFO
|
||||
* \param dreq_en Enable DMA requests when FIFO contains data
|
||||
* \param dreq_thresh Threshold for DMA requests/FIFO IRQ if enabled.
|
||||
* \param err_in_fifo If enabled, bit 15 of the FIFO contains error flag for each sample
|
||||
* \param byte_shift Shift FIFO contents to be one byte in size (for byte DMA) - enables DMA to byte buffers.
|
||||
*/
|
||||
static inline void adc_fifo_setup(bool en, bool dreq_en, uint16_t dreq_thresh, bool err_in_fifo, bool byte_shift) {
|
||||
hw_write_masked(&adc_hw->fcs,
|
||||
(!!en << ADC_FCS_EN_LSB) |
|
||||
(!!dreq_en << ADC_FCS_DREQ_EN_LSB) |
|
||||
(dreq_thresh << ADC_FCS_THRESH_LSB) |
|
||||
(!!err_in_fifo << ADC_FCS_ERR_LSB) |
|
||||
(!!byte_shift << ADC_FCS_SHIFT_LSB),
|
||||
ADC_FCS_EN_BITS |
|
||||
ADC_FCS_DREQ_EN_BITS |
|
||||
ADC_FCS_THRESH_BITS |
|
||||
ADC_FCS_ERR_BITS |
|
||||
ADC_FCS_SHIFT_BITS
|
||||
);
|
||||
}
|
||||
|
||||
/*! \brief Check FIFO empty state
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* \return Returns true if the fifo is empty
|
||||
*/
|
||||
static inline bool adc_fifo_is_empty(void) {
|
||||
return !!(adc_hw->fcs & ADC_FCS_EMPTY_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Get number of entries in the ADC FIFO
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* The ADC FIFO is 4 entries long. This function will return how many samples are currently present.
|
||||
*/
|
||||
static inline uint8_t adc_fifo_get_level(void) {
|
||||
return (adc_hw->fcs & ADC_FCS_LEVEL_BITS) >> ADC_FCS_LEVEL_LSB;
|
||||
}
|
||||
|
||||
/*! \brief Get ADC result from FIFO
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* Pops the latest result from the ADC FIFO.
|
||||
*/
|
||||
static inline uint16_t adc_fifo_get(void) {
|
||||
return adc_hw->fifo;
|
||||
}
|
||||
|
||||
/*! \brief Wait for the ADC FIFO to have data.
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* Blocks until data is present in the FIFO
|
||||
*/
|
||||
static inline uint16_t adc_fifo_get_blocking(void) {
|
||||
while (adc_fifo_is_empty())
|
||||
tight_loop_contents();
|
||||
return adc_hw->fifo;
|
||||
}
|
||||
|
||||
/*! \brief Drain the ADC FIFO
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* Will wait for any conversion to complete then drain the FIFO discarding any results.
|
||||
*/
|
||||
static inline void adc_fifo_drain(void) {
|
||||
// Potentially there is still a conversion in progress -- wait for this to complete before draining
|
||||
while (!(adc_hw->cs & ADC_CS_READY_BITS))
|
||||
tight_loop_contents();
|
||||
while (!adc_fifo_is_empty())
|
||||
(void) adc_fifo_get();
|
||||
}
|
||||
|
||||
/*! \brief Enable/Disable ADC interrupts.
|
||||
* \ingroup hardware_adc
|
||||
*
|
||||
* \param enabled Set to true to enable the ADC interrupts, false to disable
|
||||
*/
|
||||
static inline void adc_irq_set_enabled(bool enabled) {
|
||||
adc_hw->inte = !!enabled;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
3
src/rp2_common/hardware_base/CMakeLists.txt
Normal file
3
src/rp2_common/hardware_base/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
add_library(hardware_base INTERFACE)
|
||||
target_include_directories(hardware_base INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
|
||||
target_link_libraries(hardware_base INTERFACE pico_base_headers)
|
123
src/rp2_common/hardware_base/include/hardware/address_mapped.h
Normal file
123
src/rp2_common/hardware_base/include/hardware/address_mapped.h
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_ADDRESS_MAPPED_H
|
||||
#define _HARDWARE_ADDRESS_MAPPED_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/regs/addressmap.h"
|
||||
|
||||
/** \file address_mapped.h
|
||||
* \defgroup hardware_base hardware_base
|
||||
*
|
||||
* Low-level types and (atomic) accessors for memory-mapped hardware registers
|
||||
*
|
||||
* `hardware_base` defines the low level types and access functions for memory mapped hardware registers. It is included
|
||||
* by default by all other hardware libraries.
|
||||
*
|
||||
* The following register access typedefs codify the access type (read/write) and the bus size (8/16/32) of the hardware register.
|
||||
* The register type names are formed by concatenating one from each of the 3 parts A, B, C
|
||||
|
||||
* A | B | C | Meaning
|
||||
* ------|---|---|--------
|
||||
* io_ | | | A Memory mapped IO register
|
||||
* |ro_| | read-only access
|
||||
* |rw_| | read-write access
|
||||
* |wo_| | write-only access (can't actually be enforced via C API)
|
||||
* | | 8| 8-bit wide access
|
||||
* | | 16| 16-bit wide access
|
||||
* | | 32| 32-bit wide access
|
||||
*
|
||||
* When dealing with these types, you will always use a pointer, i.e. `io_rw_32 *some_reg` is a pointer to a read/write
|
||||
* 32 bit register that you can write with `*some_reg = value`, or read with `value = *some_reg`.
|
||||
*
|
||||
* RP2040 hardware is also aliased to provide atomic setting, clear or flipping of a subset of the bits within
|
||||
* a hardware register so that concurrent access by two cores is always consistent with one atomic operation
|
||||
* being performed first, followed by the second.
|
||||
*
|
||||
* See hw_set_bits(), hw_clear_bits() and hw_xor_bits() provide for atomic access via a pointer to a 32 bit register
|
||||
*
|
||||
* Additionally given a pointer to a structure representing a piece of hardware (e.g. `dma_hw_t *dma_hw` for the DMA controller), you can
|
||||
* get an alias to the entire structure such that writing any member (register) within the structure is equivalent
|
||||
* to an atomic operation via hw_set_alias(), hw_clear_alias() or hw_xor_alias()...
|
||||
*
|
||||
* For example `hw_set_alias(dma_hw)->inte1 = 0x80;` will set bit 7 of the INTE1 register of the DMA controller,
|
||||
* leaving the other bits unchanged.
|
||||
*/
|
||||
|
||||
#define check_hw_layout(type, member, offset) static_assert(offsetof(type, member) == (offset), "hw offset mismatch")
|
||||
#define check_hw_size(type, size) static_assert(sizeof(type) == (size), "hw size mismatch")
|
||||
|
||||
typedef volatile uint32_t io_rw_32;
|
||||
typedef const volatile uint32_t io_ro_32;
|
||||
typedef volatile uint32_t io_wo_32;
|
||||
typedef volatile uint16_t io_rw_16;
|
||||
typedef const volatile uint16_t io_ro_16;
|
||||
typedef volatile uint16_t io_wo_16;
|
||||
typedef volatile uint8_t io_rw_8;
|
||||
typedef const volatile uint8_t io_ro_8;
|
||||
typedef volatile uint8_t io_wo_8;
|
||||
|
||||
typedef volatile uint8_t *const ioptr;
|
||||
typedef ioptr const const_ioptr;
|
||||
|
||||
// Untyped conversion alias pointer generation macros
|
||||
#define hw_set_alias_untyped(addr) ((void *)(REG_ALIAS_SET_BITS | (uintptr_t)(addr)))
|
||||
#define hw_clear_alias_untyped(addr) ((void *)(REG_ALIAS_CLR_BITS | (uintptr_t)(addr)))
|
||||
#define hw_xor_alias_untyped(addr) ((void *)(REG_ALIAS_XOR_BITS | (uintptr_t)(addr)))
|
||||
|
||||
// Typed conversion alias pointer generation macros
|
||||
#define hw_set_alias(p) ((typeof(p))hw_set_alias_untyped(p))
|
||||
#define hw_clear_alias(p) ((typeof(p))hw_clear_alias_untyped(p))
|
||||
#define hw_xor_alias(p) ((typeof(p))hw_xor_alias_untyped(p))
|
||||
|
||||
/*! \brief Atomically set the specified bits to 1 in a HW register
|
||||
* \ingroup hardware_base
|
||||
*
|
||||
* \param addr Address of writable register
|
||||
* \param mask Bit-mask specifying bits to set
|
||||
*/
|
||||
inline static void hw_set_bits(io_rw_32 *addr, uint32_t mask) {
|
||||
*(io_rw_32 *) hw_set_alias_untyped((volatile void *) addr) = mask;
|
||||
}
|
||||
|
||||
/*! \brief Atomically clear the specified bits to 0 in a HW register
|
||||
* \ingroup hardware_base
|
||||
*
|
||||
* \param addr Address of writable register
|
||||
* \param mask Bit-mask specifying bits to clear
|
||||
*/
|
||||
inline static void hw_clear_bits(io_rw_32 *addr, uint32_t mask) {
|
||||
*(io_rw_32 *) hw_clear_alias_untyped((volatile void *) addr) = mask;
|
||||
}
|
||||
|
||||
/*! \brief Atomically flip the specified bits in a HW register
|
||||
* \ingroup hardware_base
|
||||
*
|
||||
* \param addr Address of writable register
|
||||
* \param mask Bit-mask specifying bits to invert
|
||||
*/
|
||||
inline static void hw_xor_bits(io_rw_32 *addr, uint32_t mask) {
|
||||
*(io_rw_32 *) hw_xor_alias_untyped((volatile void *) addr) = mask;
|
||||
}
|
||||
|
||||
/*! \brief Set new values for a sub-set of the bits in a HW register
|
||||
* \ingroup hardware_base
|
||||
*
|
||||
* Sets destination bits to values specified in \p values, if and only if corresponding bit in \p write_mask is set
|
||||
*
|
||||
* Note: this method allows safe concurrent modification of *different* bits of
|
||||
* a register, but multiple concurrent access to the same bits is still unsafe.
|
||||
*
|
||||
* \param addr Address of writable register
|
||||
* \param values Bits values
|
||||
* \param write_mask Mask of bits to change
|
||||
*/
|
||||
inline static void hw_write_masked(io_rw_32 *addr, uint32_t values, uint32_t write_mask) {
|
||||
hw_xor_bits(addr, (*addr ^ values) & write_mask);
|
||||
}
|
||||
|
||||
#endif
|
6
src/rp2_common/hardware_claim/CMakeLists.txt
Normal file
6
src/rp2_common/hardware_claim/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
add_library(hardware_claim INTERFACE)
|
||||
target_include_directories(hardware_claim INTERFACE include)
|
||||
target_sources(hardware_claim INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/claim.c)
|
||||
|
||||
target_link_libraries(hardware_claim INTERFACE hardware_sync)
|
65
src/rp2_common/hardware_claim/claim.c
Normal file
65
src/rp2_common/hardware_claim/claim.c
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/claim.h"
|
||||
|
||||
uint32_t hw_claim_lock() {
|
||||
return spin_lock_blocking(spin_lock_instance(PICO_SPINLOCK_ID_HARDWARE_CLAIM));
|
||||
}
|
||||
|
||||
void hw_claim_unlock(uint32_t save) {
|
||||
spin_unlock(spin_lock_instance(PICO_SPINLOCK_ID_HARDWARE_CLAIM), save);
|
||||
}
|
||||
|
||||
bool hw_is_claimed(uint8_t *bits, uint bit_index) {
|
||||
bool rc;
|
||||
uint32_t save = hw_claim_lock();
|
||||
if (bits[bit_index >> 3u] & (1u << (bit_index & 7u))) {
|
||||
rc = false;
|
||||
} else {
|
||||
bits[bit_index >> 3u] |= (1u << (bit_index & 7u));
|
||||
rc = true;
|
||||
}
|
||||
hw_claim_unlock(save);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void hw_claim_or_assert(uint8_t *bits, uint bit_index, const char *message) {
|
||||
uint32_t save = hw_claim_lock();
|
||||
if (bits[bit_index >> 3u] & (1u << (bit_index & 7u))) {
|
||||
panic(message, bit_index);
|
||||
} else {
|
||||
bits[bit_index >> 3u] |= (1u << (bit_index & 7u));
|
||||
}
|
||||
hw_claim_unlock(save);
|
||||
}
|
||||
|
||||
int hw_claim_unused_from_range(uint8_t *bits, bool required, uint bit_lsb, uint bit_msb, const char *message) {
|
||||
// don't bother check lsb / msb order as if wrong, then it'll fail anyway
|
||||
uint32_t save = hw_claim_lock();
|
||||
int found_bit = -1;
|
||||
for(uint bit=bit_lsb; bit <= bit_msb; bit++) {
|
||||
if (!(bits[bit >> 3u] & (1u << (bit & 7u)))) {
|
||||
bits[bit >> 3u] |= (1u << (bit & 7u));
|
||||
found_bit = bit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
hw_claim_unlock(save);
|
||||
if (found_bit < 0 && required) {
|
||||
panic(message);
|
||||
}
|
||||
return found_bit;
|
||||
}
|
||||
|
||||
void hw_claim_clear(uint8_t *bits, uint bit_index) {
|
||||
uint32_t save = hw_claim_lock();
|
||||
assert(bits[bit_index >> 3u] & (1u << (bit_index & 7u)));
|
||||
bits[bit_index >> 3u] &= ~(1u << (bit_index & 7u));
|
||||
hw_claim_unlock(save);
|
||||
}
|
||||
|
||||
|
101
src/rp2_common/hardware_claim/include/hardware/claim.h
Normal file
101
src/rp2_common/hardware_claim/include/hardware/claim.h
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_CLAIM_H
|
||||
#define _HARDWARE_CLAIM_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/sync.h"
|
||||
|
||||
/** \file claim.h
|
||||
* \defgroup hardware_claim hardware_claim
|
||||
*
|
||||
* Lightweight hardware resource management
|
||||
*
|
||||
* `hardware_claim` provides a simple API for management of hardware resources at runtime.
|
||||
*
|
||||
* This API is usually called by other hardware specific _claiming_ APIs and provides simple
|
||||
* multi-core safe methods to manipulate compact bit-sets representing hardware resources.
|
||||
*
|
||||
* This API allows any other library to cooperatively participate in a scheme by which
|
||||
* both compile time and runtime allocation of resources can co-exist, and conflicts
|
||||
* can be avoided or detected (depending on the use case) without the libraries having
|
||||
* any other knowledge of each other.
|
||||
*
|
||||
* Facilities are providing for:
|
||||
*
|
||||
* 1. Claiming resources (and asserting if they are already claimed)
|
||||
* 2. Freeing (unclaiming) resources
|
||||
* 3. Finding unused resources
|
||||
*/
|
||||
|
||||
/*! \brief Atomically claim a resource, panicking if it is already in use
|
||||
* \ingroup hardware_claim
|
||||
*
|
||||
* The resource ownership is indicated by the bit_index bit in an array of bits.
|
||||
*
|
||||
* \param bits pointer to an array of bits (8 bits per byte)
|
||||
* \param bit_index resource to claim (bit index into array of bits)
|
||||
* \param message string to display if the bit cannot be claimed; note this may have a single printf format "%d" for the bit
|
||||
*/
|
||||
void hw_claim_or_assert(uint8_t *bits, uint bit_index, const char *message);
|
||||
|
||||
/*! \brief Atomically claim one resource out of a range of resources, optionally asserting if none are free
|
||||
* \ingroup hardware_claim
|
||||
*
|
||||
* \param bits pointer to an array of bits (8 bits per byte)
|
||||
* \param required true if this method should panic if the resource is not free
|
||||
* \param bit_lsb the lower bound (inclusive) of the resource range to claim from
|
||||
* \param bit_msb the upper bound (inclusive) of the resource range to claim from
|
||||
* \param message string to display if the bit cannot be claimed
|
||||
* \return the bit index representing the claimed or -1 if none are available in the range, and required = false
|
||||
*/
|
||||
int hw_claim_unused_from_range(uint8_t *bits, bool required, uint bit_lsb, uint bit_msb, const char *message);
|
||||
|
||||
/*! \brief Determine if a resource is claimed at the time of the call
|
||||
* \ingroup hardware_claim
|
||||
*
|
||||
* The resource ownership is indicated by the bit_index bit in an array of bits.
|
||||
*
|
||||
* \param bits pointer to an array of bits (8 bits per byte)
|
||||
* \param bit_index resource to unclaim (bit index into array of bits)
|
||||
* \return true if the resource is claimed
|
||||
*/
|
||||
bool hw_is_claimed(uint8_t *bits, uint bit_index);
|
||||
|
||||
/*! \brief Atomically unclaim a resource
|
||||
* \ingroup hardware_claim
|
||||
*
|
||||
* The resource ownership is indicated by the bit_index bit in an array of bits.
|
||||
*
|
||||
* \param bits pointer to an array of bits (8 bits per byte)
|
||||
* \param bit_index resource to unclaim (bit index into array of bits)
|
||||
*/
|
||||
void hw_claim_clear(uint8_t *bits, uint bit_index);
|
||||
|
||||
/*! \brief Acquire the runtime mutual exclusion lock provided by the `hardware_claim` library
|
||||
* \ingroup hardware_claim
|
||||
*
|
||||
* This method is called automatically by the other `hw_claim_` methods, however it is provided as a convenience
|
||||
* to code that might want to protect other hardware initialization code from concurrent use.
|
||||
*
|
||||
* \note hw_claim_lock() uses a spin lock internally, so disables interrupts on the calling core, and will deadlock
|
||||
* if the calling core already owns the lock.
|
||||
*
|
||||
* \return a token to pass to hw_claim_unlock()
|
||||
*/
|
||||
uint32_t hw_claim_lock();
|
||||
|
||||
/*! \brief Release the runtime mutual exclusion lock provided by the `hardware_claim` library
|
||||
* \ingroup hardware_claim
|
||||
*
|
||||
* \note This method MUST be called from the same core that call hw_claim_lock()
|
||||
*
|
||||
* \param token the token returned by the corresponding call to hw_claim_lock()
|
||||
*/
|
||||
void hw_claim_unlock(uint32_t token);
|
||||
|
||||
#endif
|
11
src/rp2_common/hardware_clocks/CMakeLists.txt
Normal file
11
src/rp2_common/hardware_clocks/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
pico_simple_hardware_target(clocks)
|
||||
|
||||
target_link_libraries(hardware_clocks INTERFACE
|
||||
hardware_resets
|
||||
hardware_watchdog
|
||||
hardware_xosc
|
||||
hardware_pll
|
||||
# not currently used by clocks.c, but sensibly bundled here
|
||||
# as changing frequencies may require upping voltage
|
||||
hardware_vreg
|
||||
)
|
389
src/rp2_common/hardware_clocks/clocks.c
Normal file
389
src/rp2_common/hardware_clocks/clocks.c
Normal file
@ -0,0 +1,389 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/regs/clocks.h"
|
||||
#include "hardware/platform_defs.h"
|
||||
#include "hardware/resets.h"
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/watchdog.h"
|
||||
#include "hardware/pll.h"
|
||||
#include "hardware/xosc.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/gpio.h"
|
||||
|
||||
check_hw_layout(clocks_hw_t, clk[clk_adc].selected, CLOCKS_CLK_ADC_SELECTED_OFFSET);
|
||||
check_hw_layout(clocks_hw_t, fc0.result, CLOCKS_FC0_RESULT_OFFSET);
|
||||
check_hw_layout(clocks_hw_t, ints, CLOCKS_INTS_OFFSET);
|
||||
|
||||
static uint32_t configured_freq[CLK_COUNT];
|
||||
|
||||
static resus_callback_t _resus_callback;
|
||||
|
||||
// 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.
|
||||
static inline bool has_glitchless_mux(enum clock_index clk_index) {
|
||||
return clk_index == clk_sys || clk_index == clk_ref;
|
||||
}
|
||||
|
||||
void clock_stop(enum clock_index clk_index) {
|
||||
clock_hw_t *clock = &clocks_hw->clk[clk_index];
|
||||
hw_clear_bits(&clock->ctrl, CLOCKS_CLK_USB_CTRL_ENABLE_BITS);
|
||||
configured_freq[clk_index] = 0;
|
||||
}
|
||||
|
||||
/// \tag::clock_configure[]
|
||||
bool clock_configure(enum clock_index clk_index, uint32_t src, uint32_t auxsrc, uint32_t src_freq, uint32_t freq) {
|
||||
uint32_t div;
|
||||
|
||||
assert(src_freq >= freq);
|
||||
|
||||
if (freq > src_freq)
|
||||
return false;
|
||||
|
||||
// Div register is 24.8 int.frac divider so multiply by 2^8 (left shift by 8)
|
||||
div = (uint32_t) (((uint64_t) src_freq << 8) / freq);
|
||||
|
||||
clock_hw_t *clock = &clocks_hw->clk[clk_index];
|
||||
|
||||
// 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 > clock->div)
|
||||
clock->div = 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 (has_glitchless_mux(clk_index) && src == CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX) {
|
||||
hw_clear_bits(&clock->ctrl, CLOCKS_CLK_REF_CTRL_SRC_BITS);
|
||||
while (!(clock->selected & 1u))
|
||||
tight_loop_contents();
|
||||
}
|
||||
// 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 (clk_sys, clk_ref).
|
||||
else {
|
||||
hw_clear_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
|
||||
if (configured_freq[clk_index] > 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:
|
||||
uint delay_cyc = configured_freq[clk_sys] / configured_freq[clk_index] + 1;
|
||||
asm volatile (
|
||||
"1: \n\t"
|
||||
"sub %0, #1 \n\t"
|
||||
"bne 1b"
|
||||
: "+r" (delay_cyc)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Set aux mux first, and then glitchless mux if this clock has one
|
||||
hw_write_masked(&clock->ctrl,
|
||||
(auxsrc << CLOCKS_CLK_SYS_CTRL_AUXSRC_LSB),
|
||||
CLOCKS_CLK_SYS_CTRL_AUXSRC_BITS
|
||||
);
|
||||
|
||||
if (has_glitchless_mux(clk_index)) {
|
||||
hw_write_masked(&clock->ctrl,
|
||||
src << CLOCKS_CLK_REF_CTRL_SRC_LSB,
|
||||
CLOCKS_CLK_REF_CTRL_SRC_BITS
|
||||
);
|
||||
while (!(clock->selected & (1u << src)))
|
||||
tight_loop_contents();
|
||||
}
|
||||
|
||||
hw_set_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
|
||||
|
||||
// Now that the source is configured, we can trust that the user-supplied
|
||||
// divisor is a safe value.
|
||||
clock->div = div;
|
||||
|
||||
// Store the configured frequency
|
||||
configured_freq[clk_index] = freq;
|
||||
|
||||
return true;
|
||||
}
|
||||
/// \end::clock_configure[]
|
||||
|
||||
void clocks_init(void) {
|
||||
// Start tick in watchdog
|
||||
watchdog_start_tick(XOSC_MHZ);
|
||||
|
||||
// Everything is 48MHz on FPGA apart from RTC. Otherwise set to 0 and will be set in clock configure
|
||||
if (running_on_fpga()) {
|
||||
for (uint i = 0; i < CLK_COUNT; i++) {
|
||||
configured_freq[i] = 48 * MHZ;
|
||||
}
|
||||
configured_freq[clk_rtc] = 46875;
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable resus that may be enabled from previous software
|
||||
clocks_hw->resus.ctrl = 0;
|
||||
|
||||
// Enable the xosc
|
||||
xosc_init();
|
||||
|
||||
// Before we touch PLLs, switch sys and ref cleanly away from their aux sources.
|
||||
hw_clear_bits(&clocks_hw->clk[clk_sys].ctrl, CLOCKS_CLK_SYS_CTRL_SRC_BITS);
|
||||
while (clocks_hw->clk[clk_sys].selected != 0x1)
|
||||
tight_loop_contents();
|
||||
hw_clear_bits(&clocks_hw->clk[clk_ref].ctrl, CLOCKS_CLK_REF_CTRL_SRC_BITS);
|
||||
while (clocks_hw->clk[clk_ref].selected != 0x1)
|
||||
tight_loop_contents();
|
||||
|
||||
/// \tag::pll_settings[]
|
||||
// Configure PLLs
|
||||
// REF FBDIV VCO POSTDIV
|
||||
// PLL SYS: 12 / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz
|
||||
// PLL USB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz
|
||||
/// \end::pll_settings[]
|
||||
|
||||
reset_block(RESETS_RESET_PLL_SYS_BITS | RESETS_RESET_PLL_USB_BITS);
|
||||
unreset_block_wait(RESETS_RESET_PLL_SYS_BITS | RESETS_RESET_PLL_USB_BITS);
|
||||
|
||||
/// \tag::pll_init[]
|
||||
pll_init(pll_sys, 1, 1500 * MHZ, 6, 2);
|
||||
pll_init(pll_usb, 1, 480 * MHZ, 5, 2);
|
||||
/// \end::pll_init[]
|
||||
|
||||
// Configure clocks
|
||||
// CLK_REF = XOSC (12MHz) / 1 = 12MHz
|
||||
clock_configure(clk_ref,
|
||||
CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC,
|
||||
0, // No aux mux
|
||||
12 * MHZ,
|
||||
12 * MHZ);
|
||||
|
||||
/// \tag::configure_clk_sys[]
|
||||
// CLK SYS = PLL SYS (125MHz) / 1 = 125MHz
|
||||
clock_configure(clk_sys,
|
||||
CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
|
||||
CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
|
||||
125 * MHZ,
|
||||
125 * MHZ);
|
||||
/// \end::configure_clk_sys[]
|
||||
|
||||
// CLK USB = PLL USB (48MHz) / 1 = 48MHz
|
||||
clock_configure(clk_usb,
|
||||
0, // No GLMUX
|
||||
CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
|
||||
48 * MHZ,
|
||||
48 * MHZ);
|
||||
|
||||
// CLK ADC = PLL USB (48MHZ) / 1 = 48MHz
|
||||
clock_configure(clk_adc,
|
||||
0, // No GLMUX
|
||||
CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
|
||||
48 * MHZ,
|
||||
48 * MHZ);
|
||||
|
||||
// CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz
|
||||
clock_configure(clk_rtc,
|
||||
0, // No GLMUX
|
||||
CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
|
||||
48 * MHZ,
|
||||
46875);
|
||||
|
||||
// CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable
|
||||
// Normally choose clk_sys or clk_usb
|
||||
clock_configure(clk_peri,
|
||||
0,
|
||||
CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS,
|
||||
125 * MHZ,
|
||||
125 * MHZ);
|
||||
}
|
||||
|
||||
/// \tag::clock_get_hz[]
|
||||
uint32_t clock_get_hz(enum clock_index clk_index) {
|
||||
return configured_freq[clk_index];
|
||||
}
|
||||
/// \end::clock_get_hz[]
|
||||
|
||||
void clock_set_reported_hz(enum clock_index clk_index, uint hz) {
|
||||
configured_freq[clk_index] = hz;
|
||||
}
|
||||
|
||||
/// \tag::frequency_count_khz[]
|
||||
uint32_t frequency_count_khz(uint src) {
|
||||
fc_hw_t *fc = &clocks_hw->fc0;
|
||||
|
||||
// If frequency counter is running need to wait for it. It runs even if the source is NULL
|
||||
while(fc->status & CLOCKS_FC0_STATUS_RUNNING_BITS) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
|
||||
// Set reference freq
|
||||
fc->ref_khz = clock_get_hz(clk_ref) / 1000;
|
||||
|
||||
// FIXME: Don't pick random interval. Use best interval
|
||||
fc->interval = 10;
|
||||
|
||||
// No min or max
|
||||
fc->min_khz = 0;
|
||||
fc->max_khz = 0xffffffff;
|
||||
|
||||
// Set SRC which automatically starts the measurement
|
||||
fc->src = src;
|
||||
|
||||
while(!(fc->status & CLOCKS_FC0_STATUS_DONE_BITS)) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
|
||||
// Return the result
|
||||
return fc->result >> CLOCKS_FC0_RESULT_KHZ_LSB;
|
||||
}
|
||||
/// \end::frequency_count_khz[]
|
||||
|
||||
static void clocks_handle_resus(void) {
|
||||
// Set clk_sys back to the ref clock rather than it being forced to clk_ref
|
||||
// by resus. Call the user's resus callback if they have set one
|
||||
|
||||
// CLK SYS = CLK_REF. Must be running for this code to be running
|
||||
uint clk_ref_freq = clock_get_hz(clk_ref);
|
||||
clock_configure(clk_sys,
|
||||
CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF,
|
||||
0,
|
||||
clk_ref_freq,
|
||||
clk_ref_freq);
|
||||
|
||||
// Assert we have been resussed
|
||||
assert(clocks_hw->resus.status & CLOCKS_CLK_SYS_RESUS_STATUS_RESUSSED_BITS);
|
||||
|
||||
// Now we have fixed clk_sys we can safely remove the resus
|
||||
hw_set_bits(&clocks_hw->resus.ctrl, CLOCKS_CLK_SYS_RESUS_CTRL_CLEAR_BITS);
|
||||
hw_clear_bits(&clocks_hw->resus.ctrl, CLOCKS_CLK_SYS_RESUS_CTRL_CLEAR_BITS);
|
||||
|
||||
// Now we should no longer be resussed
|
||||
assert(!(clocks_hw->resus.status & CLOCKS_CLK_SYS_RESUS_STATUS_RESUSSED_BITS));
|
||||
|
||||
// Call the user's callback to notify them of the resus event
|
||||
if (_resus_callback) {
|
||||
_resus_callback();
|
||||
}
|
||||
}
|
||||
|
||||
static void clocks_irq_handler(void) {
|
||||
// Clocks interrupt handler. Only resus but handle irq
|
||||
// defensively just in case.
|
||||
uint32_t ints = clocks_hw->ints;
|
||||
|
||||
if (ints & CLOCKS_INTE_CLK_SYS_RESUS_BITS) {
|
||||
ints &= ~CLOCKS_INTE_CLK_SYS_RESUS_BITS;
|
||||
clocks_handle_resus();
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (ints) {
|
||||
panic("Unexpected clocks irq\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void clocks_enable_resus(resus_callback_t resus_callback) {
|
||||
// Restart clk_sys if it is stopped by forcing it
|
||||
// to the default source of clk_ref. If clk_ref stops running this will
|
||||
// not work.
|
||||
|
||||
// Store user's resus callback
|
||||
_resus_callback = resus_callback;
|
||||
|
||||
irq_set_exclusive_handler(CLOCKS_IRQ, clocks_irq_handler);
|
||||
|
||||
// Enable the resus interrupt in clocks
|
||||
clocks_hw->inte = CLOCKS_INTE_CLK_SYS_RESUS_BITS;
|
||||
|
||||
// Enable the clocks irq
|
||||
irq_set_enabled(CLOCKS_IRQ, true);
|
||||
|
||||
// 2 * clk_ref freq / clk_sys_min_freq;
|
||||
// assume clk_ref is 3MHz and we want clk_sys to be no lower than 1MHz
|
||||
uint timeout = 2 * 3 * 1;
|
||||
|
||||
// Enable resus with the maximum timeout
|
||||
clocks_hw->resus.ctrl = CLOCKS_CLK_SYS_RESUS_CTRL_ENABLE_BITS | timeout;
|
||||
}
|
||||
|
||||
void clock_gpio_init(uint gpio, uint src, uint div) {
|
||||
// Bit messy but it's as much code to loop through a lookup
|
||||
// table. The sources for each gpout generators are the same
|
||||
// so just call with the sources from GP0
|
||||
uint gpclk = 0;
|
||||
if (gpio == 21) gpclk = clk_gpout0;
|
||||
else if (gpio == 23) gpclk = clk_gpout1;
|
||||
else if (gpio == 24) gpclk = clk_gpout2;
|
||||
else if (gpio == 26) gpclk = clk_gpout3;
|
||||
else {
|
||||
invalid_params_if(CLOCKS, true);
|
||||
}
|
||||
|
||||
// Set up the gpclk generator
|
||||
clocks_hw->clk[gpclk].ctrl = (src << CLOCKS_CLK_GPOUT0_CTRL_AUXSRC_LSB) |
|
||||
CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS;
|
||||
clocks_hw->clk[gpclk].div = div << CLOCKS_CLK_GPOUT0_DIV_INT_LSB;
|
||||
|
||||
// Set gpio pin to gpclock function
|
||||
gpio_set_function(gpio, GPIO_FUNC_GPCK);
|
||||
}
|
||||
|
||||
static const uint8_t gpin0_src[CLK_COUNT] = {
|
||||
CLOCKS_CLK_GPOUT0_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0, // CLK_GPOUT0
|
||||
CLOCKS_CLK_GPOUT1_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0, // CLK_GPOUT1
|
||||
CLOCKS_CLK_GPOUT2_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0, // CLK_GPOUT2
|
||||
CLOCKS_CLK_GPOUT3_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0, // CLK_GPOUT3
|
||||
CLOCKS_CLK_REF_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0, // CLK_REF
|
||||
CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0, // CLK_SYS
|
||||
CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0, // CLK_PERI
|
||||
CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0, // CLK_USB
|
||||
CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0, // CLK_ADC
|
||||
CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0, // CLK_RTC
|
||||
};
|
||||
|
||||
// Assert GPIN1 is GPIN0 + 1
|
||||
static_assert(CLOCKS_CLK_GPOUT0_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 == (CLOCKS_CLK_GPOUT0_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 + 1), "hw mismatch");
|
||||
static_assert(CLOCKS_CLK_GPOUT1_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 == (CLOCKS_CLK_GPOUT1_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 + 1), "hw mismatch");
|
||||
static_assert(CLOCKS_CLK_GPOUT2_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 == (CLOCKS_CLK_GPOUT2_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 + 1), "hw mismatch");
|
||||
static_assert(CLOCKS_CLK_GPOUT3_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 == (CLOCKS_CLK_GPOUT3_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 + 1), "hw mismatch");
|
||||
static_assert(CLOCKS_CLK_REF_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 == (CLOCKS_CLK_REF_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 + 1), "hw mismatch");
|
||||
static_assert(CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 == (CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 + 1), "hw mismatch");
|
||||
static_assert(CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 == (CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 + 1), "hw mismatch");
|
||||
static_assert(CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 == (CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 + 1), "hw mismatch");
|
||||
static_assert(CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 == (CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 + 1), "hw mismatch");
|
||||
static_assert(CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 == (CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 + 1), "hw mismatch");
|
||||
|
||||
bool clock_configure_gpin(enum clock_index clk_index, uint gpio, uint32_t src_freq, uint32_t freq) {
|
||||
// Configure a clock to run from a GPIO input
|
||||
uint gpin = 0;
|
||||
if (gpio == 20) gpin = 0;
|
||||
else if (gpio == 22) gpin = 1;
|
||||
else {
|
||||
invalid_params_if(CLOCKS, true);
|
||||
}
|
||||
|
||||
// Work out sources. GPIN is always an auxsrc
|
||||
uint src = 0;
|
||||
|
||||
// GPIN1 == GPIN0 + 1
|
||||
uint auxsrc = gpin0_src[clk_index] + gpin;
|
||||
|
||||
if (has_glitchless_mux(clk_index)) {
|
||||
// AUX src is always 1
|
||||
src = 1;
|
||||
}
|
||||
|
||||
// Set the GPIO function
|
||||
gpio_set_function(gpio, GPIO_FUNC_GPCK);
|
||||
|
||||
// Now we have the src, auxsrc, and configured the gpio input
|
||||
// call clock configure to run the clock from a gpio
|
||||
return clock_configure(clk_index, src, auxsrc, src_freq, freq);
|
||||
}
|
194
src/rp2_common/hardware_clocks/include/hardware/clocks.h
Normal file
194
src/rp2_common/hardware_clocks/include/hardware/clocks.h
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_CLOCKS_H_
|
||||
#define _HARDWARE_CLOCKS_H_
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/clocks.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file hardware/clocks.h
|
||||
* \defgroup hardware_clocks hardware_clocks
|
||||
*
|
||||
* Clock Management API
|
||||
*
|
||||
* This API provides a high level interface to the clock functions.
|
||||
*
|
||||
* The clocks block provides independent clocks to on-chip and external components. It takes inputs from a variety of clock
|
||||
* sources allowing the user to trade off performance against cost, board area and power consumption. From these sources
|
||||
* it uses multiple clock generators to provide the required clocks. This architecture allows the user flexibility to start and
|
||||
* stop clocks independently and to vary some clock frequencies whilst maintaining others at their optimum frequencies
|
||||
*
|
||||
* Please refer to the datasheet for more details on the RP2040 clocks.
|
||||
*
|
||||
* The clock source depends on which clock you are attempting to configure. The first table below shows main clock sources. If
|
||||
* you are not setting the Reference clock or the System clock, or you are specifying that one of those two will be using an auxiliary
|
||||
* clock source, then you will need to use one of the entries from the subsequent tables.
|
||||
*
|
||||
* **Main Clock Sources**
|
||||
*
|
||||
* Source | Reference Clock | System Clock
|
||||
* -------|-----------------|---------
|
||||
* ROSC | CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH | |
|
||||
* Auxiliary | CLOCKS_CLK_REF_CTRL_SRC_VALUE_CLKSRC_CLK_REF_AUX | CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX
|
||||
* XOSC | CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC | |
|
||||
* Reference | | CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF
|
||||
*
|
||||
* **Auxiliary Clock Sources**
|
||||
*
|
||||
* The auxiliary clock sources available for use in the configure function depend on which clock is being configured. The following table
|
||||
* describes the available values that can be used. Note that for clk_gpout[x], x can be 0-3.
|
||||
*
|
||||
*
|
||||
* Aux Source | clk_gpout[x] | clk_ref | clk_sys
|
||||
* -----------|------------|---------|--------
|
||||
* System PLL | CLOCKS_CLK_GPOUTx_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS | | CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS
|
||||
* GPIO in 0 | CLOCKS_CLK_GPOUTx_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 | CLOCKS_CLK_REF_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 | CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0
|
||||
* GPIO in 1 | CLOCKS_CLK_GPOUTx_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 | CLOCKS_CLK_REF_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 | CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1
|
||||
* USB PLL | CLOCKS_CLK_GPOUTx_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB | CLOCKS_CLK_REF_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB| CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB
|
||||
* ROSC | CLOCKS_CLK_GPOUTx_CTRL_AUXSRC_VALUE_ROSC_CLKSRC | | CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_ROSC_CLKSRC
|
||||
* XOSC | CLOCKS_CLK_GPOUTx_CTRL_AUXSRC_VALUE_XOSC_CLKSRC | | CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_ROSC_CLKSRC
|
||||
* System clock | CLOCKS_CLK_GPOUTx_CTRL_AUXSRC_VALUE_CLK_SYS | | |
|
||||
* USB Clock | CLOCKS_CLK_GPOUTx_CTRL_AUXSRC_VALUE_CLK_USB | | |
|
||||
* ADC clock | CLOCKS_CLK_GPOUTx_CTRL_AUXSRC_VALUE_CLK_ADC | | |
|
||||
* RTC Clock | CLOCKS_CLK_GPOUTx_CTRL_AUXSRC_VALUE_CLK_RTC | | |
|
||||
* Ref clock | CLOCKS_CLK_GPOUTx_CTRL_AUXSRC_VALUE_CLK_REF | | |
|
||||
*
|
||||
* Aux Source | clk_peri | clk_usb | clk_adc
|
||||
* -----------|-----------|---------|--------
|
||||
* System PLL | CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS | CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS | CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS
|
||||
* GPIO in 0 | CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 | CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0 | CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0
|
||||
* GPIO in 1 | CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 | CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1 | CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1
|
||||
* USB PLL | CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB | CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB | CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB
|
||||
* ROSC | CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH | CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH | CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH
|
||||
* XOSC | CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_XOSC_CLKSRC | CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_XOSC_CLKSRC | CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC
|
||||
* System clock | CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS | | |
|
||||
*
|
||||
* Aux Source | clk_rtc
|
||||
* -----------|----------
|
||||
* System PLL | CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS
|
||||
* GPIO in 0 | CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN0
|
||||
* GPIO in 1 | CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_GPIN1
|
||||
* USB PLL | CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB
|
||||
* ROSC | CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH
|
||||
* XOSC | CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC
|
||||
|
||||
*
|
||||
* \section clock_example Example
|
||||
* \addtogroup hardware_clocks
|
||||
* \include hello_48MHz.c
|
||||
*/
|
||||
|
||||
#define KHZ 1000
|
||||
#define MHZ 1000000
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_CLOCKS, Enable/disable assertions in the clocks module, type=bool, default=0, group=hardware_clocks
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_CLOCKS
|
||||
#define PARAM_ASSERTIONS_ENABLED_CLOCKS 0
|
||||
#endif
|
||||
|
||||
/*! \brief Initialise the clock hardware
|
||||
* \ingroup hardware_clocks
|
||||
*
|
||||
* Must be called before any other clock function.
|
||||
*/
|
||||
void clocks_init();
|
||||
|
||||
/*! \brief Configure the specified clock
|
||||
* \ingroup hardware_clocks
|
||||
*
|
||||
* See the tables in the description for details on the possible values for clock sources.
|
||||
*
|
||||
* \param clk_index The clock to configure
|
||||
* \param src The main clock source, can be 0.
|
||||
* \param auxsrc The auxiliary clock source, which depends on which clock is being set. Can be 0
|
||||
* \param src_freq Frequency of the input clock source
|
||||
* \param freq Requested frequency
|
||||
*/
|
||||
bool clock_configure(enum clock_index clk_index, uint32_t src, uint32_t auxsrc, uint32_t src_freq, uint32_t freq);
|
||||
|
||||
/*! \brief Stop the specified clock
|
||||
* \ingroup hardware_clocks
|
||||
*
|
||||
* \param clk_index The clock to stop
|
||||
*/
|
||||
void clock_stop(enum clock_index clk_index);
|
||||
|
||||
/*! \brief Get the current frequency of the specified clock
|
||||
* \ingroup hardware_clocks
|
||||
*
|
||||
* \param clk_index Clock
|
||||
* \return Clock frequency in Hz
|
||||
*/
|
||||
uint32_t clock_get_hz(enum clock_index clk_index);
|
||||
|
||||
/*! \brief Measure a clocks frequency using the Frequency counter.
|
||||
* \ingroup hardware_clocks
|
||||
*
|
||||
* Uses the inbuilt frequency counter to measure the specified clocks frequency.
|
||||
* Currently, this function is accurate to +-1KHz. See the datasheet for more details.
|
||||
*/
|
||||
uint32_t frequency_count_khz(uint src);
|
||||
|
||||
/*! \brief Set the "current frequency" of the clock as reported by clock_get_hz without actually changing the clock
|
||||
* \ingroup hardware_clocks
|
||||
*
|
||||
* \see clock_get_hz
|
||||
*/
|
||||
void clock_set_reported_hz(enum clock_index clk_index, uint hz);
|
||||
|
||||
/// \tag::frequency_count_mhz[]
|
||||
static inline float frequency_count_mhz(uint src) {
|
||||
return ((float) (frequency_count_khz(src))) / KHZ;
|
||||
}
|
||||
/// \end::frequency_count_mhz[]
|
||||
|
||||
/*! \brief Resus callback function type.
|
||||
* \ingroup hardware_clocks
|
||||
*
|
||||
* User provided callback for a resus event (when clk_sys is stopped by the programmer and is restarted for them).
|
||||
*/
|
||||
typedef void (*resus_callback_t)(void);
|
||||
|
||||
/*! \brief Enable the resus function. Restarts clk_sys if it is accidentally stopped.
|
||||
* \ingroup hardware_clocks
|
||||
*
|
||||
* The resuscitate function will restart the system clock if it falls below a certain speed (or stops). This
|
||||
* could happen if the clock source the system clock is running from stops. For example if a PLL is stopped.
|
||||
*
|
||||
* \param resus_callback a function pointer provided by the user to call if a resus event happens.
|
||||
*/
|
||||
void clocks_enable_resus(resus_callback_t resus_callback);
|
||||
|
||||
/*! \brief Output an optionally divided clock to the specified gpio pin.
|
||||
* \ingroup hardware_clocks
|
||||
*
|
||||
* \param gpio The GPIO pin to output the clock to. Valid GPIOs are: 21, 23, 24, 26. These GPIOs are connected to the GPOUT0-3 clock generators.
|
||||
* \param src The source clock. See the register field CLOCKS_CLK_GPOUT0_CTRL_AUXSRC for a full list. The list is the same for each GPOUT clock generator.
|
||||
* \param div The amount to divide the source clock by. This is useful to not overwhelm the GPIO pin with a fast clock.
|
||||
*/
|
||||
void clock_gpio_init(uint gpio, uint src, uint div);
|
||||
|
||||
/*! \brief Configure a clock to come from a gpio input
|
||||
* \ingroup hardware_clocks
|
||||
*
|
||||
* \param clk_index The clock to configure
|
||||
* \param gpio The GPIO pin to run the clock from. Valid GPIOs are: 20 and 22.
|
||||
* \param src_freq Frequency of the input clock source
|
||||
* \param freq Requested frequency
|
||||
*/
|
||||
bool clock_configure_gpin(enum clock_index clk_index, uint gpio, uint32_t src_freq, uint32_t freq);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
37
src/rp2_common/hardware_clocks/scripts/vcocalc.py
Executable file
37
src/rp2_common/hardware_clocks/scripts/vcocalc.py
Executable file
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="PLL parameter calculator")
|
||||
parser.add_argument("--input", "-i", default=12, help="Input (reference) frequency. Default 12 MHz", type=float)
|
||||
parser.add_argument("--vco-max", default=1600, help="Override maximum VCO frequency. Default 1600 MHz", type=float)
|
||||
parser.add_argument("--vco-min", default=400, help="Override minimum VCO frequency. Default 400 MHz", type=float)
|
||||
parser.add_argument("--low-vco", "-l", action="store_true", help="Use a lower VCO frequency when possible. This reduces power consumption, at the cost of increased jitter")
|
||||
parser.add_argument("output", help="Output frequency in MHz.", type=float)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Fixed hardware parameters
|
||||
fbdiv_range = range(16, 320 + 1)
|
||||
postdiv_range = range(1, 7 + 1)
|
||||
|
||||
best = (0, 0, 0, 0)
|
||||
best_margin = args.output
|
||||
|
||||
for fbdiv in (fbdiv_range if args.low_vco else reversed(fbdiv_range)):
|
||||
vco = args.input * fbdiv
|
||||
if vco < args.vco_min or vco > args.vco_max:
|
||||
continue
|
||||
# pd1 is inner loop so that we prefer higher ratios of pd1:pd2
|
||||
for pd2 in postdiv_range:
|
||||
for pd1 in postdiv_range:
|
||||
out = vco / pd1 / pd2
|
||||
margin = abs(out - args.output)
|
||||
if margin < best_margin:
|
||||
best = (out, fbdiv, pd1, pd2)
|
||||
best_margin = margin
|
||||
|
||||
print("Requested: {} MHz".format(args.output))
|
||||
print("Achieved: {} MHz".format(best[0]))
|
||||
print("FBDIV: {} (VCO = {} MHz)".format(best[1], args.input * best[1]))
|
||||
print("PD1: {}".format(best[2]))
|
||||
print("PD2: {}".format(best[3]))
|
4
src/rp2_common/hardware_divider/CMakeLists.txt
Normal file
4
src/rp2_common/hardware_divider/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
add_library(hardware_divider INTERFACE)
|
||||
target_sources(hardware_divider INTERFACE ${CMAKE_CURRENT_LIST_DIR}/divider.S)
|
||||
target_include_directories(hardware_divider INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
|
||||
target_link_libraries(hardware_divider INTERFACE hardware_structs)
|
76
src/rp2_common/hardware_divider/divider.S
Normal file
76
src/rp2_common/hardware_divider/divider.S
Normal file
@ -0,0 +1,76 @@
|
||||
#include "pico/asm_helper.S"
|
||||
#include "hardware/regs/addressmap.h"
|
||||
#include "hardware/regs/sio.h"
|
||||
|
||||
.syntax unified
|
||||
.cpu cortex-m0plus
|
||||
.thumb
|
||||
|
||||
// tag::hw_div_s32[]
|
||||
|
||||
.macro __divider_delay
|
||||
// delay 8 cycles
|
||||
b 1f
|
||||
1: b 1f
|
||||
1: b 1f
|
||||
1: b 1f
|
||||
1:
|
||||
.endm
|
||||
|
||||
.align 2
|
||||
|
||||
regular_func_with_section hw_divider_divmod_s32
|
||||
ldr r3, =(SIO_BASE)
|
||||
str r0, [r3, #SIO_DIV_SDIVIDEND_OFFSET]
|
||||
str r1, [r3, #SIO_DIV_SDIVISOR_OFFSET]
|
||||
__divider_delay
|
||||
// return 64 bit value so we can efficiently return both (note quotient must be read last)
|
||||
ldr r1, [r3, #SIO_DIV_REMAINDER_OFFSET]
|
||||
ldr r0, [r3, #SIO_DIV_QUOTIENT_OFFSET]
|
||||
bx lr
|
||||
// end::hw_div_s32[]
|
||||
|
||||
.align 2
|
||||
|
||||
// tag::hw_div_u32[]
|
||||
regular_func_with_section hw_divider_divmod_u32
|
||||
ldr r3, =(SIO_BASE)
|
||||
str r0, [r3, #SIO_DIV_UDIVIDEND_OFFSET]
|
||||
str r1, [r3, #SIO_DIV_UDIVISOR_OFFSET]
|
||||
__divider_delay
|
||||
// return 64 bit value so we can efficiently return both (note quotient must be read last)
|
||||
ldr r1, [r3, #SIO_DIV_REMAINDER_OFFSET]
|
||||
ldr r0, [r3, #SIO_DIV_QUOTIENT_OFFSET]
|
||||
bx lr
|
||||
// end::hw_div_u32[]
|
||||
|
||||
#if SIO_DIV_CSR_READY_LSB == 0
|
||||
.equ SIO_DIV_CSR_READY_SHIFT_FOR_CARRY, 1
|
||||
#else
|
||||
#error need to change SHIFT above
|
||||
#endif
|
||||
|
||||
regular_func_with_section hw_divider_save_state
|
||||
push {r4, r5, lr}
|
||||
ldr r5, =SIO_BASE
|
||||
ldr r4, [r5, #SIO_DIV_CSR_OFFSET]
|
||||
# wait for results as we can't save signed-ness of operation
|
||||
1:
|
||||
lsrs r4, #SIO_DIV_CSR_READY_SHIFT_FOR_CARRY
|
||||
bcc 1b
|
||||
ldr r1, [r5, #SIO_DIV_UDIVIDEND_OFFSET]
|
||||
ldr r2, [r5, #SIO_DIV_UDIVISOR_OFFSET]
|
||||
ldr r3, [r5, #SIO_DIV_REMAINDER_OFFSET]
|
||||
ldr r4, [r5, #SIO_DIV_QUOTIENT_OFFSET]
|
||||
stmia r0!, {r1-r4}
|
||||
pop {r4, r5, pc}
|
||||
|
||||
regular_func_with_section hw_divider_restore_state
|
||||
push {r4, r5, lr}
|
||||
ldr r5, =SIO_BASE
|
||||
ldmia r0!, {r1-r4}
|
||||
str r1, [r5, #SIO_DIV_UDIVIDEND_OFFSET]
|
||||
str r2, [r5, #SIO_DIV_UDIVISOR_OFFSET]
|
||||
str r3, [r5, #SIO_DIV_REMAINDER_OFFSET]
|
||||
str r4, [r5, #SIO_DIV_QUOTIENT_OFFSET]
|
||||
pop {r4, r5, pc}
|
395
src/rp2_common/hardware_divider/include/hardware/divider.h
Normal file
395
src/rp2_common/hardware_divider/include/hardware/divider.h
Normal file
@ -0,0 +1,395 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_DIVIDER_H
|
||||
#define _HARDWARE_DIVIDER_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/sio.h"
|
||||
|
||||
/** \file hardware/divider.h
|
||||
* \defgroup hardware_divider hardware_divider
|
||||
*
|
||||
* Low-level hardware-divider access
|
||||
*
|
||||
* The SIO contains an 8-cycle signed/unsigned divide/modulo circuit, per core. Calculation is started by writing a dividend
|
||||
* and divisor to the two argument registers, DIVIDEND and DIVISOR. The divider calculates the quotient / and remainder % of
|
||||
* this division over the next 8 cycles, and on the 9th cycle the results can be read from the two result registers
|
||||
* DIV_QUOTIENT and DIV_REMAINDER. A 'ready' bit in register DIV_CSR can be polled to wait for the calculation to
|
||||
* complete, or software can insert a fixed 8-cycle delay
|
||||
*
|
||||
* This header provides low level macros and inline functions for accessing the hardware dividers directly,
|
||||
* and perhaps most usefully performing asynchronous divides. These functions however do not follow the regular
|
||||
* Pico SDK conventions for saving/restoring the divider state, so are not generally safe to call from interrupt handlers
|
||||
*
|
||||
* The pico_divider library provides a more user friendly set of APIs over the divider (and support for
|
||||
* 64 bit divides), and of course by default regular C language integer divisions are redirected through that library, meaning
|
||||
* you can just use C level `/` and `%` operators and gain the benefits of the fast hardware divider.
|
||||
*
|
||||
* @see pico_divider
|
||||
*
|
||||
* \subsection divider_example Example
|
||||
* \addtogroup hardware_divider
|
||||
* \include hello_divider.c
|
||||
*/
|
||||
|
||||
typedef uint64_t divmod_result_t;
|
||||
|
||||
/*! \brief Start a signed asynchronous divide
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Start a divide of the specified signed parameters. You should wait for 8 cycles (__div_pause()) or wait for the ready bit to be set
|
||||
* (hw_divider_wait_ready()) prior to reading the results.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
*/
|
||||
static inline void hw_divider_divmod_s32_start(int32_t a, int32_t b) {
|
||||
check_hw_layout( sio_hw_t, div_sdividend, SIO_DIV_SDIVIDEND_OFFSET);
|
||||
sio_hw->div_sdividend = a;
|
||||
sio_hw->div_sdivisor = b;
|
||||
}
|
||||
|
||||
/*! \brief Start an unsigned asynchronous divide
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Start a divide of the specified unsigned parameters. You should wait for 8 cycles (__div_pause()) or wait for the ready bit to be set
|
||||
* (hw_divider_wait_ready()) prior to reading the results.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
*/
|
||||
static inline void hw_divider_divmod_u32_start(uint32_t a, uint32_t b) {
|
||||
check_hw_layout(
|
||||
sio_hw_t, div_udividend, SIO_DIV_UDIVIDEND_OFFSET);
|
||||
sio_hw->div_udividend = a;
|
||||
sio_hw->div_udivisor = b;
|
||||
}
|
||||
|
||||
/*! \brief Wait for a divide to complete
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Wait for a divide to complete
|
||||
*/
|
||||
static inline void hw_divider_wait_ready() {
|
||||
// this is #1 in lsr below
|
||||
static_assert(SIO_DIV_CSR_READY_BITS == 1, "");
|
||||
|
||||
// we use one less register and instruction than gcc which uses a TST instruction
|
||||
|
||||
uint32_t tmp; // allow compiler to pick scratch register
|
||||
asm volatile (
|
||||
"hw_divider_result_loop_%=:"
|
||||
"ldr %0, [%1, %2]\n\t"
|
||||
"lsr %0, #1\n\t"
|
||||
"bcc hw_divider_result_loop_%=\n\t"
|
||||
: "=&l" (tmp)
|
||||
: "l" (sio_hw), "I" (SIO_DIV_CSR_OFFSET)
|
||||
:
|
||||
);
|
||||
}
|
||||
|
||||
/*! \brief Return result of HW divide, nowait
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* \note This is UNSAFE in that the calculation may not have been completed.
|
||||
*
|
||||
* \return Current result. Most significant 32 bits are the remainder, lower 32 bits are the quotient.
|
||||
*/
|
||||
static inline divmod_result_t hw_divider_result_nowait() {
|
||||
// as ugly as this looks it is actually quite efficient
|
||||
divmod_result_t rc = (((divmod_result_t) sio_hw->div_remainder) << 32u) | sio_hw->div_quotient;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! \brief Return result of last asynchronous HW divide
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* This function waits for the result to be ready by calling hw_divider_wait_ready().
|
||||
*
|
||||
* \return Current result. Most significant 32 bits are the remainder, lower 32 bits are the quotient.
|
||||
*/
|
||||
static inline divmod_result_t hw_divider_result_wait() {
|
||||
hw_divider_wait_ready();
|
||||
return hw_divider_result_nowait();
|
||||
}
|
||||
|
||||
/*! \brief Return result of last asynchronous HW divide, unsigned quotient only
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* This function waits for the result to be ready by calling hw_divider_wait_ready().
|
||||
*
|
||||
* \return Current unsigned quotient result.
|
||||
*/
|
||||
static inline uint32_t hw_divider_u32_quotient_wait() {
|
||||
hw_divider_wait_ready();
|
||||
return sio_hw->div_quotient;
|
||||
}
|
||||
|
||||
/*! \brief Return result of last asynchronous HW divide, signed quotient only
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* This function waits for the result to be ready by calling hw_divider_wait_ready().
|
||||
*
|
||||
* \return Current signed quotient result.
|
||||
*/
|
||||
static inline int32_t hw_divider_s32_quotient_wait() {
|
||||
hw_divider_wait_ready();
|
||||
return sio_hw->div_quotient;
|
||||
}
|
||||
|
||||
/*! \brief Return result of last asynchronous HW divide, unsigned remainder only
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* This function waits for the result to be ready by calling hw_divider_wait_ready().
|
||||
*
|
||||
* \return Current unsigned remainder result.
|
||||
*/
|
||||
static inline uint32_t hw_divider_u32_remainder_wait() {
|
||||
hw_divider_wait_ready();
|
||||
int32_t rc = sio_hw->div_remainder;
|
||||
sio_hw->div_quotient; // must read quotient to cooperate with other SDK code
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! \brief Return result of last asynchronous HW divide, signed remainder only
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* This function waits for the result to be ready by calling hw_divider_wait_ready().
|
||||
*
|
||||
* \return Current remainder results.
|
||||
*/
|
||||
static inline int32_t hw_divider_s32_remainder_wait() {
|
||||
hw_divider_wait_ready();
|
||||
int32_t rc = sio_hw->div_remainder;
|
||||
sio_hw->div_quotient; // must read quotient to cooperate with other SDK code
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! \brief Do a signed HW divide and wait for result
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Divide \p a by \p b, wait for calculation to complete, return result as a fixed point 32p32 value.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
* \return Results of divide as a 32p32 fixed point value.
|
||||
*/
|
||||
divmod_result_t hw_divider_divmod_s32(int32_t a, int32_t b);
|
||||
|
||||
/*! \brief Do an unsigned HW divide and wait for result
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Divide \p a by \p b, wait for calculation to complete, return result as a fixed point 32p32 value.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
* \return Results of divide as a 32p32 fixed point value.
|
||||
*/
|
||||
divmod_result_t hw_divider_divmod_u32(uint32_t a, uint32_t b);
|
||||
|
||||
/*! \brief Efficient extraction of unsigned quotient from 32p32 fixed point
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* \param r 32p32 fixed point value.
|
||||
* \return Unsigned quotient
|
||||
*/
|
||||
inline static uint32_t to_quotient_u32(divmod_result_t r) {
|
||||
return (uint32_t) r;
|
||||
}
|
||||
|
||||
/*! \brief Efficient extraction of signed quotient from 32p32 fixed point
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* \param r 32p32 fixed point value.
|
||||
* \return Unsigned quotient
|
||||
*/
|
||||
inline static int32_t to_quotient_s32(divmod_result_t r) {
|
||||
return (int32_t)(uint32_t)r;
|
||||
}
|
||||
|
||||
/*! \brief Efficient extraction of unsigned remainder from 32p32 fixed point
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* \param r 32p32 fixed point value.
|
||||
* \return Unsigned remainder
|
||||
*
|
||||
* \note On Arm this is just a 32 bit register move or a nop
|
||||
*/
|
||||
inline static uint32_t to_remainder_u32(divmod_result_t r) {
|
||||
return (uint32_t)(r >> 32u);
|
||||
}
|
||||
|
||||
/*! \brief Efficient extraction of signed remainder from 32p32 fixed point
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* \param r 32p32 fixed point value.
|
||||
* \return Signed remainder
|
||||
*
|
||||
* \note On arm this is just a 32 bit register move or a nop
|
||||
*/
|
||||
inline static int32_t to_remainder_s32(divmod_result_t r) {
|
||||
return (int32_t)(r >> 32u);
|
||||
}
|
||||
|
||||
/*! \brief Do an unsigned HW divide, wait for result, return quotient
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Divide \p a by \p b, wait for calculation to complete, return quotient.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
* \return Quotient results of the divide
|
||||
*/
|
||||
static inline uint32_t hw_divider_u32_quotient(uint32_t a, uint32_t b) {
|
||||
return to_quotient_u32(hw_divider_divmod_u32(a, b));
|
||||
}
|
||||
|
||||
/*! \brief Do an unsigned HW divide, wait for result, return remainder
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Divide \p a by \p b, wait for calculation to complete, return remainder.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
* \return Remainder results of the divide
|
||||
*/
|
||||
static inline uint32_t hw_divider_u32_remainder(uint32_t a, uint32_t b) {
|
||||
return to_remainder_u32(hw_divider_divmod_u32(a, b));
|
||||
}
|
||||
|
||||
/*! \brief Do a signed HW divide, wait for result, return quotient
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Divide \p a by \p b, wait for calculation to complete, return quotient.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
* \return Quotient results of the divide
|
||||
*/
|
||||
static inline int32_t hw_divider_quotient_s32(int32_t a, int32_t b) {
|
||||
return to_quotient_s32(hw_divider_divmod_s32(a, b));
|
||||
}
|
||||
|
||||
/*! \brief Do a signed HW divide, wait for result, return remainder
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Divide \p a by \p b, wait for calculation to complete, return remainder.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
* \return Remainder results of the divide
|
||||
*/
|
||||
static inline int32_t hw_divider_remainder_s32(int32_t a, int32_t b) {
|
||||
return to_remainder_s32(hw_divider_divmod_s32(a, b));
|
||||
}
|
||||
|
||||
/*! \brief Pause for exact amount of time needed for a asynchronous divide to complete
|
||||
* \ingroup hardware_divider
|
||||
*/
|
||||
static inline void hw_divider_pause() {
|
||||
asm volatile (
|
||||
"b _1_%=\n"
|
||||
"_1_%=:\n"
|
||||
"b _2_%=\n"
|
||||
"_2_%=:\n"
|
||||
"b _3_%=\n"
|
||||
"_3_%=:\n"
|
||||
"b _4_%=\n"
|
||||
"_4_%=:\n"
|
||||
:: : );
|
||||
}
|
||||
|
||||
/*! \brief Do a hardware unsigned HW divide, wait for result, return quotient
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Divide \p a by \p b, wait for calculation to complete, return quotient.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
* \return Quotient result of the divide
|
||||
*/
|
||||
static inline uint32_t hw_divider_u32_quotient_inlined(uint32_t a, uint32_t b) {
|
||||
hw_divider_divmod_u32_start(a, b);
|
||||
hw_divider_pause();
|
||||
return sio_hw->div_quotient;
|
||||
}
|
||||
|
||||
/*! \brief Do a hardware unsigned HW divide, wait for result, return remainder
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Divide \p a by \p b, wait for calculation to complete, return remainder.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
* \return Remainder result of the divide
|
||||
*/
|
||||
static inline uint32_t hw_divider_u32_remainder_inlined(uint32_t a, uint32_t b) {
|
||||
hw_divider_divmod_u32_start(a, b);
|
||||
hw_divider_pause();
|
||||
int32_t rc = sio_hw->div_remainder;
|
||||
sio_hw->div_quotient; // must read quotient to cooperate with other SDK code
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*! \brief Do a hardware signed HW divide, wait for result, return quotient
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Divide \p a by \p b, wait for calculation to complete, return quotient.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
* \return Quotient result of the divide
|
||||
*/
|
||||
static inline int32_t hw_divider_s32_quotient_inlined(int32_t a, int32_t b) {
|
||||
hw_divider_divmod_s32_start(a, b);
|
||||
hw_divider_pause();
|
||||
return sio_hw->div_quotient;
|
||||
}
|
||||
|
||||
/*! \brief Do a hardware signed HW divide, wait for result, return remainder
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Divide \p a by \p b, wait for calculation to complete, return remainder.
|
||||
*
|
||||
* \param a The dividend
|
||||
* \param b The divisor
|
||||
* \return Remainder result of the divide
|
||||
*/
|
||||
static inline int32_t hw_divider_s32_remainder_inlined(int32_t a, int32_t b) {
|
||||
hw_divider_divmod_s32_start(a, b);
|
||||
hw_divider_pause();
|
||||
int32_t rc = sio_hw->div_remainder;
|
||||
sio_hw->div_quotient; // must read quotient to cooperate with other SDK code
|
||||
return rc;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t values[4];
|
||||
} hw_divider_state_t;
|
||||
|
||||
/*! \brief Save the calling cores hardware divider state
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Copy the current core's hardware divider state into the provided structure. This method
|
||||
* waits for the divider results to be stable, then copies them to memory.
|
||||
* They can be restored via hw_divider_restore_state()
|
||||
*
|
||||
* \param dest the location to store the divider state
|
||||
*/
|
||||
void hw_divider_save_state(hw_divider_state_t *dest);
|
||||
|
||||
/*! \brief Load a saved hardware divider state into the current core's hardware divider
|
||||
* \ingroup hardware_divider
|
||||
*
|
||||
* Copy the passed hardware divider state into the hardware divider.
|
||||
*
|
||||
* \param src the location to load the divider state from
|
||||
*/
|
||||
|
||||
void hw_divider_restore_state(hw_divider_state_t *src);
|
||||
|
||||
#endif // _HARDWARE_DIVIDER_H
|
2
src/rp2_common/hardware_dma/CMakeLists.txt
Normal file
2
src/rp2_common/hardware_dma/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
pico_simple_hardware_target(dma)
|
||||
target_link_libraries(hardware_dma INTERFACE hardware_claim)
|
68
src/rp2_common/hardware_dma/dma.c
Normal file
68
src/rp2_common/hardware_dma/dma.c
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/claim.h"
|
||||
|
||||
#define DMA_CHAN_STRIDE (DMA_CH1_CTRL_TRIG_OFFSET - DMA_CH0_CTRL_TRIG_OFFSET)
|
||||
check_hw_size(dma_channel_hw_t, DMA_CHAN_STRIDE);
|
||||
check_hw_layout(dma_hw_t, abort, DMA_CHAN_ABORT_OFFSET);
|
||||
|
||||
// sanity check
|
||||
static_assert(__builtin_offsetof(dma_hw_t, ch[0].ctrl_trig) == DMA_CH0_CTRL_TRIG_OFFSET, "hw mismatch");
|
||||
static_assert(__builtin_offsetof(dma_hw_t, ch[1].ctrl_trig) == DMA_CH1_CTRL_TRIG_OFFSET, "hw mismatch");
|
||||
|
||||
static_assert(NUM_DMA_CHANNELS <= 16, "");
|
||||
static uint16_t _claimed;
|
||||
|
||||
void dma_channel_claim(uint channel) {
|
||||
check_dma_channel_param(channel);
|
||||
hw_claim_or_assert((uint8_t *) &_claimed, channel, "DMA channel %d is already claimed");
|
||||
}
|
||||
|
||||
void dma_claim_mask(uint32_t mask) {
|
||||
for(uint i = 0; mask; i++, mask >>= 1u) {
|
||||
if (mask & 1u) dma_channel_claim(i);
|
||||
}
|
||||
}
|
||||
|
||||
void dma_channel_unclaim(uint channel) {
|
||||
check_dma_channel_param(channel);
|
||||
hw_claim_clear((uint8_t *) &_claimed, channel);
|
||||
}
|
||||
|
||||
int dma_claim_unused_channel(bool required) {
|
||||
return hw_claim_unused_from_range((uint8_t*)&_claimed, required, 0, NUM_DMA_CHANNELS-1, "No DMA channels are available");
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
void print_dma_ctrl(dma_channel_hw_t *channel) {
|
||||
uint32_t ctrl = channel->ctrl_trig;
|
||||
int rgsz = (ctrl & DMA_CH0_CTRL_TRIG_RING_SIZE_BITS) >> DMA_CH0_CTRL_TRIG_RING_SIZE_LSB;
|
||||
printf("(%08x) ber %d rer %d wer %d busy %d trq %d cto %d rgsl %d rgsz %d inw %d inr %d sz %d hip %d en %d",
|
||||
(uint) ctrl,
|
||||
ctrl & DMA_CH0_CTRL_TRIG_AHB_ERROR_BITS ? 1 : 0,
|
||||
ctrl & DMA_CH0_CTRL_TRIG_READ_ERROR_BITS ? 1 : 0,
|
||||
ctrl & DMA_CH0_CTRL_TRIG_WRITE_ERROR_BITS ? 1 : 0,
|
||||
ctrl & DMA_CH0_CTRL_TRIG_BUSY_BITS ? 1 : 0,
|
||||
(int) ((ctrl & DMA_CH0_CTRL_TRIG_TREQ_SEL_BITS) >> DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB),
|
||||
(int) ((ctrl & DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) >> DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB),
|
||||
ctrl & DMA_CH0_CTRL_TRIG_RING_SEL_BITS ? 1 : 0,
|
||||
rgsz ? (1 << rgsz) : 0,
|
||||
ctrl & DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS ? 1 : 0,
|
||||
ctrl & DMA_CH0_CTRL_TRIG_INCR_READ_BITS ? 1 : 0,
|
||||
1 << ((ctrl & DMA_CH0_CTRL_TRIG_DATA_SIZE_BITS) >> DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB),
|
||||
ctrl & DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_BITS ? 1 : 0,
|
||||
ctrl & DMA_CH0_CTRL_TRIG_EN_BITS ? 1 : 0);
|
||||
}
|
||||
|
||||
void check_dma_channel_param_impl(uint channel) {
|
||||
valid_params_if(DMA, channel < NUM_DMA_CHANNELS);
|
||||
}
|
||||
|
||||
#endif
|
591
src/rp2_common/hardware_dma/include/hardware/dma.h
Normal file
591
src/rp2_common/hardware_dma/include/hardware/dma.h
Normal file
@ -0,0 +1,591 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_DMA_H_
|
||||
#define _HARDWARE_DMA_H_
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/dma.h"
|
||||
#include "hardware/regs/dreq.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file hardware/dma.h
|
||||
* \defgroup hardware_dma hardware_dma
|
||||
*
|
||||
* DMA Controller API
|
||||
*
|
||||
* The RP2040 Direct Memory Access (DMA) master performs bulk data transfers on a processor’s
|
||||
* behalf. This leaves processors free to attend to other tasks, or enter low-power sleep states. The
|
||||
* data throughput of the DMA is also significantly higher than one of RP2040’s processors.
|
||||
*
|
||||
* The DMA can perform one read access and one write access, up to 32 bits in size, every clock cycle.
|
||||
* There are 12 independent channels, which each supervise a sequence of bus transfers, usually in
|
||||
* one of the following scenarios:
|
||||
*
|
||||
* * Memory to peripheral
|
||||
* * Peripheral to memory
|
||||
* * Memory to memory
|
||||
*/
|
||||
|
||||
// this is not defined in generated dreq.h
|
||||
#define DREQ_FORCE 63
|
||||
|
||||
/*! \brief Mark a dma channel as used
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* Method for cooperative claiming of hardware. Will cause a panic if the channel
|
||||
* is already claimed. Use of this method by libraries detects accidental
|
||||
* configurations that would fail in unpredictable ways.
|
||||
*
|
||||
* \param channel the dma channel
|
||||
*/
|
||||
void dma_channel_claim(uint channel);
|
||||
|
||||
/*! \brief Mark multiple dma channels as used
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* Method for cooperative claiming of hardware. Will cause a panic if any of the channels
|
||||
* are already claimed. Use of this method by libraries detects accidental
|
||||
* configurations that would fail in unpredictable ways.
|
||||
*
|
||||
* \param channel_mask Bitfield of all required channels to claim (bit 0 == channel 0, bit 1 == channel 1 etc)
|
||||
*/
|
||||
void dma_claim_mask(uint32_t channel_mask);
|
||||
|
||||
/*! \brief Mark a dma channel as no longer used
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* Method for cooperative claiming of hardware.
|
||||
*
|
||||
* \param channel the dma channel to release
|
||||
*/
|
||||
void dma_channel_unclaim(uint channel);
|
||||
|
||||
/*! \brief Claim a free dma channel
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param required if true the function will panic if none are available
|
||||
* \return the dma channel number or -1 if required was false, and none were free
|
||||
*/
|
||||
int dma_claim_unused_channel(bool required);
|
||||
|
||||
/** \brief DMA channel configuration
|
||||
* \defgroup channel_config channel_config
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* A DMA channel needs to be configured, these functions provide handy helpers to set up configuration
|
||||
* structures. See \ref dma_channel_config
|
||||
*
|
||||
*/
|
||||
|
||||
/*! \brief Enumeration of available DMA channel transfer sizes.
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* Names indicate the number of bits.
|
||||
*/
|
||||
enum dma_channel_transfer_size {
|
||||
DMA_SIZE_8 = 0, ///< Byte transfer (8 bits)
|
||||
DMA_SIZE_16 = 1, ///< Half word transfer (16 bits)
|
||||
DMA_SIZE_32 = 2 ///< Word transfer (32 bits)
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint32_t ctrl;
|
||||
} dma_channel_config;
|
||||
|
||||
/*! \brief Set DMA channel read increment
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* \param c Pointer to channel configuration data
|
||||
* \param incr True to enable read address increments, if false, each read will be from the same address
|
||||
* Usually disabled for peripheral to memory transfers
|
||||
*/
|
||||
static inline void channel_config_set_read_increment(dma_channel_config *c, bool incr) {
|
||||
c->ctrl = incr ? (c->ctrl | DMA_CH0_CTRL_TRIG_INCR_READ_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_INCR_READ_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Set DMA channel write increment
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* \param c Pointer to channel configuration data
|
||||
* \param incr True to enable write address increments, if false, each write will be to the same address
|
||||
* Usually disabled for memory to peripheral transfers
|
||||
* Usually disabled for memory to peripheral transfers
|
||||
*/
|
||||
static inline void channel_config_set_write_increment(dma_channel_config *c, bool incr) {
|
||||
c->ctrl = incr ? (c->ctrl | DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Select a transfer request signal
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* The channel uses the transfer request signal to pace its data transfer rate.
|
||||
* Sources for TREQ signals are internal (TIMERS) or external (DREQ, a Data Request from the system).
|
||||
* 0x0 to 0x3a -> select DREQ n as TREQ
|
||||
* 0x3b -> Select Timer 0 as TREQ
|
||||
* 0x3c -> Select Timer 1 as TREQ
|
||||
* 0x3d -> Select Timer 2 as TREQ (Optional)
|
||||
* 0x3e -> Select Timer 3 as TREQ (Optional)
|
||||
* 0x3f -> Permanent request, for unpaced transfers.
|
||||
*
|
||||
* \param c Pointer to channel configuration data
|
||||
* \param dreq Source (see description)
|
||||
*/
|
||||
static inline void channel_config_set_dreq(dma_channel_config *c, uint dreq) {
|
||||
assert(dreq <= DREQ_FORCE);
|
||||
c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_TREQ_SEL_BITS) | (dreq << DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB);
|
||||
}
|
||||
|
||||
/*! \brief Set DMA channel completion channel
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* When this channel completes, it will trigger the channel indicated by chain_to. Disable by
|
||||
* setting chain_to to itself (the same channel)
|
||||
*
|
||||
* \param c Pointer to channel configuration data
|
||||
* \param chain_to Channel to trigger when this channel completes.
|
||||
*/
|
||||
static inline void channel_config_set_chain_to(dma_channel_config *c, uint chain_to) {
|
||||
assert(chain_to <= NUM_DMA_CHANNELS);
|
||||
c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (chain_to << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB);
|
||||
}
|
||||
|
||||
/*! \brief Set the size of each DMA bus transfer
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* Set the size of each bus transfer (byte/halfword/word). The read and write addresses
|
||||
* advance by the specific amount (1/2/4 bytes) with each transfer.
|
||||
*
|
||||
* \param c Pointer to channel configuration data
|
||||
* \param size See enum for possible values.
|
||||
*/
|
||||
static inline void channel_config_set_transfer_data_size(dma_channel_config *c, enum dma_channel_transfer_size size) {
|
||||
assert(size == DMA_SIZE_8 || size == DMA_SIZE_16 || size == DMA_SIZE_32);
|
||||
c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_DATA_SIZE_BITS) | (size << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB);
|
||||
}
|
||||
|
||||
/*! \brief Set address wrapping parameters
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* Size of address wrap region. If 0, don’t wrap. For values n > 0, only the lower n bits of the address
|
||||
* will change. This wraps the address on a (1 << n) byte boundary, facilitating access to naturally-aligned
|
||||
* ring buffers.
|
||||
* Ring sizes between 2 and 32768 bytes are possible (size_bits from 1 - 15)
|
||||
*
|
||||
* 0x0 -> No wrapping.
|
||||
*
|
||||
* \param c Pointer to channel configuration data
|
||||
* \param write True to apply to write addresses, false to apply to read addresses
|
||||
* \param size_bits 0 to disable wrapping. Otherwise the size in bits of the changing part of the address.
|
||||
* Effectively wraps the address on a (1 << size_bits) byte boundary.
|
||||
*/
|
||||
static inline void channel_config_set_ring(dma_channel_config *c, bool write, uint size_bits) {
|
||||
assert(size_bits < 32);
|
||||
c->ctrl = (c->ctrl & ~(DMA_CH0_CTRL_TRIG_RING_SIZE_BITS | DMA_CH0_CTRL_TRIG_RING_SEL_BITS)) |
|
||||
(size_bits << DMA_CH0_CTRL_TRIG_RING_SIZE_LSB) |
|
||||
(write ? DMA_CH0_CTRL_TRIG_RING_SEL_BITS : 0);
|
||||
}
|
||||
|
||||
/*! \brief Set DMA byte swapping
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* No effect for byte data, for halfword data, the two bytes of each halfword are
|
||||
* swapped. For word data, the four bytes of each word are swapped to reverse their order.
|
||||
*
|
||||
* \param c Pointer to channel configuration data
|
||||
* \param bswap True to enable byte swapping
|
||||
*/
|
||||
static inline void channel_config_set_bswap(dma_channel_config *c, bool bswap) {
|
||||
c->ctrl = bswap ? (c->ctrl | DMA_CH0_CTRL_TRIG_BSWAP_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_BSWAP_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Set IRQ quiet mode
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* In QUIET mode, the channel does not generate IRQs at the end of every transfer block. Instead,
|
||||
* an IRQ is raised when NULL is written to a trigger register, indicating the end of a control
|
||||
* block chain.
|
||||
*
|
||||
* \param c Pointer to channel configuration data
|
||||
* \param irq_quiet True to enable quiet mode, false to disable.
|
||||
*/
|
||||
static inline void channel_config_set_irq_quiet(dma_channel_config *c, bool irq_quiet) {
|
||||
c->ctrl = irq_quiet ? (c->ctrl | DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Enable/Disable the DMA channel
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* When false, the channel will ignore triggers, stop issuing transfers, and pause the current transfer sequence (i.e. BUSY will
|
||||
* remain high if already high)
|
||||
*
|
||||
* \param c Pointer to channel configuration data
|
||||
* \param enable True to enable the DMA channel. When enabled, the channel will respond to triggering events, and start transferring data.
|
||||
*
|
||||
*/
|
||||
static inline void channel_config_set_enable(dma_channel_config *c, bool enable) {
|
||||
c->ctrl = enable ? (c->ctrl | DMA_CH0_CTRL_TRIG_EN_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_EN_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Enable access to channel by sniff hardware.
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* Sniff HW must be enabled and have this channel selected.
|
||||
*
|
||||
* \param c Pointer to channel configuration data
|
||||
* \param sniff_enable True to enable the Sniff HW access to this DMA channel.
|
||||
*/
|
||||
static inline void channel_config_set_sniff_enable(dma_channel_config *c, bool sniff_enable) {
|
||||
c->ctrl = sniff_enable ? (c->ctrl | DMA_CH0_CTRL_TRIG_SNIFF_EN_BITS) : (c->ctrl &
|
||||
~DMA_CH0_CTRL_TRIG_SNIFF_EN_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Get the default channel configuration for a given channel
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* Setting | Default
|
||||
* --------|--------
|
||||
* Read Increment | true
|
||||
* Write Increment | false
|
||||
* DReq | DREQ_FORCE
|
||||
* Chain to | self
|
||||
* Data size | DMA_SIZE_32
|
||||
* Ring | write=false, size=0 (i.e. off)
|
||||
* Byte Swap | false
|
||||
* Quiet IRQs | false
|
||||
* Channel Enable | true
|
||||
* Sniff Enable | false
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \return the default configuration which can then be modified.
|
||||
*/
|
||||
static inline dma_channel_config dma_channel_get_default_config(uint channel) {
|
||||
dma_channel_config c = {0};
|
||||
channel_config_set_read_increment(&c, true);
|
||||
channel_config_set_write_increment(&c, false);
|
||||
channel_config_set_dreq(&c, DREQ_FORCE);
|
||||
channel_config_set_chain_to(&c, channel);
|
||||
channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
|
||||
channel_config_set_ring(&c, false, 0);
|
||||
channel_config_set_bswap(&c, false);
|
||||
channel_config_set_irq_quiet(&c, false);
|
||||
channel_config_set_enable(&c, true);
|
||||
channel_config_set_sniff_enable(&c, false);
|
||||
return c;
|
||||
}
|
||||
|
||||
/*! \brief Get the current configuration for the specified channel.
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \return The current configuration as read from the HW register (not cached)
|
||||
*/
|
||||
static inline dma_channel_config dma_get_channel_config(uint channel) {
|
||||
dma_channel_config c;
|
||||
c.ctrl = dma_channel_hw_addr(channel)->ctrl_trig;
|
||||
return c;
|
||||
}
|
||||
|
||||
/*! \brief Get the raw configuration register from a channel configuration
|
||||
* \ingroup channel_config
|
||||
*
|
||||
* \param config Pointer to a config structure.
|
||||
* \return Register content
|
||||
*/
|
||||
static inline uint32_t channel_config_get_ctrl_value(const dma_channel_config *config) {
|
||||
return config->ctrl;
|
||||
}
|
||||
|
||||
/*! \brief Set a channel configuration
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \param config Pointer to a config structure with required configuration
|
||||
* \param trigger True to trigger the transfer immediately
|
||||
*/
|
||||
static inline void dma_channel_set_config(uint channel, const dma_channel_config *config, bool trigger) {
|
||||
// Don't use CTRL_TRIG since we don't want to start a transfer
|
||||
if (!trigger) {
|
||||
dma_channel_hw_addr(channel)->al1_ctrl = channel_config_get_ctrl_value(config);
|
||||
} else {
|
||||
dma_channel_hw_addr(channel)->ctrl_trig = channel_config_get_ctrl_value(config);
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Set the DMA initial read address.
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \param read_addr Initial read address of transfer.
|
||||
* \param trigger True to start the transfer immediately
|
||||
*/
|
||||
static inline void dma_channel_set_read_addr(uint channel, const volatile void *read_addr, bool trigger) {
|
||||
if (!trigger) {
|
||||
dma_channel_hw_addr(channel)->read_addr = (uintptr_t) read_addr;
|
||||
} else {
|
||||
dma_channel_hw_addr(channel)->al3_read_addr_trig = (uintptr_t) read_addr;
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Set the DMA initial read address
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \param write_addr Initial write address of transfer.
|
||||
* \param trigger True to start the transfer immediately
|
||||
*/
|
||||
static inline void dma_channel_set_write_addr(uint channel, volatile void *write_addr, bool trigger) {
|
||||
if (!trigger) {
|
||||
dma_channel_hw_addr(channel)->write_addr = (uintptr_t) write_addr;
|
||||
} else {
|
||||
dma_channel_hw_addr(channel)->al2_write_addr_trig = (uintptr_t) write_addr;
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Set the number of bus transfers the channel will do
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \param trans_count The number of transfers (not NOT bytes, see channel_config_set_transfer_data_size)
|
||||
* \param trigger True to start the transfer immediately
|
||||
*/
|
||||
static inline void dma_channel_set_trans_count(uint channel, uint32_t trans_count, bool trigger) {
|
||||
if (!trigger) {
|
||||
dma_channel_hw_addr(channel)->transfer_count = trans_count;
|
||||
} else {
|
||||
dma_channel_hw_addr(channel)->al1_transfer_count_trig = trans_count;
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Configure all DMA parameters and optionally start transfer
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \param config Pointer to DMA config structure
|
||||
* \param write_addr Initial write address
|
||||
* \param read_addr Initial read address
|
||||
* \param transfer_count Number of transfers to perform
|
||||
* \param trigger True to start the transfer immediately
|
||||
*/
|
||||
static inline void dma_channel_configure(uint channel, const dma_channel_config *config, volatile void *write_addr,
|
||||
const volatile void *read_addr,
|
||||
uint transfer_count, bool trigger) {
|
||||
dma_channel_set_read_addr(channel, read_addr, false);
|
||||
dma_channel_set_write_addr(channel, write_addr, false);
|
||||
dma_channel_set_trans_count(channel, transfer_count, false);
|
||||
dma_channel_set_config(channel, config, trigger);
|
||||
}
|
||||
|
||||
/*! \brief Start a DMA transfer from a buffer immediately
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \param read_addr Sets the initial read address
|
||||
* \param transfer_count Number of transfers to make. Not bytes, but the number of transfers of channel_config_set_transfer_data_size() to be sent.
|
||||
*/
|
||||
inline static void __attribute__((always_inline)) dma_channel_transfer_from_buffer_now(uint channel, void *read_addr,
|
||||
uint32_t transfer_count) {
|
||||
// check_dma_channel_param(channel);
|
||||
dma_channel_hw_t *hw = dma_channel_hw_addr(channel);
|
||||
hw->read_addr = (uintptr_t) read_addr;
|
||||
hw->al1_transfer_count_trig = transfer_count;
|
||||
}
|
||||
|
||||
/*! \brief Start a DMA transfer to a buffer immediately
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \param write_addr Sets the initial write address
|
||||
* \param transfer_count Number of transfers to make. Not bytes, but the number of transfers of channel_config_set_transfer_data_size() to be sent.
|
||||
*/
|
||||
inline static void dma_channel_transfer_to_buffer_now(uint channel, void *write_addr, uint32_t transfer_count) {
|
||||
dma_channel_hw_t *hw = dma_channel_hw_addr(channel);
|
||||
hw->write_addr = (uintptr_t) write_addr;
|
||||
hw->al1_transfer_count_trig = transfer_count;
|
||||
}
|
||||
|
||||
/*! \brief Start one or more channels simultaneously
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param chan_mask Bitmask of all the channels requiring starting. Channel 0 = bit 0, channel 1 = bit 1 etc.
|
||||
*/
|
||||
static inline void dma_start_channel_mask(uint32_t chan_mask) {
|
||||
valid_params_if(DMA, chan_mask && chan_mask < (1u << NUM_DMA_CHANNELS));
|
||||
dma_hw->multi_channel_trigger = chan_mask;
|
||||
}
|
||||
|
||||
/*! \brief Start a single DMA channel
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
*/
|
||||
static inline void dma_channel_start(uint channel) {
|
||||
dma_start_channel_mask(1u << channel);
|
||||
}
|
||||
|
||||
/*! \brief Stop a DMA transfer
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* Function will only return once the DMA has stopped.
|
||||
*
|
||||
* \param channel DMA channel
|
||||
*/
|
||||
static inline void dma_channel_abort(uint channel) {
|
||||
check_dma_channel_param(channel);
|
||||
dma_hw->abort = 1u << channel;
|
||||
// Bit will go 0 once channel has reached safe state
|
||||
// (i.e. any in-flight transfers have retired)
|
||||
while (dma_hw->abort & (1ul << channel)) tight_loop_contents();
|
||||
}
|
||||
|
||||
/*! \brief Enable single DMA channel interrupt 0
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \param enabled true to enable interrupt 0 on specified channel, false to disable.
|
||||
*/
|
||||
static inline void dma_channel_set_irq0_enabled(uint channel, bool enabled) {
|
||||
check_dma_channel_param(channel);
|
||||
check_hw_layout(dma_hw_t, inte0, DMA_INTE0_OFFSET);
|
||||
if (enabled)
|
||||
hw_set_bits(&dma_hw->inte0, 1u << channel);
|
||||
else
|
||||
hw_clear_bits(&dma_hw->inte0, 1u << channel);
|
||||
}
|
||||
|
||||
/*! \brief Enable multiple DMA channels interrupt 0
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel_mask Bitmask of all the channels to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc.
|
||||
* \param enabled true to enable all the interrupts specified in the mask, false to disable all the interrupts specified in the mask.
|
||||
*/
|
||||
static inline void dma_set_irq0_channel_mask_enabled(uint32_t channel_mask, bool enabled) {
|
||||
if (enabled) {
|
||||
hw_set_bits(&dma_hw->inte0, channel_mask);
|
||||
} else {
|
||||
hw_clear_bits(&dma_hw->inte0, channel_mask);
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Enable single DMA channel interrupt 1
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \param enabled true to enable interrupt 1 on specified channel, false to disable.
|
||||
*/
|
||||
static inline void dma_channel_set_irq1_enabled(uint channel, bool enabled) {
|
||||
check_dma_channel_param(channel);
|
||||
check_hw_layout(dma_hw_t, inte1, DMA_INTE1_OFFSET);
|
||||
if (enabled)
|
||||
hw_set_bits(&dma_hw->inte1, 1u << channel);
|
||||
else
|
||||
hw_clear_bits(&dma_hw->inte1, 1u << channel);
|
||||
}
|
||||
|
||||
/*! \brief Enable multiple DMA channels interrupt 0
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel_mask Bitmask of all the channels to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc.
|
||||
* \param enabled true to enable all the interrupts specified in the mask, false to disable all the interrupts specified in the mask.
|
||||
*/
|
||||
static inline void dma_set_irq1_channel_mask_enabled(uint32_t channel_mask, bool enabled) {
|
||||
if (enabled) {
|
||||
hw_set_bits(&dma_hw->inte1, channel_mask);
|
||||
} else {
|
||||
hw_clear_bits(&dma_hw->inte1, channel_mask);
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Check if DMA channel is busy
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \return true if the channel is currently busy
|
||||
*/
|
||||
inline static bool dma_channel_is_busy(uint channel) {
|
||||
check_dma_channel_param(channel);
|
||||
return !!(dma_hw->ch[channel].al1_ctrl & DMA_CH0_CTRL_TRIG_BUSY_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Wait for a DMA channel transfer to complete
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* \param channel DMA channel
|
||||
*/
|
||||
inline static void dma_channel_wait_for_finish_blocking(uint channel) {
|
||||
while (dma_channel_is_busy(channel)) tight_loop_contents();
|
||||
}
|
||||
|
||||
/*! \brief Enable the DMA sniffing targeting the specified channel
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* The mode can be one of the following:
|
||||
*
|
||||
* Mode | Function
|
||||
* -----|---------
|
||||
* 0x0 | Calculate a CRC-32 (IEEE802.3 polynomial)
|
||||
* 0x1 | Calculate a CRC-32 (IEEE802.3 polynomial) with bit reversed data
|
||||
* 0x2 | Calculate a CRC-16-CCITT
|
||||
* 0x3 | Calculate a CRC-16-CCITT with bit reversed data
|
||||
* 0xe | XOR reduction over all data. == 1 if the total 1 population count is odd.
|
||||
* 0xf | Calculate a simple 32-bit checksum (addition with a 32 bit accumulator)
|
||||
*
|
||||
* \param channel DMA channel
|
||||
* \param mode See description
|
||||
* \param force_channel_enable Set true to also turn on sniffing in the channel configuration (this
|
||||
* is usually what you want, but sometimes you might have a chain DMA with only certain segments
|
||||
* of the chain sniffed, in which case you might pass false).
|
||||
*/
|
||||
inline static void dma_sniffer_enable(uint channel, uint mode, bool force_channel_enable) {
|
||||
check_dma_channel_param(channel);
|
||||
check_hw_layout(dma_hw_t, sniff_ctrl, DMA_SNIFF_CTRL_OFFSET);
|
||||
if (force_channel_enable) {
|
||||
hw_set_bits(&dma_hw->ch[channel].al1_ctrl, DMA_CH0_CTRL_TRIG_SNIFF_EN_BITS);
|
||||
}
|
||||
dma_hw->sniff_ctrl = ((channel << DMA_SNIFF_CTRL_DMACH_LSB) & DMA_SNIFF_CTRL_DMACH_BITS) |
|
||||
((mode << DMA_SNIFF_CTRL_CALC_LSB) & DMA_SNIFF_CTRL_CALC_BITS) |
|
||||
DMA_SNIFF_CTRL_EN_BITS;
|
||||
}
|
||||
|
||||
/*! \brief Enable the Sniffer byte swap function
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
* Locally perform a byte reverse on the sniffed data, before feeding into checksum.
|
||||
*
|
||||
* Note that the sniff hardware is downstream of the DMA channel byteswap performed in the
|
||||
* read master: if channel_config_set_bswap() and dma_sniffer_set_byte_swap_enabled() are both enabled,
|
||||
* their effects cancel from the sniffer’s point of view.
|
||||
*
|
||||
* \param swap Set true to enable byte swapping
|
||||
*/
|
||||
inline static void dma_sniffer_set_byte_swap_enabled(bool swap) {
|
||||
if (swap)
|
||||
hw_set_bits(&dma_hw->sniff_ctrl, DMA_SNIFF_CTRL_BSWAP_BITS);
|
||||
else
|
||||
hw_clear_bits(&dma_hw->sniff_ctrl, DMA_SNIFF_CTRL_BSWAP_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Disable the DMA sniffer
|
||||
* \ingroup hardware_dma
|
||||
*
|
||||
*/
|
||||
inline static void dma_sniffer_disable() {
|
||||
dma_hw->sniff_ctrl = 0;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
void print_dma_ctrl(dma_channel_hw_t *channel);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
8
src/rp2_common/hardware_flash/CMakeLists.txt
Normal file
8
src/rp2_common/hardware_flash/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
add_library(hardware_flash INTERFACE)
|
||||
|
||||
target_sources(hardware_flash INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/flash.c
|
||||
)
|
||||
|
||||
target_include_directories(hardware_flash INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
|
||||
target_link_libraries(hardware_flash INTERFACE pico_base_headers pico_bootrom)
|
101
src/rp2_common/hardware_flash/flash.c
Normal file
101
src/rp2_common/hardware_flash/flash.c
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/flash.h"
|
||||
#include "pico/bootrom.h"
|
||||
|
||||
#define FLASH_BLOCK_ERASE_CMD 0xd8
|
||||
|
||||
#define __compiler_barrier() asm volatile("" ::: "memory")
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Infrastructure for reentering XIP mode after exiting for programming (take
|
||||
// a copy of boot2 before XIP exit). Calling boot2 as a function works because
|
||||
// it accepts a return vector in LR (and doesn't trash r4-r7). Bootrom passes
|
||||
// NULL in LR, instructing boot2 to enter flash vector table's reset handler.
|
||||
|
||||
#if !PICO_NO_FLASH
|
||||
|
||||
#define BOOT2_SIZE_WORDS 64
|
||||
|
||||
static uint32_t boot2_copyout[BOOT2_SIZE_WORDS];
|
||||
static bool boot2_copyout_valid = false;
|
||||
|
||||
static void __no_inline_not_in_flash_func(flash_init_boot2_copyout)() {
|
||||
if (boot2_copyout_valid)
|
||||
return;
|
||||
for (int i = 0; i < BOOT2_SIZE_WORDS; ++i)
|
||||
boot2_copyout[i] = ((uint32_t *)XIP_BASE)[i];
|
||||
__compiler_barrier();
|
||||
boot2_copyout_valid = true;
|
||||
}
|
||||
|
||||
static void __no_inline_not_in_flash_func(flash_enable_xip_via_boot2)() {
|
||||
((void (*)(void))boot2_copyout+1)();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void __no_inline_not_in_flash_func(flash_init_boot2_copyout)() {}
|
||||
|
||||
static void __no_inline_not_in_flash_func(flash_enable_xip_via_boot2)() {
|
||||
// Set up XIP for 03h read on bus access (slow but generic)
|
||||
void (*flash_enter_cmd_xip)(void) = (void(*)(void))rom_func_lookup(rom_table_code('C', 'X'));
|
||||
assert(flash_enter_cmd_xip);
|
||||
flash_enter_cmd_xip();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Actual flash programming shims (work whether or not PICO_NO_FLASH==1)
|
||||
|
||||
void __no_inline_not_in_flash_func(flash_range_erase)(uint32_t flash_offs, size_t count) {
|
||||
#ifdef PICO_FLASH_SIZE_BYTES
|
||||
hard_assert(flash_offs + count <= PICO_FLASH_SIZE_BYTES);
|
||||
#endif
|
||||
invalid_params_if(FLASH, flash_offs & (FLASH_SECTOR_SIZE - 1));
|
||||
invalid_params_if(FLASH, count & (FLASH_SECTOR_SIZE - 1));
|
||||
void (*connect_internal_flash)(void) = (void(*)(void))rom_func_lookup(rom_table_code('I', 'F'));
|
||||
void (*flash_exit_xip)(void) = (void(*)(void))rom_func_lookup(rom_table_code('E', 'X'));
|
||||
void (*flash_range_erase)(uint32_t, size_t, uint32_t, uint8_t) =
|
||||
(void(*)(uint32_t, size_t, uint32_t, uint8_t))rom_func_lookup(rom_table_code('R', 'E'));
|
||||
void (*flash_flush_cache)(void) = (void(*)(void))rom_func_lookup(rom_table_code('F', 'C'));
|
||||
assert(connect_internal_flash && flash_exit_xip && flash_range_erase && flash_flush_cache);
|
||||
flash_init_boot2_copyout();
|
||||
|
||||
// No flash accesses after this point
|
||||
__compiler_barrier();
|
||||
|
||||
connect_internal_flash();
|
||||
flash_exit_xip();
|
||||
flash_range_erase(flash_offs, count, FLASH_BLOCK_SIZE, FLASH_BLOCK_ERASE_CMD);
|
||||
flash_flush_cache(); // Note this is needed to remove CSn IO force as well as cache flushing
|
||||
flash_enable_xip_via_boot2();
|
||||
}
|
||||
|
||||
void __no_inline_not_in_flash_func(flash_range_program)(uint32_t flash_offs, const uint8_t *data, size_t count) {
|
||||
#ifdef PICO_FLASH_SIZE_BYTES
|
||||
hard_assert(flash_offs + count <= PICO_FLASH_SIZE_BYTES);
|
||||
#endif
|
||||
invalid_params_if(FLASH, flash_offs & (FLASH_PAGE_SIZE - 1));
|
||||
invalid_params_if(FLASH, count & (FLASH_PAGE_SIZE - 1));
|
||||
void (*connect_internal_flash)(void) = (void(*)(void))rom_func_lookup(rom_table_code('I', 'F'));
|
||||
void (*flash_exit_xip)(void) = (void(*)(void))rom_func_lookup(rom_table_code('E', 'X'));
|
||||
void (*flash_range_program)(uint32_t, const uint8_t*, size_t) =
|
||||
(void(*)(uint32_t, const uint8_t*, size_t))rom_func_lookup(rom_table_code('R', 'P'));
|
||||
void (*flash_flush_cache)(void) = (void(*)(void))rom_func_lookup(rom_table_code('F', 'C'));
|
||||
assert(connect_internal_flash && flash_exit_xip && flash_range_program && flash_flush_cache);
|
||||
flash_init_boot2_copyout();
|
||||
|
||||
__compiler_barrier();
|
||||
|
||||
connect_internal_flash();
|
||||
flash_exit_xip();
|
||||
flash_range_program(flash_offs, data, count);
|
||||
flash_flush_cache(); // Note this is needed to remove CSn IO force as well as cache flushing
|
||||
flash_enable_xip_via_boot2();
|
||||
}
|
59
src/rp2_common/hardware_flash/include/hardware/flash.h
Normal file
59
src/rp2_common/hardware_flash/include/hardware/flash.h
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_FLASH_H
|
||||
#define _HARDWARE_FLASH_H
|
||||
|
||||
#include "pico.h"
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_FLASH, Enable/disable assertions in the flash module, type=bool, default=0, group=hardware_flash
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_FLASH
|
||||
#define PARAM_ASSERTIONS_ENABLED_FLASH 0
|
||||
#endif
|
||||
|
||||
#define FLASH_PAGE_SIZE (1u << 8)
|
||||
#define FLASH_SECTOR_SIZE (1u << 12)
|
||||
#define FLASH_BLOCK_SIZE (1u << 16)
|
||||
|
||||
/** \file flash.h
|
||||
* \defgroup hardware_flash hardware_flash
|
||||
*
|
||||
* Low level flash programming and erase API
|
||||
*
|
||||
* Note these functions are *unsafe* if you have two cores concurrently
|
||||
* executing from flash. In this case you must perform your own
|
||||
* synchronisation to make sure no XIP accesses take place during flash
|
||||
* programming.
|
||||
*
|
||||
* If PICO_NO_FLASH=1 is not defined (i.e. if the program is built to run from
|
||||
* flash) then these functions will make a static copy of the second stage
|
||||
* bootloader in SRAM, and use this to reenter execute-in-place mode after
|
||||
* programming or erasing flash, so that they can safely be called from
|
||||
* flash-resident code.
|
||||
*
|
||||
* \subsection flash_example Example
|
||||
* \include flash_program.c
|
||||
*/
|
||||
|
||||
|
||||
/*! \brief Erase areas of flash
|
||||
* \ingroup hardware_flash
|
||||
*
|
||||
* \param flash_offs Offset into flash, in bytes, to start the erase. Must be aligned to a 4096-byte flash sector.
|
||||
* \param count Number of bytes to be erased. Must be a multiple of 4096 bytes (one sector).
|
||||
*/
|
||||
void flash_range_erase(uint32_t flash_offs, size_t count);
|
||||
|
||||
/*! \brief Program flash
|
||||
* \ingroup hardware_flash
|
||||
*
|
||||
* \param flash_offs Flash address of the first byte to be programmed. Must be aligned to a 256-byte flash page.
|
||||
* \param data Pointer to the data to program into flash
|
||||
* \param count Number of bytes to program. Must be a multiple of 256 bytes (one page).
|
||||
*/
|
||||
void flash_range_program(uint32_t flash_offs, const uint8_t *data, size_t count);
|
||||
|
||||
#endif
|
1
src/rp2_common/hardware_gpio/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_gpio/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(gpio)
|
168
src/rp2_common/hardware_gpio/gpio.c
Normal file
168
src/rp2_common/hardware_gpio/gpio.c
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/sync.h"
|
||||
|
||||
#include "hardware/structs/iobank0.h"
|
||||
#include "hardware/irq.h"
|
||||
|
||||
#include "pico/binary_info.h"
|
||||
|
||||
static gpio_irq_callback_t _callbacks[NUM_CORES];
|
||||
|
||||
// Get the raw value from the pin, bypassing any muxing or overrides.
|
||||
int gpio_get_pad(uint gpio) {
|
||||
invalid_params_if(GPIO, gpio >= N_GPIOS);
|
||||
hw_set_bits(&padsbank0_hw->io[gpio], PADS_BANK0_GPIO0_IE_BITS);
|
||||
return (iobank0_hw->io[gpio].status & IO_BANK0_GPIO0_STATUS_INFROMPAD_BITS)
|
||||
>> IO_BANK0_GPIO0_STATUS_INFROMPAD_LSB;
|
||||
}
|
||||
|
||||
/// \tag::gpio_set_function[]
|
||||
// Select function for this GPIO, and ensure input/output are enabled at the pad.
|
||||
// This also clears the input/output/irq override bits.
|
||||
void gpio_set_function(uint gpio, enum gpio_function fn) {
|
||||
invalid_params_if(GPIO, gpio >= N_GPIOS);
|
||||
invalid_params_if(GPIO, fn << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB & ~IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS);
|
||||
// Set input enable on, output disable off
|
||||
hw_write_masked(&padsbank0_hw->io[gpio],
|
||||
PADS_BANK0_GPIO0_IE_BITS,
|
||||
PADS_BANK0_GPIO0_IE_BITS | PADS_BANK0_GPIO0_OD_BITS
|
||||
);
|
||||
// Zero all fields apart from fsel; we want this IO to do what the peripheral tells it.
|
||||
// This doesn't affect e.g. pullup/pulldown, as these are in pad controls.
|
||||
iobank0_hw->io[gpio].ctrl = fn << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB;
|
||||
}
|
||||
/// \end::gpio_set_function[]
|
||||
|
||||
enum gpio_function gpio_get_function(uint gpio) {
|
||||
invalid_params_if(GPIO, gpio >= N_GPIOS);
|
||||
return (enum gpio_function) ((iobank0_hw->io[gpio].ctrl & IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS) >> IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB);
|
||||
}
|
||||
|
||||
// Note that, on RP2040, setting both pulls enables a "bus keep" function,
|
||||
// i.e. weak pull to whatever is current high/low state of GPIO.
|
||||
void gpio_set_pulls(uint gpio, bool up, bool down) {
|
||||
invalid_params_if(GPIO, gpio >= N_GPIOS);
|
||||
hw_write_masked(
|
||||
&padsbank0_hw->io[gpio],
|
||||
(!!up << PADS_BANK0_GPIO0_PUE_LSB) | (!!down << PADS_BANK0_GPIO0_PDE_LSB),
|
||||
PADS_BANK0_GPIO0_PUE_BITS | PADS_BANK0_GPIO0_PDE_BITS
|
||||
);
|
||||
}
|
||||
|
||||
// Direct overrides for pad controls
|
||||
void gpio_set_inover(uint gpio, uint value) {
|
||||
invalid_params_if(GPIO, gpio >= N_GPIOS);
|
||||
hw_write_masked(&iobank0_hw->io[gpio].ctrl,
|
||||
value << IO_BANK0_GPIO0_CTRL_INOVER_LSB,
|
||||
IO_BANK0_GPIO0_CTRL_INOVER_BITS
|
||||
);
|
||||
}
|
||||
|
||||
void gpio_set_outover(uint gpio, uint value) {
|
||||
invalid_params_if(GPIO, gpio >= N_GPIOS);
|
||||
hw_write_masked(&iobank0_hw->io[gpio].ctrl,
|
||||
value << IO_BANK0_GPIO0_CTRL_OUTOVER_LSB,
|
||||
IO_BANK0_GPIO0_CTRL_OUTOVER_BITS
|
||||
);
|
||||
}
|
||||
|
||||
void gpio_set_oeover(uint gpio, uint value) {
|
||||
invalid_params_if(GPIO, gpio >= N_GPIOS);
|
||||
hw_write_masked(&iobank0_hw->io[gpio].ctrl,
|
||||
value << IO_BANK0_GPIO0_CTRL_OEOVER_LSB,
|
||||
IO_BANK0_GPIO0_CTRL_OEOVER_BITS
|
||||
);
|
||||
}
|
||||
|
||||
static void gpio_irq_handler(void) {
|
||||
io_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ?
|
||||
&iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl;
|
||||
for (uint gpio = 0; gpio < N_GPIOS; gpio++) {
|
||||
io_rw_32 *status_reg = &irq_ctrl_base->ints[gpio / 8];
|
||||
uint events = (*status_reg >> 4 * (gpio % 8)) & 0xf;
|
||||
if (events) {
|
||||
// TODO: If both cores care about this event then the second core won't get the irq?
|
||||
gpio_acknowledge_irq(gpio, events);
|
||||
gpio_irq_callback_t callback = _callbacks[get_core_num()];
|
||||
if (callback) {
|
||||
callback(gpio, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _gpio_set_irq_enabled(uint gpio, uint32_t events, bool enabled, io_irq_ctrl_hw_t *irq_ctrl_base) {
|
||||
// Clear stale events which might cause immediate spurious handler entry
|
||||
gpio_acknowledge_irq(gpio, events);
|
||||
|
||||
io_rw_32 *en_reg = &irq_ctrl_base->inte[gpio / 8];
|
||||
events <<= 4 * (gpio % 8);
|
||||
|
||||
if (enabled)
|
||||
hw_set_bits(en_reg, events);
|
||||
else
|
||||
hw_clear_bits(en_reg, events);
|
||||
}
|
||||
|
||||
void gpio_set_irq_enabled(uint gpio, uint32_t events, bool enabled) {
|
||||
// Separate mask/force/status per-core, so check which core called, and
|
||||
// set the relevant IRQ controls.
|
||||
io_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ?
|
||||
&iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl;
|
||||
_gpio_set_irq_enabled(gpio, events, enabled, irq_ctrl_base);
|
||||
}
|
||||
|
||||
void gpio_set_irq_enabled_with_callback(uint gpio, uint32_t events, bool enabled, gpio_irq_callback_t callback) {
|
||||
gpio_set_irq_enabled(gpio, events, enabled);
|
||||
|
||||
// TODO: Do we want to support a callback per GPIO pin?
|
||||
// Install IRQ handler
|
||||
_callbacks[get_core_num()] = callback;
|
||||
irq_set_exclusive_handler(IO_IRQ_BANK0, gpio_irq_handler);
|
||||
irq_set_enabled(IO_IRQ_BANK0, true);
|
||||
}
|
||||
|
||||
void gpio_set_dormant_irq_enabled(uint gpio, uint32_t events, bool enabled) {
|
||||
io_irq_ctrl_hw_t *irq_ctrl_base = &iobank0_hw->dormant_wake_irq_ctrl;
|
||||
_gpio_set_irq_enabled(gpio, events, enabled, irq_ctrl_base);
|
||||
}
|
||||
|
||||
void gpio_acknowledge_irq(uint gpio, uint32_t events) {
|
||||
iobank0_hw->intr[gpio / 8] = events << 4 * (gpio % 8);
|
||||
}
|
||||
|
||||
#define DEBUG_PIN_MASK (((1u << PICO_DEBUG_PIN_COUNT)-1) << PICO_DEBUG_PIN_BASE)
|
||||
void gpio_debug_pins_init() {
|
||||
gpio_init_mask(DEBUG_PIN_MASK);
|
||||
gpio_set_dir_masked(DEBUG_PIN_MASK, DEBUG_PIN_MASK);
|
||||
bi_decl_if_func_used(bi_pin_mask_with_names(DEBUG_PIN_MASK, "Debug"));
|
||||
}
|
||||
|
||||
void gpio_set_input_enabled(uint gpio, bool enabled) {
|
||||
if (enabled)
|
||||
hw_set_bits(&padsbank0_hw->io[gpio], PADS_BANK0_GPIO0_IE_BITS);
|
||||
else
|
||||
hw_clear_bits(&padsbank0_hw->io[gpio], PADS_BANK0_GPIO0_IE_BITS);
|
||||
}
|
||||
|
||||
void gpio_init(uint gpio) {
|
||||
sio_hw->gpio_oe_clr = 1ul << gpio;
|
||||
sio_hw->gpio_clr = 1ul << gpio;
|
||||
gpio_set_function(gpio, GPIO_FUNC_SIO);
|
||||
}
|
||||
|
||||
void gpio_init_mask(uint gpio_mask) {
|
||||
for(uint i=0;i<32;i++) {
|
||||
if (gpio_mask & 1) {
|
||||
gpio_init(i);
|
||||
}
|
||||
gpio_mask >>= 1;
|
||||
}
|
||||
}
|
||||
|
531
src/rp2_common/hardware_gpio/include/hardware/gpio.h
Normal file
531
src/rp2_common/hardware_gpio/include/hardware/gpio.h
Normal file
@ -0,0 +1,531 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_GPIO_H_
|
||||
#define _HARDWARE_GPIO_H_
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/sio.h"
|
||||
#include "hardware/structs/padsbank0.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_GPIO, Enable/disable assertions in the GPIO module, type=bool, default=0, group=hardware_gpio
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_GPIO
|
||||
#define PARAM_ASSERTIONS_ENABLED_GPIO 0
|
||||
#endif
|
||||
|
||||
/** \file gpio.h
|
||||
* \defgroup hardware_gpio hardware_gpio
|
||||
*
|
||||
* General Purpose Input/Output (GPIO) API
|
||||
*
|
||||
* RP2040 has 36 multi-functional General Purpose Input / Output (GPIO) pins, divided into two banks. In a typical use case,
|
||||
* the pins in the QSPI bank (QSPI_SS, QSPI_SCLK and QSPI_SD0 to QSPI_SD3) are used to execute code from an external
|
||||
* flash device, leaving the User bank (GPIO0 to GPIO29) for the programmer to use. All GPIOs support digital input and
|
||||
* output, but GPIO26 to GPIO29 can also be used as inputs to the chip’s Analogue to Digital Converter (ADC). Each GPIO
|
||||
* can be controlled directly by software running on the processors, or by a number of other functional blocks.
|
||||
*
|
||||
* The function allocated to each GPIO is selected by calling the \ref gpio_set_function function. \note Not all functions
|
||||
* are available on all pins.
|
||||
*
|
||||
* Each GPIO can have one function selected at a time. Likewise, each peripheral input (e.g. UART0 RX) should only be selected on
|
||||
* one _GPIO_ at a time. If the same peripheral input is connected to multiple GPIOs, the peripheral sees the logical OR of these
|
||||
* GPIO inputs. Please refer to the datasheet for more information on GPIO function select.
|
||||
*
|
||||
* ### Function Select Table
|
||||
*
|
||||
* GPIO | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9
|
||||
* -------|----------|-----------|----------|--------|-----|------|------|---------------|----
|
||||
* 0 | SPI0 RX | UART0 TX | I2C0 SDA | PWM0 A | SIO | PIO0 | PIO1 | | USB OVCUR DET
|
||||
* 1 | SPI0 CSn | UART0 RX | I2C0 SCL | PWM0 B | SIO | PIO0 | PIO1 | | USB VBUS DET
|
||||
* 2 | SPI0 SCK | UART0 CTS | I2C1 SDA | PWM1 A | SIO | PIO0 | PIO1 | | USB VBUS EN
|
||||
* 3 | SPI0 TX | UART0 RTS | I2C1 SCL | PWM1 B | SIO | PIO0 | PIO1 | | USB OVCUR DET
|
||||
* 4 | SPI0 RX | UART1 TX | I2C0 SDA | PWM2 A | SIO | PIO0 | PIO1 | | USB VBUS DET
|
||||
* 5 | SPI0 CSn | UART1 RX | I2C0 SCL | PWM2 B | SIO | PIO0 | PIO1 | | USB VBUS EN
|
||||
* 6 | SPI0 SCK | UART1 CTS | I2C1 SDA | PWM3 A | SIO | PIO0 | PIO1 | | USB OVCUR DET
|
||||
* 7 | SPI0 TX | UART1 RTS | I2C1 SCL | PWM3 B | SIO | PIO0 | PIO1 | | USB VBUS DET
|
||||
* 8 | SPI1 RX | UART1 TX | I2C0 SDA | PWM4 A | SIO | PIO0 | PIO1 | | USB VBUS EN
|
||||
* 9 | SPI1 CSn | UART1 RX | I2C0 SCL | PWM4 B | SIO | PIO0 | PIO1 | | USB OVCUR DET
|
||||
* 10 | SPI1 SCK | UART1 CTS | I2C1 SDA | PWM5 A | SIO | PIO0 | PIO1 | | USB VBUS DET
|
||||
* 11 | SPI1 TX | UART1 RTS | I2C1 SCL | PWM5 B | SIO | PIO0 | PIO1 | | USB VBUS EN
|
||||
* 12 | SPI1 RX | UART0 TX | I2C0 SDA | PWM6 A | SIO | PIO0 | PIO1 | | USB OVCUR DET
|
||||
* 13 | SPI1 CSn | UART0 RX | I2C0 SCL | PWM6 B | SIO | PIO0 | PIO1 | | USB VBUS DET
|
||||
* 14 | SPI1 SCK | UART0 CTS | I2C1 SDA | PWM7 A | SIO | PIO0 | PIO1 | | USB VBUS EN
|
||||
* 15 | SPI1 TX | UART0 RTS | I2C1 SCL | PWM7 B | SIO | PIO0 | PIO1 | | USB OVCUR DET
|
||||
* 16 | SPI0 RX | UART0 TX | I2C0 SDA | PWM0 A | SIO | PIO0 | PIO1 | | USB VBUS DET
|
||||
* 17 | SPI0 CSn | UART0 RX | I2C0 SCL | PWM0 B | SIO | PIO0 | PIO1 | | USB VBUS EN
|
||||
* 18 | SPI0 SCK | UART0 CTS | I2C1 SDA | PWM1 A | SIO | PIO0 | PIO1 | | USB OVCUR DET
|
||||
* 19 | SPI0 TX | UART0 RTS | I2C1 SCL | PWM1 B | SIO | PIO0 | PIO1 | | USB VBUS DET
|
||||
* 20 | SPI0 RX | UART1 TX | I2C0 SDA | PWM2 A | SIO | PIO0 | PIO1 | CLOCK GPIN0 | USB VBUS EN
|
||||
* 21 | SPI0 CSn | UART1 RX | I2C0 SCL | PWM2 B | SIO | PIO0 | PIO1 | CLOCK GPOUT0 | USB OVCUR DET
|
||||
* 22 | SPI0 SCK | UART1 CTS | I2C1 SDA | PWM3 A | SIO | PIO0 | PIO1 | CLOCK GPIN1 | USB VBUS DET
|
||||
* 23 | SPI0 TX | UART1 RTS | I2C1 SCL | PWM3 B | SIO | PIO0 | PIO1 | CLOCK GPOUT1 | USB VBUS EN
|
||||
* 24 | SPI1 RX | UART1 TX | I2C0 SDA | PWM4 A | SIO | PIO0 | PIO1 | CLOCK GPOUT2 | USB OVCUR DET
|
||||
* 25 | SPI1 CSn | UART1 RX | I2C0 SCL | PWM4 B | SIO | PIO0 | PIO1 | CLOCK GPOUT3 | USB VBUS DET
|
||||
* 26 | SPI1 SCK | UART1 CTS | I2C1 SDA | PWM5 A | SIO | PIO0 | PIO1 | | USB VBUS EN
|
||||
* 27 | SPI1 TX | UART1 RTS | I2C1 SCL | PWM5 B | SIO | PIO0 | PIO1 | | USB OVCUR DET
|
||||
* 28 | SPI1 RX | UART0 TX | I2C0 SDA | PWM6 A | SIO | PIO0 | PIO1 | | USB VBUS DET
|
||||
* 29 | SPI1 CSn | UART0 RX | I2C0 SCL | PWM6 B | SIO | PIO0 | PIO1 | | USB VBUS EN
|
||||
|
||||
*/
|
||||
|
||||
/*! \brief GPIO function definitions for use with function select
|
||||
* \ingroup hardware_gpio
|
||||
* \brief GPIO function selectors
|
||||
*
|
||||
* Each GPIO can have one function selected at a time. Likewise, each peripheral input (e.g. UART0 RX) should only be
|
||||
* selected on one GPIO at a time. If the same peripheral input is connected to multiple GPIOs, the peripheral sees the logical
|
||||
* OR of these GPIO inputs.
|
||||
*
|
||||
* Please refer to the datsheet for more information on GPIO function selection.
|
||||
*/
|
||||
enum gpio_function {
|
||||
GPIO_FUNC_XIP = 0,
|
||||
GPIO_FUNC_SPI = 1,
|
||||
GPIO_FUNC_UART = 2,
|
||||
GPIO_FUNC_I2C = 3,
|
||||
GPIO_FUNC_PWM = 4,
|
||||
GPIO_FUNC_SIO = 5,
|
||||
GPIO_FUNC_PIO0 = 6,
|
||||
GPIO_FUNC_PIO1 = 7,
|
||||
GPIO_FUNC_GPCK = 8,
|
||||
GPIO_FUNC_USB = 9,
|
||||
GPIO_FUNC_NULL = 0xf,
|
||||
};
|
||||
|
||||
#define GPIO_OUT 1
|
||||
#define GPIO_IN 0
|
||||
|
||||
/*! \brief GPIO Interrupt level definitions
|
||||
* \ingroup hardware_gpio
|
||||
* \brief GPIO Interrupt levels
|
||||
*
|
||||
* An interrupt can be generated for every GPIO pin in 4 scenarios:
|
||||
*
|
||||
* * Level High: the GPIO pin is a logical 1
|
||||
* * Level Low: the GPIO pin is a logical 0
|
||||
* * Edge High: the GPIO has transitioned from a logical 0 to a logical 1
|
||||
* * Edge Low: the GPIO has transitioned from a logical 1 to a logical 0
|
||||
*
|
||||
* The level interrupts are not latched. This means that if the pin is a logical 1 and the level high interrupt is active, it will
|
||||
* become inactive as soon as the pin changes to a logical 0. The edge interrupts are stored in the INTR register and can be
|
||||
* cleared by writing to the INTR register.
|
||||
*/
|
||||
enum gpio_irq_level {
|
||||
GPIO_IRQ_LEVEL_LOW = 0x1u,
|
||||
GPIO_IRQ_LEVEL_HIGH = 0x2u,
|
||||
GPIO_IRQ_EDGE_FALL = 0x4u,
|
||||
GPIO_IRQ_EDGE_RISE = 0x8u,
|
||||
};
|
||||
|
||||
typedef void (*gpio_irq_callback_t)(uint gpio, uint32_t events);
|
||||
|
||||
enum gpio_override {
|
||||
GPIO_OVERRIDE_NORMAL = 0, ///< peripheral signal selected via \ref gpio_set_function
|
||||
GPIO_OVERRIDE_INVERT = 1, ///< invert peripheral signal selected via \ref gpio_set_function
|
||||
GPIO_OVERRIDE_LOW = 2, ///< drive low/disable output
|
||||
GPIO_OVERRIDE_HIGH = 3, ///< drive high/enable output
|
||||
};
|
||||
|
||||
#define N_GPIOS 30
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Pad Controls + IO Muxing
|
||||
// ----------------------------------------------------------------------------
|
||||
// Declarations for gpio.c
|
||||
|
||||
/*! \brief Select GPIO function
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param fn Which GPIO function select to use from list \ref gpio_function
|
||||
*/
|
||||
void gpio_set_function(uint gpio, enum gpio_function fn);
|
||||
|
||||
enum gpio_function gpio_get_function(uint gpio);
|
||||
|
||||
/*! \brief Select up and down pulls on specific GPIO
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param up If true set a pull up on the GPIO
|
||||
* \param down If true set a pull down on the GPIO
|
||||
*
|
||||
* \note On the RP2040, setting both pulls enables a "bus keep" function,
|
||||
* i.e. a weak pull to whatever is current high/low state of GPIO.
|
||||
*/
|
||||
void gpio_set_pulls(uint gpio, bool up, bool down);
|
||||
|
||||
/*! \brief Set specified GPIO to be pulled up.
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
*/
|
||||
static inline void gpio_pull_up(uint gpio) {
|
||||
gpio_set_pulls(gpio, true, false);
|
||||
}
|
||||
|
||||
/*! \brief Determine if the specified GPIO is pulled up.
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \return true if the GPIO is pulled up
|
||||
*/
|
||||
static inline bool gpio_is_pulled_up(uint gpio) {
|
||||
return (padsbank0_hw->io[gpio] & PADS_BANK0_GPIO0_PUE_BITS) != 0;
|
||||
}
|
||||
|
||||
/*! \brief Set specified GPIO to be pulled down.
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
*/
|
||||
static inline void gpio_pull_down(uint gpio) {
|
||||
gpio_set_pulls(gpio, false, true);
|
||||
}
|
||||
|
||||
/*! \brief Determine if the specified GPIO is pulled down.
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \return true if the GPIO is pulled down
|
||||
*/
|
||||
static inline bool gpio_is_pulled_down(uint gpio) {
|
||||
return (padsbank0_hw->io[gpio] & PADS_BANK0_GPIO0_PDE_BITS) != 0;
|
||||
}
|
||||
|
||||
/*! \brief Disable pulls on specified GPIO
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
*/
|
||||
static inline void gpio_disable_pulls(uint gpio) {
|
||||
gpio_set_pulls(gpio, false, false);
|
||||
}
|
||||
|
||||
/*! \brief Set GPIO output override
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param value See \ref gpio_override
|
||||
*/
|
||||
void gpio_set_outover(uint gpio, uint value);
|
||||
|
||||
/*! \brief Select GPIO input override
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param value See \ref gpio_override
|
||||
*/
|
||||
void gpio_set_inover(uint gpio, uint value);
|
||||
|
||||
/*! \brief Select GPIO output enable override
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param value See \ref gpio_override
|
||||
*/
|
||||
void gpio_set_oeover(uint gpio, uint value);
|
||||
|
||||
/*! \brief Enable GPIO input
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param enabled true to enable input on specified GPIO
|
||||
*/
|
||||
void gpio_set_input_enabled(uint gpio, bool enabled);
|
||||
|
||||
/*! \brief Enable or disable interrupts for specified GPIO
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \note The IO IRQs are independent per-processor. This configures IRQs for
|
||||
* the processor that calls the function.
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param events Which events will cause an interrupt
|
||||
* \param enabled Enable or disable flag
|
||||
*
|
||||
* Events is a bitmask of the following:
|
||||
*
|
||||
* bit | interrupt
|
||||
* ----|----------
|
||||
* 0 | Low level
|
||||
* 1 | High level
|
||||
* 2 | Edge low
|
||||
* 3 | Edge high
|
||||
*/
|
||||
void gpio_set_irq_enabled(uint gpio, uint32_t events, bool enabled);
|
||||
|
||||
/*! \brief Enable interrupts for specified GPIO
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \note The IO IRQs are independent per-processor. This configures IRQs for
|
||||
* the processor that calls the function.
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param events Which events will cause an interrupt See \ref gpio_set_irq_enabled for details.
|
||||
* \param enabled Enable or disable flag
|
||||
* \param callback user function to call on GPIO irq. Note only one of these can be set per processor.
|
||||
*
|
||||
* \note Currently the GPIO parameter is ignored, and this callback will be called for any enabled GPIO IRQ on any pin.
|
||||
*
|
||||
*/
|
||||
void gpio_set_irq_enabled_with_callback(uint gpio, uint32_t events, bool enabled, gpio_irq_callback_t callback);
|
||||
|
||||
/*! \brief Enable dormant wake up interrupt for specified GPIO
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* This configures IRQs to restart the XOSC or ROSC when they are
|
||||
* disabled in dormant mode
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param events Which events will cause an interrupt. See \ref gpio_set_irq_enabled for details.
|
||||
* \param enabled Enable/disable flag
|
||||
*/
|
||||
void gpio_set_dormant_irq_enabled(uint gpio, uint32_t events, bool enabled);
|
||||
|
||||
/*! \brief Acknowledge a GPIO interrupt
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param events Bitmask of events to clear. See \ref gpio_set_irq_enabled for details.
|
||||
*
|
||||
*/
|
||||
void gpio_acknowledge_irq(uint gpio, uint32_t events);
|
||||
|
||||
/*! \brief Initialise a GPIO for (enabled I/O and set func to GPIO_FUNC_SIO)
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* Clear the output enable (i.e. set to input)
|
||||
* Clear any output value.
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
*/
|
||||
void gpio_init(uint gpio);
|
||||
|
||||
/*! \brief Initialise multiple GPIOs (enabled I/O and set func to GPIO_FUNC_SIO)
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* Clear the output enable (i.e. set to input)
|
||||
* Clear any output value.
|
||||
*
|
||||
* \param gpio_mask Mask with 1 bit per GPIO number to initialize
|
||||
*/
|
||||
void gpio_init_mask(uint gpio_mask);
|
||||
// ----------------------------------------------------------------------------
|
||||
// Input
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/*! \brief Get state of a single specified GPIO
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \return Current state of the GPIO. 0 for low, non-zero for high
|
||||
*/
|
||||
static inline bool gpio_get(uint gpio) {
|
||||
return !!((1ul << gpio) & sio_hw->gpio_in);
|
||||
}
|
||||
|
||||
/*! \brief Get raw value of all GPIOs
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \return Bitmask of raw GPIO values, as bits 0-29
|
||||
*/
|
||||
static inline uint32_t gpio_get_all() {
|
||||
return sio_hw->gpio_in;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Output
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/*! \brief Drive high every GPIO appearing in mask
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param mask Bitmask of GPIO values to set, as bits 0-29
|
||||
*/
|
||||
static inline void gpio_set_mask(uint32_t mask) {
|
||||
sio_hw->gpio_set = mask;
|
||||
}
|
||||
|
||||
/*! \brief Drive low every GPIO appearing in mask
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param mask Bitmask of GPIO values to clear, as bits 0-29
|
||||
*/
|
||||
static inline void gpio_clr_mask(uint32_t mask) {
|
||||
sio_hw->gpio_clr = mask;
|
||||
}
|
||||
|
||||
/*! \brief Toggle every GPIO appearing in mask
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param mask Bitmask of GPIO values to toggle, as bits 0-29
|
||||
*/
|
||||
static inline void gpio_xor_mask(uint32_t mask) {
|
||||
sio_hw->gpio_togl = mask;
|
||||
}
|
||||
|
||||
/*! \brief Drive GPIO high/low depending on parameters
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param mask Bitmask of GPIO values to change, as bits 0-29
|
||||
* \param value Value to set
|
||||
*
|
||||
* For each 1 bit in \p mask, drive that pin to the value given by
|
||||
* corresponding bit in \p value, leaving other pins unchanged.
|
||||
* Since this uses the TOGL alias, it is concurrency-safe with e.g. an IRQ
|
||||
* bashing different pins from the same core.
|
||||
*/
|
||||
static inline void gpio_put_masked(uint32_t mask, uint32_t value) {
|
||||
sio_hw->gpio_togl = (sio_hw->gpio_out ^ value) & mask;
|
||||
}
|
||||
|
||||
/*! \brief Drive all pins simultaneously
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param value Bitmask of GPIO values to change, as bits 0-29
|
||||
*/
|
||||
static inline void gpio_put_all(uint32_t value) {
|
||||
sio_hw->gpio_out = value;
|
||||
}
|
||||
|
||||
/*! \brief Drive a single GPIO high/low
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param value If false clear the GPIO, otherwise set it.
|
||||
*/
|
||||
static inline void gpio_put(uint gpio, bool value) {
|
||||
uint32_t mask = 1ul << gpio;
|
||||
if (value)
|
||||
gpio_set_mask(mask);
|
||||
else
|
||||
gpio_clr_mask(mask);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Direction
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/*! \brief Set a number of GPIOs to output
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* Switch all GPIOs in "mask" to output
|
||||
*
|
||||
* \param mask Bitmask of GPIO to set to output, as bits 0-29
|
||||
*/
|
||||
static inline void gpio_set_dir_out_masked(uint32_t mask) {
|
||||
sio_hw->gpio_oe_set = mask;
|
||||
}
|
||||
|
||||
/*! \brief Set a number of GPIOs to input
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param mask Bitmask of GPIO to set to input, as bits 0-29
|
||||
*/
|
||||
static inline void gpio_set_dir_in_masked(uint32_t mask) {
|
||||
sio_hw->gpio_oe_clr = mask;
|
||||
}
|
||||
|
||||
/*! \brief Set multiple GPIO directions
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param mask Bitmask of GPIO to set to input, as bits 0-29
|
||||
* \param value Values to set
|
||||
*
|
||||
* For each 1 bit in "mask", switch that pin to the direction given by
|
||||
* corresponding bit in "value", leaving other pins unchanged.
|
||||
* E.g. gpio_set_dir_masked(0x3, 0x2); -> set pin 0 to input, pin 1 to output,
|
||||
* simultaneously.
|
||||
*/
|
||||
static inline void gpio_set_dir_masked(uint32_t mask, uint32_t value) {
|
||||
sio_hw->gpio_oe_togl = (sio_hw->gpio_oe ^ value) & mask;
|
||||
}
|
||||
|
||||
/*! \brief Set direction of all pins simultaneously.
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param values individual settings for each gpio; for GPIO N, bit N is 1 for out, 0 for in
|
||||
*/
|
||||
static inline void gpio_set_dir_all_bits(uint32_t values) {
|
||||
sio_hw->gpio_oe = values;
|
||||
}
|
||||
|
||||
/*! \brief Set a single GPIO direction
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \param out true for out, false for in
|
||||
*/
|
||||
static inline void gpio_set_dir(uint gpio, bool out) {
|
||||
uint32_t mask = 1ul << gpio;
|
||||
if (out)
|
||||
gpio_set_dir_out_masked(mask);
|
||||
else
|
||||
gpio_set_dir_in_masked(mask);
|
||||
}
|
||||
|
||||
/*! \brief Check if a specific GPIO direction is OUT
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \return true if the direction for the pin is OUT
|
||||
*/
|
||||
static inline bool gpio_is_dir_out(uint gpio) {
|
||||
return !!(sio_hw->gpio_oe & (1u << (gpio)));
|
||||
}
|
||||
|
||||
/*! \brief Get a specific GPIO direction
|
||||
* \ingroup hardware_gpio
|
||||
*
|
||||
* \param gpio GPIO number
|
||||
* \return 1 for out, 0 for in
|
||||
*/
|
||||
static inline uint gpio_get_dir(uint gpio) {
|
||||
return gpio_is_dir_out(gpio); // note GPIO_OUT is 1/true and GPIO_IN is 0/false anyway
|
||||
}
|
||||
|
||||
extern void gpio_debug_pins_init();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// PICO_CONFIG: PICO_DEBUG_PIN_BASE, First pin to use for debug output (if enabled), min=0, max=28, default=19, group=hardware_gpio
|
||||
#ifndef PICO_DEBUG_PIN_BASE
|
||||
#define PICO_DEBUG_PIN_BASE 19u
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_DEBUG_PIN_COUNT, Number of pins to use for debug output (if enabled), min=1, max=28, default=3, group=hardware_gpio
|
||||
#ifndef PICO_DEBUG_PIN_COUNT
|
||||
#define PICO_DEBUG_PIN_COUNT 3u
|
||||
#endif
|
||||
|
||||
#ifndef __cplusplus
|
||||
// note these two macros may only be used once per and only apply per compilation unit (hence the CU_)
|
||||
#define CU_REGISTER_DEBUG_PINS(...) enum __unused DEBUG_PIN_TYPE { _none = 0, __VA_ARGS__ }; static enum DEBUG_PIN_TYPE __selected_debug_pins;
|
||||
#define CU_SELECT_DEBUG_PINS(x) static enum DEBUG_PIN_TYPE __selected_debug_pins = (x);
|
||||
#define DEBUG_PINS_ENABLED(p) (__selected_debug_pins == (p))
|
||||
#else
|
||||
#define CU_REGISTER_DEBUG_PINS(p...) \
|
||||
enum DEBUG_PIN_TYPE { _none = 0, p }; \
|
||||
template <enum DEBUG_PIN_TYPE> class __debug_pin_settings { \
|
||||
public: \
|
||||
static inline bool enabled() { return false; } \
|
||||
};
|
||||
#define CU_SELECT_DEBUG_PINS(x) template<> inline bool __debug_pin_settings<x>::enabled() { return true; };
|
||||
#define DEBUG_PINS_ENABLED(p) (__debug_pin_settings<p>::enabled())
|
||||
#endif
|
||||
#define DEBUG_PINS_SET(p, v) if (DEBUG_PINS_ENABLED(p)) gpio_set_mask((unsigned)(v)<<PICO_DEBUG_PIN_BASE)
|
||||
#define DEBUG_PINS_CLR(p, v) if (DEBUG_PINS_ENABLED(p)) gpio_clr_mask((unsigned)(v)<<PICO_DEBUG_PIN_BASE)
|
||||
#define DEBUG_PINS_XOR(p, v) if (DEBUG_PINS_ENABLED(p)) gpio_xor_mask((unsigned)(v)<<PICO_DEBUG_PIN_BASE)
|
||||
|
||||
#endif // _GPIO_H_
|
1
src/rp2_common/hardware_i2c/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_i2c/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(i2c)
|
277
src/rp2_common/hardware_i2c/i2c.c
Normal file
277
src/rp2_common/hardware_i2c/i2c.c
Normal file
@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/i2c.h"
|
||||
#include "hardware/resets.h"
|
||||
#include "hardware/clocks.h"
|
||||
#include "pico/timeout_helper.h"
|
||||
|
||||
check_hw_layout(i2c_hw_t, enable, I2C_IC_ENABLE_OFFSET);
|
||||
check_hw_layout(i2c_hw_t, clr_restart_det, I2C_IC_CLR_RESTART_DET_OFFSET);
|
||||
|
||||
i2c_inst_t i2c0_inst = {i2c0_hw, false};
|
||||
i2c_inst_t i2c1_inst = {i2c1_hw, false};
|
||||
|
||||
static inline void i2c_reset(i2c_inst_t *i2c) {
|
||||
invalid_params_if(I2C, i2c != i2c0 && i2c != i2c1);
|
||||
reset_block(i2c == i2c0 ? RESETS_RESET_I2C0_BITS : RESETS_RESET_I2C1_BITS);
|
||||
}
|
||||
|
||||
static inline void i2c_unreset(i2c_inst_t *i2c) {
|
||||
invalid_params_if(I2C, i2c != i2c0 && i2c != i2c1);
|
||||
unreset_block_wait(i2c == i2c0 ? RESETS_RESET_I2C0_BITS : RESETS_RESET_I2C1_BITS);
|
||||
}
|
||||
|
||||
// Addresses of the form 000 0xxx or 111 1xxx are reserved. No slave should
|
||||
// have these addresses.
|
||||
static inline bool i2c_reserved_addr(uint8_t addr) {
|
||||
return (addr & 0x78) == 0 || (addr & 0x78) == 0x78;
|
||||
}
|
||||
|
||||
uint i2c_init(i2c_inst_t *i2c, uint baudrate) {
|
||||
i2c_reset(i2c);
|
||||
i2c_unreset(i2c);
|
||||
i2c->restart_on_next = false;
|
||||
|
||||
i2c->hw->enable = 0;
|
||||
|
||||
// Configure as a fast-mode master with RepStart support, 7-bit addresses
|
||||
i2c->hw->con =
|
||||
I2C_IC_CON_SPEED_VALUE_FAST << I2C_IC_CON_SPEED_LSB |
|
||||
I2C_IC_CON_MASTER_MODE_BITS |
|
||||
I2C_IC_CON_IC_SLAVE_DISABLE_BITS |
|
||||
I2C_IC_CON_IC_RESTART_EN_BITS;
|
||||
|
||||
// Set FIFO watermarks to 1 to make things simpler. This is encoded by a register value of 0.
|
||||
i2c->hw->tx_tl = 0;
|
||||
i2c->hw->rx_tl = 0;
|
||||
|
||||
// Always enable the DREQ signalling -- harmless if DMA isn't listening
|
||||
i2c->hw->dma_cr = I2C_IC_DMA_CR_TDMAE_BITS | I2C_IC_DMA_CR_RDMAE_BITS;
|
||||
|
||||
// Re-sets i2c->hw->enable upon returning:
|
||||
return i2c_set_baudrate(i2c, baudrate);
|
||||
}
|
||||
|
||||
void i2c_deinit(i2c_inst_t *i2c) {
|
||||
i2c_reset(i2c);
|
||||
}
|
||||
|
||||
uint i2c_set_baudrate(i2c_inst_t *i2c, uint baudrate) {
|
||||
invalid_params_if(I2C, baudrate == 0);
|
||||
// I2C is synchronous design that runs from clk_sys
|
||||
uint freq_in = clock_get_hz(clk_sys);
|
||||
|
||||
// TODO there are some subtleties to I2C timing which we are completely ignoring here
|
||||
uint period = (freq_in + baudrate / 2) / baudrate;
|
||||
uint hcnt = period * 3 / 5; // oof this one hurts
|
||||
uint lcnt = period - hcnt;
|
||||
// Check for out-of-range divisors:
|
||||
invalid_params_if(I2C, hcnt > I2C_IC_FS_SCL_HCNT_IC_FS_SCL_HCNT_BITS);
|
||||
invalid_params_if(I2C, lcnt > I2C_IC_FS_SCL_LCNT_IC_FS_SCL_LCNT_BITS);
|
||||
invalid_params_if(I2C, hcnt < 8);
|
||||
invalid_params_if(I2C, lcnt < 8);
|
||||
|
||||
i2c->hw->enable = 0;
|
||||
// Always use "fast" mode (<= 400 kHz, works fine for standard mode too)
|
||||
hw_write_masked(&i2c->hw->con,
|
||||
I2C_IC_CON_SPEED_VALUE_FAST << I2C_IC_CON_SPEED_LSB,
|
||||
I2C_IC_CON_SPEED_BITS
|
||||
);
|
||||
i2c->hw->fs_scl_hcnt = hcnt;
|
||||
i2c->hw->fs_scl_lcnt = lcnt;
|
||||
i2c->hw->fs_spklen = lcnt < 16 ? 1 : lcnt / 16;
|
||||
|
||||
i2c->hw->enable = 1;
|
||||
return freq_in / period;
|
||||
}
|
||||
|
||||
void i2c_set_slave_mode(i2c_inst_t *i2c, bool slave, uint8_t addr) {
|
||||
invalid_params_if(I2C, addr >= 0x80); // 7-bit addresses
|
||||
invalid_params_if(I2C, i2c_reserved_addr(addr));
|
||||
i2c->hw->enable = 0;
|
||||
if (slave) {
|
||||
hw_clear_bits(&i2c->hw->con,
|
||||
I2C_IC_CON_MASTER_MODE_BITS |
|
||||
I2C_IC_CON_IC_SLAVE_DISABLE_BITS
|
||||
);
|
||||
i2c->hw->sar = addr;
|
||||
} else {
|
||||
hw_set_bits(&i2c->hw->con,
|
||||
I2C_IC_CON_MASTER_MODE_BITS |
|
||||
I2C_IC_CON_IC_SLAVE_DISABLE_BITS
|
||||
);
|
||||
}
|
||||
i2c->hw->enable = 1;
|
||||
}
|
||||
|
||||
static int i2c_write_blocking_internal(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop,
|
||||
check_timeout_fn timeout_check, struct timeout_state *ts) {
|
||||
invalid_params_if(I2C, addr >= 0x80); // 7-bit addresses
|
||||
invalid_params_if(I2C, i2c_reserved_addr(addr));
|
||||
// Synopsys hw accepts start/stop flags alongside data items in the same
|
||||
// FIFO word, so no 0 byte transfers.
|
||||
invalid_params_if(I2C, len == 0);
|
||||
|
||||
i2c->hw->enable = 0;
|
||||
i2c->hw->tar = addr;
|
||||
i2c->hw->enable = 1;
|
||||
|
||||
bool abort = false;
|
||||
bool timeout = false;
|
||||
|
||||
uint32_t abort_reason;
|
||||
size_t byte_ctr;
|
||||
|
||||
for (byte_ctr = 0; byte_ctr < len; ++byte_ctr) {
|
||||
bool first = byte_ctr == 0;
|
||||
bool last = byte_ctr == len - 1;
|
||||
|
||||
i2c->hw->data_cmd =
|
||||
!!(first && i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB |
|
||||
!!(last && !nostop) << I2C_IC_DATA_CMD_STOP_LSB |
|
||||
*src++;
|
||||
|
||||
do {
|
||||
// Note clearing the abort flag also clears the reason, and this
|
||||
// instance of flag is clear-on-read!
|
||||
abort_reason = i2c->hw->tx_abrt_source;
|
||||
abort = (bool) i2c->hw->clr_tx_abrt;
|
||||
if (timeout_check) {
|
||||
timeout = timeout_check(ts);
|
||||
abort |= timeout;
|
||||
}
|
||||
tight_loop_contents();
|
||||
} while (!abort && !(i2c->hw->status & I2C_IC_STATUS_TFE_BITS));
|
||||
|
||||
// Note the hardware issues a STOP automatically on an abort condition.
|
||||
// Note also the hardware clears RX FIFO as well as TX on abort,
|
||||
// because we set hwparam IC_AVOID_RX_FIFO_FLUSH_ON_TX_ABRT to 0.
|
||||
if (abort)
|
||||
break;
|
||||
}
|
||||
|
||||
int rval;
|
||||
|
||||
// A lot of things could have just happened due to the ingenious and
|
||||
// creative design of I2C. Try to figure things out.
|
||||
if (abort) {
|
||||
if (timeout)
|
||||
rval = PICO_ERROR_TIMEOUT;
|
||||
else if (!abort_reason || abort_reason & I2C_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK_BITS) {
|
||||
// No reported errors - seems to happen if there is nothing connected to the bus.
|
||||
// Address byte not acknowledged
|
||||
rval = PICO_ERROR_GENERIC;
|
||||
} else if (abort_reason & I2C_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK_BITS) {
|
||||
// Address acknowledged, some data not acknowledged
|
||||
rval = byte_ctr;
|
||||
} else {
|
||||
//panic("Unknown abort from I2C instance @%08x: %08x\n", (uint32_t) i2c->hw, abort_reason);
|
||||
rval = PICO_ERROR_GENERIC;
|
||||
}
|
||||
} else {
|
||||
rval = byte_ctr;
|
||||
}
|
||||
|
||||
// nostop means we are now at the end of a *message* but not the end of a *transfer*
|
||||
i2c->restart_on_next = nostop;
|
||||
return rval;
|
||||
}
|
||||
|
||||
int i2c_write_blocking(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop) {
|
||||
return i2c_write_blocking_internal(i2c, addr, src, len, nostop, NULL, NULL);
|
||||
}
|
||||
|
||||
int i2c_write_blocking_until(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop,
|
||||
absolute_time_t until) {
|
||||
timeout_state_t ts;
|
||||
return i2c_write_blocking_internal(i2c, addr, src, len, nostop, init_single_timeout_until(&ts, until), &ts);
|
||||
}
|
||||
|
||||
int i2c_write_timeout_per_char_us(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop,
|
||||
uint timeout_per_char_us) {
|
||||
timeout_state_t ts;
|
||||
return i2c_write_blocking_internal(i2c, addr, src, len, nostop,
|
||||
init_per_iteration_timeout_us(&ts, timeout_per_char_us), &ts);
|
||||
}
|
||||
|
||||
static int i2c_read_blocking_internal(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop,
|
||||
check_timeout_fn timeout_check, timeout_state_t *ts) {
|
||||
invalid_params_if(I2C, addr >= 0x80); // 7-bit addresses
|
||||
invalid_params_if(I2C, i2c_reserved_addr(addr));
|
||||
invalid_params_if(I2C, len == 0);
|
||||
|
||||
i2c->hw->enable = 0;
|
||||
i2c->hw->tar = addr;
|
||||
i2c->hw->enable = 1;
|
||||
|
||||
bool abort = false;
|
||||
bool timeout = false;
|
||||
uint32_t abort_reason;
|
||||
size_t byte_ctr;
|
||||
|
||||
for (byte_ctr = 0; byte_ctr < len; ++byte_ctr) {
|
||||
bool first = byte_ctr == 0;
|
||||
bool last = byte_ctr == len - 1;
|
||||
while (!i2c_get_write_available(i2c))
|
||||
tight_loop_contents();
|
||||
|
||||
i2c->hw->data_cmd =
|
||||
!!(first && i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB |
|
||||
!!(last && !nostop) << I2C_IC_DATA_CMD_STOP_LSB |
|
||||
I2C_IC_DATA_CMD_CMD_BITS; // -> 1 for read
|
||||
|
||||
do {
|
||||
abort_reason = i2c->hw->tx_abrt_source;
|
||||
abort = (bool) i2c->hw->clr_tx_abrt;
|
||||
if (timeout_check) {
|
||||
timeout = timeout_check(ts);
|
||||
abort |= timeout;
|
||||
}
|
||||
} while (!abort && !i2c_get_read_available(i2c));
|
||||
|
||||
if (abort)
|
||||
break;
|
||||
|
||||
*dst++ = i2c->hw->data_cmd;
|
||||
}
|
||||
|
||||
int rval;
|
||||
|
||||
if (abort) {
|
||||
if (timeout)
|
||||
rval = PICO_ERROR_TIMEOUT;
|
||||
else if (!abort_reason || abort_reason & I2C_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK_BITS) {
|
||||
// No reported errors - seems to happen if there is nothing connected to the bus.
|
||||
// Address byte not acknowledged
|
||||
rval = PICO_ERROR_GENERIC;
|
||||
} else {
|
||||
// panic("Unknown abort from I2C instance @%08x: %08x\n", (uint32_t) i2c->hw, abort_reason);
|
||||
rval = PICO_ERROR_GENERIC;
|
||||
}
|
||||
} else {
|
||||
rval = byte_ctr;
|
||||
}
|
||||
|
||||
i2c->restart_on_next = nostop;
|
||||
return rval;
|
||||
}
|
||||
|
||||
int i2c_read_blocking(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop) {
|
||||
return i2c_read_blocking_internal(i2c, addr, dst, len, nostop, NULL, NULL);
|
||||
}
|
||||
|
||||
int i2c_read_blocking_until(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, absolute_time_t until) {
|
||||
timeout_state_t ts;
|
||||
return i2c_read_blocking_internal(i2c, addr, dst, len, nostop, init_single_timeout_until(&ts, until), &ts);
|
||||
}
|
||||
|
||||
int i2c_read_timeout_per_char_us(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop,
|
||||
uint timeout_per_char_us) {
|
||||
timeout_state_t ts;
|
||||
return i2c_read_blocking_internal(i2c, addr, dst, len, nostop,
|
||||
init_per_iteration_timeout_us(&ts, timeout_per_char_us), &ts);
|
||||
}
|
304
src/rp2_common/hardware_i2c/include/hardware/i2c.h
Normal file
304
src/rp2_common/hardware_i2c/include/hardware/i2c.h
Normal file
@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_I2C_H
|
||||
#define _HARDWARE_I2C_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "pico/time.h"
|
||||
#include "hardware/structs/i2c.h"
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_I2C, Enable/disable assertions in the I2C module, type=bool, default=0, group=hardware_i2c
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_I2C
|
||||
#define PARAM_ASSERTIONS_ENABLED_I2C 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file hardware/i2c.h
|
||||
* \defgroup hardware_i2c hardware_i2c
|
||||
*
|
||||
* I2C Controller API
|
||||
*
|
||||
* The I2C bus is a two-wire serial interface, consisting of a serial data line SDA and a serial clock SCL. These wires carry
|
||||
* information between the devices connected to the bus. Each device is recognized by a unique address and can operate as
|
||||
* either a “transmitter” or “receiver”, depending on the function of the device. Devices can also be considered as masters or
|
||||
* slaves when performing data transfers. A master is a device that initiates a data transfer on the bus and generates the
|
||||
* clock signals to permit that transfer. At that time, any device addressed is considered a slave.
|
||||
*
|
||||
* This API allows the controller to be set up as a master or a slave using the \ref i2c_set_slave_mode function.
|
||||
*
|
||||
* The external pins of each controller are connected to GPIO pins as defined in the GPIO muxing table in the datasheet. The muxing options
|
||||
* give some IO flexibility, but each controller external pin should be connected to only one GPIO.
|
||||
*
|
||||
* Note that the controller does NOT support High speed mode or Ultra-fast speed mode, the fastest operation being fast mode plus
|
||||
* at up to 1000Kb/s.
|
||||
*
|
||||
* See the datasheet for more information on the I2C controller and its usage.
|
||||
*
|
||||
* \subsection i2c_example Example
|
||||
* \addtogroup hardware_i2c
|
||||
* \include bus_scan.c
|
||||
*/
|
||||
|
||||
typedef struct i2c_inst i2c_inst_t;
|
||||
|
||||
/** The I2C identifiers for use in I2C functions.
|
||||
*
|
||||
* e.g. i2c_init(i2c0, 48000)
|
||||
*
|
||||
* \ingroup hardware_i2c
|
||||
* @{
|
||||
*/
|
||||
extern i2c_inst_t i2c0_inst;
|
||||
extern i2c_inst_t i2c1_inst;
|
||||
|
||||
#define i2c0 (&i2c0_inst) ///< Identifier for I2C HW Block 0
|
||||
#define i2c1 (&i2c1_inst) ///< Identifier for I2C HW Block 1
|
||||
|
||||
/** @} */
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Setup
|
||||
|
||||
/*! \brief Initialise the I2C HW block
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* Put the I2C hardware into a known state, and enable it. Must be called
|
||||
* before other functions. By default, the I2C is configured to operate as a
|
||||
* master.
|
||||
*
|
||||
* The I2C bus frequency is set as close as possible to requested, and
|
||||
* the return actual rate set is returned
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \param baudrate Baudrate in Hz (e.g. 100kHz is 100000)
|
||||
* \return Actual set baudrate
|
||||
*/
|
||||
uint i2c_init(i2c_inst_t *i2c, uint baudrate);
|
||||
|
||||
/*! \brief Disable the I2C HW block
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
*
|
||||
* Disable the I2C again if it is no longer used. Must be reinitialised before
|
||||
* being used again.
|
||||
*/
|
||||
void i2c_deinit(i2c_inst_t *i2c);
|
||||
|
||||
/*! \brief Set I2C baudrate
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* Set I2C bus frequency as close as possible to requested, and return actual
|
||||
* rate set.
|
||||
* Baudrate may not be as exactly requested due to clocking limitations.
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \param baudrate Baudrate in Hz (e.g. 100kHz is 100000)
|
||||
* \return Actual set baudrate
|
||||
*/
|
||||
uint i2c_set_baudrate(i2c_inst_t *i2c, uint baudrate);
|
||||
|
||||
/*! \brief Set I2C port to slave mode
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \param slave true to use slave mode, false to use master mode
|
||||
* \param addr If \p slave is true, set the slave address to this value
|
||||
*/
|
||||
void i2c_set_slave_mode(i2c_inst_t *i2c, bool slave, uint8_t addr);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Generic input/output
|
||||
|
||||
struct i2c_inst {
|
||||
i2c_hw_t *hw;
|
||||
bool restart_on_next;
|
||||
};
|
||||
|
||||
/*! \brief Convert I2c instance to hardware instance number
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c I2C instance
|
||||
* \return Number of UART, 0 or 1.
|
||||
*/
|
||||
static inline uint i2c_hw_index(i2c_inst_t *i2c) {
|
||||
invalid_params_if(I2C, i2c != i2c0 && i2c != i2c1);
|
||||
return i2c == i2c1 ? 1 : 0;
|
||||
}
|
||||
|
||||
static inline i2c_hw_t *i2c_get_hw(i2c_inst_t *i2c) {
|
||||
i2c_hw_index(i2c); // check it is a hw i2c
|
||||
return i2c->hw;
|
||||
}
|
||||
|
||||
/*! \brief Attempt to write specified number of bytes to address, blocking until the specified absolute time is reached.
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \param addr Address of device to write to
|
||||
* \param src Pointer to data to send
|
||||
* \param len Length of data in bytes to send
|
||||
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
|
||||
* and the next transfer will begin with a Restart rather than a Start.
|
||||
* \param until The absolute time that the block will wait until the entire transaction is complete. Note, an individual timeout of
|
||||
* this value divided by the length of data is applied for each byte transfer, so if the first or subsequent
|
||||
* bytes fails to transfer within that sub timeout, the function will return with an error.
|
||||
*
|
||||
* \return Number of bytes written, or PICO_ERROR_GENERIC if address not acknowledged, no device present, or PICO_ERROR_TIMEOUT if a timeout occurred.
|
||||
*/
|
||||
int i2c_write_blocking_until(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop, absolute_time_t until);
|
||||
|
||||
/*! \brief Attempt to read specified number of bytes from address, blocking until the specified absolute time is reached.
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \param addr Address of device to read from
|
||||
* \param dst Pointer to buffer to receive data
|
||||
* \param len Length of data in bytes to receive
|
||||
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
|
||||
* and the next transfer will begin with a Restart rather than a Start.
|
||||
* \param until The absolute time that the block will wait until the entire transaction is complete.
|
||||
* \return Number of bytes read, or PICO_ERROR_GENERIC if address not acknowledged, no device present, or PICO_ERROR_TIMEOUT if a timeout occurred.
|
||||
*/
|
||||
int i2c_read_blocking_until(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, absolute_time_t until);
|
||||
|
||||
/*! \brief Attempt to write specified number of bytes to address, with timeout
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \param addr Address of device to write to
|
||||
* \param src Pointer to data to send
|
||||
* \param len Length of data in bytes to send
|
||||
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
|
||||
* and the next transfer will begin with a Restart rather than a Start.
|
||||
* \param timeout_us The time that the function will wait for the entire transaction to complete. Note, an individual timeout of
|
||||
* this value divided by the length of data is applied for each byte transfer, so if the first or subsequent
|
||||
* bytes fails to transfer within that sub timeout, the function will return with an error.
|
||||
*
|
||||
* \return Number of bytes written, or PICO_ERROR_GENERIC if address not acknowledged, no device present, or PICO_ERROR_TIMEOUT if a timeout occurred.
|
||||
*/
|
||||
static inline int i2c_write_timeout_us(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop, uint timeout_us) {
|
||||
absolute_time_t t = make_timeout_time_us(timeout_us);
|
||||
return i2c_write_blocking_until(i2c, addr, src, len, nostop, t);
|
||||
}
|
||||
|
||||
int i2c_write_timeout_per_char_us(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop, uint timeout_per_char_us);
|
||||
|
||||
/*! \brief Attempt to read specified number of bytes from address, with timeout
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \param addr Address of device to read from
|
||||
* \param dst Pointer to buffer to receive data
|
||||
* \param len Length of data in bytes to receive
|
||||
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
|
||||
* and the next transfer will begin with a Restart rather than a Start.
|
||||
* \param timeout_us The time that the function will wait for the entire transaction to complete
|
||||
* \return Number of bytes read, or PICO_ERROR_GENERIC if address not acknowledged, no device present, or PICO_ERROR_TIMEOUT if a timeout occurred.
|
||||
*/
|
||||
static inline int i2c_read_timeout_us(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, uint timeout_us) {
|
||||
absolute_time_t t = make_timeout_time_us(timeout_us);
|
||||
return i2c_read_blocking_until(i2c, addr, dst, len, nostop, t);
|
||||
}
|
||||
|
||||
int i2c_read_timeout_per_char_us(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop, uint timeout_per_char_us);
|
||||
|
||||
/*! \brief Attempt to write specified number of bytes to address, blocking
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \param addr Address of device to write to
|
||||
* \param src Pointer to data to send
|
||||
* \param len Length of data in bytes to send
|
||||
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
|
||||
* and the next transfer will begin with a Restart rather than a Start.
|
||||
* \return Number of bytes written, or PICO_ERROR_GENERIC if address not acknowledged, no device present.
|
||||
*/
|
||||
int i2c_write_blocking(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, bool nostop);
|
||||
|
||||
/*! \brief Attempt to read specified number of bytes from address, blocking
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \param addr Address of device to read from
|
||||
* \param dst Pointer to buffer to receive data
|
||||
* \param len Length of data in bytes to receive
|
||||
* \param nostop If true, master retains control of the bus at the end of the transfer (no Stop is issued),
|
||||
* and the next transfer will begin with a Restart rather than a Start.
|
||||
* \return Number of bytes read, or PICO_ERROR_GENERIC if address not acknowledged, no device present.
|
||||
*/
|
||||
int i2c_read_blocking(i2c_inst_t *i2c, uint8_t addr, uint8_t *dst, size_t len, bool nostop);
|
||||
|
||||
|
||||
/*! \brief Determine non-blocking write space available
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \return 0 if no space is available in the I2C to write more data. If return is nonzero, at
|
||||
* least that many bytes can be written without blocking.
|
||||
*/
|
||||
static inline size_t i2c_get_write_available(i2c_inst_t *i2c) {
|
||||
const size_t IC_TX_BUFFER_DEPTH = 32;
|
||||
return IC_TX_BUFFER_DEPTH - i2c_get_hw(i2c)->txflr;
|
||||
}
|
||||
|
||||
/*! \brief Determine number of bytes received
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \return 0 if no data available, if return is nonzero at
|
||||
* least that many bytes can be read without blocking.
|
||||
*/
|
||||
static inline size_t i2c_get_read_available(i2c_inst_t *i2c) {
|
||||
return i2c_get_hw(i2c)->rxflr;
|
||||
}
|
||||
|
||||
/*! \brief Write direct to TX FIFO
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \param src Data to send
|
||||
* \param len Number of bytes to send
|
||||
*
|
||||
* Writes directly to the to I2C TX FIFO which us mainly useful for
|
||||
* slave-mode operation.
|
||||
*/
|
||||
static inline void i2c_write_raw_blocking(i2c_inst_t *i2c, const uint8_t *src, size_t len) {
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
// TODO NACK or STOP on end?
|
||||
while (!i2c_get_write_available(i2c))
|
||||
tight_loop_contents();
|
||||
i2c_get_hw(i2c)->data_cmd = *src++;
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Write direct to TX FIFO
|
||||
* \ingroup hardware_i2c
|
||||
*
|
||||
* \param i2c Either \ref i2c0 or \ref i2c1
|
||||
* \param dst Buffer to accept data
|
||||
* \param len Number of bytes to send
|
||||
*
|
||||
* Reads directly from the I2C RX FIFO which us mainly useful for
|
||||
* slave-mode operation.
|
||||
*/
|
||||
static inline void i2c_read_raw_blocking(i2c_inst_t *i2c, uint8_t *dst, size_t len) {
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
while (!i2c_get_read_available(i2c))
|
||||
tight_loop_contents();
|
||||
*dst++ = i2c_get_hw(i2c)->data_cmd;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
1
src/rp2_common/hardware_interp/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_interp/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(interp)
|
435
src/rp2_common/hardware_interp/include/hardware/interp.h
Normal file
435
src/rp2_common/hardware_interp/include/hardware/interp.h
Normal file
@ -0,0 +1,435 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_INTERP_H
|
||||
#define _HARDWARE_INTERP_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/interp.h"
|
||||
#include "hardware/regs/sio.h"
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_INTERP, Enable/disable assertions in the interpolation module, type=bool, default=0, group=hardware_interp
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_INTERP
|
||||
#define PARAM_ASSERTIONS_ENABLED_INTERP 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file hardware/interp.h
|
||||
* \defgroup hardware_interp hardware_interp
|
||||
*
|
||||
* Hardware Interpolator API
|
||||
*
|
||||
* Each core is equipped with two interpolators (INTERP0 and INTERP1) which can be used to accelerate
|
||||
* tasks by combining certain pre-configured simple operations into a single processor cycle. Intended
|
||||
* for cases where the pre-configured operation is repeated a large number of times, this results in
|
||||
* code which uses both fewer CPU cycles and fewer CPU registers in the time critical sections of the
|
||||
* code.
|
||||
*
|
||||
* The interpolators are used heavily to accelerate audio operations within the Pico SDK, but their
|
||||
* flexible configuration make it possible to optimise many other tasks such as quantization and
|
||||
* dithering, table lookup address generation, affine texture mapping, decompression and linear feedback.
|
||||
*
|
||||
* Please refer to the RP2040 datasheet for more information on the HW interpolators and how they work.
|
||||
*/
|
||||
|
||||
#define interp0 interp0_hw
|
||||
#define interp1 interp1_hw
|
||||
|
||||
/** \brief Interpolator configuration
|
||||
* \defgroup interp_config interp_config
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* Each interpolator needs to be configured, these functions provide handy helpers to set up configuration
|
||||
* structures.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
uint32_t ctrl;
|
||||
} interp_config;
|
||||
|
||||
static inline uint interp_index(interp_hw_t *interp) {
|
||||
assert(interp == interp0 || interp == interp1);
|
||||
return interp == interp1 ? 1 : 0;
|
||||
}
|
||||
|
||||
/*! \brief Claim the interpolator lane specified
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* Use this function to claim exclusive access to the specified interpolator lane.
|
||||
*
|
||||
* This function will panic if the lane is already claimed.
|
||||
*
|
||||
* \param interp Interpolator on which to claim a lane. interp0 or interp1
|
||||
* \param lane The lane number, 0 or 1.
|
||||
*/
|
||||
void interp_claim_lane(interp_hw_t *interp, uint lane);
|
||||
|
||||
/*! \brief Claim the interpolator lanes specified in the mask
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* \param interp Interpolator on which to claim lanes. interp0 or interp1
|
||||
* \param lane_mask Bit pattern of lanes to claim (only bits 0 and 1 are valid)
|
||||
*/
|
||||
void interp_claim_lane_mask(interp_hw_t *interp, uint lane_mask);
|
||||
|
||||
/*! \brief Release a previously claimed interpolator lane
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* \param interp Interpolator on which to release a lane. interp0 or interp1
|
||||
* \param lane The lane number, 0 or 1
|
||||
*/
|
||||
void interp_unclaim_lane(interp_hw_t *interp, uint lane);
|
||||
|
||||
/*! \brief Set the interpolator shift value
|
||||
* \ingroup interp_config
|
||||
*
|
||||
* Sets the number of bits the accumulator is shifted before masking, on each iteration.
|
||||
*
|
||||
* \param c Pointer to an interpolator config
|
||||
* \param shift Number of bits
|
||||
*/
|
||||
static inline void interp_config_set_shift(interp_config *c, uint shift) {
|
||||
valid_params_if(INTERP, shift < 32);
|
||||
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_SHIFT_BITS) |
|
||||
((shift << SIO_INTERP0_CTRL_LANE0_SHIFT_LSB) & SIO_INTERP0_CTRL_LANE0_SHIFT_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Set the interpolator mask range
|
||||
* \ingroup interp_config
|
||||
*
|
||||
* Sets the range of bits (least to most) that are allowed to pass through the interpolator
|
||||
*
|
||||
* \param c Pointer to interpolation config
|
||||
* \param mask_lsb The least significant bit allowed to pass
|
||||
* \param mask_msb The most significant bit allowed to pass
|
||||
*/
|
||||
static inline void interp_config_set_mask(interp_config *c, uint mask_lsb, uint mask_msb) {
|
||||
valid_params_if(INTERP, mask_msb < 32);
|
||||
valid_params_if(INTERP, mask_lsb <= mask_msb);
|
||||
c->ctrl = (c->ctrl & ~(SIO_INTERP0_CTRL_LANE0_MASK_LSB_BITS | SIO_INTERP0_CTRL_LANE0_MASK_MSB_BITS)) |
|
||||
((mask_lsb << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) & SIO_INTERP0_CTRL_LANE0_MASK_LSB_BITS) |
|
||||
((mask_msb << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB) & SIO_INTERP0_CTRL_LANE0_MASK_MSB_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Enable cross input
|
||||
* \ingroup interp_config
|
||||
*
|
||||
* Allows feeding of the accumulator content from the other lane back in to this lanes shift+mask hardware.
|
||||
* This will take effect even if the interp_config_set_add_raw option is set as the cross input mux is before the
|
||||
* shift+mask bypass
|
||||
*
|
||||
* \param c Pointer to interpolation config
|
||||
* \param cross_input If true, enable the cross input.
|
||||
*/
|
||||
static inline void interp_config_set_cross_input(interp_config *c, bool cross_input) {
|
||||
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_CROSS_INPUT_BITS) |
|
||||
(cross_input ? SIO_INTERP0_CTRL_LANE0_CROSS_INPUT_BITS : 0);
|
||||
}
|
||||
|
||||
/*! \brief Enable cross results
|
||||
* \ingroup interp_config
|
||||
*
|
||||
* Allows feeding of the other lane’s result into this lane’s accumulator on a POP operation.
|
||||
*
|
||||
* \param c Pointer to interpolation config
|
||||
* \param cross_result If true, enables the cross result
|
||||
*/
|
||||
static inline void interp_config_set_cross_result(interp_config *c, bool cross_result) {
|
||||
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_CROSS_RESULT_BITS) |
|
||||
(cross_result ? SIO_INTERP0_CTRL_LANE0_CROSS_RESULT_BITS : 0);
|
||||
}
|
||||
|
||||
/*! \brief Set sign extension
|
||||
* \ingroup interp_config
|
||||
*
|
||||
* Enables signed mode, where the shifted and masked accumulator value is sign-extended to 32 bits
|
||||
* before adding to BASE1, and LANE1 PEEK/POP results appear extended to 32 bits when read by processor.
|
||||
*
|
||||
* \param c Pointer to interpolation config
|
||||
* \param _signed If true, enables sign extension
|
||||
*/
|
||||
static inline void interp_config_set_signed(interp_config *c, bool _signed) {
|
||||
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_SIGNED_BITS) |
|
||||
(_signed ? SIO_INTERP0_CTRL_LANE0_SIGNED_BITS : 0);
|
||||
}
|
||||
|
||||
/*! \brief Set raw add option
|
||||
* \ingroup interp_config
|
||||
*
|
||||
* When enabled, mask + shift is bypassed for LANE0 result. This does not affect the FULL result.
|
||||
*
|
||||
* \param c Pointer to interpolation config
|
||||
* \param add_raw If true, enable raw add option.
|
||||
*/
|
||||
static inline void interp_config_set_add_raw(interp_config *c, bool add_raw) {
|
||||
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_ADD_RAW_BITS) |
|
||||
(add_raw ? SIO_INTERP0_CTRL_LANE0_ADD_RAW_BITS : 0);
|
||||
}
|
||||
|
||||
/*! \brief Set blend mode
|
||||
* \ingroup interp_config
|
||||
*
|
||||
* If enabled, LANE1 result is a linear interpolation between BASE0 and BASE1, controlled
|
||||
* by the 8 LSBs of lane 1 shift and mask value (a fractional number between 0 and 255/256ths)
|
||||
*
|
||||
* LANE0 result does not have BASE0 added (yields only the 8 LSBs of lane 1 shift+mask value)
|
||||
*
|
||||
* FULL result does not have lane 1 shift+mask value added (BASE2 + lane 0 shift+mask)
|
||||
*
|
||||
* LANE1 SIGNED flag controls whether the interpolation is signed or unsig
|
||||
*
|
||||
* \param c Pointer to interpolation config
|
||||
* \param blend Set true to enable blend mode.
|
||||
*/
|
||||
static inline void interp_config_set_blend(interp_config *c, bool blend) {
|
||||
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_BLEND_BITS) |
|
||||
(blend ? SIO_INTERP0_CTRL_LANE0_BLEND_BITS : 0);
|
||||
}
|
||||
|
||||
/*! \brief Set interpolator clamp mode (Interpolator 1 only)
|
||||
* \ingroup interp_config
|
||||
*
|
||||
* Only present on INTERP1 on each core. If CLAMP mode is enabled:
|
||||
* - LANE0 result is a shifted and masked ACCUM0, clamped by a lower bound of BASE0 and an upper bound of BASE1.
|
||||
* - Signedness of these comparisons is determined by LANE0_CTRL_SIGNED
|
||||
*
|
||||
* \param c Pointer to interpolation config
|
||||
* \param clamp Set true to enable clamp mode
|
||||
*/
|
||||
static inline void interp_config_set_clamp(interp_config *c, bool clamp) {
|
||||
c->ctrl = (c->ctrl & ~SIO_INTERP1_CTRL_LANE0_CLAMP_BITS) |
|
||||
(clamp ? SIO_INTERP1_CTRL_LANE0_CLAMP_BITS : 0);
|
||||
}
|
||||
|
||||
/*! \brief Set interpolator Force bits
|
||||
* \ingroup interp_config
|
||||
*
|
||||
* ORed into bits 29:28 of the lane result presented to the processor on the bus.
|
||||
*
|
||||
* No effect on the internal 32-bit datapath. Handy for using a lane to generate sequence
|
||||
* of pointers into flash or SRAM
|
||||
*
|
||||
* \param c Pointer to interpolation config
|
||||
* \param bits Sets the force bits to that specified. Range 0-3 (two bits)
|
||||
*/
|
||||
static inline void interp_config_set_force_bits(interp_config *c, uint bits) {
|
||||
invalid_params_if(INTERP, bits > 3);
|
||||
// note cannot use hw_set_bits on SIO
|
||||
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_FORCE_MSB_BITS) |
|
||||
(bits << SIO_INTERP0_CTRL_LANE0_FORCE_MSB_LSB);
|
||||
}
|
||||
|
||||
/*! \brief Get a default configuration
|
||||
* \ingroup interp_config
|
||||
*
|
||||
* \return A default interpolation configuration
|
||||
*/
|
||||
static inline interp_config interp_default_config() {
|
||||
interp_config c = {0};
|
||||
// Just pass through everything
|
||||
interp_config_set_mask(&c, 0, 31);
|
||||
return c;
|
||||
}
|
||||
|
||||
/*! \brief Send configuration to a lane
|
||||
* \ingroup interp_config
|
||||
*
|
||||
* If an invalid configuration is specified (ie a lane specific item is set on wrong lane),
|
||||
* depending on setup this function can panic.
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param lane The lane to set
|
||||
* \param config Pointer to interpolation config
|
||||
*/
|
||||
|
||||
static inline void interp_set_config(interp_hw_t *interp, uint lane, interp_config *config) {
|
||||
invalid_params_if(INTERP, lane > 1);
|
||||
invalid_params_if(INTERP, config->ctrl & SIO_INTERP1_CTRL_LANE0_CLAMP_BITS &&
|
||||
(!interp_index(interp) || lane)); // only interp1 lane 0 has clamp bit
|
||||
invalid_params_if(INTERP, config->ctrl & SIO_INTERP0_CTRL_LANE0_BLEND_BITS &&
|
||||
(interp_index(interp) || lane)); // only interp0 lane 0 has blend bit
|
||||
interp->ctrl[lane] = config->ctrl;
|
||||
}
|
||||
|
||||
/*! \brief Directly set the force bits on a specified lane
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* These bits are ORed into bits 29:28 of the lane result presented to the processor on the bus.
|
||||
* There is no effect on the internal 32-bit datapath.
|
||||
*
|
||||
* Useful for using a lane to generate sequence of pointers into flash or SRAM, saving a subsequent
|
||||
* OR or add operation.
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param lane The lane to set
|
||||
* \param bits The bits to set (bits 0 and 1, value range 0-3)
|
||||
*/
|
||||
static inline void interp_set_force_bits(interp_hw_t *interp, uint lane, uint bits) {
|
||||
// note cannot use hw_set_bits on SIO
|
||||
interp->ctrl[lane] |= (bits << SIO_INTERP0_CTRL_LANE0_FORCE_MSB_LSB);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
io_rw_32 accum[2];
|
||||
io_rw_32 base[3];
|
||||
io_rw_32 ctrl[2];
|
||||
} interp_hw_save_t;
|
||||
|
||||
/*! \brief Save the specified interpolator state
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* Can be used to save state if you need an interpolator for another purpose, state
|
||||
* can then be recovered afterwards and continue from that point
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param saver Pointer to the save structure to fill in
|
||||
*/
|
||||
void interp_save(interp_hw_t *interp, interp_hw_save_t *saver);
|
||||
|
||||
/*! \brief Restore an interpolator state
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param saver Pointer to save structure to reapply to the specified interpolator
|
||||
*/
|
||||
void interp_restore(interp_hw_t *interp, interp_hw_save_t *saver);
|
||||
|
||||
/*! \brief Sets the interpolator base register by lane
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param lane The lane number, 0 or 1 or 2
|
||||
* \param val The value to apply to the register
|
||||
*/
|
||||
static inline void interp_set_base(interp_hw_t *interp, uint lane, uint32_t val) {
|
||||
interp->base[lane] = val;
|
||||
}
|
||||
|
||||
/*! \brief Gets the content of interpolator base register by lane
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param lane The lane number, 0 or 1 or 2
|
||||
* \return The current content of the lane base register
|
||||
*/
|
||||
static inline uint32_t interp_get_base(interp_hw_t *interp, uint lane) {
|
||||
return interp->base[lane];
|
||||
}
|
||||
|
||||
/*! \brief Sets the interpolator base registers simultaneously
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* The lower 16 bits go to BASE0, upper bits to BASE1 simultaneously.
|
||||
* Each half is sign-extended to 32 bits if that lane’s SIGNED flag is set.
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param val The value to apply to the register
|
||||
*/
|
||||
static inline void interp_set_base_both(interp_hw_t *interp, uint32_t val) {
|
||||
interp->base01 = val;
|
||||
}
|
||||
|
||||
|
||||
/*! \brief Sets the interpolator accumulator register by lane
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param lane The lane number, 0 or 1
|
||||
* \param val The value to apply to the register
|
||||
*/
|
||||
static inline void interp_set_accumulator(interp_hw_t *interp, uint lane, uint32_t val) {
|
||||
interp->accum[lane] = val;
|
||||
}
|
||||
|
||||
/*! \brief Gets the content of the interpolator accumulator register by lane
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param lane The lane number, 0 or 1
|
||||
* \return The current content of the register
|
||||
*/
|
||||
static inline uint32_t interp_get_accumulator(interp_hw_t *interp, uint lane) {
|
||||
return interp->accum[lane];
|
||||
}
|
||||
|
||||
/*! \brief Read lane result, and write lane results to both accumulators to update the interpolator
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param lane The lane number, 0 or 1
|
||||
* \return The content of the lane result register
|
||||
*/
|
||||
static inline uint32_t interp_pop_lane_result(interp_hw_t *interp, uint lane) {
|
||||
return interp->pop[lane];
|
||||
}
|
||||
|
||||
/*! \brief Read lane result
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param lane The lane number, 0 or 1
|
||||
* \return The content of the lane result register
|
||||
*/
|
||||
static inline uint32_t interp_peek_lane_result(interp_hw_t *interp, uint lane) {
|
||||
return interp->peek[lane];
|
||||
}
|
||||
|
||||
/*! \brief Read lane result, and write lane results to both accumulators to update the interpolator
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \return The content of the FULL register
|
||||
*/
|
||||
static inline uint32_t interp_pop_full_result(interp_hw_t *interp) {
|
||||
return interp->pop[2];
|
||||
}
|
||||
|
||||
/*! \brief Read lane result
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \return The content of the FULL register
|
||||
*/
|
||||
static inline uint32_t interp_peek_full_result(interp_hw_t *interp) {
|
||||
return interp->peek[2];
|
||||
}
|
||||
|
||||
/*! \brief Add to accumulator
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* Atomically add the specified value to the accumulator on the specified lane
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param lane The lane number, 0 or 1
|
||||
* \param val Value to add
|
||||
* \return The content of the FULL register
|
||||
*/
|
||||
static inline void interp_add_accumulater(interp_hw_t *interp, uint lane, uint32_t val) {
|
||||
interp->add_raw[lane] = val;
|
||||
}
|
||||
|
||||
/*! \brief Get raw lane value
|
||||
* \ingroup hardware_interp
|
||||
*
|
||||
* Returns the raw shift and mask value from the specified lane, BASE0 is NOT added
|
||||
*
|
||||
* \param interp Interpolator instance, interp0 or interp1.
|
||||
* \param lane The lane number, 0 or 1
|
||||
* \return The raw shift/mask value
|
||||
*/
|
||||
static inline uint32_t interp_get_raw(interp_hw_t *interp, uint lane) {
|
||||
return interp->add_raw[lane];
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
55
src/rp2_common/hardware_interp/interp.c
Normal file
55
src/rp2_common/hardware_interp/interp.c
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/interp.h"
|
||||
#include "hardware/structs/sio.h"
|
||||
#include "hardware/claim.h"
|
||||
|
||||
check_hw_size(interp_hw_t, SIO_INTERP1_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET);
|
||||
|
||||
check_hw_layout(sio_hw_t, interp, SIO_INTERP0_ACCUM0_OFFSET);
|
||||
|
||||
static_assert(NUM_DMA_CHANNELS <= 16, "");
|
||||
|
||||
static uint8_t _claimed;
|
||||
|
||||
void interp_claim_lane(interp_hw_t *interp, uint lane) {
|
||||
valid_params_if(INTERP, lane < 2);
|
||||
uint bit = (interp_index(interp) << 1u) | lane;
|
||||
hw_claim_or_assert((uint8_t *) &_claimed, bit, "Lane is already claimed");
|
||||
}
|
||||
|
||||
void interp_claim_lane_mask(interp_hw_t *interp, uint lane_mask) {
|
||||
valid_params_if(INTERP, lane_mask && lane_mask <= 0x3);
|
||||
if (lane_mask & 1u) interp_claim_lane(interp, 0);
|
||||
if (lane_mask & 2u) interp_claim_lane(interp, 1);
|
||||
}
|
||||
|
||||
void interp_unclaim_lane(interp_hw_t *interp, uint lane) {
|
||||
valid_params_if(INTERP, lane < 2);
|
||||
uint bit = (interp_index(interp) << 1u) | lane;
|
||||
hw_claim_clear((uint8_t *) &_claimed, bit);
|
||||
}
|
||||
|
||||
void interp_save(interp_hw_t *interp, interp_hw_save_t *saver) {
|
||||
saver->accum[0] = interp->accum[0];
|
||||
saver->accum[1] = interp->accum[1];
|
||||
saver->base[0] = interp->base[0];
|
||||
saver->base[1] = interp->base[1];
|
||||
saver->base[2] = interp->base[2];
|
||||
saver->ctrl[0] = interp->ctrl[0];
|
||||
saver->ctrl[1] = interp->ctrl[1];
|
||||
}
|
||||
|
||||
void interp_restore(interp_hw_t *interp, interp_hw_save_t *saver) {
|
||||
interp->accum[0] = saver->accum[0];
|
||||
interp->accum[1] = saver->accum[1];
|
||||
interp->base[0] = saver->base[0];
|
||||
interp->base[1] = saver->base[1];
|
||||
interp->base[2] = saver->base[2];
|
||||
interp->ctrl[0] = saver->ctrl[0];
|
||||
interp->ctrl[1] = saver->ctrl[1];
|
||||
}
|
6
src/rp2_common/hardware_irq/CMakeLists.txt
Normal file
6
src/rp2_common/hardware_irq/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
pico_simple_hardware_target(irq)
|
||||
|
||||
# additional sources/libraries
|
||||
|
||||
target_sources(hardware_irq INTERFACE ${CMAKE_CURRENT_LIST_DIR}/irq_handler_chain.S)
|
||||
target_link_libraries(hardware_irq INTERFACE pico_sync)
|
264
src/rp2_common/hardware_irq/include/hardware/irq.h
Normal file
264
src/rp2_common/hardware_irq/include/hardware/irq.h
Normal file
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_IRQ_H_
|
||||
#define _HARDWARE_IRQ_H_
|
||||
|
||||
// These two config items are also used by assembler, so keeping separate
|
||||
// PICO_CONFIG: PICO_MAX_SHARED_IRQ_HANDLERS, Maximum Number of shared IRQ handers, default=4, advanced=true, group=hardware_irq
|
||||
#ifndef PICO_MAX_SHARED_IRQ_HANDLERS
|
||||
#define PICO_MAX_SHARED_IRQ_HANDLERS 4u
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_DISABLE_SHARED_IRQ_HANDLERS, Disable shared IRQ handers, type=bool, default=0, group=hardware_irq
|
||||
#ifndef PICO_DISABLE_SHARED_IRQ_HANDLERS
|
||||
#define PICO_DISABLE_SHARED_IRQ_HANDLERS 0
|
||||
#endif
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/regs/intctrl.h"
|
||||
#include "hardware/regs/m0plus.h"
|
||||
|
||||
/** \file irq.h
|
||||
* \defgroup hardware_irq hardware_irq
|
||||
*
|
||||
* Hardware interrupt handling
|
||||
*
|
||||
* The RP2040 uses the standard ARM nested vectored interrupt controller (NVIC).
|
||||
*
|
||||
* Interrupts are identified by a number from 0 to 31.
|
||||
*
|
||||
* On the RP2040, only the lower 26 IRQ signals are connected on the NVIC; IRQs 26 to 31 are tied to zero (never firing).
|
||||
*
|
||||
* There is one NVIC per core, and each core's NVIC has the same hardware interrupt lines routed to it, with the exception of the IO interrupts
|
||||
* where there is one IO interrupt per bank, per core. These are completely independent, so for example, processor 0 can be
|
||||
* interrupted by GPIO 0 in bank 0, and processor 1 by GPIO 1 in the same bank.
|
||||
*
|
||||
* \note That all IRQ APIs affect the executing core only (i.e. the core calling the function).
|
||||
*
|
||||
* \note You should not enable the same (shared) IRQ number on both cores, as this will lead to race conditions
|
||||
* or starvation of one of the cores. Additionally don't forget that disabling interrupts on one core does not disable interrupts
|
||||
* on the other core.
|
||||
*
|
||||
* There are three different ways to set handlers for an IRQ:
|
||||
* - Calling irq_add_shared_handler() at runtime to add a handler for a multiplexed interrupt (e.g. GPIO bank) on the current core. Each handler, should check and clear the relevant hardware interrupt source
|
||||
* - Calling irq_set_exclusive_handler() at runtime to install a single handler for the interrupt on the current core
|
||||
* - Defining the interrupt handler explicitly in your application (e.g. by defining void `isr_dma_0` will make that function the handler for the DMA_IRQ_0 on core 0, and
|
||||
* you will not be able to change it using the above APIs at runtime). Using this method can cause link conflicts at runtime, and offers no runtime performance benefit (i.e, it should not generally be used).
|
||||
*
|
||||
* \note If an IRQ is enabled and fires with no handler installed, a breakpoint will be hit and the IRQ number will
|
||||
* be in r0.
|
||||
*
|
||||
* \section interrupt_nums Interrupt Numbers
|
||||
*
|
||||
* Interrupts are numbered as follows, a set of defines is available (intctrl.h) with these names to avoid using the numbers directly.
|
||||
*
|
||||
* IRQ | Interrupt Source
|
||||
* ----|-----------------
|
||||
* 0 | TIMER_IRQ_0
|
||||
* 1 | TIMER_IRQ_1
|
||||
* 2 | TIMER_IRQ_2
|
||||
* 3 | TIMER_IRQ_3
|
||||
* 4 | PWM_IRQ_WRAP
|
||||
* 5 | USBCTRL_IRQ
|
||||
* 6 | XIP_IRQ
|
||||
* 7 | PIO0_IRQ_0
|
||||
* 8 | PIO0_IRQ_1
|
||||
* 9 | PIO1_IRQ_0
|
||||
* 10 | PIO1_IRQ_1
|
||||
* 11 | DMA_IRQ_0
|
||||
* 12 | DMA_IRQ_1
|
||||
* 13 | IO_IRQ_BANK0
|
||||
* 14 | IO_IRQ_QSPI
|
||||
* 15 | SIO_IRQ_PROC0
|
||||
* 16 | SIO_IRQ_PROC1
|
||||
* 17 | CLOCKS_IRQ
|
||||
* 18 | SPI0_IRQ
|
||||
* 19 | SPI1_IRQ
|
||||
* 20 | UART0_IRQ
|
||||
* 21 | UART1_IRQ
|
||||
* 22 | ADC0_IRQ_FIFO
|
||||
* 23 | I2C0_IRQ
|
||||
* 24 | I2C1_IRQ
|
||||
* 25 | RTC_IRQ
|
||||
*
|
||||
*/
|
||||
|
||||
// PICO_CONFIG: PICO_DEFAULT_IRQ_PRIORITY, Define the default IRQ priority, default=0x80, group=hardware_irq
|
||||
#ifndef PICO_DEFAULT_IRQ_PRIORITY
|
||||
#define PICO_DEFAULT_IRQ_PRIORITY 0x80
|
||||
#endif
|
||||
|
||||
#define PICO_LOWEST_IRQ_PRIORITY 0x01
|
||||
#define PICO_HIGHEST_IRQ_PRIORITY 0xff
|
||||
|
||||
// PICO_CONFIG: PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY, Set default shared IRQ order priority, default=0x80, group=hardware_irq
|
||||
#ifndef PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY
|
||||
#define PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY 0x80
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_IRQ, Enable/disable assertions in the IRQ module, type=bool, default=0, group=hardware_irq
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_IRQ
|
||||
#define PARAM_ASSERTIONS_ENABLED_IRQ 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*! \brief Interrupt handler function type
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* All interrupts handlers should be of this type, and follow normal ARM EABI register saving conventions
|
||||
*/
|
||||
typedef void (*irq_handler_t)();
|
||||
|
||||
/*! \brief Set specified interrupts priority
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* \param num Interrupt number
|
||||
* \param hardware_priority Priority to set. Hardware priorities range from 0 (lowest) to 255 (highest) though only
|
||||
* the top 2 bits are significant on ARM Cortex M0+. To make it easier to specify higher or lower priorities
|
||||
* than the default, all IRQ priorities are initialized to PICO_DEFAULT_IRQ_PRIORITY by the SDK runtime at startup.
|
||||
* PICO_DEFAULT_IRQ_PRIORITY defaults to 0x80
|
||||
*/
|
||||
void irq_set_priority(uint num, uint8_t hardware_priority);
|
||||
|
||||
/*! \brief Enable or disable a specific interrupt on the executing core
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* \param num Interrupt number \ref interrupt_nums
|
||||
* \param enabled true to enable the interrupt, false to disable
|
||||
*/
|
||||
void irq_set_enabled(uint num, bool enabled);
|
||||
|
||||
/*! \brief Determine if a specific interrupt is enabled on the executing core
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* \param num Interrupt number \ref interrupt_nums
|
||||
* \return true if the interrupt is enabled
|
||||
*/
|
||||
bool irq_is_enabled(uint num);
|
||||
|
||||
/*! \brief Enable/disable multiple interrupts on the executing core
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* \param mask 32-bit mask with one bits set for the interrupts to enable/disable
|
||||
* \param enabled true to enable the interrupts, false to disable them.
|
||||
*/
|
||||
void irq_set_mask_enabled(uint32_t mask, bool enabled);
|
||||
|
||||
/*! \brief Set an exclusive interrupt handler for an interrupt on the executing core.
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* Use this method to set a handler for single IRQ source interrupts, or when
|
||||
* your code, use case or performance requirements dictate that there should
|
||||
* no other handlers for the interrupt.
|
||||
*
|
||||
* This method will assert if there is already any sort of interrupt handler installed
|
||||
* for the specified irq number.
|
||||
*
|
||||
* \param num Interrupt number \ref interrupt_nums
|
||||
* \param handler The handler to set. See \ref irq_handler_t
|
||||
* \see irq_add_shared_handler
|
||||
*/
|
||||
void irq_set_exclusive_handler(uint num, irq_handler_t handler);
|
||||
|
||||
/*! \brief Get the exclusive interrupt handler for an interrupt on the executing core.
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* This method will return an exclusive IRQ handler set on this core
|
||||
* by irq_set_exclusive_handler if there is one.
|
||||
*
|
||||
* \param num Interrupt number \ref interrupt_nums
|
||||
* \see irq_set_exclusive_handler
|
||||
* \return handler The handler if an exclusive handler is set for the IRQ,
|
||||
* NULL if no handler is set or shared/shareable handlers are installed
|
||||
*/
|
||||
irq_handler_t irq_get_exclusive_handler(uint num);
|
||||
|
||||
/*! \brief Add a shared interrupt handler for an interrupt on the executing core
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* Use this method to add a handler on an irq number shared between multiple distinct hardware sources (e.g. GPIO, DMA or PIO IRQs).
|
||||
* Handlers added by this method will all be called in sequence from highest order_priority to lowest. The
|
||||
* irq_set_exclusive_handler() method should be used instead if you know there will or should only ever be one handler for the interrupt.
|
||||
*
|
||||
* This method will assert if there is an exclusive interrupt handler set for this irq number on this core, or if
|
||||
* the (total across all IRQs on both cores) maximum (configurable via PICO_MAX_SHARED_IRQ_HANDLERS) number of shared handlers
|
||||
* would be exceeded.
|
||||
*
|
||||
* \param num Interrupt number
|
||||
* \param handler The handler to set. See \ref irq_handler_t
|
||||
* \param order_priority The order priority controls the order that handlers for the same IRQ number on the core are called.
|
||||
* The shared irq handlers for an interrupt are all called when an IRQ fires, however the order of the calls is based
|
||||
* on the order_priority (higher priorities are called first, identical priorities are called in undefined order). A good
|
||||
* rule of thumb is to use PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY if you don't much care, as it is in the middle of
|
||||
* the priority range by default.
|
||||
*
|
||||
* \see irq_set_exclusive_handler
|
||||
*/
|
||||
void irq_add_shared_handler(uint num, irq_handler_t handler, uint8_t order_priority);
|
||||
|
||||
/*! \brief Remove a specific interrupt handler for the given irq number on the executing core
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* This method may be used to remove an irq set via either irq_set_exclusive_handler() or
|
||||
* irq_add_shared_handler(), and will assert if the handler is not currently installed for the given
|
||||
* IRQ number
|
||||
*
|
||||
* \note This method may *only* be called from user (non IRQ code) or from within the handler
|
||||
* itself (i.e. an IRQ handler may remove itself as part of handling the IRQ). Attempts to call
|
||||
* from another IRQ will cause an assertion.
|
||||
*
|
||||
* \param num Interrupt number \ref interrupt_nums
|
||||
* \param handler The handler to removed.
|
||||
* \see irq_set_exclusive_handler
|
||||
* \see irq_add_shared_handler
|
||||
*/
|
||||
void irq_remove_handler(uint num, irq_handler_t handler);
|
||||
|
||||
/*! \brief Get the current IRQ handler for the specified IRQ from the currently installed hardware vector table (VTOR)
|
||||
* of the execution core
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* \param num Interrupt number \ref interrupt_nums
|
||||
* \return the address stored in the VTABLE for the given irq number
|
||||
*/
|
||||
irq_handler_t irq_get_vtable_handler(uint num);
|
||||
|
||||
/*! \brief Clear a specific interrupt on the executing core
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* \param int_num Interrupt number \ref interrupt_nums
|
||||
*/
|
||||
static inline void irq_clear(uint int_num) {
|
||||
*((volatile uint32_t *) (PPB_BASE + M0PLUS_NVIC_ICPR_OFFSET)) = (1u << ((uint32_t) (int_num & 0x1F)));
|
||||
}
|
||||
|
||||
/*! \brief Force an interrupt to pending on the executing core
|
||||
* \ingroup hardware_irq
|
||||
*
|
||||
* This should generally not be used for IRQs connected to hardware.
|
||||
*
|
||||
* \param num Interrupt number \ref interrupt_nums
|
||||
*/
|
||||
void irq_set_pending(uint num);
|
||||
|
||||
|
||||
/*! \brief Perform IRQ priority intiialization for the current core
|
||||
*
|
||||
* \note This is an internal method and user should generally not call it.
|
||||
*/
|
||||
void irq_init_priorities();
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
401
src/rp2_common/hardware_irq/irq.c
Normal file
401
src/rp2_common/hardware_irq/irq.c
Normal file
@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/regs/m0plus.h"
|
||||
#include "hardware/platform_defs.h"
|
||||
#include "hardware/structs/scb.h"
|
||||
|
||||
#include "pico/mutex.h"
|
||||
#include "pico/assert.h"
|
||||
|
||||
extern void __unhandled_user_irq();
|
||||
extern uint __get_current_exception();
|
||||
|
||||
static inline irq_handler_t *get_vtable() {
|
||||
return (irq_handler_t *) scb_hw->vtor;
|
||||
}
|
||||
|
||||
static inline void *add_thumb_bit(void *addr) {
|
||||
return (void *) (((uintptr_t) addr) | 0x1);
|
||||
}
|
||||
|
||||
static inline void *remove_thumb_bit(void *addr) {
|
||||
return (void *) (((uintptr_t) addr) & ~0x1);
|
||||
}
|
||||
|
||||
static void set_raw_irq_handler_and_unlock(uint num, irq_handler_t handler, uint32_t save) {
|
||||
// update vtable (vtable_handler may be same or updated depending on cases, but we do it anyway for compactness)
|
||||
get_vtable()[16 + num] = handler;
|
||||
__dmb();
|
||||
spin_unlock(spin_lock_instance(PICO_SPINLOCK_ID_IRQ), save);
|
||||
}
|
||||
|
||||
static inline void check_irq_param(uint num) {
|
||||
invalid_params_if(IRQ, num >= NUM_IRQS);
|
||||
}
|
||||
|
||||
void irq_set_enabled(uint num, bool enabled) {
|
||||
check_irq_param(num);
|
||||
irq_set_mask_enabled(1u << num, enabled);
|
||||
}
|
||||
|
||||
bool irq_is_enabled(uint num) {
|
||||
check_irq_param(num);
|
||||
return 0 != ((1u << num) & *((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ISER_OFFSET)));
|
||||
}
|
||||
|
||||
void irq_set_mask_enabled(uint32_t mask, bool enabled) {
|
||||
if (enabled) {
|
||||
// Clear pending before enable
|
||||
// (if IRQ is actually asserted, it will immediately re-pend)
|
||||
*((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ICPR_OFFSET)) = mask;
|
||||
*((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ISER_OFFSET)) = mask;
|
||||
} else {
|
||||
*((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ICER_OFFSET)) = mask;
|
||||
}
|
||||
}
|
||||
|
||||
void irq_set_pending(uint num) {
|
||||
check_irq_param(num);
|
||||
*((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ISPR_OFFSET)) = 1u << num;
|
||||
}
|
||||
|
||||
#if PICO_MAX_SHARED_IRQ_HANDLERS
|
||||
// limited by 8 bit relative links (and reality)
|
||||
static_assert(PICO_MAX_SHARED_IRQ_HANDLERS >= 1 && PICO_MAX_SHARED_IRQ_HANDLERS < 0x7f, "");
|
||||
|
||||
// note these are not real functions, they are code fragments (i.e. don't call them)
|
||||
extern void irq_handler_chain_first_slot();
|
||||
extern void irq_handler_chain_remove_tail();
|
||||
|
||||
extern struct irq_handler_chain_slot {
|
||||
// first 3 half words are executable code (raw vtable handler points to one slot, and inst3 will jump to next
|
||||
// in chain (or end of chain handler)
|
||||
uint16_t inst1;
|
||||
uint16_t inst2;
|
||||
uint16_t inst3;
|
||||
union {
|
||||
// when a handler is removed while executing, it needs an extra instruction, which overwrites
|
||||
// the link and the priority; this is ok because no one else is modifying the chain, as
|
||||
// the chain is effectively core local, and the user code which might still need this link
|
||||
// disable the IRQ in question before updating, which means we aren't executing!
|
||||
struct {
|
||||
int8_t link;
|
||||
uint8_t priority;
|
||||
};
|
||||
uint16_t inst4;
|
||||
};
|
||||
irq_handler_t handler;
|
||||
} irq_handler_chain_slots[PICO_MAX_SHARED_IRQ_HANDLERS];
|
||||
|
||||
static int8_t irq_hander_chain_free_slot_head;
|
||||
#endif
|
||||
|
||||
static inline bool is_shared_irq_raw_handler(irq_handler_t raw_handler) {
|
||||
return (uintptr_t)raw_handler - (uintptr_t)irq_handler_chain_slots < sizeof(irq_handler_chain_slots);
|
||||
}
|
||||
|
||||
irq_handler_t irq_get_vtable_handler(uint num) {
|
||||
check_irq_param(num);
|
||||
return get_vtable()[16 + num];
|
||||
}
|
||||
|
||||
void irq_set_exclusive_handler(uint num, irq_handler_t handler) {
|
||||
check_irq_param(num);
|
||||
#if !PICO_NO_RAM_VECTOR_TABLE
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
__unused irq_handler_t current = irq_get_vtable_handler(num);
|
||||
hard_assert(current == __unhandled_user_irq || current == handler);
|
||||
set_raw_irq_handler_and_unlock(num, handler, save);
|
||||
#else
|
||||
panic_unsupported();
|
||||
#endif
|
||||
}
|
||||
|
||||
irq_handler_t irq_get_exclusive_handler(uint num) {
|
||||
check_irq_param(num);
|
||||
#if !PICO_NO_RAM_VECTOR_TABLE
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
irq_handler_t current = irq_get_vtable_handler(num);
|
||||
spin_unlock(lock, save);
|
||||
if (current == __unhandled_user_irq || is_shared_irq_raw_handler(current)) {
|
||||
return NULL;
|
||||
}
|
||||
return current;
|
||||
#else
|
||||
panic_unsupported();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static uint16_t make_branch(uint16_t *from, void *to) {
|
||||
uint32_t ui_from = (uint32_t)from;
|
||||
uint32_t ui_to = (uint32_t)to;
|
||||
uint32_t delta = (ui_to - ui_from - 4) / 2;
|
||||
assert(!(delta >> 11u));
|
||||
return 0xe000 | (delta & 0x7ff);
|
||||
}
|
||||
|
||||
static void insert_branch_and_link(uint16_t *from, void *to) {
|
||||
uint32_t ui_from = (uint32_t)from;
|
||||
uint32_t ui_to = (uint32_t)to;
|
||||
uint32_t delta = (ui_to - ui_from - 4) / 2;
|
||||
assert(!(delta >> 11u));
|
||||
from[0] = 0xf000 | ((delta >> 11u) & 0x7ffu);
|
||||
from[1] = 0xf800 | (delta & 0x7ffu);
|
||||
}
|
||||
|
||||
static inline void *resolve_branch(uint16_t *inst) {
|
||||
assert(0x1c == (*inst)>>11u);
|
||||
int32_t i_addr = (*inst) << 21u;
|
||||
i_addr /= (int32_t)(1u<<21u);
|
||||
return inst + 2 + i_addr;
|
||||
}
|
||||
|
||||
// GCC produces horrible code for subtraction of pointers here, and it was bugging me
|
||||
static inline int8_t slot_diff(struct irq_handler_chain_slot *to, struct irq_handler_chain_slot *from) {
|
||||
static_assert(sizeof(struct irq_handler_chain_slot) == 12, "");
|
||||
int32_t result;
|
||||
// return (to - from);
|
||||
// note this implementation has limited range, but is fine for plenty more than -128->127 result
|
||||
asm (".syntax unified\n"
|
||||
"subs %1, %2\n"
|
||||
"adcs %1, %1\n" // * 2 (and + 1 if negative for rounding)
|
||||
"ldr %0, =0xaaaa\n"
|
||||
"muls %0, %1\n"
|
||||
"lsrs %0, 20\n"
|
||||
: "=l" (result), "+l" (to)
|
||||
: "l" (from)
|
||||
:
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
void irq_add_shared_handler(uint num, irq_handler_t handler, uint8_t order_priority) {
|
||||
check_irq_param(num);
|
||||
#if PICO_DISABLE_SHARED_IRQ_HANDLERS
|
||||
|
||||
#endif
|
||||
#if PICO_NO_RAM_VECTOR_TABLE || !PICO_MAX_SHARED_IRQ_HANDLERS
|
||||
panic_unsupported()
|
||||
#else
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
hard_assert(irq_hander_chain_free_slot_head >= 0);
|
||||
struct irq_handler_chain_slot *slot = &irq_handler_chain_slots[irq_hander_chain_free_slot_head];
|
||||
int slot_index = irq_hander_chain_free_slot_head;
|
||||
irq_hander_chain_free_slot_head = slot->link;
|
||||
irq_handler_t vtable_handler = get_vtable()[16 + num];
|
||||
if (!is_shared_irq_raw_handler(vtable_handler)) {
|
||||
// start new chain
|
||||
hard_assert(vtable_handler == __unhandled_user_irq);
|
||||
struct irq_handler_chain_slot slot_data = {
|
||||
.inst1 = 0xa100, // add r1, pc, #0
|
||||
.inst2 = make_branch(&slot->inst2, irq_handler_chain_first_slot), // b irq_handler_chain_first_slot
|
||||
.inst3 = 0xbd00, // pop {pc}
|
||||
.link = -1,
|
||||
.priority = order_priority,
|
||||
.handler = handler
|
||||
};
|
||||
*slot = slot_data;
|
||||
vtable_handler = (irq_handler_t)add_thumb_bit(slot);
|
||||
} else {
|
||||
assert(!((((uintptr_t)vtable_handler) - ((uintptr_t)irq_handler_chain_slots) - 1)%sizeof(struct irq_handler_chain_slot)));
|
||||
struct irq_handler_chain_slot *prev_slot = NULL;
|
||||
struct irq_handler_chain_slot *existing_vtable_slot = remove_thumb_bit(vtable_handler);
|
||||
struct irq_handler_chain_slot *cur_slot = existing_vtable_slot;
|
||||
while (cur_slot->priority > order_priority) {
|
||||
prev_slot = cur_slot;
|
||||
if (cur_slot->link < 0) break;
|
||||
cur_slot = &irq_handler_chain_slots[cur_slot->link];
|
||||
}
|
||||
if (prev_slot) {
|
||||
// insert into chain
|
||||
struct irq_handler_chain_slot slot_data = {
|
||||
.inst1 = 0x4801, // ldr r0, [pc, #4]
|
||||
.inst2 = 0x4780, // blx r0
|
||||
.inst3 = prev_slot->link >= 0 ?
|
||||
make_branch(&slot->inst3, resolve_branch(&prev_slot->inst3)) : // b next_slot
|
||||
0xbd00, // pop {pc}
|
||||
.link = prev_slot->link,
|
||||
.priority = order_priority,
|
||||
.handler = handler
|
||||
};
|
||||
// update code and data links
|
||||
prev_slot->inst3 = make_branch(&prev_slot->inst3, slot),
|
||||
prev_slot->link = slot_index;
|
||||
*slot = slot_data;
|
||||
} else {
|
||||
// update with new chain head
|
||||
struct irq_handler_chain_slot slot_data = {
|
||||
.inst1 = 0xa100, // add r1, pc, #0
|
||||
.inst2 = make_branch(&slot->inst2, irq_handler_chain_first_slot), // b irq_handler_chain_first_slot
|
||||
.inst3 = make_branch(&slot->inst3, existing_vtable_slot), // b existing_slot
|
||||
.link = slot_diff(existing_vtable_slot, irq_handler_chain_slots),
|
||||
.priority = order_priority,
|
||||
.handler = handler
|
||||
};
|
||||
*slot = slot_data;
|
||||
// fixup previous head slot
|
||||
existing_vtable_slot->inst1 = 0x4801; // ldr r0, [pc, #4]
|
||||
existing_vtable_slot->inst2 = 0x4780; // blx r0
|
||||
vtable_handler = (irq_handler_t)add_thumb_bit(slot);
|
||||
}
|
||||
}
|
||||
set_raw_irq_handler_and_unlock(num, vtable_handler, save);
|
||||
#endif
|
||||
}
|
||||
|
||||
void irq_remove_handler(uint num, irq_handler_t handler) {
|
||||
#if !PICO_NO_RAM_VECTOR_TABLE
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
irq_handler_t vtable_handler = get_vtable()[16 + num];
|
||||
if (vtable_handler != __unhandled_user_irq && vtable_handler != handler) {
|
||||
#if !PICO_DISABLE_SHARED_IRQ_HANDLERS && PICO_MAX_SHARED_IRQ_HANDLERS
|
||||
if (is_shared_irq_raw_handler(vtable_handler)) {
|
||||
// This is a bit tricky, as an executing IRQ handler doesn't take a lock.
|
||||
|
||||
// First thing to do is to disable the IRQ in question; that takes care of calls from user code.
|
||||
// Note that a irq handler chain is local to our own core, so we don't need to worry about the other core
|
||||
bool was_enabled = irq_is_enabled(num);
|
||||
irq_set_enabled(num, false);
|
||||
__dmb();
|
||||
|
||||
// It is possible we are being called while an IRQ for this chain is already in progress.
|
||||
// The issue we have here is that we must not free a slot that is currently being executed, because
|
||||
// inst3 is still to be executed, and inst3 might get overwritten if the slot is re-used.
|
||||
|
||||
// By disallowing other exceptions from removing an IRQ handler (which seems fair)
|
||||
// we now only have to worry about removing a slot from a chain that is currently executing.
|
||||
|
||||
// Note we expect that the slot we are deleting is the one that is executing.
|
||||
// In particular, bad things happen if the caller were to delete the handler in the chain
|
||||
// before it. This is not an allowed use case though, and I can't imagine anyone wanting to in practice.
|
||||
// Sadly this is not something we can detect.
|
||||
|
||||
uint exception = __get_current_exception();
|
||||
hard_assert(!exception || exception == num + 16);
|
||||
|
||||
struct irq_handler_chain_slot *prev_slot = NULL;
|
||||
struct irq_handler_chain_slot *existing_vtable_slot = remove_thumb_bit(vtable_handler);
|
||||
struct irq_handler_chain_slot *to_free_slot = existing_vtable_slot;
|
||||
int to_free_slot_index = to_free_slot - irq_handler_chain_slots;
|
||||
while (to_free_slot->handler != handler) {
|
||||
prev_slot = to_free_slot;
|
||||
if (to_free_slot->link < 0) break;
|
||||
to_free_slot = &irq_handler_chain_slots[to_free_slot->link];
|
||||
}
|
||||
if (to_free_slot->handler == handler) {
|
||||
int next_slot_index = to_free_slot->link;
|
||||
if (next_slot_index >= 0) {
|
||||
// There is another slot in the chain, so copy that over us, so that our inst3 points at something valid
|
||||
// Note this only matters in the exception case anyway, and it that case, we will skip the next handler,
|
||||
// however in that case it's IRQ cause should immediately cause re-entry of the IRQ and the only side
|
||||
// effect will be that there was potentially brief out of priority order execution of the handlers
|
||||
struct irq_handler_chain_slot *next_slot = &irq_handler_chain_slots[next_slot_index];
|
||||
to_free_slot->handler = next_slot->handler;
|
||||
to_free_slot->priority = next_slot->priority;
|
||||
to_free_slot->link = next_slot->link;
|
||||
to_free_slot->inst3 = next_slot->link >= 0 ?
|
||||
make_branch(&to_free_slot->inst3, resolve_branch(&next_slot->inst3)) : // b mext_>slot->next_slot
|
||||
0xbd00; // pop {pc}
|
||||
|
||||
// add old next slot back to free list
|
||||
next_slot->link = irq_hander_chain_free_slot_head;
|
||||
irq_hander_chain_free_slot_head = next_slot_index;
|
||||
} else {
|
||||
// Slot being removed is at the end of the chain
|
||||
if (!exception) {
|
||||
// case when we're not in exception, we physically unlink now
|
||||
if (prev_slot) {
|
||||
// chain is not empty
|
||||
prev_slot->link = -1;
|
||||
prev_slot->inst3 = 0xbd00; // pop {pc}
|
||||
} else {
|
||||
// chain is not empty
|
||||
vtable_handler = __unhandled_user_irq;
|
||||
}
|
||||
// add slot back to free list
|
||||
to_free_slot->link = irq_hander_chain_free_slot_head;
|
||||
irq_hander_chain_free_slot_head = to_free_slot_index;
|
||||
} else {
|
||||
// since we are the last slot we know that our inst3 hasn't executed yet, so we change
|
||||
// it to bl to irq_handler_chain_remove_tail which will remove the slot.
|
||||
// NOTE THAT THIS TRASHES PRIORITY AND LINK SINCE THIS IS A 4 BYTE INSTRUCTION
|
||||
// BUT THEY ARE NOT NEEDED NOW
|
||||
insert_branch_and_link(&to_free_slot->inst3, irq_handler_chain_remove_tail);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(false); // not found
|
||||
}
|
||||
irq_set_enabled(num, was_enabled);
|
||||
}
|
||||
#else
|
||||
assert(false); // not found
|
||||
#endif
|
||||
} else {
|
||||
vtable_handler = __unhandled_user_irq;
|
||||
}
|
||||
set_raw_irq_handler_and_unlock(num, vtable_handler, save);
|
||||
#else
|
||||
panic_unsupported();
|
||||
#endif
|
||||
}
|
||||
|
||||
void irq_set_priority(uint num, uint8_t hardware_priority) {
|
||||
check_irq_param(num);
|
||||
|
||||
// note that only 32 bit writes are supported
|
||||
io_rw_32 *p = (io_rw_32 *)((PPB_BASE + M0PLUS_NVIC_IPR0_OFFSET) + (num & ~3u));
|
||||
*p = (*p & ~(0xffu << (8 * (num & 3u)))) | (((uint32_t) hardware_priority) << (8 * (num & 3u)));
|
||||
}
|
||||
|
||||
#if !PICO_DISABLE_SHARED_IRQ_HANDLERS && PICO_MAX_SHARED_IRQ_HANDLERS
|
||||
// used by irq_handler_chain.S to remove the last link in a handler chain after it executes
|
||||
// note this must be called only with the last slot in a chain (and during the exception)
|
||||
void irq_add_tail_to_free_list(struct irq_handler_chain_slot *slot) {
|
||||
irq_handler_t slot_handler = (irq_handler_t) add_thumb_bit(slot);
|
||||
assert(is_shared_irq_raw_handler(slot_handler));
|
||||
|
||||
int exception = __get_current_exception();
|
||||
assert(exception);
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
int slot_index = slot - irq_handler_chain_slots;
|
||||
if (slot_handler == get_vtable()[exception]) {
|
||||
get_vtable()[exception] = __unhandled_user_irq;
|
||||
} else {
|
||||
bool __unused found = false;
|
||||
// need to find who points at the slot and update it
|
||||
for(uint i=0;i<count_of(irq_handler_chain_slots);i++) {
|
||||
if (irq_handler_chain_slots[i].link == slot_index) {
|
||||
irq_handler_chain_slots[i].link = -1;
|
||||
irq_handler_chain_slots[i].inst3 = 0xbd00; // pop {pc}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(found);
|
||||
}
|
||||
// add slot to free list
|
||||
slot->link = irq_hander_chain_free_slot_head;
|
||||
irq_hander_chain_free_slot_head = slot_index;
|
||||
spin_unlock(lock, save);
|
||||
}
|
||||
#endif
|
||||
|
||||
void irq_init_priorities() {
|
||||
#if PICO_DEFAULT_IRQ_PRIORITY != 0
|
||||
for (uint irq = 0; irq < NUM_IRQS; irq++) {
|
||||
irq_set_priority(irq, PICO_DEFAULT_IRQ_PRIORITY);
|
||||
}
|
||||
#endif
|
||||
}
|
70
src/rp2_common/hardware_irq/irq_handler_chain.S
Normal file
70
src/rp2_common/hardware_irq/irq_handler_chain.S
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/platform_defs.h"
|
||||
#include "hardware/irq.h"
|
||||
|
||||
#if !PICO_DISABLE_SHARED_IRQ_HANDLERS
|
||||
.syntax unified
|
||||
.cpu cortex-m0plus
|
||||
.thumb
|
||||
|
||||
.data
|
||||
.align 2
|
||||
|
||||
.global irq_handler_chain_slots
|
||||
|
||||
.global irq_handler_chain_first_slot
|
||||
.global irq_handler_chain_remove_tail
|
||||
|
||||
//
|
||||
// These Slots make up the code and structure of the handler chains; the only external information are the VTABLE entries
|
||||
// (obviously one set per core) and a free list head. Each individual handler chain starts with the VTABLE entry I
|
||||
// pointing at the address of slot S (with thumb bit set). Thus each slot which is part of a chain is executble.
|
||||
//
|
||||
// The execution jumps (via branch instruction) from one slot to the other, then jumps to the end of chain handler.
|
||||
// The entirety of the state needed to traverse the chain is contained within the slots of the chain, which is why
|
||||
// a VTABLE entry is all that is needed per chain (rather than requiring a separarte set of head pointers)
|
||||
//
|
||||
|
||||
irq_handler_chain_slots:
|
||||
.set next_slot_number, 1
|
||||
.rept PICO_MAX_SHARED_IRQ_HANDLERS
|
||||
// a slot is executable and is always 3 instructions long.
|
||||
.hword 0 // inst1 (either: ldr r0, [pc, #4] or for the FIRST slot : add r1, pc, #0 )
|
||||
.hword 0 // inst2 ( blx r0 b irq_handler_chain_first_slot )
|
||||
|
||||
.hword 0 // inst3 (either: b next_slot or for the LAST pop {pc} )
|
||||
|
||||
// next is a single byte index of next slot in chain (or -1 to end)
|
||||
.if next_slot_number == PICO_MAX_SHARED_IRQ_HANDLERS
|
||||
.byte 0xff
|
||||
.else
|
||||
.byte next_slot_number
|
||||
.endif
|
||||
// next is the 8 bit unsigned priority
|
||||
.byte 0x00
|
||||
1:
|
||||
// and finally the handler function pointer
|
||||
.word 0x00000000
|
||||
.set next_slot_number, next_slot_number + 1
|
||||
.endr
|
||||
|
||||
irq_handler_chain_first_slot:
|
||||
push {lr}
|
||||
ldr r0, [r1, #4]
|
||||
adds r1, #1
|
||||
mov lr, r1
|
||||
bx r0
|
||||
irq_handler_chain_remove_tail:
|
||||
mov r0, lr
|
||||
subs r0, #9
|
||||
ldr r1, =irq_add_tail_to_free_list
|
||||
blx r1
|
||||
pop {pc}
|
||||
|
||||
|
||||
#endif
|
4
src/rp2_common/hardware_pio/CMakeLists.txt
Normal file
4
src/rp2_common/hardware_pio/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
pico_simple_hardware_target(pio)
|
||||
|
||||
# additional libraries
|
||||
target_link_libraries(hardware_pio INTERFACE hardware_gpio hardware_claim)
|
1022
src/rp2_common/hardware_pio/include/hardware/pio.h
Normal file
1022
src/rp2_common/hardware_pio/include/hardware/pio.h
Normal file
File diff suppressed because it is too large
Load Diff
178
src/rp2_common/hardware_pio/include/hardware/pio_instructions.h
Normal file
178
src/rp2_common/hardware_pio/include/hardware/pio_instructions.h
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_PIO_INSTRUCTIONS_H_
|
||||
#define _HARDWARE_PIO_INSTRUCTIONS_H_
|
||||
|
||||
#include "pico.h"
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS, Enable/disable assertions in the PIO instructions, type=bool, default=0, group=hardware_pio
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS
|
||||
#define PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum pio_instr_bits {
|
||||
pio_instr_bits_jmp = 0x0000,
|
||||
pio_instr_bits_wait = 0x2000,
|
||||
pio_instr_bits_in = 0x4000,
|
||||
pio_instr_bits_out = 0x6000,
|
||||
pio_instr_bits_push = 0x8000,
|
||||
pio_instr_bits_pull = 0x8080,
|
||||
pio_instr_bits_mov = 0xa000,
|
||||
pio_instr_bits_irq = 0xc000,
|
||||
pio_instr_bits_set = 0xe000,
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define _PIO_INVALID_IN_SRC 0x08u
|
||||
#define _PIO_INVALID_OUT_DEST 0x10u
|
||||
#define _PIO_INVALID_SET_DEST 0x20u
|
||||
#define _PIO_INVALID_MOV_SRC 0x40u
|
||||
#define _PIO_INVALID_MOV_DEST 0x80u
|
||||
#else
|
||||
#define _PIO_INVALID_IN_SRC 0u
|
||||
#define _PIO_INVALID_OUT_DEST 0u
|
||||
#define _PIO_INVALID_SET_DEST 0u
|
||||
#define _PIO_INVALID_MOV_SRC 0u
|
||||
#define _PIO_INVALID_MOV_DEST 0u
|
||||
#endif
|
||||
|
||||
enum pio_src_dest {
|
||||
pio_pins = 0u,
|
||||
pio_x = 1u,
|
||||
pio_y = 2u,
|
||||
pio_null = 3u | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_DEST,
|
||||
pio_pindirs = 4u | _PIO_INVALID_IN_SRC | _PIO_INVALID_MOV_SRC | _PIO_INVALID_MOV_DEST,
|
||||
pio_exec_mov = 4u | _PIO_INVALID_IN_SRC | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC,
|
||||
pio_status = 5u | _PIO_INVALID_IN_SRC | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_DEST,
|
||||
pio_pc = 5u | _PIO_INVALID_IN_SRC | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC,
|
||||
pio_isr = 6u | _PIO_INVALID_SET_DEST,
|
||||
pio_osr = 7u | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST,
|
||||
pio_exec_out = 7u | _PIO_INVALID_IN_SRC | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC | _PIO_INVALID_MOV_DEST,
|
||||
};
|
||||
|
||||
inline static uint _pio_major_instr_bits(uint instr) {
|
||||
return instr & 0xe000u;
|
||||
}
|
||||
|
||||
inline static uint _pio_encode_instr_and_args(enum pio_instr_bits instr_bits, uint arg1, uint arg2) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, arg1 <= 0x7);
|
||||
#if PARAM_ASSERTIONS_ENABLED(PIO_INSTRUCTIONS)
|
||||
uint32_t major = _pio_major_instr_bits(instr_bits);
|
||||
if (major == pio_instr_bits_in || major == pio_instr_bits_out) {
|
||||
assert(arg2 && arg2 <= 32);
|
||||
} else {
|
||||
assert(arg2 <= 31);
|
||||
}
|
||||
#endif
|
||||
return instr_bits | (arg1 << 5u) | (arg2 & 0x1fu);
|
||||
}
|
||||
|
||||
inline static uint _pio_encode_instr_and_src_dest(enum pio_instr_bits instr_bits, enum pio_src_dest dest, uint value) {
|
||||
return _pio_encode_instr_and_args(instr_bits, dest & 7u, value);
|
||||
}
|
||||
|
||||
inline static uint pio_encode_delay(uint cycles) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, cycles <= 0x1f);
|
||||
return cycles << 8u;
|
||||
}
|
||||
|
||||
inline static uint pio_encode_sideset(uint sideset_bit_count, uint value) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, sideset_bit_count >= 1 && sideset_bit_count <= 5);
|
||||
valid_params_if(PIO_INSTRUCTIONS, value <= (0x1fu >> sideset_bit_count));
|
||||
return value << (13u - sideset_bit_count);
|
||||
}
|
||||
|
||||
inline static uint pio_encode_sideset_opt(uint sideset_bit_count, uint value) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, sideset_bit_count >= 2 && sideset_bit_count <= 5);
|
||||
valid_params_if(PIO_INSTRUCTIONS, value <= (0x1fu >> sideset_bit_count));
|
||||
return 0x1000u | value << (12u - sideset_bit_count);
|
||||
}
|
||||
|
||||
inline static uint pio_encode_jmp(uint addr) {
|
||||
return _pio_encode_instr_and_args(pio_instr_bits_jmp, 0, addr);
|
||||
}
|
||||
|
||||
inline static uint _pio_encode_irq(bool relative, uint irq) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, irq <= 7);
|
||||
return (relative ? 0x10u : 0x0u) | irq;
|
||||
}
|
||||
|
||||
inline static uint pio_encode_wait_gpio(bool polarity, uint pin) {
|
||||
return _pio_encode_instr_and_args(pio_instr_bits_wait, 0u | (polarity ? 4u : 0u), pin);
|
||||
}
|
||||
|
||||
inline static uint pio_encode_wait_pin(bool polarity, uint pin) {
|
||||
return _pio_encode_instr_and_args(pio_instr_bits_wait, 1u | (polarity ? 4u : 0u), pin);
|
||||
}
|
||||
|
||||
inline static uint pio_encode_wait_irq(bool polarity, bool relative, uint irq) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, irq <= 7);
|
||||
return _pio_encode_instr_and_args(pio_instr_bits_wait, 2u | (polarity ? 4u : 0u), _pio_encode_irq(relative, irq));
|
||||
}
|
||||
|
||||
inline static uint pio_encode_in(enum pio_src_dest src, uint value) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_IN_SRC));
|
||||
return _pio_encode_instr_and_src_dest(pio_instr_bits_in, src, value);
|
||||
}
|
||||
|
||||
inline static uint pio_encode_out(enum pio_src_dest dest, uint value) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_OUT_DEST));
|
||||
return _pio_encode_instr_and_src_dest(pio_instr_bits_out, dest, value);
|
||||
}
|
||||
|
||||
inline static uint pio_encode_push(bool if_full, bool block) {
|
||||
return _pio_encode_instr_and_args(pio_instr_bits_push, (if_full ? 2u : 0u) | (block ? 1u : 0u), 0);
|
||||
}
|
||||
|
||||
inline static uint pio_encode_pull(bool if_empty, bool block) {
|
||||
return _pio_encode_instr_and_args(pio_instr_bits_pull, (if_empty ? 2u : 0u) | (block ? 1u : 0u), 0);
|
||||
}
|
||||
|
||||
inline static uint pio_encode_mov(enum pio_src_dest dest, enum pio_src_dest src) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
|
||||
valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
|
||||
return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, src & 7u);
|
||||
}
|
||||
|
||||
inline static uint pio_encode_mov_not(enum pio_src_dest dest, enum pio_src_dest src) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
|
||||
valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
|
||||
return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, (1u << 3u) | (src & 7u));
|
||||
}
|
||||
|
||||
inline static uint pio_encode_mov_reverse(enum pio_src_dest dest, enum pio_src_dest src) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
|
||||
valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
|
||||
return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, (2u << 3u) | (src & 7u));
|
||||
}
|
||||
|
||||
inline static uint pio_encode_irq_set(bool relative, uint irq) {
|
||||
return _pio_encode_instr_and_args(pio_instr_bits_irq, 0, _pio_encode_irq(relative, irq));
|
||||
}
|
||||
|
||||
inline static uint pio_encode_irq_clear(bool relative, uint irq) {
|
||||
return _pio_encode_instr_and_args(pio_instr_bits_irq, 2, _pio_encode_irq(relative, irq));
|
||||
}
|
||||
|
||||
inline static uint pio_encode_set(enum pio_src_dest dest, uint value) {
|
||||
valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_SET_DEST));
|
||||
return _pio_encode_instr_and_src_dest(pio_instr_bits_set, dest, value);
|
||||
}
|
||||
|
||||
inline static uint pio_encode_nop() {
|
||||
return pio_encode_mov(pio_y, pio_y);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
240
src/rp2_common/hardware_pio/pio.c
Normal file
240
src/rp2_common/hardware_pio/pio.c
Normal file
@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/claim.h"
|
||||
#include "hardware/pio.h"
|
||||
#include "hardware/pio_instructions.h"
|
||||
|
||||
// sanity check
|
||||
check_hw_layout(pio_hw_t, sm[0].clkdiv, PIO_SM0_CLKDIV_OFFSET);
|
||||
check_hw_layout(pio_hw_t, sm[1].clkdiv, PIO_SM1_CLKDIV_OFFSET);
|
||||
check_hw_layout(pio_hw_t, instr_mem[0], PIO_INSTR_MEM0_OFFSET);
|
||||
check_hw_layout(pio_hw_t, inte0, PIO_IRQ0_INTE_OFFSET);
|
||||
check_hw_layout(pio_hw_t, txf[1], PIO_TXF1_OFFSET);
|
||||
check_hw_layout(pio_hw_t, rxf[3], PIO_RXF3_OFFSET);
|
||||
check_hw_layout(pio_hw_t, ints1, PIO_IRQ1_INTS_OFFSET);
|
||||
|
||||
static_assert(NUM_PIO_STATE_MACHINES * NUM_PIOS <= 8, "");
|
||||
static uint8_t claimed;
|
||||
|
||||
void pio_sm_claim(PIO pio, uint sm) {
|
||||
check_sm_param(sm);
|
||||
uint which = pio_get_index(pio);
|
||||
if (which) {
|
||||
hw_claim_or_assert(&claimed, NUM_PIO_STATE_MACHINES + sm, "PIO 1 SM %d already claimed");
|
||||
} else {
|
||||
hw_claim_or_assert(&claimed, sm, "PIO 0 SM %d already claimed");
|
||||
}
|
||||
}
|
||||
|
||||
void pio_claim_sm_mask(PIO pio, uint sm_mask) {
|
||||
for(uint i = 0; sm_mask; i++, sm_mask >>= 1u) {
|
||||
if (sm_mask & 1u) pio_sm_claim(pio, i);
|
||||
}
|
||||
}
|
||||
void pio_sm_unclaim(PIO pio, uint sm) {
|
||||
check_sm_param(sm);
|
||||
uint which = pio_get_index(pio);
|
||||
hw_claim_clear(&claimed, which * NUM_PIO_STATE_MACHINES + sm);
|
||||
}
|
||||
|
||||
int pio_claim_unused_sm(PIO pio, bool required) {
|
||||
uint which = pio_get_index(pio);
|
||||
uint base = which * NUM_PIO_STATE_MACHINES;
|
||||
int index = hw_claim_unused_from_range((uint8_t*)&claimed, required, base,
|
||||
base + NUM_PIO_STATE_MACHINES - 1, "No PIO state machines are available");
|
||||
return index >= base ? index - base : -1;
|
||||
}
|
||||
|
||||
void pio_load_program(PIO pio, const uint16_t *prog, uint8_t prog_len, uint8_t load_offset) {
|
||||
// instructions are only 16 bits, but instruction memory locations are spaced 32 bits apart
|
||||
// Adjust the addresses of any jump instructions to respect load offset
|
||||
assert(load_offset + prog_len <= PIO_INSTRUCTION_COUNT);
|
||||
|
||||
}
|
||||
|
||||
static_assert(PIO_INSTRUCTION_COUNT <= 32, "");
|
||||
static uint32_t _used_instruction_space[2];
|
||||
|
||||
static int _pio_find_offset_for_program(PIO pio, const pio_program_t *program) {
|
||||
assert(program->length < PIO_INSTRUCTION_COUNT);
|
||||
uint32_t used_mask = _used_instruction_space[pio_get_index(pio)];
|
||||
uint32_t program_mask = (1u << program->length) - 1;
|
||||
if (program->origin >= 0) {
|
||||
if (program->origin > 32 - program->length) return -1;
|
||||
return used_mask & (program_mask << program->origin) ? -1 : program->origin;
|
||||
} else {
|
||||
// work down from the top always
|
||||
for (int i = 32 - program->length; i >= 0; i--) {
|
||||
if (!(used_mask & (program_mask << (uint) i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool pio_can_add_program(PIO pio, const pio_program_t *program) {
|
||||
uint32_t save = hw_claim_lock();
|
||||
bool rc = -1 != _pio_find_offset_for_program(pio, program);
|
||||
hw_claim_unlock(save);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static bool _pio_can_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset) {
|
||||
assert(offset < PIO_INSTRUCTION_COUNT);
|
||||
assert(offset + program->length <= PIO_INSTRUCTION_COUNT);
|
||||
if (program->origin >= 0 && program->origin != offset) return false;
|
||||
uint32_t used_mask = _used_instruction_space[pio_get_index(pio)];
|
||||
uint32_t program_mask = (1u << program->length) - 1;
|
||||
return !(used_mask & (program_mask << offset));
|
||||
}
|
||||
|
||||
bool pio_can_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset) {
|
||||
uint32_t save = hw_claim_lock();
|
||||
bool rc = _pio_can_add_program_at_offset(pio, program, offset);
|
||||
hw_claim_unlock(save);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void _pio_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset) {
|
||||
if (!_pio_can_add_program_at_offset(pio, program, offset)) {
|
||||
panic("No program space");
|
||||
}
|
||||
for (uint i = 0; i < program->length; ++i) {
|
||||
uint16_t instr = program->instructions[i];
|
||||
pio->instr_mem[offset + i] = pio_instr_bits_jmp != _pio_major_instr_bits(instr) ? instr : instr + offset;
|
||||
}
|
||||
uint32_t program_mask = (1u << program->length) - 1;
|
||||
_used_instruction_space[pio_get_index(pio)] |= program_mask << offset;
|
||||
}
|
||||
|
||||
// these assert if unable
|
||||
uint pio_add_program(PIO pio, const pio_program_t *program) {
|
||||
uint32_t save = hw_claim_lock();
|
||||
int offset = _pio_find_offset_for_program(pio, program);
|
||||
if (offset < 0) {
|
||||
panic("No program space");
|
||||
}
|
||||
_pio_add_program_at_offset(pio, program, offset);
|
||||
hw_claim_unlock(save);
|
||||
return offset;
|
||||
}
|
||||
|
||||
void pio_remove_program(PIO pio, const pio_program_t *program, uint loaded_offset) {
|
||||
uint32_t program_mask = (1u << program->length) - 1;
|
||||
program_mask <<= loaded_offset;
|
||||
uint32_t save = hw_claim_lock();
|
||||
assert(program_mask == (_used_instruction_space[pio_get_index(pio)] & program_mask));
|
||||
_used_instruction_space[pio_get_index(pio)] &= ~program_mask;
|
||||
hw_claim_unlock(save);
|
||||
}
|
||||
|
||||
void pio_clear_instruction_memory(PIO pio) {
|
||||
uint32_t save = hw_claim_lock();
|
||||
_used_instruction_space[pio_get_index(pio)] = 0;
|
||||
for(uint i=0;i<PIO_INSTRUCTION_COUNT;i++) {
|
||||
pio->instr_mem[i] = pio_encode_jmp(i);
|
||||
}
|
||||
hw_claim_unlock(save);
|
||||
}
|
||||
|
||||
// Set the value of all PIO pins. This is done by forcibly executing
|
||||
// instructions on a "victim" state machine, sm. Ideally you should choose one
|
||||
// which is not currently running a program. This is intended for one-time
|
||||
// setup of initial pin states.
|
||||
void pio_sm_set_pins(PIO pio, uint sm, uint32_t pins) {
|
||||
uint32_t pinctrl_saved = pio->sm[sm].pinctrl;
|
||||
uint remaining = 32;
|
||||
uint base = 0;
|
||||
while (remaining) {
|
||||
uint decrement = remaining > 5 ? 5 : remaining;
|
||||
pio->sm[sm].pinctrl =
|
||||
(decrement << PIO_SM0_PINCTRL_SET_COUNT_LSB) |
|
||||
(base << PIO_SM0_PINCTRL_SET_BASE_LSB);
|
||||
pio_sm_exec(pio, sm, pio_encode_set(pio_pins, pins & 0x1fu));
|
||||
remaining -= decrement;
|
||||
base += decrement;
|
||||
pins >>= 5;
|
||||
}
|
||||
pio->sm[sm].pinctrl = pinctrl_saved;
|
||||
}
|
||||
|
||||
void pio_sm_set_pins_with_mask(PIO pio, uint sm, uint32_t pinvals, uint32_t pin_mask) {
|
||||
uint32_t pinctrl_saved = pio->sm[sm].pinctrl;
|
||||
while (pin_mask) {
|
||||
uint base = __builtin_ctz(pin_mask);
|
||||
pio->sm[sm].pinctrl =
|
||||
(1u << PIO_SM0_PINCTRL_SET_COUNT_LSB) |
|
||||
(base << PIO_SM0_PINCTRL_SET_BASE_LSB);
|
||||
pio_sm_exec(pio, sm, pio_encode_set(pio_pins, (pinvals >> base) & 0x1u));
|
||||
pin_mask &= pin_mask - 1;
|
||||
}
|
||||
pio->sm[sm].pinctrl = pinctrl_saved;
|
||||
}
|
||||
|
||||
void pio_sm_set_pindirs_with_mask(PIO pio, uint sm, uint32_t pindirs, uint32_t pin_mask) {
|
||||
uint32_t pinctrl_saved = pio->sm[sm].pinctrl;
|
||||
while (pin_mask) {
|
||||
uint base = __builtin_ctz(pin_mask);
|
||||
pio->sm[sm].pinctrl =
|
||||
(1u << PIO_SM0_PINCTRL_SET_COUNT_LSB) |
|
||||
(base << PIO_SM0_PINCTRL_SET_BASE_LSB);
|
||||
pio_sm_exec(pio, sm, pio_encode_set(pio_pindirs, (pindirs >> base) & 0x1u));
|
||||
pin_mask &= pin_mask - 1;
|
||||
}
|
||||
pio->sm[sm].pinctrl = pinctrl_saved;
|
||||
}
|
||||
|
||||
void pio_sm_set_consecutive_pindirs(PIO pio, uint sm, uint pin, uint count, bool is_out) {
|
||||
assert(pin < 32u);
|
||||
uint32_t pinctrl_saved = pio->sm[sm].pinctrl;
|
||||
uint pindir_val = is_out ? 0x1f : 0;
|
||||
while (count > 5) {
|
||||
pio->sm[sm].pinctrl = (5u << PIO_SM0_PINCTRL_SET_COUNT_LSB) | (pin << PIO_SM0_PINCTRL_SET_BASE_LSB);
|
||||
pio_sm_exec(pio, sm, pio_encode_set(pio_pindirs, pindir_val));
|
||||
count -= 5;
|
||||
pin = (pin + 5) & 0x1f;
|
||||
}
|
||||
pio->sm[sm].pinctrl = (count << PIO_SM0_PINCTRL_SET_COUNT_LSB) | (pin << PIO_SM0_PINCTRL_SET_BASE_LSB);
|
||||
pio_sm_exec(pio, sm, pio_encode_set(pio_pindirs, pindir_val));
|
||||
pio->sm[sm].pinctrl = pinctrl_saved;
|
||||
}
|
||||
|
||||
void pio_sm_init(PIO pio, uint sm, uint initial_pc, const pio_sm_config *config) {
|
||||
// Halt the machine, set some sensible defaults
|
||||
pio_sm_set_enabled(pio, sm, false);
|
||||
|
||||
if (config) {
|
||||
pio_sm_set_config(pio, sm, config);
|
||||
} else {
|
||||
pio_sm_config c = pio_get_default_sm_config();
|
||||
pio_sm_set_config(pio, sm, &c);
|
||||
}
|
||||
|
||||
pio_sm_clear_fifos(pio, sm);
|
||||
|
||||
// Clear FIFO debug flags
|
||||
const uint32_t fdebug_sm_mask =
|
||||
(1u << PIO_FDEBUG_TXOVER_LSB) |
|
||||
(1u << PIO_FDEBUG_RXUNDER_LSB) |
|
||||
(1u << PIO_FDEBUG_TXSTALL_LSB) |
|
||||
(1u << PIO_FDEBUG_RXSTALL_LSB);
|
||||
pio->fdebug = fdebug_sm_mask << sm;
|
||||
|
||||
// Finally, clear some internal SM state
|
||||
pio_sm_restart(pio, sm);
|
||||
pio_sm_clkdiv_restart(pio, sm);
|
||||
pio_sm_exec(pio, sm, pio_encode_jmp(initial_pc));
|
||||
}
|
||||
|
||||
void pio_sm_drain_tx_fifo(PIO pio, uint sm) {
|
||||
uint instr = (pio->sm[sm].shiftctrl & PIO_SM0_SHIFTCTRL_AUTOPULL_BITS) ? pio_encode_out(pio_null, 32) :
|
||||
pio_encode_pull(false, false);
|
||||
while (!pio_sm_is_tx_fifo_empty(pio, sm)) {
|
||||
pio_sm_exec(pio, sm, instr);
|
||||
}
|
||||
}
|
1
src/rp2_common/hardware_pll/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_pll/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(pll)
|
59
src/rp2_common/hardware_pll/include/hardware/pll.h
Normal file
59
src/rp2_common/hardware_pll/include/hardware/pll.h
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_PLL_H_
|
||||
#define _HARDWARE_PLL_H_
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/pll.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file hardware/pll.h
|
||||
* \defgroup hardware_pll hardware_pll
|
||||
*
|
||||
* Phase Locked Loop control APIs
|
||||
*
|
||||
* There are two PLLs in RP2040. They are:
|
||||
* - pll_sys - Used to generate up to a 133MHz system clock
|
||||
* - pll_usb - Used to generate a 48MHz USB reference clock
|
||||
*
|
||||
* For details on how the PLL's are calculated, please refer to the RP2040 datasheet.
|
||||
*/
|
||||
|
||||
typedef pll_hw_t *PLL;
|
||||
|
||||
#define pll_sys pll_sys_hw
|
||||
#define pll_usb pll_usb_hw
|
||||
|
||||
/*! \brief Initialise specified PLL.
|
||||
* \ingroup hardware_pll
|
||||
* \param pll pll_sys or pll_usb
|
||||
* \param ref_div Input clock divider.
|
||||
* \param vco_freq Requested output from the VCO (voltage controlled oscillator)
|
||||
* \param post_div1 Post Divider 1 - range 1-7. Must be >= post_div2
|
||||
* \param post_div2 Post Divider 2 - range 1-7
|
||||
*/
|
||||
void pll_init(PLL pll, uint32_t ref_div, uint32_t vco_freq, uint32_t post_div1, uint8_t post_div2);
|
||||
|
||||
/*! \brief Release/uninitialise specified PLL.
|
||||
* \ingroup hardware_pll
|
||||
*
|
||||
* This will turn off the power to the specified PLL. Note this function does not currently check if
|
||||
* the PLL is in use before powering it off so should be used with care.
|
||||
*
|
||||
* \param pll pll_sys or pll_usb
|
||||
*/
|
||||
void pll_deinit(PLL pll);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
66
src/rp2_common/hardware_pll/pll.c
Normal file
66
src/rp2_common/hardware_pll/pll.c
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
// For MHZ definitions etc
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/pll.h"
|
||||
|
||||
/// \tag::pll_init_calculations[]
|
||||
void pll_init(PLL pll, uint32_t refdiv, uint32_t vco_freq, uint32_t post_div1, uint8_t post_div2) {
|
||||
// Turn off PLL in case it is already running
|
||||
pll->pwr = 0xffffffff;
|
||||
pll->fbdiv_int = 0;
|
||||
|
||||
uint32_t ref_mhz = XOSC_MHZ / refdiv;
|
||||
pll->cs = 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)
|
||||
uint32_t fbdiv = vco_freq / (ref_mhz * MHZ);
|
||||
/// \end::pll_init_calculations[]
|
||||
|
||||
// fbdiv
|
||||
assert(fbdiv >= 16 && fbdiv <= 320);
|
||||
|
||||
// Check divider ranges
|
||||
assert((post_div1 >= 1 && post_div1 <= 7) && (post_div2 >= 1 && post_div2 <= 7));
|
||||
|
||||
// post_div1 should be >= post_div2
|
||||
// from appnote page 11
|
||||
// postdiv1 is designed to operate with a higher input frequency
|
||||
// than postdiv2
|
||||
assert(post_div2 <= post_div1);
|
||||
|
||||
/// \tag::pll_init_finish[]
|
||||
// Check that reference frequency is no greater than vco / 16
|
||||
assert(ref_mhz <= (vco_freq / 16));
|
||||
|
||||
// Put calculated value into feedback divider
|
||||
pll->fbdiv_int = fbdiv;
|
||||
|
||||
// Turn on PLL
|
||||
uint32_t power = PLL_PWR_PD_BITS | // Main power
|
||||
PLL_PWR_VCOPD_BITS; // VCO Power
|
||||
|
||||
hw_clear_bits(&pll->pwr, power);
|
||||
|
||||
// Wait for PLL to lock
|
||||
while (!(pll->cs & PLL_CS_LOCK_BITS)) tight_loop_contents();
|
||||
|
||||
// Set up post dividers - div1 feeds into div2 so if div1 is 5 and div2 is 2 then you get a divide by 10
|
||||
uint32_t pdiv = (post_div1 << PLL_PRIM_POSTDIV1_LSB) |
|
||||
(post_div2 << PLL_PRIM_POSTDIV2_LSB);
|
||||
pll->prim = pdiv;
|
||||
|
||||
// Turn on post divider
|
||||
hw_clear_bits(&pll->pwr, PLL_PWR_POSTDIVPD_BITS);
|
||||
/// \end::pll_init_finish[]
|
||||
}
|
||||
|
||||
void pll_deinit(PLL pll) {
|
||||
// todo: Make sure there are no sources running from this pll?
|
||||
pll->pwr = PLL_PWR_BITS;
|
||||
}
|
1
src/rp2_common/hardware_pwm/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_pwm/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_headers_only_target(pwm)
|
491
src/rp2_common/hardware_pwm/include/hardware/pwm.h
Normal file
491
src/rp2_common/hardware_pwm/include/hardware/pwm.h
Normal file
@ -0,0 +1,491 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_PWM_H
|
||||
#define _HARDWARE_PWM_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/pwm.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PWM, Enable/disable assertions in the PWM module, type=bool, default=0, group=hadrware_pwm
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_PWM
|
||||
#define PARAM_ASSERTIONS_ENABLED_PWM 0
|
||||
#endif
|
||||
|
||||
/** \file hardware/pwm.h
|
||||
* \defgroup hardware_pwm hardware_pwm
|
||||
*
|
||||
* Hardware Pulse Width Modulation (PWM) API
|
||||
*
|
||||
* The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or
|
||||
* measure the frequency or duty cycle of an input signal. This gives a total of up to 16 controllable
|
||||
* PWM outputs. All 30 GPIOs can be driven by the PWM block
|
||||
*
|
||||
* The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a
|
||||
* toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of
|
||||
* time spent at the high signal level is known as the duty cycle of the signal.
|
||||
*
|
||||
* The default behaviour of a PWM slice is to count upward until the wrap value (\ref pwm_config_set_wrap) is reached, and then
|
||||
* immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after
|
||||
* reaching TOP, until it reaches 0 again.
|
||||
*
|
||||
* \subsection pwm_example Example
|
||||
* \addtogroup hardware_pwm
|
||||
* \include hello_pwm.c
|
||||
*/
|
||||
|
||||
/** \brief PWM Divider mode settings
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
*/
|
||||
enum pwm_clkdiv_mode
|
||||
{
|
||||
PWM_DIV_FREE_RUNNING, ///< Free-running counting at rate dictated by fractional divider
|
||||
PWM_DIV_B_HIGH, ///< Fractional divider is gated by the PWM B pin
|
||||
PWM_DIV_B_RISING, ///< Fractional divider advances with each rising edge of the PWM B pin
|
||||
PWM_DIV_B_FALLING ///< Fractional divider advances with each falling edge of the PWM B pin
|
||||
};
|
||||
|
||||
enum pwm_chan
|
||||
{
|
||||
PWM_CHAN_A = 0,
|
||||
PWM_CHAN_B = 1
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint32_t csr;
|
||||
uint32_t div;
|
||||
uint32_t top;
|
||||
} pwm_config;
|
||||
|
||||
/** \brief Determine the PWM slice that is attached to the specified GPIO
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \return The PWM slice number that controls the specified GPIO.
|
||||
*/
|
||||
static inline uint pwm_gpio_to_slice_num(uint gpio) {
|
||||
valid_params_if(PWM, gpio < N_GPIOS);
|
||||
return (gpio >> 1u) & 7u;
|
||||
}
|
||||
|
||||
/** \brief Determine the PWM channel that is attached to the specified GPIO.
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Each slice 0 to 7 has two channels, A and B.
|
||||
*
|
||||
* \return The PWM channel that controls the specified GPIO.
|
||||
*/
|
||||
static inline uint pwm_gpio_to_channel(uint gpio) {
|
||||
valid_params_if(PWM, gpio < N_GPIOS);
|
||||
return gpio & 1u;
|
||||
}
|
||||
|
||||
/** \brief Set phase correction in a PWM configuration
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param c PWM configuration struct to modify
|
||||
* \param phase_correct true to set phase correct modulation, false to set trailing edge
|
||||
*
|
||||
* Setting phase control to true means that instead of wrapping back to zero when the wrap point is reached,
|
||||
* the PWM starts counting back down. The output frequency is halved when phase-correct mode is enabled.
|
||||
*/
|
||||
static inline void pwm_config_set_phase_correct(pwm_config *c, bool phase_correct) {
|
||||
c->csr = (c->csr & ~PWM_CH0_CSR_PH_CORRECT_BITS)
|
||||
| (!!phase_correct << PWM_CH0_CSR_PH_CORRECT_LSB);
|
||||
}
|
||||
|
||||
/** \brief Set clock divider in a PWM configuration
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param c PWM configuration struct to modify
|
||||
* \param div Value to divide counting rate by. Must be greater than or equal to 1.
|
||||
*
|
||||
* If the divide mode is free-running, the PWM counter runs at clk_sys / div.
|
||||
* Otherwise, the divider reduces the rate of events seen on the B pin input (level or edge)
|
||||
* before passing them on to the PWM counter.
|
||||
*/
|
||||
static inline void pwm_config_set_clkdiv(pwm_config *c, float div) {
|
||||
c->div = (uint32_t)(div * (float)(1u << PWM_CH1_DIV_INT_LSB));
|
||||
}
|
||||
|
||||
/** \brief Set PWM clock divider in a PWM configuration
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param c PWM configuration struct to modify
|
||||
* \param div integer value to reduce counting rate by. Must be greater than or equal to 1.
|
||||
*
|
||||
* If the divide mode is free-running, the PWM counter runs at clk_sys / div.
|
||||
* Otherwise, the divider reduces the rate of events seen on the B pin input (level or edge)
|
||||
* before passing them on to the PWM counter.
|
||||
*/
|
||||
static inline void pwm_config_set_clkdiv_int(pwm_config *c, uint div) {
|
||||
c->div = div << PWM_CH1_DIV_INT_LSB;
|
||||
}
|
||||
|
||||
/** \brief Set PWM counting mode in a PWM configuration
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param c PWM configuration struct to modify
|
||||
* \param mode PWM divide/count mode
|
||||
*
|
||||
* Configure which event gates the operation of the fractional divider.
|
||||
* The default is always-on (free-running PWM). Can also be configured to count on
|
||||
* high level, rising edge or falling edge of the B pin input.
|
||||
*/
|
||||
static inline void pwm_config_set_clkdiv_mode(pwm_config *c, enum pwm_clkdiv_mode mode) {
|
||||
valid_params_if(PWM, mode >= PWM_DIV_FREE_RUNNING && mode <= PWM_DIV_B_FALLING);
|
||||
c->csr = (c->csr & ~PWM_CH0_CSR_DIVMODE_BITS)
|
||||
| (mode << PWM_CH0_CSR_DIVMODE_LSB);
|
||||
}
|
||||
|
||||
/** \brief Set output polarity in a PWM configuration
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param c PWM configuration struct to modify
|
||||
* \param a true to invert output A
|
||||
* \param b true to invert output B
|
||||
*/
|
||||
static inline void pwm_config_set_output_polarity(pwm_config *c, bool a, bool b) {
|
||||
c->csr = (c->csr & ~(PWM_CH0_CSR_A_INV_BITS | PWM_CH0_CSR_B_INV_BITS))
|
||||
| ((!!a << PWM_CH0_CSR_A_INV_LSB) | (!!b << PWM_CH0_CSR_B_INV_LSB));
|
||||
}
|
||||
|
||||
/** \brief Set PWM counter wrap value in a PWM configuration
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Set the highest value the counter will reach before returning to 0. Also known as TOP.
|
||||
*
|
||||
* \param c PWM configuration struct to modify
|
||||
* \param wrap Value to set wrap to
|
||||
*/
|
||||
static inline void pwm_config_set_wrap(pwm_config *c, uint16_t wrap) {
|
||||
c->top = wrap;
|
||||
}
|
||||
|
||||
/** \brief Initialise a PWM with settings from a configuration object
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Use the \ref pwm_get_default_config() function to initialise a config structure, make changes as
|
||||
* needed using the pwm_config_* functions, then call this function to set up the PWM.
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \param c The configuration to use
|
||||
* \param start If true the PWM will be started running once configured. If false you will need to start
|
||||
* manually using \ref pwm_set_enabled() or \ref pwm_set_mask_enabled()
|
||||
*/
|
||||
static inline void pwm_init(uint slice_num, pwm_config *c, bool start) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
pwm_hw->slice[slice_num].csr = 0;
|
||||
|
||||
pwm_hw->slice[slice_num].ctr = PWM_CH0_CTR_RESET;
|
||||
pwm_hw->slice[slice_num].cc = PWM_CH0_CC_RESET;
|
||||
pwm_hw->slice[slice_num].top = c->top;
|
||||
pwm_hw->slice[slice_num].div = c->div;
|
||||
pwm_hw->slice[slice_num].csr = c->csr | (!!start << PWM_CH0_CSR_EN_LSB);
|
||||
}
|
||||
|
||||
/** \brief Get a set of default values for PWM configuration
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* PWM config is free running at system clock speed, no phase correction, wrapping at 0xffff,
|
||||
* with standard polarities for channels A and B.
|
||||
*
|
||||
* \return Set of default values.
|
||||
*/
|
||||
static inline pwm_config pwm_get_default_config() {
|
||||
pwm_config c = {0, 0, 0};
|
||||
pwm_config_set_phase_correct(&c, false);
|
||||
pwm_config_set_clkdiv_int(&c, 1);
|
||||
pwm_config_set_clkdiv_mode(&c, PWM_DIV_FREE_RUNNING);
|
||||
pwm_config_set_output_polarity(&c, false, false);
|
||||
pwm_config_set_wrap(&c, 0xffffu);
|
||||
return c;
|
||||
}
|
||||
|
||||
/** \brief Set the current PWM counter wrap value
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Set the highest value the counter will reach before returning to 0. Also known as TOP.
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \param wrap Value to set wrap to
|
||||
*/
|
||||
static inline void pwm_set_wrap(uint slice_num, uint16_t wrap) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
pwm_hw->slice[slice_num].top = wrap;
|
||||
}
|
||||
|
||||
/** \brief Set the current PWM counter compare value for one channel
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Set the value of the PWM counter compare value, for either channel A or channel B
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \param chan Which channel to update. 0 for A, 1 for B.
|
||||
* \param level new level for the selected output
|
||||
*/
|
||||
static inline void pwm_set_chan_level(uint slice_num, uint chan, uint16_t level) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
hw_write_masked(
|
||||
&pwm_hw->slice[slice_num].cc,
|
||||
level << (chan ? PWM_CH0_CC_B_LSB : PWM_CH0_CC_A_LSB),
|
||||
chan ? PWM_CH0_CC_B_BITS : PWM_CH0_CC_A_BITS
|
||||
);
|
||||
}
|
||||
|
||||
/** \brief Set PWM counter compare values
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Set the value of the PWM counter compare values, A and B
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \param level_a Value to set compare A to. When the counter reaches this value the A output is deasserted
|
||||
* \param level_b Value to set compare B to. When the counter reaches this value the B output is deasserted
|
||||
*/
|
||||
static inline void pwm_set_both_levels(uint slice_num, uint16_t level_a, uint16_t level_b) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
pwm_hw->slice[slice_num].cc = (level_b << PWM_CH0_CC_B_LSB) | (level_a << PWM_CH0_CC_A_LSB);
|
||||
}
|
||||
|
||||
/** \brief Helper function to set the PWM level for the slice and channel associated with a GPIO.
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Look up the correct slice (0 to 7) and channel (A or B) for a given GPIO, and update the corresponding
|
||||
* counter-compare field.
|
||||
*
|
||||
* This PWM slice should already have been configured and set running. Also be careful of multiple GPIOs
|
||||
* mapping to the same slice and channel (if GPIOs have a difference of 16).
|
||||
*
|
||||
* \param gpio GPIO to set level of
|
||||
* \param level PWM level for this GPIO
|
||||
*/
|
||||
static inline void pwm_set_gpio_level(uint gpio, uint16_t level) {
|
||||
valid_params_if(PWM, gpio < N_GPIOS);
|
||||
pwm_set_chan_level(pwm_gpio_to_slice_num(gpio), pwm_gpio_to_channel(gpio), level);
|
||||
}
|
||||
|
||||
/** \brief Get PWM counter
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Get current value of PWM counter
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \return Current value of PWM counter
|
||||
*/
|
||||
static inline int16_t pwm_get_counter(uint slice_num) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
return (pwm_hw->slice[slice_num].ctr);
|
||||
}
|
||||
|
||||
/** \brief Set PWM counter
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Set the value of the PWM counter
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \param c Value to set the PWM counter to
|
||||
*
|
||||
*/
|
||||
static inline void pwm_set_counter(uint slice_num, uint16_t c) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
pwm_hw->slice[slice_num].ctr = c;
|
||||
}
|
||||
|
||||
/** \brief Advance PWM count
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Advance the phase of a running the counter by 1 count.
|
||||
*
|
||||
* This function will return once the increment is complete.
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
*/
|
||||
static inline void pwm_advance_count(uint slice_num) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
hw_set_bits(&pwm_hw->slice[slice_num].csr, PWM_CH0_CSR_PH_ADV_BITS);
|
||||
while (pwm_hw->slice[slice_num].csr & PWM_CH0_CSR_PH_ADV_BITS) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
}
|
||||
|
||||
/** \brief Retard PWM count
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Retard the phase of a running counter by 1 count
|
||||
*
|
||||
* This function will return once the retardation is complete.
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
*/
|
||||
static inline void pwm_retard_count(uint slice_num) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
hw_set_bits(&pwm_hw->slice[slice_num].csr, PWM_CH0_CSR_PH_RET_BITS);
|
||||
while (pwm_hw->slice[slice_num].csr & PWM_CH0_CSR_PH_RET_BITS) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
}
|
||||
|
||||
/** \brief Set PWM clock divider using an 8:4 fractional value
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Set the clock divider. Counter increment will be on sysclock divided by this value, taking in to account the gating.
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \param integer 8 bit integer part of the clock divider
|
||||
* \param fract 4 bit fractional part of the clock divider
|
||||
*/
|
||||
static inline void pwm_set_clkdiv_int_frac(uint slice_num, uint8_t integer, uint8_t fract) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
valid_params_if(PWM, fract >= 0 && slice_num <= 16);
|
||||
pwm_hw->slice[slice_num].div = (integer << PWM_CH0_DIV_INT_LSB) | (fract << PWM_CH0_DIV_FRAC_LSB);
|
||||
}
|
||||
|
||||
/** \brief Set PWM clock divider
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Set the clock divider. Counter increment will be on sysclock divided by this value, taking in to account the gating.
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \param divider Floating point clock divider, 1.f <= value < 256.f
|
||||
*/
|
||||
static inline void pwm_set_clkdiv(uint slice_num, float divider) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
valid_params_if(PWM, divider >= 1.f && divider < 256.f);
|
||||
uint8_t i = (uint8_t)divider;
|
||||
uint8_t f = (uint8_t)((divider - i) * (0x01 << 4));
|
||||
pwm_set_clkdiv_int_frac(slice_num, i, f);
|
||||
}
|
||||
|
||||
/** \brief Set PWM output polarity
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \param a true to invert output A
|
||||
* \param b true to invert output B
|
||||
*/
|
||||
static inline void pwm_set_output_polarity(uint slice_num, bool a, bool b) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
hw_write_masked(&pwm_hw->slice[slice_num].csr, !!a << PWM_CH0_CSR_A_INV_LSB | !!b << PWM_CH0_CSR_B_INV_LSB,
|
||||
PWM_CH0_CSR_A_INV_BITS | PWM_CH0_CSR_B_INV_BITS);
|
||||
}
|
||||
|
||||
|
||||
/** \brief Set PWM divider mode
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \param mode Required divider mode
|
||||
*/
|
||||
static inline void pwm_set_clkdiv_mode(uint slice_num, enum pwm_clkdiv_mode mode) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
valid_params_if(PWM, mode >= PWM_DIV_FREE_RUNNING && mode <= PWM_DIV_B_FALLING);
|
||||
hw_write_masked(&pwm_hw->slice[slice_num].csr, mode << PWM_CH0_CSR_DIVMODE_LSB, PWM_CH0_CSR_DIVMODE_BITS);
|
||||
}
|
||||
|
||||
/** \brief Set PWM phase correct on/off
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \param phase_correct true to set phase correct modulation, false to set trailing edge
|
||||
*
|
||||
* Setting phase control to true means that instead of wrapping back to zero when the wrap point is reached,
|
||||
* the PWM starts counting back down. The output frequency is halved when phase-correct mode is enabled.
|
||||
*/
|
||||
static inline void pwm_set_phase_correct(uint slice_num, bool phase_correct) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
hw_write_masked(&pwm_hw->slice[slice_num].csr, phase_correct << PWM_CH0_CSR_PH_CORRECT_LSB, PWM_CH0_CSR_PH_CORRECT_BITS);
|
||||
}
|
||||
|
||||
/** \brief Enable/Disable PWM
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
* \param enabled true to enable the specified PWM, false to disable
|
||||
*/
|
||||
static inline void pwm_set_enabled(uint slice_num, bool enabled) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
hw_write_masked(&pwm_hw->slice[slice_num].csr, !!enabled << PWM_CH0_CSR_EN_LSB, PWM_CH0_CSR_EN_BITS);
|
||||
}
|
||||
|
||||
/** \brief Enable/Disable multiple PWM slices simultaneously
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param mask Bitmap of PWMs to enable/disable. Bits 0 to 7 enable slices 0-7 respectively
|
||||
*/
|
||||
static inline void pwm_set_mask_enabled(uint32_t mask) {
|
||||
pwm_hw->en = mask;
|
||||
}
|
||||
|
||||
/*! \brief Enable PWM instance interrupt
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Used to enable a single PWM instance interrupt
|
||||
*
|
||||
* \param slice_num PWM block to enable/disable
|
||||
* \param enabled true to enable, false to disable
|
||||
*/
|
||||
static inline void pwm_set_irq_enabled(uint slice_num, bool enabled) {
|
||||
valid_params_if(PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES);
|
||||
if (enabled) {
|
||||
hw_set_bits(&pwm_hw->inte, 1u << slice_num);
|
||||
} else {
|
||||
hw_clear_bits(&pwm_hw->inte, 1u << slice_num);
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Enable multiple PWM instance interrupts
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* Use this to enable multiple PWM interrupts at once.
|
||||
*
|
||||
* \param slice_mask Bitmask of all the blocks to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc.
|
||||
* \param enabled true to enable, false to disable
|
||||
*/
|
||||
static inline void pwm_set_irq_mask_enabled(uint32_t slice_mask, bool enabled) {
|
||||
valid_params_if(PWM, slice_mask < 256);
|
||||
if (enabled) {
|
||||
hw_set_bits(&pwm_hw->inte, slice_mask);
|
||||
} else {
|
||||
hw_clear_bits(&pwm_hw->inte, slice_mask);
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Clear single PWM channel interrupt
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
*/
|
||||
static inline void pwm_clear_irq(uint slice_num) {
|
||||
pwm_hw->intr = 1u << slice_num;
|
||||
}
|
||||
|
||||
/*! \brief Get PWM interrupt status, raw
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \return Bitmask of all PWM interrupts currently set
|
||||
*/
|
||||
static inline int32_t pwm_get_irq_status_mask() {
|
||||
return pwm_hw->ints;
|
||||
}
|
||||
|
||||
/*! \brief Force PWM interrupt
|
||||
* \ingroup hardware_pwm
|
||||
*
|
||||
* \param slice_num PWM slice number
|
||||
*/
|
||||
static inline void pwm_force_irq(uint slice_num) {
|
||||
pwm_hw->intf = 1u << slice_num;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
2
src/rp2_common/hardware_resets/CMakeLists.txt
Normal file
2
src/rp2_common/hardware_resets/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
add_library(hardware_resets INTERFACE)
|
||||
target_include_directories(hardware_resets INTERFACE include)
|
91
src/rp2_common/hardware_resets/include/hardware/resets.h
Normal file
91
src/rp2_common/hardware_resets/include/hardware/resets.h
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_RESETS_H
|
||||
#define _HARDWARE_RESETS_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/resets.h"
|
||||
|
||||
/** \file hardware/resets.h
|
||||
* \defgroup hardware_resets hardware_resets
|
||||
*
|
||||
* Hardware Reset API
|
||||
*
|
||||
* The reset controller allows software control of the resets to all of the peripherals that are not
|
||||
* critical to boot the processor in the RP2040.
|
||||
*
|
||||
* \subsubsection reset_bitmask
|
||||
* \addtogroup hardware_resets
|
||||
*
|
||||
* Multiple blocks are referred to using a bitmask as follows:
|
||||
*
|
||||
* Block to reset | Bit
|
||||
* ---------------|----
|
||||
* USB | 24
|
||||
* UART 1 | 23
|
||||
* UART 0 | 22
|
||||
* Timer | 21
|
||||
* TB Manager | 20
|
||||
* SysInfo | 19
|
||||
* System Config | 18
|
||||
* SPI 1 | 17
|
||||
* SPI 0 | 16
|
||||
* RTC | 15
|
||||
* PWM | 14
|
||||
* PLL USB | 13
|
||||
* PLL System | 12
|
||||
* PIO 1 | 11
|
||||
* PIO 0 | 10
|
||||
* Pads - QSPI | 9
|
||||
* Pads - bank 0 | 8
|
||||
* JTAG | 7
|
||||
* IO Bank 1 | 6
|
||||
* IO Bank 0 | 5
|
||||
* I2C 1 | 4
|
||||
* I2C 0 | 3
|
||||
* DMA | 2
|
||||
* Bus Control | 1
|
||||
* ADC 0 | 0
|
||||
*
|
||||
* \subsection reset_example Example
|
||||
* \addtogroup hardware_resets
|
||||
* \include hello_reset.c
|
||||
*/
|
||||
|
||||
/// \tag::reset_funcs[]
|
||||
|
||||
/*! \brief Reset the specified HW blocks
|
||||
* \ingroup hardware_resets
|
||||
*
|
||||
* \param bits Bit pattern indicating blocks to reset. See \ref reset_bitmask
|
||||
*/
|
||||
static inline void reset_block(uint32_t bits) {
|
||||
hw_set_bits(&resets_hw->reset, bits);
|
||||
}
|
||||
|
||||
/*! \brief bring specified HW blocks out of reset
|
||||
* \ingroup hardware_resets
|
||||
*
|
||||
* \param bits Bit pattern indicating blocks to unreset. See \ref reset_bitmask
|
||||
*/
|
||||
static inline void unreset_block(uint32_t bits) {
|
||||
hw_clear_bits(&resets_hw->reset, bits);
|
||||
}
|
||||
|
||||
/*! \brief Bring specified HW blocks out of reset and wait for completion
|
||||
* \ingroup hardware_resets
|
||||
*
|
||||
* \param bits Bit pattern indicating blocks to unreset. See \ref reset_bitmask
|
||||
*/
|
||||
static inline void unreset_block_wait(uint32_t bits) {
|
||||
hw_clear_bits(&resets_hw->reset, bits);
|
||||
while (~resets_hw->reset_done & bits)
|
||||
tight_loop_contents();
|
||||
}
|
||||
/// \end::reset_funcs[]
|
||||
|
||||
#endif
|
1
src/rp2_common/hardware_rtc/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_rtc/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(rtc)
|
74
src/rp2_common/hardware_rtc/include/hardware/rtc.h
Normal file
74
src/rp2_common/hardware_rtc/include/hardware/rtc.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_RTC_H
|
||||
#define _HARDWARE_RTC_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/rtc.h"
|
||||
|
||||
/** \file hardware/rtc.h
|
||||
* \defgroup hardware_rtc hardware_rtc
|
||||
*
|
||||
* Hardware Real Time Clock API
|
||||
*
|
||||
* The RTC keeps track of time in human readable format and generates events when the time is equal
|
||||
* to a preset value. Think of a digital clock, not epoch time used by most computers. There are seven
|
||||
* fields, one each for year (12 bit), month (4 bit), day (5 bit), day of the week (3 bit), hour (5 bit)
|
||||
* minute (6 bit) and second (6 bit), storing the data in binary format.
|
||||
*
|
||||
* \sa datetime_t
|
||||
*
|
||||
* \subsection rtc_example Example
|
||||
* \addtogroup hardware_rtc
|
||||
*
|
||||
* \include hello_rtc.c
|
||||
*/
|
||||
|
||||
|
||||
typedef void (*rtc_callback_t)(void);
|
||||
|
||||
/*! \brief Initialise the RTC system
|
||||
* \ingroup hardware_rtc
|
||||
*/
|
||||
void rtc_init(void);
|
||||
|
||||
/*! \brief Set the RTC to the specified time
|
||||
* \ingroup hardware_rtc
|
||||
*
|
||||
* \param t Pointer to a \ref datetime_t structure contains time to set
|
||||
* \return true if set, false if the passed in datetime was invalid.
|
||||
*/
|
||||
bool rtc_set_datetime(datetime_t *t);
|
||||
|
||||
/*! \brief Get the current time from the RTC
|
||||
* \ingroup hardware_rtc
|
||||
*
|
||||
* \param t Pointer to a \ref datetime_t structure to receive the current RTC time
|
||||
* \return true if datetime is valid, false if the RTC is not running.
|
||||
*/
|
||||
bool rtc_get_datetime(datetime_t *t);
|
||||
|
||||
/*! \brief Is the RTC running?
|
||||
* \ingroup hardware_rtc
|
||||
*
|
||||
*/
|
||||
bool rtc_running(void);
|
||||
|
||||
/*! \brief Set a time in the future for the RTC to call a user provided callback
|
||||
* \ingroup hardware_rtc
|
||||
*
|
||||
* \param t Pointer to a \ref datetime_t structure containing a time in the future to fire the alarm. Any values set to -1 will not be matched on.
|
||||
* \param user_callback pointer to a \ref rtc_callback_t to call when the alarm fires
|
||||
*/
|
||||
void rtc_set_alarm(datetime_t *t, rtc_callback_t user_callback);
|
||||
|
||||
/*! \brief Disable the RTC alarm (if active)
|
||||
* \ingroup hardware_rtc
|
||||
*/
|
||||
void rtc_disable_alarm(void);
|
||||
|
||||
#endif
|
188
src/rp2_common/hardware_rtc/rtc.c
Normal file
188
src/rp2_common/hardware_rtc/rtc.c
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "pico.h"
|
||||
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/rtc.h"
|
||||
#include "hardware/resets.h"
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
// Set this when setting an alarm
|
||||
static rtc_callback_t _callback = NULL;
|
||||
static bool _alarm_repeats = false;
|
||||
|
||||
bool rtc_running(void) {
|
||||
return (rtc_hw->ctrl & RTC_CTRL_RTC_ACTIVE_BITS);
|
||||
}
|
||||
|
||||
void rtc_init(void) {
|
||||
// Get clk_rtc freq and make sure it is running
|
||||
uint rtc_freq = clock_get_hz(clk_rtc);
|
||||
assert(rtc_freq != 0);
|
||||
|
||||
// Take rtc out of reset now that we know clk_rtc is running
|
||||
reset_block(RESETS_RESET_RTC_BITS);
|
||||
unreset_block_wait(RESETS_RESET_RTC_BITS);
|
||||
|
||||
// Set up the 1 second divider.
|
||||
// If rtc_freq is 400 then clkdiv_m1 should be 399
|
||||
rtc_freq -= 1;
|
||||
|
||||
// Check the freq is not too big to divide
|
||||
assert(rtc_freq <= RTC_CLKDIV_M1_BITS);
|
||||
|
||||
// Write divide value
|
||||
rtc_hw->clkdiv_m1 = rtc_freq;
|
||||
}
|
||||
|
||||
static bool valid_datetime(datetime_t *t) {
|
||||
// Valid ranges taken from RTC doc. Note when setting an RTC alarm
|
||||
// these values are allowed to be -1 to say "don't match this value"
|
||||
if (!(t->year >= 0 && t->year <= 4095)) return false;
|
||||
if (!(t->month >= 1 && t->month <= 12)) return false;
|
||||
if (!(t->day >= 1 && t->day <= 31)) return false;
|
||||
if (!(t->dotw >= 0 && t->dotw <= 6)) return false;
|
||||
if (!(t->hour >= 0 && t->hour <= 23)) return false;
|
||||
if (!(t->min >= 0 && t->min <= 59)) return false;
|
||||
if (!(t->sec >= 0 && t->sec <= 59)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rtc_set_datetime(datetime_t *t) {
|
||||
if (!valid_datetime(t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable RTC
|
||||
rtc_hw->ctrl = 0;
|
||||
// Wait while it is still active
|
||||
while (rtc_running()) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
|
||||
// Write to setup registers
|
||||
rtc_hw->setup_0 = (t->year << RTC_SETUP_0_YEAR_LSB ) |
|
||||
(t->month << RTC_SETUP_0_MONTH_LSB) |
|
||||
(t->day << RTC_SETUP_0_DAY_LSB);
|
||||
rtc_hw->setup_1 = (t->dotw << RTC_SETUP_1_DOTW_LSB) |
|
||||
(t->hour << RTC_SETUP_1_HOUR_LSB) |
|
||||
(t->min << RTC_SETUP_1_MIN_LSB) |
|
||||
(t->sec << RTC_SETUP_1_SEC_LSB);
|
||||
|
||||
// Load setup values into rtc clock domain
|
||||
rtc_hw->ctrl = RTC_CTRL_LOAD_BITS;
|
||||
|
||||
// Enable RTC and wait for it to be running
|
||||
rtc_hw->ctrl = RTC_CTRL_RTC_ENABLE_BITS;
|
||||
while (!rtc_running()) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rtc_get_datetime(datetime_t *t) {
|
||||
// Make sure RTC is running
|
||||
if (!rtc_running()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: RTC_0 should be read before RTC_1
|
||||
t->dotw = (rtc_hw->rtc_0 & RTC_RTC_0_DOTW_BITS ) >> RTC_RTC_0_DOTW_LSB;
|
||||
t->hour = (rtc_hw->rtc_0 & RTC_RTC_0_HOUR_BITS ) >> RTC_RTC_0_HOUR_LSB;
|
||||
t->min = (rtc_hw->rtc_0 & RTC_RTC_0_MIN_BITS ) >> RTC_RTC_0_MIN_LSB;
|
||||
t->sec = (rtc_hw->rtc_0 & RTC_RTC_0_SEC_BITS ) >> RTC_RTC_0_SEC_LSB;
|
||||
t->year = (rtc_hw->rtc_1 & RTC_RTC_1_YEAR_BITS ) >> RTC_RTC_1_YEAR_LSB;
|
||||
t->month = (rtc_hw->rtc_1 & RTC_RTC_1_MONTH_BITS) >> RTC_RTC_1_MONTH_LSB;
|
||||
t->day = (rtc_hw->rtc_1 & RTC_RTC_1_DAY_BITS ) >> RTC_RTC_1_DAY_LSB;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void rtc_enable_alarm(void) {
|
||||
// Set matching and wait for it to be enabled
|
||||
hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MATCH_ENA_BITS);
|
||||
while(!(rtc_hw->irq_setup_0 & RTC_IRQ_SETUP_0_MATCH_ACTIVE_BITS)) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
}
|
||||
|
||||
static void rtc_irq_handler(void) {
|
||||
// Always disable the alarm to clear the current IRQ.
|
||||
// Even if it is a repeatable alarm, we don't want it to keep firing.
|
||||
// If it matches on a second it can keep firing for that second.
|
||||
rtc_disable_alarm();
|
||||
|
||||
if (_alarm_repeats) {
|
||||
// If it is a repeatable alarm, re enable the alarm.
|
||||
rtc_enable_alarm();
|
||||
}
|
||||
|
||||
// Call user callback function
|
||||
if (_callback) {
|
||||
_callback();
|
||||
}
|
||||
}
|
||||
|
||||
static bool rtc_alarm_repeats(datetime_t *t) {
|
||||
// If any value is set to -1 then we don't match on that value
|
||||
// hence the alarm will eventually repeat
|
||||
if (t->year == -1) return true;
|
||||
if (t->month == -1) return true;
|
||||
if (t->day == -1) return true;
|
||||
if (t->dotw == -1) return true;
|
||||
if (t->hour == -1) return true;
|
||||
if (t->min == -1) return true;
|
||||
if (t->sec == -1) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void rtc_set_alarm(datetime_t *t, rtc_callback_t user_callback) {
|
||||
rtc_disable_alarm();
|
||||
|
||||
// Only add to setup if it isn't -1
|
||||
rtc_hw->irq_setup_0 = ((t->year == -1) ? 0 : (t->year << RTC_IRQ_SETUP_0_YEAR_LSB )) |
|
||||
((t->month == -1) ? 0 : (t->month << RTC_IRQ_SETUP_0_MONTH_LSB)) |
|
||||
((t->day == -1) ? 0 : (t->day << RTC_IRQ_SETUP_0_DAY_LSB ));
|
||||
rtc_hw->irq_setup_1 = ((t->dotw == -1) ? 0 : (t->dotw << RTC_IRQ_SETUP_1_DOTW_LSB)) |
|
||||
((t->hour == -1) ? 0 : (t->hour << RTC_IRQ_SETUP_1_HOUR_LSB)) |
|
||||
((t->min == -1) ? 0 : (t->min << RTC_IRQ_SETUP_1_MIN_LSB )) |
|
||||
((t->sec == -1) ? 0 : (t->sec << RTC_IRQ_SETUP_1_SEC_LSB ));
|
||||
|
||||
// Set the match enable bits for things we care about
|
||||
if (t->year != -1) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_YEAR_ENA_BITS);
|
||||
if (t->month != -1) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MONTH_ENA_BITS);
|
||||
if (t->day != -1) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_DAY_ENA_BITS);
|
||||
if (t->dotw != -1) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_DOTW_ENA_BITS);
|
||||
if (t->hour != -1) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_HOUR_ENA_BITS);
|
||||
if (t->min != -1) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_MIN_ENA_BITS);
|
||||
if (t->sec != -1) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_SEC_ENA_BITS);
|
||||
|
||||
// Does it repeat? I.e. do we not match on any of the bits
|
||||
_alarm_repeats = rtc_alarm_repeats(t);
|
||||
|
||||
// Store function pointer we can call later
|
||||
_callback = user_callback;
|
||||
|
||||
irq_set_exclusive_handler(RTC_IRQ, rtc_irq_handler);
|
||||
|
||||
// Enable the IRQ at the peri
|
||||
rtc_hw->inte = RTC_INTE_RTC_BITS;
|
||||
|
||||
// Enable the IRQ at the proc
|
||||
irq_set_enabled(RTC_IRQ, true);
|
||||
|
||||
rtc_enable_alarm();
|
||||
}
|
||||
|
||||
void rtc_disable_alarm(void) {
|
||||
// Disable matching and wait for it to stop being active
|
||||
hw_clear_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MATCH_ENA_BITS);
|
||||
while(rtc_hw->irq_setup_0 & RTC_IRQ_SETUP_0_MATCH_ACTIVE_BITS) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
}
|
1
src/rp2_common/hardware_spi/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_spi/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(spi)
|
297
src/rp2_common/hardware_spi/include/hardware/spi.h
Normal file
297
src/rp2_common/hardware_spi/include/hardware/spi.h
Normal file
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_SPI_H
|
||||
#define _HARDWARE_SPI_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "pico/time.h"
|
||||
#include "hardware/structs/spi.h"
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_SPI, Enable/disable assertions in the SPI module, type=bool, default=0, group=hardware_spi
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_SPI
|
||||
#define PARAM_ASSERTIONS_ENABLED_SPI 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file hardware/spi.h
|
||||
* \defgroup hardware_spi hardware_spi
|
||||
*
|
||||
* Hardware SPI API
|
||||
*
|
||||
* RP2040 has 2 identical instances of the Serial Peripheral Interface (SPI) controller.
|
||||
*
|
||||
* The PrimeCell SSP is a master or slave interface for synchronous serial communication with peripheral devices that have
|
||||
* Motorola SPI, National Semiconductor Microwire, or Texas Instruments synchronous serial interfaces.
|
||||
*
|
||||
* Controller can be defined as master or slave using the \ref spi_set_slave function.
|
||||
*
|
||||
* Each controller can be connected to a number of GPIO pins, see the datasheet GPIO function selection table for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Opaque type representing an SPI instance.
|
||||
*/
|
||||
typedef struct spi_inst spi_inst_t;
|
||||
|
||||
/** Identifier for the first (SPI 0) hardware SPI instance (for use in SPI functions).
|
||||
*
|
||||
* e.g. spi_init(spi0, 48000)
|
||||
*
|
||||
* \ingroup hardware_spi
|
||||
*/
|
||||
#define spi0 ((spi_inst_t * const)spi0_hw)
|
||||
|
||||
/** Identifier for the second (SPI 1) hardware SPI instance (for use in SPI functions).
|
||||
*
|
||||
* e.g. spi_init(spi1, 48000)
|
||||
*
|
||||
* \ingroup hardware_spi
|
||||
*/
|
||||
#define spi1 ((spi_inst_t * const)spi1_hw)
|
||||
|
||||
typedef enum {
|
||||
SPI_CPHA_0 = 0,
|
||||
SPI_CPHA_1 = 1
|
||||
} spi_cpha_t;
|
||||
|
||||
typedef enum {
|
||||
SPI_CPOL_0 = 0,
|
||||
SPI_CPOL_1 = 1
|
||||
} spi_cpol_t;
|
||||
|
||||
typedef enum {
|
||||
SPI_LSB_FIRST = 0,
|
||||
SPI_MSB_FIRST = 1
|
||||
} spi_order_t;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Setup
|
||||
|
||||
/*! \brief Initialise SPI instances
|
||||
* \ingroup hardware_spi
|
||||
* Puts the SPI into a known state, and enable it. Must be called before other
|
||||
* functions.
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \param baudrate Baudrate required in Hz
|
||||
*
|
||||
* \note There is no guarantee that the baudrate requested will be possible, the nearest will be chosen,
|
||||
* and this function does not return any indication of this. You can use the \ref spi_set_baudrate function
|
||||
* which will return the actual baudrate selected if this is important.
|
||||
*/
|
||||
void spi_init(spi_inst_t *spi, uint baudrate);
|
||||
|
||||
/*! \brief Deinitialise SPI instances
|
||||
* \ingroup hardware_spi
|
||||
* Puts the SPI into a disabled state. Init will need to be called to reenable the device
|
||||
* functions.
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
*/
|
||||
void spi_deinit(spi_inst_t *spi);
|
||||
|
||||
/*! \brief Set SPI baudrate
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* Set SPI frequency as close as possible to baudrate, and return the actual
|
||||
* achieved rate.
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \param baudrate Baudrate required in Hz, should be capable of a bitrate of at least 2Mbps, or higher, depending on system clock settings.
|
||||
* \return The actual baudrate set
|
||||
*/
|
||||
uint spi_set_baudrate(spi_inst_t *spi, uint baudrate);
|
||||
|
||||
/*! \brief Convert I2c instance to hardware instance number
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* \param spi SPI instance
|
||||
* \return Number of SPI, 0 or 1.
|
||||
*/
|
||||
static inline uint spi_get_index(spi_inst_t *spi) {
|
||||
invalid_params_if(SPI, spi != spi0 && spi != spi1);
|
||||
return spi == spi1 ? 1 : 0;
|
||||
}
|
||||
|
||||
static inline spi_hw_t *spi_get_hw(spi_inst_t *spi) {
|
||||
spi_get_index(spi); // check it is a hw spi
|
||||
return (spi_hw_t *)spi;
|
||||
}
|
||||
|
||||
/*! \brief Configure SPI
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* Configure how the SPI serialises and deserialises data on the wire
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \param data_bits Number of data bits per transfer. Valid values 4..16.
|
||||
* \param cpol SSPCLKOUT polarity, applicable to Motorola SPI frame format only.
|
||||
* \param cpha SSPCLKOUT phase, applicable to Motorola SPI frame format only
|
||||
* \param order Must be SPI_MSB_FIRST, no other values supported on the PL022
|
||||
*/
|
||||
static inline void spi_set_format(spi_inst_t *spi, uint data_bits, spi_cpol_t cpol, spi_cpha_t cpha, spi_order_t order) {
|
||||
invalid_params_if(SPI, data_bits < 4 || data_bits > 16);
|
||||
// LSB-first not supported on PL022:
|
||||
invalid_params_if(SPI, order != SPI_MSB_FIRST);
|
||||
invalid_params_if(SPI, cpol != SPI_CPOL_0 && cpol != SPI_CPOL_1);
|
||||
invalid_params_if(SPI, cpha != SPI_CPHA_0 && cpha != SPI_CPHA_1);
|
||||
hw_write_masked(&spi_get_hw(spi)->cr0,
|
||||
(data_bits - 1) << SPI_SSPCR0_DSS_LSB |
|
||||
cpol << SPI_SSPCR0_SPO_LSB |
|
||||
cpha << SPI_SSPCR0_SPH_LSB,
|
||||
SPI_SSPCR0_DSS_BITS |
|
||||
SPI_SSPCR0_SPO_BITS |
|
||||
SPI_SSPCR0_SPH_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Set SPI master/slave
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* Configure the SPI for master- or slave-mode operation. By default,
|
||||
* spi_init() sets master-mode.
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \param slave true to set SPI device as a slave device, false for master.
|
||||
*/
|
||||
static inline void spi_set_slave(spi_inst_t *spi, bool slave) {
|
||||
if (slave)
|
||||
hw_set_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_MS_BITS);
|
||||
else
|
||||
hw_clear_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_MS_BITS);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Generic input/output
|
||||
|
||||
/*! \brief Check whether a write can be done on SPI device
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \return 0 if no space is available to write. Non-zero if a write is possible
|
||||
*
|
||||
* \note Although the controllers each have a 8 deep TX FIFO, the current HW implementation can only return 0 or 1
|
||||
* rather than the space available.
|
||||
*/
|
||||
static inline size_t spi_is_writable(spi_inst_t *spi) {
|
||||
// PL022 doesn't expose levels directly, so return values are only 0 or 1
|
||||
return (spi_get_hw(spi)->sr & SPI_SSPSR_TNF_BITS) >> SPI_SSPSR_TNF_LSB;
|
||||
}
|
||||
|
||||
/*! \brief Check whether a read can be done on SPI device
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \return Non-zero if a read is possible i.e. data is present
|
||||
*
|
||||
* \note Although the controllers each have a 8 deep RX FIFO, the current HW implementation can only return 0 or 1
|
||||
* rather than the data available.
|
||||
*/
|
||||
static inline size_t spi_is_readable(spi_inst_t *spi) {
|
||||
return (spi_get_hw(spi)->sr & SPI_SSPSR_RNE_BITS) >> SPI_SSPSR_RNE_LSB;
|
||||
}
|
||||
|
||||
/*! \brief Write/Read to/from an SPI device
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* Write \p len bytes from \p src to SPI. Simultaneously read \p len bytes from SPI to \p dst.
|
||||
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \param src Buffer of data to write
|
||||
* \param dst Buffer for read data
|
||||
* \param len Length of BOTH buffers
|
||||
* \return Number of bytes written/read
|
||||
*/
|
||||
int spi_write_read_blocking(spi_inst_t *spi, const uint8_t *src, uint8_t *dst, size_t len);
|
||||
|
||||
/*! \brief Write to an SPI device, blocking
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* Write \p len bytes from \p src to SPI, and discard any data received back
|
||||
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \param src Buffer of data to write
|
||||
* \param len Length of \p src
|
||||
* \return Number of bytes written/read
|
||||
*/
|
||||
int spi_write_blocking(spi_inst_t *spi, const uint8_t *src, size_t len);
|
||||
|
||||
/*! \brief Read from an SPI device
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* Read \p len bytes from SPI to \p dst.
|
||||
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
|
||||
* \p repeated_tx_data is output repeatedly on TX as data is read in from RX.
|
||||
* Generally this can be 0, but some devices require a specific value here,
|
||||
* e.g. SD cards expect 0xff
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \param repeated_tx_data Buffer of data to write
|
||||
* \param dst Buffer for read data
|
||||
* \param len Length of buffer \p dst
|
||||
* \return Number of bytes written/read
|
||||
*/
|
||||
int spi_read_blocking(spi_inst_t *spi, uint8_t repeated_tx_data, uint8_t *dst, size_t len);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// SPI-specific operations and aliases
|
||||
|
||||
// FIXME need some instance-private data for select() and deselect() if we are going that route
|
||||
|
||||
/*! \brief Write/Read half words to/from an SPI device
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* Write \p len halfwords from \p src to SPI. Simultaneously read \p len halfwords from SPI to \p dst.
|
||||
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \param src Buffer of data to write
|
||||
* \param dst Buffer for read data
|
||||
* \param len Length of BOTH buffers in halfwords
|
||||
* \return Number of bytes written/read
|
||||
*/
|
||||
int spi_write16_read16_blocking(spi_inst_t *spi, const uint16_t *src, uint16_t *dst, size_t len);
|
||||
|
||||
/*! \brief Write to an SPI device
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* Write \p len halfwords from \p src to SPI. Discard any data received back.
|
||||
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \param src Buffer of data to write
|
||||
* \param len Length of buffers
|
||||
* \return Number of bytes written/read
|
||||
*/
|
||||
int spi_write16_blocking(spi_inst_t *spi, const uint16_t *src, size_t len);
|
||||
|
||||
/*! \brief Read from an SPI device
|
||||
* \ingroup hardware_spi
|
||||
*
|
||||
* Read \p len halfwords from SPI to \p dst.
|
||||
* Blocks until all data is transferred. No timeout, as SPI hardware always transfers at a known data rate.
|
||||
* \p repeated_tx_data is output repeatedly on TX as data is read in from RX.
|
||||
* Generally this can be 0, but some devices require a specific value here,
|
||||
* e.g. SD cards expect 0xff
|
||||
*
|
||||
* \param spi SPI instance specifier, either \ref spi0 or \ref spi1
|
||||
* \param repeated_tx_data Buffer of data to write
|
||||
* \param dst Buffer for read data
|
||||
* \param len Length of buffer \p dst in halfwords
|
||||
* \return Number of bytes written/read
|
||||
*/
|
||||
int spi_read16_blocking(spi_inst_t *spi, uint16_t repeated_tx_data, uint16_t *dst, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
199
src/rp2_common/hardware_spi/spi.c
Normal file
199
src/rp2_common/hardware_spi/spi.c
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/resets.h"
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/spi.h"
|
||||
|
||||
static inline void spi_reset(spi_inst_t *spi) {
|
||||
invalid_params_if(SPI, spi != spi0 && spi != spi1);
|
||||
reset_block(spi == spi0 ? RESETS_RESET_SPI0_BITS : RESETS_RESET_SPI1_BITS);
|
||||
}
|
||||
|
||||
static inline void spi_unreset(spi_inst_t *spi) {
|
||||
invalid_params_if(SPI, spi != spi0 && spi != spi1);
|
||||
unreset_block_wait(spi == spi0 ? RESETS_RESET_SPI0_BITS : RESETS_RESET_SPI1_BITS);
|
||||
}
|
||||
|
||||
void spi_init(spi_inst_t *spi, uint baudrate) {
|
||||
spi_reset(spi);
|
||||
spi_unreset(spi);
|
||||
|
||||
(void) spi_set_baudrate(spi, baudrate);
|
||||
spi_set_format(spi, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
|
||||
// Always enable DREQ signals -- harmless if DMA is not listening
|
||||
hw_set_bits(&spi_get_hw(spi)->dmacr, SPI_SSPDMACR_TXDMAE_BITS | SPI_SSPDMACR_RXDMAE_BITS);
|
||||
spi_set_format(spi, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
|
||||
|
||||
// Finally enable the SPI
|
||||
hw_set_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_SSE_BITS);
|
||||
}
|
||||
|
||||
void spi_deinit(spi_inst_t *spi) {
|
||||
hw_clear_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_SSE_BITS);
|
||||
hw_clear_bits(&spi_get_hw(spi)->dmacr, SPI_SSPDMACR_TXDMAE_BITS | SPI_SSPDMACR_RXDMAE_BITS);
|
||||
spi_reset(spi);
|
||||
}
|
||||
|
||||
uint spi_set_baudrate(spi_inst_t *spi, uint baudrate) {
|
||||
uint freq_in = clock_get_hz(clk_peri);
|
||||
uint prescale, postdiv;
|
||||
invalid_params_if(SPI, baudrate > freq_in);
|
||||
|
||||
// Find smallest prescale value which puts output frequency in range of
|
||||
// post-divide. Prescale is an even number from 2 to 254 inclusive.
|
||||
for (prescale = 2; prescale <= 254; prescale += 2) {
|
||||
if (freq_in < (prescale + 2) * 256 * (uint64_t) baudrate)
|
||||
break;
|
||||
}
|
||||
invalid_params_if(SPI, prescale > 254); // Frequency too low
|
||||
|
||||
// Find largest post-divide which makes output <= baudrate. Post-divide is
|
||||
// an integer in the range 1 to 256 inclusive.
|
||||
for (postdiv = 256; postdiv > 1; --postdiv) {
|
||||
if (freq_in / (prescale * (postdiv - 1)) > baudrate)
|
||||
break;
|
||||
}
|
||||
|
||||
spi_get_hw(spi)->cpsr = prescale;
|
||||
hw_write_masked(&spi_get_hw(spi)->cr0, (postdiv - 1) << SPI_SSPCR0_SCR_LSB, SPI_SSPCR0_SCR_BITS);
|
||||
|
||||
// Return the frequency we were able to achieve
|
||||
return freq_in / (prescale * postdiv);
|
||||
}
|
||||
|
||||
// Write len bytes from src to SPI. Simultaneously read len bytes from SPI to dst.
|
||||
// Note this function is guaranteed to exit in a known amount of time (bits sent * time per bit)
|
||||
int __not_in_flash_func(spi_write_read_blocking)(spi_inst_t *spi, const uint8_t *src, uint8_t *dst, size_t len) {
|
||||
// Never have more transfers in flight than will fit into the RX FIFO,
|
||||
// else FIFO will overflow if this code is heavily interrupted.
|
||||
const size_t fifo_depth = 8;
|
||||
size_t rx_remaining = len, tx_remaining = len;
|
||||
|
||||
while (rx_remaining || tx_remaining) {
|
||||
if (tx_remaining && spi_is_writable(spi) && rx_remaining - tx_remaining < fifo_depth) {
|
||||
spi_get_hw(spi)->dr = (uint32_t) *src++;
|
||||
--tx_remaining;
|
||||
}
|
||||
if (rx_remaining && spi_is_readable(spi)) {
|
||||
*dst++ = (uint8_t) spi_get_hw(spi)->dr;
|
||||
--rx_remaining;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// Write len bytes directly from src to the SPI, and discard any data received back
|
||||
int __not_in_flash_func(spi_write_blocking)(spi_inst_t *spi, const uint8_t *src, size_t len) {
|
||||
// Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX
|
||||
// is full, PL022 inhibits RX pushes, and sets a sticky flag on
|
||||
// push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set.
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
while (!spi_is_writable(spi))
|
||||
tight_loop_contents();
|
||||
spi_get_hw(spi)->dr = (uint32_t)src[i];
|
||||
}
|
||||
// Drain RX FIFO, then wait for shifting to finish (which may be *after*
|
||||
// TX FIFO drains), then drain RX FIFO again
|
||||
while (spi_is_readable(spi))
|
||||
(void)spi_get_hw(spi)->dr;
|
||||
while (spi_get_hw(spi)->sr & SPI_SSPSR_BSY_BITS)
|
||||
tight_loop_contents();
|
||||
while (spi_is_readable(spi))
|
||||
(void)spi_get_hw(spi)->dr;
|
||||
|
||||
// Don't leave overrun flag set
|
||||
spi_get_hw(spi)->icr = SPI_SSPICR_RORIC_BITS;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// Read len bytes directly from the SPI to dst.
|
||||
// repeated_tx_data is output repeatedly on SO as data is read in from SI.
|
||||
// Generally this can be 0, but some devices require a specific value here,
|
||||
// e.g. SD cards expect 0xff
|
||||
int __not_in_flash_func(spi_read_blocking)(spi_inst_t *spi, uint8_t repeated_tx_data, uint8_t *dst, size_t len) {
|
||||
const size_t fifo_depth = 8;
|
||||
size_t rx_remaining = len, tx_remaining = len;
|
||||
|
||||
while (rx_remaining || tx_remaining) {
|
||||
if (tx_remaining && spi_is_writable(spi) && rx_remaining - tx_remaining < fifo_depth) {
|
||||
spi_get_hw(spi)->dr = (uint32_t) repeated_tx_data;
|
||||
--tx_remaining;
|
||||
}
|
||||
if (rx_remaining && spi_is_readable(spi)) {
|
||||
*dst++ = (uint8_t) spi_get_hw(spi)->dr;
|
||||
--rx_remaining;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// Write len halfwords from src to SPI. Simultaneously read len halfwords from SPI to dst.
|
||||
int __not_in_flash_func(spi_write16_read16_blocking)(spi_inst_t *spi, const uint16_t *src, uint16_t *dst, size_t len) {
|
||||
// Never have more transfers in flight than will fit into the RX FIFO,
|
||||
// else FIFO will overflow if this code is heavily interrupted.
|
||||
const size_t fifo_depth = 8;
|
||||
size_t rx_remaining = len, tx_remaining = len;
|
||||
|
||||
while (rx_remaining || tx_remaining) {
|
||||
if (tx_remaining && spi_is_writable(spi) && rx_remaining - tx_remaining < fifo_depth) {
|
||||
spi_get_hw(spi)->dr = (uint32_t) *src++;
|
||||
--tx_remaining;
|
||||
}
|
||||
if (rx_remaining && spi_is_readable(spi)) {
|
||||
*dst++ = (uint16_t) spi_get_hw(spi)->dr;
|
||||
--rx_remaining;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// Write len bytes directly from src to the SPI, and discard any data received back
|
||||
int __not_in_flash_func(spi_write16_blocking)(spi_inst_t *spi, const uint16_t *src, size_t len) {
|
||||
// Deliberately overflow FIFO, then clean up afterward, to minimise amount
|
||||
// of APB polling required per halfword
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
while (!spi_is_writable(spi))
|
||||
tight_loop_contents();
|
||||
spi_get_hw(spi)->dr = (uint32_t)src[i];
|
||||
}
|
||||
|
||||
while (spi_is_readable(spi))
|
||||
(void)spi_get_hw(spi)->dr;
|
||||
while (spi_get_hw(spi)->sr & SPI_SSPSR_BSY_BITS)
|
||||
tight_loop_contents();
|
||||
while (spi_is_readable(spi))
|
||||
(void)spi_get_hw(spi)->dr;
|
||||
|
||||
// Don't leave overrun flag set
|
||||
spi_get_hw(spi)->icr = SPI_SSPICR_RORIC_BITS;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// Read len halfwords directly from the SPI to dst.
|
||||
// repeated_tx_data is output repeatedly on SO as data is read in from SI.
|
||||
int __not_in_flash_func(spi_read16_blocking)(spi_inst_t *spi, uint16_t repeated_tx_data, uint16_t *dst, size_t len) {
|
||||
const size_t fifo_depth = 8;
|
||||
size_t rx_remaining = len, tx_remaining = len;
|
||||
|
||||
while (rx_remaining || tx_remaining) {
|
||||
if (tx_remaining && spi_is_writable(spi) && rx_remaining - tx_remaining < fifo_depth) {
|
||||
spi_get_hw(spi)->dr = (uint32_t) repeated_tx_data;
|
||||
--tx_remaining;
|
||||
}
|
||||
if (rx_remaining && spi_is_readable(spi)) {
|
||||
*dst++ = (uint16_t) spi_get_hw(spi)->dr;
|
||||
--rx_remaining;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
1
src/rp2_common/hardware_sync/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_sync/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(sync)
|
336
src/rp2_common/hardware_sync/include/hardware/sync.h
Normal file
336
src/rp2_common/hardware_sync/include/hardware/sync.h
Normal file
@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_SYNC_H
|
||||
#define _HARDWARE_SYNC_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/address_mapped.h"
|
||||
#include "hardware/regs/sio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/** \file hardware/sync.h
|
||||
* \defgroup hardware_sync hardware_sync
|
||||
*
|
||||
* Low level hardware spin-lock, barrier and processor event API
|
||||
*
|
||||
* Functions for synchronisation between core's, HW, etc
|
||||
*
|
||||
* The RP2040 provides 32 hardware spin locks, which can be used to manage mutually-exclusive access to shared software
|
||||
* resources.
|
||||
*
|
||||
* \note spin locks 0-15 are currently reserved for fixed uses by the SDK - i.e. if you use them other
|
||||
* functionality may break or not function optimally
|
||||
*/
|
||||
|
||||
/** \brief A spin lock identifier
|
||||
* \ingroup hardware_sync
|
||||
*/
|
||||
typedef volatile uint32_t spin_lock_t;
|
||||
|
||||
// PICO_CONFIG: PICO_SPINLOCK_ID_IRQ, Spinlock ID for IRQ protection, min=0, max=31, default=9, group=hardware_sync
|
||||
#ifndef PICO_SPINLOCK_ID_IRQ
|
||||
#define PICO_SPINLOCK_ID_IRQ 9
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_SPINLOCK_ID_TIMER, Spinlock ID for Timer protection, min=0, max=31, default=10, group=hardware_sync
|
||||
#ifndef PICO_SPINLOCK_ID_TIMER
|
||||
#define PICO_SPINLOCK_ID_TIMER 10
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_SPINLOCK_ID_HARDWARE_CLAIM, Spinlock ID for Hardware claim protection, min=0, max=31, default=11, group=hardware_sync
|
||||
#ifndef PICO_SPINLOCK_ID_HARDWARE_CLAIM
|
||||
#define PICO_SPINLOCK_ID_HARDWARE_CLAIM 11
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_SPINLOCK_ID_STRIPED_FIRST, Spinlock ID for striped first, min=16, max=31, default=16, group=hardware_sync
|
||||
#ifndef PICO_SPINLOCK_ID_STRIPED_FIRST
|
||||
#define PICO_SPINLOCK_ID_STRIPED_FIRST 16
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_SPINLOCK_ID_STRIPED_LAST, Spinlock ID for striped last, min=16, max=31, default=23, group=hardware_sync
|
||||
#ifndef PICO_SPINLOCK_ID_STRIPED_LAST
|
||||
#define PICO_SPINLOCK_ID_STRIPED_LAST 23
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_SPINLOCK_ID_CLAIM_FREE_FIRST, Spinlock ID for claim free first, min=16, max=31, default=24, group=hardware_sync
|
||||
#ifndef PICO_SPINLOCK_ID_CLAIM_FREE_FIRST
|
||||
#define PICO_SPINLOCK_ID_CLAIM_FREE_FIRST 24
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_SPINLOCK_ID_CLAIM_FREE_END, Spinlock ID for claim free end, min=16, max=31, default=31, group=hardware_sync
|
||||
#ifndef PICO_SPINLOCK_ID_CLAIM_FREE_END
|
||||
#define PICO_SPINLOCK_ID_CLAIM_FREE_END 31
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_SYNC, Enable/disable assertions in the HW sync module, type=bool, default=0, group=hardware_sync
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_SYNC
|
||||
#define PARAM_ASSERTIONS_ENABLED_SYNC 0
|
||||
#endif
|
||||
|
||||
|
||||
/*! \brief Insert a SEV instruction in to the code path.
|
||||
* \ingroup hardware_sync
|
||||
|
||||
* The SEV (send event) instruction sends an event to both cores.
|
||||
*/
|
||||
inline static void __sev() {
|
||||
__asm volatile ("sev");
|
||||
}
|
||||
|
||||
/*! \brief Insert a WFE instruction in to the code path.
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* The WFE (wait for event) instruction waits until one of a number of
|
||||
* events occurs, including events signalled by the SEV instruction on either core.
|
||||
*/
|
||||
inline static void __wfe() {
|
||||
__asm volatile ("wfe");
|
||||
}
|
||||
|
||||
/*! \brief Insert a WFI instruction in to the code path.
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* The WFI (wait for interrupt) instruction waits for a interrupt to wake up the core.
|
||||
*/
|
||||
inline static void __wfi() {
|
||||
__asm volatile ("wfi");
|
||||
}
|
||||
|
||||
/*! \brief Insert a DMB instruction in to the code path.
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* The DMB (data memory barrier) acts as a memory barrier, all memory accesses prior to this
|
||||
* instruction will be observed before any explicit access after the instruction.
|
||||
*/
|
||||
inline static void __dmb() {
|
||||
__asm volatile ("dmb");
|
||||
}
|
||||
|
||||
/*! \brief Insert a ISB instruction in to the code path.
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* ISB acts as an instruction synchronization barrier. It flushes the pipeline of the processor,
|
||||
* so that all instructions following the ISB are fetched from cache or memory again, after
|
||||
* the ISB instruction has been completed.
|
||||
*/
|
||||
inline static void __isb() {
|
||||
__asm volatile ("isb");
|
||||
}
|
||||
|
||||
/*! \brief Acquire a memory fence
|
||||
* \ingroup hardware_sync
|
||||
*/
|
||||
inline static void __mem_fence_acquire() {
|
||||
// the original code below makes it hard for us to be included from C++ via a header
|
||||
// which itself is in an extern "C", so just use __dmb instead, which is what
|
||||
// is required on Cortex M0+
|
||||
__dmb();
|
||||
//#ifndef __cplusplus
|
||||
// atomic_thread_fence(memory_order_acquire);
|
||||
//#else
|
||||
// std::atomic_thread_fence(std::memory_order_acquire);
|
||||
//#endif
|
||||
}
|
||||
|
||||
/*! \brief Release a memory fence
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
*/
|
||||
inline static void __mem_fence_release() {
|
||||
// the original code below makes it hard for us to be included from C++ via a header
|
||||
// which itself is in an extern "C", so just use __dmb instead, which is what
|
||||
// is required on Cortex M0+
|
||||
__dmb();
|
||||
//#ifndef __cplusplus
|
||||
// atomic_thread_fence(memory_order_release);
|
||||
//#else
|
||||
// std::atomic_thread_fence(std::memory_order_release);
|
||||
//#endif
|
||||
}
|
||||
|
||||
/*! \brief Save and disable interrupts
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* \return The prior interrupt enable status for restoration later via restore_interrupts()
|
||||
*/
|
||||
inline static uint32_t save_and_disable_interrupts() {
|
||||
uint32_t status;
|
||||
__asm volatile ("mrs %0, PRIMASK" : "=r" (status)::);
|
||||
__asm volatile ("cpsid i");
|
||||
return status;
|
||||
}
|
||||
|
||||
/*! \brief Restore interrupts to a specified state
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* \param status Previous interrupt status from save_and_disable_interrupts()
|
||||
*/
|
||||
inline static void restore_interrupts(uint32_t status) {
|
||||
__asm volatile ("msr PRIMASK,%0"::"r" (status) : );
|
||||
}
|
||||
|
||||
/*! \brief Get HW Spinlock instance from number
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* \param lock_num Spinlock ID
|
||||
* \return The spinlock instance
|
||||
*/
|
||||
inline static spin_lock_t *spin_lock_instance(uint lock_num) {
|
||||
return (spin_lock_t *) (SIO_BASE + SIO_SPINLOCK0_OFFSET + lock_num * 4);
|
||||
}
|
||||
|
||||
/*! \brief Get HW Spinlock number from instance
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* \param lock The Spinlock instance
|
||||
* \return The Spinlock ID
|
||||
*/
|
||||
inline static uint spin_lock_get_num(spin_lock_t *lock) {
|
||||
return lock - (spin_lock_t *) (SIO_BASE + SIO_SPINLOCK0_OFFSET);
|
||||
}
|
||||
|
||||
/*! \brief Acquire a spin lock without disabling interrupts (hence unsafe)
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* \param lock Spinlock instance
|
||||
*/
|
||||
inline static void spin_lock_unsafe_blocking(spin_lock_t *lock) {
|
||||
// Note we don't do a wfe or anything, because by convention these spin_locks are VERY SHORT LIVED and NEVER BLOCK and run
|
||||
// with INTERRUPTS disabled (to ensure that)... therefore nothing on our core could be blocking us, so we just need to wait on another core
|
||||
// anyway which should be finished soon
|
||||
while (__builtin_expect(!*lock, 0));
|
||||
__mem_fence_acquire();
|
||||
}
|
||||
|
||||
/*! \brief Release a spin lock without re-enabling interrupts
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* \param lock Spinlock instance
|
||||
*/
|
||||
inline static void spin_unlock_unsafe(spin_lock_t *lock) {
|
||||
__mem_fence_release();
|
||||
*lock = 0;
|
||||
}
|
||||
|
||||
/*! \brief Acquire a spin lock safely
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* This function will disable interrupts prior to acquiring the spinlock
|
||||
*
|
||||
* \param lock Spinlock instance
|
||||
* \return interrupt status to be used when unlocking, to restore to original state
|
||||
*/
|
||||
inline static uint32_t spin_lock_blocking(spin_lock_t *lock) {
|
||||
uint32_t save = save_and_disable_interrupts();
|
||||
spin_lock_unsafe_blocking(lock);
|
||||
return save;
|
||||
}
|
||||
|
||||
/*! \brief Check to see if a spinlock is currently acquired elsewhere.
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* \param lock Spinlock instance
|
||||
*/
|
||||
inline static bool is_spin_locked(const spin_lock_t *lock) {
|
||||
check_hw_size(spin_lock_t, 4);
|
||||
uint32_t lock_num = lock - spin_lock_instance(0);
|
||||
return 0 != (*(io_ro_32 *) (SIO_BASE + SIO_SPINLOCK_ST_OFFSET) & (1u << lock_num));
|
||||
}
|
||||
|
||||
/*! \brief Release a spin lock safely
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* This function will re-enable interrupts according to the parameters.
|
||||
*
|
||||
* \param lock Spinlock instance
|
||||
* \param saved_irq Return value from the \ref spin_lock_blocking() function.
|
||||
* \return interrupt status to be used when unlocking, to restore to original state
|
||||
*
|
||||
* \sa spin_lock_blocking()
|
||||
*/
|
||||
inline static void spin_unlock(spin_lock_t *lock, uint32_t saved_irq) {
|
||||
spin_unlock_unsafe(lock);
|
||||
restore_interrupts(saved_irq);
|
||||
}
|
||||
|
||||
/*! \brief Get the current core number
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* \return The core number the call was made from
|
||||
*/
|
||||
static inline uint get_core_num() {
|
||||
return (*(uint32_t *) (SIO_BASE + SIO_CPUID_OFFSET));
|
||||
}
|
||||
|
||||
/*! \brief Initialise a spin lock
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* The spin lock is initially unlocked
|
||||
*
|
||||
* \param lock_num The spin lock number
|
||||
* \return The spin lock instance
|
||||
*/
|
||||
spin_lock_t *spin_lock_init(uint lock_num);
|
||||
|
||||
/*! \brief Release all spin locks
|
||||
* \ingroup hardware_sync
|
||||
*/
|
||||
void spin_locks_reset(void);
|
||||
|
||||
// this number is not claimed
|
||||
uint next_striped_spin_lock_num();
|
||||
|
||||
/*! \brief Mark a spin lock as used
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* Method for cooperative claiming of hardware. Will cause a panic if the spin lock
|
||||
* is already claimed. Use of this method by libraries detects accidental
|
||||
* configurations that would fail in unpredictable ways.
|
||||
*
|
||||
* \param lock_num the spin lock number
|
||||
*/
|
||||
void spin_lock_claim(uint lock_num);
|
||||
|
||||
/*! \brief Mark multiple spin locks as used
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* Method for cooperative claiming of hardware. Will cause a panic if any of the spin locks
|
||||
* are already claimed. Use of this method by libraries detects accidental
|
||||
* configurations that would fail in unpredictable ways.
|
||||
*
|
||||
* \param lock_num_mask Bitfield of all required spin locks to claim (bit 0 == spin lock 0, bit 1 == spin lock 1 etc)
|
||||
*/
|
||||
void spin_lock_claim_mask(uint32_t lock_num_mask);
|
||||
|
||||
/*! \brief Mark a spin lock as no longer used
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* Method for cooperative claiming of hardware.
|
||||
*
|
||||
* \param lock_num the spin lock number to release
|
||||
*/
|
||||
void spin_lock_unclaim(uint lock_num);
|
||||
|
||||
/*! \brief Claim a free spin lock
|
||||
* \ingroup hardware_sync
|
||||
*
|
||||
* \param required if true the function will panic if none are available
|
||||
* \return the spin lock number or -1 if required was false, and none were free
|
||||
*/
|
||||
int spin_lock_claim_unused(bool required);
|
||||
|
||||
#define remove_volatile_cast(t, x) ({__mem_fence_acquire(); (t)(x); })
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
58
src/rp2_common/hardware_sync/sync.c
Normal file
58
src/rp2_common/hardware_sync/sync.c
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/sync.h"
|
||||
#include "hardware/claim.h"
|
||||
|
||||
static_assert(PICO_SPINLOCK_ID_STRIPED_LAST >= PICO_SPINLOCK_ID_STRIPED_FIRST, "");
|
||||
static uint8_t striped_spin_lock_num = PICO_SPINLOCK_ID_STRIPED_FIRST;
|
||||
static uint32_t claimed;
|
||||
|
||||
static void check_lock_num(uint __unused lock_num) {
|
||||
invalid_params_if(SYNC, lock_num >= 32);
|
||||
}
|
||||
|
||||
void spin_locks_reset(void) {
|
||||
for (uint i = 0; i < NUM_SPIN_LOCKS; i++) {
|
||||
spin_unlock_unsafe(spin_lock_instance(i));
|
||||
}
|
||||
}
|
||||
|
||||
spin_lock_t *spin_lock_init(uint lock_num) {
|
||||
assert(lock_num >= 0 && lock_num < NUM_SPIN_LOCKS);
|
||||
spin_lock_t *lock = spin_lock_instance(lock_num);
|
||||
spin_unlock_unsafe(lock);
|
||||
return lock;
|
||||
}
|
||||
|
||||
uint next_striped_spin_lock_num() {
|
||||
uint rc = striped_spin_lock_num++;
|
||||
if (striped_spin_lock_num > PICO_SPINLOCK_ID_STRIPED_LAST) {
|
||||
striped_spin_lock_num = PICO_SPINLOCK_ID_STRIPED_FIRST;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
void spin_lock_claim(uint lock_num) {
|
||||
check_lock_num(lock_num);
|
||||
hw_claim_or_assert((uint8_t *) &claimed, lock_num, "Spinlock %d is already claimed");
|
||||
}
|
||||
|
||||
void spin_lock_claim_mask(uint32_t mask) {
|
||||
for(uint i = 0; mask; i++, mask >>= 1u) {
|
||||
if (mask & 1u) spin_lock_claim(i);
|
||||
}
|
||||
}
|
||||
|
||||
void spin_lock_unclaim(uint lock_num) {
|
||||
check_lock_num(lock_num);
|
||||
hw_claim_clear((uint8_t *) &claimed, lock_num);
|
||||
}
|
||||
|
||||
int spin_lock_claim_unused(bool required) {
|
||||
return hw_claim_unused_from_range((uint8_t*)&claimed, required, PICO_SPINLOCK_ID_CLAIM_FREE_FIRST, PICO_SPINLOCK_ID_CLAIM_FREE_END, "No spinlocks are available");
|
||||
}
|
||||
|
2
src/rp2_common/hardware_timer/CMakeLists.txt
Normal file
2
src/rp2_common/hardware_timer/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
pico_simple_hardware_target(timer)
|
||||
target_link_libraries(hardware_timer INTERFACE hardware_claim)
|
180
src/rp2_common/hardware_timer/include/hardware/timer.h
Normal file
180
src/rp2_common/hardware_timer/include/hardware/timer.h
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_TIMER_H
|
||||
#define _HARDWARE_TIMER_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/timer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file hardware/timer.h
|
||||
* \defgroup hardware_timer hardware_timer
|
||||
*
|
||||
* Low-level hardware timer API
|
||||
*
|
||||
* This API provides medium level access to the timer HW.
|
||||
* See also \ref pico_time which provides higher levels functionality using the hardware timer.
|
||||
*
|
||||
* The timer peripheral on RP2040 supports the following features:
|
||||
* - single 64-bit counter, incrementing once per microsecond
|
||||
* - Latching two-stage read of counter, for race-free read over 32 bit bus
|
||||
* - Four alarms: match on the lower 32 bits of counter, IRQ on match.
|
||||
*
|
||||
* By default the timer uses a one microsecond reference that is generated in the Watchdog (see Section 4.8.2) which is derived
|
||||
* from the clk_ref.
|
||||
*
|
||||
* The timer has 4 alarms, and can output a separate interrupt for each alarm. The alarms match on the lower 32 bits of the 64
|
||||
* bit counter which means they can be fired a maximum of 2^32 microseconds into the future. This is equivalent to:
|
||||
* - 2^32 ÷ 10^6: ~4295 seconds
|
||||
* - 4295 ÷ 60: ~72 minutes
|
||||
*
|
||||
* The timer is expected to be used for short sleeps, if you want a longer alarm see the \ref hardware_rtc functions.
|
||||
*
|
||||
* \subsection timer_example Example
|
||||
* \addtogroup hardware_timer
|
||||
*
|
||||
* \include hello_timer.c
|
||||
*
|
||||
* \see pico_time
|
||||
*/
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_TIMER, Enable/disable assertions in the timer module, type=bool, default=0, group=hardware_timer
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_TIMER
|
||||
#define PARAM_ASSERTIONS_ENABLED_TIMER 0
|
||||
#endif
|
||||
|
||||
static inline void check_hardware_alarm_num_param(uint alarm_num) {
|
||||
invalid_params_if(TIMER, alarm_num >= NUM_TIMERS);
|
||||
}
|
||||
|
||||
/*! \brief Return a 32 bit timestamp value in microseconds
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* Returns the low 32 bits of the hardware timer.
|
||||
* \note This value wraps roughly every 1 hour 11 minutes and 35 seconds.
|
||||
*
|
||||
* \return the 32 bit timestamp
|
||||
*/
|
||||
static inline uint32_t time_us_32() {
|
||||
return timer_hw->timerawl;
|
||||
}
|
||||
|
||||
/*! \brief Return the current 64 bit timestamp value in microseconds
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* Returns the full 64 bits of the hardware timer. The \ref pico_time and other functions rely on the fact that this
|
||||
* value monotonically increases from power up. As such it is expected that this value counts upwards and never wraps
|
||||
* (we apologize for introducing a potential year 5851444 bug).
|
||||
*
|
||||
* \return the 64 bit timestamp
|
||||
*/
|
||||
uint64_t time_us_64();
|
||||
|
||||
/*! \brief Busy wait wasting cycles for the given (32 bit) number of microseconds
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param delay_us delay amount
|
||||
*/
|
||||
void busy_wait_us_32(uint32_t delay_us);
|
||||
|
||||
/*! \brief Busy wait wasting cycles for the given (64 bit) number of microseconds
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param delay_us delay amount
|
||||
*/
|
||||
void busy_wait_us(uint64_t delay_us);
|
||||
|
||||
/*! \brief Busy wait wasting cycles until after the specified timestamp
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param t Absolute time to wait until
|
||||
*/
|
||||
void busy_wait_until(absolute_time_t t);
|
||||
|
||||
/*! \brief Check if the specified timestamp has been reached
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param t Absolute time to compare against current time
|
||||
* \return true if it is now after the specified timestamp
|
||||
*/
|
||||
static inline bool time_reached(absolute_time_t t) {
|
||||
uint64_t target = to_us_since_boot(t);
|
||||
uint32_t hi_target = target >> 32u;
|
||||
uint32_t hi = timer_hw->timerawh;
|
||||
return (hi >= hi_target && (timer_hw->timerawl >= (uint32_t) target || hi != hi_target));
|
||||
}
|
||||
|
||||
/*! Callback function type for hardware alarms
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param alarm_num the hardware alarm number
|
||||
* \sa hardware_alarm_set_callback
|
||||
*/
|
||||
typedef void (*hardware_alarm_callback_t)(uint alarm_num);
|
||||
|
||||
/*! \brief cooperatively claim the use of this hardware alarm_num
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* This method hard asserts if the hardware alarm is currently claimed.
|
||||
*
|
||||
* \param alarm_num the hardware alarm to claim
|
||||
* \sa hardware_claiming
|
||||
*/
|
||||
void hardware_alarm_claim(uint alarm_num);
|
||||
|
||||
/*! \brief cooperatively release the claim on use of this hardware alarm_num
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param alarm_num the hardware alarm to unclaim
|
||||
* \sa hardware_claiming
|
||||
*/
|
||||
void hardware_alarm_unclaim(uint alarm_num);
|
||||
|
||||
/*! \brief Enable/Disable a callback for a hardware timer on this core
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* This method enables/disables the alarm IRQ for the specified hardware alarm on the
|
||||
* calling core, and set the specified callback to be associated with that alarm.
|
||||
*
|
||||
* This callback will be used for the timeout set via hardware_alarm_set_target
|
||||
*
|
||||
* \note This will install the handler on the current core if the IRQ handler isn't already set.
|
||||
* Therefore the user has the opportunity to call this up from the core of their choice
|
||||
*
|
||||
* \param alarm_num the hardware alarm number
|
||||
* \param callback the callback to install, or NULL to unset
|
||||
*
|
||||
* \sa hardware_alarm_set_target
|
||||
*/
|
||||
void hardware_alarm_set_callback(uint alarm_num, hardware_alarm_callback_t callback);
|
||||
|
||||
/**
|
||||
* \brief Set the current target for the specified hardware alarm
|
||||
*
|
||||
* This will replace any existing target
|
||||
*
|
||||
* @param alarm_num the hardware alarm number
|
||||
* @param t the target timestamp
|
||||
* @return true if the target was "missed"; i.e. it was in the past, or occurred before a future hardware timeout could be set
|
||||
*/
|
||||
bool hardware_alarm_set_target(uint alarm_num, absolute_time_t t);
|
||||
|
||||
/**
|
||||
* \brief Cancel an existing target (if any) for a given hardware_alarm
|
||||
*
|
||||
* @param alarm_num
|
||||
*/
|
||||
|
||||
void hardware_alarm_cancel(uint alarm_num);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
207
src/rp2_common/hardware_timer/timer.c
Normal file
207
src/rp2_common/hardware_timer/timer.c
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/timer.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/sync.h"
|
||||
#include "hardware/claim.h"
|
||||
|
||||
check_hw_layout(timer_hw_t, ints, TIMER_INTS_OFFSET);
|
||||
|
||||
static hardware_alarm_callback_t alarm_callbacks[NUM_TIMERS];
|
||||
static uint32_t target_hi[NUM_TIMERS];
|
||||
static uint8_t timer_callbacks_pending;
|
||||
|
||||
static_assert(NUM_TIMERS <= 4, "");
|
||||
static uint8_t claimed;
|
||||
|
||||
void hardware_alarm_claim(uint alarm_num) {
|
||||
check_hardware_alarm_num_param(alarm_num);
|
||||
hw_claim_or_assert(&claimed, alarm_num, "Hardware alarm %d already claimed");
|
||||
}
|
||||
|
||||
void hardware_alarm_unclaim(uint alarm_num) {
|
||||
check_hardware_alarm_num_param(alarm_num);
|
||||
hw_claim_clear(&claimed, alarm_num);
|
||||
}
|
||||
|
||||
/// tag::time_us_64[]
|
||||
uint64_t time_us_64() {
|
||||
// Need to make sure that the upper 32 bits of the timer
|
||||
// don't change, so read that first
|
||||
uint32_t hi = timer_hw->timerawh;
|
||||
uint32_t lo;
|
||||
do {
|
||||
// Read the lower 32 bits
|
||||
lo = timer_hw->timerawl;
|
||||
// Now read the upper 32 bits again and
|
||||
// check that it hasn't incremented. If it has loop around
|
||||
// and read the lower 32 bits again to get an accurate value
|
||||
uint32_t next_hi = timer_hw->timerawh;
|
||||
if (hi == next_hi) break;
|
||||
hi = next_hi;
|
||||
} while (true);
|
||||
return ((uint64_t) hi << 32u) | lo;
|
||||
}
|
||||
/// end::time_us_64[]
|
||||
|
||||
/// \tag::busy_wait[]
|
||||
void busy_wait_us_32(uint32_t delay_us) {
|
||||
if (0 <= (int32_t)delay_us) {
|
||||
// we only allow 31 bits, otherwise we could have a race in the loop below with
|
||||
// values very close to 2^32
|
||||
uint32_t start = timer_hw->timerawl;
|
||||
while (timer_hw->timerawl - start < delay_us) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
} else {
|
||||
busy_wait_us(delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
void busy_wait_us(uint64_t delay_us) {
|
||||
uint64_t base = time_us_64();
|
||||
uint64_t target = base + delay_us;
|
||||
if (target < base) {
|
||||
target = (uint64_t)-1;
|
||||
}
|
||||
absolute_time_t t;
|
||||
update_us_since_boot(&t, target);
|
||||
busy_wait_until(t);
|
||||
}
|
||||
|
||||
void busy_wait_until(absolute_time_t t) {
|
||||
uint64_t target = to_us_since_boot(t);
|
||||
uint32_t hi_target = target >> 32u;
|
||||
uint32_t hi = timer_hw->timerawh;
|
||||
while (hi < hi_target) {
|
||||
hi = timer_hw->timerawh;
|
||||
tight_loop_contents();
|
||||
}
|
||||
while (hi == hi_target && timer_hw->timerawl < (uint32_t) target) {
|
||||
hi = timer_hw->timerawh;
|
||||
tight_loop_contents();
|
||||
}
|
||||
}
|
||||
/// \end::busy_wait[]
|
||||
|
||||
static inline uint harware_alarm_irq_number(uint alarm_num) {
|
||||
return TIMER_IRQ_0 + alarm_num;
|
||||
}
|
||||
|
||||
static void hardware_alarm_irq_handler() {
|
||||
// Determine which timer this IRQ is for
|
||||
uint32_t ipsr;
|
||||
__asm volatile ("mrs %0, ipsr" : "=r" (ipsr)::);
|
||||
uint alarm_num = (ipsr & 0x3fu) - 16 - TIMER_IRQ_0;
|
||||
check_hardware_alarm_num_param(alarm_num);
|
||||
|
||||
hardware_alarm_callback_t callback = NULL;
|
||||
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
// Clear the timer IRQ (inside lock, because we check whether we have handled the IRQ yet in alarm_set by looking at the interrupt status
|
||||
timer_hw->intr = 1u << alarm_num;
|
||||
|
||||
// make sure the IRQ is still valid
|
||||
if (timer_callbacks_pending & (1u << alarm_num)) {
|
||||
// Now check whether we have a timer event to handle that isn't already obsolete (this could happen if we
|
||||
// were already in the IRQ handler before someone else changed the timer setup
|
||||
if (timer_hw->timerawh >= target_hi[alarm_num]) {
|
||||
// we have reached the right high word as well as low word value
|
||||
callback = alarm_callbacks[alarm_num];
|
||||
timer_callbacks_pending &= ~(1u << alarm_num);
|
||||
} else {
|
||||
// try again in 2^32 us
|
||||
timer_hw->alarm[alarm_num] = timer_hw->alarm[alarm_num]; // re-arm the timer
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock(lock, save);
|
||||
|
||||
if (callback) {
|
||||
callback(alarm_num);
|
||||
}
|
||||
}
|
||||
|
||||
void hardware_alarm_set_callback(uint alarm_num, hardware_alarm_callback_t callback) {
|
||||
// todo check current core owner
|
||||
// note this should probably be subsumed by irq_set_exclusive_handler anyway, since that
|
||||
// should disallow IRQ handlers on both cores
|
||||
check_hardware_alarm_num_param(alarm_num);
|
||||
uint irq_num = harware_alarm_irq_number(alarm_num);
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
if (callback) {
|
||||
if (hardware_alarm_irq_handler != irq_get_vtable_handler(irq_num)) {
|
||||
// note that set_exclusive will silently allow you to set the handler to the same thing
|
||||
// since it is idempotent, which means we don't need to worry about locking ourselves
|
||||
irq_set_exclusive_handler(irq_num, hardware_alarm_irq_handler);
|
||||
irq_set_enabled(irq_num, true);
|
||||
// Enable interrupt in block and at processor
|
||||
hw_set_bits(&timer_hw->inte, 1u << alarm_num);
|
||||
}
|
||||
alarm_callbacks[alarm_num] = callback;
|
||||
} else {
|
||||
alarm_callbacks[alarm_num] = NULL;
|
||||
timer_callbacks_pending &= ~(1u << alarm_num);
|
||||
irq_remove_handler(irq_num, hardware_alarm_irq_handler);
|
||||
irq_set_enabled(irq_num, false);
|
||||
}
|
||||
spin_unlock(lock, save);
|
||||
}
|
||||
|
||||
bool hardware_alarm_set_target(uint alarm_num, absolute_time_t target) {
|
||||
bool missed;
|
||||
uint64_t now = time_us_64();
|
||||
uint64_t t = to_us_since_boot(target);
|
||||
if (now >= t) {
|
||||
missed = true;
|
||||
} else {
|
||||
missed = false;
|
||||
|
||||
// 1) actually set the hardware timer
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
timer_hw->intr = 1u << alarm_num;
|
||||
timer_callbacks_pending |= 1u << alarm_num;
|
||||
timer_hw->alarm[alarm_num] = (uint32_t) t;
|
||||
// Set the alarm. Writing time should arm it
|
||||
target_hi[alarm_num] = t >> 32u;
|
||||
|
||||
// 2) check for races
|
||||
if (!(timer_hw->armed & 1u << alarm_num)) {
|
||||
// not armed, so has already fired .. IRQ must be pending (we are still under lock)
|
||||
assert(timer_hw->ints & 1u << alarm_num);
|
||||
} else {
|
||||
if (time_us_64() >= t) {
|
||||
// ok well it is time now; the irq isn't being handled yet because of the spin lock
|
||||
// however the other core might be in the IRQ handler itself about to do a callback
|
||||
// we do the firing ourselves (and indicate to the IRQ handler if any that it shouldn't
|
||||
missed = true;
|
||||
// disarm the timer
|
||||
timer_hw->armed = 1u << alarm_num;
|
||||
timer_hw->intr = 1u << alarm_num; // clear the IRQ too
|
||||
// and set flag in case we're already in the IRQ handler waiting on the spinlock (on the other core)
|
||||
timer_callbacks_pending &= ~(1u << alarm_num);
|
||||
}
|
||||
}
|
||||
spin_unlock(lock, save);
|
||||
}
|
||||
return missed;
|
||||
}
|
||||
|
||||
void hardware_alarm_cancel(uint alarm_num) {
|
||||
check_hardware_alarm_num_param(alarm_num);
|
||||
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
timer_hw->armed = 1u << alarm_num;
|
||||
timer_callbacks_pending &= ~(1u << alarm_num);
|
||||
spin_unlock(lock, save);
|
||||
}
|
||||
|
||||
|
1
src/rp2_common/hardware_uart/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_uart/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(uart)
|
432
src/rp2_common/hardware_uart/include/hardware/uart.h
Normal file
432
src/rp2_common/hardware_uart/include/hardware/uart.h
Normal file
@ -0,0 +1,432 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_UART_H
|
||||
#define _HARDWARE_UART_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/uart.h"
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_UART, Enable/disable assertions in the UART module, type=bool, default=0, group=hardware_uart
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_UART
|
||||
#define PARAM_ASSERTIONS_ENABLED_UART 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_UART_ENABLE_CRLF_SUPPORT, Enable/disable CR/LF translation support, type=bool, default=1, group=hardware_uart
|
||||
#ifndef PICO_UART_ENABLE_CRLF_SUPPORT
|
||||
#define PICO_UART_ENABLE_CRLF_SUPPORT 1
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_UART_DEFAULT_CRLF, Enable/disable CR/LF translation on UART, type=bool, default=0, depends=PICO_UART_ENABLE_CRLF_SUPPORT, group=hardware_uart
|
||||
#ifndef PICO_UART_DEFAULT_CRLF
|
||||
#define PICO_UART_DEFAULT_CRLF 0
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_DEFAULT_UART, Define the default UART used for printf etc, default=0, group=hardware_uart
|
||||
#ifndef PICO_DEFAULT_UART
|
||||
#define PICO_DEFAULT_UART 0 ///< Default UART instance
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_DEFAULT_UART_BAUD_RATE, Define the default UART baudrate, max=921600, default=115200, group=hardware_uart
|
||||
#ifndef PICO_DEFAULT_UART_BAUD_RATE
|
||||
#define PICO_DEFAULT_UART_BAUD_RATE 115200 ///< Default baud rate
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_DEFAULT_UART_TX_PIN, Define the default UART TX pin, min=0, max=29, default=0, group=hardware_uart
|
||||
#ifndef PICO_DEFAULT_UART_TX_PIN
|
||||
#define PICO_DEFAULT_UART_TX_PIN 0 ///< Default TX pin
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_DEFAULT_UART_RX_PIN, Define the default UART RX pin, min=0, max=29, default=1, group=hardware_uart
|
||||
#ifndef PICO_DEFAULT_UART_RX_PIN
|
||||
#define PICO_DEFAULT_UART_RX_PIN 1 ///< Default RX pin
|
||||
#endif
|
||||
|
||||
/** \file hardware/uart.h
|
||||
* \defgroup hardware_uart hardware_uart
|
||||
*
|
||||
* Hardware UART API
|
||||
*
|
||||
* RP2040 has 2 identical instances of a UART peripheral, based on the ARM PL011. Each UART can be connected to a number
|
||||
* of GPIO pins as defined in the GPIO muxing.
|
||||
*
|
||||
* Only the TX, RX, RTS, and CTS signals are
|
||||
* connected, meaning that the modem mode and IrDA mode of the PL011 are not supported.
|
||||
*
|
||||
* \subsection uart_example Example
|
||||
* \addtogroup hardware_uart
|
||||
*
|
||||
* \code
|
||||
* int main() {
|
||||
*
|
||||
* // Initialise UART 0
|
||||
* uart_init(uart0, 115200);
|
||||
*
|
||||
* // Set the GPIO pin mux to the UART - 0 is TX, 1 is RX
|
||||
* gpio_set_function(0, GPIO_FUNC_UART);
|
||||
* gpio_set_function(1, GPIO_FUNC_UART);
|
||||
*
|
||||
* uart_puts(uart0, "Hello world!");
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
|
||||
// Currently always a pointer to hw but it might not be in the future
|
||||
typedef struct uart_inst uart_inst_t;
|
||||
|
||||
/** The UART identifiers for use in UART functions.
|
||||
*
|
||||
* e.g. uart_init(uart1, 48000)
|
||||
*
|
||||
* \ingroup hardware_uart
|
||||
* @{
|
||||
*/
|
||||
#define uart0 ((uart_inst_t * const)uart0_hw) ///< Identifier for UART instance 0
|
||||
#define uart1 ((uart_inst_t * const)uart1_hw) ///< Identifier for UART instance 1
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifndef PICO_DEFAULT_UART_INSTANCE
|
||||
#define PICO_DEFAULT_UART_INSTANCE (__CONCAT(uart,PICO_DEFAULT_UART))
|
||||
#endif
|
||||
|
||||
#define uart_default PICO_DEFAULT_UART_INSTANCE
|
||||
|
||||
/*! \brief Convert UART instance to hardware instance number
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* \param uart UART instance
|
||||
* \return Number of UART, 0 or 1.
|
||||
*/
|
||||
static inline uint uart_get_index(uart_inst_t *uart) {
|
||||
invalid_params_if(UART, uart != uart0 && uart != uart1);
|
||||
return uart == uart1 ? 1 : 0;
|
||||
}
|
||||
|
||||
static inline uart_hw_t *uart_get_hw(uart_inst_t *uart) {
|
||||
uart_get_index(uart); // check it is a hw uart
|
||||
return (uart_hw_t *)uart;
|
||||
}
|
||||
|
||||
/** \brief UART Parity enumeration
|
||||
* \ingroup hardware_uart
|
||||
*/
|
||||
typedef enum {
|
||||
UART_PARITY_NONE,
|
||||
UART_PARITY_EVEN,
|
||||
UART_PARITY_ODD
|
||||
} uart_parity_t;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Setup
|
||||
|
||||
/*! \brief Initialise a UART
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* Put the UART into a known state, and enable it. Must be called before other
|
||||
* functions.
|
||||
*
|
||||
* \note There is no guarantee that the baudrate requested will be possible, the nearest will be chosen,
|
||||
* and this function will return the configured baud rate.
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param baudrate Baudrate of UART in Hz
|
||||
* \return Actual set baudrate
|
||||
*/
|
||||
uint uart_init(uart_inst_t *uart, uint baudrate);
|
||||
|
||||
/*! \brief DeInitialise a UART
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* Disable the UART if it is no longer used. Must be reinitialised before
|
||||
* being used again.
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
*/
|
||||
void uart_deinit(uart_inst_t *uart);
|
||||
|
||||
/*! \brief Set UART baud rate
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* Set baud rate as close as possible to requested, and return actual rate selected.
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param baudrate Baudrate in Hz
|
||||
*/
|
||||
uint uart_set_baudrate(uart_inst_t *uart, uint baudrate);
|
||||
|
||||
/*! \brief Set UART flow control CTS/RTS
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param cts If true enable flow control of TX by clear-to-send input
|
||||
* \param rts If true enable assertion of request-to-send output by RX flow control
|
||||
*/
|
||||
static inline void uart_set_hw_flow(uart_inst_t *uart, bool cts, bool rts) {
|
||||
hw_write_masked(&uart_get_hw(uart)->cr,
|
||||
(!!cts << UART_UARTCR_CTSEN_LSB) | (!!rts << UART_UARTCR_RTSEN_LSB),
|
||||
UART_UARTCR_RTSEN_BITS | UART_UARTCR_CTSEN_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Set UART data format
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* Configure the data format (bits etc() for the UART
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param data_bits Number of bits of data. 5..8
|
||||
* \param stop_bits Number of stop bits 1..2
|
||||
* \param parity Parity option.
|
||||
*/
|
||||
static inline void uart_set_format(uart_inst_t *uart, uint data_bits, uint stop_bits, uart_parity_t parity) {
|
||||
invalid_params_if(UART, data_bits < 5 || data_bits > 8);
|
||||
invalid_params_if(UART, stop_bits != 1 && stop_bits != 2);
|
||||
invalid_params_if(UART, parity != UART_PARITY_NONE && parity != UART_PARITY_EVEN && parity != UART_PARITY_ODD);
|
||||
hw_write_masked(&uart_get_hw(uart)->lcr_h,
|
||||
((data_bits - 5) << UART_UARTLCR_H_WLEN_LSB) |
|
||||
((stop_bits - 1) << UART_UARTLCR_H_STP2_LSB) |
|
||||
((parity != UART_PARITY_NONE) << UART_UARTLCR_H_PEN_LSB) |
|
||||
((parity == UART_PARITY_EVEN) << UART_UARTLCR_H_EPS_LSB),
|
||||
UART_UARTLCR_H_WLEN_BITS |
|
||||
UART_UARTLCR_H_STP2_BITS |
|
||||
UART_UARTLCR_H_PEN_BITS |
|
||||
UART_UARTLCR_H_EPS_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Setup UART interrupts
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* Enable the UART's interrupt output. An interrupt handler will need to be installed prior to calling
|
||||
* this function.
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param rx_has_data If true an interrupt will be fired when the RX FIFO contain data.
|
||||
* \param tx_needs_data If true an interrupt will be fired when the TX FIFO needs data.
|
||||
*/
|
||||
static inline void uart_set_irq_enables(uart_inst_t *uart, bool rx_has_data, bool tx_needs_data) {
|
||||
uart_get_hw(uart)->imsc = (!!tx_needs_data << UART_UARTIMSC_TXIM_LSB) |
|
||||
(!!rx_has_data << UART_UARTIMSC_RXIM_LSB);
|
||||
if (rx_has_data) {
|
||||
// Set minimum threshold
|
||||
hw_write_masked(&uart_get_hw(uart)->ifls, 0 << UART_UARTIFLS_RXIFLSEL_LSB,
|
||||
UART_UARTIFLS_RXIFLSEL_BITS);
|
||||
}
|
||||
if (tx_needs_data) {
|
||||
// Set maximum threshold
|
||||
hw_write_masked(&uart_get_hw(uart)->ifls, 0 << UART_UARTIFLS_TXIFLSEL_LSB,
|
||||
UART_UARTIFLS_TXIFLSEL_BITS);
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Test if specific UART is enabled
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \return true if the UART is enabled
|
||||
*/
|
||||
static inline bool uart_is_enabled(uart_inst_t *uart) {
|
||||
return !!(uart_get_hw(uart)->cr & UART_UARTCR_UARTEN_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Enable/Disable the FIFOs on specified UART
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param enabled true to enable FIFO (default), false to disable
|
||||
*/
|
||||
static inline void uart_set_fifo_enabled(uart_inst_t *uart, bool enabled) {
|
||||
hw_write_masked(&uart_get_hw(uart)->lcr_h,
|
||||
(!!enabled << UART_UARTLCR_H_FEN_LSB),
|
||||
UART_UARTLCR_H_FEN_BITS);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Generic input/output
|
||||
|
||||
/*! \brief Determine if space is available in the TX FIFO
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \return false if no space available, true otherwise
|
||||
*/
|
||||
static inline bool uart_is_writable(uart_inst_t *uart) {
|
||||
return !(uart_get_hw(uart)->fr & UART_UARTFR_TXFF_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Wait for the UART TX fifo to be drained
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
*/
|
||||
static inline void uart_tx_wait_blocking(uart_inst_t *uart) {
|
||||
while (uart_get_hw(uart)->fr & UART_UARTFR_BUSY_BITS) tight_loop_contents();
|
||||
}
|
||||
|
||||
/*! \brief Determine whether data is waiting in the RX FIFO
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \return 0 if no data available, otherwise the number of bytes, at least, that can be read
|
||||
*
|
||||
* \note HW limitations mean this function will return either 0 or 1.
|
||||
*/
|
||||
static inline bool uart_is_readable(uart_inst_t *uart) {
|
||||
// PL011 doesn't expose levels directly, so return values are only 0 or 1
|
||||
return !(uart_get_hw(uart)->fr & UART_UARTFR_RXFE_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Write to the UART for transmission.
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* This function will block until all the data has been sent to the UART
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param src The bytes to send
|
||||
* \param len The number of bytes to send
|
||||
*/
|
||||
static inline void uart_write_blocking(uart_inst_t *uart, const uint8_t *src, size_t len) {
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
while (!uart_is_writable(uart))
|
||||
tight_loop_contents();
|
||||
uart_get_hw(uart)->dr = *src++;
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Read from the UART
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* This function will block until all the data has been received from the UART
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param dst Buffer to accept received bytes
|
||||
* \param len The number of bytes to receive.
|
||||
*/
|
||||
static inline void uart_read_blocking(uart_inst_t *uart, uint8_t *dst, size_t len) {
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
while (!uart_is_readable(uart))
|
||||
tight_loop_contents();
|
||||
*dst++ = uart_get_hw(uart)->dr;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// UART-specific operations and aliases
|
||||
|
||||
/*! \brief Write single character to UART for transmission.
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* This function will block until all the character has been sent
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param c The character to send
|
||||
*/
|
||||
static inline void uart_putc_raw(uart_inst_t *uart, char c) {
|
||||
uart_write_blocking(uart, (const uint8_t *) &c, 1);
|
||||
}
|
||||
|
||||
/*! \brief Write single character to UART for transmission, with optional CR/LF conversions
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* This function will block until the character has been sent
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param c The character to send
|
||||
*/
|
||||
static inline void uart_putc(uart_inst_t *uart, char c) {
|
||||
#if PICO_UART_ENABLE_CRLF_SUPPORT
|
||||
extern short uart_char_to_line_feed[NUM_UARTS];
|
||||
if (uart_char_to_line_feed[uart_get_index(uart)] == c)
|
||||
uart_putc_raw(uart, '\r');
|
||||
#endif
|
||||
uart_putc_raw(uart, c);
|
||||
}
|
||||
|
||||
/*! \brief Write string to UART for transmission, doing any CR/LF conversions
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* This function will block until the entire string has been sent
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param s The null terminated string to send
|
||||
*/
|
||||
static inline void uart_puts(uart_inst_t *uart, const char *s) {
|
||||
#if PICO_UART_ENABLE_CRLF_SUPPORT
|
||||
bool last_was_cr = false;
|
||||
while (*s) {
|
||||
// Don't add extra carriage returns if one is present
|
||||
if (last_was_cr)
|
||||
uart_putc_raw(uart, *s);
|
||||
else
|
||||
uart_putc(uart, *s);
|
||||
last_was_cr = *s++ == '\r';
|
||||
}
|
||||
#else
|
||||
while (*s)
|
||||
uart_putc(uart, *s++);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*! \brief Read a single character to UART
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* This function will block until the character has been read
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \return The character read.
|
||||
*/
|
||||
static inline char uart_getc(uart_inst_t *uart) {
|
||||
char c;
|
||||
uart_read_blocking(uart, (uint8_t *) &c, 1);
|
||||
return c;
|
||||
}
|
||||
|
||||
/*! \brief Assert a break condition on the UART transmission.
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param en Assert break condition (TX held low) if true. Clear break condition if false.
|
||||
*/
|
||||
static inline void uart_set_break(uart_inst_t *uart, bool en) {
|
||||
if (en)
|
||||
hw_set_bits(&uart_get_hw(uart)->lcr_h, UART_UARTLCR_H_BRK_BITS);
|
||||
else
|
||||
hw_clear_bits(&uart_get_hw(uart)->lcr_h, UART_UARTLCR_H_BRK_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Set CR/LF conversion on UART
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param translate If true, convert line feeds to carriage return on transmissions
|
||||
*/
|
||||
void uart_set_translate_crlf(uart_inst_t *uart, bool translate);
|
||||
|
||||
/*! \brief Wait for the default UART'S TX fifo to be drained
|
||||
* \ingroup hardware_uart
|
||||
*/
|
||||
static inline void uart_default_tx_wait_blocking() {
|
||||
uart_tx_wait_blocking(uart_default);
|
||||
}
|
||||
|
||||
/*! \brief Wait for up to a certain number of microseconds for the RX FIFO to be non empty
|
||||
* \ingroup hardware_uart
|
||||
*
|
||||
* \param uart UART instance. \ref uart0 or \ref uart1
|
||||
* \param us the number of microseconds to wait at most (may be 0 for an instantaneous check)
|
||||
* \return true if the RX FIFO became non empty before the timeout, false otherwise
|
||||
*/
|
||||
bool uart_is_readable_within_us(uart_inst_t *uart, uint32_t us);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
112
src/rp2_common/hardware_uart/uart.c
Normal file
112
src/rp2_common/hardware_uart/uart.c
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/address_mapped.h"
|
||||
#include "hardware/platform_defs.h"
|
||||
#include "hardware/uart.h"
|
||||
|
||||
#include "hardware/structs/uart.h"
|
||||
#include "hardware/resets.h"
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/timer.h"
|
||||
|
||||
#include "pico/assert.h"
|
||||
#include "pico.h"
|
||||
|
||||
check_hw_layout(uart_hw_t, fr, UART_UARTFR_OFFSET);
|
||||
check_hw_layout(uart_hw_t, dmacr, UART_UARTDMACR_OFFSET);
|
||||
|
||||
#if PICO_UART_ENABLE_CRLF_SUPPORT
|
||||
short uart_char_to_line_feed[NUM_UARTS];
|
||||
#endif
|
||||
|
||||
/// \tag::uart_reset[]
|
||||
static inline void uart_reset(uart_inst_t *uart) {
|
||||
invalid_params_if(UART, uart != uart0 && uart != uart1);
|
||||
reset_block(uart_get_index(uart) ? RESETS_RESET_UART1_BITS : RESETS_RESET_UART0_BITS);
|
||||
}
|
||||
|
||||
static inline void uart_unreset(uart_inst_t *uart) {
|
||||
invalid_params_if(UART, uart != uart0 && uart != uart1);
|
||||
unreset_block_wait(uart_get_index(uart) ? RESETS_RESET_UART1_BITS : RESETS_RESET_UART0_BITS);
|
||||
}
|
||||
/// \end::uart_reset[]
|
||||
|
||||
/// \tag::uart_init[]
|
||||
uint uart_init(uart_inst_t *uart, uint baudrate) {
|
||||
invalid_params_if(UART, uart != uart0 && uart != uart1);
|
||||
|
||||
if (clock_get_hz(clk_peri) == 0)
|
||||
return 0;
|
||||
|
||||
uart_reset(uart);
|
||||
uart_unreset(uart);
|
||||
|
||||
#if PICO_UART_ENABLE_CRLF_SUPPORT
|
||||
uart_set_translate_crlf(uart, PICO_UART_DEFAULT_CRLF);
|
||||
#endif
|
||||
|
||||
// Any LCR writes need to take place before enabling the UART
|
||||
uint baud = uart_set_baudrate(uart, baudrate);
|
||||
uart_set_format(uart, 8, 1, UART_PARITY_NONE);
|
||||
|
||||
// Enable the UART, both TX and RX
|
||||
uart_get_hw(uart)->cr = UART_UARTCR_UARTEN_BITS | UART_UARTCR_TXE_BITS | UART_UARTCR_RXE_BITS;
|
||||
// Enable FIFOs
|
||||
hw_set_bits(&uart_get_hw(uart)->lcr_h, UART_UARTLCR_H_FEN_BITS);
|
||||
// Always enable DREQ signals -- no harm in this if DMA is not listening
|
||||
uart_get_hw(uart)->dmacr = UART_UARTDMACR_TXDMAE_BITS | UART_UARTDMACR_RXDMAE_BITS;
|
||||
|
||||
return baud;
|
||||
}
|
||||
/// \end::uart_init[]
|
||||
|
||||
void uart_deinit(uart_inst_t *uart) {
|
||||
invalid_params_if(UART, uart != uart0 && uart != uart1);
|
||||
uart_reset(uart);
|
||||
}
|
||||
|
||||
/// \tag::uart_set_baudrate[]
|
||||
uint uart_set_baudrate(uart_inst_t *uart, uint baudrate) {
|
||||
invalid_params_if(UART, baudrate == 0);
|
||||
uint32_t baud_rate_div = (8 * clock_get_hz(clk_peri) / baudrate);
|
||||
uint32_t baud_ibrd = baud_rate_div >> 7;
|
||||
uint32_t baud_fbrd = ((baud_rate_div & 0x7f) + 1) / 2;
|
||||
invalid_params_if(UART, (baud_ibrd > 65535) || (baud_ibrd == 0));
|
||||
|
||||
// Load PL011's baud divisor registers
|
||||
uart_get_hw(uart)->ibrd = baud_ibrd;
|
||||
if (baud_ibrd == 65535) {
|
||||
uart_get_hw(uart)->fbrd = 0;
|
||||
} else {
|
||||
uart_get_hw(uart)->fbrd = baud_fbrd;
|
||||
}
|
||||
|
||||
// PL011 needs a (dummy) line control register write to latch in the
|
||||
// divisors. We don't want to actually change LCR contents here.
|
||||
hw_set_bits(&uart_get_hw(uart)->lcr_h, 0);
|
||||
|
||||
// See datasheet
|
||||
uint baud = (4 * clock_get_hz(clk_peri)) / (64 * baud_ibrd + baud_fbrd);
|
||||
return baud;
|
||||
}
|
||||
/// \end::uart_set_baudrate[]
|
||||
|
||||
void uart_set_translate_crlf(uart_inst_t *uart, bool crlf) {
|
||||
#if PICO_UART_ENABLE_CRLF_SUPPORT
|
||||
uart_char_to_line_feed[uart_get_index(uart)] = crlf ? '\n' : 0x100;
|
||||
#else
|
||||
panic_unsupported();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool uart_is_readable_within_us(uart_inst_t *uart, uint32_t us) {
|
||||
uint32_t t = time_us_32();
|
||||
do {
|
||||
if (uart_is_readable(uart)) return true;
|
||||
} while ((time_us_32() - t) <= us);
|
||||
return false;
|
||||
}
|
1
src/rp2_common/hardware_vreg/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_vreg/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(vreg)
|
55
src/rp2_common/hardware_vreg/include/hardware/vreg.h
Normal file
55
src/rp2_common/hardware_vreg/include/hardware/vreg.h
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_VREG_H_
|
||||
#define _HARDWARE_VREG_H_
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/vreg_and_chip_reset.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file vreg.h
|
||||
* \defgroup hardware_vreg hardware_vreg
|
||||
*
|
||||
* Voltage Regulation API
|
||||
*
|
||||
*/
|
||||
|
||||
/** Possible voltage values that can be applied to the regulator
|
||||
*/
|
||||
enum vreg_voltage {
|
||||
VREG_VOLTAGE_0_85 = 0b0110, ///< 0.85v
|
||||
VREG_VOLTAGE_0_90 = 0b0111, ///< 0.90v
|
||||
VREG_VOLTAGE_0_95 = 0b1000, ///< 0.95v
|
||||
VREG_VOLTAGE_1_00 = 0b1001, ///< 1.00v
|
||||
VREG_VOLTAGE_1_05 = 0b1010, ///< 1.05v
|
||||
VREG_VOLTAGE_1_10 = 0b1011, ///< 1.10v
|
||||
VREG_VOLTAGE_1_15 = 0b1100, ///< 1.15v
|
||||
VREG_VOLTAGE_1_20 = 0b1101, ///< 1.20v
|
||||
VREG_VOLTAGE_1_25 = 0b1110, ///< 1.25v
|
||||
VREG_VOLTAGE_1_30 = 0b1111, ///< 1.30v
|
||||
|
||||
VREG_VOLTAGE_MIN = VREG_VOLTAGE_0_85, ///< Always the minimum possible voltage
|
||||
VREG_VOLTAGE_DEFAULT = VREG_VOLTAGE_1_10, ///< Default voltage on power up.
|
||||
VREG_VOLTAGE_MAX = VREG_VOLTAGE_1_30, ///< Always the maximum possible voltage
|
||||
};
|
||||
|
||||
|
||||
/*! \brief Set voltage
|
||||
* \ingroup hardware_vreg
|
||||
*
|
||||
* \param voltage The voltage (from enumeration \ref vreg_voltage) to apply to the voltage regulator
|
||||
**/
|
||||
void vreg_set_voltage(enum vreg_voltage voltage);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
12
src/rp2_common/hardware_vreg/vreg.c
Normal file
12
src/rp2_common/hardware_vreg/vreg.c
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/vreg.h"
|
||||
|
||||
void vreg_set_voltage(enum vreg_voltage voltage) {
|
||||
hw_write_masked(&vreg_and_chip_reset_hw->vreg, voltage << VREG_AND_CHIP_RESET_VREG_VSEL_LSB, VREG_AND_CHIP_RESET_VREG_VSEL_BITS);
|
||||
}
|
1
src/rp2_common/hardware_watchdog/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_watchdog/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(watchdog)
|
87
src/rp2_common/hardware_watchdog/include/hardware/watchdog.h
Normal file
87
src/rp2_common/hardware_watchdog/include/hardware/watchdog.h
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_WATCHDOG_H
|
||||
#define _HARDWARE_WATCHDOG_H
|
||||
|
||||
#include "pico.h"
|
||||
|
||||
/** \file hardware/watchdog.h
|
||||
* \defgroup hardware_watchdog hardware_watchdog
|
||||
*
|
||||
* Hardware Watchdog Timer API
|
||||
*
|
||||
* Supporting functions for the Pico hardware watchdog timer.
|
||||
*
|
||||
* The RP2040 has a built in HW watchdog Timer. This is a countdown timer that can restart parts of the chip if it reaches zero.
|
||||
* For example, this can be used to restart the processor if the software running on it gets stuck in an infinite loop
|
||||
* or similar. The programmer has to periodically write a value to the watchdog to stop it reaching zero.
|
||||
*
|
||||
* \subsection watchdog_example Example
|
||||
* \addtogroup hardware_watchdog
|
||||
* \include hello_watchdog.c
|
||||
*/
|
||||
|
||||
/*! \brief Define actions to perform at watchdog timeout
|
||||
* \ingroup hardware_watchdog
|
||||
*
|
||||
* \note If \ref watchdog_start_tick value does not give a 1MHz clock to the watchdog system, then the \ref delay_ms
|
||||
* parameter will not be in microseconds. See the datasheet for more details.
|
||||
*
|
||||
* By default the SDK assumes a 12MHz XOSC and sets the \ref watchdog_start_tick appropriately.
|
||||
*
|
||||
* \param pc If Zero, a standard boot will be performed, if non-zero this is the program counter to jump to on reset.
|
||||
* \param sp If \p pc is non-zero, this will be the stack pointer used.
|
||||
* \param delay_ms Initial load value. Maximum value 0x7fffff, approximately 8.3s.
|
||||
*/
|
||||
void watchdog_reboot(uint32_t pc, uint32_t sp, uint32_t delay_ms);
|
||||
|
||||
/*! \brief Start the watchdog tick
|
||||
* \ingroup hardware_watchdog
|
||||
*
|
||||
* \param cycles This needs to be a divider that when applied to the XOSC input, produces a 1MHz clock. So if the XOSC is
|
||||
* 12MHz, this will need to be 12.
|
||||
*/
|
||||
void watchdog_start_tick(uint cycles);
|
||||
|
||||
/*! \brief Reload the watchdog counter with the amount of time set in watchdog_enable
|
||||
* \ingroup hardware_watchdog
|
||||
*
|
||||
*/
|
||||
void watchdog_update(void);
|
||||
|
||||
/**
|
||||
* \brief Enable the watchdog
|
||||
* \ingroup hardware_watchdog
|
||||
*
|
||||
* \note If \ref watchdog_start_tick value does not give a 1MHz clock to the watchdog system, then the \ref delay_ms
|
||||
* parameter will not be in microseconds. See the datasheet for more details.
|
||||
*
|
||||
* By default the SDK assumes a 12MHz XOSC and sets the \ref watchdog_start_tick appropriately.
|
||||
*
|
||||
* \param delay_ms Number of milliseconds before watchdog will reboot without watchdog_update being called. Maximum of 0x7fffff, which is approximately 8.3 seconds
|
||||
* \param pause_on_debug If the watchdog should be paused when the debugger is stepping through code
|
||||
*/
|
||||
void watchdog_enable(uint32_t delay_ms, bool pause_on_debug);
|
||||
|
||||
/**
|
||||
* \brief Did the watchdog cause the last reboot?
|
||||
* \ingroup hardware_watchdog
|
||||
*
|
||||
* @return true if the watchdog timer or a watchdog force caused the last reboot
|
||||
* @return false there has been no watchdog reboot since run has been
|
||||
*/
|
||||
bool watchdog_caused_reboot(void);
|
||||
|
||||
/**
|
||||
* @brief Returns the number of microseconds before the watchdog will reboot the chip.
|
||||
* \ingroup hardware_watchdog
|
||||
*
|
||||
* @return The number of microseconds before the watchdog will reboot the chip.
|
||||
*/
|
||||
uint32_t watchdog_get_count(void);
|
||||
|
||||
#endif
|
99
src/rp2_common/hardware_watchdog/watchdog.c
Normal file
99
src/rp2_common/hardware_watchdog/watchdog.c
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "hardware/watchdog.h"
|
||||
#include "hardware/structs/watchdog.h"
|
||||
#include "hardware/structs/psm.h"
|
||||
|
||||
/// \tag::watchdog_start_tick[]
|
||||
void watchdog_start_tick(uint cycles) {
|
||||
// Important: This function also provides a tick reference to the timer
|
||||
watchdog_hw->tick = cycles | WATCHDOG_TICK_ENABLE_BITS;
|
||||
}
|
||||
/// \end::watchdog_start_tick[]
|
||||
|
||||
// Value to load when updating the watchdog
|
||||
|
||||
// tag::watchdog_update[]
|
||||
static uint32_t load_value;
|
||||
|
||||
void watchdog_update(void) {
|
||||
watchdog_hw->load = load_value;
|
||||
}
|
||||
// end::watchdog_update[]
|
||||
|
||||
uint32_t watchdog_get_count(void) {
|
||||
return (watchdog_hw->ctrl & WATCHDOG_CTRL_TIME_BITS) / 2 ;
|
||||
}
|
||||
|
||||
// tag::watchdog_enable[]
|
||||
// Helper function used by both watchdog_enable and watchdog_reboot
|
||||
void _watchdog_enable(uint32_t delay_ms, bool pause_on_debug) {
|
||||
hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
|
||||
|
||||
// Reset everything apart from ROSC and XOSC
|
||||
hw_set_bits(&psm_hw->wdsel, PSM_WDSEL_BITS & ~(PSM_WDSEL_ROSC_BITS | PSM_WDSEL_XOSC_BITS));
|
||||
|
||||
uint32_t dbg_bits = WATCHDOG_CTRL_PAUSE_DBG0_BITS |
|
||||
WATCHDOG_CTRL_PAUSE_DBG1_BITS |
|
||||
WATCHDOG_CTRL_PAUSE_JTAG_BITS;
|
||||
|
||||
if (pause_on_debug) {
|
||||
hw_set_bits(&watchdog_hw->ctrl, dbg_bits);
|
||||
} else {
|
||||
hw_clear_bits(&watchdog_hw->ctrl, dbg_bits);
|
||||
}
|
||||
|
||||
if (!delay_ms) delay_ms = 50;
|
||||
|
||||
// Note, we have x2 here as the watchdog HW currently decrements twice per tick
|
||||
load_value = delay_ms * 1000 * 2;
|
||||
|
||||
if (load_value > 0xffffffu)
|
||||
load_value = 0xffffffu;
|
||||
|
||||
|
||||
watchdog_update();
|
||||
|
||||
hw_set_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
|
||||
}
|
||||
// end::watchdog_enable[]
|
||||
|
||||
void watchdog_enable(uint32_t delay_ms, bool pause_on_debug) {
|
||||
// This watchdog enable doesn't reboot so clear scratch register
|
||||
// with magic word to jump into code
|
||||
watchdog_hw->scratch[4] = 0;
|
||||
_watchdog_enable(delay_ms, pause_on_debug);
|
||||
}
|
||||
|
||||
void watchdog_reboot(uint32_t pc, uint32_t sp, uint32_t delay_ms) {
|
||||
check_hw_layout(watchdog_hw_t, scratch[7], WATCHDOG_SCRATCH7_OFFSET);
|
||||
|
||||
// Clear enable before setting up scratch registers
|
||||
hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS);
|
||||
|
||||
if (pc) {
|
||||
pc |= 1u; // thumb mode
|
||||
watchdog_hw->scratch[4] = 0xb007c0d3;
|
||||
watchdog_hw->scratch[5] = pc ^ -0xb007c0d3;
|
||||
watchdog_hw->scratch[6] = sp;
|
||||
watchdog_hw->scratch[7] = pc;
|
||||
// printf("rebooting %08x/%08x in %dms...\n", (uint) pc, (uint) sp, (uint) delay_ms);
|
||||
} else {
|
||||
watchdog_hw->scratch[4] = 0;
|
||||
// printf("rebooting (regular)) in %dms...\n", (uint) delay_ms);
|
||||
}
|
||||
|
||||
// Don't pause watchdog for debug
|
||||
_watchdog_enable(delay_ms, 0);
|
||||
}
|
||||
|
||||
bool watchdog_caused_reboot(void) {
|
||||
// If any reason bits are set this is true
|
||||
return watchdog_hw->reason;
|
||||
}
|
1
src/rp2_common/hardware_xosc/CMakeLists.txt
Normal file
1
src/rp2_common/hardware_xosc/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(xosc)
|
50
src/rp2_common/hardware_xosc/include/hardware/xosc.h
Normal file
50
src/rp2_common/hardware_xosc/include/hardware/xosc.h
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_XOSC_H_
|
||||
#define _HARDWARE_XOSC_H_
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/xosc.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file hardware/xosc.h
|
||||
* \defgroup hardware_xosc hardware_xosc
|
||||
*
|
||||
* Crystal Oscillator (XOSC) API
|
||||
*/
|
||||
|
||||
/*! \brief Initialise the crystal oscillator system
|
||||
* \ingroup hardware_xosc
|
||||
*
|
||||
* This function will block until the crystal oscillator has stabilised.
|
||||
**/
|
||||
void xosc_init(void);
|
||||
|
||||
/*! \brief Disable the Crystal oscillator
|
||||
* \ingroup hardware_xosc
|
||||
*
|
||||
* Turns off the crystal oscillator source, and waits for it to become unstable
|
||||
**/
|
||||
void xosc_disable(void);
|
||||
|
||||
/*! \brief Set the crystal oscillator system to dormant
|
||||
* \ingroup hardware_xosc
|
||||
*
|
||||
* Turns off the crystal oscillator until it is woken by an interrupt. This will block and hence
|
||||
* the entire system will stop, until an interrupt wakes it up. This function will
|
||||
* continue to block until the oscillator becomes stable after its wakeup.
|
||||
**/
|
||||
void xosc_dormant(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
46
src/rp2_common/hardware_xosc/xosc.c
Normal file
46
src/rp2_common/hardware_xosc/xosc.c
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "pico.h"
|
||||
|
||||
// For MHZ definitions etc
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
#include "hardware/platform_defs.h"
|
||||
#include "hardware/regs/xosc.h"
|
||||
#include "hardware/structs/xosc.h"
|
||||
|
||||
void xosc_init(void) {
|
||||
// Assumes 1-15 MHz input
|
||||
assert(XOSC_MHZ <= 15);
|
||||
xosc_hw->ctrl = XOSC_CTRL_FREQ_RANGE_VALUE_1_15MHZ;
|
||||
|
||||
// Set xosc startup delay
|
||||
uint32_t startup_delay = (((12 * MHZ) / 1000) + 128) / 256;
|
||||
xosc_hw->startup = startup_delay;
|
||||
|
||||
// Set the enable bit now that we have set freq range and startup delay
|
||||
hw_set_bits(&xosc_hw->ctrl, XOSC_CTRL_ENABLE_VALUE_ENABLE << XOSC_CTRL_ENABLE_LSB);
|
||||
|
||||
// Wait for XOSC to be stable
|
||||
while(!(xosc_hw->status & XOSC_STATUS_STABLE_BITS));
|
||||
}
|
||||
|
||||
void xosc_disable(void) {
|
||||
uint32_t tmp = xosc_hw->ctrl;
|
||||
tmp &= (~XOSC_CTRL_ENABLE_BITS);
|
||||
tmp |= (XOSC_CTRL_ENABLE_VALUE_DISABLE << XOSC_CTRL_ENABLE_LSB);
|
||||
xosc_hw->ctrl = tmp;
|
||||
// Wait for stable to go away
|
||||
while(xosc_hw->status & XOSC_STATUS_STABLE_BITS);
|
||||
}
|
||||
|
||||
void xosc_dormant(void) {
|
||||
// WARNING: This stops the xosc until woken up by an irq
|
||||
xosc_hw->dormant = XOSC_DORMANT_VALUE_DORMANT;
|
||||
// Wait for it to become stable once woken up
|
||||
while(!(xosc_hw->status & XOSC_STATUS_STABLE_BITS));
|
||||
}
|
57
src/rp2_common/pico_bit_ops/CMakeLists.txt
Normal file
57
src/rp2_common/pico_bit_ops/CMakeLists.txt
Normal file
@ -0,0 +1,57 @@
|
||||
if (NOT TARGET pico_bit_ops)
|
||||
#shims for ROM functions for -lgcc functions (listed below)
|
||||
add_library(pico_bit_ops INTERFACE)
|
||||
|
||||
# no custom implementation; falls thru to compiler
|
||||
add_library(pico_bit_ops_compiler INTERFACE)
|
||||
# PICO_BUILD_DEFINE: PICO_BIT_OPS_COMPILER, whether compiler provided bit_ops bit functions support is being used, type=bool, default=0, but dependent on CMake options, group=pico_bit_ops
|
||||
target_compile_definitions(pico_bit_ops_compiler INTERFACE
|
||||
PICO_BIT_OPS_COMPILER=1
|
||||
)
|
||||
|
||||
# add alias "default" which is just pico.
|
||||
add_library(pico_bit_ops_default INTERFACE)
|
||||
target_link_libraries(pico_bit_ops_default INTERFACE pico_bit_ops_pico)
|
||||
|
||||
set(PICO_DEFAULT_BIT_OPS_IMPL pico_bit_ops_default)
|
||||
|
||||
add_library(pico_bit_ops_pico INTERFACE)
|
||||
target_link_libraries(pico_bit_ops INTERFACE
|
||||
$<IF:$<BOOL:$<TARGET_PROPERTY:PICO_TARGET_BIT_OPS_IMPL>>,$<TARGET_PROPERTY:PICO_TARGET_BIT_OPS_IMPL>,${PICO_DEFAULT_BIT_OPS_IMPL}>)
|
||||
|
||||
# PICO_BUILD_DEFINE: PICO_BIT_OPS_PICO, whether optimized pico/bootrom provided bit_ops bit functions support is being used, type=bool, default=1, but dependent on CMake options, group=pico_bit_ops
|
||||
target_compile_definitions(pico_bit_ops_pico INTERFACE
|
||||
PICO_BIT_OPS_PICO=1
|
||||
)
|
||||
|
||||
target_sources(pico_bit_ops_pico INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/bit_ops_aeabi.S
|
||||
)
|
||||
|
||||
target_link_libraries(pico_bit_ops_pico INTERFACE pico_bootrom pico_bit_ops_headers)
|
||||
|
||||
# gcc
|
||||
pico_wrap_function(pico_bit_ops_pico __clzsi2)
|
||||
pico_wrap_function(pico_bit_ops_pico __clzsi2)
|
||||
pico_wrap_function(pico_bit_ops_pico __clzdi2)
|
||||
pico_wrap_function(pico_bit_ops_pico __ctzsi2)
|
||||
pico_wrap_function(pico_bit_ops_pico __ctzdi2)
|
||||
pico_wrap_function(pico_bit_ops_pico __popcountsi2)
|
||||
pico_wrap_function(pico_bit_ops_pico __popcountdi2)
|
||||
|
||||
# armclang
|
||||
pico_wrap_function(pico_bit_ops_pico __clz)
|
||||
pico_wrap_function(pico_bit_ops_pico __clzl)
|
||||
pico_wrap_function(pico_bit_ops_pico __clzsi2)
|
||||
pico_wrap_function(pico_bit_ops_pico __clzll)
|
||||
|
||||
macro(pico_set_bit_ops_implementation TARGET IMPL)
|
||||
get_target_property(target_type ${TARGET} TYPE)
|
||||
if ("EXECUTABLE" STREQUAL "${target_type}")
|
||||
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_BIT_OPS_IMPL "pico_bit_ops_${IMPL}")
|
||||
else()
|
||||
message(FATAL_ERROR "bit_ops implementation must be set on executable not library")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
endif()
|
132
src/rp2_common/pico_bit_ops/bit_ops_aeabi.S
Normal file
132
src/rp2_common/pico_bit_ops/bit_ops_aeabi.S
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
.syntax unified
|
||||
.cpu cortex-m0plus
|
||||
.thumb
|
||||
|
||||
#include "pico/asm_helper.S"
|
||||
__pre_init __aeabi_bits_init, 00010
|
||||
|
||||
.macro bits_section name
|
||||
#if PICO_BITS_IN_RAM
|
||||
.section RAM_SECTION_NAME(\name), "ax"
|
||||
#else
|
||||
.section SECTION_NAME(\name), "ax"
|
||||
#endif
|
||||
.endm
|
||||
|
||||
.section .data.aeabi_bits_funcs
|
||||
.global aeabi_bits_funcs, aeabi_bits_funcs_end
|
||||
.equ BITS_FUNC_COUNT, 4
|
||||
.align 4
|
||||
aeabi_bits_funcs:
|
||||
.word rom_table_code('P','3') // popcount32
|
||||
.word rom_table_code('L','3') // clz32
|
||||
.word rom_table_code('T','3') // ctz32
|
||||
.word rom_table_code('R','3') // reverse32
|
||||
aeabi_bits_funcs_end:
|
||||
|
||||
.section .text
|
||||
.thumb_func
|
||||
__aeabi_bits_init:
|
||||
ldr r0, =aeabi_bits_funcs
|
||||
movs r1, #BITS_FUNC_COUNT
|
||||
ldr r3, =rom_funcs_lookup
|
||||
bx r3
|
||||
|
||||
.equ POPCOUNT32, 0
|
||||
.equ CLZ32, 4
|
||||
.equ CTZ32, 8
|
||||
.equ REVERSE32, 12
|
||||
|
||||
bits_section clzsi
|
||||
wrapper_func __clz
|
||||
wrapper_func __clzl
|
||||
wrapper_func __clzsi2
|
||||
ldr r3, =aeabi_bits_funcs
|
||||
ldr r3, [r3, #CLZ32]
|
||||
bx r3
|
||||
|
||||
bits_section ctzsi
|
||||
wrapper_func __ctzsi2
|
||||
ldr r3, =aeabi_bits_funcs
|
||||
ldr r3, [r3, #CTZ32]
|
||||
bx r3
|
||||
|
||||
bits_section popcountsi
|
||||
wrapper_func __popcountsi2
|
||||
ldr r3, =aeabi_bits_funcs
|
||||
ldr r3, [r3, #POPCOUNT32]
|
||||
bx r3
|
||||
|
||||
bits_section clzdi
|
||||
wrapper_func __clzll
|
||||
wrapper_func __clzdi2
|
||||
ldr r3, =aeabi_bits_funcs
|
||||
ldr r3, [r3, #CLZ32]
|
||||
cmp r1, #0
|
||||
bne 1f
|
||||
push {lr}
|
||||
blx r3
|
||||
adds r0, #32
|
||||
pop {pc}
|
||||
1:
|
||||
mov r0, r1
|
||||
bx r3
|
||||
|
||||
bits_section ctzdi
|
||||
wrapper_func __ctzdi2
|
||||
ldr r3, =aeabi_bits_funcs
|
||||
ldr r3, [r3, #CTZ32]
|
||||
cmp r0, #0
|
||||
bne 1f
|
||||
bx r3
|
||||
1:
|
||||
push {lr}
|
||||
mov r0, r1
|
||||
blx r3
|
||||
adds r0, #32
|
||||
pop {pc}
|
||||
|
||||
bits_section popcountdi
|
||||
wrapper_func __popcountdi2
|
||||
ldr r3, =aeabi_bits_funcs
|
||||
ldr r3, [r3, #POPCOUNT32]
|
||||
push {r1, r3, lr}
|
||||
blx r3
|
||||
mov ip, r0
|
||||
pop {r0, r3}
|
||||
blx r3
|
||||
mov r1, ip
|
||||
add r0, r1
|
||||
pop {pc}
|
||||
|
||||
bits_section reverse32
|
||||
regular_func reverse32
|
||||
ldr r3, =aeabi_bits_funcs
|
||||
ldr r3, [r3, #REVERSE32]
|
||||
bx r3
|
||||
|
||||
bits_section __rev
|
||||
regular_func __rev
|
||||
regular_func __revl
|
||||
ldr r3, =aeabi_bits_funcs
|
||||
ldr r3, [r3, #REVERSE32]
|
||||
bx r3
|
||||
|
||||
bits_section __revll
|
||||
regular_func __revll
|
||||
push {lr}
|
||||
ldr r3, =aeabi_bits_funcs
|
||||
ldr r3, [r3, #REVERSE32]
|
||||
push {r1, r3}
|
||||
blx r3
|
||||
mov ip, r0 // reverse32 preserves ip
|
||||
pop {r0, r3}
|
||||
blx r3
|
||||
mov r1, ip
|
||||
pop {pc}
|
8
src/rp2_common/pico_bootrom/CMakeLists.txt
Normal file
8
src/rp2_common/pico_bootrom/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
add_library(pico_bootrom INTERFACE)
|
||||
|
||||
target_sources(pico_bootrom INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/bootrom.c
|
||||
)
|
||||
|
||||
target_include_directories(pico_bootrom INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
|
||||
target_link_libraries(pico_bootrom INTERFACE pico_base_headers)
|
38
src/rp2_common/pico_bootrom/bootrom.c
Normal file
38
src/rp2_common/pico_bootrom/bootrom.c
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "pico/bootrom.h"
|
||||
|
||||
/// \tag::table_lookup[]
|
||||
|
||||
// Bootrom function: rom_table_lookup
|
||||
// Returns the 32 bit pointer into the ROM if found or NULL otherwise.
|
||||
typedef void *(*rom_table_lookup_fn)(uint16_t *table, uint32_t code);
|
||||
|
||||
// Convert a 16 bit pointer stored at the given rom address into a 32 bit pointer
|
||||
#define rom_hword_as_ptr(rom_address) (void *)(uintptr_t)(*(uint16_t *)rom_address)
|
||||
|
||||
void *rom_func_lookup(uint32_t code) {
|
||||
rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) rom_hword_as_ptr(0x18);
|
||||
uint16_t *func_table = (uint16_t *) rom_hword_as_ptr(0x14);
|
||||
return rom_table_lookup(func_table, code);
|
||||
}
|
||||
|
||||
void *rom_data_lookup(uint32_t code) {
|
||||
rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) rom_hword_as_ptr(0x18);
|
||||
uint16_t *data_table = (uint16_t *) rom_hword_as_ptr(0x16);
|
||||
return rom_table_lookup(data_table, code);
|
||||
}
|
||||
/// \end::table_lookup[]
|
||||
|
||||
bool rom_funcs_lookup(uint32_t *table, unsigned int count) {
|
||||
bool ok = true;
|
||||
for (unsigned int i = 0; i < count; i++) {
|
||||
table[i] = (uintptr_t) rom_func_lookup(table[i]);
|
||||
if (!table[i]) ok = false;
|
||||
}
|
||||
return ok;
|
||||
}
|
85
src/rp2_common/pico_bootrom/include/pico/bootrom.h
Normal file
85
src/rp2_common/pico_bootrom/include/pico/bootrom.h
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _PLATFORM_BOOTROM_H
|
||||
#define _PLATFORM_BOOTROM_H
|
||||
|
||||
#include "pico.h"
|
||||
|
||||
/** \file bootrom.h
|
||||
* \defgroup pico_bootrom pico_bootrom
|
||||
* Access to functions and data in the RP2040 bootrom
|
||||
*/
|
||||
|
||||
|
||||
/*! \brief Return a bootrom lookup code based on two ASCII characters
|
||||
* \ingroup pico_bootrom
|
||||
*
|
||||
* These codes are uses to lookup data or function addresses in the bootrom
|
||||
*
|
||||
* \param c1 the first character
|
||||
* \param c2 the second character
|
||||
* \return the 'code' to use in rom_func_lookup() or rom_data_lookup()
|
||||
*/
|
||||
static inline uint32_t rom_table_code(char c1, char c2) {
|
||||
return (c2 << 8u) | c1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Lookup a bootrom function by code
|
||||
* \ingroup pico_bootrom
|
||||
* \param code the code
|
||||
* \return a pointer to the function, or NULL if the code does not match any bootrom function
|
||||
*/
|
||||
void *rom_func_lookup(uint32_t code);
|
||||
|
||||
/*!
|
||||
* \brief Lookup a bootrom address by code
|
||||
* \ingroup pico_bootrom
|
||||
* \param code the code
|
||||
* \return a pointer to the data, or NULL if the code does not match any bootrom function
|
||||
*/
|
||||
void *rom_data_lookup(uint32_t code);
|
||||
|
||||
/*!
|
||||
* \brief Helper function to lookup the addresses of multiple bootrom functions
|
||||
* \ingroup pico_bootrom
|
||||
*
|
||||
* This method looks up the 'codes' in the table, and convert each table entry to the looked up
|
||||
* function pointer, if there is a function for that code in the bootrom.
|
||||
*
|
||||
* \param table an IN/OUT array, elements are codes on input, function pointers on success.
|
||||
* \param count the number of elements in the table
|
||||
* \return true if all the codes were found, and converted to function pointers, false otherwise
|
||||
*/
|
||||
bool rom_funcs_lookup(uint32_t *table, unsigned int count);
|
||||
|
||||
typedef void __attribute__((noreturn)) (*reset_usb_boot_fn)(uint32_t, uint32_t);
|
||||
|
||||
/*!
|
||||
* \brief Reboot the device into BOOTSEL mode
|
||||
* \ingroup pico_bootrom
|
||||
*
|
||||
* This function reboots the device into the BOOTSEL mode ('usb boot").
|
||||
*
|
||||
* Facilities are provided to enable an "activity light" via GPIO attached LED for the USB Mass Storage Device,
|
||||
* and to limit the USB interfaces exposed.
|
||||
*
|
||||
* \param usb_activity_gpio_pin_mask 0 No pins are used as per a cold boot. Otherwise a single bit set indicating which
|
||||
* GPIO pin should be set to output and raised whenever there is mass storage activity
|
||||
* from the host.
|
||||
* \param disable_interface_mask value to control exposed interfaces
|
||||
* - 0 To enable both interfaces (as per a cold boot)
|
||||
* - 1 To disable the USB Mass Storage Interface
|
||||
* - 2 To disable the USB PICOBOOT Interface
|
||||
*/
|
||||
static inline void __attribute__((noreturn)) reset_usb_boot(uint32_t usb_activity_gpio_pin_mask,
|
||||
uint32_t disable_interface_mask) {
|
||||
reset_usb_boot_fn func = (reset_usb_boot_fn) rom_func_lookup(rom_table_code('U', 'B'));
|
||||
func(usb_activity_gpio_pin_mask, disable_interface_mask);
|
||||
}
|
||||
|
||||
#endif
|
50
src/rp2_common/pico_bootrom/include/pico/bootrom/sf_table.h
Normal file
50
src/rp2_common/pico_bootrom/include/pico/bootrom/sf_table.h
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _PICO_SF_TABLE_H
|
||||
#define _PICO_SF_TABLE_H
|
||||
|
||||
// NOTE THESE FUNCTION IMPLEMENTATIONS MATCH THE BEHAVIOR DESCRIBED IN THE BOOTROM SECTION OF THE RP2040 DATASHEET
|
||||
|
||||
#define SF_TABLE_FADD 0x00
|
||||
#define SF_TABLE_FSUB 0x04
|
||||
#define SF_TABLE_FMUL 0x08
|
||||
#define SF_TABLE_FDIV 0x0c
|
||||
#define SF_TABLE_FCMP_FAST 0x10
|
||||
#define SF_TABLE_FCMP_FAST_FLAGS 0x14
|
||||
#define SF_TABLE_FSQRT 0x18
|
||||
#define SF_TABLE_FLOAT2INT 0x1c
|
||||
#define SF_TABLE_FLOAT2FIX 0x20
|
||||
#define SF_TABLE_FLOAT2UINT 0x24
|
||||
#define SF_TABLE_FLOAT2UFIX 0x28
|
||||
#define SF_TABLE_INT2FLOAT 0x2c
|
||||
#define SF_TABLE_FIX2FLOAT 0x30
|
||||
#define SF_TABLE_UINT2FLOAT 0x34
|
||||
#define SF_TABLE_UFIX2FLOAT 0x38
|
||||
#define SF_TABLE_FCOS 0x3c
|
||||
#define SF_TABLE_FSIN 0x40
|
||||
#define SF_TABLE_FTAN 0x44
|
||||
#define SF_TABLE_V3_FSINCOS 0x48
|
||||
#define SF_TABLE_FEXP 0x4c
|
||||
#define SF_TABLE_FLN 0x50
|
||||
|
||||
#define SF_TABLE_V1_SIZE 0x54
|
||||
|
||||
#define SF_TABLE_FCMP_BASIC 0x54
|
||||
#define SF_TABLE_FATAN2 0x58
|
||||
#define SF_TABLE_INT642FLOAT 0x5c
|
||||
#define SF_TABLE_FIX642FLOAT 0x60
|
||||
#define SF_TABLE_UINT642FLOAT 0x64
|
||||
#define SF_TABLE_UFIX642FLOAT 0x68
|
||||
#define SF_TABLE_FLOAT2INT64 0x6c
|
||||
#define SF_TABLE_FLOAT2FIX64 0x70
|
||||
#define SF_TABLE_FLOAT2UINT64 0x74
|
||||
#define SF_TABLE_FLOAT2UFIX64 0x78
|
||||
#define SF_TABLE_FLOAT2DOUBLE 0x7c
|
||||
|
||||
#define SF_TABLE_V2_SIZE 0x80
|
||||
|
||||
#endif
|
23
src/rp2_common/pico_cxx_options/CMakeLists.txt
Normal file
23
src/rp2_common/pico_cxx_options/CMakeLists.txt
Normal file
@ -0,0 +1,23 @@
|
||||
if (NOT TARGET pico_cxx_options)
|
||||
add_library(pico_cxx_options INTERFACE)
|
||||
|
||||
# PICO_CMAKE_CONFIG: PICO_CXX_ENABLE_EXCEPTIONS, Enabled CXX exception handling, type=bool, default=0, group=pico_cxx_options
|
||||
# PICO_BUILD_DEFINE: PICO_CXX_ENABLE_EXCEPTIONS, value of CMake var PICO_CXX_ENABLE_EXCEPTIONS, type=string, default=0, group=pico_cxx_options
|
||||
if (NOT PICO_CXX_ENABLE_EXCEPTIONS)
|
||||
target_compile_definitions( pico_cxx_options INTERFACE PICO_CXX_ENABLE_EXCEPTIONS=0)
|
||||
target_compile_options( pico_cxx_options INTERFACE $<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>)
|
||||
target_compile_options( pico_cxx_options INTERFACE $<$<COMPILE_LANGUAGE:CXX>:-fno-unwind-tables>)
|
||||
else()
|
||||
target_compile_definitions( pico_cxx_options INTERFACE PICO_CXX_ENABLE_EXCEPTIONS=1)
|
||||
endif()
|
||||
|
||||
# PICO_CMAKE_CONFIG: PICO_CXX_ENABLE_RTTI, Enabled CXX rtti, type=bool, default=0, group=pico_cxx_options
|
||||
if (NOT PICO_CXX_ENABLE_RTTI)
|
||||
target_compile_options( pico_cxx_options INTERFACE $<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>)
|
||||
endif()
|
||||
|
||||
# PICO_CMAKE_CONFIG: PICO_CXX_ENABLE_CXA_ATEXIT, Enabled cxa-atexit, type=bool, default=0, group=pico_cxx_options
|
||||
if (NOT PICO_CXX_ENABLE_CXA_ATEXIT)
|
||||
target_compile_options( pico_cxx_options INTERFACE $<$<COMPILE_LANGUAGE:CXX>:-fno-use-cxa-atexit>)
|
||||
endif()
|
||||
endif()
|
4
src/rp2_common/pico_cxx_options/doc.h
Normal file
4
src/rp2_common/pico_cxx_options/doc.h
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* \defgroup pico_cxx_options pico_cxx_options
|
||||
* \brief non-code library controlling C++ related compile options
|
||||
*/
|
52
src/rp2_common/pico_divider/CMakeLists.txt
Normal file
52
src/rp2_common/pico_divider/CMakeLists.txt
Normal file
@ -0,0 +1,52 @@
|
||||
if (NOT TARGET pico_divider)
|
||||
# library to be depended on - we make this depend on particular implementations using per target generator expressions
|
||||
add_library(pico_divider INTERFACE)
|
||||
|
||||
# no custom implementation; falls thru to compiler
|
||||
add_library(pico_divider_compiler INTERFACE)
|
||||
target_compile_definitions(pico_divider_compiler INTERFACE
|
||||
PICO_DIVIDER_COMPILER=1
|
||||
)
|
||||
|
||||
# add alias "default" which is just hardware.
|
||||
add_library(pico_divider_default INTERFACE)
|
||||
target_link_libraries(pico_divider_default INTERFACE pico_divider_hardware)
|
||||
|
||||
set(PICO_DEFAULT_DIVIDER_IMPL pico_divider_default)
|
||||
|
||||
target_link_libraries(pico_divider INTERFACE
|
||||
$<IF:$<BOOL:$<TARGET_PROPERTY:PICO_TARGET_DIVIDER_IMPL>>,$<TARGET_PROPERTY:PICO_TARGET_DIVIDER_IMPL>,${PICO_DEFAULT_DIVIDER_IMPL}>)
|
||||
|
||||
add_library(pico_divider_hardware_explicit INTERFACE)
|
||||
target_sources(pico_divider_hardware_explicit INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/divider.S
|
||||
)
|
||||
|
||||
target_link_libraries(pico_divider_hardware_explicit INTERFACE
|
||||
pico_divider_headers
|
||||
hardware_regs
|
||||
)
|
||||
|
||||
add_library(pico_divider_hardware INTERFACE)
|
||||
target_compile_definitions(pico_divider_hardware INTERFACE
|
||||
PICO_DIVIDER_HARDWARE=1
|
||||
)
|
||||
|
||||
target_link_libraries(pico_divider_hardware INTERFACE pico_divider_hardware_explicit)
|
||||
|
||||
pico_wrap_function(pico_divider_hardware __aeabi_idiv)
|
||||
pico_wrap_function(pico_divider_hardware __aeabi_idivmod)
|
||||
pico_wrap_function(pico_divider_hardware __aeabi_ldivmod)
|
||||
pico_wrap_function(pico_divider_hardware __aeabi_uidiv)
|
||||
pico_wrap_function(pico_divider_hardware __aeabi_uidivmod)
|
||||
pico_wrap_function(pico_divider_hardware __aeabi_uldivmod)
|
||||
|
||||
macro(pico_set_divider_implementation TARGET IMPL)
|
||||
get_target_property(target_type ${TARGET} TYPE)
|
||||
if ("EXECUTABLE" STREQUAL "${target_type}")
|
||||
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_DIVIDER_IMPL "pico_divider_${IMPL}")
|
||||
else()
|
||||
message(FATAL_ERROR "divider implementation must be set on executable not library")
|
||||
endif()
|
||||
endmacro()
|
||||
endif()
|
863
src/rp2_common/pico_divider/divider.S
Normal file
863
src/rp2_common/pico_divider/divider.S
Normal file
@ -0,0 +1,863 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/regs/sio.h"
|
||||
#include "hardware/regs/addressmap.h"
|
||||
|
||||
.syntax unified
|
||||
.cpu cortex-m0plus
|
||||
.thumb
|
||||
|
||||
#include "pico/asm_helper.S"
|
||||
|
||||
#ifndef PICO_DIVIDER_CALL_IDIV0
|
||||
#define PICO_DIVIDER_CALL_IDIV0 1
|
||||
#endif
|
||||
|
||||
#ifndef PICO_DIVIDER_CALL_LDIV0
|
||||
#define PICO_DIVIDER_CALL_LDIV0 1
|
||||
#endif
|
||||
|
||||
.macro div_section name
|
||||
#if PICO_DIVIDER_IN_RAM
|
||||
.section RAM_SECTION_NAME(\name), "ax"
|
||||
#else
|
||||
.section SECTION_NAME(\name), "ax"
|
||||
#endif
|
||||
.endm
|
||||
|
||||
#if SIO_DIV_CSR_READY_LSB == 0
|
||||
.equ SIO_DIV_CSR_READY_SHIFT_FOR_CARRY, 1
|
||||
#else
|
||||
need to change SHIFT above
|
||||
#endif
|
||||
#if SIO_DIV_CSR_DIRTY_LSB == 1
|
||||
.equ SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY, 2
|
||||
#else
|
||||
need to change SHIFT above
|
||||
#endif
|
||||
|
||||
@ wait 8-n cycles for the hardware divider
|
||||
.macro wait_div n
|
||||
.rept (8-\n) / 2
|
||||
b 9f
|
||||
9:
|
||||
.endr
|
||||
.if (8-\n) % 2
|
||||
nop
|
||||
.endif
|
||||
.endm
|
||||
|
||||
|
||||
#if (SIO_DIV_SDIVISOR_OFFSET != SIO_DIV_SDIVIDEND_OFFSET + 4) || (SIO_DIV_QUOTIENT_OFFSET != SIO_DIV_SDIVISOR_OFFSET + 4) || (SIO_DIV_REMAINDER_OFFSET != SIO_DIV_QUOTIENT_OFFSET + 4)
|
||||
#error register layout has changed - we rely on this order to make sure we save/restore in the right order
|
||||
#endif
|
||||
|
||||
# SIO_BASE ptr in r2
|
||||
.macro save_div_state_and_lr
|
||||
ldr r3, [r2, #SIO_DIV_CSR_OFFSET]
|
||||
# wait for results as we can't save signed-ness of operation
|
||||
1:
|
||||
lsrs r3, #SIO_DIV_CSR_READY_SHIFT_FOR_CARRY
|
||||
bcc 1b
|
||||
push {r4, r5, r6, r7, lr}
|
||||
// note we must read quotient last, and since it isn't the last reg, we'll not use ldmia!
|
||||
ldr r4, [r2, #SIO_DIV_SDIVIDEND_OFFSET]
|
||||
ldr r5, [r2, #SIO_DIV_SDIVISOR_OFFSET]
|
||||
ldr r7, [r2, #SIO_DIV_REMAINDER_OFFSET]
|
||||
ldr r6, [r2, #SIO_DIV_QUOTIENT_OFFSET]
|
||||
.endm
|
||||
|
||||
.macro restore_div_state_and_return
|
||||
// writing sdividend (r4), sdivisor (r5), quotient (r6), remainder (r7) in that order
|
||||
//
|
||||
// it is worth considering what happens if we are interrupted
|
||||
//
|
||||
// after writing r4: we are DIRTY and !READY
|
||||
// ... interruptor using div will complete based on incorrect inputs, but dividend at least will be
|
||||
// saved/restored correctly and we'll restore the rest ourselves
|
||||
// after writing r4, r5: we are DIRTY and !READY
|
||||
// ... interruptor using div will complete based on possibly wrongly signed inputs, but dividend, divisor
|
||||
// at least will be saved/restored correctly and and we'll restore the rest ourselves
|
||||
// after writing r4, r5, r6: we are DIRTY and READY
|
||||
// ... interruptor using div will dividend, divisor, quotient registers as is (what we just restored ourselves),
|
||||
// and we'll restore the remainder after the fact
|
||||
|
||||
// note we are not use STM not because it can be restarted due to interrupt which is harmless, more because this is 1 cycle IO space
|
||||
// and so 4 reads is cheaper (and we don't have to adjust r2)
|
||||
str r4, [r2, #SIO_DIV_SDIVIDEND_OFFSET]
|
||||
str r5, [r2, #SIO_DIV_SDIVISOR_OFFSET]
|
||||
str r7, [r2, #SIO_DIV_REMAINDER_OFFSET]
|
||||
str r6, [r2, #SIO_DIV_QUOTIENT_OFFSET]
|
||||
pop {r4, r5, r6, r7, pc}
|
||||
.endm
|
||||
|
||||
.macro save_div_state_and_lr_64
|
||||
push {r4, r5, r6, r7, lr}
|
||||
ldr r6, =SIO_BASE
|
||||
1:
|
||||
ldr r5, [r6, #SIO_DIV_CSR_OFFSET]
|
||||
# wait for results as we can't save signed-ness of operation
|
||||
lsrs r5, #SIO_DIV_CSR_READY_SHIFT_FOR_CARRY
|
||||
bcc 1b
|
||||
// note we must read quotient last, and since it isn't the last reg, we'll not use ldmia!
|
||||
ldr r4, [r6, #SIO_DIV_UDIVIDEND_OFFSET]
|
||||
ldr r5, [r6, #SIO_DIV_UDIVISOR_OFFSET]
|
||||
ldr r7, [r6, #SIO_DIV_REMAINDER_OFFSET]
|
||||
ldr r6, [r6, #SIO_DIV_QUOTIENT_OFFSET]
|
||||
.endm
|
||||
|
||||
.macro restore_div_state_and_return_64
|
||||
// writing sdividend (r4), sdivisor (r5), quotient (r6), remainder (r7) in that order
|
||||
//
|
||||
// it is worth considering what happens if we are interrupted
|
||||
//
|
||||
// after writing r4: we are DIRTY and !READY
|
||||
// ... interruptor using div will complete based on incorrect inputs, but dividend at least will be
|
||||
// saved/restored correctly and we'll restore the rest ourselves
|
||||
// after writing r4, r5: we are DIRTY and !READY
|
||||
// ... interruptor using div will complete based on possibly wrongly signed inputs, but dividend, divisor
|
||||
// at least will be saved/restored correctly and and we'll restore the rest ourselves
|
||||
// after writing r4, r5, r6: we are DIRTY and READY
|
||||
// ... interruptor using div will dividend, divisor, quotient registers as is (what we just restored ourselves),
|
||||
// and we'll restore the remainder after the fact
|
||||
|
||||
mov ip, r2
|
||||
ldr r2, =SIO_BASE
|
||||
// note we are not use STM not because it can be restarted due to interrupt which is harmless, more because this is 1 cycle IO space
|
||||
// and so 4 reads is cheaper (and we don't have to adjust r2)
|
||||
str r4, [r2, #SIO_DIV_UDIVIDEND_OFFSET]
|
||||
str r5, [r2, #SIO_DIV_UDIVISOR_OFFSET]
|
||||
str r7, [r2, #SIO_DIV_REMAINDER_OFFSET]
|
||||
str r6, [r2, #SIO_DIV_QUOTIENT_OFFSET]
|
||||
mov r2, ip
|
||||
pop {r4, r5, r6, r7, pc}
|
||||
.endm
|
||||
|
||||
|
||||
// since idiv and idivmod only differ by a cycle, we'll make them the same!
|
||||
div_section WRAPPER_FUNC_NAME(__aeabi_idiv)
|
||||
.align 2
|
||||
wrapper_func __aeabi_idiv
|
||||
wrapper_func __aeabi_idivmod
|
||||
regular_func div_s32s32
|
||||
regular_func divmod_s32s32
|
||||
ldr r2, =(SIO_BASE)
|
||||
# to support IRQ usage we must save/restore
|
||||
ldr r3, [r2, #SIO_DIV_CSR_OFFSET]
|
||||
lsrs r3, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
|
||||
bcs divmod_s32s32_savestate
|
||||
regular_func divmod_s32s32_unsafe
|
||||
str r0, [r2, #SIO_DIV_SDIVIDEND_OFFSET]
|
||||
str r1, [r2, #SIO_DIV_SDIVISOR_OFFSET]
|
||||
cmp r1, #0
|
||||
beq 1f
|
||||
wait_div 2
|
||||
// return 64 bit value so we can efficiently return both (note read order is important since QUOTIENT must be read last)
|
||||
ldr r1, [r2, #SIO_DIV_REMAINDER_OFFSET]
|
||||
ldr r0, [r2, #SIO_DIV_QUOTIENT_OFFSET]
|
||||
bx lr
|
||||
1:
|
||||
push {r2, lr}
|
||||
movs r1, #0x80
|
||||
lsls r1, #24
|
||||
asrs r2, r0, #31
|
||||
eors r1, r2
|
||||
cmp r0, #0
|
||||
beq 1f
|
||||
mvns r0, r1
|
||||
1:
|
||||
#if PICO_DIVIDER_CALL_IDIV0
|
||||
bl __aeabi_idiv0
|
||||
#endif
|
||||
movs r1, #0 // remainder 0
|
||||
// need to restore saved r2 as it hold SIO ptr
|
||||
pop {r2, pc}
|
||||
.align 2
|
||||
regular_func divmod_s32s32_savestate
|
||||
save_div_state_and_lr
|
||||
bl divmod_s32s32_unsafe
|
||||
restore_div_state_and_return
|
||||
|
||||
// since uidiv and uidivmod only differ by a cycle, we'll make them the same!
|
||||
div_section WRAPPER_FUNC_NAME(__aeabi_uidiv)
|
||||
regular_func div_u32u32
|
||||
regular_func divmod_u32u32
|
||||
wrapper_func __aeabi_uidiv
|
||||
wrapper_func __aeabi_uidivmod
|
||||
ldr r2, =(SIO_BASE)
|
||||
# to support IRQ usage we must save/restore
|
||||
ldr r3, [r2, #SIO_DIV_CSR_OFFSET]
|
||||
lsrs r3, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
|
||||
bcs divmod_u32u32_savestate
|
||||
regular_func divmod_u32u32_unsafe
|
||||
str r0, [r2, #SIO_DIV_UDIVIDEND_OFFSET]
|
||||
str r1, [r2, #SIO_DIV_UDIVISOR_OFFSET]
|
||||
cmp r1, #0
|
||||
beq 1f
|
||||
wait_div 2
|
||||
// return 64 bit value so we can efficiently return both (note read order is important since QUOTIENT must be read last)
|
||||
ldr r1, [r2, #SIO_DIV_REMAINDER_OFFSET]
|
||||
ldr r0, [r2, #SIO_DIV_QUOTIENT_OFFSET]
|
||||
bx lr
|
||||
1:
|
||||
push {r2, lr}
|
||||
cmp r0, #0
|
||||
beq 1f
|
||||
movs r0, #0
|
||||
mvns r0, r0
|
||||
1:
|
||||
#if PICO_DIVIDER_CALL_IDIV0
|
||||
bl __aeabi_idiv0
|
||||
#endif
|
||||
movs r1, #0 // remainder 0
|
||||
// need to restore saved r2 as it hold SIO ptr
|
||||
pop {r2, pc}
|
||||
.align 2
|
||||
regular_func divmod_u32u32_savestate
|
||||
save_div_state_and_lr
|
||||
bl divmod_u32u32_unsafe
|
||||
restore_div_state_and_return
|
||||
|
||||
div_section WRAPPER_FUNC_NAME(__aeabi_ldiv)
|
||||
|
||||
.align 2
|
||||
wrapper_func __aeabi_ldivmod
|
||||
regular_func div_s64s64
|
||||
regular_func divmod_s64s64
|
||||
mov ip, r2
|
||||
ldr r2, =(SIO_BASE)
|
||||
# to support IRQ usage we must save/restore
|
||||
ldr r2, [r2, #SIO_DIV_CSR_OFFSET]
|
||||
lsrs r2, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
|
||||
mov r2, ip
|
||||
bcs divmod_s64s64_savestate
|
||||
b divmod_s64s64_unsafe
|
||||
.align 2
|
||||
divmod_s64s64_savestate:
|
||||
save_div_state_and_lr_64
|
||||
bl divmod_s64s64_unsafe
|
||||
restore_div_state_and_return_64
|
||||
|
||||
.align 2
|
||||
wrapper_func __aeabi_uldivmod
|
||||
regular_func div_u64u64
|
||||
regular_func divmod_u64u64
|
||||
mov ip, r2
|
||||
ldr r2, =(SIO_BASE)
|
||||
# to support IRQ usage we must save/restore
|
||||
ldr r2, [r2, #SIO_DIV_CSR_OFFSET]
|
||||
lsrs r2, #SIO_DIV_CSR_DIRTY_SHIFT_FOR_CARRY
|
||||
mov r2, ip
|
||||
bcs divmod_u64u64_savestate
|
||||
b divmod_u64u64_unsafe
|
||||
.align 2
|
||||
regular_func divmod_u64u64_savestate
|
||||
save_div_state_and_lr_64
|
||||
bl divmod_u64u64_unsafe
|
||||
restore_div_state_and_return_64
|
||||
.macro dneg lo,hi
|
||||
mvns \hi,\hi
|
||||
rsbs \lo,#0
|
||||
bne l\@_1
|
||||
adds \hi,#1
|
||||
l\@_1:
|
||||
.endm
|
||||
|
||||
.align 2
|
||||
regular_func divmod_s64s64_unsafe
|
||||
cmp r3,#0
|
||||
blt 1f
|
||||
@ here x +ve
|
||||
beq 2f @ could x be zero?
|
||||
3:
|
||||
cmp r1,#0
|
||||
bge divmod_u64u64_unsafe @ both positive
|
||||
@ y -ve, x +ve
|
||||
push {r14}
|
||||
dneg r0,r1
|
||||
bl divmod_u64u64_unsafe
|
||||
dneg r0,r1
|
||||
dneg r2,r3
|
||||
pop {r15}
|
||||
|
||||
2:
|
||||
cmp r2,#0
|
||||
bne 3b @ back if x not zero
|
||||
|
||||
cmp r0,#0 @ y==0?
|
||||
bne 4f
|
||||
cmp r1,#0
|
||||
beq 5f @ then pass 0 to __aeabi_ldiv0
|
||||
4:
|
||||
movs r0,#0
|
||||
lsrs r1,#31
|
||||
lsls r1,#31 @ get sign bit
|
||||
bne 5f @ y -ve? pass -2^63 to __aeabi_ldiv0
|
||||
mvns r0,r0
|
||||
lsrs r1,r0,#1 @ y +ve: pass 2^63-1 to __aeabi_ldiv0
|
||||
5:
|
||||
push {r14}
|
||||
#if PICO_DIVIDER_CALL_LDIV0
|
||||
bl __aeabi_ldiv0
|
||||
#endif
|
||||
movs r2,#0 @ and return 0 for the remainder
|
||||
movs r3,#0
|
||||
pop {r15}
|
||||
|
||||
1:
|
||||
@ here x -ve
|
||||
push {r14}
|
||||
cmp r1,#0
|
||||
blt 1f
|
||||
@ y +ve, x -ve
|
||||
dneg r2,r3
|
||||
bl divmod_u64u64_unsafe
|
||||
dneg r0,r1
|
||||
pop {r15}
|
||||
|
||||
1:
|
||||
@ y -ve, x -ve
|
||||
dneg r0,r1
|
||||
dneg r2,r3
|
||||
bl divmod_u64u64_unsafe
|
||||
dneg r2,r3
|
||||
pop {r15}
|
||||
|
||||
regular_func divmod_u64u64_unsafe
|
||||
cmp r1,#0
|
||||
bne y64 @ y fits in 32 bits?
|
||||
cmp r3,#0 @ yes; and x?
|
||||
bne 1f
|
||||
cmp r2,#0
|
||||
beq 2f @ x==0?
|
||||
mov r12,r7
|
||||
ldr r7,=#SIO_BASE
|
||||
str r0,[r7,#SIO_DIV_UDIVIDEND_OFFSET]
|
||||
str r2,[r7,#SIO_DIV_UDIVISOR_OFFSET]
|
||||
movs r1,#0
|
||||
movs r3,#0
|
||||
wait_div 2
|
||||
ldr r2,[r7,#SIO_DIV_REMAINDER_OFFSET]
|
||||
ldr r0,[r7,#SIO_DIV_QUOTIENT_OFFSET]
|
||||
mov r7,r12
|
||||
bx r14
|
||||
|
||||
2: @ divide by 0 with y<2^32
|
||||
cmp r0,#0 @ y==0?
|
||||
beq 3f @ then pass 0 to __aeabi_ldiv0
|
||||
udiv0:
|
||||
ldr r0,=#0xffffffff
|
||||
movs r1,r0 @ pass 2^64-1 to __aeabi_ldiv0
|
||||
3:
|
||||
push {r14}
|
||||
#if PICO_DIVIDER_CALL_LDIV0
|
||||
bl __aeabi_ldiv0
|
||||
#endif
|
||||
movs r2,#0 @ and return 0 for the remainder
|
||||
movs r3,#0
|
||||
pop {r15}
|
||||
|
||||
1:
|
||||
movs r2,r0 @ x>y, so result is 0 remainder y
|
||||
movs r3,r1
|
||||
movs r0,#0
|
||||
movs r1,#0
|
||||
bx r14
|
||||
|
||||
.ltorg
|
||||
|
||||
@ here y occupies more than 32 bits
|
||||
@ split into cases acccording to the size of x
|
||||
y64:
|
||||
cmp r3,#0
|
||||
beq 1f
|
||||
b y64_x48 @ if x does not fit in 32 bits, go to 48- and 64-bit cases
|
||||
1:
|
||||
lsrs r3,r2,#16
|
||||
bne y64_x32 @ jump if x is 17..32 bits
|
||||
|
||||
@ here x is at most 16 bits
|
||||
|
||||
cmp r2,#0
|
||||
beq udiv0 @ x==0? exit as with y!=0 case above
|
||||
push {r7}
|
||||
ldr r7,=#SIO_BASE
|
||||
str r1,[r7,#SIO_DIV_UDIVIDEND_OFFSET]
|
||||
str r2,[r7,#SIO_DIV_UDIVISOR_OFFSET]
|
||||
wait_div 4
|
||||
push {r4, r5}
|
||||
lsrs r4,r0,#16
|
||||
ldr r3,[r7,#SIO_DIV_REMAINDER_OFFSET] @ r0=y0-q0*x; 0<=r0<x
|
||||
ldr r1,[r7,#SIO_DIV_QUOTIENT_OFFSET] @ q0=y0/x;
|
||||
lsls r3,#16
|
||||
orrs r3,r4
|
||||
str r3,[r7,#SIO_DIV_UDIVIDEND_OFFSET] @ y1=(r0<<16)+(((ui32)y)>>16);
|
||||
wait_div 1
|
||||
uxth r4,r0
|
||||
ldr r3,[r7,#SIO_DIV_REMAINDER_OFFSET] @ r1=y1-q1*x; 0<=r1<x
|
||||
ldr r5,[r7,#SIO_DIV_QUOTIENT_OFFSET] @ q1=y1/x;
|
||||
lsls r3,#16
|
||||
orrs r3,r4
|
||||
str r3,[r7,#SIO_DIV_UDIVIDEND_OFFSET] @ y1=(r0<<16)+(((ui32)y)>>16);
|
||||
wait_div 3
|
||||
movs r3,#0
|
||||
lsls r4,r5,#16 @ quotient=(q0<<32)+(q1<<16)+q2
|
||||
lsrs r5,#16
|
||||
ldr r2,[r7,#SIO_DIV_REMAINDER_OFFSET] @ r2=y2-q2*x; 0<=r2<x
|
||||
ldr r0,[r7,#SIO_DIV_QUOTIENT_OFFSET] @ q2=y2/x;
|
||||
adds r0,r4
|
||||
adcs r1,r5
|
||||
pop {r4,r5,r7}
|
||||
bx r14
|
||||
|
||||
.ltorg
|
||||
|
||||
y64_x32:
|
||||
@ here x is 17..32 bits
|
||||
push {r4-r7,r14}
|
||||
mov r12,r2 @ save x
|
||||
movs r5,#0 @ xsh=0
|
||||
lsrs r4,r2,#24
|
||||
bne 1f
|
||||
lsls r2,#8 @ if(x0<1U<<24) x0<<=8,xsh =8;
|
||||
adds r5,#8
|
||||
1:
|
||||
lsrs r4,r2,#28
|
||||
bne 1f
|
||||
lsls r2,#4 @ if(x0<1U<<28) x0<<=4,xsh+=4;
|
||||
adds r5,#4
|
||||
1:
|
||||
lsrs r4,r2,#30
|
||||
bne 1f
|
||||
lsls r2,#2 @ if(x0<1U<<30) x0<<=2,xsh+=2;
|
||||
adds r5,#2
|
||||
1:
|
||||
lsrs r4,r2,#31
|
||||
bne 1f
|
||||
lsls r2,#1 @ if(x0<1U<<31) x0<<=1,xsh+=1;
|
||||
adds r5,#1
|
||||
1:
|
||||
@ now 2^31<=x0<2^32, 0<=xsh<16 (amount x is shifted in x0); number of quotient bits to be calculated qb=xsh+33 33<=qb<49
|
||||
lsrs r4,r2,#15
|
||||
adds r4,#1 @ x1=(x0>>15)+1; 2^16<x1<=2^17
|
||||
|
||||
ldr r7,=#SIO_BASE
|
||||
str r4,[r7,#SIO_DIV_UDIVISOR_OFFSET]
|
||||
ldr r4,=#0xffffffff
|
||||
str r4,[r7,#SIO_DIV_UDIVIDEND_OFFSET]
|
||||
lsrs r6,r1,#16
|
||||
uxth r3,r2 @ x0l
|
||||
wait_div 2
|
||||
ldr r4,[r7,#SIO_DIV_QUOTIENT_OFFSET] @ r=0xffffffffU/x1; 2^15<=r<2^16 r is a normalised reciprocal of x, guaranteed not an overestimate
|
||||
|
||||
@ here
|
||||
@ r0:r1 y
|
||||
@ r2 x0
|
||||
@ r4 r
|
||||
@ r5 xsh
|
||||
@ r12 x
|
||||
|
||||
muls r6,r4
|
||||
lsrs r6,#16 @ q=((ui32)(y>>48)*r)>>16;
|
||||
lsls r7,r6,#13
|
||||
mov r14,r7 @ quh=q0<<13
|
||||
|
||||
muls r3,r6 @ x0l*q
|
||||
lsrs r7,r3,#15
|
||||
lsls r3,#17 @ r3:r7 is (x0l*q)<<17
|
||||
subs r0,r3
|
||||
sbcs r1,r7 @ y-=(x0l*q)<<17
|
||||
|
||||
lsrs r3,r2,#16 @ x0h
|
||||
muls r3,r6 @ q*x0h
|
||||
adds r3,r3
|
||||
subs r1,r3 @ y-=(x0h*q)<<17
|
||||
|
||||
lsrs r6,r1,#3
|
||||
muls r6,r4
|
||||
lsrs r6,#16 @ q=((ui32)(y>>35)*r)>>16;
|
||||
add r14,r6 @ quh+=q1
|
||||
|
||||
uxth r3,r2 @ x0l
|
||||
muls r3,r6 @ x0l*q
|
||||
lsrs r7,r3,#28
|
||||
lsls r3,#4 @ r3:r7 is (x0l*q)<<4
|
||||
subs r0,r3
|
||||
sbcs r1,r7 @ y-=(x0l*q)<<4
|
||||
|
||||
lsrs r3,r2,#16 @ x0h
|
||||
muls r3,r6 @ x0h*q
|
||||
lsrs r7,r3,#12
|
||||
lsls r3,#20 @ r3:r7 is (x0h*q)<<4
|
||||
subs r0,r3
|
||||
sbcs r1,r7 @ y-=(x0h*q)<<4
|
||||
|
||||
lsrs r6,r0,#22
|
||||
lsls r7,r1,#10
|
||||
orrs r6,r7 @ y>>22
|
||||
muls r6,r4
|
||||
lsrs r6,#16 @ q=((ui32)(y>>22)*r)>>16;
|
||||
|
||||
cmp r5,#9
|
||||
blt last0 @ if(xsh<9) goto last0;
|
||||
|
||||
@ on this path xsh>=9, which means x<2^23
|
||||
lsrs r2,#9 @ x0>>9: this shift loses no bits
|
||||
@ the remainder y-x0*q is guaranteed less than a very small multiple of the remaining quotient
|
||||
@ bits (at most 6 bits) times x, and so fits in one word
|
||||
muls r2,r6 @ x0*q
|
||||
subs r0,r2 @ y-x0*q
|
||||
lsls r7,r6,#13 @ qul=q<<13
|
||||
1:
|
||||
lsrs r6,r0,#9
|
||||
muls r6,r4
|
||||
lsrs r6,#16 @ q=((ui32)(y>>9)*r)>>16;
|
||||
|
||||
@ here
|
||||
@ r0 y
|
||||
@ r2 x0>>9
|
||||
@ r5 xsh
|
||||
@ r6 q
|
||||
@ r7 qul
|
||||
@ r12 x
|
||||
@ r14 quh
|
||||
|
||||
movs r3,#22
|
||||
subs r3,r5 @ 22-xsh
|
||||
lsrs r6,r3 @ q>>=22-xsh
|
||||
lsrs r7,r3 @ qul>>=22-xsh
|
||||
adds r7,r6 @ qul+=q
|
||||
mov r4,r12
|
||||
muls r6,r4 @ x*q
|
||||
subs r2,r0,r6 @ y-=x*q
|
||||
mov r0,r14 @ quh
|
||||
adds r5,#4 @ xsh+4
|
||||
adds r3,#6 @ 28-xsh
|
||||
movs r1,r0
|
||||
lsrs r1,r3
|
||||
lsls r0,r5 @ r0:r1 is quh<<(4+xsh)
|
||||
adds r0,r7
|
||||
bcc 1f
|
||||
2:
|
||||
adds r1,#1
|
||||
1: @ qu=((ui64)quh<<(4+xsh))+qul
|
||||
cmp r2,r4
|
||||
bhs 3f
|
||||
movs r3,#0
|
||||
pop {r4-r7,r15}
|
||||
|
||||
.ltorg
|
||||
|
||||
3:
|
||||
subs r2,r4
|
||||
adds r0,#1
|
||||
bcc 1b
|
||||
b 2b @ while(y>=x) y-=x,qu++;
|
||||
|
||||
@ here:
|
||||
@ r0:r1 y
|
||||
@ r2 x0
|
||||
@ r4 r
|
||||
@ r5 xsh; xsh<9
|
||||
@ r6 q
|
||||
|
||||
last0:
|
||||
movs r7,#9
|
||||
subs r7,r5 @ 9-xsh
|
||||
lsrs r6,r7
|
||||
mov r4,r12 @ x
|
||||
uxth r2,r4
|
||||
muls r2,r6 @ q*xlo
|
||||
subs r0,r2
|
||||
bcs 1f
|
||||
subs r1,#1 @ y-=q*xlo
|
||||
1:
|
||||
lsrs r2,r4,#16 @ xhi
|
||||
muls r2,r6 @ q*xhi
|
||||
lsrs r3,r2,#16
|
||||
lsls r2,#16
|
||||
subs r2,r0,r2
|
||||
sbcs r1,r3 @ y-q*xhi
|
||||
movs r3,r1 @ y now in r2:r3
|
||||
mov r0,r14 @ quh
|
||||
adds r5,#4 @ xsh+4
|
||||
adds r7,#19 @ 28-xsh
|
||||
movs r1,r0
|
||||
lsrs r1,r7
|
||||
lsls r0,r5 @ r0:r1 is quh<<(4+xsh)
|
||||
adds r0,r6
|
||||
bcc 1f
|
||||
adds r1,#1 @ quh<<(xsh+4))+q
|
||||
1:
|
||||
cmp r3,#0 @ y>=2^32?
|
||||
bne 3f
|
||||
cmp r2,r4 @ y>=x?
|
||||
bhs 4f
|
||||
pop {r4-r7,r15}
|
||||
|
||||
3:
|
||||
adds r0,#1 @ qu++
|
||||
bcc 2f
|
||||
adds r1,#1
|
||||
2:
|
||||
subs r2,r4 @ y-=x
|
||||
bcs 3b
|
||||
subs r3,#1
|
||||
bne 3b
|
||||
|
||||
1:
|
||||
cmp r2,r4
|
||||
bhs 4f
|
||||
pop {r4-r7,r15}
|
||||
|
||||
4:
|
||||
adds r0,#1 @ qu++
|
||||
bcc 2f
|
||||
adds r1,#1
|
||||
2:
|
||||
subs r2,r4 @ y-=x
|
||||
b 1b
|
||||
|
||||
y64_x48:
|
||||
@ here x is 33..64 bits
|
||||
push {r4-r7,r14} @ save a copy of x
|
||||
lsrs r4,r3,#16
|
||||
beq 1f
|
||||
b y64_x64 @ jump if x is 49..64 bits
|
||||
1:
|
||||
push {r2-r3} @ save a copy of x
|
||||
@ here x is 33..48 bits
|
||||
movs r5,#0 @ xsh=0
|
||||
lsrs r4,r3,#8
|
||||
bne 1f
|
||||
lsls r3,#8
|
||||
lsrs r6,r2,#24
|
||||
orrs r3,r6
|
||||
lsls r2,#8 @ if(x0<1U<<40) x0<<=8,xsh =8;
|
||||
adds r5,#8
|
||||
1:
|
||||
lsrs r4,r3,#12
|
||||
bne 1f
|
||||
lsls r3,#4
|
||||
lsrs r6,r2,#28
|
||||
orrs r3,r6
|
||||
lsls r2,#4 @ if(x0<1U<<44) x0<<=4,xsh+=4;
|
||||
adds r5,#4
|
||||
1:
|
||||
lsrs r4,r3,#14
|
||||
bne 1f
|
||||
lsls r3,#2
|
||||
lsrs r6,r2,#30
|
||||
orrs r3,r6
|
||||
lsls r2,#2 @ if(x0<1U<<46) x0<<=2,xsh+=2;
|
||||
adds r5,#2
|
||||
1:
|
||||
lsrs r4,r3,#15
|
||||
bne 1f
|
||||
adds r2,r2
|
||||
adcs r3,r3 @ if(x0<1U<<47) x0<<=1,xsh+=1;
|
||||
adds r5,#1
|
||||
1:
|
||||
@ now 2^47<=x0<2^48, 0<=xsh<16 (amount x is shifted in x0); number of quotient bits to be calculated qb=xsh+17 17<=qb<33
|
||||
movs r4,r3
|
||||
adds r7,r2,r2
|
||||
adcs r4,r4
|
||||
adds r4,#1 @ x1=(ui32)(x0>>31)+1; // 2^16<x1<=2^17
|
||||
|
||||
ldr r7,=#SIO_BASE
|
||||
str r4,[r7,#SIO_DIV_UDIVISOR_OFFSET]
|
||||
ldr r4,=#0xffffffff
|
||||
str r4,[r7,#SIO_DIV_UDIVIDEND_OFFSET]
|
||||
lsrs r6,r1,#16
|
||||
wait_div 1
|
||||
ldr r4,[r7,#SIO_DIV_QUOTIENT_OFFSET] @ r=0xffffffffU/x1; 2^15<=r<2^16 r is a normalised reciprocal of x, guaranteed not an overestimate
|
||||
|
||||
@ here
|
||||
@ r0:r1 y
|
||||
@ r2:r3 x0
|
||||
@ r4 r
|
||||
@ r5 xsh 0<=xsh<16
|
||||
|
||||
muls r6,r4
|
||||
lsrs r6,#16 @ q=((ui32)(y>>48)*r)>>16;
|
||||
lsls r7,r6,#13
|
||||
mov r14,r7 @ save q<<13
|
||||
uxth r7,r2 @ x0l
|
||||
muls r7,r6
|
||||
subs r0,r7
|
||||
bcs 1f
|
||||
subs r1,#1
|
||||
1:
|
||||
subs r0,r7
|
||||
bcs 1f
|
||||
subs r1,#1
|
||||
1:
|
||||
uxth r7,r3 @ x0h
|
||||
muls r7,r6
|
||||
subs r1,r7
|
||||
subs r1,r7
|
||||
lsrs r7,r2,#16 @ x0m
|
||||
muls r7,r6
|
||||
lsls r6,r7,#17
|
||||
lsrs r7,#15
|
||||
subs r0,r6
|
||||
sbcs r1,r7 @ y-=((ui64)q*x0)<<1;
|
||||
|
||||
lsrs r6,r1,#3 @ y>>35
|
||||
muls r6,r4
|
||||
lsrs r6,#16 @ q=((ui32)(y>>35)*r)>>16;
|
||||
|
||||
cmp r5,#12
|
||||
blt last1 @ if(xsh<12) goto last1;
|
||||
|
||||
add r14,r6 @ qu<<13+q
|
||||
lsrs r2,#12
|
||||
lsls r7,r3,#20
|
||||
orrs r2,r7
|
||||
lsrs r3,#12 @ x0>>12
|
||||
|
||||
uxth r7,r2 @ x0l
|
||||
muls r7,r6
|
||||
subs r0,r7
|
||||
bcs 1f
|
||||
subs r1,#1
|
||||
1:
|
||||
uxth r7,r3 @ x0h
|
||||
muls r7,r6
|
||||
subs r1,r7
|
||||
lsrs r7,r2,#16 @ x0m
|
||||
muls r7,r6
|
||||
lsls r6,r7,#16
|
||||
lsrs r7,#16
|
||||
subs r0,r6
|
||||
sbcs r1,r7 @ y-=((ui64)q*x0)>>12
|
||||
|
||||
lsrs r6,r0,#22
|
||||
lsls r7,r1,#10
|
||||
orrs r6,r7 @ y>>22
|
||||
muls r6,r4
|
||||
movs r7,#41
|
||||
subs r7,r5
|
||||
lsrs r6,r7 @ q=((ui32)(y>>22)*r)>>(16+25-xsh)
|
||||
|
||||
subs r5,#12
|
||||
mov r7,r14
|
||||
lsls r7,r5
|
||||
2:
|
||||
adds r7,r6 @ qu=(qu<<(xsh-12))+q
|
||||
pop {r4,r5} @ recall x
|
||||
|
||||
@ here
|
||||
@ r0:r1 y
|
||||
@ r4:r5 x
|
||||
@ r6 q
|
||||
@ r7 qu
|
||||
|
||||
uxth r2,r4
|
||||
uxth r3,r5
|
||||
muls r2,r6 @ xlo*q
|
||||
muls r3,r6 @ xhi*q
|
||||
subs r0,r2
|
||||
sbcs r1,r3
|
||||
lsrs r2,r4,#16
|
||||
muls r2,r6
|
||||
lsrs r3,r2,#16
|
||||
lsls r2,#16 @ xm*q
|
||||
subs r0,r2
|
||||
sbcs r1,r3 @ y-=(ui64)q*x
|
||||
|
||||
1:
|
||||
movs r2,r0
|
||||
movs r3,r1
|
||||
adds r7,#1
|
||||
subs r0,r4
|
||||
sbcs r1,r5 @ while(y>=x) y-=x,qu++;
|
||||
bhs 1b
|
||||
subs r0,r7,#1 @ correction to qu
|
||||
movs r1,#0
|
||||
pop {r4-r7,r15}
|
||||
|
||||
last1:
|
||||
@ r0:r1 y
|
||||
@ r2:r3 x0
|
||||
@ r5 xsh
|
||||
@ r6 q
|
||||
|
||||
movs r7,#12
|
||||
subs r7,r5
|
||||
lsrs r6,r7 @ q>>=12-xsh
|
||||
mov r7,r14
|
||||
lsrs r7,#13
|
||||
lsls r7,r5
|
||||
adds r7,r7 @ qu<<(xsh+1)
|
||||
b 2b
|
||||
|
||||
y64_x64:
|
||||
@ here x is 49..64 bits
|
||||
movs r4,#0 @ q=0 if x>>32==0xffffffff
|
||||
adds r5,r3,#1
|
||||
beq 1f
|
||||
|
||||
ldr r7,=#SIO_BASE
|
||||
str r5,[r7,#SIO_DIV_UDIVISOR_OFFSET]
|
||||
str r1,[r7,#SIO_DIV_UDIVIDEND_OFFSET]
|
||||
wait_div 0
|
||||
ldr r4,[r7,#SIO_DIV_QUOTIENT_OFFSET] @ q=(ui32)(y>>32)/((x>>32)+1)
|
||||
1:
|
||||
uxth r5,r2
|
||||
uxth r6,r3
|
||||
muls r5,r4
|
||||
muls r6,r4
|
||||
subs r0,r5
|
||||
sbcs r1,r6
|
||||
lsrs r5,r2,#16
|
||||
lsrs r6,r3,#16
|
||||
muls r5,r4
|
||||
muls r6,r4
|
||||
lsls r6,#16
|
||||
lsrs r7,r5,#16
|
||||
orrs r6,r7
|
||||
lsls r5,#16
|
||||
subs r0,r5
|
||||
sbcs r1,r6 @ y-=(ui64)q*x
|
||||
|
||||
cmp r1,r3 @ while(y>=x) y-=x,q++
|
||||
bhs 1f
|
||||
3:
|
||||
movs r2,r0
|
||||
movs r3,r1
|
||||
movs r0,r4
|
||||
movs r1,#0
|
||||
pop {r4-r7,r15}
|
||||
|
||||
1:
|
||||
bne 2f
|
||||
cmp r0,r2
|
||||
blo 3b
|
||||
2:
|
||||
subs r0,r2
|
||||
sbcs r1,r3
|
||||
adds r4,#1
|
||||
cmp r1,r3
|
||||
blo 3b
|
||||
b 1b
|
||||
|
||||
div_section divmod_s64s64_rem
|
||||
regular_func divmod_s64s64_rem
|
||||
push {r4, lr}
|
||||
bl divmod_s64s64
|
||||
ldr r4, [sp, #8]
|
||||
stmia r4!, {r2,r3}
|
||||
pop {r4, pc}
|
||||
|
||||
div_section divmod_u64u64_rem
|
||||
regular_func divmod_u64u64_rem
|
||||
push {r4, lr}
|
||||
bl divmod_u64u64
|
||||
ldr r4, [sp, #8]
|
||||
stmia r4!, {r2,r3}
|
||||
pop {r4, pc}
|
127
src/rp2_common/pico_double/CMakeLists.txt
Normal file
127
src/rp2_common/pico_double/CMakeLists.txt
Normal file
@ -0,0 +1,127 @@
|
||||
if (NOT TARGET pico_double)
|
||||
# library to be depended on - we make this depend on particular implementations using per target generator expressions
|
||||
add_library(pico_double INTERFACE)
|
||||
|
||||
# no custom implementation; falls thru to compiler
|
||||
add_library(pico_double_compiler INTERFACE)
|
||||
# PICO_BUILD_DEFINE: PICO_DOUBLE_COMPILER, whether compiler provided double support is being used, type=bool, default=0, but dependent on CMake options, group=pico_double
|
||||
target_compile_definitions(pico_double_compiler INTERFACE
|
||||
PICO_DOUBLE_COMPILER=1
|
||||
)
|
||||
|
||||
add_library(pico_double_headers INTERFACE)
|
||||
target_include_directories(pico_double_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
|
||||
|
||||
# add alias "default" which is just pico.
|
||||
add_library(pico_double_default INTERFACE)
|
||||
target_link_libraries(pico_double_default INTERFACE pico_double_pico)
|
||||
|
||||
set(PICO_DEFAULT_DOUBLE_IMPL pico_double_default)
|
||||
|
||||
target_link_libraries(pico_double INTERFACE
|
||||
$<IF:$<BOOL:$<TARGET_PROPERTY:PICO_TARGET_DOUBLE_IMPL>>,$<TARGET_PROPERTY:PICO_TARGET_DOUBLE_IMPL>,${PICO_DEFAULT_DOUBLE_IMPL}>)
|
||||
|
||||
add_library(pico_double_pico INTERFACE)
|
||||
target_sources(pico_double_pico INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/double_aeabi.S
|
||||
${CMAKE_CURRENT_LIST_DIR}/double_init_rom.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/double_math.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/double_v1_rom_shim.S
|
||||
)
|
||||
# PICO_BUILD_DEFINE: PICO_DOUBLE_PICO, whether optimized pico/bootrom provided double support is being used, type=bool, default=1, but dependent on CMake options, group=pico_double
|
||||
target_compile_definitions(pico_double_pico INTERFACE
|
||||
PICO_DOUBLE_PICO=1
|
||||
)
|
||||
|
||||
target_link_libraries(pico_double_pico INTERFACE pico_bootrom pico_double_headers)
|
||||
|
||||
add_library(pico_double_none INTERFACE)
|
||||
target_sources(pico_double_none INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/double_none.S
|
||||
)
|
||||
|
||||
target_link_libraries(pico_double_none INTERFACE pico_double_headers)
|
||||
|
||||
# PICO_BUILD_DEFINE: PICO_DOUBLE_NONE, whether double support is disabled and functions will panic, type=bool, default=0, but dependent on CMake options, group=pico_double
|
||||
target_compile_definitions(pico_double_none INTERFACE
|
||||
PICO_DOUBLE_NONE=1
|
||||
PICO_PRINTF_SUPPORT_FLOAT=0 # printing floats/doubles won't work, so we can save space by removing it
|
||||
)
|
||||
|
||||
function(wrap_double_functions TARGET)
|
||||
pico_wrap_function(${TARGET} __aeabi_dadd)
|
||||
pico_wrap_function(${TARGET} __aeabi_ddiv)
|
||||
pico_wrap_function(${TARGET} __aeabi_dmul)
|
||||
pico_wrap_function(${TARGET} __aeabi_drsub)
|
||||
pico_wrap_function(${TARGET} __aeabi_dsub)
|
||||
pico_wrap_function(${TARGET} __aeabi_cdcmpeq)
|
||||
pico_wrap_function(${TARGET} __aeabi_cdrcmple)
|
||||
pico_wrap_function(${TARGET} __aeabi_cdcmple)
|
||||
pico_wrap_function(${TARGET} __aeabi_dcmpeq)
|
||||
pico_wrap_function(${TARGET} __aeabi_dcmplt)
|
||||
pico_wrap_function(${TARGET} __aeabi_dcmple)
|
||||
pico_wrap_function(${TARGET} __aeabi_dcmpge)
|
||||
pico_wrap_function(${TARGET} __aeabi_dcmpgt)
|
||||
pico_wrap_function(${TARGET} __aeabi_dcmpun)
|
||||
pico_wrap_function(${TARGET} __aeabi_i2d)
|
||||
pico_wrap_function(${TARGET} __aeabi_l2d)
|
||||
pico_wrap_function(${TARGET} __aeabi_ui2d)
|
||||
pico_wrap_function(${TARGET} __aeabi_ul2d)
|
||||
pico_wrap_function(${TARGET} __aeabi_d2iz)
|
||||
pico_wrap_function(${TARGET} __aeabi_d2lz)
|
||||
pico_wrap_function(${TARGET} __aeabi_d2uiz)
|
||||
pico_wrap_function(${TARGET} __aeabi_d2ulz)
|
||||
pico_wrap_function(${TARGET} __aeabi_d2f)
|
||||
pico_wrap_function(${TARGET} sqrt)
|
||||
pico_wrap_function(${TARGET} cos)
|
||||
pico_wrap_function(${TARGET} sin)
|
||||
pico_wrap_function(${TARGET} tan)
|
||||
pico_wrap_function(${TARGET} atan2)
|
||||
pico_wrap_function(${TARGET} exp)
|
||||
pico_wrap_function(${TARGET} log)
|
||||
|
||||
pico_wrap_function(${TARGET} ldexp)
|
||||
pico_wrap_function(${TARGET} copysign)
|
||||
pico_wrap_function(${TARGET} trunc)
|
||||
pico_wrap_function(${TARGET} floor)
|
||||
pico_wrap_function(${TARGET} ceil)
|
||||
pico_wrap_function(${TARGET} round)
|
||||
pico_wrap_function(${TARGET} sincos) # gnu
|
||||
pico_wrap_function(${TARGET} asin)
|
||||
pico_wrap_function(${TARGET} acos)
|
||||
pico_wrap_function(${TARGET} atan)
|
||||
pico_wrap_function(${TARGET} sinh)
|
||||
pico_wrap_function(${TARGET} cosh)
|
||||
pico_wrap_function(${TARGET} tanh)
|
||||
pico_wrap_function(${TARGET} asinh)
|
||||
pico_wrap_function(${TARGET} acosh)
|
||||
pico_wrap_function(${TARGET} atanh)
|
||||
pico_wrap_function(${TARGET} exp2)
|
||||
pico_wrap_function(${TARGET} log2)
|
||||
pico_wrap_function(${TARGET} exp10)
|
||||
pico_wrap_function(${TARGET} log10)
|
||||
pico_wrap_function(${TARGET} pow)
|
||||
pico_wrap_function(${TARGET} powint) #gnu
|
||||
pico_wrap_function(${TARGET} hypot)
|
||||
pico_wrap_function(${TARGET} cbrt)
|
||||
pico_wrap_function(${TARGET} fmod)
|
||||
pico_wrap_function(${TARGET} drem)
|
||||
pico_wrap_function(${TARGET} remainder)
|
||||
pico_wrap_function(${TARGET} remquo)
|
||||
pico_wrap_function(${TARGET} expm1)
|
||||
pico_wrap_function(${TARGET} log1p)
|
||||
pico_wrap_function(${TARGET} fma)
|
||||
endfunction()
|
||||
|
||||
wrap_double_functions(pico_double_pico)
|
||||
wrap_double_functions(pico_double_none)
|
||||
|
||||
macro(pico_set_double_implementation TARGET IMPL)
|
||||
get_target_property(target_type ${TARGET} TYPE)
|
||||
if ("EXECUTABLE" STREQUAL "${target_type}")
|
||||
set_target_properties(${TARGET} PROPERTIES PICO_TARGET_DOUBLE_IMPL "pico_double_${IMPL}")
|
||||
else()
|
||||
message(FATAL_ERROR "double implementation must be set on executable not library")
|
||||
endif()
|
||||
endmacro()
|
||||
endif()
|
801
src/rp2_common/pico_double/double_aeabi.S
Normal file
801
src/rp2_common/pico_double/double_aeabi.S
Normal file
@ -0,0 +1,801 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "pico/asm_helper.S"
|
||||
#include "pico/bootrom/sf_table.h"
|
||||
|
||||
__pre_init __aeabi_double_init, 00020
|
||||
|
||||
.syntax unified
|
||||
.cpu cortex-m0plus
|
||||
.thumb
|
||||
|
||||
.macro double_section name
|
||||
#if PICO_DOUBLE_IN_RAM
|
||||
.section RAM_SECTION_NAME(\name), "ax"
|
||||
#else
|
||||
.section SECTION_NAME(\name), "ax"
|
||||
#endif
|
||||
.endm
|
||||
|
||||
.macro _double_wrapper_func x
|
||||
wrapper_func \x
|
||||
.endm
|
||||
|
||||
.macro wrapper_func_d1 x
|
||||
_double_wrapper_func \x
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
mov ip, lr
|
||||
bl __check_nan_d1
|
||||
mov lr, ip
|
||||
#endif
|
||||
.endm
|
||||
|
||||
.macro wrapper_func_d2 x
|
||||
_double_wrapper_func \x
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
mov ip, lr
|
||||
bl __check_nan_d2
|
||||
mov lr, ip
|
||||
#endif
|
||||
.endm
|
||||
|
||||
.section .text
|
||||
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
.thumb_func
|
||||
__check_nan_d1:
|
||||
movs r3, #1
|
||||
lsls r3, #21
|
||||
lsls r2, r1, #1
|
||||
adds r2, r3
|
||||
bhi 1f
|
||||
bx lr
|
||||
1:
|
||||
bx ip
|
||||
|
||||
.thumb_func
|
||||
__check_nan_d2:
|
||||
push {r0, r2}
|
||||
movs r2, #1
|
||||
lsls r2, #21
|
||||
lsls r0, r1, #1
|
||||
adds r0, r2
|
||||
bhi 1f
|
||||
lsls r0, r3, #1
|
||||
adds r0, r2
|
||||
bhi 2f
|
||||
pop {r0, r2}
|
||||
bx lr
|
||||
2:
|
||||
pop {r0, r2}
|
||||
mov r0, r2
|
||||
mov r1, r3
|
||||
bx ip
|
||||
1:
|
||||
pop {r0, r2}
|
||||
bx ip
|
||||
#endif
|
||||
|
||||
.macro table_tail_call SF_TABLE_OFFSET
|
||||
push {r3, r4}
|
||||
#if PICO_DOUBLE_SUPPORT_ROM_V1
|
||||
#ifndef NDEBUG
|
||||
movs r3, #0
|
||||
mov ip, r3
|
||||
#endif
|
||||
#endif
|
||||
ldr r3, =sd_table
|
||||
ldr r3, [r3, #\SF_TABLE_OFFSET]
|
||||
str r3, [sp, #4]
|
||||
pop {r3, pc}
|
||||
.endm
|
||||
|
||||
.macro shimmable_table_tail_call SF_TABLE_OFFSET shim
|
||||
push {r3, r4}
|
||||
ldr r3, =sd_table
|
||||
ldr r3, [r3, #\SF_TABLE_OFFSET]
|
||||
#if PICO_DOUBLE_SUPPORT_ROM_V1
|
||||
mov ip, pc
|
||||
#endif
|
||||
str r3, [sp, #4]
|
||||
pop {r3, pc}
|
||||
#if PICO_DOUBLE_SUPPORT_ROM_V1
|
||||
.byte \SF_TABLE_OFFSET, 0xdf
|
||||
.word \shim
|
||||
#endif
|
||||
.endm
|
||||
|
||||
.macro double_wrapper_section func
|
||||
double_section WRAPPER_FUNC_NAME(\func)
|
||||
.endm
|
||||
|
||||
double_section push_r8_r11
|
||||
regular_func push_r8_r11
|
||||
mov r4,r8
|
||||
mov r5,r9
|
||||
mov r6,r10
|
||||
mov r7,r11
|
||||
push {r4-r7}
|
||||
bx r14
|
||||
|
||||
double_section pop_r8_r11
|
||||
regular_func pop_r8_r11
|
||||
pop {r4-r7}
|
||||
mov r8,r4
|
||||
mov r9,r5
|
||||
mov r10,r6
|
||||
mov r11,r7
|
||||
bx r14
|
||||
|
||||
# note generally each function is in a separate section unless there is fall thru or branching between them
|
||||
# note fadd, fsub, fmul, fdiv are so tiny and just defer to rom so are lumped together so they can share constant pool
|
||||
|
||||
# note functions are word aligned except where they are an odd number of linear instructions
|
||||
|
||||
// double FUNC_NAME(__aeabi_dadd)(double, double) double-precision addition
|
||||
double_wrapper_section __aeabi_darithmetic
|
||||
// double FUNC_NAME(__aeabi_drsub)(double x, double y) double-precision reverse subtraction, y - x
|
||||
|
||||
# frsub first because it is the only one that needs alignment
|
||||
.align 2
|
||||
wrapper_func __aeabi_drsub
|
||||
eors r0, r1
|
||||
eors r1, r0
|
||||
eors r0, r1
|
||||
// fall thru
|
||||
|
||||
// double FUNC_NAME(__aeabi_dsub)(double x, double y) double-precision subtraction, x - y
|
||||
wrapper_func_d2 __aeabi_dsub
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
// we want to return nan for inf-inf or -inf - -inf, but without too much upfront cost
|
||||
mov ip, r0
|
||||
mov r0, r1
|
||||
eors r0, r3
|
||||
bmi 1f // different signs
|
||||
mov r0, ip
|
||||
push {r0-r3, lr}
|
||||
bl 2f
|
||||
b ddiv_dsub_nan_helper
|
||||
1:
|
||||
mov r0, ip
|
||||
2:
|
||||
#endif
|
||||
shimmable_table_tail_call SF_TABLE_FSUB dsub_shim
|
||||
|
||||
wrapper_func_d2 __aeabi_dadd
|
||||
shimmable_table_tail_call SF_TABLE_FADD dadd_shim
|
||||
|
||||
// double FUNC_NAME(__aeabi_ddiv)(double n, double d) double-precision division, n / d
|
||||
wrapper_func_d2 __aeabi_ddiv
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
push {r0-r3, lr}
|
||||
bl 1f
|
||||
b ddiv_dsub_nan_helper
|
||||
1:
|
||||
#endif
|
||||
shimmable_table_tail_call SF_TABLE_FDIV ddiv_shim
|
||||
|
||||
ddiv_dsub_nan_helper:
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
// check for infinite op infinite (or rather check for infinite result with both
|
||||
// operands being infinite)
|
||||
lsls r2, r1, #1
|
||||
asrs r2, r2, #21
|
||||
adds r2, #1
|
||||
beq 2f
|
||||
add sp, #16
|
||||
pop {pc}
|
||||
2:
|
||||
ldr r2, [sp, #4]
|
||||
ldr r3, [sp, #12]
|
||||
lsls r2, #1
|
||||
asrs r2, r2, #21
|
||||
lsls r3, #1
|
||||
asrs r3, r3, #24
|
||||
ands r2, r3
|
||||
adds r2, #1
|
||||
bne 3f
|
||||
// infinite to nan
|
||||
movs r2, #1
|
||||
lsls r2, #19
|
||||
orrs r1, r2
|
||||
3:
|
||||
add sp, #16
|
||||
pop {pc}
|
||||
#endif
|
||||
|
||||
// double FUNC_NAME(__aeabi_dmul)(double, double) double-precision multiplication
|
||||
wrapper_func_d2 __aeabi_dmul
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
push {r0-r3, lr}
|
||||
bl 1f
|
||||
|
||||
// check for multiplication of infinite by zero (or rather check for infinite result with either
|
||||
// operand 0)
|
||||
lsls r3, r1, #1
|
||||
asrs r3, r3, #21
|
||||
adds r3, #1
|
||||
beq 2f
|
||||
add sp, #16
|
||||
pop {pc}
|
||||
2:
|
||||
ldr r2, [sp, #4]
|
||||
ldr r3, [sp, #12]
|
||||
ands r2, r3
|
||||
bne 3f
|
||||
// infinite to nan
|
||||
movs r2, #1
|
||||
lsls r2, #19
|
||||
orrs r1, r2
|
||||
3:
|
||||
add sp, #16
|
||||
pop {pc}
|
||||
1:
|
||||
#endif
|
||||
shimmable_table_tail_call SF_TABLE_FMUL dmul_shim
|
||||
|
||||
// void FUNC_NAME(__aeabi_cdrcmple)(double, double) reversed 3-way (<, =, ?>) compare [1], result in PSR ZC flags
|
||||
double_wrapper_section __aeabi_cdcmple
|
||||
|
||||
wrapper_func __aeabi_cdrcmple
|
||||
push {r0-r7,r14}
|
||||
eors r0, r2
|
||||
eors r2, r0
|
||||
eors r0, r2
|
||||
eors r1, r3
|
||||
eors r3, r1
|
||||
eors r1, r3
|
||||
b __aeabi_dfcmple_guts
|
||||
|
||||
// NOTE these share an implementation as we have no excepting NaNs.
|
||||
// void FUNC_NAME(__aeabi_cdcmple)(double, double) 3-way (<, =, ?>) compare [1], result in PSR ZC flags
|
||||
// void FUNC_NAME(__aeabi_cdcmpeq)(double, double) non-excepting equality comparison [1], result in PSR ZC flags
|
||||
@ compare r0:r1 against r2:r3, returning -1/0/1 for <, =, >
|
||||
@ also set flags accordingly
|
||||
.align 2
|
||||
wrapper_func __aeabi_cdcmple
|
||||
wrapper_func __aeabi_cdcmpeq
|
||||
push {r0-r7,r14}
|
||||
__aeabi_dfcmple_guts:
|
||||
ldr r7,=#0x7ff @ flush NaNs and denormals
|
||||
lsls r4,r1,#1
|
||||
lsrs r4,#21
|
||||
beq 1f
|
||||
cmp r4,r7
|
||||
bne 2f
|
||||
lsls r4, r1, #12
|
||||
bhi 7f
|
||||
1:
|
||||
movs r0,#0
|
||||
lsrs r1,#20
|
||||
lsls r1,#20
|
||||
2:
|
||||
lsls r4,r3,#1
|
||||
lsrs r4,#21
|
||||
beq 1f
|
||||
cmp r4,r7
|
||||
bne 2f
|
||||
lsls r4, r3, #12
|
||||
bhi 7f
|
||||
1:
|
||||
movs r2,#0
|
||||
lsrs r3,#20
|
||||
lsls r3,#20
|
||||
2:
|
||||
movs r6,#1
|
||||
eors r3,r1
|
||||
bmi 4f @ opposite signs? then can proceed on basis of sign of x
|
||||
eors r3,r1 @ restore r3
|
||||
bpl 2f
|
||||
cmp r3,r1
|
||||
bne 7f
|
||||
1:
|
||||
cmp r2,r0
|
||||
7:
|
||||
pop {r0-r7,r15}
|
||||
2:
|
||||
cmp r1,r3
|
||||
bne 7b
|
||||
1:
|
||||
cmp r0,r2
|
||||
pop {r0-r7,r15}
|
||||
4:
|
||||
orrs r3,r1 @ make -0==+0
|
||||
adds r3,r3
|
||||
orrs r3,r0
|
||||
orrs r3,r2
|
||||
beq 7b
|
||||
mvns r1, r1 @ carry inverse of r1 sign
|
||||
adds r1, r1
|
||||
pop {r0-r7,r15}
|
||||
|
||||
|
||||
// int FUNC_NAME(__aeabi_dcmpeq)(double, double) result (1, 0) denotes (=, ?<>) [2], use for C == and !=
|
||||
double_wrapper_section __aeabi_dcmpeq
|
||||
.align 2
|
||||
wrapper_func __aeabi_dcmpeq
|
||||
push {lr}
|
||||
bl __aeabi_cdcmpeq
|
||||
beq 1f
|
||||
movs r0, #0
|
||||
pop {pc}
|
||||
1:
|
||||
movs r0, #1
|
||||
pop {pc}
|
||||
|
||||
// int FUNC_NAME(__aeabi_dcmplt)(double, double) result (1, 0) denotes (<, ?>=) [2], use for C <
|
||||
double_wrapper_section __aeabi_dcmplt
|
||||
.align 2
|
||||
wrapper_func __aeabi_dcmplt
|
||||
push {lr}
|
||||
bl __aeabi_cdcmple
|
||||
sbcs r0, r0
|
||||
pop {pc}
|
||||
|
||||
// int FUNC_NAME(__aeabi_dcmple)(double, double) result (1, 0) denotes (<=, ?>) [2], use for C <=
|
||||
double_wrapper_section __aeabi_dcmple
|
||||
.align 2
|
||||
wrapper_func __aeabi_dcmple
|
||||
push {lr}
|
||||
bl __aeabi_cdcmple
|
||||
bls 1f
|
||||
movs r0, #0
|
||||
pop {pc}
|
||||
1:
|
||||
movs r0, #1
|
||||
pop {pc}
|
||||
|
||||
// int FUNC_NAME(__aeabi_dcmpge)(double, double) result (1, 0) denotes (>=, ?<) [2], use for C >=
|
||||
double_wrapper_section __aeabi_dcmpge
|
||||
.align 2
|
||||
wrapper_func __aeabi_dcmpge
|
||||
push {lr}
|
||||
// because of NaNs it is better to reverse the args than the result
|
||||
bl __aeabi_cdrcmple
|
||||
bls 1f
|
||||
movs r0, #0
|
||||
pop {pc}
|
||||
1:
|
||||
movs r0, #1
|
||||
pop {pc}
|
||||
|
||||
// int FUNC_NAME(__aeabi_dcmpgt)(double, double) result (1, 0) denotes (>, ?<=) [2], use for C >
|
||||
double_wrapper_section __aeabi_dcmpgt
|
||||
wrapper_func __aeabi_dcmpgt
|
||||
push {lr}
|
||||
// because of NaNs it is better to reverse the args than the result
|
||||
bl __aeabi_cdrcmple
|
||||
sbcs r0, r0
|
||||
pop {pc}
|
||||
|
||||
// int FUNC_NAME(__aeabi_dcmpun)(double, double) result (1, 0) denotes (?, <=>) [2], use for C99 isunordered()
|
||||
double_wrapper_section __aeabi_dcmpun
|
||||
wrapper_func __aeabi_dcmpun
|
||||
movs r0, #1
|
||||
lsls r0, #21
|
||||
lsls r2, r1, #1
|
||||
adds r2, r0
|
||||
bhi 1f
|
||||
lsls r2, r3, #1
|
||||
adds r2, r0
|
||||
bhi 1f
|
||||
movs r0, #0
|
||||
bx lr
|
||||
1:
|
||||
movs r0, #1
|
||||
bx lr
|
||||
|
||||
movs r0, #0
|
||||
bx lr
|
||||
|
||||
// double FUNC_NAME(__aeabi_ui2d)(unsigned) unsigned to double (double precision) conversion
|
||||
double_wrapper_section __aeabi_ui2d
|
||||
shimmable_table_tail_call SF_TABLE_UINT2FLOAT uint2double_shim
|
||||
|
||||
double_wrapper_section __aeabi_i2d
|
||||
|
||||
wrapper_func __aeabi_ui2d
|
||||
movs r1, #0
|
||||
cmp r0, #0
|
||||
bne 2f
|
||||
1:
|
||||
bx lr
|
||||
// double FUNC_NAME(__aeabi_i2d)(int) integer to double (double precision) conversion
|
||||
wrapper_func __aeabi_i2d
|
||||
asrs r1, r0, #31
|
||||
eors r0, r1
|
||||
subs r0, r1
|
||||
beq 1b
|
||||
lsls r1, #31
|
||||
2:
|
||||
push {r0, r1, r4, lr}
|
||||
ldr r3, =sf_clz_func
|
||||
ldr r3, [r3]
|
||||
blx r3
|
||||
pop {r2, r3}
|
||||
adds r4, r0, #1
|
||||
lsls r2, r4
|
||||
lsls r0, r2, #20
|
||||
lsrs r2, #12
|
||||
ldr r1,=#1055
|
||||
subs r1, r4
|
||||
lsls r1, #20
|
||||
orrs r1, r3
|
||||
orrs r1, r2
|
||||
pop {r4, pc}
|
||||
|
||||
// int FUNC_NAME(__aeabi_d2iz)(double) double (double precision) to integer C-style conversion [3]
|
||||
double_wrapper_section __aeabi_d2iz
|
||||
wrapper_func __aeabi_d2iz
|
||||
regular_func double2int_z
|
||||
push {r4, lr}
|
||||
lsls r4, r1, #1
|
||||
lsrs r2, r4, #21
|
||||
movs r3, #0x80
|
||||
adds r2, r3
|
||||
lsls r3, #3
|
||||
subs r2, r3
|
||||
lsls r3, #21
|
||||
cmp r2, #126
|
||||
ble 1f
|
||||
subs r2, #158
|
||||
bge 2f
|
||||
asrs r4, r1, #31
|
||||
lsls r1, #12
|
||||
lsrs r1, #1
|
||||
orrs r1, r3
|
||||
negs r2, r2
|
||||
lsrs r1, r2
|
||||
lsls r4, #1
|
||||
adds r4, #1
|
||||
adds r2, #21
|
||||
cmp r2, #32
|
||||
bge 3f
|
||||
lsrs r0, r2
|
||||
orrs r0, r1
|
||||
muls r0, r4
|
||||
pop {r4, pc}
|
||||
1:
|
||||
movs r0, #0
|
||||
pop {r4, pc}
|
||||
3:
|
||||
mov r0, r1
|
||||
muls r0, r4
|
||||
pop {r4, pc}
|
||||
2:
|
||||
// overflow
|
||||
lsrs r0, r1, #31
|
||||
adds r0, r3
|
||||
subs r0, #1
|
||||
pop {r4, pc}
|
||||
|
||||
double_section double2int
|
||||
regular_func double2int
|
||||
shimmable_table_tail_call SF_TABLE_FLOAT2INT double2int_shim
|
||||
|
||||
// unsigned FUNC_NAME(__aeabi_d2uiz)(double) double (double precision) to unsigned C-style conversion [3]
|
||||
double_wrapper_section __aeabi_d2uiz
|
||||
wrapper_func __aeabi_d2uiz
|
||||
regular_func double2uint
|
||||
shimmable_table_tail_call SF_TABLE_FLOAT2UINT double2uint_shim
|
||||
|
||||
double_section fix2double
|
||||
regular_func fix2double
|
||||
shimmable_table_tail_call SF_TABLE_FIX2FLOAT fix2double_shim
|
||||
|
||||
double_section ufix2double
|
||||
regular_func ufix2double
|
||||
shimmable_table_tail_call SF_TABLE_UFIX2FLOAT ufix2double_shim
|
||||
|
||||
double_section fix642double
|
||||
regular_func fix642double
|
||||
shimmable_table_tail_call SF_TABLE_FIX642FLOAT fix642double_shim
|
||||
|
||||
double_section ufix2double
|
||||
regular_func ufix642double
|
||||
shimmable_table_tail_call SF_TABLE_UFIX642FLOAT ufix642double_shim
|
||||
|
||||
// double FUNC_NAME(__aeabi_l2d)(long long) long long to double (double precision) conversion
|
||||
double_wrapper_section __aeabi_l2d
|
||||
wrapper_func __aeabi_l2d
|
||||
shimmable_table_tail_call SF_TABLE_INT642FLOAT int642double_shim
|
||||
|
||||
// double FUNC_NAME(__aeabi_l2f)(long long) long long to double (double precision) conversion
|
||||
double_wrapper_section __aeabi_ul2d
|
||||
wrapper_func __aeabi_ul2d
|
||||
shimmable_table_tail_call SF_TABLE_UINT642FLOAT uint642double_shim
|
||||
|
||||
// long long FUNC_NAME(__aeabi_d2lz)(double) double (double precision) to long long C-style conversion [3]
|
||||
double_wrapper_section __aeabi_d2lz
|
||||
wrapper_func __aeabi_d2lz
|
||||
regular_func double2int64_z
|
||||
cmn r1, r1
|
||||
bcc double2int64
|
||||
push {lr}
|
||||
lsls r1, #1
|
||||
lsrs r1, #1
|
||||
movs r2, #0
|
||||
bl double2ufix64
|
||||
cmp r1, #0
|
||||
bmi 1f
|
||||
movs r2, #0
|
||||
rsbs r0, #0
|
||||
sbcs r2, r1
|
||||
mov r1, r2
|
||||
pop {pc}
|
||||
1:
|
||||
movs r1, #128
|
||||
lsls r1, #24
|
||||
movs r0, #0
|
||||
pop {pc}
|
||||
|
||||
double_section double2int64
|
||||
regular_func double2int64
|
||||
shimmable_table_tail_call SF_TABLE_FLOAT2INT64 double2int64_shim
|
||||
|
||||
// unsigned long long FUNC_NAME(__aeabi_d2ulz)(double) double to unsigned long long C-style conversion [3]
|
||||
double_wrapper_section __aeabi_d2ulz
|
||||
wrapper_func __aeabi_d2ulz
|
||||
shimmable_table_tail_call SF_TABLE_FLOAT2UINT64 double2uint64_shim
|
||||
|
||||
double_section double2fix64
|
||||
regular_func double2fix64
|
||||
shimmable_table_tail_call SF_TABLE_FLOAT2FIX64 double2fix64_shim
|
||||
|
||||
double_section double2ufix64
|
||||
regular_func double2ufix64
|
||||
shimmable_table_tail_call SF_TABLE_FLOAT2UFIX64 double2ufix64_shim
|
||||
|
||||
double_section double2fix
|
||||
regular_func double2fix
|
||||
shimmable_table_tail_call SF_TABLE_FLOAT2FIX double2fix_shim
|
||||
|
||||
double_section double2ufix
|
||||
regular_func double2ufix
|
||||
shimmable_table_tail_call SF_TABLE_FLOAT2UFIX double2ufix_shim
|
||||
|
||||
double_wrapper_section __aeabi_d2f
|
||||
1:
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
// copy sign bit and 23 NAN id bits into sign bit and significant id bits, also set high id bit
|
||||
|
||||
lsrs r0, #30
|
||||
lsls r2, r1, #12
|
||||
lsrs r2, #9
|
||||
asrs r1, #22
|
||||
lsls r1, #22
|
||||
orrs r0, r1
|
||||
orrs r0, r2
|
||||
bx lr
|
||||
#endif
|
||||
wrapper_func __aeabi_d2f
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
movs r3, #1
|
||||
lsls r3, #21
|
||||
lsls r2, r1, #1
|
||||
adds r2, r3
|
||||
bhi 1b
|
||||
#endif
|
||||
// note double->float in double table at same index as float->double in double table
|
||||
shimmable_table_tail_call SF_TABLE_FLOAT2DOUBLE double2float_shim
|
||||
|
||||
double_wrapper_section srqt
|
||||
wrapper_func_d1 sqrt
|
||||
shimmable_table_tail_call SF_TABLE_FSQRT dsqrt_shim
|
||||
|
||||
double_wrapper_section sincostan_remainder
|
||||
regular_func sincostan_remainder
|
||||
ldr r2, =0x54442D18 // 2 * M_PI
|
||||
ldr r3, =0x401921FB
|
||||
push {lr}
|
||||
bl remainder
|
||||
pop {pc}
|
||||
|
||||
double_wrapper_section cos
|
||||
#don't use _d1 as we're doing a range check anyway and infinites/nans are bigger than 1024
|
||||
wrapper_func cos
|
||||
// rom version only works for -1024 < angle < 1024
|
||||
lsls r2, r1, #2
|
||||
bcc 1f
|
||||
lsrs r2, #22
|
||||
cmp r2, #9
|
||||
bge 2f
|
||||
1:
|
||||
shimmable_table_tail_call SF_TABLE_FCOS dcos_shim
|
||||
2:
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
lsls r2, r1, #1
|
||||
asrs r2, #21
|
||||
adds r2, #1
|
||||
bne 3f
|
||||
// infinite to nan
|
||||
movs r2, #1
|
||||
lsls r2, #19
|
||||
orrs r1, r2
|
||||
bx lr
|
||||
3:
|
||||
#endif
|
||||
push {lr}
|
||||
bl sincostan_remainder
|
||||
pop {r2}
|
||||
mov lr, r2
|
||||
b 1b
|
||||
|
||||
double_wrapper_section sin
|
||||
#don't use _d1 as we're doing a range check anyway and infinites/nans are bigger than 1024
|
||||
wrapper_func sin
|
||||
// rom version only works for -1024 < angle < 1024
|
||||
lsls r2, r1, #2
|
||||
bcc 1f
|
||||
lsrs r2, #22
|
||||
cmp r2, #9
|
||||
bge 2f
|
||||
1:
|
||||
shimmable_table_tail_call SF_TABLE_FSIN dsin_shim
|
||||
2:
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
lsls r2, r1, #1
|
||||
asrs r2, #21
|
||||
adds r2, #1
|
||||
bne 3f
|
||||
// infinite to nan
|
||||
movs r2, #1
|
||||
lsls r2, #19
|
||||
orrs r1, r2
|
||||
bx lr
|
||||
3:
|
||||
#endif
|
||||
push {lr}
|
||||
bl sincostan_remainder
|
||||
pop {r2}
|
||||
mov lr, r2
|
||||
b 1b
|
||||
|
||||
double_wrapper_section sincos
|
||||
// out of line remainder code for abs(angle)>=1024
|
||||
2:
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
lsls r2, r1, #1
|
||||
asrs r2, #21
|
||||
adds r2, #1
|
||||
bne 3f
|
||||
// infinite to nan
|
||||
movs r2, #1
|
||||
lsls r2, #19
|
||||
orrs r1, r2
|
||||
pop {r4-r5}
|
||||
stmia r4!, {r0, r1}
|
||||
stmia r5!, {r0, r1}
|
||||
pop {r4, r5, pc}
|
||||
3:
|
||||
#endif
|
||||
push {lr}
|
||||
bl sincostan_remainder
|
||||
pop {r2}
|
||||
mov lr, r2
|
||||
b 1f
|
||||
|
||||
wrapper_func sincos
|
||||
push {r2-r5, lr}
|
||||
// rom version only works for -1024 < angle < 1024
|
||||
lsls r2, r1, #2
|
||||
bcc 1f
|
||||
lsrs r2, #22
|
||||
cmp r2, #9
|
||||
bge 2b
|
||||
1:
|
||||
|
||||
bl 2f
|
||||
pop {r4-r5}
|
||||
stmia r4!, {r0, r1}
|
||||
stmia r5!, {r2, r3}
|
||||
pop {r4, r5, pc}
|
||||
|
||||
2:
|
||||
shimmable_table_tail_call SF_TABLE_V3_FSINCOS sincos_shim_bootstrap
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
.align 2
|
||||
1:
|
||||
pop {r2, r3}
|
||||
stmia r2!, {r0, r1}
|
||||
mov lr, r3
|
||||
pop {r3}
|
||||
stmia r3!, {r0, r1}
|
||||
bx lr
|
||||
#endif
|
||||
.thumb_func
|
||||
sincos_shim_bootstrap:
|
||||
push {r2, r3, r4}
|
||||
movs r3, #0x13
|
||||
ldrb r3, [r3]
|
||||
#if PICO_DOUBLE_SUPPORT_ROM_V1
|
||||
cmp r3, #1
|
||||
bne 1f
|
||||
ldr r3, =dsincos_shim
|
||||
b 2f
|
||||
#endif
|
||||
1:
|
||||
ldr r3, =dsincos_shim_v2
|
||||
2:
|
||||
ldr r2, =sd_table
|
||||
str r3, [r2, #SF_TABLE_V3_FSINCOS]
|
||||
str r3, [sp, #8]
|
||||
pop {r2, r3, pc}
|
||||
.thumb_func
|
||||
dsincos_shim_v2:
|
||||
push {r4-r7,r14}
|
||||
bl push_r8_r11
|
||||
bl v2_rom_dsincos_internal
|
||||
mov r12,r0 @ save ε
|
||||
bl v2_rom_dcos_finish
|
||||
push {r0,r1}
|
||||
mov r0,r12
|
||||
bl v2_rom_dsin_finish
|
||||
pop {r2,r3}
|
||||
bl pop_r8_r11
|
||||
pop {r4-r7,r15}
|
||||
.thumb_func
|
||||
v2_rom_dsincos_internal:
|
||||
push {r0, lr}
|
||||
ldr r0, =0x3855
|
||||
str r0, [sp, #4]
|
||||
pop {r0, pc}
|
||||
.thumb_func
|
||||
v2_rom_dcos_finish:
|
||||
push {r0, r1}
|
||||
ldr r0, =0x389d
|
||||
str r0, [sp, #4]
|
||||
pop {r0, pc}
|
||||
.thumb_func
|
||||
v2_rom_dsin_finish:
|
||||
push {r0, r1}
|
||||
ldr r0, =0x38d9
|
||||
str r0, [sp, #4]
|
||||
pop {r0, pc}
|
||||
|
||||
double_wrapper_section tan
|
||||
#don't use _d1 as we're doing a range check anyway and infinites/nans are bigger than 1024
|
||||
wrapper_func tan
|
||||
// rom version only works for -1024 < angle < 1024
|
||||
lsls r2, r1, #2
|
||||
bcc 1f
|
||||
lsrs r2, #22
|
||||
cmp r2, #9
|
||||
bge 2f
|
||||
1:
|
||||
shimmable_table_tail_call SF_TABLE_FTAN dtan_shim
|
||||
2:
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
lsls r2, r1, #1
|
||||
asrs r2, #21
|
||||
adds r2, #1
|
||||
bne 3f
|
||||
// infinite to nan
|
||||
movs r2, #1
|
||||
lsls r2, #19
|
||||
orrs r1, r2
|
||||
bx lr
|
||||
3:
|
||||
#endif
|
||||
push {lr}
|
||||
bl sincostan_remainder
|
||||
pop {r2}
|
||||
mov lr, r2
|
||||
b 1b
|
||||
|
||||
double_wrapper_section atan2
|
||||
wrapper_func_d2 atan2
|
||||
shimmable_table_tail_call SF_TABLE_FATAN2 datan2_shim
|
||||
|
||||
double_wrapper_section exp
|
||||
wrapper_func_d1 exp
|
||||
shimmable_table_tail_call SF_TABLE_FEXP dexp_shim
|
||||
|
||||
double_wrapper_section log
|
||||
wrapper_func_d1 log
|
||||
shimmable_table_tail_call SF_TABLE_FLN dln_shim
|
||||
|
66
src/rp2_common/pico_double/double_init_rom.c
Normal file
66
src/rp2_common/pico_double/double_init_rom.c
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "pico/bootrom.h"
|
||||
#include "pico/bootrom/sf_table.h"
|
||||
|
||||
// NOTE THIS FUNCTION TABLE IS NOT PUBLIC OR NECESSARILY COMPLETE...
|
||||
// IT IS ***NOT*** SAFE TO CALL THESE FUNCTION POINTERS FROM ARBITRARY CODE
|
||||
uint32_t sd_table[SF_TABLE_V2_SIZE / 2];
|
||||
|
||||
#if !PICO_DOUBLE_SUPPORT_ROM_V1
|
||||
static __attribute__((noreturn)) void missing_double_func_shim() {
|
||||
panic("missing double function");
|
||||
}
|
||||
#endif
|
||||
extern void double_table_shim_on_use_helper();
|
||||
|
||||
void __aeabi_double_init() {
|
||||
int rom_version = rp2040_rom_version();
|
||||
#if PICO_DOUBLE_SUPPORT_ROM_V1
|
||||
if (rom_version == 1) {
|
||||
|
||||
// this is a little tricky.. we only want to pull in a shim if the corresponding function
|
||||
// is called. to that end we include a SVC instruction with the table offset as the call number
|
||||
// followed by the shim function pointer inside the actual wrapper function. that way if the wrapper
|
||||
// function is garbage collected, so is the shim function.
|
||||
//
|
||||
// double_table_shim_on_use_helper expects this SVC instruction in the calling code soon after the address
|
||||
// pointed to by IP and patches the double_table entry with the real shim the first time the function is called.
|
||||
for(uint i=0; i<SF_TABLE_V2_SIZE/4; i++) {
|
||||
sd_table[i] = (uintptr_t)double_table_shim_on_use_helper;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (rom_version == 1) {
|
||||
// opting for soft failure for now - you'll get a panic at runtime if you call any of the missing methods
|
||||
for(uint i=0;i<SF_TABLE_V2_SIZE/4;i++) {
|
||||
sd_table[i] = (uintptr_t)missing_double_func_shim;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (rom_version >= 2) {
|
||||
void *rom_table = rom_data_lookup(rom_table_code('S', 'D'));
|
||||
assert(*((uint8_t *)(((void *)rom_data_lookup(rom_table_code('S', 'F')))-2)) * 4 >= SF_TABLE_V2_SIZE);
|
||||
memcpy(&sd_table, rom_table, SF_TABLE_V2_SIZE);
|
||||
if (rom_version == 2) {
|
||||
#ifndef NDEBUG
|
||||
if (*(uint16_t *)0x3854 != 0xb500 || // this is dsincos(_internal)
|
||||
|
||||
*(uint16_t *)0x38d8 != 0x4649 || // this is dsin_finish
|
||||
*(uint16_t *)0x389c != 0x4659 // this is dcos_finish
|
||||
) {
|
||||
panic(NULL);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (rom_version < 3) {
|
||||
// we use the unused entry for SINCOS
|
||||
sd_table[SF_TABLE_V3_FSINCOS / 4] = (uintptr_t) double_table_shim_on_use_helper;
|
||||
}
|
||||
}
|
607
src/rp2_common/pico_double/double_math.c
Normal file
607
src/rp2_common/pico_double/double_math.c
Normal file
@ -0,0 +1,607 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "pico/types.h"
|
||||
#include "pico/double.h"
|
||||
#include "pico/platform.h"
|
||||
|
||||
typedef uint64_t ui64;
|
||||
typedef uint32_t ui32;
|
||||
typedef int64_t i64;
|
||||
|
||||
#define PINF ( HUGE_VAL)
|
||||
#define MINF (-HUGE_VAL)
|
||||
#define PZERO (+0.0)
|
||||
#define MZERO (-0.0)
|
||||
|
||||
|
||||
#define PI 3.14159265358979323846
|
||||
#define LOG2 0.69314718055994530941
|
||||
// Unfortunately in double precision ln(10) is very close to half-way between to representable numbers
|
||||
#define LOG10 2.30258509299404568401
|
||||
#define LOG2E 1.44269504088896340737
|
||||
#define LOG10E 0.43429448190325182765
|
||||
#define ONETHIRD 0.33333333333333333333
|
||||
|
||||
#define PIf 3.14159265358979323846f
|
||||
#define LOG2f 0.69314718055994530941f
|
||||
#define LOG2Ef 1.44269504088896340737f
|
||||
#define LOG10Ef 0.43429448190325182765f
|
||||
#define ONETHIRDf 0.33333333333333333333f
|
||||
|
||||
#define DUNPACK(x,e,m) e=((x)>>52)&0x7ff,m=((x)&0x000fffffffffffffULL)|0x0010000000000000ULL
|
||||
#define DUNPACKS(x,s,e,m) s=((x)>>63),DUNPACK((x),(e),(m))
|
||||
|
||||
_Pragma("GCC diagnostic push")
|
||||
_Pragma("GCC diagnostic ignored \"-Wstrict-aliasing\"")
|
||||
|
||||
static inline bool disnan(double x) {
|
||||
ui64 ix=*(i64*)&x;
|
||||
// checks the top bit of the low 32 bit of the NAN, but it I think that is ok
|
||||
return ((uint32_t)(ix >> 31)) > 0xffe00000u;
|
||||
}
|
||||
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
#define check_nan_d1(x) if (disnan((x))) return (x)
|
||||
#define check_nan_d2(x,y) if (disnan((x))) return (x); else if (disnan((y))) return (y);
|
||||
#else
|
||||
#define check_nan_d1(x) ((void)0)
|
||||
#define check_nan_d2(x,y) ((void)0)
|
||||
#endif
|
||||
|
||||
static inline int dgetsignexp(double x) {
|
||||
ui64 ix=*(ui64*)&x;
|
||||
return (ix>>52)&0xfff;
|
||||
}
|
||||
|
||||
static inline int dgetexp(double x) {
|
||||
ui64 ix=*(ui64*)&x;
|
||||
return (ix>>52)&0x7ff;
|
||||
}
|
||||
|
||||
static inline double dldexp(double x,int de) {
|
||||
ui64 ix=*(ui64*)&x,iy;
|
||||
int e;
|
||||
e=dgetexp(x);
|
||||
if(e==0||e==0x7ff) return x;
|
||||
e+=de;
|
||||
if(e<=0) iy=ix&0x8000000000000000ULL; // signed zero for underflow
|
||||
else if(e>=0x7ff) iy=(ix&0x8000000000000000ULL)|0x7ff0000000000000ULL; // signed infinity on overflow
|
||||
else iy=ix+((ui64)de<<52);
|
||||
return *(double*)&iy;
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(ldexp)(double x, int de) {
|
||||
check_nan_d1(x);
|
||||
return dldexp(x, de);
|
||||
}
|
||||
|
||||
|
||||
static inline double dcopysign(double x,double y) {
|
||||
ui64 ix=*(ui64*)&x,iy=*(ui64*)&y;
|
||||
ix=((ix&0x7fffffffffffffffULL)|(iy&0x8000000000000000ULL));
|
||||
return *(double*)&ix;
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(copysign)(double x, double y) {
|
||||
check_nan_d2(x,y);
|
||||
return dcopysign(x, y);
|
||||
}
|
||||
static inline int diszero(double x) { return dgetexp (x)==0; }
|
||||
static inline int dispzero(double x) { return dgetsignexp(x)==0; }
|
||||
static inline int dismzero(double x) { return dgetsignexp(x)==0x800; }
|
||||
static inline int disinf(double x) { return dgetexp (x)==0x7ff; }
|
||||
static inline int dispinf(double x) { return dgetsignexp(x)==0x7ff; }
|
||||
static inline int disminf(double x) { return dgetsignexp(x)==0xfff; }
|
||||
|
||||
static inline int disint(double x) {
|
||||
ui64 ix=*(ui64*)&x,m;
|
||||
int e=dgetexp(x);
|
||||
if(e==0) return 1; // 0 is an integer
|
||||
e-=0x3ff; // remove exponent bias
|
||||
if(e<0) return 0; // |x|<1
|
||||
e=52-e; // bit position in mantissa with significance 1
|
||||
if(e<=0) return 1; // |x| large, so must be an integer
|
||||
m=(1ULL<<e)-1; // mask for bits of significance <1
|
||||
if(ix&m) return 0; // not an integer
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int disoddint(double x) {
|
||||
ui64 ix=*(ui64*)&x,m;
|
||||
int e=dgetexp(x);
|
||||
e-=0x3ff; // remove exponent bias
|
||||
if(e<0) return 0; // |x|<1; 0 is not odd
|
||||
e=52-e; // bit position in mantissa with significance 1
|
||||
if(e<0) return 0; // |x| large, so must be even
|
||||
m=(1ULL<<e)-1; // mask for bits of significance <1 (if any)
|
||||
if(ix&m) return 0; // not an integer
|
||||
if(e==52) return 1; // value is exactly 1
|
||||
return (ix>>e)&1;
|
||||
}
|
||||
|
||||
static inline int disstrictneg(double x) {
|
||||
ui64 ix=*(ui64*)&x;
|
||||
if(diszero(x)) return 0;
|
||||
return ix>>63;
|
||||
}
|
||||
|
||||
static inline int disneg(double x) {
|
||||
ui64 ix=*(ui64*)&x;
|
||||
return ix>>63;
|
||||
}
|
||||
|
||||
static inline double dneg(double x) {
|
||||
ui64 ix=*(ui64*)&x;
|
||||
ix^=0x8000000000000000ULL;
|
||||
return *(double*)&ix;
|
||||
}
|
||||
|
||||
static inline int dispo2(double x) {
|
||||
ui64 ix=*(ui64*)&x;
|
||||
if(diszero(x)) return 0;
|
||||
if(disinf(x)) return 0;
|
||||
ix&=0x000fffffffffffffULL;
|
||||
return ix==0;
|
||||
}
|
||||
|
||||
static inline double dnan_or(double x) {
|
||||
#if PICO_DOUBLE_PROPAGATE_NANS
|
||||
return NAN;
|
||||
#else
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(trunc)(double x) {
|
||||
check_nan_d1(x);
|
||||
ui64 ix=*(ui64*)&x,m;
|
||||
int e=dgetexp(x);
|
||||
e-=0x3ff; // remove exponent bias
|
||||
if(e<0) { // |x|<1
|
||||
ix&=0x8000000000000000ULL;
|
||||
return *(double*)&ix;
|
||||
}
|
||||
e=52-e; // bit position in mantissa with significance 1
|
||||
if(e<=0) return x; // |x| large, so must be an integer
|
||||
m=(1ULL<<e)-1; // mask for bits of significance <1
|
||||
ix&=~m;
|
||||
return *(double*)&ix;
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(round)(double x) {
|
||||
check_nan_d1(x);
|
||||
ui64 ix=*(ui64*)&x,m;
|
||||
int e=dgetexp(x);
|
||||
e-=0x3ff; // remove exponent bias
|
||||
if(e<-1) { // |x|<0.5
|
||||
ix&=0x8000000000000000ULL;
|
||||
return *(double*)&ix;
|
||||
}
|
||||
if(e==-1) { // 0.5<=|x|<1
|
||||
ix&=0x8000000000000000ULL;
|
||||
ix|=0x3ff0000000000000ULL; // ±1
|
||||
return *(double*)&ix;
|
||||
}
|
||||
e=52-e; // bit position in mantissa with significance 1, <=52
|
||||
if(e<=0) return x; // |x| large, so must be an integer
|
||||
m=1ULL<<(e-1); // mask for bit of significance 0.5
|
||||
ix+=m;
|
||||
m=m+m-1; // mask for bits of significance <1
|
||||
ix&=~m;
|
||||
return *(double*)&ix;
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(floor)(double x) {
|
||||
check_nan_d1(x);
|
||||
ui64 ix=*(ui64*)&x,m;
|
||||
int e=dgetexp(x);
|
||||
if(e==0) { // x==0
|
||||
ix&=0x8000000000000000ULL;
|
||||
return *(double*)&ix;
|
||||
}
|
||||
e-=0x3ff; // remove exponent bias
|
||||
if(e<0) { // |x|<1, not zero
|
||||
if(disneg(x)) return -1;
|
||||
return PZERO;
|
||||
}
|
||||
e=52-e; // bit position in mantissa with significance 1
|
||||
if(e<=0) return x; // |x| large, so must be an integer
|
||||
m=(1ULL<<e)-1; // mask for bit of significance <1
|
||||
if(disneg(x)) ix+=m; // add 1-ε to magnitude if negative
|
||||
ix&=~m; // truncate
|
||||
return *(double*)&ix;
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(ceil)(double x) {
|
||||
check_nan_d1(x);
|
||||
ui64 ix=*(ui64*)&x,m;
|
||||
int e=dgetexp(x);
|
||||
if(e==0) { // x==0
|
||||
ix&=0x8000000000000000ULL;
|
||||
return *(double*)&ix;
|
||||
}
|
||||
e-=0x3ff; // remove exponent bias
|
||||
if(e<0) { // |x|<1, not zero
|
||||
if(disneg(x)) return MZERO;
|
||||
return 1;
|
||||
}
|
||||
e=52-e; // bit position in mantissa with significance 1
|
||||
if(e<=0) return x; // |x| large, so must be an integer
|
||||
m=(1ULL<<e)-1; // mask for bit of significance <1
|
||||
if(!disneg(x)) ix+=m; // add 1-ε to magnitude if positive
|
||||
ix&=~m; // truncate
|
||||
return *(double*)&ix;
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(asin)(double x) {
|
||||
check_nan_d1(x);
|
||||
double u;
|
||||
u=(1-x)*(1+x);
|
||||
if(disstrictneg(u)) return dnan_or(PINF);
|
||||
return atan2(x,sqrt(u));
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(acos)(double x) {
|
||||
check_nan_d1(x);
|
||||
double u;
|
||||
u=(1-x)*(1+x);
|
||||
if(disstrictneg(u)) return dnan_or(PINF);
|
||||
return atan2(sqrt(u),x);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(atan)(double x) {
|
||||
check_nan_d1(x);
|
||||
if(dispinf(x)) return PI/2;
|
||||
if(disminf(x)) return -PI/2;
|
||||
return atan2(x,1);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(sinh)(double x) {
|
||||
check_nan_d1(x);
|
||||
return dldexp((exp(x)-exp(dneg(x))),-1);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(cosh)(double x) {
|
||||
check_nan_d1(x);
|
||||
return dldexp((exp(x)+exp(dneg(x))),-1);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(tanh)(double x) {
|
||||
check_nan_d1(x);
|
||||
double u;
|
||||
int e;
|
||||
e=dgetexp(x);
|
||||
if(e>=5+0x3ff) { // |x|>=32?
|
||||
if(!disneg(x)) return 1; // 1 << exp 2x; avoid generating infinities later
|
||||
else return -1; // 1 >> exp 2x
|
||||
}
|
||||
u=exp(dldexp(x,1));
|
||||
return (u-1)/(u+1);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(asinh)(double x) {
|
||||
check_nan_d1(x);
|
||||
int e;
|
||||
e=dgetexp(x);
|
||||
if(e>=32+0x3ff) { // |x|>=2^32?
|
||||
if(!disneg(x)) return log( x )+LOG2; // 1/x^2 << 1
|
||||
else return dneg(log(dneg(x))+LOG2); // 1/x^2 << 1
|
||||
}
|
||||
if(x>0) return log(sqrt(x*x+1)+x);
|
||||
else return dneg(log(sqrt(x*x+1)-x));
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(acosh)(double x) {
|
||||
check_nan_d1(x);
|
||||
int e;
|
||||
if(disneg(x)) x=dneg(x);
|
||||
e=dgetexp(x);
|
||||
if(e>=32+0x3ff) return log(x)+LOG2; // |x|>=2^32?
|
||||
return log(sqrt((x-1)*(x+1))+x);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(atanh)(double x) {
|
||||
check_nan_d1(x);
|
||||
return dldexp(log((1+x)/(1-x)),-1);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(exp2)(double x) {
|
||||
check_nan_d1(x);
|
||||
int e;
|
||||
// extra check for disminf as this catches -Nan, and x<=-4096 doesn't.
|
||||
if (disminf(x) || x<=-4096) return 0; // easily underflows
|
||||
else if (x>=4096) return PINF; // easily overflows
|
||||
e=(int)round(x);
|
||||
x-=e;
|
||||
return dldexp(exp(x*LOG2),e);
|
||||
}
|
||||
double WRAPPER_FUNC(log2)(double x) { check_nan_d1(x); return log(x)*LOG2E; }
|
||||
double WRAPPER_FUNC(exp10)(double x) { check_nan_d1(x); return pow(10,x); }
|
||||
double WRAPPER_FUNC(log10)(double x) { check_nan_d1(x); return log(x)*LOG10E; }
|
||||
|
||||
// todo these are marked as lofi
|
||||
double WRAPPER_FUNC(expm1(double x) { check_nan_d1(x); return exp)(x)-1; }
|
||||
double WRAPPER_FUNC(log1p(double x) { check_nan_d1(x); return log)(1+x); }
|
||||
double WRAPPER_FUNC(fma)(double x,double y,double z) { check_nan_d1(x); return x*y+z; }
|
||||
|
||||
// general power, x>0, finite
|
||||
static double dpow_1(double x,double y) {
|
||||
int a,b,c;
|
||||
double t,rt,u,v,v0,v1,w,ry;
|
||||
a=dgetexp(x)-0x3ff;
|
||||
u=log2(dldexp(x,-a)); // now log_2 x = a+u
|
||||
if(u>0.5) u-=1,a++; // |u|<=~0.5
|
||||
if(a==0) return exp2(u*y);
|
||||
// here |log_2 x| >~0.5
|
||||
if(y>= 4096) { // then easily over/underflows
|
||||
if(a<0) return 0;
|
||||
return PINF;
|
||||
}
|
||||
if(y<=-4096) { // then easily over/underflows
|
||||
if(a<0) return PINF;
|
||||
return 0;
|
||||
}
|
||||
ry=round(y);
|
||||
v=y-ry;
|
||||
v0=dldexp(round(ldexp(v,26)),-26);
|
||||
v1=v-v0;
|
||||
b=(int)ry; // guaranteed to fit in an int; y=b+v0+v1
|
||||
// now the result is exp2( (a+u) * (b+v0+v1) )
|
||||
c=a*b; // integer
|
||||
t=a*v0;
|
||||
rt=round(t);
|
||||
c+=(int)rt;
|
||||
w=t-rt;
|
||||
t=a*v1;
|
||||
w+=t;
|
||||
t=u*b;
|
||||
rt=round(t);
|
||||
c+=(int)rt;
|
||||
w+=t-rt;
|
||||
w+=u*v;
|
||||
return dldexp(exp2(w),c);
|
||||
}
|
||||
|
||||
static double dpow_int2(double x,int y) {
|
||||
double u;
|
||||
if(y==1) return x;
|
||||
u=dpow_int2(x,y/2);
|
||||
u*=u;
|
||||
if(y&1) u*=x;
|
||||
return u;
|
||||
}
|
||||
|
||||
// for the case where x not zero or infinity, y small and not zero
|
||||
static inline double dpowint_1(double x,int y) {
|
||||
if(y<0) x=1/x,y=-y;
|
||||
return dpow_int2(x,y);
|
||||
}
|
||||
|
||||
// for the case where x not zero or infinity
|
||||
static double dpowint_0(double x,int y) {
|
||||
int e;
|
||||
if(disneg(x)) {
|
||||
if(disoddint(y)) return dneg(dpowint_0(dneg(x),y));
|
||||
else return dpowint_0(dneg(x),y);
|
||||
}
|
||||
if(dispo2(x)) {
|
||||
e=dgetexp(x)-0x3ff;
|
||||
if(y>=2048) y= 2047; // avoid overflow
|
||||
if(y<-2048) y=-2048;
|
||||
y*=e;
|
||||
return dldexp(1,y);
|
||||
}
|
||||
if(y==0) return 1;
|
||||
if(y>=-32&&y<=32) return dpowint_1(x,y);
|
||||
return dpow_1(x,y);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(powint)(double x,int y) {
|
||||
_Pragma("GCC diagnostic push")
|
||||
_Pragma("GCC diagnostic ignored \"-Wfloat-equal\"")
|
||||
if(x==1.0||y==0) return 1;
|
||||
_Pragma("GCC diagnostic pop")
|
||||
check_nan_d1(x);
|
||||
if(diszero(x)) {
|
||||
if(y>0) {
|
||||
if(y&1) return x;
|
||||
else return 0;
|
||||
}
|
||||
if((y&1)) return dcopysign(PINF,x);
|
||||
return PINF;
|
||||
}
|
||||
if(dispinf(x)) {
|
||||
if(y<0) return 0;
|
||||
else return PINF;
|
||||
}
|
||||
if(disminf(x)) {
|
||||
if(y>0) {
|
||||
if((y&1)) return MINF;
|
||||
else return PINF;
|
||||
}
|
||||
if((y&1)) return MZERO;
|
||||
else return PZERO;
|
||||
}
|
||||
return dpowint_0(x,y);
|
||||
}
|
||||
|
||||
// for the case where y is guaranteed a finite integer, x not zero or infinity
|
||||
static double dpow_0(double x,double y) {
|
||||
int e,p;
|
||||
if(disneg(x)) {
|
||||
if(disoddint(y)) return dneg(dpow_0(dneg(x),y));
|
||||
else return dpow_0(dneg(x),y);
|
||||
}
|
||||
p=(int)y;
|
||||
if(dispo2(x)) {
|
||||
e=dgetexp(x)-0x3ff;
|
||||
if(p>=2048) p= 2047; // avoid overflow
|
||||
if(p<-2048) p=-2048;
|
||||
p*=e;
|
||||
return dldexp(1,p);
|
||||
}
|
||||
if(p==0) return 1;
|
||||
if(p>=-32&&p<=32) return dpowint_1(x,p);
|
||||
return dpow_1(x,y);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(pow)(double x,double y) {
|
||||
_Pragma("GCC diagnostic push")
|
||||
_Pragma("GCC diagnostic ignored \"-Wfloat-equal\"")
|
||||
|
||||
if(x==1.0||diszero(y)) return 1;
|
||||
check_nan_d2(x, y);
|
||||
if(x==-1.0&&disinf(y)) return 1;
|
||||
_Pragma("GCC diagnostic pop")
|
||||
|
||||
if(diszero(x)) {
|
||||
if(!disneg(y)) {
|
||||
if(disoddint(y)) return x;
|
||||
else return 0;
|
||||
}
|
||||
if(disoddint(y)) return dcopysign(PINF,x);
|
||||
return PINF;
|
||||
}
|
||||
if(dispinf(x)) {
|
||||
if(disneg(y)) return 0;
|
||||
else return PINF;
|
||||
}
|
||||
if(disminf(x)) {
|
||||
if(!disneg(y)) {
|
||||
if(disoddint(y)) return MINF;
|
||||
else return PINF;
|
||||
}
|
||||
if(disoddint(y)) return MZERO;
|
||||
else return PZERO;
|
||||
}
|
||||
if(dispinf(y)) {
|
||||
if(dgetexp(x)<0x3ff) return PZERO;
|
||||
else return PINF;
|
||||
}
|
||||
if(disminf(y)) {
|
||||
if(dgetexp(x)<0x3ff) return PINF;
|
||||
else return PZERO;
|
||||
}
|
||||
if(disint(y)) return dpow_0(x,y);
|
||||
if(disneg(x)) return PINF;
|
||||
return dpow_1(x,y);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(hypot)(double x,double y) {
|
||||
check_nan_d2(x, y);
|
||||
int ex,ey;
|
||||
ex=dgetexp(x); ey=dgetexp(y);
|
||||
if(ex>=0x3ff+400||ey>=0x3ff+400) { // overflow, or nearly so
|
||||
x=dldexp(x,-600),y=dldexp(y,-600);
|
||||
return dldexp(sqrt(x*x+y*y), 600);
|
||||
}
|
||||
else if(ex<=0x3ff-400&&ey<=0x3ff-400) { // underflow, or nearly so
|
||||
x=dldexp(x, 600),y=dldexp(y, 600);
|
||||
return dldexp(sqrt(x*x+y*y),-600);
|
||||
}
|
||||
return sqrt(x*x+y*y);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(cbrt)(double x) {
|
||||
check_nan_d1(x);
|
||||
int e;
|
||||
if(disneg(x)) return dneg(cbrt(dneg(x)));
|
||||
if(diszero(x)) return dcopysign(PZERO,x);
|
||||
e=dgetexp(x)-0x3ff;
|
||||
e=(e*0x5555+0x8000)>>16; // ~e/3, rounded
|
||||
x=dldexp(x,-e*3);
|
||||
x=exp(log(x)*ONETHIRD);
|
||||
return dldexp(x,e);
|
||||
}
|
||||
|
||||
// reduces mx*2^e modulo my, returning bottom bits of quotient at *pquo
|
||||
// 2^52<=|mx|,my<2^53, e>=0; 0<=result<my
|
||||
static i64 drem_0(i64 mx,i64 my,int e,int*pquo) {
|
||||
int quo=0,q,r=0,s;
|
||||
if(e>0) {
|
||||
r=0xffffffffU/(ui32)(my>>36); // reciprocal estimate Q16
|
||||
}
|
||||
while(e>0) {
|
||||
s=e; if(s>12) s=12; // gain up to 12 bits on each iteration
|
||||
q=(mx>>38)*r; // Q30
|
||||
q=((q>>(29-s))+1)>>1; // Q(s), rounded
|
||||
mx=(mx<<s)-my*q;
|
||||
quo=(quo<<s)+q;
|
||||
e-=s;
|
||||
}
|
||||
if(mx>=my) mx-=my,quo++; // when e==0 mx can be nearly as big as 2my
|
||||
if(mx>=my) mx-=my,quo++;
|
||||
if(mx<0) mx+=my,quo--;
|
||||
if(mx<0) mx+=my,quo--;
|
||||
if(pquo) *pquo=quo;
|
||||
return mx;
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(fmod)(double x,double y) {
|
||||
check_nan_d2(x, y);
|
||||
ui64 ix=*(ui64*)&x,iy=*(ui64*)&y;
|
||||
int sx,ex,ey;
|
||||
i64 mx,my;
|
||||
DUNPACKS(ix,sx,ex,mx);
|
||||
DUNPACK(iy,ey,my);
|
||||
if(ex==0x7ff) return dnan_or(PINF);
|
||||
if(ey==0) return PINF;
|
||||
if(ex==0) {
|
||||
if(!disneg(x)) return PZERO;
|
||||
return MZERO;
|
||||
}
|
||||
if(ex<ey) return x; // |x|<|y|, including case x=±0
|
||||
mx=drem_0(mx,my,ex-ey,0);
|
||||
if(sx) mx=-mx;
|
||||
return fix642double(mx,0x3ff-ey+52);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(remquo)(double x,double y,int*quo) {
|
||||
check_nan_d2(x, y);
|
||||
ui64 ix=*(ui64*)&x,iy=*(ui64*)&y;
|
||||
int sx,sy,ex,ey,q;
|
||||
i64 mx,my;
|
||||
DUNPACKS(ix,sx,ex,mx);
|
||||
DUNPACKS(iy,sy,ey,my);
|
||||
if(quo) *quo=0;
|
||||
if(ex==0x7ff) return PINF;
|
||||
if(ey==0) return PINF;
|
||||
if(ex==0) return PZERO;
|
||||
if(ey==0x7ff) return x;
|
||||
if(ex<ey-1) return x; // |x|<|y|/2
|
||||
if(ex==ey-1) {
|
||||
if(mx<=my) return x; // |x|<=|y|/2, even quotient
|
||||
// here |y|/2<|x|<|y|
|
||||
if(!sx) { // x>|y|/2
|
||||
mx-=my+my;
|
||||
ey--;
|
||||
q=1;
|
||||
} else { // x<-|y|/2
|
||||
mx=my+my-mx;
|
||||
ey--;
|
||||
q=-1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(sx) mx=-mx;
|
||||
mx=drem_0(mx,my,ex-ey,&q);
|
||||
if(mx+mx>my || (mx+mx==my&&(q&1)) ) { // |x|>|y|/2, or equality and an odd quotient?
|
||||
mx-=my;
|
||||
q++;
|
||||
}
|
||||
}
|
||||
if(sy) q=-q;
|
||||
if(quo) *quo=q;
|
||||
return fix642double(mx,0x3ff-ey+52);
|
||||
}
|
||||
|
||||
double WRAPPER_FUNC(drem)(double x,double y) { check_nan_d2(x, y); return remquo(x,y,0); }
|
||||
|
||||
double WRAPPER_FUNC(remainder)(double x,double y) { check_nan_d2(x, y); return remquo(x,y,0); }
|
||||
|
||||
_Pragma("GCC diagnostic pop") // strict-aliasing
|
82
src/rp2_common/pico_double/double_none.S
Normal file
82
src/rp2_common/pico_double/double_none.S
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "pico/asm_helper.S"
|
||||
#include "pico/bootrom/sf_table.h"
|
||||
|
||||
.syntax unified
|
||||
.cpu cortex-m0plus
|
||||
.thumb
|
||||
|
||||
wrapper_func __aeabi_dadd
|
||||
wrapper_func __aeabi_ddiv
|
||||
wrapper_func __aeabi_dmul
|
||||
wrapper_func __aeabi_drsub
|
||||
wrapper_func __aeabi_dsub
|
||||
wrapper_func __aeabi_cdcmpeq
|
||||
wrapper_func __aeabi_cdrcmple
|
||||
wrapper_func __aeabi_cdcmple
|
||||
wrapper_func __aeabi_dcmpeq
|
||||
wrapper_func __aeabi_dcmplt
|
||||
wrapper_func __aeabi_dcmple
|
||||
wrapper_func __aeabi_dcmpge
|
||||
wrapper_func __aeabi_dcmpgt
|
||||
wrapper_func __aeabi_dcmpun
|
||||
wrapper_func __aeabi_i2d
|
||||
wrapper_func __aeabi_l2d
|
||||
wrapper_func __aeabi_ui2d
|
||||
wrapper_func __aeabi_ul2d
|
||||
wrapper_func __aeabi_d2iz
|
||||
wrapper_func __aeabi_d2lz
|
||||
wrapper_func __aeabi_d2uiz
|
||||
wrapper_func __aeabi_d2ulz
|
||||
wrapper_func __aeabi_d2f
|
||||
wrapper_func sqrt
|
||||
wrapper_func cos
|
||||
wrapper_func sin
|
||||
wrapper_func tan
|
||||
wrapper_func atan2
|
||||
wrapper_func exp
|
||||
wrapper_func log
|
||||
|
||||
wrapper_func ldexp
|
||||
wrapper_func copysign
|
||||
wrapper_func trunc
|
||||
wrapper_func floor
|
||||
wrapper_func ceil
|
||||
wrapper_func round
|
||||
wrapper_func sincos
|
||||
wrapper_func asin
|
||||
wrapper_func acos
|
||||
wrapper_func atan
|
||||
wrapper_func sinh
|
||||
wrapper_func cosh
|
||||
wrapper_func tanh
|
||||
wrapper_func asinh
|
||||
wrapper_func acosh
|
||||
wrapper_func atanh
|
||||
wrapper_func exp2
|
||||
wrapper_func log2
|
||||
wrapper_func exp10
|
||||
wrapper_func log10
|
||||
wrapper_func pow
|
||||
wrapper_func powint
|
||||
wrapper_func hypot
|
||||
wrapper_func cbrt
|
||||
wrapper_func fmod
|
||||
wrapper_func drem
|
||||
wrapper_func remainder
|
||||
wrapper_func remquo
|
||||
wrapper_func expm1
|
||||
wrapper_func log1p
|
||||
wrapper_func fma
|
||||
|
||||
push {lr} // keep stack trace sane
|
||||
ldr r0, =str
|
||||
bl panic
|
||||
|
||||
str:
|
||||
.asciz "double support is disabled"
|
2184
src/rp2_common/pico_double/double_v1_rom_shim.S
Normal file
2184
src/rp2_common/pico_double/double_v1_rom_shim.S
Normal file
File diff suppressed because it is too large
Load Diff
60
src/rp2_common/pico_double/include/pico/double.h
Normal file
60
src/rp2_common/pico_double/include/pico/double.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _PICO_DOUBLE_H
|
||||
#define _PICO_DOUBLE_H
|
||||
|
||||
#include <math.h>
|
||||
#include "pico/types.h"
|
||||
#include "pico/bootrom/sf_table.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file double.h
|
||||
* \defgroup pico_double pico_double
|
||||
*
|
||||
* Optimized double-precision floating point functions
|
||||
*
|
||||
* (Replacement) optimized implementations are provided of the following compiler built-ins
|
||||
* and math library functions:
|
||||
*
|
||||
* - __aeabi_dadd, __aeabi_ddiv, __aeabi_dmul, __aeabi_drsub, __aeabi_dsub, __aeabi_cdcmpeq, __aeabi_cdrcmple, __aeabi_cdcmple, __aeabi_dcmpeq, __aeabi_dcmplt, __aeabi_dcmple, __aeabi_dcmpge, __aeabi_dcmpgt, __aeabi_dcmpun, __aeabi_i2d, __aeabi_l2d, __aeabi_ui2d, __aeabi_ul2d, __aeabi_d2iz, __aeabi_d2lz, __aeabi_d2uiz, __aeabi_d2ulz, __aeabi_d2f
|
||||
* - sqrt, cos, sin, tan, atan2, exp, log, ldexp, copysign, trunc, floor, ceil, round, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh, exp2, log2, exp10, log10, pow,, hypot, cbrt, fmod, drem, remainder, remquo, expm1, log1p, fma
|
||||
* - powint, sincos (GNU extensions)
|
||||
*
|
||||
* The following additional optimized functions are also provided:
|
||||
*
|
||||
* - fix2double, ufix2double, fix642double, ufix642double, double2fix, double2ufix, double2fix64, double2ufix64, double2int, double2int64, double2int_z, double2int64_z
|
||||
*/
|
||||
|
||||
double fix2double(int32_t m, int e);
|
||||
double ufix2double(uint32_t m, int e);
|
||||
double fix642double(int64_t m, int e);
|
||||
double ufix642double(uint64_t m, int e);
|
||||
|
||||
// These methods round towards -Infinity.
|
||||
int32_t double2fix(double f, int e);
|
||||
uint32_t double2ufix(double f, int e);
|
||||
int64_t double2fix64(double f, int e);
|
||||
uint64_t double2ufix64(double f, int e);
|
||||
int32_t double2int(double f);
|
||||
int64_t double2int64(double f);
|
||||
|
||||
// These methods round towards 0.
|
||||
int32_t double2int_z(double f);
|
||||
int64_t double2int64_z(double f);
|
||||
|
||||
double exp10(double x);
|
||||
void sincos(double x, double *sinx, double *cosx);
|
||||
double powint(double x, int y);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
1
src/rp2_common/pico_fix/CMakeLists.txt
Normal file
1
src/rp2_common/pico_fix/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
pico_add_subdirectory(rp2040_usb_device_enumeration)
|
@ -0,0 +1,9 @@
|
||||
add_library(pico_fix_rp2040_usb_device_enumeration INTERFACE)
|
||||
|
||||
target_sources(pico_fix_rp2040_usb_device_enumeration INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/rp2040_usb_device_enumeration.c
|
||||
)
|
||||
|
||||
target_include_directories(pico_fix_rp2040_usb_device_enumeration INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
|
||||
|
||||
target_link_libraries(pico_fix_rp2040_usb_device_enumeration INTERFACE hardware_structs pico_time)
|
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _PICO_FIX_RP2040_USB_DEVICE_ENUMERATION_H
|
||||
#define _PICO_FIX_RP2040_USB_DEVICE_ENUMERATION_H
|
||||
|
||||
/*! \brief Perform a brute force workaround for USB device enumeration issue
|
||||
* \ingroup pico_fix
|
||||
*
|
||||
* This method should be called during the IRQ handler for a bus reset
|
||||
*/
|
||||
void rp2040_usb_device_enumeration_fix(void);
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user