162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2016 Free Electrons
462306a36Sopenharmony_ci * Copyright (C) 2016 NextThing Co
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Maxime Ripard <maxime.ripard@free-electrons.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/clk-provider.h>
1062306a36Sopenharmony_ci#include <linux/regmap.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include "sun4i_tcon.h"
1362306a36Sopenharmony_ci#include "sun4i_tcon_dclk.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistruct sun4i_dclk {
1662306a36Sopenharmony_ci	struct clk_hw		hw;
1762306a36Sopenharmony_ci	struct regmap		*regmap;
1862306a36Sopenharmony_ci	struct sun4i_tcon	*tcon;
1962306a36Sopenharmony_ci};
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	return container_of(hw, struct sun4i_dclk, hw);
2462306a36Sopenharmony_ci}
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic void sun4i_dclk_disable(struct clk_hw *hw)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	struct sun4i_dclk *dclk = hw_to_dclk(hw);
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
3162306a36Sopenharmony_ci			   BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0);
3262306a36Sopenharmony_ci}
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic int sun4i_dclk_enable(struct clk_hw *hw)
3562306a36Sopenharmony_ci{
3662306a36Sopenharmony_ci	struct sun4i_dclk *dclk = hw_to_dclk(hw);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
3962306a36Sopenharmony_ci				  BIT(SUN4I_TCON0_DCLK_GATE_BIT),
4062306a36Sopenharmony_ci				  BIT(SUN4I_TCON0_DCLK_GATE_BIT));
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic int sun4i_dclk_is_enabled(struct clk_hw *hw)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct sun4i_dclk *dclk = hw_to_dclk(hw);
4662306a36Sopenharmony_ci	u32 val;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT);
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw,
5462306a36Sopenharmony_ci					    unsigned long parent_rate)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	struct sun4i_dclk *dclk = hw_to_dclk(hw);
5762306a36Sopenharmony_ci	u32 val;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	val >>= SUN4I_TCON0_DCLK_DIV_SHIFT;
6262306a36Sopenharmony_ci	val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	if (!val)
6562306a36Sopenharmony_ci		val = 1;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	return parent_rate / val;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate,
7162306a36Sopenharmony_ci				  unsigned long *parent_rate)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct sun4i_dclk *dclk = hw_to_dclk(hw);
7462306a36Sopenharmony_ci	struct sun4i_tcon *tcon = dclk->tcon;
7562306a36Sopenharmony_ci	unsigned long best_parent = 0;
7662306a36Sopenharmony_ci	u8 best_div = 1;
7762306a36Sopenharmony_ci	int i;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) {
8062306a36Sopenharmony_ci		u64 ideal = (u64)rate * i;
8162306a36Sopenharmony_ci		unsigned long rounded;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci		/*
8462306a36Sopenharmony_ci		 * ideal has overflowed the max value that can be stored in an
8562306a36Sopenharmony_ci		 * unsigned long, and every clk operation we might do on a
8662306a36Sopenharmony_ci		 * truncated u64 value will give us incorrect results.
8762306a36Sopenharmony_ci		 * Let's just stop there since bigger dividers will result in
8862306a36Sopenharmony_ci		 * the same overflow issue.
8962306a36Sopenharmony_ci		 */
9062306a36Sopenharmony_ci		if (ideal > ULONG_MAX)
9162306a36Sopenharmony_ci			goto out;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci		rounded = clk_hw_round_rate(clk_hw_get_parent(hw),
9462306a36Sopenharmony_ci					    ideal);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		if (rounded == ideal) {
9762306a36Sopenharmony_ci			best_parent = rounded;
9862306a36Sopenharmony_ci			best_div = i;
9962306a36Sopenharmony_ci			goto out;
10062306a36Sopenharmony_ci		}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci		if (abs(rate - rounded / i) <
10362306a36Sopenharmony_ci		    abs(rate - best_parent / best_div)) {
10462306a36Sopenharmony_ci			best_parent = rounded;
10562306a36Sopenharmony_ci			best_div = i;
10662306a36Sopenharmony_ci		}
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ciout:
11062306a36Sopenharmony_ci	*parent_rate = best_parent;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	return best_parent / best_div;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate,
11662306a36Sopenharmony_ci			       unsigned long parent_rate)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	struct sun4i_dclk *dclk = hw_to_dclk(hw);
11962306a36Sopenharmony_ci	u8 div = parent_rate / rate;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
12262306a36Sopenharmony_ci				  GENMASK(6, 0), div);
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic int sun4i_dclk_get_phase(struct clk_hw *hw)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	struct sun4i_dclk *dclk = hw_to_dclk(hw);
12862306a36Sopenharmony_ci	u32 val;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	val >>= 28;
13362306a36Sopenharmony_ci	val &= 3;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	return val * 120;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	struct sun4i_dclk *dclk = hw_to_dclk(hw);
14162306a36Sopenharmony_ci	u32 val = degrees / 120;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	val <<= 28;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG,
14662306a36Sopenharmony_ci			   GENMASK(29, 28),
14762306a36Sopenharmony_ci			   val);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	return 0;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic const struct clk_ops sun4i_dclk_ops = {
15362306a36Sopenharmony_ci	.disable	= sun4i_dclk_disable,
15462306a36Sopenharmony_ci	.enable		= sun4i_dclk_enable,
15562306a36Sopenharmony_ci	.is_enabled	= sun4i_dclk_is_enabled,
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	.recalc_rate	= sun4i_dclk_recalc_rate,
15862306a36Sopenharmony_ci	.round_rate	= sun4i_dclk_round_rate,
15962306a36Sopenharmony_ci	.set_rate	= sun4i_dclk_set_rate,
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	.get_phase	= sun4i_dclk_get_phase,
16262306a36Sopenharmony_ci	.set_phase	= sun4i_dclk_set_phase,
16362306a36Sopenharmony_ci};
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ciint sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon)
16662306a36Sopenharmony_ci{
16762306a36Sopenharmony_ci	const char *clk_name, *parent_name;
16862306a36Sopenharmony_ci	struct clk_init_data init;
16962306a36Sopenharmony_ci	struct sun4i_dclk *dclk;
17062306a36Sopenharmony_ci	int ret;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	parent_name = __clk_get_name(tcon->sclk0);
17362306a36Sopenharmony_ci	ret = of_property_read_string_index(dev->of_node,
17462306a36Sopenharmony_ci					    "clock-output-names", 0,
17562306a36Sopenharmony_ci					    &clk_name);
17662306a36Sopenharmony_ci	if (ret)
17762306a36Sopenharmony_ci		return ret;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL);
18062306a36Sopenharmony_ci	if (!dclk)
18162306a36Sopenharmony_ci		return -ENOMEM;
18262306a36Sopenharmony_ci	dclk->tcon = tcon;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	init.name = clk_name;
18562306a36Sopenharmony_ci	init.ops = &sun4i_dclk_ops;
18662306a36Sopenharmony_ci	init.parent_names = &parent_name;
18762306a36Sopenharmony_ci	init.num_parents = 1;
18862306a36Sopenharmony_ci	init.flags = CLK_SET_RATE_PARENT;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	dclk->regmap = tcon->regs;
19162306a36Sopenharmony_ci	dclk->hw.init = &init;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	tcon->dclk = clk_register(dev, &dclk->hw);
19462306a36Sopenharmony_ci	if (IS_ERR(tcon->dclk))
19562306a36Sopenharmony_ci		return PTR_ERR(tcon->dclk);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	return 0;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ciEXPORT_SYMBOL(sun4i_dclk_create);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ciint sun4i_dclk_free(struct sun4i_tcon *tcon)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	clk_unregister(tcon->dclk);
20462306a36Sopenharmony_ci	return 0;
20562306a36Sopenharmony_ci}
20662306a36Sopenharmony_ciEXPORT_SYMBOL(sun4i_dclk_free);
207