/*
 * This file is part of the libopencm3 project.
 *
 * Copyright (C) 2010 Gareth McMullin <gareth@blacksphere.co.nz>
 *
 * This library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This library 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "project.h"


#define BOOTLOADER_BUTTON GPIO12
#define BOOTLOADER_BUTTON_PORT GPIOE


static const clock_scale_t hsi_16mhz_3v3_48 = {
  /* 48MHz */
  .pllm = 16,
  .plln = 96,
  .pllp = 2,
  .pllq = 2,
  .hpre = RCC_CFGR_HPRE_DIV_NONE,
  .ppre1 = RCC_CFGR_PPRE_DIV_4,
  .ppre2 = RCC_CFGR_PPRE_DIV_2,
  .power_save = 1,
  .flash_config = FLASH_ACR_ICE | FLASH_ACR_DCE |
  FLASH_ACR_LATENCY_3WS,
  .apb1_frequency = 12000000,
  .apb2_frequency = 24000000,
};

static void rcc_clock_setup_hsi_3v3 (const clock_scale_t *clock)
{

  /* Enable internal high-speed oscillator. */
  rcc_osc_on (HSI);
  rcc_wait_for_osc_ready (HSI);

  /* Select HSI as SYSCLK source. */
  rcc_set_sysclk_source (RCC_CFGR_SW_HSI);
  rcc_wait_for_sysclk_status (HSI);

  rcc_osc_off (PLL);

  while (RCC_CR & RCC_CR_PLLRDY);

  pwr_set_vos_scale (SCALE1);

  /*
   * Set prescalers for AHB, ADC, ABP1, ABP2.
   * Do this before touching the PLL (TODO: why?).
   */
  rcc_set_hpre (clock->hpre);
  rcc_set_ppre1 (clock->ppre1);
  rcc_set_ppre2 (clock->ppre2);

  rcc_set_main_pll_hsi (clock->pllm, clock->plln,
                        clock->pllp, clock->pllq);

  /* Enable PLL oscillator and wait for it to stabilize. */
  rcc_osc_on (PLL);
  rcc_wait_for_osc_ready (PLL);

  /* Configure flash settings. */
  flash_set_ws (clock->flash_config);

  /* Select PLL as SYSCLK source. */
  rcc_set_sysclk_source (RCC_CFGR_SW_PLL);

  /* Wait for PLL clock to be selected. */
  rcc_wait_for_sysclk_status (PLL);

  /* Set the peripheral clock frequencies used. */
  rcc_apb1_frequency = clock->apb1_frequency;
  rcc_apb2_frequency = clock->apb2_frequency;

}


int main (void)
{

  rcc_periph_clock_enable (RCC_GPIOE);
  rcc_periph_clock_enable (RCC_GPIOB);
  rcc_periph_clock_enable (RCC_GPIOG);
  MAP_INPUT_PU (BOOTLOADER_BUTTON);

  if ((dfu_flag != 0xfee1dead) && (GET (BOOTLOADER_BUTTON))) {


    /* Boot the application if it's valid. */
    if ((* (volatile uint32_t *)APP_ADDRESS & 0x2FFE0000) == 0x20020000) {

      max7219 ("boot");

      rcc_periph_clock_disable (RCC_GPIOE);
      rcc_periph_clock_disable (RCC_GPIOB);
      rcc_periph_clock_disable (RCC_GPIOG);

      /* Set vector table base address. */
      SCB_VTOR = APP_ADDRESS & 0xFFFF;
      /* Initialise master stack pointer. */
      asm volatile ("msr msp, %0\n"
                    "blx %1\n" ::
                    "g" (* (volatile uint32_t *)APP_ADDRESS),
                    "r" (* (uint32_t *) (APP_ADDRESS + 4))
                    : "memory");
    } else
      max7219 ("nofw dfu");
  } else
    max7219 ("dfu");


  dfu_flag = 0;

  rcc_periph_clock_enable (RCC_SYSCFG);

  rcc_clock_setup_hsi_3v3 (&hsi_16mhz_3v3_48);

  RCC_AHB1RSTR |= RCC_AHB1RSTR_ETHMACRST;
  RCC_AHB2RSTR |= RCC_AHB2RSTR_OTGFSRST;
  asm ("nop":::"memory");
  RCC_AHB2RSTR &= ~RCC_AHB2RSTR_OTGFSRST;
  RCC_AHB1RSTR &= ~RCC_AHB1RSTR_ETHMACRST;


  usart_init();
  usart2_xmit_str ("\r\nDFU Bootloader\r\n");
  delay_ms (100);
  usart_init();
  usart2_xmit_str ("Ready\r\n");


  usb();
}