aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/clk/meson/Makefile2
-rw-r--r--drivers/clk/meson/gxbb-aoclk-32k.c194
-rw-r--r--drivers/clk/meson/gxbb-aoclk.c21
-rw-r--r--drivers/clk/meson/gxbb-aoclk.h16
4 files changed, 231 insertions, 2 deletions
diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile
index de65427efe9b..b139d41b25da 100644
--- a/drivers/clk/meson/Makefile
+++ b/drivers/clk/meson/Makefile
@@ -4,4 +4,4 @@
4 4
5obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-cpu.o clk-mpll.o clk-audio-divider.o 5obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-cpu.o clk-mpll.o clk-audio-divider.o
6obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o 6obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o
7obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o gxbb-aoclk-regmap.o 7obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o gxbb-aoclk-regmap.o gxbb-aoclk-32k.o
diff --git a/drivers/clk/meson/gxbb-aoclk-32k.c b/drivers/clk/meson/gxbb-aoclk-32k.c
new file mode 100644
index 000000000000..491634dbc985
--- /dev/null
+++ b/drivers/clk/meson/gxbb-aoclk-32k.c
@@ -0,0 +1,194 @@
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
44struct 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
54static 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 */
74static 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
110static 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
123static 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
141static 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
190const 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};
diff --git a/drivers/clk/meson/gxbb-aoclk.c b/drivers/clk/meson/gxbb-aoclk.c
index f61506c53089..6c161e0a8e59 100644
--- a/drivers/clk/meson/gxbb-aoclk.c
+++ b/drivers/clk/meson/gxbb-aoclk.c
@@ -59,6 +59,7 @@
59#include <linux/mfd/syscon.h> 59#include <linux/mfd/syscon.h>
60#include <linux/regmap.h> 60#include <linux/regmap.h>
61#include <linux/init.h> 61#include <linux/init.h>
62#include <linux/delay.h>
62#include <dt-bindings/clock/gxbb-aoclkc.h> 63#include <dt-bindings/clock/gxbb-aoclkc.h>
63#include <dt-bindings/reset/gxbb-aoclkc.h> 64#include <dt-bindings/reset/gxbb-aoclkc.h>
64#include "gxbb-aoclk.h" 65#include "gxbb-aoclk.h"
@@ -105,6 +106,17 @@ GXBB_AO_GATE(uart1, 3);
105GXBB_AO_GATE(uart2, 5); 106GXBB_AO_GATE(uart2, 5);
106GXBB_AO_GATE(ir_blaster, 6); 107GXBB_AO_GATE(ir_blaster, 6);
107 108
109static struct aoclk_cec_32k cec_32k_ao = {
110 .lock = &gxbb_aoclk_lock,
111 .hw.init = &(struct clk_init_data) {
112 .name = "cec_32k_ao",
113 .ops = &meson_aoclk_cec_32k_ops,
114 .parent_names = (const char *[]){ "xtal" },
115 .num_parents = 1,
116 .flags = CLK_IGNORE_UNUSED,
117 },
118};
119
108static unsigned int gxbb_aoclk_reset[] = { 120static unsigned int gxbb_aoclk_reset[] = {
109 [RESET_AO_REMOTE] = 16, 121 [RESET_AO_REMOTE] = 16,
110 [RESET_AO_I2C_MASTER] = 18, 122 [RESET_AO_I2C_MASTER] = 18,
@@ -131,8 +143,9 @@ static struct clk_hw_onecell_data gxbb_aoclk_onecell_data = {
131 [CLKID_AO_UART1] = &uart1_ao.hw, 143 [CLKID_AO_UART1] = &uart1_ao.hw,
132 [CLKID_AO_UART2] = &uart2_ao.hw, 144 [CLKID_AO_UART2] = &uart2_ao.hw,
133 [CLKID_AO_IR_BLASTER] = &ir_blaster_ao.hw, 145 [CLKID_AO_IR_BLASTER] = &ir_blaster_ao.hw,
146 [CLKID_AO_CEC_32K] = &cec_32k_ao.hw,
134 }, 147 },
135 .num = ARRAY_SIZE(gxbb_aoclk_gate), 148 .num = 7,
136}; 149};
137 150
138static int gxbb_aoclkc_probe(struct platform_device *pdev) 151static int gxbb_aoclkc_probe(struct platform_device *pdev)
@@ -172,6 +185,12 @@ static int gxbb_aoclkc_probe(struct platform_device *pdev)
172 return ret; 185 return ret;
173 } 186 }
174 187
188 /* Specific clocks */
189 cec_32k_ao.regmap = regmap;
190 ret = devm_clk_hw_register(dev, &cec_32k_ao.hw);
191 if (ret)
192 return ret;
193
175 return of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, 194 return of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
176 &gxbb_aoclk_onecell_data); 195 &gxbb_aoclk_onecell_data);
177} 196}
diff --git a/drivers/clk/meson/gxbb-aoclk.h b/drivers/clk/meson/gxbb-aoclk.h
index 2e26108d5ba6..e8604c8f7eee 100644
--- a/drivers/clk/meson/gxbb-aoclk.h
+++ b/drivers/clk/meson/gxbb-aoclk.h
@@ -9,7 +9,13 @@
9#define __GXBB_AOCLKC_H 9#define __GXBB_AOCLKC_H
10 10
11/* AO Configuration Clock registers offsets */ 11/* AO Configuration Clock registers offsets */
12#define AO_RTI_PWR_CNTL_REG1 0x0c
13#define AO_RTI_PWR_CNTL_REG0 0x10
12#define AO_RTI_GEN_CNTL_REG0 0x40 14#define AO_RTI_GEN_CNTL_REG0 0x40
15#define AO_OSCIN_CNTL 0x58
16#define AO_CRT_CLK_CNTL1 0x68
17#define AO_RTC_ALT_CLK_CNTL0 0x94
18#define AO_RTC_ALT_CLK_CNTL1 0x98
13 19
14struct aoclk_gate_regmap { 20struct aoclk_gate_regmap {
15 struct clk_hw hw; 21 struct clk_hw hw;
@@ -23,4 +29,14 @@ struct aoclk_gate_regmap {
23 29
24extern const struct clk_ops meson_aoclk_gate_regmap_ops; 30extern const struct clk_ops meson_aoclk_gate_regmap_ops;
25 31
32struct aoclk_cec_32k {
33 struct clk_hw hw;
34 struct regmap *regmap;
35 spinlock_t *lock;
36};
37
38#define to_aoclk_cec_32k(_hw) container_of(_hw, struct aoclk_cec_32k, hw)
39
40extern const struct clk_ops meson_aoclk_cec_32k_ops;
41
26#endif /* __GXBB_AOCLKC_H */ 42#endif /* __GXBB_AOCLKC_H */