| /* |
| * Copyright (c) 2022, Joep Buruma |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #define DT_DRV_COMPAT raspberrypi_pico_pwm |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/pwm.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/reset.h> |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(pwm_rpi_pico, CONFIG_PWM_LOG_LEVEL); |
| |
| /* pico-sdk includes */ |
| #include <hardware/pwm.h> |
| #include <hardware/structs/pwm.h> |
| |
| #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 - 1 > 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 - 1); |
| pwm_set_chan_level(slice, pico_channel, 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_PWM_INIT_PRIORITY, &pwm_rpi_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(PWM_RPI_INIT); |