econet: en7528: add USB support

Add USB host support for EN7528 SoC:

- New phy-en7528-usb driver with U2 slew rate calibration and U3
  RX impedance tuning, based on GPL vendor code and the Airoha
  AN7581 USB PHY driver
- xHCI LTSSM timing quirk for TD 6.5 compliance (patch 915)
- USB PHY and xHCI DTS nodes with IPPC register mapping
- VBUS power via regulator-fixed for DASAN H660GM-A
- Enable USB, xHCI, PHY, and regulator kernel configs

Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/22498
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
This commit is contained in:
Ahmed Naseef
2026-03-19 15:55:28 +04:00
committed by Hauke Mehrtens
parent 4e26edb2e3
commit 62c1c6a946
7 changed files with 461 additions and 9 deletions
+19
View File
@@ -3,6 +3,7 @@
#include <dt-bindings/interrupt-controller/mips-gic.h>
#include <dt-bindings/clock/en7523-clk.h>
#include <dt-bindings/phy/phy.h>
#include <dt-bindings/reset/airoha,en7523-reset.h>
/ {
@@ -131,6 +132,24 @@
};
};
usb_phy: usb-phy@1fa80000 {
compatible = "econet,en7528-usb-phy";
reg = <0x1fa80000 0x1400>;
#phy-cells = <1>;
};
usb: usb@1fb90000 {
compatible = "econet,en7528-xhci";
reg = <0x1fb90000 0x3e00>,
<0x1fb93e00 0x100>;
reg-names = "mac", "ippc";
interrupt-parent = <&gic>;
interrupts = <GIC_SHARED 17 IRQ_TYPE_LEVEL_HIGH>;
phys = <&usb_phy PHY_TYPE_USB2>, <&usb_phy PHY_TYPE_USB3>;
usb3-lpm-capable;
status = "disabled";
};
uart: serial@1fbf0000 {
compatible = "airoha,en7523-uart";
reg = <0x1fbf0000 0x30>;
@@ -20,6 +20,8 @@
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_USB;
gpios = <&gpio0 8 GPIO_ACTIVE_LOW>;
linux,default-trigger = "usbport";
trigger-sources = <&usb>;
};
led-lan1-green {
@@ -77,14 +77,13 @@
};
};
gpio_export {
compatible = "gpio-export";
usb-power {
gpio-export,name = "usb-power";
gpio-export,output = <1>;
gpios = <&gpio0 11 GPIO_ACTIVE_HIGH>;
};
reg_usb_vbus: regulator-usb-vbus {
compatible = "regulator-fixed";
regulator-name = "usb-vbus";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
enable-active-high;
};
keys {
@@ -105,6 +104,11 @@
};
};
&usb {
status = "okay";
vbus-supply = <&reg_usb_vbus>;
};
&pcie0 {
status = "okay";
};
+11
View File
@@ -58,6 +58,7 @@ CONFIG_FS_IOMAP=y
CONFIG_FUNCTION_ALIGNMENT=0
CONFIG_FW_LOADER_PAGED_BUF=y
CONFIG_FW_LOADER_SYSFS=y
CONFIG_GENERIC_ALLOCATOR=y
CONFIG_GENERIC_ATOMIC64=y
CONFIG_GENERIC_CLOCKEVENTS=y
CONFIG_GENERIC_CMOS_UPDATE=y
@@ -140,6 +141,7 @@ CONFIG_NET_EGRESS=y
CONFIG_NET_FLOW_LIMIT=y
CONFIG_NET_INGRESS=y
CONFIG_NET_XGRESS=y
CONFIG_NLS=y
CONFIG_NR_CPUS=4
CONFIG_NVMEM=y
CONFIG_NVMEM_LAYOUTS=y
@@ -165,6 +167,7 @@ CONFIG_PCI_MSI_ARCH_FALLBACKS=y
CONFIG_PERF_USE_VMALLOC=y
CONFIG_PGTABLE_LEVELS=2
CONFIG_PHY_EN7528_PCIE=y
CONFIG_PHY_EN7528_USB=y
CONFIG_PTP_1588_CLOCK_OPTIONAL=y
CONFIG_QUEUED_RWLOCKS=y
CONFIG_QUEUED_SPINLOCKS=y
@@ -172,6 +175,8 @@ CONFIG_RANDSTRUCT_NONE=y
CONFIG_RATIONAL=y
CONFIG_REGMAP=y
CONFIG_REGMAP_MMIO=y
CONFIG_REGULATOR=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
CONFIG_RESET_CONTROLLER=y
CONFIG_RFS_ACCEL=y
CONFIG_RPS=y
@@ -217,6 +222,12 @@ CONFIG_TIMER_PROBE=y
CONFIG_TREE_RCU=y
CONFIG_TREE_SRCU=y
CONFIG_UBIFS_FS=y
CONFIG_USB=y
CONFIG_USB_COMMON=y
CONFIG_USB_SUPPORT=y
CONFIG_USB_XHCI_HCD=y
CONFIG_USB_XHCI_MTK=y
# CONFIG_USB_XHCI_PLATFORM is not set
CONFIG_USE_GENERIC_EARLY_PRINTK_8250=y
CONFIG_USE_OF=y
CONFIG_WEAK_ORDERING=y
+1 -1
View File
@@ -10,7 +10,7 @@ TARGET_DEVICES += en7528_generic
define Device/dasan_h660gm-a
DEVICE_VENDOR := DASAN
DEVICE_MODEL := H660GM-A
DEVICE_PACKAGES := kmod-mt7603 kmod-mt7615e kmod-mt7663-firmware-ap
DEVICE_PACKAGES := kmod-usb2 kmod-mt7603 kmod-mt7615e kmod-mt7663-firmware-ap
TRX_MODEL := Dewberry
IMAGES := tclinux.trx
IMAGE/tclinux.trx := append-kernel | lzma | tclinux-trx
@@ -0,0 +1,358 @@
From: Ahmed Naseef <naseefkm@gmail.com>
Subject: phy: add EN7528 USB PHY driver
Add USB PHY driver for EcoNet EN7528 SoC.
Based on GPL vendor code at https://github.com/keenetic/kernel-49
and the Airoha AN7581 USB PHY driver by Christian Marangi.
Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -93,6 +93,17 @@ config PHY_EN7528_PCIE
This driver provides PHY initialization for the two PCIe ports
on EN7528 SoC.
+config PHY_EN7528_USB
+ tristate "EcoNet EN7528 USB PHY Driver"
+ depends on ECONET || COMPILE_TEST
+ depends on OF
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ help
+ Say 'Y' here to add support for EcoNet EN7528 USB PHY driver.
+ This driver creates the basic PHY instance and provides
+ initialization callback for the USB port.
+
source "drivers/phy/allwinner/Kconfig"
source "drivers/phy/amlogic/Kconfig"
source "drivers/phy/broadcom/Kconfig"
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-
obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o
obj-$(CONFIG_PHY_AIROHA_PCIE) += phy-airoha-pcie.o
obj-$(CONFIG_PHY_EN7528_PCIE) += phy-en7528-pcie.o
+obj-$(CONFIG_PHY_EN7528_USB) += phy-en7528-usb.o
obj-y += allwinner/ \
amlogic/ \
broadcom/ \
--- /dev/null
+++ b/drivers/phy/phy-en7528-usb.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * EcoNet EN7528 USB PHY driver
+ *
+ * Based on GPL vendor code at https://github.com/keenetic/kernel-49
+ * and the Airoha AN7581 USB PHY driver by
+ * Christian Marangi <ansuelsmth@gmail.com>
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/bitfield.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* Frequency Meter registers (shared across ports) */
+#define EN7528_USB_PHY_FMCR0 0x100
+#define EN7528_USB_PHY_MONCLK_SEL GENMASK(27, 26)
+#define EN7528_USB_PHY_FREQDET_EN BIT(24)
+#define EN7528_USB_PHY_CYCLECNT GENMASK(23, 0)
+#define EN7528_USB_PHY_FMMONR0 0x10c
+#define EN7528_USB_PHY_FMMONR1 0x110
+#define EN7528_USB_PHY_FRCK_EN BIT(8)
+
+/* U2 PHY port bases */
+#define EN7528_USB_PHY_U2_P0_BASE 0x300
+#define EN7528_USB_PHY_U2_P1_BASE 0x1300
+#define EN7528_USB_PHY_NUM_U2_PORTS 2
+
+/* U2 PHY register offsets (relative to port base) */
+#define EN7528_USB_PHY_ACR0 0x10
+#define EN7528_USB_PHY_HSTX_SRCAL_EN BIT(23)
+#define EN7528_USB_PHY_HSTX_SRCTRL GENMASK(18, 16)
+#define EN7528_USB_PHY_ACR3 0x1c
+#define EN7528_USB_PHY_ACR3_ENABLE 0xC0240000
+
+/* U3 PHYA registers (base at +0xb00) */
+#define EN7528_USB_PHY_U3_PHYA_REG11 0xb2c
+#define EN7528_USB_PHY_RX_IMPSEL GENMASK(13, 12)
+
+#define EN7528_USB_PHY_FM_DET_CYCLE_CNT 1024
+#define EN7528_USB_PHY_REF_CK 20 /* MHz */
+#define EN7528_USB_PHY_SR_COEF 28
+#define EN7528_USB_PHY_SR_COEF_DIVISOR 1000
+#define EN7528_USB_PHY_DEFAULT_SR 4
+
+#define EN7528_USB_PHY_FREQDET_SLEEP 1000 /* 1ms */
+#define EN7528_USB_PHY_FREQDET_TIMEOUT (EN7528_USB_PHY_FREQDET_SLEEP * 10)
+
+static const unsigned int en7528_u2_port_bases[EN7528_USB_PHY_NUM_U2_PORTS] = {
+ EN7528_USB_PHY_U2_P0_BASE,
+ EN7528_USB_PHY_U2_P1_BASE,
+};
+
+struct en7528_usb_phy_instance {
+ struct phy *phy;
+ u32 type;
+};
+
+enum en7528_usb_phy_type {
+ EN7528_PHY_USB2,
+ EN7528_PHY_USB3,
+
+ EN7528_PHY_USB_MAX,
+};
+
+struct en7528_usb_phy_priv {
+ struct device *dev;
+ struct regmap *regmap;
+
+ struct en7528_usb_phy_instance *phys[EN7528_PHY_USB_MAX];
+};
+
+static void en7528_usb_phy_slew_rate_calibration(struct en7528_usb_phy_priv *priv,
+ unsigned int port_id,
+ unsigned int port_base)
+{
+ unsigned int acr0 = port_base + EN7528_USB_PHY_ACR0;
+ u32 fm_out;
+ u32 srctrl;
+
+ /* Enable HS TX SR calibration */
+ regmap_set_bits(priv->regmap, acr0,
+ EN7528_USB_PHY_HSTX_SRCAL_EN);
+
+ usleep_range(1000, 1500);
+
+ /* Enable free run clock */
+ regmap_set_bits(priv->regmap, EN7528_USB_PHY_FMMONR1,
+ EN7528_USB_PHY_FRCK_EN);
+
+ /* Select monitor clock: port 0 = MONCLK_SEL 0, port 1 = MONCLK_SEL 1 */
+ regmap_update_bits(priv->regmap, EN7528_USB_PHY_FMCR0,
+ EN7528_USB_PHY_MONCLK_SEL,
+ FIELD_PREP(EN7528_USB_PHY_MONCLK_SEL, port_id));
+
+ /* Set cycle count */
+ regmap_update_bits(priv->regmap, EN7528_USB_PHY_FMCR0,
+ EN7528_USB_PHY_CYCLECNT,
+ FIELD_PREP(EN7528_USB_PHY_CYCLECNT,
+ EN7528_USB_PHY_FM_DET_CYCLE_CNT));
+
+ /* Enable frequency meter */
+ regmap_set_bits(priv->regmap, EN7528_USB_PHY_FMCR0,
+ EN7528_USB_PHY_FREQDET_EN);
+
+ /* Timeout can happen and we will apply default value at the end */
+ (void)regmap_read_poll_timeout(priv->regmap, EN7528_USB_PHY_FMMONR0,
+ fm_out, fm_out,
+ EN7528_USB_PHY_FREQDET_SLEEP,
+ EN7528_USB_PHY_FREQDET_TIMEOUT);
+
+ /* Disable frequency meter */
+ regmap_clear_bits(priv->regmap, EN7528_USB_PHY_FMCR0,
+ EN7528_USB_PHY_FREQDET_EN);
+
+ /* Disable free run clock */
+ regmap_clear_bits(priv->regmap, EN7528_USB_PHY_FMMONR1,
+ EN7528_USB_PHY_FRCK_EN);
+
+ /* Disable HS TX SR calibration */
+ regmap_clear_bits(priv->regmap, acr0,
+ EN7528_USB_PHY_HSTX_SRCAL_EN);
+
+ usleep_range(1000, 1500);
+
+ if (!fm_out) {
+ srctrl = EN7528_USB_PHY_DEFAULT_SR;
+ dev_err(priv->dev, "port%u: frequency not detected, using default SR calibration.\n",
+ port_id);
+ } else {
+ /* (1024 / FM_OUT) * REF_CK * SR_COEF */
+ srctrl = EN7528_USB_PHY_REF_CK * EN7528_USB_PHY_SR_COEF;
+ srctrl = (srctrl * EN7528_USB_PHY_FM_DET_CYCLE_CNT) / fm_out;
+ srctrl = DIV_ROUND_CLOSEST(srctrl, EN7528_USB_PHY_SR_COEF_DIVISOR);
+ dev_dbg(priv->dev, "port%u: SR calibration applied: %x\n",
+ port_id, srctrl);
+ }
+
+ regmap_update_bits(priv->regmap, acr0,
+ EN7528_USB_PHY_HSTX_SRCTRL,
+ FIELD_PREP(EN7528_USB_PHY_HSTX_SRCTRL, srctrl));
+}
+
+static int en7528_usb_phy_init(struct phy *phy)
+{
+ struct en7528_usb_phy_instance *instance = phy_get_drvdata(phy);
+ struct en7528_usb_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
+ unsigned int i;
+
+ switch (instance->type) {
+ case PHY_TYPE_USB2:
+ /* Enable both U2 PHY ports before calibration */
+ for (i = 0; i < EN7528_USB_PHY_NUM_U2_PORTS; i++)
+ regmap_write(priv->regmap,
+ en7528_u2_port_bases[i] + EN7528_USB_PHY_ACR3,
+ EN7528_USB_PHY_ACR3_ENABLE);
+ break;
+ case PHY_TYPE_USB3:
+ /* Combo PHY Rx R mean value too high, tune -5 Ohm */
+ regmap_update_bits(priv->regmap,
+ EN7528_USB_PHY_U3_PHYA_REG11,
+ EN7528_USB_PHY_RX_IMPSEL,
+ FIELD_PREP(EN7528_USB_PHY_RX_IMPSEL, 0x1));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int en7528_usb_phy_power_on(struct phy *phy)
+{
+ struct en7528_usb_phy_instance *instance = phy_get_drvdata(phy);
+ struct en7528_usb_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
+ unsigned int i;
+
+ if (instance->type != PHY_TYPE_USB2)
+ return 0;
+
+ /* Calibrate slew rate for both U2 PHY ports */
+ for (i = 0; i < EN7528_USB_PHY_NUM_U2_PORTS; i++)
+ en7528_usb_phy_slew_rate_calibration(priv, i,
+ en7528_u2_port_bases[i]);
+
+ return 0;
+}
+
+static const struct phy_ops en7528_usb_phy_ops = {
+ .init = en7528_usb_phy_init,
+ .power_on = en7528_usb_phy_power_on,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *en7528_usb_phy_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct en7528_usb_phy_priv *priv = dev_get_drvdata(dev);
+ struct en7528_usb_phy_instance *instance = NULL;
+ unsigned int index, phy_type;
+
+ if (args->args_count != 1) {
+ dev_err(dev, "invalid number of cells in 'phy' property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ phy_type = args->args[0];
+ if (!(phy_type == PHY_TYPE_USB2 || phy_type == PHY_TYPE_USB3)) {
+ dev_err(dev, "unsupported device type: %d\n", phy_type);
+ return ERR_PTR(-EINVAL);
+ }
+
+ for (index = 0; index < EN7528_PHY_USB_MAX; index++)
+ if (priv->phys[index] &&
+ phy_type == priv->phys[index]->type) {
+ instance = priv->phys[index];
+ break;
+ }
+
+ if (!instance) {
+ dev_err(dev, "failed to find appropriate phy\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ return instance->phy;
+}
+
+static const struct regmap_config en7528_usb_phy_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = EN7528_USB_PHY_U2_P1_BASE + EN7528_USB_PHY_ACR3,
+};
+
+static int en7528_usb_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct en7528_usb_phy_priv *priv;
+ struct device *dev = &pdev->dev;
+ unsigned int index;
+ void *base;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->regmap = devm_regmap_init_mmio(dev, base,
+ &en7528_usb_phy_regmap_config);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ platform_set_drvdata(pdev, priv);
+
+ for (index = 0; index < EN7528_PHY_USB_MAX; index++) {
+ struct en7528_usb_phy_instance *instance;
+ enum en7528_usb_phy_type phy_type;
+
+ switch (index) {
+ case EN7528_PHY_USB2:
+ phy_type = PHY_TYPE_USB2;
+ break;
+ case EN7528_PHY_USB3:
+ phy_type = PHY_TYPE_USB3;
+ break;
+ default:
+ continue;
+ }
+
+ instance = devm_kzalloc(dev, sizeof(*instance), GFP_KERNEL);
+ if (!instance)
+ return -ENOMEM;
+
+ instance->type = phy_type;
+ priv->phys[index] = instance;
+
+ instance->phy = devm_phy_create(dev, NULL, &en7528_usb_phy_ops);
+ if (IS_ERR(instance->phy))
+ return dev_err_probe(dev, PTR_ERR(instance->phy),
+ "failed to create phy\n");
+
+ phy_set_drvdata(instance->phy, instance);
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev,
+ en7528_usb_phy_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id en7528_usb_phy_match[] = {
+ { .compatible = "econet,en7528-usb-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, en7528_usb_phy_match);
+
+static struct platform_driver en7528_usb_phy_driver = {
+ .probe = en7528_usb_phy_probe,
+ .driver = {
+ .name = "en7528-usb-phy",
+ .of_match_table = en7528_usb_phy_match,
+ },
+};
+
+module_platform_driver(en7528_usb_phy_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("EcoNet EN7528 USB PHY driver");
@@ -0,0 +1,58 @@
From: Ahmed Naseef <naseefkm@gmail.com>
Subject: usb: xhci-mtk: add EN7528 LTSSM timing quirk
EN7528 needs LTSSM Timing Parameter 5 configured to fix
TD 6.5 compliance test failures.
Based on GPL vendor code:
https://github.com/keenetic/kernel-49/commit/ee0e0b7cf28c208cd5b892ea96180ffae0de9b7f
Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
--- a/drivers/usb/host/xhci-mtk.c
+++ b/drivers/usb/host/xhci-mtk.c
@@ -80,6 +80,8 @@
#define SS_GEN2_EOF_CFG 0x990
#define SSG2EOF_OFFSET 0x3c
+#define XHCI_MTK_LTSSM_TIMING_PARAMETER5 0x251c
+
#define XSEOF_OFFSET_MASK GENMASK(11, 0)
/* usb remote wakeup registers in syscon */
@@ -191,6 +193,18 @@ static void xhci_mtk_rxfifo_depth_set(st
writel(value, hcd->regs + HSCH_CFG1);
}
+/* EN7528: fix TD 6.5 compliance test failure */
+static void xhci_mtk_ltssm_quirk(struct xhci_hcd_mtk *mtk)
+{
+ struct device *dev = mtk->dev;
+ struct usb_hcd *hcd = mtk->hcd;
+
+ if (!of_device_is_compatible(dev->of_node, "econet,en7528-xhci"))
+ return;
+
+ writel(0x203e8, hcd->regs + XHCI_MTK_LTSSM_TIMING_PARAMETER5);
+}
+
static void xhci_mtk_init_quirk(struct xhci_hcd_mtk *mtk)
{
/* workaround only for mt8195 */
@@ -198,6 +212,9 @@ static void xhci_mtk_init_quirk(struct x
/* workaround for SoCs using SSUSB about before IPM v1.6.0 */
xhci_mtk_rxfifo_depth_set(mtk);
+
+ /* EN7528 LTSSM timing fix */
+ xhci_mtk_ltssm_quirk(mtk);
}
static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk)
@@ -846,6 +863,7 @@ static const struct dev_pm_ops xhci_mtk_
static const struct of_device_id mtk_xhci_of_match[] = {
{ .compatible = "mediatek,mt8173-xhci"},
{ .compatible = "mediatek,mt8195-xhci"},
+ { .compatible = "econet,en7528-xhci"},
{ .compatible = "mediatek,mtk-xhci"},
{ },
};