1 // SPDX-License-Identifier: GPL-2.0+
3 * Copyright (c) 2017 BayLibre, SAS.
4 * Author: Neil Armstrong <narmstrong@baylibre.com>
7 #include <linux/clk-provider.h>
8 #include <linux/bitfield.h>
9 #include <linux/regmap.h>
10 #include "gxbb-aoclk.h"
13 * The AO Domain embeds a dual/divider to generate a more precise
14 * 32,768KHz clock for low-power suspend mode and CEC.
17 * ______ | Div1 |-| Cnt1 | ______
18 * | | /|______| |______|\ | |
19 * Xtal-->| Gate |---| ______ ______ X-X--| Gate |-->
20 * |______| | \| | | |/ | |______|
21 * | | Div2 |-| Cnt2 | |
22 * | |______| |______| |
23 * |_______________________|
25 * The dividing can be switched to single or dual, with a counter
26 * for each divider to set when the switching is done.
27 * The entire dividing mechanism can be also bypassed.
30 #define CLK_CNTL0_N1_MASK GENMASK(11, 0)
31 #define CLK_CNTL0_N2_MASK GENMASK(23, 12)
32 #define CLK_CNTL0_DUALDIV_EN BIT(28)
33 #define CLK_CNTL0_OUT_GATE_EN BIT(30)
34 #define CLK_CNTL0_IN_GATE_EN BIT(31)
36 #define CLK_CNTL1_M1_MASK GENMASK(11, 0)
37 #define CLK_CNTL1_M2_MASK GENMASK(23, 12)
38 #define CLK_CNTL1_BYPASS_EN BIT(24)
39 #define CLK_CNTL1_SELECT_OSC BIT(27)
41 #define PWR_CNTL_ALT_32K_SEL GENMASK(13, 10)
43 struct cec_32k_freq_table
{
44 unsigned long parent_rate
;
45 unsigned long target_rate
;
53 static const struct cec_32k_freq_table aoclk_cec_32k_table
[] = {
55 .parent_rate
= 24000000,
66 * If CLK_CNTL0_DUALDIV_EN == 0
67 * - will use N1 divider only
68 * If CLK_CNTL0_DUALDIV_EN == 1
69 * - hold M1 cycles of N1 divider then changes to N2
70 * - hold M2 cycles of N2 divider then changes to N1
71 * Then we can get more accurate division.
73 static unsigned long aoclk_cec_32k_recalc_rate(struct clk_hw
*hw
,
74 unsigned long parent_rate
)
76 struct aoclk_cec_32k
*cec_32k
= to_aoclk_cec_32k(hw
);
80 regmap_read(cec_32k
->regmap
, AO_RTC_ALT_CLK_CNTL0
, ®0
);
81 regmap_read(cec_32k
->regmap
, AO_RTC_ALT_CLK_CNTL1
, ®1
);
83 if (reg1
& CLK_CNTL1_BYPASS_EN
)
86 if (reg0
& CLK_CNTL0_DUALDIV_EN
) {
87 unsigned long n2
, m1
, m2
, f1
, f2
, p1
, p2
;
89 n1
= FIELD_GET(CLK_CNTL0_N1_MASK
, reg0
) + 1;
90 n2
= FIELD_GET(CLK_CNTL0_N2_MASK
, reg0
) + 1;
92 m1
= FIELD_GET(CLK_CNTL1_M1_MASK
, reg1
) + 1;
93 m2
= FIELD_GET(CLK_CNTL1_M2_MASK
, reg1
) + 1;
95 f1
= DIV_ROUND_CLOSEST(parent_rate
, n1
);
96 f2
= DIV_ROUND_CLOSEST(parent_rate
, n2
);
98 p1
= DIV_ROUND_CLOSEST(100000000 * m1
, f1
* (m1
+ m2
));
99 p2
= DIV_ROUND_CLOSEST(100000000 * m2
, f2
* (m1
+ m2
));
101 return DIV_ROUND_UP(100000000, p1
+ p2
);
104 n1
= FIELD_GET(CLK_CNTL0_N1_MASK
, reg0
) + 1;
106 return DIV_ROUND_CLOSEST(parent_rate
, n1
);
109 static const struct cec_32k_freq_table
*find_cec_32k_freq(unsigned long rate
,
114 for (i
= 0 ; i
< ARRAY_SIZE(aoclk_cec_32k_table
) ; ++i
)
115 if (aoclk_cec_32k_table
[i
].parent_rate
== prate
&&
116 aoclk_cec_32k_table
[i
].target_rate
== rate
)
117 return &aoclk_cec_32k_table
[i
];
122 static long aoclk_cec_32k_round_rate(struct clk_hw
*hw
, unsigned long rate
,
123 unsigned long *prate
)
125 const struct cec_32k_freq_table
*freq
= find_cec_32k_freq(rate
,
128 /* If invalid return first one */
130 return aoclk_cec_32k_table
[0].target_rate
;
132 return freq
->target_rate
;
136 * From the Amlogic init procedure, the IN and OUT gates needs to be handled
137 * in the init procedure to avoid any glitches.
140 static int aoclk_cec_32k_set_rate(struct clk_hw
*hw
, unsigned long rate
,
141 unsigned long parent_rate
)
143 const struct cec_32k_freq_table
*freq
= find_cec_32k_freq(rate
,
145 struct aoclk_cec_32k
*cec_32k
= to_aoclk_cec_32k(hw
);
152 regmap_update_bits(cec_32k
->regmap
, AO_RTC_ALT_CLK_CNTL0
,
153 CLK_CNTL0_IN_GATE_EN
| CLK_CNTL0_OUT_GATE_EN
, 0);
155 reg
= FIELD_PREP(CLK_CNTL0_N1_MASK
, freq
->n1
- 1);
157 reg
|= CLK_CNTL0_DUALDIV_EN
|
158 FIELD_PREP(CLK_CNTL0_N2_MASK
, freq
->n2
- 1);
160 regmap_write(cec_32k
->regmap
, AO_RTC_ALT_CLK_CNTL0
, reg
);
162 reg
= FIELD_PREP(CLK_CNTL1_M1_MASK
, freq
->m1
- 1);
164 reg
|= FIELD_PREP(CLK_CNTL1_M2_MASK
, freq
->m2
- 1);
166 regmap_write(cec_32k
->regmap
, AO_RTC_ALT_CLK_CNTL1
, reg
);
169 regmap_update_bits(cec_32k
->regmap
, AO_RTC_ALT_CLK_CNTL0
,
170 CLK_CNTL0_IN_GATE_EN
, CLK_CNTL0_IN_GATE_EN
);
174 regmap_update_bits(cec_32k
->regmap
, AO_RTC_ALT_CLK_CNTL0
,
175 CLK_CNTL0_OUT_GATE_EN
, CLK_CNTL0_OUT_GATE_EN
);
177 regmap_update_bits(cec_32k
->regmap
, AO_CRT_CLK_CNTL1
,
178 CLK_CNTL1_SELECT_OSC
, CLK_CNTL1_SELECT_OSC
);
180 /* Select 32k from XTAL */
181 regmap_update_bits(cec_32k
->regmap
,
182 AO_RTI_PWR_CNTL_REG0
,
183 PWR_CNTL_ALT_32K_SEL
,
184 FIELD_PREP(PWR_CNTL_ALT_32K_SEL
, 4));
189 const struct clk_ops meson_aoclk_cec_32k_ops
= {
190 .recalc_rate
= aoclk_cec_32k_recalc_rate
,
191 .round_rate
= aoclk_cec_32k_round_rate
,
192 .set_rate
= aoclk_cec_32k_set_rate
,