/**************************************************************************//**
 * @file     pan_pwm.c
 * @version  V1.00
 * $Revision: 3 $
 * $Date: 19/11/18 19:41 $
 * @brief    Panchip series PWM driver source file
 *
 * @note
 * Copyright (C) 2025 Panchip Technology Corp. All rights reserved.
*****************************************************************************/

#include "pan271x.h"
#include "pan_pwm.h"
#include "pan_clk.h"

/** @addtogroup Panchip_Device_Driver Panchip Device Driver
  @{
*/

/** @addtogroup Panchip_PWM_Driver PWM Driver
  @{
*/


/** @addtogroup Panchip_PWM_EXPORTED_FUNCTIONS PWM Exported Functions
  @{
*/

/**
 * @brief This function config PWM generator and get the nearest frequency in edge aligned auto-reload mode
 * @param[in] pwm The base address of PWM module
 * @param[in] u32ChannelNum PWM channel number. Valid values are between 0~7
 * @param[in] u32Frequency Target generator frequency
 * @param[in] u32DutyCycle Target generator duty cycle percentage. Valid range are between 0 ~ 100. 10 means 10%, 20 means 20%...
 * @param[in] OperateType  Target operation type.Valid value Edge-aligned,Center-Aligned,Precise Center-Aligned 
 * @return Nearest frequency clock in nano second
 * @note Since every two channels, (0 & 1), (2 & 3), (4 & 5),  (6 & 7) shares a prescaler. Call this API to configure PWM frequency may affect
 *       existing frequency of other channel.
 */
uint32_t PWM_ConfigOutputChannel (PWM_T *pwm,
                                  uint32_t u32ChannelNum,
                                  uint32_t u32Frequency,
                                  uint32_t u32DutyCycle)
{
    uint32_t i = SystemCoreClock / u32Frequency;
    uint8_t u8Divider = 1, u8Prescale = 0xFF;
    uint16_t u16period = 0xFFFF;
    // uint32_t u32Hclk = SystemCoreClock;
    uint32_t u32clk;
	uint32_t tmpReg, tmpChannel;

    // Judge clock source
    if (!(CLK->APB1_CLK_CTRL0 & (0x1ul << ((u32ChannelNum / 2) + APB1_CLK_CTRL0_PWM0_01CKSEL_Pos))))
    {
        u32clk = CLK_GetPCLK1Freq();
    }
    else
    {
        if (PMU->SYS_CLK_CTRL & SYS_CLK_CTRL_SLOW_CLK_SEL_Msk)
        {
            u32clk = FREQ_XTL;
        }
        else
        {
            u32clk = FREQ_RCL;
        }
    }
	
	if (u32ChannelNum < 3) {
		tmpReg = pwm->PWM_CLKPSC_L;
		tmpChannel = u32ChannelNum;
	} else if (u32ChannelNum < 6) {
		tmpReg = pwm->PWM_CLKPSC_H;
		tmpChannel = u32ChannelNum - 3;
	} else {
		/* channel set error */
		return 0;
	}

    for (; u8Divider < 17; u8Divider <<= 1)
    {   // clk divider could only be 1, 2, 4, 8, 16
        /*u32Frequency = SystemCoreClock /((u8Prescale+1)*(u8Divider))/( PERIODn+1) ==>
        (u8Prescale+1)*(PERIODn+1) = SystemCoreClock/u32Frequency/u8Divider					*/
        i = (u32clk / u32Frequency) / u8Divider;

        /* If target value is larger than (u16period+1)*(u8Prescale+1), need to use a larger divider*/
        if (i > (0x10000 * 0x100))
            continue;

        /* u16period = 0xFFFF + 1, get a prescaler that u16period value is below 0xFFFF
                 Must first determine a variable value, now we choose u16period*/
        if ((tmpReg >> (tmpChannel * 8) & 0xff) > 0)
        {
            u8Prescale = (tmpReg >> (tmpChannel * 8) & 0xff) + 1;
        }
        else
        {
            u8Prescale = (i + 0xFFFF) / 0x10000;
        }
        /*  u8Prescale must at least be 2, otherwise the output stop*/
        if (u8Prescale < 3)
            u8Prescale = 2;

        /*  (PERIODn+1) = i / u8Prescale */
        i /= u8Prescale;

        /*u16period must less than  0xFFFF*/
        if (i <= 0x10000)
        {
            if (i == 1)
            {
                u16period = 1; // Too fast, and PWM cannot generate expected frequency...
            }
            else
            {
                u16period = i;
            }
            break;
        }
    }
    /*  Store return value here 'cos we're gonna change u8Divider & u8Prescale & u16period to the real value to fill into register*/
    i = u32clk / (u8Prescale * u8Divider * u16period);

    u8Prescale -= 1;
	u16period -= 1;
    
    /* convert to real register value*/
    if(u8Divider == 1)
        u8Divider = 4;
    else if (u8Divider == 2)
        u8Divider = 0;
    else if (u8Divider == 4)
        u8Divider = 1;
    else if (u8Divider == 8)
        u8Divider = 2;
    else // 16
        u8Divider = 3;

    /* every two channels share a prescaler*/
	PWM_SetPrescaler(pwm, u32ChannelNum, u8Prescale);
    pwm->PWM_CLKDIV = (pwm->PWM_CLKDIV & ~(PWM_CLKDIV_CLKDIV0_Msk << (4 * u32ChannelNum))) | (u8Divider << (4 * u32ChannelNum));
		/*set cnt mode as auto reload*/
		/* PWM_SetCntMode(pwm,u32ChannelNum,PWM_CNTMODE_AUTO_RELOAD); */

		/*update compare data register, Duty ratio = (CMPn+1)/(PERIODn+1).*/
    if(u32DutyCycle == 0){
        *((__IO uint32_t *)((((uint32_t) & ((pwm)->PWM_CMPDAT0)) + u32ChannelNum * 4))) = 0;
    }else{
		/*set cnt type as edge align compare data*/
		*((__IO uint32_t *)((((uint32_t) & ((pwm)->PWM_CMPDAT0)) + u32ChannelNum * 4))) = u32DutyCycle * (u16period + 1) / 100 - 1;
	}
		/*update Counter data register*/
    *((__IO uint32_t *)((((uint32_t) & ((pwm)->PWM_PERIOD0)) + (u32ChannelNum) * 4))) = u16period;

    return(i);
}

/**
 * @brief This function configures PWM generator to set period/duty cycles and output level mode.
 * @param[in] pwm               The base address of PWM module
 * @param[in] u32ChannelNum     PWM channel number. Valid values are between 0~7
 * @param[in] u16PeriodCycle    Target period cycles to set
 * @param[in] u16PulseCycle     Target pulse (high level) cycles to set
 * @note IF u16PeriodCycle = 0 OR u16PulseCycle = 0, THEN the output is always low;
 *       ELSE IF u16PeriodCycle < u16PulseCycle, THEN the output is always high;
 *       ELSE the final output PWM waveform meets the following formulas:
 *          Actual period cycles        = u16PeriodCycle + 1
 *          Actual high-level cycles    = u16PulseCycle + 1
 *          Actual low-level cycles     = u16PeriodCycle - u16PulseCycle
 *          Actual duty cycle           = (u16PulseCycle + 1) / (u16PeriodCycle + 1)
 * @return None
 */
void PWM_SetPeriodAndDuty(PWM_T *pwm, uint32_t u32ChannelNum, uint16_t u16PeriodCycle, uint16_t u16PulseCycle)
{
    PWM_SetPeriod(pwm, u32ChannelNum, u16PeriodCycle);
    PWM_SetCompareData(pwm, u32ChannelNum, u16PulseCycle);
}

/**
 * @brief This function start PWM module
 * @param[in] pwm The base address of PWM module
 * @param[in] u32ChannelMask Combination of enabled channels. Each bit corresponds to a channel.
 *                           Bit 0 is channel 0, bit 1 is channel 1...
 * @return None
 */
void PWM_Start(PWM_T *pwm, uint32_t u32ChannelMask)
{
    uint32_t u32Mask = 0, i;
    for(i = 0; i < PWM_CHANNEL_NUM; i ++) {
        if(u32ChannelMask & (1 << i)) {
            u32Mask |= (PWM_CTL_CNTEN0_Msk << (i * 4));
        }
    }
    pwm->PWM_CTL |= u32Mask;
}

/**
 * @brief This function stop PWM module
 * @param[in] pwm The base address of PWM module
 * @param[in] u32ChannelMask Combination of enabled channels. Each bit corresponds to a channel.
 *                           Bit 0 is channel 0, bit 1 is channel 1...
 * @return None
 */
void PWM_Stop(PWM_T *pwm, uint32_t u32ChannelMask)
{
    uint32_t i;
    for(i = 0; i < PWM_CHANNEL_NUM; i ++) {
        if(u32ChannelMask & (1 << i)) {
            *((__IO uint32_t *)((((uint32_t) & ((pwm)->PWM_PERIOD0)) + (i) * 4))) = 0;
        }
    }
}

/**
 * @brief This function stop PWM generation immediately by clear channel enable bit
 * @param[in] pwm The base address of PWM module
 * @param[in] u32ChannelMask Combination of enabled channels. Each bit corresponds to a channel.
 *                           Bit 0 is channel 0, bit 1 is channel 1...
 * @return None
 */
void PWM_ForceStop(PWM_T *pwm, uint32_t u32ChannelMask)
{
    uint32_t u32Mask = 0, i;
    for(i = 0; i < PWM_CHANNEL_NUM; i ++) {
        if(u32ChannelMask & (1 << i)) {
            u32Mask |= (PWM_CTL_CNTEN0_Msk << (i * 4));
        }
    }
    pwm->PWM_CTL &= ~u32Mask;
}

/**
 * @brief This function enable output inverter of specified channel(s)
 * @param[in] pwm The base address of PWM module
 * @param[in] u32ChannelMask Combination of enabled channels. Each bit corresponds to a channel
 *                           Bit 0 represents channel 0, bit 1 represents channel 1...
 * @return None
 * \hideinitializer
 */
void PWM_EnableOutputInverter(PWM_T *pwm, uint32_t u32ChannelMask)
{
    uint32_t tmpCtlReg = pwm->PWM_CTL;
    for(size_t i = 0; i < 6; i++)
    {
        if((u32ChannelMask) & (1 << i))
            tmpCtlReg |= (1 << (PWM_CTL_PINV0_Pos + (i * 4)));
    }
    pwm->PWM_CTL = tmpCtlReg;
}

/**
 * @brief This function disable output inverter of specified channel(s)
 * @param[in] pwm The base address of PWM module
 * @param[in] u32ChannelMask Combination of channels to disable. Each bit corresponds to a channel
 *                           Bit 0 represents channel 0, bit 1 represents channel 1...
 * @return None
 * \hideinitializer
 */
void PWM_DisableOutputInverter(PWM_T *pwm, uint32_t u32ChannelMask)
{
    uint32_t tmpCtlReg = pwm->PWM_CTL;
    for(size_t i = 0; i < 6; i++)
    {
        if((u32ChannelMask) & (1 << i))
            tmpCtlReg &= ~(1 << (PWM_CTL_PINV0_Pos + (i * 4)));
    }
    pwm->PWM_CTL = tmpCtlReg;
}


void PWM_SetPrescaler(PWM_T *pwm, uint32_t u32ChannelNum, uint32_t u32Prescaler)
{
	if (u32ChannelNum < 3) {
		pwm->PWM_CLKPSC_L = (pwm->PWM_CLKPSC_L & ~(PWM_CLKPSC_L_CLKPSC0_Msk << (u32ChannelNum * 8))) | (u32Prescaler << (u32ChannelNum * 8));
	} else if (u32ChannelNum < 6) {
		pwm->PWM_CLKPSC_H = (pwm->PWM_CLKPSC_H & ~(PWM_CLKPSC_H_CLKPSC3_Msk << ((u32ChannelNum-3) * 8))) | (u32Prescaler << ((u32ChannelNum-3) * 8));
	} else {
		/* channel number set error */
	}
}

void PWM_ResetPrescaler(PWM_T *pwm, uint32_t u32ChannelNum)
{
	if (u32ChannelNum < 3) {
		pwm->PWM_CLKPSC_L &= ~(PWM_CLKPSC_L_CLKPSC0_Msk << (u32ChannelNum * 8));
	} else if (u32ChannelNum < 6) {
		pwm->PWM_CLKPSC_H &= ~(PWM_CLKPSC_H_CLKPSC3_Msk << ((u32ChannelNum-4) * 8));
	} else {
		/* channel number set error */
	}
}


/*@}*/ /* end of group Panchip_PWM_EXPORTED_FUNCTIONS */

/*@}*/ /* end of group Panchip_PWM_Driver */

/*@}*/ /* end of group Panchip_Device_Driver */

/*** (C) COPYRIGHT 2016 Panchip Technology Corp. ***/
