162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci */
462306a36Sopenharmony_ci
562306a36Sopenharmony_ci#define pr_fmt(fmt) "imx:clk-gpr-mux: " fmt
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/module.h>
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/clk-provider.h>
1062306a36Sopenharmony_ci#include <linux/errno.h>
1162306a36Sopenharmony_ci#include <linux/export.h>
1262306a36Sopenharmony_ci#include <linux/io.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/regmap.h>
1562306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include "clk.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistruct imx_clk_gpr {
2062306a36Sopenharmony_ci	struct clk_hw hw;
2162306a36Sopenharmony_ci	struct regmap *regmap;
2262306a36Sopenharmony_ci	u32 mask;
2362306a36Sopenharmony_ci	u32 reg;
2462306a36Sopenharmony_ci	const u32 *mux_table;
2562306a36Sopenharmony_ci};
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic struct imx_clk_gpr *to_imx_clk_gpr(struct clk_hw *hw)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	return container_of(hw, struct imx_clk_gpr, hw);
3062306a36Sopenharmony_ci}
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic u8 imx_clk_gpr_mux_get_parent(struct clk_hw *hw)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	struct imx_clk_gpr *priv = to_imx_clk_gpr(hw);
3562306a36Sopenharmony_ci	unsigned int val;
3662306a36Sopenharmony_ci	int ret;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	ret = regmap_read(priv->regmap, priv->reg, &val);
3962306a36Sopenharmony_ci	if (ret)
4062306a36Sopenharmony_ci		goto get_parent_err;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	val &= priv->mask;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	ret = clk_mux_val_to_index(hw, priv->mux_table, 0, val);
4562306a36Sopenharmony_ci	if (ret < 0)
4662306a36Sopenharmony_ci		goto get_parent_err;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	return ret;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ciget_parent_err:
5162306a36Sopenharmony_ci	pr_err("%s: failed to get parent (%pe)\n",
5262306a36Sopenharmony_ci	       clk_hw_get_name(hw), ERR_PTR(ret));
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	/* return some realistic non negative value. Potentially we could
5562306a36Sopenharmony_ci	 * give index to some dummy error parent.
5662306a36Sopenharmony_ci	 */
5762306a36Sopenharmony_ci	return 0;
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic int imx_clk_gpr_mux_set_parent(struct clk_hw *hw, u8 index)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct imx_clk_gpr *priv = to_imx_clk_gpr(hw);
6362306a36Sopenharmony_ci	unsigned int val = clk_mux_index_to_val(priv->mux_table, 0, index);
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	return regmap_update_bits(priv->regmap, priv->reg, priv->mask, val);
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic const struct clk_ops imx_clk_gpr_mux_ops = {
6962306a36Sopenharmony_ci	.get_parent = imx_clk_gpr_mux_get_parent,
7062306a36Sopenharmony_ci	.set_parent = imx_clk_gpr_mux_set_parent,
7162306a36Sopenharmony_ci	.determine_rate = __clk_mux_determine_rate,
7262306a36Sopenharmony_ci};
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistruct clk_hw *imx_clk_gpr_mux(const char *name, const char *compatible,
7562306a36Sopenharmony_ci			       u32 reg, const char **parent_names,
7662306a36Sopenharmony_ci			       u8 num_parents, const u32 *mux_table, u32 mask)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	struct clk_init_data init  = { };
7962306a36Sopenharmony_ci	struct imx_clk_gpr *priv;
8062306a36Sopenharmony_ci	struct regmap *regmap;
8162306a36Sopenharmony_ci	struct clk_hw *hw;
8262306a36Sopenharmony_ci	int ret;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	regmap = syscon_regmap_lookup_by_compatible(compatible);
8562306a36Sopenharmony_ci	if (IS_ERR(regmap)) {
8662306a36Sopenharmony_ci		pr_err("failed to find %s regmap\n", compatible);
8762306a36Sopenharmony_ci		return ERR_CAST(regmap);
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
9162306a36Sopenharmony_ci	if (!priv)
9262306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	init.name = name;
9562306a36Sopenharmony_ci	init.ops = &imx_clk_gpr_mux_ops;
9662306a36Sopenharmony_ci	init.parent_names = parent_names;
9762306a36Sopenharmony_ci	init.num_parents = num_parents;
9862306a36Sopenharmony_ci	init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	priv->hw.init = &init;
10162306a36Sopenharmony_ci	priv->regmap = regmap;
10262306a36Sopenharmony_ci	priv->mux_table = mux_table;
10362306a36Sopenharmony_ci	priv->reg = reg;
10462306a36Sopenharmony_ci	priv->mask = mask;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	hw = &priv->hw;
10762306a36Sopenharmony_ci	ret = clk_hw_register(NULL, &priv->hw);
10862306a36Sopenharmony_ci	if (ret) {
10962306a36Sopenharmony_ci		kfree(priv);
11062306a36Sopenharmony_ci		hw = ERR_PTR(ret);
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	return hw;
11462306a36Sopenharmony_ci}
115