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 long meson_clk_dualdiv_round_rate(struct clk_hw
*hw
, unsigned long rate
,
90 unsigned long *parent_rate
)
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
);
98 return meson_clk_dualdiv_recalc_rate(hw
, *parent_rate
);
100 return __dualdiv_param_to_rate(*parent_rate
, setting
);
103 static int meson_clk_dualdiv_set_rate(struct clk_hw
*hw
, unsigned long rate
,
104 unsigned long parent_rate
)
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
);
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);
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
,
128 EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ops
);
130 const struct clk_ops meson_clk_dualdiv_ro_ops
= {
131 .recalc_rate
= meson_clk_dualdiv_recalc_rate
,
133 EXPORT_SYMBOL_GPL(meson_clk_dualdiv_ro_ops
);
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");