From nobody Sun Feb 8 14:57:43 2026 Received: from mail-ed1-f53.google.com (mail-ed1-f53.google.com [209.85.208.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 6781D1FBC90; Wed, 14 May 2025 17:35:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1747244160; cv=none; b=Kn4FNNhboMMmFO48Vl4wsm4VQUa5wP6GYTNiNG/Xod29qJDukB1p8tnh+osf3RWiggiiR53gdiezOrqZJrEr4NvVqDAEAbIQFGfTgF/PXcP0X/PwCnpHsNJPtz2+4RD+Xlu7dAGJMtumFfbsoSz+7jCbcp7XK3AwSRzPCG6lsOg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1747244160; c=relaxed/simple; bh=Cpolp/EIdECa6KwCIincBqvMNvZyZfcVn7WbM46WVlY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=cmWJbQWRRtV4numauf+KCJBn2P5N6IdOnTLN/eMwzj43x64MFoxYFYphKXFOkKHiEKMxDNqjQFPD7YXKkqW1hRNy1yr93KgSgrSv8foOLDDqFRqbEmJVAypUFCtkQ32GxHcHcdH15lIY/lOou6O9gskusGBeTMhXo1guI1OGsBs= 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=UQDdERnS; arc=none smtp.client-ip=209.85.208.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="UQDdERnS" Received: by mail-ed1-f53.google.com with SMTP id 4fb4d7f45d1cf-5fbc736f0c7so110932a12.2; Wed, 14 May 2025 10:35:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1747244155; x=1747848955; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Al/2Xkj2PRXgxCEbovM72Z0+TzszChKKZTMgvGrLkFI=; b=UQDdERnSViMm5wyDgHzmmqB7KpKoLJJFXjqiwVN+IhFwXnta7pWlwUIB5ksmGJq7m/ e8WgDyw/MXSSn4fullg6OkE+BJy9Iyp6RwEsVzXshAvtw8P2QKK6CDEb/nj0fw2apVru MGhmxkTIuZdvOzN8nx3W4sL+lLJyCdY9N5cF65PiljVYffXkoL27PhXhzOyGZlELm/+i 9MxkyeqylXm0e5ZxxOYTblXLUJrHzTdug8KWiv9a0Je51faWNa3T4s2krSW09Jbp0Cs+ QXyknAoxtVg7SA9JE83wPe7GZUKZ4uNvD8F9LwpPu/ojoIjgdu05z792DBxgEiufJnvr cHsw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1747244155; x=1747848955; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Al/2Xkj2PRXgxCEbovM72Z0+TzszChKKZTMgvGrLkFI=; b=c8r16eo8xIvrJI6U2h8v/WoJyfjOxWsj6KbC98ec56zTjYZy9NqJyyY3xog+V5ltBN G/+NBj/Q2Zz0wVlY5Naapkh1JlxqJqaTY2gQ1QYJdh7apSPgCVsEJUmmMArdtiz65/Dv hMzca0ZR76zbKyxXkqHwBWeNaZx1DF9e4zUZtz+E+ztiWv1jCHsf8jHt/o/uLFOZDrhV 84qyU1/6liN4dkAtX8GHZNGHLos5VfFQN9vFob1mgN5lOj00JZk5k5bHStBkda9zVIZv zUQVEI8/4QgTrw2JDumIEvHuHnJ4FZcnNpZPxTm2fjSZ9P7Tdu56tGfS4osWJcEjffmo RuKw== X-Forwarded-Encrypted: i=1; AJvYcCWBFjoEz0/ZRhrEHfCtsoH9ETvZfNZ+FgqH7jhBSWKgGNUD5H3+fa1eQr7e6/Y7J+9d+WngE8O+xj63GCA=@vger.kernel.org X-Gm-Message-State: AOJu0YyDOYvg+wocqedSGmmgbgUJ05MtdDsn7BNbq5Ep0WKk2OwDw5bS D6tV0BfwO1e7Mu80oJs2G+NjJM6G1zLEvJ2bE1N6ksvNhhMB/njAj/50WjS/ X-Gm-Gg: ASbGncvQq+UP+xM2x/B0t+eu2xJDW8qBRhKTKjHYwKYEztJk5tev7R9KKLoHzZJE+Bk QzbPkuOlJa6Ab7YD7vU/X8u6+vysL8qav4W5TVMWvfjhsu6j3YspWJWkLv6Z8L91s6sEtIKnBsP u+UA1j27bsBvFF8jgSnp9adsn6jdct1IBReUbnxo533UeMqx7A5624i9zjWpsBBoG3mzAibBppS 0LKQYbpkXrRpOZAWjSCEvG8ucP7F/tP5ol5xAwZ/4BGSK7ug3cI0WD+rlp+YOCoyOKU/i/WQVrf pblf4cM4pxLZE2Eugur0SKb+Vl3wEZt/86IcmARGyegH4tkCzxRfQXu7rXunkYH5oRMfAbbyuBM LyiQXSfFDZZgfZZ761niro3W1K0AysERTv5isTw== X-Google-Smtp-Source: AGHT+IGRsfQb4+hRnvou9OuY0gVlxy40SCU4nXevKjWBLuuowx5Wi4gaPdi9elOAdQABgKxIncaggg== X-Received: by 2002:a05:6402:5110:b0:5fd:c5c:e096 with SMTP id 4fb4d7f45d1cf-5ff988a1f06mr3318894a12.8.1747244154008; Wed, 14 May 2025 10:35:54 -0700 (PDT) Received: from Lord-Beerus.station (net-130-25-109-68.cust.vodafonedsl.it. [130.25.109.68]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-5fd1950e6dbsm6433092a12.80.2025.05.14.10.35.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 May 2025 10:35:53 -0700 (PDT) From: Stefano Radaelli To: netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: stefano.radaelli21@gmail.com, Andrew Lunn , Heiner Kallweit , Russell King , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Xu Liang Subject: [PATCH v2] net: phy: mxl-8611: add support for MaxLinear MxL86110/MxL86111 PHY Date: Wed, 14 May 2025 19:35:06 +0200 Message-ID: <20250514173511.158088-1-stefano.radaelli21@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250512191901.73823-1-stefano.radaelli21@gmail.com> References: <20250512191901.73823-1-stefano.radaelli21@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The MaxLinear MxL86110 is a low power Ethernet PHY transceiver IC compliant with the IEEE 802.3 Ethernet standard. It offers a cost-optimized solution suitable for routers, switches, and home gateways, supporting 1000, 100, and 10 Mbit/s data rates over CAT5e or higher twisted pair copper cable. The driver for this PHY is based on initial code provided by MaxLinear (MaxLinear Driver V1.0.0). Supported features: Suggested-by: Andrew Lunn ----------------------------------------+----------+----------+ Feature | MxL86110 | MxL86111 | ----------------------------------------+----------+----------+ General Device Initialization | x | x | Wake on LAN (WoL) | x | x | LED Configuration | x | x | RGMII Interface Configuration | x | x | SyncE Clock Output Config | x | x | Dual Media Mode (Fiber/Copper) | - | x | ----------------------------------------+----------+----------+ This driver was tested on a Variscite i.MX93-VAR-SOM and a i.MX8MP-VAR-SOM. LEDs configuration were tested using /sys/class/leds interface Changes since v1: - Rewrote the driver following the PHY subsystem documentation and the style of existing PHY drivers - Removed all vendor-specific code and unnecessary workarounds - Reimplemented LED control using the generic /sys/class/leds interface Suggested-by: Andrew Lunn Signed-off-by: Stefano Radaelli --- MAINTAINERS | 1 + drivers/net/phy/Kconfig | 11 + drivers/net/phy/Makefile | 1 + drivers/net/phy/mxl-8611x.c | 2037 +++++++++++++++++++++++++++++++++++ 4 files changed, 2050 insertions(+) create mode 100644 drivers/net/phy/mxl-8611x.c diff --git a/MAINTAINERS b/MAINTAINERS index 3563492e4eba..d2ba81381593 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14661,6 +14661,7 @@ MAXLINEAR ETHERNET PHY DRIVER M: Xu Liang L: netdev@vger.kernel.org S: Supported +F: drivers/net/phy/mxl-8611x.c F: drivers/net/phy/mxl-gpy.c =20 MCAN MMIO DEVICE DRIVER diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index d29f9f7fd2e1..079a12be6636 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -266,6 +266,17 @@ config MAXLINEAR_GPHY Support for the Maxlinear GPY115, GPY211, GPY212, GPY215, GPY241, GPY245 PHYs. =20 +config MAXLINEAR_8611X_PHY + tristate "MaxLinear MXL86110 and MXL86111 PHYs" + help + Support for the MaxLinear MXL86110 and MXL86111 Gigabit Ethernet + Physical Layer transceivers. + These PHYs are commonly found in networking equipment like + routers, switches, and embedded systems, providing the + physical interface for 10/100/1000 Mbps Ethernet connections + over copper media. If you are using a board with one of these + PHYs connected to your Ethernet MAC, you should enable this option. + source "drivers/net/phy/mediatek/Kconfig" =20 config MICREL_PHY diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 23ce205ae91d..4c2e24b40d82 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_MARVELL_PHY) +=3D marvell.o obj-$(CONFIG_MARVELL_88Q2XXX_PHY) +=3D marvell-88q2xxx.o obj-$(CONFIG_MARVELL_88X2222_PHY) +=3D marvell-88x2222.o obj-$(CONFIG_MAXLINEAR_GPHY) +=3D mxl-gpy.o +obj-$(CONFIG_MAXLINEAR_8611X_PHY) +=3D mxl-8611x.o obj-y +=3D mediatek/ obj-$(CONFIG_MESON_GXL_PHY) +=3D meson-gxl.o obj-$(CONFIG_MICREL_KS8995MA) +=3D spi_ks8995.o diff --git a/drivers/net/phy/mxl-8611x.c b/drivers/net/phy/mxl-8611x.c new file mode 100644 index 000000000000..ab15908046e1 --- /dev/null +++ b/drivers/net/phy/mxl-8611x.c @@ -0,0 +1,2037 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PHY driver for Maxlinear MXL86110 and MXL86111 + * + * Copyright 2023 MaxLinear Inc. + * + */ + +#include +#include +#include +#include +#include +#include + +#define MXL8611x_DRIVER_DESC "MXL86111 and MXL86110 PHY driver" + +/* PHY IDs */ +#define PHY_ID_MXL86110 0xC1335580 +#define PHY_ID_MXL86111 0xC1335588 + +/* required to access extended registers */ +#define MXL8611X_EXTD_REG_ADDR_OFFSET 0x1E +#define MXL8611X_EXTD_REG_ADDR_DATA 0x1F +#define PHY_IRQ_ENABLE_REG 0x12 +#define PHY_IRQ_ENABLE_REG_WOL BIT(6) + +/* only 1 page for MXL86110 */ +#define MXL86110_DEFAULT_PAGE 0 + +/* different pages for EXTD access for MXL86111*/ +/* SerDes/PHY Control Access Register - COM_EXT_SMI_SDS_PHY */ +#define MXL86111_EXT_SMI_SDS_PHY_REG 0xA000 +#define MXL86111_EXT_SMI_SDS_PHYSPACE_MASK BIT(1) +#define MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE (0x1 << 1) +#define MXL86111_EXT_SMI_SDS_PHYUTP_SPACE (0x0 << 1) +#define MXL86111_EXT_SMI_SDS_PHY_AUTO (0xFF) + +/* SyncE Configuration Register - COM_EXT SYNCE_CFG */ +#define MXL8611X_EXT_SYNCE_CFG_REG 0xA012 +#define MXL8611X_EXT_SYNCE_CFG_CLK_FRE_SEL BIT(4) +#define MXL8611X_EXT_SYNCE_CFG_EN_SYNC_E_DURING_LNKDN BIT(5) +#define MXL8611X_EXT_SYNCE_CFG_EN_SYNC_E BIT(6) +#define MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_MASK GENMASK(3, 1) +#define MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_125M_PLL 0 +#define MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_25M 4 + +/* WOL registers */ +#define MXL86110_WOL_MAC_ADDR_HIGH_EXTD_REG 0xA007 /* high-> FF:FF = */ +#define MXL86110_WOL_MAC_ADDR_MIDDLE_EXTD_REG 0xA008 /* middle-> :FF:FF= <-middle */ +#define MXL86110_WOL_MAC_ADDR_LOW_EXTD_REG 0xA009 /* :F= F:FF <-low */ + +#define MXL8611X_EXT_WOL_CFG_REG 0xA00A +#define MXL8611X_EXT_WOL_CFG_WOLE_MASK BIT(3) +#define MXL8611X_EXT_WOL_CFG_WOLE_DISABLE 0 +#define MXL8611X_EXT_WOL_CFG_WOLE_ENABLE BIT(3) + +/* RGMII register */ +#define MXL8611X_EXT_RGMII_CFG1_REG 0xA003 +/* delay can be adjusted in steps of about 150ps */ +#define MXL8611X_EXT_RGMII_CFG1_NO_DELAY 0 +#define MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MAX (0xF << 10) +#define MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MIN (0x1 << 10) +/* Default value for Rx Clock Delay is 0ps (disabled) */ +#define MXL8611X_EXT_RGMII_CFG1_RX_DELAY_DEFAULT \ + MXL8611X_EXT_RGMII_CFG1_NO_DELAY + +#define MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MASK GENMASK(13, 10) +#define MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MAX (0xF << 0) +#define MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MIN (0x1 << 0) +#define MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MASK GENMASK(3, 0) +/* Default value for 1000 Mbps Tx Clock Delay is 150ps */ +#define MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_DEFAULT \ + MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MIN + +#define MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MAX (0xF << 4) +#define MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MIN (0x1 << 4) +#define MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK GENMASK(7, 4) +/* Default value for 10/100 Mbps Tx Clock Delay is 2250ps */ +#define MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DEFAULT \ + MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MAX + +#define MXL8611X_EXT_RGMII_CFG1_FULL_MASK \ + ((MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MASK) | \ + (MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MASK) | \ + (MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK)) + +/* EXT Sleep Control register */ +#define MXL8611x_UTP_EXT_SLEEP_CTRL_REG 0x27 +#define MXL8611x_UTP_EXT_SLEEP_CTRL_EN_SLEEP_SW_OFF 0 +#define MXL8611x_UTP_EXT_SLEEP_CTRL_EN_SLEEP_SW_MASK BIT(15) + +/* RGMII In-Band Status and MDIO Configuration Register */ +#define MXL8611X_EXT_RGMII_MDIO_CFG 0xA005 +#define MXL8611X_EXT_RGMII_MDIO_CFG_EPA0_MASK GENMASK(6, 6) +#define MXL8611X_EXT_RGMII_MDIO_CFG_EBA_MASK GENMASK(5, 5) +#define MXL8611X_EXT_RGMII_MDIO_CFG_BA_MASK GENMASK(4, 0) + +#define MXL8611x_MAX_LEDS 3 +/* LED registers and defines */ +#define MXL8611X_LED0_CFG_REG 0xA00C +#define MXL8611X_LED1_CFG_REG 0xA00D +#define MXL8611X_LED2_CFG_REG 0xA00E + +#define MXL8611X_LEDX_CFG_TRAFFIC_ACT_BLINK_IND BIT(13) +#define MXL8611X_LEDX_CFG_LINK_UP_FULL_DUPLEX_ON BIT(12) +#define MXL8611X_LEDX_CFG_LINK_UP_HALF_DUPLEX_ON BIT(11) +#define MXL8611X_LEDX_CFG_LINK_UP_TX_ACT_ON BIT(10) /* LED 0,1,2 default= */ +#define MXL8611X_LEDX_CFG_LINK_UP_RX_ACT_ON BIT(9) /* LED 0,1,2 default = */ +#define MXL8611X_LEDX_CFG_LINK_UP_TX_ON BIT(8) +#define MXL8611X_LEDX_CFG_LINK_UP_RX_ON BIT(7) +#define MXL8611X_LEDX_CFG_LINK_UP_1GB_ON BIT(6) /* LED 2 default */ +#define MXL8611X_LEDX_CFG_LINK_UP_100MB_ON BIT(5) /* LED 1 default */ +#define MXL8611X_LEDX_CFG_LINK_UP_10MB_ON BIT(4) /* LED 0 default */ +#define MXL8611X_LEDX_CFG_LINK_UP_COLLISION BIT(3) +#define MXL8611X_LEDX_CFG_LINK_UP_1GB_BLINK BIT(2) +#define MXL8611X_LEDX_CFG_LINK_UP_100MB_BLINK BIT(1) +#define MXL8611X_LEDX_CFG_LINK_UP_10MB_BLINK BIT(0) + +#define MXL8611X_LED_BLINK_CFG_REG 0xA00F +#define MXL8611X_LED_BLINK_CFG_FREQ_MODE1_2HZ 0 +#define MXL8611X_LED_BLINK_CFG_FREQ_MODE1_4HZ BIT(0) +#define MXL8611X_LED_BLINK_CFG_FREQ_MODE1_8HZ BIT(1) +#define MXL8611X_LED_BLINK_CFG_FREQ_MODE1_16HZ (BIT(1) | BIT(0)) +#define MXL8611X_LED_BLINK_CFG_FREQ_MODE2_2HZ 0 +#define MXL8611X_LED_BLINK_CFG_FREQ_MODE2_4HZ BIT(2) +#define MXL8611X_LED_BLINK_CFG_FREQ_MODE2_8HZ BIT(3) +#define MXL8611X_LED_BLINK_CFG_FREQ_MODE2_16HZ (BIT(3) | BIT(2)) +#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_50_PERC_ON 0 +#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_67_PERC_ON (BIT(4)) +#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_75_PERC_ON (BIT(5)) +#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_83_PERC_ON (BIT(5) | BIT(4)) +#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_50_PERC_OFF (BIT(6)) +#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_33_PERC_ON (BIT(6) | BIT(4)) +#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_25_PERC_ON (BIT(6) | BIT(5)) +#define MXL8611X_LED_BLINK_CFG_DUTY_CYCLE_17_PERC_ON (BIT(6) | BIT(5) | BI= T(4)) + +/* Specific Status Register - PHY_STAT */ +#define MXL86111_PHY_STAT_REG 0x11 +#define MXL86111_PHY_STAT_SPEED_MASK GENMASK(15, 14) +#define MXL86111_PHY_STAT_SPEED_OFFSET 14 +#define MXL86111_PHY_STAT_SPEED_10M 0x0 +#define MXL86111_PHY_STAT_SPEED_100M 0x1 +#define MXL86111_PHY_STAT_SPEED_1000M 0x2 +#define MXL86111_PHY_STAT_DPX_OFFSET 13 +#define MXL86111_PHY_STAT_DPX BIT(13) +#define MXL86111_PHY_STAT_LSRT BIT(10) + +/* 3 phy reg page modes,auto mode combines utp and fiber mode*/ +#define MXL86111_MODE_FIBER 0x1 +#define MXL86111_MODE_UTP 0x2 +#define MXL86111_MODE_AUTO 0x3 + +/* FIBER Auto-Negotiation link partner ability - SDS_AN_LPA */ +#define MXL86111_SDS_AN_LPA_PAUSE (0x3 << 7) +#define MXL86111_SDS_AN_LPA_ASYM_PAUSE (0x2 << 7) + +/* Chip Configuration Register - COM_EXT_CHIP_CFG */ +#define MXL86111_EXT_CHIP_CFG_REG 0xA001 +#define MXL86111_EXT_CHIP_CFG_RXDLY_ENABLE BIT(8) +#define MXL86111_EXT_CHIP_CFG_SW_RST_N_MODE BIT(15) + +#define MXL86111_EXT_CHIP_CFG_MODE_SEL_MASK GENMASK(2, 0) +#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_RGMII 0 +#define MXL86111_EXT_CHIP_CFG_MODE_FIBER_TO_RGMII 1 +#define MXL86111_EXT_CHIP_CFG_MODE_UTP_FIBER_TO_RGMII 2 +#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_SGMII 3 +#define MXL86111_EXT_CHIP_CFG_MODE_SGPHY_TO_RGMAC 4 +#define MXL86111_EXT_CHIP_CFG_MODE_SGMAC_TO_RGPHY 5 +#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_FIBER_AUTO 6 +#define MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_FIBER_FORCE 7 + +/* Miscellaneous Control Register - COM_EXT _MISC_CFG */ +#define MXL86111_EXT_MISC_CONFIG_REG 0xA006 +#define MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL BIT(0) +#define MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_1000BX (0x1 << 0) +#define MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_100BX (0x0 << 0) + +/* Phy fiber Link timer cfg2 Register - EXT_SDS_LINK_TIMER_CFG2 */ +#define MXL86111_EXT_SDS_LINK_TIMER_CFG2_REG 0xA5 +#define MXL86111_EXT_SDS_LINK_TIMER_CFG2_EN_AUTOSEN BIT(15) + +/* default values of PHY register, required for Dual Media mode */ +#define MII_BMSR_DEFAULT_VAL 0x7949 +#define MII_ESTATUS_DEFAULT_VAL 0x2000 + +/* Timeout in ms for PHY SW reset check in STD_CTRL/SDS_CTRL */ +#define BMCR_RESET_TIMEOUT 500 + +/** + * mxl86110_read_page() - read reg page + * @phydev: pointer to the phy_device + * + * returns current reg space of MxL86110 + * (only MXL86111_EXT_SMI_SDS_PHYUTP_SPACE supported) or negative errno co= de + */ +static int mxl86110_read_page(struct phy_device *phydev) +{ + return __phy_read(phydev, MXL8611X_EXTD_REG_ADDR_OFFSET); +}; + +/** + * mxl86110_write_page() - write reg page + * @phydev: pointer to the phy_device + * @page: The reg page to write + * + * returns current reg space of MxL86110 + * (only MXL86111_EXT_SMI_SDS_PHYUTP_SPACE supported) or negative errno co= de + */ +static int mxl86110_write_page(struct phy_device *phydev, int page) +{ + return __phy_write(phydev, MXL8611X_EXTD_REG_ADDR_OFFSET, page); +}; + +/** + * mxlphy_write_extended_reg() - write to a PHY's extended register + * @phydev: pointer to a &struct phy_device + * @regnum: register number to write + * @val: value to write to @regnum + * + * NOTE:The calling function must have taken the MDIO bus lock. + * + * returns 0 or negative error code + */ +static int mxlphy_write_extended_reg(struct phy_device *phydev, u16 regnum= , u16 val) +{ + int ret; + + ret =3D __phy_write(phydev, MXL8611X_EXTD_REG_ADDR_OFFSET, regnum); + if (ret < 0) + return ret; + + return __phy_write(phydev, MXL8611X_EXTD_REG_ADDR_DATA, val); +} + +/** + * mxlphy_locked_write_extended_reg() - write to a PHY's extended register + * @phydev: pointer to a &struct phy_device + * @regnum: register number to write + * @val: value to write to @regnum + * + * returns 0 or negative error code + */ +static int mxlphy_locked_write_extended_reg(struct phy_device *phydev, u16= regnum, + u16 val) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret =3D mxlphy_write_extended_reg(phydev, regnum, val); + phy_unlock_mdio_bus(phydev); + + return ret; +} + +/** + * mxlphy_read_extended_reg() - write to a PHY's extended register + * @phydev: pointer to a &struct phy_device + * @regnum: register number to write + * @val: value to write to @regnum + * + * NOTE:The calling function must have taken the MDIO bus lock. + * + * returns the value of regnum reg or negative error code + */ +static int mxlphy_read_extended_reg(struct phy_device *phydev, u16 regnum) +{ + int ret; + + ret =3D __phy_write(phydev, MXL8611X_EXTD_REG_ADDR_OFFSET, regnum); + if (ret < 0) + return ret; + return __phy_read(phydev, MXL8611X_EXTD_REG_ADDR_DATA); +} + +/** + * mxlphy_read_extended_reg() - write to a PHY's extended register + * @phydev: pointer to a &struct phy_device + * @regnum: register number to write + * @val: value to write to @regnum + * + * returns the value of regnum reg or negative error code + */ +static int mxlphy_locked_read_extended_reg(struct phy_device *phydev, u16 = regnum) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret =3D mxlphy_read_extended_reg(phydev, regnum); + phy_unlock_mdio_bus(phydev); + + return ret; +} + +/** + * mxlphy_modify_extended_reg() - modify bits of a PHY's extended register + * @phydev: pointer to the phy_device + * @regnum: register number to write + * @mask: bit mask of bits to clear + * @set: bit mask of bits to set + * + * NOTE: register value =3D (old register value & ~mask) | set. + * The caller must have taken the MDIO bus lock. + * + * returns 0 or negative error code + */ +static int mxlphy_modify_extended_reg(struct phy_device *phydev, u16 regnu= m, u16 mask, + u16 set) +{ + int ret; + + ret =3D __phy_write(phydev, MXL8611X_EXTD_REG_ADDR_OFFSET, regnum); + if (ret < 0) + return ret; + + return __phy_modify(phydev, MXL8611X_EXTD_REG_ADDR_DATA, mask, set); +} + +/** + * mxlphy_locked_modify_extended_reg() - modify bits of a PHY's extended r= egister + * @phydev: pointer to the phy_device + * @regnum: register number to write + * @mask: bit mask of bits to clear + * @set: bit mask of bits to set + * + * NOTE: register value =3D (old register value & ~mask) | set. + * + * returns 0 or negative error code + */ +static int mxlphy_locked_modify_extended_reg(struct phy_device *phydev, u1= 6 regnum, + u16 mask, u16 set) +{ + int ret; + + phy_lock_mdio_bus(phydev); + ret =3D mxlphy_modify_extended_reg(phydev, regnum, mask, set); + phy_unlock_mdio_bus(phydev); + + return ret; +} + +/** + * mxlphy_get_wol() - report if wake-on-lan is enabled + * @phydev: pointer to the phy_device + * @wol: a pointer to a &struct ethtool_wolinfo + */ +static void mxlphy_get_wol(struct phy_device *phydev, struct ethtool_wolin= fo *wol) +{ + int value; + + wol->supported =3D WAKE_MAGIC; + wol->wolopts =3D 0; + value =3D mxlphy_locked_read_extended_reg(phydev, MXL8611X_EXT_WOL_CFG_RE= G); + if (value >=3D 0 && (value & MXL8611X_EXT_WOL_CFG_WOLE_MASK)) + wol->wolopts |=3D WAKE_MAGIC; +} + +/** + * mxlphy_set_wol() - enable/disable wake-on-lan + * @phydev: pointer to the phy_device + * @wol: a pointer to a &struct ethtool_wolinfo + * + * Configures the WOL Magic Packet MAC + * returns 0 or negative errno code + */ +static int mxlphy_set_wol(struct phy_device *phydev, struct ethtool_wolinf= o *wol) +{ + struct net_device *netdev; + int page_to_restore; + const u8 *mac; + int ret =3D 0; + + if (wol->wolopts & WAKE_MAGIC) { + netdev =3D phydev->attached_dev; + if (!netdev) + return -ENODEV; + + page_to_restore =3D phy_select_page(phydev, MXL86110_DEFAULT_PAGE); + if (page_to_restore < 0) + goto error; + + /* Configure the MAC address of the WOL magic packet */ + mac =3D (const u8 *)netdev->dev_addr; + ret =3D mxlphy_write_extended_reg(phydev, MXL86110_WOL_MAC_ADDR_HIGH_EXT= D_REG, + ((mac[0] << 8) | mac[1])); + if (ret < 0) + goto error; + ret =3D mxlphy_write_extended_reg(phydev, MXL86110_WOL_MAC_ADDR_MIDDLE_E= XTD_REG, + ((mac[2] << 8) | mac[3])); + if (ret < 0) + goto error; + ret =3D mxlphy_write_extended_reg(phydev, MXL86110_WOL_MAC_ADDR_LOW_EXTD= _REG, + ((mac[4] << 8) | mac[5])); + if (ret < 0) + goto error; + + ret =3D mxlphy_modify_extended_reg(phydev, MXL8611X_EXT_WOL_CFG_REG, + MXL8611X_EXT_WOL_CFG_WOLE_MASK, + MXL8611X_EXT_WOL_CFG_WOLE_ENABLE); + if (ret < 0) + goto error; + + ret =3D __phy_modify(phydev, PHY_IRQ_ENABLE_REG, 0, + PHY_IRQ_ENABLE_REG_WOL); + if (ret < 0) + goto error; + + phydev_dbg(phydev, "%s, WOL Magic packet MAC: %02X:%02X:%02X:%02X:%02X:%= 02X\n", + __func__, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + } else { + page_to_restore =3D phy_select_page(phydev, MXL86110_DEFAULT_PAGE); + if (page_to_restore < 0) + goto error; + + ret =3D mxlphy_modify_extended_reg(phydev, MXL8611X_EXT_WOL_CFG_REG, + MXL8611X_EXT_WOL_CFG_WOLE_MASK, + MXL8611X_EXT_WOL_CFG_WOLE_DISABLE); + + ret =3D __phy_modify(phydev, PHY_IRQ_ENABLE_REG, + PHY_IRQ_ENABLE_REG_WOL, 0); + if (ret < 0) + goto error; + } + +error: + return phy_restore_page(phydev, page_to_restore, ret); +} + +/** + * mxl8611x_enable_leds_activity_blink() - Enable activity blink on all PH= Y LEDs + * @phydev: pointer to the PHY device structure + * + * Configure all PHY LEDs to blink on traffic activity regardless of their + * ON or OFF state. This behavior allows each LED to serve as a pure activ= ity + * indicator, independently of its use as a link status indicator. + * + * By default, each LED blinks only when it is also in the ON state. This = function + * modifies the appropriate registers (LABx fields) to enable blinking even + * when the LEDs are OFF, to allow the LED to be used as a traffic indicat= or + * without requiring it to also serve as a link status LED. + * + * NOTE: Any further LED customization can be performed via the + * /sys/class/led interface; the functions led_hw_is_supported, led_hw_con= trol_get, and + * led_hw_control_set are used to support this mechanism. + * + * Return: 0 on success or a negative error code. + */ +static int mxl8611x_enable_led_activity_blink(struct phy_device *phydev) +{ + u16 val; + int ret, index; + + for (index =3D 0; index < MXL8611x_MAX_LEDS; index++) { + val =3D mxlphy_read_extended_reg(phydev, MXL8611X_LED0_CFG_REG + index); + if (val < 0) + return val; + + val |=3D MXL8611X_LEDX_CFG_TRAFFIC_ACT_BLINK_IND; + + ret =3D mxlphy_write_extended_reg(phydev, MXL8611X_LED0_CFG_REG + index,= val); + if (ret) + return ret; + } + + return 0; +} + +static const unsigned long supported_triggers =3D (BIT(TRIGGER_NETDEV_LINK= _10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_HALF_DUPLEX) | + BIT(TRIGGER_NETDEV_FULL_DUPLEX) | + BIT(TRIGGER_NETDEV_TX) | + BIT(TRIGGER_NETDEV_RX)); + +static int mxl8611x_led_hw_is_supported(struct phy_device *phydev, u8 inde= x, + unsigned long rules) +{ + if (index >=3D MXL8611x_MAX_LEDS) + return -EINVAL; + + /* All combinations of the supported triggers are allowed */ + if (rules & ~supported_triggers) + return -EOPNOTSUPP; + + return 0; +} + +static int mxl8611x_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + u16 val; + + if (index >=3D MXL8611x_MAX_LEDS) + return -EINVAL; + + val =3D mxlphy_read_extended_reg(phydev, MXL8611X_LED0_CFG_REG + index); + if (val < 0) + return val; + + if (val & MXL8611X_LEDX_CFG_LINK_UP_TX_ACT_ON) + *rules |=3D BIT(TRIGGER_NETDEV_TX); + + if (val & MXL8611X_LEDX_CFG_LINK_UP_RX_ACT_ON) + *rules |=3D BIT(TRIGGER_NETDEV_RX); + + if (val & MXL8611X_LEDX_CFG_LINK_UP_HALF_DUPLEX_ON) + *rules |=3D BIT(TRIGGER_NETDEV_HALF_DUPLEX); + + if (val & MXL8611X_LEDX_CFG_LINK_UP_FULL_DUPLEX_ON) + *rules |=3D BIT(TRIGGER_NETDEV_FULL_DUPLEX); + + if (val & MXL8611X_LEDX_CFG_LINK_UP_10MB_ON) + *rules |=3D BIT(TRIGGER_NETDEV_LINK_10); + + if (val & MXL8611X_LEDX_CFG_LINK_UP_100MB_ON) + *rules |=3D BIT(TRIGGER_NETDEV_LINK_100); + + if (val & MXL8611X_LEDX_CFG_LINK_UP_1GB_ON) + *rules |=3D BIT(TRIGGER_NETDEV_LINK_1000); + + return 0; +}; + +static int mxl8611x_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 val =3D 0; + int ret; + + if (index >=3D MXL8611x_MAX_LEDS) + return -EINVAL; + + if (rules & BIT(TRIGGER_NETDEV_LINK_10)) + val |=3D MXL8611X_LEDX_CFG_LINK_UP_10MB_ON; + + if (rules & BIT(TRIGGER_NETDEV_LINK_100)) + val |=3D MXL8611X_LEDX_CFG_LINK_UP_100MB_ON; + + if (rules & BIT(TRIGGER_NETDEV_LINK_1000)) + val |=3D MXL8611X_LEDX_CFG_LINK_UP_1GB_ON; + + if (rules & BIT(TRIGGER_NETDEV_TX)) + val |=3D MXL8611X_LEDX_CFG_LINK_UP_TX_ACT_ON; + + if (rules & BIT(TRIGGER_NETDEV_RX)) + val |=3D MXL8611X_LEDX_CFG_LINK_UP_RX_ACT_ON; + + if (rules & BIT(TRIGGER_NETDEV_HALF_DUPLEX)) + val |=3D MXL8611X_LEDX_CFG_LINK_UP_HALF_DUPLEX_ON; + + if (rules & BIT(TRIGGER_NETDEV_FULL_DUPLEX)) + val |=3D MXL8611X_LEDX_CFG_LINK_UP_FULL_DUPLEX_ON; + + if (rules & BIT(TRIGGER_NETDEV_TX) || + rules & BIT(TRIGGER_NETDEV_RX)) + val |=3D MXL8611X_LEDX_CFG_TRAFFIC_ACT_BLINK_IND; + + ret =3D mxlphy_locked_write_extended_reg(phydev, MXL8611X_LED0_CFG_REG + = index, val); + if (ret) + return ret; + + return 0; +}; + +/** + * mxl8611x_broadcast_cfg() - applies broadcast configuration + * @phydev: pointer to the phy_device + * + * configures the broadcast setting for the PHY based on the device tree + * if the "mxl-8611x,broadcast-enabled" property is present the PHY broadc= asts + * address 0 on the MDIO bus. This feature enables PHY to always respond t= o MDIO access + * returns 0 or negative errno code + */ +static int mxl8611x_broadcast_cfg(struct phy_device *phydev) +{ + int ret =3D 0; + struct device_node *node; + u32 val; + + if (!phydev) { + pr_err("%s, Invalid phy_device pointer\n", __func__); + return -EINVAL; + } + + node =3D phydev->mdio.dev.of_node; + if (!node) { + phydev_err(phydev, "%s, Invalid device tree node\n", __func__); + return -EINVAL; + } + + val =3D mxlphy_read_extended_reg(phydev, MXL8611X_EXT_RGMII_MDIO_CFG); + + if (of_property_read_bool(node, "mxl-8611x,broadcast-enabled")) + val |=3D MXL8611X_EXT_RGMII_MDIO_CFG_EPA0_MASK; + else + val &=3D ~MXL8611X_EXT_RGMII_MDIO_CFG_EPA0_MASK; + + ret =3D mxlphy_write_extended_reg(phydev, MXL8611X_EXT_RGMII_MDIO_CFG, va= l); + if (ret) { + phydev_err(phydev, "%s, failed to write 0x%x to RGMII MDIO CFG register = (0x%x): ret =3D %d\n", + __func__, val, MXL8611X_EXT_RGMII_MDIO_CFG, ret); + return ret; + } + + return 0; +} + +/** + * mxl8611x_synce_clk_cfg() - applies syncE/clk output configuration + * @phydev: pointer to the phy_device + * + * Custom settings can be defined in custom config section of the driver + * returns 0 or negative errno code + */ +static int mxl8611x_synce_clk_cfg(struct phy_device *phydev) +{ + u16 mask =3D 0, value =3D 0; + int ret; + + /* + * Configures the clock output to its default setting as per the datashee= t. + * This results in a 25MHz clock output being selected in the + * COM_EXT_SYNCE_CFG register for SyncE configuration. + */ + value =3D MXL8611X_EXT_SYNCE_CFG_EN_SYNC_E | + FIELD_PREP(MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_MASK, + MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_25M); + mask =3D MXL8611X_EXT_SYNCE_CFG_EN_SYNC_E | + MXL8611X_EXT_SYNCE_CFG_CLK_SRC_SEL_MASK | + MXL8611X_EXT_SYNCE_CFG_CLK_FRE_SEL; + + /* Write clock output configuration */ + ret =3D mxlphy_locked_modify_extended_reg(phydev, MXL8611X_EXT_SYNCE_CFG_= REG, + mask, value); + if (ret < 0) + return ret; + + return 0; +} + +/** + * mxl86110_config_init() - initialize the PHY + * @phydev: pointer to the phy_device + * + * returns 0 or negative errno code + */ +static int mxl86110_config_init(struct phy_device *phydev) +{ + int page_to_restore, ret =3D 0; + unsigned int val =3D 0; + + page_to_restore =3D phy_select_page(phydev, MXL86110_DEFAULT_PAGE); + if (page_to_restore < 0) + goto err_restore_page; + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_RXID: + val =3D MXL8611X_EXT_RGMII_CFG1_RX_DELAY_DEFAULT; + break; + case PHY_INTERFACE_MODE_RGMII_TXID: + case PHY_INTERFACE_MODE_RGMII_ID: + val =3D MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_DEFAULT | + MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DEFAULT; + val |=3D MXL8611X_EXT_RGMII_CFG1_RX_DELAY_DEFAULT; + break; + default: + ret =3D -EINVAL; + goto err_restore_page; + } + ret =3D mxlphy_modify_extended_reg(phydev, MXL8611X_EXT_RGMII_CFG1_REG, + MXL8611X_EXT_RGMII_CFG1_FULL_MASK, val); + if (ret < 0) + goto err_restore_page; + + /* Configure RXDLY (RGMII Rx Clock Delay) to keep the default + * delay value on RX_CLK (2 ns for 125 MHz, 8 ns for 25 MHz/2.5 MHz) + */ + ret =3D mxlphy_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_REG, + MXL86111_EXT_CHIP_CFG_RXDLY_ENABLE, 1); + if (ret < 0) + goto err_restore_page; + + ret =3D mxl8611x_enable_led_activity_blink(phydev); + if (ret < 0) + goto err_restore_page; + + ret =3D mxl8611x_broadcast_cfg(phydev); + if (ret < 0) + goto err_restore_page; + +err_restore_page: + return phy_restore_page(phydev, page_to_restore, ret); +} + +struct mxl86111_priv { + /* dual_media_advertising used for Dual Media mode (MXL86111_EXT_SMI_SDS_= PHY_AUTO) */ + __ETHTOOL_DECLARE_LINK_MODE_MASK(dual_media_advertising); + + /* MXL86111_MODE_FIBER / MXL86111_MODE_UTP / MXL86111_MODE_AUTO*/ + u8 reg_page_mode; + u8 strap_mode; /* 8 working modes */ + /* current reg page of mxl86111 phy: + * MXL86111_EXT_SMI_SDS_PHYUTP_SPACE + * MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE + * MXL86111_EXT_SMI_SDS_PHY_AUTO + */ + u8 reg_page; +}; + +/** + * mxl86111_read_page() - read reg page + * @phydev: pointer to the phy_device + * + * returns current reg space of mxl86111 (MXL86111_EXT_SMI_SDS_PHYFIBER_SP= ACE/ + * MXL86111_EXT_SMI_SDS_PHYUTP_SPACE) or negative errno code + */ +static int mxl86111_read_page(struct phy_device *phydev) +{ + int old_page; + + old_page =3D mxlphy_locked_read_extended_reg(phydev, MXL86111_EXT_SMI_SDS= _PHY_REG); + if (old_page < 0) + return old_page; + + if ((old_page & MXL86111_EXT_SMI_SDS_PHYSPACE_MASK) =3D=3D MXL86111_EXT_S= MI_SDS_PHYFIBER_SPACE) + return MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE; + + return MXL86111_EXT_SMI_SDS_PHYUTP_SPACE; +}; + +/** + * mxl86111_write_page() - Set reg page + * @phydev: pointer to the phy_device + * @page: The reg page to set + * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE) + * + * returns 0 or negative errno code + */ +static int mxl86111_write_page(struct phy_device *phydev, int page) +{ + int mask =3D MXL86111_EXT_SMI_SDS_PHYSPACE_MASK; + int set; + + if ((page & MXL86111_EXT_SMI_SDS_PHYSPACE_MASK) =3D=3D MXL86111_EXT_SMI_S= DS_PHYFIBER_SPACE) + set =3D MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE; + else + set =3D MXL86111_EXT_SMI_SDS_PHYUTP_SPACE; + + return mxlphy_modify_extended_reg(phydev, MXL86111_EXT_SMI_SDS_PHY_REG, m= ask, set); +}; + +/** + * mxl86111_modify_bmcr_paged - modify bits of the PHY's BMCR register of = a given page + * @phydev: pointer to the phy_device + * @page: The reg page to operate + * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE) + * @mask: bit mask of bits to clear + * @set: bit mask of bits to set + * + * NOTE: new register value =3D (old register value & ~mask) | set. + * MxL86111 has 2 register spaces (utp/fiber) and 3 modes (utp/fiber/auto). + * Each space has its MII_BMCR. + * + * returns 0 or negative errno code + */ +static int mxl86111_modify_bmcr_paged(struct phy_device *phydev, int page, + u16 mask, u16 set) +{ + int bmcr_timeout =3D BMCR_RESET_TIMEOUT; + int page_to_restore; + int ret =3D 0; + + page_to_restore =3D phy_select_page(phydev, page & MXL86111_EXT_SMI_SDS_P= HYSPACE_MASK); + if (page_to_restore < 0) + goto err_restore_page; + + ret =3D __phy_modify(phydev, MII_BMCR, mask, set); + if (ret < 0) + goto err_restore_page; + + /* In case of BMCR_RESET, check until reset bit is cleared */ + if (set =3D=3D BMCR_RESET) { + while (bmcr_timeout--) { + usleep_range(1000, 1050); + ret =3D __phy_read(phydev, MII_BMCR); + if (ret < 0) + goto err_restore_page; + + if (!(ret & BMCR_RESET)) + return phy_restore_page(phydev, page_to_restore, 0); + } + phydev_warn(phydev, "%s, BMCR reset not completed until timeout", __func= __); + ret =3D -EBUSY; + } + +err_restore_page: + return phy_restore_page(phydev, page_to_restore, ret); +} + +/** + * mxl86111_modify_bmcr - modify bits of a PHY's BMCR register + * @phydev: pointer to the phy_device + * @mask: bit mask of bits to clear + * @set: bit mask of bits to set + * + * NOTE: new register value =3D (old register value & ~mask) | set. + * MxL86111 has 2 register spaces (utp/fiber) and 3 mode (utp/fiber/poll) + * each space has its MII_BMCR. poll mode combines utp and fiber. + * + * returns 0 or negative errno code + */ +static int mxl86111_modify_bmcr(struct phy_device *phydev, u16 mask, u16 s= et) +{ + struct mxl86111_priv *priv =3D phydev->priv; + int ret =3D 0; + + if (priv->reg_page !=3D MXL86111_EXT_SMI_SDS_PHY_AUTO) { + ret =3D mxl86111_modify_bmcr_paged(phydev, priv->reg_page, mask, set); + if (ret < 0) + return ret; + } else { + ret =3D mxl86111_modify_bmcr_paged(phydev, ret, mask, set); + if (ret < 0) + return ret; + } + return 0; +} + +/** + * mxl86111_set_fiber_features() - setup fiber mode features. + * @phydev: pointer to the phy_device + * @dst: a pointer to store fiber mode features + */ +static void mxl86111_set_fiber_features(struct phy_device *phydev, + unsigned long *dst) +{ + linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT, dst); + linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, dst); + linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, dst); + linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, dst); +} + +/** + * mxlphy_utp_read_abilities - read PHY abilities from Clause 22 registers + * @phydev: pointer to the phy_device + * + * NOTE: Read the PHY abilities and set phydev->supported. + * The caller must have taken the MDIO bus lock. + * + * returns 0 or negative errno code + */ +static int mxlphy_utp_read_abilities(struct phy_device *phydev) +{ + struct mxl86111_priv *priv =3D phydev->priv; + int val, page; + + linkmode_set_bit_array(phy_basic_ports_array, + ARRAY_SIZE(phy_basic_ports_array), + phydev->supported); + + val =3D __phy_read(phydev, MII_BMSR); + if (val < 0) + return val; + + /* In case of dual mode media, we might not have access to the utp page. + * Therefore we use the default register setting of the device to + * 'extract' supported modes. + */ + page =3D mxl86111_read_page(phydev); + if (page < 0) + return page; + + if (priv->reg_page =3D=3D MXL86111_EXT_SMI_SDS_PHY_AUTO && + page =3D=3D MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE) + val =3D MII_BMSR_DEFAULT_VAL; + + phydev_info(phydev, "%s, MII_BMSR: 0x%04X\n", __func__, val); + + linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported, + val & BMSR_ANEGCAPABLE); + + linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->supported, + val & BMSR_10FULL); + linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->supported, + val & BMSR_10HALF); + linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported, + val & BMSR_100FULL); + linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported, + val & BMSR_100HALF); + + if (val & BMSR_ESTATEN) { + val =3D __phy_read(phydev, MII_ESTATUS); + if (val < 0) + return val; + + if (priv->reg_page =3D=3D MXL86111_EXT_SMI_SDS_PHY_AUTO && + page =3D=3D MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE) + val =3D MII_ESTATUS_DEFAULT_VAL; + + phydev_info(phydev, "%s, MII_ESTATUS: 0x%04X\n", __func__, val); + + linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + phydev->supported, val & ESTATUS_1000_TFULL); + linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + phydev->supported, val & ESTATUS_1000_THALF); + linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, + phydev->supported, val & ESTATUS_1000_XFULL); + } + + return 0; +} + +/** + * mxl86111_get_features_paged() - read supported link modes for a given = page + * @phydev: pointer to the phy_device + * @page: The reg page to operate + * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE) + * + * returns 0 or negative errno code + */ +static int mxl86111_get_features_paged(struct phy_device *phydev, int page) +{ + struct mxl86111_priv *priv =3D phydev->priv; + int page_to_restore; + int ret =3D 0; + + page &=3D MXL86111_EXT_SMI_SDS_PHYSPACE_MASK; + page_to_restore =3D phy_select_page(phydev, page); + if (page_to_restore < 0) + goto err_restore_page; + + if (page =3D=3D MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE && + priv->reg_page !=3D MXL86111_EXT_SMI_SDS_PHY_AUTO) { + linkmode_zero(phydev->supported); + mxl86111_set_fiber_features(phydev, phydev->supported); + } else { + ret =3D mxlphy_utp_read_abilities(phydev); + if (ret < 0) + goto err_restore_page; + } + +err_restore_page: + return phy_restore_page(phydev, page_to_restore, ret); +} + +/** + * mxl86111_get_features - select the reg space then call mxl86111_get_fea= tures_paged + * @phydev: pointer to the phy_device + * + * returns 0 or negative errno code + */ +static int mxl86111_get_features(struct phy_device *phydev) +{ + struct mxl86111_priv *priv =3D phydev->priv; + int ret; + + if (priv->reg_page !=3D MXL86111_EXT_SMI_SDS_PHY_AUTO) { + ret =3D mxl86111_get_features_paged(phydev, priv->reg_page); + } else { + /* read current page in Dual Media mode, + * since PHY HW is controlling the page select + */ + ret =3D mxl86111_read_page(phydev); + if (ret < 0) + return ret; + + ret =3D mxl86111_get_features_paged(phydev, ret); + if (ret < 0) + return ret; + + /* add fiber mode features to phydev->supported */ + mxl86111_set_fiber_features(phydev, phydev->supported); + } + return ret; +} + +/** + * mxl86110_probe() - read chip config then set suitable reg_page_mode + * @phydev: pointer to the phy_device + * + * returns 0 or negative errno code + */ +static int mxl86110_probe(struct phy_device *phydev) +{ + int ret; + + /* configure syncE / clk output */ + ret =3D mxl8611x_synce_clk_cfg(phydev); + if (ret < 0) + return ret; + + return 0; +} + +/** + * mxl86111_probe() - read chip config then set reg_page_mode, port and pa= ge + * @phydev: pointer to the phy_device + * + * returns 0 or negative errno code + */ +static int mxl86111_probe(struct phy_device *phydev) +{ + struct device *dev =3D &phydev->mdio.dev; + struct mxl86111_priv *priv; + int chip_config; + int ret; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + phydev->priv =3D priv; + + // TM: Debugging of pinstrap mode + ret =3D mxlphy_locked_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_R= EG, + MXL86111_EXT_CHIP_CFG_MODE_SEL_MASK, + MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_RGMII); + if (ret < 0) + return ret; + // TM: Debugging of pinstrap mode + ret =3D mxlphy_locked_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_R= EG, + MXL86111_EXT_CHIP_CFG_SW_RST_N_MODE, 0); + if (ret < 0) + return ret; + + chip_config =3D mxlphy_locked_read_extended_reg(phydev, MXL86111_EXT_CHIP= _CFG_REG); + if (chip_config < 0) + return chip_config; + + priv->strap_mode =3D chip_config & MXL86111_EXT_CHIP_CFG_MODE_SEL_MASK; + switch (priv->strap_mode) { + case MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_SGMII: + case MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_RGMII: + phydev->port =3D PORT_TP; + priv->reg_page =3D MXL86111_EXT_SMI_SDS_PHYUTP_SPACE; + priv->reg_page_mode =3D MXL86111_MODE_UTP; + break; + case MXL86111_EXT_CHIP_CFG_MODE_FIBER_TO_RGMII: + case MXL86111_EXT_CHIP_CFG_MODE_SGPHY_TO_RGMAC: + case MXL86111_EXT_CHIP_CFG_MODE_SGMAC_TO_RGPHY: + phydev->port =3D PORT_FIBRE; + priv->reg_page =3D MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE; + priv->reg_page_mode =3D MXL86111_MODE_FIBER; + break; + case MXL86111_EXT_CHIP_CFG_MODE_UTP_FIBER_TO_RGMII: + case MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_FIBER_AUTO: + case MXL86111_EXT_CHIP_CFG_MODE_UTP_TO_FIBER_FORCE: + phydev->port =3D PORT_NONE; + priv->reg_page =3D MXL86111_EXT_SMI_SDS_PHY_AUTO; + priv->reg_page_mode =3D MXL86111_MODE_AUTO; + break; + } + /* set default reg space when it is not Dual Media mode */ + if (priv->reg_page !=3D MXL86111_EXT_SMI_SDS_PHY_AUTO) { + ret =3D mxlphy_locked_write_extended_reg(phydev, + MXL86111_EXT_SMI_SDS_PHY_REG, + priv->reg_page); + if (ret < 0) + return ret; + } + + phydev_dbg(phydev, "%s, pinstrap mode: %d\n", __func__, priv->strap_mode); + + /* configure syncE / clk output - can be defined in custom config section= */ + ret =3D mxl8611x_synce_clk_cfg(phydev); + if (ret < 0) + return ret; + + return 0; +} + +/** + * mxlphy_check_and_restart_aneg - Enable and restart auto-negotiation + * @phydev: pointer to the phy_device + * @restart: bool if aneg restart is requested + * + * NOTE: The caller must have taken the MDIO bus lock. + * + * identical to genphy_check_and_restart_aneg, but phy_read without mdio l= ock + * + * returns 0 or negative errno code + */ +static int mxlphy_check_and_restart_aneg(struct phy_device *phydev, bool r= estart) +{ + int ret; + + if (!restart) { + ret =3D __phy_read(phydev, MII_BMCR); + if (ret < 0) + return ret; + + /* Advertisement did not change, but aneg maybe was never to begin with + * or phy was in isolation state + */ + if (!(ret & BMCR_ANENABLE) || (ret & BMCR_ISOLATE)) + restart =3D true; + } + /* Enable and restart Autonegotiation */ + if (restart) + return __phy_modify(phydev, MII_BMCR, BMCR_ISOLATE, + BMCR_ANENABLE | BMCR_ANRESTART); + + return 0; +} + +/** + * mxlphy_config_advert - sanitize and advertise auto-negotiation paramete= rs + * @phydev: pointer to the phy_device + * + * NOTE: Writes MII_ADVERTISE with the appropriate values, + * Returns < 0 on error, 0 if the PHY's advertisement hasn't changed, + * and > 0 if it has changed. + * + * identical to genphy_config_advert, but phy_read without mdio lock + * + * NOTE: The caller must have taken the MDIO bus lock. + * + * returns 0 or negative errno code + */ +static int mxlphy_config_advert(struct phy_device *phydev) +{ + int err, bmsr, changed =3D 0; + u32 adv; + + /* Only allow advertising what this PHY supports */ + linkmode_and(phydev->advertising, phydev->advertising, + phydev->supported); + + adv =3D linkmode_adv_to_mii_adv_t(phydev->advertising); + + /* Setup standard advertisement */ + err =3D __phy_modify_changed(phydev, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_100BASE4 | + ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM, + adv); + if (err < 0) + return err; + if (err > 0) + changed =3D 1; + + bmsr =3D __phy_read(phydev, MII_BMSR); + if (bmsr < 0) + return bmsr; + + /* Per 802.3-2008, Section 22.2.4.2.16 Extended status all + * 1000Mbits/sec capable PHYs shall have the BMSR_ESTATEN bit set to a + * logical 1. + */ + if (!(bmsr & BMSR_ESTATEN)) + return changed; + + adv =3D linkmode_adv_to_mii_ctrl1000_t(phydev->advertising); + + err =3D __phy_modify_changed(phydev, MII_CTRL1000, + ADVERTISE_1000FULL | ADVERTISE_1000HALF, + adv); + if (err < 0) + return err; + if (err > 0) + changed =3D 1; + + return changed; +} + +/** + * mxlphy_setup_master_slave - set master/slave capabilities + * @phydev: pointer to the phy_device + * + * NOTE: The caller must have taken the MDIO bus lock. + * + * identical to genphy_setup_master_slave, but phy_read without mdio lock + * + * returns 0 or negative errno code + */ +static int mxlphy_setup_master_slave(struct phy_device *phydev) +{ + u16 ctl =3D 0; + + if (!phydev->is_gigabit_capable) + + return 0; + + switch (phydev->master_slave_set) { + case MASTER_SLAVE_CFG_MASTER_PREFERRED: + ctl |=3D CTL1000_PREFER_MASTER; + break; + case MASTER_SLAVE_CFG_SLAVE_PREFERRED: + break; + case MASTER_SLAVE_CFG_MASTER_FORCE: + ctl |=3D CTL1000_AS_MASTER; + fallthrough; + case MASTER_SLAVE_CFG_SLAVE_FORCE: + ctl |=3D CTL1000_ENABLE_MASTER; + break; + case MASTER_SLAVE_CFG_UNKNOWN: + case MASTER_SLAVE_CFG_UNSUPPORTED: + return 0; + default: + phydev_warn(phydev, "Unsupported Master/Slave mode\n"); + return -EOPNOTSUPP; + } + + return __phy_modify_changed(phydev, MII_CTRL1000, + (CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER | + CTL1000_PREFER_MASTER), ctl); +} + +/** + * mxlphy_config_aneg_utp - restart auto-negotiation or write BMCR + * @phydev: pointer to the phy_device + * @changed: bool if autoneg is requested + * + * If auto-negotiation is enabled, advertising will be configure, + * and then auto-negotiation will be restarted. Otherwise write the BMCR. + * + * NOTE: The caller must have taken the MDIO bus lock. + * + * returns 0 or negative errno code + */ +static int mxlphy_config_aneg_utp(struct phy_device *phydev, bool changed) +{ + struct mxl86111_priv *priv =3D phydev->priv; + int err; + u16 ctl; + + /* identical to genphy_setup_master_slave, but phy_read without mdio lock= */ + err =3D mxlphy_setup_master_slave(phydev); + if (err < 0) + return err; + else if (err) + changed =3D true; + + if (phydev->autoneg !=3D AUTONEG_ENABLE) { + /* configures/forces speed/duplex, empty rate selection would result in = 10M */ + ctl =3D mii_bmcr_encode_fixed(phydev->speed, phydev->duplex); + /* In dual media mode prevent 1000BASE-T half duplex + * due to risk of permanent blocking + */ + if (priv->reg_page_mode =3D=3D MXL86111_MODE_AUTO) { + if (ctl & BMCR_SPEED1000) { + phydev_err(phydev, + "%s, Error: no supported fixed UTP rate configured\n", + __func__); + return -EINVAL; + } + } + + return __phy_modify(phydev, MII_BMCR, ~(BMCR_LOOPBACK | + BMCR_ISOLATE | BMCR_PDOWN), ctl); + } + + /* At least one valid rate must be advertised in Dual Media mode, since i= t could + * lead to permanent blocking of the UTP path otherwise. The PHY HW is co= ntrolling + * the page select autonomously in this mode. + * If mode switches to Fiber reg page when a link partner is connected, w= hile UTP + * link is down, it can never switch back to UTP if the link cannot be es= tablished + * anymore due to invalid configuration. A HW reset would be required to = re-enable + * UTP mode. Therefore prevent this situation and ignore invalid speed co= nfiguration. + * Returning a negative return code (e.g. EINVAL) results in stack trace = dumps from + * some PAL functions. + */ + if (priv->reg_page_mode =3D=3D MXL86111_MODE_AUTO) { + ctl =3D 0; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, phydev->advert= ising)) + ctl |=3D ADVERTISE_10HALF; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, phydev->advert= ising)) + ctl |=3D ADVERTISE_10FULL; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->adver= tising)) + ctl |=3D ADVERTISE_100HALF; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->adver= tising)) + ctl |=3D ADVERTISE_100FULL; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->adve= rtising)) + ctl |=3D ADVERTISE_1000FULL; + + if (ctl =3D=3D 0) { + phydev_err(phydev, + "%s, Error: no supported UTP rate configured - setting ignored\n", + __func__); + return -EINVAL; + } + } + + /* identical to genphy_config_advert, but phy_read without mdio lock */ + err =3D mxlphy_config_advert(phydev); + if (err < 0) + return err; + else if (err) + changed =3D true; + + return mxlphy_check_and_restart_aneg(phydev, changed); +} + +/** + * mxl86111_fiber_setup_forced - configures/forces speed for fiber mode + * @phydev: pointer to the phy_device + * + * NOTE: The caller must have taken the MDIO bus lock. + * + * returns 0 or negative errno code + */ +static int mxl86111_fiber_setup_forced(struct phy_device *phydev) +{ + u16 val; + int ret; + + if (phydev->speed =3D=3D SPEED_1000) + val =3D MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_1000BX; + else if (phydev->speed =3D=3D SPEED_100) + val =3D MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL_100BX; + else + return -EINVAL; + + ret =3D __phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0); + if (ret < 0) + return ret; + + ret =3D mxlphy_modify_extended_reg(phydev, MXL86111_EXT_MISC_CONFIG_REG, + MXL86111_EXT_MISC_CONFIG_FIB_SPEED_SEL, val); + if (ret < 0) + return ret; + + ret =3D mxlphy_modify_extended_reg(phydev, MXL86111_EXT_SDS_LINK_TIMER_C= FG2_REG, + MXL86111_EXT_SDS_LINK_TIMER_CFG2_EN_AUTOSEN, 0); + if (ret < 0) + return ret; + + return mxlphy_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_REG, + MXL86111_EXT_CHIP_CFG_SW_RST_N_MODE, 0); +} + +/** + * mxl86111_fiber_config_aneg - restart auto-negotiation or write + * MXL86111_EXT_MISC_CONFIG_REG. + * @phydev: pointer to the phy_device + * + * NOTE: The caller must have taken the MDIO bus lock. + * + * returns 0 or negative errno code + */ +static int mxl86111_fiber_config_aneg(struct phy_device *phydev) +{ + int ret, bmcr; + u16 adv; + bool changed =3D false; + + if (phydev->autoneg !=3D AUTONEG_ENABLE) + return mxl86111_fiber_setup_forced(phydev); + + /* enable fiber auto sensing */ + ret =3D mxlphy_modify_extended_reg(phydev, MXL86111_EXT_SDS_LINK_TIMER_C= FG2_REG, + 0, MXL86111_EXT_SDS_LINK_TIMER_CFG2_EN_AUTOSEN); + if (ret < 0) + return ret; + + ret =3D mxlphy_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_REG, + MXL86111_EXT_CHIP_CFG_SW_RST_N_MODE, 0); + if (ret < 0) + return ret; + + bmcr =3D __phy_read(phydev, MII_BMCR); + if (bmcr < 0) + return bmcr; + + /* For fiber forced mode, power down/up to re-aneg */ + if (!(bmcr & BMCR_ANENABLE)) { + __phy_modify(phydev, MII_BMCR, 0, BMCR_PDOWN); + usleep_range(1000, 1050); + __phy_modify(phydev, MII_BMCR, BMCR_PDOWN, 0); + } + + adv =3D linkmode_adv_to_mii_adv_x(phydev->advertising, + ETHTOOL_LINK_MODE_1000baseX_Full_BIT); + + /* Setup fiber advertisement */ + ret =3D __phy_modify_changed(phydev, MII_ADVERTISE, + ADVERTISE_1000XHALF | ADVERTISE_1000XFULL | + ADVERTISE_1000XPAUSE | + ADVERTISE_1000XPSE_ASYM, + adv); + if (ret < 0) + return ret; + + if (ret > 0) + changed =3D true; + + /* identical to genphy_check_and_restart_aneg, but phy_read without mdio = lock */ + return mxlphy_check_and_restart_aneg(phydev, changed); +} + +/** + * mxl86111_config_aneg_paged() - configure advertising for a givenpage + * @phydev: pointer to the phy_device + * @page: The reg page to operate + * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE) + * + * returns 0 or negative errno code + */ +static int mxl86111_config_aneg_paged(struct phy_device *phydev, int page) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(fiber_supported); + struct mxl86111_priv *priv =3D phydev->priv; + int page_to_restore; + int ret =3D 0; + + page &=3D MXL86111_EXT_SMI_SDS_PHYSPACE_MASK; + + page_to_restore =3D phy_select_page(phydev, page); + if (page_to_restore < 0) + goto err_restore_page; + + /* In case of Dual Media mode (MXL86111_EXT_SMI_SDS_PHY_AUTO), + * update phydev->advertising. + */ + if (priv->reg_page =3D=3D MXL86111_EXT_SMI_SDS_PHY_AUTO) { + /* prepare the general fiber supported modes */ + linkmode_zero(fiber_supported); + mxl86111_set_fiber_features(phydev, fiber_supported); + + /* configure fiber_supported, then prepare advertising */ + if (page =3D=3D MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE) { + linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, + fiber_supported); + linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, + fiber_supported); + linkmode_and(phydev->advertising, + priv->dual_media_advertising, fiber_supported); + } else { + /* ETHTOOL_LINK_MODE_Autoneg_BIT is also used in UTP mode */ + linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + fiber_supported); + linkmode_andnot(phydev->advertising, + priv->dual_media_advertising, + fiber_supported); + } + } + + if (priv->reg_page_mode =3D=3D MXL86111_MODE_AUTO) { + /* read current page in Dual Media mode as late as possible, + * since PHY HW is controlling the page select + */ + ret =3D mxl86111_read_page(phydev); + if (ret < 0) + return ret; + if (ret !=3D page) + phydev_warn(phydev, + "%s, Dual Media mode: page changed during configuration.\n", + __func__); + } + + if (page =3D=3D MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE) + ret =3D mxl86111_fiber_config_aneg(phydev); + else + ret =3D mxlphy_config_aneg_utp(phydev, false); + +err_restore_page: + return phy_restore_page(phydev, page_to_restore, ret); +} + +/** + * mxl86111_config_aneg() - set reg page and then call mxl86111_config_ane= g_paged + * @phydev: pointer to the phy_device + * + * returns 0 or negative errno code + */ +static int mxl86111_config_aneg(struct phy_device *phydev) +{ + struct mxl86111_priv *priv =3D phydev->priv; + int ret; + + if (priv->reg_page !=3D MXL86111_EXT_SMI_SDS_PHY_AUTO) { + ret =3D mxl86111_config_aneg_paged(phydev, priv->reg_page); + if (ret < 0) + return ret; + } else { + /* In Dual Media mode (MXL86111_EXT_SMI_SDS_PHY_AUTO) + * phydev->advertising need to be saved at first run, since it contains = the + * advertising which supported by both mac and mxl86111(utp and fiber). + */ + if (linkmode_empty(priv->dual_media_advertising)) { + linkmode_copy(priv->dual_media_advertising, + phydev->advertising); + } + /* read current page in Dual Media mode, + * since PHY HW is controlling the page select + */ + ret =3D mxl86111_read_page(phydev); + if (ret < 0) + return ret; + ret =3D mxl86111_config_aneg_paged(phydev, ret); + if (ret < 0) + return ret; + + /* we don't known which mode will link, so restore + * phydev->advertising as default value. + */ + linkmode_copy(phydev->advertising, priv->dual_media_advertising); + } + return 0; +} + +/** + * mxl86111_aneg_done_paged() - determines the auto negotiation result of = a given page. + * @phydev: pointer to the phy_device + * @page: The reg page to operate + * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE) + * + * returns 0 (no link) or 1 (fiber or utp link) or negative errno code + */ +static int mxl86111_aneg_done_paged(struct phy_device *phydev, int page) +{ + int page_to_restore; + int ret =3D 0; + int link; + + page_to_restore =3D phy_select_page(phydev, page & MXL86111_EXT_SMI_SDS_P= HYSPACE_MASK); + if (page_to_restore < 0) + goto err_restore_page; + + ret =3D __phy_read(phydev, MXL86111_PHY_STAT_REG); + if (ret < 0) + goto err_restore_page; + + link =3D !!(ret & MXL86111_PHY_STAT_LSRT); + ret =3D link; + +err_restore_page: + return phy_restore_page(phydev, page_to_restore, ret); +} + +/** + * mxl86111_aneg_done() - determines the auto negotiation result + * @phydev: pointer to the phy_device + * + * returns 0 (no link) or 1 (fiber or utp link) or negative errno code + */ +static int mxl86111_aneg_done(struct phy_device *phydev) +{ + struct mxl86111_priv *priv =3D phydev->priv; + + int link; + int ret; + + if (priv->reg_page !=3D MXL86111_EXT_SMI_SDS_PHY_AUTO) { + link =3D mxl86111_aneg_done_paged(phydev, priv->reg_page); + } else { + /* read current page in Dual Media mode, + * since PHY HW is controlling the page select + */ + ret =3D mxl86111_read_page(phydev); + if (ret < 0) + return ret; + link =3D mxl86111_aneg_done_paged(phydev, ret); + } + + return link; +} + +/** + * mxl86111_config_init() - initialize the PHY + * @phydev: pointer to the phy_device + * + * returns 0 or negative errno code + */ +static int mxl86111_config_init(struct phy_device *phydev) +{ + int page_to_restore, ret =3D 0; + unsigned int val =3D 0; + + page_to_restore =3D phy_select_page(phydev, MXL86111_EXT_SMI_SDS_PHYUTP_S= PACE); + if (page_to_restore < 0) + goto err_restore_page; + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_RXID: + val =3D MXL8611X_EXT_RGMII_CFG1_RX_DELAY_DEFAULT; + break; + case PHY_INTERFACE_MODE_RGMII_TXID: + case PHY_INTERFACE_MODE_RGMII_ID: + val =3D MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_DEFAULT | + MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DEFAULT; + val |=3D MXL8611X_EXT_RGMII_CFG1_RX_DELAY_DEFAULT; + break; + case PHY_INTERFACE_MODE_SGMII: + break; + default: + ret =3D -EOPNOTSUPP; + goto err_restore_page; + } + + /* configure rgmii delay mode */ + if (phydev->interface !=3D PHY_INTERFACE_MODE_SGMII) { + ret =3D mxlphy_modify_extended_reg(phydev, MXL8611X_EXT_RGMII_CFG1_REG, + MXL8611X_EXT_RGMII_CFG1_FULL_MASK, val); + if (ret < 0) + goto err_restore_page; + + /* Configure RXDLY (RGMII Rx Clock Delay) to keep the default + * delay value on RX_CLK (2 ns for 125 MHz, 8 ns for 25 MHz/2.5 MHz) + */ + ret =3D mxlphy_modify_extended_reg(phydev, MXL86111_EXT_CHIP_CFG_REG, + MXL86111_EXT_CHIP_CFG_RXDLY_ENABLE, 1); + + if (ret < 0) + goto err_restore_page; + } + + ret =3D mxl8611x_enable_led_activity_blink(phydev); + if (ret < 0) + goto err_restore_page; + + ret =3D mxl8611x_broadcast_cfg(phydev); + if (ret < 0) + goto err_restore_page; + +err_restore_page: + return phy_restore_page(phydev, page_to_restore, ret); +} + +/** + * mxlphy_read_lpa() - read LPA and setup lp_advertising for UTP mode + * @phydev: pointer to the phy_device + * + * NOTE: The caller must have taken the MDIO bus lock. + * + * identical to genphy_check_read_lpa, but phy_read without mdio lock + * + * returns 0 or negative errno code + */ +static int mxlphy_read_lpa(struct phy_device *phydev) +{ + int lpa, lpagb; + + if (phydev->autoneg =3D=3D AUTONEG_ENABLE) { + if (!phydev->autoneg_complete) { + mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, + 0); + mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 0); + return 0; + } + + if (phydev->is_gigabit_capable) { + lpagb =3D __phy_read(phydev, MII_STAT1000); + if (lpagb < 0) + return lpagb; + + if (lpagb & LPA_1000MSFAIL) { + int adv =3D __phy_read(phydev, MII_CTRL1000); + + if (adv < 0) + return adv; + + if (adv & CTL1000_ENABLE_MASTER) + phydev_err(phydev, "Master/Slave resolution failed, maybe conflicting= manual settings?\n"); + else + phydev_err(phydev, "Master/Slave resolution failed\n"); + return -ENOLINK; + } + + mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, + lpagb); + } + + lpa =3D __phy_read(phydev, MII_LPA); + if (lpa < 0) + return lpa; + + mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, lpa); + } else { + linkmode_zero(phydev->lp_advertising); + } + + return 0; +} + +/** + * mxl86111_adjust_status() - update speed and duplex to phydev. when in f= iber + * mode, adjust speed and duplex. + * @phydev: pointer to the phy_device + * @status: mxl86111 status read from MXL86111_PHY_STAT_REG + * @is_utp: false(mxl86111 work in fiber mode) or true(mxl86111 work in ut= p mode) + * + * NOTE: The caller must have taken the MDIO bus lock. + * + * returns 0 + */ +static int mxl86111_adjust_status(struct phy_device *phydev, int status, b= ool is_utp) +{ + int speed_mode, duplex; + int speed; + int err; + int lpa; + + if (is_utp) + duplex =3D (status & MXL86111_PHY_STAT_DPX) >> MXL86111_PHY_STAT_DPX_OFF= SET; + else + duplex =3D DUPLEX_FULL; /* FIBER is always DUPLEX_FULL */ + + speed_mode =3D (status & MXL86111_PHY_STAT_SPEED_MASK) >> + MXL86111_PHY_STAT_SPEED_OFFSET; + + switch (speed_mode) { + case MXL86111_PHY_STAT_SPEED_10M: + if (is_utp) + speed =3D SPEED_10; + else + /* not supported for FIBER mode */ + speed =3D SPEED_UNKNOWN; + break; + case MXL86111_PHY_STAT_SPEED_100M: + speed =3D SPEED_100; + break; + case MXL86111_PHY_STAT_SPEED_1000M: + speed =3D SPEED_1000; + break; + default: + speed =3D SPEED_UNKNOWN; + break; + } + + phydev->speed =3D speed; + phydev->duplex =3D duplex; + + if (is_utp) { + err =3D mxlphy_read_lpa(phydev); + if (err < 0) + return err; + + phy_resolve_aneg_pause(phydev); + } else { + lpa =3D __phy_read(phydev, MII_LPA); + if (lpa < 0) + return lpa; + /* only support 1000baseX Full Duplex in Fiber mode */ + linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, + phydev->lp_advertising, lpa & LPA_1000XFULL); + if (!(lpa & MXL86111_SDS_AN_LPA_PAUSE)) { + /* disable pause */ + phydev->pause =3D 0; + phydev->asym_pause =3D 0; + } else if ((lpa & MXL86111_SDS_AN_LPA_ASYM_PAUSE)) { + /* asymmetric pause */ + phydev->pause =3D 1; + phydev->asym_pause =3D 1; + } else { + /* symmetric pause */ + phydev->pause =3D 1; + phydev->asym_pause =3D 0; + } + } + + return 0; +} + +/** + * mxl86111_read_status_paged() - determines the speed and duplex of one = page + * @phydev: pointer to the phy_device + * @page: The reg page to operate + * (MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE/MXL86111_EXT_SMI_SDS_PHYUTP_SPACE) + * + * returns 1 (utp or fiber link), 0 (no link) or negative errno code + */ +static int mxl86111_read_status_paged(struct phy_device *phydev, int page) +{ + int sds_stat_stored, sds_stat_curr; + int page_to_restore; + int ret =3D 0; + int status; + int link; + + linkmode_zero(phydev->lp_advertising); + phydev->duplex =3D DUPLEX_UNKNOWN; + phydev->speed =3D SPEED_UNKNOWN; + phydev->asym_pause =3D 0; + phydev->pause =3D 0; + + /* MxL86111 uses two reg spaces (utp/fiber). In Dual Media mode, + * the PHY HW will do the switching autonomously upon link status. + * By default, utp has priority. In this mode the HW controlled + * reg page selection bit is read-only and can only be read. + * The reg space should be properly set before reading + * MXL86111_PHY_STAT_REG. + */ + + page &=3D MXL86111_EXT_SMI_SDS_PHYSPACE_MASK; + page_to_restore =3D phy_select_page(phydev, page); + if (page_to_restore < 0) + goto err_restore_page; + + /* Read MXL86111_PHY_STAT_REG, which reports the actual speed + * and duplex. + */ + ret =3D __phy_read(phydev, MXL86111_PHY_STAT_REG); + if (ret < 0) + goto err_restore_page; + + status =3D ret; + link =3D !!(status & MXL86111_PHY_STAT_LSRT); + + /* In fiber mode there is no link down from MXL86111_PHY_STAT_REG + * when speed switches from 1000 Mbps to 100Mbps. + * It is required to check MII_BMSR to identify such a situation. + */ + if (page =3D=3D MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE) { + ret =3D __phy_read(phydev, MII_BMSR); + if (ret < 0) + goto err_restore_page; + sds_stat_stored =3D ret; + + ret =3D __phy_read(phydev, MII_BMSR); + if (ret < 0) + goto err_restore_page; + sds_stat_curr =3D ret; + + if (link && sds_stat_stored !=3D sds_stat_curr) { + link =3D 0; + phydev_info(phydev, + "%s, fiber link down detected by SDS_STAT change from %04x to %04x= \n", + __func__, sds_stat_stored, sds_stat_curr); + } + } else { + /* Read autonegotiation status */ + ret =3D __phy_read(phydev, MII_BMSR); + if (ret < 0) + goto err_restore_page; + + phydev->autoneg_complete =3D ret & BMSR_ANEGCOMPLETE ? 1 : 0; + } + + if (link) { + if (page =3D=3D MXL86111_EXT_SMI_SDS_PHYUTP_SPACE) + mxl86111_adjust_status(phydev, status, true); + else + mxl86111_adjust_status(phydev, status, false); + } + return phy_restore_page(phydev, page_to_restore, link); + +err_restore_page: + return phy_restore_page(phydev, page_to_restore, ret); +} + +/** + * mxl86111_read_status() - determines the negotiated speed and duplex + * @phydev: pointer to the phy_device + * + * returns 0 or negative errno code + */ +static int mxl86111_read_status(struct phy_device *phydev) +{ + struct mxl86111_priv *priv =3D phydev->priv; + int link; + int page; + + page =3D mxl86111_read_page(phydev); + if (page < 0) + return page; + + if (priv->reg_page !=3D MXL86111_EXT_SMI_SDS_PHY_AUTO) { + link =3D mxl86111_read_status_paged(phydev, priv->reg_page); + if (link < 0) + return link; + } else { + link =3D mxl86111_read_status_paged(phydev, page); + } + + if (link) { + if (phydev->link =3D=3D 0) { + /* set port type in Dual Media mode based on + * active page select (controlled by PHY HW). + */ + if (priv->reg_page_mode =3D=3D MXL86111_MODE_AUTO && + priv->reg_page =3D=3D MXL86111_EXT_SMI_SDS_PHY_AUTO) { + priv->reg_page =3D page; + + phydev->port =3D (page =3D=3D MXL86111_EXT_SMI_SDS_PHYFIBER_SPACE) ? + PORT_FIBRE : PORT_TP; + + phydev_info(phydev, "%s, link up, media: %s\n", __func__, + (phydev->port =3D=3D PORT_TP) ? "UTP" : "Fiber"); + } + phydev->link =3D 1; + } else { + if (priv->reg_page !=3D page) { + /* PHY HW detected UTP port active in parallel to FIBER port. + * Since UTP has priority, the page will be hard switched from FIBER + * to UTP. This needs to be detected and FIBER link reported as down + */ + phydev_info(phydev, "%s, link down, media: %s\n", + __func__, (phydev->port =3D=3D PORT_TP) ? "UTP" : "Fiber"); + + /* When in MXL86111_MODE_AUTO mode, + * prepare to detect the next activation mode + * to support Dual Media mode + */ + if (priv->reg_page_mode =3D=3D MXL86111_MODE_AUTO) { + priv->reg_page =3D MXL86111_EXT_SMI_SDS_PHY_AUTO; + phydev->port =3D PORT_NONE; + } + phydev->link =3D 0; + } + } + } else { + if (phydev->link =3D=3D 1) { + phydev_info(phydev, "%s, link down, media: %s\n", + __func__, (phydev->port =3D=3D PORT_TP) ? "UTP" : "Fiber"); + + /* When in MXL86111_MODE_AUTO mode, arbitration will be prepare + * to support Dual Media mode + */ + if (priv->reg_page_mode =3D=3D MXL86111_MODE_AUTO) { + priv->reg_page =3D MXL86111_EXT_SMI_SDS_PHY_AUTO; + phydev->port =3D PORT_NONE; + } + } + + phydev->link =3D 0; + } + + return 0; +} + +/** + * mxl86111_soft_reset() - issue a PHY software reset + * @phydev: pointer to the phy_device + * + * returns 0 or negative errno code + */ +static int mxl86111_soft_reset(struct phy_device *phydev) +{ + return mxl86111_modify_bmcr(phydev, 0, BMCR_RESET); +} + +/** + * mxl86111_suspend() - suspend the PHY hardware + * @phydev: pointer to the phy_device + * + * returns 0 or negative errno code + */ +static int mxl86111_suspend(struct phy_device *phydev) +{ + struct mxl86111_priv *priv =3D phydev->priv; + int wol_config; + + wol_config =3D mxlphy_locked_read_extended_reg(phydev, MXL8611X_EXT_WOL_C= FG_REG); + if (wol_config < 0) + return wol_config; + + /* if wake-on-lan is enabled, do nothing */ + if (wol_config & MXL8611X_EXT_WOL_CFG_WOLE_MASK) + return 0; + + /* do not power down in Dual Media mode, since page is controlled by HW + * and the mode might not 'wake up' anymore later on when the other mode = is active + */ + if (priv->reg_page_mode =3D=3D MXL86111_MODE_AUTO) + return 0; + + return mxl86111_modify_bmcr(phydev, 0, BMCR_PDOWN); +} + +/** + * mxl86111_resume() - resume the PHY hardware + * @phydev: pointer to the phy_device + * + * returns 0 or negative errno code + */ +static int mxl86111_resume(struct phy_device *phydev) +{ + int wol_config; + + wol_config =3D mxlphy_locked_read_extended_reg(phydev, MXL8611X_EXT_WOL_C= FG_REG); + if (wol_config < 0) + return wol_config; + + /* if wake-on-lan is enabled, do nothing */ + if (wol_config & MXL8611X_EXT_WOL_CFG_WOLE_MASK) + return 0; + + return mxl86111_modify_bmcr(phydev, BMCR_PDOWN, 0); +} + +static struct phy_driver mxl_phy_drvs[] =3D { + { + PHY_ID_MATCH_EXACT(PHY_ID_MXL86110), + .name =3D "MXL86110 Gigabit Ethernet", + .probe =3D mxl86110_probe, + .config_init =3D mxl86110_config_init, + .config_aneg =3D genphy_config_aneg, + .read_page =3D mxl86110_read_page, + .write_page =3D mxl86110_write_page, + .read_status =3D genphy_read_status, + .get_wol =3D mxlphy_get_wol, + .set_wol =3D mxlphy_set_wol, + .suspend =3D genphy_suspend, + .resume =3D genphy_resume, + .led_hw_is_supported =3D mxl8611x_led_hw_is_supported, + .led_hw_control_get =3D mxl8611x_led_hw_control_get, + .led_hw_control_set =3D mxl8611x_led_hw_control_set, + }, + { + PHY_ID_MATCH_EXACT(PHY_ID_MXL86111), + .name =3D "MXL86111 Gigabit Ethernet", + .get_features =3D mxl86111_get_features, + .probe =3D mxl86111_probe, + .config_init =3D mxl86111_config_init, + .config_aneg =3D mxl86111_config_aneg, + .aneg_done =3D mxl86111_aneg_done, + .read_status =3D mxl86111_read_status, + .read_page =3D mxl86111_read_page, + .write_page =3D mxl86111_write_page, + .get_wol =3D mxlphy_get_wol, + .set_wol =3D mxlphy_set_wol, + .suspend =3D mxl86111_suspend, + .resume =3D mxl86111_resume, + .soft_reset =3D mxl86111_soft_reset, + .led_hw_is_supported =3D mxl8611x_led_hw_is_supported, + .led_hw_control_get =3D mxl8611x_led_hw_control_get, + .led_hw_control_set =3D mxl8611x_led_hw_control_set, + }, +}; + +module_phy_driver(mxl_phy_drvs); + +MODULE_DESCRIPTION(MXL8611x_DRIVER_DESC); +MODULE_VERSION(MXL8611x_DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static const struct mdio_device_id __maybe_unused mxl_tbl[] =3D { + { PHY_ID_MATCH_EXACT(PHY_ID_MXL86110) }, + { PHY_ID_MATCH_EXACT(PHY_ID_MXL86111) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, mxl_tbl); --=20 2.43.0