276 lines
10 KiB
Ada
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;
|