[PATCH 5/5] clocksource: timer-cadence-ttc: Add PWM auxiliary-device support

From: Meher Thanmaiee

Date: Wed Oct 07 2026 - 06:43:34 EST


When a TTC device tree node contains #pwm-cells, create an
auxiliary-bus child device so the Cadence TTC PWM driver can bind to
that instance instead of using it as a clocksource and clockevent
device.

Propagate the parent device tree node to the auxiliary device so PWM
consumers can be matched through the device tree, and pass the mapped
register base, input clock, clock rate, and timer width to the child
driver through shared TTC data.

PWM support is limited to TTC instances whose channels use the shared
internal/reference clock. Reject instances configured to use the
alternate external clock source, which would require per-channel clock
source selection and rate handling.

Select AUXILIARY_BUS in Kconfig for the auxiliary-bus device creation.

Signed-off-by: Meher Thanmaiee <meherthanmaiee.vanukuri@xxxxxxx>
---
drivers/clocksource/Kconfig | 5 ++
drivers/clocksource/timer-cadence-ttc.c | 124 ++++++++++++++++++++++++++++++++
include/linux/timer-cadence-ttc.h | 1 +
3 files changed, 130 insertions(+)

diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index d9c76dd443f8..098cc40f68b0 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -201,9 +201,14 @@ config NPCM7XX_TIMER
config CADENCE_TTC_TIMER
bool "Cadence TTC timer driver" if COMPILE_TEST
depends on COMMON_CLK
+ select AUXILIARY_BUS
help
Enables support for the Cadence TTC driver.

+ When a TTC device-tree node contains #pwm-cells, this
+ driver creates an auxiliary-bus device that is bound by
+ the Cadence TTC PWM driver.
+
config ASM9260_TIMER
bool "ASM9260 timer driver" if COMPILE_TEST
select CLKSRC_MMIO
diff --git a/drivers/clocksource/timer-cadence-ttc.c b/drivers/clocksource/timer-cadence-ttc.c
index 954a9b85a958..fded136b448e 100644
--- a/drivers/clocksource/timer-cadence-ttc.c
+++ b/drivers/clocksource/timer-cadence-ttc.c
@@ -7,6 +7,8 @@
* based on arch/mips/kernel/time.c timer driver
*/

+#include <linux/atomic.h>
+#include <linux/auxiliary_bus.h>
#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/clocksource.h>
@@ -476,6 +478,121 @@ static int ttc_init_common(struct platform_device *pdev,
return 0;
}

+#if IS_ENABLED(CONFIG_PWM_CADENCE)
+
+/* Auxiliary-bus device wrapper */
+struct ttc_auxdev {
+ struct auxiliary_device adev;
+ struct ttc_shared_data sd;
+};
+
+static void ttc_auxdev_release(struct device *dev)
+{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+ struct ttc_auxdev *ta = container_of(adev, struct ttc_auxdev, adev);
+
+ of_node_put(dev->of_node);
+ kfree(ta);
+}
+
+static atomic_t ttc_auxdev_id;
+
+static void ttc_auxdev_teardown(void *data)
+{
+ struct auxiliary_device *adev = data;
+
+ auxiliary_device_delete(adev);
+ auxiliary_device_uninit(adev);
+}
+
+static int ttc_create_aux(struct device *parent, const char *name,
+ const struct ttc_shared_data *sd)
+{
+ struct ttc_auxdev *ta;
+ int ret;
+
+ ta = kzalloc_obj(*ta);
+ if (!ta)
+ return -ENOMEM;
+
+ ta->sd = *sd;
+ ta->adev.name = name;
+ ta->adev.id = atomic_fetch_inc(&ttc_auxdev_id);
+ ta->adev.dev.parent = parent;
+ ta->adev.dev.release = ttc_auxdev_release;
+ ta->adev.dev.platform_data = &ta->sd;
+ device_set_of_node_from_dev(&ta->adev.dev, parent);
+
+ ret = auxiliary_device_init(&ta->adev);
+ if (ret) {
+ of_node_put(ta->adev.dev.of_node);
+ kfree(ta);
+ return ret;
+ }
+
+ ret = auxiliary_device_add(&ta->adev);
+ if (ret) {
+ auxiliary_device_uninit(&ta->adev);
+ return ret;
+ }
+
+ return devm_add_action_or_reset(parent, ttc_auxdev_teardown,
+ &ta->adev);
+}
+
+static int ttc_probe_auxbus(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ttc_shared_data sd;
+ struct clk *clk;
+ int i, ret;
+ u32 val;
+
+ ret = ttc_init_common(pdev, &sd.base, &sd.timer_width);
+ if (ret)
+ return ret;
+
+ if (sd.timer_width == 0 || sd.timer_width > TTC_MAX_TIMER_WIDTH)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid timer-width value\n");
+
+ /*
+ * PWM support is limited to the shared internal/reference clock on
+ * all channels. Supporting the alternate external clock source
+ * would require per-channel clock and rate handling.
+ */
+ for (i = 0; i < TTC_NUM_CHANNELS; i++) {
+ val = ttc_ch_readl(&sd, i, TTC_CLK_CNTRL_OFFSET);
+ if (val & TTC_CLK_CNTRL_CSRC_MASK)
+ return dev_err_probe(dev, -EINVAL,
+ "external clock source not supported for PWM\n");
+ }
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk),
+ "failed to get timer input clock\n");
+
+ sd.clk = clk;
+ sd.rate = clk_get_rate(clk);
+ if (!sd.rate)
+ return dev_err_probe(dev, -EINVAL,
+ "failed to get clock rate\n");
+
+ ret = ttc_create_aux(dev, "pwm", &sd);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to create PWM aux device\n");
+
+ return 0;
+}
+#else
+static inline int ttc_probe_auxbus(struct platform_device *pdev)
+{
+ return 0;
+}
+#endif
+
static int ttc_timer_probe(struct platform_device *pdev)
{
struct device_node *timer = pdev->dev.of_node;
@@ -486,6 +603,13 @@ static int ttc_timer_probe(struct platform_device *pdev)
u32 timer_width;
int clksel, ret;

+ /*
+ * If #pwm-cells is present, this TTC is used for PWM.
+ * Create an auxiliary-bus child device for the PWM driver.
+ */
+ if (device_property_present(&pdev->dev, "#pwm-cells"))
+ return ttc_probe_auxbus(pdev);
+
if (initialized)
return 0;

diff --git a/include/linux/timer-cadence-ttc.h b/include/linux/timer-cadence-ttc.h
index 05b7edd88626..58cc7db72693 100644
--- a/include/linux/timer-cadence-ttc.h
+++ b/include/linux/timer-cadence-ttc.h
@@ -16,6 +16,7 @@
struct clk;

#define TTC_DEFAULT_TIMER_WIDTH 16
+#define TTC_MAX_TIMER_WIDTH 32

/* Per-channel register offset stride */
#define TTC_CH_OFFSET 0x04

--
2.34.1