diff options
Diffstat (limited to 'drivers/clocksource/cadence_ttc_timer.c')
| -rw-r--r-- | drivers/clocksource/cadence_ttc_timer.c | 181 | 
1 files changed, 118 insertions, 63 deletions
diff --git a/drivers/clocksource/cadence_ttc_timer.c b/drivers/clocksource/cadence_ttc_timer.c index b2bb3a4bc20..7a08811df9a 100644 --- a/drivers/clocksource/cadence_ttc_timer.c +++ b/drivers/clocksource/cadence_ttc_timer.c @@ -16,6 +16,7 @@   */  #include <linux/clk.h> +#include <linux/clk-provider.h>  #include <linux/interrupt.h>  #include <linux/clockchips.h>  #include <linux/of_address.h> @@ -52,6 +53,8 @@  #define TTC_CNT_CNTRL_DISABLE_MASK	0x1  #define TTC_CLK_CNTRL_CSRC_MASK		(1 << 5)	/* clock source */ +#define TTC_CLK_CNTRL_PSV_MASK		0x1e +#define TTC_CLK_CNTRL_PSV_SHIFT		1  /*   * Setup the timers to use pre-scaling, using a fixed value for now that will @@ -63,15 +66,19 @@  #define CLK_CNTRL_PRESCALE_EN	1  #define CNT_CNTRL_RESET		(1 << 4) +#define MAX_F_ERR 50 +  /**   * struct ttc_timer - This definition defines local timer structure   *   * @base_addr:	Base address of timer + * @freq:	Timer input clock frequency   * @clk:	Associated clock source   * @clk_rate_change_nb	Notifier block for clock rate changes   */  struct ttc_timer {  	void __iomem *base_addr; +	unsigned long freq;  	struct clk *clk;  	struct notifier_block clk_rate_change_nb;  }; @@ -80,6 +87,8 @@ struct ttc_timer {  		container_of(x, struct ttc_timer, clk_rate_change_nb)  struct ttc_timer_clocksource { +	u32			scale_clk_ctrl_reg_old; +	u32			scale_clk_ctrl_reg_new;  	struct ttc_timer	ttc;  	struct clocksource	cs;  }; @@ -109,11 +118,11 @@ static void ttc_set_interval(struct ttc_timer *timer,  	u32 ctrl_reg;  	/* Disable the counter, set the counter value  and re-enable counter */ -	ctrl_reg = __raw_readl(timer->base_addr + TTC_CNT_CNTRL_OFFSET); +	ctrl_reg = readl_relaxed(timer->base_addr + TTC_CNT_CNTRL_OFFSET);  	ctrl_reg |= TTC_CNT_CNTRL_DISABLE_MASK; -	__raw_writel(ctrl_reg, timer->base_addr + TTC_CNT_CNTRL_OFFSET); +	writel_relaxed(ctrl_reg, timer->base_addr + TTC_CNT_CNTRL_OFFSET); -	__raw_writel(cycles, timer->base_addr + TTC_INTR_VAL_OFFSET); +	writel_relaxed(cycles, timer->base_addr + TTC_INTR_VAL_OFFSET);  	/*  	 * Reset the counter (0x10) so that it starts from 0, one-shot @@ -121,7 +130,7 @@ static void ttc_set_interval(struct ttc_timer *timer,  	 */  	ctrl_reg |= CNT_CNTRL_RESET;  	ctrl_reg &= ~TTC_CNT_CNTRL_DISABLE_MASK; -	__raw_writel(ctrl_reg, timer->base_addr + TTC_CNT_CNTRL_OFFSET); +	writel_relaxed(ctrl_reg, timer->base_addr + TTC_CNT_CNTRL_OFFSET);  }  /** @@ -138,7 +147,7 @@ static irqreturn_t ttc_clock_event_interrupt(int irq, void *dev_id)  	struct ttc_timer *timer = &ttce->ttc;  	/* Acknowledge the interrupt and call event handler */ -	__raw_readl(timer->base_addr + TTC_ISR_OFFSET); +	readl_relaxed(timer->base_addr + TTC_ISR_OFFSET);  	ttce->ce.event_handler(&ttce->ce); @@ -154,13 +163,13 @@ static cycle_t __ttc_clocksource_read(struct clocksource *cs)  {  	struct ttc_timer *timer = &to_ttc_timer_clksrc(cs)->ttc; -	return (cycle_t)__raw_readl(timer->base_addr + +	return (cycle_t)readl_relaxed(timer->base_addr +  				TTC_COUNT_VAL_OFFSET);  } -static u32 notrace ttc_sched_clock_read(void) +static u64 notrace ttc_sched_clock_read(void)  { -	return __raw_readl(ttc_sched_clock_val_reg); +	return readl_relaxed(ttc_sched_clock_val_reg);  }  /** @@ -196,24 +205,23 @@ static void ttc_set_mode(enum clock_event_mode mode,  	switch (mode) {  	case CLOCK_EVT_MODE_PERIODIC: -		ttc_set_interval(timer, -				DIV_ROUND_CLOSEST(clk_get_rate(ttce->ttc.clk), -					PRESCALE * HZ)); +		ttc_set_interval(timer, DIV_ROUND_CLOSEST(ttce->ttc.freq, +						PRESCALE * HZ));  		break;  	case CLOCK_EVT_MODE_ONESHOT:  	case CLOCK_EVT_MODE_UNUSED:  	case CLOCK_EVT_MODE_SHUTDOWN: -		ctrl_reg = __raw_readl(timer->base_addr + +		ctrl_reg = readl_relaxed(timer->base_addr +  					TTC_CNT_CNTRL_OFFSET);  		ctrl_reg |= TTC_CNT_CNTRL_DISABLE_MASK; -		__raw_writel(ctrl_reg, +		writel_relaxed(ctrl_reg,  				timer->base_addr + TTC_CNT_CNTRL_OFFSET);  		break;  	case CLOCK_EVT_MODE_RESUME: -		ctrl_reg = __raw_readl(timer->base_addr + +		ctrl_reg = readl_relaxed(timer->base_addr +  					TTC_CNT_CNTRL_OFFSET);  		ctrl_reg &= ~TTC_CNT_CNTRL_DISABLE_MASK; -		__raw_writel(ctrl_reg, +		writel_relaxed(ctrl_reg,  				timer->base_addr + TTC_CNT_CNTRL_OFFSET);  		break;  	} @@ -228,32 +236,89 @@ static int ttc_rate_change_clocksource_cb(struct notifier_block *nb,  			struct ttc_timer_clocksource, ttc);  	switch (event) { -	case POST_RATE_CHANGE: +	case PRE_RATE_CHANGE: +	{ +		u32 psv; +		unsigned long factor, rate_low, rate_high; + +		if (ndata->new_rate > ndata->old_rate) { +			factor = DIV_ROUND_CLOSEST(ndata->new_rate, +					ndata->old_rate); +			rate_low = ndata->old_rate; +			rate_high = ndata->new_rate; +		} else { +			factor = DIV_ROUND_CLOSEST(ndata->old_rate, +					ndata->new_rate); +			rate_low = ndata->new_rate; +			rate_high = ndata->old_rate; +		} + +		if (!is_power_of_2(factor)) +				return NOTIFY_BAD; + +		if (abs(rate_high - (factor * rate_low)) > MAX_F_ERR) +			return NOTIFY_BAD; + +		factor = __ilog2_u32(factor); +  		/* -		 * Do whatever is necessary to maintain a proper time base -		 * -		 * I cannot find a way to adjust the currently used clocksource -		 * to the new frequency. __clocksource_updatefreq_hz() sounds -		 * good, but does not work. Not sure what's that missing. -		 * -		 * This approach works, but triggers two clocksource switches. -		 * The first after unregister to clocksource jiffies. And -		 * another one after the register to the newly registered timer. -		 * -		 * Alternatively we could 'waste' another HW timer to ping pong -		 * between clock sources. That would also use one register and -		 * one unregister call, but only trigger one clocksource switch -		 * for the cost of another HW timer used by the OS. +		 * store timer clock ctrl register so we can restore it in case +		 * of an abort.  		 */ -		clocksource_unregister(&ttccs->cs); -		clocksource_register_hz(&ttccs->cs, -				ndata->new_rate / PRESCALE); -		/* fall through */ -	case PRE_RATE_CHANGE: +		ttccs->scale_clk_ctrl_reg_old = +			readl_relaxed(ttccs->ttc.base_addr + +			TTC_CLK_CNTRL_OFFSET); + +		psv = (ttccs->scale_clk_ctrl_reg_old & +				TTC_CLK_CNTRL_PSV_MASK) >> +				TTC_CLK_CNTRL_PSV_SHIFT; +		if (ndata->new_rate < ndata->old_rate) +			psv -= factor; +		else +			psv += factor; + +		/* prescaler within legal range? */ +		if (psv & ~(TTC_CLK_CNTRL_PSV_MASK >> TTC_CLK_CNTRL_PSV_SHIFT)) +			return NOTIFY_BAD; + +		ttccs->scale_clk_ctrl_reg_new = ttccs->scale_clk_ctrl_reg_old & +			~TTC_CLK_CNTRL_PSV_MASK; +		ttccs->scale_clk_ctrl_reg_new |= psv << TTC_CLK_CNTRL_PSV_SHIFT; + + +		/* scale down: adjust divider in post-change notification */ +		if (ndata->new_rate < ndata->old_rate) +			return NOTIFY_DONE; + +		/* scale up: adjust divider now - before frequency change */ +		writel_relaxed(ttccs->scale_clk_ctrl_reg_new, +			       ttccs->ttc.base_addr + TTC_CLK_CNTRL_OFFSET); +		break; +	} +	case POST_RATE_CHANGE: +		/* scale up: pre-change notification did the adjustment */ +		if (ndata->new_rate > ndata->old_rate) +			return NOTIFY_OK; + +		/* scale down: adjust divider now - after frequency change */ +		writel_relaxed(ttccs->scale_clk_ctrl_reg_new, +			       ttccs->ttc.base_addr + TTC_CLK_CNTRL_OFFSET); +		break; +  	case ABORT_RATE_CHANGE: +		/* we have to undo the adjustment in case we scale up */ +		if (ndata->new_rate < ndata->old_rate) +			return NOTIFY_OK; + +		/* restore original register value */ +		writel_relaxed(ttccs->scale_clk_ctrl_reg_old, +			       ttccs->ttc.base_addr + TTC_CLK_CNTRL_OFFSET); +		/* fall through */  	default:  		return NOTIFY_DONE;  	} + +	return NOTIFY_DONE;  }  static void __init ttc_setup_clocksource(struct clk *clk, void __iomem *base) @@ -273,6 +338,8 @@ static void __init ttc_setup_clocksource(struct clk *clk, void __iomem *base)  		return;  	} +	ttccs->ttc.freq = clk_get_rate(ttccs->ttc.clk); +  	ttccs->ttc.clk_rate_change_nb.notifier_call =  		ttc_rate_change_clocksource_cb;  	ttccs->ttc.clk_rate_change_nb.next = NULL; @@ -292,22 +359,20 @@ static void __init ttc_setup_clocksource(struct clk *clk, void __iomem *base)  	 * with no interrupt and it rolls over at 0xFFFF. Pre-scale  	 * it by 32 also. Let it start running now.  	 */ -	__raw_writel(0x0,  ttccs->ttc.base_addr + TTC_IER_OFFSET); -	__raw_writel(CLK_CNTRL_PRESCALE | CLK_CNTRL_PRESCALE_EN, +	writel_relaxed(0x0,  ttccs->ttc.base_addr + TTC_IER_OFFSET); +	writel_relaxed(CLK_CNTRL_PRESCALE | CLK_CNTRL_PRESCALE_EN,  		     ttccs->ttc.base_addr + TTC_CLK_CNTRL_OFFSET); -	__raw_writel(CNT_CNTRL_RESET, +	writel_relaxed(CNT_CNTRL_RESET,  		     ttccs->ttc.base_addr + TTC_CNT_CNTRL_OFFSET); -	err = clocksource_register_hz(&ttccs->cs, -			clk_get_rate(ttccs->ttc.clk) / PRESCALE); +	err = clocksource_register_hz(&ttccs->cs, ttccs->ttc.freq / PRESCALE);  	if (WARN_ON(err)) {  		kfree(ttccs);  		return;  	}  	ttc_sched_clock_val_reg = base + TTC_COUNT_VAL_OFFSET; -	setup_sched_clock(ttc_sched_clock_read, 16, -			clk_get_rate(ttccs->ttc.clk) / PRESCALE); +	sched_clock_register(ttc_sched_clock_read, 16, ttccs->ttc.freq / PRESCALE);  }  static int ttc_rate_change_clockevent_cb(struct notifier_block *nb, @@ -320,22 +385,12 @@ static int ttc_rate_change_clockevent_cb(struct notifier_block *nb,  	switch (event) {  	case POST_RATE_CHANGE: -	{ -		unsigned long flags; +		/* update cached frequency */ +		ttc->freq = ndata->new_rate; -		/* -		 * clockevents_update_freq should be called with IRQ disabled on -		 * the CPU the timer provides events for. The timer we use is -		 * common to both CPUs, not sure if we need to run on both -		 * cores. -		 */ -		local_irq_save(flags); -		clockevents_update_freq(&ttcce->ce, -				ndata->new_rate / PRESCALE); -		local_irq_restore(flags); +		clockevents_update_freq(&ttcce->ce, ndata->new_rate / PRESCALE);  		/* fall through */ -	}  	case PRE_RATE_CHANGE:  	case ABORT_RATE_CHANGE:  	default: @@ -367,6 +422,7 @@ static void __init ttc_setup_clockevent(struct clk *clk,  	if (clk_notifier_register(ttcce->ttc.clk,  				&ttcce->ttc.clk_rate_change_nb))  		pr_warn("Unable to register clock notifier.\n"); +	ttcce->ttc.freq = clk_get_rate(ttcce->ttc.clk);  	ttcce->ttc.base_addr = base;  	ttcce->ce.name = "ttc_clockevent"; @@ -382,21 +438,20 @@ static void __init ttc_setup_clockevent(struct clk *clk,  	 * is prescaled by 32 using the interval interrupt. Leave it  	 * disabled for now.  	 */ -	__raw_writel(0x23, ttcce->ttc.base_addr + TTC_CNT_CNTRL_OFFSET); -	__raw_writel(CLK_CNTRL_PRESCALE | CLK_CNTRL_PRESCALE_EN, +	writel_relaxed(0x23, ttcce->ttc.base_addr + TTC_CNT_CNTRL_OFFSET); +	writel_relaxed(CLK_CNTRL_PRESCALE | CLK_CNTRL_PRESCALE_EN,  		     ttcce->ttc.base_addr + TTC_CLK_CNTRL_OFFSET); -	__raw_writel(0x1,  ttcce->ttc.base_addr + TTC_IER_OFFSET); +	writel_relaxed(0x1,  ttcce->ttc.base_addr + TTC_IER_OFFSET);  	err = request_irq(irq, ttc_clock_event_interrupt, -			  IRQF_DISABLED | IRQF_TIMER, -			  ttcce->ce.name, ttcce); +			  IRQF_TIMER, ttcce->ce.name, ttcce);  	if (WARN_ON(err)) {  		kfree(ttcce);  		return;  	}  	clockevents_config_and_register(&ttcce->ce, -			clk_get_rate(ttcce->ttc.clk) / PRESCALE, 1, 0xfffe); +			ttcce->ttc.freq / PRESCALE, 1, 0xfffe);  }  /** @@ -435,7 +490,7 @@ static void __init ttc_timer_init(struct device_node *timer)  		BUG();  	} -	clksel = __raw_readl(timer_baseaddr + TTC_CLK_CNTRL_OFFSET); +	clksel = readl_relaxed(timer_baseaddr + TTC_CLK_CNTRL_OFFSET);  	clksel = !!(clksel & TTC_CLK_CNTRL_CSRC_MASK);  	clk_cs = of_clk_get(timer, clksel);  	if (IS_ERR(clk_cs)) { @@ -443,7 +498,7 @@ static void __init ttc_timer_init(struct device_node *timer)  		BUG();  	} -	clksel = __raw_readl(timer_baseaddr + 4 + TTC_CLK_CNTRL_OFFSET); +	clksel = readl_relaxed(timer_baseaddr + 4 + TTC_CLK_CNTRL_OFFSET);  	clksel = !!(clksel & TTC_CLK_CNTRL_CSRC_MASK);  	clk_ce = of_clk_get(timer, clksel);  	if (IS_ERR(clk_ce)) {  | 
