Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Chen Wang | 2099 | 99.76% | 1 | 33.33% |
Nathan Chancellor | 3 | 0.14% | 1 | 33.33% |
Dan Carpenter | 2 | 0.10% | 1 | 33.33% |
Total | 2104 | 3 |
// SPDX-License-Identifier: GPL-2.0 /* * Sophgo SG2042 PLL clock Driver * * Copyright (C) 2024 Sophgo Technology Inc. * Copyright (C) 2024 Chen Wang <unicorn_wang@outlook.com> */ #include <linux/array_size.h> #include <linux/bitfield.h> #include <linux/bits.h> #include <linux/clk-provider.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/platform_device.h> #include <asm/div64.h> #include <dt-bindings/clock/sophgo,sg2042-pll.h> #include "clk-sg2042.h" /* Registers defined in SYS_CTRL */ #define R_PLL_BEGIN 0xC0 #define R_PLL_STAT (0xC0 - R_PLL_BEGIN) #define R_PLL_CLKEN_CONTROL (0xC4 - R_PLL_BEGIN) #define R_MPLL_CONTROL (0xE8 - R_PLL_BEGIN) #define R_FPLL_CONTROL (0xF4 - R_PLL_BEGIN) #define R_DPLL0_CONTROL (0xF8 - R_PLL_BEGIN) #define R_DPLL1_CONTROL (0xFC - R_PLL_BEGIN) /** * struct sg2042_pll_clock - PLL clock * @hw: clk_hw for initialization * @id: used to map clk_onecell_data * @base: used for readl/writel. * **NOTE**: PLL registers are all in SYS_CTRL! * @lock: spinlock to protect register access, modification * of frequency can only be served one at the time. * @offset_ctrl: offset of pll control registers * @shift_status_lock: shift of XXX_LOCK in pll status register * @shift_status_updating: shift of UPDATING_XXX in pll status register * @shift_enable: shift of XXX_CLK_EN in pll enable register */ struct sg2042_pll_clock { struct clk_hw hw; unsigned int id; void __iomem *base; /* protect register access */ spinlock_t *lock; u32 offset_ctrl; u8 shift_status_lock; u8 shift_status_updating; u8 shift_enable; }; #define to_sg2042_pll_clk(_hw) container_of(_hw, struct sg2042_pll_clock, hw) #define KHZ 1000UL #define MHZ (KHZ * KHZ) #define REFDIV_MIN 1 #define REFDIV_MAX 63 #define FBDIV_MIN 16 #define FBDIV_MAX 320 #define PLL_FREF_SG2042 (25 * MHZ) #define PLL_FOUTPOSTDIV_MIN (16 * MHZ) #define PLL_FOUTPOSTDIV_MAX (3200 * MHZ) #define PLL_FOUTVCO_MIN (800 * MHZ) #define PLL_FOUTVCO_MAX (3200 * MHZ) struct sg2042_pll_ctrl { unsigned long freq; unsigned int fbdiv; unsigned int postdiv1; unsigned int postdiv2; unsigned int refdiv; }; #define PLLCTRL_FBDIV_MASK GENMASK(27, 16) #define PLLCTRL_POSTDIV2_MASK GENMASK(14, 12) #define PLLCTRL_POSTDIV1_MASK GENMASK(10, 8) #define PLLCTRL_REFDIV_MASK GENMASK(5, 0) static inline u32 sg2042_pll_ctrl_encode(struct sg2042_pll_ctrl *ctrl) { return FIELD_PREP(PLLCTRL_FBDIV_MASK, ctrl->fbdiv) | FIELD_PREP(PLLCTRL_POSTDIV2_MASK, ctrl->postdiv2) | FIELD_PREP(PLLCTRL_POSTDIV1_MASK, ctrl->postdiv1) | FIELD_PREP(PLLCTRL_REFDIV_MASK, ctrl->refdiv); } static inline void sg2042_pll_ctrl_decode(unsigned int reg_value, struct sg2042_pll_ctrl *ctrl) { ctrl->fbdiv = FIELD_GET(PLLCTRL_FBDIV_MASK, reg_value); ctrl->refdiv = FIELD_GET(PLLCTRL_REFDIV_MASK, reg_value); ctrl->postdiv1 = FIELD_GET(PLLCTRL_POSTDIV1_MASK, reg_value); ctrl->postdiv2 = FIELD_GET(PLLCTRL_POSTDIV2_MASK, reg_value); } static inline void sg2042_pll_enable(struct sg2042_pll_clock *pll, bool en) { u32 value; if (en) { /* wait pll lock */ if (readl_poll_timeout_atomic(pll->base + R_PLL_STAT, value, ((value >> pll->shift_status_lock) & 0x1), 0, 100000)) pr_warn("%s not locked\n", pll->hw.init->name); /* wait pll updating */ if (readl_poll_timeout_atomic(pll->base + R_PLL_STAT, value, !((value >> pll->shift_status_updating) & 0x1), 0, 100000)) pr_warn("%s still updating\n", pll->hw.init->name); /* enable pll */ value = readl(pll->base + R_PLL_CLKEN_CONTROL); writel(value | (1 << pll->shift_enable), pll->base + R_PLL_CLKEN_CONTROL); } else { /* disable pll */ value = readl(pll->base + R_PLL_CLKEN_CONTROL); writel(value & (~(1 << pll->shift_enable)), pll->base + R_PLL_CLKEN_CONTROL); } } /** * sg2042_pll_recalc_rate() - Calculate rate for plls * @reg_value: current register value * @parent_rate: parent frequency * * This function is used to calculate below "rate" in equation * rate = (parent_rate/REFDIV) x FBDIV/POSTDIV1/POSTDIV2 * = (parent_rate x FBDIV) / (REFDIV x POSTDIV1 x POSTDIV2) * * Return: The rate calculated. */ static unsigned long sg2042_pll_recalc_rate(unsigned int reg_value, unsigned long parent_rate) { struct sg2042_pll_ctrl ctrl_table; u64 numerator, denominator; sg2042_pll_ctrl_decode(reg_value, &ctrl_table); numerator = parent_rate * ctrl_table.fbdiv; denominator = ctrl_table.refdiv * ctrl_table.postdiv1 * ctrl_table.postdiv2; do_div(numerator, denominator); return numerator; } /** * sg2042_pll_get_postdiv_1_2() - Based on input rate/prate/fbdiv/refdiv, * look up the postdiv1_2 table to get the closest postdiiv combination. * @rate: FOUTPOSTDIV * @prate: parent rate, i.e. FREF * @fbdiv: FBDIV * @refdiv: REFDIV * @postdiv1: POSTDIV1, output * @postdiv2: POSTDIV2, output * * postdiv1_2 contains all the possible combination lists of POSTDIV1 and POSTDIV2 * for example: * postdiv1_2[0] = {2, 4, 8}, where div1 = 2, div2 = 4 , div1 * div2 = 8 * * See TRM: * FOUTPOSTDIV = FREF * FBDIV / REFDIV / (POSTDIV1 * POSTDIV2) * So we get following formula to get POSTDIV1 and POSTDIV2: * POSTDIV = (prate/REFDIV) x FBDIV/rate * above POSTDIV = POSTDIV1*POSTDIV2 * * Return: * %0 - OK * %-EINVAL - invalid argument, which means Failed to get the postdivs. */ static int sg2042_pll_get_postdiv_1_2(unsigned long rate, unsigned long prate, unsigned int fbdiv, unsigned int refdiv, unsigned int *postdiv1, unsigned int *postdiv2) { int index; u64 tmp0; /* POSTDIV_RESULT_INDEX point to 3rd element in the array postdiv1_2 */ #define POSTDIV_RESULT_INDEX 2 static const int postdiv1_2[][3] = { {2, 4, 8}, {3, 3, 9}, {2, 5, 10}, {2, 6, 12}, {2, 7, 14}, {3, 5, 15}, {4, 4, 16}, {3, 6, 18}, {4, 5, 20}, {3, 7, 21}, {4, 6, 24}, {5, 5, 25}, {4, 7, 28}, {5, 6, 30}, {5, 7, 35}, {6, 6, 36}, {6, 7, 42}, {7, 7, 49} }; /* prate/REFDIV and result save to tmp0 */ tmp0 = prate; do_div(tmp0, refdiv); /* ((prate/REFDIV) x FBDIV) and result save to tmp0 */ tmp0 *= fbdiv; /* ((prate/REFDIV) x FBDIV)/rate and result save to tmp0 */ do_div(tmp0, rate); /* tmp0 is POSTDIV1*POSTDIV2, now we calculate div1 and div2 value */ if (tmp0 <= 7) { /* (div1 * div2) <= 7, no need to use array search */ *postdiv1 = tmp0; *postdiv2 = 1; return 0; } /* (div1 * div2) > 7, use array search */ for (index = 0; index < ARRAY_SIZE(postdiv1_2); index++) { if (tmp0 > postdiv1_2[index][POSTDIV_RESULT_INDEX]) { continue; } else { /* found it */ *postdiv1 = postdiv1_2[index][1]; *postdiv2 = postdiv1_2[index][0]; return 0; } } pr_warn("%s can not find in postdiv array!\n", __func__); return -EINVAL; } /** * sg2042_get_pll_ctl_setting() - Based on the given FOUTPISTDIV and the input * FREF to calculate the REFDIV/FBDIV/PSTDIV1/POSTDIV2 combination for pllctrl * register. * @req_rate: expected output clock rate, i.e. FOUTPISTDIV * @parent_rate: input parent clock rate, i.e. FREF * @best: output to hold calculated combination of REFDIV/FBDIV/PSTDIV1/POSTDIV2 * * Return: * %0 - OK * %-EINVAL - invalid argument */ static int sg2042_get_pll_ctl_setting(struct sg2042_pll_ctrl *best, unsigned long req_rate, unsigned long parent_rate) { unsigned int fbdiv, refdiv, postdiv1, postdiv2; unsigned long foutpostdiv; u64 foutvco; int ret; u64 tmp; if (parent_rate != PLL_FREF_SG2042) { pr_err("INVALID FREF: %ld\n", parent_rate); return -EINVAL; } if (req_rate < PLL_FOUTPOSTDIV_MIN || req_rate > PLL_FOUTPOSTDIV_MAX) { pr_alert("INVALID FOUTPOSTDIV: %ld\n", req_rate); return -EINVAL; } memset(best, 0, sizeof(struct sg2042_pll_ctrl)); for (refdiv = REFDIV_MIN; refdiv < REFDIV_MAX + 1; refdiv++) { /* required by hardware: FREF/REFDIV must > 10 */ tmp = parent_rate; do_div(tmp, refdiv); if (tmp <= 10) continue; for (fbdiv = FBDIV_MIN; fbdiv < FBDIV_MAX + 1; fbdiv++) { /* * FOUTVCO = FREF*FBDIV/REFDIV validation * required by hardware, FOUTVCO must [800MHz, 3200MHz] */ foutvco = parent_rate * fbdiv; do_div(foutvco, refdiv); if (foutvco < PLL_FOUTVCO_MIN || foutvco > PLL_FOUTVCO_MAX) continue; ret = sg2042_pll_get_postdiv_1_2(req_rate, parent_rate, fbdiv, refdiv, &postdiv1, &postdiv2); if (ret) continue; /* * FOUTPOSTDIV = FREF*FBDIV/REFDIV/(POSTDIV1*POSTDIV2) * = FOUTVCO/(POSTDIV1*POSTDIV2) */ tmp = foutvco; do_div(tmp, (postdiv1 * postdiv2)); foutpostdiv = (unsigned long)tmp; /* Iterative to approach the expected value */ if (abs_diff(foutpostdiv, req_rate) < abs_diff(best->freq, req_rate)) { best->freq = foutpostdiv; best->refdiv = refdiv; best->fbdiv = fbdiv; best->postdiv1 = postdiv1; best->postdiv2 = postdiv2; if (foutpostdiv == req_rate) return 0; } continue; } } if (best->freq == 0) return -EINVAL; else return 0; } /** * sg2042_clk_pll_recalc_rate() - recalc_rate callback for pll clks * @hw: ccf use to hook get sg2042_pll_clock * @parent_rate: parent rate * * The is function will be called through clk_get_rate * and return current rate after decoding reg value * * Return: Current rate recalculated. */ static unsigned long sg2042_clk_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct sg2042_pll_clock *pll = to_sg2042_pll_clk(hw); unsigned long rate; u32 value; value = readl(pll->base + pll->offset_ctrl); rate = sg2042_pll_recalc_rate(value, parent_rate); pr_debug("--> %s: pll_recalc_rate: val = %ld\n", clk_hw_get_name(hw), rate); return rate; } static long sg2042_clk_pll_round_rate(struct clk_hw *hw, unsigned long req_rate, unsigned long *prate) { struct sg2042_pll_ctrl pctrl_table; unsigned int value; long proper_rate; int ret; ret = sg2042_get_pll_ctl_setting(&pctrl_table, req_rate, *prate); if (ret) { proper_rate = 0; goto out; } value = sg2042_pll_ctrl_encode(&pctrl_table); proper_rate = (long)sg2042_pll_recalc_rate(value, *prate); out: pr_debug("--> %s: pll_round_rate: val = %ld\n", clk_hw_get_name(hw), proper_rate); return proper_rate; } static int sg2042_clk_pll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { req->rate = sg2042_clk_pll_round_rate(hw, min(req->rate, req->max_rate), &req->best_parent_rate); pr_debug("--> %s: pll_determine_rate: val = %ld\n", clk_hw_get_name(hw), req->rate); return 0; } static int sg2042_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct sg2042_pll_clock *pll = to_sg2042_pll_clk(hw); struct sg2042_pll_ctrl pctrl_table; unsigned long flags; u32 value = 0; int ret; spin_lock_irqsave(pll->lock, flags); sg2042_pll_enable(pll, 0); ret = sg2042_get_pll_ctl_setting(&pctrl_table, rate, parent_rate); if (ret) { pr_warn("%s: Can't find a proper pll setting\n", pll->hw.init->name); goto out; } value = sg2042_pll_ctrl_encode(&pctrl_table); /* write the value to top register */ writel(value, pll->base + pll->offset_ctrl); out: sg2042_pll_enable(pll, 1); spin_unlock_irqrestore(pll->lock, flags); pr_debug("--> %s: pll_set_rate: val = 0x%x\n", clk_hw_get_name(hw), value); return ret; } static const struct clk_ops sg2042_clk_pll_ops = { .recalc_rate = sg2042_clk_pll_recalc_rate, .round_rate = sg2042_clk_pll_round_rate, .determine_rate = sg2042_clk_pll_determine_rate, .set_rate = sg2042_clk_pll_set_rate, }; static const struct clk_ops sg2042_clk_pll_ro_ops = { .recalc_rate = sg2042_clk_pll_recalc_rate, .round_rate = sg2042_clk_pll_round_rate, }; /* * Clock initialization macro naming rules: * FW: use CLK_HW_INIT_FW_NAME * RO: means Read-Only */ #define SG2042_PLL_FW(_id, _name, _parent, _r_ctrl, _shift) \ { \ .id = _id, \ .hw.init = CLK_HW_INIT_FW_NAME( \ _name, \ _parent, \ &sg2042_clk_pll_ops, \ CLK_GET_RATE_NOCACHE | CLK_GET_ACCURACY_NOCACHE),\ .offset_ctrl = _r_ctrl, \ .shift_status_lock = 8 + (_shift), \ .shift_status_updating = _shift, \ .shift_enable = _shift, \ } #define SG2042_PLL_FW_RO(_id, _name, _parent, _r_ctrl, _shift) \ { \ .id = _id, \ .hw.init = CLK_HW_INIT_FW_NAME( \ _name, \ _parent, \ &sg2042_clk_pll_ro_ops, \ CLK_GET_RATE_NOCACHE | CLK_GET_ACCURACY_NOCACHE),\ .offset_ctrl = _r_ctrl, \ .shift_status_lock = 8 + (_shift), \ .shift_status_updating = _shift, \ .shift_enable = _shift, \ } static struct sg2042_pll_clock sg2042_pll_clks[] = { SG2042_PLL_FW(MPLL_CLK, "mpll_clock", "cgi_main", R_MPLL_CONTROL, 0), SG2042_PLL_FW_RO(FPLL_CLK, "fpll_clock", "cgi_main", R_FPLL_CONTROL, 3), SG2042_PLL_FW_RO(DPLL0_CLK, "dpll0_clock", "cgi_dpll0", R_DPLL0_CONTROL, 4), SG2042_PLL_FW_RO(DPLL1_CLK, "dpll1_clock", "cgi_dpll1", R_DPLL1_CONTROL, 5), }; static DEFINE_SPINLOCK(sg2042_clk_lock); static int sg2042_clk_register_plls(struct device *dev, struct sg2042_clk_data *clk_data, struct sg2042_pll_clock pll_clks[], int num_pll_clks) { struct sg2042_pll_clock *pll; struct clk_hw *hw; int i, ret = 0; for (i = 0; i < num_pll_clks; i++) { pll = &pll_clks[i]; /* assign these for ops usage during registration */ pll->base = clk_data->iobase; pll->lock = &sg2042_clk_lock; hw = &pll->hw; ret = devm_clk_hw_register(dev, hw); if (ret) { pr_err("failed to register clock %s\n", pll->hw.init->name); break; } clk_data->onecell_data.hws[pll->id] = hw; } return ret; } static int sg2042_init_clkdata(struct platform_device *pdev, int num_clks, struct sg2042_clk_data **pp_clk_data) { struct sg2042_clk_data *clk_data; clk_data = devm_kzalloc(&pdev->dev, struct_size(clk_data, onecell_data.hws, num_clks), GFP_KERNEL); if (!clk_data) return -ENOMEM; clk_data->iobase = devm_platform_ioremap_resource(pdev, 0); if (WARN_ON(IS_ERR(clk_data->iobase))) return PTR_ERR(clk_data->iobase); clk_data->onecell_data.num = num_clks; *pp_clk_data = clk_data; return 0; } static int sg2042_pll_probe(struct platform_device *pdev) { struct sg2042_clk_data *clk_data = NULL; int num_clks; int ret; num_clks = ARRAY_SIZE(sg2042_pll_clks); ret = sg2042_init_clkdata(pdev, num_clks, &clk_data); if (ret) goto error_out; ret = sg2042_clk_register_plls(&pdev->dev, clk_data, sg2042_pll_clks, num_clks); if (ret) goto error_out; return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_onecell_get, &clk_data->onecell_data); error_out: pr_err("%s failed error number %d\n", __func__, ret); return ret; } static const struct of_device_id sg2042_pll_match[] = { { .compatible = "sophgo,sg2042-pll" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sg2042_pll_match); static struct platform_driver sg2042_pll_driver = { .probe = sg2042_pll_probe, .driver = { .name = "clk-sophgo-sg2042-pll", .of_match_table = sg2042_pll_match, .suppress_bind_attrs = true, }, }; module_platform_driver(sg2042_pll_driver); MODULE_AUTHOR("Chen Wang"); MODULE_DESCRIPTION("Sophgo SG2042 pll clock driver"); MODULE_LICENSE("GPL");
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