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