in timer-cadence-ttc.c [226:318]
static int ttc_rate_change_clocksource_cb(struct notifier_block *nb,
unsigned long event, void *data)
{
struct clk_notifier_data *ndata = data;
struct ttc_timer *ttc = to_ttc_timer(nb);
struct ttc_timer_clocksource *ttccs = container_of(ttc,
struct ttc_timer_clocksource, ttc);
switch (event) {
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 =
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);
fallthrough;
default:
return NOTIFY_DONE;
}
return NOTIFY_DONE;
}