2005-04-16 15:20:36 -07:00
/*
* arch / ppc / platforms / pmac_low_i2c . c
*
* Copyright ( C ) 2003 Ben . Herrenschmidt ( benh @ kernel . crashing . org )
*
* 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 file contains some low - level i2c access routines that
* need to be used by various bits of the PowerMac platform code
* at times where the real asynchronous & interrupt driven driver
* cannot be used . The API borrows some semantics from the darwin
* driver in order to ease the implementation of the platform
* properties parser
*/
# include <linux/config.h>
# include <linux/types.h>
# include <linux/delay.h>
# include <linux/sched.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/adb.h>
# include <linux/pmu.h>
# include <asm/keylargo.h>
# include <asm/uninorth.h>
# include <asm/io.h>
# include <asm/prom.h>
# include <asm/machdep.h>
# include <asm/pmac_low_i2c.h>
# define MAX_LOW_I2C_HOST 4
# if 1
# define DBG(x...) do {\
printk ( KERN_DEBUG " KW: " x ) ; \
} while ( 0 )
# else
# define DBGG(x...)
# endif
struct low_i2c_host ;
typedef int ( * low_i2c_func_t ) ( struct low_i2c_host * host , u8 addr , u8 sub , u8 * data , int len ) ;
struct low_i2c_host
{
struct device_node * np ; /* OF device node */
struct semaphore mutex ; /* Access mutex for use by i2c-keywest */
low_i2c_func_t func ; /* Access function */
int is_open : 1 ; /* Poor man's access control */
int mode ; /* Current mode */
int channel ; /* Current channel */
int num_channels ; /* Number of channels */
2005-04-25 07:55:58 -07:00
void __iomem * base ; /* For keywest-i2c, base address */
2005-04-16 15:20:36 -07:00
int bsteps ; /* And register stepping */
int speed ; /* And speed */
} ;
static struct low_i2c_host low_i2c_hosts [ MAX_LOW_I2C_HOST ] ;
/* No locking is necessary on allocation, we are running way before
* anything can race with us
*/
static struct low_i2c_host * find_low_i2c_host ( struct device_node * np )
{
int i ;
for ( i = 0 ; i < MAX_LOW_I2C_HOST ; i + + )
if ( low_i2c_hosts [ i ] . np = = np )
return & low_i2c_hosts [ i ] ;
return NULL ;
}
/*
*
* i2c - keywest implementation ( UniNorth , U2 , U3 , Keylargo ' s )
*
*/
/*
* Keywest i2c definitions borrowed from drivers / i2c / i2c - keywest . h ,
* should be moved somewhere in include / asm - ppc /
*/
/* Register indices */
typedef enum {
reg_mode = 0 ,
reg_control ,
reg_status ,
reg_isr ,
reg_ier ,
reg_addr ,
reg_subaddr ,
reg_data
} reg_t ;
/* Mode register */
# define KW_I2C_MODE_100KHZ 0x00
# define KW_I2C_MODE_50KHZ 0x01
# define KW_I2C_MODE_25KHZ 0x02
# define KW_I2C_MODE_DUMB 0x00
# define KW_I2C_MODE_STANDARD 0x04
# define KW_I2C_MODE_STANDARDSUB 0x08
# define KW_I2C_MODE_COMBINED 0x0C
# define KW_I2C_MODE_MODE_MASK 0x0C
# define KW_I2C_MODE_CHAN_MASK 0xF0
/* Control register */
# define KW_I2C_CTL_AAK 0x01
# define KW_I2C_CTL_XADDR 0x02
# define KW_I2C_CTL_STOP 0x04
# define KW_I2C_CTL_START 0x08
/* Status register */
# define KW_I2C_STAT_BUSY 0x01
# define KW_I2C_STAT_LAST_AAK 0x02
# define KW_I2C_STAT_LAST_RW 0x04
# define KW_I2C_STAT_SDA 0x08
# define KW_I2C_STAT_SCL 0x10
/* IER & ISR registers */
# define KW_I2C_IRQ_DATA 0x01
# define KW_I2C_IRQ_ADDR 0x02
# define KW_I2C_IRQ_STOP 0x04
# define KW_I2C_IRQ_START 0x08
# define KW_I2C_IRQ_MASK 0x0F
/* State machine states */
enum {
state_idle ,
state_addr ,
state_read ,
state_write ,
state_stop ,
state_dead
} ;
# define WRONG_STATE(name) do {\
printk ( KERN_DEBUG " KW: wrong state. Got %s, state: %s (isr: %02x) \n " , \
name , __kw_state_names [ state ] , isr ) ; \
} while ( 0 )
static const char * __kw_state_names [ ] = {
" state_idle " ,
" state_addr " ,
" state_read " ,
" state_write " ,
" state_stop " ,
" state_dead "
} ;
static inline u8 __kw_read_reg ( struct low_i2c_host * host , reg_t reg )
{
2005-04-25 07:55:58 -07:00
return in_8 ( host - > base + ( ( ( unsigned ) reg ) < < host - > bsteps ) ) ;
2005-04-16 15:20:36 -07:00
}
static inline void __kw_write_reg ( struct low_i2c_host * host , reg_t reg , u8 val )
{
2005-04-25 07:55:58 -07:00
out_8 ( host - > base + ( ( ( unsigned ) reg ) < < host - > bsteps ) , val ) ;
2005-04-16 15:20:36 -07:00
( void ) __kw_read_reg ( host , reg_subaddr ) ;
}
# define kw_write_reg(reg, val) __kw_write_reg(host, reg, val)
# define kw_read_reg(reg) __kw_read_reg(host, reg)
/* Don't schedule, the g5 fan controller is too
* timing sensitive
*/
static u8 kw_wait_interrupt ( struct low_i2c_host * host )
{
int i ;
u8 isr ;
for ( i = 0 ; i < 200000 ; i + + ) {
isr = kw_read_reg ( reg_isr ) & KW_I2C_IRQ_MASK ;
if ( isr ! = 0 )
return isr ;
udelay ( 1 ) ;
}
return isr ;
}
static int kw_handle_interrupt ( struct low_i2c_host * host , int state , int rw , int * rc , u8 * * data , int * len , u8 isr )
{
u8 ack ;
if ( isr = = 0 ) {
if ( state ! = state_stop ) {
DBG ( " KW: Timeout ! \n " ) ;
* rc = - EIO ;
goto stop ;
}
if ( state = = state_stop ) {
ack = kw_read_reg ( reg_status ) ;
if ( ! ( ack & KW_I2C_STAT_BUSY ) ) {
state = state_idle ;
kw_write_reg ( reg_ier , 0x00 ) ;
}
}
return state ;
}
if ( isr & KW_I2C_IRQ_ADDR ) {
ack = kw_read_reg ( reg_status ) ;
if ( state ! = state_addr ) {
kw_write_reg ( reg_isr , KW_I2C_IRQ_ADDR ) ;
WRONG_STATE ( " KW_I2C_IRQ_ADDR " ) ;
* rc = - EIO ;
goto stop ;
}
if ( ( ack & KW_I2C_STAT_LAST_AAK ) = = 0 ) {
* rc = - ENODEV ;
DBG ( " KW: NAK on address \n " ) ;
return state_stop ;
} else {
if ( rw ) {
state = state_read ;
if ( * len > 1 )
kw_write_reg ( reg_control , KW_I2C_CTL_AAK ) ;
} else {
state = state_write ;
kw_write_reg ( reg_data , * * data ) ;
( * data ) + + ; ( * len ) - - ;
}
}
kw_write_reg ( reg_isr , KW_I2C_IRQ_ADDR ) ;
}
if ( isr & KW_I2C_IRQ_DATA ) {
if ( state = = state_read ) {
* * data = kw_read_reg ( reg_data ) ;
( * data ) + + ; ( * len ) - - ;
kw_write_reg ( reg_isr , KW_I2C_IRQ_DATA ) ;
if ( ( * len ) = = 0 )
state = state_stop ;
else if ( ( * len ) = = 1 )
kw_write_reg ( reg_control , 0 ) ;
} else if ( state = = state_write ) {
ack = kw_read_reg ( reg_status ) ;
if ( ( ack & KW_I2C_STAT_LAST_AAK ) = = 0 ) {
DBG ( " KW: nack on data write \n " ) ;
* rc = - EIO ;
goto stop ;
} else if ( * len ) {
kw_write_reg ( reg_data , * * data ) ;
( * data ) + + ; ( * len ) - - ;
} else {
kw_write_reg ( reg_control , KW_I2C_CTL_STOP ) ;
state = state_stop ;
* rc = 0 ;
}
kw_write_reg ( reg_isr , KW_I2C_IRQ_DATA ) ;
} else {
kw_write_reg ( reg_isr , KW_I2C_IRQ_DATA ) ;
WRONG_STATE ( " KW_I2C_IRQ_DATA " ) ;
if ( state ! = state_stop ) {
* rc = - EIO ;
goto stop ;
}
}
}
if ( isr & KW_I2C_IRQ_STOP ) {
kw_write_reg ( reg_isr , KW_I2C_IRQ_STOP ) ;
if ( state ! = state_stop ) {
WRONG_STATE ( " KW_I2C_IRQ_STOP " ) ;
* rc = - EIO ;
}
return state_idle ;
}
if ( isr & KW_I2C_IRQ_START )
kw_write_reg ( reg_isr , KW_I2C_IRQ_START ) ;
return state ;
stop :
kw_write_reg ( reg_control , KW_I2C_CTL_STOP ) ;
return state_stop ;
}
static int keywest_low_i2c_func ( struct low_i2c_host * host , u8 addr , u8 subaddr , u8 * data , int len )
{
u8 mode_reg = host - > speed ;
int state = state_addr ;
int rc = 0 ;
/* Setup mode & subaddress if any */
switch ( host - > mode ) {
case pmac_low_i2c_mode_dumb :
printk ( KERN_ERR " low_i2c: Dumb mode not supported ! \n " ) ;
return - EINVAL ;
case pmac_low_i2c_mode_std :
mode_reg | = KW_I2C_MODE_STANDARD ;
break ;
case pmac_low_i2c_mode_stdsub :
mode_reg | = KW_I2C_MODE_STANDARDSUB ;
kw_write_reg ( reg_subaddr , subaddr ) ;
break ;
case pmac_low_i2c_mode_combined :
mode_reg | = KW_I2C_MODE_COMBINED ;
kw_write_reg ( reg_subaddr , subaddr ) ;
break ;
}
/* Setup channel & clear pending irqs */
kw_write_reg ( reg_isr , kw_read_reg ( reg_isr ) ) ;
kw_write_reg ( reg_mode , mode_reg | ( host - > channel < < 4 ) ) ;
kw_write_reg ( reg_status , 0 ) ;
/* Set up address and r/w bit */
kw_write_reg ( reg_addr , addr ) ;
/* Start sending address & disable interrupt*/
kw_write_reg ( reg_ier , 0 /*KW_I2C_IRQ_MASK*/ ) ;
kw_write_reg ( reg_control , KW_I2C_CTL_XADDR ) ;
/* State machine, to turn into an interrupt handler */
while ( state ! = state_idle ) {
u8 isr = kw_wait_interrupt ( host ) ;
state = kw_handle_interrupt ( host , state , addr & 1 , & rc , & data , & len , isr ) ;
}
return rc ;
}
static void keywest_low_i2c_add ( struct device_node * np )
{
struct low_i2c_host * host = find_low_i2c_host ( NULL ) ;
unsigned long * psteps , * prate , steps , aoffset = 0 ;
struct device_node * parent ;
if ( host = = NULL ) {
printk ( KERN_ERR " low_i2c: Can't allocate host for %s \n " ,
np - > full_name ) ;
return ;
}
memset ( host , 0 , sizeof ( * host ) ) ;
init_MUTEX ( & host - > mutex ) ;
host - > np = of_node_get ( np ) ;
psteps = ( unsigned long * ) get_property ( np , " AAPL,address-step " , NULL ) ;
steps = psteps ? ( * psteps ) : 0x10 ;
for ( host - > bsteps = 0 ; ( steps & 0x01 ) = = 0 ; host - > bsteps + + )
steps > > = 1 ;
parent = of_get_parent ( np ) ;
host - > num_channels = 1 ;
if ( parent & & parent - > name [ 0 ] = = ' u ' ) {
host - > num_channels = 2 ;
aoffset = 3 ;
}
/* Select interface rate */
host - > speed = KW_I2C_MODE_100KHZ ;
prate = ( unsigned long * ) get_property ( np , " AAPL,i2c-rate " , NULL ) ;
if ( prate ) switch ( * prate ) {
case 100 :
host - > speed = KW_I2C_MODE_100KHZ ;
break ;
case 50 :
host - > speed = KW_I2C_MODE_50KHZ ;
break ;
case 25 :
host - > speed = KW_I2C_MODE_25KHZ ;
break ;
}
host - > mode = pmac_low_i2c_mode_std ;
2005-04-25 07:55:58 -07:00
host - > base = ioremap ( np - > addrs [ 0 ] . address + aoffset ,
2005-04-16 15:20:36 -07:00
np - > addrs [ 0 ] . size ) ;
host - > func = keywest_low_i2c_func ;
}
/*
*
* PMU implementation
*
*/
# ifdef CONFIG_ADB_PMU
static int pmu_low_i2c_func ( struct low_i2c_host * host , u8 addr , u8 sub , u8 * data , int len )
{
// TODO
return - ENODEV ;
}
static void pmu_low_i2c_add ( struct device_node * np )
{
struct low_i2c_host * host = find_low_i2c_host ( NULL ) ;
if ( host = = NULL ) {
printk ( KERN_ERR " low_i2c: Can't allocate host for %s \n " ,
np - > full_name ) ;
return ;
}
memset ( host , 0 , sizeof ( * host ) ) ;
init_MUTEX ( & host - > mutex ) ;
host - > np = of_node_get ( np ) ;
host - > num_channels = 3 ;
host - > mode = pmac_low_i2c_mode_std ;
host - > func = pmu_low_i2c_func ;
}
# endif /* CONFIG_ADB_PMU */
void __init pmac_init_low_i2c ( void )
{
struct device_node * np ;
/* Probe keywest-i2c busses */
np = of_find_compatible_node ( NULL , " i2c " , " keywest-i2c " ) ;
while ( np ) {
keywest_low_i2c_add ( np ) ;
np = of_find_compatible_node ( np , " i2c " , " keywest-i2c " ) ;
}
# ifdef CONFIG_ADB_PMU
/* Probe PMU busses */
np = of_find_node_by_name ( NULL , " via-pmu " ) ;
if ( np )
pmu_low_i2c_add ( np ) ;
# endif /* CONFIG_ADB_PMU */
/* TODO: Add CUDA support as well */
}
int pmac_low_i2c_lock ( struct device_node * np )
{
struct low_i2c_host * host = find_low_i2c_host ( np ) ;
if ( ! host )
return - ENODEV ;
down ( & host - > mutex ) ;
return 0 ;
}
EXPORT_SYMBOL ( pmac_low_i2c_lock ) ;
int pmac_low_i2c_unlock ( struct device_node * np )
{
struct low_i2c_host * host = find_low_i2c_host ( np ) ;
if ( ! host )
return - ENODEV ;
up ( & host - > mutex ) ;
return 0 ;
}
EXPORT_SYMBOL ( pmac_low_i2c_unlock ) ;
int pmac_low_i2c_open ( struct device_node * np , int channel )
{
struct low_i2c_host * host = find_low_i2c_host ( np ) ;
if ( ! host )
return - ENODEV ;
if ( channel > = host - > num_channels )
return - EINVAL ;
down ( & host - > mutex ) ;
host - > is_open = 1 ;
host - > channel = channel ;
return 0 ;
}
EXPORT_SYMBOL ( pmac_low_i2c_open ) ;
int pmac_low_i2c_close ( struct device_node * np )
{
struct low_i2c_host * host = find_low_i2c_host ( np ) ;
if ( ! host )
return - ENODEV ;
host - > is_open = 0 ;
up ( & host - > mutex ) ;
return 0 ;
}
EXPORT_SYMBOL ( pmac_low_i2c_close ) ;
int pmac_low_i2c_setmode ( struct device_node * np , int mode )
{
struct low_i2c_host * host = find_low_i2c_host ( np ) ;
if ( ! host )
return - ENODEV ;
WARN_ON ( ! host - > is_open ) ;
host - > mode = mode ;
return 0 ;
}
EXPORT_SYMBOL ( pmac_low_i2c_setmode ) ;
int pmac_low_i2c_xfer ( struct device_node * np , u8 addrdir , u8 subaddr , u8 * data , int len )
{
struct low_i2c_host * host = find_low_i2c_host ( np ) ;
if ( ! host )
return - ENODEV ;
WARN_ON ( ! host - > is_open ) ;
return host - > func ( host , addrdir , subaddr , data , len ) ;
}
EXPORT_SYMBOL ( pmac_low_i2c_xfer ) ;