[PATCH 12/14] staging: clocking-wizard: Automatically set internal clock rates

James Kelly jamespeterkelly at gmail.com
Mon May 7 01:20:38 UTC 2018


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 <jamespeterkelly at gmail.com>
---
 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 at 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)



More information about the devel mailing list