| -- |
| -- Copyright (C) 2014-2016, 2019 secunet Security Networks AG |
| -- |
| -- 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.Time; |
| with HW.Debug; |
| with HW.GFX.GMA.Config; |
| with HW.GFX.GMA.Registers; |
| with HW.GFX.GMA.PCode; |
| with HW.GFX.GMA.Transcoder; |
| |
| use type HW.Word64; |
| |
| package body HW.GFX.GMA.Power_And_Clocks is |
| |
| type Power_Domain is (MISC_IO, PW1, PW2, DDI_AE, DDI_B, DDI_C, DDI_D); |
| subtype Power_Well is Power_Domain range PW1 .. PW2; |
| subtype Dynamic_Domain is Power_Domain range PW2 .. DDI_D; |
| |
| NDE_RSTWRN_OPT_RST_PCH_Handshake_En : constant := 1 * 2 ** 4; |
| |
| FUSE_STATUS_DOWNLOAD_STATUS : constant := 1 * 2 ** 31; |
| FUSE_STATUS_PG0_DIST_STATUS : constant := 1 * 2 ** 27; |
| |
| DFSM_DISPLAY_CDCLK_LIMIT_675MHZ : constant := 0 * 2 ** 23; |
| DFSM_DISPLAY_CDCLK_LIMIT_540MHZ : constant := 1 * 2 ** 23; |
| DFSM_DISPLAY_CDCLK_LIMIT_450MHZ : constant := 2 * 2 ** 23; |
| DFSM_DISPLAY_CDCLK_LIMIT_337_5MHZ : constant := 3 * 2 ** 23; |
| DFSM_DISPLAY_CDCLK_LIMIT_MASK : constant := 3 * 2 ** 23; |
| |
| SFUSE_STRAP_RAW_FREQUENCY : constant := 1 * 2 ** 8; |
| |
| type Power_Domain_Values is array (Power_Domain) of Word32; |
| PWR_WELL_CTL_POWER_REQUEST : constant Power_Domain_Values := |
| (MISC_IO => 1 * 2 ** 1, |
| DDI_AE => 1 * 2 ** 3, |
| DDI_B => 1 * 2 ** 5, |
| DDI_C => 1 * 2 ** 7, |
| DDI_D => 1 * 2 ** 9, |
| PW1 => 1 * 2 ** 29, |
| PW2 => 1 * 2 ** 31); |
| PWR_WELL_CTL_POWER_STATE : constant Power_Domain_Values := |
| (MISC_IO => 1 * 2 ** 0, |
| DDI_AE => 1 * 2 ** 2, |
| DDI_B => 1 * 2 ** 4, |
| DDI_C => 1 * 2 ** 6, |
| DDI_D => 1 * 2 ** 8, |
| PW1 => 1 * 2 ** 28, |
| PW2 => 1 * 2 ** 30); |
| |
| type Power_Well_Values is array (Power_Well) of Word32; |
| FUSE_STATUS_PGx_DIST_STATUS : constant Power_Well_Values := |
| (PW1 => 1 * 2 ** 26, |
| PW2 => 1 * 2 ** 25); |
| |
| DBUF_CTL_DBUF_POWER_REQUEST : constant := 1 * 2 ** 31; |
| DBUF_CTL_DBUF_POWER_STATE : constant := 1 * 2 ** 30; |
| |
| ---------------------------------------------------------------------------- |
| |
| DPLL_CTRL1_DPLL0_LINK_RATE_MASK : constant := 7 * 2 ** 1; |
| DPLL_CTRL1_DPLL0_LINK_RATE_2700MHZ : constant := 0 * 2 ** 1; |
| DPLL_CTRL1_DPLL0_LINK_RATE_1350MHZ : constant := 1 * 2 ** 1; |
| DPLL_CTRL1_DPLL0_LINK_RATE_810MHZ : constant := 2 * 2 ** 1; |
| DPLL_CTRL1_DPLL0_LINK_RATE_1620MHZ : constant := 3 * 2 ** 1; |
| DPLL_CTRL1_DPLL0_LINK_RATE_1080MHZ : constant := 4 * 2 ** 1; |
| DPLL_CTRL1_DPLL0_LINK_RATE_2160MHZ : constant := 5 * 2 ** 1; |
| DPLL_CTRL1_DPLL0_OVERRIDE : constant := 1 * 2 ** 0; |
| |
| LCPLL1_CTL_PLL_ENABLE : constant := 1 * 2 ** 31; |
| LCPLL1_CTL_PLL_LOCK : constant := 1 * 2 ** 30; |
| |
| DISP_FBC_MEMORY_WAKE : constant := 1 * 2 ** 31; |
| |
| ---------------------------------------------------------------------------- |
| |
| CDCLK_CTL_CD_FREQ_SELECT_MASK : constant := 3 * 2 ** 26; |
| CDCLK_CTL_CD_FREQ_SELECT_450MHZ : constant := 0 * 2 ** 26; |
| CDCLK_CTL_CD_FREQ_SELECT_540MHZ : constant := 1 * 2 ** 26; |
| CDCLK_CTL_CD_FREQ_SELECT_337_5MHZ : constant := 2 * 2 ** 26; |
| CDCLK_CTL_CD_FREQ_SELECT_675MHZ : constant := 3 * 2 ** 26; |
| CDCLK_CTL_CD_FREQ_DECIMAL_MASK : constant := 16#7ff#; |
| |
| SKL_PCODE_CDCLK_CONTROL : constant := 7; |
| SKL_CDCLK_PREPARE_FOR_CHANGE : constant := 3; |
| SKL_CDCLK_READY_FOR_CHANGE : constant := 1; |
| |
| function CDCLK_CTL_CD_FREQ_DECIMAL (CDClk : Frequency_Type) return Word32 is |
| begin |
| -- Weirdest representation: CDClk - 1MHz in 10.1 (10 + 1 fractional bit) |
| return Word32 ((CDClk - 1_000_000) / 500_000); |
| end CDCLK_CTL_CD_FREQ_DECIMAL; |
| |
| ---------------------------------------------------------------------------- |
| |
| procedure PD_Off (PD : Power_Domain) |
| is |
| Ctl1, Ctl2, Ctl3, Ctl4 : Word32; |
| begin |
| pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); |
| |
| Registers.Read (Registers.PWR_WELL_CTL_BIOS, Ctl1); |
| Registers.Read (Registers.PWR_WELL_CTL_DRIVER, Ctl2); |
| Registers.Read (Registers.PWR_WELL_CTL_KVMR, Ctl3); |
| Registers.Read (Registers.PWR_WELL_CTL_DEBUG, Ctl4); |
| pragma Debug (Registers.Posting_Read (Registers.PWR_WELL_CTL5)); -- Result for debugging only |
| pragma Debug (Registers.Posting_Read (Registers.PWR_WELL_CTL6)); -- Result for debugging only |
| |
| if ((Ctl1 or Ctl2 or Ctl3 or Ctl4) and |
| PWR_WELL_CTL_POWER_REQUEST (PD)) /= 0 |
| then |
| Registers.Wait_Set_Mask |
| (Register => Registers.PWR_WELL_CTL_DRIVER, |
| Mask => PWR_WELL_CTL_POWER_STATE (PD)); |
| end if; |
| |
| if (Ctl1 and PWR_WELL_CTL_POWER_REQUEST (PD)) /= 0 then |
| Registers.Unset_Mask |
| (Register => Registers.PWR_WELL_CTL_BIOS, |
| Mask => PWR_WELL_CTL_POWER_REQUEST (PD)); |
| end if; |
| |
| if (Ctl2 and PWR_WELL_CTL_POWER_REQUEST (PD)) /= 0 then |
| Registers.Unset_Mask |
| (Register => Registers.PWR_WELL_CTL_DRIVER, |
| Mask => PWR_WELL_CTL_POWER_REQUEST (PD)); |
| end if; |
| end PD_Off; |
| |
| procedure PD_On (PD : Power_Domain) |
| with |
| Pre => True |
| is |
| Ctl1, Ctl2, Ctl3, Ctl4 : Word32; |
| begin |
| pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); |
| |
| Registers.Read (Registers.PWR_WELL_CTL_BIOS, Ctl1); |
| Registers.Read (Registers.PWR_WELL_CTL_DRIVER, Ctl2); |
| Registers.Read (Registers.PWR_WELL_CTL_KVMR, Ctl3); |
| Registers.Read (Registers.PWR_WELL_CTL_DEBUG, Ctl4); |
| pragma Debug (Registers.Posting_Read (Registers.PWR_WELL_CTL5)); -- Result for debugging only |
| pragma Debug (Registers.Posting_Read (Registers.PWR_WELL_CTL6)); -- Result for debugging only |
| |
| if ((Ctl1 or Ctl2 or Ctl3 or Ctl4) and |
| PWR_WELL_CTL_POWER_REQUEST (PD)) = 0 |
| then |
| Registers.Wait_Unset_Mask |
| (Register => Registers.PWR_WELL_CTL_DRIVER, |
| Mask => PWR_WELL_CTL_POWER_STATE (PD)); |
| end if; |
| |
| if (Ctl2 and PWR_WELL_CTL_POWER_REQUEST (PD)) = 0 then |
| Registers.Set_Mask |
| (Register => Registers.PWR_WELL_CTL_DRIVER, |
| Mask => PWR_WELL_CTL_POWER_REQUEST (PD)); |
| Registers.Wait_Set_Mask |
| (Register => Registers.PWR_WELL_CTL_DRIVER, |
| Mask => PWR_WELL_CTL_POWER_STATE (PD)); |
| |
| if PD in Power_Well then |
| Registers.Wait_Set_Mask |
| (Register => Registers.FUSE_STATUS, |
| Mask => FUSE_STATUS_PGx_DIST_STATUS (PD)); |
| end if; |
| end if; |
| end PD_On; |
| |
| function Need_PD (PD : Dynamic_Domain; Configs : Pipe_Configs) return Boolean |
| is |
| begin |
| return (case PD is |
| when DDI_AE => Configs (Primary).Port = eDP or |
| Configs (Secondary).Port = eDP or |
| Configs (Tertiary).Port = eDP, |
| when DDI_B => Configs (Primary).Port = HDMI1 or |
| Configs (Primary).Port = DP1 or |
| Configs (Secondary).Port = HDMI1 or |
| Configs (Secondary).Port = DP1 or |
| Configs (Tertiary).Port = HDMI1 or |
| Configs (Tertiary).Port = DP1, |
| when DDI_C => Configs (Primary).Port = HDMI2 or |
| Configs (Primary).Port = DP2 or |
| Configs (Secondary).Port = HDMI2 or |
| Configs (Secondary).Port = DP2 or |
| Configs (Tertiary).Port = HDMI2 or |
| Configs (Tertiary).Port = DP2, |
| when DDI_D => Configs (Primary).Port = HDMI3 or |
| Configs (Primary).Port = DP3 or |
| Configs (Secondary).Port = HDMI3 or |
| Configs (Secondary).Port = DP3 or |
| Configs (Tertiary).Port = HDMI3 or |
| Configs (Tertiary).Port = DP3, |
| when PW2 => (Configs (Primary).Port /= Disabled and |
| Configs (Primary).Port /= eDP) or |
| Configs (Secondary).Port /= Disabled or |
| Configs (Tertiary).Port /= Disabled); |
| end Need_PD; |
| |
| ---------------------------------------------------------------------------- |
| |
| procedure Pre_All_Off is |
| begin |
| Transcoder.PSR_Off; |
| end Pre_All_Off; |
| |
| procedure Post_All_Off is |
| begin |
| for PD in reverse Dynamic_Domain loop |
| PD_Off (PD); |
| end loop; |
| |
| Registers.Unset_Mask |
| (Register => Registers.DBUF_CTL_S0, |
| Mask => DBUF_CTL_DBUF_POWER_REQUEST); |
| Registers.Wait_Unset_Mask |
| (Register => Registers.DBUF_CTL_S0, |
| Mask => DBUF_CTL_DBUF_POWER_STATE); |
| |
| Registers.Unset_Mask |
| (Register => Registers.LCPLL1_CTL, |
| Mask => LCPLL1_CTL_PLL_ENABLE); |
| Registers.Wait_Unset_Mask |
| (Register => Registers.LCPLL1_CTL, |
| Mask => LCPLL1_CTL_PLL_LOCK); |
| |
| PD_Off (MISC_IO); |
| PD_Off (PW1); |
| end Post_All_Off; |
| |
| function Normalize_CDClk (CDClk : in Int64) return Config.CDClk_Range is |
| ( if CDClk <= 337_500_000 then 337_500_000 |
| elsif CDClk <= 450_000_000 then 450_000_000 |
| elsif CDClk <= 540_000_000 then 540_000_000 |
| else 675_000_000); |
| |
| procedure Get_Cur_CDClk (CDClk : out Config.CDClk_Range) |
| is |
| CDCLK_CTL : Word32; |
| begin |
| Registers.Read (Registers.CDCLK_CTL, CDCLK_CTL); |
| CDCLK_CTL := CDCLK_CTL and CDCLK_CTL_CD_FREQ_DECIMAL_MASK; |
| CDClk := Normalize_CDClk (Int64 (CDCLK_CTL) * 500_000 + 1_000_000); |
| end Get_Cur_CDClk; |
| |
| procedure Get_Max_CDClk (CDClk : out Config.CDClk_Range) |
| is |
| DFSM : Word32; |
| begin |
| Registers.Read (Registers.DFSM, DFSM); |
| CDClk := |
| (case DFSM and DFSM_DISPLAY_CDCLK_LIMIT_MASK is |
| when DFSM_DISPLAY_CDCLK_LIMIT_675MHZ => 675_000_000, |
| when DFSM_DISPLAY_CDCLK_LIMIT_540MHZ => 540_000_000, |
| when DFSM_DISPLAY_CDCLK_LIMIT_450MHZ => 450_000_000, |
| when others => 337_500_000); |
| end Get_Max_CDClk; |
| |
| procedure Set_CDClk (CDClk_In : Frequency_Type) |
| is |
| CDClk : constant Config.CDClk_Range := |
| Normalize_CDClk (Frequency_Type'Min (CDClk_In, Config.Max_CDClk)); |
| Success : Boolean; |
| begin |
| PCode.Mailbox_Request |
| (MBox => SKL_PCODE_CDCLK_CONTROL, |
| Command => SKL_CDCLK_PREPARE_FOR_CHANGE, |
| Reply_Mask => SKL_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.Write |
| (Register => Registers.CDCLK_CTL, |
| Value => (case CDClk is |
| when 675_000_000 => CDCLK_CTL_CD_FREQ_SELECT_675MHZ, |
| when 540_000_000 => CDCLK_CTL_CD_FREQ_SELECT_540MHZ, |
| when 450_000_000 => CDCLK_CTL_CD_FREQ_SELECT_450MHZ, |
| when others => CDCLK_CTL_CD_FREQ_SELECT_337_5MHZ) |
| or CDCLK_CTL_CD_FREQ_DECIMAL (CDClk)); |
| |
| PCode.Mailbox_Write |
| (MBox => SKL_PCODE_CDCLK_CONTROL, |
| Command => (case CDClk is |
| when 675_000_000 => 3, |
| when 540_000_000 => 2, |
| when 450_000_000 => 1, |
| when others => 0)); |
| Registers.Set_Mask |
| (Register => Registers.DBUF_CTL_S0, |
| Mask => DBUF_CTL_DBUF_POWER_REQUEST); |
| Registers.Wait_Set_Mask |
| (Register => Registers.DBUF_CTL_S0, |
| Mask => DBUF_CTL_DBUF_POWER_STATE); |
| |
| Config.CDClk := CDClk; |
| end Set_CDClk; |
| |
| procedure Get_Raw_Clock (Raw_Clock : out Frequency_Type) |
| is |
| Freq_24MHz : Boolean; |
| begin |
| Raw_Clock := Config.Default_RawClk_Freq; |
| if Config.Has_Fractional_RawClk then |
| Registers.Is_Set_Mask |
| (Register => Registers.SFUSE_STRAP, |
| Mask => SFUSE_STRAP_RAW_FREQUENCY, |
| Result => Freq_24MHz); |
| if not Freq_24MHz then |
| Raw_Clock := 19_200_000; |
| end if; |
| end if; |
| end Get_Raw_Clock; |
| |
| procedure Initialize is |
| begin |
| Registers.Set_Mask |
| (Register => Registers.NDE_RSTWRN_OPT, |
| Mask => NDE_RSTWRN_OPT_RST_PCH_Handshake_En); |
| |
| Registers.Wait_Set_Mask |
| (Register => Registers.FUSE_STATUS, |
| Mask => FUSE_STATUS_PG0_DIST_STATUS); |
| PD_On (PW1); |
| PD_On (MISC_IO); |
| |
| -- TODO: Set to preferred eDP rate: |
| -- Registers.Unset_And_Set_Mask |
| -- (Register => Registers.DPLL_CTRL1, |
| -- Unset_Mask => DPLL_CTRL1_DPLL0_LINK_RATE_MASK, |
| -- Set_Mask => DPLL_CTRL1_DPLL0_LINK_RATE_...); |
| Registers.Set_Mask |
| (Register => Registers.LCPLL1_CTL, |
| Mask => LCPLL1_CTL_PLL_ENABLE); |
| Registers.Wait_Set_Mask |
| (Register => Registers.LCPLL1_CTL, |
| Mask => LCPLL1_CTL_PLL_LOCK); |
| |
| -- WaEnableChickenDCPR:skl,bxt,kbl,glk,cfl |
| Registers.Set_Mask |
| (Register => Registers.GEN8_CHICKEN_DCPR_1, |
| Mask => 1 * 2 ** 13); |
| |
| -- Display WA #0859 WaFbcWakeMemOn:skl,bxt,kbl,glk,cfl |
| Registers.Set_Mask |
| (Register => Registers.ARB_CTL, |
| Mask => DISP_FBC_MEMORY_WAKE); |
| |
| Get_Cur_CDClk (Config.CDClk); |
| Get_Max_CDClk (Config.Max_CDClk); |
| Set_CDClk (Config.Default_CDClk_Freq); |
| |
| declare -- avoid aliasing |
| Raw_Clock : Frequency_Type; |
| begin |
| Get_Raw_Clock (Raw_Clock); |
| Config.Raw_Clock := Raw_Clock; |
| end; |
| end Initialize; |
| |
| procedure Limit_Dotclocks |
| (Configs : in out Pipe_Configs; |
| CDClk_Switch : out Boolean) |
| is |
| begin |
| Config_Helpers.Limit_Dotclocks (Configs, Config.Max_CDClk); |
| CDClk_Switch := |
| Config.CDClk /= Normalize_CDClk |
| (Config_Helpers.Highest_Dotclock (Configs)); |
| 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 Power_Set_To (Configs : Pipe_Configs) is |
| begin |
| 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 (Old_Configs, New_Configs : Pipe_Configs) is |
| begin |
| for PD in Dynamic_Domain loop |
| if not Need_PD (PD, Old_Configs) and Need_PD (PD, New_Configs) then |
| PD_On (PD); |
| end if; |
| end loop; |
| end Power_Up; |
| |
| procedure Power_Down (Old_Configs, Tmp_Configs, New_Configs : Pipe_Configs) |
| is |
| begin |
| 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; |
| |
| end HW.GFX.GMA.Power_And_Clocks; |