coreboot-libre-fam15h-rdimm/3rdparty/libgfxinit/common/hw-gfx-gma-i2c.adb

276 lines
10 KiB
Ada

--
-- Copyright (C) 2015 secunet Security Networks AG
-- Copyright (C) 2019 Nico Huber <nico.h@gmx.de>
--
-- 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.Time;
with HW.Debug;
with GNAT.Source_Info;
with HW.GFX.GMA.Config;
with HW.GFX.GMA.Registers;
use type HW.Word8;
package body HW.GFX.GMA.I2C is
PCH_DSPCLK_GATE_D_GMBUS_UNIT_LVL : constant := 1 * 2 ** 31;
GMBUS0_GMBUS_RATE_SELECT_MASK : constant := 7 * 2 ** 8;
GMBUS0_GMBUS_RATE_SELECT_100KHZ : constant := 0 * 2 ** 8;
GMBUS0_GMBUS_RATE_SELECT_50KHZ : constant := 1 * 2 ** 8;
GMBUS0_PIN_PAIR_SELECT_MASK : constant := 7 * 2 ** 0;
GMBUS0_PIN_PAIR_SELECT_NONE : constant := 0 * 2 ** 0;
GMBUS0_PIN_PAIR_SELECT_DAC : constant := 2 * 2 ** 0;
GMBUS0_PIN_PAIR_SELECT_LVDS : constant := 3 * 2 ** 0;
-- Order is C, B, D: no typo!
GMBUS0_PIN_PAIR_SELECT_DIGI_C : constant := 4 * 2 ** 0;
GMBUS0_PIN_PAIR_SELECT_DIGI_B : constant := 5 * 2 ** 0;
GMBUS0_PIN_PAIR_SELECT_DIGI_D : constant := 6 * 2 ** 0;
-- Broxton uses different pins
GMBUS0_PIN_PAIR_SELECT_BXT_B : constant := 1 * 2 ** 0;
GMBUS0_PIN_PAIR_SELECT_BXT_C : constant := 2 * 2 ** 0;
GMBUS1_SOFTWARE_CLEAR_INTERRUPT : constant := 1 * 2 ** 31;
GMBUS1_SOFTWARE_READY : constant := 1 * 2 ** 30;
GMBUS1_ENABLE_TIMEOUT : constant := 1 * 2 ** 29;
GMBUS1_BUS_CYCLE_SELECT_MASK : constant := 7 * 2 ** 25;
GMBUS1_BUS_CYCLE_STOP : constant := 1 * 2 ** 27;
GMBUS1_BUS_CYCLE_INDEX : constant := 1 * 2 ** 26;
GMBUS1_BUS_CYCLE_WAIT : constant := 1 * 2 ** 25;
GMBUS1_TOTAL_BYTE_COUNT_MASK : constant := 511 * 2 ** 16;
GMBUS1_TOTAL_BYTE_COUNT_SHIFT : constant := 16;
GMBUS1_8BIT_SLAVE_INDEX_MASK : constant := 255 * 2 ** 8;
GMBUS1_8BIT_SLAVE_INDEX_SHIFT : constant := 8;
GMBUS1_SLAVE_ADDRESS_MASK : constant := 127 * 2 ** 1;
GMBUS1_SLAVE_ADDRESS_SHIFT : constant := 1;
GMBUS1_DIRECTION_MASK : constant := 1 * 2 ** 0;
GMBUS1_DIRECTION_WRITE : constant := 0 * 2 ** 0;
GMBUS1_DIRECTION_READ : constant := 1 * 2 ** 0;
GMBUS2_INUSE : constant := 1 * 2 ** 15;
GMBUS2_HARDWARE_WAIT_PHASE : constant := 1 * 2 ** 14;
GMBUS2_SLAVE_STALL_TIMEOUT_ERROR : constant := 1 * 2 ** 13;
GMBUS2_GMBUS_INTERRUPT_STATUS : constant := 1 * 2 ** 12;
GMBUS2_HARDWARE_READY : constant := 1 * 2 ** 11;
GMBUS2_NAK_INDICATOR : constant := 1 * 2 ** 10;
GMBUS2_GMBUS_ACTIVE : constant := 1 * 2 ** 9;
GMBUS2_CURRENT_BYTE_COUNT_MASK : constant := 511 * 2 ** 0;
GMBUS4_INTERRUPT_MASK : constant := 31 * 2 ** 0;
GMBUS5_2BYTE_INDEX_ENABLE : constant := 1 * 2 ** 31;
GMBUS_Regs : constant array (0 .. 5) of Registers.Registers_Index :=
(if Config.Has_PCH_GMBUS then
(0 => Registers.PCH_GMBUS0,
1 => Registers.PCH_GMBUS1,
2 => Registers.PCH_GMBUS2,
3 => Registers.PCH_GMBUS3,
4 => Registers.PCH_GMBUS4,
5 => Registers.PCH_GMBUS5)
else
(0 => Registers.GMCH_GMBUS0,
1 => Registers.GMCH_GMBUS1,
2 => Registers.GMCH_GMBUS2,
3 => Registers.GMCH_GMBUS3,
4 => Registers.GMCH_GMBUS4,
5 => Registers.GMCH_GMBUS5));
function GMBUS1_TOTAL_BYTE_COUNT
(Count : HW.GFX.I2C.Transfer_Length)
return Word32 is
begin
return Shift_Left (Word32 (Count), GMBUS1_TOTAL_BYTE_COUNT_SHIFT);
end GMBUS1_TOTAL_BYTE_COUNT;
function GMBUS1_SLAVE_ADDRESS
(Address : HW.GFX.I2C.Transfer_Address)
return Word32 is
begin
return Shift_Left (Word32 (Address), GMBUS1_SLAVE_ADDRESS_SHIFT);
end GMBUS1_SLAVE_ADDRESS;
function GMBUS0_PIN_PAIR_SELECT (Port : PCH_Port) return Word32 is
begin
return
(if Config.GMBUS_Alternative_Pins then
(case Port is
when PCH_HDMI_B => GMBUS0_PIN_PAIR_SELECT_BXT_B,
when PCH_HDMI_C => GMBUS0_PIN_PAIR_SELECT_BXT_C,
when others => GMBUS0_PIN_PAIR_SELECT_NONE)
else
(case Port is
when PCH_DAC => GMBUS0_PIN_PAIR_SELECT_DAC,
when PCH_LVDS => GMBUS0_PIN_PAIR_SELECT_LVDS,
when PCH_HDMI_B => GMBUS0_PIN_PAIR_SELECT_DIGI_B,
when PCH_HDMI_C => GMBUS0_PIN_PAIR_SELECT_DIGI_C,
when PCH_HDMI_D => GMBUS0_PIN_PAIR_SELECT_DIGI_D,
when others => GMBUS0_PIN_PAIR_SELECT_NONE));
end GMBUS0_PIN_PAIR_SELECT;
----------------------------------------------------------------------------
function GMBUS_Ready (GMBUS2 : Word32) return Boolean is
((GMBUS2 and (GMBUS2_HARDWARE_WAIT_PHASE or
GMBUS2_SLAVE_STALL_TIMEOUT_ERROR or
GMBUS2_GMBUS_INTERRUPT_STATUS or
GMBUS2_NAK_INDICATOR or
GMBUS2_GMBUS_ACTIVE)) = 0);
procedure Check_And_Reset (Success : out Boolean)
is
GMBUS2 : Word32;
begin
pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
Registers.Read (GMBUS_Regs (2), GMBUS2);
if (GMBUS2 and GMBUS2_GMBUS_ACTIVE) /= 0 then
Registers.Write
(Register => GMBUS_Regs (1),
Value => GMBUS1_SOFTWARE_READY or GMBUS1_BUS_CYCLE_STOP);
Registers.Wait_Unset_Mask
(Register => GMBUS_Regs (2),
Mask => GMBUS2_GMBUS_ACTIVE,
TOut_MS => 1);
Registers.Read (GMBUS_Regs (2), GMBUS2);
end if;
Success := GMBUS_Ready (GMBUS2);
if not Success then
Registers.Write (GMBUS_Regs (1), GMBUS1_SOFTWARE_CLEAR_INTERRUPT);
Registers.Write (GMBUS_Regs (1), 0);
Registers.Read (GMBUS_Regs (2), GMBUS2);
Success := GMBUS_Ready (GMBUS2);
end if;
end Check_And_Reset;
procedure Init_GMBUS (Port : PCH_Port; Success : out Boolean) is
begin
pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
if Config.Ungate_GMBUS_Unit_Level then
Registers.Set_Mask
(Register => Registers.PCH_DSPCLK_GATE_D,
Mask => PCH_DSPCLK_GATE_D_GMBUS_UNIT_LVL);
end if;
-- TODO: Refactor + check for timeout.
Registers.Wait_Unset_Mask (GMBUS_Regs (2), GMBUS2_INUSE);
Registers.Write (GMBUS_Regs (4), 0);
Registers.Write (GMBUS_Regs (5), 0);
-- Resetting the state machine only works if a valid port
-- is selected and we don't always know which ports are
-- valid. So do the cleanup before we use the GMBUS with
-- the current port. If the port is valid, the reset should
-- work, if not, it shouldn't matter.
Registers.Write
(Register => GMBUS_Regs (0),
Value => GMBUS0_GMBUS_RATE_SELECT_100KHZ or
GMBUS0_PIN_PAIR_SELECT (Port));
Check_And_Reset (Success);
end Init_GMBUS;
procedure Release_GMBUS
is
begin
pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
Registers.Write (GMBUS_Regs (0), GMBUS0_PIN_PAIR_SELECT_NONE);
-- Clear INUSE. TODO: Don't do it, if timeout occured (see above).
Registers.Write (GMBUS_Regs (2), GMBUS2_INUSE);
if Config.Ungate_GMBUS_Unit_Level then
Registers.Unset_Mask
(Register => Registers.PCH_DSPCLK_GATE_D,
Mask => PCH_DSPCLK_GATE_D_GMBUS_UNIT_LVL);
end if;
end Release_GMBUS;
procedure I2C_Read
(Port : in PCH_Port;
Address : in HW.GFX.I2C.Transfer_Address;
Length : in out HW.GFX.I2C.Transfer_Length;
Data : out HW.GFX.I2C.Transfer_Data;
Success : out Boolean)
is
GMBUS2 : Word32 := 0;
GMBUS3 : Word32;
Current : HW.GFX.I2C.Transfer_Length;
Transfered : HW.GFX.I2C.Transfer_Length := 0;
begin
pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
Data := (others => 0);
Init_GMBUS (Port, Success);
if Success then
Registers.Write
(Register => GMBUS_Regs (1),
Value => GMBUS1_SOFTWARE_READY or
GMBUS1_BUS_CYCLE_INDEX or
GMBUS1_BUS_CYCLE_WAIT or
GMBUS1_TOTAL_BYTE_COUNT (Length) or
GMBUS1_SLAVE_ADDRESS (Address) or
GMBUS1_DIRECTION_READ);
while Success and then Transfered < Length loop
Registers.Wait_Set_Mask
(Register => GMBUS_Regs (2),
Mask => GMBUS2_HARDWARE_READY,
TOut_MS => 500);
Registers.Read (GMBUS_Regs (2), GMBUS2);
Success := (GMBUS2 and GMBUS2_HARDWARE_READY) /= 0 and
(GMBUS2 and GMBUS2_NAK_INDICATOR) = 0;
if Success then
Current := GFX.I2C.Transfer_Length'Min (Length, Transfered + 4);
Registers.Read (GMBUS_Regs (3), GMBUS3);
for I in Transfered .. Current - 1 loop
Data (I) := Byte (GMBUS3 and 16#ff#);
GMBUS3 := Shift_Right (GMBUS3, 8);
end loop;
Transfered := Current;
end if;
end loop;
if Success then
Registers.Wait_Set_Mask
(Register => GMBUS_Regs (2),
Mask => GMBUS2_HARDWARE_WAIT_PHASE);
Registers.Write
(Register => GMBUS_Regs (1),
Value => GMBUS1_SOFTWARE_READY or GMBUS1_BUS_CYCLE_STOP);
Registers.Wait_Unset_Mask
(Register => GMBUS_Regs (2),
Mask => GMBUS2_GMBUS_ACTIVE);
elsif (GMBUS2 and GMBUS2_NAK_INDICATOR) /= 0 then
Registers.Wait_Unset_Mask
(Register => GMBUS_Regs (2),
Mask => GMBUS2_GMBUS_ACTIVE);
Registers.Write (GMBUS_Regs (1), GMBUS1_SOFTWARE_CLEAR_INTERRUPT);
Registers.Write (GMBUS_Regs (1), 0);
end if;
end if;
Length := Transfered;
Release_GMBUS;
end I2C_Read;
end HW.GFX.GMA.I2C;