2013-08-29 16:57:33 +10:00
/*
* PowerNV LPC bus handling .
*
* Copyright 2013 IBM Corp .
*
* 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 .
*/
# include <linux/kernel.h>
# include <linux/of.h>
# include <linux/bug.h>
# include <linux/gfp.h>
# include <linux/slab.h>
# include <asm/machdep.h>
# include <asm/firmware.h>
# include <asm/opal.h>
# include <asm/scom.h>
/*
* We could probably fit that inside the scom_map_t
* which is a void * after all but it ' s really too ugly
* so let ' s kmalloc it for now
*/
struct opal_scom_map {
uint32_t chip ;
2013-10-10 19:18:02 +11:00
uint64_t addr ;
2013-08-29 16:57:33 +10:00
} ;
static scom_map_t opal_scom_map ( struct device_node * dev , u64 reg , u64 count )
{
struct opal_scom_map * m ;
const __be32 * gcid ;
if ( ! of_get_property ( dev , " scom-controller " , NULL ) ) {
pr_err ( " %s: device %s is not a SCOM controller \n " ,
__func__ , dev - > full_name ) ;
return SCOM_MAP_INVALID ;
}
gcid = of_get_property ( dev , " ibm,chip-id " , NULL ) ;
if ( ! gcid ) {
pr_err ( " %s: device %s has no ibm,chip-id \n " ,
__func__ , dev - > full_name ) ;
return SCOM_MAP_INVALID ;
}
m = kmalloc ( sizeof ( struct opal_scom_map ) , GFP_KERNEL ) ;
if ( ! m )
return NULL ;
m - > chip = be32_to_cpup ( gcid ) ;
m - > addr = reg ;
return ( scom_map_t ) m ;
}
static void opal_scom_unmap ( scom_map_t map )
{
kfree ( map ) ;
}
static int opal_xscom_err_xlate ( int64_t rc )
{
switch ( rc ) {
case 0 :
return 0 ;
/* Add more translations if necessary */
default :
return - EIO ;
}
}
2014-02-28 16:20:38 +11:00
static u64 opal_scom_unmangle ( u64 addr )
2013-10-10 19:19:15 +11:00
{
/*
* XSCOM indirect addresses have the top bit set . Additionally
2014-02-28 16:20:38 +11:00
* the rest of the top 3 nibbles is always 0.
2013-10-10 19:19:15 +11:00
*
* Because the debugfs interface uses signed offsets and shifts
* the address left by 3 , we basically cannot use the top 4 bits
* of the 64 - bit address , and thus cannot use the indirect bit .
*
* To deal with that , we support the indirect bit being in bit
* 4 ( IBM notation ) instead of bit 0 in this API , we do the
* conversion here . To leave room for further xscom address
* expansion , we only clear out the top byte
*
2014-02-28 16:20:38 +11:00
* For in - kernel use , we also support the real indirect bit , so
* we test for any of the top 5 bits
*
2013-10-10 19:19:15 +11:00
*/
2014-02-28 16:20:38 +11:00
if ( addr & ( 0x1full < < 59 ) )
addr = ( addr & ~ ( 0xffull < < 56 ) ) | ( 1ull < < 63 ) ;
return addr ;
2013-10-10 19:19:15 +11:00
}
2013-10-10 19:18:02 +11:00
static int opal_scom_read ( scom_map_t map , u64 reg , u64 * value )
2013-08-29 16:57:33 +10:00
{
struct opal_scom_map * m = map ;
int64_t rc ;
2013-12-13 15:53:43 +11:00
__be64 v ;
2013-08-29 16:57:33 +10:00
2014-02-28 16:20:38 +11:00
reg = opal_scom_unmangle ( m - > addr + reg ) ;
rc = opal_xscom_read ( m - > chip , reg , ( __be64 * ) __pa ( & v ) ) ;
2013-12-13 15:53:43 +11:00
* value = be64_to_cpu ( v ) ;
2013-08-29 16:57:33 +10:00
return opal_xscom_err_xlate ( rc ) ;
}
2013-10-10 19:18:02 +11:00
static int opal_scom_write ( scom_map_t map , u64 reg , u64 value )
2013-08-29 16:57:33 +10:00
{
struct opal_scom_map * m = map ;
int64_t rc ;
2014-02-28 16:20:38 +11:00
reg = opal_scom_unmangle ( m - > addr + reg ) ;
rc = opal_xscom_write ( m - > chip , reg , value ) ;
2013-08-29 16:57:33 +10:00
return opal_xscom_err_xlate ( rc ) ;
}
static const struct scom_controller opal_scom_controller = {
. map = opal_scom_map ,
. unmap = opal_scom_unmap ,
. read = opal_scom_read ,
. write = opal_scom_write
} ;
static int opal_xscom_init ( void )
{
2015-12-09 17:18:20 +11:00
if ( firmware_has_feature ( FW_FEATURE_OPAL ) )
2013-08-29 16:57:33 +10:00
scom_init ( & opal_scom_controller ) ;
return 0 ;
}
2014-07-15 22:22:24 +10:00
machine_arch_initcall ( powernv , opal_xscom_init ) ;