gma: Add Intel i945 (Gen3) graphics init support
Add i945G (desktop) and i945GM (mobile) generation support, modeled
after the existing G45 generation code with hardware-specific
adaptations based on the Linux i915 DRM driver and coreboot.
Key hardware differences from G45 (Gen4):
- GTT on separate PCI BAR3 (not within BAR0)
- Simple 32-bit GTT PTEs (addr[31:12] | valid[0])
- No DSPSURF register (uses DSPADDR/DSPLINOFF instead)
- Gen3 fence registers: 32-bit at split 0x2000/0x3000 addresses
- Different PLL limits (VCO 1400-2800 MHz, 96 MHz refclk)
- SDVO multiplier in DPLL register bits[7:4]
- LVDS restricted to Pipe B (pre-i965 requirement)
- CDClk: fixed 400 MHz (desktop) or GCFGC-based (mobile)
- No HDMI/DP, only VGA, LVDS, and SDVO outputs
- PCI IDs: 0x2772 (I945G), 0x27a2/0x27ae (I945GM)
TESTED with thinkpad x60: LVDS & VGA works with a linear framebuffer.
Change-Id: Ib67b3d0ee5e06df427869dce4db926ba57a80fd8
Signed-off-by: Arthur Heymans <arthur@aheymans.xyz>
Reviewed-on: https://review.sourcearcade.org/c/libgfxinit/+/476
Reviewed-by: Nico Huber <nico.h@gmx.de>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Tested-by: Nico Huber <nico.h@gmx.de>
diff --git a/common/i945/hw-gfx-gma-power_and_clocks.adb b/common/i945/hw-gfx-gma-power_and_clocks.adb
new file mode 100644
index 0000000..e18d1e3
--- /dev/null
+++ b/common/i945/hw-gfx-gma-power_and_clocks.adb
@@ -0,0 +1,131 @@
+--
+-- Copyright (C) 2026 Arthur Heymans <arthur@aheymans.xyz>
+--
+-- 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.Config;
+with HW.GFX.GMA.Registers;
+
+package body HW.GFX.GMA.Power_And_Clocks is
+
+ CLKCFG_FSB_MASK : constant := 7 * 2 ** 0;
+ -- hrawclk = FSB / 4. The CLKCFG FSB bits encode different FSB speeds
+ -- depending on whether the platform is mobile or desktop (ALT encoding):
+ -- mobile: 0=FSB400, 1=FSB533, 2=FSB800, 3=FSB667, 6=FSB1067, 7=FSB1333
+ -- desktop: 0=FSB1067, 1=FSB533, 2=FSB800, 3=FSB667, 4=FSB1333,
+ -- 5=FSB400, 6=FSB1600
+ HRAWCLK_100 : constant Frequency_Type := 100_000_000; -- FSB 400
+ HRAWCLK_133 : constant Frequency_Type := 133_333_333; -- FSB 533
+ HRAWCLK_167 : constant Frequency_Type := 166_666_666; -- FSB 667
+ HRAWCLK_200 : constant Frequency_Type := 200_000_000; -- FSB 800
+ HRAWCLK_267 : constant Frequency_Type := 266_666_667; -- FSB 1067
+ HRAWCLK_333 : constant Frequency_Type := 333_333_333; -- FSB 1333
+ HRAWCLK_400 : constant Frequency_Type := 400_000_000; -- FSB 1600 (desktop)
+
+ -- i945 CDClk values (from Linux kernel intel_cdclk.c):
+ --
+ -- i945G (desktop): fixed 400 MHz CDClk
+ --
+ -- i945GM (mobile): read from GCFGC PCI register (0xF0)
+ -- GC_LOW_FREQUENCY_ENABLE (bit 7) => 133 MHz
+ -- GC_DISPLAY_CLOCK_190_200_MHZ (0 << 4) => 200 MHz (default)
+ -- GC_DISPLAY_CLOCK_333_320_MHZ (4 << 4) => 320 MHz
+
+ procedure Get_CDClk (CDClk : out Config.CDClk_Range)
+ is
+ use type HW.Word16;
+
+ GC_LOW_FREQUENCY_ENABLE : constant Word16 := 1 * 2 ** 7;
+ GC_DISPLAY_CLOCK_MASK : constant Word16 := 7 * 2 ** 4;
+ GC_DISPLAY_CLOCK_320_MHZ : constant Word16 := 4 * 2 ** 4;
+
+ GCFGC : Word16;
+ Tmp_Clk : Frequency_Type := 200_000_000;
+ begin
+ if Config.GMCH_I945GM then
+ if PCI_Usable then
+ PCI_Read16 (GCFGC, 16#f0#);
+ if (GCFGC and GC_LOW_FREQUENCY_ENABLE) /= 0 then
+ Tmp_Clk := 133_333_333;
+ elsif (GCFGC and GC_DISPLAY_CLOCK_MASK) = GC_DISPLAY_CLOCK_320_MHZ then
+ Tmp_Clk := 320_000_000;
+ else
+ Tmp_Clk := 200_000_000;
+ end if;
+ end if;
+ else
+ -- i945G desktop: fixed 400 MHz
+ Tmp_Clk := 400_000_000;
+ end if;
+
+ if Tmp_Clk in Config.CDClk_Range then
+ CDClk := Tmp_Clk;
+ else
+ CDClk := 200_000_000;
+ end if;
+ end Get_CDClk;
+
+ -- hrawclk = FSB / 4. The CLKCFG FSB encoding differs between
+ -- mobile (i945GM) and desktop (i945G), see CLKCFG_FSB_MASK comment above.
+ procedure Get_Raw_Clock (Raw_Clock : out Frequency_Type)
+ is
+ CLK_CFG : Word32;
+ type Freq_Sel is new Natural range 0 .. 7;
+ begin
+ Registers.Read
+ (Register => Registers.GMCH_CLKCFG,
+ Value => CLK_CFG);
+ if Config.GMCH_I945GM then
+ Raw_Clock := (case Freq_Sel (CLK_CFG and CLKCFG_FSB_MASK) is
+ when 0 => HRAWCLK_100, -- FSB 400
+ when 1 => HRAWCLK_133, -- FSB 533
+ when 2 => HRAWCLK_200, -- FSB 800
+ when 3 => HRAWCLK_167, -- FSB 667
+ when 6 => HRAWCLK_267, -- FSB 1067
+ when 7 => HRAWCLK_333, -- FSB 1333
+ when others => HRAWCLK_133);
+ else
+ Raw_Clock := (case Freq_Sel (CLK_CFG and CLKCFG_FSB_MASK) is
+ when 0 => HRAWCLK_267, -- FSB 1067
+ when 1 => HRAWCLK_133, -- FSB 533
+ when 2 => HRAWCLK_200, -- FSB 800
+ when 3 => HRAWCLK_167, -- FSB 667
+ when 4 => HRAWCLK_333, -- FSB 1333
+ when 5 => HRAWCLK_100, -- FSB 400
+ when 6 => HRAWCLK_400, -- FSB 1600
+ when others => HRAWCLK_133);
+ end if;
+ end Get_Raw_Clock;
+
+ procedure Initialize
+ is
+ CDClk : Config.CDClk_Range;
+ Raw_Clk : Frequency_Type;
+ begin
+ Get_CDClk (CDClk);
+ Config.CDClk := CDClk;
+ Config.Max_CDClk := CDClk;
+
+ Get_Raw_Clock (Raw_Clk);
+ Config.Raw_Clock := Raw_Clk;
+ end Initialize;
+
+ procedure Limit_Dotclocks
+ (Configs : in out Pipe_Configs;
+ CDClk_Switch : out Boolean)
+ is
+ begin
+ Config_Helpers.Limit_Dotclocks (Configs, Config.CDClk * 90 / 100);
+ CDClk_Switch := False;
+ end Limit_Dotclocks;
+
+end HW.GFX.GMA.Power_And_Clocks;