ocores_i2c: add

Change-Id: Ib48f8dbc0bf485b469b590f1472994d250b6ed45
Reviewed-on: https://review.haiku-os.org/c/haiku/+/5900
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
Reviewed-by: Jérôme Duval <jerome.duval@gmail.com>
This commit is contained in:
X512 2022-12-04 00:40:12 +09:00 committed by Jérôme Duval
parent 8798a36c9b
commit 85d9d60045
5 changed files with 427 additions and 0 deletions

View File

@ -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 ;

View File

@ -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
;

View File

@ -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<OcoresI2c*>(driverCookie)->UninitDriver();
},
},
.set_i2c_bus = [](i2c_bus_cookie cookie, i2c_bus bus) {
static_cast<OcoresI2c*>(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<OcoresI2c*>(cookie)->ExecCommand(op, slaveAddress,
(const uint8*)cmdBuffer, cmdLength, (uint8*)dataBuffer, dataLength);
},
.acquire_bus = [](i2c_bus_cookie cookie) {
return static_cast<OcoresI2c*>(cookie)->AcquireBus();
},
.release_bus = [](i2c_bus_cookie cookie) {
static_cast<OcoresI2c*>(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
};

View File

@ -0,0 +1,232 @@
/*
* Copyright 2022, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#include "ocores_i2c.h"
#include <bus/FDT.h>
#include <AutoDeleterDrivers.h>
#include <string.h>
#include <new>
int32
OcoresI2c::InterruptReceived(void* arg)
{
return static_cast<OcoresI2c*>(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<OcoresI2c> 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, &regs, &regsLen))
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);
}

View File

@ -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 <i2c.h>
#include <ByteOrder.h>
#include <assert.h>
#include <AutoDeleterOS.h>
#include <lock.h>
#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_