| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Samsung Exynos7420 clock driver. |
| * Copyright (C) 2016 Samsung Electronics |
| * Thomas Abraham <thomas.ab@samsung.com> |
| */ |
| |
| #include <dm.h> |
| #include <errno.h> |
| #include <clk-uclass.h> |
| #include <asm/io.h> |
| #include <div64.h> |
| #include <dt-bindings/clock/exynos7420-clk.h> |
| |
| #define PLL145X_MDIV_SHIFT 16 |
| #define PLL145X_MDIV_MASK 0x3ff |
| #define PLL145X_PDIV_SHIFT 8 |
| #define PLL145X_PDIV_MASK 0x3f |
| #define PLL145X_SDIV_SHIFT 0 |
| #define PLL145X_SDIV_MASK 0x7 |
| |
| #define DIVIDER(reg, shift, mask) \ |
| (((readl(reg) >> shift) & mask) + 1) |
| |
| /* CMU TOPC block device structure */ |
| struct exynos7420_clk_cmu_topc { |
| unsigned int rsvd1[68]; |
| unsigned int bus0_pll_con[2]; |
| unsigned int rsvd2[2]; |
| unsigned int bus1_pll_con[2]; |
| unsigned int rsvd3[54]; |
| unsigned int mux_sel[6]; |
| unsigned int rsvd4[250]; |
| unsigned int div[4]; |
| }; |
| |
| /* CMU TOP0 block device structure */ |
| struct exynos7420_clk_cmu_top0 { |
| unsigned int rsvd0[128]; |
| unsigned int mux_sel[7]; |
| unsigned int rsvd1[261]; |
| unsigned int div_peric[5]; |
| }; |
| |
| /** |
| * struct exynos7420_clk_topc_priv - private data for CMU topc clock driver. |
| * |
| * @topc: base address of the memory mapped CMU TOPC controller. |
| * @fin_freq: frequency of the Oscillator clock. |
| * @sclk_bus0_pll_a: frequency of sclk_bus0_pll_a clock. |
| * @sclk_bus1_pll_a: frequency of sclk_bus1_pll_a clock. |
| */ |
| struct exynos7420_clk_topc_priv { |
| struct exynos7420_clk_cmu_topc *topc; |
| unsigned long fin_freq; |
| unsigned long sclk_bus0_pll_a; |
| unsigned long sclk_bus1_pll_a; |
| }; |
| |
| /** |
| * struct exynos7420_clk_top0_priv - private data for CMU top0 clock driver. |
| * |
| * @top0: base address of the memory mapped CMU TOP0 controller. |
| * @mout_top0_bus0_pll_half: frequency of mout_top0_bus0_pll_half clock |
| * @sclk_uart2: frequency of sclk_uart2 clock. |
| */ |
| struct exynos7420_clk_top0_priv { |
| struct exynos7420_clk_cmu_top0 *top0; |
| unsigned long mout_top0_bus0_pll_half; |
| unsigned long sclk_uart2; |
| }; |
| |
| static unsigned long pll145x_get_rate(unsigned int *con1, |
| unsigned long fin_freq) |
| { |
| unsigned long pll_con1 = readl(con1); |
| unsigned long mdiv, sdiv, pdiv; |
| u64 fvco = fin_freq; |
| |
| mdiv = (pll_con1 >> PLL145X_MDIV_SHIFT) & PLL145X_MDIV_MASK; |
| pdiv = (pll_con1 >> PLL145X_PDIV_SHIFT) & PLL145X_PDIV_MASK; |
| sdiv = (pll_con1 >> PLL145X_SDIV_SHIFT) & PLL145X_SDIV_MASK; |
| |
| fvco *= mdiv; |
| do_div(fvco, (pdiv << sdiv)); |
| return (unsigned long)fvco; |
| } |
| |
| static ulong exynos7420_topc_get_rate(struct clk *clk) |
| { |
| struct exynos7420_clk_topc_priv *priv = dev_get_priv(clk->dev); |
| |
| switch (clk->id) { |
| case DOUT_SCLK_BUS0_PLL: |
| case SCLK_BUS0_PLL_A: |
| case SCLK_BUS0_PLL_B: |
| return priv->sclk_bus0_pll_a; |
| case DOUT_SCLK_BUS1_PLL: |
| case SCLK_BUS1_PLL_A: |
| case SCLK_BUS1_PLL_B: |
| return priv->sclk_bus1_pll_a; |
| default: |
| return 0; |
| } |
| } |
| |
| static struct clk_ops exynos7420_clk_topc_ops = { |
| .get_rate = exynos7420_topc_get_rate, |
| }; |
| |
| static int exynos7420_clk_topc_probe(struct udevice *dev) |
| { |
| struct exynos7420_clk_topc_priv *priv = dev_get_priv(dev); |
| struct exynos7420_clk_cmu_topc *topc; |
| struct clk in_clk; |
| unsigned long rate; |
| fdt_addr_t base; |
| int ret; |
| |
| base = dev_read_addr(dev); |
| if (base == FDT_ADDR_T_NONE) |
| return -EINVAL; |
| |
| topc = (struct exynos7420_clk_cmu_topc *)base; |
| priv->topc = topc; |
| |
| ret = clk_get_by_index(dev, 0, &in_clk); |
| if (ret >= 0) |
| priv->fin_freq = clk_get_rate(&in_clk); |
| |
| rate = pll145x_get_rate(&topc->bus0_pll_con[0], priv->fin_freq); |
| if (readl(&topc->mux_sel[1]) & (1 << 16)) |
| rate >>= 1; |
| rate /= DIVIDER(&topc->div[3], 0, 0xf); |
| priv->sclk_bus0_pll_a = rate; |
| |
| rate = pll145x_get_rate(&topc->bus1_pll_con[0], priv->fin_freq) / |
| DIVIDER(&topc->div[3], 8, 0xf); |
| priv->sclk_bus1_pll_a = rate; |
| |
| return 0; |
| } |
| |
| static ulong exynos7420_top0_get_rate(struct clk *clk) |
| { |
| struct exynos7420_clk_top0_priv *priv = dev_get_priv(clk->dev); |
| struct exynos7420_clk_cmu_top0 *top0 = priv->top0; |
| |
| switch (clk->id) { |
| case CLK_SCLK_UART2: |
| return priv->mout_top0_bus0_pll_half / |
| DIVIDER(&top0->div_peric[3], 8, 0xf); |
| default: |
| return 0; |
| } |
| } |
| |
| static struct clk_ops exynos7420_clk_top0_ops = { |
| .get_rate = exynos7420_top0_get_rate, |
| }; |
| |
| static int exynos7420_clk_top0_probe(struct udevice *dev) |
| { |
| struct exynos7420_clk_top0_priv *priv; |
| struct exynos7420_clk_cmu_top0 *top0; |
| struct clk in_clk; |
| fdt_addr_t base; |
| int ret; |
| |
| priv = dev_get_priv(dev); |
| if (!priv) |
| return -EINVAL; |
| |
| base = dev_read_addr(dev); |
| if (base == FDT_ADDR_T_NONE) |
| return -EINVAL; |
| |
| top0 = (struct exynos7420_clk_cmu_top0 *)base; |
| priv->top0 = top0; |
| |
| ret = clk_get_by_index(dev, 1, &in_clk); |
| if (ret >= 0) { |
| priv->mout_top0_bus0_pll_half = |
| clk_get_rate(&in_clk); |
| if (readl(&top0->mux_sel[1]) & (1 << 16)) |
| priv->mout_top0_bus0_pll_half >>= 1; |
| } |
| |
| return 0; |
| } |
| |
| static ulong exynos7420_peric1_get_rate(struct clk *clk) |
| { |
| struct clk in_clk; |
| unsigned int ret; |
| unsigned long freq = 0; |
| |
| switch (clk->id) { |
| case SCLK_UART2: |
| ret = clk_get_by_index(clk->dev, 3, &in_clk); |
| if (ret < 0) |
| return ret; |
| freq = clk_get_rate(&in_clk); |
| break; |
| } |
| |
| return freq; |
| } |
| |
| static struct clk_ops exynos7420_clk_peric1_ops = { |
| .get_rate = exynos7420_peric1_get_rate, |
| }; |
| |
| static const struct udevice_id exynos7420_clk_topc_compat[] = { |
| { .compatible = "samsung,exynos7-clock-topc" }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(exynos7420_clk_topc) = { |
| .name = "exynos7420-clock-topc", |
| .id = UCLASS_CLK, |
| .of_match = exynos7420_clk_topc_compat, |
| .probe = exynos7420_clk_topc_probe, |
| .priv_auto = sizeof(struct exynos7420_clk_topc_priv), |
| .ops = &exynos7420_clk_topc_ops, |
| }; |
| |
| static const struct udevice_id exynos7420_clk_top0_compat[] = { |
| { .compatible = "samsung,exynos7-clock-top0" }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(exynos7420_clk_top0) = { |
| .name = "exynos7420-clock-top0", |
| .id = UCLASS_CLK, |
| .of_match = exynos7420_clk_top0_compat, |
| .probe = exynos7420_clk_top0_probe, |
| .priv_auto = sizeof(struct exynos7420_clk_top0_priv), |
| .ops = &exynos7420_clk_top0_ops, |
| }; |
| |
| static const struct udevice_id exynos7420_clk_peric1_compat[] = { |
| { .compatible = "samsung,exynos7-clock-peric1" }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(exynos7420_clk_peric1) = { |
| .name = "exynos7420-clock-peric1", |
| .id = UCLASS_CLK, |
| .of_match = exynos7420_clk_peric1_compat, |
| .ops = &exynos7420_clk_peric1_ops, |
| }; |