All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/3] Make TI dmtimer features more usable
@ 2022-10-31 11:56 ` Tony Lindgren
  0 siblings, 0 replies; 10+ messages in thread
From: Tony Lindgren @ 2022-10-31 11:56 UTC (permalink / raw)
  To: Daniel Lezcano, Thomas Gleixner
  Cc: Georgi Vlaev, Grygorii Strashko, Keerthy, Ladislav Michl,
	Nishanth Menon, Suman Anna, Vignesh Raghavendra, linux-kernel,
	linux-omap, linux-arm-kernel

Hi all,

The TI dmtimer has been using a custom API to expose some hardware timer
features for PWM and remoteproc so far with struct omap_dm_timer_ops. It
seems that for most part we can nowadays replace most of the custom API
with chained interrupts and clock provider features.

There are lots of the dmtimer instances on TI SoCs, some have tens of
them. Some timers have an IO pad available, which means the timers could
be used as clock output for an external device with a 50% duty cycle. The
timers also have input capture capability, but that is not currently
supported. There have been some patches posted earlier for the PWM capture
support though.

These patches are against v6.1-rc2, and need also the following pending
patch:

[PATCH] clocksource/drivers/timer-ti-dm: Clear settings on probe and free

For am6, these patches depend on the pending dts changes posted earlier:

[PATCH v3 0/2] Configure dmtimers for am65
[PATCH 1/2] arm64: dts: ti: k3-am62: Add general purpose timers for am62

I have only lightly tested this so far to make sure I get clock output on
k3-am625-sk on TIMER_IO2 at user expansion connector pin 10. I have not
worried at all so far about disabling the legacy API if used with
interrupts and clock framework.

Regards,

Tony


Tony Lindgren (3):
  clocksource/drivers/timer-ti-dm: Add lock for register access
  clocksource/drivers/timer-ti-dm: Implement chained irq
  clocksource/drivers/timer-ti-dm: Add clock provider support

 drivers/clocksource/timer-ti-dm.c | 591 +++++++++++++++++++++++++++++-
 drivers/pwm/pwm-omap-dmtimer.c    |   1 +
 include/clocksource/timer-ti-dm.h |   2 +
 3 files changed, 578 insertions(+), 16 deletions(-)

-- 
2.37.3

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH 0/3] Make TI dmtimer features more usable
@ 2022-10-31 11:56 ` Tony Lindgren
  0 siblings, 0 replies; 10+ messages in thread
From: Tony Lindgren @ 2022-10-31 11:56 UTC (permalink / raw)
  To: Daniel Lezcano, Thomas Gleixner
  Cc: Georgi Vlaev, Grygorii Strashko, Keerthy, Ladislav Michl,
	Nishanth Menon, Suman Anna, Vignesh Raghavendra, linux-kernel,
	linux-omap, linux-arm-kernel

Hi all,

The TI dmtimer has been using a custom API to expose some hardware timer
features for PWM and remoteproc so far with struct omap_dm_timer_ops. It
seems that for most part we can nowadays replace most of the custom API
with chained interrupts and clock provider features.

There are lots of the dmtimer instances on TI SoCs, some have tens of
them. Some timers have an IO pad available, which means the timers could
be used as clock output for an external device with a 50% duty cycle. The
timers also have input capture capability, but that is not currently
supported. There have been some patches posted earlier for the PWM capture
support though.

These patches are against v6.1-rc2, and need also the following pending
patch:

[PATCH] clocksource/drivers/timer-ti-dm: Clear settings on probe and free

For am6, these patches depend on the pending dts changes posted earlier:

[PATCH v3 0/2] Configure dmtimers for am65
[PATCH 1/2] arm64: dts: ti: k3-am62: Add general purpose timers for am62

I have only lightly tested this so far to make sure I get clock output on
k3-am625-sk on TIMER_IO2 at user expansion connector pin 10. I have not
worried at all so far about disabling the legacy API if used with
interrupts and clock framework.

Regards,

Tony


Tony Lindgren (3):
  clocksource/drivers/timer-ti-dm: Add lock for register access
  clocksource/drivers/timer-ti-dm: Implement chained irq
  clocksource/drivers/timer-ti-dm: Add clock provider support

 drivers/clocksource/timer-ti-dm.c | 591 +++++++++++++++++++++++++++++-
 drivers/pwm/pwm-omap-dmtimer.c    |   1 +
 include/clocksource/timer-ti-dm.h |   2 +
 3 files changed, 578 insertions(+), 16 deletions(-)

-- 
2.37.3

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH 1/3] clocksource/drivers/timer-ti-dm: Add lock for register access
  2022-10-31 11:56 ` Tony Lindgren
@ 2022-10-31 11:56   ` Tony Lindgren
  -1 siblings, 0 replies; 10+ messages in thread
From: Tony Lindgren @ 2022-10-31 11:56 UTC (permalink / raw)
  To: Daniel Lezcano, Thomas Gleixner
  Cc: Georgi Vlaev, Grygorii Strashko, Keerthy, Ladislav Michl,
	Nishanth Menon, Suman Anna, Vignesh Raghavendra, linux-kernel,
	linux-omap, linux-arm-kernel

We currently have no locking for timer control register and we assume that
the consumer device using struct omap_dm_timer_ops takes care of locking
for the timer.

In order to prepare for adding irchip and clock provider support to start
removing the struct omap_dm_timer_ops custom API, let's add timer->lock
and use it for the timer control register. This is needed to protect the
control register access between the different kernel frameworks.

Not-Yet-Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/clocksource/timer-ti-dm.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c
--- a/drivers/clocksource/timer-ti-dm.c
+++ b/drivers/clocksource/timer-ti-dm.c
@@ -121,6 +121,7 @@ struct dmtimer {
 	int id;
 	int irq;
 	struct clk *fclk;
+	spinlock_t lock;		/* For shared register access */
 
 	void __iomem	*io_base;
 	int		irq_stat;	/* TISR/IRQSTATUS interrupt status */
@@ -633,6 +634,7 @@ static struct omap_dm_timer *omap_dm_timer_request_by_node(struct device_node *n
 static int omap_dm_timer_free(struct omap_dm_timer *cookie)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	int rc;
 
@@ -649,7 +651,9 @@ static int omap_dm_timer_free(struct omap_dm_timer *cookie)
 		return rc;
 
 	/* Clear timer configuration */
+	spin_lock_irqsave(&timer->lock, flags);
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, 0);
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
 
@@ -728,6 +732,7 @@ __u32 omap_dm_timer_modify_idlect_mask(__u32 inputmask)
 static int omap_dm_timer_start(struct omap_dm_timer *cookie)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	int rc;
 	u32 l;
@@ -742,11 +747,13 @@ static int omap_dm_timer_start(struct omap_dm_timer *cookie)
 	if (rc)
 		return rc;
 
+	spin_lock_irqsave(&timer->lock, flags);
 	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
 	if (!(l & OMAP_TIMER_CTRL_ST)) {
 		l |= OMAP_TIMER_CTRL_ST;
 		dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
 	}
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	return 0;
 }
@@ -754,6 +761,7 @@ static int omap_dm_timer_start(struct omap_dm_timer *cookie)
 static int omap_dm_timer_stop(struct omap_dm_timer *cookie)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	unsigned long rate = 0;
 
@@ -766,7 +774,9 @@ static int omap_dm_timer_stop(struct omap_dm_timer *cookie)
 	if (!timer->omap1)
 		rate = clk_get_rate(timer->fclk);
 
+	spin_lock_irqsave(&timer->lock, flags);
 	__omap_dm_timer_stop(timer, rate);
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
 
@@ -800,6 +810,7 @@ static int omap_dm_timer_set_match(struct omap_dm_timer *cookie, int enable,
 				   unsigned int match)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	int rc;
 	u32 l;
@@ -813,6 +824,7 @@ static int omap_dm_timer_set_match(struct omap_dm_timer *cookie, int enable,
 	if (rc)
 		return rc;
 
+	spin_lock_irqsave(&timer->lock, flags);
 	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
 	if (enable)
 		l |= OMAP_TIMER_CTRL_CE;
@@ -820,6 +832,7 @@ static int omap_dm_timer_set_match(struct omap_dm_timer *cookie, int enable,
 		l &= ~OMAP_TIMER_CTRL_CE;
 	dmtimer_write(timer, OMAP_TIMER_MATCH_REG, match);
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
 
@@ -830,6 +843,7 @@ static int omap_dm_timer_set_pwm(struct omap_dm_timer *cookie, int def_on,
 				 int toggle, int trigger, int autoreload)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	int rc;
 	u32 l;
@@ -843,6 +857,7 @@ static int omap_dm_timer_set_pwm(struct omap_dm_timer *cookie, int def_on,
 	if (rc)
 		return rc;
 
+	spin_lock_irqsave(&timer->lock, flags);
 	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
 	l &= ~(OMAP_TIMER_CTRL_GPOCFG | OMAP_TIMER_CTRL_SCPWM |
 	       OMAP_TIMER_CTRL_PT | (0x03 << 10) | OMAP_TIMER_CTRL_AR);
@@ -854,6 +869,7 @@ static int omap_dm_timer_set_pwm(struct omap_dm_timer *cookie, int def_on,
 	if (autoreload)
 		l |= OMAP_TIMER_CTRL_AR;
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
 
@@ -887,6 +903,7 @@ static int omap_dm_timer_set_prescaler(struct omap_dm_timer *cookie,
 				       int prescaler)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	int rc;
 	u32 l;
@@ -900,6 +917,7 @@ static int omap_dm_timer_set_prescaler(struct omap_dm_timer *cookie,
 	if (rc)
 		return rc;
 
+	spin_lock_irqsave(&timer->lock, flags);
 	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
 	l &= ~(OMAP_TIMER_CTRL_PRE | (0x07 << 2));
 	if (prescaler >= 0) {
@@ -907,6 +925,7 @@ static int omap_dm_timer_set_prescaler(struct omap_dm_timer *cookie,
 		l |= prescaler << 2;
 	}
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
 
@@ -1093,6 +1112,8 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
 	if (!timer)
 		return  -ENOMEM;
 
+	spin_lock_init(&timer->lock);
+
 	timer->irq = platform_get_irq(pdev, 0);
 	if (timer->irq < 0)
 		return timer->irq;
-- 
2.37.3

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH 1/3] clocksource/drivers/timer-ti-dm: Add lock for register access
@ 2022-10-31 11:56   ` Tony Lindgren
  0 siblings, 0 replies; 10+ messages in thread
From: Tony Lindgren @ 2022-10-31 11:56 UTC (permalink / raw)
  To: Daniel Lezcano, Thomas Gleixner
  Cc: Georgi Vlaev, Grygorii Strashko, Keerthy, Ladislav Michl,
	Nishanth Menon, Suman Anna, Vignesh Raghavendra, linux-kernel,
	linux-omap, linux-arm-kernel

We currently have no locking for timer control register and we assume that
the consumer device using struct omap_dm_timer_ops takes care of locking
for the timer.

In order to prepare for adding irchip and clock provider support to start
removing the struct omap_dm_timer_ops custom API, let's add timer->lock
and use it for the timer control register. This is needed to protect the
control register access between the different kernel frameworks.

Not-Yet-Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/clocksource/timer-ti-dm.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c
--- a/drivers/clocksource/timer-ti-dm.c
+++ b/drivers/clocksource/timer-ti-dm.c
@@ -121,6 +121,7 @@ struct dmtimer {
 	int id;
 	int irq;
 	struct clk *fclk;
+	spinlock_t lock;		/* For shared register access */
 
 	void __iomem	*io_base;
 	int		irq_stat;	/* TISR/IRQSTATUS interrupt status */
@@ -633,6 +634,7 @@ static struct omap_dm_timer *omap_dm_timer_request_by_node(struct device_node *n
 static int omap_dm_timer_free(struct omap_dm_timer *cookie)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	int rc;
 
@@ -649,7 +651,9 @@ static int omap_dm_timer_free(struct omap_dm_timer *cookie)
 		return rc;
 
 	/* Clear timer configuration */
+	spin_lock_irqsave(&timer->lock, flags);
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, 0);
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
 
@@ -728,6 +732,7 @@ __u32 omap_dm_timer_modify_idlect_mask(__u32 inputmask)
 static int omap_dm_timer_start(struct omap_dm_timer *cookie)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	int rc;
 	u32 l;
@@ -742,11 +747,13 @@ static int omap_dm_timer_start(struct omap_dm_timer *cookie)
 	if (rc)
 		return rc;
 
+	spin_lock_irqsave(&timer->lock, flags);
 	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
 	if (!(l & OMAP_TIMER_CTRL_ST)) {
 		l |= OMAP_TIMER_CTRL_ST;
 		dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
 	}
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	return 0;
 }
@@ -754,6 +761,7 @@ static int omap_dm_timer_start(struct omap_dm_timer *cookie)
 static int omap_dm_timer_stop(struct omap_dm_timer *cookie)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	unsigned long rate = 0;
 
@@ -766,7 +774,9 @@ static int omap_dm_timer_stop(struct omap_dm_timer *cookie)
 	if (!timer->omap1)
 		rate = clk_get_rate(timer->fclk);
 
+	spin_lock_irqsave(&timer->lock, flags);
 	__omap_dm_timer_stop(timer, rate);
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
 
@@ -800,6 +810,7 @@ static int omap_dm_timer_set_match(struct omap_dm_timer *cookie, int enable,
 				   unsigned int match)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	int rc;
 	u32 l;
@@ -813,6 +824,7 @@ static int omap_dm_timer_set_match(struct omap_dm_timer *cookie, int enable,
 	if (rc)
 		return rc;
 
+	spin_lock_irqsave(&timer->lock, flags);
 	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
 	if (enable)
 		l |= OMAP_TIMER_CTRL_CE;
@@ -820,6 +832,7 @@ static int omap_dm_timer_set_match(struct omap_dm_timer *cookie, int enable,
 		l &= ~OMAP_TIMER_CTRL_CE;
 	dmtimer_write(timer, OMAP_TIMER_MATCH_REG, match);
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
 
@@ -830,6 +843,7 @@ static int omap_dm_timer_set_pwm(struct omap_dm_timer *cookie, int def_on,
 				 int toggle, int trigger, int autoreload)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	int rc;
 	u32 l;
@@ -843,6 +857,7 @@ static int omap_dm_timer_set_pwm(struct omap_dm_timer *cookie, int def_on,
 	if (rc)
 		return rc;
 
+	spin_lock_irqsave(&timer->lock, flags);
 	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
 	l &= ~(OMAP_TIMER_CTRL_GPOCFG | OMAP_TIMER_CTRL_SCPWM |
 	       OMAP_TIMER_CTRL_PT | (0x03 << 10) | OMAP_TIMER_CTRL_AR);
@@ -854,6 +869,7 @@ static int omap_dm_timer_set_pwm(struct omap_dm_timer *cookie, int def_on,
 	if (autoreload)
 		l |= OMAP_TIMER_CTRL_AR;
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
 
@@ -887,6 +903,7 @@ static int omap_dm_timer_set_prescaler(struct omap_dm_timer *cookie,
 				       int prescaler)
 {
 	struct dmtimer *timer;
+	unsigned long flags;
 	struct device *dev;
 	int rc;
 	u32 l;
@@ -900,6 +917,7 @@ static int omap_dm_timer_set_prescaler(struct omap_dm_timer *cookie,
 	if (rc)
 		return rc;
 
+	spin_lock_irqsave(&timer->lock, flags);
 	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
 	l &= ~(OMAP_TIMER_CTRL_PRE | (0x07 << 2));
 	if (prescaler >= 0) {
@@ -907,6 +925,7 @@ static int omap_dm_timer_set_prescaler(struct omap_dm_timer *cookie,
 		l |= prescaler << 2;
 	}
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
 
@@ -1093,6 +1112,8 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
 	if (!timer)
 		return  -ENOMEM;
 
+	spin_lock_init(&timer->lock);
+
 	timer->irq = platform_get_irq(pdev, 0);
 	if (timer->irq < 0)
 		return timer->irq;
-- 
2.37.3

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH 2/3] clocksource/drivers/timer-ti-dm: Implement chained irq
  2022-10-31 11:56 ` Tony Lindgren
@ 2022-10-31 11:56   ` Tony Lindgren
  -1 siblings, 0 replies; 10+ messages in thread
From: Tony Lindgren @ 2022-10-31 11:56 UTC (permalink / raw)
  To: Daniel Lezcano, Thomas Gleixner
  Cc: Georgi Vlaev, Grygorii Strashko, Keerthy, Ladislav Michl,
	Nishanth Menon, Suman Anna, Vignesh Raghavendra, linux-kernel,
	linux-omap, linux-arm-kernel

The timer has input capture support that can be used for PWM capture. And
some drivers, like remoteproc, already use hardware timers directly as
described in commit e28edc571925 ("remoteproc/omap: Request a timer(s) for
remoteproc usage"). We can simplify the the use of some hardware timer
features for PWM and remoteproc with chained irq.

This allows us to eventually deprecate the following functions exposed
by struct omap_dm_timer_ops:

get_irq()
set_int_enable()
set_int_disable(),
read_status()
write_status()

The functions above can be dropped after omap_remoteproc has been updated
to request a timer irq. For PWM, set_match() is still needed for input
capture.

Not-Yet-Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/clocksource/timer-ti-dm.c | 255 +++++++++++++++++++++++++++++-
 drivers/pwm/pwm-omap-dmtimer.c    |   1 +
 2 files changed, 248 insertions(+), 8 deletions(-)

diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c
--- a/drivers/clocksource/timer-ti-dm.c
+++ b/drivers/clocksource/timer-ti-dm.c
@@ -22,6 +22,8 @@
 #include <linux/clk-provider.h>
 #include <linux/cpu_pm.h>
 #include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
 #include <linux/io.h>
 #include <linux/device.h>
 #include <linux/err.h>
@@ -29,6 +31,7 @@
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
+#include <linux/irqchip/chained_irq.h>
 #include <linux/platform_data/dmtimer-omap.h>
 
 #include <clocksource/timer-ti-dm.h>
@@ -46,6 +49,7 @@
 /* posted mode types */
 #define OMAP_TIMER_NONPOSTED			0x00
 #define OMAP_TIMER_POSTED			0x01
+#define OMAP_DM_TIMER_NR_IRQS			3	/* Capture, overflow, match */
 
 /* register offsets with the write pending bit encoded */
 #define	WPSHIFT					16
@@ -142,6 +146,8 @@ struct dmtimer {
 	struct platform_device *pdev;
 	struct list_head node;
 	struct notifier_block nb;
+	struct irq_chip chip;
+	struct irq_domain *domain;
 };
 
 static u32 omap_reserved_systimers;
@@ -286,6 +292,232 @@ static inline void __omap_dm_timer_int_enable(struct dmtimer *timer,
 	dmtimer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, value);
 }
 
+static inline void __omap_dm_timer_int_disable(struct dmtimer *timer,
+					       unsigned int mask)
+{
+	u32 l = mask;
+
+	if (timer->revision == 1)
+		l = dmtimer_read(timer, timer->irq_ena) & ~mask;
+
+	dmtimer_write(timer, timer->irq_dis, l);
+	l = dmtimer_read(timer, OMAP_TIMER_WAKEUP_EN_REG) & ~mask;
+	dmtimer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, l);
+}
+
+/*
+ * Chained interrupt support for input pin event capture for PWM.
+ */
+static void dmtimer_chain_handler(struct irq_desc *desc)
+{
+	struct dmtimer *timer = irq_desc_get_handler_data(desc);
+	struct irq_chip *chip;
+	unsigned int bit;
+	unsigned long l;
+
+	chip = irq_desc_get_chip(desc);
+	chained_irq_enter(chip, desc);
+	l = dmtimer_read(timer, timer->irq_stat);
+
+	for_each_set_bit(bit, &l, OMAP_DM_TIMER_NR_IRQS)
+		generic_handle_domain_irq(timer->domain, bit);
+
+	chained_irq_exit(chip, desc);
+}
+
+static struct lock_class_key dmtimer_lock_class;
+static struct lock_class_key dmtimer_request_class;
+
+static int dmtimer_irqdomain_map(struct irq_domain *d, unsigned int irq,
+				 irq_hw_number_t hwirq)
+{
+	struct dmtimer *timer = d->host_data;
+
+	irq_set_chip_data(irq, timer);
+	irq_set_chip_and_handler(irq, &timer->chip,
+				 handle_level_irq);
+	irq_set_lockdep_class(irq, &dmtimer_lock_class,
+			      &dmtimer_request_class);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops dmtimer_irqdomain_ops = {
+	.map = dmtimer_irqdomain_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+static void dmtimer_irq_mask(struct irq_data *d);
+static void dmtimer_irq_unmask(struct irq_data *d);
+
+static unsigned int dmtimer_irq_startup(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	unsigned long flags;
+	u32 l, val;
+
+	switch (BIT(d->hwirq)) {
+	case OMAP_TIMER_INT_CAPTURE:
+		val = OMAP_TIMER_CTRL_GPOCFG;
+		break;
+	case OMAP_TIMER_INT_OVERFLOW:
+		goto unmask;
+	case OMAP_TIMER_INT_MATCH:
+		val = OMAP_TIMER_CTRL_CE;
+		break;
+	default:
+		return 0;
+	}
+
+	spin_lock_irqsave(&timer->lock, flags);
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	l |= val;
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
+
+unmask:
+	dmtimer_irq_unmask(d);
+
+	return 0;
+}
+
+static void dmtimer_irq_shutdown(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	unsigned long flags;
+	u32 l, val;
+
+	switch (BIT(d->hwirq)) {
+	case OMAP_TIMER_INT_CAPTURE:
+		val = OMAP_TIMER_CTRL_GPOCFG;
+		break;
+	case OMAP_TIMER_INT_OVERFLOW:
+		goto mask;
+	case OMAP_TIMER_INT_MATCH:
+		val = OMAP_TIMER_CTRL_CE;
+		break;
+	default:
+		return;
+	}
+
+	spin_lock_irqsave(&timer->lock, flags);
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	l &= ~val;
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
+
+mask:
+	dmtimer_irq_mask(d);
+}
+
+static void dmtimer_irq_mask(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	u32 mask = BIT(d->hwirq);
+
+	__omap_dm_timer_int_disable(timer, mask);
+}
+
+static void dmtimer_irq_unmask(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	u32 mask = BIT(d->hwirq);
+
+	__omap_dm_timer_int_enable(timer, mask);
+}
+
+static void dmtimer_irq_ack(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+
+	dmtimer_write(timer, timer->irq_stat, BIT(d->hwirq));
+}
+
+static void dmtimer_irq_mask_ack(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	u32 mask = BIT(d->hwirq);
+
+	__omap_dm_timer_int_disable(timer, mask);
+	dmtimer_write(timer, timer->irq_stat, mask);
+}
+
+static int dmtimer_irq_set_type(struct irq_data *d, unsigned int type)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	unsigned long flags;
+	u32 l;
+
+	/* TIMER_TCLR register TCM bits match IRQ_TYPE values for edge */
+	switch (type) {
+	case IRQ_TYPE_NONE:
+	case IRQ_TYPE_EDGE_RISING:
+	case IRQ_TYPE_EDGE_FALLING:
+	case IRQ_TYPE_EDGE_BOTH:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Configure TCLR register TCM bits */
+	spin_lock_irqsave(&timer->lock, flags);
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	l &= ~(3 << 8);
+	l |= (type << 8);
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
+
+	return 0;
+}
+
+static int dmtimer_irq_set_wake(struct irq_data *d, unsigned int state)
+{
+	if (state)
+		dmtimer_irq_unmask(d);
+	else
+		dmtimer_irq_mask(d);
+
+	return 0;
+}
+
+static int dmtimer_register_irqchip(struct dmtimer *timer)
+{
+	struct device *dev = &timer->pdev->dev;
+	struct device_node *np = dev->of_node;
+
+	/* Can be removed if omap1 starts using timer-ti-dm-systimer */
+	if (timer->omap1)
+		return 0;
+
+	timer->chip.name = dev->driver->name;
+	timer->chip.irq_startup = dmtimer_irq_startup;
+	timer->chip.irq_shutdown = dmtimer_irq_shutdown;
+	timer->chip.irq_mask = dmtimer_irq_mask;
+	timer->chip.irq_unmask = dmtimer_irq_unmask;
+	timer->chip.irq_ack = dmtimer_irq_ack;
+	timer->chip.irq_mask_ack = dmtimer_irq_mask_ack;
+	timer->chip.irq_set_type = dmtimer_irq_set_type;
+	timer->chip.irq_set_wake = dmtimer_irq_set_wake;
+
+	irq_set_chained_handler_and_data(timer->irq,
+					 dmtimer_chain_handler,
+					 timer);
+
+	timer->domain = irq_domain_add_simple(np, OMAP_DM_TIMER_NR_IRQS, 0,
+					      &dmtimer_irqdomain_ops,
+					      timer);
+	if (!timer->domain) {
+		irq_set_chained_handler(timer->irq, NULL);
+		return -EINVAL;
+	}
+
+	/* Initialize dev for runtime PM, see irq_chip_pm_get() */
+	timer->domain->dev = dev;
+
+	return 0;
+}
+
 static inline unsigned int
 __omap_dm_timer_read_counter(struct dmtimer *timer)
 {
@@ -966,7 +1198,6 @@ static int omap_dm_timer_set_int_disable(struct omap_dm_timer *cookie, u32 mask)
 {
 	struct dmtimer *timer;
 	struct device *dev;
-	u32 l = mask;
 	int rc;
 
 	timer = to_dmtimer(cookie);
@@ -978,12 +1209,7 @@ static int omap_dm_timer_set_int_disable(struct omap_dm_timer *cookie, u32 mask)
 	if (rc)
 		return rc;
 
-	if (timer->revision == 1)
-		l = dmtimer_read(timer, timer->irq_ena) & ~mask;
-
-	dmtimer_write(timer, timer->irq_dis, l);
-	l = dmtimer_read(timer, OMAP_TIMER_WAKEUP_EN_REG) & ~mask;
-	dmtimer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, l);
+	__omap_dm_timer_int_disable(timer, mask);
 
 	pm_runtime_put_sync(dev);
 
@@ -1173,6 +1399,12 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
 		/* Clear timer configuration */
 		dmtimer_write(timer, OMAP_TIMER_CTRL_REG, 0);
 
+		ret = dmtimer_register_irqchip(timer);
+		if (ret) {
+			dev_err(dev, "irqchip register failed: %i\n", ret);
+			goto err_put;
+		}
+
 		pm_runtime_put(dev);
 	}
 
@@ -1185,6 +1417,8 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
 
 	return 0;
 
+err_put:
+	pm_runtime_put(dev);
 err_disable:
 	pm_runtime_disable(dev);
 	return ret;
@@ -1205,15 +1439,20 @@ static int omap_dm_timer_remove(struct platform_device *pdev)
 	int ret = -EINVAL;
 
 	spin_lock_irqsave(&dm_timer_lock, flags);
-	list_for_each_entry(timer, &omap_timer_list, node)
+	list_for_each_entry(timer, &omap_timer_list, node) {
 		if (!strcmp(dev_name(&timer->pdev->dev),
 			    dev_name(&pdev->dev))) {
+			if (timer->domain) {
+				irq_domain_remove(timer->domain);
+				irq_set_chained_handler(timer->irq, NULL);
+			}
 			if (!(timer->capability & OMAP_TIMER_ALWON))
 				cpu_pm_unregister_notifier(&timer->nb);
 			list_del(&timer->node);
 			ret = 0;
 			break;
 		}
+	}
 	spin_unlock_irqrestore(&dm_timer_lock, flags);
 
 	pm_runtime_disable(&pdev->dev);
diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c
--- a/drivers/pwm/pwm-omap-dmtimer.c
+++ b/drivers/pwm/pwm-omap-dmtimer.c
@@ -35,6 +35,7 @@
 
 #include <linux/clk.h>
 #include <linux/err.h>
+#include <linux/irq.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
-- 
2.37.3

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH 2/3] clocksource/drivers/timer-ti-dm: Implement chained irq
@ 2022-10-31 11:56   ` Tony Lindgren
  0 siblings, 0 replies; 10+ messages in thread
From: Tony Lindgren @ 2022-10-31 11:56 UTC (permalink / raw)
  To: Daniel Lezcano, Thomas Gleixner
  Cc: Georgi Vlaev, Grygorii Strashko, Keerthy, Ladislav Michl,
	Nishanth Menon, Suman Anna, Vignesh Raghavendra, linux-kernel,
	linux-omap, linux-arm-kernel

The timer has input capture support that can be used for PWM capture. And
some drivers, like remoteproc, already use hardware timers directly as
described in commit e28edc571925 ("remoteproc/omap: Request a timer(s) for
remoteproc usage"). We can simplify the the use of some hardware timer
features for PWM and remoteproc with chained irq.

This allows us to eventually deprecate the following functions exposed
by struct omap_dm_timer_ops:

get_irq()
set_int_enable()
set_int_disable(),
read_status()
write_status()

The functions above can be dropped after omap_remoteproc has been updated
to request a timer irq. For PWM, set_match() is still needed for input
capture.

Not-Yet-Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/clocksource/timer-ti-dm.c | 255 +++++++++++++++++++++++++++++-
 drivers/pwm/pwm-omap-dmtimer.c    |   1 +
 2 files changed, 248 insertions(+), 8 deletions(-)

diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c
--- a/drivers/clocksource/timer-ti-dm.c
+++ b/drivers/clocksource/timer-ti-dm.c
@@ -22,6 +22,8 @@
 #include <linux/clk-provider.h>
 #include <linux/cpu_pm.h>
 #include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
 #include <linux/io.h>
 #include <linux/device.h>
 #include <linux/err.h>
@@ -29,6 +31,7 @@
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
+#include <linux/irqchip/chained_irq.h>
 #include <linux/platform_data/dmtimer-omap.h>
 
 #include <clocksource/timer-ti-dm.h>
@@ -46,6 +49,7 @@
 /* posted mode types */
 #define OMAP_TIMER_NONPOSTED			0x00
 #define OMAP_TIMER_POSTED			0x01
+#define OMAP_DM_TIMER_NR_IRQS			3	/* Capture, overflow, match */
 
 /* register offsets with the write pending bit encoded */
 #define	WPSHIFT					16
@@ -142,6 +146,8 @@ struct dmtimer {
 	struct platform_device *pdev;
 	struct list_head node;
 	struct notifier_block nb;
+	struct irq_chip chip;
+	struct irq_domain *domain;
 };
 
 static u32 omap_reserved_systimers;
@@ -286,6 +292,232 @@ static inline void __omap_dm_timer_int_enable(struct dmtimer *timer,
 	dmtimer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, value);
 }
 
+static inline void __omap_dm_timer_int_disable(struct dmtimer *timer,
+					       unsigned int mask)
+{
+	u32 l = mask;
+
+	if (timer->revision == 1)
+		l = dmtimer_read(timer, timer->irq_ena) & ~mask;
+
+	dmtimer_write(timer, timer->irq_dis, l);
+	l = dmtimer_read(timer, OMAP_TIMER_WAKEUP_EN_REG) & ~mask;
+	dmtimer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, l);
+}
+
+/*
+ * Chained interrupt support for input pin event capture for PWM.
+ */
+static void dmtimer_chain_handler(struct irq_desc *desc)
+{
+	struct dmtimer *timer = irq_desc_get_handler_data(desc);
+	struct irq_chip *chip;
+	unsigned int bit;
+	unsigned long l;
+
+	chip = irq_desc_get_chip(desc);
+	chained_irq_enter(chip, desc);
+	l = dmtimer_read(timer, timer->irq_stat);
+
+	for_each_set_bit(bit, &l, OMAP_DM_TIMER_NR_IRQS)
+		generic_handle_domain_irq(timer->domain, bit);
+
+	chained_irq_exit(chip, desc);
+}
+
+static struct lock_class_key dmtimer_lock_class;
+static struct lock_class_key dmtimer_request_class;
+
+static int dmtimer_irqdomain_map(struct irq_domain *d, unsigned int irq,
+				 irq_hw_number_t hwirq)
+{
+	struct dmtimer *timer = d->host_data;
+
+	irq_set_chip_data(irq, timer);
+	irq_set_chip_and_handler(irq, &timer->chip,
+				 handle_level_irq);
+	irq_set_lockdep_class(irq, &dmtimer_lock_class,
+			      &dmtimer_request_class);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops dmtimer_irqdomain_ops = {
+	.map = dmtimer_irqdomain_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+static void dmtimer_irq_mask(struct irq_data *d);
+static void dmtimer_irq_unmask(struct irq_data *d);
+
+static unsigned int dmtimer_irq_startup(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	unsigned long flags;
+	u32 l, val;
+
+	switch (BIT(d->hwirq)) {
+	case OMAP_TIMER_INT_CAPTURE:
+		val = OMAP_TIMER_CTRL_GPOCFG;
+		break;
+	case OMAP_TIMER_INT_OVERFLOW:
+		goto unmask;
+	case OMAP_TIMER_INT_MATCH:
+		val = OMAP_TIMER_CTRL_CE;
+		break;
+	default:
+		return 0;
+	}
+
+	spin_lock_irqsave(&timer->lock, flags);
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	l |= val;
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
+
+unmask:
+	dmtimer_irq_unmask(d);
+
+	return 0;
+}
+
+static void dmtimer_irq_shutdown(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	unsigned long flags;
+	u32 l, val;
+
+	switch (BIT(d->hwirq)) {
+	case OMAP_TIMER_INT_CAPTURE:
+		val = OMAP_TIMER_CTRL_GPOCFG;
+		break;
+	case OMAP_TIMER_INT_OVERFLOW:
+		goto mask;
+	case OMAP_TIMER_INT_MATCH:
+		val = OMAP_TIMER_CTRL_CE;
+		break;
+	default:
+		return;
+	}
+
+	spin_lock_irqsave(&timer->lock, flags);
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	l &= ~val;
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
+
+mask:
+	dmtimer_irq_mask(d);
+}
+
+static void dmtimer_irq_mask(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	u32 mask = BIT(d->hwirq);
+
+	__omap_dm_timer_int_disable(timer, mask);
+}
+
+static void dmtimer_irq_unmask(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	u32 mask = BIT(d->hwirq);
+
+	__omap_dm_timer_int_enable(timer, mask);
+}
+
+static void dmtimer_irq_ack(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+
+	dmtimer_write(timer, timer->irq_stat, BIT(d->hwirq));
+}
+
+static void dmtimer_irq_mask_ack(struct irq_data *d)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	u32 mask = BIT(d->hwirq);
+
+	__omap_dm_timer_int_disable(timer, mask);
+	dmtimer_write(timer, timer->irq_stat, mask);
+}
+
+static int dmtimer_irq_set_type(struct irq_data *d, unsigned int type)
+{
+	struct dmtimer *timer = irq_data_get_irq_chip_data(d);
+	unsigned long flags;
+	u32 l;
+
+	/* TIMER_TCLR register TCM bits match IRQ_TYPE values for edge */
+	switch (type) {
+	case IRQ_TYPE_NONE:
+	case IRQ_TYPE_EDGE_RISING:
+	case IRQ_TYPE_EDGE_FALLING:
+	case IRQ_TYPE_EDGE_BOTH:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Configure TCLR register TCM bits */
+	spin_lock_irqsave(&timer->lock, flags);
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	l &= ~(3 << 8);
+	l |= (type << 8);
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+	spin_unlock_irqrestore(&timer->lock, flags);
+
+	return 0;
+}
+
+static int dmtimer_irq_set_wake(struct irq_data *d, unsigned int state)
+{
+	if (state)
+		dmtimer_irq_unmask(d);
+	else
+		dmtimer_irq_mask(d);
+
+	return 0;
+}
+
+static int dmtimer_register_irqchip(struct dmtimer *timer)
+{
+	struct device *dev = &timer->pdev->dev;
+	struct device_node *np = dev->of_node;
+
+	/* Can be removed if omap1 starts using timer-ti-dm-systimer */
+	if (timer->omap1)
+		return 0;
+
+	timer->chip.name = dev->driver->name;
+	timer->chip.irq_startup = dmtimer_irq_startup;
+	timer->chip.irq_shutdown = dmtimer_irq_shutdown;
+	timer->chip.irq_mask = dmtimer_irq_mask;
+	timer->chip.irq_unmask = dmtimer_irq_unmask;
+	timer->chip.irq_ack = dmtimer_irq_ack;
+	timer->chip.irq_mask_ack = dmtimer_irq_mask_ack;
+	timer->chip.irq_set_type = dmtimer_irq_set_type;
+	timer->chip.irq_set_wake = dmtimer_irq_set_wake;
+
+	irq_set_chained_handler_and_data(timer->irq,
+					 dmtimer_chain_handler,
+					 timer);
+
+	timer->domain = irq_domain_add_simple(np, OMAP_DM_TIMER_NR_IRQS, 0,
+					      &dmtimer_irqdomain_ops,
+					      timer);
+	if (!timer->domain) {
+		irq_set_chained_handler(timer->irq, NULL);
+		return -EINVAL;
+	}
+
+	/* Initialize dev for runtime PM, see irq_chip_pm_get() */
+	timer->domain->dev = dev;
+
+	return 0;
+}
+
 static inline unsigned int
 __omap_dm_timer_read_counter(struct dmtimer *timer)
 {
@@ -966,7 +1198,6 @@ static int omap_dm_timer_set_int_disable(struct omap_dm_timer *cookie, u32 mask)
 {
 	struct dmtimer *timer;
 	struct device *dev;
-	u32 l = mask;
 	int rc;
 
 	timer = to_dmtimer(cookie);
@@ -978,12 +1209,7 @@ static int omap_dm_timer_set_int_disable(struct omap_dm_timer *cookie, u32 mask)
 	if (rc)
 		return rc;
 
-	if (timer->revision == 1)
-		l = dmtimer_read(timer, timer->irq_ena) & ~mask;
-
-	dmtimer_write(timer, timer->irq_dis, l);
-	l = dmtimer_read(timer, OMAP_TIMER_WAKEUP_EN_REG) & ~mask;
-	dmtimer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, l);
+	__omap_dm_timer_int_disable(timer, mask);
 
 	pm_runtime_put_sync(dev);
 
@@ -1173,6 +1399,12 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
 		/* Clear timer configuration */
 		dmtimer_write(timer, OMAP_TIMER_CTRL_REG, 0);
 
+		ret = dmtimer_register_irqchip(timer);
+		if (ret) {
+			dev_err(dev, "irqchip register failed: %i\n", ret);
+			goto err_put;
+		}
+
 		pm_runtime_put(dev);
 	}
 
@@ -1185,6 +1417,8 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
 
 	return 0;
 
+err_put:
+	pm_runtime_put(dev);
 err_disable:
 	pm_runtime_disable(dev);
 	return ret;
@@ -1205,15 +1439,20 @@ static int omap_dm_timer_remove(struct platform_device *pdev)
 	int ret = -EINVAL;
 
 	spin_lock_irqsave(&dm_timer_lock, flags);
-	list_for_each_entry(timer, &omap_timer_list, node)
+	list_for_each_entry(timer, &omap_timer_list, node) {
 		if (!strcmp(dev_name(&timer->pdev->dev),
 			    dev_name(&pdev->dev))) {
+			if (timer->domain) {
+				irq_domain_remove(timer->domain);
+				irq_set_chained_handler(timer->irq, NULL);
+			}
 			if (!(timer->capability & OMAP_TIMER_ALWON))
 				cpu_pm_unregister_notifier(&timer->nb);
 			list_del(&timer->node);
 			ret = 0;
 			break;
 		}
+	}
 	spin_unlock_irqrestore(&dm_timer_lock, flags);
 
 	pm_runtime_disable(&pdev->dev);
diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c
--- a/drivers/pwm/pwm-omap-dmtimer.c
+++ b/drivers/pwm/pwm-omap-dmtimer.c
@@ -35,6 +35,7 @@
 
 #include <linux/clk.h>
 #include <linux/err.h>
+#include <linux/irq.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
-- 
2.37.3

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH 3/3] clocksource/drivers/timer-ti-dm: Add clock provider support
  2022-10-31 11:56 ` Tony Lindgren
@ 2022-10-31 11:56   ` Tony Lindgren
  -1 siblings, 0 replies; 10+ messages in thread
From: Tony Lindgren @ 2022-10-31 11:56 UTC (permalink / raw)
  To: Daniel Lezcano, Thomas Gleixner
  Cc: Georgi Vlaev, Grygorii Strashko, Keerthy, Ladislav Michl,
	Nishanth Menon, Suman Anna, Vignesh Raghavendra, linux-kernel,
	linux-omap, linux-arm-kernel

Kernel drivers can use some of the dedicated hardware timer features to
provide a clock source on a timer output pin. Instead of exposing the
timer features with struct omap_dm_timer_ops, let's add clock provider
support. Also the PWM driver can be simplified with clk_set_duty_cycle()
and clk_get_scaled_duty_cycle().

This allows us to eventually deprecate the following functions exposed
by struct omap_dm_timer_ops:

enable()
disable()
start()
stop()
get_fclk()
set_source()

Not-Yet-Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/clocksource/timer-ti-dm.c | 315 +++++++++++++++++++++++++++++-
 include/clocksource/timer-ti-dm.h |   2 +
 2 files changed, 309 insertions(+), 8 deletions(-)

diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c
--- a/drivers/clocksource/timer-ti-dm.c
+++ b/drivers/clocksource/timer-ti-dm.c
@@ -99,6 +99,16 @@
 #define OMAP_TIMER_TICK_INT_MASK_COUNT_REG				\
 		(_OMAP_TIMER_TICK_INT_MASK_COUNT_OFFSET | (WP_TOWR << WPSHIFT))
 
+/*
+ * Timer counts up with a maximum theoretical value of 0xffffffff meaning one
+ * clock cycle. In counter mode, the timer is however limited to a maximum value
+ * of 0xfffffffe for two clock cycles. And in PWM mode, the timer is limited to
+ * a maximum value of 0xfffffffd for three clock cycles. DMTIMER_CYCLES is only
+ * used for clock cycle calculations, and does not consider the hardware limits.
+ * The related timer functions check for the minimum allowed clock cycles.
+ */
+#define DMTIMER_CYCLES(x)			(0xffffffff - (x) + 1)
+
 struct timer_regs {
 	u32 ocp_cfg;
 	u32 tidr;
@@ -143,11 +153,14 @@ struct dmtimer {
 	int revision;
 	u32 capability;
 	u32 errata;
+	u32 load_val;
+	u32 match_val;
 	struct platform_device *pdev;
 	struct list_head node;
 	struct notifier_block nb;
 	struct irq_chip chip;
 	struct irq_domain *domain;
+	struct clk_hw hw;
 };
 
 static u32 omap_reserved_systimers;
@@ -261,6 +274,19 @@ static inline void __omap_dm_timer_enable_posted(struct dmtimer *timer)
 	timer->posted = OMAP_TIMER_POSTED;
 }
 
+static inline void __omap_dm_timer_start(struct dmtimer *timer, bool autoreload)
+{
+	u32 l, mask;
+
+	mask = OMAP_TIMER_CTRL_ST;
+	if (autoreload)
+		mask |= OMAP_TIMER_CTRL_AR;
+
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	if ((l & mask) != mask)
+		dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l | mask);
+}
+
 static inline void __omap_dm_timer_stop(struct dmtimer *timer,
 					unsigned long rate)
 {
@@ -610,6 +636,275 @@ static int omap_dm_timer_reset(struct dmtimer *timer)
 	return 0;
 }
 
+/* Clock provider support */
+#if defined(CONFIG_COMMON_CLK)
+
+#define hw_to_dmtimer(x) container_of(x, struct dmtimer, hw)
+
+static int dmtimer_clk_prepare(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	struct device *dev = &timer->pdev->dev;
+	int rc;
+	u32 l;
+
+	rc = pm_runtime_resume_and_get(dev);
+	if (rc)
+		return rc;
+
+	/* Clear output and set toggle modulation */
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	l &= ~OMAP_TIMER_CTRL_GPOCFG;
+	l |= OMAP_TIMER_CTRL_PT;
+	l &= ~(OMAP_TIMER_CTRL_TRIGGER_MASK << OMAP_TIMER_CTRL_TRIGGER_SHIFT);
+	l |= OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE << OMAP_TIMER_CTRL_TRIGGER_SHIFT;
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+
+	return rc;
+}
+
+static void dmtimer_clk_unprepare(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	struct device *dev = &timer->pdev->dev;
+	u32 l;
+
+	/* Clear output and toggle modulation */
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	l &= ~(OMAP_TIMER_CTRL_GPOCFG | OMAP_TIMER_CTRL_PT);
+	l &= ~(OMAP_TIMER_CTRL_TRIGGER_MASK << OMAP_TIMER_CTRL_TRIGGER_SHIFT);
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+
+	pm_runtime_put_sync(dev);
+}
+
+static int dmtimer_clk_enable(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	unsigned long flags;
+
+	/* Configure immediate overflow to toggle PWM on first event, see TRM */
+	spin_lock_irqsave(&timer->lock, flags);
+	dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, DMTIMER_CYCLES(1));
+	__omap_dm_timer_start(timer, true);
+	spin_unlock_irqrestore(&timer->lock, flags);
+
+	return 0;
+}
+
+static void dmtimer_clk_disable(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	unsigned long rate = 0;
+
+	if (!timer->omap1)
+		rate = clk_get_rate(timer->fclk);
+
+	__omap_dm_timer_stop(timer, rate);
+}
+
+static int dmtimer_clk_is_enabled(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+
+	return atomic_read(&timer->enabled);
+}
+
+static int dmtimer_get_prescale(struct dmtimer *timer)
+{
+	u32 l;
+
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	if (!(l & OMAP_TIMER_CTRL_PRE))
+		return 1;
+
+	return 2 << ((l >> OMAP_TIMER_CTRL_PTV_SHIFT) & 7);
+}
+
+static unsigned long dmtimer_clk_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	int prescale;
+	u32 loadval;
+
+	prescale = dmtimer_get_prescale(timer);
+	loadval = dmtimer_read(timer, OMAP_TIMER_LOAD_REG);
+
+	return parent_rate / prescale / DMTIMER_CYCLES(loadval);
+}
+
+static long dmtimer_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+				   unsigned long *parent_rate)
+{
+	unsigned int div;
+
+	div = DIV_ROUND_CLOSEST(*parent_rate, rate);
+
+	return *parent_rate / div;
+}
+
+static int dmtimer_clk_get_duty_cycle(struct clk_hw *hw,
+				      struct clk_duty *duty)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	u32 match, load;
+
+	match = dmtimer_read(timer, OMAP_TIMER_MATCH_REG);
+	load = dmtimer_read(timer, OMAP_TIMER_LOAD_REG);
+	duty->num =  match - load;
+	duty->den = DMTIMER_CYCLES(load);
+
+	return 0;
+}
+
+/*
+ * See am62 TRM "Table 12-312. Prescaler Clock Ratio Values" for PWM limits for
+ * the max load register value.
+ */
+static int dmtimer_clk_set_duty_cycle(struct clk_hw *hw,
+				      struct clk_duty *duty)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	u32 cycles, match;
+
+	if (!timer->load_val) {
+		dev_err(&timer->pdev->dev, "no rate configured for duty_cycle");
+		return -EINVAL;
+	}
+
+	cycles = DMTIMER_CYCLES(timer->load_val);
+	match = mult_frac(cycles, duty->num, duty->den);
+	timer->match_val = timer->load_val + match;
+	dmtimer_write(timer, OMAP_TIMER_MATCH_REG, timer->match_val);
+
+	return 0;
+}
+
+/*
+ * Note that setting the TCLR register prescaler value is not currently
+ * implemented, it can be added if needed with some clock sources.
+ */
+static int dmtimer_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent_rate)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	struct device *dev = &timer->pdev->dev;
+	struct clk_duty duty;
+	unsigned int div;
+	int prescale, rc;
+	u32 cycles;
+
+	if (rate > parent_rate)
+		return -EINVAL;
+
+	prescale = dmtimer_get_prescale(timer);
+	div = parent_rate / prescale;
+	cycles = div / rate;
+
+	/*
+	 * TRM "Timer Pulse-Width Modulation" chapter says in PWM mode
+	 * TIMER_TLDR load register must be limited to max 0xfffffffd
+	 * limiting the minimum usable clock cycles to 3.
+	 */
+	if (cycles < 3)
+		return -EINVAL;
+
+	timer->load_val = DMTIMER_CYCLES(cycles);
+	dmtimer_write(timer, OMAP_TIMER_LOAD_REG, timer->load_val);
+
+	/* Configure 50% duty cycle by default */
+	if (!timer->match_val) {
+		duty.num = div / 2;
+		duty.den = div;
+
+		rc = dmtimer_clk_set_duty_cycle(hw, &duty);
+		if (rc)
+			dev_err(dev, "set_rate duty cycle failed: %i\n", rc);
+	}
+
+	return rc;
+}
+
+static int dmtimer_clk_get_phase(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	u32 l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+
+	return (l & OMAP_TIMER_CTRL_SCPWM) ? 180 : 0;
+}
+
+static int dmtimer_clk_set_phase(struct clk_hw *hw, int degrees)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	u32 l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+
+	l &= ~OMAP_TIMER_CTRL_SCPWM;
+
+	switch (degrees) {
+	case 0:
+		break;
+	case 180:
+		l |= OMAP_TIMER_CTRL_SCPWM;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+
+	return 0;
+}
+
+static const struct clk_ops dmtimer_clk_ops = {
+	.prepare = dmtimer_clk_prepare,
+	.unprepare = dmtimer_clk_unprepare,
+	.enable = dmtimer_clk_enable,
+	.disable = dmtimer_clk_disable,
+	.is_enabled = dmtimer_clk_is_enabled,
+	.recalc_rate = dmtimer_clk_recalc_rate,
+	.round_rate = dmtimer_clk_round_rate,
+	.set_rate = dmtimer_clk_set_rate,
+	.get_phase = dmtimer_clk_get_phase,
+	.set_phase = dmtimer_clk_set_phase,
+	.get_duty_cycle = dmtimer_clk_get_duty_cycle,
+	.set_duty_cycle = dmtimer_clk_set_duty_cycle,
+};
+
+static int dmtimer_register_clock(struct dmtimer *timer)
+{
+	struct device *dev = &timer->pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct clk_init_data init = { };
+	struct clk_hw *hw;
+	const char *fck;
+	int ret;
+
+	if (!np || !of_find_property(np, "#clock-cells", NULL))
+		return 0;
+
+	hw = &timer->hw;
+	init.name = dev_name(dev);
+	init.ops = &dmtimer_clk_ops;
+	init.flags = CLK_GET_RATE_NOCACHE;
+	init.num_parents = 1;
+	fck = __clk_get_name(timer->fclk);
+	init.parent_names = &fck;
+	hw->init = &init;
+	ret = devm_clk_hw_register(dev, hw);
+	if (ret)
+		return ret;
+
+	return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw);
+}
+
+#else
+static inline int dmtimer_register_clock(struct dmtimer *timer)
+{
+	return 0;
+}
+#endif	/* CONFIG_COMMON_CLK */
+
 /*
  * Functions exposed to PWM and remoteproc drivers via platform_data.
  * Do not use these in the driver, these will get deprecated and will
@@ -885,6 +1180,7 @@ static int omap_dm_timer_free(struct omap_dm_timer *cookie)
 	/* Clear timer configuration */
 	spin_lock_irqsave(&timer->lock, flags);
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, 0);
+	dmtimer_write(timer, OMAP_TIMER_LOAD_REG, timer->load_val);
 	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
@@ -967,7 +1263,6 @@ static int omap_dm_timer_start(struct omap_dm_timer *cookie)
 	unsigned long flags;
 	struct device *dev;
 	int rc;
-	u32 l;
 
 	timer = to_dmtimer(cookie);
 	if (unlikely(!timer))
@@ -980,11 +1275,7 @@ static int omap_dm_timer_start(struct omap_dm_timer *cookie)
 		return rc;
 
 	spin_lock_irqsave(&timer->lock, flags);
-	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
-	if (!(l & OMAP_TIMER_CTRL_ST)) {
-		l |= OMAP_TIMER_CTRL_ST;
-		dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
-	}
+	__omap_dm_timer_start(timer, false);
 	spin_unlock_irqrestore(&timer->lock, flags);
 
 	return 0;
@@ -1092,12 +1383,14 @@ static int omap_dm_timer_set_pwm(struct omap_dm_timer *cookie, int def_on,
 	spin_lock_irqsave(&timer->lock, flags);
 	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
 	l &= ~(OMAP_TIMER_CTRL_GPOCFG | OMAP_TIMER_CTRL_SCPWM |
-	       OMAP_TIMER_CTRL_PT | (0x03 << 10) | OMAP_TIMER_CTRL_AR);
+	       OMAP_TIMER_CTRL_PT |
+	       (OMAP_TIMER_CTRL_TRIGGER_MASK << OMAP_TIMER_CTRL_TRIGGER_SHIFT) |
+	       OMAP_TIMER_CTRL_AR);
 	if (def_on)
 		l |= OMAP_TIMER_CTRL_SCPWM;
 	if (toggle)
 		l |= OMAP_TIMER_CTRL_PT;
-	l |= trigger << 10;
+	l |= trigger << OMAP_TIMER_CTRL_TRIGGER_SHIFT;
 	if (autoreload)
 		l |= OMAP_TIMER_CTRL_AR;
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
@@ -1399,6 +1692,12 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
 		/* Clear timer configuration */
 		dmtimer_write(timer, OMAP_TIMER_CTRL_REG, 0);
 
+		ret = dmtimer_register_clock(timer);
+		if (ret) {
+			dev_err(dev, "clock provider register failed: %i\n", ret);
+			goto err_put;
+		}
+
 		ret = dmtimer_register_irqchip(timer);
 		if (ret) {
 			dev_err(dev, "irqchip register failed: %i\n", ret);
diff --git a/include/clocksource/timer-ti-dm.h b/include/clocksource/timer-ti-dm.h
--- a/include/clocksource/timer-ti-dm.h
+++ b/include/clocksource/timer-ti-dm.h
@@ -98,6 +98,8 @@ u32 omap_dm_timer_modify_idlect_mask(u32 inputmask);
 #define		OMAP_TIMER_CTRL_GPOCFG		(1 << 14)
 #define		OMAP_TIMER_CTRL_CAPTMODE	(1 << 13)
 #define		OMAP_TIMER_CTRL_PT		(1 << 12)
+#define		OMAP_TIMER_CTRL_TRIGGER_SHIFT	10
+#define		OMAP_TIMER_CTRL_TRIGGER_MASK	3
 #define		OMAP_TIMER_CTRL_TCM_LOWTOHIGH	(0x1 << 8)
 #define		OMAP_TIMER_CTRL_TCM_HIGHTOLOW	(0x2 << 8)
 #define		OMAP_TIMER_CTRL_TCM_BOTHEDGES	(0x3 << 8)
-- 
2.37.3

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH 3/3] clocksource/drivers/timer-ti-dm: Add clock provider support
@ 2022-10-31 11:56   ` Tony Lindgren
  0 siblings, 0 replies; 10+ messages in thread
From: Tony Lindgren @ 2022-10-31 11:56 UTC (permalink / raw)
  To: Daniel Lezcano, Thomas Gleixner
  Cc: Georgi Vlaev, Grygorii Strashko, Keerthy, Ladislav Michl,
	Nishanth Menon, Suman Anna, Vignesh Raghavendra, linux-kernel,
	linux-omap, linux-arm-kernel

Kernel drivers can use some of the dedicated hardware timer features to
provide a clock source on a timer output pin. Instead of exposing the
timer features with struct omap_dm_timer_ops, let's add clock provider
support. Also the PWM driver can be simplified with clk_set_duty_cycle()
and clk_get_scaled_duty_cycle().

This allows us to eventually deprecate the following functions exposed
by struct omap_dm_timer_ops:

enable()
disable()
start()
stop()
get_fclk()
set_source()

Not-Yet-Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/clocksource/timer-ti-dm.c | 315 +++++++++++++++++++++++++++++-
 include/clocksource/timer-ti-dm.h |   2 +
 2 files changed, 309 insertions(+), 8 deletions(-)

diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c
--- a/drivers/clocksource/timer-ti-dm.c
+++ b/drivers/clocksource/timer-ti-dm.c
@@ -99,6 +99,16 @@
 #define OMAP_TIMER_TICK_INT_MASK_COUNT_REG				\
 		(_OMAP_TIMER_TICK_INT_MASK_COUNT_OFFSET | (WP_TOWR << WPSHIFT))
 
+/*
+ * Timer counts up with a maximum theoretical value of 0xffffffff meaning one
+ * clock cycle. In counter mode, the timer is however limited to a maximum value
+ * of 0xfffffffe for two clock cycles. And in PWM mode, the timer is limited to
+ * a maximum value of 0xfffffffd for three clock cycles. DMTIMER_CYCLES is only
+ * used for clock cycle calculations, and does not consider the hardware limits.
+ * The related timer functions check for the minimum allowed clock cycles.
+ */
+#define DMTIMER_CYCLES(x)			(0xffffffff - (x) + 1)
+
 struct timer_regs {
 	u32 ocp_cfg;
 	u32 tidr;
@@ -143,11 +153,14 @@ struct dmtimer {
 	int revision;
 	u32 capability;
 	u32 errata;
+	u32 load_val;
+	u32 match_val;
 	struct platform_device *pdev;
 	struct list_head node;
 	struct notifier_block nb;
 	struct irq_chip chip;
 	struct irq_domain *domain;
+	struct clk_hw hw;
 };
 
 static u32 omap_reserved_systimers;
@@ -261,6 +274,19 @@ static inline void __omap_dm_timer_enable_posted(struct dmtimer *timer)
 	timer->posted = OMAP_TIMER_POSTED;
 }
 
+static inline void __omap_dm_timer_start(struct dmtimer *timer, bool autoreload)
+{
+	u32 l, mask;
+
+	mask = OMAP_TIMER_CTRL_ST;
+	if (autoreload)
+		mask |= OMAP_TIMER_CTRL_AR;
+
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	if ((l & mask) != mask)
+		dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l | mask);
+}
+
 static inline void __omap_dm_timer_stop(struct dmtimer *timer,
 					unsigned long rate)
 {
@@ -610,6 +636,275 @@ static int omap_dm_timer_reset(struct dmtimer *timer)
 	return 0;
 }
 
+/* Clock provider support */
+#if defined(CONFIG_COMMON_CLK)
+
+#define hw_to_dmtimer(x) container_of(x, struct dmtimer, hw)
+
+static int dmtimer_clk_prepare(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	struct device *dev = &timer->pdev->dev;
+	int rc;
+	u32 l;
+
+	rc = pm_runtime_resume_and_get(dev);
+	if (rc)
+		return rc;
+
+	/* Clear output and set toggle modulation */
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	l &= ~OMAP_TIMER_CTRL_GPOCFG;
+	l |= OMAP_TIMER_CTRL_PT;
+	l &= ~(OMAP_TIMER_CTRL_TRIGGER_MASK << OMAP_TIMER_CTRL_TRIGGER_SHIFT);
+	l |= OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE << OMAP_TIMER_CTRL_TRIGGER_SHIFT;
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+
+	return rc;
+}
+
+static void dmtimer_clk_unprepare(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	struct device *dev = &timer->pdev->dev;
+	u32 l;
+
+	/* Clear output and toggle modulation */
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	l &= ~(OMAP_TIMER_CTRL_GPOCFG | OMAP_TIMER_CTRL_PT);
+	l &= ~(OMAP_TIMER_CTRL_TRIGGER_MASK << OMAP_TIMER_CTRL_TRIGGER_SHIFT);
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+
+	pm_runtime_put_sync(dev);
+}
+
+static int dmtimer_clk_enable(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	unsigned long flags;
+
+	/* Configure immediate overflow to toggle PWM on first event, see TRM */
+	spin_lock_irqsave(&timer->lock, flags);
+	dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, DMTIMER_CYCLES(1));
+	__omap_dm_timer_start(timer, true);
+	spin_unlock_irqrestore(&timer->lock, flags);
+
+	return 0;
+}
+
+static void dmtimer_clk_disable(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	unsigned long rate = 0;
+
+	if (!timer->omap1)
+		rate = clk_get_rate(timer->fclk);
+
+	__omap_dm_timer_stop(timer, rate);
+}
+
+static int dmtimer_clk_is_enabled(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+
+	return atomic_read(&timer->enabled);
+}
+
+static int dmtimer_get_prescale(struct dmtimer *timer)
+{
+	u32 l;
+
+	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+	if (!(l & OMAP_TIMER_CTRL_PRE))
+		return 1;
+
+	return 2 << ((l >> OMAP_TIMER_CTRL_PTV_SHIFT) & 7);
+}
+
+static unsigned long dmtimer_clk_recalc_rate(struct clk_hw *hw,
+					     unsigned long parent_rate)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	int prescale;
+	u32 loadval;
+
+	prescale = dmtimer_get_prescale(timer);
+	loadval = dmtimer_read(timer, OMAP_TIMER_LOAD_REG);
+
+	return parent_rate / prescale / DMTIMER_CYCLES(loadval);
+}
+
+static long dmtimer_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+				   unsigned long *parent_rate)
+{
+	unsigned int div;
+
+	div = DIV_ROUND_CLOSEST(*parent_rate, rate);
+
+	return *parent_rate / div;
+}
+
+static int dmtimer_clk_get_duty_cycle(struct clk_hw *hw,
+				      struct clk_duty *duty)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	u32 match, load;
+
+	match = dmtimer_read(timer, OMAP_TIMER_MATCH_REG);
+	load = dmtimer_read(timer, OMAP_TIMER_LOAD_REG);
+	duty->num =  match - load;
+	duty->den = DMTIMER_CYCLES(load);
+
+	return 0;
+}
+
+/*
+ * See am62 TRM "Table 12-312. Prescaler Clock Ratio Values" for PWM limits for
+ * the max load register value.
+ */
+static int dmtimer_clk_set_duty_cycle(struct clk_hw *hw,
+				      struct clk_duty *duty)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	u32 cycles, match;
+
+	if (!timer->load_val) {
+		dev_err(&timer->pdev->dev, "no rate configured for duty_cycle");
+		return -EINVAL;
+	}
+
+	cycles = DMTIMER_CYCLES(timer->load_val);
+	match = mult_frac(cycles, duty->num, duty->den);
+	timer->match_val = timer->load_val + match;
+	dmtimer_write(timer, OMAP_TIMER_MATCH_REG, timer->match_val);
+
+	return 0;
+}
+
+/*
+ * Note that setting the TCLR register prescaler value is not currently
+ * implemented, it can be added if needed with some clock sources.
+ */
+static int dmtimer_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent_rate)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	struct device *dev = &timer->pdev->dev;
+	struct clk_duty duty;
+	unsigned int div;
+	int prescale, rc;
+	u32 cycles;
+
+	if (rate > parent_rate)
+		return -EINVAL;
+
+	prescale = dmtimer_get_prescale(timer);
+	div = parent_rate / prescale;
+	cycles = div / rate;
+
+	/*
+	 * TRM "Timer Pulse-Width Modulation" chapter says in PWM mode
+	 * TIMER_TLDR load register must be limited to max 0xfffffffd
+	 * limiting the minimum usable clock cycles to 3.
+	 */
+	if (cycles < 3)
+		return -EINVAL;
+
+	timer->load_val = DMTIMER_CYCLES(cycles);
+	dmtimer_write(timer, OMAP_TIMER_LOAD_REG, timer->load_val);
+
+	/* Configure 50% duty cycle by default */
+	if (!timer->match_val) {
+		duty.num = div / 2;
+		duty.den = div;
+
+		rc = dmtimer_clk_set_duty_cycle(hw, &duty);
+		if (rc)
+			dev_err(dev, "set_rate duty cycle failed: %i\n", rc);
+	}
+
+	return rc;
+}
+
+static int dmtimer_clk_get_phase(struct clk_hw *hw)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	u32 l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+
+	return (l & OMAP_TIMER_CTRL_SCPWM) ? 180 : 0;
+}
+
+static int dmtimer_clk_set_phase(struct clk_hw *hw, int degrees)
+{
+	struct dmtimer *timer = hw_to_dmtimer(hw);
+	u32 l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
+
+	l &= ~OMAP_TIMER_CTRL_SCPWM;
+
+	switch (degrees) {
+	case 0:
+		break;
+	case 180:
+		l |= OMAP_TIMER_CTRL_SCPWM;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
+
+	return 0;
+}
+
+static const struct clk_ops dmtimer_clk_ops = {
+	.prepare = dmtimer_clk_prepare,
+	.unprepare = dmtimer_clk_unprepare,
+	.enable = dmtimer_clk_enable,
+	.disable = dmtimer_clk_disable,
+	.is_enabled = dmtimer_clk_is_enabled,
+	.recalc_rate = dmtimer_clk_recalc_rate,
+	.round_rate = dmtimer_clk_round_rate,
+	.set_rate = dmtimer_clk_set_rate,
+	.get_phase = dmtimer_clk_get_phase,
+	.set_phase = dmtimer_clk_set_phase,
+	.get_duty_cycle = dmtimer_clk_get_duty_cycle,
+	.set_duty_cycle = dmtimer_clk_set_duty_cycle,
+};
+
+static int dmtimer_register_clock(struct dmtimer *timer)
+{
+	struct device *dev = &timer->pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct clk_init_data init = { };
+	struct clk_hw *hw;
+	const char *fck;
+	int ret;
+
+	if (!np || !of_find_property(np, "#clock-cells", NULL))
+		return 0;
+
+	hw = &timer->hw;
+	init.name = dev_name(dev);
+	init.ops = &dmtimer_clk_ops;
+	init.flags = CLK_GET_RATE_NOCACHE;
+	init.num_parents = 1;
+	fck = __clk_get_name(timer->fclk);
+	init.parent_names = &fck;
+	hw->init = &init;
+	ret = devm_clk_hw_register(dev, hw);
+	if (ret)
+		return ret;
+
+	return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw);
+}
+
+#else
+static inline int dmtimer_register_clock(struct dmtimer *timer)
+{
+	return 0;
+}
+#endif	/* CONFIG_COMMON_CLK */
+
 /*
  * Functions exposed to PWM and remoteproc drivers via platform_data.
  * Do not use these in the driver, these will get deprecated and will
@@ -885,6 +1180,7 @@ static int omap_dm_timer_free(struct omap_dm_timer *cookie)
 	/* Clear timer configuration */
 	spin_lock_irqsave(&timer->lock, flags);
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, 0);
+	dmtimer_write(timer, OMAP_TIMER_LOAD_REG, timer->load_val);
 	spin_unlock_irqrestore(&timer->lock, flags);
 
 	pm_runtime_put_sync(dev);
@@ -967,7 +1263,6 @@ static int omap_dm_timer_start(struct omap_dm_timer *cookie)
 	unsigned long flags;
 	struct device *dev;
 	int rc;
-	u32 l;
 
 	timer = to_dmtimer(cookie);
 	if (unlikely(!timer))
@@ -980,11 +1275,7 @@ static int omap_dm_timer_start(struct omap_dm_timer *cookie)
 		return rc;
 
 	spin_lock_irqsave(&timer->lock, flags);
-	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
-	if (!(l & OMAP_TIMER_CTRL_ST)) {
-		l |= OMAP_TIMER_CTRL_ST;
-		dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
-	}
+	__omap_dm_timer_start(timer, false);
 	spin_unlock_irqrestore(&timer->lock, flags);
 
 	return 0;
@@ -1092,12 +1383,14 @@ static int omap_dm_timer_set_pwm(struct omap_dm_timer *cookie, int def_on,
 	spin_lock_irqsave(&timer->lock, flags);
 	l = dmtimer_read(timer, OMAP_TIMER_CTRL_REG);
 	l &= ~(OMAP_TIMER_CTRL_GPOCFG | OMAP_TIMER_CTRL_SCPWM |
-	       OMAP_TIMER_CTRL_PT | (0x03 << 10) | OMAP_TIMER_CTRL_AR);
+	       OMAP_TIMER_CTRL_PT |
+	       (OMAP_TIMER_CTRL_TRIGGER_MASK << OMAP_TIMER_CTRL_TRIGGER_SHIFT) |
+	       OMAP_TIMER_CTRL_AR);
 	if (def_on)
 		l |= OMAP_TIMER_CTRL_SCPWM;
 	if (toggle)
 		l |= OMAP_TIMER_CTRL_PT;
-	l |= trigger << 10;
+	l |= trigger << OMAP_TIMER_CTRL_TRIGGER_SHIFT;
 	if (autoreload)
 		l |= OMAP_TIMER_CTRL_AR;
 	dmtimer_write(timer, OMAP_TIMER_CTRL_REG, l);
@@ -1399,6 +1692,12 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
 		/* Clear timer configuration */
 		dmtimer_write(timer, OMAP_TIMER_CTRL_REG, 0);
 
+		ret = dmtimer_register_clock(timer);
+		if (ret) {
+			dev_err(dev, "clock provider register failed: %i\n", ret);
+			goto err_put;
+		}
+
 		ret = dmtimer_register_irqchip(timer);
 		if (ret) {
 			dev_err(dev, "irqchip register failed: %i\n", ret);
diff --git a/include/clocksource/timer-ti-dm.h b/include/clocksource/timer-ti-dm.h
--- a/include/clocksource/timer-ti-dm.h
+++ b/include/clocksource/timer-ti-dm.h
@@ -98,6 +98,8 @@ u32 omap_dm_timer_modify_idlect_mask(u32 inputmask);
 #define		OMAP_TIMER_CTRL_GPOCFG		(1 << 14)
 #define		OMAP_TIMER_CTRL_CAPTMODE	(1 << 13)
 #define		OMAP_TIMER_CTRL_PT		(1 << 12)
+#define		OMAP_TIMER_CTRL_TRIGGER_SHIFT	10
+#define		OMAP_TIMER_CTRL_TRIGGER_MASK	3
 #define		OMAP_TIMER_CTRL_TCM_LOWTOHIGH	(0x1 << 8)
 #define		OMAP_TIMER_CTRL_TCM_HIGHTOLOW	(0x2 << 8)
 #define		OMAP_TIMER_CTRL_TCM_BOTHEDGES	(0x3 << 8)
-- 
2.37.3

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH 0/3] Make TI dmtimer features more usable
  2022-10-31 11:56 ` Tony Lindgren
@ 2022-10-31 12:07   ` Tony Lindgren
  -1 siblings, 0 replies; 10+ messages in thread
From: Tony Lindgren @ 2022-10-31 12:07 UTC (permalink / raw)
  To: Daniel Lezcano, Thomas Gleixner
  Cc: Georgi Vlaev, Grygorii Strashko, Keerthy, Ladislav Michl,
	Nishanth Menon, Suman Anna, Vignesh Raghavendra, linux-kernel,
	linux-omap, linux-arm-kernel

* Tony Lindgren <tony@atomide.com> [221031 11:46]:
> I have only lightly tested this so far to make sure I get clock output on
> k3-am625-sk on TIMER_IO2 at user expansion connector pin 10.

For reference, I used something like the patch below to configure the timer2
for clock output.

Regards,

Tony

8< ---------------------
diff --git a/arch/arm64/boot/dts/ti/k3-am625-sk.dts b/arch/arm64/boot/dts/ti/k3-am625-sk.dts
--- a/arch/arm64/boot/dts/ti/k3-am625-sk.dts
+++ b/arch/arm64/boot/dts/ti/k3-am625-sk.dts
@@ -143,6 +143,25 @@ led-0 {
 			default-state = "off";
 		};
 	};
+
+	main_pwm2: dmtimer-main-pwm-2 {
+		pinctrl-0 = <&main_timer2_pins_default>;
+		pinctrl-names = "default";
+		compatible = "ti,omap-dmtimer-pwm";
+		#pwm-cells = <3>;
+		ti,timers = <&main_timer2>;
+		interrupts-extended = <&main_timer2 2 IRQ_TYPE_EDGE_RISING>,
+				      <&main_timer2 1 IRQ_TYPE_EDGE_RISING>,
+				      <&main_timer2 0 IRQ_TYPE_EDGE_RISING>;
+		interrupt-names = "compare", "overflow", "match";
+		clocks = <&main_timer2>;
+	};
+};
+
+&main_timer2 {
+	interrupt-controller;
+	#interrupt-cells = <2>;
+	#clock-cells = <0>;
 };
 
 &main_pmx0 {
@@ -270,6 +289,13 @@ AM62X_IOPAD(0x07c, PIN_OUTPUT, 7) /* (P25) GPMC0_CLK.GPIO0_31 */
 		>;
 	};
 
+	main_timer2_pins_default: main-timer2-pins-default {
+		pinctrl-single,pins = <
+			/* (C15) PADCFG_CTRL_PADCONFIG118 0x000f41d8 TIMER_IO2 */
+			AM65X_IOPAD(0x1d8, PIN_OUTPUT, 2)
+		>;
+	};
+
 	main_gpio1_ioexp_intr_pins_default: main-gpio1-ioexp-intr-pins-default {
 		pinctrl-single,pins = <
 			AM62X_IOPAD(0x01d4, PIN_INPUT, 7) /* (B15) UART0_RTSn.GPIO1_23 */

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH 0/3] Make TI dmtimer features more usable
@ 2022-10-31 12:07   ` Tony Lindgren
  0 siblings, 0 replies; 10+ messages in thread
From: Tony Lindgren @ 2022-10-31 12:07 UTC (permalink / raw)
  To: Daniel Lezcano, Thomas Gleixner
  Cc: Georgi Vlaev, Grygorii Strashko, Keerthy, Ladislav Michl,
	Nishanth Menon, Suman Anna, Vignesh Raghavendra, linux-kernel,
	linux-omap, linux-arm-kernel

* Tony Lindgren <tony@atomide.com> [221031 11:46]:
> I have only lightly tested this so far to make sure I get clock output on
> k3-am625-sk on TIMER_IO2 at user expansion connector pin 10.

For reference, I used something like the patch below to configure the timer2
for clock output.

Regards,

Tony

8< ---------------------
diff --git a/arch/arm64/boot/dts/ti/k3-am625-sk.dts b/arch/arm64/boot/dts/ti/k3-am625-sk.dts
--- a/arch/arm64/boot/dts/ti/k3-am625-sk.dts
+++ b/arch/arm64/boot/dts/ti/k3-am625-sk.dts
@@ -143,6 +143,25 @@ led-0 {
 			default-state = "off";
 		};
 	};
+
+	main_pwm2: dmtimer-main-pwm-2 {
+		pinctrl-0 = <&main_timer2_pins_default>;
+		pinctrl-names = "default";
+		compatible = "ti,omap-dmtimer-pwm";
+		#pwm-cells = <3>;
+		ti,timers = <&main_timer2>;
+		interrupts-extended = <&main_timer2 2 IRQ_TYPE_EDGE_RISING>,
+				      <&main_timer2 1 IRQ_TYPE_EDGE_RISING>,
+				      <&main_timer2 0 IRQ_TYPE_EDGE_RISING>;
+		interrupt-names = "compare", "overflow", "match";
+		clocks = <&main_timer2>;
+	};
+};
+
+&main_timer2 {
+	interrupt-controller;
+	#interrupt-cells = <2>;
+	#clock-cells = <0>;
 };
 
 &main_pmx0 {
@@ -270,6 +289,13 @@ AM62X_IOPAD(0x07c, PIN_OUTPUT, 7) /* (P25) GPMC0_CLK.GPIO0_31 */
 		>;
 	};
 
+	main_timer2_pins_default: main-timer2-pins-default {
+		pinctrl-single,pins = <
+			/* (C15) PADCFG_CTRL_PADCONFIG118 0x000f41d8 TIMER_IO2 */
+			AM65X_IOPAD(0x1d8, PIN_OUTPUT, 2)
+		>;
+	};
+
 	main_gpio1_ioexp_intr_pins_default: main-gpio1-ioexp-intr-pins-default {
 		pinctrl-single,pins = <
 			AM62X_IOPAD(0x01d4, PIN_INPUT, 7) /* (B15) UART0_RTSn.GPIO1_23 */

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2022-10-31 12:08 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-10-31 11:56 [RFC PATCH 0/3] Make TI dmtimer features more usable Tony Lindgren
2022-10-31 11:56 ` Tony Lindgren
2022-10-31 11:56 ` [RFC PATCH 1/3] clocksource/drivers/timer-ti-dm: Add lock for register access Tony Lindgren
2022-10-31 11:56   ` Tony Lindgren
2022-10-31 11:56 ` [RFC PATCH 2/3] clocksource/drivers/timer-ti-dm: Implement chained irq Tony Lindgren
2022-10-31 11:56   ` Tony Lindgren
2022-10-31 11:56 ` [RFC PATCH 3/3] clocksource/drivers/timer-ti-dm: Add clock provider support Tony Lindgren
2022-10-31 11:56   ` Tony Lindgren
2022-10-31 12:07 ` [RFC PATCH 0/3] Make TI dmtimer features more usable Tony Lindgren
2022-10-31 12:07   ` Tony Lindgren

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.