From ba8b3dc2b479591b6abf7d666444adfa31ce986e Mon Sep 17 00:00:00 2001 From: Dylan Laduranty Date: Mon, 18 Mar 2024 21:04:27 +0100 Subject: [PATCH] cpu/rpx0xx: add PWM support Signed-off-by: Dylan Laduranty --- cpu/rpx0xx/include/periph_cpu.h | 28 ++++++ cpu/rpx0xx/periph/pwm.c | 149 ++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 cpu/rpx0xx/periph/pwm.c 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); +}