162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2016 Socionext Inc.
462306a36Sopenharmony_ci *   Author: Masahiro Yamada <yamada.masahiro@socionext.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/clk-provider.h>
862306a36Sopenharmony_ci#include <linux/device.h>
962306a36Sopenharmony_ci#include <linux/regmap.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include "clk-uniphier.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define UNIPHIER_CLK_CPUGEAR_STAT	0	/* status */
1462306a36Sopenharmony_ci#define UNIPHIER_CLK_CPUGEAR_SET	4	/* set */
1562306a36Sopenharmony_ci#define UNIPHIER_CLK_CPUGEAR_UPD	8	/* update */
1662306a36Sopenharmony_ci#define   UNIPHIER_CLK_CPUGEAR_UPD_BIT	BIT(0)
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistruct uniphier_clk_cpugear {
1962306a36Sopenharmony_ci	struct clk_hw hw;
2062306a36Sopenharmony_ci	struct regmap *regmap;
2162306a36Sopenharmony_ci	unsigned int regbase;
2262306a36Sopenharmony_ci	unsigned int mask;
2362306a36Sopenharmony_ci};
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define to_uniphier_clk_cpugear(_hw) \
2662306a36Sopenharmony_ci			container_of(_hw, struct uniphier_clk_cpugear, hw)
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic int uniphier_clk_cpugear_set_parent(struct clk_hw *hw, u8 index)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	struct uniphier_clk_cpugear *gear = to_uniphier_clk_cpugear(hw);
3162306a36Sopenharmony_ci	int ret;
3262306a36Sopenharmony_ci	unsigned int val;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	ret = regmap_write_bits(gear->regmap,
3562306a36Sopenharmony_ci				gear->regbase + UNIPHIER_CLK_CPUGEAR_SET,
3662306a36Sopenharmony_ci				gear->mask, index);
3762306a36Sopenharmony_ci	if (ret)
3862306a36Sopenharmony_ci		return ret;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	ret = regmap_write_bits(gear->regmap,
4162306a36Sopenharmony_ci				gear->regbase + UNIPHIER_CLK_CPUGEAR_UPD,
4262306a36Sopenharmony_ci				UNIPHIER_CLK_CPUGEAR_UPD_BIT,
4362306a36Sopenharmony_ci				UNIPHIER_CLK_CPUGEAR_UPD_BIT);
4462306a36Sopenharmony_ci	if (ret)
4562306a36Sopenharmony_ci		return ret;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	return regmap_read_poll_timeout(gear->regmap,
4862306a36Sopenharmony_ci				gear->regbase + UNIPHIER_CLK_CPUGEAR_UPD,
4962306a36Sopenharmony_ci				val, !(val & UNIPHIER_CLK_CPUGEAR_UPD_BIT),
5062306a36Sopenharmony_ci				0, 1);
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic u8 uniphier_clk_cpugear_get_parent(struct clk_hw *hw)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct uniphier_clk_cpugear *gear = to_uniphier_clk_cpugear(hw);
5662306a36Sopenharmony_ci	int num_parents = clk_hw_get_num_parents(hw);
5762306a36Sopenharmony_ci	int ret;
5862306a36Sopenharmony_ci	unsigned int val;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	ret = regmap_read(gear->regmap,
6162306a36Sopenharmony_ci			  gear->regbase + UNIPHIER_CLK_CPUGEAR_STAT, &val);
6262306a36Sopenharmony_ci	if (ret)
6362306a36Sopenharmony_ci		return ret;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	val &= gear->mask;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	return val < num_parents ? val : -EINVAL;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic const struct clk_ops uniphier_clk_cpugear_ops = {
7162306a36Sopenharmony_ci	.determine_rate = __clk_mux_determine_rate,
7262306a36Sopenharmony_ci	.set_parent = uniphier_clk_cpugear_set_parent,
7362306a36Sopenharmony_ci	.get_parent = uniphier_clk_cpugear_get_parent,
7462306a36Sopenharmony_ci};
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistruct clk_hw *uniphier_clk_register_cpugear(struct device *dev,
7762306a36Sopenharmony_ci					 struct regmap *regmap,
7862306a36Sopenharmony_ci					 const char *name,
7962306a36Sopenharmony_ci				const struct uniphier_clk_cpugear_data *data)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	struct uniphier_clk_cpugear *gear;
8262306a36Sopenharmony_ci	struct clk_init_data init;
8362306a36Sopenharmony_ci	int ret;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	gear = devm_kzalloc(dev, sizeof(*gear), GFP_KERNEL);
8662306a36Sopenharmony_ci	if (!gear)
8762306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	init.name = name;
9062306a36Sopenharmony_ci	init.ops = &uniphier_clk_cpugear_ops;
9162306a36Sopenharmony_ci	init.flags = CLK_SET_RATE_PARENT;
9262306a36Sopenharmony_ci	init.parent_names = data->parent_names;
9362306a36Sopenharmony_ci	init.num_parents = data->num_parents;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	gear->regmap = regmap;
9662306a36Sopenharmony_ci	gear->regbase = data->regbase;
9762306a36Sopenharmony_ci	gear->mask = data->mask;
9862306a36Sopenharmony_ci	gear->hw.init = &init;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	ret = devm_clk_hw_register(dev, &gear->hw);
10162306a36Sopenharmony_ci	if (ret)
10262306a36Sopenharmony_ci		return ERR_PTR(ret);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	return &gear->hw;
10562306a36Sopenharmony_ci}
106