IOI2CControllerPPC.cpp [plain text]
#include <IOKit/IORegistryEntry.h>
#include <IOKit/IOPlatformExpert.h>
#include <IOKit/IODeviceTreeSupport.h>
#include "IOI2CController.h"
#include <mach/task.h>
#include <mach/semaphore.h>
#ifdef DEBUG
#include <IOKit/firewire/IOFireLog.h>
#define PPC_I2C_DEBUG 1
#endif
#if (defined(PPC_I2C_DEBUG) && PPC_I2C_DEBUG)
#define DLOG(fmt, args...) FireLog(fmt, ## args)
#else
#define DLOG(fmt, args...)
#endif
#ifdef DEBUG
#define I2C_ERRLOG 1
#endif
#if (defined(I2C_ERRLOG) && I2C_ERRLOG)
#define ERRLOG(fmt, args...) FireLog(fmt, ## args)
#else
#define ERRLOG(fmt, args...)
#endif
extern "C" vm_offset_t ml_io_map(vm_offset_t phys_addr, vm_size_t size);
#if (defined(PPC_I2C_DEBUG) && PPC_I2C_DEBUG)
void TLOG(const char *str)
{
AbsoluteTime abst;
uint64_t t;
clock_get_uptime(&abst);
absolutetime_to_nanoseconds(abst, &t);
DLOG("%s @%d.%u\n", str?str:"", (int)(t/1000000ULL), (int)(t%1000000ULL));
}
#else
#define TLOG(s)
#endif
class IOI2CControllerPPC : public IOI2CController
{
OSDeclareDefaultStructors(IOI2CControllerPPC)
public:
virtual bool start(IOService *provider);
virtual void stop(IOService *provider);
virtual void free(void);
protected:
virtual IOReturn processLockI2CBus(
UInt32 bus);
virtual IOReturn processUnlockI2CBus(
UInt32 bus);
virtual IOReturn processReadI2CBus(
IOI2CCommand *cmd);
virtual IOReturn processWriteI2CBus(
IOI2CCommand *cmd);
private:
bool fU3NoSleep;
virtual IOReturn setPowerState(
unsigned long newPowerState,
IOService *dontCare);
enum
{
iMODE = 0x0, iCNTRL = 0x1, iSTATUS = 0x2, iISR = 0x3, iIER = 0x4, iADDR = 0x5, iSUBADDR = 0x6, iDATA = 0x7, iREVNUM = 0x8, iRISETIMECNT = 0x9, iBITTIMECNT = 0xa,
kIIC_REG_COUNT = 0xb };
enum
{
fMODE_PORTSEL = (1 << 4),
fMODE_APMODE = (3 << 2), fMODE_APMODE_dumb = (0 << 2),
fMODE_APMODE_std = (1 << 2),
fMODE_APMODE_sub = (2 << 2),
fMODE_APMODE_comb = (3 << 2),
fMODE_CLKDIV = (3 << 0), fMODE_CLKDIV_100kHz = (0 << 0),
fMODE_CLKDIV_50kHz = (1 << 0),
fMODE_CLKDIV_25kHz = (2 << 0),
};
enum
{
fCNTRL_START = (1 << 3), fCNTRL_STOP = (1 << 2), fCNTRL_XADDR = (1 << 1), fCNTRL_AAK = (1 << 0), fCNTRL_NoAAK = (0 << 0), };
enum
{
fSTATUS_ISCL = (1 << 4), fSTATUS_ISDA = (1 << 3), fSTATUS_LASTRW = (1 << 2), fSTATUS_LASTAAK = (1 << 1), fSTATUS_BUSY = (1 << 0), };
enum
{
fISR_ISTART = (1 << 3), fISR_ISTOP = (1 << 2), fISR_IADDR = (1 << 1), fISR_IDATA = (1 << 0), fISR_IMASK = (fISR_ISTART | fISR_ISTOP | fISR_IADDR | fISR_IDATA),
};
enum
{
fIER_ESTART = (1 << 3), fIER_ESTOP = (1 << 2), fIER_EADDR = (1 << 1), fIER_EDATA = (1 << 0), };
enum
{
fADDR_MASK = (0xFE), fADDR_ReadWrite = (1 << 0), fADDR_WRITE = (0 << 0),
fADDR_READ = (1 << 0),
};
private:
volatile UInt8 *iic[kIIC_REG_COUNT];
bool fInterruptCapable;
bool fInterruptRegistered;
int cellID;
void writeReg(
int reg_index,
UInt8 value);
UInt8 readReg(
int reg_index);
IOReturn i2cTransaction(
IOI2CCommand *cmd,
bool isRead);
static void sProcessInterrupt(
OSObject *target,
void *refCon,
IOService *nub,
int source);
void processInterrupt(void);
UInt8 *i2c_data;
int i2c_index;
int i2c_count;
volatile UInt32 i2c_xfer;
bool i2c_readDirection;
UInt32 i2c_rate;
IOReturn i2c_status;
UInt32 i2c_state;
semaphore_t i2c_sema;
};
#define super IOI2CController
OSDefineMetaClassAndStructors( IOI2CControllerPPC, IOI2CController )
bool
IOI2CControllerPPC::start(
IOService *provider)
{
OSData *t;
UInt32 baseAddress;
UInt32 steps;
UInt8 *base;
char *resourceName = 0;
DLOG("+IOI2CControllerPPC::start\n");
if (0 == (t = OSDynamicCast(OSData, provider->getProperty("AAPL,address"))))
return false;
if (0 == (baseAddress = *((UInt32*)t->getBytesNoCopy())))
return false;
if (0 == (base = (UInt8 *)ml_io_map(baseAddress, 0x1000)))
return false;
if (0 == (t = OSDynamicCast(OSData, provider->getProperty("AAPL,address-step"))))
return false;
steps = *((UInt32*)t->getBytesNoCopy());
if (0 == (t = OSDynamicCast(OSData, provider->getProperty("AAPL,i2c-rate"))))
return false;
i2c_rate = *((UInt32*)t->getBytesNoCopy());
if (0 == (t = OSDynamicCast(OSData, provider->getProperty("interrupts"))))
fInterruptCapable = FALSE;
else
fInterruptCapable = TRUE;
semaphore_create(current_task(), &i2c_sema, SYNC_POLICY_FIFO, 0);
if(0==i2c_sema) return false;
DLOG("IOI2CControllerPPC::start base:%08lx steps:%ld\n", (UInt32)base, steps);
iic[iMODE] = base + (steps * iMODE); iic[iCNTRL] = base + (steps * iCNTRL); iic[iSTATUS] = base + (steps * iSTATUS); iic[iISR] = base + (steps * iISR); iic[iIER] = base + (steps * iIER); iic[iADDR] = base + (steps * iADDR); iic[iSUBADDR] = base + (steps * iSUBADDR); iic[iDATA] = base + (steps * iDATA);
writeReg(iIER, 0x00);
writeReg(iISR, 0x00);
DLOG("IOI2CControllerPPC::start publishResource...\n");
if (t = OSDynamicCast(OSData, provider->getProperty("compatible")))
{
const char *cstr = (const char *)t->getBytesNoCopy();
if (cstr)
while (*cstr)
{
if (0 == strncmp("uni-n-i2c-control", cstr, strlen("uni-n-i2c-control")))
{
if (provider->getProperty("AAPL,i2c-no-sleep"))
fU3NoSleep = true;
cellID = 1;
resourceName = "IOI2CControllerPPC.uni-n";
break;
}
else
if (0 == strncmp("mac-io-i2c-control", cstr, strlen("mac-io-i2c-control")))
{
fDisablePowerManagement = true;
cellID = 2;
resourceName = "IOI2CControllerPPC.mac-io";
break;
}
cstr += (strlen(cstr) + 1);
}
if (resourceName == 0)
DLOG("IOI2CControllerPPC::start publishResource NO MATCH\n");
}
else
DLOG("IOI2CControllerPPC::start publishResource compatible property not found\n");
if (false == super::start(provider))
{
DLOG("-IOI2CControllerPPC::start super::start failed\n");
return false;
}
registerService();
publishChildren();
if (resourceName)
{
publishResource(resourceName, this);
DLOG("IOI2CControllerPPC::start publishResource %s\n", resourceName);
}
DLOG("-IOI2CControllerPPC::start\n");
return true;
}
void IOI2CControllerPPC::stop(IOService * provider)
{
DLOG("IOI2CControllerPPC::stop\n");
if (fInterruptRegistered)
{
fInterruptCapable = FALSE;
provider->disableInterrupt(0);
provider->unregisterInterrupt(0);
}
super::stop(provider);
}
void IOI2CControllerPPC::free( void )
{
if(i2c_sema) semaphore_destroy(current_task(), i2c_sema);
super::free();
}
IOReturn
IOI2CControllerPPC::processLockI2CBus(
UInt32 bus)
{
return kIOReturnSuccess; }
IOReturn
IOI2CControllerPPC::processUnlockI2CBus(
UInt32 bus)
{
return kIOReturnSuccess; }
IOReturn
IOI2CControllerPPC::processReadI2CBus(
IOI2CCommand *cmd)
{
IOReturn status;
if (cmd == NULL)
return kIOReturnBadArgument;
cmd->bytesTransfered = 0;
status = i2cTransaction(cmd, true);
if (status == kIOReturnTimeout)
ERRLOG("IOI2CControllerPPC::i2cRead timed out\n");
else
if (status != kIOReturnSuccess)
ERRLOG("IOI2CControllerPPC::i2cRead error: %x\n", status);
return status;
}
IOReturn
IOI2CControllerPPC::processWriteI2CBus(
IOI2CCommand *cmd)
{
IOReturn status;
if (cmd == NULL)
return kIOReturnBadArgument;
cmd->bytesTransfered = 0;
status = i2cTransaction(cmd, false);
if (status == kIOReturnTimeout)
ERRLOG("IOI2CControllerPPC::i2cWrite timed out\n");
else
if (status != kIOReturnSuccess)
ERRLOG("IOI2CControllerPPC::i2cWrite error: %x\n", status);
return status;
}
IOReturn
IOI2CControllerPPC::i2cTransaction(
IOI2CCommand *cmd,
bool isRead)
{
IOReturn status;
kern_return_t rval = 0;
int retry;
bool intMode;
AbsoluteTime deadline;
UInt8 mode = cmd->mode;
UInt8 address = cmd->address;
UInt8 subAddress = cmd->subAddress;
UInt32 timeout_uS = cmd->timeout_uS;
if (timeout_uS < 5000000) timeout_uS = 5000000;
clock_interval_to_deadline(timeout_uS, kMicrosecondScale, &deadline);
if (isRead)
address |= fADDR_READ;
else
address &= ~fADDR_READ;
switch (mode)
{
default:
case kI2CMode_Unspecified: return kIOReturnUnsupported; case kI2CMode_Standard: mode = fMODE_APMODE_std; break;
case kI2CMode_StandardSub: mode = fMODE_APMODE_sub; break;
case kI2CMode_Combined: mode = fMODE_APMODE_comb; break;
}
switch (cmd->bus) {
case 0: break;
case 1: mode |= fMODE_PORTSEL; break;
default:
ERRLOG("-i2cTransaction invalid bus:%ld\n", cmd->bus);
return kIOReturnBadArgument;
}
if (i2c_rate < 50)
mode |= fMODE_CLKDIV_25kHz;
else
if (i2c_rate < 100)
mode |= fMODE_CLKDIV_50kHz;
else
mode |= fMODE_CLKDIV_100kHz;
for (retry = 1000; (retry > 0) && (readReg(iSTATUS) & fSTATUS_BUSY); retry--)
{
if (ml_at_interrupt_context())
IODelay(1000);
else
IOSleep(1);
}
if (retry <= 0)
{
ERRLOG("-IOI2CControllerPPC::i2cTransaction IIC cell busy.\n");
return kIOReturnDeviceError; }
writeReg(iIER, 0); writeReg(iISR, fISR_IMASK);
i2c_xfer = true; i2c_state = 0;
i2c_status = kIOReturnSuccess;
i2c_data = cmd->buffer;
i2c_count = cmd->count;
i2c_index = 0;
i2c_readDirection = isRead;
intMode = ( fInterruptCapable && (false == ml_at_interrupt_context()) && (0 == (cmd->options & kI2COption_NoInterrupts)) );
if (intMode)
{
if (fInterruptRegistered == false)
{
DLOG("IOI2CControllerPPC::i2cTransaction calling registerInterrupt\n");
if (kIOReturnSuccess != (status = fProvider->registerInterrupt(0, this, sProcessInterrupt, 0)))
{
ERRLOG("-IOI2CControllerPPC::i2cTransaction registerInterrupt returned:0x%lx\n", status);
return status;
}
fInterruptRegistered = true;
}
if (kIOReturnSuccess != (status = fProvider->enableInterrupt(0)))
{
ERRLOG("-IOI2CControllerPPC::i2cTransaction enableInterrupt failed: 0x%08x\n", status);
return status;
}
}
else
i2c_state |= 0x80000000;
writeReg(iMODE, mode);
writeReg(iADDR, address);
writeReg(iSUBADDR, subAddress);
writeReg(iISR, fISR_IMASK); writeReg(iIER, fISR_IMASK); writeReg(iCNTRL, fCNTRL_XADDR);
if (intMode == false)
{
while (i2c_xfer)
{
if (readReg(iISR))
processInterrupt();
else
{
if (ml_at_interrupt_context())
IODelay(1000);
else
IOSleep(1);
}
}
}
else
{
mach_timespec_t timeout = { 5, 0 }; rval = semaphore_timedwait(i2c_sema, timeout);
DLOG("[%p] woke from semaphore, i2c_state = %x\n", this, i2c_state);
}
i2c_xfer = false;
UInt8 reg = readReg(iSTATUS);
if (reg & fSTATUS_BUSY)
{
for (retry = 1000; retry > 0; retry--)
{
reg = readReg(iSTATUS);
if ((reg & fSTATUS_BUSY) == 0)
break;
if (ml_at_interrupt_context())
IODelay(1000);
else
IOSleep(1);
}
if (retry == 0)
ERRLOG("IOI2CControllerPPC::i2cTransaction IIC cell got no stop and still busy: 0x%02x\n", reg);
if (retry < 998)
ERRLOG("IOI2CControllerPPC::i2cTransaction Waited %d ms for IIC Cell to complete:\n", 1000-retry);
}
writeReg(iIER, 0); writeReg(iISR, fISR_IMASK);
if (intMode)
{
if (kIOReturnSuccess != (status = fProvider->disableInterrupt(0)))
{
ERRLOG("IOI2CControllerPPC::i2cTransaction disableInterrupt failed: 0x%08x\n", status);
}
}
if (i2c_status == kIOReturnSuccess)
{
if (rval == KERN_OPERATION_TIMED_OUT)
{
ERRLOG("IOI2CControllerPPC::i2c%c timed-out B:0x%02x A:0x%02x %d/%d i2c_state:0x%08x\n",
i2c_readDirection?'R':'W', cmd->bus, address, i2c_index, i2c_count, i2c_state);
if (0 == (i2c_state & 0x1000100))
i2c_status = kIOReturnTimeout; }
}
else {
ERRLOG("i2c%c B:0x%02x A:0x%02x %s: %d/%d i2c_state:0x%08x\n", i2c_readDirection?'R':'W', cmd->bus, address, (i2c_status == kIOReturnAborted)?"aborted":(i2c_status == kIOReturnNotResponding)?"not responding":"???", i2c_index, i2c_count, i2c_state);
}
cmd->bytesTransfered = i2c_index;
return i2c_status;
}
#define iLOG(fmt, args...)
void
IOI2CControllerPPC::sProcessInterrupt(
OSObject *target,
void *refCon,
IOService *nub,
int source)
{
IOI2CControllerPPC *self = OSDynamicCast(IOI2CControllerPPC, target);
if (self)
self->processInterrupt();
}
void
IOI2CControllerPPC::processInterrupt(void)
{
register UInt8 byte;
register UInt8 isrReg;
register UInt8 statusReg;
isrReg = readReg(iISR);
DLOG("+ [%p] IOI2CControllerPPC::processInterrupt iISR=%02x\n", this, isrReg);
if (i2c_readDirection)
{
if (isrReg & fISR_IADDR)
{
ERRLOG("[%p] i2cR Addr\n", this);
i2c_state |= 0x01;
statusReg = readReg(iSTATUS);
if (statusReg & fSTATUS_LASTAAK) {
if (i2c_count > 1) {
iLOG(" ->aak\n");
i2c_state |= 0x02;
writeReg(iCNTRL, fCNTRL_AAK); }
else {
iLOG(" ->nak\n");
i2c_state |= 0x04;
writeReg(iCNTRL, fCNTRL_NoAAK); }
}
else {
iLOG(" got nak\n");
i2c_status = kIOReturnNotResponding;
i2c_state |= 0x08;
}
writeReg(iISR, fISR_IADDR); }
if (isrReg & fISR_IDATA)
{
iLOG("i2cR Data");
if (i2c_count)
{
byte = readReg(iDATA); i2c_data[i2c_index++] = byte;
iLOG(" %d/%d=0x%02x",i2c_index,i2c_count,byte);
i2c_state |= 0x10;
}
if (i2c_index < i2c_count) {
if (i2c_index >= (i2c_count - 1)) {
iLOG(" ->nak\n");
writeReg(iCNTRL, fCNTRL_NoAAK); i2c_state |= 0x20;
}
else
{
iLOG(" ->aak\n");
writeReg(iCNTRL, fCNTRL_AAK); i2c_state |= 0x40;
}
}
else
{
iLOG(" ->stop\n");
i2c_state |= 0x80;
}
writeReg(iISR, fISR_IDATA); }
if (isrReg & fISR_ISTOP)
{
ERRLOG("[%p] i2cR Stop\n", this);
writeReg(iISR, fISR_ISTOP); i2c_state |= 0x100;
if (i2c_xfer)
{
i2c_xfer = false; i2c_state |= 0x200;
}
else
{
ERRLOG("i2cR Stop not i2c_xfer\n");
}
if (0 == (i2c_state & 0x80000000)) {
kern_return_t result=semaphore_signal(i2c_sema);
DLOG(" [%p] processInterrupt: signalled semaphore, result %08x\n", this, result);
}
else
{
ERRLOG("i2cR Stop - synchronous, not signalling\n");
}
}
}
else {
if (isrReg & fISR_IADDR)
{
ERRLOG("[%p] i2cW Addr\n", this);
i2c_state |= 0x10000;
statusReg = readReg(iSTATUS); if (statusReg & fSTATUS_LASTAAK) {
byte = i2c_data[i2c_index++];
writeReg(iDATA, byte); iLOG(" ack -> %d/%d=0x%02x\n",i2c_index,i2c_count,byte);
i2c_state |= 0x20000;
}
else
{
iLOG(" got nak\n");
i2c_status = kIOReturnNotResponding;
i2c_state |= 0x80000;
}
writeReg(iISR, fISR_IADDR); }
if (isrReg & fISR_IDATA)
{
iLOG("i2cW Data");
statusReg = readReg(iSTATUS); if (statusReg & fSTATUS_LASTAAK) {
if (i2c_index < i2c_count) {
byte = i2c_data[i2c_index++];
writeReg(iDATA, byte); iLOG(" -> %d/%d=0x%02x\n",i2c_index,i2c_count,byte);
i2c_state |= 0x100000;
}
else {
iLOG(" ->stop\n");
writeReg(iCNTRL, fCNTRL_STOP); i2c_state |= 0x200000;
}
}
else
{
i2c_status = kIOReturnAborted;
iLOG(" got nak\n");
i2c_state |= 0x400000;
}
writeReg(iISR, fISR_IDATA); }
if (isrReg & fISR_ISTOP)
{
ERRLOG("[%p] i2cW Stop\n", this);
writeReg(iISR, fISR_ISTOP); i2c_state |= 0x1000000;
if (i2c_xfer)
{
i2c_xfer = false; i2c_state |= 0x2000000;
}
else
{
ERRLOG("i2cW Stop NOT i2c_xfer\n");
}
if (0 == (i2c_state & 0x80000000)) {
kern_return_t result=semaphore_signal(i2c_sema);
DLOG(" [%p] processInterrupt: signalled semaphore, result %08x\n", this, result);
}
else
{
ERRLOG("i2cW Stop - synchronous, not signalling\n");
}
}
}
ERRLOG("- IOI2CControllerPPC::processInterrupt\n");
}
void
IOI2CControllerPPC::writeReg( int reg_index, UInt8 value)
{
*(iic[reg_index]) = value;
eieio();
}
UInt8
IOI2CControllerPPC::readReg( int reg_index)
{
UInt8 value = *(iic[reg_index]);
eieio();
return value;
}
IOReturn
IOI2CControllerPPC::setPowerState(
unsigned long newPowerState,
IOService *dontCare)
{
if (fU3NoSleep)
{
if (newPowerState == kIOI2CPowerState_SLEEP)
{
DLOG("IOI2CControllerPPC::setPowerState -> sleep (leave it on for PE4CPU)\n");
return IOPMAckImplied;
}
}
return super::setPowerState(newPowerState, dontCare);
}