* [PATCH RESEND v8 10/10] dt-bindings: watchdog: renesas,wdt: Document RZ/G3S support
From: Claudiu @ 2024-04-10 13:40 UTC (permalink / raw)
To: wim, linux, robh, krzk+dt, conor+dt, p.zabel, geert+renesas,
magnus.damm, biju.das.jz
Cc: linux-watchdog, devicetree, linux-kernel, linux-renesas-soc,
claudiu.beznea, Claudiu Beznea, Conor Dooley
In-Reply-To: <20240410134044.2138310-1-claudiu.beznea.uj@bp.renesas.com>
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Document the support for the watchdog IP available on RZ/G3S SoC. The
watchdog IP available on RZ/G3S SoC is identical to the one found on
RZ/G2L SoC.
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
---
Changes in v8:
- none
Changes in v7:
- none
Changes in v6:
- none
Changes in v5:
- none
Changes in v4:
- none
Changes in v3:
- re-arranged the tags as my b4 am/shazam placed previously the
Ab, Rb tags before the author's Sob
Changes in v2:
- collected tags
- s/G2UL/G2L in patch description
Documentation/devicetree/bindings/watchdog/renesas,wdt.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/watchdog/renesas,wdt.yaml b/Documentation/devicetree/bindings/watchdog/renesas,wdt.yaml
index ffb17add491a..eba454d1680f 100644
--- a/Documentation/devicetree/bindings/watchdog/renesas,wdt.yaml
+++ b/Documentation/devicetree/bindings/watchdog/renesas,wdt.yaml
@@ -29,6 +29,7 @@ properties:
- renesas,r9a07g043-wdt # RZ/G2UL and RZ/Five
- renesas,r9a07g044-wdt # RZ/G2{L,LC}
- renesas,r9a07g054-wdt # RZ/V2L
+ - renesas,r9a08g045-wdt # RZ/G3S
- const: renesas,rzg2l-wdt
- items:
--
2.39.2
^ permalink raw reply related
* [PATCH RESEND v8 09/10] watchdog: rzg2l_wdt: Power on the PM domain in rzg2l_wdt_restart()
From: Claudiu @ 2024-04-10 13:40 UTC (permalink / raw)
To: wim, linux, robh, krzk+dt, conor+dt, p.zabel, geert+renesas,
magnus.damm, biju.das.jz
Cc: linux-watchdog, devicetree, linux-kernel, linux-renesas-soc,
claudiu.beznea, Claudiu Beznea
In-Reply-To: <20240410134044.2138310-1-claudiu.beznea.uj@bp.renesas.com>
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
The rzg2l_wdt_restart() is called from atomic context. Calling
pm_runtime_{get_sync, resume_and_get}() or any other runtime PM resume
APIs is not an option as it may lead to issues as described in commit
e4cf89596c1f ("watchdog: rzg2l_wdt: Fix 'BUG: Invalid wait context'")
that removed the pm_runtime_get_sync() and used directly the
clk_prepare_enable() APIs.
Starting with RZ/G3S the watchdog could be part of its own software
controlled power domain (see the initial implementation in Link section).
In case the watchdog is not used the power domain is off and accessing
watchdog registers leads to aborts.
To solve this the patch powers on the power domain using
dev_pm_genpd_resume() API before enabling its clock. This is not
sleeping or taking any other locks as the power domain will not be
registered with GENPD_FLAG_IRQ_SAFE flags.
Link: https://lore.kernel.org/all/20240208124300.2740313-1-claudiu.beznea.uj@bp.renesas.com
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v8:
- none, this patch is new
drivers/watchdog/rzg2l_wdt.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/watchdog/rzg2l_wdt.c b/drivers/watchdog/rzg2l_wdt.c
index c8c20cfb97a3..98e5e9914a5d 100644
--- a/drivers/watchdog/rzg2l_wdt.c
+++ b/drivers/watchdog/rzg2l_wdt.c
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/units.h>
@@ -164,6 +165,17 @@ static int rzg2l_wdt_restart(struct watchdog_device *wdev,
struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
int ret;
+ /*
+ * The device may be part of a power domain that is currently
+ * powered off. We need to power it up before accessing registers.
+ * We don't undo the dev_pm_genpd_resume() as the device need to
+ * be up for the reboot to happen. Also, as we are in atomic context
+ * here there is no need to increment PM runtime usage counter
+ * (to make sure pm_runtime_active() doesn't return wrong code).
+ */
+ if (!pm_runtime_active(wdev->parent))
+ dev_pm_genpd_resume(wdev->parent);
+
clk_prepare_enable(priv->pclk);
clk_prepare_enable(priv->osc_clk);
--
2.39.2
^ permalink raw reply related
* [PATCH RESEND v8 08/10] watchdog: rzg2l_wdt: Add suspend/resume support
From: Claudiu @ 2024-04-10 13:40 UTC (permalink / raw)
To: wim, linux, robh, krzk+dt, conor+dt, p.zabel, geert+renesas,
magnus.damm, biju.das.jz
Cc: linux-watchdog, devicetree, linux-kernel, linux-renesas-soc,
claudiu.beznea, Claudiu Beznea
In-Reply-To: <20240410134044.2138310-1-claudiu.beznea.uj@bp.renesas.com>
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
The RZ/G3S supports deep sleep states where power to most of the IP blocks
is cut off. To ensure proper working of the watchdog when resuming from
such states, the suspend function is stopping the watchdog and the resume
function is starting it. There is no need to configure the watchdog
in case the watchdog was stopped prior to starting suspend.
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v8:
- none
Changes in v7:
- none
Changes in v6:
- none
Changes in v5:
- none
Changes in v4:
- none
Changes in v3:
- none
Changes in v2:
- remove the usage of pm_ptr()
drivers/watchdog/rzg2l_wdt.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/drivers/watchdog/rzg2l_wdt.c b/drivers/watchdog/rzg2l_wdt.c
index 42f1d5d6f07e..c8c20cfb97a3 100644
--- a/drivers/watchdog/rzg2l_wdt.c
+++ b/drivers/watchdog/rzg2l_wdt.c
@@ -284,6 +284,7 @@ static int rzg2l_wdt_probe(struct platform_device *pdev)
priv->wdev.timeout = WDT_DEFAULT_TIMEOUT;
watchdog_set_drvdata(&priv->wdev, priv);
+ dev_set_drvdata(dev, priv);
ret = devm_add_action_or_reset(&pdev->dev, rzg2l_wdt_pm_disable, &priv->wdev);
if (ret)
return ret;
@@ -305,10 +306,35 @@ static const struct of_device_id rzg2l_wdt_ids[] = {
};
MODULE_DEVICE_TABLE(of, rzg2l_wdt_ids);
+static int rzg2l_wdt_suspend_late(struct device *dev)
+{
+ struct rzg2l_wdt_priv *priv = dev_get_drvdata(dev);
+
+ if (!watchdog_active(&priv->wdev))
+ return 0;
+
+ return rzg2l_wdt_stop(&priv->wdev);
+}
+
+static int rzg2l_wdt_resume_early(struct device *dev)
+{
+ struct rzg2l_wdt_priv *priv = dev_get_drvdata(dev);
+
+ if (!watchdog_active(&priv->wdev))
+ return 0;
+
+ return rzg2l_wdt_start(&priv->wdev);
+}
+
+static const struct dev_pm_ops rzg2l_wdt_pm_ops = {
+ LATE_SYSTEM_SLEEP_PM_OPS(rzg2l_wdt_suspend_late, rzg2l_wdt_resume_early)
+};
+
static struct platform_driver rzg2l_wdt_driver = {
.driver = {
.name = "rzg2l_wdt",
.of_match_table = rzg2l_wdt_ids,
+ .pm = &rzg2l_wdt_pm_ops,
},
.probe = rzg2l_wdt_probe,
};
--
2.39.2
^ permalink raw reply related
* [PATCH RESEND v8 07/10] watchdog: rzg2l_wdt: Rely on the reset driver for doing proper reset
From: Claudiu @ 2024-04-10 13:40 UTC (permalink / raw)
To: wim, linux, robh, krzk+dt, conor+dt, p.zabel, geert+renesas,
magnus.damm, biju.das.jz
Cc: linux-watchdog, devicetree, linux-kernel, linux-renesas-soc,
claudiu.beznea, Claudiu Beznea
In-Reply-To: <20240410134044.2138310-1-claudiu.beznea.uj@bp.renesas.com>
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
The reset driver has been adapted in commit da235d2fac21
("clk: renesas: rzg2l: Check reset monitor registers") to check the reset
monitor bits before declaring reset asserts/de-asserts as
successful/failure operations. With that, there is no need to keep the
reset workaround for RZ/V2M in place in the watchdog driver.
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Reviewed-by: Philipp Zabel <p.zabel@pengutronix.de>
---
Changes in v8:
- none
Changes in v7:
- none
Changes in v6:
- none
Changes in v5:
- none
Changes in v4:
- collected tag
Changes in v3:
- none
Changes in v2:
- none
drivers/watchdog/rzg2l_wdt.c | 39 ++++--------------------------------
1 file changed, 4 insertions(+), 35 deletions(-)
diff --git a/drivers/watchdog/rzg2l_wdt.c b/drivers/watchdog/rzg2l_wdt.c
index 29eb47bcf984..42f1d5d6f07e 100644
--- a/drivers/watchdog/rzg2l_wdt.c
+++ b/drivers/watchdog/rzg2l_wdt.c
@@ -8,7 +8,6 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
-#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -54,35 +53,11 @@ struct rzg2l_wdt_priv {
struct reset_control *rstc;
unsigned long osc_clk_rate;
unsigned long delay;
- unsigned long minimum_assertion_period;
struct clk *pclk;
struct clk *osc_clk;
enum rz_wdt_type devtype;
};
-static int rzg2l_wdt_reset(struct rzg2l_wdt_priv *priv)
-{
- int err, status;
-
- if (priv->devtype == WDT_RZV2M) {
- /* WDT needs TYPE-B reset control */
- err = reset_control_assert(priv->rstc);
- if (err)
- return err;
- ndelay(priv->minimum_assertion_period);
- err = reset_control_deassert(priv->rstc);
- if (err)
- return err;
- err = read_poll_timeout(reset_control_status, status,
- status != 1, 0, 1000, false,
- priv->rstc);
- } else {
- err = reset_control_reset(priv->rstc);
- }
-
- return err;
-}
-
static void rzg2l_wdt_wait_delay(struct rzg2l_wdt_priv *priv)
{
/* delay timer when change the setting register */
@@ -187,13 +162,12 @@ static int rzg2l_wdt_restart(struct watchdog_device *wdev,
unsigned long action, void *data)
{
struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ int ret;
clk_prepare_enable(priv->pclk);
clk_prepare_enable(priv->osc_clk);
if (priv->devtype == WDT_RZG2L) {
- int ret;
-
ret = reset_control_deassert(priv->rstc);
if (ret)
return ret;
@@ -205,7 +179,9 @@ static int rzg2l_wdt_restart(struct watchdog_device *wdev,
rzg2l_wdt_write(priv, PEEN_FORCE, PEEN);
} else {
/* RZ/V2M doesn't have parity error registers */
- rzg2l_wdt_reset(priv);
+ ret = reset_control_reset(priv->rstc);
+ if (ret)
+ return ret;
wdev->timeout = 0;
@@ -297,13 +273,6 @@ static int rzg2l_wdt_probe(struct platform_device *pdev)
priv->devtype = (uintptr_t)of_device_get_match_data(dev);
- if (priv->devtype == WDT_RZV2M) {
- priv->minimum_assertion_period = RZV2M_A_NSEC +
- 3 * F2CYCLE_NSEC(pclk_rate) + 5 *
- max(F2CYCLE_NSEC(priv->osc_clk_rate),
- F2CYCLE_NSEC(pclk_rate));
- }
-
pm_runtime_enable(&pdev->dev);
priv->wdev.info = &rzg2l_wdt_ident;
--
2.39.2
^ permalink raw reply related
* [PATCH RESEND v8 06/10] watchdog: rzg2l_wdt: Remove comparison with zero
From: Claudiu @ 2024-04-10 13:40 UTC (permalink / raw)
To: wim, linux, robh, krzk+dt, conor+dt, p.zabel, geert+renesas,
magnus.damm, biju.das.jz
Cc: linux-watchdog, devicetree, linux-kernel, linux-renesas-soc,
claudiu.beznea, Claudiu Beznea
In-Reply-To: <20240410134044.2138310-1-claudiu.beznea.uj@bp.renesas.com>
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
devm_add_action_or_reset() could return -ENOMEM or zero. Thus, remove
comparison with zero of the returning value to make code simpler.
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v8:
- none
Changes in v7:
- none
Changes in v6:
- none
Changes in v5:
- none
Changes in v4:
- none
Changes in v3:
- none
Changes in v2:
- none
drivers/watchdog/rzg2l_wdt.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/watchdog/rzg2l_wdt.c b/drivers/watchdog/rzg2l_wdt.c
index 93a49fd0c7aa..29eb47bcf984 100644
--- a/drivers/watchdog/rzg2l_wdt.c
+++ b/drivers/watchdog/rzg2l_wdt.c
@@ -316,7 +316,7 @@ static int rzg2l_wdt_probe(struct platform_device *pdev)
watchdog_set_drvdata(&priv->wdev, priv);
ret = devm_add_action_or_reset(&pdev->dev, rzg2l_wdt_pm_disable, &priv->wdev);
- if (ret < 0)
+ if (ret)
return ret;
watchdog_set_nowayout(&priv->wdev, nowayout);
--
2.39.2
^ permalink raw reply related
* [PATCH RESEND v8 05/10] watchdog: rzg2l_wdt: Remove reset de-assert from probe
From: Claudiu @ 2024-04-10 13:40 UTC (permalink / raw)
To: wim, linux, robh, krzk+dt, conor+dt, p.zabel, geert+renesas,
magnus.damm, biju.das.jz
Cc: linux-watchdog, devicetree, linux-kernel, linux-renesas-soc,
claudiu.beznea, Claudiu Beznea
In-Reply-To: <20240410134044.2138310-1-claudiu.beznea.uj@bp.renesas.com>
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
There is no need to de-assert the reset signal on probe as the watchdog
is not used prior executing start. Also, the clocks are not enabled in
probe (pm_runtime_enable() doesn't do that), thus this is another indicator
that the watchdog wasn't used previously like this. Instead, keep the
watchdog hardware in its previous state at probe (by default it is in
reset state), enable it when it is started and move it to reset state
when it is stopped. This saves some extra power when the watchdog is
unused.
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v8:
- none
Changes in v7:
- none
Changes in v6:
- none
Changes in v5:
- update patch title
Changes in v4:
- none
Changes in v3:
- none
Changes in v2:
- none
drivers/watchdog/rzg2l_wdt.c | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/drivers/watchdog/rzg2l_wdt.c b/drivers/watchdog/rzg2l_wdt.c
index 7bce093316c4..93a49fd0c7aa 100644
--- a/drivers/watchdog/rzg2l_wdt.c
+++ b/drivers/watchdog/rzg2l_wdt.c
@@ -129,6 +129,10 @@ static int rzg2l_wdt_start(struct watchdog_device *wdev)
if (ret)
return ret;
+ ret = reset_control_deassert(priv->rstc);
+ if (ret)
+ return ret;
+
/* Initialize time out */
rzg2l_wdt_init_timeout(wdev);
@@ -146,7 +150,9 @@ static int rzg2l_wdt_stop(struct watchdog_device *wdev)
struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
int ret;
- rzg2l_wdt_reset(priv);
+ ret = reset_control_assert(priv->rstc);
+ if (ret)
+ return ret;
ret = pm_runtime_put(wdev->parent);
if (ret < 0)
@@ -186,6 +192,12 @@ static int rzg2l_wdt_restart(struct watchdog_device *wdev,
clk_prepare_enable(priv->osc_clk);
if (priv->devtype == WDT_RZG2L) {
+ int ret;
+
+ ret = reset_control_deassert(priv->rstc);
+ if (ret)
+ return ret;
+
/* Generate Reset (WDTRSTB) Signal on parity error */
rzg2l_wdt_write(priv, 0, PECR);
@@ -236,13 +248,11 @@ static const struct watchdog_ops rzg2l_wdt_ops = {
.restart = rzg2l_wdt_restart,
};
-static void rzg2l_wdt_reset_assert_pm_disable(void *data)
+static void rzg2l_wdt_pm_disable(void *data)
{
struct watchdog_device *wdev = data;
- struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
pm_runtime_disable(wdev->parent);
- reset_control_assert(priv->rstc);
}
static int rzg2l_wdt_probe(struct platform_device *pdev)
@@ -285,10 +295,6 @@ static int rzg2l_wdt_probe(struct platform_device *pdev)
return dev_err_probe(&pdev->dev, PTR_ERR(priv->rstc),
"failed to get cpg reset");
- ret = reset_control_deassert(priv->rstc);
- if (ret)
- return dev_err_probe(dev, ret, "failed to deassert");
-
priv->devtype = (uintptr_t)of_device_get_match_data(dev);
if (priv->devtype == WDT_RZV2M) {
@@ -309,9 +315,7 @@ static int rzg2l_wdt_probe(struct platform_device *pdev)
priv->wdev.timeout = WDT_DEFAULT_TIMEOUT;
watchdog_set_drvdata(&priv->wdev, priv);
- ret = devm_add_action_or_reset(&pdev->dev,
- rzg2l_wdt_reset_assert_pm_disable,
- &priv->wdev);
+ ret = devm_add_action_or_reset(&pdev->dev, rzg2l_wdt_pm_disable, &priv->wdev);
if (ret < 0)
return ret;
--
2.39.2
^ permalink raw reply related
* [PATCH RESEND v8 04/10] watchdog: rzg2l_wdt: Check return status of pm_runtime_put()
From: Claudiu @ 2024-04-10 13:40 UTC (permalink / raw)
To: wim, linux, robh, krzk+dt, conor+dt, p.zabel, geert+renesas,
magnus.damm, biju.das.jz
Cc: linux-watchdog, devicetree, linux-kernel, linux-renesas-soc,
claudiu.beznea, Claudiu Beznea
In-Reply-To: <20240410134044.2138310-1-claudiu.beznea.uj@bp.renesas.com>
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
pm_runtime_put() may return an error code. Check its return status.
Along with it the rzg2l_wdt_set_timeout() function was updated to
propagate the result of rzg2l_wdt_stop() to its caller.
Fixes: 2cbc5cd0b55f ("watchdog: Add Watchdog Timer driver for RZ/G2L")
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v8:
- none
Changes in v7:
- none
Changes in v6:
- none
Changes in v5:
- none
Changes in v4:
- none
Changes in v3:
- none
Changes in v2:
- propagate the return code of rzg2l_wdt_stop() to it's callers
drivers/watchdog/rzg2l_wdt.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/drivers/watchdog/rzg2l_wdt.c b/drivers/watchdog/rzg2l_wdt.c
index d87d4f50180c..7bce093316c4 100644
--- a/drivers/watchdog/rzg2l_wdt.c
+++ b/drivers/watchdog/rzg2l_wdt.c
@@ -144,9 +144,13 @@ static int rzg2l_wdt_start(struct watchdog_device *wdev)
static int rzg2l_wdt_stop(struct watchdog_device *wdev)
{
struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ int ret;
rzg2l_wdt_reset(priv);
- pm_runtime_put(wdev->parent);
+
+ ret = pm_runtime_put(wdev->parent);
+ if (ret < 0)
+ return ret;
return 0;
}
@@ -163,7 +167,10 @@ static int rzg2l_wdt_set_timeout(struct watchdog_device *wdev, unsigned int time
* to reset the module) so that it is updated with new timeout values.
*/
if (watchdog_active(wdev)) {
- rzg2l_wdt_stop(wdev);
+ ret = rzg2l_wdt_stop(wdev);
+ if (ret)
+ return ret;
+
ret = rzg2l_wdt_start(wdev);
}
--
2.39.2
^ permalink raw reply related
* [PATCH RESEND v8 03/10] watchdog: rzg2l_wdt: Use pm_runtime_resume_and_get()
From: Claudiu @ 2024-04-10 13:40 UTC (permalink / raw)
To: wim, linux, robh, krzk+dt, conor+dt, p.zabel, geert+renesas,
magnus.damm, biju.das.jz
Cc: linux-watchdog, devicetree, linux-kernel, linux-renesas-soc,
claudiu.beznea, Claudiu Beznea
In-Reply-To: <20240410134044.2138310-1-claudiu.beznea.uj@bp.renesas.com>
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
pm_runtime_get_sync() may return with error. In case it returns with error
dev->power.usage_count needs to be decremented. pm_runtime_resume_and_get()
takes care of this. Thus use it.
Along with it the rzg2l_wdt_set_timeout() function was updated to
propagate the result of rzg2l_wdt_start() to its caller.
Fixes: 2cbc5cd0b55f ("watchdog: Add Watchdog Timer driver for RZ/G2L")
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v8:
- none
Changes in v7:
- none
Changes in v6:
- none
Changes in v5:
- none
Changes in v4:
- none
Changes in v3:
- none
Changes in v2:
- propagate the return code of rzg2l_wdt_start() to it's callers
drivers/watchdog/rzg2l_wdt.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/drivers/watchdog/rzg2l_wdt.c b/drivers/watchdog/rzg2l_wdt.c
index 1741f98ca67c..d87d4f50180c 100644
--- a/drivers/watchdog/rzg2l_wdt.c
+++ b/drivers/watchdog/rzg2l_wdt.c
@@ -123,8 +123,11 @@ static void rzg2l_wdt_init_timeout(struct watchdog_device *wdev)
static int rzg2l_wdt_start(struct watchdog_device *wdev)
{
struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ int ret;
- pm_runtime_get_sync(wdev->parent);
+ ret = pm_runtime_resume_and_get(wdev->parent);
+ if (ret)
+ return ret;
/* Initialize time out */
rzg2l_wdt_init_timeout(wdev);
@@ -150,6 +153,8 @@ static int rzg2l_wdt_stop(struct watchdog_device *wdev)
static int rzg2l_wdt_set_timeout(struct watchdog_device *wdev, unsigned int timeout)
{
+ int ret = 0;
+
wdev->timeout = timeout;
/*
@@ -159,10 +164,10 @@ static int rzg2l_wdt_set_timeout(struct watchdog_device *wdev, unsigned int time
*/
if (watchdog_active(wdev)) {
rzg2l_wdt_stop(wdev);
- rzg2l_wdt_start(wdev);
+ ret = rzg2l_wdt_start(wdev);
}
- return 0;
+ return ret;
}
static int rzg2l_wdt_restart(struct watchdog_device *wdev,
--
2.39.2
^ permalink raw reply related
* [PATCH RESEND v8 02/10] watchdog: rzg2l_wdt: Make the driver depend on PM
From: Claudiu @ 2024-04-10 13:40 UTC (permalink / raw)
To: wim, linux, robh, krzk+dt, conor+dt, p.zabel, geert+renesas,
magnus.damm, biju.das.jz
Cc: linux-watchdog, devicetree, linux-kernel, linux-renesas-soc,
claudiu.beznea, Claudiu Beznea, kernel test robot
In-Reply-To: <20240410134044.2138310-1-claudiu.beznea.uj@bp.renesas.com>
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
The rzg2l_wdt watchdog driver cannot work w/o CONFIG_PM=y (e.g. the
clocks are enabled though pm_runtime_* specific APIs). To avoid building
a driver that doesn't work make explicit the dependency on CONFIG_PM.
Suggested-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
---
Changes in v8:
- collected tags
Changes in v7:
- updated the dependency to PM || COMPILE_TEST to be able to
compile-test the driver when compiling for a
!(ARCH_RZG2L || ARCH_R9A09G011) platform and CONFIG_PM is disabled
Changes in v6:
- update patch description
- fixed the dependency on COMPILE_TEST previously introduced
Changes in v5:
- updated patch description
- added on a new line the dependency on PM and COMPILE_TEST
Changes in v4:
- s/ARCH_RENESAS/ARCH_RZG2L || ARCH_R9A09G011 due to patch 1/9
Changes in v3:
- make driver depend on PM; with that the "unmet direct dependency"
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/linux-devicetree/202402020445.TOBlFPcS-lkp@intel.com
was also fixed
- adapt commit message
Changes in v2:
- this patch is new
drivers/watchdog/Kconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index e2439967417a..7b66fe06ac85 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -921,6 +921,7 @@ config RENESAS_RZN1WDT
config RENESAS_RZG2LWDT
tristate "Renesas RZ/G2L WDT Watchdog"
depends on ARCH_RZG2L || ARCH_R9A09G011 || COMPILE_TEST
+ depends on PM || COMPILE_TEST
select WATCHDOG_CORE
help
This driver adds watchdog support for the integrated watchdogs in the
--
2.39.2
^ permalink raw reply related
* [PATCH RESEND v8 01/10] watchdog: rzg2l_wdt: Restrict the driver to ARCH_RZG2L and ARCH_R9A09G011
From: Claudiu @ 2024-04-10 13:40 UTC (permalink / raw)
To: wim, linux, robh, krzk+dt, conor+dt, p.zabel, geert+renesas,
magnus.damm, biju.das.jz
Cc: linux-watchdog, devicetree, linux-kernel, linux-renesas-soc,
claudiu.beznea, Claudiu Beznea
In-Reply-To: <20240410134044.2138310-1-claudiu.beznea.uj@bp.renesas.com>
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
The rzg2l_wdt driver is used only by ARCH_RZG2L and ARCH_R9A09G011
micro-architectures of Renesas. Thus, limit it's usage only to these.
Suggested-by: Biju Das <biju.das.jz@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---
Changes in v8:
- none
Changes in v7:
- none
Changes in v6:
- none
Changes in v5:
- none
Changes in v4:
- none; this patch is introduced in v4
drivers/watchdog/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 6bee137cfbe0..e2439967417a 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -920,7 +920,7 @@ config RENESAS_RZN1WDT
config RENESAS_RZG2LWDT
tristate "Renesas RZ/G2L WDT Watchdog"
- depends on ARCH_RENESAS || COMPILE_TEST
+ depends on ARCH_RZG2L || ARCH_R9A09G011 || COMPILE_TEST
select WATCHDOG_CORE
help
This driver adds watchdog support for the integrated watchdogs in the
--
2.39.2
^ permalink raw reply related
* [PATCH RESEND v8 00/10] watchdog: rzg2l_wdt: Add support for RZ/G3S
From: Claudiu @ 2024-04-10 13:40 UTC (permalink / raw)
To: wim, linux, robh, krzk+dt, conor+dt, p.zabel, geert+renesas,
magnus.damm, biju.das.jz
Cc: linux-watchdog, devicetree, linux-kernel, linux-renesas-soc,
claudiu.beznea, Claudiu Beznea
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Hi,
Series adds watchdog support for Renesas RZ/G3S (R9A08G045) SoC.
Patches do the following:
- patch 1/10 makes the driver depend on ARCH_RZG2L || ARCH_R9A09G011
- patch 2/10 makes the driver depend on PM
- patches 3-7/10 adds fixes and cleanups for the watchdog driver
- patch 8/10 adds suspend to RAM to the watchdog driver (to be used by
RZ/G3S)
- patch 9/10 adapt for power domain support
- patch 10/10 documents the RZ/G3S support
Thank you,
Claudiu Beznea
Changes in v8:
- added patch 9
- collected tags
Changes in v7:
- updated the dependency on patch 2/9
Changes in v6:
- update patch 2/9 description
- fixed the dependency on COMPILE_TEST previously introduced in patch
2/9
Changes in v5:
- updated description of patch 2/9
- simplify the code in patch 2/9 by using on a new line:
depends on PM || COMPILE_TEST
Changes in v4:
- added patch "watchdog: rzg2l_wdt: Restrict the driver to ARCH_RZG2L and
ARCH_R9A09G011"
- collected tags
Changes in v3:
- make driver depend on PM not select it
- drop patches already accepted (patches 1, 10, 11 from v2)
- re-arranged the tags in patch 8/8 as they were messed by b4 am/shazam
Changes in v2:
- added patch "watchdog: rzg2l_wdt: Select PM"
- propagate the return status of rzg2l_wdt_start() to it's callers
in patch "watchdog: rzg2l_wdt: Use pm_runtime_resume_and_get()"
- propagate the return status of rzg2l_wdt_stop() to it's callers
in patch "watchdog: rzg2l_wdt: Check return status of pm_runtime_put()"
- removed pm_ptr() from patch "watchdog: rzg2l_wdt: Add suspend/resume support"
- s/G2UL/G2L in patch "dt-bindings: watchdog: renesas,wdt: Document RZ/G3S support"
- collected tags
Claudiu Beznea (10):
watchdog: rzg2l_wdt: Restrict the driver to ARCH_RZG2L and
ARCH_R9A09G011
watchdog: rzg2l_wdt: Make the driver depend on PM
watchdog: rzg2l_wdt: Use pm_runtime_resume_and_get()
watchdog: rzg2l_wdt: Check return status of pm_runtime_put()
watchdog: rzg2l_wdt: Remove reset de-assert from probe
watchdog: rzg2l_wdt: Remove comparison with zero
watchdog: rzg2l_wdt: Rely on the reset driver for doing proper reset
watchdog: rzg2l_wdt: Add suspend/resume support
watchdog: rzg2l_wdt: Power on the PM domain in rzg2l_wdt_restart()
dt-bindings: watchdog: renesas,wdt: Document RZ/G3S support
.../bindings/watchdog/renesas,wdt.yaml | 1 +
drivers/watchdog/Kconfig | 3 +-
drivers/watchdog/rzg2l_wdt.c | 123 +++++++++++-------
3 files changed, 76 insertions(+), 51 deletions(-)
--
2.39.2
^ permalink raw reply
* Re: [PATCH] arm64: dts: qcom: sm8650: add description of CCI controllers
From: Vladimir Zapolskiy @ 2024-04-10 13:22 UTC (permalink / raw)
To: Krzysztof Kozlowski, Bjorn Andersson, Konrad Dybcio,
Jagadeesh Kona
Cc: Krzysztof Kozlowski, Neil Armstrong, linux-arm-msm, devicetree
In-Reply-To: <d47ce3e8-8ce7-416f-be0f-2283575f24b2@linaro.org>
On 4/10/24 11:26, Krzysztof Kozlowski wrote:
> On 10/04/2024 09:49, Vladimir Zapolskiy wrote:
>> Qualcomm SM8650 SoC has three CCI controllers with two I2C busses
>> connected to each of them.
>>
>> The CCI controllers on SM8650 are compatible with the ones found on
>> many other older generations of Qualcomm SoCs.
>>
>> Signed-off-by: Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org>
>> ---
>> The change is based and depends on a patch series from Jagadeesh Kona:
>>
>> https://lore.kernel.org/linux-arm-msm/20240321092529.13362-1-quic_jkona@quicinc.com/
>>
>> It might be an option to add this change right to the series,
>> since it anyway requires a respin.
>>
>> A new compatible value "qcom,sm8650-cci" is NOT added to
>> Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml , because
>> the controller IP description and selection is covered by a generic
>> compatible value "qcom,msm8996-cci".
>
> I do not understand this reasoning. So you introduce known errors
> because errors are ok?
>
> How does it pass dtbs_check validation?
To continue the technical discussion let me ask you to comment on the
absolutely identical subject, which has been taken in the past in connection
to "qcom,sc8280xp-cci" compatible, probably it didn't attact any sufficient
attention before, so let's continue now.
https://lore.kernel.org/linux-arm-msm/0a3cd2f3-85e9-4769-9749-62353e842625@linaro.org/
--
Best wishes,
Vladimir
^ permalink raw reply
* Re: [PATCH] arm64: dts: qcom: sm8650: add description of CCI controllers
From: Vladimir Zapolskiy @ 2024-04-10 13:11 UTC (permalink / raw)
To: neil.armstrong, Bjorn Andersson, Konrad Dybcio, Jagadeesh Kona
Cc: Krzysztof Kozlowski, linux-arm-msm, devicetree
In-Reply-To: <72816a9f-3c25-44d3-8386-9b561a8ae996@linaro.org>
On 4/10/24 10:52, Neil Armstrong wrote:
> Hi,
>
> On 10/04/2024 09:49, Vladimir Zapolskiy wrote:
>> Qualcomm SM8650 SoC has three CCI controllers with two I2C busses
>> connected to each of them.
>>
>> The CCI controllers on SM8650 are compatible with the ones found on
>> many other older generations of Qualcomm SoCs.
>>
>> Signed-off-by: Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org>
>> ---
>> The change is based and depends on a patch series from Jagadeesh Kona:
>>
>> https://lore.kernel.org/linux-arm-msm/20240321092529.13362-1-quic_jkona@quicinc.com/
>>
>> It might be an option to add this change right to the series,
>> since it anyway requires a respin.
>>
>> A new compatible value "qcom,sm8650-cci" is NOT added to
>> Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml , because
>> the controller IP description and selection is covered by a generic
>> compatible value "qcom,msm8996-cci".
>
> You'll still need to add qcom,sm8650-cci to the "CCI v2" list in qcom,i2c-cci.yaml,
> otherwise the DTBS check fail, even if the fallback is already present.
I do recognize the problem related to a build time warning, my motivation was
to follow the rationale described in commit 3e383dce513f
("Revert "dt-bindings: i2c: qcom-cci: Document sc8280xp compatible"").
For a similar sc8280xp-cci case it was asked by Konrad to drop a new
compatible, I kindly ask the reviewers and maintainers to stick to one
of the two contradicting asks.
--
Best wishes,
Vladimir
^ permalink raw reply
* Re: [PATCH v4 03/18] ASoC: mediatek: mt8188: Migrate to mtk_soundcard_common_probe
From: Alexandre Mergnat @ 2024-04-10 12:58 UTC (permalink / raw)
To: AngeloGioacchino Del Regno, broonie
Cc: wenst, lgirdwood, robh, krzysztof.kozlowski+dt, conor+dt,
matthias.bgg, perex, tiwai, trevor.wu, maso.huang, xiazhengqiao,
arnd, kuninori.morimoto.gx, shraash, nicolas.ferre,
u.kleine-koenig, dianders, frank.li, allen-kh.cheng,
eugen.hristev, claudiu.beznea, jarkko.nikula, jiaxin.yu,
alpernebiyasak, ckeepax, zhourui, nfraprado, alsa-devel,
shane.chien, linux-sound, devicetree, linux-kernel,
linux-arm-kernel, linux-mediatek, kernel
In-Reply-To: <20240409113310.303261-4-angelogioacchino.delregno@collabora.com>
Similar changes has been done for mt8365, it works.
Reviewed-by: Alexandre Mergnat <amergnat@baylibre.com>
On 09/04/2024 13:32, AngeloGioacchino Del Regno wrote:
> Add mtk_soundcard_pdata platform data for the MediaTek common sound card
> probe mechanism, including a driver/soc-specific probe extension (used
> for bits that cannot be commonized hence specific to this driver), and
> change the probe function to mtk_soundcard_common_probe.
--
Regards,
Alexandre
^ permalink raw reply
* [PATCH v7 16/16] PCI/pwrctl: add a PCI power control driver for power sequenced devices
From: Bartosz Golaszewski @ 2024-04-10 12:46 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kalle Valo, Bjorn Andersson,
Konrad Dybcio, Liam Girdwood, Mark Brown, Catalin Marinas,
Will Deacon, Bjorn Helgaas, Bartosz Golaszewski, Saravana Kannan,
Geert Uytterhoeven, Arnd Bergmann, Neil Armstrong,
Marek Szyprowski, Alex Elder, Srini Kandagatla,
Greg Kroah-Hartman, Abel Vesa, Manivannan Sadhasivam,
Lukas Wunner, Dmitry Baryshkov, Amit Pundir, Xilin Wu
Cc: linux-bluetooth, netdev, devicetree, linux-kernel, linux-wireless,
linux-arm-msm, linux-arm-kernel, linux-pci, linux-pm,
Bartosz Golaszewski
In-Reply-To: <20240410124628.171783-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Add a PCI power control driver that's capable of correctly powering up
devices using the power sequencing subsystem. The first user of this
driver is the ath11k module on QCA6390.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
drivers/pci/pwrctl/Kconfig | 9 +++
drivers/pci/pwrctl/Makefile | 2 +
drivers/pci/pwrctl/pci-pwrctl-pwrseq.c | 89 ++++++++++++++++++++++++++
3 files changed, 100 insertions(+)
create mode 100644 drivers/pci/pwrctl/pci-pwrctl-pwrseq.c
diff --git a/drivers/pci/pwrctl/Kconfig b/drivers/pci/pwrctl/Kconfig
index 96195395af69..eb126225eb9f 100644
--- a/drivers/pci/pwrctl/Kconfig
+++ b/drivers/pci/pwrctl/Kconfig
@@ -5,4 +5,13 @@ menu "PCI Power control drivers"
config PCI_PWRCTL
tristate
+config PCI_PWRCTL_PWRSEQ
+ tristate "PCI Power Control driver using the Power Sequencing subsystem"
+ select POWER_SEQUENCING
+ select PCI_PWRCTL
+ default m if (ATH11K_PCI && ARCH_QCOM)
+ help
+ Enable support for the PCI power control driver for device
+ drivers using the Power Sequencing subsystem.
+
endmenu
diff --git a/drivers/pci/pwrctl/Makefile b/drivers/pci/pwrctl/Makefile
index 52ae0640ef7b..d308aae4800c 100644
--- a/drivers/pci/pwrctl/Makefile
+++ b/drivers/pci/pwrctl/Makefile
@@ -2,3 +2,5 @@
obj-$(CONFIG_PCI_PWRCTL) += pci-pwrctl-core.o
pci-pwrctl-core-y := core.o
+
+obj-$(CONFIG_PCI_PWRCTL_PWRSEQ) += pci-pwrctl-pwrseq.o
diff --git a/drivers/pci/pwrctl/pci-pwrctl-pwrseq.c b/drivers/pci/pwrctl/pci-pwrctl-pwrseq.c
new file mode 100644
index 000000000000..c7a113a76c0c
--- /dev/null
+++ b/drivers/pci/pwrctl/pci-pwrctl-pwrseq.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Linaro Ltd.
+ */
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pci-pwrctl.h>
+#include <linux/platform_device.h>
+#include <linux/pwrseq/consumer.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+struct pci_pwrctl_pwrseq_data {
+ struct pci_pwrctl ctx;
+ struct pwrseq_desc *pwrseq;
+};
+
+static void devm_pci_pwrctl_pwrseq_power_off(void *data)
+{
+ struct pwrseq_desc *pwrseq = data;
+
+ pwrseq_power_off(pwrseq);
+}
+
+static int pci_pwrctl_pwrseq_probe(struct platform_device *pdev)
+{
+ struct pci_pwrctl_pwrseq_data *data;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->pwrseq = devm_pwrseq_get(dev, of_device_get_match_data(dev));
+ if (IS_ERR(data->pwrseq))
+ return dev_err_probe(dev, PTR_ERR(data->pwrseq),
+ "Failed to get the power sequencer\n");
+
+ ret = pwrseq_power_on(data->pwrseq);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to power-on the device\n");
+
+ ret = devm_add_action_or_reset(dev, devm_pci_pwrctl_pwrseq_power_off,
+ data->pwrseq);
+ if (ret)
+ return ret;
+
+ data->ctx.dev = dev;
+
+ ret = devm_pci_pwrctl_device_set_ready(dev, &data->ctx);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register the pwrctl wrapper\n");
+
+ return 0;
+}
+
+static const struct of_device_id pci_pwrctl_pwrseq_of_match[] = {
+ {
+ /* ATH11K in QCA6390 package. */
+ .compatible = "pci17cb,1101",
+ .data = "wlan",
+ },
+ {
+ /* ATH12K in WCN7850 package. */
+ .compatible = "pci17cb,1107",
+ .data = "wlan",
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pci_pwrctl_pwrseq_of_match);
+
+static struct platform_driver pci_pwrctl_pwrseq_driver = {
+ .driver = {
+ .name = "pci-pwrctl-pwrseq",
+ .of_match_table = pci_pwrctl_pwrseq_of_match,
+ },
+ .probe = pci_pwrctl_pwrseq_probe,
+};
+module_platform_driver(pci_pwrctl_pwrseq_driver);
+
+MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
+MODULE_DESCRIPTION("Generic PCI Power Control module for power sequenced devices");
+MODULE_LICENSE("GPL");
--
2.40.1
^ permalink raw reply related
* [PATCH v7 15/16] Bluetooth: qca: use the power sequencer for QCA6390
From: Bartosz Golaszewski @ 2024-04-10 12:46 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kalle Valo, Bjorn Andersson,
Konrad Dybcio, Liam Girdwood, Mark Brown, Catalin Marinas,
Will Deacon, Bjorn Helgaas, Bartosz Golaszewski, Saravana Kannan,
Geert Uytterhoeven, Arnd Bergmann, Neil Armstrong,
Marek Szyprowski, Alex Elder, Srini Kandagatla,
Greg Kroah-Hartman, Abel Vesa, Manivannan Sadhasivam,
Lukas Wunner, Dmitry Baryshkov, Amit Pundir, Xilin Wu
Cc: linux-bluetooth, netdev, devicetree, linux-kernel, linux-wireless,
linux-arm-msm, linux-arm-kernel, linux-pci, linux-pm,
Bartosz Golaszewski
In-Reply-To: <20240410124628.171783-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Use the pwrseq subsystem's consumer API to run the power-up sequence for
the Bluetooth module of the QCA6390 package.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
drivers/bluetooth/hci_qca.c | 74 +++++++++++++++++++++++++++++--------
1 file changed, 59 insertions(+), 15 deletions(-)
diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c
index 8a60ad7acd70..d31dcea650c1 100644
--- a/drivers/bluetooth/hci_qca.c
+++ b/drivers/bluetooth/hci_qca.c
@@ -29,6 +29,7 @@
#include <linux/of.h>
#include <linux/acpi.h>
#include <linux/platform_device.h>
+#include <linux/pwrseq/consumer.h>
#include <linux/regulator/consumer.h>
#include <linux/serdev.h>
#include <linux/mutex.h>
@@ -215,6 +216,7 @@ struct qca_power {
struct regulator_bulk_data *vreg_bulk;
int num_vregs;
bool vregs_on;
+ struct pwrseq_desc *pwrseq;
};
struct qca_serdev {
@@ -1682,6 +1684,27 @@ static bool qca_wakeup(struct hci_dev *hdev)
return wakeup;
}
+static int qca_port_reopen(struct hci_uart *hu)
+{
+ int ret;
+
+ /* Now the device is in ready state to communicate with host.
+ * To sync host with device we need to reopen port.
+ * Without this, we will have RTS and CTS synchronization
+ * issues.
+ */
+ serdev_device_close(hu->serdev);
+ ret = serdev_device_open(hu->serdev);
+ if (ret) {
+ bt_dev_err(hu->hdev, "failed to open port");
+ return ret;
+ }
+
+ hci_uart_set_flow_control(hu, false);
+
+ return 0;
+}
+
static int qca_regulator_init(struct hci_uart *hu)
{
enum qca_btsoc_type soc_type = qca_soc_type(hu);
@@ -1750,21 +1773,7 @@ static int qca_regulator_init(struct hci_uart *hu)
break;
}
- /* Now the device is in ready state to communicate with host.
- * To sync host with device we need to reopen port.
- * Without this, we will have RTS and CTS synchronization
- * issues.
- */
- serdev_device_close(hu->serdev);
- ret = serdev_device_open(hu->serdev);
- if (ret) {
- bt_dev_err(hu->hdev, "failed to open port");
- return ret;
- }
-
- hci_uart_set_flow_control(hu, false);
-
- return 0;
+ return qca_port_reopen(hu);
}
static int qca_power_on(struct hci_dev *hdev)
@@ -1792,6 +1801,17 @@ static int qca_power_on(struct hci_dev *hdev)
ret = qca_regulator_init(hu);
break;
+ case QCA_QCA6390:
+ qcadev = serdev_device_get_drvdata(hu->serdev);
+ ret = pwrseq_power_on(qcadev->bt_power->pwrseq);
+ if (ret)
+ return ret;
+
+ ret = qca_port_reopen(hu);
+ if (ret)
+ return ret;
+ break;
+
default:
qcadev = serdev_device_get_drvdata(hu->serdev);
if (qcadev->bt_en) {
@@ -2170,6 +2190,10 @@ static void qca_power_shutdown(struct hci_uart *hu)
}
break;
+ case QCA_QCA6390:
+ pwrseq_power_off(qcadev->bt_power->pwrseq);
+ break;
+
default:
gpiod_set_value_cansleep(qcadev->bt_en, 0);
}
@@ -2308,12 +2332,25 @@ static int qca_serdev_probe(struct serdev_device *serdev)
case QCA_WCN6750:
case QCA_WCN6855:
case QCA_WCN7850:
+ case QCA_QCA6390:
qcadev->bt_power = devm_kzalloc(&serdev->dev,
sizeof(struct qca_power),
GFP_KERNEL);
if (!qcadev->bt_power)
return -ENOMEM;
+ break;
+ default:
+ break;
+ }
+ switch (qcadev->btsoc_type) {
+ case QCA_WCN3988:
+ case QCA_WCN3990:
+ case QCA_WCN3991:
+ case QCA_WCN3998:
+ case QCA_WCN6750:
+ case QCA_WCN6855:
+ case QCA_WCN7850:
qcadev->bt_power->dev = &serdev->dev;
err = qca_init_regulators(qcadev->bt_power, data->vregs,
data->num_vregs);
@@ -2354,6 +2391,13 @@ static int qca_serdev_probe(struct serdev_device *serdev)
}
break;
+ case QCA_QCA6390:
+ qcadev->bt_power->pwrseq = devm_pwrseq_get(&serdev->dev,
+ "bluetooth");
+ if (IS_ERR(qcadev->bt_power->pwrseq))
+ return PTR_ERR(qcadev->bt_power->pwrseq);
+ fallthrough;
+
default:
qcadev->bt_en = devm_gpiod_get_optional(&serdev->dev, "enable",
GPIOD_OUT_LOW);
--
2.40.1
^ permalink raw reply related
* [PATCH v7 14/16] power: pwrseq: add a driver for the PMU module on the QCom WCN chipsets
From: Bartosz Golaszewski @ 2024-04-10 12:46 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kalle Valo, Bjorn Andersson,
Konrad Dybcio, Liam Girdwood, Mark Brown, Catalin Marinas,
Will Deacon, Bjorn Helgaas, Bartosz Golaszewski, Saravana Kannan,
Geert Uytterhoeven, Arnd Bergmann, Neil Armstrong,
Marek Szyprowski, Alex Elder, Srini Kandagatla,
Greg Kroah-Hartman, Abel Vesa, Manivannan Sadhasivam,
Lukas Wunner, Dmitry Baryshkov, Amit Pundir, Xilin Wu
Cc: linux-bluetooth, netdev, devicetree, linux-kernel, linux-wireless,
linux-arm-msm, linux-arm-kernel, linux-pci, linux-pm,
Bartosz Golaszewski
In-Reply-To: <20240410124628.171783-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
This adds the power sequencing driver for the PMU modules present on the
Qualcomm WCN Bluetooth and Wifi chipsets. It uses the pwrseq subsystem
and knows how to match the sequencer to the consumer device by verifying
the relevant properties and DT layout.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
drivers/power/sequencing/Kconfig | 16 +
drivers/power/sequencing/Makefile | 2 +
drivers/power/sequencing/pwrseq-qcom-wcn.c | 336 +++++++++++++++++++++
3 files changed, 354 insertions(+)
create mode 100644 drivers/power/sequencing/pwrseq-qcom-wcn.c
diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig
index ba5732b1dbf8..468d58fac07d 100644
--- a/drivers/power/sequencing/Kconfig
+++ b/drivers/power/sequencing/Kconfig
@@ -10,3 +10,19 @@ menuconfig POWER_SEQUENCING
during power-up.
If unsure, say no.
+
+if POWER_SEQUENCING
+
+config POWER_SEQUENCING_QCOM_WCN
+ tristate "Qualcomm WCN family PMU driver"
+ default m if ARCH_QCOM
+ help
+ Say U here to enable the power sequencing driver for Qualcomm
+ WCN Bluetooth/WLAN chipsets.
+
+ Typically, a package from the Qualcomm WCN family contains the BT
+ and WLAN modules whose power is controlled by the PMU module. As the
+ former two share the power-up sequence which is executed by the PMU,
+ this driver is needed for correct power control.
+
+endif
diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/Makefile
index dcdf8c0c159e..2eec2df7912d 100644
--- a/drivers/power/sequencing/Makefile
+++ b/drivers/power/sequencing/Makefile
@@ -2,3 +2,5 @@
obj-$(CONFIG_POWER_SEQUENCING) += pwrseq-core.o
pwrseq-core-y := core.o
+
+obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) += pwrseq-qcom-wcn.o
diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c
new file mode 100644
index 000000000000..380305227b3f
--- /dev/null
+++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Linaro Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/jiffies.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pwrseq/provider.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+struct pwrseq_qcom_wcn_pdata {
+ const char *const *vregs;
+ size_t num_vregs;
+ unsigned int pwup_delay_msec;
+ unsigned int gpio_enable_delay;
+};
+
+struct pwrseq_qcom_wcn_ctx {
+ struct pwrseq_device *pwrseq;
+ struct device_node *of_node;
+ const struct pwrseq_qcom_wcn_pdata *pdata;
+ struct regulator_bulk_data *regs;
+ struct gpio_desc *bt_gpio;
+ struct gpio_desc *wlan_gpio;
+ struct clk *clk;
+ unsigned long last_gpio_enable;
+};
+
+static void pwrseq_qcom_wcn_ensure_gpio_delay(struct pwrseq_qcom_wcn_ctx *ctx)
+{
+ unsigned long diff_jiffies;
+ unsigned int diff_msecs;
+
+ if (!ctx->pdata->gpio_enable_delay)
+ return;
+
+ diff_jiffies = jiffies - ctx->last_gpio_enable;
+ diff_msecs = jiffies_to_msecs(diff_jiffies);
+
+ if (diff_msecs < ctx->pdata->gpio_enable_delay)
+ msleep(ctx->pdata->gpio_enable_delay - diff_msecs);
+}
+
+static int pwrseq_qcom_wcn_vregs_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_bulk_enable(ctx->pdata->num_vregs, ctx->regs);
+}
+
+static int pwrseq_qcom_wcn_vregs_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_bulk_disable(ctx->pdata->num_vregs, ctx->regs);
+}
+
+static const struct pwrseq_unit_data pwrseq_qcom_wcn_vregs_unit_data = {
+ .name = "regulators-enable",
+ .enable = pwrseq_qcom_wcn_vregs_enable,
+ .disable = pwrseq_qcom_wcn_vregs_disable,
+};
+
+static int pwrseq_qcom_wcn_clk_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return clk_prepare_enable(ctx->clk);
+}
+
+static int pwrseq_qcom_wcn_clk_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ clk_disable_unprepare(ctx->clk);
+
+ return 0;
+}
+
+static const struct pwrseq_unit_data pwrseq_qcom_wcn_clk_unit_data = {
+ .name = "clock-enable",
+ .enable = pwrseq_qcom_wcn_clk_enable,
+ .disable = pwrseq_qcom_wcn_clk_disable,
+};
+
+static const struct pwrseq_unit_data *pwrseq_qcom_wcn_unit_deps[] = {
+ &pwrseq_qcom_wcn_vregs_unit_data,
+ &pwrseq_qcom_wcn_clk_unit_data,
+ NULL
+};
+
+static int pwrseq_qcom_wcn_bt_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ pwrseq_qcom_wcn_ensure_gpio_delay(ctx);
+ gpiod_set_value_cansleep(ctx->bt_gpio, 1);
+ ctx->last_gpio_enable = jiffies;
+
+ return 0;
+}
+
+static int pwrseq_qcom_wcn_bt_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ gpiod_set_value_cansleep(ctx->bt_gpio, 0);
+
+ return 0;
+}
+
+static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = {
+ .name = "bluetooth-enable",
+ .deps = pwrseq_qcom_wcn_unit_deps,
+ .enable = pwrseq_qcom_wcn_bt_enable,
+ .disable = pwrseq_qcom_wcn_bt_disable,
+};
+
+static int pwrseq_qcom_wcn_wlan_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ pwrseq_qcom_wcn_ensure_gpio_delay(ctx);
+ gpiod_set_value_cansleep(ctx->wlan_gpio, 1);
+ ctx->last_gpio_enable = jiffies;
+
+ return 0;
+}
+
+static int pwrseq_qcom_wcn_wlan_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ gpiod_set_value_cansleep(ctx->wlan_gpio, 0);
+
+ return 0;
+}
+
+static const struct pwrseq_unit_data pwrseq_qcom_wcn_wlan_unit_data = {
+ .name = "wlan-enable",
+ .deps = pwrseq_qcom_wcn_unit_deps,
+ .enable = pwrseq_qcom_wcn_wlan_enable,
+ .disable = pwrseq_qcom_wcn_wlan_disable,
+};
+
+static int pwrseq_qcom_wcn_pwup_delay(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ if (ctx->pdata->pwup_delay_msec)
+ msleep(ctx->pdata->pwup_delay_msec);
+
+ return 0;
+}
+
+static const struct pwrseq_target_data pwrseq_qcom_wcn_bt_target_data = {
+ .name = "bluetooth",
+ .unit = &pwrseq_qcom_wcn_bt_unit_data,
+ .post_enable = pwrseq_qcom_wcn_pwup_delay,
+};
+
+static const struct pwrseq_target_data pwrseq_qcom_wcn_wlan_target_data = {
+ .name = "wlan",
+ .unit = &pwrseq_qcom_wcn_wlan_unit_data,
+ .post_enable = pwrseq_qcom_wcn_pwup_delay,
+};
+
+static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = {
+ &pwrseq_qcom_wcn_bt_target_data,
+ &pwrseq_qcom_wcn_wlan_target_data,
+ NULL
+};
+
+static const char *const pwrseq_qca6390_vregs[] = {
+ "vddio",
+ "vddaon",
+ "vddpmu",
+ "vddrfa0p95",
+ "vddrfa1p3",
+ "vddrfa1p9",
+ "vddpcie1p3",
+ "vddpcie1p9",
+};
+
+static const struct pwrseq_qcom_wcn_pdata pwrseq_qca6390_of_data = {
+ .vregs = pwrseq_qca6390_vregs,
+ .num_vregs = ARRAY_SIZE(pwrseq_qca6390_vregs),
+ .pwup_delay_msec = 60,
+ .gpio_enable_delay = 100,
+};
+
+static const char *const pwrseq_wcn7850_vregs[] = {
+ "vdd",
+ "vddio",
+ "vddio1p2",
+ "vddaon",
+ "vdddig",
+ "vddrfa1p2",
+ "vddrfa1p8",
+};
+
+static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn7850_of_data = {
+ .vregs = pwrseq_wcn7850_vregs,
+ .num_vregs = ARRAY_SIZE(pwrseq_wcn7850_vregs),
+ .pwup_delay_msec = 50,
+};
+
+static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
+ struct device *dev)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+ struct device_node *dev_node = dev->of_node;
+
+ /*
+ * The PMU supplies power to the Bluetooth and WLAN modules. both
+ * consume the PMU AON output so check the presence of the
+ * 'vddaon-supply' property and whether it leads us to the right
+ * device.
+ */
+ if (!of_property_present(dev_node, "vddaon-supply"))
+ return 0;
+
+ struct device_node *reg_node __free(device_node) =
+ of_parse_phandle(dev_node, "vddaon-supply", 0);
+ if (!reg_node)
+ return 0;
+
+ /*
+ * `reg_node` is the PMU AON regulator, its parent is the `regulators`
+ * node and finally its grandparent is the PMU device node that we're
+ * looking for.
+ */
+ if (!reg_node->parent || !reg_node->parent->parent ||
+ reg_node->parent->parent != ctx->of_node)
+ return 0;
+
+ return 1;
+}
+
+static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwrseq_qcom_wcn_ctx *ctx;
+ struct pwrseq_config config;
+ int i, ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->of_node = dev->of_node;
+
+ ctx->pdata = of_device_get_match_data(dev);
+ if (!ctx->pdata)
+ return dev_err_probe(dev, -ENODEV,
+ "Failed to obtain platform data\n");
+
+ ctx->regs = devm_kcalloc(dev, ctx->pdata->num_vregs,
+ sizeof(*ctx->regs), GFP_KERNEL);
+ if (!ctx->regs)
+ return -ENOMEM;
+
+ for (i = 0; i < ctx->pdata->num_vregs; i++)
+ ctx->regs[i].supply = ctx->pdata->vregs[i];
+
+ ret = devm_regulator_bulk_get(dev, ctx->pdata->num_vregs, ctx->regs);
+ if (ret < 0)
+ return dev_err_probe(dev, PTR_ERR(ctx->regs),
+ "Failed to get all regulators\n");
+
+ ctx->bt_gpio = devm_gpiod_get_optional(dev, "bt-enable", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->bt_gpio))
+ return dev_err_probe(dev, PTR_ERR(ctx->bt_gpio),
+ "Failed to get the Bluetooth enable GPIO\n");
+
+ ctx->wlan_gpio = devm_gpiod_get_optional(dev, "wlan-enable",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->wlan_gpio))
+ return dev_err_probe(dev, PTR_ERR(ctx->wlan_gpio),
+ "Failed to get the WLAN enable GPIO\n");
+
+ ctx->clk = devm_clk_get_optional(dev, NULL);
+ if (IS_ERR(ctx->clk))
+ return dev_err_probe(dev, PTR_ERR(ctx->clk),
+ "Failed to get the reference clock\n");
+
+ memset(&config, 0, sizeof(config));
+
+ config.parent = dev;
+ config.owner = THIS_MODULE;
+ config.drvdata = ctx;
+ config.match = pwrseq_qcom_wcn_match;
+ config.targets = pwrseq_qcom_wcn_targets;
+
+ ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
+ if (IS_ERR(ctx->pwrseq))
+ return dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
+ "Failed to register the power sequencer\n");
+
+ return 0;
+}
+
+static const struct of_device_id pwrseq_qcom_wcn_of_match[] = {
+ {
+ .compatible = "qcom,qca6390-pmu",
+ .data = &pwrseq_qca6390_of_data,
+ },
+ {
+ .compatible = "qcom,wcn7850-pmu",
+ .data = &pwrseq_wcn7850_of_data,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pwrseq_qcom_wcn_of_match);
+
+static struct platform_driver pwrseq_qcom_wcn_driver = {
+ .driver = {
+ .name = "pwrseq-qcom_wcn",
+ .of_match_table = pwrseq_qcom_wcn_of_match,
+ },
+ .probe = pwrseq_qcom_wcn_probe,
+};
+module_platform_driver(pwrseq_qcom_wcn_driver);
+
+MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
+MODULE_DESCRIPTION("Qualcomm WCN PMU power sequencing driver");
+MODULE_LICENSE("GPL");
--
2.40.1
^ permalink raw reply related
* [PATCH v7 13/16] power: sequencing: implement the pwrseq core
From: Bartosz Golaszewski @ 2024-04-10 12:46 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kalle Valo, Bjorn Andersson,
Konrad Dybcio, Liam Girdwood, Mark Brown, Catalin Marinas,
Will Deacon, Bjorn Helgaas, Bartosz Golaszewski, Saravana Kannan,
Geert Uytterhoeven, Arnd Bergmann, Neil Armstrong,
Marek Szyprowski, Alex Elder, Srini Kandagatla,
Greg Kroah-Hartman, Abel Vesa, Manivannan Sadhasivam,
Lukas Wunner, Dmitry Baryshkov, Amit Pundir, Xilin Wu
Cc: linux-bluetooth, netdev, devicetree, linux-kernel, linux-wireless,
linux-arm-msm, linux-arm-kernel, linux-pci, linux-pm,
Bartosz Golaszewski
In-Reply-To: <20240410124628.171783-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Implement the power sequencing subsystem allowing devices to share
complex powering-up and down procedures. It's split into the consumer
and provider parts but does not implement any new DT bindings so that
the actual power sequencing is never revealed in the DT representation.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
MAINTAINERS | 8 +
drivers/power/Kconfig | 1 +
drivers/power/Makefile | 1 +
drivers/power/sequencing/Kconfig | 12 +
drivers/power/sequencing/Makefile | 4 +
drivers/power/sequencing/core.c | 1065 +++++++++++++++++++++++++++++
include/linux/pwrseq/consumer.h | 56 ++
include/linux/pwrseq/provider.h | 75 ++
8 files changed, 1222 insertions(+)
create mode 100644 drivers/power/sequencing/Kconfig
create mode 100644 drivers/power/sequencing/Makefile
create mode 100644 drivers/power/sequencing/core.c
create mode 100644 include/linux/pwrseq/consumer.h
create mode 100644 include/linux/pwrseq/provider.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 51d5a64a5a36..37039fa1b721 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17631,6 +17631,14 @@ F: include/linux/pm_*
F: include/linux/powercap.h
F: kernel/configs/nopm.config
+POWER SEQUENCING
+M: Bartosz Golaszewski <brgl@bgdev.pl>
+L: linux-pm@vger.kernel.org
+S: Maintained
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git
+F: drivers/power/sequencing/
+F: include/linux/pwrseq/
+
POWER STATE COORDINATION INTERFACE (PSCI)
M: Mark Rutland <mark.rutland@arm.com>
M: Lorenzo Pieralisi <lpieralisi@kernel.org>
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 696bf77a7042..9a8e44ca9ae4 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
source "drivers/power/reset/Kconfig"
+source "drivers/power/sequencing/Kconfig"
source "drivers/power/supply/Kconfig"
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index effbf0377f32..962a2cd30a51 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_POWER_RESET) += reset/
+obj-$(CONFIG_POWER_SEQUENCING) += sequencing/
obj-$(CONFIG_POWER_SUPPLY) += supply/
diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig
new file mode 100644
index 000000000000..ba5732b1dbf8
--- /dev/null
+++ b/drivers/power/sequencing/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+menuconfig POWER_SEQUENCING
+ tristate "Power Sequencing support"
+ help
+ Say Y here to enable the Power Sequencing subsystem.
+
+ This subsystem is designed to control power to devices that share
+ complex resources and/or require specific power sequences to be run
+ during power-up.
+
+ If unsure, say no.
diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/Makefile
new file mode 100644
index 000000000000..dcdf8c0c159e
--- /dev/null
+++ b/drivers/power/sequencing/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_POWER_SEQUENCING) += pwrseq-core.o
+pwrseq-core-y := core.o
diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c
new file mode 100644
index 000000000000..f5f3dc9d2407
--- /dev/null
+++ b/drivers/power/sequencing/core.c
@@ -0,0 +1,1065 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Linaro Ltd.
+ */
+
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/idr.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/pwrseq/consumer.h>
+#include <linux/pwrseq/provider.h>
+#include <linux/radix-tree.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+
+/*
+ * Power-sequencing framework for linux.
+ *
+ * This subsystem allows power sequence providers to register a set of targets
+ * that consumers may request and power-up/down.
+ *
+ * Glossary:
+ *
+ * Unit - a unit is a discreet chunk of a power sequence. For instance one unit
+ * may enable a set of regulators, another may enable a specific GPIO. Units
+ * can define dependencies in the form of other units that must be enabled
+ * before it itself can be.
+ *
+ * Target - a target is a set of units (composed of the "final" unit and its
+ * dependencies) that a consumer selects by its name when requesting a handle
+ * to the power sequencer. Via the dependency system, multiple targets may
+ * share the same parts of a power sequence but ignore parts that are
+ * irrelevant.
+ *
+ * Descriptor - a handle passed by the pwrseq core to every consumer that
+ * serves as the entry point to the provider layer. It ensures coherence
+ * between different users and keeps reference counting consistent.
+ *
+ * Each provider must define a .match() callback whose role is to determine
+ * whether a potential consumer is in fact associated with this sequencer.
+ * This allows creating abstraction layers on top of regular device-tree
+ * resources like regulators, clocks and other nodes connected to the consumer
+ * via phandle.
+ */
+
+static DEFINE_IDA(pwrseq_ida);
+
+/*
+ * Protects the device list on the pwrseq bus from concurrent modifications
+ * but allows simultaneous read-only access.
+ */
+static DECLARE_RWSEM(pwrseq_sem);
+
+/**
+ * struct pwrseq_unit - Private power-sequence unit data.
+ * @ref: Reference count for this object. When it goes to 0, the object is
+ * destroyed.
+ * @name: Name of this target.
+ * @list: Link to siblings on the list of all units of a single sequencer.
+ * @deps: List of units on which this unit depends.
+ * @enable: Callback running the part of the power-on sequence provided by
+ * this unit.
+ * @disable: Callback running the part of the power-off sequence provided
+ * by this unit.
+ * @enable_count: Current number of users that enabled this unit. May be the
+ * consumer of the power sequencer or other units that depend
+ * on this one.
+ */
+struct pwrseq_unit {
+ struct kref ref;
+ const char *name;
+ struct list_head list;
+ struct list_head deps;
+ pwrseq_power_state_func enable;
+ pwrseq_power_state_func disable;
+ unsigned int enable_count;
+};
+
+static struct pwrseq_unit *pwrseq_unit_new(const struct pwrseq_unit_data *data)
+{
+ struct pwrseq_unit *unit;
+
+ unit = kzalloc(sizeof(*unit), GFP_KERNEL);
+ if (!unit)
+ return NULL;
+
+ unit->name = kstrdup_const(data->name, GFP_KERNEL);
+ if (!unit->name) {
+ kfree(unit);
+ return NULL;
+ }
+
+ kref_init(&unit->ref);
+ INIT_LIST_HEAD(&unit->deps);
+ unit->enable = data->enable;
+ unit->disable = data->disable;
+
+ return unit;
+}
+
+static struct pwrseq_unit *pwrseq_unit_incref(struct pwrseq_unit *unit)
+{
+ kref_get(&unit->ref);
+
+ return unit;
+}
+
+static void pwrseq_unit_release(struct kref *ref);
+
+static void pwrseq_unit_decref(struct pwrseq_unit *unit)
+{
+ kref_put(&unit->ref, pwrseq_unit_release);
+}
+
+/**
+ * struct pwrseq_unit_dep - Wrapper around a reference to the unit structure
+ * allowing to keep it on multiple dependency lists
+ * in different units.
+ * @list: Siblings on the list.
+ * @unit: Address of the referenced unit.
+ */
+struct pwrseq_unit_dep {
+ struct list_head list;
+ struct pwrseq_unit *unit;
+};
+
+static struct pwrseq_unit_dep *pwrseq_unit_dep_new(struct pwrseq_unit *unit)
+{
+ struct pwrseq_unit_dep *dep;
+
+ dep = kzalloc(sizeof(*dep), GFP_KERNEL);
+ if (!dep)
+ return NULL;
+
+ dep->unit = unit;
+
+ return dep;
+}
+
+static void pwrseq_unit_dep_free(struct pwrseq_unit_dep *ref)
+{
+ pwrseq_unit_decref(ref->unit);
+ kfree(ref);
+}
+
+static void pwrseq_unit_free_deps(struct list_head *list)
+{
+ struct pwrseq_unit_dep *dep, *next;
+
+ list_for_each_entry_safe(dep, next, list, list) {
+ list_del(&dep->list);
+ pwrseq_unit_dep_free(dep);
+ }
+}
+
+static void pwrseq_unit_release(struct kref *ref)
+{
+ struct pwrseq_unit *unit = container_of(ref, struct pwrseq_unit, ref);
+
+ pwrseq_unit_free_deps(&unit->deps);
+ list_del(&unit->list);
+ kfree_const(unit->name);
+ kfree(unit);
+}
+
+/**
+ * struct pwrseq_target - Private power-sequence target data.
+ * @list: Siblings on the list of all targets exposed by a power sequencer.
+ * @name: Name of the target.
+ * @unit: Final unit for this target.
+ * @post_enable: Callback run after the target unit has been enabled, *after*
+ * the state lock has been released. It's useful for implementing
+ * boot-up delays without blocking other users from powering up
+ * using the same power sequencer.
+ */
+struct pwrseq_target {
+ struct list_head list;
+ const char *name;
+ struct pwrseq_unit *unit;
+ pwrseq_power_state_func post_enable;
+};
+
+static struct pwrseq_target *
+pwrseq_target_new(const struct pwrseq_target_data *data)
+{
+ struct pwrseq_target *target;
+
+ target = kzalloc(sizeof(*target), GFP_KERNEL);
+ if (!target)
+ return NULL;
+
+ target->name = kstrdup_const(data->name, GFP_KERNEL);
+ if (!target->name) {
+ kfree(target);
+ return NULL;
+ }
+
+ target->post_enable = data->post_enable;
+
+ return target;
+}
+
+static void pwrseq_target_free(struct pwrseq_target *target)
+{
+ pwrseq_unit_decref(target->unit);
+ kfree_const(target->name);
+ kfree(target);
+}
+
+/**
+ * struct pwrseq_device - Private power sequencing data.
+ * @dev: Device struct associated with this sequencer.
+ * @id: Device ID.
+ * @owner: Prevents removal of active power sequencing providers.
+ * @rw_lock: Protects the device from being unregistered while in use.
+ * @state_lock: Prevents multiple users running the power sequence at the same
+ * time.
+ * @match: Power sequencer matching callback.
+ * @targets: List of targets exposed by this sequencer.
+ * @units: List of all units supported by this sequencer.
+ */
+struct pwrseq_device {
+ struct device dev;
+ int id;
+ struct module *owner;
+ struct rw_semaphore rw_lock;
+ struct mutex state_lock;
+ pwrseq_match_func match;
+ struct list_head targets;
+ struct list_head units;
+};
+
+static struct pwrseq_device *to_pwrseq_device(struct device *dev)
+{
+ return container_of(dev, struct pwrseq_device, dev);
+}
+
+static struct pwrseq_device *pwrseq_device_get(struct pwrseq_device *pwrseq)
+{
+ get_device(&pwrseq->dev);
+
+ return pwrseq;
+}
+
+static void pwrseq_device_put(struct pwrseq_device *pwrseq)
+{
+ put_device(&pwrseq->dev);
+}
+
+/**
+ * struct pwrseq_desc - Wraps access to the pwrseq_device and ensures that one
+ * user cannot break the reference counting for others.
+ * @pwrseq: Reference to the power sequencing device.
+ * @target: Reference to the target this descriptor allows to control.
+ * @powered_on: Power state set by the holder of the descriptor (not necessarily
+ * corresponding to the actual power state of the device).
+ */
+struct pwrseq_desc {
+ struct pwrseq_device *pwrseq;
+ struct pwrseq_target *target;
+ bool powered_on;
+};
+
+static const struct bus_type pwrseq_bus = {
+ .name = "pwrseq",
+};
+
+static void pwrseq_release(struct device *dev)
+{
+ struct pwrseq_device *pwrseq = to_pwrseq_device(dev);
+ struct pwrseq_target *target;
+
+ list_for_each_entry(target, &pwrseq->targets, list)
+ pwrseq_target_free(target);
+
+ mutex_destroy(&pwrseq->state_lock);
+ ida_free(&pwrseq_ida, pwrseq->id);
+ kfree(pwrseq);
+}
+
+static const struct device_type pwrseq_device_type = {
+ .name = "power_sequencer",
+ .release = pwrseq_release,
+};
+
+static int pwrseq_unit_setup_deps(const struct pwrseq_unit_data **data,
+ struct list_head *dep_list,
+ struct list_head *unit_list,
+ struct radix_tree_root *processed_units)
+{
+ const struct pwrseq_unit_data *pos;
+ struct pwrseq_unit_dep *dep;
+ struct pwrseq_unit *unit;
+ int ret, i;
+
+ for (i = 0; data[i]; i++) {
+ pos = data[i];
+
+ unit = radix_tree_lookup(processed_units, (unsigned long)pos);
+ if (unit) {
+ pwrseq_unit_incref(unit);
+ } else {
+ unit = pwrseq_unit_new(pos);
+ if (!unit)
+ return -ENOMEM;
+
+ if (pos->deps) {
+ ret = pwrseq_unit_setup_deps(pos->deps,
+ &unit->deps,
+ unit_list,
+ processed_units);
+ if (ret) {
+ pwrseq_unit_decref(unit);
+ return ret;
+ }
+ }
+
+ ret = radix_tree_insert(processed_units,
+ (unsigned long)pos, unit);
+ if (ret) {
+ pwrseq_unit_decref(unit);
+ return ret;
+ }
+
+ list_add_tail(&unit->list, unit_list);
+ }
+
+ dep = pwrseq_unit_dep_new(unit);
+ if (!dep) {
+ pwrseq_unit_decref(unit);
+ return -ENOMEM;
+ }
+
+ list_add_tail(&dep->list, dep_list);
+ }
+
+ return 0;
+}
+
+static int pwrseq_target_setup_unit(struct pwrseq_target *target,
+ const struct pwrseq_unit_data *data,
+ struct list_head *unit_list,
+ struct radix_tree_root *processed_units)
+{
+ struct pwrseq_unit *unit;
+ int ret;
+
+ unit = radix_tree_lookup(processed_units, (unsigned long)data);
+ if (unit) {
+ target->unit = pwrseq_unit_incref(unit);
+ return 0;
+ }
+
+ unit = pwrseq_unit_new(data);
+ if (!unit)
+ return -ENOMEM;
+
+ ret = pwrseq_unit_setup_deps(data->deps, &unit->deps, unit_list,
+ processed_units);
+ if (ret) {
+ pwrseq_unit_decref(unit);
+ return ret;
+ }
+
+ ret = radix_tree_insert(processed_units, (unsigned long)data, unit);
+ if (ret) {
+ pwrseq_unit_decref(unit);
+ return ret;
+ }
+
+ target->unit = unit;
+ list_add_tail(&unit->list, unit_list);
+
+ return 0;
+}
+
+static int pwrseq_do_setup_targets(const struct pwrseq_target_data **data,
+ struct pwrseq_device *pwrseq,
+ struct radix_tree_root *processed_units)
+{
+ const struct pwrseq_target_data *pos;
+ struct pwrseq_target *target;
+ int ret, i;
+
+ for (i = 0; data[i]; i++) {
+ pos = data[i];
+
+ target = pwrseq_target_new(pos);
+ if (!target)
+ return -ENOMEM;
+
+ ret = pwrseq_target_setup_unit(target, pos->unit,
+ &pwrseq->units,
+ processed_units);
+ if (ret) {
+ pwrseq_target_free(target);
+ return ret;
+ }
+
+ list_add_tail(&target->list, &pwrseq->targets);
+ }
+
+ return 0;
+}
+
+static int pwrseq_setup_targets(const struct pwrseq_target_data **targets,
+ struct pwrseq_device *pwrseq)
+{
+ struct radix_tree_root processed_units;
+ struct radix_tree_iter iter;
+ void __rcu **slot;
+ int ret;
+
+ INIT_RADIX_TREE(&processed_units, GFP_KERNEL);
+ ret = pwrseq_do_setup_targets(targets, pwrseq, &processed_units);
+ radix_tree_for_each_slot(slot, &processed_units, &iter, 0)
+ radix_tree_delete(&processed_units, iter.index);
+
+ return ret;
+}
+
+/**
+ * pwrseq_device_register() - Register a new power sequencer.
+ * @config: Configuration of the new power sequencing device.
+ *
+ * The config structure is only used during the call and can be freed after
+ * the function returns. The config structure *must* have the parent device
+ * as well as the match() callback and at least one target set.
+ *
+ * Returns:
+ * Returns the address of the new pwrseq device or ERR_PTR() on failure.
+ */
+struct pwrseq_device *
+pwrseq_device_register(const struct pwrseq_config *config)
+{
+ struct pwrseq_device *pwrseq;
+ int ret;
+
+ if (!config->parent || !config->match || !config->targets ||
+ !config->targets[0])
+ return ERR_PTR(-EINVAL);
+
+ pwrseq = kzalloc(sizeof(*pwrseq), GFP_KERNEL);
+ if (!pwrseq)
+ return ERR_PTR(-ENOMEM);
+
+ pwrseq->dev.type = &pwrseq_device_type;
+ pwrseq->dev.bus = &pwrseq_bus;
+ pwrseq->dev.parent = config->parent;
+ device_set_node(&pwrseq->dev, dev_fwnode(config->parent));
+ dev_set_drvdata(&pwrseq->dev, config->drvdata);
+
+ pwrseq->id = ida_alloc(&pwrseq_ida, GFP_KERNEL);
+ if (pwrseq->id < 0) {
+ kfree(pwrseq);
+ return ERR_PTR(pwrseq->id);
+ }
+
+ /*
+ * From this point onwards the device's release() callback is
+ * responsible for freeing resources.
+ */
+ device_initialize(&pwrseq->dev);
+
+ ret = dev_set_name(&pwrseq->dev, "pwrseq.%d", pwrseq->id);
+ if (ret)
+ goto err_put_pwrseq;
+
+ pwrseq->owner = config->owner ?: THIS_MODULE;
+ pwrseq->match = config->match;
+
+ init_rwsem(&pwrseq->rw_lock);
+ mutex_init(&pwrseq->state_lock);
+ INIT_LIST_HEAD(&pwrseq->targets);
+ INIT_LIST_HEAD(&pwrseq->units);
+
+ ret = pwrseq_setup_targets(config->targets, pwrseq);
+ if (ret)
+ goto err_put_pwrseq;
+
+ scoped_guard(rwsem_write, &pwrseq_sem) {
+ ret = device_add(&pwrseq->dev);
+ if (ret)
+ goto err_put_pwrseq;
+ }
+
+ return pwrseq;
+
+err_put_pwrseq:
+ pwrseq_device_put(pwrseq);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(pwrseq_device_register);
+
+/**
+ * pwrseq_device_unregister() - Unregister the power sequencer.
+ * @pwrseq: Power sequencer to unregister.
+ */
+void pwrseq_device_unregister(struct pwrseq_device *pwrseq)
+{
+ struct device *dev = &pwrseq->dev;
+ struct pwrseq_target *target;
+
+ scoped_guard(mutex, &pwrseq->state_lock) {
+ guard(rwsem_write)(&pwrseq->rw_lock);
+
+ list_for_each_entry(target, &pwrseq->targets, list)
+ WARN_ONCE(target->unit->enable_count,
+ "REMOVING POWER SEQUENCER WITH ACTIVE USERS\n");
+
+ guard(rwsem_write)(&pwrseq_sem);
+
+ device_del(dev);
+ }
+
+ pwrseq_device_put(pwrseq);
+}
+EXPORT_SYMBOL_GPL(pwrseq_device_unregister);
+
+static void devm_pwrseq_device_unregister(void *data)
+{
+ struct pwrseq_device *pwrseq = data;
+
+ pwrseq_device_unregister(pwrseq);
+}
+
+/**
+ * devm_pwrseq_device_register() - Managed variant of pwrseq_device_register().
+ * @dev: Managing device.
+ * @config: Configuration of the new power sequencing device.
+ *
+ * Returns:
+ * Returns the address of the new pwrseq device or ERR_PTR() on failure.
+ */
+struct pwrseq_device *
+devm_pwrseq_device_register(struct device *dev,
+ const struct pwrseq_config *config)
+{
+ struct pwrseq_device *pwrseq;
+ int ret;
+
+ pwrseq = pwrseq_device_register(config);
+ if (IS_ERR(pwrseq))
+ return pwrseq;
+
+ ret = devm_add_action_or_reset(dev, devm_pwrseq_device_unregister,
+ pwrseq);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return pwrseq;
+}
+EXPORT_SYMBOL_GPL(devm_pwrseq_device_register);
+
+/**
+ * pwrseq_device_get_drvdata() - Get the driver private data associated with
+ * this sequencer.
+ * @pwrseq: Power sequencer object.
+ *
+ * Returns:
+ * Address of the private driver data.
+ */
+void *pwrseq_device_get_drvdata(struct pwrseq_device *pwrseq)
+{
+ return dev_get_drvdata(&pwrseq->dev);
+}
+EXPORT_SYMBOL_GPL(pwrseq_device_get_drvdata);
+
+struct pwrseq_match_data {
+ struct pwrseq_desc *desc;
+ struct device *dev;
+ const char *target;
+};
+
+static int pwrseq_match_device(struct device *pwrseq_dev, void *data)
+{
+ struct pwrseq_device *pwrseq = to_pwrseq_device(pwrseq_dev);
+ struct pwrseq_match_data *match_data = data;
+ struct pwrseq_target *target;
+ int ret;
+
+ guard(rwsem_read)(&pwrseq->rw_lock);
+ if (!device_is_registered(&pwrseq->dev))
+ return 0;
+
+ ret = pwrseq->match(pwrseq, match_data->dev);
+ if (ret <= 0)
+ return ret;
+
+ /* We got the matching device, let's find the right target. */
+ list_for_each_entry(target, &pwrseq->targets, list) {
+ if (strcmp(target->name, match_data->target))
+ continue;
+
+ match_data->desc->target = target;
+ }
+
+ /*
+ * This device does not have this target. No point in deferring as it
+ * will not get a new target dynamically later.
+ */
+ if (!match_data->desc->target)
+ return -ENOENT;
+
+ if (!try_module_get(pwrseq->owner))
+ return -EPROBE_DEFER;
+
+ match_data->desc->pwrseq = pwrseq_device_get(pwrseq);
+
+ return 1;
+}
+
+/**
+ * pwrseq_get() - Get the power sequencer associated with this device.
+ * @dev: Device for which to get the sequencer.
+ * @target: Name of the target exposed by the sequencer this device wants to
+ * reach.
+ *
+ * Returns:
+ * New power sequencer descriptor for use by the consumer driver or ERR_PTR()
+ * on failure.
+ */
+struct pwrseq_desc *pwrseq_get(struct device *dev, const char *target)
+{
+ struct pwrseq_match_data match_data;
+ int ret;
+
+ struct pwrseq_desc *desc __free(kfree) = kzalloc(sizeof(*desc),
+ GFP_KERNEL);
+ if (!desc)
+ return ERR_PTR(-ENOMEM);
+
+ match_data.desc = desc;
+ match_data.dev = dev;
+ match_data.target = target;
+
+ guard(rwsem_read)(&pwrseq_sem);
+
+ ret = bus_for_each_dev(&pwrseq_bus, NULL, &match_data,
+ pwrseq_match_device);
+ if (ret < 0)
+ return ERR_PTR(ret);
+ if (ret == 0)
+ /* No device matched. */
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return no_free_ptr(desc);
+}
+EXPORT_SYMBOL_GPL(pwrseq_get);
+
+/**
+ * pwrseq_put() - Release the power sequencer descriptor.
+ * @desc: Descriptor to release.
+ */
+void pwrseq_put(struct pwrseq_desc *desc)
+{
+ struct pwrseq_device *pwrseq;
+
+ if (!desc)
+ return;
+
+ pwrseq = desc->pwrseq;
+
+ if (desc->powered_on)
+ pwrseq_power_off(desc);
+
+ kfree(desc);
+ module_put(pwrseq->owner);
+ pwrseq_device_put(pwrseq);
+}
+EXPORT_SYMBOL_GPL(pwrseq_put);
+
+static void devm_pwrseq_put(void *data)
+{
+ struct pwrseq_desc *desc = data;
+
+ pwrseq_put(desc);
+}
+
+/**
+ * devm_pwrseq_get() - Managed variant of pwrseq_get().
+ * @dev: Device for which to get the sequencer and which also manages its
+ * lifetime.
+ * @target: Name of the target exposed by the sequencer this device wants to
+ * reach.
+ *
+ * Returns:
+ * New power sequencer descriptor for use by the consumer driver or ERR_PTR()
+ * on failure.
+ */
+struct pwrseq_desc *devm_pwrseq_get(struct device *dev, const char *target)
+{
+ struct pwrseq_desc *desc;
+ int ret;
+
+ desc = pwrseq_get(dev, target);
+ if (IS_ERR(desc))
+ return desc;
+
+ ret = devm_add_action_or_reset(dev, devm_pwrseq_put, desc);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return desc;
+}
+EXPORT_SYMBOL_GPL(devm_pwrseq_get);
+
+static int pwrseq_unit_enable(struct pwrseq_device *pwrseq,
+ struct pwrseq_unit *target);
+static int pwrseq_unit_disable(struct pwrseq_device *pwrseq,
+ struct pwrseq_unit *target);
+
+static int pwrseq_unit_enable_deps(struct pwrseq_device *pwrseq,
+ struct list_head *list)
+{
+ struct pwrseq_unit_dep *pos;
+ int ret = 0;
+
+ list_for_each_entry(pos, list, list) {
+ ret = pwrseq_unit_enable(pwrseq, pos->unit);
+ if (ret) {
+ list_for_each_entry_continue_reverse(pos, list, list)
+ pwrseq_unit_disable(pwrseq, pos->unit);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int pwrseq_unit_disable_deps(struct pwrseq_device *pwrseq,
+ struct list_head *list)
+{
+ struct pwrseq_unit_dep *pos;
+ int ret = 0;
+
+ list_for_each_entry_reverse(pos, list, list) {
+ ret = pwrseq_unit_disable(pwrseq, pos->unit);
+ if (ret) {
+ list_for_each_entry_continue(pos, list, list)
+ pwrseq_unit_enable(pwrseq, pos->unit);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int pwrseq_unit_enable(struct pwrseq_device *pwrseq,
+ struct pwrseq_unit *unit)
+{
+ int ret;
+
+ if (unit->enable_count != 0) {
+ unit->enable_count++;
+ return 0;
+ }
+
+ ret = pwrseq_unit_enable_deps(pwrseq, &unit->deps);
+ if (ret) {
+ dev_err(&pwrseq->dev,
+ "Failed to enable dependencies before power-on for target '%s': %d\n",
+ unit->name, ret);
+ return ret;
+ }
+
+ if (unit->enable) {
+ ret = unit->enable(pwrseq);
+ if (ret) {
+ dev_err(&pwrseq->dev,
+ "Failed to enable target '%s': %d\n",
+ unit->name, ret);
+ pwrseq_unit_disable_deps(pwrseq, &unit->deps);
+ return ret;
+ }
+ }
+
+ unit->enable_count++;
+
+ return 0;
+}
+
+static int pwrseq_unit_disable(struct pwrseq_device *pwrseq,
+ struct pwrseq_unit *unit)
+{
+ int ret;
+
+ if (unit->enable_count == 0) {
+ WARN_ONCE(1, "Unmatched power-off for target '%s'\n",
+ unit->name);
+ return -EBUSY;
+ }
+
+ if (unit->enable_count != 1) {
+ unit->enable_count--;
+ return 0;
+ }
+
+ if (unit->disable) {
+ ret = unit->disable(pwrseq);
+ if (ret) {
+ dev_err(&pwrseq->dev,
+ "Failed to disable target '%s': %d\n",
+ unit->name, ret);
+ return ret;
+ }
+ }
+
+ ret = pwrseq_unit_disable_deps(pwrseq, &unit->deps);
+ if (ret) {
+ dev_err(&pwrseq->dev,
+ "Failed to disable dependencies after power-off for target '%s': %d\n",
+ unit->name, ret);
+ if (unit->enable)
+ unit->enable(pwrseq);
+ return ret;
+ }
+
+ unit->enable_count--;
+
+ return 0;
+}
+
+/**
+ * pwrseq_power_on() - Issue a power-on request on behalf of the consumer
+ * device.
+ * @desc: Descriptor referencing the power sequencer.
+ *
+ * This function tells the power sequencer that the consumer wants to be
+ * powered-up. The sequencer may already have powered-up the device in which
+ * case the function returns 0. If the power-up sequence is already in
+ * progress, the function will block until it's done and return 0. If this is
+ * the first request, the device will be powered up.
+ *
+ * Returns:
+ * 0 on success, negative error number on failure.
+ */
+int pwrseq_power_on(struct pwrseq_desc *desc)
+{
+ struct pwrseq_device *pwrseq;
+ struct pwrseq_target *target;
+ struct pwrseq_unit *unit;
+ int ret;
+
+ might_sleep();
+
+ if (!desc || desc->powered_on)
+ return 0;
+
+ pwrseq = desc->pwrseq;
+ target = desc->target;
+ unit = target->unit;
+
+ guard(rwsem_read)(&pwrseq->rw_lock);
+ if (!device_is_registered(&pwrseq->dev))
+ return -ENODEV;
+
+ scoped_guard(mutex, &pwrseq->state_lock) {
+ ret = pwrseq_unit_enable(pwrseq, unit);
+ if (!ret)
+ desc->powered_on = true;
+ }
+
+ if (target->post_enable) {
+ ret = target->post_enable(pwrseq);
+ if (ret) {
+ pwrseq_unit_disable(pwrseq, unit);
+ desc->powered_on = false;
+ }
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pwrseq_power_on);
+
+/**
+ * pwrseq_power_off() - Issue a power-off request on behalf of the consumer
+ * device.
+ * @desc: Descriptor referencing the power sequencer.
+ *
+ * This undoes the effects of pwrseq_power_on(). It issues a power-off request
+ * on behalf of the consumer and when the last remaining user does so, the
+ * power-down sequence will be started. If one is in progress, the function
+ * will block until it's complete and then return.
+ *
+ * Returns:
+ * 0 on success, negative error number on failure.
+ */
+int pwrseq_power_off(struct pwrseq_desc *desc)
+{
+ struct pwrseq_device *pwrseq;
+ struct pwrseq_unit *unit;
+ int ret;
+
+ might_sleep();
+
+ if (!desc || !desc->powered_on)
+ return 0;
+
+ pwrseq = desc->pwrseq;
+ unit = desc->target->unit;
+
+ guard(rwsem_read)(&pwrseq->rw_lock);
+ if (!device_is_registered(&pwrseq->dev))
+ return -ENODEV;
+
+ guard(mutex)(&pwrseq->state_lock);
+
+ ret = pwrseq_unit_disable(pwrseq, unit);
+ if (!ret)
+ desc->powered_on = false;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pwrseq_power_off);
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+
+struct pwrseq_debugfs_count_ctx {
+ struct device *dev;
+ loff_t index;
+};
+
+static int pwrseq_debugfs_seq_count(struct device *dev, void *data)
+{
+ struct pwrseq_debugfs_count_ctx *ctx = data;
+
+ ctx->dev = dev;
+
+ return ctx->index-- ? 0 : 1;
+}
+
+static void *pwrseq_debugfs_seq_start(struct seq_file *seq, loff_t *pos)
+{
+ struct pwrseq_debugfs_count_ctx ctx;
+
+ ctx.dev = NULL;
+ ctx.index = *pos;
+
+ /*
+ * We're holding the lock for the entire printout so no need to fiddle
+ * with device reference count.
+ */
+ down_read(&pwrseq_sem);
+
+ bus_for_each_dev(&pwrseq_bus, NULL, &ctx, pwrseq_debugfs_seq_count);
+ if (!ctx.index)
+ return NULL;
+
+ return ctx.dev;
+}
+
+static void *pwrseq_debugfs_seq_next(struct seq_file *seq, void *data,
+ loff_t *pos)
+{
+ struct device *curr = data;
+
+ ++*pos;
+
+ struct device *next __free(put_device) =
+ bus_find_next_device(&pwrseq_bus, curr);
+ return next;
+}
+
+static void pwrseq_debugfs_seq_show_target(struct seq_file *seq,
+ struct pwrseq_target *target)
+{
+ seq_printf(seq, " target: [%s] (target unit: [%s])\n",
+ target->name, target->unit->name);
+}
+
+static void pwrseq_debugfs_seq_show_unit(struct seq_file *seq,
+ struct pwrseq_unit *unit)
+{
+ struct pwrseq_unit_dep *ref;
+
+ seq_printf(seq, " unit: [%s] - enable count: %u\n",
+ unit->name, unit->enable_count);
+
+ if (list_empty(&unit->deps))
+ return;
+
+ seq_puts(seq, " dependencies:\n");
+ list_for_each_entry(ref, &unit->deps, list)
+ seq_printf(seq, " [%s]\n", ref->unit->name);
+}
+
+static int pwrseq_debugfs_seq_show(struct seq_file *seq, void *data)
+{
+ struct device *dev = data;
+ struct pwrseq_device *pwrseq = to_pwrseq_device(dev);
+ struct pwrseq_target *target;
+ struct pwrseq_unit *unit;
+
+ seq_printf(seq, "%s:\n", dev_name(dev));
+
+ seq_puts(seq, " targets:\n");
+ list_for_each_entry(target, &pwrseq->targets, list)
+ pwrseq_debugfs_seq_show_target(seq, target);
+
+ seq_puts(seq, " units:\n");
+ list_for_each_entry(unit, &pwrseq->units, list)
+ pwrseq_debugfs_seq_show_unit(seq, unit);
+
+ return 0;
+}
+
+static void pwrseq_debugfs_seq_stop(struct seq_file *seq, void *data)
+{
+ up_read(&pwrseq_sem);
+}
+
+static const struct seq_operations pwrseq_debugfs_sops = {
+ .start = pwrseq_debugfs_seq_start,
+ .next = pwrseq_debugfs_seq_next,
+ .show = pwrseq_debugfs_seq_show,
+ .stop = pwrseq_debugfs_seq_stop,
+};
+DEFINE_SEQ_ATTRIBUTE(pwrseq_debugfs);
+
+static struct dentry *pwrseq_debugfs_dentry;
+
+#endif /* CONFIG_DEBUG_FS */
+
+static int __init pwrseq_init(void)
+{
+ int ret;
+
+ ret = bus_register(&pwrseq_bus);
+ if (ret) {
+ pr_err("Failed to register the power sequencer bus\n");
+ return ret;
+ }
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ pwrseq_debugfs_dentry = debugfs_create_file("pwrseq", 0444, NULL, NULL,
+ &pwrseq_debugfs_fops);
+#endif /* CONFIG_DEBUG_FS */
+
+ return 0;
+}
+subsys_initcall(pwrseq_init);
+
+static void __exit pwrseq_exit(void)
+{
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ debugfs_remove_recursive(pwrseq_debugfs_dentry);
+#endif /* CONFIG_DEBUG_FS */
+
+ bus_unregister(&pwrseq_bus);
+}
+module_exit(pwrseq_exit);
+
+MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
+MODULE_DESCRIPTION("Power Sequencing subsystem core");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/pwrseq/consumer.h b/include/linux/pwrseq/consumer.h
new file mode 100644
index 000000000000..7d583b4f266e
--- /dev/null
+++ b/include/linux/pwrseq/consumer.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 Linaro Ltd.
+ */
+
+#ifndef __POWER_SEQUENCING_CONSUMER_H__
+#define __POWER_SEQUENCING_CONSUMER_H__
+
+#include <linux/err.h>
+
+struct device;
+struct pwrseq_desc;
+
+#if IS_ENABLED(CONFIG_POWER_SEQUENCING)
+
+struct pwrseq_desc * __must_check
+pwrseq_get(struct device *dev, const char *target);
+void pwrseq_put(struct pwrseq_desc *desc);
+
+struct pwrseq_desc * __must_check
+devm_pwrseq_get(struct device *dev, const char *target);
+
+int pwrseq_power_on(struct pwrseq_desc *desc);
+int pwrseq_power_off(struct pwrseq_desc *desc);
+
+#else /* CONFIG_POWER_SEQUENCING */
+
+static inline struct pwrseq_desc * __must_check
+pwrseq_get(struct device *dev, const char *target)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+static inline void pwrseq_put(struct pwrseq_desc *desc)
+{
+}
+
+static inline struct pwrseq_desc * __must_check
+devm_pwrseq_get(struct device *dev, const char *target)
+{
+ return ERR_PTR(-ENOSYS);
+}
+
+static inline int pwrseq_power_on(struct pwrseq_desc *desc)
+{
+ return -ENOSYS;
+}
+
+static inline int pwrseq_power_off(struct pwrseq_desc *desc)
+{
+ return -ENOSYS;
+}
+
+#endif /* CONFIG_POWER_SEQUENCING */
+
+#endif /* __POWER_SEQUENCING_CONSUMER_H__ */
diff --git a/include/linux/pwrseq/provider.h b/include/linux/pwrseq/provider.h
new file mode 100644
index 000000000000..e627ed2f4d91
--- /dev/null
+++ b/include/linux/pwrseq/provider.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 Linaro Ltd.
+ */
+
+#ifndef __POWER_SEQUENCING_PROVIDER_H__
+#define __POWER_SEQUENCING_PROVIDER_H__
+
+struct device;
+struct module;
+struct pwrseq_device;
+
+typedef int (*pwrseq_power_state_func)(struct pwrseq_device *);
+typedef int (*pwrseq_match_func)(struct pwrseq_device *, struct device *);
+
+/**
+ * struct pwrseq_unit_data - Configuration of a single power sequencing
+ * unit.
+ * @name: Name of the unit.
+ * @deps: Units that must be enabled before this one and disabled after it
+ * in the order they come in this array.
+ * @enable: Callback running the part of the power-on sequence provided by
+ * this unit.
+ * @disable: Callback running the part of the power-off sequence provided
+ * by this unit.
+ */
+struct pwrseq_unit_data {
+ const char *name;
+ const struct pwrseq_unit_data **deps;
+ pwrseq_power_state_func enable;
+ pwrseq_power_state_func disable;
+};
+
+/**
+ * struct pwrseq_target_data - Configuration of a power sequencing target.
+ * @name: Name of the target.
+ * @unit: Final unit that this target must reach in order to be considered
+ * enabled.
+ * @post_enable: Callback run after the target unit has been enabled, *after*
+ * the state lock has been released. It's useful for implementing
+ * boot-up delays without blocking other users from powering up
+ * using the same power sequencer.
+ */
+struct pwrseq_target_data {
+ const char *name;
+ const struct pwrseq_unit_data *unit;
+ pwrseq_power_state_func post_enable;
+};
+
+/**
+ * struct pwrseq_config - Configuration used for registering a new provider.
+ * @parent: Parent device for the sequencer. Must be set.
+ * @owner: Module providing this device.
+ * @drvdata: Private driver data.
+ * @match: Provider callback used to match the consumer device to the sequencer.
+ * @targets: Array of targets for this power sequencer. Must be NULL-terminated.
+ */
+struct pwrseq_config {
+ struct device *parent;
+ struct module *owner;
+ void *drvdata;
+ pwrseq_match_func match;
+ const struct pwrseq_target_data **targets;
+};
+
+struct pwrseq_device *
+pwrseq_device_register(const struct pwrseq_config *config);
+void pwrseq_device_unregister(struct pwrseq_device *pwrseq);
+struct pwrseq_device *
+devm_pwrseq_device_register(struct device *dev,
+ const struct pwrseq_config *config);
+
+void *pwrseq_device_get_drvdata(struct pwrseq_device *pwrseq);
+
+#endif /* __POWER_SEQUENCING_PROVIDER_H__ */
--
2.40.1
^ permalink raw reply related
* [PATCH v7 12/16] PCI/pwrctl: add PCI power control core code
From: Bartosz Golaszewski @ 2024-04-10 12:46 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kalle Valo, Bjorn Andersson,
Konrad Dybcio, Liam Girdwood, Mark Brown, Catalin Marinas,
Will Deacon, Bjorn Helgaas, Bartosz Golaszewski, Saravana Kannan,
Geert Uytterhoeven, Arnd Bergmann, Neil Armstrong,
Marek Szyprowski, Alex Elder, Srini Kandagatla,
Greg Kroah-Hartman, Abel Vesa, Manivannan Sadhasivam,
Lukas Wunner, Dmitry Baryshkov, Amit Pundir, Xilin Wu
Cc: linux-bluetooth, netdev, devicetree, linux-kernel, linux-wireless,
linux-arm-msm, linux-arm-kernel, linux-pci, linux-pm,
Bartosz Golaszewski
In-Reply-To: <20240410124628.171783-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Some PCI devices must be powered-on before they can be detected on the
bus. Introduce a simple framework reusing the existing PCI OF
infrastructure.
The way this works is: a DT node representing a PCI device connected to
the port can be matched against its power control platform driver. If
the match succeeds, the driver is responsible for powering-up the device
and calling pcie_pwrctl_device_set_ready() which will trigger a PCI bus
rescan as well as subscribe to PCI bus notifications.
When the device is detected and created, we'll make it consume the same
DT node that the platform device did. When the device is bound, we'll
create a device link between it and the parent power control device.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
drivers/pci/Kconfig | 1 +
drivers/pci/Makefile | 1 +
drivers/pci/pwrctl/Kconfig | 8 +++
drivers/pci/pwrctl/Makefile | 4 ++
drivers/pci/pwrctl/core.c | 137 ++++++++++++++++++++++++++++++++++++
include/linux/pci-pwrctl.h | 51 ++++++++++++++
6 files changed, 202 insertions(+)
create mode 100644 drivers/pci/pwrctl/Kconfig
create mode 100644 drivers/pci/pwrctl/Makefile
create mode 100644 drivers/pci/pwrctl/core.c
create mode 100644 include/linux/pci-pwrctl.h
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index d35001589d88..aa4d1833f442 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -296,5 +296,6 @@ source "drivers/pci/hotplug/Kconfig"
source "drivers/pci/controller/Kconfig"
source "drivers/pci/endpoint/Kconfig"
source "drivers/pci/switch/Kconfig"
+source "drivers/pci/pwrctl/Kconfig"
endif
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 175302036890..8ddad57934a6 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \
obj-$(CONFIG_PCI) += msi/
obj-$(CONFIG_PCI) += pcie/
+obj-$(CONFIG_PCI) += pwrctl/
ifdef CONFIG_PCI
obj-$(CONFIG_PROC_FS) += proc.o
diff --git a/drivers/pci/pwrctl/Kconfig b/drivers/pci/pwrctl/Kconfig
new file mode 100644
index 000000000000..96195395af69
--- /dev/null
+++ b/drivers/pci/pwrctl/Kconfig
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+menu "PCI Power control drivers"
+
+config PCI_PWRCTL
+ tristate
+
+endmenu
diff --git a/drivers/pci/pwrctl/Makefile b/drivers/pci/pwrctl/Makefile
new file mode 100644
index 000000000000..52ae0640ef7b
--- /dev/null
+++ b/drivers/pci/pwrctl/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_PCI_PWRCTL) += pci-pwrctl-core.o
+pci-pwrctl-core-y := core.o
diff --git a/drivers/pci/pwrctl/core.c b/drivers/pci/pwrctl/core.c
new file mode 100644
index 000000000000..332e16003505
--- /dev/null
+++ b/drivers/pci/pwrctl/core.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Linaro Ltd.
+ */
+
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/pci-pwrctl.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+static int pci_pwrctl_notify(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct pci_pwrctl *pwrctl = container_of(nb, struct pci_pwrctl, nb);
+ struct device *dev = data;
+
+ if (dev_fwnode(dev) != dev_fwnode(pwrctl->dev))
+ return NOTIFY_DONE;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ /*
+ * We will have two struct device objects bound to two different
+ * drivers on different buses but consuming the same DT node. We
+ * must not bind the pins twice in this case but only once for
+ * the first device to be added.
+ *
+ * If we got here then the PCI device is the second after the
+ * power control platform device. Mark its OF node as reused.
+ */
+ dev->of_node_reused = true;
+ break;
+ case BUS_NOTIFY_BOUND_DRIVER:
+ pwrctl->link = device_link_add(dev, pwrctl->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ if (!pwrctl->link)
+ dev_err(pwrctl->dev, "Failed to add device link\n");
+ break;
+ case BUS_NOTIFY_UNBOUND_DRIVER:
+ if (pwrctl->link)
+ device_link_del(pwrctl->link);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+/**
+ * pci_pwrctl_device_set_ready() - Notify the pwrctl subsystem that the PCI
+ * device is powered-up and ready to be detected.
+ *
+ * @pwrctl: PCI power control data.
+ *
+ * Returns:
+ * 0 on success, negative error number on error.
+ *
+ * Note:
+ * This function returning 0 doesn't mean the device was detected. It means,
+ * that the bus rescan was successfully started. The device will get bound to
+ * its PCI driver asynchronously.
+ */
+int pci_pwrctl_device_set_ready(struct pci_pwrctl *pwrctl)
+{
+ int ret;
+
+ if (!pwrctl->dev)
+ return -ENODEV;
+
+ pwrctl->nb.notifier_call = pci_pwrctl_notify;
+ ret = bus_register_notifier(&pci_bus_type, &pwrctl->nb);
+ if (ret)
+ return ret;
+
+ pci_lock_rescan_remove();
+ pci_rescan_bus(to_pci_dev(pwrctl->dev->parent)->bus);
+ pci_unlock_rescan_remove();
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_pwrctl_device_set_ready);
+
+/**
+ * pci_pwrctl_device_unset_ready() - Notify the pwrctl subsystem that the PCI
+ * device is about to be powered-down.
+ *
+ * @pwrctl: PCI power control data.
+ */
+void pci_pwrctl_device_unset_ready(struct pci_pwrctl *pwrctl)
+{
+ /*
+ * We don't have to delete the link here. Typically, this function
+ * is only called when the power control device is being detached. If
+ * it is being detached then the child PCI device must have already
+ * been unbound too or the device core wouldn't let us unbind.
+ */
+ bus_unregister_notifier(&pci_bus_type, &pwrctl->nb);
+}
+EXPORT_SYMBOL_GPL(pci_pwrctl_device_unset_ready);
+
+static void devm_pci_pwrctl_device_unset_ready(void *data)
+{
+ struct pci_pwrctl *pwrctl = data;
+
+ pci_pwrctl_device_set_ready(pwrctl);
+}
+
+/**
+ * devm_pci_pwrctl_device_set_ready - Managed variant of
+ * pci_pwrctl_device_set_ready().
+ *
+ * @dev: Device managing this pwrctl provider.
+ * @pwrctl: PCI power control data.
+ *
+ * Returns:
+ * 0 on success, negative error number on error.
+ */
+int devm_pci_pwrctl_device_set_ready(struct device *dev,
+ struct pci_pwrctl *pwrctl)
+{
+ int ret;
+
+ ret = pci_pwrctl_device_set_ready(pwrctl);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev,
+ devm_pci_pwrctl_device_unset_ready,
+ pwrctl);
+}
+EXPORT_SYMBOL_GPL(devm_pci_pwrctl_device_set_ready);
+
+MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
+MODULE_DESCRIPTION("PCI Device Power Control core driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/pci-pwrctl.h b/include/linux/pci-pwrctl.h
new file mode 100644
index 000000000000..45e9cfe740e4
--- /dev/null
+++ b/include/linux/pci-pwrctl.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2024 Linaro Ltd.
+ */
+
+#ifndef __PCI_PWRCTL_H__
+#define __PCI_PWRCTL_H__
+
+#include <linux/notifier.h>
+
+struct device;
+struct device_link;
+
+/*
+ * This is a simple framework for solving the issue of PCI devices that require
+ * certain resources (regulators, GPIOs, clocks) to be enabled before the
+ * device can actually be detected on the PCI bus.
+ *
+ * The idea is to reuse the platform bus to populate OF nodes describing the
+ * PCI device and its resources, let these platform devices probe and enable
+ * relevant resources and then trigger a rescan of the PCI bus allowing for the
+ * same device (with a second associated struct device) to be registered with
+ * the PCI subsystem.
+ *
+ * To preserve a correct hierarchy for PCI power management and device reset,
+ * we create a device link between the power control platform device (parent)
+ * and the supplied PCI device (child).
+ */
+
+/**
+ * struct pci_pwrctl - PCI device power control context.
+ * @dev: Address of the power controlling device.
+ *
+ * An object of this type must be allocated by the PCI power control device and
+ * passed to the pwrctl subsystem to trigger a bus rescan and setup a device
+ * link with the device once it's up.
+ */
+struct pci_pwrctl {
+ struct device *dev;
+
+ /* Private: don't use. */
+ struct notifier_block nb;
+ struct device_link *link;
+};
+
+int pci_pwrctl_device_set_ready(struct pci_pwrctl *pwrctl);
+void pci_pwrctl_device_unset_ready(struct pci_pwrctl *pwrctl);
+int devm_pci_pwrctl_device_set_ready(struct device *dev,
+ struct pci_pwrctl *pwrctl);
+
+#endif /* __PCI_PWRCTL_H__ */
--
2.40.1
^ permalink raw reply related
* [PATCH v7 11/16] PCI/pwrctl: create platform devices for child OF nodes of the port node
From: Bartosz Golaszewski @ 2024-04-10 12:46 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kalle Valo, Bjorn Andersson,
Konrad Dybcio, Liam Girdwood, Mark Brown, Catalin Marinas,
Will Deacon, Bjorn Helgaas, Bartosz Golaszewski, Saravana Kannan,
Geert Uytterhoeven, Arnd Bergmann, Neil Armstrong,
Marek Szyprowski, Alex Elder, Srini Kandagatla,
Greg Kroah-Hartman, Abel Vesa, Manivannan Sadhasivam,
Lukas Wunner, Dmitry Baryshkov, Amit Pundir, Xilin Wu
Cc: linux-bluetooth, netdev, devicetree, linux-kernel, linux-wireless,
linux-arm-msm, linux-arm-kernel, linux-pci, linux-pm,
Bartosz Golaszewski
In-Reply-To: <20240410124628.171783-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
In preparation for introducing PCI device power control - a set of
library functions that will allow powering-up of PCI devices before
they're detected on the PCI bus - we need to populate the devices
defined on the device-tree.
We are reusing the platform bus as it provides us with all the
infrastructure we need to match the pwrctl drivers against the
compatibles from OF nodes.
These platform devices will be probed by the driver core and bound to
the PCI pwrctl drivers we'll introduce later.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
drivers/pci/bus.c | 9 +++++++++
drivers/pci/remove.c | 2 ++
2 files changed, 11 insertions(+)
diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
index 826b5016a101..3e3517567721 100644
--- a/drivers/pci/bus.c
+++ b/drivers/pci/bus.c
@@ -12,6 +12,7 @@
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/of.h>
+#include <linux/of_platform.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
@@ -354,6 +355,14 @@ void pci_bus_add_device(struct pci_dev *dev)
pci_warn(dev, "device attach failed (%d)\n", retval);
pci_dev_assign_added(dev, true);
+
+ if (pci_is_bridge(dev)) {
+ retval = of_platform_populate(dev->dev.of_node, NULL, NULL,
+ &dev->dev);
+ if (retval)
+ pci_err(dev, "failed to populate child OF nodes (%d)\n",
+ retval);
+ }
}
EXPORT_SYMBOL_GPL(pci_bus_add_device);
diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c
index d749ea8250d6..fc9db2805888 100644
--- a/drivers/pci/remove.c
+++ b/drivers/pci/remove.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/pci.h>
#include <linux/module.h>
+#include <linux/of_platform.h>
#include "pci.h"
static void pci_free_resources(struct pci_dev *dev)
@@ -22,6 +23,7 @@ static void pci_stop_dev(struct pci_dev *dev)
device_release_driver(&dev->dev);
pci_proc_detach_device(dev);
pci_remove_sysfs_dev_files(dev);
+ of_platform_depopulate(&dev->dev);
of_pci_remove_node(dev);
pci_dev_assign_added(dev, false);
--
2.40.1
^ permalink raw reply related
* [PATCH v7 10/16] PCI/pwrctl: reuse the OF node for power controlled devices
From: Bartosz Golaszewski @ 2024-04-10 12:46 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kalle Valo, Bjorn Andersson,
Konrad Dybcio, Liam Girdwood, Mark Brown, Catalin Marinas,
Will Deacon, Bjorn Helgaas, Bartosz Golaszewski, Saravana Kannan,
Geert Uytterhoeven, Arnd Bergmann, Neil Armstrong,
Marek Szyprowski, Alex Elder, Srini Kandagatla,
Greg Kroah-Hartman, Abel Vesa, Manivannan Sadhasivam,
Lukas Wunner, Dmitry Baryshkov, Amit Pundir, Xilin Wu
Cc: linux-bluetooth, netdev, devicetree, linux-kernel, linux-wireless,
linux-arm-msm, linux-arm-kernel, linux-pci, linux-pm,
Bartosz Golaszewski
In-Reply-To: <20240410124628.171783-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
With PCI power control we deal with two struct device objects bound to
two different drivers but consuming the same OF node. We must not bind
the pinctrl twice. To that end: before setting the OF node of the newly
instantiated PCI device, check if a platform device consuming the same
OF node doesn't already exist on the platform bus and - if so - mark the
PCI device as reusing the OF node.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
drivers/pci/of.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/pci/of.c b/drivers/pci/of.c
index 51e3dd0ea5ab..b908fe1ae951 100644
--- a/drivers/pci/of.c
+++ b/drivers/pci/of.c
@@ -6,6 +6,7 @@
*/
#define pr_fmt(fmt) "PCI: OF: " fmt
+#include <linux/cleanup.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/pci.h>
@@ -13,6 +14,7 @@
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/of_pci.h>
+#include <linux/platform_device.h>
#include "pci.h"
#ifdef CONFIG_PCI
@@ -25,16 +27,20 @@
*/
int pci_set_of_node(struct pci_dev *dev)
{
- struct device_node *node;
-
if (!dev->bus->dev.of_node)
return 0;
- node = of_pci_find_child_device(dev->bus->dev.of_node, dev->devfn);
+ struct device_node *node __free(device_node) =
+ of_pci_find_child_device(dev->bus->dev.of_node, dev->devfn);
if (!node)
return 0;
- device_set_node(&dev->dev, of_fwnode_handle(node));
+ struct device *pdev __free(put_device) =
+ bus_find_device_by_of_node(&platform_bus_type, node);
+ if (pdev)
+ dev->bus->dev.of_node_reused = true;
+
+ device_set_node(&dev->dev, of_fwnode_handle(no_free_ptr(node)));
return 0;
}
--
2.40.1
^ permalink raw reply related
* [PATCH v7 09/16] PCI: hold the rescan mutex when scanning for the first time
From: Bartosz Golaszewski @ 2024-04-10 12:46 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kalle Valo, Bjorn Andersson,
Konrad Dybcio, Liam Girdwood, Mark Brown, Catalin Marinas,
Will Deacon, Bjorn Helgaas, Bartosz Golaszewski, Saravana Kannan,
Geert Uytterhoeven, Arnd Bergmann, Neil Armstrong,
Marek Szyprowski, Alex Elder, Srini Kandagatla,
Greg Kroah-Hartman, Abel Vesa, Manivannan Sadhasivam,
Lukas Wunner, Dmitry Baryshkov, Amit Pundir, Xilin Wu
Cc: linux-bluetooth, netdev, devicetree, linux-kernel, linux-wireless,
linux-arm-msm, linux-arm-kernel, linux-pci, linux-pm,
Bartosz Golaszewski
In-Reply-To: <20240410124628.171783-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
With the introduction of PCI device power control drivers that will be
able to trigger the port rescan when probing, we need to hold the rescan
mutex during the initial pci_host_probe() too or the two could get in
each other's way.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
drivers/pci/probe.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 1325fbae2f28..1802900328a0 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -3066,7 +3066,9 @@ int pci_host_probe(struct pci_host_bridge *bridge)
struct pci_bus *bus, *child;
int ret;
+ pci_lock_rescan_remove();
ret = pci_scan_root_bus_bridge(bridge);
+ pci_unlock_rescan_remove();
if (ret < 0) {
dev_err(bridge->dev.parent, "Scanning root bridge failed");
return ret;
--
2.40.1
^ permalink raw reply related
* [PATCH v7 08/16] arm64: dts: qcom: qrb5165-rb5: add the Wifi node
From: Bartosz Golaszewski @ 2024-04-10 12:46 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kalle Valo, Bjorn Andersson,
Konrad Dybcio, Liam Girdwood, Mark Brown, Catalin Marinas,
Will Deacon, Bjorn Helgaas, Bartosz Golaszewski, Saravana Kannan,
Geert Uytterhoeven, Arnd Bergmann, Neil Armstrong,
Marek Szyprowski, Alex Elder, Srini Kandagatla,
Greg Kroah-Hartman, Abel Vesa, Manivannan Sadhasivam,
Lukas Wunner, Dmitry Baryshkov, Amit Pundir, Xilin Wu
Cc: linux-bluetooth, netdev, devicetree, linux-kernel, linux-wireless,
linux-arm-msm, linux-arm-kernel, linux-pci, linux-pm,
Bartosz Golaszewski
In-Reply-To: <20240410124628.171783-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Add a node for the PMU module of the QCA6391 present on the RB5 board.
Assign its LDO power outputs to the existing Bluetooth module. Add a
node for the PCIe port to sm8250.dtsi and define the WLAN node on it in
the board's .dts and also make it consume the power outputs of the PMU.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
arch/arm64/boot/dts/qcom/qrb5165-rb5.dts | 103 ++++++++++++++++++++---
arch/arm64/boot/dts/qcom/sm8250.dtsi | 10 +++
2 files changed, 102 insertions(+), 11 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
index cd0db4f31d4a..24f528fc741f 100644
--- a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
+++ b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
@@ -108,6 +108,67 @@ lt9611_3v3: lt9611-3v3 {
regulator-always-on;
};
+ qca6390-pmu {
+ compatible = "qcom,qca6390-pmu";
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&bt_en_state>, <&wlan_en_state>;
+
+ vddaon-supply = <&vreg_s6a_0p95>;
+ vddpmu-supply = <&vreg_s2f_0p95>;
+ vddrfa0p95-supply = <&vreg_s2f_0p95>;
+ vddrfa1p3-supply = <&vreg_s8c_1p3>;
+ vddrfa1p9-supply = <&vreg_s5a_1p9>;
+ vddpcie1p3-supply = <&vreg_s8c_1p3>;
+ vddpcie1p9-supply = <&vreg_s5a_1p9>;
+ vddio-supply = <&vreg_s4a_1p8>;
+
+ wlan-enable-gpios = <&tlmm 20 GPIO_ACTIVE_HIGH>;
+ bt-enable-gpios = <&tlmm 21 GPIO_ACTIVE_HIGH>;
+
+ regulators {
+ vreg_pmu_rfa_cmn: ldo0 {
+ regulator-name = "vreg_pmu_rfa_cmn";
+ };
+
+ vreg_pmu_aon_0p59: ldo1 {
+ regulator-name = "vreg_pmu_aon_0p59";
+ };
+
+ vreg_pmu_wlcx_0p8: ldo2 {
+ regulator-name = "vreg_pmu_wlcx_0p8";
+ };
+
+ vreg_pmu_wlmx_0p85: ldo3 {
+ regulator-name = "vreg_pmu_wlmx_0p85";
+ };
+
+ vreg_pmu_btcmx_0p85: ldo4 {
+ regulator-name = "vreg_pmu_btcmx_0p85";
+ };
+
+ vreg_pmu_rfa_0p8: ldo5 {
+ regulator-name = "vreg_pmu_rfa_0p8";
+ };
+
+ vreg_pmu_rfa_1p2: ldo6 {
+ regulator-name = "vreg_pmu_rfa_1p2";
+ };
+
+ vreg_pmu_rfa_1p7: ldo7 {
+ regulator-name = "vreg_pmu_rfa_1p7";
+ };
+
+ vreg_pmu_pcie_0p9: ldo8 {
+ regulator-name = "vreg_pmu_pcie_0p9";
+ };
+
+ vreg_pmu_pcie_1p8: ldo9 {
+ regulator-name = "vreg_pmu_pcie_1p8";
+ };
+ };
+ };
+
thermal-zones {
conn-thermal {
polling-delay-passive = <0>;
@@ -734,6 +795,23 @@ &pcie0_phy {
vdda-pll-supply = <&vreg_l9a_1p2>;
};
+&pcieport0 {
+ wifi@0 {
+ compatible = "pci17cb,1101";
+ reg = <0x10000 0x0 0x0 0x0 0x0>;
+
+ vddrfacmn-supply = <&vreg_pmu_rfa_cmn>;
+ vddaon-supply = <&vreg_pmu_aon_0p59>;
+ vddwlcx-supply = <&vreg_pmu_wlcx_0p8>;
+ vddwlmx-supply = <&vreg_pmu_wlmx_0p85>;
+ vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>;
+ vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>;
+ vddrfa1p7-supply = <&vreg_pmu_rfa_1p7>;
+ vddpcie0p9-supply = <&vreg_pmu_pcie_0p9>;
+ vddpcie1p8-supply = <&vreg_pmu_pcie_1p8>;
+ };
+};
+
&pcie1 {
status = "okay";
};
@@ -1303,6 +1381,14 @@ sdc2_card_det_n: sd-card-det-n-state {
function = "gpio";
bias-pull-up;
};
+
+ wlan_en_state: wlan-default-state {
+ pins = "gpio20";
+ function = "gpio";
+ drive-strength = <16>;
+ output-low;
+ bias-pull-up;
+ };
};
&uart6 {
@@ -1311,17 +1397,12 @@ &uart6 {
bluetooth {
compatible = "qcom,qca6390-bt";
- pinctrl-names = "default";
- pinctrl-0 = <&bt_en_state>;
-
- enable-gpios = <&tlmm 21 GPIO_ACTIVE_HIGH>;
-
- vddio-supply = <&vreg_s4a_1p8>;
- vddpmu-supply = <&vreg_s2f_0p95>;
- vddaon-supply = <&vreg_s6a_0p95>;
- vddrfa0p9-supply = <&vreg_s2f_0p95>;
- vddrfa1p3-supply = <&vreg_s8c_1p3>;
- vddrfa1p9-supply = <&vreg_s5a_1p9>;
+ vddrfacmn-supply = <&vreg_pmu_rfa_cmn>;
+ vddaon-supply = <&vreg_pmu_aon_0p59>;
+ vddbtcmx-supply = <&vreg_pmu_btcmx_0p85>;
+ vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>;
+ vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>;
+ vddrfa1p7-supply = <&vreg_pmu_rfa_1p7>;
};
};
diff --git a/arch/arm64/boot/dts/qcom/sm8250.dtsi b/arch/arm64/boot/dts/qcom/sm8250.dtsi
index 7f2333c9d17d..06decf15a2d3 100644
--- a/arch/arm64/boot/dts/qcom/sm8250.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8250.dtsi
@@ -2203,6 +2203,16 @@ pcie0: pcie@1c00000 {
dma-coherent;
status = "disabled";
+
+ pcieport0: pcie@0 {
+ device_type = "pci";
+ reg = <0x0 0x0 0x0 0x0 0x0>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+
+ bus-range = <0x01 0xff>;
+ };
};
pcie0_phy: phy@1c06000 {
--
2.40.1
^ permalink raw reply related
* [PATCH v7 07/16] arm64: dts: qcom: sm8650-qrd: add the Wifi node
From: Bartosz Golaszewski @ 2024-04-10 12:46 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kalle Valo, Bjorn Andersson,
Konrad Dybcio, Liam Girdwood, Mark Brown, Catalin Marinas,
Will Deacon, Bjorn Helgaas, Bartosz Golaszewski, Saravana Kannan,
Geert Uytterhoeven, Arnd Bergmann, Neil Armstrong,
Marek Szyprowski, Alex Elder, Srini Kandagatla,
Greg Kroah-Hartman, Abel Vesa, Manivannan Sadhasivam,
Lukas Wunner, Dmitry Baryshkov, Amit Pundir, Xilin Wu
Cc: linux-bluetooth, netdev, devicetree, linux-kernel, linux-wireless,
linux-arm-msm, linux-arm-kernel, linux-pci, linux-pm,
Bartosz Golaszewski
In-Reply-To: <20240410124628.171783-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Describe the ath12k WLAN on-board the WCN7850 module present on the
board.
[Neil: authored the initial version of the change]
Co-developed-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
arch/arm64/boot/dts/qcom/sm8650-qrd.dts | 89 +++++++++++++++++++++++++
arch/arm64/boot/dts/qcom/sm8650.dtsi | 10 +++
2 files changed, 99 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/sm8650-qrd.dts b/arch/arm64/boot/dts/qcom/sm8650-qrd.dts
index b07cac2e5bc8..c87d21115716 100644
--- a/arch/arm64/boot/dts/qcom/sm8650-qrd.dts
+++ b/arch/arm64/boot/dts/qcom/sm8650-qrd.dts
@@ -203,6 +203,71 @@ wcd_codec_headset_in: endpoint {
};
};
};
+
+ wcn7850-pmu {
+ compatible = "qcom,wcn7850-pmu";
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&wlan_en>;
+
+ wlan-enable-gpios = <&tlmm 16 GPIO_ACTIVE_HIGH>;
+ /*
+ * TODO Add bt-enable-gpios once the Bluetooth driver is
+ * converted to using the power sequencer.
+ */
+
+ vdd-supply = <&vreg_s4i_0p85>;
+ vddio-supply = <&vreg_l15b_1p8>;
+ vddio1p2-supply = <&vreg_l3c_1p2>;
+ vddaon-supply = <&vreg_s2c_0p8>;
+ vdddig-supply = <&vreg_s3c_0p9>;
+ vddrfa1p2-supply = <&vreg_s1c_1p2>;
+ vddrfa1p8-supply = <&vreg_s6c_1p8>;
+
+ clocks = <&rpmhcc RPMH_RF_CLK1>;
+
+ regulators {
+ vreg_pmu_rfa_cmn: ldo0 {
+ regulator-name = "vreg_pmu_rfa_cmn";
+ };
+
+ vreg_pmu_aon_0p59: ldo1 {
+ regulator-name = "vreg_pmu_aon_0p59";
+ };
+
+ vreg_pmu_wlcx_0p8: ldo2 {
+ regulator-name = "vreg_pmu_wlcx_0p8";
+ };
+
+ vreg_pmu_wlmx_0p85: ldo3 {
+ regulator-name = "vreg_pmu_wlmx_0p85";
+ };
+
+ vreg_pmu_btcmx_0p85: ldo4 {
+ regulator-name = "vreg_pmu_btcmx_0p85";
+ };
+
+ vreg_pmu_rfa_0p8: ldo5 {
+ regulator-name = "vreg_pmu_rfa_0p8";
+ };
+
+ vreg_pmu_rfa_1p2: ldo6 {
+ regulator-name = "vreg_pmu_rfa_1p2";
+ };
+
+ vreg_pmu_rfa_1p8: ldo7 {
+ regulator-name = "vreg_pmu_rfa_1p8";
+ };
+
+ vreg_pmu_pcie_0p9: ldo8 {
+ regulator-name = "vreg_pmu_pcie_0p9";
+ };
+
+ vreg_pmu_pcie_1p8: ldo9 {
+ regulator-name = "vreg_pmu_pcie_1p8";
+ };
+ };
+ };
};
&apps_rsc {
@@ -845,6 +910,23 @@ &pcie0 {
status = "okay";
};
+&pcieport0 {
+ wifi@0 {
+ compatible = "pci17cb,1107";
+ reg = <0x10000 0x0 0x0 0x0 0x0>;
+
+ vddrfacmn-supply = <&vreg_pmu_rfa_cmn>;
+ vddaon-supply = <&vreg_pmu_aon_0p59>;
+ vddwlcx-supply = <&vreg_pmu_wlcx_0p8>;
+ vddwlmx-supply = <&vreg_pmu_wlmx_0p85>;
+ vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>;
+ vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>;
+ vddrfa1p8-supply = <&vreg_pmu_rfa_1p8>;
+ vddpcie0p9-supply = <&vreg_pmu_pcie_0p9>;
+ vddpcie1p8-supply = <&vreg_pmu_pcie_1p8>;
+ };
+};
+
&pcie0_phy {
vdda-phy-supply = <&vreg_l1i_0p88>;
vdda-pll-supply = <&vreg_l3i_1p2>;
@@ -1139,6 +1221,13 @@ wcd_default: wcd-reset-n-active-state {
bias-disable;
output-low;
};
+
+ wlan_en: wlan-en-state {
+ pins = "gpio16";
+ function = "gpio";
+ drive-strength = <8>;
+ bias-pull-down;
+ };
};
&uart14 {
diff --git a/arch/arm64/boot/dts/qcom/sm8650.dtsi b/arch/arm64/boot/dts/qcom/sm8650.dtsi
index 78757f01a7d8..693857905fe9 100644
--- a/arch/arm64/boot/dts/qcom/sm8650.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8650.dtsi
@@ -2293,6 +2293,16 @@ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>,
dma-coherent;
status = "disabled";
+
+ pcieport0: pcie@0 {
+ device_type = "pci";
+ reg = <0x0 0x0 0x0 0x0 0x0>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+
+ bus-range = <0x01 0xff>;
+ };
};
pcie0_phy: phy@1c06000 {
--
2.40.1
^ permalink raw reply related
* [PATCH v7 06/16] arm64: dts: qcom: sm8550-qrd: add the Wifi node
From: Bartosz Golaszewski @ 2024-04-10 12:46 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Kalle Valo, Bjorn Andersson,
Konrad Dybcio, Liam Girdwood, Mark Brown, Catalin Marinas,
Will Deacon, Bjorn Helgaas, Bartosz Golaszewski, Saravana Kannan,
Geert Uytterhoeven, Arnd Bergmann, Neil Armstrong,
Marek Szyprowski, Alex Elder, Srini Kandagatla,
Greg Kroah-Hartman, Abel Vesa, Manivannan Sadhasivam,
Lukas Wunner, Dmitry Baryshkov, Amit Pundir, Xilin Wu
Cc: linux-bluetooth, netdev, devicetree, linux-kernel, linux-wireless,
linux-arm-msm, linux-arm-kernel, linux-pci, linux-pm,
Bartosz Golaszewski
In-Reply-To: <20240410124628.171783-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Describe the ath12k WLAN on-board the WCN7850 module present on the
board.
[Neil: authored the initial version of the change]
Co-developed-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
---
arch/arm64/boot/dts/qcom/sm8550-qrd.dts | 97 +++++++++++++++++++++++++
arch/arm64/boot/dts/qcom/sm8550.dtsi | 10 +++
2 files changed, 107 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/sm8550-qrd.dts b/arch/arm64/boot/dts/qcom/sm8550-qrd.dts
index 92f015017418..caac40a799a5 100644
--- a/arch/arm64/boot/dts/qcom/sm8550-qrd.dts
+++ b/arch/arm64/boot/dts/qcom/sm8550-qrd.dts
@@ -214,6 +214,68 @@ vph_pwr: vph-pwr-regulator {
regulator-always-on;
regulator-boot-on;
};
+
+ wcn7850-pmu {
+ compatible = "qcom,wcn7850-pmu";
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&wlan_en>, <&pmk8550_sleep_clk>;
+
+ wlan-enable-gpios = <&tlmm 80 GPIO_ACTIVE_HIGH>;
+ /*
+ * TODO Add bt-enable-gpios once the Bluetooth driver is
+ * converted to using the power sequencer.
+ */
+
+ vdd-supply = <&vreg_s5g_0p85>;
+ vddio-supply = <&vreg_l15b_1p8>;
+ vddaon-supply = <&vreg_s2g_0p85>;
+ vdddig-supply = <&vreg_s4e_0p95>;
+ vddrfa1p2-supply = <&vreg_s4g_1p25>;
+ vddrfa1p8-supply = <&vreg_s6g_1p86>;
+
+ regulators {
+ vreg_pmu_rfa_cmn: ldo0 {
+ regulator-name = "vreg_pmu_rfa_cmn";
+ };
+
+ vreg_pmu_aon_0p59: ldo1 {
+ regulator-name = "vreg_pmu_aon_0p59";
+ };
+
+ vreg_pmu_wlcx_0p8: ldo2 {
+ regulator-name = "vreg_pmu_wlcx_0p8";
+ };
+
+ vreg_pmu_wlmx_0p85: ldo3 {
+ regulator-name = "vreg_pmu_wlmx_0p85";
+ };
+
+ vreg_pmu_btcmx_0p85: ldo4 {
+ regulator-name = "vreg_pmu_btcmx_0p85";
+ };
+
+ vreg_pmu_rfa_0p8: ldo5 {
+ regulator-name = "vreg_pmu_rfa_0p8";
+ };
+
+ vreg_pmu_rfa_1p2: ldo6 {
+ regulator-name = "vreg_pmu_rfa_1p2";
+ };
+
+ vreg_pmu_rfa_1p8: ldo7 {
+ regulator-name = "vreg_pmu_rfa_1p8";
+ };
+
+ vreg_pmu_pcie_0p9: ldo8 {
+ regulator-name = "vreg_pmu_pcie_0p9";
+ };
+
+ vreg_pmu_pcie_1p8: ldo9 {
+ regulator-name = "vreg_pmu_pcie_1p8";
+ };
+ };
+ };
};
&apps_rsc {
@@ -824,6 +886,23 @@ &pcie0 {
status = "okay";
};
+&pcieport0 {
+ wifi@0 {
+ compatible = "pci17cb,1107";
+ reg = <0x10000 0x0 0x0 0x0 0x0>;
+
+ vddrfacmn-supply = <&vreg_pmu_rfa_cmn>;
+ vddaon-supply = <&vreg_pmu_aon_0p59>;
+ vddwlcx-supply = <&vreg_pmu_wlcx_0p8>;
+ vddwlmx-supply = <&vreg_pmu_wlmx_0p85>;
+ vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>;
+ vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>;
+ vddrfa1p8-supply = <&vreg_pmu_rfa_1p8>;
+ vddpcie0p9-supply = <&vreg_pmu_pcie_0p9>;
+ vddpcie1p8-supply = <&vreg_pmu_pcie_1p8>;
+ };
+};
+
&pcie0_phy {
vdda-phy-supply = <&vreg_l1e_0p88>;
vdda-pll-supply = <&vreg_l3e_1p2>;
@@ -911,6 +990,17 @@ &pcie_1_phy_aux_clk {
clock-frequency = <1000>;
};
+&pmk8550_gpios {
+ pmk8550_sleep_clk: sleep-clk-state {
+ pins = "gpio3";
+ function = "func1";
+ input-disable;
+ output-enable;
+ bias-disable;
+ power-source = <0>;
+ };
+};
+
&qupv3_id_0 {
status = "okay";
};
@@ -1084,6 +1174,13 @@ wcd_default: wcd-reset-n-active-state {
bias-disable;
output-low;
};
+
+ wlan_en: wlan-en-state {
+ pins = "gpio80";
+ function = "gpio";
+ drive-strength = <8>;
+ bias-pull-down;
+ };
};
&uart7 {
diff --git a/arch/arm64/boot/dts/qcom/sm8550.dtsi b/arch/arm64/boot/dts/qcom/sm8550.dtsi
index 5cae8d773cec..f09406fd0ca6 100644
--- a/arch/arm64/boot/dts/qcom/sm8550.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8550.dtsi
@@ -1770,6 +1770,16 @@ pcie0: pcie@1c00000 {
phy-names = "pciephy";
status = "disabled";
+
+ pcieport0: pcie@0 {
+ device_type = "pci";
+ reg = <0x0 0x0 0x0 0x0 0x0>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+
+ bus-range = <0x01 0xff>;
+ };
};
pcie0_phy: phy@1c06000 {
--
2.40.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox