blob: 9172dad60b269487b69e5dc5412e68885a542099 [file] [log] [blame]
--
-- Copyright (C) 2022 Google, LLC
--
-- 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 2 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.
--
with HW.GFX.GMA.Config;
with HW.GFX.GMA.Registers;
with HW.GFX.GMA.Power_And_Clocks;
package body HW.GFX.GMA.PLLs.Dekel_Phy is
use type HW.Word64;
subtype Frequency_KHz is Pos64
range (Frequency_Type'First / 1_000) .. (Frequency_Type'Last / 1_000);
subtype DCO_Range_KHz is Pos64 range 7_992_000 .. 10_000_000;
DPLL_ENABLE_PLL_ENABLE : constant := 1 * 2 ** 31;
DPLL_ENABLE_PLL_LOCK : constant := 1 * 2 ** 30;
DPLL_ENABLE_POWER_ENABLE : constant := 1 * 2 ** 27;
DPLL_ENABLE_POWER_STATE : constant := 1 * 2 ** 26;
function HIP_INDEX_REG (P : DKL_DPLLs) return Registers.Registers_Index
is (if P <= TCPLL4
then Registers.HIP_INDEX_REG0
else Registers.HIP_INDEX_REG1);
function HIP_INDEX_VAL (P : DKL_DPLLs; Val : Word32) return Word32 is
(case P is
when TCPLL1 => Val * 2 ** 0,
when TCPLL2 => Val * 2 ** 8,
when TCPLL3 => Val * 2 ** 16,
when TCPLL4 => Val * 2 ** 24,
when TCPLL5 => Val * 2 ** 0,
when TCPLL6 => Val * 2 ** 8);
function DKL_PLL_ENABLE (P : DKL_DPLLs) return Registers.Registers_Index is
(if Config.Has_New_Type_C_PLL_Enable then
(case P is
when TCPLL1 => Registers.PORTTC1_PLL1_ENABLE,
when TCPLL2 => Registers.PORTTC2_PLL1_ENABLE,
when TCPLL3 => Registers.PORTTC3_PLL1_ENABLE,
when TCPLL4 => Registers.PORTTC4_PLL1_ENABLE,
when TCPLL5 => Registers.MGPLL5_ENABLE,
when TCPLL6 => Registers.MGPLL6_ENABLE)
else
(case P is
when TCPLL1 => Registers.MGPLL1_ENABLE,
when TCPLL2 => Registers.MGPLL2_ENABLE,
when TCPLL3 => Registers.MGPLL3_ENABLE,
when TCPLL4 => Registers.MGPLL4_ENABLE,
when TCPLL5 => Registers.MGPLL5_ENABLE,
when TCPLL6 => Registers.MGPLL6_ENABLE));
type PLL_Regs_Record is record
DKL_REFCLKIN_CTL : Registers.Registers_Index;
DKL_CLKTOP2_CORECLKCTL1 : Registers.Registers_Index;
DKL_CLKTOP2_HSCLKCTL : Registers.Registers_Index;
DKL_PLL_DIV0 : Registers.Registers_Index;
DKL_PLL_DIV1 : Registers.Registers_Index;
DKL_PLL_SSC : Registers.Registers_Index;
DKL_PLL_BIAS : Registers.Registers_Index;
DKL_PLL_COLDST_BIAS : Registers.Registers_Index;
end record;
type PLL_Regs_Array is array (DKL_DPLLs) of PLL_Regs_Record;
PLL_Regs : constant PLL_Regs_Array :=
PLL_Regs_Array'
(TCPLL1 =>
(Registers.DKL_REFCLKIN_CTL_1,
Registers.DKL_CLKTOP2_CCC1_1,
Registers.DKL_CLKTOP2_HSCC_1,
Registers.DKL_PLL_DIV0_1,
Registers.DKL_PLL_DIV1_1,
Registers.DKL_PLL_SSC_1,
Registers.DKL_PLL_BIAS_1,
Registers.DKL_PLL_COLDST_BIAS_1),
TCPLL2 =>
(Registers.DKL_REFCLKIN_CTL_2,
Registers.DKL_CLKTOP2_CCC1_2,
Registers.DKL_CLKTOP2_HSCC_2,
Registers.DKL_PLL_DIV0_2,
Registers.DKL_PLL_DIV1_2,
Registers.DKL_PLL_SSC_2,
Registers.DKL_PLL_BIAS_2,
Registers.DKL_PLL_COLDST_BIAS_2),
TCPLL3 =>
(Registers.DKL_REFCLKIN_CTL_3,
Registers.DKL_CLKTOP2_CCC1_3,
Registers.DKL_CLKTOP2_HSCC_3,
Registers.DKL_PLL_DIV0_3,
Registers.DKL_PLL_DIV1_3,
Registers.DKL_PLL_SSC_3,
Registers.DKL_PLL_BIAS_3,
Registers.DKL_PLL_COLDST_BIAS_3),
TCPLL4 =>
(Registers.DKL_REFCLKIN_CTL_4,
Registers.DKL_CLKTOP2_CCC1_4,
Registers.DKL_CLKTOP2_HSCC_4,
Registers.DKL_PLL_DIV0_4,
Registers.DKL_PLL_DIV1_4,
Registers.DKL_PLL_SSC_4,
Registers.DKL_PLL_BIAS_4,
Registers.DKL_PLL_COLDST_BIAS_4),
TCPLL5 =>
(Registers.DKL_REFCLKIN_CTL_5,
Registers.DKL_CLKTOP2_CCC1_5,
Registers.DKL_CLKTOP2_HSCC_5,
Registers.DKL_PLL_DIV0_5,
Registers.DKL_PLL_DIV1_5,
Registers.DKL_PLL_SSC_5,
Registers.DKL_PLL_BIAS_5,
Registers.DKL_PLL_COLDST_BIAS_5),
TCPLL6 =>
(Registers.DKL_REFCLKIN_CTL_6,
Registers.DKL_CLKTOP2_CCC1_6,
Registers.DKL_CLKTOP2_HSCC_6,
Registers.DKL_PLL_DIV0_6,
Registers.DKL_PLL_DIV1_6,
Registers.DKL_PLL_SSC_6,
Registers.DKL_PLL_BIAS_6,
Registers.DKL_PLL_COLDST_BIAS_6));
DKL_REFCLKIN_CTL_OD_2_MUX_MASK : constant := 7 * 2 ** 8;
DKL_CLKTOP2_CORECLKCTL1_A_DIVRATIO_MASK : constant := 16#ff# * 2 ** 8;
DKL_CLKTOP2_HSCLKCTL_MASK : constant := 16#1_FF00#;
DKL_PLL_DIV0_MASK : constant := 16#1F_FFFF#;
DKL_PLL_DIV1_MASK : constant := 16#1F_00FF#;
DKL_PLL_SSC_MASK : constant := 16#E0FF_3A00#;
DKL_PLL_BIAS_MASK : constant := 16#7FFF_FF00#;
DKL_PLL_COLD_BIAS_MASK : constant := 16#ffff#;
DKL_PLL_BIAS_FRAC_EN_H : constant := 1 * 2 ** 30;
procedure Calc_Dividers
(Clock : in Frequency_Type;
Display : in Display_Type;
DKL_Refclkin_Ctl : out Word32;
DKL_Clktop2_Coreclkctl1 : out Word32;
DKL_Clktop2_HSClkCtl : out Word32;
DCO_Khz : out DCO_Range_KHz;
Success : out Boolean)
is
DCO_Min_Freq, DCO_Max_Freq : Int64;
type Dividers_List is array (1 .. 4) of Int64;
Dividers : constant Dividers_List := (7, 5, 3, 2);
DKL_CLKTOP2_HSCLKCTL_HSDIV_RATIO_2 : constant := 0 * 2 ** 12;
DKL_CLKTOP2_HSCLKCTL_HSDIV_RATIO_3 : constant := 1 * 2 ** 12;
DKL_CLKTOP2_HSCLKCTL_HSDIV_RATIO_5 : constant := 2 * 2 ** 12;
DKL_CLKTOP2_HSCLKCTL_HSDIV_RATIO_7 : constant := 3 * 2 ** 12;
function DKL_REFCLKIN_CTL_OD_2_MUX (N : Word32) return Word32
is (Shift_Left (N, 8));
function DKL_CLKTOP2_CORECLKCTL1_A_DIVRATIO (N : Word32) return Word32
is (Shift_Left (N, 8));
function DKL_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL (N : Word32) return Word32
is (Shift_Left (N, 14));
function DKL_CLKTOP2_HSCLKCTL_CORE_INPUTSEL (N : Word32) return Word32
is (Shift_Left (N, 16));
function DKL_CLKTOP2_HSCLKCTL_DSDIV_RATIO (N : Word32) return Word32
is (Shift_Left (N, 8));
Clock_Khz : constant Frequency_KHz := Frequency_KHz(Clock / 1_000);
begin
if Display = DP then
DCO_Min_Freq := 8_100_000;
DCO_Max_Freq := 8_100_000;
else
DCO_Min_Freq := DCO_Range_KHz'First;
DCO_Max_Freq := DCO_Range_KHz'Last;
end if;
DKL_Refclkin_Ctl := 0;
DKL_clktop2_coreclkctl1 := 0;
DKL_CLKTOP2_hsclkctl := 0;
DCO_KHz := DCO_Min_Freq;
Success := False;
for I in Dividers'Range loop
pragma Loop_Invariant (DCO_KHz >= DCO_Min_Freq and DCO_KHz <= DCO_Max_Freq);
for Div2 in reverse 1 .. 10 loop
declare
Tmp : constant Pos64 := Dividers (I) * Pos64 (Div2) * Pos64(Clock_Khz) * 5;
A_DivRatio, TLineDrv, Inputsel, Hsdiv : Word32;
begin
if Tmp >= DCO_Min_Freq and then Tmp <= DCO_Max_Freq then
DCO_KHz := Tmp;
if Div2 >= 2 then
A_DivRatio := (if Display = DP then 10 else 5);
TLineDrv := 1;
else
A_DivRatio := 5;
TLineDrv := 0;
end if;
Inputsel := (if Display = DP then 0 else 1);
Hsdiv := (case Dividers (I) is
when 2 => DKL_CLKTOP2_HSCLKCTL_HSDIV_RATIO_2,
when 3 => DKL_CLKTOP2_HSCLKCTL_HSDIV_RATIO_3,
when 5 => DKL_CLKTOP2_HSCLKCTL_HSDIV_RATIO_5,
when 7 => DKL_CLKTOP2_HSCLKCTL_HSDIV_RATIO_7,
when others => DKL_CLKTOP2_HSCLKCTL_HSDIV_RATIO_2);
DKL_Refclkin_Ctl := DKL_REFCLKIN_CTL_OD_2_MUX (1);
DKL_clktop2_coreclkctl1 :=
DKL_CLKTOP2_CORECLKCTL1_A_DIVRATIO (A_DivRatio);
DKL_CLKTOP2_hsclkctl :=
DKL_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL (TLineDrv) or
DKL_CLKTOP2_HSCLKCTL_CORE_INPUTSEL (Inputsel) or
Hsdiv or
DKL_CLKTOP2_HSCLKCTL_DSDIV_RATIO (Word32 (Div2));
Success := True;
end if;
exit when Success;
end;
end loop;
exit when Success;
end loop;
end Calc_Dividers;
procedure On
(PLL : in DKL_DPLLs;
Port_Cfg : in Port_Config;
Success : out Boolean)
is
DKL_Refclkin_Ctl : Word32;
DKL_Clktop2_Coreclkctl1 : Word32;
DKL_Clktop2_HSClkCtl : Word32;
DCO_Khz : DCO_Range_KHz;
Clock : Frequency_Type;
begin
if Port_Cfg.Display = HDMI then
Clock := Port_Cfg.Mode.Dotclock;
else
Clock := Frequency_Type (DP_Symbol_Rate (Port_Cfg.DP.Bandwidth));
end if;
Calc_Dividers
(Clock => Clock,
Display => Port_Cfg.Display,
DKL_Refclkin_Ctl => DKL_Refclkin_Ctl,
DKL_Clktop2_Coreclkctl1 => DKL_Clktop2_Coreclkctl1,
DKL_Clktop2_HSClkCtl => DKL_Clktop2_HSClkCtl,
DCO_Khz => DCO_Khz,
Success => Success);
if not Success then
Debug.Put_Line ("Could not find dividers for port!");
return;
end if;
Registers.Set_Mask
(Register => DKL_PLL_ENABLE (PLL),
Mask => DPLL_ENABLE_POWER_ENABLE);
Registers.Wait_Set_Mask
(Register => DKL_PLL_ENABLE (PLL),
Mask => DPLL_ENABLE_POWER_STATE);
declare
Tmp : Int64;
Refclk : Power_And_Clocks.Refclk_Range;
Refclk_Khz : Power_And_Clocks.Refclk_Range_KHz;
FeedFwGain : Word32;
M1Div : constant := 2;
M2Div_Int, M2Div_Rem: Int64;
M2Div_Frac : Word32;
TDC_Target : Word32;
Prop_Coeff, Int_Coeff : Word32;
IRef_Ndiv, Iref_Itrim : Word32;
begin
Power_And_Clocks.Get_Refclk (Refclk);
Refclk_Khz := Power_And_Clocks.Refclk_Range_KHz (Refclk / 1_000);
M2Div_Int := Int64(DCO_Khz / (Refclk_Khz * M1Div));
M2Div_Rem := Int64(DCO_Khz rem (Refclk_Khz * M1Div));
Tmp := M2Div_Rem * 2 ** 22;
M2Div_Frac := Word32 (Tmp / Int64(Refclk_Khz * M1Div));
case Refclk is
when 24_000_000 =>
Iref_Ndiv := 1;
Iref_Itrim := 25;
when 38_400_000 =>
Iref_Ndiv := 2;
Iref_Itrim := 28;
when others =>
Iref_Ndiv := 1;
Iref_Itrim := 28;
end case;
-- Real number math converted to fixed point
-- see note in i915 about these calculations
TDC_Target := Word32 (2 * 1_000 * 100_000 * 10 / (132 * Refclk_Khz) + 5) / 10;
if M2Div_Rem > 0 then
FeedFwGain := (M1Div * 1_000_000 * 100) / (Word32 (DCO_Khz) * 3 / 10);
else
FeedFwGain := 0;
end if;
if DCO_Khz >= 9_000_000 then
Prop_Coeff := 5;
Int_Coeff := 10;
else
Prop_Coeff := 4;
Int_Coeff := 8;
end if;
-- The following DKL registers are at MMIO index 2
Registers.Write (HIP_INDEX_REG (PLL), HIP_INDEX_VAL (PLL, 2));
Registers.Unset_And_Set_Mask
(Register => PLL_Regs (PLL).DKL_REFCLKIN_CTL,
Mask_Unset => DKL_REFCLKIN_CTL_OD_2_MUX_MASK,
Mask_Set => DKL_Refclkin_Ctl);
Registers.Unset_And_Set_Mask
(Register => PLL_Regs (PLL).DKL_CLKTOP2_CORECLKCTL1,
Mask_Unset => DKL_CLKTOP2_CORECLKCTL1_A_DIVRATIO_MASK,
Mask_Set => DKL_Clktop2_Coreclkctl1);
Registers.Unset_And_Set_Mask
(Register => PLL_Regs (PLL).DKL_CLKTOP2_HSCLKCTL,
Mask_Unset => DKL_CLKTOP2_HSCLKCTL_MASK,
Mask_Set => DKL_CLKTOP2_hsclkctl);
Registers.Unset_And_Set_mask
(Register => PLL_Regs (PLL).DKL_PLL_DIV0,
Mask_Unset => DKL_PLL_DIV0_MASK,
Mask_Set => Shift_Left (Int_Coeff, 16) or
Shift_Left (Prop_Coeff, 12) or
Shift_Left (M1Div, 8) or
Word32(M2Div_Int));
Registers.Unset_And_Set_Mask
(Register => PLL_Regs (PLL).DKL_PLL_DIV1,
Mask_Unset => DKL_PLL_DIV1_MASK,
Mask_Set => Shift_Left (Iref_Itrim, 16) or
TDC_Target);
Registers.Unset_And_Set_Mask
(Register => PLL_Regs (PLL).DKL_PLL_SSC,
Mask_Unset => DKL_PLL_SSC_MASK,
Mask_Set => Shift_Left (Iref_Ndiv, 29) or
Shift_Left (4, 11)); -- SSC_STEP_NUM (always 4)
Registers.Unset_And_Set_Mask
(Register => PLL_Regs (PLL).DKL_PLL_BIAS,
Mask_Unset => DKL_PLL_BIAS_MASK,
Mask_Set => Shift_Left (M2Div_Frac, 8) or
(if M2Div_Frac > 0 then DKL_PLL_BIAS_FRAC_EN_H else 0));
Registers.Unset_And_Set_Mask
(Register => PLL_Regs (PLL).DKL_PLL_COLDST_BIAS,
Mask_Unset => DKL_PLL_COLD_BIAS_MASK,
Mask_Set => FeedFwGain);
-- Read back the last programmed PHY PLL register
Registers.Posting_Read (PLL_Regs (PLL).DKL_PLL_COLDST_BIAS);
Registers.Set_Mask
(Register => DKL_PLL_ENABLE (PLL),
Mask => DPLL_ENABLE_PLL_ENABLE);
Registers.Wait_Set_Mask
(Register => DKL_PLL_ENABLE (PLL),
Mask => DPLL_ENABLE_PLL_LOCK);
end;
end On;
procedure Free (PLL : DKL_DPLLs)
is
begin
Registers.Unset_Mask
(Register => DKL_PLL_ENABLE (PLL),
Mask => DPLL_ENABLE_PLL_ENABLE);
Registers.Wait_Unset_Mask
(Register => DKL_PLL_ENABLE (PLL),
Mask => DPLL_ENABLE_PLL_LOCK);
Registers.Unset_Mask
(Register => DKL_PLL_ENABLE (PLL),
Mask => DPLL_ENABLE_POWER_ENABLE);
Registers.Wait_Unset_Mask
(Register => DKL_PLL_ENABLE (PLL),
Mask => DPLL_ENABLE_POWER_STATE);
end Free;
procedure All_Off is
begin
for PLL in DKL_DPLLs loop
Free (PLL);
end loop;
end All_Off;
end HW.GFX.GMA.PLLs.Dekel_Phy;