162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:
662306a36Sopenharmony_ci *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
762306a36Sopenharmony_ci *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Baikal-T1 CCU PLL clocks driver
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/printk.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci#include <linux/clk-provider.h>
1962306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
2062306a36Sopenharmony_ci#include <linux/of.h>
2162306a36Sopenharmony_ci#include <linux/of_address.h>
2262306a36Sopenharmony_ci#include <linux/ioport.h>
2362306a36Sopenharmony_ci#include <linux/regmap.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#include <dt-bindings/clock/bt1-ccu.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#include "ccu-pll.h"
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define CCU_CPU_PLL_BASE		0x000
3062306a36Sopenharmony_ci#define CCU_SATA_PLL_BASE		0x008
3162306a36Sopenharmony_ci#define CCU_DDR_PLL_BASE		0x010
3262306a36Sopenharmony_ci#define CCU_PCIE_PLL_BASE		0x018
3362306a36Sopenharmony_ci#define CCU_ETH_PLL_BASE		0x020
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#define CCU_PLL_INFO(_id, _name, _pname, _base, _flags, _features)	\
3662306a36Sopenharmony_ci	{								\
3762306a36Sopenharmony_ci		.id = _id,						\
3862306a36Sopenharmony_ci		.name = _name,						\
3962306a36Sopenharmony_ci		.parent_name = _pname,					\
4062306a36Sopenharmony_ci		.base = _base,						\
4162306a36Sopenharmony_ci		.flags = _flags,					\
4262306a36Sopenharmony_ci		.features = _features,					\
4362306a36Sopenharmony_ci	}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci#define CCU_PLL_NUM			ARRAY_SIZE(pll_info)
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistruct ccu_pll_info {
4862306a36Sopenharmony_ci	unsigned int id;
4962306a36Sopenharmony_ci	const char *name;
5062306a36Sopenharmony_ci	const char *parent_name;
5162306a36Sopenharmony_ci	unsigned int base;
5262306a36Sopenharmony_ci	unsigned long flags;
5362306a36Sopenharmony_ci	unsigned long features;
5462306a36Sopenharmony_ci};
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci/*
5762306a36Sopenharmony_ci * Alas we have to mark all PLLs as critical. CPU and DDR PLLs are sources of
5862306a36Sopenharmony_ci * CPU cores and DDR controller reference clocks, due to which they obviously
5962306a36Sopenharmony_ci * shouldn't be ever gated. SATA and PCIe PLLs are the parents of APB-bus and
6062306a36Sopenharmony_ci * DDR controller AXI-bus clocks. If they are gated the system will be
6162306a36Sopenharmony_ci * unusable. Moreover disabling SATA and Ethernet PLLs causes automatic reset
6262306a36Sopenharmony_ci * of the corresponding subsystems. So until we aren't ready to re-initialize
6362306a36Sopenharmony_ci * all the devices consuming those PLLs, they will be marked as critical too.
6462306a36Sopenharmony_ci */
6562306a36Sopenharmony_cistatic const struct ccu_pll_info pll_info[] = {
6662306a36Sopenharmony_ci	CCU_PLL_INFO(CCU_CPU_PLL, "cpu_pll", "ref_clk", CCU_CPU_PLL_BASE,
6762306a36Sopenharmony_ci		     CLK_IS_CRITICAL, CCU_PLL_BASIC),
6862306a36Sopenharmony_ci	CCU_PLL_INFO(CCU_SATA_PLL, "sata_pll", "ref_clk", CCU_SATA_PLL_BASE,
6962306a36Sopenharmony_ci		     CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0),
7062306a36Sopenharmony_ci	CCU_PLL_INFO(CCU_DDR_PLL, "ddr_pll", "ref_clk", CCU_DDR_PLL_BASE,
7162306a36Sopenharmony_ci		     CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0),
7262306a36Sopenharmony_ci	CCU_PLL_INFO(CCU_PCIE_PLL, "pcie_pll", "ref_clk", CCU_PCIE_PLL_BASE,
7362306a36Sopenharmony_ci		     CLK_IS_CRITICAL, CCU_PLL_BASIC),
7462306a36Sopenharmony_ci	CCU_PLL_INFO(CCU_ETH_PLL, "eth_pll", "ref_clk", CCU_ETH_PLL_BASE,
7562306a36Sopenharmony_ci		     CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0)
7662306a36Sopenharmony_ci};
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistruct ccu_pll_data {
7962306a36Sopenharmony_ci	struct device_node *np;
8062306a36Sopenharmony_ci	struct regmap *sys_regs;
8162306a36Sopenharmony_ci	struct ccu_pll *plls[CCU_PLL_NUM];
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic struct ccu_pll_data *pll_data;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic struct ccu_pll *ccu_pll_find_desc(struct ccu_pll_data *data,
8762306a36Sopenharmony_ci					 unsigned int clk_id)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	int idx;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
9262306a36Sopenharmony_ci		if (pll_info[idx].id == clk_id)
9362306a36Sopenharmony_ci			return data->plls[idx];
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	return ERR_PTR(-EINVAL);
9762306a36Sopenharmony_ci}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_cistatic struct ccu_pll_data *ccu_pll_create_data(struct device_node *np)
10062306a36Sopenharmony_ci{
10162306a36Sopenharmony_ci	struct ccu_pll_data *data;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	data = kzalloc(sizeof(*data), GFP_KERNEL);
10462306a36Sopenharmony_ci	if (!data)
10562306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	data->np = np;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	return data;
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic void ccu_pll_free_data(struct ccu_pll_data *data)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	kfree(data);
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic int ccu_pll_find_sys_regs(struct ccu_pll_data *data)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	data->sys_regs = syscon_node_to_regmap(data->np->parent);
12062306a36Sopenharmony_ci	if (IS_ERR(data->sys_regs)) {
12162306a36Sopenharmony_ci		pr_err("Failed to find syscon regs for '%s'\n",
12262306a36Sopenharmony_ci			of_node_full_name(data->np));
12362306a36Sopenharmony_ci		return PTR_ERR(data->sys_regs);
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return 0;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic struct clk_hw *ccu_pll_of_clk_hw_get(struct of_phandle_args *clkspec,
13062306a36Sopenharmony_ci					    void *priv)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct ccu_pll_data *data = priv;
13362306a36Sopenharmony_ci	struct ccu_pll *pll;
13462306a36Sopenharmony_ci	unsigned int clk_id;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	clk_id = clkspec->args[0];
13762306a36Sopenharmony_ci	pll = ccu_pll_find_desc(data, clk_id);
13862306a36Sopenharmony_ci	if (IS_ERR(pll)) {
13962306a36Sopenharmony_ci		if (pll != ERR_PTR(-EPROBE_DEFER))
14062306a36Sopenharmony_ci			pr_info("Invalid PLL clock ID %d specified\n", clk_id);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci		return ERR_CAST(pll);
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	return ccu_pll_get_clk_hw(pll);
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic int ccu_pll_clk_register(struct ccu_pll_data *data, bool defer)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	int idx, ret;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
15362306a36Sopenharmony_ci		const struct ccu_pll_info *info = &pll_info[idx];
15462306a36Sopenharmony_ci		struct ccu_pll_init_data init = {0};
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci		/* Defer non-basic PLLs allocation for the probe stage */
15762306a36Sopenharmony_ci		if (!!(info->features & CCU_PLL_BASIC) ^ defer) {
15862306a36Sopenharmony_ci			if (!data->plls[idx])
15962306a36Sopenharmony_ci				data->plls[idx] = ERR_PTR(-EPROBE_DEFER);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci			continue;
16262306a36Sopenharmony_ci		}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci		init.id = info->id;
16562306a36Sopenharmony_ci		init.name = info->name;
16662306a36Sopenharmony_ci		init.parent_name = info->parent_name;
16762306a36Sopenharmony_ci		init.base = info->base;
16862306a36Sopenharmony_ci		init.sys_regs = data->sys_regs;
16962306a36Sopenharmony_ci		init.np = data->np;
17062306a36Sopenharmony_ci		init.flags = info->flags;
17162306a36Sopenharmony_ci		init.features = info->features;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci		data->plls[idx] = ccu_pll_hw_register(&init);
17462306a36Sopenharmony_ci		if (IS_ERR(data->plls[idx])) {
17562306a36Sopenharmony_ci			ret = PTR_ERR(data->plls[idx]);
17662306a36Sopenharmony_ci			pr_err("Couldn't register PLL hw '%s'\n",
17762306a36Sopenharmony_ci				init.name);
17862306a36Sopenharmony_ci			goto err_hw_unregister;
17962306a36Sopenharmony_ci		}
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	return 0;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cierr_hw_unregister:
18562306a36Sopenharmony_ci	for (--idx; idx >= 0; --idx) {
18662306a36Sopenharmony_ci		if (!!(pll_info[idx].features & CCU_PLL_BASIC) ^ defer)
18762306a36Sopenharmony_ci			continue;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci		ccu_pll_hw_unregister(data->plls[idx]);
19062306a36Sopenharmony_ci	}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	return ret;
19362306a36Sopenharmony_ci}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic void ccu_pll_clk_unregister(struct ccu_pll_data *data, bool defer)
19662306a36Sopenharmony_ci{
19762306a36Sopenharmony_ci	int idx;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	/* Uninstall only the clocks registered on the specfied stage */
20062306a36Sopenharmony_ci	for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
20162306a36Sopenharmony_ci		if (!!(pll_info[idx].features & CCU_PLL_BASIC) ^ defer)
20262306a36Sopenharmony_ci			continue;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci		ccu_pll_hw_unregister(data->plls[idx]);
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cistatic int ccu_pll_of_register(struct ccu_pll_data *data)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	int ret;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	ret = of_clk_add_hw_provider(data->np, ccu_pll_of_clk_hw_get, data);
21362306a36Sopenharmony_ci	if (ret) {
21462306a36Sopenharmony_ci		pr_err("Couldn't register PLL provider of '%s'\n",
21562306a36Sopenharmony_ci			of_node_full_name(data->np));
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	return ret;
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic int ccu_pll_probe(struct platform_device *pdev)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	struct ccu_pll_data *data = pll_data;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	if (!data)
22662306a36Sopenharmony_ci		return -EINVAL;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	return ccu_pll_clk_register(data, false);
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic const struct of_device_id ccu_pll_of_match[] = {
23262306a36Sopenharmony_ci	{ .compatible = "baikal,bt1-ccu-pll" },
23362306a36Sopenharmony_ci	{ }
23462306a36Sopenharmony_ci};
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic struct platform_driver ccu_pll_driver = {
23762306a36Sopenharmony_ci	.probe  = ccu_pll_probe,
23862306a36Sopenharmony_ci	.driver = {
23962306a36Sopenharmony_ci		.name = "clk-ccu-pll",
24062306a36Sopenharmony_ci		.of_match_table = ccu_pll_of_match,
24162306a36Sopenharmony_ci		.suppress_bind_attrs = true,
24262306a36Sopenharmony_ci	},
24362306a36Sopenharmony_ci};
24462306a36Sopenharmony_cibuiltin_platform_driver(ccu_pll_driver);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cistatic __init void ccu_pll_init(struct device_node *np)
24762306a36Sopenharmony_ci{
24862306a36Sopenharmony_ci	struct ccu_pll_data *data;
24962306a36Sopenharmony_ci	int ret;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	data = ccu_pll_create_data(np);
25262306a36Sopenharmony_ci	if (IS_ERR(data))
25362306a36Sopenharmony_ci		return;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	ret = ccu_pll_find_sys_regs(data);
25662306a36Sopenharmony_ci	if (ret)
25762306a36Sopenharmony_ci		goto err_free_data;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	ret = ccu_pll_clk_register(data, true);
26062306a36Sopenharmony_ci	if (ret)
26162306a36Sopenharmony_ci		goto err_free_data;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	ret = ccu_pll_of_register(data);
26462306a36Sopenharmony_ci	if (ret)
26562306a36Sopenharmony_ci		goto err_clk_unregister;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	pll_data = data;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	return;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_cierr_clk_unregister:
27262306a36Sopenharmony_ci	ccu_pll_clk_unregister(data, true);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cierr_free_data:
27562306a36Sopenharmony_ci	ccu_pll_free_data(data);
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ciCLK_OF_DECLARE_DRIVER(ccu_pll, "baikal,bt1-ccu-pll", ccu_pll_init);
278