162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Author: Conor Dooley <conor.dooley@microchip.com>
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include "asm-generic/errno-base.h"
862306a36Sopenharmony_ci#include <linux/clk-provider.h>
962306a36Sopenharmony_ci#include <linux/io.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/platform_device.h>
1262306a36Sopenharmony_ci#include <dt-bindings/clock/microchip,mpfs-clock.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci/* address offset of control registers */
1562306a36Sopenharmony_ci#define MPFS_CCC_PLL_CR			0x04u
1662306a36Sopenharmony_ci#define MPFS_CCC_REF_CR			0x08u
1762306a36Sopenharmony_ci#define MPFS_CCC_SSCG_2_CR		0x2Cu
1862306a36Sopenharmony_ci#define MPFS_CCC_POSTDIV01_CR		0x10u
1962306a36Sopenharmony_ci#define MPFS_CCC_POSTDIV23_CR		0x14u
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define MPFS_CCC_FBDIV_SHIFT		0x00u
2262306a36Sopenharmony_ci#define MPFS_CCC_FBDIV_WIDTH		0x0Cu
2362306a36Sopenharmony_ci#define MPFS_CCC_POSTDIV0_SHIFT		0x08u
2462306a36Sopenharmony_ci#define MPFS_CCC_POSTDIV1_SHIFT		0x18u
2562306a36Sopenharmony_ci#define MPFS_CCC_POSTDIV2_SHIFT		MPFS_CCC_POSTDIV0_SHIFT
2662306a36Sopenharmony_ci#define MPFS_CCC_POSTDIV3_SHIFT		MPFS_CCC_POSTDIV1_SHIFT
2762306a36Sopenharmony_ci#define MPFS_CCC_POSTDIV_WIDTH		0x06u
2862306a36Sopenharmony_ci#define MPFS_CCC_REFCLK_SEL		BIT(6)
2962306a36Sopenharmony_ci#define MPFS_CCC_REFDIV_SHIFT		0x08u
3062306a36Sopenharmony_ci#define MPFS_CCC_REFDIV_WIDTH		0x06u
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define MPFS_CCC_FIXED_DIV		4
3362306a36Sopenharmony_ci#define MPFS_CCC_OUTPUTS_PER_PLL	4
3462306a36Sopenharmony_ci#define MPFS_CCC_REFS_PER_PLL		2
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistruct mpfs_ccc_data {
3762306a36Sopenharmony_ci	void __iomem **pll_base;
3862306a36Sopenharmony_ci	struct device *dev;
3962306a36Sopenharmony_ci	struct clk_hw_onecell_data hw_data;
4062306a36Sopenharmony_ci};
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistruct mpfs_ccc_pll_hw_clock {
4362306a36Sopenharmony_ci	void __iomem *base;
4462306a36Sopenharmony_ci	const char *name;
4562306a36Sopenharmony_ci	const struct clk_parent_data *parents;
4662306a36Sopenharmony_ci	unsigned int id;
4762306a36Sopenharmony_ci	u32 reg_offset;
4862306a36Sopenharmony_ci	u32 shift;
4962306a36Sopenharmony_ci	u32 width;
5062306a36Sopenharmony_ci	u32 flags;
5162306a36Sopenharmony_ci	struct clk_hw hw;
5262306a36Sopenharmony_ci	struct clk_init_data init;
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci#define to_mpfs_ccc_clk(_hw) container_of(_hw, struct mpfs_ccc_pll_hw_clock, hw)
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci/*
5862306a36Sopenharmony_ci * mpfs_ccc_lock prevents anything else from writing to a fabric ccc
5962306a36Sopenharmony_ci * while a software locked register is being written.
6062306a36Sopenharmony_ci */
6162306a36Sopenharmony_cistatic DEFINE_SPINLOCK(mpfs_ccc_lock);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic const struct clk_parent_data mpfs_ccc_pll0_refs[] = {
6462306a36Sopenharmony_ci	{ .fw_name = "pll0_ref0" },
6562306a36Sopenharmony_ci	{ .fw_name = "pll0_ref1" },
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic const struct clk_parent_data mpfs_ccc_pll1_refs[] = {
6962306a36Sopenharmony_ci	{ .fw_name = "pll1_ref0" },
7062306a36Sopenharmony_ci	{ .fw_name = "pll1_ref1" },
7162306a36Sopenharmony_ci};
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic unsigned long mpfs_ccc_pll_recalc_rate(struct clk_hw *hw, unsigned long prate)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	struct mpfs_ccc_pll_hw_clock *ccc_hw = to_mpfs_ccc_clk(hw);
7662306a36Sopenharmony_ci	void __iomem *mult_addr = ccc_hw->base + ccc_hw->reg_offset;
7762306a36Sopenharmony_ci	void __iomem *ref_div_addr = ccc_hw->base + MPFS_CCC_REF_CR;
7862306a36Sopenharmony_ci	u32 mult, ref_div;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	mult = readl_relaxed(mult_addr) >> MPFS_CCC_FBDIV_SHIFT;
8162306a36Sopenharmony_ci	mult &= clk_div_mask(MPFS_CCC_FBDIV_WIDTH);
8262306a36Sopenharmony_ci	ref_div = readl_relaxed(ref_div_addr) >> MPFS_CCC_REFDIV_SHIFT;
8362306a36Sopenharmony_ci	ref_div &= clk_div_mask(MPFS_CCC_REFDIV_WIDTH);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	return prate * mult / (ref_div * MPFS_CCC_FIXED_DIV);
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic u8 mpfs_ccc_pll_get_parent(struct clk_hw *hw)
8962306a36Sopenharmony_ci{
9062306a36Sopenharmony_ci	struct mpfs_ccc_pll_hw_clock *ccc_hw = to_mpfs_ccc_clk(hw);
9162306a36Sopenharmony_ci	void __iomem *pll_cr_addr = ccc_hw->base + MPFS_CCC_PLL_CR;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	return !!(readl_relaxed(pll_cr_addr) & MPFS_CCC_REFCLK_SEL);
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic const struct clk_ops mpfs_ccc_pll_ops = {
9762306a36Sopenharmony_ci	.recalc_rate = mpfs_ccc_pll_recalc_rate,
9862306a36Sopenharmony_ci	.get_parent = mpfs_ccc_pll_get_parent,
9962306a36Sopenharmony_ci};
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci#define CLK_CCC_PLL(_id, _parents, _shift, _width, _flags, _offset) {	\
10262306a36Sopenharmony_ci	.id = _id,							\
10362306a36Sopenharmony_ci	.shift = _shift,						\
10462306a36Sopenharmony_ci	.width = _width,						\
10562306a36Sopenharmony_ci	.reg_offset = _offset,						\
10662306a36Sopenharmony_ci	.flags = _flags,						\
10762306a36Sopenharmony_ci	.parents = _parents,						\
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic struct mpfs_ccc_pll_hw_clock mpfs_ccc_pll_clks[] = {
11162306a36Sopenharmony_ci	CLK_CCC_PLL(CLK_CCC_PLL0, mpfs_ccc_pll0_refs, MPFS_CCC_FBDIV_SHIFT,
11262306a36Sopenharmony_ci		    MPFS_CCC_FBDIV_WIDTH, 0, MPFS_CCC_SSCG_2_CR),
11362306a36Sopenharmony_ci	CLK_CCC_PLL(CLK_CCC_PLL1, mpfs_ccc_pll1_refs, MPFS_CCC_FBDIV_SHIFT,
11462306a36Sopenharmony_ci		    MPFS_CCC_FBDIV_WIDTH, 0, MPFS_CCC_SSCG_2_CR),
11562306a36Sopenharmony_ci};
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistruct mpfs_ccc_out_hw_clock {
11862306a36Sopenharmony_ci	struct clk_divider divider;
11962306a36Sopenharmony_ci	struct clk_init_data init;
12062306a36Sopenharmony_ci	unsigned int id;
12162306a36Sopenharmony_ci	u32 reg_offset;
12262306a36Sopenharmony_ci};
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci#define CLK_CCC_OUT(_id, _shift, _width, _flags, _offset) {	\
12562306a36Sopenharmony_ci	.id = _id,						\
12662306a36Sopenharmony_ci	.divider.shift = _shift,				\
12762306a36Sopenharmony_ci	.divider.width = _width,				\
12862306a36Sopenharmony_ci	.reg_offset = _offset,					\
12962306a36Sopenharmony_ci	.divider.flags = _flags,				\
13062306a36Sopenharmony_ci	.divider.lock = &mpfs_ccc_lock,				\
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic struct mpfs_ccc_out_hw_clock mpfs_ccc_pll0out_clks[] = {
13462306a36Sopenharmony_ci	CLK_CCC_OUT(CLK_CCC_PLL0_OUT0, MPFS_CCC_POSTDIV0_SHIFT, MPFS_CCC_POSTDIV_WIDTH,
13562306a36Sopenharmony_ci		    CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV01_CR),
13662306a36Sopenharmony_ci	CLK_CCC_OUT(CLK_CCC_PLL0_OUT1, MPFS_CCC_POSTDIV1_SHIFT, MPFS_CCC_POSTDIV_WIDTH,
13762306a36Sopenharmony_ci		    CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV01_CR),
13862306a36Sopenharmony_ci	CLK_CCC_OUT(CLK_CCC_PLL0_OUT2, MPFS_CCC_POSTDIV2_SHIFT, MPFS_CCC_POSTDIV_WIDTH,
13962306a36Sopenharmony_ci		    CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV23_CR),
14062306a36Sopenharmony_ci	CLK_CCC_OUT(CLK_CCC_PLL0_OUT3, MPFS_CCC_POSTDIV3_SHIFT, MPFS_CCC_POSTDIV_WIDTH,
14162306a36Sopenharmony_ci		    CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV23_CR),
14262306a36Sopenharmony_ci};
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic struct mpfs_ccc_out_hw_clock mpfs_ccc_pll1out_clks[] = {
14562306a36Sopenharmony_ci	CLK_CCC_OUT(CLK_CCC_PLL1_OUT0, MPFS_CCC_POSTDIV0_SHIFT, MPFS_CCC_POSTDIV_WIDTH,
14662306a36Sopenharmony_ci		    CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV01_CR),
14762306a36Sopenharmony_ci	CLK_CCC_OUT(CLK_CCC_PLL1_OUT1, MPFS_CCC_POSTDIV1_SHIFT, MPFS_CCC_POSTDIV_WIDTH,
14862306a36Sopenharmony_ci		    CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV01_CR),
14962306a36Sopenharmony_ci	CLK_CCC_OUT(CLK_CCC_PLL1_OUT2, MPFS_CCC_POSTDIV2_SHIFT, MPFS_CCC_POSTDIV_WIDTH,
15062306a36Sopenharmony_ci		    CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV23_CR),
15162306a36Sopenharmony_ci	CLK_CCC_OUT(CLK_CCC_PLL1_OUT3, MPFS_CCC_POSTDIV3_SHIFT, MPFS_CCC_POSTDIV_WIDTH,
15262306a36Sopenharmony_ci		    CLK_DIVIDER_ONE_BASED, MPFS_CCC_POSTDIV23_CR),
15362306a36Sopenharmony_ci};
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic struct mpfs_ccc_out_hw_clock *mpfs_ccc_pllout_clks[] = {
15662306a36Sopenharmony_ci	mpfs_ccc_pll0out_clks, mpfs_ccc_pll1out_clks
15762306a36Sopenharmony_ci};
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic int mpfs_ccc_register_outputs(struct device *dev, struct mpfs_ccc_out_hw_clock *out_hws,
16062306a36Sopenharmony_ci				     unsigned int num_clks, struct mpfs_ccc_data *data,
16162306a36Sopenharmony_ci				     struct mpfs_ccc_pll_hw_clock *parent)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	int ret;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	for (unsigned int i = 0; i < num_clks; i++) {
16662306a36Sopenharmony_ci		struct mpfs_ccc_out_hw_clock *out_hw = &out_hws[i];
16762306a36Sopenharmony_ci		char *name = devm_kasprintf(dev, GFP_KERNEL, "%s_out%u", parent->name, i);
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci		if (!name)
17062306a36Sopenharmony_ci			return -ENOMEM;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci		out_hw->divider.hw.init = CLK_HW_INIT_HW(name, &parent->hw, &clk_divider_ops, 0);
17362306a36Sopenharmony_ci		out_hw->divider.reg = data->pll_base[i / MPFS_CCC_OUTPUTS_PER_PLL] +
17462306a36Sopenharmony_ci			out_hw->reg_offset;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci		ret = devm_clk_hw_register(dev, &out_hw->divider.hw);
17762306a36Sopenharmony_ci		if (ret)
17862306a36Sopenharmony_ci			return dev_err_probe(dev, ret, "failed to register clock id: %d\n",
17962306a36Sopenharmony_ci					     out_hw->id);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci		data->hw_data.hws[out_hw->id] = &out_hw->divider.hw;
18262306a36Sopenharmony_ci	}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	return 0;
18562306a36Sopenharmony_ci}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci#define CLK_HW_INIT_PARENTS_DATA_FIXED_SIZE(_name, _parents, _ops, _flags)	\
18862306a36Sopenharmony_ci	(&(struct clk_init_data) {						\
18962306a36Sopenharmony_ci		.flags		= _flags,					\
19062306a36Sopenharmony_ci		.name		= _name,					\
19162306a36Sopenharmony_ci		.parent_data	= _parents,					\
19262306a36Sopenharmony_ci		.num_parents	= MPFS_CCC_REFS_PER_PLL,			\
19362306a36Sopenharmony_ci		.ops		= _ops,						\
19462306a36Sopenharmony_ci	})
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic int mpfs_ccc_register_plls(struct device *dev, struct mpfs_ccc_pll_hw_clock *pll_hws,
19762306a36Sopenharmony_ci				  unsigned int num_clks, struct mpfs_ccc_data *data)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	int ret;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	for (unsigned int i = 0; i < num_clks; i++) {
20262306a36Sopenharmony_ci		struct mpfs_ccc_pll_hw_clock *pll_hw = &pll_hws[i];
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci		pll_hw->name = devm_kasprintf(dev, GFP_KERNEL, "ccc%s_pll%u",
20562306a36Sopenharmony_ci					      strchrnul(dev->of_node->full_name, '@'), i);
20662306a36Sopenharmony_ci		if (!pll_hw->name)
20762306a36Sopenharmony_ci			return -ENOMEM;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci		pll_hw->base = data->pll_base[i];
21062306a36Sopenharmony_ci		pll_hw->hw.init = CLK_HW_INIT_PARENTS_DATA_FIXED_SIZE(pll_hw->name,
21162306a36Sopenharmony_ci								      pll_hw->parents,
21262306a36Sopenharmony_ci								      &mpfs_ccc_pll_ops, 0);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci		ret = devm_clk_hw_register(dev, &pll_hw->hw);
21562306a36Sopenharmony_ci		if (ret)
21662306a36Sopenharmony_ci			return dev_err_probe(dev, ret, "failed to register ccc id: %d\n",
21762306a36Sopenharmony_ci					     pll_hw->id);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci		data->hw_data.hws[pll_hw->id] = &pll_hw->hw;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci		ret = mpfs_ccc_register_outputs(dev, mpfs_ccc_pllout_clks[i],
22262306a36Sopenharmony_ci						MPFS_CCC_OUTPUTS_PER_PLL, data, pll_hw);
22362306a36Sopenharmony_ci		if (ret)
22462306a36Sopenharmony_ci			return ret;
22562306a36Sopenharmony_ci	}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	return 0;
22862306a36Sopenharmony_ci}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic int mpfs_ccc_probe(struct platform_device *pdev)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci	struct mpfs_ccc_data *clk_data;
23362306a36Sopenharmony_ci	void __iomem *pll_base[ARRAY_SIZE(mpfs_ccc_pll_clks)];
23462306a36Sopenharmony_ci	unsigned int num_clks;
23562306a36Sopenharmony_ci	int ret;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	num_clks = ARRAY_SIZE(mpfs_ccc_pll_clks) + ARRAY_SIZE(mpfs_ccc_pll0out_clks) +
23862306a36Sopenharmony_ci		   ARRAY_SIZE(mpfs_ccc_pll1out_clks);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	clk_data = devm_kzalloc(&pdev->dev, struct_size(clk_data, hw_data.hws, num_clks),
24162306a36Sopenharmony_ci				GFP_KERNEL);
24262306a36Sopenharmony_ci	if (!clk_data)
24362306a36Sopenharmony_ci		return -ENOMEM;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	pll_base[0] = devm_platform_ioremap_resource(pdev, 0);
24662306a36Sopenharmony_ci	if (IS_ERR(pll_base[0]))
24762306a36Sopenharmony_ci		return PTR_ERR(pll_base[0]);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	pll_base[1] = devm_platform_ioremap_resource(pdev, 1);
25062306a36Sopenharmony_ci	if (IS_ERR(pll_base[1]))
25162306a36Sopenharmony_ci		return PTR_ERR(pll_base[1]);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	clk_data->pll_base = pll_base;
25462306a36Sopenharmony_ci	clk_data->hw_data.num = num_clks;
25562306a36Sopenharmony_ci	clk_data->dev = &pdev->dev;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	ret = mpfs_ccc_register_plls(clk_data->dev, mpfs_ccc_pll_clks,
25862306a36Sopenharmony_ci				     ARRAY_SIZE(mpfs_ccc_pll_clks), clk_data);
25962306a36Sopenharmony_ci	if (ret)
26062306a36Sopenharmony_ci		return ret;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	return devm_of_clk_add_hw_provider(clk_data->dev, of_clk_hw_onecell_get,
26362306a36Sopenharmony_ci					   &clk_data->hw_data);
26462306a36Sopenharmony_ci}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_cistatic const struct of_device_id mpfs_ccc_of_match_table[] = {
26762306a36Sopenharmony_ci	{ .compatible = "microchip,mpfs-ccc", },
26862306a36Sopenharmony_ci	{}
26962306a36Sopenharmony_ci};
27062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mpfs_ccc_of_match_table);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_cistatic struct platform_driver mpfs_ccc_driver = {
27362306a36Sopenharmony_ci	.probe = mpfs_ccc_probe,
27462306a36Sopenharmony_ci	.driver	= {
27562306a36Sopenharmony_ci		.name = "microchip-mpfs-ccc",
27662306a36Sopenharmony_ci		.of_match_table = mpfs_ccc_of_match_table,
27762306a36Sopenharmony_ci	},
27862306a36Sopenharmony_ci};
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic int __init clk_ccc_init(void)
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci	return platform_driver_register(&mpfs_ccc_driver);
28362306a36Sopenharmony_ci}
28462306a36Sopenharmony_cicore_initcall(clk_ccc_init);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_cistatic void __exit clk_ccc_exit(void)
28762306a36Sopenharmony_ci{
28862306a36Sopenharmony_ci	platform_driver_unregister(&mpfs_ccc_driver);
28962306a36Sopenharmony_ci}
29062306a36Sopenharmony_cimodule_exit(clk_ccc_exit);
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ciMODULE_DESCRIPTION("Microchip PolarFire SoC Clock Conditioning Circuitry Driver");
29362306a36Sopenharmony_ciMODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>");
294