/*
 * Copyright (c) 2004 Apple Computer, Inc.  All rights reserved.
 *
 *	File: $Id: IOI2CControllerPPC.cpp,v 1.5 2004/12/17 04:53:15 jlehrer Exp $
 *
 *  DRI: Joseph Lehrer
 *
 *		$Log: IOI2CControllerPPC.cpp,v $
 *		Revision 1.5  2004/12/17 04:53:15  jlehrer
 *		[3925249] Fix polled mode timeout warning.
 *		
 *		Revision 1.4  2004/12/17 00:40:47  jlehrer
 *		[3915595] Allow at least 5 seconds before timeout for devices with long clock stretching after wake from sleep.
 *		Client driver can specify a longer timeout in IOI2CCommand.timeout_uS param.
 *		Replaced semaphore with an IOLock.
 *		
 *		Revision 1.3  2004/12/15 02:39:41  jlehrer
 *		[3867728] Fix problem with address no-ack on write transactions.
 *		[3893026] Return specific I2C transaction errors.
 *		
 *		Revision 1.2  2004/09/17 21:56:27  jlehrer
 *		Removed ASPL headers.
 *		Added workaround for u3 DP timebase synchronization:
 *		   On wake from sleep MR4CPU timebase sync happens before PM threads start.
 *		   We need to be online and ready to process the i2c transactions.
 *		   This is enabled by the PE by setting the "AAPL,i2c-no-sleep" property in our provider.
 *		Added publish IOResource for uni-n and mac-io instance.
 *		Removed unused test code fragments.
 *		
 *		Revision 1.1  2004/06/07 21:53:41  jlehrer
 *		Initial Checkin
 *		
 *
 */

#include <IOKit/IORegistryEntry.h>
#include <IOKit/IOPlatformExpert.h>
#include <IOKit/IODeviceTreeSupport.h>
#include "IOI2CController.h"

//#define PPC_I2C_DEBUG 1

#if (defined(PPC_I2C_DEBUG) && PPC_I2C_DEBUG)
#define DLOG(fmt, args...)  kprintf(fmt, ## args)
#else
#define DLOG(fmt, args...)
#endif

#define I2C_ERRLOG 1

#if (defined(I2C_ERRLOG) && I2C_ERRLOG)
#define ERRLOG(fmt, args...)  kprintf(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

//	kprintf("read\nM:%02x C:%02x S:%02x I:%02x E:%02x A:%02x U:%02x D:%02x\n", 
//		readReg(iMODE), readReg(iCNTRL), readReg(iSTATUS), readReg(iISR),
//		readReg(iIER), readReg(iADDR), readReg(iSUBADDR), readReg(iDATA));


class IOI2CControllerPPC : public IOI2CController
{
	OSDeclareDefaultStructors(IOI2CControllerPPC)

public:
	virtual bool start(IOService *provider);
	virtual void stop(IOService *provider);
	virtual void free(void);

protected:
	// Subclass required methods...
	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,	// MODE Read/Write Mode Register
		iCNTRL			= 0x1,	// CNTRL Read/Write Control Register
		iSTATUS			= 0x2,	// STATUS Read Only Status Register
		iISR			= 0x3,	// ISR Read/Write Interrupt Status Register
		iIER			= 0x4,	// IER Read/Write Interrupt Enable Register
		iADDR			= 0x5,	// ADDR Read/Write Address Register
		iSUBADDR		= 0x6,	// SUBADDR Read/Write Sub-address Register
		iDATA			= 0x7,	// DATA Read/Write IIC Data Transmit/Receive Register
		iREVNUM			= 0x8,	// REVNUM Read Only Revision Number Register
		iRISETIMECNT	= 0x9,	// RISETIMECNT Read/Write Clock Rise Time Counter value
		iBITTIMECNT		= 0xa,	// BITTIMECNT Read/Write IIC Bit Time Counter value

		kIIC_REG_COUNT	= 0xb	// Number of IIC cell registers.
	};

	enum
	{
		fMODE_PORTSEL		= (1 << 4),		// PORTSEL IIC Port Select

		fMODE_APMODE		= (3 << 2),		// APMODE Address Phase Mode, bits[2:3]
		fMODE_APMODE_dumb	= (0 << 2),
		fMODE_APMODE_std	= (1 << 2),
		fMODE_APMODE_sub	= (2 << 2),
		fMODE_APMODE_comb	= (3 << 2),

		fMODE_CLKDIV		= (3 << 0),		// N Clock divider N, bits[0:1]
		fMODE_CLKDIV_25kHz	= (0 << 0),
		fMODE_CLKDIV_50kHz	= (1 << 0),
		fMODE_CLKDIV_100kHz	= (2 << 0),
	};

	enum
	{
		fCNTRL_START		= (1 << 3),		// START Send Start Condition
		fCNTRL_STOP			= (1 << 2),		// STOP Send Stop Condition
		fCNTRL_XADDR		= (1 << 1),		// XADDR Send Address Phase
		fCNTRL_AAK			= (1 << 0),		// AAK Assert Acknowledge
		fCNTRL_NoAAK		= (0 << 0),		// NoAAK Assert Acknowledge
	};

	enum
	{
		fSTATUS_ISCL		= (1 << 4),		// ISCL Double rank synchronized ISCL of active port
		fSTATUS_ISDA		= (1 << 3),		// ISDA Double rank synchronized ISDA of active port
		fSTATUS_LASTRW		= (1 << 2),		// LASTRW_ Last R/W_
		fSTATUS_LASTAAK		= (1 << 1),		// LASTAAK Last Acknowledge
		fSTATUS_BUSY		= (1 << 0),		// BUSY Bus Busy Indicator
	};

	enum
	{
		fISR_ISTART			= (1 << 3),		// ISTART Start Condition Sent Interrupt
		fISR_ISTOP			= (1 << 2),		// ISTOP Stop Condition Sent Interrupt
		fISR_IADDR			= (1 << 1),		// IADDR Address Phase Sent Interrupt
		fISR_IDATA			= (1 << 0),		// IDATA Data Byte Sent or Received Interrupt
		fISR_IMASK = (fISR_ISTART | fISR_ISTOP | fISR_IADDR | fISR_IDATA),
	};

	enum
	{
		fIER_ESTART			= (1 << 3),		// ESTART Enable Start Condition Sent Interrupt
		fIER_ESTOP			= (1 << 2),		// ESTOP Enable Stop Condition Sent Interrupt
		fIER_EADDR			= (1 << 1),		// EADDR Enable Address Phase Sent Interrupt
		fIER_EDATA			= (1 << 0),		// EDATA Enable Data Byte Sent or Received Interrupt
	};

	enum
	{
		fADDR_MASK			= (0xFE),		// ADDR[1:7] Master Address bits[0:6]
		fADDR_ReadWrite		= (1 << 0),		// ReadWrite_ R/W_ bit
		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;
	IOLock					*i2c_lock;
};





#define super IOI2CController
OSDefineMetaClassAndStructors( IOI2CControllerPPC, IOI2CController )

bool
IOI2CControllerPPC::start(
	IOService	*provider)
{
//	IOReturn	status;
	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;

	if (0 == (i2c_lock = IOLockAlloc()))
		return false;

	DLOG("IOI2CControllerPPC::start base:%08lx steps:%ld\n", (UInt32)base, steps);

	iic[iMODE]		= base + (steps * iMODE);		// Configure the transmission mode of the i2c cell and the databit rate.
	iic[iCNTRL]		= base + (steps * iCNTRL);		// Holds the 4 bits used to start the operations on the i2c interface.
	iic[iSTATUS]	= base + (steps * iSTATUS);		// Status bits for the i2 cell and the i2c interface.
	iic[iISR]		= base + (steps * iISR);	 	// Holds the status bits for the interrupt conditions.
	iic[iIER]		= base + (steps * iIER);		// Eneables the bits that allow the four interrupt status conditions.
	iic[iADDR]		= base + (steps * iADDR);		// Holds the 7 bits address and the R/W bit.
	iic[iSUBADDR]	= base + (steps * iSUBADDR);	// the 8bit subaddress..
	iic[iDATA]		= base + (steps * iDATA);		// from where we read and write data

	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_lock)			{ IOLockFree(i2c_lock);			i2c_lock = 0;  }

	super::free();
}


// Subclass required methods...
IOReturn
IOI2CControllerPPC::processLockI2CBus(
	UInt32			bus)
{
	return kIOReturnSuccess;	// Just acknowledge. The superclass handles locking.
}

IOReturn
IOI2CControllerPPC::processUnlockI2CBus(
	UInt32			bus)
{
	return kIOReturnSuccess;	// Just acknowledge. The superclass handles unlocking.
}

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


// *******************************************************************
// I2C Read / Write Interface Methods
// *******************************************************************

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;

	// By default we allow up to 5 seconds for a transaction to complete.
	if (timeout_uS < 5000000) // that's the minimum timeout the caller can request.
		timeout_uS = 5000000;
	clock_interval_to_deadline(timeout_uS, kMicrosecondScale, &deadline);

	if (isRead)
		address |= fADDR_READ;
	else
		address &= ~fADDR_READ;

	// Translate IOI2C mode to PPCI2C mode...
	switch (mode)
	{
		default:
		case kI2CMode_Unspecified:	return kIOReturnUnsupported; //	mode = fMODE_APMODE_dumb;	break;
		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)	// bus can be either {0 or 1} only!
	{
		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;

	// Wait up to 1 second for busy=0 before writing mode reg...
	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; // 0x2e9
	}

	// Ready to go...
	writeReg(iIER, 0);									// disable all interrupts
	writeReg(iISR, fISR_IMASK);							// clear pending interrupts

	i2c_xfer = true;									// set transfer in progress flag
	i2c_state = 0;
	i2c_status = kIOReturnSuccess;
	i2c_data = cmd->buffer;
	i2c_count = cmd->count;
	i2c_index = 0;
	i2c_readDirection = isRead;

	// Check conditions for an interrupt transaction.
	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;
		}

//		DLOG("IOI2CPPC imode\n");
		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);
//	if ((mode == fMODE_APMODE_sub) || (mode == fMODE_APMODE_comb))
	writeReg(iSUBADDR, subAddress);

	writeReg(iISR, fISR_IMASK);							// clear pending interrupts
	writeReg(iIER, fISR_IMASK);							// enable all interrupts
	writeReg(iCNTRL, fCNTRL_XADDR);						// start the bus transaction

	if (intMode == false)
	{
//		DLOG("IOI2CPPC pmode\n");
		while (i2c_xfer)
		{
			if (readReg(iISR))
				processInterrupt();
			else
			{
				if (ml_at_interrupt_context())
					IODelay(1000);
				else
					IOSleep(1);
			}
		}
	}
	else
	{
		rval = IOLockSleepDeadline(i2c_lock, &i2c_state, deadline, THREAD_UNINT);
	}

	// Clear transfer in progress flag to try preventing the interupt context from calling IOLockWake after a timeout.
	// If it does.. no big deal.
	i2c_xfer = false;

	// Wait for IIC cell to clear its busy bit before the next transaction...
	// This indicates the stop bit was sent and the SCL and SDA lines have deasserted.
	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);
	}

	// This is the end of the transaction.
	writeReg(iIER, 0);									// disable all interrupts
	writeReg(iISR, fISR_IMASK);							// clear pending interrupts

	if (intMode)
	{
		if (kIOReturnSuccess != (status = fProvider->disableInterrupt(0)))
		{
			ERRLOG("IOI2CControllerPPC::i2cTransaction disableInterrupt failed: 0x%08x\n", status);
		}
	}

	// Evaluate transaction status
	if (i2c_status == kIOReturnSuccess)
	{
		// If transaction successful and didn't timeout?
		if (rval == THREAD_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 (i2c_state & 0x2000200) // IOLockWake was signaled after the timeout.
//				ERRLOG("I2C got a stop after the timeout\n");

			// Only indicate a timeout if the stop still has not occured.
			if (0 == (i2c_state & 0x1000100))
				i2c_status = kIOReturnTimeout; // 0x2d6
		}
	}
	else // kIOReturnAborted or kIOReturnNotResponding
	{
		// kIOReturnAborted is returned only for write transactions during the data phase.
		// It means the slave device no-acked a data byte.

		// kIOReturnNotResponding is returned for address phase no-acks.

		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);
	}

	// Return the actual number of bytes transfered.
	cmd->bytesTransfered = i2c_index;

//	kprintf("-i2cTransaction **** done\n");
	return i2c_status;
}


// *******************************************************************
// interrupt processing
// *******************************************************************
#define iLOG(fmt, args...)
//#define iLOG kprintf
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;

//	TLOG("intr");

	isrReg = readReg(iISR);

//	iLOG("i2c intr: %02x\n", isrReg);

	if (i2c_readDirection)
	{
		if (isrReg & fISR_IADDR)
		{
			iLOG("i2cR Addr");
			i2c_state |= 0x01;
			statusReg = readReg(iSTATUS);					// read bus status
			if (statusReg & fSTATUS_LASTAAK)				// got an ack?
			{
				if (i2c_count > 1)							// more than one byte?
				{
					iLOG(" ->aak\n");
					i2c_state |= 0x02;
					writeReg(iCNTRL, fCNTRL_AAK);			// ..signal ack control
				}
				else										// zero or one bytes?
				{
					iLOG(" ->nak\n");
					i2c_state |= 0x04;
					writeReg(iCNTRL, fCNTRL_NoAAK);			// ..signal noack control
				}
			}
			else											// got noack?
			{
				iLOG(" got nak\n");
				i2c_status = kIOReturnNotResponding;
				i2c_state |= 0x08;
//				writeReg(iCNTRL, fCNTRL_STOP);				// stop is automatically sent after nak
			}

			writeReg(iISR, fISR_IADDR);						// clear interrupt
		}

		if (isrReg & fISR_IDATA)
		{
			iLOG("i2cR Data");
			if (i2c_count)
			{
				byte = readReg(iDATA);						// save next data byte
				i2c_data[i2c_index++] = byte;
				iLOG(" %d/%d=0x%02x",i2c_index,i2c_count,byte);
				i2c_state |= 0x10;
			}

			if (i2c_index < i2c_count)						// more bytes?
			{
				if (i2c_index >= (i2c_count - 1))			// is next byte the last byte?
				{
					iLOG(" ->nak\n");
					writeReg(iCNTRL, fCNTRL_NoAAK);			// signal noack control
					i2c_state |= 0x20;
				}
				else
				{
					iLOG(" ->aak\n");
					writeReg(iCNTRL, fCNTRL_AAK);			// signal ack control
					i2c_state |= 0x40;
				}
			}
			else
			{
				iLOG(" ->stop\n");
				i2c_state |= 0x80;
			}
			writeReg(iISR, fISR_IDATA);						// clear interrupt
		}

		if (isrReg & fISR_ISTOP)
		{
			iLOG("i2cR Stop\n");
//			writeReg(iIER, 0);								// disable all interrupts
			writeReg(iISR, fISR_ISTOP);						// clear stop interrupt
			i2c_state |= 0x100;
			if (i2c_xfer)
			{
				i2c_xfer = false;							// clear transfer in progress flag
				if (0 == (i2c_state & 0x80000000))			// wakeup sleeping i2cTransaction thread if not in polled mode.
					IOLockWakeup(i2c_lock, &i2c_state, true);
				i2c_state |= 0x200;
			}
		}
	}
	else // write direction
	{
		if (isrReg & fISR_IADDR)
		{
			iLOG("i2cW Addr");
			i2c_state |= 0x10000;
			statusReg = readReg(iSTATUS);					// read bus status
			if (statusReg & fSTATUS_LASTAAK)				// got an ack?
			{
				byte = i2c_data[i2c_index++];
				writeReg(iDATA, byte);		// load first data 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);						// clear interrupt
		}

		if (isrReg & fISR_IDATA)
		{
			iLOG("i2cW Data");
			statusReg = readReg(iSTATUS);					// read bus status
			if (statusReg & fSTATUS_LASTAAK)				// got an ack?
			{
				if (i2c_index < i2c_count)					// more bytes?
				{
					byte = i2c_data[i2c_index++];
					writeReg(iDATA, byte);					// load next data byte
					iLOG(" -> %d/%d=0x%02x\n",i2c_index,i2c_count,byte);
					i2c_state |= 0x100000;
				}
				else										// last byte?
				{
					iLOG(" ->stop\n");
					writeReg(iCNTRL, fCNTRL_STOP);			// signal stop control
					i2c_state |= 0x200000;
				}
			}
			else
			{
				i2c_status = kIOReturnAborted;
				iLOG(" got nak\n");
				i2c_state |= 0x400000;
			}
			writeReg(iISR, fISR_IDATA);						// clear data interrupt
		}

		if (isrReg & fISR_ISTOP)
		{
			iLOG("i2cW Stop\n");
			writeReg(iISR, fISR_ISTOP);						// clear stop interrupt
//			writeReg(iIER, 0);								// disable all interrupts
			i2c_state |= 0x1000000;
			if (i2c_xfer)
			{
				i2c_xfer = false;							// clear transfer in progress flag
				if (0 == (i2c_state & 0x80000000))			// wakeup sleeping i2cTransaction thread if not in polled mode.
					IOLockWakeup(i2c_lock, &i2c_state, true);
				i2c_state |= 0x2000000;
			}
		}
	}
}


// *******************************************************************
// IIC Cell Hardware I/O
// *******************************************************************
/*
static const char *vstrs[]=
{"iMODE","iCNTRL","iSTATUS","iISR","iIER","iADDR","iSUBADDR","iDATA","iREVNUM","iRISETIMECNT","iBITTIMECNT","UNKNOWN"};

const char *v2s(UInt8 v)
{
	if (v < 0x0b)
		return vstrs[v];
	return vstrs[0x0b];
}
*/

void
IOI2CControllerPPC::writeReg(
	int reg_index,
	UInt8 value)
{
//	kprintf("wReg:%s, %02x\n",v2s(reg_index),value);
	*(iic[reg_index]) = value;
	eieio();
}

UInt8
IOI2CControllerPPC::readReg(
	int reg_index)
{
	UInt8 value = *(iic[reg_index]);
	eieio();
//	kprintf("rReg:%s, %02x\n",v2s(reg_index),value);
	return value;
}

IOReturn
IOI2CControllerPPC::setPowerState(
	unsigned long	newPowerState,
	IOService		*dontCare)
{
	if (fU3NoSleep)
	{
		if (newPowerState == kIOI2CPowerState_SLEEP)
		{
			kprintf("IOI2CControllerPPC::setPowerState -> sleep (leave it on for PE4CPU)\n");
			return IOPMAckImplied;
		}
	}
	return super::setPowerState(newPowerState, dontCare);
}
