123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright (c) 2017 BayLibre, SAS
- * Author: Neil Armstrong <[email protected]>
- * Author: Jerome Brunet <[email protected]>
- */
- /*
- * The AO Domain embeds a dual/divider to generate a more precise
- * 32,768KHz clock for low-power suspend mode and CEC.
- * ______ ______
- * | | | |
- * | Div1 |-| Cnt1 |
- * /|______| |______|\
- * -| ______ ______ X--> Out
- * \| | | |/
- * | Div2 |-| Cnt2 |
- * |______| |______|
- *
- * The dividing can be switched to single or dual, with a counter
- * for each divider to set when the switching is done.
- */
- #include <linux/clk-provider.h>
- #include <linux/module.h>
- #include "clk-regmap.h"
- #include "clk-dualdiv.h"
- static inline struct meson_clk_dualdiv_data *
- meson_clk_dualdiv_data(struct clk_regmap *clk)
- {
- return (struct meson_clk_dualdiv_data *)clk->data;
- }
- static unsigned long
- __dualdiv_param_to_rate(unsigned long parent_rate,
- const struct meson_clk_dualdiv_param *p)
- {
- if (!p->dual)
- return DIV_ROUND_CLOSEST(parent_rate, p->n1);
- return DIV_ROUND_CLOSEST(parent_rate * (p->m1 + p->m2),
- p->n1 * p->m1 + p->n2 * p->m2);
- }
- static unsigned long meson_clk_dualdiv_recalc_rate(struct clk_hw *hw,
- unsigned long parent_rate)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
- struct meson_clk_dualdiv_param setting;
- setting.dual = meson_parm_read(clk->map, &dualdiv->dual);
- setting.n1 = meson_parm_read(clk->map, &dualdiv->n1) + 1;
- setting.m1 = meson_parm_read(clk->map, &dualdiv->m1) + 1;
- setting.n2 = meson_parm_read(clk->map, &dualdiv->n2) + 1;
- setting.m2 = meson_parm_read(clk->map, &dualdiv->m2) + 1;
- return __dualdiv_param_to_rate(parent_rate, &setting);
- }
- static const struct meson_clk_dualdiv_param *
- __dualdiv_get_setting(unsigned long rate, unsigned long parent_rate,
- struct meson_clk_dualdiv_data *dualdiv)
- {
- const struct meson_clk_dualdiv_param *table = dualdiv->table;
- unsigned long best = 0, now = 0;
- unsigned int i, best_i = 0;
- if (!table)
- return NULL;
- for (i = 0; table[i].n1; i++) {
- now = __dualdiv_param_to_rate(parent_rate, &table[i]);
- /* If we get an exact match, don't bother any further */
- if (now == rate) {
- return &table[i];
- } else if (abs(now - rate) < abs(best - rate)) {
- best = now;
- best_i = i;
- }
- }
- return (struct meson_clk_dualdiv_param *)&table[best_i];
- }
- static long meson_clk_dualdiv_round_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long *parent_rate)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
- const struct meson_clk_dualdiv_param *setting =
- __dualdiv_get_setting(rate, *parent_rate, dualdiv);
- if (!setting)
- return meson_clk_dualdiv_recalc_rate(hw, *parent_rate);
- return __dualdiv_param_to_rate(*parent_rate, setting);
- }
- static int meson_clk_dualdiv_set_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long parent_rate)
- {
- struct clk_regmap *clk = to_clk_regmap(hw);
- struct meson_clk_dualdiv_data *dualdiv = meson_clk_dualdiv_data(clk);
- const struct meson_clk_dualdiv_param *setting =
- __dualdiv_get_setting(rate, parent_rate, dualdiv);
- if (!setting)
- return -EINVAL;
- meson_parm_write(clk->map, &dualdiv->dual, setting->dual);
- meson_parm_write(clk->map, &dualdiv->n1, setting->n1 - 1);
- meson_parm_write(clk->map, &dualdiv->m1, setting->m1 - 1);
- meson_parm_write(clk->map, &dualdiv->n2, setting->n2 - 1);
- meson_parm_write(clk->map, &dualdiv->m2, setting->m2 - 1);
- return 0;
- }
- const struct clk_ops meson_clk_dualdiv_ops = {
- .recalc_rate = meson_clk_dualdiv_recalc_rate,
- .round_rate = meson_clk_dualdiv_round_rate,
- .set_rate = meson_clk_dualdiv_set_rate,
- };
- EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ops);
- const struct clk_ops meson_clk_dualdiv_ro_ops = {
- .recalc_rate = meson_clk_dualdiv_recalc_rate,
- };
- EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ro_ops);
- MODULE_DESCRIPTION("Amlogic dual divider driver");
- MODULE_AUTHOR("Neil Armstrong <[email protected]>");
- MODULE_AUTHOR("Jerome Brunet <[email protected]>");
- MODULE_LICENSE("GPL v2");
|