diff options
-rw-r--r-- | drivers/clk/meson/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/meson/gxbb-aoclk-32k.c | 194 | ||||
-rw-r--r-- | drivers/clk/meson/gxbb-aoclk.c | 21 | ||||
-rw-r--r-- | drivers/clk/meson/gxbb-aoclk.h | 16 |
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 | ||
5 | obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-cpu.o clk-mpll.o clk-audio-divider.o | 5 | obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-cpu.o clk-mpll.o clk-audio-divider.o |
6 | obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o | 6 | obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o |
7 | obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o gxbb-aoclk-regmap.o | 7 | obj-$(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 | |||
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 | */ | ||
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, ®0); | ||
82 | regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, ®1); | ||
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 | |||
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 | |||
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 | |||
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 | }; | ||
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); | |||
105 | GXBB_AO_GATE(uart2, 5); | 106 | GXBB_AO_GATE(uart2, 5); |
106 | GXBB_AO_GATE(ir_blaster, 6); | 107 | GXBB_AO_GATE(ir_blaster, 6); |
107 | 108 | ||
109 | static 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 | |||
108 | static unsigned int gxbb_aoclk_reset[] = { | 120 | static 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 | ||
138 | static int gxbb_aoclkc_probe(struct platform_device *pdev) | 151 | static 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 | ||
14 | struct aoclk_gate_regmap { | 20 | struct aoclk_gate_regmap { |
15 | struct clk_hw hw; | 21 | struct clk_hw hw; |
@@ -23,4 +29,14 @@ struct aoclk_gate_regmap { | |||
23 | 29 | ||
24 | extern const struct clk_ops meson_aoclk_gate_regmap_ops; | 30 | extern const struct clk_ops meson_aoclk_gate_regmap_ops; |
25 | 31 | ||
32 | struct 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 | |||
40 | extern const struct clk_ops meson_aoclk_cec_32k_ops; | ||
41 | |||
26 | #endif /* __GXBB_AOCLKC_H */ | 42 | #endif /* __GXBB_AOCLKC_H */ |