Linux-PHY Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v8 5/7] i2c: omap: switch to new generic helper for getting selected mux-state
From: Josua Mayer @ 2026-02-03 13:01 UTC (permalink / raw)
  To: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
	Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
	Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
	Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang
  Cc: Yazan Shhady, Jon Nettleton, Mikhail Anikin, linux-can, linux-phy,
	linux-kernel, linux-omap, linux-i2c, linux-mmc, devicetree,
	linux-renesas-soc, Josua Mayer
In-Reply-To: <20260203-rz-sdio-mux-v8-0-024ea405863e@solid-run.com>

Multiplexer subsystem has added generic helper functions for getting an
already selected mux-state object.

Replace existing logic in probe with the equivalent helper function.

There is a functional difference in that the mux is now automatically
deselected on release, replacing the explicit mux_state_deselect call.

This change is only compile-tested.

Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Reviewed-by: Andreas Kemnade <andreas@kemnade.info>
Signed-off-by: Josua Mayer <josua@solid-run.com>
---
 drivers/i2c/busses/i2c-omap.c | 24 +++++-------------------
 1 file changed, 5 insertions(+), 19 deletions(-)

diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c
index d9f590f0c384..f02d294db42a 100644
--- a/drivers/i2c/busses/i2c-omap.c
+++ b/drivers/i2c/busses/i2c-omap.c
@@ -1453,27 +1453,16 @@ omap_i2c_probe(struct platform_device *pdev)
 				       (1000 * omap->speed / 8);
 	}
 
-	if (of_property_present(node, "mux-states")) {
-		struct mux_state *mux_state;
-
-		mux_state = devm_mux_state_get(&pdev->dev, NULL);
-		if (IS_ERR(mux_state)) {
-			r = PTR_ERR(mux_state);
-			dev_dbg(&pdev->dev, "failed to get I2C mux: %d\n", r);
-			goto err_put_pm;
-		}
-		omap->mux_state = mux_state;
-		r = mux_state_select(omap->mux_state);
-		if (r) {
-			dev_err(&pdev->dev, "failed to select I2C mux: %d\n", r);
-			goto err_put_pm;
-		}
+	omap->mux_state = devm_mux_state_get_optional_selected(&pdev->dev, NULL);
+	if (IS_ERR(omap->mux_state)) {
+		r = PTR_ERR(omap->mux_state);
+		goto err_put_pm;
 	}
 
 	/* reset ASAP, clearing any IRQs */
 	r = omap_i2c_init(omap);
 	if (r)
-		goto err_mux_state_deselect;
+		goto err_put_pm;
 
 	if (omap->rev < OMAP_I2C_OMAP1_REV_2)
 		r = devm_request_irq(&pdev->dev, omap->irq, omap_i2c_omap1_isr,
@@ -1515,9 +1504,6 @@ omap_i2c_probe(struct platform_device *pdev)
 
 err_unuse_clocks:
 	omap_i2c_write_reg(omap, OMAP_I2C_CON_REG, 0);
-err_mux_state_deselect:
-	if (omap->mux_state)
-		mux_state_deselect(omap->mux_state);
 err_put_pm:
 	pm_runtime_put_sync(omap->dev);
 err_disable_pm:

-- 
2.43.0



-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH v8 4/7] phy: can-transceiver: drop temporary helper getting optional mux-state
From: Josua Mayer @ 2026-02-03 13:01 UTC (permalink / raw)
  To: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
	Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
	Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
	Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang
  Cc: Yazan Shhady, Jon Nettleton, Mikhail Anikin, linux-can, linux-phy,
	linux-kernel, linux-omap, linux-i2c, linux-mmc, devicetree,
	linux-renesas-soc, Josua Mayer
In-Reply-To: <20260203-rz-sdio-mux-v8-0-024ea405863e@solid-run.com>

Multiplexer subsystem has now added helpers for getting managed optional
mux-state.

Switch to the new devm_mux_state_get_optional helper.

This change is only compile-tested.

Acked-by: Vinod Koul <vkoul@kernel.org>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Signed-off-by: Josua Mayer <josua@solid-run.com>
---
 drivers/phy/phy-can-transceiver.c | 12 +-----------
 1 file changed, 1 insertion(+), 11 deletions(-)

diff --git a/drivers/phy/phy-can-transceiver.c b/drivers/phy/phy-can-transceiver.c
index 81591d247128..2b52e47f247a 100644
--- a/drivers/phy/phy-can-transceiver.c
+++ b/drivers/phy/phy-can-transceiver.c
@@ -126,16 +126,6 @@ static const struct of_device_id can_transceiver_phy_ids[] = {
 };
 MODULE_DEVICE_TABLE(of, can_transceiver_phy_ids);
 
-/* Temporary wrapper until the multiplexer subsystem supports optional muxes */
-static inline struct mux_state *
-temp_devm_mux_state_get_optional(struct device *dev, const char *mux_name)
-{
-	if (!of_property_present(dev->of_node, "mux-states"))
-		return NULL;
-
-	return devm_mux_state_get(dev, mux_name);
-}
-
 static struct phy *can_transceiver_phy_xlate(struct device *dev,
 					     const struct of_phandle_args *args)
 {
@@ -183,7 +173,7 @@ static int can_transceiver_phy_probe(struct platform_device *pdev)
 	priv->num_ch = num_ch;
 	platform_set_drvdata(pdev, priv);
 
-	mux_state = temp_devm_mux_state_get_optional(dev, NULL);
+	mux_state = devm_mux_state_get_optional(dev, NULL);
 	if (IS_ERR(mux_state))
 		return PTR_ERR(mux_state);
 

-- 
2.43.0



-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH v8 1/7] phy: can-transceiver: rename temporary helper function to avoid conflict
From: Josua Mayer @ 2026-02-03 13:01 UTC (permalink / raw)
  To: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
	Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
	Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
	Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang
  Cc: Yazan Shhady, Jon Nettleton, Mikhail Anikin, linux-can, linux-phy,
	linux-kernel, linux-omap, linux-i2c, linux-mmc, devicetree,
	linux-renesas-soc, Josua Mayer
In-Reply-To: <20260203-rz-sdio-mux-v8-0-024ea405863e@solid-run.com>

Rename the temporary devm_mux_state_get_optional function to avoid
conflict with upcoming implementation in multiplexer subsystem.

Acked-by: Vinod Koul <vkoul@kernel.org>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Signed-off-by: Josua Mayer <josua@solid-run.com>
---
 drivers/phy/phy-can-transceiver.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/phy/phy-can-transceiver.c b/drivers/phy/phy-can-transceiver.c
index 330356706ad7..81591d247128 100644
--- a/drivers/phy/phy-can-transceiver.c
+++ b/drivers/phy/phy-can-transceiver.c
@@ -128,7 +128,7 @@ MODULE_DEVICE_TABLE(of, can_transceiver_phy_ids);
 
 /* Temporary wrapper until the multiplexer subsystem supports optional muxes */
 static inline struct mux_state *
-devm_mux_state_get_optional(struct device *dev, const char *mux_name)
+temp_devm_mux_state_get_optional(struct device *dev, const char *mux_name)
 {
 	if (!of_property_present(dev->of_node, "mux-states"))
 		return NULL;
@@ -183,7 +183,7 @@ static int can_transceiver_phy_probe(struct platform_device *pdev)
 	priv->num_ch = num_ch;
 	platform_set_drvdata(pdev, priv);
 
-	mux_state = devm_mux_state_get_optional(dev, NULL);
+	mux_state = temp_devm_mux_state_get_optional(dev, NULL);
 	if (IS_ERR(mux_state))
 		return PTR_ERR(mux_state);
 

-- 
2.43.0



-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH v8 3/7] mux: add help text for MULTIPLEXER config option
From: Josua Mayer @ 2026-02-03 13:01 UTC (permalink / raw)
  To: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
	Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
	Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
	Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang
  Cc: Yazan Shhady, Jon Nettleton, Mikhail Anikin, linux-can, linux-phy,
	linux-kernel, linux-omap, linux-i2c, linux-mmc, devicetree,
	linux-renesas-soc, Josua Mayer
In-Reply-To: <20260203-rz-sdio-mux-v8-0-024ea405863e@solid-run.com>

Add prompt and help text for CONFIG_MULTIPLEXER to allow enabling this
option thorugh the kernel configuration without explicit "select" driver
dependencies.

Select it by default when COMPILE_TEST is set for better coverage.

Signed-off-by: Josua Mayer <josua@solid-run.com>
---
 drivers/mux/Kconfig | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
index c68132e38138..e31c46820bdf 100644
--- a/drivers/mux/Kconfig
+++ b/drivers/mux/Kconfig
@@ -4,7 +4,13 @@
 #
 
 config MULTIPLEXER
-	tristate
+	tristate "Generic Multiplexer Support" if COMPILE_TEST
+	help
+	  This framework is designed to abstract multiplexer handling for
+	  devices via various GPIO-, MMIO/Regmap or specific multiplexer
+	  controller chips.
+
+	  If unsure, say no.
 
 menu "Multiplexer drivers"
 	depends on MULTIPLEXER

-- 
2.43.0



-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH v8 2/7] mux: Add helper functions for getting optional and selected mux-state
From: Josua Mayer @ 2026-02-03 13:01 UTC (permalink / raw)
  To: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
	Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
	Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
	Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang
  Cc: Yazan Shhady, Jon Nettleton, Mikhail Anikin, linux-can, linux-phy,
	linux-kernel, linux-omap, linux-i2c, linux-mmc, devicetree,
	linux-renesas-soc, Josua Mayer
In-Reply-To: <20260203-rz-sdio-mux-v8-0-024ea405863e@solid-run.com>

In-tree phy-can-transceiver driver has already implemented a local
version of devm_mux_state_get_optional.

The omap-i2c driver gets and selects an optional mux in its probe
function without using any helper.

Add new helper functions covering both aforementioned use-cases:

- mux_control_get_optional:
  Get a mux-control if specified in dt, return NULL otherwise.
- devm_mux_state_get_optional:
  Get a mux-state if specified in dt, return NULL otherwise.
- devm_mux_state_get_selected:
  Get and select a mux-state specified in dt, return error otherwise.
- devm_mux_state_get_optional_selected:
  Get and select a mux-state if specified in dt, return error or NULL.

Existing mux_get helper function is changed to take an extra argument
indicating whether the mux is optional.
In this case no error is printed, and NULL returned in case of ENOENT.

Calling code is adapted to handle NULL return case, and to pass optional
argument as required.

To support automatic deselect for _selected helper, a new structure is
created storing an exit pointer similar to clock core which is called on
release.

To facilitate code sharing between optional/mandatory/selected helpers,
a new internal helper function is added to handle quiet (optional) and
verbose (mandatory) errors, as well as storing the correct callback for
devm release: __devm_mux_state_get

Due to this structure devm_mux_state_get_*_selected can no longer print
a useful error message when select fails. Instead callers should print
errors where needed.

Commit e153fdea9db04 ("phy: can-transceiver: Re-instate "mux-states"
property presence check") noted that "mux_get() always prints an error
message in case of an error, including when the property is not present,
confusing the user."

The first error message covers the case that a mux name is not matched
in dt. The second error message is based on of_parse_phandle_with_args
return value.

In optional case no error is printed and NULL is returned.
This ensures that the new helper functions will not confuse the user
either.

With the addition of optional helper functions it became clear that
drivers should compile and link even if CONFIG_MULTIPLEXER was not enabled.
Add stubs for all symbols exported by mux core.

Signed-off-by: Josua Mayer <josua@solid-run.com>
---
 drivers/mux/core.c           | 205 ++++++++++++++++++++++++++++++++++++-------
 include/linux/mux/consumer.h | 108 ++++++++++++++++++++++-
 2 files changed, 278 insertions(+), 35 deletions(-)

diff --git a/drivers/mux/core.c b/drivers/mux/core.c
index a3840fe0995f..2e8295e3aabe 100644
--- a/drivers/mux/core.c
+++ b/drivers/mux/core.c
@@ -46,6 +46,16 @@ static const struct class mux_class = {
 	.name = "mux",
 };
 
+/**
+ * struct devm_mux_state_state -	Tracks managed resources for mux-state objects.
+ * @mstate:				Pointer to a mux state.
+ * @exit:				An optional callback to execute before free.
+ */
+struct devm_mux_state_state {
+	struct mux_state *mstate;
+	int (*exit)(struct mux_state *mstate);
+};
+
 static DEFINE_IDA(mux_ida);
 
 static int __init mux_init(void)
@@ -516,17 +526,19 @@ static struct mux_chip *of_find_mux_chip_by_node(struct device_node *np)
 	return dev ? to_mux_chip(dev) : NULL;
 }
 
-/*
+/**
  * mux_get() - Get the mux-control for a device.
  * @dev: The device that needs a mux-control.
  * @mux_name: The name identifying the mux-control.
  * @state: Pointer to where the requested state is returned, or NULL when
  *         the required multiplexer states are handled by other means.
+ * @optional: Whether to return NULL and silence errors when mux doesn't exist.
  *
- * Return: A pointer to the mux-control, or an ERR_PTR with a negative errno.
+ * Return: Pointer to the mux-control on success, an ERR_PTR with a negative errno on error,
+ * or NULL if optional is true and mux doesn't exist.
  */
 static struct mux_control *mux_get(struct device *dev, const char *mux_name,
-				   unsigned int *state)
+				   unsigned int *state, bool optional)
 {
 	struct device_node *np = dev->of_node;
 	struct of_phandle_args args;
@@ -542,7 +554,9 @@ static struct mux_control *mux_get(struct device *dev, const char *mux_name,
 		else
 			index = of_property_match_string(np, "mux-control-names",
 							 mux_name);
-		if (index < 0) {
+		if (index < 0 && optional) {
+			return NULL;
+		} else if (index < 0) {
 			dev_err(dev, "mux controller '%s' not found\n",
 				mux_name);
 			return ERR_PTR(index);
@@ -558,8 +572,12 @@ static struct mux_control *mux_get(struct device *dev, const char *mux_name,
 						 "mux-controls", "#mux-control-cells",
 						 index, &args);
 	if (ret) {
+		if (optional && ret == -ENOENT)
+			return NULL;
+
 		dev_err(dev, "%pOF: failed to get mux-%s %s(%i)\n",
-			np, state ? "state" : "control", mux_name ?: "", index);
+			np, state ? "state" : "control",
+			mux_name ?: "", index);
 		return ERR_PTR(ret);
 	}
 
@@ -617,10 +635,28 @@ static struct mux_control *mux_get(struct device *dev, const char *mux_name,
  */
 struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
 {
-	return mux_get(dev, mux_name, NULL);
+	struct mux_control *mux = mux_get(dev, mux_name, NULL, false);
+
+	if (!mux)
+		return ERR_PTR(-ENOENT);
+
+	return mux;
 }
 EXPORT_SYMBOL_GPL(mux_control_get);
 
+/**
+ * mux_control_get_optional() - Get the optional mux-control for a device.
+ * @dev: The device that needs a mux-control.
+ * @mux_name: The name identifying the mux-control.
+ *
+ * Return: A pointer to the mux-control, an ERR_PTR with a negative errno.
+ */
+struct mux_control *mux_control_get_optional(struct device *dev, const char *mux_name)
+{
+	return mux_get(dev, mux_name, NULL, true);
+}
+EXPORT_SYMBOL_GPL(mux_control_get_optional);
+
 /**
  * mux_control_put() - Put away the mux-control for good.
  * @mux: The mux-control to put away.
@@ -657,10 +693,13 @@ struct mux_control *devm_mux_control_get(struct device *dev,
 	if (!ptr)
 		return ERR_PTR(-ENOMEM);
 
-	mux = mux_control_get(dev, mux_name);
+	mux = mux_get(dev, mux_name, NULL, false);
 	if (IS_ERR(mux)) {
 		devres_free(ptr);
 		return mux;
+	} else if (!mux) {
+		devres_free(ptr);
+		return ERR_PTR(-ENOENT);
 	}
 
 	*ptr = mux;
@@ -670,14 +709,16 @@ struct mux_control *devm_mux_control_get(struct device *dev,
 }
 EXPORT_SYMBOL_GPL(devm_mux_control_get);
 
-/*
+/**
  * mux_state_get() - Get the mux-state for a device.
  * @dev: The device that needs a mux-state.
  * @mux_name: The name identifying the mux-state.
+ * @optional: Whether to return NULL and silence errors when mux doesn't exist.
  *
- * Return: A pointer to the mux-state, or an ERR_PTR with a negative errno.
+ * Return: Pointer to the mux-state on success, an ERR_PTR with a negative errno on error,
+ * or NULL if optional is true and mux doesn't exist.
  */
-static struct mux_state *mux_state_get(struct device *dev, const char *mux_name)
+static struct mux_state *mux_state_get(struct device *dev, const char *mux_name, bool optional)
 {
 	struct mux_state *mstate;
 
@@ -685,12 +726,16 @@ static struct mux_state *mux_state_get(struct device *dev, const char *mux_name)
 	if (!mstate)
 		return ERR_PTR(-ENOMEM);
 
-	mstate->mux = mux_get(dev, mux_name, &mstate->state);
+	mstate->mux = mux_get(dev, mux_name, &mstate->state, optional);
 	if (IS_ERR(mstate->mux)) {
-		int err = PTR_ERR(mstate->mux);
-
 		kfree(mstate);
-		return ERR_PTR(err);
+		return ERR_CAST(mstate->mux);
+	} else if (optional && !mstate->mux) {
+		kfree(mstate);
+		return NULL;
+	} else if (!mstate->mux) {
+		kfree(mstate);
+		return ERR_PTR(-ENOENT);
 	}
 
 	return mstate;
@@ -710,9 +755,66 @@ static void mux_state_put(struct mux_state *mstate)
 
 static void devm_mux_state_release(struct device *dev, void *res)
 {
-	struct mux_state *mstate = *(struct mux_state **)res;
+	struct devm_mux_state_state *devm_state = res;
 
+	if (devm_state->exit)
+		devm_state->exit(devm_state->mstate);
+
+	mux_state_put(devm_state->mstate);
+}
+
+/**
+ * __devm_mux_state_get() - Get the optional mux-state for a device,
+ *			    with resource management.
+ * @dev: The device that needs a mux-state.
+ * @mux_name: The name identifying the mux-state.
+ * @optional: Whether to return NULL and silence errors when mux doesn't exist.
+ * @init: Optional function pointer for mux-state object initialisation.
+ * @exit: Optional function pointer for mux-state object cleanup on release.
+ *
+ * Return: Pointer to the mux-state on success, an ERR_PTR with a negative errno on error,
+ * or NULL if optional is true and mux doesn't exist.
+ */
+static struct mux_state *__devm_mux_state_get(struct device *dev, const char *mux_name,
+					      bool optional,
+					      int (*init)(struct mux_state *mstate),
+					      int (*exit)(struct mux_state *mstate))
+{
+	struct devm_mux_state_state *devm_state;
+	struct mux_state *mstate;
+	int ret;
+
+	mstate = mux_state_get(dev, mux_name, optional);
+	if (IS_ERR(mstate))
+		return ERR_CAST(mstate);
+	else if (optional && !mstate)
+		return NULL;
+	else if (!mstate)
+		return ERR_PTR(-ENOENT);
+
+	devm_state = devres_alloc(devm_mux_state_release, sizeof(*devm_state), GFP_KERNEL);
+	if (!devm_state) {
+		ret = -ENOMEM;
+		goto err_devres_alloc;
+	}
+
+	if (init) {
+		ret = init(mstate);
+		if (ret)
+			goto err_mux_state_init;
+	}
+
+	devm_state->mstate = mstate;
+	devm_state->exit = exit;
+	devres_add(dev, devm_state);
+
+	return mstate;
+
+err_mux_state_init:
+	devres_free(devm_state);
+err_devres_alloc:
 	mux_state_put(mstate);
+	return ERR_PTR(ret);
 }
 
 /**
@@ -722,28 +824,69 @@ static void devm_mux_state_release(struct device *dev, void *res)
  * @mux_name: The name identifying the mux-control.
  *
  * Return: Pointer to the mux-state, or an ERR_PTR with a negative errno.
+ *
+ * The mux-state will automatically be freed on release.
  */
-struct mux_state *devm_mux_state_get(struct device *dev,
-				     const char *mux_name)
+struct mux_state *devm_mux_state_get(struct device *dev, const char *mux_name)
 {
-	struct mux_state **ptr, *mstate;
-
-	ptr = devres_alloc(devm_mux_state_release, sizeof(*ptr), GFP_KERNEL);
-	if (!ptr)
-		return ERR_PTR(-ENOMEM);
+	return __devm_mux_state_get(dev, mux_name, false, NULL, NULL);
+}
+EXPORT_SYMBOL_GPL(devm_mux_state_get);
 
-	mstate = mux_state_get(dev, mux_name);
-	if (IS_ERR(mstate)) {
-		devres_free(ptr);
-		return mstate;
-	}
+/**
+ * devm_mux_state_get_optional() - Get the optional mux-state for a device,
+ *				   with resource management.
+ * @dev: The device that needs a mux-state.
+ * @mux_name: The name identifying the mux-state.
+ *
+ * Return: Pointer to the mux-state on success, an ERR_PTR with a negative errno on error,
+ * or NULL if mux doesn't exist.
+ *
+ * The mux-state will automatically be freed on release.
+ */
+struct mux_state *devm_mux_state_get_optional(struct device *dev, const char *mux_name)
+{
+	return __devm_mux_state_get(dev, mux_name, true, NULL, NULL);
+}
+EXPORT_SYMBOL_GPL(devm_mux_state_get_optional);
 
-	*ptr = mstate;
-	devres_add(dev, ptr);
+/**
+ * devm_mux_state_get_selected() - Get the mux-state for a device, with
+ *				   resource management.
+ * @dev: The device that needs a mux-state.
+ * @mux_name: The name identifying the mux-state.
+ *
+ * Return: Pointer to the mux-state, or an ERR_PTR with a negative errno.
+ *
+ * The returned mux-state (if valid) is already selected.
+ *
+ * The mux-state will automatically be deselected and freed on release.
+ */
+struct mux_state *devm_mux_state_get_selected(struct device *dev, const char *mux_name)
+{
+	return __devm_mux_state_get(dev, mux_name, false, mux_state_select, mux_state_deselect);
+}
+EXPORT_SYMBOL_GPL(devm_mux_state_get_selected);
 
-	return mstate;
+/**
+ * devm_mux_state_get_optional_selected() - Get the optional mux-state for
+ *					    a device, with resource management.
+ * @dev: The device that needs a mux-state.
+ * @mux_name: The name identifying the mux-state.
+ *
+ * Return: Pointer to the mux-state on success, an ERR_PTR with a negative errno on error,
+ * or NULL if mux doesn't exist.
+ *
+ * The returned mux-state (if valid) is already selected.
+ *
+ * The mux-state will automatically be deselected and freed on release.
+ */
+struct mux_state *devm_mux_state_get_optional_selected(struct device *dev,
+						       const char *mux_name)
+{
+	return __devm_mux_state_get(dev, mux_name, true, mux_state_select, mux_state_deselect);
 }
-EXPORT_SYMBOL_GPL(devm_mux_state_get);
+EXPORT_SYMBOL_GPL(devm_mux_state_get_optional_selected);
 
 /*
  * Using subsys_initcall instead of module_init here to try to ensure - for
diff --git a/include/linux/mux/consumer.h b/include/linux/mux/consumer.h
index 2e25c838f831..3ede55b907eb 100644
--- a/include/linux/mux/consumer.h
+++ b/include/linux/mux/consumer.h
@@ -16,6 +16,8 @@ struct device;
 struct mux_control;
 struct mux_state;
 
+#ifdef CONFIG_MULTIPLEXER
+
 unsigned int mux_control_states(struct mux_control *mux);
 int __must_check mux_control_select_delay(struct mux_control *mux,
 					  unsigned int state,
@@ -54,11 +56,109 @@ int mux_control_deselect(struct mux_control *mux);
 int mux_state_deselect(struct mux_state *mstate);
 
 struct mux_control *mux_control_get(struct device *dev, const char *mux_name);
+struct mux_control *mux_control_get_optional(struct device *dev, const char *mux_name);
 void mux_control_put(struct mux_control *mux);
 
-struct mux_control *devm_mux_control_get(struct device *dev,
-					 const char *mux_name);
-struct mux_state *devm_mux_state_get(struct device *dev,
-				     const char *mux_name);
+struct mux_control *devm_mux_control_get(struct device *dev, const char *mux_name);
+struct mux_state *devm_mux_state_get(struct device *dev, const char *mux_name);
+struct mux_state *devm_mux_state_get_optional(struct device *dev, const char *mux_name);
+struct mux_state *devm_mux_state_get_selected(struct device *dev, const char *mux_name);
+struct mux_state *devm_mux_state_get_optional_selected(struct device *dev, const char *mux_name);
+
+#else
+
+static inline unsigned int mux_control_states(struct mux_control *mux)
+{
+	return 0;
+}
+static inline int __must_check mux_control_select_delay(struct mux_control *mux,
+							unsigned int state, unsigned int delay_us)
+{
+	return -EOPNOTSUPP;
+}
+static inline int __must_check mux_state_select_delay(struct mux_state *mstate,
+						      unsigned int delay_us)
+{
+	return -EOPNOTSUPP;
+}
+static inline int __must_check mux_control_try_select_delay(struct mux_control *mux,
+							    unsigned int state,
+							    unsigned int delay_us)
+{
+	return -EOPNOTSUPP;
+}
+static inline int __must_check mux_state_try_select_delay(struct mux_state *mstate,
+							  unsigned int delay_us)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int __must_check mux_control_select(struct mux_control *mux,
+						  unsigned int state)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int __must_check mux_state_select(struct mux_state *mstate)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int __must_check mux_control_try_select(struct mux_control *mux,
+						      unsigned int state)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int __must_check mux_state_try_select(struct mux_state *mstate)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int mux_control_deselect(struct mux_control *mux)
+{
+	return -EOPNOTSUPP;
+}
+static inline int mux_state_deselect(struct mux_state *mstate)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+static inline struct mux_control *mux_control_get_optional(struct device *dev,
+							   const char *mux_name)
+{
+	return NULL;
+}
+static inline void mux_control_put(struct mux_control *mux) {}
+
+static inline struct mux_control *devm_mux_control_get(struct device *dev, const char *mux_name)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+static inline struct mux_state *devm_mux_state_get(struct device *dev, const char *mux_name)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+static inline struct mux_state *devm_mux_state_get_optional(struct device *dev,
+							    const char *mux_name)
+{
+	return NULL;
+}
+static inline struct mux_state *devm_mux_state_get_selected(struct device *dev,
+							    const char *mux_name)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+static inline struct mux_state *devm_mux_state_get_optional_selected(struct device *dev,
+								     const char *mux_name)
+{
+	return NULL;
+}
+
+#endif /* CONFIG_MULTIPLEXER */
 
 #endif /* _LINUX_MUX_CONSUMER_H */

-- 
2.43.0



-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH v8 0/7] mmc: host: renesas_sdhi_core: support configuring an optional sdio mux
From: Josua Mayer @ 2026-02-03 13:01 UTC (permalink / raw)
  To: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
	Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
	Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
	Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang
  Cc: Yazan Shhady, Jon Nettleton, Mikhail Anikin, linux-can, linux-phy,
	linux-kernel, linux-omap, linux-i2c, linux-mmc, devicetree,
	linux-renesas-soc, Josua Mayer

Some Renesas SoC based boards mux SD and eMMC on a single sdio
controller, exposing user control by dip switch and software control by
gpio.

Purpose is to simplify development and provisioning by selecting boot
media at power-on, and again before starting linux.

Add binding and driver support for linking a (gpio) mux to renesas sdio
controller.

Introduce generic helper functions for getting managed and selected
mux-state objects, and switch i2c-omap and phy-can-transceiver drivers.

Signed-off-by: Josua Mayer <josua@solid-run.com>
---
Changes in v8:
- Add defensive null checks for all non-optional calls to internal
  mux_get function.
- Document NULL return value on applicable functions.
- Avoid IS_ERR_OR_NULL and ERR_PTR(0) to disarm smatch errors.
- Link to v7: https://lore.kernel.org/r/20260128-rz-sdio-mux-v7-0-92ebb6da0df8@solid-run.com

Changes in v7:
- picked up reviewed-tags
- fix Kconfig change to add the missing prompt for CONFIG_MULTIPLEXER,
  and enable it by default when COMPILE_TEST is set.
  (Reported-by: Geert Uytterhoeven <geert@linux-m68k.org>)
- fix another kernel build robot warning: undocumented C struct member
- Link to v6: https://lore.kernel.org/r/20260121-rz-sdio-mux-v6-0-38aa39527928@solid-run.com

Changes in v6:
- replaced /* with /** for devm_mux_state_state function description.
- collected review tags.
- fixed checkpatch warnings (space-before-tab, void-return).
  (Reported-by: Geert Uytterhoeven)
- fixed use-after-free in mux core mux_get function.
  (Reported-by: Geert Uytterhoeven)
- fix mux helper error path uninitialised return code variable.
  (Reported-by: kernel test robot <lkp@intel.com>)
- Link to v5: https://lore.kernel.org/r/20260118-rz-sdio-mux-v5-0-3c37e8872683@solid-run.com

Changes in v5:
- implemented automatic mux deselect for devm_*_selected.
  (Reported-by: Wolfram Sang <wsa+renesas@sang-engineering.com>)
- because of semantic changes I dropped reviewed and acks from omap-i2c
  patch (Andreas Kemnade / Wolfram Sang).
- fix invalid return value in void function for mux helper stubs
  (Reported-by: kernel test robot <lkp@intel.com>)
- Link to v4: https://lore.kernel.org/r/20251229-rz-sdio-mux-v4-0-a023e55758fe@solid-run.com

Changes in v4:
- added MULTIPLEXER Kconfig help text.
- removed "select MULTIPLEXER" from renesas sdhi Kconfig, as it is
  not required for all devices using this driver.
- added stubs for all symbols exported by mux core.
  (Reported-by: Geert Uytterhoeven <geert@linux-m68k.org>)
- refactored mux core logic to silence ENOENT errors only on optional
  code paths, keeping error printing unchanged otherwise.
  (Reported-by: Geert Uytterhoeven <geert@linux-m68k.org>)
- picked up various reviewed- and acked-by tags
- Link to v3: https://lore.kernel.org/r/20251210-rz-sdio-mux-v3-0-ca628db56d60@solid-run.com

Changes in v3:
- updated omap-i2c and phy-can-transceiver to use new helpers.
- created generic helper functions for getting managed optional mux-state.
  (Reported-by: Rob Herring <robh@kernel.org>)
- picked up binding ack by Rob Herring.
- replaced use of "SDIO" with "SD/SDIO/eMMC" in binding document and
  commit descriptions.
  (Reported-by: Ulf Hansson <ulf.hansson@linaro.org>)
- Link to v2: https://lore.kernel.org/r/20251201-rz-sdio-mux-v2-0-bcb581b88dd7@solid-run.com

Changes in v2:
- dropped mux-controller node from dt binding example
  (Reported-by: Conor Dooley <conor@kernel.org>
   Reported-by: Krzysztof Kozlowski <krzk@kernel.org>)
- Link to v1: https://lore.kernel.org/r/20251128-rz-sdio-mux-v1-0-1ede318d160f@solid-run.com

---
Josua Mayer (7):
      phy: can-transceiver: rename temporary helper function to avoid conflict
      mux: Add helper functions for getting optional and selected mux-state
      mux: add help text for MULTIPLEXER config option
      phy: can-transceiver: drop temporary helper getting optional mux-state
      i2c: omap: switch to new generic helper for getting selected mux-state
      dt-bindings: mmc: renesas,sdhi: Add mux-states property
      mmc: host: renesas_sdhi_core: support selecting an optional mux

 .../devicetree/bindings/mmc/renesas,sdhi.yaml      |   6 +
 drivers/i2c/busses/i2c-omap.c                      |  24 +--
 drivers/mmc/host/renesas_sdhi_core.c               |   6 +
 drivers/mux/Kconfig                                |   8 +-
 drivers/mux/core.c                                 | 205 +++++++++++++++++----
 drivers/phy/phy-can-transceiver.c                  |  10 -
 include/linux/mux/consumer.h                       | 108 ++++++++++-
 7 files changed, 302 insertions(+), 65 deletions(-)
---
base-commit: 8f0b4cce4481fb22653697cced8d0d04027cb1e8
change-id: 20251128-rz-sdio-mux-acc5137f1618

Best regards,
-- 
Josua Mayer <josua@solid-run.com>



-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply

* Re: [PATCH phy] phy: enter drivers/phy/Makefile even without CONFIG_GENERIC_PHY
From: Mark Brown @ 2026-02-03 12:25 UTC (permalink / raw)
  To: Venkat Rao Bagalkote
  Cc: Vladimir Oltean, linux-phy, Vinod Koul, Neil Armstrong, netdev,
	Paolo Abeni, Bjørn Mork, linux-kernel,
	Christophe Leroy (CS GROUP)
In-Reply-To: <007a2100-0913-4a8f-82d3-8118664fc1a7@linux.ibm.com>


[-- Attachment #1.1: Type: text/plain, Size: 324 bytes --]

On Tue, Feb 03, 2026 at 03:26:47PM +0530, Venkat Rao Bagalkote wrote:

> I still see linux-next build failures with next-20260202. Can we please have
> this patch merged.

> Mark, can you please pull this one.

Patches aren't applied directly to linux-next, they are applied to the
various trees and then merged into -next.

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

[-- Attachment #2: Type: text/plain, Size: 112 bytes --]

-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply

* Re: [PATCH v7 2/7] mux: Add helper functions for getting optional and selected mux-state
From: Josua Mayer @ 2026-02-03 11:31 UTC (permalink / raw)
  To: Geert Uytterhoeven, Ulf Hansson
  Cc: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
	Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
	Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
	Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Geert Uytterhoeven, Magnus Damm, Wolfram Sang, Yazan Shhady,
	Jon Nettleton, Mikhail Anikin, linux-can@vger.kernel.org,
	linux-phy@lists.infradead.org, linux-kernel@vger.kernel.org,
	linux-omap@vger.kernel.org, linux-i2c@vger.kernel.org,
	linux-mmc@vger.kernel.org, devicetree@vger.kernel.org,
	linux-renesas-soc@vger.kernel.org, Dan Carpenter
In-Reply-To: <CAMuHMdXgLR9yeLa5JKNdAOtNEirJ8263bmn1zRNAnKnmf8F-Sg@mail.gmail.com>

On 29/01/2026 18:13, Geert Uytterhoeven wrote:
> On Thu, 29 Jan 2026 at 17:06, Ulf Hansson <ulf.hansson@linaro.org> wrote:
>> On Wed, 28 Jan 2026 at 15:46, Josua Mayer <josua@solid-run.com> wrote:
>>> In-tree phy-can-transceiver driver has already implemented a local
>>> version of devm_mux_state_get_optional.
>>>
>>> The omap-i2c driver gets and selects an optional mux in its probe
>>> function without using any helper.
>>>
>>> Add new helper functions covering both aforementioned use-cases:
>>>
>>> - mux_control_get_optional:
>>>    Get a mux-control if specified in dt, return NULL otherwise.
>>> - devm_mux_state_get_optional:
>>>    Get a mux-state if specified in dt, return NULL otherwise.
>>> - devm_mux_state_get_selected:
>>>    Get and select a mux-state specified in dt, return error otherwise.
>>> - devm_mux_state_get_optional_selected:
>>>    Get and select a mux-state if specified in dt, return error or NULL.
>>>
>>> Existing mux_get helper function is changed to take an extra argument
>>> indicating whether the mux is optional.
>>> In this case no error is printed, and NULL returned in case of ENOENT.
>>>
>>> Calling code is adapted to handle NULL return case, and to pass optional
>>> argument as required.
>>>
>>> To support automatic deselect for _selected helper, a new structure is
>>> created storing an exit pointer similar to clock core which is called on
>>> release.
>>>
>>> To facilitate code sharing between optional/mandatory/selected helpers,
>>> a new internal helper function is added to handle quiet (optional) and
>>> verbose (mandatory) errors, as well as storing the correct callback for
>>> devm release: __devm_mux_state_get
>>>
>>> Due to this structure devm_mux_state_get_*_selected can no longer print
>>> a useful error message when select fails. Instead callers should print
>>> errors where needed.
>>>
>>> Commit e153fdea9db04 ("phy: can-transceiver: Re-instate "mux-states"
>>> property presence check") noted that "mux_get() always prints an error
>>> message in case of an error, including when the property is not present,
>>> confusing the user."
>>>
>>> The first error message covers the case that a mux name is not matched
>>> in dt. The second error message is based on of_parse_phandle_with_args
>>> return value.
>>>
>>> In optional case no error is printed and NULL is returned.
>>> This ensures that the new helper functions will not confuse the user
>>> either.
>>>
>>> With the addition of optional helper functions it became clear that
>>> drivers should compile and link even if CONFIG_MULTIPLEXER was not enabled.
>>> Add stubs for all symbols exported by mux core.
>>>
>>> Signed-off-by: Josua Mayer <josua@solid-run.com>
>>> ---
>>>   drivers/mux/core.c           | 178 ++++++++++++++++++++++++++++++++++++-------
>>>   include/linux/mux/consumer.h | 108 +++++++++++++++++++++++++-
>>>   2 files changed, 253 insertions(+), 33 deletions(-)
>>>
>>> diff --git a/drivers/mux/core.c b/drivers/mux/core.c
>>> index a3840fe0995f..b01ec126caaf 100644
>>> --- a/drivers/mux/core.c
>>> +++ b/drivers/mux/core.c
>> [...]
>>
>>>   static void devm_mux_state_release(struct device *dev, void *res)
>>>   {
>>> -       struct mux_state *mstate = *(struct mux_state **)res;
>>> +       struct devm_mux_state_state *devm_state = res;
>>>
>>> -       mux_state_put(mstate);
>>> +       if (devm_state->exit)
>>> +               devm_state->exit(devm_state->mstate);
>>> +
>>> +       mux_state_put(devm_state->mstate);
>>>   }
>>>
>>>   /**
>>> - * devm_mux_state_get() - Get the mux-state for a device, with resource
>>> - *                       management.
>>> - * @dev: The device that needs a mux-control.
>>> - * @mux_name: The name identifying the mux-control.
>>> + * __devm_mux_state_get() - Get the optional mux-state for a device,
>>> + *                         with resource management.
>>> + * @dev: The device that needs a mux-state.
>>> + * @mux_name: The name identifying the mux-state.
>>> + * @optional: Whether to return NULL and silence errors when mux doesn't exist.
>>> + * @init: Optional function pointer for mux-state object initialisation.
>>> + * @exit: Optional function pointer for mux-state object cleanup on release.
>>>    *
>>>    * Return: Pointer to the mux-state, or an ERR_PTR with a negative errno.
>>>    */
>>> -struct mux_state *devm_mux_state_get(struct device *dev,
>>> -                                    const char *mux_name)
>>> +static struct mux_state *__devm_mux_state_get(struct device *dev, const char *mux_name,
>>> +                                             bool optional,
>>> +                                             int (*init)(struct mux_state *mstate),
>>> +                                             int (*exit)(struct mux_state *mstate))
>>>   {
>>> -       struct mux_state **ptr, *mstate;
>>> +       struct devm_mux_state_state *devm_state;
>>> +       struct mux_state *mstate;
>>> +       int ret;
>>>
>>> -       ptr = devres_alloc(devm_mux_state_release, sizeof(*ptr), GFP_KERNEL);
>>> -       if (!ptr)
>>> +       devm_state = devres_alloc(devm_mux_state_release, sizeof(*devm_state), GFP_KERNEL);
>>> +       if (!devm_state)
>>>                  return ERR_PTR(-ENOMEM);
>>>
>>> -       mstate = mux_state_get(dev, mux_name);
>>> -       if (IS_ERR(mstate)) {
>>> -               devres_free(ptr);
>>> -               return mstate;
>>> +       mstate = mux_state_get(dev, mux_name, optional);
>>> +       if (IS_ERR_OR_NULL(mstate)) {
>>> +               ret = PTR_ERR(mstate);
>> Should this be PTR_ERR_OR_ZERO?
> "mux_state_get() never returns NULL"
> https://lore.kernel.org/202601221036.J0kR78Uw-lkp@intel.com

mux_state_get after my modifications does return NULL:

     if (mux_name) {
         ...
         if (index < 0 && optional) {
             return NULL;
         ...
     }
...
     if (ret) {
         if (optional && ret == -ENOENT)
             return NULL;
...

>
>>> +               goto err_mux_state_get;
>>>          }
> Gr{oetje,eeting}s,
>
>                          Geert
>

-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply

* Re: [PATCH v7 2/7] mux: Add helper functions for getting optional and selected mux-state
From: Josua Mayer @ 2026-02-03 11:28 UTC (permalink / raw)
  To: Ulf Hansson, Geert Uytterhoeven
  Cc: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
	Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
	Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
	Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Geert Uytterhoeven, Magnus Damm, Wolfram Sang, Yazan Shhady,
	Jon Nettleton, Mikhail Anikin, linux-can@vger.kernel.org,
	linux-phy@lists.infradead.org, linux-kernel@vger.kernel.org,
	linux-omap@vger.kernel.org, linux-i2c@vger.kernel.org,
	linux-mmc@vger.kernel.org, devicetree@vger.kernel.org,
	linux-renesas-soc@vger.kernel.org, Dan Carpenter
In-Reply-To: <CAPDyKFrvSp6tmhP=2=9kca1d3+b6MezvxJWwMOW=Gv6D8TKrgA@mail.gmail.com>

On 30/01/2026 12:28, Ulf Hansson wrote:
> On Thu, 29 Jan 2026 at 18:46, Geert Uytterhoeven <geert@linux-m68k.org> wrote:
>> On Thu, 29 Jan 2026 at 17:06, Ulf Hansson <ulf.hansson@linaro.org> wrote:
>>> On Wed, 28 Jan 2026 at 15:46, Josua Mayer <josua@solid-run.com> wrote:
>>>> In-tree phy-can-transceiver driver has already implemented a local
>>>> version of devm_mux_state_get_optional.
>>>>
>>>> The omap-i2c driver gets and selects an optional mux in its probe
>>>> function without using any helper.
>>>>
>>>> Add new helper functions covering both aforementioned use-cases:
>>>>
>>>> - mux_control_get_optional:
>>>>    Get a mux-control if specified in dt, return NULL otherwise.
>>>> - devm_mux_state_get_optional:
>>>>    Get a mux-state if specified in dt, return NULL otherwise.
>>>> - devm_mux_state_get_selected:
>>>>    Get and select a mux-state specified in dt, return error otherwise.
>>>> - devm_mux_state_get_optional_selected:
>>>>    Get and select a mux-state if specified in dt, return error or NULL.
>>>>
>>>> Existing mux_get helper function is changed to take an extra argument
>>>> indicating whether the mux is optional.
>>>> In this case no error is printed, and NULL returned in case of ENOENT.
>>>>
>>>> Calling code is adapted to handle NULL return case, and to pass optional
>>>> argument as required.
>>>>
>>>> To support automatic deselect for _selected helper, a new structure is
>>>> created storing an exit pointer similar to clock core which is called on
>>>> release.
>>>>
>>>> To facilitate code sharing between optional/mandatory/selected helpers,
>>>> a new internal helper function is added to handle quiet (optional) and
>>>> verbose (mandatory) errors, as well as storing the correct callback for
>>>> devm release: __devm_mux_state_get
>>>>
>>>> Due to this structure devm_mux_state_get_*_selected can no longer print
>>>> a useful error message when select fails. Instead callers should print
>>>> errors where needed.
>>>>
>>>> Commit e153fdea9db04 ("phy: can-transceiver: Re-instate "mux-states"
>>>> property presence check") noted that "mux_get() always prints an error
>>>> message in case of an error, including when the property is not present,
>>>> confusing the user."
>>>>
>>>> The first error message covers the case that a mux name is not matched
>>>> in dt. The second error message is based on of_parse_phandle_with_args
>>>> return value.
>>>>
>>>> In optional case no error is printed and NULL is returned.
>>>> This ensures that the new helper functions will not confuse the user
>>>> either.
>>>>
>>>> With the addition of optional helper functions it became clear that
>>>> drivers should compile and link even if CONFIG_MULTIPLEXER was not enabled.
>>>> Add stubs for all symbols exported by mux core.
>>>>
>>>> Signed-off-by: Josua Mayer <josua@solid-run.com>
>>>> ---
>>>>   drivers/mux/core.c           | 178 ++++++++++++++++++++++++++++++++++++-------
>>>>   include/linux/mux/consumer.h | 108 +++++++++++++++++++++++++-
>>>>   2 files changed, 253 insertions(+), 33 deletions(-)
>>>>
>>>> diff --git a/drivers/mux/core.c b/drivers/mux/core.c
>>>> index a3840fe0995f..b01ec126caaf 100644
>>>> --- a/drivers/mux/core.c
>>>> +++ b/drivers/mux/core.c
>>> [...]
>>>
>>>>   static void devm_mux_state_release(struct device *dev, void *res)
>>>>   {
>>>> -       struct mux_state *mstate = *(struct mux_state **)res;
>>>> +       struct devm_mux_state_state *devm_state = res;
>>>>
>>>> -       mux_state_put(mstate);
>>>> +       if (devm_state->exit)
>>>> +               devm_state->exit(devm_state->mstate);
>>>> +
>>>> +       mux_state_put(devm_state->mstate);
>>>>   }
>>>>
>>>>   /**
>>>> - * devm_mux_state_get() - Get the mux-state for a device, with resource
>>>> - *                       management.
>>>> - * @dev: The device that needs a mux-control.
>>>> - * @mux_name: The name identifying the mux-control.
>>>> + * __devm_mux_state_get() - Get the optional mux-state for a device,
>>>> + *                         with resource management.
>>>> + * @dev: The device that needs a mux-state.
>>>> + * @mux_name: The name identifying the mux-state.
>>>> + * @optional: Whether to return NULL and silence errors when mux doesn't exist.
>>>> + * @init: Optional function pointer for mux-state object initialisation.
>>>> + * @exit: Optional function pointer for mux-state object cleanup on release.
>>>>    *
>>>>    * Return: Pointer to the mux-state, or an ERR_PTR with a negative errno.
>>>>    */
>>>> -struct mux_state *devm_mux_state_get(struct device *dev,
>>>> -                                    const char *mux_name)
>>>> +static struct mux_state *__devm_mux_state_get(struct device *dev, const char *mux_name,
>>>> +                                             bool optional,
>>>> +                                             int (*init)(struct mux_state *mstate),
>>>> +                                             int (*exit)(struct mux_state *mstate))
>>>>   {
>>>> -       struct mux_state **ptr, *mstate;
>>>> +       struct devm_mux_state_state *devm_state;
>>>> +       struct mux_state *mstate;
>>>> +       int ret;
>>>>
>>>> -       ptr = devres_alloc(devm_mux_state_release, sizeof(*ptr), GFP_KERNEL);
>>>> -       if (!ptr)
>>>> +       devm_state = devres_alloc(devm_mux_state_release, sizeof(*devm_state), GFP_KERNEL);
>>>> +       if (!devm_state)
>>>>                  return ERR_PTR(-ENOMEM);
>>>>
>>>> -       mstate = mux_state_get(dev, mux_name);
>>>> -       if (IS_ERR(mstate)) {
>>>> -               devres_free(ptr);
>>>> -               return mstate;
>>>> +       mstate = mux_state_get(dev, mux_name, optional);
>>>> +       if (IS_ERR_OR_NULL(mstate)) {
>>>> +               ret = PTR_ERR(mstate);
>>> Should this be PTR_ERR_OR_ZERO?
>> "mux_state_get() never returns NULL"
>> https://lore.kernel.org/202601221036.J0kR78Uw-lkp@intel.com
> By looking at the code in mux_state_get() and mux_get() that $subject
> patch changes, it intends to add a "bool optional" as in-parameter. If
> it's set, it looks like the intent is to allow returning NULL, which
> makes sense to me.
The intent behind "optional" parameter was to conditionally
silence certain error paths that would eventually lead to
at least the exported devm_* functions to return NULL.

I'll review my logic ...

>
> Anyway, there seems to be some additional smatch warnings to fix
> before this is ready to go.
>
> [...]
>
> Kind regards
> Uffe
>

-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply

* Re: [PATCH phy] phy: enter drivers/phy/Makefile even without CONFIG_GENERIC_PHY
From: Venkat Rao Bagalkote @ 2026-02-03  9:56 UTC (permalink / raw)
  To: Vladimir Oltean, linux-phy, Vinod Koul, Neil Armstrong, broonie
  Cc: netdev, Paolo Abeni, Bjørn Mork, linux-kernel,
	Christophe Leroy (CS GROUP)
In-Reply-To: <20260123110600.3118561-1-vladimir.oltean@nxp.com>


On 23/01/26 4:36 pm, Vladimir Oltean wrote:
> Kconfig option CONFIG_PHY_COMMON_PROPS, which builds
> drivers/phy/phy-common-props.c, was intended to be selectable
> independently of CONFIG_GENERIC_PHY. Yet it lives in drivers/phy/, which
> is entered by the Makefile only if CONFIG_GENERIC_PHY is set.
>
> Allow the Makefile to enter one level deeper, but stop at drivers/phy/
> if CONFIG_GENERIC_PHY is unselected (i.e. do not enter vendor folders).
> The other stuff from drivers/phy/Makefile except for CONFIG_PHY_COMMON_PROPS,
> like CONFIG_PHY_NXP_PTN3222, all depends on CONFIG_GENERIC_PHY.
>
> Fixes: e7556b59ba65 ("phy: add phy_get_rx_polarity() and phy_get_tx_polarity()")
> Closes: https://lore.kernel.org/lkml/43ea0202-891d-4582-980b-5cb557b41114@linux.ibm.com/
> Reported-by: Venkat Rao Bagalkote <venkat88@linux.ibm.com>
> Debugged-by: "Christophe Leroy (CS GROUP)" <chleroy@kernel.org>
> Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
> ---
>   drivers/Makefile     | 2 +-
>   drivers/phy/Makefile | 2 +-
>   2 files changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/Makefile b/drivers/Makefile
> index ccc05f1eae3e..53fbd2e0acdd 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -10,7 +10,7 @@ obj-y				+= cache/
>   obj-y				+= irqchip/
>   obj-y				+= bus/
>   
> -obj-$(CONFIG_GENERIC_PHY)	+= phy/
> +obj-y				+= phy/
>   
>   # GPIO must come after pinctrl as gpios may need to mux pins etc
>   obj-$(CONFIG_PINCTRL)		+= pinctrl/
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 30b150d68de7..93715ea0d010 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -15,7 +15,7 @@ obj-$(CONFIG_PHY_SNPS_EUSB2)		+= phy-snps-eusb2.o
>   obj-$(CONFIG_USB_LGM_PHY)		+= phy-lgm-usb.o
>   obj-$(CONFIG_PHY_AIROHA_PCIE)		+= phy-airoha-pcie.o
>   obj-$(CONFIG_PHY_NXP_PTN3222)		+= phy-nxp-ptn3222.o
> -obj-y					+= allwinner/	\
> +obj-$(CONFIG_GENERIC_PHY)		+= allwinner/	\
>   					   amlogic/	\
>   					   broadcom/	\
>   					   cadence/	\


I still see linux-next build failures with next-20260202. Can we please 
have this patch merged.


Mark, can you please pull this one.


Regards,

Venkat.


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply

* Re: [PATCH] phy: renesas: rcar-gen3-usb2: add regulator dependency
From: Tommaso Merciai @ 2026-02-03  8:24 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Vinod Koul, Arnd Bergmann, Neil Armstrong, Biju Das, linux-phy,
	linux-kernel
In-Reply-To: <20260202095118.1233046-1-arnd@kernel.org>

On Mon, Feb 02, 2026 at 10:51:14AM +0100, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
> 
> The driver start registering a regulator, but can still be
> enabled even when it is unable to call into the regulator
> subsystem:
> 
> aarch64-linux-ld: drivers/phy/renesas/phy-rcar-gen3-usb2.o: in function `rcar_gen3_phy_usb2_probe':
> phy-rcar-gen3-usb2.c:(.text+0x2884): undefined reference to `devm_regulator_register'
> 
> Add a Kconfig dependency to avoid this configuration.
> 
> Fixes: b6d7dd157763 ("phy: renesas: rcar-gen3-usb2: Add regulator for OTG VBUS control")

Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>

> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> ---
>  drivers/phy/renesas/Kconfig | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig
> index d217c630b2fd..90a9ca2db7fc 100644
> --- a/drivers/phy/renesas/Kconfig
> +++ b/drivers/phy/renesas/Kconfig
> @@ -29,6 +29,7 @@ config PHY_RCAR_GEN3_USB2
>  	depends on ARCH_RENESAS
>  	depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in
>  	depends on USB_SUPPORT
> +	depends on REGULATOR
>  	select GENERIC_PHY
>  	select MULTIPLEXER
>  	select USB_COMMON
> -- 
> 2.39.5
> 

Thanks & Regards,
Tommaso

-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply

* Re: [PATCH] phy: tegra: xusb: Remove unused powered_on variable
From: Mikko Perttunen @ 2026-02-03  5:31 UTC (permalink / raw)
  To: JC Kuo, Vinod Koul, Thierry Reding, Jon Hunter
  Cc: linux-phy, linux-tegra, Jon Hunter
In-Reply-To: <20260202153314.1634145-1-jonathanh@nvidia.com>

On Tuesday, February 3, 2026 12:33 AM Jon Hunter wrote:
> Commit bbf711682cd5 ("phy: tegra: xusb: Add Tegra186 support") added the
> variable 'powered_on' to the structure 'tegra_xusb_usb2_lane' but it has
> never been used. Therefore, remove this unused variable.
> 
> Signed-off-by: Jon Hunter <jonathanh@nvidia.com>
> ---
>  drivers/phy/tegra/xusb.h | 1 -
>  1 file changed, 1 deletion(-)
> 
> diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h
> index d2b5f9565132..cd277d0ed9e1 100644
> --- a/drivers/phy/tegra/xusb.h
> +++ b/drivers/phy/tegra/xusb.h
> @@ -69,7 +69,6 @@ struct tegra_xusb_usb2_lane {
>  	struct tegra_xusb_lane base;
>  
>  	u32 hs_curr_level_offset;
> -	bool powered_on;
>  };
>  
>  static inline struct tegra_xusb_usb2_lane *
> 

Reviewed-by: Mikko Perttunen <mperttunen@nvidia.com>




-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply

* [PATCH RESEND v9 3/3] MAINTAINERS: Add ASPEED PCIe PHY driver
From: Jacky Chou @ 2026-02-03  3:30 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Joel Stanley, Andrew Jeffery
  Cc: linux-aspeed, linux-phy, devicetree, linux-arm-kernel,
	linux-kernel, Jacky Chou
In-Reply-To: <20260203-upstream_pcie_rc-v9-0-899ee4efe94e@aspeedtech.com>

Add maintainer entry for ASPEED PCIe PHY driver.

Signed-off-by: Jacky Chou <jacky_chou@aspeedtech.com>
---
 MAINTAINERS | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index cf755238c429..a1979c574759 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3844,6 +3844,14 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/crypto/aspeed,*
 F:	drivers/crypto/aspeed/
 
+ASPEED PCIE PHY DRIVER
+M:	Jacky Chou <jacky_chou@aspeedtech.com>
+L:	linux-aspeed@lists.ozlabs.org (moderated for non-subscribers)
+L:	linux-phy@lists.infradead.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/phy/aspeed,ast2600-pcie-phy.yaml
+F:	drivers/phy/aspeed/pcie-phy-aspeed.c
+
 ASPEED PECI CONTROLLER
 M:	Iwona Winiarska <iwona.winiarska@intel.com>
 L:	linux-aspeed@lists.ozlabs.org (moderated for non-subscribers)

-- 
2.34.1


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH RESEND v9 2/3] phy: aspeed: Add ASPEED PCIe PHY driver
From: Jacky Chou @ 2026-02-03  3:30 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Joel Stanley, Andrew Jeffery
  Cc: linux-aspeed, linux-phy, devicetree, linux-arm-kernel,
	linux-kernel, Jacky Chou
In-Reply-To: <20260203-upstream_pcie_rc-v9-0-899ee4efe94e@aspeedtech.com>

Introduce support for Aspeed PCIe PHY controller available in
AST2600/2700.

Signed-off-by: Jacky Chou <jacky_chou@aspeedtech.com>
---
 drivers/phy/Kconfig                  |   1 +
 drivers/phy/Makefile                 |   1 +
 drivers/phy/aspeed/Kconfig           |  14 +++
 drivers/phy/aspeed/Makefile          |   2 +
 drivers/phy/aspeed/phy-aspeed-pcie.c | 194 +++++++++++++++++++++++++++++++++++
 5 files changed, 212 insertions(+)

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 678dd0452f0a..f6a8f06fd244 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -103,6 +103,7 @@ config PHY_NXP_PTN3222
 
 source "drivers/phy/allwinner/Kconfig"
 source "drivers/phy/amlogic/Kconfig"
+source "drivers/phy/aspeed/Kconfig"
 source "drivers/phy/broadcom/Kconfig"
 source "drivers/phy/cadence/Kconfig"
 source "drivers/phy/freescale/Kconfig"
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index bfb27fb5a494..18990c87dfb0 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_PHY_AIROHA_PCIE)		+= phy-airoha-pcie.o
 obj-$(CONFIG_PHY_NXP_PTN3222)		+= phy-nxp-ptn3222.o
 obj-y					+= allwinner/	\
 					   amlogic/	\
+					   aspeed/	\
 					   broadcom/	\
 					   cadence/	\
 					   freescale/	\
diff --git a/drivers/phy/aspeed/Kconfig b/drivers/phy/aspeed/Kconfig
new file mode 100644
index 000000000000..f7aad553f3fd
--- /dev/null
+++ b/drivers/phy/aspeed/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Phy drivers for Aspeed platforms
+#
+config PHY_ASPEED_PCIE
+	tristate "ASPEED PCIe PHY driver"
+	select GENERIC_PHY
+	depends on ARCH_ASPEED
+	help
+	  This option enables support for the ASPEED PCIe PHY driver.
+	  The driver provides the necessary interface to control and
+	  configure the PCIe PHY hardware found on ASPEED SoCs.
+	  It is required for proper operation of PCIe devices on
+	  platforms using ASPEED chips.
diff --git a/drivers/phy/aspeed/Makefile b/drivers/phy/aspeed/Makefile
new file mode 100644
index 000000000000..3edce7f522e1
--- /dev/null
+++ b/drivers/phy/aspeed/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_ASPEED_PCIE)		+= phy-aspeed-pcie.o
diff --git a/drivers/phy/aspeed/phy-aspeed-pcie.c b/drivers/phy/aspeed/phy-aspeed-pcie.c
new file mode 100644
index 000000000000..0f8ca7bf69b5
--- /dev/null
+++ b/drivers/phy/aspeed/phy-aspeed-pcie.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2025 Aspeed Technology Inc.
+ */
+
+#include <linux/phy/pcie.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* AST2600 PCIe Host Controller Registers */
+#define PEHR_GLOBAL		0x30
+#define  AST2600_PORT_TYPE_MASK		GENMASK(5, 4)
+#define  AST2600_PORT_TYPE(x)		FIELD_PREP(AST2600_PORT_TYPE_MASK, x)
+#define PEHR_LOCK		0x7c
+#define  PCIE_UNLOCK			0xa8
+
+/* AST2700 PEHR */
+#define PEHR_MISC_58		0x58
+#define  LOCAL_SCALE_SUP		BIT(0)
+#define PEHR_MISC_5C		0x5c
+#define  CONFIG_RC_DEVICE		BIT(30)
+#define PEHR_MISC_60		0x60
+#define  AST2700_PORT_TYPE_MASK		GENMASK(7, 4)
+#define  PORT_TYPE_ROOT			0x4
+#define PEHR_MISC_70		0x70
+#define  POSTED_DATA_CREDITS(x)		FIELD_PREP(GENMASK(15, 0), x)
+#define  POSTED_HEADER_CREDITS(x)	FIELD_PREP(GENMASK(27, 16), x)
+#define PEHR_MISC_78		0x78
+#define  COMPLETION_DATA_CREDITS(x)	FIELD_PREP(GENMASK(15, 0), x)
+#define  COMPLETION_HEADER_CREDITS(x)	FIELD_PREP(GENMASK(27, 16), x)
+
+/**
+ * struct aspeed_pcie_phy - PCIe PHY information
+ * @dev: pointer to device structure
+ * @reg: PCIe host register base address
+ * @phy: pointer to PHY structure
+ * @platform: platform specific information
+ */
+struct aspeed_pcie_phy {
+	struct device *dev;
+	void __iomem *reg;
+	struct phy *phy;
+	const struct aspeed_pcie_phy_platform *platform;
+};
+
+/**
+ * struct aspeed_pcie_phy_platform - Platform information
+ * @phy_ops: phy operations
+ */
+struct aspeed_pcie_phy_platform {
+	const struct phy_ops *phy_ops;
+};
+
+static int ast2600_phy_init(struct phy *phy)
+{
+	struct aspeed_pcie_phy *pcie_phy = phy_get_drvdata(phy);
+
+	writel(PCIE_UNLOCK, pcie_phy->reg + PEHR_LOCK);
+
+	return 0;
+}
+
+static int ast2600_phy_set_mode(struct phy *phy, enum phy_mode mode,
+				int submode)
+{
+	struct aspeed_pcie_phy *pcie_phy = phy_get_drvdata(phy);
+
+	switch (submode) {
+	case PHY_MODE_PCIE_RC:
+		writel(AST2600_PORT_TYPE(0x3), pcie_phy->reg + PEHR_GLOBAL);
+		break;
+	default:
+		dev_err(&phy->dev, "Unsupported submode %d\n", submode);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct phy_ops ast2600_phy_ops = {
+	.init		= ast2600_phy_init,
+	.set_mode	= ast2600_phy_set_mode,
+	.owner		= THIS_MODULE,
+};
+
+static int ast2700_phy_init(struct phy *phy)
+{
+	struct aspeed_pcie_phy *pcie_phy = phy_get_drvdata(phy);
+
+	writel(POSTED_DATA_CREDITS(0xc0) | POSTED_HEADER_CREDITS(0xa),
+	       pcie_phy->reg + PEHR_MISC_70);
+	writel(COMPLETION_DATA_CREDITS(0x30) | COMPLETION_HEADER_CREDITS(0x8),
+	       pcie_phy->reg + PEHR_MISC_78);
+	writel(LOCAL_SCALE_SUP, pcie_phy->reg + PEHR_MISC_58);
+
+	return 0;
+}
+
+static int ast2700_phy_set_mode(struct phy *phy, enum phy_mode mode,
+				int submode)
+{
+	struct aspeed_pcie_phy *pcie_phy = phy_get_drvdata(phy);
+	u32 cfg_val;
+
+	switch (submode) {
+	case PHY_MODE_PCIE_RC:
+		writel(CONFIG_RC_DEVICE, pcie_phy->reg + PEHR_MISC_5C);
+		cfg_val = readl(pcie_phy->reg + PEHR_MISC_60);
+		FIELD_MODIFY(AST2700_PORT_TYPE_MASK, &cfg_val,
+			     PORT_TYPE_ROOT);
+		writel(cfg_val, pcie_phy->reg + PEHR_MISC_60);
+		break;
+	default:
+		dev_err(&phy->dev, "Unsupported submode %d\n", submode);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct phy_ops ast2700_phy_ops = {
+	.init		= ast2700_phy_init,
+	.set_mode	= ast2700_phy_set_mode,
+	.owner		= THIS_MODULE,
+};
+
+const struct aspeed_pcie_phy_platform pcie_phy_ast2600 = {
+	.phy_ops = &ast2600_phy_ops,
+};
+
+const struct aspeed_pcie_phy_platform pcie_phy_ast2700 = {
+	.phy_ops = &ast2700_phy_ops,
+};
+
+static int aspeed_pcie_phy_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct phy_provider *phy_provider;
+	struct aspeed_pcie_phy *pcie_phy;
+	const struct aspeed_pcie_phy_platform *md;
+
+	md = of_device_get_match_data(dev);
+	if (!md)
+		return -ENODEV;
+
+	pcie_phy = devm_kzalloc(dev, sizeof(*pcie_phy), GFP_KERNEL);
+	if (!pcie_phy)
+		return -ENOMEM;
+
+	pcie_phy->reg = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(pcie_phy->reg))
+		return PTR_ERR(pcie_phy->reg);
+
+	pcie_phy->dev = dev;
+	pcie_phy->platform = md;
+
+	pcie_phy->phy = devm_phy_create(dev, dev->of_node,
+					pcie_phy->platform->phy_ops);
+	if (IS_ERR(pcie_phy->phy))
+		return dev_err_probe(dev, PTR_ERR(pcie_phy->phy),
+				     "failed to create PHY\n");
+
+	phy_set_drvdata(pcie_phy->phy, pcie_phy);
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+	return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id aspeed_pcie_phy_of_match_table[] = {
+	{
+		.compatible = "aspeed,ast2600-pcie-phy",
+		.data = &pcie_phy_ast2600,
+	},
+	{
+		.compatible = "aspeed,ast2700-pcie-phy",
+		.data = &pcie_phy_ast2700,
+	},
+	{ },
+};
+MODULE_DEVICE_TABLE(of, aspeed_pcie_phy_of_match_table);
+
+static struct platform_driver aspeed_pcie_driver = {
+	.probe		= aspeed_pcie_phy_probe,
+	.driver = {
+		.name	= "aspeed-pcie-phy",
+		.of_match_table = aspeed_pcie_phy_of_match_table,
+	},
+};
+
+module_platform_driver(aspeed_pcie_driver);
+
+MODULE_AUTHOR("Jacky Chou <jacky_chou@aspeedtech.com>");
+MODULE_DESCRIPTION("ASPEED PCIe PHY");
+MODULE_LICENSE("GPL");

-- 
2.34.1


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH RESEND v9 1/3] dt-bindings: phy: aspeed: Add ASPEED PCIe PHY
From: Jacky Chou @ 2026-02-03  3:30 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Joel Stanley, Andrew Jeffery
  Cc: linux-aspeed, linux-phy, devicetree, linux-arm-kernel,
	linux-kernel, Jacky Chou
In-Reply-To: <20260203-upstream_pcie_rc-v9-0-899ee4efe94e@aspeedtech.com>

Introduce device-binding for ASPEED AST2600/2700 PCIe PHY.
The PCIe PHY is used for PCIe RC to configure as RC mode.

Signed-off-by: Jacky Chou <jacky_chou@aspeedtech.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
 .../bindings/phy/aspeed,ast2600-pcie-phy.yaml      | 42 ++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/Documentation/devicetree/bindings/phy/aspeed,ast2600-pcie-phy.yaml b/Documentation/devicetree/bindings/phy/aspeed,ast2600-pcie-phy.yaml
new file mode 100644
index 000000000000..71a5cd91fb3f
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/aspeed,ast2600-pcie-phy.yaml
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/aspeed,ast2600-pcie-phy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ASPEED PCIe PHY
+
+maintainers:
+  - Jacky Chou <jacky_chou@aspeedtech.com>
+
+description:
+  The ASPEED PCIe PHY provides the physical layer functionality for PCIe
+  controllers in the SoC.
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - aspeed,ast2600-pcie-phy
+          - aspeed,ast2700-pcie-phy
+
+  reg:
+    maxItems: 1
+
+  "#phy-cells":
+    const: 0
+
+required:
+  - compatible
+  - reg
+  - "#phy-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    phy@1e6ed200 {
+      compatible = "aspeed,ast2600-pcie-phy";
+      reg = <0x1e6ed200 0x100>;
+      #phy-cells = <0>;
+    };

-- 
2.34.1


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH RESEND v9 0/3] Add ASPEED PCIe PHY support
From: Jacky Chou @ 2026-02-03  3:30 UTC (permalink / raw)
  To: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Joel Stanley, Andrew Jeffery
  Cc: linux-aspeed, linux-phy, devicetree, linux-arm-kernel,
	linux-kernel, Jacky Chou

This patch series adds support for the ASPEED PCIe PHY,
including device tree bindings and PCIe PHY driver.
The patches introduce the necessary device tree nodes.

Summary of changes:
- Add device tree binding documents for ASPEED PCIe PHY
- Update MAINTAINERS for new bindings and driver
- Implement ASPEED PCIe PHY driver

This series has been tested on AST2600/AST2700 platforms and enables PCIe device
enumeration and operation.

Signed-off-by: Jacky Chou <jacky_chou@aspeedtech.com>
---
Changes in v9:
- Remove '[v8,2/4] ARM: dts: aspeed-g6: Add PCIe RC and PCIe PHY node'
  and send it separately.
- Use FIELD_MODIFY to configure register.
- Link to v8: https://lore.kernel.org/r/20251230-upstream_pcie_rc-v8-0-03598cdd80cd@aspeedtech.com

Changes in v8:
- Update the title of cover letter
- Remove 'default y' from Kconfig
- Change 'PHY:' to 'phy:' in the title of ASPEED PCIe PHY commit.
- Remove unnecessary headers from phy-aspeed-pcie.c.
- Add ASPEED PCIe PHY MAINTAINERS
- Remove applied patches
  [v7,2/7] dt-bindings: PCI: Add ASPEED PCIe RC support
  [v7,5/7] PCI: Add FMT, TYPE and CPL status definition for TLP header
  [v7,6/7] PCI: aspeed: Add ASPEED PCIe RC driver
  [v7,7/7] MAINTAINERS: Add ASPEED PCIe RC driver
- Link to v7: https://lore.kernel.org/r/20251216-upstream_pcie_rc-v7-0-4aeb0f53c4ce@aspeedtech.com

Changes in v7:
- Add PCIe port into aspeed,ast2600-pci.yaml.
- Remove aspeed_pcie_init_ports() and merge aspeed_pcie_port_init() to
  aspeed_pcie_parse_port()
- Refactor aspeed remap pcie addr to aspeed_pcie_map_ranges()
- Link to v6: https://lore.kernel.org/r/20251201-upstream_pcie_rc-v6-0-8c8800c56b16@aspeedtech.com

Changes in v6:
- Refer to pci-cpi-bridge.yaml to update aspeed,ast2600-pcie.yaml and
  the pcie node of aspeed-g6.dtsi.
- 'dt-bindings: pinctrl: aspeed,ast2600-pinctrl: Add PCIe RC PERST#
  group' have applied, remove it from this version.
- Adjust the defnitions in pci.h.
- Link to v5: https://lore.kernel.org/r/20251117-upstream_pcie_rc-v5-0-b4a198576acf@aspeedtech.com

Changes in v5:
- Remove legacy-interrupt-controller and the INTx points to pcie node itself.
- Correct bar mapping description and implementation to PCIe address
  configuration in pcie-aspeed.c driver.
- Link to v4: https://lore.kernel.org/r/20251027095825.181161-1-jacky_chou@aspeedtech.com/

Changes in v4:
- Remove aspeed,ast2700-pcie-cfg.yaml
- Add more descriptions for AST2600 PCIe RC in aspeed,ast2600-pcie.yaml
- Change interrupt-controller to legacy-interrupt-controller in yaml
  and dtsi
- Remove msi-parent property in yaml and dtsi
- Modify the bus range to starting from 0x00 in aspeed-g6.dtsi
- Fixed the typo on MODULE_DEVICE_TABLE() in phy-aspeed-pcie.c
- Add PCIE_CPL_STS_SUCCESS definition in pci/pci.h
- Add prefix ASPEED_ for register definition in RC driver
- Add a flag to indicate clear msi status twice for AST2700 workaround
- Remove getting domain number
- Remove scanning AST2600 HOST bridge on device number 0
- Remove all codes about CONFIG_PCI_MSI
- Get root but number from resouce list by IORESOURCE_BUS
- Change module_platform_driver to builtin_platform_driver
- Link to v3: https://lore.kernel.org/r/20250901055922.1553550-1-jacky_chou@aspeedtech.com/

Changes in v3:
- Add ASPEED PCIe PHY driver
- Remove the aspeed,pciecfg property from AST2600 RC node, merged into RC node
- Update the binding doc for aspeed,ast2700-pcie-cfg to reflect the changes
- Update the binding doc for aspeed,ast2600-pcie to reflect the changes
- Update the binding doc for aspeed,ast2600-pinctrl to reflect the changes
- Update the device tree source to reflect the changes
- Adjusted the use of mutex in RC drivers to use GRAND
- Updated from reviewer comments
- Link to v2: https://lore.kernel.org/r/20250715034320.2553837-1-jacky_chou@aspeedtech.com/

Changes in v2:
- Moved ASPEED PCIe PHY yaml binding to `soc/aspeed` directory and
  changed it as syscon
- Added `MAINTAINERS` entry for the new PCIe RC driver
- Updated device tree bindings to reflect the new structure
- Refactored configuration read and write functions to main bus and
  child bus ops
- Refactored initialization to implement multiple ports support
- Added PCIe FMT and TYPE definitions for TLP header in
  `include/uapi/linux/pci_regs.h`
- Updated from reviewer comments
- Link to v1: https://lore.kernel.org/r/20250613033001.3153637-1-jacky_chou@aspeedtech.com/

---
Jacky Chou (3):
      dt-bindings: phy: aspeed: Add ASPEED PCIe PHY
      phy: aspeed: Add ASPEED PCIe PHY driver
      MAINTAINERS: Add ASPEED PCIe PHY driver

 .../bindings/phy/aspeed,ast2600-pcie-phy.yaml      |  42 +++++
 MAINTAINERS                                        |   8 +
 drivers/phy/Kconfig                                |   1 +
 drivers/phy/Makefile                               |   1 +
 drivers/phy/aspeed/Kconfig                         |  14 ++
 drivers/phy/aspeed/Makefile                        |   2 +
 drivers/phy/aspeed/phy-aspeed-pcie.c               | 194 +++++++++++++++++++++
 7 files changed, 262 insertions(+)
---
base-commit: 944aacb68baf7624ab8d277d0ebf07f025ca137c
change-id: 20251103-upstream_pcie_rc-8445759db813

Best regards,
-- 
Jacky Chou <jacky_chou@aspeedtech.com>


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply

* [PATCH 8/8] arm64: zynqmp: Add PCIe resets
From: Sean Anderson @ 2026-02-03  0:21 UTC (permalink / raw)
  To: Laurent Pinchart, Vinod Koul, linux-phy
  Cc: Krzysztof Wilczyński, Lorenzo Pieralisi, Radhey Shyam Pandey,
	linux-kernel, Michal Simek, linux-arm-kernel, linux-pci,
	Neil Armstrong, Rob Herring, Thippeswamy Havalige,
	Manivannan Sadhasivam, Bjorn Helgaas, Sean Anderson
In-Reply-To: <20260203002128.935842-1-sean.anderson@linux.dev>

Add PCIe reset bindings for the ZynqMP.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

 arch/arm64/boot/dts/xilinx/zynqmp.dtsi | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
index 938b014ca923..178b4c3a7ba4 100644
--- a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
+++ b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
@@ -969,6 +969,10 @@ pcie: pcie@fd0e0000 {
 					<0x0 0x0 0x0 0x4 &pcie_intc 0x4>;
 			/* iommus = <&smmu 0x4d0>; */
 			power-domains = <&zynqmp_firmware PD_PCIE>;
+			resets = <&zynqmp_reset ZYNQMP_RESET_PCIE_CFG>,
+				 <&zynqmp_reset ZYNQMP_RESET_PCIE_BRIDGE>,
+				 <&zynqmp_reset ZYNQMP_RESET_PCIE_CTRL>;
+			reset-names = "cfg", "bridge", "ctrl";
 			pcie_intc: legacy-interrupt-controller {
 				interrupt-controller;
 				#address-cells = <0>;
-- 
2.35.1.1320.gc452695387.dirty


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH 7/8] PCI: xilinx-nwl: Reset the core during probe
From: Sean Anderson @ 2026-02-03  0:21 UTC (permalink / raw)
  To: Laurent Pinchart, Vinod Koul, linux-phy
  Cc: Krzysztof Wilczyński, Lorenzo Pieralisi, Radhey Shyam Pandey,
	linux-kernel, Michal Simek, linux-arm-kernel, linux-pci,
	Neil Armstrong, Rob Herring, Thippeswamy Havalige,
	Manivannan Sadhasivam, Bjorn Helgaas, Sean Anderson
In-Reply-To: <20260203002128.935842-1-sean.anderson@linux.dev>

The PCIe core must be held in reset when initializing phys.
Assert/deassert the appropriate resets.

Resetting the core also resets the PCIe attributes to their default
values, so initialize those too. For the most part the defaults are
fine, but there are many attributes that default to an endpoint
configuration and must be reprogrammed to function as a root device.
We generally follow the controller programming sequence from UG1085.

Xilinx was extremely imaginative and named all the registers ATTR_1,
ATTR_2 etc. (with the fields organized in alphabetical order) so we
follow the same convention. Only the fields are named, but sometimes a
field is split across multiple registers. All the BARs are unused but
some are repurposed as bridge registers when used as a root port.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

 drivers/pci/controller/pcie-xilinx-nwl.c | 177 +++++++++++++++++++++++
 1 file changed, 177 insertions(+)

diff --git a/drivers/pci/controller/pcie-xilinx-nwl.c b/drivers/pci/controller/pcie-xilinx-nwl.c
index 7cfdc21e6f40..b78fbad1efa5 100644
--- a/drivers/pci/controller/pcie-xilinx-nwl.c
+++ b/drivers/pci/controller/pcie-xilinx-nwl.c
@@ -22,6 +22,7 @@
 #include <linux/pci-ecam.h>
 #include <linux/phy/phy.h>
 #include <linux/platform_device.h>
+#include <linux/reset.h>
 #include <linux/irqchip/chained_irq.h>
 
 #include "../pci.h"
@@ -133,6 +134,54 @@
 #define CFG_DMA_REG_BAR			GENMASK(2, 0)
 #define CFG_PCIE_CACHE			GENMASK(7, 0)
 
+#define PCIE_ATTR2_AER_CAP_PERMIT_ROOTERR_UPDATE	BIT(0)
+
+#define PCIE_ATTR25_CPL_TIMEOUT_DISABLE_SUPPORTED	BIT(9)
+#define PCIE_ATTR25_INTX_IMPLEMENTED			BIT(8)
+#define PCIE_ATTR25_CLASS_CODE				GENMASK(7, 0)
+
+#define PCIE_ATTR27_DEV_CAP_ENDPOINT_L1_LATENCY		GENMASK(5, 3)
+
+#define PCIE_ATTR34_HEADER_TYPE				GENMASK(7, 0)
+
+#define PCIE_ATTR35_LINK_CAP_DLL_ACTIVE_REPORTING	BIT(15)
+
+#define PCIE_ATTR37_LINK_CAP_MAX_LINK_SPEED		GENMASK(13, 10)
+#define PCIE_ATTR37_LINK_CAP_MAX_LINK_SPEED_2_5		1
+#define PCIE_ATTR37_LINK_CAP_MAX_LINK_SPEED_5_0		2
+#define PCIE_ATTR37_LINK_CAP_BANDWIDTH_NOTIFICATION	BIT(9)
+
+#define PCIE_ATTR50_CAP_DEVICE_PORT_TYPE		GENMASK(7, 4)
+#define PCIE_ATTR50_CAP_NEXTPTR				GENMASK(15, 8)
+
+#define PCIE_ATTR53_CAP_NEXTPTR				GENMASK(7, 0)
+
+#define PCIE_ATTR93_LL_REPLAY_TIMEOUT_EN		BIT(15)
+
+#define PCIE_ATTR97_LTSSM_MAX_LINK_WIDTH		GENMASK(11, 6)
+#define PCIE_ATTR97_LINK_CAP_MAX_LINK_WIDTH		GENMASK(5, 0)
+
+#define PCIE_ATTR100_UPSTREAM_FACING			BIT(6)
+
+#define PCIE_ATTR101_EN_MSG_ROUTE			GENMASK(15, 5)
+#define PCIE_ATTR101_EN_MSG_ROUTE_PME_TURN_OFF		BIT(15)
+#define PCIE_ATTR101_EN_MSG_ROUTE_UNLOCK		BIT(14)
+#define PCIE_ATTR101_EN_MSG_ROUTE_PME_TO_ACK		BIT(13)
+#define PCIE_ATTR101_EN_MSG_ROUTE_PM_PME		BIT(12)
+#define PCIE_ATTR101_EN_MSG_ROUTE_INTD			BIT(11)
+#define PCIE_ATTR101_EN_MSG_ROUTE_INTC			BIT(10)
+#define PCIE_ATTR101_EN_MSG_ROUTE_INTB			BIT(9)
+#define PCIE_ATTR101_EN_MSG_ROUTE_INTA			BIT(8)
+#define PCIE_ATTR101_EN_MSG_ROUTE_ERR_FATAL		BIT(7)
+#define PCIE_ATTR101_EN_MSG_ROUTE_ERR_NONFATAL		BIT(6)
+#define PCIE_ATTR101_EN_MSG_ROUTE_ERR_COR		BIT(5)
+#define PCIE_ATTR101_DISABLE_BAR_FILTERING		BIT(1)
+
+#define PCIE_ATTR106_VC0_TOTAL_CREDITS_NPH		GENMASK(13, 7)
+#define PCIE_ATTR106_VC0_TOTAL_CREDITS_CH		GENMASK(6, 0)
+
+#define PCIE_ATTR109_VC0_TOTAL_CREDITS_PH		GENMASK(6, 0)
+
 #define INT_PCI_MSI_NR			(2 * 32)
 
 /* Readin the PS_LINKUP */
@@ -159,6 +208,7 @@ struct nwl_pcie {
 	void __iomem *pcireg_base;
 	void __iomem *ecam_base;
 	struct phy *phy[4];
+	struct reset_control *ctrl_reset;
 	phys_addr_t phys_breg_base;	/* Physical Bridge Register Base */
 	phys_addr_t phys_pcie_reg_base;	/* Physical PCIe Controller Base */
 	phys_addr_t phys_ecam_base;	/* Physical Configuration Base */
@@ -173,6 +223,115 @@ struct nwl_pcie {
 	raw_spinlock_t leg_mask_lock;
 };
 
+static void nwl_pcie_write_attr(struct nwl_pcie *pcie, u32 attr, u16 val)
+{
+	writel(val, pcie->pcireg_base + attr * 4);
+}
+
+static void nwl_pcie_modify_attr(struct nwl_pcie *pcie, u32 attr, u16 clear,
+				u16 set)
+{
+	u32 val = readl(pcie->pcireg_base + attr * 4);
+
+	nwl_pcie_write_attr(pcie, attr, (val & ~clear) | set);
+}
+
+static void nwl_pcie_attr_init(struct nwl_pcie *pcie)
+{
+	unsigned int width;
+
+	for (width = ARRAY_SIZE(pcie->phy); width; width--)
+		if (pcie->phy[width - 1])
+			break;
+
+	/* Set TLP header to type-1 */
+	nwl_pcie_modify_attr(pcie, 34, PCIE_ATTR34_HEADER_TYPE, PCI_HEADER_TYPE_BRIDGE);
+	nwl_pcie_modify_attr(pcie, 100, PCIE_ATTR100_UPSTREAM_FACING, 0);
+
+	/* Disable BAR0/1 */
+	nwl_pcie_write_attr(pcie, 7, 0);
+	nwl_pcie_write_attr(pcie, 8, 0);
+	nwl_pcie_write_attr(pcie, 9, 0);
+	nwl_pcie_write_attr(pcie, 10, 0);
+	/* Enable primary/secondary/subordinate busses, disable latency timer */
+	nwl_pcie_write_attr(pcie, 11, 0xffff);
+	nwl_pcie_write_attr(pcie, 12, 0x00ff);
+	nwl_pcie_write_attr(pcie, 13, 0x0000); /* Disable I/O window */
+	nwl_pcie_write_attr(pcie, 14, 0x0000); /* Enable secondary status */
+	/* Enable memory window */
+	nwl_pcie_write_attr(pcie, 15, (u16)PCI_MEMORY_RANGE_MASK);
+	nwl_pcie_write_attr(pcie, 16, (u16)PCI_MEMORY_RANGE_MASK);
+	/* Enable 64-bit prefetchable window */
+	nwl_pcie_write_attr(pcie, 17,
+			    (u16)PCI_PREF_RANGE_MASK | PCI_PREF_RANGE_TYPE_64);
+	nwl_pcie_write_attr(pcie, 18,
+			    (u16)PCI_PREF_RANGE_MASK | PCI_PREF_RANGE_TYPE_64);
+	nwl_pcie_modify_attr(pcie, 101, 0, PCIE_ATTR101_DISABLE_BAR_FILTERING);
+
+	/* Set class code to PCI_CLASS_BRIDGE_PCI_NORMAL */
+	nwl_pcie_write_attr(pcie, 24, PCI_CLASS_BRIDGE_PCI_NORMAL & 0xffff);
+	nwl_pcie_modify_attr(pcie, 25, PCIE_ATTR25_CLASS_CODE,
+			     PCIE_ATTR25_CPL_TIMEOUT_DISABLE_SUPPORTED |
+			     PCI_BASE_CLASS_BRIDGE);
+
+	/* Enable PCIe capability */
+	nwl_pcie_modify_attr(pcie, 53, PCIE_ATTR53_CAP_NEXTPTR, 0x60);
+	nwl_pcie_modify_attr(pcie, 50,
+			     PCIE_ATTR50_CAP_NEXTPTR |
+			     PCIE_ATTR50_CAP_DEVICE_PORT_TYPE,
+			     FIELD_PREP(PCIE_ATTR50_CAP_DEVICE_PORT_TYPE,
+					PCI_EXP_TYPE_ROOT_PORT));
+
+	/* Disable MSI(-X) capability */
+	nwl_pcie_write_attr(pcie, 41, 0);
+	nwl_pcie_write_attr(pcie, 43, 0);
+	nwl_pcie_write_attr(pcie, 44, 0);
+	nwl_pcie_write_attr(pcie, 45, 0);
+	nwl_pcie_write_attr(pcie, 46, 0);
+	nwl_pcie_write_attr(pcie, 48, 0);
+
+	/* Disable DSN capability */
+	nwl_pcie_write_attr(pcie, 31, 0);
+	nwl_pcie_write_attr(pcie, 82, PCI_CFG_SPACE_SIZE);
+
+	/* Enable AER */
+	nwl_pcie_modify_attr(pcie, 2, 0,
+			     PCIE_ATTR2_AER_CAP_PERMIT_ROOTERR_UPDATE);
+
+	/* Disable L1 latency for root port */
+	nwl_pcie_modify_attr(pcie, 27,
+			     PCIE_ATTR27_DEV_CAP_ENDPOINT_L1_LATENCY, 0);
+
+	/* Enable bandwidth notification */
+	nwl_pcie_modify_attr(pcie, 37, 0,
+			     PCIE_ATTR37_LINK_CAP_BANDWIDTH_NOTIFICATION);
+
+	/* Set max link width */
+	nwl_pcie_write_attr(pcie, 97,
+			    FIELD_PREP(PCIE_ATTR97_LTSSM_MAX_LINK_WIDTH, width) |
+			    FIELD_PREP(PCIE_ATTR97_LINK_CAP_MAX_LINK_WIDTH, width));
+
+	/* Route misc. TLPs to controller */
+	nwl_pcie_modify_attr(pcie, 101, PCIE_ATTR101_EN_MSG_ROUTE,
+			     PCIE_ATTR101_EN_MSG_ROUTE_INTA |
+			     PCIE_ATTR101_EN_MSG_ROUTE_INTB |
+			     PCIE_ATTR101_EN_MSG_ROUTE_INTC |
+			     PCIE_ATTR101_EN_MSG_ROUTE_INTD |
+			     PCIE_ATTR101_EN_MSG_ROUTE_PM_PME |
+			     PCIE_ATTR101_EN_MSG_ROUTE_PME_TO_ACK |
+			     PCIE_ATTR101_EN_MSG_ROUTE_UNLOCK |
+			     PCIE_ATTR101_EN_MSG_ROUTE_PME_TURN_OFF);
+
+	/* Initialize completion credits */
+	nwl_pcie_write_attr(pcie, 105, 205); /* CD */
+	nwl_pcie_write_attr(pcie, 106,
+			    FIELD_PREP(PCIE_ATTR106_VC0_TOTAL_CREDITS_NPH, 12) |
+			    FIELD_PREP(PCIE_ATTR106_VC0_TOTAL_CREDITS_CH, 36));
+	nwl_pcie_write_attr(pcie, 107, 24); /* NPD */
+	nwl_pcie_write_attr(pcie, 108, 181); /* PD */
+	nwl_pcie_modify_attr(pcie, 109, PCIE_ATTR109_VC0_TOTAL_CREDITS_PH, 32);
+}
+
 static inline u32 nwl_bridge_readl(struct nwl_pcie *pcie, u32 off)
 {
 	return readl(pcie->breg_base + off);
@@ -806,6 +965,9 @@ static int nwl_pcie_parse_dt(struct nwl_pcie *pcie,
 	irq_set_chained_handler_and_data(pcie->irq_intx,
 					 nwl_pcie_leg_handler, pcie);
 
+	pcie->ctrl_reset = devm_reset_control_get_optional(dev, "ctrl");
+	if (IS_ERR(pcie->ctrl_reset))
+		return PTR_ERR(pcie->ctrl_reset);
 
 	for (i = 0; i < ARRAY_SIZE(pcie->phy); i++) {
 		pcie->phy[i] = devm_of_phy_get_by_index(dev, dev->of_node, i);
@@ -852,6 +1014,12 @@ static int nwl_pcie_probe(struct platform_device *pdev)
 	if (IS_ERR(pcie->clk))
 		return PTR_ERR(pcie->clk);
 
+	err = reset_control_assert(pcie->ctrl_reset);
+	if (err) {
+		dev_err(dev, "could not enter reset\n");
+		return err;
+	}
+
 	err = clk_prepare_enable(pcie->clk);
 	if (err) {
 		dev_err(dev, "can't enable PCIe ref clock\n");
@@ -864,6 +1032,15 @@ static int nwl_pcie_probe(struct platform_device *pdev)
 		goto err_clk;
 	}
 
+	if (pcie->ctrl_reset)
+		nwl_pcie_attr_init(pcie);
+
+	err = reset_control_deassert(pcie->ctrl_reset);
+	if (err) {
+		dev_err(dev, "could not release from reset\n");
+		goto err_phy_init;
+	}
+
 	err = nwl_pcie_phy_power_on(pcie);
 	if (err) {
 		dev_err(dev, "could not power on PHYs\n");
-- 
2.35.1.1320.gc452695387.dirty


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH 6/8] PCI: xilinx-nwl: Split phy_init from phy_power_on
From: Sean Anderson @ 2026-02-03  0:21 UTC (permalink / raw)
  To: Laurent Pinchart, Vinod Koul, linux-phy
  Cc: Krzysztof Wilczyński, Lorenzo Pieralisi, Radhey Shyam Pandey,
	linux-kernel, Michal Simek, linux-arm-kernel, linux-pci,
	Neil Armstrong, Rob Herring, Thippeswamy Havalige,
	Manivannan Sadhasivam, Bjorn Helgaas, Sean Anderson
In-Reply-To: <20260203002128.935842-1-sean.anderson@linux.dev>

In preparation for reset support, split phy_init from phy_power_on. The
former must be performed while the controller is in reset, while the
latter must be performed while the controller is not in reset.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

 drivers/pci/controller/pcie-xilinx-nwl.c | 78 +++++++++++++-----------
 1 file changed, 43 insertions(+), 35 deletions(-)

diff --git a/drivers/pci/controller/pcie-xilinx-nwl.c b/drivers/pci/controller/pcie-xilinx-nwl.c
index 7db2c96c6cec..7cfdc21e6f40 100644
--- a/drivers/pci/controller/pcie-xilinx-nwl.c
+++ b/drivers/pci/controller/pcie-xilinx-nwl.c
@@ -517,56 +517,55 @@ static int nwl_pcie_init_msi_irq_domain(struct nwl_pcie *pcie)
 
 static void nwl_pcie_phy_power_off(struct nwl_pcie *pcie, int i)
 {
-	int err = phy_power_off(pcie->phy[i]);
+	while (i--) {
+		int err = phy_power_off(pcie->phy[i]);
 
-	if (err)
-		dev_err(pcie->dev, "could not power off phy %d (err=%d)\n", i,
-			err);
+		if (err)
+			dev_err(pcie->dev,
+				"could not power off phy %d (err=%d)\n", i,
+				err);
+	}
 }
 
 static void nwl_pcie_phy_exit(struct nwl_pcie *pcie, int i)
 {
-	int err = phy_exit(pcie->phy[i]);
+	while (i--) {
+		int err = phy_exit(pcie->phy[i]);
 
-	if (err)
-		dev_err(pcie->dev, "could not exit phy %d (err=%d)\n", i, err);
+		if (err)
+			dev_err(pcie->dev, "could not exit phy %d (err=%d)\n",
+				i, err);
+	}
 }
 
-static int nwl_pcie_phy_enable(struct nwl_pcie *pcie)
+static int nwl_pcie_phy_init(struct nwl_pcie *pcie)
 {
 	int i, ret;
 
-	for (i = 0; i < ARRAY_SIZE(pcie->phy); i++) {
+	for (i = ARRAY_SIZE(pcie->phy) - 1; i >= 0; i--) {
 		ret = phy_init(pcie->phy[i]);
-		if (ret)
-			goto err;
-
-		ret = phy_power_on(pcie->phy[i]);
 		if (ret) {
 			nwl_pcie_phy_exit(pcie, i);
-			goto err;
+			return ret;
 		}
 	}
 
 	return 0;
-
-err:
-	while (i--) {
-		nwl_pcie_phy_power_off(pcie, i);
-		nwl_pcie_phy_exit(pcie, i);
-	}
-
-	return ret;
 }
 
-static void nwl_pcie_phy_disable(struct nwl_pcie *pcie)
+static int nwl_pcie_phy_power_on(struct nwl_pcie *pcie)
 {
-	int i;
+	int i, ret;
 
-	for (i = ARRAY_SIZE(pcie->phy); i--;) {
-		nwl_pcie_phy_power_off(pcie, i);
-		nwl_pcie_phy_exit(pcie, i);
+	for (i = ARRAY_SIZE(pcie->phy) - 1; i >= 0; i--) {
+		ret = phy_power_on(pcie->phy[i]);
+		if (ret) {
+			nwl_pcie_phy_power_off(pcie, i);
+			return ret;
+		}
 	}
+
+	return 0;
 }
 
 static int nwl_pcie_init_irq_domain(struct nwl_pcie *pcie)
@@ -859,22 +858,28 @@ static int nwl_pcie_probe(struct platform_device *pdev)
 		return err;
 	}
 
-	err = nwl_pcie_phy_enable(pcie);
+	err = nwl_pcie_phy_init(pcie);
 	if (err) {
-		dev_err(dev, "could not enable PHYs\n");
+		dev_err(dev, "could not init PHYs\n");
 		goto err_clk;
 	}
 
+	err = nwl_pcie_phy_power_on(pcie);
+	if (err) {
+		dev_err(dev, "could not power on PHYs\n");
+		goto err_phy_init;
+	}
+
 	err = nwl_pcie_bridge_init(pcie);
 	if (err) {
 		dev_err(dev, "HW Initialization failed\n");
-		goto err_phy;
+		goto err_phy_power;
 	}
 
 	err = nwl_pcie_init_irq_domain(pcie);
 	if (err) {
 		dev_err(dev, "Failed creating IRQ Domain\n");
-		goto err_phy;
+		goto err_phy_power;
 	}
 
 	bridge->sysdata = pcie;
@@ -884,7 +889,7 @@ static int nwl_pcie_probe(struct platform_device *pdev)
 		err = nwl_pcie_enable_msi(pcie);
 		if (err < 0) {
 			dev_err(dev, "failed to enable MSI support: %d\n", err);
-			goto err_phy;
+			goto err_phy_power;
 		}
 	}
 
@@ -892,8 +897,10 @@ static int nwl_pcie_probe(struct platform_device *pdev)
 	if (!err)
 		return 0;
 
-err_phy:
-	nwl_pcie_phy_disable(pcie);
+err_phy_power:
+	nwl_pcie_phy_power_off(pcie, ARRAY_SIZE(pcie->phy));
+err_phy_init:
+	nwl_pcie_phy_exit(pcie, ARRAY_SIZE(pcie->phy));
 err_clk:
 	clk_disable_unprepare(pcie->clk);
 	return err;
@@ -903,7 +910,8 @@ static void nwl_pcie_remove(struct platform_device *pdev)
 {
 	struct nwl_pcie *pcie = platform_get_drvdata(pdev);
 
-	nwl_pcie_phy_disable(pcie);
+	nwl_pcie_phy_power_off(pcie, ARRAY_SIZE(pcie->phy));
+	nwl_pcie_phy_exit(pcie, ARRAY_SIZE(pcie->phy));
 	clk_disable_unprepare(pcie->clk);
 }
 
-- 
2.35.1.1320.gc452695387.dirty


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH 5/8] phy: zynqmp: Initialize chicken bits
From: Sean Anderson @ 2026-02-03  0:21 UTC (permalink / raw)
  To: Laurent Pinchart, Vinod Koul, linux-phy
  Cc: Krzysztof Wilczyński, Lorenzo Pieralisi, Radhey Shyam Pandey,
	linux-kernel, Michal Simek, linux-arm-kernel, linux-pci,
	Neil Armstrong, Rob Herring, Thippeswamy Havalige,
	Manivannan Sadhasivam, Bjorn Helgaas, Sean Anderson
In-Reply-To: <20260203002128.935842-1-sean.anderson@linux.dev>

These bits are all set by serdes_init(). Move them to the phy driver so
we can skip serdes_init().

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

 drivers/phy/xilinx/phy-zynqmp.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/drivers/phy/xilinx/phy-zynqmp.c b/drivers/phy/xilinx/phy-zynqmp.c
index 854b0ea04648..1bdf29ba284c 100644
--- a/drivers/phy/xilinx/phy-zynqmp.c
+++ b/drivers/phy/xilinx/phy-zynqmp.c
@@ -54,7 +54,13 @@
 /* PCS control parameters */
 #define L0_TM_ANA_BYP_4			0x1010
 #define L0_TM_ANA_BYP_7			0x1018
+#define L0_TM_ANA_BYP_12		0x102c
+#define L0_TM_ANA_BYP_12_FORCE_UPHY_PSO_HSRXDIG		BIT(6)
+#define L0_TM_ANA_BYP_15		0x1038
+#define L0_TM_ANA_BYP_15_FORCE_UPHY_ENABLE_LOW_LEAKAGE	BIT(6)
 #define L0_TM_DIG_6			0x106c
+#define L0_TM_DIG_8			0x1074
+#define L0_TM_DIG_8_EYESURF		BIT(4)
 #define L0_TM_DIG_22			0x10ac
 #define L0_TM_DIS_DESCRAMBLE_DECODER	0x0f
 #define L0_TX_DIG_61			0x00f4
@@ -82,7 +88,13 @@
 #define L0_TM_EQ1_EQ_STG2_PREAMP_MODE_VAL	BIT(2)
 #define L0_TM_E_ILL8			0x1940
 #define L0_TM_E_ILL9			0x1944
+#define L0_TM_EQ11			0x1978
+#define L0_TM_EQ11_FORCE_EQ_OFFS_OFF	BIT(4)
 #define L0_TM_ILL13			0x1994
+#define L0_TM_RST_DLY			0x19a4
+#define L0_TM_MISC3			0x19ac
+#define L0_TM_MISC3_CDR_EN_FPL		BIT(1)
+#define L0_TM_MISC3_CDR_EN_FFL		BIT(0)
 #define L0_TM_CDR5			0x1c14
 #define L0_TM_CDR5_FPHL_FSM_ACC_CYCLES	GENMASK(7, 5)
 #define L0_TM_CDR5_FFL_PH0_INT_GAIN	GENMASK(4, 0)
@@ -849,6 +861,18 @@ static int xpsgtr_common_init(struct xpsgtr_phy *gtr_phy)
 	/* Enable coarse code saturation limiting logic. */
 	xpsgtr_write_phy(gtr_phy, L0_TM_PLL_DIG_37, L0_TM_COARSE_CODE_LIMIT);
 
+	/* Miscellaneous chicken bits */
+	xpsgtr_clr_set_phy(gtr_phy, L0_TM_DIG_8, 0, L0_TM_DIG_8_EYESURF);
+	xpsgtr_write_phy(gtr_phy, L0_TM_ILL13, 7);
+	xpsgtr_write_phy(gtr_phy, L0_TM_RST_DLY, 255);
+	xpsgtr_clr_set_phy(gtr_phy, L0_TM_ANA_BYP_15, 0,
+			   L0_TM_ANA_BYP_15_FORCE_UPHY_ENABLE_LOW_LEAKAGE);
+	xpsgtr_clr_set_phy(gtr_phy, L0_TM_ANA_BYP_12, 0,
+			   L0_TM_ANA_BYP_12_FORCE_UPHY_PSO_HSRXDIG);
+	xpsgtr_clr_set_phy(gtr_phy, L0_TM_MISC3, L0_TM_MISC3_CDR_EN_FPL |
+						 L0_TM_MISC3_CDR_EN_FFL, 0);
+	xpsgtr_clr_set_phy(gtr_phy, L0_TM_EQ11, 0, L0_TM_EQ11_FORCE_EQ_OFFS_OFF);
+
 	ret = xpsgtr_configure_pll(gtr_phy);
 	if (ret)
 		return ret;
-- 
2.35.1.1320.gc452695387.dirty


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH 4/8] phy: zynqmp: Calibrate ILL if necessary
From: Sean Anderson @ 2026-02-03  0:21 UTC (permalink / raw)
  To: Laurent Pinchart, Vinod Koul, linux-phy
  Cc: Krzysztof Wilczyński, Lorenzo Pieralisi, Radhey Shyam Pandey,
	linux-kernel, Michal Simek, linux-arm-kernel, linux-pci,
	Neil Armstrong, Rob Herring, Thippeswamy Havalige,
	Manivannan Sadhasivam, Bjorn Helgaas, Sean Anderson
In-Reply-To: <20260203002128.935842-1-sean.anderson@linux.dev>

init_serdes in psu_init_gpl is supposed to calibrate the ILL. However, this
may fail if the reference clock is not running, such as if the clock needs
to be configured on boot. To work around this, add support for ILL
calibration in U-Boot. If the ILL is already calibrated (any non-zero
value) we skip calibration.

The algorithm is substantially the same as serdes_illcalib [1], but it has
been updated for readability (and to remove all the "if (lane0_active)"
conditions). Due to the amount of register fields, many of which are
undocumented (especially the chicken bits), I have mostly used defines only
for the register names. There are certainly areas where register writes are
superfluous, but I have left these in order to minimize deviation from the
procedure in serdes_illcalib.

[1] Example implementation; xpsgtr_phy_illcalib coresponds to
    serdes_illcalib_pcie_gen1:
https://source.denx.de/u-boot/u-boot/-/blob/v2026.01/board/xilinx/zynqmp/zynqmp-zcu208-revA/psu_init_gpl.c?ref_type=tags#L710

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

 drivers/phy/xilinx/phy-zynqmp.c | 421 +++++++++++++++++++++++++++++++-
 1 file changed, 420 insertions(+), 1 deletion(-)

diff --git a/drivers/phy/xilinx/phy-zynqmp.c b/drivers/phy/xilinx/phy-zynqmp.c
index 152af1702bbd..854b0ea04648 100644
--- a/drivers/phy/xilinx/phy-zynqmp.c
+++ b/drivers/phy/xilinx/phy-zynqmp.c
@@ -12,6 +12,7 @@
  * PCIe should also work but that is experimental as of now.
  */
 
+#include <linux/bitfield.h>
 #include <linux/clk.h>
 #include <linux/debugfs.h>
 #include <linux/delay.h>
@@ -31,6 +32,7 @@
  */
 
 /* TX De-emphasis parameters */
+#define L0_TX_ANA_TM_3			0x000c
 #define L0_TX_ANA_TM_18			0x0048
 #define L0_TX_ANA_TM_118		0x01d8
 #define L0_TX_ANA_TM_118_FORCE_17_0	BIT(0)
@@ -50,16 +52,49 @@
 #define L0_TXPMD_TM_45_ENABLE_DP_POST2	BIT(5)
 
 /* PCS control parameters */
+#define L0_TM_ANA_BYP_4			0x1010
+#define L0_TM_ANA_BYP_7			0x1018
 #define L0_TM_DIG_6			0x106c
+#define L0_TM_DIG_22			0x10ac
 #define L0_TM_DIS_DESCRAMBLE_DECODER	0x0f
 #define L0_TX_DIG_61			0x00f4
 #define L0_TM_DISABLE_SCRAMBLE_ENCODER	0x0f
+#define L0_TM_AUX_0			0x10cc
+#define L0_TM_MISC2			0x189c
+#define L0_TM_MISC2_ILL_CAL_BYPASS	BIT(7)
+#define L0_TM_IQ_ILL1			0x18f8
+#define L0_TM_IQ_ILL2			0x18fc
+#define L0_TM_ILL11			0x198c
+#define L0_TM_ILL12			0x1990
+#define L0_TM_E_ILL1			0x1924
+#define L0_TM_E_ILL2			0x1928
+#define L0_TM_IQ_ILL3			0x1900
+#define L0_TM_E_ILL3			0x192c
+#define L0_TM_IQ_ILL7			0x1910
+#define L0_TM_E_ILL7			0x193c
+#define L0_TM_ILL8			0x1980
+#define L0_TM_IQ_ILL8			0x1914
+#define L0_TM_IQ_ILL9			0x1918
+#define L0_TM_EQ0			0x194c
+#define L0_TM_EQ0_EQ_STG2_CTRL_BYP	BIT(5)
+#define L0_TM_EQ1			0x1950
+#define L0_TM_EQ1_EQ_STG2_RL_PROG	GENMASK(1, 0)
+#define L0_TM_EQ1_EQ_STG2_PREAMP_MODE_VAL	BIT(2)
+#define L0_TM_E_ILL8			0x1940
+#define L0_TM_E_ILL9			0x1944
+#define L0_TM_ILL13			0x1994
+#define L0_TM_CDR5			0x1c14
+#define L0_TM_CDR5_FPHL_FSM_ACC_CYCLES	GENMASK(7, 5)
+#define L0_TM_CDR5_FFL_PH0_INT_GAIN	GENMASK(4, 0)
+#define L0_TM_CDR16			0x1c40
 
 /* PLL Test Mode register parameters */
+#define L0_TM_PLL_DIG_33		0x2084
 #define L0_TM_PLL_DIG_37		0x2094
 #define L0_TM_COARSE_CODE_LIMIT		0x10
 
 /* PLL SSC step size offsets */
+#define L0_PLL_FBDIV_FRAC_3_MSB		0x2360
 #define L0_PLL_SS_STEPS_0_LSB		0x2368
 #define L0_PLL_SS_STEPS_1_MSB		0x236c
 #define L0_PLL_SS_STEP_SIZE_0_LSB	0x2370
@@ -69,6 +104,7 @@
 #define L0_PLL_STATUS_READ_1		0x23e4
 
 /* SSC step size parameters */
+#define TM_FORCE_EN_FRAC		BIT(6)
 #define STEP_SIZE_0_MASK		0xff
 #define STEP_SIZE_1_MASK		0xff
 #define STEP_SIZE_2_MASK		0xff
@@ -76,6 +112,7 @@
 #define STEP_SIZE_SHIFT			8
 #define FORCE_STEP_SIZE			0x10
 #define FORCE_STEPS			0x20
+#define TM_FORCE_EN			BIT(7)
 #define STEPS_0_MASK			0xff
 #define STEPS_1_MASK			0x07
 
@@ -84,6 +121,32 @@
 #define L0_REF_CLK_LCL_SEL		BIT(7)
 #define L0_REF_CLK_SEL_MASK		0x9f
 
+/* Built-in self-test parameters */
+#define L0_BIST_CTRL_1			0x3004
+#define L0_BIST_CTRL_2			0x3008
+#define L0_BIST_RUN_LEN_L		0x300c
+#define L0_BIST_ERR_INJ_POINT_L		0x3010
+#define L0_BIST_RUNLEN_ERR_INJ_H	0x3014
+#define L0_BIST_IDLE_TIME		0x3018
+#define L0_BIST_MARKER_L		0x301c
+#define L0_BIST_IDLE_CHAR_L		0x3020
+#define L0_BIST_MARKER_IDLE_H		0x3024
+#define L0_BIST_LOW_PULSE_TIME		0x3028
+#define L0_BIST_TOTAL_PULSE_TIME	0x302c
+#define L0_BIST_TEST_PAT_1		0x3030
+#define L0_BIST_TEST_PAT_2		0x3034
+#define L0_BIST_TEST_PAT_3		0x3038
+#define L0_BIST_TEST_PAT_4		0x303c
+#define L0_BIST_TEST_PAT_MSBS		0x3040
+#define L0_BIST_PKT_NUM			0x3044
+#define L0_BIST_FRM_IDLE_TIME		0x3048
+#define L0_BIST_PKT_CTR_L		0x304c
+#define L0_BIST_PKT_CTR_H		0x3050
+#define L0_BIST_ERR_CTR_L		0x3054
+#define L0_BIST_ERR_CTR_H		0x3058
+#define L0_BIST_FILLER_OUT		0x3068
+#define L0_BIST_FORCE_MK_RST		0x306c
+
 /* Calibration digital logic parameters */
 #define L3_TM_CALIB_DIG19		0xec4c
 #define L3_CALIB_DONE_STATUS		0xef14
@@ -139,6 +202,9 @@ static const char *const xpsgtr_icm_str[] = {
 #define TM_CMN_RST_SET			0x2
 #define TM_CMN_RST_MASK			0x3
 
+#define LPBK_CTRL0			0x10038
+#define LPBK_CTRL1			0x1003c
+
 /* Bus width parameters */
 #define TX_PROT_BUS_WIDTH		0x10040
 #define RX_PROT_BUS_WIDTH		0x10044
@@ -148,9 +214,13 @@ static const char *const xpsgtr_icm_str[] = {
 #define PROT_BUS_WIDTH_SHIFT(n)		((n) * 2)
 #define PROT_BUS_WIDTH_MASK(n)		GENMASK((n) * 2 + 1, (n) * 2)
 
+#define UPHY_SPARE0			0X10098
+
 /* Number of GT lanes */
 #define NUM_LANES			4
 
+#define SIOU_ECO_0			0x1c
+
 /* SIOU SATA control register */
 #define SATA_CONTROL_OFFSET		0x0100
 
@@ -338,6 +408,33 @@ static void xpsgtr_restore_lane_regs(struct xpsgtr_dev *gtr_dev)
 			     gtr_dev->saved_regs[i]);
 }
 
+static inline void xpsgtr_write_lanes(struct xpsgtr_dev *gtr_dev,
+				     unsigned long *lanes, u32 reg, u32 value)
+{
+	unsigned long lane;
+
+	for_each_set_bit(lane, lanes, NUM_LANES) {
+		void __iomem *addr = gtr_dev->serdes + lane * PHY_REG_OFFSET
+				     + reg;
+
+		writel(value, addr);
+	}
+}
+
+static inline void xpsgtr_clr_set_lanes(struct xpsgtr_dev *gtr_dev,
+				       unsigned long *lanes, u32 reg, u32 clr,
+				       u32 set)
+{
+	unsigned long lane;
+
+	for_each_set_bit(lane, lanes, NUM_LANES) {
+		void __iomem *addr = gtr_dev->serdes + lane * PHY_REG_OFFSET
+				     + reg;
+
+		writel((readl(addr) & ~clr) | set, addr);
+	}
+}
+
 /*
  * Hardware Configuration
  */
@@ -351,7 +448,7 @@ static int xpsgtr_wait_pll_lock(struct phy *phy)
 	u8 protocol = gtr_phy->protocol;
 	int ret;
 
-	dev_dbg(gtr_dev->dev, "Waiting for PLL lock\n");
+	dev_vdbg(gtr_dev->dev, "Waiting for PLL lock\n");
 
 	/*
 	 * For DP and PCIe, only the instance 0 PLL is used. Switch to that phy
@@ -520,6 +617,231 @@ static void xpsgtr_bypass_scrambler_8b10b(struct xpsgtr_phy *gtr_phy)
 	xpsgtr_write_phy(gtr_phy, L0_TX_DIG_61, L0_TM_DISABLE_SCRAMBLE_ENCODER);
 }
 
+/* Enable or disable loopback */
+static void xpsgtr_phy_set_loopback(struct xpsgtr_phy *gtr_phy, bool enabled)
+{
+	struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
+	u32 reg = gtr_phy->lane >= 2 ? LPBK_CTRL1 : LPBK_CTRL0;
+	u32 shift = gtr_phy->lane & 1 ? 4 : 0;
+
+	xpsgtr_clr_set(gtr_dev, reg, 7 << shift, (u32)enabled << shift);
+}
+
+static void xpsgtr_phy_set_ill(struct xpsgtr_phy *gtr_phy, u32 ill, bool gen2)
+{
+	u32 val = 4 + ill * 8;
+
+	if (gen2) {
+		xpsgtr_write_phy(gtr_phy, L0_TM_E_ILL2, val & 0xff);
+		xpsgtr_clr_set_phy(gtr_phy, L0_TM_ILL12, 0x0f,
+				   1 << (val >> 8));
+	} else {
+		xpsgtr_write_phy(gtr_phy, L0_TM_E_ILL1, val & 0xff);
+		xpsgtr_clr_set_phy(gtr_phy, L0_TM_ILL12, 0xf0,
+				   (val >> 4) & 0x10);
+	}
+}
+
+static bool xpsgtr_ill_calibrated(struct xpsgtr_phy *gtr_phy)
+{
+	u32 ill1 = xpsgtr_read_phy(gtr_phy, L0_TM_E_ILL1);
+	u32 ill2 = xpsgtr_read_phy(gtr_phy, L0_TM_E_ILL2);
+	u32 ill12 = xpsgtr_read_phy(gtr_phy, L0_TM_ILL12);
+
+	dev_dbg(gtr_phy->dev->dev, "lane %u gen1 ILL was %u gen2 ILL was %u\n",
+		gtr_phy->lane, ill1 / 8 + (ill12 & 0x10 ? 32 : 0),
+		ill2 / 8 + (ill12 & 0x02 ? 32 : 0));
+	return ill1 || ill2 || ill12;
+}
+
+static void xpsgtr_init_ill(struct xpsgtr_phy *gtr_phy)
+{
+	struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
+	struct clk *clk = gtr_dev->clk[gtr_phy->refclk];
+	u32 ill123 = DIV_ROUND_CLOSEST(clk_get_rate(clk), 1000000);
+
+	xpsgtr_clr_set_phy(gtr_phy, L0_TM_MISC2, 0, L0_TM_MISC2_ILL_CAL_BYPASS);
+	xpsgtr_write_phy(gtr_phy, L0_TM_IQ_ILL1, ill123);
+	xpsgtr_write_phy(gtr_phy, L0_TM_IQ_ILL2, ill123);
+	xpsgtr_write_phy(gtr_phy, L0_TM_IQ_ILL3, ill123);
+	xpsgtr_write_phy(gtr_phy, L0_TM_IQ_ILL7, 0xf3);
+	xpsgtr_write_phy(gtr_phy, L0_TM_E_ILL7, 0xf3);
+	xpsgtr_write_phy(gtr_phy, L0_TM_ILL8, 0xff);
+	xpsgtr_write_phy(gtr_phy, L0_TM_IQ_ILL8, 0xf3);
+	xpsgtr_write_phy(gtr_phy, L0_TM_E_ILL8, 0xf3);
+	xpsgtr_write_phy(gtr_phy, L0_TM_IQ_ILL9, 1);
+	xpsgtr_write_phy(gtr_phy, L0_TM_E_ILL9, 1);
+	xpsgtr_clr_set(gtr_dev, UPHY_SPARE0, BIT(5), 0);
+}
+
+static void xpsgtr_phy_illcalib(struct xpsgtr_dev *gtr_dev,
+				     unsigned long *lanes, bool gen2)
+{
+	bool last_ok[NUM_LANES] = { 0 };
+	int pass[NUM_LANES] = { 0 }, altpass[NUM_LANES] = { 0 };
+	int best[NUM_LANES] = { 0 }, altbest[NUM_LANES] = { 0 };
+	unsigned long lane;
+	int i;
+
+	/* Initialize the BIST */
+	xpsgtr_clr_set_lanes(gtr_dev, lanes, L0_BIST_CTRL_1, 0xe0, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_FILLER_OUT, 1);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_FORCE_MK_RST, 1);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_TM_DIG_22, 0x20);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_CTRL_2, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_RUN_LEN_L, 0xf4);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_ERR_INJ_POINT_L, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_RUNLEN_ERR_INJ_H, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_IDLE_TIME, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_MARKER_L, 0xfb);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_IDLE_CHAR_L, 0xff);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_MARKER_IDLE_H, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_LOW_PULSE_TIME, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TOTAL_PULSE_TIME, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TEST_PAT_1, 0x4a);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TEST_PAT_2, 0x4a);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TEST_PAT_3, 0x4a);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TEST_PAT_4, 0x4a);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TEST_PAT_MSBS, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_PKT_NUM, 0x14);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_FRM_IDLE_TIME, 2);
+	xpsgtr_clr_set_lanes(gtr_dev, lanes, L0_BIST_CTRL_1, 0xe0, 0);
+
+	for (i = 0; i < 64; i++) {
+		bool ok[NUM_LANES];
+
+		for_each_set_bit(lane, lanes, NUM_LANES)
+			xpsgtr_phy_set_ill(&gtr_dev->phys[lane], i, gen2);
+
+		/* Reset lanes */
+		xpsgtr_clr_set_lanes(gtr_dev, lanes, L0_TM_ANA_BYP_7, 0x20,
+				     0x10);
+		xpsgtr_write(gtr_dev, UPHY_SPARE0, 0x00);
+		xpsgtr_write_lanes(gtr_dev, lanes, L0_TM_ANA_BYP_4, 0x40);
+		xpsgtr_write_lanes(gtr_dev, lanes, L0_TM_PLL_DIG_33, 0x80);
+		xpsgtr_write(gtr_dev, UPHY_SPARE0, 0x04);
+		udelay(50);
+		if (gen2)
+			xpsgtr_write(gtr_dev, UPHY_SPARE0, 0x0e);
+		xpsgtr_write(gtr_dev, UPHY_SPARE0, 0x06);
+		if (gen2) {
+			xpsgtr_write_lanes(gtr_dev, lanes, L0_TX_ANA_TM_3, 0x04);
+			xpsgtr_write(gtr_dev, UPHY_SPARE0, 0x07);
+			udelay(400);
+			xpsgtr_write_lanes(gtr_dev, lanes, L0_TX_ANA_TM_3, 0x0c);
+			udelay(15);
+			xpsgtr_write(gtr_dev, UPHY_SPARE0, 0x0f);
+			udelay(100);
+		}
+
+		if (xpsgtr_wait_pll_lock(gtr_dev->phys[0].phy)) {
+			memset(last_ok, 0, sizeof(last_ok));
+			continue;
+		}
+
+		udelay(50);
+		xpsgtr_write_lanes(gtr_dev, lanes, L0_TM_ANA_BYP_4, 0xc0);
+		xpsgtr_write_lanes(gtr_dev, lanes, L0_TM_ANA_BYP_4, 0x80);
+		xpsgtr_write_lanes(gtr_dev, lanes, L0_TM_PLL_DIG_33, 0xc0);
+		udelay(50);
+		xpsgtr_write_lanes(gtr_dev, lanes, L0_TM_PLL_DIG_33, 0x80);
+		udelay(50);
+		xpsgtr_write_lanes(gtr_dev, lanes, L0_TM_ANA_BYP_4, 0);
+		xpsgtr_write_lanes(gtr_dev, lanes, L0_TM_PLL_DIG_33, 0);
+		udelay(500);
+
+		/* Do the BIST */
+		for_each_set_bit(lane, lanes, NUM_LANES) {
+			struct xpsgtr_phy *gtr_phy = &gtr_dev->phys[lane];
+			u32 packets, errors;
+
+			xpsgtr_phy_init_bus_width(gtr_phy, PROT_BUS_WIDTH_10);
+			xpsgtr_phy_set_loopback(gtr_phy, true);
+			xpsgtr_write_phy(gtr_phy, L0_TM_DIG_22, 0x20);
+			xpsgtr_clr_set_phy(gtr_phy, L0_BIST_CTRL_1, 0, 1);
+
+			udelay(200);
+			xpsgtr_write_phy(gtr_phy, L0_BIST_CTRL_1, 0);
+			packets = xpsgtr_read_phy(gtr_phy, L0_BIST_PKT_CTR_L);
+			packets |= xpsgtr_read_phy(gtr_phy, L0_BIST_PKT_CTR_H) << 8;
+			errors = xpsgtr_read_phy(gtr_phy, L0_BIST_ERR_CTR_L);
+			errors |= xpsgtr_read_phy(gtr_phy, L0_BIST_ERR_CTR_H) << 8;
+			ok[lane] = packets && !errors;
+
+			dev_dbg(gtr_dev->dev,
+				"lane %lu ILL %d packets %10u errors %10u\n",
+				lane, i, packets, errors);
+		}
+
+		xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_CTRL_1, 0);
+		xpsgtr_write(gtr_dev, UPHY_SPARE0, 0x00);
+		xpsgtr_write(gtr_dev, UPHY_SPARE0, 0x02);
+
+		for_each_set_bit(lane, lanes, NUM_LANES) {
+			pass[lane] += ok[lane] && last_ok[lane];
+			if (pass[lane] < 4) {
+				if (!ok[lane] && i > 2) {
+					if (altpass[lane] < pass[lane]) {
+						altpass[lane] = pass[lane];
+						altbest[lane] =
+							(i - 1) - (pass[lane] + 1) / 2;
+					}
+					pass[lane] = 0;
+				}
+			} else if (!best[lane] && (!ok[lane] || i == 63) &&
+				   last_ok[lane]) {
+				best[lane] = (i - 1) - (pass[lane] + 1) / 2;
+			}
+		}
+
+		memcpy(last_ok, ok, sizeof(ok));
+	}
+
+	for_each_set_bit(lane, lanes, NUM_LANES) {
+		dev_dbg(gtr_dev->dev, "lane %lu ILL best %d alt best %d\n",
+			lane, best[lane], altbest[lane]);
+
+		xpsgtr_phy_set_ill(&gtr_dev->phys[lane],
+				   best[lane] ?: altbest[lane], gen2);
+	}
+
+	/* Clean up */
+	xpsgtr_clr_set_lanes(gtr_dev, lanes, L0_TM_ANA_BYP_7, 0x30, 0);
+	xpsgtr_write(gtr_dev, UPHY_SPARE0, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_CTRL_1, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_CTRL_2, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_RUN_LEN_L, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_ERR_INJ_POINT_L, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_RUNLEN_ERR_INJ_H, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_IDLE_TIME, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_MARKER_L, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_IDLE_CHAR_L, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_MARKER_IDLE_H, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_LOW_PULSE_TIME, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TOTAL_PULSE_TIME, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TEST_PAT_1, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TEST_PAT_2, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TEST_PAT_3, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TEST_PAT_4, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_TEST_PAT_MSBS, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_PKT_NUM, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_FRM_IDLE_TIME, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_PKT_CTR_L, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_PKT_CTR_H, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_ERR_CTR_L, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_ERR_CTR_H, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_FILLER_OUT, 1);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_BIST_FORCE_MK_RST, 0);
+	xpsgtr_write_lanes(gtr_dev, lanes, L0_TM_DIG_22, 0);
+
+	for_each_set_bit(lane, lanes, NUM_LANES) {
+		struct xpsgtr_phy *gtr_phy = &gtr_dev->phys[lane];
+
+		xpsgtr_phy_init_bus_width(gtr_phy, PROT_BUS_WIDTH_20);
+		xpsgtr_phy_set_loopback(gtr_phy, false);
+	}
+}
+
 static int xpsgtr_common_init(struct xpsgtr_phy *gtr_phy)
 {
 	int ret;
@@ -553,6 +875,37 @@ static void xpsgtr_phy_init_sata(struct xpsgtr_phy *gtr_phy)
 {
 	struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
 
+	if (!xpsgtr_ill_calibrated(gtr_phy)) {
+		DECLARE_BITMAP(lanes, NUM_LANES) = { 0 };
+
+		xpsgtr_init_ill(gtr_phy);
+		xpsgtr_write_phy(gtr_phy, L0_TM_E_ILL3, 100);
+		xpsgtr_clr_set_phy(gtr_phy, L0_TM_ILL11, 0xf0, 0x20);
+
+		__set_bit(gtr_phy->lane, lanes);
+		xpsgtr_phy_illcalib(gtr_dev, lanes, false);
+		xpsgtr_phy_set_ill(gtr_phy, 7, true);
+	}
+
+	/* Disable SSC */
+	xpsgtr_write_phy(gtr_phy, L0_PLL_FBDIV_FRAC_3_MSB, TM_FORCE_EN_FRAC);
+	xpsgtr_clr_set_phy(gtr_phy, L0_PLL_SS_STEP_SIZE_3_MSB, 0, TM_FORCE_EN);
+
+	/* Disable Tx deemphasis */
+	xpsgtr_write_phy(gtr_phy, L0_TM_CDR5,
+			 FIELD_PREP(L0_TM_CDR5_FPHL_FSM_ACC_CYCLES, 7) |
+			 FIELD_PREP(L0_TM_CDR5_FFL_PH0_INT_GAIN, 6));
+	xpsgtr_write_phy(gtr_phy, L0_TM_CDR16, 12);
+
+	/* Configure equalization */
+	xpsgtr_write_phy(gtr_phy, L0_TX_ANA_TM_118,
+			 L0_TX_ANA_TM_118_FORCE_17_0);
+	xpsgtr_clr_set_phy(gtr_phy, L0_TM_EQ0, 0, L0_TM_EQ0_EQ_STG2_CTRL_BYP);
+	xpsgtr_clr_set_phy(gtr_phy, L0_TM_EQ1, L0_TM_EQ1_EQ_STG2_RL_PROG,
+			   FIELD_PREP(L0_TM_EQ1_EQ_STG2_RL_PROG, 2) |
+			   L0_TM_EQ1_EQ_STG2_PREAMP_MODE_VAL);
+	xpsgtr_write_phy(gtr_phy, L0_TX_ANA_TM_18, 2); /* -3.5 dB deemphasis */
+
 	xpsgtr_bypass_scrambler_8b10b(gtr_phy);
 
 	writel(gtr_phy->lane, gtr_dev->siou + SATA_CONTROL_OFFSET);
@@ -565,6 +918,64 @@ static void xpsgtr_phy_init_sgmii(struct xpsgtr_phy *gtr_phy)
 	xpsgtr_bypass_scrambler_8b10b(gtr_phy);
 }
 
+/* PCIe-specific initialization. */
+static int xpsgtr_phy_init_pcie(struct xpsgtr_phy *gtr_phy)
+{
+	struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
+	DECLARE_BITMAP(lanes, NUM_LANES) = { 0 };
+	unsigned long lane;
+	bool calibrated = false;
+
+	xpsgtr_clr_set_phy(gtr_phy, L0_TM_AUX_0, 0, 0x20);
+
+	for (lane = 0; lane < NUM_LANES; lane++) {
+		struct xpsgtr_phy *gtr_phy = &gtr_dev->phys[lane];
+
+		if (gtr_phy->protocol != ICM_PROTOCOL_PCIE)
+			continue;
+
+		__set_bit(lane, lanes);
+		calibrated = calibrated || xpsgtr_ill_calibrated(gtr_phy);
+	}
+
+	if (calibrated)
+		return 0;
+
+	/* Write default ILL config */
+	for_each_set_bit(lane, lanes, NUM_LANES) {
+		struct xpsgtr_phy *p = &gtr_dev->phys[lane];
+
+		if (lane != gtr_phy->lane) {
+			int ret = xpsgtr_common_init(p);
+
+			if (ret)
+				return ret;
+		}
+
+		xpsgtr_init_ill(p);
+		xpsgtr_write_phy(p, L0_TM_E_ILL3, 0);
+		xpsgtr_clr_set_phy(p, L0_TM_MISC2, 0,
+				   L0_TM_MISC2_ILL_CAL_BYPASS);
+	}
+
+	/* Perform the ILL calibration procedure */
+	xpsgtr_phy_illcalib(gtr_dev, lanes, false);
+	xpsgtr_phy_illcalib(gtr_dev, lanes, true);
+
+	/* Disable PCIe ECO */
+	writel(1, gtr_dev->siou + SIOU_ECO_0);
+	return 0;
+}
+
+/* USB-specific initialization. */
+static void xpsgtr_phy_init_usb(struct xpsgtr_phy *gtr_phy)
+{
+	xpsgtr_clr_set_phy(gtr_phy, L0_TM_AUX_0, 0, 0x20);
+	xpsgtr_write_phy(gtr_phy, L0_TM_IQ_ILL8, 0xf3);
+	xpsgtr_write_phy(gtr_phy, L0_TM_E_ILL8, 0xf3);
+	xpsgtr_phy_set_ill(gtr_phy, 7, false);
+}
+
 /* Configure TX de-emphasis and margining for DP. */
 static void xpsgtr_phy_configure_dp(struct xpsgtr_phy *gtr_phy, unsigned int pre,
 				    unsigned int voltage)
@@ -710,6 +1121,10 @@ static int xpsgtr_phy_init(struct phy *phy)
 		xpsgtr_phy_init_dp(gtr_phy);
 		break;
 
+	case ICM_PROTOCOL_PCIE:
+		ret = xpsgtr_phy_init_pcie(gtr_phy);
+		break;
+
 	case ICM_PROTOCOL_SATA:
 		xpsgtr_phy_init_sata(gtr_phy);
 		break;
@@ -717,6 +1132,10 @@ static int xpsgtr_phy_init(struct phy *phy)
 	case ICM_PROTOCOL_SGMII:
 		xpsgtr_phy_init_sgmii(gtr_phy);
 		break;
+
+	case ICM_PROTOCOL_USB:
+		xpsgtr_phy_init_usb(gtr_phy);
+		break;
 	}
 
 out:
-- 
2.35.1.1320.gc452695387.dirty


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH 3/8] phy: zynqmp: Refactor common phy initialization into a helper
From: Sean Anderson @ 2026-02-03  0:21 UTC (permalink / raw)
  To: Laurent Pinchart, Vinod Koul, linux-phy
  Cc: Krzysztof Wilczyński, Lorenzo Pieralisi, Radhey Shyam Pandey,
	linux-kernel, Michal Simek, linux-arm-kernel, linux-pci,
	Neil Armstrong, Rob Herring, Thippeswamy Havalige,
	Manivannan Sadhasivam, Bjorn Helgaas, Sean Anderson
In-Reply-To: <20260203002128.935842-1-sean.anderson@linux.dev>

All lanes undergoing ILL calibration must be initialized. Split off
common phy initialization into a helper so that we can ensure all lanes
are initialized before performing calibration.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

 drivers/phy/xilinx/phy-zynqmp.c | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/drivers/phy/xilinx/phy-zynqmp.c b/drivers/phy/xilinx/phy-zynqmp.c
index 0d3c578d0f3f..152af1702bbd 100644
--- a/drivers/phy/xilinx/phy-zynqmp.c
+++ b/drivers/phy/xilinx/phy-zynqmp.c
@@ -520,6 +520,21 @@ static void xpsgtr_bypass_scrambler_8b10b(struct xpsgtr_phy *gtr_phy)
 	xpsgtr_write_phy(gtr_phy, L0_TX_DIG_61, L0_TM_DISABLE_SCRAMBLE_ENCODER);
 }
 
+static int xpsgtr_common_init(struct xpsgtr_phy *gtr_phy)
+{
+	int ret;
+
+	/* Enable coarse code saturation limiting logic. */
+	xpsgtr_write_phy(gtr_phy, L0_TM_PLL_DIG_37, L0_TM_COARSE_CODE_LIMIT);
+
+	ret = xpsgtr_configure_pll(gtr_phy);
+	if (ret)
+		return ret;
+
+	xpsgtr_lane_set_protocol(gtr_phy);
+	return 0;
+}
+
 /* DP-specific initialization. */
 static void xpsgtr_phy_init_dp(struct xpsgtr_phy *gtr_phy)
 {
@@ -682,19 +697,14 @@ static int xpsgtr_phy_init(struct phy *phy)
 		gtr_dev->tx_term_fix = false;
 	}
 
-	/* Enable coarse code saturation limiting logic. */
-	xpsgtr_write_phy(gtr_phy, L0_TM_PLL_DIG_37, L0_TM_COARSE_CODE_LIMIT);
-
 	/*
 	 * Configure the PLL, the lane protocol, and perform protocol-specific
 	 * initialization.
 	 */
-	ret = xpsgtr_configure_pll(gtr_phy);
+	ret = xpsgtr_common_init(gtr_phy);
 	if (ret)
 		goto out;
 
-	xpsgtr_lane_set_protocol(gtr_phy);
-
 	switch (gtr_phy->protocol) {
 	case ICM_PROTOCOL_DP:
 		xpsgtr_phy_init_dp(gtr_phy);
-- 
2.35.1.1320.gc452695387.dirty


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH 2/8] phy: zynqmp: Refactor bus width configuration into helper
From: Sean Anderson @ 2026-02-03  0:21 UTC (permalink / raw)
  To: Laurent Pinchart, Vinod Koul, linux-phy
  Cc: Krzysztof Wilczyński, Lorenzo Pieralisi, Radhey Shyam Pandey,
	linux-kernel, Michal Simek, linux-arm-kernel, linux-pci,
	Neil Armstrong, Rob Herring, Thippeswamy Havalige,
	Manivannan Sadhasivam, Bjorn Helgaas, Sean Anderson
In-Reply-To: <20260203002128.935842-1-sean.anderson@linux.dev>

Split off the bus width configuration into a helper function for reuse.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

 drivers/phy/xilinx/phy-zynqmp.c | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/drivers/phy/xilinx/phy-zynqmp.c b/drivers/phy/xilinx/phy-zynqmp.c
index fe6b4925d166..0d3c578d0f3f 100644
--- a/drivers/phy/xilinx/phy-zynqmp.c
+++ b/drivers/phy/xilinx/phy-zynqmp.c
@@ -502,6 +502,17 @@ static void xpsgtr_lane_set_protocol(struct xpsgtr_phy *gtr_phy)
 	}
 }
 
+/* Set the bus width */
+static void xpsgtr_phy_init_bus_width(struct xpsgtr_phy *gtr_phy, u32 width)
+{
+	struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
+	u32 mask = PROT_BUS_WIDTH_MASK(gtr_phy->lane);
+	u32 val = width << PROT_BUS_WIDTH_SHIFT(gtr_phy->lane);
+
+	xpsgtr_clr_set(gtr_dev, TX_PROT_BUS_WIDTH, mask, val);
+	xpsgtr_clr_set(gtr_dev, RX_PROT_BUS_WIDTH, mask, val);
+}
+
 /* Bypass (de)scrambler and 8b/10b decoder and encoder. */
 static void xpsgtr_bypass_scrambler_8b10b(struct xpsgtr_phy *gtr_phy)
 {
@@ -535,14 +546,7 @@ static void xpsgtr_phy_init_sata(struct xpsgtr_phy *gtr_phy)
 /* SGMII-specific initialization. */
 static void xpsgtr_phy_init_sgmii(struct xpsgtr_phy *gtr_phy)
 {
-	struct xpsgtr_dev *gtr_dev = gtr_phy->dev;
-	u32 mask = PROT_BUS_WIDTH_MASK(gtr_phy->lane);
-	u32 val = PROT_BUS_WIDTH_10 << PROT_BUS_WIDTH_SHIFT(gtr_phy->lane);
-
-	/* Set SGMII protocol TX and RX bus width to 10 bits. */
-	xpsgtr_clr_set(gtr_dev, TX_PROT_BUS_WIDTH, mask, val);
-	xpsgtr_clr_set(gtr_dev, RX_PROT_BUS_WIDTH, mask, val);
-
+	xpsgtr_phy_init_bus_width(gtr_phy, PROT_BUS_WIDTH_10);
 	xpsgtr_bypass_scrambler_8b10b(gtr_phy);
 }
 
-- 
2.35.1.1320.gc452695387.dirty


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH 1/8] dt-bindings: pci: xilinx-nwl: Add resets
From: Sean Anderson @ 2026-02-03  0:21 UTC (permalink / raw)
  To: Laurent Pinchart, Vinod Koul, linux-phy
  Cc: Krzysztof Wilczyński, Lorenzo Pieralisi, Radhey Shyam Pandey,
	linux-kernel, Michal Simek, linux-arm-kernel, linux-pci,
	Neil Armstrong, Rob Herring, Thippeswamy Havalige,
	Manivannan Sadhasivam, Bjorn Helgaas, Sean Anderson, Conor Dooley,
	Krzysztof Kozlowski, devicetree
In-Reply-To: <20260203002128.935842-1-sean.anderson@linux.dev>

Add resets so we can hold the bridge in reset while we perform phy
calibration.

Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
---

 .../devicetree/bindings/pci/xlnx,nwl-pcie.yaml  | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml b/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml
index 9de3c09efb6e..7efb3dd9955f 100644
--- a/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml
+++ b/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml
@@ -69,6 +69,18 @@ properties:
   power-domains:
     maxItems: 1
 
+  resets:
+    maxItems: 3
+
+  reset-names:
+    items:
+      - description: APB register block reset
+        const: cfg
+      - description: AXI-PCIe bridge reset
+        const: bridge
+      - description: PCIe MAC reset
+        const: ctrl
+
   iommus:
     maxItems: 1
 
@@ -117,6 +129,7 @@ examples:
     #include <dt-bindings/interrupt-controller/irq.h>
     #include <dt-bindings/phy/phy.h>
     #include <dt-bindings/power/xlnx-zynqmp-power.h>
+    #include <dt-bindings/reset/xlnx-zynqmp-resets.h>
     soc {
         #address-cells = <2>;
         #size-cells = <2>;
@@ -146,6 +159,10 @@ examples:
             msi-parent = <&nwl_pcie>;
             phys = <&psgtr 0 PHY_TYPE_PCIE 0 0>;
             power-domains = <&zynqmp_firmware PD_PCIE>;
+            resets = <&zynqmp_reset ZYNQMP_RESET_PCIE_CFG>,
+                     <&zynqmp_reset ZYNQMP_RESET_PCIE_BRIDGE>,
+                     <&zynqmp_reset ZYNQMP_RESET_PCIE_CTRL>;
+            reset-names = "cfg", "bridge", "ctrl";
             iommus = <&smmu 0x4d0>;
             pcie_intc: legacy-interrupt-controller {
                 interrupt-controller;
-- 
2.35.1.1320.gc452695387.dirty


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply related

* [PATCH 0/8] phy: zynqmp: Perform complete initialization, including ILL calibration
From: Sean Anderson @ 2026-02-03  0:21 UTC (permalink / raw)
  To: Laurent Pinchart, Vinod Koul, linux-phy
  Cc: Krzysztof Wilczyński, Lorenzo Pieralisi, Radhey Shyam Pandey,
	linux-kernel, Michal Simek, linux-arm-kernel, linux-pci,
	Neil Armstrong, Rob Herring, Thippeswamy Havalige,
	Manivannan Sadhasivam, Bjorn Helgaas, Sean Anderson, Conor Dooley,
	Krzysztof Kozlowski, devicetree

This series completely initializes the GTRs in Linux, making all
bootloader initialization (as performed by init_serdes() in
psu_init_gpl.c) optional. This gives the following advantages:

- On some boards (mine) the reference clocks may not be configured in
  SPL/FSBL. So ILL calibration will fail (and take a long time to do so)
  unless we defer initialization to U-Boot/Linux where the phy driver
  can request the clocks.
- If PCIe/SATA are not used in U-Boot, ILL calibration can be deferred
  until Linux when it can be done it parallel with other initialization.
- We will have flexibility to switch between different configurations at
  runtime. For example, this could allow supporting both SATA and PCIe M.2
  cards with [1].

I have tested this series with DP, PCIe, SGMII, and SATA. USB3 is broken
on my dev board at the moment (independent of this series; need to
investigate) so I have not tested that. I have an equivalent set of
patches for U-Boot that I will try to post soon.

[1] https://lore.kernel.org/linux-pci/20260107-pci-m2-v5-0-8173d8a72641@oss.qualcomm.com/


Sean Anderson (8):
  dt-bindings: pci: xilinx-nwl: Add resets
  phy: zynqmp: Refactor bus width configuration into helper
  phy: zynqmp: Refactor common phy initialization into a helper
  phy: zynqmp: Calibrate ILL if necessary
  phy: zynqmp: Initialize chicken bits
  PCI: xilinx-nwl: Split phy_init from phy_power_on
  PCI: xilinx-nwl: Reset the core during probe
  arm64: zynqmp: Add PCIe resets

 .../bindings/pci/xlnx,nwl-pcie.yaml           |  17 +
 arch/arm64/boot/dts/xilinx/zynqmp.dtsi        |   4 +
 drivers/pci/controller/pcie-xilinx-nwl.c      | 255 +++++++--
 drivers/phy/xilinx/phy-zynqmp.c               | 487 +++++++++++++++++-
 4 files changed, 713 insertions(+), 50 deletions(-)

-- 
2.35.1.1320.gc452695387.dirty


-- 
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox