1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (c) 2017 BayLibre, SAS
4 * Author: Neil Armstrong <narmstrong@baylibre.com>
5 * Author: Jerome Brunet <jbrunet@baylibre.com>
9 * The AO Domain embeds a dual/divider to generate a more precise
10 * 32,768KHz clock for low-power suspend mode and CEC.
15 * -| ______ ______ X--> Out
20 * The dividing can be switched to single or dual, with a counter
21 * for each divider to set when the switching is done.
24 #include <linux/clk-provider.h>
25 #include <linux/module.h>
27 #include "clk-regmap.h"
28 #include "clk-dualdiv.h"
30 static inline struct meson_clk_dualdiv_data
*
31 meson_clk_dualdiv_data(struct clk_regmap
*clk
)
33 return (struct meson_clk_dualdiv_data
*)clk
->data
;
37 __dualdiv_param_to_rate(unsigned long parent_rate
,
38 const struct meson_clk_dualdiv_param
*p
)
41 return DIV_ROUND_CLOSEST(parent_rate
, p
->n1
);
43 return DIV_ROUND_CLOSEST(parent_rate
* (p
->m1
+ p
->m2
),
44 p
->n1
* p
->m1
+ p
->n2
* p
->m2
);
47 static unsigned long meson_clk_dualdiv_recalc_rate(struct clk_hw
*hw
,
48 unsigned long parent_rate
)
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
;
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;
60 return __dualdiv_param_to_rate(parent_rate
, &setting
);
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
)
67 const struct meson_clk_dualdiv_param
*table
= dualdiv
->table
;
68 unsigned long best
= 0, now
= 0;
69 unsigned int i
, best_i
= 0;
74 for (i
= 0; table
[i
].n1
; i
++) {
75 now
= __dualdiv_param_to_rate(parent_rate
, &table
[i
]);
77 /* If we get an exact match, don't bother any further */
80 } else if (abs(now
- rate
) < abs(best
- rate
)) {
86 return (struct meson_clk_dualdiv_param
*)&table
[best_i
];
89 static int meson_clk_dualdiv_determine_rate(struct clk_hw
*hw
,
90 struct clk_rate_request
*req
)
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
;
96 setting
= __dualdiv_get_setting(req
->rate
, req
->best_parent_rate
,
99 req
->rate
= __dualdiv_param_to_rate(req
->best_parent_rate
,
102 req
->rate
= meson_clk_dualdiv_recalc_rate(hw
,
103 req
->best_parent_rate
);
108 static int meson_clk_dualdiv_set_rate(struct clk_hw
*hw
, unsigned long rate
,
109 unsigned long parent_rate
)
111 struct clk_regmap
*clk
= to_clk_regmap(hw
);
112 struct meson_clk_dualdiv_data
*dualdiv
= meson_clk_dualdiv_data(clk
);
113 const struct meson_clk_dualdiv_param
*setting
=
114 __dualdiv_get_setting(rate
, parent_rate
, dualdiv
);
119 meson_parm_write(clk
->map
, &dualdiv
->dual
, setting
->dual
);
120 meson_parm_write(clk
->map
, &dualdiv
->n1
, setting
->n1
- 1);
121 meson_parm_write(clk
->map
, &dualdiv
->m1
, setting
->m1
- 1);
122 meson_parm_write(clk
->map
, &dualdiv
->n2
, setting
->n2
- 1);
123 meson_parm_write(clk
->map
, &dualdiv
->m2
, setting
->m2
- 1);
128 const struct clk_ops meson_clk_dualdiv_ops
= {
129 .recalc_rate
= meson_clk_dualdiv_recalc_rate
,
130 .determine_rate
= meson_clk_dualdiv_determine_rate
,
131 .set_rate
= meson_clk_dualdiv_set_rate
,
133 EXPORT_SYMBOL_NS_GPL(meson_clk_dualdiv_ops
, "CLK_MESON");
135 const struct clk_ops meson_clk_dualdiv_ro_ops
= {
136 .recalc_rate
= meson_clk_dualdiv_recalc_rate
,
138 EXPORT_SYMBOL_NS_GPL(meson_clk_dualdiv_ro_ops
, "CLK_MESON");
140 MODULE_DESCRIPTION("Amlogic dual divider driver");
141 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
142 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
143 MODULE_LICENSE("GPL");
144 MODULE_IMPORT_NS("CLK_MESON");