The patch adds support for dynamic reconfiguration of clock output rate.
Output clocks are registered as dividers and set rate callback function
is used for dynamic reconfiguration.

Signed-off-by: Shubhrajyoti Datta <shubhrajyoti.da...@xilinx.com>
Co-developed-by: Chirag Parekh <chirag.par...@xilinx.com>
---
v6:
Remove the typecast.
use min for capping frequency.
use polled timeout

v7:
Use devm functions
Add the spinlock

v10:
Add a codeveloped by tag.

 .../clocking-wizard/clk-xlnx-clock-wizard.c        | 178 ++++++++++++++++++++-
 1 file changed, 173 insertions(+), 5 deletions(-)

diff --git a/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c 
b/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c
index e08fc2f..b1bfdb86 100644
--- a/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c
+++ b/drivers/staging/clocking-wizard/clk-xlnx-clock-wizard.c
@@ -15,6 +15,7 @@
 #include <linux/of.h>
 #include <linux/module.h>
 #include <linux/err.h>
+#include <linux/iopoll.h>
 
 #define WZRD_NUM_OUTPUTS       7
 #define WZRD_ACLK_MAX_FREQ     250000000UL
@@ -29,8 +30,24 @@
 #define WZRD_DIVCLK_DIVIDE_SHIFT       0
 #define WZRD_DIVCLK_DIVIDE_MASK                (0xff << 
WZRD_DIVCLK_DIVIDE_SHIFT)
 #define WZRD_CLKOUT_DIVIDE_SHIFT       0
+#define WZRD_CLKOUT_DIVIDE_WIDTH       8
 #define WZRD_CLKOUT_DIVIDE_MASK                (0xff << 
WZRD_DIVCLK_DIVIDE_SHIFT)
 
+#define WZRD_DR_MAX_INT_DIV_VALUE      255
+#define WZRD_DR_STATUS_REG_OFFSET      0x04
+#define WZRD_DR_LOCK_BIT_MASK          0x00000001
+#define WZRD_DR_INIT_REG_OFFSET                0x25C
+#define WZRD_DR_DIV_TO_PHASE_OFFSET    4
+#define WZRD_DR_BEGIN_DYNA_RECONF      0x03
+
+#define WZRD_USEC_POLL         10
+#define WZRD_TIMEOUT_POLL              1000
+/* Get the mask from width */
+#define div_mask(width)                        ((1 << (width)) - 1)
+
+/* Extract divider instance from clock hardware instance */
+#define to_clk_wzrd_divider(_hw) container_of(_hw, struct clk_wzrd_divider, hw)
+
 enum clk_wzrd_int_clks {
        wzrd_clk_mul,
        wzrd_clk_mul_div,
@@ -62,6 +79,29 @@ struct clk_wzrd {
        bool suspended;
 };
 
+/**
+ * struct clk_wzrd_divider - clock divider specific to clk_wzrd
+ *
+ * @hw:                handle between common and hardware-specific interfaces
+ * @base:      base address of register containing the divider
+ * @offset:    offset address of register containing the divider
+ * @shift:     shift to the divider bit field
+ * @width:     width of the divider bit field
+ * @flags:     clk_wzrd divider flags
+ * @table:     array of value/divider pairs, last entry should have div = 0
+ * @lock:      register lock
+ */
+struct clk_wzrd_divider {
+       struct clk_hw hw;
+       void __iomem *base;
+       u16 offset;
+       u8 shift;
+       u8 width;
+       u8 flags;
+       const struct clk_div_table *table;
+       spinlock_t *lock;  /* divider lock */
+};
+
 #define to_clk_wzrd(_nb) container_of(_nb, struct clk_wzrd, nb)
 
 /* maximum frequencies for input/output clocks per speed grade */
@@ -71,6 +111,131 @@ static const unsigned long clk_wzrd_max_freq[] = {
        1066000000UL
 };
 
+/* spin lock variable for clk_wzrd */
+static DEFINE_SPINLOCK(clkwzrd_lock);
+
+static unsigned long clk_wzrd_recalc_rate(struct clk_hw *hw,
+                                         unsigned long parent_rate)
+{
+       struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
+       void __iomem *div_addr = divider->base + divider->offset;
+       unsigned int val;
+
+       val = readl(div_addr) >> divider->shift;
+       val &= div_mask(divider->width);
+
+       return divider_recalc_rate(hw, parent_rate, val, divider->table,
+                       divider->flags, divider->width);
+}
+
+static int clk_wzrd_dynamic_reconfig(struct clk_hw *hw, unsigned long rate,
+                                    unsigned long parent_rate)
+{
+       int err;
+       u32 value;
+       unsigned long flags = 0;
+       struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
+       void __iomem *div_addr = divider->base + divider->offset;
+
+       if (divider->lock)
+               spin_lock_irqsave(divider->lock, flags);
+       else
+               __acquire(divider->lock);
+
+       value = DIV_ROUND_CLOSEST(parent_rate, rate);
+
+       /* Cap the value to max */
+       min_t(u32, value, WZRD_DR_MAX_INT_DIV_VALUE);
+
+       /* Set divisor and clear phase offset */
+       writel(value, div_addr);
+       writel(0x00, div_addr + WZRD_DR_DIV_TO_PHASE_OFFSET);
+
+       /* Check status register */
+       err = readl_poll_timeout(divider->base + WZRD_DR_STATUS_REG_OFFSET,
+                                value, value & WZRD_DR_LOCK_BIT_MASK,
+                                WZRD_USEC_POLL, WZRD_TIMEOUT_POLL);
+       if (err)
+               goto err_reconfig;
+
+       /* Initiate reconfiguration */
+       writel(WZRD_DR_BEGIN_DYNA_RECONF,
+              divider->base + WZRD_DR_INIT_REG_OFFSET);
+
+       /* Check status register */
+       err = readl_poll_timeout(divider->base + WZRD_DR_STATUS_REG_OFFSET,
+                                value, value & WZRD_DR_LOCK_BIT_MASK,
+                                WZRD_USEC_POLL, WZRD_TIMEOUT_POLL);
+err_reconfig:
+       if (divider->lock)
+               spin_unlock_irqrestore(divider->lock, flags);
+       else
+               __release(divider->lock);
+       return err;
+}
+
+static long clk_wzrd_round_rate(struct clk_hw *hw, unsigned long rate,
+                               unsigned long *prate)
+{
+       u8 div;
+
+       /*
+        * since we don't change parent rate we just round rate to closest
+        * achievable
+        */
+       div = DIV_ROUND_CLOSEST(*prate, rate);
+
+       return *prate / div;
+}
+
+static const struct clk_ops clk_wzrd_clk_divider_ops = {
+       .round_rate = clk_wzrd_round_rate,
+       .set_rate = clk_wzrd_dynamic_reconfig,
+       .recalc_rate = clk_wzrd_recalc_rate,
+};
+
+static struct clk *clk_wzrd_register_divider(struct device *dev,
+                                            const char *name,
+                                            const char *parent_name,
+                                            unsigned long flags,
+                                            void __iomem *base, u16 offset,
+                                            u8 shift, u8 width,
+                                            u8 clk_divider_flags,
+                                            const struct clk_div_table *table,
+                                            spinlock_t *lock)
+{
+       struct clk_wzrd_divider *div;
+       struct clk_hw *hw;
+       struct clk_init_data init;
+       int ret;
+
+       div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
+       if (!div)
+               return ERR_PTR(-ENOMEM);
+
+       init.name = name;
+       init.ops = &clk_wzrd_clk_divider_ops;
+       init.flags = flags;
+       init.parent_names =  &parent_name;
+       init.num_parents =  1;
+
+       div->base = base;
+       div->offset = offset;
+       div->shift = shift;
+       div->width = width;
+       div->flags = clk_divider_flags;
+       div->lock = lock;
+       div->hw.init = &init;
+       div->table = table;
+
+       hw = &div->hw;
+       ret = devm_clk_hw_register(dev, hw);
+       if (ret)
+               hw = ERR_PTR(ret);
+
+       return hw->clk;
+}
+
 static int clk_wzrd_clk_notifier(struct notifier_block *nb, unsigned long 
event,
                                 void *data)
 {
@@ -251,11 +416,14 @@ static int clk_wzrd_probe(struct platform_device *pdev)
                        ret = -EINVAL;
                        goto err_rm_int_clks;
                }
-               reg = readl(clk_wzrd->base + WZRD_CLK_CFG_REG(2) + i * 12);
-               reg &= WZRD_CLKOUT_DIVIDE_MASK;
-               reg >>= WZRD_CLKOUT_DIVIDE_SHIFT;
-               clk_wzrd->clkout[i] = clk_register_fixed_factor
-                       (&pdev->dev, clkout_name, clk_name, 0, 1, reg);
+               clk_wzrd->clkout[i] = clk_wzrd_register_divider(&pdev->dev,
+                                                               clkout_name,
+                               clk_name, 0,
+                               clk_wzrd->base, (WZRD_CLK_CFG_REG(2) + i * 12),
+                               WZRD_CLKOUT_DIVIDE_SHIFT,
+                               WZRD_CLKOUT_DIVIDE_WIDTH,
+                               CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO,
+                               NULL, &clkwzrd_lock);
                if (IS_ERR(clk_wzrd->clkout[i])) {
                        int j;
 
-- 
2.1.1

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

Reply via email to