Merge "coresight: tgu: Add support for Trigger Generation Unit"
diff --git a/Documentation/devicetree/bindings/arm/coresight.txt b/Documentation/devicetree/bindings/arm/coresight.txt
index d4a352b..18386ab 100644
--- a/Documentation/devicetree/bindings/arm/coresight.txt
+++ b/Documentation/devicetree/bindings/arm/coresight.txt
@@ -39,6 +39,8 @@
- System Trace Macrocell:
"arm,coresight-stm", "arm,primecell"; [1]
+ - Trigger Generation Unit:
+ "arm,primecell";
* reg: physical base address and length of the register
set(s) of the component.
@@ -86,6 +88,16 @@
* qcom,dummy-sink: Configure the device as sink.
+* Additional required property for coresight-tgu devices:
+ * tgu-steps: must be present. Indicates number of steps supported
+ by the TGU.
+ * tgu-conditions: must be present. Indicates the number of conditions
+ supported by the TGU.
+ * tgu-regs: must be present. Indicates the number of regs supported
+ by the TGU.
+ * tgu-timer-counters: must be present. Indicates the number of timers and
+ counters available in the TGU to do a comparision.
+
* Optional properties for all components:
* reg-names: names corresponding to each reg property value.
@@ -388,7 +400,7 @@
};
};
-4. CTIs
+5. CTIs
cti0: cti@6010000 {
compatible = "arm,coresight-cti", "arm,primecell";
reg = <0x6010000 0x1000>;
@@ -400,5 +412,21 @@
clock-names = "apb_pclk";
};
+6. TGUs
+ ipcb_tgu: tgu@6b0c000 {
+ compatible = "arm,primecell";
+ arm,primecell-periphid = <0x0003b999>;
+ reg = <0x06B0C000 0x1000>;
+ reg-names = "tgu-base";
+ tgu-steps = <3>;
+ tgu-conditions = <4>;
+ tgu-regs = <4>;
+ tgu-timer-counters = <8>;
+
+ coresight-name = "coresight-tgu-ipcb";
+
+ clocks = <&clock_aop QDSS_CLK>;
+ clock-names = "apb_pclk";
+ };
[1]. There is currently two version of STM: STM32 and STM500. Both
have the same HW interface and as such don't need an explicit binding name.
diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig
index ae494d7..8d242f8 100644
--- a/drivers/hwtracing/coresight/Kconfig
+++ b/drivers/hwtracing/coresight/Kconfig
@@ -156,6 +156,17 @@
occurrence. These events can be controlled by using module
parameters.
+config CORESIGHT_TGU
+ bool "CoreSight Trigger Generation Unit driver"
+ help
+ This driver provides support for Trigger Generation Unit that is
+ used to detect patterns or sequences on a given set of signals.
+ TGU is used to monitor a particular bus within a given region to
+ detect illegal transaction sequences or slave responses. It is also
+ used to monitor a data stream to detect protocol violations and to
+ provide a trigger point for centering data around a specific event
+ within the trace data buffer.
+
config CORESIGHT_CSR
bool "CoreSight Slave Register driver"
help
diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile
index 0b5e434..44e55ff 100644
--- a/drivers/hwtracing/coresight/Makefile
+++ b/drivers/hwtracing/coresight/Makefile
@@ -21,6 +21,7 @@
obj-$(CONFIG_CORESIGHT_TPDM) += coresight-tpdm.o
obj-$(CONFIG_CORESIGHT_EVENT) += coresight-event.o
obj-$(CONFIG_CORESIGHT_CTI) += coresight-cti.o
+obj-$(CONFIG_CORESIGHT_TGU) += coresight-tgu.o
obj-$(CONFIG_CORESIGHT_CSR) += coresight-csr.o
obj-$(CONFIG_CORESIGHT_HWEVENT) += coresight-hwevent.o
obj-$(CONFIG_CORESIGHT_DUMMY) += coresight-dummy.o
diff --git a/drivers/hwtracing/coresight/coresight-tgu.c b/drivers/hwtracing/coresight/coresight-tgu.c
new file mode 100644
index 0000000..e919f47
--- /dev/null
+++ b/drivers/hwtracing/coresight/coresight-tgu.c
@@ -0,0 +1,534 @@
+/* Copyright (c) 2017, The Linux Foundation. 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 version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/amba/bus.h>
+#include <linux/topology.h>
+#include <linux/of.h>
+#include <linux/coresight.h>
+
+#include "coresight-priv.h"
+
+#define tgu_writel(drvdata, val, off) __raw_writel((val), drvdata->base + off)
+#define tgu_readl(drvdata, off) __raw_readl(drvdata->base + off)
+
+#define TGU_LOCK(drvdata) \
+do { \
+ mb(); /* ensure configuration take effect before we lock it */ \
+ tgu_writel(drvdata, 0x0, CORESIGHT_LAR); \
+} while (0)
+#define TGU_UNLOCK(drvdata) \
+do { \
+ tgu_writel(drvdata, CORESIGHT_UNLOCK, CORESIGHT_LAR); \
+ mb(); /* ensure unlock take effect before we configure */ \
+} while (0)
+
+#define TGU_CONTROL 0x0000
+#define TIMER0_STATUS 0x0004
+#define COUNTER0_STATUS 0x000C
+#define TGU_STATUS 0x0014
+#define TIMER0_COMPARE_STEP(n) (0x0040 + 0x1D8 * n)
+#define COUNTER0_COMPARE_STEP(n) (0x0048 + 0x1D8 * n)
+#define GROUP_REG_STEP(grp, reg, step) (0x0074 + 0x60 * grp + 0x4 * reg + \
+ 0x1D8 * step)
+#define CONDITION_DECODE_STEP(m, n) (0x0050 + 0x4 * m + 0x1D8 * n)
+#define CONDITION_SELECT_STEP(m, n) (0x0060 + 0x4 * m + 0x1D8 * n)
+#define GROUP0 0x0074
+#define GROUP1 0x00D4
+#define GROUP2 0x0134
+#define GROUP3 0x0194
+#define TGU_LAR 0x0FB0
+
+#define MAX_GROUP_SETS 256
+#define MAX_GROUPS 4
+#define MAX_CONDITION_SETS 64
+#define MAX_TIMER_COUNTER_SETS 8
+
+#define to_tgu_drvdata(c) container_of(c, struct tgu_drvdata, tgu)
+
+struct Trigger_group_data {
+ unsigned long grpaddr;
+ unsigned long value;
+};
+
+struct Trigger_condition_data {
+ unsigned long condaddr;
+ unsigned long value;
+};
+
+struct Trigger_select_data {
+ unsigned long selectaddr;
+ unsigned long value;
+};
+
+struct Trigger_timer_data {
+ unsigned long timeraddr;
+ unsigned long value;
+};
+
+struct Trigger_counter_data {
+ unsigned long counteraddr;
+ unsigned long value;
+};
+struct tgu_drvdata {
+ void __iomem *base;
+ struct device *dev;
+ struct coresight_device *csdev;
+ struct clk *clk;
+ spinlock_t spinlock;
+ int max_steps;
+ int max_conditions;
+ int max_regs;
+ int max_timer_counter;
+ struct Trigger_group_data *grp_data;
+ struct Trigger_condition_data *condition_data;
+ struct Trigger_select_data *select_data;
+ struct Trigger_timer_data *timer_data;
+ struct Trigger_counter_data *counter_data;
+ int grp_refcnt;
+ int cond_refcnt;
+ int select_refcnt;
+ int timer_refcnt;
+ int counter_refcnt;
+ bool enable;
+};
+
+static ssize_t enable_tgu(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ unsigned long value;
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ int ret, i, j;
+
+ if (kstrtoul(buf, 16, &value))
+ return -EINVAL;
+
+ /* Enable clock */
+ ret = pm_runtime_get_sync(drvdata->dev);
+ if (ret)
+ return ret;
+
+ spin_lock(&drvdata->spinlock);
+ /* Unlock the TGU LAR */
+ TGU_UNLOCK(drvdata);
+
+ if (value) {
+
+ /* Disable TGU to program the triggers */
+ tgu_writel(drvdata, 0, TGU_CONTROL);
+
+ /* program the TGU Group data for the desired use case*/
+
+ for (i = 0; i <= drvdata->grp_refcnt; i++)
+ tgu_writel(drvdata, drvdata->grp_data[i].value,
+ drvdata->grp_data[i].grpaddr);
+
+ /* program the unused Condition Decode registers NOT bits to 1*/
+ for (i = 0; i <= drvdata->max_conditions; i++) {
+ for (j = 0; j <= drvdata->max_steps; j++)
+ tgu_writel(drvdata, 0x1000000,
+ CONDITION_DECODE_STEP(i, j));
+ }
+ /* program the TGU Condition Decode for the desired use case*/
+ for (i = 0; i <= drvdata->cond_refcnt; i++)
+ tgu_writel(drvdata, drvdata->condition_data[i].value,
+ drvdata->condition_data[i].condaddr);
+
+ /* program the TGU Condition Select for the desired use case*/
+ for (i = 0; i <= drvdata->select_refcnt; i++)
+ tgu_writel(drvdata, drvdata->select_data[i].value,
+ drvdata->select_data[i].selectaddr);
+
+ /* Timer and Counter Check */
+ for (i = 0; i <= drvdata->timer_refcnt; i++)
+ tgu_writel(drvdata, drvdata->timer_data[i].value,
+ drvdata->timer_data[i].timeraddr);
+
+ for (i = 0; i <= drvdata->counter_refcnt; i++)
+ tgu_writel(drvdata, drvdata->counter_data[i].value,
+ drvdata->counter_data[i].counteraddr);
+
+ /* Enable TGU to program the triggers */
+ tgu_writel(drvdata, 1, TGU_CONTROL);
+
+ drvdata->enable = true;
+ dev_dbg(dev, "Coresight-TGU enabled\n");
+
+ } else {
+ /* Disable TGU to program the triggers */
+ tgu_writel(drvdata, 0, TGU_CONTROL);
+ TGU_LOCK(drvdata);
+ spin_unlock(&drvdata->spinlock);
+
+ pm_runtime_put(drvdata->dev);
+ dev_dbg(dev, "Coresight-TGU disabled\n");
+ }
+
+ TGU_LOCK(drvdata);
+ spin_unlock(&drvdata->spinlock);
+ return size;
+}
+static DEVICE_ATTR(enable_tgu, 0200, NULL, enable_tgu);
+
+static ssize_t reset_tgu(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ unsigned long value;
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ int ret;
+
+ if (kstrtoul(buf, 16, &value))
+ return -EINVAL;
+
+ if (!drvdata->enable) {
+ /* Enable clock */
+ ret = pm_runtime_get_sync(drvdata->dev);
+ if (ret)
+ return ret;
+ }
+
+ spin_lock(&drvdata->spinlock);
+ /* Unlock the TGU LAR */
+ TGU_UNLOCK(drvdata);
+
+ if (value) {
+ /* Disable TGU to program the triggers */
+ tgu_writel(drvdata, 0, TGU_CONTROL);
+
+ /* Reset the Reference counters*/
+ drvdata->grp_refcnt = 0;
+ drvdata->cond_refcnt = 0;
+ drvdata->select_refcnt = 0;
+ drvdata->timer_refcnt = 0;
+ drvdata->counter_refcnt = 0;
+
+ dev_dbg(dev, "Coresight-TGU disabled\n");
+ } else
+ dev_dbg(dev, "Invalid input to reset the TGU\n");
+
+ TGU_LOCK(drvdata);
+ spin_unlock(&drvdata->spinlock);
+ pm_runtime_put(drvdata->dev);
+ return size;
+}
+static DEVICE_ATTR(reset_tgu, 0200, NULL, reset_tgu);
+
+static ssize_t set_group(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ int grp, reg, step;
+ unsigned long value;
+
+ if (drvdata->grp_refcnt >= MAX_GROUP_SETS) {
+ dev_err(drvdata->dev, " Too many groups are being configured");
+ return -EINVAL;
+ }
+
+ if (sscanf(buf, "%d %d %d %lx", &grp, ®, &step, &value) != 4)
+ return -EINVAL;
+
+ spin_lock(&drvdata->spinlock);
+ if ((grp <= MAX_GROUPS) && (reg <= drvdata->max_regs)) {
+ drvdata->grp_data[drvdata->grp_refcnt].grpaddr =
+ GROUP_REG_STEP(grp, reg, step);
+ drvdata->grp_data[drvdata->grp_refcnt].value = value;
+ drvdata->grp_refcnt++;
+ } else
+ dev_err(drvdata->dev, "Invalid group data\n");
+
+ spin_unlock(&drvdata->spinlock);
+
+ return size;
+}
+static DEVICE_ATTR(set_group, 0200, NULL, set_group);
+
+static ssize_t tgu_set_condition(struct device *dev, struct device_attribute
+ *attr, const char *buf, size_t size)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ unsigned long value;
+ int cond, step;
+
+ if (drvdata->cond_refcnt >= MAX_CONDITION_SETS) {
+ dev_err(drvdata->dev, " Too many groups are being configured");
+ return -EINVAL;
+ }
+
+ if (sscanf(buf, "%d %d %lx", &cond, &step, &value) != 3)
+ return -EINVAL;
+
+ spin_lock(&drvdata->spinlock);
+ if ((cond <= drvdata->max_conditions) && (step <=
+ drvdata->max_steps)) {
+ drvdata->condition_data[drvdata->cond_refcnt].condaddr =
+ CONDITION_DECODE_STEP(cond, step);
+ drvdata->condition_data[drvdata->cond_refcnt].value = value;
+ drvdata->cond_refcnt++;
+ } else
+ dev_err(drvdata->dev, "Invalid condition decode data\n");
+
+ spin_unlock(&drvdata->spinlock);
+
+ return size;
+}
+static DEVICE_ATTR(set_condition, 0200, NULL, tgu_set_condition);
+
+static ssize_t tgu_set_select(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ unsigned long value;
+ int select, step;
+
+ if (drvdata->select_refcnt >= MAX_CONDITION_SETS) {
+ dev_err(drvdata->dev, " Too many groups are being configured");
+ return -EINVAL;
+ }
+
+ if (sscanf(buf, "%d %d %lx", &select, &step, &value) != 3)
+ return -EINVAL;
+
+ spin_lock(&drvdata->spinlock);
+
+ if ((select <= drvdata->max_conditions) && (step <=
+ drvdata->max_steps)) {
+ drvdata->select_data[drvdata->select_refcnt].selectaddr =
+ CONDITION_SELECT_STEP(select, step);
+ drvdata->select_data[drvdata->select_refcnt].value = value;
+ drvdata->select_refcnt++;
+ } else
+ dev_err(drvdata->dev, "Invalid select decode data\n");
+
+ spin_unlock(&drvdata->spinlock);
+
+ return size;
+}
+static DEVICE_ATTR(set_select, 0200, NULL, tgu_set_select);
+
+static ssize_t tgu_set_timers(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ unsigned long value;
+ int step;
+
+ if (drvdata->select_refcnt >= MAX_TIMER_COUNTER_SETS) {
+ dev_err(drvdata->dev, " Too many groups are being configured");
+ return -EINVAL;
+ }
+
+ if (sscanf(buf, "%d %lx", &step, &value) != 2)
+ return -EINVAL;
+
+ spin_lock(&drvdata->spinlock);
+ if (step <= drvdata->max_timer_counter) {
+ drvdata->timer_data[drvdata->timer_refcnt].timeraddr =
+ TIMER0_COMPARE_STEP(step);
+ drvdata->timer_data[drvdata->timer_refcnt].value = value;
+ drvdata->timer_refcnt++;
+ } else
+ dev_err(drvdata->dev, "Invalid TGU timer data\n");
+
+ spin_unlock(&drvdata->spinlock);
+
+ return size;
+}
+static DEVICE_ATTR(set_timer, 0200, NULL, tgu_set_timers);
+
+static ssize_t tgu_set_counters(struct device *dev, struct device_attribute
+ *attr, const char *buf, size_t size)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ unsigned long value;
+ int step;
+
+ if (drvdata->counter_refcnt >= MAX_TIMER_COUNTER_SETS) {
+ dev_err(drvdata->dev, " Too many groups are being configured");
+ return -EINVAL;
+ }
+
+ if (sscanf(buf, "%d %lx", &step, &value) != 2)
+ return -EINVAL;
+
+ spin_lock(&drvdata->spinlock);
+ if (step <= drvdata->max_timer_counter) {
+ drvdata->counter_data[drvdata->counter_refcnt].counteraddr =
+ COUNTER0_COMPARE_STEP(step);
+ drvdata->counter_data[drvdata->counter_refcnt].value = value;
+ drvdata->counter_refcnt++;
+ } else
+ dev_err(drvdata->dev, "Invalid TGU counter data\n");
+
+ spin_unlock(&drvdata->spinlock);
+
+ return size;
+}
+static DEVICE_ATTR(set_counter, 0200, NULL, tgu_set_counters);
+
+static struct attribute *tgu_attrs[] = {
+ &dev_attr_enable_tgu.attr,
+ &dev_attr_reset_tgu.attr,
+ &dev_attr_set_group.attr,
+ &dev_attr_set_condition.attr,
+ &dev_attr_set_select.attr,
+ &dev_attr_set_timer.attr,
+ &dev_attr_set_counter.attr,
+ NULL,
+};
+
+static struct attribute_group tgu_attr_grp = {
+ .attrs = tgu_attrs,
+};
+
+static const struct attribute_group *tgu_attr_grps[] = {
+ &tgu_attr_grp,
+ NULL,
+};
+
+static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
+{
+ int ret = 0;
+ struct device *dev = &adev->dev;
+ struct coresight_platform_data *pdata;
+ struct tgu_drvdata *drvdata;
+ struct coresight_desc *desc;
+
+ pdata = of_get_coresight_platform_data(dev, adev->dev.of_node);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ adev->dev.platform_data = pdata;
+
+ drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ drvdata->dev = &adev->dev;
+
+ dev_set_drvdata(dev, drvdata);
+
+ drvdata->base = devm_ioremap_resource(dev, &adev->res);
+ if (!drvdata->base)
+ return -ENOMEM;
+
+ spin_lock_init(&drvdata->spinlock);
+
+ ret = of_property_read_u32(adev->dev.of_node, "tgu-steps",
+ &drvdata->max_steps);
+ if (ret)
+ return -EINVAL;
+
+ ret = of_property_read_u32(adev->dev.of_node, "tgu-conditions",
+ &drvdata->max_conditions);
+ if (ret)
+ return -EINVAL;
+
+ ret = of_property_read_u32(adev->dev.of_node, "tgu-regs",
+ &drvdata->max_regs);
+ if (ret)
+ return -EINVAL;
+
+ ret = of_property_read_u32(adev->dev.of_node, "tgu-timer-counters",
+ &drvdata->max_timer_counter);
+ if (ret)
+ return -EINVAL;
+
+ /* Alloc memory for Grps, Conditions and Steps */
+ drvdata->grp_data = devm_kzalloc(dev, MAX_GROUP_SETS *
+ sizeof(*drvdata->grp_data),
+ GFP_KERNEL);
+ if (!drvdata->grp_data)
+ return -ENOMEM;
+
+ drvdata->condition_data = devm_kzalloc(dev, MAX_CONDITION_SETS *
+ sizeof(*drvdata->condition_data),
+ GFP_KERNEL);
+
+ if (!drvdata->condition_data)
+ return -ENOMEM;
+
+ drvdata->select_data = devm_kzalloc(dev, MAX_CONDITION_SETS *
+ sizeof(*drvdata->select_data),
+ GFP_KERNEL);
+ if (!drvdata->select_data)
+ return -ENOMEM;
+
+ drvdata->timer_data = devm_kzalloc(dev, MAX_TIMER_COUNTER_SETS *
+ sizeof(*drvdata->timer_data),
+ GFP_KERNEL);
+ if (!drvdata->timer_data)
+ return -ENOMEM;
+
+ drvdata->counter_data = devm_kzalloc(dev, MAX_TIMER_COUNTER_SETS *
+ sizeof(*drvdata->counter_data),
+ GFP_KERNEL);
+ if (!drvdata->counter_data)
+ return -ENOMEM;
+
+ drvdata->enable = false;
+
+ desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+ if (!desc) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ desc->type = CORESIGHT_DEV_TYPE_NONE;
+ desc->pdata = adev->dev.platform_data;
+ desc->dev = &adev->dev;
+ desc->groups = tgu_attr_grps;
+ drvdata->csdev = coresight_register(desc);
+ if (IS_ERR(drvdata->csdev)) {
+ ret = PTR_ERR(drvdata->csdev);
+ goto err;
+ }
+
+ pm_runtime_put(&adev->dev);
+ dev_dbg(dev, "TGU initialized\n");
+ return 0;
+err:
+ pm_runtime_put(&adev->dev);
+ return ret;
+}
+
+static struct amba_id tgu_ids[] = {
+ {
+ .id = 0x0003b999,
+ .mask = 0x0003ffff,
+ .data = "TGU",
+ },
+ { 0, 0},
+};
+
+static struct amba_driver tgu_driver = {
+ .drv = {
+ .name = "coresight-tgu",
+ .owner = THIS_MODULE,
+ .suppress_bind_attrs = true,
+ },
+ .probe = tgu_probe,
+ .id_table = tgu_ids,
+};
+
+builtin_amba_driver(tgu_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("CoreSight TGU driver");