Linux 4.19.133
[linux/fpc-iii.git] / drivers / clk / meson / gxbb-aoclk-32k.c
blob680467141a1d6d0b1a12a4f71972222aadf2a507
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Copyright (c) 2017 BayLibre, SAS.
4 * Author: Neil Armstrong <narmstrong@baylibre.com>
5 */
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.
15 * ______ ______
16 * | | | |
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;
46 bool dualdiv;
47 unsigned int n1;
48 unsigned int n2;
49 unsigned int m1;
50 unsigned int m2;
53 static const struct cec_32k_freq_table aoclk_cec_32k_table[] = {
54 [0] = {
55 .parent_rate = 24000000,
56 .target_rate = 32768,
57 .dualdiv = true,
58 .n1 = 733,
59 .n2 = 732,
60 .m1 = 8,
61 .m2 = 11,
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);
77 unsigned long n1;
78 u32 reg0, reg1;
80 regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, &reg0);
81 regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, &reg1);
83 if (reg1 & CLK_CNTL1_BYPASS_EN)
84 return parent_rate;
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,
110 unsigned long prate)
112 int i;
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];
119 return NULL;
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,
126 *prate);
128 /* If invalid return first one */
129 if (!freq)
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,
144 parent_rate);
145 struct aoclk_cec_32k *cec_32k = to_aoclk_cec_32k(hw);
146 u32 reg = 0;
148 if (!freq)
149 return -EINVAL;
151 /* Disable clock */
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);
156 if (freq->dualdiv)
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);
163 if (freq->dualdiv)
164 reg |= FIELD_PREP(CLK_CNTL1_M2_MASK, freq->m2 - 1);
166 regmap_write(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, reg);
168 /* Enable clock */
169 regmap_update_bits(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0,
170 CLK_CNTL0_IN_GATE_EN, CLK_CNTL0_IN_GATE_EN);
172 udelay(200);
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));
186 return 0;
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,