162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci// Copyright (C) 2014 Broadcom Corporation
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/kernel.h>
562306a36Sopenharmony_ci#include <linux/err.h>
662306a36Sopenharmony_ci#include <linux/clk-provider.h>
762306a36Sopenharmony_ci#include <linux/io.h>
862306a36Sopenharmony_ci#include <linux/of.h>
962306a36Sopenharmony_ci#include <linux/clkdev.h>
1062306a36Sopenharmony_ci#include <linux/of_address.h>
1162306a36Sopenharmony_ci#include <linux/delay.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "clk-iproc.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistruct iproc_asiu;
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistruct iproc_asiu_clk {
1862306a36Sopenharmony_ci	struct clk_hw hw;
1962306a36Sopenharmony_ci	const char *name;
2062306a36Sopenharmony_ci	struct iproc_asiu *asiu;
2162306a36Sopenharmony_ci	unsigned long rate;
2262306a36Sopenharmony_ci	struct iproc_asiu_div div;
2362306a36Sopenharmony_ci	struct iproc_asiu_gate gate;
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistruct iproc_asiu {
2762306a36Sopenharmony_ci	void __iomem *div_base;
2862306a36Sopenharmony_ci	void __iomem *gate_base;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	struct clk_hw_onecell_data *clk_data;
3162306a36Sopenharmony_ci	struct iproc_asiu_clk *clks;
3262306a36Sopenharmony_ci};
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define to_asiu_clk(hw) container_of(hw, struct iproc_asiu_clk, hw)
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic int iproc_asiu_clk_enable(struct clk_hw *hw)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	struct iproc_asiu_clk *clk = to_asiu_clk(hw);
3962306a36Sopenharmony_ci	struct iproc_asiu *asiu = clk->asiu;
4062306a36Sopenharmony_ci	u32 val;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	/* some clocks at the ASIU level are always enabled */
4362306a36Sopenharmony_ci	if (clk->gate.offset == IPROC_CLK_INVALID_OFFSET)
4462306a36Sopenharmony_ci		return 0;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	val = readl(asiu->gate_base + clk->gate.offset);
4762306a36Sopenharmony_ci	val |= (1 << clk->gate.en_shift);
4862306a36Sopenharmony_ci	writel(val, asiu->gate_base + clk->gate.offset);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	return 0;
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic void iproc_asiu_clk_disable(struct clk_hw *hw)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct iproc_asiu_clk *clk = to_asiu_clk(hw);
5662306a36Sopenharmony_ci	struct iproc_asiu *asiu = clk->asiu;
5762306a36Sopenharmony_ci	u32 val;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	/* some clocks at the ASIU level are always enabled */
6062306a36Sopenharmony_ci	if (clk->gate.offset == IPROC_CLK_INVALID_OFFSET)
6162306a36Sopenharmony_ci		return;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	val = readl(asiu->gate_base + clk->gate.offset);
6462306a36Sopenharmony_ci	val &= ~(1 << clk->gate.en_shift);
6562306a36Sopenharmony_ci	writel(val, asiu->gate_base + clk->gate.offset);
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic unsigned long iproc_asiu_clk_recalc_rate(struct clk_hw *hw,
6962306a36Sopenharmony_ci						unsigned long parent_rate)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	struct iproc_asiu_clk *clk = to_asiu_clk(hw);
7262306a36Sopenharmony_ci	struct iproc_asiu *asiu = clk->asiu;
7362306a36Sopenharmony_ci	u32 val;
7462306a36Sopenharmony_ci	unsigned int div_h, div_l;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	if (parent_rate == 0) {
7762306a36Sopenharmony_ci		clk->rate = 0;
7862306a36Sopenharmony_ci		return 0;
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	/* if clock divisor is not enabled, simply return parent rate */
8262306a36Sopenharmony_ci	val = readl(asiu->div_base + clk->div.offset);
8362306a36Sopenharmony_ci	if ((val & (1 << clk->div.en_shift)) == 0) {
8462306a36Sopenharmony_ci		clk->rate = parent_rate;
8562306a36Sopenharmony_ci		return parent_rate;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	/* clock rate = parent rate / (high_div + 1) + (low_div + 1) */
8962306a36Sopenharmony_ci	div_h = (val >> clk->div.high_shift) & bit_mask(clk->div.high_width);
9062306a36Sopenharmony_ci	div_h++;
9162306a36Sopenharmony_ci	div_l = (val >> clk->div.low_shift) & bit_mask(clk->div.low_width);
9262306a36Sopenharmony_ci	div_l++;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	clk->rate = parent_rate / (div_h + div_l);
9562306a36Sopenharmony_ci	pr_debug("%s: rate: %lu. parent rate: %lu div_h: %u div_l: %u\n",
9662306a36Sopenharmony_ci		 __func__, clk->rate, parent_rate, div_h, div_l);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	return clk->rate;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic long iproc_asiu_clk_round_rate(struct clk_hw *hw, unsigned long rate,
10262306a36Sopenharmony_ci				      unsigned long *parent_rate)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	unsigned int div;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	if (rate == 0 || *parent_rate == 0)
10762306a36Sopenharmony_ci		return -EINVAL;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (rate == *parent_rate)
11062306a36Sopenharmony_ci		return *parent_rate;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	div = DIV_ROUND_CLOSEST(*parent_rate, rate);
11362306a36Sopenharmony_ci	if (div < 2)
11462306a36Sopenharmony_ci		return *parent_rate;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	return *parent_rate / div;
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic int iproc_asiu_clk_set_rate(struct clk_hw *hw, unsigned long rate,
12062306a36Sopenharmony_ci				   unsigned long parent_rate)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	struct iproc_asiu_clk *clk = to_asiu_clk(hw);
12362306a36Sopenharmony_ci	struct iproc_asiu *asiu = clk->asiu;
12462306a36Sopenharmony_ci	unsigned int div, div_h, div_l;
12562306a36Sopenharmony_ci	u32 val;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	if (rate == 0 || parent_rate == 0)
12862306a36Sopenharmony_ci		return -EINVAL;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/* simply disable the divisor if one wants the same rate as parent */
13162306a36Sopenharmony_ci	if (rate == parent_rate) {
13262306a36Sopenharmony_ci		val = readl(asiu->div_base + clk->div.offset);
13362306a36Sopenharmony_ci		val &= ~(1 << clk->div.en_shift);
13462306a36Sopenharmony_ci		writel(val, asiu->div_base + clk->div.offset);
13562306a36Sopenharmony_ci		return 0;
13662306a36Sopenharmony_ci	}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	div = DIV_ROUND_CLOSEST(parent_rate, rate);
13962306a36Sopenharmony_ci	if (div < 2)
14062306a36Sopenharmony_ci		return -EINVAL;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	div_h = div_l = div >> 1;
14362306a36Sopenharmony_ci	div_h--;
14462306a36Sopenharmony_ci	div_l--;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	val = readl(asiu->div_base + clk->div.offset);
14762306a36Sopenharmony_ci	val |= 1 << clk->div.en_shift;
14862306a36Sopenharmony_ci	if (div_h) {
14962306a36Sopenharmony_ci		val &= ~(bit_mask(clk->div.high_width)
15062306a36Sopenharmony_ci			 << clk->div.high_shift);
15162306a36Sopenharmony_ci		val |= div_h << clk->div.high_shift;
15262306a36Sopenharmony_ci	} else {
15362306a36Sopenharmony_ci		val &= ~(bit_mask(clk->div.high_width)
15462306a36Sopenharmony_ci			 << clk->div.high_shift);
15562306a36Sopenharmony_ci	}
15662306a36Sopenharmony_ci	if (div_l) {
15762306a36Sopenharmony_ci		val &= ~(bit_mask(clk->div.low_width) << clk->div.low_shift);
15862306a36Sopenharmony_ci		val |= div_l << clk->div.low_shift;
15962306a36Sopenharmony_ci	} else {
16062306a36Sopenharmony_ci		val &= ~(bit_mask(clk->div.low_width) << clk->div.low_shift);
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci	writel(val, asiu->div_base + clk->div.offset);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	return 0;
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic const struct clk_ops iproc_asiu_ops = {
16862306a36Sopenharmony_ci	.enable = iproc_asiu_clk_enable,
16962306a36Sopenharmony_ci	.disable = iproc_asiu_clk_disable,
17062306a36Sopenharmony_ci	.recalc_rate = iproc_asiu_clk_recalc_rate,
17162306a36Sopenharmony_ci	.round_rate = iproc_asiu_clk_round_rate,
17262306a36Sopenharmony_ci	.set_rate = iproc_asiu_clk_set_rate,
17362306a36Sopenharmony_ci};
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_civoid __init iproc_asiu_setup(struct device_node *node,
17662306a36Sopenharmony_ci			     const struct iproc_asiu_div *div,
17762306a36Sopenharmony_ci			     const struct iproc_asiu_gate *gate,
17862306a36Sopenharmony_ci			     unsigned int num_clks)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	int i, ret;
18162306a36Sopenharmony_ci	struct iproc_asiu *asiu;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	if (WARN_ON(!gate || !div))
18462306a36Sopenharmony_ci		return;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	asiu = kzalloc(sizeof(*asiu), GFP_KERNEL);
18762306a36Sopenharmony_ci	if (WARN_ON(!asiu))
18862306a36Sopenharmony_ci		return;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	asiu->clk_data = kzalloc(struct_size(asiu->clk_data, hws, num_clks),
19162306a36Sopenharmony_ci				 GFP_KERNEL);
19262306a36Sopenharmony_ci	if (WARN_ON(!asiu->clk_data))
19362306a36Sopenharmony_ci		goto err_clks;
19462306a36Sopenharmony_ci	asiu->clk_data->num = num_clks;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	asiu->clks = kcalloc(num_clks, sizeof(*asiu->clks), GFP_KERNEL);
19762306a36Sopenharmony_ci	if (WARN_ON(!asiu->clks))
19862306a36Sopenharmony_ci		goto err_asiu_clks;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	asiu->div_base = of_iomap(node, 0);
20162306a36Sopenharmony_ci	if (WARN_ON(!asiu->div_base))
20262306a36Sopenharmony_ci		goto err_iomap_div;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	asiu->gate_base = of_iomap(node, 1);
20562306a36Sopenharmony_ci	if (WARN_ON(!asiu->gate_base))
20662306a36Sopenharmony_ci		goto err_iomap_gate;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	for (i = 0; i < num_clks; i++) {
20962306a36Sopenharmony_ci		struct clk_init_data init;
21062306a36Sopenharmony_ci		const char *parent_name;
21162306a36Sopenharmony_ci		struct iproc_asiu_clk *asiu_clk;
21262306a36Sopenharmony_ci		const char *clk_name;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci		ret = of_property_read_string_index(node, "clock-output-names",
21562306a36Sopenharmony_ci						    i, &clk_name);
21662306a36Sopenharmony_ci		if (WARN_ON(ret))
21762306a36Sopenharmony_ci			goto err_clk_register;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci		asiu_clk = &asiu->clks[i];
22062306a36Sopenharmony_ci		asiu_clk->name = clk_name;
22162306a36Sopenharmony_ci		asiu_clk->asiu = asiu;
22262306a36Sopenharmony_ci		asiu_clk->div = div[i];
22362306a36Sopenharmony_ci		asiu_clk->gate = gate[i];
22462306a36Sopenharmony_ci		init.name = clk_name;
22562306a36Sopenharmony_ci		init.ops = &iproc_asiu_ops;
22662306a36Sopenharmony_ci		init.flags = 0;
22762306a36Sopenharmony_ci		parent_name = of_clk_get_parent_name(node, 0);
22862306a36Sopenharmony_ci		init.parent_names = (parent_name ? &parent_name : NULL);
22962306a36Sopenharmony_ci		init.num_parents = (parent_name ? 1 : 0);
23062306a36Sopenharmony_ci		asiu_clk->hw.init = &init;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci		ret = clk_hw_register(NULL, &asiu_clk->hw);
23362306a36Sopenharmony_ci		if (WARN_ON(ret))
23462306a36Sopenharmony_ci			goto err_clk_register;
23562306a36Sopenharmony_ci		asiu->clk_data->hws[i] = &asiu_clk->hw;
23662306a36Sopenharmony_ci	}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get,
23962306a36Sopenharmony_ci				     asiu->clk_data);
24062306a36Sopenharmony_ci	if (WARN_ON(ret))
24162306a36Sopenharmony_ci		goto err_clk_register;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	return;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cierr_clk_register:
24662306a36Sopenharmony_ci	while (--i >= 0)
24762306a36Sopenharmony_ci		clk_hw_unregister(asiu->clk_data->hws[i]);
24862306a36Sopenharmony_ci	iounmap(asiu->gate_base);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cierr_iomap_gate:
25162306a36Sopenharmony_ci	iounmap(asiu->div_base);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_cierr_iomap_div:
25462306a36Sopenharmony_ci	kfree(asiu->clks);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cierr_asiu_clks:
25762306a36Sopenharmony_ci	kfree(asiu->clk_data);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cierr_clks:
26062306a36Sopenharmony_ci	kfree(asiu);
26162306a36Sopenharmony_ci}
262