From 85d9d60045d56e6a004b0bae5067a401573ea6d4 Mon Sep 17 00:00:00 2001 From: X512 Date: Sun, 4 Dec 2022 00:40:12 +0900 Subject: [PATCH] ocores_i2c: add MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Ib48f8dbc0bf485b469b590f1472994d250b6ed45 Reviewed-on: https://review.haiku-os.org/c/haiku/+/5900 Tested-by: Commit checker robot Reviewed-by: Jérôme Duval --- src/add-ons/kernel/busses/i2c/Jamfile | 1 + src/add-ons/kernel/busses/i2c/ocores/Jamfile | 11 + .../busses/i2c/ocores/kernel_interface.cpp | 59 +++++ .../kernel/busses/i2c/ocores/ocores_i2c.cpp | 232 ++++++++++++++++++ .../kernel/busses/i2c/ocores/ocores_i2c.h | 124 ++++++++++ 5 files changed, 427 insertions(+) create mode 100644 src/add-ons/kernel/busses/i2c/ocores/Jamfile create mode 100644 src/add-ons/kernel/busses/i2c/ocores/kernel_interface.cpp create mode 100644 src/add-ons/kernel/busses/i2c/ocores/ocores_i2c.cpp create mode 100644 src/add-ons/kernel/busses/i2c/ocores/ocores_i2c.h diff --git a/src/add-ons/kernel/busses/i2c/Jamfile b/src/add-ons/kernel/busses/i2c/Jamfile index d3b297d2ad..17b385a68e 100644 --- a/src/add-ons/kernel/busses/i2c/Jamfile +++ b/src/add-ons/kernel/busses/i2c/Jamfile @@ -1,3 +1,4 @@ SubDir HAIKU_TOP src add-ons kernel busses i2c ; +SubInclude HAIKU_TOP src add-ons kernel busses i2c ocores ; SubInclude HAIKU_TOP src add-ons kernel busses i2c pch ; diff --git a/src/add-ons/kernel/busses/i2c/ocores/Jamfile b/src/add-ons/kernel/busses/i2c/ocores/Jamfile new file mode 100644 index 0000000000..f886f9c22c --- /dev/null +++ b/src/add-ons/kernel/busses/i2c/ocores/Jamfile @@ -0,0 +1,11 @@ +SubDir HAIKU_TOP src add-ons kernel busses i2c ocores ; + +SubDirC++Flags -fno-rtti ; + +UsePrivateHeaders i2c ; +UsePrivateKernelHeaders ; + +KernelAddon ocores_i2c : + ocores_i2c.cpp + kernel_interface.cpp +; diff --git a/src/add-ons/kernel/busses/i2c/ocores/kernel_interface.cpp b/src/add-ons/kernel/busses/i2c/ocores/kernel_interface.cpp new file mode 100644 index 0000000000..e37e311a60 --- /dev/null +++ b/src/add-ons/kernel/busses/i2c/ocores/kernel_interface.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2022, Haiku, Inc. + * Distributed under the terms of the MIT License. + */ + + +#include "ocores_i2c.h" + + +device_manager_info* gDeviceManager; +i2c_for_controller_interface* gI2c; + + +i2c_sim_interface gOcoresI2cDriver = { + .info = { + .info = { + .name = OCORES_I2C_DRIVER_MODULE_NAME, + }, + .supports_device = [](device_node* parent) { + return OcoresI2c::SupportsDevice(parent); + }, + .register_device = [](device_node* parent) { + return OcoresI2c::RegisterDevice(parent); + }, + .init_driver = [](device_node* node, void** driverCookie) { + return OcoresI2c::InitDriver(node, *(OcoresI2c**)driverCookie); + }, + .uninit_driver = [](void* driverCookie) { + return static_cast(driverCookie)->UninitDriver(); + }, + }, + .set_i2c_bus = [](i2c_bus_cookie cookie, i2c_bus bus) { + static_cast(cookie)->SetI2cBus(bus); + }, + .exec_command = [](i2c_bus_cookie cookie, i2c_op op, + i2c_addr slaveAddress, const void *cmdBuffer, size_t cmdLength, + void* dataBuffer, size_t dataLength) { + return static_cast(cookie)->ExecCommand(op, slaveAddress, + (const uint8*)cmdBuffer, cmdLength, (uint8*)dataBuffer, dataLength); + }, + .acquire_bus = [](i2c_bus_cookie cookie) { + return static_cast(cookie)->AcquireBus(); + }, + .release_bus = [](i2c_bus_cookie cookie) { + static_cast(cookie)->ReleaseBus(); + }, +}; + + +_EXPORT module_dependency module_dependencies[] = { + { B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager }, + { I2C_FOR_CONTROLLER_MODULE_NAME, (module_info**)&gI2c }, + {} +}; + +_EXPORT module_info *modules[] = { + (module_info *)&gOcoresI2cDriver, + NULL +}; diff --git a/src/add-ons/kernel/busses/i2c/ocores/ocores_i2c.cpp b/src/add-ons/kernel/busses/i2c/ocores/ocores_i2c.cpp new file mode 100644 index 0000000000..5dfbc5feca --- /dev/null +++ b/src/add-ons/kernel/busses/i2c/ocores/ocores_i2c.cpp @@ -0,0 +1,232 @@ +/* + * Copyright 2022, Haiku, Inc. + * Distributed under the terms of the MIT License. + */ + + +#include "ocores_i2c.h" +#include + +#include + +#include +#include + + +int32 +OcoresI2c::InterruptReceived(void* arg) +{ + return static_cast(arg)->InterruptReceivedInt(); +} + + +int32 +OcoresI2c::InterruptReceivedInt() +{ + // TODO: implement interrupt handling, use polling for now + return B_HANDLED_INTERRUPT; +} + + +status_t +OcoresI2c::WaitCompletion() +{ + while (!fRegs->status.interrupt) {} + return B_OK; +} + + +status_t +OcoresI2c::WriteByte(OcoresI2cRegsCommand cmd, uint8 val) +{ + cmd.intAck = true; + cmd.write = true; + //dprintf("OcoresI2c::WriteByte(cmd: %#02x, val: %#02x)\n", cmd.val, val); + fRegs->data = val; + fRegs->command.val = cmd.val; + CHECK_RET(WaitCompletion()); + return B_OK; +} + + +status_t +OcoresI2c::ReadByte(OcoresI2cRegsCommand cmd, uint8& val) +{ + cmd.intAck = true; + cmd.read = true; + cmd.nack = cmd.stop; + fRegs->command.val = cmd.val; + CHECK_RET(WaitCompletion()); + val = fRegs->data; + //dprintf("OcoresI2c::ReadByte(cmd: %#02x, val: %#02x)\n", cmd.val, val); + return B_OK; +} + + +status_t +OcoresI2c::WriteAddress(i2c_addr adr, bool isRead) +{ + // TODO: 10 bit address support + //dprintf("OcoresI2c::WriteAddress(adr: %#04x, isRead: %d)\n", adr, isRead); + uint8 val = OcoresI2cRegsAddress7{.read = isRead, .address = (uint8)adr}.val; + CHECK_RET(WriteByte({.start = true}, val)); + return B_OK; +} + + +//#pragma mark - driver + +float +OcoresI2c::SupportsDevice(device_node* parent) +{ + const char* bus; + status_t status = gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false); + if (status < B_OK) + return -1.0f; + + if (strcmp(bus, "fdt") != 0) + return 0.0f; + + const char* compatible; + status = gDeviceManager->get_attr_string(parent, "fdt/compatible", &compatible, false); + if (status < B_OK) + return -1.0f; + + if (strcmp(compatible, "opencores,i2c-ocores") != 0) + return 0.0f; + + return 1.0f; +} + + +status_t +OcoresI2c::RegisterDevice(device_node* parent) +{ + device_attr attrs[] = { + { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {string: "Opencores I2C Controller"} }, + { B_DEVICE_FIXED_CHILD, B_STRING_TYPE, {string: I2C_FOR_CONTROLLER_MODULE_NAME} }, + {} + }; + + return gDeviceManager->register_node(parent, OCORES_I2C_DRIVER_MODULE_NAME, attrs, NULL, NULL); +} + + +status_t +OcoresI2c::InitDriver(device_node* node, OcoresI2c*& outDriver) +{ + ObjectDeleter driver(new(std::nothrow) OcoresI2c()); + if (!driver.IsSet()) + return B_NO_MEMORY; + + CHECK_RET(driver->InitDriverInt(node)); + outDriver = driver.Detach(); + return B_OK; +} + + +status_t +OcoresI2c::InitDriverInt(device_node* node) +{ + fNode = node; + dprintf("+OcoresI2c::InitDriver()\n"); + + DeviceNodePutter<&gDeviceManager> parent(gDeviceManager->get_parent_node(node)); + + const char* bus; + CHECK_RET(gDeviceManager->get_attr_string(parent.Get(), B_DEVICE_BUS, &bus, false)); + if (strcmp(bus, "fdt") != 0) + return B_ERROR; + + fdt_device_module_info *parentModule; + fdt_device* parentDev; + CHECK_RET(gDeviceManager->get_driver(parent.Get(), + (driver_module_info**)&parentModule, (void**)&parentDev)); + + uint64 regs = 0; + uint64 regsLen = 0; + if (!parentModule->get_reg(parentDev, 0, ®s, ®sLen)) + return B_ERROR; + + fRegsArea.SetTo(map_physical_memory("Ocores i2c MMIO", regs, regsLen, B_ANY_KERNEL_ADDRESS, + B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, (void**)&fRegs)); + if (!fRegsArea.IsSet()) + return fRegsArea.Get(); + + uint64 irq; + if (!parentModule->get_interrupt(parentDev, 0, NULL, &irq)) + return B_ERROR; + fIrqVector = irq; // TODO: take interrupt controller into account + + // TODO: enable when implement interrupt handling + if (false) + install_io_interrupt_handler(fIrqVector, InterruptReceived, this, 0); + + dprintf("-OcoresI2c::InitDriver()\n"); + return B_OK; +} + + +void +OcoresI2c::UninitDriver() +{ + if (false) + remove_io_interrupt_handler(fIrqVector, InterruptReceived, this); + delete this; +} + + +//#pragma mark - i2c controller + +void +OcoresI2c::SetI2cBus(i2c_bus bus) +{ + dprintf("OcoresI2c::SetI2cBus()\n"); + fBus = bus; +} + + +status_t +OcoresI2c::ExecCommand(i2c_op op, + i2c_addr slaveAddress, const uint8 *cmdBuffer, size_t cmdLength, + uint8* dataBuffer, size_t dataLength) +{ + //dprintf("OcoresI2c::ExecCommand()\n"); + (void)op; + if (cmdLength > 0) { + CHECK_RET(WriteAddress(slaveAddress, false)); + do { + if (fRegs->status.nackReceived) { + fRegs->command.val = OcoresI2cRegsCommand{ + .intAck = true, + .stop = true + }.val; + return B_ERROR; + } + cmdLength--; + CHECK_RET(WriteByte({.stop = cmdLength == 0 && dataLength == 0}, *cmdBuffer++)); + } while (cmdLength > 0); + } + if (dataLength > 0) { + CHECK_RET(WriteAddress(slaveAddress, true)); + do { + dataLength--; + CHECK_RET(ReadByte({.stop = dataLength == 0}, *dataBuffer++)); + } while (dataLength > 0); + } + return B_OK; +} + + +status_t +OcoresI2c::AcquireBus() +{ + return mutex_lock(&fLock); +} + + +void +OcoresI2c::ReleaseBus() +{ + mutex_unlock(&fLock); +} diff --git a/src/add-ons/kernel/busses/i2c/ocores/ocores_i2c.h b/src/add-ons/kernel/busses/i2c/ocores/ocores_i2c.h new file mode 100644 index 0000000000..7ed6ce9d1c --- /dev/null +++ b/src/add-ons/kernel/busses/i2c/ocores/ocores_i2c.h @@ -0,0 +1,124 @@ +/* + * Copyright 2022, Haiku, Inc. + * Distributed under the terms of the MIT License. + */ + + +#ifndef _OCORES_I2C_H_ +#define _OCORES_I2C_H_ + +#include +#include +#include + +#include +#include + + +#define CHECK_RET(err) {status_t _err = (err); if (_err < B_OK) return _err;} + +#define OCORES_I2C_DRIVER_MODULE_NAME "busses/i2c/ocores_i2c/driver_v1" + + +static_assert(B_HOST_IS_LENDIAN); + +union OcoresI2cRegsAddress7 { + struct { + uint8 read: 1; + uint8 address: 7; + }; + uint8 val; +}; + +union OcoresI2cRegsControl { + struct { + uint8 unused1: 6; + uint8 intEnabled: 1; + uint8 enabled: 1; + }; + uint8 val; +}; + +union OcoresI2cRegsCommand { + struct { + uint8 intAck: 1; + uint8 unused1: 2; + uint8 nack: 1; + uint8 write: 1; + uint8 read: 1; + uint8 stop: 1; + uint8 start: 1; + }; + uint8 val; +}; + +union OcoresI2cRegsStatus { + struct { + uint8 interrupt: 1; + uint8 transferInProgress: 1; + uint8 reserved1: 3; + uint8 arbitrationLost: 1; + uint8 busy: 1; + uint8 nackReceived: 1; + }; + uint8 val; +}; + +struct OcoresI2cRegs { + uint8 preLo; + uint8 align1[3]; + uint8 preHi; + uint8 align2[3]; + OcoresI2cRegsControl control; + uint8 align3[3]; + uint8 data; + uint8 align4[3]; + union { + OcoresI2cRegsCommand command; + OcoresI2cRegsStatus status; + }; + uint8 align5[3]; +}; + + +class OcoresI2c { +public: + static float SupportsDevice(device_node* parent); + static status_t RegisterDevice(device_node* parent); + static status_t InitDriver(device_node* node, OcoresI2c*& outDriver); + void UninitDriver(); + + void SetI2cBus(i2c_bus bus); + status_t ExecCommand(i2c_op op, + i2c_addr slaveAddress, const uint8 *cmdBuffer, size_t cmdLength, + uint8* dataBuffer, size_t dataLength); + status_t AcquireBus(); + void ReleaseBus(); + +private: + inline status_t InitDriverInt(device_node* node); + static int32 InterruptReceived(void* arg); + inline int32 InterruptReceivedInt(); + + status_t WaitCompletion(); + status_t WriteByte(OcoresI2cRegsCommand cmd, uint8 val); + status_t ReadByte(OcoresI2cRegsCommand cmd, uint8& val); + status_t WriteAddress(i2c_addr address, bool isRead); + +private: + struct mutex fLock = MUTEX_INITIALIZER("Opencores i2c"); + + AreaDeleter fRegsArea; + volatile OcoresI2cRegs* fRegs{}; + long fIrqVector = -1; + + device_node* fNode{}; + i2c_bus fBus{}; +}; + + +extern device_manager_info* gDeviceManager; +extern i2c_for_controller_interface* gI2c; +extern i2c_sim_interface gOcoresI2cDriver; + +#endif // _OCORES_I2C_H_