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

564 lines
16 KiB
C
Raw Normal View History

/* 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;
}