* Re: [PATCH V1 0/2] arm64: dts: qcom: Shikra SD Card support
From: Monish Chunara @ 2026-06-15 7:37 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Vinod Koul, Neil Armstrong, Wesley Cheng,
Ulf Hansson, Kernel Team, linux-arm-msm, devicetree, linux-kernel,
linux-phy, linux-mmc, Nitin Rawat, Pradeep Pragallapati,
Komal Bajaj, Konrad Dybcio
In-Reply-To: <hpvnpq2rx2axmtxmk2y6tddeo42ohvd4ae3z37lynzm5nug4vf@sg5ndaqfdlvh>
On Thu, Jun 04, 2026 at 04:22:42PM +0300, Dmitry Baryshkov wrote:
> On Thu, Jun 04, 2026 at 05:50:43PM +0530, Monish Chunara wrote:
> > This series adds SD card support for the Shikra platform.
> >
> > The first patch adds the SDHC2 controller node and the necessary pinctrl
> > configurations to the base Shikra SoC dtsi. The second patch enables
> > this support on the Shikra EVK (CQS, CQM, and IQS variants) by defining
> > the regulator supplies and the card detection GPIO.
> >
> > Testing:
> > - Validated on Shikra EVK variants.
> >
> > This series depends on:
> > - https://lore.kernel.org/all/20260527-shikra-dt-v4-0-b5ca1fa0b392@oss.qualcomm.com/
> > - https://lore.kernel.org/all/20260521-shikra-rproc-v3-0-2fca0bbe1ad7@oss.qualcomm.com/
> > - https://lore.kernel.org/linux-devicetree/20260513-tsens_binding-v1-1-1780c6a6caf2@oss.qualcomm.com/
>
> And how does SDCC depend on TSENS or remote proc?
>
Added the dependencies as per M0/M1 series. Will update exact dependencies for
SD card in the cover letter of next series.
> > - https://lore.kernel.org/all/20260524-shikra_epss_l3-v1-0-b1528a436134@oss.qualcomm.com/
> > - https://lore.kernel.org/all/20260522-shikra-cpufreq-scaling-v4-0-f042a25896c5@oss.qualcomm.com/
> > - https://lore.kernel.org/all/20260530-shikra-dt-m1-v2-0-6bb581035d13@oss.qualcomm.com/
> >
> > Monish Chunara (2):
> > arm64: dts: qcom: Add SD Card support for Shikra SoC
> > arm64: dts: qcom: Enable SD card for Shikra EVK
> >
> > arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts | 18 ++++
> > arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts | 18 ++++
> > arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts | 18 ++++
> > arch/arm64/boot/dts/qcom/shikra.dtsi | 93 +++++++++++++++++++++
> > 4 files changed, 147 insertions(+)
> >
> > --
> > 2.34.1
> >
>
> --
> With best wishes
> Dmitry
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH] phy: ti: twl4030-usb: unwind late probe failures
From: sashiko-bot @ 2026-06-15 7:13 UTC (permalink / raw)
To: Pengpeng Hou; +Cc: linux-phy, olteanv, vkoul, neil.armstrong
In-Reply-To: <20260615070211.59144-1-pengpeng@iscas.ac.cn>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
New issues:
- [High] The newly added `err_runtime` cleanup path uses `pm_runtime_put_noidle()` before `pm_runtime_disable()`, violating PM guidelines and leaving hardware regulators permanently enabled.
Pre-existing issues:
- [High] The delayed work `id_workaround_work` is not cancelled in the probe error path, leading to a potential use-after-free if the work executes after the driver state is freed.
- [High] Re-initializing the PHY notifier head after the PHY has been published to the system drops concurrent registrations and corrupts the notifier list.
- [High] Registering the USB PHY early in probe exposes it to concurrent lookups, leading to a use-after-free if probe subsequently fails.
- [High] The PHY lookup created by `phy_create_lookup()` is never removed during driver unbind, leading to a memory leak and a dangling pointer in the global PHY list.
--
commit 6c9da7a5a1fe8af7dbe4915303ad564e9cf8b693
Author: Pengpeng Hou <pengpeng@iscas.ac.cn>
phy: ti: twl4030-usb: unwind late probe failures
This commit addresses missing unwinding in the late probe failure paths of
the twl4030-usb driver. It remembers if the vbus sysfs file was created and
adds an error path to properly undo PM, sysfs, and PHY registrations.
> --- a/drivers/phy/ti/phy-twl4030-usb.c
> +++ b/drivers/phy/ti/phy-twl4030-usb.c
[ ... ]
> @@ -746,11 +747,15 @@ static int twl4030_usb_probe(struct platform_device *pdev)
> dev_err(&pdev->dev, "ldo init failed\n");
> return err;
> }
> - usb_add_phy_dev(&twl->phy);
> + err = usb_add_phy_dev(&twl->phy);
> + if (err)
> + return err;
[Severity: High]
This is a pre-existing issue, but does registering the USB PHY this early in
probe expose it to concurrent lookups?
If a later probe step fails, we call usb_remove_phy() and free the twl
structure via devres. If a concurrent thread called usb_get_phy() during the
race window, could it lead to a use-after-free?
> platform_set_drvdata(pdev, twl);
> if (device_create_file(&pdev->dev, &dev_attr_vbus))
> dev_warn(&pdev->dev, "could not create sysfs file\n");
> + else
> + twl->vbus_file = true;
>
> ATOMIC_INIT_NOTIFIER_HEAD(&twl->phy.notifier);
[Severity: High]
This is a pre-existing issue, but does re-initializing the notifier head
after usb_add_phy_dev() corrupt the notifier list?
Since usb_add_phy_dev() internally initializes this exact notifier head and
exposes the PHY globally, could this drop concurrent registrations made by
consumer drivers?
[ ... ]
> @@ -773,18 +778,29 @@ static int twl4030_usb_probe(struct platform_device *pdev)
> if (status < 0) {
> dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n",
> twl->irq, status);
> - return status;
> + err = status;
> + goto err_runtime;
> }
>
> if (pdata)
> err = phy_create_lookup(phy, "usb", "musb-hdrc.0");
> if (err)
> - return err;
> + goto err_runtime;
>
> pm_runtime_mark_last_busy(&pdev->dev);
> pm_runtime_put_autosuspend(twl->dev);
>
> return 0;
> +
> +err_runtime:
> + pm_runtime_dont_use_autosuspend(&pdev->dev);
[Severity: High]
This is a pre-existing issue, but is it possible for the scheduled delayed
work twl->id_workaround_work to run after the driver state is freed?
Since devm_request_threaded_irq() can schedule the work before we jump to
err_runtime, shouldn't we call cancel_delayed_work_sync() here to prevent a
potential use-after-free?
> + pm_runtime_put_noidle(twl->dev);
[Severity: High]
Does using pm_runtime_put_noidle() here bypass the suspend cycle?
Since pm_runtime_get_sync() resumed the device and enabled the regulators
earlier, and pm_runtime_disable() prevents future state transitions, it seems
the device will remain in RPM_ACTIVE. Should this use pm_runtime_put_sync()
to ensure the hardware is properly suspended and regulators disabled?
> + pm_runtime_disable(&pdev->dev);
> + if (twl->vbus_file)
> + device_remove_file(twl->dev, &dev_attr_vbus);
> + usb_remove_phy(&twl->phy);
> +
> + return err;
> }
>
> static void twl4030_usb_remove(struct platform_device *pdev)
> {
> struct twl4030_usb *twl = platform_get_drvdata(pdev);
> int val;
[Severity: High]
This is a pre-existing issue, but does this function leak the PHY lookup
created during probe?
twl4030_usb_probe() calls phy_create_lookup(), which allocates a lookup
object. Since there is no corresponding phy_remove_lookup() here, could this
leave a dangling pointer in the global PHY list when devres frees the phy
object?
> usb_remove_phy(&twl->phy);
> pm_runtime_get_sync(twl->dev);
> cancel_delayed_work_sync(&twl->id_workaround_work);
> - device_remove_file(twl->dev, &dev_attr_vbus);
> + if (twl->vbus_file)
> + device_remove_file(twl->dev, &dev_attr_vbus);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260615070211.59144-1-pengpeng@iscas.ac.cn?part=1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH] phy: broadcom: brcm-usb: unwind late probe failures
From: sashiko-bot @ 2026-06-15 7:09 UTC (permalink / raw)
To: Pengpeng Hou; +Cc: linux-phy, vkoul, neil.armstrong, olteanv
In-Reply-To: <20260615070123.51155-1-pengpeng@iscas.ac.cn>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Modifying a static global sysfs attribute array during probe permanently alters the state for subsequent device instances.
- [High] Clock references and clock enable states are permanently leaked during initialization error paths.
--
commit 05dff911114f75fd3f8dc846d904d6dea7ced63e
Author: Pengpeng Hou <pengpeng@iscas.ac.cn>
phy: broadcom: brcm-usb: unwind late probe failures
This commit adds tracking for dynamically created resources like the PM
notifier and sysfs group during probe. It ensures these resources are
properly unwound on late provider-registration failure paths and matched
during device removal.
> diff --git a/drivers/phy/broadcom/phy-brcm-usb.c b/drivers/phy/broadcom/phy-brcm-usb.c
> index 59d756a10d6cb..e606db489698a 100644
> --- a/drivers/phy/broadcom/phy-brcm-usb.c
> +++ b/drivers/phy/broadcom/phy-brcm-usb.c
[ ... ]
> @@ -545,7 +547,11 @@ static int brcm_usb_phy_probe(struct platform_device *pdev)
> return err;
[Severity: High]
This is a pre-existing issue, but do we leak clock references and enable
states during initialization error paths?
This error path comes right after brcm_usb_phy_dvr_init(). Inside
brcm_usb_phy_dvr_init(), clocks are obtained using non-managed APIs like
of_clk_get_by_name() but are never released via clk_put(). If an error
occurs after a clock is prepared and enabled:
brcm_usb_phy_dvr_init() {
...
if (IS_ERR(gphy)) {
dev_err(dev, "failed to create EHCI/OHCI PHY\n");
return PTR_ERR(gphy);
}
...
if (IS_ERR(priv->usb_30_clk)) {
if (PTR_ERR(priv->usb_30_clk) == -EPROBE_DEFER)
return -EPROBE_DEFER;
...
}
The function returns without calling clk_disable_unprepare(). Probe deferral
is particularly problematic as each retry increments the enable count and
clock reference count without bound, permanently leaking resources.
> priv->pm_notifier.notifier_call = brcm_pm_notifier;
> - register_pm_notifier(&priv->pm_notifier);
> + err = register_pm_notifier(&priv->pm_notifier);
> + if (err)
> + dev_warn(dev, "Error registering PM notifier\n");
> + else
> + priv->pm_notifier_registered = true;
>
> mutex_init(&priv->mutex);
>
> @@ -561,6 +567,8 @@ static int brcm_usb_phy_probe(struct platform_device *pdev)
> err = sysfs_create_group(&dev->kobj, &brcm_usb_phy_group);
[Severity: High]
This is a pre-existing issue, but does modifying the static global array
brcm_usb_phy_attrs just before this sysfs_create_group() call permanently
alter the state for subsequent device instances?
Just above this hunk, the code mutates a shared static array:
if (priv->ini.supported_port_modes != USB_CTLR_MODE_DRD)
brcm_usb_phy_attrs[1] = NULL;
Because this array is shared across all driver instances in the module,
modifying it truncates the sysfs attributes for the entire module. Any
subsequently probed devices will be missing the dual_select attribute even
if they do support DRD mode, leading to incorrect functional behavior for
multiple PHY instances.
> if (err)
> dev_warn(dev, "Error creating sysfs attributes\n");
> + else
> + priv->sysfs_group_created = true;
>
> /* Get piarbctl syscon if it exists */
> rmap = syscon_regmap_lookup_by_phandle(dev->of_node,
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260615070123.51155-1-pengpeng@iscas.ac.cn?part=1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH] phy: ti: twl4030-usb: unwind late probe failures
From: Pengpeng Hou @ 2026-06-15 7:02 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Johan Hovold, linux-phy, linux-kernel
Cc: pengpeng
twl4030_usb_probe() publishes the USB PHY and may create the vbus sysfs
file before enabling runtime PM and requesting the IRQ.
If the IRQ request or PHY lookup fails, probe returns directly. That
leaves the USB PHY registered, can leave the vbus sysfs file published,
and leaves the runtime-PM get from probe unbalanced. The return value
from usb_add_phy_dev() is also ignored.
Check usb_add_phy_dev(), remember whether the vbus file was created, and
unwind the runtime-PM, sysfs and USB-PHY state on the late failure paths.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/phy/ti/phy-twl4030-usb.c | 25 +++++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/drivers/phy/ti/phy-twl4030-usb.c b/drivers/phy/ti/phy-twl4030-usb.c
index a26aec3ab29e..6a5968e6cfd8 100644
--- a/drivers/phy/ti/phy-twl4030-usb.c
+++ b/drivers/phy/ti/phy-twl4030-usb.c
@@ -162,6 +162,7 @@ struct twl4030_usb {
enum musb_vbus_id_status linkstat;
atomic_t connected;
bool vbus_supplied;
+ bool vbus_file;
bool musb_mailbox_pending;
unsigned long runtime_suspended:1;
unsigned long needs_resume:1;
@@ -746,11 +747,15 @@ static int twl4030_usb_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "ldo init failed\n");
return err;
}
- usb_add_phy_dev(&twl->phy);
+ err = usb_add_phy_dev(&twl->phy);
+ if (err)
+ return err;
platform_set_drvdata(pdev, twl);
if (device_create_file(&pdev->dev, &dev_attr_vbus))
dev_warn(&pdev->dev, "could not create sysfs file\n");
+ else
+ twl->vbus_file = true;
ATOMIC_INIT_NOTIFIER_HEAD(&twl->phy.notifier);
@@ -773,18 +778,29 @@ static int twl4030_usb_probe(struct platform_device *pdev)
if (status < 0) {
dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n",
twl->irq, status);
- return status;
+ err = status;
+ goto err_runtime;
}
if (pdata)
err = phy_create_lookup(phy, "usb", "musb-hdrc.0");
if (err)
- return err;
+ goto err_runtime;
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_put_autosuspend(twl->dev);
return 0;
+
+err_runtime:
+ pm_runtime_dont_use_autosuspend(&pdev->dev);
+ pm_runtime_put_noidle(twl->dev);
+ pm_runtime_disable(&pdev->dev);
+ if (twl->vbus_file)
+ device_remove_file(twl->dev, &dev_attr_vbus);
+ usb_remove_phy(&twl->phy);
+
+ return err;
}
static void twl4030_usb_remove(struct platform_device *pdev)
@@ -795,7 +811,8 @@ static void twl4030_usb_remove(struct platform_device *pdev)
usb_remove_phy(&twl->phy);
pm_runtime_get_sync(twl->dev);
cancel_delayed_work_sync(&twl->id_workaround_work);
- device_remove_file(twl->dev, &dev_attr_vbus);
+ if (twl->vbus_file)
+ device_remove_file(twl->dev, &dev_attr_vbus);
/* set transceiver mode to power on defaults */
twl4030_usb_set_mode(twl, -1);
--
2.50.1 (Apple Git-155)
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH] phy: broadcom: brcm-usb: unwind late probe failures
From: Pengpeng Hou @ 2026-06-15 7:01 UTC (permalink / raw)
To: Justin Chen, Al Cooper, Broadcom internal kernel review list,
Vinod Koul, Neil Armstrong, linux-kernel, linux-phy
Cc: pengpeng
brcm_usb_phy_probe() registers a PM notifier and may create a sysfs group
before registering the OF PHY provider. If provider registration fails,
probe returns the error with those resources still active.
Track which optional resources were installed and remove them on the late
provider-registration failure path. Use the same state in remove() so
cleanup matches probe progress.
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/phy/broadcom/phy-brcm-usb.c | 29 +++++++++++++++++++++++++----
1 file changed, 25 insertions(+), 4 deletions(-)
diff --git a/drivers/phy/broadcom/phy-brcm-usb.c b/drivers/phy/broadcom/phy-brcm-usb.c
index 59d756a10d6c..e606db489698 100644
--- a/drivers/phy/broadcom/phy-brcm-usb.c
+++ b/drivers/phy/broadcom/phy-brcm-usb.c
@@ -71,6 +71,8 @@ struct brcm_usb_phy_data {
int wake_irq;
struct brcm_usb_phy phys[BRCM_USB_PHY_ID_MAX];
struct notifier_block pm_notifier;
+ bool pm_notifier_registered;
+ bool sysfs_group_created;
bool pm_active;
};
@@ -545,7 +547,11 @@ static int brcm_usb_phy_probe(struct platform_device *pdev)
return err;
priv->pm_notifier.notifier_call = brcm_pm_notifier;
- register_pm_notifier(&priv->pm_notifier);
+ err = register_pm_notifier(&priv->pm_notifier);
+ if (err)
+ dev_warn(dev, "Error registering PM notifier\n");
+ else
+ priv->pm_notifier_registered = true;
mutex_init(&priv->mutex);
@@ -561,6 +567,8 @@ static int brcm_usb_phy_probe(struct platform_device *pdev)
err = sysfs_create_group(&dev->kobj, &brcm_usb_phy_group);
if (err)
dev_warn(dev, "Error creating sysfs attributes\n");
+ else
+ priv->sysfs_group_created = true;
/* Get piarbctl syscon if it exists */
rmap = syscon_regmap_lookup_by_phandle(dev->of_node,
@@ -581,16 +589,29 @@ static int brcm_usb_phy_probe(struct platform_device *pdev)
clk_disable_unprepare(priv->usb_30_clk);
phy_provider = devm_of_phy_provider_register(dev, brcm_usb_phy_xlate);
+ err = PTR_ERR_OR_ZERO(phy_provider);
+ if (err)
+ goto err_remove_debug;
+
+ return 0;
+
+err_remove_debug:
+ if (priv->sysfs_group_created)
+ sysfs_remove_group(&dev->kobj, &brcm_usb_phy_group);
+ if (priv->pm_notifier_registered)
+ unregister_pm_notifier(&priv->pm_notifier);
- return PTR_ERR_OR_ZERO(phy_provider);
+ return err;
}
static void brcm_usb_phy_remove(struct platform_device *pdev)
{
struct brcm_usb_phy_data *priv = dev_get_drvdata(&pdev->dev);
- sysfs_remove_group(&pdev->dev.kobj, &brcm_usb_phy_group);
- unregister_pm_notifier(&priv->pm_notifier);
+ if (priv->sysfs_group_created)
+ sysfs_remove_group(&pdev->dev.kobj, &brcm_usb_phy_group);
+ if (priv->pm_notifier_registered)
+ unregister_pm_notifier(&priv->pm_notifier);
}
#ifdef CONFIG_PM_SLEEP
--
2.50.1 (Apple Git-155)
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* Re: [PATCH 3/3] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support
From: sashiko-bot @ 2026-06-15 5:59 UTC (permalink / raw)
To: Joey Lu
Cc: conor+dt, neil.armstrong, devicetree, linux-phy, robh, olteanv,
vkoul
In-Reply-To: <20260615054911.48821-4-a0987203069@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Missing cleanup of the USB role switch on the probe error path leads to a memory leak and a triggerable use-after-free vulnerability.
- [Medium] Missing bounds check on `args_count` in `ma35_usb_phy_xlate` leads to an uninitialized stack memory read and potential out-of-bounds access.
- [Medium] Unchecked return values of `regmap_read()` lead to the use of uninitialized stack variables.
- [Medium] Failure to explicitly clear `MISCFCR0_UHOVRCURH` when `nuvoton,oc-active-high` is absent allows bootloader state to bleed over.
--
commit c78d99f91b7305f8a53b899cb3cb3fa45c43c783
Author: Joey Lu <a0987203069@gmail.com>
phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support
The existing driver handled only PHY0 in device mode (DWC2 gadget). Extend it
to manage both PHY ports and integrate OTG support, per reviewer suggestion to
reuse the existing driver rather than add a separate one.
> diff --git a/drivers/phy/nuvoton/phy-ma35d1-usb2.c b/drivers/phy/nuvoton/phy-ma35d1-usb2.c
> index 9a459b700ed48..336680161104f 100644
> --- a/drivers/phy/nuvoton/phy-ma35d1-usb2.c
> +++ b/drivers/phy/nuvoton/phy-ma35d1-usb2.c
[ ... ]
> +static int ma35_usb_phy_init(struct phy *phy)
> {
> - struct ma35_usb_phy *p_phy = phy_get_drvdata(phy);
> + struct ma35_phy_port *port = phy_get_drvdata(phy);
> + struct ma35_usb_phy *p = container_of(port - port->idx,
> + struct ma35_usb_phy, port[0]);
> + unsigned int n = port->idx;
> unsigned int val;
> int ret;
>
> - ret = clk_prepare_enable(p_phy->clk);
> - if (ret < 0) {
> - dev_err(p_phy->dev, "Failed to enable PHY clock: %d\n", ret);
> - return ret;
> - }
> + regmap_read(p->sysreg, MA35_SYS_USBPMISCR, &val);
[Severity: Medium]
Could this leave val uninitialized if regmap_read() fails?
If regmap_read() fails (e.g. due to a bus error), val remains uninitialized.
Evaluating it in the next line could lead to skipping the PHY initialization
incorrectly.
> + if (val & USBPMISCR_PHY_SUSPEND(n))
> + return 0;
[ ... ]
> +static enum usb_role ma35_role_sw_get(struct usb_role_switch *sw)
> +{
> + struct ma35_usb_phy *p = usb_role_switch_get_drvdata(sw);
> + u32 val;
> +
> + regmap_read(p->sysreg, MA35_SYS_PWRONOTP, &val);
> +
> + return (val & PWRONOTP_USBP0ID) ? USB_ROLE_HOST : USB_ROLE_DEVICE;
[Severity: Medium]
Does this code return an unpredictable role if regmap_read() fails?
If regmap_read() encounters an error, val will remain uninitialized, which
could lead to nondeterministic driver behavior when evaluated for the returned
role.
> +}
[ ... ]
> +static struct phy *ma35_usb_phy_xlate(struct device *dev,
> + const struct of_phandle_args *args)
> +{
> + struct ma35_usb_phy *p = dev_get_drvdata(dev);
> +
> + if (args->args[0] >= MA35_PHY_NUM)
[Severity: Medium]
Can this code access uninitialized memory if args->args_count is 0?
If the device tree incorrectly specifies #phy-cells = <0>, args_count will
be 0 and the args->args array (allocated on the caller's stack) might be
uninitialized. Could we add a bounds check for args->args_count >= 1 before
accessing args->args[0]?
> + return ERR_PTR(-EINVAL);
> +
> + return p->port[args->args[0]].phy;
> +}
[ ... ]
> static int ma35_usb_phy_probe(struct platform_device *pdev)
> {
[ ... ]
> + if (of_property_read_bool(pdev->dev.of_node, "nuvoton,oc-active-high"))
> + regmap_update_bits(p->sysreg, MA35_SYS_MISCFCR0,
> + MISCFCR0_UHOVRCURH, MISCFCR0_UHOVRCURH);
[Severity: Medium]
Does this code fail to clear MISCFCR0_UHOVRCURH when "nuvoton,oc-active-high"
is absent?
If a bootloader previously set this bit to active-high, Linux will fail to
clear it upon boot when active-low is expected, potentially breaking USB
functionality.
> +
> + for (n = 0; n < MA35_PHY_NUM; n++) {
[ ... ]
> + ret = ma35_role_switch_init(pdev, p);
> + if (ret)
> + return ret;
>
> - provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
> + provider = devm_of_phy_provider_register(&pdev->dev, ma35_usb_phy_xlate);
> if (IS_ERR(provider))
> return dev_err_probe(&pdev->dev, PTR_ERR(provider),
> - "Failed to register PHY provider\n");
> + "failed to register PHY provider\n");
[Severity: High]
Does this error path leak the USB role switch created by
ma35_role_switch_init()?
If devm_of_phy_provider_register() fails, the function returns immediately
without unregistering the role switch.
The driver core will then free the devm-allocated memory (including the
struct ma35_usb_phy *p passed as driver data). Since the role switch remains
registered, any later interaction with it (e.g. from sysfs) will invoke
ma35_role_sw_get():
static enum usb_role ma35_role_sw_get(struct usb_role_switch *sw)
{
struct ma35_usb_phy *p = usb_role_switch_get_drvdata(sw);
u32 val;
regmap_read(p->sysreg, MA35_SYS_PWRONOTP, &val);
This dereferences the freed p pointer, causing a Use-After-Free.
> +
> return 0;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260615054911.48821-1-a0987203069@gmail.com?part=3
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG support
From: sashiko-bot @ 2026-06-15 5:58 UTC (permalink / raw)
To: Joey Lu
Cc: devicetree, vkoul, robh, conor+dt, neil.armstrong, olteanv,
linux-phy
In-Reply-To: <20260615054911.48821-2-a0987203069@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The device tree example fails `dt_binding_check` validation due to a unit address mismatch and violations of the parent reset controller schema.
--
commit abaf23416a1205bdc22b4891ef46a49e088031a4
Author: Joey Lu <a0987203069@gmail.com>
dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG support
This extends the Nuvoton MA35D1 USB2 PHY binding to support two ports (OTG
and host-only). The PHY node becomes a child of the syscon node, and
properties are updated to reflect the new architecture.
> diff --git a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
> --- a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
> +++ b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
> @@ -8,38 +8,72 @@ title: Nuvoton MA35D1 USB2 phy
[ ... ]
> examples:
> - |
> - #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
> + system-management@40460000 {
> + compatible = "nuvoton,ma35d1-reset", "syscon", "simple-mfd";
> + reg = <0x0 0x40460000 0x0 0x200>;
> + #reset-cells = <1>;
> + #address-cells = <1>;
> + #size-cells = <1>;
>
> - usb_phy: usb-phy {
> - compatible = "nuvoton,ma35d1-usb2-phy";
> - clocks = <&clk USBD_GATE>;
> - nuvoton,sys = <&sys>;
> - #phy-cells = <0>;
> + usb-phy@60 {
> + compatible = "nuvoton,ma35d1-usb2-phy";
> + reg = <0x60 0x14>;
> + #phy-cells = <1>;
> + };
> };
[Severity: Low]
Will this example pass dt_binding_check validation?
The schema for the parent node (nuvoton,ma35d1-reset.yaml) enforces
additionalProperties: false and requires exactly two compatible items
("nuvoton,ma35d1-reset" and "syscon"). It does not permit child nodes,
the "simple-mfd" compatible, or the #address-cells and #size-cells
properties.
Additionally, does the 4-cell reg array <0x0 0x40460000 0x0 0x200> mismatch
the unit address @40460000? Without a ranges property, the dt-schema wrapper
defaults to 1 cell for address and size, which parses this as two distinct
ranges and triggers a dtc unit_address_vs_reg warning.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260615054911.48821-1-a0987203069@gmail.com?part=1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH 2/3] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node
From: sashiko-bot @ 2026-06-15 5:57 UTC (permalink / raw)
To: Joey Lu
Cc: linux-phy, conor+dt, olteanv, robh, devicetree, neil.armstrong,
vkoul
In-Reply-To: <20260615054911.48821-3-a0987203069@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The unit addresses for EHCI1 and OHCI1 nodes use uppercase hexadecimal characters.
--
commit 05d96a79d90d16847229b2f5b5cfd71d8eb6ef98
Author: Joey Lu <a0987203069@gmail.com>
arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node
This commit adds device tree nodes for the MA35D1 USB subsystem,
including a dual-port PHY node and nodes for the DWC2, EHCI, and OHCI
controllers. It also enables the corresponding nodes and configures
pinctrl in the board files.
> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> index e51b98f5bdce4..73ff1d78d2848 100644
> --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
[ ... ]
> @@ -379,5 +388,62 @@ uart16: serial@40880000 {
> clocks = <&clk UART16_GATE>;
> status = "disabled";
> };
[ ... ]
> + ehci1: usb@401C0000 {
[Severity: Low]
Should this unit address use lowercase hexadecimal characters?
The Devicetree specification requires unit addresses to be formatted using
lowercase hex. Using uppercase 'C' here (unlike the 'c' in the reg property
below) can trigger W_simple_bus_reg warnings when checking device trees.
> + compatible = "generic-ehci";
> + reg = <0x0 0x401c0000 0x0 0x1000>;
> + interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clk HUSBH1_GATE>;
> + phys = <&usb_phy 1>;
> + phy-names = "usb";
> + companion = <&ohci1>;
> + status = "disabled";
> + };
> +
> + ohci0: usb@40150000 {
> + compatible = "generic-ohci";
> + reg = <0x0 0x40150000 0x0 0x1000>;
> + interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clk HUSBH0_GATE>;
> + phys = <&usb_phy 0>;
> + phy-names = "usb";
> + status = "disabled";
> + };
> +
> + ohci1: usb@401D0000 {
[Severity: Low]
Similarly, could this node be named usb@401d0000 to comply with the
standard Devicetree lowercase hex conventions?
> + compatible = "generic-ohci";
> + reg = <0x0 0x401d0000 0x0 0x1000>;
> + interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clk HUSBH1_GATE>;
> + phys = <&usb_phy 1>;
> + phy-names = "usb";
> + status = "disabled";
> + };
> };
> };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260615054911.48821-1-a0987203069@gmail.com?part=2
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v3 3/3] phy: qcom-qmp-ufs: Add UFS PHY support on Hawi
From: Palash Kambar @ 2026-06-15 5:51 UTC (permalink / raw)
To: Vinod Koul
Cc: neil.armstrong, robh, krzk+dt, conor+dt, mani, alim.akhtar,
bvanassche, andersson, dmitry.baryshkov, abel.vesa, luca.weiss,
linux-arm-msm, linux-phy, devicetree, linux-kernel, linux-scsi,
nitin.rawat
In-Reply-To: <airUb6wT-I-7cOXK@vaman>
On 6/11/2026 8:59 PM, Vinod Koul wrote:
> On 26-05-26, 14:39, palash.kambar@oss.qualcomm.com wrote:
>> From: Palash Kambar <palash.kambar@oss.qualcomm.com>
>>
>> Add the init sequence tables and config for the UFS QMP phy found in
>> the Hawi SoC.
>
> This fails to build for me on phy/next
>
> In file included from drivers/phy/qualcomm/phy-qcom-qmp-ufs.c:24:
> drivers/phy/qualcomm/phy-qcom-qmp-ufs.c:1878:26: error: ‘QSERDES_V8_COM_PLL_IVCO_MODE1’ undeclared here (not in a function); did you mean ‘QSERDES_V6_COM_PLL_IVCO_MODE1’?
> 1878 | QMP_PHY_INIT_CFG(QSERDES_V8_COM_PLL_IVCO_MODE1, 0x1f),
> | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> drivers/phy/qualcomm/phy-qcom-qmp-common.h:22:27: note: in definition of macro ‘QMP_PHY_INIT_CFG’
> 22 | .offset = o, \
> | ^
> drivers/phy/qualcomm/phy-qcom-qmp-ufs.c:1879:26: error: ‘QSERDES_V8_COM_CMN_IETRIM’ undeclared here (not in a function); did you mean ‘QSERDES_V6_COM_CMN_IETRIM’?
> 1879 | QMP_PHY_INIT_CFG(QSERDES_V8_COM_CMN_IETRIM, 0x07),
> | ^~~~~~~~~~~~~~~~~~~~~~~~~
> drivers/phy/qualcomm/phy-qcom-qmp-common.h:22:27: note: in definition of macro ‘QMP_PHY_INIT_CFG’
> 22 | .offset = o, \
>
> And so on. Looks like QSERDES_V8_COM_PLL_IVCO_MODE1 etc are not define.
> Please rebase test and send again
>
Hi Vinod,
Sure, will rebase and check. Thanks.
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH 3/3] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support
From: Joey Lu @ 2026-06-15 5:49 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
Joey Lu
In-Reply-To: <20260615054911.48821-1-a0987203069@gmail.com>
The existing driver handled only PHY0 in device mode (DWC2 gadget).
Extend it to manage both PHY ports and integrate OTG support, per
reviewer suggestion to reuse the existing driver rather than add a
separate one.
The MA35D1 SoC has two USB PHY ports:
- PHY0 (USB0): OTG port shared between the DWC2 gadget controller
and EHCI0/OHCI0 host controllers. A hardware mux in the SoC
automatically routes the USB0 signals to the appropriate
controller based on the USB ID pin state.
- PHY1 (USB1): dedicated host-only port for EHCI1/OHCI1.
Key changes:
Dual-port support
A loop in probe() creates two struct phy objects, one per port,
each with its own phy_set_drvdata() context. A custom xlate
function selects the correct phy by the single #phy-cells argument.
Unified .init callback
A single ma35_usb_phy_init() handles both ports using parametric
register macros (USBPMISCR_PHY_*(n)). If the SUSPEND bit is
already set the init is skipped entirely, preventing the shared
PHY0 from being reset while a live link is active. On cold boot,
PHY0 polls for either host-mode clocks (HSTCKSTB + CK12MSTB) or
device-mode clock (DEVCKSTB) since the hardware selects the role
automatically; PHY1 polls for host-mode clocks only.
Clock management removed
.power_on/.power_off and all struct clk handling are removed.
Each USB controller (DWC2, EHCI, OHCI) already gates its own
clock directly through its DTS clocks binding. Having the PHY
driver redundantly enable the same gates added unnecessary
coupling without benefit.
OTG role switch for PHY0
A read-only USB role switch is registered, reporting the current
OTG role by reading the USB ID pin state from PWRONOTP[16].
.set returns -EOPNOTSUPP since the hardware mux is fully
automatic. allow_userspace_control is kept true to preserve the
sysfs attribute for observation; writes are rejected by .set.
syscon regmap via parent
The driver obtains the regmap by calling
syscon_node_to_regmap(pdev->dev.parent->of_node), removing the
need for the nuvoton,sys phandle.
Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
drivers/phy/nuvoton/phy-ma35d1-usb2.c | 263 ++++++++++++++++++--------
1 file changed, 189 insertions(+), 74 deletions(-)
diff --git a/drivers/phy/nuvoton/phy-ma35d1-usb2.c b/drivers/phy/nuvoton/phy-ma35d1-usb2.c
index 9a459b700ed4..336680161104 100644
--- a/drivers/phy/nuvoton/phy-ma35d1-usb2.c
+++ b/drivers/phy/nuvoton/phy-ma35d1-usb2.c
@@ -1,11 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Copyright (C) 2024 Nuvoton Technology Corp.
+ * Nuvoton MA35D1 USB 2.0 PHY driver
+ *
+ * Supports PHY0 (USB0 OTG port, shared between DWC2 gadget and EHCI0/OHCI0)
+ * and PHY1 (USB1 host-only port, used by EHCI1/OHCI1). The hardware mux on
+ * PHY0 switches automatically via the USB ID pin.
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
*/
#include <linux/bitfield.h>
-#include <linux/clk.h>
#include <linux/delay.h>
-#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
@@ -13,131 +17,242 @@
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <linux/usb/role.h>
-/* USB PHY Miscellaneous Control Register */
-#define MA35_SYS_REG_USBPMISCR 0x60
-#define PHY0POR BIT(0) /* PHY Power-On Reset Control Bit */
-#define PHY0SUSPEND BIT(1) /* PHY Suspend; 0: suspend, 1: operaion */
-#define PHY0COMN BIT(2) /* PHY Common Block Power-Down Control */
-#define PHY0DEVCKSTB BIT(10) /* PHY 60 MHz UTMI clock stable bit */
+#define MA35_SYS_PWRONOTP 0x04
+#define PWRONOTP_USBP0ID BIT(16) /* USB0 ID pin state */
+
+#define MA35_SYS_USBPMISCR 0x60
+#define USBPMISCR_PHY_POR(n) BIT(0 + (n) * 16)
+#define USBPMISCR_PHY_SUSPEND(n) BIT(1 + (n) * 16)
+#define USBPMISCR_PHY_COMN(n) BIT(2 + (n) * 16)
+#define USBPMISCR_PHY_HSTCKSTB(n) BIT(8 + (n) * 16)
+#define USBPMISCR_PHY_CK12MSTB(n) BIT(9 + (n) * 16)
+#define USBPMISCR_PHY_DEVCKSTB(n) BIT(10 + (n) * 16)
+/* Mask for control bits (POR, SUSPEND, COMN) of one PHY */
+#define USBPMISCR_PHY_CTL_MASK(n) (0x7u << ((n) * 16))
+/* Host-mode ready: SUSPEND set */
+#define USBPMISCR_PHY_HOST_READY(n) (USBPMISCR_PHY_SUSPEND(n) | \
+ USBPMISCR_PHY_HSTCKSTB(n) | \
+ USBPMISCR_PHY_CK12MSTB(n))
+/* Device-mode ready: SUSPEND set */
+#define USBPMISCR_PHY_DEV_READY(n) (USBPMISCR_PHY_SUSPEND(n) | \
+ USBPMISCR_PHY_DEVCKSTB(n))
+/* RCALCODE: 4-bit resistor trim at bits [15:12] (PHY0) or [31:28] (PHY1) */
+#define USBPMISCR_RCAL_SHIFT(n) (12 + (n) * 16)
+#define USBPMISCR_RCAL_MASK(n) GENMASK(USBPMISCR_RCAL_SHIFT(n) + 3, \
+ USBPMISCR_RCAL_SHIFT(n))
+
+#define MA35_SYS_MISCFCR0 0x70
+/* Bit 12: USB host over-current detect polarity (shared, both ports) */
+#define MISCFCR0_UHOVRCURH BIT(12)
+
+#define MA35_PHY_NUM 2
+
+struct ma35_phy_port {
+ struct phy *phy;
+ unsigned int idx;
+};
struct ma35_usb_phy {
- struct clk *clk;
struct device *dev;
struct regmap *sysreg;
+ struct ma35_phy_port port[MA35_PHY_NUM];
+ struct usb_role_switch *role_sw;
};
-static int ma35_usb_phy_power_on(struct phy *phy)
+static int ma35_usb_phy_init(struct phy *phy)
{
- struct ma35_usb_phy *p_phy = phy_get_drvdata(phy);
+ struct ma35_phy_port *port = phy_get_drvdata(phy);
+ struct ma35_usb_phy *p = container_of(port - port->idx,
+ struct ma35_usb_phy, port[0]);
+ unsigned int n = port->idx;
unsigned int val;
int ret;
- ret = clk_prepare_enable(p_phy->clk);
- if (ret < 0) {
- dev_err(p_phy->dev, "Failed to enable PHY clock: %d\n", ret);
- return ret;
- }
+ regmap_read(p->sysreg, MA35_SYS_USBPMISCR, &val);
- regmap_read(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, &val);
- if (val & PHY0SUSPEND) {
- /*
- * USB PHY0 is in operation mode already
- * make sure USB PHY 60 MHz UTMI Interface Clock ready
- */
- ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val,
- val & PHY0DEVCKSTB, 10, 1000);
- if (ret == 0)
- return 0;
- }
+ if (val & USBPMISCR_PHY_SUSPEND(n))
+ return 0;
- /*
- * reset USB PHY0.
- * wait until USB PHY0 60 MHz UTMI Interface Clock ready
- */
- regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, (PHY0POR | PHY0SUSPEND));
+ regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR,
+ USBPMISCR_PHY_CTL_MASK(n),
+ USBPMISCR_PHY_POR(n) | USBPMISCR_PHY_SUSPEND(n));
udelay(20);
- /* make USB PHY0 enter operation mode */
- regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, PHY0SUSPEND);
+ regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR,
+ USBPMISCR_PHY_CTL_MASK(n),
+ USBPMISCR_PHY_SUSPEND(n));
- /* make sure USB PHY 60 MHz UTMI Interface Clock ready */
- ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val,
- val & PHY0DEVCKSTB, 10, 1000);
- if (ret == -ETIMEDOUT) {
- dev_err(p_phy->dev, "Check PHY clock, Timeout: %d\n", ret);
- clk_disable_unprepare(p_phy->clk);
+ if (n == 0) {
+ ret = regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR,
+ val,
+ ((val & USBPMISCR_PHY_HOST_READY(0)) ==
+ USBPMISCR_PHY_HOST_READY(0)) ||
+ ((val & USBPMISCR_PHY_DEV_READY(0)) ==
+ USBPMISCR_PHY_DEV_READY(0)),
+ 10, 1000);
+ } else {
+ ret = regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR,
+ val,
+ (val & USBPMISCR_PHY_HOST_READY(n)) ==
+ USBPMISCR_PHY_HOST_READY(n),
+ 10, 1000);
+ }
+
+ if (ret) {
+ dev_err(p->dev, "USB PHY%u clock not stable (USBPMISCR=0x%08x)\n",
+ n, val);
return ret;
}
return 0;
}
-static int ma35_usb_phy_power_off(struct phy *phy)
+static const struct phy_ops ma35_usb_phy_ops = {
+ .init = ma35_usb_phy_init,
+ .owner = THIS_MODULE,
+};
+
+static int ma35_role_sw_set(struct usb_role_switch *sw, enum usb_role role)
+{
+ return -EOPNOTSUPP;
+}
+
+static enum usb_role ma35_role_sw_get(struct usb_role_switch *sw)
+{
+ struct ma35_usb_phy *p = usb_role_switch_get_drvdata(sw);
+ u32 val;
+
+ regmap_read(p->sysreg, MA35_SYS_PWRONOTP, &val);
+
+ return (val & PWRONOTP_USBP0ID) ? USB_ROLE_HOST : USB_ROLE_DEVICE;
+}
+
+static int ma35_role_switch_init(struct platform_device *pdev,
+ struct ma35_usb_phy *p)
{
- struct ma35_usb_phy *p_phy = phy_get_drvdata(phy);
+ struct usb_role_switch_desc sw_desc = {0};
+
+ sw_desc.set = ma35_role_sw_set;
+ sw_desc.get = ma35_role_sw_get;
+ sw_desc.allow_userspace_control = true;
+ sw_desc.driver_data = p;
+ sw_desc.fwnode = dev_fwnode(&pdev->dev);
+
+ p->role_sw = usb_role_switch_register(&pdev->dev, &sw_desc);
+ if (IS_ERR(p->role_sw))
+ return dev_err_probe(&pdev->dev, PTR_ERR(p->role_sw),
+ "failed to register role switch\n");
- clk_disable_unprepare(p_phy->clk);
return 0;
}
-static const struct phy_ops ma35_usb_phy_ops = {
- .power_on = ma35_usb_phy_power_on,
- .power_off = ma35_usb_phy_power_off,
- .owner = THIS_MODULE,
-};
+static void ma35_role_switch_exit(struct ma35_usb_phy *p)
+{
+ if (p->role_sw) {
+ usb_role_switch_unregister(p->role_sw);
+ p->role_sw = NULL;
+ }
+}
+
+static struct phy *ma35_usb_phy_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct ma35_usb_phy *p = dev_get_drvdata(dev);
+
+ if (args->args[0] >= MA35_PHY_NUM)
+ return ERR_PTR(-EINVAL);
+
+ return p->port[args->args[0]].phy;
+}
static int ma35_usb_phy_probe(struct platform_device *pdev)
{
struct phy_provider *provider;
- struct ma35_usb_phy *p_phy;
- struct phy *phy;
+ struct ma35_usb_phy *p;
+ int n, ret;
+ u32 code;
- p_phy = devm_kzalloc(&pdev->dev, sizeof(*p_phy), GFP_KERNEL);
- if (!p_phy)
+ p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
return -ENOMEM;
- p_phy->dev = &pdev->dev;
- platform_set_drvdata(pdev, p_phy);
+ p->dev = &pdev->dev;
+ platform_set_drvdata(pdev, p);
+
+ p->sysreg = syscon_node_to_regmap(pdev->dev.parent->of_node);
+ if (IS_ERR(p->sysreg))
+ return dev_err_probe(&pdev->dev, PTR_ERR(p->sysreg),
+ "failed to get parent SYS regmap\n");
- p_phy->sysreg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "nuvoton,sys");
- if (IS_ERR(p_phy->sysreg))
- return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->sysreg),
- "Failed to get SYS registers\n");
+ for (n = 0; n < MA35_PHY_NUM; n++) {
+ if (of_property_read_u32_index(pdev->dev.of_node,
+ "nuvoton,rcalcode", n, &code))
+ continue;
- p_phy->clk = of_clk_get(pdev->dev.of_node, 0);
- if (IS_ERR(p_phy->clk))
- return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->clk),
- "failed to find usb_phy clock\n");
+ if (code > 15)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "rcalcode[%d] %u out of range (0-15)\n",
+ n, code);
- phy = devm_phy_create(&pdev->dev, NULL, &ma35_usb_phy_ops);
- if (IS_ERR(phy))
- return dev_err_probe(&pdev->dev, PTR_ERR(phy), "Failed to create PHY\n");
+ regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR,
+ USBPMISCR_RCAL_MASK(n),
+ code << USBPMISCR_RCAL_SHIFT(n));
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "nuvoton,oc-active-high"))
+ regmap_update_bits(p->sysreg, MA35_SYS_MISCFCR0,
+ MISCFCR0_UHOVRCURH, MISCFCR0_UHOVRCURH);
+
+ for (n = 0; n < MA35_PHY_NUM; n++) {
+ p->port[n].idx = n;
+
+ p->port[n].phy = devm_phy_create(&pdev->dev, pdev->dev.of_node,
+ &ma35_usb_phy_ops);
+ if (IS_ERR(p->port[n].phy))
+ return dev_err_probe(&pdev->dev, PTR_ERR(p->port[n].phy),
+ "failed to create PHY%d\n", n);
+
+ phy_set_drvdata(p->port[n].phy, &p->port[n]);
+ }
- phy_set_drvdata(phy, p_phy);
+ ret = ma35_role_switch_init(pdev, p);
+ if (ret)
+ return ret;
- provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
+ provider = devm_of_phy_provider_register(&pdev->dev, ma35_usb_phy_xlate);
if (IS_ERR(provider))
return dev_err_probe(&pdev->dev, PTR_ERR(provider),
- "Failed to register PHY provider\n");
+ "failed to register PHY provider\n");
+
return 0;
}
+static void ma35_usb_phy_remove(struct platform_device *pdev)
+{
+ struct ma35_usb_phy *p = platform_get_drvdata(pdev);
+
+ ma35_role_switch_exit(p);
+}
+
static const struct of_device_id ma35_usb_phy_of_match[] = {
- { .compatible = "nuvoton,ma35d1-usb2-phy", },
- { },
+ { .compatible = "nuvoton,ma35d1-usb2-phy" },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ma35_usb_phy_of_match);
static struct platform_driver ma35_usb_phy_driver = {
.probe = ma35_usb_phy_probe,
- .driver = {
- .name = "ma35d1-usb2-phy",
- .of_match_table = ma35_usb_phy_of_match,
+ .remove = ma35_usb_phy_remove,
+ .driver = {
+ .name = "ma35d1-usb2-phy",
+ .of_match_table = ma35_usb_phy_of_match,
},
};
module_platform_driver(ma35_usb_phy_driver);
MODULE_DESCRIPTION("Nuvoton ma35d1 USB2.0 PHY driver");
MODULE_AUTHOR("Hui-Ping Chen <hpchen0nvt@gmail.com>");
+MODULE_AUTHOR("Joey Lu <a0987203069@gmail.com>");
MODULE_LICENSE("GPL");
--
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 2/3] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node
From: Joey Lu @ 2026-06-15 5:49 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
Joey Lu
In-Reply-To: <20260615054911.48821-1-a0987203069@gmail.com>
Add device tree nodes for the MA35D1 USB subsystem:
- sys node gains simple-mfd + address/size-cells so it can contain
the usb-phy@60 child.
- usb-phy@60 is added as a child of sys, using the combined
nuvoton,ma35d1-usb2-phy driver with #phy-cells = <1>. No clock
properties: clock gating is handled by each controller node.
- DWC2 gadget (usb@40200000), EHCI0/1, and OHCI0/1 nodes are
added. Each controller names its clock gate directly and
references the PHY by index (0 for the OTG port, 1 for the
dedicated host port).
- Board files (ma35d1-som-256m.dts, ma35d1-iot-512m.dts) enable the
PHY, dwc2, ehci0/1, and ohci0/1 nodes and add pinctrl for the
HSUSB signals (VBUSVLD, PWREN, OVC).
Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
.../boot/dts/nuvoton/ma35d1-iot-512m.dts | 36 ++++++++++
.../boot/dts/nuvoton/ma35d1-som-256m.dts | 36 ++++++++++
arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 68 ++++++++++++++++++-
3 files changed, 139 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
index 9482bec1aa57..32fea36da7f4 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
@@ -95,6 +95,16 @@ pinctrl_uart14: uart14-pins {
power-source = <1>;
};
};
+
+ hsusb {
+ pinctrl_hsusb: hsusb-pins {
+ nuvoton,pins = <5 15 1>, /* VBUSVLD */
+ <11 12 9>, /* PWREN */
+ <11 13 9>; /* OVC */
+ bias-disable;
+ power-source = <1>;
+ };
+ };
};
&uart0 {
@@ -126,3 +136,29 @@ &uart14 {
pinctrl-0 = <&pinctrl_uart14>;
status = "okay";
};
+
+&usb_phy {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_hsusb>;
+ status = "okay";
+};
+
+&usb {
+ status = "okay";
+};
+
+&ehci0 {
+ status = "okay";
+};
+
+&ehci1 {
+ status = "okay";
+};
+
+&ohci0 {
+ status = "okay";
+};
+
+&ohci1 {
+ status = "okay";
+};
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
index f6f20a17e501..85d1c5db8bd9 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
@@ -98,6 +98,16 @@ pinctrl_uart16: uart16-pins {
power-source = <1>;
};
};
+
+ hsusb {
+ pinctrl_hsusb: hsusb-pins {
+ nuvoton,pins = <5 15 1>, /* VBUSVLD */
+ <11 12 9>, /* PWREN */
+ <11 13 9>; /* OVC */
+ bias-disable;
+ power-source = <1>;
+ };
+ };
};
&uart0 {
@@ -129,3 +139,29 @@ &uart16 {
pinctrl-0 = <&pinctrl_uart16>;
status = "okay";
};
+
+&usb_phy {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_hsusb>;
+ status = "okay";
+};
+
+&usb {
+ status = "okay";
+};
+
+&ehci0 {
+ status = "okay";
+};
+
+&ehci1 {
+ status = "okay";
+};
+
+&ohci0 {
+ status = "okay";
+};
+
+&ohci1 {
+ status = "okay";
+};
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
index e51b98f5bdce..73ff1d78d284 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
@@ -83,9 +83,18 @@ soc {
ranges;
sys: system-management@40460000 {
- compatible = "nuvoton,ma35d1-reset", "syscon";
+ compatible = "nuvoton,ma35d1-reset", "syscon", "simple-mfd";
reg = <0x0 0x40460000 0x0 0x200>;
#reset-cells = <1>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ usb_phy: usb-phy@60 {
+ compatible = "nuvoton,ma35d1-usb2-phy";
+ reg = <0x60 0x14>;
+ #phy-cells = <1>;
+ status = "disabled";
+ };
};
clk: clock-controller@40460200 {
@@ -379,5 +388,62 @@ uart16: serial@40880000 {
clocks = <&clk UART16_GATE>;
status = "disabled";
};
+
+ usb: usb@40200000 {
+ compatible = "snps,dwc2";
+ reg = <0x0 0x40200000 0x0 0x1000>;
+ interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk USBD_GATE>;
+ clock-names = "otg";
+ phys = <&usb_phy 0>;
+ phy-names = "usb2-phy";
+ dr_mode = "peripheral";
+ g-np-tx-fifo-size = <16>;
+ g-rx-fifo-size = <0x100>;
+ g-tx-fifo-size = <256 256 64 64 64 32 32 32>;
+ status = "disabled";
+ };
+
+ ehci0: usb@40140000 {
+ compatible = "generic-ehci";
+ reg = <0x0 0x40140000 0x0 0x1000>;
+ interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk HUSBH0_GATE>;
+ phys = <&usb_phy 0>;
+ phy-names = "usb";
+ companion = <&ohci0>;
+ status = "disabled";
+ };
+
+ ehci1: usb@401C0000 {
+ compatible = "generic-ehci";
+ reg = <0x0 0x401c0000 0x0 0x1000>;
+ interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk HUSBH1_GATE>;
+ phys = <&usb_phy 1>;
+ phy-names = "usb";
+ companion = <&ohci1>;
+ status = "disabled";
+ };
+
+ ohci0: usb@40150000 {
+ compatible = "generic-ohci";
+ reg = <0x0 0x40150000 0x0 0x1000>;
+ interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk HUSBH0_GATE>;
+ phys = <&usb_phy 0>;
+ phy-names = "usb";
+ status = "disabled";
+ };
+
+ ohci1: usb@401D0000 {
+ compatible = "generic-ohci";
+ reg = <0x0 0x401d0000 0x0 0x1000>;
+ interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk HUSBH1_GATE>;
+ phys = <&usb_phy 1>;
+ phy-names = "usb";
+ status = "disabled";
+ };
};
};
--
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 1/3] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG support
From: Joey Lu @ 2026-06-15 5:49 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
Joey Lu
In-Reply-To: <20260615054911.48821-1-a0987203069@gmail.com>
The MA35D1 has two USB PHY ports managed by the same hardware block:
- PHY0 (index 0): OTG port shared between the DWC2 gadget controller
and EHCI0/OHCI0 host controllers. A hardware mux follows the USB
ID pin automatically.
- PHY1 (index 1): dedicated host-only port for EHCI1/OHCI1.
Extend the existing binding to cover both ports:
- The PHY node is now a child of the system-management syscon node
with a reg property. The nuvoton,sys phandle and clocks
properties are removed; the driver derives the regmap from its
parent, and clock gating is owned by each individual USB controller.
- #phy-cells changes from 0 to 1: the cell selects the PHY port.
- Two optional board-tuning properties are added: nuvoton,rcalcode
for per-port resistor trim and nuvoton,oc-active-high for
over-current polarity.
Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
.../bindings/phy/nuvoton,ma35d1-usb2-phy.yaml | 62 ++++++++++++++-----
1 file changed, 48 insertions(+), 14 deletions(-)
diff --git a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
index fff858c909a0..dde045aff44e 100644
--- a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
+++ b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
@@ -8,38 +8,72 @@ title: Nuvoton MA35D1 USB2 phy
maintainers:
- Hui-Ping Chen <hpchen0nvt@gmail.com>
+ - Joey Lu <yclu4@nuvoton.com>
+
+description:
+ USB 2.0 PHY for the Nuvoton MA35D1 SoC. The PHY node is a child of the
+ system-management syscon node and covers both PHY ports.
+
+ PHY0 (index 0) is the OTG port whose signals are routed to either the DWC2
+ gadget controller or the EHCI0/OHCI0 host controller by a hardware mux that
+ follows the USB ID pin automatically.
+
+ PHY1 (index 1) is a dedicated host-only port used by EHCI1/OHCI1.
properties:
compatible:
enum:
- nuvoton,ma35d1-usb2-phy
+ reg:
+ maxItems: 1
+
"#phy-cells":
- const: 0
+ const: 1
+ description:
+ The single cell selects the PHY port. 0 selects the OTG port (USB0,
+ shared with DWC2 gadget controller) and 1 selects the host-only port
+ (USB1).
- clocks:
- maxItems: 1
+ nuvoton,rcalcode:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 1
+ maxItems: 2
+ items:
+ minimum: 0
+ maximum: 15
+ description:
+ Resistor calibration trim codes for PHY0 and, optionally, PHY1.
+ Each value is written to the RCALCODE field in USBPMISCR for the
+ corresponding PHY. The 4-bit value adjusts the PHY's internal
+ termination resistance. When absent the hardware reset default is used.
- nuvoton,sys:
- $ref: /schemas/types.yaml#/definitions/phandle
+ nuvoton,oc-active-high:
+ type: boolean
description:
- phandle to syscon for checking the PHY clock status.
+ When present, the over-current detect input from the VBUS power switch
+ is treated as active-high. The default (property absent) is active-low.
+ This setting is shared by both USB host ports.
required:
- compatible
+ - reg
- "#phy-cells"
- - clocks
- - nuvoton,sys
additionalProperties: false
examples:
- |
- #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+ system-management@40460000 {
+ compatible = "nuvoton,ma35d1-reset", "syscon", "simple-mfd";
+ reg = <0x0 0x40460000 0x0 0x200>;
+ #reset-cells = <1>;
+ #address-cells = <1>;
+ #size-cells = <1>;
- usb_phy: usb-phy {
- compatible = "nuvoton,ma35d1-usb2-phy";
- clocks = <&clk USBD_GATE>;
- nuvoton,sys = <&sys>;
- #phy-cells = <0>;
+ usb-phy@60 {
+ compatible = "nuvoton,ma35d1-usb2-phy";
+ reg = <0x60 0x14>;
+ #phy-cells = <1>;
+ };
};
--
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 0/3] phy: nuvoton: extend MA35D1 USB2 PHY driver for dual-port OTG support
From: Joey Lu @ 2026-06-15 5:49 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
Joey Lu
The MA35D1 SoC has two USB PHY ports managed by a shared hardware block:
- PHY0 (USB0): OTG port shared between the DWC2 gadget controller and
the EHCI0/OHCI0 host controllers. A hardware mux automatically routes
USB0 signals to the correct controller based on the USB ID pin.
- PHY1 (USB1): dedicated host-only port for EHCI1/OHCI1.
A previous series [1] added a separate phy-ma35d1-otg.c driver for this.
Following reviewer suggestion to reuse the existing phy-ma35d1-usb2.c
driver rather than introduce a new one, that series has been dropped and
this series instead extends the existing driver.
Changes in this series:
Patch 1 updates the nuvoton,ma35d1-usb2-phy binding: the PHY node
becomes a child of the syscon node (reg = <0x60 0x14>), nuvoton,sys
phandle and clocks are removed, and #phy-cells changes from 0 to 1
for per-port selection. Optional nuvoton,rcalcode and
nuvoton,oc-active-high properties are added.
Patch 2 updates the MA35D1 DTS: sys gains simple-mfd, usb-phy@60 is
added as a syscon child, and DWC2/EHCI0/EHCI1/OHCI0/OHCI1 nodes are
added. Board files enable the nodes and add HSUSB pinctrl.
Patch 3 extends phy-ma35d1-usb2.c: a loop creates two struct phy
objects; a unified .init handles both ports with parametric register
macros; clock management is removed (each controller gates its own
clock); a read-only USB role switch is registered for PHY0 reporting
the USB ID pin via PWRONOTP[16].
Link: [1] https://lore.kernel.org/linux-phy/20260604101220.1092822-1-a0987203069@gmail.com/T/#t
Joey Lu (3):
dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG
support
arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY
node
phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support
.../bindings/phy/nuvoton,ma35d1-usb2-phy.yaml | 67 +++--
.../boot/dts/nuvoton/ma35d1-iot-512m.dts | 36 +++
.../boot/dts/nuvoton/ma35d1-som-256m.dts | 36 +++
arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 68 ++++-
drivers/phy/nuvoton/phy-ma35d1-usb2.c | 263 +++++++++++++-----
5 files changed, 378 insertions(+), 92 deletions(-)
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
--
2.43.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH 2/4] phy: qcom-qusb2: Fix SM6115 init sequence
From: Iskren Chernev @ 2026-06-14 12:29 UTC (permalink / raw)
To: Konrad Dybcio, Vinod Koul, Neil Armstrong, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Wesley Cheng,
Greg Kroah-Hartman, Bjorn Andersson
Cc: linux-arm-msm, linux-phy, devicetree, linux-kernel, Konrad Dybcio
In-Reply-To: <20260610-topic-8996_61x5_qusb2phy-v1-2-d7135980e78f@oss.qualcomm.com>
On 6/10/26 3:04 PM, Konrad Dybcio wrote:
> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> I don't know where the existing one came from, but it's apparently
> wrong, according to both docs and a downstream DT [1]. Fix it up.
They came from DTB extracted from a running billie2 (OnePlus Nord N100):
[1]
https://mainlining.dev/wp-content/uploads/2021/02/03_dtbdump_Qualcomm_Technologies_Inc._Bengal_SoC.dts
The phone was bough early after launch, so it could have been
wrong/updated later.
> The updated values also happen to match SM6125, which will allow us
> to fix that platform too.
>
> [1] https://android.googlesource.com/kernel/msm-extra/devicetree/+/refs/heads/android-msm-bramble-4.19-android11-qpr1/qcom/bengal-usb.dtsi#145
> Fixes: 7756f1d6369e ("phy: qcom-qusb2: Add configuration for SM4250 and SM6115")
> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> drivers/phy/qualcomm/phy-qcom-qusb2.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c
> index eb93015be841..c304ccd9f31f 100644
> --- a/drivers/phy/qualcomm/phy-qcom-qusb2.c
> +++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c
> @@ -233,9 +233,9 @@ static const struct qusb2_phy_init_tbl msm8998_init_tbl[] = {
>
> static const struct qusb2_phy_init_tbl sm6115_init_tbl[] = {
> QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE1, 0xf8),
> - QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE2, 0x53),
> + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE2, 0xb3),
> QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE3, 0x81),
> - QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE4, 0x17),
> + QUSB2_PHY_INIT_CFG_L(QUSB2PHY_PORT_TUNE4, 0xc0),
>
> QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_TUNE, 0x30),
> QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL1, 0x79),
>
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH 1/4] dt-bindings: phy: qcom,qusb2: Straighten out SM6125 and MSM8996
From: Krzysztof Kozlowski @ 2026-06-13 18:36 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Wesley Cheng, Iskren Chernev, Greg Kroah-Hartman,
Bjorn Andersson, linux-arm-msm, linux-phy, devicetree,
linux-kernel, Konrad Dybcio
In-Reply-To: <20260610-topic-8996_61x5_qusb2phy-v1-1-d7135980e78f@oss.qualcomm.com>
On Wed, Jun 10, 2026 at 02:04:14PM +0200, Konrad Dybcio wrote:
> From: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>
> SM6125 DT currently uses just the MSM8996 compatible (without a primary
> SM6125-specific one). This is not only wrong for the reasons of
> violating guidelines, but also happens to not be valid.
>
> The MSM8996 PHY is quite similar, although it requies a different init
> sequence (for arch reasons). MSM8996 also needs different power
> plumbing, as the VDD supply is fed through VDD_MX (which we define as
> a power domain rather than a regulator), unlike on SM6125.
>
> The init sequence seems to have been "good enough", but now that the
> bindings clearly diverge, add a new compatible for SM6125 with a SM6115
> fallback (as they seem to be an exact match from the SW interface POV).
>
> Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> ---
> .../devicetree/bindings/phy/qcom,qusb2-phy.yaml | 31 ++++++++++++++++++++--
> 1 file changed, 29 insertions(+), 2 deletions(-)
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH 02/11] dt-bindings: phy: renesas,usb2-phy: Document RZ/G3L PHY bindings
From: Krzysztof Kozlowski @ 2026-06-13 10:40 UTC (permalink / raw)
To: Biju
Cc: Vinod Koul, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Geert Uytterhoeven, Magnus Damm, Biju Das, Neil Armstrong,
Yoshihiro Shimoda, linux-phy, devicetree, linux-kernel,
linux-renesas-soc, Prabhakar Mahadev Lad
In-Reply-To: <20260612143048.317907-3-biju.das.jz@bp.renesas.com>
On Fri, Jun 12, 2026 at 03:30:30PM +0100, Biju wrote:
> From: Biju Das <biju.das.jz@bp.renesas.com>
>
> Add device tree binding support for the RZ/G3L (r9a08g046) USB2 PHY.
> The RZ/G3L USB PHY is almost identical to the RZ/G3S USB PHY, the
> difference being 2 OTG blocks on RZ/G3L compared to 1 on RZ/G3S.
>
> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> ---
> Documentation/devicetree/bindings/phy/renesas,usb2-phy.yaml | 2 ++
> 1 file changed, 2 insertions(+)
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v2 phy-next 8/8] phy: lynx-10g: use RCW override procedure for dynamic protocol change
From: sashiko-bot @ 2026-06-12 21:30 UTC (permalink / raw)
To: Vladimir Oltean
Cc: vkoul, neil.armstrong, linux-phy, robh, conor+dt, devicetree,
olteanv
In-Reply-To: <20260612210859.266759-9-vladimir.oltean@nxp.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Read-modify-write in `fsl_guts_rmw()` drops dynamic overrides of other lanes sharing the same register.
- [High] If `fsl_guts_init()` fails during `soc_device_register()`, the newly added call to `fsl_guts_lane_set_mode()` will trigger an uninitialized spinlock BUG.
--
commit b8f6b1d355d36a951d62ed32d06cbd69afef40c2
Author: Vladimir Oltean <vladimir.oltean@nxp.com>
phy: lynx-10g: use RCW override procedure for dynamic protocol change
This patch enables dynamic protocol changes beyond 1000Base-X/SGMII and
2500Base-X. It removes the previous limitations by utilizing the RCW
override procedure provided by the guts driver.
diff --git a/drivers/phy/freescale/phy-fsl-lynx-10g.c b/drivers/phy/freescale/phy-fsl-lynx-10g.c
index 38def160ef1aa..5ece7889aed7b 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-10g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-10g.c
[ ... ]
@@ -1225,6 +1223,13 @@ static int lynx_10g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
if (powered_up)
lynx_10g_lane_halt(phy);
+ if (lynx_10g_lane_mode_needs_rcw_override(lane, lane_mode)) {
+ err = fsl_guts_lane_set_mode(priv->info->index, lane->id,
+ lane_mode);
^^^^^^^^^^^^^^^^^^^^^^
[Severity: High]
This isn't a bug introduced by this patch, but does calling
fsl_guts_lane_set_mode() here expose a bug in fsl_guts_rmw() where dynamic
overrides on other lanes are dropped?
When fsl_guts_lane_set_mode() calls fsl_guts_rmw() to update the hardware mux,
it incorrectly reads from the boot-time status register (RCWSR) instead of the
control register (RCWCR):
drivers/soc/fsl/guts.c:fsl_guts_rmw() {
...
u32 tmp = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[offset]);
tmp &= ~mask;
tmp |= val;
fsl_guts_write(&soc.dcfg_dcsr->rcwcr[offset], tmp);
...
}
Because RCWSR is read-only and reflects the boot-time configuration, and
multiple lanes share the same register, won't dynamically changing the
protocol of one lane revert any previous dynamic changes applied to the other
lanes?
[Severity: High]
This is a pre-existing issue, but could this also trigger an uninitialized
spinlock BUG if fsl_guts_init() fails?
In drivers/soc/fsl/guts.c, if fsl_guts_init() fails during
soc_device_register(), the error path doesn't clean up the global soc.data
and skips initializing soc.rcwcr_lock:
drivers/soc/fsl/guts.c:fsl_guts_init() {
...
soc_dev = soc_device_register(soc_dev_attr);
if (IS_ERR(soc_dev)) {
ret = PTR_ERR(soc_dev);
goto err;
}
spin_lock_init(&soc.rcwcr_lock);
...
}
Since soc.data is left populated, the readiness check in
fsl_guts_lane_set_mode() will pass. Won't this then proceed to acquire the
uninitialized soc.rcwcr_lock, triggering a SPIN_BUG_ON?
> + if (err)
> + goto out;
> + }
> +
> err = lynx_10g_lane_disable_pcvt(lane, lane->mode);
> if (err)
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260612210859.266759-1-vladimir.oltean@nxp.com?part=8
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v2 phy-next 7/8] soc: fsl: guts: implement the RCW override procedure
From: sashiko-bot @ 2026-06-12 21:22 UTC (permalink / raw)
To: Vladimir Oltean
Cc: linux-phy, vkoul, olteanv, devicetree, robh, conor+dt,
neil.armstrong
In-Reply-To: <20260612210859.266759-8-vladimir.oltean@nxp.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Failure to clear global pointers on error paths allows exported APIs to operate on dangling I/O pointers.
- [High] The Read-Modify-Write logic relies on a read-only boot status register, causing runtime overrides on other lanes to be silently discarded.
- [Medium] Direct returns and bypassed error labels in `fsl_guts_init` leak I/O memory mappings.
- [Medium] Exported APIs contradict their own zero-based indexing documentation and lack parameter bounds checking.
--
commit 2b919af199a087611275f238825912a50ae47330
Author: Ioana Ciornei <ioana.ciornei@nxp.com>
Date: Fri Jun 12 21:08:58 2026 +0000
soc: fsl: guts: implement the RCW override procedure
Add support for the RCW override procedure which enables runtime
reconfiguration of the protocol running on a SerDes lane. The procedure
is done through the DCFG DCSR space which now can be defined as the
second memory region of the guts DT node.
> diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c
> index 9f2aff07a2744..0fa2bd99d3c10 100644
> --- a/drivers/soc/fsl/guts.c
> +++ b/drivers/soc/fsl/guts.c
[ ... ]
> +/* Some fields of the Reset Configuration Word (RCW) can be overridden at
> + * runtime by writing to the RCWCRn registers contained within the DCSR space
> + * of the Device Configuration (DCFG) block. The layout of the RCWCRn registers
> + * is identical with the read-only RCWSRn from the CCSR space.
> + */
> +static void fsl_guts_rmw(int offset, u32 val, u32 mask)
> +{
> + u32 tmp = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[offset]);
> +
> + tmp &= ~mask;
> + tmp |= val;
> + fsl_guts_write(&soc.dcfg_dcsr->rcwcr[offset], tmp);
> +}
[Severity: High]
Does this read-modify-write logic accidentally discard previous runtime
overrides?
Since rcwsr is a read-only boot status register, it reflects the static boot
state. If multiple lanes share the same register, reading from rcwsr to update
a subsequent lane will overwrite any dynamic configurations previously written
to the write-only rcwcr register.
Would it be safer to use a software shadow register to track the runtime state
of rcwcr?
[ ... ]
> +/**
> + * fsl_guts_lane_init() - Notify guts module of SerDes lane configuration
> + * @serdes_idx: zero-based SerDes block index
> + * @lane: zero-based lane index within SerDes
> + *
> + * On the LS208xA SoC, the RCW override procedure needs to be aware of all link
> + * modes which are configured on a SerDes block.
> + */
> +void fsl_guts_lane_init(int serdes_idx, int lane, enum lynx_lane_mode lane_mode)
> +{
> + soc.lane_mode[serdes_idx - 1][lane] = lane_mode;
> +}
> +EXPORT_SYMBOL_NS_GPL(fsl_guts_lane_init, "FSL_GUTS");
[Severity: Medium]
Is the documentation out of sync with the implementation?
The docstring specifies that serdes_idx is a zero-based index, but the code
subtracts 1. If a caller follows the documentation and passes 0, won't this
result in an out-of-bounds array access?
Additionally, is there a risk of out-of-bounds access if the lane parameter
exceeds MAX_NUM_LANES? An unvalidated lane value could also cause undefined
behavior later when expanded by the GENMASK macros in the override lookup
functions.
[ ... ]
> @@ -250,6 +522,8 @@ static int __init fsl_guts_init(void)
> of_node_put(np);
> return -ENOMEM;
> }
> + /* DCFG_DCSR is optional */
> + soc.dcfg_dcsr = of_iomap(np, DCFG_DCSR);
>
> soc.little_endian = of_property_read_bool(np, "little-endian");
> soc.svr = fsl_guts_read(&soc.dcfg_ccsr->svr);
> @@ -274,7 +548,7 @@ static int __init fsl_guts_init(void)
> /* Register soc device */
> soc_dev_attr = kzalloc_obj(*soc_dev_attr);
> if (!soc_dev_attr)
> return -ENOMEM;
[Severity: Medium]
Will this direct return leak the I/O memory mapped earlier?
If kzalloc_obj() fails, the function returns immediately without unmapping
soc.dcfg_ccsr or the newly added soc.dcfg_dcsr.
[ ... ]
> @@ -296,6 +570,8 @@ static int __init fsl_guts_init(void)
> goto err;
> }
[Severity: Medium]
Does jumping to the err label skip the necessary unmapping?
If soc_device_register() fails, the code uses goto err. Because err is
placed after err_nomem, the iounmap() calls are bypassed, leaving the memory
mappings leaked.
[ ... ]
> err_nomem:
> ret = -ENOMEM;
> -
> + if (soc.dcfg_dcsr)
> + iounmap(soc.dcfg_dcsr);
> iounmap(soc.dcfg_ccsr);
> err:
> kfree(soc_dev_attr->family);
[Severity: High]
Should the global pointers be set to NULL after unmapping?
If fsl_guts_init() fails, it unmaps soc.dcfg_ccsr and soc.dcfg_dcsr but
leaves the global pointers dangling. Since this is a core_initcall, the boot
process will continue even if initialization fails.
If an exported API like fsl_guts_lane_set_mode() is called later, it checks
the pointer validity:
if (!soc.dcfg_dcsr) {
pr_debug("Device tree does not define DCFG_DCSR...");
return -EINVAL;
}
Since the pointer isn't NULL, this validation passes, which could lead to a
use-after-free when the driver attempts to write to the unmapped memory or
acquires the uninitialized spinlock.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260612210859.266759-1-vladimir.oltean@nxp.com?part=7
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v2 phy-next 5/8] soc: fsl: guts: make fsl_soc_data available after fsl_guts_init()
From: sashiko-bot @ 2026-06-12 21:21 UTC (permalink / raw)
To: Vladimir Oltean
Cc: neil.armstrong, vkoul, olteanv, linux-phy, robh, conor+dt,
devicetree
In-Reply-To: <20260612210859.266759-6-vladimir.oltean@nxp.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The iomem mappings `soc.dcfg_ccsr` and `soc.dcfg_dcsr` are leaked on multiple error paths during initialization.
- [High] The global `soc.data`, `soc.dcfg_ccsr` and `soc.dcfg_dcsr` pointers are not cleared on error paths, leading to dangling pointers, Use-After-Free, and uninitialized spinlock access.
--
commit 9f45015d702d2f4ca41d93015c23b3d9950743a7
Author: Vladimir Oltean <vladimir.oltean@nxp.com>
soc: fsl: guts: make fsl_soc_data available after fsl_guts_init()
In a future change, struct fsl_soc_data will be extended with methods
for performing RCW override.
Since this will be performed from a calling context outside
fsl_guts_init(), we need to keep track of the soc_data that we determine
at fsl_guts_init() time, so we can reference it later.
> diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c
> index 1494b545bbb47..9f2aff07a2744 100644
> --- a/drivers/soc/fsl/guts.c
> +++ b/drivers/soc/fsl/guts.c
> @@ -243,7 +243,7 @@ static int __init fsl_guts_init(void)
> np = of_find_matching_node_and_match(NULL, fsl_guts_of_match, &match);
> if (!np)
> return 0;
> - soc_data = match->data;
> + soc.data = match->data;
>
> soc.dcfg_ccsr = of_iomap(np, DCFG_CCSR);
> if (!soc.dcfg_ccsr) {
[Severity: High]
Does this code leak the soc.dcfg_ccsr and soc.dcfg_dcsr mappings on error paths?
Looking at fsl_guts_init(), if the soc_dev_attr allocation fails, the function
returns directly, leaking the mappings:
fsl_guts_init() {
...
soc_dev_attr = kzalloc_obj(*soc_dev_attr);
if (!soc_dev_attr)
return -ENOMEM;
...
}
If soc_device_register() fails, the code jumps to the err label, which bypasses
the err_nomem block where iounmap() is called, also leaking the mappings:
fsl_guts_init() {
...
soc_dev = soc_device_register(soc_dev_attr);
if (IS_ERR(soc_dev)) {
ret = PTR_ERR(soc_dev);
goto err;
}
...
err_nomem:
ret = -ENOMEM;
iounmap(soc.dcfg_ccsr);
err:
...
}
Could the error paths be updated to ensure iounmap() is called when these
failures occur?
[Severity: High]
Are the global soc.data, soc.dcfg_ccsr, and soc.dcfg_dcsr pointers cleared on
error paths?
In fsl_guts_init(), global state is set early:
fsl_guts_init() {
...
soc.data = match->data;
soc.dcfg_ccsr = of_iomap(np, DCFG_CCSR);
...
}
If an error occurs later, the memory is unmapped but the global pointers are
not cleared to NULL:
fsl_guts_init() {
...
err_nomem:
ret = -ENOMEM;
if (soc.dcfg_dcsr)
iounmap(soc.dcfg_dcsr);
iounmap(soc.dcfg_ccsr);
...
}
External callers checking if the driver is initialized via if (!soc.data) or
if (!soc.dcfg_dcsr) could bypass the checks and access the unmapped IO memory
or an uninitialized spinlock:
fsl_guts_lane_set_mode() {
...
spin_lock(&soc.rcwcr_lock);
if (soc.data->serdes_init_rcwcr)
soc.data->serdes_init_rcwcr(serdes_idx);
fsl_guts_rmw(override.offset, override.val << __bf_shf(override.mask),
override.mask);
...
}
Could the error paths be updated to set these pointers to NULL?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260612210859.266759-1-vladimir.oltean@nxp.com?part=5
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v2 phy-next 2/8] soc: fsl: guts: add a global structure to hold state
From: sashiko-bot @ 2026-06-12 21:18 UTC (permalink / raw)
To: Vladimir Oltean
Cc: olteanv, linux-phy, robh, conor+dt, devicetree, vkoul,
neil.armstrong
In-Reply-To: <20260612210859.266759-3-vladimir.oltean@nxp.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The global `soc.dcfg_ccsr` pointer is not cleared after being unmapped on the error path, resulting in a dangling pointer.
- [Medium] The globally mapped `soc.dcfg_ccsr` is leaked on multiple error paths during initialization.
--
commit 28927a6c50db9adc7ddb8b131d5e08a366298fb3
Author: Ioana Ciornei <ioana.ciornei@nxp.com>
soc: fsl: guts: add a global structure to hold state
Add the fsl_soc_guts structure in order to pass information like base
addresses, endianness etc between the init time and the runtime
operations. There is no point in mapping and unmapping the DCFG CCSR
space every time we need to make a read, just map it once and keep its
reference in this new global structure.
> diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c
> index f87ee47c15035..a0a52a5603a5f 100644
> --- a/drivers/soc/fsl/guts.c
> +++ b/drivers/soc/fsl/guts.c
[ ... ]
> @@ -199,18 +202,17 @@ static int __init fsl_guts_init(void)
> return 0;
> soc_data = match->data;
>
> - regs = of_iomap(np, DCFG_CCSR);
> - if (!regs) {
> + soc.dcfg_ccsr = of_iomap(np, DCFG_CCSR);
> + if (!soc.dcfg_ccsr) {
> of_node_put(np);
> return -ENOMEM;
> }
>
> - little_endian = of_property_read_bool(np, "little-endian");
> - if (little_endian)
> - svr = ioread32(®s->svr);
> + soc.little_endian = of_property_read_bool(np, "little-endian");
> + if (soc.little_endian)
> + svr = ioread32(&soc.dcfg_ccsr->svr);
> else
> - svr = ioread32be(®s->svr);
> - iounmap(regs);
> + svr = ioread32be(&soc.dcfg_ccsr->svr);
> of_node_put(np);
>
> /* Register soc device */
[Severity: Medium]
Since the iounmap() was removed here to keep soc.dcfg_ccsr mapped, what
happens if the subsequent soc_dev_attr allocation fails?
fsl_guts_init()
/* Register soc device */
soc_dev_attr = kzalloc_obj(*soc_dev_attr);
if (!soc_dev_attr)
return -ENOMEM;
Does this direct return leak the soc.dcfg_ccsr mapping?
Also, if soc_device_register() fails later in this function, the code does a
goto err:
soc_dev = soc_device_register(soc_dev_attr);
if (IS_ERR(soc_dev)) {
ret = PTR_ERR(soc_dev);
goto err;
}
Because this bypasses the err_nomem label, does it also leak the
soc.dcfg_ccsr mapping?
> @@ -263,6 +265,8 @@ static int __init fsl_guts_init(void)
>
> err_nomem:
> ret = -ENOMEM;
> +
> + iounmap(soc.dcfg_ccsr);
[Severity: High]
Since soc is a static global structure, does calling iounmap() here leave
soc.dcfg_ccsr with a dangling, non-NULL pointer?
If a client driver later attempts to use the API and checks this pointer
after an initialization failure, could it lead to a kernel oops?
> err:
> kfree(soc_dev_attr->family);
> kfree(soc_dev_attr->soc_id);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260612210859.266759-1-vladimir.oltean@nxp.com?part=2
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH v2 phy-next 8/8] phy: lynx-10g: use RCW override procedure for dynamic protocol change
From: Vladimir Oltean @ 2026-06-12 21:08 UTC (permalink / raw)
To: linux-phy
Cc: devicetree, linuxppc-dev, linux-arm-kernel, Ioana Ciornei,
Vinod Koul, Neil Armstrong, Tanjeff Moos,
Christophe Leroy (CS GROUP), Michael Walle, Shawn Guo, Frank Li,
linux-kernel
In-Reply-To: <20260612210859.266759-1-vladimir.oltean@nxp.com>
Up until this patch, the only protocol change supported was between
1000Base-X/SGMII and 2500Base-X. The others require an RCW override
procedure which was lacking.
Since now the guts driver provides the means of applying this procedure,
make use of it and remove any comment which mentioned the limitation.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: none
---
drivers/phy/freescale/Kconfig | 1 +
drivers/phy/freescale/phy-fsl-lynx-10g.c | 24 +++++++++++++++---------
2 files changed, 16 insertions(+), 9 deletions(-)
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
index 5bf3864fbe64..d4e189fffbf8 100644
--- a/drivers/phy/freescale/Kconfig
+++ b/drivers/phy/freescale/Kconfig
@@ -58,6 +58,7 @@ config PHY_FSL_LYNX_10G
tristate "Freescale Layerscape Lynx 10G SerDes PHY support"
depends on OF
depends on ARCH_LAYERSCAPE || COMPILE_TEST
+ select FSL_GUTS
select GENERIC_PHY
select PHY_FSL_LYNX_CORE
help
diff --git a/drivers/phy/freescale/phy-fsl-lynx-10g.c b/drivers/phy/freescale/phy-fsl-lynx-10g.c
index 38def160ef1a..5ece7889aed7 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-10g.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-10g.c
@@ -8,6 +8,7 @@
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
+#include <linux/fsl/guts.h>
#include "phy-fsl-lynx-core.h"
@@ -446,6 +447,7 @@ static void lynx_10g_lane_read_configuration(struct lynx_lane *lane)
}
lynx_10g_backup_pccr_val(lane);
+ fsl_guts_lane_init(priv->info->index, lane->id, lane->mode);
}
static int ls1028a_get_pccr(enum lynx_lane_mode lane_mode, int lane,
@@ -1167,14 +1169,7 @@ static bool lynx_10g_lane_mode_needs_rcw_override(struct lynx_lane *lane,
/* Major protocol changes, which involve changing the PCS connection to
* the GMII MAC with the one to the XGMII MAC, require an RCW override
- * procedure to reconfigure an internal mux, as documented here:
- * https://lore.kernel.org/linux-phy/20230810102631.bvozjer3t67r67iy@skbuf/
- * This is SoC-specific, and not yet implemented in drivers/soc/fsl/guts.c.
- *
- * So the supported set of protocols depends on the initial lane mode.
- *
- * Minor protocol changes (SGMII <-> 1000Base-X <-> 2500Base-X or
- * 10GBase-R <-> USXGMII) are supported.
+ * procedure to reconfigure an internal mux.
*/
if ((lynx_lane_mode_uses_gmii_mac(curr) &&
lynx_lane_mode_uses_xgmii_mac(new)) ||
@@ -1189,6 +1184,7 @@ static int lynx_10g_validate(struct phy *phy, enum phy_mode mode, int submode,
union phy_configure_opts *opts)
{
struct lynx_lane *lane = phy_get_drvdata(phy);
+ struct lynx_priv *priv = lane->priv;
enum lynx_lane_mode lane_mode;
int err;
@@ -1197,7 +1193,8 @@ static int lynx_10g_validate(struct phy *phy, enum phy_mode mode, int submode,
return err;
if (lynx_10g_lane_mode_needs_rcw_override(lane, lane_mode))
- return -EINVAL;
+ return fsl_guts_lane_validate(priv->info->index, lane->id,
+ lane_mode);
return 0;
}
@@ -1205,6 +1202,7 @@ static int lynx_10g_validate(struct phy *phy, enum phy_mode mode, int submode,
static int lynx_10g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
{
struct lynx_lane *lane = phy_get_drvdata(phy);
+ struct lynx_priv *priv = lane->priv;
bool powered_up = lane->powered_up;
enum lynx_lane_mode lane_mode;
int err;
@@ -1225,6 +1223,13 @@ static int lynx_10g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
if (powered_up)
lynx_10g_lane_halt(phy);
+ if (lynx_10g_lane_mode_needs_rcw_override(lane, lane_mode)) {
+ err = fsl_guts_lane_set_mode(priv->info->index, lane->id,
+ lane_mode);
+ if (err)
+ goto out;
+ }
+
err = lynx_10g_lane_disable_pcvt(lane, lane->mode);
if (err)
goto out;
@@ -1314,6 +1319,7 @@ static struct platform_driver lynx_10g_driver = {
};
module_platform_driver(lynx_10g_driver);
+MODULE_IMPORT_NS("FSL_GUTS");
MODULE_IMPORT_NS("PHY_FSL_LYNX");
MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>");
MODULE_AUTHOR("Vladimir Oltean <vladimir.oltean@nxp.com>");
--
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 v2 phy-next 7/8] soc: fsl: guts: implement the RCW override procedure
From: Vladimir Oltean @ 2026-06-12 21:08 UTC (permalink / raw)
To: linux-phy
Cc: devicetree, linuxppc-dev, linux-arm-kernel, Ioana Ciornei,
Vinod Koul, Neil Armstrong, Tanjeff Moos,
Christophe Leroy (CS GROUP), Michael Walle, Shawn Guo, Frank Li,
linux-kernel
In-Reply-To: <20260612210859.266759-1-vladimir.oltean@nxp.com>
From: Ioana Ciornei <ioana.ciornei@nxp.com>
Add support for the RCW override procedure which enables runtime
reconfiguration of the protocol running on a SerDes lane. The procedure
is done through the DCFG DCSR space which now can be defined as the
second memory region of the guts DT node.
Support is added on the following SoCs: LS1046A, LS1088A, LS2088A.
The procedure is exported to the "client" driver - the Lynx10G SerDes
PHY driver - through the following functions:
- fsl_guts_lane_init() used to notify the initial / boot time lane mode
running on a SerDes lane.
- fsl_guts_lane_validate() used to validate that changing the protocol
on a specific lane is supported.
- fsl_guts_lane_set_mode() which can be used to request the RCW
procedure be executed for a specific lane.
Since the RCW override procedure is different depending on the SoC, the
private fsl_soc_data structure is updated with two new per SoC callbacks
(.serdes_get_rcw_override() and .serdes_init_rcwcr()) which get used
from the generic fsl_guts_lane_set_mode() function. These two callbacks
hide all the SoC specific register offsets, masks and values so that the
_set_mode() procedure is straightforward.
Signed-off-by: Ioana Ciornei <ioana.ciornei@nxp.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2:
- drop DT maintainers from explicit CC
- keep devicetree@vger.kernel.org CCed on entire series
- include missing <linux/bitfield.h>
- namespace SRDS_PRTCL values for LS1046A and LS1088A, even if they are
the same. For LS1028A (not covered here) they are not.
- prefix SRDS_CLK_SEL_{GMII,XGMII} with LS2088A_
- reorder alphanumerically (LS1046A should come before LS1088A)
---
drivers/soc/fsl/guts.c | 285 ++++++++++++++++++++++++++++++++++++++-
include/linux/fsl/guts.h | 20 ++-
2 files changed, 298 insertions(+), 7 deletions(-)
diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c
index 9f2aff07a274..0fa2bd99d3c1 100644
--- a/drivers/soc/fsl/guts.c
+++ b/drivers/soc/fsl/guts.c
@@ -5,6 +5,7 @@
* Copyright (C) 2016 Freescale Semiconductor, Inc.
*/
+#include <linux/bitfield.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/module.h>
@@ -15,6 +16,28 @@
#include <linux/fsl/guts.h>
#define DCFG_CCSR 0
+#define DCFG_DCSR 1
+
+#define MAX_NUM_LANES 8
+#define MAX_NUM_SERDES 2
+
+#define LS1046A_RCWSR5_SRDS_PRTCL_S1(lane) \
+ GENMASK(19 + 4 * (lane), 16 + 4 * (lane))
+#define LS1046A_SRDS_PRTCL_XFI 1
+#define LS1046A_SRDS_PRTCL_100BASEX_SGMII 3
+
+#define LS1088A_RCWSR29_SRDS_PRTCL_S1_LNn(lane) \
+ GENMASK(19 + 4 * (3 - lane), 16 + 4 * (3 - lane))
+#define LS1088A_RCWSR30_SRDS_PRTCL_S2_LNn(lane) \
+ GENMASK(3 + 4 * (3 - lane), 4 * (3 - lane))
+#define LS1088A_SRDS_PRTCL_XFI 1
+#define LS1088A_SRDS_PRTCL_100BASEX_SGMII 3
+
+#define LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1 BIT(14)
+#define LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(lane) BIT(6 + (7 - (lane)))
+#define LS2088A_RCWSR30_SRDS_CLK_SEL_MSK GENMASK(13, 6)
+#define LS2088A_SRDS_CLK_SEL_XGMII 1
+#define LS2088A_SRDS_CLK_SEL_GMII 0
struct fsl_soc_die_attr {
char *die;
@@ -22,9 +45,19 @@ struct fsl_soc_die_attr {
u32 mask;
};
+struct fsl_soc_serdes_rcw_override {
+ int offset;
+ int mask;
+ int val;
+};
+
struct fsl_soc_data {
const char *sfp_compat;
u32 uid_offset;
+ int (*serdes_get_rcw_override)(int index, int lane,
+ enum lynx_lane_mode lane_mode,
+ struct fsl_soc_serdes_rcw_override *override);
+ void (*serdes_init_rcwcr)(int index);
};
enum qoriq_die {
@@ -138,9 +171,13 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = {
static struct fsl_soc_guts {
struct ccsr_guts __iomem *dcfg_ccsr;
+ struct ccsr_guts __iomem *dcfg_dcsr;
const struct fsl_soc_data *data;
bool little_endian;
u32 svr;
+ enum lynx_lane_mode lane_mode[MAX_NUM_SERDES][MAX_NUM_LANES];
+ bool rcwcr_init_done;
+ spinlock_t rcwcr_lock; /* serializes concurrent writes to the RCWCR */
} soc;
static unsigned int fsl_guts_read(const void __iomem *reg)
@@ -151,6 +188,28 @@ static unsigned int fsl_guts_read(const void __iomem *reg)
return ioread32be(reg);
}
+static void fsl_guts_write(void __iomem *reg, u32 val)
+{
+ if (soc.little_endian)
+ iowrite32(val, reg);
+ else
+ iowrite32be(val, reg);
+}
+
+/* Some fields of the Reset Configuration Word (RCW) can be overridden at
+ * runtime by writing to the RCWCRn registers contained within the DCSR space
+ * of the Device Configuration (DCFG) block. The layout of the RCWCRn registers
+ * is identical with the read-only RCWSRn from the CCSR space.
+ */
+static void fsl_guts_rmw(int offset, u32 val, u32 mask)
+{
+ u32 tmp = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[offset]);
+
+ tmp &= ~mask;
+ tmp |= val;
+ fsl_guts_write(&soc.dcfg_dcsr->rcwcr[offset], tmp);
+}
+
static bool fsl_soc_die_match_one(u32 svr, const struct fsl_soc_die_attr *match)
{
return match->svr == (svr & match->mask);
@@ -167,6 +226,97 @@ static const struct fsl_soc_die_attr *fsl_soc_die_match(
return NULL;
}
+static int
+fsl_guts_serdes_get_rcw_override(int serdes_idx, int lane,
+ enum lynx_lane_mode lane_mode,
+ struct fsl_soc_serdes_rcw_override *override)
+{
+ if ((!fsl_soc_die_match_one(soc.svr, &fsl_soc_die[DIE_LS1088A]) &&
+ !fsl_soc_die_match_one(soc.svr, &fsl_soc_die[DIE_LS2088A]) &&
+ !fsl_soc_die_match_one(soc.svr, &fsl_soc_die[DIE_LS1046A])) ||
+ !soc.data || !soc.data->serdes_get_rcw_override) {
+ pr_debug("RCW override not implemented for SoC\n");
+ return -EINVAL;
+ }
+
+ if (!soc.dcfg_dcsr) {
+ pr_debug("Device tree does not define DCFG_DCSR region necessary for RCW override\n");
+ return -EINVAL;
+ }
+
+ return soc.data->serdes_get_rcw_override(serdes_idx, lane, lane_mode,
+ override);
+}
+
+/**
+ * fsl_guts_lane_init() - Notify guts module of SerDes lane configuration
+ * @serdes_idx: zero-based SerDes block index
+ * @lane: zero-based lane index within SerDes
+ * @lane_mode: initial / boot time SerDes protocol for lane
+ *
+ * On the LS208xA SoC, the RCW override procedure needs to be aware of all link
+ * modes which are configured on a SerDes block.
+ */
+void fsl_guts_lane_init(int serdes_idx, int lane, enum lynx_lane_mode lane_mode)
+{
+ soc.lane_mode[serdes_idx - 1][lane] = lane_mode;
+}
+EXPORT_SYMBOL_NS_GPL(fsl_guts_lane_init, "FSL_GUTS");
+
+/**
+ * fsl_guts_lane_validate() - Validate that SerDes protocol is implemented and
+ * supported on current SoC
+ * @serdes_idx: zero-based SerDes block index
+ * @lane: zero-based lane index within SerDes
+ * @lane_mode: requested SerDes protocol
+ *
+ * Should be called before actually requesting the RCW override procedure to be
+ * applied using %fsl_guts_lane_set_mode()
+ *
+ * Return: 0 if RCW override to protocol is possible, negative error otherwise
+ */
+int fsl_guts_lane_validate(int serdes_idx, int lane, enum lynx_lane_mode lane_mode)
+{
+ struct fsl_soc_serdes_rcw_override override;
+
+ return fsl_guts_serdes_get_rcw_override(serdes_idx, lane, lane_mode,
+ &override);
+}
+EXPORT_SYMBOL_NS_GPL(fsl_guts_lane_validate, "FSL_GUTS");
+
+/**
+ * fsl_guts_lane_set_mode() - apply RCW override procedure for SerDes lane
+ * @serdes_idx: zero-based SerDes block index
+ * @lane: zero-based lane index within SerDes
+ * @lane_mode: requested SerDes protocol
+ *
+ * Return: 0 on success, negative error otherwise
+ */
+int fsl_guts_lane_set_mode(int serdes_idx, int lane, enum lynx_lane_mode lane_mode)
+{
+ struct fsl_soc_serdes_rcw_override override;
+ int err;
+
+ err = fsl_guts_serdes_get_rcw_override(serdes_idx, lane, lane_mode,
+ &override);
+ if (err)
+ return err;
+
+ spin_lock(&soc.rcwcr_lock);
+
+ if (soc.data->serdes_init_rcwcr)
+ soc.data->serdes_init_rcwcr(serdes_idx);
+
+ fsl_guts_rmw(override.offset, override.val << __bf_shf(override.mask),
+ override.mask);
+ soc.lane_mode[serdes_idx - 1][lane] = lane_mode;
+
+ spin_unlock(&soc.rcwcr_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(fsl_guts_lane_set_mode, "FSL_GUTS");
+
static u64 fsl_guts_get_soc_uid(const char *compat, unsigned int offset)
{
struct device_node *np;
@@ -193,6 +343,128 @@ static u64 fsl_guts_get_soc_uid(const char *compat, unsigned int offset)
return uid;
}
+static int ls1046a_serdes_get_rcw_override(int index, int lane,
+ enum lynx_lane_mode lane_mode,
+ struct fsl_soc_serdes_rcw_override *override)
+{
+ /* The RCW override procedure has to write to different registers
+ * depending on the SerDes block index.
+ */
+ switch (index) {
+ case 1:
+ override->offset = 4;
+ override->mask = LS1046A_RCWSR5_SRDS_PRTCL_S1(lane);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (lynx_lane_mode_uses_xgmii_mac(lane_mode))
+ override->val = LS1046A_SRDS_PRTCL_XFI;
+ else if (lynx_lane_mode_uses_gmii_mac(lane_mode))
+ override->val = LS1046A_SRDS_PRTCL_100BASEX_SGMII;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ls1088a_serdes_get_rcw_override(int index, int lane,
+ enum lynx_lane_mode lane_mode,
+ struct fsl_soc_serdes_rcw_override *override)
+{
+ /* The RCW override procedure has to write to different registers
+ * depending on the SerDes block index.
+ */
+ switch (index) {
+ case 1:
+ override->offset = 28;
+ override->mask = LS1088A_RCWSR29_SRDS_PRTCL_S1_LNn(lane);
+ break;
+ case 2:
+ override->offset = 29;
+ override->mask = LS1088A_RCWSR30_SRDS_PRTCL_S2_LNn(lane);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (lynx_lane_mode_uses_xgmii_mac(lane_mode))
+ override->val = LS1088A_SRDS_PRTCL_XFI;
+ else if (lynx_lane_mode_uses_gmii_mac(lane_mode))
+ override->val = LS1088A_SRDS_PRTCL_100BASEX_SGMII;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ls2088a_serdes_get_rcw_override(int index, int lane,
+ enum lynx_lane_mode lane_mode,
+ struct fsl_soc_serdes_rcw_override *override)
+{
+ switch (index) {
+ case 1:
+ override->offset = 29;
+ override->mask = LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(lane);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (lynx_lane_mode_uses_xgmii_mac(lane_mode))
+ override->val = LS2088A_SRDS_CLK_SEL_XGMII;
+ else if (lynx_lane_mode_uses_gmii_mac(lane_mode))
+ override->val = LS2088A_SRDS_CLK_SEL_GMII;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static void ls2088a_serdes_init_rcwcr(int serdes_idx)
+{
+ u32 reg;
+ int i;
+
+ if (serdes_idx != 1)
+ return;
+ if (soc.rcwcr_init_done)
+ return;
+
+ /* SRDS_CLK_EN_SEL_XGMII_S1: SerDes Clock Enable Select XGMII Serdes 1:
+ * Enables to select GMII/XGMII clock according to
+ * SRDS_CLK_SEL_XGMII_Ln_S1
+ */
+ reg = LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1;
+
+ /* We need to configure the initial state of all lanes for
+ * the SerDes block #1
+ */
+ for (i = 0; i < MAX_NUM_LANES; i++)
+ if (lynx_lane_mode_uses_xgmii_mac(soc.lane_mode[serdes_idx - 1][i]))
+ reg |= LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(i);
+
+ fsl_guts_rmw(29, reg,
+ LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1 |
+ LS2088A_RCWSR30_SRDS_CLK_SEL_MSK);
+
+ soc.rcwcr_init_done = true;
+}
+
+static const struct fsl_soc_data ls1088a_data = {
+ .serdes_get_rcw_override = ls1088a_serdes_get_rcw_override,
+};
+
+static const struct fsl_soc_data ls1046a_data = {
+ .serdes_get_rcw_override = ls1046a_serdes_get_rcw_override,
+};
+
+static const struct fsl_soc_data ls2088a_data = {
+ .serdes_get_rcw_override = ls2088a_serdes_get_rcw_override,
+ .serdes_init_rcwcr = ls2088a_serdes_init_rcwcr,
+};
+
static const struct fsl_soc_data ls1028a_data = {
.sfp_compat = "fsl,ls1028a-sfp",
.uid_offset = 0x21c,
@@ -221,10 +493,10 @@ static const struct of_device_id fsl_guts_of_match[] = {
{ .compatible = "fsl,mpc8572-guts", },
{ .compatible = "fsl,ls1021a-dcfg", },
{ .compatible = "fsl,ls1043a-dcfg", },
- { .compatible = "fsl,ls2080a-dcfg", },
- { .compatible = "fsl,ls1088a-dcfg", },
+ { .compatible = "fsl,ls2080a-dcfg", .data = &ls2088a_data},
+ { .compatible = "fsl,ls1088a-dcfg", .data = &ls1088a_data},
{ .compatible = "fsl,ls1012a-dcfg", },
- { .compatible = "fsl,ls1046a-dcfg", },
+ { .compatible = "fsl,ls1046a-dcfg", .data = &ls1046a_data},
{ .compatible = "fsl,lx2160a-dcfg", },
{ .compatible = "fsl,ls1028a-dcfg", .data = &ls1028a_data},
{}
@@ -250,6 +522,8 @@ static int __init fsl_guts_init(void)
of_node_put(np);
return -ENOMEM;
}
+ /* DCFG_DCSR is optional */
+ soc.dcfg_dcsr = of_iomap(np, DCFG_DCSR);
soc.little_endian = of_property_read_bool(np, "little-endian");
soc.svr = fsl_guts_read(&soc.dcfg_ccsr->svr);
@@ -296,6 +570,8 @@ static int __init fsl_guts_init(void)
goto err;
}
+ spin_lock_init(&soc.rcwcr_lock);
+
pr_info("Machine: %s\n", soc_dev_attr->machine);
pr_info("SoC family: %s\n", soc_dev_attr->family);
pr_info("SoC ID: %s, Revision: %s\n",
@@ -305,7 +581,8 @@ static int __init fsl_guts_init(void)
err_nomem:
ret = -ENOMEM;
-
+ if (soc.dcfg_dcsr)
+ iounmap(soc.dcfg_dcsr);
iounmap(soc.dcfg_ccsr);
err:
kfree(soc_dev_attr->family);
diff --git a/include/linux/fsl/guts.h b/include/linux/fsl/guts.h
index fdb55ca47a4f..176842531241 100644
--- a/include/linux/fsl/guts.h
+++ b/include/linux/fsl/guts.h
@@ -13,6 +13,7 @@
#include <linux/types.h>
#include <linux/io.h>
+#include <soc/fsl/phy-fsl-lynx.h>
/*
* Global Utility Registers.
@@ -91,9 +92,15 @@ struct ccsr_guts {
u32 iovselsr; /* 0x.00c0 - I/O voltage select status register
Called 'elbcvselcr' on 86xx SOCs */
u8 res0c4[0x100 - 0xc4];
- u32 rcwsr[16]; /* 0x.0100 - Reset Control Word Status registers
- There are 16 registers */
- u8 res140[0x224 - 0x140];
+ /* 0x.0100 - read-only Reset Configuration Word Status registers in
+ * CCSR, or write-only Reset Configuration Word Control registers in
+ * DCSR. In both cases there are 32 registers.
+ */
+ union {
+ u32 rcwsr[32];
+ u32 rcwcr[32];
+ };
+ u8 res180[0x224 - 0x180];
u32 iodelay1; /* 0x.0224 - IO delay control register 1 */
u32 iodelay2; /* 0x.0228 - IO delay control register 2 */
u8 res22c[0x604 - 0x22c];
@@ -131,6 +138,13 @@ struct ccsr_guts {
u32 srds2cr1; /* 0x.0f44 - SerDes2 Control Register 0 */
} __attribute__ ((packed));
+void fsl_guts_lane_init(int serdes_idx, int lane,
+ enum lynx_lane_mode lane_mode);
+int fsl_guts_lane_validate(int serdes_idx, int lane,
+ enum lynx_lane_mode lane_mode);
+int fsl_guts_lane_set_mode(int serdes_idx, int lane,
+ enum lynx_lane_mode lane_mode);
+
/* Alternate function signal multiplex control */
#define MPC85xx_PMUXCR_QE(x) (0x8000 >> (x))
--
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 v2 phy-next 6/8] dt-bindings: fsl: layerscape-dcfg: define DCFG_DCSR region
From: Vladimir Oltean @ 2026-06-12 21:08 UTC (permalink / raw)
To: linux-phy
Cc: devicetree, linuxppc-dev, linux-arm-kernel, Ioana Ciornei,
Vinod Koul, Neil Armstrong, Tanjeff Moos,
Christophe Leroy (CS GROUP), Michael Walle, Shawn Guo, Frank Li,
linux-kernel, Conor Dooley, Conor Dooley, Krzysztof Kozlowski,
Rob Herring
In-Reply-To: <20260612210859.266759-1-vladimir.oltean@nxp.com>
In Layerscape (Arm) and QorIQ (PowerPC) devices, hardware peripherals
are accessed by the CPU through a portion of the SoC address space
called CCSR ("Configuration, Control, and Status Registers"). All
hardware IP blocks have their registers mapped here, and the Device
Configuration block makes no exception.
However, there exists a secondary range of the address space named DCSR
("Debug Control and Status Registers") which, like CCSR, also holds
registers of hardware IP blocks, except the DCSR contents is hidden in
all public reference manuals.
The intention of the CCSR/DCSR split, to the best of my knowledge, was
to place the functionality that is too low level for normal use, and
which is necessary only for debug, in a completely separate address
space which can be hidden.
A use case has appeared where networking SerDes lanes need to be
reconfigured at runtime for a different protocol (example: 10GBase-R to
SGMII), and the architecture of the SoCs does not normally permit that.
The Reset Configuration Word (RCW) is a data structure read by the SoC
preboot loader (PBL) which contains stuff like pinmuxing and SerDes
protocol mapping for each lane.
The RCW that the PBL has loaded is visible in the DCFG block's normal
status registers (from CCSR), as read only. Turns out, the RCW is also
mapped in the DCFG's shadow register map (in DCSR), in a write-only
form. Writing to the RCW registers from the DCFG's DCSR space to change
what the PBL has loaded is called "RCW override".
It has been validated that the RCW override procedure is necessary to
reconfigure the networking data path when a SerDes lane performs a major
protocol change. It changes some internal muxes which connect the PCS to
either the 10G MAC or to the 1G MAC.
Defining the DCSR area of the DCFG as a secondary 'reg' array element
allows operating systems to perform RCW overrides. Since it is
introduced late in the binding's lifetime, it is optional. It can be
identified by name, but also by index (first 'reg' is CCSR).
Note that while all SoCs should have a DCFG register block in DCSR, we
only need to expose it for the SoCs where the RCW override procedure is
known to be needed and has been validated.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
---
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Rob Herring <robh@kernel.org>
v1->v2:
- add Conor's review tag
- update email addresses of DT maintainers
---
.../bindings/soc/fsl/fsl,layerscape-dcfg.yaml | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/soc/fsl/fsl,layerscape-dcfg.yaml b/Documentation/devicetree/bindings/soc/fsl/fsl,layerscape-dcfg.yaml
index 3fb0534ea597..fc14fd0bf84b 100644
--- a/Documentation/devicetree/bindings/soc/fsl/fsl,layerscape-dcfg.yaml
+++ b/Documentation/devicetree/bindings/soc/fsl/fsl,layerscape-dcfg.yaml
@@ -36,7 +36,20 @@ properties:
- const: simple-mfd
reg:
- maxItems: 1
+ minItems: 1
+ items:
+ - description:
+ Customer-visible DCFG register map from CCSR address space
+ (Configuration, Control and Status Registers)
+ - description:
+ Customer-hidden DCFG register map from DCSR address space
+ (Debug Control and Status Registers)
+
+ reg-names:
+ minItems: 1
+ items:
+ - const: dcfg_ccsr
+ - const: dcfg_dcsr
little-endian: true
big-endian: true
--
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 v2 phy-next 5/8] soc: fsl: guts: make fsl_soc_data available after fsl_guts_init()
From: Vladimir Oltean @ 2026-06-12 21:08 UTC (permalink / raw)
To: linux-phy
Cc: devicetree, linuxppc-dev, linux-arm-kernel, Ioana Ciornei,
Vinod Koul, Neil Armstrong, Tanjeff Moos,
Christophe Leroy (CS GROUP), Michael Walle, Shawn Guo, Frank Li,
linux-kernel
In-Reply-To: <20260612210859.266759-1-vladimir.oltean@nxp.com>
In a future change, struct fsl_soc_data will be extended with methods
for performing RCW override.
Since this will be performed from a calling context outside
fsl_guts_init(), we need to keep track of the soc_data that we determine
at fsl_guts_init() time, so we can reference it later.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: none
---
drivers/soc/fsl/guts.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c
index 1494b545bbb4..9f2aff07a274 100644
--- a/drivers/soc/fsl/guts.c
+++ b/drivers/soc/fsl/guts.c
@@ -138,6 +138,7 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = {
static struct fsl_soc_guts {
struct ccsr_guts __iomem *dcfg_ccsr;
+ const struct fsl_soc_data *data;
bool little_endian;
u32 svr;
} soc;
@@ -234,7 +235,6 @@ static int __init fsl_guts_init(void)
struct soc_device_attribute *soc_dev_attr;
static struct soc_device *soc_dev;
const struct fsl_soc_die_attr *soc_die;
- const struct fsl_soc_data *soc_data;
const struct of_device_id *match;
struct device_node *np;
u64 soc_uid = 0;
@@ -243,7 +243,7 @@ static int __init fsl_guts_init(void)
np = of_find_matching_node_and_match(NULL, fsl_guts_of_match, &match);
if (!np)
return 0;
- soc_data = match->data;
+ soc.data = match->data;
soc.dcfg_ccsr = of_iomap(np, DCFG_CCSR);
if (!soc.dcfg_ccsr) {
@@ -283,9 +283,9 @@ static int __init fsl_guts_init(void)
if (!soc_dev_attr->revision)
goto err_nomem;
- if (soc_data)
- soc_uid = fsl_guts_get_soc_uid(soc_data->sfp_compat,
- soc_data->uid_offset);
+ if (soc.data)
+ soc_uid = fsl_guts_get_soc_uid(soc.data->sfp_compat,
+ soc.data->uid_offset);
if (soc_uid)
soc_dev_attr->serial_number = kasprintf(GFP_KERNEL, "%016llX",
soc_uid);
--
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 v2 phy-next 4/8] soc: fsl: guts: make it easier to determine on which SoC we are running
From: Vladimir Oltean @ 2026-06-12 21:08 UTC (permalink / raw)
To: linux-phy
Cc: devicetree, linuxppc-dev, linux-arm-kernel, Ioana Ciornei,
Vinod Koul, Neil Armstrong, Tanjeff Moos,
Christophe Leroy (CS GROUP), Michael Walle, Shawn Guo, Frank Li,
linux-kernel
In-Reply-To: <20260612210859.266759-1-vladimir.oltean@nxp.com>
From: Ioana Ciornei <ioana.ciornei@nxp.com>
The guts driver will need to easily determine on which SoC it's running
when it will need to perform RCW override at runtime. The guts driver
knows this already because fsl_guts_init() reads the QorIQ/Layerscape
architectural System Version Register (SVR), but it doesn't save this
for later lookups.
Add a new qoriq_die enum to be used as an index in the fsl_soc_die
array. A new fsl_soc_die_match_one() function is also added so that we
can directly determine if the SVR is a match with a specific die.
The SVR value read from the DCFG CCSR is also kept in the global soc
structure so that it can be accessed when needed.
Signed-off-by: Ioana Ciornei <ioana.ciornei@nxp.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
v1->v2: none
---
drivers/soc/fsl/guts.c | 47 ++++++++++++++++++++++++++++++++++++------
1 file changed, 41 insertions(+), 6 deletions(-)
diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c
index dc1a42cd9544..1494b545bbb4 100644
--- a/drivers/soc/fsl/guts.c
+++ b/drivers/soc/fsl/guts.c
@@ -27,6 +27,23 @@ struct fsl_soc_data {
u32 uid_offset;
};
+enum qoriq_die {
+ DIE_T4240,
+ DIE_T1040,
+ DIE_T2080,
+ DIE_T1024,
+ DIE_LS1043A,
+ DIE_LS2080A,
+ DIE_LS1088A,
+ DIE_LS1012A,
+ DIE_LS1046A,
+ DIE_LS2088A,
+ DIE_LS1021A,
+ DIE_LX2160A,
+ DIE_LS1028A,
+ DIE_MAX,
+};
+
/* SoC die attribute definition for QorIQ platform */
static const struct fsl_soc_die_attr fsl_soc_die[] = {
/*
@@ -34,21 +51,25 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = {
*/
/* Die: T4240, SoC: T4240/T4160/T4080 */
+ [DIE_T4240] =
{ .die = "T4240",
.svr = 0x82400000,
.mask = 0xfff00000,
},
/* Die: T1040, SoC: T1040/T1020/T1042/T1022 */
+ [DIE_T1040] =
{ .die = "T1040",
.svr = 0x85200000,
.mask = 0xfff00000,
},
/* Die: T2080, SoC: T2080/T2081 */
+ [DIE_T2080] =
{ .die = "T2080",
.svr = 0x85300000,
.mask = 0xfff00000,
},
/* Die: T1024, SoC: T1024/T1014/T1023/T1013 */
+ [DIE_T1024] =
{ .die = "T1024",
.svr = 0x85400000,
.mask = 0xfff00000,
@@ -59,46 +80,55 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = {
*/
/* Die: LS1043A, SoC: LS1043A/LS1023A */
+ [DIE_LS1043A] =
{ .die = "LS1043A",
.svr = 0x87920000,
.mask = 0xffff0000,
},
/* Die: LS2080A, SoC: LS2080A/LS2040A/LS2085A */
+ [DIE_LS2080A] =
{ .die = "LS2080A",
.svr = 0x87010000,
.mask = 0xff3f0000,
},
/* Die: LS1088A, SoC: LS1088A/LS1048A/LS1084A/LS1044A */
+ [DIE_LS1088A] =
{ .die = "LS1088A",
.svr = 0x87030000,
.mask = 0xff3f0000,
},
/* Die: LS1012A, SoC: LS1012A */
+ [DIE_LS1012A] =
{ .die = "LS1012A",
.svr = 0x87040000,
.mask = 0xffff0000,
},
/* Die: LS1046A, SoC: LS1046A/LS1026A */
+ [DIE_LS1046A] =
{ .die = "LS1046A",
.svr = 0x87070000,
.mask = 0xffff0000,
},
/* Die: LS2088A, SoC: LS2088A/LS2048A/LS2084A/LS2044A */
+ [DIE_LS2088A] =
{ .die = "LS2088A",
.svr = 0x87090000,
.mask = 0xff3f0000,
},
/* Die: LS1021A, SoC: LS1021A/LS1020A/LS1022A */
+ [DIE_LS1021A] =
{ .die = "LS1021A",
.svr = 0x87000000,
.mask = 0xfff70000,
},
/* Die: LX2160A, SoC: LX2160A/LX2120A/LX2080A */
+ [DIE_LX2160A] =
{ .die = "LX2160A",
.svr = 0x87360000,
.mask = 0xff3f0000,
},
/* Die: LS1028A, SoC: LS1028A */
+ [DIE_LS1028A] =
{ .die = "LS1028A",
.svr = 0x870b0000,
.mask = 0xff3f0000,
@@ -109,6 +139,7 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = {
static struct fsl_soc_guts {
struct ccsr_guts __iomem *dcfg_ccsr;
bool little_endian;
+ u32 svr;
} soc;
static unsigned int fsl_guts_read(const void __iomem *reg)
@@ -119,11 +150,16 @@ static unsigned int fsl_guts_read(const void __iomem *reg)
return ioread32be(reg);
}
+static bool fsl_soc_die_match_one(u32 svr, const struct fsl_soc_die_attr *match)
+{
+ return match->svr == (svr & match->mask);
+}
+
static const struct fsl_soc_die_attr *fsl_soc_die_match(
u32 svr, const struct fsl_soc_die_attr *matches)
{
while (matches->svr) {
- if (matches->svr == (svr & matches->mask))
+ if (fsl_soc_die_match_one(svr, matches))
return matches;
matches++;
}
@@ -202,7 +238,6 @@ static int __init fsl_guts_init(void)
const struct of_device_id *match;
struct device_node *np;
u64 soc_uid = 0;
- u32 svr;
int ret;
np = of_find_matching_node_and_match(NULL, fsl_guts_of_match, &match);
@@ -217,7 +252,7 @@ static int __init fsl_guts_init(void)
}
soc.little_endian = of_property_read_bool(np, "little-endian");
- svr = fsl_guts_read(&soc.dcfg_ccsr->svr);
+ soc.svr = fsl_guts_read(&soc.dcfg_ccsr->svr);
of_node_put(np);
/* Register soc device */
@@ -229,7 +264,7 @@ static int __init fsl_guts_init(void)
if (ret)
of_machine_read_compatible(&soc_dev_attr->machine, 0);
- soc_die = fsl_soc_die_match(svr, fsl_soc_die);
+ soc_die = fsl_soc_die_match(soc.svr, fsl_soc_die);
if (soc_die) {
soc_dev_attr->family = kasprintf(GFP_KERNEL, "QorIQ %s",
soc_die->die);
@@ -239,12 +274,12 @@ static int __init fsl_guts_init(void)
if (!soc_dev_attr->family)
goto err_nomem;
- soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "svr:0x%08x", svr);
+ soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "svr:0x%08x", soc.svr);
if (!soc_dev_attr->soc_id)
goto err_nomem;
soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d.%d",
- (svr >> 4) & 0xf, svr & 0xf);
+ (soc.svr >> 4) & 0xf, soc.svr & 0xf);
if (!soc_dev_attr->revision)
goto err_nomem;
--
2.34.1
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox