/*
 * This file is part of the Micro Python project, http://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Lucian Copeland for Adafruit Industries
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include "timers.h"

#include "py/mpconfig.h"
#include "py/obj.h"
#include "py/runtime.h"
#include "supervisor/shared/translate/translate.h"

#include "shared-bindings/microcontroller/__init__.h"
#include "shared-bindings/microcontroller/Pin.h"

#if !(CPY_STM32H7)

#define ALL_CLOCKS 0xFFFF
#define NULL_IRQ 0xFF

static bool stm_timer_reserved[MP_ARRAY_SIZE(mcu_tim_banks)];
static bool stm_timer_never_reset[MP_ARRAY_SIZE(mcu_tim_banks)];

typedef void (*stm_timer_callback_t)(void);
// Array of function pointers.
static stm_timer_callback_t stm_timer_callback[MP_ARRAY_SIZE(mcu_tim_banks)];

static size_t irq_map[] = {
    #ifdef TIM1
    TIM1_CC_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM2
    TIM2_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM3
    TIM3_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM4
    TIM4_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM5
    TIM5_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM6
    #if !defined(DAC_BASE) && !defined(DAC1_BASE)
    TIM6_IRQn,
    #else
    TIM6_DAC_IRQn,
    #endif
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM7
    TIM7_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM8
    TIM8_CC_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM9
    TIM1_BRK_TIM9_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM10
    TIM1_UP_TIM10_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM11
    TIM1_TRG_COM_TIM11_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM12
    TIM8_BRK_TIM12_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM13
    TIM8_UP_TIM13_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM14
    TIM8_TRG_COM_TIM14_IRQn,
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM15
    #ifdef STM32L4
    TIM1_BRK_TIM15_IRQn,
    #else
    TIM15_IRQn,
    #endif
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM16
    #ifdef STM32L4
    TIM1_UP_TIM16_IRQn,
    #else
    TIM16_IRQn,
    #endif
    #else
    NULL_IRQ,
    #endif
    #ifdef TIM17
    #ifdef STM32L4
    TIM1_TRG_COM_TIM17_IRQn
    #else
    TIM17_IRQn,
    #endif
    #else
    NULL_IRQ,
    #endif
};

// Get the frequency (in Hz) of the source clock for the given timer.
//
// From STM ref manual: DM00031020 Rev 19, section 7.2, page 217:
//
// The timer clock frequencies for STM32F405xx/07xx and STM32F415xx/17xx are
// automatically set by hardware. There are two cases:
// 1. If the APB prescaler is 1, the timer clock frequencies are set to the same frequency as
// that of the APB domain to which the timers are connected.
// 2. Otherwise, they are set to twice (×2) the frequency of the APB domain to which the
// timers are connected.

// From STM ref manual: DM00031020 Rev 19, section 6.2, page 153:
//
// The timer clock frequencies for STM32F42xxx and STM32F43xxx are automatically set by
// hardware. There are two cases depending on the value of TIMPRE bit in RCC_CFGR [sic - should be RCC_DKCFGR]
// register:
// * If TIMPRE bit in RCC_DKCFGR register is reset:
//   If the APB prescaler is configured to a division factor of 1, the timer clock frequencies
//   (TIMxCLK) are set to PCLKx. Otherwise, the timer clock frequencies are twice the
//   frequency of the APB domain to which the timers are connected: TIMxCLK = 2xPCLKx.
// * If TIMPRE bit in RCC_DKCFGR register is set:
//   If the APB prescaler is configured to a division factor of 1, 2 or 4, the timer clock
//   frequencies (TIMxCLK) are set to HCLK. Otherwise, the timer clock frequencies is four
//   times the frequency of the APB domain to which the timers are connected: TIMxCLK = 4xPCLKx.

uint32_t stm_peripherals_timer_get_source_freq(TIM_TypeDef *timer) {
    // The timer index starts at 0, but the timer numbers start at TIM1.
    size_t tim_id = stm_peripherals_timer_get_index(timer) + 1;
    uint32_t source, clk_div;
    if (tim_id == 1 || (8 <= tim_id && tim_id <= 11)) {
        // TIM{1,8,9,10,11} are on APB2
        source = HAL_RCC_GetPCLK2Freq();
        // 0b0xx means not divided; 0b100 is divide by 2; 0b101 by 4; 0b110 by 8; 0b111 by 16.
        clk_div = (RCC->CFGR & RCC_CFGR_PPRE2) >> RCC_CFGR_PPRE2_Pos;
    } else {
        // TIM{2,3,4,5,6,7,12,13,14} are on APB1
        source = HAL_RCC_GetPCLK1Freq();
        // 0b0xx means not divided; 0b100 is divide by 2; 0b101 by 4; 0b110 by 8; 0b111 by 16.
        clk_div = (RCC->CFGR & RCC_CFGR_PPRE1) >> RCC_CFGR_PPRE1_Pos;
    }

    // Only some STM32's have TIMPRE.
    #if defined(RCC_CFGR_TIMPRE)
    uint32_t timpre = RCC->DCKCFGR & RCC_CFGR_TIMPRE;
    if (timpre == 0) {
        if (clk_div >= 0b100) {
            source *= 2;
        }
    } else {
        if (clk_div > 0b101) {
            source *= 4;
        } else {
            source = HAL_RCC_GetHCLKFreq();
        }
    }
    #else
    if (clk_div >= 0b100) {
        source *= 2;
    }
    #endif
    return source;
}

size_t stm_peripherals_timer_get_irqnum(TIM_TypeDef *instance) {
    size_t tim_id = stm_peripherals_timer_get_index(instance);
    return irq_map[tim_id];
}

void timers_reset(void) {
    uint16_t never_reset_mask = 0x00;
    for (size_t i = 0; i < MP_ARRAY_SIZE(mcu_tim_banks); i++) {
        if (!stm_timer_never_reset[i]) {
            stm_timer_reserved[i] = false;
        } else {
            never_reset_mask |= 1 << i;
        }
    }
    tim_clock_disable(ALL_CLOCKS & ~(never_reset_mask));
}

TIM_TypeDef *stm_peripherals_find_timer(void) {
    // Check for timers on pins outside the package size
    for (size_t i = 0; i < MP_ARRAY_SIZE(mcu_tim_banks); i++) {
        bool timer_in_package = false;
        // Find each timer instance on the given bank
        for (size_t j = 0; j < MP_ARRAY_SIZE(mcu_tim_pin_list); j++) {
            // If a pin is claimed, we skip it
            if ((mcu_tim_pin_list[j].tim_index == i)
                && (common_hal_mcu_pin_is_free(mcu_tim_pin_list[j].pin) == true)) {
                // Search whether any pins in the package array match it
                for (size_t k = 0; k < mcu_pin_globals.map.alloc; k++) {
                    if ((mcu_tim_pin_list[j].pin == (mcu_pin_obj_t *)(mcu_pin_globals.map.table[k].value))) {
                        timer_in_package = true;
                    }
                }
            }
        }
        // If no results are found, no unclaimed pins with this timer are in this package,
        // and it is safe to pick
        if (timer_in_package == false && mcu_tim_banks[i] != NULL) {
            return mcu_tim_banks[i];
        }
    }
    // TODO: secondary search for timers outside the pins in the board profile

    // Work backwards - higher index timers have fewer pin allocations
    for (size_t i = (MP_ARRAY_SIZE(mcu_tim_banks) - 1); i >= 0; i--) {
        if ((!stm_timer_reserved[i]) && (mcu_tim_banks[i] != NULL)) {
            return mcu_tim_banks[i];
        }
    }
    mp_raise_RuntimeError(translate("All timers in use"));
    return NULL;
}

void stm_peripherals_timer_preinit(TIM_TypeDef *instance, uint8_t prio, void (*callback)(void)) {
    size_t tim_idx = stm_peripherals_timer_get_index(instance);
    stm_timer_callback[tim_idx] = callback;
    tim_clock_enable(1 << tim_idx);
    HAL_NVIC_SetPriority(irq_map[tim_idx], prio, 0);
    HAL_NVIC_EnableIRQ(irq_map[tim_idx]);
}

void stm_peripherals_timer_reserve(TIM_TypeDef *instance) {
    size_t tim_idx = stm_peripherals_timer_get_index(instance);
    stm_timer_reserved[tim_idx] = true;
}

void stm_peripherals_timer_set_callback(void (*callback)(void), TIM_TypeDef *timer) {
    stm_timer_callback[stm_peripherals_timer_get_index(timer)] = callback;
}

void stm_peripherals_timer_free(TIM_TypeDef *instance) {
    size_t tim_idx = stm_peripherals_timer_get_index(instance);
    HAL_NVIC_DisableIRQ(irq_map[tim_idx]);
    stm_timer_callback[tim_idx] = NULL;
    tim_clock_disable(1 << tim_idx);
    stm_timer_reserved[tim_idx] = false;
    stm_timer_never_reset[tim_idx] = false;
}

void stm_peripherals_timer_never_reset(TIM_TypeDef *instance) {
    size_t tim_idx = stm_peripherals_timer_get_index(instance);
    stm_timer_never_reset[tim_idx] = true;
}

void stm_peripherals_timer_reset_ok(TIM_TypeDef *instance) {
    size_t tim_idx = stm_peripherals_timer_get_index(instance);
    stm_timer_never_reset[tim_idx] = false;
}

bool stm_peripherals_timer_is_never_reset(TIM_TypeDef *instance) {
    size_t tim_idx = stm_peripherals_timer_get_index(instance);
    return stm_timer_never_reset[tim_idx];
}

bool stm_peripherals_timer_is_reserved(TIM_TypeDef *instance) {
    size_t tim_idx = stm_peripherals_timer_get_index(instance);
    return stm_timer_reserved[tim_idx];
}

// Note this returns a timer index starting at zero, corresponding to TIM1.
size_t stm_peripherals_timer_get_index(TIM_TypeDef *instance) {
    for (size_t i = 0; i < MP_ARRAY_SIZE(mcu_tim_banks); i++) {
        if (instance == mcu_tim_banks[i]) {
            return i;
        }
    }
    return ~(size_t)0;
}

void tim_clock_enable(uint16_t mask) {
    #ifdef TIM1
    if (mask & (1 << 0)) {
        __HAL_RCC_TIM1_CLK_ENABLE();
    }
    #endif
    #ifdef TIM2
    if (mask & (1 << 1)) {
        __HAL_RCC_TIM2_CLK_ENABLE();
    }
    #endif
    #ifdef TIM3
    if (mask & (1 << 2)) {
        __HAL_RCC_TIM3_CLK_ENABLE();
    }
    #endif
    #ifdef TIM4
    if (mask & (1 << 3)) {
        __HAL_RCC_TIM4_CLK_ENABLE();
    }
    #endif
    #ifdef TIM5
    if (mask & (1 << 4)) {
        __HAL_RCC_TIM5_CLK_ENABLE();
    }
    #endif
    // 6 and 7 are reserved ADC timers
    #ifdef TIM8
    if (mask & (1 << 7)) {
        __HAL_RCC_TIM8_CLK_ENABLE();
    }
    #endif
    #ifdef TIM9
    if (mask & (1 << 8)) {
        __HAL_RCC_TIM9_CLK_ENABLE();
    }
    #endif
    #ifdef TIM10
    if (mask & (1 << 9)) {
        __HAL_RCC_TIM10_CLK_ENABLE();
    }
    #endif
    #ifdef TIM11
    if (mask & (1 << 10)) {
        __HAL_RCC_TIM11_CLK_ENABLE();
    }
    #endif
    #ifdef TIM12
    if (mask & (1 << 11)) {
        __HAL_RCC_TIM12_CLK_ENABLE();
    }
    #endif
    #ifdef TIM13
    if (mask & (1 << 12)) {
        __HAL_RCC_TIM13_CLK_ENABLE();
    }
    #endif
    #ifdef TIM14
    if (mask & (1 << 13)) {
        __HAL_RCC_TIM14_CLK_ENABLE();
    }
    #endif
}

void tim_clock_disable(uint16_t mask) {
    #ifdef TIM1
    if (mask & (1 << 0)) {
        __HAL_RCC_TIM1_CLK_DISABLE();
    }
    #endif
    #ifdef TIM2
    if (mask & (1 << 1)) {
        __HAL_RCC_TIM2_CLK_DISABLE();
    }
    #endif
    #ifdef TIM3
    if (mask & (1 << 2)) {
        __HAL_RCC_TIM3_CLK_DISABLE();
    }
    #endif
    #ifdef TIM4
    if (mask & (1 << 3)) {
        __HAL_RCC_TIM4_CLK_DISABLE();
    }
    #endif
    #ifdef TIM5
    if (mask & (1 << 4)) {
        __HAL_RCC_TIM5_CLK_DISABLE();
    }
    #endif
    // 6 and 7 are reserved ADC timers
    #ifdef TIM8
    if (mask & (1 << 7)) {
        __HAL_RCC_TIM8_CLK_DISABLE();
    }
    #endif
    #ifdef TIM9
    if (mask & (1 << 8)) {
        __HAL_RCC_TIM9_CLK_DISABLE();
    }
    #endif
    #ifdef TIM10
    if (mask & (1 << 9)) {
        __HAL_RCC_TIM10_CLK_DISABLE();
    }
    #endif
    #ifdef TIM11
    if (mask & (1 << 10)) {
        __HAL_RCC_TIM11_CLK_DISABLE();
    }
    #endif
    #ifdef TIM12
    if (mask & (1 << 11)) {
        __HAL_RCC_TIM12_CLK_DISABLE();
    }
    #endif
    #ifdef TIM13
    if (mask & (1 << 12)) {
        __HAL_RCC_TIM13_CLK_DISABLE();
    }
    #endif
    #ifdef TIM14
    if (mask & (1 << 13)) {
        __HAL_RCC_TIM14_CLK_DISABLE();
    }
    #endif
}

STATIC void callback_router(size_t index) {
    if (stm_timer_callback[index - 1]) {
        (*stm_timer_callback[index - 1])();
    }
}

void TIM1_CC_IRQHandler(void) { // Advanced timer
    callback_router(1);
}

void TIM2_IRQHandler(void) {
    callback_router(2);
}

void TIM3_IRQHandler(void) {
    callback_router(3);
}

void TIM4_IRQHandler(void) {
    callback_router(4);
}

void TIM5_IRQHandler(void) {
    callback_router(5);
}

void TIM6_DAC_IRQHandler(void) { // Basic timer (DAC)
    callback_router(6);
}

void TIM7_IRQHandler(void) {    // Basic timer
    callback_router(7);
}

void TIM8_CC_IRQHandler(void) { // Advanced timer
    callback_router(8);
}

// Advanced timer interrupts are currently unused.
void TIM1_BRK_TIM9_IRQHandler(void) {
    callback_router(9);
}

void TIM1_UP_TIM10_IRQHandler(void) {
    callback_router(10);
}

void TIM1_TRG_COM_TIM11_IRQHandler(void) {
    callback_router(11);
}

void TIM8_BRK_TIM12_IRQHandler(void) {
    callback_router(12);
}

void TIM8_UP_TIM13_IRQHandler(void) {
    callback_router(13);
}

void TIM8_TRG_COM_TIM14_IRQHandler(void) {
    callback_router(14);
}

#if (CPY_STM32H7)
void TIM15_IRQHandler(void) {
    callback_router(15);
}

void TIM16_IRQHandler(void) {
    callback_router(16);
}

void TIM17_IRQHandler(void) {
    callback_router(17);
}
#endif

#endif
