162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2016 Maxime Ripard
462306a36Sopenharmony_ci * Maxime Ripard <maxime.ripard@free-electrons.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/clk.h>
862306a36Sopenharmony_ci#include <linux/clk-provider.h>
962306a36Sopenharmony_ci#include <linux/delay.h>
1062306a36Sopenharmony_ci#include <linux/io.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include "ccu_gate.h"
1362306a36Sopenharmony_ci#include "ccu_mux.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define CCU_MUX_KEY_VALUE		0x16aa0000
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistatic u16 ccu_mux_get_prediv(struct ccu_common *common,
1862306a36Sopenharmony_ci			      struct ccu_mux_internal *cm,
1962306a36Sopenharmony_ci			      int parent_index)
2062306a36Sopenharmony_ci{
2162306a36Sopenharmony_ci	u16 prediv = 1;
2262306a36Sopenharmony_ci	u32 reg;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	if (!((common->features & CCU_FEATURE_FIXED_PREDIV) ||
2562306a36Sopenharmony_ci	      (common->features & CCU_FEATURE_VARIABLE_PREDIV) ||
2662306a36Sopenharmony_ci	      (common->features & CCU_FEATURE_ALL_PREDIV)))
2762306a36Sopenharmony_ci		return 1;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	if (common->features & CCU_FEATURE_ALL_PREDIV)
3062306a36Sopenharmony_ci		return common->prediv;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	reg = readl(common->base + common->reg);
3362306a36Sopenharmony_ci	if (parent_index < 0) {
3462306a36Sopenharmony_ci		parent_index = reg >> cm->shift;
3562306a36Sopenharmony_ci		parent_index &= (1 << cm->width) - 1;
3662306a36Sopenharmony_ci	}
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	if (common->features & CCU_FEATURE_FIXED_PREDIV) {
3962306a36Sopenharmony_ci		int i;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci		for (i = 0; i < cm->n_predivs; i++)
4262306a36Sopenharmony_ci			if (parent_index == cm->fixed_predivs[i].index)
4362306a36Sopenharmony_ci				prediv = cm->fixed_predivs[i].div;
4462306a36Sopenharmony_ci	}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	if (common->features & CCU_FEATURE_VARIABLE_PREDIV) {
4762306a36Sopenharmony_ci		int i;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci		for (i = 0; i < cm->n_var_predivs; i++)
5062306a36Sopenharmony_ci			if (parent_index == cm->var_predivs[i].index) {
5162306a36Sopenharmony_ci				u8 div;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci				div = reg >> cm->var_predivs[i].shift;
5462306a36Sopenharmony_ci				div &= (1 << cm->var_predivs[i].width) - 1;
5562306a36Sopenharmony_ci				prediv = div + 1;
5662306a36Sopenharmony_ci			}
5762306a36Sopenharmony_ci	}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	return prediv;
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ciunsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
6362306a36Sopenharmony_ci					  struct ccu_mux_internal *cm,
6462306a36Sopenharmony_ci					  int parent_index,
6562306a36Sopenharmony_ci					  unsigned long parent_rate)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	return parent_rate / ccu_mux_get_prediv(common, cm, parent_index);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(ccu_mux_helper_apply_prediv, SUNXI_CCU);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic unsigned long ccu_mux_helper_unapply_prediv(struct ccu_common *common,
7262306a36Sopenharmony_ci					    struct ccu_mux_internal *cm,
7362306a36Sopenharmony_ci					    int parent_index,
7462306a36Sopenharmony_ci					    unsigned long parent_rate)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	return parent_rate * ccu_mux_get_prediv(common, cm, parent_index);
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ciint ccu_mux_helper_determine_rate(struct ccu_common *common,
8062306a36Sopenharmony_ci				  struct ccu_mux_internal *cm,
8162306a36Sopenharmony_ci				  struct clk_rate_request *req,
8262306a36Sopenharmony_ci				  unsigned long (*round)(struct ccu_mux_internal *,
8362306a36Sopenharmony_ci							 struct clk_hw *,
8462306a36Sopenharmony_ci							 unsigned long *,
8562306a36Sopenharmony_ci							 unsigned long,
8662306a36Sopenharmony_ci							 void *),
8762306a36Sopenharmony_ci				  void *data)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	unsigned long best_parent_rate = 0, best_rate = 0;
9062306a36Sopenharmony_ci	struct clk_hw *best_parent, *hw = &common->hw;
9162306a36Sopenharmony_ci	unsigned int i;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	if (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT) {
9462306a36Sopenharmony_ci		unsigned long adj_parent_rate;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		best_parent = clk_hw_get_parent(hw);
9762306a36Sopenharmony_ci		best_parent_rate = clk_hw_get_rate(best_parent);
9862306a36Sopenharmony_ci		adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, -1,
9962306a36Sopenharmony_ci							      best_parent_rate);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci		best_rate = round(cm, best_parent, &adj_parent_rate,
10262306a36Sopenharmony_ci				  req->rate, data);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci		/*
10562306a36Sopenharmony_ci		 * adj_parent_rate might have been modified by our clock.
10662306a36Sopenharmony_ci		 * Unapply the pre-divider if there's one, and give
10762306a36Sopenharmony_ci		 * the actual frequency the parent needs to run at.
10862306a36Sopenharmony_ci		 */
10962306a36Sopenharmony_ci		best_parent_rate = ccu_mux_helper_unapply_prediv(common, cm, -1,
11062306a36Sopenharmony_ci								 adj_parent_rate);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci		goto out;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
11662306a36Sopenharmony_ci		unsigned long tmp_rate, parent_rate;
11762306a36Sopenharmony_ci		struct clk_hw *parent;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci		parent = clk_hw_get_parent_by_index(hw, i);
12062306a36Sopenharmony_ci		if (!parent)
12162306a36Sopenharmony_ci			continue;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci		parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
12462306a36Sopenharmony_ci							  clk_hw_get_rate(parent));
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci		tmp_rate = round(cm, parent, &parent_rate, req->rate, data);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci		/*
12962306a36Sopenharmony_ci		 * parent_rate might have been modified by our clock.
13062306a36Sopenharmony_ci		 * Unapply the pre-divider if there's one, and give
13162306a36Sopenharmony_ci		 * the actual frequency the parent needs to run at.
13262306a36Sopenharmony_ci		 */
13362306a36Sopenharmony_ci		parent_rate = ccu_mux_helper_unapply_prediv(common, cm, i,
13462306a36Sopenharmony_ci							    parent_rate);
13562306a36Sopenharmony_ci		if (tmp_rate == req->rate) {
13662306a36Sopenharmony_ci			best_parent = parent;
13762306a36Sopenharmony_ci			best_parent_rate = parent_rate;
13862306a36Sopenharmony_ci			best_rate = tmp_rate;
13962306a36Sopenharmony_ci			goto out;
14062306a36Sopenharmony_ci		}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci		if (ccu_is_better_rate(common, req->rate, tmp_rate, best_rate)) {
14362306a36Sopenharmony_ci			best_rate = tmp_rate;
14462306a36Sopenharmony_ci			best_parent_rate = parent_rate;
14562306a36Sopenharmony_ci			best_parent = parent;
14662306a36Sopenharmony_ci		}
14762306a36Sopenharmony_ci	}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	if (best_rate == 0)
15062306a36Sopenharmony_ci		return -EINVAL;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ciout:
15362306a36Sopenharmony_ci	req->best_parent_hw = best_parent;
15462306a36Sopenharmony_ci	req->best_parent_rate = best_parent_rate;
15562306a36Sopenharmony_ci	req->rate = best_rate;
15662306a36Sopenharmony_ci	return 0;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(ccu_mux_helper_determine_rate, SUNXI_CCU);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ciu8 ccu_mux_helper_get_parent(struct ccu_common *common,
16162306a36Sopenharmony_ci			     struct ccu_mux_internal *cm)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	u32 reg;
16462306a36Sopenharmony_ci	u8 parent;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	reg = readl(common->base + common->reg);
16762306a36Sopenharmony_ci	parent = reg >> cm->shift;
16862306a36Sopenharmony_ci	parent &= (1 << cm->width) - 1;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	if (cm->table) {
17162306a36Sopenharmony_ci		int num_parents = clk_hw_get_num_parents(&common->hw);
17262306a36Sopenharmony_ci		int i;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci		for (i = 0; i < num_parents; i++)
17562306a36Sopenharmony_ci			if (cm->table[i] == parent)
17662306a36Sopenharmony_ci				return i;
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	return parent;
18062306a36Sopenharmony_ci}
18162306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(ccu_mux_helper_get_parent, SUNXI_CCU);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ciint ccu_mux_helper_set_parent(struct ccu_common *common,
18462306a36Sopenharmony_ci			      struct ccu_mux_internal *cm,
18562306a36Sopenharmony_ci			      u8 index)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	unsigned long flags;
18862306a36Sopenharmony_ci	u32 reg;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	if (cm->table)
19162306a36Sopenharmony_ci		index = cm->table[index];
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	spin_lock_irqsave(common->lock, flags);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	reg = readl(common->base + common->reg);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	/* The key field always reads as zero. */
19862306a36Sopenharmony_ci	if (common->features & CCU_FEATURE_KEY_FIELD)
19962306a36Sopenharmony_ci		reg |= CCU_MUX_KEY_VALUE;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	reg &= ~GENMASK(cm->width + cm->shift - 1, cm->shift);
20262306a36Sopenharmony_ci	writel(reg | (index << cm->shift), common->base + common->reg);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	spin_unlock_irqrestore(common->lock, flags);
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	return 0;
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(ccu_mux_helper_set_parent, SUNXI_CCU);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic void ccu_mux_disable(struct clk_hw *hw)
21162306a36Sopenharmony_ci{
21262306a36Sopenharmony_ci	struct ccu_mux *cm = hw_to_ccu_mux(hw);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	return ccu_gate_helper_disable(&cm->common, cm->enable);
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic int ccu_mux_enable(struct clk_hw *hw)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	struct ccu_mux *cm = hw_to_ccu_mux(hw);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	return ccu_gate_helper_enable(&cm->common, cm->enable);
22262306a36Sopenharmony_ci}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic int ccu_mux_is_enabled(struct clk_hw *hw)
22562306a36Sopenharmony_ci{
22662306a36Sopenharmony_ci	struct ccu_mux *cm = hw_to_ccu_mux(hw);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	return ccu_gate_helper_is_enabled(&cm->common, cm->enable);
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic u8 ccu_mux_get_parent(struct clk_hw *hw)
23262306a36Sopenharmony_ci{
23362306a36Sopenharmony_ci	struct ccu_mux *cm = hw_to_ccu_mux(hw);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	return ccu_mux_helper_get_parent(&cm->common, &cm->mux);
23662306a36Sopenharmony_ci}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	struct ccu_mux *cm = hw_to_ccu_mux(hw);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	return ccu_mux_helper_set_parent(&cm->common, &cm->mux, index);
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cistatic int ccu_mux_determine_rate(struct clk_hw *hw,
24662306a36Sopenharmony_ci				  struct clk_rate_request *req)
24762306a36Sopenharmony_ci{
24862306a36Sopenharmony_ci	struct ccu_mux *cm = hw_to_ccu_mux(hw);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	if (cm->common.features & CCU_FEATURE_CLOSEST_RATE)
25162306a36Sopenharmony_ci		return clk_mux_determine_rate_flags(hw, req, CLK_MUX_ROUND_CLOSEST);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	return clk_mux_determine_rate_flags(hw, req, 0);
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cistatic unsigned long ccu_mux_recalc_rate(struct clk_hw *hw,
25762306a36Sopenharmony_ci					 unsigned long parent_rate)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	struct ccu_mux *cm = hw_to_ccu_mux(hw);
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	return ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
26262306a36Sopenharmony_ci					   parent_rate);
26362306a36Sopenharmony_ci}
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ciconst struct clk_ops ccu_mux_ops = {
26662306a36Sopenharmony_ci	.disable	= ccu_mux_disable,
26762306a36Sopenharmony_ci	.enable		= ccu_mux_enable,
26862306a36Sopenharmony_ci	.is_enabled	= ccu_mux_is_enabled,
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	.get_parent	= ccu_mux_get_parent,
27162306a36Sopenharmony_ci	.set_parent	= ccu_mux_set_parent,
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	.determine_rate	= ccu_mux_determine_rate,
27462306a36Sopenharmony_ci	.recalc_rate	= ccu_mux_recalc_rate,
27562306a36Sopenharmony_ci};
27662306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(ccu_mux_ops, SUNXI_CCU);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci/*
27962306a36Sopenharmony_ci * This clock notifier is called when the frequency of the of the parent
28062306a36Sopenharmony_ci * PLL clock is to be changed. The idea is to switch the parent to a
28162306a36Sopenharmony_ci * stable clock, such as the main oscillator, while the PLL frequency
28262306a36Sopenharmony_ci * stabilizes.
28362306a36Sopenharmony_ci */
28462306a36Sopenharmony_cistatic int ccu_mux_notifier_cb(struct notifier_block *nb,
28562306a36Sopenharmony_ci			       unsigned long event, void *data)
28662306a36Sopenharmony_ci{
28762306a36Sopenharmony_ci	struct ccu_mux_nb *mux = to_ccu_mux_nb(nb);
28862306a36Sopenharmony_ci	int ret = 0;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	if (event == PRE_RATE_CHANGE) {
29162306a36Sopenharmony_ci		mux->original_index = ccu_mux_helper_get_parent(mux->common,
29262306a36Sopenharmony_ci								mux->cm);
29362306a36Sopenharmony_ci		ret = ccu_mux_helper_set_parent(mux->common, mux->cm,
29462306a36Sopenharmony_ci						mux->bypass_index);
29562306a36Sopenharmony_ci	} else if (event == POST_RATE_CHANGE) {
29662306a36Sopenharmony_ci		ret = ccu_mux_helper_set_parent(mux->common, mux->cm,
29762306a36Sopenharmony_ci						mux->original_index);
29862306a36Sopenharmony_ci	}
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	udelay(mux->delay_us);
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	return notifier_from_errno(ret);
30362306a36Sopenharmony_ci}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ciint ccu_mux_notifier_register(struct clk *clk, struct ccu_mux_nb *mux_nb)
30662306a36Sopenharmony_ci{
30762306a36Sopenharmony_ci	mux_nb->clk_nb.notifier_call = ccu_mux_notifier_cb;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	return clk_notifier_register(clk, &mux_nb->clk_nb);
31062306a36Sopenharmony_ci}
31162306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(ccu_mux_notifier_register, SUNXI_CCU);
312