linux/drivers/clk/bcm/clk-iproc-armpll.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2014 Broadcom Corporation
   3 *
   4 * This program is free software; you can redistribute it and/or
   5 * modify it under the terms of the GNU General Public License as
   6 * published by the Free Software Foundation version 2.
   7 *
   8 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
   9 * kind, whether express or implied; without even the implied warranty
  10 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11 * GNU General Public License for more details.
  12 */
  13
  14#include <linux/kernel.h>
  15#include <linux/slab.h>
  16#include <linux/err.h>
  17#include <linux/clk-provider.h>
  18#include <linux/io.h>
  19#include <linux/of.h>
  20#include <linux/clkdev.h>
  21#include <linux/of_address.h>
  22
  23#include "clk-iproc.h"
  24
  25#define IPROC_CLK_MAX_FREQ_POLICY                    0x3
  26#define IPROC_CLK_POLICY_FREQ_OFFSET                 0x008
  27#define IPROC_CLK_POLICY_FREQ_POLICY_FREQ_SHIFT      8
  28#define IPROC_CLK_POLICY_FREQ_POLICY_FREQ_MASK       0x7
  29
  30#define IPROC_CLK_PLLARMA_OFFSET                     0xc00
  31#define IPROC_CLK_PLLARMA_LOCK_SHIFT                 28
  32#define IPROC_CLK_PLLARMA_PDIV_SHIFT                 24
  33#define IPROC_CLK_PLLARMA_PDIV_MASK                  0xf
  34#define IPROC_CLK_PLLARMA_NDIV_INT_SHIFT             8
  35#define IPROC_CLK_PLLARMA_NDIV_INT_MASK              0x3ff
  36
  37#define IPROC_CLK_PLLARMB_OFFSET                     0xc04
  38#define IPROC_CLK_PLLARMB_NDIV_FRAC_MASK             0xfffff
  39
  40#define IPROC_CLK_PLLARMC_OFFSET                     0xc08
  41#define IPROC_CLK_PLLARMC_BYPCLK_EN_SHIFT            8
  42#define IPROC_CLK_PLLARMC_MDIV_MASK                  0xff
  43
  44#define IPROC_CLK_PLLARMCTL5_OFFSET                  0xc20
  45#define IPROC_CLK_PLLARMCTL5_H_MDIV_MASK             0xff
  46
  47#define IPROC_CLK_PLLARM_OFFSET_OFFSET               0xc24
  48#define IPROC_CLK_PLLARM_SW_CTL_SHIFT                29
  49#define IPROC_CLK_PLLARM_NDIV_INT_OFFSET_SHIFT       20
  50#define IPROC_CLK_PLLARM_NDIV_INT_OFFSET_MASK        0xff
  51#define IPROC_CLK_PLLARM_NDIV_FRAC_OFFSET_MASK       0xfffff
  52
  53#define IPROC_CLK_ARM_DIV_OFFSET                     0xe00
  54#define IPROC_CLK_ARM_DIV_PLL_SELECT_OVERRIDE_SHIFT  4
  55#define IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK        0xf
  56
  57#define IPROC_CLK_POLICY_DBG_OFFSET                  0xec0
  58#define IPROC_CLK_POLICY_DBG_ACT_FREQ_SHIFT          12
  59#define IPROC_CLK_POLICY_DBG_ACT_FREQ_MASK           0x7
  60
  61enum iproc_arm_pll_fid {
  62        ARM_PLL_FID_CRYSTAL_CLK   = 0,
  63        ARM_PLL_FID_SYS_CLK       = 2,
  64        ARM_PLL_FID_CH0_SLOW_CLK  = 6,
  65        ARM_PLL_FID_CH1_FAST_CLK  = 7
  66};
  67
  68struct iproc_arm_pll {
  69        struct clk_hw hw;
  70        void __iomem *base;
  71        unsigned long rate;
  72};
  73
  74#define to_iproc_arm_pll(hw) container_of(hw, struct iproc_arm_pll, hw)
  75
  76static unsigned int __get_fid(struct iproc_arm_pll *pll)
  77{
  78        u32 val;
  79        unsigned int policy, fid, active_fid;
  80
  81        val = readl(pll->base + IPROC_CLK_ARM_DIV_OFFSET);
  82        if (val & (1 << IPROC_CLK_ARM_DIV_PLL_SELECT_OVERRIDE_SHIFT))
  83                policy = val & IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK;
  84        else
  85                policy = 0;
  86
  87        /* something is seriously wrong */
  88        BUG_ON(policy > IPROC_CLK_MAX_FREQ_POLICY);
  89
  90        val = readl(pll->base + IPROC_CLK_POLICY_FREQ_OFFSET);
  91        fid = (val >> (IPROC_CLK_POLICY_FREQ_POLICY_FREQ_SHIFT * policy)) &
  92                IPROC_CLK_POLICY_FREQ_POLICY_FREQ_MASK;
  93
  94        val = readl(pll->base + IPROC_CLK_POLICY_DBG_OFFSET);
  95        active_fid = IPROC_CLK_POLICY_DBG_ACT_FREQ_MASK &
  96                (val >> IPROC_CLK_POLICY_DBG_ACT_FREQ_SHIFT);
  97        if (fid != active_fid) {
  98                pr_debug("%s: fid override %u->%u\n", __func__, fid,
  99                                active_fid);
 100                fid = active_fid;
 101        }
 102
 103        pr_debug("%s: active fid: %u\n", __func__, fid);
 104
 105        return fid;
 106}
 107
 108/*
 109 * Determine the mdiv (post divider) based on the frequency ID being used.
 110 * There are 4 sources that can be used to derive the output clock rate:
 111 *    - 25 MHz Crystal
 112 *    - System clock
 113 *    - PLL channel 0 (slow clock)
 114 *    - PLL channel 1 (fast clock)
 115 */
 116static int __get_mdiv(struct iproc_arm_pll *pll)
 117{
 118        unsigned int fid;
 119        int mdiv;
 120        u32 val;
 121
 122        fid = __get_fid(pll);
 123
 124        switch (fid) {
 125        case ARM_PLL_FID_CRYSTAL_CLK:
 126        case ARM_PLL_FID_SYS_CLK:
 127                mdiv = 1;
 128                break;
 129
 130        case ARM_PLL_FID_CH0_SLOW_CLK:
 131                val = readl(pll->base + IPROC_CLK_PLLARMC_OFFSET);
 132                mdiv = val & IPROC_CLK_PLLARMC_MDIV_MASK;
 133                if (mdiv == 0)
 134                        mdiv = 256;
 135                break;
 136
 137        case ARM_PLL_FID_CH1_FAST_CLK:
 138                val = readl(pll->base + IPROC_CLK_PLLARMCTL5_OFFSET);
 139                mdiv = val & IPROC_CLK_PLLARMCTL5_H_MDIV_MASK;
 140                if (mdiv == 0)
 141                        mdiv = 256;
 142                break;
 143
 144        default:
 145                mdiv = -EFAULT;
 146        }
 147
 148        return mdiv;
 149}
 150
 151static unsigned int __get_ndiv(struct iproc_arm_pll *pll)
 152{
 153        u32 val;
 154        unsigned int ndiv_int, ndiv_frac, ndiv;
 155
 156        val = readl(pll->base + IPROC_CLK_PLLARM_OFFSET_OFFSET);
 157        if (val & (1 << IPROC_CLK_PLLARM_SW_CTL_SHIFT)) {
 158                /*
 159                 * offset mode is active. Read the ndiv from the PLLARM OFFSET
 160                 * register
 161                 */
 162                ndiv_int = (val >> IPROC_CLK_PLLARM_NDIV_INT_OFFSET_SHIFT) &
 163                        IPROC_CLK_PLLARM_NDIV_INT_OFFSET_MASK;
 164                if (ndiv_int == 0)
 165                        ndiv_int = 256;
 166
 167                ndiv_frac = val & IPROC_CLK_PLLARM_NDIV_FRAC_OFFSET_MASK;
 168        } else {
 169                /* offset mode not active */
 170                val = readl(pll->base + IPROC_CLK_PLLARMA_OFFSET);
 171                ndiv_int = (val >> IPROC_CLK_PLLARMA_NDIV_INT_SHIFT) &
 172                        IPROC_CLK_PLLARMA_NDIV_INT_MASK;
 173                if (ndiv_int == 0)
 174                        ndiv_int = 1024;
 175
 176                val = readl(pll->base + IPROC_CLK_PLLARMB_OFFSET);
 177                ndiv_frac = val & IPROC_CLK_PLLARMB_NDIV_FRAC_MASK;
 178        }
 179
 180        ndiv = (ndiv_int << 20) | ndiv_frac;
 181
 182        return ndiv;
 183}
 184
 185/*
 186 * The output frequency of the ARM PLL is calculated based on the ARM PLL
 187 * divider values:
 188 *   pdiv = ARM PLL pre-divider
 189 *   ndiv = ARM PLL multiplier
 190 *   mdiv = ARM PLL post divider
 191 *
 192 * The frequency is calculated by:
 193 *   ((ndiv * parent clock rate) / pdiv) / mdiv
 194 */
 195static unsigned long iproc_arm_pll_recalc_rate(struct clk_hw *hw,
 196                unsigned long parent_rate)
 197{
 198        struct iproc_arm_pll *pll = to_iproc_arm_pll(hw);
 199        u32 val;
 200        int mdiv;
 201        u64 ndiv;
 202        unsigned int pdiv;
 203
 204        /* in bypass mode, use parent rate */
 205        val = readl(pll->base + IPROC_CLK_PLLARMC_OFFSET);
 206        if (val & (1 << IPROC_CLK_PLLARMC_BYPCLK_EN_SHIFT)) {
 207                pll->rate = parent_rate;
 208                return pll->rate;
 209        }
 210
 211        /* PLL needs to be locked */
 212        val = readl(pll->base + IPROC_CLK_PLLARMA_OFFSET);
 213        if (!(val & (1 << IPROC_CLK_PLLARMA_LOCK_SHIFT))) {
 214                pll->rate = 0;
 215                return 0;
 216        }
 217
 218        pdiv = (val >> IPROC_CLK_PLLARMA_PDIV_SHIFT) &
 219                IPROC_CLK_PLLARMA_PDIV_MASK;
 220        if (pdiv == 0)
 221                pdiv = 16;
 222
 223        ndiv = __get_ndiv(pll);
 224        mdiv = __get_mdiv(pll);
 225        if (mdiv <= 0) {
 226                pll->rate = 0;
 227                return 0;
 228        }
 229        pll->rate = (ndiv * parent_rate) >> 20;
 230        pll->rate = (pll->rate / pdiv) / mdiv;
 231
 232        pr_debug("%s: ARM PLL rate: %lu. parent rate: %lu\n", __func__,
 233                 pll->rate, parent_rate);
 234        pr_debug("%s: ndiv_int: %u, pdiv: %u, mdiv: %d\n", __func__,
 235                 (unsigned int)(ndiv >> 20), pdiv, mdiv);
 236
 237        return pll->rate;
 238}
 239
 240static const struct clk_ops iproc_arm_pll_ops = {
 241        .recalc_rate = iproc_arm_pll_recalc_rate,
 242};
 243
 244void __init iproc_armpll_setup(struct device_node *node)
 245{
 246        int ret;
 247        struct iproc_arm_pll *pll;
 248        struct clk_init_data init;
 249        const char *parent_name;
 250
 251        pll = kzalloc(sizeof(*pll), GFP_KERNEL);
 252        if (WARN_ON(!pll))
 253                return;
 254
 255        pll->base = of_iomap(node, 0);
 256        if (WARN_ON(!pll->base))
 257                goto err_free_pll;
 258
 259        init.name = node->name;
 260        init.ops = &iproc_arm_pll_ops;
 261        init.flags = 0;
 262        parent_name = of_clk_get_parent_name(node, 0);
 263        init.parent_names = (parent_name ? &parent_name : NULL);
 264        init.num_parents = (parent_name ? 1 : 0);
 265        pll->hw.init = &init;
 266
 267        ret = clk_hw_register(NULL, &pll->hw);
 268        if (WARN_ON(ret))
 269                goto err_iounmap;
 270
 271        ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &pll->hw);
 272        if (WARN_ON(ret))
 273                goto err_clk_unregister;
 274
 275        return;
 276
 277err_clk_unregister:
 278        clk_hw_unregister(&pll->hw);
 279err_iounmap:
 280        iounmap(pll->base);
 281err_free_pll:
 282        kfree(pll);
 283}
 284