linux/drivers/clk/qcom/clk-cpu-8996.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright (c) 2020, The Linux Foundation. All rights reserved.
   4 */
   5
   6/*
   7 * Each of the CPU clusters (Power and Perf) on msm8996 are
   8 * clocked via 2 PLLs, a primary and alternate. There are also
   9 * 2 Mux'es, a primary and secondary all connected together
  10 * as shown below
  11 *
  12 *                              +-------+
  13 *               XO             |       |
  14 *           +------------------>0      |
  15 *                              |       |
  16 *                    PLL/2     | SMUX  +----+
  17 *                      +------->1      |    |
  18 *                      |       |       |    |
  19 *                      |       +-------+    |    +-------+
  20 *                      |                    +---->0      |
  21 *                      |                         |       |
  22 * +---------------+    |             +----------->1      | CPU clk
  23 * |Primary PLL    +----+ PLL_EARLY   |           |       +------>
  24 * |               +------+-----------+    +------>2 PMUX |
  25 * +---------------+      |                |      |       |
  26 *                        |   +------+     |   +-->3      |
  27 *                        +--^+  ACD +-----+   |  +-------+
  28 * +---------------+          +------+         |
  29 * |Alt PLL        |                           |
  30 * |               +---------------------------+
  31 * +---------------+         PLL_EARLY
  32 *
  33 * The primary PLL is what drives the CPU clk, except for times
  34 * when we are reprogramming the PLL itself (for rate changes) when
  35 * we temporarily switch to an alternate PLL.
  36 *
  37 * The primary PLL operates on a single VCO range, between 600MHz
  38 * and 3GHz. However the CPUs do support OPPs with frequencies
  39 * between 300MHz and 600MHz. In order to support running the CPUs
  40 * at those frequencies we end up having to lock the PLL at twice
  41 * the rate and drive the CPU clk via the PLL/2 output and SMUX.
  42 *
  43 * So for frequencies above 600MHz we follow the following path
  44 *  Primary PLL --> PLL_EARLY --> PMUX(1) --> CPU clk
  45 * and for frequencies between 300MHz and 600MHz we follow
  46 *  Primary PLL --> PLL/2 --> SMUX(1) --> PMUX(0) --> CPU clk
  47 *
  48 * ACD stands for Adaptive Clock Distribution and is used to
  49 * detect voltage droops.
  50 */
  51
  52#include <linux/clk.h>
  53#include <linux/clk-provider.h>
  54#include <linux/io.h>
  55#include <linux/module.h>
  56#include <linux/platform_device.h>
  57#include <linux/regmap.h>
  58#include <soc/qcom/kryo-l2-accessors.h>
  59
  60#include "clk-alpha-pll.h"
  61#include "clk-regmap.h"
  62
  63enum _pmux_input {
  64        DIV_2_INDEX = 0,
  65        PLL_INDEX,
  66        ACD_INDEX,
  67        ALT_INDEX,
  68        NUM_OF_PMUX_INPUTS
  69};
  70
  71#define DIV_2_THRESHOLD         600000000
  72#define PWRCL_REG_OFFSET 0x0
  73#define PERFCL_REG_OFFSET 0x80000
  74#define MUX_OFFSET      0x40
  75#define ALT_PLL_OFFSET  0x100
  76#define SSSCTL_OFFSET 0x160
  77
  78static const u8 prim_pll_regs[PLL_OFF_MAX_REGS] = {
  79        [PLL_OFF_L_VAL] = 0x04,
  80        [PLL_OFF_ALPHA_VAL] = 0x08,
  81        [PLL_OFF_USER_CTL] = 0x10,
  82        [PLL_OFF_CONFIG_CTL] = 0x18,
  83        [PLL_OFF_CONFIG_CTL_U] = 0x1c,
  84        [PLL_OFF_TEST_CTL] = 0x20,
  85        [PLL_OFF_TEST_CTL_U] = 0x24,
  86        [PLL_OFF_STATUS] = 0x28,
  87};
  88
  89static const u8 alt_pll_regs[PLL_OFF_MAX_REGS] = {
  90        [PLL_OFF_L_VAL] = 0x04,
  91        [PLL_OFF_ALPHA_VAL] = 0x08,
  92        [PLL_OFF_ALPHA_VAL_U] = 0x0c,
  93        [PLL_OFF_USER_CTL] = 0x10,
  94        [PLL_OFF_USER_CTL_U] = 0x14,
  95        [PLL_OFF_CONFIG_CTL] = 0x18,
  96        [PLL_OFF_TEST_CTL] = 0x20,
  97        [PLL_OFF_TEST_CTL_U] = 0x24,
  98        [PLL_OFF_STATUS] = 0x28,
  99};
 100
 101/* PLLs */
 102
 103static const struct alpha_pll_config hfpll_config = {
 104        .l = 60,
 105        .config_ctl_val = 0x200d4aa8,
 106        .config_ctl_hi_val = 0x006,
 107        .pre_div_mask = BIT(12),
 108        .post_div_mask = 0x3 << 8,
 109        .post_div_val = 0x1 << 8,
 110        .main_output_mask = BIT(0),
 111        .early_output_mask = BIT(3),
 112};
 113
 114static struct clk_alpha_pll perfcl_pll = {
 115        .offset = PERFCL_REG_OFFSET,
 116        .regs = prim_pll_regs,
 117        .flags = SUPPORTS_DYNAMIC_UPDATE | SUPPORTS_FSM_MODE,
 118        .clkr.hw.init = &(struct clk_init_data){
 119                .name = "perfcl_pll",
 120                .parent_names = (const char *[]){ "xo" },
 121                .num_parents = 1,
 122                .ops = &clk_alpha_pll_huayra_ops,
 123        },
 124};
 125
 126static struct clk_alpha_pll pwrcl_pll = {
 127        .offset = PWRCL_REG_OFFSET,
 128        .regs = prim_pll_regs,
 129        .flags = SUPPORTS_DYNAMIC_UPDATE | SUPPORTS_FSM_MODE,
 130        .clkr.hw.init = &(struct clk_init_data){
 131                .name = "pwrcl_pll",
 132                .parent_names = (const char *[]){ "xo" },
 133                .num_parents = 1,
 134                .ops = &clk_alpha_pll_huayra_ops,
 135        },
 136};
 137
 138static const struct pll_vco alt_pll_vco_modes[] = {
 139        VCO(3,  250000000,  500000000),
 140        VCO(2,  500000000,  750000000),
 141        VCO(1,  750000000, 1000000000),
 142        VCO(0, 1000000000, 2150400000),
 143};
 144
 145static const struct alpha_pll_config altpll_config = {
 146        .l = 16,
 147        .vco_val = 0x3 << 20,
 148        .vco_mask = 0x3 << 20,
 149        .config_ctl_val = 0x4001051b,
 150        .post_div_mask = 0x3 << 8,
 151        .post_div_val = 0x1 << 8,
 152        .main_output_mask = BIT(0),
 153        .early_output_mask = BIT(3),
 154};
 155
 156static struct clk_alpha_pll perfcl_alt_pll = {
 157        .offset = PERFCL_REG_OFFSET + ALT_PLL_OFFSET,
 158        .regs = alt_pll_regs,
 159        .vco_table = alt_pll_vco_modes,
 160        .num_vco = ARRAY_SIZE(alt_pll_vco_modes),
 161        .flags = SUPPORTS_OFFLINE_REQ | SUPPORTS_FSM_MODE,
 162        .clkr.hw.init = &(struct clk_init_data) {
 163                .name = "perfcl_alt_pll",
 164                .parent_names = (const char *[]){ "xo" },
 165                .num_parents = 1,
 166                .ops = &clk_alpha_pll_hwfsm_ops,
 167        },
 168};
 169
 170static struct clk_alpha_pll pwrcl_alt_pll = {
 171        .offset = PWRCL_REG_OFFSET + ALT_PLL_OFFSET,
 172        .regs = alt_pll_regs,
 173        .vco_table = alt_pll_vco_modes,
 174        .num_vco = ARRAY_SIZE(alt_pll_vco_modes),
 175        .flags = SUPPORTS_OFFLINE_REQ | SUPPORTS_FSM_MODE,
 176        .clkr.hw.init = &(struct clk_init_data) {
 177                .name = "pwrcl_alt_pll",
 178                .parent_names = (const char *[]){ "xo" },
 179                .num_parents = 1,
 180                .ops = &clk_alpha_pll_hwfsm_ops,
 181        },
 182};
 183
 184struct clk_cpu_8996_mux {
 185        u32     reg;
 186        u8      shift;
 187        u8      width;
 188        struct notifier_block nb;
 189        struct clk_hw   *pll;
 190        struct clk_hw   *pll_div_2;
 191        struct clk_regmap clkr;
 192};
 193
 194static int cpu_clk_notifier_cb(struct notifier_block *nb, unsigned long event,
 195                               void *data);
 196
 197#define to_clk_cpu_8996_mux_nb(_nb) \
 198        container_of(_nb, struct clk_cpu_8996_mux, nb)
 199
 200static inline struct clk_cpu_8996_mux *to_clk_cpu_8996_mux_hw(struct clk_hw *hw)
 201{
 202        return container_of(to_clk_regmap(hw), struct clk_cpu_8996_mux, clkr);
 203}
 204
 205static u8 clk_cpu_8996_mux_get_parent(struct clk_hw *hw)
 206{
 207        struct clk_regmap *clkr = to_clk_regmap(hw);
 208        struct clk_cpu_8996_mux *cpuclk = to_clk_cpu_8996_mux_hw(hw);
 209        u32 mask = GENMASK(cpuclk->width - 1, 0);
 210        u32 val;
 211
 212        regmap_read(clkr->regmap, cpuclk->reg, &val);
 213        val >>= cpuclk->shift;
 214
 215        return val & mask;
 216}
 217
 218static int clk_cpu_8996_mux_set_parent(struct clk_hw *hw, u8 index)
 219{
 220        struct clk_regmap *clkr = to_clk_regmap(hw);
 221        struct clk_cpu_8996_mux *cpuclk = to_clk_cpu_8996_mux_hw(hw);
 222        u32 mask = GENMASK(cpuclk->width + cpuclk->shift - 1, cpuclk->shift);
 223        u32 val;
 224
 225        val = index;
 226        val <<= cpuclk->shift;
 227
 228        return regmap_update_bits(clkr->regmap, cpuclk->reg, mask, val);
 229}
 230
 231static int clk_cpu_8996_mux_determine_rate(struct clk_hw *hw,
 232                                           struct clk_rate_request *req)
 233{
 234        struct clk_cpu_8996_mux *cpuclk = to_clk_cpu_8996_mux_hw(hw);
 235        struct clk_hw *parent = cpuclk->pll;
 236
 237        if (cpuclk->pll_div_2 && req->rate < DIV_2_THRESHOLD) {
 238                if (req->rate < (DIV_2_THRESHOLD / 2))
 239                        return -EINVAL;
 240
 241                parent = cpuclk->pll_div_2;
 242        }
 243
 244        req->best_parent_rate = clk_hw_round_rate(parent, req->rate);
 245        req->best_parent_hw = parent;
 246
 247        return 0;
 248}
 249
 250static const struct clk_ops clk_cpu_8996_mux_ops = {
 251        .set_parent = clk_cpu_8996_mux_set_parent,
 252        .get_parent = clk_cpu_8996_mux_get_parent,
 253        .determine_rate = clk_cpu_8996_mux_determine_rate,
 254};
 255
 256static struct clk_cpu_8996_mux pwrcl_smux = {
 257        .reg = PWRCL_REG_OFFSET + MUX_OFFSET,
 258        .shift = 2,
 259        .width = 2,
 260        .clkr.hw.init = &(struct clk_init_data) {
 261                .name = "pwrcl_smux",
 262                .parent_names = (const char *[]){
 263                        "xo",
 264                        "pwrcl_pll_main",
 265                },
 266                .num_parents = 2,
 267                .ops = &clk_cpu_8996_mux_ops,
 268                .flags = CLK_SET_RATE_PARENT,
 269        },
 270};
 271
 272static struct clk_cpu_8996_mux perfcl_smux = {
 273        .reg = PERFCL_REG_OFFSET + MUX_OFFSET,
 274        .shift = 2,
 275        .width = 2,
 276        .clkr.hw.init = &(struct clk_init_data) {
 277                .name = "perfcl_smux",
 278                .parent_names = (const char *[]){
 279                        "xo",
 280                        "perfcl_pll_main",
 281                },
 282                .num_parents = 2,
 283                .ops = &clk_cpu_8996_mux_ops,
 284                .flags = CLK_SET_RATE_PARENT,
 285        },
 286};
 287
 288static struct clk_cpu_8996_mux pwrcl_pmux = {
 289        .reg = PWRCL_REG_OFFSET + MUX_OFFSET,
 290        .shift = 0,
 291        .width = 2,
 292        .pll = &pwrcl_pll.clkr.hw,
 293        .pll_div_2 = &pwrcl_smux.clkr.hw,
 294        .nb.notifier_call = cpu_clk_notifier_cb,
 295        .clkr.hw.init = &(struct clk_init_data) {
 296                .name = "pwrcl_pmux",
 297                .parent_names = (const char *[]){
 298                        "pwrcl_smux",
 299                        "pwrcl_pll",
 300                        "pwrcl_pll_acd",
 301                        "pwrcl_alt_pll",
 302                },
 303                .num_parents = 4,
 304                .ops = &clk_cpu_8996_mux_ops,
 305                /* CPU clock is critical and should never be gated */
 306                .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
 307        },
 308};
 309
 310static struct clk_cpu_8996_mux perfcl_pmux = {
 311        .reg = PERFCL_REG_OFFSET + MUX_OFFSET,
 312        .shift = 0,
 313        .width = 2,
 314        .pll = &perfcl_pll.clkr.hw,
 315        .pll_div_2 = &perfcl_smux.clkr.hw,
 316        .nb.notifier_call = cpu_clk_notifier_cb,
 317        .clkr.hw.init = &(struct clk_init_data) {
 318                .name = "perfcl_pmux",
 319                .parent_names = (const char *[]){
 320                        "perfcl_smux",
 321                        "perfcl_pll",
 322                        "perfcl_pll_acd",
 323                        "perfcl_alt_pll",
 324                },
 325                .num_parents = 4,
 326                .ops = &clk_cpu_8996_mux_ops,
 327                /* CPU clock is critical and should never be gated */
 328                .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
 329        },
 330};
 331
 332static const struct regmap_config cpu_msm8996_regmap_config = {
 333        .reg_bits               = 32,
 334        .reg_stride             = 4,
 335        .val_bits               = 32,
 336        .max_register           = 0x80210,
 337        .fast_io                = true,
 338        .val_format_endian      = REGMAP_ENDIAN_LITTLE,
 339};
 340
 341static struct clk_regmap *cpu_msm8996_clks[] = {
 342        &perfcl_pll.clkr,
 343        &pwrcl_pll.clkr,
 344        &perfcl_alt_pll.clkr,
 345        &pwrcl_alt_pll.clkr,
 346        &perfcl_smux.clkr,
 347        &pwrcl_smux.clkr,
 348        &perfcl_pmux.clkr,
 349        &pwrcl_pmux.clkr,
 350};
 351
 352static int qcom_cpu_clk_msm8996_register_clks(struct device *dev,
 353                                              struct regmap *regmap)
 354{
 355        int i, ret;
 356
 357        perfcl_smux.pll = clk_hw_register_fixed_factor(dev, "perfcl_pll_main",
 358                                                       "perfcl_pll",
 359                                                       CLK_SET_RATE_PARENT,
 360                                                       1, 2);
 361        if (IS_ERR(perfcl_smux.pll)) {
 362                dev_err(dev, "Failed to initialize perfcl_pll_main\n");
 363                return PTR_ERR(perfcl_smux.pll);
 364        }
 365
 366        pwrcl_smux.pll = clk_hw_register_fixed_factor(dev, "pwrcl_pll_main",
 367                                                      "pwrcl_pll",
 368                                                      CLK_SET_RATE_PARENT,
 369                                                      1, 2);
 370        if (IS_ERR(pwrcl_smux.pll)) {
 371                dev_err(dev, "Failed to initialize pwrcl_pll_main\n");
 372                clk_hw_unregister(perfcl_smux.pll);
 373                return PTR_ERR(pwrcl_smux.pll);
 374        }
 375
 376        for (i = 0; i < ARRAY_SIZE(cpu_msm8996_clks); i++) {
 377                ret = devm_clk_register_regmap(dev, cpu_msm8996_clks[i]);
 378                if (ret) {
 379                        clk_hw_unregister(perfcl_smux.pll);
 380                        clk_hw_unregister(pwrcl_smux.pll);
 381                        return ret;
 382                }
 383        }
 384
 385        clk_alpha_pll_configure(&perfcl_pll, regmap, &hfpll_config);
 386        clk_alpha_pll_configure(&pwrcl_pll, regmap, &hfpll_config);
 387        clk_alpha_pll_configure(&perfcl_alt_pll, regmap, &altpll_config);
 388        clk_alpha_pll_configure(&pwrcl_alt_pll, regmap, &altpll_config);
 389
 390        /* Enable alt PLLs */
 391        clk_prepare_enable(pwrcl_alt_pll.clkr.hw.clk);
 392        clk_prepare_enable(perfcl_alt_pll.clkr.hw.clk);
 393
 394        clk_notifier_register(pwrcl_pmux.clkr.hw.clk, &pwrcl_pmux.nb);
 395        clk_notifier_register(perfcl_pmux.clkr.hw.clk, &perfcl_pmux.nb);
 396
 397        return ret;
 398}
 399
 400static int qcom_cpu_clk_msm8996_unregister_clks(void)
 401{
 402        int ret = 0;
 403
 404        ret = clk_notifier_unregister(pwrcl_pmux.clkr.hw.clk, &pwrcl_pmux.nb);
 405        if (ret)
 406                return ret;
 407
 408        ret = clk_notifier_unregister(perfcl_pmux.clkr.hw.clk, &perfcl_pmux.nb);
 409        if (ret)
 410                return ret;
 411
 412        clk_hw_unregister(perfcl_smux.pll);
 413        clk_hw_unregister(pwrcl_smux.pll);
 414
 415        return 0;
 416}
 417
 418#define CPU_AFINITY_MASK 0xFFF
 419#define PWRCL_CPU_REG_MASK 0x3
 420#define PERFCL_CPU_REG_MASK 0x103
 421
 422#define L2ACDCR_REG 0x580ULL
 423#define L2ACDTD_REG 0x581ULL
 424#define L2ACDDVMRC_REG 0x584ULL
 425#define L2ACDSSCR_REG 0x589ULL
 426
 427static DEFINE_SPINLOCK(qcom_clk_acd_lock);
 428static void __iomem *base;
 429
 430static void qcom_cpu_clk_msm8996_acd_init(void __iomem *base)
 431{
 432        u64 hwid;
 433        unsigned long flags;
 434
 435        spin_lock_irqsave(&qcom_clk_acd_lock, flags);
 436
 437        hwid = read_cpuid_mpidr() & CPU_AFINITY_MASK;
 438
 439        kryo_l2_set_indirect_reg(L2ACDTD_REG, 0x00006a11);
 440        kryo_l2_set_indirect_reg(L2ACDDVMRC_REG, 0x000e0f0f);
 441        kryo_l2_set_indirect_reg(L2ACDSSCR_REG, 0x00000601);
 442
 443        if (PWRCL_CPU_REG_MASK == (hwid | PWRCL_CPU_REG_MASK)) {
 444                writel(0xf, base + PWRCL_REG_OFFSET + SSSCTL_OFFSET);
 445                kryo_l2_set_indirect_reg(L2ACDCR_REG, 0x002c5ffd);
 446        }
 447
 448        if (PERFCL_CPU_REG_MASK == (hwid | PERFCL_CPU_REG_MASK)) {
 449                kryo_l2_set_indirect_reg(L2ACDCR_REG, 0x002c5ffd);
 450                writel(0xf, base + PERFCL_REG_OFFSET + SSSCTL_OFFSET);
 451        }
 452
 453        spin_unlock_irqrestore(&qcom_clk_acd_lock, flags);
 454}
 455
 456static int cpu_clk_notifier_cb(struct notifier_block *nb, unsigned long event,
 457                               void *data)
 458{
 459        struct clk_cpu_8996_mux *cpuclk = to_clk_cpu_8996_mux_nb(nb);
 460        struct clk_notifier_data *cnd = data;
 461        int ret;
 462
 463        switch (event) {
 464        case PRE_RATE_CHANGE:
 465                ret = clk_cpu_8996_mux_set_parent(&cpuclk->clkr.hw, ALT_INDEX);
 466                qcom_cpu_clk_msm8996_acd_init(base);
 467                break;
 468        case POST_RATE_CHANGE:
 469                if (cnd->new_rate < DIV_2_THRESHOLD)
 470                        ret = clk_cpu_8996_mux_set_parent(&cpuclk->clkr.hw,
 471                                                          DIV_2_INDEX);
 472                else
 473                        ret = clk_cpu_8996_mux_set_parent(&cpuclk->clkr.hw,
 474                                                          ACD_INDEX);
 475                break;
 476        default:
 477                ret = 0;
 478                break;
 479        }
 480
 481        return notifier_from_errno(ret);
 482};
 483
 484static int qcom_cpu_clk_msm8996_driver_probe(struct platform_device *pdev)
 485{
 486        struct regmap *regmap;
 487        struct clk_hw_onecell_data *data;
 488        struct device *dev = &pdev->dev;
 489        int ret;
 490
 491        data = devm_kzalloc(dev, struct_size(data, hws, 2), GFP_KERNEL);
 492        if (!data)
 493                return -ENOMEM;
 494
 495        base = devm_platform_ioremap_resource(pdev, 0);
 496        if (IS_ERR(base))
 497                return PTR_ERR(base);
 498
 499        regmap = devm_regmap_init_mmio(dev, base, &cpu_msm8996_regmap_config);
 500        if (IS_ERR(regmap))
 501                return PTR_ERR(regmap);
 502
 503        ret = qcom_cpu_clk_msm8996_register_clks(dev, regmap);
 504        if (ret)
 505                return ret;
 506
 507        qcom_cpu_clk_msm8996_acd_init(base);
 508
 509        data->hws[0] = &pwrcl_pmux.clkr.hw;
 510        data->hws[1] = &perfcl_pmux.clkr.hw;
 511        data->num = 2;
 512
 513        return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, data);
 514}
 515
 516static int qcom_cpu_clk_msm8996_driver_remove(struct platform_device *pdev)
 517{
 518        return qcom_cpu_clk_msm8996_unregister_clks();
 519}
 520
 521static const struct of_device_id qcom_cpu_clk_msm8996_match_table[] = {
 522        { .compatible = "qcom,msm8996-apcc" },
 523        {}
 524};
 525MODULE_DEVICE_TABLE(of, qcom_cpu_clk_msm8996_match_table);
 526
 527static struct platform_driver qcom_cpu_clk_msm8996_driver = {
 528        .probe = qcom_cpu_clk_msm8996_driver_probe,
 529        .remove = qcom_cpu_clk_msm8996_driver_remove,
 530        .driver = {
 531                .name = "qcom-msm8996-apcc",
 532                .of_match_table = qcom_cpu_clk_msm8996_match_table,
 533        },
 534};
 535module_platform_driver(qcom_cpu_clk_msm8996_driver);
 536
 537MODULE_DESCRIPTION("QCOM MSM8996 CPU Clock Driver");
 538MODULE_LICENSE("GPL v2");
 539
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.