2012-05-30 21:02:49 +00:00
/*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
* Copyright ( C ) 2012 MIPS Technologies , Inc . All rights reserved .
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/delay.h>
# include <linux/i2c.h>
# include <linux/platform_device.h>
# define PIC32_I2CxCON 0x0000
2013-01-22 12:59:30 +01:00
# define PIC32_I2CCON_ON (1<<15)
# define PIC32_I2CCON_ACKDT (1<<5)
# define PIC32_I2CCON_ACKEN (1<<4)
# define PIC32_I2CCON_RCEN (1<<3)
# define PIC32_I2CCON_PEN (1<<2)
# define PIC32_I2CCON_RSEN (1<<1)
# define PIC32_I2CCON_SEN (1<<0)
2012-05-30 21:02:49 +00:00
# define PIC32_I2CxCONCLR 0x0004
# define PIC32_I2CxCONSET 0x0008
# define PIC32_I2CxSTAT 0x0010
# define PIC32_I2CxSTATCLR 0x0014
2013-01-22 12:59:30 +01:00
# define PIC32_I2CSTAT_ACKSTAT (1<<15)
# define PIC32_I2CSTAT_TRSTAT (1<<14)
# define PIC32_I2CSTAT_BCL (1<<10)
# define PIC32_I2CSTAT_IWCOL (1<<7)
# define PIC32_I2CSTAT_I2COV (1<<6)
2012-05-30 21:02:49 +00:00
# define PIC32_I2CxBRG 0x0040
# define PIC32_I2CxTRN 0x0050
# define PIC32_I2CxRCV 0x0060
static DEFINE_SPINLOCK ( pic32_bus_lock ) ;
2013-01-22 12:59:30 +01:00
static void __iomem * bus_xfer = ( void __iomem * ) 0xbf000600 ;
2012-05-30 21:02:49 +00:00
static void __iomem * bus_status = ( void __iomem * ) 0xbf000060 ;
2013-01-22 12:59:30 +01:00
# define DELAY() udelay(100)
2012-05-30 21:02:49 +00:00
static inline unsigned int ioready ( void )
{
return readl ( bus_status ) & 1 ;
}
static inline void wait_ioready ( void )
{
do { } while ( ! ioready ( ) ) ;
}
static inline void wait_ioclear ( void )
{
do { } while ( ioready ( ) ) ;
}
static inline void check_ioclear ( void )
{
if ( ioready ( ) ) {
do {
( void ) readl ( bus_xfer ) ;
DELAY ( ) ;
} while ( ioready ( ) ) ;
}
}
static u32 pic32_bus_readl ( u32 reg )
{
unsigned long flags ;
u32 status , val ;
spin_lock_irqsave ( & pic32_bus_lock , flags ) ;
check_ioclear ( ) ;
writel ( ( 0x01 < < 24 ) | ( reg & 0x00ffffff ) , bus_xfer ) ;
DELAY ( ) ;
wait_ioready ( ) ;
status = readl ( bus_xfer ) ;
DELAY ( ) ;
val = readl ( bus_xfer ) ;
wait_ioclear ( ) ;
spin_unlock_irqrestore ( & pic32_bus_lock , flags ) ;
return val ;
}
static void pic32_bus_writel ( u32 val , u32 reg )
{
unsigned long flags ;
u32 status ;
spin_lock_irqsave ( & pic32_bus_lock , flags ) ;
check_ioclear ( ) ;
writel ( ( 0x10 < < 24 ) | ( reg & 0x00ffffff ) , bus_xfer ) ;
DELAY ( ) ;
writel ( val , bus_xfer ) ;
DELAY ( ) ;
wait_ioready ( ) ;
status = readl ( bus_xfer ) ;
wait_ioclear ( ) ;
spin_unlock_irqrestore ( & pic32_bus_lock , flags ) ;
}
struct pic32_i2c_platform_data {
u32 base ;
struct i2c_adapter adap ;
u32 xfer_timeout ;
u32 ack_timeout ;
u32 ctl_timeout ;
} ;
static inline void pic32_i2c_start ( struct pic32_i2c_platform_data * adap )
{
pic32_bus_writel ( PIC32_I2CCON_SEN , adap - > base + PIC32_I2CxCONSET ) ;
}
static inline void pic32_i2c_stop ( struct pic32_i2c_platform_data * adap )
{
pic32_bus_writel ( PIC32_I2CCON_PEN , adap - > base + PIC32_I2CxCONSET ) ;
}
static inline void pic32_i2c_ack ( struct pic32_i2c_platform_data * adap )
{
pic32_bus_writel ( PIC32_I2CCON_ACKDT , adap - > base + PIC32_I2CxCONCLR ) ;
pic32_bus_writel ( PIC32_I2CCON_ACKEN , adap - > base + PIC32_I2CxCONSET ) ;
}
static inline void pic32_i2c_nack ( struct pic32_i2c_platform_data * adap )
{
pic32_bus_writel ( PIC32_I2CCON_ACKDT , adap - > base + PIC32_I2CxCONSET ) ;
pic32_bus_writel ( PIC32_I2CCON_ACKEN , adap - > base + PIC32_I2CxCONSET ) ;
}
static inline int pic32_i2c_idle ( struct pic32_i2c_platform_data * adap )
{
int i ;
for ( i = 0 ; i < adap - > ctl_timeout ; i + + ) {
if ( ( ( pic32_bus_readl ( adap - > base + PIC32_I2CxCON ) &
( PIC32_I2CCON_ACKEN | PIC32_I2CCON_RCEN |
PIC32_I2CCON_PEN | PIC32_I2CCON_RSEN |
PIC32_I2CCON_SEN ) ) = = 0 ) & &
( ( pic32_bus_readl ( adap - > base + PIC32_I2CxSTAT ) &
( PIC32_I2CSTAT_TRSTAT ) ) = = 0 ) )
return 0 ;
udelay ( 1 ) ;
}
return - ETIMEDOUT ;
}
static inline u32 pic32_i2c_master_write ( struct pic32_i2c_platform_data * adap ,
u32 byte )
{
pic32_bus_writel ( byte , adap - > base + PIC32_I2CxTRN ) ;
return pic32_bus_readl ( adap - > base + PIC32_I2CxSTAT ) &
PIC32_I2CSTAT_IWCOL ;
}
static inline u32 pic32_i2c_master_read ( struct pic32_i2c_platform_data * adap )
{
pic32_bus_writel ( PIC32_I2CCON_RCEN , adap - > base + PIC32_I2CxCONSET ) ;
while ( pic32_bus_readl ( adap - > base + PIC32_I2CxCON ) & PIC32_I2CCON_RCEN )
;
pic32_bus_writel ( PIC32_I2CSTAT_I2COV , adap - > base + PIC32_I2CxSTATCLR ) ;
return pic32_bus_readl ( adap - > base + PIC32_I2CxRCV ) ;
}
static int pic32_i2c_address ( struct pic32_i2c_platform_data * adap ,
unsigned int addr , int rd )
{
pic32_i2c_idle ( adap ) ;
pic32_i2c_start ( adap ) ;
pic32_i2c_idle ( adap ) ;
addr < < = 1 ;
if ( rd )
addr | = 1 ;
if ( pic32_i2c_master_write ( adap , addr ) )
return - EIO ;
pic32_i2c_idle ( adap ) ;
if ( pic32_bus_readl ( adap - > base + PIC32_I2CxSTAT ) &
PIC32_I2CSTAT_ACKSTAT )
return - EIO ;
return 0 ;
}
static int sead3_i2c_read ( struct pic32_i2c_platform_data * adap ,
unsigned char * buf , unsigned int len )
{
u32 data ;
int i ;
i = 0 ;
while ( i < len ) {
data = pic32_i2c_master_read ( adap ) ;
buf [ i + + ] = data ;
if ( i < len )
pic32_i2c_ack ( adap ) ;
else
pic32_i2c_nack ( adap ) ;
}
pic32_i2c_stop ( adap ) ;
pic32_i2c_idle ( adap ) ;
return 0 ;
}
static int sead3_i2c_write ( struct pic32_i2c_platform_data * adap ,
unsigned char * buf , unsigned int len )
{
int i ;
u32 data ;
i = 0 ;
while ( i < len ) {
data = buf [ i ] ;
if ( pic32_i2c_master_write ( adap , data ) )
return - EIO ;
pic32_i2c_idle ( adap ) ;
if ( pic32_bus_readl ( adap - > base + PIC32_I2CxSTAT ) &
PIC32_I2CSTAT_ACKSTAT )
return - EIO ;
i + + ;
}
pic32_i2c_stop ( adap ) ;
pic32_i2c_idle ( adap ) ;
return 0 ;
}
static int sead3_pic32_platform_xfer ( struct i2c_adapter * i2c_adap ,
struct i2c_msg * msgs , int num )
{
struct pic32_i2c_platform_data * adap = i2c_adap - > algo_data ;
struct i2c_msg * p ;
int i , err = 0 ;
for ( i = 0 ; i < num ; i + + ) {
# define __BUFSIZE 80
int ii ;
static char buf [ __BUFSIZE ] ;
char * b = buf ;
p = & msgs [ i ] ;
b + = sprintf ( buf , " [%d bytes] " , p - > len ) ;
if ( ( p - > flags & I2C_M_RD ) = = 0 ) {
for ( ii = 0 ; ii < p - > len ; ii + + ) {
if ( b < & buf [ __BUFSIZE - 4 ] ) {
b + = sprintf ( b , " %02x " , p - > buf [ ii ] ) ;
} else {
strcat ( b , " ... " ) ;
break ;
}
}
}
}
for ( i = 0 ; ! err & & i < num ; i + + ) {
p = & msgs [ i ] ;
err = pic32_i2c_address ( adap , p - > addr , p - > flags & I2C_M_RD ) ;
if ( err | | ! p - > len )
continue ;
if ( p - > flags & I2C_M_RD )
err = sead3_i2c_read ( adap , p - > buf , p - > len ) ;
else
err = sead3_i2c_write ( adap , p - > buf , p - > len ) ;
}
/* Return the number of messages processed, or the error code. */
if ( err = = 0 )
err = num ;
return err ;
}
static u32 sead3_pic32_platform_func ( struct i2c_adapter * adap )
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL ;
}
static const struct i2c_algorithm sead3_platform_algo = {
. master_xfer = sead3_pic32_platform_xfer ,
. functionality = sead3_pic32_platform_func ,
} ;
static void sead3_i2c_platform_setup ( struct pic32_i2c_platform_data * priv )
{
pic32_bus_writel ( 500 , priv - > base + PIC32_I2CxBRG ) ;
pic32_bus_writel ( PIC32_I2CCON_ON , priv - > base + PIC32_I2CxCONCLR ) ;
pic32_bus_writel ( PIC32_I2CCON_ON , priv - > base + PIC32_I2CxCONSET ) ;
pic32_bus_writel ( PIC32_I2CSTAT_BCL | PIC32_I2CSTAT_IWCOL ,
priv - > base + PIC32_I2CxSTATCLR ) ;
}
2012-12-21 14:04:39 -08:00
static int sead3_i2c_platform_probe ( struct platform_device * pdev )
2012-05-30 21:02:49 +00:00
{
struct pic32_i2c_platform_data * priv ;
struct resource * r ;
int ret ;
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! r ) {
ret = - ENODEV ;
goto out ;
}
priv = kzalloc ( sizeof ( struct pic32_i2c_platform_data ) , GFP_KERNEL ) ;
if ( ! priv ) {
ret = - ENOMEM ;
goto out ;
}
priv - > base = r - > start ;
if ( ! priv - > base ) {
ret = - EBUSY ;
goto out_mem ;
}
priv - > xfer_timeout = 200 ;
priv - > ack_timeout = 200 ;
priv - > ctl_timeout = 200 ;
priv - > adap . nr = pdev - > id ;
priv - > adap . algo = & sead3_platform_algo ;
priv - > adap . algo_data = priv ;
priv - > adap . dev . parent = & pdev - > dev ;
strlcpy ( priv - > adap . name , " SEAD3 PIC32 " , sizeof ( priv - > adap . name ) ) ;
sead3_i2c_platform_setup ( priv ) ;
ret = i2c_add_numbered_adapter ( & priv - > adap ) ;
if ( ret = = 0 ) {
platform_set_drvdata ( pdev , priv ) ;
return 0 ;
}
out_mem :
kfree ( priv ) ;
out :
return ret ;
}
2012-12-21 14:04:39 -08:00
static int sead3_i2c_platform_remove ( struct platform_device * pdev )
2012-05-30 21:02:49 +00:00
{
struct pic32_i2c_platform_data * priv = platform_get_drvdata ( pdev ) ;
platform_set_drvdata ( pdev , NULL ) ;
i2c_del_adapter ( & priv - > adap ) ;
kfree ( priv ) ;
return 0 ;
}
# ifdef CONFIG_PM
static int sead3_i2c_platform_suspend ( struct platform_device * pdev ,
pm_message_t state )
{
dev_dbg ( & pdev - > dev , " i2c_platform_disable \n " ) ;
return 0 ;
}
static int sead3_i2c_platform_resume ( struct platform_device * pdev )
{
struct pic32_i2c_platform_data * priv = platform_get_drvdata ( pdev ) ;
dev_dbg ( & pdev - > dev , " sead3_i2c_platform_setup \n " ) ;
sead3_i2c_platform_setup ( priv ) ;
return 0 ;
}
# else
# define sead3_i2c_platform_suspend NULL
# define sead3_i2c_platform_resume NULL
# endif
static struct platform_driver sead3_i2c_platform_driver = {
. driver = {
. name = " sead3-i2c " ,
} ,
. probe = sead3_i2c_platform_probe ,
2012-12-21 14:04:39 -08:00
. remove = sead3_i2c_platform_remove ,
2012-05-30 21:02:49 +00:00
. suspend = sead3_i2c_platform_suspend ,
. resume = sead3_i2c_platform_resume ,
} ;
static int __init sead3_i2c_platform_init ( void )
{
return platform_driver_register ( & sead3_i2c_platform_driver ) ;
}
module_init ( sead3_i2c_platform_init ) ;
static void __exit sead3_i2c_platform_exit ( void )
{
platform_driver_unregister ( & sead3_i2c_platform_driver ) ;
}
module_exit ( sead3_i2c_platform_exit ) ;
MODULE_AUTHOR ( " Chris Dearman, MIPS Technologies INC. " ) ;
MODULE_DESCRIPTION ( " SEAD3 PIC32 I2C driver " ) ;
MODULE_LICENSE ( " GPL " ) ;