/* Megumin LED display firmware
 * Copyright (C) 2018 Sebastian Götte <code@jaseg.net>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "global.h"
#include "adc.h"
#include "serial.h"


volatile unsigned int sys_time_seconds = 0;
volatile union leds leds;
volatile int32_t gps_1pps_period_sysclk = -1;

int main(void) {
    /* Get GPIOA and SPI1 up to flash status LEDs */
    RCC->AHBENR  |= RCC_AHBENR_GPIOAEN;
    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;

    GPIOA->MODER |=
          (3<<GPIO_MODER_MODER2_Pos)  /* PA2  - LINE_MEAS */
        | (1<<GPIO_MODER_MODER3_Pos)  /* PA3  - LED_STB */
        | (1<<GPIO_MODER_MODER4_Pos)  /* PA4  - SD_CS */
        | (2<<GPIO_MODER_MODER5_Pos)  /* PA5  - SCK */
        | (2<<GPIO_MODER_MODER6_Pos)  /* PA6  - MISO */
        | (2<<GPIO_MODER_MODER7_Pos)  /* PA7  - MOSI */
        | (2<<GPIO_MODER_MODER9_Pos)  /* PA9  - HOST_RX */
        | (2<<GPIO_MODER_MODER10_Pos);/* PA10 - HOST_TX */

    /* Set shift register IO GPIO output speed */
    GPIOA->OSPEEDR |=
          (2<<GPIO_OSPEEDR_OSPEEDR3_Pos)   /* LED_STB */
        | (2<<GPIO_OSPEEDR_OSPEEDR4_Pos)   /* SD_CS */
        | (2<<GPIO_OSPEEDR_OSPEEDR5_Pos)   /* SCK */
        | (2<<GPIO_OSPEEDR_OSPEEDR7_Pos)   /* MOSI */
        | (2<<GPIO_OSPEEDR_OSPEEDR9_Pos);  /* HOST_RX */

    GPIOA->AFR[0] = (0<<GPIO_AFRL_AFRL5_Pos) | (0<<GPIO_AFRL_AFRL6_Pos) | (0<<GPIO_AFRL_AFRL7_Pos);
    GPIOA->AFR[1] = (1<<8) | (1<<4);

    SPI1->CR1 =
          SPI_CR1_SSM
        | SPI_CR1_SSI
        | SPI_CR1_CPOL
        | SPI_CR1_CPHA
        | (4<<SPI_CR1_BR_Pos) /* /32 ~1.5MHz */
        | SPI_CR1_MSTR;
    SPI1->CR2 = (7<<SPI_CR2_DS_Pos);
    SPI1->CR1 |= SPI_CR1_SPE;
    *((volatile uint8_t*)&(SPI1->DR)) = 0xff;

    /* Wait for OCXO to settle */
    for (int i=0; i<1000000; i++)
        ;

    /* Switch clock to PLL based on OCXO input */
    RCC->CR |= RCC_CR_HSEBYP;
    RCC->CR |= RCC_CR_HSEON;
    RCC->CFGR &= ~RCC_CFGR_PLLMUL_Msk & ~RCC_CFGR_SW_Msk & ~RCC_CFGR_PPRE_Msk & ~RCC_CFGR_HPRE_Msk;
    /* PLL config: 19.44MHz /2 x5 -> 48.6MHz */
    RCC->CFGR |= ((5-2)<<RCC_CFGR_PLLMUL_Pos) | RCC_CFGR_PLLSRC_HSE_PREDIV;
    RCC->CFGR2 = ((2-1)<<RCC_CFGR2_PREDIV_Pos);
    RCC->CR |= RCC_CR_PLLON;
    while (!(RCC->CR&RCC_CR_PLLRDY));
    RCC->CFGR |= (2<<RCC_CFGR_SW_Pos);
    SystemCoreClockUpdate();

    /* Start systick */
    SysTick_Config(SystemCoreClock/10); /* 100ms interval */
    NVIC_EnableIRQ(SysTick_IRQn);
    NVIC_SetPriority(SysTick_IRQn, 3<<5);

    /* Turn on rest of periphery */
    RCC->AHBENR  |= RCC_AHBENR_DMAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_FLITFEN | RCC_AHBENR_CRCEN;
    RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN | RCC_APB2ENR_ADCEN  | RCC_APB2ENR_DBGMCUEN |\
                    RCC_APB2ENR_TIM1EN | RCC_APB2ENR_TIM16EN | RCC_APB2ENR_USART1EN;
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM14EN;

    GPIOB->MODER |=
          (2<<GPIO_MODER_MODER1_Pos); /* PB0  - GPS 1pps input */
    GPIOB->AFR[0] = (0<<GPIO_AFRL_AFRL1_Pos);
    GPIOB->PUPDR = 2<<GPIO_PUPDR_PUPDR1_Pos;

    /* Configure TIM16 for LED update via SPI */
    TIM16->CR2 = 0;
    TIM16->DIER = TIM_DIER_UIE | TIM_DIER_CC1IE;
    TIM16->CCMR1 = 0;
    TIM16->CCR1 = 32;
    TIM16->PSC = 48-1; /* 1us */
    TIM16->ARR = 1000-1; /* 1ms */
    TIM16->CR1 = TIM_CR1_CEN;
    NVIC_EnableIRQ(TIM16_IRQn);

    /* Configure TIM14 for GPS 1pps input capture */
    TIM14->CCMR1 = (1<<TIM_CCMR1_CC1S_Pos) | (3<<TIM_CCMR1_IC1F_Pos);
    TIM14->CCER = TIM_CCER_CC1E;
    TIM14->PSC = 1;
    TIM14->ARR = 0xffff;
    TIM14->DIER = TIM_DIER_CC1IE | TIM_DIER_UIE;
    TIM14->EGR = TIM_EGR_UG;
    TIM14->CR1  |= TIM_CR1_CEN;
    NVIC_EnableIRQ(TIM14_IRQn);

    adc_init(1000000);
	adc_timer_init(243, 200); /* 19.44 MHz / 243 -> 200 kHz; /200 -> 1 kHz */

    usart_dma_init();

    while (42) {
        /* Do nothing and let the interrupts do all the work. */
    }
}

void tim14_sr_cc1of(void) {} /* gdb hook */

void TIM14_IRQHandler(void) {
    static uint32_t gps_1pps_period = 0;
    static uint32_t update_inc = 0;
    static bool in_sync = false;

    uint32_t sr = TIM14->SR;
    if (sr & TIM_SR_CC1OF) {
        TIM14->SR &= ~(TIM_SR_CC1IF | TIM_SR_CC1OF);
        tim14_sr_cc1of();

    }
    if (sr & TIM_SR_UIF) {
        TIM14->SR &= ~TIM_SR_UIF;
        if (in_sync) {
            gps_1pps_period += update_inc;
            if (gps_1pps_period > 30000000) { /* Signal out of range */
                in_sync = false;
                gps_1pps_period_sysclk = -1;
                gps_1pps_period = (uint32_t)-1;
            }
        }
        update_inc = 0x10000;
    }

    if (sr & TIM_SR_CC1IF) { /* CC1 event (GPS 1pps input) */
        /* Don't reset update event: If update event arrives while CC1 event is being processed leave UIF set to process
         * update event immediately after return from ISR. */
        uint16_t ccr = TIM14->CCR1;
        if (in_sync) {
            uint32_t new_period = gps_1pps_period + ccr;
            if (new_period < 20000000 || new_period > 30000000) { /* Signal out of range */
                in_sync = false;
                gps_1pps_period_sysclk = -1;
                gps_1pps_period = (uint32_t)-1;
            } else {
                if ((sr & TIM_SR_UIF) /* we processed an update event in this ISR */
                    && (ccr > 0xc000) /* and the capture happened late in the cycle */
                        ) {
                    gps_1pps_period_sysclk = new_period - 0x10000;
                    update_inc = 0x10000;
                    gps_1pps_period = 0x10000 - ccr;
                } else {
                    gps_1pps_period_sysclk = new_period;
                    update_inc = 0x10000 - ccr; /* remaining cycles in this period */
                    gps_1pps_period = 0;
                }
                leds.pps = 200; /* ms */
            }
        } else {
            gps_1pps_period = 0;
            update_inc = 0x10000 - ccr; /* remaining cycles in this period */
            in_sync = true;
        }

    }
}

void TIM16_IRQHandler(void) {
    static int leds_update_counter = 0;
    if (TIM16->SR & TIM_SR_UIF) {
        TIM16->SR &= ~TIM_SR_UIF;

        uint8_t bits = 0, mask = 1;
        for (int i=0; i<8; i++) {
            if (leds.arr[i]) {
                leds.arr[i]--;
                bits |= mask;
            }
            mask <<= 1;
        }

        if (leds_update_counter++ == 10) {
            leds_update_counter = 0;

            /* Workaround for SPI hardware bug: Even if configured to 8-bit mode, the SPI will do a 16-bit transfer if the
             * data register is accessed through a 16-bit write. Unfortunately, the STMCube register defs define DR as an
             * uint16_t, so we have to do some magic here to force an 8-bit write. */
            *((volatile uint8_t*)&(SPI1->DR)) = bits;
            GPIOA->BRR = 1<<3;
        }
    } else {
        TIM16->SR &= ~TIM_SR_CC1IF;
        GPIOA->BSRR = 1<<3;
    }
}

void NMI_Handler(void) {
    asm volatile ("bkpt");
}

void HardFault_Handler(void) __attribute__((naked));
void HardFault_Handler() {
    asm volatile ("bkpt");
}

void SVC_Handler(void) {
    asm volatile ("bkpt");
}


void PendSV_Handler(void) {
    asm volatile ("bkpt");
}

void SysTick_Handler(void) {
    static int n = 0;
    if (n++ == 10) {
        n = 0;
        sys_time_seconds++;
        if (gps_1pps_period_sysclk < 0)
            leds.pps = 200; /* ms */
    }
}