Channagoud Kadabi | dd7cb38 | 2015-03-23 23:30:25 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2015, The Linux Foundation. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions are met: |
| 6 | * * Redistributions of source code must retain the above copyright |
| 7 | * notice, this list of conditions and the following disclaimer. |
| 8 | * * Redistributions in binary form must reproduce the above copyright |
| 9 | * notice, this list of conditions and the following disclaimer in the |
| 10 | * documentation and/or other materials provided with the distribution. |
| 11 | * * Neither the name of The Linux Foundation nor |
| 12 | * the names of its contributors may be used to endorse or promote |
| 13 | * products derived from this software without specific prior written |
| 14 | * permission. |
| 15 | * |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 19 | * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| 20 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 21 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 22 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| 23 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| 24 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| 25 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| 26 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 27 | */ |
| 28 | |
| 29 | #include <stdint.h> |
| 30 | #include <string.h> |
| 31 | #include <debug.h> |
| 32 | #include <reg.h> |
| 33 | #include <bits.h> |
| 34 | #include <limits.h> |
| 35 | #include <clock.h> |
| 36 | #include <clock_pll.h> |
| 37 | #include <clock_alpha_pll.h> |
| 38 | #include <arch/defines.h> |
| 39 | #include <platform/timer.h> |
| 40 | |
| 41 | #define ALPHA_REG_BITWIDTH 40 |
| 42 | #define ALPHA_BITWIDTH 32 |
| 43 | |
| 44 | static void set_fsm_mode(uint32_t mode_reg) |
| 45 | { |
| 46 | uint32_t regval = readl_relaxed(mode_reg); |
| 47 | |
| 48 | /* De-assert reset to FSM */ |
| 49 | regval &= ~PLL_VOTE_FSM_RESET; |
| 50 | writel_relaxed(regval, mode_reg); |
| 51 | |
| 52 | /* Program bias count */ |
| 53 | regval &= ~BM(PLL_BIAS_COUNT_END, PLL_BIAS_COUNT_START); |
| 54 | regval |= BVAL(PLL_BIAS_COUNT_END, PLL_BIAS_COUNT_START, PLL_BIAS_COUNT); |
| 55 | writel_relaxed(regval, mode_reg); |
| 56 | |
| 57 | /* Program lock count */ |
| 58 | regval &= ~BM(PLL_LOCK_COUNT_END, PLL_LOCK_COUNT_START); |
| 59 | regval |= BVAL(PLL_LOCK_COUNT_END, PLL_LOCK_COUNT_START, 0x0); |
| 60 | writel_relaxed(regval, mode_reg); |
| 61 | |
| 62 | /* Enable PLL FSM voting */ |
| 63 | regval |= PLL_VOTE_FSM_ENA; |
| 64 | writel_relaxed(regval, mode_reg); |
| 65 | } |
| 66 | |
| 67 | static inline struct alpha_pll_clk *to_alpha_pll_clk(struct clk *c) |
| 68 | { |
| 69 | return container_of(c, struct alpha_pll_clk, c); |
| 70 | } |
| 71 | |
| 72 | static void init_alpha_pll(struct clk *c) |
| 73 | { |
| 74 | struct alpha_pll_clk *pll = to_alpha_pll_clk(c); |
| 75 | struct alpha_pll_masks *masks = pll->masks; |
| 76 | uint32_t output_en, userval; |
| 77 | |
| 78 | if (masks->output_mask && pll->enable_config) { |
| 79 | output_en = readl_relaxed(OUTPUT_REG(pll)); |
| 80 | output_en &= ~masks->output_mask; |
| 81 | output_en |= pll->enable_config; |
| 82 | writel_relaxed(output_en, OUTPUT_REG(pll)); |
| 83 | } |
| 84 | |
| 85 | if (masks->post_div_mask) { |
| 86 | userval = readl_relaxed(USER_CTL_LO_REG(pll)); |
| 87 | userval &= ~masks->post_div_mask; |
| 88 | userval |= pll->post_div_config; |
| 89 | writel_relaxed(userval, USER_CTL_LO_REG(pll)); |
| 90 | } |
| 91 | |
| 92 | if (pll->fsm_en_mask) |
| 93 | set_fsm_mode(MODE_REG(pll)); |
| 94 | |
| 95 | pll->inited = true; |
| 96 | } |
| 97 | |
| 98 | static bool is_active(struct alpha_pll_clk *pll) |
| 99 | { |
| 100 | uint32_t reg = readl(ACTIVE_REG(pll)); |
| 101 | uint32_t mask = pll->masks->active_mask; |
| 102 | return (reg & mask) == mask; |
| 103 | } |
| 104 | |
| 105 | static bool is_locked(struct alpha_pll_clk *pll) |
| 106 | { |
| 107 | uint32_t reg = readl(LOCK_REG(pll)); |
| 108 | uint32_t mask = pll->masks->lock_mask; |
| 109 | return (reg & mask) == mask; |
| 110 | } |
| 111 | |
| 112 | static bool is_fsm_mode(uint32_t mode_reg) |
| 113 | { |
| 114 | return !!(readl(mode_reg) & PLL_VOTE_FSM_ENA); |
| 115 | } |
| 116 | |
| 117 | static bool update_finish(struct alpha_pll_clk *pll) |
| 118 | { |
| 119 | return is_active(pll); |
| 120 | } |
| 121 | |
| 122 | static int wait_for_update(struct alpha_pll_clk *pll) |
| 123 | { |
| 124 | uint32_t retry = 100; |
| 125 | |
| 126 | while (retry) |
| 127 | { |
| 128 | if (update_finish(pll)) |
| 129 | break; |
| 130 | udelay(1); |
| 131 | retry--; |
| 132 | } |
| 133 | |
| 134 | if (!retry) |
| 135 | { |
| 136 | dprintf(CRITICAL, "%s didn't lock after enabling it!\n", pll->c.dbg_name); |
| 137 | return -1; |
| 138 | } |
| 139 | |
| 140 | return 0; |
| 141 | } |
| 142 | |
| 143 | static unsigned long compute_rate(struct alpha_pll_clk *pll, |
| 144 | uint32_t l_val, uint32_t a_val) |
| 145 | { |
| 146 | uint64_t rate, parent_rate; |
| 147 | |
| 148 | parent_rate = pll->parent->rate; |
| 149 | rate = parent_rate * l_val; |
| 150 | rate += (parent_rate * a_val) >> ALPHA_BITWIDTH; |
| 151 | return rate; |
| 152 | } |
| 153 | |
| 154 | static uint32_t do_div(uint64_t *n, uint32_t divisor) |
| 155 | { |
| 156 | uint32_t remainder = *n % divisor; |
| 157 | *n = *n / divisor; |
| 158 | return remainder; |
| 159 | } |
| 160 | |
| 161 | static unsigned long calc_values(struct alpha_pll_clk *pll, |
| 162 | uint64_t rate, int *l_val, uint64_t *a_val, bool round_up) |
| 163 | { |
| 164 | uint32_t parent_rate; |
| 165 | uint64_t remainder; |
| 166 | uint64_t quotient; |
| 167 | unsigned long freq_hz; |
| 168 | |
| 169 | parent_rate = pll->parent->rate; |
| 170 | quotient = rate; |
| 171 | remainder = do_div("ient, parent_rate); |
| 172 | |
| 173 | *l_val = quotient; |
| 174 | |
| 175 | if (!remainder) { |
| 176 | *a_val = 0; |
| 177 | return rate; |
| 178 | } |
| 179 | |
| 180 | /* Upper ALPHA_BITWIDTH bits of Alpha */ |
| 181 | quotient = remainder << ALPHA_BITWIDTH; |
| 182 | remainder = do_div("ient, parent_rate); |
| 183 | |
| 184 | if (remainder && round_up) |
| 185 | quotient++; |
| 186 | |
| 187 | *a_val = quotient; |
| 188 | freq_hz = compute_rate(pll, *l_val, *a_val); |
| 189 | return freq_hz; |
| 190 | } |
| 191 | |
Channagoud Kadabi | f016e55 | 2015-05-29 16:59:25 -0700 | [diff] [blame] | 192 | static unsigned long round_rate_down(struct alpha_pll_clk *pll, |
| 193 | unsigned long rate, int *l_val, uint64_t *a_val) |
| 194 | { |
| 195 | return calc_values(pll, rate, l_val, a_val, false); |
| 196 | } |
| 197 | |
Channagoud Kadabi | dd7cb38 | 2015-03-23 23:30:25 -0700 | [diff] [blame] | 198 | static unsigned long round_rate_up(struct alpha_pll_clk *pll, |
| 199 | unsigned long rate, int *l_val, uint64_t *a_val) |
| 200 | { |
| 201 | return calc_values(pll, rate, l_val, a_val, true); |
| 202 | } |
| 203 | |
| 204 | static uint32_t find_vco(struct alpha_pll_clk *pll, unsigned long rate) |
| 205 | { |
| 206 | unsigned long i; |
| 207 | struct alpha_pll_vco_tbl *v = pll->vco_tbl; |
| 208 | |
| 209 | for (i = 0; i < pll->vco_num; i++) { |
| 210 | if (rate >= v[i].min_freq && rate <= v[i].max_freq) |
| 211 | return v[i].vco_val; |
| 212 | } |
| 213 | return -1; |
| 214 | } |
| 215 | |
| 216 | static int alpha_pll_set_rate(struct clk *c, unsigned long rate) |
| 217 | { |
| 218 | struct alpha_pll_clk *pll = to_alpha_pll_clk(c); |
| 219 | struct alpha_pll_masks *masks = pll->masks; |
| 220 | unsigned long freq_hz; |
| 221 | int regval, l_val; |
| 222 | uint64_t a_val; |
| 223 | uint32_t vco_val; |
| 224 | |
| 225 | freq_hz = round_rate_up(pll, rate, &l_val, &a_val); |
| 226 | if (freq_hz != rate) { |
| 227 | dprintf(CRITICAL, "alpha_pll: Call clk_set_rate with rounded rates!\n"); |
| 228 | return -1; |
| 229 | } |
| 230 | |
| 231 | vco_val = find_vco(pll, freq_hz); |
| 232 | if (!vco_val) |
| 233 | { |
| 234 | dprintf(CRITICAL, "alpha pll: VCO not valid\n"); |
| 235 | return -1; |
| 236 | } |
| 237 | |
| 238 | /* |
| 239 | * Ensure PLL is off before changing rate. For optimization reasons, |
| 240 | * assume no downstream clock is actively using it. No support |
| 241 | * for dynamic update at the moment. |
| 242 | */ |
| 243 | if (c->count) |
| 244 | alpha_pll_disable(c); |
| 245 | |
| 246 | a_val = a_val << (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH); |
| 247 | |
| 248 | writel_relaxed(l_val, L_REG(pll)); |
| 249 | writel_relaxed(a_val, A_REG(pll)); |
| 250 | a_val = (a_val >> ALPHA_BITWIDTH); |
| 251 | writel_relaxed(a_val, A_REG_U(pll)); |
| 252 | |
| 253 | if (masks->vco_mask) { |
| 254 | regval = readl_relaxed(VCO_REG(pll)); |
| 255 | regval &= ~(masks->vco_mask << masks->vco_shift); |
| 256 | regval |= vco_val << masks->vco_shift; |
| 257 | writel_relaxed(regval, VCO_REG(pll)); |
| 258 | } |
| 259 | |
| 260 | regval = readl_relaxed(ALPHA_EN_REG(pll)); |
| 261 | regval |= masks->alpha_en_mask; |
| 262 | writel_relaxed(regval, ALPHA_EN_REG(pll)); |
| 263 | |
| 264 | if (c->count) |
| 265 | alpha_pll_enable(c); |
| 266 | |
| 267 | return 0; |
| 268 | } |
| 269 | |
Channagoud Kadabi | f016e55 | 2015-05-29 16:59:25 -0700 | [diff] [blame] | 270 | static void update_vco_tbl(struct alpha_pll_clk *pll) |
| 271 | { |
| 272 | uint32_t i; |
| 273 | int l_val; |
| 274 | uint64_t a_val; |
| 275 | uint64_t rate; |
| 276 | |
| 277 | for (i = 0 ; i < pll->vco_num; i++) |
| 278 | { |
| 279 | rate = round_rate_up(pll, pll->vco_tbl[i].min_freq, &l_val, &a_val); |
| 280 | pll->vco_tbl[i].min_freq = rate; |
| 281 | |
| 282 | rate = round_rate_down(pll, pll->vco_tbl[i].max_freq, &l_val, &a_val); |
| 283 | pll->vco_tbl[i].max_freq = rate; |
| 284 | } |
| 285 | } |
| 286 | |
Channagoud Kadabi | dd7cb38 | 2015-03-23 23:30:25 -0700 | [diff] [blame] | 287 | int alpha_pll_enable(struct clk *c) |
| 288 | { |
| 289 | struct alpha_pll_clk *pll = to_alpha_pll_clk(c); |
| 290 | uint32_t ena; |
| 291 | |
Channagoud Kadabi | f016e55 | 2015-05-29 16:59:25 -0700 | [diff] [blame] | 292 | update_vco_tbl(pll); |
| 293 | |
Channagoud Kadabi | dd7cb38 | 2015-03-23 23:30:25 -0700 | [diff] [blame] | 294 | /* if PLL is not initialized already and is not in FSM state */ |
| 295 | if (!pll->inited && !is_locked(pll)) |
| 296 | { |
| 297 | if (c->rate && alpha_pll_set_rate(c, c->rate)) |
| 298 | dprintf(INFO, "Warning: Failed to set rate for alpha pll\n"); |
| 299 | init_alpha_pll(c); |
| 300 | } |
| 301 | else if (!is_fsm_mode(MODE_REG(pll))) |
| 302 | { |
| 303 | dprintf(INFO, "FSM mode is not set for: %s\n", c->dbg_name); |
| 304 | } |
| 305 | |
| 306 | ena = readl_relaxed(VOTE_REG(pll)); |
| 307 | ena |= pll->fsm_en_mask; |
| 308 | writel_relaxed(ena, VOTE_REG(pll)); |
| 309 | dmb(); |
| 310 | |
| 311 | return wait_for_update(pll); |
| 312 | } |
| 313 | |
| 314 | void alpha_pll_disable(struct clk *c) |
| 315 | { |
| 316 | struct alpha_pll_clk *pll = to_alpha_pll_clk(c); |
| 317 | uint32_t ena; |
| 318 | |
| 319 | ena = readl_relaxed(VOTE_REG(pll)); |
| 320 | ena &= ~pll->fsm_en_mask; |
| 321 | writel_relaxed(ena, VOTE_REG(pll)); |
| 322 | dmb(); |
| 323 | } |