/*
 * @File name    : driver_ledc.c
 * @Author       : Bluetrum IOT Team K.
 * @Date         : 2023-07-10
 * @Description  : This file provides functions to manage the most functionalities
 *                 of the LEDC peripheral.
 * @WARNING      : Due to IC logical error, we should make sure no other peripherals 
                   use the same DMA address as LEDC(SRAM0), or dma of ledc would be error.
 * Copyright (c) by Bluetrum, All Rights Reserved.
 */
#include "driver_ledc.h"


/**
 * @brief  Initializes the LEDC peripheral according to the specified
 *         parameters in the ledc_timing_init_struct.
 * @param  ledc_timing_init_struct: pointer to a ledc_init_typedef structure that
 *         contains the configuration information for the specified LEDC peripheral.
 * @retval None
 */
void ledc_init(ledc_init_typedef *ledc_timing_init_struct)
{
    u32 tmp_reg;

    /* Load the register value to variable */
    tmp_reg = LEDC->con;

    /* Clear value need to configure */
    tmp_reg &= ~(uint32_t)(LEDC_CON_OUT_INV | \
                           LEDC_CON_BAUD | \
                           LEDC_CON_DELAY | \
                           LEDC_CON_IN_FORMAT | \
                           LEDC_CON_IDLE_LEVEL | \
                           LEDC_CON_VLD_INV);

    /* Config output data all invert */
    tmp_reg |= ledc_timing_init_struct->output_invert & LEDC_CON_OUT_INV;
    /* Config input format */
    tmp_reg |= ledc_timing_init_struct->format & LEDC_CON_IN_FORMAT;
    /* Config idle level */
    tmp_reg |= ledc_timing_init_struct->idle_level & LEDC_CON_IDLE_LEVEL;
    /* Config valid data direct or invert */
    tmp_reg |= ledc_timing_init_struct->valid_data_mode & LEDC_CON_VLD_INV;
    if (ledc_timing_init_struct->valid_data_mode & LEDC_CON_VLD_INV) {
        tmp_reg &= ~LEDC_CON_BYTE_INV;
        tmp_reg |= ledc_timing_init_struct->byte_inv_number & LEDC_CON_BYTE_INV;
    }

    /* Restore the value to register */
    LEDC->con = tmp_reg;
}

/**
 * @brief  Initialize the code element timing accordingto the specified
 *         parameters in the ledc_timing_init_struct.
 * @param  ledc_timing_init_struct: pointer to a ledc_timing_init_typedef structure that
 *         contains the configuration information for the specified LEDC peripheral.
 * @retval None
 */
void ledc_timing_init(ledc_timing_init_typedef *ledc_timing_init_struct)
{
    u32 tmp_reg;
    ledc_timing_init_typedef *p = ledc_timing_init_struct;

    /* Load the register value to variable */
    tmp_reg = LEDC->con;
    /* Config baud */
    tmp_reg |= (ledc_timing_init_struct->baud << 16) & LEDC_CON_BAUD;
    /* Config delay */
    tmp_reg |= (ledc_timing_init_struct->delay << 8) & LEDC_CON_DELAY;
    /* Restore the value to register */
    LEDC->con = tmp_reg;

    /* Config code element timing */
    tmp_reg = 0;
    if ((p->code_1_high <= p->baud) && (p->code_0_high <= p->baud)) {
        tmp_reg = (p->code_1_high << 24) | \
                  ((p->baud - p->code_1_high) << 16) | \
                  (p->code_0_high << 8) | \
                  ((p->baud - p->code_0_high));
    }
    LEDC->tix = tmp_reg;

    /* Config reset timing */
    tmp_reg = (p->reset_low << 16) | p->reset_high;
    LEDC->rstx = tmp_reg;
}

/**
 * @brief  De-initialize the specified LEDC peripheral.
 * @retval None
 */
void ledc_deinit(void)
{
    LEDC->con &= ~(LEDC_CON_DMA_INT_EN | \
                   LEDC_CON_RST_INT_EN | \
                   LEDC_CON_INT_EN | \
                   LEDC_CON_EN);
    clk_gate2_cmd(CLK_GATE2_LEDC, CLK_DIS);
}

/**
 * @brief  Enable or disable the specified LEDC peripheral.
 * @param  state: the state of the LEDC peripheral.
 *         This parameter can be: ENABLE or DISABLE.
 * @retval None
 */
void ledc_cmd(FUNCTIONAL_STATE state)
{
    if (state != DISABLE) {
        LEDC->con |= LEDC_CON_EN;
    } else {
        LEDC->con &= ~LEDC_CON_EN;
    }
}

/**
 * @brief  Enable the LEDC DMA function.
 * @param  addr: DMA start address.
 * @param  len: DMA transmit len.
 * @retval None
 */
AT(.com_periph.ledc.transport)
void ledc_dma_kick(uint32_t addr, uint16_t len)
{
    LEDC->addr = addr;
    LEDC->cnt = (0x01 << 31) | len;
}

/**
 * @brief  Start ledc transmit.
 * @retval None
 */
AT(.com_periph.ledc.transport)
void ledc_kick(void)
{
    LEDC->con |= LEDC_CON_LEDC_KICK;
}

/**
 * @brief  Enable or disable the specified LEDC interrupt.
 * @param  isr: Function to be executed for service interruption.
 * @param  pr: Priority of service interruption.
 * @param  flag_type: specifies the LEDC interrupt sources to be enable or disable.
 *         This parameter can be one of the following values:
 *             @arg LEDC_FLAG_DONE: One frame has tx done.
 *             @arg LEDC_FLAG_RST: One frame has tx done and reset time has finished.
 *             @arg LEDC_FLAG_DMA: One DMA transmit finished.
 * @param  state: the new state need to config.
 * @retval None
 */
void ledc_pic_config(isr_t isr, int pr, LEDC_FLAG_TYPEDEF flag_type, FUNCTIONAL_STATE state)
{
    u32 flag_pending_bit = 0;

    if (flag_type == 0) {
        return;
    }

    if (flag_type & LEDC_FLAG_DONE) {
        flag_pending_bit |= LEDC_CON_INT_EN;
    }

    if (flag_type & LEDC_FLAG_RST) {
        flag_pending_bit |= LEDC_CON_RST_INT_EN;
    }

    if (flag_type & LEDC_FLAG_DMA) {
        flag_pending_bit |= LEDC_CON_DMA_INT_EN;
    }

    if (state != DISABLE) {
        LEDC->con |= flag_pending_bit;
        sys_irq_init(IRQn_IR_QDEC_LEDC, pr, isr);
    } else {
        LEDC->con &= ~flag_pending_bit;
    }
}

/**
 * @brief  Check the specified LEDC flag is set or not.
 * @param  flag_type: specifies the flag to check.
 *         the parameter can be one of the following values:
 *             @arg LEDC_FLAG_DONE: One frame has tx done.
 *             @arg LEDC_FLAG_RST: One frame has tx done and reset time has finished.
 *             @arg LEDC_FLAG_DMA: One DMA transmit finished.
 * @retval The state of flag_type (SET or RESET).
 */
AT(.com_periph.ledc.flag)
FLAG_STATE ledc_get_flag(LEDC_FLAG_TYPEDEF flag_type)
{
    u32 flag_pending_bit = 0;

    if (flag_type & LEDC_FLAG_DONE) {
        flag_pending_bit |= LEDC_PEND_LEDC_PND;
    }

    if (flag_type & LEDC_FLAG_RST) {
        flag_pending_bit |= LEDC_PEND_RST_PND;
    }

    if (flag_type & LEDC_FLAG_DMA) {
        flag_pending_bit |= LEDC_PEND_DMA_PND;
    }

    if ((LEDC->pend & flag_pending_bit) != RESET) {
        return SET;
    } else {
        return RESET;
    }
}

/**
 * @brief  Clear the LEDC pending.
 * @param  flag_type: specified flag to clear.
 *             @arg LEDC_FLAG_DONE: One frame has tx done.
 *             @arg LEDC_FLAG_RST: One frame has tx done and reset time has finished.
 *             @arg LEDC_FLAG_DMA: One DMA transmit finished.
 * @retval None
 */
AT(.com_periph.ledc.flag)
void ledc_clear_flag(LEDC_FLAG_TYPEDEF flag_type)
{
    u32 flag_pending_bit = 0;

    if (flag_type & LEDC_FLAG_DONE) {
        flag_pending_bit |= LEDC_PEND_LEDC_PND;
    }

    if (flag_type & LEDC_FLAG_RST) {
        flag_pending_bit |= LEDC_PEND_RST_PND;
    }

    if (flag_type & LEDC_FLAG_DMA) {
        flag_pending_bit |= LEDC_PEND_DMA_PND;
    }

    LEDC->pend = flag_pending_bit;
}

/**
 * @brief  Set the length of frame length.
 * @param  frame_length: the length param of frame that need to config.
 * @retval None
 */
void ledc_set_frame_length(uint16_t frame_length)
{
    LEDC->fd = frame_length & 0xfff;
}

/**
 * @brief  Set the number of loop.
 * @param  loop_n: the number param of loop that need to config.
 * @retval None
 */
void ledc_set_loop_number(uint16_t loop_n)
{
    LEDC->lp = loop_n & 0x7ff;
}

/**
 * @brief  Get the length of frame length.
 * @retval None
 */
uint16_t ledc_get_frame_length(void)
{
    return (LEDC->fd & 0xfff);
}

/**
 * @brief  Get the number of loop.
 * @retval None
 */
uint16_t ledc_get_loop_number(void)
{
    return (LEDC->lp & 0x7ff);
}
