* [PATCH v17 01/10] dt-bindings: arm: Add support for Qualcomm TGU trace
2026-11-13 2:49 [PATCH v17 00/10] Provide support for Trigger Generation Unit Songwei Chai
@ 2026-11-13 2:49 ` Songwei Chai
2026-11-13 2:49 ` [PATCH v17 02/10] coresight: Move CS_LOCK/CS_UNLOCK to linux/coresight.h Songwei Chai
` (8 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Songwei Chai @ 2026-11-13 2:49 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, songchai, Rob Herring
The Trigger Generation Unit (TGU) is designed to detect patterns or
sequences within a specific region of the System on Chip (SoC). Once
configured and activated, it monitors sense inputs and can detect a
pre-programmed state or sequence across clock cycles, subsequently
producing a trigger.
TGU configuration space
offset table
x-------------------------x
| |
| |
| | Step configuration
| | space layout
| coresight management | x-------------x
| registers | |---> | |
| | | | reserve |
| | | | |
|-------------------------| | |-------------|
| | | | priority[3] |
| step[7] |<-- | |-------------|
|-------------------------| | | | priority[2] |
| | | | |-------------|
| ... | |Steps region | | priority[1] |
| | | | |-------------|
|-------------------------| | | | priority[0] |
| |<-- | |-------------|
| step[0] |--------------------> | |
|-------------------------| | condition |
| | | |
| control and status | x-------------x
| space | | |
x-------------------------x |Timer/Counter|
| |
x-------------x
TGU Configuration in Hardware
The TGU provides a step region for user configuration, similar
to a flow chart. Each step region consists of three register clusters:
1.Priority Region: Sets the required signals with priority.
2.Condition Region: Defines specific requirements (e.g., signal A
reaches three times) and the subsequent action once the requirement is
met.
3.Timer/Counter (Optional): Provides timing or counting functionality.
Add a new tgu.yaml file to describe the bindings required to
define the TGU in the device trees.
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../devicetree/bindings/arm/qcom,tgu.yaml | 71 +++++++++++++++++++
1 file changed, 71 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/qcom,tgu.yaml
diff --git a/Documentation/devicetree/bindings/arm/qcom,tgu.yaml b/Documentation/devicetree/bindings/arm/qcom,tgu.yaml
new file mode 100644
index 000000000000..76440f2497b9
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/qcom,tgu.yaml
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+# Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/qcom,tgu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Trigger Generation Unit - TGU
+
+description: |
+ The Trigger Generation Unit (TGU) is a Data Engine which can be utilized
+ to sense a plurality of signals and create a trigger into the CTI or
+ generate interrupts to processors. The TGU is like the trigger circuit
+ of a Logic Analyzer. The corresponding trigger logic can be realized by
+ configuring the conditions for each step after sensing the signal.
+ Once setup and enabled, it will observe sense inputs and based upon
+ the activity of those inputs, even over clock cycles, may detect a
+ preprogrammed state/sequence and then produce a trigger or interrupt.
+
+ The primary use case of the TGU is to detect patterns or sequences on a
+ given set of signals within some region to identify the issue in time
+ once there is abnormal behavior in the subsystem.
+
+maintainers:
+ - Mao Jinlong <jinlong.mao@oss.qualcomm.com>
+ - Songwei Chai <songwei.chai@oss.qualcomm.com>
+
+# Need a custom select here or 'arm,primecell' will match on lots of nodes
+select:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - qcom,tgu
+ required:
+ - compatible
+
+properties:
+ compatible:
+ items:
+ - const: qcom,tgu
+ - const: arm,primecell
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ items:
+ - const: apb_pclk
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ tgu@10b0e000 {
+ compatible = "qcom,tgu", "arm,primecell";
+ reg = <0x10b0e000 0x1000>;
+
+ clocks = <&aoss_qmp>;
+ clock-names = "apb_pclk";
+ };
+...
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v17 02/10] coresight: Move CS_LOCK/CS_UNLOCK to linux/coresight.h
2026-11-13 2:49 [PATCH v17 00/10] Provide support for Trigger Generation Unit Songwei Chai
2026-11-13 2:49 ` [PATCH v17 01/10] dt-bindings: arm: Add support for Qualcomm TGU trace Songwei Chai
@ 2026-11-13 2:49 ` Songwei Chai
2026-11-13 10:25 ` Suzuki K Poulose
2026-11-13 2:49 ` [PATCH v17 03/10] qcom-tgu: Add TGU driver Songwei Chai
` (7 subsequent siblings)
9 siblings, 1 reply; 14+ messages in thread
From: Songwei Chai @ 2026-11-13 2:49 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, songchai
CORESIGHT_LAR and the CS_LOCK()/CS_UNLOCK() helpers implement the
generic ARM CoreSight Software Lock Access Register sequence, which
is also present on non-CoreSight-labelled components that follow the
same architecture (e.g. the Qualcomm TGU). Keeping them private to
coresight-priv.h forces such drivers to duplicate the exact same
definitions instead of reusing them.
Move CORESIGHT_LAR, CS_LOCK() and CS_UNLOCK() into the public
include/linux/coresight.h so they can be shared outside of
drivers/hwtracing/coresight/ as well.
Suggested-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
drivers/hwtracing/coresight/coresight-priv.h | 19 -------------------
include/linux/coresight.h | 19 +++++++++++++++++++
2 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/drivers/hwtracing/coresight/coresight-priv.h b/drivers/hwtracing/coresight/coresight-priv.h
index dddac946659f..0d3f6229b282 100644
--- a/drivers/hwtracing/coresight/coresight-priv.h
+++ b/drivers/hwtracing/coresight/coresight-priv.h
@@ -23,7 +23,6 @@ extern const struct device_type coresight_dev_type[];
#define CORESIGHT_ITCTRL 0xf00
#define CORESIGHT_CLAIMSET 0xfa0
#define CORESIGHT_CLAIMCLR 0xfa4
-#define CORESIGHT_LAR 0xfb0
#define CORESIGHT_LSR 0xfb4
#define CORESIGHT_DEVARCH 0xfbc
#define CORESIGHT_AUTHSTATUS 0xfb8
@@ -116,24 +115,6 @@ static inline void coresight_insert_barrier_packet(void *buf)
memcpy(buf, coresight_barrier_pkt, CORESIGHT_BARRIER_PKT_SIZE);
}
-static inline void CS_LOCK(void __iomem *addr)
-{
- do {
- /* Wait for things to settle */
- mb();
- writel_relaxed(0x0, addr + CORESIGHT_LAR);
- } while (0);
-}
-
-static inline void CS_UNLOCK(void __iomem *addr)
-{
- do {
- writel_relaxed(CORESIGHT_UNLOCK, addr + CORESIGHT_LAR);
- /* Make sure everyone has seen this */
- mb();
- } while (0);
-}
-
void coresight_disable_path(struct coresight_path *path);
int coresight_enable_path(struct coresight_path *path, enum cs_mode mode);
struct coresight_device *coresight_get_sink(struct coresight_path *path);
diff --git a/include/linux/coresight.h b/include/linux/coresight.h
index edf2a2fc697c..0168f6abffaf 100644
--- a/include/linux/coresight.h
+++ b/include/linux/coresight.h
@@ -35,6 +35,25 @@
#define PFT_ARCH_V1_1 0x31
#define CORESIGHT_UNLOCK 0xc5acce55
+#define CORESIGHT_LAR 0xfb0
+
+static inline void CS_LOCK(void __iomem *addr)
+{
+ do {
+ /* Wait for things to settle */
+ mb();
+ writel_relaxed(0x0, addr + CORESIGHT_LAR);
+ } while (0);
+}
+
+static inline void CS_UNLOCK(void __iomem *addr)
+{
+ do {
+ writel_relaxed(CORESIGHT_UNLOCK, addr + CORESIGHT_LAR);
+ /* Make sure everyone has seen this */
+ mb();
+ } while (0);
+}
extern const struct bus_type coresight_bustype;
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v17 02/10] coresight: Move CS_LOCK/CS_UNLOCK to linux/coresight.h
2026-11-13 2:49 ` [PATCH v17 02/10] coresight: Move CS_LOCK/CS_UNLOCK to linux/coresight.h Songwei Chai
@ 2026-11-13 10:25 ` Suzuki K Poulose
0 siblings, 0 replies; 14+ messages in thread
From: Suzuki K Poulose @ 2026-11-13 10:25 UTC (permalink / raw)
To: Songwei Chai, andersson, alexander.shishkin, mike.leach,
konrad.dybcio, james.clark, krzk+dt, conor+dt
Cc: linux-kernel, linux-arm-kernel, linux-arm-msm, coresight,
devicetree, gregkh, songchai
On 13/11/2026 02:49, Songwei Chai wrote:
> CORESIGHT_LAR and the CS_LOCK()/CS_UNLOCK() helpers implement the
> generic ARM CoreSight Software Lock Access Register sequence, which
> is also present on non-CoreSight-labelled components that follow the
> same architecture (e.g. the Qualcomm TGU). Keeping them private to
> coresight-priv.h forces such drivers to duplicate the exact same
> definitions instead of reusing them.
>
> Move CORESIGHT_LAR, CS_LOCK() and CS_UNLOCK() into the public
> include/linux/coresight.h so they can be shared outside of
> drivers/hwtracing/coresight/ as well.
>
> Suggested-by: Suzuki K Poulose <suzuki.poulose@arm.com>
> Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
> ---
> drivers/hwtracing/coresight/coresight-priv.h | 19 -------------------
> include/linux/coresight.h | 19 +++++++++++++++++++
> 2 files changed, 19 insertions(+), 19 deletions(-)
>
> diff --git a/drivers/hwtracing/coresight/coresight-priv.h b/drivers/hwtracing/coresight/coresight-priv.h
> index dddac946659f..0d3f6229b282 100644
> --- a/drivers/hwtracing/coresight/coresight-priv.h
> +++ b/drivers/hwtracing/coresight/coresight-priv.h
> @@ -23,7 +23,6 @@ extern const struct device_type coresight_dev_type[];
> #define CORESIGHT_ITCTRL 0xf00
> #define CORESIGHT_CLAIMSET 0xfa0
> #define CORESIGHT_CLAIMCLR 0xfa4
> -#define CORESIGHT_LAR 0xfb0
> #define CORESIGHT_LSR 0xfb4
> #define CORESIGHT_DEVARCH 0xfbc
> #define CORESIGHT_AUTHSTATUS 0xfb8
Move all of these register defs above to linux/coresight.h. We can keep
them in a central place, in linux/coresight.h and drop patch 3
Suzuki
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v17 03/10] qcom-tgu: Add TGU driver
2026-11-13 2:49 [PATCH v17 00/10] Provide support for Trigger Generation Unit Songwei Chai
2026-11-13 2:49 ` [PATCH v17 01/10] dt-bindings: arm: Add support for Qualcomm TGU trace Songwei Chai
2026-11-13 2:49 ` [PATCH v17 02/10] coresight: Move CS_LOCK/CS_UNLOCK to linux/coresight.h Songwei Chai
@ 2026-11-13 2:49 ` Songwei Chai
2026-11-13 10:34 ` Suzuki K Poulose
2026-11-13 2:49 ` [PATCH v17 04/10] coresight: Move CORESIGHT_DEVID to linux/coresight.h Songwei Chai
` (6 subsequent siblings)
9 siblings, 1 reply; 14+ messages in thread
From: Songwei Chai @ 2026-11-13 2:49 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, songchai, Jie Gan
Add driver to support device TGU (Trigger Generation Unit).
TGU is a Data Engine which can be utilized to sense a plurality of
signals and create a trigger into the CTI or generate interrupts to
processors. Add probe/enable/disable functions for tgu.
Acked-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 9 +
MAINTAINERS | 11 +
drivers/Makefile | 1 +
drivers/hwtracing/Kconfig | 2 +
drivers/hwtracing/qcom/Kconfig | 20 ++
drivers/hwtracing/qcom/Makefile | 3 +
drivers/hwtracing/qcom/tgu.c | 193 ++++++++++++++++++
drivers/hwtracing/qcom/tgu.h | 31 +++
8 files changed, 270 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
create mode 100644 drivers/hwtracing/qcom/Kconfig
create mode 100644 drivers/hwtracing/qcom/Makefile
create mode 100644 drivers/hwtracing/qcom/tgu.c
create mode 100644 drivers/hwtracing/qcom/tgu.h
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
new file mode 100644
index 000000000000..66b12ffc2892
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -0,0 +1,9 @@
+What: /sys/bus/amba/devices/<tgu-name>/enable_tgu
+Date: November 2026
+KernelVersion: 7.5
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the enable/disable status of TGU
+ Accepts only one of the 2 values - 0 or 1.
+ 0 : disable TGU.
+ 1 : enable TGU.
diff --git a/MAINTAINERS b/MAINTAINERS
index fe23913b1e43..faf8706b6584 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23264,6 +23264,17 @@ S: Maintained
F: Documentation/tee/qtee.rst
F: drivers/tee/qcomtee/
+QUALCOMM HARDWARETRACING DRIVER
+M: Songwei Chai <songwei.chai@oss.qualcomm.com>
+M: Jie Gan <jie.gan@oss.qualcomm.com>
+M: Suzuki K Poulose <suzuki.poulose@arm.com>
+L: linux-arm-msm@vger.kernel.org
+S: Maintained
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/coresight/linux.git
+F: Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+F: Documentation/devicetree/bindings/arm/qcom,tgu.yaml
+F: drivers/hwtracing/qcom/
+
QUALCOMM TRUST ZONE MEMORY ALLOCATOR
M: Bartosz Golaszewski <brgl@kernel.org>
L: linux-arm-msm@vger.kernel.org
diff --git a/drivers/Makefile b/drivers/Makefile
index 0841ea851847..f37edcf34e0d 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -176,6 +176,7 @@ obj-$(CONFIG_RAS) += ras/
obj-$(CONFIG_USB4) += thunderbolt/
obj-$(CONFIG_CORESIGHT) += hwtracing/coresight/
obj-y += hwtracing/intel_th/
+obj-$(CONFIG_QCOM_TGU) += hwtracing/qcom/
obj-$(CONFIG_STM) += hwtracing/stm/
obj-$(CONFIG_HISI_PTT) += hwtracing/ptt/
obj-y += android/
diff --git a/drivers/hwtracing/Kconfig b/drivers/hwtracing/Kconfig
index 911ee977103c..8a640218eed8 100644
--- a/drivers/hwtracing/Kconfig
+++ b/drivers/hwtracing/Kconfig
@@ -7,4 +7,6 @@ source "drivers/hwtracing/intel_th/Kconfig"
source "drivers/hwtracing/ptt/Kconfig"
+source "drivers/hwtracing/qcom/Kconfig"
+
endmenu
diff --git a/drivers/hwtracing/qcom/Kconfig b/drivers/hwtracing/qcom/Kconfig
new file mode 100644
index 000000000000..5c94c75ffa39
--- /dev/null
+++ b/drivers/hwtracing/qcom/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# QCOM specific hwtracing drivers
+#
+menu "Qualcomm specific hwtracing drivers"
+
+config QCOM_TGU
+ tristate "QCOM Trigger Generation Unit driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on ARM_AMBA
+ 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.
+
+endmenu
diff --git a/drivers/hwtracing/qcom/Makefile b/drivers/hwtracing/qcom/Makefile
new file mode 100644
index 000000000000..5a0a868c1ea0
--- /dev/null
+++ b/drivers/hwtracing/qcom/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_QCOM_TGU) += tgu.o
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
new file mode 100644
index 000000000000..ac1ae8bb0ec4
--- /dev/null
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/amba/bus.h>
+#include <linux/coresight.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/pm_runtime.h>
+
+#include "tgu.h"
+
+static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
+{
+ CS_UNLOCK(drvdata->base);
+ /* Enable TGU to program the triggers */
+ writel(1, drvdata->base + TGU_CONTROL);
+ CS_LOCK(drvdata->base);
+}
+
+static void tgu_enable(struct tgu_drvdata *drvdata)
+{
+ tgu_write_all_hw_regs(drvdata);
+ drvdata->enabled = true;
+}
+
+static void tgu_disable(struct tgu_drvdata *drvdata)
+{
+ CS_UNLOCK(drvdata->base);
+ writel(0, drvdata->base + TGU_CONTROL);
+ CS_LOCK(drvdata->base);
+
+ drvdata->enabled = false;
+}
+
+static ssize_t enable_tgu_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ bool enabled;
+
+ scoped_guard(mutex, &drvdata->lock)
+ enabled = drvdata->enabled;
+
+ return sysfs_emit(buf, "%d\n", enabled);
+}
+
+/* enable_tgu_store - Enable or disable the Trigger Generation Unit (TGU). */
+static ssize_t enable_tgu_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t size)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret || val > 1)
+ return -EINVAL;
+
+ /*
+ * Hold the mutex across the whole check-and-act sequence: the check of
+ * drvdata->enabled, the PM runtime reference counting and the state
+ * update must be atomic, otherwise concurrent writers could each
+ * imbalance the PM runtime usage counter.
+ */
+ guard(mutex)(&drvdata->lock);
+
+ if (val) {
+ if (drvdata->enabled)
+ return -EBUSY;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ tgu_enable(drvdata);
+ } else {
+ if (!drvdata->enabled)
+ return -EINVAL;
+
+ tgu_disable(drvdata);
+ pm_runtime_put(dev);
+ }
+
+ return size;
+}
+static DEVICE_ATTR_RW(enable_tgu);
+
+static struct attribute *tgu_common_attrs[] = {
+ &dev_attr_enable_tgu.attr,
+ NULL,
+};
+
+static const struct attribute_group tgu_common_grp = {
+ .attrs = tgu_common_attrs,
+};
+
+static const struct attribute_group *tgu_attr_groups[] = {
+ &tgu_common_grp,
+ NULL,
+};
+
+static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct tgu_drvdata *drvdata;
+ int ret;
+
+ 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 (IS_ERR(drvdata->base))
+ return PTR_ERR(drvdata->base);
+
+ ret = devm_mutex_init(dev, &drvdata->lock);
+ if (ret)
+ return ret;
+
+ drvdata->enabled = false;
+
+ ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
+ if (ret) {
+ dev_err(dev, "failed to create sysfs groups: %d\n", ret);
+ return ret;
+ }
+
+ pm_runtime_put(&adev->dev);
+
+ return 0;
+}
+
+static void tgu_remove(struct amba_device *adev)
+{
+ struct device *dev = &adev->dev;
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+
+ sysfs_remove_groups(&dev->kobj, tgu_attr_groups);
+
+ /*
+ * If the device is still enabled through sysfs, disable the hardware
+ * and drop the PM runtime reference taken in enable_tgu_store() so the
+ * usage counter stays balanced. Use the synchronous put: the driver core
+ * disables runtime PM right after ->remove() returns, and the barrier in
+ * pm_runtime_disable() would cancel a still-queued async idle/suspend,
+ * leaving the runtime-PM state inconsistent.
+ */
+ scoped_guard(mutex, &drvdata->lock) {
+ if (drvdata->enabled) {
+ tgu_disable(drvdata);
+ pm_runtime_put_sync(dev);
+ }
+ }
+}
+
+static const struct amba_id tgu_ids[] = {
+ {
+ .id = 0x000f0e00,
+ .mask = 0x000fffff,
+ },
+ { 0, 0, NULL },
+};
+
+MODULE_DEVICE_TABLE(amba, tgu_ids);
+
+static struct amba_driver tgu_driver = {
+ .drv = {
+ .name = "qcom-tgu",
+ .suppress_bind_attrs = true,
+ },
+ .probe = tgu_probe,
+ .remove = tgu_remove,
+ .id_table = tgu_ids,
+};
+
+module_amba_driver(tgu_driver);
+
+MODULE_AUTHOR("Songwei Chai <songwei.chai@oss.qualcomm.com>");
+MODULE_AUTHOR("Jinlong Mao <jinlong.mao@oss.qualcomm.com>");
+MODULE_DESCRIPTION("Qualcomm Trigger Generation Unit driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
new file mode 100644
index 000000000000..c6426b4e3617
--- /dev/null
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef _QCOM_TGU_H
+#define _QCOM_TGU_H
+
+/* Register addresses */
+#define TGU_CONTROL 0x0000
+
+/**
+ * struct tgu_drvdata - Data structure for a TGU (Trigger Generation Unit)
+ * @base: Memory-mapped base address of the TGU device
+ * @dev: Pointer to the associated device structure
+ * @lock: Mutex serialising enable/disable and the associated PM runtime
+ * reference counting against concurrent sysfs access
+ * @enabled: Flag indicating whether the TGU device is enabled
+ *
+ * This structure defines the data associated with a TGU device,
+ * including its base address, device pointer, the lock serialising
+ * enable/disable, and the enable status.
+ */
+struct tgu_drvdata {
+ void __iomem *base;
+ struct device *dev;
+ struct mutex lock;
+ bool enabled;
+};
+
+#endif
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v17 03/10] qcom-tgu: Add TGU driver
2026-11-13 2:49 ` [PATCH v17 03/10] qcom-tgu: Add TGU driver Songwei Chai
@ 2026-11-13 10:34 ` Suzuki K Poulose
0 siblings, 0 replies; 14+ messages in thread
From: Suzuki K Poulose @ 2026-11-13 10:34 UTC (permalink / raw)
To: Songwei Chai, andersson, alexander.shishkin, mike.leach,
konrad.dybcio, james.clark, krzk+dt, conor+dt
Cc: linux-kernel, linux-arm-kernel, linux-arm-msm, coresight,
devicetree, gregkh, songchai, Jie Gan
On 13/11/2026 02:49, Songwei Chai wrote:
> Add driver to support device TGU (Trigger Generation Unit).
> TGU is a Data Engine which can be utilized to sense a plurality of
> signals and create a trigger into the CTI or generate interrupts to
> processors. Add probe/enable/disable functions for tgu.
>
> Acked-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
> Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
> ---
> .../ABI/testing/sysfs-bus-amba-devices-tgu | 9 +
> MAINTAINERS | 11 +
> drivers/Makefile | 1 +
> drivers/hwtracing/Kconfig | 2 +
> drivers/hwtracing/qcom/Kconfig | 20 ++
> drivers/hwtracing/qcom/Makefile | 3 +
> drivers/hwtracing/qcom/tgu.c | 193 ++++++++++++++++++
> drivers/hwtracing/qcom/tgu.h | 31 +++
> 8 files changed, 270 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
> create mode 100644 drivers/hwtracing/qcom/Kconfig
> create mode 100644 drivers/hwtracing/qcom/Makefile
> create mode 100644 drivers/hwtracing/qcom/tgu.c
> create mode 100644 drivers/hwtracing/qcom/tgu.h
>
> diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
> new file mode 100644
> index 000000000000..66b12ffc2892
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
> @@ -0,0 +1,9 @@
> +What: /sys/bus/amba/devices/<tgu-name>/enable_tgu
> +Date: November 2026
> +KernelVersion: 7.5
> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
> +Description:
> + (RW) Set/Get the enable/disable status of TGU
> + Accepts only one of the 2 values - 0 or 1.
> + 0 : disable TGU.
> + 1 : enable TGU.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index fe23913b1e43..faf8706b6584 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23264,6 +23264,17 @@ S: Maintained
> F: Documentation/tee/qtee.rst
> F: drivers/tee/qcomtee/
>
> +QUALCOMM HARDWARETRACING DRIVER
nit: HARDWARE TRACING DRIVER
> +M: Songwei Chai <songwei.chai@oss.qualcomm.com>
> +M: Jie Gan <jie.gan@oss.qualcomm.com>
> +M: Suzuki K Poulose <suzuki.poulose@arm.com>
> +L: linux-arm-msm@vger.kernel.org
> +S: Maintained
> +T: git git://git.kernel.org/pub/scm/linux/kernel/git/coresight/linux.git
> +F: Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
> +F: Documentation/devicetree/bindings/arm/qcom,tgu.yaml
> +F: drivers/hwtracing/qcom/
> +
> QUALCOMM TRUST ZONE MEMORY ALLOCATOR
> M: Bartosz Golaszewski <brgl@kernel.org>
> L: linux-arm-msm@vger.kernel.org
> diff --git a/drivers/Makefile b/drivers/Makefile
> index 0841ea851847..f37edcf34e0d 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -176,6 +176,7 @@ obj-$(CONFIG_RAS) += ras/
> obj-$(CONFIG_USB4) += thunderbolt/
> obj-$(CONFIG_CORESIGHT) += hwtracing/coresight/
> obj-y += hwtracing/intel_th/
> +obj-$(CONFIG_QCOM_TGU) += hwtracing/qcom/
> obj-$(CONFIG_STM) += hwtracing/stm/
> obj-$(CONFIG_HISI_PTT) += hwtracing/ptt/
> obj-y += android/
> diff --git a/drivers/hwtracing/Kconfig b/drivers/hwtracing/Kconfig
> index 911ee977103c..8a640218eed8 100644
> --- a/drivers/hwtracing/Kconfig
> +++ b/drivers/hwtracing/Kconfig
> @@ -7,4 +7,6 @@ source "drivers/hwtracing/intel_th/Kconfig"
>
> source "drivers/hwtracing/ptt/Kconfig"
>
> +source "drivers/hwtracing/qcom/Kconfig"
> +
> endmenu
> diff --git a/drivers/hwtracing/qcom/Kconfig b/drivers/hwtracing/qcom/Kconfig
> new file mode 100644
> index 000000000000..5c94c75ffa39
> --- /dev/null
> +++ b/drivers/hwtracing/qcom/Kconfig
> @@ -0,0 +1,20 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# QCOM specific hwtracing drivers
> +#
> +menu "Qualcomm specific hwtracing drivers"
> +
> +config QCOM_TGU
> + tristate "QCOM Trigger Generation Unit driver"
> + depends on ARCH_QCOM || COMPILE_TEST
> + depends on ARM_AMBA
> + 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.
> +
> +endmenu
> diff --git a/drivers/hwtracing/qcom/Makefile b/drivers/hwtracing/qcom/Makefile
> new file mode 100644
> index 000000000000..5a0a868c1ea0
> --- /dev/null
> +++ b/drivers/hwtracing/qcom/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +obj-$(CONFIG_QCOM_TGU) += tgu.o
> diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
> new file mode 100644
> index 000000000000..ac1ae8bb0ec4
> --- /dev/null
> +++ b/drivers/hwtracing/qcom/tgu.c
> @@ -0,0 +1,193 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#include <linux/amba/bus.h>
> +#include <linux/coresight.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/pm_runtime.h>
> +
> +#include "tgu.h"
...
> +static ssize_t enable_tgu_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
...
> +}
> +
> +/* enable_tgu_store - Enable or disable the Trigger Generation Unit (TGU). */
> +static ssize_t enable_tgu_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t size)
> +{
...
> +}
> +static DEVICE_ATTR_RW(enable_tgu);
minor nit: Do we need "enable_tgu" in the name under /sys/.../tgu0/ ?
Couldn' this be "enable" ? Also, the function names could be named
as "tgu_enable_store/show"
Suzuki
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v17 04/10] coresight: Move CORESIGHT_DEVID to linux/coresight.h
2026-11-13 2:49 [PATCH v17 00/10] Provide support for Trigger Generation Unit Songwei Chai
` (2 preceding siblings ...)
2026-11-13 2:49 ` [PATCH v17 03/10] qcom-tgu: Add TGU driver Songwei Chai
@ 2026-11-13 2:49 ` Songwei Chai
2026-11-13 2:49 ` [PATCH v17 05/10] qcom-tgu: Add signal priority support Songwei Chai
` (5 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Songwei Chai @ 2026-11-13 2:49 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, songchai
CORESIGHT_DEVID lives at a fixed, architecturally-defined offset
(0xfc8) within the generic CoreSight management register block, which
is also present on non-CoreSight-labelled components that follow the
same convention (e.g. the Qualcomm TGU). Keeping it private to
coresight-priv.h forces such drivers to redefine the same constant
under a different name instead of reusing it, as already done for
CORESIGHT_LAR/CS_LOCK/CS_UNLOCK.
Move CORESIGHT_DEVID into the public include/linux/coresight.h so it
can be shared outside of drivers/hwtracing/coresight/ as well.
Suggested-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
drivers/hwtracing/coresight/coresight-priv.h | 1 -
include/linux/coresight.h | 1 +
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/hwtracing/coresight/coresight-priv.h b/drivers/hwtracing/coresight/coresight-priv.h
index 0d3f6229b282..a28b89d43291 100644
--- a/drivers/hwtracing/coresight/coresight-priv.h
+++ b/drivers/hwtracing/coresight/coresight-priv.h
@@ -26,7 +26,6 @@ extern const struct device_type coresight_dev_type[];
#define CORESIGHT_LSR 0xfb4
#define CORESIGHT_DEVARCH 0xfbc
#define CORESIGHT_AUTHSTATUS 0xfb8
-#define CORESIGHT_DEVID 0xfc8
#define CORESIGHT_DEVTYPE 0xfcc
diff --git a/include/linux/coresight.h b/include/linux/coresight.h
index 0168f6abffaf..5286b3606581 100644
--- a/include/linux/coresight.h
+++ b/include/linux/coresight.h
@@ -36,6 +36,7 @@
#define CORESIGHT_UNLOCK 0xc5acce55
#define CORESIGHT_LAR 0xfb0
+#define CORESIGHT_DEVID 0xfc8
static inline void CS_LOCK(void __iomem *addr)
{
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v17 05/10] qcom-tgu: Add signal priority support
2026-11-13 2:49 [PATCH v17 00/10] Provide support for Trigger Generation Unit Songwei Chai
` (3 preceding siblings ...)
2026-11-13 2:49 ` [PATCH v17 04/10] coresight: Move CORESIGHT_DEVID to linux/coresight.h Songwei Chai
@ 2026-11-13 2:49 ` Songwei Chai
2026-11-13 2:49 ` [PATCH v17 06/10] qcom-tgu: Add TGU decode support Songwei Chai
` (4 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Songwei Chai @ 2026-11-13 2:49 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, songchai, Jie Gan
Like circuit of a Logic analyzer, in TGU, the requirement could be
configured in each step and the trigger will be created once the
requirements are met. Add priority functionality here to sort the
signals into different priorities. The signal which is wanted could
be configured in each step's priority node, the larger number means
the higher priority and the signal with higher priority will be sensed
more preferentially.
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 7 +
drivers/hwtracing/qcom/tgu.c | 211 ++++++++++++++++++
drivers/hwtracing/qcom/tgu.h | 68 ++++++
3 files changed, 286 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index 66b12ffc2892..09370481758c 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -7,3 +7,10 @@ Description:
Accepts only one of the 2 values - 0 or 1.
0 : disable TGU.
1 : enable TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_priority[0:3]/reg[0:23]
+Date: November 2026
+KernelVersion: 7.5
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the sensed signal with specific step and priority for TGU.
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
index ac1ae8bb0ec4..c00e95e68214 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -16,14 +16,145 @@
#include "tgu.h"
+struct tgu_attribute {
+ struct device_attribute attr;
+ u8 step_index;
+ u8 priority;
+ u8 reg_num;
+};
+
+/*
+ * drvdata->priority is a flat [priority][step][reg] array.
+ */
+static int calculate_array_location(struct tgu_drvdata *drvdata,
+ int step_index, int priority,
+ int reg_index)
+{
+ return priority * (drvdata->num_step) * (drvdata->num_reg) +
+ step_index * (drvdata->num_reg) + reg_index;
+}
+
+static ssize_t tgu_dataset_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ struct tgu_attribute *tgu_attr =
+ container_of(attr, struct tgu_attribute, attr);
+ int index;
+
+ index = calculate_array_location(drvdata, tgu_attr->step_index,
+ tgu_attr->priority,
+ tgu_attr->reg_num);
+
+ /*
+ * Serialise against tgu_dataset_store() and tgu_enable(), which read
+ * and write the same priority array, so the value is not observed
+ * while being updated concurrently.
+ */
+ guard(mutex)(&drvdata->lock);
+
+ return sysfs_emit(buf, "0x%x\n", drvdata->priority[index]);
+}
+
+static ssize_t tgu_dataset_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct tgu_drvdata *tgu_drvdata = dev_get_drvdata(dev);
+ struct tgu_attribute *tgu_attr =
+ container_of(attr, struct tgu_attribute, attr);
+ unsigned long val;
+ int index;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&tgu_drvdata->lock);
+ index = calculate_array_location(tgu_drvdata, tgu_attr->step_index,
+ tgu_attr->priority,
+ tgu_attr->reg_num);
+
+ tgu_drvdata->priority[index] = val;
+
+ return size;
+}
+
+static umode_t tgu_node_visible(struct kobject *kobject,
+ struct attribute *attr,
+ int n)
+{
+ struct device *dev = kobj_to_dev(kobject);
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ struct device_attribute *dev_attr =
+ container_of(attr, struct device_attribute, attr);
+ struct tgu_attribute *tgu_attr =
+ container_of(dev_attr, struct tgu_attribute, attr);
+
+ if (tgu_attr->step_index >= drvdata->num_step)
+ return SYSFS_GROUP_INVISIBLE;
+
+ if (tgu_attr->reg_num >= drvdata->num_reg)
+ return 0;
+
+ return attr->mode;
+}
+
static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
{
+ int i, j, k, index;
+
CS_UNLOCK(drvdata->base);
+ for (i = 0; i < drvdata->num_step; i++) {
+ for (j = 0; j < MAX_PRIORITY; j++) {
+ for (k = 0; k < drvdata->num_reg; k++) {
+ index = calculate_array_location(
+ drvdata, i, j, k);
+
+ writel(drvdata->priority[index],
+ drvdata->base +
+ PRIORITY_REG_STEP(i, j, k));
+ }
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
CS_LOCK(drvdata->base);
}
+static void tgu_set_dev_caps(struct tgu_drvdata *drvdata)
+{
+ int num_sense_input, num_reg, num_step;
+ u32 devid;
+
+ devid = readl(drvdata->base + CORESIGHT_DEVID);
+
+ num_sense_input = TGU_DEVID_SENSE_INPUT(devid);
+ num_reg = (num_sense_input * TGU_BITS_PER_SIGNAL) / LENGTH_REGISTER;
+
+ if ((num_sense_input * TGU_BITS_PER_SIGNAL) % LENGTH_REGISTER)
+ num_reg++;
+
+ if (num_reg > TGU_MAX_PRIORITY_REGS) {
+ dev_warn(drvdata->dev,
+ "DEVID reports %d priority regs, clamping to %d\n",
+ num_reg, TGU_MAX_PRIORITY_REGS);
+ num_reg = TGU_MAX_PRIORITY_REGS;
+ }
+ drvdata->num_reg = num_reg;
+
+ num_step = TGU_DEVID_STEPS(devid);
+
+ if (num_step > TGU_MAX_STEPS) {
+ dev_warn(drvdata->dev,
+ "DEVID reports %d steps, clamping to %d\n",
+ num_step, TGU_MAX_STEPS);
+ num_step = TGU_MAX_STEPS;
+ }
+ drvdata->num_step = num_step;
+}
+
static void tgu_enable(struct tgu_drvdata *drvdata)
{
tgu_write_all_hw_regs(drvdata);
@@ -103,8 +234,86 @@ static const struct attribute_group tgu_common_grp = {
.attrs = tgu_common_attrs,
};
+#define tgu_dataset_rw(name, step_index, priority, reg_num) \
+ (&((struct tgu_attribute[]){ { \
+ __ATTR(name, 0644, tgu_dataset_show, tgu_dataset_store), \
+ step_index, \
+ priority, \
+ reg_num, \
+ } })[0].attr.attr)
+
+#define STEP_PRIORITY(step_index, reg_num, priority) \
+ tgu_dataset_rw(reg##reg_num, step_index, priority, reg_num)
+
+#define STEP_PRIORITY_LIST(step_index, priority) \
+ {STEP_PRIORITY(step_index, 0, priority), \
+ STEP_PRIORITY(step_index, 1, priority), \
+ STEP_PRIORITY(step_index, 2, priority), \
+ STEP_PRIORITY(step_index, 3, priority), \
+ STEP_PRIORITY(step_index, 4, priority), \
+ STEP_PRIORITY(step_index, 5, priority), \
+ STEP_PRIORITY(step_index, 6, priority), \
+ STEP_PRIORITY(step_index, 7, priority), \
+ STEP_PRIORITY(step_index, 8, priority), \
+ STEP_PRIORITY(step_index, 9, priority), \
+ STEP_PRIORITY(step_index, 10, priority), \
+ STEP_PRIORITY(step_index, 11, priority), \
+ STEP_PRIORITY(step_index, 12, priority), \
+ STEP_PRIORITY(step_index, 13, priority), \
+ STEP_PRIORITY(step_index, 14, priority), \
+ STEP_PRIORITY(step_index, 15, priority), \
+ STEP_PRIORITY(step_index, 16, priority), \
+ STEP_PRIORITY(step_index, 17, priority), \
+ STEP_PRIORITY(step_index, 18, priority), \
+ STEP_PRIORITY(step_index, 19, priority), \
+ STEP_PRIORITY(step_index, 20, priority), \
+ STEP_PRIORITY(step_index, 21, priority), \
+ STEP_PRIORITY(step_index, 22, priority), \
+ STEP_PRIORITY(step_index, 23, priority), \
+ NULL \
+ }
+
+#define PRIORITY_ATTRIBUTE_GROUP_INIT(step, priority)\
+ (&(const struct attribute_group){\
+ .attrs = (struct attribute*[])STEP_PRIORITY_LIST(step, priority),\
+ .is_visible = tgu_node_visible,\
+ .name = "step" #step "_priority" #priority \
+ })
+
static const struct attribute_group *tgu_attr_groups[] = {
&tgu_common_grp,
+ PRIORITY_ATTRIBUTE_GROUP_INIT(0, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(0, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(0, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(0, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(1, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(1, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(1, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(1, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(2, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(2, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(2, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(2, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(3, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(3, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(3, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(3, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(4, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(4, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(4, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(4, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(5, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(5, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(5, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(5, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(6, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(6, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(6, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(6, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(7, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(7, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(7, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(7, 3),
NULL,
};
@@ -129,6 +338,8 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
if (ret)
return ret;
+ tgu_set_dev_caps(drvdata);
+
drvdata->enabled = false;
ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index c6426b4e3617..d769379b8421 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -6,9 +6,70 @@
#ifndef _QCOM_TGU_H
#define _QCOM_TGU_H
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+
/* Register addresses */
#define TGU_CONTROL 0x0000
+#define TGU_DEVID_SENSE_INPUT(devid_val) \
+ ((int)FIELD_GET(GENMASK(17, 10), devid_val))
+#define TGU_DEVID_STEPS(devid_val) \
+ ((int)FIELD_GET(GENMASK(6, 3), devid_val))
+
+#define TGU_BITS_PER_SIGNAL 4
+#define LENGTH_REGISTER 32
+
+/*
+ * TGU configuration space Per-step register layout.
+ * (one step occupies STEP_SIZE bytes) Offsets below are relative to
+ * the start of that step's block.
+ * x-------------------------x x-----------------x
+ * | coresight management | | reserved |
+ * | registers | |-----------------| +0x1F4
+ * | | ---> | priority[3] |
+ * |-------------------------| | |-----------------| +0x194
+ * | step[7] | | | priority[2] |
+ * |-------------------------| step[0] + 7*STEP_SIZE | |-----------------| +0x134
+ * | ... | | | priority[1] |
+ * |-------------------------| step[0] + 1*STEP_SIZE | |-----------------| +0xD4
+ * | step[0] |-----------------------| | priority[0] |
+ * |-------------------------| step[0] (base+0x40) |-----------------| +0x74
+ * | Control and status | | condition select|
+ * | space | |-----------------| +0x60
+ * x-------------------------x base | condition decode|
+ * |-----------------| +0x50
+ * | Timer/Counter |
+ * x-----------------x +0x40
+ */
+#define STEP_SIZE 0x1D8
+#define PRIORITY0_OFFSET 0x0074
+#define PRIORITY_STRIDE 0x0060
+#define REG_SIZE 0x4
+
+/*
+ * Upper bounds on the DEVID-derived per-region counts, used to clamp
+ * whatever the hardware reports so writes never fall outside the
+ * ioremap()'d window or spill into an adjacent region.
+ *
+ * TGU_MAX_STEPS matches the static step0..step7 sysfs tables.
+ * TGU_MAX_PRIORITY_REGS matches the static reg0..reg23 sysfs tables.
+ *
+ * Each priority level's block spans a full PRIORITY_STRIDE (0x60 bytes),
+ * i.e. PRIORITY_STRIDE / REG_SIZE = 24 registers, all of them implemented.
+ */
+
+#define TGU_MAX_STEPS 8
+#define TGU_MAX_PRIORITY_REGS 24
+
+/* Maximum priority that TGU supports */
+#define MAX_PRIORITY 4
+
+/* Calculate compare step addresses */
+#define PRIORITY_REG_STEP(step, priority, reg)\
+ (PRIORITY0_OFFSET + PRIORITY_STRIDE * priority +\
+ REG_SIZE * reg + STEP_SIZE * step)
+
/**
* struct tgu_drvdata - Data structure for a TGU (Trigger Generation Unit)
* @base: Memory-mapped base address of the TGU device
@@ -16,6 +77,10 @@
* @lock: Mutex serialising enable/disable and the associated PM runtime
* reference counting against concurrent sysfs access
* @enabled: Flag indicating whether the TGU device is enabled
+ * @priority: Per-step, per-priority-level, per-register values staged by
+ * sysfs writes and flushed to hardware by tgu_enable()
+ * @num_reg: Number of priority registers implemented per priority level
+ * @num_step: Number of steps implemented
*
* This structure defines the data associated with a TGU device,
* including its base address, device pointer, the lock serialising
@@ -26,6 +91,9 @@ struct tgu_drvdata {
struct device *dev;
struct mutex lock;
bool enabled;
+ unsigned int priority[TGU_MAX_STEPS * MAX_PRIORITY * TGU_MAX_PRIORITY_REGS];
+ int num_reg;
+ int num_step;
};
#endif
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v17 06/10] qcom-tgu: Add TGU decode support
2026-11-13 2:49 [PATCH v17 00/10] Provide support for Trigger Generation Unit Songwei Chai
` (4 preceding siblings ...)
2026-11-13 2:49 ` [PATCH v17 05/10] qcom-tgu: Add signal priority support Songwei Chai
@ 2026-11-13 2:49 ` Songwei Chai
2026-11-13 2:49 ` [PATCH v17 07/10] qcom-tgu: Add support to configure next action Songwei Chai
` (3 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Songwei Chai @ 2026-11-13 2:49 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, songchai, Jie Gan
Decoding is when all the potential pieces for creating a trigger
are brought together for a given step. Example - there may be a
counter keeping track of some occurrences and a priority-group that
is being used to detect a pattern on the sense inputs. These 2
inputs to condition_decode must be programmed, for a given step,
to establish the condition for the trigger, or movement to another
steps.
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 7 +
drivers/hwtracing/qcom/tgu.c | 187 ++++++++++++++++--
drivers/hwtracing/qcom/tgu.h | 35 +++-
3 files changed, 207 insertions(+), 22 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index 09370481758c..b94da9497243 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -14,3 +14,10 @@ KernelVersion: 7.5
Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
Description:
(RW) Set/Get the sensed signal with specific step and priority for TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_condition_decode/reg[0:3]
+Date: November 2026
+KernelVersion: 7.5
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the decode mode with specific step for TGU.
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
index c00e95e68214..72b98a96d7f2 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -18,8 +18,8 @@
struct tgu_attribute {
struct device_attribute attr;
+ enum operation_index operation_index;
u8 step_index;
- u8 priority;
u8 reg_num;
};
@@ -27,11 +27,36 @@ struct tgu_attribute {
* drvdata->priority is a flat [priority][step][reg] array.
*/
static int calculate_array_location(struct tgu_drvdata *drvdata,
- int step_index, int priority,
+ int step_index, int operation_index,
int reg_index)
{
- return priority * (drvdata->num_step) * (drvdata->num_reg) +
- step_index * (drvdata->num_reg) + reg_index;
+ switch (operation_index) {
+ case TGU_PRIORITY0:
+ case TGU_PRIORITY1:
+ case TGU_PRIORITY2:
+ case TGU_PRIORITY3:
+ return operation_index * (drvdata->num_step) *
+ (drvdata->num_reg) +
+ step_index * (drvdata->num_reg) + reg_index;
+ case TGU_CONDITION_DECODE:
+ return step_index * (drvdata->num_condition_decode) +
+ reg_index;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int check_array_location(struct tgu_drvdata *drvdata, int step,
+ int ops, int reg)
+{
+ int result = calculate_array_location(drvdata, step, ops, reg);
+
+ if (result == -EINVAL)
+ dev_err(drvdata->dev, "check array location - Fail\n");
+
+ return result;
}
static ssize_t tgu_dataset_show(struct device *dev,
@@ -42,9 +67,11 @@ static ssize_t tgu_dataset_show(struct device *dev,
container_of(attr, struct tgu_attribute, attr);
int index;
- index = calculate_array_location(drvdata, tgu_attr->step_index,
- tgu_attr->priority,
- tgu_attr->reg_num);
+ index = check_array_location(drvdata, tgu_attr->step_index,
+ tgu_attr->operation_index, tgu_attr->reg_num);
+
+ if (index == -EINVAL)
+ return index;
/*
* Serialise against tgu_dataset_store() and tgu_enable(), which read
@@ -53,7 +80,20 @@ static ssize_t tgu_dataset_show(struct device *dev,
*/
guard(mutex)(&drvdata->lock);
- return sysfs_emit(buf, "0x%x\n", drvdata->priority[index]);
+ switch (tgu_attr->operation_index) {
+ case TGU_PRIORITY0:
+ case TGU_PRIORITY1:
+ case TGU_PRIORITY2:
+ case TGU_PRIORITY3:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->priority[index]);
+ case TGU_CONDITION_DECODE:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->condition_decode[index]);
+ default:
+ break;
+ }
+ return -EINVAL;
}
static ssize_t tgu_dataset_store(struct device *dev,
@@ -73,12 +113,30 @@ static ssize_t tgu_dataset_store(struct device *dev,
guard(mutex)(&tgu_drvdata->lock);
index = calculate_array_location(tgu_drvdata, tgu_attr->step_index,
- tgu_attr->priority,
+ tgu_attr->operation_index,
tgu_attr->reg_num);
- tgu_drvdata->priority[index] = val;
+ if (index == -EINVAL)
+ return index;
+
+ switch (tgu_attr->operation_index) {
+ case TGU_PRIORITY0:
+ case TGU_PRIORITY1:
+ case TGU_PRIORITY2:
+ case TGU_PRIORITY3:
+ tgu_drvdata->priority[index] = val;
+ ret = size;
+ break;
+ case TGU_CONDITION_DECODE:
+ tgu_drvdata->condition_decode[index] = val;
+ ret = size;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
- return size;
+ return ret;
}
static umode_t tgu_node_visible(struct kobject *kobject,
@@ -95,22 +153,41 @@ static umode_t tgu_node_visible(struct kobject *kobject,
if (tgu_attr->step_index >= drvdata->num_step)
return SYSFS_GROUP_INVISIBLE;
- if (tgu_attr->reg_num >= drvdata->num_reg)
- return 0;
+ switch (tgu_attr->operation_index) {
+ case TGU_PRIORITY0:
+ case TGU_PRIORITY1:
+ case TGU_PRIORITY2:
+ case TGU_PRIORITY3:
+ if (!drvdata->num_reg)
+ return SYSFS_GROUP_INVISIBLE;
+ if (tgu_attr->reg_num < drvdata->num_reg)
+ return attr->mode;
+ break;
+ case TGU_CONDITION_DECODE:
+ if (!drvdata->num_condition_decode)
+ return SYSFS_GROUP_INVISIBLE;
+ if (tgu_attr->reg_num < drvdata->num_condition_decode)
+ return attr->mode;
+ break;
+ default:
+ break;
+ }
- return attr->mode;
+ return 0;
}
-static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
+static ssize_t tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
{
- int i, j, k, index;
+ int i, j, k, index = 0;
CS_UNLOCK(drvdata->base);
for (i = 0; i < drvdata->num_step; i++) {
for (j = 0; j < MAX_PRIORITY; j++) {
for (k = 0; k < drvdata->num_reg; k++) {
- index = calculate_array_location(
+ index = check_array_location(
drvdata, i, j, k);
+ if (index == -EINVAL)
+ goto exit;
writel(drvdata->priority[index],
drvdata->base +
@@ -118,14 +195,28 @@ static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
}
}
}
+
+ for (i = 0; i < drvdata->num_step; i++) {
+ for (j = 0; j < drvdata->num_condition_decode; j++) {
+ index = check_array_location(drvdata, i,
+ TGU_CONDITION_DECODE, j);
+ if (index == -EINVAL)
+ goto exit;
+
+ writel(drvdata->condition_decode[index],
+ drvdata->base + CONDITION_DECODE_STEP(i, j));
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
+exit:
CS_LOCK(drvdata->base);
+ return index >= 0 ? 0 : -EINVAL;
}
static void tgu_set_dev_caps(struct tgu_drvdata *drvdata)
{
- int num_sense_input, num_reg, num_step;
+ int num_sense_input, num_reg, num_step, num_condition_decode;
u32 devid;
devid = readl(drvdata->base + CORESIGHT_DEVID);
@@ -153,12 +244,28 @@ static void tgu_set_dev_caps(struct tgu_drvdata *drvdata)
num_step = TGU_MAX_STEPS;
}
drvdata->num_step = num_step;
+
+ num_condition_decode = TGU_DEVID_CONDITIONS(devid);
+
+ if (num_condition_decode > TGU_MAX_CONDITION_DECODE) {
+ dev_warn(drvdata->dev,
+ "DEVID reports %d condition decode regs, clamping to %d\n",
+ num_condition_decode, TGU_MAX_CONDITION_DECODE);
+ num_condition_decode = TGU_MAX_CONDITION_DECODE;
+ }
+ drvdata->num_condition_decode = num_condition_decode;
}
-static void tgu_enable(struct tgu_drvdata *drvdata)
+static int tgu_enable(struct tgu_drvdata *drvdata)
{
- tgu_write_all_hw_regs(drvdata);
+ int ret;
+
+ ret = tgu_write_all_hw_regs(drvdata);
+ if (ret)
+ return ret;
+
drvdata->enabled = true;
+ return 0;
}
static void tgu_disable(struct tgu_drvdata *drvdata)
@@ -212,7 +319,11 @@ static ssize_t enable_tgu_store(struct device *dev,
if (ret)
return ret;
- tgu_enable(drvdata);
+ ret = tgu_enable(drvdata);
+ if (ret) {
+ pm_runtime_put(dev);
+ return ret;
+ }
} else {
if (!drvdata->enabled)
return -EINVAL;
@@ -280,6 +391,24 @@ static const struct attribute_group tgu_common_grp = {
.name = "step" #step "_priority" #priority \
})
+#define STEP_DECODE(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_DECODE, reg_num)
+
+#define STEP_DECODE_LIST(step_index) \
+ {STEP_DECODE(step_index, 0), \
+ STEP_DECODE(step_index, 1), \
+ STEP_DECODE(step_index, 2), \
+ STEP_DECODE(step_index, 3), \
+ NULL \
+ }
+
+#define CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(step)\
+ (&(const struct attribute_group){\
+ .attrs = (struct attribute*[])STEP_DECODE_LIST(step),\
+ .is_visible = tgu_node_visible,\
+ .name = "step" #step "_condition_decode" \
+ })
+
static const struct attribute_group *tgu_attr_groups[] = {
&tgu_common_grp,
PRIORITY_ATTRIBUTE_GROUP_INIT(0, 0),
@@ -314,6 +443,14 @@ static const struct attribute_group *tgu_attr_groups[] = {
PRIORITY_ATTRIBUTE_GROUP_INIT(7, 1),
PRIORITY_ATTRIBUTE_GROUP_INIT(7, 2),
PRIORITY_ATTRIBUTE_GROUP_INIT(7, 3),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(0),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(1),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(2),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(3),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(4),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(5),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(6),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(7),
NULL,
};
@@ -321,6 +458,7 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device *dev = &adev->dev;
struct tgu_drvdata *drvdata;
+ int i, j;
int ret;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
@@ -340,6 +478,13 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
tgu_set_dev_caps(drvdata);
+ /* Initialize all condition decode registers to NOT */
+ for (i = 0; i < drvdata->num_step; i++)
+ for (j = 0; j < drvdata->num_condition_decode; j++)
+ drvdata->condition_decode[calculate_array_location(
+ drvdata, i, TGU_CONDITION_DECODE, j)] =
+ TGU_CONDITION_DECODE_NOT;
+
drvdata->enabled = false;
ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index d769379b8421..7d88ad8e730f 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -16,7 +16,8 @@
((int)FIELD_GET(GENMASK(17, 10), devid_val))
#define TGU_DEVID_STEPS(devid_val) \
((int)FIELD_GET(GENMASK(6, 3), devid_val))
-
+#define TGU_DEVID_CONDITIONS(devid_val) \
+ ((int)FIELD_GET(GENMASK(2, 0), devid_val))
#define TGU_BITS_PER_SIGNAL 4
#define LENGTH_REGISTER 32
@@ -44,6 +45,7 @@
*/
#define STEP_SIZE 0x1D8
#define PRIORITY0_OFFSET 0x0074
+#define CONDITION_DECODE_OFFSET 0x0050
#define PRIORITY_STRIDE 0x0060
#define REG_SIZE 0x4
@@ -54,6 +56,7 @@
*
* TGU_MAX_STEPS matches the static step0..step7 sysfs tables.
* TGU_MAX_PRIORITY_REGS matches the static reg0..reg23 sysfs tables.
+ * TGU_MAX_CONDITION_DECODE decode region 0x50..0x5F holds 4 u32 registers
*
* Each priority level's block spans a full PRIORITY_STRIDE (0x60 bytes),
* i.e. PRIORITY_STRIDE / REG_SIZE = 24 registers, all of them implemented.
@@ -61,15 +64,40 @@
#define TGU_MAX_STEPS 8
#define TGU_MAX_PRIORITY_REGS 24
+#define TGU_MAX_CONDITION_DECODE 4
/* Maximum priority that TGU supports */
#define MAX_PRIORITY 4
+/*
+ * Values for struct tgu_attribute.operation_index: TGU_PRIORITY0..
+ * TGU_PRIORITY3 select a priority level, TGU_CONDITION_DECODE selects the
+ * condition decode registers instead.
+ */
+enum operation_index {
+ TGU_PRIORITY0,
+ TGU_PRIORITY1,
+ TGU_PRIORITY2,
+ TGU_PRIORITY3,
+ TGU_CONDITION_DECODE,
+};
+
/* Calculate compare step addresses */
#define PRIORITY_REG_STEP(step, priority, reg)\
(PRIORITY0_OFFSET + PRIORITY_STRIDE * priority +\
REG_SIZE * reg + STEP_SIZE * step)
+#define CONDITION_DECODE_STEP(step, decode) \
+ (CONDITION_DECODE_OFFSET + REG_SIZE * decode + STEP_SIZE * step)
+
+/*
+ * Neutral ('NOT') value for the condition decode registers. This is the
+ * driver's default so that, before userspace programs a real decode, an
+ * enabled step does not spuriously match. Used both when seeding the shadow
+ * table at probe and when clearing it via reset_tgu.
+ */
+#define TGU_CONDITION_DECODE_NOT 0x1000000
+
/**
* struct tgu_drvdata - Data structure for a TGU (Trigger Generation Unit)
* @base: Memory-mapped base address of the TGU device
@@ -79,8 +107,11 @@
* @enabled: Flag indicating whether the TGU device is enabled
* @priority: Per-step, per-priority-level, per-register values staged by
* sysfs writes and flushed to hardware by tgu_enable()
+ * @condition_decode: Per-step, per-register decode values staged by sysfs
+ * writes and flushed to hardware by tgu_enable()
* @num_reg: Number of priority registers implemented per priority level
* @num_step: Number of steps implemented
+ * @num_condition_decode: Maximum number of condition_decode
*
* This structure defines the data associated with a TGU device,
* including its base address, device pointer, the lock serialising
@@ -92,8 +123,10 @@ struct tgu_drvdata {
struct mutex lock;
bool enabled;
unsigned int priority[TGU_MAX_STEPS * MAX_PRIORITY * TGU_MAX_PRIORITY_REGS];
+ unsigned int condition_decode[TGU_MAX_STEPS * TGU_MAX_CONDITION_DECODE];
int num_reg;
int num_step;
+ int num_condition_decode;
};
#endif
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v17 07/10] qcom-tgu: Add support to configure next action
2026-11-13 2:49 [PATCH v17 00/10] Provide support for Trigger Generation Unit Songwei Chai
` (5 preceding siblings ...)
2026-11-13 2:49 ` [PATCH v17 06/10] qcom-tgu: Add TGU decode support Songwei Chai
@ 2026-11-13 2:49 ` Songwei Chai
2026-11-13 2:49 ` [PATCH v17 08/10] coresight: Add CORESIGHT_DEVID2 to linux/coresight.h Songwei Chai
` (2 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Songwei Chai @ 2026-11-13 2:49 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, songchai, Jie Gan
Add "select" node for each step to determine if another step is taken,
trigger(s) are generated, counters/timers incremented/decremented, etc.
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 16 +++
drivers/hwtracing/qcom/tgu.c | 97 ++++++++++++++++++-
drivers/hwtracing/qcom/tgu.h | 17 +++-
3 files changed, 128 insertions(+), 2 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index b94da9497243..f487881b7def 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -21,3 +21,19 @@ KernelVersion: 7.5
Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
Description:
(RW) Set/Get the decode mode with specific step for TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_condition_select/reg[0:3]
+Date: November 2026
+KernelVersion: 7.5
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the next action with specific step for TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_condition_select/default
+Date: November 2026
+KernelVersion: 7.5
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the default next action for a specific step of the
+ TGU. This is the last register of the condition select region
+ and is applied when none of the numbered select registers match.
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
index 72b98a96d7f2..6e66752a7b8d 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -23,6 +23,18 @@ struct tgu_attribute {
u8 reg_num;
};
+/*
+ * The last register of the 'condition select' region is the next-action
+ * ('default') register. Its position depends on the runtime value of
+ * num_condition_select, so it is exposed as a dedicated statically named
+ * "default" attribute carrying a sentinel reg number, rather than renaming
+ * a shared static attribute at runtime.
+ *
+ * Must fit in struct tgu_attribute.reg_num (u8) while staying outside every
+ * legitimate reg_num range (0..23 across priority/decode/select).
+ */
+#define TGU_SELECT_DEFAULT_REG 0xff
+
/*
* drvdata->priority is a flat [priority][step][reg] array.
*/
@@ -41,6 +53,16 @@ static int calculate_array_location(struct tgu_drvdata *drvdata,
case TGU_CONDITION_DECODE:
return step_index * (drvdata->num_condition_decode) +
reg_index;
+ case TGU_CONDITION_SELECT:
+ /*
+ * The 'default' register is the last one in the select region;
+ * it is exposed with a sentinel reg number, translated here to
+ * its actual position.
+ */
+ if (reg_index == TGU_SELECT_DEFAULT_REG)
+ reg_index = drvdata->num_condition_select - 1;
+ return step_index * (drvdata->num_condition_select) +
+ reg_index;
default:
break;
}
@@ -90,6 +112,9 @@ static ssize_t tgu_dataset_show(struct device *dev,
case TGU_CONDITION_DECODE:
return sysfs_emit(buf, "0x%x\n",
drvdata->condition_decode[index]);
+ case TGU_CONDITION_SELECT:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->condition_select[index]);
default:
break;
}
@@ -131,6 +156,10 @@ static ssize_t tgu_dataset_store(struct device *dev,
tgu_drvdata->condition_decode[index] = val;
ret = size;
break;
+ case TGU_CONDITION_SELECT:
+ tgu_drvdata->condition_select[index] = val;
+ ret = size;
+ break;
default:
ret = -EINVAL;
break;
@@ -169,6 +198,17 @@ static umode_t tgu_node_visible(struct kobject *kobject,
if (tgu_attr->reg_num < drvdata->num_condition_decode)
return attr->mode;
break;
+ case TGU_CONDITION_SELECT:
+ /*
+ * The next-action ('default') register is exposed as its own
+ * statically named attribute and is always present. The numbered
+ * data registers occupy the positions before it.
+ */
+ if (tgu_attr->reg_num == TGU_SELECT_DEFAULT_REG)
+ return attr->mode;
+ if (tgu_attr->reg_num < drvdata->num_condition_select - 1)
+ return attr->mode;
+ break;
default:
break;
}
@@ -207,6 +247,18 @@ static ssize_t tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
drvdata->base + CONDITION_DECODE_STEP(i, j));
}
}
+
+ for (i = 0; i < drvdata->num_step; i++) {
+ for (j = 0; j < drvdata->num_condition_select; j++) {
+ index = check_array_location(drvdata, i,
+ TGU_CONDITION_SELECT, j);
+ if (index == -EINVAL)
+ goto exit;
+
+ writel(drvdata->condition_select[index],
+ drvdata->base + CONDITION_SELECT_STEP(i, j));
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
exit:
@@ -216,7 +268,8 @@ static ssize_t tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
static void tgu_set_dev_caps(struct tgu_drvdata *drvdata)
{
- int num_sense_input, num_reg, num_step, num_condition_decode;
+ int num_sense_input, num_reg, num_step;
+ int num_condition_decode, num_condition_select;
u32 devid;
devid = readl(drvdata->base + CORESIGHT_DEVID);
@@ -254,6 +307,17 @@ static void tgu_set_dev_caps(struct tgu_drvdata *drvdata)
num_condition_decode = TGU_MAX_CONDITION_DECODE;
}
drvdata->num_condition_decode = num_condition_decode;
+
+ /* The select region has an additional 'default' register. */
+ num_condition_select = TGU_DEVID_CONDITIONS(devid) + 1;
+
+ if (num_condition_select > TGU_MAX_CONDITION_SELECT) {
+ dev_warn(drvdata->dev,
+ "DEVID reports %d condition select regs, clamping to %d\n",
+ num_condition_select, TGU_MAX_CONDITION_SELECT);
+ num_condition_select = TGU_MAX_CONDITION_SELECT;
+ }
+ drvdata->num_condition_select = num_condition_select;
}
static int tgu_enable(struct tgu_drvdata *drvdata)
@@ -409,6 +473,29 @@ static const struct attribute_group tgu_common_grp = {
.name = "step" #step "_condition_decode" \
})
+#define STEP_SELECT(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_SELECT, reg_num)
+
+#define STEP_SELECT_DEFAULT(step_index) \
+ tgu_dataset_rw(default, step_index, TGU_CONDITION_SELECT, \
+ TGU_SELECT_DEFAULT_REG)
+
+#define STEP_SELECT_LIST(step_index) \
+ {STEP_SELECT(step_index, 0), \
+ STEP_SELECT(step_index, 1), \
+ STEP_SELECT(step_index, 2), \
+ STEP_SELECT(step_index, 3), \
+ STEP_SELECT_DEFAULT(step_index), \
+ NULL \
+ }
+
+#define CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(step)\
+ (&(const struct attribute_group){\
+ .attrs = (struct attribute*[])STEP_SELECT_LIST(step),\
+ .is_visible = tgu_node_visible,\
+ .name = "step" #step "_condition_select" \
+ })
+
static const struct attribute_group *tgu_attr_groups[] = {
&tgu_common_grp,
PRIORITY_ATTRIBUTE_GROUP_INIT(0, 0),
@@ -451,6 +538,14 @@ static const struct attribute_group *tgu_attr_groups[] = {
CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(5),
CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(6),
CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(7),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(0),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(1),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(2),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(3),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(4),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(5),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(6),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(7),
NULL,
};
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index 7d88ad8e730f..27eb1f81effa 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -46,6 +46,7 @@
#define STEP_SIZE 0x1D8
#define PRIORITY0_OFFSET 0x0074
#define CONDITION_DECODE_OFFSET 0x0050
+#define CONDITION_SELECT_OFFSET 0x0060
#define PRIORITY_STRIDE 0x0060
#define REG_SIZE 0x4
@@ -57,6 +58,8 @@
* TGU_MAX_STEPS matches the static step0..step7 sysfs tables.
* TGU_MAX_PRIORITY_REGS matches the static reg0..reg23 sysfs tables.
* TGU_MAX_CONDITION_DECODE decode region 0x50..0x5F holds 4 u32 registers
+ * TGU_MAX_CONDITION_SELECT select region 0x60..0x73 holds 5 u32 registers
+ * (the last one being the 'default' next-action)
*
* Each priority level's block spans a full PRIORITY_STRIDE (0x60 bytes),
* i.e. PRIORITY_STRIDE / REG_SIZE = 24 registers, all of them implemented.
@@ -65,6 +68,7 @@
#define TGU_MAX_STEPS 8
#define TGU_MAX_PRIORITY_REGS 24
#define TGU_MAX_CONDITION_DECODE 4
+#define TGU_MAX_CONDITION_SELECT 5
/* Maximum priority that TGU supports */
#define MAX_PRIORITY 4
@@ -72,7 +76,8 @@
/*
* Values for struct tgu_attribute.operation_index: TGU_PRIORITY0..
* TGU_PRIORITY3 select a priority level, TGU_CONDITION_DECODE selects the
- * condition decode registers instead.
+ * condition decode registers, and TGU_CONDITION_SELECT selects the
+ * condition select registers.
*/
enum operation_index {
TGU_PRIORITY0,
@@ -80,6 +85,7 @@ enum operation_index {
TGU_PRIORITY2,
TGU_PRIORITY3,
TGU_CONDITION_DECODE,
+ TGU_CONDITION_SELECT,
};
/* Calculate compare step addresses */
@@ -90,6 +96,9 @@ enum operation_index {
#define CONDITION_DECODE_STEP(step, decode) \
(CONDITION_DECODE_OFFSET + REG_SIZE * decode + STEP_SIZE * step)
+#define CONDITION_SELECT_STEP(step, select) \
+ (CONDITION_SELECT_OFFSET + REG_SIZE * select + STEP_SIZE * step)
+
/*
* Neutral ('NOT') value for the condition decode registers. This is the
* driver's default so that, before userspace programs a real decode, an
@@ -109,9 +118,13 @@ enum operation_index {
* sysfs writes and flushed to hardware by tgu_enable()
* @condition_decode: Per-step, per-register decode values staged by sysfs
* writes and flushed to hardware by tgu_enable()
+ * @condition_select: Per-step, per-register select values (including the
+ * trailing 'default' next-action register) staged by
+ * sysfs writes and flushed to hardware by tgu_enable()
* @num_reg: Number of priority registers implemented per priority level
* @num_step: Number of steps implemented
* @num_condition_decode: Maximum number of condition_decode
+ * @num_condition_select: Maximum number of condition_select
*
* This structure defines the data associated with a TGU device,
* including its base address, device pointer, the lock serialising
@@ -124,9 +137,11 @@ struct tgu_drvdata {
bool enabled;
unsigned int priority[TGU_MAX_STEPS * MAX_PRIORITY * TGU_MAX_PRIORITY_REGS];
unsigned int condition_decode[TGU_MAX_STEPS * TGU_MAX_CONDITION_DECODE];
+ unsigned int condition_select[TGU_MAX_STEPS * TGU_MAX_CONDITION_SELECT];
int num_reg;
int num_step;
int num_condition_decode;
+ int num_condition_select;
};
#endif
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v17 08/10] coresight: Add CORESIGHT_DEVID2 to linux/coresight.h
2026-11-13 2:49 [PATCH v17 00/10] Provide support for Trigger Generation Unit Songwei Chai
` (6 preceding siblings ...)
2026-11-13 2:49 ` [PATCH v17 07/10] qcom-tgu: Add support to configure next action Songwei Chai
@ 2026-11-13 2:49 ` Songwei Chai
2026-11-13 10:27 ` Suzuki K Poulose
2026-11-13 2:49 ` [PATCH v17 09/10] qcom-tgu: Add timer/counter functionality for TGU Songwei Chai
2026-11-13 2:49 ` [PATCH v17 10/10] qcom-tgu: Add reset node to initialize Songwei Chai
9 siblings, 1 reply; 14+ messages in thread
From: Songwei Chai @ 2026-11-13 2:49 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, songchai
CORESIGHT_DEVID2 lives at a fixed, architecturally-defined offset
(0xfc0) within the generic CoreSight management register block,
immediately preceding CORESIGHT_DEVID (0xfc8). Components outside
drivers/hwtracing/coresight/ that follow the same register convention
(e.g. the Qualcomm TGU, which uses it to detect its timer/counter
capabilities) need to read it too.
Add it to the public include/linux/coresight.h so it can be shared
outside of drivers/hwtracing/coresight/, matching CORESIGHT_LAR and
CORESIGHT_DEVID already moved there.
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
include/linux/coresight.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/linux/coresight.h b/include/linux/coresight.h
index 5286b3606581..7ce0366c7c70 100644
--- a/include/linux/coresight.h
+++ b/include/linux/coresight.h
@@ -37,6 +37,7 @@
#define CORESIGHT_UNLOCK 0xc5acce55
#define CORESIGHT_LAR 0xfb0
#define CORESIGHT_DEVID 0xfc8
+#define CORESIGHT_DEVID2 0xfc0
static inline void CS_LOCK(void __iomem *addr)
{
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v17 08/10] coresight: Add CORESIGHT_DEVID2 to linux/coresight.h
2026-11-13 2:49 ` [PATCH v17 08/10] coresight: Add CORESIGHT_DEVID2 to linux/coresight.h Songwei Chai
@ 2026-11-13 10:27 ` Suzuki K Poulose
0 siblings, 0 replies; 14+ messages in thread
From: Suzuki K Poulose @ 2026-11-13 10:27 UTC (permalink / raw)
To: Songwei Chai, andersson, alexander.shishkin, mike.leach,
konrad.dybcio, james.clark, krzk+dt, conor+dt
Cc: linux-kernel, linux-arm-kernel, linux-arm-msm, coresight,
devicetree, gregkh, songchai
On 13/11/2026 02:49, Songwei Chai wrote:
> CORESIGHT_DEVID2 lives at a fixed, architecturally-defined offset
> (0xfc0) within the generic CoreSight management register block,
> immediately preceding CORESIGHT_DEVID (0xfc8). Components outside
> drivers/hwtracing/coresight/ that follow the same register convention
> (e.g. the Qualcomm TGU, which uses it to detect its timer/counter
> capabilities) need to read it too.
>
> Add it to the public include/linux/coresight.h so it can be shared
> outside of drivers/hwtracing/coresight/, matching CORESIGHT_LAR and
> CORESIGHT_DEVID already moved there.
>
> Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
> ---
> include/linux/coresight.h | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/include/linux/coresight.h b/include/linux/coresight.h
> index 5286b3606581..7ce0366c7c70 100644
> --- a/include/linux/coresight.h
> +++ b/include/linux/coresight.h
> @@ -37,6 +37,7 @@
> #define CORESIGHT_UNLOCK 0xc5acce55
> #define CORESIGHT_LAR 0xfb0
> #define CORESIGHT_DEVID 0xfc8
> +#define CORESIGHT_DEVID2 0xfc0
>
> static inline void CS_LOCK(void __iomem *addr)
> {
Merge it with Patch 1 please.
Suzuki
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v17 09/10] qcom-tgu: Add timer/counter functionality for TGU
2026-11-13 2:49 [PATCH v17 00/10] Provide support for Trigger Generation Unit Songwei Chai
` (7 preceding siblings ...)
2026-11-13 2:49 ` [PATCH v17 08/10] coresight: Add CORESIGHT_DEVID2 to linux/coresight.h Songwei Chai
@ 2026-11-13 2:49 ` Songwei Chai
2026-11-13 2:49 ` [PATCH v17 10/10] qcom-tgu: Add reset node to initialize Songwei Chai
9 siblings, 0 replies; 14+ messages in thread
From: Songwei Chai @ 2026-11-13 2:49 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, songchai, Jie Gan
Add counter and timer node for each step which could be
programed if they are to be utilized in trigger event/sequence.
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 14 ++
drivers/hwtracing/qcom/tgu.c | 120 +++++++++++++++++-
drivers/hwtracing/qcom/tgu.h | 32 +++++
3 files changed, 165 insertions(+), 1 deletion(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index f487881b7def..e0ea5419188e 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -37,3 +37,17 @@ Description:
(RW) Set/Get the default next action for a specific step of the
TGU. This is the last register of the condition select region
and is applied when none of the numbered select registers match.
+
+What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_timer/reg[0:1]
+Date: November 2026
+KernelVersion: 7.5
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the timer value with specific step for TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_counter/reg[0:1]
+Date: November 2026
+KernelVersion: 7.5
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the counter value with specific step for TGU.
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
index 6e66752a7b8d..1cb131a3c521 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -63,6 +63,10 @@ static int calculate_array_location(struct tgu_drvdata *drvdata,
reg_index = drvdata->num_condition_select - 1;
return step_index * (drvdata->num_condition_select) +
reg_index;
+ case TGU_COUNTER:
+ return step_index * (drvdata->num_counter) + reg_index;
+ case TGU_TIMER:
+ return step_index * (drvdata->num_timer) + reg_index;
default:
break;
}
@@ -115,6 +119,12 @@ static ssize_t tgu_dataset_show(struct device *dev,
case TGU_CONDITION_SELECT:
return sysfs_emit(buf, "0x%x\n",
drvdata->condition_select[index]);
+ case TGU_TIMER:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->timer[index]);
+ case TGU_COUNTER:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->counter[index]);
default:
break;
}
@@ -160,6 +170,14 @@ static ssize_t tgu_dataset_store(struct device *dev,
tgu_drvdata->condition_select[index] = val;
ret = size;
break;
+ case TGU_TIMER:
+ tgu_drvdata->timer[index] = val;
+ ret = size;
+ break;
+ case TGU_COUNTER:
+ tgu_drvdata->counter[index] = val;
+ ret = size;
+ break;
default:
ret = -EINVAL;
break;
@@ -209,6 +227,18 @@ static umode_t tgu_node_visible(struct kobject *kobject,
if (tgu_attr->reg_num < drvdata->num_condition_select - 1)
return attr->mode;
break;
+ case TGU_COUNTER:
+ if (!drvdata->num_counter)
+ return SYSFS_GROUP_INVISIBLE;
+ if (tgu_attr->reg_num < drvdata->num_counter)
+ return attr->mode;
+ break;
+ case TGU_TIMER:
+ if (!drvdata->num_timer)
+ return SYSFS_GROUP_INVISIBLE;
+ if (tgu_attr->reg_num < drvdata->num_timer)
+ return attr->mode;
+ break;
default:
break;
}
@@ -259,6 +289,30 @@ static ssize_t tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
drvdata->base + CONDITION_SELECT_STEP(i, j));
}
}
+
+ for (i = 0; i < drvdata->num_step; i++) {
+ for (j = 0; j < drvdata->num_timer; j++) {
+ index = check_array_location(drvdata, i, TGU_TIMER, j);
+
+ if (index == -EINVAL)
+ goto exit;
+
+ writel(drvdata->timer[index],
+ drvdata->base + TIMER_COMPARE_STEP(i, j));
+ }
+ }
+
+ for (i = 0; i < drvdata->num_step; i++) {
+ for (j = 0; j < drvdata->num_counter; j++) {
+ index = check_array_location(drvdata, i, TGU_COUNTER, j);
+
+ if (index == -EINVAL)
+ goto exit;
+
+ writel(drvdata->counter[index],
+ drvdata->base + COUNTER_COMPARE_STEP(i, j));
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
exit:
@@ -270,7 +324,8 @@ static void tgu_set_dev_caps(struct tgu_drvdata *drvdata)
{
int num_sense_input, num_reg, num_step;
int num_condition_decode, num_condition_select;
- u32 devid;
+ int num_timers = 0, num_counters = 0;
+ u32 devid, devid2;
devid = readl(drvdata->base + CORESIGHT_DEVID);
@@ -318,6 +373,21 @@ static void tgu_set_dev_caps(struct tgu_drvdata *drvdata)
num_condition_select = TGU_MAX_CONDITION_SELECT;
}
drvdata->num_condition_select = num_condition_select;
+
+ devid2 = readl(drvdata->base + CORESIGHT_DEVID2);
+
+ if (TGU_DEVID2_TIMER0(devid2))
+ num_timers++;
+ if (TGU_DEVID2_TIMER1(devid2))
+ num_timers++;
+
+ if (TGU_DEVID2_COUNTER0(devid2))
+ num_counters++;
+ if (TGU_DEVID2_COUNTER1(devid2))
+ num_counters++;
+
+ drvdata->num_timer = num_timers;
+ drvdata->num_counter = num_counters;
}
static int tgu_enable(struct tgu_drvdata *drvdata)
@@ -496,6 +566,38 @@ static const struct attribute_group tgu_common_grp = {
.name = "step" #step "_condition_select" \
})
+#define STEP_TIMER(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_TIMER, reg_num)
+
+#define STEP_TIMER_LIST(step_index) \
+ {STEP_TIMER(step_index, 0), \
+ STEP_TIMER(step_index, 1), \
+ NULL \
+ }
+
+#define TIMER_ATTRIBUTE_GROUP_INIT(step)\
+ (&(const struct attribute_group){\
+ .attrs = (struct attribute*[])STEP_TIMER_LIST(step),\
+ .is_visible = tgu_node_visible,\
+ .name = "step" #step "_timer" \
+ })
+
+#define STEP_COUNTER(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_COUNTER, reg_num)
+
+#define STEP_COUNTER_LIST(step_index) \
+ {STEP_COUNTER(step_index, 0), \
+ STEP_COUNTER(step_index, 1), \
+ NULL \
+ }
+
+#define COUNTER_ATTRIBUTE_GROUP_INIT(step)\
+ (&(const struct attribute_group){\
+ .attrs = (struct attribute*[])STEP_COUNTER_LIST(step),\
+ .is_visible = tgu_node_visible,\
+ .name = "step" #step "_counter" \
+ })
+
static const struct attribute_group *tgu_attr_groups[] = {
&tgu_common_grp,
PRIORITY_ATTRIBUTE_GROUP_INIT(0, 0),
@@ -546,6 +648,22 @@ static const struct attribute_group *tgu_attr_groups[] = {
CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(5),
CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(6),
CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(7),
+ TIMER_ATTRIBUTE_GROUP_INIT(0),
+ TIMER_ATTRIBUTE_GROUP_INIT(1),
+ TIMER_ATTRIBUTE_GROUP_INIT(2),
+ TIMER_ATTRIBUTE_GROUP_INIT(3),
+ TIMER_ATTRIBUTE_GROUP_INIT(4),
+ TIMER_ATTRIBUTE_GROUP_INIT(5),
+ TIMER_ATTRIBUTE_GROUP_INIT(6),
+ TIMER_ATTRIBUTE_GROUP_INIT(7),
+ COUNTER_ATTRIBUTE_GROUP_INIT(0),
+ COUNTER_ATTRIBUTE_GROUP_INIT(1),
+ COUNTER_ATTRIBUTE_GROUP_INIT(2),
+ COUNTER_ATTRIBUTE_GROUP_INIT(3),
+ COUNTER_ATTRIBUTE_GROUP_INIT(4),
+ COUNTER_ATTRIBUTE_GROUP_INIT(5),
+ COUNTER_ATTRIBUTE_GROUP_INIT(6),
+ COUNTER_ATTRIBUTE_GROUP_INIT(7),
NULL,
};
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index 27eb1f81effa..3b103a399815 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -18,6 +18,14 @@
((int)FIELD_GET(GENMASK(6, 3), devid_val))
#define TGU_DEVID_CONDITIONS(devid_val) \
((int)FIELD_GET(GENMASK(2, 0), devid_val))
+#define TGU_DEVID2_TIMER0(devid_val) \
+ ((int)FIELD_GET(GENMASK(23, 18), devid_val))
+#define TGU_DEVID2_TIMER1(devid_val) \
+ ((int)FIELD_GET(GENMASK(17, 12), devid_val))
+#define TGU_DEVID2_COUNTER0(devid_val) \
+ ((int)FIELD_GET(GENMASK(11, 6), devid_val))
+#define TGU_DEVID2_COUNTER1(devid_val) \
+ ((int)FIELD_GET(GENMASK(5, 0), devid_val))
#define TGU_BITS_PER_SIGNAL 4
#define LENGTH_REGISTER 32
@@ -47,6 +55,8 @@
#define PRIORITY0_OFFSET 0x0074
#define CONDITION_DECODE_OFFSET 0x0050
#define CONDITION_SELECT_OFFSET 0x0060
+#define TIMER_START_OFFSET 0x0040
+#define COUNTER_START_OFFSET 0x0048
#define PRIORITY_STRIDE 0x0060
#define REG_SIZE 0x4
@@ -60,6 +70,8 @@
* TGU_MAX_CONDITION_DECODE decode region 0x50..0x5F holds 4 u32 registers
* TGU_MAX_CONDITION_SELECT select region 0x60..0x73 holds 5 u32 registers
* (the last one being the 'default' next-action)
+ * TGU_MAX_TIMER timer region 0x40..0x47 holds 2 u32 registers
+ * TGU_MAX_COUNTER counter region 0x48..0x4F holds 2 u32 registers
*
* Each priority level's block spans a full PRIORITY_STRIDE (0x60 bytes),
* i.e. PRIORITY_STRIDE / REG_SIZE = 24 registers, all of them implemented.
@@ -69,6 +81,8 @@
#define TGU_MAX_PRIORITY_REGS 24
#define TGU_MAX_CONDITION_DECODE 4
#define TGU_MAX_CONDITION_SELECT 5
+#define TGU_MAX_TIMER 2
+#define TGU_MAX_COUNTER 2
/* Maximum priority that TGU supports */
#define MAX_PRIORITY 4
@@ -86,6 +100,8 @@ enum operation_index {
TGU_PRIORITY3,
TGU_CONDITION_DECODE,
TGU_CONDITION_SELECT,
+ TGU_TIMER,
+ TGU_COUNTER,
};
/* Calculate compare step addresses */
@@ -99,6 +115,12 @@ enum operation_index {
#define CONDITION_SELECT_STEP(step, select) \
(CONDITION_SELECT_OFFSET + REG_SIZE * select + STEP_SIZE * step)
+#define TIMER_COMPARE_STEP(step, timer) \
+ (TIMER_START_OFFSET + REG_SIZE * timer + STEP_SIZE * step)
+
+#define COUNTER_COMPARE_STEP(step, counter) \
+ (COUNTER_START_OFFSET + REG_SIZE * counter + STEP_SIZE * step)
+
/*
* Neutral ('NOT') value for the condition decode registers. This is the
* driver's default so that, before userspace programs a real decode, an
@@ -121,10 +143,16 @@ enum operation_index {
* @condition_select: Per-step, per-register select values (including the
* trailing 'default' next-action register) staged by
* sysfs writes and flushed to hardware by tgu_enable()
+ * @timer: Per-step, per-register timer values staged by sysfs writes and
+ * flushed to hardware by tgu_enable()
+ * @counter: Per-step, per-register counter values staged by sysfs writes
+ * and flushed to hardware by tgu_enable()
* @num_reg: Number of priority registers implemented per priority level
* @num_step: Number of steps implemented
* @num_condition_decode: Maximum number of condition_decode
* @num_condition_select: Maximum number of condition_select
+ * @num_timer: Maximum number of timers
+ * @num_counter: Maximum number of counters
*
* This structure defines the data associated with a TGU device,
* including its base address, device pointer, the lock serialising
@@ -138,10 +166,14 @@ struct tgu_drvdata {
unsigned int priority[TGU_MAX_STEPS * MAX_PRIORITY * TGU_MAX_PRIORITY_REGS];
unsigned int condition_decode[TGU_MAX_STEPS * TGU_MAX_CONDITION_DECODE];
unsigned int condition_select[TGU_MAX_STEPS * TGU_MAX_CONDITION_SELECT];
+ unsigned int timer[TGU_MAX_STEPS * TGU_MAX_TIMER];
+ unsigned int counter[TGU_MAX_STEPS * TGU_MAX_COUNTER];
int num_reg;
int num_step;
int num_condition_decode;
int num_condition_select;
+ int num_timer;
+ int num_counter;
};
#endif
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v17 10/10] qcom-tgu: Add reset node to initialize
2026-11-13 2:49 [PATCH v17 00/10] Provide support for Trigger Generation Unit Songwei Chai
` (8 preceding siblings ...)
2026-11-13 2:49 ` [PATCH v17 09/10] qcom-tgu: Add timer/counter functionality for TGU Songwei Chai
@ 2026-11-13 2:49 ` Songwei Chai
9 siblings, 0 replies; 14+ messages in thread
From: Songwei Chai @ 2026-11-13 2:49 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, songchai, Jie Gan
Add reset node to initialize the value of
priority/condition_decode/condition_select/timer/counter nodes.
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 7 ++
drivers/hwtracing/qcom/tgu.c | 85 +++++++++++++++++++
2 files changed, 92 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index e0ea5419188e..c0f10678e4df 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -51,3 +51,10 @@ KernelVersion: 7.5
Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
Description:
(RW) Set/Get the counter value with specific step for TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/reset_tgu
+Date: November 2026
+KernelVersion: 7.5
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (Write) Write 1 to reset the dataset for TGU.
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
index 1cb131a3c521..2386c71ab8d1 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -251,6 +251,18 @@ static ssize_t tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
int i, j, k, index = 0;
CS_UNLOCK(drvdata->base);
+
+ /*
+ * Quiesce the detection engine before mutating any comparison input.
+ * This function is also reached from reset_tgu_store() while the TGU is
+ * already running (TGU_CONTROL == 1); rewriting the priority/condition/
+ * timer/counter registers register-by-register on a live engine could
+ * feed a transient, half-updated match into CTI/interrupt consumers.
+ * Programming with CONTROL == 0 and only re-enabling at the end keeps the
+ * program-then-enable ordering for every caller.
+ */
+ writel(0, drvdata->base + TGU_CONTROL);
+
for (i = 0; i < drvdata->num_step; i++) {
for (j = 0; j < MAX_PRIORITY; j++) {
for (k = 0; k < drvdata->num_reg; k++) {
@@ -470,8 +482,81 @@ static ssize_t enable_tgu_store(struct device *dev,
}
static DEVICE_ATTR_RW(enable_tgu);
+/*
+ * Element counts for drvdata's shadow tables, shared so the reset path
+ * below can't drift from how each array is actually sized/indexed.
+ */
+#define TGU_PRIORITY_COUNT(drvdata) \
+ (MAX_PRIORITY * (drvdata)->num_step * (drvdata)->num_reg)
+#define TGU_CONDITION_DECODE_COUNT(drvdata) \
+ ((drvdata)->num_step * (drvdata)->num_condition_decode)
+#define TGU_CONDITION_SELECT_COUNT(drvdata) \
+ ((drvdata)->num_step * (drvdata)->num_condition_select)
+#define TGU_TIMER_COUNT(drvdata) \
+ ((drvdata)->num_step * (drvdata)->num_timer)
+#define TGU_COUNTER_COUNT(drvdata) \
+ ((drvdata)->num_step * (drvdata)->num_counter)
+
+static ssize_t reset_tgu_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t size)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ unsigned long value;
+ int i, ret;
+
+ if (kstrtoul(buf, 0, &value) || value != 1)
+ return -EINVAL;
+
+ /*
+ * Hold the mutex across the whole sequence so it cannot race with
+ * enable_tgu_store()/tgu_dataset_store(): the enabled check and the
+ * hardware access stay consistent. No PM reference is taken or dropped
+ * here - the shadow tables are plain memory, and the hardware is only
+ * touched when the device is already resumed via the enable reference.
+ */
+ guard(mutex)(&drvdata->lock);
+
+ memset(drvdata->priority, 0,
+ TGU_PRIORITY_COUNT(drvdata) * sizeof(*drvdata->priority));
+
+ /*
+ * Every decode register is about to be overwritten with the neutral
+ * 'NOT' value below, so there is nothing for a prior memset to zero;
+ * calculate_array_location()'s step/reg indices for TGU_CONDITION_DECODE
+ * densely cover the whole 0..TGU_CONDITION_DECODE_COUNT(drvdata)-1 range
+ * with no gaps, so a flat loop over that range reaches every decode
+ * register.
+ */
+ for (i = 0; i < TGU_CONDITION_DECODE_COUNT(drvdata); i++)
+ drvdata->condition_decode[i] = TGU_CONDITION_DECODE_NOT;
+
+ memset(drvdata->condition_select, 0,
+ TGU_CONDITION_SELECT_COUNT(drvdata) *
+ sizeof(*drvdata->condition_select));
+
+ memset(drvdata->timer, 0,
+ TGU_TIMER_COUNT(drvdata) * sizeof(*drvdata->timer));
+
+ memset(drvdata->counter, 0,
+ TGU_COUNTER_COUNT(drvdata) * sizeof(*drvdata->counter));
+
+ /* If the TGU is running, apply the cleared configuration immediately. */
+ if (drvdata->enabled) {
+ ret = tgu_write_all_hw_regs(drvdata);
+ if (ret)
+ return ret;
+ }
+
+ dev_dbg(dev, "Qualcomm-TGU reset complete\n");
+
+ return size;
+}
+static DEVICE_ATTR_WO(reset_tgu);
+
static struct attribute *tgu_common_attrs[] = {
&dev_attr_enable_tgu.attr,
+ &dev_attr_reset_tgu.attr,
NULL,
};
--
2.34.1
^ permalink raw reply [flat|nested] 14+ messages in thread