Thread (7 messages) 7 messages, 2 authors, 2014-12-04

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

From: Krzysztof Kozlowski <hidden>
Date: 2014-12-04 12:03:57
Also in: linux-samsung-soc, lkml

On czw, 2014-12-04 at 12:49 +0100, Sylwester Nawrocki wrote:
On 04/12/14 11:47, Krzysztof Kozlowski wrote:
quoted
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 <redacted>
Reported-by: Javier Martinez Canillas <redacted>
Reported-by: Kevin Hilman <khilman@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...
quoted
+/*
+ * 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_*().
quoted
+	gate->lock = &lock;
+	gate->hw.init = &init;
+
+	clk = clk_register(dev, &gate->hw);
+
+	return clk;
Just do
	return clk_register(dev, &gate->hw);
OK.
quoted
+}
+
+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.
quoted
+
+	ret = clk_divider_ops.recalc_rate(hw, parent_rate);
+
+	pll_clk_disable();
+
+	return ret;
+}
+
quoted
+/*
+ * 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.
quoted
+	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.
quoted
+	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.
quoted
+	/* register the clock */
That comment has really no value, I'd remove it.
OK.
quoted
+	clk = clk_register(dev, &div->hw);
+
+	return clk;
Please change it to:

	return clk_register(dev, &div->hw);
OK.
quoted
+}
+
+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.
quoted
+	}
+
+	parent = clk_mux_ops.get_parent(hw);
+
+	pll_clk_disable();
+
+	return parent;
+}
quoted
+/*
+ * 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.
quoted
+	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.
quoted
+	mux->lock = &lock;
+	mux->table = NULL;
Ditto.
OK.
quoted
+	mux->hw.init = &init;
+
+	clk = clk_register(dev, &mux->hw);
+
+	return clk;
	return clk_register(dev, &mux->hw);
OK.
quoted
+}
+
 /* 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
quoted
+	epll = ERR_PTR(-ENODEV);
 
 	clk_table = devm_kzalloc(&pdev->dev,
 				sizeof(struct clk *) * EXYNOS_AUDSS_MAX_CLKS,
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help