[PATCH v3 1/3] clk: samsung: Fix clock disable failure because domain being gated
Krzysztof Kozlowski
k.kozlowski at samsung.com
Thu Dec 4 04:03:51 PST 2014
On czw, 2014-12-04 at 12:49 +0100, Sylwester Nawrocki wrote:
> 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 at samsung.com>
> > Reported-by: Javier Martinez Canillas <javier.martinez at collabora.co.uk>
> > Reported-by: Kevin Hilman <khilman at kernel.org>
> > ---
> > 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.
The regmap-mmio could solve it in a cleaner way but that would be much
bigger task.
>
> 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().
Sure. Most of these minor issues came from copying generic
clk_register_*().
>
> > + gate->lock = &lock;
> > + gate->hw.init = &init;
> > +
> > + clk = clk_register(dev, &gate->hw);
> > +
> > + return clk;
>
> Just do
> return clk_register(dev, &gate->hw);
OK.
>
> > +}
> > +
> > +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;
> ?
In other uses of pll_clk_enable(), the error is returned so I think
there is no need to print a warning. This is different because here
error cannot be returned.
> > +
> > + 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.
I'll remove it.
>
> > + 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.
OK.
>
> > + div->lock = &lock;
> > + div->hw.init = &init;
> > + div->table = NULL;
>
> This could be safely removed as well, but up to you.
I'll remove it.
>
> > + /* register the clock */
>
> That comment has really no value, I'd remove it.
OK.
>
> > + clk = clk_register(dev, &div->hw);
> > +
> > + return clk;
>
> Please change it to:
>
> return clk_register(dev, &div->hw);
OK.
> > +}
> > +
> > +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;
>
Similar to previous case - the warning is here because return value does
not accept error condition. I'll re-use existing error code but if you
don't mind I think warning should stay here.
> > + }
> > +
> > + 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.
OK.
>
> > + 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.
OK.
>
> > + mux->lock = &lock;
> > + mux->table = NULL;
>
> Ditto.
OK.
>
> > + mux->hw.init = &init;
> > +
> > + clk = clk_register(dev, &mux->hw);
> > +
> > + return clk;
>
> return clk_register(dev, &mux->hw);
OK.
> > +}
> > +
> > /* 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 ?
OK.
Thanks for feedback!
Best regards,
Krzysztof
>
> > + epll = ERR_PTR(-ENODEV);
> >
> > clk_table = devm_kzalloc(&pdev->dev,
> > sizeof(struct clk *) * EXYNOS_AUDSS_MAX_CLKS,
>
More information about the linux-arm-kernel
mailing list