/* * Copyright (c) 2017 Rockchip Electronics Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "clk-regmap.h" #define PLLCON_OFFSET(x) (x * 4) #define PLL_BYPASS(x) HIWORD_UPDATE(x, 15, 15) #define PLL_BYPASS_MASK BIT(15) #define PLL_BYPASS_SHIFT 15 #define PLL_POSTDIV1(x) HIWORD_UPDATE(x, 14, 12) #define PLL_POSTDIV1_MASK GENMASK(14, 12) #define PLL_POSTDIV1_SHIFT 12 #define PLL_FBDIV(x) HIWORD_UPDATE(x, 11, 0) #define PLL_FBDIV_MASK GENMASK(11, 0) #define PLL_FBDIV_SHIFT 0 #define PLL_POSTDIV2(x) HIWORD_UPDATE(x, 8, 6) #define PLL_POSTDIV2_MASK GENMASK(8, 6) #define PLL_POSTDIV2_SHIFT 6 #define PLL_REFDIV(x) HIWORD_UPDATE(x, 5, 0) #define PLL_REFDIV_MASK GENMASK(5, 0) #define PLL_REFDIV_SHIFT 0 #define PLL_FOUT_4PHASE_CLK_POWER_DOWN BIT(27) #define PLL_FOUT_VCO_CLK_POWER_DOWN BIT(26) #define PLL_FOUT_POST_DIV_POWER_DOWN BIT(25) #define PLL_DAC_POWER_DOWN BIT(24) #define PLL_FRAC(x) UPDATE(x, 23, 0) #define PLL_FRAC_MASK GENMASK(23, 0) #define PLL_FRAC_SHIFT 0 #define MIN_FREF_RATE 10000000UL #define MAX_FREF_RATE 800000000UL #define MIN_FREFDIV_RATE 1000000UL #define MAX_FREFDIV_RATE 40000000UL #define MIN_FVCO_RATE 400000000UL #define MAX_FVCO_RATE 1600000000UL #define MIN_FOUTPOSTDIV_RATE 8000000UL #define MAX_FOUTPOSTDIV_RATE 1600000000UL struct clk_regmap_pll { struct clk_hw hw; struct device *dev; struct regmap *regmap; unsigned int reg; u8 pd_shift; u8 dsmpd_shift; u8 lock_shift; }; #define to_clk_regmap_pll(_hw) container_of(_hw, struct clk_regmap_pll, hw) static unsigned long clk_regmap_pll_recalc_rate(struct clk_hw *hw, unsigned long prate) { struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); unsigned int postdiv1, fbdiv, dsmpd, postdiv2, refdiv, frac, bypass; unsigned int con0, con1, con2; u64 foutvco, foutpostdiv; regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(0), &con0); regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(1), &con1); regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(2), &con2); bypass = (con0 & PLL_BYPASS_MASK) >> PLL_BYPASS_SHIFT; postdiv1 = (con0 & PLL_POSTDIV1_MASK) >> PLL_POSTDIV1_SHIFT; fbdiv = (con0 & PLL_FBDIV_MASK) >> PLL_FBDIV_SHIFT; dsmpd = (con1 & BIT(pll->dsmpd_shift)) >> pll->dsmpd_shift; postdiv2 = (con1 & PLL_POSTDIV2_MASK) >> PLL_POSTDIV2_SHIFT; refdiv = (con1 & PLL_REFDIV_MASK) >> PLL_REFDIV_SHIFT; frac = (con2 & PLL_FRAC_MASK) >> PLL_FRAC_SHIFT; if (bypass) return prate; foutvco = prate * fbdiv; do_div(foutvco, refdiv); if (!dsmpd) { u64 frac_rate = (u64)prate * frac; do_div(frac_rate, refdiv); foutvco += frac_rate >> 24; } foutpostdiv = foutvco; do_div(foutpostdiv, postdiv1); do_div(foutpostdiv, postdiv2); return foutpostdiv; } static long clk_pll_round_rate(unsigned long fin, unsigned long fout, u8 *refdiv, u16 *fbdiv, u8 *postdiv1, u8 *postdiv2, u32 *frac, u8 *dsmpd, u8 *bypass) { u8 min_refdiv, max_refdiv, postdiv; u8 _dsmpd = 1, _postdiv1 = 0, _postdiv2 = 0, _refdiv = 0; u16 _fbdiv = 0; u32 _frac = 0; u64 foutvco, foutpostdiv; /* * FREF : 10MHz ~ 800MHz * FREFDIV : 1MHz ~ 40MHz * FOUTVCO : 400MHz ~ 1.6GHz * FOUTPOSTDIV : 8MHz ~ 1.6GHz */ if (fin < MIN_FREF_RATE || fin > MAX_FREF_RATE) return -EINVAL; if (fout < MIN_FOUTPOSTDIV_RATE || fout > MAX_FOUTPOSTDIV_RATE) return -EINVAL; if (fin == fout) { if (bypass) *bypass = true; return fin; } min_refdiv = DIV_ROUND_UP(fin, MAX_FREFDIV_RATE); max_refdiv = fin / MIN_FREFDIV_RATE; if (max_refdiv > 64) max_refdiv = 64; if (fout < MIN_FVCO_RATE) { postdiv = DIV_ROUND_UP_ULL(MIN_FVCO_RATE, fout); for (_postdiv2 = 1; _postdiv2 < 8; _postdiv2++) { if (postdiv % _postdiv2) continue; _postdiv1 = postdiv / _postdiv2; if (_postdiv1 > 0 && _postdiv1 < 8) break; } if (_postdiv2 > 7) return -EINVAL; fout *= _postdiv1 * _postdiv2; } else { _postdiv1 = 1; _postdiv2 = 1; } for (_refdiv = min_refdiv; _refdiv <= max_refdiv; _refdiv++) { u64 tmp, frac_rate; if (fin % _refdiv) continue; tmp = (u64)fout * _refdiv; do_div(tmp, fin); _fbdiv = tmp; if (_fbdiv < 10 || _fbdiv > 1600) continue; tmp = (u64)_fbdiv * fin; do_div(tmp, _refdiv); if (fout < MIN_FVCO_RATE || fout > MAX_FVCO_RATE) continue; frac_rate = fout - tmp; if (frac_rate) { tmp = (u64)frac_rate * _refdiv; tmp <<= 24; do_div(tmp, fin); _frac = tmp; _dsmpd = 0; } break; } /* * If DSMPD = 1 (DSM is disabled, "integer mode") * FOUTVCO = FREF / REFDIV * FBDIV * FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2 * * If DSMPD = 0 (DSM is enabled, "fractional mode") * FOUTVCO = FREF / REFDIV * (FBDIV + FRAC / 2^24) * FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2 */ foutvco = fin * _fbdiv; do_div(foutvco, _refdiv); if (!_dsmpd) { u64 frac_rate = (u64)fin * _frac; do_div(frac_rate, _refdiv); foutvco += frac_rate >> 24; } foutpostdiv = foutvco; do_div(foutpostdiv, _postdiv1); do_div(foutpostdiv, _postdiv2); if (refdiv) *refdiv = _refdiv; if (fbdiv) *fbdiv = _fbdiv; if (postdiv1) *postdiv1 = _postdiv1; if (postdiv2) *postdiv2 = _postdiv2; if (frac) *frac = _frac; if (dsmpd) *dsmpd = _dsmpd; if (bypass) *bypass = false; return (unsigned long)foutpostdiv; } static long clk_regmap_pll_round_rate(struct clk_hw *hw, unsigned long drate, unsigned long *prate) { struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); long rate; rate = clk_pll_round_rate(*prate, drate, NULL, NULL, NULL, NULL, NULL, NULL, NULL); dev_dbg(pll->dev, "%s: prate=%ld, drate=%ld, rate=%ld\n", clk_hw_get_name(hw), *prate, drate, rate); return rate; } static int clk_regmap_pll_set_rate(struct clk_hw *hw, unsigned long drate, unsigned long prate) { struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); u8 refdiv, postdiv1, postdiv2, dsmpd, bypass; u16 fbdiv; u32 frac; long rate; rate = clk_pll_round_rate(prate, drate, &refdiv, &fbdiv, &postdiv1, &postdiv2, &frac, &dsmpd, &bypass); if (rate < 0) return rate; dev_dbg(pll->dev, "%s: rate=%ld, bypass=%d\n", clk_hw_get_name(hw), drate, bypass); if (bypass) { regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(0), PLL_BYPASS(1)); } else { regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(0), PLL_BYPASS(0) | PLL_POSTDIV1(postdiv1) | PLL_FBDIV(fbdiv)); regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), HIWORD_UPDATE(dsmpd, pll->dsmpd_shift, pll->dsmpd_shift) | PLL_POSTDIV2(postdiv2) | PLL_REFDIV(refdiv)); regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(2), PLL_FRAC(frac)); dev_dbg(pll->dev, "refdiv=%d, fbdiv=%d, frac=%d\n", refdiv, fbdiv, frac); dev_dbg(pll->dev, "postdiv1=%d, postdiv2=%d\n", postdiv1, postdiv2); } return 0; } static int clk_regmap_pll_prepare(struct clk_hw *hw) { struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); u32 v; int ret; regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), HIWORD_UPDATE(0, pll->pd_shift, pll->pd_shift)); ret = regmap_read_poll_timeout(pll->regmap, pll->reg + PLLCON_OFFSET(1), v, v & BIT(pll->lock_shift), 50, 50000); if (ret) dev_err(pll->dev, "%s is not lock\n", clk_hw_get_name(hw)); return 0; } static void clk_regmap_pll_unprepare(struct clk_hw *hw) { struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); regmap_write(pll->regmap, pll->reg + PLLCON_OFFSET(1), HIWORD_UPDATE(1, pll->pd_shift, pll->pd_shift)); } static int clk_regmap_pll_is_prepared(struct clk_hw *hw) { struct clk_regmap_pll *pll = to_clk_regmap_pll(hw); unsigned int con1; regmap_read(pll->regmap, pll->reg + PLLCON_OFFSET(1), &con1); return !(con1 & BIT(pll->pd_shift)); } static const struct clk_ops clk_regmap_pll_ops = { .recalc_rate = clk_regmap_pll_recalc_rate, .round_rate = clk_regmap_pll_round_rate, .set_rate = clk_regmap_pll_set_rate, .prepare = clk_regmap_pll_prepare, .unprepare = clk_regmap_pll_unprepare, .is_prepared = clk_regmap_pll_is_prepared, }; struct clk * devm_clk_regmap_register_pll(struct device *dev, const char *name, const char *parent_name, struct regmap *regmap, u32 reg, u8 pd_shift, u8 dsmpd_shift, u8 lock_shift, unsigned long flags) { struct clk_regmap_pll *pll; struct clk_init_data init = {}; pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); if (!pll) return ERR_PTR(-ENOMEM); init.name = name; init.ops = &clk_regmap_pll_ops; init.flags = flags; init.parent_names = (parent_name ? &parent_name : NULL); init.num_parents = (parent_name ? 1 : 0); pll->dev = dev; pll->regmap = regmap; pll->reg = reg; pll->pd_shift = pd_shift; pll->dsmpd_shift = dsmpd_shift; pll->lock_shift = lock_shift; pll->hw.init = &init; return devm_clk_register(dev, &pll->hw); } EXPORT_SYMBOL_GPL(devm_clk_regmap_register_pll);