diff mbox series

[v4,1/2] clk: samsung: gs101: allow earlycon to work unconditionally

Message ID 20240712-gs101-non-essential-clocks-2-v4-1-310aee0de46e@linaro.org
State New
Headers show
Series gs101 oriole: UART clock fixes | expand

Commit Message

André Draszik July 12, 2024, 5:09 p.m. UTC
earlycon depends on the bootloader setup UART clocks being retained.
This patch adds some logic to detect these clocks if earlycon is
enabled, to bump their usage count during init and release them again
at the end of init.

This helps with cases where the UART clocks (or their parents) get
disabled during loading of other drivers (e.g. i2c) causing earlycon to
stop to work sometime into the boot, halting the whole system.

The general idea is based on similar code in the i.MX clock driver, but
since our clocks are coming from various different clock units, we have
to run this code multiple times until all required UART clocks have
probed.

Signed-off-by: André Draszik <andre.draszik@linaro.org>
---
 drivers/clk/samsung/clk-gs101.c | 100 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 100 insertions(+)

Comments

Stephen Boyd July 18, 2024, 8:46 p.m. UTC | #1
Quoting André Draszik (2024-07-12 10:09:43)
> diff --git a/drivers/clk/samsung/clk-gs101.c b/drivers/clk/samsung/clk-gs101.c
> index 85098c61c15e..429690757923 100644
> --- a/drivers/clk/samsung/clk-gs101.c
> +++ b/drivers/clk/samsung/clk-gs101.c
> @@ -4381,6 +4386,99 @@ static const struct samsung_cmu_info peric1_cmu_info __initconst = {
>  
>  /* ---- platform_driver ----------------------------------------------------- */
>  
> +static struct {
> +       struct mutex lock;
> +
> +       bool bump_refs;
> +
> +       struct clk **clks;
> +       size_t n_clks;
> +} gs101_stdout_clks __initdata = {
> +       .lock = __MUTEX_INITIALIZER(gs101_stdout_clks.lock),
> +};
> +
> +static int __init gs101_keep_uart_clocks_param(char *str)
> +{
> +       gs101_stdout_clks.bump_refs = true;
> +       return 0;
> +}
> +early_param("earlycon", gs101_keep_uart_clocks_param);
> +
> +static void __init gs101_bump_uart_clock_references(void)
> +{
> +       size_t n_clks;
> +
> +       /* We only support device trees - do nothing if not available. */
> +       if (!IS_ENABLED(CONFIG_OF))
> +               return;
> +
> +       n_clks = of_clk_get_parent_count(of_stdout);
> +       if (!n_clks || !of_stdout)
> +               return;

I don't see anything in here that's driver specific. Please move this to
the clk framework, hook something like of_clk_add_provider() similar to
how of_clk_set_defaults(), and have it look at the of_stdout node for
'clocks' to munge the clks in the core framework for the serial console.
André Draszik July 25, 2024, 7:14 a.m. UTC | #2
Hi Stephen,

On Thu, 2024-07-18 at 13:46 -0700, Stephen Boyd wrote:
> I don't see anything in here that's driver specific. Please move this to
> the clk framework, hook something like of_clk_add_provider() similar to
> how of_clk_set_defaults(), and have it look at the of_stdout node for
> 'clocks' to munge the clks in the core framework for the serial console.

That makes sense, I'll have a stab at that instead.

Cheers,
Andre'
diff mbox series

Patch

diff --git a/drivers/clk/samsung/clk-gs101.c b/drivers/clk/samsung/clk-gs101.c
index 85098c61c15e..429690757923 100644
--- a/drivers/clk/samsung/clk-gs101.c
+++ b/drivers/clk/samsung/clk-gs101.c
@@ -8,8 +8,13 @@ 
 
 #include <linux/clk.h>
 #include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
 #include <linux/of.h>
+#include <linux/of_clk.h>
 #include <linux/platform_device.h>
+#include <linux/slab.h>
 
 #include <dt-bindings/clock/google,gs101.h>
 
@@ -4381,6 +4386,99 @@  static const struct samsung_cmu_info peric1_cmu_info __initconst = {
 
 /* ---- platform_driver ----------------------------------------------------- */
 
+static struct {
+	struct mutex lock;
+
+	bool bump_refs;
+
+	struct clk **clks;
+	size_t n_clks;
+} gs101_stdout_clks __initdata = {
+	.lock = __MUTEX_INITIALIZER(gs101_stdout_clks.lock),
+};
+
+static int __init gs101_keep_uart_clocks_param(char *str)
+{
+	gs101_stdout_clks.bump_refs = true;
+	return 0;
+}
+early_param("earlycon", gs101_keep_uart_clocks_param);
+
+static void __init gs101_bump_uart_clock_references(void)
+{
+	size_t n_clks;
+
+	/* We only support device trees - do nothing if not available. */
+	if (!IS_ENABLED(CONFIG_OF))
+		return;
+
+	n_clks = of_clk_get_parent_count(of_stdout);
+	if (!n_clks || !of_stdout)
+		return;
+
+	mutex_lock(&gs101_stdout_clks.lock);
+
+	/*
+	 * We only need to run this code if required to do so, and if we have
+	 * not succeeded previously, which will be the case if not all required
+	 * clocks were ready yet during previous attempts.
+	 */
+	if (!gs101_stdout_clks.bump_refs)
+		goto out_unlock;
+
+	if (!gs101_stdout_clks.clks) {
+		gs101_stdout_clks.n_clks = n_clks;
+
+		gs101_stdout_clks.clks = kcalloc(gs101_stdout_clks.n_clks,
+					       sizeof(*gs101_stdout_clks.clks),
+					       GFP_KERNEL);
+		if (!gs101_stdout_clks.clks)
+			goto out_unlock;
+	}
+
+	/* assume that this time we'll be able to grab all required clocks */
+	gs101_stdout_clks.bump_refs = false;
+	for (size_t i = 0; i < n_clks; ++i) {
+		struct clk *clk;
+
+		/* we might have grabbed this clock in a previous attempt */
+		if (gs101_stdout_clks.clks[i])
+			continue;
+
+		clk = of_clk_get(of_stdout, i);
+		if (IS_ERR(clk)) {
+			/*
+			 * clock might not have probed yet so we'll have to try
+			 * again next time
+			 */
+			gs101_stdout_clks.bump_refs = true;
+			continue;
+		}
+
+		if (clk_prepare_enable(clk)) {
+			clk_put(clk);
+			continue;
+		}
+		gs101_stdout_clks.clks[i] = clk;
+	}
+
+out_unlock:
+	mutex_unlock(&gs101_stdout_clks.lock);
+}
+
+static int __init gs101_drop_extra_uart_clock_references(void)
+{
+	for (size_t i = 0; i < gs101_stdout_clks.n_clks; ++i) {
+		clk_disable_unprepare(gs101_stdout_clks.clks[i]);
+		clk_put(gs101_stdout_clks.clks[i]);
+	}
+
+	kfree(gs101_stdout_clks.clks);
+
+	return 0;
+}
+late_initcall_sync(gs101_drop_extra_uart_clock_references);
+
 static int __init gs101_cmu_probe(struct platform_device *pdev)
 {
 	const struct samsung_cmu_info *info;
@@ -4389,6 +4487,8 @@  static int __init gs101_cmu_probe(struct platform_device *pdev)
 	info = of_device_get_match_data(dev);
 	exynos_arm64_register_cmu(dev, dev->of_node, info);
 
+	gs101_bump_uart_clock_references();
+
 	return 0;
 }