| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <common.h> |
| #include <asm/arch/clock.h> |
| #include <asm/io.h> |
| #include <clk-uclass.h> |
| #include <div64.h> |
| #include <dm.h> |
| #include <dt-bindings/clock/c2-clkc.h> |
| #include "clk_meson.h" |
| |
| /* change it later */ |
| #define SYS_CLK 166666666 |
| |
| /* clk81 gates */ |
| static struct meson_gate gates[] = { |
| {CLKID_SYS_SPIFC, C2_SYS_CLK_EN1, 28}, |
| {CLKID_SPIFC_GATE, C2_SPIFC_CLK_CTRL, 8}, |
| {CLKID_SPIFC_XTAL_GATE, C2_SPIFC_CLK_CTRL, 15}, |
| {CLKID_SAR_ADC_GATE, C2_SAR_ADC_CLK_CTRL, 8}, |
| {CLKID_SPICC_A_GATE, C2_SPICC_CLK_CTRL, 8}, |
| {CLKID_SPICC_B_GATE, C2_SPICC_CLK_CTRL, 24}, |
| {CLKID_SD_EMMC_A_GATE, C2_SD_EMMC_CLK_CTRL, 8}, |
| {CLKID_SD_EMMC_A_XTAL_GATE, C2_SD_EMMC_CLK_CTRL, 15}, |
| {CLKID_SD_EMMC_B_GATE, C2_SD_EMMC_CLK_CTRL, 24}, |
| {CLKID_SD_EMMC_B_XTAL_GATE, C2_SD_EMMC_CLK_CTRL, 31}, |
| {CLKID_SD_EMMC_C_GATE, C2_SD_EMMC_CLK_CTRL1, 8}, |
| {CLKID_SD_EMMC_C_XTAL_GATE, C2_SD_EMMC_CLK_CTRL1, 15}, |
| }; |
| |
| static unsigned int spifc_parents[] = {CLKID_FCLK_DIV2, CLKID_FCLK_DIV3, |
| CLKID_FCLK_DIV2P5, CLKID_UNREALIZED, CLKID_UNREALIZED, |
| CLKID_FCLK_DIV4, CLKID_FCLK_DIV5, CLKID_FCLK_DIV7}; |
| |
| static unsigned int saradc_parents[] = {CLKID_XTAL, CLKID_SYS_CLK}; |
| |
| static unsigned int sd_emmc_parents[] = {CLKID_FCLK_DIV2, CLKID_FCLK_DIV3, |
| CLKID_FCLK_DIV2P5, CLKID_UNREALIZED, CLKID_UNREALIZED, |
| CLKID_FCLK_DIV4, CLKID_FCLK_DIV5, CLKID_FCLK_DIV7}; |
| |
| static struct meson_mux muxes[] = { |
| {CLKID_SPIFC_MUX, C2_SPIFC_CLK_CTRL, 9, 0x7, spifc_parents, ARRAY_SIZE(spifc_parents)}, |
| {CLKID_SARADC_MUX, C2_SAR_ADC_CLK_CTRL, 9, 0x1, saradc_parents, ARRAY_SIZE(saradc_parents)}, |
| {CLKID_SPICC_A_MUX, C2_SPICC_CLK_CTRL, 9, 0x7, spifc_parents, ARRAY_SIZE(spifc_parents)}, |
| {CLKID_SPICC_B_MUX, C2_SPICC_CLK_CTRL, 25, 0x7, spifc_parents, ARRAY_SIZE(spifc_parents)}, |
| {CLKID_SD_EMMC_A_MUX, C2_SD_EMMC_CLK_CTRL, 9, 0x7, sd_emmc_parents, ARRAY_SIZE(sd_emmc_parents)}, |
| {CLKID_SD_EMMC_B_MUX, C2_SD_EMMC_CLK_CTRL, 25, 0x7, sd_emmc_parents, ARRAY_SIZE(sd_emmc_parents)}, |
| {CLKID_SD_EMMC_C_MUX, C2_SD_EMMC_CLK_CTRL1, 9, 0x7, sd_emmc_parents, ARRAY_SIZE(sd_emmc_parents)}, |
| }; |
| |
| static struct meson_div divs[] = { |
| {CLKID_SPIFC_DIV, C2_SPIFC_CLK_CTRL, 0, 8, CLKID_SPIFC_MUX}, |
| {CLKID_SARADC_DIV, C2_SAR_ADC_CLK_CTRL, 0, 8, CLKID_SARADC_MUX}, |
| {CLKID_SPICC_A_DIV, C2_SPICC_CLK_CTRL, 0, 8, CLKID_SPICC_A_MUX}, |
| {CLKID_SPICC_B_DIV, C2_SPICC_CLK_CTRL, 16, 8, CLKID_SPICC_B_MUX}, |
| {CLKID_SD_EMMC_A_DIV, C2_SD_EMMC_CLK_CTRL, 0, 8, CLKID_SD_EMMC_A_MUX}, |
| {CLKID_SD_EMMC_B_DIV, C2_SD_EMMC_CLK_CTRL, 16, 8, CLKID_SD_EMMC_B_MUX}, |
| {CLKID_SD_EMMC_C_DIV, C2_SD_EMMC_CLK_CTRL1, 0, 8, CLKID_SD_EMMC_C_MUX}, |
| }; |
| |
| static struct parm meson_fixed_pll_parm[3] = { |
| {C2_ANACTRL_FIXPLL_CTRL0, 0, 8}, /* pm */ |
| {C2_ANACTRL_FIXPLL_CTRL0, 10, 5}, /* pn */ |
| {C2_ANACTRL_FIXPLL_CTRL0, 16, 2}, /* pod */ |
| }; |
| |
| static struct parm meson_sys_pll_parm[3] = { |
| {C2_ANACTRL_SYSPLL_CTRL0, 0, 8}, /* pm */ |
| {C2_ANACTRL_SYSPLL_CTRL0, 10, 5}, /* pn */ |
| {C2_ANACTRL_SYSPLL_CTRL0, 24, 2}, /* pod */ |
| }; |
| |
| static int meson_clk_enable(struct clk *clk) |
| { |
| return meson_set_gate_by_id(clk, gates, ARRAY_SIZE(gates), true); |
| } |
| |
| static int meson_clk_disable(struct clk *clk) |
| { |
| return meson_set_gate_by_id(clk, gates, ARRAY_SIZE(gates), false); |
| } |
| |
| static ulong meson_pll_get_rate(struct clk *clk, unsigned long id) |
| { |
| struct meson_clk *priv = dev_get_priv(clk->dev); |
| struct parm *pm, *pn, *pod; |
| unsigned long parent_rate_mhz = clk_get_rate(&priv->clkin)/1000000; |
| u16 n, m, od; |
| u32 reg; |
| |
| switch (id) { |
| case CLKID_FIXED_PLL: |
| pm = &meson_fixed_pll_parm[0]; |
| pn = &meson_fixed_pll_parm[1]; |
| pod = &meson_fixed_pll_parm[2]; |
| break; |
| case CLKID_SYS_PLL: |
| pm = &meson_sys_pll_parm[0]; |
| pn = &meson_sys_pll_parm[1]; |
| pod = &meson_sys_pll_parm[2]; |
| break; |
| default: |
| return -ENOENT; |
| } |
| |
| reg = readl(priv->addr + pn->reg_off); |
| n = PARM_GET(pn->width, pn->shift, reg); |
| |
| reg = readl(priv->addr + pm->reg_off); |
| m = PARM_GET(pm->width, pm->shift, reg); |
| |
| /* there is OD in C1 */ |
| reg = readl(priv->addr + pod->reg_off); |
| od = PARM_GET(pod->width, pod->shift, reg); |
| |
| return ((parent_rate_mhz * m * 2 / n) >> od) * 1000000; |
| } |
| |
| static ulong meson_clk_get_rate_by_id(struct clk *clk, ulong id) |
| { |
| ulong rate; |
| struct meson_clk *priv = dev_get_priv(clk->dev); |
| |
| switch (id) { |
| case CLKID_XTAL: |
| rate = clk_get_rate(&priv->clkin); |
| break; |
| case CLKID_FIXED_PLL: |
| case CLKID_SYS_PLL: |
| rate = meson_pll_get_rate(clk, id); |
| break; |
| case CLKID_FCLK_DIV2: |
| rate = meson_pll_get_rate(clk, CLKID_FIXED_PLL) / 2; |
| break; |
| case CLKID_FCLK_DIV3: |
| rate = meson_pll_get_rate(clk, CLKID_FIXED_PLL) / 3; |
| break; |
| case CLKID_FCLK_DIV4: |
| rate = meson_pll_get_rate(clk, CLKID_FIXED_PLL) / 4; |
| break; |
| case CLKID_FCLK_DIV5: |
| rate = meson_pll_get_rate(clk, CLKID_FIXED_PLL) / 5; |
| break; |
| case CLKID_FCLK_DIV7: |
| rate = meson_pll_get_rate(clk, CLKID_FIXED_PLL) / 7; |
| break; |
| case CLKID_FCLK_DIV2P5: |
| rate = (meson_pll_get_rate(clk, CLKID_FIXED_PLL) * 2) / 5; |
| break; |
| /* sys clk has realized in rom code*/ |
| case CLKID_SYS_CLK: |
| rate = SYS_CLK; |
| break; |
| default: |
| rate = priv->actual_rate; |
| break; |
| } |
| |
| return rate; |
| } |
| |
| static ulong meson_clk_get_rate(struct clk *clk) |
| { |
| return meson_clk_get_rate_by_id(clk, clk->id); |
| } |
| |
| static ulong meson_clk_set_rate(struct clk *clk, ulong rate) |
| { |
| ulong div_parent, mux_parent, parent_rate; |
| unsigned int div_val; |
| struct meson_clk *priv = dev_get_priv(clk->dev); |
| unsigned int i; |
| struct meson_div *div = NULL; |
| |
| for (i = 0; i < ARRAY_SIZE(divs); i++) { |
| if (clk->id == divs[i].index) |
| div = &divs[i]; |
| } |
| div_parent = div->parent_index; |
| mux_parent = meson_clk_get_mux_parent(clk, muxes, |
| ARRAY_SIZE(muxes), div_parent); |
| parent_rate = meson_clk_get_rate_by_id(clk, mux_parent); |
| |
| div_val = DIV_ROUND_CLOSEST(parent_rate, rate) - 1; |
| |
| priv->actual_rate = DIV_ROUND_CLOSEST(parent_rate, div_val + 1); |
| meson_clk_set_div(priv, div, div_val); |
| |
| return 0; |
| } |
| |
| static int meson_clk_set_parent(struct clk* clk, struct clk* parent_clk) |
| { |
| return meson_mux_set_parent_by_id(clk, muxes, ARRAY_SIZE(muxes), parent_clk); |
| } |
| |
| static struct clk_ops meson_clk_ops = { |
| .disable = meson_clk_disable, |
| .enable = meson_clk_enable, |
| .get_rate = meson_clk_get_rate, |
| .set_rate = meson_clk_set_rate, |
| .set_parent = meson_clk_set_parent, |
| }; |
| |
| static int meson_clk_probe(struct udevice *dev) |
| { |
| struct meson_clk *priv = dev_get_priv(dev); |
| |
| clk_get_by_name(dev, "xtal", &priv->clkin); |
| priv->addr = dev_read_addr_ptr(dev); |
| |
| debug("meson-clk: probed at addr %p\n", priv->addr); |
| |
| return 0; |
| } |
| |
| static const struct udevice_id meson_clk_ids[] = { |
| { .compatible = "amlogic,c1-clkc" }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(meson_clk) = { |
| .name = "meson-clk-c2", |
| .id = UCLASS_CLK, |
| .of_match = meson_clk_ids, |
| .priv_auto_alloc_size = sizeof(struct meson_clk), |
| .ops = &meson_clk_ops, |
| .probe = meson_clk_probe, |
| }; |