2014-12-13 21:01:05 +03:00
/*
* IBM OPAL I2C driver
* Copyright ( C ) 2014 IBM
*
* 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 .
*/
# include <linux/device.h>
# include <linux/i2c.h>
# include <linux/kernel.h>
# include <linux/mm.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <asm/firmware.h>
# include <asm/opal.h>
static int i2c_opal_translate_error ( int rc )
{
switch ( rc ) {
case OPAL_NO_MEM :
return - ENOMEM ;
case OPAL_PARAMETER :
return - EINVAL ;
case OPAL_I2C_ARBT_LOST :
return - EAGAIN ;
case OPAL_I2C_TIMEOUT :
return - ETIMEDOUT ;
case OPAL_I2C_NACK_RCVD :
return - ENXIO ;
case OPAL_I2C_STOP_ERR :
return - EBUSY ;
default :
return - EIO ;
}
}
static int i2c_opal_send_request ( u32 bus_id , struct opal_i2c_request * req )
{
struct opal_msg msg ;
int token , rc ;
token = opal_async_get_token_interruptible ( ) ;
if ( token < 0 ) {
if ( token ! = - ERESTARTSYS )
pr_err ( " Failed to get the async token \n " ) ;
return token ;
}
rc = opal_i2c_request ( token , bus_id , req ) ;
if ( rc ! = OPAL_ASYNC_COMPLETION ) {
rc = i2c_opal_translate_error ( rc ) ;
goto exit ;
}
rc = opal_async_wait_response ( token , & msg ) ;
if ( rc )
goto exit ;
rc = be64_to_cpu ( msg . params [ 1 ] ) ;
if ( rc ! = OPAL_SUCCESS ) {
rc = i2c_opal_translate_error ( rc ) ;
goto exit ;
}
exit :
opal_async_release_token ( token ) ;
return rc ;
}
static int i2c_opal_master_xfer ( struct i2c_adapter * adap , struct i2c_msg * msgs ,
int num )
{
unsigned long opal_id = ( unsigned long ) adap - > algo_data ;
struct opal_i2c_request req ;
int rc , i ;
/* We only support fairly simple combinations here of one
* or two messages
*/
memset ( & req , 0 , sizeof ( req ) ) ;
switch ( num ) {
case 0 :
return 0 ;
case 1 :
req . type = ( msgs [ 0 ] . flags & I2C_M_RD ) ?
OPAL_I2C_RAW_READ : OPAL_I2C_RAW_WRITE ;
req . addr = cpu_to_be16 ( msgs [ 0 ] . addr ) ;
req . size = cpu_to_be32 ( msgs [ 0 ] . len ) ;
req . buffer_ra = cpu_to_be64 ( __pa ( msgs [ 0 ] . buf ) ) ;
break ;
case 2 :
2015-03-13 17:25:33 +03:00
req . type = ( msgs [ 1 ] . flags & I2C_M_RD ) ?
OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE ;
2014-12-13 21:01:05 +03:00
req . addr = cpu_to_be16 ( msgs [ 0 ] . addr ) ;
req . subaddr_sz = msgs [ 0 ] . len ;
for ( i = 0 ; i < msgs [ 0 ] . len ; i + + )
req . subaddr = ( req . subaddr < < 8 ) | msgs [ 0 ] . buf [ i ] ;
req . subaddr = cpu_to_be32 ( req . subaddr ) ;
req . size = cpu_to_be32 ( msgs [ 1 ] . len ) ;
req . buffer_ra = cpu_to_be64 ( __pa ( msgs [ 1 ] . buf ) ) ;
break ;
default :
return - EOPNOTSUPP ;
}
rc = i2c_opal_send_request ( opal_id , & req ) ;
if ( rc )
return rc ;
return num ;
}
static int i2c_opal_smbus_xfer ( struct i2c_adapter * adap , u16 addr ,
unsigned short flags , char read_write ,
u8 command , int size , union i2c_smbus_data * data )
{
unsigned long opal_id = ( unsigned long ) adap - > algo_data ;
struct opal_i2c_request req ;
u8 local [ 2 ] ;
int rc ;
memset ( & req , 0 , sizeof ( req ) ) ;
req . addr = cpu_to_be16 ( addr ) ;
switch ( size ) {
case I2C_SMBUS_BYTE :
req . buffer_ra = cpu_to_be64 ( __pa ( & data - > byte ) ) ;
req . size = cpu_to_be32 ( 1 ) ;
/* Fall through */
case I2C_SMBUS_QUICK :
req . type = ( read_write = = I2C_SMBUS_READ ) ?
OPAL_I2C_RAW_READ : OPAL_I2C_RAW_WRITE ;
break ;
case I2C_SMBUS_BYTE_DATA :
req . buffer_ra = cpu_to_be64 ( __pa ( & data - > byte ) ) ;
req . size = cpu_to_be32 ( 1 ) ;
req . subaddr = cpu_to_be32 ( command ) ;
req . subaddr_sz = 1 ;
req . type = ( read_write = = I2C_SMBUS_READ ) ?
OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE ;
break ;
case I2C_SMBUS_WORD_DATA :
if ( ! read_write ) {
local [ 0 ] = data - > word & 0xff ;
local [ 1 ] = ( data - > word > > 8 ) & 0xff ;
}
req . buffer_ra = cpu_to_be64 ( __pa ( local ) ) ;
req . size = cpu_to_be32 ( 2 ) ;
req . subaddr = cpu_to_be32 ( command ) ;
req . subaddr_sz = 1 ;
req . type = ( read_write = = I2C_SMBUS_READ ) ?
OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE ;
break ;
case I2C_SMBUS_I2C_BLOCK_DATA :
req . buffer_ra = cpu_to_be64 ( __pa ( & data - > block [ 1 ] ) ) ;
req . size = cpu_to_be32 ( data - > block [ 0 ] ) ;
req . subaddr = cpu_to_be32 ( command ) ;
req . subaddr_sz = 1 ;
req . type = ( read_write = = I2C_SMBUS_READ ) ?
OPAL_I2C_SM_READ : OPAL_I2C_SM_WRITE ;
break ;
default :
return - EINVAL ;
}
rc = i2c_opal_send_request ( opal_id , & req ) ;
if ( ! rc & & read_write & & size = = I2C_SMBUS_WORD_DATA ) {
data - > word = ( ( u16 ) local [ 1 ] ) < < 8 ;
data - > word | = local [ 0 ] ;
}
return rc ;
}
static u32 i2c_opal_func ( struct i2c_adapter * adapter )
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
I2C_FUNC_SMBUS_I2C_BLOCK ;
}
static const struct i2c_algorithm i2c_opal_algo = {
. master_xfer = i2c_opal_master_xfer ,
. smbus_xfer = i2c_opal_smbus_xfer ,
. functionality = i2c_opal_func ,
} ;
2015-03-13 17:25:33 +03:00
/*
* For two messages , we basically support simple smbus transactions of a
* write - then - anything .
2015-01-07 14:24:10 +03:00
*/
static struct i2c_adapter_quirks i2c_opal_quirks = {
2015-03-13 17:25:33 +03:00
. flags = I2C_AQ_COMB | I2C_AQ_COMB_WRITE_FIRST | I2C_AQ_COMB_SAME_ADDR ,
2015-01-07 14:24:10 +03:00
. max_comb_1st_msg_len = 4 ,
} ;
2014-12-13 21:01:05 +03:00
static int i2c_opal_probe ( struct platform_device * pdev )
{
struct i2c_adapter * adapter ;
const char * pname ;
u32 opal_id ;
int rc ;
if ( ! pdev - > dev . of_node )
return - ENODEV ;
rc = of_property_read_u32 ( pdev - > dev . of_node , " ibm,opal-id " , & opal_id ) ;
if ( rc ) {
dev_err ( & pdev - > dev , " Missing ibm,opal-id property ! \n " ) ;
return - EIO ;
}
adapter = devm_kzalloc ( & pdev - > dev , sizeof ( * adapter ) , GFP_KERNEL ) ;
if ( ! adapter )
return - ENOMEM ;
adapter - > algo = & i2c_opal_algo ;
adapter - > algo_data = ( void * ) ( unsigned long ) opal_id ;
2015-01-07 14:24:10 +03:00
adapter - > quirks = & i2c_opal_quirks ;
2014-12-13 21:01:05 +03:00
adapter - > dev . parent = & pdev - > dev ;
adapter - > dev . of_node = of_node_get ( pdev - > dev . of_node ) ;
pname = of_get_property ( pdev - > dev . of_node , " ibm,port-name " , NULL ) ;
if ( pname )
strlcpy ( adapter - > name , pname , sizeof ( adapter - > name ) ) ;
else
strlcpy ( adapter - > name , " opal " , sizeof ( adapter - > name ) ) ;
platform_set_drvdata ( pdev , adapter ) ;
rc = i2c_add_adapter ( adapter ) ;
if ( rc )
dev_err ( & pdev - > dev , " Failed to register the i2c adapter \n " ) ;
return rc ;
}
static int i2c_opal_remove ( struct platform_device * pdev )
{
struct i2c_adapter * adapter = platform_get_drvdata ( pdev ) ;
i2c_del_adapter ( adapter ) ;
return 0 ;
}
static const struct of_device_id i2c_opal_of_match [ ] = {
{
. compatible = " ibm,opal-i2c " ,
} ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , i2c_opal_of_match ) ;
static struct platform_driver i2c_opal_driver = {
. probe = i2c_opal_probe ,
. remove = i2c_opal_remove ,
. driver = {
. name = " i2c-opal " ,
. of_match_table = i2c_opal_of_match ,
} ,
} ;
static int __init i2c_opal_init ( void )
{
if ( ! firmware_has_feature ( FW_FEATURE_OPAL ) )
return - ENODEV ;
return platform_driver_register ( & i2c_opal_driver ) ;
}
module_init ( i2c_opal_init ) ;
static void __exit i2c_opal_exit ( void )
{
return platform_driver_unregister ( & i2c_opal_driver ) ;
}
module_exit ( i2c_opal_exit ) ;
MODULE_AUTHOR ( " Neelesh Gupta <neelegup@linux.vnet.ibm.com> " ) ;
MODULE_DESCRIPTION ( " IBM OPAL I2C driver " ) ;
MODULE_LICENSE ( " GPL " ) ;