diff --git a/boards/rpi-pico/Kconfig b/boards/rpi-pico/Kconfig index dd6e0d414135..c83fc56a7efc 100644 --- a/boards/rpi-pico/Kconfig +++ b/boards/rpi-pico/Kconfig @@ -14,5 +14,6 @@ config BOARD_RPI_PICO select CPU_MODEL_RP2040 select HAS_PERIPH_ADC select HAS_PERIPH_UART + select HAS_PERIPH_SPI select HAVE_SAUL_GPIO diff --git a/boards/rpi-pico/Makefile.features b/boards/rpi-pico/Makefile.features index 57455378e57d..a8ac4884251f 100644 --- a/boards/rpi-pico/Makefile.features +++ b/boards/rpi-pico/Makefile.features @@ -2,5 +2,6 @@ CPU := rpx0xx # Put defined MCU peripherals here (in alphabetical order) FEATURES_PROVIDED += periph_adc +FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart diff --git a/boards/rpi-pico/include/periph_conf.h b/boards/rpi-pico/include/periph_conf.h index d8f99513cc50..aff1c49a3a4e 100644 --- a/boards/rpi-pico/include/periph_conf.h +++ b/boards/rpi-pico/include/periph_conf.h @@ -48,6 +48,23 @@ static const uart_conf_t uart_config[] = { #define UART_NUMOF ARRAY_SIZE(uart_config) +static const spi_conf_t spi_config[] = { + { + .dev = SPI0, + .miso_pin = GPIO_PIN(0, 4), + .mosi_pin = GPIO_PIN(0, 3), + .clk_pin = GPIO_PIN(0, 2) + }, + { + .dev = SPI1, + .miso_pin = GPIO_PIN(0, 12), + .mosi_pin = GPIO_PIN(0, 11), + .clk_pin = GPIO_PIN(0, 10) + } +}; + +#define SPI_NUMOF ARRAY_SIZE(spi_config) + static const timer_channel_conf_t timer0_channel_config[] = { { .irqn = TIMER_IRQ_0_IRQn diff --git a/cpu/rpx0xx/include/periph_cpu.h b/cpu/rpx0xx/include/periph_cpu.h index a7236d0c3e8d..f779bc438a70 100644 --- a/cpu/rpx0xx/include/periph_cpu.h +++ b/cpu/rpx0xx/include/periph_cpu.h @@ -731,6 +731,39 @@ void rosc_stop(void); /** @} */ +/** + * @brief Override SPI clock speed values + * @{ + */ +#define HAVE_SPI_CLK_T +enum { + SPI_CLK_100KHZ = KHZ(100), /**< drive the SPI bus with 100KHz */ + SPI_CLK_400KHZ = KHZ(400), /**< drive the SPI bus with 400KHz */ + SPI_CLK_1MHZ = MHZ(1), /**< drive the SPI bus with 1MHz */ + SPI_CLK_5MHZ = MHZ(5), /**< drive the SPI bus with 5MHz */ + SPI_CLK_10MHZ = MHZ(10), /**< drive the SPI bus with 10MHz */ +}; + +/** + * @brief SPI clock type + */ +typedef uint32_t spi_clk_t; +/** @} */ + +/** + * @brief Configuration details for an SPI interface needed by the RPX0XX peripheral + */ +typedef struct { + SPI0_Type *dev; /**< Base address of the I/O registers of the device */ + gpio_t miso_pin; /**< GPIO pin to use for MISO */ + gpio_t mosi_pin; /**< GPIO pin to use for MOSI */ + gpio_t clk_pin; /**< GPIO pin to use for CLK */ +} spi_conf_t; + +#define PERIPH_SPI_NEEDS_TRANSFER_REG +#define PERIPH_SPI_NEEDS_TRANSFER_REGS +#define PERIPH_SPI_NEEDS_TRANSFER_BYTE + #ifdef __cplusplus } #endif diff --git a/cpu/rpx0xx/periph/spi.c b/cpu/rpx0xx/periph/spi.c new file mode 100644 index 000000000000..da174a9185fd --- /dev/null +++ b/cpu/rpx0xx/periph/spi.c @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2023 Frank Engelhardt + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_rpx0xx + * @{ + * + * @file + * @brief Implementation of SPI. + * + * @author Frank Engelhardt + * + * @} + */ + +#include "assert.h" +#include "bitarithm.h" +#include "cpu.h" +#include "mutex.h" +#include "periph/gpio.h" +#include "periph/spi.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/** + * @brief Allocate one lock per SPI device. + */ +static mutex_t locks[SPI_NUMOF]; + +/** + * @brief Save the clock prescaler values for faster bus acquisition. + */ +typedef struct { + spi_clk_t clk; + uint8_t cpsdvsr; + uint8_t scr; +} _pl022_clk_t; +static _pl022_clk_t pl022_clk[SPI_NUMOF]; + +gpio_t spi_pin_clk(spi_t spi) +{ + assert((unsigned)spi < SPI_NUMOF); + return spi_config[spi].clk_pin; +} + +gpio_t spi_pin_miso(spi_t spi) +{ + assert((unsigned)spi < SPI_NUMOF); + return spi_config[spi].miso_pin; +} + +gpio_t spi_pin_mosi(spi_t spi) +{ + assert((unsigned)spi < SPI_NUMOF); + return spi_config[spi].mosi_pin; +} + +static void _poweron(spi_t spi) +{ + uint32_t reset_bit_mask = (spi_config[spi].dev == SPI0) + ? RESETS_RESET_spi0_Msk + : RESETS_RESET_spi1_Msk; + + periph_reset(reset_bit_mask); + periph_reset_done(reset_bit_mask); +} + +static void _poweroff(spi_t spi) +{ + uint32_t reset_bit_mask = (spi_config[spi].dev == SPI0) + ? RESETS_RESET_spi0_Msk + : RESETS_RESET_spi1_Msk; + + periph_reset(reset_bit_mask); +} + +void spi_init(spi_t spi) +{ + DEBUG("[rpx0xx] Call spi_init(spi=%" PRIdFAST8 ")", spi); + + assert((unsigned)spi < SPI_NUMOF); + + /* initialize device lock */ + mutex_init(&locks[spi]); + /* trigger pin initialization */ + spi_init_pins(spi); + /* clock prescaler values must be calculated */ + pl022_clk[spi].clk = 0xff; +} + +void spi_init_pins(spi_t spi) +{ + DEBUG("[rpx0xx] Call spi_init_pins(spi=%" PRIdFAST8 ")", spi); + + assert((unsigned)spi < SPI_NUMOF); + + const gpio_pad_ctrl_t mosi_pad_config = { + /* SPI should typically draw less than 2 mA */ + .drive_strength = DRIVE_STRENGTH_2MA, + }; + const gpio_io_ctrl_t mosi_io_config = { + .function_select = FUNCTION_SELECT_SPI, + }; + const gpio_pad_ctrl_t clk_pad_config = { + /* SPI should typically draw less than 2 mA */ + .drive_strength = DRIVE_STRENGTH_2MA, + }; + const gpio_io_ctrl_t clk_io_config = { + .function_select = FUNCTION_SELECT_SPI, + }; + const gpio_pad_ctrl_t miso_pad_config = { + .input_enable = 1, + .schmitt_trig_enable = 1, + }; + const gpio_io_ctrl_t miso_io_config = { + .function_select = FUNCTION_SELECT_SPI, + }; + + if (gpio_is_valid(spi_config[spi].mosi_pin)) { + gpio_set_pad_config(spi_config[spi].mosi_pin, mosi_pad_config); + gpio_set_io_config(spi_config[spi].mosi_pin, mosi_io_config); + } + if (gpio_is_valid(spi_config[spi].clk_pin)) { + gpio_set_pad_config(spi_config[spi].clk_pin, clk_pad_config); + gpio_set_io_config(spi_config[spi].clk_pin, clk_io_config); + } + if (gpio_is_valid(spi_config[spi].miso_pin)) { + gpio_set_pad_config(spi_config[spi].miso_pin, miso_pad_config); + gpio_set_io_config(spi_config[spi].miso_pin, miso_io_config); + } + + /* allow access to the bus */ + mutex_unlock(&locks[spi]); +} + +void spi_deinit_pins(spi_t spi) +{ + DEBUG("[rpx0xx] Call spi_deinit_pins(spi=%" PRIdFAST8 ")\n", spi); + + /* lock mutex to block usage of the SPI bus */ + mutex_lock(&locks[spi]); + + assert((unsigned)spi < SPI_NUMOF); + + if (gpio_is_valid(spi_config[spi].mosi_pin)) { + gpio_reset_all_config(spi_config[spi].mosi_pin); + } + + if (gpio_is_valid(spi_config[spi].miso_pin)) { + gpio_reset_all_config(spi_config[spi].miso_pin); + } + + if (gpio_is_valid(spi_config[spi].clk_pin)) { + gpio_reset_all_config(spi_config[spi].clk_pin); + } +} + +int spi_init_cs(spi_t spi, spi_cs_t cs) +{ + DEBUG("[rpx0xx] Call spi_init_cs(spi=%" PRIdFAST8 ", cs=%" PRIu32 ")\n", + spi, cs); + + if (spi >= SPI_NUMOF) { + return SPI_NODEV; + } + if (!gpio_is_valid(cs)) { + return SPI_NOCS; + } + + gpio_init((gpio_t)cs, GPIO_OUT); + gpio_set((gpio_t)cs); + + return SPI_OK; +} + +int spi_deinit_cs(spi_t spi, spi_cs_t cs) +{ + DEBUG("[rpx0xx] Call spi_deinit_cs(spi=%" PRIdFAST8 ", cs=%" PRIu32 ")\n", + spi, cs); + + if (spi >= SPI_NUMOF) { + return SPI_NODEV; + } + if (!gpio_is_valid(cs)) { + return SPI_NOCS; + } + + gpio_reset_all_config((gpio_t)cs); + + return SPI_OK; +} + +static inline void _check_best_clk_result(uint16_t target_dvsr, + uint16_t cur_dvsr, + uint16_t scr, + uint8_t cpsdvsr, + uint16_t *best_diff, + uint16_t *best_scr, + uint8_t *best_cpsdvsr) +{ + uint16_t diff = (target_dvsr > cur_dvsr) + ? target_dvsr - cur_dvsr + : cur_dvsr - target_dvsr; + + if (diff < *best_diff) { + *best_diff = diff; + *best_cpsdvsr = cpsdvsr; + *best_scr = scr; + } +} + +static void _calc_pl022_clk(_pl022_clk_t *pl022_clk, spi_clk_t clk) +{ + uint16_t dvsr = CLOCK_PERIPH / clk; + /* The divisor must be split into two 8-bit divisors, cpsdvsr and scr, + * dvsr = cpsdvsr*scr. cpsdvsr must be an even number greater than 0. */ + uint8_t cpsdvsr = 2, best_cpsdvsr = 2; + uint16_t scr = 256, best_scr = 256; + uint16_t best_diff = dvsr; + uint16_t cur_dvsr = scr * cpsdvsr; + + /* Come from above with cpsdvsr and from below with scr. + * Check all intermediate combinations and use the best. */ + _check_best_clk_result(dvsr, cur_dvsr, scr, cpsdvsr, &best_diff, + &best_scr, &best_cpsdvsr); + while (scr > 0 && cpsdvsr < 254 && best_diff != 0) { + while (dvsr > cur_dvsr && cpsdvsr < 254) { + cpsdvsr += 2; + cur_dvsr += 2 * scr; + _check_best_clk_result(dvsr, cur_dvsr, scr, cpsdvsr, &best_diff, + &best_scr, &best_cpsdvsr); + } + while (dvsr < cur_dvsr && scr > 0) { + scr -= 1; + cur_dvsr -= cpsdvsr; + _check_best_clk_result(dvsr, cur_dvsr, scr, cpsdvsr, &best_diff, + &best_scr, &best_cpsdvsr); + } + } + uint32_t resulting_clk_hz = CLOCK_PERIPH / (best_cpsdvsr * best_scr); + + pl022_clk->clk = clk; + pl022_clk->cpsdvsr = best_cpsdvsr; + /* For scr, +1 is added internally. */ + pl022_clk->scr = (best_scr - 1); + DEBUG("[rpx0xx] Values for spi clock divider registers CPSDVSR=%" PRIu16 + ", SCR=%" PRIu16 ". Resulting clock is %" PRIu32 " Hz.\n", + best_cpsdvsr, best_scr - 1, resulting_clk_hz); +} + +void spi_acquire(spi_t spi, spi_cs_t cs, spi_mode_t mode, spi_clk_t clk) +{ + DEBUG("[rpx0xx] Call spi_acquire(spi=%" PRIuFAST8 ", cs=%" PRIu32 + ", mode=%" PRIu16 ", clk=%" PRIu32 ")\n", spi, cs, mode, clk); + + assert((unsigned)spi < SPI_NUMOF); + assert(clk == SPI_CLK_100KHZ || clk == SPI_CLK_10MHZ || clk == SPI_CLK_1MHZ + || clk == SPI_CLK_400KHZ || clk == SPI_CLK_5MHZ); + (void)cs; + + /* lock bus */ + mutex_lock(&locks[spi]); + + _poweron(spi); + + SPI0_Type *dev = spi_config[spi].dev; + + io_reg_write_dont_corrupt(&dev->SSPCR0, + 0x7 << SPI0_SSPCR0_DSS_Pos, + SPI0_SSPCR0_DSS_Msk); + /* uncomment the following line for debug loopback mode */ + /* io_reg_atomic_set(&dev->SSPCR1, SPI0_SSPCR1_LBM_Msk); */ + + /* set SPI mode */ + switch (mode) { + case SPI_MODE_0: + io_reg_atomic_clear(&dev->SSPCR0, SPI0_SSPCR0_SPO_Msk); + io_reg_atomic_clear(&dev->SSPCR0, SPI0_SSPCR0_SPH_Msk); + break; + case SPI_MODE_1: + io_reg_atomic_clear(&dev->SSPCR0, SPI0_SSPCR0_SPO_Msk); + io_reg_atomic_set(&dev->SSPCR0, SPI0_SSPCR0_SPH_Msk); + break; + case SPI_MODE_2: + io_reg_atomic_set(&dev->SSPCR0, SPI0_SSPCR0_SPO_Msk); + io_reg_atomic_clear(&dev->SSPCR0, SPI0_SSPCR0_SPH_Msk); + break; + case SPI_MODE_3: + io_reg_atomic_set(&dev->SSPCR0, SPI0_SSPCR0_SPO_Msk); + io_reg_atomic_set(&dev->SSPCR0, SPI0_SSPCR0_SPH_Msk); + break; + } + + /* set clock speed */ + if (clk != pl022_clk[spi].clk) { + _calc_pl022_clk(&pl022_clk[spi], clk); + } + io_reg_write_dont_corrupt(&dev->SSPCPSR, + pl022_clk[spi].cpsdvsr << SPI0_SSPCPSR_CPSDVSR_Pos, + SPI0_SSPCPSR_CPSDVSR_Msk); + io_reg_write_dont_corrupt(&dev->SSPCR0, + pl022_clk[spi].scr << SPI0_SSPCR0_SCR_Pos, + SPI0_SSPCR0_SCR_Msk); + + /* enable SPI */ + io_reg_atomic_set(&dev->SSPCR1, SPI0_SSPCR1_SSE_Msk); +} + +void spi_release(spi_t spi) +{ + DEBUG("[rpx0xx] Call spi_release(spi=%" PRIdFAST8 ")\n", spi); + + assert((unsigned)spi < SPI_NUMOF); + + SPI0_Type *dev = spi_config[spi].dev; + + /* disable SPI */ + io_reg_atomic_clear(&dev->SSPCR1, SPI0_SSPCR1_SSE_Msk); + + _poweroff(spi); + + mutex_unlock(&locks[spi]); +} + +static inline void _wait_for_end(spi_t spi) +{ + DEBUG("[rpx0xx] Call _wait_for_end(spi=%" PRIdFAST8 ")\n", spi); + SPI0_Type *dev = spi_config[spi].dev; + + while (!(dev->SSPSR & SPI0_SSPSR_TFE_Msk)) {} + while (dev->SSPSR & SPI0_SSPSR_BSY_Msk) {} +} + +static void _transfer_no_dma(spi_t spi, const void *out, void *in, size_t len) +{ + DEBUG("[rpx0xx] Call _transfer_no_dma(spi=%" PRIdFAST8 + ", out=%p, in=%p, len=%" PRIu16 ")\n", + spi, out, in, len); + + const uint8_t *outbuf = out; + uint8_t *inbuf = in; + SPI0_Type *dev = spi_config[spi].dev; + + /* transfer data, use shortpath if only sending data */ + if (!inbuf) { + for (size_t i = 0; i < len; i++) { + while (!(dev->SSPSR & SPI0_SSPSR_TFE_Msk)) {} + dev->SSPDR = outbuf[i]; + } + /* wait until everything is finished and empty the receive buffer */ + while (!(dev->SSPSR & SPI0_SSPSR_TFE_Msk)) {} + while (dev->SSPSR & SPI0_SSPSR_BSY_Msk) {} + while (dev->SSPSR & SPI0_SSPSR_RNE_Msk) { + dev->SSPDR; + } + } + else if (!outbuf) { + for (size_t i = 0; i < len; i++) { + while (!(dev->SSPSR & SPI0_SSPSR_TFE_Msk)) {} + dev->SSPDR = 0; + while (!(dev->SSPSR & SPI0_SSPSR_RNE_Msk)) {} + inbuf[i] = dev->SSPDR & 0xffUL; + } + } + else { + for (size_t i = 0; i < len; i++) { + while (!(dev->SSPSR & SPI0_SSPSR_TFE_Msk)) {} + dev->SSPDR = outbuf[i]; + while (!(dev->SSPSR & SPI0_SSPSR_RNE_Msk)) {} + inbuf[i] = dev->SSPDR & 0xffUL; + } + } + + _wait_for_end(spi); +} + +void spi_transfer_bytes(spi_t spi, spi_cs_t cs, bool cont, + const void *out, void *in, size_t len) +{ + DEBUG("[rpx0xx] Call spi_transfer_bytes(spi=%" PRIdFAST8 + ", out=%p, in=%p, len=%" PRIu16 ")\n", + spi, out, in, len); + + /* make sure at least one input or one output buffer is given */ + assert(out || in); + + /* activate the given chip select line */ + if (gpio_is_valid(cs)) { + gpio_clear((gpio_t)cs); + } + + _transfer_no_dma(spi, out, in, len); + + /* release the chip select if not specified differently */ + if ((!cont) && gpio_is_valid(cs)) { + gpio_set((gpio_t)cs); + } +}