/* * Copyright (c) 2006-2018, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2020-06-02 hqfang first version */ #include "drv_pwm.h" // #define DBG_LVL DBG_INFO #include #ifdef BSP_USING_PWM #if !defined(BSP_USING_PWM0) && !defined(BSP_USING_PWM1) && !defined(BSP_USING_PWM2) \ && !defined(BSP_USING_PWM3) && !defined(BSP_USING_PWM4) #error "Please define at least one BSP_USING_PWMx" /* this driver can be disabled at menuconfig -> Hardware Drivers Config -> On-chip Peripheral Drivers -> Enable PWM */ #endif static struct gd32_pwm_config pwm_config[] = { #ifdef BSP_USING_PWM0 { "pwm0", TIMER0, 1000000, }, #endif #ifdef BSP_USING_PWM1 { "pwm1", TIMER1, 1000000, }, #endif #ifdef BSP_USING_PWM2 { "pwm2", TIMER2, 1000000, }, #endif #ifdef BSP_USING_PWM3 { "pwm3", TIMER3, 1000000, }, #endif #ifdef BSP_USING_PWM4 { "pwm4", TIMER4, 1000000, }, #endif }; #define GD32_MAX_PWM_CHANNELS TIMER_CH_3 static struct gd32_pwm pwm_obj[sizeof(pwm_config) / sizeof(pwm_config[0])] = {0}; static rt_err_t gd32_pwm_enable(struct rt_device_pwm *device, struct rt_pwm_configuration *configuration, rt_bool_t enable) { struct gd32_pwm_config *config; config = (struct gd32_pwm_config *)device->parent.user_data; RT_ASSERT(config); if (configuration->channel > GD32_MAX_PWM_CHANNELS) { return RT_EINVAL; } if (!enable) { timer_channel_output_state_config(config->periph, configuration->channel, TIMER_CCX_DISABLE); } else { timer_channel_output_state_config(config->periph, configuration->channel, TIMER_CCX_ENABLE); } return RT_EOK; } static uint32_t gd32_get_pwm_clk(rt_uint32_t periph) { uint32_t clk; uint8_t clkpre; if (periph != TIMER0) { clk = rcu_clock_freq_get(CK_APB1); clkpre = GET_BITS(RCU_CFG0, 8, 10); } else { clk = rcu_clock_freq_get(CK_APB2); clkpre = GET_BITS(RCU_CFG0, 11, 13); } if (clkpre >= 4) { clk = clk * 2; } return clk; } static rt_err_t gd32_pwm_get(struct rt_device_pwm *device, struct rt_pwm_configuration *configuration) { uint32_t pwmclk; uint16_t prescale, period, clkdiv, pulse; struct gd32_pwm_config *config; config = (struct gd32_pwm_config *)device->parent.user_data; RT_ASSERT(config); pwmclk = gd32_get_pwm_clk(config->periph); prescale = (uint16_t)TIMER_PSC(config->periph) + 1; clkdiv = ((uint16_t)(TIMER_CTL0(config->periph) & TIMER_CTL0_CKDIV) >> 8); clkdiv = 1 << clkdiv; period = (uint16_t)TIMER_CAR(config->periph) + 1; pulse = (uint16_t)REG32((config->periph) + 0x34U + configuration->channel << 2) + 1; pwmclk = pwmclk / prescale / clkdiv; LOG_I("current pwmclk is %d\n", pwmclk); configuration->period = (uint64_t)period * 1000000000 / pwmclk; configuration->pulse = (uint64_t)pulse * 1000000000 / pwmclk; return RT_EOK; } static rt_err_t gd32_pwm_set(struct rt_device_pwm *device, struct rt_pwm_configuration *configuration) { timer_oc_parameter_struct timer_ocinitpara; timer_parameter_struct timer_initpara; uint32_t pwmclk, pwmclkv2; uint64_t period_cmp; uint16_t prescale, period, clkdiv, pulse; struct gd32_pwm_config *config; config = (struct gd32_pwm_config *)device->parent.user_data; RT_ASSERT(config); if (configuration->channel > GD32_MAX_PWM_CHANNELS) { LOG_I("max channel supported is %d\n", GD32_MAX_PWM_CHANNELS); return RT_EINVAL; } if (configuration->period < configuration->pulse) { LOG_I("period should > pulse \n"); return RT_EINVAL; } pwmclk = gd32_get_pwm_clk(config->periph); // min period value >= 100 period_cmp = (uint64_t)(1000000000 / pwmclk) * 10; if (configuration->period < period_cmp) { return RT_EINVAL; } period_cmp = (uint64_t)(1000000000 / (pwmclk / 65536 / 4)) * 65536; if (configuration->period > period_cmp) { return RT_EINVAL; } period_cmp = (uint64_t) pwmclk * configuration->period / 1000000000; if (period_cmp < 65536) { prescale = 0; clkdiv = TIMER_CKDIV_DIV1; period = period_cmp; } else if (period_cmp < 4294967296) { prescale = period_cmp / 65536; period = period_cmp / (prescale + 1); clkdiv = TIMER_CKDIV_DIV1; } else if (period_cmp < 8589934592) { prescale = period_cmp / 65536; period = period_cmp / (prescale + 1) / 2; clkdiv = TIMER_CKDIV_DIV2; } else { prescale = period_cmp / 65536; period = period_cmp / (prescale + 1) / 4; clkdiv = TIMER_CKDIV_DIV4; } pwmclkv2 = pwmclk / (prescale + 1) / (1 << clkdiv); LOG_I("current pwmclk is %d\n", pwmclkv2); LOG_I("Set channel %d, period %dns, pulse %dns\n", configuration->channel, \ configuration->period, configuration->pulse); pulse = (uint64_t)period * configuration->pulse / configuration->period; LOG_I("pwmclk %d, pwmcmp %d, prescale %d, period %d, pulse %d, clkdiv %d\n", \ pwmclk, (uint32_t)period_cmp, prescale, period, pulse, clkdiv); /* initialize TIMER init parameter struct */ timer_struct_para_init(&timer_initpara); /* TIMER configuration */ timer_initpara.prescaler = prescale; timer_initpara.alignedmode = TIMER_COUNTER_EDGE; timer_initpara.counterdirection = TIMER_COUNTER_UP; timer_initpara.period = period; timer_initpara.clockdivision = clkdiv; timer_initpara.repetitioncounter = 0; timer_init(config->periph, &timer_initpara); /* initialize TIMER channel output parameter struct */ timer_channel_output_struct_para_init(&timer_ocinitpara); /* CH0, CH1 and CH2 configuration in PWM mode */ timer_ocinitpara.outputstate = TIMER_CCX_DISABLE; timer_ocinitpara.outputnstate = TIMER_CCXN_DISABLE; timer_ocinitpara.ocpolarity = TIMER_OC_POLARITY_HIGH; timer_ocinitpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH; timer_ocinitpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW; timer_ocinitpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; timer_channel_output_config(config->periph, configuration->channel, &timer_ocinitpara); /* Channel configuration in PWM mode */ timer_channel_output_pulse_value_config(config->periph, configuration->channel, pulse); timer_channel_output_mode_config(config->periph, configuration->channel, TIMER_OC_MODE_PWM0); timer_channel_output_shadow_config(config->periph, configuration->channel, TIMER_OC_SHADOW_DISABLE); timer_primary_output_config(config->periph, ENABLE); /* auto-reload preload enable */ timer_auto_reload_shadow_enable(config->periph); timer_enable(config->periph); return RT_EOK; } static rt_err_t gd32_pwm_control(struct rt_device_pwm *device, int cmd, void *arg) { struct rt_pwm_configuration *configuration = (struct rt_pwm_configuration *)arg; switch (cmd) { case PWM_CMD_ENABLE: return gd32_pwm_enable(device, configuration, RT_TRUE); case PWM_CMD_DISABLE: return gd32_pwm_enable(device, configuration, RT_FALSE); case PWM_CMD_SET: return gd32_pwm_set(device, configuration); case PWM_CMD_GET: return gd32_pwm_get(device, configuration); default: return RT_EINVAL; } } static rt_err_t gd32_pwm_init(struct gd32_pwm_config *config) { timer_oc_parameter_struct timer_ocinitpara; timer_parameter_struct timer_initpara; uint32_t pwmclk; uint16_t prescale; pwmclk = gd32_get_pwm_clk(config->periph); /* period 1ms, duty 50% */ prescale = pwmclk / 1000 / 1000 - 1; config->period = 1000000; LOG_I("pwmclk %d, prescale %d, period %d, clkdiv %d\n", pwmclk, prescale, 999, 0); /* initialize TIMER init parameter struct */ timer_struct_para_init(&timer_initpara); /* TIMER configuration */ timer_initpara.prescaler = prescale; timer_initpara.alignedmode = TIMER_COUNTER_EDGE; timer_initpara.counterdirection = TIMER_COUNTER_UP; timer_initpara.period = 999; timer_initpara.clockdivision = TIMER_CKDIV_DIV1; timer_initpara.repetitioncounter = 0; timer_init(config->periph, &timer_initpara); /* initialize TIMER channel output parameter struct */ timer_channel_output_struct_para_init(&timer_ocinitpara); /* CH0, CH1 and CH2 configuration in PWM mode */ timer_ocinitpara.outputstate = TIMER_CCX_DISABLE; timer_ocinitpara.outputnstate = TIMER_CCXN_DISABLE; timer_ocinitpara.ocpolarity = TIMER_OC_POLARITY_HIGH; timer_ocinitpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH; timer_ocinitpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW; timer_ocinitpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; /* Channel configuration in PWM mode */ for (int i = 0; i <= GD32_MAX_PWM_CHANNELS; i ++) { timer_channel_output_config(config->periph, i, &timer_ocinitpara); timer_channel_output_pulse_value_config(config->periph, i, 499); timer_channel_output_mode_config(config->periph, i, TIMER_OC_MODE_PWM0); timer_channel_output_shadow_config(config->periph, i, TIMER_OC_SHADOW_DISABLE); } timer_primary_output_config(config->periph, ENABLE); /* auto-reload preload enable */ timer_auto_reload_shadow_enable(config->periph); timer_enable(config->periph); return RT_EOK; } static struct rt_pwm_ops gd32_drv_ops = { .control = gd32_pwm_control }; static int rt_pwm_init(void) { int i = 0; int result = RT_EOK; #ifdef BSP_USING_PWM0 rcu_periph_clock_enable(RCU_TIMER0); #endif #ifdef BSP_USING_PWM1 rcu_periph_clock_enable(RCU_TIMER1); #endif #ifdef BSP_USING_PWM2 rcu_periph_clock_enable(RCU_TIMER2); #endif #ifdef BSP_USING_PWM3 rcu_periph_clock_enable(RCU_TIMER3); #endif #ifdef BSP_USING_PWM4 rcu_periph_clock_enable(RCU_TIMER4); #endif rcu_periph_clock_enable(RCU_AF); for (i = 0; i < sizeof(pwm_obj) / sizeof(pwm_obj[0]); i++) { pwm_obj[i].config = &pwm_config[i]; rt_device_pwm_register(&pwm_obj[i].pwm_device, pwm_config[i].name, &gd32_drv_ops, pwm_obj[i].config); gd32_pwm_init(&pwm_config[i]); } return result; } INIT_DEVICE_EXPORT(rt_pwm_init); #endif /* RT_USING_PWM */