Maximilian Brune | 2ccb8e7 | 2024-01-14 21:59:27 +0600 | [diff] [blame] | 1 | /* SPDX-License-Identifier: GPL-2.0-only */ |
| 2 | |
| 3 | // This file is used for setting up clocks and get devices out of reset |
| 4 | // For more Information see FU740-C000 Manual Chapter 7 Clocking and Reset |
| 5 | |
Maximilian Brune | 2ccb8e7 | 2024-01-14 21:59:27 +0600 | [diff] [blame] | 6 | #include <delay.h> |
| 7 | #include <device/mmio.h> |
| 8 | #include <soc/addressmap.h> |
| 9 | #include <soc/clock.h> |
| 10 | #include <soc/gpio.h> |
| 11 | #include <gpio.h> |
| 12 | #include <stdint.h> |
| 13 | |
| 14 | // Clock frequencies for the cores, ddr and the peripherals are all derived from the hfclk (high frequency clock) and it is always 26 MHz |
| 15 | #define FU740_HFCLK_FREQ (26 * MHz) |
| 16 | |
| 17 | struct prci_ctlr { |
| 18 | u32 hfxosccfg; // offset 0x00 |
| 19 | u32 core_pllcfg; // offset 0x04 |
| 20 | u32 core_plloutdiv; // offset 0x08 |
| 21 | u32 ddr_pllcfg; // offset 0x0c |
| 22 | u32 ddr_plloutdiv; // offset 0x10 |
| 23 | u32 pcieaux_plloutdiv; // offset 0x14 (undocumented) |
| 24 | u32 reserved18; // offset 0x18 |
| 25 | u32 gemgxl_pllcfg; // offset 0x1c |
| 26 | u32 gemgxl_plloutdiv; // offset 0x20 |
| 27 | u32 core_clk_sel_reg; // offset 0x24 |
| 28 | u32 devices_reset_n; // offset 0x28 |
| 29 | u32 clk_mux_status; // offset 0x2C |
| 30 | u32 cltx_pllcfg; // offset 0x30 chiplink (undocumented) |
| 31 | u32 cltx_plloutdiv; // offset 0x34 chiplink (undocumented) |
| 32 | u32 dvfs_core_pllcfg; // offset 0x38 |
| 33 | u32 dvfs_core_plloutdiv; // offset 0x3C |
| 34 | u32 corepllsel; // offset 0x40 (undocumented, but probably same as last gen) |
| 35 | u8 reserved44[12]; // offset 0x44 |
| 36 | u32 hfpclk_pllcfg; // offset 0x50 |
| 37 | u32 hfpclk_plloutdiv; // offset 0x54 |
| 38 | u32 hfpclkpllsel; // offset 0x58 (undocumented, but probably same as last gen) |
| 39 | u32 hfpclk_div_reg; // offset 0x5C |
| 40 | u8 reserved60[128]; // offset 0x60 |
| 41 | u32 prci_plls; // offset 0xE0 |
| 42 | u8 reservedE4[12]; // offset 0xE4 |
| 43 | u32 procmoncfg_core_clock; // offset 0xF0 (undocumented) |
| 44 | } __packed; |
| 45 | |
| 46 | static struct prci_ctlr *prci = (void *)FU740_PRCI; |
| 47 | |
| 48 | // ================================= |
| 49 | // clock selections |
| 50 | // ================================= |
| 51 | |
| 52 | #define PRCI_COREPLLSEL_MASK 1 |
| 53 | #define PRCI_COREPLLSEL_COREPLL 0 |
| 54 | #define PRCI_COREPLLSEL_DVFSCOREPLL 1 |
| 55 | |
| 56 | #define PRCI_CORECLKSEL_MASK 1 |
| 57 | #define PRCI_CORECLKSEL_CORECLKPLL 0 |
| 58 | #define PRCI_CORECLKSEL_HFCLK 1 |
| 59 | |
| 60 | #define PRCI_HFPCLKSEL_MASK 1 |
| 61 | #define PRCI_HFPCLKSEL_PLL 0 |
| 62 | #define PRCI_HFPCLKSEL_HFCLK 1 |
| 63 | |
| 64 | // =================================== |
| 65 | // pllcfg register format is used by all PLLs |
| 66 | // =================================== |
| 67 | |
| 68 | #define PRCI_PLLCFG_DIVR_SHIFT 0 |
| 69 | #define PRCI_PLLCFG_DIVF_SHIFT 6 |
| 70 | #define PRCI_PLLCFG_DIVQ_SHIFT 15 |
| 71 | #define PRCI_PLLCFG_RANGE_SHIFT 18 |
| 72 | #define PRCI_PLLCFG_BYPASS_SHIFT 24 |
| 73 | #define PRCI_PLLCFG_FSEBYPASS_SHIFT 25 |
| 74 | #define PRCI_PLLCFG_LOCK_SHIFT 31 |
| 75 | |
| 76 | #define PRCI_PLLCFG_DIVR_MASK (0x03f << PRCI_PLLCFG_DIVR_SHIFT) |
| 77 | #define PRCI_PLLCFG_DIVF_MASK (0x1ff << PRCI_PLLCFG_DIVF_SHIFT) |
| 78 | #define PRCI_PLLCFG_DIVQ_MASK (0x007 << PRCI_PLLCFG_DIVQ_SHIFT) |
| 79 | #define PRCI_PLLCFG_RANGE_MASK (0x007 << PRCI_PLLCFG_RANGE_SHIFT) |
| 80 | #define PRCI_PLLCFG_BYPASS_MASK (0x001 << PRCI_PLLCFG_BYPASS_SHIFT) |
| 81 | #define PRCI_PLLCFG_FSEBYPASS_MASK (0x001 << PRCI_PLLCFG_FSEBYPASS_SHIFT) |
| 82 | #define PRCI_PLLCFG_LOCK_MASK (0x001 << PRCI_PLLCFG_LOCK_SHIFT) |
| 83 | |
| 84 | // =================================== |
| 85 | // plloutdiv register formats |
| 86 | // =================================== |
| 87 | |
| 88 | // registered are used to enable/disable PLLs |
| 89 | #define PRCI_DVFSCORE_PLLOUTDIV_MASK (1 << 24) // Note: u-boot and fu740 manual differ here ... |
| 90 | #define PRCI_HFPCLK_PLLOUTDIV_MASK (1 << 31) // Note: according to u-boot it is (1 << 24) but if I use that it gets stuck |
| 91 | #define PRCI_DDR_PLLOUTDIV_MASK (1 << 31) |
| 92 | #define PRCI_GEMGXL_PLLOUTDIV_MASK (1 << 31) |
| 93 | #define PRCI_CLTX_PLLOUTDIV_MASK (1 << 24) // undocumented (chiplink tx) |
| 94 | #define PRCI_PCIEAUX_PLLOUTDIV_MASK (1 << 0) // undocumented |
| 95 | #define PRCI_CORE_PLLOUTDIV_MASK (1 << 31) // undocumented |
| 96 | |
| 97 | // =================================== |
| 98 | // devicereset register formats |
| 99 | // =================================== |
| 100 | |
| 101 | // used to get devices in or out of reset |
| 102 | #define PRCI_DEVICES_RESET_DDR_CTRL_RST (1 << 0) // DDR Controller |
| 103 | #define PRCI_DEVICES_RESET_DDR_AXI_RST (1 << 1) // DDR Controller AXI Interface |
| 104 | #define PRCI_DEVICES_RESET_DDR_AHB_RST (1 << 2) // DDR Controller AHB Interface |
| 105 | #define PRCI_DEVICES_RESET_DDR_PHY_RST (1 << 3) // DDR PHY |
| 106 | #define PRCI_DEVICES_RESET_PCIEAUX_RST (1 << 4) |
| 107 | #define PRCI_DEVICES_RESET_GEMGXL_RST (1 << 5) // Gigabit Ethernet Subsystem |
| 108 | #define PRCI_DEVICES_RESET_CLTX_RST (1 << 6) // chiplink reset (undocumented) |
| 109 | |
| 110 | // =================================== |
| 111 | // prci_plls register format |
| 112 | // =================================== |
| 113 | |
| 114 | // used to check if certain PLLs are present in the SOC |
| 115 | #define PRCI_PLLS_CLTXPLL (1 << 0) |
| 116 | #define PRCI_PLLS_GEMGXLPLL (1 << 1) |
| 117 | #define PRCI_PLLS_DDRPLL (1 << 2) |
| 118 | #define PRCI_PLLS_HFPCLKPLL (1 << 3) |
| 119 | #define PRCI_PLLS_DVFSCOREPLL (1 << 4) |
| 120 | #define PRCI_PLLS_COREPLL (1 << 5) |
| 121 | |
| 122 | // =================================== |
| 123 | // clk_mux_status register format |
| 124 | // =================================== |
| 125 | |
| 126 | // read only register which is used to set some clock multiplex settings |
| 127 | // the value of this register depends on the state of pins connected to the FU740 SOC |
| 128 | // on the hifive-unmatched board the state of the pins is set by a hardware switch |
| 129 | #define PRCI_CLK_MUX_STATUS_CORECLKPLLSEL (1 << 0) |
| 130 | // 0 - HFCLK or CORECLK |
| 131 | // 1 - only HFCLK |
| 132 | #define PRCI_CLK_MUX_STATUS_TLCLKSEL (1 << 1) |
| 133 | // 0 - CORECLK/2 |
| 134 | // 1 - CORECLK |
| 135 | #define PRCI_CLK_MUX_STATUS_RTCXSEL (1 << 2) |
| 136 | // 0 - use HFXCLK for RTC |
| 137 | // 1 - use RTCXALTCLKIN for RTC |
| 138 | #define PRCI_CLK_MUX_STATUS_DDRCTRLCLKSEL (1 << 3) |
| 139 | #define PRCI_CLK_MUX_STATUS_DDRPHYCLKSEL (1 << 4) |
| 140 | #define PRCI_CLK_MUX_STATUS_RESERVED (1 << 5) |
| 141 | #define PRCI_CLK_MUX_STATUS_GEMGXLCLKSEL (1 << 6) |
| 142 | #define PRCI_CLK_MUX_STATUS_MAINMEMCLKSEL (1 << 7) |
| 143 | |
| 144 | // =================================== |
| 145 | // hfxosccfg register format |
| 146 | // =================================== |
| 147 | |
| 148 | #define PRCI_HFXOSCCFG_HFXOSEN (1 << 30) // Crystal oscillator enable |
| 149 | // Note: I guess (it is not documented) |
| 150 | // 0 - XTAL PADS |
| 151 | // 1 - OSC PADS |
| 152 | #define PRCI_HFXOSCCFG_HFXOSCRDY (1 << 31) // Crystal oscillator ready |
| 153 | |
| 154 | struct pll_settings { |
| 155 | unsigned int divr:6; // divider before PLL loop (reference), equal to divr + 1 |
| 156 | unsigned int divf:9; // VCO feedback divider value, equal to 2 * (divf + 1) |
| 157 | unsigned int divq:3; // divider after PLL loop, equal to 2^divq |
| 158 | // PLL filter range (TODO documentation is not really clear on how to set it) |
| 159 | unsigned int range:3; |
| 160 | unsigned int bypass:1; // probably used to bypass the PLL |
| 161 | // internal or external input path (internal = 1, external = 0) |
| 162 | //WARN this is only a guess since it is undocumented |
| 163 | unsigned int fsebypass:1; |
| 164 | }; |
| 165 | |
| 166 | static void configure_pll(u32 *reg, const struct pll_settings *s) |
| 167 | { |
| 168 | // Write the settings to the register |
| 169 | u32 c = read32(reg); |
| 170 | clrsetbits32(&c, PRCI_PLLCFG_DIVR_MASK |
| 171 | | PRCI_PLLCFG_DIVF_MASK |
| 172 | | PRCI_PLLCFG_DIVQ_MASK |
| 173 | | PRCI_PLLCFG_RANGE_MASK |
| 174 | | PRCI_PLLCFG_BYPASS_MASK |
| 175 | | PRCI_PLLCFG_FSEBYPASS_MASK, |
| 176 | (s->divr << PRCI_PLLCFG_DIVR_SHIFT) |
| 177 | | (s->divf << PRCI_PLLCFG_DIVF_SHIFT) |
| 178 | | (s->divq << PRCI_PLLCFG_DIVQ_SHIFT) |
| 179 | | (s->range << PRCI_PLLCFG_RANGE_SHIFT) |
| 180 | | (s->bypass << PRCI_PLLCFG_BYPASS_SHIFT) |
| 181 | | (s->fsebypass << PRCI_PLLCFG_FSEBYPASS_SHIFT)); |
| 182 | write32(reg, c); |
| 183 | |
| 184 | // Wait for PLL lock |
| 185 | while (!(read32(reg) & PRCI_PLLCFG_LOCK_MASK)) |
| 186 | ; |
| 187 | } |
| 188 | |
| 189 | /* |
| 190 | * Section 7.1 recommends a frequency of 1.0 GHz (up to 1.5 GHz is possible) |
| 191 | * Section 7.4.2 provides the necessary values |
| 192 | * |
| 193 | * COREPLL is set up for ~1 GHz output frequency. |
| 194 | * divr = 0 (x1), divf = 76 (x154) => (4004 MHz VCO), divq = 2 (/4 Output divider) |
| 195 | */ |
| 196 | static const struct pll_settings corepll_settings = { |
| 197 | .divr = 0, |
| 198 | .divf = 76, |
| 199 | .divq = 2, |
| 200 | .range = 4, |
| 201 | .bypass = 0, |
| 202 | .fsebypass = 1, // external feedback mode is not supported |
| 203 | }; |
| 204 | |
| 205 | /* |
| 206 | * Section 7.4.3: DDR and Ethernet Subsystem Clocking and Reset |
| 207 | * |
| 208 | * DDRPLL is set up for 933 MHz output frequency. |
| 209 | * divr = 0 (x1), divf = 71 (x144) => (3744 MHz VCO), divq = 2 (/4 output divider) |
| 210 | */ |
| 211 | static const struct pll_settings ddrpll_settings = { |
| 212 | .divr = 0, |
| 213 | .divf = 71, |
| 214 | .divq = 2, |
| 215 | .range = 4, |
| 216 | .bypass = 0, |
| 217 | .fsebypass = 1, // external feedback mode is not supported |
| 218 | }; |
| 219 | |
| 220 | /* |
| 221 | * GEMGXLPLL is set up for 125 MHz output frequency. |
| 222 | * divr = 0 (x1), divf = 76 (x154) => (4004 MHz VCO), divq = 5 (/32 output divider) |
| 223 | */ |
| 224 | static const struct pll_settings gemgxlpll_settings = { |
| 225 | .divr = 0, |
| 226 | .divf = 76, |
| 227 | .divq = 5, |
| 228 | .range = 4, |
| 229 | .bypass = 0, |
| 230 | .fsebypass = 1, // external feedback mode is not supported |
| 231 | }; |
| 232 | |
| 233 | /* |
| 234 | * HFPCLKPLL is set up for 520 MHz output frequency. |
| 235 | * TODO a lower value should also suffice as well as safe some power |
| 236 | * divr = 1 (/2), divf = 39 (x80) => (2080 MHz VCO), divq = 2 (/4 output divider) |
| 237 | */ |
| 238 | static const struct pll_settings hfpclkpll_settings = { |
| 239 | .divr = 1, |
| 240 | //.divf = 122, |
| 241 | .divf = 39, |
| 242 | .divq = 2, |
| 243 | .range = 4, |
| 244 | .bypass = 0, |
| 245 | .fsebypass = 1, // external feedback mode is not supported |
| 246 | }; |
| 247 | |
| 248 | /* |
| 249 | * CLTXCLKPLL is set up for 520 MHz output frequency. |
| 250 | * divr = 1 (/2), divf = 122 (x154) => (4004 MHz VCO), divq = 2 (/4 output divider) |
| 251 | */ |
| 252 | static const struct pll_settings cltxpll_settings = { |
| 253 | .divr = 1, |
| 254 | .divf = 39, |
| 255 | .divq = 2, |
| 256 | .range = 4, |
| 257 | .bypass = 0, |
| 258 | .fsebypass = 1, // external feedback mode is not supported |
| 259 | }; |
| 260 | |
| 261 | static void init_coreclk(void) |
| 262 | { |
| 263 | // we can't modify the coreclk PLL while we are running on it, so let coreclk devise |
| 264 | // its clock from hfclk before modifying PLL |
| 265 | clrsetbits32(&prci->core_clk_sel_reg, PRCI_CORECLKSEL_MASK, PRCI_CORECLKSEL_HFCLK); |
| 266 | |
| 267 | // only configure pll if it is present |
| 268 | if (!(read32(&prci->prci_plls) & PRCI_PLLS_COREPLL)) { |
| 269 | return; |
| 270 | } |
| 271 | |
| 272 | configure_pll(&prci->core_pllcfg, &corepll_settings); |
| 273 | |
| 274 | // switch coreclk multiplexer to use corepll as clock source again |
| 275 | clrsetbits32(&prci->core_clk_sel_reg, PRCI_CORECLKSEL_MASK, PRCI_CORECLKSEL_CORECLKPLL); |
| 276 | } |
| 277 | |
| 278 | static void init_ddrclk(void) |
| 279 | { |
| 280 | // only configure pll if it is present |
| 281 | if (!(read32(&prci->prci_plls) & PRCI_PLLS_DDRPLL)) { |
| 282 | return; |
| 283 | } |
| 284 | |
| 285 | // disable ddr clock output before reconfiguring the PLL |
| 286 | u32 cfg1 = read32(&prci->ddr_plloutdiv); |
| 287 | clrbits32(&cfg1, PRCI_DDR_PLLOUTDIV_MASK); |
| 288 | write32(&prci->ddr_plloutdiv, cfg1); |
| 289 | |
| 290 | configure_pll(&prci->ddr_pllcfg, &ddrpll_settings); |
| 291 | |
| 292 | // PLL is ready/locked so enable it (its gated) |
| 293 | setbits32(&cfg1, PRCI_DDR_PLLOUTDIV_MASK); |
| 294 | write32(&prci->ddr_plloutdiv, cfg1); |
| 295 | } |
| 296 | |
| 297 | static void init_gemgxlclk(void) |
| 298 | { |
| 299 | // only configure pll if it is present |
| 300 | if (!(read32(&prci->prci_plls) & PRCI_PLLS_GEMGXLPLL)) { |
| 301 | return; |
| 302 | } |
| 303 | |
| 304 | // disable gemgxl clock output before reconfiguring the PLL |
| 305 | u32 cfg1 = read32(&prci->gemgxl_plloutdiv); |
| 306 | clrbits32(&cfg1, PRCI_GEMGXL_PLLOUTDIV_MASK); |
| 307 | write32(&prci->gemgxl_plloutdiv, cfg1); |
| 308 | |
| 309 | configure_pll(&prci->gemgxl_pllcfg, &gemgxlpll_settings); |
| 310 | |
| 311 | // PLL is ready/locked so enable it (its gated) |
| 312 | setbits32(&cfg1, PRCI_GEMGXL_PLLOUTDIV_MASK); |
| 313 | write32(&prci->gemgxl_plloutdiv, cfg1); |
| 314 | } |
| 315 | |
| 316 | /* |
| 317 | * Configure High Frequency peripheral clock which is used by |
| 318 | * UART, SPI, GPIO, I2C and PWM subsystem |
| 319 | */ |
| 320 | static void init_hfpclk(void) |
| 321 | { |
| 322 | // we can't modify the hfpclk PLL while we are running on it, so let pclk devise |
| 323 | // its clock from hfclk before modifying PLL |
| 324 | u32 hfpclksel = read32(&prci->hfpclkpllsel); |
| 325 | hfpclksel |= PRCI_HFPCLKSEL_HFCLK; |
| 326 | write32(&prci->hfpclkpllsel, hfpclksel); |
| 327 | |
| 328 | configure_pll(&prci->hfpclk_pllcfg, &hfpclkpll_settings); |
| 329 | |
| 330 | // PLL is ready/locked so enable it (its gated) |
| 331 | u32 hfpclk_plloutdiv = read32(&prci->hfpclk_plloutdiv); |
| 332 | hfpclk_plloutdiv |= PRCI_HFPCLK_PLLOUTDIV_MASK; |
| 333 | write32(&prci->hfpclk_plloutdiv, hfpclk_plloutdiv); |
| 334 | |
| 335 | mdelay(1); |
| 336 | |
| 337 | // switch to using PLL for hfpclk |
| 338 | clrbits32(&prci->hfpclkpllsel, PRCI_HFPCLKSEL_MASK); |
| 339 | |
| 340 | udelay(70); |
| 341 | } |
| 342 | |
| 343 | static void reset_deassert(u8 reset_index) |
| 344 | { |
| 345 | u32 device_reset = read32(&prci->devices_reset_n); |
| 346 | device_reset |= reset_index; |
| 347 | write32(&prci->devices_reset_n, device_reset); |
| 348 | } |
| 349 | |
| 350 | static void init_cltx(void) |
| 351 | { |
| 352 | // disable hfpclkpll before configuring it |
| 353 | u32 cfg1 = read32(&prci->cltx_plloutdiv); |
| 354 | clrbits32(&cfg1, PRCI_CLTX_PLLOUTDIV_MASK); |
| 355 | write32(&prci->cltx_plloutdiv, cfg1); |
| 356 | |
| 357 | configure_pll(&prci->cltx_pllcfg, &cltxpll_settings); |
| 358 | |
| 359 | // PLL is ready/locked so enable it (its gated) |
| 360 | setbits32(&cfg1, PRCI_CLTX_PLLOUTDIV_MASK); |
| 361 | write32(&prci->cltx_plloutdiv, cfg1); |
| 362 | |
| 363 | // get chiplink out of reset |
| 364 | reset_deassert(PRCI_DEVICES_RESET_CLTX_RST); |
| 365 | |
| 366 | udelay(70); |
| 367 | } |
| 368 | |
| 369 | void clock_init(void) |
| 370 | { |
| 371 | // first configure the coreclk (used by HARTs) to get maximum speed early on |
| 372 | init_coreclk(); |
| 373 | |
| 374 | // put all devices in reset (e.g. DDR, ethernet, pcie) before configuring their clocks |
| 375 | write32(&prci->devices_reset_n, 0); |
| 376 | |
| 377 | // initialize clock used by DDR subsystem |
| 378 | init_ddrclk(); |
| 379 | |
| 380 | // get DDR controller out of reset |
| 381 | reset_deassert(PRCI_DEVICES_RESET_DDR_CTRL_RST); |
| 382 | |
| 383 | // wait at least one full DDR controller clock cycle |
| 384 | asm volatile ("fence"); |
| 385 | |
| 386 | // get DDR controller (register interface) out of reset |
| 387 | // get DDR subsystem PHY out of reset |
| 388 | reset_deassert(PRCI_DEVICES_RESET_DDR_AXI_RST | |
| 389 | PRCI_DEVICES_RESET_DDR_AHB_RST | |
| 390 | PRCI_DEVICES_RESET_DDR_PHY_RST); |
| 391 | |
| 392 | // we need to wait 256 full ddrctrl clock cycles until we can interact with the DDR subsystem |
| 393 | for (int i = 0; i < 256; i++) |
| 394 | asm volatile ("nop"); |
| 395 | |
| 396 | if (read32(&prci->prci_plls) & PRCI_PLLS_HFPCLKPLL) { |
| 397 | // set hfclk as reference for peripheral clock since we don't have the PLL |
| 398 | //clrsetbits32(&prci->hfpclkpllsel, PRCI_HFPCLKSEL_MASK, PRCI_HFPCLKSEL_HFCLK); |
| 399 | init_hfpclk(); |
| 400 | } else if (read32(&prci->prci_plls) & PRCI_PLLS_CLTXPLL) { |
| 401 | // Note: this path has never been tested since the platforms tested with |
| 402 | // always have HFPCLKPLL |
| 403 | init_cltx(); |
| 404 | // get chiplink out of reset |
| 405 | reset_deassert(PRCI_DEVICES_RESET_CLTX_RST); |
| 406 | } |
| 407 | |
| 408 | // GEMGXL init VSC8541 PHY reset sequence; |
| 409 | gpio_set_direction(GEMGXL_RST, GPIO_OUTPUT); |
| 410 | gpio_set(GEMGXL_RST, 1); |
| 411 | |
| 412 | udelay(1); |
| 413 | |
| 414 | /* Reset PHY again to enter unmanaged mode */ |
| 415 | gpio_set(GEMGXL_RST, 0); |
| 416 | udelay(1); |
| 417 | gpio_set(GEMGXL_RST, 1); |
| 418 | mdelay(15); |
| 419 | |
| 420 | init_gemgxlclk(); |
| 421 | |
| 422 | // get ethernet out of reset |
| 423 | reset_deassert(PRCI_DEVICES_RESET_GEMGXL_RST); |
| 424 | } |
| 425 | |
| 426 | // get the peripheral clock frequency used by UART (probably also SPI, GPIO, I2C and PWM) |
| 427 | int clock_get_pclk(void) |
| 428 | { |
| 429 | u64 pclk = FU740_HFCLK_FREQ; |
| 430 | |
| 431 | // check if hfpclkpll is present and |
| 432 | // check if hfpclkpll is selected in the multiplexer TODO |
| 433 | // check if hpfclkpll is enabled |
| 434 | if ((read32(&prci->prci_plls) & PRCI_PLLS_HFPCLKPLL) && |
| 435 | (read32(&prci->hfpclk_plloutdiv) & PRCI_HFPCLK_PLLOUTDIV_MASK)) { |
| 436 | int hfpclk_pllcfg = read32(&prci->hfpclk_pllcfg); |
| 437 | int divr = (hfpclk_pllcfg & PRCI_PLLCFG_DIVR_MASK) >> PRCI_PLLCFG_DIVR_SHIFT; |
| 438 | int divf = (hfpclk_pllcfg & PRCI_PLLCFG_DIVF_MASK) >> PRCI_PLLCFG_DIVF_SHIFT; |
| 439 | int divq = (hfpclk_pllcfg & PRCI_PLLCFG_DIVQ_MASK) >> PRCI_PLLCFG_DIVQ_SHIFT; |
| 440 | pclk /= (divr + 1); // reference divider |
| 441 | pclk *= (2 * (divf + 1)); // feedback divider |
| 442 | pclk /= (1 << divq); // output divider |
| 443 | } |
| 444 | |
| 445 | // divider value before pclk seems to be (hfpclkdiv + 2). Not mentioned in fu740 manual though. |
| 446 | return pclk / (read32(&prci->hfpclk_div_reg) + 2); |
| 447 | } |