1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "aml-cpufreq.h"
6 #include "aml-fclk.h"
7 #include "hiu-registers.h"
8 #include <ddk/debug.h>
9 #include <unistd.h>
10
11 namespace thermal {
12
13 namespace {
14
15 #define SYS_CPU_WAIT_BUSY_RETRIES 5
16 #define SYS_CPU_WAIT_BUSY_TIMEOUT_US 10000
17
18 // CLK indexes.
19 constexpr uint32_t kSysPllDiv16 = 0;
20 constexpr uint32_t kSysCpuClkDiv16 = 1;
21
22 // MMIO indexes.
23 constexpr uint32_t kHiuMmio = 2;
24
25 // 1GHz Frequency.
26 constexpr uint32_t kFrequencyThreshold = 1000000000;
27
28 // 1.896GHz Frequency.
29 constexpr uint32_t kMaxCPUFrequency = 1896000000;
30
31 // Final Mux for selecting clock source.
32 constexpr uint32_t kFixedPll = 0;
33 constexpr uint32_t kSysPll = 1;
34
35 } // namespace
36
InitPdev(zx_device_t * parent)37 zx_status_t AmlCpuFrequency::InitPdev(zx_device_t* parent) {
38 pdev_ = ddk::PDev(parent);
39 if (!pdev_.is_valid()) {
40 zxlogf(ERROR, "aml-cpufreq: failed to get clk protocol\n");
41 return ZX_ERR_NO_RESOURCES;
42 }
43
44 // Get the clock protocol
45 clk_ = ddk::ClkProtocolClient(parent);
46 if (!clk_.is_valid()) {
47 zxlogf(ERROR, "aml-cpufreq: failed to get clk protocol\n");
48 return ZX_ERR_NO_RESOURCES;
49 }
50
51 // Initialized the MMIOs
52 zx_status_t status = pdev_.MapMmio(kHiuMmio, &hiu_mmio_);
53 if (status != ZX_OK) {
54 zxlogf(ERROR, "aml-cpufreq: could not map periph mmio: %d\n", status);
55 return status;
56 }
57
58 // Get BTI handle.
59 status = pdev_.GetBti(0, &bti_);
60 if (status != ZX_OK) {
61 zxlogf(ERROR, "aml-cpufreq: could not get BTI handle: %d\n", status);
62 return status;
63 }
64
65 return ZX_OK;
66 }
67
Init(zx_device_t * parent)68 zx_status_t AmlCpuFrequency::Init(zx_device_t* parent) {
69 zx_status_t status = InitPdev(parent);
70 if (status != ZX_OK) {
71 return status;
72 }
73
74 // HIU Init.
75 status = s905d2_hiu_init(&hiu_);
76 if (status != ZX_OK) {
77 zxlogf(ERROR, "aml-cpufreq: hiu_init failed: %d\n", status);
78 return status;
79 }
80
81 // Enable the following clocks so we can measure them
82 // and calculate what the actual CPU freq is set to at
83 // any given point.
84 status = clk_.Enable(kSysPllDiv16);
85 if (status != ZX_OK) {
86 zxlogf(ERROR, "aml-cpufreq: failed to enable clock, status = %d\n", status);
87 return status;
88 }
89
90 status = clk_.Enable(kSysCpuClkDiv16);
91 if (status != ZX_OK) {
92 zxlogf(ERROR, "aml-cpufreq: failed to enable clock, status = %d\n", status);
93 return status;
94 }
95
96 // Set up CPU freq. frequency to 1GHz.
97 // Once we switch to using the MPLL, we re-initialize the SYS PLL
98 // to known values and then the thermal driver can take over the dynamic
99 // switching.
100 status = SetFrequency(kFrequencyThreshold);
101 if (status != ZX_OK) {
102 zxlogf(ERROR, "aml-cpufreq: failed to set CPU freq, status = %d\n", status);
103 return status;
104 }
105
106 // SYS PLL Init.
107 status = s905d2_pll_init(&hiu_, &sys_pll_, SYS_PLL);
108 if (status != ZX_OK) {
109 zxlogf(ERROR, "aml-cpufreq: s905d2_pll_init failed: %d\n", status);
110 return status;
111 }
112
113 // Set the SYS PLL to some known rate, before enabling the PLL.
114 status = s905d2_pll_set_rate(&sys_pll_, kMaxCPUFrequency);
115 if (status != ZX_OK) {
116 zxlogf(ERROR, "aml-cpufreq: failed to set SYS_PLL rate, status = %d\n", status);
117 return status;
118 }
119
120 // Enable SYS PLL.
121 status = s905d2_pll_ena(&sys_pll_);
122 if (status != ZX_OK) {
123 zxlogf(ERROR, "aml-cpufreq: s905d2_pll_ena failed: %d\n", status);
124 return status;
125 }
126
127 return ZX_OK;
128 }
129
WaitForBusy()130 zx_status_t AmlCpuFrequency::WaitForBusy() {
131 auto sys_cpu_ctrl0 = SysCpuClkControl0::Get().ReadFrom(&*hiu_mmio_);
132
133 // Wait till we are not busy.
134 for (uint32_t i = 0; i < SYS_CPU_WAIT_BUSY_RETRIES; i++) {
135 sys_cpu_ctrl0 = SysCpuClkControl0::Get().ReadFrom(&*hiu_mmio_);
136 if (sys_cpu_ctrl0.busy()) {
137 // Wait a little bit before trying again.
138 zx_nanosleep(zx_deadline_after(ZX_USEC(SYS_CPU_WAIT_BUSY_TIMEOUT_US)));
139 continue;
140 } else {
141 return ZX_OK;
142 }
143 }
144 return ZX_ERR_TIMED_OUT;
145 }
146
147 // NOTE: This block doesn't modify the MPLL, it just programs the muxes &
148 // dividers to get the new_rate in the sys_pll_div block. Refer fig. 6.6 Multi
149 // Phase PLLS for A53 in the datasheet.
ConfigureFixedPLL(uint32_t new_rate)150 zx_status_t AmlCpuFrequency::ConfigureFixedPLL(uint32_t new_rate) {
151 const aml_fclk_rate_table_t* fclk_rate_table = s905d2_fclk_get_rate_table();
152 size_t rate_count = s905d2_fclk_get_rate_table_count();
153 size_t i;
154
155 // Validate if the new_rate is available
156 for (i = 0; i < rate_count; i++) {
157 if (new_rate == fclk_rate_table[i].rate) {
158 break;
159 }
160 }
161 if (i == rate_count) {
162 return ZX_ERR_NOT_SUPPORTED;
163 }
164
165 zx_status_t status = WaitForBusy();
166 if (status != ZX_OK) {
167 zxlogf(ERROR, "aml-cpufreq: failed to wait for busy, status = %d\n", status);
168 return status;
169 }
170
171 // Now program the values into sys cpu clk control0
172 auto sys_cpu_ctrl0 = SysCpuClkControl0::Get().ReadFrom(&*hiu_mmio_);
173
174 if (sys_cpu_ctrl0.final_dyn_mux_sel()) {
175 // Dynamic mux 1 is in use, we setup dynamic mux 0
176 sys_cpu_ctrl0.set_final_dyn_mux_sel(0)
177 .set_mux0_divn_tcnt(fclk_rate_table[i].mux_div)
178 .set_postmux0(fclk_rate_table[i].postmux)
179 .set_premux0(fclk_rate_table[i].premux);
180 } else {
181 // Dynamic mux 0 is in use, we setup dynamic mux 1
182 sys_cpu_ctrl0.set_final_dyn_mux_sel(1)
183 .set_mux1_divn_tcnt(fclk_rate_table[i].mux_div)
184 .set_postmux1(fclk_rate_table[i].postmux)
185 .set_premux1(fclk_rate_table[i].premux);
186 }
187
188 // Select the final mux.
189 sys_cpu_ctrl0.set_final_mux_sel(kFixedPll).WriteTo(&*hiu_mmio_);
190
191 current_rate_ = new_rate;
192 return ZX_OK;
193 }
194
ConfigureSysPLL(uint32_t new_rate)195 zx_status_t AmlCpuFrequency::ConfigureSysPLL(uint32_t new_rate) {
196 // This API also validates if the new_rate is valid.
197 // So no need to validate it here.
198 zx_status_t status = s905d2_pll_set_rate(&sys_pll_, new_rate);
199 if (status != ZX_OK) {
200 zxlogf(ERROR, "aml-cpufreq: failed to set SYS_PLL rate, status = %d\n", status);
201 return status;
202 }
203
204 // Now we need to change the final mux to select input as SYS_PLL.
205 status = WaitForBusy();
206 if (status != ZX_OK) {
207 zxlogf(ERROR, "aml-cpufreq: failed to wait for busy, status = %d\n", status);
208 return status;
209 }
210
211 // Select the final mux.
212 auto sys_cpu_ctrl0 = SysCpuClkControl0::Get().ReadFrom(&*hiu_mmio_);
213 sys_cpu_ctrl0.set_final_mux_sel(kSysPll).WriteTo(&*hiu_mmio_);
214
215 current_rate_ = new_rate;
216 return status;
217 }
218
SetFrequency(uint32_t new_rate)219 zx_status_t AmlCpuFrequency::SetFrequency(uint32_t new_rate) {
220 zx_status_t status;
221
222 if (new_rate > kFrequencyThreshold && current_rate_ > kFrequencyThreshold) {
223 // Switching between two frequencies both higher than 1GHz.
224 // In this case, as per the datasheet it is recommended to change
225 // to a frequency lower than 1GHz first and then switch to higher
226 // frequency to avoid glitches.
227
228 // Let's first switch to 1GHz
229 status = SetFrequency(kFrequencyThreshold);
230 if (status != ZX_OK) {
231 zxlogf(ERROR, "aml-cpufreq: failed to set CPU freq to intermediate freq, status = %d\n",
232 status);
233 return status;
234 }
235
236 // Now let's set SYS_PLL rate to new_rate.
237 return ConfigureSysPLL(new_rate);
238
239 } else if (new_rate > kFrequencyThreshold && current_rate_ <= kFrequencyThreshold) {
240 // Switching from a frequency lower than 1GHz to one greater than 1GHz.
241 // In this case we just need to set the SYS_PLL to required rate and
242 // then set the final mux to 1 (to select SYS_PLL as the source.)
243
244 // Now let's set SYS_PLL rate to new_rate.
245 return ConfigureSysPLL(new_rate);
246
247 } else {
248 // Switching between two frequencies below 1GHz.
249 // In this case we change the source and dividers accordingly
250 // to get the required rate from MPLL and do not touch the
251 // final mux.
252 return ConfigureFixedPLL(new_rate);
253 }
254 return ZX_OK;
255 }
256
GetFrequency()257 uint32_t AmlCpuFrequency::GetFrequency() {
258 return current_rate_;
259 }
260
261 } // namespace thermal
262