/* I2C bus driver for mpc8540-based boards */

/*
 * Authorship
 * ----------
 * This software ('mvme3100' RTEMS BSP) was created by
 *
 *     Till Straumann <strauman@slac.stanford.edu>, 2005-2007,
 * 	   Stanford Linear Accelerator Center, Stanford University.
 *
 * Acknowledgement of sponsorship
 * ------------------------------
 * The 'mvme3100' BSP was produced by
 *     the Stanford Linear Accelerator Center, Stanford University,
 * 	   under Contract DE-AC03-76SFO0515 with the Department of Energy.
 *
 * Government disclaimer of liability
 * ----------------------------------
 * Neither the United States nor the United States Department of Energy,
 * nor any of their employees, makes any warranty, express or implied, or
 * assumes any legal liability or responsibility for the accuracy,
 * completeness, or usefulness of any data, apparatus, product, or process
 * disclosed, or represents that its use would not infringe privately owned
 * rights.
 *
 * Stanford disclaimer of liability
 * --------------------------------
 * Stanford University makes no representations or warranties, express or
 * implied, nor assumes any liability for the use of this software.
 *
 * Stanford disclaimer of copyright
 * --------------------------------
 * Stanford University, owner of the copyright, hereby disclaims its
 * copyright and all other rights in this software.  Hence, anyone may
 * freely use it for any purpose without restriction.
 *
 * Maintenance of notices
 * ----------------------
 * In the interest of clarity regarding the origin and status of this
 * SLAC software, this and all the preceding Stanford University notices
 * are to remain affixed to any copy or derivative of this software made
 * or distributed by the recipient and are to be affixed to any copy of
 * software made or distributed by the recipient that contains a copy or
 * derivative of this software.
 *
 * ------------------ SLAC Software Notices, Set 4 OTT.002a, 2004 FEB 03
 */

/* Note: We maintain base address, IRQ etc. statically and
 *       globally. We don't bother creating driver-specific
 *       data or using the bus handle but simply assume
 *       this is the only 8540/i2c bus in the system.
 *       Proper support for multiple instances would not
 *       be very hard to add but I don't see the point...
 */

#include <rtems.h>
#include <bsp.h>
#include <rtems/libi2c.h>
#include <bsp/irq.h>
#include <libcpu/spr.h>
#include <libcpu/io.h>
#include <rtems/bspIo.h>
#include <rtems/score/sysstate.h>

#include <bsp/mpc8540_i2c_busdrv.h>

#define STATIC static

/* I2C controller register definitions */
#define	I2CADR				0x3000
#define	I2CFDR				0x3004
#define	I2CCR				0x3008
#define	I2CCR_MEN				(1<<(7-0))
#define	I2CCR_MIEN				(1<<(7-1))
#define	I2CCR_MSTA				(1<<(7-2))
#define	I2CCR_MTX				(1<<(7-3))
#define	I2CCR_TXAK				(1<<(7-4))
#define	I2CCR_RSTA				(1<<(7-5))
#define	I2CCR_BCST				(1<<(7-7))
#define	I2CSR				0x300c
#define	I2CSR_MCF				(1<<(7-0))
#define	I2CSR_MAAS				(1<<(7-1))
#define	I2CSR_MBB				(1<<(7-2))
#define	I2CSR_MAL				(1<<(7-3))
#define	I2CSR_BCSTM				(1<<(7-4))
#define	I2CSR_SRW				(1<<(7-5))
#define	I2CSR_MIF				(1<<(7-6))
#define	I2CSR_RXAK				(1<<(7-7))
#define	I2CDR				0x3010
#define	I2CDFSRR			0x3014

SPR_RO(TBRL)

/********* Global Variables **********/

/*
 * Semaphore for synchronizing accessing task
 * with the (slow) hardware operation.
 * Task takes semaphore and blocks, ISR releases.
 */
static rtems_id syncsem = 0;

static inline int ok_to_block(void)
{
	return syncsem && _System_state_Is_up( _System_state_Get() );
}

/*
 * Wild guess for 0.2 s; this timeout is effective
 * in polling mode; during early init we don't know
 * the system clock rate yet - it's one of the things
 * we have to read from VPD -- via i2c.
 */

static uint32_t poll_timeout = 333333333/8/5;

/********* Primitives ****************/

static inline uint8_t
i2c_rd(unsigned reg)
{
	return in_8( (volatile uint8_t *)(BSP_8540_CCSR_BASE + reg) );
}

static inline void
i2c_wr(unsigned reg, uint8_t val)
{
	out_8( (volatile uint8_t *)(BSP_8540_CCSR_BASE + reg), val );
}

static inline void
i2c_set(unsigned reg, uint8_t val)
{
	i2c_wr( reg, i2c_rd( reg ) | val );
}

static inline void
i2c_clr(unsigned reg, uint8_t val)
{
	i2c_wr( reg, i2c_rd( reg ) & ~val );
}

/********* Helper Routines ***********/

/* Synchronize (wait) for a condition on the
 * i2c bus. Wait for START or STOP to be complete
 * or wait for a byte-transfer.
 * The latter is much slower (9 bit times vs. 1/2
 * in the former cases).
 *
 * If the system is up (and we may block) then
 * this routine attempts to block the current
 * task rather than busy-waiting.
 *
 * NOTE: waiting for START/STOP always requires
 *       polling.
 */

/* wait until i2c status reg AND mask == cond */
static rtems_status_code
i2c_wait( uint8_t msk, uint8_t cond )
{
uint32_t	      then;
rtems_status_code sc;
static int        warn = 0;

	if ( I2CSR_MIF == msk && ok_to_block() ) {
		/* block on semaphore only if system is up and sema initialized */
		sc = rtems_semaphore_obtain( syncsem, RTEMS_WAIT, 100 );
		if ( RTEMS_SUCCESSFUL != sc )
			return sc;
	} else {
		/* system not up (no SEMA yet ) or waiting on something other
		 * than MIF
		 */
		if ( I2CSR_MIF == msk && _System_state_Is_up( _System_state_Get() ) ) {
			if ( warn < 8 || ! (warn & 0x1f) )
				printk("WARNING: i2c bus driver running in polled mode -- should initialize properly!\n");
			warn++;
		}

		then = _read_TBRL();
		do {
			/* poll for .2 seconds */
			if ( (_read_TBRL() - then) > poll_timeout )
				return RTEMS_TIMEOUT;
		} while ( (msk & i2c_rd( I2CSR )) != cond );
	}

	return RTEMS_SUCCESSFUL;
}

/*
 * multi-byte transfer
 * - set transfer direction (master read or master write)
 * - transfer byte
 * - wait/synchronize
 * - check for ACK
 *
 * RETURNS: number of bytes transferred or negative error code.
 */

STATIC int
i2c_xfer(int rw, uint8_t *buf, int len)
{
int               i;
rtems_status_code sc;

	if ( rw ) {
		i2c_clr( I2CCR, I2CCR_MTX );
	} else {
		i2c_set( I2CCR, I2CCR_MTX );
	}

	for ( i = 0; i< len; i++ ) {
		i2c_clr( I2CSR, I2CSR_MIF );
		/* Enable interrupts if necessary */
		if ( ok_to_block() )
			i2c_set( I2CCR, I2CCR_MIEN );
		if ( rw ) {
			buf[i] = i2c_rd( I2CDR );
		} else {
			i2c_wr( I2CDR, buf[i] );
		}
		if ( RTEMS_SUCCESSFUL != (sc = i2c_wait( I2CSR_MIF, I2CSR_MIF )) )
			return -sc;
		if ( (I2CSR_RXAK & i2c_rd( I2CSR )) ) {
			/* NO ACK */
			return -RTEMS_IO_ERROR;
		}
	}

	return i;
}

/*
 * This bus controller gives us lagging data, i.e.,
 * when we read a byte from the data reg then that
 * issues a read cycle on the bus and gives us the
 * byte from the *previous* read cycle :-(
 *
 * This makes it impossible to properly terminate
 * a read transaction w/o knowing ahead of time
 * how many bytes are going to be read (API decouples
 * 'START'/'STOP' from 'READ') since we would have to
 * set TXAK when reading the next-to-last byte
 * (i.e., when the last byte is read on the i2c bus).
 *
 * Hence, (if we are reading) we must do a dummy
 * read-cycle here -- hopefully
 * that has no side-effects! (i.e., EEPROM drivers should
 * reposition file pointers after issuing STOP)
 *
 */

static void
rd1byte_noack(void)
{
uint8_t dum;
uint8_t ccr;

	/* If we are in reading state then read one more
	 * byte w/o acknowledge
	 */

	ccr = i2c_rd (I2CCR );

	if ( ! ( I2CCR_MTX & ccr ) ) {
		i2c_wr( I2CCR, ccr | I2CCR_TXAK );
		i2c_xfer(1, &dum, 1);
		/* restore original TXAK bit setting */
		i2c_clr( I2CCR, (I2CCR_TXAK & ccr) );
	}
}


/********* ISR ***********************/

static void i2c_isr(rtems_irq_hdl_param arg)
{
	/* disable irq */
	i2c_clr( I2CCR, I2CCR_MIEN );
	/* release task */
	rtems_semaphore_release( syncsem );
}

/********* IIC Bus Driver Ops ********/

STATIC rtems_status_code
i2c_init(rtems_libi2c_bus_t *bh)
{
rtems_status_code sc;

	/* compute more accurate timeout */
	if ( BSP_bus_frequency && BSP_time_base_divisor )
		poll_timeout = BSP_bus_frequency/BSP_time_base_divisor*1000/5;

	i2c_clr( I2CCR, I2CCR_MEN );
	i2c_set( I2CCR, I2CCR_MEN );

	i2c_wr( I2CADR, 0 );

	/* leave motload settings for divisor and filter registers */

	if ( SYSTEM_STATE_BEFORE_MULTITASKING <= _System_state_Get() && !syncsem ) {
		sc = rtems_semaphore_create(
				rtems_build_name('i','2','c','b'),
				0,
				RTEMS_SIMPLE_BINARY_SEMAPHORE | RTEMS_LOCAL,
				0,
				&syncsem);
		if ( RTEMS_SUCCESSFUL == sc ) {
			rtems_irq_connect_data xxx;
			xxx.name   = BSP_I2C_IRQ;
			xxx.on     = 0;
			xxx.off    = 0;
			xxx.isOn   = 0;
			xxx.hdl    = i2c_isr;
			xxx.handle = 0;
			if ( ! BSP_install_rtems_irq_handler( &xxx ) ) {
				printk("Unable to install i2c ISR -- falling back to polling mode\n");
				rtems_semaphore_delete( syncsem );
				/* fall back to polling mode */
				syncsem = 0;
			}
		} else {
			syncsem = 0;
		}
	}

	return RTEMS_SUCCESSFUL;
}

STATIC rtems_status_code
i2c_start(rtems_libi2c_bus_t *bh)
{
uint8_t           v;
rtems_status_code sc = RTEMS_SUCCESSFUL;

	v = i2c_rd( I2CCR );
	if ( I2CCR_MSTA & v ) {
		/* RESTART */
		rd1byte_noack();
		v |= I2CCR_RSTA;
	} else {
		v |= I2CCR_MSTA;
	}
	i2c_wr( I2CCR, v );

	/* On MBB we can only poll-wait (no IRQ is generated)
	 * and this is also much faster than reading a byte
	 * (1/2-bit time) so the overhead of an IRQ may not
	 * be justified.
	 * OTOH, we can put this off into the 'send_addr' routine
	 *

	sc = i2c_wait( I2CSR_MBB, I2CSR_MBB );
	 */

	return sc;
}

STATIC rtems_status_code
i2c_stop(rtems_libi2c_bus_t *bh)
{
	rd1byte_noack();

	/* STOP */
	i2c_clr( I2CCR, I2CCR_TXAK | I2CCR_MSTA );

	/* FIXME: should we really spend 1/2 bit-time polling
	 *        or should we just go ahead and hope noone
	 *        else will get a chance to do something to
	 *        the bus until the STOP completes?
	 */
	return i2c_wait( I2CSR_MBB, 0 );
}

STATIC rtems_status_code
i2c_send_addr(rtems_libi2c_bus_t *bh, uint32_t addr, int rw)
{
uint8_t           buf[2];
int               l = 0;
uint8_t           read_mask = rw ? 1 : 0;
rtems_status_code sc;

	/* Make sure we are started; (i2c_start() didn't bother to wait
	 * so we do it here - some time already has expired.
	 */
	sc = i2c_wait( I2CSR_MBB, I2CSR_MBB );

	if ( RTEMS_SUCCESSFUL != sc )
		return sc;

	if ( addr > 0x7f ) {
		/* 10-bit request; 1st address byte is 0b11110<b9><b8><r/w> */
		buf[l] = 0xf0 | ((addr >> 7) & 0x06) | read_mask;
		read_mask = 0;
		l++;
		buf[l] = addr & 0xff;
	} else {
		buf[l] = (addr << 1) | read_mask;
		l++;
	}

	/*
	 * After sending a an address for reading we must
	 * read a dummy byte (this actually clocks the first real
	 * byte on the i2c bus and makes it available in the
	 * data register so that the first 'read_bytes' operation
	 * obtains the byte we clock in here [and starts clocking
	 * the second byte]) to overcome the pipeline
	 * delay in the hardware (I don't like this design) :-(.
	 */
	sc = i2c_xfer( 0, buf, l );
	if ( rw && l == sc ) {
		sc = i2c_xfer( 1, buf, 1 );
	}
	return sc >=0 ? RTEMS_SUCCESSFUL : -sc;
}

STATIC int
i2c_read_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len)
{
	return i2c_xfer( 1, buf, len );
}

STATIC int
i2c_write_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len)
{
	return i2c_xfer( 0, buf, len );
}

/********* Driver Glue Vars **********/

static rtems_libi2c_bus_ops_t  myops = {
	init:            i2c_init,
	send_start:      i2c_start,
	send_stop:       i2c_stop,
	send_addr:       i2c_send_addr,
	read_bytes:      i2c_read_bytes,
	write_bytes:     i2c_write_bytes,
};

static rtems_libi2c_bus_t my_bus_tbl = {
	ops:	&myops,
	size:   sizeof(my_bus_tbl),
};

/********* Global Driver Handle ******/

rtems_libi2c_bus_t *mpc8540_i2c_bus_descriptor = &my_bus_tbl;
