[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20251021-macb-eyeq5-v1-7-3b0b5a9d2f85@bootlin.com>
Date: Tue, 21 Oct 2025 18:32:48 +0200
From: Théo Lebrun <theo.lebrun@...tlin.com>
To: Andrew Lunn <andrew+netdev@...n.ch>,
"David S. Miller" <davem@...emloft.net>, Eric Dumazet <edumazet@...gle.com>,
Jakub Kicinski <kuba@...nel.org>, Paolo Abeni <pabeni@...hat.com>,
Rob Herring <robh@...nel.org>, Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>,
Nicolas Ferre <nicolas.ferre@...rochip.com>,
Claudiu Beznea <claudiu.beznea@...on.dev>,
Vladimir Kondratiev <vladimir.kondratiev@...ileye.com>,
Grégory Clement <gregory.clement@...tlin.com>,
Russell King <linux@...linux.org.uk>, Vinod Koul <vkoul@...nel.org>,
Kishon Vijay Abraham I <kishon@...nel.org>,
Michael Turquette <mturquette@...libre.com>,
Stephen Boyd <sboyd@...nel.org>,
Thomas Bogendoerfer <tsbogend@...ha.franken.de>,
Philipp Zabel <p.zabel@...gutronix.de>
Cc: netdev@...r.kernel.org, devicetree@...r.kernel.org,
linux-kernel@...r.kernel.org, linux-mips@...r.kernel.org,
linux-phy@...ts.infradead.org, linux-clk@...r.kernel.org,
Tawfik Bayouk <tawfik.bayouk@...ileye.com>,
Thomas Petazzoni <thomas.petazzoni@...tlin.com>,
Benoît Monin <benoit.monin@...tlin.com>,
Maxime Chevallier <maxime.chevallier@...tlin.com>,
Théo Lebrun <theo.lebrun@...tlin.com>
Subject: [PATCH net-next 07/12] phy: Add driver for EyeQ5 Ethernet PHY
wrapper
EyeQ5 embeds a system-controller called OLB. It features many unrelated
registers, and some of those are registers used to configure the
integration of the RGMII/SGMII Cadence PHY used by MACB/GEM instances.
Wrap in a neat generic PHY provider, exposing two PHYs with standard
phy_init() / phy_set_mode() / phy_power_on() operations.
Signed-off-by: Théo Lebrun <theo.lebrun@...tlin.com>
---
MAINTAINERS | 1 +
drivers/phy/Kconfig | 13 +++
drivers/phy/Makefile | 1 +
drivers/phy/phy-eyeq5-eth.c | 254 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 269 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 4faa7719bf86..0ff4d9ce57e8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17382,6 +17382,7 @@ F: arch/mips/boot/dts/mobileye/
F: arch/mips/configs/eyeq5_defconfig
F: arch/mips/mobileye/board-epm5.its.S
F: drivers/clk/clk-eyeq.c
+F: drivers/phy/phy-eyeq5-eth.c
F: drivers/pinctrl/pinctrl-eyeq5.c
F: drivers/reset/reset-eyeq.c
F: include/dt-bindings/clock/mobileye,eyeq5-clk.h
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 678dd0452f0a..1aa6eff12dbc 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -101,6 +101,19 @@ config PHY_NXP_PTN3222
schemes. It supports all three USB 2.0 data rates: Low Speed, Full
Speed and High Speed.
+config PHY_EYEQ5_ETH
+ tristate "Ethernet PHY Driver on EyeQ5"
+ depends on OF
+ depends on MACH_EYEQ5 || COMPILE_TEST
+ select AUXILIARY_BUS
+ select GENERIC_PHY
+ default MACH_EYEQ5
+ help
+ Enable this to support the Ethernet PHY integrated on EyeQ5.
+ It supports both RGMII and SGMII. Registers are located in a
+ shared register region called OLB. If M is selected, the
+ module will be called phy-eyeq5-eth.
+
source "drivers/phy/allwinner/Kconfig"
source "drivers/phy/amlogic/Kconfig"
source "drivers/phy/broadcom/Kconfig"
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index bfb27fb5a494..8289497ece55 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_PHY_SNPS_EUSB2) += phy-snps-eusb2.o
obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o
obj-$(CONFIG_PHY_AIROHA_PCIE) += phy-airoha-pcie.o
obj-$(CONFIG_PHY_NXP_PTN3222) += phy-nxp-ptn3222.o
+obj-$(CONFIG_PHY_EYEQ5_ETH) += phy-eyeq5-eth.o
obj-y += allwinner/ \
amlogic/ \
broadcom/ \
diff --git a/drivers/phy/phy-eyeq5-eth.c b/drivers/phy/phy-eyeq5-eth.c
new file mode 100644
index 000000000000..b65c3d733f6c
--- /dev/null
+++ b/drivers/phy/phy-eyeq5-eth.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/array_size.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/lockdep.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/phy/phy.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#define EQ5_PHY_COUNT 2
+
+#define EQ5_PHY0_GP 0x128
+#define EQ5_PHY1_GP 0x12c
+#define EQ5_PHY0_SGMII 0x134
+#define EQ5_PHY1_SGMII 0x138
+
+#define EQ5_GP_TX_SWRST_DIS BIT(0) // Tx SW reset
+#define EQ5_GP_TX_M_CLKE BIT(1) // Tx M clock enable
+#define EQ5_GP_SYS_SWRST_DIS BIT(2) // Sys SW reset
+#define EQ5_GP_SYS_M_CLKE BIT(3) // Sys clock enable
+#define EQ5_GP_SGMII_MODE BIT(4) // SGMII mode
+#define EQ5_GP_RGMII_DRV GENMASK(8, 5) // RGMII drive strength
+
+#define EQ5_SGMII_PWR_EN BIT(0)
+#define EQ5_SGMII_RST_DIS BIT(1)
+#define EQ5_SGMII_PLL_EN BIT(2)
+#define EQ5_SGMII_SIG_DET_SW BIT(3)
+#define EQ5_SGMII_PWR_STATE BIT(4)
+#define EQ5_SGMII_PLL_ACK BIT(18)
+#define EQ5_SGMII_PWR_STATE_ACK GENMASK(24, 20)
+
+struct eq5_phy_inst {
+ struct eq5_phy_private *priv;
+ struct phy *phy;
+ void __iomem *gp, *sgmii;
+ phy_interface_t phy_interface;
+};
+
+struct eq5_phy_private {
+ struct device *dev;
+ struct eq5_phy_inst phys[EQ5_PHY_COUNT];
+};
+
+static int eq5_phy_init(struct phy *phy)
+{
+ struct eq5_phy_inst *inst = phy_get_drvdata(phy);
+ struct eq5_phy_private *priv = inst->priv;
+ struct device *dev = priv->dev;
+ u32 reg;
+
+ dev_dbg(dev, "phy_init(inst=%ld)\n", inst - priv->phys);
+
+ writel(0, inst->gp);
+ writel(0, inst->sgmii);
+
+ udelay(5);
+
+ reg = readl(inst->gp) | EQ5_GP_TX_SWRST_DIS | EQ5_GP_TX_M_CLKE |
+ EQ5_GP_SYS_SWRST_DIS | EQ5_GP_SYS_M_CLKE |
+ FIELD_PREP(EQ5_GP_RGMII_DRV, 0x9);
+ writel(reg, inst->gp);
+
+ return 0;
+}
+
+static int eq5_phy_exit(struct phy *phy)
+{
+ struct eq5_phy_inst *inst = phy_get_drvdata(phy);
+ struct eq5_phy_private *priv = inst->priv;
+ struct device *dev = priv->dev;
+
+ dev_dbg(dev, "phy_exit(inst=%ld)\n", inst - priv->phys);
+
+ writel(0, inst->gp);
+ writel(0, inst->sgmii);
+ udelay(5);
+
+ return 0;
+}
+
+static int eq5_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ struct eq5_phy_inst *inst = phy_get_drvdata(phy);
+ struct eq5_phy_private *priv = inst->priv;
+ struct device *dev = priv->dev;
+
+ dev_dbg(dev, "phy_set_mode(inst=%ld, mode=%d, submode=%d)\n",
+ inst - priv->phys, mode, submode);
+
+ if (mode != PHY_MODE_ETHERNET)
+ return -EOPNOTSUPP;
+
+ if (!phy_interface_mode_is_rgmii(submode) &&
+ submode != PHY_INTERFACE_MODE_SGMII)
+ return -EOPNOTSUPP;
+
+ inst->phy_interface = submode;
+ return 0;
+}
+
+static int eq5_phy_power_on(struct phy *phy)
+{
+ struct eq5_phy_inst *inst = phy_get_drvdata(phy);
+ struct eq5_phy_private *priv = inst->priv;
+ struct device *dev = priv->dev;
+ u32 reg;
+
+ dev_dbg(dev, "phy_power_on(inst=%ld)\n", inst - priv->phys);
+
+ if (inst->phy_interface == PHY_INTERFACE_MODE_SGMII) {
+ writel(readl(inst->gp) | EQ5_GP_SGMII_MODE, inst->gp);
+
+ reg = EQ5_SGMII_PWR_EN | EQ5_SGMII_RST_DIS | EQ5_SGMII_PLL_EN;
+ writel(reg, inst->sgmii);
+
+ if (readl_poll_timeout(inst->sgmii, reg,
+ reg & EQ5_SGMII_PLL_ACK, 1, 100)) {
+ dev_err(dev, "PLL timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ reg = readl(inst->sgmii);
+ reg |= EQ5_SGMII_PWR_STATE | EQ5_SGMII_SIG_DET_SW;
+ writel(reg, inst->sgmii);
+ } else {
+ writel(readl(inst->gp) & ~EQ5_GP_SGMII_MODE, inst->gp);
+ writel(0, inst->sgmii);
+ }
+
+ return 0;
+}
+
+static int eq5_phy_power_off(struct phy *phy)
+{
+ struct eq5_phy_inst *inst = phy_get_drvdata(phy);
+ struct eq5_phy_private *priv = inst->priv;
+ struct device *dev = priv->dev;
+
+ dev_dbg(dev, "phy_power_off(inst=%ld)\n", inst - priv->phys);
+
+ writel(readl(inst->gp) & ~EQ5_GP_SGMII_MODE, inst->gp);
+ writel(0, inst->sgmii);
+
+ return 0;
+}
+
+static const struct phy_ops eq5_phy_ops = {
+ .init = eq5_phy_init,
+ .exit = eq5_phy_exit,
+ .set_mode = eq5_phy_set_mode,
+ .power_on = eq5_phy_power_on,
+ .power_off = eq5_phy_power_off,
+};
+
+static struct phy *eq5_phy_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct eq5_phy_private *priv = dev_get_drvdata(dev);
+
+ if (args->args_count != 1 || args->args[0] > 1)
+ return ERR_PTR(-EINVAL);
+
+ return priv->phys[args->args[0]].phy;
+}
+
+static int eq5_phy_probe_phy(struct eq5_phy_private *priv, unsigned int index,
+ void __iomem *base, unsigned int gp,
+ unsigned int sgmii)
+{
+ struct eq5_phy_inst *inst = &priv->phys[index];
+ struct device *dev = priv->dev;
+ struct phy *phy;
+
+ phy = devm_phy_create(dev, dev->of_node, &eq5_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create PHY %u\n", index);
+ return PTR_ERR(phy);
+ }
+
+ inst->priv = priv;
+ inst->phy = phy;
+ inst->gp = base + gp;
+ inst->sgmii = base + sgmii;
+ inst->phy_interface = PHY_INTERFACE_MODE_NA;
+ phy_set_drvdata(phy, inst);
+
+ return 0;
+}
+
+static int eq5_phy_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct phy_provider *provider;
+ struct eq5_phy_private *priv;
+ void __iomem *base;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ dev_set_drvdata(dev, priv);
+
+ base = (void __iomem *)dev_get_platdata(dev);
+
+ ret = eq5_phy_probe_phy(priv, 0, base, EQ5_PHY0_GP, EQ5_PHY0_SGMII);
+ if (ret)
+ return ret;
+
+ ret = eq5_phy_probe_phy(priv, 1, base, EQ5_PHY1_GP, EQ5_PHY1_SGMII);
+ if (ret)
+ return ret;
+
+ provider = devm_of_phy_provider_register(dev, eq5_phy_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(dev, "registering provider failed\n");
+ return PTR_ERR(provider);
+ }
+
+ return 0;
+}
+
+static const struct auxiliary_device_id eq5_phy_id_table[] = {
+ { .name = "clk_eyeq.phy" },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, eq5_phy_id_table);
+
+static struct auxiliary_driver eq5_phy_driver = {
+ .probe = eq5_phy_probe,
+ .id_table = eq5_phy_id_table,
+};
+module_auxiliary_driver(eq5_phy_driver);
+
+MODULE_DESCRIPTION("EyeQ5 Ethernet PHY driver");
+MODULE_AUTHOR("Théo Lebrun <theo.lebrun@...tlin.com>");
+MODULE_LICENSE("GPL");
--
2.51.1
Powered by blists - more mailing lists