blob: 5986e1ca95080264d7be2c16a4a4a1f170062add [file] [log] [blame]
/*
* Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
*/
/*
* 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.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/sysdev.h>
#include <linux/bitops.h>
#include <linux/platform_device.h>
#include <linux/sysfs.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/fsl_devices.h>
#include <mach/hardware.h>
#include <asm/irq.h>
#include <mach/system.h>
#include "regs-perfmon.h"
#define MONITOR "Monitor"
struct mxs_perfmon_cmd_config {
int field;
int val;
const char *cmd;
};
static struct mxs_perfmon_cmd_config
common_perfmon_cmd_config[] = {
{.val = 1, .cmd = "start", .field = BM_PERFMON_CTRL_RUN },
{.val = 0, .cmd = "stop", .field = BM_PERFMON_CTRL_RUN },
{.val = 1, .cmd = "fetch", .field = BM_PERFMON_CTRL_SNAP },
{.val = 1, .cmd = "clear", .field = BM_PERFMON_CTRL_CLR },
{.val = 1, .cmd = "read", .field = BM_PERFMON_CTRL_READ_EN },
{.val = 0, .cmd = "write", .field = BM_PERFMON_CTRL_READ_EN }
};
static struct mxs_perfmon_bit_config
common_perfmon_bit_config[] = {
{.reg = HW_PERFMON_CTRL, .name = MONITOR,
.field = ~0 },
{.reg = HW_PERFMON_CTRL, .name = "Trap-En",
.field = BM_PERFMON_CTRL_TRAP_ENABLE },
{.reg = HW_PERFMON_CTRL, .name = "Trap-In-Range",
.field = BM_PERFMON_CTRL_TRAP_IN_RANGE },
{.reg = HW_PERFMON_CTRL, .name = "Latency-En",
.field = BM_PERFMON_CTRL_LATENCY_ENABLE },
{.reg = HW_PERFMON_CTRL, .name = "Trap-IRQ",
.field = BM_PERFMON_CTRL_TRAP_IRQ },
{.reg = HW_PERFMON_CTRL, .name = "Latency-IRQ",
.field = BM_PERFMON_CTRL_LATENCY_IRQ },
{.reg = HW_PERFMON_TRAP_ADDR_LOW, .name = "Trap-Low",
.field = BM_PERFMON_TRAP_ADDR_LOW_ADDR },
{.reg = HW_PERFMON_TRAP_ADDR_HIGH, .name = "Trap-High",
.field = BM_PERFMON_TRAP_ADDR_HIGH_ADDR },
{.reg = HW_PERFMON_LAT_THRESHOLD, .name = "Latency-Threshold",
.field = BM_PERFMON_LAT_THRESHOLD_VALUE },
{.reg = HW_PERFMON_ACTIVE_CYCLE, .name = "Active-Cycle",
.field = BM_PERFMON_ACTIVE_CYCLE_COUNT },
{.reg = HW_PERFMON_TRANSFER_COUNT, .name = "Transfer-count",
.field = BM_PERFMON_TRANSFER_COUNT_VALUE },
{.reg = HW_PERFMON_TOTAL_LATENCY, .name = "Latency-count",
.field = BM_PERFMON_TOTAL_LATENCY_COUNT },
{.reg = HW_PERFMON_DATA_COUNT, .name = "Data-count",
.field = BM_PERFMON_DATA_COUNT_COUNT },
{.reg = HW_PERFMON_MAX_LATENCY, .name = "ABurst",
.field = BM_PERFMON_MAX_LATENCY_ABURST },
{.reg = HW_PERFMON_MAX_LATENCY, .name = "ALen",
.field = BM_PERFMON_MAX_LATENCY_ALEN },
{.reg = HW_PERFMON_MAX_LATENCY, .name = "ASize",
.field = BM_PERFMON_MAX_LATENCY_ASIZE },
{.reg = HW_PERFMON_MAX_LATENCY, .name = "TAGID",
.field = BM_PERFMON_MAX_LATENCY_TAGID },
{.reg = HW_PERFMON_MAX_LATENCY, .name = "Max-Count",
.field = BM_PERFMON_MAX_LATENCY_COUNT }
};
static struct mxs_platform_perfmon_data common_perfmon_data = {
.bit_config_tab = common_perfmon_bit_config,
.bit_config_cnt = ARRAY_SIZE(common_perfmon_bit_config),
};
struct mxs_perfmon_data {
struct device *dev;
struct mxs_platform_perfmon_data *pdata;
struct mxs_platform_perfmon_data *pdata_common;
int count;
struct attribute_group attr_group;
unsigned int base;
unsigned int initial;
struct clk *clk;
/* attribute ** follow */
/* device_attribute follow */
};
#define pd_attribute_ptr(x) \
((struct attribute **)((x) + 1))
#define pd_device_attribute_ptr(x) \
((struct device_attribute *)(pd_attribute_ptr(x) + (x)->count + 1))
static inline u32 perfmon_reg_read(struct mxs_perfmon_data *pdata,
int reg)
{
return __raw_readl(pdata->base + reg);
}
static inline void perfmon_reg_write(struct mxs_perfmon_data *pdata,
u32 val, int reg)
{
__raw_writel(val, pdata->base + reg);
}
static int get_offset_form_field(int field)
{
int offset = 0;
if (!field)
return offset;
while (!(field & 0x1)) {
field >>= 1;
offset++;
}
return offset;
}
static ssize_t
perfmon_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct mxs_perfmon_data *pd = platform_get_drvdata(pdev);
struct device_attribute *devattr = pd_device_attribute_ptr(pd);
struct mxs_perfmon_bit_config *pb;
int idx;
u32 val;
ssize_t result = 0;
struct mxs_platform_perfmon_data *pdata = pdev->dev.platform_data;
idx = attr - devattr;
if ((unsigned int)idx >= pd->count)
return -EINVAL;
if (idx < pd->pdata->bit_config_cnt) {
pb = &pd->pdata->bit_config_tab[idx];
pb->reg = HW_PERFMON_MASTER_EN;
} else
pb = &pd->pdata_common->bit_config_tab \
[idx - pd->pdata->bit_config_cnt];
if (!pd->initial) {
if (pd->clk)
clk_enable(pd->clk);
if (pdata->plt_init)
pdata->plt_init();
mxs_reset_block((void *)pd->base, true);
pd->initial = true;
}
if (!memcmp(pb->name, MONITOR, sizeof(MONITOR))) {
/* cat monitor command, we return monitor status */
val = perfmon_reg_read(pd, pb->reg);
if (val & BV_PERFMON_CTRL_RUN__RUN)
result += sprintf(buf, "Run mode\r\n");
else
result += sprintf(buf, "Stop mode\r\n");
if (val & BM_PERFMON_CTRL_READ_EN)
result += \
sprintf(buf + result, "PM Read Activities\r\n");
else
result += \
sprintf(buf + result, "PM Write Activities\r\n");
return result;
}
/* read value and shift */
val = perfmon_reg_read(pd, pb->reg);
val &= pb->field;
val >>= get_offset_form_field(pb->field);
return sprintf(buf, "0x%x\n", val);
}
static ssize_t
perfmon_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct platform_device *pdev = to_platform_device(dev);
struct mxs_perfmon_data *pd = platform_get_drvdata(pdev);
struct device_attribute *devattr = pd_device_attribute_ptr(pd);
struct mxs_perfmon_bit_config *pb;
int idx, r;
unsigned long val, newval;
struct mxs_platform_perfmon_data *pdata = pdev->dev.platform_data;
idx = attr - devattr;
if ((unsigned int)idx >= pd->count)
return -EINVAL;
if (!buf)
return -EINVAL;
if (idx < pd->pdata->bit_config_cnt) {
pb = &pd->pdata->bit_config_tab[idx];
pb->reg = HW_PERFMON_MASTER_EN;
} else
pb = &pd->pdata_common->bit_config_tab \
[idx - pd->pdata->bit_config_cnt];
if (!pd->initial) {
if (pd->clk)
clk_enable(pd->clk);
if (pdata->plt_init)
pdata->plt_init();
mxs_reset_block((void *)pd->base, true);
pd->initial = true;
}
if (!memcmp(pb->name, MONITOR, sizeof(MONITOR))) {
/* it's a cmd */
int scan, size;
const struct mxs_perfmon_cmd_config *pcfg;
size = ARRAY_SIZE(common_perfmon_cmd_config);
for (scan = 0; scan < size; scan++) {
pcfg = &common_perfmon_cmd_config[scan];
if (!memcmp(buf, pcfg->cmd, strlen(pcfg->cmd))) {
val = perfmon_reg_read(pd, HW_PERFMON_CTRL);
val &= ~pcfg->field;
val |= \
pcfg->val << get_offset_form_field(pcfg->field);
perfmon_reg_write(pd, val, HW_PERFMON_CTRL);
return count;
}
}
if (scan == ARRAY_SIZE(common_perfmon_cmd_config))
return -EINVAL;
}
/* get value to write */
if (buf && (count >= 2) && buf[0] == '0' && buf[1] == 'x')
r = strict_strtoul(buf, 16, &val);
else
r = strict_strtoul(buf, 10, &val);
if (r != 0)
return r;
/* verify it fits */
if ((unsigned int)val > (pb->field >> get_offset_form_field(pb->field)))
return -EINVAL;
newval = perfmon_reg_read(pd, pb->reg);
newval &= ~pb->field;
newval |= val << get_offset_form_field(pb->field);
perfmon_reg_write(pd, newval, pb->reg);
return count;
}
static int __devinit mxs_perfmon_probe(struct platform_device *pdev)
{
struct mxs_perfmon_data *pd;
struct mxs_platform_perfmon_data *pdata;
struct mxs_platform_perfmon_data *pdata_common;
struct resource *res;
struct mxs_perfmon_bit_config *pb;
struct attribute **attr;
struct device_attribute *devattr;
int i, cnt, size;
int err;
struct device *dev = &pdev->dev;
pdata = pdev->dev.platform_data;
if (pdata == NULL)
return -ENODEV;
pdata_common = &common_perfmon_data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL)
return -ENODEV;
cnt = pdata->bit_config_cnt + pdata_common->bit_config_cnt;
size = sizeof(*pd) +
(cnt + 1) * sizeof(struct atrribute *) +
cnt * sizeof(struct device_attribute);
pd = kzalloc(size, GFP_KERNEL);
if (pd == NULL)
return -ENOMEM;
pd->dev = &pdev->dev;
pd->pdata = pdata;
pd->pdata_common = pdata_common;
pd->base = (unsigned int)ioremap(res->start, res->end - res->start);
pd->initial = false;
pd->clk = clk_get(dev, "perfmon");
platform_set_drvdata(pdev, pd);
pd->count = cnt;
attr = pd_attribute_ptr(pd);
devattr = pd_device_attribute_ptr(pd);
/* build the attributes structures */
pd->attr_group.attrs = attr;
pb = pdata->bit_config_tab;
for (i = 0; i < pdata->bit_config_cnt; i++) {
devattr[i].attr.name = pb[i].name;
devattr[i].attr.mode = S_IWUSR | S_IRUGO;
devattr[i].show = perfmon_show;
devattr[i].store = perfmon_store;
attr[i] = &devattr[i].attr;
}
pb = pdata_common->bit_config_tab;
for (i = 0; i < pdata_common->bit_config_cnt; i++) {
devattr[i + pdata->bit_config_cnt].attr.name = pb[i].name;
devattr[i + pdata->bit_config_cnt].attr.mode = \
S_IWUSR | S_IRUGO;
devattr[i + pdata->bit_config_cnt].show = perfmon_show;
devattr[i + pdata->bit_config_cnt].store = perfmon_store;
attr[i + pdata->bit_config_cnt] = \
&devattr[i + pdata->bit_config_cnt].attr;
}
err = sysfs_create_group(&pdev->dev.kobj, &pd->attr_group);
if (err != 0) {
platform_set_drvdata(pdev, NULL);
kfree(pd);
return err;
}
return 0;
}
static int __devexit mxs_perfmon_remove(struct platform_device *pdev)
{
struct mxs_perfmon_data *pd;
struct mxs_platform_perfmon_data *pdata = pdev->dev.platform_data;;
pd = platform_get_drvdata(pdev);
sysfs_remove_group(&pdev->dev.kobj, &pd->attr_group);
platform_set_drvdata(pdev, NULL);
if (pdata->plt_exit)
pdata->plt_exit();
if (pd->clk) {
if (pd->initial)
clk_disable(pd->clk);
clk_put(pd->clk);
}
kfree(pd);
return 0;
}
#ifdef CONFIG_PM
static int
mxs_perfmon_suspend(struct platform_device *pdev, pm_message_t state)
{
return 0;
}
static int mxs_perfmon_resume(struct platform_device *pdev)
{
return 0;
}
#else
#define mxs_perfmon_suspend NULL
#define mxs_perfmon_resume NULL
#endif
static struct platform_driver mxs_perfmon_driver = {
.probe = mxs_perfmon_probe,
.remove = __exit_p(mxs_perfmon_remove),
.suspend = mxs_perfmon_suspend,
.resume = mxs_perfmon_resume,
.driver = {
.name = "mxs-perfmon",
.owner = THIS_MODULE,
},
};
static int __init mxs_perfmon_init(void)
{
return platform_driver_register(&mxs_perfmon_driver);
}
static void __exit mxs_perfmon_exit(void)
{
platform_driver_unregister(&mxs_perfmon_driver);
}
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("Performance Monitor user-access driver");
MODULE_LICENSE("GPL");
module_init(mxs_perfmon_init);
module_exit(mxs_perfmon_exit);