mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-27 20:07:09 +00:00
clocksource/cadence_ttc: Overhaul clocksource frequency adjustment
The currently used method adjusting the clocksource to a changing input frequency does not work on kernels from 3.11 on. The new approach is to keep the timer frequency as constant as possible. I.e. - due to the TTC's prescaler limitations, allow frequency changes only if the frequency scales by a power of 2 - adjust the counter's divider on the fly when a frequency change occurs This limits cpufreq to scale by certain factors only. But we may keep the time base somewhat constant, so that sleep() & co keep working as expected, while supporting cpufreq. Signed-off-by: Soren Brinkmann <soren.brinkmann@xilinx.com> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org> Acked-by: Michal Simek <michal.simek@xilinx.com>
This commit is contained in:
parent
5f0ba3b462
commit
b3e90722f6
@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/clockchips.h>
|
#include <linux/clockchips.h>
|
||||||
#include <linux/of_address.h>
|
#include <linux/of_address.h>
|
||||||
@ -52,6 +53,8 @@
|
|||||||
#define TTC_CNT_CNTRL_DISABLE_MASK 0x1
|
#define TTC_CNT_CNTRL_DISABLE_MASK 0x1
|
||||||
|
|
||||||
#define TTC_CLK_CNTRL_CSRC_MASK (1 << 5) /* clock source */
|
#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
|
* Setup the timers to use pre-scaling, using a fixed value for now that will
|
||||||
@ -63,6 +66,8 @@
|
|||||||
#define CLK_CNTRL_PRESCALE_EN 1
|
#define CLK_CNTRL_PRESCALE_EN 1
|
||||||
#define CNT_CNTRL_RESET (1 << 4)
|
#define CNT_CNTRL_RESET (1 << 4)
|
||||||
|
|
||||||
|
#define MAX_F_ERR 50
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct ttc_timer - This definition defines local timer structure
|
* struct ttc_timer - This definition defines local timer structure
|
||||||
*
|
*
|
||||||
@ -82,6 +87,8 @@ struct ttc_timer {
|
|||||||
container_of(x, struct ttc_timer, clk_rate_change_nb)
|
container_of(x, struct ttc_timer, clk_rate_change_nb)
|
||||||
|
|
||||||
struct ttc_timer_clocksource {
|
struct ttc_timer_clocksource {
|
||||||
|
u32 scale_clk_ctrl_reg_old;
|
||||||
|
u32 scale_clk_ctrl_reg_new;
|
||||||
struct ttc_timer ttc;
|
struct ttc_timer ttc;
|
||||||
struct clocksource cs;
|
struct clocksource cs;
|
||||||
};
|
};
|
||||||
@ -229,32 +236,89 @@ static int ttc_rate_change_clocksource_cb(struct notifier_block *nb,
|
|||||||
struct ttc_timer_clocksource, ttc);
|
struct ttc_timer_clocksource, ttc);
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case POST_RATE_CHANGE:
|
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
clocksource_unregister(&ttccs->cs);
|
|
||||||
clocksource_register_hz(&ttccs->cs,
|
|
||||||
ndata->new_rate / PRESCALE);
|
|
||||||
/* fall through */
|
|
||||||
case PRE_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);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* store timer clock ctrl register so we can restore it in case
|
||||||
|
* of an abort.
|
||||||
|
*/
|
||||||
|
ttccs->scale_clk_ctrl_reg_old =
|
||||||
|
__raw_readl(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 */
|
||||||
|
__raw_writel(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 */
|
||||||
|
__raw_writel(ttccs->scale_clk_ctrl_reg_new,
|
||||||
|
ttccs->ttc.base_addr + TTC_CLK_CNTRL_OFFSET);
|
||||||
|
break;
|
||||||
|
|
||||||
case ABORT_RATE_CHANGE:
|
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 */
|
||||||
|
__raw_writel(ttccs->scale_clk_ctrl_reg_old,
|
||||||
|
ttccs->ttc.base_addr + TTC_CLK_CNTRL_OFFSET);
|
||||||
|
/* fall through */
|
||||||
default:
|
default:
|
||||||
return NOTIFY_DONE;
|
return NOTIFY_DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return NOTIFY_DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __init ttc_setup_clocksource(struct clk *clk, void __iomem *base)
|
static void __init ttc_setup_clocksource(struct clk *clk, void __iomem *base)
|
||||||
|
Loading…
Reference in New Issue
Block a user