From 2a40bd5eaada3e9ed7f379f9c7ae6193ed82a9d8 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sun, 7 Jan 2024 10:51:44 -0800 Subject: [PATCH 1/6] Various changes for Arduino core support --- CMakeLists.txt | 6 +- drivers/include/drivers/I2CSlave.h | 8 +- drivers/source/I2CSlave.cpp | 2 +- extern/CMakeLists.txt | 15 +- mbed.h | 5 + targets/TARGET_RASPBERRYPI/CMakeLists.txt | 4 + .../TARGET_RP2040/PeripheralPins.c | 8 +- .../hardware_adc/include/hardware/adc.h | 2 +- .../src/rp2_common/hardware_clocks/clocks.c | 2 +- .../hardware_clocks/include/hardware/clocks.h | 6 +- .../src/rp2_common/hardware_dma/dma.c | 112 ++ .../hardware_dma/include/hardware/dma.h | 921 ++++++++++++ .../hardware_pio/include/hardware/pio.h | 1313 +++++++++++++++++ .../include/hardware/pio_instructions.h | 484 ++++++ .../src/rp2_common/hardware_pio/pio.c | 268 ++++ .../TARGET_RASPBERRYPI/reimport_pico_sdk.py | 84 +- tools/cmake/mbed_generate_config_header.cmake | 19 +- 17 files changed, 3189 insertions(+), 70 deletions(-) create mode 100644 targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_dma/dma.c create mode 100644 targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_dma/include/hardware/dma.h create mode 100644 targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/include/hardware/pio.h create mode 100644 targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/include/hardware/pio_instructions.h create mode 100644 targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/pio.c diff --git a/CMakeLists.txt b/CMakeLists.txt index c7e1fdb7782..6cf161556ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,8 +228,10 @@ endif() # Generate target config header and include it in all files if(NOT MBED_IS_NATIVE_BUILD) - mbed_write_target_config_header(${CMAKE_CURRENT_BINARY_DIR}/mbed-target-config.h MBED_TARGET_DEFINITIONS MBED_CONFIG_DEFINITIONS) - target_compile_options(mbed-core-flags INTERFACE -include ${CMAKE_CURRENT_BINARY_DIR}/mbed-target-config.h) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/generated-headers) + mbed_write_target_config_header(${CMAKE_CURRENT_BINARY_DIR}/generated-headers/mbed-target-config.h MBED_TARGET_DEFINITIONS MBED_CONFIG_DEFINITIONS) + target_compile_options(mbed-core-flags INTERFACE -include ${CMAKE_CURRENT_BINARY_DIR}/generated-headers/mbed-target-config.h) + target_include_directories(mbed-core-flags INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/generated-headers) endif() # Include mbed.h and config from generate folder diff --git a/drivers/include/drivers/I2CSlave.h b/drivers/include/drivers/I2CSlave.h index 663b326870b..1eec996e9b9 100644 --- a/drivers/include/drivers/I2CSlave.h +++ b/drivers/include/drivers/I2CSlave.h @@ -165,14 +165,12 @@ class I2CSlave { */ int receive(void); - /** Read specified number of bytes from an I2C master. + /** Read bytes transmitted to this MCU from an I2C master. * * @param data Pointer to the buffer to read data into. - * @param length Number of bytes to read. + * @param length Maximum number of bytes to read. * - * @return Result of the operation. - * @retval 0 If the number of bytes read is equal to length requested. - * @retval nonzero On error or if the number of bytes read is less than requested. + * @return The number of bytes read */ int read(char *data, int length); diff --git a/drivers/source/I2CSlave.cpp b/drivers/source/I2CSlave.cpp index 4317df9656e..e4515820add 100644 --- a/drivers/source/I2CSlave.cpp +++ b/drivers/source/I2CSlave.cpp @@ -52,7 +52,7 @@ int I2CSlave::receive(void) int I2CSlave::read(char *data, int length) { - return i2c_slave_read(&_i2c, data, length) != length; + return i2c_slave_read(&_i2c, data, length); } int I2CSlave::read(void) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 8441653ab3a..d2089218199 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -3,10 +3,13 @@ include(FetchContent) -FetchContent_Declare( - greentea-client - GIT_REPOSITORY https://github.com/ARMmbed/greentea-client.git - GIT_TAG 472aad2327fbfde827852fc44775904706847a3a -) +if(MBED_ENABLE_TESTING) + # Build greentea only when testing is enabled + FetchContent_Declare( + greentea-client + GIT_REPOSITORY https://github.com/ARMmbed/greentea-client.git + GIT_TAG 472aad2327fbfde827852fc44775904706847a3a + ) -FetchContent_MakeAvailable(greentea-client) + FetchContent_MakeAvailable(greentea-client) +endif() \ No newline at end of file diff --git a/mbed.h b/mbed.h index 896000a6a00..bfb6986b42a 100644 --- a/mbed.h +++ b/mbed.h @@ -17,6 +17,11 @@ #ifndef MBED_H #define MBED_H +// In some cases, Mbed headers are included from source files that don't have +// mbed-target-config.h applied via "-include" flag (this is true for the Arduino Mbed core). +// In that case, we should manually include the target config header here. +#include "mbed-target-config.h" + #include "platform/mbed_version.h" #if MBED_CONF_RTOS_API_PRESENT diff --git a/targets/TARGET_RASPBERRYPI/CMakeLists.txt b/targets/TARGET_RASPBERRYPI/CMakeLists.txt index e07c82f0fd1..e6f82e5074b 100644 --- a/targets/TARGET_RASPBERRYPI/CMakeLists.txt +++ b/targets/TARGET_RASPBERRYPI/CMakeLists.txt @@ -123,6 +123,8 @@ target_include_directories(mbed-raspberrypi pico-sdk/src/rp2_common/hardware_pll/include pico-sdk/src/rp2_common/hardware_sync/include pico-sdk/src/rp2_common/hardware_xosc/include + pico-sdk/src/rp2_common/hardware_pio/include + pico-sdk/src/rp2_common/hardware_dma/include pico-sdk/src/rp2_common/pico_platform/include pico-sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include/ pico-sdk/src/rp2_common/pico_bootrom/include @@ -157,6 +159,8 @@ target_sources(mbed-raspberrypi pico-sdk/src/rp2_common/hardware_timer/timer.c pico-sdk/src/rp2_common/hardware_sync/sync.c pico-sdk/src/rp2_common/hardware_rtc/rtc.c + pico-sdk/src/rp2_common/hardware_pio/pio.c + pico-sdk/src/rp2_common/hardware_dma/dma.c pico-sdk/src/rp2_common/pico_bootrom/bootrom.c pico-sdk/src/rp2_common/pico_platform/platform.c pico-sdk/src/common/pico_time/time.c diff --git a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/PeripheralPins.c b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/PeripheralPins.c index 6ba32555659..40e4840b348 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/PeripheralPins.c +++ b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/PeripheralPins.c @@ -210,10 +210,10 @@ const PinMap PinMap_I2C_SCL[] = { * is the onboard temperature sensor. */ const PinMap PinMap_ADC[] = { - { A0, ADC0, 0}, - { A1, ADC0, 1}, - { A2, ADC0, 2}, - { A3, ADC0, 3}, + { p26, ADC0, 0}, + { p27, ADC0, 1}, + { p28, ADC0, 2}, + { p29, ADC0, 3}, { ADC_TEMP, ADC0, 4}, { NC, NC, 0} }; diff --git a/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_adc/include/hardware/adc.h b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_adc/include/hardware/adc.h index ddabc39cbb3..372930f8f9f 100644 --- a/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_adc/include/hardware/adc.h +++ b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_adc/include/hardware/adc.h @@ -66,7 +66,7 @@ void adc_init(void); * * \param gpio The GPIO number to use. Allowable GPIO numbers are 26 to 29 inclusive. */ -static inline void adc_pico_sdk_gpio_init(uint gpio) { +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); diff --git a/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_clocks/clocks.c b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_clocks/clocks.c index e2fd59c147a..ad1eaeb77d6 100644 --- a/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_clocks/clocks.c +++ b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_clocks/clocks.c @@ -307,7 +307,7 @@ void clocks_enable_resus(resus_callback_t resus_callback) { clocks_hw->resus.ctrl = CLOCKS_CLK_SYS_RESUS_CTRL_ENABLE_BITS | timeout; } -void clock_pico_sdk_gpio_init_int_frac(uint gpio, uint src, uint32_t div_int, uint8_t div_frac) { +void clock_gpio_init_int_frac(uint gpio, uint src, uint32_t div_int, uint8_t div_frac) { // 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 diff --git a/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_clocks/include/hardware/clocks.h b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_clocks/include/hardware/clocks.h index 2091d859e22..66adefc627e 100644 --- a/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_clocks/include/hardware/clocks.h +++ b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_clocks/include/hardware/clocks.h @@ -239,7 +239,7 @@ void clocks_enable_resus(resus_callback_t resus_callback); * \param div_int The integer part of the value to divide the source clock by. This is useful to not overwhelm the GPIO pin with a fast clock. this is in range of 1..2^24-1. * \param div_frac The fractional part of the value to divide the source clock by. This is in range of 0..255 (/256). */ -void clock_pico_sdk_gpio_init_int_frac(uint gpio, uint src, uint32_t div_int, uint8_t div_frac); +void clock_gpio_init_int_frac(uint gpio, uint src, uint32_t div_int, uint8_t div_frac); /*! \brief Output an optionally divided clock to the specified gpio pin. * \ingroup hardware_clocks @@ -248,11 +248,11 @@ void clock_pico_sdk_gpio_init_int_frac(uint gpio, uint src, uint32_t div_int, ui * \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 float amount to divide the source clock by. This is useful to not overwhelm the GPIO pin with a fast clock. */ -static inline void clock_pico_sdk_gpio_init(uint gpio, uint src, float div) +static inline void clock_gpio_init(uint gpio, uint src, float div) { uint div_int = (uint)div; uint8_t frac = (uint8_t)((div - (float)div_int) * (1u << CLOCKS_CLK_GPOUT0_DIV_INT_LSB)); - clock_pico_sdk_gpio_init_int_frac(gpio, src, div_int, frac); + clock_gpio_init_int_frac(gpio, src, div_int, frac); } /*! \brief Configure a clock to come from a gpio input diff --git a/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_dma/dma.c b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_dma/dma.c new file mode 100644 index 00000000000..f306def8bb6 --- /dev/null +++ b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_dma/dma.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#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(offsetof(dma_hw_t, ch[0].ctrl_trig) == DMA_CH0_CTRL_TRIG_OFFSET, "hw mismatch"); +static_assert(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; +static uint8_t _timer_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); +} + +void dma_unclaim_mask(uint32_t mask) { + for(uint i = 0; mask; i++, mask >>= 1u) { + if (mask & 1u) dma_channel_unclaim(i); + } +} + +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"); +} + +bool dma_channel_is_claimed(uint channel) { + check_dma_channel_param(channel); + return hw_is_claimed((uint8_t *) &_claimed, channel); +} + +void dma_timer_claim(uint timer) { + check_dma_timer_param(timer); + hw_claim_or_assert(&_timer_claimed, timer, "DMA timer %d is already claimed"); +} + +void dma_timer_unclaim(uint timer) { + check_dma_timer_param(timer); + hw_claim_clear(&_timer_claimed, timer); +} + +int dma_claim_unused_timer(bool required) { + return hw_claim_unused_from_range(&_timer_claimed, required, 0, NUM_DMA_TIMERS-1, "No DMA timers are available"); +} + +bool dma_timer_is_claimed(uint timer) { + check_dma_timer_param(timer); + return hw_is_claimed(&_timer_claimed, timer); +} + +void dma_channel_cleanup(uint channel) { + check_dma_channel_param(channel); + // Disable CHAIN_TO, and disable channel, so that it ignores any further triggers + hw_write_masked( &dma_hw->ch[channel].al1_ctrl, (channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB) | (0u << DMA_CH0_CTRL_TRIG_EN_LSB), DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS | DMA_CH0_CTRL_TRIG_EN_BITS ); + // disable IRQs first as abort can cause spurious IRQs + dma_channel_set_irq0_enabled(channel, false); + dma_channel_set_irq1_enabled(channel, false); + dma_channel_abort(channel); + // finally clear the IRQ status, which may have been set during abort + dma_hw->intr = 1u << channel; +} + +#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); +} +#endif + +#if PARAM_ASSERTIONS_ENABLED(DMA) +void check_dma_channel_param_impl(uint __unused channel) { + valid_params_if(DMA, channel < NUM_DMA_CHANNELS); +} +#endif diff --git a/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_dma/include/hardware/dma.h b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_dma/include/hardware/dma.h new file mode 100644 index 00000000000..65c357e9fe5 --- /dev/null +++ b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_dma/include/hardware/dma.h @@ -0,0 +1,921 @@ +/* + * 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" +#include "pico/assert.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 + */ + +// these are not defined in generated dreq.h +#define DREQ_DMA_TIMER0 DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER0 +#define DREQ_DMA_TIMER1 DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER1 +#define DREQ_DMA_TIMER2 DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER2 +#define DREQ_DMA_TIMER3 DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER3 +#define DREQ_FORCE DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_PERMANENT + +// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_DMA, Enable/disable DMA assertions, type=bool, default=0, group=hardware_dma +#ifndef PARAM_ASSERTIONS_ENABLED_DMA +#define PARAM_ASSERTIONS_ENABLED_DMA 0 +#endif + +static inline void check_dma_channel_param(__unused uint channel) { +#if PARAM_ASSERTIONS_ENABLED(DMA) + // this method is used a lot by inline functions so avoid code bloat by deferring to function + extern void check_dma_channel_param_impl(uint channel); + check_dma_channel_param_impl(channel); +#endif +} + +static inline void check_dma_timer_param(__unused uint timer_num) { + valid_params_if(DMA, timer_num < NUM_DMA_TIMERS); +} + +inline static dma_channel_hw_t *dma_channel_hw_addr(uint channel) { + check_dma_channel_param(channel); + return &dma_hw->ch[channel]; +} + +/*! \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 + * + * \param channel the dma channel to release + */ +void dma_channel_unclaim(uint channel); + +/*! \brief Mark multiple dma channels as no longer used + * \ingroup hardware_dma + * + * \param channel_mask Bitfield of all channels to unclaim (bit 0 == channel 0, bit 1 == channel 1 etc) + */ +void dma_unclaim_mask(uint32_t channel_mask); + +/*! \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 Determine if a dma channel is claimed + * \ingroup hardware_dma + * + * \param channel the dma channel + * \return true if the channel is claimed, false otherwise + * \see dma_channel_claim + * \see dma_channel_claim_mask + */ +bool dma_channel_is_claimed(uint channel); + +/** \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 in a channel configuration object + * \ingroup channel_config + * + * \param c Pointer to channel configuration object + * \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 in a channel configuration object + * \ingroup channel_config + * + * \param c Pointer to channel configuration object + * \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 + */ +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 in a channel configuration object + * \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 chain_to channel in a channel configuration object + * \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 object + * \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 in a channel configuration object + * \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 object + * \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) | (((uint)size) << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB); +} + +/*! \brief Set address wrapping parameters in a channel configuration object + * \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 object + * \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 config in a channel configuration object + * \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 object + * \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 in a channel configuration object + * \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 object + * \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 Set the channel priority in a channel configuration object + * \ingroup channel_config + * + * When true, gives a channel preferential treatment in issue scheduling: in each scheduling round, + * all high priority channels are considered first, and then only a single low + * priority channel, before returning to the high priority channels. + * + * This only affects the order in which the DMA schedules channels. The DMA's bus priority is not changed. + * If the DMA is not saturated then a low priority channel will see no loss of throughput. + * + * \param c Pointer to channel configuration object + * \param high_priority True to enable high priority + */ +static inline void channel_config_set_high_priority(dma_channel_config *c, bool high_priority) { + c->ctrl = high_priority ? (c->ctrl | DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_BITS); +} + +/*! + * \brief Enable/Disable the DMA channel in a channel configuration object + * \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 object + * \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 in a channel configuration object + * \ingroup channel_config + * + * Sniff HW must be enabled and have this channel selected. + * + * \param c Pointer to channel configuration object + * \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 + * High Priority | 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); + channel_config_set_high_priority( &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 write 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, + const volatile 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, volatile 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. + * + * Note that due to errata RP2040-E13, aborting a channel which has transfers + * in-flight (i.e. an individual read has taken place but the corresponding write has not), the ABORT + * status bit will clear prematurely, and subsequently the in-flight + * transfers will trigger a completion interrupt once they complete. + * + * The effect of this is that you \em may see a spurious completion interrupt + * on the channel as a result of calling this method. + * + * The calling code should be sure to ignore a completion IRQ as a result of this method. This may + * not require any additional work, as aborting a channel which may be about to complete, when you have a completion + * IRQ handler registered, is inherently race-prone, and so code is likely needed to disambiguate the two occurrences. + * + * If that is not the case, but you do have a channel completion IRQ handler registered, you can simply + * disable/re-enable the IRQ around the call to this method as shown by this code fragment (using DMA IRQ0). + * + * \code + * // disable the channel on IRQ0 + * dma_channel_set_irq0_enabled(channel, false); + * // abort the channel + * dma_channel_abort(channel); + * // clear the spurious IRQ (if there was one) + * dma_channel_acknowledge_irq0(channel); + * // re-enable the channel on IRQ0 + * dma_channel_set_irq0_enabled(channel, true); + *\endcode + * + * \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->ch[channel].ctrl_trig & DMA_CH0_CTRL_TRIG_BUSY_BITS) tight_loop_contents(); +} + +/*! \brief Enable single DMA channel's interrupt via DMA_IRQ_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' interrupts via DMA_IRQ_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's interrupt via DMA_IRQ_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' interrupts via DMA_IRQ_1 + * \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 Enable single DMA channel interrupt on either DMA_IRQ_0 or DMA_IRQ_1 + * \ingroup hardware_dma + * + * \param irq_index the IRQ index; either 0 or 1 for DMA_IRQ_0 or DMA_IRQ_1 + * \param channel DMA channel + * \param enabled true to enable interrupt via irq_index for specified channel, false to disable. + */ +static inline void dma_irqn_set_channel_enabled(uint irq_index, uint channel, bool enabled) { + invalid_params_if(DMA, irq_index > 1); + if (irq_index) { + dma_channel_set_irq1_enabled(channel, enabled); + } else { + dma_channel_set_irq0_enabled(channel, enabled); + } +} + +/*! \brief Enable multiple DMA channels' interrupt via either DMA_IRQ_0 or DMA_IRQ_1 + * \ingroup hardware_dma + * + * \param irq_index the IRQ index; either 0 or 1 for DMA_IRQ_0 or DMA_IRQ_1 + * \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_irqn_set_channel_mask_enabled(uint irq_index, uint32_t channel_mask, bool enabled) { + invalid_params_if(DMA, irq_index > 1); + if (irq_index) { + dma_set_irq1_channel_mask_enabled(channel_mask, enabled); + } else { + dma_set_irq0_channel_mask_enabled(channel_mask, enabled); + } +} + +/*! \brief Determine if a particular channel is a cause of DMA_IRQ_0 + * \ingroup hardware_dma + * + * \param channel DMA channel + * \return true if the channel is a cause of DMA_IRQ_0, false otherwise + */ +static inline bool dma_channel_get_irq0_status(uint channel) { + check_dma_channel_param(channel); + return dma_hw->ints0 & (1u << channel); +} + +/*! \brief Determine if a particular channel is a cause of DMA_IRQ_1 + * \ingroup hardware_dma + * + * \param channel DMA channel + * \return true if the channel is a cause of DMA_IRQ_1, false otherwise + */ +static inline bool dma_channel_get_irq1_status(uint channel) { + check_dma_channel_param(channel); + return dma_hw->ints1 & (1u << channel); +} + +/*! \brief Determine if a particular channel is a cause of DMA_IRQ_N + * \ingroup hardware_dma + * + * \param irq_index the IRQ index; either 0 or 1 for DMA_IRQ_0 or DMA_IRQ_1 + * \param channel DMA channel + * \return true if the channel is a cause of the DMA_IRQ_N, false otherwise + */ +static inline bool dma_irqn_get_channel_status(uint irq_index, uint channel) { + invalid_params_if(DMA, irq_index > 1); + check_dma_channel_param(channel); + return (irq_index ? dma_hw->ints1 : dma_hw->ints0) & (1u << channel); +} + +/*! \brief Acknowledge a channel IRQ, resetting it as the cause of DMA_IRQ_0 + * \ingroup hardware_dma + * + * \param channel DMA channel + */ +static inline void dma_channel_acknowledge_irq0(uint channel) { + check_dma_channel_param(channel); + dma_hw->ints0 = 1u << channel; +} + +/*! \brief Acknowledge a channel IRQ, resetting it as the cause of DMA_IRQ_1 + * \ingroup hardware_dma + * + * \param channel DMA channel + */ +static inline void dma_channel_acknowledge_irq1(uint channel) { + check_dma_channel_param(channel); + dma_hw->ints1 = 1u << channel; +} + +/*! \brief Acknowledge a channel IRQ, resetting it as the cause of DMA_IRQ_N + * \ingroup hardware_dma + * + * \param irq_index the IRQ index; either 0 or 1 for DMA_IRQ_0 or DMA_IRQ_1 + * \param channel DMA channel + */ +static inline void dma_irqn_acknowledge_channel(uint irq_index, uint channel) { + invalid_params_if(DMA, irq_index > 1); + check_dma_channel_param(channel); + if (irq_index) + dma_hw->ints1 = 1u << channel; + else + dma_hw->ints0 = 1u << channel; +} + +/*! \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(); + // stop the compiler hoisting a non volatile buffer access above the DMA completion. + __compiler_memory_barrier(); +} + +/*! \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); + } + hw_write_masked(&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), + (DMA_SNIFF_CTRL_DMACH_BITS | + 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 Enable the Sniffer output invert function + * \ingroup hardware_dma + * + * If enabled, the sniff data result appears bit-inverted when read. + * This does not affect the way the checksum is calculated. + * + * \param invert Set true to enable output bit inversion + */ +inline static void dma_sniffer_set_output_invert_enabled(bool invert) { + if (invert) + hw_set_bits(&dma_hw->sniff_ctrl, DMA_SNIFF_CTRL_OUT_INV_BITS); + else + hw_clear_bits(&dma_hw->sniff_ctrl, DMA_SNIFF_CTRL_OUT_INV_BITS); +} + +/*! \brief Enable the Sniffer output bit reversal function + * \ingroup hardware_dma + * + * If enabled, the sniff data result appears bit-reversed when read. + * This does not affect the way the checksum is calculated. + * + * \param reverse Set true to enable output bit reversal + */ +inline static void dma_sniffer_set_output_reverse_enabled(bool reverse) { + if (reverse) + hw_set_bits(&dma_hw->sniff_ctrl, DMA_SNIFF_CTRL_OUT_REV_BITS); + else + hw_clear_bits(&dma_hw->sniff_ctrl, DMA_SNIFF_CTRL_OUT_REV_BITS); +} + +/*! \brief Disable the DMA sniffer + * \ingroup hardware_dma + * + */ +inline static void dma_sniffer_disable(void) { + dma_hw->sniff_ctrl = 0; +} + +/*! \brief Set the sniffer's data accumulator with initial value + * \ingroup hardware_dma + * + * Generally, CRC algorithms are used with the data accumulator initially + * seeded with 0xFFFF or 0xFFFFFFFF (for crc16 and crc32 algorithms) + * + * \param seed_value value to set data accumulator + */ +inline static void dma_sniffer_set_data_accumulator(uint32_t seed_value) { + dma_hw->sniff_data = seed_value; +} + +/*! \brief Get the sniffer's data accumulator value + * \ingroup hardware_dma + * + * Read value calculated by the hardware from sniffing the DMA stream + */ +inline static uint32_t dma_sniffer_get_data_accumulator(void) { + return dma_hw->sniff_data; +} + +/*! \brief Mark a dma timer as used + * \ingroup hardware_dma + * + * Method for cooperative claiming of hardware. Will cause a panic if the timer + * is already claimed. Use of this method by libraries detects accidental + * configurations that would fail in unpredictable ways. + * + * \param timer the dma timer + */ +void dma_timer_claim(uint timer); + +/*! \brief Mark a dma timer as no longer used + * \ingroup hardware_dma + * + * Method for cooperative claiming of hardware. + * + * \param timer the dma timer to release + */ +void dma_timer_unclaim(uint timer); + +/*! \brief Claim a free dma timer + * \ingroup hardware_dma + * + * \param required if true the function will panic if none are available + * \return the dma timer number or -1 if required was false, and none were free + */ +int dma_claim_unused_timer(bool required); + +/*! \brief Determine if a dma timer is claimed + * \ingroup hardware_dma + * + * \param timer the dma timer + * \return true if the timer is claimed, false otherwise + * \see dma_timer_claim + */ +bool dma_timer_is_claimed(uint timer); + +/*! \brief Set the divider for the given DMA timer + * \ingroup hardware_dma + * + * The timer will run at the system_clock_freq * numerator / denominator, so this is the speed + * that data elements will be transferred at via a DMA channel using this timer as a DREQ + * + * \param timer the dma timer + * \param numerator the fraction's numerator + * \param denominator the fraction's denominator + */ +static inline void dma_timer_set_fraction(uint timer, uint16_t numerator, uint16_t denominator) { + check_dma_timer_param(timer); + dma_hw->timer[timer] = (((uint32_t)numerator) << DMA_TIMER0_X_LSB) | (((uint32_t)denominator) << DMA_TIMER0_Y_LSB); +} + +/*! \brief Return the DREQ number for a given DMA timer + * \ingroup hardware_dma + * + * \param timer_num DMA timer number 0-3 + */ +static inline uint dma_get_timer_dreq(uint timer_num) { + static_assert(DREQ_DMA_TIMER1 == DREQ_DMA_TIMER0 + 1, ""); + static_assert(DREQ_DMA_TIMER2 == DREQ_DMA_TIMER0 + 2, ""); + static_assert(DREQ_DMA_TIMER3 == DREQ_DMA_TIMER0 + 3, ""); + check_dma_timer_param(timer_num); + return DREQ_DMA_TIMER0 + timer_num; +} + +/*! \brief Performs DMA channel cleanup after use + * \ingroup hardware_dma + * + * This can be used to cleanup dma channels when they're no longer needed, such that they are in a clean state for reuse. + * IRQ's for the channel are disabled, any in flight-transfer is aborted and any outstanding interrupts are cleared. + * The channel is then clear to be reused for other purposes. + * + * \code + * if (dma_channel >= 0) { + * dma_channel_cleanup(dma_channel); + * dma_channel_unclaim(dma_channel); + * dma_channel = -1; + * } + * \endcode + * + * \param channel DMA channel + */ +void dma_channel_cleanup(uint channel); + +#ifndef NDEBUG +void print_dma_ctrl(dma_channel_hw_t *channel); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/include/hardware/pio.h b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/include/hardware/pio.h new file mode 100644 index 00000000000..762c3be56e9 --- /dev/null +++ b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/include/hardware/pio.h @@ -0,0 +1,1313 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _HARDWARE_PIO_H +#define _HARDWARE_PIO_H + +#include "pico.h" +#include "hardware/address_mapped.h" +#include "hardware/structs/pio.h" +#include "hardware/gpio.h" +#include "hardware/regs/dreq.h" +#include "hardware/pio_instructions.h" + +// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PIO, Enable/disable assertions in the PIO module, type=bool, default=0, group=hardware_pio +#ifndef PARAM_ASSERTIONS_ENABLED_PIO +#define PARAM_ASSERTIONS_ENABLED_PIO 0 +#endif + +/** \file hardware/pio.h + * \defgroup hardware_pio hardware_pio + * + * Programmable I/O (PIO) API + * + * A programmable input/output block (PIO) is a versatile hardware interface which + * can support a number of different IO standards. There are two PIO blocks in the RP2040. + * + * Each PIO is programmable in the same sense as a processor: the four state machines independently + * execute short, sequential programs, to manipulate GPIOs and transfer data. Unlike a general + * purpose processor, PIO state machines are highly specialised for IO, with a focus on determinism, + * precise timing, and close integration with fixed-function hardware. Each state machine is equipped + * with: + * * Two 32-bit shift registers – either direction, any shift count + * * Two 32-bit scratch registers + * * 4×32 bit bus FIFO in each direction (TX/RX), reconfigurable as 8×32 in a single direction + * * Fractional clock divider (16 integer, 8 fractional bits) + * * Flexible GPIO mapping + * * DMA interface, sustained throughput up to 1 word per clock from system DMA + * * IRQ flag set/clear/status + * + * Full details of the PIO can be found in the RP2040 datasheet. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +static_assert(PIO_SM0_SHIFTCTRL_FJOIN_RX_LSB == PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB + 1, ""); + +/** \brief FIFO join states + * \ingroup hardware_pio + */ +enum pio_fifo_join { + PIO_FIFO_JOIN_NONE = 0, + PIO_FIFO_JOIN_TX = 1, + PIO_FIFO_JOIN_RX = 2, +}; + +/** \brief MOV status types + * \ingroup hardware_pio + */ +enum pio_mov_status_type { + STATUS_TX_LESSTHAN = 0, + STATUS_RX_LESSTHAN = 1 +}; + +typedef pio_hw_t *PIO; + +/** Identifier for the first (PIO 0) hardware PIO instance (for use in PIO functions). + * + * e.g. pio_gpio_init(pio0, 5) + * + * \ingroup hardware_pio + */ +#define pio0 pio0_hw + +/** Identifier for the second (PIO 1) hardware PIO instance (for use in PIO functions). + * + * e.g. pio_gpio_init(pio1, 5) + * + * \ingroup hardware_pio + */ +#define pio1 pio1_hw + +/** \brief PIO state machine configuration + * \defgroup sm_config sm_config + * \ingroup hardware_pio + * + * A PIO block needs to be configured, these functions provide helpers to set up configuration + * structures. See \ref pio_sm_set_config + * + */ + +/** \brief PIO Configuration structure + * \ingroup sm_config + * + * This structure is an in-memory representation of the configuration that can be applied to a PIO + * state machine later using pio_sm_set_config() or pio_sm_init(). + */ +typedef struct { + uint32_t clkdiv; + uint32_t execctrl; + uint32_t shiftctrl; + uint32_t pinctrl; +} pio_sm_config; + +static inline void check_sm_param(__unused uint sm) { + valid_params_if(PIO, sm < NUM_PIO_STATE_MACHINES); +} + +static inline void check_sm_mask(__unused uint mask) { + valid_params_if(PIO, mask < (1u << NUM_PIO_STATE_MACHINES)); +} + + +static inline void check_pio_param(__unused PIO pio) { + valid_params_if(PIO, pio == pio0 || pio == pio1); +} + +/*! \brief Set the 'out' pins in a state machine configuration + * \ingroup sm_config + * + * Can overlap with the 'in', 'set' and 'sideset' pins + * + * \param c Pointer to the configuration structure to modify + * \param out_base 0-31 First pin to set as output + * \param out_count 0-32 Number of pins to set. + */ +static inline void sm_config_set_out_pins(pio_sm_config *c, uint out_base, uint out_count) { + valid_params_if(PIO, out_base < 32); + valid_params_if(PIO, out_count <= 32); + c->pinctrl = (c->pinctrl & ~(PIO_SM0_PINCTRL_OUT_BASE_BITS | PIO_SM0_PINCTRL_OUT_COUNT_BITS)) | + (out_base << PIO_SM0_PINCTRL_OUT_BASE_LSB) | + (out_count << PIO_SM0_PINCTRL_OUT_COUNT_LSB); +} + +/*! \brief Set the 'set' pins in a state machine configuration + * \ingroup sm_config + * + * Can overlap with the 'in', 'out' and 'sideset' pins + * + * \param c Pointer to the configuration structure to modify + * \param set_base 0-31 First pin to set as + * \param set_count 0-5 Number of pins to set. + */ +static inline void sm_config_set_set_pins(pio_sm_config *c, uint set_base, uint set_count) { + valid_params_if(PIO, set_base < 32); + valid_params_if(PIO, set_count <= 5); + c->pinctrl = (c->pinctrl & ~(PIO_SM0_PINCTRL_SET_BASE_BITS | PIO_SM0_PINCTRL_SET_COUNT_BITS)) | + (set_base << PIO_SM0_PINCTRL_SET_BASE_LSB) | + (set_count << PIO_SM0_PINCTRL_SET_COUNT_LSB); +} + +/*! \brief Set the 'in' pins in a state machine configuration + * \ingroup sm_config + * + * Can overlap with the 'out', 'set' and 'sideset' pins + * + * \param c Pointer to the configuration structure to modify + * \param in_base 0-31 First pin to use as input + */ +static inline void sm_config_set_in_pins(pio_sm_config *c, uint in_base) { + valid_params_if(PIO, in_base < 32); + c->pinctrl = (c->pinctrl & ~PIO_SM0_PINCTRL_IN_BASE_BITS) | + (in_base << PIO_SM0_PINCTRL_IN_BASE_LSB); +} + +/*! \brief Set the 'sideset' pins in a state machine configuration + * \ingroup sm_config + * + * Can overlap with the 'in', 'out' and 'set' pins + * + * \param c Pointer to the configuration structure to modify + * \param sideset_base 0-31 base pin for 'side set' + */ +static inline void sm_config_set_sideset_pins(pio_sm_config *c, uint sideset_base) { + valid_params_if(PIO, sideset_base < 32); + c->pinctrl = (c->pinctrl & ~PIO_SM0_PINCTRL_SIDESET_BASE_BITS) | + (sideset_base << PIO_SM0_PINCTRL_SIDESET_BASE_LSB); +} + +/*! \brief Set the 'sideset' options in a state machine configuration + * \ingroup sm_config + * + * \param c Pointer to the configuration structure to modify + * \param bit_count Number of bits to steal from delay field in the instruction for use of side set (max 5) + * \param optional True if the topmost side set bit is used as a flag for whether to apply side set on that instruction + * \param pindirs True if the side set affects pin directions rather than values + */ +static inline void sm_config_set_sideset(pio_sm_config *c, uint bit_count, bool optional, bool pindirs) { + valid_params_if(PIO, bit_count <= 5); + valid_params_if(PIO, !optional || bit_count >= 1); + c->pinctrl = (c->pinctrl & ~PIO_SM0_PINCTRL_SIDESET_COUNT_BITS) | + (bit_count << PIO_SM0_PINCTRL_SIDESET_COUNT_LSB); + + c->execctrl = (c->execctrl & ~(PIO_SM0_EXECCTRL_SIDE_EN_BITS | PIO_SM0_EXECCTRL_SIDE_PINDIR_BITS)) | + (bool_to_bit(optional) << PIO_SM0_EXECCTRL_SIDE_EN_LSB) | + (bool_to_bit(pindirs) << PIO_SM0_EXECCTRL_SIDE_PINDIR_LSB); +} + +/*! \brief Set the state machine clock divider (from integer and fractional parts - 16:8) in a state machine configuration + * \ingroup sm_config + * + * The clock divider can slow the state machine's execution to some rate below + * the system clock frequency, by enabling the state machine on some cycles + * but not on others, in a regular pattern. This can be used to generate e.g. + * a given UART baud rate. See the datasheet for further detail. + * + * \param c Pointer to the configuration structure to modify + * \param div_int Integer part of the divisor + * \param div_frac Fractional part in 1/256ths + * \sa sm_config_set_clkdiv() + */ +static inline void sm_config_set_clkdiv_int_frac(pio_sm_config *c, uint16_t div_int, uint8_t div_frac) { + invalid_params_if(PIO, div_int == 0 && div_frac != 0); + c->clkdiv = + (((uint)div_frac) << PIO_SM0_CLKDIV_FRAC_LSB) | + (((uint)div_int) << PIO_SM0_CLKDIV_INT_LSB); +} + +static inline void pio_calculate_clkdiv_from_float(float div, uint16_t *div_int, uint8_t *div_frac) { + valid_params_if(PIO, div >= 1 && div <= 65536); + *div_int = (uint16_t)div; + if (*div_int == 0) { + *div_frac = 0; + } else { + *div_frac = (uint8_t)((div - (float)*div_int) * (1u << 8u)); + } +} + +/*! \brief Set the state machine clock divider (from a floating point value) in a state machine configuration + * \ingroup sm_config + * + * The clock divider slows the state machine's execution by masking the + * system clock on some cycles, in a repeating pattern, so that the state + * machine does not advance. Effectively this produces a slower clock for the + * state machine to run from, which can be used to generate e.g. a particular + * UART baud rate. See the datasheet for further detail. + * + * \param c Pointer to the configuration structure to modify + * \param div The fractional divisor to be set. 1 for full speed. An integer clock divisor of n + * will cause the state machine to run 1 cycle in every n. + * Note that for small n, the jitter introduced by a fractional divider (e.g. 2.5) may be unacceptable + * although it will depend on the use case. + */ +static inline void sm_config_set_clkdiv(pio_sm_config *c, float div) { + uint16_t div_int; + uint8_t div_frac; + pio_calculate_clkdiv_from_float(div, &div_int, &div_frac); + sm_config_set_clkdiv_int_frac(c, div_int, div_frac); +} + +/*! \brief Set the wrap addresses in a state machine configuration + * \ingroup sm_config + * + * \param c Pointer to the configuration structure to modify + * \param wrap_target the instruction memory address to wrap to + * \param wrap the instruction memory address after which to set the program counter to wrap_target + * if the instruction does not itself update the program_counter + */ +static inline void sm_config_set_wrap(pio_sm_config *c, uint wrap_target, uint wrap) { + valid_params_if(PIO, wrap < PIO_INSTRUCTION_COUNT); + valid_params_if(PIO, wrap_target < PIO_INSTRUCTION_COUNT); + c->execctrl = (c->execctrl & ~(PIO_SM0_EXECCTRL_WRAP_TOP_BITS | PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS)) | + (wrap_target << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) | + (wrap << PIO_SM0_EXECCTRL_WRAP_TOP_LSB); +} + +/*! \brief Set the 'jmp' pin in a state machine configuration + * \ingroup sm_config + * + * \param c Pointer to the configuration structure to modify + * \param pin The raw GPIO pin number to use as the source for a `jmp pin` instruction + */ +static inline void sm_config_set_jmp_pin(pio_sm_config *c, uint pin) { + valid_params_if(PIO, pin < 32); + c->execctrl = (c->execctrl & ~PIO_SM0_EXECCTRL_JMP_PIN_BITS) | + (pin << PIO_SM0_EXECCTRL_JMP_PIN_LSB); +} + +/*! \brief Setup 'in' shifting parameters in a state machine configuration + * \ingroup sm_config + * + * \param c Pointer to the configuration structure to modify + * \param shift_right true to shift ISR to right, false to shift ISR to left + * \param autopush whether autopush is enabled + * \param push_threshold threshold in bits to shift in before auto/conditional re-pushing of the ISR + */ +static inline void sm_config_set_in_shift(pio_sm_config *c, bool shift_right, bool autopush, uint push_threshold) { + valid_params_if(PIO, push_threshold <= 32); + c->shiftctrl = (c->shiftctrl & + ~(PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_BITS | + PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS | + PIO_SM0_SHIFTCTRL_PUSH_THRESH_BITS)) | + (bool_to_bit(shift_right) << PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_LSB) | + (bool_to_bit(autopush) << PIO_SM0_SHIFTCTRL_AUTOPUSH_LSB) | + ((push_threshold & 0x1fu) << PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB); +} + +/*! \brief Setup 'out' shifting parameters in a state machine configuration + * \ingroup sm_config + * + * \param c Pointer to the configuration structure to modify + * \param shift_right true to shift OSR to right, false to shift OSR to left + * \param autopull whether autopull is enabled + * \param pull_threshold threshold in bits to shift out before auto/conditional re-pulling of the OSR + */ +static inline void sm_config_set_out_shift(pio_sm_config *c, bool shift_right, bool autopull, uint pull_threshold) { + valid_params_if(PIO, pull_threshold <= 32); + c->shiftctrl = (c->shiftctrl & + ~(PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_BITS | + PIO_SM0_SHIFTCTRL_AUTOPULL_BITS | + PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS)) | + (bool_to_bit(shift_right) << PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_LSB) | + (bool_to_bit(autopull) << PIO_SM0_SHIFTCTRL_AUTOPULL_LSB) | + ((pull_threshold & 0x1fu) << PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB); +} + +/*! \brief Setup the FIFO joining in a state machine configuration + * \ingroup sm_config + * + * \param c Pointer to the configuration structure to modify + * \param join Specifies the join type. \see enum pio_fifo_join + */ +static inline void sm_config_set_fifo_join(pio_sm_config *c, enum pio_fifo_join join) { + valid_params_if(PIO, join == PIO_FIFO_JOIN_NONE || join == PIO_FIFO_JOIN_TX || join == PIO_FIFO_JOIN_RX); + c->shiftctrl = (c->shiftctrl & (uint)~(PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS | PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS)) | + (((uint)join) << PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB); +} + +/*! \brief Set special 'out' operations in a state machine configuration + * \ingroup sm_config + * + * \param c Pointer to the configuration structure to modify + * \param sticky to enable 'sticky' output (i.e. re-asserting most recent OUT/SET pin values on subsequent cycles) + * \param has_enable_pin true to enable auxiliary OUT enable pin + * \param enable_pin_index pin index for auxiliary OUT enable + */ +static inline void sm_config_set_out_special(pio_sm_config *c, bool sticky, bool has_enable_pin, uint enable_pin_index) { + c->execctrl = (c->execctrl & + (uint)~(PIO_SM0_EXECCTRL_OUT_STICKY_BITS | PIO_SM0_EXECCTRL_INLINE_OUT_EN_BITS | + PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS)) | + (bool_to_bit(sticky) << PIO_SM0_EXECCTRL_OUT_STICKY_LSB) | + (bool_to_bit(has_enable_pin) << PIO_SM0_EXECCTRL_INLINE_OUT_EN_LSB) | + ((enable_pin_index << PIO_SM0_EXECCTRL_OUT_EN_SEL_LSB) & PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS); +} + +/*! \brief Set source for 'mov status' in a state machine configuration + * \ingroup sm_config + * + * \param c Pointer to the configuration structure to modify + * \param status_sel the status operation selector. \see enum pio_mov_status_type + * \param status_n parameter for the mov status operation (currently a bit count) + */ +static inline void sm_config_set_mov_status(pio_sm_config *c, enum pio_mov_status_type status_sel, uint status_n) { + valid_params_if(PIO, status_sel == STATUS_TX_LESSTHAN || status_sel == STATUS_RX_LESSTHAN); + c->execctrl = (c->execctrl + & ~(PIO_SM0_EXECCTRL_STATUS_SEL_BITS | PIO_SM0_EXECCTRL_STATUS_N_BITS)) + | ((((uint)status_sel) << PIO_SM0_EXECCTRL_STATUS_SEL_LSB) & PIO_SM0_EXECCTRL_STATUS_SEL_BITS) + | ((status_n << PIO_SM0_EXECCTRL_STATUS_N_LSB) & PIO_SM0_EXECCTRL_STATUS_N_BITS); +} + + +/*! \brief Get the default state machine configuration + * \ingroup sm_config + * + * Setting | Default + * --------|-------- + * Out Pins | 32 starting at 0 + * Set Pins | 0 starting at 0 + * In Pins (base) | 0 + * Side Set Pins (base) | 0 + * Side Set | disabled + * Wrap | wrap=31, wrap_to=0 + * In Shift | shift_direction=right, autopush=false, push_threshold=32 + * Out Shift | shift_direction=right, autopull=false, pull_threshold=32 + * Jmp Pin | 0 + * Out Special | sticky=false, has_enable_pin=false, enable_pin_index=0 + * Mov Status | status_sel=STATUS_TX_LESSTHAN, n=0 + * + * \return the default state machine configuration which can then be modified. + */ +static inline pio_sm_config pio_get_default_sm_config(void) { + pio_sm_config c = {0, 0, 0, 0}; + sm_config_set_clkdiv_int_frac(&c, 1, 0); + sm_config_set_wrap(&c, 0, 31); + sm_config_set_in_shift(&c, true, false, 32); + sm_config_set_out_shift(&c, true, false, 32); + return c; +} + +/*! \brief Apply a state machine configuration to a state machine + * \ingroup hardware_pio + * + * \param pio Handle to PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param config the configuration to apply +*/ +static inline void pio_sm_set_config(PIO pio, uint sm, const pio_sm_config *config) { + check_pio_param(pio); + check_sm_param(sm); + pio->sm[sm].clkdiv = config->clkdiv; + pio->sm[sm].execctrl = config->execctrl; + pio->sm[sm].shiftctrl = config->shiftctrl; + pio->sm[sm].pinctrl = config->pinctrl; +} + +/*! \brief Return the instance number of a PIO instance + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \return the PIO instance number (either 0 or 1) + */ +static inline uint pio_get_index(PIO pio) { + check_pio_param(pio); + return pio == pio1 ? 1 : 0; +} + +/*! \brief Setup the function select for a GPIO to use output from the given PIO instance + * \ingroup hardware_pio + * + * PIO appears as an alternate function in the GPIO muxing, just like an SPI + * or UART. This function configures that multiplexing to connect a given PIO + * instance to a GPIO. Note that this is not necessary for a state machine to + * be able to read the *input* value from a GPIO, but only for it to set the + * output value or output enable. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param pin the GPIO pin whose function select to set + */ +static inline void pio_gpio_init(PIO pio, uint pin) { + check_pio_param(pio); + valid_params_if(PIO, pin < 32); + gpio_set_function(pin, pio == pio0 ? GPIO_FUNC_PIO0 : GPIO_FUNC_PIO1); +} + +/*! \brief Return the DREQ to use for pacing transfers to/from a particular state machine FIFO + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param is_tx true for sending data to the state machine, false for receiving data from the state machine + */ +static inline uint pio_get_dreq(PIO pio, uint sm, bool is_tx) { + static_assert(DREQ_PIO0_TX1 == DREQ_PIO0_TX0 + 1, ""); + static_assert(DREQ_PIO0_TX2 == DREQ_PIO0_TX0 + 2, ""); + static_assert(DREQ_PIO0_TX3 == DREQ_PIO0_TX0 + 3, ""); + static_assert(DREQ_PIO0_RX0 == DREQ_PIO0_TX0 + NUM_PIO_STATE_MACHINES, ""); + static_assert(DREQ_PIO1_RX0 == DREQ_PIO1_TX0 + NUM_PIO_STATE_MACHINES, ""); + check_pio_param(pio); + check_sm_param(sm); + return sm + (is_tx ? 0 : NUM_PIO_STATE_MACHINES) + (pio == pio0 ? DREQ_PIO0_TX0 : DREQ_PIO1_TX0); +} + +typedef struct pio_program { + const uint16_t *instructions; + uint8_t length; + int8_t origin; // required instruction memory origin or -1 +} __packed pio_program_t; + +/*! \brief Determine whether the given program can (at the time of the call) be loaded onto the PIO instance + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param program the program definition + * \return true if the program can be loaded; false if there is not suitable space in the instruction memory + */ +bool pio_can_add_program(PIO pio, const pio_program_t *program); + +/*! \brief Determine whether the given program can (at the time of the call) be loaded onto the PIO instance starting at a particular location + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param program the program definition + * \param offset the instruction memory offset wanted for the start of the program + * \return true if the program can be loaded at that location; false if there is not space in the instruction memory + */ +bool pio_can_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset); + +/*! \brief Attempt to load the program, panicking if not possible + * \ingroup hardware_pio + * + * \see pio_can_add_program() if you need to check whether the program can be loaded + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param program the program definition + * \return the instruction memory offset the program is loaded at + */ +uint pio_add_program(PIO pio, const pio_program_t *program); + +/*! \brief Attempt to load the program at the specified instruction memory offset, panicking if not possible + * \ingroup hardware_pio + * + * \see pio_can_add_program_at_offset() if you need to check whether the program can be loaded + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param program the program definition + * \param offset the instruction memory offset wanted for the start of the program + */ +void pio_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset); + +/*! \brief Remove a program from a PIO instance's instruction memory + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param program the program definition + * \param loaded_offset the loaded offset returned when the program was added + */ +void pio_remove_program(PIO pio, const pio_program_t *program, uint loaded_offset); + +/*! \brief Clears all of a PIO instance's instruction memory + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + */ +void pio_clear_instruction_memory(PIO pio); + +/*! \brief Resets the state machine to a consistent state, and configures it + * \ingroup hardware_pio + * + * This method: + * - Disables the state machine (if running) + * - Clears the FIFOs + * - Applies the configuration specified by 'config' + * - Resets any internal state e.g. shift counters + * - Jumps to the initial program location given by 'initial_pc' + * + * The state machine is left disabled on return from this call. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param initial_pc the initial program memory offset to run from + * \param config the configuration to apply (or NULL to apply defaults) + */ +void pio_sm_init(PIO pio, uint sm, uint initial_pc, const pio_sm_config *config); + +/*! \brief Enable or disable a PIO state machine + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param enabled true to enable the state machine; false to disable + */ +static inline void pio_sm_set_enabled(PIO pio, uint sm, bool enabled) { + check_pio_param(pio); + check_sm_param(sm); + pio->ctrl = (pio->ctrl & ~(1u << sm)) | (bool_to_bit(enabled) << sm); +} + +/*! \brief Enable or disable multiple PIO state machines + * \ingroup hardware_pio + * + * Note that this method just sets the enabled state of the state machine; + * if now enabled they continue exactly from where they left off. + * + * \see pio_enable_sm_mask_in_sync() if you wish to enable multiple state machines + * and ensure their clock dividers are in sync. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param mask bit mask of state machine indexes to modify the enabled state of + * \param enabled true to enable the state machines; false to disable + */ +static inline void pio_set_sm_mask_enabled(PIO pio, uint32_t mask, bool enabled) { + check_pio_param(pio); + check_sm_mask(mask); + pio->ctrl = (pio->ctrl & ~mask) | (enabled ? mask : 0u); +} + +/*! \brief Restart a state machine with a known state + * \ingroup hardware_pio + * + * This method clears the ISR, shift counters, clock divider counter + * pin write flags, delay counter, latched EXEC instruction, and IRQ wait condition. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + */ +static inline void pio_sm_restart(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + hw_set_bits(&pio->ctrl, 1u << (PIO_CTRL_SM_RESTART_LSB + sm)); +} + +/*! \brief Restart multiple state machine with a known state + * \ingroup hardware_pio + * + * This method clears the ISR, shift counters, clock divider counter + * pin write flags, delay counter, latched EXEC instruction, and IRQ wait condition. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param mask bit mask of state machine indexes to modify the enabled state of + */ +static inline void pio_restart_sm_mask(PIO pio, uint32_t mask) { + check_pio_param(pio); + check_sm_mask(mask); + hw_set_bits(&pio->ctrl, (mask << PIO_CTRL_SM_RESTART_LSB) & PIO_CTRL_SM_RESTART_BITS); +} + +/*! \brief Restart a state machine's clock divider from a phase of 0 + * \ingroup hardware_pio + * + * Each state machine's clock divider is a free-running piece of hardware, + * that generates a pattern of clock enable pulses for the state machine, + * based *only* on the configured integer/fractional divisor. The pattern of + * running/halted cycles slows the state machine's execution to some + * controlled rate. + * + * This function clears the divider's integer and fractional phase + * accumulators so that it restarts this pattern from the beginning. It is + * called automatically by pio_sm_init() but can also be called at a later + * time, when you enable the state machine, to ensure precisely consistent + * timing each time you load and run a given PIO program. + * + * More commonly this hardware mechanism is used to synchronise the execution + * clocks of multiple state machines -- see pio_clkdiv_restart_sm_mask(). + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + */ +static inline void pio_sm_clkdiv_restart(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + hw_set_bits(&pio->ctrl, 1u << (PIO_CTRL_CLKDIV_RESTART_LSB + sm)); +} + +/*! \brief Restart multiple state machines' clock dividers from a phase of 0. + * \ingroup hardware_pio + * + * Each state machine's clock divider is a free-running piece of hardware, + * that generates a pattern of clock enable pulses for the state machine, + * based *only* on the configured integer/fractional divisor. The pattern of + * running/halted cycles slows the state machine's execution to some + * controlled rate. + * + * This function simultaneously clears the integer and fractional phase + * accumulators of multiple state machines' clock dividers. If these state + * machines all have the same integer and fractional divisors configured, + * their clock dividers will run in precise deterministic lockstep from this + * point. + * + * With their execution clocks synchronised in this way, it is then safe to + * e.g. have multiple state machines performing a 'wait irq' on the same flag, + * and all clear it on the same cycle. + * + * Also note that this function can be called whilst state machines are + * running (e.g. if you have just changed the clock divisors of some state + * machines and wish to resynchronise them), and that disabling a state + * machine does not halt its clock divider: that is, if multiple state + * machines have their clocks synchronised, you can safely disable and + * reenable one of the state machines without losing synchronisation. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param mask bit mask of state machine indexes to modify the enabled state of + */ +static inline void pio_clkdiv_restart_sm_mask(PIO pio, uint32_t mask) { + check_pio_param(pio); + check_sm_mask(mask); + hw_set_bits(&pio->ctrl, (mask << PIO_CTRL_CLKDIV_RESTART_LSB) & PIO_CTRL_CLKDIV_RESTART_BITS); +} + +/*! \brief Enable multiple PIO state machines synchronizing their clock dividers + * \ingroup hardware_pio + * + * This is equivalent to calling both pio_set_sm_mask_enabled() and + * pio_clkdiv_restart_sm_mask() on the *same* clock cycle. All state machines + * specified by 'mask' are started simultaneously and, assuming they have the + * same clock divisors, their divided clocks will stay precisely synchronised. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param mask bit mask of state machine indexes to modify the enabled state of + */ +static inline void pio_enable_sm_mask_in_sync(PIO pio, uint32_t mask) { + check_pio_param(pio); + check_sm_mask(mask); + hw_set_bits(&pio->ctrl, + ((mask << PIO_CTRL_CLKDIV_RESTART_LSB) & PIO_CTRL_CLKDIV_RESTART_BITS) | + ((mask << PIO_CTRL_SM_ENABLE_LSB) & PIO_CTRL_SM_ENABLE_BITS)); +} + +/*! \brief PIO interrupt source numbers for pio related IRQs + * \ingroup hardware_pio + */ +enum pio_interrupt_source { + pis_interrupt0 = PIO_INTR_SM0_LSB, + pis_interrupt1 = PIO_INTR_SM1_LSB, + pis_interrupt2 = PIO_INTR_SM2_LSB, + pis_interrupt3 = PIO_INTR_SM3_LSB, + pis_sm0_tx_fifo_not_full = PIO_INTR_SM0_TXNFULL_LSB, + pis_sm1_tx_fifo_not_full = PIO_INTR_SM1_TXNFULL_LSB, + pis_sm2_tx_fifo_not_full = PIO_INTR_SM2_TXNFULL_LSB, + pis_sm3_tx_fifo_not_full = PIO_INTR_SM3_TXNFULL_LSB, + pis_sm0_rx_fifo_not_empty = PIO_INTR_SM0_RXNEMPTY_LSB, + pis_sm1_rx_fifo_not_empty = PIO_INTR_SM1_RXNEMPTY_LSB, + pis_sm2_rx_fifo_not_empty = PIO_INTR_SM2_RXNEMPTY_LSB, + pis_sm3_rx_fifo_not_empty = PIO_INTR_SM3_RXNEMPTY_LSB, +}; + +/*! \brief Enable/Disable a single source on a PIO's IRQ 0 + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param source the source number (see \ref pio_interrupt_source) + * \param enabled true to enable IRQ 0 for the source, false to disable. + */ +static inline void pio_set_irq0_source_enabled(PIO pio, enum pio_interrupt_source source, bool enabled) { + check_pio_param(pio); + invalid_params_if(PIO, source >= 12); + if (enabled) + hw_set_bits(&pio->inte0, 1u << source); + else + hw_clear_bits(&pio->inte0, 1u << source); +} + +/*! \brief Enable/Disable a single source on a PIO's IRQ 1 + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param source the source number (see \ref pio_interrupt_source) + * \param enabled true to enable IRQ 0 for the source, false to disable. + */ +static inline void pio_set_irq1_source_enabled(PIO pio, enum pio_interrupt_source source, bool enabled) { + check_pio_param(pio); + invalid_params_if(PIO, source >= 12); + if (enabled) + hw_set_bits(&pio->inte1, 1u << source); + else + hw_clear_bits(&pio->inte1, 1u << source); +} + +/*! \brief Enable/Disable multiple sources on a PIO's IRQ 0 + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param source_mask Mask of bits, one for each source number (see \ref pio_interrupt_source) to affect + * \param enabled true to enable all the sources specified in the mask on IRQ 0, false to disable all the sources specified in the mask on IRQ 0 + */ +static inline void pio_set_irq0_source_mask_enabled(PIO pio, uint32_t source_mask, bool enabled) { + check_pio_param(pio); + invalid_params_if(PIO, source_mask > PIO_INTR_BITS); + if (enabled) { + hw_set_bits(&pio->inte0, source_mask); + } else { + hw_clear_bits(&pio->inte0, source_mask); + } +} + +/*! \brief Enable/Disable multiple sources on a PIO's IRQ 1 + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param source_mask Mask of bits, one for each source number (see \ref pio_interrupt_source) to affect + * \param enabled true to enable all the sources specified in the mask on IRQ 1, false to disable all the source specified in the mask on IRQ 1 + */ +static inline void pio_set_irq1_source_mask_enabled(PIO pio, uint32_t source_mask, bool enabled) { + check_pio_param(pio); + invalid_params_if(PIO, source_mask > PIO_INTR_BITS); + if (enabled) { + hw_set_bits(&pio->inte1, source_mask); + } else { + hw_clear_bits(&pio->inte1, source_mask); + } +} + +/*! \brief Enable/Disable a single source on a PIO's specified (0/1) IRQ index + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param irq_index the IRQ index; either 0 or 1 + * \param source the source number (see \ref pio_interrupt_source) + * \param enabled true to enable the source on the specified IRQ, false to disable. + */ +static inline void pio_set_irqn_source_enabled(PIO pio, uint irq_index, enum pio_interrupt_source source, bool enabled) { + invalid_params_if(PIO, irq_index > 1); + if (irq_index) { + pio_set_irq1_source_enabled(pio, source, enabled); + } else { + pio_set_irq0_source_enabled(pio, source, enabled); + } +} + +/*! \brief Enable/Disable multiple sources on a PIO's specified (0/1) IRQ index + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param irq_index the IRQ index; either 0 or 1 + * \param source_mask Mask of bits, one for each source number (see \ref pio_interrupt_source) to affect + * \param enabled true to enable all the sources specified in the mask on the specified IRQ, false to disable all the sources specified in the mask on the specified IRQ + */ +static inline void pio_set_irqn_source_mask_enabled(PIO pio, uint irq_index, uint32_t source_mask, bool enabled) { + invalid_params_if(PIO, irq_index > 1); + if (irq_index) { + pio_set_irq1_source_mask_enabled(pio, source_mask, enabled); + } else { + pio_set_irq0_source_mask_enabled(pio, source_mask, enabled); + } +} + +/*! \brief Determine if a particular PIO interrupt is set + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param pio_interrupt_num the PIO interrupt number 0-7 + * \return true if corresponding PIO interrupt is currently set + */ +static inline bool pio_interrupt_get(PIO pio, uint pio_interrupt_num) { + check_pio_param(pio); + invalid_params_if(PIO, pio_interrupt_num >= 8); + return pio->irq & (1u << pio_interrupt_num); +} + +/*! \brief Clear a particular PIO interrupt + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param pio_interrupt_num the PIO interrupt number 0-7 + */ +static inline void pio_interrupt_clear(PIO pio, uint pio_interrupt_num) { + check_pio_param(pio); + invalid_params_if(PIO, pio_interrupt_num >= 8); + pio->irq = (1u << pio_interrupt_num); +} + +/*! \brief Return the current program counter for a state machine + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \return the program counter + */ +static inline uint8_t pio_sm_get_pc(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + return (uint8_t) pio->sm[sm].addr; +} + +/*! \brief Immediately execute an instruction on a state machine + * \ingroup hardware_pio + * + * This instruction is executed instead of the next instruction in the normal control flow on the state machine. + * Subsequent calls to this method replace the previous executed + * instruction if it is still running. \see pio_sm_is_exec_stalled() to see if an executed instruction + * is still running (i.e. it is stalled on some condition) + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param instr the encoded PIO instruction + */ +inline static void pio_sm_exec(PIO pio, uint sm, uint instr) { + check_pio_param(pio); + check_sm_param(sm); + pio->sm[sm].instr = instr; +} + +/*! \brief Determine if an instruction set by pio_sm_exec() is stalled executing + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \return true if the executed instruction is still running (stalled) + */ +static inline bool pio_sm_is_exec_stalled(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + return !!(pio->sm[sm].execctrl & PIO_SM0_EXECCTRL_EXEC_STALLED_BITS); +} + +/*! \brief Immediately execute an instruction on a state machine and wait for it to complete + * \ingroup hardware_pio + * + * This instruction is executed instead of the next instruction in the normal control flow on the state machine. + * Subsequent calls to this method replace the previous executed + * instruction if it is still running. \see pio_sm_is_exec_stalled() to see if an executed instruction + * is still running (i.e. it is stalled on some condition) + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param instr the encoded PIO instruction + */ +static inline void pio_sm_exec_wait_blocking(PIO pio, uint sm, uint instr) { + check_pio_param(pio); + check_sm_param(sm); + pio_sm_exec(pio, sm, instr); + while (pio_sm_is_exec_stalled(pio, sm)) tight_loop_contents(); +} + +/*! \brief Set the current wrap configuration for a state machine + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param wrap_target the instruction memory address to wrap to + * \param wrap the instruction memory address after which to set the program counter to wrap_target + * if the instruction does not itself update the program_counter + */ +static inline void pio_sm_set_wrap(PIO pio, uint sm, uint wrap_target, uint wrap) { + check_pio_param(pio); + check_sm_param(sm); + valid_params_if(PIO, wrap < PIO_INSTRUCTION_COUNT); + valid_params_if(PIO, wrap_target < PIO_INSTRUCTION_COUNT); + pio->sm[sm].execctrl = + (pio->sm[sm].execctrl & ~(PIO_SM0_EXECCTRL_WRAP_TOP_BITS | PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS)) | + (wrap_target << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) | + (wrap << PIO_SM0_EXECCTRL_WRAP_TOP_LSB); +} + +/*! \brief Set the current 'out' pins for a state machine + * \ingroup hardware_pio + * + * Can overlap with the 'in', 'set' and 'sideset' pins + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param out_base 0-31 First pin to set as output + * \param out_count 0-32 Number of pins to set. + */ +static inline void pio_sm_set_out_pins(PIO pio, uint sm, uint out_base, uint out_count) { + check_pio_param(pio); + check_sm_param(sm); + valid_params_if(PIO, out_base < 32); + valid_params_if(PIO, out_count <= 32); + pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~(PIO_SM0_PINCTRL_OUT_BASE_BITS | PIO_SM0_PINCTRL_OUT_COUNT_BITS)) | + (out_base << PIO_SM0_PINCTRL_OUT_BASE_LSB) | + (out_count << PIO_SM0_PINCTRL_OUT_COUNT_LSB); +} + + +/*! \brief Set the current 'set' pins for a state machine + * \ingroup hardware_pio + * + * Can overlap with the 'in', 'out' and 'sideset' pins + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param set_base 0-31 First pin to set as + * \param set_count 0-5 Number of pins to set. + */ +static inline void pio_sm_set_set_pins(PIO pio, uint sm, uint set_base, uint set_count) { + check_pio_param(pio); + check_sm_param(sm); + valid_params_if(PIO, set_base < 32); + valid_params_if(PIO, set_count <= 5); + pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~(PIO_SM0_PINCTRL_SET_BASE_BITS | PIO_SM0_PINCTRL_SET_COUNT_BITS)) | + (set_base << PIO_SM0_PINCTRL_SET_BASE_LSB) | + (set_count << PIO_SM0_PINCTRL_SET_COUNT_LSB); +} + +/*! \brief Set the current 'in' pins for a state machine + * \ingroup hardware_pio + * + * Can overlap with the 'out', 'set' and 'sideset' pins + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param in_base 0-31 First pin to use as input + */ +static inline void pio_sm_set_in_pins(PIO pio, uint sm, uint in_base) { + check_pio_param(pio); + check_sm_param(sm); + valid_params_if(PIO, in_base < 32); + pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~PIO_SM0_PINCTRL_IN_BASE_BITS) | + (in_base << PIO_SM0_PINCTRL_IN_BASE_LSB); +} + +/*! \brief Set the current 'sideset' pins for a state machine + * \ingroup hardware_pio + * + * Can overlap with the 'in', 'out' and 'set' pins + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param sideset_base 0-31 base pin for 'side set' + */ +static inline void pio_sm_set_sideset_pins(PIO pio, uint sm, uint sideset_base) { + check_pio_param(pio); + check_sm_param(sm); + valid_params_if(PIO, sideset_base < 32); + pio->sm[sm].pinctrl = (pio->sm[sm].pinctrl & ~PIO_SM0_PINCTRL_SIDESET_BASE_BITS) | + (sideset_base << PIO_SM0_PINCTRL_SIDESET_BASE_LSB); +} + +/*! \brief Write a word of data to a state machine's TX FIFO + * \ingroup hardware_pio + * + * This is a raw FIFO access that does not check for fullness. If the FIFO is + * full, the FIFO contents and state are not affected by the write attempt. + * Hardware sets the TXOVER sticky flag for this FIFO in FDEBUG, to indicate + * that the system attempted to write to a full FIFO. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param data the 32 bit data value + * + * \sa pio_sm_put_blocking() + */ +static inline void pio_sm_put(PIO pio, uint sm, uint32_t data) { + check_pio_param(pio); + check_sm_param(sm); + pio->txf[sm] = data; +} + +/*! \brief Read a word of data from a state machine's RX FIFO + * \ingroup hardware_pio + * + * This is a raw FIFO access that does not check for emptiness. If the FIFO is + * empty, the hardware ignores the attempt to read from the FIFO (the FIFO + * remains in an empty state following the read) and the sticky RXUNDER flag + * for this FIFO is set in FDEBUG to indicate that the system tried to read + * from this FIFO when empty. The data returned by this function is undefined + * when the FIFO is empty. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * + * \sa pio_sm_get_blocking() + */ +static inline uint32_t pio_sm_get(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + return pio->rxf[sm]; +} + +/*! \brief Determine if a state machine's RX FIFO is full + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \return true if the RX FIFO is full + */ +static inline bool pio_sm_is_rx_fifo_full(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + return (pio->fstat & (1u << (PIO_FSTAT_RXFULL_LSB + sm))) != 0; +} + +/*! \brief Determine if a state machine's RX FIFO is empty + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \return true if the RX FIFO is empty + */ +static inline bool pio_sm_is_rx_fifo_empty(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + return (pio->fstat & (1u << (PIO_FSTAT_RXEMPTY_LSB + sm))) != 0; +} + +/*! \brief Return the number of elements currently in a state machine's RX FIFO + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \return the number of elements in the RX FIFO + */ +static inline uint pio_sm_get_rx_fifo_level(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + uint bitoffs = PIO_FLEVEL_RX0_LSB + sm * (PIO_FLEVEL_RX1_LSB - PIO_FLEVEL_RX0_LSB); + const uint32_t mask = PIO_FLEVEL_RX0_BITS >> PIO_FLEVEL_RX0_LSB; + return (pio->flevel >> bitoffs) & mask; +} + +/*! \brief Determine if a state machine's TX FIFO is full + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \return true if the TX FIFO is full + */ +static inline bool pio_sm_is_tx_fifo_full(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + return (pio->fstat & (1u << (PIO_FSTAT_TXFULL_LSB + sm))) != 0; +} + +/*! \brief Determine if a state machine's TX FIFO is empty + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \return true if the TX FIFO is empty + */ +static inline bool pio_sm_is_tx_fifo_empty(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + return (pio->fstat & (1u << (PIO_FSTAT_TXEMPTY_LSB + sm))) != 0; +} + +/*! \brief Return the number of elements currently in a state machine's TX FIFO + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \return the number of elements in the TX FIFO + */ +static inline uint pio_sm_get_tx_fifo_level(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + unsigned int bitoffs = PIO_FLEVEL_TX0_LSB + sm * (PIO_FLEVEL_TX1_LSB - PIO_FLEVEL_TX0_LSB); + const uint32_t mask = PIO_FLEVEL_TX0_BITS >> PIO_FLEVEL_TX0_LSB; + return (pio->flevel >> bitoffs) & mask; +} + +/*! \brief Write a word of data to a state machine's TX FIFO, blocking if the FIFO is full + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param data the 32 bit data value + */ +static inline void pio_sm_put_blocking(PIO pio, uint sm, uint32_t data) { + check_pio_param(pio); + check_sm_param(sm); + while (pio_sm_is_tx_fifo_full(pio, sm)) tight_loop_contents(); + pio_sm_put(pio, sm, data); +} + +/*! \brief Read a word of data from a state machine's RX FIFO, blocking if the FIFO is empty + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + */ +static inline uint32_t pio_sm_get_blocking(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + while (pio_sm_is_rx_fifo_empty(pio, sm)) tight_loop_contents(); + return pio_sm_get(pio, sm); +} + +/*! \brief Empty out a state machine's TX FIFO + * \ingroup hardware_pio + * + * This method executes `pull` instructions on the state machine until the TX + * FIFO is empty. This disturbs the contents of the OSR, so see also + * pio_sm_clear_fifos() which clears both FIFOs but leaves the state machine's + * internal state undisturbed. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * + * \sa pio_sm_clear_fifos() + */ +void pio_sm_drain_tx_fifo(PIO pio, uint sm); + +/*! \brief set the current clock divider for a state machine using a 16:8 fraction + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param div_int the integer part of the clock divider + * \param div_frac the fractional part of the clock divider in 1/256s + */ +static inline void pio_sm_set_clkdiv_int_frac(PIO pio, uint sm, uint16_t div_int, uint8_t div_frac) { + check_pio_param(pio); + check_sm_param(sm); + invalid_params_if(PIO, div_int == 0 && div_frac != 0); + pio->sm[sm].clkdiv = + (((uint)div_frac) << PIO_SM0_CLKDIV_FRAC_LSB) | + (((uint)div_int) << PIO_SM0_CLKDIV_INT_LSB); +} + +/*! \brief set the current clock divider for a state machine + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \param div the floating point clock divider + */ +static inline void pio_sm_set_clkdiv(PIO pio, uint sm, float div) { + check_pio_param(pio); + check_sm_param(sm); + uint16_t div_int; + uint8_t div_frac; + pio_calculate_clkdiv_from_float(div, &div_int, &div_frac); + pio_sm_set_clkdiv_int_frac(pio, sm, div_int, div_frac); +} + +/*! \brief Clear a state machine's TX and RX FIFOs + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + */ +static inline void pio_sm_clear_fifos(PIO pio, uint sm) { + // changing the FIFO join state clears the fifo + check_pio_param(pio); + check_sm_param(sm); + hw_xor_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS); + hw_xor_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS); +} + +/*! \brief Use a state machine to set a value on all pins for the PIO instance + * \ingroup hardware_pio + * + * This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set values on all 32 pins, + * before restoring the state machine's pin configuration to what it was. + * + * This method is provided as a convenience to set initial pin states, and should not be used against a state machine that is enabled. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) to use + * \param pin_values the pin values to set + */ +void pio_sm_set_pins(PIO pio, uint sm, uint32_t pin_values); + +/*! \brief Use a state machine to set a value on multiple pins for the PIO instance + * \ingroup hardware_pio + * + * This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set values on up to 32 pins, + * before restoring the state machine's pin configuration to what it was. + * + * This method is provided as a convenience to set initial pin states, and should not be used against a state machine that is enabled. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) to use + * \param pin_values the pin values to set (if the corresponding bit in pin_mask is set) + * \param pin_mask a bit for each pin to indicate whether the corresponding pin_value for that pin should be applied. + */ +void pio_sm_set_pins_with_mask(PIO pio, uint sm, uint32_t pin_values, uint32_t pin_mask); + +/*! \brief Use a state machine to set the pin directions for multiple pins for the PIO instance + * \ingroup hardware_pio + * + * This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set pin directions on up to 32 pins, + * before restoring the state machine's pin configuration to what it was. + * + * This method is provided as a convenience to set initial pin directions, and should not be used against a state machine that is enabled. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) to use + * \param pin_dirs the pin directions to set - 1 = out, 0 = in (if the corresponding bit in pin_mask is set) + * \param pin_mask a bit for each pin to indicate whether the corresponding pin_value for that pin should be applied. + */ +void pio_sm_set_pindirs_with_mask(PIO pio, uint sm, uint32_t pin_dirs, uint32_t pin_mask); + +/*! \brief Use a state machine to set the same pin direction for multiple consecutive pins for the PIO instance + * \ingroup hardware_pio + * + * This method repeatedly reconfigures the target state machine's pin configuration and executes 'set' instructions to set the pin direction on consecutive pins, + * before restoring the state machine's pin configuration to what it was. + * + * This method is provided as a convenience to set initial pin directions, and should not be used against a state machine that is enabled. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) to use + * \param pin_base the first pin to set a direction for + * \param pin_count the count of consecutive pins to set the direction for + * \param is_out the direction to set; true = out, false = in + */ +void pio_sm_set_consecutive_pindirs(PIO pio, uint sm, uint pin_base, uint pin_count, bool is_out); + +/*! \brief Mark a state machine as used + * \ingroup hardware_pio + * + * Method for cooperative claiming of hardware. Will cause a panic if the state machine + * is already claimed. Use of this method by libraries detects accidental + * configurations that would fail in unpredictable ways. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + */ +void pio_sm_claim(PIO pio, uint sm); + +/*! \brief Mark multiple state machines as used + * \ingroup hardware_pio + * + * Method for cooperative claiming of hardware. Will cause a panic if any of the state machines + * are already claimed. Use of this method by libraries detects accidental + * configurations that would fail in unpredictable ways. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm_mask Mask of state machine indexes + */ +void pio_claim_sm_mask(PIO pio, uint sm_mask); + +/*! \brief Mark a state machine as no longer used + * \ingroup hardware_pio + * + * Method for cooperative claiming of hardware. + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + */ +void pio_sm_unclaim(PIO pio, uint sm); + +/*! \brief Claim a free state machine on a PIO instance + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param required if true the function will panic if none are available + * \return the state machine index or -1 if required was false, and none were free + */ +int pio_claim_unused_sm(PIO pio, bool required); + +/*! \brief Determine if a PIO state machine is claimed + * \ingroup hardware_pio + * + * \param pio The PIO instance; either \ref pio0 or \ref pio1 + * \param sm State machine index (0..3) + * \return true if claimed, false otherwise + * \see pio_sm_claim + * \see pio_claim_sm_mask + */ +bool pio_sm_is_claimed(PIO pio, uint sm); + +#ifdef __cplusplus +} +#endif + +#endif // _PIO_H_ diff --git a/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/include/hardware/pio_instructions.h b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/include/hardware/pio_instructions.h new file mode 100644 index 00000000000..c27a4c17879 --- /dev/null +++ b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/include/hardware/pio_instructions.h @@ -0,0 +1,484 @@ +/* + * 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" + +/** \brief PIO instruction encoding + * \defgroup pio_instructions pio_instructions + * \ingroup hardware_pio + * + * Functions for generating PIO instruction encodings programmatically. In debug builds + *`PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS` can be set to 1 to enable validation of encoding function + * parameters. + * + * For fuller descriptions of the instructions in question see the "RP2040 Datasheet" + */ + +// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS, Enable/disable assertions in the PIO instructions, type=bool, default=0, group=pio_instructions +#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 + +/*! \brief Enumeration of values to pass for source/destination args for instruction encoding functions + * \ingroup pio_instructions + * + * \note Not all values are suitable for all functions. Validity is only checked in debug mode when + * `PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS` is 1 + */ +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, +}; + +static inline uint _pio_major_instr_bits(uint instr) { + return instr & 0xe000u; +} + +static inline 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); +} + +static inline 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); +} + +/*! \brief Encode just the delay slot bits of an instruction + * \ingroup pio_instructions + * + * \note This function does not return a valid instruction encoding; instead it returns an encoding of the delay + * slot suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when + * combining the results of this function with the results of \ref pio_encode_sideset and \ref pio_encode_sideset_opt + * as they share the same bits within the instruction encoding. + * + * \param cycles the number of cycles 0-31 (or less if side set is being used) + * \return the delay slot bits to be ORed with an instruction encoding + */ +static inline uint pio_encode_delay(uint cycles) { + // note that the maximum cycles will be smaller if sideset_bit_count > 0 + valid_params_if(PIO_INSTRUCTIONS, cycles <= 0x1f); + return cycles << 8u; +} + +/*! \brief Encode just the side set bits of an instruction (in non optional side set mode) + * \ingroup pio_instructions + * + * \note This function does not return a valid instruction encoding; instead it returns an encoding of the side set bits + * suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when + * combining the results of this function with the results of \ref pio_encode_delay as they share the same bits + * within the instruction encoding. + * + * \param sideset_bit_count number of side set bits as would be specified via `.sideset` in pioasm + * \param value the value to sideset on the pins + * \return the side set bits to be ORed with an instruction encoding + */ +static inline 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 <= ((1u << sideset_bit_count) - 1)); + return value << (13u - sideset_bit_count); +} + +/*! \brief Encode just the side set bits of an instruction (in optional -`opt` side set mode) + * \ingroup pio_instructions + * + * \note This function does not return a valid instruction encoding; instead it returns an encoding of the side set bits + * suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when + * combining the results of this function with the results of \ref pio_encode_delay as they share the same bits + * within the instruction encoding. + * + * \param sideset_bit_count number of side set bits as would be specified via `.sideset opt` in pioasm + * \param value the value to sideset on the pins + * \return the side set bits to be ORed with an instruction encoding + */ +static inline uint pio_encode_sideset_opt(uint sideset_bit_count, uint value) { + valid_params_if(PIO_INSTRUCTIONS, sideset_bit_count >= 1 && sideset_bit_count <= 4); + valid_params_if(PIO_INSTRUCTIONS, value <= ((1u << sideset_bit_count) - 1)); + return 0x1000u | value << (12u - sideset_bit_count); +} + +/*! \brief Encode an unconditional JMP instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP ` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 0, addr); +} + +/*! \brief Encode a conditional JMP if scratch X zero instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP !X ` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_not_x(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 1, addr); +} + +/*! \brief Encode a conditional JMP if scratch X non-zero (and post-decrement X) instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP X-- ` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_x_dec(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 2, addr); +} + +/*! \brief Encode a conditional JMP if scratch Y zero instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP !Y ` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_not_y(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 3, addr); +} + +/*! \brief Encode a conditional JMP if scratch Y non-zero (and post-decrement Y) instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP Y-- ` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_y_dec(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 4, addr); +} + +/*! \brief Encode a conditional JMP if scratch X not equal scratch Y instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP X!=Y ` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_x_ne_y(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 5, addr); +} + +/*! \brief Encode a conditional JMP if input pin high instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP PIN ` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_pin(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 6, addr); +} + +/*! \brief Encode a conditional JMP if output shift register not empty instruction + * \ingroup pio_instructions + * + * This is the equivalent of `JMP !OSRE ` + * + * \param addr The target address 0-31 (an absolute address within the PIO instruction memory) + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_jmp_not_osre(uint addr) { + return _pio_encode_instr_and_args(pio_instr_bits_jmp, 7, addr); +} + +static inline uint _pio_encode_irq(bool relative, uint irq) { + valid_params_if(PIO_INSTRUCTIONS, irq <= 7); + return (relative ? 0x10u : 0x0u) | irq; +} + +/*! \brief Encode a WAIT for GPIO pin instruction + * \ingroup pio_instructions + * + * This is the equivalent of `WAIT GPIO ` + * + * \param polarity true for `WAIT 1`, false for `WAIT 0` + * \param gpio The real GPIO number 0-31 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_wait_gpio(bool polarity, uint gpio) { + return _pio_encode_instr_and_args(pio_instr_bits_wait, 0u | (polarity ? 4u : 0u), gpio); +} + +/*! \brief Encode a WAIT for pin instruction + * \ingroup pio_instructions + * + * This is the equivalent of `WAIT PIN ` + * + * \param polarity true for `WAIT 1`, false for `WAIT 0` + * \param pin The pin number 0-31 relative to the executing SM's input pin mapping + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_wait_pin(bool polarity, uint pin) { + return _pio_encode_instr_and_args(pio_instr_bits_wait, 1u | (polarity ? 4u : 0u), pin); +} + +/*! \brief Encode a WAIT for IRQ instruction + * \ingroup pio_instructions + * + * This is the equivalent of `WAIT IRQ ` + * + * \param polarity true for `WAIT 1`, false for `WAIT 0` + * \param relative true for a `WAIT IRQ REL`, false for regular `WAIT IRQ ` + * \param irq the irq number 0-7 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline 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)); +} + +/*! \brief Encode an IN instruction + * \ingroup pio_instructions + * + * This is the equivalent of `IN , ` + * + * \param src The source to take data from + * \param count The number of bits 1-32 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_in(enum pio_src_dest src, uint count) { + valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_IN_SRC)); + return _pio_encode_instr_and_src_dest(pio_instr_bits_in, src, count); +} + +/*! \brief Encode an OUT instruction + * \ingroup pio_instructions + * + * This is the equivalent of `OUT , ` + * + * \param dest The destination to write data to + * \param count The number of bits 1-32 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_out(enum pio_src_dest dest, uint count) { + valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_OUT_DEST)); + return _pio_encode_instr_and_src_dest(pio_instr_bits_out, dest, count); +} + +/*! \brief Encode a PUSH instruction + * \ingroup pio_instructions + * + * This is the equivalent of `PUSH , ` + * + * \param if_full true for `PUSH IF_FULL ...`, false for `PUSH ...` + * \param block true for `PUSH ... BLOCK`, false for `PUSH ...` + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline 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); +} + +/*! \brief Encode a PULL instruction + * \ingroup pio_instructions + * + * This is the equivalent of `PULL , ` + * + * \param if_empty true for `PULL IF_EMPTY ...`, false for `PULL ...` + * \param block true for `PULL ... BLOCK`, false for `PULL ...` + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline 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); +} + +/*! \brief Encode a MOV instruction + * \ingroup pio_instructions + * + * This is the equivalent of `MOV , ` + * + * \param dest The destination to write data to + * \param src The source to take data from + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline 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); +} + +/*! \brief Encode a MOV instruction with bit invert + * \ingroup pio_instructions + * + * This is the equivalent of `MOV , ~` + * + * \param dest The destination to write inverted data to + * \param src The source to take data from + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline 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)); +} + +/*! \brief Encode a MOV instruction with bit reverse + * \ingroup pio_instructions + * + * This is the equivalent of `MOV , ::` + * + * \param dest The destination to write bit reversed data to + * \param src The source to take data from + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline 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)); +} + +/*! \brief Encode a IRQ SET instruction + * \ingroup pio_instructions + * + * This is the equivalent of `IRQ SET ` + * + * \param relative true for a `IRQ SET REL`, false for regular `IRQ SET ` + * \param irq the irq number 0-7 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline 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)); +} + +/*! \brief Encode a IRQ WAIT instruction + * \ingroup pio_instructions + * + * This is the equivalent of `IRQ WAIT ` + * + * \param relative true for a `IRQ WAIT REL`, false for regular `IRQ WAIT ` + * \param irq the irq number 0-7 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_irq_wait(bool relative, uint irq) { + return _pio_encode_instr_and_args(pio_instr_bits_irq, 1, _pio_encode_irq(relative, irq)); +} + +/*! \brief Encode a IRQ CLEAR instruction + * \ingroup pio_instructions + * + * This is the equivalent of `IRQ CLEAR ` + * + * \param relative true for a `IRQ CLEAR REL`, false for regular `IRQ CLEAR ` + * \param irq the irq number 0-7 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline 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)); +} + +/*! \brief Encode a SET instruction + * \ingroup pio_instructions + * + * This is the equivalent of `SET , ` + * + * \param dest The destination to apply the value to + * \param value The value 0-31 + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline 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); +} + +/*! \brief Encode a NOP instruction + * \ingroup pio_instructions + * + * This is the equivalent of `NOP` which is itself encoded as `MOV y, y` + * + * \return The instruction encoding with 0 delay and no side set value + * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt + */ +static inline uint pio_encode_nop(void) { + return pio_encode_mov(pio_y, pio_y); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/pio.c b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/pio.c new file mode 100644 index 00000000000..6137566211f --- /dev/null +++ b/targets/TARGET_RASPBERRYPI/pico-sdk/src/rp2_common/hardware_pio/pio.c @@ -0,0 +1,268 @@ +/* + * 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 - 4) 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) { + // PIO index is 0 or 1. + 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 >= (int)base ? index - (int)base : -1; +} + +bool pio_sm_is_claimed(PIO pio, uint sm) { + check_sm_param(sm); + uint which = pio_get_index(pio); + return hw_is_claimed(&claimed, which * NUM_PIO_STATE_MACHINES + sm); +} + +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) { + valid_params_if(PIO, offset < PIO_INSTRUCTION_COUNT); + valid_params_if(PIO, offset + program->length <= PIO_INSTRUCTION_COUNT); + if (program->origin >= 0 && (uint)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, (uint)offset); + hw_claim_unlock(save); + return (uint)offset; +} + +void pio_add_program_at_offset(PIO pio, const pio_program_t *program, uint offset) { + uint32_t save = hw_claim_lock(); + _pio_add_program_at_offset(pio, program, offset); + hw_claim_unlock(save); +} + +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;iinstr_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) { + check_pio_param(pio); + check_sm_param(sm); + uint32_t pinctrl_saved = pio->sm[sm].pinctrl; + uint32_t execctrl_saved = pio->sm[sm].execctrl; + hw_clear_bits(&pio->sm[sm].execctrl, 1u << PIO_SM0_EXECCTRL_OUT_STICKY_LSB); + 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; + pio->sm[sm].execctrl = execctrl_saved; +} + +void pio_sm_set_pins_with_mask(PIO pio, uint sm, uint32_t pinvals, uint32_t pin_mask) { + check_pio_param(pio); + check_sm_param(sm); + uint32_t pinctrl_saved = pio->sm[sm].pinctrl; + uint32_t execctrl_saved = pio->sm[sm].execctrl; + hw_clear_bits(&pio->sm[sm].execctrl, 1u << PIO_SM0_EXECCTRL_OUT_STICKY_LSB); + while (pin_mask) { + uint base = (uint)__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; + pio->sm[sm].execctrl = execctrl_saved; +} + +void pio_sm_set_pindirs_with_mask(PIO pio, uint sm, uint32_t pindirs, uint32_t pin_mask) { + check_pio_param(pio); + check_sm_param(sm); + uint32_t pinctrl_saved = pio->sm[sm].pinctrl; + uint32_t execctrl_saved = pio->sm[sm].execctrl; + hw_clear_bits(&pio->sm[sm].execctrl, 1u << PIO_SM0_EXECCTRL_OUT_STICKY_LSB); + while (pin_mask) { + uint base = (uint)__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; + pio->sm[sm].execctrl = execctrl_saved; +} + +void pio_sm_set_consecutive_pindirs(PIO pio, uint sm, uint pin, uint count, bool is_out) { + check_pio_param(pio); + check_sm_param(sm); + valid_params_if(PIO, pin < 32u); + uint32_t pinctrl_saved = pio->sm[sm].pinctrl; + uint32_t execctrl_saved = pio->sm[sm].execctrl; + hw_clear_bits(&pio->sm[sm].execctrl, 1u << PIO_SM0_EXECCTRL_OUT_STICKY_LSB); + 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; + pio->sm[sm].execctrl = execctrl_saved; +} + +void pio_sm_init(PIO pio, uint sm, uint initial_pc, const pio_sm_config *config) { + valid_params_if(PIO, initial_pc < PIO_INSTRUCTION_COUNT); + // 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); + } +} diff --git a/targets/TARGET_RASPBERRYPI/reimport_pico_sdk.py b/targets/TARGET_RASPBERRYPI/reimport_pico_sdk.py index 6eb9c0dbb4e..0ba7784098a 100644 --- a/targets/TARGET_RASPBERRYPI/reimport_pico_sdk.py +++ b/targets/TARGET_RASPBERRYPI/reimport_pico_sdk.py @@ -12,53 +12,54 @@ import pathlib import shutil import sys +import re from typing import List, Tuple this_script_dir = pathlib.Path(__file__).resolve().parent # List of identifiers to rename b/c they clash with Mbed symbols -IDENTIFIERS_TO_RENAME: List[Tuple[bytes, bytes]] = [ - (b"gpio_irq_handler", b"pico_sdk_gpio_irq_handler"), - (b"gpio_init", b"pico_sdk_gpio_init"), - (b"i2c_init", b"pico_sdk_i2c_init"), - (b"rtc_init", b"pico_sdk_rtc_init"), - (b"spi_init", b"pico_sdk_spi_init"), +IDENTIFIERS_TO_RENAME: List[Tuple[str, str]] = [ + (r"gpio_irq_handler", r"pico_sdk_gpio_irq_handler"), + (r"gpio_init", r"pico_sdk_gpio_init"), + (r"i2c_init", r"pico_sdk_i2c_init"), + (r"rtc_init", r"pico_sdk_rtc_init"), + (r"spi_init", r"pico_sdk_spi_init"), # Rename IRQ handlers to the CMSIS exception names. # Pico SDK does this with macros, but easier to just # do it here. # Based on cmsis/include/rename_exceptions.h. - (b"isr_nmi", b"NMI_Handler"), - (b"isr_hardfault", b"HardFault_Handler"), - (b"isr_svcall", b"SVC_Handler"), - (b"isr_pendsv", b"PendSV_Handler"), - (b"isr_systick", b"SysTick_Handler"), - (b"isr_irq0", b"TIMER_IRQ_0_Handler"), - (b"isr_irq1", b"TIMER_IRQ_1_Handler"), - (b"isr_irq2", b"TIMER_IRQ_2_Handler"), - (b"isr_irq3", b"TIMER_IRQ_3_Handler"), - (b"isr_irq4", b"PWM_IRQ_WRAP_Handler"), - (b"isr_irq5", b"USBCTRL_IRQ_Handler"), - (b"isr_irq6", b"XIP_IRQ_Handler"), - (b"isr_irq7", b"PIO0_IRQ_0_Handler"), - (b"isr_irq8", b"PIO0_IRQ_1_Handler"), - (b"isr_irq9", b"PIO1_IRQ_0_Handler"), - (b"isr_irq10", b"PIO1_IRQ_1_Handler"), - (b"isr_irq11", b"DMA_IRQ_0_Handler"), - (b"isr_irq12", b"DMA_IRQ_1_Handler"), - (b"isr_irq13", b"IO_IRQ_BANK0_Handler"), - (b"isr_irq14", b"IO_IRQ_QSPI_Handler"), - (b"isr_irq15", b"SIO_IRQ_PROC0_Handler"), - (b"isr_irq16", b"SIO_IRQ_PROC1_Handler"), - (b"isr_irq17", b"CLOCKS_IRQ_Handler"), - (b"isr_irq18", b"SPI0_IRQ_Handler"), - (b"isr_irq19", b"SPI1_IRQ_Handler"), - (b"isr_irq20", b"UART0_IRQ_Handler"), - (b"isr_irq21", b"UART1_IRQ_Handler"), - (b"isr_irq22", b"ADC_IRQ_FIFO_Handler"), - (b"isr_irq23", b"I2C0_IRQ_Handler"), - (b"isr_irq24", b"I2C1_IRQ_Handler"), - (b"isr_irq25", b"RTC_IRQ_Handler"), + (r"isr_nmi", r"NMI_Handler"), + (r"isr_hardfault", r"HardFault_Handler"), + (r"isr_svcall", r"SVC_Handler"), + (r"isr_pendsv", r"PendSV_Handler"), + (r"isr_systick", r"SysTick_Handler"), + (r"isr_irq0", r"TIMER_IRQ_0_Handler"), + (r"isr_irq1", r"TIMER_IRQ_1_Handler"), + (r"isr_irq2", r"TIMER_IRQ_2_Handler"), + (r"isr_irq3", r"TIMER_IRQ_3_Handler"), + (r"isr_irq4", r"PWM_IRQ_WRAP_Handler"), + (r"isr_irq5", r"USBCTRL_IRQ_Handler"), + (r"isr_irq6", r"XIP_IRQ_Handler"), + (r"isr_irq7", r"PIO0_IRQ_0_Handler"), + (r"isr_irq8", r"PIO0_IRQ_1_Handler"), + (r"isr_irq9", r"PIO1_IRQ_0_Handler"), + (r"isr_irq10", r"PIO1_IRQ_1_Handler"), + (r"isr_irq11", r"DMA_IRQ_0_Handler"), + (r"isr_irq12", r"DMA_IRQ_1_Handler"), + (r"isr_irq13", r"IO_IRQ_BANK0_Handler"), + (r"isr_irq14", r"IO_IRQ_QSPI_Handler"), + (r"isr_irq15", r"SIO_IRQ_PROC0_Handler"), + (r"isr_irq16", r"SIO_IRQ_PROC1_Handler"), + (r"isr_irq17", r"CLOCKS_IRQ_Handler"), + (r"isr_irq18", r"SPI0_IRQ_Handler"), + (r"isr_irq19", r"SPI1_IRQ_Handler"), + (r"isr_irq20", r"UART0_IRQ_Handler"), + (r"isr_irq21", r"UART1_IRQ_Handler"), + (r"isr_irq22", r"ADC_IRQ_FIFO_Handler"), + (r"isr_irq23", r"I2C0_IRQ_Handler"), + (r"isr_irq24", r"I2C1_IRQ_Handler"), + (r"isr_irq25", r"RTC_IRQ_Handler"), ] # List of files and directories which need to be copied into Mbed. @@ -85,6 +86,8 @@ pathlib.Path("src") / "rp2_common" / "hardware_timer", pathlib.Path("src") / "rp2_common" / "hardware_sync", pathlib.Path("src") / "rp2_common" / "hardware_rtc", + pathlib.Path("src") / "rp2_common" / "hardware_pio", + pathlib.Path("src") / "rp2_common" / "hardware_dma", pathlib.Path("src") / "rp2_common" / "pico_bootrom", pathlib.Path("src") / "rp2_common" / "pico_platform", pathlib.Path("src") / "rp2_common" / "pico_float", @@ -137,11 +140,12 @@ # Load file with open(source_file_path, "rb") as source_file: - source_file_contents = source_file.read() + source_file_contents = source_file.read().decode("UTF-8") # Perform replacements for old_identifier, new_identifier in IDENTIFIERS_TO_RENAME: - source_file_contents = source_file_contents.replace(old_identifier, new_identifier) + # Always require one non-word character before each replacement so we can do a full word match + source_file_contents = re.sub(r"(\W)" + old_identifier, r"\1" + new_identifier, source_file_contents) # Figure out new path relative_path = source_file_path.relative_to(sdk_path) @@ -150,6 +154,6 @@ # Write new contents dest_file_path.parent.mkdir(parents=True, exist_ok=True) with open(dest_file_path, "wb") as dest_file: - dest_file.write(source_file_contents) + dest_file.write(source_file_contents.encode("UTF-8")) print(f"Copied {relative_path}") \ No newline at end of file diff --git a/tools/cmake/mbed_generate_config_header.cmake b/tools/cmake/mbed_generate_config_header.cmake index 92c0b8e06b2..5030f60b265 100644 --- a/tools/cmake/mbed_generate_config_header.cmake +++ b/tools/cmake/mbed_generate_config_header.cmake @@ -7,16 +7,19 @@ function(mbed_write_target_config_header HEADER_PATH) # ARGN: Lists of defines to add to the file. set(TARGET_HEADER_CONTENTS -" /* - Mbed OS Target Define Header. - This contains all of the #defines specific to your target and device. - It is prepended to every source file using the -include compiler option. - AUTOGENERATED by cmake. DO NOT EDIT! - */ +" +/* +Mbed OS Target Define Header. +This contains all of the #defines specific to your target and device. +It is prepended to every source file using the -include compiler option. +AUTOGENERATED by cmake. DO NOT EDIT! +*/ +#ifndef MBED_TARGET_CONFIG_H +#define MBED_TARGET_CONFIG_H ") foreach(DEFINE_LIST ${ARGN}) - string(APPEND TARGET_HEADER_CONTENTS "\n // Defines from ${DEFINE_LIST}:\n") + string(APPEND TARGET_HEADER_CONTENTS "\n// Defines from ${DEFINE_LIST}:\n") foreach(DEFINE_COMMAND ${${DEFINE_LIST}}) # double dereference needed to get contents of list @@ -30,6 +33,8 @@ function(mbed_write_target_config_header HEADER_PATH) # ARGN: Lists of defines t endforeach() endforeach() + string(APPEND TARGET_HEADER_CONTENTS "#endif\n") + # Write the file, using file(GENERATE) so that the timestamp is not updated if the contents don't change file(GENERATE OUTPUT ${HEADER_PATH} CONTENT ${TARGET_HEADER_CONTENTS}) From da9b97f4af5a7e9944ff15a62732349c5ad5afd3 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sun, 7 Jan 2024 12:16:27 -0800 Subject: [PATCH 2/6] Apply Arduino Mbed TLS patches --- connectivity/mbedtls/include/mbedtls/config.h | 4 +- connectivity/mbedtls/mbed_lib.json | 3 - connectivity/mbedtls/mbed_lib.json5 | 11 ++++ .../mbedtls/platform/inc/platform_mbed.h | 7 ++- connectivity/mbedtls/source/x509_crt.c | 4 ++ .../mbedtls/tools/importer/adjust-config.sh | 2 - .../coap-service/unittest/stub/mbedtls_stub.c | 6 ++ .../include/netsocket/TLSSocketWrapper.h | 40 +++++++++++++ .../netsocket/source/TLSSocketWrapper.cpp | 56 +++++++++++++++++++ .../tests/TESTS/netsocket/tls/CMakeLists.txt | 5 +- .../tests/TESTS/netsocket/tls/main.cpp | 4 +- .../tests/TESTS/netsocket/tls/tls_tests.h | 1 + .../tls/tlssocket_cert_in_filesystem.cpp | 50 +++++++++++++++++ .../test_TLSSocketWrapper.cpp | 6 ++ .../TLSSocketWrapper/tls_test_config.h | 2 +- 15 files changed, 188 insertions(+), 13 deletions(-) delete mode 100644 connectivity/mbedtls/mbed_lib.json create mode 100644 connectivity/mbedtls/mbed_lib.json5 create mode 100644 connectivity/netsocket/tests/TESTS/netsocket/tls/tlssocket_cert_in_filesystem.cpp diff --git a/connectivity/mbedtls/include/mbedtls/config.h b/connectivity/mbedtls/include/mbedtls/config.h index 249e5e38fb5..6d428ae0b48 100644 --- a/connectivity/mbedtls/include/mbedtls/config.h +++ b/connectivity/mbedtls/include/mbedtls/config.h @@ -1204,7 +1204,7 @@ * * Enable functions that use the filesystem. */ -//#define MBEDTLS_FS_IO +#define MBEDTLS_FS_IO /** * \def MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES @@ -3227,7 +3227,7 @@ * on it, and considering stronger message digests instead. * */ -//#define MBEDTLS_SHA1_C +#define MBEDTLS_SHA1_C /** * \def MBEDTLS_SHA256_C diff --git a/connectivity/mbedtls/mbed_lib.json b/connectivity/mbedtls/mbed_lib.json deleted file mode 100644 index f3f5377517e..00000000000 --- a/connectivity/mbedtls/mbed_lib.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "mbedtls" -} diff --git a/connectivity/mbedtls/mbed_lib.json5 b/connectivity/mbedtls/mbed_lib.json5 new file mode 100644 index 00000000000..a1d6f4226fc --- /dev/null +++ b/connectivity/mbedtls/mbed_lib.json5 @@ -0,0 +1,11 @@ +{ + "name": "mbedtls", + "config": { + "entropy-nv-seed": { + "macro_name": "MBEDTLS_ENTROPY_NV_SEED", + "help": "Set to 1 to enable Mbed TLS's Non-Volatile Storage entropy source. This source allows usage of Mbed TLS on devices which do not have a cryptographic RNG.", + "value": null, + // Note: see here for details on how to implement the seed I/O: https://os.mbed.com/docs/mbed-os/v6.16/porting/entropy-sources.html + } + } +} diff --git a/connectivity/mbedtls/platform/inc/platform_mbed.h b/connectivity/mbedtls/platform/inc/platform_mbed.h index 5f7d058cfa9..146bdfcb812 100644 --- a/connectivity/mbedtls/platform/inc/platform_mbed.h +++ b/connectivity/mbedtls/platform/inc/platform_mbed.h @@ -31,6 +31,10 @@ * \ingroup public-crypto */ +#if CONFIG_MBEDTLS_ENTROPY_NV_SEED +#define MBEDTLS_ENTROPY_NV_SEED +#endif + #if defined(FEATURE_EXPERIMENTAL_API) && defined(FEATURE_PSA) #if defined(MBEDTLS_ENTROPY_NV_SEED) @@ -72,9 +76,6 @@ #include "mbedtls_device.h" #endif -// Include SHA1 certificate support. Used for a lot of root CAs. -#define MBEDTLS_SHA1_C 1 - /* * MBEDTLS_ERR_PLATFORM_HW_FAILED is deprecated and should not be used. */ diff --git a/connectivity/mbedtls/source/x509_crt.c b/connectivity/mbedtls/source/x509_crt.c index a623c57a6c1..57d5c788f31 100644 --- a/connectivity/mbedtls/source/x509_crt.c +++ b/connectivity/mbedtls/source/x509_crt.c @@ -74,7 +74,11 @@ #if !defined(_WIN32) || defined(EFIX64) || defined(EFI32) #include #include +#if defined(__MBED__) +#include +#else #include +#endif /* __MBED__ */ #endif /* !_WIN32 || EFIX64 || EFI32 */ #endif diff --git a/connectivity/mbedtls/tools/importer/adjust-config.sh b/connectivity/mbedtls/tools/importer/adjust-config.sh index 143bda15607..f46987b402b 100755 --- a/connectivity/mbedtls/tools/importer/adjust-config.sh +++ b/connectivity/mbedtls/tools/importer/adjust-config.sh @@ -53,7 +53,6 @@ conf unset MBEDTLS_TIMING_C # not supported on all targets with mbed OS, nor used by mbed Client conf unset MBEDTLS_HAVE_TIME_DATE -conf unset MBEDTLS_FS_IO conf unset MBEDTLS_PSA_ITS_FILE_C conf unset MBEDTLS_PSA_CRYPTO_STORAGE_C conf set MBEDTLS_NO_PLATFORM_ENTROPY @@ -89,7 +88,6 @@ conf unset MBEDTLS_PEM_WRITE_C conf unset MBEDTLS_PKCS5_C conf unset MBEDTLS_PKCS12_C conf unset MBEDTLS_RIPEMD160_C -conf unset MBEDTLS_SHA1_C conf unset MBEDTLS_XTEA_C conf set MBEDTLS_CMAC_C diff --git a/connectivity/nanostack/coap-service/test/coap-service/unittest/stub/mbedtls_stub.c b/connectivity/nanostack/coap-service/test/coap-service/unittest/stub/mbedtls_stub.c index b63eb0269d2..32026c4fe6e 100644 --- a/connectivity/nanostack/coap-service/test/coap-service/unittest/stub/mbedtls_stub.c +++ b/connectivity/nanostack/coap-service/test/coap-service/unittest/stub/mbedtls_stub.c @@ -303,6 +303,12 @@ int mbedtls_x509_crt_parse(mbedtls_x509_crt *a, const unsigned char *b, size_t c return mbedtls_stub.expected_int; } +int mbedtls_x509_crt_parse_path(mbedtls_x509_crt *a, const char *b) +{ + // means 5 valid certificates found + return 5; +} + int mbedtls_x509_crt_info(char *buf, size_t size, const char *prefix, const mbedtls_x509_crt *crt) { diff --git a/connectivity/netsocket/include/netsocket/TLSSocketWrapper.h b/connectivity/netsocket/include/netsocket/TLSSocketWrapper.h index d188aa8a4e1..8fad631f49d 100644 --- a/connectivity/netsocket/include/netsocket/TLSSocketWrapper.h +++ b/connectivity/netsocket/include/netsocket/TLSSocketWrapper.h @@ -116,6 +116,46 @@ class TLSSocketWrapper : public Socket { */ nsapi_error_t set_root_ca_cert(const char *root_ca_pem); + /** + * @brief Sets the Root CA certificate to a collection of files on the filesystem. + * + * All files in the supplied directory will be scanned. Note that to set up a filesystem, + * you must mount one or more block devices before calling this function. + * + * @note Must be called before calling connect() + * + * @param root_ca_path Path containing Root CA Certificate files in any Mbed TLS-supported format. + * This can point to a directory on any mounted filesystem. + * @retval NSAPI_ERROR_OK on success. + * @retval NSAPI_ERROR_NO_MEMORY in case there is not enough memory to allocate certificate. + * @retval NSAPI_ERROR_PARAMETER in case the provided root_ca parameter failed parsing. + * + */ + nsapi_error_t set_root_ca_cert_path(const char *root_ca_path); + + /** Appends the certificate to an existing CA chain. + * + * @note Must be called before calling connect() + * + * @param root_ca Root CA Certificate in any Mbed TLS-supported format. + * @param len Length of certificate (including terminating 0 for PEM). + * @retval NSAPI_ERROR_OK on success. + * @retval NSAPI_ERROR_NO_MEMORY in case there is not enough memory to allocate certificate. + * @retval NSAPI_ERROR_PARAMETER in case the provided root_ca parameter failed parsing. + */ + nsapi_error_t append_root_ca_cert(const void *root_ca, size_t len); + + /** Appends the certificate to an existing CA chain. + * + * @note Must be called before calling connect() + * + * @param root_ca_pem Root CA Certificate in PEM format. + * @retval NSAPI_ERROR_OK on success. + * @retval NSAPI_ERROR_NO_MEMORY in case there is not enough memory to allocate certificate. + * @retval NSAPI_ERROR_PARAMETER in case the provided root_ca parameter failed parsing. + */ + nsapi_error_t append_root_ca_cert(const char *root_ca_pem); + /** Sets client certificate, and client private key. * * @param client_cert Client certification in PEM or DER format. diff --git a/connectivity/netsocket/source/TLSSocketWrapper.cpp b/connectivity/netsocket/source/TLSSocketWrapper.cpp index 29ee13271fb..e7b16d26c33 100644 --- a/connectivity/netsocket/source/TLSSocketWrapper.cpp +++ b/connectivity/netsocket/source/TLSSocketWrapper.cpp @@ -136,6 +136,62 @@ nsapi_error_t TLSSocketWrapper::set_root_ca_cert(const char *root_ca_pem) return set_root_ca_cert(root_ca_pem, strlen(root_ca_pem) + 1); } +nsapi_error_t TLSSocketWrapper::set_root_ca_cert_path(const char *root_ca_path) +{ +#if !defined(MBEDTLS_X509_CRT_PARSE_C) || !defined(MBEDTLS_FS_IO) + return NSAPI_ERROR_UNSUPPORTED; +#else + mbedtls_x509_crt *crt; + + crt = new (std::nothrow) mbedtls_x509_crt; + if (!crt) { + return NSAPI_ERROR_NO_MEMORY; + } + + mbedtls_x509_crt_init(crt); + + /* Parse CA certification */ + int ret = mbedtls_x509_crt_parse_path(crt, root_ca_path); + if (ret < 0) { + print_mbedtls_error("mbedtls_x509_crt_parse", ret); + mbedtls_x509_crt_free(crt); + delete crt; + return NSAPI_ERROR_PARAMETER; + } + set_ca_chain(crt); + _cacert_allocated = true; + return NSAPI_ERROR_OK; +#endif +} + +nsapi_error_t TLSSocketWrapper::append_root_ca_cert(const void *root_ca, size_t len) +{ +#if !defined(MBEDTLS_X509_CRT_PARSE_C) + return NSAPI_ERROR_UNSUPPORTED; +#else + mbedtls_x509_crt *crt; + + crt = get_ca_chain(); + if (!crt) { + return NSAPI_ERROR_NO_MEMORY; + } + + /* Parse CA certification */ + int ret; + if ((ret = mbedtls_x509_crt_parse(crt, static_cast(root_ca), + len)) != 0) { + print_mbedtls_error("mbedtls_x509_crt_parse", ret); + return NSAPI_ERROR_PARAMETER; + } + return NSAPI_ERROR_OK; +#endif +} + +nsapi_error_t TLSSocketWrapper::append_root_ca_cert(const char *root_ca_pem) +{ + return append_root_ca_cert(root_ca_pem, strlen(root_ca_pem) + 1); +} + nsapi_error_t TLSSocketWrapper::set_client_cert_key(const char *client_cert_pem, const char *client_private_key_pem) { return set_client_cert_key(client_cert_pem, strlen(client_cert_pem) + 1, client_private_key_pem, strlen(client_private_key_pem) + 1); diff --git a/connectivity/netsocket/tests/TESTS/netsocket/tls/CMakeLists.txt b/connectivity/netsocket/tests/TESTS/netsocket/tls/CMakeLists.txt index 76319155545..0399ebafcf0 100644 --- a/connectivity/netsocket/tests/TESTS/netsocket/tls/CMakeLists.txt +++ b/connectivity/netsocket/tests/TESTS/netsocket/tls/CMakeLists.txt @@ -19,7 +19,8 @@ list( tlssocket_endpoint_close.cpp tlssocket_echotest.cpp tlssocket_echotest_burst.cpp - tlssocket_connect_invalid.cpp + tlssocket_connect_invalid.cpp + tlssocket_cert_in_filesystem.cpp ) if(MBED_GREENTEA_TEST_BAREMETAL) @@ -33,6 +34,8 @@ mbed_greentea_add_test( ${TEST_SOURCE_LIST} TEST_REQUIRED_LIBS mbed-netsocket + mbed-storage-blockdevice + mbed-storage-littlefs TEST_SKIPPED ${TEST_SKIPPED} ) diff --git a/connectivity/netsocket/tests/TESTS/netsocket/tls/main.cpp b/connectivity/netsocket/tests/TESTS/netsocket/tls/main.cpp index 6693e1560c7..c3a5d7e86ab 100644 --- a/connectivity/netsocket/tests/TESTS/netsocket/tls/main.cpp +++ b/connectivity/netsocket/tests/TESTS/netsocket/tls/main.cpp @@ -218,7 +218,6 @@ static void test_failure_handler(const failure_t failure) Case cases[] = { -// Disable tests temporarily till echo server is back on Case("TLSSOCKET_ECHOTEST", TLSSOCKET_ECHOTEST), Case("TLSSOCKET_ECHOTEST_NONBLOCK", TLSSOCKET_ECHOTEST_NONBLOCK), Case("TLSSOCKET_CONNECT_INVALID", TLSSOCKET_CONNECT_INVALID), @@ -235,6 +234,9 @@ Case cases[] = { Case("TLSSOCKET_SEND_REPEAT", TLSSOCKET_SEND_REPEAT), Case("TLSSOCKET_SEND_TIMEOUT", TLSSOCKET_SEND_TIMEOUT), Case("TLSSOCKET_NO_CERT", TLSSOCKET_NO_CERT), +#if defined(MBEDTLS_SSL_CLI_C) && defined(MBEDTLS_FS_IO) + Case("TLSSOCKET_CERT_IN_FILESYSTEM", TLSSOCKET_CERT_IN_FILESYSTEM), +#endif // Temporarily removing this test, as TLS library consumes too much memory // and we see frequent memory allocation failures on architectures with less // RAM such as DISCO_L475VG_IOT1A and NUCLEO_F207ZG (both have 128 kB RAM) diff --git a/connectivity/netsocket/tests/TESTS/netsocket/tls/tls_tests.h b/connectivity/netsocket/tests/TESTS/netsocket/tls/tls_tests.h index a8b8c851ad5..cf471b8f6c7 100644 --- a/connectivity/netsocket/tests/TESTS/netsocket/tls/tls_tests.h +++ b/connectivity/netsocket/tests/TESTS/netsocket/tls/tls_tests.h @@ -91,6 +91,7 @@ void TLSSOCKET_SEND_REPEAT(); void TLSSOCKET_NO_CERT(); void TLSSOCKET_SIMULTANEOUS(); void TLSSOCKET_SEND_TIMEOUT(); +void TLSSOCKET_CERT_IN_FILESYSTEM(); #endif // defined(MBEDTLS_SSL_CLI_C) || defined(DOXYGEN_ONLY) diff --git a/connectivity/netsocket/tests/TESTS/netsocket/tls/tlssocket_cert_in_filesystem.cpp b/connectivity/netsocket/tests/TESTS/netsocket/tls/tlssocket_cert_in_filesystem.cpp new file mode 100644 index 00000000000..cf3dff2e932 --- /dev/null +++ b/connectivity/netsocket/tests/TESTS/netsocket/tls/tlssocket_cert_in_filesystem.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, Arduino SA, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mbed.h" +#include "TLSSocket.h" +#include "greentea-client/test_env.h" +#include "unity/unity.h" +#include "utest.h" +#include "tls_tests.h" +#include "HeapBlockDevice.h" +#include "LittleFileSystem.h" + +using namespace utest::v1; + +void TLSSOCKET_CERT_IN_FILESYSTEM() +{ + SKIP_IF_TCP_UNSUPPORTED(); + + HeapBlockDevice bd(1024 * 10); + LittleFileSystem fs("fs"); + TEST_ASSERT_EQUAL(0, fs.format(&bd)); + TEST_ASSERT_EQUAL(0, fs.mount(&bd)); + + FILE *fp = fopen("/fs/certs.pem", "wb"); + int ret = fwrite(tls_global::cert, strlen(tls_global::cert), 1, fp); + fclose(fp); + + TLSSocket sock; + TEST_ASSERT_EQUAL(NSAPI_ERROR_OK, sock.open(NetworkInterface::get_default_instance())); + TEST_ASSERT_EQUAL(NSAPI_ERROR_OK, sock.set_root_ca_cert_path("/fs")); + + SocketAddress a; + TEST_ASSERT_EQUAL(NSAPI_ERROR_OK, NetworkInterface::get_default_instance()->gethostbyname(ECHO_SERVER_ADDR, &a)); + a.set_port(ECHO_SERVER_PORT_TLS); + TEST_ASSERT_EQUAL(NSAPI_ERROR_OK, sock.connect(a)); +} \ No newline at end of file diff --git a/connectivity/netsocket/tests/UNITTESTS/netsocket/TLSSocketWrapper/test_TLSSocketWrapper.cpp b/connectivity/netsocket/tests/UNITTESTS/netsocket/TLSSocketWrapper/test_TLSSocketWrapper.cpp index b0e473a401e..6ba24218175 100644 --- a/connectivity/netsocket/tests/UNITTESTS/netsocket/TLSSocketWrapper/test_TLSSocketWrapper.cpp +++ b/connectivity/netsocket/tests/UNITTESTS/netsocket/TLSSocketWrapper/test_TLSSocketWrapper.cpp @@ -399,6 +399,12 @@ TEST_F(TestTLSSocketWrapper, set_root_ca_cert_invalid) EXPECT_EQ(wrapper->set_root_ca_cert(cert, strlen(cert)), NSAPI_ERROR_PARAMETER); } +TEST_F(TestTLSSocketWrapper, set_root_ca_cert_path) +{ + EXPECT_EQ(transport->open(&stack), NSAPI_ERROR_OK); + EXPECT_EQ(wrapper->set_root_ca_cert_path("/"), NSAPI_ERROR_OK); +} + TEST_F(TestTLSSocketWrapper, set_client_cert_key) { EXPECT_EQ(wrapper->get_own_cert(), static_cast(NULL)); diff --git a/connectivity/netsocket/tests/UNITTESTS/netsocket/TLSSocketWrapper/tls_test_config.h b/connectivity/netsocket/tests/UNITTESTS/netsocket/TLSSocketWrapper/tls_test_config.h index 4f9ce6b96c5..ad2d3d8d44c 100644 --- a/connectivity/netsocket/tests/UNITTESTS/netsocket/TLSSocketWrapper/tls_test_config.h +++ b/connectivity/netsocket/tests/UNITTESTS/netsocket/TLSSocketWrapper/tls_test_config.h @@ -19,6 +19,6 @@ #define UNITTESTS_FEATURES_NETSOCKET_TLSSOCKET_TLS_TEST_CONFIG_H_ #define MBEDTLS_SSL_CLI_C - +#define MBEDTLS_FS_IO #endif /* UNITTESTS_FEATURES_NETSOCKET_TLSSOCKET_TLS_TEST_CONFIG_H_ */ From 4e13dfaae679cf3f0e8a6baa613b510b96898bd0 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Tue, 9 Jan 2024 01:51:48 -0800 Subject: [PATCH 3/6] Apply linker script patch, add missing CXX standard --- .../TARGET_RP2040/TOOLCHAIN_GCC_ARM/memmap_default_mbed.ld | 3 +++ targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c | 2 +- tools/cmake/mbed_toolchain.cmake | 6 ++++++ tools/cmake/profiles/debug.cmake | 2 -- tools/cmake/profiles/develop.cmake | 1 - 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/TOOLCHAIN_GCC_ARM/memmap_default_mbed.ld b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/TOOLCHAIN_GCC_ARM/memmap_default_mbed.ld index e09304cdbdb..d4ef6bd2912 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/TOOLCHAIN_GCC_ARM/memmap_default_mbed.ld +++ b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/TOOLCHAIN_GCC_ARM/memmap_default_mbed.ld @@ -51,6 +51,9 @@ SECTIONS and checksummed. It is usually built by the boot_stage2 target in the Raspberry Pi Pico SDK */ + .second_stage_ota : { + KEEP (*(.second_stage_ota)) + } > FLASH .flash_begin : { __flash_binary_start = .; diff --git a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c index 4f3007bbd7e..a5cf6995f98 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c +++ b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c @@ -26,7 +26,7 @@ void analogin_init(analogin_t *obj, PinName pin) /* Lookup the corresponding ADC channel for a given pin. */ obj->channel = pinmap_find_function(pin, PinMap_ADC); /* Make sure GPIO is high-impedance, no pullups etc. */ - adc_pico_sdk_gpio_init(pin); + adc_gpio_init(pin); /* Check if the ADC channel we just configured belongs to the * temperature sensor. If that's the case, enable the temperature * sensor. diff --git a/tools/cmake/mbed_toolchain.cmake b/tools/cmake/mbed_toolchain.cmake index fa40e10f438..fd5f85d1e92 100644 --- a/tools/cmake/mbed_toolchain.cmake +++ b/tools/cmake/mbed_toolchain.cmake @@ -90,3 +90,9 @@ list_to_space_separated(CMAKE_EXE_LINKER_FLAGS_INIT ${link_options}) set(CMAKE_C_FLAGS_INIT "${CMAKE_C_FLAGS_INIT} ") set(CMAKE_CXX_FLAGS_INIT "${CMAKE_CXX_FLAGS_INIT} ") set(CMAKE_ASM_FLAGS_INIT "${CMAKE_ASM_FLAGS_INIT} ") + +# Set language standards +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_EXTENSIONS TRUE) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_EXTENSIONS TRUE) diff --git a/tools/cmake/profiles/debug.cmake b/tools/cmake/profiles/debug.cmake index 62f391dd2aa..45abcbd3ebf 100644 --- a/tools/cmake/profiles/debug.cmake +++ b/tools/cmake/profiles/debug.cmake @@ -7,7 +7,6 @@ function(mbed_set_profile_options target mbed_toolchain) if(${mbed_toolchain} STREQUAL "GCC_ARM") list(APPEND profile_c_compile_options - "-c" "-Og" ) target_compile_options(${target} @@ -16,7 +15,6 @@ function(mbed_set_profile_options target mbed_toolchain) ) list(APPEND profile_cxx_compile_options - "-c" "-fno-rtti" "-Wvla" "-Og" diff --git a/tools/cmake/profiles/develop.cmake b/tools/cmake/profiles/develop.cmake index 331fa185bc2..08fcee1ebc0 100644 --- a/tools/cmake/profiles/develop.cmake +++ b/tools/cmake/profiles/develop.cmake @@ -7,7 +7,6 @@ function(mbed_set_profile_options target mbed_toolchain) if(${mbed_toolchain} STREQUAL "GCC_ARM") list(APPEND profile_c_compile_options - "-c" "-Os" ) target_compile_options(${target} From c452370c9d4a73ce9ae81409018e305f280ab8f6 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Thu, 11 Jan 2024 00:47:21 -0800 Subject: [PATCH 4/6] Fix SDBlockDevice compile error, fix "no rule to make mbed-target-config.h" --- CMakeLists.txt | 2 +- .../COMPONENT_SD/source/SDBlockDevice.cpp | 2 ++ targets/targets.json5 | 27 ++++++++++--------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cf161556ea..ecfbca1c53a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -285,7 +285,7 @@ if(NOT MBED_IS_NATIVE_BUILD) mbed_create_distro(mbed-os ${MBED_TARGET_CMAKE_NAME} mbed-core-flags mbed-core-sources mbed-rtos-flags mbed-rtos-sources) # Set up the linker script and hook it up to the top-level OS targets - mbed_setup_linker_script(mbed-baremetal mbed-os ${CMAKE_CURRENT_BINARY_DIR}/mbed-target-config.h) + mbed_setup_linker_script(mbed-baremetal mbed-os ${CMAKE_CURRENT_BINARY_DIR}/generated-headers/mbed-target-config.h) # Make sure that things linking mbed-core-flags can also get the target-specific include dirs and flags. mbed_extract_flags(${MBED_TARGET_CMAKE_NAME}-flags ${MBED_TARGET_CMAKE_NAME}) diff --git a/storage/blockdevice/COMPONENT_SD/source/SDBlockDevice.cpp b/storage/blockdevice/COMPONENT_SD/source/SDBlockDevice.cpp index 73569fb6fbf..db3f1338413 100644 --- a/storage/blockdevice/COMPONENT_SD/source/SDBlockDevice.cpp +++ b/storage/blockdevice/COMPONENT_SD/source/SDBlockDevice.cpp @@ -441,11 +441,13 @@ int SDBlockDevice::init() return BD_ERROR_OK; } +#if DEVICE_SPI_ASYNCH void SDBlockDevice::set_async_spi_mode(bool enabled, DMAUsage dma_usage_hint) { _async_spi_enabled = enabled; _spi.set_dma_usage(dma_usage_hint); } +#endif int SDBlockDevice::deinit() { diff --git a/targets/targets.json5 b/targets/targets.json5 index bb03d4cccda..57df8503104 100644 --- a/targets/targets.json5 +++ b/targets/targets.json5 @@ -7205,6 +7205,8 @@ ], "macros_add": [ "CONFIG_GPIO_AS_PINRESET", + + // Make room for the bootloader "MBED_APP_START=0x10000", "MBED_APP_SIZE=0xf0000" ], @@ -7216,20 +7218,19 @@ }, "ARDUINO_NANO33BLE_SWD": { "inherits": [ - "MCU_NRF52840" - ], - "features_add": [ - "STORAGE" + "ARDUINO_NANO33BLE" ], - "components_remove": [ - "QSPIF" - ], - "device_has_remove": [ - "QSPI", - "ITM" - ], - "macros_add": [ - "CONFIG_GPIO_AS_PINRESET" + + // for SWD we default to UART console + "overrides": { + "console-usb": false, + "console-uart": true + }, + + // For SWD we don't want to leave space for the bootloader + "macros_remove": [ + "MBED_APP_START=0x10000", + "MBED_APP_SIZE=0xf0000" ] }, "NUMAKER_PFM_NUC472": { From 0213109392c9690e419d075759f2f6f47ee92f23 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Thu, 11 Jan 2024 22:01:41 -0800 Subject: [PATCH 5/6] Fix missing source file for RPi Pico --- targets/TARGET_RASPBERRYPI/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/targets/TARGET_RASPBERRYPI/CMakeLists.txt b/targets/TARGET_RASPBERRYPI/CMakeLists.txt index e6f82e5074b..b65e278e816 100644 --- a/targets/TARGET_RASPBERRYPI/CMakeLists.txt +++ b/targets/TARGET_RASPBERRYPI/CMakeLists.txt @@ -144,6 +144,7 @@ target_include_directories(mbed-raspberrypi target_sources(mbed-raspberrypi INTERFACE + pico-sdk/src/rp2_common/hardware_adc/adc.c pico-sdk/src/rp2_common/hardware_flash/flash.c pico-sdk/src/rp2_common/hardware_uart/uart.c pico-sdk/src/rp2_common/hardware_spi/spi.c From be718506f3f7d54eebcc459d8cdeeffa5bf022db Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Thu, 11 Jan 2024 23:15:38 -0800 Subject: [PATCH 6/6] Fix missing licenses --- .../mbedtls/tools/importer/adjust-config.sh | 15 +++++++++++- .../importer/adjust-no-entropy-config.sh | 15 +++++++++++- .../TARGET_RP2040/.mbedignore | 23 ------------------- .../TARGET_RP2040/PeripheralPins.c | 18 +++++++++++++++ .../TARGET_RP2040/analogin_api.c | 18 +++++++++++++++ 5 files changed, 64 insertions(+), 25 deletions(-) delete mode 100644 targets/TARGET_RASPBERRYPI/TARGET_RP2040/.mbedignore diff --git a/connectivity/mbedtls/tools/importer/adjust-config.sh b/connectivity/mbedtls/tools/importer/adjust-config.sh index f46987b402b..7d7b53a79f9 100755 --- a/connectivity/mbedtls/tools/importer/adjust-config.sh +++ b/connectivity/mbedtls/tools/importer/adjust-config.sh @@ -2,7 +2,20 @@ # # This file is part of mbed TLS (https://tls.mbed.org) # -# Copyright (c) 2015-2016, ARM Limited, All Rights Reserved +# Copyright (c) 2023, Arm Limited, All Rights Reserved +# +# SPDX-License-Identifier: Apache-2.0 +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# * http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # Purpose # diff --git a/connectivity/mbedtls/tools/importer/adjust-no-entropy-config.sh b/connectivity/mbedtls/tools/importer/adjust-no-entropy-config.sh index 10abcc264f5..40cc18ef3ab 100755 --- a/connectivity/mbedtls/tools/importer/adjust-no-entropy-config.sh +++ b/connectivity/mbedtls/tools/importer/adjust-no-entropy-config.sh @@ -2,7 +2,20 @@ # # This file is part of mbed TLS (https://tls.mbed.org) # -# Copyright (c) 2018, ARM Limited, All Rights Reserved +# Copyright (c) 2018, Arm Limited, All Rights Reserved +# +# SPDX-License-Identifier: Apache-2.0 +# Licensed under the Apache License, Version 2.0 (the License); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# * http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an AS IS BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # Purpose # diff --git a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/.mbedignore b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/.mbedignore deleted file mode 100644 index 0581f97d6eb..00000000000 --- a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/.mbedignore +++ /dev/null @@ -1,23 +0,0 @@ -pico-sdk/common/pico_stdlib/include/pico/* -pico-sdk/common/pico_time/include/pico/* -pico-sdk/rp2_common/pico_stdio* -pico-sdk/rp2_common/pico_printf* -pico-sdk/boards/include/boards/* -pico-sdk/common/pico_base/include/pico/* -pico-sdk/rp2_common/boot_stage2/* -pico-sdk/rp2_common/pico_malloc/* -pico-sdk/rp2_common/pico_stdlib/ -pico-sdk/rp2_common/pico_mem_ops/* -pico-sdk/rp2_common/pico_double/double_aeabi.S -pico-sdk/rp2_common/pico_double/double_none.S -pico-sdk/rp2_common/pico_float/float_aeabi.S -pico-sdk/rp2_common/pico_float/float_none.S -pico-sdk/rp2_common/pico_float/include/pico/* -pico-sdk/rp2_common/pico_standard_link/new_delete.cpp -pico-sdk/rp2_common/pico_standard_link/*.ld -pico-sdk/rp2_common/pico_unique_id/* -pico-sdk/rp2_common/hardware_divider/* -pico-sdk/rp2_common/hardware_spi/include/hardware/* -pico-sdk/rp2040/hardware_structs/include/hardware/structs/* -pico-sdk/rp2040/hardware_regs/include/hardware/regs/* -pico-sdk/host/* diff --git a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/PeripheralPins.c b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/PeripheralPins.c index 40e4840b348..2dbb39a25e5 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/PeripheralPins.c +++ b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/PeripheralPins.c @@ -1,3 +1,21 @@ +/* mbed Microcontroller Library + * Copyright (c) 2019, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #include "pinmap.h" #include "objects.h" #include "PeripheralPins.h" diff --git a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c index a5cf6995f98..a89123f0b0a 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c +++ b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c @@ -1,3 +1,21 @@ +/* mbed Microcontroller Library + * Copyright (c) 2019, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #include "mbed_assert.h" #include "analogin_api.h" #include "hardware/adc.h"