| -- |
| -- Copyright (C) 2016 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 HW.GFX.GMA.Registers; |
| |
| package body HW.GFX.GMA.PLLs.DPLL is |
| |
| -- NOTE: Order of DPLLs is twisted => always use named associations! |
| |
| type Regs is array (Configurable_DPLLs) of Registers.Registers_Index; |
| |
| DPLL_CTL : constant Regs := Regs' |
| (DPLL1 => Registers.LCPLL2_CTL, |
| DPLL2 => Registers.WRPLL_CTL_1, |
| DPLL3 => Registers.WRPLL_CTL_2); |
| DPLL_CTL_PLL_ENABLE : constant := 1 * 2 ** 31; |
| |
| ---------------------------------------------------------------------------- |
| |
| DPLL_CFGR1 : constant Regs := Regs' |
| (DPLL1 => Registers.DPLL1_CFGR1, |
| DPLL2 => Registers.DPLL2_CFGR1, |
| DPLL3 => Registers.DPLL3_CFGR1); |
| DPLL_CFGR1_FREQUENCY_ENABLE : constant := 1 * 2 ** 31; |
| DPLL_CFGR1_DCO_FRACTION_SHIFT : constant := 9; |
| DPLL_CFGR1_DCO_FRACTION_MASK : constant := 16#7fff# * 2 ** 9; |
| DPLL_CFGR1_DCO_INTEGER_MASK : constant := 16#01ff# * 2 ** 0; |
| |
| DPLL_CFGR2 : constant Regs := Regs' |
| (DPLL1 => Registers.DPLL1_CFGR2, |
| DPLL2 => Registers.DPLL2_CFGR2, |
| DPLL3 => Registers.DPLL3_CFGR2); |
| DPLL_CFGR2_QDIV_RATIO_SHIFT : constant := 8; |
| DPLL_CFGR2_QDIV_RATIO_MASK : constant := 255 * 2 ** 8; |
| DPLL_CFGR2_QDIV_MODE : constant := 1 * 2 ** 7; |
| DPLL_CFGR2_KDIV_SHIFT : constant := 5; |
| DPLL_CFGR2_KDIV_MASK : constant := 3 * 2 ** 5; |
| DPLL_CFGR2_PDIV_SHIFT : constant := 2; |
| DPLL_CFGR2_PDIV_MASK : constant := 7 * 2 ** 2; |
| DPLL_CFGR2_CENTRAL_FREQ_MASK : constant := 3 * 2 ** 0; |
| DPLL_CFGR2_CENTRAL_FREQ_9600MHZ : constant := 0 * 2 ** 0; |
| DPLL_CFGR2_CENTRAL_FREQ_9000MHZ : constant := 1 * 2 ** 0; |
| DPLL_CFGR2_CENTRAL_FREQ_8400MHZ : constant := 3 * 2 ** 0; |
| |
| ---------------------------------------------------------------------------- |
| |
| HDMI_MODE : constant := 1 * 2 ** 5; |
| SSC : constant := 1 * 2 ** 4; |
| LINK_RATE_MASK : constant := 7 * 2 ** 1; |
| LINK_RATE_2700MHZ : constant := 0 * 2 ** 1; |
| LINK_RATE_1350MHZ : constant := 1 * 2 ** 1; |
| LINK_RATE_810MHZ : constant := 2 * 2 ** 1; |
| LINK_RATE_1620MHZ : constant := 3 * 2 ** 1; |
| LINK_RATE_1080MHZ : constant := 4 * 2 ** 1; |
| LINK_RATE_2160MHZ : constant := 5 * 2 ** 1; |
| OVERRIDE : constant := 1 * 2 ** 0; |
| |
| LOCK : constant := 1 * 2 ** 0; |
| |
| type Shifts is array (Configurable_DPLLs) of Natural; |
| DPLL_CTRL1_SHIFT : constant Shifts := |
| (DPLL1 => 6, DPLL2 => 12, DPLL3 => 18); |
| DPLL_STATUS_SHIFT : constant Shifts := |
| (DPLL1 => 8, DPLL2 => 16, DPLL3 => 24); |
| |
| function LINK_RATE (Link_Rate : DP_Bandwidth) return Word32 is |
| begin |
| return (case Link_Rate is |
| when DP_Bandwidth_5_4 => LINK_RATE_2700MHZ, |
| when DP_Bandwidth_2_7 => LINK_RATE_1350MHZ, |
| when DP_Bandwidth_1_62 => LINK_RATE_810MHZ); |
| end LINK_RATE; |
| |
| function DPLL_CTRL1_DPLLx |
| (Value : Word32; |
| PLL : Configurable_DPLLs) |
| return Word32 is |
| begin |
| return Shift_Left (Value, DPLL_CTRL1_SHIFT (PLL)); |
| end DPLL_CTRL1_DPLLx; |
| |
| function DPLL_STATUS_DPLLx_LOCK (PLL : Configurable_DPLLs) return Word32 is |
| begin |
| return Shift_Left (LOCK, DPLL_STATUS_SHIFT (PLL)); |
| end DPLL_STATUS_DPLLx_LOCK; |
| |
| ---------------------------------------------------------------------------- |
| |
| subtype PDiv_Range is Pos64 range 1 .. 7; |
| subtype QDiv_Range is Pos64 range 1 .. 255; |
| subtype KDiv_Range is Pos64 range 1 .. 5; |
| |
| type Central_Frequency is (CF_INVALID, CF_9600MHZ, CF_9000MHZ, CF_8400MHZ); |
| subtype Valid_Central_Freq is |
| Central_Frequency range CF_9600MHZ .. CF_8400MHZ; |
| |
| type CF_Pos is array (Valid_Central_Freq) of Pos64; |
| CF_Pos64 : constant CF_Pos := CF_Pos' |
| (CF_9600MHZ => 9_600_000_000, |
| CF_9000MHZ => 9_000_000_000, |
| CF_8400MHZ => 8_400_000_000); |
| |
| subtype DCO_Frequency is |
| Pos64 range 1 .. CF_Pos64 (CF_9600MHZ) + CF_Pos64 (CF_9600MHZ) / 100; |
| |
| function DPLL_CFGR1_DCO_FRACTION (DCO_Freq : DCO_Frequency) return Word32 |
| with |
| Pre => True |
| is |
| begin |
| return Shift_Left |
| (Word32 ((DCO_Freq * 2 ** 15) / 24_000_000) and 16#7fff#, |
| DPLL_CFGR1_DCO_FRACTION_SHIFT); |
| end DPLL_CFGR1_DCO_FRACTION; |
| |
| function DPLL_CFGR1_DCO_INTEGER (DCO_Freq : DCO_Frequency) return Word32 |
| with |
| Pre => True |
| is |
| begin |
| return Word32 (DCO_Freq / 24_000_000); |
| end DPLL_CFGR1_DCO_INTEGER; |
| |
| function DPLL_CFGR2_PDIV (PDiv : PDiv_Range) return Word32 is |
| begin |
| return Shift_Left |
| ((case PDiv is |
| when 1 => 0, |
| when 2 => 1, |
| when 3 => 2, |
| when 7 => 4, |
| when others => 4), |
| DPLL_CFGR2_PDIV_SHIFT); |
| end DPLL_CFGR2_PDIV; |
| |
| function DPLL_CFGR2_QDIV (QDiv : QDiv_Range) return Word32 is |
| begin |
| return Shift_Left (Word32 (QDiv), DPLL_CFGR2_QDIV_RATIO_SHIFT) or |
| (if QDiv /= 1 then DPLL_CFGR2_QDIV_MODE else 0); |
| end DPLL_CFGR2_QDIV; |
| |
| function DPLL_CFGR2_KDIV (KDiv : KDiv_Range) return Word32 is |
| begin |
| return Shift_Left |
| ((case KDiv is |
| when 5 => 0, |
| when 2 => 1, |
| when 3 => 2, |
| when 1 => 3, |
| when others => 0), |
| DPLL_CFGR2_KDIV_SHIFT); |
| end DPLL_CFGR2_KDIV; |
| |
| function DPLL_CFGR2_CENTRAL_FREQ (CF : Valid_Central_Freq) return Word32 is |
| begin |
| return (case CF is |
| when CF_9600MHZ => DPLL_CFGR2_CENTRAL_FREQ_9600MHZ, |
| when CF_9000MHZ => DPLL_CFGR2_CENTRAL_FREQ_9000MHZ, |
| when CF_8400MHZ => DPLL_CFGR2_CENTRAL_FREQ_8400MHZ); |
| end DPLL_CFGR2_CENTRAL_FREQ; |
| |
| ---------------------------------------------------------------------------- |
| |
| procedure Calculate_DPLL |
| (Dotclock : in Frequency_Type; |
| Central_Freq : out Central_Frequency; |
| DCO_Freq : out DCO_Frequency; |
| PDiv : out PDiv_Range; |
| QDiv : out QDiv_Range; |
| KDiv : out KDiv_Range) |
| with |
| Pre => True |
| is |
| Max_Pos_Deviation : constant := 1; |
| Max_Neg_Deviation : constant := 6; |
| |
| subtype Div_Range is Pos64 range 1 .. 98; |
| subtype Candidate_Index is Positive range 1 .. 36; |
| type Candidate_Array is array (Candidate_Index) of Div_Range; |
| type Candidate_List is record |
| Divs : Candidate_Array; |
| Count : Candidate_Index; |
| end record; |
| type Parity_Type is (Even, Odd); |
| type Candidates_Type is array (Parity_Type) of Candidate_List; |
| |
| Candidates : constant Candidates_Type := Candidates_Type' |
| (Even => Candidate_List' |
| (Divs => Candidate_Array' |
| (4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30, 32, 36, 40, 42, 44, |
| 48, 52, 54, 56, 60, 64, 66, 68, 70, 72, 76, 78, 80, 84, 88, 90, |
| 92, 96, 98), |
| Count => 36), |
| Odd => Candidate_List' |
| (Divs => Candidate_Array'(3, 5, 7, 9, 15, 21, 35, others => 1), |
| Count => 7)); |
| |
| Temp_Freq, |
| Allowed_Deviation : Pos64; |
| Deviation : Int64; |
| Temp_Central : DCO_Frequency; |
| Min_Deviation : Int64 := Int64'Last; |
| Div : Div_Range := Div_Range'Last; |
| begin |
| Central_Freq := CF_INVALID; |
| DCO_Freq := 1; |
| PDiv := 1; |
| QDiv := 1; |
| KDiv := 1; |
| |
| for Parity in Parity_Type loop |
| for CF in Valid_Central_Freq loop |
| Temp_Central := CF_Pos64 (CF); |
| for I in Candidate_Index range 1 .. Candidates (Parity).Count loop |
| Temp_Freq := Candidates (Parity).Divs (I) * 5 * Dotclock; |
| if Temp_Freq > Temp_Central then |
| Deviation := Temp_Freq - Temp_Central; |
| Allowed_Deviation := (Max_Pos_Deviation * Temp_Central) / 100; |
| else |
| Deviation := Temp_Central - Temp_Freq; |
| Allowed_Deviation := (Max_Neg_Deviation * Temp_Central) / 100; |
| end if; |
| if Deviation < Min_Deviation and |
| Deviation < Allowed_Deviation |
| then |
| Min_Deviation := Deviation; |
| Central_Freq := CF; |
| DCO_Freq := Temp_Freq; |
| Div := Candidates (Parity).Divs (I); |
| end if; |
| end loop; |
| end loop; |
| exit when Central_Freq /= CF_INVALID; |
| end loop; |
| |
| if Central_Freq /= CF_INVALID then |
| if Div mod 2 = 0 then |
| pragma Assert (Div /= 1); |
| pragma Assert (Div > 1); |
| Div := Div / 2; |
| if Div = 1 or Div = 3 or Div = 5 then |
| -- 2, 6 and 10 |
| PDiv := 2; |
| QDiv := 1; |
| KDiv := Div; |
| elsif Div mod 2 = 0 then |
| -- divisible by 4 |
| PDiv := 2; |
| QDiv := Div / 2; |
| KDiv := 2; |
| elsif Div mod 3 = 0 then |
| -- divisible by 6 |
| PDiv := 3; |
| QDiv := Div / 3; |
| KDiv := 2; |
| elsif Div mod 7 = 0 then |
| -- divisible by 14 |
| PDiv := 7; |
| QDiv := Div / 7; |
| KDiv := 2; |
| end if; |
| elsif Div = 7 or Div = 21 or Div = 35 then |
| -- 7, 21 and 35 |
| PDiv := 7; |
| QDiv := 1; |
| KDiv := Div / 7; |
| elsif Div = 3 or Div = 9 or Div = 15 then |
| -- 3, 9 and 15 |
| PDiv := 3; |
| QDiv := 1; |
| KDiv := Div / 3; |
| elsif Div = 5 then |
| -- 5 |
| PDiv := 5; |
| QDiv := 1; |
| KDiv := 1; |
| end if; |
| end if; |
| end Calculate_DPLL; |
| |
| ---------------------------------------------------------------------------- |
| |
| procedure On |
| (PLL : in Configurable_DPLLs; |
| Port_Cfg : in Port_Config; |
| Success : out Boolean) |
| is |
| Central_Freq : Central_Frequency; |
| DCO_Freq : DCO_Frequency; |
| PDiv : PDiv_Range; |
| QDiv : QDiv_Range; |
| KDiv : KDiv_Range; |
| begin |
| if Port_Cfg.Display = DP then |
| Registers.Unset_And_Set_Mask |
| (Register => Registers.DPLL_CTRL1, |
| Mask_Unset => DPLL_CTRL1_DPLLx (HDMI_MODE, PLL) or |
| DPLL_CTRL1_DPLLx (SSC, PLL) or |
| DPLL_CTRL1_DPLLx (LINK_RATE_MASK, PLL), |
| Mask_Set => DPLL_CTRL1_DPLLx (LINK_RATE |
| (Port_Cfg.DP.Bandwidth), PLL) or |
| DPLL_CTRL1_DPLLx (OVERRIDE, PLL)); |
| Registers.Posting_Read (Registers.DPLL_CTRL1); |
| Success := True; |
| else |
| Calculate_DPLL |
| (Port_Cfg.Mode.Dotclock, Central_Freq, DCO_Freq, PDiv, QDiv, KDiv); |
| Success := Central_Freq /= CF_INVALID; |
| if Success then |
| Registers.Unset_And_Set_Mask |
| (Register => Registers.DPLL_CTRL1, |
| Mask_Unset => DPLL_CTRL1_DPLLx (SSC, PLL), |
| Mask_Set => DPLL_CTRL1_DPLLx (HDMI_MODE, PLL) or |
| DPLL_CTRL1_DPLLx (OVERRIDE, PLL)); |
| Registers.Write |
| (Register => DPLL_CFGR1 (PLL), |
| Value => DPLL_CFGR1_FREQUENCY_ENABLE or |
| DPLL_CFGR1_DCO_FRACTION (DCO_Freq) or |
| DPLL_CFGR1_DCO_INTEGER (DCO_Freq)); |
| Registers.Write |
| (Register => DPLL_CFGR2 (PLL), |
| Value => DPLL_CFGR2_QDIV (QDiv) or |
| DPLL_CFGR2_KDIV (KDiv) or |
| DPLL_CFGR2_PDIV (PDiv) or |
| DPLL_CFGR2_CENTRAL_FREQ (Central_Freq)); |
| Registers.Posting_Read (Registers.DPLL_CTRL1); |
| Registers.Posting_Read (DPLL_CFGR1 (PLL)); |
| Registers.Posting_Read (DPLL_CFGR2 (PLL)); |
| end if; |
| end if; |
| |
| if Success then |
| Registers.Write |
| (Register => DPLL_CTL (PLL), |
| Value => DPLL_CTL_PLL_ENABLE); |
| Registers.Wait_Set_Mask |
| (Register => Registers.DPLL_STATUS, |
| Mask => DPLL_STATUS_DPLLx_LOCK (PLL)); |
| end if; |
| end On; |
| |
| procedure Off (PLL : Configurable_DPLLs) is |
| begin |
| Registers.Unset_Mask (DPLL_CTL (PLL), DPLL_CTL_PLL_ENABLE); |
| end Off; |
| |
| end HW.GFX.GMA.PLLs.DPLL; |