2023-06-12 14:56:56 -05:00
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) IBM Corporation 2023 */
# include <linux/device.h>
# include <linux/fsi.h>
# include <linux/i2c.h>
# include <linux/module.h>
# include <linux/mod_devicetable.h>
# include <linux/mutex.h>
# include "fsi-master-i2cr.h"
# define CREATE_TRACE_POINTS
# include <trace/events/fsi_master_i2cr.h>
# define I2CR_ADDRESS_CFAM(a) ((a) >> 2)
# define I2CR_INITIAL_PARITY true
# define I2CR_STATUS_CMD 0x60002
# define I2CR_STATUS_ERR BIT_ULL(61)
# define I2CR_ERROR_CMD 0x60004
# define I2CR_LOG_CMD 0x60008
static const u8 i2cr_cfam [ ] = {
0xc0 , 0x02 , 0x0d , 0xa6 ,
0x80 , 0x01 , 0x10 , 0x02 ,
0x80 , 0x01 , 0x10 , 0x02 ,
0x80 , 0x01 , 0x10 , 0x02 ,
0x80 , 0x01 , 0x80 , 0x52 ,
0x80 , 0x01 , 0x10 , 0x02 ,
0x80 , 0x01 , 0x10 , 0x02 ,
0x80 , 0x01 , 0x10 , 0x02 ,
0x80 , 0x01 , 0x10 , 0x02 ,
0x80 , 0x01 , 0x22 , 0x2d ,
0x00 , 0x00 , 0x00 , 0x00 ,
0xde , 0xad , 0xc0 , 0xde
} ;
static bool i2cr_check_parity32 ( u32 v , bool parity )
{
u32 i ;
for ( i = 0 ; i < 32 ; + + i ) {
if ( v & ( 1u < < i ) )
parity = ! parity ;
}
return parity ;
}
static bool i2cr_check_parity64 ( u64 v )
{
u32 i ;
bool parity = I2CR_INITIAL_PARITY ;
for ( i = 0 ; i < 64 ; + + i ) {
if ( v & ( 1llu < < i ) )
parity = ! parity ;
}
return parity ;
}
static u32 i2cr_get_command ( u32 address , bool parity )
{
address < < = 1 ;
if ( i2cr_check_parity32 ( address , parity ) )
address | = 1 ;
return address ;
}
static int i2cr_transfer ( struct i2c_client * client , u32 command , u64 * data )
{
struct i2c_msg msgs [ 2 ] ;
int ret ;
msgs [ 0 ] . addr = client - > addr ;
msgs [ 0 ] . flags = 0 ;
msgs [ 0 ] . len = sizeof ( command ) ;
msgs [ 0 ] . buf = ( __u8 * ) & command ;
msgs [ 1 ] . addr = client - > addr ;
msgs [ 1 ] . flags = I2C_M_RD ;
msgs [ 1 ] . len = sizeof ( * data ) ;
msgs [ 1 ] . buf = ( __u8 * ) data ;
ret = i2c_transfer ( client - > adapter , msgs , 2 ) ;
if ( ret = = 2 )
return 0 ;
trace_i2cr_i2c_error ( client , command , ret ) ;
if ( ret < 0 )
return ret ;
return - EIO ;
}
static int i2cr_check_status ( struct i2c_client * client )
{
u64 status ;
int ret ;
ret = i2cr_transfer ( client , I2CR_STATUS_CMD , & status ) ;
if ( ret )
return ret ;
if ( status & I2CR_STATUS_ERR ) {
u32 buf [ 3 ] = { 0 , 0 , 0 } ;
u64 error ;
u64 log ;
i2cr_transfer ( client , I2CR_ERROR_CMD , & error ) ;
i2cr_transfer ( client , I2CR_LOG_CMD , & log ) ;
trace_i2cr_status_error ( client , status , error , log ) ;
buf [ 0 ] = I2CR_STATUS_CMD ;
i2c_master_send ( client , ( const char * ) buf , sizeof ( buf ) ) ;
buf [ 0 ] = I2CR_ERROR_CMD ;
i2c_master_send ( client , ( const char * ) buf , sizeof ( buf ) ) ;
buf [ 0 ] = I2CR_LOG_CMD ;
i2c_master_send ( client , ( const char * ) buf , sizeof ( buf ) ) ;
dev_err ( & client - > dev , " status:%016llx error:%016llx log:%016llx \n " , status , error ,
log ) ;
return - EREMOTEIO ;
}
trace_i2cr_status ( client , status ) ;
return 0 ;
}
int fsi_master_i2cr_read ( struct fsi_master_i2cr * i2cr , u32 addr , u64 * data )
{
u32 command = i2cr_get_command ( addr , I2CR_INITIAL_PARITY ) ;
int ret ;
mutex_lock ( & i2cr - > lock ) ;
ret = i2cr_transfer ( i2cr - > client , command , data ) ;
if ( ret )
goto unlock ;
ret = i2cr_check_status ( i2cr - > client ) ;
if ( ret )
goto unlock ;
trace_i2cr_read ( i2cr - > client , command , data ) ;
unlock :
mutex_unlock ( & i2cr - > lock ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( fsi_master_i2cr_read ) ;
int fsi_master_i2cr_write ( struct fsi_master_i2cr * i2cr , u32 addr , u64 data )
{
u32 buf [ 3 ] = { 0 } ;
int ret ;
buf [ 0 ] = i2cr_get_command ( addr , i2cr_check_parity64 ( data ) ) ;
memcpy ( & buf [ 1 ] , & data , sizeof ( data ) ) ;
mutex_lock ( & i2cr - > lock ) ;
ret = i2c_master_send ( i2cr - > client , ( const char * ) buf , sizeof ( buf ) ) ;
if ( ret = = sizeof ( buf ) ) {
ret = i2cr_check_status ( i2cr - > client ) ;
if ( ! ret )
trace_i2cr_write ( i2cr - > client , buf [ 0 ] , data ) ;
} else {
trace_i2cr_i2c_error ( i2cr - > client , buf [ 0 ] , ret ) ;
if ( ret > = 0 )
ret = - EIO ;
}
mutex_unlock ( & i2cr - > lock ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( fsi_master_i2cr_write ) ;
static int i2cr_read ( struct fsi_master * master , int link , uint8_t id , uint32_t addr , void * val ,
size_t size )
{
struct fsi_master_i2cr * i2cr = container_of ( master , struct fsi_master_i2cr , master ) ;
u64 data ;
size_t i ;
int ret ;
if ( link | | id | | ( addr & 0xffff0000 ) | | ! ( size = = 1 | | size = = 2 | | size = = 4 ) )
return - EINVAL ;
/*
* The I2CR doesn ' t have CFAM or FSI slave address space - only the
* engines . In order for this to work with the FSI core , we need to
* emulate at minimum the CFAM config table so that the appropriate
* engines are discovered .
*/
if ( addr < 0xc00 ) {
if ( addr > sizeof ( i2cr_cfam ) - 4 )
addr = ( addr & 0x3 ) + ( sizeof ( i2cr_cfam ) - 4 ) ;
memcpy ( val , & i2cr_cfam [ addr ] , size ) ;
return 0 ;
}
ret = fsi_master_i2cr_read ( i2cr , I2CR_ADDRESS_CFAM ( addr ) , & data ) ;
if ( ret )
return ret ;
/*
* FSI core expects up to 4 bytes BE back , while I2CR replied with LE
* bytes on the wire .
*/
for ( i = 0 ; i < size ; + + i )
( ( u8 * ) val ) [ i ] = ( ( u8 * ) & data ) [ 7 - i ] ;
return 0 ;
}
static int i2cr_write ( struct fsi_master * master , int link , uint8_t id , uint32_t addr ,
const void * val , size_t size )
{
struct fsi_master_i2cr * i2cr = container_of ( master , struct fsi_master_i2cr , master ) ;
u64 data = 0 ;
size_t i ;
if ( link | | id | | ( addr & 0xffff0000 ) | | ! ( size = = 1 | | size = = 2 | | size = = 4 ) )
return - EINVAL ;
/* I2CR writes to CFAM or FSI slave address are a successful no-op. */
if ( addr < 0xc00 )
return 0 ;
/*
* FSI core passes up to 4 bytes BE , while the I2CR expects LE bytes on
* the wire .
*/
for ( i = 0 ; i < size ; + + i )
( ( u8 * ) & data ) [ 7 - i ] = ( ( u8 * ) val ) [ i ] ;
return fsi_master_i2cr_write ( i2cr , I2CR_ADDRESS_CFAM ( addr ) , data ) ;
}
static void i2cr_release ( struct device * dev )
{
struct fsi_master_i2cr * i2cr = to_fsi_master_i2cr ( to_fsi_master ( dev ) ) ;
of_node_put ( dev - > of_node ) ;
kfree ( i2cr ) ;
}
static int i2cr_probe ( struct i2c_client * client )
{
struct fsi_master_i2cr * i2cr ;
int ret ;
i2cr = kzalloc ( sizeof ( * i2cr ) , GFP_KERNEL ) ;
if ( ! i2cr )
return - ENOMEM ;
/* Only one I2CR on any given I2C bus (fixed I2C device address) */
i2cr - > master . idx = client - > adapter - > nr ;
dev_set_name ( & i2cr - > master . dev , " i2cr%d " , i2cr - > master . idx ) ;
i2cr - > master . dev . parent = & client - > dev ;
i2cr - > master . dev . of_node = of_node_get ( dev_of_node ( & client - > dev ) ) ;
i2cr - > master . dev . release = i2cr_release ;
i2cr - > master . n_links = 1 ;
i2cr - > master . read = i2cr_read ;
i2cr - > master . write = i2cr_write ;
mutex_init ( & i2cr - > lock ) ;
i2cr - > client = client ;
ret = fsi_master_register ( & i2cr - > master ) ;
if ( ret )
return ret ;
i2c_set_clientdata ( client , i2cr ) ;
return 0 ;
}
static void i2cr_remove ( struct i2c_client * client )
{
struct fsi_master_i2cr * i2cr = i2c_get_clientdata ( client ) ;
fsi_master_unregister ( & i2cr - > master ) ;
}
static const struct of_device_id i2cr_ids [ ] = {
{ . compatible = " ibm,i2cr-fsi-master " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , i2cr_ids ) ;
static struct i2c_driver i2cr_driver = {
2023-08-16 19:19:44 +02:00
. probe = i2cr_probe ,
2023-06-12 14:56:56 -05:00
. remove = i2cr_remove ,
. driver = {
. name = " fsi-master-i2cr " ,
. of_match_table = i2cr_ids ,
} ,
} ;
module_i2c_driver ( i2cr_driver )
MODULE_AUTHOR ( " Eddie James <eajames@linux.ibm.com> " ) ;
MODULE_DESCRIPTION ( " IBM I2C Responder virtual FSI master driver " ) ;
MODULE_LICENSE ( " GPL " ) ;