/*
 *  i2c-pnx0105.c: driver for PNX833X I2C (IP0105 Block)
 *    Copyright (C) 2006 Nikita Youshchenko <yoush@debian.org>
 *  Partially based on i2c-pca-isa driver, Copyright (C) 2004 Arcom Control
 *  Systems.
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/i2c-id.h>
#include <linux/i2c-pnx0105.h>
#include <linux/i2c-algo-pca.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <linux/errno.h>
#include "prjconfig.h"

#include "../algos/i2c-algo-pca.h"

static inline unsigned long i2c_pnx0105_in(struct i2c_pnx0105_dev *dev, int offset)
{
	return readl((unsigned long *)(dev->base + offset));
}

static inline void i2c_pnx0105_out(struct i2c_pnx0105_dev *dev, int offset, unsigned long value)
{
	writel(value, (unsigned long *)(dev->base + offset));
}

static int i2c_pnx0105_getown(struct i2c_algo_pca_data *algo_data)
{
	struct i2c_pnx0105_dev *dev = container_of(algo_data, struct i2c_pnx0105_dev, algo_data);
	return dev->bus_addr;
}

static int i2c_pnx0105_getclock(struct i2c_algo_pca_data *algo_data)
{
	struct i2c_pnx0105_dev *dev = container_of(algo_data, struct i2c_pnx0105_dev, algo_data);
	return dev->clock;
}

static void i2c_pnx0105_writebyte(struct i2c_algo_pca_data *algo_data, int reg, int val)
{
	struct i2c_pnx0105_dev *dev = container_of(algo_data, struct i2c_pnx0105_dev, algo_data);
	int old_si;

#ifdef DEBUG
	static char *names[] = { "T/O", "DAT", "ADR", "CON" };
	printk("i2c_pnx0105(0x%08lx): write %s <= %#04x\n", dev->base, names[reg], val);
#endif
	
	switch(reg) {
		
		case I2C_PCA_DAT:
			i2c_pnx0105_out(dev, I2C_PNX0105_DAT, val & 255);
			break;
			
		case I2C_PCA_ADR:
			dev->last_address = val;
			i2c_pnx0105_out(dev, I2C_PNX0105_ADDRESS, val & 255);
			break;
			
		case I2C_PCA_CON:
			/* Possible RACE: just after init, or after stop,
			   SI bit is zero. That means that when STA bit
			   is written, hardware starts to process it
			   immediately. It could complete very fast (or
			   perhaps thread may get preempted), so when code
			   several lines below is executed, SI could already
			   be set to indicate that STA processing is complete.
			   In this case, SI must NOT be cleared here, so
			   hardware won't continue and send slave address
			   before it was written to register.
			   However, if SI bit is currently set, hardware
			   won't process command immediately, and SI should
			   be cleared at the bottom, to enable processing.

			   Solution: just check SI here, and clear it only
			   if it was set before any new value was written
			   to command register.
			 */
			old_si = i2c_pnx0105_in(dev, I2C_PNX0105_INT_STATUS) & 1;

			i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, val & 255);
			
			/* We have to process STO bit separately */
			if (val & I2C_PCA_CON_STO)
				i2c_pnx0105_out(dev, I2C_PNX0105_STOP, 1);

			/* And also SI bit ... */
			if (old_si && !(val & I2C_PCA_CON_SI)) {
				i2c_pnx0105_out(dev, I2C_PNX0105_INT_CLEAR, 1);
				if (dev->irq > -1 && !(val & I2C_PCA_CON_STO))
					i2c_pnx0105_out(dev, I2C_PNX0105_INT_ENABLE, 1);
			}

			break;
			
		default:
			BUG();
	}
}

static int i2c_pnx0105_readbyte(struct i2c_algo_pca_data *algo_data, int reg)
{
	struct i2c_pnx0105_dev *dev = container_of(algo_data, struct i2c_pnx0105_dev, algo_data);
	int res = 0;

	switch(reg) {
		
		case I2C_PCA_STA:
			if (dev->timeout) {
				res = 0xff;
				dev->timeout = 0;
			} else
				res = i2c_pnx0105_in(dev, I2C_PNX0105_STATUS) & 255;
			break;
			
		case I2C_PCA_DAT:
			res = i2c_pnx0105_in(dev, I2C_PNX0105_DAT) & 255;
			break;
			
		case I2C_PCA_CON:
			res = i2c_pnx0105_in(dev, I2C_PNX0105_CONTROL) & 255;

			/* Read SI bit from elsewhere */
			if (i2c_pnx0105_in(dev, I2C_PNX0105_INT_STATUS))
				res |= I2C_PCA_CON_SI;
			else
				res &= ~I2C_PCA_CON_SI;

			break;

		default:
			BUG();
	}

#ifdef DEBUG
	{
		static char *names[] = { "STA", "DAT", "ADR", "CON" };	
		printk("i2c_pnx0105(0x%08lx): read %s => %#04x\n", dev->base, names[reg], res);
	}
#endif
	return res;
}

static inline void i2c_pnx0105_reset(struct i2c_pnx0105_dev *dev)
{
	unsigned long val = i2c_pnx0105_in(dev, I2C_PNX0105_CONTROL) & 0x47;
	i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, val | 0x40);
	i2c_pnx0105_out(dev, I2C_PNX0105_STOP, 1);
	i2c_pnx0105_out(dev, I2C_PNX0105_INT_CLEAR, 1);
	udelay(200);
	i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, val);
}

static inline int i2c_pnx0105_intr_condition(struct i2c_pnx0105_dev *dev)
{
	return i2c_pnx0105_in(dev, I2C_PNX0105_INT_STATUS) & 1;
}

static int i2c_pnx0105_waitforinterrupt(struct i2c_algo_pca_data *algo_data)
{
	struct i2c_pnx0105_dev *dev = container_of(algo_data, struct i2c_pnx0105_dev, algo_data);

	/* Set some timeout */
//#define JIFFIES_TO_WAIT	((HZ / 100) + 1)	/* attempt to model 10 milliseconds */
#define JIFFIES_TO_WAIT	((HZ / 5) + 1)	/* give it 200ms to see if that helps */

	if (dev->irq > -1) {
		wait_event_timeout(dev->wait,
			       	i2c_pnx0105_intr_condition(dev), JIFFIES_TO_WAIT);
	} else {
		unsigned long end = jiffies + JIFFIES_TO_WAIT;
		while (!i2c_pnx0105_intr_condition(dev) &&
			       	time_before(jiffies, end))
		{
		    if (in_atomic()) 
		    {
		        udelay(100);
		    }
		    else
		    {
		        schedule();
		    }
		}
	}

	if (i2c_pnx0105_intr_condition(dev))
		return 0;

	pr_err("i2c_pnx0105: timeout on IRQ %d (base 0x%x) and reset, last address=0x%x\n", dev->irq, dev->base, dev->last_address);
	pr_info("  control=%lx, dat=%lx, status=%lx, setpins=%lx, intstatus=%lx, intenable=%lx, powerdown=%lx\n", i2c_pnx0105_in(dev, I2C_PNX0105_CONTROL),
		i2c_pnx0105_in(dev, I2C_PNX0105_DAT),
		i2c_pnx0105_in(dev, I2C_PNX0105_STATUS),
		i2c_pnx0105_in(dev, I2C_PNX0105_SET_PINS),
		i2c_pnx0105_in(dev, I2C_PNX0105_INT_STATUS),
		i2c_pnx0105_in(dev, I2C_PNX0105_INT_ENABLE),
		i2c_pnx0105_in(dev, I2C_PNX0105_POWER_DOWN)
		);

	/* Timeout. Reset device and make next status read to return 0xff */
	i2c_pnx0105_reset(dev);
	dev->timeout = 1;
	return -EIO;	/* Ignored anyway */
}

/* This function is called from Primary ISR */
static void recover_from_hardware_error(struct i2c_adapter * i2c_adap)
{
        //TBD
}


static int i2c_pnx0105_enable_slave(struct i2c_algo_pca_data *algo_data, int bufLen)
{
	struct i2c_pnx0105_dev *dev = container_of(algo_data, struct i2c_pnx0105_dev, algo_data);
    int    i2c_state;
	
	if(!dev->slave_enable) {
		//enable slave
		if( bufLen > 0) {
            dev->slave_rd_buf = kmalloc(bufLen, GFP_KERNEL);
            if (dev->slave_rd_buf == NULL)
				return -ENOMEM;
				
			dev->slave_wr_buf = kmalloc(bufLen, GFP_KERNEL);
			if (dev->slave_wr_buf == NULL)
				return -ENOMEM;

	  	    dev->slave_buflen = 0;
	    	dev->slave_bufsize = bufLen;
	    	dev->slave_rd_bufindex = 1;
            dev->slave_wr_bufindex = 0;
			dev->slave_enable = true;

            dev->i2c_mode = i2c_pnx0105_idle;

            i2c_pnx0105_out(dev, I2C_PNX0105_DAT, 0x00);
            //clear interrupt
            i2c_pnx0105_out(dev, I2C_PNX0105_INT_CLEAR, 1);
            udelay(200);

            i2c_state = i2c_pnx0105_in(dev, I2C_PNX0105_CONTROL);

            i2c_state &= ~I2C_PCA_CON_STA;
            i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, i2c_state & 0xFF);

            i2c_state |= I2C_PCA_CON_ENSIO;
            i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, i2c_state & 0xFF);

            i2c_state |= I2C_PCA_CON_AA;
            i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL,(i2c_state & 0xFF));

            //enable the interrupt
            i2c_pnx0105_out(dev, I2C_PNX0105_INT_ENABLE, 1);
		}
    }
    else {
        if(bufLen > 0) {
            return -EBUSY;
        }
        else {
            //disable the salve
            if(dev->slave_rd_buf != NULL) {
                kfree(dev->slave_rd_buf);
            }
            
            if(dev->slave_wr_buf != NULL) {
                kfree(dev->slave_wr_buf);
            }

  			dev->slave_rd_buf = NULL;
            dev->slave_wr_buf = NULL;
		  	dev->slave_buflen = 0;// for transimtter
		  	dev->slave_wr_bufindex = 0;//do cnt read/write number of bytes
            dev->slave_rd_bufindex = 0;
		  	dev->slave_bufsize = 0;//for reciever
			dev->slave_enable = false;	

            i2c_pnx0105_out(dev, I2C_PNX0105_STOP, 1);

            //disable the INT
            i2c_pnx0105_out(dev, I2C_PNX0105_INT_ENABLE, 0);
        }
    }
  
    return 0;			
}

static int i2c_pnx0105_read_slave_data(struct i2c_algo_pca_data *algo_data, unsigned char *user_data)
{
	struct i2c_pnx0105_dev *dev = container_of(algo_data, struct i2c_pnx0105_dev, algo_data);
	int    nr_of_bytes = dev->slave_rd_bufindex - 1;

	if( dev->i2c_mode == i2c_pnx0105_slave_receiver )
	{
		return 0;
	}
	
	if(dev->slave_enable) {
        nr_of_bytes = dev->slave_rd_bufindex - 1;

		//first byte is the data len;		
		if( nr_of_bytes > 0 ) { 
            dev->slave_rd_buf[0] = (unsigned char)(nr_of_bytes & 0xFF);
			copy_to_user(user_data, dev->slave_rd_buf, dev->slave_rd_bufindex);
		}
	}

    dev->slave_rd_bufindex = 1;

	//return total len
	return (nr_of_bytes + 1);
}

static int i2c_pnx0105_write_slave_data(struct i2c_algo_pca_data *algo_data, unsigned char *user_data)
{
	struct i2c_pnx0105_dev *dev = container_of(algo_data, struct i2c_pnx0105_dev, algo_data);
	int    nr_of_bytes = 0;

	if( dev->i2c_mode == i2c_pnx0105_slave_transmitter )
	{
		return 0;
	}

	if(dev->slave_enable) {
		nr_of_bytes = *user_data; 
		user_data++;
		
		if(nr_of_bytes > 0) {
			dev->slave_buflen = nr_of_bytes;  
			copy_from_user(dev->slave_wr_buf, user_data, dev->slave_buflen);
			return (nr_of_bytes + 1);
		}
	}
	else {
		return -1;
	}
	
	return 0;		
}

static __inline void i2c_interrupt_idle(struct i2c_adapter * i2c_adap, struct i2c_pnx0105_dev *dev, int i2c_state)
{
    int state = i2c_pnx0105_in( dev, I2C_PNX0105_CONTROL);
	   
    switch(i2c_state) {
        case 0x60: 
            /* Own SLA+W has been received; ACK has been returned */
            dev->slave_rd_bufindex = 1; /* reset buffer pointer !!! */
                
            //send ACK in the slave mode
            //ACK bit will be returned after next byte reception 
            state |= I2C_PCA_CON_AA;
            i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, state);
            dev->i2c_mode = i2c_pnx0105_slave_receiver;
            i2c_pnx0105_out(dev, I2C_PNX0105_INT_CLEAR, 1);
            break;

        case 0xA8: /* Own SLA+R has been received; ACK has been returned */
            dev->slave_wr_bufindex = 0;
            dev->i2c_mode = i2c_pnx0105_master_transmitter;

			if( dev->slave_buflen > 0 ) {
				i2c_pnx0105_out(dev, I2C_PNX0105_DAT,*( dev->slave_wr_buf + dev->slave_wr_bufindex ));
				dev->slave_wr_bufindex++;

				/* Data byte will be transmitted */
				state |= I2C_PCA_CON_AA;
				i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, state);
			}
			else
			{
				i2c_pnx0105_out( dev, I2C_PNX0105_DAT, 0 & 255 );

				/* Last data byte will be transmitted */
				state &= ~I2C_PCA_CON_AA;								
				i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, state); 
			}

            i2c_pnx0105_out( dev, I2C_PNX0105_INT_CLEAR, 1 );
            break;

        case 0x70: /* General call address (0x00) has been received; ACK has been returned */
            /* fall through; */

        case 0x78: /* Arbitration lost in SLA+R/~W as master; General call address (0x00) has been received; ACK has been returned */
            /* fall through; */

        default:
            recover_from_hardware_error(i2c_adap);
            break;
       }
}

static __inline void i2c_interrupt_slave_receiver(struct i2c_adapter * i2c_adap, struct i2c_pnx0105_dev *dev, int i2c_state)
{
    int state = i2c_pnx0105_in(dev, I2C_PNX0105_CONTROL);

    switch (i2c_state) {
        case 0x80: /* Previously addressed with own SLA; DATA byte has been received; ACK has been returned */
            if (dev->slave_rd_bufindex < dev->slave_bufsize) {
                *( dev->slave_rd_buf + dev->slave_rd_bufindex++ ) = i2c_pnx0105_in( dev, I2C_PNX0105_DAT ) & 255;
            }
            else {
                (void)(i2c_pnx0105_in(dev, I2C_PNX0105_DAT) & 255);
            }
                
            //ACK bit will be returned after next byte reception 
            state |= I2C_PCA_CON_AA;
            i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, state);
            i2c_pnx0105_out(dev, I2C_PNX0105_INT_CLEAR, 1);
            break;

        case 0x88: /* Previously addressed with own SLA; DATA byte has been received; NOT ACK has been returned */
            if (dev->slave_rd_bufindex < dev->slave_bufsize) {
                *( dev->slave_rd_buf + dev->slave_rd_bufindex++ ) = i2c_pnx0105_in(dev, I2C_PNX0105_DAT) & 255;
            }
            else {
                (void)(i2c_pnx0105_in(dev, I2C_PNX0105_DAT ) & 255);
            }

            //ACK bit will be returned after next byte reception 
            state |= I2C_PCA_CON_AA;
            i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, state);
            i2c_pnx0105_out(dev, I2C_PNX0105_INT_CLEAR, 1);
            break;

        case 0xA0: /* A STOP condition or repeated START has been received while still address as SLV/REC or SLV/TRX */
            // Slave will be enabled again when data is out of the buffer
            state |= I2C_PCA_CON_AA;
            i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, state);

            dev->i2c_mode = i2c_pnx0105_idle;
                         
            i2c_pnx0105_out(dev, I2C_PNX0105_INT_CLEAR, 1);                
            break;

        case 0x70: /* General call address (0x00) has been received; ACK has been returned */
            /* fall through; */
        case 0x78: /* Arbitration lost in SLA+R/~W as master; General call address (0x00) has been received; ACK has been returned */
            /* fall through; */
        case 0x90: /* Previously addressed with General call; DATA byte has been received; ACK has been returned */
            /* fall through; */
        case 0x98: /* Previously addressed with General call; DATA byte has been received; NOT ACK has been returned */
            /* fall through; */
        default:
            recover_from_hardware_error( i2c_adap );
            break;
    }
}

static __inline void i2c_interrupt_slave_transmitter(struct i2c_adapter * i2c_adap, struct i2c_pnx0105_dev *dev, int i2c_state)
{
    int state = i2c_pnx0105_in(dev, I2C_PNX0105_CONTROL);
	  
    switch (i2c_state) {
        case 0xA0: /* A STOP condition or repeated START has been received while still address as SLV/TRX */
				   // Slave will be enabled again when data is out of the buffer
            state |= I2C_PCA_CON_AA;                             
            i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, state);
                        
            dev->i2c_mode = i2c_pnx0105_idle;         
            dev->slave_buflen = 0;

            i2c_pnx0105_out(dev, I2C_PNX0105_INT_CLEAR, 1);   /* Reset i2c interrupt */

            break;

        case 0xB8: /* Data byte in I2DAT has been transmitted; ACK has been received */
            if (dev->slave_wr_bufindex <= dev->slave_bufsize && dev->slave_buflen > 0 ) {
                i2c_pnx0105_out(dev, I2C_PNX0105_DAT,*( dev->slave_wr_buf + dev->slave_wr_bufindex ));
                dev->slave_wr_bufindex++;
                       
                if (dev->slave_wr_bufindex < dev->slave_buflen) {
                    /* Data byte will be transmitted */
                    state |= I2C_PCA_CON_AA;
                    i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, state);
                }
                else {
                    /* Last data byte will be transmitted */
                    state &= ~I2C_PCA_CON_AA;								
                    i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, state); 
                }                
        
                i2c_pnx0105_out(dev, I2C_PNX0105_INT_CLEAR, 1);
            }
            else {
                    i2c_pnx0105_out(dev, I2C_PNX0105_DAT, 0 & 255 );
                     /* Last data byte will be transmitted */
                    state &= ~I2C_PCA_CON_AA;								
                    i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, state); 
                 
		    // Don't reset i2c interrupt 
                i2c_pnx0105_out(dev, I2C_PNX0105_INT_CLEAR, 1); 
            }
            break;

        case 0xC0: /* Data byte in I2DAT has been transmitted; NOT ACK has been received */
        case 0xC8:
            /* Last data byte in I2DAT has been transmitted (AA=0); ACK has been received */
            state |= I2C_PCA_CON_AA;
            i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, state);

            dev->i2c_mode = i2c_pnx0105_idle;
            dev->slave_buflen = 0;
              
            i2c_pnx0105_out(dev, I2C_PNX0105_INT_CLEAR, 1);  
            break;

        case 0x70: /* General call address (0x00) has been received; ACK has been returned */
            /* fall through; */
        case 0x78: /* Arbitration lost in SLA+R/~W as master; General call address (0x00) has been received; ACK has been returned */
            /* fall through; */
        default: /* Miscellaneous: No relevant state information available; SI=0 */
            recover_from_hardware_error(i2c_adap);
            break;
    }
}


static irqreturn_t i2c_pnx0105_interrupt(int this_irq, void *dev_id)
{
    struct i2c_pnx0105_dev *dev = (struct i2c_pnx0105_dev *)dev_id;
    int res = 0;

    //slave mode
    if(dev->slave_enable) {
        //read the I2C status
        res = i2c_pnx0105_in(dev, I2C_PNX0105_STATUS) & 255;

        //procress the status
        switch(dev->i2c_mode) {
            case i2c_pnx0105_idle:
                //process the idle
                i2c_interrupt_idle(&dev->adap, dev, res); 
                break;
  			
            case i2c_pnx0105_slave_receiver:
                //process the slave receiver
                i2c_interrupt_slave_receiver(&dev->adap, dev, res);
                break;
  			
            case i2c_pnx0105_master_transmitter:
                i2c_interrupt_slave_transmitter(&dev->adap, dev, res);
                //process the slave transmitter
                break;
  			
            default:
                break;  			
        }
    }
    //master mode
    else {		
        /* Disable interrupt for a while (until it's actually handled) */
        i2c_pnx0105_out(dev, I2C_PNX0105_INT_ENABLE, 0);
	
        /* Wake up any process waiting for this interrupt */
        wake_up_interruptible(&dev->wait);
    }
	
    return IRQ_HANDLED;
}

static int __devinit i2c_pnx0105_probe(struct platform_device *pdev)
{
	struct i2c_pnx0105_dev *dev = (struct i2c_pnx0105_dev *) pdev->dev.platform_data;
	struct i2c_algo_pca_data *algo_data = &dev->algo_data;
	struct i2c_adapter *adap = &dev->adap;
	int res;

#if defined (CONFIG_I2C0_DISABLE)
	if (pdev->id == 0) {
		return 0;
	}
#endif

#if defined (CONFIG_I2C1_DISABLE)
	if (pdev->id == 1) {
		return 0;
	}
#endif

	algo_data->get_own = i2c_pnx0105_getown;
	algo_data->get_clock = i2c_pnx0105_getclock;
	algo_data->write_byte = i2c_pnx0105_writebyte;
	algo_data->read_byte = i2c_pnx0105_readbyte;
	algo_data->wait_for_interrupt = i2c_pnx0105_waitforinterrupt;
	algo_data->enable_slave = i2c_pnx0105_enable_slave;
	algo_data->rd_slave_data = i2c_pnx0105_read_slave_data;
	algo_data->wr_slave_data = i2c_pnx0105_write_slave_data;
	
	adap->owner = THIS_MODULE;
	adap->id = (I2C_HW_A_PNX0105 +  pdev->id); //use the paltform ID
	adap->algo_data = algo_data;
	strncpy(adap->name, pdev->name, I2C_NAME_SIZE);

	dev->timeout = 0;
	init_waitqueue_head(&dev->wait);
	
	if (request_region(dev->base, I2C_PNX0105_IO_SIZE, "i2c-pnx") == 0) {
		printk(KERN_ERR "i2c-pnx0105: request_region(0x%08lx) failed\n",
				dev->base);
		return -EBUSY;
	}

	/* Disable interrupt - just to be sure ... */
	i2c_pnx0105_out(dev, I2C_PNX0105_INT_ENABLE, 0);

	if (dev->irq > -1) {
		res = request_irq(dev->irq, i2c_pnx0105_interrupt, IRQF_SHARED, "i2c-pnx", dev);
		if (res < 0) {
			printk(KERN_ERR "i2c-pnx0105: request_irq() failed\n");
			goto err_region;
		}
	}

	disable_irq(dev->irq);   /* disable i2c interrupt in Interrupt controller */
	enable_irq(dev->irq);    /* Enable i2c interrupt in Interrupt controller */

	/* Rude attempt to probe hardware, to avoid future hangups if it is
	   not responding */
	i2c_pnx0105_out(dev, I2C_PNX0105_CONTROL, 0x60);
	udelay(200);
	res = i2c_pnx0105_intr_condition(dev) ? 0 : -ENODEV;
	i2c_pnx0105_reset(dev);

	if (res < 0) {
		printk(KERN_ERR "i2c-pnx0105: device at 0x%08lx is not responding\n",
				dev->base);
		goto err_irq;
	}
	
	res = i2c_pca_add_bus(adap);
	if (res < 0) {
		printk(KERN_ERR "i2c-pnx0105: i2c_pca_add_bus() failed\n");
		goto err_irq;
	}
	
	printk(KERN_INFO "i2c-pnx0105: registered device at 0x%08lx", dev->base);
	if (dev->irq > -1)
		printk(", irq %d", dev->irq);
	printk("\n");

	return 0;

err_irq:
	if (dev->irq > -1)
		free_irq(dev->irq, dev);

err_region:
	release_region(dev->base, I2C_PNX0105_IO_SIZE);

	return res;
}

static int __devexit i2c_pnx0105_remove(struct platform_device *pdev)
{
	struct i2c_pnx0105_dev *dev = (struct i2c_pnx0105_dev *) pdev->dev.platform_data;
	struct i2c_adapter *adap = &dev->adap;
	int res;

	res = i2c_del_adapter(adap);
	if (res < 0)
		return res;

	if (dev->irq > -1)
		free_irq(dev->irq, dev);

	release_region(dev->base, I2C_PNX0105_IO_SIZE);

	return 0;
}

static struct platform_driver i2c_pnx0105_driver = {
	.probe		= i2c_pnx0105_probe,
	.remove		= __devexit_p(i2c_pnx0105_remove),
	.driver		= {
        .owner	= THIS_MODULE,
		.name	= "i2c-pnx0105",
	},
};

static int __init i2c_pnx0105_init(void)
{
	return platform_driver_register(&i2c_pnx0105_driver);
}

static void __exit i2c_pnx0105_cleanup(void)
{
	platform_driver_unregister(&i2c_pnx0105_driver);
}

module_init(i2c_pnx0105_init);
module_exit(i2c_pnx0105_cleanup);

MODULE_AUTHOR("Cindy Chen <cindy.c.chen@nxp.com>");
MODULE_DESCRIPTION("PNX833X I2C driver");
MODULE_LICENSE("GPL");


