| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2023 Nuvoton Technology Corp. |
| * Author: Chi-Fang Li <cfli0@nuvoton.com> |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/clk-provider.h> |
| #include <linux/container_of.h> |
| #include <linux/device.h> |
| #include <linux/io.h> |
| #include <linux/kernel.h> |
| #include <linux/math64.h> |
| #include <linux/slab.h> |
| #include <linux/units.h> |
| #include <dt-bindings/clock/nuvoton,ma35d1-clk.h> |
| |
| #include "clk-ma35d1.h" |
| |
| /* PLL frequency limits */ |
| #define PLL_FREF_MAX_FREQ (200 * HZ_PER_MHZ) |
| #define PLL_FREF_MIN_FREQ (1 * HZ_PER_MHZ) |
| #define PLL_FREF_M_MAX_FREQ (40 * HZ_PER_MHZ) |
| #define PLL_FREF_M_MIN_FREQ (10 * HZ_PER_MHZ) |
| #define PLL_FCLK_MAX_FREQ (2400 * HZ_PER_MHZ) |
| #define PLL_FCLK_MIN_FREQ (600 * HZ_PER_MHZ) |
| #define PLL_FCLKO_MAX_FREQ (2400 * HZ_PER_MHZ) |
| #define PLL_FCLKO_MIN_FREQ (85700 * HZ_PER_KHZ) |
| #define PLL_SS_RATE 0x77 |
| #define PLL_SLOPE 0x58CFA |
| |
| #define REG_PLL_CTL0_OFFSET 0x0 |
| #define REG_PLL_CTL1_OFFSET 0x4 |
| #define REG_PLL_CTL2_OFFSET 0x8 |
| |
| /* bit fields for REG_CLK_PLL0CTL0, which is SMIC PLL design */ |
| #define SPLL0_CTL0_FBDIV GENMASK(7, 0) |
| #define SPLL0_CTL0_INDIV GENMASK(11, 8) |
| #define SPLL0_CTL0_OUTDIV GENMASK(13, 12) |
| #define SPLL0_CTL0_PD BIT(16) |
| #define SPLL0_CTL0_BP BIT(17) |
| |
| /* bit fields for REG_CLK_PLLxCTL0 ~ REG_CLK_PLLxCTL2, where x = 2 ~ 5 */ |
| #define PLL_CTL0_FBDIV GENMASK(10, 0) |
| #define PLL_CTL0_INDIV GENMASK(17, 12) |
| #define PLL_CTL0_MODE GENMASK(19, 18) |
| #define PLL_CTL0_SSRATE GENMASK(30, 20) |
| #define PLL_CTL1_PD BIT(0) |
| #define PLL_CTL1_BP BIT(1) |
| #define PLL_CTL1_OUTDIV GENMASK(6, 4) |
| #define PLL_CTL1_FRAC GENMASK(31, 24) |
| #define PLL_CTL2_SLOPE GENMASK(23, 0) |
| |
| #define INDIV_MIN 1 |
| #define INDIV_MAX 63 |
| #define FBDIV_MIN 16 |
| #define FBDIV_MAX 2047 |
| #define FBDIV_FRAC_MIN 1600 |
| #define FBDIV_FRAC_MAX 204700 |
| #define OUTDIV_MIN 1 |
| #define OUTDIV_MAX 7 |
| |
| #define PLL_MODE_INT 0 |
| #define PLL_MODE_FRAC 1 |
| #define PLL_MODE_SS 2 |
| |
| struct ma35d1_clk_pll { |
| struct clk_hw hw; |
| u32 id; |
| u8 mode; |
| void __iomem *ctl0_base; |
| void __iomem *ctl1_base; |
| void __iomem *ctl2_base; |
| }; |
| |
| static inline struct ma35d1_clk_pll *to_ma35d1_clk_pll(struct clk_hw *_hw) |
| { |
| return container_of(_hw, struct ma35d1_clk_pll, hw); |
| } |
| |
| static unsigned long ma35d1_calc_smic_pll_freq(u32 pll0_ctl0, |
| unsigned long parent_rate) |
| { |
| u32 m, n, p, outdiv; |
| u64 pll_freq; |
| |
| if (pll0_ctl0 & SPLL0_CTL0_BP) |
| return parent_rate; |
| |
| n = FIELD_GET(SPLL0_CTL0_FBDIV, pll0_ctl0); |
| m = FIELD_GET(SPLL0_CTL0_INDIV, pll0_ctl0); |
| p = FIELD_GET(SPLL0_CTL0_OUTDIV, pll0_ctl0); |
| outdiv = 1 << p; |
| pll_freq = (u64)parent_rate * n; |
| div_u64(pll_freq, m * outdiv); |
| return pll_freq; |
| } |
| |
| static unsigned long ma35d1_calc_pll_freq(u8 mode, u32 *reg_ctl, unsigned long parent_rate) |
| { |
| unsigned long pll_freq, x; |
| u32 m, n, p; |
| |
| if (reg_ctl[1] & PLL_CTL1_BP) |
| return parent_rate; |
| |
| n = FIELD_GET(PLL_CTL0_FBDIV, reg_ctl[0]); |
| m = FIELD_GET(PLL_CTL0_INDIV, reg_ctl[0]); |
| p = FIELD_GET(PLL_CTL1_OUTDIV, reg_ctl[1]); |
| |
| if (mode == PLL_MODE_INT) { |
| pll_freq = (u64)parent_rate * n; |
| div_u64(pll_freq, m * p); |
| } else { |
| x = FIELD_GET(PLL_CTL1_FRAC, reg_ctl[1]); |
| /* 2 decimal places floating to integer (ex. 1.23 to 123) */ |
| n = n * 100 + ((x * 100) / FIELD_MAX(PLL_CTL1_FRAC)); |
| pll_freq = div_u64(parent_rate * n, 100 * m * p); |
| } |
| return pll_freq; |
| } |
| |
| static int ma35d1_pll_find_closest(struct ma35d1_clk_pll *pll, unsigned long rate, |
| unsigned long parent_rate, u32 *reg_ctl, |
| unsigned long *freq) |
| { |
| unsigned long min_diff = ULONG_MAX; |
| int fbdiv_min, fbdiv_max; |
| int p, m, n; |
| |
| *freq = 0; |
| if (rate < PLL_FCLKO_MIN_FREQ || rate > PLL_FCLKO_MAX_FREQ) |
| return -EINVAL; |
| |
| if (pll->mode == PLL_MODE_INT) { |
| fbdiv_min = FBDIV_MIN; |
| fbdiv_max = FBDIV_MAX; |
| } else { |
| fbdiv_min = FBDIV_FRAC_MIN; |
| fbdiv_max = FBDIV_FRAC_MAX; |
| } |
| |
| for (m = INDIV_MIN; m <= INDIV_MAX; m++) { |
| for (n = fbdiv_min; n <= fbdiv_max; n++) { |
| for (p = OUTDIV_MIN; p <= OUTDIV_MAX; p++) { |
| unsigned long tmp, fout, fclk, diff; |
| |
| tmp = div_u64(parent_rate, m); |
| if (tmp < PLL_FREF_M_MIN_FREQ || |
| tmp > PLL_FREF_M_MAX_FREQ) |
| continue; /* constrain */ |
| |
| fclk = div_u64(parent_rate * n, m); |
| /* for 2 decimal places */ |
| if (pll->mode != PLL_MODE_INT) |
| fclk = div_u64(fclk, 100); |
| |
| if (fclk < PLL_FCLK_MIN_FREQ || |
| fclk > PLL_FCLK_MAX_FREQ) |
| continue; /* constrain */ |
| |
| fout = div_u64(fclk, p); |
| if (fout < PLL_FCLKO_MIN_FREQ || |
| fout > PLL_FCLKO_MAX_FREQ) |
| continue; /* constrain */ |
| |
| diff = abs(rate - fout); |
| if (diff < min_diff) { |
| reg_ctl[0] = FIELD_PREP(PLL_CTL0_INDIV, m) | |
| FIELD_PREP(PLL_CTL0_FBDIV, n); |
| reg_ctl[1] = FIELD_PREP(PLL_CTL1_OUTDIV, p); |
| *freq = fout; |
| min_diff = diff; |
| if (min_diff == 0) |
| break; |
| } |
| } |
| } |
| } |
| if (*freq == 0) |
| return -EINVAL; /* cannot find even one valid setting */ |
| return 0; |
| } |
| |
| static int ma35d1_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate, |
| unsigned long parent_rate) |
| { |
| struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw); |
| u32 reg_ctl[3] = { 0 }; |
| unsigned long pll_freq; |
| int ret; |
| |
| if (parent_rate < PLL_FREF_MIN_FREQ || parent_rate > PLL_FREF_MAX_FREQ) |
| return -EINVAL; |
| |
| ret = ma35d1_pll_find_closest(pll, rate, parent_rate, reg_ctl, &pll_freq); |
| if (ret != 0) |
| return ret; |
| |
| switch (pll->mode) { |
| case PLL_MODE_INT: |
| reg_ctl[0] |= FIELD_PREP(PLL_CTL0_MODE, PLL_MODE_INT); |
| break; |
| case PLL_MODE_FRAC: |
| reg_ctl[0] |= FIELD_PREP(PLL_CTL0_MODE, PLL_MODE_FRAC); |
| break; |
| case PLL_MODE_SS: |
| reg_ctl[0] |= FIELD_PREP(PLL_CTL0_MODE, PLL_MODE_SS) | |
| FIELD_PREP(PLL_CTL0_SSRATE, PLL_SS_RATE); |
| reg_ctl[2] = FIELD_PREP(PLL_CTL2_SLOPE, PLL_SLOPE); |
| break; |
| } |
| reg_ctl[1] |= PLL_CTL1_PD; |
| |
| writel_relaxed(reg_ctl[0], pll->ctl0_base); |
| writel_relaxed(reg_ctl[1], pll->ctl1_base); |
| writel_relaxed(reg_ctl[2], pll->ctl2_base); |
| return 0; |
| } |
| |
| static unsigned long ma35d1_clk_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) |
| { |
| struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw); |
| u32 reg_ctl[3]; |
| unsigned long pll_freq; |
| |
| if (parent_rate < PLL_FREF_MIN_FREQ || parent_rate > PLL_FREF_MAX_FREQ) |
| return 0; |
| |
| switch (pll->id) { |
| case CAPLL: |
| reg_ctl[0] = readl_relaxed(pll->ctl0_base); |
| pll_freq = ma35d1_calc_smic_pll_freq(reg_ctl[0], parent_rate); |
| return pll_freq; |
| case DDRPLL: |
| case APLL: |
| case EPLL: |
| case VPLL: |
| reg_ctl[0] = readl_relaxed(pll->ctl0_base); |
| reg_ctl[1] = readl_relaxed(pll->ctl1_base); |
| pll_freq = ma35d1_calc_pll_freq(pll->mode, reg_ctl, parent_rate); |
| return pll_freq; |
| } |
| return 0; |
| } |
| |
| static long ma35d1_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate, |
| unsigned long *parent_rate) |
| { |
| struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw); |
| u32 reg_ctl[3] = { 0 }; |
| unsigned long pll_freq; |
| long ret; |
| |
| if (*parent_rate < PLL_FREF_MIN_FREQ || *parent_rate > PLL_FREF_MAX_FREQ) |
| return -EINVAL; |
| |
| ret = ma35d1_pll_find_closest(pll, rate, *parent_rate, reg_ctl, &pll_freq); |
| if (ret < 0) |
| return ret; |
| |
| switch (pll->id) { |
| case CAPLL: |
| reg_ctl[0] = readl_relaxed(pll->ctl0_base); |
| pll_freq = ma35d1_calc_smic_pll_freq(reg_ctl[0], *parent_rate); |
| return pll_freq; |
| case DDRPLL: |
| case APLL: |
| case EPLL: |
| case VPLL: |
| reg_ctl[0] = readl_relaxed(pll->ctl0_base); |
| reg_ctl[1] = readl_relaxed(pll->ctl1_base); |
| pll_freq = ma35d1_calc_pll_freq(pll->mode, reg_ctl, *parent_rate); |
| return pll_freq; |
| } |
| return 0; |
| } |
| |
| static int ma35d1_clk_pll_is_prepared(struct clk_hw *hw) |
| { |
| struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw); |
| u32 val = readl_relaxed(pll->ctl1_base); |
| |
| return !(val & PLL_CTL1_PD); |
| } |
| |
| static int ma35d1_clk_pll_prepare(struct clk_hw *hw) |
| { |
| struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw); |
| u32 val; |
| |
| val = readl_relaxed(pll->ctl1_base); |
| val &= ~PLL_CTL1_PD; |
| writel_relaxed(val, pll->ctl1_base); |
| return 0; |
| } |
| |
| static void ma35d1_clk_pll_unprepare(struct clk_hw *hw) |
| { |
| struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw); |
| u32 val; |
| |
| val = readl_relaxed(pll->ctl1_base); |
| val |= PLL_CTL1_PD; |
| writel_relaxed(val, pll->ctl1_base); |
| } |
| |
| static const struct clk_ops ma35d1_clk_pll_ops = { |
| .is_prepared = ma35d1_clk_pll_is_prepared, |
| .prepare = ma35d1_clk_pll_prepare, |
| .unprepare = ma35d1_clk_pll_unprepare, |
| .set_rate = ma35d1_clk_pll_set_rate, |
| .recalc_rate = ma35d1_clk_pll_recalc_rate, |
| .round_rate = ma35d1_clk_pll_round_rate, |
| }; |
| |
| static const struct clk_ops ma35d1_clk_fixed_pll_ops = { |
| .recalc_rate = ma35d1_clk_pll_recalc_rate, |
| .round_rate = ma35d1_clk_pll_round_rate, |
| }; |
| |
| struct clk_hw *ma35d1_reg_clk_pll(struct device *dev, u32 id, u8 u8mode, const char *name, |
| struct clk_hw *parent_hw, void __iomem *base) |
| { |
| struct clk_parent_data pdata = { .index = 0 }; |
| struct clk_init_data init = {}; |
| struct ma35d1_clk_pll *pll; |
| struct clk_hw *hw; |
| int ret; |
| |
| pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); |
| if (!pll) |
| return ERR_PTR(-ENOMEM); |
| |
| pll->id = id; |
| pll->mode = u8mode; |
| pll->ctl0_base = base + REG_PLL_CTL0_OFFSET; |
| pll->ctl1_base = base + REG_PLL_CTL1_OFFSET; |
| pll->ctl2_base = base + REG_PLL_CTL2_OFFSET; |
| |
| init.name = name; |
| init.flags = 0; |
| pdata.hw = parent_hw; |
| init.parent_data = &pdata; |
| init.num_parents = 1; |
| |
| if (id == CAPLL || id == DDRPLL) |
| init.ops = &ma35d1_clk_fixed_pll_ops; |
| else |
| init.ops = &ma35d1_clk_pll_ops; |
| |
| pll->hw.init = &init; |
| hw = &pll->hw; |
| |
| ret = devm_clk_hw_register(dev, hw); |
| if (ret) |
| return ERR_PTR(ret); |
| return hw; |
| } |
| EXPORT_SYMBOL_GPL(ma35d1_reg_clk_pll); |