| /* |
| * Copyright (c) 2021 Teslabs Engineering S.L. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT gd_gd32_pwm |
| |
| #include <errno.h> |
| |
| #include <zephyr/drivers/pwm.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/sys/util_macro.h> |
| |
| #include <gd32_rcu.h> |
| #include <gd32_timer.h> |
| |
| #include <zephyr/logging/log.h> |
| |
| LOG_MODULE_REGISTER(pwm_gd32, CONFIG_PWM_LOG_LEVEL); |
| |
| /** PWM data. */ |
| struct pwm_gd32_data { |
| /** Timer clock (Hz). */ |
| uint32_t tim_clk; |
| }; |
| |
| /** PWM configuration. */ |
| struct pwm_gd32_config { |
| /** Timer register. */ |
| uint32_t reg; |
| /** Number of channels */ |
| uint8_t channels; |
| /** Flag to indicate if timer has 32-bit counter */ |
| bool is_32bit; |
| /** Flag to indicate if timer is advanced */ |
| bool is_advanced; |
| /** Prescaler. */ |
| uint16_t prescaler; |
| /** RCU peripheral clock. */ |
| uint32_t rcu_periph_clock; |
| /** RCU peripheral reset. */ |
| uint32_t rcu_periph_reset; |
| /** pinctrl configurations. */ |
| const struct pinctrl_dev_config *pcfg; |
| }; |
| |
| /** Obtain channel enable bit for the given channel */ |
| #define TIMER_CHCTL2_CHXEN(ch) BIT(4U * (ch)) |
| /** Obtain polarity bit for the given channel */ |
| #define TIMER_CHCTL2_CHXP(ch) BIT(1U + (4U * (ch))) |
| /** Obtain CHCTL0/1 mask for the given channel (0 or 1) */ |
| #define TIMER_CHCTLX_MSK(ch) (0xFU << (8U * (ch))) |
| |
| /** Obtain RCU register offset from RCU clock value */ |
| #define RCU_CLOCK_OFFSET(rcu_clock) ((rcu_clock) >> 6U) |
| |
| /** |
| * Obtain the timer clock. |
| * |
| * @param dev Device instance. |
| * |
| * @return Timer clock (Hz). |
| */ |
| static uint32_t pwm_gd32_get_tim_clk(const struct device *dev) |
| { |
| const struct pwm_gd32_config *config = dev->config; |
| uint32_t apb_psc, apb_clk; |
| |
| /* obtain APB prescaler value */ |
| if (RCU_CLOCK_OFFSET(config->rcu_periph_clock) == APB1EN_REG_OFFSET) { |
| apb_psc = RCU_CFG0 & RCU_CFG0_APB1PSC; |
| } else { |
| apb_psc = RCU_CFG0 & RCU_CFG0_APB2PSC; |
| } |
| |
| switch (apb_psc) { |
| case RCU_APB1_CKAHB_DIV2: |
| apb_psc = 2U; |
| break; |
| case RCU_APB1_CKAHB_DIV4: |
| apb_psc = 4U; |
| break; |
| case RCU_APB1_CKAHB_DIV8: |
| apb_psc = 8U; |
| break; |
| case RCU_APB1_CKAHB_DIV16: |
| apb_psc = 16U; |
| break; |
| default: |
| apb_psc = 1U; |
| break; |
| } |
| |
| apb_clk = CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / apb_psc; |
| |
| #ifdef RCU_CFG1_TIMERSEL |
| /* |
| * The TIMERSEL bit in RCU_CFG1 controls the clock frequency of all the |
| * timers connected to the APB1 and APB2 domains. |
| * |
| * Up to a certain threshold value of APB{1,2} prescaler, timer clock |
| * equals to CK_AHB. This threshold value depends on TIMERSEL setting |
| * (2 if TIMERSEL=0, 4 if TIMERSEL=1). Above threshold, timer clock is |
| * set to a multiple of the APB domain clock CK_APB{1,2} (2 if |
| * TIMERSEL=0, 4 if TIMERSEL=1). |
| */ |
| |
| /* TIMERSEL = 0 */ |
| if ((RCU_CFG1 & RCU_CFG1_TIMERSEL) == 0U) { |
| if (apb_psc <= 2U) { |
| return CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC; |
| } |
| |
| return apb_clk * 2U; |
| } |
| |
| /* TIMERSEL = 1 */ |
| if (apb_psc <= 4U) { |
| return CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC; |
| } |
| |
| return apb_clk * 4U; |
| #else |
| /* |
| * If the APB prescaler equals 1, the timer clock frequencies are set to |
| * the same frequency as that of the APB domain. Otherwise, they are set |
| * to twice the frequency of the APB domain. |
| */ |
| if (apb_psc == 1U) { |
| return apb_clk; |
| } |
| |
| return apb_clk * 2U; |
| #endif /* RCU_CFG1_TIMERSEL */ |
| } |
| |
| static int pwm_gd32_set_cycles(const struct device *dev, uint32_t channel, |
| uint32_t period_cycles, uint32_t pulse_cycles, |
| pwm_flags_t flags) |
| { |
| const struct pwm_gd32_config *config = dev->config; |
| |
| if (channel >= config->channels) { |
| return -EINVAL; |
| } |
| |
| /* 16-bit timers can count up to UINT16_MAX */ |
| if (!config->is_32bit && (period_cycles > UINT16_MAX)) { |
| return -ENOTSUP; |
| } |
| |
| /* disable channel output if period is zero */ |
| if (period_cycles == 0U) { |
| TIMER_CHCTL2(config->reg) &= ~TIMER_CHCTL2_CHXEN(channel); |
| return 0; |
| } |
| |
| /* update polarity */ |
| if ((flags & PWM_POLARITY_INVERTED) != 0U) { |
| TIMER_CHCTL2(config->reg) |= TIMER_CHCTL2_CHXP(channel); |
| } else { |
| TIMER_CHCTL2(config->reg) &= ~TIMER_CHCTL2_CHXP(channel); |
| } |
| |
| /* update pulse */ |
| switch (channel) { |
| case 0U: |
| TIMER_CH0CV(config->reg) = pulse_cycles; |
| break; |
| case 1U: |
| TIMER_CH1CV(config->reg) = pulse_cycles; |
| break; |
| case 2U: |
| TIMER_CH2CV(config->reg) = pulse_cycles; |
| break; |
| case 3U: |
| TIMER_CH3CV(config->reg) = pulse_cycles; |
| break; |
| default: |
| __ASSERT_NO_MSG(NULL); |
| break; |
| } |
| |
| /* update period */ |
| TIMER_CAR(config->reg) = period_cycles; |
| |
| /* channel not enabled: configure it */ |
| if ((TIMER_CHCTL2(config->reg) & TIMER_CHCTL2_CHXEN(channel)) == 0U) { |
| volatile uint32_t *chctl; |
| |
| /* select PWM1 mode, enable OC shadowing */ |
| if (channel < 2U) { |
| chctl = &TIMER_CHCTL0(config->reg); |
| } else { |
| chctl = &TIMER_CHCTL1(config->reg); |
| } |
| |
| *chctl &= ~TIMER_CHCTLX_MSK(channel); |
| *chctl |= (TIMER_OC_MODE_PWM1 | TIMER_OC_SHADOW_ENABLE) << |
| (8U * (channel % 2U)); |
| |
| /* enable channel output */ |
| TIMER_CHCTL2(config->reg) |= TIMER_CHCTL2_CHXEN(channel); |
| |
| /* generate update event (to load shadow values) */ |
| TIMER_SWEVG(config->reg) |= TIMER_SWEVG_UPG; |
| } |
| |
| return 0; |
| } |
| |
| static int pwm_gd32_get_cycles_per_sec(const struct device *dev, |
| uint32_t channel, uint64_t *cycles) |
| { |
| struct pwm_gd32_data *data = dev->data; |
| const struct pwm_gd32_config *config = dev->config; |
| |
| *cycles = (uint64_t)(data->tim_clk / (config->prescaler + 1U)); |
| |
| return 0; |
| } |
| |
| static const struct pwm_driver_api pwm_gd32_driver_api = { |
| .set_cycles = pwm_gd32_set_cycles, |
| .get_cycles_per_sec = pwm_gd32_get_cycles_per_sec, |
| }; |
| |
| static int pwm_gd32_init(const struct device *dev) |
| { |
| const struct pwm_gd32_config *config = dev->config; |
| struct pwm_gd32_data *data = dev->data; |
| int ret; |
| |
| rcu_periph_clock_enable(config->rcu_periph_clock); |
| |
| /* reset timer to its default state */ |
| rcu_periph_reset_enable(config->rcu_periph_reset); |
| rcu_periph_reset_disable(config->rcu_periph_reset); |
| |
| /* apply pin configuration */ |
| ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* cache timer clock value */ |
| data->tim_clk = pwm_gd32_get_tim_clk(dev); |
| |
| /* basic timer operation: edge aligned, up counting, shadowed CAR */ |
| TIMER_CTL0(config->reg) = TIMER_CKDIV_DIV1 | TIMER_COUNTER_EDGE | |
| TIMER_COUNTER_UP | TIMER_CTL0_ARSE; |
| TIMER_PSC(config->reg) = config->prescaler; |
| |
| /* enable primary output for advanced timers */ |
| if (config->is_advanced) { |
| TIMER_CCHP(config->reg) |= TIMER_CCHP_POEN; |
| } |
| |
| /* enable timer counter */ |
| TIMER_CTL0(config->reg) |= TIMER_CTL0_CEN; |
| |
| return 0; |
| } |
| |
| #define PWM_GD32_DEFINE(i) \ |
| static struct pwm_gd32_data pwm_gd32_data_##i; \ |
| \ |
| PINCTRL_DT_INST_DEFINE(i); \ |
| \ |
| static const struct pwm_gd32_config pwm_gd32_config_##i = { \ |
| .reg = DT_REG_ADDR(DT_INST_PARENT(i)), \ |
| .rcu_periph_clock = DT_PROP(DT_INST_PARENT(i), \ |
| rcu_periph_clock), \ |
| .rcu_periph_reset = DT_PROP(DT_INST_PARENT(i), \ |
| rcu_periph_reset), \ |
| .prescaler = DT_PROP(DT_INST_PARENT(i), prescaler), \ |
| .channels = DT_PROP(DT_INST_PARENT(i), channels), \ |
| .is_32bit = DT_PROP(DT_INST_PARENT(i), is_32bit), \ |
| .is_advanced = DT_PROP(DT_INST_PARENT(i), is_advanced), \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(i), \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(i, &pwm_gd32_init, NULL, &pwm_gd32_data_##i, \ |
| &pwm_gd32_config_##i, POST_KERNEL, \ |
| CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ |
| &pwm_gd32_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(PWM_GD32_DEFINE) |