2020-05-26 15:59:28 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright ( C ) 2020 BAIKAL ELECTRONICS , JSC
*
* Authors :
* Serge Semin < Sergey . Semin @ baikalelectronics . ru >
*
* Baikal - T1 CM2 L2 - cache Control Block driver .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/bitfield.h>
# include <linux/types.h>
# include <linux/device.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/mfd/syscon.h>
# include <linux/sysfs.h>
# include <linux/of.h>
# define L2_CTL_REG 0x028
# define L2_CTL_DATA_STALL_FLD 0
# define L2_CTL_DATA_STALL_MASK GENMASK(1, L2_CTL_DATA_STALL_FLD)
# define L2_CTL_TAG_STALL_FLD 2
# define L2_CTL_TAG_STALL_MASK GENMASK(3, L2_CTL_TAG_STALL_FLD)
# define L2_CTL_WS_STALL_FLD 4
# define L2_CTL_WS_STALL_MASK GENMASK(5, L2_CTL_WS_STALL_FLD)
# define L2_CTL_SET_CLKRATIO BIT(13)
# define L2_CTL_CLKRATIO_LOCK BIT(31)
# define L2_CTL_STALL_MIN 0
# define L2_CTL_STALL_MAX 3
# define L2_CTL_STALL_SET_DELAY_US 1
# define L2_CTL_STALL_SET_TOUT_US 1000
/*
* struct l2_ctl - Baikal - T1 L2 Control block private data .
* @ dev : Pointer to the device structure .
* @ sys_regs : Baikal - T1 System Controller registers map .
*/
struct l2_ctl {
struct device * dev ;
struct regmap * sys_regs ;
} ;
/*
* enum l2_ctl_stall - Baikal - T1 L2 - cache - RAM stall identifier .
* @ L2_WSSTALL : Way - select latency .
* @ L2_TAGSTALL : Tag latency .
* @ L2_DATASTALL : Data latency .
*/
enum l2_ctl_stall {
L2_WS_STALL ,
L2_TAG_STALL ,
L2_DATA_STALL
} ;
/*
* struct l2_ctl_device_attribute - Baikal - T1 L2 - cache device attribute .
* @ dev_attr : Actual sysfs device attribute .
* @ id : L2 - cache stall field identifier .
*/
struct l2_ctl_device_attribute {
struct device_attribute dev_attr ;
enum l2_ctl_stall id ;
} ;
2020-07-24 21:23:13 +03:00
2020-05-26 15:59:28 +03:00
# define to_l2_ctl_dev_attr(_dev_attr) \
container_of ( _dev_attr , struct l2_ctl_device_attribute , dev_attr )
# define L2_CTL_ATTR_RW(_name, _prefix, _id) \
struct l2_ctl_device_attribute l2_ctl_attr_ # # _name = \
{ __ATTR ( _name , 0644 , _prefix # # _show , _prefix # # _store ) , _id }
static int l2_ctl_get_latency ( struct l2_ctl * l2 , enum l2_ctl_stall id , u32 * val )
{
u32 data = 0 ;
int ret ;
ret = regmap_read ( l2 - > sys_regs , L2_CTL_REG , & data ) ;
if ( ret )
return ret ;
switch ( id ) {
case L2_WS_STALL :
* val = FIELD_GET ( L2_CTL_WS_STALL_MASK , data ) ;
break ;
case L2_TAG_STALL :
* val = FIELD_GET ( L2_CTL_TAG_STALL_MASK , data ) ;
break ;
case L2_DATA_STALL :
* val = FIELD_GET ( L2_CTL_DATA_STALL_MASK , data ) ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static int l2_ctl_set_latency ( struct l2_ctl * l2 , enum l2_ctl_stall id , u32 val )
{
u32 mask = 0 , data = 0 ;
int ret ;
val = clamp_val ( val , L2_CTL_STALL_MIN , L2_CTL_STALL_MAX ) ;
switch ( id ) {
case L2_WS_STALL :
data = FIELD_PREP ( L2_CTL_WS_STALL_MASK , val ) ;
mask = L2_CTL_WS_STALL_MASK ;
break ;
case L2_TAG_STALL :
data = FIELD_PREP ( L2_CTL_TAG_STALL_MASK , val ) ;
mask = L2_CTL_TAG_STALL_MASK ;
break ;
case L2_DATA_STALL :
data = FIELD_PREP ( L2_CTL_DATA_STALL_MASK , val ) ;
mask = L2_CTL_DATA_STALL_MASK ;
break ;
default :
return - EINVAL ;
}
data | = L2_CTL_SET_CLKRATIO ;
mask | = L2_CTL_SET_CLKRATIO ;
ret = regmap_update_bits ( l2 - > sys_regs , L2_CTL_REG , mask , data ) ;
if ( ret )
return ret ;
return regmap_read_poll_timeout ( l2 - > sys_regs , L2_CTL_REG , data ,
data & L2_CTL_CLKRATIO_LOCK ,
L2_CTL_STALL_SET_DELAY_US ,
L2_CTL_STALL_SET_TOUT_US ) ;
}
static void l2_ctl_clear_data ( void * data )
{
struct l2_ctl * l2 = data ;
struct platform_device * pdev = to_platform_device ( l2 - > dev ) ;
platform_set_drvdata ( pdev , NULL ) ;
}
static struct l2_ctl * l2_ctl_create_data ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct l2_ctl * l2 ;
int ret ;
l2 = devm_kzalloc ( dev , sizeof ( * l2 ) , GFP_KERNEL ) ;
if ( ! l2 )
return ERR_PTR ( - ENOMEM ) ;
ret = devm_add_action ( dev , l2_ctl_clear_data , l2 ) ;
if ( ret ) {
dev_err ( dev , " Can't add L2 CTL data clear action \n " ) ;
return ERR_PTR ( ret ) ;
}
l2 - > dev = dev ;
platform_set_drvdata ( pdev , l2 ) ;
return l2 ;
}
static int l2_ctl_find_sys_regs ( struct l2_ctl * l2 )
{
l2 - > sys_regs = syscon_node_to_regmap ( l2 - > dev - > of_node - > parent ) ;
if ( IS_ERR ( l2 - > sys_regs ) ) {
dev_err ( l2 - > dev , " Couldn't get L2 CTL register map \n " ) ;
return PTR_ERR ( l2 - > sys_regs ) ;
}
return 0 ;
}
static int l2_ctl_of_parse_property ( struct l2_ctl * l2 , enum l2_ctl_stall id ,
const char * propname )
{
int ret = 0 ;
u32 data ;
if ( ! of_property_read_u32 ( l2 - > dev - > of_node , propname , & data ) ) {
ret = l2_ctl_set_latency ( l2 , id , data ) ;
if ( ret )
dev_err ( l2 - > dev , " Invalid value of '%s' \n " , propname ) ;
}
return ret ;
}
static int l2_ctl_of_parse ( struct l2_ctl * l2 )
{
int ret ;
ret = l2_ctl_of_parse_property ( l2 , L2_WS_STALL , " baikal,l2-ws-latency " ) ;
if ( ret )
return ret ;
ret = l2_ctl_of_parse_property ( l2 , L2_TAG_STALL , " baikal,l2-tag-latency " ) ;
if ( ret )
return ret ;
return l2_ctl_of_parse_property ( l2 , L2_DATA_STALL ,
" baikal,l2-data-latency " ) ;
}
static ssize_t l2_ctl_latency_show ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct l2_ctl_device_attribute * devattr = to_l2_ctl_dev_attr ( attr ) ;
struct l2_ctl * l2 = dev_get_drvdata ( dev ) ;
u32 data ;
int ret ;
ret = l2_ctl_get_latency ( l2 , devattr - > id , & data ) ;
if ( ret )
return ret ;
return scnprintf ( buf , PAGE_SIZE , " %u \n " , data ) ;
}
static ssize_t l2_ctl_latency_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct l2_ctl_device_attribute * devattr = to_l2_ctl_dev_attr ( attr ) ;
struct l2_ctl * l2 = dev_get_drvdata ( dev ) ;
u32 data ;
int ret ;
if ( kstrtouint ( buf , 0 , & data ) < 0 )
return - EINVAL ;
ret = l2_ctl_set_latency ( l2 , devattr - > id , data ) ;
if ( ret )
return ret ;
return count ;
}
2020-07-24 21:23:13 +03:00
2020-05-26 15:59:28 +03:00
static L2_CTL_ATTR_RW ( l2_ws_latency , l2_ctl_latency , L2_WS_STALL ) ;
static L2_CTL_ATTR_RW ( l2_tag_latency , l2_ctl_latency , L2_TAG_STALL ) ;
static L2_CTL_ATTR_RW ( l2_data_latency , l2_ctl_latency , L2_DATA_STALL ) ;
static struct attribute * l2_ctl_sysfs_attrs [ ] = {
& l2_ctl_attr_l2_ws_latency . dev_attr . attr ,
& l2_ctl_attr_l2_tag_latency . dev_attr . attr ,
& l2_ctl_attr_l2_data_latency . dev_attr . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( l2_ctl_sysfs ) ;
static void l2_ctl_remove_sysfs ( void * data )
{
struct l2_ctl * l2 = data ;
device_remove_groups ( l2 - > dev , l2_ctl_sysfs_groups ) ;
}
static int l2_ctl_init_sysfs ( struct l2_ctl * l2 )
{
int ret ;
ret = device_add_groups ( l2 - > dev , l2_ctl_sysfs_groups ) ;
if ( ret ) {
dev_err ( l2 - > dev , " Failed to create L2 CTL sysfs nodes \n " ) ;
return ret ;
}
ret = devm_add_action_or_reset ( l2 - > dev , l2_ctl_remove_sysfs , l2 ) ;
if ( ret )
dev_err ( l2 - > dev , " Can't add L2 CTL sysfs remove action \n " ) ;
return ret ;
}
static int l2_ctl_probe ( struct platform_device * pdev )
{
struct l2_ctl * l2 ;
int ret ;
l2 = l2_ctl_create_data ( pdev ) ;
if ( IS_ERR ( l2 ) )
return PTR_ERR ( l2 ) ;
ret = l2_ctl_find_sys_regs ( l2 ) ;
if ( ret )
return ret ;
ret = l2_ctl_of_parse ( l2 ) ;
if ( ret )
return ret ;
ret = l2_ctl_init_sysfs ( l2 ) ;
if ( ret )
return ret ;
return 0 ;
}
static const struct of_device_id l2_ctl_of_match [ ] = {
{ . compatible = " baikal,bt1-l2-ctl " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , l2_ctl_of_match ) ;
static struct platform_driver l2_ctl_driver = {
. probe = l2_ctl_probe ,
. driver = {
. name = " bt1-l2-ctl " ,
. of_match_table = l2_ctl_of_match
}
} ;
module_platform_driver ( l2_ctl_driver ) ;
MODULE_AUTHOR ( " Serge Semin <Sergey.Semin@baikalelectronics.ru> " ) ;
MODULE_DESCRIPTION ( " Baikal-T1 L2-cache driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;