Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Android/prebuilts/prebuilts/abi-dumps/ndk/32/arm/libz/   (Android Betriebssystem Version 17©)  Datei vom 26.5.2026 mit Größe 29 kB image not shown  

Quelle  clk-xlnx-clock-wizard.c  Sprache: unbekannt

 
// SPDX-License-Identifier: GPL-2.0
/*
 * Xilinx 'Clocking Wizard' driver
 *
 *  Copyright (C) 2013 - 2021 Xilinx
 *
 *  Sören Brinkmann <soren.brinkmann@xilinx.com>
 *
 */


#include <linux/bitfield.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/err.h>
#include <linux/iopoll.h>

#define WZRD_NUM_OUTPUTS 7
#define WZRD_ACLK_MAX_FREQ 250000000UL

#define WZRD_CLK_CFG_REG(v, n) (0x200 + 0x130 * (v) + 4 * (n))

#define WZRD_CLKOUT0_FRAC_EN BIT(18)
#define WZRD_CLKFBOUT_1  0
#define WZRD_CLKFBOUT_2  1
#define WZRD_CLKOUT0_1  2
#define WZRD_CLKOUT0_2  3
#define WZRD_DESKEW_2  20
#define WZRD_DIVCLK  21
#define WZRD_CLKFBOUT_4  51
#define WZRD_CLKFBOUT_3  48
#define WZRD_DUTY_CYCLE  2
#define WZRD_O_DIV  4

#define WZRD_CLKFBOUT_FRAC_EN BIT(1)
#define WZRD_CLKFBOUT_PREDIV2 (BIT(11) | BIT(12) | BIT(9))
#define WZRD_MULT_PREDIV2 (BIT(10) | BIT(9) | BIT(12))
#define WZRD_CLKFBOUT_EDGE BIT(8)
#define WZRD_P5EN  BIT(13)
#define WZRD_P5EN_SHIFT  13
#define WZRD_P5FEDGE  BIT(15)
#define WZRD_DIVCLK_EDGE BIT(10)
#define WZRD_P5FEDGE_SHIFT 15
#define WZRD_CLKOUT0_PREDIV2 BIT(11)
#define WZRD_EDGE_SHIFT  8

#define WZRD_CLKFBOUT_MULT_SHIFT 8
#define WZRD_CLKFBOUT_MULT_MASK  (0xff << WZRD_CLKFBOUT_MULT_SHIFT)
#define WZRD_CLKFBOUT_MULT_FRAC_MASK GENMASK(25, 16)
#define WZRD_CLKFBOUT_O_MASK  GENMASK(7, 0)
#define WZRD_CLKFBOUT_L_SHIFT 0
#define WZRD_CLKFBOUT_H_SHIFT 8
#define WZRD_CLKFBOUT_L_MASK GENMASK(7, 0)
#define WZRD_CLKFBOUT_H_MASK GENMASK(15, 8)
#define WZRD_CLKFBOUT_FRAC_SHIFT 16
#define WZRD_CLKFBOUT_FRAC_MASK  (0x3ff << WZRD_CLKFBOUT_FRAC_SHIFT)
#define WZRD_VERSAL_FRAC_MASK  GENMASK(5, 0)
#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_CLKOUT_FRAC_SHIFT  8
#define WZRD_CLKOUT_FRAC_MASK  0x3ff
#define WZRD_CLKOUT0_FRAC_MASK  GENMASK(17, 8)

#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_INIT_VERSAL_OFFSET 0x14
#define WZRD_DR_DIV_TO_PHASE_OFFSET 4
#define WZRD_DR_BEGIN_DYNA_RECONF 0x03
#define WZRD_DR_BEGIN_DYNA_RECONF_5_2 0x07
#define WZRD_DR_BEGIN_DYNA_RECONF1_5_2 0x02

#define WZRD_USEC_POLL  10
#define WZRD_TIMEOUT_POLL  1000
#define WZRD_FRAC_GRADIENT  64
#define PREDIV2_MULT   2

/* Divider limits, from UG572 Table 3-4 for Ultrascale+ */
#define DIV_O    0x01
#define DIV_ALL    0x03

#define WZRD_M_MIN   2ULL
#define WZRD_M_MAX   128ULL
#define WZRD_D_MIN   1ULL
#define WZRD_D_MAX   106ULL
#define WZRD_VCO_MIN   800000000ULL
#define WZRD_VCO_MAX   1600000000ULL
#define WZRD_O_MIN   2ULL
#define WZRD_O_MAX   128ULL
#define VER_WZRD_M_MIN   4
#define VER_WZRD_M_MAX   432
#define VER_WZRD_D_MIN   1
#define VER_WZRD_D_MAX   123
#define VER_WZRD_VCO_MIN  2160000000ULL
#define VER_WZRD_VCO_MAX  4320000000ULL
#define VER_WZRD_O_MIN   2
#define VER_WZRD_O_MAX   511
#define WZRD_MIN_ERR   20000
#define WZRD_FRAC_POINTS  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,
 wzrd_clk_mul_frac,
 wzrd_clk_int_max
};

/**
 * struct clk_wzrd - Clock wizard private data structure
 *
 * @nb:   Notifier block
 * @base:  Memory base
 * @clk_in1:  Handle to input clock 'clk_in1'
 * @axi_clk:  Handle to input clock 's_axi_aclk'
 * @clks_internal: Internal clocks
 * @speed_grade: Speed grade of the device
 * @suspended:  Flag indicating power state of the device
 * @clk_data:  Output clock data
 */

struct clk_wzrd {
 struct notifier_block nb;
 void __iomem *base;
 struct clk *clk_in1;
 struct clk *axi_clk;
 struct clk_hw *clks_internal[wzrd_clk_int_max];
 unsigned int speed_grade;
 bool suspended;
 struct clk_hw_onecell_data clk_data;
};

/**
 * 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
 * @m: value of the multiplier
 * @m_frac: fractional value of the multiplier
 * @d: value of the common divider
 * @o: value of the leaf divider
 * @o_frac: value of the fractional leaf divider
 * @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;
 u32 m;
 u32 m_frac;
 u32 d;
 u32 o;
 u32 o_frac;
 spinlock_t *lock;  /* divider lock */
};

struct versal_clk_data {
 bool is_versal;
};

#define to_clk_wzrd(_nb) container_of(_nb, struct clk_wzrd, nb)

/* maximum frequencies for input/output clocks per speed grade */
static const unsigned long clk_wzrd_max_freq[] = {
 800000000UL,
 933000000UL,
 1066000000UL
};

/* spin lock variable for clk_wzrd */
static DEFINE_SPINLOCK(clkwzrd_lock);

static unsigned long clk_wzrd_recalc_rate_ver(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;
 u32 div, p5en, edge, prediv2, all;
 unsigned int vall, valh;

 edge = !!(readl(div_addr) & WZRD_CLKFBOUT_EDGE);
 p5en = !!(readl(div_addr) & WZRD_P5EN);
 prediv2 = !!(readl(div_addr) & WZRD_CLKOUT0_PREDIV2);
 vall = readl(div_addr + 4) & WZRD_CLKFBOUT_L_MASK;
 valh = readl(div_addr + 4) >> WZRD_CLKFBOUT_H_SHIFT;
 all = valh + vall + edge;
 if (!all)
  all = 1;

 if (prediv2)
  div = 2 * all + prediv2 * p5en;
 else
  div = all;

 return DIV_ROUND_UP_ULL((u64)parent_rate, div);
}

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_ver_dynamic_reconfig(struct clk_hw *hw, unsigned long rate,
      unsigned long parent_rate)
{
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 void __iomem *div_addr = divider->base + divider->offset;
 u32 value, regh, edged, p5en, p5fedge, regval, regval1;
 unsigned long flags;
 int err;

 spin_lock_irqsave(divider->lock, flags);

 value = DIV_ROUND_CLOSEST(parent_rate, rate);

 regh = (value / 4);
 regval1 = readl(div_addr);
 regval1 |= WZRD_CLKFBOUT_PREDIV2;
 regval1 = regval1 & ~(WZRD_CLKFBOUT_EDGE | WZRD_P5EN | WZRD_P5FEDGE);
 if (value % 4 > 1) {
  edged = 1;
  regval1 |= (edged << WZRD_EDGE_SHIFT);
 }
 p5fedge = value % 2;
 p5en = value % 2;
 regval1 = regval1 | p5en << WZRD_P5EN_SHIFT | p5fedge << WZRD_P5FEDGE_SHIFT;
 writel(regval1, div_addr);

 regval = regh | regh << WZRD_CLKFBOUT_H_SHIFT;
 writel(regval, div_addr + 4);
 /* Check status register */
 err = readl_poll_timeout_atomic(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_VERSAL_OFFSET);

 /* Check status register */
 err = readl_poll_timeout_atomic(divider->base + WZRD_DR_STATUS_REG_OFFSET,
     value, value & WZRD_DR_LOCK_BIT_MASK,
     WZRD_USEC_POLL, WZRD_TIMEOUT_POLL);
err_reconfig:
 spin_unlock_irqrestore(divider->lock, flags);
 return err;
}

static int clk_wzrd_dynamic_reconfig(struct clk_hw *hw, unsigned long rate,
         unsigned long parent_rate)
{
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 void __iomem *div_addr = divider->base + divider->offset;
 unsigned long flags;
 u32 value;
 int err;

 spin_lock_irqsave(divider->lock, flags);

 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_atomic(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_5_2,
        divider->base + WZRD_DR_INIT_REG_OFFSET);
 writel(WZRD_DR_BEGIN_DYNA_RECONF1_5_2,
        divider->base + WZRD_DR_INIT_REG_OFFSET);

 /* Check status register */
 err = readl_poll_timeout_atomic(divider->base + WZRD_DR_STATUS_REG_OFFSET,
     value, value & WZRD_DR_LOCK_BIT_MASK,
     WZRD_USEC_POLL, WZRD_TIMEOUT_POLL);
err_reconfig:
 spin_unlock_irqrestore(divider->lock, flags);
 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 int clk_wzrd_get_divisors_ver(struct clk_hw *hw, unsigned long rate,
         unsigned long parent_rate)
{
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 u64 vco_freq, freq, diff, vcomin, vcomax;
 u32 m, d, o;
 u32 mmin, mmax, dmin, dmax, omin, omax;

 mmin = VER_WZRD_M_MIN;
 mmax = VER_WZRD_M_MAX;
 dmin = VER_WZRD_D_MIN;
 dmax = VER_WZRD_D_MAX;
 omin = VER_WZRD_O_MIN;
 omax = VER_WZRD_O_MAX;
 vcomin = VER_WZRD_VCO_MIN;
 vcomax = VER_WZRD_VCO_MAX;

 for (m = mmin; m <= mmax; m++) {
  for (d = dmin; d <= dmax; d++) {
   vco_freq = DIV_ROUND_CLOSEST((parent_rate * m), d);
   if (vco_freq >= vcomin && vco_freq <= vcomax) {
    for (o = omin; o <= omax; o++) {
     freq = DIV_ROUND_CLOSEST_ULL(vco_freq, o);
     diff = abs(freq - rate);

     if (diff < WZRD_MIN_ERR) {
      divider->m = m;
      divider->d = d;
      divider->o = o;
      return 0;
     }
    }
   }
  }
 }
 return -EBUSY;
}

static int clk_wzrd_get_divisors(struct clk_hw *hw, unsigned long rate,
     unsigned long parent_rate)
{
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 u64 vco_freq, freq, diff, vcomin, vcomax, best_diff = -1ULL;
 u64 m, d, o;
 u64 mmin, mmax, dmin, dmax, omin, omax, mdmin, mdmax;

 mmin = WZRD_M_MIN << 3;
 mmax = WZRD_M_MAX << 3;
 dmin = WZRD_D_MIN;
 dmax = WZRD_D_MAX;
 omin = WZRD_O_MIN << 3;
 omax = WZRD_O_MAX << 3;
 vcomin = WZRD_VCO_MIN << 3;
 vcomax = WZRD_VCO_MAX << 3;

 for (m = mmin; m <= mmax; m++) {
  mdmin = max(dmin, div64_u64(parent_rate * m + vcomax / 2, vcomax));
  mdmax = min(dmax, div64_u64(parent_rate * m + vcomin / 2, vcomin));
  for (d = mdmin; d <= mdmax; d++) {
   vco_freq = DIV_ROUND_CLOSEST_ULL((parent_rate * m), d);
   o = DIV_ROUND_CLOSEST_ULL(vco_freq, rate);
   if (o < omin || o > omax)
    continue;
   freq = DIV_ROUND_CLOSEST_ULL(vco_freq, o);
   diff = freq - rate;
   if (diff < best_diff) {
    best_diff = diff;
    divider->m = m >> 3;
    divider->m_frac = (m - (divider->m << 3)) * 125;
    divider->d = d;
    divider->o = o >> 3;
    divider->o_frac = (o - (divider->o << 3)) * 125;
   }
  }
 }
 return best_diff < WZRD_MIN_ERR ? 0 : -EBUSY;
}

static int clk_wzrd_reconfig(struct clk_wzrd_divider *divider, void __iomem *div_addr)
{
 u32 value;
 int err;

 /* Check status register */
 err = readl_poll_timeout_atomic(divider->base + WZRD_DR_STATUS_REG_OFFSET, value,
     value & WZRD_DR_LOCK_BIT_MASK,
     WZRD_USEC_POLL, WZRD_TIMEOUT_POLL);
 if (err)
  return -ETIMEDOUT;

 /* Initiate reconfiguration */
 writel(WZRD_DR_BEGIN_DYNA_RECONF, div_addr);
 /* Check status register */
 return readl_poll_timeout_atomic(divider->base + WZRD_DR_STATUS_REG_OFFSET, value,
     value & WZRD_DR_LOCK_BIT_MASK,
     WZRD_USEC_POLL, WZRD_TIMEOUT_POLL);
}

static int clk_wzrd_dynamic_ver_all_nolock(struct clk_hw *hw, unsigned long rate,
        unsigned long parent_rate)
{
 u32 regh, edged, p5en, p5fedge, value2, m, regval, regval1, value;
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 void __iomem *div_addr;
 int err;

 err = clk_wzrd_get_divisors_ver(hw, rate, parent_rate);
 if (err)
  return err;

 writel(0, divider->base + WZRD_CLK_CFG_REG(1, WZRD_CLKFBOUT_4));

 m = divider->m;
 edged = m % WZRD_DUTY_CYCLE;
 regh = m / WZRD_DUTY_CYCLE;
 regval1 = readl(divider->base + WZRD_CLK_CFG_REG(1,
        WZRD_CLKFBOUT_1));
 regval1 |= WZRD_MULT_PREDIV2;
 if (edged)
  regval1 = regval1 | WZRD_CLKFBOUT_EDGE;
 else
  regval1 = regval1 & ~WZRD_CLKFBOUT_EDGE;

 writel(regval1, divider->base + WZRD_CLK_CFG_REG(1,
        WZRD_CLKFBOUT_1));
 regval1 = regh | regh << WZRD_CLKFBOUT_H_SHIFT;
 writel(regval1, divider->base + WZRD_CLK_CFG_REG(1,
        WZRD_CLKFBOUT_2));

 value2 = divider->d;
 edged = value2 % WZRD_DUTY_CYCLE;
 regh = (value2 / WZRD_DUTY_CYCLE);
 regval1 = FIELD_PREP(WZRD_DIVCLK_EDGE, edged);
 writel(regval1, divider->base + WZRD_CLK_CFG_REG(1,
        WZRD_DESKEW_2));
 regval1 = regh | regh << WZRD_CLKFBOUT_H_SHIFT;
 writel(regval1, divider->base + WZRD_CLK_CFG_REG(1, WZRD_DIVCLK));

 value = divider->o;
 regh = value / WZRD_O_DIV;
 regval1 = readl(divider->base + WZRD_CLK_CFG_REG(1,
        WZRD_CLKOUT0_1));
 regval1 |= WZRD_CLKFBOUT_PREDIV2;
 regval1 = regval1 & ~(WZRD_CLKFBOUT_EDGE | WZRD_P5EN | WZRD_P5FEDGE);

 if (value % WZRD_O_DIV > 1) {
  edged = 1;
  regval1 |= edged << WZRD_CLKFBOUT_H_SHIFT;
 }

 p5fedge = value % WZRD_DUTY_CYCLE;
 p5en = value % WZRD_DUTY_CYCLE;

 regval1 = regval1 | FIELD_PREP(WZRD_P5EN, p5en) | FIELD_PREP(WZRD_P5FEDGE, p5fedge);
 writel(regval1, divider->base + WZRD_CLK_CFG_REG(1,
        WZRD_CLKOUT0_1));
 regval = regh | regh << WZRD_CLKFBOUT_H_SHIFT;
 writel(regval, divider->base + WZRD_CLK_CFG_REG(1,
       WZRD_CLKOUT0_2));
 div_addr = divider->base + WZRD_DR_INIT_VERSAL_OFFSET;

 return clk_wzrd_reconfig(divider, div_addr);
}

static int clk_wzrd_dynamic_all_nolock(struct clk_hw *hw, unsigned long rate,
           unsigned long parent_rate)
{
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 void __iomem *div_addr;
 u32 reg;
 int err;

 err = clk_wzrd_get_divisors(hw, rate, parent_rate);
 if (err)
  return err;

 reg = FIELD_PREP(WZRD_CLKOUT_DIVIDE_MASK, divider->o) |
       FIELD_PREP(WZRD_CLKOUT0_FRAC_MASK, divider->o_frac);

 writel(reg, divider->base + WZRD_CLK_CFG_REG(0, 2));
 reg = FIELD_PREP(WZRD_CLKFBOUT_MULT_MASK, divider->m) |
       FIELD_PREP(WZRD_CLKFBOUT_MULT_FRAC_MASK, divider->m_frac) |
       FIELD_PREP(WZRD_DIVCLK_DIVIDE_MASK, divider->d);
 writel(reg, divider->base + WZRD_CLK_CFG_REG(0, 0));
 writel(0, divider->base + WZRD_CLK_CFG_REG(0, 3));
 div_addr = divider->base + WZRD_DR_INIT_REG_OFFSET;
 return clk_wzrd_reconfig(divider, div_addr);
}

static int clk_wzrd_dynamic_all(struct clk_hw *hw, unsigned long rate,
    unsigned long parent_rate)
{
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 unsigned long flags;
 int ret;

 spin_lock_irqsave(divider->lock, flags);

 ret = clk_wzrd_dynamic_all_nolock(hw, rate, parent_rate);

 spin_unlock_irqrestore(divider->lock, flags);

 return ret;
}

static int clk_wzrd_dynamic_all_ver(struct clk_hw *hw, unsigned long rate,
        unsigned long parent_rate)
{
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 unsigned long flags;
 int ret;

 spin_lock_irqsave(divider->lock, flags);

 ret = clk_wzrd_dynamic_ver_all_nolock(hw, rate, parent_rate);

 spin_unlock_irqrestore(divider->lock, flags);

 return ret;
}

static unsigned long clk_wzrd_recalc_rate_all(struct clk_hw *hw,
           unsigned long parent_rate)
{
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 u32 m, d, o, reg, f, mf;
 u64 mul;

 reg = readl(divider->base + WZRD_CLK_CFG_REG(0, 0));
 d = FIELD_GET(WZRD_DIVCLK_DIVIDE_MASK, reg);
 m = FIELD_GET(WZRD_CLKFBOUT_MULT_MASK, reg);
 mf = FIELD_GET(WZRD_CLKFBOUT_MULT_FRAC_MASK, reg);
 reg = readl(divider->base + WZRD_CLK_CFG_REG(0, 2));
 o = FIELD_GET(WZRD_DIVCLK_DIVIDE_MASK, reg);
 f = FIELD_GET(WZRD_CLKOUT0_FRAC_MASK, reg);

 mul = m * 1000 + mf;
 return DIV_ROUND_CLOSEST_ULL(parent_rate * mul, d * (o * 1000 + f));
}

static unsigned long clk_wzrd_recalc_rate_all_ver(struct clk_hw *hw,
        unsigned long parent_rate)
{
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 u32 edged, div2, p5en, edge, prediv2, all, regl, regh, mult;
 u32 div, reg;

 edge = !!(readl(divider->base + WZRD_CLK_CFG_REG(1, WZRD_CLKFBOUT_1)) &
   WZRD_CLKFBOUT_EDGE);

 reg = readl(divider->base + WZRD_CLK_CFG_REG(1, WZRD_CLKFBOUT_2));
 regl = FIELD_GET(WZRD_CLKFBOUT_L_MASK, reg);
 regh = FIELD_GET(WZRD_CLKFBOUT_H_MASK, reg);

 mult = regl + regh + edge;
 if (!mult)
  mult = 1;

 regl = readl(divider->base + WZRD_CLK_CFG_REG(1, WZRD_CLKFBOUT_4)) &
       WZRD_CLKFBOUT_FRAC_EN;
 if (regl) {
  regl = readl(divider->base + WZRD_CLK_CFG_REG(1, WZRD_CLKFBOUT_3))
    & WZRD_VERSAL_FRAC_MASK;
  mult = mult * WZRD_FRAC_GRADIENT + regl;
  parent_rate = DIV_ROUND_CLOSEST((parent_rate * mult), WZRD_FRAC_GRADIENT);
 } else {
  parent_rate = parent_rate * mult;
 }

 /* O Calculation */
 reg = readl(divider->base + WZRD_CLK_CFG_REG(1, WZRD_CLKOUT0_1));
 edged = FIELD_GET(WZRD_CLKFBOUT_EDGE, reg);
 p5en = FIELD_GET(WZRD_P5EN, reg);
 prediv2 = FIELD_GET(WZRD_CLKOUT0_PREDIV2, reg);

 reg = readl(divider->base + WZRD_CLK_CFG_REG(1, WZRD_CLKOUT0_2));
 /* Low time */
 regl = FIELD_GET(WZRD_CLKFBOUT_L_MASK, reg);
 /* High time */
 regh = FIELD_GET(WZRD_CLKFBOUT_H_MASK, reg);
 all = regh + regl + edged;
 if (!all)
  all = 1;

 if (prediv2)
  div2 = PREDIV2_MULT * all + p5en;
 else
  div2 = all;

 /* D calculation */
 edged = !!(readl(divider->base + WZRD_CLK_CFG_REG(1, WZRD_DESKEW_2)) &
       WZRD_DIVCLK_EDGE);
 reg = readl(divider->base + WZRD_CLK_CFG_REG(1, WZRD_DIVCLK));
 /* Low time */
 regl = FIELD_GET(WZRD_CLKFBOUT_L_MASK, reg);
 /* High time */
 regh = FIELD_GET(WZRD_CLKFBOUT_H_MASK, reg);
 div = regl + regh + edged;
 if (!div)
  div = 1;

 div = div * div2;
 return divider_recalc_rate(hw, parent_rate, div, divider->table,
   divider->flags, divider->width);
}

static long clk_wzrd_round_rate_all(struct clk_hw *hw, unsigned long rate,
        unsigned long *prate)
{
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 u32 m, d, o;
 int err;

 err = clk_wzrd_get_divisors(hw, rate, *prate);
 if (err)
  return err;

 m = divider->m;
 d = divider->d;
 o = divider->o;

 rate = div_u64(*prate * (m * 1000 + divider->m_frac), d * (o * 1000 + divider->o_frac));
 return rate;
}

static long clk_wzrd_ver_round_rate_all(struct clk_hw *hw, unsigned long rate,
     unsigned long *prate)
{
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 unsigned long int_freq;
 u32 m, d, o, div, f;
 int err;

 err = clk_wzrd_get_divisors_ver(hw, rate, *prate);
 if (err)
  return err;

 m = divider->m;
 d = divider->d;
 o = divider->o;

 div = d * o;
 int_freq =  divider_recalc_rate(hw, *prate * m, div, divider->table,
     divider->flags, divider->width);

 if (rate > int_freq) {
  f = DIV_ROUND_CLOSEST_ULL(rate * WZRD_FRAC_POINTS, int_freq);
  rate = DIV_ROUND_CLOSEST(int_freq * f, WZRD_FRAC_POINTS);
 }
 return rate;
}

static const struct clk_ops clk_wzrd_ver_divider_ops = {
 .round_rate = clk_wzrd_round_rate,
 .set_rate = clk_wzrd_ver_dynamic_reconfig,
 .recalc_rate = clk_wzrd_recalc_rate_ver,
};

static const struct clk_ops clk_wzrd_ver_div_all_ops = {
 .round_rate = clk_wzrd_ver_round_rate_all,
 .set_rate = clk_wzrd_dynamic_all_ver,
 .recalc_rate = clk_wzrd_recalc_rate_all_ver,
};

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 const struct clk_ops clk_wzrd_clk_div_all_ops = {
 .round_rate = clk_wzrd_round_rate_all,
 .set_rate = clk_wzrd_dynamic_all,
 .recalc_rate = clk_wzrd_recalc_rate_all,
};

static unsigned long clk_wzrd_recalc_ratef(struct clk_hw *hw,
        unsigned long parent_rate)
{
 unsigned int val;
 u32 div, frac;
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 void __iomem *div_addr = divider->base + divider->offset;

 val = readl(div_addr);
 div = val & div_mask(divider->width);
 frac = (val >> WZRD_CLKOUT_FRAC_SHIFT) & WZRD_CLKOUT_FRAC_MASK;

 return mult_frac(parent_rate, 1000, (div * 1000) + frac);
}

static int clk_wzrd_dynamic_reconfig_f(struct clk_hw *hw, unsigned long rate,
           unsigned long parent_rate)
{
 int err;
 u32 value, pre;
 unsigned long rate_div, f, clockout0_div;
 struct clk_wzrd_divider *divider = to_clk_wzrd_divider(hw);
 void __iomem *div_addr = divider->base + divider->offset;

 rate_div = DIV_ROUND_DOWN_ULL(parent_rate * 1000, rate);
 clockout0_div = rate_div / 1000;

 pre = DIV_ROUND_CLOSEST((parent_rate * 1000), rate);
 f = (u32)(pre - (clockout0_div * 1000));
 f = f & WZRD_CLKOUT_FRAC_MASK;
 f = f << WZRD_CLKOUT_DIVIDE_WIDTH;

 value = (f  | (clockout0_div & WZRD_CLKOUT_DIVIDE_MASK));

 /* Set divisor and clear phase offset */
 writel(value, div_addr);
 writel(0x0, 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)
  return err;

 /* Initiate reconfiguration */
 writel(WZRD_DR_BEGIN_DYNA_RECONF_5_2,
        divider->base + WZRD_DR_INIT_REG_OFFSET);
 writel(WZRD_DR_BEGIN_DYNA_RECONF1_5_2,
        divider->base + WZRD_DR_INIT_REG_OFFSET);

 /* Check status register */
 return readl_poll_timeout(divider->base + WZRD_DR_STATUS_REG_OFFSET, value,
    value & WZRD_DR_LOCK_BIT_MASK,
    WZRD_USEC_POLL, WZRD_TIMEOUT_POLL);
}

static long clk_wzrd_round_rate_f(struct clk_hw *hw, unsigned long rate,
      unsigned long *prate)
{
 return rate;
}

static const struct clk_ops clk_wzrd_clk_divider_ops_f = {
 .round_rate = clk_wzrd_round_rate_f,
 .set_rate = clk_wzrd_dynamic_reconfig_f,
 .recalc_rate = clk_wzrd_recalc_ratef,
};

static struct clk_hw *clk_wzrd_register_divf(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,
       u32 div_type,
       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_f;

 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;

 hw = &div->hw;
 ret =  devm_clk_hw_register(dev, hw);
 if (ret)
  return ERR_PTR(ret);

 return hw;
}

static struct clk_hw *clk_wzrd_ver_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,
       u32 div_type,
       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;
 if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)
  init.ops = &clk_divider_ro_ops;
 else if (div_type == DIV_O)
  init.ops = &clk_wzrd_ver_divider_ops;
 else
  init.ops = &clk_wzrd_ver_div_all_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;

 hw = &div->hw;
 ret = devm_clk_hw_register(dev, hw);
 if (ret)
  return ERR_PTR(ret);

 return hw;
}

static struct clk_hw *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,
          u32 div_type,
          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;
 if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)
  init.ops = &clk_divider_ro_ops;
 else if (div_type == DIV_O)
  init.ops = &clk_wzrd_clk_divider_ops;
 else
  init.ops = &clk_wzrd_clk_div_all_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;

 hw = &div->hw;
 ret = devm_clk_hw_register(dev, hw);
 if (ret)
  return ERR_PTR(ret);

 return hw;
}

static int clk_wzrd_clk_notifier(struct notifier_block *nb, unsigned long event,
     void *data)
{
 unsigned long max;
 struct clk_notifier_data *ndata = data;
 struct clk_wzrd *clk_wzrd = to_clk_wzrd(nb);

 if (clk_wzrd->suspended)
  return NOTIFY_OK;

 if (ndata->clk == clk_wzrd->clk_in1)
  max = clk_wzrd_max_freq[clk_wzrd->speed_grade - 1];
 else if (ndata->clk == clk_wzrd->axi_clk)
  max = WZRD_ACLK_MAX_FREQ;
 else
  return NOTIFY_DONE; /* should never happen */

 switch (event) {
 case PRE_RATE_CHANGE:
  if (ndata->new_rate > max)
   return NOTIFY_BAD;
  return NOTIFY_OK;
 case POST_RATE_CHANGE:
 case ABORT_RATE_CHANGE:
 default:
  return NOTIFY_DONE;
 }
}

static int __maybe_unused clk_wzrd_suspend(struct device *dev)
{
 struct clk_wzrd *clk_wzrd = dev_get_drvdata(dev);

 clk_disable_unprepare(clk_wzrd->axi_clk);
 clk_wzrd->suspended = true;

 return 0;
}

static int __maybe_unused clk_wzrd_resume(struct device *dev)
{
 int ret;
 struct clk_wzrd *clk_wzrd = dev_get_drvdata(dev);

 ret = clk_prepare_enable(clk_wzrd->axi_clk);
 if (ret) {
  dev_err(dev, "unable to enable s_axi_aclk\n");
  return ret;
 }

 clk_wzrd->suspended = false;

 return 0;
}

static SIMPLE_DEV_PM_OPS(clk_wzrd_dev_pm_ops, clk_wzrd_suspend,
    clk_wzrd_resume);

static const struct versal_clk_data versal_data = {
 .is_versal = true,
};

static int clk_wzrd_register_output_clocks(struct device *dev, int nr_outputs)
{
 const char *clkout_name, *clk_name, *clk_mul_name;
 struct clk_wzrd *clk_wzrd = dev_get_drvdata(dev);
 u32 regl, regh, edge, regld, reghd, edged, div;
 const struct versal_clk_data *data;
 unsigned long flags = 0;
 bool is_versal = false;
 void __iomem *ctrl_reg;
 u32 reg, reg_f, mult;
 int i;

 data = device_get_match_data(dev);
 if (data)
  is_versal = data->is_versal;

 clkout_name = devm_kasprintf(dev, GFP_KERNEL, "%s_out0", dev_name(dev));
 if (!clkout_name)
  return -ENOMEM;

 if (is_versal) {
  if (nr_outputs == 1) {
   clk_wzrd->clk_data.hws[0] = clk_wzrd_ver_register_divider
    (dev, clkout_name,
    __clk_get_name(clk_wzrd->clk_in1), 0,
    clk_wzrd->base, WZRD_CLK_CFG_REG(is_versal, 3),
    WZRD_CLKOUT_DIVIDE_SHIFT,
    WZRD_CLKOUT_DIVIDE_WIDTH,
    CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO,
    DIV_ALL, &clkwzrd_lock);

   return 0;
  }
  /* register multiplier */
  edge = !!(readl(clk_wzrd->base + WZRD_CLK_CFG_REG(is_versal, 0)) &
    BIT(8));
  regl = (readl(clk_wzrd->base + WZRD_CLK_CFG_REG(is_versal, 1)) &
        WZRD_CLKFBOUT_L_MASK) >> WZRD_CLKFBOUT_L_SHIFT;
  regh = (readl(clk_wzrd->base + WZRD_CLK_CFG_REG(is_versal, 1)) &
        WZRD_CLKFBOUT_H_MASK) >> WZRD_CLKFBOUT_H_SHIFT;
  mult = regl + regh + edge;
  if (!mult)
   mult = 1;
  mult = mult * WZRD_FRAC_GRADIENT;

  regl = readl(clk_wzrd->base + WZRD_CLK_CFG_REG(is_versal, 51)) &
        WZRD_CLKFBOUT_FRAC_EN;
  if (regl) {
   regl = readl(clk_wzrd->base + WZRD_CLK_CFG_REG(is_versal, 48)) &
    WZRD_VERSAL_FRAC_MASK;
   mult = mult + regl;
  }
  div = 64;
 } else {
  if (nr_outputs == 1) {
   clk_wzrd->clk_data.hws[0] = clk_wzrd_register_divider
    (dev, clkout_name,
    __clk_get_name(clk_wzrd->clk_in1), 0,
    clk_wzrd->base, WZRD_CLK_CFG_REG(is_versal, 3),
    WZRD_CLKOUT_DIVIDE_SHIFT,
    WZRD_CLKOUT_DIVIDE_WIDTH,
    CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO,
    DIV_ALL, &clkwzrd_lock);

   return 0;
  }
  reg = readl(clk_wzrd->base + WZRD_CLK_CFG_REG(is_versal, 0));
  reg_f = reg & WZRD_CLKFBOUT_FRAC_MASK;
  reg_f =  reg_f >> WZRD_CLKFBOUT_FRAC_SHIFT;

  reg = reg & WZRD_CLKFBOUT_MULT_MASK;
  reg =  reg >> WZRD_CLKFBOUT_MULT_SHIFT;
  mult = (reg * 1000) + reg_f;
  div = 1000;
 }
 clk_name = devm_kasprintf(dev, GFP_KERNEL, "%s_mul", dev_name(dev));
 if (!clk_name)
  return -ENOMEM;
 clk_wzrd->clks_internal[wzrd_clk_mul] = devm_clk_hw_register_fixed_factor
   (dev, clk_name,
    __clk_get_name(clk_wzrd->clk_in1),
   0, mult, div);
 if (IS_ERR(clk_wzrd->clks_internal[wzrd_clk_mul])) {
  dev_err(dev, "unable to register fixed-factor clock\n");
  return PTR_ERR(clk_wzrd->clks_internal[wzrd_clk_mul]);
 }

 clk_name = devm_kasprintf(dev, GFP_KERNEL, "%s_mul_div", dev_name(dev));
 if (!clk_name)
  return -ENOMEM;

 if (is_versal) {
  edged = !!(readl(clk_wzrd->base + WZRD_CLK_CFG_REG(is_versal, 20)) &
        BIT(10));
  regld = (readl(clk_wzrd->base + WZRD_CLK_CFG_REG(is_versal, 21)) &
        WZRD_CLKFBOUT_L_MASK) >> WZRD_CLKFBOUT_L_SHIFT;
  reghd = (readl(clk_wzrd->base + WZRD_CLK_CFG_REG(is_versal, 21)) &
       WZRD_CLKFBOUT_H_MASK) >> WZRD_CLKFBOUT_H_SHIFT;
  div = (regld  + reghd + edged);
  if (!div)
   div = 1;

  clk_mul_name = clk_hw_get_name(clk_wzrd->clks_internal[wzrd_clk_mul]);
  clk_wzrd->clks_internal[wzrd_clk_mul_div] =
   devm_clk_hw_register_fixed_factor(dev, clk_name, clk_mul_name, 0, 1, div);
 } else {
  ctrl_reg = clk_wzrd->base + WZRD_CLK_CFG_REG(is_versal, 0);
  clk_wzrd->clks_internal[wzrd_clk_mul_div] = devm_clk_hw_register_divider
   (dev, clk_name,
    clk_hw_get_name(clk_wzrd->clks_internal[wzrd_clk_mul]),
   flags, ctrl_reg, 0, 8, CLK_DIVIDER_ONE_BASED |
   CLK_DIVIDER_ALLOW_ZERO, &clkwzrd_lock);
 }
 if (IS_ERR(clk_wzrd->clks_internal[wzrd_clk_mul_div])) {
  dev_err(dev, "unable to register divider clock\n");
  return PTR_ERR(clk_wzrd->clks_internal[wzrd_clk_mul_div]);
 }

 /* register div per output */
 for (i = nr_outputs - 1; i >= 0 ; i--) {
  clkout_name = devm_kasprintf(dev, GFP_KERNEL, "%s_out%d", dev_name(dev), i);
  if (!clkout_name)
   return -ENOMEM;

  if (is_versal) {
   clk_wzrd->clk_data.hws[i] = clk_wzrd_ver_register_divider
      (dev,
       clkout_name, clk_name, 0,
       clk_wzrd->base,
       (WZRD_CLK_CFG_REG(is_versal, 2) + i * 8),
       WZRD_CLKOUT_DIVIDE_SHIFT,
       WZRD_CLKOUT_DIVIDE_WIDTH,
       CLK_DIVIDER_ONE_BASED |
       CLK_DIVIDER_ALLOW_ZERO,
       DIV_O, &clkwzrd_lock);
  } else {
   if (!i)
    clk_wzrd->clk_data.hws[i] = clk_wzrd_register_divf
     (dev, clkout_name, clk_name, flags, clk_wzrd->base,
     (WZRD_CLK_CFG_REG(is_versal, 2) + i * 12),
     WZRD_CLKOUT_DIVIDE_SHIFT,
     WZRD_CLKOUT_DIVIDE_WIDTH,
     CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO,
     DIV_O, &clkwzrd_lock);
   else
    clk_wzrd->clk_data.hws[i] = clk_wzrd_register_divider
     (dev, clkout_name, clk_name, 0, clk_wzrd->base,
     (WZRD_CLK_CFG_REG(is_versal, 2) + i * 12),
     WZRD_CLKOUT_DIVIDE_SHIFT,
     WZRD_CLKOUT_DIVIDE_WIDTH,
     CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO,
     DIV_O, &clkwzrd_lock);
  }
  if (IS_ERR(clk_wzrd->clk_data.hws[i])) {
   dev_err(dev, "unable to register divider clock\n");
   return PTR_ERR(clk_wzrd->clk_data.hws[i]);
  }
 }

 return 0;
}

static int clk_wzrd_probe(struct platform_device *pdev)
{
 struct device_node *np = pdev->dev.of_node;
 struct clk_wzrd *clk_wzrd;
 unsigned long rate;
 int nr_outputs;
 int ret;

 ret = of_property_read_u32(np, "xlnx,nr-outputs", &nr_outputs);
 if (ret || nr_outputs > WZRD_NUM_OUTPUTS)
  return -EINVAL;

 clk_wzrd = devm_kzalloc(&pdev->dev, struct_size(clk_wzrd, clk_data.hws, nr_outputs),
    GFP_KERNEL);
 if (!clk_wzrd)
  return -ENOMEM;
 platform_set_drvdata(pdev, clk_wzrd);

 clk_wzrd->base = devm_platform_ioremap_resource(pdev, 0);
 if (IS_ERR(clk_wzrd->base))
  return PTR_ERR(clk_wzrd->base);

 clk_wzrd->axi_clk = devm_clk_get_enabled(&pdev->dev, "s_axi_aclk");
 if (IS_ERR(clk_wzrd->axi_clk))
  return dev_err_probe(&pdev->dev, PTR_ERR(clk_wzrd->axi_clk),
         "s_axi_aclk not found\n");
 rate = clk_get_rate(clk_wzrd->axi_clk);
 if (rate > WZRD_ACLK_MAX_FREQ) {
  dev_err(&pdev->dev, "s_axi_aclk frequency (%lu) too high\n", rate);
  return -EINVAL;
 }

 if (!of_property_present(np, "xlnx,static-config")) {
  ret = of_property_read_u32(np, "xlnx,speed-grade", &clk_wzrd->speed_grade);
  if (!ret) {
   if (clk_wzrd->speed_grade < 1 || clk_wzrd->speed_grade > 3) {
    dev_warn(&pdev->dev, "invalid speed grade '%d'\n",
      clk_wzrd->speed_grade);
    clk_wzrd->speed_grade = 0;
   }
  }

  clk_wzrd->clk_in1 = devm_clk_get(&pdev->dev, "clk_in1");
  if (IS_ERR(clk_wzrd->clk_in1))
   return dev_err_probe(&pdev->dev, PTR_ERR(clk_wzrd->clk_in1),
          "clk_in1 not found\n");

  ret = clk_wzrd_register_output_clocks(&pdev->dev, nr_outputs);
  if (ret)
   return ret;

  clk_wzrd->clk_data.num = nr_outputs;
  ret = devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_onecell_get,
        &clk_wzrd->clk_data);
  if (ret) {
   dev_err(&pdev->dev, "unable to register clock provider\n");
   return ret;
  }

  if (clk_wzrd->speed_grade) {
   clk_wzrd->nb.notifier_call = clk_wzrd_clk_notifier;

   ret = devm_clk_notifier_register(&pdev->dev, clk_wzrd->clk_in1,
        &clk_wzrd->nb);
   if (ret)
    dev_warn(&pdev->dev,
      "unable to register clock notifier\n");

   ret = devm_clk_notifier_register(&pdev->dev, clk_wzrd->axi_clk,
        &clk_wzrd->nb);
   if (ret)
    dev_warn(&pdev->dev,
      "unable to register clock notifier\n");
  }
 }

 return 0;
}

static const struct of_device_id clk_wzrd_ids[] = {
 { .compatible = "xlnx,versal-clk-wizard", .data = &versal_data },
 { .compatible = "xlnx,clocking-wizard"   },
 { .compatible = "xlnx,clocking-wizard-v5.2"   },
 { .compatible = "xlnx,clocking-wizard-v6.0"  },
 { },
};
MODULE_DEVICE_TABLE(of, clk_wzrd_ids);

static struct platform_driver clk_wzrd_driver = {
 .driver = {
  .name = "clk-wizard",
  .of_match_table = clk_wzrd_ids,
  .pm = &clk_wzrd_dev_pm_ops,
 },
 .probe = clk_wzrd_probe,
};
module_platform_driver(clk_wzrd_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Soeren Brinkmann <soren.brinkmann@xilinx.com");
MODULE_DESCRIPTION("Driver for the Xilinx Clocking Wizard IP core");

Messung V0.5 in Prozent
C=97 H=98 G=97

[Dauer der Verarbeitung: 0.17 Sekunden, vorverarbeitet 2026-09-29]