1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (c) 2017 BayLibre, SAS.
4  * Author: Neil Armstrong <narmstrong@baylibre.com>
5  */
6 
7 #include <linux/clk-provider.h>
8 #include <linux/bitfield.h>
9 #include <linux/regmap.h>
10 #include "gxbb-aoclk.h"
11 
12 /*
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  *                 |_______________________|
24  *
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.
28  */
29 
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)
35 
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)
40 
41 #define PWR_CNTL_ALT_32K_SEL	GENMASK(13, 10)
42 
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;
51 };
52 
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,
62 	},
63 };
64 
65 /*
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.
72  */
aoclk_cec_32k_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)73 static unsigned long aoclk_cec_32k_recalc_rate(struct clk_hw *hw,
74 					       unsigned long parent_rate)
75 {
76 	struct aoclk_cec_32k *cec_32k = to_aoclk_cec_32k(hw);
77 	unsigned long n1;
78 	u32 reg0, reg1;
79 
80 	regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, &reg0);
81 	regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, &reg1);
82 
83 	if (reg1 & CLK_CNTL1_BYPASS_EN)
84 		return parent_rate;
85 
86 	if (reg0 & CLK_CNTL0_DUALDIV_EN) {
87 		unsigned long n2, m1, m2, f1, f2, p1, p2;
88 
89 		n1 = FIELD_GET(CLK_CNTL0_N1_MASK, reg0) + 1;
90 		n2 = FIELD_GET(CLK_CNTL0_N2_MASK, reg0) + 1;
91 
92 		m1 = FIELD_GET(CLK_CNTL1_M1_MASK, reg1) + 1;
93 		m2 = FIELD_GET(CLK_CNTL1_M2_MASK, reg1) + 1;
94 
95 		f1 = DIV_ROUND_CLOSEST(parent_rate, n1);
96 		f2 = DIV_ROUND_CLOSEST(parent_rate, n2);
97 
98 		p1 = DIV_ROUND_CLOSEST(100000000 * m1, f1 * (m1 + m2));
99 		p2 = DIV_ROUND_CLOSEST(100000000 * m2, f2 * (m1 + m2));
100 
101 		return DIV_ROUND_UP(100000000, p1 + p2);
102 	}
103 
104 	n1 = FIELD_GET(CLK_CNTL0_N1_MASK, reg0) + 1;
105 
106 	return DIV_ROUND_CLOSEST(parent_rate, n1);
107 }
108 
find_cec_32k_freq(unsigned long rate,unsigned long prate)109 static const struct cec_32k_freq_table *find_cec_32k_freq(unsigned long rate,
110 							  unsigned long prate)
111 {
112 	int i;
113 
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];
118 
119 	return NULL;
120 }
121 
aoclk_cec_32k_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * prate)122 static long aoclk_cec_32k_round_rate(struct clk_hw *hw, unsigned long rate,
123 				     unsigned long *prate)
124 {
125 	const struct cec_32k_freq_table *freq = find_cec_32k_freq(rate,
126 								  *prate);
127 
128 	/* If invalid return first one */
129 	if (!freq)
130 		return aoclk_cec_32k_table[0].target_rate;
131 
132 	return freq->target_rate;
133 }
134 
135 /*
136  * From the Amlogic init procedure, the IN and OUT gates needs to be handled
137  * in the init procedure to avoid any glitches.
138  */
139 
aoclk_cec_32k_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)140 static int aoclk_cec_32k_set_rate(struct clk_hw *hw, unsigned long rate,
141 				  unsigned long parent_rate)
142 {
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;
147 
148 	if (!freq)
149 		return -EINVAL;
150 
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);
154 
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);
159 
160 	regmap_write(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, reg);
161 
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);
165 
166 	regmap_write(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, reg);
167 
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);
171 
172 	udelay(200);
173 
174 	regmap_update_bits(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0,
175 			   CLK_CNTL0_OUT_GATE_EN, CLK_CNTL0_OUT_GATE_EN);
176 
177 	regmap_update_bits(cec_32k->regmap, AO_CRT_CLK_CNTL1,
178 			   CLK_CNTL1_SELECT_OSC, CLK_CNTL1_SELECT_OSC);
179 
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));
185 
186 	return 0;
187 }
188 
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,
193 };
194