* Re: [PATCH] phy: GOOGLE_USB: add TYPEC dependency
From: Neil Armstrong @ 2026-02-04 12:41 UTC (permalink / raw)
To: Arnd Bergmann, Vinod Koul, Joy Chakraborty, Naveen Kumar, Roy Luo
Cc: Arnd Bergmann, Alex Elder, Ivaylo Ivanov, Dmitry Baryshkov,
Inochi Amaoto, Sven Peter, Vladimir Oltean, linux-phy,
linux-kernel
In-Reply-To: <20260202095655.1289973-1-arnd@kernel.org>
On 2/2/26 10:56, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
>
> With CONFIG_TYPEC=m, this driver cannot be built-in:
>
> arm-linux-gnueabi/bin/arm-linux-gnueabi-ld: drivers/phy/phy-google-usb.o: in function `google_usb_phy_remove':
> phy-google-usb.c:(.text+0x24): undefined reference to `typec_switch_unregister'
>
> Add CONFIG_TYPEC as a hard dependency here to force a clean build.
> In theory, compile-testing with CONFIG_TYPEC=n would also work, but
> that seems pointless.
>
> Fixes: cbce66669c82 ("phy: Add Google Tensor SoC USB PHY driver")
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> ---
> drivers/phy/Kconfig | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
> index 142e7b0ef2ef..02467dfd4fb0 100644
> --- a/drivers/phy/Kconfig
> +++ b/drivers/phy/Kconfig
> @@ -50,6 +50,7 @@ config GENERIC_PHY_MIPI_DPHY
> config PHY_GOOGLE_USB
> tristate "Google Tensor SoC USB PHY driver"
> select GENERIC_PHY
> + depends on TYPEC
> help
> Enable support for the USB PHY on Google Tensor SoCs, starting with
> the G5 generation (Laguna). This driver provides the PHY interfaces
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Thanks,
Neil
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re:[PATCH 0/7] phy: rockchip: usbdp: Fixes, DP 1-lane support and cleanups
From: Andy Yan @ 2026-02-04 12:09 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Vinod Koul, Neil Armstrong, Heiko Stuebner, Andy Yan,
Yubing Zhang, linux-phy, linux-arm-kernel, linux-rockchip,
linux-kernel, kernel, Frank Wang, William Wu
In-Reply-To: <20260203-rockchip-usbdp-cleanup-v1-0-16a6f92ed176@collabora.com>
Hello Sebastian,
At 2026-02-04 02:41:26, "Sebastian Reichel" <sebastian.reichel@collabora.com> wrote:
>As there are some issues with the DisplayPort support, I went
>though Rockchip's BSP kernel tree and looked for fixes. I found
>two small changes for the initial register setup, which do not
>help with the DP issue but seem sensible in general. Afterwards
>I added one more fix, which corrects an issue that effectively
>results in USB-C adapters with combined USB3+DP capabilities
>not being able to use the DP part.
>
>Afterwards I added one more patch adding single-lane DP support
>(found in Rockchip BSP kernel) and a couple of cleanups from my
>side. These are logically independent from the fixes, but I put
>all together in a single series because they need to be applied
>in the right order to avoid conflicts.
>
>Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
>---
>Frank Wang (1):
> phy: rockchip: usbdp: Amend SSC modulation deviation
>
>Sebastian Reichel (4):
> phy: rockchip: usbdp: Add missing mode_change update
> phy: rockchip: usbdp: Rename DP lane functions
> phy: rockchip: usbdp: Use FIELD_PREP_WM16_CONST
> phy: rockchip: usbdp: Cleanup DP lane selection function
>
>William Wu (1):
> phy: rockchip: usbdp: Fix LFPS detect threshold control
>
>Zhang Yubing (1):
> phy: rockchip: usbdp: Support single-lane DP
>
> drivers/phy/rockchip/phy-rockchip-usbdp.c | 114 +++++++++++++-----------------
> 1 file changed, 48 insertions(+), 66 deletions(-)
After applying this series of patches, the hub that previously couldn’t recognize Alt Mode can now detect it and work properly,
but it also triggers a USB-related SERR — did I miss something?
[ 2.639842][ T244] 8021q: adding VLAN 0 to HW filter on device eth1
[ 3.872936][ T23] usb 3-1: new high-speed USB device number 2 using xhci-hcd
[ 4.011232][ T23] usb 3-1: New USB device found, idVendor=2109, idProduct=2817, bcdDevice= 5.54
[ 4.012102][ T23] usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 4.012908][ T23] usb 3-1: Product: USB2.0 Hub
[ 4.013469][ T23] usb 3-1: Manufacturer: VIA Labs, Inc.
[ 4.014064][ T23] usb 3-1: SerialNumber: 000000000
[ 4.071154][ T23] hub 3-1:1.0: USB hub found
[ 4.071876][ T23] hub 3-1:1.0: 5 ports detected
[ 4.385554][ C1] SError Interrupt on CPU1, code 0x00000000bf000002 -- SError
[ 4.385592][ C1] CPU: 1 UID: 0 PID: 96 Comm: kworker/1:1 Tainted: G M 6.19.0-rc4+ #223 PREEMPT
[ 4.385623][ C1] Tainted: [M]=MACHINE_CHECK
[ 4.385632][ C1] Hardware name: ArmSoM Sige5 (DT)
[ 4.385644][ C1] Workqueue: usb_hub_wq hub_event
[ 4.385680][ C1] pstate: 000000c5 (nzcv daIF -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[ 4.385701][ C1] pc : el1_abort+0x34/0x68
[ 4.385728][ C1] lr : el1_abort+0x24/0x68
[ 4.385749][ C1] sp : ffff8000833a36c0
[ 4.385758][ C1] x29: ffff8000833a36c0 x28: ffff0000c3568000 x27: ffff0000c3d70c48
[ 4.385792][ C1] x26: ffff0000c13c8d78 x25: 0000000000000004 x24: 0000000000000001
[ 4.385823][ C1] x23: 00000000600000c5 x22: ffff800084088430 x21: 0000000096000210
[ 4.385854][ C1] x20: 0000000000000000 x19: ffff8000833a3700 x18: 00000000ffffffff
[ 4.385885][ C1] x17: ffff800083179000 x16: ffffffffffffffff x15: ffff0000c11ba128
[ 4.385916][ C1] x14: 0000000000000376 x13: 0000000000000376 x12: 0000000000000000
[ 4.385946][ C1] x11: 00000000000000c0 x10: 0000000000000af0 x9 : 0000000000002303
[ 4.385976][ C1] x8 : ffff8000833a3740 x7 : ffff8000833a3830 x6 : ffff8000833a3800
[ 4.386006][ C1] x5 : ffff8000833a3780 x4 : ffff8000833a4000 x3 : ffff8000812c986c
[ 4.386037][ C1] x2 : ffff8000812c986c x1 : 00000000000000c0 x0 : 0000000000000000
[ 4.386071][ C1] Kernel panic - not syncing: Asynchronous SError Interrupt
[ 4.386085][ C1] CPU: 1 UID: 0 PID: 96 Comm: kworker/1:1 Tainted: G M 6.19.0-rc4+ #223 PREEMPT
[ 4.386113][ C1] Tainted: [M]=MACHINE_CHECK
[ 4.386121][ C1] Hardware name: ArmSoM Sige5 (DT)
[ 4.386131][ C1] Workqueue: usb_hub_wq hub_event
[ 4.386156][ C1] Call trace:
[ 4.386164][ C1] show_stack+0x18/0x24 (C)
[ 4.386202][ C1] dump_stack_lvl+0x60/0x80
[ 4.386236][ C1] dump_stack+0x18/0x24
[ 4.386266][ C1] vpanic+0xf8/0x2ac
[ 4.386290][ C1] trace_suspend_resume+0x0/0x7c
[ 4.386314][ C1] add_taint+0x0/0xbc
[ 4.386342][ C1] arm64_serror_panic+0x70/0x80
[ 4.386364][ C1] do_serror+0x3c/0x74
[ 4.386384][ C1] el1h_64_error_handler+0x34/0x50
[ 4.386414][ C1] el1h_64_error+0x6c/0x70
[ 4.386436][ C1] el1_abort+0x34/0x68 (P)
[ 4.386462][ C1] el1h_64_sync_handler+0x50/0xac
[ 4.386490][ C1] el1h_64_sync+0x6c/0x70
[ 4.386511][ C1] xhci_portsc_readl+0x4/0x1c (P)
[ 4.386538][ C1] usb_hcd_submit_urb+0x48c/0xa3c
[ 4.386565][ C1] usb_submit_urb+0x1e0/0x650
[ 4.386593][ C1] usb_start_wait_urb+0x74/0x17c
[ 4.386621][ C1] usb_control_msg+0xc4/0x140
[ 4.386648][ C1] hub_port_reset+0x118/0x9c0
[ 4.386672][ C1] hub_port_init+0xb4/0xd84
[ 4.386694][ C1] hub_event+0x10e4/0x1a5c
[ 4.386718][ C1] process_one_work+0x16c/0x3d4
[ 4.386752][ C1] worker_thread+0x2c4/0x3f4
[ 4.386773][ C1] kthread+0x130/0x1ec
[ 4.386803][ C1] ret_from_fork+0x10/0x20
[ 4.386829][ C1] SMP: stopping secondary CPUs
[ 4.386854][ C1] Kernel Offset: disabled
[ 4.386861][ C1] CPU features: 0x200000,10410001,00008000,0400420b
[ 4.386876][ C1] Memory Limit: none
[ 4.414238][ C1] ---[ end Kernel panic - not syncing: Asynchronous SError Interrupt ]---
>---
>base-commit: 18f7fcd5e69a04df57b563360b88be72471d6b62
>change-id: 20260203-rockchip-usbdp-cleanup-5b59dfb561a3
>
>Best regards,
>--
>Sebastian Reichel <sebastian.reichel@collabora.com>
>
>
>_______________________________________________
>Linux-rockchip mailing list
>Linux-rockchip@lists.infradead.org
>http://lists.infradead.org/mailman/listinfo/linux-rockchip
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v6 0/8] Add generic PHY driver used by MACB/GEM on EyeQ5
From: Théo Lebrun @ 2026-02-04 9:35 UTC (permalink / raw)
To: Théo Lebrun, Vladimir Kondratiev, Grégory Clement,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Vinod Koul,
Kishon Vijay Abraham I, Michael Turquette, Stephen Boyd,
Philipp Zabel, Thomas Bogendoerfer, Neil Armstrong
Cc: linux-mips, devicetree, linux-kernel, linux-phy, linux-clk,
Benoît Monin, Tawfik Bayouk, Thomas Petazzoni, Luca Ceresoli,
Conor Dooley, Jerome Brunet, Andrew Lunn
In-Reply-To: <20260127-macb-phy-v6-0-cdd840588188@bootlin.com>
On Tue Jan 27, 2026 at 6:09 PM CET, Théo Lebrun wrote:
> EyeQ5 SoCs integrate two GEM instances. A system-controller register
> region named "OLB" has some control over the Ethernet PHY integration.
Is the plan to take this for the v6.20 merge window or await v7.0?
The driver has seldom changed since its first revision 3 months ago.
Thanks!
Have a nice day,
--
Théo Lebrun, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH 0/4] Add eMMC PHY support for Axiado AX3000 SoC
From: Tzu-Hao Wei @ 2026-02-04 9:19 UTC (permalink / raw)
To: Rob Herring
Cc: devicetree, Krzysztof Kozlowski, SriNavmani A, openbmc,
Neil Armstrong, Prasad Bolisetty, linux-kernel, Conor Dooley,
linux-phy, linux-arm-kernel, Vinod Koul
In-Reply-To: <176822994353.7723.17700786956910310022.robh@kernel.org>
On 1/12/2026 11:17 PM, Rob Herring wrote:
> My bot found new DTB warnings on the .dts files added or changed in this
> series.
>
> Some warnings may be from an existing SoC .dtsi. Or perhaps the warnings
> are fixed by another series. Ultimately, it is up to the platform
> maintainer whether these warnings are acceptable or not. No need to reply
> unless the platform maintainer has comments.
>
> If you already ran DT checks and didn't see these error(s), then
> make sure dt-schema is up to date:
>
> pip3 install dtschema --upgrade
>
>
> This patch series was applied (using b4) to base:
> Deps: looking for dependencies matching 4 patch-ids
> Deps: Applying prerequisite patch: [PATCH 1/4] dt-bindings: phy: axiado,ax3000-emmc-phy: add Axiado eMMC PHY
> Deps: Applying prerequisite patch: [PATCH 2/4] phy: axiado: add Axiado eMMC PHY driver
> Deps: Applying prerequisite patch: [PATCH 3/4] MAINTAINERS: Add Axiado AX3000 eMMC PHY driver
> Deps: Applying prerequisite patch: [PATCH 4/4] arm64: dts: axiado: Add eMMC PHY node
> Base: f10c325a345fef0a688a2bcdfab1540d1c924148 (use --merge-base to override)
>
> If this is not the correct base, please add 'base-commit' tag
> (or use b4 which does this automatically)
>
> New warnings running 'make CHECK_DTBS=y for arch/arm64/boot/dts/axiado/' for 20260109-axiado-ax3000-add-emmc-phy-driver-support-v1-0-dd43459dbfea@axiado.com:
>
> arch/arm64/boot/dts/axiado/ax3000-evk.dtb: /soc/phy@80801c00: failed to match any schema with compatible: ['axiado,ax3000-emmc-phy']
Thanks. It will be fixed in the next version.
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v14 0/9] Add Type-C DP support for RK3399 EVB IND board
From: Chaoyi Chen @ 2026-02-04 8:59 UTC (permalink / raw)
To: Hugh Cole-Baker
Cc: Chaoyi Chen, Heikki Krogerus, Greg Kroah-Hartman,
Dmitry Baryshkov, Peter Chen, Luca Ceresoli, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Vinod Koul,
Kishon Vijay Abraham I, Heiko Stuebner, Sandy Huang, Andy Yan,
Yubing Zhang, Frank Wang, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Amit Sunil Dhamne, Dragan Simic, Johan Jonker,
Diederik de Haas, Peter Robinson, linux-usb, devicetree,
linux-kernel, linux-phy, linux-arm-kernel, linux-rockchip,
dri-devel
In-Reply-To: <e1b172bd-443f-40a5-9d9e-9e575b0b551e@rock-chips.com>
On 1/26/2026 11:22 AM, Chaoyi Chen wrote:
> Hello,
>
> On 1/26/2026 3:42 AM, Hugh Cole-Baker wrote:
>> On 19/01/2026 07:30, Chaoyi Chen wrote:
>>> From: Chaoyi Chen <chaoyi.chen@rock-chips.com>
>>>
>>> This series focuses on adding Type-C DP support for USBDP PHY and DP
>>> driver. The USBDP PHY and DP will perceive the changes in cable status
>>> based on the USB PD and Type-C state machines provided by TCPM. Before
>>> this, the USBDP PHY and DP controller of RK3399 sensed cable state
>>> changes through extcon, and devices such as the RK3399 Gru-Chromebook
>>> rely on them. This series should not break them.
>>>
>>> ====
>>> 1. DisplayPort HPD status notify
>>>
>>> Before v7, I implemented a variety of DP HPD status notify. However,
>>> they all had various problems and it was difficult to become a generic
>>> solution.
>>>
>>> Under the guidance of Heikki and Dmitry, a decoupled notification
>>> method between the TypeC and DRM subsystems was introduced in v7.
>>> First, a notification is sent when TypeC registers a new altmode.
>>> Then, a generic DP AUX HPD bridge is implemented on the DRM side.
>>>
>>> During v7-v10, we added a new notifier in typec to notify the altmode
>>> device register event. With the help of Greg and Heikki, we implemented
>>> the reuse of notifiers for the type bus itself in patch1 of v11.
>>>
>>> The USB subsystem related parts have already been merged into the
>>> usb-next branch in v13 [0][1]. Therefore, this series no longer includes
>>> these patches starting from v14. Thanks to Greg and Heikki!
>>>
>>> [0]: https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git/commit/?h=usb-next&id=67ab45426215c7fdccb65aecd4cac15bbe4dfcbb
>>> [1]: https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git/commit/?h=usb-next&id=4dee13db29de6dd869af9b3827e1ff569644e838
>>>
>>> That makes it redundant for each Type-C controller driver to implement
>>> a similar DP AUX HPD bridge in embedded scenarios.
>>>
>>> ====
>>> 2. Altmode switching and orientation switching for USBDP PHY
>>>
>>> For USB Type-C interfaces, an external Type-C controller chip assists
>>> by detecting cable attachment, determining plug orientation, and
>>> reporting USB PD message. The USB/DP combo PHY supports software
>>> configurable pin mapping and DisplayPort lane assignment. Based on
>>> these message, the combo PHY can perform both altmode switching and
>>> orientation switching via software.
>>>
>>> The RK3399 EVB IND board has a Type-C interface DisplayPort. It use
>>> fusb302 chip as Type-C controller. The connection diagram is shown below:
>>>
>>> fusb302 chip +---> USB2.0 PHY ----> DWC3 USB controller
>>> |
>>> +---> USB/DP PHY0 +--> CDN-DP controller
>>> |
>>> +--> DWC3 USB controller
>>>
>>> ====
>>> 3. Multiple bridge model for RK3399 CDN-DP
>>>
>>> The RK3399 has two USB/DP combo PHY and one CDN-DP controller. And
>>> the CDN-DP can be switched to output to one of the PHYs.
>>>
>>> USB/DP PHY0 ---+
>>> | <----> CDN-DP controller
>>> USB/DP PHY1 ---+
>>>
>>> In previous versions, if both PHY ports were connected to DP,
>>> the CDN-DP driver would select the first PHY port for output.
>>>
>>> On Dmitry's suggestion, we introduced a multi-bridge model to support
>>> flexible selection of the output PHY port. For each PHY port, a
>>> separate encoder and bridge are registered.
>>>
>>> The change is based on the DRM AUX HPD bridge, rather than the
>>> extcon approach. This requires the DT to correctly describe the
>>> connections between the first bridge in bridge chain and DP
>>> controller. And Once the first bridge is obtained, we can get the
>>> last bridge corresponding to the USB-C connector, and then set the
>>> DRM connector's fwnode to the corresponding one to enable HPD
>>> notification.
>>
>> With a similar dts patch [1] on top of this series I tested a type-C to
>> DP adapter/cable for display output on the ROCKPro64 board, which also
>> pairs a FUSB302 with RK3399. Booting it up with the cable plugged in
>> works, as does hotplugging the cable after booting in both orientations.
>> The correct mode for the display is detected. I wasn't able to test
>> audio, only video output, as this display doesn't have speakers.
>>
>> I did once, after unplugging and reconnecting the cable a few times,
>> see it get into a state where it didn't detect the attached display.
>> Logs from that unplug/reconnect attempt are here [2] if of interest.
>> Nevertheless, hotplug seems to work the majority of the time, so
>>
>> Tested-by: Hugh Cole-Baker <sigmaris@gmail.com>
>>
>> [1]: https://github.com/sigmaris/linux/commit/91724088b19bee7d248946442a801423e8cd0634
>> [2]: https://gist.github.com/sigmaris/fa107384a7492583ceee1c2962f5030a
>>
>
> Thank you for the test. I also have the same board, and I will
> try it later :)
>
I just tested on the ROCKPro64 board using a Type-C to HDMI dock,
and after repeatedly plugging and unplugging it over 30 times,
it performed correctly every time :)
--
Best,
Chaoyi
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* RE: [PATCH 1/8] dt-bindings: pci: xilinx-nwl: Add resets
From: Pandey, Radhey Shyam @ 2026-02-04 8:32 UTC (permalink / raw)
To: Sean Anderson, Laurent Pinchart, Vinod Koul,
linux-phy@lists.infradead.org
Cc: Krzysztof Wilczyński, Lorenzo Pieralisi,
linux-kernel@vger.kernel.org, Simek, Michal,
linux-arm-kernel@lists.infradead.org, linux-pci@vger.kernel.org,
Neil Armstrong, Rob Herring, Havalige, Thippeswamy,
Manivannan Sadhasivam, Bjorn Helgaas, Conor Dooley,
Krzysztof Kozlowski, devicetree@vger.kernel.org
In-Reply-To: <20260203002128.935842-2-sean.anderson@linux.dev>
[AMD Official Use Only - AMD Internal Distribution Only]
> -----Original Message-----
> From: Sean Anderson <sean.anderson@linux.dev>
> Sent: Tuesday, February 3, 2026 5:51 AM
> To: Laurent Pinchart <laurent.pinchart@ideasonboard.com>; Vinod Koul
> <vkoul@kernel.org>; linux-phy@lists.infradead.org
> Cc: Krzysztof Wilczyński <kwilczynski@kernel.org>; Lorenzo Pieralisi
> <lpieralisi@kernel.org>; Pandey, Radhey Shyam
> <radhey.shyam.pandey@amd.com>; linux-kernel@vger.kernel.org; Simek, Michal
> <michal.simek@amd.com>; linux-arm-kernel@lists.infradead.org; linux-
> pci@vger.kernel.org; Neil Armstrong <neil.armstrong@linaro.org>; Rob Herring
> <robh@kernel.org>; Havalige, Thippeswamy <thippeswamy.havalige@amd.com>;
> Manivannan Sadhasivam <mani@kernel.org>; Bjorn Helgaas
> <bhelgaas@google.com>; Sean Anderson <sean.anderson@linux.dev>; Conor
> Dooley <conor+dt@kernel.org>; Krzysztof Kozlowski <krzk+dt@kernel.org>;
> devicetree@vger.kernel.org
> Subject: [PATCH 1/8] dt-bindings: pci: xilinx-nwl: Add resets
>
> Add resets so we can hold the bridge in reset while we perform phy calibration.
Seems like this should a required property?
Rest looks fine to me.
>
> Signed-off-by: Sean Anderson <sean.anderson@linux.dev>
> ---
>
> .../devicetree/bindings/pci/xlnx,nwl-pcie.yaml | 17 +++++++++++++++++
> 1 file changed, 17 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml
> b/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml
> index 9de3c09efb6e..7efb3dd9955f 100644
> --- a/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml
> +++ b/Documentation/devicetree/bindings/pci/xlnx,nwl-pcie.yaml
> @@ -69,6 +69,18 @@ properties:
> power-domains:
> maxItems: 1
>
> + resets:
> + maxItems: 3
> +
> + reset-names:
> + items:
> + - description: APB register block reset
> + const: cfg
> + - description: AXI-PCIe bridge reset
> + const: bridge
> + - description: PCIe MAC reset
> + const: ctrl
> +
> iommus:
> maxItems: 1
>
> @@ -117,6 +129,7 @@ examples:
> #include <dt-bindings/interrupt-controller/irq.h>
> #include <dt-bindings/phy/phy.h>
> #include <dt-bindings/power/xlnx-zynqmp-power.h>
> + #include <dt-bindings/reset/xlnx-zynqmp-resets.h>
> soc {
> #address-cells = <2>;
> #size-cells = <2>;
> @@ -146,6 +159,10 @@ examples:
> msi-parent = <&nwl_pcie>;
> phys = <&psgtr 0 PHY_TYPE_PCIE 0 0>;
> power-domains = <&zynqmp_firmware PD_PCIE>;
> + resets = <&zynqmp_reset ZYNQMP_RESET_PCIE_CFG>,
> + <&zynqmp_reset ZYNQMP_RESET_PCIE_BRIDGE>,
> + <&zynqmp_reset ZYNQMP_RESET_PCIE_CTRL>;
> + reset-names = "cfg", "bridge", "ctrl";
> iommus = <&smmu 0x4d0>;
> pcie_intc: legacy-interrupt-controller {
> interrupt-controller;
> --
> 2.35.1.1320.gc452695387.dirty
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH 2/7] phy: rockchip: usbdp: Fix LFPS detect threshold control
From: Sebastian Reichel @ 2026-02-03 18:41 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner
Cc: Andy Yan, Yubing Zhang, linux-phy, linux-arm-kernel,
linux-rockchip, linux-kernel, kernel, Sebastian Reichel,
William Wu
In-Reply-To: <20260203-rockchip-usbdp-cleanup-v1-0-16a6f92ed176@collabora.com>
From: William Wu <william.wu@rock-chips.com>
According to the LFPS Tx Low Power/LFPS Rx Detect Threshold [1],
the device under test(DUT) must not respond if LFPS below the
minimum LFPS Rx Detect Threshold 100mV. Test fail on Rockchip
platforms, because the default LFPS detect threshold is set to
65mV.
The USBDP PHY LFPS detect threshold voltage could be set to
30mV ~ 140mV, and since there could be 10-20% PVT variation,
we set LFPS detect threshold voltage to 110mV.
[1] https://compliance.usb.org/resources/LFPS_Rx_Tx_Low_Power_Compliance_Update_Rev5.pdf
Signed-off-by: William Wu <william.wu@rock-chips.com>
[Taken over from rockchip's kernel tree; the registers are not described
in the TRM]
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-usbdp.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c
index b494819be284..7e4142aef192 100644
--- a/drivers/phy/rockchip/phy-rockchip-usbdp.c
+++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c
@@ -413,7 +413,8 @@ static const struct reg_sequence rk_udphy_init_sequence[] = {
{0x0070, 0x7d}, {0x0074, 0x68},
{0x0af4, 0x1a}, {0x1af4, 0x1a},
{0x0440, 0x3f}, {0x10d4, 0x08},
- {0x20d4, 0x08}, {0x0024, 0x6e}
+ {0x20d4, 0x08}, {0x0024, 0x6e},
+ {0x09c0, 0x0a}, {0x19c0, 0x0a}
};
static inline int rk_udphy_grfreg_write(struct regmap *base,
--
2.51.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH 0/7] phy: rockchip: usbdp: Fixes, DP 1-lane support and cleanups
From: Sebastian Reichel @ 2026-02-03 18:41 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner
Cc: Andy Yan, Yubing Zhang, linux-phy, linux-arm-kernel,
linux-rockchip, linux-kernel, kernel, Sebastian Reichel,
Frank Wang, William Wu
As there are some issues with the DisplayPort support, I went
though Rockchip's BSP kernel tree and looked for fixes. I found
two small changes for the initial register setup, which do not
help with the DP issue but seem sensible in general. Afterwards
I added one more fix, which corrects an issue that effectively
results in USB-C adapters with combined USB3+DP capabilities
not being able to use the DP part.
Afterwards I added one more patch adding single-lane DP support
(found in Rockchip BSP kernel) and a couple of cleanups from my
side. These are logically independent from the fixes, but I put
all together in a single series because they need to be applied
in the right order to avoid conflicts.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
Frank Wang (1):
phy: rockchip: usbdp: Amend SSC modulation deviation
Sebastian Reichel (4):
phy: rockchip: usbdp: Add missing mode_change update
phy: rockchip: usbdp: Rename DP lane functions
phy: rockchip: usbdp: Use FIELD_PREP_WM16_CONST
phy: rockchip: usbdp: Cleanup DP lane selection function
William Wu (1):
phy: rockchip: usbdp: Fix LFPS detect threshold control
Zhang Yubing (1):
phy: rockchip: usbdp: Support single-lane DP
drivers/phy/rockchip/phy-rockchip-usbdp.c | 114 +++++++++++++-----------------
1 file changed, 48 insertions(+), 66 deletions(-)
---
base-commit: 18f7fcd5e69a04df57b563360b88be72471d6b62
change-id: 20260203-rockchip-usbdp-cleanup-5b59dfb561a3
Best regards,
--
Sebastian Reichel <sebastian.reichel@collabora.com>
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH 7/7] phy: rockchip: usbdp: Cleanup DP lane selection function
From: Sebastian Reichel @ 2026-02-03 18:41 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner
Cc: Andy Yan, Yubing Zhang, linux-phy, linux-arm-kernel,
linux-rockchip, linux-kernel, kernel, Sebastian Reichel
In-Reply-To: <20260203-rockchip-usbdp-cleanup-v1-0-16a6f92ed176@collabora.com>
Use FIELD_PREP_WM16() helpers to simplify the DP lane selection
logic.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-usbdp.c | 28 +++++++---------------------
1 file changed, 7 insertions(+), 21 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c
index 9291f56120f1..ebc11d06e5ea 100644
--- a/drivers/phy/rockchip/phy-rockchip-usbdp.c
+++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c
@@ -550,30 +550,16 @@ static void rk_udphy_usb_bvalid_enable(struct rk_udphy *udphy, u8 enable)
static void rk_udphy_dp_lane_select(struct rk_udphy *udphy)
{
const struct rk_udphy_cfg *cfg = udphy->cfgs;
- u32 value = 0;
-
- switch (udphy->dp_lanes) {
- case 4:
- value |= 3 << udphy->dp_lane_sel[3] * 2;
- value |= 2 << udphy->dp_lane_sel[2] * 2;
- fallthrough;
-
- case 2:
- value |= 1 << udphy->dp_lane_sel[1] * 2;
- fallthrough;
+ u32 value = FIELD_PREP_WM16(DP_LANE_SEL_ALL, 0);
+ int i;
- case 1:
- value |= 0 << udphy->dp_lane_sel[0] * 2;
- break;
+ for (i = 0; i < udphy->dp_lanes; i++)
+ value |= field_prep(DP_LANE_SEL_N(udphy->dp_lane_sel[i]), i);
- default:
- break;
- }
+ value |= FIELD_PREP_WM16(DP_AUX_DIN_SEL, udphy->dp_aux_din_sel);
+ value |= FIELD_PREP_WM16(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel);
- regmap_write(udphy->vogrf, cfg->vogrfcfg[udphy->id].dp_lane_reg,
- ((DP_AUX_DIN_SEL | DP_AUX_DOUT_SEL | DP_LANE_SEL_ALL) << 16) |
- FIELD_PREP(DP_AUX_DIN_SEL, udphy->dp_aux_din_sel) |
- FIELD_PREP(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel) | value);
+ regmap_write(udphy->vogrf, cfg->vogrfcfg[udphy->id].dp_lane_reg, value);
}
static void rk_udphy_dp_lane_enable(struct rk_udphy *udphy, int dp_lanes)
--
2.51.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH 5/7] phy: rockchip: usbdp: Rename DP lane functions
From: Sebastian Reichel @ 2026-02-03 18:41 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner
Cc: Andy Yan, Yubing Zhang, linux-phy, linux-arm-kernel,
linux-rockchip, linux-kernel, kernel, Sebastian Reichel
In-Reply-To: <20260203-rockchip-usbdp-cleanup-v1-0-16a6f92ed176@collabora.com>
The common prefix for DisplayPort related functions is rk_udphy_dp_
(with a final _), so update the two DP lane functions to follow that
scheme.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-usbdp.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c
index 29cf3f3bcec4..4388105dd8ce 100644
--- a/drivers/phy/rockchip/phy-rockchip-usbdp.c
+++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c
@@ -547,7 +547,7 @@ static void rk_udphy_usb_bvalid_enable(struct rk_udphy *udphy, u8 enable)
* ---------------------------------------------------------------------------
*/
-static void rk_udphy_dplane_select(struct rk_udphy *udphy)
+static void rk_udphy_dp_lane_select(struct rk_udphy *udphy)
{
const struct rk_udphy_cfg *cfg = udphy->cfgs;
u32 value = 0;
@@ -576,7 +576,7 @@ static void rk_udphy_dplane_select(struct rk_udphy *udphy)
FIELD_PREP(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel) | value);
}
-static void rk_udphy_dplane_enable(struct rk_udphy *udphy, int dp_lanes)
+static void rk_udphy_dp_lane_enable(struct rk_udphy *udphy, int dp_lanes)
{
u32 val = 0;
int i;
@@ -1074,9 +1074,9 @@ static int rk_udphy_dp_phy_power_on(struct phy *phy)
if (ret)
goto unlock;
- rk_udphy_dplane_enable(udphy, udphy->dp_lanes);
+ rk_udphy_dp_lane_enable(udphy, udphy->dp_lanes);
- rk_udphy_dplane_select(udphy);
+ rk_udphy_dp_lane_select(udphy);
unlock:
mutex_unlock(&udphy->mutex);
@@ -1094,7 +1094,7 @@ static int rk_udphy_dp_phy_power_off(struct phy *phy)
struct rk_udphy *udphy = phy_get_drvdata(phy);
mutex_lock(&udphy->mutex);
- rk_udphy_dplane_enable(udphy, 0);
+ rk_udphy_dp_lane_enable(udphy, 0);
rk_udphy_power_off(udphy, UDPHY_MODE_DP);
mutex_unlock(&udphy->mutex);
--
2.51.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH 6/7] phy: rockchip: usbdp: Use FIELD_PREP_WM16_CONST
From: Sebastian Reichel @ 2026-02-03 18:41 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner
Cc: Andy Yan, Yubing Zhang, linux-phy, linux-arm-kernel,
linux-rockchip, linux-kernel, kernel, Sebastian Reichel
In-Reply-To: <20260203-rockchip-usbdp-cleanup-v1-0-16a6f92ed176@collabora.com>
Cleanup code by replacing open-coded version of FIELD_PREP_WM16_CONST
with the existing helper macro.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-usbdp.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c
index 4388105dd8ce..9291f56120f1 100644
--- a/drivers/phy/rockchip/phy-rockchip-usbdp.c
+++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c
@@ -12,6 +12,7 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio.h>
+#include <linux/hw_bitfield.h>
#include <linux/mfd/syscon.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
@@ -75,7 +76,6 @@
#define TRSV_LN2_MON_RX_CDR_DONE_OFFSET 0x1b84 /* trsv_reg06E1 */
#define TRSV_LN2_MON_RX_CDR_LOCK_DONE BIT(0)
-#define BIT_WRITEABLE_SHIFT 16
#define PHY_AUX_DP_DATA_POL_NORMAL 0
#define PHY_AUX_DP_DATA_POL_INVERT 1
#define PHY_LANE_MUX_USB 0
@@ -104,8 +104,8 @@ struct rk_udphy_grf_reg {
#define _RK_UDPHY_GEN_GRF_REG(offset, mask, disable, enable) \
{\
offset, \
- FIELD_PREP_CONST(mask, disable) | (mask << BIT_WRITEABLE_SHIFT), \
- FIELD_PREP_CONST(mask, enable) | (mask << BIT_WRITEABLE_SHIFT), \
+ FIELD_PREP_WM16_CONST(mask, disable), \
+ FIELD_PREP_WM16_CONST(mask, enable), \
}
#define RK_UDPHY_GEN_GRF_REG(offset, bitend, bitstart, disable, enable) \
--
2.51.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH 1/7] phy: rockchip: usbdp: Amend SSC modulation deviation
From: Sebastian Reichel @ 2026-02-03 18:41 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner
Cc: Andy Yan, Yubing Zhang, linux-phy, linux-arm-kernel,
linux-rockchip, linux-kernel, kernel, Sebastian Reichel,
Frank Wang
In-Reply-To: <20260203-rockchip-usbdp-cleanup-v1-0-16a6f92ed176@collabora.com>
From: Frank Wang <frank.wang@rock-chips.com>
Move SSC modulation deviation into private config of clock
- 24M: 0x00d4[5:0] = 0x30
- 26M: 0x00d4[5:0] = 0x33
Signed-off-by: Frank Wang <frank.wang@rock-chips.com>
[Taken over from rockchip's kernel tree; register 0x00d4 is not
described in the TRM]
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-usbdp.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c
index fba35510d88c..b494819be284 100644
--- a/drivers/phy/rockchip/phy-rockchip-usbdp.c
+++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c
@@ -350,7 +350,8 @@ static const struct reg_sequence rk_udphy_24m_refclk_cfg[] = {
{0x0a64, 0xa8}, {0x1a3c, 0xd0},
{0x1a44, 0xd0}, {0x1a48, 0x01},
{0x1a4c, 0x0d}, {0x1a54, 0xe0},
- {0x1a5c, 0xe0}, {0x1a64, 0xa8}
+ {0x1a5c, 0xe0}, {0x1a64, 0xa8},
+ {0x00d4, 0x30}
};
static const struct reg_sequence rk_udphy_26m_refclk_cfg[] = {
@@ -377,7 +378,7 @@ static const struct reg_sequence rk_udphy_26m_refclk_cfg[] = {
{0x0c30, 0x0e}, {0x0c48, 0x06},
{0x1c30, 0x0e}, {0x1c48, 0x06},
{0x028c, 0x18}, {0x0af0, 0x00},
- {0x1af0, 0x00}
+ {0x1af0, 0x00}, {0x00d4, 0x33}
};
static const struct reg_sequence rk_udphy_init_sequence[] = {
@@ -412,8 +413,7 @@ static const struct reg_sequence rk_udphy_init_sequence[] = {
{0x0070, 0x7d}, {0x0074, 0x68},
{0x0af4, 0x1a}, {0x1af4, 0x1a},
{0x0440, 0x3f}, {0x10d4, 0x08},
- {0x20d4, 0x08}, {0x00d4, 0x30},
- {0x0024, 0x6e},
+ {0x20d4, 0x08}, {0x0024, 0x6e}
};
static inline int rk_udphy_grfreg_write(struct regmap *base,
--
2.51.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH 3/7] phy: rockchip: usbdp: Add missing mode_change update
From: Sebastian Reichel @ 2026-02-03 18:41 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner
Cc: Andy Yan, Yubing Zhang, linux-phy, linux-arm-kernel,
linux-rockchip, linux-kernel, kernel, Sebastian Reichel
In-Reply-To: <20260203-rockchip-usbdp-cleanup-v1-0-16a6f92ed176@collabora.com>
rk_udphy_set_typec_default_mapping() updates the available modes,
but does not set the mode_change as required. This results in
missing re-initialization and thus non-working DisplayPort.
Fix this issue by introducing a new helper to update the available
modes.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-usbdp.c | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c
index 7e4142aef192..5304e2d86d11 100644
--- a/drivers/phy/rockchip/phy-rockchip-usbdp.c
+++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c
@@ -619,6 +619,15 @@ static void rk_udphy_dp_hpd_event_trigger(struct rk_udphy *udphy, bool hpd)
rk_udphy_grfreg_write(udphy->vogrf, &cfg->vogrfcfg[udphy->id].hpd_trigger, hpd);
}
+static void rk_udphy_mode_set(struct rk_udphy *udphy, u8 mode)
+{
+ if (udphy->mode == mode)
+ return;
+
+ udphy->mode_change = true;
+ udphy->mode = mode;
+}
+
static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy)
{
if (udphy->flip) {
@@ -649,7 +658,7 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy)
gpiod_set_value_cansleep(udphy->sbu2_dc_gpio, 1);
}
- udphy->mode = UDPHY_MODE_DP_USB;
+ rk_udphy_mode_set(udphy, UDPHY_MODE_DP_USB);
}
static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw,
@@ -1387,10 +1396,7 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux,
usleep_range(750, 800);
rk_udphy_dp_hpd_event_trigger(udphy, true);
} else if (data->status & DP_STATUS_HPD_STATE) {
- if (udphy->mode != mode) {
- udphy->mode = mode;
- udphy->mode_change = true;
- }
+ rk_udphy_mode_set(udphy, mode);
rk_udphy_dp_hpd_event_trigger(udphy, true);
} else {
rk_udphy_dp_hpd_event_trigger(udphy, false);
--
2.51.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH 4/7] phy: rockchip: usbdp: Support single-lane DP
From: Sebastian Reichel @ 2026-02-03 18:41 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner
Cc: Andy Yan, Yubing Zhang, linux-phy, linux-arm-kernel,
linux-rockchip, linux-kernel, kernel, Sebastian Reichel
In-Reply-To: <20260203-rockchip-usbdp-cleanup-v1-0-16a6f92ed176@collabora.com>
From: Zhang Yubing <yubing.zhang@rock-chips.com>
Implement support for using just a single DisplayPort line.
Signed-off-by: Zhang Yubing <yubing.zhang@rock-chips.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-usbdp.c | 61 +++++++++++++------------------
1 file changed, 25 insertions(+), 36 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-usbdp.c b/drivers/phy/rockchip/phy-rockchip-usbdp.c
index 5304e2d86d11..29cf3f3bcec4 100644
--- a/drivers/phy/rockchip/phy-rockchip-usbdp.c
+++ b/drivers/phy/rockchip/phy-rockchip-usbdp.c
@@ -193,6 +193,7 @@ struct rk_udphy {
int id;
bool dp_in_use;
+ int dp_lanes;
/* PHY const config */
const struct rk_udphy_cfg *cfgs;
@@ -537,6 +538,13 @@ static void rk_udphy_usb_bvalid_enable(struct rk_udphy *udphy, u8 enable)
* <0 1> dpln0 dpln1 usbrx usbtx
* <2 3> usbrx usbtx dpln0 dpln1
* ---------------------------------------------------------------------------
+ * if 1 lane for dp function, 2 lane for usb function, define rockchip,dp-lane-mux = <x>;
+ * sample as follow:
+ * ---------------------------------------------------------------------------
+ * B11-B10 A2-A3 A11-A10 B2-B3
+ * rockchip,dp-lane-mux ln0(tx/rx) ln1(tx) ln2(tx/rx) ln3(tx)
+ * <0> dpln0 \ usbrx usbtx
+ * ---------------------------------------------------------------------------
*/
static void rk_udphy_dplane_select(struct rk_udphy *udphy)
@@ -544,18 +552,18 @@ static void rk_udphy_dplane_select(struct rk_udphy *udphy)
const struct rk_udphy_cfg *cfg = udphy->cfgs;
u32 value = 0;
- switch (udphy->mode) {
- case UDPHY_MODE_DP:
- value |= 2 << udphy->dp_lane_sel[2] * 2;
+ switch (udphy->dp_lanes) {
+ case 4:
value |= 3 << udphy->dp_lane_sel[3] * 2;
+ value |= 2 << udphy->dp_lane_sel[2] * 2;
fallthrough;
- case UDPHY_MODE_DP_USB:
- value |= 0 << udphy->dp_lane_sel[0] * 2;
+ case 2:
value |= 1 << udphy->dp_lane_sel[1] * 2;
- break;
+ fallthrough;
- case UDPHY_MODE_USB:
+ case 1:
+ value |= 0 << udphy->dp_lane_sel[0] * 2;
break;
default:
@@ -568,28 +576,6 @@ static void rk_udphy_dplane_select(struct rk_udphy *udphy)
FIELD_PREP(DP_AUX_DOUT_SEL, udphy->dp_aux_dout_sel) | value);
}
-static int rk_udphy_dplane_get(struct rk_udphy *udphy)
-{
- int dp_lanes;
-
- switch (udphy->mode) {
- case UDPHY_MODE_DP:
- dp_lanes = 4;
- break;
-
- case UDPHY_MODE_DP_USB:
- dp_lanes = 2;
- break;
-
- case UDPHY_MODE_USB:
- default:
- dp_lanes = 0;
- break;
- }
-
- return dp_lanes;
-}
-
static void rk_udphy_dplane_enable(struct rk_udphy *udphy, int dp_lanes)
{
u32 val = 0;
@@ -659,6 +645,7 @@ static void rk_udphy_set_typec_default_mapping(struct rk_udphy *udphy)
}
rk_udphy_mode_set(udphy, UDPHY_MODE_DP_USB);
+ udphy->dp_lanes = 2;
}
static int rk_udphy_orien_sw_set(struct typec_switch_dev *sw,
@@ -897,7 +884,7 @@ static int rk_udphy_parse_lane_mux_data(struct rk_udphy *udphy)
return 0;
}
- if (num_lanes != 2 && num_lanes != 4)
+ if (num_lanes != 1 && num_lanes != 2 && num_lanes != 4)
return dev_err_probe(udphy->dev, -EINVAL,
"invalid number of lane mux\n");
@@ -923,7 +910,8 @@ static int rk_udphy_parse_lane_mux_data(struct rk_udphy *udphy)
}
udphy->mode = UDPHY_MODE_DP;
- if (num_lanes == 2) {
+ udphy->dp_lanes = num_lanes;
+ if (num_lanes == 1 || num_lanes == 2) {
udphy->mode |= UDPHY_MODE_USB;
udphy->flip = (udphy->lane_mux_sel[0] == PHY_LANE_MUX_DP);
}
@@ -1076,18 +1064,17 @@ static int rk_udphy_dp_phy_exit(struct phy *phy)
static int rk_udphy_dp_phy_power_on(struct phy *phy)
{
struct rk_udphy *udphy = phy_get_drvdata(phy);
- int ret, dp_lanes;
+ int ret;
mutex_lock(&udphy->mutex);
- dp_lanes = rk_udphy_dplane_get(udphy);
- phy_set_bus_width(phy, dp_lanes);
+ phy_set_bus_width(phy, udphy->dp_lanes);
ret = rk_udphy_power_on(udphy, UDPHY_MODE_DP);
if (ret)
goto unlock;
- rk_udphy_dplane_enable(udphy, dp_lanes);
+ rk_udphy_dplane_enable(udphy, udphy->dp_lanes);
rk_udphy_dplane_select(udphy);
@@ -1367,6 +1354,7 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux,
udphy->lane_mux_sel[2] = PHY_LANE_MUX_DP;
udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP;
mode = UDPHY_MODE_DP;
+ udphy->dp_lanes = 4;
break;
case TYPEC_DP_STATE_D:
@@ -1383,6 +1371,7 @@ static int rk_udphy_typec_mux_set(struct typec_mux_dev *mux,
udphy->lane_mux_sel[3] = PHY_LANE_MUX_DP;
}
mode = UDPHY_MODE_DP_USB;
+ udphy->dp_lanes = 2;
break;
}
@@ -1531,7 +1520,7 @@ static int rk_udphy_probe(struct platform_device *pdev)
ret = PTR_ERR(udphy->phy_dp);
return dev_err_probe(dev, ret, "failed to create DP phy\n");
}
- phy_set_bus_width(udphy->phy_dp, rk_udphy_dplane_get(udphy));
+ phy_set_bus_width(udphy->phy_dp, udphy->dp_lanes);
udphy->phy_dp->attrs.max_link_rate = 8100;
phy_set_drvdata(udphy->phy_dp, udphy);
--
2.51.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH 3/4 v2] phy: s32g: Add serdes xpcs subsystem
From: Vincent Guittot @ 2026-02-03 16:19 UTC (permalink / raw)
To: vkoul, neil.armstrong, krzk+dt, conor+dt, ciprianmarian.costea,
s32, p.zabel, linux, ghennadi.procopciuc, Ionut.Vicovan,
linux-phy, devicetree, linux-kernel, linux-arm-kernel, netdev,
horms
Cc: Frank.li
In-Reply-To: <20260203161917.1666696-1-vincent.guittot@linaro.org>
s32g SoC family includes 2 serdes subsystems which are made of one PCIe
controller, 2 XPCS and one Phy. The Phy got 2 lanes that can be configure
to output PCIe lanes and/or SGMII.
Add XPCS and SGMII support.
Co-developed-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
Co-developed-by: Alexandru-Catalin Ionita <alexandru-catalin.ionita@nxp.com>
Signed-off-by: Alexandru-Catalin Ionita <alexandru-catalin.ionita@nxp.com>
Co-developed-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
Signed-off-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
Co-developed-by: Ionut Vicovan <Ionut.Vicovan@nxp.com>
Signed-off-by: Ionut Vicovan <Ionut.Vicovan@nxp.com>
Co-developed-by: Bogdan Roman <bogdan-gabriel.roman@nxp.com>
Signed-off-by: Bogdan Roman <bogdan-gabriel.roman@nxp.com>
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
drivers/net/pcs/Makefile | 1 +
drivers/net/pcs/pcs-nxp-s32g-xpcs.c | 1006 +++++++++++++++++++
drivers/phy/freescale/Kconfig | 1 +
drivers/phy/freescale/phy-nxp-s32g-serdes.c | 374 ++++++-
include/linux/pcs/pcs-nxp-s32g-xpcs.h | 50 +
5 files changed, 1430 insertions(+), 2 deletions(-)
create mode 100644 drivers/net/pcs/pcs-nxp-s32g-xpcs.c
create mode 100644 include/linux/pcs/pcs-nxp-s32g-xpcs.h
diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
index 4f7920618b90..55107cbaa6d5 100644
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
+obj-$(CONFIG_PHY_S32G_SERDES) += pcs-nxp-s32g-xpcs.o
diff --git a/drivers/net/pcs/pcs-nxp-s32g-xpcs.c b/drivers/net/pcs/pcs-nxp-s32g-xpcs.c
new file mode 100644
index 000000000000..5636440ed38d
--- /dev/null
+++ b/drivers/net/pcs/pcs-nxp-s32g-xpcs.c
@@ -0,0 +1,1006 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * Copyright 2021-2026 NXP
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/pcs/pcs-nxp-s32g-xpcs.h>
+#include <linux/processor.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#define XPCS_TIMEOUT_MS 300
+
+#define ADDR1_OFS 0x3FC
+
+#define SR_MII_CTRL 0x1F0000
+#define SR_RST BIT(15)
+#define SS13 BIT(13)
+#define AN_ENABLE BIT(12)
+#define RESTART_AN BIT(9)
+#define DUPLEX_MODE BIT(8)
+#define SS6 BIT(6)
+#define SR_MII_STS 0x1F0001
+#define LINK_STS BIT(2)
+#define AN_ABL BIT(3)
+#define AN_CMPL BIT(5)
+#define SR_MII_DEV_ID1 0x1F0002
+#define SR_MII_DEV_ID2 0x1F0003
+#define SR_MII_EXT_STS 0x1F000F
+#define CAP_1G_T_FD BIT(13)
+#define CAP_1G_T_HD BIT(12)
+#define VR_MII_DIG_CTRL1 0x1F8000
+#define BYP_PWRUP BIT(1)
+#define EN_2_5G_MODE BIT(2)
+#define CL37_TMR_OVRRIDE BIT(3)
+#define INIT BIT(8)
+#define MAC_AUTO_SW BIT(9)
+#define CS_EN BIT(10)
+#define PWRSV BIT(11)
+#define EN_VSMMD1 BIT(13)
+#define R2TLBE BIT(14)
+#define VR_RST BIT(15)
+#define VR_MII_AN_CTRL 0x1F8001
+#define MII_AN_INTR_EN BIT(0)
+#define PCS_MODE_MASK GENMASK(2, 1)
+#define PCS_MODE_SGMII 2
+#define MII_CTRL BIT(8)
+#define VR_MII_AN_INTR_STS 0x1F8002
+#define CL37_ANCMPLT_INTR BIT(0)
+#define CL37_ANSGM_STS_DUPLEX BIT(1)
+#define CL37_ANSGM_STS_SPEED_MASK GENMASK(3, 2)
+#define CL37_ANSGM_10MBPS 0
+#define CL37_ANSGM_100MBPS 1
+#define CL37_ANSGM_1000MBPS 2
+#define CL37_ANSGM_STS_LINK BIT(4)
+#define VR_MII_DBG_CTRL 0x1F8005
+#define SUPPRESS_LOS_DET BIT(4)
+#define RX_DT_EN_CTL BIT(6)
+#define VR_MII_LINK_TIMER_CTRL 0x1F800A
+#define VR_MII_DIG_STS 0x1F8010
+#define PSEQ_STATE_MASK GENMASK(4, 2)
+#define POWER_GOOD_STATE 0x4
+#define VR_MII_GEN5_12G_16G_TX_GENCTRL1 0x1F8031
+#define VBOOST_EN_0 BIT(4)
+#define TX_CLK_RDY_0 BIT(12)
+#define VR_MII_GEN5_12G_16G_TX_GENCTRL2 0x1F8032
+#define TX_REQ_0 BIT(0)
+#define VR_MII_GEN5_12G_16G_TX_RATE_CTRL 0x1F8034
+#define TX0_RATE_MASK GENMASK(2, 0)
+#define TX0_BAUD_DIV_1 0
+#define TX0_BAUD_DIV_4 2
+#define VR_MII_GEN5_12G_16G_TX_EQ_CTRL0 0x1F8036
+#define TX_EQ_MAIN_MASK GENMASK(13, 8)
+#define VR_MII_GEN5_12G_16G_TX_EQ_CTRL1 0x1F8037
+#define TX_EQ_OVR_RIDE BIT(6)
+#define VR_MII_CONSUMER_10G_TX_TERM_CTRL 0x1F803C
+#define TX0_TERM_MASK GENMASK(2, 0)
+#define VR_MII_GEN5_12G_16G_RX_GENCTRL1 0x1F8051
+#define RX_RST_0 BIT(4)
+#define VR_MII_GEN5_12G_16G_RX_GENCTRL2 0x1F8052
+#define RX_REQ_0 BIT(0)
+#define VR_MII_GEN5_12G_16G_RX_RATE_CTRL 0x1F8054
+#define RX0_RATE_MASK GENMASK(1, 0)
+#define RX0_BAUD_DIV_2 0x1
+#define RX0_BAUD_DIV_8 0x3
+#define VR_MII_GEN5_12G_16G_CDR_CTRL 0x1F8056
+#define CDR_SSC_EN_0 BIT(4)
+#define VCO_LOW_FREQ_0 BIT(8)
+#define VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL 0x1F8070
+#define MPLLB_SEL_0 BIT(4)
+#define VR_MII_GEN5_12G_16G_MPLLA_CTRL0 0x1F8071
+#define MPLLA_CAL_DISABLE BIT(15)
+#define MLLA_MULTIPLIER_MASK GENMASK(7, 0)
+#define VR_MII_GEN5_12G_MPLLA_CTRL1 0x1F8072
+#define MPLLA_FRACN_CTRL_MASK GENMASK(15, 5)
+#define VR_MII_GEN5_12G_16G_MPLLA_CTRL2 0x1F8073
+#define MPLLA_TX_CLK_DIV_MASK GENMASK(13, 11)
+#define MPLLA_DIV10_CLK_EN BIT(9)
+#define VR_MII_GEN5_12G_16G_MPLLB_CTRL0 0x1F8074
+#define MPLLB_CAL_DISABLE BIT(15)
+#define MLLB_MULTIPLIER_OFF 0
+#define MLLB_MULTIPLIER_MASK GENMASK(7, 0)
+#define VR_MII_GEN5_12G_MPLLB_CTRL1 0x1F8075
+#define MPLLB_FRACN_CTRL_MASK GENMASK(15, 5)
+#define VR_MII_GEN5_12G_16G_MPLLB_CTRL2 0x1F8076
+#define MPLLB_TX_CLK_DIV_MASK GENMASK(13, 11)
+#define MPLLB_DIV10_CLK_EN BIT(9)
+#define VR_MII_RX_LSTS 0x1F8020
+#define RX_VALID_0 BIT(12)
+#define VR_MII_GEN5_12G_MPLLA_CTRL3 0x1F8077
+#define MPLLA_BANDWIDTH_MASK GENMASK(15, 0)
+#define VR_MII_GEN5_12G_MPLLB_CTRL3 0x1F8078
+#define MPLLB_BANDWIDTH_MASK GENMASK(15, 0)
+#define VR_MII_GEN5_12G_16G_MISC_CTRL0 0x1F8090
+#define PLL_CTRL BIT(15)
+#define VR_MII_GEN5_12G_16G_REF_CLK_CTRL 0x1F8091
+#define REF_CLK_EN BIT(0)
+#define REF_USE_PAD BIT(1)
+#define REF_CLK_DIV2 BIT(2)
+#define REF_RANGE_MASK GENMASK(5, 3)
+#define RANGE_26_53_MHZ 0x1
+#define RANGE_52_78_MHZ 0x2
+#define RANGE_78_104_MHZ 0x3
+#define REF_MPLLA_DIV2 BIT(6)
+#define REF_MPLLB_DIV2 BIT(7)
+#define REF_RPT_CLK_EN BIT(8)
+#define VR_MII_GEN5_12G_16G_VCO_CAL_LD0 0x1F8092
+#define VCO_LD_VAL_0_MASK GENMASK(12, 0)
+#define VR_MII_GEN5_12G_VCO_CAL_REF0 0x1F8096
+#define VCO_REF_LD_0_MASK GENMASK(5, 0)
+
+typedef bool (*xpcs_poll_func_t)(struct s32g_xpcs *);
+
+/*
+ * XPCS registers can't be accessed directly and an indirect address method
+ * must be used instead.
+ */
+
+static const struct regmap_range s32g_xpcs_wr_ranges[] = {
+ regmap_reg_range(0x1F0000, 0x1F0000),
+ regmap_reg_range(0x1F0004, 0x1F0004),
+ regmap_reg_range(0x1F8000, 0x1F8003),
+ regmap_reg_range(0x1F8005, 0x1F8005),
+ regmap_reg_range(0x1F800A, 0x1F800A),
+ regmap_reg_range(0x1F8012, 0x1F8012),
+ regmap_reg_range(0x1F8015, 0x1F8015),
+ regmap_reg_range(0x1F8030, 0x1F8037),
+ regmap_reg_range(0x1F803C, 0x1F803E),
+ regmap_reg_range(0x1F8050, 0x1F8058),
+ regmap_reg_range(0x1F805C, 0x1F805E),
+ regmap_reg_range(0x1F8064, 0x1F8064),
+ regmap_reg_range(0x1F806B, 0x1F806B),
+ regmap_reg_range(0x1F8070, 0x1F8078),
+ regmap_reg_range(0x1F8090, 0x1F8092),
+ regmap_reg_range(0x1F8096, 0x1F8096),
+ regmap_reg_range(0x1F8099, 0x1F8099),
+ regmap_reg_range(0x1F80A0, 0x1F80A2),
+ regmap_reg_range(0x1F80E1, 0x1F80E1),
+};
+
+static const struct regmap_access_table s32g_xpcs_wr_table = {
+ .yes_ranges = s32g_xpcs_wr_ranges,
+ .n_yes_ranges = ARRAY_SIZE(s32g_xpcs_wr_ranges),
+};
+
+static const struct regmap_range s32g_xpcs_rd_ranges[] = {
+ regmap_reg_range(0x1F0000, 0x1F0006),
+ regmap_reg_range(0x1F000F, 0x1F000F),
+ regmap_reg_range(0x1F0708, 0x1F0710),
+ regmap_reg_range(0x1F8000, 0x1F8003),
+ regmap_reg_range(0x1F8005, 0x1F8005),
+ regmap_reg_range(0x1F800A, 0x1F800A),
+ regmap_reg_range(0x1F8010, 0x1F8012),
+ regmap_reg_range(0x1F8015, 0x1F8015),
+ regmap_reg_range(0x1F8018, 0x1F8018),
+ regmap_reg_range(0x1F8020, 0x1F8020),
+ regmap_reg_range(0x1F8030, 0x1F8037),
+ regmap_reg_range(0x1F803C, 0x1F803C),
+ regmap_reg_range(0x1F8040, 0x1F8040),
+ regmap_reg_range(0x1F8050, 0x1F8058),
+ regmap_reg_range(0x1F805C, 0x1F805E),
+ regmap_reg_range(0x1F8060, 0x1F8060),
+ regmap_reg_range(0x1F8064, 0x1F8064),
+ regmap_reg_range(0x1F806B, 0x1F806B),
+ regmap_reg_range(0x1F8070, 0x1F8078),
+ regmap_reg_range(0x1F8090, 0x1F8092),
+ regmap_reg_range(0x1F8096, 0x1F8096),
+ regmap_reg_range(0x1F8098, 0x1F8099),
+ regmap_reg_range(0x1F80A0, 0x1F80A2),
+ regmap_reg_range(0x1F80E1, 0x1F80E1),
+};
+
+static const struct regmap_access_table s32g_xpcs_rd_table = {
+ .yes_ranges = s32g_xpcs_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(s32g_xpcs_rd_ranges),
+};
+
+static int s32g_xpcs_regmap_reg_read(void *context, unsigned int reg,
+ unsigned int *result)
+{
+ struct s32g_xpcs *xpcs = context;
+ u16 ofsleft = (reg >> 8) & 0xffffU;
+ u16 ofsright = (reg & 0xffU);
+
+ writew(ofsleft, xpcs->base + ADDR1_OFS);
+ *result = readw(xpcs->base + (ofsright * 4));
+
+ return 0;
+}
+
+static int s32g_xpcs_regmap_reg_write(void *context, unsigned int reg,
+ unsigned int val)
+{
+ struct s32g_xpcs *xpcs = context;
+ u16 ofsleft = (reg >> 8) & 0xffffU;
+ u16 ofsright = (reg & 0xffU);
+
+ writew(ofsleft, xpcs->base + ADDR1_OFS);
+ writew(val, xpcs->base + (ofsright * 4));
+
+ return 0;
+}
+
+static const struct regmap_config s32g_xpcs_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 16,
+ .reg_read = s32g_xpcs_regmap_reg_read,
+ .reg_write = s32g_xpcs_regmap_reg_write,
+ .wr_table = &s32g_xpcs_wr_table,
+ .rd_table = &s32g_xpcs_rd_table,
+ .max_register = 0x1F80E1,
+};
+
+static void s32g_xpcs_write_bits(struct s32g_xpcs *xpcs, unsigned int reg,
+ unsigned int mask, unsigned int value)
+{
+ int ret = regmap_write_bits(xpcs->regmap, reg, mask, value);
+
+ if (ret)
+ dev_err(xpcs->dev, "Failed to write bits of XPCS reg: 0x%x\n", reg);
+}
+
+static void s32g_xpcs_write(struct s32g_xpcs *xpcs, unsigned int reg,
+ unsigned int value)
+{
+ int ret = regmap_write(xpcs->regmap, reg, value);
+
+ if (ret)
+ dev_err(xpcs->dev, "Failed to write XPCS reg: 0x%x\n", reg);
+}
+
+static unsigned int s32g_xpcs_read(struct s32g_xpcs *xpcs, unsigned int reg)
+{
+ unsigned int val = 0;
+ int ret;
+
+ ret = regmap_read(xpcs->regmap, reg, &val);
+ if (ret)
+ dev_err(xpcs->dev, "Failed to read XPCS reg: 0x%x\n", reg);
+
+ return val;
+}
+
+/*
+ * Internal XPCS function
+ */
+
+static bool s32g_xpcs_poll_timeout(struct s32g_xpcs *xpcs, xpcs_poll_func_t func,
+ ktime_t timeout)
+{
+ ktime_t cur = ktime_get();
+
+ return func(xpcs) || ktime_after(cur, timeout);
+}
+
+static int s32g_xpcs_wait(struct s32g_xpcs *xpcs, xpcs_poll_func_t func)
+{
+ ktime_t timeout = ktime_add_ms(ktime_get(), XPCS_TIMEOUT_MS);
+
+ spin_until_cond(s32g_xpcs_poll_timeout(xpcs, func, timeout));
+ if (!func(xpcs))
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int s32g_xpcs_wait_bits(struct s32g_xpcs *xpcs, unsigned int reg,
+ unsigned int mask, unsigned int bits)
+{
+ ktime_t cur;
+ ktime_t timeout = ktime_add_ms(ktime_get(), XPCS_TIMEOUT_MS);
+
+ spin_until_cond((cur = ktime_get(),
+ (s32g_xpcs_read(xpcs, reg) & mask) == bits ||
+ ktime_after(cur, timeout)));
+ if ((s32g_xpcs_read(xpcs, reg) & mask) != bits)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static unsigned int s32g_xpcs_digital_status(struct s32g_xpcs *xpcs)
+{
+ return s32g_xpcs_read(xpcs, VR_MII_DIG_STS);
+}
+
+static int s32g_xpcs_wait_power_good_state(struct s32g_xpcs *xpcs)
+{
+ unsigned int val;
+
+ return read_poll_timeout(s32g_xpcs_digital_status, val,
+ FIELD_GET(PSEQ_STATE_MASK, val) == POWER_GOOD_STATE,
+ 0,
+ XPCS_TIMEOUT_MS, false, xpcs);
+}
+
+void s32g_xpcs_vreset(struct s32g_xpcs *xpcs)
+{
+ /* Step 19 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, VR_RST, VR_RST);
+}
+
+static bool s32g_xpcs_is_not_in_reset(struct s32g_xpcs *xpcs)
+{
+ unsigned int val;
+
+ val = s32g_xpcs_read(xpcs, VR_MII_DIG_CTRL1);
+
+ return !(val & VR_RST);
+}
+
+int s32g_xpcs_wait_vreset(struct s32g_xpcs *xpcs)
+{
+ int ret;
+
+ /* Step 20 */
+ ret = s32g_xpcs_wait(xpcs, s32g_xpcs_is_not_in_reset);
+ if (ret)
+ dev_err(xpcs->dev, "XPCS%d is in reset\n", xpcs->id);
+
+ return ret;
+}
+
+int s32g_xpcs_reset_rx(struct s32g_xpcs *xpcs)
+{
+ int ret = 0;
+
+ ret = s32g_xpcs_wait_power_good_state(xpcs);
+ if (ret) {
+ dev_err(xpcs->dev, "Failed to enter in PGOOD state after vendor reset\n");
+ return ret;
+ }
+
+ /* Step 21 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL1,
+ RX_RST_0, RX_RST_0);
+
+ /* Step 22 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL1,
+ RX_RST_0, 0);
+
+ /* Step 23 */
+ /* Wait until SR_MII_STS[LINK_STS] = 1 */
+
+ return ret;
+}
+
+static int s32g_xpcs_ref_clk_sel(struct s32g_xpcs *xpcs,
+ enum s32g_xpcs_pll ref_pll)
+{
+ switch (ref_pll) {
+ case XPCS_PLLA:
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL,
+ MPLLB_SEL_0, 0);
+ xpcs->ref = XPCS_PLLA;
+ break;
+ case XPCS_PLLB:
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL,
+ MPLLB_SEL_0, MPLLB_SEL_0);
+ xpcs->ref = XPCS_PLLB;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void s32g_xpcs_electrical_configure(struct s32g_xpcs *xpcs)
+{
+ /* Step 2 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_EQ_CTRL0,
+ TX_EQ_MAIN_MASK, FIELD_PREP(TX_EQ_MAIN_MASK, 0xC));
+
+ /* Step 3 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_CONSUMER_10G_TX_TERM_CTRL,
+ TX0_TERM_MASK, FIELD_PREP(TX0_TERM_MASK, 0x4));
+}
+
+static int s32g_xpcs_vco_cfg(struct s32g_xpcs *xpcs, enum s32g_xpcs_pll vco_pll)
+{
+ unsigned int vco_ld = 0;
+ unsigned int vco_ref = 0;
+ unsigned int rx_baud = 0;
+ unsigned int tx_baud = 0;
+
+ switch (vco_pll) {
+ case XPCS_PLLA:
+ if (xpcs->mhz125) {
+ vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1360);
+ vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 17);
+ } else {
+ vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1350);
+ vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 27);
+ }
+
+ rx_baud = FIELD_PREP(RX0_RATE_MASK, RX0_BAUD_DIV_8);
+ tx_baud = FIELD_PREP(TX0_RATE_MASK, TX0_BAUD_DIV_4);
+ break;
+ case XPCS_PLLB:
+ if (xpcs->mhz125) {
+ vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1350);
+ vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 27);
+ } else {
+ vco_ld = FIELD_PREP(VCO_LD_VAL_0_MASK, 1344);
+ vco_ref = FIELD_PREP(VCO_REF_LD_0_MASK, 43);
+ }
+
+ rx_baud = FIELD_PREP(RX0_RATE_MASK, RX0_BAUD_DIV_2);
+ tx_baud = FIELD_PREP(TX0_RATE_MASK, TX0_BAUD_DIV_1);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_VCO_CAL_LD0,
+ VCO_LD_VAL_0_MASK, vco_ld);
+
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_VCO_CAL_REF0,
+ VCO_REF_LD_0_MASK, vco_ref);
+
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_RATE_CTRL,
+ TX0_RATE_MASK, tx_baud);
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_RATE_CTRL,
+ RX0_RATE_MASK, rx_baud);
+
+ if (vco_pll == XPCS_PLLB) {
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_CDR_CTRL,
+ VCO_LOW_FREQ_0, VCO_LOW_FREQ_0);
+ } else {
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_CDR_CTRL,
+ VCO_LOW_FREQ_0, 0);
+ }
+
+ return 0;
+}
+
+static int s32g_xpcs_init_mplla(struct s32g_xpcs *xpcs)
+{
+ unsigned int val;
+
+ if (!xpcs)
+ return -EINVAL;
+
+ /* Step 7 */
+ val = 0;
+ if (xpcs->ext_clk)
+ val |= REF_USE_PAD;
+
+ if (xpcs->mhz125) {
+ val |= REF_MPLLA_DIV2;
+ val |= REF_CLK_DIV2;
+ val |= FIELD_PREP(REF_RANGE_MASK, RANGE_52_78_MHZ);
+ } else {
+ val |= FIELD_PREP(REF_RANGE_MASK, RANGE_26_53_MHZ);
+ }
+
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_REF_CLK_CTRL,
+ REF_MPLLA_DIV2 | REF_USE_PAD | REF_RANGE_MASK |
+ REF_CLK_DIV2, val);
+
+ /* Step 8 */
+ if (xpcs->mhz125)
+ val = FIELD_PREP(MLLA_MULTIPLIER_MASK, 80);
+ else
+ val = FIELD_PREP(MLLA_MULTIPLIER_MASK, 25);
+
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLA_CTRL0,
+ MPLLA_CAL_DISABLE | MLLA_MULTIPLIER_MASK,
+ val);
+
+ /* Step 9 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLA_CTRL1,
+ MPLLA_FRACN_CTRL_MASK, 0);
+
+ /* Step 10 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLA_CTRL2,
+ MPLLA_TX_CLK_DIV_MASK | MPLLA_DIV10_CLK_EN,
+ FIELD_PREP(MPLLA_TX_CLK_DIV_MASK, 1) | MPLLA_DIV10_CLK_EN);
+
+ /* Step 11 */
+ if (xpcs->mhz125)
+ val = FIELD_PREP(MPLLA_BANDWIDTH_MASK, 43);
+ else
+ val = FIELD_PREP(MPLLA_BANDWIDTH_MASK, 357);
+
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLA_CTRL3,
+ MPLLA_BANDWIDTH_MASK, val);
+
+ return 0;
+}
+
+static int s32g_xpcs_init_mpllb(struct s32g_xpcs *xpcs)
+{
+ unsigned int val;
+
+ if (!xpcs)
+ return -EINVAL;
+
+ /* Step 7 */
+ val = 0;
+ if (xpcs->ext_clk)
+ val |= REF_USE_PAD;
+
+ if (xpcs->mhz125) {
+ val |= REF_MPLLB_DIV2;
+ val |= REF_CLK_DIV2;
+ val |= FIELD_PREP(REF_RANGE_MASK, RANGE_52_78_MHZ);
+ } else {
+ val |= FIELD_PREP(REF_RANGE_MASK, RANGE_26_53_MHZ);
+ }
+
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_REF_CLK_CTRL,
+ REF_MPLLB_DIV2 | REF_USE_PAD | REF_RANGE_MASK |
+ REF_CLK_DIV2, val);
+
+ /* Step 8 */
+ if (xpcs->mhz125)
+ val = 125 << MLLB_MULTIPLIER_OFF;
+ else
+ val = 39 << MLLB_MULTIPLIER_OFF;
+
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLB_CTRL0,
+ MPLLB_CAL_DISABLE | MLLB_MULTIPLIER_MASK,
+ val);
+
+ /* Step 9 */
+ if (xpcs->mhz125)
+ val = FIELD_PREP(MPLLB_FRACN_CTRL_MASK, 0);
+ else
+ val = FIELD_PREP(MPLLB_FRACN_CTRL_MASK, 1044);
+
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLB_CTRL1,
+ MPLLB_FRACN_CTRL_MASK, val);
+
+ /* Step 10 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MPLLB_CTRL2,
+ MPLLB_TX_CLK_DIV_MASK | MPLLB_DIV10_CLK_EN,
+ FIELD_PREP(MPLLB_TX_CLK_DIV_MASK, 5) | MPLLB_DIV10_CLK_EN);
+
+ /* Step 11 */
+ if (xpcs->mhz125)
+ val = FIELD_PREP(MPLLB_BANDWIDTH_MASK, 68);
+ else
+ val = FIELD_PREP(MPLLB_BANDWIDTH_MASK, 102);
+
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_MPLLB_CTRL3,
+ MPLLB_BANDWIDTH_MASK, val);
+
+ return 0;
+}
+
+static void s32g_serdes_pma_high_freq_recovery(struct s32g_xpcs *xpcs)
+{
+ /* PCS signal protection, PLL railout recovery */
+ s32g_xpcs_write_bits(xpcs, VR_MII_DBG_CTRL, SUPPRESS_LOS_DET | RX_DT_EN_CTL,
+ SUPPRESS_LOS_DET | RX_DT_EN_CTL);
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_MISC_CTRL0,
+ PLL_CTRL, PLL_CTRL);
+}
+
+static void s32g_serdes_pma_configure_tx_eq_post(struct s32g_xpcs *xpcs)
+{
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_EQ_CTRL1,
+ TX_EQ_OVR_RIDE, TX_EQ_OVR_RIDE);
+}
+
+static int s32g_serdes_bifurcation_pll_transit(struct s32g_xpcs *xpcs,
+ enum s32g_xpcs_pll target_pll)
+{
+ int ret = 0;
+ struct device *dev = xpcs->dev;
+
+ /* Configure XPCS speed and VCO */
+ if (target_pll == XPCS_PLLA) {
+ s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, EN_2_5G_MODE, 0);
+ s32g_xpcs_vco_cfg(xpcs, XPCS_PLLA);
+ } else {
+ s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1,
+ EN_2_5G_MODE, EN_2_5G_MODE);
+ s32g_xpcs_vco_cfg(xpcs, XPCS_PLLB);
+ }
+
+ /* Signal that clock are not available */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL1,
+ TX_CLK_RDY_0, 0);
+
+ /* Select PLL reference */
+ if (target_pll == XPCS_PLLA)
+ s32g_xpcs_ref_clk_sel(xpcs, XPCS_PLLA);
+ else
+ s32g_xpcs_ref_clk_sel(xpcs, XPCS_PLLB);
+
+ /* Initiate transmitter TX reconfiguration request */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL2,
+ TX_REQ_0, TX_REQ_0);
+
+ /* Wait for transmitter to reconfigure */
+ ret = s32g_xpcs_wait_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL2,
+ TX_REQ_0, 0);
+ if (ret) {
+ dev_err(dev, "Switch to TX_REQ_0 failed\n");
+ return ret;
+ }
+
+ /* Initiate transmitter RX reconfiguration request */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL2,
+ RX_REQ_0, RX_REQ_0);
+
+ /* Wait for receiver to reconfigure */
+ ret = s32g_xpcs_wait_bits(xpcs, VR_MII_GEN5_12G_16G_RX_GENCTRL2,
+ RX_REQ_0, 0);
+ if (ret) {
+ dev_err(dev, "Switch to RX_REQ_0 failed\n");
+ return ret;
+ }
+
+ /* Signal that clock are available */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL1,
+ TX_CLK_RDY_0, TX_CLK_RDY_0);
+
+ /* Flush internal logic */
+ s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, INIT, INIT);
+
+ /* Wait for init */
+ ret = s32g_xpcs_wait_bits(xpcs, VR_MII_DIG_CTRL1, INIT, 0);
+ if (ret) {
+ dev_err(dev, "XPCS INIT failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+/*
+ * phylink_pcs_ops
+ */
+
+static unsigned int s32cc_phylink_pcs_inband_caps(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+
+ default:
+ return 0;
+ }
+}
+
+static int s32cc_phylink_pcs_config(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ struct s32g_xpcs *xpcs = phylink_pcs_to_s32g_xpcs(pcs);
+
+ /* Step 1: Disable SGMII AN */
+ s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0);
+
+ s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL,
+ MII_AN_INTR_EN,
+ 0);
+
+ if (!(neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED))
+ return 0;
+
+ /* Step 2 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL,
+ PCS_MODE_MASK,
+ FIELD_PREP(PCS_MODE_MASK, PCS_MODE_SGMII));
+
+ /* Step 3 */
+ s32g_xpcs_write_bits(xpcs, SR_MII_CTRL,
+ SS13 | SS6,
+ SS6);
+
+ /* Step 4 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL,
+ MII_CTRL,
+ 0);
+ /* Step 5 and 8 */
+ if (xpcs->pcie_shared == PCIE_XPCS_2G5) {
+ s32g_xpcs_write(xpcs, VR_MII_LINK_TIMER_CTRL, 0x2faf);
+ s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1,
+ MAC_AUTO_SW, MAC_AUTO_SW);
+ } else {
+ s32g_xpcs_write(xpcs, VR_MII_LINK_TIMER_CTRL, 0x7a1);
+ s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, MAC_AUTO_SW, 0);
+ }
+
+ /* Step 6 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1,
+ CL37_TMR_OVRRIDE, CL37_TMR_OVRRIDE);
+
+ /* Step 7 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL,
+ MII_AN_INTR_EN,
+ MII_AN_INTR_EN);
+
+ /* Step 9: Enable SGMII AN */
+ s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, AN_ENABLE);
+
+ return 0;
+}
+
+static void s32cc_phylink_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
+ struct phylink_link_state *state)
+{
+ struct s32g_xpcs *xpcs = phylink_pcs_to_s32g_xpcs(pcs);
+ bool ss6, ss13, an_enabled;
+ struct device *dev = xpcs->dev;
+ unsigned int val, ss;
+
+ an_enabled = (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED);
+
+ if (an_enabled) {
+ state->link = 0;
+ val = s32g_xpcs_read(xpcs, VR_MII_AN_INTR_STS);
+
+ /* Interrupt is raised with each SGMII AN that is in cases
+ * Link down - Every SGMII link timer expire
+ * Link up - Once before link goes up
+ * So either linkup or raised interrupt mean AN was completed
+ */
+ if ((val & CL37_ANCMPLT_INTR) || (val & CL37_ANSGM_STS_LINK)) {
+ state->an_complete = 1;
+ if (val & CL37_ANSGM_STS_LINK)
+ state->link = 1;
+ else
+ return;
+ if (val & CL37_ANSGM_STS_DUPLEX)
+ state->duplex = DUPLEX_FULL;
+ else
+ state->duplex = DUPLEX_HALF;
+ ss = FIELD_GET(CL37_ANSGM_STS_SPEED_MASK, val);
+ } else {
+ return;
+ }
+
+ } else {
+ val = s32g_xpcs_read(xpcs, SR_MII_STS);
+ state->link = !!(val & LINK_STS);
+ state->an_complete = 0;
+ state->pause = MLO_PAUSE_NONE;
+
+ val = s32g_xpcs_read(xpcs, SR_MII_CTRL);
+ if (val & DUPLEX_MODE)
+ state->duplex = DUPLEX_FULL;
+ else
+ state->duplex = DUPLEX_HALF;
+
+ /*
+ * Build similar value as CL37_ANSGM_STS_SPEED with
+ * SS6 and SS13 of SR_MII_CTRL:
+ * - 0 for 10 Mbps
+ * - 1 for 100 Mbps
+ * - 2 for 1000 Mbps
+ */
+ ss6 = !!(val & SS6);
+ ss13 = !!(val & SS13);
+ ss = ss6 << 1 | ss13;
+ }
+
+ switch (ss) {
+ case CL37_ANSGM_10MBPS:
+ state->speed = SPEED_10;
+ break;
+ case CL37_ANSGM_100MBPS:
+ state->speed = SPEED_100;
+ break;
+ case CL37_ANSGM_1000MBPS:
+ state->speed = SPEED_1000;
+ break;
+ default:
+ dev_err(dev, "Failed to interpret the value of SR_MII_CTRL\n");
+ break;
+ }
+
+ val = s32g_xpcs_read(xpcs, VR_MII_DIG_CTRL1);
+ if ((val & EN_2_5G_MODE) && state->speed == SPEED_1000)
+ state->speed = SPEED_2500;
+
+ /* Cover SGMII AN inability to distigunish between 1G and 2.5G */
+ if ((val & EN_2_5G_MODE) &&
+ state->speed != SPEED_2500 && an_enabled) {
+ dev_err(dev, "Speed not supported in SGMII AN mode\n");
+ }
+}
+
+static void s32cc_phylink_pcs_link_up(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ phy_interface_t interface, int speed,
+ int duplex)
+{
+ struct s32g_xpcs *xpcs = phylink_pcs_to_s32g_xpcs(pcs);
+ struct device *dev = xpcs->dev;
+ bool an_enabled = (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED);
+ unsigned int val;
+ int ret;
+
+ dev_dbg(dev, "xpcs_%d: speed=%u duplex=%d an=%d\n", xpcs->id,
+ speed, duplex, an_enabled);
+
+ if (an_enabled)
+ return;
+
+ s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0);
+ s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL, MII_AN_INTR_EN, 0);
+ s32g_xpcs_write_bits(xpcs, VR_MII_AN_CTRL, MII_CTRL, 0);
+
+ if (duplex == DUPLEX_FULL)
+ val = DUPLEX_MODE;
+ else
+ val = 0;
+
+ s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, DUPLEX_MODE, val);
+
+ switch (speed) {
+ case SPEED_10:
+ val = 0;
+ break;
+ case SPEED_100:
+ val = SS13;
+ break;
+ case SPEED_1000:
+ val = SS6;
+ break;
+ case SPEED_2500:
+ val = SS6;
+ break;
+ default:
+ dev_err(dev, "Speed not supported\n");
+ break;
+ }
+
+ if (speed == SPEED_2500) {
+ ret = s32g_serdes_bifurcation_pll_transit(xpcs, XPCS_PLLB);
+ if (ret)
+ dev_err(dev, "Switch to PLLB failed\n");
+ } else {
+ ret = s32g_serdes_bifurcation_pll_transit(xpcs, XPCS_PLLA);
+ if (ret)
+ dev_err(dev, "Switch to PLLA failed\n");
+ }
+
+ s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, SS6 | SS13, val);
+}
+
+static const struct phylink_pcs_ops s32cc_phylink_pcs_ops = {
+ .pcs_inband_caps = s32cc_phylink_pcs_inband_caps,
+ .pcs_get_state = s32cc_phylink_pcs_get_state,
+ .pcs_config = s32cc_phylink_pcs_config,
+ .pcs_link_up = s32cc_phylink_pcs_link_up,
+};
+
+/*
+ * Serdes functions for initializing/configuring/releasing the xpcs
+ */
+
+int s32g_xpcs_pre_pcie_2g5(struct s32g_xpcs *xpcs)
+{
+ int ret;
+
+ /* Enable voltage boost */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_GENCTRL1, VBOOST_EN_0,
+ VBOOST_EN_0);
+
+ /* TX rate baud */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_TX_RATE_CTRL, 0x7, 0x0U);
+
+ /* Rx rate baud/2 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_RX_RATE_CTRL, 0x3U, 0x1U);
+
+ /* Set low-frequency operating band */
+ s32g_xpcs_write_bits(xpcs, VR_MII_GEN5_12G_16G_CDR_CTRL, CDR_SSC_EN_0,
+ VCO_LOW_FREQ_0);
+
+ ret = s32g_serdes_bifurcation_pll_transit(xpcs, XPCS_PLLB);
+ if (ret)
+ dev_err(xpcs->dev, "Switch to PLLB failed\n");
+
+ return ret;
+}
+
+int s32g_xpcs_init_plls(struct s32g_xpcs *xpcs)
+{
+ int ret;
+ struct device *dev = xpcs->dev;
+
+ if (!xpcs->ext_clk) {
+ /* Step 1 */
+ s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, BYP_PWRUP, BYP_PWRUP);
+ } else if (xpcs->pcie_shared == NOT_SHARED) {
+ ret = s32g_xpcs_wait_power_good_state(xpcs);
+ if (ret)
+ return ret;
+ } else if (xpcs->pcie_shared == PCIE_XPCS_2G5) {
+ ret = s32g_xpcs_wait_power_good_state(xpcs);
+ if (ret)
+ return ret;
+ /* Configure equalization */
+ s32g_serdes_pma_configure_tx_eq_post(xpcs);
+ s32g_xpcs_electrical_configure(xpcs);
+
+ /* Enable receiver recover */
+ s32g_serdes_pma_high_freq_recovery(xpcs);
+ return 0;
+ }
+
+ s32g_xpcs_electrical_configure(xpcs);
+
+ s32g_xpcs_ref_clk_sel(xpcs, XPCS_PLLA);
+ ret = s32g_xpcs_init_mplla(xpcs);
+ if (ret) {
+ dev_err(dev, "Failed to initialize PLLA\n");
+ return ret;
+ }
+ ret = s32g_xpcs_init_mpllb(xpcs);
+ if (ret) {
+ dev_err(dev, "Failed to initialize PLLB\n");
+ return ret;
+ }
+ s32g_xpcs_vco_cfg(xpcs, XPCS_PLLA);
+
+ /* Step 18 */
+ if (!xpcs->ext_clk)
+ s32g_xpcs_write_bits(xpcs, VR_MII_DIG_CTRL1, BYP_PWRUP, 0);
+
+ /* Will be cleared by Step 19 Vreset */
+ s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0);
+ s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, DUPLEX_MODE, DUPLEX_MODE);
+
+ return ret;
+}
+
+void s32g_xpcs_disable_an(struct s32g_xpcs *xpcs)
+{
+ s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, DUPLEX_MODE, DUPLEX_MODE);
+ s32g_xpcs_write_bits(xpcs, SR_MII_CTRL, AN_ENABLE, 0);
+}
+
+int s32g_xpcs_init(struct s32g_xpcs *xpcs, struct device *dev,
+ unsigned char id, void __iomem *base, bool ext_clk,
+ unsigned long rate, enum s32g_xpcs_shared pcie_shared)
+{
+ struct regmap_config conf;
+
+ if (rate != (125 * HZ_PER_MHZ) && rate != (100 * HZ_PER_MHZ)) {
+ dev_err(dev, "XPCS cannot operate @%lu HZ\n", rate);
+ return -EINVAL;
+ }
+
+ xpcs->base = base;
+ xpcs->ext_clk = ext_clk;
+ xpcs->id = id;
+ xpcs->dev = dev;
+ xpcs->pcie_shared = pcie_shared;
+
+ if (rate == (125 * HZ_PER_MHZ))
+ xpcs->mhz125 = true;
+ else
+ xpcs->mhz125 = false;
+
+ conf = s32g_xpcs_regmap_config;
+
+ if (!id)
+ conf.name = "xpcs0";
+ else
+ conf.name = "xpcs1";
+
+ xpcs->regmap = devm_regmap_init(dev, NULL, xpcs, &conf);
+ if (IS_ERR(xpcs->regmap))
+ return dev_err_probe(dev, PTR_ERR(xpcs->regmap),
+ "Failed to init register amp\n");
+
+ /* Phylink PCS */
+ xpcs->pcs.ops = &s32cc_phylink_pcs_ops;
+ xpcs->pcs.poll = true;
+ __set_bit(PHY_INTERFACE_MODE_SGMII, xpcs->pcs.supported_interfaces);
+
+ return 0;
+}
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
index 45184a3cdd69..bb7f59897faf 100644
--- a/drivers/phy/freescale/Kconfig
+++ b/drivers/phy/freescale/Kconfig
@@ -66,6 +66,7 @@ config PHY_S32G_SERDES
tristate "NXP S32G SERDES support"
depends on ARCH_S32 || COMPILE_TEST
select GENERIC_PHY
+ select REGMAP
help
This option enables support for S23G SerDes PHY used for
PCIe & Ethernet
diff --git a/drivers/phy/freescale/phy-nxp-s32g-serdes.c b/drivers/phy/freescale/phy-nxp-s32g-serdes.c
index 321a80c02be5..f2f7eb5aa327 100644
--- a/drivers/phy/freescale/phy-nxp-s32g-serdes.c
+++ b/drivers/phy/freescale/phy-nxp-s32g-serdes.c
@@ -12,12 +12,14 @@
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
+#include <linux/pcs/pcs-nxp-s32g-xpcs.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/processor.h>
#include <linux/reset.h>
#include <linux/units.h>
+#define S32G_SERDES_XPCS_MAX 2
#define S32G_SERDES_MODE_MAX 5
#define EXTERNAL_CLK_NAME "ext"
@@ -32,6 +34,52 @@
#define S32G_PCIE_PHY_MPLLA_CTRL 0x10
#define MPLL_STATE BIT(30)
+#define S32G_PCIE_PHY_MPLLB_CTRL 0x14
+#define MPLLB_SSC_EN BIT(1)
+
+#define S32G_PCIE_PHY_EXT_CTRL_SEL 0x18
+#define EXT_PHY_CTRL_SEL BIT(0)
+
+#define S32G_PCIE_PHY_EXT_BS_CTRL 0x1C
+#define EXT_BS_TX_LOWSWING BIT(6)
+#define EXT_BS_RX_BIGSWING BIT(5)
+#define EXT_BS_RX_LEVEL_MASK GENMASK(4, 0)
+
+#define S32G_PCIE_PHY_REF_CLK_CTRL 0x20
+#define EXT_REF_RANGE_MASK GENMASK(5, 3)
+#define REF_CLK_DIV2_EN BIT(2)
+#define REF_CLK_MPLLB_DIV2_EN BIT(1)
+
+#define S32G_PCIE_PHY_EXT_MPLLA_CTRL_1 0x30
+#define EXT_MPLLA_BANDWIDTH_MASK GENMASK(15, 0)
+
+#define S32G_PCIE_PHY_EXT_MPLLB_CTRL_1 0x40
+#define EXT_MPLLB_DIV_MULTIPLIER_MASK GENMASK(31, 24)
+#define EXT_MPLLB_DIV_CLK_EN BIT(19)
+#define EXT_MPLLB_DIV8_CLK_EN BIT(18)
+#define EXT_MPLLB_DIV10_CLK_EN BIT(16)
+#define EXT_MPLLB_BANDWIDTH_MASK GENMASK(15, 0)
+
+#define S32G_PCIE_PHY_EXT_MPLLB_CTRL_2 0x44
+#define EXT_MPLLB_FRACN_CTRL_MASK GENMASK(22, 12)
+#define MPLLB_MULTIPLIER_MASK GENMASK(8, 0)
+
+#define S32G_PCIE_PHY_EXT_MPLLB_CTRL_3 0x48
+#define EXT_MPLLB_WORD_DIV2_EN BIT(31)
+#define EXT_MPLLB_TX_CLK_DIV_MASK GENMASK(30, 28)
+
+#define S32G_PCIE_PHY_EXT_MISC_CTRL_1 0xA0
+#define EXT_RX_LOS_THRESHOLD_MASK GENMASK(7, 1)
+#define EXT_RX_VREF_CTRL_MASK GENMASK(28, 24)
+
+#define S32G_PCIE_PHY_EXT_MISC_CTRL_2 0xA4
+#define EXT_TX_VBOOST_LVL_MASK GENMASK(18, 16)
+#define EXT_TX_TERM_CTRL_MASK GENMASK(26, 24)
+
+#define S32G_PCIE_PHY_XPCS1_RX_OVRD_CTRL 0xD0
+#define XPCS1_RX_VCO_LD_VAL_MASK GENMASK(28, 16)
+#define XPCS1_RX_REF_LD_VAL_MASK GENMASK(14, 8)
+
#define S32G_SS_RW_REG_0 0xF0
#define SUBMODE_MASK GENMASK(3, 0)
#define CLKEN_MASK BIT(23)
@@ -44,6 +92,9 @@
#define S32G_PHY_REG_DATA 0x4
+#define S32G_PHY_RST_CTRL 0x8
+#define WARM_RST BIT(1)
+
#define RAWLANE0_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN 0x3019
#define RAWLANE1_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN 0x3119
@@ -76,16 +127,33 @@ struct s32g_pcie_ctrl {
bool powered_on;
};
+struct s32g_xpcs_ctrl {
+ struct s32g_xpcs *phys[2];
+ void __iomem *base0, *base1;
+};
+
struct s32g_serdes {
struct s32g_serdes_ctrl ctrl;
struct s32g_pcie_ctrl pcie;
+ struct s32g_xpcs_ctrl xpcs;
struct device *dev;
+ u8 lanes_status;
};
/* PCIe phy subsystem */
#define S32G_SERDES_PCIE_FREQ (100 * HZ_PER_MHZ)
+static void s32g_pcie_phy_reset(struct s32g_serdes *serdes)
+{
+ u32 val;
+
+ val = readl(serdes->pcie.phy_base + S32G_PHY_RST_CTRL);
+ writel(val | WARM_RST, serdes->pcie.phy_base + S32G_PHY_RST_CTRL);
+ usleep_range(1000, 1100);
+ writel(val, serdes->pcie.phy_base + S32G_PHY_RST_CTRL);
+}
+
static int s32g_pcie_check_clk(struct s32g_serdes *serdes)
{
struct s32g_serdes_ctrl *sctrl = &serdes->ctrl;
@@ -277,6 +345,192 @@ static struct phy *s32g_serdes_phy_xlate(struct device *dev,
return phy;
}
+/* XPCS subsystem */
+
+static int s32g_serdes_xpcs_init(struct s32g_serdes *serdes, int id)
+{
+ struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+ struct s32g_xpcs_ctrl *xpcs = &serdes->xpcs;
+ enum s32g_xpcs_shared shared = NOT_SHARED;
+ unsigned long rate = ctrl->ref_clk_rate;
+ struct device *dev = serdes->dev;
+ void __iomem *base;
+
+ if (!id)
+ base = xpcs->base0;
+ else
+ base = xpcs->base1;
+
+ if (ctrl->ss_mode == 1 || ctrl->ss_mode == 2)
+ shared = PCIE_XPCS_1G;
+ else if (ctrl->ss_mode == 5)
+ shared = PCIE_XPCS_2G5;
+
+ return s32g_xpcs_init(xpcs->phys[id], dev, id, base,
+ ctrl->ext_clk, rate, shared);
+}
+
+static void s32g_serdes_prepare_pma_mode5(struct s32g_serdes *serdes)
+{
+ u32 val;
+ /* Configure TX_VBOOST_LVL and TX_TERM_CTRL */
+ val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_2);
+ val &= ~(EXT_TX_VBOOST_LVL_MASK | EXT_TX_TERM_CTRL_MASK);
+ val |= FIELD_PREP(EXT_TX_VBOOST_LVL_MASK, 0x3) |
+ FIELD_PREP(EXT_TX_TERM_CTRL_MASK, 0x4);
+ writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_2);
+
+ /* Enable phy external control */
+ val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_CTRL_SEL);
+ val |= EXT_PHY_CTRL_SEL;
+ writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_CTRL_SEL);
+
+ /* Configure ref range, disable PLLB/ref div2 */
+ val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_REF_CLK_CTRL);
+ val &= ~(REF_CLK_DIV2_EN | REF_CLK_MPLLB_DIV2_EN | EXT_REF_RANGE_MASK);
+ val |= FIELD_PREP(EXT_REF_RANGE_MASK, 0x3);
+ writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_REF_CLK_CTRL);
+
+ /* Configure multiplier */
+ val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_2);
+ val &= ~(MPLLB_MULTIPLIER_MASK | EXT_MPLLB_FRACN_CTRL_MASK | BIT(24) | BIT(28));
+ val |= FIELD_PREP(MPLLB_MULTIPLIER_MASK, 0x27U) |
+ FIELD_PREP(EXT_MPLLB_FRACN_CTRL_MASK, 0x414);
+ writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_2);
+
+ val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_MPLLB_CTRL);
+ val &= ~MPLLB_SSC_EN;
+ writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_MPLLB_CTRL);
+
+ /* Configure tx lane division, disable word clock div2*/
+ val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_3);
+ val &= ~(EXT_MPLLB_WORD_DIV2_EN | EXT_MPLLB_TX_CLK_DIV_MASK);
+ val |= FIELD_PREP(EXT_MPLLB_TX_CLK_DIV_MASK, 0x5);
+ writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_3);
+
+ /* Configure bandwidth for filtering and div10*/
+ val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_1);
+ val &= ~(EXT_MPLLB_BANDWIDTH_MASK | EXT_MPLLB_DIV_CLK_EN |
+ EXT_MPLLB_DIV8_CLK_EN | EXT_MPLLB_DIV_MULTIPLIER_MASK);
+ val |= FIELD_PREP(EXT_MPLLB_BANDWIDTH_MASK, 0x5f) | EXT_MPLLB_DIV10_CLK_EN;
+ writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLB_CTRL_1);
+
+ val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLA_CTRL_1);
+ val &= ~(EXT_MPLLA_BANDWIDTH_MASK);
+ val |= FIELD_PREP(EXT_MPLLA_BANDWIDTH_MASK, 0xc5);
+ writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MPLLA_CTRL_1);
+
+ /* Configure VCO */
+ val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_XPCS1_RX_OVRD_CTRL);
+ val &= ~(XPCS1_RX_VCO_LD_VAL_MASK | XPCS1_RX_REF_LD_VAL_MASK);
+ val |= FIELD_PREP(XPCS1_RX_VCO_LD_VAL_MASK, 0x540) |
+ FIELD_PREP(XPCS1_RX_REF_LD_VAL_MASK, 0x2b);
+ writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_XPCS1_RX_OVRD_CTRL);
+
+ /* Boundary scan control */
+ val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_BS_CTRL);
+ val &= ~(EXT_BS_RX_LEVEL_MASK | EXT_BS_TX_LOWSWING);
+ val |= FIELD_PREP(EXT_BS_RX_LEVEL_MASK, 0xB) | EXT_BS_RX_BIGSWING;
+ writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_BS_CTRL);
+
+ /* Rx loss threshold */
+ val = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_1);
+ val &= ~(EXT_RX_LOS_THRESHOLD_MASK | EXT_RX_VREF_CTRL_MASK);
+ val |= FIELD_PREP(EXT_RX_LOS_THRESHOLD_MASK, 0x3U) |
+ FIELD_PREP(EXT_RX_VREF_CTRL_MASK, 0x11U);
+ writel(val, serdes->ctrl.ss_base + S32G_PCIE_PHY_EXT_MISC_CTRL_1);
+}
+
+static int s32g_serdes_enable_mode5(struct s32g_serdes *serdes, struct s32g_xpcs *xpcs)
+{
+ int ret;
+
+ s32g_serdes_prepare_pma_mode5(serdes);
+
+ ret = s32g_xpcs_pre_pcie_2g5(xpcs);
+ if (ret) {
+ dev_err(serdes->dev,
+ "Failed to prepare SerDes for PCIE & XPCS @ 2G5 mode\n");
+ return ret;
+ }
+
+ s32g_pcie_phy_reset(serdes);
+
+ return 0;
+}
+
+static int s32g_serdes_init_clks(struct s32g_serdes *serdes)
+{
+ struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+ struct s32g_xpcs_ctrl *xpcs = &serdes->xpcs;
+ struct s32g_xpcs *order[2];
+ size_t i;
+ int ret;
+
+ switch (ctrl->ss_mode) {
+ case 0:
+ return 0;
+ case 1:
+ order[0] = xpcs->phys[0];
+ order[1] = NULL;
+ break;
+ case 2:
+ case 5:
+ order[0] = xpcs->phys[1];
+ order[1] = NULL;
+ break;
+ case 3:
+ order[0] = xpcs->phys[1];
+ order[1] = xpcs->phys[0];
+ break;
+ case 4:
+ order[0] = xpcs->phys[0];
+ order[1] = xpcs->phys[1];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(order); i++) {
+ if (!order[i])
+ continue;
+
+ ret = s32g_xpcs_init_plls(order[i]);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(order); i++) {
+ if (!order[i])
+ continue;
+
+ if (ctrl->ss_mode == 5) {
+ ret = s32g_serdes_enable_mode5(serdes, order[i]);
+ if (ret)
+ return ret;
+ } else {
+ s32g_xpcs_vreset(order[i]);
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(order); i++) {
+ if (!order[i])
+ continue;
+
+ ret = s32g_xpcs_wait_vreset(order[i]);
+ if (ret)
+ return ret;
+
+ ret = s32g_xpcs_reset_rx(order[i]);
+ if (ret)
+ return ret;
+
+ s32g_xpcs_disable_an(order[i]);
+ }
+
+ return 0;
+}
+
/* Serdes subsystem */
static int s32g_serdes_assert_reset(struct s32g_serdes *serdes)
@@ -331,6 +585,10 @@ static int s32g_serdes_init(struct s32g_serdes *serdes)
return ret;
}
+ /*
+ * We have a tight timing for the init sequence and any delay linked to
+ * printk as an example can fail the init after reset
+ */
ret = s32g_serdes_assert_reset(serdes);
if (ret)
goto disable_clks;
@@ -363,7 +621,13 @@ static int s32g_serdes_init(struct s32g_serdes *serdes)
dev_info(serdes->dev, "Using mode %d for SerDes subsystem\n",
ctrl->ss_mode);
- return 0;
+ ret = s32g_serdes_init_clks(serdes);
+ if (ret) {
+ dev_err(serdes->dev, "XPCS init failed\n");
+ goto disable_clks;
+ }
+
+ return ret;
disable_clks:
clk_bulk_disable_unprepare(serdes->ctrl.nclks,
@@ -449,12 +713,32 @@ static int s32g_serdes_get_pcie_resources(struct platform_device *pdev, struct s
return 0;
}
+static int s32g_serdes_get_xpcs_resources(struct platform_device *pdev, struct s32g_serdes *serdes)
+{
+ struct s32g_xpcs_ctrl *xpcs = &serdes->xpcs;
+ struct device *dev = &pdev->dev;
+
+ xpcs->base0 = devm_platform_ioremap_resource_byname(pdev, "xpcs0");
+ if (IS_ERR(xpcs->base0)) {
+ dev_err(dev, "Failed to map 'xpcs0'\n");
+ return PTR_ERR(xpcs->base0);
+ }
+
+ xpcs->base1 = devm_platform_ioremap_resource_byname(pdev, "xpcs1");
+ if (IS_ERR(xpcs->base1)) {
+ dev_err(dev, "Failed to map 'xpcs1'\n");
+ return PTR_ERR(xpcs->base1);
+ }
+
+ return 0;
+}
+
static int s32g2_serdes_create_phy(struct s32g_serdes *serdes, struct device_node *child_node)
{
struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
struct phy_provider *phy_provider;
struct device *dev = serdes->dev;
- int ss_mode = ctrl->ss_mode;
+ int ret, ss_mode = ctrl->ss_mode;
struct phy *phy;
if (of_device_is_compatible(child_node, "nxp,s32g2-serdes-pcie-phy")) {
@@ -476,6 +760,37 @@ static int s32g2_serdes_create_phy(struct s32g_serdes *serdes, struct device_nod
if (IS_ERR(phy_provider))
return PTR_ERR(phy_provider);
+ } else if (of_device_is_compatible(child_node, "nxp,s32g2-serdes-xpcs")) {
+ struct s32g_xpcs_ctrl *xpcs_ctrl = &serdes->xpcs;
+ struct s32g_xpcs *xpcs;
+ int port;
+
+ /* no Ethernet phy lane */
+ if (ss_mode == 0)
+ return 0;
+
+ /* Get XPCS port number connected to the lane */
+ if (of_property_read_u32(child_node, "reg", &port))
+ return -EINVAL;
+
+ /* XPCS1 is not used */
+ if (ss_mode == 1 && port == 1)
+ return -EINVAL;
+
+ /* XPCS0 is not used */
+ if (ss_mode == 2 && port == 0)
+ return -EINVAL;
+
+ xpcs = devm_kmalloc(dev, sizeof(*xpcs), GFP_KERNEL);
+ if (!xpcs)
+ return -ENOMEM;
+
+ xpcs_ctrl->phys[port] = xpcs;
+
+ ret = s32g_serdes_xpcs_init(serdes, port);
+ if (ret)
+ return ret;
+
} else {
dev_warn(dev, "Skipping unknown child node %pOFn\n", child_node);
}
@@ -517,6 +832,10 @@ static int s32g_serdes_probe(struct platform_device *pdev)
if (ret)
return ret;
+ ret = s32g_serdes_get_xpcs_resources(pdev, serdes);
+ if (ret)
+ return ret;
+
ret = s32g_serdes_parse_lanes(dev, serdes);
if (ret)
return ret;
@@ -555,6 +874,57 @@ static int __maybe_unused s32g_serdes_resume(struct device *device)
return ret;
}
+struct phylink_pcs *s32g_serdes_pcs_create(struct device *dev, struct device_node *np)
+{
+ struct platform_device *pdev;
+ struct device_node *pcs_np;
+ struct s32g_serdes *serdes;
+ u32 port;
+
+ if (of_property_read_u32(np, "reg", &port))
+ return ERR_PTR(-EINVAL);
+
+ if (port >= S32G_SERDES_XPCS_MAX)
+ return ERR_PTR(-EINVAL);
+
+ /* The PCS pdev is attached to the parent node */
+ pcs_np = of_get_parent(np);
+ if (!pcs_np)
+ return ERR_PTR(-ENODEV);
+
+ if (!of_device_is_available(pcs_np)) {
+ of_node_put(pcs_np);
+ return ERR_PTR(-ENODEV);
+ }
+
+ pdev = of_find_device_by_node(pcs_np);
+ of_node_put(pcs_np);
+ if (!pdev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ serdes = platform_get_drvdata(pdev);
+ if (!serdes) {
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ if (!serdes->xpcs.phys[port]) {
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ return &serdes->xpcs.phys[port]->pcs;
+}
+EXPORT_SYMBOL(s32g_serdes_pcs_create);
+
+void s32g_serdes_pcs_destroy(struct phylink_pcs *pcs)
+{
+ struct s32g_xpcs *xpcs = phylink_pcs_to_s32g_xpcs(pcs);
+
+ put_device(xpcs->dev);
+}
+EXPORT_SYMBOL(s32g_serdes_pcs_destroy);
+
static const struct of_device_id s32g_serdes_match[] = {
{
.compatible = "nxp,s32g2-serdes",
diff --git a/include/linux/pcs/pcs-nxp-s32g-xpcs.h b/include/linux/pcs/pcs-nxp-s32g-xpcs.h
new file mode 100644
index 000000000000..96a0049b93a6
--- /dev/null
+++ b/include/linux/pcs/pcs-nxp-s32g-xpcs.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ * Copyright 2021-2026 NXP
+ */
+#ifndef PCS_NXP_S32G_XPCS_H
+#define PCS_NXP_S32G_XPCS_H
+
+#include <linux/phylink.h>
+
+enum s32g_xpcs_shared {
+ NOT_SHARED,
+ PCIE_XPCS_1G,
+ PCIE_XPCS_2G5,
+};
+
+enum s32g_xpcs_pll {
+ XPCS_PLLA, /* Slow PLL */
+ XPCS_PLLB, /* Fast PLL */
+};
+
+struct s32g_xpcs {
+ void __iomem *base;
+ struct device *dev;
+ unsigned char id;
+ struct regmap *regmap;
+ enum s32g_xpcs_pll ref;
+ bool ext_clk;
+ bool mhz125;
+ enum s32g_xpcs_shared pcie_shared;
+ struct phylink_pcs pcs;
+};
+
+#define phylink_pcs_to_s32g_xpcs(pl_pcs) \
+ container_of((pl_pcs), struct s32g_xpcs, pcs)
+
+int s32g_xpcs_init(struct s32g_xpcs *xpcs, struct device *dev,
+ unsigned char id, void __iomem *base, bool ext_clk,
+ unsigned long rate, enum s32g_xpcs_shared pcie_shared);
+int s32g_xpcs_init_plls(struct s32g_xpcs *xpcs);
+int s32g_xpcs_pre_pcie_2g5(struct s32g_xpcs *xpcs);
+void s32g_xpcs_vreset(struct s32g_xpcs *xpcs);
+int s32g_xpcs_wait_vreset(struct s32g_xpcs *xpcs);
+int s32g_xpcs_reset_rx(struct s32g_xpcs *xpcs);
+void s32g_xpcs_disable_an(struct s32g_xpcs *xpcs);
+
+struct phylink_pcs *s32g_serdes_pcs_create(struct device *dev, struct device_node *np);
+void s32g_serdes_pcs_destroy(struct phylink_pcs *pcs);
+
+#endif /* PCS_NXP_S32G_XPCS_H */
+
--
2.43.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH 4/4 v2] MAINTAINERS: Add MAINTAINER for NXP S32G Serdes driver
From: Vincent Guittot @ 2026-02-03 16:19 UTC (permalink / raw)
To: vkoul, neil.armstrong, krzk+dt, conor+dt, ciprianmarian.costea,
s32, p.zabel, linux, ghennadi.procopciuc, Ionut.Vicovan,
linux-phy, devicetree, linux-kernel, linux-arm-kernel, netdev,
horms
Cc: Frank.li
In-Reply-To: <20260203161917.1666696-1-vincent.guittot@linaro.org>
Add a new entry for S32G Serdes driver.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
MAINTAINERS | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 67db88b04537..fab748292821 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3206,6 +3206,16 @@ S: Maintained
F: Documentation/devicetree/bindings/net/nxp,s32-dwmac.yaml
F: drivers/net/ethernet/stmicro/stmmac/dwmac-s32.c
+ARM/NXP S32G SERDES DRIVER
+M: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
+R: NXP S32 Linux Team <s32@nxp.com>
+L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+S: Maintained
+F: Documentation/devicetree/bindings/phy/nxp,s32g-serdes.yaml
+F: drivers/net/pcs/pcs-nxp-s32g-xpcs.c
+F: drivers/phy/freescale/phy-nxp-s32g-serdes.c
+F: include/linux/pcs/pcs-nxp-s32g-xpcs.h
+
ARM/Orion SoC/Technologic Systems TS-78xx platform support
M: Alexander Clouter <alex@digriz.org.uk>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
--
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/4 v2] phy: s32g: Add serdes subsystem phy
From: Vincent Guittot @ 2026-02-03 16:19 UTC (permalink / raw)
To: vkoul, neil.armstrong, krzk+dt, conor+dt, ciprianmarian.costea,
s32, p.zabel, linux, ghennadi.procopciuc, Ionut.Vicovan,
linux-phy, devicetree, linux-kernel, linux-arm-kernel, netdev,
horms
Cc: Frank.li
In-Reply-To: <20260203161917.1666696-1-vincent.guittot@linaro.org>
s32g SoC family includes 2 serdes subsystems which are made of one PCIe
controller, 2 XPCS and one Phy. The Phy got 2 lanes that can be configure
to output PCIe lanes and/or SGMII.
Implement PCIe phy support
Co-developed-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
Co-developed-by: Alexandru-Catalin Ionita <alexandru-catalin.ionita@nxp.com>
Signed-off-by: Alexandru-Catalin Ionita <alexandru-catalin.ionita@nxp.com>
Co-developed-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
Signed-off-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
Co-developed-by: Ionut Vicovan <Ionut.Vicovan@nxp.com>
Signed-off-by: Ionut Vicovan <Ionut.Vicovan@nxp.com>
Co-developed-by: Bogdan Roman <bogdan-gabriel.roman@nxp.com>
Signed-off-by: Bogdan Roman <bogdan-gabriel.roman@nxp.com>
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
drivers/phy/freescale/Kconfig | 9 +
drivers/phy/freescale/Makefile | 1 +
drivers/phy/freescale/phy-nxp-s32g-serdes.c | 583 ++++++++++++++++++++
3 files changed, 593 insertions(+)
create mode 100644 drivers/phy/freescale/phy-nxp-s32g-serdes.c
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
index 81f53564ee15..45184a3cdd69 100644
--- a/drivers/phy/freescale/Kconfig
+++ b/drivers/phy/freescale/Kconfig
@@ -61,3 +61,12 @@ config PHY_FSL_LYNX_28G
found on NXP's Layerscape platforms such as LX2160A.
Used to change the protocol running on SerDes lanes at runtime.
Only useful for a restricted set of Ethernet protocols.
+
+config PHY_S32G_SERDES
+ tristate "NXP S32G SERDES support"
+ depends on ARCH_S32 || COMPILE_TEST
+ select GENERIC_PHY
+ help
+ This option enables support for S23G SerDes PHY used for
+ PCIe & Ethernet
+
diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile
index 658eac7d0a62..86d948417252 100644
--- a/drivers/phy/freescale/Makefile
+++ b/drivers/phy/freescale/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_PHY_FSL_IMX8M_PCIE) += phy-fsl-imx8m-pcie.o
obj-$(CONFIG_PHY_FSL_IMX8QM_HSIO) += phy-fsl-imx8qm-hsio.o
obj-$(CONFIG_PHY_FSL_LYNX_28G) += phy-fsl-lynx-28g.o
obj-$(CONFIG_PHY_FSL_SAMSUNG_HDMI_PHY) += phy-fsl-samsung-hdmi.o
+obj-$(CONFIG_PHY_S32G_SERDES) += phy-nxp-s32g-serdes.o
diff --git a/drivers/phy/freescale/phy-nxp-s32g-serdes.c b/drivers/phy/freescale/phy-nxp-s32g-serdes.c
new file mode 100644
index 000000000000..321a80c02be5
--- /dev/null
+++ b/drivers/phy/freescale/phy-nxp-s32g-serdes.c
@@ -0,0 +1,583 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * SerDes driver for S32G SoCs
+ *
+ * Copyright 2021-2026 NXP
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/processor.h>
+#include <linux/reset.h>
+#include <linux/units.h>
+
+#define S32G_SERDES_MODE_MAX 5
+
+#define EXTERNAL_CLK_NAME "ext"
+#define INTERNAL_CLK_NAME "ref"
+
+/* Serdes Sub system registers */
+
+#define S32G_PCIE_PHY_GEN_CTRL 0x0
+#define REF_USE_PAD BIT(17)
+#define RX_SRIS_MODE BIT(9)
+
+#define S32G_PCIE_PHY_MPLLA_CTRL 0x10
+#define MPLL_STATE BIT(30)
+
+#define S32G_SS_RW_REG_0 0xF0
+#define SUBMODE_MASK GENMASK(3, 0)
+#define CLKEN_MASK BIT(23)
+#define PHY0_CR_PARA_SEL BIT(9)
+
+/* PCIe phy subsystem registers */
+
+#define S32G_PHY_REG_ADDR 0x0
+#define PHY_REG_EN BIT(31)
+
+#define S32G_PHY_REG_DATA 0x4
+
+#define RAWLANE0_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN 0x3019
+#define RAWLANE1_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN 0x3119
+
+/*
+ * Until now, there is no generic way to describe and set PCIe clock mode.
+ * PCIe controller uses the default CRNS = 0 mode.
+ */
+enum pcie_phy_mode {
+ CRNS = 0, /* Common Reference Clock, No Spread Spectrum */
+ CRSS = 1, /* Common Reference Clock, Spread Spectrum */
+ SRNS = 2, /* Separate Reference Clock, No Spread Spectrum */
+ SRIS = 3 /* Separate Reference Clock, Spread Spectrum */
+};
+
+struct s32g_serdes_ctrl {
+ void __iomem *ss_base;
+ struct reset_control *rst;
+ struct clk_bulk_data *clks;
+ int nclks;
+ u32 ss_mode;
+ unsigned long ref_clk_rate;
+ bool ext_clk;
+};
+
+struct s32g_pcie_ctrl {
+ void __iomem *phy_base;
+ struct reset_control *rst;
+ struct phy *phy;
+ enum pcie_phy_mode phy_mode;
+ bool powered_on;
+};
+
+struct s32g_serdes {
+ struct s32g_serdes_ctrl ctrl;
+ struct s32g_pcie_ctrl pcie;
+ struct device *dev;
+};
+
+/* PCIe phy subsystem */
+
+#define S32G_SERDES_PCIE_FREQ (100 * HZ_PER_MHZ)
+
+static int s32g_pcie_check_clk(struct s32g_serdes *serdes)
+{
+ struct s32g_serdes_ctrl *sctrl = &serdes->ctrl;
+ unsigned long rate = sctrl->ref_clk_rate;
+
+ if (rate != S32G_SERDES_PCIE_FREQ) {
+ dev_err(serdes->dev, "PCIe PHY cannot operate at %lu HZ\n", rate);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static bool s32g_pcie_phy_is_locked(struct s32g_serdes *serdes)
+{
+ u32 mplla = readl(serdes->ctrl.ss_base + S32G_PCIE_PHY_MPLLA_CTRL);
+ const u32 mask = MPLL_STATE;
+
+ return (mplla & mask) == mask;
+}
+
+/* Serdes RFM says between 3.4 and 5.2 ms depending of pll */
+#define S32G_SERDES_LOCK_TIMEOUT_MS 6
+
+static int s32g_pcie_phy_power_on_common(struct s32g_serdes *serdes)
+{
+ struct s32g_serdes_ctrl *sctrl = &serdes->ctrl;
+ struct s32g_pcie_ctrl *pcie = &serdes->pcie;
+ u32 reg;
+ int val, ret = 0;
+
+ ret = s32g_pcie_check_clk(serdes);
+ if (ret)
+ return ret;
+
+ reg = readl(sctrl->ss_base + S32G_PCIE_PHY_GEN_CTRL);
+
+ /* if PCIE PHY is in SRIS mode */
+ if (pcie->phy_mode == SRIS)
+ reg |= RX_SRIS_MODE;
+
+ if (sctrl->ext_clk)
+ reg |= REF_USE_PAD;
+ else
+ reg &= ~REF_USE_PAD;
+
+ writel(reg, sctrl->ss_base + S32G_PCIE_PHY_GEN_CTRL);
+
+ /* Monitor Serdes MPLL state */
+ ret = read_poll_timeout(s32g_pcie_phy_is_locked, val,
+ (val),
+ 0,
+ S32G_SERDES_LOCK_TIMEOUT_MS, false, serdes);
+ if (ret) {
+ dev_err(serdes->dev, "Failed to lock PCIE phy\n");
+ return -ETIMEDOUT;
+ }
+
+ /* Set PHY register access to CR interface */
+ reg = readl(sctrl->ss_base + S32G_SS_RW_REG_0);
+ reg |= PHY0_CR_PARA_SEL;
+ writel(reg, sctrl->ss_base + S32G_SS_RW_REG_0);
+
+ return ret;
+}
+
+static void s32g_pcie_phy_write(struct s32g_serdes *serdes, u32 reg, u32 val)
+{
+ writel(PHY_REG_EN, serdes->pcie.phy_base + S32G_PHY_REG_ADDR);
+ writel(reg | PHY_REG_EN, serdes->pcie.phy_base + S32G_PHY_REG_ADDR);
+ usleep_range(100, 110);
+ writel(val, serdes->pcie.phy_base + S32G_PHY_REG_DATA);
+ usleep_range(100, 110);
+ writel(0, serdes->pcie.phy_base + S32G_PHY_REG_ADDR);
+}
+
+static int s32g_pcie_phy_power_on(struct s32g_serdes *serdes)
+{
+ struct s32g_pcie_ctrl *pcie = &serdes->pcie;
+ struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+ u32 iq_ovrd_in;
+ int ret = 0;
+
+ ret = s32g_pcie_phy_power_on_common(serdes);
+ if (ret)
+ return ret;
+
+ /* RX_EQ_DELTA_IQ_OVRD enable and override value for PCIe lanes */
+ iq_ovrd_in = RAWLANE0_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN;
+
+ s32g_pcie_phy_write(serdes, iq_ovrd_in, 0x3);
+ s32g_pcie_phy_write(serdes, iq_ovrd_in, 0x13);
+
+ if (ctrl->ss_mode == 0) {
+ iq_ovrd_in = RAWLANE1_DIG_PCS_XF_RX_EQ_DELTA_IQ_OVRD_IN;
+
+ s32g_pcie_phy_write(serdes, iq_ovrd_in, 0x3);
+ s32g_pcie_phy_write(serdes, iq_ovrd_in, 0x13);
+ }
+
+ pcie->powered_on = true;
+
+ return 0;
+}
+
+/* PCIe phy ops function */
+
+static int s32g_serdes_phy_power_on(struct phy *p)
+{
+ struct s32g_serdes *serdes = phy_get_drvdata(p);
+
+ return s32g_pcie_phy_power_on(serdes);
+}
+
+static int s32g_serdes_phy_power_off(struct phy *p)
+{
+ struct s32g_serdes *serdes = phy_get_drvdata(p);
+
+ serdes->pcie.powered_on = false;
+
+ return 0;
+}
+
+static inline bool is_pcie_phy_mode_valid(int mode)
+{
+ switch (mode) {
+ case CRNS:
+ case CRSS:
+ case SRNS:
+ case SRIS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int s32g_serdes_phy_set_mode_ext(struct phy *p,
+ enum phy_mode mode, int submode)
+{
+ struct s32g_serdes *serdes = phy_get_drvdata(p);
+
+ if (mode != PHY_MODE_PCIE)
+ return -EINVAL;
+
+ if (!is_pcie_phy_mode_valid(submode))
+ return -EINVAL;
+
+ /*
+ * Do not configure SRIS or CRSS PHY MODE in conjunction
+ * with any SGMII mode on the same SerDes subsystem
+ */
+ if ((submode == CRSS || submode == SRIS) &&
+ serdes->ctrl.ss_mode != 0)
+ return -EINVAL;
+
+ /*
+ * Internal reference clock cannot be used with either Common clock
+ * or Spread spectrum, leaving only SRNSS
+ */
+ if (submode != SRNS && !serdes->ctrl.ext_clk)
+ return -EINVAL;
+
+ if (serdes->pcie.powered_on)
+ dev_warn(serdes->dev, "The phy is already powered on.\n");
+
+ serdes->pcie.phy_mode = submode;
+
+ return 0;
+}
+
+static const struct phy_ops serdes_pcie_ops = {
+ .power_on = s32g_serdes_phy_power_on,
+ .power_off = s32g_serdes_phy_power_off,
+ .set_mode = s32g_serdes_phy_set_mode_ext,
+};
+
+static struct phy *s32g_serdes_phy_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct s32g_serdes *serdes;
+ struct phy *phy;
+
+ serdes = dev_get_drvdata(dev);
+ if (!serdes)
+ return ERR_PTR(-EINVAL);
+
+ phy = serdes->pcie.phy;
+
+ return phy;
+}
+
+/* Serdes subsystem */
+
+static int s32g_serdes_assert_reset(struct s32g_serdes *serdes)
+{
+ struct device *dev = serdes->dev;
+ int ret;
+
+ ret = reset_control_assert(serdes->pcie.rst);
+ if (ret) {
+ dev_err(dev, "Failed to assert PCIE reset: %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_control_assert(serdes->ctrl.rst);
+ if (ret) {
+ dev_err(dev, "Failed to assert SerDes reset: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int s32g_serdes_deassert_reset(struct s32g_serdes *serdes)
+{
+ struct device *dev = serdes->dev;
+ int ret;
+
+ ret = reset_control_deassert(serdes->pcie.rst);
+ if (ret) {
+ dev_err(dev, "Failed to assert PCIE reset: %d\n", ret);
+ return ret;
+ }
+
+ ret = reset_control_deassert(serdes->ctrl.rst);
+ if (ret) {
+ dev_err(dev, "Failed to assert SerDes reset: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int s32g_serdes_init(struct s32g_serdes *serdes)
+{
+ struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+ u32 reg0;
+ int ret;
+
+ ret = clk_bulk_prepare_enable(ctrl->nclks, ctrl->clks);
+ if (ret) {
+ dev_err(serdes->dev, "Failed to enable SerDes clocks\n");
+ return ret;
+ }
+
+ ret = s32g_serdes_assert_reset(serdes);
+ if (ret)
+ goto disable_clks;
+
+ /* Set serdes mode */
+ reg0 = readl(ctrl->ss_base + S32G_SS_RW_REG_0);
+ reg0 &= ~SUBMODE_MASK;
+ if (ctrl->ss_mode == 5)
+ reg0 |= 2;
+ else
+ reg0 |= ctrl->ss_mode;
+ writel(reg0, ctrl->ss_base + S32G_SS_RW_REG_0);
+
+ /* Set Clock source: internal or external */
+ reg0 = readl(ctrl->ss_base + S32G_SS_RW_REG_0);
+ if (ctrl->ext_clk)
+ reg0 &= ~CLKEN_MASK;
+ else
+ reg0 |= CLKEN_MASK;
+
+ writel(reg0, ctrl->ss_base + S32G_SS_RW_REG_0);
+
+ /* Wait for the selection of working mode (as per the manual specs) */
+ usleep_range(100, 110);
+
+ ret = s32g_serdes_deassert_reset(serdes);
+ if (ret)
+ goto disable_clks;
+
+ dev_info(serdes->dev, "Using mode %d for SerDes subsystem\n",
+ ctrl->ss_mode);
+
+ return 0;
+
+disable_clks:
+ clk_bulk_disable_unprepare(serdes->ctrl.nclks,
+ serdes->ctrl.clks);
+
+ return ret;
+}
+
+static int s32g_serdes_get_ctrl_resources(struct platform_device *pdev, struct s32g_serdes *serdes)
+{
+ struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+ struct device *dev = &pdev->dev;
+ int ret, idx;
+
+ ret = of_property_read_u32(dev->of_node, "nxp,sys-mode",
+ &ctrl->ss_mode);
+ if (ret) {
+ dev_err(dev, "Failed to get SerDes subsystem mode\n");
+ return -EINVAL;
+ }
+
+ if (ctrl->ss_mode > S32G_SERDES_MODE_MAX) {
+ dev_err(dev, "Invalid SerDes subsystem mode %u\n",
+ ctrl->ss_mode);
+ return -EINVAL;
+ }
+
+ ctrl->ss_base = devm_platform_ioremap_resource_byname(pdev, "ss_pcie");
+ if (IS_ERR(ctrl->ss_base)) {
+ dev_err(dev, "Failed to map 'ss_pcie'\n");
+ return PTR_ERR(ctrl->ss_base);
+ }
+
+ ctrl->rst = devm_reset_control_get_exclusive(dev, "serdes");
+ if (IS_ERR(ctrl->rst))
+ return dev_err_probe(dev, PTR_ERR(ctrl->rst),
+ "Failed to get 'serdes' reset control\n");
+
+ ctrl->nclks = devm_clk_bulk_get_all(dev, &ctrl->clks);
+ if (ctrl->nclks < 1) {
+ ret = ctrl->nclks ? : -EINVAL;
+ return dev_err_probe(dev, ret,
+ "Failed to get SerDes clocks\n");
+ }
+
+ idx = of_property_match_string(dev->of_node, "clock-names", EXTERNAL_CLK_NAME);
+ if (idx < 0)
+ idx = of_property_match_string(dev->of_node, "clock-names", INTERNAL_CLK_NAME);
+ else
+ ctrl->ext_clk = true;
+
+ if (idx < 0) {
+ dev_err(dev, "Failed to get Phy reference clock source\n");
+ return -EINVAL;
+ }
+
+ ctrl->ref_clk_rate = clk_get_rate(ctrl->clks[idx].clk);
+ if (!ctrl->ref_clk_rate) {
+ dev_err(dev, "Failed to get Phy reference clock rate\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int s32g_serdes_get_pcie_resources(struct platform_device *pdev, struct s32g_serdes *serdes)
+{
+ struct s32g_pcie_ctrl *pcie = &serdes->pcie;
+ struct device *dev = &pdev->dev;
+
+ pcie->phy_base = devm_platform_ioremap_resource_byname(pdev,
+ "pcie_phy");
+ if (IS_ERR(pcie->phy_base)) {
+ dev_err(dev, "Failed to map 'pcie_phy'\n");
+ return PTR_ERR(pcie->phy_base);
+ }
+
+ pcie->rst = devm_reset_control_get_exclusive(dev, "pcie");
+ if (IS_ERR(pcie->rst))
+ return dev_err_probe(dev, IS_ERR(pcie->rst),
+ "Failed to get 'pcie' reset control\n");
+
+ return 0;
+}
+
+static int s32g2_serdes_create_phy(struct s32g_serdes *serdes, struct device_node *child_node)
+{
+ struct s32g_serdes_ctrl *ctrl = &serdes->ctrl;
+ struct phy_provider *phy_provider;
+ struct device *dev = serdes->dev;
+ int ss_mode = ctrl->ss_mode;
+ struct phy *phy;
+
+ if (of_device_is_compatible(child_node, "nxp,s32g2-serdes-pcie-phy")) {
+ /* no PCIe phy lane */
+ if (ss_mode > 2)
+ return 0;
+
+ phy = devm_phy_create(dev, child_node, &serdes_pcie_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ phy_set_drvdata(phy, serdes);
+
+ phy->attrs.mode = PHY_MODE_PCIE;
+ phy->id = 0;
+ serdes->pcie.phy = phy;
+
+ phy_provider = devm_of_phy_provider_register(&phy->dev, s32g_serdes_phy_xlate);
+ if (IS_ERR(phy_provider))
+ return PTR_ERR(phy_provider);
+
+ } else {
+ dev_warn(dev, "Skipping unknown child node %pOFn\n", child_node);
+ }
+
+ return 0;
+}
+
+static int s32g_serdes_parse_lanes(struct device *dev, struct s32g_serdes *serdes)
+{
+ int ret;
+
+ for_each_available_child_of_node_scoped(dev->of_node, of_port) {
+ ret = s32g2_serdes_create_phy(serdes, of_port);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int s32g_serdes_probe(struct platform_device *pdev)
+{
+ struct s32g_serdes *serdes;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ serdes = devm_kzalloc(dev, sizeof(*serdes), GFP_KERNEL);
+ if (!serdes)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, serdes);
+ serdes->dev = dev;
+
+ ret = s32g_serdes_get_ctrl_resources(pdev, serdes);
+ if (ret)
+ return ret;
+
+ ret = s32g_serdes_get_pcie_resources(pdev, serdes);
+ if (ret)
+ return ret;
+
+ ret = s32g_serdes_parse_lanes(dev, serdes);
+ if (ret)
+ return ret;
+
+ return s32g_serdes_init(serdes);
+}
+
+static int __maybe_unused s32g_serdes_suspend(struct device *device)
+{
+ struct s32g_serdes *serdes = dev_get_drvdata(device);
+
+ clk_bulk_disable_unprepare(serdes->ctrl.nclks, serdes->ctrl.clks);
+
+ return 0;
+}
+
+static int __maybe_unused s32g_serdes_resume(struct device *device)
+{
+ struct s32g_serdes *serdes = dev_get_drvdata(device);
+ struct s32g_pcie_ctrl *pcie = &serdes->pcie;
+ int ret;
+
+ ret = s32g_serdes_init(serdes);
+ if (ret) {
+ dev_err(device, "Failed to initialize\n");
+ return ret;
+ }
+
+ /* Restore PCIe phy power */
+ if (pcie->powered_on) {
+ ret = s32g_pcie_phy_power_on(serdes);
+ if (ret)
+ dev_err(device, "Failed to power-on PCIe phy\n");
+ }
+
+ return ret;
+}
+
+static const struct of_device_id s32g_serdes_match[] = {
+ {
+ .compatible = "nxp,s32g2-serdes",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, s32g_serdes_match);
+
+static const struct dev_pm_ops s32g_serdes_pm_ops = {
+ NOIRQ_SYSTEM_SLEEP_PM_OPS(s32g_serdes_suspend,
+ s32g_serdes_resume)
+};
+
+static struct platform_driver s32g_serdes_driver = {
+ .probe = s32g_serdes_probe,
+ .driver = {
+ .name = "phy-s32g-serdes",
+ .of_match_table = s32g_serdes_match,
+ .pm = &s32g_serdes_pm_ops,
+ },
+};
+module_platform_driver(s32g_serdes_driver);
+
+MODULE_AUTHOR("Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>");
+MODULE_DESCRIPTION("S32CC SerDes driver");
+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 1/4 v2] dt-bindings: serdes: s32g: Add NXP serdes subsystem
From: Vincent Guittot @ 2026-02-03 16:19 UTC (permalink / raw)
To: vkoul, neil.armstrong, krzk+dt, conor+dt, ciprianmarian.costea,
s32, p.zabel, linux, ghennadi.procopciuc, Ionut.Vicovan,
linux-phy, devicetree, linux-kernel, linux-arm-kernel, netdev,
horms
Cc: Frank.li
In-Reply-To: <20260203161917.1666696-1-vincent.guittot@linaro.org>
Describe the serdes subsystem available on the S32G platforms.
Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
---
.../bindings/phy/nxp,s32g-serdes.yaml | 154 ++++++++++++++++++
1 file changed, 154 insertions(+)
create mode 100644 Documentation/devicetree/bindings/phy/nxp,s32g-serdes.yaml
diff --git a/Documentation/devicetree/bindings/phy/nxp,s32g-serdes.yaml b/Documentation/devicetree/bindings/phy/nxp,s32g-serdes.yaml
new file mode 100644
index 000000000000..fad34bee2a4f
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/nxp,s32g-serdes.yaml
@@ -0,0 +1,154 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/nxp,s32g-serdes.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP S32G2xxx/S32G3xxx SerDes PHY subsystem
+
+maintainers:
+ - Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
+
+description: |
+ The SerDes subsystem on S32G SoC Family includes two types of PHYs:
+ - One PCIe PHY: Supports various PCIe operation modes
+ - Two Ethernet Physical Coding Sublayer (XPCS) controllers
+
+ SerDes operation mode selects the enabled PHYs and speeds. Clock frequency
+ must be adapted accordingly. Below table describes all possible operation
+ modes.
+
+ Mode PCIe XPCS0 XPCS1 PHY clock Description
+ SGMII SGMII (MHz)
+ -------------------------------------------------------------------------
+ 0 Gen3 N/A N/A 100 Single PCIe
+ 1 Gen2 1.25Gbps N/A 100 PCIe/SGMII
+ 2 Gen2 N/A 1.25Gbps 100 PCIe/SGMII
+ 3 N/A 1.25Gbps 1.25Gbps 100,125 SGMII
+ 4 N/A 3.125/1.25Gbps 3.125/1.25Gbps 125 SGMII
+ 5 Gen2 N/A 3.125Gbps 100 PCIe/SGMII
+
+properties:
+ compatible:
+ oneOf:
+ - enum:
+ - nxp,s32g2-serdes
+ - items:
+ - const: nxp,s32g3-serdes
+ - const: nxp,s32g2-serdes
+
+ reg:
+ maxItems: 4
+
+ reg-names:
+ items:
+ - const: ss_pcie
+ - const: pcie_phy
+ - const: xpcs0
+ - const: xpcs1
+
+ clocks:
+ minItems: 4
+ maxItems: 5
+
+ clock-names:
+ items:
+ - const: axi
+ - const: aux
+ - const: apb
+ - const: ref
+ - const: ext
+ minItems: 4
+
+ resets:
+ maxItems: 2
+
+ reset-names:
+ items:
+ - const: serdes
+ - const: pcie
+
+ nxp,sys-mode:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ SerDes operational mode. See above table for possible values.
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+patternProperties:
+ '^serdes[0,1]_lane@[0,1]$':
+ description:
+ Describe a serdes lane.
+ type: object
+
+ properties:
+ compatible:
+ enum:
+ - nxp,s32g2-serdes-pcie-phy
+ - nxp,s32g2-serdes-xpcs
+
+ reg:
+ maxItems: 1
+
+ '#phy-cells':
+ const: 0
+
+ required:
+ - reg
+ - compatible
+
+ unevaluatedProperties: false
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - clocks
+ - clock-names
+ - resets
+ - reset-names
+ - nxp,sys-mode
+ - '#address-cells'
+ - '#size-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ bus {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ serdes0: serdes@40480000 {
+ compatible = "nxp,s32g3-serdes", "nxp,s32g2-serdes";
+ reg = <0x0 0x40480000 0x0 0x108>,
+ <0x0 0x40483008 0x0 0x10>,
+ <0x0 0x40482000 0x0 0x800>,
+ <0x0 0x40482800 0x0 0x800>;
+ reg-names = "ss_pcie", "pcie_phy", "xpcs0", "xpcs1";
+ clocks = <&clks 1>,
+ <&clks 2>,
+ <&clks 3>,
+ <&clks 4>,
+ <&serdes_100_ext>;
+ clock-names = "axi", "aux", "apb", "ref", "ext";
+ resets = <&reset 9>,
+ <&reset 8>;
+ reset-names = "serdes", "pcie";
+ nxp,sys-mode = <1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ phy_pcie0: serdes0_lane@0 {
+ compatible = "nxp,s32g2-serdes-pcie-phy";
+ #phy-cells = <0>;
+ reg = <0>;
+ };
+ phy_xpcs0_0: serdes0_lane@1 {
+ compatible = "nxp,s32g2-serdes-xpcs";
+ reg = <0>;
+ };
+ };
+ };
--
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/4 v2] Serdes: s32g: Add support for serdes subsystem
From: Vincent Guittot @ 2026-02-03 16:19 UTC (permalink / raw)
To: vkoul, neil.armstrong, krzk+dt, conor+dt, ciprianmarian.costea,
s32, p.zabel, linux, ghennadi.procopciuc, Ionut.Vicovan,
linux-phy, devicetree, linux-kernel, linux-arm-kernel, netdev,
horms
Cc: Frank.li
s32g SoC family includes 2 serdes subsystems which are made of one PCIe
controller, 2 XPCS and a shared Phy. The Phy got 2 lanes that can be
configured to output PCIe lanes and/or SGMII.
Implement PCIe phy and XPCS support.
Change since v1:
- Fix compile_test
- Use devm_reset_control_get_exclusive()
- Fix s32g_serdes_phy_set_mode_ext()
- Manage devm_clk_bulk_get_all() returns 0
- Fix s32g_serdes_parse_lanes() error management
- Move xpcs filein drivers/net/pcs/
- Add pcs_inband_caps()
- Fix functions in phylink_pcs_ops
- Fix MAINTAINERS
Vincent Guittot (4):
dt-bindings: serdes: s32g: Add NXP serdes subsystem
phy: s32g: Add serdes subsystem phy
phy: s32g: Add serdes xpcs subsystem
MAINTAINERS: Add MAINTAINER for NXP S32G Serdes driver
.../bindings/phy/nxp,s32g-serdes.yaml | 154 +++
MAINTAINERS | 10 +
drivers/net/pcs/Makefile | 1 +
drivers/net/pcs/pcs-nxp-s32g-xpcs.c | 1006 +++++++++++++++++
drivers/phy/freescale/Kconfig | 10 +
drivers/phy/freescale/Makefile | 1 +
drivers/phy/freescale/phy-nxp-s32g-serdes.c | 953 ++++++++++++++++
include/linux/pcs/pcs-nxp-s32g-xpcs.h | 50 +
8 files changed, 2185 insertions(+)
create mode 100644 Documentation/devicetree/bindings/phy/nxp,s32g-serdes.yaml
create mode 100644 drivers/net/pcs/pcs-nxp-s32g-xpcs.c
create mode 100644 drivers/phy/freescale/phy-nxp-s32g-serdes.c
create mode 100644 include/linux/pcs/pcs-nxp-s32g-xpcs.h
--
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 v8 2/7] mux: Add helper functions for getting optional and selected mux-state
From: Josua Mayer @ 2026-02-03 15:35 UTC (permalink / raw)
To: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang
Cc: Yazan Shhady, Jon Nettleton, Mikhail Anikin,
linux-can@vger.kernel.org, linux-phy@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-omap@vger.kernel.org,
linux-i2c@vger.kernel.org, linux-mmc@vger.kernel.org,
devicetree@vger.kernel.org, linux-renesas-soc@vger.kernel.org,
Dan Carpenter
In-Reply-To: <20260203-rz-sdio-mux-v8-2-024ea405863e@solid-run.com>
On 03/02/2026 15:01, Josua Mayer wrote:
> In-tree phy-can-transceiver driver has already implemented a local
> version of devm_mux_state_get_optional.
>
> The omap-i2c driver gets and selects an optional mux in its probe
> function without using any helper.
>
> Add new helper functions covering both aforementioned use-cases:
>
> - mux_control_get_optional:
> Get a mux-control if specified in dt, return NULL otherwise.
> - devm_mux_state_get_optional:
> Get a mux-state if specified in dt, return NULL otherwise.
> - devm_mux_state_get_selected:
> Get and select a mux-state specified in dt, return error otherwise.
> - devm_mux_state_get_optional_selected:
> Get and select a mux-state if specified in dt, return error or NULL.
>
> Existing mux_get helper function is changed to take an extra argument
> indicating whether the mux is optional.
> In this case no error is printed, and NULL returned in case of ENOENT.
>
> Calling code is adapted to handle NULL return case, and to pass optional
> argument as required.
>
> To support automatic deselect for _selected helper, a new structure is
> created storing an exit pointer similar to clock core which is called on
> release.
>
> To facilitate code sharing between optional/mandatory/selected helpers,
> a new internal helper function is added to handle quiet (optional) and
> verbose (mandatory) errors, as well as storing the correct callback for
> devm release: __devm_mux_state_get
>
> Due to this structure devm_mux_state_get_*_selected can no longer print
> a useful error message when select fails. Instead callers should print
> errors where needed.
>
> Commit e153fdea9db04 ("phy: can-transceiver: Re-instate "mux-states"
> property presence check") noted that "mux_get() always prints an error
> message in case of an error, including when the property is not present,
> confusing the user."
>
> The first error message covers the case that a mux name is not matched
> in dt. The second error message is based on of_parse_phandle_with_args
> return value.
>
> In optional case no error is printed and NULL is returned.
> This ensures that the new helper functions will not confuse the user
> either.
>
> With the addition of optional helper functions it became clear that
> drivers should compile and link even if CONFIG_MULTIPLEXER was not enabled.
> Add stubs for all symbols exported by mux core.
>
> Signed-off-by: Josua Mayer <josua@solid-run.com>
> ---
> drivers/mux/core.c | 205 ++++++++++++++++++++++++++++++++++++-------
> include/linux/mux/consumer.h | 108 ++++++++++++++++++++++-
> 2 files changed, 278 insertions(+), 35 deletions(-)
>
> diff --git a/drivers/mux/core.c b/drivers/mux/core.c
> index a3840fe0995f..2e8295e3aabe 100644
> --- a/drivers/mux/core.c
> +++ b/drivers/mux/core.c
> @@ -46,6 +46,16 @@ static const struct class mux_class = {
> .name = "mux",
> };
>
> +/**
> + * struct devm_mux_state_state - Tracks managed resources for mux-state objects.
> + * @mstate: Pointer to a mux state.
> + * @exit: An optional callback to execute before free.
> + */
> +struct devm_mux_state_state {
> + struct mux_state *mstate;
> + int (*exit)(struct mux_state *mstate);
> +};
> +
> static DEFINE_IDA(mux_ida);
>
> static int __init mux_init(void)
> @@ -516,17 +526,19 @@ static struct mux_chip *of_find_mux_chip_by_node(struct device_node *np)
> return dev ? to_mux_chip(dev) : NULL;
> }
>
> -/*
> +/**
> * mux_get() - Get the mux-control for a device.
> * @dev: The device that needs a mux-control.
> * @mux_name: The name identifying the mux-control.
> * @state: Pointer to where the requested state is returned, or NULL when
> * the required multiplexer states are handled by other means.
> + * @optional: Whether to return NULL and silence errors when mux doesn't exist.
> *
> - * Return: A pointer to the mux-control, or an ERR_PTR with a negative errno.
> + * Return: Pointer to the mux-control on success, an ERR_PTR with a negative errno on error,
> + * or NULL if optional is true and mux doesn't exist.
> */
> static struct mux_control *mux_get(struct device *dev, const char *mux_name,
> - unsigned int *state)
> + unsigned int *state, bool optional)
> {
> struct device_node *np = dev->of_node;
> struct of_phandle_args args;
> @@ -542,7 +554,9 @@ static struct mux_control *mux_get(struct device *dev, const char *mux_name,
> else
> index = of_property_match_string(np, "mux-control-names",
> mux_name);
> - if (index < 0) {
> + if (index < 0 && optional) {
> + return NULL;
> + } else if (index < 0) {
> dev_err(dev, "mux controller '%s' not found\n",
> mux_name);
> return ERR_PTR(index);
> @@ -558,8 +572,12 @@ static struct mux_control *mux_get(struct device *dev, const char *mux_name,
> "mux-controls", "#mux-control-cells",
> index, &args);
> if (ret) {
> + if (optional && ret == -ENOENT)
> + return NULL;
> +
> dev_err(dev, "%pOF: failed to get mux-%s %s(%i)\n",
> - np, state ? "state" : "control", mux_name ?: "", index);
> + np, state ? "state" : "control",
> + mux_name ?: "", index);
> return ERR_PTR(ret);
> }
>
> @@ -617,10 +635,28 @@ static struct mux_control *mux_get(struct device *dev, const char *mux_name,
> */
> struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
> {
> - return mux_get(dev, mux_name, NULL);
> + struct mux_control *mux = mux_get(dev, mux_name, NULL, false);
> +
> + if (!mux)
> + return ERR_PTR(-ENOENT);
> +
> + return mux;
> }
> EXPORT_SYMBOL_GPL(mux_control_get);
>
> +/**
> + * mux_control_get_optional() - Get the optional mux-control for a device.
> + * @dev: The device that needs a mux-control.
> + * @mux_name: The name identifying the mux-control.
> + *
> + * Return: A pointer to the mux-control, an ERR_PTR with a negative errno.
* Return: Pointer to the mux-state on success, an ERR_PTR with a
negative errno on error,
* or NULL if mux doesn't exist.
If there will be v9, I shall change this to be more precise similar to
other functions.
> + */
> +struct mux_control *mux_control_get_optional(struct device *dev, const char *mux_name)
> +{
> + return mux_get(dev, mux_name, NULL, true);
> +}
> +EXPORT_SYMBOL_GPL(mux_control_get_optional);
> +
> /**
> * mux_control_put() - Put away the mux-control for good.
> * @mux: The mux-control to put away.
> @@ -657,10 +693,13 @@ struct mux_control *devm_mux_control_get(struct device *dev,
> if (!ptr)
> return ERR_PTR(-ENOMEM);
>
> - mux = mux_control_get(dev, mux_name);
> + mux = mux_get(dev, mux_name, NULL, false);
> if (IS_ERR(mux)) {
> devres_free(ptr);
> return mux;
> + } else if (!mux) {
> + devres_free(ptr);
> + return ERR_PTR(-ENOENT);
> }
>
> *ptr = mux;
> @@ -670,14 +709,16 @@ struct mux_control *devm_mux_control_get(struct device *dev,
> }
> EXPORT_SYMBOL_GPL(devm_mux_control_get);
>
> -/*
> +/**
> * mux_state_get() - Get the mux-state for a device.
> * @dev: The device that needs a mux-state.
> * @mux_name: The name identifying the mux-state.
> + * @optional: Whether to return NULL and silence errors when mux doesn't exist.
> *
> - * Return: A pointer to the mux-state, or an ERR_PTR with a negative errno.
> + * Return: Pointer to the mux-state on success, an ERR_PTR with a negative errno on error,
> + * or NULL if optional is true and mux doesn't exist.
> */
> -static struct mux_state *mux_state_get(struct device *dev, const char *mux_name)
> +static struct mux_state *mux_state_get(struct device *dev, const char *mux_name, bool optional)
> {
> struct mux_state *mstate;
>
...
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v7 2/7] mux: Add helper functions for getting optional and selected mux-state
From: Josua Mayer @ 2026-02-03 15:31 UTC (permalink / raw)
To: Dan Carpenter
Cc: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang,
Yazan Shhady, Jon Nettleton, Mikhail Anikin,
linux-can@vger.kernel.org, linux-phy@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-omap@vger.kernel.org,
linux-i2c@vger.kernel.org, linux-mmc@vger.kernel.org,
devicetree@vger.kernel.org, linux-renesas-soc@vger.kernel.org
In-Reply-To: <aYIOnErmiSZ6wnP1@stanley.mountain>
On 03/02/2026 17:05, Dan Carpenter wrote:
> Sorry about that, I just looked at the implementation in linux-next and
> didn't read the code from the patch too closely.
:)
>
> On Wed, Jan 28, 2026 at 04:46:10PM +0200, Josua Mayer wrote:
>> +/**
>> + * mux_control_get_optional() - Get the optional mux-control for a device.
>> + * @dev: The device that needs a mux-control.
>> + * @mux_name: The name identifying the mux-control.
>> + *
>> + * Return: A pointer to the mux-control, an ERR_PTR with a negative errno.
> I wish the documentation for these "optional" said it could return NULL.
Same. I fixed that for v8 on most functions, but missed one .... .
>
>> + */
>> +struct mux_control *mux_control_get_optional(struct device *dev, const char *mux_name)
>> +{
>> + return mux_get(dev, mux_name, NULL, true);
>> +}
>> +EXPORT_SYMBOL_GPL(mux_control_get_optional);
>> +
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* Re: [PATCH v7 2/7] mux: Add helper functions for getting optional and selected mux-state
From: Dan Carpenter @ 2026-02-03 15:05 UTC (permalink / raw)
To: Josua Mayer
Cc: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang,
Yazan Shhady, Jon Nettleton, Mikhail Anikin, linux-can, linux-phy,
linux-kernel, linux-omap, linux-i2c, linux-mmc, devicetree,
linux-renesas-soc
In-Reply-To: <20260128-rz-sdio-mux-v7-2-92ebb6da0df8@solid-run.com>
Sorry about that, I just looked at the implementation in linux-next and
didn't read the code from the patch too closely.
On Wed, Jan 28, 2026 at 04:46:10PM +0200, Josua Mayer wrote:
> +/**
> + * mux_control_get_optional() - Get the optional mux-control for a device.
> + * @dev: The device that needs a mux-control.
> + * @mux_name: The name identifying the mux-control.
> + *
> + * Return: A pointer to the mux-control, an ERR_PTR with a negative errno.
I wish the documentation for these "optional" said it could return NULL.
> + */
> +struct mux_control *mux_control_get_optional(struct device *dev, const char *mux_name)
> +{
> + return mux_get(dev, mux_name, NULL, true);
> +}
> +EXPORT_SYMBOL_GPL(mux_control_get_optional);
> +
regards,
dan carpenter
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply
* [PATCH v8 6/7] dt-bindings: mmc: renesas,sdhi: Add mux-states property
From: Josua Mayer @ 2026-02-03 13:01 UTC (permalink / raw)
To: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang
Cc: Yazan Shhady, Jon Nettleton, Mikhail Anikin, linux-can, linux-phy,
linux-kernel, linux-omap, linux-i2c, linux-mmc, devicetree,
linux-renesas-soc, Josua Mayer
In-Reply-To: <20260203-rz-sdio-mux-v8-0-024ea405863e@solid-run.com>
Add mux controller support for data or control lines that are muxed
between a host and multiple cards.
There are several devices supporting a choice of eMMC or SD on a single
board by both dip switch and gpio, e.g. Renesas RZ/G2L SMARC SoM and
SolidRun RZ/G2L SoM.
In-tree dts for the Renesas boards currently rely on preprocessor macros
and gpio hogs to describe the respective cards.
By adding mux-states property to sdhi controller description, boards can
correctly describe the mux that already exists in hardware - and drivers
can coordinate between mux selection and probing for cards.
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Josua Mayer <josua@solid-run.com>
---
Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml b/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml
index c754ea71f51f..64fac0d11329 100644
--- a/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml
+++ b/Documentation/devicetree/bindings/mmc/renesas,sdhi.yaml
@@ -106,6 +106,11 @@ properties:
iommus:
maxItems: 1
+ mux-states:
+ description:
+ mux controller node to route the SD/SDIO/eMMC signals from SoC to cards.
+ maxItems: 1
+
power-domains:
maxItems: 1
@@ -275,6 +280,7 @@ examples:
max-frequency = <195000000>;
power-domains = <&sysc R8A7790_PD_ALWAYS_ON>;
resets = <&cpg 314>;
+ mux-states = <&mux 0>;
};
sdhi1: mmc@ee120000 {
--
2.43.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v8 7/7] mmc: host: renesas_sdhi_core: support selecting an optional mux
From: Josua Mayer @ 2026-02-03 13:01 UTC (permalink / raw)
To: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang
Cc: Yazan Shhady, Jon Nettleton, Mikhail Anikin, linux-can, linux-phy,
linux-kernel, linux-omap, linux-i2c, linux-mmc, devicetree,
linux-renesas-soc, Josua Mayer
In-Reply-To: <20260203-rz-sdio-mux-v8-0-024ea405863e@solid-run.com>
Some hardware designs route data or control signals through a mux to
support multiple devices on a single sdhi controller.
In particular SolidRun RZ/G2L/G2LC/V2L System on Module use a mux for
switching between soldered eMMC and an optional microSD on a carrier
board, e.g. for development or provisioning.
SD/SDIO/eMMC are not well suited for runtime switching between different
cards, however boot-time selection is possible and useful - in
particular considering dt overlays.
Add support for an optional SD/SDIO/eMMC mux defined in dt, and select
it during probe.
Similar functionality already exists in other places, e.g. i2c-omap.
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Signed-off-by: Josua Mayer <josua@solid-run.com>
---
drivers/mmc/host/renesas_sdhi_core.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/mmc/host/renesas_sdhi_core.c b/drivers/mmc/host/renesas_sdhi_core.c
index 2a310a145785..f9ec78d699f4 100644
--- a/drivers/mmc/host/renesas_sdhi_core.c
+++ b/drivers/mmc/host/renesas_sdhi_core.c
@@ -26,6 +26,7 @@
#include <linux/mmc/mmc.h>
#include <linux/mmc/slot-gpio.h>
#include <linux/module.h>
+#include <linux/mux/consumer.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pinctrl/pinctrl-state.h>
#include <linux/platform_data/tmio.h>
@@ -1062,6 +1063,7 @@ int renesas_sdhi_probe(struct platform_device *pdev,
struct regulator_dev *rdev;
struct renesas_sdhi_dma *dma_priv;
struct device *dev = &pdev->dev;
+ struct mux_state *mux_state;
struct tmio_mmc_host *host;
struct renesas_sdhi *priv;
int num_irqs, irq, ret, i;
@@ -1116,6 +1118,10 @@ int renesas_sdhi_probe(struct platform_device *pdev,
"state_uhs");
}
+ mux_state = devm_mux_state_get_optional_selected(&pdev->dev, NULL);
+ if (IS_ERR(mux_state))
+ return PTR_ERR(mux_state);
+
host = tmio_mmc_host_alloc(pdev, mmc_data);
if (IS_ERR(host))
return PTR_ERR(host);
--
2.43.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
* [PATCH v8 5/7] i2c: omap: switch to new generic helper for getting selected mux-state
From: Josua Mayer @ 2026-02-03 13:01 UTC (permalink / raw)
To: Marc Kleine-Budde, Vincent Mailhol, Vinod Koul, Neil Armstrong,
Peter Rosin, Aaro Koskinen, Andreas Kemnade, Kevin Hilman,
Roger Quadros, Tony Lindgren, Janusz Krzysztofik, Vignesh R,
Andi Shyti, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Geert Uytterhoeven, Magnus Damm, Wolfram Sang
Cc: Yazan Shhady, Jon Nettleton, Mikhail Anikin, linux-can, linux-phy,
linux-kernel, linux-omap, linux-i2c, linux-mmc, devicetree,
linux-renesas-soc, Josua Mayer
In-Reply-To: <20260203-rz-sdio-mux-v8-0-024ea405863e@solid-run.com>
Multiplexer subsystem has added generic helper functions for getting an
already selected mux-state object.
Replace existing logic in probe with the equivalent helper function.
There is a functional difference in that the mux is now automatically
deselected on release, replacing the explicit mux_state_deselect call.
This change is only compile-tested.
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Reviewed-by: Andreas Kemnade <andreas@kemnade.info>
Signed-off-by: Josua Mayer <josua@solid-run.com>
---
drivers/i2c/busses/i2c-omap.c | 24 +++++-------------------
1 file changed, 5 insertions(+), 19 deletions(-)
diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c
index d9f590f0c384..f02d294db42a 100644
--- a/drivers/i2c/busses/i2c-omap.c
+++ b/drivers/i2c/busses/i2c-omap.c
@@ -1453,27 +1453,16 @@ omap_i2c_probe(struct platform_device *pdev)
(1000 * omap->speed / 8);
}
- if (of_property_present(node, "mux-states")) {
- struct mux_state *mux_state;
-
- mux_state = devm_mux_state_get(&pdev->dev, NULL);
- if (IS_ERR(mux_state)) {
- r = PTR_ERR(mux_state);
- dev_dbg(&pdev->dev, "failed to get I2C mux: %d\n", r);
- goto err_put_pm;
- }
- omap->mux_state = mux_state;
- r = mux_state_select(omap->mux_state);
- if (r) {
- dev_err(&pdev->dev, "failed to select I2C mux: %d\n", r);
- goto err_put_pm;
- }
+ omap->mux_state = devm_mux_state_get_optional_selected(&pdev->dev, NULL);
+ if (IS_ERR(omap->mux_state)) {
+ r = PTR_ERR(omap->mux_state);
+ goto err_put_pm;
}
/* reset ASAP, clearing any IRQs */
r = omap_i2c_init(omap);
if (r)
- goto err_mux_state_deselect;
+ goto err_put_pm;
if (omap->rev < OMAP_I2C_OMAP1_REV_2)
r = devm_request_irq(&pdev->dev, omap->irq, omap_i2c_omap1_isr,
@@ -1515,9 +1504,6 @@ omap_i2c_probe(struct platform_device *pdev)
err_unuse_clocks:
omap_i2c_write_reg(omap, OMAP_I2C_CON_REG, 0);
-err_mux_state_deselect:
- if (omap->mux_state)
- mux_state_deselect(omap->mux_state);
err_put_pm:
pm_runtime_put_sync(omap->dev);
err_disable_pm:
--
2.43.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related
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