------------------------------------------------------------------------------
--                                                                          --
--                         GNAT RUN-TIME COMPONENTS                         --
--                                                                          --
--          Copyright (C) 2012-2015, Free Software Foundation, Inc.         --
--                                                                          --
-- GNAT is free software;  you can  redistribute it  and/or modify it under --
-- terms of the  GNU General Public License as published  by the Free Soft- --
-- ware  Foundation;  either version 3,  or (at your option) any later ver- --
-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE.                                     --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
--                                                                          --
-- You should have received a copy of the GNU General Public License and    --
-- a copy of the GCC Runtime Library Exception along with this program;     --
-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
-- <http://www.gnu.org/licenses/>.                                          --
--                                                                          --
-- GNAT was originally developed  by the GNAT team at  New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc.      --
--                                                                          --
------------------------------------------------------------------------------

--  Customized for the Mikroelektronika STM32 M4 Mini board

pragma Ada_2012; -- To work around pre-commit check?
pragma Restrictions (No_Elaboration_Code);

--  This initialization procedure mainly initializes the PLLs and
--  all derived clocks. For now it also initializes the first USART.
--  To be moved to s-textio, but needs clock info ???

with System.STM32F4; use System.STM32F4;
with System.STM32F4.RCC;
with System.BB.Parameters; use System.BB.Parameters;

procedure Setup_Pll is
   --  Local Subprograms

   package RCC renames System.STM32F4.RCC;

   function "and" (Left, Right : Word) return Boolean is
     ((Left and Right) /= 0);

   procedure Reset (Register : in out Word; Mask : Word);
   procedure Set (Register : in out Word; Mask : Word);

   procedure Initialize_Clocks;
   procedure Reset_Clocks;

   ------------------------------
   -- Clock Tree Configuration --
   ------------------------------

   HSE_Enabled     : constant := 1;  -- use high-speed ext. clock
   HSE_Bypass      : constant := 0;  -- don't bypass ext. resonator
   LSI_Enabled     : constant := 0;  -- use low-speed internal clock

   Activate_PLL    : constant := 1;
   Activate_PLLI2S : constant := 0;

   pragma Assert ((if Activate_PLL /= 0 then HSE_Enabled /= 0),
                  "PLL only supported with external clock");
   pragma Assert (Activate_PLLI2S = 0, "not yet implemented");

   -----------
   -- Reset --
   -----------

   procedure Reset (Register : in out Word; Mask : Word) is
   begin
      Register := Register and not Mask;
   end Reset;

   ---------
   -- Set --
   ---------

   procedure Set (Register : in out Word; Mask : Word) is
   begin
      Register := Register or Mask;
   end Set;

   -----------------------
   -- Initialize_Clocks --
   -----------------------

   procedure Initialize_Clocks is

      HSECLK    : constant Integer := Integer (HSE_Clock (MCU_ID.DEV_ID));

      -------------------------------
      -- Compute Clock Frequencies --
      -------------------------------

      PLLP_Value  : constant RCC.PLLP_Range := 2;
      --  Arbitrary fixed to a convenient value

      PLLM_Value : constant Integer  := HSECLK / 1_000_000;
      PLLCLKIN   : constant Integer := HSECLK / PLLM_Value;
      --  First divider M is set to produce a 1Mhz clock

      PLLN_Value  : constant Integer :=
        (PLLP_Value * Clock_Frequency) / PLLCLKIN;
      --  Compute M to to generate the required frequency

      PLLVC0      : constant Integer := PLLCLKIN * PLLN_Value;
      PLLCLKOUT   : constant Integer := PLLVC0 / PLLP_Value;

      PLLQ_Value  : constant RCC.PLLQ_Range := 7;
      --  Arbitrary fixed

      PLLM     : constant Word := Word (PLLM_Value);
      PLLN     : constant Word := Word (PLLN_Value * 2**6);
      PLLP     : constant Word := Word ((PLLP_Value / 2 - 1) * 2**16);
      PLLQ     : constant Word := Word (PLLQ_Value * 2**24);

      SW       : constant := (if Activate_PLL /= 0 then RCC.CFGR.SW_PLL
                              else RCC.CFGR.SW_HSI);

      SYSCLK   : constant Integer := (if Activate_PLL /= 0 then PLLCLKOUT
                                      else RCC.HSICLK);

      HCLK     : constant Integer :=
        (case AHB_PRE is
            when RCC.CFGR.AHBPRE_DIV1   => SYSCLK / 1,
            when RCC.CFGR.AHBPRE_DIV2   => SYSCLK / 2,
            when RCC.CFGR.AHBPRE_DIV4   => SYSCLK / 4,
            when RCC.CFGR.AHBPRE_DIV8   => SYSCLK / 8,
            when RCC.CFGR.AHBPRE_DIV16  => SYSCLK / 16,
            when RCC.CFGR.AHBPRE_DIV64  => SYSCLK / 64,
            when RCC.CFGR.AHBPRE_DIV128 => SYSCLK / 128,
            when RCC.CFGR.AHBPRE_DIV256 => SYSCLK / 256,
            when RCC.CFGR.AHBPRE_DIV512 => SYSCLK / 512);

      PCLK1    : constant Integer :=
        (case APB1_PRE is
            when RCC.CFGR.APB1PRE_DIV1  => HCLK / 1,
            when RCC.CFGR.APB1PRE_DIV2  => HCLK / 2,
            when RCC.CFGR.APB1PRE_DIV4  => HCLK / 4,
            when RCC.CFGR.APB1PRE_DIV8  => HCLK / 8,
            when RCC.CFGR.APB1PRE_DIV16 => HCLK / 16);

      PCLK2    : constant Integer :=
        (case APB2_PRE is
            when RCC.CFGR.APB2PRE_DIV1  => HCLK / 1,
            when RCC.CFGR.APB2PRE_DIV2  => HCLK / 2,
            when RCC.CFGR.APB2PRE_DIV4  => HCLK / 4,
            when RCC.CFGR.APB2PRE_DIV8  => HCLK / 8,
            when RCC.CFGR.APB2PRE_DIV16 => HCLK / 16);

      AHB_PRE_Rep : constant Word :=
        Word (RCC.CFGR.AHB_PRE_Value'Enum_Rep (AHB_PRE));
      APB1_PRE_Rep : constant Word :=
        Word (RCC.CFGR.APB1_PRE_Value'Enum_Rep (APB1_PRE));
      APB2_PRE_Rep : constant Word :=
        Word (RCC.CFGR.APB2_PRE_Value'Enum_Rep (APB2_PRE));
   begin

      --  Check configuration
      if PLLCLKIN not in RCC.PLLIN_Range
        or else
          PLLVC0 not in RCC.PLLVC0_Range
        or else
          PLLCLKOUT not in RCC.PLLOUT_Range
      then
         raise Program_Error with "Invalid clock configuration";
      end if;

      if SYSCLK /= Clock_Frequency then
         raise Program_Error with "Cannot generate requested clock";
      end if;

      if HCLK not in RCC.HCLK_Range
        or else
         PCLK1 not in RCC.PCLK1_Range
        or else
         PCLK2 not in RCC.PCLK2_Range
      then
         raise Program_Error with "Invalid AHB/APB prescalers configuration";
      end if;

      --  PWR clock enable
      --  Reset the power interface

      RCC.Registers.APB1ENR := RCC.RCC_APB1ENR_PWR;

      --  PWR initialization
      --  Select higher supply power for stable operation at max. freq.

      PWR.CR := PWR_CR_VOS_HIGH_407;

      --  Setup internal clock and wait for HSI stabilisation.
      --  The internal high speed clock is always enabled, because it is the
      --  fallback clock when the PLL fails.

      RCC.Registers.CR := RCC.Registers.CR or RCC.CR.HSION;

      loop
         exit when RCC.Registers.CR and RCC.CR.HSIRDY;
      end loop;

      --  Configure high-speed external clock, if enabled

      if Boolean'Val (HSE_Enabled) then
         RCC.Registers.CR := RCC.Registers.CR or RCC.CR.HSEON
           or (if Boolean'Val (HSE_Bypass) then RCC.CR.HSEBYP else 0);

         loop
            exit when RCC.Registers.CR and RCC.CR.HSERDY;
         end loop;
      end if;

      --  Configure low-speed internal clock if enabled

      if Boolean'Val (LSI_Enabled) then
         RCC.Registers.CSR := RCC.Registers.CSR or RCC.CSR.LSION;

         loop
            exit when RCC.Registers.CSR and RCC.CSR.LSIRDY;
         end loop;
      end if;

      --  Activate PLL if enabled

      if Boolean'Val (Activate_PLL) then
         RCC.Registers.PLLCFGR := RCC.PLLSRC_HSE or
           PLLQ or PLLP or PLLN or PLLM;

         Set (RCC.Registers.CR, RCC.CR.PLLON);
         loop
            exit when RCC.Registers.CR and RCC.CR.PLLRDY;
         end loop;
      end if;

      --  Configure flash
      --  Must be done before increasing the frequency, otherwise the CPU
      --  won't be able to fetch new instructions.

      FLASH.ACR := FLASH_ACR.LATENCY_5WS or FLASH_ACR.ICEN or FLASH_ACR.DCEN
        or FLASH_ACR.PRFTEN;

      --  Configure derived clocks

      RCC.Registers.CFGR :=
        --  AHB prescaler is 1, APB1 uses 4 and APB2 prescaler is 2
        AHB_PRE_Rep or APB1_PRE_Rep or APB2_PRE_Rep or
        --  Configure MC01 pin to have the HSI (high speed internal clock)
        RCC.CFGR.MCO1PRE_DIV1 or RCC.CFGR.MCO1SEL_HSI or
        --  Configure MCO2 pin to have SYSCLK / 5
        RCC.CFGR.MCO2PRE_DIV5 or RCC.CFGR.MCO2SEL_SYSCLK or
        --  Select system clock source
        SW;

      if Boolean'Val (Activate_PLL) then
         loop
            exit when (RCC.Registers.CFGR and
                         (RCC.CFGR.SWS_HSE or RCC.CFGR.SWS_PLL))
              = RCC.CFGR.SWS_PLL;
         end loop;

         --  Wait until voltage supply scaling has completed

         loop
            exit when PWR.CSR and PWR_CSR_VOSRDY;
         end loop;
      end if;

   end Initialize_Clocks;

   ------------------
   -- Reset_Clocks --
   ------------------

   procedure Reset_Clocks is
   begin
      --  Switch on high speed internal clock
      Set (RCC.Registers.CR, RCC.CR.HSION);

      --  Reset CFGR regiser
      RCC.Registers.CFGR := 0;

      --  Reset HSEON, CSSON and PLLON bits
      Reset (RCC.Registers.CR,
             RCC.CR.HSEON or RCC.CR.CSSON or RCC.CR.PLLON);

      --  Reset PLL configuration register
      RCC.Registers.PLLCFGR := 16#2400_3010#;

      --  Reset HSE bypass bit
      Reset (RCC.Registers.CR, RCC.CR.HSEBYP);

      --  Disable all interrupts
      RCC.Registers.CIR := 0;
   end Reset_Clocks;

begin
   Reset_Clocks;
   Initialize_Clocks;
end Setup_Pll;