| -- |
| -- 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 GNAT.Source_Info; |
| with HW.Debug; |
| with HW.GFX.GMA.Combo_Phy; |
| with HW.GFX.GMA.Config; |
| with HW.GFX.GMA.PCode; |
| with HW.GFX.GMA.Registers; |
| with HW.GFX.GMA.Transcoder; |
| with HW.GFX.GMA.Connectors.TC; |
| |
| use type HW.Word64; |
| |
| package body HW.GFX.GMA.Power_And_Clocks is |
| |
| subtype CDClk_Range is Config.CDClk_Range; |
| |
| type Power_Domain is |
| (PW1, PW2, PW3, PW4, PW5, |
| DDI_A, DDI_B, DDI_C, |
| DDI_USBC1, DDI_USBC2, DDI_USBC3, DDI_USBC4, DDI_USBC5, DDI_USBC6, |
| AUX_A, AUX_B, AUX_C, |
| AUX_USBC1, AUX_USBC2, AUX_USBC3, AUX_USBC4, AUX_USBC5, AUX_USBC6); |
| type Power_Domain_Types is (Power_Well, Power_DDI, Power_AUX); |
| subtype Dynamic_Domain is Power_Domain range PW2 .. Power_Domain'Last; |
| subtype PW_Domain is Power_Domain range PW1 .. PW5; |
| subtype Dynamic_Well is Power_Domain range PW2 .. PW_Domain'Last; |
| subtype Port_Domain is Power_Domain range DDI_A .. AUX_USBC6; |
| subtype DDI_Domain is Power_Domain range DDI_A .. DDI_USBC6; |
| subtype DDI_USBC_Domain is Power_Domain range DDI_USBC1 .. DDI_USBC6; |
| subtype AUX_Domain is Power_Domain range AUX_A .. AUX_USBC6; |
| subtype AUX_USBC_Domain is Power_Domain range AUX_USBC1 .. AUX_USBC6; |
| |
| function PW_Index (PW : PW_Domain) return Natural |
| is |
| (Power_Domain'Pos (PW) - Power_Domain'Pos (PW_Domain'First)); |
| |
| function DDI_Index (DDI : DDI_Domain) return Natural |
| is |
| (Power_Domain'Pos (DDI) - Power_Domain'Pos (DDI_Domain'First)); |
| |
| function AUX_Index (AUX : AUX_Domain) return Natural |
| is |
| (Power_Domain'Pos (AUX) - Power_Domain'Pos (AUX_Domain'First)); |
| |
| function AUX_USBC_Index (AUX: AUX_USBC_Domain) return Natural |
| is |
| (Power_Domain'Pos (AUX) - Power_Domain'Pos (AUX_USBC_Domain'First)); |
| |
| ---------------------------------------------------------------------------- |
| |
| function Power_Domain_Type (PD : Power_Domain) return Power_Domain_Types |
| is |
| (if PD in PW_Domain then Power_Well |
| elsif PD in DDI_Domain then Power_DDI |
| else Power_AUX); |
| |
| type Power_Well_Regs is array (Power_Domain_Types) of Registers.Registers_Index; |
| PWR_CTL_BIOS : constant Power_Well_Regs := |
| (Power_Well => Registers.PWR_WELL_CTL_BIOS, |
| Power_DDI => Registers.PWR_DDI_CTL_BIOS, |
| Power_AUX => Registers.PWR_AUX_CTL_BIOS); |
| PWR_CTL_DRIVER : constant Power_Well_Regs := |
| (Power_Well => Registers.PWR_WELL_CTL_DRIVER, |
| Power_DDI => Registers.PWR_DDI_CTL_DRIVER, |
| Power_AUX => Registers.PWR_AUX_CTL_DRIVER); |
| |
| function Power_State_Mask (PD : Power_Domain) return Word32 |
| is |
| (case PD is |
| when PW_Domain'Range => 1 * 2 ** (2 * PW_Index (PD)), |
| when DDI_Domain'Range => 1 * 2 ** (2 * DDI_Index (PD)), |
| when AUX_Domain'Range => 1 * 2 ** (2 * AUX_Index (PD))); |
| |
| function Power_Request_Mask (PD : Power_Domain) return Word32 is |
| begin |
| return Shift_Left (Power_State_Mask (PD), 1); |
| end Power_Request_Mask; |
| |
| ---------------------------------------------------------------------------- |
| |
| PCH_DPMGUNIT_CLOCK_GATE_DISABLE : constant := 1 * 2 ** 15; |
| NDE_RSTWRN_OPT_RST_PCH_Handshake_En : constant := 1 * 2 ** 4; |
| |
| ---------------------------------------------------------------------------- |
| |
| DBUF_CTL_DBUF_POWER_REQUEST : constant := 1 * 2 ** 31; |
| DBUF_CTL_TRACKER_STATE_SERVICE_MASK : constant := 16#f8_0000#; |
| DBUF_CTL_TRACKER_STATE_SERVICE_SHIFT : constant := 19; |
| DBUF_CTL_DBUF_POWER_STATE : constant := 1 * 2 ** 30; |
| |
| type DBUF_Slices is (S1, S2); |
| DBUF_CTL : constant array (DBUF_Slices) of Registers.Registers_Index := |
| (Registers.DBUF_CTL_S0, |
| Registers.DBUF_CTL_S1); |
| |
| ---------------------------------------------------------------------------- |
| |
| MBUS_ABOX_CTL_BW_CREDITS_MASK : constant := 16#3# * 2 ** 20; |
| MBUS_ABOX_CTL_B_CREDITS_MASK : constant := 16#f# * 2 ** 16; |
| MBUS_ABOX_CTL_BT_CREDITS_POOL1_MASK : constant := 16#1f# * 2 ** 0; |
| MBUS_ABOX_CTL_BT_CREDITS_POOL2_MASK : constant := 16#1f# * 2 ** 8; |
| MBUS_ABOX_CTL_BW_CREDITS_SHIFT : constant := 20; |
| MBUS_ABOX_CTL_B_CREDITS_SHIFT : constant := 16; |
| MBUS_ABOX_CTL_BT_CREDITS_POOL1_SHIFT : constant := 0; |
| MBUS_ABOX_CTL_BT_CREDITS_POOL2_SHIFT : constant := 8; |
| |
| MBUS_ABOX_MASK : constant Word32 := |
| (MBUS_ABOX_CTL_BW_CREDITS_MASK or |
| MBUS_ABOX_CTL_B_CREDITS_MASK or |
| MBUS_ABOX_CTL_BT_CREDITS_POOL1_MASK or |
| MBUS_ABOX_CTL_BT_CREDITS_POOL2_MASK); |
| MBUS_ABOX_CREDITS : constant Word32 := |
| ( 1 * 2 ** MBUS_ABOX_CTL_BW_CREDITS_SHIFT or |
| 1 * 2 ** MBUS_ABOX_CTL_B_CREDITS_SHIFT or |
| 16 * 2 ** MBUS_ABOX_CTL_BT_CREDITS_POOL1_SHIFT or |
| 16 * 2 ** MBUS_ABOX_CTL_BT_CREDITS_POOL2_SHIFT); |
| |
| MBUS_ABOX_CTL : constant array (0 .. 2) of Registers.Registers_Index := |
| (Registers.MBUS_ABOX_CTL, |
| Registers.MBUS_ABOX1_CTL, |
| Registers.MBUS_ABOX2_CTL); |
| |
| ---------------------------------------------------------------------------- |
| |
| DCPR_MASK_MAXLATENCY_MEMUP_CLR : constant := 1 * 2 ** 27; |
| DCPR_MASK_LPMODE : constant := 1 * 2 ** 26; |
| DCPR_SEND_RESP_IMM : constant := 1 * 2 ** 25; |
| DCPR_CLEAR_MEMSTAT_DIS : constant := 1 * 2 ** 24; |
| |
| ---------------------------------------------------------------------------- |
| |
| function HIP_INDEX_REG (Aux : AUX_USBC_Domain) return Registers.Registers_Index |
| is |
| (if Aux <= AUX_USBC4 |
| then Registers.HIP_INDEX_REG0 |
| else Registers.HIP_INDEX_REG1); |
| |
| function HIP_INDEX_VAL (Aux : AUX_USBC_Domain; Val : Word32) return Word32 |
| is |
| (Val * 2 ** (8 * (AUX_USBC_Index (Aux) mod 4))); |
| |
| type DKL_Regs is array (AUX_USBC_Domain) of Registers.Registers_Index; |
| DKL_CMN_UC_DW_27 : constant DKL_Regs := |
| (AUX_USBC1 => Registers.DKL_CMN_UC_DW_27_1, |
| AUX_USBC2 => Registers.DKL_CMN_UC_DW_27_2, |
| AUX_USBC3 => Registers.DKL_CMN_UC_DW_27_3, |
| AUX_USBC4 => Registers.DKL_CMN_UC_DW_27_4, |
| AUX_USBC5 => Registers.DKL_CMN_UC_DW_27_5, |
| AUX_USBC6 => Registers.DKL_CMN_UC_DW_27_6); |
| |
| ---------------------------------------------------------------------------- |
| |
| FUSE_STATUS_PG0_DIST_STATUS : constant := 1 * 2 ** 27; |
| FUSE_STATUS_PGx_DIST_STATUS : constant array (PW_Domain) of Word32 := |
| (PW1 => 1 * 2 ** 26, |
| PW2 => 1 * 2 ** 25, |
| PW3 => 1 * 2 ** 24, |
| PW4 => 1 * 2 ** 23, |
| PW5 => 1 * 2 ** 22); |
| |
| ---------------------------------------------------------------------------- |
| |
| TGL_PCODE_MEM_SUBSYSTEM_INFO : constant := 16#d#; |
| TGL_PCODE_MEM_SS_READ_GLOBAL_INFO : constant := 0 * 2 ** 8; |
| TGL_PCODE_CDCLK_CONTROL : constant := 7; |
| TGL_CDCLK_PREPARE_FOR_CHANGE : constant := 3; |
| TGL_CDCLK_READY_FOR_CHANGE : constant := 1; |
| |
| ---------------------------------------------------------------------------- |
| |
| CDCLK_PLL_ENABLE_PLL_RATIO_MASK : constant := 16#ff#; |
| CDCLK_PLL_ENABLE_PLL_ENABLE : constant := 1 * 2 ** 31; |
| CDCLK_PLL_ENABLE_PLL_LOCK : constant := 1 * 2 ** 30; |
| CDCLK_CD2X_DIV_SEL_MASK : constant := 3 * 2 ** 22; |
| CDCLK_CD2X_DIV_SEL_1 : constant := 0 * 2 ** 22; |
| CDCLK_CD2X_DIV_SEL_2 : constant := 2 * 2 ** 22; |
| CDCLK_CD2X_PIPE_NONE : constant := 7 * 2 ** 19; |
| CDCLK_CTL_CD_FREQ_DECIMAL_MASK : constant := 16#7ff#; |
| |
| ---------------------------------------------------------------------------- |
| |
| type AUX_CTL_Array is array (AUX_USBC_Domain) of Registers.Registers_Index; |
| AUX_CTL_Regs : constant AUX_CTL_Array := |
| (AUX_USBC1 => Registers.DDI_AUX_CTL_USBC1, |
| AUX_USBC2 => Registers.DDI_AUX_CTL_USBC2, |
| AUX_USBC3 => Registers.DDI_AUX_CTL_USBC3, |
| AUX_USBC4 => Registers.DDI_AUX_CTL_USBC4, |
| AUX_USBC5 => Registers.DDI_AUX_CTL_USBC5, |
| AUX_USBC6 => Registers.DDI_AUX_CTL_USBC6); |
| |
| ---------------------------------------------------------------------------- |
| |
| function To_GPU_Port (PD : Port_Domain) return GPU_Port |
| is |
| (case PD is |
| when DDI_A | AUX_A => DIGI_A, |
| when DDI_B | AUX_B => DIGI_B, |
| when DDI_C | AUX_C => DIGI_C, |
| when DDI_USBC1 | AUX_USBC1 => DDI_TC1, |
| when DDI_USBC2 | AUX_USBC2 => DDI_TC2, |
| when DDI_USBC3 | AUX_USBC3 => DDI_TC3, |
| when DDI_USBC4 | AUX_USBC4 => DDI_TC4, |
| when DDI_USBC5 | AUX_USBC5 => DDI_TC5, |
| when DDI_USBC6 | AUX_USBC6 => DDI_TC6); |
| |
| procedure Pre_PD_On (PD : in Power_Domain; Success : out Boolean) |
| is |
| DP_AUX_CH_CTL_TBT_IO : constant := 1 * 2 ** 11; |
| begin |
| if PD in AUX_USBC_Domain then |
| -- Disable TBT IO mode for AUX |
| Registers.Unset_Mask |
| (Register => AUX_CTL_Regs (PD), |
| Mask => DP_AUX_CH_CTL_TBT_IO); |
| Connectors.TC.Claimed (To_GPU_Port (PD), Success); |
| elsif PD = PW1 then |
| Registers.Wait_Set_Mask |
| (Register => Registers.FUSE_STATUS, |
| Mask => FUSE_STATUS_PG0_DIST_STATUS, |
| Success => Success); |
| else |
| Success := True; |
| end if; |
| end Pre_PD_On; |
| |
| procedure Post_PD_On (PD : Power_Domain) |
| is |
| DKL_CMN_UC_DW_27_UC_HEALTH : constant := 1 * 2 ** 15; |
| begin |
| if PD in PW_Domain then |
| Registers.Wait_Set_Mask |
| (Register => Registers.FUSE_STATUS, |
| Mask => FUSE_STATUS_PGx_DIST_STATUS (PD), |
| TOut_MS => 1); |
| elsif PD in AUX_USBC_Domain then |
| Registers.Write (HIP_INDEX_REG (PD), HIP_INDEX_VAL (PD, 2)); |
| Registers.Wait_Set_Mask |
| (Register => DKL_CMN_UC_DW_27 (PD), |
| Mask => DKL_CMN_UC_DW_27_UC_HEALTH, |
| TOut_MS => 1); |
| end if; |
| end Post_PD_On; |
| |
| procedure Pre_PD_Off (PD : Power_Domain) is |
| begin |
| if PD in DDI_USBC_Domain then |
| -- Could be moved to a higher level, but right now it's |
| -- convenient to do it here: When requested to turn the |
| -- power off, we know exactly that we don't want to use |
| -- the port (anymore). |
| Connectors.TC.Disconnect (To_GPU_Port (PD)); |
| end if; |
| end Pre_PD_Off; |
| |
| procedure PD_On (PD : Power_Domain) |
| is |
| Ctl1, Ctl2 : Word32; |
| PD_Type : constant Power_Domain_Types := Power_Domain_Type (PD); |
| Success : Boolean; |
| begin |
| Registers.Read (PWR_CTL_BIOS (PD_Type), Ctl1); |
| Registers.Read (PWR_CTL_DRIVER (PD_Type), Ctl2); |
| |
| if ((Ctl1 or Ctl2) and Power_Request_Mask (PD)) = 0 then |
| Registers.Wait_Unset_Mask |
| (Register => PWR_CTL_DRIVER (PD_Type), |
| Mask => Power_State_Mask (PD), |
| TOut_MS => 1); |
| end if; |
| |
| if (Ctl2 and Power_Request_Mask (PD)) = 0 then |
| Pre_PD_On (PD, Success); |
| if not Success then |
| pragma Debug (Debug.Put_Line ("Connection flow failed!")); |
| return; |
| end if; |
| |
| Registers.Set_Mask (PWR_CTL_DRIVER (PD_Type), Power_Request_Mask (PD)); |
| |
| Registers.Wait_Set_Mask |
| (Register => PWR_CTL_DRIVER (PD_Type), |
| Mask => Power_State_Mask (PD), |
| TOut_MS => 1, |
| Success => Success); |
| pragma Debug (not Success, Debug.Put_Line ("Failed to enable power domain!")); |
| |
| if Success then |
| Post_PD_On (PD); |
| end if; |
| end if; |
| end PD_On; |
| |
| procedure PD_Off (PD : Power_Domain) |
| is |
| Ctl1, Ctl2 : Word32; |
| PD_Type : constant Power_Domain_Types := Power_Domain_Type (PD); |
| begin |
| pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); |
| |
| Registers.Read (PWR_CTL_BIOS (PD_Type), Ctl1); |
| Registers.Read (PWR_CTL_DRIVER (PD_Type), Ctl2); |
| |
| if ((Ctl1 or Ctl2) and Power_Request_Mask (PD)) /= 0 then |
| Registers.Wait_Set_Mask |
| (Register => PWR_CTL_DRIVER (PD_Type), |
| Mask => Power_State_Mask (PD), |
| TOut_MS => 1); |
| |
| Pre_PD_Off (PD); |
| |
| Registers.Unset_Mask (PWR_CTL_DRIVER (PD_Type), Power_Request_Mask (PD)); |
| Registers.Unset_Mask (PWR_CTL_BIOS (PD_Type), Power_Request_Mask (PD)); |
| end if; |
| end PD_Off; |
| |
| function Need_PW (PW : Dynamic_Well; Configs : Pipe_Configs) return Boolean |
| is |
| function Any_TC_Port return Boolean is |
| (for some Pipe in Pipe_Index => |
| Configs (Pipe).Port /= Disabled and then |
| Config_Helpers.To_GPU_Port (Pipe, Configs (Pipe).Port) in USBC_Port); |
| |
| function Any_Pipe_From (First : Pipe_Index) return Boolean is |
| (for some Pipe in First .. Pipe_Index'Last => Configs (Pipe).Port /= Disabled); |
| |
| function VGA return Boolean is |
| (Configs (Primary).Framebuffer.Offset = VGA_PLANE_FRAMEBUFFER_OFFSET); |
| begin |
| case PW is |
| when PW2 | PW3 => |
| return Any_Pipe_From (Secondary) or Any_TC_Port or VGA; |
| when PW4 => |
| return Any_Pipe_From (Tertiary); |
| when PW5 => |
| return False; -- Fourth pipe not supported yet. |
| end case; |
| end Need_PW; |
| |
| function Need_PD (PD : Dynamic_Domain; Configs : Pipe_Configs) return Boolean |
| is |
| function Any_Port_Is (Port : GPU_Port) return Boolean is |
| (for some Pipe in Pipe_Index => |
| Configs (Pipe).Port /= Disabled and then |
| Config_Helpers.To_GPU_Port (Pipe, Configs (Pipe).Port) = Port); |
| begin |
| return |
| (case PD is |
| when Dynamic_Well'Range => Need_PW (PD, Configs), |
| when Port_Domain'Range => Any_Port_Is (To_GPU_Port (PD))); |
| end Need_PD; |
| |
| procedure Get_RefClk (Refclk : out Refclk_Range) |
| is |
| DSSM : Word32; |
| DSSM_REFERENCE_FREQUENCY_MASK : constant := 16#e000_0000#; |
| DSSM_REFERENCE_FREQUENCY_24MHZ : constant := 16#0000_0000#; |
| DSSM_REFERENCE_FREQUENCY_19_2MHZ : constant := 16#2000_0000#; |
| DSSM_REFERENCE_FREQUENCY_38_4MHZ : constant := 16#4000_0000#; |
| begin |
| Registers.Read (Registers.DSSM, DSSM); |
| Refclk := |
| (case DSSM and DSSM_REFERENCE_FREQUENCY_MASK is |
| when DSSM_REFERENCE_FREQUENCY_24MHZ => 24_000_000, |
| when DSSM_REFERENCE_FREQUENCY_19_2MHZ => 19_200_000, |
| when DSSM_REFERENCE_FREQUENCY_38_4MHZ => 38_400_000, |
| when others => 24_000_000); |
| end Get_Refclk; |
| |
| procedure Get_RawClk (Rawclk : out Frequency_Type) |
| is |
| Raw_Frequency_24_MHz : Boolean; |
| SFUSE_STRAP_RAW_FREQUENCY : constant := 1 * 2 ** 8; |
| begin |
| Rawclk := Config.Default_RawClk_Freq; |
| Registers.Is_Set_Mask |
| (Register => Registers.SFUSE_STRAP, |
| Mask => SFUSE_STRAP_RAW_FREQUENCY, |
| Result => Raw_Frequency_24_MHz); |
| |
| if not Raw_Frequency_24_MHz then |
| Rawclk := 19_200_000; |
| end if; |
| end Get_RawClk; |
| |
| procedure Get_Max_CDClk (CDClk : out CDClk_Range) |
| is |
| Refclk_Freq : Refclk_Range; |
| begin |
| Get_Refclk (Refclk_Freq); |
| CDClk := |
| (case Refclk_Freq is |
| when 24_000_000 => 648_000_000, |
| when others => 652_800_000); |
| end Get_Max_CDClk; |
| |
| procedure Normalize_CDClk |
| (CDClk : in Int64; |
| Normalized : out CDClk_Range) |
| with |
| Post => Normalized >= 172_800_000 |
| is |
| Refclk_Freq : Refclk_Range; |
| begin |
| Get_Refclk (Refclk_Freq); |
| Normalized := |
| (case Refclk_Freq is |
| when 19_200_000 | 38_400_000 => |
| (if CDClk <= 172_800_000 then 172_800_000 |
| elsif CDClk <= 192_000_000 then 192_000_000 |
| elsif CDClk <= 307_200_000 then 307_200_000 |
| elsif CDClk <= 326_400_000 then 326_400_000 |
| elsif CDClk <= 556_800_000 then 556_800_000 |
| else 652_800_000), |
| when others => |
| (if CDClk <= 180_000_000 then 180_000_000 |
| elsif CDClk <= 192_000_000 then 192_000_000 |
| elsif CDClk <= 312_000_000 then 312_000_000 |
| elsif CDClk <= 324_000_000 then 324_000_000 |
| elsif CDClk <= 552_000_000 then 552_000_000 |
| else 648_000_000)); |
| end Normalize_CDClk; |
| |
| procedure Get_Cur_CDClk (CDClk : out CDClk_Range) |
| with |
| Post => CDClk >= 172_800_000 |
| is |
| CDCLK_CTL : Word32; |
| begin |
| Registers.Read (Registers.CDCLK_CTL, CDCLK_CTL); |
| CDCLK_CTL := CDCLK_CTL and CDCLK_CTL_CD_FREQ_DECIMAL_MASK; |
| Normalize_CDClk (Int64 (CDCLK_CTL) * 500_000 + 1_000_000, CDClk); |
| end Get_Cur_CDClk; |
| |
| procedure Set_CDClk (CDClk_In : CDClk_Range) |
| is |
| subtype PLL_Ratio_Range is Word32 range 0 .. 68; |
| function Ratio_For_19_2_MHz (CDClk : CDClk_Range) return PLL_Ratio_Range is |
| begin |
| case CDClk is |
| when 172_800_000 => return 18; |
| when 192_000_000 => return 20; |
| when 307_200_000 => return 32; |
| when 326_400_000 | 652_800_000 => return 68; |
| when 556_800_000 => return 58; |
| when others => return 0; |
| end case; |
| end Ratio_For_19_2_MHz; |
| |
| function Ratio_For_24_MHz (CDCLk : CDClk_Range) return PLL_Ratio_Range is |
| begin |
| case CDClk is |
| when 180_800_000 => return 15; |
| when 192_000_000 => return 16; |
| when 312_000_000 => return 26; |
| when 324_000_000 | 648_000_000 => return 54; |
| when 552_000_000 => return 46; |
| when others => return 0; |
| end case; |
| end Ratio_For_24_MHz; |
| |
| function CDCLK_CTL_CD_FREQ_DECIMAL (Freq : CDClk_Range) return Word32 |
| with |
| Pre => Freq > 1_000_000 |
| is |
| begin |
| -- Weirdest representation: CDClk - 1MHz in 10.1 (10 + 1 fractional bit) |
| return Word32 (Div_Round_Closest (Pos64 (Freq) - 1_000_000, 500_000)); |
| end CDCLK_CTL_CD_FREQ_DECIMAL; |
| |
| Success : Boolean; |
| CD2X : Word32; |
| PLL_Ratio : PLL_Ratio_Range; |
| CDClk : CDClk_Range; |
| Refclk_Freq : Refclk_Range; |
| VCO : Pos64; |
| begin |
| pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); |
| |
| Normalize_CDClk (CDClk_Range'Min (CDClk_In, Config.Max_CDClk), CDClk); |
| Get_Refclk (Refclk_Freq); |
| PLL_Ratio := (case Refclk_Freq is |
| when 19_200_000 => Ratio_For_19_2_MHz (CDClk), |
| when 38_400_000 => Ratio_For_19_2_MHz (CDClk) / 2, |
| when 24_000_000 => Ratio_For_24_MHz (CDClk), |
| when others => 0); |
| |
| if PLL_Ratio = 0 then |
| pragma Debug (Debug.Put_Line |
| ("ERROR: Invalid Refclk frequency, bad hardware?")); |
| return; |
| end if; |
| |
| PCode.Mailbox_Request |
| (Mbox => TGL_PCODE_CDCLK_CONTROL, |
| Command => TGL_CDCLK_PREPARE_FOR_CHANGE, |
| Reply_Mask => TGL_CDCLK_READY_FOR_CHANGE, |
| Wait_Ready => True, |
| Success => Success); |
| |
| if not Success then |
| pragma Debug (Debug.Put_Line |
| ("ERROR: PCODE not ready for frequency change.")); |
| return; |
| end if; |
| |
| Registers.Unset_Mask |
| (Register => Registers.CDCLK_PLL_ENABLE, |
| Mask => CDCLK_PLL_ENABLE_PLL_ENABLE); |
| Registers.Wait_Unset_Mask |
| (Register => Registers.CDCLK_PLL_ENABLE, |
| Mask => CDCLK_PLL_ENABLE_PLL_LOCK); |
| |
| Registers.Write |
| (Register => Registers.CDCLK_PLL_ENABLE, |
| Value => PLL_Ratio); |
| Registers.Write |
| (Register => Registers.CDCLK_PLL_ENABLE, |
| Value => PLL_Ratio or CDCLK_PLL_ENABLE_PLL_ENABLE); |
| Registers.Wait_Set_Mask |
| (Register => Registers.CDCLK_PLL_ENABLE, |
| Mask => CDCLK_PLL_ENABLE_PLL_LOCK, |
| Success => Success); |
| |
| if not Success then |
| Debug.Put_Line ("CDClk PLL failed to lock!"); |
| return; |
| end if; |
| |
| VCO := (Refclk_Freq / 1_000) * Pos64 (PLL_Ratio); |
| CD2X := |
| (case (Div_Round_Closest (VCO, CDClk / 1_000)) is |
| when 2 => CDCLK_CD2X_DIV_SEL_1, |
| when 4 => CDCLK_CD2X_DIV_SEL_2, |
| when others => CDCLK_CD2X_DIV_SEL_1); |
| |
| Registers.Write |
| (Register => Registers.CDCLK_CTL, |
| Value => CDCLK_CTL_CD_FREQ_DECIMAL (CDClk) or |
| CDCLK_CD2X_PIPE_NONE or CD2X); |
| |
| PCode.Mailbox_Write |
| (MBox => TGL_PCODE_CDCLK_CONTROL, |
| Command => (if CDClk <= 312_000_000 then 0 |
| elsif CDClk <= 326_400_000 then 1 |
| elsif CDClk <= 556_800_000 then 2 |
| else 3)); |
| Config.CDClk := CDClk; |
| |
| pragma Debug (Debug.Put ("Set CDClk to ")); |
| pragma Debug (Debug.Put_Int64 (CDClk / 1_000_000)); |
| pragma Debug (Debug.Put (".")); |
| pragma Debug (Debug.Put_Int64 ((CDClk mod 1_000_000) / 100_000)); |
| pragma Debug (Debug.Put_Line ("MHz.")); |
| end Set_CDClk; |
| |
| procedure Configure_Bandwidth_Buddy |
| is |
| BW_BUDDY_DISABLE : constant := 1 * 2 ** 31; |
| BW_BUDDY_TLB_REQ_TIMER_MASK : constant := 16#3f_0000#; |
| |
| type DRAM_Module_Type is (DDR4, DDR5, LPDDR4, LPDDR5); |
| type Bw_Buddy_Info is record |
| DRAM_Channels : Natural; |
| DRAM_Type : DRAM_Module_Type; |
| BW_BUDDY_MASK : Word32; |
| end record; |
| type Bw_Buddy_Info_Array is array (1 .. 8) of Bw_Buddy_Info; |
| |
| Buddy_Info : constant Bw_Buddy_Info_Array := |
| ((1, DDR4, 16#0f#), |
| (1, DDR5, 16#0f#), |
| (2, LPDDR4, 16#1c#), |
| (2, LPDDR5, 16#1c#), |
| (2, DDR4, 16#1f#), |
| (2, DDR5, 16#1e#), |
| (4, LPDDR4, 16#38#), |
| (4, LPDDR5, 16#38#)); |
| |
| -- TODO: use for ADL-S, RKL A0/B0 |
| Buddy_Info_Wa_1409767108 : constant Bw_Buddy_Info_Array := |
| ((1, DDR4, 1), |
| (1, DDR5, 1), |
| (1, LPDDR4, 1), |
| (1, LPDDR5, 1), |
| (2, DDR4, 3), |
| (2, DDR5, 3), |
| (2, LPDDR4, 3), |
| (2, LPDDR5, 3)); |
| |
| Result : Word64; |
| Module_Type: DRAM_Module_Type; |
| Channels : Natural; |
| Success : Boolean; |
| begin |
| PCode.Mailbox_Read(MBox => TGL_PCODE_MEM_SUBSYSTEM_INFO or |
| TGL_PCODE_MEM_SS_READ_GLOBAL_INFO, |
| Wait_Ready => True, |
| Reply => Result, |
| Success => Success); |
| if not Success then |
| pragma Debug (Debug.Put_Line |
| ("ERROR: PCODE didn't return memory info.")); |
| return; |
| end if; |
| |
| case (Result and 16#f#) is |
| when 0 => Module_Type := DDR4; |
| when 1 => Module_Type := DDR5; |
| when 2 => Module_Type := LPDDR5; |
| when 3 => Module_Type := LPDDR4; |
| when others => |
| pragma Debug (Debug.Put_Line ("ERROR: Invalid DRAM Module Type.")); |
| return; |
| end case; |
| |
| Channels := Natural (Shift_Right (Result and 16#f0#, 4)); |
| for I in Buddy_Info'Range loop |
| if Buddy_Info (I).DRAM_Type = Module_Type and |
| Buddy_Info (I).DRAM_Channels = Channels |
| then |
| Registers.Set_Mask |
| (Register => Registers.BW_BUDDY1_PAGE_MASK, |
| Mask => Buddy_Info (I).BW_BUDDY_MASK); |
| Registers.Set_Mask |
| (Register => Registers.BW_BUDDY2_PAGE_MASK, |
| Mask => Buddy_Info (I).BW_BUDDY_MASK); |
| |
| -- Wa_22010178259:tgl,rkl |
| Registers.Unset_And_Set_Mask |
| (Register => Registers.BW_BUDDY1_CTL, |
| Mask_Unset => BW_BUDDY_TLB_REQ_TIMER_MASK, |
| Mask_Set => 8 * 2 ** 16); |
| Registers.Unset_And_Set_Mask |
| (Register => Registers.BW_BUDDY2_CTL, |
| Mask_Unset => BW_BUDDY_TLB_REQ_TIMER_MASK, |
| Mask_Set => 8 * 2 ** 16); |
| |
| return; |
| end if; |
| end loop; |
| |
| Registers.Write (Registers.BW_BUDDY1_CTL, BW_BUDDY_DISABLE); |
| Registers.Write (Registers.BW_BUDDY2_CTL, BW_BUDDY_DISABLE); |
| end Configure_Bandwidth_Buddy; |
| |
| ---------------------------------------------------------------------------- |
| |
| procedure Initialize |
| is |
| RawClk : Frequency_Type; |
| begin |
| pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); |
| |
| -- Wa_14011294188:ehl,jsl,tgl,rkl,adl-s,adl-p |
| Registers.Set_Mask |
| (Register => Registers.PCH_DSPCLK_GATE_D, |
| Mask => PCH_DPMGUNIT_CLOCK_GATE_DISABLE); |
| |
| Registers.Set_Mask |
| (Register => Registers.NDE_RSTWRN_OPT, |
| Mask => NDE_RSTWRN_OPT_RST_PCH_Handshake_En); |
| |
| PD_On (PW1); |
| |
| Get_Cur_CDClk (Config.CDClk); |
| Get_Max_CDClk (Config.Max_CDClk); |
| if Config.CDClk < Config.Default_CDClk_Freq then |
| Set_CDClk (Config.Default_CDClk_Freq); |
| end if; |
| |
| Get_RawClk (RawClk); |
| Config.Raw_Clock := RawClk; |
| |
| -- TGL: Set DBUF Tracker State Service to 8 |
| Registers.Unset_And_Set_Mask |
| (Register => DBUF_CTL (S1), |
| Mask_Unset => DBUF_CTL_TRACKER_STATE_SERVICE_MASK, |
| Mask_Set => 8 * 2 ** DBUF_CTL_TRACKER_STATE_SERVICE_SHIFT); |
| |
| -- Enable first DBUF slice (TODO: Is this ok to use for all pipes?) |
| Registers.Set_Mask (DBUF_CTL (S1), DBUF_CTL_DBUF_POWER_REQUEST); |
| Registers.Wait_Set_Mask (DBUF_CTL (S1), DBUF_CTL_DBUF_POWER_STATE); |
| |
| for I in MBUS_ABOX_CTL'Range loop |
| Registers.Unset_And_Set_Mask |
| (Register => MBUS_ABOX_CTL (I), |
| Mask_Unset => MBUS_ABOX_MASK, |
| Mask_Set => MBUS_ABOX_CREDITS); |
| end loop; |
| |
| Configure_Bandwidth_Buddy; |
| |
| -- Display WA #14011508470 tgl,dg1,rkl,adl-s,adl-p,dg2 |
| Registers.Set_Mask |
| (Register => Registers.GEN11_CHICKEN_DCPR_2, |
| Mask => DCPR_MASK_MAXLATENCY_MEMUP_CLR or DCPR_MASK_LPMODE or |
| DCPR_SEND_RESP_IMM or DCPR_CLEAR_MEMSTAT_DIS); |
| end Initialize; |
| |
| procedure Limit_Dotclocks |
| (Configs : in out Pipe_Configs; |
| CDClk_Switch : out Boolean) |
| is |
| CDClk : CDClk_Range; |
| begin |
| Config_Helpers.Limit_Dotclocks (Configs, Config.Max_CDClk); |
| Normalize_CDClk (Config_Helpers.Highest_Dotclock (Configs), CDClk); |
| CDClk_Switch := Config.CDClk /= CDClk; |
| end Limit_Dotclocks; |
| |
| procedure Update_CDClk (Configs : in out Pipe_Configs) |
| is |
| New_CDClk : constant Frequency_Type := |
| Config_Helpers.Highest_Dotclock (Configs); |
| begin |
| Set_CDClk (New_CDClk); |
| Config_Helpers.Limit_Dotclocks (Configs, Config.CDClk); |
| end Update_CDClk; |
| |
| procedure Enable_CDClk is |
| begin |
| if Config.CDClk < Config.Default_CDClk_Freq then |
| Set_CDClk (Config.Default_CDClk_Freq); |
| end if; |
| end Enable_CDClk; |
| |
| ---------------------------------------------------------------------------- |
| |
| procedure Power_Set_To (Configs : Pipe_Configs) is |
| begin |
| pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); |
| |
| for PD in reverse Dynamic_Domain loop |
| if not Need_PD (PD, Configs) then |
| PD_Off (PD); |
| end if; |
| end loop; |
| |
| for PD in Dynamic_Domain loop |
| if Need_PD (PD, Configs) then |
| PD_On (PD); |
| end if; |
| end loop; |
| end Power_Set_To; |
| |
| procedure Power_Up (Port : Active_Port_Type; Success : out Boolean) |
| is |
| GPU_Port : constant GMA.GPU_Port := |
| Config_Helpers.To_GPU_Port (Pipe_Index'First, Port); |
| |
| procedure On (Aux : AUX_Domain; DDI : DDI_Domain) is |
| begin |
| PD_On (PW1); |
| if GPU_Port in USBC_Port then |
| PD_On (PW2); |
| PD_On (PW3); |
| end if; |
| PD_On (DDI); |
| |
| if GPU_Port in USBC_Port then |
| Connectors.TC.Claim |
| (Port => GPU_Port, |
| DP_Alt => Port in Physical_USBC_Ports, |
| Success => Success); |
| if not Success then |
| return; |
| end if; |
| end if; |
| PD_On (Aux); |
| |
| Success := True; |
| end On; |
| begin |
| pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); |
| |
| case GPU_Port is |
| when GMA.DIGI_A => On (AUX_A, DDI_A); |
| when GMA.DIGI_B => On (AUX_B, DDI_B); |
| when GMA.DIGI_C => On (AUX_C, DDI_C); |
| when GMA.DDI_TC1 => On (AUX_USBC1, DDI_USBC1); |
| when GMA.DDI_TC2 => On (AUX_USBC2, DDI_USBC2); |
| when GMA.DDI_TC3 => On (AUX_USBC3, DDI_USBC3); |
| when GMA.DDI_TC4 => On (AUX_USBC4, DDI_USBC4); |
| when GMA.DDI_TC5 => On (AUX_USBC5, DDI_USBC5); |
| when GMA.DDI_TC6 => On (AUX_USBC6, DDI_USBC6); |
| when others => Success := True; |
| end case; |
| end Power_Up; |
| |
| procedure Power_Up (Old_Configs, New_Configs : Pipe_Configs) is |
| begin |
| pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); |
| |
| -- Power wells only, Aux/DDI domains are enabled later on explicit request. |
| for PW in Dynamic_Well loop |
| if not Need_PW (PW, Old_Configs) and Need_PW (PW, New_Configs) then |
| PD_On (PW); |
| end if; |
| end loop; |
| end Power_Up; |
| |
| procedure Power_Down (Old_Configs, Tmp_Configs, New_Configs : Pipe_Configs) |
| is |
| begin |
| pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); |
| |
| for PD in reverse Dynamic_Domain loop |
| if (Need_PD (PD, Old_Configs) or Need_PD (PD, Tmp_Configs)) and |
| not Need_PD (PD, New_Configs) |
| then |
| PD_Off (PD); |
| end if; |
| end loop; |
| end Power_Down; |
| |
| procedure Pre_All_Off is |
| begin |
| Transcoder.PSR_Off; |
| end Pre_All_Off; |
| |
| procedure Post_All_Off is |
| begin |
| pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); |
| |
| for S in reverse DBUF_CTL'Range loop |
| Registers.Unset_Mask |
| (DBUF_CTL (S), DBUF_CTL_DBUF_POWER_REQUEST); |
| Registers.Wait_Unset_Mask |
| (DBUF_CTL (S), DBUF_CTL_DBUF_POWER_STATE); |
| end loop; |
| |
| -- Disable CDClk PLL. FIXME: Not implemented yet. |
| Set_CDClk (CDClk_Range'First); |
| |
| for PD in reverse Power_Domain loop |
| PD_Off (PD); |
| end loop; |
| |
| Combo_Phy.All_Off; |
| end Post_All_Off; |
| |
| end HW.GFX.GMA.Power_And_Clocks; |