2011-02-26 21:29:29 +08:00
/*
* I2C driver for PKUnity - v3 SoC
* Code specific to PKUnity SoC and UniCore ISA
*
* Maintained by GUAN Xue - tao < gxt @ mprc . pku . edu . cn >
* Copyright ( C ) 2001 - 2010 Guan Xuetao
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/err.h>
# include <linux/slab.h>
# include <linux/types.h>
# include <linux/delay.h>
# include <linux/i2c.h>
# include <linux/clk.h>
# include <linux/platform_device.h>
# include <linux/io.h>
# include <mach/hardware.h>
/*
* Poll the i2c status register until the specified bit is set .
* Returns 0 if timed out ( 100 msec ) .
*/
static short poll_status ( unsigned long bit )
{
int loop_cntr = 1000 ;
if ( bit & I2C_STATUS_TFNF ) {
do {
udelay ( 10 ) ;
} while ( ! ( readl ( I2C_STATUS ) & bit ) & & ( - - loop_cntr > 0 ) ) ;
} else {
/* RXRDY handler */
do {
if ( readl ( I2C_TAR ) = = I2C_TAR_EEPROM )
msleep ( 20 ) ;
else
udelay ( 10 ) ;
} while ( ! ( readl ( I2C_RXFLR ) & 0xf ) & & ( - - loop_cntr > 0 ) ) ;
}
return ( loop_cntr > 0 ) ;
}
static int xfer_read ( struct i2c_adapter * adap , unsigned char * buf , int length )
{
int i2c_reg = * buf ;
/* Read data */
while ( length - - ) {
if ( ! poll_status ( I2C_STATUS_TFNF ) ) {
dev_dbg ( & adap - > dev , " Tx FIFO Not Full timeout \n " ) ;
return - ETIMEDOUT ;
}
/* send addr */
writel ( i2c_reg | I2C_DATACMD_WRITE , I2C_DATACMD ) ;
/* get ready to next write */
i2c_reg + + ;
/* send read CMD */
writel ( I2C_DATACMD_READ , I2C_DATACMD ) ;
/* wait until the Rx FIFO have available */
if ( ! poll_status ( I2C_STATUS_RFNE ) ) {
dev_dbg ( & adap - > dev , " RXRDY timeout \n " ) ;
return - ETIMEDOUT ;
}
/* read the data to buf */
* buf = ( readl ( I2C_DATACMD ) & I2C_DATACMD_DAT_MASK ) ;
buf + + ;
}
return 0 ;
}
static int xfer_write ( struct i2c_adapter * adap , unsigned char * buf , int length )
{
int i2c_reg = * buf ;
/* Do nothing but storing the reg_num to a static variable */
if ( i2c_reg = = - 1 ) {
printk ( KERN_WARNING " Error i2c reg \n " ) ;
return - ETIMEDOUT ;
}
if ( length = = 1 )
return 0 ;
buf + + ;
length - - ;
while ( length - - ) {
/* send addr */
writel ( i2c_reg | I2C_DATACMD_WRITE , I2C_DATACMD ) ;
/* send write CMD */
writel ( * buf | I2C_DATACMD_WRITE , I2C_DATACMD ) ;
/* wait until the Rx FIFO have available */
msleep ( 20 ) ;
/* read the data to buf */
i2c_reg + + ;
buf + + ;
}
return 0 ;
}
/*
* Generic i2c master transfer entrypoint .
*
*/
static int puv3_i2c_xfer ( struct i2c_adapter * adap , struct i2c_msg * pmsg ,
int num )
{
int i , ret ;
unsigned char swap ;
/* Disable i2c */
writel ( I2C_ENABLE_DISABLE , I2C_ENABLE ) ;
/* Set the work mode and speed*/
writel ( I2C_CON_MASTER | I2C_CON_SPEED_STD | I2C_CON_SLAVEDISABLE , I2C_CON ) ;
writel ( pmsg - > addr , I2C_TAR ) ;
/* Enable i2c */
writel ( I2C_ENABLE_ENABLE , I2C_ENABLE ) ;
dev_dbg ( & adap - > dev , " puv3_i2c_xfer: processing %d messages: \n " , num ) ;
for ( i = 0 ; i < num ; i + + ) {
dev_dbg ( & adap - > dev , " #%d: %sing %d byte%s %s 0x%02x \n " , i ,
pmsg - > flags & I2C_M_RD ? " read " : " writ " ,
pmsg - > len , pmsg - > len > 1 ? " s " : " " ,
pmsg - > flags & I2C_M_RD ? " from " : " to " , pmsg - > addr ) ;
if ( pmsg - > len & & pmsg - > buf ) { /* sanity check */
if ( pmsg - > flags & I2C_M_RD )
ret = xfer_read ( adap , pmsg - > buf , pmsg - > len ) ;
else
ret = xfer_write ( adap , pmsg - > buf , pmsg - > len ) ;
if ( ret )
return ret ;
}
dev_dbg ( & adap - > dev , " transfer complete \n " ) ;
pmsg + + ; /* next message */
}
/* XXX: fixup be16_to_cpu in bq27x00_battery.c */
if ( pmsg - > addr = = I2C_TAR_PWIC ) {
swap = pmsg - > buf [ 0 ] ;
pmsg - > buf [ 0 ] = pmsg - > buf [ 1 ] ;
pmsg - > buf [ 1 ] = swap ;
}
return i ;
}
/*
* Return list of supported functionality .
*/
static u32 puv3_i2c_func ( struct i2c_adapter * adapter )
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL ;
}
static struct i2c_algorithm puv3_i2c_algorithm = {
. master_xfer = puv3_i2c_xfer ,
. functionality = puv3_i2c_func ,
} ;
/*
* Main initialization routine .
*/
2012-11-27 15:59:38 -05:00
static int puv3_i2c_probe ( struct platform_device * pdev )
2011-02-26 21:29:29 +08:00
{
struct i2c_adapter * adapter ;
struct resource * mem ;
int rc ;
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! mem )
return - ENODEV ;
if ( ! request_mem_region ( mem - > start , resource_size ( mem ) , " puv3_i2c " ) )
return - EBUSY ;
adapter = kzalloc ( sizeof ( struct i2c_adapter ) , GFP_KERNEL ) ;
if ( adapter = = NULL ) {
2013-03-16 20:53:05 +09:00
dev_err ( & pdev - > dev , " can't allocate interface! \n " ) ;
2011-02-26 21:29:29 +08:00
rc = - ENOMEM ;
goto fail_nomem ;
}
snprintf ( adapter - > name , sizeof ( adapter - > name ) , " PUV3-I2C at 0x%08x " ,
mem - > start ) ;
adapter - > algo = & puv3_i2c_algorithm ;
adapter - > class = I2C_CLASS_HWMON ;
adapter - > dev . parent = & pdev - > dev ;
platform_set_drvdata ( pdev , adapter ) ;
adapter - > nr = pdev - > id ;
rc = i2c_add_numbered_adapter ( adapter ) ;
if ( rc ) {
dev_err ( & pdev - > dev , " Adapter '%s' registration failed \n " ,
adapter - > name ) ;
goto fail_add_adapter ;
}
dev_info ( & pdev - > dev , " PKUnity v3 i2c bus adapter. \n " ) ;
return 0 ;
fail_add_adapter :
kfree ( adapter ) ;
fail_nomem :
release_mem_region ( mem - > start , resource_size ( mem ) ) ;
return rc ;
}
2012-11-27 15:59:38 -05:00
static int puv3_i2c_remove ( struct platform_device * pdev )
2011-02-26 21:29:29 +08:00
{
struct i2c_adapter * adapter = platform_get_drvdata ( pdev ) ;
struct resource * mem ;
2013-03-09 08:16:46 +00:00
i2c_del_adapter ( adapter ) ;
2011-02-26 21:29:29 +08:00
put_device ( & pdev - > dev ) ;
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
release_mem_region ( mem - > start , resource_size ( mem ) ) ;
2013-03-09 08:16:46 +00:00
return 0 ;
2011-02-26 21:29:29 +08:00
}
2013-07-15 11:30:36 +09:00
# ifdef CONFIG_PM_SLEEP
2012-07-11 21:25:55 +02:00
static int puv3_i2c_suspend ( struct device * dev )
2011-02-26 21:29:29 +08:00
{
int poll_count ;
/* Disable the IIC */
writel ( I2C_ENABLE_DISABLE , I2C_ENABLE ) ;
for ( poll_count = 0 ; poll_count < 50 ; poll_count + + ) {
if ( readl ( I2C_ENSTATUS ) & I2C_ENSTATUS_ENABLE )
udelay ( 25 ) ;
}
return 0 ;
}
2012-07-11 21:25:55 +02:00
static SIMPLE_DEV_PM_OPS ( puv3_i2c_pm , puv3_i2c_suspend , NULL ) ;
# define PUV3_I2C_PM (&puv3_i2c_pm)
2011-02-26 21:29:29 +08:00
# else
2012-07-11 21:25:55 +02:00
# define PUV3_I2C_PM NULL
2011-02-26 21:29:29 +08:00
# endif
static struct platform_driver puv3_i2c_driver = {
. probe = puv3_i2c_probe ,
2012-11-27 15:59:38 -05:00
. remove = puv3_i2c_remove ,
2011-02-26 21:29:29 +08:00
. driver = {
. name = " PKUnity-v3-I2C " ,
2012-07-11 21:25:55 +02:00
. pm = PUV3_I2C_PM ,
2011-02-26 21:29:29 +08:00
}
} ;
2011-12-26 16:59:34 +08:00
module_platform_driver ( puv3_i2c_driver ) ;
2011-02-26 21:29:29 +08:00
MODULE_DESCRIPTION ( " PKUnity v3 I2C driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
2011-12-26 16:59:34 +08:00
MODULE_ALIAS ( " platform:puv3_i2c " ) ;