2005-09-22 21:44:06 -07:00
/*
i2c Support for Apple SMU Controller
Copyright ( c ) 2005 Benjamin Herrenschmidt , IBM Corp .
< 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 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 0213 9 , USA .
*/
# include <linux/config.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/types.h>
# include <linux/i2c.h>
# include <linux/init.h>
# include <linux/completion.h>
# include <linux/device.h>
# include <asm/prom.h>
# include <asm/of_device.h>
# include <asm/smu.h>
static int probe ;
MODULE_AUTHOR ( " Benjamin Herrenschmidt <benh@kernel.crashing.org> " ) ;
MODULE_DESCRIPTION ( " I2C driver for Apple's SMU " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_param ( probe , bool , 0 ) ;
/* Physical interface */
struct smu_iface
{
struct i2c_adapter adapter ;
struct completion complete ;
u32 busid ;
} ;
static void smu_i2c_done ( struct smu_i2c_cmd * cmd , void * misc )
{
struct smu_iface * iface = misc ;
complete ( & iface - > complete ) ;
}
/*
* SMBUS - type transfer entrypoint
*/
static s32 smu_smbus_xfer ( struct i2c_adapter * adap ,
u16 addr ,
unsigned short flags ,
char read_write ,
u8 command ,
int size ,
union i2c_smbus_data * data )
{
struct smu_iface * iface = i2c_get_adapdata ( adap ) ;
struct smu_i2c_cmd cmd ;
int rc = 0 ;
int read = ( read_write = = I2C_SMBUS_READ ) ;
cmd . info . bus = iface - > busid ;
cmd . info . devaddr = ( addr < < 1 ) | ( read ? 0x01 : 0x00 ) ;
/* Prepare datas & select mode */
switch ( size ) {
case I2C_SMBUS_QUICK :
cmd . info . type = SMU_I2C_TRANSFER_SIMPLE ;
cmd . info . datalen = 0 ;
break ;
case I2C_SMBUS_BYTE :
cmd . info . type = SMU_I2C_TRANSFER_SIMPLE ;
cmd . info . datalen = 1 ;
if ( ! read )
cmd . info . data [ 0 ] = data - > byte ;
break ;
case I2C_SMBUS_BYTE_DATA :
cmd . info . type = SMU_I2C_TRANSFER_STDSUB ;
cmd . info . datalen = 1 ;
cmd . info . sublen = 1 ;
cmd . info . subaddr [ 0 ] = command ;
cmd . info . subaddr [ 1 ] = 0 ;
cmd . info . subaddr [ 2 ] = 0 ;
if ( ! read )
cmd . info . data [ 0 ] = data - > byte ;
break ;
case I2C_SMBUS_WORD_DATA :
cmd . info . type = SMU_I2C_TRANSFER_STDSUB ;
cmd . info . datalen = 2 ;
cmd . info . sublen = 1 ;
cmd . info . subaddr [ 0 ] = command ;
cmd . info . subaddr [ 1 ] = 0 ;
cmd . info . subaddr [ 2 ] = 0 ;
if ( ! read ) {
cmd . info . data [ 0 ] = data - > byte & 0xff ;
cmd . info . data [ 1 ] = ( data - > byte > > 8 ) & 0xff ;
}
break ;
/* Note that these are broken vs. the expected smbus API where
* on reads , the lenght is actually returned from the function ,
* but I think the current API makes no sense and I don ' t want
* any driver that I haven ' t verified for correctness to go
* anywhere near a pmac i2c bus anyway . . .
*/
case I2C_SMBUS_BLOCK_DATA :
cmd . info . type = SMU_I2C_TRANSFER_STDSUB ;
cmd . info . datalen = data - > block [ 0 ] + 1 ;
if ( cmd . info . datalen > 6 )
return - EINVAL ;
if ( ! read )
memcpy ( cmd . info . data , data - > block , cmd . info . datalen ) ;
cmd . info . sublen = 1 ;
cmd . info . subaddr [ 0 ] = command ;
cmd . info . subaddr [ 1 ] = 0 ;
cmd . info . subaddr [ 2 ] = 0 ;
break ;
case I2C_SMBUS_I2C_BLOCK_DATA :
cmd . info . type = SMU_I2C_TRANSFER_STDSUB ;
cmd . info . datalen = data - > block [ 0 ] ;
if ( cmd . info . datalen > 7 )
return - EINVAL ;
if ( ! read )
memcpy ( cmd . info . data , & data - > block [ 1 ] ,
cmd . info . datalen ) ;
cmd . info . sublen = 1 ;
cmd . info . subaddr [ 0 ] = command ;
cmd . info . subaddr [ 1 ] = 0 ;
cmd . info . subaddr [ 2 ] = 0 ;
break ;
default :
return - EINVAL ;
}
/* Turn a standardsub read into a combined mode access */
if ( read_write = = I2C_SMBUS_READ & &
cmd . info . type = = SMU_I2C_TRANSFER_STDSUB )
cmd . info . type = SMU_I2C_TRANSFER_COMBINED ;
/* Finish filling command and submit it */
cmd . done = smu_i2c_done ;
cmd . misc = iface ;
rc = smu_queue_i2c ( & cmd ) ;
if ( rc < 0 )
return rc ;
wait_for_completion ( & iface - > complete ) ;
rc = cmd . status ;
if ( ! read | | rc < 0 )
return rc ;
switch ( size ) {
case I2C_SMBUS_BYTE :
case I2C_SMBUS_BYTE_DATA :
data - > byte = cmd . info . data [ 0 ] ;
break ;
case I2C_SMBUS_WORD_DATA :
data - > word = ( ( u16 ) cmd . info . data [ 1 ] ) < < 8 ;
data - > word | = cmd . info . data [ 0 ] ;
break ;
/* Note that these are broken vs. the expected smbus API where
* on reads , the lenght is actually returned from the function ,
* but I think the current API makes no sense and I don ' t want
* any driver that I haven ' t verified for correctness to go
* anywhere near a pmac i2c bus anyway . . .
*/
case I2C_SMBUS_BLOCK_DATA :
case I2C_SMBUS_I2C_BLOCK_DATA :
memcpy ( & data - > block [ 0 ] , cmd . info . data , cmd . info . datalen ) ;
break ;
}
return rc ;
}
static u32
smu_smbus_func ( struct i2c_adapter * adapter )
{
return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
I2C_FUNC_SMBUS_BLOCK_DATA ;
}
/* For now, we only handle combined mode (smbus) */
static struct i2c_algorithm smu_algorithm = {
. smbus_xfer = smu_smbus_xfer ,
. functionality = smu_smbus_func ,
} ;
static int create_iface ( struct device_node * np , struct device * dev )
{
struct smu_iface * iface ;
u32 * reg , busid ;
int rc ;
reg = ( u32 * ) get_property ( np , " reg " , NULL ) ;
if ( reg = = NULL ) {
printk ( KERN_ERR " i2c-pmac-smu: can't find bus number ! \n " ) ;
return - ENXIO ;
}
busid = * reg ;
2005-10-17 23:09:43 +02:00
iface = kzalloc ( sizeof ( struct smu_iface ) , GFP_KERNEL ) ;
2005-09-22 21:44:06 -07:00
if ( iface = = NULL ) {
printk ( KERN_ERR " i2c-pmac-smu: can't allocate inteface ! \n " ) ;
return - ENOMEM ;
}
init_completion ( & iface - > complete ) ;
iface - > busid = busid ;
dev_set_drvdata ( dev , iface ) ;
sprintf ( iface - > adapter . name , " smu-i2c-%02x " , busid ) ;
iface - > adapter . algo = & smu_algorithm ;
iface - > adapter . algo_data = NULL ;
iface - > adapter . client_register = NULL ;
iface - > adapter . client_unregister = NULL ;
i2c_set_adapdata ( & iface - > adapter , iface ) ;
iface - > adapter . dev . parent = dev ;
rc = i2c_add_adapter ( & iface - > adapter ) ;
if ( rc ) {
printk ( KERN_ERR " i2c-pamc-smu.c: Adapter %s registration "
" failed \n " , iface - > adapter . name ) ;
i2c_set_adapdata ( & iface - > adapter , NULL ) ;
}
if ( probe ) {
unsigned char addr ;
printk ( " Probe: " ) ;
for ( addr = 0x00 ; addr < = 0x7f ; addr + + ) {
if ( i2c_smbus_xfer ( & iface - > adapter , addr ,
0 , 0 , 0 , I2C_SMBUS_QUICK , NULL ) > = 0 )
printk ( " %02x " , addr ) ;
}
printk ( " \n " ) ;
}
printk ( KERN_INFO " SMU i2c bus %x registered \n " , busid ) ;
return 0 ;
}
static int dispose_iface ( struct device * dev )
{
struct smu_iface * iface = dev_get_drvdata ( dev ) ;
int rc ;
rc = i2c_del_adapter ( & iface - > adapter ) ;
i2c_set_adapdata ( & iface - > adapter , NULL ) ;
/* We aren't that prepared to deal with this... */
if ( rc )
printk ( " i2c-pmac-smu.c: Failed to remove bus %s ! \n " ,
iface - > adapter . name ) ;
dev_set_drvdata ( dev , NULL ) ;
kfree ( iface ) ;
return 0 ;
}
static int create_iface_of_platform ( struct of_device * dev ,
const struct of_device_id * match )
{
return create_iface ( dev - > node , & dev - > dev ) ;
}
static int dispose_iface_of_platform ( struct of_device * dev )
{
return dispose_iface ( & dev - > dev ) ;
}
static struct of_device_id i2c_smu_match [ ] =
{
{
. compatible = " smu-i2c " ,
} ,
{ } ,
} ;
static struct of_platform_driver i2c_smu_of_platform_driver =
{
. name = " i2c-smu " ,
. match_table = i2c_smu_match ,
. probe = create_iface_of_platform ,
. remove = dispose_iface_of_platform
} ;
static int __init i2c_pmac_smu_init ( void )
{
of_register_driver ( & i2c_smu_of_platform_driver ) ;
return 0 ;
}
static void __exit i2c_pmac_smu_cleanup ( void )
{
of_unregister_driver ( & i2c_smu_of_platform_driver ) ;
}
module_init ( i2c_pmac_smu_init ) ;
module_exit ( i2c_pmac_smu_cleanup ) ;