[RFC PATCH 3/3] clocksource/drivers/timer-ti-dm: Add clock provider support
From: Tony Lindgren
Date: Mon Oct 31 2022 - 07:56:42 EST
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@xxxxxxxxxxx>
---
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