Kernel/Graphics: Introduce the IntelDisplayConnectorGroup class

In the real world, graphics hardware tend to have multiple display
connectors. However, usually the connectors share one register space but
still keeping different PLL timings and display lanes.
This new class should represent a group of multiple display connectors
working together in the same Intel graphics adapter. This opens an
opportunity to abstract the interface so we could support future Intel
iGPU generations.

This is also a preparation before the driver can support newer devices
and utilize their capabilities.
The mentioned preparation is applied in a these aspects:
1. The code is splitted into more classes to adjust to future expansion.
2 classes are introduced: IntelDisplayPlane and IntelDisplayTranscoder,
so the IntelDisplayPlane controls the plane registers and second class
controls the pipeline (transcoder, encoder) registers. On gen4 it's not
really useful because there are probably one plane and one encoder to
care about, but in future generations, there are likely to be multiple
transcoders and planes to accommodate multi head support.
2. The set_edid_bytes method in the DisplayConnector class can now be
told to not assume the provided EDID bytes are always invalid. Therefore
it can refrain from printing error messages if this flag parameter is
true. This is useful for supporting real hardware situation when on boot
not all ports are connected to a monitor, which can result in floating
bus condition (essentially all the bytes we read are 0xFF).
3. An IntelNativeDisplayConnector could now be set to flag other types
of connections such as eDP (embedded DisplayPort), Analog output, etc.
This is important because on the Intel gen4 graphics we could assume to
have one analog output connector, but on future generations this is very
likely to not be the case, as there might be no VGA outputs, but rather
only an eDP connector which is converted to VGA by a design choice of
the motherboard manufacturer.
4. Add ConnectorIndex to IntelNativeDisplayConnector class - Currently
this is used to verify we always handle the correct connector when doing
modesetting.
Later, it will be used to locate special settings needed when handling
connector requests.
5. Prepare to support more types of display planes. For example, the
Intel Skylake register set for display planes is a bit different, so
let's ensure we can properly support it in the near future.
This commit is contained in:
Liav A 2022-05-05 13:25:51 +03:00 committed by Andrew Kaster
parent e2dde64628
commit 2def16a3d2
Notes: sideshowbarker 2024-07-17 00:00:11 +09:00
19 changed files with 1211 additions and 689 deletions

View file

@ -76,6 +76,11 @@ set(KERNEL_SOURCES
Graphics/Generic/DisplayConnector.cpp
Graphics/GraphicsManagement.cpp
Graphics/Intel/Auxiliary/GMBusConnector.cpp
Graphics/Intel/Plane/DisplayPlane.cpp
Graphics/Intel/Plane/G33DisplayPlane.cpp
Graphics/Intel/Transcoder/AnalogDisplayTranscoder.cpp
Graphics/Intel/Transcoder/DisplayTranscoder.cpp
Graphics/Intel/DisplayConnectorGroup.cpp
Graphics/Intel/NativeDisplayConnector.cpp
Graphics/Intel/NativeGraphicsAdapter.cpp
Graphics/VMWare/Console.cpp

View file

@ -10,27 +10,6 @@
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;
};
// Note: Address 0x50 is expected to be the DDC2 (EDID) i2c address.
static constexpr u8 ddc2_i2c_address = 0x50;

View file

@ -233,22 +233,24 @@ ErrorOr<void> DisplayConnector::initialize_edid_for_generic_monitor(Optional<Arr
return {};
}
void DisplayConnector::set_edid_bytes(Array<u8, 128> const& edid_bytes)
void DisplayConnector::set_edid_bytes(Array<u8, 128> const& edid_bytes, bool might_be_invalid)
{
memcpy((u8*)m_edid_bytes, edid_bytes.data(), sizeof(m_edid_bytes));
if (auto parsed_edid = EDID::Parser::from_bytes({ m_edid_bytes, sizeof(m_edid_bytes) }); !parsed_edid.is_error()) {
m_edid_parser = parsed_edid.release_value();
m_edid_valid = true;
} else {
dmesgln("DisplayConnector: Print offending EDID");
for (size_t x = 0; x < 128; x = x + 16) {
dmesgln("{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
m_edid_bytes[x], m_edid_bytes[x + 1], m_edid_bytes[x + 2], m_edid_bytes[x + 3],
m_edid_bytes[x + 4], m_edid_bytes[x + 5], m_edid_bytes[x + 6], m_edid_bytes[x + 7],
m_edid_bytes[x + 8], m_edid_bytes[x + 9], m_edid_bytes[x + 10], m_edid_bytes[x + 11],
m_edid_bytes[x + 12], m_edid_bytes[x + 13], m_edid_bytes[x + 14], m_edid_bytes[x + 15]);
if (!might_be_invalid) {
dmesgln("DisplayConnector: Print offending EDID");
for (size_t x = 0; x < 128; x = x + 16) {
dmesgln("{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
m_edid_bytes[x], m_edid_bytes[x + 1], m_edid_bytes[x + 2], m_edid_bytes[x + 3],
m_edid_bytes[x + 4], m_edid_bytes[x + 5], m_edid_bytes[x + 6], m_edid_bytes[x + 7],
m_edid_bytes[x + 8], m_edid_bytes[x + 9], m_edid_bytes[x + 10], m_edid_bytes[x + 11],
m_edid_bytes[x + 12], m_edid_bytes[x + 13], m_edid_bytes[x + 14], m_edid_bytes[x + 15]);
}
dmesgln("DisplayConnector: Parsing EDID failed: {}", parsed_edid.error());
}
dmesgln("DisplayConnector: Parsing EDID failed: {}", parsed_edid.error());
}
}

View file

@ -103,7 +103,7 @@ public:
Memory::Region const& framebuffer_region() const { return *m_framebuffer_region; }
protected:
void set_edid_bytes(Array<u8, 128> const& edid_bytes);
void set_edid_bytes(Array<u8, 128> const& edid_bytes, bool might_be_invalid = false);
DisplayConnector(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, bool enable_write_combine_optimization);
DisplayConnector(size_t framebuffer_resource_size, bool enable_write_combine_optimization);

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Kernel::IntelGraphics {
enum class GlobalGenerationRegister {
PipeAConf = 0x70008,
PipeBConf = 0x71008,
};
struct PLLSettings;
struct PLLParameterLimit {
size_t min, max;
};
struct PLLMaxSettings {
PLLParameterLimit dot_clock, vco, n, m, m1, m2, p, p1, p2;
};
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 };
};
}

View file

@ -0,0 +1,464 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/Delay.h>
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Debug.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Graphics/Console/ContiguousFramebufferConsole.h>
#include <Kernel/Graphics/GraphicsManagement.h>
#include <Kernel/Graphics/Intel/DisplayConnectorGroup.h>
#include <Kernel/Graphics/Intel/Plane/G33DisplayPlane.h>
#include <Kernel/Graphics/Intel/Transcoder/AnalogDisplayTranscoder.h>
#include <Kernel/Memory/Region.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
namespace IntelGraphics {
static constexpr 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
};
}
static bool check_pll_settings(IntelGraphics::PLLSettings const& settings, size_t reference_clock, IntelGraphics::PLLMaxSettings const& 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<IntelGraphics::PLLSettings> IntelDisplayConnectorGroup::create_pll_settings(u64 target_frequency, u64 reference_clock, IntelGraphics::PLLMaxSettings const& limits)
{
IntelGraphics::PLLSettings settings;
IntelGraphics::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 {};
}
ErrorOr<NonnullLockRefPtr<IntelDisplayConnectorGroup>> IntelDisplayConnectorGroup::try_create(Badge<IntelNativeGraphicsAdapter>, Generation generation, MMIORegion const& first_region, MMIORegion const& second_region)
{
auto registers_region = TRY(MM.allocate_kernel_region(first_region.pci_bar_paddr, first_region.pci_bar_space_length, "Intel Native Graphics Registers"sv, Memory::Region::Access::ReadWrite));
// NOTE: 0x5100 is the offset of the start of the GMBus registers
auto gmbus_connector = TRY(GMBusConnector::create_with_physical_address(first_region.pci_bar_paddr.offset(0x5100)));
auto connector_group = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) IntelDisplayConnectorGroup(generation, move(gmbus_connector), move(registers_region), first_region, second_region)));
TRY(connector_group->initialize_connectors());
return connector_group;
}
IntelDisplayConnectorGroup::IntelDisplayConnectorGroup(Generation generation, NonnullOwnPtr<GMBusConnector> gmbus_connector, NonnullOwnPtr<Memory::Region> registers_region, MMIORegion const& first_region, MMIORegion const& second_region)
: m_mmio_first_region(first_region)
, m_mmio_second_region(second_region)
, m_assigned_mmio_registers_region(m_mmio_first_region)
, m_generation(generation)
, m_registers_region(move(registers_region))
, m_gmbus_connector(move(gmbus_connector))
{
}
ErrorOr<void> IntelDisplayConnectorGroup::initialize_gen4_connectors()
{
// NOTE: Just assume we will need one Gen4 "transcoder"
// NOTE: Main block of registers starting at HorizontalTotalA register (0x60000)
auto transcoder_registers_paddr = m_mmio_first_region.pci_bar_paddr.offset(0x60000);
// NOTE: DPLL registers starting at DPLLDivisorA0 register (0x6040)
auto dpll_registers_paddr = m_mmio_first_region.pci_bar_paddr.offset(0x6040);
// NOTE: DPLL A control registers starting at 0x6014 (DPLL A Control register),
// DPLL A Multiplier is at 0x601C, between them (at 0x6018) there is the DPLL B Control register.
auto dpll_control_registers_paddr = m_mmio_first_region.pci_bar_paddr.offset(0x6014);
m_transcoders[0] = TRY(IntelAnalogDisplayTranscoder::create_with_physical_addresses(transcoder_registers_paddr, dpll_registers_paddr, dpll_control_registers_paddr));
m_planes[0] = TRY(IntelG33DisplayPlane::create_with_physical_address(m_mmio_first_region.pci_bar_paddr.offset(0x70180)));
Array<u8, 128> crt_edid_bytes {};
{
SpinlockLocker control_lock(m_control_lock);
TRY(m_gmbus_connector->write(Graphics::ddc2_i2c_address, 0));
TRY(m_gmbus_connector->read(Graphics::ddc2_i2c_address, crt_edid_bytes.data(), crt_edid_bytes.size()));
}
m_connectors[0] = TRY(IntelNativeDisplayConnector::try_create_with_display_connector_group(*this, IntelNativeDisplayConnector::ConnectorIndex::PortA, IntelNativeDisplayConnector::Type::Analog, m_mmio_second_region.pci_bar_paddr, m_mmio_second_region.pci_bar_space_length));
m_connectors[0]->set_edid_bytes({}, crt_edid_bytes);
return {};
}
ErrorOr<void> IntelDisplayConnectorGroup::initialize_connectors()
{
// NOTE: Intel Graphics Generation 4 is pretty ancient beast, and we should not
// assume we can find a VBT for it. Just initialize the (assumed) CRT connector and be done with it.
if (m_generation == Generation::Gen4) {
TRY(initialize_gen4_connectors());
} else {
VERIFY_NOT_REACHED();
}
for (size_t connector_index = 0; connector_index < m_connectors.size(); connector_index++) {
if (!m_connectors[connector_index])
continue;
if (!m_connectors[connector_index]->m_edid_valid)
continue;
TRY(m_connectors[connector_index]->set_safe_mode_setting());
TRY(m_connectors[connector_index]->create_attached_framebuffer_console({}));
}
return {};
}
ErrorOr<void> IntelDisplayConnectorGroup::set_safe_mode_setting(Badge<IntelNativeDisplayConnector>, IntelNativeDisplayConnector& connector)
{
VERIFY(connector.m_modeset_lock.is_locked());
if (!connector.m_edid_parser.has_value())
return Error::from_errno(ENOTSUP);
if (!connector.m_edid_parser.value().detailed_timing(0).has_value())
return Error::from_errno(ENOTSUP);
auto details = connector.m_edid_parser.value().detailed_timing(0).release_value();
DisplayConnector::ModeSetting modesetting {
// Note: We assume that we always use 32 bit framebuffers.
.horizontal_stride = details.horizontal_addressable_pixels() * sizeof(u32),
.pixel_clock_in_khz = details.pixel_clock_khz(),
.horizontal_active = details.horizontal_addressable_pixels(),
.horizontal_front_porch_pixels = details.horizontal_front_porch_pixels(),
.horizontal_sync_time_pixels = details.horizontal_sync_pulse_width_pixels(),
.horizontal_blank_pixels = details.horizontal_blanking_pixels(),
.vertical_active = details.vertical_addressable_lines(),
.vertical_front_porch_lines = details.vertical_front_porch_lines(),
.vertical_sync_time_lines = details.vertical_sync_pulse_width_lines(),
.vertical_blank_lines = details.vertical_blanking_lines(),
.horizontal_offset = 0,
.vertical_offset = 0,
};
return set_mode_setting(connector, modesetting);
}
ErrorOr<void> IntelDisplayConnectorGroup::set_mode_setting(Badge<IntelNativeDisplayConnector>, IntelNativeDisplayConnector& connector, DisplayConnector::ModeSetting const& mode_setting)
{
return set_mode_setting(connector, mode_setting);
}
ErrorOr<void> IntelDisplayConnectorGroup::set_mode_setting(IntelNativeDisplayConnector& connector, DisplayConnector::ModeSetting const& mode_setting)
{
VERIFY(connector.m_modeset_lock.is_locked());
VERIFY(to_underlying(connector.connector_index()) < m_connectors.size());
VERIFY(&connector == m_connectors[to_underlying(connector.connector_index())].ptr());
DisplayConnector::ModeSetting actual_mode_setting = mode_setting;
actual_mode_setting.horizontal_stride = actual_mode_setting.horizontal_active * sizeof(u32);
VERIFY(actual_mode_setting.horizontal_stride != 0);
if (m_generation == Generation::Gen4) {
TRY(set_gen4_mode_setting(connector, actual_mode_setting));
} else {
VERIFY_NOT_REACHED();
}
connector.m_current_mode_setting = actual_mode_setting;
if (!connector.m_framebuffer_console.is_null())
static_cast<Graphics::GenericFramebufferConsoleImpl*>(connector.m_framebuffer_console.ptr())->set_resolution(actual_mode_setting.horizontal_active, actual_mode_setting.vertical_active, actual_mode_setting.horizontal_stride);
return {};
}
ErrorOr<void> IntelDisplayConnectorGroup::set_gen4_mode_setting(IntelNativeDisplayConnector& connector, DisplayConnector::ModeSetting const& mode_setting)
{
VERIFY(connector.m_modeset_lock.is_locked());
SpinlockLocker control_lock(m_control_lock);
SpinlockLocker modeset_lock(m_modeset_lock);
if (!set_crt_resolution(mode_setting))
return Error::from_errno(ENOTSUP);
return {};
}
void IntelDisplayConnectorGroup::enable_vga_plane()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
}
StringView IntelDisplayConnectorGroup::convert_analog_output_register_to_string(AnalogOutputRegisterOffset index) const
{
switch (index) {
case AnalogOutputRegisterOffset::AnalogDisplayPort:
return "AnalogDisplayPort"sv;
case AnalogOutputRegisterOffset::VGADisplayPlaneControl:
return "VGADisplayPlaneControl"sv;
default:
VERIFY_NOT_REACHED();
}
}
void IntelDisplayConnectorGroup::write_to_general_register(RegisterOffset offset, u32 value)
{
VERIFY(m_control_lock.is_locked());
SpinlockLocker lock(m_registers_lock);
auto* reg = (u32 volatile*)m_registers_region->vaddr().offset(offset.value()).as_ptr();
*reg = value;
}
u32 IntelDisplayConnectorGroup::read_from_general_register(RegisterOffset offset) const
{
VERIFY(m_control_lock.is_locked());
SpinlockLocker lock(m_registers_lock);
auto* reg = (u32 volatile*)m_registers_region->vaddr().offset(offset.value()).as_ptr();
u32 value = *reg;
return value;
}
void IntelDisplayConnectorGroup::write_to_analog_output_register(AnalogOutputRegisterOffset index, u32 value)
{
dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Graphics Display Connector:: Write to {} value of {:x}", convert_analog_output_register_to_string(index), value);
write_to_general_register(to_underlying(index), value);
}
u32 IntelDisplayConnectorGroup::read_from_analog_output_register(AnalogOutputRegisterOffset index) const
{
u32 value = read_from_general_register(to_underlying(index));
dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Graphics Display Connector: Read from {} value of {:x}", convert_analog_output_register_to_string(index), value);
return value;
}
void IntelDisplayConnectorGroup::write_to_global_generation_register(IntelGraphics::GlobalGenerationRegister index, u32 value)
{
write_to_general_register(to_underlying(index), value);
}
u32 IntelDisplayConnectorGroup::read_from_global_generation_register(IntelGraphics::GlobalGenerationRegister index) const
{
u32 value = read_from_general_register(to_underlying(index));
return value;
}
bool IntelDisplayConnectorGroup::pipe_a_enabled() const
{
VERIFY(m_control_lock.is_locked());
return read_from_global_generation_register(IntelGraphics::GlobalGenerationRegister::PipeAConf) & (1 << 30);
}
bool IntelDisplayConnectorGroup::pipe_b_enabled() const
{
VERIFY(m_control_lock.is_locked());
return read_from_global_generation_register(IntelGraphics::GlobalGenerationRegister::PipeBConf) & (1 << 30);
}
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;
}
}
bool IntelDisplayConnectorGroup::set_crt_resolution(DisplayConnector::ModeSetting const& mode_setting)
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
// Note: Just in case we still allow access to VGA IO ports, disable it now.
GraphicsManagement::the().disable_vga_emulation_access_permanently();
auto dac_multiplier = compute_dac_multiplier(mode_setting.pixel_clock_in_khz);
auto pll_settings = create_pll_settings((1000 * mode_setting.pixel_clock_in_khz * dac_multiplier), 96'000'000, IntelGraphics::G35Limits);
if (!pll_settings.has_value())
return false;
auto settings = pll_settings.value();
disable_dac_output();
MUST(m_planes[0]->disable({}));
disable_pipe_a();
disable_pipe_b();
MUST(m_transcoders[0]->disable_dpll({}));
disable_vga_emulation();
dbgln_if(INTEL_GRAPHICS_DEBUG, "PLL settings for {} {} {} {} {}", settings.n, settings.m1, settings.m2, settings.p1, settings.p2);
MUST(m_transcoders[0]->set_dpll_settings({}, settings, dac_multiplier));
MUST(m_transcoders[0]->disable_dpll({}));
MUST(m_transcoders[0]->enable_dpll_without_vga({}));
MUST(m_transcoders[0]->set_mode_setting_timings({}, mode_setting));
VERIFY(!pipe_a_enabled());
enable_pipe_a();
MUST(m_planes[0]->set_plane_settings({}, m_mmio_second_region.pci_bar_paddr, IntelDisplayPlane::PipeSelect::PipeA, mode_setting.horizontal_active));
MUST(m_planes[0]->enable({}));
enable_dac_output();
return true;
}
bool IntelDisplayConnectorGroup::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;
microseconds_delay(1000);
current_time++;
}
return false;
}
bool IntelDisplayConnectorGroup::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;
microseconds_delay(1000);
current_time++;
}
return false;
}
bool IntelDisplayConnectorGroup::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;
microseconds_delay(1000);
current_time++;
}
return false;
}
void IntelDisplayConnectorGroup::disable_pipe_a()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_global_generation_register(IntelGraphics::GlobalGenerationRegister::PipeAConf, 0);
dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe A");
wait_for_disabled_pipe_a(100);
dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe A - done.");
}
void IntelDisplayConnectorGroup::disable_pipe_b()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_global_generation_register(IntelGraphics::GlobalGenerationRegister::PipeAConf, 0);
dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe B");
wait_for_disabled_pipe_b(100);
dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe B - done.");
}
void IntelDisplayConnectorGroup::enable_pipe_a()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
VERIFY(!(read_from_global_generation_register(IntelGraphics::GlobalGenerationRegister::PipeAConf) & (1 << 31)));
VERIFY(!(read_from_global_generation_register(IntelGraphics::GlobalGenerationRegister::PipeAConf) & (1 << 30)));
write_to_global_generation_register(IntelGraphics::GlobalGenerationRegister::PipeAConf, (1 << 31) | (1 << 24));
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 IntelDisplayConnectorGroup::disable_dac_output()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_analog_output_register(AnalogOutputRegisterOffset::AnalogDisplayPort, 0b11 << 10);
}
void IntelDisplayConnectorGroup::enable_dac_output()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_analog_output_register(AnalogOutputRegisterOffset::AnalogDisplayPort, (1 << 31));
}
void IntelDisplayConnectorGroup::disable_vga_emulation()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_analog_output_register(AnalogOutputRegisterOffset::VGADisplayPlaneControl, (1 << 31));
read_from_analog_output_register(AnalogOutputRegisterOffset::VGADisplayPlaneControl);
}
}

View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefPtr.h>
#include <AK/Try.h>
#include <Kernel/Graphics/Console/GenericFramebufferConsole.h>
#include <Kernel/Graphics/Intel/Auxiliary/GMBusConnector.h>
#include <Kernel/Graphics/Intel/Definitions.h>
#include <Kernel/Graphics/Intel/NativeDisplayConnector.h>
#include <Kernel/Graphics/Intel/Plane/DisplayPlane.h>
#include <Kernel/Graphics/Intel/Transcoder/DisplayTranscoder.h>
#include <Kernel/Library/LockRefPtr.h>
#include <Kernel/Memory/TypedMapping.h>
#include <LibEDID/EDID.h>
namespace Kernel {
class IntelNativeGraphicsAdapter;
class IntelDisplayConnectorGroup : public RefCounted<IntelDisplayConnectorGroup> {
friend class IntelNativeGraphicsAdapter;
public:
enum class Generation {
Gen4,
};
struct MMIORegion {
enum class BARAssigned {
BAR0,
BAR2,
};
BARAssigned pci_bar_assigned;
PhysicalAddress pci_bar_paddr;
size_t pci_bar_space_length;
};
private:
AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, RegisterOffset);
enum class AnalogOutputRegisterOffset {
AnalogDisplayPort = 0x61100,
VGADisplayPlaneControl = 0x71400,
};
public:
static ErrorOr<NonnullLockRefPtr<IntelDisplayConnectorGroup>> try_create(Badge<IntelNativeGraphicsAdapter>, Generation, MMIORegion const&, MMIORegion const&);
ErrorOr<void> set_safe_mode_setting(Badge<IntelNativeDisplayConnector>, IntelNativeDisplayConnector&);
ErrorOr<void> set_mode_setting(Badge<IntelNativeDisplayConnector>, IntelNativeDisplayConnector&, DisplayConnector::ModeSetting const&);
private:
IntelDisplayConnectorGroup(Generation generation, NonnullOwnPtr<GMBusConnector>, NonnullOwnPtr<Memory::Region> registers_region, MMIORegion const&, MMIORegion const&);
ErrorOr<void> set_mode_setting(IntelNativeDisplayConnector&, DisplayConnector::ModeSetting const&);
StringView convert_analog_output_register_to_string(AnalogOutputRegisterOffset index) const;
void write_to_analog_output_register(AnalogOutputRegisterOffset, u32 value);
u32 read_from_analog_output_register(AnalogOutputRegisterOffset) const;
u32 read_from_global_generation_register(IntelGraphics::GlobalGenerationRegister index) const;
void write_to_global_generation_register(IntelGraphics::GlobalGenerationRegister index, u32 value);
void write_to_general_register(RegisterOffset offset, u32 value);
u32 read_from_general_register(RegisterOffset offset) const;
// DisplayConnector initialization related methods
ErrorOr<void> initialize_connectors();
ErrorOr<void> initialize_gen4_connectors();
// General Modesetting methods
ErrorOr<void> set_gen4_mode_setting(IntelNativeDisplayConnector&, DisplayConnector::ModeSetting const&);
bool pipe_a_enabled() const;
bool pipe_b_enabled() const;
bool set_crt_resolution(DisplayConnector::ModeSetting const&);
void disable_vga_emulation();
void enable_vga_plane();
void disable_dac_output();
void enable_dac_output();
void disable_pipe_a();
void disable_pipe_b();
void enable_pipe_a();
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;
Optional<IntelGraphics::PLLSettings> create_pll_settings(u64 target_frequency, u64 reference_clock, IntelGraphics::PLLMaxSettings const&);
Spinlock<LockRank::None> m_control_lock;
Spinlock<LockRank::None> m_modeset_lock;
mutable Spinlock<LockRank::None> m_registers_lock;
// Note: The linux driver specifies an enum of possible ports and there is only
// 9 ports (PORT_{A-I}). PORT_TC{1-6} are mapped to PORT_{D-I}.
Array<LockRefPtr<IntelNativeDisplayConnector>, 9> m_connectors;
Array<OwnPtr<IntelDisplayTranscoder>, 5> m_transcoders;
Array<OwnPtr<IntelDisplayPlane>, 3> m_planes;
const MMIORegion m_mmio_first_region;
const MMIORegion m_mmio_second_region;
MMIORegion const& m_assigned_mmio_registers_region;
const Generation m_generation;
NonnullOwnPtr<Memory::Region> m_registers_region;
NonnullOwnPtr<GMBusConnector> m_gmbus_connector;
};
}

View file

@ -10,194 +10,47 @@
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Graphics/Console/ContiguousFramebufferConsole.h>
#include <Kernel/Graphics/GraphicsManagement.h>
#include <Kernel/Graphics/Intel/DisplayConnectorGroup.h>
#include <Kernel/Graphics/Intel/NativeDisplayConnector.h>
#include <Kernel/Memory/Region.h>
namespace Kernel {
namespace IntelGraphics {
#define DDC2_I2C_ADDRESS 0x50
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 };
};
static constexpr 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
};
ErrorOr<NonnullLockRefPtr<IntelNativeDisplayConnector>> IntelNativeDisplayConnector::try_create_with_display_connector_group(IntelDisplayConnectorGroup const& parent_connector_group, ConnectorIndex connector_index, Type type, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size)
{
return TRY(DeviceManagement::try_create_device<IntelNativeDisplayConnector>(parent_connector_group, connector_index, type, framebuffer_address, framebuffer_resource_size));
}
static Graphics::Modesetting calculate_modesetting_from_edid(EDID::Parser& edid, size_t index)
ErrorOr<void> IntelNativeDisplayConnector::create_attached_framebuffer_console(Badge<IntelDisplayConnectorGroup>)
{
auto details = edid.detailed_timing(index).release_value();
Graphics::Modesetting mode;
VERIFY(details.pixel_clock_khz());
mode.pixel_clock_in_khz = details.pixel_clock_khz();
size_t horizontal_active = details.horizontal_addressable_pixels();
size_t horizontal_sync_offset = details.horizontal_front_porch_pixels();
mode.horizontal.active = horizontal_active;
mode.horizontal.sync_start = horizontal_active + horizontal_sync_offset;
mode.horizontal.sync_end = horizontal_active + horizontal_sync_offset + details.horizontal_sync_pulse_width_pixels();
mode.horizontal.total = horizontal_active + details.horizontal_blanking_pixels();
size_t vertical_active = details.vertical_addressable_lines();
size_t vertical_sync_offset = details.vertical_front_porch_lines();
mode.vertical.active = vertical_active;
mode.vertical.sync_start = vertical_active + vertical_sync_offset;
mode.vertical.sync_end = vertical_active + vertical_sync_offset + details.vertical_sync_pulse_width_lines();
mode.vertical.total = vertical_active + details.vertical_blanking_lines();
return mode;
}
static bool check_pll_settings(IntelGraphics::PLLSettings const& settings, size_t reference_clock, IntelGraphics::PLLMaxSettings const& limits)
{
if (settings.n < limits.n.min || settings.n > limits.n.max) {
dbgln_if(INTEL_GRAPHICS_DEBUG, "N is invalid {}", settings.n);
return false;
size_t width = 0;
size_t height = 0;
size_t pitch = 0;
{
SpinlockLocker control_locker(m_control_lock);
SpinlockLocker mode_set_locker(m_modeset_lock);
width = m_current_mode_setting.horizontal_active;
height = m_current_mode_setting.vertical_active;
pitch = m_current_mode_setting.horizontal_stride;
}
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<IntelGraphics::PLLSettings> IntelNativeDisplayConnector::create_pll_settings(u64 target_frequency, u64 reference_clock, IntelGraphics::PLLMaxSettings const& limits)
{
IntelGraphics::PLLSettings settings;
IntelGraphics::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;
m_framebuffer_console = Graphics::ContiguousFramebufferConsole::initialize(m_framebuffer_address.value(), width, height, pitch);
GraphicsManagement::the().set_console(*m_framebuffer_console);
return {};
}
ErrorOr<NonnullLockRefPtr<IntelNativeDisplayConnector>> IntelNativeDisplayConnector::try_create(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, PhysicalAddress registers_region_address, size_t registers_region_length)
IntelNativeDisplayConnector::IntelNativeDisplayConnector(IntelDisplayConnectorGroup const& parent_connector_group, ConnectorIndex connector_index, Type type, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size)
: DisplayConnector(framebuffer_address, framebuffer_resource_size, true)
, m_type(type)
, m_connector_index(connector_index)
, m_parent_connector_group(parent_connector_group)
{
auto registers_region = TRY(MM.allocate_kernel_region(PhysicalAddress(registers_region_address), registers_region_length, "Intel Native Graphics Registers"sv, Memory::Region::Access::ReadWrite));
// FIXME: Try to put the address as parameter to this function to allow creating this DisplayConnector for many generations...
auto gmbus_connector = TRY(GMBusConnector::create_with_physical_address(registers_region_address.offset(to_underlying(IntelGraphics::RegisterIndex::GMBusClock))));
auto connector = TRY(DeviceManagement::try_create_device<IntelNativeDisplayConnector>(framebuffer_address, framebuffer_resource_size, move(gmbus_connector), move(registers_region)));
TRY(connector->initialize_gmbus_settings_and_read_edid());
// Note: This is very important to set the resolution to something safe so we
// can create a framebuffer console with valid resolution.
{
SpinlockLocker control_lock(connector->m_control_lock);
TRY(connector->set_safe_mode_setting());
}
TRY(connector->create_attached_framebuffer_console());
return connector;
}
ErrorOr<void> IntelNativeDisplayConnector::initialize_gmbus_settings_and_read_edid()
void IntelNativeDisplayConnector::set_edid_bytes(Badge<IntelDisplayConnectorGroup>, Array<u8, 128> const& raw_bytes)
{
gmbus_read_edid();
return {};
// Note: The provided EDID might be invalid (because there's no attached monitor)
// Therefore, set might_be_invalid to true to indicate that.
DisplayConnector::set_edid_bytes(raw_bytes, true);
}
ErrorOr<void> IntelNativeDisplayConnector::set_y_offset(size_t)
@ -210,6 +63,12 @@ ErrorOr<void> IntelNativeDisplayConnector::unblank()
return Error::from_errno(ENOTIMPL);
}
ErrorOr<void> IntelNativeDisplayConnector::set_safe_mode_setting()
{
SpinlockLocker locker(m_modeset_lock);
return m_parent_connector_group->set_safe_mode_setting({}, *this);
}
void IntelNativeDisplayConnector::enable_console()
{
VERIFY(m_control_lock.is_locked());
@ -229,398 +88,9 @@ ErrorOr<void> IntelNativeDisplayConnector::flush_first_surface()
return Error::from_errno(ENOTSUP);
}
ErrorOr<void> IntelNativeDisplayConnector::create_attached_framebuffer_console()
{
m_framebuffer_console = Graphics::ContiguousFramebufferConsole::initialize(m_framebuffer_address.value(), m_current_mode_setting.horizontal_active, m_current_mode_setting.vertical_active, m_current_mode_setting.horizontal_stride);
GraphicsManagement::the().set_console(*m_framebuffer_console);
return {};
}
IntelNativeDisplayConnector::IntelNativeDisplayConnector(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, NonnullOwnPtr<GMBusConnector> gmbus_connector, NonnullOwnPtr<Memory::Region> registers_region)
: DisplayConnector(framebuffer_address, framebuffer_resource_size, true)
, m_registers_region(move(registers_region))
, m_gmbus_connector(move(gmbus_connector))
{
}
ErrorOr<void> IntelNativeDisplayConnector::set_mode_setting(DisplayConnector::ModeSetting const&)
{
return Error::from_errno(ENOTIMPL);
}
ErrorOr<void> IntelNativeDisplayConnector::set_safe_mode_setting()
{
set_safe_crt_resolution();
return {};
}
void IntelNativeDisplayConnector::enable_vga_plane()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
}
[[maybe_unused]] static StringView convert_register_index_to_string(IntelGraphics::RegisterIndex index)
{
switch (index) {
case IntelGraphics::RegisterIndex::PipeAConf:
return "PipeAConf"sv;
case IntelGraphics::RegisterIndex::PipeBConf:
return "PipeBConf"sv;
case IntelGraphics::RegisterIndex::GMBusData:
return "GMBusData"sv;
case IntelGraphics::RegisterIndex::GMBusStatus:
return "GMBusStatus"sv;
case IntelGraphics::RegisterIndex::GMBusCommand:
return "GMBusCommand"sv;
case IntelGraphics::RegisterIndex::GMBusClock:
return "GMBusClock"sv;
case IntelGraphics::RegisterIndex::DisplayPlaneAControl:
return "DisplayPlaneAControl"sv;
case IntelGraphics::RegisterIndex::DisplayPlaneALinearOffset:
return "DisplayPlaneALinearOffset"sv;
case IntelGraphics::RegisterIndex::DisplayPlaneAStride:
return "DisplayPlaneAStride"sv;
case IntelGraphics::RegisterIndex::DisplayPlaneASurface:
return "DisplayPlaneASurface"sv;
case IntelGraphics::RegisterIndex::DPLLDivisorA0:
return "DPLLDivisorA0"sv;
case IntelGraphics::RegisterIndex::DPLLDivisorA1:
return "DPLLDivisorA1"sv;
case IntelGraphics::RegisterIndex::DPLLControlA:
return "DPLLControlA"sv;
case IntelGraphics::RegisterIndex::DPLLControlB:
return "DPLLControlB"sv;
case IntelGraphics::RegisterIndex::DPLLMultiplierA:
return "DPLLMultiplierA"sv;
case IntelGraphics::RegisterIndex::HTotalA:
return "HTotalA"sv;
case IntelGraphics::RegisterIndex::HBlankA:
return "HBlankA"sv;
case IntelGraphics::RegisterIndex::HSyncA:
return "HSyncA"sv;
case IntelGraphics::RegisterIndex::VTotalA:
return "VTotalA"sv;
case IntelGraphics::RegisterIndex::VBlankA:
return "VBlankA"sv;
case IntelGraphics::RegisterIndex::VSyncA:
return "VSyncA"sv;
case IntelGraphics::RegisterIndex::PipeASource:
return "PipeASource"sv;
case IntelGraphics::RegisterIndex::AnalogDisplayPort:
return "AnalogDisplayPort"sv;
case IntelGraphics::RegisterIndex::VGADisplayPlaneControl:
return "VGADisplayPlaneControl"sv;
default:
VERIFY_NOT_REACHED();
}
}
void IntelNativeDisplayConnector::write_to_register(IntelGraphics::RegisterIndex index, u32 value) const
{
VERIFY(m_control_lock.is_locked());
SpinlockLocker lock(m_registers_lock);
dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Graphics Display Connector:: Write to {} value of {:x}", convert_register_index_to_string(index), value);
auto* reg = (u32 volatile*)m_registers_region->vaddr().offset(to_underlying(index)).as_ptr();
*reg = value;
}
u32 IntelNativeDisplayConnector::read_from_register(IntelGraphics::RegisterIndex index) const
{
VERIFY(m_control_lock.is_locked());
SpinlockLocker lock(m_registers_lock);
auto* reg = (u32 volatile*)m_registers_region->vaddr().offset(to_underlying(index)).as_ptr();
u32 value = *reg;
dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Graphics Display Connector: Read from {} value of {:x}", convert_register_index_to_string(index), value);
return value;
}
bool IntelNativeDisplayConnector::pipe_a_enabled() const
{
VERIFY(m_control_lock.is_locked());
return read_from_register(IntelGraphics::RegisterIndex::PipeAConf) & (1 << 30);
}
bool IntelNativeDisplayConnector::pipe_b_enabled() const
{
VERIFY(m_control_lock.is_locked());
return read_from_register(IntelGraphics::RegisterIndex::PipeBConf) & (1 << 30);
}
void IntelNativeDisplayConnector::gmbus_read_edid()
{
Array<u8, 128> crt_edid_bytes {};
{
SpinlockLocker control_lock(m_control_lock);
MUST(m_gmbus_connector->write(Graphics::ddc2_i2c_address, 0));
MUST(m_gmbus_connector->read(Graphics::ddc2_i2c_address, crt_edid_bytes.data(), crt_edid_bytes.size()));
}
set_edid_bytes(crt_edid_bytes);
}
bool IntelNativeDisplayConnector::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 IntelNativeDisplayConnector::disable_output()
{
VERIFY(m_control_lock.is_locked());
disable_dac_output();
disable_all_planes();
disable_pipe_a();
disable_pipe_b();
disable_dpll();
disable_vga_emulation();
}
void IntelNativeDisplayConnector::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();
}
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;
}
}
bool IntelNativeDisplayConnector::set_safe_crt_resolution()
{
VERIFY(m_control_lock.is_locked());
SpinlockLocker modeset_lock(m_modeset_lock);
// Note: Just in case we still allow access to VGA IO ports, disable it now.
GraphicsManagement::the().disable_vga_emulation_access_permanently();
// FIXME: Get the requested resolution from the EDID!!
auto modesetting = calculate_modesetting_from_edid(m_edid_parser.value(), 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, IntelGraphics::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);
enable_dpll_without_vga(pll_settings.value(), dac_multiplier);
set_display_timings(modesetting);
enable_output(m_framebuffer_address.value(), modesetting.horizontal.blanking_start());
DisplayConnector::ModeSetting mode_set {
.horizontal_stride = modesetting.horizontal.blanking_start() * sizeof(u32),
.pixel_clock_in_khz = 0,
.horizontal_active = modesetting.horizontal.blanking_start(),
.horizontal_front_porch_pixels = 0,
.horizontal_sync_time_pixels = 0,
.horizontal_blank_pixels = 0,
.vertical_active = modesetting.vertical.blanking_start(),
.vertical_front_porch_lines = 0,
.vertical_sync_time_lines = 0,
.vertical_blank_lines = 0,
.horizontal_offset = 0,
.vertical_offset = 0,
};
m_current_mode_setting = mode_set;
if (m_framebuffer_console)
m_framebuffer_console->set_resolution(m_current_mode_setting.horizontal_active, m_current_mode_setting.vertical_active, m_current_mode_setting.horizontal_stride);
return true;
}
void IntelNativeDisplayConnector::set_display_timings(Graphics::Modesetting const& 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);
microseconds_delay(200);
}
bool IntelNativeDisplayConnector::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;
microseconds_delay(1000);
current_time++;
}
return false;
}
bool IntelNativeDisplayConnector::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;
microseconds_delay(1000);
current_time++;
}
return false;
}
bool IntelNativeDisplayConnector::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;
microseconds_delay(1000);
current_time++;
}
return false;
}
void IntelNativeDisplayConnector::disable_dpll()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_register(IntelGraphics::RegisterIndex::DPLLControlA, 0);
write_to_register(IntelGraphics::RegisterIndex::DPLLControlB, 0);
}
void IntelNativeDisplayConnector::disable_pipe_a()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_register(IntelGraphics::RegisterIndex::PipeAConf, 0);
dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe A");
wait_for_disabled_pipe_a(100);
dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe A - done.");
}
void IntelNativeDisplayConnector::disable_pipe_b()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_register(IntelGraphics::RegisterIndex::PipeAConf, 0);
dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe B");
wait_for_disabled_pipe_b(100);
dbgln_if(INTEL_GRAPHICS_DEBUG, "Disabling Pipe B - done.");
}
void IntelNativeDisplayConnector::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, (1 << 31) | (1 << 24));
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 IntelNativeDisplayConnector::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, (0b0110 << 26) | (1 << 31));
}
void IntelNativeDisplayConnector::set_dpll_registers(IntelGraphics::PLLSettings const& settings)
{
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, 0);
}
void IntelNativeDisplayConnector::enable_dpll_without_vga(IntelGraphics::PLLSettings const& settings, size_t dac_multiplier)
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
set_dpll_registers(settings);
microseconds_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
microseconds_delay(200);
VERIFY(read_from_register(IntelGraphics::RegisterIndex::DPLLControlA) & (1 << 31));
}
void IntelNativeDisplayConnector::disable_dac_output()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_register(IntelGraphics::RegisterIndex::AnalogDisplayPort, 0b11 << 10);
}
void IntelNativeDisplayConnector::enable_dac_output()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_register(IntelGraphics::RegisterIndex::AnalogDisplayPort, (1 << 31));
}
void IntelNativeDisplayConnector::disable_vga_emulation()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_register(IntelGraphics::RegisterIndex::VGADisplayPlaneControl, (1 << 31));
read_from_register(IntelGraphics::RegisterIndex::VGADisplayPlaneControl);
}
void IntelNativeDisplayConnector::disable_all_planes()
{
VERIFY(m_control_lock.is_locked());
VERIFY(m_modeset_lock.is_locked());
write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneAControl, 0);
write_to_register(IntelGraphics::RegisterIndex::DisplayPlaneBControl, 0);
}
}

View file

@ -16,54 +16,42 @@
namespace Kernel {
namespace IntelGraphics {
enum class RegisterIndex {
PipeAConf = 0x70008,
PipeBConf = 0x71008,
GMBusData = 0x510C,
GMBusStatus = 0x5108,
GMBusCommand = 0x5104,
GMBusClock = 0x5100,
DisplayPlaneAControl = 0x70180,
DisplayPlaneBControl = 0x71180,
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,
};
struct PLLSettings;
struct PLLParameterLimit {
size_t min, max;
};
struct PLLMaxSettings {
PLLParameterLimit dot_clock, vco, n, m, m1, m2, p, p1, p2;
};
}
class IntelDisplayConnectorGroup;
class IntelNativeDisplayConnector final
: public DisplayConnector {
friend class IntelNativeGraphicsAdapter;
friend class IntelDisplayConnectorGroup;
friend class DeviceManagement;
public:
static ErrorOr<NonnullLockRefPtr<IntelNativeDisplayConnector>> try_create(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, PhysicalAddress registers_region_address, size_t registers_region_length);
enum class Type {
Invalid,
Analog,
DVO,
LVDS,
TVOut,
HDMI,
DisplayPort,
EmbeddedDisplayPort,
};
enum class ConnectorIndex : size_t {
PortA = 0,
PortB = 1,
PortC = 2,
PortD = 3,
PortE = 4,
PortF = 5,
PortH = 6,
PortG = 7,
PortI = 8,
};
static ErrorOr<NonnullLockRefPtr<IntelNativeDisplayConnector>> try_create_with_display_connector_group(IntelDisplayConnectorGroup const&, ConnectorIndex, Type, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size);
void set_edid_bytes(Badge<IntelDisplayConnectorGroup>, Array<u8, 128> const& edid_bytes);
ErrorOr<void> create_attached_framebuffer_console(Badge<IntelDisplayConnectorGroup>);
ConnectorIndex connector_index() const { return m_connector_index; }
private:
// ^DisplayConnector
@ -83,55 +71,10 @@ private:
// Note: Paravirtualized hardware doesn't require a defined refresh rate for modesetting.
virtual bool refresh_rate_support() const override { return true; }
IntelNativeDisplayConnector(PhysicalAddress framebuffer_address, size_t framebuffer_resource_size, NonnullOwnPtr<GMBusConnector>, NonnullOwnPtr<Memory::Region> registers_region);
ErrorOr<void> create_attached_framebuffer_console();
ErrorOr<void> initialize_gmbus_settings_and_read_edid();
void write_to_register(IntelGraphics::RegisterIndex, u32 value) const;
u32 read_from_register(IntelGraphics::RegisterIndex) const;
bool pipe_a_enabled() const;
bool pipe_b_enabled() const;
bool is_resolution_valid(size_t width, size_t height);
bool set_safe_crt_resolution();
void disable_output();
void enable_output(PhysicalAddress fb_address, size_t width);
void disable_vga_emulation();
void enable_vga_plane();
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(IntelGraphics::PLLSettings const&);
void enable_dpll_without_vga(IntelGraphics::PLLSettings const&, size_t dac_multiplier);
void set_display_timings(Graphics::Modesetting const&);
void enable_pipe_a();
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 gmbus_read_edid();
Optional<IntelGraphics::PLLSettings> create_pll_settings(u64 target_frequency, u64 reference_clock, IntelGraphics::PLLMaxSettings const&);
mutable Spinlock<LockRank::None> m_registers_lock {};
IntelNativeDisplayConnector(IntelDisplayConnectorGroup const&, ConnectorIndex connector_index, Type, PhysicalAddress framebuffer_address, size_t framebuffer_resource_size);
Type const m_type { Type::Analog };
ConnectorIndex const m_connector_index { 0 };
NonnullLockRefPtr<IntelDisplayConnectorGroup> m_parent_connector_group;
LockRefPtr<Graphics::GenericFramebufferConsole> m_framebuffer_console;
const PhysicalAddress m_registers;
NonnullOwnPtr<Memory::Region> m_registers_region;
NonnullOwnPtr<GMBusConnector> m_gmbus_connector;
};
}

View file

@ -42,14 +42,26 @@ ErrorOr<void> IntelNativeGraphicsAdapter::initialize_adapter()
{
dbgln_if(INTEL_GRAPHICS_DEBUG, "Intel Native Graphics Adapter @ {}", device_identifier().address());
auto bar0_space_size = PCI::get_BAR_space_size(device_identifier(), PCI::HeaderType0BaseRegister::BAR0);
VERIFY(bar0_space_size == 0x80000);
auto bar2_space_size = PCI::get_BAR_space_size(device_identifier(), PCI::HeaderType0BaseRegister::BAR2);
dmesgln_pci(*this, "MMIO @ {}, space size is {:x} bytes", PhysicalAddress(PCI::get_BAR0(device_identifier())), bar0_space_size);
dmesgln_pci(*this, "framebuffer @ {}", PhysicalAddress(PCI::get_BAR2(device_identifier())));
PCI::enable_bus_mastering(device_identifier());
m_display_connector = TRY(IntelNativeDisplayConnector::try_create(PhysicalAddress(PCI::get_BAR2(device_identifier()) & 0xfffffff0), bar2_space_size, PhysicalAddress(PCI::get_BAR0(device_identifier()) & 0xfffffff0), bar0_space_size));
return {};
using MMIORegion = IntelDisplayConnectorGroup::MMIORegion;
MMIORegion first_region { MMIORegion::BARAssigned::BAR0, PhysicalAddress(PCI::get_BAR0(device_identifier()) & 0xfffffff0), bar0_space_size };
MMIORegion second_region { MMIORegion::BARAssigned::BAR2, PhysicalAddress(PCI::get_BAR2(device_identifier()) & 0xfffffff0), bar2_space_size };
PCI::enable_bus_mastering(device_identifier());
PCI::enable_io_space(device_identifier());
PCI::enable_memory_space(device_identifier());
using Generation = IntelDisplayConnectorGroup::Generation;
switch (device_identifier().hardware_id().device_id) {
case 0x29c2:
m_connector_group = TRY(IntelDisplayConnectorGroup::try_create({}, Generation::Gen4, first_region, second_region));
return {};
default:
return Error::from_errno(ENODEV);
}
}
IntelNativeGraphicsAdapter::IntelNativeGraphicsAdapter(PCI::DeviceIdentifier const& pci_device_identifier)

View file

@ -9,6 +9,7 @@
#include <AK/Types.h>
#include <Kernel/Bus/PCI/Device.h>
#include <Kernel/Graphics/Definitions.h>
#include <Kernel/Graphics/Intel/DisplayConnectorGroup.h>
#include <Kernel/Graphics/Intel/NativeDisplayConnector.h>
#include <Kernel/PhysicalAddress.h>
#include <LibEDID/EDID.h>
@ -32,6 +33,6 @@ private:
explicit IntelNativeGraphicsAdapter(PCI::DeviceIdentifier const&);
LockRefPtr<IntelNativeDisplayConnector> m_display_connector;
LockRefPtr<IntelDisplayConnectorGroup> m_connector_group;
};
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Graphics/Intel/Plane/DisplayPlane.h>
#include <Kernel/PhysicalAddress.h>
namespace Kernel {
IntelDisplayPlane::IntelDisplayPlane(Memory::TypedMapping<PlaneRegisters volatile> plane_registers_mapping)
: m_plane_registers(move(plane_registers_mapping))
{
}
IntelDisplayPlane::ShadowRegisters IntelDisplayPlane::shadow_registers() const
{
SpinlockLocker locker(m_access_lock);
return m_shadow_registers;
}
ErrorOr<void> IntelDisplayPlane::enable(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
// Note: We use the shadow register so we don't have the already set
// settings being lost.
m_plane_registers->control = m_shadow_registers.control | (1 << 31);
m_shadow_registers.control |= (1 << 31);
return {};
}
bool IntelDisplayPlane::is_enabled(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
return m_shadow_registers.control & (1 << 31);
}
ErrorOr<void> IntelDisplayPlane::disable(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
// Note: We use the shadow register so we don't have the already set
// settings being lost.
m_shadow_registers.control &= ~(1 << 31);
m_plane_registers->control = m_shadow_registers.control;
return {};
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefPtr.h>
#include <AK/Try.h>
#include <AK/Types.h>
#include <Kernel/Graphics/DisplayConnector.h>
#include <Kernel/Graphics/Intel/Definitions.h>
#include <Kernel/Locking/Spinlock.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
class IntelDisplayConnectorGroup;
class IntelDisplayPlane {
public:
enum class PipeSelect {
PipeA,
PipeB,
PipeC,
PipeD,
};
// Note: This is used to "cache" all the registers we wrote to, because
// we might not be able to read them directly from hardware later.
struct ShadowRegisters {
u32 control;
u32 linear_offset;
u32 stride;
u32 surface_base;
};
public:
static ErrorOr<NonnullOwnPtr<IntelDisplayPlane>> create_with_physical_address(PhysicalAddress plane_registers_start_address);
virtual ErrorOr<void> set_plane_settings(Badge<IntelDisplayConnectorGroup>, PhysicalAddress aperture_start, PipeSelect, size_t horizontal_active_pixels_count) = 0;
ErrorOr<void> enable(Badge<IntelDisplayConnectorGroup>);
bool is_enabled(Badge<IntelDisplayConnectorGroup>);
ErrorOr<void> disable(Badge<IntelDisplayConnectorGroup>);
ShadowRegisters shadow_registers() const;
virtual ~IntelDisplayPlane() = default;
protected:
struct [[gnu::packed]] PlaneRegisters {
u32 control;
u32 linear_offset;
u32 stride;
u8 padding[24]; // Note: This might contain other registers, don't touch them.
u32 surface_base;
};
explicit IntelDisplayPlane(Memory::TypedMapping<PlaneRegisters volatile> registers_mapping);
mutable Spinlock<LockRank::None> m_access_lock;
ShadowRegisters m_shadow_registers {};
Memory::TypedMapping<PlaneRegisters volatile> m_plane_registers;
};
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Graphics/Intel/Plane/G33DisplayPlane.h>
#include <Kernel/PhysicalAddress.h>
namespace Kernel {
ErrorOr<NonnullOwnPtr<IntelG33DisplayPlane>> IntelG33DisplayPlane::create_with_physical_address(PhysicalAddress plane_registers_start_address)
{
auto registers_mapping = TRY(Memory::map_typed<PlaneRegisters volatile>(plane_registers_start_address, sizeof(PlaneRegisters), Memory::Region::Access::ReadWrite));
return adopt_nonnull_own_or_enomem(new (nothrow) IntelG33DisplayPlane(move(registers_mapping)));
}
IntelG33DisplayPlane::IntelG33DisplayPlane(Memory::TypedMapping<PlaneRegisters volatile> registers_mapping)
: IntelDisplayPlane(move(registers_mapping))
{
}
ErrorOr<void> IntelG33DisplayPlane::set_plane_settings(Badge<IntelDisplayConnectorGroup>, PhysicalAddress aperture_start, PipeSelect pipe_select, size_t horizontal_active_pixels_count)
{
SpinlockLocker locker(m_access_lock);
VERIFY(((horizontal_active_pixels_count * 4) % 64 == 0));
VERIFY(aperture_start < PhysicalAddress(0x1'0000'0000));
u32 control_value = 0;
switch (pipe_select) {
case PipeSelect::PipeA:
control_value |= (0b00 << 24);
break;
case PipeSelect::PipeB:
control_value |= (0b01 << 24);
break;
case PipeSelect::PipeC:
control_value |= (0b10 << 24);
break;
case PipeSelect::PipeD:
control_value |= (0b11 << 24);
break;
}
// Note: Set the plane to work with 32 bit BGRX (Ignore Alpha channel).
control_value |= (0b0110 << 26);
m_plane_registers->stride = horizontal_active_pixels_count * 4;
m_shadow_registers.stride = horizontal_active_pixels_count * 4;
m_plane_registers->linear_offset = 0;
m_shadow_registers.linear_offset = 0;
m_plane_registers->surface_base = aperture_start.get();
m_shadow_registers.surface_base = aperture_start.get();
m_plane_registers->control = control_value;
m_shadow_registers.control = control_value;
return {};
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefPtr.h>
#include <AK/Try.h>
#include <AK/Types.h>
#include <Kernel/Graphics/Intel/Plane/DisplayPlane.h>
namespace Kernel {
class IntelDisplayConnectorGroup;
class IntelG33DisplayPlane final : public IntelDisplayPlane {
public:
static ErrorOr<NonnullOwnPtr<IntelG33DisplayPlane>> create_with_physical_address(PhysicalAddress plane_registers_start_address);
virtual ErrorOr<void> set_plane_settings(Badge<IntelDisplayConnectorGroup>, PhysicalAddress aperture_start, PipeSelect, size_t horizontal_active_pixels_count) override;
private:
explicit IntelG33DisplayPlane(Memory::TypedMapping<volatile IntelDisplayPlane::PlaneRegisters> plane_registers_mapping);
};
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/Delay.h>
#include <Kernel/Graphics/Intel/Transcoder/AnalogDisplayTranscoder.h>
#include <Kernel/PhysicalAddress.h>
namespace Kernel {
ErrorOr<NonnullOwnPtr<IntelAnalogDisplayTranscoder>> IntelAnalogDisplayTranscoder::create_with_physical_addresses(PhysicalAddress transcoder_registers_start_address,
PhysicalAddress dpll_registers_start_address, PhysicalAddress dpll_multiplier_register_start_address)
{
auto transcoder_registers_mapping = TRY(Memory::map_typed<TranscoderRegisters volatile>(transcoder_registers_start_address, sizeof(IntelDisplayTranscoder::TranscoderRegisters), Memory::Region::Access::ReadWrite));
auto dpll_registers_mapping = TRY(Memory::map_typed<DPLLRegisters volatile>(dpll_registers_start_address, sizeof(DPLLRegisters), Memory::Region::Access::ReadWrite));
auto dpll_control_mapping = TRY(Memory::map_typed<DPLLControlRegisters volatile>(dpll_multiplier_register_start_address, sizeof(DPLLControlRegisters), Memory::Region::Access::ReadWrite));
return adopt_nonnull_own_or_enomem(new (nothrow) IntelAnalogDisplayTranscoder(move(transcoder_registers_mapping), move(dpll_registers_mapping), move(dpll_control_mapping)));
}
IntelAnalogDisplayTranscoder::IntelAnalogDisplayTranscoder(Memory::TypedMapping<TranscoderRegisters volatile> transcoder_registers_mapping,
Memory::TypedMapping<DPLLRegisters volatile> dpll_registers_mapping, Memory::TypedMapping<DPLLControlRegisters volatile> dpll_control_registers)
: IntelDisplayTranscoder(move(transcoder_registers_mapping))
, m_dpll_registers(move(dpll_registers_mapping))
, m_dpll_control_registers(move(dpll_control_registers))
{
}
ErrorOr<void> IntelAnalogDisplayTranscoder::set_dpll_settings(Badge<IntelDisplayConnectorGroup>, IntelGraphics::PLLSettings const& settings, size_t dac_multiplier)
{
SpinlockLocker locker(m_access_lock);
u32 value = (settings.m2 - 2) | ((settings.m1 - 2) << 8) | ((settings.n - 2) << 16);
m_dpll_registers->divisor_a0 = value;
m_dpll_registers->divisor_a1 = value;
m_shadow_registers.dpll_divisor_a0 = value;
m_shadow_registers.dpll_divisor_a1 = value;
// Note: We don't set the DAC multiplier now but reserve it for later usage (e.g. when enabling the DPLL)
m_shadow_registers.dpll_reserved_dac_multiplier = dac_multiplier;
// Note: We don't set the DPLL P1 now but reserve it for later usage (e.g. when enabling the DPLL)
m_shadow_registers.dpll_p1 = settings.p1;
return {};
}
ErrorOr<void> IntelAnalogDisplayTranscoder::enable_dpll_without_vga(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
// Explanation for Gen4 DPLL control bits:
// 1. 0b0110 in bits 9 to 12 - use clock phase 6 (Default)
// 2. bits 24,25 - set to 0b00 to ensure FPA0/FPA1 (DPLL A Divisor 0, 1) divide by 10 (used for DAC modes under 270 MHz)
// 3. bit 26 - set to 0b1 to ensure mode select to DAC mode
// 4. bit 28 - set to 0b1 to disable VGA mode
// 5. bit 31 - enable DPLL VCO (DPLL enabled and operational)
u32 control_value = (6 << 9) | (m_shadow_registers.dpll_p1) << 16 | (1 << 26) | (1 << 28) | (1 << 31);
m_dpll_control_registers->control = control_value;
m_shadow_registers.dpll_control = control_value;
// Explanation for Gen4 DPLL multiplier bits:
// 1. 0b0110 in bits 9 to 12 - use clock phase 6 (Default)
// 2. bits 24,25 - set to 0b00 to ensure FPA0/FPA1 (DPLL A Divisor 0, 1) divide by 10 (used for DAC modes under 270 MHz)
// 3. bit 26 - set to 0b1 to ensure mode select to DAC mode
// 4. bit 28 - set to 0b1 to disable VGA mode
// 5. bit 31 - enable DPLL VCO (DPLL enabled and operational)
u32 dac_multiplier_value = (m_shadow_registers.dpll_reserved_dac_multiplier - 1) | ((m_shadow_registers.dpll_reserved_dac_multiplier - 1) << 8);
m_dpll_control_registers->multiplier = dac_multiplier_value;
m_shadow_registers.dpll_raw_dac_multiplier = dac_multiplier_value;
// The specification says we should wait (at least) about 150 microseconds
// after enabling the DPLL to allow the clock to stabilize
microseconds_delay(200);
for (size_t milliseconds_elapsed = 0; milliseconds_elapsed < 5; milliseconds_elapsed++) {
u32 control_value = m_dpll_control_registers->control;
if (control_value & (1 << 31))
return {};
}
return Error::from_errno(EBUSY);
}
ErrorOr<void> IntelAnalogDisplayTranscoder::disable_dpll(Badge<IntelDisplayConnectorGroup>)
{
SpinlockLocker locker(m_access_lock);
m_dpll_control_registers->control = 0;
m_shadow_registers.dpll_control = 0;
return {};
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefPtr.h>
#include <AK/Try.h>
#include <AK/Types.h>
#include <Kernel/Graphics/Intel/Transcoder/DisplayTranscoder.h>
namespace Kernel {
class IntelDisplayConnectorGroup;
class IntelAnalogDisplayTranscoder final : public IntelDisplayTranscoder {
public:
static ErrorOr<NonnullOwnPtr<IntelAnalogDisplayTranscoder>> create_with_physical_addresses(PhysicalAddress transcoder_registers_start_address,
PhysicalAddress dpll_registers_start_address, PhysicalAddress dpll_control_registers_start_address);
virtual ErrorOr<void> set_dpll_settings(Badge<IntelDisplayConnectorGroup>, IntelGraphics::PLLSettings const& settings, size_t dac_multiplier) override;
virtual ErrorOr<void> enable_dpll_without_vga(Badge<IntelDisplayConnectorGroup>) override;
virtual ErrorOr<void> disable_dpll(Badge<IntelDisplayConnectorGroup>) override;
private:
struct [[gnu::packed]] DPLLRegisters {
u32 divisor_a0;
u32 divisor_a1;
};
struct [[gnu::packed]] DPLLControlRegisters {
u32 control;
u32 padding; // On Gen4, this is the control register of DPLL B, don't touch this
u32 multiplier;
};
IntelAnalogDisplayTranscoder(Memory::TypedMapping<TranscoderRegisters volatile>, Memory::TypedMapping<DPLLRegisters volatile>, Memory::TypedMapping<DPLLControlRegisters volatile>);
Memory::TypedMapping<DPLLRegisters volatile> m_dpll_registers;
Memory::TypedMapping<DPLLControlRegisters volatile> m_dpll_control_registers;
};
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Graphics/Intel/Transcoder/DisplayTranscoder.h>
#include <Kernel/PhysicalAddress.h>
namespace Kernel {
IntelDisplayTranscoder::IntelDisplayTranscoder(Memory::TypedMapping<TranscoderRegisters volatile> registers_mapping)
: m_transcoder_registers(move(registers_mapping))
{
}
IntelDisplayTranscoder::ShadowRegisters IntelDisplayTranscoder::current_registers_state() const
{
SpinlockLocker locker(m_access_lock);
return m_shadow_registers;
}
ErrorOr<void> IntelDisplayTranscoder::set_mode_setting_timings(Badge<IntelDisplayConnectorGroup>, DisplayConnector::ModeSetting const& mode_setting)
{
SpinlockLocker locker(m_access_lock);
dbgln_if(INTEL_GRAPHICS_DEBUG, "htotal - {}, {}", (mode_setting.horizontal_active - 1), (mode_setting.horizontal_total() - 1));
m_shadow_registers.horizontal_total = ((mode_setting.horizontal_active - 1) | (mode_setting.horizontal_total() - 1) << 16);
m_transcoder_registers->horizontal_total = ((mode_setting.horizontal_active - 1) | (mode_setting.horizontal_total() - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "hblank - {}, {}", (mode_setting.horizontal_blanking_start() - 1), (mode_setting.horizontal_blanking_start() + mode_setting.horizontal_blank_pixels - 1));
m_shadow_registers.horizontal_blank = ((mode_setting.horizontal_blanking_start() - 1) | (mode_setting.horizontal_blanking_start() + mode_setting.horizontal_blank_pixels - 1) << 16);
m_transcoder_registers->horizontal_blank = ((mode_setting.horizontal_blanking_start() - 1) | (mode_setting.horizontal_blanking_start() + mode_setting.horizontal_blank_pixels - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "hsync - {}, {}", (mode_setting.horizontal_sync_start() - 1), (mode_setting.horizontal_sync_end() - 1));
m_shadow_registers.horizontal_sync = ((mode_setting.horizontal_sync_start() - 1) | (mode_setting.horizontal_sync_end() - 1) << 16);
m_transcoder_registers->horizontal_sync = ((mode_setting.horizontal_sync_start() - 1) | (mode_setting.horizontal_sync_end() - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "vtotal - {}, {}", (mode_setting.vertical_active - 1), (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1));
m_shadow_registers.vertical_total = ((mode_setting.vertical_active - 1) | (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1) << 16);
m_transcoder_registers->vertical_total = ((mode_setting.vertical_active - 1) | (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "vblank - {}, {}", (mode_setting.vertical_blanking_start() - 1), (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1));
m_shadow_registers.vertical_blank = ((mode_setting.vertical_blanking_start() - 1) | (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1) << 16);
m_transcoder_registers->vertical_blank = ((mode_setting.vertical_blanking_start() - 1) | (mode_setting.vertical_blanking_start() + mode_setting.vertical_blank_lines - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "vsync - {}, {}", (mode_setting.vertical_sync_start() - 1), (mode_setting.vertical_sync_end() - 1));
m_shadow_registers.vertical_sync = ((mode_setting.vertical_sync_start() - 1) | (mode_setting.vertical_sync_end() - 1) << 16);
m_transcoder_registers->vertical_sync = ((mode_setting.vertical_sync_start() - 1) | (mode_setting.vertical_sync_end() - 1) << 16);
dbgln_if(INTEL_GRAPHICS_DEBUG, "sourceSize - {}, {}", (mode_setting.vertical_active - 1), (mode_setting.horizontal_active - 1));
m_shadow_registers.pipe_source = ((mode_setting.vertical_active - 1) | (mode_setting.horizontal_active - 1) << 16);
m_transcoder_registers->pipe_source = ((mode_setting.vertical_active - 1) | (mode_setting.horizontal_active - 1) << 16);
return {};
}
}

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefPtr.h>
#include <AK/Try.h>
#include <AK/Types.h>
#include <Kernel/Graphics/DisplayConnector.h>
#include <Kernel/Graphics/Intel/Definitions.h>
#include <Kernel/Locking/Spinlock.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
class IntelDisplayConnectorGroup;
class IntelDisplayTranscoder {
public:
// Note: This is used to "cache" all the registers we wrote to, because
// we might not be able to read them directly from hardware later.
struct ShadowRegisters {
u32 horizontal_total;
u32 horizontal_blank;
u32 horizontal_sync;
u32 vertical_total;
u32 vertical_blank;
u32 vertical_sync;
u32 exit_line;
u32 pipe_source;
u32 pipe_border_color_pattern;
u32 reserved;
u32 vsync_shift;
u32 pipe_mult;
u32 dpll_reserved_dac_multiplier;
u32 dpll_raw_dac_multiplier;
u32 dpll_divisor_a0;
u32 dpll_divisor_a1;
u32 dpll_p1;
u32 dpll_control;
u32 m1_value;
u32 n1_value;
u32 m2_value;
u32 n2_value;
u32 m1_link;
u32 n1_link;
u32 m2_link;
u32 n2_link;
};
ErrorOr<void> set_mode_setting_timings(Badge<IntelDisplayConnectorGroup>, DisplayConnector::ModeSetting const&);
virtual ErrorOr<void> set_dpll_settings(Badge<IntelDisplayConnectorGroup>, IntelGraphics::PLLSettings const& settings, size_t dac_multiplier) = 0;
virtual ErrorOr<void> enable_dpll_without_vga(Badge<IntelDisplayConnectorGroup>) = 0;
virtual ErrorOr<void> disable_dpll(Badge<IntelDisplayConnectorGroup>) = 0;
ShadowRegisters current_registers_state() const;
virtual ~IntelDisplayTranscoder() = default;
protected:
struct [[gnu::packed]] TranscoderRegisters {
u32 horizontal_total;
u32 horizontal_blank;
u32 horizontal_sync;
u32 vertical_total;
u32 vertical_blank;
u32 vertical_sync;
u32 exit_line;
u32 pipe_source;
u32 pipe_border_color_pattern;
u32 reserved;
u32 vsync_shift;
u32 pipe_mult;
u32 m1_value;
u32 n1_value;
u32 m2_value;
u32 n2_value;
u32 m1_link;
u32 n1_link;
u32 m2_link;
u32 n2_link;
};
explicit IntelDisplayTranscoder(Memory::TypedMapping<TranscoderRegisters volatile>);
mutable Spinlock<LockRank::None> m_access_lock;
ShadowRegisters m_shadow_registers {};
Memory::TypedMapping<TranscoderRegisters volatile> m_transcoder_registers;
};
}