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