[PATCH 11/14] staging: clocking-wizard: Support clk_set_rate

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Provide initial support for CCF clk_set_rate API on all clock components.

Clock consumers that want to set the first divider or PLL clock will need
to use clk_get_parent on one of the output clocks as there is no support
for CLK_SET_RATE_PARENT yet.

Care must be taken when setting the first divider clock to ensure that
the PLL clock rate will remain within a valid range for the VCO, as it
is impossible to subsequently update any clock if the PLL does not lock.
A subsequent patch will address this issue.

Signed-off-by: James Kelly <jamespeterkelly@xxxxxxxxx>
---
 drivers/staging/clocking-wizard/TODO               |   4 +-
 .../clocking-wizard/clk-xlnx-clock-wizard.c        | 115 +++++++++++++++++++++
 2 files changed, 117 insertions(+), 2 deletions(-)

diff --git a/drivers/staging/clocking-wizard/TODO b/drivers/staging/clocking-wizard/TODO
index 53c9941fcc35..50193bdd61e1 100644
--- a/drivers/staging/clocking-wizard/TODO
+++ b/drivers/staging/clocking-wizard/TODO
@@ -1,8 +1,8 @@
 TODO:
-	- support for set_rate() operations (may benefit from Stephen Boyd's
-	  refactoring of the clk primitives: https://lkml.org/lkml/2014/9/5/766)
 	- 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 8828dac6faaf..455ee9887c77 100644
--- a/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c
+++ b/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c
@@ -76,6 +76,7 @@
 #define KHz			1000UL
 #define MHz			1000000UL
 #define WZRD_ACLK_MAX_FREQ	(250 * MHz)
+#define WZRD_PLL_LOCK_TIMEOUT	1000		// usec
 #define WZRD_FRAC_BITS		3
 #define WZRD_FRAC_MASK		(BIT(WZRD_FRAC_BITS) - 1)
 #define WZRD_FRAC_SCALE		(1000 >> WZRD_FRAC_BITS)
@@ -85,6 +86,8 @@
 #define WZRD_FLAG_FRAC		BIT(1)
 #define WZRD_FLAG_ADJUST_MIN	BIT(2)
 
+struct clk_wzrd;
+
 /*
  * Clock rate constraints extracted from Xilinx data sheets listed below.
  * The minimum rates depend on family and clock type and the maximum rates
@@ -310,6 +313,7 @@ static const struct reg_field clk_wzrd_reconfig     = REG_FIELD(0x25C, 0,  1);
  *
  * @hw:			handle between common and hardware-specific interfaces
  * @flags:		hardware specific flags
+ * @cw;			pointer to platform device data
  * @ratio_limit:	pointer to divider/multiplier ratio limits
  * @int_field:		pointer to regmap field for integer part
  * @frac_field:		pointer to regmap field for fractional part
@@ -322,6 +326,7 @@ static const struct reg_field clk_wzrd_reconfig     = REG_FIELD(0x25C, 0,  1);
 struct clk_wzrd_clk_data {
 	struct clk_hw			hw;
 	unsigned long			flags;
+	struct clk_wzrd			*cw;
 	const struct clk_wzrd_ratio	*ratio_limit;
 	struct regmap_field		*int_field;
 	struct regmap_field		*frac_field;
@@ -344,6 +349,9 @@ struct clk_wzrd_clk_data {
  * @div:		Divider internal clock provider data
  * @pll:		Phase locked loop internal clock provider data
  * @chip:		Chip data including rate constraints
+ * @pll_locked:		Phase locked loop locked status regmap field
+ * @reconfig:		Reconfiguration regmap field
+ * @dev:		Handle to device
  * @clkout_data:	Array of output clock provider data
  */
 struct clk_wzrd {
@@ -358,6 +366,9 @@ struct clk_wzrd {
 	struct clk_wzrd_clk_data	div_data;
 	struct clk_wzrd_clk_data	pll_data;
 	const struct clk_wzrd_chip	*chip;
+	struct regmap_field		*pll_locked;
+	struct regmap_field		*reconfig;
+	struct device			*dev;
 	struct clk_wzrd_clk_data	clkout_data[0];
 };
 #define to_clk_wzrd(_nb) container_of(_nb, struct clk_wzrd, nb)
@@ -509,6 +520,97 @@ static int clk_wzrd_determine_rate(struct clk_hw *hw,
 	return 0;
 }
 
+static bool clk_wzrd_set_ratio(struct clk_wzrd_clk_data *cwc,
+			       unsigned long parent_rate,
+			       unsigned long rate)
+{
+	unsigned long old_ratio = clk_wzrd_get_ratio(cwc);
+	unsigned long ratio = clk_wzrd_limit_calc_ratio(cwc, parent_rate, rate);
+
+	if (ratio == old_ratio)
+		return false;
+
+	regmap_field_write(cwc->int_field, ratio >> WZRD_FRAC_BITS);
+	if (cwc->flags & WZRD_FLAG_FRAC)
+		regmap_field_write(cwc->frac_field, (ratio & WZRD_FRAC_MASK) *
+				   WZRD_FRAC_SCALE);
+
+	return true;
+}
+
+/*
+ * Wait for PLL to become locked
+ *
+ * The main reason the PLL will not be locked is because the parent clock
+ * changed rate.  It is entirely possible that the PLL will never lock and
+ * the only way to fix this is to change the parent clock rate, as dynamic
+ * reconfiguration of the first divider and PLL multiplier is not possible
+ * when the PLL is not locked!
+ *
+ * Returns true if PLL is already locked or obtains lock within the timeout.
+ */
+static inline bool clk_wzrd_wait_pll_lock(struct clk_wzrd *cw)
+{
+	u32 status;
+
+	return !regmap_field_read_poll_timeout(cw->pll_locked, status,
+					       (status == 1), 100,
+					       WZRD_PLL_LOCK_TIMEOUT);
+}
+
+/*
+ * Wait for dynamic reconfiguration of Clocking Wizard IP to complete
+ *
+ * According to the Xilinx documentation the reconfiguration is
+ * not completed until the PLL is locked so we use the same timeout
+ * logic as we used for clk_wzrd_wait_pll_lock.
+ *
+ * Returns true if reconfiguration succeeded and the PLL locked.
+ */
+static inline bool clk_wzrd_wait_reconfigure(struct clk_wzrd *cw)
+{
+	u32 status;
+
+	return !regmap_field_read_poll_timeout(cw->reconfig, status,
+					       ((status & 1) == 0),
+					       100, WZRD_PLL_LOCK_TIMEOUT);
+}
+
+static inline int clk_wzrd_reconfigure(struct clk_wzrd *cw)
+{
+	regmap_field_write(cw->reconfig, 3);
+
+	if (!clk_wzrd_wait_reconfigure(cw)) {
+		dev_err(cw->dev, "Reconfiguration failed\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+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;
+
+	if (cwc->flags & WZRD_FLAG_MULTIPLY && parent_rate == 0)
+		return -EINVAL;
+
+	if (!(cwc->flags & WZRD_FLAG_MULTIPLY) && req_rate == 0)
+		return -EINVAL;
+
+	if (!clk_wzrd_wait_pll_lock(cw)) {
+		dev_err(cw->dev, "Set rate not possible - PLL not locked\n");
+		return -EIO;
+	}
+
+	if (!clk_wzrd_set_ratio(cwc, parent_rate, req_rate))
+		return 0;
+
+	return clk_wzrd_reconfigure(cw);
+}
+
 #ifdef CONFIG_DEBUG_FS
 
 static int clk_wzrd_flags_show(struct seq_file *s, void *data)
@@ -576,6 +678,7 @@ static int clk_wzrd_debug_init(struct clk_hw *hw, struct dentry *dentry)
 static const struct clk_ops clk_wzrd_clk_ops = {
 	.recalc_rate = clk_wzrd_recalc_rate,
 	.determine_rate = clk_wzrd_determine_rate,
+	.set_rate = clk_wzrd_set_rate,
 #ifdef CONFIG_DEBUG_FS
 	.debug_init = clk_wzrd_debug_init,
 #endif
@@ -641,6 +744,7 @@ static int clk_wzrd_register_clk(struct device *dev, const char *name,
 	init.parent_names = &parent_name;
 	init.num_parents = 1;
 	cwc->hw.init = &init;
+	cwc->cw = cw;
 	cwc->ratio_limit = &ratio_constraints[cw->chip->cell][type];
 	cwc->int_field = devm_regmap_field_alloc(dev, cw->regmap,
 						 *int_reg_field);
@@ -770,6 +874,7 @@ static int clk_wzrd_get_device_tree_data(struct device *dev)
 	if (!cw)
 		return -ENOMEM;
 	dev_set_drvdata(dev, cw);
+	cw->dev = dev;
 	cw->chip = &chip_constraints[family][type];
 	cw->clk_data.clk_num = num_outputs;
 
@@ -842,6 +947,16 @@ static int clk_wzrd_regmap_alloc(struct device *dev)
 	if (IS_ERR(cw->regmap))
 		return PTR_ERR(cw->regmap);
 
+	cw->pll_locked = devm_regmap_field_alloc(dev, cw->regmap,
+						 clk_wzrd_status_locked);
+	if (IS_ERR(cw->pll_locked))
+		return PTR_ERR(cw->pll_locked);
+
+	cw->reconfig = devm_regmap_field_alloc(dev, cw->regmap,
+					       clk_wzrd_reconfig);
+	if (IS_ERR(cw->reconfig))
+		return PTR_ERR(cw->reconfig);
+
 	return 0;
 }
 
-- 
2.15.1 (Apple Git-101)

_______________________________________________
devel mailing list
devel@xxxxxxxxxxxxxxxxxxxxxx
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel



[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux