/*
 * Copyright (c) 2006-2023
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author         	Notes
 * 2023-06-05     zengjianwei         	first version
 */

#include <board.h>
#include <gd32f30x.h>
#include <rtdevice.h>
#include <rtthread.h>

#ifdef RT_USING_PWM

//#define DRV_DEBUG
#define LOG_TAG "drv.pwm"
#include <rtdbg.h>

#define MAX_PERIOD 65535
#define MIN_PERIOD 3
#define MIN_PULSE 2

typedef struct
{
    rt_int8_t TimerIndex; // timer index:0~13
    rt_uint32_t Port;     // gpio port:GPIOA/GPIOB/GPIOC/...
    rt_uint32_t pin;      // gpio pin:GPIO_PIN_0~GPIO_PIN_15
    rt_uint16_t channel;  // timer channel
    char *name;
} TIMER_PORT_CHANNEL_MAP_S;

struct gd32_pwm
{
    struct rt_device_pwm pwm_device;
    TIMER_PORT_CHANNEL_MAP_S tim_handle;
};

static struct gd32_pwm gd32_pwm_obj[] = {
#ifdef RT_USING_PWM1
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm1"}},
#endif

#ifdef RT_USING_PWM2
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm2"}},
#endif

#ifdef RT_USING_PWM3
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm3"}},
#endif

#ifdef RT_USING_PWM4
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm4"}},
#endif

#ifdef RT_USING_PWM5
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm5"}},
#endif

#ifdef RT_USING_PWM6
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm6"}},
#endif

#ifdef RT_USING_PWM7
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm7"}},
#endif

#ifdef RT_USING_PWM8
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm8"}},
#endif

#ifdef RT_USING_PWM9
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm9"}},
#endif

#ifdef RT_USING_PWM10
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm10"}},
#endif

#ifdef RT_USING_PWM11
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm11"}},
#endif

#ifdef RT_USING_PWM12
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm12"}},
#endif

#ifdef RT_USING_PWM13
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm13"}},
#endif

#ifdef RT_USING_PWM14
    {.tim_handle = {3, GPIOB, GPIO_PIN_8, 2, "pwm14"}},
#endif
};

typedef struct
{
    rt_uint32_t Port[7];
    rt_int8_t TimerIndex[14];
} TIMER_PERIPH_LIST_S;

static TIMER_PERIPH_LIST_S gd32_timer_periph_list = {
    .Port = {0, 0, 0, 0, 0, 0, 0},
    .TimerIndex = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
};

/*
 * 将所有用到的 gpio port 和 timer 不重复地列举出来，以方便后面不重复地初始化
 */
static rt_err_t pwm_find_timer_periph(void)
{
    rt_int16_t i, j, k;

    /* find gpio port of defined table */
    for (i = 0; i < sizeof(gd32_pwm_obj) / sizeof(gd32_pwm_obj[0]); ++i)
    {
        /* find -1 of gd32_periph_list's member of Port */
        for (j = 0; j < sizeof(gd32_timer_periph_list.Port) / sizeof(gd32_timer_periph_list.Port[0]); ++j)
        {
            if (0 == gd32_timer_periph_list.Port[j])
            {
                break;
            }
        }

        if (j >= sizeof(gd32_timer_periph_list.Port) / sizeof(gd32_timer_periph_list.Port[0]))
        {
            LOG_E("Can not find -1 of gd32_periph_list's member of Port!\n");
            break;
        }

        /* find the different of Port */
        for (k = 0; k < j; ++k)
        {
            if (gd32_pwm_obj[i].tim_handle.Port == gd32_timer_periph_list.Port[k])
            {
                break;
            }
        }

        /* if can not find the same Port */
        if (k == j)
        {
            gd32_timer_periph_list.Port[j] = gd32_pwm_obj[i].tim_handle.Port;
        }
    }

    /* find timer periph of defined table */
    for (i = 0; i < sizeof(gd32_pwm_obj) / sizeof(gd32_pwm_obj[0]); ++i)
    {
        /* find -1 of gd32_periph_list's member of TimerIndex */
        for (j = 0; j < sizeof(gd32_timer_periph_list.TimerIndex) / sizeof(gd32_timer_periph_list.TimerIndex[0]); ++j)
        {
            if (-1 == gd32_timer_periph_list.TimerIndex[j])
            {
                break;
            }
        }

        if (j >= sizeof(gd32_timer_periph_list.TimerIndex) / sizeof(gd32_timer_periph_list.TimerIndex[0]))
        {
            LOG_E("Can not find -1 of gd32_periph_list's member of TimerIndex!\n");
            break;
        }

        /* find the different of TimerIndex */
        for (k = 0; k < j; ++k)
        {
            if (gd32_pwm_obj[i].tim_handle.TimerIndex == gd32_timer_periph_list.TimerIndex[k])
            {
                break;
            }
        }

        /* if can not find the same TimerIndex */
        if (k == j)
        {
            gd32_timer_periph_list.TimerIndex[j] = gd32_pwm_obj[i].tim_handle.TimerIndex;
        }
    }

    return RT_EOK;
}

static rt_uint32_t index_to_timer(rt_int8_t TimerIndex)
{
    switch (TimerIndex)
    {
    case 0:
        return TIMER0;
    case 1:
        return TIMER1;
    case 2:
        return TIMER2;
    case 3:
        return TIMER3;
    case 4:
        return TIMER4;
    case 5:
        return TIMER5;
    case 6:
        return TIMER6;
    case 7:
        return TIMER7;
    case 8:
        return TIMER8;
    case 9:
        return TIMER9;
    case 10:
        return TIMER10;
    case 11:
        return TIMER11;
    case 12:
        return TIMER12;
    case 13:
        return TIMER13;

    default:
        LOG_E("Unsport timer periph!\n");
    }
    return TIMER0;
}

static void gpio_clock_enable(rt_uint32_t Port)
{
    switch (Port)
    {
    case GPIOA:
        rcu_periph_clock_enable(RCU_GPIOA);
        break;
    case GPIOB:
        rcu_periph_clock_enable(RCU_GPIOB);
        break;
    case GPIOC:
        rcu_periph_clock_enable(RCU_GPIOC);
        break;
    case GPIOD:
        rcu_periph_clock_enable(RCU_GPIOD);
        break;
    case GPIOE:
        rcu_periph_clock_enable(RCU_GPIOE);
        break;
    case GPIOF:
        rcu_periph_clock_enable(RCU_GPIOF);
        break;
    case GPIOG:
        rcu_periph_clock_enable(RCU_GPIOG);
        break;

    default:
        LOG_E("Unsport gpio port!\n");
    }
}

static void timer_clock_enable(rt_int8_t TimerIndex)
{
    switch (TimerIndex)
    {
    case 0:
        rcu_periph_clock_enable(RCU_TIMER0);
        break;
    case 1:
        rcu_periph_clock_enable(RCU_TIMER1);
        break;
    case 2:
        rcu_periph_clock_enable(RCU_TIMER2);
        break;
    case 3:
        rcu_periph_clock_enable(RCU_TIMER3);
        break;
    case 4:
        rcu_periph_clock_enable(RCU_TIMER4);
        break;
    case 5:
        rcu_periph_clock_enable(RCU_TIMER5);
        break;
    case 6:
        rcu_periph_clock_enable(RCU_TIMER6);
        break;
    case 7:
        rcu_periph_clock_enable(RCU_TIMER7);
        break;
#ifndef GD32F30X_HD
    case 8:
        rcu_periph_clock_enable(RCU_TIMER8);
        break;
    case 9:
        rcu_periph_clock_enable(RCU_TIMER9);
        break;
    case 10:
        rcu_periph_clock_enable(RCU_TIMER10);
        break;
    case 11:
        rcu_periph_clock_enable(RCU_TIMER11);
        break;
    case 12:
        rcu_periph_clock_enable(RCU_TIMER12);
        break;
    case 13:
        rcu_periph_clock_enable(RCU_TIMER13);
        break;
#endif
    default:
        LOG_E("Unsport timer periph!\n");
    }
}

static void rcu_config(void)
{
    rt_int16_t i;

    for (i = 0; i < sizeof(gd32_timer_periph_list.Port) / sizeof(gd32_timer_periph_list.Port[0]); ++i)
    {
        if (0 == gd32_timer_periph_list.Port[i])
        {
            break;
        }

        /* enable GPIO clock */
        gpio_clock_enable(gd32_timer_periph_list.Port[i]);
    }

    rcu_periph_clock_enable(RCU_AF);

    for (i = 0; i < sizeof(gd32_timer_periph_list.TimerIndex) / sizeof(gd32_timer_periph_list.TimerIndex[0]); ++i)
    {
        if (-1 == gd32_timer_periph_list.TimerIndex[i])
        {
            break;
        }

        /* enable timer clock */
        timer_clock_enable(gd32_timer_periph_list.TimerIndex[i]);
        timer_deinit(index_to_timer(gd32_timer_periph_list.TimerIndex[i]));
    }
}

static void gpio_config(void)
{
    rt_int16_t i;

    /* config the GPIO as analog mode */
    for (i = 0; i < sizeof(gd32_pwm_obj) / sizeof(gd32_pwm_obj[0]); ++i)
    {
        gpio_init(gd32_pwm_obj[i].tim_handle.Port, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, gd32_pwm_obj[i].tim_handle.pin);
    }
}

static void timer_init_para(timer_parameter_struct *initpara)
{
    rt_int16_t i;

    for (i = 0; i < sizeof(gd32_timer_periph_list.TimerIndex) / sizeof(gd32_timer_periph_list.TimerIndex[0]); ++i)
    {
        /* config timer */
        if (-1 != gd32_timer_periph_list.TimerIndex[i])
        {
            timer_init(index_to_timer(gd32_timer_periph_list.TimerIndex[i]), initpara);
        }
    }
}

static void channel_output_config(timer_oc_parameter_struct *ocpara)
{
    rt_int16_t i;
    rt_uint32_t timer_periph;

    /* config the channel config */
    for (i = 0; i < sizeof(gd32_pwm_obj) / sizeof(gd32_pwm_obj[0]); ++i)
    {
        timer_periph = index_to_timer(gd32_pwm_obj[i].tim_handle.TimerIndex);
        timer_channel_output_config(timer_periph, gd32_pwm_obj[i].tim_handle.channel, ocpara);

        timer_channel_output_pulse_value_config(timer_periph, gd32_pwm_obj[i].tim_handle.channel, 7999);
        timer_channel_output_mode_config(timer_periph, gd32_pwm_obj[i].tim_handle.channel, TIMER_OC_MODE_PWM0);
        timer_channel_output_shadow_config(timer_periph, gd32_pwm_obj[i].tim_handle.channel, TIMER_OC_SHADOW_DISABLE);
        /* auto-reload preload shadow reg enable */
        // timer_auto_reload_shadow_enable(timer_periph);
        timer_channel_output_state_config(timer_periph, gd32_pwm_obj[i].tim_handle.channel, TIMER_CCX_DISABLE);
    }

    /* enable timer */
    for (i = 0; i < sizeof(gd32_timer_periph_list.TimerIndex) / sizeof(gd32_timer_periph_list.TimerIndex[0]); ++i)
    {
        if (-1 != gd32_timer_periph_list.TimerIndex[i])
        {
            timer_periph = index_to_timer(gd32_timer_periph_list.TimerIndex[i]);
            timer_enable(timer_periph);
        }
    }
}

static void timer_config(void)
{
    timer_oc_parameter_struct timer_ocintpara;
    timer_parameter_struct timer_initpara;

    /* TIMER configuration */
    timer_initpara.prescaler = 119;
    timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection = TIMER_COUNTER_UP;
    timer_initpara.period = 15999;
    timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
    timer_initpara.repetitioncounter = 0;
    timer_init_para(&timer_initpara);

    /* CHX configuration in PWM mode */
    timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
    timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;
    timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
    timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
    timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
    timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
    channel_output_config(&timer_ocintpara);
}

static rt_err_t drv_pwm_enable(TIMER_PORT_CHANNEL_MAP_S *pstTimerMap, struct rt_pwm_configuration *configuration,
                               rt_bool_t enable)
{
    if (!enable)
    {
        timer_channel_output_state_config(index_to_timer(pstTimerMap->TimerIndex), configuration->channel,
                                          TIMER_CCX_DISABLE);
    }
    else
    {
        timer_channel_output_state_config(index_to_timer(pstTimerMap->TimerIndex), configuration->channel,
                                          TIMER_CCX_ENABLE);
    }

    return RT_EOK;
}

static rt_err_t drv_pwm_get(TIMER_PORT_CHANNEL_MAP_S *pstTimerMap, struct rt_pwm_configuration *configuration)
{
    rt_uint64_t tim_clock;
    rt_uint16_t psc;
    rt_uint32_t chxcv;

    tim_clock = rcu_clock_freq_get(CK_SYS);

    psc = timer_prescaler_read(index_to_timer(pstTimerMap->TimerIndex));
    if (psc == TIMER_CKDIV_DIV2)
    {
        tim_clock = tim_clock / 2;
    }
    else if (psc == TIMER_CKDIV_DIV4)
    {
        tim_clock = tim_clock / 4;
    }

    chxcv = timer_channel_capture_value_register_read(index_to_timer(pstTimerMap->TimerIndex), configuration->channel);
    /* Convert nanosecond to frequency and duty cycle. 1s = 1 * 1000 * 1000 * 1000 ns */
    tim_clock /= 1000000UL;
    configuration->period = (TIMER_CAR(index_to_timer(pstTimerMap->TimerIndex)) + 1) * (psc + 1) * 1000UL / tim_clock;
    configuration->pulse = (chxcv + 1) * (psc + 1) * 1000UL / tim_clock;

    return RT_EOK;
}

static rt_err_t drv_pwm_set(TIMER_PORT_CHANNEL_MAP_S *pstTimerMap, struct rt_pwm_configuration *configuration)
{
    rt_uint32_t period, pulse;
    rt_uint64_t tim_clock, psc;

    tim_clock = rcu_clock_freq_get(CK_SYS);

    /* Convert nanosecond to frequency and duty cycle. 1s = 1 * 1000 * 1000 * 1000 ns */
    tim_clock /= 1000000UL;
    period = (unsigned long long)configuration->period * tim_clock / 1000ULL;
    psc = period / MAX_PERIOD + 1;
    period = period / psc;

    timer_prescaler_config(index_to_timer(pstTimerMap->TimerIndex), psc - 1, TIMER_PSC_RELOAD_NOW);

    if (period < MIN_PERIOD)
    {
        period = MIN_PERIOD;
    }

    timer_autoreload_value_config(index_to_timer(pstTimerMap->TimerIndex), period - 1);

    pulse = (unsigned long long)configuration->pulse * tim_clock / psc / 1000ULL;
    if (pulse < MIN_PULSE)
    {
        pulse = MIN_PULSE;
    }
    else if (pulse > period)
    {
        pulse = period;
    }

    timer_channel_output_pulse_value_config(index_to_timer(pstTimerMap->TimerIndex), configuration->channel, pulse);
    timer_counter_value_config(index_to_timer(pstTimerMap->TimerIndex), 0);

    /* Update frequency value */
    timer_event_software_generate(index_to_timer(pstTimerMap->TimerIndex), TIMER_EVENT_SRC_UPG);

    return RT_EOK;
}

static rt_err_t drv_pwm_control(struct rt_device_pwm *device, int cmd, void *arg)
{
    struct rt_pwm_configuration *configuration = (struct rt_pwm_configuration *)arg;
    TIMER_PORT_CHANNEL_MAP_S *pstTimerMap = (TIMER_PORT_CHANNEL_MAP_S *)device->parent.user_data;

    switch (cmd)
    {
    case PWM_CMD_ENABLE:
        return drv_pwm_enable(pstTimerMap, configuration, RT_TRUE);
    case PWM_CMD_DISABLE:
        return drv_pwm_enable(pstTimerMap, configuration, RT_FALSE);
    case PWM_CMD_SET:
        return drv_pwm_set(pstTimerMap, configuration);
    case PWM_CMD_GET:
        return drv_pwm_get(pstTimerMap, configuration);
    default:
        return RT_EINVAL;
    }
}

static struct rt_pwm_ops drv_ops = {drv_pwm_control};

static rt_err_t gd32_hw_pwm_init(void)
{
    pwm_find_timer_periph();
    rcu_config();
    gpio_config();
    timer_config();

    return RT_EOK;
}

static int gd32_pwm_init(void)
{
    int i = 0;
    int result = RT_EOK;

    /* pwm init */
    if (gd32_hw_pwm_init() != RT_EOK)
    {
        LOG_E("PWM init failed");
        result = -RT_ERROR;
        goto __exit;
    }

    LOG_D("PWM init success");

    for (i = 0; i < sizeof(gd32_pwm_obj) / sizeof(gd32_pwm_obj[0]); i++)
    {
        /* register pwm device */
        if (rt_device_pwm_register(&gd32_pwm_obj[i].pwm_device, gd32_pwm_obj[i].tim_handle.name, &drv_ops,
                                   &gd32_pwm_obj[i].tim_handle) == RT_EOK)
        {
            LOG_D("%s register success", gd32_pwm_obj[i].tim_handle.name);
        }
        else
        {
            LOG_E("%s register failed", gd32_pwm_obj[i].tim_handle.name);
            result = -RT_ERROR;
        }
    }

__exit:
    return result;
}
INIT_DEVICE_EXPORT(gd32_pwm_init);
#endif /* RT_USING_PWM */
