diff --git a/CODEOWNERS b/CODEOWNERS index a3157e1cca7..dd0e4fcfe6b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -344,6 +344,7 @@ /drivers/ps2/*xec* @franciscomunoz @sjvasanth1 /drivers/ps2/*npcx* @MulinChao @WealianLiao @ChiHuaL /drivers/pwm/*b91* @andy-liu-telink +/drivers/pwm/*rpi_pico* @burumaj /drivers/pwm/*rv32m1* @henrikbrixandersen /drivers/pwm/*sam0* @nzmichaelh /drivers/pwm/*stm32* @gmarull diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index 93c9828898d..08034483267 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -25,6 +25,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_PWT pwm_mcux_pwt.c) zephyr_library_sources_ifdef(CONFIG_PWM_GECKO pwm_gecko.c) zephyr_library_sources_ifdef(CONFIG_PWM_GD32 pwm_gd32.c) zephyr_library_sources_ifdef(CONFIG_PWM_TEST pwm_test.c) +zephyr_library_sources_ifdef(CONFIG_PWM_RPI_PICO pwm_rpi_pico.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c) zephyr_library_sources_ifdef(CONFIG_PWM_CAPTURE pwm_capture.c) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index b301291673e..3782c9282a9 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -73,4 +73,6 @@ source "drivers/pwm/Kconfig.gd32" source "drivers/pwm/Kconfig.test" +source "drivers/pwm/Kconfig.rpi_pico" + endif # PWM diff --git a/drivers/pwm/Kconfig.rpi_pico b/drivers/pwm/Kconfig.rpi_pico new file mode 100644 index 00000000000..b3113d6ebcd --- /dev/null +++ b/drivers/pwm/Kconfig.rpi_pico @@ -0,0 +1,14 @@ +# Copyright (c) 2022, Joep Buruma +# SPDX-License-Identifier: Apache-2.0 + +# Workaround for not being able to have commas in macro arguments +DT_COMPAT_RPI_PICO_PWM := raspberrypi,pico-pwm + +config PWM_RPI_PICO + bool "RPi Pico PWM" + default $(dt_compat_enabled,$(DT_COMPAT_RPI_PICO_PWM)) + depends on SOC_SERIES_RP2XXX + depends on RESET + select PICOSDK_USE_PWM + help + Enable PWM driver for RPi Pico family of MCUs diff --git a/drivers/pwm/pwm_rpi_pico.c b/drivers/pwm/pwm_rpi_pico.c new file mode 100644 index 00000000000..d75ee314ebf --- /dev/null +++ b/drivers/pwm/pwm_rpi_pico.c @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2022, Joep Buruma + * + * SPDX-License-Identifier: Apache-2.0 + */ +#define DT_DRV_COMPAT raspberrypi_pico_pwm + +#include +#include +#include +#include +#include +LOG_MODULE_REGISTER(pwm_rpi_pico, CONFIG_PWM_LOG_LEVEL); + +/* pico-sdk includes */ +#include +#include + +#define PWM_RPI_PICO_COUNTER_TOP_MAX UINT16_MAX +#define PWM_RPI_NUM_CHANNELS (16U) + +struct pwm_rpi_slice_config { + uint8_t integral; + uint8_t frac; + bool phase_correct; +}; + +struct pwm_rpi_config { + /* + * pwm_controller is the start address of the pwm peripheral. + */ + pwm_hw_t *pwm_controller; + struct pwm_rpi_slice_config slice_configs[NUM_PWM_SLICES]; + const struct pinctrl_dev_config *pcfg; + const struct reset_dt_spec reset; +}; + +static float pwm_rpi_get_clkdiv(const struct device *dev, int slice) +{ + const struct pwm_rpi_config *cfg = dev->config; + + /* the divider is a fixed point 8.4 convert to float for use in pico-sdk */ + return (float)cfg->slice_configs[slice].integral + + (float)cfg->slice_configs[slice].frac / 16.0; +} + +static inline uint32_t pwm_rpi_channel_to_slice(uint32_t channel) +{ + return channel / 2; +} + +static inline uint32_t pwm_rpi_channel_to_pico_channel(uint32_t channel) +{ + return channel % 2; +} + +static int pwm_rpi_get_cycles_per_sec(const struct device *dev, uint32_t ch, uint64_t *cycles) +{ + float f_clock_in; + int slice = pwm_rpi_channel_to_slice(ch); + + if (ch >= PWM_RPI_NUM_CHANNELS) { + return -EINVAL; + } + + f_clock_in = (float)sys_clock_hw_cycles_per_sec(); + + /* No need to check for divide by 0 since the minimum value of + * pwm_rpi_get_clkdiv is 1 + */ + *cycles = (uint64_t)(f_clock_in / pwm_rpi_get_clkdiv(dev, slice)); + return 0; +} + +/* The pico_sdk only allows setting the polarity of both channels at once. + * This is a convenience function to make setting the polarity of a single + * channel easier. + */ +static void pwm_rpi_set_channel_polarity(const struct device *dev, int slice, + int pico_channel, bool inverted) +{ + const struct pwm_rpi_config *cfg = dev->config; + + bool pwm_polarity_a = (cfg->pwm_controller->slice[slice].csr & PWM_CH0_CSR_A_INV_BITS) > 0; + bool pwm_polarity_b = (cfg->pwm_controller->slice[slice].csr & PWM_CH0_CSR_B_INV_BITS) > 0; + + if (pico_channel == PWM_CHAN_A) { + pwm_polarity_a = inverted; + } else if (pico_channel == PWM_CHAN_B) { + pwm_polarity_b = inverted; + } + + pwm_set_output_polarity(slice, pwm_polarity_a, pwm_polarity_b); +} + +static int pwm_rpi_set_cycles(const struct device *dev, uint32_t ch, uint32_t period_cycles, + uint32_t pulse_cycles, pwm_flags_t flags) +{ + if (ch >= PWM_RPI_NUM_CHANNELS) { + return -EINVAL; + } + + if (period_cycles > PWM_RPI_PICO_COUNTER_TOP_MAX || + pulse_cycles > PWM_RPI_PICO_COUNTER_TOP_MAX) { + return -EINVAL; + } + + int slice = pwm_rpi_channel_to_slice(ch); + + /* this is the channel within a pwm slice */ + int pico_channel = pwm_rpi_channel_to_pico_channel(ch); + + pwm_rpi_set_channel_polarity(dev, slice, pico_channel, + (flags & PWM_POLARITY_MASK) == PWM_POLARITY_INVERTED); + pwm_set_wrap(slice, period_cycles); + pwm_set_chan_level(slice, ch, pulse_cycles); + + return 0; +}; + +struct pwm_driver_api pwm_rpi_driver_api = { + .get_cycles_per_sec = pwm_rpi_get_cycles_per_sec, + .set_cycles = pwm_rpi_set_cycles, +}; + +static int pwm_rpi_init(const struct device *dev) +{ + const struct pwm_rpi_config *cfg = dev->config; + pwm_config slice_cfg; + size_t slice_idx; + int err; + + err = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (err) { + LOG_ERR("Failed to configure pins for PWM. err=%d", err); + return err; + } + + for (slice_idx = 0; slice_idx < NUM_PWM_SLICES; slice_idx++) { + slice_cfg = pwm_get_default_config(); + pwm_config_set_clkdiv_mode(&slice_cfg, PWM_DIV_FREE_RUNNING); + + pwm_init(slice_idx, &slice_cfg, false); + + pwm_set_clkdiv_int_frac(slice_idx, + cfg->slice_configs[slice_idx].integral, + cfg->slice_configs[slice_idx].frac); + pwm_set_enabled(slice_idx, true); + } + + return 0; +} + +#define PWM_INST_RPI_SLICE_DIVIDER(idx, n) \ + { \ + .integral = DT_INST_PROP(idx, UTIL_CAT(divider_int_, n)), \ + .frac = DT_INST_PROP(idx, UTIL_CAT(divider_frac_, n)), \ + } + +#define PWM_RPI_INIT(idx) \ + \ + PINCTRL_DT_INST_DEFINE(idx); \ + static const struct pwm_rpi_config pwm_rpi_config_##idx = { \ + .pwm_controller = (pwm_hw_t *)DT_INST_REG_ADDR(idx), \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(idx), \ + .slice_configs = { \ + PWM_INST_RPI_SLICE_DIVIDER(idx, 0), \ + PWM_INST_RPI_SLICE_DIVIDER(idx, 1), \ + PWM_INST_RPI_SLICE_DIVIDER(idx, 2), \ + PWM_INST_RPI_SLICE_DIVIDER(idx, 3), \ + PWM_INST_RPI_SLICE_DIVIDER(idx, 4), \ + PWM_INST_RPI_SLICE_DIVIDER(idx, 5), \ + PWM_INST_RPI_SLICE_DIVIDER(idx, 6), \ + PWM_INST_RPI_SLICE_DIVIDER(idx, 7), \ + }, \ + .reset = RESET_DT_SPEC_INST_GET(idx), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(idx, pwm_rpi_init, NULL, NULL, &pwm_rpi_config_##idx, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &pwm_rpi_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(PWM_RPI_INIT); diff --git a/dts/arm/rpi_pico/rp2040.dtsi b/dts/arm/rpi_pico/rp2040.dtsi index 6f33d674343..7621d515de1 100644 --- a/dts/arm/rpi_pico/rp2040.dtsi +++ b/dts/arm/rpi_pico/rp2040.dtsi @@ -151,7 +151,7 @@ clocks = <&system_clk>; interrupts = <4 RPI_PICO_DEFAULT_IRQ_PRIORITY>; interrupt-names = "PWM_IRQ_WRAP"; - label = "PWM_CTRL_0"; + label = "PWM_0"; status = "disabled"; #pwm-cells = <3>; }; diff --git a/modules/hal_rpi_pico/CMakeLists.txt b/modules/hal_rpi_pico/CMakeLists.txt index f0034d59b03..5e32815ba0c 100644 --- a/modules/hal_rpi_pico/CMakeLists.txt +++ b/modules/hal_rpi_pico/CMakeLists.txt @@ -93,6 +93,9 @@ if(CONFIG_HAS_RPI_PICO) zephyr_include_directories_ifdef(CONFIG_PICOSDK_USE_FLASH ${rp2_common_dir}/hardware_flash/include) + zephyr_include_directories_ifdef(CONFIG_PICOSDK_USE_PWM + ${rp2_common_dir}/hardware_pwm/include) + # Some flash driver functions must be executed from the RAM. # Originally pico-sdk places them in the RW data section, so this # implementation does the same. @@ -104,4 +107,5 @@ if(CONFIG_HAS_RPI_PICO) PROPERTIES COMPILE_FLAGS $ ) + endif() diff --git a/modules/hal_rpi_pico/Kconfig b/modules/hal_rpi_pico/Kconfig index 35ba2082e9b..8b3c518ee38 100644 --- a/modules/hal_rpi_pico/Kconfig +++ b/modules/hal_rpi_pico/Kconfig @@ -18,3 +18,8 @@ config PICOSDK_USE_FLASH bool help Use the flash driver from pico-sdk + +config PICOSDK_USE_PWM + bool + help + Use the PWM driver from pico-sdk diff --git a/soc/arm/rpi_pico/rp2/soc.c b/soc/arm/rpi_pico/rp2/soc.c index 04f73ef81a7..3df9b983723 100644 --- a/soc/arm/rpi_pico/rp2/soc.c +++ b/soc/arm/rpi_pico/rp2/soc.c @@ -41,7 +41,7 @@ static int rp2040_init(const struct device *arg) ~(RESETS_RESET_ADC_BITS | RESETS_RESET_RTC_BITS | RESETS_RESET_SPI0_BITS | RESETS_RESET_SPI1_BITS | RESETS_RESET_UART0_BITS | RESETS_RESET_UART1_BITS | - RESETS_RESET_USBCTRL_BITS)); + RESETS_RESET_USBCTRL_BITS | RESETS_RESET_PWM_BITS)); clocks_init();