| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string.h> |
| #include <zircon/assert.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include <ddk/debug.h> |
| #include <ddk/mmio-buffer.h> |
| #include <hw/reg.h> |
| #include <soc/aml-s905d2/s905d2-hiu-regs.h> |
| #include <soc/aml-s905d2/s905d2-hiu.h> |
| #include <soc/aml-s905d2/s905d2-hw.h> |
| |
| static inline uint32_t hiu_clk_get_reg(aml_hiu_dev_t* dev, uint32_t offset) { |
| return MmioRead32((MMIO_PTR uint32_t*)(dev->regs_vaddr + offset)); |
| } |
| |
| static inline uint32_t hiu_clk_set_reg(aml_hiu_dev_t* dev, uint32_t offset, uint32_t value) { |
| MmioWrite32(value, (MMIO_PTR uint32_t*)(dev->regs_vaddr + offset)); |
| return hiu_clk_get_reg(dev, offset); |
| } |
| |
| static inline uint32_t hiu_get_pll_offs(aml_pll_dev_t* pll_dev) { |
| switch (pll_dev->pll_num) { |
| case GP0_PLL: |
| return HHI_GP0_PLL_CNTL0; |
| case PCIE_PLL: |
| return HHI_PCIE_PLL_CNTL0; |
| case HIFI_PLL: |
| return HHI_HIFI_PLL_CNTL0; |
| case SYS_PLL: |
| return HHI_SYS_PLL_CNTL0; |
| case SYS1_PLL: |
| return HHI_SYS1_PLL_CNTL0; |
| default: |
| ZX_DEBUG_ASSERT(0); |
| } |
| return 0; |
| } |
| |
| zx_status_t s905d2_hiu_init(aml_hiu_dev_t* device) { |
| // Please do not use get_root_resource() in new code. See fxbug.dev/31358. |
| zx_handle_t resource = get_root_resource(); |
| zx_status_t status; |
| |
| status = mmio_buffer_init_physical(&device->mmio, S905D2_HIU_BASE, S905D2_HIU_LENGTH, resource, |
| ZX_CACHE_POLICY_UNCACHED_DEVICE); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: mmio_buffer_init_physical failed %d", __func__, status); |
| return status; |
| } |
| device->regs_vaddr = device->mmio.vaddr; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t s905d2_hiu_init_etc(aml_hiu_dev_t* device, MMIO_PTR uint8_t* hiubase) { |
| memset(device, 0, sizeof(*device)); |
| |
| device->mmio.vmo = ZX_HANDLE_INVALID; |
| |
| device->regs_vaddr = hiubase; |
| |
| return ZX_OK; |
| } |
| |
| static zx_status_t s905d2_pll_init_regs(aml_pll_dev_t* pll_dev) { |
| aml_hiu_dev_t* device = pll_dev->hiu; |
| |
| if (pll_dev->pll_num == HIFI_PLL) { |
| // write config values |
| hiu_clk_set_reg(device, HHI_HIFI_PLL_CNTL1, G12A_HIFI_PLL_CNTL1); |
| hiu_clk_set_reg(device, HHI_HIFI_PLL_CNTL2, G12A_HIFI_PLL_CNTL2); |
| hiu_clk_set_reg(device, HHI_HIFI_PLL_CNTL3, G12A_HIFI_PLL_CNTL3); |
| hiu_clk_set_reg(device, HHI_HIFI_PLL_CNTL4, G12A_HIFI_PLL_CNTL4); |
| hiu_clk_set_reg(device, HHI_HIFI_PLL_CNTL5, G12A_HIFI_PLL_CNTL5); |
| hiu_clk_set_reg(device, HHI_HIFI_PLL_CNTL6, G12A_HIFI_PLL_CNTL6); |
| zx_nanosleep(zx_deadline_after(ZX_USEC(10))); |
| return ZX_OK; |
| } else if (pll_dev->pll_num == SYS_PLL) { |
| // write config values |
| hiu_clk_set_reg(device, HHI_SYS_PLL_CNTL1, G12A_SYS_PLL_CNTL1); |
| hiu_clk_set_reg(device, HHI_SYS_PLL_CNTL2, G12A_SYS_PLL_CNTL2); |
| hiu_clk_set_reg(device, HHI_SYS_PLL_CNTL3, G12A_SYS_PLL_CNTL3); |
| hiu_clk_set_reg(device, HHI_SYS_PLL_CNTL4, G12A_SYS_PLL_CNTL4); |
| hiu_clk_set_reg(device, HHI_SYS_PLL_CNTL5, G12A_SYS_PLL_CNTL5); |
| hiu_clk_set_reg(device, HHI_SYS_PLL_CNTL6, G12A_SYS_PLL_CNTL6); |
| zx_nanosleep(zx_deadline_after(ZX_USEC(10))); |
| return ZX_OK; |
| } else if (pll_dev->pll_num == SYS1_PLL) { |
| // write config values |
| hiu_clk_set_reg(device, HHI_SYS1_PLL_CNTL1, G12A_SYS1_PLL_CNTL1); |
| hiu_clk_set_reg(device, HHI_SYS1_PLL_CNTL2, G12A_SYS1_PLL_CNTL2); |
| hiu_clk_set_reg(device, HHI_SYS1_PLL_CNTL3, G12A_SYS1_PLL_CNTL3); |
| hiu_clk_set_reg(device, HHI_SYS1_PLL_CNTL4, G12A_SYS1_PLL_CNTL4); |
| hiu_clk_set_reg(device, HHI_SYS1_PLL_CNTL5, G12A_SYS1_PLL_CNTL5); |
| hiu_clk_set_reg(device, HHI_SYS1_PLL_CNTL6, G12A_SYS1_PLL_CNTL6); |
| zx_nanosleep(zx_deadline_after(ZX_USEC(10))); |
| return ZX_OK; |
| } else if (pll_dev->pll_num == GP0_PLL) { |
| // write config values |
| hiu_clk_set_reg(device, HHI_GP0_PLL_CNTL1, G12A_GP0_PLL_CNTL1); |
| hiu_clk_set_reg(device, HHI_GP0_PLL_CNTL2, G12A_GP0_PLL_CNTL2); |
| hiu_clk_set_reg(device, HHI_GP0_PLL_CNTL3, G12A_GP0_PLL_CNTL3); |
| hiu_clk_set_reg(device, HHI_GP0_PLL_CNTL4, G12A_GP0_PLL_CNTL4); |
| hiu_clk_set_reg(device, HHI_GP0_PLL_CNTL5, G12A_GP0_PLL_CNTL5); |
| hiu_clk_set_reg(device, HHI_GP0_PLL_CNTL6, G12A_GP0_PLL_CNTL6); |
| zx_nanosleep(zx_deadline_after(ZX_USEC(10))); |
| return ZX_OK; |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| void s905d2_pll_init_etc(aml_hiu_dev_t* device, aml_pll_dev_t* pll_dev, hhi_plls_t pll_num) { |
| ZX_DEBUG_ASSERT(device); |
| ZX_DEBUG_ASSERT(pll_dev); |
| |
| pll_dev->hiu = device; |
| |
| pll_dev->rate_table = s905d2_pll_get_rate_table(pll_num); |
| pll_dev->rate_idx = 0; |
| pll_dev->frequency = 0; |
| pll_dev->pll_num = pll_num; |
| pll_dev->rate_count = s905d2_get_rate_table_count(pll_num); |
| |
| ZX_DEBUG_ASSERT(pll_dev->rate_table); |
| ZX_DEBUG_ASSERT(pll_dev->rate_count); |
| } |
| |
| zx_status_t s905d2_pll_init(aml_hiu_dev_t* device, aml_pll_dev_t* pll_dev, hhi_plls_t pll_num) { |
| ZX_DEBUG_ASSERT(device); |
| ZX_DEBUG_ASSERT(pll_dev); |
| |
| s905d2_pll_init_etc(device, pll_dev, pll_num); |
| |
| // Disable and reset the pll |
| s905d2_pll_disable(pll_dev); |
| // Write configuration registers |
| return s905d2_pll_init_regs(pll_dev); |
| } |
| |
| bool s905d2_pll_disable(aml_pll_dev_t* pll_dev) { |
| uint32_t offs = hiu_get_pll_offs(pll_dev); |
| uint32_t ctl0 = hiu_clk_get_reg(pll_dev->hiu, offs); |
| |
| bool retval = ctl0 & HHI_PLL_CNTL0_EN; |
| |
| ctl0 = (ctl0 & ~HHI_PLL_CNTL0_EN) | HHI_PLL_CNTL0_RESET; |
| hiu_clk_set_reg(pll_dev->hiu, offs, ctl0); |
| |
| return retval; |
| } |
| |
| zx_status_t s905d2_pll_ena(aml_pll_dev_t* pll_dev) { |
| ZX_DEBUG_ASSERT(pll_dev); |
| |
| uint32_t offs = hiu_get_pll_offs(pll_dev); |
| uint32_t reg_val = hiu_clk_get_reg(pll_dev->hiu, offs); |
| |
| // Set Enable bit |
| reg_val |= HHI_PLL_CNTL0_EN; |
| hiu_clk_set_reg(pll_dev->hiu, offs, reg_val); |
| zx_nanosleep(zx_deadline_after(ZX_USEC(50))); |
| |
| // Clear Reset bit |
| reg_val &= ~HHI_PLL_CNTL0_RESET; |
| hiu_clk_set_reg(pll_dev->hiu, offs, reg_val); |
| |
| uint32_t wait_count = 100; |
| while (wait_count) { |
| if (hiu_clk_get_reg(pll_dev->hiu, offs) & HHI_PLL_LOCK) { |
| return ZX_OK; |
| } |
| zx_nanosleep(zx_deadline_after(ZX_USEC(10))); |
| wait_count--; |
| } |
| |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| /* Notes: |
| -VCO needs to be between 3-6GHz per the datasheet. It appears that if you |
| provide values which would result in a VCO outside of this range, it will |
| still oscillate, but at unknown (but likely close to target) frequency. |
| */ |
| zx_status_t s905d2_pll_set_rate(aml_pll_dev_t* pll_dev, uint64_t freq) { |
| ZX_DEBUG_ASSERT(pll_dev); |
| |
| const hhi_pll_rate_t* pll_rate; |
| |
| zx_status_t status = s905d2_pll_fetch_rate(pll_dev, freq, &pll_rate); |
| if (status != ZX_OK) { |
| return status; |
| } |
| // Disable/reset the pll, save previous state |
| bool ena = s905d2_pll_disable(pll_dev); |
| |
| // Initialize the registers to defaults (may not be retained after reset) |
| s905d2_pll_init_regs(pll_dev); |
| |
| uint32_t offs = hiu_get_pll_offs(pll_dev); |
| uint32_t ctl0 = hiu_clk_get_reg(pll_dev->hiu, offs); |
| |
| ctl0 &= ~HHI_PLL_CNTL0_M; |
| ctl0 |= pll_rate->m << HHI_PLL_CNTL0_M_SHIFT; |
| |
| ctl0 &= ~HHI_PLL_CNTL0_N; |
| ctl0 |= pll_rate->n << HHI_PLL_CNTL0_N_SHIFT; |
| |
| ctl0 &= ~HHI_PLL_CNTL0_OD; |
| ctl0 |= pll_rate->od << HHI_PLL_CNTL0_OD_SHIFT; |
| |
| hiu_clk_set_reg(pll_dev->hiu, offs, ctl0); |
| |
| hiu_clk_set_reg(pll_dev->hiu, offs + 4, pll_rate->frac); |
| |
| if (ena) { |
| return s905d2_pll_ena(pll_dev); |
| } |
| |
| return ZX_OK; |
| } |