| /* |
| * (C) Copyright 2016 Google, Inc |
| * |
| * SPDX-License-Identifier: GPL-2.0 |
| */ |
| |
| #include <common.h> |
| #include <clk-uclass.h> |
| #include <dm.h> |
| #include <asm/io.h> |
| #include <asm/arch/scu_ast2500.h> |
| #include <dm/lists.h> |
| #include <dt-bindings/clock/ast2500-scu.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| /* |
| * For H-PLL and M-PLL the formula is |
| * (Output Frequency) = CLKIN * ((M + 1) / (N + 1)) / (P + 1) |
| * M - Numerator |
| * N - Denumerator |
| * P - Post Divider |
| * They have the same layout in their control register. |
| */ |
| |
| /* |
| * Get the rate of the M-PLL clock from input clock frequency and |
| * the value of the M-PLL Parameter Register. |
| */ |
| static ulong ast2500_get_mpll_rate(ulong clkin, u32 mpll_reg) |
| { |
| const ulong num = (mpll_reg >> SCU_MPLL_NUM_SHIFT) & SCU_MPLL_NUM_MASK; |
| const ulong denum = (mpll_reg >> SCU_MPLL_DENUM_SHIFT) |
| & SCU_MPLL_DENUM_MASK; |
| const ulong post_div = (mpll_reg >> SCU_MPLL_POST_SHIFT) |
| & SCU_MPLL_POST_MASK; |
| |
| return (clkin * ((num + 1) / (denum + 1))) / (post_div + 1); |
| } |
| |
| /* |
| * Get the rate of the H-PLL clock from input clock frequency and |
| * the value of the H-PLL Parameter Register. |
| */ |
| static ulong ast2500_get_hpll_rate(ulong clkin, u32 hpll_reg) |
| { |
| const ulong num = (hpll_reg >> SCU_HPLL_NUM_SHIFT) & SCU_HPLL_NUM_MASK; |
| const ulong denum = (hpll_reg >> SCU_HPLL_DENUM_SHIFT) |
| & SCU_HPLL_DENUM_MASK; |
| const ulong post_div = (hpll_reg >> SCU_HPLL_POST_SHIFT) |
| & SCU_HPLL_POST_MASK; |
| |
| return (clkin * ((num + 1) / (denum + 1))) / (post_div + 1); |
| } |
| |
| static ulong ast2500_get_clkin(struct ast2500_scu *scu) |
| { |
| return readl(&scu->hwstrap) & SCU_HWSTRAP_CLKIN_25MHZ |
| ? 25 * 1000 * 1000 : 24 * 1000 * 1000; |
| } |
| |
| /** |
| * Get current rate or uart clock |
| * |
| * @scu SCU registers |
| * @uart_index UART index, 1-5 |
| * |
| * @return current setting for uart clock rate |
| */ |
| static ulong ast2500_get_uart_clk_rate(struct ast2500_scu *scu, int uart_index) |
| { |
| /* |
| * ast2500 datasheet is very confusing when it comes to UART clocks, |
| * especially when CLKIN = 25 MHz. The settings are in |
| * different registers and it is unclear how they interact. |
| * |
| * This has only been tested with default settings and CLKIN = 24 MHz. |
| */ |
| ulong uart_clkin; |
| |
| if (readl(&scu->misc_ctrl2) & |
| (1 << (uart_index - 1 + SCU_MISC2_UARTCLK_SHIFT))) |
| uart_clkin = 192 * 1000 * 1000; |
| else |
| uart_clkin = 24 * 1000 * 1000; |
| |
| if (readl(&scu->misc_ctrl1) & SCU_MISC_UARTCLK_DIV13) |
| uart_clkin /= 13; |
| |
| return uart_clkin; |
| } |
| |
| static ulong ast2500_clk_get_rate(struct clk *clk) |
| { |
| struct ast2500_clk_priv *priv = dev_get_priv(clk->dev); |
| ulong clkin = ast2500_get_clkin(priv->scu); |
| ulong rate; |
| |
| switch (clk->id) { |
| case PLL_HPLL: |
| case ARMCLK: |
| /* |
| * This ignores dynamic/static slowdown of ARMCLK and may |
| * be inaccurate. |
| */ |
| rate = ast2500_get_hpll_rate(clkin, |
| readl(&priv->scu->h_pll_param)); |
| break; |
| case MCLK_DDR: |
| rate = ast2500_get_mpll_rate(clkin, |
| readl(&priv->scu->m_pll_param)); |
| break; |
| case PCLK_UART1: |
| rate = ast2500_get_uart_clk_rate(priv->scu, 1); |
| break; |
| case PCLK_UART2: |
| rate = ast2500_get_uart_clk_rate(priv->scu, 2); |
| break; |
| case PCLK_UART3: |
| rate = ast2500_get_uart_clk_rate(priv->scu, 3); |
| break; |
| case PCLK_UART4: |
| rate = ast2500_get_uart_clk_rate(priv->scu, 4); |
| break; |
| case PCLK_UART5: |
| rate = ast2500_get_uart_clk_rate(priv->scu, 5); |
| break; |
| default: |
| return -ENOENT; |
| } |
| |
| return rate; |
| } |
| |
| static void ast2500_scu_unlock(struct ast2500_scu *scu) |
| { |
| writel(SCU_UNLOCK_VALUE, &scu->protection_key); |
| while (!readl(&scu->protection_key)) |
| ; |
| } |
| |
| static void ast2500_scu_lock(struct ast2500_scu *scu) |
| { |
| writel(~SCU_UNLOCK_VALUE, &scu->protection_key); |
| while (readl(&scu->protection_key)) |
| ; |
| } |
| |
| static ulong ast2500_configure_ddr(struct ast2500_scu *scu, ulong rate) |
| { |
| ulong clkin = ast2500_get_clkin(scu); |
| u32 mpll_reg; |
| |
| /* |
| * There are not that many combinations of numerator, denumerator |
| * and post divider, so just brute force the best combination. |
| * However, to avoid overflow when multiplying, use kHz. |
| */ |
| const ulong clkin_khz = clkin / 1000; |
| const ulong rate_khz = rate / 1000; |
| ulong best_num = 0; |
| ulong best_denum = 0; |
| ulong best_post = 0; |
| ulong delta = rate; |
| ulong num, denum, post; |
| |
| for (denum = 0; denum <= SCU_MPLL_DENUM_MASK; ++denum) { |
| for (post = 0; post <= SCU_MPLL_POST_MASK; ++post) { |
| num = (rate_khz * (post + 1) / clkin_khz) * (denum + 1); |
| ulong new_rate_khz = (clkin_khz |
| * ((num + 1) / (denum + 1))) |
| / (post + 1); |
| |
| /* Keep the rate below requested one. */ |
| if (new_rate_khz > rate_khz) |
| continue; |
| |
| if (new_rate_khz - rate_khz < delta) { |
| delta = new_rate_khz - rate_khz; |
| |
| best_num = num; |
| best_denum = denum; |
| best_post = post; |
| |
| if (delta == 0) |
| goto rate_calc_done; |
| } |
| } |
| } |
| |
| rate_calc_done: |
| mpll_reg = readl(&scu->m_pll_param); |
| mpll_reg &= ~((SCU_MPLL_POST_MASK << SCU_MPLL_POST_SHIFT) |
| | (SCU_MPLL_NUM_MASK << SCU_MPLL_NUM_SHIFT) |
| | (SCU_MPLL_DENUM_MASK << SCU_MPLL_DENUM_SHIFT)); |
| mpll_reg |= (best_post << SCU_MPLL_POST_SHIFT) |
| | (best_num << SCU_MPLL_NUM_SHIFT) |
| | (best_denum << SCU_MPLL_DENUM_SHIFT); |
| |
| ast2500_scu_unlock(scu); |
| writel(mpll_reg, &scu->m_pll_param); |
| ast2500_scu_lock(scu); |
| |
| return ast2500_get_mpll_rate(clkin, mpll_reg); |
| } |
| |
| static ulong ast2500_clk_set_rate(struct clk *clk, ulong rate) |
| { |
| struct ast2500_clk_priv *priv = dev_get_priv(clk->dev); |
| |
| ulong new_rate; |
| switch (clk->id) { |
| case PLL_MPLL: |
| case MCLK_DDR: |
| new_rate = ast2500_configure_ddr(priv->scu, rate); |
| break; |
| default: |
| return -ENOENT; |
| } |
| |
| return new_rate; |
| } |
| |
| struct clk_ops ast2500_clk_ops = { |
| .get_rate = ast2500_clk_get_rate, |
| .set_rate = ast2500_clk_set_rate, |
| }; |
| |
| static int ast2500_clk_probe(struct udevice *dev) |
| { |
| struct ast2500_clk_priv *priv = dev_get_priv(dev); |
| |
| priv->scu = dev_get_addr_ptr(dev); |
| if (IS_ERR(priv->scu)) |
| return PTR_ERR(priv->scu); |
| |
| return 0; |
| } |
| |
| static int ast2500_clk_bind(struct udevice *dev) |
| { |
| int ret; |
| |
| /* The reset driver does not have a device node, so bind it here */ |
| ret = device_bind_driver(gd->dm_root, "ast_sysreset", "reset", &dev); |
| if (ret) |
| debug("Warning: No reset driver: ret=%d\n", ret); |
| |
| return 0; |
| } |
| |
| static const struct udevice_id ast2500_clk_ids[] = { |
| { .compatible = "aspeed,ast2500-scu" }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(aspeed_ast2500_scu) = { |
| .name = "aspeed_ast2500_scu", |
| .id = UCLASS_CLK, |
| .of_match = ast2500_clk_ids, |
| .priv_auto_alloc_size = sizeof(struct ast2500_clk_priv), |
| .ops = &ast2500_clk_ops, |
| .bind = ast2500_clk_bind, |
| .probe = ast2500_clk_probe, |
| }; |