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 *
meson_clk_dualdiv_data(struct clk_regmap * clk)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
__dualdiv_param_to_rate(unsigned long parent_rate,const struct meson_clk_dualdiv_param * p)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
meson_clk_dualdiv_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)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 *
__dualdiv_get_setting(unsigned long rate,unsigned long parent_rate,struct meson_clk_dualdiv_data * dualdiv)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
meson_clk_dualdiv_determine_rate(struct clk_hw * hw,struct clk_rate_request * req)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
meson_clk_dualdiv_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)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