summaryrefslogtreecommitdiff
path: root/Kernel/Graphics/Intel/Auxiliary
diff options
context:
space:
mode:
authorLiav A <liavalb@gmail.com>2022-02-03 18:16:44 +0200
committerAndrew Kaster <andrewdkaster@gmail.com>2023-02-02 02:10:33 -0700
commit0c64abb5e35d31758e35f3a2a4e5ef5c62db7a17 (patch)
treee85f0b9d7df6f72952dc0c69ea90eae010e2ce0d /Kernel/Graphics/Intel/Auxiliary
parentddc5c41253d9a38986c3e2969b30c9f60e06af9f (diff)
downloadserenity-0c64abb5e35d31758e35f3a2a4e5ef5c62db7a17.zip
Kernel: Split I2C functionality from IntelNativeDisplayConnector code
Splitting the I2C-related code lets the DisplayConnector code to utilize I2C operations without caring about the specific details of the hardware and allow future expansion of the driver to other newer generations sharing the same GMBus code. We should require a timeout for GMBus operations always, because faulty hardware could let us just spin forever. Also, if nothing is listening to the bus (which should result in a NAK), we could also spin forever.
Diffstat (limited to 'Kernel/Graphics/Intel/Auxiliary')
-rw-r--r--Kernel/Graphics/Intel/Auxiliary/GMBusConnector.cpp123
-rw-r--r--Kernel/Graphics/Intel/Auxiliary/GMBusConnector.h53
2 files changed, 176 insertions, 0 deletions
diff --git a/Kernel/Graphics/Intel/Auxiliary/GMBusConnector.cpp b/Kernel/Graphics/Intel/Auxiliary/GMBusConnector.cpp
new file mode 100644
index 0000000000..3b599d562d
--- /dev/null
+++ b/Kernel/Graphics/Intel/Auxiliary/GMBusConnector.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <Kernel/Arch/Delay.h>
+#include <Kernel/Graphics/Intel/Auxiliary/GMBusConnector.h>
+#include <Kernel/PhysicalAddress.h>
+
+namespace Kernel {
+
+enum class GMBusStatus {
+ TransactionCompletion,
+ HardwareReady
+};
+
+enum GMBusCycle {
+ Wait = 1,
+ Stop = 4,
+};
+
+ErrorOr<NonnullOwnPtr<GMBusConnector>> GMBusConnector::create_with_physical_address(PhysicalAddress gmbus_start_address)
+{
+ auto registers_mapping = TRY(map_typed<GMBusRegisters volatile>(gmbus_start_address, sizeof(GMBusRegisters), Memory::Region::Access::ReadWrite));
+ return adopt_nonnull_own_or_enomem(new (nothrow) GMBusConnector(move(registers_mapping)));
+}
+
+GMBusConnector::GMBusConnector(Memory::TypedMapping<GMBusRegisters volatile> registers_mapping)
+ : m_gmbus_registers(move(registers_mapping))
+{
+ set_default_rate();
+ set_pin_pair(PinPair::DedicatedAnalog);
+}
+
+bool GMBusConnector::wait_for(GMBusStatus desired_status, size_t milliseconds_timeout)
+{
+ VERIFY(m_access_lock.is_locked());
+ size_t milliseconds_passed = 0;
+ while (1) {
+ if (milliseconds_timeout < milliseconds_passed)
+ return false;
+ full_memory_barrier();
+ u32 status = m_gmbus_registers->status;
+ 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();
+ }
+ microseconds_delay(1000);
+ milliseconds_passed++;
+ }
+}
+
+ErrorOr<void> GMBusConnector::write(unsigned address, u32 data)
+{
+ VERIFY(address < 256);
+ SpinlockLocker locker(m_access_lock);
+ full_memory_barrier();
+ m_gmbus_registers->data = data;
+ full_memory_barrier();
+ m_gmbus_registers->command = ((address << 1) | (1 << 16) | (GMBusCycle::Wait << 25) | (1 << 30));
+ full_memory_barrier();
+ if (!wait_for(GMBusStatus::TransactionCompletion, 250))
+ return Error::from_errno(EBUSY);
+ return {};
+}
+
+void GMBusConnector::set_default_rate()
+{
+ // FIXME: Verify GMBUS Rate Select is set only when GMBUS is idle
+ SpinlockLocker locker(m_access_lock);
+ // Set the rate to 100KHz
+ m_gmbus_registers->clock = m_gmbus_registers->clock & ~(0b111 << 8);
+}
+
+void GMBusConnector::set_pin_pair(PinPair pin_pair)
+{
+ // FIXME: Verify GMBUS is idle
+ SpinlockLocker locker(m_access_lock);
+ m_gmbus_registers->clock = (m_gmbus_registers->clock & (~0b111)) | (pin_pair & 0b111);
+}
+
+ErrorOr<void> GMBusConnector::read(unsigned address, u8* buf, size_t length)
+{
+ VERIFY(address < 256);
+ SpinlockLocker locker(m_access_lock);
+ size_t nread = 0;
+ auto read_set = [&] {
+ full_memory_barrier();
+ u32 data = m_gmbus_registers->data;
+ 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();
+ m_gmbus_registers->command = (1 | (address << 1) | (length << 16) | (GMBusCycle::Wait << 25) | (1 << 30));
+ full_memory_barrier();
+ while (nread < length) {
+ if (!wait_for(GMBusStatus::HardwareReady, 250))
+ return Error::from_errno(EBUSY);
+ read_set();
+ }
+ if (!wait_for(GMBusStatus::TransactionCompletion, 250))
+ return Error::from_errno(EBUSY);
+ return {};
+}
+
+}
diff --git a/Kernel/Graphics/Intel/Auxiliary/GMBusConnector.h b/Kernel/Graphics/Intel/Auxiliary/GMBusConnector.h
new file mode 100644
index 0000000000..fbcb787355
--- /dev/null
+++ b/Kernel/Graphics/Intel/Auxiliary/GMBusConnector.h
@@ -0,0 +1,53 @@
+/*
+ * 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/Locking/Spinlock.h>
+#include <Kernel/Memory/TypedMapping.h>
+
+namespace Kernel {
+
+struct [[gnu::packed]] GMBusRegisters {
+ u32 clock;
+ u32 command;
+ u32 status;
+ u32 data;
+};
+
+enum class GMBusStatus;
+
+class GMBusConnector {
+public:
+ enum PinPair : u8 {
+ None = 0,
+ DedicatedControl = 1,
+ DedicatedAnalog = 0b10,
+ IntegratedDigital = 0b11,
+ sDVO = 0b101,
+ Dconnector = 0b111,
+ };
+
+public:
+ static ErrorOr<NonnullOwnPtr<GMBusConnector>> create_with_physical_address(PhysicalAddress gmbus_start_address);
+
+ ErrorOr<void> write(unsigned address, u32 data);
+ ErrorOr<void> read(unsigned address, u8* buf, size_t length);
+ void set_default_rate();
+
+private:
+ void set_pin_pair(PinPair pin_pair);
+
+ bool wait_for(GMBusStatus desired_status, size_t milliseconds_timeout);
+
+ explicit GMBusConnector(Memory::TypedMapping<GMBusRegisters volatile>);
+ Spinlock<LockRank::None> m_access_lock;
+ Memory::TypedMapping<GMBusRegisters volatile> m_gmbus_registers;
+};
+}