/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
#include "pwm_stm32/pwm_stm32.h"

#include <bsp.h>
#include <hal/hal_bsp.h>
#include <mcu/cmsis_nvic.h>
#include <os/os.h>
#include <pwm/pwm.h>
#include <stm32_common/stm32_hal.h>

#include <assert.h>
#include <stdint.h>
#include <string.h>

#define STM32_PWM_CH_MAX          4
#define STM32_PWM_CH_IDLE         0x0000
#define STM32_PWM_CH_MODE_ENA     LL_TIM_OCMODE_PWM2
#define STM32_PWM_CH_MODE_DIS     LL_TIM_OCMODE_ACTIVE


typedef struct {
    uint32_t           n_cycles;
    user_handler_t     cycle_handler;
    user_handler_t     seq_end_handler;
    void              *cycle_data;
    void              *seq_end_data;
} stm32_pwm_dev_cfg_t;

typedef struct {
    TIM_TypeDef         *timx;
    uint32_t             cycle;
    uint16_t             pin[STM32_PWM_CH_MAX];
    uint16_t             irq;
    stm32_pwm_dev_cfg_t  cfg;
} stm32_pwm_dev_t;

static stm32_pwm_dev_t stm32_pwm_dev[PWM_CNT];

static inline bool
stm32_pwm_ch_is_active(const stm32_pwm_dev_t *pwm, int ch)
{
    return MCU_AFIO_PIN_NONE != pwm->pin[ch];
}

static uint32_t
stm32_pwm_ch(int ch)
{
    switch (ch) {
    case 0: return LL_TIM_CHANNEL_CH1;
    case 1: return LL_TIM_CHANNEL_CH2;
    case 2: return LL_TIM_CHANNEL_CH3;
    case 3: return LL_TIM_CHANNEL_CH4;
    }
    assert(0);
    return 0;
}

static void
stm32_pwm_active_ch_set_mode(stm32_pwm_dev_t *pwm, uint32_t mode)
{
    for (int i=0; i < STM32_PWM_CH_MAX; ++i) {
        if (stm32_pwm_ch_is_active(pwm, i)) {
            LL_TIM_OC_SetMode(pwm->timx, stm32_pwm_ch(i), mode);
        }
    }
}

static void
stm32_pwm_ch_set_compare(TIM_TypeDef *tim, int ch, uint32_t value)
{
    switch (ch) {
    case 0:
        LL_TIM_OC_SetCompareCH1(tim, value);
        break;
    case 1:
        LL_TIM_OC_SetCompareCH2(tim, value);
        break;
    case 2:
        LL_TIM_OC_SetCompareCH3(tim, value);
        break;
    case 3:
        LL_TIM_OC_SetCompareCH4(tim, value);
        break;
    }
}

static void
stm32_pwm_ch_unconfigure(const stm32_pwm_dev_t *pwm, uint32_t id)
{
    uint32_t ch = stm32_pwm_ch(id);

    LL_TIM_CC_DisableChannel(pwm->timx, ch);
    LL_TIM_OC_SetMode(pwm->timx, ch, 0);
    LL_TIM_OC_SetPolarity(pwm->timx, ch, 0);
    LL_TIM_OC_DisablePreload(pwm->timx,  ch);
}

static void
stm32_pwm_isr(stm32_pwm_dev_t *pwm)
{
    uint32_t sr = pwm->timx->SR;
    pwm->timx->SR = ~sr;

    if (pwm->cfg.cycle_handler) {
        pwm->cfg.cycle_handler(pwm->cfg.cycle_data);
    }

    if (pwm->cfg.n_cycles) {
        if (!pwm->cycle) {

            LL_TIM_DisableCounter(pwm->timx);
            LL_TIM_SetCounter(pwm->timx, 0);

            if (pwm->cfg.seq_end_handler) {
                pwm->cfg.seq_end_handler(pwm->cfg.seq_end_data);
            }
        } else {
            if (1 == pwm->cycle) {
                /* prep output pins for shutdown */
                stm32_pwm_active_ch_set_mode(pwm, STM32_PWM_CH_MODE_DIS);
            }
            --pwm->cycle;
        }
    }
}

static void
stm32_pwm_isr_0(void)
{
    stm32_pwm_isr(&stm32_pwm_dev[0]);
}

static void
stm32_pwm_isr_1(void)
{
    stm32_pwm_isr(&stm32_pwm_dev[1]);
}

static void
stm32_pwm_isr_2(void)
{
    stm32_pwm_isr(&stm32_pwm_dev[2]);
}

static int
stm32_pwm_enable(struct pwm_dev *dev)
{
    stm32_pwm_dev_t *pwm;

    pwm = &stm32_pwm_dev[dev->pwm_instance_id];
    pwm->cycle = pwm->cfg.n_cycles;

    stm32_pwm_active_ch_set_mode(pwm, STM32_PWM_CH_MODE_ENA);

    LL_TIM_GenerateEvent_UPDATE(pwm->timx);
    LL_TIM_EnableCounter(pwm->timx);

    return STM32_PWM_ERR_OK;
}

static int
stm32_pwm_disable(struct pwm_dev *dev)
{
    stm32_pwm_dev_t *pwm;

    pwm = &stm32_pwm_dev[dev->pwm_instance_id];

    LL_TIM_DisableCounter(pwm->timx);
    LL_TIM_SetCounter(pwm->timx, 0);

    return STM32_PWM_ERR_OK;
}

static bool
stm32_pwm_is_enabled(struct pwm_dev *dev)
{
    stm32_pwm_dev_t *pwm;

    pwm = &stm32_pwm_dev[dev->pwm_instance_id];

    return LL_TIM_IsEnabledCounter(pwm->timx);
}

static int
stm32_pwm_open(struct os_dev *odev, uint32_t wait, void *arg)
{
    struct pwm_dev *dev;
    int rc;

    dev = (struct pwm_dev *)odev;

    if (os_started()) {
        rc = os_mutex_pend(&dev->pwm_lock, wait);
        if (OS_OK != rc) {
            return rc;
        }
    }

    if (odev->od_flags & OS_DEV_F_STATUS_OPEN) {
        os_mutex_release(&dev->pwm_lock);
        rc = OS_EBUSY;
        return rc;
    }

    return STM32_PWM_ERR_OK;
}

static int
stm32_pwm_close(struct os_dev *odev)
{
    struct pwm_dev *dev;
    stm32_pwm_dev_t *pwm;

    dev = (struct pwm_dev *)odev;

    stm32_pwm_disable(dev);
    pwm = &stm32_pwm_dev[dev->pwm_instance_id];

    for (int i=0; i < STM32_PWM_CH_MAX; ++i) {
        stm32_pwm_ch_set_compare(pwm->timx, i, STM32_PWM_CH_IDLE);
        if (stm32_pwm_ch_is_active(pwm, i)) {
            hal_gpio_init_af(MCU_AFIO_PIN_PAD(pwm->pin[i]), 0, HAL_GPIO_PULL_NONE, 0);
        }
        pwm->pin[i] = MCU_AFIO_PIN_NONE;
        stm32_pwm_ch_unconfigure(pwm, i);
    }

    if (os_started()) {
        os_mutex_release(&dev->pwm_lock);
    }

    return STM32_PWM_ERR_OK;
}

static int
stm32_pwm_ch_configure(struct pwm_dev *dev, uint8_t cnum, struct pwm_chan_cfg *cfg)
{
    stm32_pwm_dev_t *pwm;
    uint32_t ch;

    assert(cnum < STM32_PWM_CH_MAX);
    if (cnum >= dev->pwm_chan_count) {
        return STM32_PWM_ERR_CHAN;
    }

    pwm = &stm32_pwm_dev[dev->pwm_instance_id];
    ch  = stm32_pwm_ch(cnum);

    LL_TIM_CC_DisableChannel(pwm->timx, ch);

    if (stm32_pwm_ch_is_active(pwm, cnum)) {
        if (hal_gpio_init_af(MCU_AFIO_PIN_PAD(pwm->pin[cnum]), 0, HAL_GPIO_PULL_NONE, 0)) {
            return STM32_PWM_ERR_GPIO;
        }
        pwm->pin[cnum] = MCU_AFIO_PIN_NONE;
    }

    if (cfg && MCU_AFIO_PIN_NONE != cfg->pin ) {
        LL_TIM_OC_SetMode(pwm->timx, ch, STM32_PWM_CH_MODE_ENA);
        LL_TIM_OC_SetPolarity(pwm->timx, ch, cfg->inverted ? LL_TIM_OCPOLARITY_HIGH : LL_TIM_OCPOLARITY_LOW);
        LL_TIM_OC_EnablePreload(pwm->timx,  ch);

        if (hal_gpio_init_af(MCU_AFIO_PIN_PAD(cfg->pin), MCU_AFIO_PIN_AF(cfg->pin), HAL_GPIO_PULL_NONE, 0)) {
            return STM32_PWM_ERR_GPIO;
        }
        pwm->pin[cnum] = cfg->pin;

        LL_TIM_CC_EnableChannel(pwm->timx, ch);
    } else {
        stm32_pwm_ch_unconfigure(pwm, cnum);
        pwm->pin[cnum] = MCU_AFIO_PIN_NONE;
    }

    return STM32_PWM_ERR_OK;
}


static int
stm32_pwm_ch_set_duty_cycle(struct pwm_dev *dev, uint8_t cnum, uint16_t fraction)
{
    stm32_pwm_dev_t *pwm;

    assert(cnum < STM32_PWM_CH_MAX);
    if (cnum >= dev->pwm_chan_count) {
        return STM32_PWM_ERR_CHAN;
    }

    pwm = &stm32_pwm_dev[dev->pwm_instance_id];

    stm32_pwm_ch_set_compare(pwm->timx, cnum, fraction);

    return STM32_PWM_ERR_OK;
}

static int
stm32_pwm_set_frequency(struct pwm_dev *dev, uint32_t freq_hz)
{
    stm32_pwm_dev_t *pwm;
    uint32_t id;
    uint32_t timer_clock;
    uint32_t div, div1, div2;

    if (!freq_hz) {
        return -STM32_PWM_ERR_FREQ;
    }

    id = dev->pwm_instance_id;
    pwm = &stm32_pwm_dev[id];

    timer_clock = stm32_hal_timer_get_freq(pwm->timx);
    assert(timer_clock);

    div  = timer_clock / freq_hz;
    if (!div) {
        return -STM32_PWM_ERR_FREQ;
    }

    div1 = div >> 16;
    div2 = div / (div1 + 1);

    if (div1 > div2) {
        uint32_t tmp = div1;
        div1 = div2;
        div2 = tmp;
    }
    div2 -= 1;

    LL_TIM_SetPrescaler(pwm->timx, div1);
    LL_TIM_SetAutoReload(pwm->timx, div2);

    return timer_clock / (div1 + 1) / (div2 + 1);
}

static int
stm32_pwm_configure(struct pwm_dev *dev, struct pwm_dev_cfg *cfg)
{
    stm32_pwm_dev_t *pwm;
    uint32_t prio;

    pwm = &stm32_pwm_dev[dev->pwm_instance_id];

    if (!pwm->irq) {
        return STM32_PWM_ERR_NOIRQ;
    }

    NVIC_DisableIRQ(pwm->irq);

    if (cfg) {
        prio = cfg->int_prio;
        if (!prio) {
            prio = (1 << __NVIC_PRIO_BITS) - 1;
        }

        LL_TIM_EnableIT_UPDATE(pwm->timx);
        NVIC_SetPriority(pwm->irq, prio);
        switch (dev->pwm_instance_id) {
        case 0:
            NVIC_SetVector(pwm->irq, (uintptr_t)stm32_pwm_isr_0);
            break;
        case 1:
            NVIC_SetVector(pwm->irq, (uintptr_t)stm32_pwm_isr_1);
            break;
        case 2:
            NVIC_SetVector(pwm->irq, (uintptr_t)stm32_pwm_isr_2);
            break;
        default:
            return STM32_PWM_ERR_NODEV;
        }

        pwm->cfg.n_cycles         = cfg->n_cycles;
        pwm->cfg.cycle_handler    = cfg->cycle_handler;
        pwm->cfg.seq_end_handler  = cfg->seq_end_handler;
        pwm->cfg.cycle_data       = cfg->cycle_data;
        pwm->cfg.seq_end_data     = cfg->seq_end_data;

        NVIC_EnableIRQ(pwm->irq);
    } else {
        LL_TIM_DisableIT_UPDATE(pwm->timx);
    }

    return STM32_PWM_ERR_OK;
}

static int
stm32_pwm_get_clock_freq(struct pwm_dev *dev)
{
    stm32_pwm_dev_t *pwm;

    pwm = &stm32_pwm_dev[dev->pwm_instance_id];
    return stm32_hal_timer_get_freq(pwm->timx) / (LL_TIM_GetPrescaler(pwm->timx) + 1);
}

static int
stm32_pwm_get_top_value(struct pwm_dev *dev)
{
    stm32_pwm_dev_t *pwm;

    pwm = &stm32_pwm_dev[dev->pwm_instance_id];
    return LL_TIM_GetAutoReload(pwm->timx) + 1;
}

static int
stm32_pwm_get_resolution_bits(struct pwm_dev *dev)
{
    uint32_t period = stm32_pwm_get_top_value(dev) - 1;

    for (int bit=15; bit; --bit) {
        if (period & (0x01 << bit)) {
            return bit + 1;
        }
    }

    return period ? 1 : 0;
}


int
stm32_pwm_dev_init(struct os_dev *odev, void *arg)
{
    stm32_pwm_conf_t *cfg;
    stm32_pwm_dev_t *pwm;
    struct pwm_dev *dev;
    uint32_t id;

    for (id=0; id < PWM_CNT; ++id) {
        pwm = &stm32_pwm_dev[id];
        if (!pwm->timx) {
            break;
        }
    }
    if (PWM_CNT <= id) {
        return STM32_PWM_ERR_NODEV;
    }
    memset(pwm, 0, sizeof(stm32_pwm_dev_t));

    if (NULL == arg) {
        return STM32_PWM_ERR_NOTIM;
    }

    cfg = (stm32_pwm_conf_t*)arg;

    pwm->timx = cfg->tim;
    pwm->irq  = cfg->irq;

    LL_TIM_SetPrescaler(cfg->tim, 0xffff);
    LL_TIM_SetAutoReload(cfg->tim, 0);

    dev = (struct pwm_dev *)odev;
    dev->pwm_instance_id = id;

    switch ((uintptr_t)cfg->tim) {
#ifdef TIM1
    case (uintptr_t)TIM1:
#if defined(LL_APB2_GRP1_PERIPH_TIM1)
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM1);
#elif defined(LL_APB1_GRP2_PERIPH_TIM1)
        LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_TIM1);
#endif
        dev->pwm_chan_count = 4;
        break;
#endif
#ifdef TIM2
    case (uintptr_t)TIM2:
        LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2);
        dev->pwm_chan_count = 4;
        break;
#endif
#ifdef TIM3
    case (uintptr_t)TIM3:
        LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM3);
        dev->pwm_chan_count = 4;
        break;
#endif
#ifdef TIM4
    case (uintptr_t)TIM4:
        LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM4);
        dev->pwm_chan_count = 4;
        break;
#endif
#ifdef TIM5
    case (uintptr_t)TIM5:
        LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM5);
        dev->pwm_chan_count = 4;
        break;
#endif

    /* basic timers TIM6 and TIM7 have no PWM capabilities */

#ifdef TIM8
    case (uintptr_t)TIM8:
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM8);
        dev->pwm_chan_count = 4;
        break;
#endif
#ifdef TIM9
    case (uintptr_t)TIM9:
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM9);
        dev->pwm_chan_count = 2;
        break;
#endif
#ifdef TIM10
    case (uintptr_t)TIM10:
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM10);
        dev->pwm_chan_count = 1;
        break;
#endif
#ifdef TIM11
    case (uintptr_t)TIM11:
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM11);
        dev->pwm_chan_count = 1;
        break;
#endif
#ifdef TIM12
    case (uintptr_t)TIM12:
        LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM12);
        dev->pwm_chan_count = 2;
        break;
#endif
#ifdef TIM13
    case (uintptr_t)TIM13:
        LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM13);
        dev->pwm_chan_count = 1;
        break;
#endif
#ifdef TIM14
    case (uintptr_t)TIM14:
#if defined(LL_APB2_GRP1_PERIPH_TIM14)
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM14);
#elif defined(LL_APB1_GRP1_PERIPH_TIM14)
        LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM14);
#endif
        dev->pwm_chan_count = 1;
        break;
#endif
#ifdef TIM15
    case (uintptr_t)TIM15:
#if defined(LL_APB2_GRP1_PERIPH_TIM15)
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM15);
#elif defined(LL_APB1_GRP2_PERIPH_TIM15)
        LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_TIM15);
#endif
        dev->pwm_chan_count = 2;
        break;
#endif
#ifdef TIM16
    case (uintptr_t)TIM16:
#if defined(LL_APB2_GRP1_PERIPH_TIM16)
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM16);
#elif defined(LL_APB1_GRP2_PERIPH_TIM16)
        LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_TIM16);
#endif
        dev->pwm_chan_count = 1;
        break;
#endif
#ifdef TIM17
    case (uintptr_t)TIM17:
#if defined(LL_APB2_GRP1_PERIPH_TIM17)
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM17);
#elif defined(LL_APB1_GRP2_PERIPH_TIM17)
        LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_TIM17);
#endif
        dev->pwm_chan_count = 1;
        break;
#endif

    /* basic timer TIM18 has no PWM capabilities */

#ifdef TIM19
    case (uintptr_t)TIM19:
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM19);
        dev->pwm_chan_count = 4;
        break;
#endif
#ifdef TIM20
    case (uintptr_t)TIM20:
        LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM20);
        dev->pwm_chan_count = 4;
        break;
#endif

    default:
        assert(0);
    }

    for (int i=0; i < STM32_PWM_CH_MAX; ++i) {
        stm32_pwm_ch_set_compare(pwm->timx, i, STM32_PWM_CH_IDLE);
        pwm->pin[i] = MCU_AFIO_PIN_NONE;
    }

    dev->pwm_funcs.pwm_configure_channel = stm32_pwm_ch_configure;
    dev->pwm_funcs.pwm_configure_device = stm32_pwm_configure;
    dev->pwm_funcs.pwm_disable = stm32_pwm_disable;
    dev->pwm_funcs.pwm_enable = stm32_pwm_enable;
    dev->pwm_funcs.pwm_get_clock_freq = stm32_pwm_get_clock_freq;
    dev->pwm_funcs.pwm_get_resolution_bits = stm32_pwm_get_resolution_bits;
    dev->pwm_funcs.pwm_get_top_value = stm32_pwm_get_top_value;
    dev->pwm_funcs.pwm_is_enabled = stm32_pwm_is_enabled;
    dev->pwm_funcs.pwm_set_duty_cycle = stm32_pwm_ch_set_duty_cycle;
    dev->pwm_funcs.pwm_set_frequency = stm32_pwm_set_frequency;

    os_mutex_init(&dev->pwm_lock);
    OS_DEV_SETHANDLERS(odev, stm32_pwm_open, stm32_pwm_close);

    LL_TIM_EnableARRPreload(cfg->tim);
    LL_TIM_SetCounter(pwm->timx, 0);

    /* If using advanced timer, enable main output (MOE). */
#ifdef IS_TIM_BREAK_INSTANCE
    if (IS_TIM_BREAK_INSTANCE(pwm->timx)) {
        LL_TIM_EnableAllOutputs(pwm->timx);
    }
#endif

    return STM32_PWM_ERR_OK;
}
