/*
 * Copyright (C) 2019  Max Regan
 *
 * 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 "Bsp/Drivers/LptimPwm.h"
#include "Bsp/macros.h"

namespace BSP {

using BSP::ReturnCode;

LptimPwm::LptimPwm(LPTIM_TypeDef *lptim)
    : m_lptim(lptim)
{}


void LptimPwm::init_lptim()
{
    /* Enable LPTIM in APB1 */
    SET(RCC->APB1ENR,
        RCC_APB1ENR_LPTIM1EN);

    // Enable low-speed internal
    RCC->CSR |= RCC_CSR_LSION;
    while (!(RCC->CSR & RCC_CSR_LSIRDY)) {};

    /*!< Set the LSE clock to be the source of the LPTIM */
    SET_TO(RCC->CCIPR,
           RCC_CCIPR_LPTIM1SEL,
           RCC_CCIPR_LPTIM1SEL_0);

    /** Write CR CFGR and IER while LPTIM is disabled (LPTIM_CR_ENABLE not yet set) */
    /*!< Disable Interrupts (not needed, this is the default */
    LPTIM1->IER = 0;

    /*!< Reset
     * ENC (Disable encoder mode)
     * TIMOUT (disable timeout mode)
     * TRIGEN (Trigger count start with software only)
     * PRELOAD (Update ARR and CMP registers immediately after write)
     * CKSEL (LPTIM is not using an input clock)
     * COUNTMODE (LPTIM counter updated on every clock pulse)
     * TRGFLT (Do not debounce triggers)
     */
    CLR(LPTIM1->CFGR,
          LPTIM_CFGR_ENC | LPTIM_CFGR_TIMOUT | LPTIM_CFGR_TRIGEN |
          LPTIM_CFGR_TRIGSEL | LPTIM_CFGR_PRELOAD | LPTIM_CFGR_CKSEL |
          LPTIM_CFGR_COUNTMODE);

    /*!< Set
     * PRESC (Set prescaler to 128. Using 32kHz LSE as input, this should
     *    correspond to 250Hz counting.
     */
    CLR(LPTIM1->CFGR, LPTIM_CFGR_PRESC);

    SET(LPTIM1->CR, LPTIM_CR_ENABLE);

    /*!< Do not modify ARR and CMP until after ENABLE bit is set */
    /*!< Produce a 60Hz, signal with minimal "high" time. The display
       only needs 2us of "high" time on EXTCOMM, and it draws a fair
       amount of power. */
    LPTIM1->ARR = 0x4FF;
    LPTIM1->CMP = 0x4FE;
    while(!(LPTIM1->ISR & LPTIM_ISR_ARROK)) {}
    while(!(LPTIM1->ISR & LPTIM_ISR_CMPOK)) {}

    /*!< Enable and start the timer */
    SET(LPTIM1->CR, LPTIM_CR_CNTSTRT);
}


ReturnCode LptimPwm::init()
{
    init_lptim();

    /* Enable GPIO port A */
    SET(RCC->IOPENR, RCC_IOPENR_IOPAEN);

    /* Assign LPTIM1_OUT to PA7 */
    SET_TO(GPIOA->AFR[0],
           GPIO_AFRL_AFRL7,
           1u << GPIO_AFRL_AFRL7_Pos);

    SET_TO(GPIOA->MODER,
           GPIO_MODER_MODE7,
           2u << GPIO_MODER_MODE7_Pos);

    CLR(GPIOA->OTYPER, GPIO_OTYPER_OT_7);
    CLR(GPIOA->PUPDR, GPIO_PUPDR_PUPD7);

    return ReturnCode::OK;
}

}
