/*	$NetBSD: gti2c.c,v 1.2 2005/02/27 00:27:21 perry Exp $	*/

/*
 * Copyright (c) 2005 Brocade Communcations, inc.
 * All rights reserved.
 *
 * Written by Matt Thomas for Brocade Communcations, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of Brocade Communications, Inc. may not be used to endorse
 *    or promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY BROCADE COMMUNICATIONS, INC. ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL EITHER BROCADE COMMUNICATIONS, INC. BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* Fixed many things + ported to RTEMS by Till Straumann, 2005 */

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

#include <sys/cdefs.h>

#include <bsp/gtintrreg.h>
#include <bsp/gti2creg.h>
#include <bsp/gti2c_busdrv.h>

#define ENABLE_IRQ_AT_PIC_HACK	/* workaround for a bad HW bug */
#undef  DEBUG

#ifndef BSP_IRQ_MIN_PRIO
#define BSP_IRQ_MIN_PRIO 1
#endif

struct gti2c_softc {
	uint32_t	sc_gt;
	uint32_t	sc_cntl;
	int			sc_inited;
	rtems_id	sc_sync;
	int			sc_irqs; /* statistics */
};

#ifdef DEBUG
#define STATIC
#else
#define STATIC static
#endif

typedef struct {
	rtems_libi2c_bus_t	bus_desc;	
	struct gti2c_softc	pvt;
} gti2c_desc_rec, *gti2c_desc;

STATIC rtems_status_code
gt_i2c_init(rtems_libi2c_bus_t *bh);
STATIC rtems_status_code
gt_i2c_send_start(rtems_libi2c_bus_t *bh);
STATIC rtems_status_code
gt_i2c_send_stop(rtems_libi2c_bus_t *bh);
STATIC rtems_status_code
gt_i2c_send_addr(rtems_libi2c_bus_t *bh, uint32_t addr, int rw);
STATIC int
gt_i2c_read_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len);
STATIC int
gt_i2c_write_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len);

static rtems_libi2c_bus_ops_t myops = {
	init:			gt_i2c_init,
	send_start:		gt_i2c_send_start,
	send_stop:		gt_i2c_send_stop,
	send_addr:		gt_i2c_send_addr,
	read_bytes:		gt_i2c_read_bytes,
	write_bytes:	gt_i2c_write_bytes,
};

static gti2c_desc_rec my_bus_tbl = {
	{
		ops:	&myops,
		size:	sizeof(my_bus_tbl),
	},/* public fields */
	{
		sc_gt:		BSP_MV64x60_BASE,
		sc_cntl:	I2C_Control_TWSIEn,
		sc_inited:	0,
		sc_sync:	0
	} /* our private fields */
};


static inline uint32_t
gt_read(uint32_t base, uint32_t off)
{
	return in_le32((volatile uint32_t*)(base+off));
}

static inline void
gt_write(uint32_t base, uint32_t off, uint32_t val)
{
	out_le32((volatile uint32_t*)(base+off), val);
}


static inline void
disable_irq(struct gti2c_softc *sc)
{
uint32_t v = gt_read(sc->sc_gt, I2C_REG_Control);
	gt_write(sc->sc_gt, I2C_REG_Control, v & ~I2C_Control_IntEn);
}


static rtems_status_code
gt_i2c_wait(struct gti2c_softc *sc, uint32_t control, uint32_t desired_status)
{
	uint32_t status;
	rtems_status_code rval;

	control |= I2C_Control_IntEn;

	gt_write(sc->sc_gt, I2C_REG_Control, control | sc->sc_cntl);

	if ( sc->sc_inited ) {

#ifdef ENABLE_IRQ_AT_PIC_HACK
		BSP_enable_irq_at_pic(BSP_IRQ_I2C);
#endif

		rval = rtems_semaphore_obtain(sc->sc_sync, RTEMS_WAIT, 100);

		if ( RTEMS_SUCCESSFUL != rval )
			return rval;
	} else {
		uint32_t then, now;

		/* run in polling mode - useful during init */
		if ( _System_state_Is_up(_System_state_Get()) ) {
			printk("WARNING: gti2c running in polled mode -- should initialize properly!\n");
		}

		asm volatile("mftb %0":"=r"(then));

		do {
			asm volatile("mftb %0":"=r"(now));
			/* poll timebase for .2 seconds assuming a bus clock of 100MHz */
			if ( now - then > (uint32_t)100000000/4/5 )
				return RTEMS_TIMEOUT;
		} while ( ! (I2C_Control_IFlg & gt_read(sc->sc_gt, I2C_REG_Control)) );
	}

	status = gt_read(sc->sc_gt, I2C_REG_Status);

	if ( status != desired_status && (status!=I2C_Status_ReStarted || desired_status!=I2C_Status_Started) )
		return RTEMS_IO_ERROR;

	return RTEMS_SUCCESSFUL;
}

static void
gt_i2c_intr(void *arg)
{
struct gti2c_softc * const sc = &my_bus_tbl.pvt;
	uint32_t v;

	v = gt_read(sc->sc_gt, I2C_REG_Control);
	if ((v & I2C_Control_IFlg) == 0) {
		printk("gt_i2c_intr: IRQ but IFlg not set??\n");
		return;
	}
	gt_write(sc->sc_gt, I2C_REG_Control, v & ~(I2C_Control_IntEn));
#if 0
	gt_read(sc->sc_gt, I2C_REG_Control);
	asm volatile("sync");
/* This is how bad it is: after turning off the IntEn bit, the line
 * still remains asserted! (shame on you.)
 *
 * The test below (on MVME6100; the MVME5500 has the same problem
 * but the main cause register address is different; substitute
 * 0xf100000c for 0xf1000c68 on a 5500).
 * 
 * The skew was 101 TB ticks or ~3us (bus freq 133MHz) which
 * really sucks.
 *
 * Therefore, we must disable the interrupt at the PIC 
 */
{unsigned from,to;
	asm volatile("mftb %0":"=r"(from));
	while ( in_le32((volatile uint32_t*)0xf100000c) & 0x20 )
		;
	asm volatile("mftb %0":"=r"(to));
	printk("I2C IRQ remained asserted for %i TB ticks!\n",to-from);
}
#endif
#ifdef ENABLE_IRQ_AT_PIC_HACK
	BSP_disable_irq_at_pic(BSP_IRQ_I2C);
#endif

	sc->sc_irqs++;

	rtems_semaphore_release(sc->sc_sync);
}

STATIC rtems_status_code
gt_i2c_init(rtems_libi2c_bus_t *bh)
{
struct gti2c_softc * const	sc = &((gti2c_desc)bh)->pvt;
unsigned					m,n,N;

	disable_irq(sc);

	/* reset */
	gt_write(sc->sc_gt, I2C_REG_SoftReset, 0);
	gt_write(sc->sc_gt, I2C_REG_SlaveAddr, 0);
	gt_write(sc->sc_gt, I2C_REG_ExtSlaveAddr, 0);

	/* Set baud rate; I don't know the details
	 * but have to assume that it has to fit into 7 bits
	 * (as indicated by some experiment)
	 */
	n = 0, N=1<<n;
	do {
		n++, N<<=1;
		/* increase 2^n until m becomes small enough */
		m = BSP_bus_frequency / 10 / 62500 / N;
	} while ( m > 16 );

	/* n is at least 1 */
	if ( n > 8 ) {
		n = 8; m = 16;	/* nothing else we can do */
	}
	if ( 0 == m )
		m = 1; /* nothing we can do */

	gt_write(sc->sc_gt, I2C_REG_BaudRate, I2C_BaudRate(m-1, n-1));

	if ( !sc->sc_inited ) { 

		if ( _System_state_Is_up(_System_state_Get()) ) {
			rtems_irq_connect_data ii = {
				name:	BSP_IRQ_I2C,
				hdl:	gt_i2c_intr,
				on:		0,
				off:	0,
				isOn:	0
			};
			rtems_status_code err;
			/* synchronization semaphore */
			err = rtems_semaphore_create(
					rtems_build_name('g','i','2','c'),
					0, 
					RTEMS_SIMPLE_BINARY_SEMAPHORE | RTEMS_LOCAL,
					0,
					&sc->sc_sync);
			if ( err ) {
				sc->sc_sync = 0;
				return err;
			}
			if ( !BSP_install_rtems_irq_handler(&ii) ) {
				fprintf(stderr,"Unable to install interrupt handler\n");
				rtems_semaphore_delete(sc->sc_sync);
				return RTEMS_INTERNAL_ERROR;
			}
			BSP_irq_set_priority(BSP_IRQ_I2C, BSP_IRQ_MIN_PRIO);
			sc->sc_inited = 1;
		} else {
		}
	} else {
		rtems_semaphore_flush(sc->sc_sync);
	}
	return RTEMS_SUCCESSFUL;
}

STATIC rtems_status_code
gt_i2c_send_start(rtems_libi2c_bus_t *bh)
{
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;

	return gt_i2c_wait(sc, I2C_Control_Start, I2C_Status_Started);
}

STATIC rtems_status_code
gt_i2c_send_stop(rtems_libi2c_bus_t *bh)
{
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
uint32_t	data;

	data = gt_read(sc->sc_gt, I2C_REG_Status);
	if ( I2C_Status_Started == data || I2C_Status_ReStarted == data ) {
		/* According to the spec, a void message (start - stop sequence)
		 * is illegal and indeed, the chip plays bad tricks with us, i.e.,
		 * sometimes it hangs the bus so that it remains idle forever.
		 * so we have to address someone...
		 */
		gt_i2c_send_addr(bh, /*just something... */ 8, 1);
		data = gt_read(sc->sc_gt, I2C_REG_Status);
	}

	if ( I2C_Status_AddrReadAck == data ) {
		/* Another thing: spec says that the master generates stop only after
		 * not acknowledging the last byte. Again, the chip doesn't like
		 * to be stopped in this condition - hence we just do it the favor
		 * and read a single byte...
		 */
		gt_i2c_read_bytes(bh, (unsigned char *)&data, 1);
	}

	gt_write(sc->sc_gt, I2C_REG_Control, I2C_Control_Stop | sc->sc_cntl);

	/* should we poll for idle? There seems to be in IRQ when this completes */
	return RTEMS_SUCCESSFUL;
}

STATIC rtems_status_code
gt_i2c_send_addr(rtems_libi2c_bus_t *bh, uint32_t addr, int rw)
{
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
uint32_t data, wanted_status;
uint8_t read_mask = rw ? 1 : 0;
rtems_status_code error;

	if (read_mask) {
		wanted_status = I2C_Status_AddrReadAck;
	} else {
		wanted_status = I2C_Status_AddrWriteAck;
	}
	/*
	 * First byte contains whether this xfer is a read or write.
	 */
	data = read_mask;
	if (addr > 0x7f) {
		/*
		 * If this is a 10bit request, the first address byte is
		 * 0b11110<b9><b8><r/w>.
		 */
		data |= 0xf0 | ((addr & 0x300) >> 7);
		gt_write(sc->sc_gt, I2C_REG_Data, data);
		error = gt_i2c_wait(sc, 0, wanted_status);
		if (error)
			return error;
		/*
		 * The first address byte has been sent, now to send
		 * the second one.
		 */
		if (read_mask) {
			wanted_status = I2C_Status_2ndAddrReadAck;
		} else {
			wanted_status = I2C_Status_2ndAddrWriteAck;
		}
		data = (uint8_t) addr;
	} else {
		data |= (addr << 1);
	}

	gt_write(sc->sc_gt, I2C_REG_Data, data);
	return gt_i2c_wait(sc, 0, wanted_status);
}

STATIC int
gt_i2c_read_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len)
{
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
rtems_status_code error;
register unsigned char *p=buf;

	while ( len-- > 0 ) {
		error = gt_i2c_wait(
					sc,
					len ? I2C_Control_ACK : 0,
					len ? I2C_Status_MasterReadAck : I2C_Status_MasterReadNoAck);
		if ( error ) {
			return -error;
		}
		*p++ = gt_read(sc->sc_gt, I2C_REG_Data);
	}

	return p-buf;
}

STATIC int
gt_i2c_write_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len)
{
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
int rval = 0;
rtems_status_code error;

	while ( len-- > 0 ) {
		gt_write(sc->sc_gt, I2C_REG_Data, buf[rval]);
		error = gt_i2c_wait(sc, 0, I2C_Status_MasterWriteAck);
		if ( error ) {
			return -error;
		}
		rval++;
	}

	return rval;
}

rtems_libi2c_bus_t *gt64260_i2c_bus_descriptor = &my_bus_tbl.bus_desc;

#ifdef DEBUG_MODULAR

void
_cexpModuleInitialize(void *arg)
{
	gt_i2c_init(&gt64260_i2c_bus_descriptor->bus_desc);
}

int
_cexpModuleFinalize(void * arg)
{
struct gti2c_softc * const sc = &gt64260_i2c_bus_descriptor->pvt;

	rtems_irq_connect_data ii = {
			name:	BSP_IRQ_I2C,
			hdl:	gt_i2c_intr,
			on:		noop,
			off:	noop,
			isOn:	inoop
	};

	rtems_semaphore_delete(sc->sc_sync);

	return !BSP_remove_rtems_irq_handler(&ii);
}

#endif
