linux/drivers/clk/meson/clk-dualdiv.c
<<
>>
Prefs
   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
  30static inline struct meson_clk_dualdiv_data *
  31meson_clk_dualdiv_data(struct clk_regmap *clk)
  32{
  33        return (struct meson_clk_dualdiv_data *)clk->data;
  34}
  35
  36static 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
  47static 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
  63static 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
  89static 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
 103static 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
 123const 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};
 128EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ops);
 129
 130const struct clk_ops meson_clk_dualdiv_ro_ops = {
 131        .recalc_rate    = meson_clk_dualdiv_recalc_rate,
 132};
 133EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ro_ops);
 134
 135MODULE_DESCRIPTION("Amlogic dual divider driver");
 136MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
 137MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
 138MODULE_LICENSE("GPL v2");
 139