Commit | Line | Data |
---|---|---|
a8d552a6 JB |
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> | |
889c2b7e JB |
25 | #include <linux/module.h> |
26 | ||
27 | #include "clk-regmap.h" | |
28 | #include "clk-dualdiv.h" | |
a8d552a6 JB |
29 | |
30 | static inline struct meson_clk_dualdiv_data * | |
31 | meson_clk_dualdiv_data(struct clk_regmap *clk) | |
32 | { | |
33 | return (struct meson_clk_dualdiv_data *)clk->data; | |
34 | } | |
35 | ||
36 | static 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 | ||
47 | static 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 | ||
63 | static 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 | ||
89 | static 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 | ||
103 | static 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 | ||
123 | const 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 | }; | |
128 | EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ops); | |
129 | ||
130 | const struct clk_ops meson_clk_dualdiv_ro_ops = { | |
131 | .recalc_rate = meson_clk_dualdiv_recalc_rate, | |
132 | }; | |
133 | EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ro_ops); | |
889c2b7e JB |
134 | |
135 | MODULE_DESCRIPTION("Amlogic dual divider driver"); | |
136 | MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); | |
137 | MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); | |
138 | MODULE_LICENSE("GPL v2"); |