From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from hemlock.osuosl.org (smtp2.osuosl.org [140.211.166.133]) by ash.osuosl.org (Postfix) with ESMTP id 9B8ED1C24AE for ; Mon, 7 May 2018 01:22:02 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by hemlock.osuosl.org (Postfix) with ESMTP id 98FD287612 for ; Mon, 7 May 2018 01:22:02 +0000 (UTC) Received: from hemlock.osuosl.org ([127.0.0.1]) by localhost (.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id INQ06VHrvnDZ for ; Mon, 7 May 2018 01:22:01 +0000 (UTC) Received: from mail-pf0-f196.google.com (mail-pf0-f196.google.com [209.85.192.196]) by hemlock.osuosl.org (Postfix) with ESMTPS id A1E8F8765A for ; Mon, 7 May 2018 01:22:01 +0000 (UTC) Received: by mail-pf0-f196.google.com with SMTP id e9so17512485pfi.4 for ; Sun, 06 May 2018 18:22:01 -0700 (PDT) From: James Kelly Subject: [PATCH 12/14] staging: clocking-wizard: Automatically set internal clock rates Date: Mon, 7 May 2018 11:20:38 +1000 Message-Id: <20180507012040.18187-13-jamespeterkelly@gmail.com> In-Reply-To: <20180507012040.18187-1-jamespeterkelly@gmail.com> References: <20180507012040.18187-1-jamespeterkelly@gmail.com> List-Id: Linux Driver Project Developer List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: driverdev-devel-bounces@linuxdriverproject.org Sender: "devel" To: Michal Simek , Greg Kroah-Hartman Cc: driverdev-devel@linuxdriverproject.org Allow CLK_SET_RATE_PARENT to be optionally enabled on one of the output clocks. This will automatically choose the "best" rates for the first divider and PLL multiplier. Best is defined as those first divider and PLL multplier rates that minimise the error in the rate of the output clock that has CLK_SET_RATE_PARENT enabled. The current implementation uses a constrained brute force search for the best parent rate that stops when a rate is within 10ppm of that requested. The output clock for which CLK_SET_RATE_PARENT should be enabled is specified using a new device-tree property named "set-parent-output". This is an optional property and if not present this feature is disabled. If first divider clock is updated before the PLL multiplier clock adjust the PLL multiplier to keep PLL lock. Signed-off-by: James Kelly --- drivers/staging/clocking-wizard/TODO | 1 - .../clocking-wizard/clk-xlnx-clock-wizard.c | 151 ++++++++++++++++++++- drivers/staging/clocking-wizard/dt-binding.txt | 3 + 3 files changed, 149 insertions(+), 6 deletions(-) diff --git a/drivers/staging/clocking-wizard/TODO b/drivers/staging/clocking-wizard/TODO index 50193bdd61e1..bf7435c5b67e 100644 --- a/drivers/staging/clocking-wizard/TODO +++ b/drivers/staging/clocking-wizard/TODO @@ -1,7 +1,6 @@ TODO: - review arithmetic - overflow after multiplication? - - implement CLK_SET_RATE_PARENT to set internal clocks - implement CLK_SET_RATE_PARENT to set input clock - test on 64-bit ARM and Microblaze architectures. - support clk_set_phase diff --git a/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c b/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c index 455ee9887c77..f706c3d6496e 100644 --- a/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c +++ b/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c @@ -315,6 +315,8 @@ static const struct reg_field clk_wzrd_reconfig = REG_FIELD(0x25C, 0, 1); * @flags: hardware specific flags * @cw; pointer to platform device data * @ratio_limit: pointer to divider/multiplier ratio limits + * @min_parent: minimum parent clk rate + * @max_parent: maximum parent clk rate * @int_field: pointer to regmap field for integer part * @frac_field: pointer to regmap field for fractional part * @@ -328,6 +330,8 @@ struct clk_wzrd_clk_data { unsigned long flags; struct clk_wzrd *cw; const struct clk_wzrd_ratio *ratio_limit; + unsigned long min_parent; + unsigned long max_parent; struct regmap_field *int_field; struct regmap_field *frac_field; }; @@ -501,10 +505,77 @@ static inline unsigned long clk_wzrd_round_rate(struct clk_wzrd_clk_data *cwc, return clk_wzrd_calc_rate(parent_rate, ratio, cwc->flags); } +static inline unsigned long clk_wzrd_parent_rate(struct clk_wzrd_clk_data *cwc, + unsigned long rate, + unsigned long ratio) +{ + return clk_wzrd_calc_rate(rate, ratio, cwc->flags ^ + WZRD_FLAG_MULTIPLY); +} + +/* + * Search for the best parent rate + * + * This has the potential to run a long time if our parent clocks also + * search for their best parent rate. + */ +static bool clk_wzrd_best_parent_rate(struct clk_wzrd_clk_data *cwc, + struct clk_rate_request *req) +{ + unsigned long ratio, min_ratio, max_ratio; + unsigned long min_parent_rate = cwc->min_parent; + unsigned long max_parent_rate = cwc->max_parent; + unsigned long best_delta = ULONG_MAX; + unsigned long inc = cwc->flags & WZRD_FLAG_FRAC ? 1 : + BIT(WZRD_FRAC_BITS); + + /* + * Search by testing parent rates that corresponds to all possible + * ratios that are within our parent rate constraints. + */ + min_ratio = clk_wzrd_limit_calc_ratio(cwc, min_parent_rate, req->rate); + max_ratio = clk_wzrd_limit_calc_ratio(cwc, max_parent_rate, req->rate); + + if (cwc->flags & WZRD_FLAG_MULTIPLY) + swap(min_ratio, max_ratio); + + for (ratio = max_ratio; ratio >= min_ratio; ratio -= inc) { + unsigned long parent_rate, rate, delta; + + parent_rate = clk_wzrd_parent_rate(cwc, req->rate, ratio); + + /* + * Figure out what rate we will actually end up with. This + * may take a while if our parent can set their parent rate. + */ + parent_rate = clk_hw_round_rate(req->best_parent_hw, + parent_rate); + if (parent_rate > max_parent_rate || + parent_rate < min_parent_rate || parent_rate == 0) + continue; + + rate = clk_wzrd_round_rate(cwc, parent_rate, req->rate); + if (rate < req->min_rate || rate > req->max_rate) + continue; + + delta = rate > req->rate ? rate - req->rate : req->rate - rate; + if (delta < best_delta) { + req->best_parent_rate = parent_rate; + best_delta = delta; + + if ((unsigned long long)best_delta * 100000 < rate) + return false; + } + } + + return best_delta == ULONG_MAX; +} + static int clk_wzrd_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { struct clk_wzrd_clk_data *cwc = to_clk_wzrd_clk_data(hw); + unsigned long flags = clk_hw_get_flags(hw); if (cwc->flags & WZRD_FLAG_MULTIPLY && req->best_parent_rate == 0) return -EINVAL; @@ -512,6 +583,9 @@ static int clk_wzrd_determine_rate(struct clk_hw *hw, if (!(cwc->flags & WZRD_FLAG_MULTIPLY) && req->rate == 0) return -EINVAL; + if (flags & CLK_SET_RATE_PARENT && clk_wzrd_best_parent_rate(cwc, req)) + return -EINVAL; + req->rate = clk_wzrd_round_rate(cwc, req->best_parent_rate, req->rate); if (req->rate < req->min_rate || req->rate > req->max_rate) @@ -588,11 +662,40 @@ static inline int clk_wzrd_reconfigure(struct clk_wzrd *cw) return 0; } +/* + * Check if the PLL multiplier ratio needs adjustment to ensure that the VCO + * frequency remains within the valid range. This is necessary to ensure the + * PLL will still lock when the first divider ratio is updated before the PLL + * multiplier ratio is updated as is the want of the CCF when using + * CLK_SET_RATE_PARENT. + * + * Returns true if the PLL multiplier ratio had to be adjusted. + */ +static bool clk_wzrd_check_pll_rate(struct clk_wzrd *cw, + unsigned long input_rate) +{ + unsigned long div_rate, pll_rate, clamped_rate; + struct clk_wzrd_clk_data *out_cwc = &cw->clkout_data[0]; + + div_rate = clk_wzrd_recalc_rate(&cw->div_data.hw, input_rate); + pll_rate = clk_wzrd_recalc_rate(&cw->pll_data.hw, div_rate); + clamped_rate = clamp_val(pll_rate, out_cwc->min_parent, + out_cwc->max_parent); + + if (pll_rate == clamped_rate) + return false; + + clk_wzrd_set_ratio(&cw->pll_data, div_rate, clamped_rate); + + return true; +} + static int clk_wzrd_set_rate(struct clk_hw *hw, unsigned long req_rate, unsigned long parent_rate) { struct clk_wzrd_clk_data *cwc = to_clk_wzrd_clk_data(hw); struct clk_wzrd *cw = cwc->cw; + bool reconfigure = false; if (cwc->flags & WZRD_FLAG_MULTIPLY && parent_rate == 0) return -EINVAL; @@ -605,7 +708,12 @@ static int clk_wzrd_set_rate(struct clk_hw *hw, unsigned long req_rate, return -EIO; } - if (!clk_wzrd_set_ratio(cwc, parent_rate, req_rate)) + reconfigure = clk_wzrd_set_ratio(cwc, parent_rate, req_rate); + + if (cwc == &cw->div_data) + reconfigure |= clk_wzrd_check_pll_rate(cw, parent_rate); + + if (!reconfigure) return 0; return clk_wzrd_reconfigure(cw); @@ -661,6 +769,16 @@ static int clk_wzrd_debug_init(struct clk_hw *hw, struct dentry *dentry) if (IS_ERR(d)) return PTR_ERR(d); + d = debugfs_create_u32("clk_parent_rate_min", 0444, dentry, + (u32 *)&cwc->min_parent); + if (IS_ERR(d)) + return PTR_ERR(d); + + d = debugfs_create_u32("clk_parent_rate_max", 0444, dentry, + (u32 *)&cwc->max_parent); + if (IS_ERR(d)) + return PTR_ERR(d); + d = debugfs_create_file_unsafe("clk_hw_flags", 0444, dentry, cwc, &clk_wzrd_flags_fops); if (IS_ERR(d)) @@ -831,7 +949,7 @@ static SIMPLE_DEV_PM_OPS(clk_wzrd_dev_pm_ops, clk_wzrd_suspend, static int clk_wzrd_get_device_tree_data(struct device *dev) { - int num_outputs, ret, speed_grade; + int num_outputs, ret, speed_grade, i; enum clk_wzrd_family family; enum clk_wzrd_type type; struct clk_wzrd *cw; @@ -897,6 +1015,16 @@ static int clk_wzrd_get_device_tree_data(struct device *dev) cw->speed_grade = speed_grade - 1; } + cw->div_data.min_parent = cw->chip->min[WZRD_RATE_FIN]; + cw->div_data.max_parent = cw->chip->max[cw->speed_grade][WZRD_RATE_FIN]; + cw->pll_data.min_parent = cw->chip->min[WZRD_RATE_PFD]; + cw->pll_data.max_parent = cw->chip->max[cw->speed_grade][WZRD_RATE_PFD]; + for (i = 0; i < num_outputs; i++) { + cw->clkout_data[i].min_parent = cw->chip->min[WZRD_RATE_VCO]; + cw->clkout_data[i].max_parent = + cw->chip->max[cw->speed_grade][WZRD_RATE_VCO]; + } + cw->clk_in1 = devm_clk_get(dev, WZRD_CLKNAME_IN1); if (IS_ERR(cw->clk_in1)) { if (cw->clk_in1 != ERR_PTR(-EPROBE_DEFER)) @@ -963,11 +1091,22 @@ static int clk_wzrd_regmap_alloc(struct device *dev) static int clk_wzrd_register_ccf(struct device *dev) { int i, ret; + unsigned int set_output; const char *clk_div_name; const char *clk_pll_name; + unsigned long pll_flags = 0, out_flags = 0; struct clk_wzrd *cw = dev_get_drvdata(dev); + ret = of_property_read_u32(dev->of_node, "set-parent-output", + &set_output); + if (!ret) { + if (set_output >= cw->clk_data.clk_num) + dev_warn(dev, "set-parent-output invalid\n"); + else + pll_flags = CLK_SET_RATE_PARENT; + } + clk_div_name = kasprintf(GFP_KERNEL, "%s_div", dev_name(dev)); if (!clk_div_name) return -ENOMEM; @@ -980,7 +1119,8 @@ static int clk_wzrd_register_ccf(struct device *dev) ret = -ENOMEM; goto err_free_div_name; } - ret = clk_wzrd_register_clk(dev, clk_pll_name, WZRD_CLK_PLL, 0, 0); + ret = clk_wzrd_register_clk(dev, clk_pll_name, WZRD_CLK_PLL, 0, + pll_flags); if (ret) goto err_free_pll_name; @@ -995,8 +1135,9 @@ static int clk_wzrd_register_ccf(struct device *dev) goto err_free_pll_name; } - ret = clk_wzrd_register_clk(dev, clkout_name, WZRD_CLK_OUT, - i, 0); + out_flags = (i == set_output) ? pll_flags : 0; + ret = clk_wzrd_register_clk(dev, clkout_name, WZRD_CLK_OUT, i, + out_flags); if (ret) goto err_free_pll_name; diff --git a/drivers/staging/clocking-wizard/dt-binding.txt b/drivers/staging/clocking-wizard/dt-binding.txt index 6e7859a6e751..37133a3f2ee9 100644 --- a/drivers/staging/clocking-wizard/dt-binding.txt +++ b/drivers/staging/clocking-wizard/dt-binding.txt @@ -24,6 +24,8 @@ Optional properties: - xlnx,primitive: Clocking Wizard primitive 0 - MMCM (default) 1 - PLL + - set-parent-output: Set rate on this output can set parent rates + Valid values are 0..n-1, where n is number of clock outputs. Example: clock-generator@40040000 { @@ -37,4 +39,5 @@ Example: "clk_out6", "clk_out7"; xlnx,family = <0>; xlnx,primitive = <0>; + set-parent-output <0>; }; -- 2.15.1 (Apple Git-101) _______________________________________________ devel mailing list devel@linuxdriverproject.org http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel