/*
 * (C) Copyright 2003
 * Humboldt Solutions Ltd, adrian@humboldt.co.uk.
 
 * This is a combined i2c adapter and algorithm driver for Motorola's
 * MPC107 PowerPC northbridge and related silicon. Currently the driver
 * as only been tested on SMBus transactions to a LM75 sensor.
 *
 * Release 0.2
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */
 
#include <linux/config.h>	
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <asm/io.h>
#include <asm/mpc10x.h>
#include <linux/i2c.h>

#ifdef DEBUG
#define DPRINTK(args...) printk(KERN_INFO __FILE__": " ##args)
#else
#define DPRINTK(args...)
#endif


#define MPC107_I2CADDR  0x00
#define MPC107_I2CFDR 	0x04
#define MPC107_I2CCR	0x08
#define MPC107_I2CSR	0x0c
#define MPC107_I2CDR	0x10

#define MPC107_CCR_MEN  0x80
#define MPC107_CCR_MIEN 0x40
#define MPC107_CCR_MSTA 0x20
#define MPC107_CCR_MTX  0x10
#define MPC107_CCR_TXAK 0x08
#define MPC107_CCR_RSTA 0x04

#define MPC107_CSR_MCF  0x80
#define MPC107_CSR_MAAS 0x40
#define MPC107_CSR_MBB  0x20
#define MPC107_CSR_MAL  0x10
#define MPC107_CSR_SRW  0x04
#define MPC107_CSR_MIF  0x02
#define MPC107_CSR_RXAK 0x01

static int i2c_vec;
static char *i2c_base;
static u32 eumbbar;

static __inline__ void writeccr(u32 x)
{
        writel(x, i2c_base + MPC107_I2CCR);
}

static DECLARE_WAIT_QUEUE_HEAD(mpc107_wait);
static volatile u32 mpc107_interrupt;

static void mpc107_i2c_isr(int irq, void *dev_id, struct pt_regs * regs)
{
    if (readl(i2c_base + MPC107_I2CSR) & MPC107_CSR_MIF)
    {
	/* Read again to allow register to stabilise */
	mpc107_interrupt = readl(i2c_base + MPC107_I2CSR);
        writel(0, i2c_base + MPC107_I2CSR);
	wake_up_interruptible(&mpc107_wait);
    }
}

static int i2c_wait(unsigned timeout, int writing)
{
	DECLARE_WAITQUEUE(wait, current);
        unsigned long orig_jiffies = jiffies;
        u32 x;
	int result = 0;
	
	add_wait_queue(&mpc107_wait, &wait);
        while ( ! (mpc107_interrupt & MPC107_CSR_MIF)) {
		set_current_state(TASK_INTERRUPTIBLE);
                if (signal_pending(current))
		{
		    	DPRINTK("I2C: Interrupted\n");
                        result = -EINTR;
			break;
		}
                if (orig_jiffies + timeout < jiffies)
		{
		    	DPRINTK("I2C: timeout\n");
                        result = -EIO;
			break;
		}
                schedule_timeout(timeout);
        }
	current->state = TASK_RUNNING;
	remove_wait_queue(&mpc107_wait, &wait);
	x = mpc107_interrupt;
	mpc107_interrupt = 0;
	
	if (result < -0)
	    return result;
	
	if (! (x & MPC107_CSR_MCF))
	{
	    DPRINTK("I2C: unfinished\n");
	    return -EIO;
	}

        if (x & MPC107_CSR_MAL)
	{
	    	DPRINTK("I2C: MAL\n");
                return -EIO;
	}

        if (writing && (x & MPC107_CSR_RXAK)) {
                DPRINTK("I2C: No RXAK\n");
                /* generate stop */
                writeccr(MPC107_CCR_MEN);
                return -EIO;
        }
        return 0;
}

static void mpc107_i2c_start(void)
{
        /* Set clock */
        writel(0x1031, i2c_base + MPC107_I2CFDR);
        /* Clear arbitration */
        writel(0, i2c_base + MPC107_I2CSR);
       	/* Start with MEN */
	writeccr(MPC107_CCR_MEN);
}

static void mpc107_i2c_stop(void)
{
	writeccr(MPC107_CCR_MEN);
}

static int mpc107_write(int target, const u8 *data, int length, int restart)
{
        int i;
        unsigned timeout = HZ;
    	u32 flags = restart ? MPC107_CCR_RSTA : 0;

	/* Start with MEN */
	if (! restart)
	    	writeccr(MPC107_CCR_MEN);
	/* Start as master */
	writeccr(MPC107_CCR_MIEN | MPC107_CCR_MEN | MPC107_CCR_MSTA | MPC107_CCR_MTX | flags);
	/* Write target byte */
	writel((target << 1), i2c_base + MPC107_I2CDR);

	if (i2c_wait(timeout, 1) < 0)
	    return -1;
            
        for(i = 0; i < length; i++) {
	        /* Write data byte */
	        writel(data[i], i2c_base + MPC107_I2CDR);

	        if (i2c_wait(timeout, 1) < 0)
	            return -1;
        }
	
        return 0;
}

static int mpc107_read(int target, u8 *data, int length, int restart)
{
    	unsigned timeout = HZ;
    	int i;
    	u32 flags = restart ? MPC107_CCR_RSTA : 0;

	/* Start with MEN */
	if (! restart)
	    	writeccr(MPC107_CCR_MEN);
	/* Switch to read - restart */
	writeccr(MPC107_CCR_MIEN | MPC107_CCR_MEN | MPC107_CCR_MSTA | MPC107_CCR_MTX | flags);
	/* Write target address byte - this time with the read flag set */
	writel((target << 1) | 1, i2c_base + MPC107_I2CDR);

	if (i2c_wait(timeout, 0) < 0)
	    return -1;

	if (length == 1)
	    writeccr(MPC107_CCR_MIEN| MPC107_CCR_MEN | MPC107_CCR_MSTA | MPC107_CCR_TXAK);
	else
	    writeccr(MPC107_CCR_MIEN| MPC107_CCR_MEN | MPC107_CCR_MSTA);
    	/* Dummy read */
	readl(i2c_base + MPC107_I2CDR);

	for(i = 0; i < length; i++) {
	    if (i2c_wait(timeout, 0) < 0)
		return -1;

	    /* Generate txack on next to last byte */
	    if (i == length - 2)
		writeccr(MPC107_CCR_MIEN | MPC107_CCR_MEN | MPC107_CCR_MSTA | MPC107_CCR_TXAK);
	    /* Generate stop on last byte */
	    if (i == length - 1)
		writeccr(MPC107_CCR_MIEN | MPC107_CCR_MEN | MPC107_CCR_TXAK);
	    data[i] = readl(i2c_base + MPC107_I2CDR);
	}

	return length;
}

static int mpc107_control(struct i2c_adapter *adap, 
                        unsigned int x, unsigned long y)
{
        return 0;
}


static int mpc107_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], 
	                   int num)
{
	struct i2c_msg *pmsg;
	int i;
	int ret=0;
        unsigned long orig_jiffies = jiffies;
	
        mpc107_i2c_start();
    	
	/* Allow bus up to 1s to become not busy */
	while(readl(i2c_base + MPC107_I2CSR) & MPC107_CSR_MBB) {
                if (signal_pending(current))
		{
		    	DPRINTK("I2C: Interrupted\n");
                        return -EINTR;
		}
                if (orig_jiffies + HZ < jiffies)
		{
		    	printk("I2C: timeout\n");
                        return -EIO;
		}
                schedule();
        }

    	for (i = 0;ret >= 0 && i < num; i++) {
		pmsg = &msgs[i];
		DPRINTK("Doing %s %d bytes to 0x%02x - %d of %d messages\n",
		     pmsg->flags & I2C_M_RD ? "read" : "write",
                     pmsg->len, pmsg->addr, i + 1, num);
		if (pmsg->flags & I2C_M_RD)
                        ret = mpc107_read(pmsg->addr, pmsg->buf, pmsg->len, i);
                else
                        ret = mpc107_write(pmsg->addr, pmsg->buf, pmsg->len, i);
        }
        mpc107_i2c_stop();
        return ret;
}

static u32 mpc107_functionality(struct i2c_adapter *adap)
{
        return I2C_FUNC_SMBUS_EMUL;
}

static int mpc107_i2c_reg(struct i2c_client *client)
{
	return 0;
}


static int mpc107_i2c_unreg(struct i2c_client *client)
{
	return 0;
}

static void mpc107_i2c_inc_use(struct i2c_adapter *adap)
{
	MOD_INC_USE_COUNT;
}

static void mpc107_i2c_dec_use(struct i2c_adapter *adap)
{
	MOD_DEC_USE_COUNT;
}

#ifndef I2C_ALGO_MPC107
#define I2C_ALGO_MPC107 0x0d0000
#endif
#ifndef I2C_HW_MPC107
#define I2C_HW_MPC107 0x00
#endif

static struct i2c_algorithm mpc107_algo = {
        .name = "MPC10x algorithm",
        .id = I2C_ALGO_MPC107,
        .master_xfer = mpc107_xfer,
        .algo_control = mpc107_control,
        .functionality = mpc107_functionality,
};

static struct i2c_adapter mpc107_ops = {
    	.name = "MPC10x adapter",
        .id = I2C_HW_MPC107,
        .algo = &mpc107_algo,
        .inc_use = mpc107_i2c_inc_use,
        .dec_use = mpc107_i2c_dec_use,
        .client_register = mpc107_i2c_reg,
        .client_unregister = mpc107_i2c_unreg,
};

static int __init mpc107_i2c_init(void)
{
    	int result = 0;
        /* If we have a MPC107 it should appear as PCI device 0.
           We then need to check the EUMBBAR to find the I2c unit.
	   We can't make this a PCI driver, because everything in the 107
	   appears under one function.
	*/
    	u32 devvenid;
	char *epic_base;

    	struct pci_dev *hostbridge = pci_find_slot(0, 0);
	if (! hostbridge) {
	    	return -ENODEV;
	}
	pci_read_config_dword(hostbridge, PCI_VENDOR_ID, &devvenid);
	DPRINTK("Got ID 0x%08x\n", devvenid);
	if (devvenid != MPC10X_BRIDGE_8240 &&
	    devvenid != MPC10X_BRIDGE_107 &&
	    devvenid != MPC10X_BRIDGE_8245) {
	    	printk(KERN_WARNING "MPC10x I2C - Unknown northbridge\n");
		return -ENODEV;
	}
	
	pci_read_config_dword(hostbridge, MPC10X_CFG_EUMBBAR, &eumbbar);
	DPRINTK("Got EUMBBAR 0x%08x\n", eumbbar);
	
	/* Read the interrupt vector out of the EPIC. */
	epic_base = ioremap(eumbbar + MPC10X_EUMB_EPIC_OFFSET,
		    	    MPC10X_EUMB_EPIC_SIZE);
	if (! epic_base)
	{
	    printk(KERN_ERR "MPC10x I2C - unable to map EPIC\n");
	    return -ENODEV;
	}
	i2c_vec = readl(epic_base + 0x11020) & 0xff;
	printk("MPC10x I2C - interrupt vector %d\n", i2c_vec);
	iounmap(epic_base);
	
	if (! request_mem_region(eumbbar + MPC10X_EUMB_I2C_OFFSET,
		                 MPC10X_EUMB_I2C_SIZE,
		                 "Motorola MPC10x I2C Controller")) {
	    printk(KERN_ERR "MPC10x I2C - resource unavailable\n");
	    return -ENODEV;
	}
	
	i2c_base = ioremap(eumbbar + MPC10X_EUMB_I2C_OFFSET,
		           MPC10X_EUMB_I2C_SIZE);
	
	if (! i2c_base) {
	    	printk(KERN_ERR "MPC10x I2C - failed to map controller\n");
		result = -ENOMEM;
		goto fail_map;
	}
	
	if ((result = request_irq(i2c_vec, &mpc107_i2c_isr,
		    	          0, "mpc10x_i2c", 0)) < 0) {
	    	printk(KERN_ERR "MPC10x I2C - failed to attach interrupt\n");
		goto fail_irq;
    	}	    
	    
	
    	if  ((result = i2c_add_adapter(&mpc107_ops)) < 0) {
	    printk(KERN_ERR "MPC10x I2C - failed to add adapter\n");
	    goto fail_add;
	}
	return result;
	
fail_add:
	free_irq(i2c_vec, 0);
fail_irq:
	iounmap(i2c_base);
fail_map:
	release_mem_region(eumbbar + MPC10X_EUMB_I2C_OFFSET, MPC10X_EUMB_I2C_SIZE);
    	return result;
}

static void __exit mpc107_i2c_exit(void)
{
	free_irq(i2c_vec, 0);
    	i2c_del_adapter(&mpc107_ops);
	release_mem_region(eumbbar + MPC10X_EUMB_I2C_OFFSET, MPC10X_EUMB_I2C_SIZE);
	iounmap(i2c_base);
}

MODULE_AUTHOR("Adrian Cox <adrian@humboldt.co.uk>");
MODULE_DESCRIPTION("I2C-Bus adapter routines for Motorola MPC10x bridges");
MODULE_LICENSE("GPL");
EXPORT_NO_SYMBOLS;

module_init(mpc107_i2c_init);
module_exit(mpc107_i2c_exit);
