forked from torvalds/linux
Permalink
Show file tree
Hide file tree
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
clk: imx: support fracn gppll
This PLL module is a Fractional-N synthesizer, supporting 30-bit numerator and denominator. Numerator is a signed number. It has feature to adjust fractional portion of feedback divider dynamically. This fracn gppll is used in i.MX93. Signed-off-by: Peng Fan <peng.fan@nxp.com>
- Loading branch information
1 parent
70f8076
commit 37b42f1a06ef92797e800461d1f46e849fa63c91
Showing
3 changed files
with
349 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,327 @@ | ||
| // SPDX-License-Identifier: GPL-2.0 | ||
| /* | ||
| * Copyright 2021 NXP | ||
| */ | ||
|
|
||
| #include <linux/bits.h> | ||
| #include <linux/clk-provider.h> | ||
| #include <linux/err.h> | ||
| #include <linux/export.h> | ||
| #include <linux/io.h> | ||
| #include <linux/iopoll.h> | ||
| #include <linux/slab.h> | ||
| #include <linux/jiffies.h> | ||
|
|
||
| #include "clk.h" | ||
|
|
||
| #define PLL_CTRL 0x0 | ||
| #define CLKMUX_BYPASS BIT(2) | ||
| #define CLKMUX_EN BIT(1) | ||
| #define POWERUP_MASK BIT(0) | ||
|
|
||
| #define PLL_ANA_PRG 0x10 | ||
| #define PLL_SPREAD_SPECTRUM 0x30 | ||
|
|
||
| #define PLL_NUMERATOR 0x40 | ||
| #define PLL_MFN_MASK GENMASK(31, 2) | ||
| #define PLL_MFN_SHIFT 2 | ||
|
|
||
| #define PLL_DENOMINATOR 0x50 | ||
| #define PLL_MFD_MASK GENMASK(29, 0) | ||
|
|
||
| #define PLL_DIV 0x60 | ||
| #define PLL_MFI_MASK GENMASK(24, 16) | ||
| #define PLL_MFI_SHIFT 16 | ||
| #define PLL_RDIV_MASK GENMASK(15, 13) | ||
| #define PLL_RDIV_SHIFT 13 | ||
| #define PLL_ODIV_MASK GENMASK(7, 0) | ||
|
|
||
| #define PLL_DFS_CTRL(x) (0x70 + (x) * 0x10) | ||
|
|
||
| #define PLL_STATUS 0xF0 | ||
| #define LOCK_STATUS BIT(0) | ||
|
|
||
| #define DFS_STATUS 0xF4 | ||
|
|
||
| #define LOCK_TIMEOUT_US 200 | ||
|
|
||
| #define PLL_FRACN_GP(_rate, _mfi, _mfn, _mfd, _rdiv, _odiv) \ | ||
| { \ | ||
| .rate = (_rate), \ | ||
| .mfi = (_mfi), \ | ||
| .mfn = (_mfn), \ | ||
| .mfd = (_mfd), \ | ||
| .rdiv = (_rdiv), \ | ||
| .odiv = (_odiv), \ | ||
| } | ||
|
|
||
| struct clk_fracn_gppll { | ||
| struct clk_hw hw; | ||
| void __iomem *base; | ||
| const struct imx_fracn_gppll_rate_table *rate_table; | ||
| int rate_count; | ||
| }; | ||
|
|
||
| #define to_clk_fracn_gppll(_hw) container_of(_hw, struct clk_fracn_gppll, hw) | ||
|
|
||
| /* | ||
| * Fvco = 𝐹𝑟𝑒𝑓∙(𝑀𝐹𝐼+𝑀𝐹𝑁/𝑀𝐹𝐷) | ||
| * Fout = Fvco / (rdiv * odiv) | ||
| */ | ||
| static const struct imx_fracn_gppll_rate_table fracn_tbl[] = { | ||
| PLL_FRACN_GP(650000000U, 81, 0, 0, 0, 3), | ||
| PLL_FRACN_GP(594000000U, 198, 0, 0, 0, 8), | ||
| PLL_FRACN_GP(560000000U, 70, 0, 0, 0, 3), | ||
| PLL_FRACN_GP(400000000U, 50, 0, 0, 0, 3), | ||
| PLL_FRACN_GP(393216000U, 81, 92, 100, 0, 5) | ||
| }; | ||
|
|
||
| struct imx_fracn_gppll_clk imx_fracn_gppll = { | ||
| .rate_table = fracn_tbl, | ||
| .rate_count = ARRAY_SIZE(fracn_tbl), | ||
| }; | ||
| EXPORT_SYMBOL_GPL(imx_fracn_gppll); | ||
|
|
||
| static const struct imx_fracn_gppll_rate_table * | ||
| imx_get_pll_settings(struct clk_fracn_gppll *pll, unsigned long rate) | ||
| { | ||
| const struct imx_fracn_gppll_rate_table *rate_table = pll->rate_table; | ||
| int i; | ||
|
|
||
| for (i = 0; i < pll->rate_count; i++) | ||
| if (rate == rate_table[i].rate) | ||
| return &rate_table[i]; | ||
|
|
||
| return NULL; | ||
| } | ||
|
|
||
| static long clk_fracn_gppll_round_rate(struct clk_hw *hw, unsigned long rate, | ||
| unsigned long *prate) | ||
| { | ||
| struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw); | ||
| const struct imx_fracn_gppll_rate_table *rate_table = pll->rate_table; | ||
| int i; | ||
|
|
||
| /* Assumming rate_table is in descending order */ | ||
| for (i = 0; i < pll->rate_count; i++) | ||
| if (rate >= rate_table[i].rate) | ||
| return rate_table[i].rate; | ||
|
|
||
| if (i == pll->rate_count) | ||
| pr_err("Not able to round rate for %s: %lu\n", clk_hw_get_name(hw), rate); | ||
|
|
||
| /* return minimum supported value */ | ||
| return rate_table[i - 1].rate; | ||
| } | ||
|
|
||
| static unsigned long clk_fracn_gppll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) | ||
| { | ||
| struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw); | ||
| const struct imx_fracn_gppll_rate_table *rate_table = pll->rate_table; | ||
| u32 pll_numerator, pll_denominator, pll_div; | ||
| u32 mfi, mfn, mfd, rdiv, odiv; | ||
| u64 fvco = parent_rate; | ||
| long rate = 0; | ||
| int i; | ||
|
|
||
| pll_numerator = readl_relaxed(pll->base + PLL_NUMERATOR); | ||
| mfn = (pll_numerator & PLL_MFN_MASK) >> PLL_MFN_SHIFT; | ||
|
|
||
| pll_denominator = readl_relaxed(pll->base + PLL_DENOMINATOR); | ||
| mfd = pll_denominator & PLL_MFD_MASK; | ||
|
|
||
| pll_div = readl_relaxed(pll->base + PLL_DIV); | ||
| mfi = (pll_div & PLL_MFI_MASK) >> PLL_MFI_SHIFT; | ||
|
|
||
| rdiv = (pll_div & PLL_RDIV_MASK) >> PLL_RDIV_SHIFT; | ||
| rdiv = rdiv + 1; | ||
| odiv = pll_div & PLL_ODIV_MASK; | ||
| switch (odiv) { | ||
| case 0: | ||
| odiv = 2; | ||
| break; | ||
| case 1: | ||
| odiv = 3; | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
|
|
||
| /* | ||
| * Sometimes, the recalculated rate has deviation due to | ||
| * the frac part. So find the accurate pll rate from the table | ||
| * first, if no match rate in the table, use the rate calculated | ||
| * from the equation below. | ||
| */ | ||
| for (i = 0; i < pll->rate_count; i++) { | ||
| if (rate_table[i].mfn == mfn && rate_table[i].mfi == mfi && | ||
| rate_table[i].mfd == mfd && rate_table[i].rdiv == rdiv && | ||
| rate_table[i].odiv == odiv) | ||
| rate = rate_table[i].rate; | ||
| } | ||
|
|
||
| /* Fvco = 𝐹𝑟𝑒𝑓∙(𝑀𝐹𝐼+𝑀𝐹𝑁/𝑀𝐹𝐷) */ | ||
| fvco = fvco * mfi + fvco * mfn / mfd; | ||
|
|
||
| do_div(fvco, rdiv * odiv); | ||
|
|
||
| return rate ? (unsigned long) rate : (unsigned long)fvco; | ||
| } | ||
|
|
||
| static int clk_fracn_gppll_wait_lock(struct clk_fracn_gppll *pll) | ||
| { | ||
| u32 val; | ||
|
|
||
| return readl_poll_timeout(pll->base + PLL_STATUS, val, | ||
| val & LOCK_STATUS, 0, LOCK_TIMEOUT_US); | ||
| } | ||
|
|
||
| static int clk_fracn_gppll_set_rate(struct clk_hw *hw, unsigned long drate, | ||
| unsigned long prate) | ||
| { | ||
| struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw); | ||
| const struct imx_fracn_gppll_rate_table *rate; | ||
| u32 tmp, pll_div, ana_mfn; | ||
| int ret; | ||
|
|
||
| rate = imx_get_pll_settings(pll, drate); | ||
| if (!rate) { | ||
| pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__, | ||
| drate, clk_hw_get_name(hw)); | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| /* Disable output */ | ||
| tmp = readl_relaxed(pll->base + PLL_CTRL); | ||
| tmp &= ~CLKMUX_EN; | ||
| writel_relaxed(tmp, pll->base + PLL_CTRL); | ||
|
|
||
| /* Power Down */ | ||
| tmp &= ~POWERUP_MASK; | ||
| writel_relaxed(tmp, pll->base + PLL_CTRL); | ||
|
|
||
| /* Disable BYPASS */ | ||
| tmp &= ~CLKMUX_BYPASS; | ||
| writel_relaxed(tmp, pll->base + PLL_CTRL); | ||
|
|
||
| pll_div = (rate->rdiv << PLL_RDIV_SHIFT) | rate->odiv | (rate->mfi << PLL_MFI_SHIFT); | ||
| writel_relaxed(pll_div, pll->base + PLL_DIV); | ||
| writel_relaxed(rate->mfd, pll->base + PLL_DENOMINATOR); | ||
| writel_relaxed(rate->mfn << PLL_MFN_SHIFT, pll->base + PLL_NUMERATOR); | ||
|
|
||
| /* Wait for 5us according to fracn mode pll doc */ | ||
| udelay(5); | ||
|
|
||
| /* Enable Powerup */ | ||
| tmp |= POWERUP_MASK; | ||
| writel_relaxed(tmp, pll->base + PLL_CTRL); | ||
|
|
||
| /* Wait Lock*/ | ||
| ret = clk_fracn_gppll_wait_lock(pll); | ||
| if (ret) | ||
| return ret; | ||
|
|
||
| /* Enable output */ | ||
| tmp |= CLKMUX_EN; | ||
| writel_relaxed(tmp, pll->base + PLL_CTRL); | ||
|
|
||
| ana_mfn = (readl_relaxed(pll->base + PLL_STATUS) & PLL_MFN_MASK) >> PLL_MFN_SHIFT; | ||
|
|
||
| WARN(ana_mfn != rate->mfn, "ana_mfn != rate->mfn\n"); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| static int clk_fracn_gppll_prepare(struct clk_hw *hw) | ||
| { | ||
| struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw); | ||
| u32 val; | ||
| int ret; | ||
|
|
||
| val = readl_relaxed(pll->base + PLL_CTRL); | ||
| if (val & POWERUP_MASK) | ||
| return 0; | ||
|
|
||
| val |= CLKMUX_BYPASS; | ||
| writel_relaxed(val, pll->base + PLL_CTRL); | ||
|
|
||
| val |= POWERUP_MASK; | ||
| writel_relaxed(val, pll->base + PLL_CTRL); | ||
|
|
||
| val |= CLKMUX_EN; | ||
| writel_relaxed(val, pll->base + PLL_CTRL); | ||
|
|
||
| ret = clk_fracn_gppll_wait_lock(pll); | ||
| if (ret) | ||
| return ret; | ||
|
|
||
| val &= ~CLKMUX_BYPASS; | ||
| writel_relaxed(val, pll->base + PLL_CTRL); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| static int clk_fracn_gppll_is_prepared(struct clk_hw *hw) | ||
| { | ||
| struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw); | ||
| u32 val; | ||
|
|
||
| val = readl_relaxed(pll->base + PLL_CTRL); | ||
|
|
||
| return (val & POWERUP_MASK) ? 1 : 0; | ||
| } | ||
|
|
||
| static void clk_fracn_gppll_unprepare(struct clk_hw *hw) | ||
| { | ||
| struct clk_fracn_gppll *pll = to_clk_fracn_gppll(hw); | ||
| u32 val; | ||
|
|
||
| val = readl_relaxed(pll->base + PLL_CTRL); | ||
| val &= ~POWERUP_MASK; | ||
| writel_relaxed(val, pll->base + PLL_CTRL); | ||
| } | ||
|
|
||
| static const struct clk_ops clk_fracn_gppll_ops = { | ||
| .prepare = clk_fracn_gppll_prepare, | ||
| .unprepare = clk_fracn_gppll_unprepare, | ||
| .is_prepared = clk_fracn_gppll_is_prepared, | ||
| .recalc_rate = clk_fracn_gppll_recalc_rate, | ||
| .round_rate = clk_fracn_gppll_round_rate, | ||
| .set_rate = clk_fracn_gppll_set_rate, | ||
| }; | ||
|
|
||
| struct clk_hw *imx_clk_fracn_gppll(const char *name, const char *parent_name, void __iomem *base, | ||
| const struct imx_fracn_gppll_clk *pll_clk) | ||
| { | ||
| struct clk_fracn_gppll *pll; | ||
| struct clk_hw *hw; | ||
| struct clk_init_data init; | ||
| int ret; | ||
|
|
||
| pll = kzalloc(sizeof(*pll), GFP_KERNEL); | ||
| if (!pll) | ||
| return ERR_PTR(-ENOMEM); | ||
|
|
||
| init.name = name; | ||
| init.flags = pll_clk->flags; | ||
| init.parent_names = &parent_name; | ||
| init.num_parents = 1; | ||
| init.ops = &clk_fracn_gppll_ops; | ||
|
|
||
| pll->base = base; | ||
| pll->hw.init = &init; | ||
| pll->rate_table = pll_clk->rate_table; | ||
| pll->rate_count = pll_clk->rate_count; | ||
|
|
||
| hw = &pll->hw; | ||
|
|
||
| ret = clk_hw_register(NULL, hw); | ||
| if (ret) { | ||
| pr_err("%s: failed to register pll %s %d\n", __func__, name, ret); | ||
| kfree(pll); | ||
| return ERR_PTR(ret); | ||
| } | ||
|
|
||
| return hw; | ||
| } | ||
| EXPORT_SYMBOL_GPL(imx_clk_fracn_gppll); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters