blob: 69d9802eb9c2010c4f6f1d55d50865daf9c4df7a [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2019 Synaptics Incorporated
*
* Author: Benson Gui <Benson.Gui@synaptics.com>
*
*/
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/sysfs.h>
#define AS370_AXI_CNT_NUM 3
#define RA_MC6Ctrl_AxiPCntCTRL 0x001C
#define RA_MC6Ctrl_AxiMst0 0x0020
#define RA_MC6Ctrl_AxiMst0DXBAR 0x0024
#define RA_MC6Ctrl_AxiMst1 0x0028
#define RA_MC6Ctrl_Mstr0PCnt 0x002C
#define RA_MC6Ctrl_Mstr0DXBARPCnt 0x0060
#define RA_AxiPCntStat_OF_STATUS 0x002C
#define RA_AxiPCntStat_TOTAL_CNT 0x0000
#define RA_AxiPCntStat_ARWAIT_CNT 0x0004
#define RA_AxiPCntStat_RWAIT_CNT 0x0008
#define RA_AxiPCntStat_RIDLE_CNT 0x000C
#define RA_AxiPCntStat_RDATA_CNT 0x0010
#define RA_AxiPCntStat_AWWAIT_CNT 0x0014
#define RA_AxiPCntStat_WWAIT_CNT 0x0018
#define RA_AxiPCntStat_WIDLE_CNT 0x001C
#define RA_AxiPCntStat_WDATA_CNT 0x0020
#define RA_AxiPCntStat_AWDATA_CNT 0x0024
#define RA_AxiPCntStat_ARDATA_CNT 0x0028
struct axi_perf_counter {
unsigned int total;
unsigned int arwait;
unsigned int rwait;
unsigned int rdata;
unsigned int ardata;
unsigned int awwait;
unsigned int wwait;
unsigned int wdata;
unsigned int awdata;
unsigned int status;
};
#define CTRL_CLR_SHIFT 0
#define CTRL_EN_SHIFT 8
#define CTRL_LATCH_SHIFT 16
#define CTRL_CLR_MASK (0xf << CTRL_CLR_SHIFT)
#define CTRL_EN_MASK (0xf << CTRL_EN_SHIFT)
#define CTRL_LATCH_MASK (0xf << CTRL_LATCH_SHIFT)
#define OVERFLOW_TOTAL BIT(0)
#define OVERFLOW_ARWAIT BIT(1)
#define OVERFLOW_RWAIT BIT(2)
#define OVERFLOW_RIDLE BIT(3)
#define OVERFLOW_RDATA BIT(4)
#define OVERFLOW_AWWAIT BIT(5)
#define OVERFLOW_WWAIT BIT(6)
#define OVERFLOW_WIDLE BIT(7)
#define OVERFLOW_WDATA BIT(8)
#define OVERFLOW_AWDATA BIT(9)
#define OVERFLOW_ARDATA BIT(10)
static const struct
{
const char* name;
unsigned int mask_id;
} as370_mask_table[] =
{
{"*PORT0" , (0x0000 << 16) + 0x0000, }, // 0
{"CPU" , (0x0001 << 16) + 0x0001, },
{"DXBAR" , (0x0001 << 16) + 0x0000, },
{"USB2" , (0x003f << 16) + 0x0020, },
{"SDIO" , (0x003f << 16) + 0x0028, }, // 4
{"NAND" , (0x003f << 16) + 0x0000, },
{"PCIE" , (0x002f << 16) + 0x0008, },
{"PCIE_M1" , (0x003f << 16) + 0x0018, },
{"PCIE_M0" , (0x003f << 16) + 0x0008, }, // 8
{"EMMC" , (0x003f << 16) + 0x0010, },
{"PB" , (0x003f << 16) + 0x0030, },
{"BUSMON" , (0xffff << 16) + 0x0004, },
{"BCM" , (0x003f << 16) + 0x0016, }, // 12
{"BCMCPU" , (0xffff << 16) + 0x002e, },
{"BCMDIR" , (0xffff << 16) + 0x000e, },
{"AIO" , (0x0007 << 16) + 0x0002, },
{"*PORT1" , (0x0000 << 16) + 0x0000, }, // 16
{"NNA" , (0x0001 << 16) + 0x0001, },
{"IMTEST" , (0x0001 << 16) + 0x0000, },
};
#define CTL_CH0_BIT BIT(0)
#define CTL_CH1_BIT BIT(7)
#define CTL_CH2_BIT BIT(1)
#define CTL_ALL_CH_BITS (CTL_CH0_BIT | CTL_CH1_BIT | CTL_CH2_BIT)
static unsigned int as370_axi_cnt_ctrl_bit[AS370_AXI_CNT_NUM] =
{
CTL_CH0_BIT,
CTL_CH1_BIT,
CTL_CH2_BIT,
};
/* default axi counter mask id*/
static unsigned int as370_axi_default_id[AS370_AXI_CNT_NUM] =
{
1, /* CPU */
2, /* DXBAR */
16, /* PORT1 */
};
static const unsigned int as370_axi_id_range[AS370_AXI_CNT_NUM + 1] =
{
0, /* *PORT0 */
2, /* DXBAR */
16, /* *PORT1 */
19, /* IMTEST + 1 */
};
static unsigned int as370_axi_cnt_mst_offset[AS370_AXI_CNT_NUM] =
{
RA_MC6Ctrl_AxiMst0,
RA_MC6Ctrl_AxiMst0DXBAR,
RA_MC6Ctrl_AxiMst1,
};
struct axi_meter_priv {
struct platform_device *pdev;
const char *dev_name;
void __iomem *base;
spinlock_t lock;
struct axi_perf_counter perf_cnts[AS370_AXI_CNT_NUM];
unsigned int cnt_ids[AS370_AXI_CNT_NUM];
bool cnt_en[AS370_AXI_CNT_NUM];
/* axi_meter sysfs interface */
struct kobject kobj;
};
static int as370_axi_clear_perf_cnt(struct axi_meter_priv *axi_meter, int cnt_idx)
{
u32 ctrl;
if (cnt_idx >= AS370_AXI_CNT_NUM)
return -EINVAL;
spin_lock(&axi_meter->lock);
ctrl = readl(axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
/* set clr bit, latch bit -> 0*/
ctrl &= ~(as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_LATCH_SHIFT);
ctrl |= (as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_CLR_SHIFT);
writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
/* clear clr bit, latch bit 0 -> 1*/
ctrl &= ~(as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_CLR_SHIFT);
ctrl |= (as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_LATCH_SHIFT);
writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
spin_unlock(&axi_meter->lock);
return 0;
}
static int as370_axi_enable_perf_cnt(struct axi_meter_priv *axi_meter, int cnt_idx, int en)
{
u32 ctrl;
if (cnt_idx >= AS370_AXI_CNT_NUM)
return -EINVAL;
spin_lock(&axi_meter->lock);
ctrl = readl(axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
if (en) {
axi_meter->cnt_en[cnt_idx] = 1;
ctrl |= (as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_EN_SHIFT);
} else {
axi_meter->cnt_en[cnt_idx] = 0;
ctrl &= ~(as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_EN_SHIFT);
}
writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
spin_unlock(&axi_meter->lock);
return 0;
}
static int as370_axi_config_mask(struct axi_meter_priv *axi_meter, unsigned int cnt_idx, unsigned int id)
{
unsigned int mask;
if (cnt_idx >= AS370_AXI_CNT_NUM || (id >= ARRAY_SIZE(as370_mask_table)))
return -EINVAL;
if ((id < as370_axi_id_range[cnt_idx]) || (id >= as370_axi_id_range[cnt_idx + 1]))
return -EINVAL;
axi_meter->cnt_ids[cnt_idx] = id;
mask = as370_mask_table[id].mask_id;
/*disable the counter*/
as370_axi_enable_perf_cnt(axi_meter, cnt_idx, 0);
as370_axi_clear_perf_cnt(axi_meter, cnt_idx);
/*update the mask*/
writel(mask, axi_meter->base + as370_axi_cnt_mst_offset[cnt_idx]);
/*enable the counter*/
as370_axi_enable_perf_cnt(axi_meter, cnt_idx, 1);
return 0;
}
static int as370_axi_latch_perf_cnt(struct axi_meter_priv *axi_meter, int cnt_idx)
{
u32 ctrl;
if (cnt_idx >= AS370_AXI_CNT_NUM)
return -EINVAL;
spin_lock(&axi_meter->lock);
ctrl = readl(axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
ctrl &= ~(as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_LATCH_SHIFT);
writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
ctrl |= (as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_LATCH_SHIFT);
writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
spin_unlock(&axi_meter->lock);
return 0;
}
static void as370_axi_init_perf_cnt(struct axi_meter_priv *axi_meter)
{
unsigned int mask;
int i;
u32 ctrl;
for (i = 0; i < AS370_AXI_CNT_NUM; i++) {
axi_meter->cnt_ids[i] = as370_axi_default_id[i];
mask = as370_mask_table[axi_meter->cnt_ids[i]].mask_id;
writel(mask, axi_meter->base + as370_axi_cnt_mst_offset[i]);
}
/* clear all counters */
ctrl = CTL_ALL_CH_BITS << CTRL_CLR_SHIFT;
writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
/* latch zero and disable all counters */
ctrl = CTL_ALL_CH_BITS << CTRL_LATCH_SHIFT;
writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
}
static void as370_axi_stop_perf_cnt(struct axi_meter_priv *axi_meter)
{
u32 ctrl;
spin_lock(&axi_meter->lock);
/* clear all counters */
ctrl = CTL_ALL_CH_BITS << CTRL_CLR_SHIFT;
writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
/* latch zero and disable all counters */
ctrl = CTL_ALL_CH_BITS << CTRL_LATCH_SHIFT;
writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL);
spin_unlock(&axi_meter->lock);
}
static int as370_axi_update_perf_cnt(struct axi_meter_priv *axi_meter, unsigned int cnt_idx)
{
unsigned int group_offset;
struct axi_perf_counter *axi_perf_cnt;
if (cnt_idx >= AS370_AXI_CNT_NUM)
return -EINVAL;
as370_axi_latch_perf_cnt(axi_meter, cnt_idx);
axi_perf_cnt = &axi_meter->perf_cnts[cnt_idx];
group_offset = RA_MC6Ctrl_Mstr0PCnt + cnt_idx*(RA_MC6Ctrl_Mstr0DXBARPCnt - RA_MC6Ctrl_Mstr0PCnt);
spin_lock(&axi_meter->lock);
axi_perf_cnt->status = readl(axi_meter->base + group_offset + RA_AxiPCntStat_OF_STATUS);
axi_perf_cnt->total = readl(axi_meter->base + group_offset + RA_AxiPCntStat_TOTAL_CNT);
axi_perf_cnt->rdata = readl(axi_meter->base + group_offset + RA_AxiPCntStat_RDATA_CNT);
axi_perf_cnt->ardata = readl(axi_meter->base + group_offset + RA_AxiPCntStat_ARDATA_CNT);
axi_perf_cnt->wdata = readl(axi_meter->base + group_offset + RA_AxiPCntStat_WDATA_CNT);
axi_perf_cnt->awdata = readl(axi_meter->base + group_offset + RA_AxiPCntStat_AWDATA_CNT);
spin_unlock(&axi_meter->lock);
return 0;
}
struct axi_meter_sysfs_attr {
struct attribute attr;
ssize_t (*show)(struct axi_meter_priv *, char *);
ssize_t (*store)(struct axi_meter_priv *, const char *, size_t count);
};
#define AM_ATTR_RO(_name) \
struct axi_meter_sysfs_attr axi_meter_attr_##_name = \
__ATTR(_name, S_IRUGO, axi_meter_attr_##_name##_show, NULL)
#define AM_ATTR_RW(_name) \
struct axi_meter_sysfs_attr axi_meter_attr_##_name = \
__ATTR(_name, S_IRUGO | S_IWUSR, axi_meter_attr_##_name##_show, axi_meter_attr_##_name##_store)
static ssize_t axi_meter_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct axi_meter_priv *axi_meter;
struct axi_meter_sysfs_attr *axi_meter_attr;
ssize_t ret;
axi_meter_attr = container_of(attr, struct axi_meter_sysfs_attr, attr);
if (!axi_meter_attr->show)
return -EIO;
axi_meter = container_of(kobj, struct axi_meter_priv, kobj);
ret = axi_meter_attr->show(axi_meter, buf);
return ret;
}
static ssize_t axi_meter_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct axi_meter_priv *axi_meter;
struct axi_meter_sysfs_attr *axi_meter_attr;
ssize_t ret;
axi_meter_attr = container_of(attr, struct axi_meter_sysfs_attr, attr);
if (!axi_meter_attr->store)
return -EIO;
axi_meter = container_of(kobj, struct axi_meter_priv, kobj);
ret = axi_meter_attr->store(axi_meter, buf, count);
return ret;
}
static ssize_t axi_meter_attr_cnt0_show(struct axi_meter_priv *axi_meter, char *buf)
{
int len;
u32 overflow;
struct axi_perf_counter *perf_cnt;
as370_axi_update_perf_cnt(axi_meter, 0);
perf_cnt = &axi_meter->perf_cnts[0];
overflow = perf_cnt->status;
len = sprintf(buf, "%s: tot 0x%08x, AR: 0x%08x%s, AW: 0x%08x%s\n",
as370_mask_table[axi_meter->cnt_ids[0]].name,
perf_cnt->total,
perf_cnt->ardata, (overflow & OVERFLOW_ARDATA) ? " overflow" : "",
perf_cnt->awdata, (overflow & OVERFLOW_AWDATA) ? " overflow" : "");
return len;
}
static ssize_t axi_meter_attr_cnt1_show(struct axi_meter_priv *axi_meter, char *buf)
{
int len;
u32 overflow;
struct axi_perf_counter *perf_cnt;
as370_axi_update_perf_cnt(axi_meter, 1);
perf_cnt = &axi_meter->perf_cnts[1];
overflow = perf_cnt->status;
len = sprintf(buf, "%s: tot 0x%08x, AR: 0x%08x%s, AW: 0x%08x%s\n",
as370_mask_table[axi_meter->cnt_ids[1]].name,
perf_cnt->total,
perf_cnt->ardata, (overflow & OVERFLOW_ARDATA) ? " overflow" : "",
perf_cnt->awdata, (overflow & OVERFLOW_AWDATA) ? " overflow" : "");
return len;
}
static ssize_t axi_meter_attr_cnt2_show(struct axi_meter_priv *axi_meter, char *buf)
{
int len;
u32 overflow;
struct axi_perf_counter *perf_cnt;
as370_axi_update_perf_cnt(axi_meter, 2);
perf_cnt = &axi_meter->perf_cnts[2];
overflow = perf_cnt->status;
len = sprintf(buf, "%s: tot 0x%08x, AR: 0x%08x%s, AW: 0x%08x%s\n",
as370_mask_table[axi_meter->cnt_ids[2]].name,
perf_cnt->total,
perf_cnt->ardata, (overflow & OVERFLOW_ARDATA) ? " overflow" : "",
perf_cnt->awdata, (overflow & OVERFLOW_AWDATA) ? " overflow" : "");
return len;
}
static ssize_t axi_meter_attr_enable_show(struct axi_meter_priv *axi_meter, char *buf)
{
return sprintf(buf, "cnt0 : %d\ncnt1 : %d\ncnt2 : %d\n",
axi_meter->cnt_en[0], axi_meter->cnt_en[1], axi_meter->cnt_en[2]);
}
static ssize_t axi_meter_attr_enable_store(struct axi_meter_priv *axi_meter, const char *buf, size_t count)
{
unsigned int cnt_idx;
cnt_idx = simple_strtoul(buf, NULL, 0);
as370_axi_enable_perf_cnt(axi_meter, cnt_idx, 1);
return count;
}
static ssize_t axi_meter_attr_disable_show(struct axi_meter_priv *axi_meter, char *buf)
{
return 0;
}
static ssize_t axi_meter_attr_disable_store(struct axi_meter_priv *axi_meter, const char *buf, size_t count)
{
unsigned int cnt_idx;
cnt_idx = simple_strtoul(buf, NULL, 0);
as370_axi_enable_perf_cnt(axi_meter, cnt_idx, 0);
return count;
}
static ssize_t axi_meter_attr_clear_show(struct axi_meter_priv *axi_meter, char *buf)
{
return 0;
}
static ssize_t axi_meter_attr_clear_store(struct axi_meter_priv *axi_meter, const char *buf, size_t count)
{
unsigned int cnt_idx;
cnt_idx = simple_strtoul(buf, NULL, 0);
as370_axi_clear_perf_cnt(axi_meter, cnt_idx);
return count;
}
static ssize_t axi_meter_attr_mask_show(struct axi_meter_priv *axi_meter, char *buf)
{
return sprintf(buf, "cnt0 : %s\ncnt1 : %s\ncnt2 : %s\n",
as370_mask_table[axi_meter->cnt_ids[0]].name,
as370_mask_table[axi_meter->cnt_ids[1]].name,
as370_mask_table[axi_meter->cnt_ids[2]].name);
}
static ssize_t axi_meter_attr_mask_store(struct axi_meter_priv *axi_meter, const char *buf, size_t count)
{
unsigned int cnt_idx, mask_id;
if (sscanf(buf, "%d %d", &cnt_idx, &mask_id) < 0) {
return -1;
}
if (as370_axi_config_mask(axi_meter, cnt_idx, mask_id) < 0)
return -1;
return count;
}
static AM_ATTR_RO(cnt0);
static AM_ATTR_RO(cnt1);
static AM_ATTR_RO(cnt2);
static AM_ATTR_RW(enable);
static AM_ATTR_RW(disable);
static AM_ATTR_RW(clear);
static AM_ATTR_RW(mask);
static struct attribute *as370_axi_meter_attrs[] = {
&axi_meter_attr_cnt0.attr,
&axi_meter_attr_cnt1.attr,
&axi_meter_attr_cnt2.attr,
&axi_meter_attr_enable.attr,
&axi_meter_attr_disable.attr,
&axi_meter_attr_clear.attr,
&axi_meter_attr_mask.attr,
NULL,
};
static const struct sysfs_ops as370_axi_meter_sysfs_ops = {
.show = axi_meter_attr_show,
.store = axi_meter_attr_store,
};
static struct kobj_type axi_meter_ktype = {
.sysfs_ops = &as370_axi_meter_sysfs_ops,
.default_attrs = as370_axi_meter_attrs,
};
static int as370_axi_meter_sysfs_init(struct axi_meter_priv *axi_meter)
{
return kobject_init_and_add(&axi_meter->kobj, &axi_meter_ktype,
&axi_meter->pdev->dev.kobj,
"%s", axi_meter->dev_name);
}
static int as370_axi_meter_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct axi_meter_priv *axi_meter;
struct resource *res;
axi_meter = devm_kzalloc(dev, sizeof(struct axi_meter_priv), GFP_KERNEL);
if (!axi_meter)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
axi_meter->base = devm_ioremap_resource(dev, res);
if (IS_ERR(axi_meter->base)) {
dev_err(dev, "map devmem failed\n");
return -ENODEV;
}
spin_lock_init(&axi_meter->lock);
axi_meter->dev_name = dev_name(dev);
axi_meter->pdev = pdev;
dev_set_drvdata(dev, axi_meter);
as370_axi_init_perf_cnt(axi_meter);
as370_axi_meter_sysfs_init(axi_meter);
dev_info(dev, "as370 axi meter start\n");
return 0;
}
static int as370_axi_meter_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct axi_meter_priv *axi_meter;
axi_meter = (struct axi_meter_priv *)dev_get_drvdata(dev);
kobject_put(&axi_meter->kobj);
as370_axi_stop_perf_cnt(axi_meter);
return 0;
}
static const struct of_device_id as370_axi_meter_dt_ids[] = {
{ .compatible = "syna,as370-axi-meter",},
{}
};
MODULE_DEVICE_TABLE(of, as370_axi-meter_dt_ids);
static struct platform_driver as370_axi_meter_driver = {
.probe = as370_axi_meter_probe,
.remove = as370_axi_meter_remove,
.driver = {
.name = "syna-as370-axi-meter",
.of_match_table = as370_axi_meter_dt_ids,
},
};
module_platform_driver(as370_axi_meter_driver);
MODULE_DESCRIPTION("Synaptics AS370 mc6 axi meter");
MODULE_ALIAS("platform:as370-axi-meter");
MODULE_LICENSE("GPL v2");