From 72281c58fa2cc4324db388d9140f154762aa9dd1 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 14 Apr 2025 20:21:50 -0500 Subject: [PATCH 1/5] usb: dwc3: core: Expose core driver as library mainline inclusion from mainline-7.2-rc1 commit 613a2e655d4d ("usb: dwc3: core: Expose core driver as library") category: feature bugzilla: https://github.com/RVCK-Project/rvck/issues/322 And the dwc3_properties structure and the `ignore_clocks_and_resets` flag have been added to glue.h so that generic glue can use them directly without further modifications to the header file. -------------------------------- The DWC3 IP block is handled by three distinct device drivers: XHCI, DWC3 core and a platform specific (optional) DWC3 glue driver. This has resulted in, at least in the case of the Qualcomm glue, the presence of a number of layering violations, where the glue code either can't handle, or has to work around, the fact that core might not probe deterministically. An example of this is that the suspend path should operate slightly different depending on the device operating in host or peripheral mode, and the only way to determine the operating state is to peek into the core's drvdata. The Qualcomm glue driver is expected to make updates in the qscratch register region (the "glue" region) during role switch events, but with the glue and core split using the driver model, there is no reasonable way to introduce listeners for mode changes. Split the dwc3 core platform_driver callbacks and their implementation and export the implementation, to make it possible to deterministically instantiate the dwc3 core as part of the dwc3 glue drivers and to allow flattening of the DeviceTree representation. Acked-by: Thinh Nguyen Tested-by: Neil Armstrong # on SM8650-QRD Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20250414-dwc3-refactor-v7-3-f015b358722d@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman Signed-off-by: Yongchao Jia --- drivers/usb/dwc3/core.c | 159 +++++++++++++++++++++++++++------------- drivers/usb/dwc3/core.h | 2 + drivers/usb/dwc3/glue.h | 51 +++++++++++++ 3 files changed, 160 insertions(+), 52 deletions(-) create mode 100644 drivers/usb/dwc3/glue.h diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index c00d6328f052..6a57c494b968 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -37,6 +37,7 @@ #include "core.h" #include "gadget.h" +#include "glue.h" #include "io.h" #include "debug.h" @@ -1940,26 +1941,15 @@ static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc) return usb_psy; } -static int dwc3_probe(struct platform_device *pdev) +int dwc3_core_probe(const struct dwc3_probe_data *data) { - struct device *dev = &pdev->dev; - struct resource *res, dwc_res; + struct dwc3 *dwc = data->dwc; + struct device *dev = dwc->dev; + struct resource dwc_res; void __iomem *regs; - struct dwc3 *dwc; + struct resource *res = data->res; int ret; - dwc = devm_kzalloc(dev, sizeof(*dwc), GFP_KERNEL); - if (!dwc) - return -ENOMEM; - - dwc->dev = dev; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "missing memory resource\n"); - return -ENODEV; - } - dwc->xhci_resources[0].start = res->start; dwc->xhci_resources[0].end = dwc->xhci_resources[0].start + DWC3_XHCI_REGS_END; @@ -1997,15 +1987,17 @@ static int dwc3_probe(struct platform_device *pdev) if (IS_ERR(dwc->usb_psy)) return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n"); - dwc->reset = devm_reset_control_array_get_optional_shared(dev); - if (IS_ERR(dwc->reset)) { - ret = PTR_ERR(dwc->reset); - goto err_put_psy; - } + if (!data->ignore_clocks_and_resets) { + dwc->reset = devm_reset_control_array_get_optional_shared(dev); + if (IS_ERR(dwc->reset)) { + ret = PTR_ERR(dwc->reset); + goto err_put_psy; + } - ret = dwc3_get_clocks(dwc); - if (ret) - goto err_put_psy; + ret = dwc3_get_clocks(dwc); + if (ret) + goto err_put_psy; + } ret = reset_control_deassert(dwc->reset); if (ret) @@ -2021,7 +2013,7 @@ static int dwc3_probe(struct platform_device *pdev) goto err_disable_clks; } - platform_set_drvdata(pdev, dwc); + dev_set_drvdata(dev, dwc); dwc3_cache_hwparams(dwc); if (!dev_is_pci(dwc->sysdev) && @@ -2103,12 +2095,35 @@ static int dwc3_probe(struct platform_device *pdev) return ret; } +EXPORT_SYMBOL_GPL(dwc3_core_probe); -static void dwc3_remove(struct platform_device *pdev) +static int dwc3_probe(struct platform_device *pdev) { - struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_probe_data probe_data = {}; + struct resource *res; + struct dwc3 *dwc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + dwc = devm_kzalloc(&pdev->dev, sizeof(*dwc), GFP_KERNEL); + if (!dwc) + return -ENOMEM; + + dwc->dev = &pdev->dev; + + probe_data.dwc = dwc; + probe_data.res = res; - pm_runtime_get_sync(&pdev->dev); + return dwc3_core_probe(&probe_data); +} + +void dwc3_core_remove(struct dwc3 *dwc) +{ + pm_runtime_get_sync(dwc->dev); dwc3_core_exit_mode(dwc); dwc3_debugfs_exit(dwc); @@ -2116,16 +2131,16 @@ static void dwc3_remove(struct platform_device *pdev) dwc3_core_exit(dwc); dwc3_ulpi_exit(dwc); - pm_runtime_allow(&pdev->dev); - pm_runtime_disable(&pdev->dev); - pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_put_noidle(&pdev->dev); + pm_runtime_allow(dwc->dev); + pm_runtime_disable(dwc->dev); + pm_runtime_dont_use_autosuspend(dwc->dev); + pm_runtime_put_noidle(dwc->dev); /* * HACK: Clear the driver data, which is currently accessed by parent * glue drivers, before allowing the parent to suspend. */ - platform_set_drvdata(pdev, NULL); - pm_runtime_set_suspended(&pdev->dev); + dev_set_drvdata(dwc->dev, NULL); + pm_runtime_set_suspended(dwc->dev); dwc3_free_event_buffers(dwc); @@ -2134,6 +2149,12 @@ static void dwc3_remove(struct platform_device *pdev) power_supply_put(dwc->usb_psy); } } +EXPORT_SYMBOL_GPL(dwc3_core_remove); + +static void dwc3_remove(struct platform_device *pdev) +{ + dwc3_core_remove(platform_get_drvdata(pdev)); +} #ifdef CONFIG_PM static int dwc3_core_init_for_resume(struct dwc3 *dwc) @@ -2317,9 +2338,8 @@ static int dwc3_runtime_checks(struct dwc3 *dwc) return 0; } -static int dwc3_runtime_suspend(struct device *dev) +int dwc3_runtime_suspend(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); int ret; if (dwc3_runtime_checks(dwc)) @@ -2331,10 +2351,11 @@ static int dwc3_runtime_suspend(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_runtime_suspend); -static int dwc3_runtime_resume(struct device *dev) +int dwc3_runtime_resume(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; int ret; ret = dwc3_resume_common(dwc, PMSG_AUTO_RESUME); @@ -2344,7 +2365,7 @@ static int dwc3_runtime_resume(struct device *dev) switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: if (dwc->pending_events) { - pm_runtime_put(dwc->dev); + pm_runtime_put(dev); dwc->pending_events = false; enable_irq(dwc->irq_gadget); } @@ -2359,10 +2380,11 @@ static int dwc3_runtime_resume(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_runtime_resume); -static int dwc3_runtime_idle(struct device *dev) +int dwc3_runtime_idle(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; switch (dwc->current_dr_role) { case DWC3_GCTL_PRTCAP_DEVICE: @@ -2380,12 +2402,28 @@ static int dwc3_runtime_idle(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_runtime_idle); + +static int dwc3_plat_runtime_suspend(struct device *dev) +{ + return dwc3_runtime_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_plat_runtime_resume(struct device *dev) +{ + return dwc3_runtime_resume(dev_get_drvdata(dev)); +} + +static int dwc3_plat_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} #endif /* CONFIG_PM */ #ifdef CONFIG_PM_SLEEP -static int dwc3_suspend(struct device *dev) +int dwc3_pm_suspend(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; int ret; ret = dwc3_suspend_common(dwc, PMSG_SUSPEND); @@ -2396,10 +2434,11 @@ static int dwc3_suspend(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_pm_suspend); -static int dwc3_resume(struct device *dev) +int dwc3_pm_resume(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); + struct device *dev = dwc->dev; int ret; pinctrl_pm_select_default_state(dev); @@ -2414,10 +2453,10 @@ static int dwc3_resume(struct device *dev) return 0; } +EXPORT_SYMBOL_GPL(dwc3_pm_resume); -static void dwc3_complete(struct device *dev) +void dwc3_pm_complete(struct dwc3 *dwc) { - struct dwc3 *dwc = dev_get_drvdata(dev); u32 reg; if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && @@ -2427,21 +2466,37 @@ static void dwc3_complete(struct device *dev) dwc3_writel(dwc->regs, DWC3_GUCTL3, reg); } } +EXPORT_SYMBOL_GPL(dwc3_pm_complete); + +static int dwc3_plat_suspend(struct device *dev) +{ + return dwc3_pm_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_plat_resume(struct device *dev) +{ + return dwc3_pm_resume(dev_get_drvdata(dev)); +} + +static void dwc3_plat_complete(struct device *dev) +{ + dwc3_pm_complete(dev_get_drvdata(dev)); +} #else -#define dwc3_complete NULL +#define dwc3_plat_complete NULL #endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops dwc3_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(dwc3_suspend, dwc3_resume) - .complete = dwc3_complete, + SET_SYSTEM_SLEEP_PM_OPS(dwc3_plat_suspend, dwc3_plat_resume) + .complete = dwc3_plat_complete, /* * Runtime suspend halts the controller on disconnection. It relies on * platforms with custom connection notification to start the controller * again. */ - SET_RUNTIME_PM_OPS(dwc3_runtime_suspend, dwc3_runtime_resume, - dwc3_runtime_idle) + SET_RUNTIME_PM_OPS(dwc3_plat_runtime_suspend, dwc3_plat_runtime_resume, + dwc3_plat_runtime_idle) }; #ifdef CONFIG_OF diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 45084ca66806..699a665d1119 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -184,6 +184,8 @@ #define DWC3_GSBUSCFG0_INCR4BRSTENA (1 << 1) /* INCR4 burst */ #define DWC3_GSBUSCFG0_INCRBRSTENA (1 << 0) /* undefined length enable */ #define DWC3_GSBUSCFG0_INCRBRST_MASK 0xff +#define DWC3_GSBUSCFG0_REQINFO(n) (((n) & 0xffff) << 16) +#define DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED 0xffffffff /* Global Debug LSP MUX Select */ #define DWC3_GDBGLSPMUX_ENDBC BIT(15) /* Host only */ diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h new file mode 100644 index 000000000000..4ec09f32d8b9 --- /dev/null +++ b/drivers/usb/dwc3/glue.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * glue.h - DesignWare USB3 DRD glue header + */ + +#ifndef __DRIVERS_USB_DWC3_GLUE_H +#define __DRIVERS_USB_DWC3_GLUE_H + +#include +#include "core.h" + +/** + * dwc3_properties: DWC3 core properties + * @gsbuscfg0_reqinfo: Value to be programmed in the GSBUSCFG0.REQINFO field + * @needs_full_reinit: indicate the controller may not remain power during system + * pm and need full initialization + */ +struct dwc3_properties { + u32 gsbuscfg0_reqinfo; + unsigned needs_full_reinit:1; +}; + +#define DWC3_DEFAULT_PROPERTIES ((struct dwc3_properties){ \ + .gsbuscfg0_reqinfo = DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED, \ + }) + +/** + * dwc3_probe_data: Initialization parameters passed to dwc3_core_probe() + * @dwc: Reference to dwc3 context structure + * @res: resource for the DWC3 core mmio region + * @ignore_clocks_and_resets: clocks and resets defined for the device should + * be ignored by the DWC3 core, as they are managed by the glue + */ +struct dwc3_probe_data { + struct dwc3 *dwc; + struct resource *res; + bool ignore_clocks_and_resets; + struct dwc3_properties properties; +}; + +int dwc3_core_probe(const struct dwc3_probe_data *data); +void dwc3_core_remove(struct dwc3 *dwc); + +int dwc3_runtime_suspend(struct dwc3 *dwc); +int dwc3_runtime_resume(struct dwc3 *dwc); +int dwc3_runtime_idle(struct dwc3 *dwc); +int dwc3_pm_suspend(struct dwc3 *dwc); +int dwc3_pm_resume(struct dwc3 *dwc); +void dwc3_pm_complete(struct dwc3 *dwc); + +#endif From 0eb5650fdb178d12be7946e90e144b5d9cd9a0d0 Mon Sep 17 00:00:00 2001 From: Ze Huang Date: Sat, 13 Sep 2025 00:53:47 +0800 Subject: [PATCH 2/5] usb: dwc3: add generic driver to support flattened mainline inclusion from mainline-7.2-rc1 commit e0b6dc00c701 ("usb: dwc3: add generic driver to support flattened") category: feature bugzilla: https://github.com/RVCK-Project/rvck/issues/322 The compatible string has been updated from "spacemit,k1-dwc3" to "spacemit,k3-dwc3" to match the actual hardware platform being adapted. -------------------------------- To support flattened dwc3 dt model and drop the glue layer, introduce the `dwc3-generic` driver. This enables direct binding of the DWC3 core driver and offers an alternative to the existing glue driver `dwc3-of-simple`. Acked-by: Thinh Nguyen Signed-off-by: Ze Huang Link: https://lore.kernel.org/r/20250913-dwc3_generic-v8-2-b50f81f05f95@linux.dev Signed-off-by: Greg Kroah-Hartman Signed-off-by: Yongchao Jia --- drivers/usb/dwc3/Kconfig | 11 ++ drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-generic-plat.c | 170 +++++++++++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 drivers/usb/dwc3/dwc3-generic-plat.c diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 000bfa3a4ddf..0d1ebf4c8a41 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -198,4 +198,15 @@ config USB_DWC3_THEAD Only the host mode is currently supported. Say 'Y' or 'M' here if you have one such device. +config USB_DWC3_GENERIC_PLAT + tristate "DWC3 Generic Platform Driver" + depends on OF && COMMON_CLK + default USB_DWC3 + help + Support USB3 functionality in simple SoC integrations. + Currently supports SpacemiT DWC USB3. Platforms using + dwc3-of-simple can easily switch to dwc3-generic by flattening + the dwc3 child node in the device tree. + Say 'Y' or 'M' here if your platform integrates DWC3 in a similar way. + endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 79afa5c03c10..dc6b1e9f6d69 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -57,3 +57,4 @@ obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o obj-$(CONFIG_USB_DWC3_THEAD) += dwc3-thead.o +obj-$(CONFIG_USB_DWC3_GENERIC_PLAT) += dwc3-generic-plat.o diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c new file mode 100644 index 000000000000..36d392ec615d --- /dev/null +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dwc3-generic-plat.c - DesignWare USB3 generic platform driver + * + * Copyright (C) 2025 Ze Huang + * + * Inspired by dwc3-qcom.c and dwc3-of-simple.c + */ + +#include +#include +#include +#include "glue.h" + +struct dwc3_generic { + struct device *dev; + struct dwc3 dwc; + struct clk_bulk_data *clks; + int num_clocks; + struct reset_control *resets; +}; + +#define to_dwc3_generic(d) container_of((d), struct dwc3_generic, dwc) + +static void dwc3_generic_reset_control_assert(void *data) +{ + reset_control_assert(data); +} + +static int dwc3_generic_probe(struct platform_device *pdev) +{ + struct dwc3_probe_data probe_data = {}; + struct device *dev = &pdev->dev; + struct dwc3_generic *dwc3g; + struct resource *res; + int ret; + + dwc3g = devm_kzalloc(dev, sizeof(*dwc3g), GFP_KERNEL); + if (!dwc3g) + return -ENOMEM; + + dwc3g->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + dwc3g->resets = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(dwc3g->resets)) + return dev_err_probe(dev, PTR_ERR(dwc3g->resets), "failed to get resets\n"); + + ret = reset_control_assert(dwc3g->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to assert resets\n"); + + /* Not strict timing, just for safety */ + udelay(2); + + ret = reset_control_deassert(dwc3g->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert resets\n"); + + ret = devm_add_action_or_reset(dev, dwc3_generic_reset_control_assert, dwc3g->resets); + if (ret) + return ret; + + ret = devm_clk_bulk_get_all(dwc3g->dev, &dwc3g->clks); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get clocks\n"); + + dwc3g->num_clocks = ret; + + ret = clk_bulk_prepare_enable(dwc3g->num_clocks, dwc3g->clks); + if (ret) + return dev_err_probe(dev, ret, "failed to enable clocks\n"); + dwc3g->dwc.dev = dev; + probe_data.dwc = &dwc3g->dwc; + probe_data.res = res; + probe_data.ignore_clocks_and_resets = true; + ret = dwc3_core_probe(&probe_data); + if (ret) + return dev_err_probe(dev, ret, "failed to register DWC3 Core\n"); + + return 0; +} + +static void dwc3_generic_remove(struct platform_device *pdev) +{ + struct dwc3 *dwc = platform_get_drvdata(pdev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + + dwc3_core_remove(dwc); + + clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks); +} + +static int dwc3_generic_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + int ret; + + ret = dwc3_pm_suspend(dwc); + if (ret) + return ret; + + clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks); + + return 0; +} + +static int dwc3_generic_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_generic *dwc3g = to_dwc3_generic(dwc); + int ret; + + ret = clk_bulk_prepare_enable(dwc3g->num_clocks, dwc3g->clks); + if (ret) + return ret; + + ret = dwc3_pm_resume(dwc); + if (ret) + return ret; + + return 0; +} + +static int dwc3_generic_runtime_suspend(struct device *dev) +{ + return dwc3_runtime_suspend(dev_get_drvdata(dev)); +} + +static int dwc3_generic_runtime_resume(struct device *dev) +{ + return dwc3_runtime_resume(dev_get_drvdata(dev)); +} + +static int dwc3_generic_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} + +static const struct dev_pm_ops dwc3_generic_dev_pm_ops = { + SYSTEM_SLEEP_PM_OPS(dwc3_generic_suspend, dwc3_generic_resume) + RUNTIME_PM_OPS(dwc3_generic_runtime_suspend, dwc3_generic_runtime_resume, + dwc3_generic_runtime_idle) +}; + +static const struct of_device_id dwc3_generic_of_match[] = { + { .compatible = "spacemit,k3-dwc3", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dwc3_generic_of_match); + +static struct platform_driver dwc3_generic_driver = { + .probe = dwc3_generic_probe, + .remove_new = dwc3_generic_remove, + .driver = { + .name = "dwc3-generic-plat", + .of_match_table = dwc3_generic_of_match, + .pm = pm_ptr(&dwc3_generic_dev_pm_ops), + }, +}; +module_platform_driver(dwc3_generic_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare USB3 generic platform driver"); From 90b3120e6e75c9e85fe2a89c01c626c47a924415 Mon Sep 17 00:00:00 2001 From: Ze Huang Date: Fri, 17 Oct 2025 22:49:52 +0800 Subject: [PATCH 3/5] phy: spacemit: support K1 USB2.0 PHY controller mainline inclusion from mainline-7.2-rc1 commit fe4bc1a08638 ("phy: spacemit: support K1 USB2.0 PHY controller") category: feature bugzilla: https://github.com/RVCK-Project/rvck/issues/322 -------------------------------- The SpacemiT K1 SoC includes three USB ports: - One USB2.0 OTG port - One USB2.0 host-only port - One USB3.0 port with an integrated USB2.0 DRD interface Each of these ports is connected to a USB2.0 PHY responsible for USB2 transmission. This commit adds support for the SpacemiT K1 USB2.0 PHY, which is compliant with the USB 2.0 specification and supports both 8-bit 60MHz and 16-bit 30MHz parallel interfaces. Signed-off-by: Ze Huang Tested-by: Aurelien Jarno Tested-by: Junzhong Pan Link: https://patch.msgid.link/20251017-k1-usb2phy-v6-2-7cf9ea2477a1@linux.dev Signed-off-by: Vinod Koul Signed-off-by: Yongchao Jia --- drivers/phy/Kconfig | 1 + drivers/phy/Makefile | 1 + drivers/phy/spacemit/Kconfig | 13 ++ drivers/phy/spacemit/Makefile | 2 + drivers/phy/spacemit/phy-k1-usb2.c | 210 +++++++++++++++++++++++++++++ 5 files changed, 227 insertions(+) create mode 100644 drivers/phy/spacemit/Kconfig create mode 100644 drivers/phy/spacemit/Makefile create mode 100644 drivers/phy/spacemit/phy-k1-usb2.c diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 50f729360df1..852b71aa402a 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -99,5 +99,6 @@ source "drivers/phy/ti/Kconfig" source "drivers/phy/intel/Kconfig" source "drivers/phy/xilinx/Kconfig" source "drivers/phy/synopsys/Kconfig" +source "drivers/phy/spacemit/Kconfig" endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index df5a0b92f828..01765631af5c 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -36,4 +36,5 @@ obj-y += allwinner/ \ tegra/ \ ti/ \ xilinx/ \ + spacemit/ \ synopsys/ diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig new file mode 100644 index 000000000000..f768e540dd76 --- /dev/null +++ b/drivers/phy/spacemit/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Phy drivers for SpacemiT platforms +# +config PHY_SPACEMIT_K1_USB2 + tristate "SpacemiT K1/K3 USB 2.0 PHY support" + depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF + depends on COMMON_CLK + depends on USB_COMMON + select GENERIC_PHY + help + Enable this to support K1 USB 2.0 PHY driver. This driver takes care of + enabling and clock setup and will be used by K1 udc/ehci/otg/xhci driver. diff --git a/drivers/phy/spacemit/Makefile b/drivers/phy/spacemit/Makefile new file mode 100644 index 000000000000..41b3e94a9884 --- /dev/null +++ b/drivers/phy/spacemit/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_PHY_SPACEMIT_K1_USB2) += phy-k1-usb2.o diff --git a/drivers/phy/spacemit/phy-k1-usb2.c b/drivers/phy/spacemit/phy-k1-usb2.c new file mode 100644 index 000000000000..13f92efcb8ea --- /dev/null +++ b/drivers/phy/spacemit/phy-k1-usb2.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SpacemiT K1 USB 2.0 PHY driver + * + * Copyright (C) 2025 SpacemiT (Hangzhou) Technology Co. Ltd + * Copyright (C) 2025 Ze Huang + */ + +#include +#include +#include +#include +#include +#include + +#define PHY_RST_MODE_CTRL 0x04 +#define PHY_PLL_RDY BIT(0) +#define PHY_CLK_CDR_EN BIT(1) +#define PHY_CLK_PLL_EN BIT(2) +#define PHY_CLK_MAC_EN BIT(3) +#define PHY_MAC_RSTN BIT(5) +#define PHY_CDR_RSTN BIT(6) +#define PHY_PLL_RSTN BIT(7) +/* + * hs line state sel (Bit 13): + * - 1 (Default): Internal HS line state is set to 01 when usb_hs_tx_en is valid. + * - 0: Internal HS line state is always driven by usb_hs_lstate. + * + * fs line state sel (Bit 14): + * - 1 (Default): FS line state is determined by the output data + * (usb_fs_datain/b). + * - 0: FS line state is always determined by the input data (dmo/dpo). + */ +#define PHY_HS_LINE_TX_MODE BIT(13) +#define PHY_FS_LINE_TX_MODE BIT(14) + +#define PHY_INIT_MODE_BITS (PHY_FS_LINE_TX_MODE | PHY_HS_LINE_TX_MODE) +#define PHY_CLK_ENABLE_BITS (PHY_CLK_PLL_EN | PHY_CLK_CDR_EN | \ + PHY_CLK_MAC_EN) +#define PHY_DEASSERT_RST_BITS (PHY_PLL_RSTN | PHY_CDR_RSTN | \ + PHY_MAC_RSTN) + +#define PHY_TX_HOST_CTRL 0x10 +#define PHY_HST_DISC_AUTO_CLR BIT(2) /* autoclear hs host disc when re-connect */ + +#define PHY_HSTXP_HW_CTRL 0x34 +#define PHY_HSTXP_RSTN BIT(2) /* generate reset for clock hstxp */ +#define PHY_CLK_HSTXP_EN BIT(3) /* clock hstxp enable */ +#define PHY_HSTXP_MODE BIT(4) /* 0: force en_txp to be 1; 1: no force */ + +#define PHY_PLL_DIV_CFG 0x98 +#define PHY_FDIV_FRACT_8_15 GENMASK(7, 0) +#define PHY_FDIV_FRACT_16_19 GENMASK(11, 8) +#define PHY_FDIV_FRACT_20_21 BIT(12) /* fdiv_reg<21>, <20>, bit21 == bit20 */ +/* + * freq_sel<1:0> + * if ref clk freq=24.0MHz-->freq_sel<2:0> == 3b'001, then internal divider value == 80 + */ +#define PHY_FDIV_FRACT_0_1 GENMASK(14, 13) +/* + * pll divider value selection + * 1: divider value will choose internal default value ,dependent on freq_sel<1:0> + * 0: divider value will be over ride by fdiv_reg<21:0> + */ +#define PHY_DIV_LOCAL_EN BIT(15) + +#define PHY_SEL_FREQ_24MHZ 0x01 +#define FDIV_REG_MASK (PHY_FDIV_FRACT_20_21 | PHY_FDIV_FRACT_16_19 | \ + PHY_FDIV_FRACT_8_15) +#define FDIV_REG_VAL 0x1ec4 /* 0x100 selects 24MHz, rest are default */ + +#define K1_USB2PHY_RESET_TIME_MS 50 + +struct spacemit_usb2phy { + struct phy *phy; + struct clk *clk; + struct regmap *regmap_base; +}; + +static const struct regmap_config phy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x200, +}; + +static int spacemit_usb2phy_init(struct phy *phy) +{ + struct spacemit_usb2phy *sphy = phy_get_drvdata(phy); + struct regmap *map = sphy->regmap_base; + u32 val; + int ret; + + ret = clk_enable(sphy->clk); + if (ret) { + dev_err(&phy->dev, "failed to enable clock\n"); + clk_disable(sphy->clk); + return ret; + } + + /* + * make sure the usb controller is not under reset process before + * any configuration + */ + usleep_range(150, 200); + + /* 24M ref clk */ + val = FIELD_PREP(FDIV_REG_MASK, FDIV_REG_VAL) | + FIELD_PREP(PHY_FDIV_FRACT_0_1, PHY_SEL_FREQ_24MHZ) | + PHY_DIV_LOCAL_EN; + regmap_write(map, PHY_PLL_DIV_CFG, val); + + ret = regmap_read_poll_timeout(map, PHY_RST_MODE_CTRL, val, + (val & PHY_PLL_RDY), + 500, K1_USB2PHY_RESET_TIME_MS * 1000); + if (ret) { + dev_err(&phy->dev, "wait PLLREADY timeout\n"); + clk_disable(sphy->clk); + return ret; + } + + /* release usb2 phy internal reset and enable clock gating */ + val = (PHY_INIT_MODE_BITS | PHY_CLK_ENABLE_BITS | PHY_DEASSERT_RST_BITS); + regmap_write(map, PHY_RST_MODE_CTRL, val); + + val = (PHY_HSTXP_RSTN | PHY_CLK_HSTXP_EN | PHY_HSTXP_MODE); + regmap_write(map, PHY_HSTXP_HW_CTRL, val); + + /* auto clear host disc */ + regmap_update_bits(map, PHY_TX_HOST_CTRL, PHY_HST_DISC_AUTO_CLR, + PHY_HST_DISC_AUTO_CLR); + + return 0; +} + +static int spacemit_usb2phy_exit(struct phy *phy) +{ + struct spacemit_usb2phy *sphy = phy_get_drvdata(phy); + + clk_disable(sphy->clk); + + return 0; +} + +static const struct phy_ops spacemit_k1_usb2phy_ops = { + .init = spacemit_usb2phy_init, + .exit = spacemit_usb2phy_exit, + .owner = THIS_MODULE, +}; + +static const struct phy_ops spacemit_k3_usb2phy_ops = { + .init = spacemit_usb2phy_init, + .exit = spacemit_usb2phy_exit, + .owner = THIS_MODULE, +}; + +static int spacemit_usb2phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct spacemit_usb2phy *sphy; + const struct phy_ops *ops; + void __iomem *base; + + sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL); + if (!sphy) + return -ENOMEM; + + ops = device_get_match_data(dev); + + sphy->clk = devm_clk_get_optional_prepared(&pdev->dev, NULL); + if (IS_ERR(sphy->clk)) + return dev_err_probe(dev, PTR_ERR(sphy->clk), "Failed to get clock\n"); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + sphy->regmap_base = devm_regmap_init_mmio(dev, base, &phy_regmap_config); + if (IS_ERR(sphy->regmap_base)) + return dev_err_probe(dev, PTR_ERR(sphy->regmap_base), "Failed to init regmap\n"); + + sphy->phy = devm_phy_create(dev, NULL, ops); + if (IS_ERR(sphy->phy)) + return dev_err_probe(dev, PTR_ERR(sphy->phy), "Failed to create phy\n"); + + phy_set_drvdata(sphy->phy, sphy); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id spacemit_usb2phy_dt_match[] = { + { .compatible = "spacemit,k1-usb2-phy", .data = &spacemit_k1_usb2phy_ops }, + { .compatible = "spacemit,k3-usb2-phy", .data = &spacemit_k3_usb2phy_ops }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, spacemit_usb2phy_dt_match); + +static struct platform_driver spacemit_usb2_phy_driver = { + .probe = spacemit_usb2phy_probe, + .driver = { + .name = "spacemit-usb2-phy", + .of_match_table = spacemit_usb2phy_dt_match, + }, +}; +module_platform_driver(spacemit_usb2_phy_driver); + +MODULE_DESCRIPTION("Spacemit USB 2.0 PHY driver"); +MODULE_LICENSE("GPL"); From b20b6d24b4292d56eb855fdf01dac2cf1beec8fd Mon Sep 17 00:00:00 2001 From: Yongchao Jia Date: Fri, 3 Jul 2026 10:33:26 +0800 Subject: [PATCH 4/5] phy: spacemit: add K3 USB3 combo PHY driver riscv inclusion category: feature bugzilla: https://github.com/RVCK-Project/rvck/issues/322 -------------------------------- Add the SpacemiT K3 USB3 combo PHY driver. On K3 the PCIe/USB subsystem provides three single-lane PIPE3 PHYs (PHY2/3/4) shared between PCIe Port C/D and USB3 Port B/C/D. Lane ownership is selected through the APMU PMUA_PCIE_SUBSYS_MGMT register: the driver acquires the syscon-apmu regmap, disables PCIe-A x8 mode and switches the requested lane to USB before bringing the PHY up, so USB3 operation is only possible when the conflicting PCIe x8 configuration is not in use. Bring-up performs R-calibration trim override (N/P trim) and PLL/PU lock polling via the HSIO APB spare registers, and exposes the standard generic-phy init/exit/power and set_speed operations for the xHCI/dwc3 stack. The driver also registers a Type-C orientation switch/mux so the combo PHY can follow cable orientation on Type-C ports. Signed-off-by: Yongchao Jia --- drivers/phy/spacemit/Kconfig | 10 + drivers/phy/spacemit/Makefile | 1 + drivers/phy/spacemit/phy-k3-usb3.c | 635 +++++++++++++++++++++++++++++ 3 files changed, 646 insertions(+) create mode 100644 drivers/phy/spacemit/phy-k3-usb3.c diff --git a/drivers/phy/spacemit/Kconfig b/drivers/phy/spacemit/Kconfig index f768e540dd76..fee8db9ff15e 100644 --- a/drivers/phy/spacemit/Kconfig +++ b/drivers/phy/spacemit/Kconfig @@ -2,6 +2,16 @@ # # Phy drivers for SpacemiT platforms # +config PHY_SPACEMIT_K3_USB3 + tristate "SpacemiT K3 USB 3.0 PHY support" + depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF + depends on COMMON_CLK + depends on USB_COMMON + depends on TYPEC || TYPEC=n + select GENERIC_PHY + help + Enable this to support K3 USB 3.0 PHY driver. + config PHY_SPACEMIT_K1_USB2 tristate "SpacemiT K1/K3 USB 2.0 PHY support" depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF diff --git a/drivers/phy/spacemit/Makefile b/drivers/phy/spacemit/Makefile index 41b3e94a9884..5d7fc8b280f3 100644 --- a/drivers/phy/spacemit/Makefile +++ b/drivers/phy/spacemit/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_PHY_SPACEMIT_K1_USB2) += phy-k1-usb2.o +obj-$(CONFIG_PHY_SPACEMIT_K3_USB3) += phy-k3-usb3.o diff --git a/drivers/phy/spacemit/phy-k3-usb3.c b/drivers/phy/spacemit/phy-k3-usb3.c new file mode 100644 index 000000000000..eb8fd12332ce --- /dev/null +++ b/drivers/phy/spacemit/phy-k3-usb3.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * phy-k3-usb3.c - SpacemiT K3 USB3.0 PHY & Type-C Orientation Switch Driver + * + * Copyright (c) 2025 SpacemiT Technology Co. Ltd + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include + +#define MAX_NUM_PHY 2 + +#define PLL_TIMEOUT 500000 /* For PHY PLL lock (usec) */ +#define PU_CAL_TIMEOUT 2000000 +#define POLL_DELAY 500 /* Time between polls (usec) */ + +/* Selecting the combo PHY operating mode requires APMU regmap access */ +#define SYSCON_APMU "spacemit,syscon-apmu" + +/* + * The PCIE/USB Subsystem on SpacemiT K3 have 3 single lane PIPE3 PHYs + * (PHY2/3/4) shared by PCIE PortC/D and USB3 PortB/C/D. + * + * PMUA_PCIE_SUBSYS_MGMT[4:0] + * + * bit4 = 0 : PCIe A X8 mode, all 8 lanes dedicated to PCIe Port A + * 1 : PHY lanes shared between PCIe or USB according to [3:0] + * + * All PHY matrix combinations according to [4:0]: + * + * 0x0X : PCIe-A X8 + * 0x10 : PCIe-C x2 (PHY2+PHY3) + PCIe-D x1 (PHY4) + * 0x11 : PCIe-C x2 (PHY2+PHY3) + USB-D (PHY4) + * 0x12 : PCIe-C x1 (PHY2) + USB-C (PHY3) + * 0x13 : PCIe-C x1 (PHY2) + USB-C (PHY3) + USB-D (PHY4) + * 0x14 : PCIe-C x1 (PHY3) + USB-B (PHY2) + * 0x15 : PCIe-C x1 (PHY3) + USB-B (PHY2) + USB-D (PHY4) + * 0x16 : USB-B (PHY2) + USB-C (PHY3) + PCIe D x1 (PHY4) + * 0x17 : USB-B (PHY2) + USB-C (PHY3) + USB-D (PHY4) + * + * So any USB Port B/C/D operation requires PCIe A X8 mode to be disabled. + */ +#define PMUA_PCIE_SUBSYS_MGMT 0x1d8 +#define PU_MATRIX_CONF_X8_DISABLE BIT(4) +#define PU_MATRIX_CONF_USB_MASK GENMASK(2, 0) + +#define PMUA_TYPEC_CTRL 0x110 +#define TYPEC_ORIENT_FLIP BIT(2) +#define TYPEC_ORIENT_OVRD_EN BIT(3) +#define TYPEC_ORIENT_OVRD BIT(4) + +/* PHY rcal init requires APB_SPARE regmap access */ +#define SYSCON_APB_SPARE "spacemit,syscon-apb-spare" + +#define APB_SPARE_PU_CAL 0x178 +#define PU_CAL BIT(17) + +#define APB_SPARE_RCAL_HSIO 0x17c +#define PU_CAL_DONE BIT(8) +#define R_CAL_OVRD_STABLE_EN BIT(31) +#define R_CAL_OVRD_STABLE_VAL BIT(30) +#define R_CAL_OVRD_NTRIM_EN BIT(29) +#define R_CAL_OVRD_PTRIM_EN BIT(28) +#define R_CAL_OVRD_TRIM_EN (R_CAL_OVRD_NTRIM_EN | R_CAL_OVRD_PTRIM_EN) +#define R_CAL_OVRD_NTRIM_MASK GENMASK(27, 24) +#define R_CAL_OVRD_NTRIM_VAL(val) FIELD_PREP(R_CAL_OVRD_NTRIM_MASK, val) +#define NTRIM_DEFAULT 0x6 +#define R_CAL_OVRD_PTRIM_MASK GENMASK(23, 20) +#define R_CAL_OVRD_PTRIM_VAL(val) FIELD_PREP(R_CAL_OVRD_PTRIM_MASK, val) +#define PTRIM_DEFAULT 0xa +#define R_CAL_OVRD_TRIM_MASK (R_CAL_OVRD_NTRIM_MASK | R_CAL_OVRD_PTRIM_MASK) + +/* PHY Registers */ +#define PHY_VERSION 0x0 + +#define PHY_RESET_CFG 0x04 +#define EN_SAMPLE_DATA_AFTER_LOCK BIT(6) + +#define PHY_CLK_CFG 0x08 +#define PLL_READY BIT(0) +#define CFG_RXCLK_EN BIT(3) +#define CFG_TXCLK_EN BIT(4) +#define CFG_PCLK_EN BIT(5) +#define CFG_PIPE_PCLK_EN BIT(6) +#define CFG_REFCLK_FREQ GENMASK(10, 7) +#define REFCLK_24M 0x2 +#define CFG_SW_INIT_DONE BIT(11) +#define CFG_PU_SSC_OUT BIT(23) + +#define PCIE_PHY_OVERRIDE 0x18 +#define OVRD_MPU_U3 BIT(17) +#define CFG_MPU_U3 BIT(16) + +#define PHY_MODE_CFG 0x0C +#define CFG_LFPS_RX_FILTER_EN BIT(11) +#define CFG_LFPS_TPERIOD GENMASK(9, 8) +#define LFPS_TPERIOD_USB 0x3 + +#define PHY_PU_SEL 0x40 +#define OVRD_STATUS BIT(10) +#define CFG_STATUS BIT(9) + +#define PHY_PU_CK_REG 0x54 +#define PU_REFCLK_100 BIT(25) + +#define PHY_PLL_REG1 0x58 +#define FREF_SEL GENMASK(15, 13) +#define FREF_24M 0x1 +#define SSC_DEP_SEL GENMASK(27, 24) +#define SSC_5000PPM 0xa +#define SSC_MODE GENMASK(29, 28) +#define SSC_CENTER_SPREAD 0x0 +#define SSC_UP_SPREAD 0x1 +#define SSC_DOWN_SPREAD 0x2 +#define SSC_DOWN_SPREAD1 0x3 + +#define PHY_PLL_REG2 0x5c +#define SEL_REF100 BIT(21) + +/* PHY RX Register Definitions */ +#define PHY_RX_REG_A 0x60 +#define RX_REG3_MASK GENMASK(31, 24) +#define RX_REG3_RDEG1(n) FIELD_PREP(GENMASK(31, 30), (n)) +#define RX_REG3_RDEG1_DEFAULT 0x3 +#define RX_REG3_ADJ_BIAS(n) FIELD_PREP(GENMASK(29, 28), (n)) +#define RX_REG3_ADJ_BIAS_DEFAULT 0x1 +#define RX_REG3_SEL_CBOOST_CODE BIT(27) +#define RX_REG3_I_LOAD_REG(n) FIELD_PREP(GENMASK(26, 24), (n)) +#define RX_REG3_I_LOAD_REG_DEFAULT 0x7 +#define RX_REG2_MASK GENMASK(23, 16) +#define RX_REG2_PSEL(n) FIELD_PREP(GENMASK(23, 21), (n)) +#define RX_REG2_PSEL_DEFAULT 0x4 +#define RX_REG2_FORCE_CSEL BIT(20) +#define RX_REG2_CSEL(n) FIELD_PREP(GENMASK(19, 16), (n)) +#define RX_REG2_CSEL_DEFAULT 0x8 +#define RX_REG1_MASK GENMASK(15, 8) +#define RX_REG1_RC_CALI_REG(n) FIELD_PREP(GENMASK(15, 12), (n)) +#define RX_REG1_RC_CALI_REG_DEFAULT 0x7 +#define RX_REG1_RTERM_REG(n) FIELD_PREP(GENMASK(11, 8), (n)) +#define RX_REG1_RTERM_REG_DEFAULT 0x8 +#define RX_REG0_MASK GENMASK(7, 0) +#define RX_REG0_RLOAD BIT(4) + +#define PHY_RX_REG_B 0x64 +#define RX_REG6_MASK GENMASK(23, 16) +#define RX_REG6_BYPASS_ADPT BIT(22) +#define RX_REG6_ADAPT_GAIN(n) FIELD_PREP(GENMASK(21, 20), (n)) +#define RX_REG6_ADAPT_GAIN_DEFAULT 0x2 +#define RX_REG6_H1_REG(n) FIELD_PREP(GENMASK(19, 16), (n)) +#define RX_REG6_H1_REG_DEFAULT 0x8 +#define RX_REG5_MASK GENMASK(15, 8) +#define RX_REG5_RCELL_BIAS(n) FIELD_PREP(GENMASK(15, 12), (n)) +#define RX_REG5_RCELL_BIAS_DEFAULT 0x8 +#define RX_REG5_RCELL_VCM(n) FIELD_PREP(GENMASK(11, 8), (n)) +#define RX_REG5_RCELL_VCM_DEFAULT 0x8 +#define RX_REG4_MASK GENMASK(7, 0) +#define RX_REG4_MANUAL_CFG BIT(7) +#define RX_REG4_RTERM_SEL BIT(5) +#define RX_REG4_ENVOS BIT(4) +#define RX_REG4_RDEG2(n) FIELD_PREP(GENMASK(2, 1), (n)) +#define RX_REG4_RDEG2_DEFAULT 0x2 + +#define PHY_RXEQ_TIME 0xb4 +#define RXEQ_TIME_OVRD_AMP_SOC BIT(24) +#define RXEQ_TIME_CFG_AMP_SOC(n) FIELD_PREP(GENMASK(23, 22), (n)) +#define AMP_SOC_650M 0x0 +#define AMP_SOC_800M 0x1 +#define AMP_SOC_870M 0x2 +#define AMP_SOC_900M 0x3 +#define OVRD_POST_C_SOC BIT(21) +#define CFG_POST_C_SOC(n) FIELD_PREP(GENMASK(20, 19), (n)) +#define OVRD_PRE_C_SOC BIT(18) +#define CFG_PRE_C_SOC(n) FIELD_PREP(GENMASK(17, 16), (n)) +#define CFG_RXEQ_TIMEOUT(n) FIELD_PREP(GENMASK(15, 0), (n)) + +#define PHY_ADPT_CFG0 0x140 +#define AFE_ADPT_RST_OVRD_EN BIT(1) +#define AFE_ADPT_RST_OVRD_VAL BIT(4) + +struct k3_usb3phy { + struct device *dev; + struct phy *phy; + /* dual phy for orentation switch */ + struct regmap *regmap_bases[MAX_NUM_PHY]; + + bool is_combo; + u32 combo_sel_bit; + + /* MMIO regmap (no errors) */ + struct regmap *pmu; + struct regmap *apb_spare; + + /* For USB only */ + bool nop; + bool orientation_flip; + struct typec_switch_dev *sw; +}; + +static void k3_usb3phy_combo_set_usb(struct k3_usb3phy *k3_phy, bool usb) +{ + u32 combo_mode_mask = BIT(k3_phy->combo_sel_bit); + u32 combo_mode_val = usb << k3_phy->combo_sel_bit; + + combo_mode_mask |= PU_MATRIX_CONF_X8_DISABLE; + combo_mode_val |= usb ? PU_MATRIX_CONF_X8_DISABLE : 0; + + if (k3_phy->is_combo && + !regmap_test_bits(k3_phy->pmu, PMUA_PCIE_SUBSYS_MGMT, + combo_mode_val) == usb) { + regmap_update_bits(k3_phy->pmu, PMUA_PCIE_SUBSYS_MGMT, + combo_mode_mask, combo_mode_val); + dev_info(k3_phy->dev, "Update Combo Mode %d to %s Mode\n", + combo_mode_val, usb ? "USB" : "PCIE"); + } +} + +static void k3_usb3phy_update_status(struct regmap *regm) +{ + int ret; + + ret = regmap_update_bits(regm, PHY_PU_SEL, + CFG_STATUS | OVRD_STATUS, + OVRD_STATUS); + if (ret != 0) { + pr_err("regmap update PHY_PU_SEL failed, ret=%d\n", ret); + return; + } + usleep_range(200, 300); +} + +static int k3_usb3phy_init_single(struct k3_usb3phy *k3_phy, + struct regmap *regm) +{ + struct phy *phy = k3_phy->phy; + struct regmap *apb_spare = k3_phy->apb_spare; + int ret; + u32 version, reg; + + regmap_update_bits(regm, PCIE_PHY_OVERRIDE, + OVRD_MPU_U3 | CFG_MPU_U3, 0); + + ret = regmap_read(regm, PHY_CLK_CFG, ®); + if (ret) + return ret; + + if (reg & CFG_SW_INIT_DONE) { + dev_dbg(&phy->dev, "PHY already initialized, skip init\n"); + return 0; + } + + ret = regmap_read(regm, PHY_VERSION, &version); + if (ret) + return ret; + + regmap_update_bits(apb_spare, APB_SPARE_PU_CAL, PU_CAL, + PU_CAL); + + ret = regmap_read_poll_timeout(apb_spare, APB_SPARE_RCAL_HSIO, + reg, (reg & PU_CAL_DONE), POLL_DELAY, + PU_CAL_TIMEOUT); + if (ret) { + dev_warn(k3_phy->dev, "PU PHY RCAL timeout, trim override\n"); + + regmap_update_bits(apb_spare, APB_SPARE_RCAL_HSIO, + R_CAL_OVRD_TRIM_EN | R_CAL_OVRD_STABLE_VAL | + R_CAL_OVRD_TRIM_MASK, + R_CAL_OVRD_TRIM_EN | R_CAL_OVRD_STABLE_VAL | + R_CAL_OVRD_NTRIM_VAL(NTRIM_DEFAULT) | + R_CAL_OVRD_PTRIM_VAL(PTRIM_DEFAULT)); + regmap_set_bits(apb_spare, APB_SPARE_RCAL_HSIO, R_CAL_OVRD_STABLE_EN); + } + + /* Do not wait CDR lock before sampling data */ + regmap_update_bits(regm, PHY_RESET_CFG, EN_SAMPLE_DATA_AFTER_LOCK, + 0); + + /* Power down 100MHz refclk buffer */ + regmap_update_bits(regm, PHY_PU_CK_REG, PU_REFCLK_100, 0); + + /* Program PLL REG1 configure the SSC */ + regmap_write(regm, PHY_PLL_REG1, + FIELD_PREP(SSC_MODE, SSC_DOWN_SPREAD1) | + FIELD_PREP(SSC_DEP_SEL, SSC_5000PPM) | + FIELD_PREP(FREF_SEL, FREF_24M)); + + /* Un-select 100MHz PLL reference */ + regmap_update_bits(regm, PHY_PLL_REG2, SEL_REF100, 0); + + /* USB LFPS period configuration */ + regmap_update_bits(regm, PHY_MODE_CFG, CFG_LFPS_TPERIOD, + FIELD_PREP(CFG_LFPS_TPERIOD, + LFPS_TPERIOD_USB)); + + /* Force AFE adaptation reset */ + regmap_update_bits(regm, PHY_ADPT_CFG0, + AFE_ADPT_RST_OVRD_EN | AFE_ADPT_RST_OVRD_VAL, + AFE_ADPT_RST_OVRD_EN | AFE_ADPT_RST_OVRD_VAL); + + /* Override driver amplitude value to 900m */ + regmap_set_bits(regm, PHY_RXEQ_TIME, + RXEQ_TIME_OVRD_AMP_SOC | RXEQ_TIME_CFG_AMP_SOC(AMP_SOC_900M)); + + /* Configure RX parameters */ + regmap_update_bits(regm, PHY_RX_REG_A, RX_REG0_MASK, RX_REG0_RLOAD); + regmap_update_bits(regm, PHY_RX_REG_A, RX_REG1_MASK, + RX_REG1_RC_CALI_REG(RX_REG1_RC_CALI_REG_DEFAULT) | + RX_REG1_RTERM_REG(RX_REG1_RTERM_REG_DEFAULT)); + regmap_update_bits(regm, PHY_RX_REG_A, RX_REG2_MASK, + RX_REG2_PSEL(RX_REG2_PSEL_DEFAULT) | RX_REG2_FORCE_CSEL | + RX_REG2_CSEL(RX_REG2_CSEL_DEFAULT)); + regmap_update_bits(regm, PHY_RX_REG_A, RX_REG3_MASK, + RX_REG3_RDEG1(RX_REG3_RDEG1_DEFAULT) | + RX_REG3_ADJ_BIAS(RX_REG3_ADJ_BIAS_DEFAULT) | + RX_REG3_SEL_CBOOST_CODE | + RX_REG3_I_LOAD_REG(RX_REG3_I_LOAD_REG_DEFAULT)); + regmap_update_bits(regm, PHY_RX_REG_B, RX_REG4_MASK, + RX_REG4_MANUAL_CFG | RX_REG4_RTERM_SEL | RX_REG4_ENVOS | + RX_REG4_RDEG2(RX_REG4_RDEG2_DEFAULT)); + regmap_update_bits(regm, PHY_RX_REG_B, RX_REG5_MASK, + RX_REG5_RCELL_BIAS(RX_REG5_RCELL_BIAS_DEFAULT) | + RX_REG5_RCELL_VCM(RX_REG5_RCELL_VCM_DEFAULT)); + regmap_update_bits(regm, PHY_RX_REG_B, RX_REG6_MASK, + RX_REG6_ADAPT_GAIN(RX_REG6_ADAPT_GAIN_DEFAULT) | + RX_REG6_H1_REG(RX_REG6_H1_REG_DEFAULT)); + dev_info(&phy->dev, "PUPHY Rx Reg Configured\n"); + + /* + * Inform PHY that all PLL-related configuration is done. + * PLL will not start locking until CFG_SW_INIT_DONE is set. + */ + regmap_write(regm, PHY_CLK_CFG, + CFG_SW_INIT_DONE | + CFG_PU_SSC_OUT | + FIELD_PREP(CFG_REFCLK_FREQ, REFCLK_24M) | + CFG_RXCLK_EN | CFG_PCLK_EN | + CFG_PIPE_PCLK_EN | CFG_TXCLK_EN); + + ret = regmap_read_poll_timeout(regm, PHY_CLK_CFG, reg, + (reg & PLL_READY), POLL_DELAY, + PLL_TIMEOUT); + if (ret) { + dev_err(&phy->dev, "PHY PLL polling Timeout!\n"); + return -ETIMEDOUT; + } + + dev_info(&phy->dev, "PHY version: 0x%x init as USB3 mode\n", version); + + return 0; +} + +static int k3_usb3phy_power_on(struct phy *phy) +{ + struct k3_usb3phy *k3_phy = phy_get_drvdata(phy); + + if (k3_phy->nop) + return 0; + + /* RX Filter requires soc 24M, which is disabled during system sleep */ + for (int i = 0; i < MAX_NUM_PHY && k3_phy->regmap_bases[i]; i++) + regmap_set_bits(k3_phy->regmap_bases[i], PHY_MODE_CFG, + CFG_LFPS_RX_FILTER_EN); + + return 0; +} + +static int k3_usb3phy_power_off(struct phy *phy) +{ + struct k3_usb3phy *k3_phy = phy_get_drvdata(phy); + + if (k3_phy->nop) + return 0; + + for (int i = 0; i < MAX_NUM_PHY && k3_phy->regmap_bases[i]; i++) + regmap_clear_bits(k3_phy->regmap_bases[i], PHY_MODE_CFG, + CFG_LFPS_RX_FILTER_EN); + + return 0; +} + +static int k3_usb3phy_init(struct phy *phy) +{ + struct k3_usb3phy *k3_phy = phy_get_drvdata(phy); + + if (k3_phy->nop) { + dev_info(&phy->dev, + "maximum high-speed configuration requested\n"); + return 0; + } + + k3_usb3phy_combo_set_usb(k3_phy, true); + + k3_usb3phy_init_single(k3_phy, k3_phy->regmap_bases[0]); + if (k3_phy->regmap_bases[1]) + k3_usb3phy_init_single(k3_phy, k3_phy->regmap_bases[1]); + + return 0; +} + +static int k3_usb3phy_exit(struct phy *phy) +{ + struct k3_usb3phy *k3_phy = phy_get_drvdata(phy); + int ret; + + if (k3_phy->nop) + return 0; + + /* Completely shutdown the phy */ + for (int i = 0; i < MAX_NUM_PHY; i++) { + if (!k3_phy->regmap_bases[i]) + continue; + + ret = regmap_update_bits(k3_phy->regmap_bases[i], PHY_CLK_CFG, + CFG_SW_INIT_DONE, 0); + if (ret) + return ret; + + ret = regmap_update_bits(k3_phy->regmap_bases[i], PCIE_PHY_OVERRIDE, + OVRD_MPU_U3 | CFG_MPU_U3, OVRD_MPU_U3); + if (ret) + return ret; + } + + return 0; +} + +static int k3_usb3phy_set_speed(struct phy *phy, int speed) +{ + struct k3_usb3phy *k3_phy = phy_get_drvdata(phy); + + switch (speed) { + case USB_SPEED_HIGH: + k3_usb3phy_update_status(k3_phy->regmap_bases[0]); + k3_phy->nop = true; + default: + break; + } + return 0; +} + +static const struct phy_ops k3_usb3phy_ops = { + .init = k3_usb3phy_init, + .exit = k3_usb3phy_exit, + .power_on = k3_usb3phy_power_on, + .power_off = k3_usb3phy_power_off, + .set_speed = k3_usb3phy_set_speed, + .owner = THIS_MODULE, +}; + +#if IS_ENABLED(CONFIG_TYPEC) +static int k3_usb3phy_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct k3_usb3phy *k3_phy = typec_switch_get_drvdata(sw); + bool val; + + val = orientation == TYPEC_ORIENTATION_REVERSE; + if (k3_phy->orientation_flip) + val = !val; + + if (regmap_test_bits(k3_phy->pmu, PMUA_TYPEC_CTRL, + TYPEC_ORIENT_OVRD | TYPEC_ORIENT_OVRD_EN) != val) + regmap_update_bits(k3_phy->pmu, PMUA_TYPEC_CTRL, + TYPEC_ORIENT_OVRD | TYPEC_ORIENT_OVRD_EN, + val ? TYPEC_ORIENT_OVRD | TYPEC_ORIENT_OVRD_EN : 0); + + dev_dbg(k3_phy->dev, "Override orientation with %d\n", val); + return 0; +} + +static void k3_usb3_typec_unregister(void *data) +{ + struct k3_usb3phy *k3_phy = data; + + typec_switch_unregister(k3_phy->sw); +} + +static int k3_usb3_typec_register(struct k3_usb3phy *k3_phy) +{ + struct typec_switch_desc sw_desc = {}; + struct device *dev = k3_phy->dev; + + sw_desc.drvdata = k3_phy; + sw_desc.name = dev_name(dev); + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = k3_usb3phy_switch_set; + k3_phy->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(k3_phy->sw)) { + dev_err(dev, "Unable to register typec switch: %pe\n", + k3_phy->sw); + return PTR_ERR(k3_phy->sw); + } + + dev_info(dev, "Using orientation-switch mode, flip: %d\n", + k3_phy->orientation_flip); + + return devm_add_action_or_reset(dev, k3_usb3_typec_unregister, k3_phy); +} +#else +static int k3_usb3_typec_register(struct k3_usb3phy *k3_phy) +{ + return 0; +} +#endif + +static int k3_usb3phy_switch_init(struct k3_usb3phy *k3_phy) +{ + struct device *dev = k3_phy->dev; + bool flip, override; + u32 val = 0; + + if (!device_is_compatible(dev, "spacemit,k3-typec-switch")) + return 0; + + flip = device_property_read_bool(dev, "orientation-flip"); + override = device_property_read_bool(dev, "orientation-switch"); + if (flip && !override) + regmap_update_bits(k3_phy->pmu, PMUA_TYPEC_CTRL, + TYPEC_ORIENT_FLIP, val); + + if (!override) + return 0; + + k3_phy->orientation_flip = flip; + return k3_usb3_typec_register(k3_phy); +}; + +static int k3_usb3phy_probe(struct platform_device *pdev) +{ + struct phy *(*xlate)(struct device *dev, + struct of_phandle_args *args); + struct device *dev = &pdev->dev; + struct k3_usb3phy *k3_phy; + struct phy_provider *provider; + void __iomem *base; + int num_phy, ret; + + xlate = of_device_get_match_data(dev); + + k3_phy = devm_kzalloc(dev, sizeof(*k3_phy), GFP_KERNEL); + if (!k3_phy) + return -ENOMEM; + + k3_phy->is_combo = device_property_read_bool(dev, "combo-usb-bit"); + if (k3_phy->is_combo) { + ret = device_property_read_u32(dev, "combo-usb-bit", &k3_phy->combo_sel_bit); + if (ret || !(BIT(k3_phy->combo_sel_bit) & PU_MATRIX_CONF_USB_MASK)) + return dev_err_probe(dev, ret, "Wrong combo-usb-bit configuration"); + } + + k3_phy->dev = dev; + + k3_phy->pmu = + syscon_regmap_lookup_by_phandle(dev_of_node(dev), SYSCON_APMU); + if (IS_ERR(k3_phy->pmu)) + return dev_err_probe(dev, PTR_ERR(k3_phy->pmu), + SYSCON_APMU " lookup failed"); + + k3_phy->apb_spare = syscon_regmap_lookup_by_phandle(dev_of_node(dev), + SYSCON_APB_SPARE); + if (IS_ERR(k3_phy->apb_spare)) + return dev_err_probe(dev, PTR_ERR(k3_phy->apb_spare), + SYSCON_APB_SPARE " lookup failed"); + + /* dual phy for orientation switch */ + for (num_phy = 0; num_phy < MAX_NUM_PHY; num_phy++) + if (!platform_get_resource(pdev, IORESOURCE_MEM, num_phy)) + break; + + for (unsigned int i = 0; i < num_phy; ++i) { + static struct regmap_config phy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x200, + }; + + base = devm_platform_ioremap_resource(pdev, i); + if (IS_ERR(base)) + return dev_err_probe(dev, PTR_ERR(base), + "error mapping registers\n"); + + phy_regmap_config.name = devm_kasprintf(dev, GFP_KERNEL, + "%s-%d", dev_name(dev), i); + k3_phy->regmap_bases[i] = + devm_regmap_init_mmio(dev, base, &phy_regmap_config); + if (IS_ERR(k3_phy->regmap_bases[i])) + return dev_err_probe(dev, PTR_ERR(k3_phy->regmap_bases[i]), + "Failed to init regmap\n"); + } + + k3_phy->phy = devm_phy_create(dev, NULL, &k3_usb3phy_ops); + if (IS_ERR(k3_phy->phy)) + return dev_err_probe(dev, PTR_ERR(k3_phy->phy), + "Failed to create phy\n"); + phy_set_drvdata(k3_phy->phy, k3_phy); + + provider = devm_of_phy_provider_register(dev, xlate); + if (IS_ERR(provider)) + return dev_err_probe(dev, PTR_ERR(provider), + "error registering provider\n"); + + return k3_usb3phy_switch_init(k3_phy); +} + +static const struct of_device_id k3_usb3phy_of_match[] = { + { .compatible = "spacemit,k3-usb3-phy", of_phy_simple_xlate }, + {}, +}; +MODULE_DEVICE_TABLE(of, k3_usb3phy_of_match); + +static struct platform_driver k3_usb3phy_driver = { + .probe = k3_usb3phy_probe, + .driver = { + .name = "spacemit,k3-usb3-phy", + .of_match_table = k3_usb3phy_of_match, + }, +}; +module_platform_driver(k3_usb3phy_driver); + +MODULE_DESCRIPTION("SpacemiT K3 USB3 PHY Driver"); +MODULE_LICENSE("GPL"); From 9a5e85b5be03a2698885bb5399694e217b4d3536 Mon Sep 17 00:00:00 2001 From: Yongchao Jia Date: Fri, 3 Jul 2026 18:52:29 +0800 Subject: [PATCH 5/5] riscv: dts: spacemit: add K3 USB3 support for com260 riscv inclusion category: feature bugzilla: https://github.com/RVCK-Project/rvck/issues/322 -------------------------------- Add the K3 USB3 controller and combo PHY nodes to k3.dtsi and enable them on the com260 board, wiring up the usb3_portb controller, its USB2/USB3 PHYs and the on-board VL817 hub. Signed-off-by: Yongchao Jia --- arch/riscv/boot/dts/spacemit/k3-com260.dts | 43 ++++++++++++++++++++++ arch/riscv/boot/dts/spacemit/k3.dtsi | 42 ++++++++++++++++++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/arch/riscv/boot/dts/spacemit/k3-com260.dts b/arch/riscv/boot/dts/spacemit/k3-com260.dts index c71aba704596..5732b38b3b5f 100644 --- a/arch/riscv/boot/dts/spacemit/k3-com260.dts +++ b/arch/riscv/boot/dts/spacemit/k3-com260.dts @@ -3,6 +3,7 @@ #include "k3.dtsi" #include "k3-pinctrl.dtsi" +#include #include #include @@ -60,6 +61,17 @@ vin-supply = <&p5v>; status = "okay"; }; + + hub_reset: hub-reset { + compatible = "regulator-fixed"; + regulator-name = "hub-reset"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + enable-active-high; + regulator-boot-on; + regulator-always-on; + gpio = <&gpio 1 21 GPIO_ACTIVE_HIGH>; + }; }; &resmem { @@ -345,3 +357,34 @@ }; }; }; + +&usb3_portb_u2phy { + status = "okay"; +}; + +&usb3_portb_u3phy { + status = "okay"; +}; + +&usb3_portb { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + reset-on-resume; + + /* VL817 USB2.0 hub (4 ports) */ + hub_2_0: hub@1 { + compatible = "usb2109,2817"; + reg = <1>; + peer-hub = <&hub_3_0>; + vdd-supply = <&hub_reset>; + }; + + /* VL817 USB3.0 hub (4 ports) */ + hub_3_0: hub@2 { + compatible = "usb2109,817"; + reg = <2>; + peer-hub = <&hub_2_0>; + }; +}; + diff --git a/arch/riscv/boot/dts/spacemit/k3.dtsi b/arch/riscv/boot/dts/spacemit/k3.dtsi index 974ea1ea6cf0..8726a5a6eebc 100644 --- a/arch/riscv/boot/dts/spacemit/k3.dtsi +++ b/arch/riscv/boot/dts/spacemit/k3.dtsi @@ -505,7 +505,7 @@ }; pll: clock-controller@d4090000 { - compatible = "spacemit,k3-pll"; + compatible = "spacemit,k3-pll", "syscon", "simple-mfd"; reg = <0x0 0xd4090000 0x0 0x10000>; clocks = <&vctcxo_24m>; spacemit,mpmu = <&syscon_mpmu>; @@ -910,5 +910,45 @@ reset-names = "sdh_axi", "sdh2"; status = "disabled"; }; + + usb3_portb_u2phy: phy@81500000 { + compatible = "spacemit,k3-usb2-phy"; + reg = <0x0 0x81500000 0x0 0x200>; + #phy-cells = <0>; + status = "disabled"; + }; + + usb3_portb_u3phy: phy@81f00000 { + compatible = "spacemit,k3-usb3-phy"; + spacemit,syscon-apmu = <&syscon_apmu>; + spacemit,syscon-apb-spare = <&pll>; + reg = <0x0 0x81f00000 0x0 0x200>; + combo-usb-bit = <2>; + #phy-cells = <0>; + status = "disabled"; + }; + + usb3_portb: usb3@81400000 { + compatible = "spacemit,k3-dwc3"; + reg = <0x0 0x81400000 0x0 0x10000>; + spacemit,syscon-apmu = <&syscon_apmu 0x3c8>; + interrupts = <125 IRQ_TYPE_LEVEL_HIGH>, + <157 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "dwc3", "wakeup"; + interrupt-parent = <&saplic>; + clocks = <&syscon_apmu CLK_APMU_USB3_PORTB_BUS>; + resets = <&syscon_apmu RESET_APMU_USB3_PORTB>; + phys = <&usb3_portb_u2phy>, <&usb3_portb_u3phy>; + phy-names = "usb2-phy", "usb3-phy"; + phy_type = "utmi"; + snps,dis_enblslpm_quirk; + snps,dis_u2_susphy_quirk; + snps,dis_u3_susphy_quirk; + snps,dis-del-phy-power-chg-quirk; + snps,dis-tx-ipgap-linecheck-quirk; + snps,parkmode-disable-ss-quirk; + dr_mode = "host"; + status = "disabled"; + }; }; };