/* * Xilinx AXI Performance Monitor * * Copyright (C) 2013 Xilinx, Inc. All rights reserved. * * Description: * This driver is developed for AXI Performance Monitor IP, * designed to monitor AXI4 traffic for performance analysis * of AXI bus in the system. Driver maps HW registers and parameters * to userspace. Userspace need not clear the interrupt of IP since * driver clears the interrupt. * * 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, version 2 of the License. * * 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, see . */ #include #include #include #include #include #include #include #include #define XAPM_IS_OFFSET 0x0038 /* Interrupt Status Register */ #define DRV_NAME "xilinxapm_uio" #define DRV_VERSION "1.0" #define UIO_DUMMY_MEMSIZE 4096 #define XAPM_MODE_ADVANCED 1 #define XAPM_MODE_PROFILE 2 #define XAPM_MODE_TRACE 3 /** * struct xapm_param - HW parameters structure * @mode: Mode in which APM is working * @maxslots: Maximum number of Slots in APM * @eventcnt: Event counting enabled in APM * @eventlog: Event logging enabled in APM * @sampledcnt: Sampled metric counters enabled in APM * @numcounters: Number of counters in APM * @metricwidth: Metric Counter width (32/64) * @sampledwidth: Sampled metric counter width * @globalcntwidth: Global Clock counter width * @scalefactor: Scaling factor * @isr: Interrupts info shared to userspace * @is_32bit_filter: Flags for 32bit filter * @clk: Clock handle */ struct xapm_param { u32 mode; u32 maxslots; u32 eventcnt; u32 eventlog; u32 sampledcnt; u32 numcounters; u32 metricwidth; u32 sampledwidth; u32 globalcntwidth; u32 scalefactor; u32 isr; bool is_32bit_filter; struct clk *clk; }; /** * struct xapm_dev - Global driver structure * @info: uio_info structure * @param: xapm_param structure * @regs: IOmapped base address */ struct xapm_dev { struct uio_info info; struct xapm_param param; void __iomem *regs; }; /** * xapm_handler - Interrupt handler for APM * @irq: IRQ number * @info: Pointer to uio_info structure * * Return: Always returns IRQ_HANDLED */ static irqreturn_t xapm_handler(int irq, struct uio_info *info) { struct xapm_dev *xapm = (struct xapm_dev *)info->priv; void *ptr; ptr = (unsigned long *)xapm->info.mem[1].addr; /* Clear the interrupt and copy the ISR value to userspace */ xapm->param.isr = readl(xapm->regs + XAPM_IS_OFFSET); writel(xapm->param.isr, xapm->regs + XAPM_IS_OFFSET); memcpy(ptr, &xapm->param, sizeof(struct xapm_param)); return IRQ_HANDLED; } /** * xapm_getprop - Retrieves dts properties to param structure * @pdev: Pointer to platform device * @param: Pointer to param structure * * Returns: '0' on success and failure value on error */ static int xapm_getprop(struct platform_device *pdev, struct xapm_param *param) { u32 mode = 0; int ret; struct device_node *node; node = pdev->dev.of_node; /* Retrieve required dts properties and fill param structure */ ret = of_property_read_u32(node, "xlnx,enable-profile", &mode); if (ret < 0) dev_info(&pdev->dev, "no property xlnx,enable-profile\n"); else if (mode) param->mode = XAPM_MODE_PROFILE; ret = of_property_read_u32(node, "xlnx,enable-trace", &mode); if (ret < 0) dev_info(&pdev->dev, "no property xlnx,enable-trace\n"); else if (mode) param->mode = XAPM_MODE_TRACE; ret = of_property_read_u32(node, "xlnx,num-monitor-slots", ¶m->maxslots); if (ret < 0) { dev_err(&pdev->dev, "no property xlnx,num-monitor-slots"); return ret; } ret = of_property_read_u32(node, "xlnx,enable-event-count", ¶m->eventcnt); if (ret < 0) { dev_err(&pdev->dev, "no property xlnx,enable-event-count"); return ret; } ret = of_property_read_u32(node, "xlnx,enable-event-log", ¶m->eventlog); if (ret < 0) { dev_err(&pdev->dev, "no property xlnx,enable-event-log"); return ret; } ret = of_property_read_u32(node, "xlnx,have-sampled-metric-cnt", ¶m->sampledcnt); if (ret < 0) { dev_err(&pdev->dev, "no property xlnx,have-sampled-metric-cnt"); return ret; } ret = of_property_read_u32(node, "xlnx,num-of-counters", ¶m->numcounters); if (ret < 0) { dev_err(&pdev->dev, "no property xlnx,num-of-counters"); return ret; } ret = of_property_read_u32(node, "xlnx,metric-count-width", ¶m->metricwidth); if (ret < 0) { dev_err(&pdev->dev, "no property xlnx,metric-count-width"); return ret; } ret = of_property_read_u32(node, "xlnx,metrics-sample-count-width", ¶m->sampledwidth); if (ret < 0) { dev_err(&pdev->dev, "no property metrics-sample-count-width"); return ret; } ret = of_property_read_u32(node, "xlnx,global-count-width", ¶m->globalcntwidth); if (ret < 0) { dev_err(&pdev->dev, "no property xlnx,global-count-width"); return ret; } ret = of_property_read_u32(node, "xlnx,metric-count-scale", ¶m->scalefactor); if (ret < 0) { dev_err(&pdev->dev, "no property xlnx,metric-count-scale"); return ret; } param->is_32bit_filter = of_property_read_bool(node, "xlnx,id-filter-32bit"); return 0; } /** * xapm_probe - Driver probe function * @pdev: Pointer to the platform_device structure * * Returns: '0' on success and failure value on error */ static int xapm_probe(struct platform_device *pdev) { struct xapm_dev *xapm; struct resource *res; int irq; int ret; void *ptr; xapm = devm_kzalloc(&pdev->dev, (sizeof(struct xapm_dev)), GFP_KERNEL); if (!xapm) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); xapm->regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(xapm->regs)) { dev_err(&pdev->dev, "unable to iomap registers\n"); return PTR_ERR(xapm->regs); } xapm->param.clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(xapm->param.clk)) { if (PTR_ERR(xapm->param.clk) != -EPROBE_DEFER) dev_err(&pdev->dev, "axi clock error\n"); return PTR_ERR(xapm->param.clk); } ret = clk_prepare_enable(xapm->param.clk); if (ret) { dev_err(&pdev->dev, "Unable to enable clock.\n"); return ret; } pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); /* Initialize mode as Advanced so that if no mode in dts, default * is Advanced */ xapm->param.mode = XAPM_MODE_ADVANCED; ret = xapm_getprop(pdev, &xapm->param); if (ret < 0) goto err_clk_dis; xapm->info.mem[0].name = "xilinx_apm"; xapm->info.mem[0].addr = res->start; xapm->info.mem[0].size = resource_size(res); xapm->info.mem[0].memtype = UIO_MEM_PHYS; xapm->info.mem[1].addr = (unsigned long)kzalloc(UIO_DUMMY_MEMSIZE, GFP_KERNEL); ptr = (unsigned long *)xapm->info.mem[1].addr; xapm->info.mem[1].size = UIO_DUMMY_MEMSIZE; xapm->info.mem[1].memtype = UIO_MEM_LOGICAL; xapm->info.name = "axi-pmon"; xapm->info.version = DRV_VERSION; irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "unable to get irq\n"); ret = irq; goto err_clk_dis; } xapm->info.irq = irq; xapm->info.handler = xapm_handler; xapm->info.priv = xapm; xapm->info.irq_flags = IRQF_SHARED; memcpy(ptr, &xapm->param, sizeof(struct xapm_param)); ret = uio_register_device(&pdev->dev, &xapm->info); if (ret < 0) { dev_err(&pdev->dev, "unable to register to UIO\n"); goto err_clk_dis; } platform_set_drvdata(pdev, xapm); dev_info(&pdev->dev, "Probed Xilinx APM\n"); return 0; err_clk_dis: clk_disable_unprepare(xapm->param.clk); pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); return ret; } /** * xapm_remove - Driver remove function * @pdev: Pointer to the platform_device structure * * Return: Always returns '0' */ static int xapm_remove(struct platform_device *pdev) { struct xapm_dev *xapm = platform_get_drvdata(pdev); uio_unregister_device(&xapm->info); clk_disable_unprepare(xapm->param.clk); pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); return 0; } static int __maybe_unused xapm_runtime_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct xapm_dev *xapm = platform_get_drvdata(pdev); clk_disable_unprepare(xapm->param.clk); return 0; }; static int __maybe_unused xapm_runtime_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct xapm_dev *xapm = platform_get_drvdata(pdev); int ret; ret = clk_prepare_enable(xapm->param.clk); if (ret) { dev_err(&pdev->dev, "Unable to enable clock.\n"); return ret; } return 0; }; static const struct dev_pm_ops xapm_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(xapm_runtime_suspend, xapm_runtime_resume) SET_RUNTIME_PM_OPS(xapm_runtime_suspend, xapm_runtime_resume, NULL) }; static const struct of_device_id xapm_of_match[] = { { .compatible = "xlnx,axi-perf-monitor", }, { /* end of table*/ } }; MODULE_DEVICE_TABLE(of, xapm_of_match); static struct platform_driver xapm_driver = { .driver = { .name = "xilinx-axipmon", .of_match_table = xapm_of_match, .pm = &xapm_dev_pm_ops, }, .probe = xapm_probe, .remove = xapm_remove, }; module_platform_driver(xapm_driver); MODULE_AUTHOR("Xilinx Inc."); MODULE_DESCRIPTION("Xilinx AXI Performance Monitor driver"); MODULE_LICENSE("GPL v2");