Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Chunyan Zhang | 1526 | 100.00% | 1 | 100.00% |
Total | 1526 | 1 |
// SPDX-License-Identifier: GPL-2.0 // // Spreadtrum pll clock driver // // Copyright (C) 2015~2017 Spreadtrum, Inc. // Author: Chunyan Zhang <chunyan.zhang@spreadtrum.com> #include <linux/delay.h> #include <linux/err.h> #include <linux/regmap.h> #include <linux/slab.h> #include "pll.h" #define CLK_PLL_1M 1000000 #define CLK_PLL_10M (CLK_PLL_1M * 10) #define pindex(pll, member) \ (pll->factors[member].shift / (8 * sizeof(pll->regs_num))) #define pshift(pll, member) \ (pll->factors[member].shift % (8 * sizeof(pll->regs_num))) #define pwidth(pll, member) \ pll->factors[member].width #define pmask(pll, member) \ ((pwidth(pll, member)) ? \ GENMASK(pwidth(pll, member) + pshift(pll, member) - 1, \ pshift(pll, member)) : 0) #define pinternal(pll, cfg, member) \ (cfg[pindex(pll, member)] & pmask(pll, member)) #define pinternal_val(pll, cfg, member) \ (pinternal(pll, cfg, member) >> pshift(pll, member)) static inline unsigned int sprd_pll_read(const struct sprd_pll *pll, u8 index) { const struct sprd_clk_common *common = &pll->common; unsigned int val = 0; if (WARN_ON(index >= pll->regs_num)) return 0; regmap_read(common->regmap, common->reg + index * 4, &val); return val; } static inline void sprd_pll_write(const struct sprd_pll *pll, u8 index, u32 msk, u32 val) { const struct sprd_clk_common *common = &pll->common; unsigned int offset, reg; int ret = 0; if (WARN_ON(index >= pll->regs_num)) return; offset = common->reg + index * 4; ret = regmap_read(common->regmap, offset, ®); if (!ret) regmap_write(common->regmap, offset, (reg & ~msk) | val); } static unsigned long pll_get_refin(const struct sprd_pll *pll) { u32 shift, mask, index, refin_id = 3; const unsigned long refin[4] = { 2, 4, 13, 26 }; if (pwidth(pll, PLL_REFIN)) { index = pindex(pll, PLL_REFIN); shift = pshift(pll, PLL_REFIN); mask = pmask(pll, PLL_REFIN); refin_id = (sprd_pll_read(pll, index) & mask) >> shift; if (refin_id > 3) refin_id = 3; } return refin[refin_id]; } static u32 pll_get_ibias(u64 rate, const u64 *table) { u32 i, num = table[0]; for (i = 1; i < num + 1; i++) if (rate <= table[i]) break; return (i == num + 1) ? num : i; } static unsigned long _sprd_pll_recalc_rate(const struct sprd_pll *pll, unsigned long parent_rate) { u32 *cfg; u32 i, mask, regs_num = pll->regs_num; unsigned long rate, nint, kint = 0; u64 refin; u16 k1, k2; cfg = kcalloc(regs_num, sizeof(*cfg), GFP_KERNEL); if (!cfg) return -ENOMEM; for (i = 0; i < regs_num; i++) cfg[i] = sprd_pll_read(pll, i); refin = pll_get_refin(pll); if (pinternal(pll, cfg, PLL_PREDIV)) refin = refin * 2; if (pwidth(pll, PLL_POSTDIV) && ((pll->fflag == 1 && pinternal(pll, cfg, PLL_POSTDIV)) || (!pll->fflag && !pinternal(pll, cfg, PLL_POSTDIV)))) refin = refin / 2; if (!pinternal(pll, cfg, PLL_DIV_S)) { rate = refin * pinternal_val(pll, cfg, PLL_N) * CLK_PLL_10M; } else { nint = pinternal_val(pll, cfg, PLL_NINT); if (pinternal(pll, cfg, PLL_SDM_EN)) kint = pinternal_val(pll, cfg, PLL_KINT); mask = pmask(pll, PLL_KINT); k1 = pll->k1; k2 = pll->k2; rate = DIV_ROUND_CLOSEST_ULL(refin * kint * k1, ((mask >> __ffs(mask)) + 1)) * k2 + refin * nint * CLK_PLL_1M; } return rate; } #define SPRD_PLL_WRITE_CHECK(pll, i, mask, val) \ (((sprd_pll_read(pll, i) & mask) == val) ? 0 : (-EFAULT)) static int _sprd_pll_set_rate(const struct sprd_pll *pll, unsigned long rate, unsigned long parent_rate) { struct reg_cfg *cfg; int ret = 0; u32 mask, shift, width, ibias_val, index; u32 regs_num = pll->regs_num, i = 0; unsigned long kint, nint; u64 tmp, refin, fvco = rate; cfg = kcalloc(regs_num, sizeof(*cfg), GFP_KERNEL); if (!cfg) return -ENOMEM; refin = pll_get_refin(pll); mask = pmask(pll, PLL_PREDIV); index = pindex(pll, PLL_PREDIV); width = pwidth(pll, PLL_PREDIV); if (width && (sprd_pll_read(pll, index) & mask)) refin = refin * 2; mask = pmask(pll, PLL_POSTDIV); index = pindex(pll, PLL_POSTDIV); width = pwidth(pll, PLL_POSTDIV); cfg[index].msk = mask; if (width && ((pll->fflag == 1 && fvco <= pll->fvco) || (pll->fflag == 0 && fvco > pll->fvco))) cfg[index].val |= mask; if (width && fvco <= pll->fvco) fvco = fvco * 2; mask = pmask(pll, PLL_DIV_S); index = pindex(pll, PLL_DIV_S); cfg[index].val |= mask; cfg[index].msk |= mask; mask = pmask(pll, PLL_SDM_EN); index = pindex(pll, PLL_SDM_EN); cfg[index].val |= mask; cfg[index].msk |= mask; nint = do_div(fvco, refin * CLK_PLL_1M); mask = pmask(pll, PLL_NINT); index = pindex(pll, PLL_NINT); shift = pshift(pll, PLL_NINT); cfg[index].val |= (nint << shift) & mask; cfg[index].msk |= mask; mask = pmask(pll, PLL_KINT); index = pindex(pll, PLL_KINT); width = pwidth(pll, PLL_KINT); shift = pshift(pll, PLL_KINT); tmp = fvco - refin * nint * CLK_PLL_1M; tmp = do_div(tmp, 10000) * ((mask >> shift) + 1); kint = DIV_ROUND_CLOSEST_ULL(tmp, refin * 100); cfg[index].val |= (kint << shift) & mask; cfg[index].msk |= mask; ibias_val = pll_get_ibias(fvco, pll->itable); mask = pmask(pll, PLL_IBIAS); index = pindex(pll, PLL_IBIAS); shift = pshift(pll, PLL_IBIAS); cfg[index].val |= ibias_val << shift & mask; cfg[index].msk |= mask; for (i = 0; i < regs_num; i++) { if (cfg[i].msk) { sprd_pll_write(pll, i, cfg[i].msk, cfg[i].val); ret |= SPRD_PLL_WRITE_CHECK(pll, i, cfg[i].msk, cfg[i].val); } } if (!ret) udelay(pll->udelay); return ret; } static unsigned long sprd_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct sprd_pll *pll = hw_to_sprd_pll(hw); return _sprd_pll_recalc_rate(pll, parent_rate); } static int sprd_pll_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct sprd_pll *pll = hw_to_sprd_pll(hw); return _sprd_pll_set_rate(pll, rate, parent_rate); } static int sprd_pll_clk_prepare(struct clk_hw *hw) { struct sprd_pll *pll = hw_to_sprd_pll(hw); udelay(pll->udelay); return 0; } static long sprd_pll_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate) { return rate; } const struct clk_ops sprd_pll_ops = { .prepare = sprd_pll_clk_prepare, .recalc_rate = sprd_pll_recalc_rate, .round_rate = sprd_pll_round_rate, .set_rate = sprd_pll_set_rate, }; EXPORT_SYMBOL_GPL(sprd_pll_ops);
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1