diff mbox series

[08/12] soc: xilinx: vcu: implement clock provider for output clocks

Message ID 20201116075532.4019252-9-m.tretter@pengutronix.de
State New
Headers show
Series [01/12] ARM: dts: define indexes for output clocks | expand

Commit Message

Michael Tretter Nov. 16, 2020, 7:55 a.m. UTC
The VCU System-Level Control uses an internal PLL to drive the core and
MCU clock for the allegro encoder and decoder based on an external PL
clock.

In order be able to ensure that the clocks are enabled and to get their
rate from other drivers, the module must implement a clock provider and
register the clocks at the common clock framework. Other drivers are
then able to access the clock via devicetree bindings.

Signed-off-by: Michael Tretter <m.tretter@pengutronix.de>
---
 drivers/soc/xilinx/xlnx_vcu.c | 191 +++++++++++++++++++++++++++-------
 1 file changed, 154 insertions(+), 37 deletions(-)

Comments

Michal Simek Dec. 2, 2020, 2:49 p.m. UTC | #1
On 16. 11. 20 8:55, Michael Tretter wrote:
> The VCU System-Level Control uses an internal PLL to drive the core and

> MCU clock for the allegro encoder and decoder based on an external PL

> clock.

> 

> In order be able to ensure that the clocks are enabled and to get their

> rate from other drivers, the module must implement a clock provider and

> register the clocks at the common clock framework. Other drivers are

> then able to access the clock via devicetree bindings.

> 

> Signed-off-by: Michael Tretter <m.tretter@pengutronix.de>

> ---

>  drivers/soc/xilinx/xlnx_vcu.c | 191 +++++++++++++++++++++++++++-------

>  1 file changed, 154 insertions(+), 37 deletions(-)

> 

> diff --git a/drivers/soc/xilinx/xlnx_vcu.c b/drivers/soc/xilinx/xlnx_vcu.c

> index 725e646aa726..cedc8b7859f7 100644

> --- a/drivers/soc/xilinx/xlnx_vcu.c

> +++ b/drivers/soc/xilinx/xlnx_vcu.c

> @@ -18,6 +18,8 @@

>  #include <linux/platform_device.h>

>  #include <linux/regmap.h>

>  

> +#include <dt-bindings/clock/xlnx-vcu.h>

> +

>  /* vcu slcr registers, bitmask and shift */

>  #define VCU_PLL_CTRL			0x24

>  #define VCU_PLL_CTRL_RESET_MASK		0x01

> @@ -50,11 +52,6 @@

>  #define VCU_ENC_MCU_CTRL		0x34

>  #define VCU_DEC_CORE_CTRL		0x38

>  #define VCU_DEC_MCU_CTRL		0x3c

> -#define VCU_PLL_DIVISOR_MASK		0x3f

> -#define VCU_PLL_DIVISOR_SHIFT		4

> -#define VCU_SRCSEL_MASK			0x01

> -#define VCU_SRCSEL_SHIFT		0

> -#define VCU_SRCSEL_PLL			1

>  

>  #define VCU_PLL_STATUS			0x60

>  #define VCU_PLL_STATUS_LOCK_STATUS_MASK	0x01

> @@ -82,6 +79,7 @@ struct xvcu_device {

>  	struct regmap *logicore_reg_ba;

>  	void __iomem *vcu_slcr_ba;

>  	struct clk_hw *pll;

> +	struct clk_hw_onecell_data *clk_data;


The same here with clk_data - please update kernel-doc format.

>  };

>  

>  static struct regmap_config vcu_settings_regmap_config = {

> @@ -403,7 +401,7 @@ static int xvcu_set_vcu_pll_info(struct xvcu_device *xvcu)

>  	u32 refclk, coreclk, mcuclk, inte, deci;

>  	u32 divisor_mcu, divisor_core, fvco;

>  	u32 clkoutdiv, vcu_pll_ctrl, pll_clk;

> -	u32 mod, ctrl;

> +	u32 mod;

>  	int i;

>  	int ret;

>  	const struct xvcu_pll_cfg *found = NULL;

> @@ -478,37 +476,6 @@ static int xvcu_set_vcu_pll_info(struct xvcu_device *xvcu)

>  	dev_dbg(xvcu->dev, "Actual Core clock freq is %uHz\n", coreclk);

>  	dev_dbg(xvcu->dev, "Actual Mcu clock freq is %uHz\n", mcuclk);

>  

> -	/* Set divisor for the core and mcu clock */

> -	ctrl = xvcu_read(xvcu->vcu_slcr_ba, VCU_ENC_CORE_CTRL);

> -	ctrl &= ~(VCU_PLL_DIVISOR_MASK << VCU_PLL_DIVISOR_SHIFT);

> -	ctrl |= (divisor_core & VCU_PLL_DIVISOR_MASK) <<

> -		 VCU_PLL_DIVISOR_SHIFT;

> -	ctrl &= ~(VCU_SRCSEL_MASK << VCU_SRCSEL_SHIFT);

> -	ctrl |= (VCU_SRCSEL_PLL & VCU_SRCSEL_MASK) << VCU_SRCSEL_SHIFT;

> -	xvcu_write(xvcu->vcu_slcr_ba, VCU_ENC_CORE_CTRL, ctrl);

> -

> -	ctrl = xvcu_read(xvcu->vcu_slcr_ba, VCU_DEC_CORE_CTRL);

> -	ctrl &= ~(VCU_PLL_DIVISOR_MASK << VCU_PLL_DIVISOR_SHIFT);

> -	ctrl |= (divisor_core & VCU_PLL_DIVISOR_MASK) <<

> -		 VCU_PLL_DIVISOR_SHIFT;

> -	ctrl &= ~(VCU_SRCSEL_MASK << VCU_SRCSEL_SHIFT);

> -	ctrl |= (VCU_SRCSEL_PLL & VCU_SRCSEL_MASK) << VCU_SRCSEL_SHIFT;

> -	xvcu_write(xvcu->vcu_slcr_ba, VCU_DEC_CORE_CTRL, ctrl);

> -

> -	ctrl = xvcu_read(xvcu->vcu_slcr_ba, VCU_ENC_MCU_CTRL);

> -	ctrl &= ~(VCU_PLL_DIVISOR_MASK << VCU_PLL_DIVISOR_SHIFT);

> -	ctrl |= (divisor_mcu & VCU_PLL_DIVISOR_MASK) << VCU_PLL_DIVISOR_SHIFT;

> -	ctrl &= ~(VCU_SRCSEL_MASK << VCU_SRCSEL_SHIFT);

> -	ctrl |= (VCU_SRCSEL_PLL & VCU_SRCSEL_MASK) << VCU_SRCSEL_SHIFT;

> -	xvcu_write(xvcu->vcu_slcr_ba, VCU_ENC_MCU_CTRL, ctrl);

> -

> -	ctrl = xvcu_read(xvcu->vcu_slcr_ba, VCU_DEC_MCU_CTRL);

> -	ctrl &= ~(VCU_PLL_DIVISOR_MASK << VCU_PLL_DIVISOR_SHIFT);

> -	ctrl |= (divisor_mcu & VCU_PLL_DIVISOR_MASK) << VCU_PLL_DIVISOR_SHIFT;

> -	ctrl &= ~(VCU_SRCSEL_MASK << VCU_SRCSEL_SHIFT);

> -	ctrl |= (VCU_SRCSEL_PLL & VCU_SRCSEL_MASK) << VCU_SRCSEL_SHIFT;

> -	xvcu_write(xvcu->vcu_slcr_ba, VCU_DEC_MCU_CTRL, ctrl);

> -

>  	ret = xvcu_pll_set_rate(xvcu, fvco, refclk);

>  	if (ret)

>  		return ret;

> @@ -545,6 +512,146 @@ static int xvcu_set_pll(struct xvcu_device *xvcu)

>  	return xvcu_pll_enable(xvcu);

>  }

>  

> +static struct clk_hw *xvcu_clk_hw_register_leaf(struct device *dev,

> +						const char *name,

> +						const char * const *parent_names,

> +						u8 num_parents,

> +						struct clk_hw *parent_default,

> +						void __iomem *reg,

> +						spinlock_t *lock)

> +{

> +	unsigned long flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT;

> +	u8 divider_flags = CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO |

> +			   CLK_DIVIDER_ROUND_CLOSEST;

> +	struct clk_hw *mux = NULL;

> +	struct clk_hw *divider = NULL;

> +	struct clk_hw *gate = NULL;

> +	char *name_mux;

> +	char *name_div;

> +	int err;

> +

> +	name_mux = devm_kasprintf(dev, GFP_KERNEL, "%s%s", name, "_mux");

> +	if (!name_mux) {

> +		err = -ENOMEM;

> +		goto err;

> +	}

> +	mux = clk_hw_register_mux(dev, name_mux, parent_names, num_parents,

> +				  flags, reg, 0, 1, 0, lock);

> +	if (IS_ERR(mux)) {

> +		err = PTR_ERR(divider);


mux here?
And smatch is reporting also this issue. Please take a look.

drivers/soc/xilinx/xlnx_vcu.c:541 xvcu_clk_hw_register_leaf() warn:
passing zero to 'PTR_ERR'
drivers/soc/xilinx/xlnx_vcu.c:577 xvcu_clk_hw_register_leaf() warn:
passing zero to 'ERR_PTR'



> +		goto err;

> +	}

> +	clk_hw_set_parent(mux, parent_default);

> +

> +	name_div = devm_kasprintf(dev, GFP_KERNEL, "%s%s", name, "_div");

> +	if (!name_div) {

> +		err = -ENOMEM;

> +		goto err;

> +	}

> +	divider = clk_hw_register_divider_parent_hw(dev, name_div, mux, flags,

> +						    reg, 4, 6, divider_flags,

> +						    lock);

> +	if (IS_ERR(divider)) {

> +		err = PTR_ERR(divider);

> +		goto err;

> +	}

> +

> +	gate = clk_hw_register_gate_parent_hw(dev, name, divider,

> +					      CLK_SET_RATE_PARENT, reg, 12, 0,

> +					      lock);

> +	if (IS_ERR(gate)) {

> +		err = PTR_ERR(gate);

> +		goto err;

> +	}

> +

> +	return gate;

> +

> +err:

> +	if (!IS_ERR_OR_NULL(gate))

> +		clk_hw_unregister_gate(gate);

> +	if (!IS_ERR_OR_NULL(divider))

> +		clk_hw_unregister_divider(divider);

> +	if (!IS_ERR_OR_NULL(mux))

> +		clk_hw_unregister_divider(mux);

> +

> +	return ERR_PTR(err);

> +}

> +

> +static void xvcu_clk_hw_unregister_leaf(struct clk_hw *hw)

> +{

> +	struct clk_hw *gate = hw;

> +	struct clk_hw *divider;

> +	struct clk_hw *mux;

> +

> +	if (!gate)

> +		return;

> +

> +	divider = clk_hw_get_parent(gate);

> +	clk_hw_unregister_gate(gate);

> +	if (!divider)

> +		return;

> +

> +	mux = clk_hw_get_parent(divider);

> +	clk_hw_unregister_mux(mux);

> +	if (!divider)

> +		return;

> +

> +	clk_hw_unregister_divider(divider);

> +}

> +

> +static DEFINE_SPINLOCK(venc_core_lock);

> +static DEFINE_SPINLOCK(venc_mcu_lock);

> +

> +static int xvcu_register_clock_provider(struct xvcu_device *xvcu)

> +{

> +	struct device *dev = xvcu->dev;

> +	const char *parent_names[2];

> +	struct clk_hw *parent_default;

> +	struct clk_hw_onecell_data *data;

> +	struct clk_hw **hws;

> +	void __iomem *reg_base = xvcu->vcu_slcr_ba;

> +

> +	data = devm_kzalloc(dev, struct_size(data, hws, CLK_XVCU_NUM_CLOCKS), GFP_KERNEL);

> +	if (!data)

> +		return -ENOMEM;

> +	data->num = CLK_XVCU_NUM_CLOCKS;

> +	hws = data->hws;

> +

> +	xvcu->clk_data = data;

> +

> +	parent_default = xvcu->pll;

> +	parent_names[0] = "dummy";

> +	parent_names[1] = clk_hw_get_name(parent_default);

> +

> +	hws[CLK_XVCU_ENC_CORE] =

> +		xvcu_clk_hw_register_leaf(dev, "venc_core_clk",

> +					  parent_names,

> +					  ARRAY_SIZE(parent_names),

> +					  parent_default,

> +					  reg_base + VCU_ENC_CORE_CTRL,

> +					  &venc_core_lock);

> +	hws[CLK_XVCU_ENC_MCU] =

> +		xvcu_clk_hw_register_leaf(dev, "venc_mcu_clk",

> +					  parent_names,

> +					  ARRAY_SIZE(parent_names),

> +					  parent_default,

> +					  reg_base + VCU_ENC_MCU_CTRL,

> +					  &venc_mcu_lock);

> +

> +	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, data);

> +}

> +

> +static void xvcu_unregister_clock_provider(struct xvcu_device *xvcu)

> +{

> +	struct clk_hw_onecell_data *data = xvcu->clk_data;

> +	struct clk_hw **hws = data->hws;

> +

> +	if (!IS_ERR_OR_NULL(hws[CLK_XVCU_ENC_MCU]))

> +		xvcu_clk_hw_unregister_leaf(hws[CLK_XVCU_ENC_MCU]);

> +	if (!IS_ERR_OR_NULL(hws[CLK_XVCU_ENC_CORE]))

> +		xvcu_clk_hw_unregister_leaf(hws[CLK_XVCU_ENC_CORE]);

> +}

> +

>  /**

>   * xvcu_probe - Probe existence of the logicoreIP

>   *			and initialize PLL

> @@ -639,10 +746,18 @@ static int xvcu_probe(struct platform_device *pdev)

>  		goto error_pll_ref;

>  	}

>  

> +	ret = xvcu_register_clock_provider(xvcu);

> +	if (ret) {

> +		dev_err(&pdev->dev, "failed to register clock provider\n");

> +		goto error_clk_provider;

> +	}

> +

>  	dev_set_drvdata(&pdev->dev, xvcu);

>  

>  	return 0;

>  

> +error_clk_provider:

> +	xvcu_unregister_clock_provider(xvcu);

>  error_pll_ref:

>  	clk_disable_unprepare(xvcu->aclk);

>  	return ret;

> @@ -664,6 +779,8 @@ static int xvcu_remove(struct platform_device *pdev)

>  	if (!xvcu)

>  		return -ENODEV;

>  

> +	xvcu_unregister_clock_provider(xvcu);

> +

>  	/* Add the the Gasket isolation and put the VCU in reset. */

>  	regmap_write(xvcu->logicore_reg_ba, VCU_GASKET_INIT, 0);

>  

> 


M
Stephen Boyd Dec. 13, 2020, 5:55 a.m. UTC | #2
Quoting Michael Tretter (2020-11-15 23:55:28)
> diff --git a/drivers/soc/xilinx/xlnx_vcu.c b/drivers/soc/xilinx/xlnx_vcu.c

> index 725e646aa726..cedc8b7859f7 100644

> --- a/drivers/soc/xilinx/xlnx_vcu.c

> +++ b/drivers/soc/xilinx/xlnx_vcu.c

> @@ -545,6 +512,146 @@ static int xvcu_set_pll(struct xvcu_device *xvcu)

>         return xvcu_pll_enable(xvcu);

>  }

>  

> +static struct clk_hw *xvcu_clk_hw_register_leaf(struct device *dev,

> +                                               const char *name,

> +                                               const char * const *parent_names,

> +                                               u8 num_parents,

> +                                               struct clk_hw *parent_default,

> +                                               void __iomem *reg,

> +                                               spinlock_t *lock)

> +{

> +       unsigned long flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT;

> +       u8 divider_flags = CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO |


Why u8?

> +                          CLK_DIVIDER_ROUND_CLOSEST;

> +       struct clk_hw *mux = NULL;

> +       struct clk_hw *divider = NULL;

> +       struct clk_hw *gate = NULL;

> +       char *name_mux;

> +       char *name_div;

> +       int err;

> +

> +       name_mux = devm_kasprintf(dev, GFP_KERNEL, "%s%s", name, "_mux");

> +       if (!name_mux) {

> +               err = -ENOMEM;

> +               goto err;

> +       }

> +       mux = clk_hw_register_mux(dev, name_mux, parent_names, num_parents,

> +                                 flags, reg, 0, 1, 0, lock);

> +       if (IS_ERR(mux)) {

> +               err = PTR_ERR(divider);

> +               goto err;

> +       }

> +       clk_hw_set_parent(mux, parent_default);


Can this be done from assigned-clock-parents binding?

> +

> +       name_div = devm_kasprintf(dev, GFP_KERNEL, "%s%s", name, "_div");

> +       if (!name_div) {

> +               err = -ENOMEM;

> +               goto err;

> +       }

> +       divider = clk_hw_register_divider_parent_hw(dev, name_div, mux, flags,

> +                                                   reg, 4, 6, divider_flags,

> +                                                   lock);

> +       if (IS_ERR(divider)) {

> +               err = PTR_ERR(divider);

> +               goto err;

> +       }

> +

> +       gate = clk_hw_register_gate_parent_hw(dev, name, divider,

> +                                             CLK_SET_RATE_PARENT, reg, 12, 0,

> +                                             lock);

> +       if (IS_ERR(gate)) {

> +               err = PTR_ERR(gate);

> +               goto err;

> +       }

> +

> +       return gate;

> +

> +err:

> +       if (!IS_ERR_OR_NULL(gate))


Would be nicer to have some more goto labels and skip the IS_ERR_OR_NULL
checks.

> +               clk_hw_unregister_gate(gate);

> +       if (!IS_ERR_OR_NULL(divider))

> +               clk_hw_unregister_divider(divider);

> +       if (!IS_ERR_OR_NULL(mux))

> +               clk_hw_unregister_divider(mux);

> +

> +       return ERR_PTR(err);

> +}

> +

> +static void xvcu_clk_hw_unregister_leaf(struct clk_hw *hw)

> +{

> +       struct clk_hw *gate = hw;

> +       struct clk_hw *divider;

> +       struct clk_hw *mux;

> +

> +       if (!gate)

> +               return;

> +

> +       divider = clk_hw_get_parent(gate);

> +       clk_hw_unregister_gate(gate);

> +       if (!divider)

> +               return;

> +

> +       mux = clk_hw_get_parent(divider);

> +       clk_hw_unregister_mux(mux);

> +       if (!divider)

> +               return;

> +

> +       clk_hw_unregister_divider(divider);

> +}

> +

> +static DEFINE_SPINLOCK(venc_core_lock);

> +static DEFINE_SPINLOCK(venc_mcu_lock);


Any reason to not allocate these spinlocks too?

> +

> +static int xvcu_register_clock_provider(struct xvcu_device *xvcu)

> +{

> +       struct device *dev = xvcu->dev;

> +       const char *parent_names[2];

> +       struct clk_hw *parent_default;

> +       struct clk_hw_onecell_data *data;

> +       struct clk_hw **hws;

> +       void __iomem *reg_base = xvcu->vcu_slcr_ba;

> +

> +       data = devm_kzalloc(dev, struct_size(data, hws, CLK_XVCU_NUM_CLOCKS), GFP_KERNEL);

> +       if (!data)

> +               return -ENOMEM;

> +       data->num = CLK_XVCU_NUM_CLOCKS;

> +       hws = data->hws;

> +

> +       xvcu->clk_data = data;

> +

> +       parent_default = xvcu->pll;

> +       parent_names[0] = "dummy";


What is "dummy"?

> +       parent_names[1] = clk_hw_get_name(parent_default);


Can we use the new way of specifying clk parents from DT or by using
direct pointers instead of using string names? The idea is to get rid of
clk_hw_get_name() eventually.

> +

> +       hws[CLK_XVCU_ENC_CORE] =

> +               xvcu_clk_hw_register_leaf(dev, "venc_core_clk",

> +                                         parent_names,

> +                                         ARRAY_SIZE(parent_names),

> +                                         parent_default,

> +                                         reg_base + VCU_ENC_CORE_CTRL,

> +                                         &venc_core_lock);

> +       hws[CLK_XVCU_ENC_MCU] =

> +               xvcu_clk_hw_register_leaf(dev, "venc_mcu_clk",

> +                                         parent_names,

> +                                         ARRAY_SIZE(parent_names),

> +                                         parent_default,

> +                                         reg_base + VCU_ENC_MCU_CTRL,

> +                                         &venc_mcu_lock);

> +
Michael Tretter Dec. 15, 2020, 11:38 a.m. UTC | #3
On Sat, 12 Dec 2020 21:55:34 -0800, Stephen Boyd wrote:
> Quoting Michael Tretter (2020-11-15 23:55:28)

> > diff --git a/drivers/soc/xilinx/xlnx_vcu.c b/drivers/soc/xilinx/xlnx_vcu.c

> > index 725e646aa726..cedc8b7859f7 100644

> > --- a/drivers/soc/xilinx/xlnx_vcu.c

> > +++ b/drivers/soc/xilinx/xlnx_vcu.c

> > @@ -545,6 +512,146 @@ static int xvcu_set_pll(struct xvcu_device *xvcu)

> >         return xvcu_pll_enable(xvcu);

> >  }

> >  

> > +static struct clk_hw *xvcu_clk_hw_register_leaf(struct device *dev,

> > +                                               const char *name,

> > +                                               const char * const *parent_names,

> > +                                               u8 num_parents,

> > +                                               struct clk_hw *parent_default,

> > +                                               void __iomem *reg,

> > +                                               spinlock_t *lock)

> > +{

> > +       unsigned long flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT;

> > +       u8 divider_flags = CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO |

> 

> Why u8?


__clk_hw_register_divider expects u8 as divider_flags.

> 

> > +                          CLK_DIVIDER_ROUND_CLOSEST;

> > +       struct clk_hw *mux = NULL;

> > +       struct clk_hw *divider = NULL;

> > +       struct clk_hw *gate = NULL;

> > +       char *name_mux;

> > +       char *name_div;

> > +       int err;

> > +

> > +       name_mux = devm_kasprintf(dev, GFP_KERNEL, "%s%s", name, "_mux");

> > +       if (!name_mux) {

> > +               err = -ENOMEM;

> > +               goto err;

> > +       }

> > +       mux = clk_hw_register_mux(dev, name_mux, parent_names, num_parents,

> > +                                 flags, reg, 0, 1, 0, lock);

> > +       if (IS_ERR(mux)) {

> > +               err = PTR_ERR(divider);

> > +               goto err;

> > +       }

> > +       clk_hw_set_parent(mux, parent_default);

> 

> Can this be done from assigned-clock-parents binding?


Could be done, if the driver provides at least the PLL and the mux in addition
to the actual output clocks. Otherwise, it is not possible to reference the
PLL post divider and the mux from the device tree. I wanted to avoid to expose
the complexity to the device tree. Would you prefer, if all clocks are
provided instead of only the output clocks?

> 

> > +

> > +       name_div = devm_kasprintf(dev, GFP_KERNEL, "%s%s", name, "_div");

> > +       if (!name_div) {

> > +               err = -ENOMEM;

> > +               goto err;

> > +       }

> > +       divider = clk_hw_register_divider_parent_hw(dev, name_div, mux, flags,

> > +                                                   reg, 4, 6, divider_flags,

> > +                                                   lock);

> > +       if (IS_ERR(divider)) {

> > +               err = PTR_ERR(divider);

> > +               goto err;

> > +       }

> > +

> > +       gate = clk_hw_register_gate_parent_hw(dev, name, divider,

> > +                                             CLK_SET_RATE_PARENT, reg, 12, 0,

> > +                                             lock);

> > +       if (IS_ERR(gate)) {

> > +               err = PTR_ERR(gate);

> > +               goto err;

> > +       }

> > +

> > +       return gate;

> > +

> > +err:

> > +       if (!IS_ERR_OR_NULL(gate))

> 

> Would be nicer to have some more goto labels and skip the IS_ERR_OR_NULL

> checks.


Ack.

> 

> > +               clk_hw_unregister_gate(gate);

> > +       if (!IS_ERR_OR_NULL(divider))

> > +               clk_hw_unregister_divider(divider);

> > +       if (!IS_ERR_OR_NULL(mux))

> > +               clk_hw_unregister_divider(mux);

> > +

> > +       return ERR_PTR(err);

> > +}

> > +

> > +static void xvcu_clk_hw_unregister_leaf(struct clk_hw *hw)

> > +{

> > +       struct clk_hw *gate = hw;

> > +       struct clk_hw *divider;

> > +       struct clk_hw *mux;

> > +

> > +       if (!gate)

> > +               return;

> > +

> > +       divider = clk_hw_get_parent(gate);

> > +       clk_hw_unregister_gate(gate);

> > +       if (!divider)

> > +               return;

> > +

> > +       mux = clk_hw_get_parent(divider);

> > +       clk_hw_unregister_mux(mux);

> > +       if (!divider)

> > +               return;

> > +

> > +       clk_hw_unregister_divider(divider);

> > +}

> > +

> > +static DEFINE_SPINLOCK(venc_core_lock);

> > +static DEFINE_SPINLOCK(venc_mcu_lock);

> 

> Any reason to not allocate these spinlocks too?


I will change this.

> 

> > +

> > +static int xvcu_register_clock_provider(struct xvcu_device *xvcu)

> > +{

> > +       struct device *dev = xvcu->dev;

> > +       const char *parent_names[2];

> > +       struct clk_hw *parent_default;

> > +       struct clk_hw_onecell_data *data;

> > +       struct clk_hw **hws;

> > +       void __iomem *reg_base = xvcu->vcu_slcr_ba;

> > +

> > +       data = devm_kzalloc(dev, struct_size(data, hws, CLK_XVCU_NUM_CLOCKS), GFP_KERNEL);

> > +       if (!data)

> > +               return -ENOMEM;

> > +       data->num = CLK_XVCU_NUM_CLOCKS;

> > +       hws = data->hws;

> > +

> > +       xvcu->clk_data = data;

> > +

> > +       parent_default = xvcu->pll;

> > +       parent_names[0] = "dummy";

> 

> What is "dummy"?


According to the register reference [0], the output clocks can be driven by an
external clock instead of the PLL, but the VCU Product Guide [1] does not
mention any ports for actually driving the clock. From my understanding of the
IP core, this is a clock mux which has a not-connected first parent. Maybe
someone at Xilinx can clarify, what is happening here.

[0] https://www.xilinx.com/html_docs/registers/ug1087/ug1087-zynq-ultrascale-registers.html
[1] https://www.xilinx.com/support/documentation-navigation/see-all-versions.html?xlnxproducttypes=IP%20Cores&xlnxipcoresname=v-vcu

What would be a better way to handle this?

> 

> > +       parent_names[1] = clk_hw_get_name(parent_default);

> 

> Can we use the new way of specifying clk parents from DT or by using

> direct pointers instead of using string names? The idea is to get rid of

> clk_hw_get_name() eventually.


It should be possible to use the direct pointers, but this really depends on
how the "dummy" clock is handled.

Thanks,

Michael

> 

> > +

> > +       hws[CLK_XVCU_ENC_CORE] =

> > +               xvcu_clk_hw_register_leaf(dev, "venc_core_clk",

> > +                                         parent_names,

> > +                                         ARRAY_SIZE(parent_names),

> > +                                         parent_default,

> > +                                         reg_base + VCU_ENC_CORE_CTRL,

> > +                                         &venc_core_lock);

> > +       hws[CLK_XVCU_ENC_MCU] =

> > +               xvcu_clk_hw_register_leaf(dev, "venc_mcu_clk",

> > +                                         parent_names,

> > +                                         ARRAY_SIZE(parent_names),

> > +                                         parent_default,

> > +                                         reg_base + VCU_ENC_MCU_CTRL,

> > +                                         &venc_mcu_lock);

> > +

>
Stephen Boyd Dec. 16, 2020, 1:09 a.m. UTC | #4
Quoting Michael Tretter (2020-12-15 03:38:09)
> On Sat, 12 Dec 2020 21:55:34 -0800, Stephen Boyd wrote:

> > Quoting Michael Tretter (2020-11-15 23:55:28)

> > > diff --git a/drivers/soc/xilinx/xlnx_vcu.c b/drivers/soc/xilinx/xlnx_vcu.c

> > > index 725e646aa726..cedc8b7859f7 100644

> > > --- a/drivers/soc/xilinx/xlnx_vcu.c

> > > +++ b/drivers/soc/xilinx/xlnx_vcu.c

> > > @@ -545,6 +512,146 @@ static int xvcu_set_pll(struct xvcu_device *xvcu)

> > >         return xvcu_pll_enable(xvcu);

> > >  }

> > >  

> > > +static struct clk_hw *xvcu_clk_hw_register_leaf(struct device *dev,

> > > +                                               const char *name,

> > > +                                               const char * const *parent_names,

> > > +                                               u8 num_parents,

> > > +                                               struct clk_hw *parent_default,

> > > +                                               void __iomem *reg,

> > > +                                               spinlock_t *lock)

> > > +{

> > > +       unsigned long flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT;

> > > +       u8 divider_flags = CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO |

> > 

> > Why u8?

> 

> __clk_hw_register_divider expects u8 as divider_flags.


Ok.

> 

> > 

> > > +                          CLK_DIVIDER_ROUND_CLOSEST;

> > > +       struct clk_hw *mux = NULL;

> > > +       struct clk_hw *divider = NULL;

> > > +       struct clk_hw *gate = NULL;

> > > +       char *name_mux;

> > > +       char *name_div;

> > > +       int err;

> > > +

> > > +       name_mux = devm_kasprintf(dev, GFP_KERNEL, "%s%s", name, "_mux");

> > > +       if (!name_mux) {

> > > +               err = -ENOMEM;

> > > +               goto err;

> > > +       }

> > > +       mux = clk_hw_register_mux(dev, name_mux, parent_names, num_parents,

> > > +                                 flags, reg, 0, 1, 0, lock);

> > > +       if (IS_ERR(mux)) {

> > > +               err = PTR_ERR(divider);

> > > +               goto err;

> > > +       }

> > > +       clk_hw_set_parent(mux, parent_default);

> > 

> > Can this be done from assigned-clock-parents binding?

> 

> Could be done, if the driver provides at least the PLL and the mux in addition

> to the actual output clocks. Otherwise, it is not possible to reference the

> PLL post divider and the mux from the device tree. I wanted to avoid to expose

> the complexity to the device tree. Would you prefer, if all clocks are

> provided instead of only the output clocks?


It is fine to do this in software too so not a big deal and no need to
expose it from the device.

> 

> > 

> > > +

> > > +static int xvcu_register_clock_provider(struct xvcu_device *xvcu)

> > > +{

> > > +       struct device *dev = xvcu->dev;

> > > +       const char *parent_names[2];

> > > +       struct clk_hw *parent_default;

> > > +       struct clk_hw_onecell_data *data;

> > > +       struct clk_hw **hws;

> > > +       void __iomem *reg_base = xvcu->vcu_slcr_ba;

> > > +

> > > +       data = devm_kzalloc(dev, struct_size(data, hws, CLK_XVCU_NUM_CLOCKS), GFP_KERNEL);

> > > +       if (!data)

> > > +               return -ENOMEM;

> > > +       data->num = CLK_XVCU_NUM_CLOCKS;

> > > +       hws = data->hws;

> > > +

> > > +       xvcu->clk_data = data;

> > > +

> > > +       parent_default = xvcu->pll;

> > > +       parent_names[0] = "dummy";

> > 

> > What is "dummy"?

> 

> According to the register reference [0], the output clocks can be driven by an

> external clock instead of the PLL, but the VCU Product Guide [1] does not

> mention any ports for actually driving the clock. From my understanding of the

> IP core, this is a clock mux which has a not-connected first parent. Maybe

> someone at Xilinx can clarify, what is happening here.

> 

> [0] https://www.xilinx.com/html_docs/registers/ug1087/ug1087-zynq-ultrascale-registers.html

> [1] https://www.xilinx.com/support/documentation-navigation/see-all-versions.html?xlnxproducttypes=IP%20Cores&xlnxipcoresname=v-vcu

> 

> What would be a better way to handle this?

> 

> > 

> > > +       parent_names[1] = clk_hw_get_name(parent_default);

> > 

> > Can we use the new way of specifying clk parents from DT or by using

> > direct pointers instead of using string names? The idea is to get rid of

> > clk_hw_get_name() eventually.

> 

> It should be possible to use the direct pointers, but this really depends on

> how the "dummy" clock is handled.

> 


I think if clk_parent_data is used then we can have the binding list the
external clk as a 'clocks' property and then if it's not present in DT
we will know that it can't ever be a parent. So it hopefully "just
works" if clk_parent_data is used.
Michael Tretter Dec. 21, 2020, 9:18 a.m. UTC | #5
On Tue, 15 Dec 2020 17:09:50 -0800, Stephen Boyd wrote:
> Quoting Michael Tretter (2020-12-15 03:38:09)

> > On Sat, 12 Dec 2020 21:55:34 -0800, Stephen Boyd wrote:

> > > Quoting Michael Tretter (2020-11-15 23:55:28)

> > > > +                          CLK_DIVIDER_ROUND_CLOSEST;

> > > > +       struct clk_hw *mux = NULL;

> > > > +       struct clk_hw *divider = NULL;

> > > > +       struct clk_hw *gate = NULL;

> > > > +       char *name_mux;

> > > > +       char *name_div;

> > > > +       int err;

> > > > +

> > > > +       name_mux = devm_kasprintf(dev, GFP_KERNEL, "%s%s", name, "_mux");

> > > > +       if (!name_mux) {

> > > > +               err = -ENOMEM;

> > > > +               goto err;

> > > > +       }

> > > > +       mux = clk_hw_register_mux(dev, name_mux, parent_names, num_parents,

> > > > +                                 flags, reg, 0, 1, 0, lock);

> > > > +       if (IS_ERR(mux)) {

> > > > +               err = PTR_ERR(divider);

> > > > +               goto err;

> > > > +       }

> > > > +       clk_hw_set_parent(mux, parent_default);

> > > 

> > > Can this be done from assigned-clock-parents binding?

> > 

> > Could be done, if the driver provides at least the PLL and the mux in addition

> > to the actual output clocks. Otherwise, it is not possible to reference the

> > PLL post divider and the mux from the device tree. I wanted to avoid to expose

> > the complexity to the device tree. Would you prefer, if all clocks are

> > provided instead of only the output clocks?

> 

> It is fine to do this in software too so not a big deal and no need to

> expose it from the device.

> 

> > 

> > > 

> > > > +

> > > > +static int xvcu_register_clock_provider(struct xvcu_device *xvcu)

> > > > +{

> > > > +       struct device *dev = xvcu->dev;

> > > > +       const char *parent_names[2];

> > > > +       struct clk_hw *parent_default;

> > > > +       struct clk_hw_onecell_data *data;

> > > > +       struct clk_hw **hws;

> > > > +       void __iomem *reg_base = xvcu->vcu_slcr_ba;

> > > > +

> > > > +       data = devm_kzalloc(dev, struct_size(data, hws, CLK_XVCU_NUM_CLOCKS), GFP_KERNEL);

> > > > +       if (!data)

> > > > +               return -ENOMEM;

> > > > +       data->num = CLK_XVCU_NUM_CLOCKS;

> > > > +       hws = data->hws;

> > > > +

> > > > +       xvcu->clk_data = data;

> > > > +

> > > > +       parent_default = xvcu->pll;

> > > > +       parent_names[0] = "dummy";

> > > 

> > > What is "dummy"?

> > 

> > According to the register reference [0], the output clocks can be driven by an

> > external clock instead of the PLL, but the VCU Product Guide [1] does not

> > mention any ports for actually driving the clock. From my understanding of the

> > IP core, this is a clock mux which has a not-connected first parent. Maybe

> > someone at Xilinx can clarify, what is happening here.

> > 

> > [0] https://www.xilinx.com/html_docs/registers/ug1087/ug1087-zynq-ultrascale-registers.html

> > [1] https://www.xilinx.com/support/documentation-navigation/see-all-versions.html?xlnxproducttypes=IP%20Cores&xlnxipcoresname=v-vcu

> > 

> > What would be a better way to handle this?

> > 

> > > 

> > > > +       parent_names[1] = clk_hw_get_name(parent_default);

> > > 

> > > Can we use the new way of specifying clk parents from DT or by using

> > > direct pointers instead of using string names? The idea is to get rid of

> > > clk_hw_get_name() eventually.

> > 

> > It should be possible to use the direct pointers, but this really depends on

> > how the "dummy" clock is handled.

> > 

> 

> I think if clk_parent_data is used then we can have the binding list the

> external clk as a 'clocks' property and then if it's not present in DT

> we will know that it can't ever be a parent. So it hopefully "just

> works" if clk_parent_data is used.


Thanks. It actually just works. I will send v2.

Michael
diff mbox series

Patch

diff --git a/drivers/soc/xilinx/xlnx_vcu.c b/drivers/soc/xilinx/xlnx_vcu.c
index 725e646aa726..cedc8b7859f7 100644
--- a/drivers/soc/xilinx/xlnx_vcu.c
+++ b/drivers/soc/xilinx/xlnx_vcu.c
@@ -18,6 +18,8 @@ 
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
 
+#include <dt-bindings/clock/xlnx-vcu.h>
+
 /* vcu slcr registers, bitmask and shift */
 #define VCU_PLL_CTRL			0x24
 #define VCU_PLL_CTRL_RESET_MASK		0x01
@@ -50,11 +52,6 @@ 
 #define VCU_ENC_MCU_CTRL		0x34
 #define VCU_DEC_CORE_CTRL		0x38
 #define VCU_DEC_MCU_CTRL		0x3c
-#define VCU_PLL_DIVISOR_MASK		0x3f
-#define VCU_PLL_DIVISOR_SHIFT		4
-#define VCU_SRCSEL_MASK			0x01
-#define VCU_SRCSEL_SHIFT		0
-#define VCU_SRCSEL_PLL			1
 
 #define VCU_PLL_STATUS			0x60
 #define VCU_PLL_STATUS_LOCK_STATUS_MASK	0x01
@@ -82,6 +79,7 @@  struct xvcu_device {
 	struct regmap *logicore_reg_ba;
 	void __iomem *vcu_slcr_ba;
 	struct clk_hw *pll;
+	struct clk_hw_onecell_data *clk_data;
 };
 
 static struct regmap_config vcu_settings_regmap_config = {
@@ -403,7 +401,7 @@  static int xvcu_set_vcu_pll_info(struct xvcu_device *xvcu)
 	u32 refclk, coreclk, mcuclk, inte, deci;
 	u32 divisor_mcu, divisor_core, fvco;
 	u32 clkoutdiv, vcu_pll_ctrl, pll_clk;
-	u32 mod, ctrl;
+	u32 mod;
 	int i;
 	int ret;
 	const struct xvcu_pll_cfg *found = NULL;
@@ -478,37 +476,6 @@  static int xvcu_set_vcu_pll_info(struct xvcu_device *xvcu)
 	dev_dbg(xvcu->dev, "Actual Core clock freq is %uHz\n", coreclk);
 	dev_dbg(xvcu->dev, "Actual Mcu clock freq is %uHz\n", mcuclk);
 
-	/* Set divisor for the core and mcu clock */
-	ctrl = xvcu_read(xvcu->vcu_slcr_ba, VCU_ENC_CORE_CTRL);
-	ctrl &= ~(VCU_PLL_DIVISOR_MASK << VCU_PLL_DIVISOR_SHIFT);
-	ctrl |= (divisor_core & VCU_PLL_DIVISOR_MASK) <<
-		 VCU_PLL_DIVISOR_SHIFT;
-	ctrl &= ~(VCU_SRCSEL_MASK << VCU_SRCSEL_SHIFT);
-	ctrl |= (VCU_SRCSEL_PLL & VCU_SRCSEL_MASK) << VCU_SRCSEL_SHIFT;
-	xvcu_write(xvcu->vcu_slcr_ba, VCU_ENC_CORE_CTRL, ctrl);
-
-	ctrl = xvcu_read(xvcu->vcu_slcr_ba, VCU_DEC_CORE_CTRL);
-	ctrl &= ~(VCU_PLL_DIVISOR_MASK << VCU_PLL_DIVISOR_SHIFT);
-	ctrl |= (divisor_core & VCU_PLL_DIVISOR_MASK) <<
-		 VCU_PLL_DIVISOR_SHIFT;
-	ctrl &= ~(VCU_SRCSEL_MASK << VCU_SRCSEL_SHIFT);
-	ctrl |= (VCU_SRCSEL_PLL & VCU_SRCSEL_MASK) << VCU_SRCSEL_SHIFT;
-	xvcu_write(xvcu->vcu_slcr_ba, VCU_DEC_CORE_CTRL, ctrl);
-
-	ctrl = xvcu_read(xvcu->vcu_slcr_ba, VCU_ENC_MCU_CTRL);
-	ctrl &= ~(VCU_PLL_DIVISOR_MASK << VCU_PLL_DIVISOR_SHIFT);
-	ctrl |= (divisor_mcu & VCU_PLL_DIVISOR_MASK) << VCU_PLL_DIVISOR_SHIFT;
-	ctrl &= ~(VCU_SRCSEL_MASK << VCU_SRCSEL_SHIFT);
-	ctrl |= (VCU_SRCSEL_PLL & VCU_SRCSEL_MASK) << VCU_SRCSEL_SHIFT;
-	xvcu_write(xvcu->vcu_slcr_ba, VCU_ENC_MCU_CTRL, ctrl);
-
-	ctrl = xvcu_read(xvcu->vcu_slcr_ba, VCU_DEC_MCU_CTRL);
-	ctrl &= ~(VCU_PLL_DIVISOR_MASK << VCU_PLL_DIVISOR_SHIFT);
-	ctrl |= (divisor_mcu & VCU_PLL_DIVISOR_MASK) << VCU_PLL_DIVISOR_SHIFT;
-	ctrl &= ~(VCU_SRCSEL_MASK << VCU_SRCSEL_SHIFT);
-	ctrl |= (VCU_SRCSEL_PLL & VCU_SRCSEL_MASK) << VCU_SRCSEL_SHIFT;
-	xvcu_write(xvcu->vcu_slcr_ba, VCU_DEC_MCU_CTRL, ctrl);
-
 	ret = xvcu_pll_set_rate(xvcu, fvco, refclk);
 	if (ret)
 		return ret;
@@ -545,6 +512,146 @@  static int xvcu_set_pll(struct xvcu_device *xvcu)
 	return xvcu_pll_enable(xvcu);
 }
 
+static struct clk_hw *xvcu_clk_hw_register_leaf(struct device *dev,
+						const char *name,
+						const char * const *parent_names,
+						u8 num_parents,
+						struct clk_hw *parent_default,
+						void __iomem *reg,
+						spinlock_t *lock)
+{
+	unsigned long flags = CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT;
+	u8 divider_flags = CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO |
+			   CLK_DIVIDER_ROUND_CLOSEST;
+	struct clk_hw *mux = NULL;
+	struct clk_hw *divider = NULL;
+	struct clk_hw *gate = NULL;
+	char *name_mux;
+	char *name_div;
+	int err;
+
+	name_mux = devm_kasprintf(dev, GFP_KERNEL, "%s%s", name, "_mux");
+	if (!name_mux) {
+		err = -ENOMEM;
+		goto err;
+	}
+	mux = clk_hw_register_mux(dev, name_mux, parent_names, num_parents,
+				  flags, reg, 0, 1, 0, lock);
+	if (IS_ERR(mux)) {
+		err = PTR_ERR(divider);
+		goto err;
+	}
+	clk_hw_set_parent(mux, parent_default);
+
+	name_div = devm_kasprintf(dev, GFP_KERNEL, "%s%s", name, "_div");
+	if (!name_div) {
+		err = -ENOMEM;
+		goto err;
+	}
+	divider = clk_hw_register_divider_parent_hw(dev, name_div, mux, flags,
+						    reg, 4, 6, divider_flags,
+						    lock);
+	if (IS_ERR(divider)) {
+		err = PTR_ERR(divider);
+		goto err;
+	}
+
+	gate = clk_hw_register_gate_parent_hw(dev, name, divider,
+					      CLK_SET_RATE_PARENT, reg, 12, 0,
+					      lock);
+	if (IS_ERR(gate)) {
+		err = PTR_ERR(gate);
+		goto err;
+	}
+
+	return gate;
+
+err:
+	if (!IS_ERR_OR_NULL(gate))
+		clk_hw_unregister_gate(gate);
+	if (!IS_ERR_OR_NULL(divider))
+		clk_hw_unregister_divider(divider);
+	if (!IS_ERR_OR_NULL(mux))
+		clk_hw_unregister_divider(mux);
+
+	return ERR_PTR(err);
+}
+
+static void xvcu_clk_hw_unregister_leaf(struct clk_hw *hw)
+{
+	struct clk_hw *gate = hw;
+	struct clk_hw *divider;
+	struct clk_hw *mux;
+
+	if (!gate)
+		return;
+
+	divider = clk_hw_get_parent(gate);
+	clk_hw_unregister_gate(gate);
+	if (!divider)
+		return;
+
+	mux = clk_hw_get_parent(divider);
+	clk_hw_unregister_mux(mux);
+	if (!divider)
+		return;
+
+	clk_hw_unregister_divider(divider);
+}
+
+static DEFINE_SPINLOCK(venc_core_lock);
+static DEFINE_SPINLOCK(venc_mcu_lock);
+
+static int xvcu_register_clock_provider(struct xvcu_device *xvcu)
+{
+	struct device *dev = xvcu->dev;
+	const char *parent_names[2];
+	struct clk_hw *parent_default;
+	struct clk_hw_onecell_data *data;
+	struct clk_hw **hws;
+	void __iomem *reg_base = xvcu->vcu_slcr_ba;
+
+	data = devm_kzalloc(dev, struct_size(data, hws, CLK_XVCU_NUM_CLOCKS), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	data->num = CLK_XVCU_NUM_CLOCKS;
+	hws = data->hws;
+
+	xvcu->clk_data = data;
+
+	parent_default = xvcu->pll;
+	parent_names[0] = "dummy";
+	parent_names[1] = clk_hw_get_name(parent_default);
+
+	hws[CLK_XVCU_ENC_CORE] =
+		xvcu_clk_hw_register_leaf(dev, "venc_core_clk",
+					  parent_names,
+					  ARRAY_SIZE(parent_names),
+					  parent_default,
+					  reg_base + VCU_ENC_CORE_CTRL,
+					  &venc_core_lock);
+	hws[CLK_XVCU_ENC_MCU] =
+		xvcu_clk_hw_register_leaf(dev, "venc_mcu_clk",
+					  parent_names,
+					  ARRAY_SIZE(parent_names),
+					  parent_default,
+					  reg_base + VCU_ENC_MCU_CTRL,
+					  &venc_mcu_lock);
+
+	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, data);
+}
+
+static void xvcu_unregister_clock_provider(struct xvcu_device *xvcu)
+{
+	struct clk_hw_onecell_data *data = xvcu->clk_data;
+	struct clk_hw **hws = data->hws;
+
+	if (!IS_ERR_OR_NULL(hws[CLK_XVCU_ENC_MCU]))
+		xvcu_clk_hw_unregister_leaf(hws[CLK_XVCU_ENC_MCU]);
+	if (!IS_ERR_OR_NULL(hws[CLK_XVCU_ENC_CORE]))
+		xvcu_clk_hw_unregister_leaf(hws[CLK_XVCU_ENC_CORE]);
+}
+
 /**
  * xvcu_probe - Probe existence of the logicoreIP
  *			and initialize PLL
@@ -639,10 +746,18 @@  static int xvcu_probe(struct platform_device *pdev)
 		goto error_pll_ref;
 	}
 
+	ret = xvcu_register_clock_provider(xvcu);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register clock provider\n");
+		goto error_clk_provider;
+	}
+
 	dev_set_drvdata(&pdev->dev, xvcu);
 
 	return 0;
 
+error_clk_provider:
+	xvcu_unregister_clock_provider(xvcu);
 error_pll_ref:
 	clk_disable_unprepare(xvcu->aclk);
 	return ret;
@@ -664,6 +779,8 @@  static int xvcu_remove(struct platform_device *pdev)
 	if (!xvcu)
 		return -ENODEV;
 
+	xvcu_unregister_clock_provider(xvcu);
+
 	/* Add the the Gasket isolation and put the VCU in reset. */
 	regmap_write(xvcu->logicore_reg_ba, VCU_GASKET_INIT, 0);