Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Copyright (c) 2017 BayLibre, SAS
0004  * Author: Neil Armstrong <narmstrong@baylibre.com>
0005  * Author: Jerome Brunet <jbrunet@baylibre.com>
0006  */
0007 
0008 /*
0009  * The AO Domain embeds a dual/divider to generate a more precise
0010  * 32,768KHz clock for low-power suspend mode and CEC.
0011  *     ______   ______
0012  *    |      | |      |
0013  *    | Div1 |-| Cnt1 |
0014  *   /|______| |______|\
0015  * -|  ______   ______  X--> Out
0016  *   \|      | |      |/
0017  *    | Div2 |-| Cnt2 |
0018  *    |______| |______|
0019  *
0020  * The dividing can be switched to single or dual, with a counter
0021  * for each divider to set when the switching is done.
0022  */
0023 
0024 #include <linux/clk-provider.h>
0025 #include <linux/module.h>
0026 
0027 #include "clk-regmap.h"
0028 #include "clk-dualdiv.h"
0029 
0030 static inline struct meson_clk_dualdiv_data *
0031 meson_clk_dualdiv_data(struct clk_regmap *clk)
0032 {
0033     return (struct meson_clk_dualdiv_data *)clk->data;
0034 }
0035 
0036 static unsigned long
0037 __dualdiv_param_to_rate(unsigned long parent_rate,
0038             const struct meson_clk_dualdiv_param *p)
0039 {
0040     if (!p->dual)
0041         return DIV_ROUND_CLOSEST(parent_rate, p->n1);
0042 
0043     return DIV_ROUND_CLOSEST(parent_rate * (p->m1 + p->m2),
0044                  p->n1 * p->m1 + p->n2 * p->m2);
0045 }
0046 
0047 static unsigned long meson_clk_dualdiv_recalc_rate(struct clk_hw *hw,
0048                            unsigned long parent_rate)
0049 {
0050     struct clk_regmap *clk = to_clk_regmap(hw);
0051     struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
0052     struct meson_clk_dualdiv_param setting;
0053 
0054     setting.dual = meson_parm_read(clk->map, &dualdiv->dual);
0055     setting.n1 = meson_parm_read(clk->map, &dualdiv->n1) + 1;
0056     setting.m1 = meson_parm_read(clk->map, &dualdiv->m1) + 1;
0057     setting.n2 = meson_parm_read(clk->map, &dualdiv->n2) + 1;
0058     setting.m2 = meson_parm_read(clk->map, &dualdiv->m2) + 1;
0059 
0060     return __dualdiv_param_to_rate(parent_rate, &setting);
0061 }
0062 
0063 static const struct meson_clk_dualdiv_param *
0064 __dualdiv_get_setting(unsigned long rate, unsigned long parent_rate,
0065               struct meson_clk_dualdiv_data *dualdiv)
0066 {
0067     const struct meson_clk_dualdiv_param *table = dualdiv->table;
0068     unsigned long best = 0, now = 0;
0069     unsigned int i, best_i = 0;
0070 
0071     if (!table)
0072         return NULL;
0073 
0074     for (i = 0; table[i].n1; i++) {
0075         now = __dualdiv_param_to_rate(parent_rate, &table[i]);
0076 
0077         /* If we get an exact match, don't bother any further */
0078         if (now == rate) {
0079             return &table[i];
0080         } else if (abs(now - rate) < abs(best - rate)) {
0081             best = now;
0082             best_i = i;
0083         }
0084     }
0085 
0086     return (struct meson_clk_dualdiv_param *)&table[best_i];
0087 }
0088 
0089 static long meson_clk_dualdiv_round_rate(struct clk_hw *hw, unsigned long rate,
0090                      unsigned long *parent_rate)
0091 {
0092     struct clk_regmap *clk = to_clk_regmap(hw);
0093     struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
0094     const struct meson_clk_dualdiv_param *setting =
0095         __dualdiv_get_setting(rate, *parent_rate, dualdiv);
0096 
0097     if (!setting)
0098         return meson_clk_dualdiv_recalc_rate(hw, *parent_rate);
0099 
0100     return __dualdiv_param_to_rate(*parent_rate, setting);
0101 }
0102 
0103 static int meson_clk_dualdiv_set_rate(struct clk_hw *hw, unsigned long rate,
0104                       unsigned long parent_rate)
0105 {
0106     struct clk_regmap *clk = to_clk_regmap(hw);
0107     struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
0108     const struct meson_clk_dualdiv_param *setting =
0109         __dualdiv_get_setting(rate, parent_rate, dualdiv);
0110 
0111     if (!setting)
0112         return -EINVAL;
0113 
0114     meson_parm_write(clk->map, &dualdiv->dual, setting->dual);
0115     meson_parm_write(clk->map, &dualdiv->n1, setting->n1 - 1);
0116     meson_parm_write(clk->map, &dualdiv->m1, setting->m1 - 1);
0117     meson_parm_write(clk->map, &dualdiv->n2, setting->n2 - 1);
0118     meson_parm_write(clk->map, &dualdiv->m2, setting->m2 - 1);
0119 
0120     return 0;
0121 }
0122 
0123 const struct clk_ops meson_clk_dualdiv_ops = {
0124     .recalc_rate    = meson_clk_dualdiv_recalc_rate,
0125     .round_rate = meson_clk_dualdiv_round_rate,
0126     .set_rate   = meson_clk_dualdiv_set_rate,
0127 };
0128 EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ops);
0129 
0130 const struct clk_ops meson_clk_dualdiv_ro_ops = {
0131     .recalc_rate    = meson_clk_dualdiv_recalc_rate,
0132 };
0133 EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ro_ops);
0134 
0135 MODULE_DESCRIPTION("Amlogic dual divider driver");
0136 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
0137 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
0138 MODULE_LICENSE("GPL v2");