• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2017 BayLibre, SAS.
3  * Author: Neil Armstrong <narmstrong@baylibre.com>
4  *
5  * SPDX-License-Identifier: GPL-2.0+
6  */
7 
8 #include <linux/clk-provider.h>
9 #include <linux/bitfield.h>
10 #include <linux/regmap.h>
11 #include "gxbb-aoclk.h"
12 
13 /*
14  * The AO Domain embeds a dual/divider to generate a more precise
15  * 32,768KHz clock for low-power suspend mode and CEC.
16  *                      ______   ______
17  *                     |      | |      |
18  *         ______      | Div1 |-| Cnt1 |       ______
19  *        |      |    /|______| |______|\     |      |
20  * Xtal-->| Gate |---|  ______   ______  X-X--| Gate |-->
21  *        |______| |  \|      | |      |/  |  |______|
22  *                 |   | Div2 |-| Cnt2 |   |
23  *                 |   |______| |______|   |
24  *                 |_______________________|
25  *
26  * The dividing can be switched to single or dual, with a counter
27  * for each divider to set when the switching is done.
28  * The entire dividing mechanism can be also bypassed.
29  */
30 
31 #define CLK_CNTL0_N1_MASK	GENMASK(11, 0)
32 #define CLK_CNTL0_N2_MASK	GENMASK(23, 12)
33 #define CLK_CNTL0_DUALDIV_EN	BIT(28)
34 #define CLK_CNTL0_OUT_GATE_EN	BIT(30)
35 #define CLK_CNTL0_IN_GATE_EN	BIT(31)
36 
37 #define CLK_CNTL1_M1_MASK	GENMASK(11, 0)
38 #define CLK_CNTL1_M2_MASK	GENMASK(23, 12)
39 #define CLK_CNTL1_BYPASS_EN	BIT(24)
40 #define CLK_CNTL1_SELECT_OSC	BIT(27)
41 
42 #define PWR_CNTL_ALT_32K_SEL	GENMASK(13, 10)
43 
44 struct cec_32k_freq_table {
45 	unsigned long parent_rate;
46 	unsigned long target_rate;
47 	bool dualdiv;
48 	unsigned int n1;
49 	unsigned int n2;
50 	unsigned int m1;
51 	unsigned int m2;
52 };
53 
54 static const struct cec_32k_freq_table aoclk_cec_32k_table[] = {
55 	[0] = {
56 		.parent_rate = 24000000,
57 		.target_rate = 32768,
58 		.dualdiv = true,
59 		.n1 = 733,
60 		.n2 = 732,
61 		.m1 = 8,
62 		.m2 = 11,
63 	},
64 };
65 
66 /*
67  * If CLK_CNTL0_DUALDIV_EN == 0
68  *  - will use N1 divider only
69  * If CLK_CNTL0_DUALDIV_EN == 1
70  *  - hold M1 cycles of N1 divider then changes to N2
71  *  - hold M2 cycles of N2 divider then changes to N1
72  * Then we can get more accurate division.
73  */
aoclk_cec_32k_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)74 static unsigned long aoclk_cec_32k_recalc_rate(struct clk_hw *hw,
75 					       unsigned long parent_rate)
76 {
77 	struct aoclk_cec_32k *cec_32k = to_aoclk_cec_32k(hw);
78 	unsigned long n1;
79 	u32 reg0, reg1;
80 
81 	regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, &reg0);
82 	regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, &reg1);
83 
84 	if (reg1 & CLK_CNTL1_BYPASS_EN)
85 		return parent_rate;
86 
87 	if (reg0 & CLK_CNTL0_DUALDIV_EN) {
88 		unsigned long n2, m1, m2, f1, f2, p1, p2;
89 
90 		n1 = FIELD_GET(CLK_CNTL0_N1_MASK, reg0) + 1;
91 		n2 = FIELD_GET(CLK_CNTL0_N2_MASK, reg0) + 1;
92 
93 		m1 = FIELD_GET(CLK_CNTL1_M1_MASK, reg1) + 1;
94 		m2 = FIELD_GET(CLK_CNTL1_M2_MASK, reg1) + 1;
95 
96 		f1 = DIV_ROUND_CLOSEST(parent_rate, n1);
97 		f2 = DIV_ROUND_CLOSEST(parent_rate, n2);
98 
99 		p1 = DIV_ROUND_CLOSEST(100000000 * m1, f1 * (m1 + m2));
100 		p2 = DIV_ROUND_CLOSEST(100000000 * m2, f2 * (m1 + m2));
101 
102 		return DIV_ROUND_UP(100000000, p1 + p2);
103 	}
104 
105 	n1 = FIELD_GET(CLK_CNTL0_N1_MASK, reg0) + 1;
106 
107 	return DIV_ROUND_CLOSEST(parent_rate, n1);
108 }
109 
find_cec_32k_freq(unsigned long rate,unsigned long prate)110 static const struct cec_32k_freq_table *find_cec_32k_freq(unsigned long rate,
111 							  unsigned long prate)
112 {
113 	int i;
114 
115 	for (i = 0 ; i < ARRAY_SIZE(aoclk_cec_32k_table) ; ++i)
116 		if (aoclk_cec_32k_table[i].parent_rate == prate &&
117 		    aoclk_cec_32k_table[i].target_rate == rate)
118 			return &aoclk_cec_32k_table[i];
119 
120 	return NULL;
121 }
122 
aoclk_cec_32k_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * prate)123 static long aoclk_cec_32k_round_rate(struct clk_hw *hw, unsigned long rate,
124 				     unsigned long *prate)
125 {
126 	const struct cec_32k_freq_table *freq = find_cec_32k_freq(rate,
127 								  *prate);
128 
129 	/* If invalid return first one */
130 	if (!freq)
131 		return aoclk_cec_32k_table[0].target_rate;
132 
133 	return freq->target_rate;
134 }
135 
136 /*
137  * From the Amlogic init procedure, the IN and OUT gates needs to be handled
138  * in the init procedure to avoid any glitches.
139  */
140 
aoclk_cec_32k_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)141 static int aoclk_cec_32k_set_rate(struct clk_hw *hw, unsigned long rate,
142 				  unsigned long parent_rate)
143 {
144 	const struct cec_32k_freq_table *freq = find_cec_32k_freq(rate,
145 								  parent_rate);
146 	struct aoclk_cec_32k *cec_32k = to_aoclk_cec_32k(hw);
147 	u32 reg = 0;
148 
149 	if (!freq)
150 		return -EINVAL;
151 
152 	/* Disable clock */
153 	regmap_update_bits(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0,
154 			   CLK_CNTL0_IN_GATE_EN | CLK_CNTL0_OUT_GATE_EN, 0);
155 
156 	reg = FIELD_PREP(CLK_CNTL0_N1_MASK, freq->n1 - 1);
157 	if (freq->dualdiv)
158 		reg |= CLK_CNTL0_DUALDIV_EN |
159 		       FIELD_PREP(CLK_CNTL0_N2_MASK, freq->n2 - 1);
160 
161 	regmap_write(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, reg);
162 
163 	reg = FIELD_PREP(CLK_CNTL1_M1_MASK, freq->m1 - 1);
164 	if (freq->dualdiv)
165 		reg |= FIELD_PREP(CLK_CNTL1_M2_MASK, freq->m2 - 1);
166 
167 	regmap_write(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, reg);
168 
169 	/* Enable clock */
170 	regmap_update_bits(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0,
171 			   CLK_CNTL0_IN_GATE_EN, CLK_CNTL0_IN_GATE_EN);
172 
173 	udelay(200);
174 
175 	regmap_update_bits(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0,
176 			   CLK_CNTL0_OUT_GATE_EN, CLK_CNTL0_OUT_GATE_EN);
177 
178 	regmap_update_bits(cec_32k->regmap, AO_CRT_CLK_CNTL1,
179 			   CLK_CNTL1_SELECT_OSC, CLK_CNTL1_SELECT_OSC);
180 
181 	/* Select 32k from XTAL */
182 	regmap_update_bits(cec_32k->regmap,
183 			  AO_RTI_PWR_CNTL_REG0,
184 			  PWR_CNTL_ALT_32K_SEL,
185 			  FIELD_PREP(PWR_CNTL_ALT_32K_SEL, 4));
186 
187 	return 0;
188 }
189 
190 const struct clk_ops meson_aoclk_cec_32k_ops = {
191 	.recalc_rate = aoclk_cec_32k_recalc_rate,
192 	.round_rate = aoclk_cec_32k_round_rate,
193 	.set_rate = aoclk_cec_32k_set_rate,
194 };
195