* [PATCH v13 6/7] qcom-tgu: Add timer/counter functionality for TGU
From: Songwei Chai @ 2026-04-02 9:28 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, Jie Gan
In-Reply-To: <20260402092838.341295-1-songwei.chai@oss.qualcomm.com>
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 | 116 +++++++++++++++++-
drivers/hwtracing/qcom/tgu.h | 56 +++++++++
3 files changed, 184 insertions(+), 2 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index 786cb852bbe5..7a3573e03e27 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -28,3 +28,17 @@ KernelVersion: 7.1
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]_timer/reg[0:1]
+Date: April 2026
+KernelVersion: 7.1
+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: April 2026
+KernelVersion: 7.1
+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 4112e6a691d6..4a529520b428 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -32,6 +32,10 @@ static int calculate_array_location(struct tgu_drvdata *drvdata,
case TGU_CONDITION_SELECT:
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;
}
@@ -77,6 +81,12 @@ static ssize_t tgu_dataset_show(struct device *dev,
case TGU_CONDITION_SELECT:
return sysfs_emit(buf, "0x%x\n",
drvdata->value_table->condition_select[index]);
+ case TGU_TIMER:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->value_table->timer[index]);
+ case TGU_COUNTER:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->value_table->counter[index]);
default:
break;
}
@@ -122,6 +132,14 @@ static ssize_t tgu_dataset_store(struct device *dev,
tgu_drvdata->value_table->condition_select[index] = val;
ret = size;
break;
+ case TGU_TIMER:
+ tgu_drvdata->value_table->timer[index] = val;
+ ret = size;
+ break;
+ case TGU_COUNTER:
+ tgu_drvdata->value_table->counter[index] = val;
+ ret = size;
+ break;
default:
ret = -EINVAL;
break;
@@ -163,6 +181,18 @@ static umode_t tgu_node_visible(struct kobject *kobject,
if (tgu_attr->reg_num < drvdata->num_condition_select)
return attr->mode;
break;
+ case TGU_COUNTER:
+ if (!drvdata->num_counter)
+ break;
+ if (tgu_attr->reg_num < drvdata->num_counter)
+ return attr->mode;
+ break;
+ case TGU_TIMER:
+ if (!drvdata->num_timer)
+ break;
+ if (tgu_attr->reg_num < drvdata->num_timer)
+ return attr->mode;
+ break;
default:
break;
}
@@ -213,6 +243,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->value_table->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->value_table->counter[index],
+ drvdata->base + COUNTER_COMPARE_STEP(i, j));
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
exit:
@@ -256,6 +310,27 @@ static void tgu_set_conditions(struct tgu_drvdata *drvdata)
drvdata->num_condition_select = TGU_DEVID_CONDITIONS(devid) + 1;
}
+static void tgu_set_timer_counter(struct tgu_drvdata *drvdata)
+{
+ int num_timers = 0, num_counters = 0;
+ u32 devid2;
+
+ 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 device *dev)
{
struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
@@ -405,6 +480,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,
};
@@ -412,8 +503,8 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device *dev = &adev->dev;
struct tgu_drvdata *drvdata;
- unsigned int *priority, *condition, *select;
- size_t priority_size, condition_size, select_size;
+ unsigned int *priority, *condition, *select, *timer, *counter;
+ size_t priority_size, condition_size, select_size, timer_size, counter_size;
int ret;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
@@ -432,6 +523,7 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
tgu_set_reg_number(drvdata);
tgu_set_steps(drvdata);
tgu_set_conditions(drvdata);
+ tgu_set_timer_counter(drvdata);
ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
if (ret) {
@@ -474,6 +566,26 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
drvdata->value_table->condition_select = select;
+ timer_size = drvdata->num_step * drvdata->num_timer;
+
+ timer = devm_kcalloc(dev, timer_size,
+ sizeof(*(drvdata->value_table->timer)),
+ GFP_KERNEL);
+ if (!timer)
+ return -ENOMEM;
+
+ drvdata->value_table->timer = timer;
+
+ counter_size = drvdata->num_step * drvdata->num_counter;
+
+ counter = devm_kcalloc(dev, counter_size,
+ sizeof(*(drvdata->value_table->counter)),
+ GFP_KERNEL);
+ if (!counter)
+ return -ENOMEM;
+
+ drvdata->value_table->counter = counter;
+
drvdata->enabled = false;
pm_runtime_put(&adev->dev);
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index ac46a2875209..5dfef0afbad6 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -11,6 +11,7 @@
#define TGU_LAR 0xfb0
#define TGU_UNLOCK_OFFSET 0xc5acce55
#define TGU_DEVID 0xfc8
+#define CORESIGHT_DEVID2 0xfc0
#define TGU_DEVID_SENSE_INPUT(devid_val) \
((int)FIELD_GET(GENMASK(17, 10), devid_val))
@@ -18,6 +19,16 @@
((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, 13), 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
@@ -53,6 +64,8 @@
#define PRIORITY_START_OFFSET 0x0074
#define CONDITION_DECODE_OFFSET 0x0050
#define CONDITION_SELECT_OFFSET 0x0060
+#define TIMER_START_OFFSET 0x0040
+#define COUNTER_START_OFFSET 0x0048
#define PRIORITY_OFFSET 0x60
#define REG_OFFSET 0x4
@@ -67,6 +80,12 @@
#define CONDITION_SELECT_STEP(step, select) \
(CONDITION_SELECT_OFFSET + REG_OFFSET * select + STEP_OFFSET * step)
+#define TIMER_COMPARE_STEP(step, timer) \
+ (TIMER_START_OFFSET + REG_OFFSET * timer + STEP_OFFSET * step)
+
+#define COUNTER_COMPARE_STEP(step, counter) \
+ (COUNTER_START_OFFSET + REG_OFFSET * counter + STEP_OFFSET * step)
+
#define tgu_dataset_rw(name, step_index, type, reg_num) \
(&((struct tgu_attribute[]){ { \
__ATTR(name, 0644, tgu_dataset_show, tgu_dataset_store), \
@@ -82,6 +101,10 @@
tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_DECODE, reg_num)
#define STEP_SELECT(step_index, reg_num) \
tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_SELECT, reg_num)
+#define STEP_TIMER(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_TIMER, reg_num)
+#define STEP_COUNTER(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_COUNTER, reg_num)
#define STEP_PRIORITY_LIST(step_index, priority) \
{STEP_PRIORITY(step_index, 0, priority), \
@@ -122,6 +145,18 @@
NULL \
}
+#define STEP_TIMER_LIST(n) \
+ {STEP_TIMER(n, 0), \
+ STEP_TIMER(n, 1), \
+ NULL \
+ }
+
+#define STEP_COUNTER_LIST(n) \
+ {STEP_COUNTER(n, 0), \
+ STEP_COUNTER(n, 1), \
+ NULL \
+ }
+
#define PRIORITY_ATTRIBUTE_GROUP_INIT(step, priority)\
(&(const struct attribute_group){\
.attrs = (struct attribute*[])STEP_PRIORITY_LIST(step, priority),\
@@ -143,6 +178,19 @@
.name = "step" #step "_condition_select" \
})
+#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 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" \
+ })
enum operation_index {
TGU_PRIORITY0,
@@ -151,6 +199,8 @@ enum operation_index {
TGU_PRIORITY3,
TGU_CONDITION_DECODE,
TGU_CONDITION_SELECT,
+ TGU_TIMER,
+ TGU_COUNTER
};
/* Maximum priority that TGU supports */
@@ -167,6 +217,8 @@ struct value_table {
unsigned int *priority;
unsigned int *condition_decode;
unsigned int *condition_select;
+ unsigned int *timer;
+ unsigned int *counter;
};
static inline void TGU_LOCK(void __iomem *addr)
@@ -198,6 +250,8 @@ static inline void TGU_UNLOCK(void __iomem *addr)
* @num_step: Maximum step size
* @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 pointers, clock, spinlock for
@@ -214,6 +268,8 @@ struct tgu_drvdata {
int num_step;
int num_condition_decode;
int num_condition_select;
+ int num_timer;
+ int num_counter;
};
#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v13 5/7] qcom-tgu: Add support to configure next action
From: Songwei Chai @ 2026-04-02 9:28 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, Jie Gan
In-Reply-To: <20260402092838.341295-1-songwei.chai@oss.qualcomm.com>
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 | 7 +++
drivers/hwtracing/qcom/tgu.c | 53 ++++++++++++++++++-
drivers/hwtracing/qcom/tgu.h | 27 ++++++++++
3 files changed, 85 insertions(+), 2 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index 4ef0d696d3d0..786cb852bbe5 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -21,3 +21,10 @@ KernelVersion: 7.1
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: April 2026
+KernelVersion: 7.1
+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.
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
index 5b37eb10f863..4112e6a691d6 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -29,6 +29,9 @@ 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:
+ return step_index * (drvdata->num_condition_select) +
+ reg_index;
default:
break;
}
@@ -71,6 +74,9 @@ static ssize_t tgu_dataset_show(struct device *dev,
case TGU_CONDITION_DECODE:
return sysfs_emit(buf, "0x%x\n",
drvdata->value_table->condition_decode[index]);
+ case TGU_CONDITION_SELECT:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->value_table->condition_select[index]);
default:
break;
}
@@ -112,6 +118,10 @@ static ssize_t tgu_dataset_store(struct device *dev,
tgu_drvdata->value_table->condition_decode[index] = val;
ret = size;
break;
+ case TGU_CONDITION_SELECT:
+ tgu_drvdata->value_table->condition_select[index] = val;
+ ret = size;
+ break;
default:
ret = -EINVAL;
break;
@@ -146,6 +156,13 @@ 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:
+ /* 'default' register is at the end of 'select' region */
+ if (tgu_attr->reg_num == drvdata->num_condition_select - 1)
+ attr->name = "default";
+ if (tgu_attr->reg_num < drvdata->num_condition_select)
+ return attr->mode;
+ break;
default:
break;
}
@@ -184,6 +201,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->value_table->condition_select[index],
+ drvdata->base + CONDITION_SELECT_STEP(i, j));
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
exit:
@@ -223,6 +252,8 @@ static void tgu_set_conditions(struct tgu_drvdata *drvdata)
devid = readl(drvdata->base + TGU_DEVID);
drvdata->num_condition_decode = TGU_DEVID_CONDITIONS(devid);
+ /* select region has an additional 'default' register */
+ drvdata->num_condition_select = TGU_DEVID_CONDITIONS(devid) + 1;
}
static int tgu_enable(struct device *dev)
@@ -366,6 +397,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,
};
@@ -373,8 +412,8 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device *dev = &adev->dev;
struct tgu_drvdata *drvdata;
- unsigned int *priority, *condition;
- size_t priority_size, condition_size;
+ unsigned int *priority, *condition, *select;
+ size_t priority_size, condition_size, select_size;
int ret;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
@@ -425,6 +464,16 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
drvdata->value_table->condition_decode = condition;
+ select_size = drvdata->num_condition_select * drvdata->num_step;
+
+ select = devm_kcalloc(dev, select_size,
+ sizeof(*(drvdata->value_table->condition_select)),
+ GFP_KERNEL);
+ if (!select)
+ return -ENOMEM;
+
+ drvdata->value_table->condition_select = select;
+
drvdata->enabled = false;
pm_runtime_put(&adev->dev);
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index 987ea07bd618..ac46a2875209 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -52,6 +52,7 @@
#define STEP_OFFSET 0x1D8
#define PRIORITY_START_OFFSET 0x0074
#define CONDITION_DECODE_OFFSET 0x0050
+#define CONDITION_SELECT_OFFSET 0x0060
#define PRIORITY_OFFSET 0x60
#define REG_OFFSET 0x4
@@ -63,6 +64,9 @@
#define CONDITION_DECODE_STEP(step, decode) \
(CONDITION_DECODE_OFFSET + REG_OFFSET * decode + STEP_OFFSET * step)
+#define CONDITION_SELECT_STEP(step, select) \
+ (CONDITION_SELECT_OFFSET + REG_OFFSET * select + STEP_OFFSET * step)
+
#define tgu_dataset_rw(name, step_index, type, reg_num) \
(&((struct tgu_attribute[]){ { \
__ATTR(name, 0644, tgu_dataset_show, tgu_dataset_store), \
@@ -76,6 +80,8 @@
reg_num)
#define STEP_DECODE(step_index, reg_num) \
tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_DECODE, reg_num)
+#define STEP_SELECT(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_SELECT, reg_num)
#define STEP_PRIORITY_LIST(step_index, priority) \
{STEP_PRIORITY(step_index, 0, priority), \
@@ -107,6 +113,15 @@
NULL \
}
+#define STEP_SELECT_LIST(n) \
+ {STEP_SELECT(n, 0), \
+ STEP_SELECT(n, 1), \
+ STEP_SELECT(n, 2), \
+ STEP_SELECT(n, 3), \
+ STEP_SELECT(n, 4), \
+ NULL \
+ }
+
#define PRIORITY_ATTRIBUTE_GROUP_INIT(step, priority)\
(&(const struct attribute_group){\
.attrs = (struct attribute*[])STEP_PRIORITY_LIST(step, priority),\
@@ -121,12 +136,21 @@
.name = "step" #step "_condition_decode" \
})
+#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" \
+ })
+
+
enum operation_index {
TGU_PRIORITY0,
TGU_PRIORITY1,
TGU_PRIORITY2,
TGU_PRIORITY3,
TGU_CONDITION_DECODE,
+ TGU_CONDITION_SELECT,
};
/* Maximum priority that TGU supports */
@@ -142,6 +166,7 @@ struct tgu_attribute {
struct value_table {
unsigned int *priority;
unsigned int *condition_decode;
+ unsigned int *condition_select;
};
static inline void TGU_LOCK(void __iomem *addr)
@@ -172,6 +197,7 @@ static inline void TGU_UNLOCK(void __iomem *addr)
* @num_reg: Maximum number of registers
* @num_step: Maximum step size
* @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 pointers, clock, spinlock for
@@ -187,6 +213,7 @@ struct tgu_drvdata {
int num_reg;
int num_step;
int num_condition_decode;
+ int num_condition_select;
};
#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v3 07/11] drm/bridge: dw-hdmi: move next_bridge lookup to attach time
From: Luca Ceresoli @ 2026-04-02 9:26 UTC (permalink / raw)
To: Marek Vasut, Stefan Agner, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Liu Ying, Rob Herring,
Saravana Kannan
Cc: Damon Ding, Kory Maincent (TI.com), Hervé Codina, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, imx, linux-arm-kernel,
linux-kernel, devicetree, Adam Ford, Alexander Stein,
Christopher Obbard, Daniel Scally, Emanuele Ghidoli,
Fabio Estevam, Francesco Dolcini, Frieder Schrempf, Gilles Talis,
Goran Rađenović, Heiko Schocher, Josua Mayer,
Kieran Bingham, Marco Felsch, Martyn Welch, Oleksij Rempel,
Peng Fan, Richard Hu, Shengjiu Wang, Stefan Eichenberger,
Vitor Soares, Luca Ceresoli
In-Reply-To: <20260402-drm-lcdif-dbanc-v3-0-27cd247a0847@bootlin.com>
This driver looks up the next_bridge at probe time and stores it in
hdmi->bridge.next_bridge, but only uses the stored value when attaching,
and only in the DRM_BRIDGE_ATTACH_NO_CONNECTOR case.
This will be problematic with an upcoming change, adding an hdmi-connector
using a device tree overlay when not present. That change is in turn
necessary to migrate the i.MX LCDIF driver to the bridge-connector.
The problem is that, adding the hdmi-connector via an overlay, devlink
considers hdmi-connector a consumer of the dw-hdmi device, generating a
chicken-egg problem:
* hdmi-connector probe won't be tried until dw-hdmi is probed (devlink)
* dw-hdmi probe will defer until it finds the next_bridge (the
hdmi-connector wrapper bridge)
In preparation for those changes, move the next_bridge lookup from probe to
attach, when it is actually used. This allows dw-hdmi to probe, so that the
hdmi-connector can probe as well.
Also avoid storing the pointer in hdmi->bridge.next_bridge: the value is
computed when needed, thus a local variable is enough.
Finally, this also allows to slightly improve the code by not doing any DT
lookup in the !DRM_BRIDGE_ATTACH_NO_CONNECTOR case.
Tested-by: Martyn Welch <martyn.welch@collabora.com>
Tested-by: Alexander Stein <alexander.stein@ew.tq-group.com> # TQMa8MPxL/MBa8MPxL
Acked-by: Liu Ying <victor.liu@nxp.com>
Tested-by: Damon Ding <damon.ding@rock-chips.com> # rk3399
Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
Changes in v2:
- Fix returned error codes
- Added missing cleanup.h include
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 45 +++++++++++--------------------
1 file changed, 16 insertions(+), 29 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index ab1a6a8783cd..f4a1ebb79716 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -6,6 +6,8 @@
* Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
* Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
*/
+
+#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
@@ -2914,9 +2916,20 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
if (WARN_ON((flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) && !hdmi->plat_data->output_port))
return -EINVAL;
- if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
- return drm_bridge_attach(encoder, hdmi->bridge.next_bridge,
- bridge, flags);
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ struct device_node *remote __free(device_node) =
+ of_graph_get_remote_node(hdmi->dev->of_node,
+ hdmi->plat_data->output_port, -1);
+ if (!remote)
+ return -ENODEV;
+
+ struct drm_bridge *next_bridge __free(drm_bridge_put) =
+ of_drm_find_and_get_bridge(remote);
+ if (!next_bridge)
+ return -EPROBE_DEFER;
+
+ return drm_bridge_attach(encoder, next_bridge, bridge, flags);
+ }
return dw_hdmi_connector_create(hdmi);
}
@@ -3307,28 +3320,6 @@ static void dw_hdmi_init_hw(struct dw_hdmi *hdmi)
* Probe/remove API, used from platforms based on the DRM bridge API.
*/
-static int dw_hdmi_parse_dt(struct dw_hdmi *hdmi)
-{
- struct device_node *remote;
-
- if (!hdmi->plat_data->output_port)
- return 0;
-
-
- remote = of_graph_get_remote_node(hdmi->dev->of_node,
- hdmi->plat_data->output_port,
- -1);
- if (!remote)
- return -ENODEV;
-
- hdmi->bridge.next_bridge = of_drm_find_and_get_bridge(remote);
- of_node_put(remote);
- if (!hdmi->bridge.next_bridge)
- return -EPROBE_DEFER;
-
- return 0;
-}
-
bool dw_hdmi_bus_fmt_is_420(struct dw_hdmi *hdmi)
{
return hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format);
@@ -3373,10 +3364,6 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
mutex_init(&hdmi->cec_notifier_mutex);
spin_lock_init(&hdmi->audio_lock);
- ret = dw_hdmi_parse_dt(hdmi);
- if (ret < 0)
- return ERR_PTR(ret);
-
ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0);
if (ddc_node) {
hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node);
--
2.53.0
^ permalink raw reply related
* [PATCH v13 4/7] qcom-tgu: Add TGU decode support
From: Songwei Chai @ 2026-04-02 9:28 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, Jie Gan
In-Reply-To: <20260402092838.341295-1-songwei.chai@oss.qualcomm.com>
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 | 157 +++++++++++++++---
drivers/hwtracing/qcom/tgu.h | 27 +++
3 files changed, 170 insertions(+), 21 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index 223873789ca6..4ef0d696d3d0 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.1
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: April 2026
+KernelVersion: 7.1
+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 7d69986c3e3d..5b37eb10f863 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -18,8 +18,33 @@ static int calculate_array_location(struct tgu_drvdata *drvdata,
int step_index, int operation_index,
int reg_index)
{
- return operation_index * (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 arrary location - Fail\n");
+
+ return result;
}
static ssize_t tgu_dataset_show(struct device *dev,
@@ -30,12 +55,26 @@ 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->operation_index,
- tgu_attr->reg_num);
-
- return sysfs_emit(buf, "0x%x\n",
- drvdata->value_table->priority[index]);
+ index = check_array_location(drvdata, tgu_attr->step_index,
+ tgu_attr->operation_index, tgu_attr->reg_num);
+
+ if (index == -EINVAL)
+ return 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->value_table->priority[index]);
+ case TGU_CONDITION_DECODE:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->value_table->condition_decode[index]);
+ default:
+ break;
+ }
+ return -EINVAL;
}
static ssize_t tgu_dataset_store(struct device *dev,
@@ -54,13 +93,31 @@ static ssize_t tgu_dataset_store(struct device *dev,
return ret;
guard(spinlock)(&tgu_drvdata->lock);
- index = calculate_array_location(tgu_drvdata, tgu_attr->step_index,
+ index = check_array_location(tgu_drvdata, tgu_attr->step_index,
tgu_attr->operation_index,
tgu_attr->reg_num);
- tgu_drvdata->value_table->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->value_table->priority[index] = val;
+ ret = size;
+ break;
+ case TGU_CONDITION_DECODE:
+ tgu_drvdata->value_table->condition_decode[index] = val;
+ ret = size;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
- return size;
+ return ret;
}
static umode_t tgu_node_visible(struct kobject *kobject,
@@ -77,13 +134,26 @@ 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 (tgu_attr->reg_num < drvdata->num_reg)
+ return attr->mode;
+ break;
+ case TGU_CONDITION_DECODE:
+ 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;
@@ -91,8 +161,10 @@ static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
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->value_table->priority[index],
drvdata->base +
@@ -100,9 +172,23 @@ 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->value_table->condition_decode[index],
+ drvdata->base + CONDITION_DECODE_STEP(i, j));
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
+exit:
TGU_LOCK(drvdata->base);
+ return index >= 0 ? 0 : -EINVAL;
}
static void tgu_set_reg_number(struct tgu_drvdata *drvdata)
@@ -131,16 +217,26 @@ static void tgu_set_steps(struct tgu_drvdata *drvdata)
drvdata->num_step = TGU_DEVID_STEPS(devid);
}
+static void tgu_set_conditions(struct tgu_drvdata *drvdata)
+{
+ u32 devid;
+
+ devid = readl(drvdata->base + TGU_DEVID);
+ drvdata->num_condition_decode = TGU_DEVID_CONDITIONS(devid);
+}
+
static int tgu_enable(struct device *dev)
{
struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret;
guard(spinlock)(&drvdata->lock);
- drvdata->enabled = true;
- tgu_write_all_hw_regs(drvdata);
+ ret = tgu_write_all_hw_regs(drvdata);
+ if (!ret)
+ drvdata->enabled = true;
- return 0;
+ return ret;
}
static void tgu_do_disable(struct tgu_drvdata *drvdata)
@@ -262,6 +358,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,
};
@@ -269,8 +373,8 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device *dev = &adev->dev;
struct tgu_drvdata *drvdata;
- unsigned int *priority;
- size_t priority_size;
+ unsigned int *priority, *condition;
+ size_t priority_size, condition_size;
int ret;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
@@ -288,6 +392,7 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
tgu_set_reg_number(drvdata);
tgu_set_steps(drvdata);
+ tgu_set_conditions(drvdata);
ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
if (ret) {
@@ -310,6 +415,16 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
drvdata->value_table->priority = priority;
+ condition_size = drvdata->num_condition_decode * drvdata->num_step;
+
+ condition = devm_kcalloc(dev, condition_size,
+ sizeof(*(drvdata->value_table->condition_decode)),
+ GFP_KERNEL);
+ if (!condition)
+ return -ENOMEM;
+
+ drvdata->value_table->condition_decode = condition;
+
drvdata->enabled = false;
pm_runtime_put(&adev->dev);
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index df570c89ffd7..987ea07bd618 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -16,6 +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
@@ -49,6 +51,7 @@
*/
#define STEP_OFFSET 0x1D8
#define PRIORITY_START_OFFSET 0x0074
+#define CONDITION_DECODE_OFFSET 0x0050
#define PRIORITY_OFFSET 0x60
#define REG_OFFSET 0x4
@@ -57,6 +60,9 @@
(PRIORITY_START_OFFSET + PRIORITY_OFFSET * priority +\
REG_OFFSET * reg + STEP_OFFSET * step)
+#define CONDITION_DECODE_STEP(step, decode) \
+ (CONDITION_DECODE_OFFSET + REG_OFFSET * decode + STEP_OFFSET * step)
+
#define tgu_dataset_rw(name, step_index, type, reg_num) \
(&((struct tgu_attribute[]){ { \
__ATTR(name, 0644, tgu_dataset_show, tgu_dataset_store), \
@@ -68,6 +74,8 @@
#define STEP_PRIORITY(step_index, reg_num, priority) \
tgu_dataset_rw(reg##reg_num, step_index, TGU_PRIORITY##priority, \
reg_num)
+#define STEP_DECODE(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_DECODE, reg_num)
#define STEP_PRIORITY_LIST(step_index, priority) \
{STEP_PRIORITY(step_index, 0, priority), \
@@ -91,6 +99,14 @@
NULL \
}
+#define STEP_DECODE_LIST(n) \
+ {STEP_DECODE(n, 0), \
+ STEP_DECODE(n, 1), \
+ STEP_DECODE(n, 2), \
+ STEP_DECODE(n, 3), \
+ NULL \
+ }
+
#define PRIORITY_ATTRIBUTE_GROUP_INIT(step, priority)\
(&(const struct attribute_group){\
.attrs = (struct attribute*[])STEP_PRIORITY_LIST(step, priority),\
@@ -98,11 +114,19 @@
.name = "step" #step "_priority" #priority \
})
+#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" \
+ })
+
enum operation_index {
TGU_PRIORITY0,
TGU_PRIORITY1,
TGU_PRIORITY2,
TGU_PRIORITY3,
+ TGU_CONDITION_DECODE,
};
/* Maximum priority that TGU supports */
@@ -117,6 +141,7 @@ struct tgu_attribute {
struct value_table {
unsigned int *priority;
+ unsigned int *condition_decode;
};
static inline void TGU_LOCK(void __iomem *addr)
@@ -146,6 +171,7 @@ static inline void TGU_UNLOCK(void __iomem *addr)
* @value_table: Store given value based on relevant parameters
* @num_reg: Maximum number of registers
* @num_step: Maximum step size
+ * @num_condition_decode: Maximum number of condition_decode
*
* This structure defines the data associated with a TGU device,
* including its base address, device pointers, clock, spinlock for
@@ -160,6 +186,7 @@ struct tgu_drvdata {
struct value_table *value_table;
int num_reg;
int num_step;
+ int num_condition_decode;
};
#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v13 3/7] qcom-tgu: Add signal priority support
From: Songwei Chai @ 2026-04-02 9:28 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, Jie Gan
In-Reply-To: <20260402092838.341295-1-songwei.chai@oss.qualcomm.com>
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 | 161 ++++++++++++++++++
drivers/hwtracing/qcom/tgu.h | 114 +++++++++++++
3 files changed, 282 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index f877a00fcaa5..223873789ca6 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:17]
+Date: April 2026
+KernelVersion: 7.1
+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 49c8f710b931..7d69986c3e3d 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -14,14 +14,123 @@
#include "tgu.h"
+static int calculate_array_location(struct tgu_drvdata *drvdata,
+ int step_index, int operation_index,
+ int reg_index)
+{
+ return operation_index * (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->operation_index,
+ tgu_attr->reg_num);
+
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->value_table->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(spinlock)(&tgu_drvdata->lock);
+ index = calculate_array_location(tgu_drvdata, tgu_attr->step_index,
+ tgu_attr->operation_index,
+ tgu_attr->reg_num);
+
+ tgu_drvdata->value_table->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;
+
TGU_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->value_table->priority[index],
+ drvdata->base +
+ PRIORITY_REG_STEP(i, j, k));
+ }
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
TGU_LOCK(drvdata->base);
}
+static void tgu_set_reg_number(struct tgu_drvdata *drvdata)
+{
+ int num_sense_input;
+ int num_reg;
+ u32 devid;
+
+ devid = readl(drvdata->base + TGU_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++;
+
+ drvdata->num_reg = num_reg;
+}
+
+static void tgu_set_steps(struct tgu_drvdata *drvdata)
+{
+ u32 devid;
+
+ devid = readl(drvdata->base + TGU_DEVID);
+
+ drvdata->num_step = TGU_DEVID_STEPS(devid);
+}
+
static int tgu_enable(struct device *dev)
{
struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
@@ -121,6 +230,38 @@ static const struct attribute_group tgu_common_grp = {
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,
};
@@ -128,6 +269,8 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device *dev = &adev->dev;
struct tgu_drvdata *drvdata;
+ unsigned int *priority;
+ size_t priority_size;
int ret;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
@@ -143,12 +286,30 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
spin_lock_init(&drvdata->lock);
+ tgu_set_reg_number(drvdata);
+ tgu_set_steps(drvdata);
+
ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
if (ret) {
dev_err(dev, "failed to create sysfs groups: %d\n", ret);
return ret;
}
+ drvdata->value_table =
+ devm_kzalloc(dev, sizeof(*drvdata->value_table), GFP_KERNEL);
+ if (!drvdata->value_table)
+ return -ENOMEM;
+
+ priority_size = MAX_PRIORITY * drvdata->num_reg * drvdata->num_step;
+
+ priority = devm_kcalloc(dev, priority_size,
+ sizeof(*drvdata->value_table->priority),
+ GFP_KERNEL);
+ if (!priority)
+ return -ENOMEM;
+
+ drvdata->value_table->priority = priority;
+
drvdata->enabled = false;
pm_runtime_put(&adev->dev);
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index dd7533b9d735..df570c89ffd7 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -10,6 +10,114 @@
#define TGU_CONTROL 0x0000
#define TGU_LAR 0xfb0
#define TGU_UNLOCK_OFFSET 0xc5acce55
+#define TGU_DEVID 0xfc8
+
+#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 Step configuration
+ * offset table space layout
+ * x-------------------------x$ x-------------x$
+ * | |$ | |$
+ * | | | reserve |$
+ * | | | |$
+ * |coresight management | |-------------|base+n*0x1D8+0x1F4$
+ * | registe | |---> |prioroty[3] |$
+ * | | | |-------------|base+n*0x1D8+0x194$
+ * | | | |prioroty[2] |$
+ * |-------------------------| | |-------------|base+n*0x1D8+0x134$
+ * | | | |prioroty[1] |$
+ * | step[7] | | |-------------|base+n*0x1D8+0xD4$
+ * |-------------------------|->base+0x40+7*0x1D8 | |prioroty[0] |$
+ * | | | |-------------|base+n*0x1D8+0x74$
+ * | ... | | | condition |$
+ * | | | | select |$
+ * |-------------------------|->base+0x40+1*0x1D8 | |-------------|base+n*0x1D8+0x60$
+ * | | | | condition |$
+ * | step[0] |--------------------> | decode |$
+ * |-------------------------|-> base+0x40 |-------------|base+n*0x1D8+0x50$
+ * | | | |$
+ * | Control and status space| |Timer/Counter|$
+ * | space | | |$
+ * x-------------------------x->base x-------------x base+n*0x1D8+0x40$
+ *
+ */
+#define STEP_OFFSET 0x1D8
+#define PRIORITY_START_OFFSET 0x0074
+#define PRIORITY_OFFSET 0x60
+#define REG_OFFSET 0x4
+
+/* Calculate compare step addresses */
+#define PRIORITY_REG_STEP(step, priority, reg)\
+ (PRIORITY_START_OFFSET + PRIORITY_OFFSET * priority +\
+ REG_OFFSET * reg + STEP_OFFSET * step)
+
+#define tgu_dataset_rw(name, step_index, type, reg_num) \
+ (&((struct tgu_attribute[]){ { \
+ __ATTR(name, 0644, tgu_dataset_show, tgu_dataset_store), \
+ step_index, \
+ type, \
+ reg_num, \
+ } })[0].attr.attr)
+
+#define STEP_PRIORITY(step_index, reg_num, priority) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_PRIORITY##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), \
+ 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 \
+ })
+
+enum operation_index {
+ TGU_PRIORITY0,
+ TGU_PRIORITY1,
+ TGU_PRIORITY2,
+ TGU_PRIORITY3,
+};
+
+/* Maximum priority that TGU supports */
+#define MAX_PRIORITY 4
+
+struct tgu_attribute {
+ struct device_attribute attr;
+ u32 step_index;
+ enum operation_index operation_index;
+ u32 reg_num;
+};
+
+struct value_table {
+ unsigned int *priority;
+};
static inline void TGU_LOCK(void __iomem *addr)
{
@@ -35,6 +143,9 @@ static inline void TGU_UNLOCK(void __iomem *addr)
* @dev: Pointer to the associated device structure
* @lock: Spinlock for handling concurrent access to private data
* @enabled: Flag indicating whether the TGU device is enabled
+ * @value_table: Store given value based on relevant parameters
+ * @num_reg: Maximum number of registers
+ * @num_step: Maximum step size
*
* This structure defines the data associated with a TGU device,
* including its base address, device pointers, clock, spinlock for
@@ -46,6 +157,9 @@ struct tgu_drvdata {
struct device *dev;
spinlock_t lock;
bool enabled;
+ struct value_table *value_table;
+ int num_reg;
+ int num_step;
};
#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v13 2/7] qcom-tgu: Add TGU driver
From: Songwei Chai @ 2026-04-02 9:28 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
In-Reply-To: <20260402092838.341295-1-songwei.chai@oss.qualcomm.com>
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.
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 9 +
drivers/Makefile | 1 +
drivers/hwtracing/Kconfig | 2 +
drivers/hwtracing/qcom/Kconfig | 18 ++
drivers/hwtracing/qcom/Makefile | 3 +
drivers/hwtracing/qcom/tgu.c | 193 ++++++++++++++++++
drivers/hwtracing/qcom/tgu.h | 51 +++++
7 files changed, 277 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..f877a00fcaa5
--- /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: April 2026
+KernelVersion: 7.1
+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/drivers/Makefile b/drivers/Makefile
index 53fbd2e0acdd..82b712a12a26 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -177,6 +177,7 @@ obj-$(CONFIG_RAS) += ras/
obj-$(CONFIG_USB4) += thunderbolt/
obj-$(CONFIG_CORESIGHT) += hwtracing/coresight/
obj-y += hwtracing/intel_th/
+obj-y += 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..d6f6d4b0f28e
--- /dev/null
+++ b/drivers/hwtracing/qcom/Kconfig
@@ -0,0 +1,18 @@
+# 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"
+ 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..49c8f710b931
--- /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/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_runtime.h>
+
+#include "tgu.h"
+
+static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
+{
+ TGU_UNLOCK(drvdata->base);
+ /* Enable TGU to program the triggers */
+ writel(1, drvdata->base + TGU_CONTROL);
+ TGU_LOCK(drvdata->base);
+}
+
+static int tgu_enable(struct device *dev)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+
+ guard(spinlock)(&drvdata->lock);
+ drvdata->enabled = true;
+
+ tgu_write_all_hw_regs(drvdata);
+
+ return 0;
+}
+
+static void tgu_do_disable(struct tgu_drvdata *drvdata)
+{
+ TGU_UNLOCK(drvdata->base);
+ writel(0, drvdata->base + TGU_CONTROL);
+ TGU_LOCK(drvdata->base);
+
+ drvdata->enabled = false;
+}
+
+static void tgu_disable(struct device *dev)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+
+ guard(spinlock)(&drvdata->lock);
+ if (!drvdata->enabled)
+ return;
+
+ tgu_do_disable(drvdata);
+}
+
+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;
+
+ guard(spinlock)(&drvdata->lock);
+ enabled = drvdata->enabled;
+
+ return sysfs_emit(buf, "%d\n", !!enabled);
+}
+
+/* enable_tgu_store - Configure Trace and Gating Unit (TGU) triggers. */
+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;
+
+ if (val) {
+ scoped_guard(spinlock, &drvdata->lock) {
+ if (drvdata->enabled)
+ return -EBUSY;
+ }
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = tgu_enable(dev);
+ if (ret) {
+ pm_runtime_put(dev);
+ return ret;
+ }
+ } else {
+ scoped_guard(spinlock, &drvdata->lock) {
+ if (!drvdata->enabled)
+ return -EINVAL;
+ }
+
+ tgu_disable(dev);
+ 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,
+ NULL,
+};
+
+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);
+
+ spin_lock_init(&drvdata->lock);
+
+ ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
+ if (ret) {
+ dev_err(dev, "failed to create sysfs groups: %d\n", ret);
+ return ret;
+ }
+
+ drvdata->enabled = false;
+
+ pm_runtime_put(&adev->dev);
+
+ return 0;
+}
+
+static void tgu_remove(struct amba_device *adev)
+{
+ struct device *dev = &adev->dev;
+
+ sysfs_remove_groups(&dev->kobj, tgu_attr_groups);
+
+ tgu_disable(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..dd7533b9d735
--- /dev/null
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -0,0 +1,51 @@
+/* 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
+#define TGU_LAR 0xfb0
+#define TGU_UNLOCK_OFFSET 0xc5acce55
+
+static inline void TGU_LOCK(void __iomem *addr)
+{
+ do {
+ /* Wait for things to settle */
+ mb();
+ writel_relaxed(0x0, addr + TGU_LAR);
+ } while (0);
+}
+
+static inline void TGU_UNLOCK(void __iomem *addr)
+{
+ do {
+ writel_relaxed(TGU_UNLOCK_OFFSET, addr + TGU_LAR);
+ /* Make sure everyone has seen this */
+ mb();
+ } while (0);
+}
+
+/**
+ * struct tgu_drvdata - Data structure for a TGU (Trigger Generator Unit)
+ * @base: Memory-mapped base address of the TGU device
+ * @dev: Pointer to the associated device structure
+ * @lock: Spinlock for handling concurrent access to private data
+ * @enabled: Flag indicating whether the TGU device is enabled
+ *
+ * This structure defines the data associated with a TGU device,
+ * including its base address, device pointers, clock, spinlock for
+ * synchronization, trigger data pointers, maximum limits for various
+ * trigger-related parameters, and enable status.
+ */
+struct tgu_drvdata {
+ void __iomem *base;
+ struct device *dev;
+ spinlock_t lock;
+ bool enabled;
+};
+
+#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v13 1/7] dt-bindings: arm: Add support for Qualcomm TGU trace
From: Songwei Chai @ 2026-04-02 9:28 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, Rob Herring
In-Reply-To: <20260402092838.341295-1-songwei.chai@oss.qualcomm.com>
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 related
* [PATCH v13 0/7] Provide support for Trigger Generation Unit
From: Songwei Chai @ 2026-04-02 9:28 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
We propose creating a new qcom directory under drivers/hwtracing
to host this TGU driver, as well as additional Qualcomm-specific
hwtracing drivers that we plan to submit in the coming months.
This structure will help organize vendor-specific implementations
and facilitate future development and maintenance.
Feedback from the community on this proposal is highly appreciated.
- Why we are proposing this:
TGU has the ability to monitor signal conditions and trigger debug-related
actions, serving as a programmable hardware component that enhances system
trace and debug capabilities. Placing it under drivers/hwtracing aligns
with its function as a trace generation utility.
We previously attempted to push this driver to drivers/hwtracing/coresight,
but did not receive support from the maintainers of the CoreSight
subsystem. The reason provided was: “This component is primarily a part
of the Qualcomm proprietary QPMDA subsystem, and is capable of operating
independently from the CoreSight hardware trace generation system.”
Chat history : https://lore.kernel.org/all/CAJ9a7ViKxHThyZfFFDV_FkNRimk4uo1NrMtQ-kcaj1qO4ZcGnA@mail.gmail.com/
Given this, we have been considering whether it would be appropriate
to create a dedicated drivers/hwtracing/qcom directory for
Qualcomm-related hwtracing drivers. This would follow the precedent set
by Intel, which maintains its own directory at drivers/hwtracing/intel_th.
We believe this structure would significantly facilitate
future submissions of related Qualcomm drivers.
- Maintenance of drivers/hwtracing/qcom:
Bjorn, who maintains linux-arm-msm, will be the maintainer of this
directory — we’ve discussed this with him and he’s aware that his task
list may grow accordingly. Additionally, Qualcomm engineers familiar with
the debug hardware — such as [Tingwei Zhang, Jinlong Mao, Songwei Chai],
will be available to review incoming patches and support ongoing
development.
- Detail for TGU:
This component can be utilized to sense a plurality of signals and
create a trigger into the CTI or generate interrupts to processors
once the input signal meets the conditions. We can treat the TGU’s
workflow as a flowsheet, it has some “steps” regions for customization.
In each step region, we can set the signals that we want with priority
in priority_group, set the conditions in each step via condition_decode,
and set the resultant action by condition_select. Meanwhile,
some TGUs (not all) also provide timer/counter functionality.
Based on the characteristics described above, we consider the TGU as a
helper in the CoreSight subsystem. Its master device is the TPDM, which
can transmit signals from other subsystems, and we reuse the existing
ports mechanism to link the TPDM to the connected TGU.
Here is a detailed example to explain how to use the TGU:
In this example, the TGU is configured to use 2 conditions, 2 steps, and
the timer. The goal is to look for one of two patterns which are generated
from TPDM, giving priority to one, and then generate a trigger once the
timer reaches a certain value. In other words, two conditions are used
for the first step to look for the two patterns, where the one with the
highest priority is used in the first condition. Then, in the second step,
the timer is enabled and set to be compared to the given value at each
clock cycle. These steps are better shown below.
|-----------------|
| |
| TPDM |
| |
|-----------------|
|
|
--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ------
| | |
| | |--------------------| |
| |---- ---> | | Go to next steps | |
| | | |--- ---> | Enable timer | |
| | v | | | |
| | |-----------------| | |--------------------| |
| | | | Yes | | |
| | | inputs==0xB | ----->| | <-------- |
| | | | | | No | |
| No | |-----------------| | v | |
| | | | |-----------------| | |
| | | | | | | |
| | | | | timer>=3 |-- |
| | v | | | |
| | |-----------------| | |-----------------| |
| | | | Yes | | |
| |--- | inputs==0xA | ----->| | Yes |
| | | | |
| |-----------------| v |
| |-----------------| |
| | | |
| | Trigger | |
| | | |
| |-----------------| |
| TGU | |
|--- --- --- --- --- --- --- --- --- --- --- --- --- --- |--- --- -- |
|
v
|-----------------|
|The controllers |
|which will use |
|triggers further |
|-----------------|
steps:
1. Reset TGU /*it will disable tgu and reset dataset*/
- echo 1 > /sys/bus/amba/devices/<tgu-name>/reset_tgu
2. Set the pattern match for priority0 to 0xA = 0b1010 and for
priority 1 to 0xB = 0b1011.
- echo 0x11113232 > /sys/bus/amba/devices/<tgu-name>/step0_priority0/reg0
- echo 0x11113233 > /sys/bus/amba/devices/<tgu-name>/step0_priority1/reg0
Note:
Bit distribution diagram for each priority register
|-------------------------------------------------------------------|
| Bits | Field Nam | Description |
|-------------------------------------------------------------------|
| | | 00 = bypass for OR output |
| 29:28 | SEL_BIT7_TYPE2 | 01 = bypass for AND output |
| | | 10 = sense input '0' is true|
| | | 11 = sense input '1' is true|
|-------------------------------------------------------------------|
| | | 00 = bypass for OR output |
| 25:24 | SEL_BIT6_TYPE2 | 01 = bypass for AND output |
| | | 10 = sense input '0' is true|
| | | 11 = sense input '1' is true|
|-------------------------------------------------------------------|
| | | 00 = bypass for OR output |
| 21:20 | SEL_BIT5_TYPE2 | 01 = bypass for AND output |
| | | 10 = sense input '0' is true|
| | | 11 = sense input '1' is true|
|-------------------------------------------------------------------|
| | | 00 = bypass for OR output |
| 17:16 | SEL_BIT4_TYPE2 | 01 = bypass for AND output |
| | | 10 = sense input '0' is true|
| | | 11 = sense input '1' is true|
|-------------------------------------------------------------------|
| | | 00 = bypass for OR output |
| 13:12 | SEL_BIT3_TYPE2 | 01 = bypass for AND output |
| | | 10 = sense input '0' is true|
| | | 11 = sense input '1' is true|
|-------------------------------------------------------------------|
| | | 00 = bypass for OR output |
| 9:8 | SEL_BIT2_TYPE2 | 01 = bypass for AND output |
| | | 10 = sense input '0' is true|
| | | 11 = sense input '1' is true|
|-------------------------------------------------------------------|
| | | 00 = bypass for OR output |
| 5:4 | SEL_BIT1_TYPE2 | 01 = bypass for AND output |
| | | 10 = sense input '0' is true|
| | | 11 = sense input '1' is true|
|-------------------------------------------------------------------|
| | | 00 = bypass for OR output |
| 1:0 | SEL_BIT0_TYPE2 | 01 = bypass for AND output |
| | | 10 = sense input '0' is true|
| | | 11 = sense input '1' is true|
|-------------------------------------------------------------------|
These bits are used to identify the signals we want to sense, with
a maximum signal number of 140. For example, to sense the signal
0xA (binary 1010), we set the value of bits 0 to 13 to 3232, which
represents 1010. The remaining bits are set to 1, as we want to use
AND gate to summarize all the signals we want to sense here. For
rising or falling edge detection of any input to the priority, set
the remaining bits to 0 to use an OR gate.
3. look for the pattern for priority_i i=0,1.
- echo 0x3 > /sys/bus/amba/devices/<tgu-name>/step0_condition_decode/reg0
- echo 0x30 > /sys/bus/amba/devices/<tgu-name>/step0_condition_decode/reg1
|-------------------------------------------------------------------------------|
| Bits | Field Nam | Description |
|-------------------------------------------------------------------------------|
| | |For each decoded condition, this |
| 24 | NOT |inverts the output. If the condition |
| | |decodes to true, and the NOT field |
| | |is '1', then the output is NOT true. |
|-------------------------------------------------------------------------------|
| | |When '1' the output from the associated|
| 21 | BC0_COMP_ACTIVE |comparator will be actively included in|
| | |the decoding of this particular |
| | |condition. |
|-------------------------------------------------------------------------------|
| | |When '1' the output from the associated|
| | |comparator will need to be 1 to affect |
| 20 | BC0_COMP_HIGH |the decoding of this condition. |
| | |Conversely, a '0' here requires a '0' |
| | |from the comparator |
|-------------------------------------------------------------------------------|
| | |When '1' the output from the associated|
| 17 | |comparator will be actively included in|
| | TC0_COMP_ACTIVE |the decoding of this particular |
| | |condition. |
|-------------------------------------------------------------------------------|
| | |When '1' the output from the associated|
| | |comparator will need to be 1 to affect |
| 16 | TC0_COMP_HIGH |the decoding of this particular |
| | |condition.Conversely, a 0 here |
| | |requires a '0' from the comparator |
|-------------------------------------------------------------------------------|
| | |When '1' the output from Priority_n |
| | |OR logic will be actively |
| 4n+3 | Priority_n_OR_ACTIVE|included in the decoding of |
| | (n=0,1,2,3) |this particular condition. |
| | | |
|-------------------------------------------------------------------------------|
| | |When '1' the output from Priority_n |
| | |will need to be '1' to affect the |
| 4n+2 | Priority_n_OR_HIGH |decoding of this particular |
| | (n=0,1,2,3) |condition. Conversely, a '0' here |
| | |requires a '0' from Priority_n OR logic|
|-------------------------------------------------------------------------------|
| | |When '1' the output from Priority_n |
| | |AND logic will be actively |
| 4n+1 |Priority_n_AND_ACTIVE|included in the decoding of this |
| | (n=0,1,2,3) |particular condition. |
| | | |
|-------------------------------------------------------------------------------|
| | |When '1' the output from Priority_n |
| | |AND logic will need to be '1' to |
| 4n | Priority_n_AND_HIGH |affect the decoding of this |
| | (n=0,1,2,3) |particular condition. Conversely, |
| | |a '0' here requires a '0' from |
| | |Priority_n AND logic. |
|-------------------------------------------------------------------------------|
Since we use `priority_0` and `priority_1` with an AND output in step 2, we set `0x3`
and `0x30` here to activate them.
4. Set NEXT_STEP = 1 and TC0_ENABLE = 1 so that when the conditions
are met then the next step will be step 1 and the timer will be enabled.
- echo 0x20008 > /sys/bus/amba/devices/<tgu-name>/step0_condition_select/reg0
- echo 0x20008 > /sys/bus/amba/devices/<tgu-name>/step0_condition_select/reg1
|-----------------------------------------------------------------------------|
| Bits | Field Nam | Description |
|-----------------------------------------------------------------------------|
| | |This field defines the next step the |
| 18:17 | NEXT_STEP |TGU will 'goto' for the associated |
| | |Condition and Step. |
|-----------------------------------------------------------------------------|
| | |For each possible output trigger |
| 13 | TRIGGER |available, set a '1' if you want |
| | |the trigger to go active for the |
| | |associated condition and Step. |
|-----------------------------------------------------------------------------|
| | |This will cause BC0 to increment if the|
| 9 | BC0_INC |associated Condition is decoded for |
| | |this step. |
|-----------------------------------------------------------------------------|
| | |This will cause BC0 to decrement if the|
| 8 | BC0_DEC |associated Condition is decoded for |
| | |this step. |
|-----------------------------------------------------------------------------|
| | |This will clear BC0 count value to 0 if|
| 7 | BC0_CLEAR |the associated Condition is decoded |
| | |for this step. |
|-----------------------------------------------------------------------------|
| | |This will cause TC0 to increment until |
| 3 | TC0_ENABLE |paused or cleared if the associated |
| | |Condition is decoded for this step. |
|-----------------------------------------------------------------------------|
| | |This will cause TC0 to pause until |
| 2 | TC0_PAUSE |enabled if the associated Condition |
| | |is decoded for this step. |
|-----------------------------------------------------------------------------|
| | |This will clear TC0 count value to 0 |
| 1 | TC0_CLEAR |if the associated Condition is |
| | |decoded for this step. |
|-----------------------------------------------------------------------------|
| | |This will set the done signal to the |
| 0 | DONE |TGU FSM if the associated Condition |
| | |is decoded for this step. |
|-----------------------------------------------------------------------------|
Based on the distribution diagram, we set `0x20008` for `priority0` and `priority1` to
achieve "jump to step 1 and enable TC0" once the signal is sensed.
5. activate the timer comparison for this step.
- echo 0x30000 > /sys/bus/amba/devices/<tgu-name>/step1_condition_decode/reg0
|-------------------------------------------------------------------------------|
| | |When '1' the output from the associated|
| 17 | |comparator will be actively included in|
| | TC0_COMP_ACTIVE |the decoding of this particular |
| | |condition. |
|-------------------------------------------------------------------------------|
| | |When '1' the output from the associated|
| | |comparator will need to be 1 to affect |
| 16 | TC0_COMP_HIGH |the decoding of this particular |
| | |condition.Conversely, a 0 here |
| | |requires a '0' from the comparator |
|-------------------------------------------------------------------------------|
Accroding to the decode distribution diagram , we give 0x30000 here to set 16th&17th bit
to enable timer comparison.
6. Set the NEXT_STEP = 0 and TC0_PAUSE = 1 and TC0_CLEAR = 1 once the timer
has reached the given value.
- echo 0x6 > /sys/bus/amba/devices/<tgu-name>/step1_condition_select/reg0
7. Enable Trigger 0 for TGU when the condition 0 is met in step1,
i.e. when the timer reaches 3.
- echo 0x2000 > /sys/bus/amba/devices/<tgu-name>/step1_condition_select/default
Note:
1. 'default' register allows for establishing the resultant action for
the default condition
2. Trigger:For each possible output trigger available from
the Design document, there are three triggers: interrupts, CTI,
and Cross-TGU mapping.All three triggers can occur, but
the choice of which trigger to use depends on the user's
needs.
8. Compare the timer to 3 in step 1.
- echo 0x3 > /sys/bus/amba/devices/<tgu-name>/step1_timer/reg0
9. enale tgu
- echo 1 > /sys/bus/amba/devices/<tgu-name>/enable_tgu
---
Link to V12: https://lore.kernel.org/all/20260317032639.2393221-1-songwei.chai@oss.qualcomm.com/
Changes in V13:
- add ":" after "KernelVersion"
- add an enablement check in the enable function to avoid increasing the counter each time
---
Link to V11: https://lore.kernel.org/all/ee1ca8e6-8e5f-47d8-8a24-f904ee2fc6d0@oss.qualcomm.com/
Changes in V12:
- Remove the in-ports property from the bindings, as this device is decoupled from CoreSight.
- Update kernel version and date.
---
Link to V10: https://lore.kernel.org/all/20c5406d-3e9f-4fdb-84ba-4cbe629c79b5@oss.qualcomm.com/
Changes in V11:
- Change the names of members in drvdata: max_xxx -> num_xxx, enable -> enabled
- Use "FIELD_GET" to replace "BMVAL"
- Use devm_kcalloc to replace devm_kzalloc once create members of value_table
- Keep a consistent \n above return
- Keep reverse-Christmas-tree style
- Add checks so that the enable and reset nodes only accept 0 or 1
---
Link to V9: https://lore.kernel.org/all/20251219065902.2296896-1-songwei.chai@oss.qualcomm.com/
Changes in V10:
- Modified code formatting based on Jie's feedback to improve readability.
- Applied inverse Christmas tree order to the variables.
---
Link to V8: https://lore.kernel.org/all/20251203090055.2432719-1-songwei.chai@oss.qualcomm.com/
Changes in V9:
- Decoupled the tgu driver from coresight header file and registered it as an amba device.
- Retained Rob's reviewed-by tag on patch1/7 since the file remains unchanged.
- Updated the sysfs node path in the Documentation directory.
---
Link to V7: https://lore.kernel.org/all/20251104064043.88972-1-songwei.chai@oss.qualcomm.com/
Changes in V8:
- Add "select" section in bindings.
- Update publish date in "sysfs-bus-coresight-devices-tgu".
---
Link to V6: https://lore.kernel.org/all/20250709104114.22240-1-songchai@qti.qualcomm.com/
Changes in V7:
- Move the TGU code location from 'drivers/hwtracing/coresight/' to 'drivers/hwtracing/qcom/'.
- Rename the spinlock used in the code from 'spinlock' to 'lock'.
- Perform the 'calculate_array_location' separately, instead of doing it within the function.
- Update the sender email address.
---
Link to V5: https://lore.kernel.org/all/20250529081949.26493-1-quic_songchai@quicinc.com/
Changes in V6:
- Replace spinlock with guard(spinlock) in tgu_enable.
- Remove redundant blank line.
- Update publish date and contact member's name in "sysfs-bus-coresight-devices-tgu".
---
Link to V4: https://patchwork.kernel.org/project/linux-arm-msm/cover/20250423101054.954066-1-quic_songchai@quicinc.com/
Changes in V5:
- Update publish date and kernel_version in "sysfs-bus-coresight-devices-tgu"
---
Link to V3: https://lore.kernel.org/all/20250227092640.2666894-1-quic_songchai@quicinc.com/
Changes in V4:
- Add changlog in coverletter.
- Correct 'year' in Copyright in patch1.
- Correct port mechansim description in patch1.
- Remove 'tgu-steps','tgu-regs','tgu-conditions','tgu-timer-counters' from dt-binding
and set them through reading DEVID register as per Mike's suggestion.
- Modify tgu_disable func to make it have single return point in patch2 as per
Mike's suggestion.
- Use sysfs_emit in enable_tgu_show func in ptach2.
- Remove redundant judgement in enable_tgu_store in patch2.
- Correct typo in description in patch3.
- Set default ret as SYSFS_GROUP_INVISIBLE, and returnret at end in pacth3 as
per Mike's suggestion.
- Remove tgu_dataset_ro definition in patch3
- Use #define constants with explanations of what they are rather than
arbitrary magic numbers in patch3 and patch4.
- Check -EINVAL before using 'calculate_array_location()' in array in patch4.
- Add 'default' in 'tgu_dataset_show''s switch part in patch4.
- Document the value needed to initiate the reset in pacth7.
- Check "value" in 'reset_tgu_store' and bail out with an error code if 0 in patch7.
- Remove dev_dbg in 'reset_tgu_store' in patch7.
---
Link to V2: https://lore.kernel.org/all/20241010073917.16023-1-quic_songchai@quicinc.com/
Changes in V3:
- Correct typo and format in dt-binding in patch1
- Rebase to the latest kernel version
---
Link to V1: https://lore.kernel.org/all/20240830092311.14400-1-quic_songchai@quicinc.com/
Changes in V2:
- Use real name instead of login name,
- Correct typo and format in dt-binding and code.
- Bring order in tgu_prob(declarations with and without assignments) as per
Krzysztof's suggestion.
- Add module device table in patch2.
- Set const for tgu_common_grp and tgu_ids in patch2.
- Initialize 'data' in tgu_ids to fix the warning in pacth2.
---
Songwei Chai (7):
dt-bindings: arm: Add support for Qualcomm TGU trace
qcom-tgu: Add TGU driver
qcom-tgu: Add signal priority support
qcom-tgu: Add TGU decode support
qcom-tgu: Add support to configure next action
qcom-tgu: Add timer/counter functionality for TGU
qcom-tgu: Add reset node to initialize
.../ABI/testing/sysfs-bus-amba-devices-tgu | 51 ++
.../devicetree/bindings/arm/qcom,tgu.yaml | 71 ++
drivers/Makefile | 1 +
drivers/hwtracing/Kconfig | 2 +
drivers/hwtracing/qcom/Kconfig | 18 +
drivers/hwtracing/qcom/Makefile | 3 +
drivers/hwtracing/qcom/tgu.c | 704 ++++++++++++++++++
drivers/hwtracing/qcom/tgu.h | 275 +++++++
8 files changed, 1125 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
create mode 100644 Documentation/devicetree/bindings/arm/qcom,tgu.yaml
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
--
2.34.1
^ permalink raw reply
* [PATCH v3 06/11] drm/bridge: dw-hdmi: warn on unsupported attach combination
From: Luca Ceresoli @ 2026-04-02 9:26 UTC (permalink / raw)
To: Marek Vasut, Stefan Agner, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Liu Ying, Rob Herring,
Saravana Kannan
Cc: Damon Ding, Kory Maincent (TI.com), Hervé Codina, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, imx, linux-arm-kernel,
linux-kernel, devicetree, Adam Ford, Alexander Stein,
Christopher Obbard, Daniel Scally, Emanuele Ghidoli,
Fabio Estevam, Francesco Dolcini, Frieder Schrempf, Gilles Talis,
Goran Rađenović, Heiko Schocher, Josua Mayer,
Kieran Bingham, Marco Felsch, Martyn Welch, Oleksij Rempel,
Peng Fan, Richard Hu, Shengjiu Wang, Stefan Eichenberger,
Vitor Soares, Luca Ceresoli
In-Reply-To: <20260402-drm-lcdif-dbanc-v3-0-27cd247a0847@bootlin.com>
dw-hdmi can operate in two different modes, depending on the platform data
as set by the driver:
A. hdmi->plat_data->output_port = 0:
the HDMI output (port@1) in device tree is not used
B. hdmi->plat_data->output_port = 1:
the HDMI output (port@1) is parsed to find the next bridge
Only case B is supported when the DRM_BRIDGE_ATTACH_NO_CONNECTOR flag is
passed to the attach callback. Emit a warning when this is violated. Also
return -EINVAL which would be returned by drm_bridge_attach() right after
anyway.
Reviewed-by: Liu Ying <victor.liu@nxp.com>
Tested-by: Martyn Welch <martyn.welch@collabora.com>
Tested-by: Alexander Stein <alexander.stein@ew.tq-group.com> # TQMa8MPxL/MBa8MPxL
Tested-by: Damon Ding <damon.ding@rock-chips.com> # rk3399
Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
Note: Returning when the warning triggers does not change the functional
behaviour of this function. It is not strictly necessary in this patch but
it will have to be done anyway in the following patch.
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 0296e110ce65..ab1a6a8783cd 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2910,6 +2910,10 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
{
struct dw_hdmi *hdmi = bridge->driver_private;
+ /* DRM_BRIDGE_ATTACH_NO_CONNECTOR requires a remote-endpoint to the next bridge */
+ if (WARN_ON((flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) && !hdmi->plat_data->output_port))
+ return -EINVAL;
+
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
return drm_bridge_attach(encoder, hdmi->bridge.next_bridge,
bridge, flags);
--
2.53.0
^ permalink raw reply related
* [PATCH v3 05/11] drm/bridge: dw-hdmi: document the output_port field
From: Luca Ceresoli @ 2026-04-02 9:26 UTC (permalink / raw)
To: Marek Vasut, Stefan Agner, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Liu Ying, Rob Herring,
Saravana Kannan
Cc: Damon Ding, Kory Maincent (TI.com), Hervé Codina, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, imx, linux-arm-kernel,
linux-kernel, devicetree, Adam Ford, Alexander Stein,
Christopher Obbard, Daniel Scally, Emanuele Ghidoli,
Fabio Estevam, Francesco Dolcini, Frieder Schrempf, Gilles Talis,
Goran Rađenović, Heiko Schocher, Josua Mayer,
Kieran Bingham, Marco Felsch, Martyn Welch, Oleksij Rempel,
Peng Fan, Richard Hu, Shengjiu Wang, Stefan Eichenberger,
Vitor Soares, Luca Ceresoli
In-Reply-To: <20260402-drm-lcdif-dbanc-v3-0-27cd247a0847@bootlin.com>
The meaning of this flag may not be obvious at first sight.
Reviewed-by: Liu Ying <victor.liu@nxp.com>
Tested-by: Martyn Welch <martyn.welch@collabora.com>
Tested-by: Alexander Stein <alexander.stein@ew.tq-group.com> # TQMa8MPxL/MBa8MPxL
Tested-by: Damon Ding <damon.ding@rock-chips.com> # rk3399
Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
Changes in v2:
- improved comment as suggested by Liu
---
include/drm/bridge/dw_hdmi.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
index 336f062e1f9d..8500dd4f99d8 100644
--- a/include/drm/bridge/dw_hdmi.h
+++ b/include/drm/bridge/dw_hdmi.h
@@ -126,6 +126,12 @@ struct dw_hdmi_phy_ops {
struct dw_hdmi_plat_data {
struct regmap *regm;
+ /*
+ * The HDMI output port number must be 1 if the port is described
+ * in the device tree. 0 if the device tree does not describe the
+ * next component (legacy mode, i.e. without
+ * DRM_BRIDGE_ATTACH_NO_CONNECTOR flag when attaching bridge).
+ */
unsigned int output_port;
unsigned long input_bus_encoding;
--
2.53.0
^ permalink raw reply related
* [PATCH v3 04/11] drm/mxsfb/lcdif: move iteration-specific variables declaration inside loop in lcdif_attach_bridge
From: Luca Ceresoli @ 2026-04-02 9:25 UTC (permalink / raw)
To: Marek Vasut, Stefan Agner, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Liu Ying, Rob Herring,
Saravana Kannan
Cc: Damon Ding, Kory Maincent (TI.com), Hervé Codina, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, imx, linux-arm-kernel,
linux-kernel, devicetree, Adam Ford, Alexander Stein,
Christopher Obbard, Daniel Scally, Emanuele Ghidoli,
Fabio Estevam, Francesco Dolcini, Frieder Schrempf, Gilles Talis,
Goran Rađenović, Heiko Schocher, Josua Mayer,
Kieran Bingham, Marco Felsch, Martyn Welch, Oleksij Rempel,
Peng Fan, Richard Hu, Shengjiu Wang, Stefan Eichenberger,
Vitor Soares, Luca Ceresoli
In-Reply-To: <20260402-drm-lcdif-dbanc-v3-0-27cd247a0847@bootlin.com>
The bridge and ret variables are per-iteration variables, whose values
don't have to be carried to the next iteration or be used after the loop
end. Move their declaration inside the loop scope as a cleanup and to make
code clearer.
Reviewed-by: Liu Ying <victor.liu@nxp.com>
Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
This patch is new in v2
---
drivers/gpu/drm/mxsfb/lcdif_drv.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/mxsfb/lcdif_drv.c b/drivers/gpu/drm/mxsfb/lcdif_drv.c
index 1c76709c4d94..c8ba8f9b1da8 100644
--- a/drivers/gpu/drm/mxsfb/lcdif_drv.c
+++ b/drivers/gpu/drm/mxsfb/lcdif_drv.c
@@ -50,14 +50,14 @@ static int lcdif_attach_bridge(struct lcdif_drm_private *lcdif)
{
struct device *dev = lcdif->drm->dev;
struct device_node *ep __free(device_node) = NULL;
- struct drm_bridge *bridge;
- int ret;
for_each_endpoint_of_node(dev->of_node, ep) {
struct device_node *remote __free(device_node) =
of_graph_get_remote_port_parent(ep);
struct of_endpoint of_ep;
+ struct drm_bridge *bridge;
struct drm_encoder *encoder;
+ int ret;
if (!of_device_is_available(remote))
continue;
--
2.53.0
^ permalink raw reply related
* [PATCH v3 03/11] drm/mxsfb/lcdif: use dev_err_probe() consistently in lcdif_attach_bridge
From: Luca Ceresoli @ 2026-04-02 9:25 UTC (permalink / raw)
To: Marek Vasut, Stefan Agner, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Liu Ying, Rob Herring,
Saravana Kannan
Cc: Damon Ding, Kory Maincent (TI.com), Hervé Codina, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, imx, linux-arm-kernel,
linux-kernel, devicetree, Adam Ford, Alexander Stein,
Christopher Obbard, Daniel Scally, Emanuele Ghidoli,
Fabio Estevam, Francesco Dolcini, Frieder Schrempf, Gilles Talis,
Goran Rađenović, Heiko Schocher, Josua Mayer,
Kieran Bingham, Marco Felsch, Martyn Welch, Oleksij Rempel,
Peng Fan, Richard Hu, Shengjiu Wang, Stefan Eichenberger,
Vitor Soares, Luca Ceresoli
In-Reply-To: <20260402-drm-lcdif-dbanc-v3-0-27cd247a0847@bootlin.com>
lcdif_attach_bridge() uses dev_err_probe() in some error paths, dev_err() +
return in others. Use dev_err_probe() for all of them to make code
consistent, simpler and with better error reporting.
Reviewed-by: Liu Ying <victor.liu@nxp.com>
Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
Changes in v2:
- rewrote after removal of previous patch removing loop
- not added review/test trailers as the code is a bit different
---
drivers/gpu/drm/mxsfb/lcdif_drv.c | 24 ++++++++++--------------
1 file changed, 10 insertions(+), 14 deletions(-)
diff --git a/drivers/gpu/drm/mxsfb/lcdif_drv.c b/drivers/gpu/drm/mxsfb/lcdif_drv.c
index 8da8a265c05c..1c76709c4d94 100644
--- a/drivers/gpu/drm/mxsfb/lcdif_drv.c
+++ b/drivers/gpu/drm/mxsfb/lcdif_drv.c
@@ -63,10 +63,8 @@ static int lcdif_attach_bridge(struct lcdif_drm_private *lcdif)
continue;
ret = of_graph_parse_endpoint(ep, &of_ep);
- if (ret < 0) {
- dev_err(dev, "Failed to parse endpoint %pOF\n", ep);
- return ret;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to parse endpoint %pOF\n", ep);
bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, of_ep.id);
if (IS_ERR(bridge))
@@ -75,20 +73,18 @@ static int lcdif_attach_bridge(struct lcdif_drm_private *lcdif)
of_ep.id);
encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL);
- if (!encoder) {
- dev_err(dev, "Failed to allocate encoder for endpoint%u\n",
- of_ep.id);
- return -ENOMEM;
- }
+ if (!encoder)
+ return dev_err_probe(dev, -ENOMEM,
+ "Failed to allocate encoder for endpoint%u\n",
+ of_ep.id);
encoder->possible_crtcs = drm_crtc_mask(&lcdif->crtc);
ret = drm_encoder_init(lcdif->drm, encoder, &lcdif_encoder_funcs,
DRM_MODE_ENCODER_NONE, NULL);
- if (ret) {
- dev_err(dev, "Failed to initialize encoder for endpoint%u: %d\n",
- of_ep.id, ret);
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to initialize encoder for endpoint%u\n",
+ of_ep.id);
ret = drm_bridge_attach(encoder, bridge, NULL, 0);
if (ret)
--
2.53.0
^ permalink raw reply related
* [PATCH v3 02/11] drm/mxsfb/lcdif: simplify ep pointer management using __free
From: Luca Ceresoli @ 2026-04-02 9:25 UTC (permalink / raw)
To: Marek Vasut, Stefan Agner, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Liu Ying, Rob Herring,
Saravana Kannan
Cc: Damon Ding, Kory Maincent (TI.com), Hervé Codina, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, imx, linux-arm-kernel,
linux-kernel, devicetree, Adam Ford, Alexander Stein,
Christopher Obbard, Daniel Scally, Emanuele Ghidoli,
Fabio Estevam, Francesco Dolcini, Frieder Schrempf, Gilles Talis,
Goran Rađenović, Heiko Schocher, Josua Mayer,
Kieran Bingham, Marco Felsch, Martyn Welch, Oleksij Rempel,
Peng Fan, Richard Hu, Shengjiu Wang, Stefan Eichenberger,
Vitor Soares, Luca Ceresoli
In-Reply-To: <20260402-drm-lcdif-dbanc-v3-0-27cd247a0847@bootlin.com>
Putting the ep device_node reference requires a of_node_put(ep) in many
return points. Use a cleanup action to simplify the code.
Reviewed-by: Liu Ying <victor.liu@nxp.com>
Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
This patch is new in v2
---
drivers/gpu/drm/mxsfb/lcdif_drv.c | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/mxsfb/lcdif_drv.c b/drivers/gpu/drm/mxsfb/lcdif_drv.c
index 7719629487da..8da8a265c05c 100644
--- a/drivers/gpu/drm/mxsfb/lcdif_drv.c
+++ b/drivers/gpu/drm/mxsfb/lcdif_drv.c
@@ -49,7 +49,7 @@ static const struct drm_encoder_funcs lcdif_encoder_funcs = {
static int lcdif_attach_bridge(struct lcdif_drm_private *lcdif)
{
struct device *dev = lcdif->drm->dev;
- struct device_node *ep;
+ struct device_node *ep __free(device_node) = NULL;
struct drm_bridge *bridge;
int ret;
@@ -65,23 +65,19 @@ static int lcdif_attach_bridge(struct lcdif_drm_private *lcdif)
ret = of_graph_parse_endpoint(ep, &of_ep);
if (ret < 0) {
dev_err(dev, "Failed to parse endpoint %pOF\n", ep);
- of_node_put(ep);
return ret;
}
bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, of_ep.id);
- if (IS_ERR(bridge)) {
- of_node_put(ep);
+ if (IS_ERR(bridge))
return dev_err_probe(dev, PTR_ERR(bridge),
"Failed to get bridge for endpoint%u\n",
of_ep.id);
- }
encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL);
if (!encoder) {
dev_err(dev, "Failed to allocate encoder for endpoint%u\n",
of_ep.id);
- of_node_put(ep);
return -ENOMEM;
}
@@ -91,17 +87,14 @@ static int lcdif_attach_bridge(struct lcdif_drm_private *lcdif)
if (ret) {
dev_err(dev, "Failed to initialize encoder for endpoint%u: %d\n",
of_ep.id, ret);
- of_node_put(ep);
return ret;
}
ret = drm_bridge_attach(encoder, bridge, NULL, 0);
- if (ret) {
- of_node_put(ep);
+ if (ret)
return dev_err_probe(dev, ret,
"Failed to attach bridge for endpoint%u\n",
of_ep.id);
- }
}
return 0;
--
2.53.0
^ permalink raw reply related
* [PATCH v3 01/11] drm/mxsfb/lcdif: simplify remote pointer management using __free
From: Luca Ceresoli @ 2026-04-02 9:25 UTC (permalink / raw)
To: Marek Vasut, Stefan Agner, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Liu Ying, Rob Herring,
Saravana Kannan
Cc: Damon Ding, Kory Maincent (TI.com), Hervé Codina, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, imx, linux-arm-kernel,
linux-kernel, devicetree, Adam Ford, Alexander Stein,
Christopher Obbard, Daniel Scally, Emanuele Ghidoli,
Fabio Estevam, Francesco Dolcini, Frieder Schrempf, Gilles Talis,
Goran Rađenović, Heiko Schocher, Josua Mayer,
Kieran Bingham, Marco Felsch, Martyn Welch, Oleksij Rempel,
Peng Fan, Richard Hu, Shengjiu Wang, Stefan Eichenberger,
Vitor Soares, Luca Ceresoli
In-Reply-To: <20260402-drm-lcdif-dbanc-v3-0-27cd247a0847@bootlin.com>
Putting the remote device_node reference requires a of_node_put(remote) in
two places. Use a cleanup action to simplify the code.
Reviewed-by: Liu Ying <victor.liu@nxp.com>
Tested-by: Martyn Welch <martyn.welch@collabora.com>
Tested-by: Alexander Stein <alexander.stein@ew.tq-group.com> # TQMa8MPxL/MBa8MPxL
Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
Changes in v2:
- Use the correct cleanup action
- Fix commit message
- add missing cleanup.h include
---
drivers/gpu/drm/mxsfb/lcdif_drv.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/mxsfb/lcdif_drv.c b/drivers/gpu/drm/mxsfb/lcdif_drv.c
index 47da1d9336b9..7719629487da 100644
--- a/drivers/gpu/drm/mxsfb/lcdif_drv.c
+++ b/drivers/gpu/drm/mxsfb/lcdif_drv.c
@@ -5,6 +5,7 @@
* This code is based on drivers/gpu/drm/mxsfb/mxsfb*
*/
+#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
@@ -53,16 +54,13 @@ static int lcdif_attach_bridge(struct lcdif_drm_private *lcdif)
int ret;
for_each_endpoint_of_node(dev->of_node, ep) {
- struct device_node *remote;
+ struct device_node *remote __free(device_node) =
+ of_graph_get_remote_port_parent(ep);
struct of_endpoint of_ep;
struct drm_encoder *encoder;
- remote = of_graph_get_remote_port_parent(ep);
- if (!of_device_is_available(remote)) {
- of_node_put(remote);
+ if (!of_device_is_available(remote))
continue;
- }
- of_node_put(remote);
ret = of_graph_parse_endpoint(ep, &of_ep);
if (ret < 0) {
--
2.53.0
^ permalink raw reply related
* [PATCH v3 00/11] drm/mxsfb/lcdif: use DRM_BRIDGE_ATTACH_NO_CONNECTOR and the bridge-connector
From: Luca Ceresoli @ 2026-04-02 9:25 UTC (permalink / raw)
To: Marek Vasut, Stefan Agner, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Liu Ying, Rob Herring,
Saravana Kannan
Cc: Damon Ding, Kory Maincent (TI.com), Hervé Codina, Hui Pu,
Ian Ray, Thomas Petazzoni, dri-devel, imx, linux-arm-kernel,
linux-kernel, devicetree, Adam Ford, Alexander Stein,
Christopher Obbard, Daniel Scally, Emanuele Ghidoli,
Fabio Estevam, Francesco Dolcini, Frieder Schrempf, Gilles Talis,
Goran Rađenović, Heiko Schocher, Josua Mayer,
Kieran Bingham, Marco Felsch, Martyn Welch, Oleksij Rempel,
Peng Fan, Richard Hu, Shengjiu Wang, Stefan Eichenberger,
Vitor Soares, Luca Ceresoli
This series modernizes the i.mx8mp LCDIF driver to use the
bridge-connector, which is the current best practice in DRM.
== Call for testing on i.MX8MP boards (especially those using HDMI)!
For who tested v1 or v2 (thanks!): some patches have changed so I had to
drop your Tested-by on them. A new round of test would still be useful.
This series applies changes to how video output devices are probed on
i.MX8MP, especially those using HDMI. Even though I have put care in not
breaking anything, there could potentially be pitfalls I haven't realized,
causing regressions on existing boards.
I have thus added in Cc all developers which appeared active on dts files
for imx8mp boards involving video. I would appreciate testing on as many
boards as possible, along with a Tested-by tag, or a report about any
issues encountered.
Thanks in advance to all testers!
== Review recommendation
I recommend reviewing patches in this order to be understood more
effectively:
* Cover letter
* Patches 1-6: small preliminary cleanups (can be applied independently)
* Patch 11: the goal of this series, but would not work alone
* Patch 10: lets patch 11 work; but in turn it can't work alone
* Patch 8+9: lets patch 10 work; but in turn it can't work alone
* Patch 7: lets patch 8 work
== Series description
This series is not strictly related to DRM bridge hotplug, it is rather a
preparation step. Introducing hotplug would need two different approaches:
one for the new way, for drivers using bridge-connector and
DRM_BRIDGE_ATTACH_NO_CONNECTOR, another for drivers using the "old, legacy
way" where the last bridge is supposed to instantiate the
drm_connector. Hotplug is complicated enough in one case, so it makes sense
to only support the new way.
The hardware I'm working on is an i.MX8MP, whose LCDIF driver is still
using the old way. So this series converts to the new way as a preparation
step.
Patch 11 does the conversion, which is simple. However this would introduce
a regression on some boards. Here's why:
There are 3 instances of the LCDIF in i.MX8MP:
* LCDIF1, driving the DSI output
* LCDIF2, driving the LVDS output
* LCDIF3, driving the HDMI output
The device drivers of peripherals connected to LCDIF1 and LCDIF2 already
support the DRM_BRIDGE_ATTACH_NO_CONNECTOR flag. So far so good.
LCDIF3 is more tricky. The HDMI pipeline is:
LCDIF3 -> fsl,imx8mp-hdmi-pvi -> fsl,imx8mp-hdmi-tx -> HDMI connector
The fsl,imx8mp-hdmi-tx (hdmi-tx) does not support
DRM_BRIDGE_ATTACH_NO_CONNECTOR, but it is based on the dw-hdmi component
which supports it by simply changing a setting in the driver platform
data. Patch 10 does this switch.
However, for that switch to work, the device tree must describe the HDMI
connector (compatible = "hdmi-connector").
Unfortunately not all device trees in mainline have an hdmi-connector
node. Adding one is easy, but would break existing hardware upgrading to a
newer kernel without upgrading the device tree blob. This is addressed by
patch 8+9 reusing an existing approach to add such a node to the live device
tree at init time using a device tree overlay for boards which don't have
one.
Finally, patch 8+9 cannot work alone because of a bad interaction between
devlink and device tree overlays. Patch 7 solves that.
Tested on the Avnet MSC SM2-MB-EP1 board which currently has no
"hdmi-connector" in the upstream device tree.
== Grand plan
This is part of the work to support hotplug of DRM bridges. The grand plan
was discussed in [0].
Here's the work breakdown (➜ marks the current series):
1. … add refcounting to DRM bridges struct drm_bridge,
based on devm_drm_bridge_alloc()
A. ✔ add new alloc API and refcounting (v6.16)
B. ✔ convert all bridge drivers to new API (v6.17)
C. ✔ kunit tests (v6.17)
D. ✔ add get/put to drm_bridge_add/remove() + attach/detach()
and warn on old allocation pattern (v6.17)
E. … add get/put on drm_bridge accessors
1. ✔ drm_bridge_chain_get_first_bridge(), add cleanup action (v6.18)
2. ✔ drm_bridge_get_prev_bridge() (v6.18)
3. ✔ drm_bridge_get_next_bridge() (v6.19)
4. ✔ drm_for_each_bridge_in_chain() (v6.19)
5. ✔ drm_bridge_connector_init (v6.19)
6. … protect encoder bridge chain with a mutex
7. … of_drm_find_bridge
a. ✔ add of_drm_get_bridge() (v7.0),
convert basic direct users (v7.0-v7.1)
b. ✔ convert direct of_drm_get_bridge() users, part 2 (v7.0)
c. ✔ convert direct of_drm_get_bridge() users, part 3 (v7.0)
d. ✔… convert direct of_drm_get_bridge() users, part 4
(some v7.1, some pending)
e. convert bridge-only drm_of_find_panel_or_bridge() users
8. drm_of_find_panel_or_bridge, *_of_get_bridge
9. ✔ enforce drm_bridge_add before drm_bridge_attach (v6.19)
F. ✔ debugfs improvements
1. ✔ add top-level 'bridges' file (v6.16)
2. ✔ show refcount and list lingering bridges (v6.19)
2. … handle gracefully atomic updates during bridge removal
A. ✔ Add drm_bridge_enter/exit() to protect device resources (v7.0)
B. … protect private_obj removal from list
C. ✔ Add drm_bridge_clear_and_put() (v7.1)
3. … DSI host-device driver interaction
4. ✔ removing the need for the "always-disconnected" connector
5. ➜ Migrate i.MX LCDIF driver to bridge-connector
6. DRM bridge hotplug
A. Bridge hotplug management in the DRM core
B. Device tree description
[0] https://lore.kernel.org/lkml/20250206-hotplug-drm-bridge-v6-0-9d6f2c9c3058@bootlin.com/#t
Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
---
Changes in v3:
- Patch 8: simplified overlay, handle of_overlay_fdt_apply() errors, use
of_graph_get_endpoint_by_regs() + add warning in separate patch
- Updated cover and mentioned the hardware used for testing
- Minor fixes to other patches
- Link to v2: https://patch.msgid.link/20260330-drm-lcdif-dbanc-v2-0-c7f2af536a24@bootlin.com
Changes in v2:
- Dropped patch removing the loop in lcdif_attach_bridge, adapted following
patches as needed, added patch to use __free on the ep pointer
- Added new cleanup patch (patch 6)
- Build the fixup module unconditionally
- patch 7: fix returned error codes
- patch 1: fix cleanup action
- Various minor improvements based on reviews, see per-patch changelog
- Removed bouncing recipients from Cc
- Link to v1: https://lore.kernel.org/r/20260320-drm-lcdif-dbanc-v1-0-479a04133e70@bootlin.com
---
Luca Ceresoli (11):
drm/mxsfb/lcdif: simplify remote pointer management using __free
drm/mxsfb/lcdif: simplify ep pointer management using __free
drm/mxsfb/lcdif: use dev_err_probe() consistently in lcdif_attach_bridge
drm/mxsfb/lcdif: move iteration-specific variables declaration inside loop in lcdif_attach_bridge
drm/bridge: dw-hdmi: document the output_port field
drm/bridge: dw-hdmi: warn on unsupported attach combination
drm/bridge: dw-hdmi: move next_bridge lookup to attach time
drm/bridge: imx8mp-hdmi-tx-connector-fixup: add an hdmi-connector when missing using a DT overlay at boot time
drm/bridge: imx8mp-hdmi-tx-connector-fixup: show a warning when adding the overlay
drm/bridge: imx8mp-hdmi-tx: switch to DRM_BRIDGE_ATTACH_NO_CONNECTOR
drm/mxsfb/lcdif: use DRM_BRIDGE_ATTACH_NO_CONNECTOR and the bridge-connector
drivers/gpu/drm/bridge/imx/Kconfig | 18 ++++++
drivers/gpu/drm/bridge/imx/Makefile | 2 +
.../bridge/imx/imx8mp-hdmi-tx-connector-fixup.c | 75 ++++++++++++++++++++++
.../bridge/imx/imx8mp-hdmi-tx-connector-fixup.dtso | 33 ++++++++++
drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 1 +
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 49 ++++++--------
drivers/gpu/drm/mxsfb/Kconfig | 2 +
drivers/gpu/drm/mxsfb/lcdif_drv.c | 69 ++++++++++----------
include/drm/bridge/dw_hdmi.h | 6 ++
9 files changed, 192 insertions(+), 63 deletions(-)
---
base-commit: 5304ff59fe90c4f89c1ba8fea5e07d4a6673a9bf
change-id: 20260306-drm-lcdif-dbanc-83dd948327de
Best regards,
--
Luca Ceresoli <luca.ceresoli@bootlin.com>
^ permalink raw reply
* Re: [PATCH v2 1/3] arm64: dts: qcom: sdm845-shift-axolotl: Enable sdcard
From: Konrad Dybcio @ 2026-04-02 9:23 UTC (permalink / raw)
To: david, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Dylan Van Assche
Cc: linux-arm-msm, Petr Hodina, Casey Connolly, Dmitry Baryshkov,
Alexander Martinz, devicetree, linux-kernel, phone-devel
In-Reply-To: <20260401-axolotl-misc-p1-v2-1-f3af384bbb50@ixit.cz>
On 4/1/26 6:51 PM, David Heidelberg via B4 Relay wrote:
> From: Casey Connolly <casey.connolly@linaro.org>
>
> The SHIFT6mq features an sdcard slot, add it.
>
> Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
> Signed-off-by: David Heidelberg <david@ixit.cz>
> ---
> arch/arm64/boot/dts/qcom/sdm845-shift-axolotl.dts | 44 +++++++++++++++++++++++
> 1 file changed, 44 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/qcom/sdm845-shift-axolotl.dts b/arch/arm64/boot/dts/qcom/sdm845-shift-axolotl.dts
> index 740eb22550724..c394350998c26 100644
> --- a/arch/arm64/boot/dts/qcom/sdm845-shift-axolotl.dts
> +++ b/arch/arm64/boot/dts/qcom/sdm845-shift-axolotl.dts
> @@ -600,6 +600,24 @@ &qupv3_id_1 {
> status = "okay";
> };
>
> +&sdhc_2 {
> + status = "okay";
couple nits:
Status should be last, although the file is all over the place
> +
> + pinctrl-names = "default";
> + pinctrl-0 = <&sdc2_default_state &sdc2_card_det_n>;
preferably in this order
xxx
xxx-names
[...]
> + sdc2_default_state: sdc2-default-state {
> + clk-pins {
> + pins = "sdc2_clk";
> + bias-disable;
> + drive-strength = <16>;
All other pin definitions in this file have the bias property below
drive-strength (like the card_det_n node you're adding)
other than that
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Konrad
^ permalink raw reply
* Re: [PATCH 1/3] dt-bindings: cache: qcom,llcc: Document Hawi and future SoCs
From: Krzysztof Kozlowski @ 2026-04-02 9:19 UTC (permalink / raw)
To: Francisco Munoz Ruiz
Cc: Bjorn Andersson, Konrad Dybcio, Krzysztof Kozlowski, Conor Dooley,
Jonathan Cameron, Rob Herring, Kees Cook, Gustavo A. R. Silva,
linux-arm-msm, devicetree, linux-kernel, linux-hardening,
Konrad Dybcio
In-Reply-To: <20260401-external_llcc_changes2set-v1-1-97645ede9f6a@oss.qualcomm.com>
On Wed, Apr 01, 2026 at 08:01:34PM -0700, Francisco Munoz Ruiz wrote:
> Add documentation for the Last Level Cache Controller (LLCC) bindings to
> support Hawi and upcoming Qualcomm SoCs where the System Cache Table (SCT)
> is programmed by firmware outside of Linux.
>
> Introduce a property that specifies the base address of the shared memory
> region from which the driver should read SCT descriptors provided by
> firmware.
Subject - I do not see any future SoCs in the binding. Which future SoCs
are you documenting here?
>
> Signed-off-by: Francisco Munoz Ruiz <francisco.ruiz@oss.qualcomm.com>
> Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> .../devicetree/bindings/cache/qcom,llcc.yaml | 29 ++++++++++++++++++----
> 1 file changed, 24 insertions(+), 5 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/cache/qcom,llcc.yaml b/Documentation/devicetree/bindings/cache/qcom,llcc.yaml
> index 995d57815781..ca1313de10ca 100644
> --- a/Documentation/devicetree/bindings/cache/qcom,llcc.yaml
> +++ b/Documentation/devicetree/bindings/cache/qcom,llcc.yaml
> @@ -11,16 +11,17 @@ maintainers:
>
> description: |
> LLCC (Last Level Cache Controller) provides last level of cache memory in SoC,
> - that can be shared by multiple clients. Clients here are different cores in the
> - SoC, the idea is to minimize the local caches at the clients and migrate to
> - common pool of memory. Cache memory is divided into partitions called slices
> - which are assigned to clients. Clients can query the slice details, activate
> - and deactivate them.
> + that can be shared by multiple clients. Clients here are different cores in
> + the SoC. The idea is to minimize the local caches at the clients and migrate
> + to a common pool of memory. Cache memory is divided into partitions called
> + slices which are assigned to clients. Clients can query the slice details,
> + activate and deactivate them.
I don't get why you are changing this. I read it and still cannot find
the difference.
Introducing irrelevant changes only obfuscates the work you are doing
here.
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH v3 2/2] pwm: dwc: add of/platform support
From: dongxuyang @ 2026-04-02 9:19 UTC (permalink / raw)
To: ukleinek, robh, krzk+dt, conor+dt, ben-linux, ben.dooks, p.zabel,
linux-pwm, devicetree, linux-kernel
Cc: ningyu, linmin, xuxiang, wangguosheng, pinkesh.vaghela,
Xuyang Dong
In-Reply-To: <20260402091718.1608-1-dongxuyang@eswincomputing.com>
From: Xuyang Dong <dongxuyang@eswincomputing.com>
The dwc pwm controller can be used in non-PCI systems, so allow
either platform or OF based probing.
The controller is reset only when no PWM channel is enabled.
Otherwise, clocks are enabled and the runtime PM state is updated
to reflect the active hardware configuration.
Co-developed-by: Ben Dooks <ben.dooks@codethink.co.uk>
Signed-off-by: Ben Dooks <ben.dooks@codethink.co.uk>
Signed-off-by: Xiang Xu <xuxiang@eswincomputing.com>
Signed-off-by: Guosheng Wang <wangguosheng@eswincomputing.com>
Signed-off-by: Xuyang Dong <dongxuyang@eswincomputing.com>
---
drivers/pwm/Kconfig | 10 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-dwc-core.c | 101 ++++++++---
drivers/pwm/pwm-dwc-of.c | 331 +++++++++++++++++++++++++++++++++++++
drivers/pwm/pwm-dwc.h | 25 ++-
5 files changed, 439 insertions(+), 29 deletions(-)
create mode 100644 drivers/pwm/pwm-dwc-of.c
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 6f3147518376..50aea24b6168 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -249,6 +249,16 @@ config PWM_DWC
To compile this driver as a module, choose M here: the module
will be called pwm-dwc.
+config PWM_DWC_OF
+ tristate "DesignWare PWM Controller (OF bus)"
+ depends on HAS_IOMEM && (OF || COMPILE_TEST)
+ select PWM_DWC_CORE
+ help
+ PWM driver for Synopsys DWC PWM Controller on an OF bus or
+ a platform bus.
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-dwc-of.
+
config PWM_EP93XX
tristate "Cirrus Logic EP93xx PWM support"
depends on ARCH_EP93XX || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 0dc0d2b69025..470411a7e5ea 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_CRC) += pwm-crc.o
obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o
obj-$(CONFIG_PWM_DWC_CORE) += pwm-dwc-core.o
obj-$(CONFIG_PWM_DWC) += pwm-dwc.o
+obj-$(CONFIG_PWM_DWC_OF) += pwm-dwc-of.o
obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o
obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o
obj-$(CONFIG_PWM_GPIO) += pwm-gpio.o
diff --git a/drivers/pwm/pwm-dwc-core.c b/drivers/pwm/pwm-dwc-core.c
index 6dabec93a3c6..55dd503842c3 100644
--- a/drivers/pwm/pwm-dwc-core.c
+++ b/drivers/pwm/pwm-dwc-core.c
@@ -12,6 +12,7 @@
#define DEFAULT_SYMBOL_NAMESPACE "dwc_pwm"
#include <linux/bitops.h>
+#include <linux/clk.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -44,21 +45,52 @@ static int __dwc_pwm_configure_timer(struct dwc_pwm *dwc,
u32 high;
u32 low;
- /*
- * Calculate width of low and high period in terms of input clock
- * periods and check are the result within HW limits between 1 and
- * 2^32 periods.
- */
- tmp = DIV_ROUND_CLOSEST_ULL(state->duty_cycle, dwc->clk_ns);
- if (tmp < 1 || tmp > (1ULL << 32))
- return -ERANGE;
- low = tmp - 1;
-
- tmp = DIV_ROUND_CLOSEST_ULL(state->period - state->duty_cycle,
- dwc->clk_ns);
- if (tmp < 1 || tmp > (1ULL << 32))
- return -ERANGE;
- high = tmp - 1;
+ if (dwc->clk)
+ dwc->clk_rate = clk_get_rate(dwc->clk);
+
+ if (dwc->features & DWC_TIM_CTRL_0N100PWM_EN) {
+ /*
+ * Calculate width of low and high period in terms of input
+ * clock periods and check are the result within HW limits
+ * between 0 and 2^32 periods.
+ */
+ tmp = state->duty_cycle * dwc->clk_rate;
+ tmp = DIV_ROUND_UP_ULL(tmp, NSEC_PER_SEC);
+ if (tmp >= (1ULL << 32))
+ return -ERANGE;
+
+ if (pwm->args.polarity == PWM_POLARITY_INVERSED)
+ high = tmp;
+ else
+ low = tmp;
+
+ tmp = (state->period - state->duty_cycle) * dwc->clk_rate;
+ tmp = DIV_ROUND_UP_ULL(tmp, NSEC_PER_SEC);
+ if (tmp >= (1ULL << 32))
+ return -ERANGE;
+
+ if (pwm->args.polarity == PWM_POLARITY_INVERSED)
+ low = tmp;
+ else
+ high = tmp;
+ } else {
+ /*
+ * Calculate width of low and high period in terms of input
+ * clock periods and check are the result within HW limits
+ * between 1 and 2^32 periods.
+ */
+ tmp = state->duty_cycle * dwc->clk_rate;
+ tmp = DIV_ROUND_UP_ULL(tmp, NSEC_PER_SEC);
+ if (tmp < 1 || tmp > (1ULL << 32))
+ return -ERANGE;
+ low = tmp - 1;
+
+ tmp = (state->period - state->duty_cycle) * dwc->clk_rate;
+ tmp = DIV_ROUND_UP_ULL(tmp, NSEC_PER_SEC);
+ if (tmp < 1 || tmp > (1ULL << 32))
+ return -ERANGE;
+ high = tmp - 1;
+ }
/*
* Specification says timer usage flow is to disable timer, then
@@ -74,6 +106,7 @@ static int __dwc_pwm_configure_timer(struct dwc_pwm *dwc,
* width of low period and latter the width of high period in terms
* multiple of input clock periods:
* Width = ((Count + 1) * input clock period).
+ * Width = (Count * input clock period) : supported 0% and 100%).
*/
dwc_pwm_writel(dwc, low, DWC_TIM_LD_CNT(pwm->hwpwm));
dwc_pwm_writel(dwc, high, DWC_TIM_LD_CNT2(pwm->hwpwm));
@@ -85,6 +118,9 @@ static int __dwc_pwm_configure_timer(struct dwc_pwm *dwc,
* periods are set by Load Count registers.
*/
ctrl = DWC_TIM_CTRL_MODE_USER | DWC_TIM_CTRL_PWM;
+ if (dwc->features & DWC_TIM_CTRL_0N100PWM_EN)
+ ctrl |= DWC_TIM_CTRL_0N100PWM_EN;
+
dwc_pwm_writel(dwc, ctrl, DWC_TIM_CTRL(pwm->hwpwm));
/*
@@ -121,11 +157,17 @@ static int dwc_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state)
{
struct dwc_pwm *dwc = to_dwc_pwm(chip);
+ unsigned long clk_rate;
u64 duty, period;
u32 ctrl, ld, ld2;
pm_runtime_get_sync(pwmchip_parent(chip));
+ if (dwc->clk)
+ dwc->clk_rate = clk_get_rate(dwc->clk);
+
+ clk_rate = dwc->clk_rate;
+
ctrl = dwc_pwm_readl(dwc, DWC_TIM_CTRL(pwm->hwpwm));
ld = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(pwm->hwpwm));
ld2 = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(pwm->hwpwm));
@@ -137,17 +179,32 @@ static int dwc_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
* based on the timer load-count only.
*/
if (ctrl & DWC_TIM_CTRL_PWM) {
- duty = (ld + 1) * dwc->clk_ns;
- period = (ld2 + 1) * dwc->clk_ns;
- period += duty;
+ if (dwc->features & DWC_TIM_CTRL_0N100PWM_EN) {
+ if (pwm->args.polarity == PWM_POLARITY_INVERSED)
+ duty = ld2;
+ else
+ duty = ld;
+ period = (u64)ld + ld2;
+ } else {
+ duty = ld + 1;
+ period = ld2 + 1;
+ period += duty;
+ }
} else {
- duty = (ld + 1) * dwc->clk_ns;
+ duty = ld + 1;
period = duty * 2;
}
state->polarity = PWM_POLARITY_INVERSED;
- state->period = period;
- state->duty_cycle = duty;
+ /*
+ * If the ld register is at its maximum value. The duty value is
+ * 4,294,967,295 (0xFFFF FFFF). The product (duty * NSEC_PER_SEC)
+ * is guaranteed to be less than 2^64.
+ */
+ duty *= NSEC_PER_SEC;
+ period *= NSEC_PER_SEC;
+ state->period = DIV_ROUND_UP_ULL(period, clk_rate);
+ state->duty_cycle = DIV_ROUND_UP_ULL(duty, clk_rate);
pm_runtime_put_sync(pwmchip_parent(chip));
@@ -169,7 +226,7 @@ struct pwm_chip *dwc_pwm_alloc(struct device *dev)
return chip;
dwc = to_dwc_pwm(chip);
- dwc->clk_ns = 10;
+ dwc->clk_rate = NSEC_PER_SEC / 10;
chip->ops = &dwc_pwm_ops;
return chip;
diff --git a/drivers/pwm/pwm-dwc-of.c b/drivers/pwm/pwm-dwc-of.c
new file mode 100644
index 000000000000..dc3361f44121
--- /dev/null
+++ b/drivers/pwm/pwm-dwc-of.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DesignWare PWM Controller driver OF
+ *
+ * Copyright (C) 2026 SiFive, Inc.
+ */
+
+#define DEFAULT_SYMBOL_NAMESPACE "dwc_pwm_of"
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/pwm.h>
+#include <linux/reset.h>
+
+#include "pwm-dwc.h"
+
+static int dwc_pwm_plat_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dwc_pwm_drvdata *data;
+ u32 ctrl[DWC_TIMERS_TOTAL];
+ struct pwm_chip *chip;
+ struct dwc_pwm *dwc;
+ bool pwm_en = false;
+ u32 nr_pwm, tim_id;
+ unsigned int i;
+ int ret;
+
+ data = devm_kzalloc(dev, struct_size(data, chips, 1), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ chip = dwc_pwm_alloc(dev);
+ if (IS_ERR(chip))
+ return dev_err_probe(dev, -ENOMEM, "failed to alloc pwm\n");
+
+ dwc = to_dwc_pwm(chip);
+
+ dwc->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dwc->base))
+ return PTR_ERR(dwc->base);
+
+ if (!device_property_read_u32(dev, "snps,pwm-number", &nr_pwm)) {
+ if (nr_pwm > DWC_TIMERS_TOTAL)
+ dev_warn
+ (dev, "too many PWMs (%d) specified, capping at %d\n",
+ nr_pwm, chip->npwm);
+ else
+ chip->npwm = nr_pwm;
+ }
+
+ dwc->bus_clk = devm_clk_get(dev, "bus");
+ if (IS_ERR(dwc->bus_clk))
+ return dev_err_probe(dev, PTR_ERR(dwc->bus_clk),
+ "failed to get bus clock\n");
+
+ dwc->clk = devm_clk_get(dev, "timer");
+ if (IS_ERR(dwc->clk))
+ return dev_err_probe(dev, PTR_ERR(dwc->clk),
+ "failed to get timer clock\n");
+
+ ret = devm_clk_rate_exclusive_get(dev, dwc->clk);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get exclusive rate\n");
+
+ dwc->clk_rate = clk_get_rate(dwc->clk);
+
+ dwc->rst = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(dwc->rst))
+ return dev_err_probe(dev, PTR_ERR(dwc->rst),
+ "failed to get reset control\n");
+
+ ret = clk_prepare_enable(dwc->bus_clk);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to enable bus clock\n");
+
+ ret = clk_prepare_enable(dwc->clk);
+ if (ret) {
+ dev_err(dev, "failed to enable timer clock\n");
+ goto disable_busclk;
+ }
+
+ /*
+ * Check all channels to see if any channel is enabled.
+ * Read the control register of each channel and extract the enable bit
+ */
+ for (i = 0; i < chip->npwm; i++) {
+ ctrl[i] = dwc_pwm_readl(dwc, DWC_TIM_CTRL(i)) & DWC_TIM_CTRL_EN;
+ if (ctrl[i])
+ pwm_en = true;
+ }
+
+ /* Only issue reset when all channels are disabled */
+ if (!pwm_en) {
+ ret = reset_control_reset(dwc->rst);
+ if (ret) {
+ dev_err(dev, "failed to reset\n");
+ goto disable_clk;
+ }
+ }
+
+ /* init PWM feature */
+ dwc->features = 0;
+ /*
+ * Support for 0% and 100% duty cycle mode was added in version 2.11a
+ * and later.
+ */
+ tim_id = dwc_pwm_readl(dwc, DWC_TIMERS_COMP_VERSION);
+ if (tim_id >= DWC_TIM_VERSION_ID_2_11A)
+ dwc->features |= DWC_TIM_CTRL_0N100PWM_EN;
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret) {
+ dev_err(dev, "failed to add pwm chip");
+ goto reset_assert;
+ }
+
+ data->chips[0] = chip;
+ dev_set_drvdata(dev, data);
+
+ /*
+ * If any PWM channel is enabled, mark device active and hold runtime PM
+ * references for each enabled channel. Otherwise, gate the clocks.
+ */
+ if (pwm_en) {
+ pm_runtime_set_active(dev);
+ for (i = 0; i < chip->npwm; i++) {
+ if (ctrl[i])
+ pm_runtime_get_noresume(dev);
+ }
+ } else {
+ clk_disable_unprepare(dwc->clk);
+ clk_disable_unprepare(dwc->bus_clk);
+ }
+
+ pm_runtime_enable(dev);
+
+ return 0;
+
+reset_assert:
+ reset_control_assert(dwc->rst);
+disable_clk:
+ clk_disable_unprepare(dwc->clk);
+disable_busclk:
+ clk_disable_unprepare(dwc->bus_clk);
+
+ return ret;
+}
+
+static void dwc_pwm_plat_remove(struct platform_device *pdev)
+{
+ struct dwc_pwm_drvdata *data = platform_get_drvdata(pdev);
+ struct pwm_chip *chip = data->chips[0];
+ struct dwc_pwm *dwc = to_dwc_pwm(chip);
+ bool pwm_en = false;
+ unsigned int idx;
+ bool pm_flags;
+
+ /*
+ * Resume the device if it is runtime suspended to allow
+ * safe register access.
+ */
+ pm_flags = pm_runtime_status_suspended(&pdev->dev);
+ if (pm_flags)
+ pm_runtime_get_sync(&pdev->dev);
+
+ for (idx = 0; idx < chip->npwm; idx++) {
+ if (dwc_pwm_readl(dwc, DWC_TIM_CTRL(idx)) & DWC_TIM_CTRL_EN) {
+ pwm_en = true;
+ pm_runtime_put_noidle(&pdev->dev);
+ }
+ }
+
+ /*
+ * Re-suspend the device if it was runtime suspended prior to
+ * the register access.
+ */
+ if (pm_flags)
+ pm_runtime_put_sync(&pdev->dev);
+
+ if (pwm_en) {
+ clk_disable_unprepare(dwc->clk);
+ clk_disable_unprepare(dwc->bus_clk);
+ }
+
+ pm_runtime_disable(&pdev->dev);
+ reset_control_assert(dwc->rst);
+}
+
+static int dwc_pwm_runtime_suspend(struct device *dev)
+{
+ struct dwc_pwm_drvdata *data = dev_get_drvdata(dev);
+ struct pwm_chip *chip = data->chips[0];
+ struct dwc_pwm *dwc = to_dwc_pwm(chip);
+
+ clk_disable_unprepare(dwc->clk);
+ clk_disable_unprepare(dwc->bus_clk);
+
+ return 0;
+}
+
+static int dwc_pwm_runtime_resume(struct device *dev)
+{
+ struct dwc_pwm_drvdata *data = dev_get_drvdata(dev);
+ struct pwm_chip *chip = data->chips[0];
+ struct dwc_pwm *dwc = to_dwc_pwm(chip);
+ int ret;
+
+ ret = clk_prepare_enable(dwc->bus_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable bus clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dwc->clk);
+ if (ret) {
+ dev_err(dev, "failed to enable timer clock: %d\n", ret);
+ clk_disable_unprepare(dwc->bus_clk);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dwc_pwm_suspend(struct device *dev)
+{
+ struct dwc_pwm_drvdata *data = dev_get_drvdata(dev);
+ struct pwm_chip *chip = data->chips[0];
+ struct dwc_pwm *dwc = to_dwc_pwm(chip);
+ unsigned int idx;
+ int ret;
+
+ if (pm_runtime_status_suspended(dev)) {
+ ret = dwc_pwm_runtime_resume(dev);
+ if (ret)
+ return ret;
+ }
+
+ for (idx = 0; idx < chip->npwm; idx++) {
+ if (chip->pwms[idx].state.enabled)
+ return -EBUSY;
+
+ dwc->ctx[idx].cnt = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT(idx));
+ dwc->ctx[idx].cnt2 = dwc_pwm_readl(dwc, DWC_TIM_LD_CNT2(idx));
+ dwc->ctx[idx].ctrl = dwc_pwm_readl(dwc, DWC_TIM_CTRL(idx));
+ }
+
+ ret = dwc_pwm_runtime_suspend(dev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int dwc_pwm_resume(struct device *dev)
+{
+ struct dwc_pwm_drvdata *data = dev_get_drvdata(dev);
+ struct pwm_chip *chip = data->chips[0];
+ struct dwc_pwm *dwc = to_dwc_pwm(chip);
+ unsigned int idx;
+ bool pm_flags;
+ int ret;
+
+ /* Check if device was runtime suspended before system resume */
+ pm_flags = pm_runtime_status_suspended(dev);
+ if (pm_flags) {
+ /*
+ * Use PM framework to resume device
+ * (calls dwc_pwm_runtime_resume)
+ */
+ ret = pm_runtime_get_sync(dev);
+ if (ret)
+ return ret;
+ } else {
+ /*
+ * Device was active, but clocks might be off after system sleep
+ * Call runtime_resume directly to restore hardware state
+ */
+ ret = dwc_pwm_runtime_resume(dev);
+ if (ret)
+ return ret;
+ }
+
+ for (idx = 0; idx < chip->npwm; idx++) {
+ dwc_pwm_writel(dwc, dwc->ctx[idx].cnt, DWC_TIM_LD_CNT(idx));
+ dwc_pwm_writel(dwc, dwc->ctx[idx].cnt2, DWC_TIM_LD_CNT2(idx));
+ dwc_pwm_writel(dwc, dwc->ctx[idx].ctrl, DWC_TIM_CTRL(idx));
+ }
+
+ if (pm_flags) {
+ /* Balance the refcount taken by pm_runtime_get_sync
+ * if it was used
+ */
+ ret = pm_runtime_put_sync(dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops dwc_pwm_pm_ops = {
+ RUNTIME_PM_OPS(dwc_pwm_runtime_suspend, dwc_pwm_runtime_resume, NULL)
+ SYSTEM_SLEEP_PM_OPS(dwc_pwm_suspend, dwc_pwm_resume)
+};
+
+static const struct of_device_id dwc_pwm_dt_ids[] = {
+ { .compatible = "snps,dw-apb-timers-pwm2" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, dwc_pwm_dt_ids);
+
+static struct platform_driver dwc_pwm_plat_driver = {
+ .driver = {
+ .name = "dwc-pwm",
+ .pm = pm_ptr(&dwc_pwm_pm_ops),
+ .of_match_table = dwc_pwm_dt_ids,
+ },
+ .probe = dwc_pwm_plat_probe,
+ .remove = dwc_pwm_plat_remove,
+};
+
+module_platform_driver(dwc_pwm_plat_driver);
+
+MODULE_ALIAS("platform:dwc-pwm-of");
+MODULE_AUTHOR("Ben Dooks <ben.dooks@codethink.co.uk>");
+MODULE_DESCRIPTION("DesignWare PWM Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-dwc.h b/drivers/pwm/pwm-dwc.h
index 1562594e7f85..75f7c2d031c4 100644
--- a/drivers/pwm/pwm-dwc.h
+++ b/drivers/pwm/pwm-dwc.h
@@ -26,12 +26,19 @@ MODULE_IMPORT_NS("dwc_pwm");
#define DWC_TIMERS_TOTAL 8
/* Timer Control Register */
-#define DWC_TIM_CTRL_EN BIT(0)
-#define DWC_TIM_CTRL_MODE BIT(1)
-#define DWC_TIM_CTRL_MODE_FREE (0 << 1)
-#define DWC_TIM_CTRL_MODE_USER (1 << 1)
-#define DWC_TIM_CTRL_INT_MASK BIT(2)
-#define DWC_TIM_CTRL_PWM BIT(3)
+#define DWC_TIM_CTRL_EN BIT(0)
+#define DWC_TIM_CTRL_MODE BIT(1)
+#define DWC_TIM_CTRL_MODE_FREE (0 << 1)
+#define DWC_TIM_CTRL_MODE_USER BIT(1)
+#define DWC_TIM_CTRL_INT_MASK BIT(2)
+#define DWC_TIM_CTRL_PWM BIT(3)
+#define DWC_TIM_CTRL_0N100PWM_EN BIT(4)
+
+/*
+ * The version 2.11a and later add "Pulse Width Modulation with
+ * 0% and 100% Duty Cycle".
+ */
+#define DWC_TIM_VERSION_ID_2_11A 0x3231312a
struct dwc_pwm_info {
unsigned int nr;
@@ -52,8 +59,12 @@ struct dwc_pwm_ctx {
struct dwc_pwm {
void __iomem *base;
- unsigned int clk_ns;
+ struct clk *bus_clk;
+ struct clk *clk;
+ unsigned long clk_rate;
+ struct reset_control *rst;
struct dwc_pwm_ctx ctx[DWC_TIMERS_TOTAL];
+ u32 features;
};
static inline struct dwc_pwm *to_dwc_pwm(struct pwm_chip *chip)
--
2.34.1
^ permalink raw reply related
* [PATCH v3 1/2] dt-bindings: pwm: dwc: add reset optional
From: dongxuyang @ 2026-04-02 9:18 UTC (permalink / raw)
To: ukleinek, robh, krzk+dt, conor+dt, ben-linux, ben.dooks, p.zabel,
linux-pwm, devicetree, linux-kernel
Cc: ningyu, linmin, xuxiang, wangguosheng, pinkesh.vaghela,
Xuyang Dong
In-Reply-To: <20260402091718.1608-1-dongxuyang@eswincomputing.com>
From: Xuyang Dong <dongxuyang@eswincomputing.com>
The DesignWare PWM controller provides separate reset signals for each
clock domain, as specified in the hardware documentation. Without
asserting and deasserting these resets during probe, PWM outputs may
remain in an undefined state after system reboot.
To address this, the driver now supports an optional 'resets' property.
A full reset is performed only when no PWM channel is enabled, as
determined by reading the enable bit in each channel's control register.
This allows safe coexistence with bootloaders that have already
configured active PWM channels.
Signed-off-by: Xuyang Dong <dongxuyang@eswincomputing.com>
---
.../devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml b/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml
index 7523a89a1773..fd9f73c75121 100644
--- a/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml
+++ b/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml
@@ -43,6 +43,9 @@ properties:
- const: bus
- const: timer
+ resets:
+ maxItems: 1
+
snps,pwm-number:
$ref: /schemas/types.yaml#/definitions/uint32
description: The number of PWM channels configured for this instance
--
2.34.1
^ permalink raw reply related
* [PATCH v3 0/2] Update designware pwm driver
From: dongxuyang @ 2026-04-02 9:17 UTC (permalink / raw)
To: ukleinek, robh, krzk+dt, conor+dt, ben-linux, ben.dooks, p.zabel,
linux-pwm, devicetree, linux-kernel
Cc: ningyu, linmin, xuxiang, wangguosheng, pinkesh.vaghela,
Xuyang Dong
From: Xuyang Dong <dongxuyang@eswincomputing.com>
There is already a patch [1] for the DesignWare PWM driver,
which is posted by Ben and still under review.
Based on this patch, this series is a continuation of [1]
to add support for IP versions 2.11a and later, which
includes support for "Pulse Width Modulation with 0%
and 100% Duty Cycle".
Supported chips:
ESWIN EIC7700 series SoC.
Test:
Tested this patch on the Sifive HiFive Premier P550 (which uses the EIC7700
SoC).
[1] https://lore.kernel.org/lkml/20230907161242.67190-1-ben.dooks@codethink.co.uk/
Updates:
Change in v3:
- YAML:
- Added a clear justification for the optional resets property. It is
required to support proper controller initialization when no PWM
channel is active at boot time, while allowing the driver to skip
reset deassertion if any channel is already enabled.
- Driver:
- Update the boundary value check of tmp in __dwc_pwm_configure_timer()
for DWC_TIM_CTRL_0N100PWM_EN.
- Replace 'sizeof(struct dwc_pwm_drvdata)' with
'struct_size(data, chips, 1)'.
- Drop devm_clk_get_enabled() in favor of devm_clk_get() with explicit
clk_prepare_enable() and clk_disable_unprepare() allowing runtime PM
to manage clock state.
- Replace devm_reset_control_get_optional_exclusive_deasserted() with
devm_reset_control_get_optional_exclusive() and issue a full reset via
reset_control_reset() only when no PWM channel is active at probe time.
- Detect bootloader-enabled PWM channels by reading the enable bit, and
initialize runtime PM as active for those channels by calling
pm_runtime_set_active() and pm_runtime_get_noresume().
- Remove autosuspend as it is not required for this driver.
- Use explicit pm_runtime_enable() and pm_runtime_disable() instead of
the managed devm_pm_runtime_enable() variant to ensure correct cleanup.
- On device removal, recheck the channel enable status. If any channel
remains active, call pm_runtime_put_noidle() before disabling clocks
via clk_disable_unprepare().
Resume device before register access during removal if it is runtime
suspended, and re-suspend it afterward.
- If device is suspended, resume it before register access during system
resume/suspend.
- Use pm_ptr() instead of pm_sleep_ptr() for correct PM operation.
- Link to v2: https://lore.kernel.org/all/20260306093000.2065-1-dongxuyang@eswincomputing.com/
Change in v2:
- YAML:
- Remove eswin,eic7700-pwm.yaml. Use snps,dw-apb-timers-pwm2.yaml.
The description in snps,dw-apb-timers-pwm2.yaml is better.
- Add the resets property as optional, as defined in the databook.
- Remove snps,pwm-full-range-enable as no additional property is needed.
- Driver:
- Change the file from pwm-dwc-eic7700.c to pwm-dwc-of.c from [1].
- Define DWC_TIM_VERSION_ID_2_11A 2.11a as the baseline version.
- Enable the 0% and 100% duty cycle mode by setting dwc->feature if
the version read from the TIMERS_COMP_VERSION register is later
than or equal to DWC_TIM_VERSION_ID_2_11A.
- Use the DIV_ROUND_UP_ULL() to calculate width in the .apply and
.get_state.
- Additionally, Power Management (PM) support has been added to the
pwm-dwc-of.c driver.
- Drop the headers that are not used.
- Use devm_clk_get_enabled() instead of devm_clk_get().
- Drop of_match_ptr.
- Fix build error with 1ULL << 32.
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202512061720.j31AsgM7-lkp@intel.com/
- Link to v1: https://lore.kernel.org/all/20251205090411.1388-1-dongxuyang@eswincomputing.com/
- Link to v9: https://lore.kernel.org/lkml/20230907161242.67190-1-ben.dooks@codethink.co.uk/
Xuyang Dong (2):
dt-bindings: pwm: dwc: add reset optional
pwm: dwc: add of/platform support
.../bindings/pwm/snps,dw-apb-timers-pwm2.yaml | 3 +
drivers/pwm/Kconfig | 10 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-dwc-core.c | 101 ++++--
drivers/pwm/pwm-dwc-of.c | 331 ++++++++++++++++++
drivers/pwm/pwm-dwc.h | 25 +-
6 files changed, 442 insertions(+), 29 deletions(-)
create mode 100644 drivers/pwm/pwm-dwc-of.c
--
2.34.1
^ permalink raw reply
* Re: [PATCH v8 15/15] arm64: dts: qcom: sdm845-lg-{judyln, judyp}: Reference memory region in fb
From: Konrad Dybcio @ 2026-04-02 9:16 UTC (permalink / raw)
To: Paul Sajna, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, David Heidelberg
Cc: linux-arm-msm, devicetree, linux-kernel, phone-devel,
~postmarketos/upstreaming, Amir Dahan, Christopher Brown
In-Reply-To: <20260401-judyln-dts-v8-15-cf13065e52ab@postmarketos.org>
On 4/2/26 8:43 AM, Paul Sajna wrote:
> To prevent duplicating the framebuffer address and size point out the
> existing framebuffer memory region instead of specifying the address
> manually.
>
> Signed-off-by: Paul Sajna <sajattack@postmarketos.org>
> ---
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Konrad
^ permalink raw reply
* Re: [PATCH v6 02/10] reset: Add Realtek basic reset support
From: Philipp Zabel @ 2026-04-02 9:15 UTC (permalink / raw)
To: Yu-Chun Lin, mturquette, sboyd, robh, krzk+dt, conor+dt, cylee12,
afaerber, jyanchou
Cc: devicetree, linux-clk, linux-kernel, linux-arm-kernel,
linux-realtek-soc, james.tai, cy.huang, stanley_chang
In-Reply-To: <20260402073957.2742459-3-eleanor.lin@realtek.com>
On Do, 2026-04-02 at 15:39 +0800, Yu-Chun Lin wrote:
> From: Cheng-Yu Lee <cylee12@realtek.com>
>
> Define the reset operations backed by a regmap-based register interface
> and prepare the reset controller to be registered through the reset
> framework.
>
> Since the reset controllers on Realtek SoCs often share the same register
> space with the clock controllers, this common framework is designed to
> extract the regmap and device tree node from the parent device
> (e.g., an auxiliary device parent).
>
> Signed-off-by: Cheng-Yu Lee <cylee12@realtek.com>
> Co-developed-by: Yu-Chun Lin <eleanor.lin@realtek.com>
> Signed-off-by: Yu-Chun Lin <eleanor.lin@realtek.com>
> ---
> Changes in v6:
> - Remove the global header include/linux/reset/realtek.h and use a local common.h
> instead.
> - Extract regmap and of_node directly from the parent device.
> - Remove struct rtk_reset_initdata. Now, pass struct rtk_reset_data directly when
> calling rtk_reset_controller_add().
> ---
> MAINTAINERS | 1 +
> drivers/reset/Kconfig | 1 +
> drivers/reset/Makefile | 1 +
> drivers/reset/realtek/Kconfig | 3 ++
> drivers/reset/realtek/Makefile | 2 +
> drivers/reset/realtek/common.c | 85 ++++++++++++++++++++++++++++++++++
> drivers/reset/realtek/common.h | 29 ++++++++++++
> 7 files changed, 122 insertions(+)
> create mode 100644 drivers/reset/realtek/Kconfig
> create mode 100644 drivers/reset/realtek/Makefile
> create mode 100644 drivers/reset/realtek/common.c
> create mode 100644 drivers/reset/realtek/common.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 07e73bf621b0..8f355896583b 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22240,6 +22240,7 @@ L: devicetree@vger.kernel.org
> L: linux-clk@vger.kernel.org
> S: Supported
> F: Documentation/devicetree/bindings/clock/realtek*
> +F: drivers/reset/realtek/*
> F: include/dt-bindings/clock/realtek*
> F: include/dt-bindings/reset/realtek*
>
> diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
> index 7ce151f6a7e4..03be1931f264 100644
> --- a/drivers/reset/Kconfig
> +++ b/drivers/reset/Kconfig
> @@ -398,6 +398,7 @@ config RESET_ZYNQMP
>
> source "drivers/reset/amlogic/Kconfig"
> source "drivers/reset/hisilicon/Kconfig"
> +source "drivers/reset/realtek/Kconfig"
> source "drivers/reset/spacemit/Kconfig"
> source "drivers/reset/starfive/Kconfig"
> source "drivers/reset/sti/Kconfig"
> diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
> index fc0cc99f8514..4407d1630070 100644
> --- a/drivers/reset/Makefile
> +++ b/drivers/reset/Makefile
> @@ -2,6 +2,7 @@
> obj-y += core.o
> obj-y += amlogic/
> obj-y += hisilicon/
> +obj-y += realtek/
> obj-y += spacemit/
> obj-y += starfive/
> obj-y += sti/
> diff --git a/drivers/reset/realtek/Kconfig b/drivers/reset/realtek/Kconfig
> new file mode 100644
> index 000000000000..99a14d355803
> --- /dev/null
> +++ b/drivers/reset/realtek/Kconfig
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config RESET_RTK_COMMON
> + bool
Please make this build-testable with COMPILE_TEST.
> diff --git a/drivers/reset/realtek/Makefile b/drivers/reset/realtek/Makefile
> new file mode 100644
> index 000000000000..b59a3f7f2453
> --- /dev/null
> +++ b/drivers/reset/realtek/Makefile
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_RESET_RTK_COMMON) += common.o
> diff --git a/drivers/reset/realtek/common.c b/drivers/reset/realtek/common.c
> new file mode 100644
> index 000000000000..ea7ff27117e7
> --- /dev/null
> +++ b/drivers/reset/realtek/common.c
> @@ -0,0 +1,85 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2019 Realtek Semiconductor Corporation
> + */
> +
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/regmap.h>
> +#include "common.h"
> +
> +static inline struct rtk_reset_data *to_rtk_reset_controller(struct reset_controller_dev *r)
> +{
> + return container_of(r, struct rtk_reset_data, rcdev);
> +}
> +
> +static inline struct rtk_reset_desc *rtk_reset_get_desc(struct rtk_reset_data *data,
> + unsigned long idx)
> +{
> + return &data->descs[idx];
> +}
> +
> +static int rtk_reset_assert(struct reset_controller_dev *rcdev,
> + unsigned long idx)
> +{
> + struct rtk_reset_data *data = to_rtk_reset_controller(rcdev);
> + struct rtk_reset_desc *desc = rtk_reset_get_desc(data, idx);
> + u32 mask = desc->write_en ? (0x3 << desc->bit) : BIT(desc->bit);
> + u32 val = desc->write_en ? (0x2 << desc->bit) : 0;
> +
> + return regmap_update_bits(data->regmap, desc->ofs, mask, val);
> +}
> +
> +static int rtk_reset_deassert(struct reset_controller_dev *rcdev,
> + unsigned long idx)
> +{
> + struct rtk_reset_data *data = to_rtk_reset_controller(rcdev);
> + struct rtk_reset_desc *desc = rtk_reset_get_desc(data, idx);
> + u32 mask = desc->write_en ? (0x3 << desc->bit) : BIT(desc->bit);
> + u32 val = mask;
> +
> + return regmap_update_bits(data->regmap, desc->ofs, mask, val);
> +}
> +
> +static int rtk_reset_status(struct reset_controller_dev *rcdev,
> + unsigned long idx)
> +{
> + struct rtk_reset_data *data = to_rtk_reset_controller(rcdev);
> + struct rtk_reset_desc *desc = rtk_reset_get_desc(data, idx);
> + u32 val;
> + int ret;
> +
> + ret = regmap_read(data->regmap, desc->ofs, &val);
> + if (ret)
> + return ret;
> +
> + return !((val >> desc->bit) & 1);
> +}
> +
> +static const struct reset_control_ops rtk_reset_ops = {
> + .assert = rtk_reset_assert,
> + .deassert = rtk_reset_deassert,
> + .status = rtk_reset_status,
> +};
> +
> +/* The caller must initialize data->rcdev.nr_resets and data->descs before
> + * calling rtk_reset_controller_add().
> + */
> +int rtk_reset_controller_add(struct device *dev,
> + struct rtk_reset_data *data)
> +{
> + struct device *parent = dev->parent;
> +
> + data->regmap = dev_get_regmap(parent, NULL);
> + if (!data->regmap)
> + return -ENODEV;
> +
> + data->rcdev.owner = THIS_MODULE;
The rtk_reset_desc arrays used by this code live in the calling module,
so it would be better to let the caller initialize .owner as well.
It doesn't make a difference in practice, since CONFIG_RESET_RTK_COMMON
isn't tristate (right now).
regards
Philipp
^ permalink raw reply
* Re: [PATCH v2 06/10] drm/bridge: dw-hdmi: warn on unsupported attach combination
From: Damon Ding @ 2026-04-02 9:14 UTC (permalink / raw)
To: Luca Ceresoli, Marek Vasut, Stefan Agner, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Frank Li, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Liu Ying, Rob Herring,
Saravana Kannan
Cc: Kory Maincent (TI.com), Hervé Codina, Hui Pu, Ian Ray,
Thomas Petazzoni, dri-devel, imx, linux-arm-kernel, linux-kernel,
devicetree, Adam Ford, Alexander Stein, Christopher Obbard,
Daniel Scally, Emanuele Ghidoli, Fabio Estevam, Francesco Dolcini,
Frieder Schrempf, Gilles Talis, Goran Rađenović,
Heiko Schocher, Josua Mayer, Kieran Bingham, Marco Felsch,
Martyn Welch, Oleksij Rempel, Peng Fan, Richard Hu, Shengjiu Wang,
Stefan Eichenberger, Vitor Soares
In-Reply-To: <20260330-drm-lcdif-dbanc-v2-6-c7f2af536a24@bootlin.com>
[-- Attachment #1: Type: text/plain, Size: 2768 bytes --]
Hi Luca,
On 3/31/2026 3:25 AM, Luca Ceresoli wrote:
> dw-hdmi can operate in two different modes, depending on the platform data
> as set by the driver:
>
> A. hdmi->plat_data->output_port = 0:
> the HDMI output (port@1) in device tree is not used
>
> B. hdmi->plat_data->output_port = 1:
> the HDMI output (port@1) is parsed to find the next bridge
>
> Only case B is supported when the DRM_BRIDGE_ATTACH_NO_CONNECTOR flag is
> passed to the attach callback. Emit a warning when this is violated. Also
> return -EINVAL which would be returned by drm_bridge_attach() right after
> anyway.
>
> Reviewed-by: Liu Ying <victor.liu@nxp.com>
> Tested-by: Martyn Welch <martyn.welch@collabora.com>
> Tested-by: Alexander Stein <alexander.stein@ew.tq-group.com> # TQMa8MPxL/MBa8MPxL
> Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
> ---
> Note: Returning when the warning triggers does not change the functional
> behaviour of this function. It is not strictly necessary in this patch but
> it will have to be done anyway in the following patch.
> ---
> drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> index 0296e110ce65..ab1a6a8783cd 100644
> --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
> @@ -2910,6 +2910,10 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
> {
> struct dw_hdmi *hdmi = bridge->driver_private;
>
> + /* DRM_BRIDGE_ATTACH_NO_CONNECTOR requires a remote-endpoint to the next bridge */
> + if (WARN_ON((flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) && !hdmi->plat_data->output_port))
> + return -EINVAL;
> +
> if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
> return drm_bridge_attach(encoder, hdmi->bridge.next_bridge,
> bridge, flags);
>
Since many older Rockchip platforms (RK3288, RK3399, etc.) lack a
hdmi-connector node linked to the HDMI DT node, which corresponds to
case A. Could we relax this restriction and treat cases where
DRM_BRIDGE_ATTACH_NO_CONNECTOR is set but hdmi->plat_data->output_port =
0 as a new case C?
For Rockchip platforms, the HDMI driver invokes dw_hdmi_bind() to attach
the Synopsys bridge. This sequence differs from that on the XNP
platform, but is similar to the Allwinner implementation.
If we treat the case where DRM_BRIDGE_ATTACH_NO_CONNECTOR is set and
hdmi->plat_data->output_port = 0 as -EINVAL, I will have to modify the
HDMI DT configuration for all Rockchip platforms when adapting to the
bridge-connector framework.
The patch that adapts to the bridge-connector framework and has been
verified OK on RK3399 is attached.
Best regards,
Damon
[-- Attachment #2: dw_hdmi-rockchip_test_env_20260402.patch --]
[-- Type: text/plain, Size: 3041 bytes --]
diff --git a/arch/arm64/boot/dts/rockchip/rk3399-evb-ind.dts b/arch/arm64/boot/dts/rockchip/rk3399-evb-ind.dts
index 70aee1ab904c..5a78e741f113 100644
--- a/arch/arm64/boot/dts/rockchip/rk3399-evb-ind.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3399-evb-ind.dts
@@ -17,6 +17,7 @@ aliases {
chosen {
stdout-path = "serial2:1500000n8";
+ bootargs = "root=PARTUUID=614e0000-0000 rootwait";
};
vcc5v0_sys: regulator-vcc5v0-sys {
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index ada45e8b3e2c..0e97e183a8b3 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2914,7 +2914,7 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
/* DRM_BRIDGE_ATTACH_NO_CONNECTOR requires a remote-endpoint to the next bridge */
if (WARN_ON((flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) && !hdmi->plat_data->output_port))
- return -EINVAL;
+ return 0;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
struct device_node *remote __free(device_node) =
@@ -3628,7 +3628,7 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
if (IS_ERR(hdmi))
return hdmi;
- ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0);
+ ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
dw_hdmi_remove(hdmi);
return ERR_PTR(ret);
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index e7f49fe845ea..69b832d0c8c4 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -76,6 +76,7 @@ config ROCKCHIP_DW_DP
config ROCKCHIP_DW_HDMI
bool "Rockchip specific extensions for Synopsys DW HDMI"
+ select DRM_BRIDGE_CONNECTOR
help
This selects support for Rockchip SoC specific extensions
for the Synopsys DesignWare HDMI driver. If you want to
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 0dc1eb5d2ae3..174a10c1d7c2 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -13,6 +13,7 @@
#include <linux/regulator/consumer.h>
#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_bridge_connector.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
@@ -542,6 +543,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
struct drm_device *drm = data;
struct drm_encoder *encoder;
struct rockchip_hdmi *hdmi;
+ struct drm_connector *connector;
int ret;
if (!pdev->dev.of_node)
@@ -619,7 +621,14 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
goto err_bind;
}
- return 0;
+ connector = drm_bridge_connector_init(drm, encoder);
+ if (IS_ERR(connector)) {
+ ret = PTR_ERR(connector);
+ dev_err(hdmi->dev, "Failed to initialize bridge_connector\n");
+ goto err_bind;
+ }
+
+ return drm_connector_attach_encoder(connector, encoder);
err_bind:
drm_encoder_cleanup(encoder);
^ permalink raw reply related
* RE: [PATCH v2 20/24] arm64: dts: renesas: r9a09g047: Add R-Car Sound support
From: Biju Das @ 2026-04-02 9:12 UTC (permalink / raw)
To: John Madieu, Geert Uytterhoeven, Kuninori Morimoto, Vinod Koul,
Mark Brown, Rob Herring, Krzysztof Kozlowski
Cc: Michael Turquette, Stephen Boyd, Conor Dooley, Frank Li,
Liam Girdwood, magnus.damm, Thomas Gleixner, Jaroslav Kysela,
Takashi Iwai, Philipp Zabel, Claudiu.Beznea, Fabrizio Castro,
Prabhakar Mahadev Lad, John Madieu,
linux-renesas-soc@vger.kernel.org, linux-clk@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
dmaengine@vger.kernel.org, linux-sound@vger.kernel.org,
John Madieu
In-Reply-To: <20260402090524.9137-21-john.madieu.xa@bp.renesas.com>
Hi John,
Thanks for the patch
> -----Original Message-----
> From: John Madieu <john.madieu.xa@bp.renesas.com>
> Sent: 02 April 2026 10:05
> Subject: [PATCH v2 20/24] arm64: dts: renesas: r9a09g047: Add R-Car Sound support
Typo RZ/G3E sound support??
Cheers,
Biju
>
> Add the rzg3e_sound node for the RZ/G3E SoC with all sub-components:
>
> - SSI (Serial Sound Interface) units 0-9
> - SSIU (Serial Sound Interface Unit) units 0-27
> - SRC (Sample Rate Converter) units 0-9
> - CTU (Channel Transfer Unit) units 0-7
> - DVC (Digital Volume Control) units 0-1
> - MIX (Mixer) units 0-1
>
> Wire up all 5 DMA controllers (dmac0-dmac4) for each audio sub-node with repeated channel names, so
> that the DMA core can pick the first available controller.
>
> Signed-off-by: John Madieu <john.madieu.xa@bp.renesas.com>
> ---
>
> Changes:
>
> v2:
> - Remove 2-cells specifier on audio DMA assignment
> - Do not update DMAC #dma-cells anymore
>
> arch/arm64/boot/dts/renesas/r9a09g047.dtsi | 502 +++++++++++++++++++++
> 1 file changed, 502 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/renesas/r9a09g047.dtsi b/arch/arm64/boot/dts/renesas/r9a09g047.dtsi
> index 1ff48c8f98e1..b1e567d71c26 100644
> --- a/arch/arm64/boot/dts/renesas/r9a09g047.dtsi
> +++ b/arch/arm64/boot/dts/renesas/r9a09g047.dtsi
> @@ -918,6 +918,508 @@ rsci9: serial@12803000 {
> status = "disabled";
> };
>
> + snd_rzg3e: sound@13c00000 {
> + /*
> + * #sound-dai-cells is required
> + *
> + * Single DAI : #sound-dai-cells = <0>; <&snd_rzg3e>;
> + * Multi DAI : #sound-dai-cells = <1>; <&snd_rzg3e N>;
> + */
> + /*
> + * #clock-cells is required for audio_clkout0/1/2/3
> + *
> + * clkout : #clock-cells = <0>; <&snd_rzg3e>;
> + * clkout0/1/2/3: #clock-cells = <1>; <&snd_rzg3e N>;
> + */
> + compatible = "renesas,r9a09g047-sound";
> + reg = <0 0x13c00000 0 0x10000>, /* SCU */
> + <0 0x13c20000 0 0x10000>, /* ADG */
> + <0 0x13c30000 0 0x1000>, /* SSIU */
> + <0 0x13c31000 0 0x1F000>, /* SSI */
> + <0 0x13c50000 0 0x10000>; /* Audio DMAC peri peri */
> + reg-names = "scu", "adg", "ssiu", "ssi", "audmapp";
> + clocks = <&cpg CPG_MOD 245>,
> + <&cpg CPG_MOD 394>,
> + <&cpg CPG_MOD 393>,
> + <&cpg CPG_MOD 392>,
> + <&cpg CPG_MOD 391>,
> + <&cpg CPG_MOD 390>,
> + <&cpg CPG_MOD 389>,
> + <&cpg CPG_MOD 388>,
> + <&cpg CPG_MOD 387>,
> + <&cpg CPG_MOD 386>,
> + <&cpg CPG_MOD 385>,
> + <&cpg CPG_MOD 381>,
> + <&cpg CPG_MOD 380>,
> + <&cpg CPG_MOD 379>,
> + <&cpg CPG_MOD 378>,
> + <&cpg CPG_MOD 377>,
> + <&cpg CPG_MOD 376>,
> + <&cpg CPG_MOD 375>,
> + <&cpg CPG_MOD 374>,
> + <&cpg CPG_MOD 373>,
> + <&cpg CPG_MOD 372>,
> + <&cpg CPG_MOD 371>,
> + <&cpg CPG_MOD 370>,
> + <&cpg CPG_MOD 371>,
> + <&cpg CPG_MOD 370>,
> + <&cpg CPG_MOD 368>,
> + <&cpg CPG_MOD 369>,
> + <&cpg CPG_MOD 251>,
> + <&cpg CPG_MOD 252>,
> + <&cpg CPG_MOD 253>,
> + <&cpg CPG_MOD 250>,
> + <&cpg CPG_MOD 384>,
> + <&cpg CPG_MOD 246>,
> + <&cpg CPG_MOD 247>,
> + <&cpg CPG_MOD 382>,
> + <&cpg CPG_MOD 361>,
> + <&cpg CPG_MOD 360>,
> + <&cpg CPG_MOD 359>,
> + <&cpg CPG_MOD 358>,
> + <&cpg CPG_MOD 357>,
> + <&cpg CPG_MOD 356>,
> + <&cpg CPG_MOD 355>,
> + <&cpg CPG_MOD 354>,
> + <&cpg CPG_MOD 353>,
> + <&cpg CPG_MOD 352>,
> + <&cpg CPG_MOD 248>,
> + <&cpg CPG_MOD 249>;
> + clock-names = "ssi-all",
> + "ssi.9", "ssi.8",
> + "ssi.7", "ssi.6",
> + "ssi.5", "ssi.4",
> + "ssi.3", "ssi.2",
> + "ssi.1", "ssi.0",
> + "src.9", "src.8",
> + "src.7", "src.6",
> + "src.5", "src.4",
> + "src.3", "src.2",
> + "src.1", "src.0",
> + "mix.1", "mix.0",
> + "ctu.1", "ctu.0",
> + "dvc.0", "dvc.1",
> + "clk_a", "clk_b",
> + "clk_c", "clk_i",
> + "ssif_supply",
> + "scu", "scu_x2",
> + "scu_supply",
> + "adg.ssi.9", "adg.ssi.8",
> + "adg.ssi.7", "adg.ssi.6",
> + "adg.ssi.5", "adg.ssi.4",
> + "adg.ssi.3", "adg.ssi.2",
> + "adg.ssi.1", "adg.ssi.0",
> + "audmapp", "adg";
> + power-domains = <&cpg>;
> + resets = <&cpg 225>,
> + <&cpg 235>,
> + <&cpg 234>,
> + <&cpg 233>,
> + <&cpg 232>,
> + <&cpg 231>,
> + <&cpg 230>,
> + <&cpg 229>,
> + <&cpg 228>,
> + <&cpg 227>,
> + <&cpg 226>,
> + <&cpg 236>,
> + <&cpg 238>,
> + <&cpg 237>;
> + reset-names = "ssi-all",
> + "ssi.9", "ssi.8",
> + "ssi.7", "ssi.6",
> + "ssi.5", "ssi.4",
> + "ssi.3", "ssi.2",
> + "ssi.1", "ssi.0",
> + "scu", "adg",
> + "audmapp";
> + status = "disabled";
> +
> + rcar_sound,ctu {
> + ctu00: ctu-0 { };
> + ctu01: ctu-1 { };
> + ctu02: ctu-2 { };
> + ctu03: ctu-3 { };
> + ctu10: ctu-4 { };
> + ctu11: ctu-5 { };
> + ctu12: ctu-6 { };
> + ctu13: ctu-7 { };
> + };
> +
> + rcar_sound,dvc {
> + dvc0: dvc-0 {
> + dmas = <&dmac0 0x1db3>, <&dmac1 0x1db3>,
> + <&dmac2 0x1db3>, <&dmac3 0x1db3>,
> + <&dmac4 0x1db3>;
> + dma-names = "tx", "tx", "tx", "tx", "tx";
> + };
> + dvc1: dvc-1 {
> + dmas = <&dmac0 0x1db4>, <&dmac1 0x1db4>,
> + <&dmac2 0x1db4>, <&dmac3 0x1db4>,
> + <&dmac4 0x1db4>;
> + dma-names = "tx", "tx", "tx", "tx", "tx";
> + };
> + };
> +
> + rcar_sound,mix {
> + mix0: mix-0 { };
> + mix1: mix-1 { };
> + };
> +
> + rcar_sound,src {
> + src0: src-0 {
> + interrupts = <GIC_SPI 902 IRQ_TYPE_LEVEL_HIGH>;
> + dmas = <&dmac0 0x1d9f>, <&dmac0 0x1da9>,
> + <&dmac1 0x1d9f>, <&dmac1 0x1da9>,
> + <&dmac2 0x1d9f>, <&dmac2 0x1da9>,
> + <&dmac3 0x1d9f>, <&dmac3 0x1da9>,
> + <&dmac4 0x1d9f>, <&dmac4 0x1da9>;
> + dma-names = "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx";
> + };
> + src1: src-1 {
> + interrupts = <GIC_SPI 903 IRQ_TYPE_LEVEL_HIGH>;
> + dmas = <&dmac0 0x1da0>, <&dmac0 0x1daa>,
> + <&dmac1 0x1da0>, <&dmac1 0x1daa>,
> + <&dmac2 0x1da0>, <&dmac2 0x1daa>,
> + <&dmac3 0x1da0>, <&dmac3 0x1daa>,
> + <&dmac4 0x1da0>, <&dmac4 0x1daa>;
> + dma-names = "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx";
> + };
> + src2: src-2 {
> + interrupts = <GIC_SPI 904 IRQ_TYPE_LEVEL_HIGH>;
> + dmas = <&dmac0 0x1da1>, <&dmac0 0x1dab>,
> + <&dmac1 0x1da1>, <&dmac1 0x1dab>,
> + <&dmac2 0x1da1>, <&dmac2 0x1dab>,
> + <&dmac3 0x1da1>, <&dmac3 0x1dab>,
> + <&dmac4 0x1da1>, <&dmac4 0x1dab>;
> + dma-names = "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx";
> + };
> + src3: src-3 {
> + interrupts = <GIC_SPI 905 IRQ_TYPE_LEVEL_HIGH>;
> + dmas = <&dmac0 0x1da2>, <&dmac0 0x1dac>,
> + <&dmac1 0x1da2>, <&dmac1 0x1dac>,
> + <&dmac2 0x1da2>, <&dmac2 0x1dac>,
> + <&dmac3 0x1da2>, <&dmac3 0x1dac>,
> + <&dmac4 0x1da2>, <&dmac4 0x1dac>;
> + dma-names = "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx";
> + };
> + src4: src-4 {
> + interrupts = <GIC_SPI 906 IRQ_TYPE_LEVEL_HIGH>;
> + dmas = <&dmac0 0x1da3>, <&dmac0 0x1dad>,
> + <&dmac1 0x1da3>, <&dmac1 0x1dad>,
> + <&dmac2 0x1da3>, <&dmac2 0x1dad>,
> + <&dmac3 0x1da3>, <&dmac3 0x1dad>,
> + <&dmac4 0x1da3>, <&dmac4 0x1dad>;
> + dma-names = "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx";
> + };
> + src5: src-5 {
> + interrupts = <GIC_SPI 907 IRQ_TYPE_LEVEL_HIGH>;
> + dmas = <&dmac0 0x1da4>, <&dmac0 0x1dae>,
> + <&dmac1 0x1da4>, <&dmac1 0x1dae>,
> + <&dmac2 0x1da4>, <&dmac2 0x1dae>,
> + <&dmac3 0x1da4>, <&dmac3 0x1dae>,
> + <&dmac4 0x1da4>, <&dmac4 0x1dae>;
> + dma-names = "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx";
> + };
> + src6: src-6 {
> + interrupts = <GIC_SPI 908 IRQ_TYPE_LEVEL_HIGH>;
> + dmas = <&dmac0 0x1da5>, <&dmac0 0x1daf>,
> + <&dmac1 0x1da5>, <&dmac1 0x1daf>,
> + <&dmac2 0x1da5>, <&dmac2 0x1daf>,
> + <&dmac3 0x1da5>, <&dmac3 0x1daf>,
> + <&dmac4 0x1da5>, <&dmac4 0x1daf>;
> + dma-names = "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx";
> + };
> + src7: src-7 {
> + interrupts = <GIC_SPI 909 IRQ_TYPE_LEVEL_HIGH>;
> + dmas = <&dmac0 0x1da6>, <&dmac0 0x1db0>,
> + <&dmac1 0x1da6>, <&dmac1 0x1db0>,
> + <&dmac2 0x1da6>, <&dmac2 0x1db0>,
> + <&dmac3 0x1da6>, <&dmac3 0x1db0>,
> + <&dmac4 0x1da6>, <&dmac4 0x1db0>;
> + dma-names = "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx";
> + };
> + src8: src-8 {
> + interrupts = <GIC_SPI 910 IRQ_TYPE_LEVEL_HIGH>;
> + dmas = <&dmac0 0x1da7>, <&dmac0 0x1db1>,
> + <&dmac1 0x1da7>, <&dmac1 0x1db1>,
> + <&dmac2 0x1da7>, <&dmac2 0x1db1>,
> + <&dmac3 0x1da7>, <&dmac3 0x1db1>,
> + <&dmac4 0x1da7>, <&dmac4 0x1db1>;
> + dma-names = "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx";
> + };
> + src9: src-9 {
> + interrupts = <GIC_SPI 911 IRQ_TYPE_LEVEL_HIGH>;
> + dmas = <&dmac0 0x1da8>, <&dmac0 0x1db2>,
> + <&dmac1 0x1da8>, <&dmac1 0x1db2>,
> + <&dmac2 0x1da8>, <&dmac2 0x1db2>,
> + <&dmac3 0x1da8>, <&dmac3 0x1db2>,
> + <&dmac4 0x1da8>, <&dmac4 0x1db2>;
> + dma-names = "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx";
> + };
> + };
> +
> + rcar_sound,ssi {
> + ssi0: ssi-0 {
> + interrupts = <GIC_SPI 889 IRQ_TYPE_LEVEL_HIGH>;
> + };
> + ssi1: ssi-1 {
> + interrupts = <GIC_SPI 890 IRQ_TYPE_LEVEL_HIGH>;
> + };
> + ssi2: ssi-2 {
> + interrupts = <GIC_SPI 891 IRQ_TYPE_LEVEL_HIGH>;
> + };
> + ssi3: ssi-3 {
> + interrupts = <GIC_SPI 892 IRQ_TYPE_LEVEL_HIGH>;
> + };
> + ssi4: ssi-4 {
> + interrupts = <GIC_SPI 893 IRQ_TYPE_LEVEL_HIGH>;
> + };
> + ssi5: ssi-5 {
> + interrupts = <GIC_SPI 894 IRQ_TYPE_LEVEL_HIGH>;
> + };
> + ssi6: ssi-6 {
> + interrupts = <GIC_SPI 895 IRQ_TYPE_LEVEL_HIGH>;
> + };
> + ssi7: ssi-7 {
> + interrupts = <GIC_SPI 896 IRQ_TYPE_LEVEL_HIGH>;
> + };
> + ssi8: ssi-8 {
> + interrupts = <GIC_SPI 897 IRQ_TYPE_LEVEL_HIGH>;
> + };
> + ssi9: ssi-9 {
> + interrupts = <GIC_SPI 898 IRQ_TYPE_LEVEL_HIGH>;
> + };
> + };
> +
> + rcar_sound,ssiu {
> + ssiu00: ssiu-0 {
> + dmas = <&dmac0 0x1d61>, <&dmac0 0x1d62>,
> + <&dmac1 0x1d61>, <&dmac1 0x1d62>,
> + <&dmac2 0x1d61>, <&dmac2 0x1d62>,
> + <&dmac3 0x1d61>, <&dmac3 0x1d62>,
> + <&dmac4 0x1d61>, <&dmac4 0x1d62>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu01: ssiu-1 {
> + dmas = <&dmac0 0x1d63>, <&dmac0 0x1d64>,
> + <&dmac1 0x1d63>, <&dmac1 0x1d64>,
> + <&dmac2 0x1d63>, <&dmac2 0x1d64>,
> + <&dmac3 0x1d63>, <&dmac3 0x1d64>,
> + <&dmac4 0x1d63>, <&dmac4 0x1d64>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu02: ssiu-2 {
> + dmas = <&dmac0 0x1d65>, <&dmac0 0x1d66>,
> + <&dmac1 0x1d65>, <&dmac1 0x1d66>,
> + <&dmac2 0x1d65>, <&dmac2 0x1d66>,
> + <&dmac3 0x1d65>, <&dmac3 0x1d66>,
> + <&dmac4 0x1d65>, <&dmac4 0x1d66>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu03: ssiu-3 {
> + dmas = <&dmac0 0x1d67>, <&dmac0 0x1d68>,
> + <&dmac1 0x1d67>, <&dmac1 0x1d68>,
> + <&dmac2 0x1d67>, <&dmac2 0x1d68>,
> + <&dmac3 0x1d67>, <&dmac3 0x1d68>,
> + <&dmac4 0x1d67>, <&dmac4 0x1d68>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu10: ssiu-4 {
> + dmas = <&dmac0 0x1d69>, <&dmac0 0x1d6a>,
> + <&dmac1 0x1d69>, <&dmac1 0x1d6a>,
> + <&dmac2 0x1d69>, <&dmac2 0x1d6a>,
> + <&dmac3 0x1d69>, <&dmac3 0x1d6a>,
> + <&dmac4 0x1d69>, <&dmac4 0x1d6a>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu11: ssiu-5 {
> + dmas = <&dmac0 0x1d6b>, <&dmac0 0x1d6c>,
> + <&dmac1 0x1d6b>, <&dmac1 0x1d6c>,
> + <&dmac2 0x1d6b>, <&dmac2 0x1d6c>,
> + <&dmac3 0x1d6b>, <&dmac3 0x1d6c>,
> + <&dmac4 0x1d6b>, <&dmac4 0x1d6c>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu12: ssiu-6 {
> + dmas = <&dmac0 0x1d6d>, <&dmac0 0x1d6e>,
> + <&dmac1 0x1d6d>, <&dmac1 0x1d6e>,
> + <&dmac2 0x1d6d>, <&dmac2 0x1d6e>,
> + <&dmac3 0x1d6d>, <&dmac3 0x1d6e>,
> + <&dmac4 0x1d6d>, <&dmac4 0x1d6e>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu13: ssiu-7 {
> + dmas = <&dmac0 0x1d6f>, <&dmac0 0x1d70>,
> + <&dmac1 0x1d6f>, <&dmac1 0x1d70>,
> + <&dmac2 0x1d6f>, <&dmac2 0x1d70>,
> + <&dmac3 0x1d6f>, <&dmac3 0x1d70>,
> + <&dmac4 0x1d6f>, <&dmac4 0x1d70>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu20: ssiu-8 {
> + dmas = <&dmac0 0x1d71>, <&dmac0 0x1d72>,
> + <&dmac1 0x1d71>, <&dmac1 0x1d72>,
> + <&dmac2 0x1d71>, <&dmac2 0x1d72>,
> + <&dmac3 0x1d71>, <&dmac3 0x1d72>,
> + <&dmac4 0x1d71>, <&dmac4 0x1d72>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu21: ssiu-9 {
> + dmas = <&dmac0 0x1d73>, <&dmac0 0x1d74>,
> + <&dmac1 0x1d73>, <&dmac1 0x1d74>,
> + <&dmac2 0x1d73>, <&dmac2 0x1d74>,
> + <&dmac3 0x1d73>, <&dmac3 0x1d74>,
> + <&dmac4 0x1d73>, <&dmac4 0x1d74>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu22: ssiu-10 {
> + dmas = <&dmac0 0x1d75>, <&dmac0 0x1d76>,
> + <&dmac1 0x1d75>, <&dmac1 0x1d76>,
> + <&dmac2 0x1d75>, <&dmac2 0x1d76>,
> + <&dmac3 0x1d75>, <&dmac3 0x1d76>,
> + <&dmac4 0x1d75>, <&dmac4 0x1d76>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu23: ssiu-11 {
> + dmas = <&dmac0 0x1d77>, <&dmac0 0x1d78>,
> + <&dmac1 0x1d77>, <&dmac1 0x1d78>,
> + <&dmac2 0x1d77>, <&dmac2 0x1d78>,
> + <&dmac3 0x1d77>, <&dmac3 0x1d78>,
> + <&dmac4 0x1d77>, <&dmac4 0x1d78>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu30: ssiu-12 {
> + dmas = <&dmac0 0x1d79>, <&dmac0 0x1d7a>,
> + <&dmac1 0x1d79>, <&dmac1 0x1d7a>,
> + <&dmac2 0x1d79>, <&dmac2 0x1d7a>,
> + <&dmac3 0x1d79>, <&dmac3 0x1d7a>,
> + <&dmac4 0x1d79>, <&dmac4 0x1d7a>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu31: ssiu-13 {
> + dmas = <&dmac0 0x1d7b>, <&dmac0 0x1d7c>,
> + <&dmac1 0x1d7b>, <&dmac1 0x1d7c>,
> + <&dmac2 0x1d7b>, <&dmac2 0x1d7c>,
> + <&dmac3 0x1d7b>, <&dmac3 0x1d7c>,
> + <&dmac4 0x1d7b>, <&dmac4 0x1d7c>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu32: ssiu-14 {
> + dmas = <&dmac0 0x1d7d>, <&dmac0 0x1d7e>,
> + <&dmac1 0x1d7d>, <&dmac1 0x1d7e>,
> + <&dmac2 0x1d7d>, <&dmac2 0x1d7e>,
> + <&dmac3 0x1d7d>, <&dmac3 0x1d7e>,
> + <&dmac4 0x1d7d>, <&dmac4 0x1d7e>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu33: ssiu-15 {
> + dmas = <&dmac0 0x1d7f>, <&dmac0 0x1d80>,
> + <&dmac1 0x1d7f>, <&dmac1 0x1d80>,
> + <&dmac2 0x1d7f>, <&dmac2 0x1d80>,
> + <&dmac3 0x1d7f>, <&dmac3 0x1d80>,
> + <&dmac4 0x1d7f>, <&dmac4 0x1d80>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu40: ssiu-16 {
> + dmas = <&dmac0 0x1d81>, <&dmac0 0x1d82>,
> + <&dmac1 0x1d81>, <&dmac1 0x1d82>,
> + <&dmac2 0x1d81>, <&dmac2 0x1d82>,
> + <&dmac3 0x1d81>, <&dmac3 0x1d82>,
> + <&dmac4 0x1d81>, <&dmac4 0x1d82>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu41: ssiu-17 {
> + dmas = <&dmac0 0x1d83>, <&dmac0 0x1d84>,
> + <&dmac1 0x1d83>, <&dmac1 0x1d84>,
> + <&dmac2 0x1d83>, <&dmac2 0x1d84>,
> + <&dmac3 0x1d83>, <&dmac3 0x1d84>,
> + <&dmac4 0x1d83>, <&dmac4 0x1d84>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu42: ssiu-18 {
> + dmas = <&dmac0 0x1d85>, <&dmac0 0x1d86>,
> + <&dmac1 0x1d85>, <&dmac1 0x1d86>,
> + <&dmac2 0x1d85>, <&dmac2 0x1d86>,
> + <&dmac3 0x1d85>, <&dmac3 0x1d86>,
> + <&dmac4 0x1d85>, <&dmac4 0x1d86>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu43: ssiu-19 {
> + dmas = <&dmac0 0x1d87>, <&dmac0 0x1d88>,
> + <&dmac1 0x1d87>, <&dmac1 0x1d88>,
> + <&dmac2 0x1d87>, <&dmac2 0x1d88>,
> + <&dmac3 0x1d87>, <&dmac3 0x1d88>,
> + <&dmac4 0x1d87>, <&dmac4 0x1d88>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu50: ssiu-20 {
> + dmas = <&dmac0 0x1d89>, <&dmac0 0x1d8a>,
> + <&dmac1 0x1d89>, <&dmac1 0x1d8a>,
> + <&dmac2 0x1d89>, <&dmac2 0x1d8a>,
> + <&dmac3 0x1d89>, <&dmac3 0x1d8a>,
> + <&dmac4 0x1d89>, <&dmac4 0x1d8a>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu60: ssiu-21 {
> + dmas = <&dmac0 0x1d8b>, <&dmac0 0x1d8c>,
> + <&dmac1 0x1d8b>, <&dmac1 0x1d8c>,
> + <&dmac2 0x1d8b>, <&dmac2 0x1d8c>,
> + <&dmac3 0x1d8b>, <&dmac3 0x1d8c>,
> + <&dmac4 0x1d8b>, <&dmac4 0x1d8c>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu70: ssiu-22 {
> + dmas = <&dmac0 0x1d8d>, <&dmac0 0x1d8e>,
> + <&dmac1 0x1d8d>, <&dmac1 0x1d8e>,
> + <&dmac2 0x1d8d>, <&dmac2 0x1d8e>,
> + <&dmac3 0x1d8d>, <&dmac3 0x1d8e>,
> + <&dmac4 0x1d8d>, <&dmac4 0x1d8e>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu80: ssiu-23 {
> + dmas = <&dmac0 0x1d8f>, <&dmac0 0x1d90>,
> + <&dmac1 0x1d8f>, <&dmac1 0x1d90>,
> + <&dmac2 0x1d8f>, <&dmac2 0x1d90>,
> + <&dmac3 0x1d8f>, <&dmac3 0x1d90>,
> + <&dmac4 0x1d8f>, <&dmac4 0x1d90>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu90: ssiu-24 {
> + dmas = <&dmac0 0x1d91>, <&dmac0 0x1d92>,
> + <&dmac1 0x1d91>, <&dmac1 0x1d92>,
> + <&dmac2 0x1d91>, <&dmac2 0x1d92>,
> + <&dmac3 0x1d91>, <&dmac3 0x1d92>,
> + <&dmac4 0x1d91>, <&dmac4 0x1d92>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu91: ssiu-25 {
> + dmas = <&dmac0 0x1d93>, <&dmac0 0x1d94>,
> + <&dmac1 0x1d93>, <&dmac1 0x1d94>,
> + <&dmac2 0x1d93>, <&dmac2 0x1d94>,
> + <&dmac3 0x1d93>, <&dmac3 0x1d94>,
> + <&dmac4 0x1d93>, <&dmac4 0x1d94>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu92: ssiu-26 {
> + dmas = <&dmac0 0x1d95>, <&dmac0 0x1d96>,
> + <&dmac1 0x1d95>, <&dmac1 0x1d96>,
> + <&dmac2 0x1d95>, <&dmac2 0x1d96>,
> + <&dmac3 0x1d95>, <&dmac3 0x1d96>,
> + <&dmac4 0x1d95>, <&dmac4 0x1d96>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + ssiu93: ssiu-27 {
> + dmas = <&dmac0 0x1d97>, <&dmac0 0x1d98>,
> + <&dmac1 0x1d97>, <&dmac1 0x1d98>,
> + <&dmac2 0x1d97>, <&dmac2 0x1d98>,
> + <&dmac3 0x1d97>, <&dmac3 0x1d98>,
> + <&dmac4 0x1d97>, <&dmac4 0x1d98>;
> + dma-names = "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx", "tx", "rx";
> + };
> + };
> + };
> +
> wdt1: watchdog@14400000 {
> compatible = "renesas,r9a09g047-wdt", "renesas,r9a09g057-wdt";
> reg = <0 0x14400000 0 0x400>;
> --
> 2.25.1
^ permalink raw reply
* [PATCH v2 24/24] arm64: dts: renesas: r9a09g047e57-smarc: add DA7212 audio codec support
From: John Madieu @ 2026-04-02 9:05 UTC (permalink / raw)
To: Geert Uytterhoeven, Kuninori Morimoto, Vinod Koul, Mark Brown,
Rob Herring, Krzysztof Kozlowski
Cc: Michael Turquette, Stephen Boyd, Conor Dooley, Frank Li,
Liam Girdwood, Magnus Damm, Thomas Gleixner, Jaroslav Kysela,
Takashi Iwai, Philipp Zabel, Claudiu Beznea, Biju Das,
Fabrizio Castro, Lad Prabhakar, John Madieu, linux-renesas-soc,
linux-clk, devicetree, linux-kernel, dmaengine, linux-sound,
John Madieu
In-Reply-To: <20260402090524.9137-1-john.madieu.xa@bp.renesas.com>
RZ/G3E SMARC board has a DA7212 audio codec connected via I2C1 for
sound input/output using SSI3/SSI4 where:
- The codec receives its master clock from the Versa3 clock
generator present on the SoM
- SSI4 shares clock pins with SSI3 to provide a separate data
line for full-duplex audio capture.
Enable audio support on RZ/G3E SMARC2 EVK boards with a DA7212 audio codec.
Signed-off-by: John Madieu <john.madieu.xa@bp.renesas.com>
---
Changes:
v2: No changes
.../boot/dts/renesas/r9a09g047e57-smarc.dts | 114 ++++++++++++++++++
1 file changed, 114 insertions(+)
diff --git a/arch/arm64/boot/dts/renesas/r9a09g047e57-smarc.dts b/arch/arm64/boot/dts/renesas/r9a09g047e57-smarc.dts
index 9be57785d9d5..2f4795e5e82b 100644
--- a/arch/arm64/boot/dts/renesas/r9a09g047e57-smarc.dts
+++ b/arch/arm64/boot/dts/renesas/r9a09g047e57-smarc.dts
@@ -32,6 +32,37 @@
#include "rzg3e-smarc-som.dtsi"
#include "renesas-smarc2.dtsi"
+/*
+ * SSI-DA7212
+ *
+ * These commands are required when Playback/Capture
+ *
+ * amixer -q cset name='Aux Switch' on
+ * amixer -q cset name='Mixin Left Aux Left Switch' on
+ * amixer -q cset name='Mixin Right Aux Right Switch' on
+ * amixer -q cset name='ADC Switch' on
+ * amixer -q cset name='Mixout Right Mixin Right Switch' off
+ * amixer -q cset name='Mixout Left Mixin Left Switch' off
+ * amixer -q cset name='Headphone Volume' 70%
+ * amixer -q cset name='Headphone Switch' on
+ * amixer -q cset name='Mixout Left DAC Left Switch' on
+ * amixer -q cset name='Mixout Right DAC Right Switch' on
+ * amixer -q cset name='DAC Left Source MUX' 'DAI Input Left'
+ * amixer -q cset name='DAC Right Source MUX' 'DAI Input Right'
+ * amixer -q sset 'Mic 1 Amp Source MUX' 'MIC_P'
+ * amixer -q sset 'Mic 2 Amp Source MUX' 'MIC_P'
+ * amixer -q sset 'Mixin Left Mic 1' on
+ * amixer -q sset 'Mixin Right Mic 2' on
+ * amixer -q sset 'Mic 1' 90% on
+ * amixer -q sset 'Mic 2' 90% on
+ * amixer -q sset 'Lineout' 80% on
+ * amixer -q set "Headphone" 100% on
+ *
+ * When Capture chained with DVC, use this command to amplify sound
+ * amixer set 'DVC In',0 80%
+ * For playback, use: amixer set 'DVC Out',0 80%
+ */
+
/ {
model = "Renesas SMARC EVK version 2 based on r9a09g047e57";
compatible = "renesas,smarc2-evk", "renesas,rzg3e-smarcm",
@@ -55,6 +86,22 @@ vqmmc_sd1_pvdd: regulator-vqmmc-sd1-pvdd {
gpios-states = <0>;
states = <3300000 0>, <1800000 1>;
};
+
+ sound_card: sound {
+ compatible = "audio-graph-card";
+
+ label = "snd-rzg3e";
+
+ dais = <&rsnd_port0>; /* DA7212 */
+ };
+};
+
+&audio_clkb {
+ clock-frequency = <11289600>;
+};
+
+&audio_clkc {
+ clock-frequency = <12288000>;
};
&canfd {
@@ -99,6 +146,37 @@ &i2c0 {
pinctrl-names = "default";
};
+&i2c1 {
+ da7212: codec@1a {
+ compatible = "dlg,da7212";
+ #sound-dai-cells = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x1a>;
+
+ clocks = <&versa3 1>;
+ clock-names = "mclk";
+
+ dlg,micbias1-lvl = <2500>;
+ dlg,micbias2-lvl = <2500>;
+ dlg,dmic-data-sel = "lrise_rfall";
+ dlg,dmic-samplephase = "between_clkedge";
+ dlg,dmic-clkrate = <3000000>;
+
+ VDDA-supply = <®_1p8v>;
+ VDDSP-supply = <®_3p3v>;
+ VDDMIC-supply = <®_3p3v>;
+ VDDIO-supply = <®_1p8v>;
+
+ port {
+ da7212_endpoint: endpoint {
+ remote-endpoint = <&rsnd_endpoint0>;
+ mclk-fs = <256>;
+ };
+ };
+ };
+};
+
&keys {
pinctrl-0 = <&nmi_pins>;
pinctrl-names = "default";
@@ -280,6 +358,42 @@ &sdhi1 {
vqmmc-supply = <&vqmmc_sd1_pvdd>;
};
+&snd_rzg3e {
+ pinctrl-0 = <&sound_clk_pins &sound_pins>;
+ pinctrl-names = "default";
+
+ status = "okay";
+
+ /* audio_clkout */
+ #clock-cells = <0>;
+ clock-frequency = <11289600>;
+
+ /* Multi DAI */
+ #sound-dai-cells = <1>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ rsnd_port0: port@0 {
+ reg = <0>;
+ rsnd_endpoint0: endpoint {
+ remote-endpoint = <&da7212_endpoint>;
+
+ dai-format = "i2s";
+ bitclock-master = <&rsnd_endpoint0>;
+ frame-master = <&rsnd_endpoint0>;
+
+ playback = <&ssi3>, <&src1>, <&dvc1>;
+ capture = <&ssi4>, <&src0>, <&dvc0>;
+ };
+ };
+ };
+};
+
+&ssi4 {
+ shared-pin;
+};
+
&xhci {
pinctrl-0 = <&usb3_pins>;
pinctrl-names = "default";
--
2.25.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox