From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5C9983164C5 for ; Fri, 10 Apr 2026 00:53:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775782423; cv=none; b=oCVNgd9TUmeaWZXcLnZeTkvYoLleMYVFnzR5btXv2tf+p3A0fcap1pPWrPCMAjLH4uEGakblcII81IZIGCym91QrcODvsYB4lVri88K9pFsAdZoVbw9S8ZitoICxTPGzcMm2WoU1Dg2pHka7U4XZHfYL8Jw6id10BWIogkfgGBk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775782423; c=relaxed/simple; bh=NFvlA2k7ZKF5s/mKYTomnRlhSXaZ8Fq0qpjzk3gHunQ=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=flLK649bWkDskAHSTHJJPCxAcgv4sRYCfgJlRS0EonHabHyH410+DPdko3SuckOXjcaoaAESX0T21pVAgzR9XB2UfhOW5oDq06mHIPIX6UlEK6AeAYWNDdI31cZRxgxa50+e67ZSWsWQ5NnfOJo5f5d0Xbq63mXco0F2bT/Vpu8= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=E0wqclTl; arc=none smtp.client-ip=209.85.128.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="E0wqclTl" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-488ab2db91aso21289605e9.3 for ; Thu, 09 Apr 2026 17:53:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775782415; x=1776387215; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=CnWUeY1tdgfii00Q939zG7kbngA3aJMynI2I8W/AA2Y=; b=E0wqclTlf+eY/313Jnc6m5kLpffMHDfX1pS6en6fq/EVrq/wUlPdtwomgoi7mP19ak sYzCbJkrOzK+60PkmueXyVSKdHH8nDLqpx/3lJ80Rhba8QM+QVjjUTE1DbUy8uqotJss s5PpQfhyZOnbJIlyfK9xAcx2GJ4R0lOKsmNt3H2AXxJ2eJ433QxLssaTDhjLBDCX6aC7 J2XybdZrL5JexSmn2mNV2oJjoVc9GhoYi8ZraVMVwA5nI1jv+BUh95Os8QkAJ/KIjtjA nq/fCYZ16Uilbb/FwPLkIubsazIWUWYOryVFqHaac7JJcDDJW13UtK1h7Z+dpoM67SSN foyw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775782415; x=1776387215; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=CnWUeY1tdgfii00Q939zG7kbngA3aJMynI2I8W/AA2Y=; b=g5uZQuofvlXdJl4U2Hr52rtpQXLYcR4+PSS2UXd/Sbh2hcI96WCGyN686Lx/96X9KK oMzu74iI39ePZxyMfAT+/7KxyWgpuoPeuHxTVdJsUoZPoW01hZWoU0HQCvflmoG+RyIz hzXNwYv9wPBiTJIDgr8eWyn1HZiEuI8ojPYp6N2ILBpC/7lgm3jECPpm5kgyyBGuUiiF cRouwX3/v0tqPi3Yt0yhNZ+ktlBDQxZIZrPRdAY0Y/9jMVjDZqMzh8t3uiaSOBPXq55S 9Qd/lDitOUiqn1HlgoD4y4UQz52wWMOyHdWqmp4YWmgYOR2hVV9tRirvduLSeI7RIYVa 1WZw== X-Forwarded-Encrypted: i=1; AJvYcCW17QEOtI2M9l9fvT60H5beZyS0MAzLyLWzh1rNNACLtCCUSGOvkNx7UnsCwYG4D7yHsBMuk4CzfiPIsVk=@vger.kernel.org X-Gm-Message-State: AOJu0Yx8vcw4f9zdULkBgsOuaEl/8KsXCCZRE5uS4xzEwtfubPmQmtMZ fm5zCtx6vxfr4ntJijZkBdTQLgYarF2XH+MFQHhG66BaHcOOmQ0JPY1W X-Gm-Gg: AeBDiesjRpnagsINj5AOmC9nDJazhKgmS6NN3T1OfaE5w/qgfdRHTNdLX+HSZbV04ln jBlVf6CdoQPpjZLsJZNfVJi6Uql0C8Ugbo7MWdoHZFzlLm/lUVOpqgnT54LhPgwkrvLcImelDSF hwzD4hS3Lv5UUQtunMyGbMaN2xWu44Gk0EIf99PnTWP0Z959mdGDakV3I7mT9yWzDy9x7rlBYGs 7V/noDZHN3Gp9sNbRfgX/VXKa8DPhtey0mo45p5hocwf39xemQ7aIatx9aHbC+SHEIUlIDNVXv0 aKujDmTkEExeHvVhof3C21iNhDx+L4t6V/m74k5rrU5Rdv5aYvHmwpKWfTpPnCwDcrR0xBDxT0s mOjoyWTLRWg4bECIqIK+ovkCaq5qiRtWmUxZX7tqBN1yBPNsosA0U0Wtj2jn2YzGV23AleVU0Nr an6uxdkpdCAuaB+ktnOxq5qle5 X-Received: by 2002:a05:600c:45ce:b0:488:a8f0:35bd with SMTP id 5b1f17b1804b1-488d67df745mr10284985e9.8.1775782414558; Thu, 09 Apr 2026 17:53:34 -0700 (PDT) Received: from rpi5.lan ([37.228.206.31]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-488d681ea59sm6502495e9.14.2026.04.09.17.53.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 09 Apr 2026 17:53:33 -0700 (PDT) From: Fabio Baltieri To: Heiner Kallweit , nic_swsd@realtek.com, Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Fabio Baltieri Subject: [PATCH RFC v2] r8169: implement SFP support Date: Fri, 10 Apr 2026 01:53:31 +0100 Message-ID: <20260410005331.2045-1-fabio.baltieri@gmail.com> X-Mailer: git-send-email 2.47.3 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Implement support for reading the identification and diagnostic information on SFP modules for rtl8127atf devices. This uses the sfp module, implements a GPIO devices for presence detection and loss of signal and i2c communication using the designware module. Signed-off-by: Fabio Baltieri --- Hi, here's the rework of the v1 I sent as "r8169: implement get_module functions for rtl8127atf", this is now implementing sfp support using the kernel sfp module, using the support nodes as well, including reusing the designware driver for i2c. Module presence detection seems to work correctly: [ 555.853597] sfp sfp.256: module removed [ 561.628005] sfp sfp.256: module QSFPTEK QT-SFP+-SR rev sn QT8250805132 dc 250806 Had to guess the gpio input register offset (the out of tree driver does not implement this), hopefully the realtek folks can chime in down the road and other functions can be implemented too. This is largely a copy paste of the txgbe txgbe_phy.c code, though the pcs and phylink code is missing since as far as I understand it should be implemented separately, so the sfp module here just reports the status via hwmon and is stuck in: Module state: waitdev Just looking for early feedback, this is functional as is but I guess it'll have to wait for the phylink support to get implemented first and then rebase on top of it, and I guess a realtek specific variant of the wx,i2c-snps-model property. Cheers, Fabio drivers/net/ethernet/realtek/Kconfig | 3 + drivers/net/ethernet/realtek/r8169_main.c | 302 +++++++++++++++++++++- 2 files changed, 304 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/realtek/Kconfig b/drivers/net/ethernet/realtek/Kconfig index 9b0f4f9631db..ae936e1586aa 100644 --- a/drivers/net/ethernet/realtek/Kconfig +++ b/drivers/net/ethernet/realtek/Kconfig @@ -88,6 +88,9 @@ config R8169 select CRC32 select PHYLIB select REALTEK_PHY + select REGMAP + select SFP + select GPIOLIB help Say Y here if you have a Realtek Ethernet adapter belonging to the following families: diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c index 58788d196c57..77266de27656 100644 --- a/drivers/net/ethernet/realtek/r8169_main.c +++ b/drivers/net/ethernet/realtek/r8169_main.c @@ -29,6 +29,15 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -724,6 +733,37 @@ enum rtl_dash_type { RTL_DASH_25_BP, }; +#define NODE_PROP(_NAME, _PROP) \ + (const struct software_node) { \ + .name = (_NAME), \ + .properties = (_PROP), \ + } + +enum rtl8169_swnodes { + SWNODE_GPIO = 0, + SWNODE_I2C, + SWNODE_SFP, + SWNODE_PHYLINK, + SWNODE_MAX +}; + +struct rtl8169_nodes { + char gpio_name[32]; + char i2c_name[32]; + char sfp_name[32]; + char phylink_name[32]; + struct property_entry gpio_props[2]; + struct property_entry i2c_props[4]; + struct property_entry sfp_props[5]; + struct property_entry phylink_props[3]; + struct software_node_ref_args i2c_ref[1]; + struct software_node_ref_args gpio0_ref[1]; + struct software_node_ref_args gpio1_ref[1]; + struct software_node_ref_args sfp_ref[1]; + struct software_node swnodes[SWNODE_MAX]; + const struct software_node *group[SWNODE_MAX + 1]; +}; + struct rtl8169_private { void __iomem *mmio_addr; /* memory map physical address */ struct pci_dev *pci_dev; @@ -770,6 +810,13 @@ struct rtl8169_private { struct r8169_led_classdev *leds; u32 ocp_base; + + struct platform_device *sfp_dev; + struct platform_device *i2c_dev; + struct clk_lookup *i2c_clock; + struct clk *i2c_clk; + struct gpio_chip *gpio; + struct rtl8169_nodes nodes; }; typedef void (*rtl_generic_fct)(struct rtl8169_private *tp); @@ -2411,6 +2458,246 @@ static int rtl8169_set_link_ksettings(struct net_device *ndev, return 0; } +static int r8169_swnodes_register(struct rtl8169_private *tp) +{ + struct rtl8169_nodes *nodes = &tp->nodes; + struct pci_dev *pdev = tp->pci_dev; + struct software_node *swnodes; + u32 id; + + id = pci_dev_id(pdev); + + snprintf(nodes->gpio_name, sizeof(nodes->gpio_name), "r8169_gpio-%x", id); + snprintf(nodes->i2c_name, sizeof(nodes->i2c_name), "r8169_i2c-%x", id); + snprintf(nodes->sfp_name, sizeof(nodes->sfp_name), "r8169_sfp-%x", id); + snprintf(nodes->phylink_name, sizeof(nodes->phylink_name), "r8169_phylink-%x", id); + + swnodes = nodes->swnodes; + + /* GPIO 8: module presence + * GPIO 11: rx signal lost + */ + nodes->gpio_props[0] = PROPERTY_ENTRY_STRING("pinctrl-names", "default"); + swnodes[SWNODE_GPIO] = NODE_PROP(nodes->gpio_name, nodes->gpio_props); + nodes->gpio0_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_GPIO], 8, GPIO_ACTIVE_LOW); + nodes->gpio1_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_GPIO], 11, GPIO_ACTIVE_LOW); + + nodes->i2c_props[0] = PROPERTY_ENTRY_STRING("compatible", "snps,designware-i2c"); + nodes->i2c_props[1] = PROPERTY_ENTRY_BOOL("wx,i2c-snps-model"); + nodes->i2c_props[2] = PROPERTY_ENTRY_U32("clock-frequency", I2C_MAX_STANDARD_MODE_FREQ); + swnodes[SWNODE_I2C] = NODE_PROP(nodes->i2c_name, nodes->i2c_props); + nodes->i2c_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_I2C]); + + nodes->sfp_props[0] = PROPERTY_ENTRY_STRING("compatible", "sff,sfp"); + nodes->sfp_props[1] = PROPERTY_ENTRY_REF_ARRAY("i2c-bus", nodes->i2c_ref); + nodes->sfp_props[2] = PROPERTY_ENTRY_REF_ARRAY("mod-def0-gpios", nodes->gpio0_ref); + nodes->sfp_props[3] = PROPERTY_ENTRY_REF_ARRAY("los-gpios", nodes->gpio1_ref); + swnodes[SWNODE_SFP] = NODE_PROP(nodes->sfp_name, nodes->sfp_props); + nodes->sfp_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_SFP]); + + nodes->phylink_props[0] = PROPERTY_ENTRY_STRING("managed", "in-band-status"); + nodes->phylink_props[1] = PROPERTY_ENTRY_REF_ARRAY("sfp", nodes->sfp_ref); + swnodes[SWNODE_PHYLINK] = NODE_PROP(nodes->phylink_name, nodes->phylink_props); + + nodes->group[SWNODE_GPIO] = &swnodes[SWNODE_GPIO]; + nodes->group[SWNODE_I2C] = &swnodes[SWNODE_I2C]; + nodes->group[SWNODE_SFP] = &swnodes[SWNODE_SFP]; + nodes->group[SWNODE_PHYLINK] = &swnodes[SWNODE_PHYLINK]; + + return software_node_register_node_group(nodes->group); +} + +static int r8169_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct rtl8169_private *tp = gpiochip_get_data(chip); + int val; + + val = r8168_mac_ocp_read(tp, 0xdc30); + + return !!(val & BIT(offset)); +} + +static int r8169_gpio_init(struct rtl8169_private *tp) +{ + struct gpio_chip *gc; + struct pci_dev *pdev = tp->pci_dev; + struct device *dev; + int ret; + + dev = &pdev->dev; + + gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL); + if (!gc) + return -ENOMEM; + + gc->label = devm_kasprintf(dev, GFP_KERNEL, "r8169_gpio-%x", + pci_dev_id(pdev)); + if (!gc->label) + return -ENOMEM; + + gc->base = -1; + gc->ngpio = 16; + gc->owner = THIS_MODULE; + gc->parent = dev; + gc->fwnode = software_node_fwnode(tp->nodes.group[SWNODE_GPIO]); + gc->get = r8169_gpio_get; + + ret = devm_gpiochip_add_data(dev, gc, tp); + if (ret) + return ret; + + tp->gpio = gc; + + return 0; +} + +static int r8169_clock_register(struct rtl8169_private *tp) +{ + struct pci_dev *pdev = tp->pci_dev; + struct clk_lookup *clock; + char clk_name[32]; + struct clk *clk; + + snprintf(clk_name, sizeof(clk_name), "i2c_designware.%d", + pci_dev_id(pdev)); + + /* 115MHz seems to result in an i2c clock of 100kHz */ + clk = clk_register_fixed_rate(NULL, clk_name, NULL, 0, 115000000); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + clock = clkdev_create(clk, NULL, "%s", clk_name); + if (!clock) { + clk_unregister(clk); + return -ENOMEM; + } + + tp->i2c_clk = clk; + tp->i2c_clock = clock; + + return 0; +} + +#define R8127_SDS_I2C_BASE 0xe200 + +static int r8169_i2c_read(void *context, unsigned int reg, unsigned int *val) +{ + struct rtl8169_private *tp = context; + + *val = r8168_mac_ocp_read(tp, R8127_SDS_I2C_BASE + reg); + + return 0; +} + +static int r8169_i2c_write(void *context, unsigned int reg, unsigned int val) +{ + struct rtl8169_private *tp = context; + + r8168_mac_ocp_write(tp, R8127_SDS_I2C_BASE + reg, val); + + return 0; +} + +static const struct regmap_config i2c_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_read = r8169_i2c_read, + .reg_write = r8169_i2c_write, + .fast_io = true, +}; + +static int r8169_i2c_register(struct rtl8169_private *tp) +{ + struct platform_device_info info = {}; + struct platform_device *i2c_dev; + struct regmap *i2c_regmap; + struct pci_dev *pdev = tp->pci_dev; + + i2c_regmap = devm_regmap_init(&pdev->dev, NULL, tp, &i2c_regmap_config); + if (IS_ERR(i2c_regmap)) { + dev_err(&pdev->dev, "failed to init I2C regmap\n"); + return PTR_ERR(i2c_regmap); + } + + info.parent = &pdev->dev; + info.fwnode = software_node_fwnode(tp->nodes.group[SWNODE_I2C]); + info.name = "i2c_designware"; + info.id = pci_dev_id(pdev); + + i2c_dev = platform_device_register_full(&info); + if (IS_ERR(i2c_dev)) + return PTR_ERR(i2c_dev); + + tp->i2c_dev = i2c_dev; + + return 0; +} + +static int r8169_sfp_register(struct rtl8169_private *tp) +{ + struct pci_dev *pdev = tp->pci_dev; + struct platform_device_info info = {}; + struct platform_device *sfp_dev; + + info.parent = &pdev->dev; + info.fwnode = software_node_fwnode(tp->nodes.group[SWNODE_SFP]); + info.name = "sfp"; + info.id = pci_dev_id(pdev); + sfp_dev = platform_device_register_full(&info); + if (IS_ERR(sfp_dev)) + return PTR_ERR(sfp_dev); + + tp->sfp_dev = sfp_dev; + + return 0; +} + +static int r8169_sfp_nodes_init(struct rtl8169_private *tp) +{ + struct pci_dev *pdev = tp->pci_dev; + int ret; + + ret = r8169_swnodes_register(tp); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "r8169_swnodes_register\n"); + + ret = r8169_gpio_init(tp); + if (ret < 0) { + ret = dev_err_probe(&pdev->dev, ret, "r8169_gpio_init\n"); + goto err_unregister_swnode; + } + + ret = r8169_clock_register(tp); + if (ret < 0) { + ret = dev_err_probe(&pdev->dev, ret, "r8169_clock_register\n"); + goto err_unregister_swnode; + } + + ret = r8169_i2c_register(tp); + if (ret < 0) { + ret = dev_err_probe(&pdev->dev, ret, "r8169_i2c_register\n"); + goto err_unregister_clk; + } + + ret = r8169_sfp_register(tp); + if (ret < 0) { + ret = dev_err_probe(&pdev->dev, ret, "r8169_sfp_register\n"); + goto err_unregister_i2c; + } + + return 0; + +err_unregister_i2c: + platform_device_unregister(tp->i2c_dev); +err_unregister_clk: + clkdev_drop(tp->i2c_clock); + clk_unregister(tp->i2c_clk); +err_unregister_swnode: + software_node_unregister_node_group(tp->nodes.group); + + return ret; +} + static const struct ethtool_ops rtl8169_ethtool_ops = { .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_MAX_FRAMES, @@ -5289,6 +5576,14 @@ static void rtl_remove_one(struct pci_dev *pdev) /* restore original MAC address */ rtl_rar_set(tp, tp->dev->perm_addr); + + if (tp->sfp_mode) { + platform_device_unregister(tp->sfp_dev); + platform_device_unregister(tp->i2c_dev); + clkdev_drop(tp->i2c_clock); + clk_unregister(tp->i2c_clk); + software_node_unregister_node_group(tp->nodes.group); + } } static const struct net_device_ops rtl_netdev_ops = { @@ -5675,8 +5970,13 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) if (rtl_is_8125(tp)) { u16 data = r8168_mac_ocp_read(tp, 0xd006); - if ((data & 0xff) == 0x07) + if ((data & 0xff) == 0x07) { tp->sfp_mode = true; + + rc = r8169_sfp_nodes_init(tp); + if (rc < 0) + return rc; + } } tp->dash_type = rtl_get_dash_type(tp); -- 2.47.3