rt-thread/bsp/allwinner/libraries/sunxi-hal/hal/source/pwm/hal_pwm.c

564 lines
16 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Copyright (c) 2019-2025 Allwinner Technology Co., Ltd. ALL rights reserved.
* Allwinner is a trademark of Allwinner Technology Co.,Ltd., registered in
* the the People's Republic of China and other countries.
* All Allwinner Technology Co.,Ltd. trademarks are used with permission.
* DISCLAIMER
* THIRD PARTY LICENCES MAY BE REQUIRED TO IMPLEMENT THE SOLUTION/PRODUCT.
* IF YOU NEED TO INTEGRATE THIRD PARTYS TECHNOLOGY (SONY, DTS, DOLBY, AVS OR MPEGLA, ETC.)
* IN ALLWINNERSSDK OR PRODUCTS, YOU SHALL BE SOLELY RESPONSIBLE TO OBTAIN
* ALL APPROPRIATELY REQUIRED THIRD PARTY LICENCES.
* ALLWINNER SHALL HAVE NO WARRANTY, INDEMNITY OR OTHER OBLIGATIONS WITH RESPECT TO MATTERS
* COVERED UNDER ANY REQUIRED THIRD PARTY LICENSE.
* YOU ARE SOLELY RESPONSIBLE FOR YOUR USAGE OF THIRD PARTYS TECHNOLOGY.
* THIS SOFTWARE IS PROVIDED BY ALLWINNER"AS IS" AND TO THE MAXIMUM EXTENT
* PERMITTED BY LAW, ALLWINNER EXPRESSLY DISCLAIMS ALL WARRANTIES OF ANY KIND,
* WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION REGARDING
* THE TITLE, NON-INFRINGEMENT, ACCURACY, CONDITION, COMPLETENESS, PERFORMANCE
* OR MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
* IN NO EVENT SHALL ALLWINNER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* ===========================================================================================
*
* Filename: hal_pwm.c
*
* Description: spi driver core hal,be used by drv_pwm.c
*
* Version: Melis3.0
* Create: 2019-12-23
* Revision: none
* Compiler: GCC:version 9.2.1 20170904 (release),ARM/embedded-7-branch revision 255204
*
* Author: liuyus@allwinnertech.com
* Organization: SWC-BPD
* Last Modified: 2019-12-31 17:55
*
* ===========================================================================================
*/
#include <hal_log.h>
#include <stdio.h>
#include <stdint.h>
#include <hal_clk.h>
#include <hal_reset.h>
#include <sunxi_hal_common.h>
#include <sunxi_hal_pwm.h>
#include "sunxi/clk.h"
#include <hal_cfg.h>
#include <script.h>
// #include <standby/standby.h>
hal_pwm_t sunxi_pwm;
static int pwm_init = 0;
#define SET_REG_VAL(reg_val, shift, width, set_val) ((reg_val & ~((-1UL) >> (32 - width) << shift)) | (set_val << shift))
#define pwm_do_div(n,base) ({ \
u32 __base = (base); \
u32 __rem; \
__rem = ((u64)(n)) % __base; \
(n) = ((u64)(n)) / __base; \
if (__rem > __base / 2) \
++(n); \
__rem; \
})
/************** config *************************/
/*
* pwm_set_clk_src(): pwm clock source selection
*
* @channel_in: pwm channel number
* pwm01 pwm23 pwm45 pwm67 pwm89
*
* @clk_src: The clock you want to set
* 0:OSC24M 1:APB1
*/
void hal_pwm_clk_src_set(uint32_t channel_in, hal_pwm_clk_src clk_src)
{
unsigned long reg_addr = PWM_BASE + PWM_PCCR_BASE;
uint32_t reg_val;
uint32_t channel = channel_in / 2;
reg_addr += 4 * channel;
/*set clock source OSC24M or apb1*/
reg_val = hal_readl(reg_addr);
reg_val = SET_REG_VAL(reg_val, PWM_CLK_SRC_SHIFT, PWM_CLK_SRC_WIDTH, clk_src);
hal_writel(reg_val, reg_addr);
}
/*
* pwm_clk_div_m(): pwm clock divide
*
* @div_m: 1 2 4 8 16 32 64 128 256
*/
void hal_pwm_clk_div_m(uint32_t channel_in, uint32_t div_m)
{
unsigned long reg_addr = PWM_BASE + PWM_PCCR_BASE;
uint32_t reg_val;
uint32_t channel = channel_in / 2;
reg_addr += 4 * channel;
/*set clock div_m*/
reg_val = hal_readl(reg_addr);
reg_val = SET_REG_VAL(reg_val, PWM_DIV_M_SHIFT, PWM_DIV_M_WIDTH, div_m);
hal_writel(reg_val, reg_addr);
}
void hal_pwm_prescal_set(uint32_t channel_in, uint32_t prescal)
{
unsigned long reg_addr = PWM_BASE + PWM_PCR;
uint32_t reg_val;
uint32_t channel = channel_in;
reg_addr += 0x20 * channel;
/*set prescal*/
reg_val = hal_readl(reg_addr);
reg_val = SET_REG_VAL(reg_val, PWM_PRESCAL_SHIFT, PWM_PRESCAL_WIDTH, prescal);
hal_writel(reg_val, reg_addr);
}
/* active cycles */
void hal_pwm_set_active_cycles(uint32_t channel_in, uint32_t active_cycles) //64
{
unsigned long reg_addr = PWM_BASE + PWM_PPR ;
uint32_t reg_val;
uint32_t channel = channel_in;
reg_addr += 0x20 * channel;
/*set active*/
reg_val = hal_readl(reg_addr);
reg_val = SET_REG_VAL(reg_val, PWM_ACTIVE_CYCLES_SHIFT, PWM_ACTIVE_CYCLES_WIDTH, active_cycles);
hal_writel(reg_val, reg_addr);
}
/* entire cycles */
void hal_pwm_set_period_cycles(uint32_t channel_in, uint32_t period_cycles)
{
unsigned long reg_addr = PWM_BASE + PWM_PPR ;
uint32_t reg_val;
uint32_t channel = channel_in;
reg_addr += 0x20 * channel;
/*set clock BYPASS*/
reg_val = hal_readl(reg_addr);
reg_val = SET_REG_VAL(reg_val, PWM_PERIOD_SHIFT, PWM_PERIOD_WIDTH, period_cycles);
hal_writel(reg_val, reg_addr);
}
static uint32_t get_pccr_reg_offset(uint32_t channel)
{
uint32_t val;
switch (channel)
{
case 0:
case 1:
return PWM_PCCR01;
break;
case 2:
case 3:
return PWM_PCCR23;
break;
case 4:
case 5:
return PWM_PCCR45;
break;
case 6:
case 7:
return PWM_PCCR67;
break;
default :
PWM_ERR("channel is error \n");
return PWM_PCCR01;
break;
}
}
/************ enable **************/
void hal_pwm_enable_clk_gating(uint32_t channel_in)
{
unsigned long reg_addr = PWM_BASE + PWM_PCGR;
uint32_t reg_val;
uint32_t channel = channel_in / 2;
/*enable clk_gating*/
reg_addr += 4 * channel;
reg_val = hal_readl(reg_addr);
reg_val = SET_REG_VAL(reg_val, PWM_CLK_GATING_SHIFT, PWM_CLK_GATING_WIDTH, 1);
hal_writel(reg_val, reg_addr);
}
void hal_pwm_enable_controller(uint32_t channel_in)
{
unsigned long reg_addr = PWM_BASE + PWM_PER;
uint32_t reg_val;
reg_val = readl(reg_addr);
reg_val |= 1 << channel_in;
writel(reg_val, reg_addr);
}
/************ disable **************/
void hal_pwm_disable_controller(uint32_t channel_in)
{
unsigned long reg_val;
unsigned long reg_addr = PWM_BASE + PWM_PER;
reg_val = readl(reg_addr);
reg_val |= 1 << channel_in;
writel(reg_val, reg_addr);
}
/*************** polarity *****************/
void hal_pwm_porality(uint32_t channel_in, hal_pwm_polarity polarity)
{
uint32_t reg_val;
unsigned long reg_addr = PWM_BASE + PWM_PCR;
uint32_t channel = channel_in;
reg_addr += 0x20 * channel;
/*set polarity*/
reg_val = hal_readl(reg_addr);
reg_val = SET_REG_VAL(reg_val, PWM_ACT_STA_SHIFT, PWM_ACT_STA_WIDTH, polarity);
hal_writel(reg_val, reg_addr);
}
static int hal_pwm_pinctrl_init(hal_pwm_t sunxi_pwm, int channel)
{
user_gpio_set_t gpio_cfg = {0};
char pwm_name[16];
int count, ret;
sprintf(pwm_name, "pwm%d", channel);
count = Hal_Cfg_GetGPIOSecKeyCount(pwm_name);
if (!count)
{
PWM_ERR("[pwm%d] not support in sys_config\n", channel);
return -1;
}
Hal_Cfg_GetGPIOSecData(pwm_name, &gpio_cfg, count);
sunxi_pwm.pin[channel] = (gpio_cfg.port - 1) * 32 + gpio_cfg.port_num;
sunxi_pwm.enable_muxsel[channel] = gpio_cfg.mul_sel;
ret = hal_gpio_pinmux_set_function(sunxi_pwm.pin[channel], sunxi_pwm.enable_muxsel[channel]);
if (ret)
{
PWM_ERR("[pwm%d] PIN%u set function failed! return %d\n", channel, sunxi_pwm.pin[channel], ret);
return -1;
}
ret = hal_gpio_set_driving_level(sunxi_pwm.pin[channel], gpio_cfg.drv_level);
if (ret)
{
PWM_ERR("[spi%d] PIN%u set driving level failed! return %d\n", channel, gpio_cfg.drv_level, ret);
return -1;
}
if (gpio_cfg.pull)
{
return hal_gpio_set_pull(sunxi_pwm.pin[channel], gpio_cfg.pull);
}
return 0;
}
static int hal_pwm_pinctrl_exit(hal_pwm_t sunxi_pwm, uint32_t channel)
{
if (sunxi_pwm.pin[channel]) //sys_config
{
return hal_gpio_pinmux_set_function(sunxi_pwm.pin[channel], 0); //gpio_in
}
else
{
return hal_gpio_pinmux_set_function(pwm_gpio[channel].pwm_pin, 0);
}
}
/****the function provide for pwm driverr******************************************/
pwm_status_t hal_pwm_init(void)
{
PWM_INFO("pwm init start");
if (pwm_init)
{
pwm_init++;
return 0;
}
sunxi_pwm.pwm_clk_type = SUNXI_PWM_CLK_TYPE;
sunxi_pwm.pwm_bus_clk_id = SUNXI_PWM_CLK_ID;
sunxi_pwm.pwm_reset_type = SUNXI_PWM_RESET_TYPE;
sunxi_pwm.pwm_reset_id = SUNXI_PWM_RESET_ID;
if (!sunxi_pwm.pwm_reset)
{
sunxi_pwm.pwm_reset = hal_reset_control_get(sunxi_pwm.pwm_reset_type, sunxi_pwm.pwm_reset_id);
}
hal_reset_control_deassert(sunxi_pwm.pwm_reset);
if (!sunxi_pwm.pwm_bus_clk)
{
sunxi_pwm.pwm_bus_clk = hal_clock_get(sunxi_pwm.pwm_clk_type, sunxi_pwm.pwm_bus_clk_id);
PWM_INFO("pwm_bus_clk name:%s", sunxi_pwm.pwm_bus_clk->name);
}
if (hal_clock_enable(sunxi_pwm.pwm_bus_clk))
{
return -1;
}
#ifdef CONFIG_STANDBY
register_pm_dev_notify(hal_pwm_suspend, hal_pwm_resume, NULL);
#endif
PWM_INFO("pwm init end ");
pwm_init++;
return 0;
}
pwm_status_t hal_pwm_deinit(void)
{
if (pwm_init)
{
pwm_init--;
if (!pwm_init)
{
hal_reset_control_assert(sunxi_pwm.pwm_reset);
hal_reset_control_put(sunxi_pwm.pwm_reset);
hal_clock_disable(sunxi_pwm.pwm_bus_clk);
hal_clock_put(sunxi_pwm.pwm_bus_clk);
}
}
PWM_INFO("pwm deinit end");
return 0;
}
pwm_status_t hal_pwm_control(int channel, struct pwm_config *config_pwm)
{
PWM_INFO("pwm control start");
uint32_t ret;
unsigned int temp;
unsigned long long c = 0;
unsigned long entire_cycles = 256, active_cycles = 192;
unsigned int reg_offset, reg_shift, reg_width;
unsigned int reg_bypass_shift /*, group_reg_offset*/;
unsigned int reg_clk_src_shift, reg_clk_src_width;
unsigned int reg_div_m_shift, reg_div_m_width, value;
PWM_INFO("period_ns = %ld", config_pwm->period_ns);
PWM_INFO("duty_ns = %ld", config_pwm->duty_ns);
PWM_INFO("polarity = %d", config_pwm->polarity);
PWM_INFO("channel = %d", channel);
if ((config_pwm->period_ns < config_pwm->duty_ns) || (!config_pwm->period_ns))
{
PWM_ERR("paremeter error : period_ns can't greater than duty_ns and period_ns can't be 0");
return -1;
}
/* pwm set port */
ret = hal_pwm_pinctrl_init(sunxi_pwm, channel);
if (ret)
{
hal_gpio_pinmux_set_function(pwm_gpio[channel].pwm_pin, pwm_gpio[channel].pwm_function);
}
/* pwm enable controller */
hal_pwm_enable_controller(channel);
/* pwm set polarity */
hal_pwm_porality(channel, config_pwm->polarity);
/* pwm config function */
uint32_t pre_scal_id = 0, div_m = 0, prescale = 0;
uint32_t pre_scal[][2] =
{
/*reg_val clk_pre_div*/
{0, 1},
{1, 2},
{2, 4},
{3, 8},
{4, 16},
{5, 32},
{6, 64},
{7, 128},
{8, 256},
};
reg_clk_src_shift = PWM_CLK_SRC_SHIFT;
reg_clk_src_width = PWM_CLK_SRC_WIDTH;
reg_offset = get_pccr_reg_offset(channel);
if (config_pwm->period_ns > 0 && config_pwm->period_ns <= 10)
{
/* if freq lt 100M, then direct output 100M clock,set by pass. */
c = 100000000;
reg_bypass_shift = channel;
reg_offset = get_pccr_reg_offset(channel);
temp = hal_readl(PWM_BASE + PWM_PCGR);
temp = SET_BITS(reg_bypass_shift, 1, temp, 1); /* bypass set */
hal_writel(temp, PWM_BASE + PWM_PCGR);
/*clk_src_reg*/
temp = hal_readl(PWM_BASE + reg_offset);
temp = SET_BITS(reg_clk_src_shift, reg_clk_src_width, temp, 1);/*clock source*/
hal_writel(temp, PWM_BASE + reg_offset);
return 0;
}
else if (config_pwm->period_ns > 10 && config_pwm->period_ns <= 334)
{
/* if freq between 3M~100M, then select 100M as clock */
c = 100000000;
/*clk_src_reg : use APB1 clock */
temp = hal_readl(PWM_BASE + reg_offset);
temp = SET_BITS(reg_clk_src_shift, reg_clk_src_width, temp, 1);
hal_writel(temp, PWM_BASE + reg_offset);
}
else if (config_pwm->period_ns > 334)
{
/* if freq < 3M, then select 24M clock */
c = 24000000;
/*clk_src_reg : use OSC24M clock */
temp = hal_readl(PWM_BASE + reg_offset);
temp = SET_BITS(reg_clk_src_shift, reg_clk_src_width, temp, 0);
hal_writel(temp, PWM_BASE + reg_offset);
}
c = c * config_pwm->period_ns;
pwm_do_div(c, 1000000000);
entire_cycles = (unsigned long)c;
for (pre_scal_id = 0; pre_scal_id < 9; pre_scal_id++)
{
if (entire_cycles <= 65536)
{
break;
}
for (prescale = 0; prescale < PRESCALE_MAX + 1; prescale++)
{
entire_cycles = ((unsigned long)c / pre_scal[pre_scal_id][1]) / (prescale + 1);
if (entire_cycles <= 65536)
{
div_m = pre_scal[pre_scal_id][0];
break;
}
}
}
c = (unsigned long long)entire_cycles * config_pwm->duty_ns;
pwm_do_div(c, config_pwm->period_ns);
active_cycles = c;
if (entire_cycles == 0)
{
entire_cycles++;
}
/* config clk div_m */
reg_div_m_shift = PWM_DIV_M_SHIFT;
reg_div_m_width = PWM_DIV_M_WIDTH;
temp = hal_readl(PWM_BASE + reg_offset);
temp = SET_BITS(reg_div_m_shift, reg_div_m_width, temp, div_m);
hal_writel(temp, PWM_BASE + reg_offset);
/* config gating */
reg_shift = channel;
value = hal_readl(PWM_BASE + PWM_PCGR);
value = SET_BITS(reg_shift, 1, value, 1);/* set gating */
hal_writel(value, PWM_BASE + PWM_PCGR);
/* config prescal */
reg_offset = PWM_PCR + 0x20 * channel;
reg_shift = PWM_PRESCAL_SHIFT;
reg_width = PWM_PRESCAL_WIDTH;
temp = hal_readl(PWM_BASE + reg_offset);
temp = SET_BITS(reg_shift, reg_width, temp, prescale);
hal_writel(temp, PWM_BASE + reg_offset);
/* config active cycles */
reg_offset = PWM_PPR + 0x20 * channel;
reg_shift = PWM_ACT_CYCLES_SHIFT;
reg_width = PWM_ACT_CYCLES_WIDTH;
temp = hal_readl(PWM_BASE + reg_offset);
temp = SET_BITS(reg_shift, reg_width, temp, active_cycles);
hal_writel(temp, PWM_BASE + reg_offset);
/* config period cycles */
reg_offset = PWM_PPR + 0x20 * channel;
reg_shift = PWM_PERIOD_CYCLES_SHIFT;
reg_width = PWM_PERIOD_CYCLES_WIDTH;
temp = hal_readl(PWM_BASE + reg_offset);
temp = SET_BITS(reg_shift, reg_width, temp, (entire_cycles - 1));
hal_writel(temp, PWM_BASE + reg_offset);
PWM_INFO("pwm control end ");
return 0;
}
pwm_status_t hal_pwm_resume(void)
{
if (hal_reset_control_assert(sunxi_pwm.pwm_reset))
{
return -1;
}
if (hal_reset_control_deassert(sunxi_pwm.pwm_reset))
{
return -1;
}
if (hal_clock_enable(sunxi_pwm.pwm_bus_clk))
{
return -1;
}
PWM_INFO("hal pwm resume");
return 0;
}
pwm_status_t hal_pwm_suspend(void)
{
if (hal_reset_control_assert(sunxi_pwm.pwm_reset))
{
return -1;
}
if (hal_clock_disable(sunxi_pwm.pwm_bus_clk))
{
return -1;
}
PWM_INFO("hal pwm suspend");
return 0;
}