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