linux/drivers/clk/baikal-t1/clk-ccu-pll.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
   4 *
   5 * Authors:
   6 *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
   7 *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
   8 *
   9 * Baikal-T1 CCU PLL clocks driver
  10 */
  11
  12#define pr_fmt(fmt) "bt1-ccu-pll: " fmt
  13
  14#include <linux/kernel.h>
  15#include <linux/printk.h>
  16#include <linux/slab.h>
  17#include <linux/clk-provider.h>
  18#include <linux/mfd/syscon.h>
  19#include <linux/of.h>
  20#include <linux/of_address.h>
  21#include <linux/ioport.h>
  22#include <linux/regmap.h>
  23
  24#include <dt-bindings/clock/bt1-ccu.h>
  25
  26#include "ccu-pll.h"
  27
  28#define CCU_CPU_PLL_BASE                0x000
  29#define CCU_SATA_PLL_BASE               0x008
  30#define CCU_DDR_PLL_BASE                0x010
  31#define CCU_PCIE_PLL_BASE               0x018
  32#define CCU_ETH_PLL_BASE                0x020
  33
  34#define CCU_PLL_INFO(_id, _name, _pname, _base, _flags) \
  35        {                                               \
  36                .id = _id,                              \
  37                .name = _name,                          \
  38                .parent_name = _pname,                  \
  39                .base = _base,                          \
  40                .flags = _flags                         \
  41        }
  42
  43#define CCU_PLL_NUM                     ARRAY_SIZE(pll_info)
  44
  45struct ccu_pll_info {
  46        unsigned int id;
  47        const char *name;
  48        const char *parent_name;
  49        unsigned int base;
  50        unsigned long flags;
  51};
  52
  53/*
  54 * Alas we have to mark all PLLs as critical. CPU and DDR PLLs are sources of
  55 * CPU cores and DDR controller reference clocks, due to which they obviously
  56 * shouldn't be ever gated. SATA and PCIe PLLs are the parents of APB-bus and
  57 * DDR controller AXI-bus clocks. If they are gated the system will be
  58 * unusable. Moreover disabling SATA and Ethernet PLLs causes automatic reset
  59 * of the corresponding subsystems. So until we aren't ready to re-initialize
  60 * all the devices consuming those PLLs, they will be marked as critical too.
  61 */
  62static const struct ccu_pll_info pll_info[] = {
  63        CCU_PLL_INFO(CCU_CPU_PLL, "cpu_pll", "ref_clk", CCU_CPU_PLL_BASE,
  64                     CLK_IS_CRITICAL),
  65        CCU_PLL_INFO(CCU_SATA_PLL, "sata_pll", "ref_clk", CCU_SATA_PLL_BASE,
  66                     CLK_IS_CRITICAL | CLK_SET_RATE_GATE),
  67        CCU_PLL_INFO(CCU_DDR_PLL, "ddr_pll", "ref_clk", CCU_DDR_PLL_BASE,
  68                     CLK_IS_CRITICAL | CLK_SET_RATE_GATE),
  69        CCU_PLL_INFO(CCU_PCIE_PLL, "pcie_pll", "ref_clk", CCU_PCIE_PLL_BASE,
  70                     CLK_IS_CRITICAL),
  71        CCU_PLL_INFO(CCU_ETH_PLL, "eth_pll", "ref_clk", CCU_ETH_PLL_BASE,
  72                     CLK_IS_CRITICAL | CLK_SET_RATE_GATE)
  73};
  74
  75struct ccu_pll_data {
  76        struct device_node *np;
  77        struct regmap *sys_regs;
  78        struct ccu_pll *plls[CCU_PLL_NUM];
  79};
  80
  81static struct ccu_pll *ccu_pll_find_desc(struct ccu_pll_data *data,
  82                                         unsigned int clk_id)
  83{
  84        struct ccu_pll *pll;
  85        int idx;
  86
  87        for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
  88                pll = data->plls[idx];
  89                if (pll && pll->id == clk_id)
  90                        return pll;
  91        }
  92
  93        return ERR_PTR(-EINVAL);
  94}
  95
  96static struct ccu_pll_data *ccu_pll_create_data(struct device_node *np)
  97{
  98        struct ccu_pll_data *data;
  99
 100        data = kzalloc(sizeof(*data), GFP_KERNEL);
 101        if (!data)
 102                return ERR_PTR(-ENOMEM);
 103
 104        data->np = np;
 105
 106        return data;
 107}
 108
 109static void ccu_pll_free_data(struct ccu_pll_data *data)
 110{
 111        kfree(data);
 112}
 113
 114static int ccu_pll_find_sys_regs(struct ccu_pll_data *data)
 115{
 116        data->sys_regs = syscon_node_to_regmap(data->np->parent);
 117        if (IS_ERR(data->sys_regs)) {
 118                pr_err("Failed to find syscon regs for '%s'\n",
 119                        of_node_full_name(data->np));
 120                return PTR_ERR(data->sys_regs);
 121        }
 122
 123        return 0;
 124}
 125
 126static struct clk_hw *ccu_pll_of_clk_hw_get(struct of_phandle_args *clkspec,
 127                                            void *priv)
 128{
 129        struct ccu_pll_data *data = priv;
 130        struct ccu_pll *pll;
 131        unsigned int clk_id;
 132
 133        clk_id = clkspec->args[0];
 134        pll = ccu_pll_find_desc(data, clk_id);
 135        if (IS_ERR(pll)) {
 136                pr_info("Invalid PLL clock ID %d specified\n", clk_id);
 137                return ERR_CAST(pll);
 138        }
 139
 140        return ccu_pll_get_clk_hw(pll);
 141}
 142
 143static int ccu_pll_clk_register(struct ccu_pll_data *data)
 144{
 145        int idx, ret;
 146
 147        for (idx = 0; idx < CCU_PLL_NUM; ++idx) {
 148                const struct ccu_pll_info *info = &pll_info[idx];
 149                struct ccu_pll_init_data init = {0};
 150
 151                init.id = info->id;
 152                init.name = info->name;
 153                init.parent_name = info->parent_name;
 154                init.base = info->base;
 155                init.sys_regs = data->sys_regs;
 156                init.np = data->np;
 157                init.flags = info->flags;
 158
 159                data->plls[idx] = ccu_pll_hw_register(&init);
 160                if (IS_ERR(data->plls[idx])) {
 161                        ret = PTR_ERR(data->plls[idx]);
 162                        pr_err("Couldn't register PLL hw '%s'\n",
 163                                init.name);
 164                        goto err_hw_unregister;
 165                }
 166        }
 167
 168        ret = of_clk_add_hw_provider(data->np, ccu_pll_of_clk_hw_get, data);
 169        if (ret) {
 170                pr_err("Couldn't register PLL provider of '%s'\n",
 171                        of_node_full_name(data->np));
 172                goto err_hw_unregister;
 173        }
 174
 175        return 0;
 176
 177err_hw_unregister:
 178        for (--idx; idx >= 0; --idx)
 179                ccu_pll_hw_unregister(data->plls[idx]);
 180
 181        return ret;
 182}
 183
 184static __init void ccu_pll_init(struct device_node *np)
 185{
 186        struct ccu_pll_data *data;
 187        int ret;
 188
 189        data = ccu_pll_create_data(np);
 190        if (IS_ERR(data))
 191                return;
 192
 193        ret = ccu_pll_find_sys_regs(data);
 194        if (ret)
 195                goto err_free_data;
 196
 197        ret = ccu_pll_clk_register(data);
 198        if (ret)
 199                goto err_free_data;
 200
 201        return;
 202
 203err_free_data:
 204        ccu_pll_free_data(data);
 205}
 206CLK_OF_DECLARE(ccu_pll, "baikal,bt1-ccu-pll", ccu_pll_init);
 207
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.