353 lines
10 KiB
C
353 lines
10 KiB
C
/*
|
|
* 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 <rtdbg.h>
|
|
|
|
#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 */
|