From: Tony Lindgren <tony@atomide.com> To: Daniel Lezcano <daniel.lezcano@linaro.org>, Thomas Gleixner <tglx@linutronix.de> Cc: Georgi Vlaev <g-vlaev@ti.com>, Grygorii Strashko <grygorii.strashko@ti.com>, Keerthy <j-keerthy@ti.com>, Ladislav Michl <ladis@linux-mips.org>, Nishanth Menon <nm@ti.com>, Suman Anna <s-anna@ti.com>, Vignesh Raghavendra <vigneshr@ti.com>, linux-kernel@vger.kernel.org, linux-omap@vger.kernel.org, linux-arm-kernel@lists.infradead.org Subject: [RFC PATCH 3/3] clocksource/drivers/timer-ti-dm: Add clock provider support Date: Mon, 31 Oct 2022 13:56:13 +0200 [thread overview] Message-ID: <20221031115613.56229-4-tony@atomide.com> (raw) In-Reply-To: <20221031115613.56229-1-tony@atomide.com> 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
WARNING: multiple messages have this Message-ID (diff)
From: Tony Lindgren <tony@atomide.com> To: Daniel Lezcano <daniel.lezcano@linaro.org>, Thomas Gleixner <tglx@linutronix.de> Cc: Georgi Vlaev <g-vlaev@ti.com>, Grygorii Strashko <grygorii.strashko@ti.com>, Keerthy <j-keerthy@ti.com>, Ladislav Michl <ladis@linux-mips.org>, Nishanth Menon <nm@ti.com>, Suman Anna <s-anna@ti.com>, Vignesh Raghavendra <vigneshr@ti.com>, linux-kernel@vger.kernel.org, linux-omap@vger.kernel.org, linux-arm-kernel@lists.infradead.org Subject: [RFC PATCH 3/3] clocksource/drivers/timer-ti-dm: Add clock provider support Date: Mon, 31 Oct 2022 13:56:13 +0200 [thread overview] Message-ID: <20221031115613.56229-4-tony@atomide.com> (raw) In-Reply-To: <20221031115613.56229-1-tony@atomide.com> 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
next prev parent reply other threads:[~2022-10-31 11:56 UTC|newest] Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top 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 ` Tony Lindgren [this message] 2022-10-31 11:56 ` [RFC PATCH 3/3] clocksource/drivers/timer-ti-dm: Add clock provider support 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
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20221031115613.56229-4-tony@atomide.com \ --to=tony@atomide.com \ --cc=daniel.lezcano@linaro.org \ --cc=g-vlaev@ti.com \ --cc=grygorii.strashko@ti.com \ --cc=j-keerthy@ti.com \ --cc=ladis@linux-mips.org \ --cc=linux-arm-kernel@lists.infradead.org \ --cc=linux-kernel@vger.kernel.org \ --cc=linux-omap@vger.kernel.org \ --cc=nm@ti.com \ --cc=s-anna@ti.com \ --cc=tglx@linutronix.de \ --cc=vigneshr@ti.com \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
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.