* 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()
{
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;
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;
return B_OK;
}
status_t
OcoresI2c::WriteAddress(i2c_addr adr, bool isRead)
{
uint8 val = OcoresI2cRegsAddress7{.read = isRead, .address = (uint8)adr}.val;
CHECK_RET(WriteByte({.start = true}, val));
return B_OK;
}
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
&& strcmp(compatible, "sifive,fu740-c000-i2c") != 0
&& strcmp(compatible, "sifive,i2c0") != 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, ®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;
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;
}
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)
{
(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);
}