/* * Copyright (c) 2021 Teslabs Engineering S.L. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT gd_gd32_pwm #include #include #include #include #include #include #include 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)