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, &reg, &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");