2019-01-28 10:23:23 -06:00
// SPDX-License-Identifier: GPL-2.0+
// Copyright IBM Corp 2019
2018-11-08 15:05:24 -06:00
# include <linux/device.h>
# include <linux/errno.h>
2018-11-08 15:05:25 -06:00
# include <linux/fsi-occ.h>
2018-11-08 15:05:24 -06:00
# include <linux/i2c.h>
2018-11-08 15:05:25 -06:00
# include <linux/jiffies.h>
2018-11-08 15:05:24 -06:00
# include <linux/module.h>
2018-11-08 15:05:25 -06:00
# include <linux/sched.h>
# include <asm/unaligned.h>
2018-11-08 15:05:24 -06:00
# include "common.h"
2018-11-08 15:05:25 -06:00
# define OCC_TIMEOUT_MS 1000
# define OCC_CMD_IN_PRG_WAIT_MS 50
/* OCB (on-chip control bridge - interface to OCC) registers */
# define OCB_DATA1 0x6B035
# define OCB_ADDR 0x6B070
# define OCB_DATA3 0x6B075
/* OCC SRAM address space */
# define OCC_SRAM_ADDR_CMD 0xFFFF6000
# define OCC_SRAM_ADDR_RESP 0xFFFF7000
# define OCC_DATA_ATTN 0x20010000
2018-11-08 15:05:24 -06:00
struct p8_i2c_occ {
struct occ occ ;
struct i2c_client * client ;
} ;
# define to_p8_i2c_occ(x) container_of((x), struct p8_i2c_occ, occ)
2018-11-08 15:05:25 -06:00
static int p8_i2c_occ_getscom ( struct i2c_client * client , u32 address , u8 * data )
{
ssize_t rc ;
__be64 buf ;
struct i2c_msg msgs [ 2 ] ;
/* p8 i2c slave requires shift */
address < < = 1 ;
msgs [ 0 ] . addr = client - > addr ;
msgs [ 0 ] . flags = client - > flags & I2C_M_TEN ;
msgs [ 0 ] . len = sizeof ( u32 ) ;
/* address is a scom address; bus-endian */
msgs [ 0 ] . buf = ( char * ) & address ;
/* data from OCC is big-endian */
msgs [ 1 ] . addr = client - > addr ;
msgs [ 1 ] . flags = ( client - > flags & I2C_M_TEN ) | I2C_M_RD ;
msgs [ 1 ] . len = sizeof ( u64 ) ;
msgs [ 1 ] . buf = ( char * ) & buf ;
rc = i2c_transfer ( client - > adapter , msgs , 2 ) ;
if ( rc < 0 )
return rc ;
* ( u64 * ) data = be64_to_cpu ( buf ) ;
return 0 ;
}
static int p8_i2c_occ_putscom ( struct i2c_client * client , u32 address , u8 * data )
{
u32 buf [ 3 ] ;
ssize_t rc ;
/* p8 i2c slave requires shift */
address < < = 1 ;
/* address is bus-endian; data passed through from user as-is */
buf [ 0 ] = address ;
memcpy ( & buf [ 1 ] , & data [ 4 ] , sizeof ( u32 ) ) ;
memcpy ( & buf [ 2 ] , data , sizeof ( u32 ) ) ;
rc = i2c_master_send ( client , ( const char * ) buf , sizeof ( buf ) ) ;
if ( rc < 0 )
return rc ;
else if ( rc ! = sizeof ( buf ) )
return - EIO ;
return 0 ;
}
static int p8_i2c_occ_putscom_u32 ( struct i2c_client * client , u32 address ,
u32 data0 , u32 data1 )
{
u8 buf [ 8 ] ;
memcpy ( buf , & data0 , 4 ) ;
memcpy ( buf + 4 , & data1 , 4 ) ;
return p8_i2c_occ_putscom ( client , address , buf ) ;
}
static int p8_i2c_occ_putscom_be ( struct i2c_client * client , u32 address ,
u8 * data )
{
__be32 data0 , data1 ;
memcpy ( & data0 , data , 4 ) ;
memcpy ( & data1 , data + 4 , 4 ) ;
return p8_i2c_occ_putscom_u32 ( client , address , be32_to_cpu ( data0 ) ,
be32_to_cpu ( data1 ) ) ;
}
2018-11-08 15:05:24 -06:00
static int p8_i2c_occ_send_cmd ( struct occ * occ , u8 * cmd )
{
2018-11-08 15:05:25 -06:00
int i , rc ;
unsigned long start ;
u16 data_length ;
const unsigned long timeout = msecs_to_jiffies ( OCC_TIMEOUT_MS ) ;
const long wait_time = msecs_to_jiffies ( OCC_CMD_IN_PRG_WAIT_MS ) ;
struct p8_i2c_occ * ctx = to_p8_i2c_occ ( occ ) ;
struct i2c_client * client = ctx - > client ;
struct occ_response * resp = & occ - > resp ;
start = jiffies ;
/* set sram address for command */
rc = p8_i2c_occ_putscom_u32 ( client , OCB_ADDR , OCC_SRAM_ADDR_CMD , 0 ) ;
if ( rc )
return rc ;
/* write command (expected to already be BE), we need bus-endian... */
rc = p8_i2c_occ_putscom_be ( client , OCB_DATA3 , cmd ) ;
if ( rc )
return rc ;
/* trigger OCC attention */
rc = p8_i2c_occ_putscom_u32 ( client , OCB_DATA1 , OCC_DATA_ATTN , 0 ) ;
if ( rc )
return rc ;
do {
/* set sram address for response */
rc = p8_i2c_occ_putscom_u32 ( client , OCB_ADDR ,
OCC_SRAM_ADDR_RESP , 0 ) ;
if ( rc )
return rc ;
rc = p8_i2c_occ_getscom ( client , OCB_DATA3 , ( u8 * ) resp ) ;
if ( rc )
return rc ;
/* wait for OCC */
if ( resp - > return_status = = OCC_RESP_CMD_IN_PRG ) {
rc = - EALREADY ;
if ( time_after ( jiffies , start + timeout ) )
break ;
set_current_state ( TASK_INTERRUPTIBLE ) ;
schedule_timeout ( wait_time ) ;
}
} while ( rc ) ;
/* check the OCC response */
switch ( resp - > return_status ) {
case OCC_RESP_CMD_IN_PRG :
rc = - ETIMEDOUT ;
break ;
case OCC_RESP_SUCCESS :
rc = 0 ;
break ;
case OCC_RESP_CMD_INVAL :
case OCC_RESP_CMD_LEN_INVAL :
case OCC_RESP_DATA_INVAL :
case OCC_RESP_CHKSUM_ERR :
rc = - EINVAL ;
break ;
case OCC_RESP_INT_ERR :
case OCC_RESP_BAD_STATE :
case OCC_RESP_CRIT_EXCEPT :
case OCC_RESP_CRIT_INIT :
case OCC_RESP_CRIT_WATCHDOG :
case OCC_RESP_CRIT_OCB :
case OCC_RESP_CRIT_HW :
rc = - EREMOTEIO ;
break ;
default :
rc = - EPROTO ;
}
if ( rc < 0 )
return rc ;
data_length = get_unaligned_be16 ( & resp - > data_length ) ;
if ( data_length > OCC_RESP_DATA_BYTES )
return - EMSGSIZE ;
/* fetch the rest of the response data */
for ( i = 8 ; i < data_length + 7 ; i + = 8 ) {
rc = p8_i2c_occ_getscom ( client , OCB_DATA3 , ( ( u8 * ) resp ) + i ) ;
if ( rc )
return rc ;
}
return 0 ;
2018-11-08 15:05:24 -06:00
}
static int p8_i2c_occ_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct occ * occ ;
struct p8_i2c_occ * ctx = devm_kzalloc ( & client - > dev , sizeof ( * ctx ) ,
GFP_KERNEL ) ;
if ( ! ctx )
return - ENOMEM ;
ctx - > client = client ;
occ = & ctx - > occ ;
occ - > bus_dev = & client - > dev ;
dev_set_drvdata ( & client - > dev , occ ) ;
2018-11-08 15:05:27 -06:00
occ - > powr_sample_time_us = 250 ;
2018-11-08 15:05:24 -06:00
occ - > poll_cmd_data = 0x10 ; /* P8 OCC poll data */
occ - > send_cmd = p8_i2c_occ_send_cmd ;
return occ_setup ( occ , " p8_occ " ) ;
}
2018-11-08 15:05:29 -06:00
static int p8_i2c_occ_remove ( struct i2c_client * client )
{
struct occ * occ = dev_get_drvdata ( & client - > dev ) ;
occ_shutdown ( occ ) ;
return 0 ;
}
2018-11-08 15:05:24 -06:00
static const struct of_device_id p8_i2c_occ_of_match [ ] = {
{ . compatible = " ibm,p8-occ-hwmon " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , p8_i2c_occ_of_match ) ;
static struct i2c_driver p8_i2c_occ_driver = {
. class = I2C_CLASS_HWMON ,
. driver = {
. name = " occ-hwmon " ,
. of_match_table = p8_i2c_occ_of_match ,
} ,
. probe = p8_i2c_occ_probe ,
2018-11-08 15:05:29 -06:00
. remove = p8_i2c_occ_remove ,
2018-11-08 15:05:24 -06:00
} ;
module_i2c_driver ( p8_i2c_occ_driver ) ;
MODULE_AUTHOR ( " Eddie James <eajames@linux.ibm.com> " ) ;
MODULE_DESCRIPTION ( " BMC P8 OCC hwmon driver " ) ;
MODULE_LICENSE ( " GPL " ) ;