linux/drivers/clk/sunxi-ng/ccu_mp.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2016 Maxime Ripard
   3 * Maxime Ripard <maxime.ripard@free-electrons.com>
   4 *
   5 * This program is free software; you can redistribute it and/or
   6 * modify it under the terms of the GNU General Public License as
   7 * published by the Free Software Foundation; either version 2 of
   8 * the License, or (at your option) any later version.
   9 */
  10
  11#include <linux/clk-provider.h>
  12
  13#include "ccu_gate.h"
  14#include "ccu_mp.h"
  15
  16static void ccu_mp_find_best(unsigned long parent, unsigned long rate,
  17                             unsigned int max_m, unsigned int max_p,
  18                             unsigned int *m, unsigned int *p)
  19{
  20        unsigned long best_rate = 0;
  21        unsigned int best_m = 0, best_p = 0;
  22        unsigned int _m, _p;
  23
  24        for (_p = 1; _p <= max_p; _p <<= 1) {
  25                for (_m = 1; _m <= max_m; _m++) {
  26                        unsigned long tmp_rate = parent / _p / _m;
  27
  28                        if (tmp_rate > rate)
  29                                continue;
  30
  31                        if ((rate - tmp_rate) < (rate - best_rate)) {
  32                                best_rate = tmp_rate;
  33                                best_m = _m;
  34                                best_p = _p;
  35                        }
  36                }
  37        }
  38
  39        *m = best_m;
  40        *p = best_p;
  41}
  42
  43static unsigned long ccu_mp_find_best_with_parent_adj(struct clk_hw *hw,
  44                                                      unsigned long *parent,
  45                                                      unsigned long rate,
  46                                                      unsigned int max_m,
  47                                                      unsigned int max_p)
  48{
  49        unsigned long parent_rate_saved;
  50        unsigned long parent_rate, now;
  51        unsigned long best_rate = 0;
  52        unsigned int _m, _p, div;
  53        unsigned long maxdiv;
  54
  55        parent_rate_saved = *parent;
  56
  57        /*
  58         * The maximum divider we can use without overflowing
  59         * unsigned long in rate * m * p below
  60         */
  61        maxdiv = max_m * max_p;
  62        maxdiv = min(ULONG_MAX / rate, maxdiv);
  63
  64        for (_p = 1; _p <= max_p; _p <<= 1) {
  65                for (_m = 1; _m <= max_m; _m++) {
  66                        div = _m * _p;
  67
  68                        if (div > maxdiv)
  69                                break;
  70
  71                        if (rate * div == parent_rate_saved) {
  72                                /*
  73                                 * It's the most ideal case if the requested
  74                                 * rate can be divided from parent clock without
  75                                 * needing to change parent rate, so return the
  76                                 * divider immediately.
  77                                 */
  78                                *parent = parent_rate_saved;
  79                                return rate;
  80                        }
  81
  82                        parent_rate = clk_hw_round_rate(hw, rate * div);
  83                        now = parent_rate / div;
  84
  85                        if (now <= rate && now > best_rate) {
  86                                best_rate = now;
  87                                *parent = parent_rate;
  88
  89                                if (now == rate)
  90                                        return rate;
  91                        }
  92                }
  93        }
  94
  95        return best_rate;
  96}
  97
  98static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux,
  99                                       struct clk_hw *hw,
 100                                       unsigned long *parent_rate,
 101                                       unsigned long rate,
 102                                       void *data)
 103{
 104        struct ccu_mp *cmp = data;
 105        unsigned int max_m, max_p;
 106        unsigned int m, p;
 107
 108        if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV)
 109                rate *= cmp->fixed_post_div;
 110
 111        max_m = cmp->m.max ?: 1 << cmp->m.width;
 112        max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
 113
 114        if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) {
 115                ccu_mp_find_best(*parent_rate, rate, max_m, max_p, &m, &p);
 116                rate = *parent_rate / p / m;
 117        } else {
 118                rate = ccu_mp_find_best_with_parent_adj(hw, parent_rate, rate,
 119                                                        max_m, max_p);
 120        }
 121
 122        if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV)
 123                rate /= cmp->fixed_post_div;
 124
 125        return rate;
 126}
 127
 128static void ccu_mp_disable(struct clk_hw *hw)
 129{
 130        struct ccu_mp *cmp = hw_to_ccu_mp(hw);
 131
 132        return ccu_gate_helper_disable(&cmp->common, cmp->enable);
 133}
 134
 135static int ccu_mp_enable(struct clk_hw *hw)
 136{
 137        struct ccu_mp *cmp = hw_to_ccu_mp(hw);
 138
 139        return ccu_gate_helper_enable(&cmp->common, cmp->enable);
 140}
 141
 142static int ccu_mp_is_enabled(struct clk_hw *hw)
 143{
 144        struct ccu_mp *cmp = hw_to_ccu_mp(hw);
 145
 146        return ccu_gate_helper_is_enabled(&cmp->common, cmp->enable);
 147}
 148
 149static unsigned long ccu_mp_recalc_rate(struct clk_hw *hw,
 150                                        unsigned long parent_rate)
 151{
 152        struct ccu_mp *cmp = hw_to_ccu_mp(hw);
 153        unsigned long rate;
 154        unsigned int m, p;
 155        u32 reg;
 156
 157        /* Adjust parent_rate according to pre-dividers */
 158        parent_rate = ccu_mux_helper_apply_prediv(&cmp->common, &cmp->mux, -1,
 159                                                  parent_rate);
 160
 161        reg = readl(cmp->common.base + cmp->common.reg);
 162
 163        m = reg >> cmp->m.shift;
 164        m &= (1 << cmp->m.width) - 1;
 165        m += cmp->m.offset;
 166        if (!m)
 167                m++;
 168
 169        p = reg >> cmp->p.shift;
 170        p &= (1 << cmp->p.width) - 1;
 171
 172        rate = (parent_rate >> p) / m;
 173        if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV)
 174                rate /= cmp->fixed_post_div;
 175
 176        return rate;
 177}
 178
 179static int ccu_mp_determine_rate(struct clk_hw *hw,
 180                                 struct clk_rate_request *req)
 181{
 182        struct ccu_mp *cmp = hw_to_ccu_mp(hw);
 183
 184        return ccu_mux_helper_determine_rate(&cmp->common, &cmp->mux,
 185                                             req, ccu_mp_round_rate, cmp);
 186}
 187
 188static int ccu_mp_set_rate(struct clk_hw *hw, unsigned long rate,
 189                           unsigned long parent_rate)
 190{
 191        struct ccu_mp *cmp = hw_to_ccu_mp(hw);
 192        unsigned long flags;
 193        unsigned int max_m, max_p;
 194        unsigned int m, p;
 195        u32 reg;
 196
 197        /* Adjust parent_rate according to pre-dividers */
 198        parent_rate = ccu_mux_helper_apply_prediv(&cmp->common, &cmp->mux, -1,
 199                                                  parent_rate);
 200
 201        max_m = cmp->m.max ?: 1 << cmp->m.width;
 202        max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
 203
 204        /* Adjust target rate according to post-dividers */
 205        if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV)
 206                rate = rate * cmp->fixed_post_div;
 207
 208        ccu_mp_find_best(parent_rate, rate, max_m, max_p, &m, &p);
 209
 210        spin_lock_irqsave(cmp->common.lock, flags);
 211
 212        reg = readl(cmp->common.base + cmp->common.reg);
 213        reg &= ~GENMASK(cmp->m.width + cmp->m.shift - 1, cmp->m.shift);
 214        reg &= ~GENMASK(cmp->p.width + cmp->p.shift - 1, cmp->p.shift);
 215        reg |= (m - cmp->m.offset) << cmp->m.shift;
 216        reg |= ilog2(p) << cmp->p.shift;
 217
 218        writel(reg, cmp->common.base + cmp->common.reg);
 219
 220        spin_unlock_irqrestore(cmp->common.lock, flags);
 221
 222        return 0;
 223}
 224
 225static u8 ccu_mp_get_parent(struct clk_hw *hw)
 226{
 227        struct ccu_mp *cmp = hw_to_ccu_mp(hw);
 228
 229        return ccu_mux_helper_get_parent(&cmp->common, &cmp->mux);
 230}
 231
 232static int ccu_mp_set_parent(struct clk_hw *hw, u8 index)
 233{
 234        struct ccu_mp *cmp = hw_to_ccu_mp(hw);
 235
 236        return ccu_mux_helper_set_parent(&cmp->common, &cmp->mux, index);
 237}
 238
 239const struct clk_ops ccu_mp_ops = {
 240        .disable        = ccu_mp_disable,
 241        .enable         = ccu_mp_enable,
 242        .is_enabled     = ccu_mp_is_enabled,
 243
 244        .get_parent     = ccu_mp_get_parent,
 245        .set_parent     = ccu_mp_set_parent,
 246
 247        .determine_rate = ccu_mp_determine_rate,
 248        .recalc_rate    = ccu_mp_recalc_rate,
 249        .set_rate       = ccu_mp_set_rate,
 250};
 251
 252/*
 253 * Support for MMC timing mode switching
 254 *
 255 * The MMC clocks on some SoCs support switching between old and
 256 * new timing modes. A platform specific API is provided to query
 257 * and set the timing mode on supported SoCs.
 258 *
 259 * In addition, a special class of ccu_mp_ops is provided, which
 260 * takes in to account the timing mode switch. When the new timing
 261 * mode is active, the clock output rate is halved. This new class
 262 * is a wrapper around the generic ccu_mp_ops. When clock rates
 263 * are passed through to ccu_mp_ops callbacks, they are doubled
 264 * if the new timing mode bit is set, to account for the post
 265 * divider. Conversely, when clock rates are passed back, they
 266 * are halved if the mode bit is set.
 267 */
 268
 269static unsigned long ccu_mp_mmc_recalc_rate(struct clk_hw *hw,
 270                                            unsigned long parent_rate)
 271{
 272        unsigned long rate = ccu_mp_recalc_rate(hw, parent_rate);
 273        struct ccu_common *cm = hw_to_ccu_common(hw);
 274        u32 val = readl(cm->base + cm->reg);
 275
 276        if (val & CCU_MMC_NEW_TIMING_MODE)
 277                return rate / 2;
 278        return rate;
 279}
 280
 281static int ccu_mp_mmc_determine_rate(struct clk_hw *hw,
 282                                     struct clk_rate_request *req)
 283{
 284        struct ccu_common *cm = hw_to_ccu_common(hw);
 285        u32 val = readl(cm->base + cm->reg);
 286        int ret;
 287
 288        /* adjust the requested clock rate */
 289        if (val & CCU_MMC_NEW_TIMING_MODE) {
 290                req->rate *= 2;
 291                req->min_rate *= 2;
 292                req->max_rate *= 2;
 293        }
 294
 295        ret = ccu_mp_determine_rate(hw, req);
 296
 297        /* re-adjust the requested clock rate back */
 298        if (val & CCU_MMC_NEW_TIMING_MODE) {
 299                req->rate /= 2;
 300                req->min_rate /= 2;
 301                req->max_rate /= 2;
 302        }
 303
 304        return ret;
 305}
 306
 307static int ccu_mp_mmc_set_rate(struct clk_hw *hw, unsigned long rate,
 308                               unsigned long parent_rate)
 309{
 310        struct ccu_common *cm = hw_to_ccu_common(hw);
 311        u32 val = readl(cm->base + cm->reg);
 312
 313        if (val & CCU_MMC_NEW_TIMING_MODE)
 314                rate *= 2;
 315
 316        return ccu_mp_set_rate(hw, rate, parent_rate);
 317}
 318
 319const struct clk_ops ccu_mp_mmc_ops = {
 320        .disable        = ccu_mp_disable,
 321        .enable         = ccu_mp_enable,
 322        .is_enabled     = ccu_mp_is_enabled,
 323
 324        .get_parent     = ccu_mp_get_parent,
 325        .set_parent     = ccu_mp_set_parent,
 326
 327        .determine_rate = ccu_mp_mmc_determine_rate,
 328        .recalc_rate    = ccu_mp_mmc_recalc_rate,
 329        .set_rate       = ccu_mp_mmc_set_rate,
 330};
 331