2005-04-17 02:20:36 +04:00
/*
* i2c - au1550 . c : SMBus ( i2c ) adapter for Alchemy PSC interface
* Copyright ( C ) 2004 Embedded Edge , LLC < dan @ embeddededge . com >
*
* 2.6 port by Matt Porter < mporter @ kernel . crashing . org >
*
* The documentation describes this as an SMBus controller , but it doesn ' t
* understand any of the SMBus protocol in hardware . It ' s really an I2C
* controller that could emulate most of the SMBus in software .
*
* This is just a skeleton adapter to use with the Au1550 PSC
* algorithm . It was developed for the Pb1550 , but will work with
* any Au1550 board that has a similar PSC configuration .
*
* 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/delay.h>
# include <linux/kernel.h>
# include <linux/module.h>
2008-01-27 20:14:52 +03:00
# include <linux/platform_device.h>
2005-04-17 02:20:36 +04:00
# include <linux/init.h>
# include <linux/errno.h>
# include <linux/i2c.h>
2008-01-27 20:14:52 +03:00
# include <linux/slab.h>
2005-04-17 02:20:36 +04:00
2006-08-14 01:37:13 +04:00
# include <asm/mach-au1x00/au1xxx.h>
2005-04-17 02:20:36 +04:00
# include <asm/mach-au1x00/au1xxx_psc.h>
2008-01-27 20:14:52 +03:00
struct i2c_au1550_data {
u32 psc_base ;
int xfer_timeout ;
int ack_timeout ;
struct i2c_adapter adap ;
struct resource * ioarea ;
} ;
2005-04-17 02:20:36 +04:00
static int
wait_xfer_done ( struct i2c_au1550_data * adap )
{
u32 stat ;
int i ;
volatile psc_smb_t * sp ;
sp = ( volatile psc_smb_t * ) ( adap - > psc_base ) ;
2007-10-14 01:56:33 +04:00
/* Wait for Tx Buffer Empty
2005-04-17 02:20:36 +04:00
*/
for ( i = 0 ; i < adap - > xfer_timeout ; i + + ) {
2007-10-14 01:56:33 +04:00
stat = sp - > psc_smbstat ;
2005-04-17 02:20:36 +04:00
au_sync ( ) ;
2007-10-14 01:56:33 +04:00
if ( ( stat & PSC_SMBSTAT_TE ) ! = 0 )
2005-04-17 02:20:36 +04:00
return 0 ;
2007-10-14 01:56:33 +04:00
2005-04-17 02:20:36 +04:00
udelay ( 1 ) ;
}
return - ETIMEDOUT ;
}
static int
wait_ack ( struct i2c_au1550_data * adap )
{
u32 stat ;
volatile psc_smb_t * sp ;
if ( wait_xfer_done ( adap ) )
return - ETIMEDOUT ;
sp = ( volatile psc_smb_t * ) ( adap - > psc_base ) ;
stat = sp - > psc_smbevnt ;
au_sync ( ) ;
if ( ( stat & ( PSC_SMBEVNT_DN | PSC_SMBEVNT_AN | PSC_SMBEVNT_AL ) ) ! = 0 )
return - ETIMEDOUT ;
return 0 ;
}
static int
wait_master_done ( struct i2c_au1550_data * adap )
{
u32 stat ;
int i ;
volatile psc_smb_t * sp ;
sp = ( volatile psc_smb_t * ) ( adap - > psc_base ) ;
/* Wait for Master Done.
*/
for ( i = 0 ; i < adap - > xfer_timeout ; i + + ) {
stat = sp - > psc_smbevnt ;
au_sync ( ) ;
if ( ( stat & PSC_SMBEVNT_MD ) ! = 0 )
return 0 ;
udelay ( 1 ) ;
}
return - ETIMEDOUT ;
}
static int
2008-01-27 20:14:52 +03:00
do_address ( struct i2c_au1550_data * adap , unsigned int addr , int rd , int q )
2005-04-17 02:20:36 +04:00
{
volatile psc_smb_t * sp ;
u32 stat ;
sp = ( volatile psc_smb_t * ) ( adap - > psc_base ) ;
/* Reset the FIFOs, clear events.
*/
2006-08-14 01:35:40 +04:00
stat = sp - > psc_smbstat ;
2005-04-17 02:20:36 +04:00
sp - > psc_smbevnt = PSC_SMBEVNT_ALLCLR ;
au_sync ( ) ;
2006-08-14 01:35:40 +04:00
if ( ! ( stat & PSC_SMBSTAT_TE ) | | ! ( stat & PSC_SMBSTAT_RE ) ) {
sp - > psc_smbpcr = PSC_SMBPCR_DC ;
2005-04-17 02:20:36 +04:00
au_sync ( ) ;
2006-08-14 01:35:40 +04:00
do {
stat = sp - > psc_smbpcr ;
au_sync ( ) ;
} while ( ( stat & PSC_SMBPCR_DC ) ! = 0 ) ;
udelay ( 50 ) ;
}
2005-04-17 02:20:36 +04:00
/* Write out the i2c chip address and specify operation
*/
addr < < = 1 ;
if ( rd )
addr | = 1 ;
2008-01-27 20:14:52 +03:00
/* zero-byte xfers stop immediately */
if ( q )
addr | = PSC_SMBTXRX_STP ;
2005-04-17 02:20:36 +04:00
/* Put byte into fifo, start up master.
*/
sp - > psc_smbtxrx = addr ;
au_sync ( ) ;
sp - > psc_smbpcr = PSC_SMBPCR_MS ;
au_sync ( ) ;
if ( wait_ack ( adap ) )
return - EIO ;
2008-01-27 20:14:52 +03:00
return ( q ) ? wait_master_done ( adap ) : 0 ;
2005-04-17 02:20:36 +04:00
}
static u32
wait_for_rx_byte ( struct i2c_au1550_data * adap , u32 * ret_data )
{
int j ;
u32 data , stat ;
volatile psc_smb_t * sp ;
if ( wait_xfer_done ( adap ) )
return - EIO ;
sp = ( volatile psc_smb_t * ) ( adap - > psc_base ) ;
j = adap - > xfer_timeout * 100 ;
do {
j - - ;
if ( j < = 0 )
return - EIO ;
stat = sp - > psc_smbstat ;
au_sync ( ) ;
if ( ( stat & PSC_SMBSTAT_RE ) = = 0 )
j = 0 ;
else
udelay ( 1 ) ;
} while ( j > 0 ) ;
data = sp - > psc_smbtxrx ;
au_sync ( ) ;
* ret_data = data ;
return 0 ;
}
static int
i2c_read ( struct i2c_au1550_data * adap , unsigned char * buf ,
unsigned int len )
{
int i ;
u32 data ;
volatile psc_smb_t * sp ;
if ( len = = 0 )
return 0 ;
/* A read is performed by stuffing the transmit fifo with
* zero bytes for timing , waiting for bytes to appear in the
* receive fifo , then reading the bytes .
*/
sp = ( volatile psc_smb_t * ) ( adap - > psc_base ) ;
i = 0 ;
while ( i < ( len - 1 ) ) {
sp - > psc_smbtxrx = 0 ;
au_sync ( ) ;
if ( wait_for_rx_byte ( adap , & data ) )
return - EIO ;
buf [ i ] = data ;
i + + ;
}
/* The last byte has to indicate transfer done.
*/
sp - > psc_smbtxrx = PSC_SMBTXRX_STP ;
au_sync ( ) ;
if ( wait_master_done ( adap ) )
return - EIO ;
data = sp - > psc_smbtxrx ;
au_sync ( ) ;
buf [ i ] = data ;
return 0 ;
}
static int
i2c_write ( struct i2c_au1550_data * adap , unsigned char * buf ,
unsigned int len )
{
int i ;
u32 data ;
volatile psc_smb_t * sp ;
if ( len = = 0 )
return 0 ;
sp = ( volatile psc_smb_t * ) ( adap - > psc_base ) ;
i = 0 ;
while ( i < ( len - 1 ) ) {
data = buf [ i ] ;
sp - > psc_smbtxrx = data ;
au_sync ( ) ;
if ( wait_ack ( adap ) )
return - EIO ;
i + + ;
}
/* The last byte has to indicate transfer done.
*/
data = buf [ i ] ;
data | = PSC_SMBTXRX_STP ;
sp - > psc_smbtxrx = data ;
au_sync ( ) ;
if ( wait_master_done ( adap ) )
return - EIO ;
return 0 ;
}
static int
au1550_xfer ( struct i2c_adapter * i2c_adap , struct i2c_msg * msgs , int num )
{
struct i2c_au1550_data * adap = i2c_adap - > algo_data ;
struct i2c_msg * p ;
int i , err = 0 ;
for ( i = 0 ; ! err & & i < num ; i + + ) {
p = & msgs [ i ] ;
2008-01-27 20:14:52 +03:00
err = do_address ( adap , p - > addr , p - > flags & I2C_M_RD ,
( p - > len = = 0 ) ) ;
2005-04-17 02:20:36 +04:00
if ( err | | ! p - > len )
continue ;
if ( p - > flags & I2C_M_RD )
err = i2c_read ( adap , p - > buf , p - > len ) ;
else
err = 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
au1550_func ( struct i2c_adapter * adap )
{
2006-08-14 01:36:27 +04:00
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL ;
2005-04-17 02:20:36 +04:00
}
2006-09-04 00:39:46 +04:00
static const struct i2c_algorithm au1550_algo = {
2005-04-17 02:20:36 +04:00
. master_xfer = au1550_xfer ,
. functionality = au1550_func ,
} ;
/*
* registering functions to load algorithms at runtime
* Prior to calling us , the 50 MHz clock frequency and routing
* must have been set up for the PSC indicated by the adapter .
*/
2008-01-27 20:14:52 +03:00
static int __devinit
i2c_au1550_probe ( struct platform_device * pdev )
2005-04-17 02:20:36 +04:00
{
2008-01-27 20:14:52 +03:00
struct i2c_au1550_data * priv ;
volatile psc_smb_t * sp ;
struct resource * r ;
u32 stat ;
int ret ;
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! r ) {
ret = - ENODEV ;
goto out ;
}
2005-04-17 02:20:36 +04:00
2008-01-27 20:14:52 +03:00
priv = kzalloc ( sizeof ( struct i2c_au1550_data ) , GFP_KERNEL ) ;
if ( ! priv ) {
ret = - ENOMEM ;
goto out ;
}
priv - > ioarea = request_mem_region ( r - > start , r - > end - r - > start + 1 ,
pdev - > name ) ;
if ( ! priv - > ioarea ) {
ret = - EBUSY ;
goto out_mem ;
}
priv - > psc_base = r - > start ;
priv - > xfer_timeout = 200 ;
priv - > ack_timeout = 200 ;
priv - > adap . id = I2C_HW_AU1550_PSC ;
priv - > adap . nr = pdev - > id ;
priv - > adap . algo = & au1550_algo ;
priv - > adap . algo_data = priv ;
priv - > adap . dev . parent = & pdev - > dev ;
strlcpy ( priv - > adap . name , " Au1xxx PSC I2C " , sizeof ( priv - > adap . name ) ) ;
2005-04-17 02:20:36 +04:00
/* Now, set up the PSC for SMBus PIO mode.
*/
2008-01-27 20:14:52 +03:00
sp = ( volatile psc_smb_t * ) priv - > psc_base ;
2005-04-17 02:20:36 +04:00
sp - > psc_ctrl = PSC_CTRL_DISABLE ;
au_sync ( ) ;
sp - > psc_sel = PSC_SEL_PS_SMBUSMODE ;
sp - > psc_smbcfg = 0 ;
au_sync ( ) ;
sp - > psc_ctrl = PSC_CTRL_ENABLE ;
au_sync ( ) ;
do {
stat = sp - > psc_smbstat ;
au_sync ( ) ;
} while ( ( stat & PSC_SMBSTAT_SR ) = = 0 ) ;
sp - > psc_smbcfg = ( PSC_SMBCFG_RT_FIFO8 | PSC_SMBCFG_TT_FIFO8 |
PSC_SMBCFG_DD_DISABLE ) ;
/* Divide by 8 to get a 6.25 MHz clock. The later protocol
* timings are based on this clock .
*/
sp - > psc_smbcfg | = PSC_SMBCFG_SET_DIV ( PSC_SMBCFG_DIV8 ) ;
sp - > psc_smbmsk = PSC_SMBMSK_ALLMASK ;
au_sync ( ) ;
/* Set the protocol timer values. See Table 71 in the
* Au1550 Data Book for standard timing values .
*/
sp - > psc_smbtmr = PSC_SMBTMR_SET_TH ( 0 ) | PSC_SMBTMR_SET_PS ( 15 ) | \
PSC_SMBTMR_SET_PU ( 15 ) | PSC_SMBTMR_SET_SH ( 15 ) | \
PSC_SMBTMR_SET_SU ( 15 ) | PSC_SMBTMR_SET_CL ( 15 ) | \
PSC_SMBTMR_SET_CH ( 15 ) ;
au_sync ( ) ;
sp - > psc_smbcfg | = PSC_SMBCFG_DE_ENABLE ;
do {
stat = sp - > psc_smbstat ;
au_sync ( ) ;
} while ( ( stat & PSC_SMBSTAT_DR ) = = 0 ) ;
2008-01-27 20:14:52 +03:00
ret = i2c_add_numbered_adapter ( & priv - > adap ) ;
if ( ret = = 0 ) {
platform_set_drvdata ( pdev , priv ) ;
return 0 ;
}
/* disable the PSC */
sp - > psc_smbcfg = 0 ;
sp - > psc_ctrl = PSC_CTRL_DISABLE ;
au_sync ( ) ;
2005-04-17 02:20:36 +04:00
2008-01-27 20:14:52 +03:00
release_resource ( priv - > ioarea ) ;
kfree ( priv - > ioarea ) ;
out_mem :
kfree ( priv ) ;
out :
return ret ;
}
2005-04-17 02:20:36 +04:00
2008-01-27 20:14:52 +03:00
static int __devexit
i2c_au1550_remove ( struct platform_device * pdev )
2005-04-17 02:20:36 +04:00
{
2008-01-27 20:14:52 +03:00
struct i2c_au1550_data * priv = platform_get_drvdata ( pdev ) ;
volatile psc_smb_t * sp = ( volatile psc_smb_t * ) priv - > psc_base ;
platform_set_drvdata ( pdev , NULL ) ;
i2c_del_adapter ( & priv - > adap ) ;
sp - > psc_smbcfg = 0 ;
sp - > psc_ctrl = PSC_CTRL_DISABLE ;
au_sync ( ) ;
release_resource ( priv - > ioarea ) ;
kfree ( priv - > ioarea ) ;
kfree ( priv ) ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
static int
2008-01-27 20:14:52 +03:00
i2c_au1550_suspend ( struct platform_device * pdev , pm_message_t state )
2005-04-17 02:20:36 +04:00
{
2008-01-27 20:14:52 +03:00
struct i2c_au1550_data * priv = platform_get_drvdata ( pdev ) ;
volatile psc_smb_t * sp = ( volatile psc_smb_t * ) priv - > psc_base ;
sp - > psc_ctrl = PSC_CTRL_SUSPEND ;
au_sync ( ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int
2008-01-27 20:14:52 +03:00
i2c_au1550_resume ( struct platform_device * pdev )
2005-04-17 02:20:36 +04:00
{
2008-01-27 20:14:52 +03:00
struct i2c_au1550_data * priv = platform_get_drvdata ( pdev ) ;
volatile psc_smb_t * sp = ( volatile psc_smb_t * ) priv - > psc_base ;
sp - > psc_ctrl = PSC_CTRL_ENABLE ;
au_sync ( ) ;
while ( ! ( sp - > psc_smbstat & PSC_SMBSTAT_SR ) )
au_sync ( ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2008-01-27 20:14:52 +03:00
static struct platform_driver au1xpsc_smbus_driver = {
. driver = {
. name = " au1xpsc_smbus " ,
. owner = THIS_MODULE ,
} ,
. probe = i2c_au1550_probe ,
. remove = __devexit_p ( i2c_au1550_remove ) ,
. suspend = i2c_au1550_suspend ,
. resume = i2c_au1550_resume ,
2005-04-17 02:20:36 +04:00
} ;
static int __init
i2c_au1550_init ( void )
{
2008-01-27 20:14:52 +03:00
return platform_driver_register ( & au1xpsc_smbus_driver ) ;
2005-04-17 02:20:36 +04:00
}
static void __exit
i2c_au1550_exit ( void )
{
2008-01-27 20:14:52 +03:00
platform_driver_unregister ( & au1xpsc_smbus_driver ) ;
2005-04-17 02:20:36 +04:00
}
MODULE_AUTHOR ( " Dan Malek, Embedded Edge, LLC. " ) ;
MODULE_DESCRIPTION ( " SMBus adapter Alchemy pb1550 " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( i2c_au1550_init ) ;
module_exit ( i2c_au1550_exit ) ;