root/drivers/clk/meson/clk-dualdiv.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. meson_clk_dualdiv_data
  2. __dualdiv_param_to_rate
  3. meson_clk_dualdiv_recalc_rate
  4. __dualdiv_get_setting
  5. meson_clk_dualdiv_round_rate
  6. meson_clk_dualdiv_set_rate

   1 // SPDX-License-Identifier: GPL-2.0
   2 /*
   3  * Copyright (c) 2017 BayLibre, SAS
   4  * Author: Neil Armstrong <narmstrong@baylibre.com>
   5  * Author: Jerome Brunet <jbrunet@baylibre.com>
   6  */
   7 
   8 /*
   9  * The AO Domain embeds a dual/divider to generate a more precise
  10  * 32,768KHz clock for low-power suspend mode and CEC.
  11  *     ______   ______
  12  *    |      | |      |
  13  *    | Div1 |-| Cnt1 |
  14  *   /|______| |______|\
  15  * -|  ______   ______  X--> Out
  16  *   \|      | |      |/
  17  *    | Div2 |-| Cnt2 |
  18  *    |______| |______|
  19  *
  20  * The dividing can be switched to single or dual, with a counter
  21  * for each divider to set when the switching is done.
  22  */
  23 
  24 #include <linux/clk-provider.h>
  25 #include <linux/module.h>
  26 
  27 #include "clk-regmap.h"
  28 #include "clk-dualdiv.h"
  29 
  30 static inline struct meson_clk_dualdiv_data *
  31 meson_clk_dualdiv_data(struct clk_regmap *clk)
  32 {
  33         return (struct meson_clk_dualdiv_data *)clk->data;
  34 }
  35 
  36 static unsigned long
  37 __dualdiv_param_to_rate(unsigned long parent_rate,
  38                         const struct meson_clk_dualdiv_param *p)
  39 {
  40         if (!p->dual)
  41                 return DIV_ROUND_CLOSEST(parent_rate, p->n1);
  42 
  43         return DIV_ROUND_CLOSEST(parent_rate * (p->m1 + p->m2),
  44                                  p->n1 * p->m1 + p->n2 * p->m2);
  45 }
  46 
  47 static unsigned long meson_clk_dualdiv_recalc_rate(struct clk_hw *hw,
  48                                                    unsigned long parent_rate)
  49 {
  50         struct clk_regmap *clk = to_clk_regmap(hw);
  51         struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
  52         struct meson_clk_dualdiv_param setting;
  53 
  54         setting.dual = meson_parm_read(clk->map, &dualdiv->dual);
  55         setting.n1 = meson_parm_read(clk->map, &dualdiv->n1) + 1;
  56         setting.m1 = meson_parm_read(clk->map, &dualdiv->m1) + 1;
  57         setting.n2 = meson_parm_read(clk->map, &dualdiv->n2) + 1;
  58         setting.m2 = meson_parm_read(clk->map, &dualdiv->m2) + 1;
  59 
  60         return __dualdiv_param_to_rate(parent_rate, &setting);
  61 }
  62 
  63 static const struct meson_clk_dualdiv_param *
  64 __dualdiv_get_setting(unsigned long rate, unsigned long parent_rate,
  65                       struct meson_clk_dualdiv_data *dualdiv)
  66 {
  67         const struct meson_clk_dualdiv_param *table = dualdiv->table;
  68         unsigned long best = 0, now = 0;
  69         unsigned int i, best_i = 0;
  70 
  71         if (!table)
  72                 return NULL;
  73 
  74         for (i = 0; table[i].n1; i++) {
  75                 now = __dualdiv_param_to_rate(parent_rate, &table[i]);
  76 
  77                 /* If we get an exact match, don't bother any further */
  78                 if (now == rate) {
  79                         return &table[i];
  80                 } else if (abs(now - rate) < abs(best - rate)) {
  81                         best = now;
  82                         best_i = i;
  83                 }
  84         }
  85 
  86         return (struct meson_clk_dualdiv_param *)&table[best_i];
  87 }
  88 
  89 static long meson_clk_dualdiv_round_rate(struct clk_hw *hw, unsigned long rate,
  90                                          unsigned long *parent_rate)
  91 {
  92         struct clk_regmap *clk = to_clk_regmap(hw);
  93         struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
  94         const struct meson_clk_dualdiv_param *setting =
  95                 __dualdiv_get_setting(rate, *parent_rate, dualdiv);
  96 
  97         if (!setting)
  98                 return meson_clk_dualdiv_recalc_rate(hw, *parent_rate);
  99 
 100         return __dualdiv_param_to_rate(*parent_rate, setting);
 101 }
 102 
 103 static int meson_clk_dualdiv_set_rate(struct clk_hw *hw, unsigned long rate,
 104                                       unsigned long parent_rate)
 105 {
 106         struct clk_regmap *clk = to_clk_regmap(hw);
 107         struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
 108         const struct meson_clk_dualdiv_param *setting =
 109                 __dualdiv_get_setting(rate, parent_rate, dualdiv);
 110 
 111         if (!setting)
 112                 return -EINVAL;
 113 
 114         meson_parm_write(clk->map, &dualdiv->dual, setting->dual);
 115         meson_parm_write(clk->map, &dualdiv->n1, setting->n1 - 1);
 116         meson_parm_write(clk->map, &dualdiv->m1, setting->m1 - 1);
 117         meson_parm_write(clk->map, &dualdiv->n2, setting->n2 - 1);
 118         meson_parm_write(clk->map, &dualdiv->m2, setting->m2 - 1);
 119 
 120         return 0;
 121 }
 122 
 123 const struct clk_ops meson_clk_dualdiv_ops = {
 124         .recalc_rate    = meson_clk_dualdiv_recalc_rate,
 125         .round_rate     = meson_clk_dualdiv_round_rate,
 126         .set_rate       = meson_clk_dualdiv_set_rate,
 127 };
 128 EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ops);
 129 
 130 const struct clk_ops meson_clk_dualdiv_ro_ops = {
 131         .recalc_rate    = meson_clk_dualdiv_recalc_rate,
 132 };
 133 EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ro_ops);
 134 
 135 MODULE_DESCRIPTION("Amlogic dual divider driver");
 136 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
 137 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
 138 MODULE_LICENSE("GPL v2");

/* [<][>][^][v][top][bottom][index][help] */