Переглянути джерело

Kernel/Graphics: Add basic support for Intel native accelerator

We simply modeset the resolution after determining the preferred
resolution after getting the EDID from the attached display.
Liav A 4 роки тому
батько
коміт
cc92538d49

+ 1 - 0
Kernel/CMakeLists.txt

@@ -54,6 +54,7 @@ set(KERNEL_SOURCES
     Graphics/BochsGraphicsAdapter.cpp
     Graphics/FramebufferDevice.cpp
     Graphics/GraphicsManagement.cpp
+    Graphics/IntelNativeGraphicsAdapter.cpp
     Graphics/RawFramebufferDevice.cpp
     Graphics/VGACompatibleAdapter.cpp
     Storage/Partition/DiskPartition.cpp

+ 4 - 0
Kernel/Debug.h.in

@@ -102,6 +102,10 @@
 #cmakedefine01 ICMP_DEBUG
 #endif
 
+#ifndef INTEL_GRAPHICS_DEBUG
+#cmakedefine01 INTEL_GRAPHICS_DEBUG
+#endif
+
 #ifndef INTERRUPT_DEBUG
 #cmakedefine01 INTERRUPT_DEBUG
 #endif

+ 85 - 0
Kernel/Graphics/Definitions.h

@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+
+namespace Kernel::Graphics {
+
+struct Timings {
+    size_t blanking_start() const
+    {
+        return active;
+    }
+    size_t blanking_end() const
+    {
+        return total;
+    }
+
+    size_t active;
+    size_t sync_start;
+    size_t sync_end;
+    size_t total;
+};
+
+struct Modesetting {
+    size_t pixel_clock_in_khz;
+    Timings horizontal;
+    Timings vertical;
+};
+
+struct [[gnu::packed]] StandardTimings {
+    u8 resolution;
+    u8 frequency;
+};
+
+struct [[gnu::packed]] DetailTimings {
+    u16 pixel_clock;
+    u8 horizontal_active;
+    u8 horizontal_blank;
+    u8 horizontal_active_blank_msb;
+    u8 vertical_active;
+    u8 vertical_blank;
+    u8 vertical_active_blank_msb;
+    u8 horizontal_sync_offset;
+    u8 horizontal_sync_pulse;
+    u8 vertical_sync;
+    u8 sync_msb;
+    u8 dimension_width;
+    u8 dimension_height;
+    u8 dimension_msb;
+    u8 horizontal_border;
+    u8 vertical_border;
+    u8 features;
+};
+
+struct [[gnu::packed]] VideoInfoBlock {
+    u64 padding;
+    u16 manufacture_id;
+    u16 product_id;
+    u32 serial_number;
+    u8 manufacture_week;
+    u8 manufacture_year;
+    u8 edid_version;
+    u8 edid_revision;
+    u8 video_input_type;
+    u8 max_horizontal_size;
+    u8 max_vertical_size;
+    u8 gama_factor;
+    u8 dpms_flags;
+    u8 chroma_info[10];
+    u8 established_timing[2];
+    u8 manufacture_reserved_timings;
+    StandardTimings timings[8];
+    DetailTimings details[4];
+    u8 unused;
+    u8 checksum;
+};
+
+static_assert(sizeof(VideoInfoBlock) == 128);
+
+}

+ 6 - 0
Kernel/Graphics/GraphicsManagement.cpp

@@ -10,6 +10,7 @@
 #include <Kernel/Debug.h>
 #include <Kernel/Graphics/BochsGraphicsAdapter.h>
 #include <Kernel/Graphics/GraphicsManagement.h>
+#include <Kernel/Graphics/IntelNativeGraphicsAdapter.h>
 #include <Kernel/Graphics/VGACompatibleAdapter.h>
 #include <Kernel/Multiboot.h>
 
@@ -38,6 +39,11 @@ UNMAP_AFTER_INIT RefPtr<GraphicsDevice> GraphicsManagement::determine_graphics_d
         return BochsGraphicsAdapter::initialize(address);
     }
     if (PCI::get_class(address) == 0x3 && PCI::get_subclass(address) == 0x0) {
+        if (id.vendor_id == 0x8086) {
+            auto adapter = IntelNativeGraphicsAdapter::initialize(address);
+            if (!adapter.is_null())
+                return adapter;
+        }
         VERIFY(multiboot_info_ptr->framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_RGB || multiboot_info_ptr->framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT);
         return VGACompatibleAdapter::initialize_with_preset_resolution(address,
             PhysicalAddress((u32)(multiboot_info_ptr->framebuffer_addr)),

+ 605 - 0
Kernel/Graphics/IntelNativeGraphicsAdapter.cpp

@@ -0,0 +1,605 @@
+/*
+ * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <Kernel/Graphics/Definitions.h>
+#include <Kernel/Graphics/IntelNativeGraphicsAdapter.h>
+#include <Kernel/IO.h>
+#include <Kernel/PhysicalAddress.h>
+
+namespace Kernel {
+
+static constexpr IntelNativeGraphicsAdapter::PLLMaxSettings G35Limits {
+    { 20'000'000, 400'000'000 },      // values in Hz, dot_clock
+    { 1'400'000'000, 2'800'000'000 }, // values in Hz, VCO
+    { 3, 8 },                         // n
+    { 70, 120 },                      // m
+    { 10, 20 },                       // m1
+    { 5, 9 },                         // m2
+    { 5, 80 },                        // p
+    { 1, 8 },                         // p1
+    { 5, 10 }                         // p2
+};
+
+#define DDC2_I2C_ADDRESS 0x50
+
+RefPtr<IntelNativeGraphicsAdapter> IntelNativeGraphicsAdapter::initialize(PCI::Address address)
+{
+    auto id = PCI::get_id(address);
+    VERIFY(id.vendor_id == 0x8086);
+    if (id.device_id != 0x29c2)
+        return {};
+    return adopt_ref(*new IntelNativeGraphicsAdapter(address));
+}
+
+static size_t compute_dac_multiplier(size_t pixel_clock_in_khz)
+{
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel native graphics: Pixel clock is {} KHz", pixel_clock_in_khz);
+    VERIFY(pixel_clock_in_khz >= 25000);
+    if (pixel_clock_in_khz >= 100000) {
+        return 1;
+    } else if (pixel_clock_in_khz >= 50000) {
+        return 2;
+    } else {
+        return 4;
+    }
+}
+
+static Graphics::Modesetting calculate_modesetting_from_edid(const Graphics::VideoInfoBlock& edid, size_t index)
+{
+    Graphics::Modesetting mode;
+    VERIFY(edid.details[0].pixel_clock);
+    mode.pixel_clock_in_khz = edid.details[0].pixel_clock * 10;
+
+    size_t horizontal_active = edid.details[index].horizontal_active | ((edid.details[index].horizontal_active_blank_msb >> 4) << 8);
+    size_t horizontal_blank = edid.details[index].horizontal_blank | ((edid.details[index].horizontal_active_blank_msb & 0xF) << 8);
+    size_t horizontal_sync_offset = edid.details[index].horizontal_sync_offset | ((edid.details[index].sync_msb >> 6) << 8);
+    size_t horizontal_sync_pulse = edid.details[index].horizontal_sync_pulse | (((edid.details[index].sync_msb >> 4) & 0x3) << 8);
+
+    mode.horizontal.active = horizontal_active;
+    mode.horizontal.sync_start = horizontal_active + horizontal_sync_offset;
+    mode.horizontal.sync_end = horizontal_active + horizontal_sync_offset + horizontal_sync_pulse;
+    mode.horizontal.total = horizontal_active + horizontal_blank;
+
+    size_t vertical_active = edid.details[index].vertical_active | ((edid.details[index].vertical_active_blank_msb >> 4) << 8);
+    size_t vertical_blank = edid.details[index].vertical_blank | ((edid.details[index].vertical_active_blank_msb & 0xF) << 8);
+    size_t vertical_sync_offset = (edid.details[index].vertical_sync >> 4) | (((edid.details[index].sync_msb >> 2) & 0x3) << 4);
+    size_t vertical_sync_pulse = (edid.details[index].vertical_sync & 0xF) | ((edid.details[index].sync_msb & 0x3) << 4);
+
+    mode.vertical.active = vertical_active;
+    mode.vertical.sync_start = vertical_active + vertical_sync_offset;
+    mode.vertical.sync_end = vertical_active + vertical_sync_offset + vertical_sync_pulse;
+    mode.vertical.total = vertical_active + vertical_blank;
+    return mode;
+}
+
+static bool check_pll_settings(const IntelNativeGraphicsAdapter::PLLSettings& settings, size_t reference_clock, const IntelNativeGraphicsAdapter::PLLMaxSettings& limits)
+{
+    if (settings.n < limits.n.min || settings.n > limits.n.max) {
+        dbgln_if(INTEL_GRAPHICS_DEBUG, "N is invalid {}", settings.n);
+        return false;
+    }
+    if (settings.m1 < limits.m1.min || settings.m1 > limits.m1.max) {
+        dbgln_if(INTEL_GRAPHICS_DEBUG, "m1 is invalid {}", settings.m1);
+        return false;
+    }
+    if (settings.m2 < limits.m2.min || settings.m2 > limits.m2.max) {
+        dbgln_if(INTEL_GRAPHICS_DEBUG, "m2 is invalid {}", settings.m2);
+        return false;
+    }
+    if (settings.p1 < limits.p1.min || settings.p1 > limits.p1.max) {
+        dbgln_if(INTEL_GRAPHICS_DEBUG, "p1 is invalid {}", settings.p1);
+        return false;
+    }
+
+    if (settings.m1 <= settings.m2) {
+        dbgln_if(INTEL_GRAPHICS_DEBUG, "m2 is invalid {} as it is bigger than m1 {}", settings.m2, settings.m1);
+        return false;
+    }
+
+    auto m = settings.compute_m();
+    auto p = settings.compute_p();
+
+    if (m < limits.m.min || m > limits.m.max) {
+        dbgln_if(INTEL_GRAPHICS_DEBUG, "m invalid {}", m);
+        return false;
+    }
+    if (p < limits.p.min || p > limits.p.max) {
+        dbgln_if(INTEL_GRAPHICS_DEBUG, "p invalid {}", p);
+        return false;
+    }
+
+    auto dot = settings.compute_dot_clock(reference_clock);
+    auto vco = settings.compute_vco(reference_clock);
+
+    if (dot < limits.dot_clock.min || dot > limits.dot_clock.max) {
+        dbgln_if(INTEL_GRAPHICS_DEBUG, "Dot clock invalid {}", dot);
+        return false;
+    }
+    if (vco < limits.vco.min || vco > limits.vco.max) {
+        dbgln_if(INTEL_GRAPHICS_DEBUG, "VCO clock invalid {}", vco);
+        return false;
+    }
+    return true;
+}
+
+static size_t find_absolute_difference(u64 target_frequency, u64 checked_frequency)
+{
+    if (target_frequency >= checked_frequency)
+        return target_frequency - checked_frequency;
+    return checked_frequency - target_frequency;
+}
+
+Optional<IntelNativeGraphicsAdapter::PLLSettings> IntelNativeGraphicsAdapter::create_pll_settings(u64 target_frequency, u64 reference_clock, const PLLMaxSettings& limits)
+{
+    IntelNativeGraphicsAdapter::PLLSettings settings;
+    IntelNativeGraphicsAdapter::PLLSettings best_settings;
+    // FIXME: Is this correct for all Intel Native graphics cards?
+    settings.p2 = 10;
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "Check PLL settings for ref clock of {} Hz, for target of {} Hz", reference_clock, target_frequency);
+    u64 best_difference = 0xffffffff;
+    for (settings.n = limits.n.min; settings.n <= limits.n.max; ++settings.n) {
+        for (settings.m1 = limits.m1.max; settings.m1 >= limits.m1.min; --settings.m1) {
+            for (settings.m2 = limits.m2.max; settings.m2 >= limits.m2.min; --settings.m2) {
+                for (settings.p1 = limits.p1.max; settings.p1 >= limits.p1.min; --settings.p1) {
+                    dbgln_if(INTEL_GRAPHICS_DEBUG, "Check PLL settings for {} {} {} {} {}", settings.n, settings.m1, settings.m2, settings.p1, settings.p2);
+                    if (!check_pll_settings(settings, reference_clock, limits))
+                        continue;
+                    auto current_dot_clock = settings.compute_dot_clock(reference_clock);
+                    if (current_dot_clock == target_frequency)
+                        return settings;
+                    auto difference = find_absolute_difference(target_frequency, current_dot_clock);
+                    if (difference < best_difference && (current_dot_clock > target_frequency)) {
+                        best_settings = settings;
+                        best_difference = difference;
+                    }
+                }
+            }
+        }
+    }
+    if (best_settings.is_valid())
+        return best_settings;
+    return {};
+}
+
+IntelNativeGraphicsAdapter::IntelNativeGraphicsAdapter(PCI::Address address)
+    : PCI::DeviceController(address)
+    , m_registers(PCI::get_BAR0(address) & 0xfffffffc)
+    , m_framebuffer_addr(PCI::get_BAR2(address) & 0xfffffffc)
+{
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Native Graphics Adapter @ {}", address);
+    auto bar0_space_size = PCI::get_BAR_space_size(address, 0);
+    VERIFY(bar0_space_size == 0x80000);
+    dmesgln("Intel Native Graphics Adapter @ {}, MMIO @ {}, space size is {:x} bytes", address, PhysicalAddress(PCI::get_BAR0(address)), bar0_space_size);
+    dmesgln("Intel Native Graphics Adapter @ {}, framebuffer @ {}", address, PhysicalAddress(PCI::get_BAR2(address)));
+    m_registers_region = MM.allocate_kernel_region(PhysicalAddress(PCI::get_BAR0(address)).page_base(), bar0_space_size, "Intel Native Graphics Registers", Region::Access::Read | Region::Access::Write);
+    PCI::enable_bus_mastering(address);
+    {
+        ScopedSpinLock control_lock(m_control_lock);
+        set_gmbus_default_rate();
+        set_gmbus_pin_pair(GMBusPinPair::DedicatedAnalog);
+    }
+    gmbus_read_edid();
+    auto modesetting = calculate_modesetting_from_edid(m_crt_edid, 0);
+    dmesgln("Intel Native Graphics Adapter @ {}, preferred resolution is {:d}x{:d}", address, modesetting.horizontal.active, modesetting.vertical.active);
+
+    set_crt_resolution(modesetting.horizontal.active, modesetting.vertical.active);
+}
+
+static inline const char* convert_register_index_to_string(IntelGraphics::RegisterIndex index)
+{
+    switch (index) {
+    case IntelGraphics::RegisterIndex::PipeAConf:
+        return "PipeAConf";
+    case IntelGraphics::RegisterIndex::PipeBConf:
+        return "PipeBConf";
+    case IntelGraphics::RegisterIndex::GMBusData:
+        return "GMBusData";
+    case IntelGraphics::RegisterIndex::GMBusStatus:
+        return "GMBusStatus";
+    case IntelGraphics::RegisterIndex::GMBusCommand:
+        return "GMBusCommand";
+    case IntelGraphics::RegisterIndex::GMBusClock:
+        return "GMBusClock";
+    case IntelGraphics::RegisterIndex::DisplayPlaneAControl:
+        return "DisplayPlaneAControl";
+    case IntelGraphics::RegisterIndex::DisplayPlaneALinearOffset:
+        return "DisplayPlaneALinearOffset";
+    case IntelGraphics::RegisterIndex::DisplayPlaneAStride:
+        return "DisplayPlaneAStride";
+    case IntelGraphics::RegisterIndex::DisplayPlaneASurface:
+        return "DisplayPlaneASurface";
+    case IntelGraphics::RegisterIndex::DPLLDivisorA0:
+        return "DPLLDivisorA0";
+    case IntelGraphics::RegisterIndex::DPLLDivisorA1:
+        return "DPLLDivisorA1";
+    case IntelGraphics::RegisterIndex::DPLLControlA:
+        return "DPLLControlA";
+    case IntelGraphics::RegisterIndex::DPLLControlB:
+        return "DPLLControlB";
+    case IntelGraphics::RegisterIndex::DPLLMultiplierA:
+        return "DPLLMultiplierA";
+    case IntelGraphics::RegisterIndex::HTotalA:
+        return "HTotalA";
+    case IntelGraphics::RegisterIndex::HBlankA:
+        return "HBlankA";
+    case IntelGraphics::RegisterIndex::HSyncA:
+        return "HSyncA";
+    case IntelGraphics::RegisterIndex::VTotalA:
+        return "VTotalA";
+    case IntelGraphics::RegisterIndex::VBlankA:
+        return "VBlankA";
+    case IntelGraphics::RegisterIndex::VSyncA:
+        return "VSyncA";
+    case IntelGraphics::RegisterIndex::PipeASource:
+        return "PipeASource";
+    case IntelGraphics::RegisterIndex::AnalogDisplayPort:
+        return "AnalogDisplayPort";
+    case IntelGraphics::RegisterIndex::VGADisplayPlaneControl:
+        return "VGADisplayPlaneControl";
+    default:
+        VERIFY_NOT_REACHED();
+    }
+}
+
+void IntelNativeGraphicsAdapter::write_to_register(IntelGraphics::RegisterIndex index, u32 value) const
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_registers_region);
+    ScopedSpinLock lock(m_registers_lock);
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Graphics {}: Write to {} value of {:x}", pci_address(), convert_register_index_to_string(index), value);
+    auto* reg = (volatile u32*)m_registers_region->vaddr().offset(index).as_ptr();
+    *reg = value;
+}
+u32 IntelNativeGraphicsAdapter::read_from_register(IntelGraphics::RegisterIndex index) const
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_registers_region);
+    ScopedSpinLock lock(m_registers_lock);
+    auto* reg = (volatile u32*)m_registers_region->vaddr().offset(index).as_ptr();
+    u32 value = *reg;
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Graphics {}: Read from {} value of {:x}", pci_address(), convert_register_index_to_string(index), value);
+    return value;
+}
+
+bool IntelNativeGraphicsAdapter::pipe_a_enabled() const
+{
+    VERIFY(m_control_lock.is_locked());
+    return read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & (1 << 30);
+}
+bool IntelNativeGraphicsAdapter::pipe_b_enabled() const
+{
+    VERIFY(m_control_lock.is_locked());
+    return read_from_register(IntelGraphics::RegisterIndex::PipeBConf) & (1 << 30);
+}
+
+bool IntelNativeGraphicsAdapter::gmbus_wait_for(GMBusStatus desired_status, Optional<size_t> milliseconds_timeout)
+{
+    VERIFY(m_control_lock.is_locked());
+    size_t milliseconds_passed = 0;
+    while (1) {
+        if (milliseconds_timeout.has_value() && milliseconds_timeout.value() < milliseconds_passed)
+            return false;
+        full_memory_barrier();
+        u32 status = read_from_register(IntelGraphics::RegisterIndex::GMBusStatus);
+        full_memory_barrier();
+        VERIFY(!(status & (1 << 10))); // error happened
+        switch (desired_status) {
+        case GMBusStatus::HardwareReady:
+            if (status & (1 << 11))
+                return true;
+            break;
+        case GMBusStatus::TransactionCompletion:
+            if (status & (1 << 14))
+                return true;
+            break;
+        default:
+            VERIFY_NOT_REACHED();
+        }
+        IO::delay(1000);
+        milliseconds_passed++;
+    }
+}
+
+void IntelNativeGraphicsAdapter::gmbus_write(unsigned address, u32 byte)
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(address < 256);
+    full_memory_barrier();
+    write_to_register(IntelGraphics::RegisterIndex::GMBusData, byte);
+    full_memory_barrier();
+    write_to_register(IntelGraphics::RegisterIndex::GMBusCommand, ((address << 1) | (1 << 16) | (GMBusCycle::Wait << 25) | (1 << 30)));
+    full_memory_barrier();
+    gmbus_wait_for(GMBusStatus::TransactionCompletion, {});
+}
+void IntelNativeGraphicsAdapter::gmbus_read(unsigned address, u8* buf, size_t length)
+{
+    VERIFY(address < 256);
+    VERIFY(m_control_lock.is_locked());
+    size_t nread = 0;
+    auto read_set = [&] {
+        full_memory_barrier();
+        u32 data = read_from_register(IntelGraphics::RegisterIndex::GMBusData);
+        full_memory_barrier();
+        for (size_t index = 0; index < 4; index++) {
+            if (nread == length)
+                break;
+            buf[nread] = (data >> (8 * index)) & 0xFF;
+            nread++;
+        }
+    };
+
+    full_memory_barrier();
+    write_to_register(IntelGraphics::RegisterIndex::GMBusCommand, (1 | (address << 1) | (length << 16) | (GMBusCycle::Wait << 25) | (1 << 30)));
+    full_memory_barrier();
+    while (nread < length) {
+        gmbus_wait_for(GMBusStatus::HardwareReady, {});
+        read_set();
+    }
+    gmbus_wait_for(GMBusStatus::TransactionCompletion, {});
+}
+
+void IntelNativeGraphicsAdapter::gmbus_read_edid()
+{
+    ScopedSpinLock control_lock(m_control_lock);
+    gmbus_write(DDC2_I2C_ADDRESS, 0);
+    gmbus_read(DDC2_I2C_ADDRESS, (u8*)&m_crt_edid, sizeof(Graphics::VideoInfoBlock));
+}
+
+bool IntelNativeGraphicsAdapter::is_resolution_valid(size_t, size_t)
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    // FIXME: Check that we are able to modeset to the requested resolution!
+    return true;
+}
+
+void IntelNativeGraphicsAdapter::disable_output()
+{
+    VERIFY(m_control_lock.is_locked());
+    disable_dac_output();
+    disable_all_planes();
+    disable_pipe_a();
+    disable_pipe_b();
+    disable_vga_emulation();
+    disable_dpll();
+}
+
+void IntelNativeGraphicsAdapter::enable_output(PhysicalAddress fb_address, size_t width)
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(!pipe_a_enabled());
+
+    enable_pipe_a();
+    enable_primary_plane(fb_address, width);
+    enable_dac_output();
+}
+
+bool IntelNativeGraphicsAdapter::set_crt_resolution(size_t width, size_t height)
+{
+    ScopedSpinLock control_lock(m_control_lock);
+    ScopedSpinLock modeset_lock(m_modeset_lock);
+    if (!is_resolution_valid(width, height)) {
+        return false;
+    }
+
+    // FIXME: Get the requested resolution from the EDID!!
+    auto modesetting = calculate_modesetting_from_edid(m_crt_edid, 0);
+
+    disable_output();
+
+    auto dac_multiplier = compute_dac_multiplier(modesetting.pixel_clock_in_khz);
+    auto pll_settings = create_pll_settings((1000 * modesetting.pixel_clock_in_khz * dac_multiplier), 96'000'000, G35Limits);
+    if (!pll_settings.has_value())
+        VERIFY_NOT_REACHED();
+    auto settings = pll_settings.value();
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "PLL settings for {} {} {} {} {}", settings.n, settings.m1, settings.m2, settings.p1, settings.p2);
+    set_dpll_registers(pll_settings.value(), dac_multiplier);
+    set_display_timings(modesetting);
+    auto address = PhysicalAddress(PCI::get_BAR2(pci_address()) & 0xfffffff0);
+    VERIFY(!address.is_null());
+    enable_output(address, width);
+
+    m_framebuffer_width = width;
+    m_framebuffer_height = height;
+    m_framebuffer_stride = width * 4;
+
+    return true;
+}
+
+void IntelNativeGraphicsAdapter::set_display_timings(const Graphics::Modesetting& modesetting)
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    VERIFY(!(read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & (1 << 31)));
+    VERIFY(!(read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & (1 << 30)));
+
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "htotal - {}, {}", (modesetting.horizontal.active - 1), (modesetting.horizontal.total - 1));
+    write_to_register(IntelGraphics::RegisterIndex::HTotalA, (modesetting.horizontal.active - 1) | (modesetting.horizontal.total - 1) << 16);
+
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "hblank - {}, {}", (modesetting.horizontal.blanking_start() - 1), (modesetting.horizontal.blanking_end() - 1));
+    write_to_register(IntelGraphics::RegisterIndex::HBlankA, (modesetting.horizontal.blanking_start() - 1) | (modesetting.horizontal.blanking_end() - 1) << 16);
+
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "hsync - {}, {}", (modesetting.horizontal.sync_start - 1), (modesetting.horizontal.sync_end - 1));
+    write_to_register(IntelGraphics::RegisterIndex::HSyncA, (modesetting.horizontal.sync_start - 1) | (modesetting.horizontal.sync_end - 1) << 16);
+
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "vtotal - {}, {}", (modesetting.vertical.active - 1), (modesetting.vertical.total - 1));
+    write_to_register(IntelGraphics::RegisterIndex::VTotalA, (modesetting.vertical.active - 1) | (modesetting.vertical.total - 1) << 16);
+
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "vblank - {}, {}", (modesetting.vertical.blanking_start() - 1), (modesetting.vertical.blanking_end() - 1));
+    write_to_register(IntelGraphics::RegisterIndex::VBlankA, (modesetting.vertical.blanking_start() - 1) | (modesetting.vertical.blanking_end() - 1) << 16);
+
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "vsync - {}, {}", (modesetting.vertical.sync_start - 1), (modesetting.vertical.sync_end - 1));
+    write_to_register(IntelGraphics::RegisterIndex::VSyncA, (modesetting.vertical.sync_start - 1) | (modesetting.vertical.sync_end - 1) << 16);
+
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "sourceSize - {}, {}", (modesetting.vertical.active - 1), (modesetting.horizontal.active - 1));
+    write_to_register(IntelGraphics::RegisterIndex::PipeASource, (modesetting.vertical.active - 1) | (modesetting.horizontal.active - 1) << 16);
+
+    IO::delay(200);
+}
+
+bool IntelNativeGraphicsAdapter::wait_for_enabled_pipe_a(size_t milliseconds_timeout) const
+{
+    size_t current_time = 0;
+    while (current_time < milliseconds_timeout) {
+        if (pipe_a_enabled())
+            return true;
+        IO::delay(1000);
+        current_time++;
+    }
+    return false;
+}
+bool IntelNativeGraphicsAdapter::wait_for_disabled_pipe_a(size_t milliseconds_timeout) const
+{
+    size_t current_time = 0;
+    while (current_time < milliseconds_timeout) {
+        if (!pipe_a_enabled())
+            return true;
+        IO::delay(1000);
+        current_time++;
+    }
+    return false;
+}
+
+bool IntelNativeGraphicsAdapter::wait_for_disabled_pipe_b(size_t milliseconds_timeout) const
+{
+    size_t current_time = 0;
+    while (current_time < milliseconds_timeout) {
+        if (!pipe_b_enabled())
+            return true;
+        IO::delay(1000);
+        current_time++;
+    }
+    return false;
+}
+
+void IntelNativeGraphicsAdapter::disable_dpll()
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    write_to_register(IntelGraphics::RegisterIndex::DPLLControlA, read_from_register(IntelGraphics::RegisterIndex::DPLLControlA) & ~0x80000000);
+    write_to_register(IntelGraphics::RegisterIndex::DPLLControlB, read_from_register(IntelGraphics::RegisterIndex::DPLLControlB) & ~0x80000000);
+}
+
+void IntelNativeGraphicsAdapter::disable_pipe_a()
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    write_to_register(IntelGraphics::RegisterIndex::PipeAConf, read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & ~0x80000000);
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe A");
+    wait_for_disabled_pipe_a(100);
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe A - done.");
+}
+
+void IntelNativeGraphicsAdapter::disable_pipe_b()
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    write_to_register(IntelGraphics::RegisterIndex::PipeAConf, read_from_register(IntelGraphics::RegisterIndex::PipeBConf) & ~0x80000000);
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe B");
+    wait_for_disabled_pipe_b(100);
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe B - done.");
+}
+
+void IntelNativeGraphicsAdapter::set_gmbus_default_rate()
+{
+    // FIXME: Verify GMBUS Rate Select is set only when GMBUS is idle
+    VERIFY(m_control_lock.is_locked());
+    // Set the rate to 100KHz
+    write_to_register(IntelGraphics::RegisterIndex::GMBusClock, read_from_register(IntelGraphics::RegisterIndex::GMBusClock) & ~(0b111 << 8));
+}
+
+void IntelNativeGraphicsAdapter::enable_pipe_a()
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    VERIFY(!(read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & (1 << 31)));
+    VERIFY(!(read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & (1 << 30)));
+    write_to_register(IntelGraphics::RegisterIndex::PipeAConf, read_from_register(IntelGraphics::RegisterIndex::PipeAConf) | 0x80000000);
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "enabling Pipe A");
+    // FIXME: Seems like my video card is buggy and doesn't set the enabled bit (bit 30)!!
+    wait_for_enabled_pipe_a(100);
+    dbgln_if(INTEL_GRAPHICS_DEBUG, "enabling Pipe A - done.");
+}
+
+void IntelNativeGraphicsAdapter::enable_primary_plane(PhysicalAddress fb_address, size_t width)
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    VERIFY(((width * 4) % 64 == 0));
+
+    write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneAStride, width * 4);
+    write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneALinearOffset, 0);
+    write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneASurface, fb_address.get());
+
+    // FIXME: Serenity uses BGR 32 bit pixel format, but maybe we should try to determine it somehow!
+    write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneAControl, (read_from_register(IntelGraphics::RegisterIndex::DisplayPlaneAControl) & (~(0b1111 << 26))) | (0b0110 << 26) | (1 << 31));
+}
+
+void IntelNativeGraphicsAdapter::set_dpll_registers(const PLLSettings& settings, size_t dac_multiplier)
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    write_to_register(IntelGraphics::RegisterIndex::DPLLDivisorA0, (settings.m2 - 2) | ((settings.m1 - 2) << 8) | ((settings.n - 2) << 16));
+    write_to_register(IntelGraphics::RegisterIndex::DPLLDivisorA1, (settings.m2 - 2) | ((settings.m1 - 2) << 8) | ((settings.n - 2) << 16));
+
+    write_to_register(IntelGraphics::RegisterIndex::DPLLControlA, read_from_register(IntelGraphics::RegisterIndex::DPLLControlA) & ~0x80000000);
+
+    IO::delay(200);
+
+    write_to_register(IntelGraphics::RegisterIndex::DPLLControlA, (6 << 9) | (settings.p1) << 16 | (1 << 26) | (1 << 28) | (1 << 31));
+    write_to_register(IntelGraphics::RegisterIndex::DPLLMultiplierA, (dac_multiplier - 1) | ((dac_multiplier - 1) << 8));
+
+    // The specification says we should wait (at least) about 150 microseconds
+    // after enabling the DPLL to allow the clock to stabilize
+    IO::delay(200);
+    VERIFY(read_from_register(IntelGraphics::RegisterIndex::DPLLControlA) & (1 << 31));
+}
+
+void IntelNativeGraphicsAdapter::set_gmbus_pin_pair(GMBusPinPair pin_pair)
+{
+    // FIXME: Verify GMBUS is idle
+    VERIFY(m_control_lock.is_locked());
+    write_to_register(IntelGraphics::RegisterIndex::GMBusClock, (read_from_register(IntelGraphics::RegisterIndex::GMBusClock) & (~0b111)) | (pin_pair & 0b111));
+}
+
+void IntelNativeGraphicsAdapter::disable_dac_output()
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    write_to_register(IntelGraphics::RegisterIndex::AnalogDisplayPort, 0b11 << 10);
+}
+
+void IntelNativeGraphicsAdapter::enable_dac_output()
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    write_to_register(IntelGraphics::RegisterIndex::AnalogDisplayPort, (read_from_register(IntelGraphics::RegisterIndex::AnalogDisplayPort) & (~(0b11 << 10))) | 0x80000000);
+}
+
+void IntelNativeGraphicsAdapter::disable_vga_emulation()
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    write_to_register(IntelGraphics::RegisterIndex::VGADisplayPlaneControl, (read_from_register(IntelGraphics::RegisterIndex::VGADisplayPlaneControl) & (~(1 << 30))) | 0x80000000);
+}
+
+void IntelNativeGraphicsAdapter::disable_all_planes()
+{
+    VERIFY(m_control_lock.is_locked());
+    VERIFY(m_modeset_lock.is_locked());
+    write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneAControl, read_from_register(IntelGraphics::RegisterIndex::DisplayPlaneAControl) & ~(1 << 31));
+}
+
+void IntelNativeGraphicsAdapter::initialize_framebuffer_devices()
+{
+    auto address = PhysicalAddress(PCI::get_BAR2(pci_address()) & 0xfffffff0);
+    VERIFY(!address.is_null());
+    VERIFY(m_framebuffer_stride != 0);
+    VERIFY(m_framebuffer_height != 0);
+    VERIFY(m_framebuffer_width != 0);
+    m_framebuffer = m_framebuffer = RawFramebufferDevice::create(*this, address, m_framebuffer_stride, m_framebuffer_width, m_framebuffer_height);
+}
+}

+ 180 - 0
Kernel/Graphics/IntelNativeGraphicsAdapter.h

@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <Kernel/Graphics/Definitions.h>
+#include <Kernel/Graphics/GraphicsDevice.h>
+#include <Kernel/Graphics/RawFramebufferDevice.h>
+#include <Kernel/PCI/DeviceController.h>
+#include <Kernel/PhysicalAddress.h>
+
+namespace Kernel {
+
+namespace IntelGraphics {
+
+enum RegisterIndex {
+    PipeAConf = 0x70008,
+    PipeBConf = 0x71008,
+    GMBusData = 0x510C,
+    GMBusStatus = 0x5108,
+    GMBusCommand = 0x5104,
+    GMBusClock = 0x5100,
+    DisplayPlaneAControl = 0x70180,
+    DisplayPlaneALinearOffset = 0x70184,
+    DisplayPlaneAStride = 0x70188,
+    DisplayPlaneASurface = 0x7019C,
+    DPLLDivisorA0 = 0x6040,
+    DPLLDivisorA1 = 0x6044,
+    DPLLControlA = 0x6014,
+    DPLLControlB = 0x6018,
+    DPLLMultiplierA = 0x601C,
+    HTotalA = 0x60000,
+    HBlankA = 0x60004,
+    HSyncA = 0x60008,
+    VTotalA = 0x6000C,
+    VBlankA = 0x60010,
+    VSyncA = 0x60014,
+    PipeASource = 0x6001C,
+    AnalogDisplayPort = 0x61100,
+    VGADisplayPlaneControl = 0x71400,
+};
+}
+
+class IntelNativeGraphicsAdapter final
+    : public GraphicsDevice
+    , public PCI::DeviceController {
+    AK_MAKE_ETERNAL
+public:
+    struct PLLSettings {
+        bool is_valid() const { return (n != 0 && m1 != 0 && m2 != 0 && p1 != 0 && p2 != 0); }
+        u64 compute_dot_clock(u64 refclock) const
+        {
+            return (refclock * (5 * (m1) + (m2)) / (n)) / (p1 * p2);
+        }
+
+        u64 compute_vco(u64 refclock) const
+        {
+            return refclock * (5 * (m1) + (m2)) / n;
+        }
+
+        u64 compute_m() const
+        {
+            return 5 * (m1) + (m2);
+        }
+
+        u64 compute_p() const
+        {
+            return p1 * p2;
+        }
+        u64 n { 0 };
+        u64 m1 { 0 };
+        u64 m2 { 0 };
+        u64 p1 { 0 };
+        u64 p2 { 0 };
+    };
+    struct PLLParameterLimit {
+        size_t min, max;
+    };
+    struct PLLMaxSettings {
+        PLLParameterLimit dot_clock, vco, n, m, m1, m2, p, p1, p2;
+    };
+
+private:
+    enum GMBusPinPair : u8 {
+        None = 0,
+        DedicatedControl = 1,
+        DedicatedAnalog = 0b10,
+        IntegratedDigital = 0b11,
+        sDVO = 0b101,
+        Dconnector = 0b111,
+    };
+
+    enum class GMBusStatus {
+        TransactionCompletion,
+        HardwareReady
+    };
+
+    enum GMBusCycle {
+        Wait = 1,
+        Stop = 4,
+    };
+
+public:
+    static RefPtr<IntelNativeGraphicsAdapter> initialize(PCI::Address);
+
+private:
+    explicit IntelNativeGraphicsAdapter(PCI::Address);
+
+    void write_to_register(IntelGraphics::RegisterIndex, u32 value) const;
+    u32 read_from_register(IntelGraphics::RegisterIndex) const;
+
+    // ^GraphicsDevice
+    virtual void initialize_framebuffer_devices() override;
+    virtual Type type() const override { return Type::VGACompatible; }
+
+    bool pipe_a_enabled() const;
+    bool pipe_b_enabled() const;
+
+    bool is_resolution_valid(size_t width, size_t height);
+
+    bool set_crt_resolution(size_t width, size_t height);
+
+    void disable_output();
+    void enable_output(PhysicalAddress fb_address, size_t width);
+
+    void disable_vga_emulation();
+
+    void disable_dac_output();
+    void enable_dac_output();
+
+    void disable_all_planes();
+    void disable_pipe_a();
+    void disable_pipe_b();
+    void disable_dpll();
+
+    void set_dpll_registers(const PLLSettings&, size_t dac_multiplier);
+    void set_display_timings(const Graphics::Modesetting&);
+    void enable_pipe_a();
+    void set_framebuffer_parameters(size_t, size_t);
+    void enable_primary_plane(PhysicalAddress fb_address, size_t stride);
+
+    bool wait_for_enabled_pipe_a(size_t milliseconds_timeout) const;
+    bool wait_for_disabled_pipe_a(size_t milliseconds_timeout) const;
+    bool wait_for_disabled_pipe_b(size_t milliseconds_timeout) const;
+
+    void set_gmbus_default_rate();
+    void set_gmbus_pin_pair(GMBusPinPair pin_pair);
+
+    // FIXME: It would be better if we generalize the I2C access later on
+    void gmbus_read_edid();
+    void gmbus_write(unsigned address, u32 byte);
+    void gmbus_read(unsigned address, u8* buf, size_t length);
+    bool gmbus_wait_for(GMBusStatus desired_status, Optional<size_t> milliseconds_timeout);
+
+    Optional<PLLSettings> create_pll_settings(u64 target_frequency, u64 reference_clock, const PLLMaxSettings&);
+
+    SpinLock<u8> m_control_lock;
+    SpinLock<u8> m_modeset_lock;
+    mutable SpinLock<u8> m_registers_lock;
+
+    Graphics::VideoInfoBlock m_crt_edid;
+    const PhysicalAddress m_registers;
+    const PhysicalAddress m_framebuffer_addr;
+
+    OwnPtr<Region> m_registers_region;
+
+    size_t m_framebuffer_width { 0 };
+    size_t m_framebuffer_height { 0 };
+    size_t m_framebuffer_stride { 0 };
+
+protected:
+    RefPtr<RawFramebufferDevice> m_framebuffer;
+};
+
+}

+ 1 - 0
Meta/CMake/all_the_debug_macros.cmake

@@ -136,6 +136,7 @@ set(HTTPSJOB_DEBUG ON)
 set(ICMP_DEBUG ON)
 set(ICO_DEBUG ON)
 set(IPV4_DEBUG ON)
+set(INTEL_GRAPHICS_DEBUG ON)
 set(IRC_DEBUG ON)
 set(KEYBOARD_DEBUG ON)
 set(LEXER_DEBUG ON)