diff --git a/boards/rpi-pico/Kconfig b/boards/rpi-pico/Kconfig index d1244d9b03..1b8b19d751 100644 --- a/boards/rpi-pico/Kconfig +++ b/boards/rpi-pico/Kconfig @@ -14,6 +14,7 @@ config BOARD_RPI_PICO select CPU_MODEL_RP2040 select HAS_PERIPH_ADC select HAS_PERIPH_I2C + select HAS_PERIPH_PWM select HAS_PERIPH_UART select HAS_PERIPH_SPI diff --git a/boards/rpi-pico/Makefile.features b/boards/rpi-pico/Makefile.features index f68cf169c8..b57a6d9cc7 100644 --- a/boards/rpi-pico/Makefile.features +++ b/boards/rpi-pico/Makefile.features @@ -4,5 +4,6 @@ CPU := rpx0xx FEATURES_PROVIDED += periph_adc FEATURES_PROVIDED += periph_i2c FEATURES_PROVIDED += periph_spi +FEATURES_PROVIDED += periph_pwm 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 76d5abfa02..fec980584f 100644 --- a/boards/rpi-pico/include/periph_conf.h +++ b/boards/rpi-pico/include/periph_conf.h @@ -176,6 +176,23 @@ static const pio_i2c_conf_t pio_i2c_config[] = { #endif /** @} */ +/** + * @name PWM configuration + * @{ + */ +static const pwm_conf_t pwm_config[] = { + { + .pwm_slice = 4, + .chan = { + { .pin = GPIO_PIN(0, 25), .cc_chan = 1 }, /* rpi-pico onboard LED */ + { .pin = GPIO_UNDEF, .cc_chan = 0 }, + }, + }, +}; + +#define PWM_NUMOF ARRAY_SIZE(pwm_config) +/** @} */ + #ifdef __cplusplus } #endif diff --git a/cpu/rpx0xx/include/periph_cpu.h b/cpu/rpx0xx/include/periph_cpu.h index a977e0eeda..4aeb36fef6 100644 --- a/cpu/rpx0xx/include/periph_cpu.h +++ b/cpu/rpx0xx/include/periph_cpu.h @@ -399,6 +399,34 @@ typedef struct { uint8_t chan; /**< CPU ADC channel connected to the pin */ } adc_conf_t; +/** + * @brief Number of slices available per PWM device + */ +#define PWM_SLICE_NUMOF (8) + +/** + * @brief Number of channels available per slice + */ +#define PWM_CHANNEL_NUMOF (2) + +/** + * @brief PWM channel + */ +typedef struct { + gpio_t pin; /**< GPIO pin mapped to this channel */ + uint8_t cc_chan; /**< capture compare channel used */ +} pwm_chan_t; + +/** + * @brief PWM device configuration data structure + */ +typedef struct { + uint8_t pwm_slice; /**< PWM slice instance, + must be < to PWM_SLICE_NUMOF */ + pwm_chan_t chan[PWM_CHANNEL_NUMOF]; /**< channel mapping set to + {GPIO_UNDEF, 0} if not used */ +} pwm_conf_t; + /** * @brief Configuration details for an UART interface needed by the RPX0XX peripheral */ diff --git a/cpu/rpx0xx/periph/pwm.c b/cpu/rpx0xx/periph/pwm.c new file mode 100644 index 0000000000..4ffa816dc6 --- /dev/null +++ b/cpu/rpx0xx/periph/pwm.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2023-2024 Mesotic SAS + * + * 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 + * @ingroup drivers_periph_pwm + * @{ + * + * @file + * @brief Low-level PWM driver implementation + * + * @author Dylan Laduranty + * + * @} + */ + +#include "cpu.h" +#include "assert.h" +#include "periph/pwm.h" +#include "periph/gpio.h" +#include "periph_conf.h" +#include + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/* Vendor files don't offer a convenient way to access these registers + through a dedicated struct, thus create one for this purpose */ +typedef struct { + uint32_t csr; + uint32_t div; + uint32_t ctr; + uint32_t cc; + uint32_t top; +} pwm_slice_reg_t; + +/* Structure holding all PWM slices registers */ +struct pwm_reg { + pwm_slice_reg_t slices[PWM_SLICE_NUMOF]; +}; + +/* Start address of PWM slices */ +#define PWM_REG ((struct pwm_reg *)PWM_BASE) + +/* Helper to get slice register */ +static inline pwm_slice_reg_t *pwm_slice(unsigned slice_idx) +{ + return &PWM_REG->slices[slice_idx]; +} + +/* PWM block is feed by RP2040 sysclk (CLOCK_CORECLOCK) */ +uint32_t pwm_init(pwm_t pwm, pwm_mode_t mode, uint32_t freq, uint16_t res) +{ + uint8_t div_int; + uint8_t div_frac; + uint32_t val; + uint32_t ret; + uint8_t slice = pwm_config[pwm].pwm_slice; + + (void)mode; + + const gpio_io_ctrl_t pwm_io_config = { + .function_select = FUNCTION_SELECT_PWM, + }; + + /* Initialize associated GPIO pin */ + if (pwm_config[pwm].chan[0].pin != GPIO_UNDEF) { + gpio_set_io_config(pwm_config[pwm].chan[0].pin, pwm_io_config); + } + if (pwm_config[pwm].chan[1].pin != GPIO_UNDEF) { + gpio_set_io_config(pwm_config[pwm].chan[1].pin, pwm_io_config); + } + + /* Compute DIV register value to get closest match for + freq and res variables */ + val = (((uint32_t)CLOCK_CORECLOCK) << 4) / (freq * res); + /* If the value is above 4095, we will not be able to reach the desired + frequency so set the divisor value to maximum to get to the closest + possible value for the PWM frequency */ + if (val > 4095) { + div_frac = 0x0F; + div_int = 0xFF; + } else { + div_frac = val % 16; + div_int = val / 16; + } + /* Compute the real frequency we will get */ + ret = CLOCK_CORECLOCK / (res * (div_int + (div_frac / 16))); + + DEBUG("[pwm]: div_int:%d, div_frac:%d\n", div_int, div_frac); + /* Set the slice divider to reach the desired frequency */ + pwm_slice(slice)->div = ((div_int << PWM_CH0_DIV_INT_Pos) | div_frac); + + /* Let PWM slice run in free running mode */ + pwm_slice(slice)->csr = PWM_CH0_CSR_DIVMODE_div; + + /* Set PWM slice TOP value */ + pwm_slice(slice)->top = res-1; + + /* Enable PWM slice */ + io_reg_atomic_set(&PWM->EN, 1 << slice); + + DEBUG("[pwm]: Init done, frequency set to %ld\n", ret); + return ret; + +} + +uint8_t pwm_channels(pwm_t pwm) +{ + assert(pwm < PWM_NUMOF); + return PWM_CHANNEL_NUMOF; +} + +void pwm_set(pwm_t pwm, uint8_t channel, uint16_t value) +{ + assert((pwm < PWM_NUMOF) && (channel < PWM_CHANNEL_NUMOF)); + uint8_t slice = pwm_config[pwm].pwm_slice; + + /* Set channel compare value */ + if (channel) { + io_reg_write_dont_corrupt(&pwm_slice(slice)->cc, + (value << PWM_CH0_CC_B_Pos), + PWM_CH0_CC_B_Msk); + } + else { + io_reg_write_dont_corrupt(&pwm_slice(slice)->cc, + (value << PWM_CH0_CC_A_Pos), + PWM_CH0_CC_A_Msk); + } +} + +void pwm_poweron(pwm_t pwm) +{ + assert(pwm < PWM_NUMOF); + uint8_t slice = pwm_config[pwm].pwm_slice; + io_reg_atomic_set(&PWM->EN, 1 << slice); +} + +void pwm_poweroff(pwm_t pwm) +{ + assert(pwm < PWM_NUMOF); + uint8_t slice = pwm_config[pwm].pwm_slice; + io_reg_atomic_clear(&PWM->EN, 1 << slice); +}