/* * Copyright (C) 2011-2012 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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); 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); 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);