Skip to content
4 changes: 4 additions & 0 deletions AK/Endian.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ class [[gnu::packed]] LittleEndian {

constexpr operator T() const { return convert_between_host_and_little_endian(m_value); }

constexpr ReadonlyBytes bytes() const { return { this, sizeof(*this) }; }

private:
T m_value { 0 };
};
Expand All @@ -81,6 +83,8 @@ class [[gnu::packed]] BigEndian {

constexpr operator T() const { return convert_between_host_and_big_endian(m_value); }

constexpr ReadonlyBytes bytes() const { return { this, sizeof(*this) }; }

private:
T m_value { 0 };
};
Expand Down
42 changes: 42 additions & 0 deletions Kernel/Bus/I2C/Controller/Controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2024-2026, Sönke Holz <soenke.holz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <AK/Error.h>
#include <AK/Types.h>
#include <AK/Variant.h>
#include <Kernel/Bus/I2C/I2C.h>

// I²C-bus specification and user manual: https://www.nxp.com/docs/en/user-guide/UM10204.pdf

namespace Kernel::I2C {

// Target-transmitter to controller-receiver transfer
struct ReadTransfer {
Address target_address; // 7-bit or 10-bit I²C target address
Bytes data_read;
};

// Controller-transmitter to target-receiver transfer
struct WriteTransfer {
Address target_address; // 7-bit or 10-bit I²C target address
ReadonlyBytes data_to_write;
};

using Transfer = Variant<ReadTransfer, WriteTransfer>;

class Controller {
public:
virtual ~Controller() = default;

virtual ErrorOr<void> do_transfers(Span<Transfer>) = 0;

protected:
Controller() = default;
};

}
159 changes: 159 additions & 0 deletions Kernel/Bus/I2C/Controller/OpenCoresI2CController.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright (c) 2024-2026, Sönke Holz <soenke.holz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <Kernel/Bus/I2C/Controller/OpenCoresI2CController.h>
#include <Kernel/Firmware/DeviceTree/DeviceTree.h>
#include <Kernel/Firmware/DeviceTree/Driver.h>
#include <Kernel/Firmware/DeviceTree/Management.h>

namespace Kernel::I2C {

// FIXME: Handle bus arbitration to support systems with multiple I²C controllers connected to the same bus.

ErrorOr<NonnullOwnPtr<OpenCoresI2CController>> OpenCoresI2CController::create(DeviceTree::Device const& device)
{
u32 register_shift = device.node().get_u32_property("reg-shift"sv).value_or(0);

auto maybe_register_io_width = device.node().get_property("reg-io-width"sv);
if (!maybe_register_io_width.has_value())
return EINVAL;
u32 register_io_width = maybe_register_io_width.value().as<u32>();

if (register_io_width != 1 && register_io_width != 2 && register_io_width != 4)
return EINVAL;

auto registers_resource = TRY(device.get_resource(0));

auto register_region_size = TRY(Memory::page_round_up(registers_resource.paddr.offset_in_page() + REGISTER_COUNT * (1 << register_shift)));
auto register_region = TRY(MM.allocate_mmio_kernel_region(registers_resource.paddr.page_base(), register_region_size, {}, Memory::Region::Access::ReadWrite));
auto register_address = register_region->vaddr().offset(registers_resource.paddr.offset_in_page());

return adopt_nonnull_own_or_enomem(new (nothrow) OpenCoresI2CController(move(register_region), register_address, register_shift, register_io_width));
}

OpenCoresI2CController::OpenCoresI2CController(NonnullOwnPtr<Memory::Region> register_region, VirtualAddress register_address, u32 register_shift, u32 register_io_width)
: m_register_region(move(register_region))
, m_register_address(register_address)
, m_register_shift(register_shift)
, m_register_io_width(register_io_width)
{
// Set the core to be enabled and interrupts to be disabled.
write_reg(RegisterOffset::Control, to_underlying(ControlRegisterFlags::Enable));

// FIXME: Configure the prescale register based on the IP clock and wanted bus clock from the devicetree.
}

ErrorOr<void> OpenCoresI2CController::do_transfers(Span<Transfer> transfers)
{
if (transfers.is_empty())
return EINVAL;

auto check_target_ack = [this] -> ErrorOr<void> {
if ((read_reg(RegisterOffset::Status) & to_underlying(StatusRegisterFlags::NoACKReceived)) != 0) {
write_reg(RegisterOffset::Command, to_underlying(CommandRegisterFlags::GenerateStopCondition));
return EIO;
}

return {};
};

// FIXME: Don't busy wait. Instead, use interrupts and make transfers asynchronous.
auto wait_until_transfer_is_done = [this] {
while ((read_reg(RegisterOffset::Status) & to_underlying(StatusRegisterFlags::TransferInProgress)) != 0) {
Processor::pause();
}
};

for (size_t transfer_index = 0; transfer_index < transfers.size(); transfer_index++) {
TRY(transfers[transfer_index].visit(
[&](ReadTransfer& transfer) -> ErrorOr<void> {
// FIXME: Support 10-bit addresses.
if (transfer.target_address > 0x7f)
return ENOTIMPL;

// Send the address.
write_reg(RegisterOffset::Transmit, seven_bit_address_byte(transfer.target_address, DataDirection::Read));
write_reg(RegisterOffset::Command, to_underlying(CommandRegisterFlags::WriteToSlave | CommandRegisterFlags::GenerateStartCondition));

for (size_t byte_index = 0; byte_index < transfer.data_read.size(); byte_index++) {
wait_until_transfer_is_done();
TRY(check_target_ack());

bool is_last_byte_of_this_transfer = (byte_index == transfer.data_read.size() - 1);
bool is_last_byte_to_be_transferred = is_last_byte_of_this_transfer && (transfer_index == transfers.size() - 1);

auto command_reg_flags = CommandRegisterFlags::ReadFromSlave;
if (is_last_byte_of_this_transfer)
command_reg_flags |= CommandRegisterFlags::NACK;
if (is_last_byte_to_be_transferred)
command_reg_flags |= CommandRegisterFlags::GenerateStopCondition;

write_reg(RegisterOffset::Command, to_underlying(command_reg_flags));

transfer.data_read[byte_index] = read_reg(RegisterOffset::Receive);
}

wait_until_transfer_is_done();
TRY(check_target_ack());

return {};
},
[&](WriteTransfer const& transfer) -> ErrorOr<void> {
// FIXME: Support 10-bit addresses.
if (transfer.target_address > 0x7f)
return ENOTIMPL;

// Send the address.
write_reg(RegisterOffset::Transmit, seven_bit_address_byte(transfer.target_address, DataDirection::Write));
write_reg(RegisterOffset::Command, to_underlying(CommandRegisterFlags::WriteToSlave | CommandRegisterFlags::GenerateStartCondition));

for (size_t byte_index = 0; byte_index < transfer.data_to_write.size(); byte_index++) {
wait_until_transfer_is_done();
TRY(check_target_ack());

write_reg(RegisterOffset::Transmit, transfer.data_to_write[byte_index]);

bool is_last_byte_of_this_transfer = (byte_index == transfer.data_to_write.size() - 1);
bool is_last_byte_to_be_transferred = is_last_byte_of_this_transfer && (transfer_index == transfers.size() - 1);

auto command_reg_flags = CommandRegisterFlags::WriteToSlave;
if (is_last_byte_to_be_transferred)
command_reg_flags |= CommandRegisterFlags::GenerateStopCondition;

write_reg(RegisterOffset::Command, to_underlying(command_reg_flags));
}

wait_until_transfer_is_done();
TRY(check_target_ack());

return {};
}));
}

return {};
}

static constinit Array const compatibles_array = {
"opencores,i2c-ocores"sv,
};

DEVICETREE_DRIVER(OpenCoresI2CControllerDriver, compatibles_array);

// https://www.kernel.org/doc/Documentation/devicetree/bindings/i2c/opencores,i2c-ocores.yaml
ErrorOr<void> OpenCoresI2CControllerDriver::probe(DeviceTree::Device const& device, StringView) const
{
auto* i2c_controller = TRY(OpenCoresI2CController::create(device)).leak_ptr();

TRY(DeviceTree::Management::the().register_i2c_controller(device, *i2c_controller));

auto res = DeviceTree::Management::the().scan_node_for_devices(device.node(), DeviceTree::ShouldProbeImmediately::Yes);
if (res.is_error())
dbgln("{}: Failed to probe child nodes: {}", device.node_name(), res.release_error());

return {};
}

}
173 changes: 173 additions & 0 deletions Kernel/Bus/I2C/Controller/OpenCoresI2CController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Copyright (c) 2024, Sönke Holz <soenke.holz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <Kernel/Bus/I2C/Controller/Controller.h>
#include <Kernel/Firmware/DeviceTree/Device.h>
#include <Kernel/Memory/MemoryManager.h>

// https://opencores.org/websvn/filedetails?repname=i2c&path=%2Fi2c%2Ftags%2Frel_1%2Fdoc%2Fi2c_specs.pdf

namespace Kernel::I2C {

class OpenCoresI2CController final : public I2C::Controller {
public:
static ErrorOr<NonnullOwnPtr<OpenCoresI2CController>> create(DeviceTree::Device const&);
~OpenCoresI2CController() override = default;

ErrorOr<void> do_transfers(Span<Transfer>) override;

private:
OpenCoresI2CController(NonnullOwnPtr<Memory::Region> register_region, VirtualAddress register_address, u32 register_shift, u32 register_io_width);

NonnullOwnPtr<Memory::Region> m_register_region;
VirtualAddress m_register_address;

u32 m_register_shift;
u32 m_register_io_width;

// "Please note that all reserved bits are read as zeros. To ensure forward compatibility, they should be written as zeros."

// 3.1 Registers list
enum class RegisterOffset {
// PRERlo, "Clock Prescale register lo-byte (RW)"
PrescaleLow = 0x00,

// PRERhi, "Clock Prescale register hi-byte (RW)"
PrescaleHigh = 0x01,

// CTR, "Control register (RW)"
Control = 0x02,

// TXR, "Transmit register (W)"
Transmit = 0x03,

// RXR, "Receive register (R)"
Receive = 0x03,

// CR, "Command register (W)"
Command = 0x04,

// SR, "Status register (R)"
Status = 0x04,
};

static constexpr size_t REGISTER_COUNT = 5;

// 3.2.2 Control register
enum class ControlRegisterFlags {
// "EN, I2C core enable bit.
// When set to ‘1’, the core is enabled.
// When set to ‘0’, the core is disabled."
Enable = 1 << 7, // RW

// "IEN, I 2C core interrupt enable bit.
// When set to ‘1’, interrupt is enabled.
// When set to ‘0’, interrupt is disabled."
InterruptEnable = 1 << 6, // RW
};
AK_ENUM_BITWISE_FRIEND_OPERATORS(ControlRegisterFlags);

// 3.2.5 Command register
enum class CommandRegisterFlags {
// "STA, generate (repeated) start condition"
GenerateStartCondition = 1 << 7, // W

// "STO, generate stop condition"
GenerateStopCondition = 1 << 6, // W

// "RD, read from slave"
ReadFromSlave = 1 << 5, // W

// "WR, write to slave"
WriteToSlave = 1 << 4, // W

// "ACK, when a receiver, sent ACK (ACK = ‘0’) or NACK (ACK = ‘1’)"
NACK = 1 << 3, // W

// "IACK, Interrupt acknowledge. When set, clears a pending interrupt."
ClearPendingInterrupt = 1 << 0, // W
};
AK_ENUM_BITWISE_FRIEND_OPERATORS(CommandRegisterFlags);

// 3.2.6 Status register
enum class StatusRegisterFlags {
// "RxACK, Received acknowledge from slave.
// This flag represents acknowledge from the addressed slave.
// ‘1’ = No acknowledge received
// ‘0’ = Acknowledge received"
NoACKReceived = 1 << 7, // R

// "Busy, I2C bus busy
// ‘1’ after START signal detected
// ‘0’ after STOP signal detected"
BusBusy = 1 << 6, // R

// "AL, Arbitration lost
// This bit is set when the core lost arbitration. Arbitration is lost when:
// • a STOP signal is detected, but non requested
// • The master drives SDA high, but SDA is low.
// See bus-arbitration section for more information."
ArbitrationLost = 1 << 5, // R

// "TIP, Transfer in progress.
// ‘1’ when transferring data
// ‘0’ when transfer complete"
TransferInProgress = 1 << 1, // R

// "IF, Interrupt Flag. This bit is set when an interrupt is pending, which
// will cause a processor interrupt request if the IEN bit is set.
// The Interrupt Flag is set when:
// • one byte transfer has been completed
// • arbitration is lost"
InterruptPending = 1 << 0, // R
};
AK_ENUM_BITWISE_FRIEND_OPERATORS(StatusRegisterFlags);

void write_reg(RegisterOffset reg, u8 value)
{
auto offset = to_underlying(reg) << m_register_shift;

// FIXME: What is the endianness for reg-io-width > 1?
switch (m_register_io_width) {
case 1:
*reinterpret_cast<u8 volatile*>(m_register_address.get() + offset) = value;
break;
case 2:
VERIFY(offset % 2 == 0); // FIXME: Check for alignment in try_to_initialize
*reinterpret_cast<u16 volatile*>(m_register_address.get() + offset) = value;
break;
case 4:
VERIFY(offset % 4 == 0);
*reinterpret_cast<u32 volatile*>(m_register_address.get() + offset) = value;
break;
default:
VERIFY_NOT_REACHED();
}
}

u8 read_reg(RegisterOffset reg)
{
auto offset = to_underlying(reg) << m_register_shift;

// FIXME: What is the endianness for reg-io-width > 1?
switch (m_register_io_width) {
case 1:
return *reinterpret_cast<u8 volatile*>(m_register_address.get() + offset);
case 2:
VERIFY(offset % 2 == 0);
return *reinterpret_cast<u16 volatile*>(m_register_address.get() + offset);
case 4:
VERIFY(offset % 4 == 0);
return *reinterpret_cast<u32 volatile*>(m_register_address.get() + offset);
default:
VERIFY_NOT_REACHED();
}
}
};

}
Loading
Loading