linux/drivers/clk/meson/sclk-div.c
<<
>>
Prefs
   1// SPDX-License-Identifier: (GPL-2.0 OR MIT)
   2/*
   3 * Copyright (c) 2018 BayLibre, SAS.
   4 * Author: Jerome Brunet <jbrunet@baylibre.com>
   5 *
   6 * Sample clock generator divider:
   7 * This HW divider gates with value 0 but is otherwise a zero based divider:
   8 *
   9 * val >= 1
  10 * divider = val + 1
  11 *
  12 * The duty cycle may also be set for the LR clock variant. The duty cycle
  13 * ratio is:
  14 *
  15 * hi = [0 - val]
  16 * duty_cycle = (1 + hi) / (1 + val)
  17 */
  18
  19#include "clkc-audio.h"
  20
  21static inline struct meson_sclk_div_data *
  22meson_sclk_div_data(struct clk_regmap *clk)
  23{
  24        return (struct meson_sclk_div_data *)clk->data;
  25}
  26
  27static int sclk_div_maxval(struct meson_sclk_div_data *sclk)
  28{
  29        return (1 << sclk->div.width) - 1;
  30}
  31
  32static int sclk_div_maxdiv(struct meson_sclk_div_data *sclk)
  33{
  34        return sclk_div_maxval(sclk) + 1;
  35}
  36
  37static int sclk_div_getdiv(struct clk_hw *hw, unsigned long rate,
  38                           unsigned long prate, int maxdiv)
  39{
  40        int div = DIV_ROUND_CLOSEST_ULL((u64)prate, rate);
  41
  42        return clamp(div, 2, maxdiv);
  43}
  44
  45static int sclk_div_bestdiv(struct clk_hw *hw, unsigned long rate,
  46                            unsigned long *prate,
  47                            struct meson_sclk_div_data *sclk)
  48{
  49        struct clk_hw *parent = clk_hw_get_parent(hw);
  50        int bestdiv = 0, i;
  51        unsigned long maxdiv, now, parent_now;
  52        unsigned long best = 0, best_parent = 0;
  53
  54        if (!rate)
  55                rate = 1;
  56
  57        maxdiv = sclk_div_maxdiv(sclk);
  58
  59        if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT))
  60                return sclk_div_getdiv(hw, rate, *prate, maxdiv);
  61
  62        /*
  63         * The maximum divider we can use without overflowing
  64         * unsigned long in rate * i below
  65         */
  66        maxdiv = min(ULONG_MAX / rate, maxdiv);
  67
  68        for (i = 2; i <= maxdiv; i++) {
  69                /*
  70                 * It's the most ideal case if the requested rate can be
  71                 * divided from parent clock without needing to change
  72                 * parent rate, so return the divider immediately.
  73                 */
  74                if (rate * i == *prate)
  75                        return i;
  76
  77                parent_now = clk_hw_round_rate(parent, rate * i);
  78                now = DIV_ROUND_UP_ULL((u64)parent_now, i);
  79
  80                if (abs(rate - now) < abs(rate - best)) {
  81                        bestdiv = i;
  82                        best = now;
  83                        best_parent = parent_now;
  84                }
  85        }
  86
  87        if (!bestdiv)
  88                bestdiv = sclk_div_maxdiv(sclk);
  89        else
  90                *prate = best_parent;
  91
  92        return bestdiv;
  93}
  94
  95static long sclk_div_round_rate(struct clk_hw *hw, unsigned long rate,
  96                                unsigned long *prate)
  97{
  98        struct clk_regmap *clk = to_clk_regmap(hw);
  99        struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
 100        int div;
 101
 102        div = sclk_div_bestdiv(hw, rate, prate, sclk);
 103
 104        return DIV_ROUND_UP_ULL((u64)*prate, div);
 105}
 106
 107static void sclk_apply_ratio(struct clk_regmap *clk,
 108                             struct meson_sclk_div_data *sclk)
 109{
 110        unsigned int hi = DIV_ROUND_CLOSEST(sclk->cached_div *
 111                                            sclk->cached_duty.num,
 112                                            sclk->cached_duty.den);
 113
 114        if (hi)
 115                hi -= 1;
 116
 117        meson_parm_write(clk->map, &sclk->hi, hi);
 118}
 119
 120static int sclk_div_set_duty_cycle(struct clk_hw *hw,
 121                                   struct clk_duty *duty)
 122{
 123        struct clk_regmap *clk = to_clk_regmap(hw);
 124        struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
 125
 126        if (MESON_PARM_APPLICABLE(&sclk->hi)) {
 127                memcpy(&sclk->cached_duty, duty, sizeof(*duty));
 128                sclk_apply_ratio(clk, sclk);
 129        }
 130
 131        return 0;
 132}
 133
 134static int sclk_div_get_duty_cycle(struct clk_hw *hw,
 135                                   struct clk_duty *duty)
 136{
 137        struct clk_regmap *clk = to_clk_regmap(hw);
 138        struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
 139        int hi;
 140
 141        if (!MESON_PARM_APPLICABLE(&sclk->hi)) {
 142                duty->num = 1;
 143                duty->den = 2;
 144                return 0;
 145        }
 146
 147        hi = meson_parm_read(clk->map, &sclk->hi);
 148        duty->num = hi + 1;
 149        duty->den = sclk->cached_div;
 150        return 0;
 151}
 152
 153static void sclk_apply_divider(struct clk_regmap *clk,
 154                               struct meson_sclk_div_data *sclk)
 155{
 156        if (MESON_PARM_APPLICABLE(&sclk->hi))
 157                sclk_apply_ratio(clk, sclk);
 158
 159        meson_parm_write(clk->map, &sclk->div, sclk->cached_div - 1);
 160}
 161
 162static int sclk_div_set_rate(struct clk_hw *hw, unsigned long rate,
 163                             unsigned long prate)
 164{
 165        struct clk_regmap *clk = to_clk_regmap(hw);
 166        struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
 167        unsigned long maxdiv = sclk_div_maxdiv(sclk);
 168
 169        sclk->cached_div = sclk_div_getdiv(hw, rate, prate, maxdiv);
 170
 171        if (clk_hw_is_enabled(hw))
 172                sclk_apply_divider(clk, sclk);
 173
 174        return 0;
 175}
 176
 177static unsigned long sclk_div_recalc_rate(struct clk_hw *hw,
 178                                          unsigned long prate)
 179{
 180        struct clk_regmap *clk = to_clk_regmap(hw);
 181        struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
 182
 183        return DIV_ROUND_UP_ULL((u64)prate, sclk->cached_div);
 184}
 185
 186static int sclk_div_enable(struct clk_hw *hw)
 187{
 188        struct clk_regmap *clk = to_clk_regmap(hw);
 189        struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
 190
 191        sclk_apply_divider(clk, sclk);
 192
 193        return 0;
 194}
 195
 196static void sclk_div_disable(struct clk_hw *hw)
 197{
 198        struct clk_regmap *clk = to_clk_regmap(hw);
 199        struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
 200
 201        meson_parm_write(clk->map, &sclk->div, 0);
 202}
 203
 204static int sclk_div_is_enabled(struct clk_hw *hw)
 205{
 206        struct clk_regmap *clk = to_clk_regmap(hw);
 207        struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
 208
 209        if (meson_parm_read(clk->map, &sclk->div))
 210                return 1;
 211
 212        return 0;
 213}
 214
 215static void sclk_div_init(struct clk_hw *hw)
 216{
 217        struct clk_regmap *clk = to_clk_regmap(hw);
 218        struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
 219        unsigned int val;
 220
 221        val = meson_parm_read(clk->map, &sclk->div);
 222
 223        /* if the divider is initially disabled, assume max */
 224        if (!val)
 225                sclk->cached_div = sclk_div_maxdiv(sclk);
 226        else
 227                sclk->cached_div = val + 1;
 228
 229        sclk_div_get_duty_cycle(hw, &sclk->cached_duty);
 230}
 231
 232const struct clk_ops meson_sclk_div_ops = {
 233        .recalc_rate    = sclk_div_recalc_rate,
 234        .round_rate     = sclk_div_round_rate,
 235        .set_rate       = sclk_div_set_rate,
 236        .enable         = sclk_div_enable,
 237        .disable        = sclk_div_disable,
 238        .is_enabled     = sclk_div_is_enabled,
 239        .get_duty_cycle = sclk_div_get_duty_cycle,
 240        .set_duty_cycle = sclk_div_set_duty_cycle,
 241        .init           = sclk_div_init,
 242};
 243EXPORT_SYMBOL_GPL(meson_sclk_div_ops);
 244