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