162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Microchip Sparx5 SoC Clock driver.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2019 Microchip Inc.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author: Lars Povlsen <lars.povlsen@microchip.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/io.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/clk-provider.h>
1362306a36Sopenharmony_ci#include <linux/bitfield.h>
1462306a36Sopenharmony_ci#include <linux/of.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci#include <dt-bindings/clock/microchip,sparx5.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define PLL_DIV		GENMASK(7, 0)
2062306a36Sopenharmony_ci#define PLL_PRE_DIV	GENMASK(10, 8)
2162306a36Sopenharmony_ci#define PLL_ROT_DIR	BIT(11)
2262306a36Sopenharmony_ci#define PLL_ROT_SEL	GENMASK(13, 12)
2362306a36Sopenharmony_ci#define PLL_ROT_ENA	BIT(14)
2462306a36Sopenharmony_ci#define PLL_CLK_ENA	BIT(15)
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define MAX_SEL 4
2762306a36Sopenharmony_ci#define MAX_PRE BIT(3)
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic const u8 sel_rates[MAX_SEL] = { 0, 2*8, 2*4, 2*2 };
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic const char *clk_names[N_CLOCKS] = {
3262306a36Sopenharmony_ci	"core", "ddr", "cpu2", "arm2",
3362306a36Sopenharmony_ci	"aux1", "aux2", "aux3", "aux4",
3462306a36Sopenharmony_ci	"synce",
3562306a36Sopenharmony_ci};
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistruct s5_hw_clk {
3862306a36Sopenharmony_ci	struct clk_hw hw;
3962306a36Sopenharmony_ci	void __iomem *reg;
4062306a36Sopenharmony_ci};
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistruct s5_clk_data {
4362306a36Sopenharmony_ci	void __iomem *base;
4462306a36Sopenharmony_ci	struct s5_hw_clk s5_hw[N_CLOCKS];
4562306a36Sopenharmony_ci};
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistruct s5_pll_conf {
4862306a36Sopenharmony_ci	unsigned long freq;
4962306a36Sopenharmony_ci	u8 div;
5062306a36Sopenharmony_ci	bool rot_ena;
5162306a36Sopenharmony_ci	u8 rot_sel;
5262306a36Sopenharmony_ci	u8 rot_dir;
5362306a36Sopenharmony_ci	u8 pre_div;
5462306a36Sopenharmony_ci};
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci#define to_s5_pll(hw) container_of(hw, struct s5_hw_clk, hw)
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic unsigned long s5_calc_freq(unsigned long parent_rate,
5962306a36Sopenharmony_ci				  const struct s5_pll_conf *conf)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	unsigned long rate = parent_rate / conf->div;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	if (conf->rot_ena) {
6462306a36Sopenharmony_ci		int sign = conf->rot_dir ? -1 : 1;
6562306a36Sopenharmony_ci		int divt = sel_rates[conf->rot_sel] * (1 + conf->pre_div);
6662306a36Sopenharmony_ci		int divb = divt + sign;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci		rate = mult_frac(rate, divt, divb);
6962306a36Sopenharmony_ci		rate = roundup(rate, 1000);
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	return rate;
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic void s5_search_fractional(unsigned long rate,
7662306a36Sopenharmony_ci				 unsigned long parent_rate,
7762306a36Sopenharmony_ci				 int div,
7862306a36Sopenharmony_ci				 struct s5_pll_conf *conf)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	struct s5_pll_conf best;
8162306a36Sopenharmony_ci	ulong cur_offset, best_offset = rate;
8262306a36Sopenharmony_ci	int d, i, j;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	memset(conf, 0, sizeof(*conf));
8562306a36Sopenharmony_ci	conf->div = div;
8662306a36Sopenharmony_ci	conf->rot_ena = 1;	/* Fractional rate */
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	for (d = 0; best_offset > 0 && d <= 1 ; d++) {
8962306a36Sopenharmony_ci		conf->rot_dir = !!d;
9062306a36Sopenharmony_ci		for (i = 0; best_offset > 0 && i < MAX_PRE; i++) {
9162306a36Sopenharmony_ci			conf->pre_div = i;
9262306a36Sopenharmony_ci			for (j = 1; best_offset > 0 && j < MAX_SEL; j++) {
9362306a36Sopenharmony_ci				conf->rot_sel = j;
9462306a36Sopenharmony_ci				conf->freq = s5_calc_freq(parent_rate, conf);
9562306a36Sopenharmony_ci				cur_offset = abs(rate - conf->freq);
9662306a36Sopenharmony_ci				if (cur_offset < best_offset) {
9762306a36Sopenharmony_ci					best_offset = cur_offset;
9862306a36Sopenharmony_ci					best = *conf;
9962306a36Sopenharmony_ci				}
10062306a36Sopenharmony_ci			}
10162306a36Sopenharmony_ci		}
10262306a36Sopenharmony_ci	}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	/* Best match */
10562306a36Sopenharmony_ci	*conf = best;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic unsigned long s5_calc_params(unsigned long rate,
10962306a36Sopenharmony_ci				    unsigned long parent_rate,
11062306a36Sopenharmony_ci				    struct s5_pll_conf *conf)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	if (parent_rate % rate) {
11362306a36Sopenharmony_ci		struct s5_pll_conf alt1, alt2;
11462306a36Sopenharmony_ci		int div;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci		div = DIV_ROUND_CLOSEST_ULL(parent_rate, rate);
11762306a36Sopenharmony_ci		s5_search_fractional(rate, parent_rate, div, &alt1);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci		/* Straight match? */
12062306a36Sopenharmony_ci		if (alt1.freq == rate) {
12162306a36Sopenharmony_ci			*conf = alt1;
12262306a36Sopenharmony_ci		} else {
12362306a36Sopenharmony_ci			/* Try without rounding divider */
12462306a36Sopenharmony_ci			div = parent_rate / rate;
12562306a36Sopenharmony_ci			if (div != alt1.div) {
12662306a36Sopenharmony_ci				s5_search_fractional(rate, parent_rate, div,
12762306a36Sopenharmony_ci						     &alt2);
12862306a36Sopenharmony_ci				/* Select the better match */
12962306a36Sopenharmony_ci				if (abs(rate - alt1.freq) <
13062306a36Sopenharmony_ci				    abs(rate - alt2.freq))
13162306a36Sopenharmony_ci					*conf = alt1;
13262306a36Sopenharmony_ci				else
13362306a36Sopenharmony_ci					*conf = alt2;
13462306a36Sopenharmony_ci			}
13562306a36Sopenharmony_ci		}
13662306a36Sopenharmony_ci	} else {
13762306a36Sopenharmony_ci		/* Straight fit */
13862306a36Sopenharmony_ci		memset(conf, 0, sizeof(*conf));
13962306a36Sopenharmony_ci		conf->div = parent_rate / rate;
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	return conf->freq;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic int s5_pll_enable(struct clk_hw *hw)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	struct s5_hw_clk *pll = to_s5_pll(hw);
14862306a36Sopenharmony_ci	u32 val = readl(pll->reg);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	val |= PLL_CLK_ENA;
15162306a36Sopenharmony_ci	writel(val, pll->reg);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	return 0;
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cistatic void s5_pll_disable(struct clk_hw *hw)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	struct s5_hw_clk *pll = to_s5_pll(hw);
15962306a36Sopenharmony_ci	u32 val = readl(pll->reg);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	val &= ~PLL_CLK_ENA;
16262306a36Sopenharmony_ci	writel(val, pll->reg);
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cistatic int s5_pll_set_rate(struct clk_hw *hw,
16662306a36Sopenharmony_ci			   unsigned long rate,
16762306a36Sopenharmony_ci			   unsigned long parent_rate)
16862306a36Sopenharmony_ci{
16962306a36Sopenharmony_ci	struct s5_hw_clk *pll = to_s5_pll(hw);
17062306a36Sopenharmony_ci	struct s5_pll_conf conf;
17162306a36Sopenharmony_ci	unsigned long eff_rate;
17262306a36Sopenharmony_ci	u32 val;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	eff_rate = s5_calc_params(rate, parent_rate, &conf);
17562306a36Sopenharmony_ci	if (eff_rate != rate)
17662306a36Sopenharmony_ci		return -EOPNOTSUPP;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	val = readl(pll->reg) & PLL_CLK_ENA;
17962306a36Sopenharmony_ci	val |= FIELD_PREP(PLL_DIV, conf.div);
18062306a36Sopenharmony_ci	if (conf.rot_ena) {
18162306a36Sopenharmony_ci		val |= PLL_ROT_ENA;
18262306a36Sopenharmony_ci		val |= FIELD_PREP(PLL_ROT_SEL, conf.rot_sel);
18362306a36Sopenharmony_ci		val |= FIELD_PREP(PLL_PRE_DIV, conf.pre_div);
18462306a36Sopenharmony_ci		if (conf.rot_dir)
18562306a36Sopenharmony_ci			val |= PLL_ROT_DIR;
18662306a36Sopenharmony_ci	}
18762306a36Sopenharmony_ci	writel(val, pll->reg);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return 0;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic unsigned long s5_pll_recalc_rate(struct clk_hw *hw,
19362306a36Sopenharmony_ci					unsigned long parent_rate)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	struct s5_hw_clk *pll = to_s5_pll(hw);
19662306a36Sopenharmony_ci	struct s5_pll_conf conf;
19762306a36Sopenharmony_ci	u32 val;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	val = readl(pll->reg);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	if (val & PLL_CLK_ENA) {
20262306a36Sopenharmony_ci		conf.div     = FIELD_GET(PLL_DIV, val);
20362306a36Sopenharmony_ci		conf.pre_div = FIELD_GET(PLL_PRE_DIV, val);
20462306a36Sopenharmony_ci		conf.rot_ena = FIELD_GET(PLL_ROT_ENA, val);
20562306a36Sopenharmony_ci		conf.rot_dir = FIELD_GET(PLL_ROT_DIR, val);
20662306a36Sopenharmony_ci		conf.rot_sel = FIELD_GET(PLL_ROT_SEL, val);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci		conf.freq = s5_calc_freq(parent_rate, &conf);
20962306a36Sopenharmony_ci	} else {
21062306a36Sopenharmony_ci		conf.freq = 0;
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	return conf.freq;
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic long s5_pll_round_rate(struct clk_hw *hw, unsigned long rate,
21762306a36Sopenharmony_ci			      unsigned long *parent_rate)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	struct s5_pll_conf conf;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	return s5_calc_params(rate, *parent_rate, &conf);
22262306a36Sopenharmony_ci}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic const struct clk_ops s5_pll_ops = {
22562306a36Sopenharmony_ci	.enable		= s5_pll_enable,
22662306a36Sopenharmony_ci	.disable	= s5_pll_disable,
22762306a36Sopenharmony_ci	.set_rate	= s5_pll_set_rate,
22862306a36Sopenharmony_ci	.round_rate	= s5_pll_round_rate,
22962306a36Sopenharmony_ci	.recalc_rate	= s5_pll_recalc_rate,
23062306a36Sopenharmony_ci};
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic struct clk_hw *s5_clk_hw_get(struct of_phandle_args *clkspec, void *data)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	struct s5_clk_data *s5_clk = data;
23562306a36Sopenharmony_ci	unsigned int idx = clkspec->args[0];
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	if (idx >= N_CLOCKS) {
23862306a36Sopenharmony_ci		pr_err("%s: invalid index %u\n", __func__, idx);
23962306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	return &s5_clk->s5_hw[idx].hw;
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cistatic int s5_clk_probe(struct platform_device *pdev)
24662306a36Sopenharmony_ci{
24762306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
24862306a36Sopenharmony_ci	int i, ret;
24962306a36Sopenharmony_ci	struct s5_clk_data *s5_clk;
25062306a36Sopenharmony_ci	struct clk_parent_data pdata = { .index = 0 };
25162306a36Sopenharmony_ci	struct clk_init_data init = {
25262306a36Sopenharmony_ci		.ops = &s5_pll_ops,
25362306a36Sopenharmony_ci		.num_parents = 1,
25462306a36Sopenharmony_ci		.parent_data = &pdata,
25562306a36Sopenharmony_ci	};
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	s5_clk = devm_kzalloc(dev, sizeof(*s5_clk), GFP_KERNEL);
25862306a36Sopenharmony_ci	if (!s5_clk)
25962306a36Sopenharmony_ci		return -ENOMEM;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	s5_clk->base = devm_platform_ioremap_resource(pdev, 0);
26262306a36Sopenharmony_ci	if (IS_ERR(s5_clk->base))
26362306a36Sopenharmony_ci		return PTR_ERR(s5_clk->base);
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	for (i = 0; i < N_CLOCKS; i++) {
26662306a36Sopenharmony_ci		struct s5_hw_clk *s5_hw = &s5_clk->s5_hw[i];
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci		init.name = clk_names[i];
26962306a36Sopenharmony_ci		s5_hw->reg = s5_clk->base + (i * 4);
27062306a36Sopenharmony_ci		s5_hw->hw.init = &init;
27162306a36Sopenharmony_ci		ret = devm_clk_hw_register(dev, &s5_hw->hw);
27262306a36Sopenharmony_ci		if (ret) {
27362306a36Sopenharmony_ci			dev_err(dev, "failed to register %s clock\n",
27462306a36Sopenharmony_ci				init.name);
27562306a36Sopenharmony_ci			return ret;
27662306a36Sopenharmony_ci		}
27762306a36Sopenharmony_ci	}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	return devm_of_clk_add_hw_provider(dev, s5_clk_hw_get, s5_clk);
28062306a36Sopenharmony_ci}
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_cistatic const struct of_device_id s5_clk_dt_ids[] = {
28362306a36Sopenharmony_ci	{ .compatible = "microchip,sparx5-dpll", },
28462306a36Sopenharmony_ci	{ }
28562306a36Sopenharmony_ci};
28662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, s5_clk_dt_ids);
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic struct platform_driver s5_clk_driver = {
28962306a36Sopenharmony_ci	.probe  = s5_clk_probe,
29062306a36Sopenharmony_ci	.driver = {
29162306a36Sopenharmony_ci		.name = "sparx5-clk",
29262306a36Sopenharmony_ci		.of_match_table = s5_clk_dt_ids,
29362306a36Sopenharmony_ci	},
29462306a36Sopenharmony_ci};
29562306a36Sopenharmony_cibuiltin_platform_driver(s5_clk_driver);
296