162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2015 Linaro Ltd.
462306a36Sopenharmony_ci * Author: Pi-Cheng Chen <pi-cheng.chen@linaro.org>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/clk-provider.h>
862306a36Sopenharmony_ci#include <linux/container_of.h>
962306a36Sopenharmony_ci#include <linux/err.h>
1062306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/regmap.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include "clk-mtk.h"
1662306a36Sopenharmony_ci#include "clk-cpumux.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistruct mtk_clk_cpumux {
1962306a36Sopenharmony_ci	struct clk_hw	hw;
2062306a36Sopenharmony_ci	struct regmap	*regmap;
2162306a36Sopenharmony_ci	u32		reg;
2262306a36Sopenharmony_ci	u32		mask;
2362306a36Sopenharmony_ci	u8		shift;
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic inline struct mtk_clk_cpumux *to_mtk_clk_cpumux(struct clk_hw *_hw)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	return container_of(_hw, struct mtk_clk_cpumux, hw);
2962306a36Sopenharmony_ci}
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic u8 clk_cpumux_get_parent(struct clk_hw *hw)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	struct mtk_clk_cpumux *mux = to_mtk_clk_cpumux(hw);
3462306a36Sopenharmony_ci	unsigned int val;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	regmap_read(mux->regmap, mux->reg, &val);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	val >>= mux->shift;
3962306a36Sopenharmony_ci	val &= mux->mask;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	return val;
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic int clk_cpumux_set_parent(struct clk_hw *hw, u8 index)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	struct mtk_clk_cpumux *mux = to_mtk_clk_cpumux(hw);
4762306a36Sopenharmony_ci	u32 mask, val;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	val = index << mux->shift;
5062306a36Sopenharmony_ci	mask = mux->mask << mux->shift;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	return regmap_update_bits(mux->regmap, mux->reg, mask, val);
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic const struct clk_ops clk_cpumux_ops = {
5662306a36Sopenharmony_ci	.determine_rate = clk_hw_determine_rate_no_reparent,
5762306a36Sopenharmony_ci	.get_parent = clk_cpumux_get_parent,
5862306a36Sopenharmony_ci	.set_parent = clk_cpumux_set_parent,
5962306a36Sopenharmony_ci};
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic struct clk_hw *
6262306a36Sopenharmony_cimtk_clk_register_cpumux(struct device *dev, const struct mtk_composite *mux,
6362306a36Sopenharmony_ci			struct regmap *regmap)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	struct mtk_clk_cpumux *cpumux;
6662306a36Sopenharmony_ci	int ret;
6762306a36Sopenharmony_ci	struct clk_init_data init;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	cpumux = kzalloc(sizeof(*cpumux), GFP_KERNEL);
7062306a36Sopenharmony_ci	if (!cpumux)
7162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	init.name = mux->name;
7462306a36Sopenharmony_ci	init.ops = &clk_cpumux_ops;
7562306a36Sopenharmony_ci	init.parent_names = mux->parent_names;
7662306a36Sopenharmony_ci	init.num_parents = mux->num_parents;
7762306a36Sopenharmony_ci	init.flags = mux->flags;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	cpumux->reg = mux->mux_reg;
8062306a36Sopenharmony_ci	cpumux->shift = mux->mux_shift;
8162306a36Sopenharmony_ci	cpumux->mask = BIT(mux->mux_width) - 1;
8262306a36Sopenharmony_ci	cpumux->regmap = regmap;
8362306a36Sopenharmony_ci	cpumux->hw.init = &init;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	ret = clk_hw_register(dev, &cpumux->hw);
8662306a36Sopenharmony_ci	if (ret) {
8762306a36Sopenharmony_ci		kfree(cpumux);
8862306a36Sopenharmony_ci		return ERR_PTR(ret);
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	return &cpumux->hw;
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic void mtk_clk_unregister_cpumux(struct clk_hw *hw)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	struct mtk_clk_cpumux *cpumux;
9762306a36Sopenharmony_ci	if (!hw)
9862306a36Sopenharmony_ci		return;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	cpumux = to_mtk_clk_cpumux(hw);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	clk_hw_unregister(hw);
10362306a36Sopenharmony_ci	kfree(cpumux);
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ciint mtk_clk_register_cpumuxes(struct device *dev, struct device_node *node,
10762306a36Sopenharmony_ci			      const struct mtk_composite *clks, int num,
10862306a36Sopenharmony_ci			      struct clk_hw_onecell_data *clk_data)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	int i;
11162306a36Sopenharmony_ci	struct clk_hw *hw;
11262306a36Sopenharmony_ci	struct regmap *regmap;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	regmap = device_node_to_regmap(node);
11562306a36Sopenharmony_ci	if (IS_ERR(regmap)) {
11662306a36Sopenharmony_ci		pr_err("Cannot find regmap for %pOF: %pe\n", node, regmap);
11762306a36Sopenharmony_ci		return PTR_ERR(regmap);
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	for (i = 0; i < num; i++) {
12162306a36Sopenharmony_ci		const struct mtk_composite *mux = &clks[i];
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci		if (!IS_ERR_OR_NULL(clk_data->hws[mux->id])) {
12462306a36Sopenharmony_ci			pr_warn("%pOF: Trying to register duplicate clock ID: %d\n",
12562306a36Sopenharmony_ci				node, mux->id);
12662306a36Sopenharmony_ci			continue;
12762306a36Sopenharmony_ci		}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci		hw = mtk_clk_register_cpumux(dev, mux, regmap);
13062306a36Sopenharmony_ci		if (IS_ERR(hw)) {
13162306a36Sopenharmony_ci			pr_err("Failed to register clk %s: %pe\n", mux->name,
13262306a36Sopenharmony_ci			       hw);
13362306a36Sopenharmony_ci			goto err;
13462306a36Sopenharmony_ci		}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci		clk_data->hws[mux->id] = hw;
13762306a36Sopenharmony_ci	}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	return 0;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cierr:
14262306a36Sopenharmony_ci	while (--i >= 0) {
14362306a36Sopenharmony_ci		const struct mtk_composite *mux = &clks[i];
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci		if (IS_ERR_OR_NULL(clk_data->hws[mux->id]))
14662306a36Sopenharmony_ci			continue;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci		mtk_clk_unregister_cpumux(clk_data->hws[mux->id]);
14962306a36Sopenharmony_ci		clk_data->hws[mux->id] = ERR_PTR(-ENOENT);
15062306a36Sopenharmony_ci	}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	return PTR_ERR(hw);
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mtk_clk_register_cpumuxes);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_civoid mtk_clk_unregister_cpumuxes(const struct mtk_composite *clks, int num,
15762306a36Sopenharmony_ci				 struct clk_hw_onecell_data *clk_data)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	int i;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	for (i = num; i > 0; i--) {
16262306a36Sopenharmony_ci		const struct mtk_composite *mux = &clks[i - 1];
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci		if (IS_ERR_OR_NULL(clk_data->hws[mux->id]))
16562306a36Sopenharmony_ci			continue;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci		mtk_clk_unregister_cpumux(clk_data->hws[mux->id]);
16862306a36Sopenharmony_ci		clk_data->hws[mux->id] = ERR_PTR(-ENOENT);
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mtk_clk_unregister_cpumuxes);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
174