Re: [PATCH v3 1/3] clk: samsung: Fix clock disable failure because domain being gated

From: Sylwester Nawrocki
Date: Thu Dec 04 2014 - 06:49:56 EST


On 04/12/14 11:47, Krzysztof Kozlowski wrote:
> Audio subsystem clocks are located in separate block. If clock for this
> block (from main clock domain) 'mau_epll' is gated then any read or
> write to audss registers will block.
>
> This was observed on Exynos 5420 platforms (Arndale Octa and Peach
> Pi/Pit) after introducing runtime PM to pl330 DMA driver. After that
> commit the 'mau_epll' was gated, because the "amba" clock was disabled
> and there were no more users of mau_epll. The system hang on disabling
> unused clocks from audss block.
>
> Unfortunately the 'mau_epll' clock is not parent of some of audss clocks.
>
> Whenever system wants to operate on audss clocks it has to enable epll
> clock. The solution reuses common clk-gate/divider/mux code and duplicates
> clk_register_*() functions.
>
> Additionally this patch fixes memory leak of clock gate/divider/mux
> structures. The leak exists in generic clk_register_*() functions. Patch
> replaces them with custom code with managed allocation.
>
> Signed-off-by: Krzysztof Kozlowski <k.kozlowski@xxxxxxxxxxx>
> Reported-by: Javier Martinez Canillas <javier.martinez@xxxxxxxxxxxxxxx>
> Reported-by: Kevin Hilman <khilman@xxxxxxxxxx>
> ---
> drivers/clk/samsung/clk-exynos-audss.c | 357 +++++++++++++++++++++++++++++----
> 1 file changed, 321 insertions(+), 36 deletions(-)

In general I'm not happy with this as we are more than doubling LOC in this
driver. I don't have a better idea though ATM on how to address the issue for
v3.19. I suspect we will need a regmap based clocks for some Exynos clocks
controllers in near future and then we could refactor this driver by using
the common code.

A few style comments below...

> +/*
> + * A simplified copy of clk-gate.c:clk_register_gate() to mimic
> + * clk-gate behavior while using customized ops.
> + */
> +static struct clk *audss_clk_register_gate(struct device *dev, const char *name,
> + const char *parent_name, unsigned long flags,
> + void __iomem *reg, u8 bit_idx)
> +{
> + struct clk_gate *gate;
> + struct clk *clk;
> + struct clk_init_data init;
> +
> + /* allocate the gate */
> + gate = devm_kzalloc(dev, sizeof(struct clk_gate), GFP_KERNEL);
> + if (!gate)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &audss_clk_gate_ops;
> + init.flags = flags | CLK_IS_BASIC;
> + init.parent_names = (parent_name ? &parent_name : NULL);
> + init.num_parents = (parent_name ? 1 : 0);
> +
> + /* struct clk_gate assignments */
> + gate->reg = reg;
> + gate->bit_idx = bit_idx;
> + gate->flags = 0;

The assignment here redundant, since the data structure has been allocated
with kzalloc().

> + gate->lock = &lock;
> + gate->hw.init = &init;
> +
> + clk = clk_register(dev, &gate->hw);
> +
> + return clk;

Just do
return clk_register(dev, &gate->hw);

> +}
> +
> +static unsigned long audss_clk_divider_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + unsigned long ret;
> +
> + ret = pll_clk_enable();
> + if (ret) {
> + WARN(ret, "Could not enable epll clock\n");
> + return parent_rate;
> + }

How about moving the error log to pll_clk_enable() and making this

ret = pll_clk_enable();
if (ret < 0)
return parent_rate;
?
> +
> + ret = clk_divider_ops.recalc_rate(hw, parent_rate);
> +
> + pll_clk_disable();
> +
> + return ret;
> +}
> +

> +/*
> + * A simplified copy of clk-divider.c:clk_register_divider() to mimic
> + * clk-divider behavior while using customized ops.
> + */
> +static struct clk *audss_clk_register_divider(struct device *dev,
> + const char *name, const char *parent_name, unsigned long flags,
> + void __iomem *reg, u8 shift, u8 width)
> +{
> + struct clk_divider *div;
> + struct clk *clk;
> + struct clk_init_data init;
> +
> + /* allocate the divider */

Not sure if this comment helps in anything.

> + div = devm_kzalloc(dev, sizeof(struct clk_divider), GFP_KERNEL);
> + if (!div)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &audss_clk_divider_ops;
> + init.flags = flags | CLK_IS_BASIC;
> + init.parent_names = (parent_name ? &parent_name : NULL);
> + init.num_parents = (parent_name ? 1 : 0);
> +
> + /* struct clk_divider assignments */
> + div->reg = reg;
> + div->shift = shift;
> + div->width = width;
> + div->flags = 0;

Redundant assignment.

> + div->lock = &lock;
> + div->hw.init = &init;
> + div->table = NULL;

This could be safely removed as well, but up to you.

> + /* register the clock */

That comment has really no value, I'd remove it.

> + clk = clk_register(dev, &div->hw);
> +
> + return clk;

Please change it to:

return clk_register(dev, &div->hw);
> +}
> +
> +static u8 audss_clk_mux_get_parent(struct clk_hw *hw)
> +{
> + u8 parent;
> + int ret;
> +
> + ret = pll_clk_enable();
> + if (ret) {
> + WARN(ret, "Could not enable epll clock\n");
> + return -EINVAL; /* Just like clk_mux_get_parent() */

Do we need to overwrite the error code ? The error log could be moved
inside pll_clk_enable() and it all would become:

ret = pll_clk_enable();
if (ret < 0)
return ret;

> + }
> +
> + parent = clk_mux_ops.get_parent(hw);
> +
> + pll_clk_disable();
> +
> + return parent;
> +}

> +/*
> + * A simplified copy of clk-mux.c:clk_register_mux_table() to mimic
> + * clk-mux behavior while using customized ops.
> + */
> +static struct clk *audss_clk_register_mux(struct device *dev, const char *name,
> + const char **parent_names, u8 num_parents, unsigned long flags,
> + void __iomem *reg, u8 shift, u8 width)
> +{
> + struct clk_mux *mux;
> + struct clk *clk;
> + struct clk_init_data init;
> + u32 mask = BIT(width) - 1;
> +
> + /* allocate the mux */

I'd remove this comment.

> + mux = devm_kzalloc(dev, sizeof(struct clk_mux), GFP_KERNEL);
> + if (!mux)
> + return ERR_PTR(-ENOMEM);
> +
> + init.name = name;
> + init.ops = &audss_clk_mux_ops;
> + init.flags = flags | CLK_IS_BASIC;
> + init.parent_names = parent_names;
> + init.num_parents = num_parents;
> +
> + /* struct clk_mux assignments */
> + mux->reg = reg;
> + mux->shift = shift;
> + mux->mask = mask;
> + mux->flags = 0;

Redundant assignment.

> + mux->lock = &lock;
> + mux->table = NULL;

Ditto.

> + mux->hw.init = &init;
> +
> + clk = clk_register(dev, &mux->hw);
> +
> + return clk;

return clk_register(dev, &mux->hw);
> +}
> +
> /* register exynos_audss clocks */
> static int exynos_audss_clk_probe(struct platform_device *pdev)
> {
> @@ -98,6 +364,8 @@ static int exynos_audss_clk_probe(struct platform_device *pdev)
> dev_err(&pdev->dev, "failed to map audss registers\n");
> return PTR_ERR(reg_base);
> }
> + /* epll don't have to be enabled for boards != Exynos5420 */

s/!=/other than/ ?
s/epll/EPLL ?

> + epll = ERR_PTR(-ENODEV);
>
> clk_table = devm_kzalloc(&pdev->dev,
> sizeof(struct clk *) * EXYNOS_AUDSS_MAX_CLKS,

--
Thanks,
Sylwester
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/