static int ttc_rate_change_clocksource_cb()

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;
}