rt-thread-official/bsp/nuclei/libraries/gd32vf103/HAL_Drivers/drv_pwm.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 */