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