| -- |
| -- 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; |