linux/drivers/clk/qcom/clk-hfpll.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2// Copyright (c) 2018, The Linux Foundation. All rights reserved.
   3
   4#include <linux/kernel.h>
   5#include <linux/export.h>
   6#include <linux/regmap.h>
   7#include <linux/delay.h>
   8#include <linux/err.h>
   9#include <linux/clk-provider.h>
  10#include <linux/spinlock.h>
  11
  12#include "clk-regmap.h"
  13#include "clk-hfpll.h"
  14
  15#define PLL_OUTCTRL     BIT(0)
  16#define PLL_BYPASSNL    BIT(1)
  17#define PLL_RESET_N     BIT(2)
  18
  19/* Initialize a HFPLL at a given rate and enable it. */
  20static void __clk_hfpll_init_once(struct clk_hw *hw)
  21{
  22        struct clk_hfpll *h = to_clk_hfpll(hw);
  23        struct hfpll_data const *hd = h->d;
  24        struct regmap *regmap = h->clkr.regmap;
  25
  26        if (likely(h->init_done))
  27                return;
  28
  29        /* Configure PLL parameters for integer mode. */
  30        if (hd->config_val)
  31                regmap_write(regmap, hd->config_reg, hd->config_val);
  32        regmap_write(regmap, hd->m_reg, 0);
  33        regmap_write(regmap, hd->n_reg, 1);
  34
  35        if (hd->user_reg) {
  36                u32 regval = hd->user_val;
  37                unsigned long rate;
  38
  39                rate = clk_hw_get_rate(hw);
  40
  41                /* Pick the right VCO. */
  42                if (hd->user_vco_mask && rate > hd->low_vco_max_rate)
  43                        regval |= hd->user_vco_mask;
  44                regmap_write(regmap, hd->user_reg, regval);
  45        }
  46
  47        if (hd->droop_reg)
  48                regmap_write(regmap, hd->droop_reg, hd->droop_val);
  49
  50        h->init_done = true;
  51}
  52
  53static void __clk_hfpll_enable(struct clk_hw *hw)
  54{
  55        struct clk_hfpll *h = to_clk_hfpll(hw);
  56        struct hfpll_data const *hd = h->d;
  57        struct regmap *regmap = h->clkr.regmap;
  58        u32 val;
  59
  60        __clk_hfpll_init_once(hw);
  61
  62        /* Disable PLL bypass mode. */
  63        regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL);
  64
  65        /*
  66         * H/W requires a 5us delay between disabling the bypass and
  67         * de-asserting the reset. Delay 10us just to be safe.
  68         */
  69        udelay(10);
  70
  71        /* De-assert active-low PLL reset. */
  72        regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N);
  73
  74        /* Wait for PLL to lock. */
  75        if (hd->status_reg) {
  76                do {
  77                        regmap_read(regmap, hd->status_reg, &val);
  78                } while (!(val & BIT(hd->lock_bit)));
  79        } else {
  80                udelay(60);
  81        }
  82
  83        /* Enable PLL output. */
  84        regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL);
  85}
  86
  87/* Enable an already-configured HFPLL. */
  88static int clk_hfpll_enable(struct clk_hw *hw)
  89{
  90        unsigned long flags;
  91        struct clk_hfpll *h = to_clk_hfpll(hw);
  92        struct hfpll_data const *hd = h->d;
  93        struct regmap *regmap = h->clkr.regmap;
  94        u32 mode;
  95
  96        spin_lock_irqsave(&h->lock, flags);
  97        regmap_read(regmap, hd->mode_reg, &mode);
  98        if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)))
  99                __clk_hfpll_enable(hw);
 100        spin_unlock_irqrestore(&h->lock, flags);
 101
 102        return 0;
 103}
 104
 105static void __clk_hfpll_disable(struct clk_hfpll *h)
 106{
 107        struct hfpll_data const *hd = h->d;
 108        struct regmap *regmap = h->clkr.regmap;
 109
 110        /*
 111         * Disable the PLL output, disable test mode, enable the bypass mode,
 112         * and assert the reset.
 113         */
 114        regmap_update_bits(regmap, hd->mode_reg,
 115                           PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0);
 116}
 117
 118static void clk_hfpll_disable(struct clk_hw *hw)
 119{
 120        struct clk_hfpll *h = to_clk_hfpll(hw);
 121        unsigned long flags;
 122
 123        spin_lock_irqsave(&h->lock, flags);
 124        __clk_hfpll_disable(h);
 125        spin_unlock_irqrestore(&h->lock, flags);
 126}
 127
 128static long clk_hfpll_round_rate(struct clk_hw *hw, unsigned long rate,
 129                                 unsigned long *parent_rate)
 130{
 131        struct clk_hfpll *h = to_clk_hfpll(hw);
 132        struct hfpll_data const *hd = h->d;
 133        unsigned long rrate;
 134
 135        rate = clamp(rate, hd->min_rate, hd->max_rate);
 136
 137        rrate = DIV_ROUND_UP(rate, *parent_rate) * *parent_rate;
 138        if (rrate > hd->max_rate)
 139                rrate -= *parent_rate;
 140
 141        return rrate;
 142}
 143
 144/*
 145 * For optimization reasons, assumes no downstream clocks are actively using
 146 * it.
 147 */
 148static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate,
 149                              unsigned long parent_rate)
 150{
 151        struct clk_hfpll *h = to_clk_hfpll(hw);
 152        struct hfpll_data const *hd = h->d;
 153        struct regmap *regmap = h->clkr.regmap;
 154        unsigned long flags;
 155        u32 l_val, val;
 156        bool enabled;
 157
 158        l_val = rate / parent_rate;
 159
 160        spin_lock_irqsave(&h->lock, flags);
 161
 162        enabled = __clk_is_enabled(hw->clk);
 163        if (enabled)
 164                __clk_hfpll_disable(h);
 165
 166        /* Pick the right VCO. */
 167        if (hd->user_reg && hd->user_vco_mask) {
 168                regmap_read(regmap, hd->user_reg, &val);
 169                if (rate <= hd->low_vco_max_rate)
 170                        val &= ~hd->user_vco_mask;
 171                else
 172                        val |= hd->user_vco_mask;
 173                regmap_write(regmap, hd->user_reg, val);
 174        }
 175
 176        regmap_write(regmap, hd->l_reg, l_val);
 177
 178        if (enabled)
 179                __clk_hfpll_enable(hw);
 180
 181        spin_unlock_irqrestore(&h->lock, flags);
 182
 183        return 0;
 184}
 185
 186static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw,
 187                                           unsigned long parent_rate)
 188{
 189        struct clk_hfpll *h = to_clk_hfpll(hw);
 190        struct hfpll_data const *hd = h->d;
 191        struct regmap *regmap = h->clkr.regmap;
 192        u32 l_val;
 193
 194        regmap_read(regmap, hd->l_reg, &l_val);
 195
 196        return l_val * parent_rate;
 197}
 198
 199static int clk_hfpll_init(struct clk_hw *hw)
 200{
 201        struct clk_hfpll *h = to_clk_hfpll(hw);
 202        struct hfpll_data const *hd = h->d;
 203        struct regmap *regmap = h->clkr.regmap;
 204        u32 mode, status;
 205
 206        regmap_read(regmap, hd->mode_reg, &mode);
 207        if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) {
 208                __clk_hfpll_init_once(hw);
 209                return 0;
 210        }
 211
 212        if (hd->status_reg) {
 213                regmap_read(regmap, hd->status_reg, &status);
 214                if (!(status & BIT(hd->lock_bit))) {
 215                        WARN(1, "HFPLL %s is ON, but not locked!\n",
 216                             __clk_get_name(hw->clk));
 217                        clk_hfpll_disable(hw);
 218                        __clk_hfpll_init_once(hw);
 219                }
 220        }
 221
 222        return 0;
 223}
 224
 225static int hfpll_is_enabled(struct clk_hw *hw)
 226{
 227        struct clk_hfpll *h = to_clk_hfpll(hw);
 228        struct hfpll_data const *hd = h->d;
 229        struct regmap *regmap = h->clkr.regmap;
 230        u32 mode;
 231
 232        regmap_read(regmap, hd->mode_reg, &mode);
 233        mode &= 0x7;
 234        return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL);
 235}
 236
 237const struct clk_ops clk_ops_hfpll = {
 238        .enable = clk_hfpll_enable,
 239        .disable = clk_hfpll_disable,
 240        .is_enabled = hfpll_is_enabled,
 241        .round_rate = clk_hfpll_round_rate,
 242        .set_rate = clk_hfpll_set_rate,
 243        .recalc_rate = clk_hfpll_recalc_rate,
 244        .init = clk_hfpll_init,
 245};
 246EXPORT_SYMBOL_GPL(clk_ops_hfpll);
 247