mediatek: add driver for built-in 2.5G Ethernet PHY
Add driver for the built-in 2.5G Ethernet PHY found in the MT7988 SoC. To function the PHY also needs firmware files which have not yet been published via linux-firmware. Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
		| @@ -0,0 +1,262 @@ | |||||||
|  | // SPDX-License-Identifier: GPL-2.0+ | ||||||
|  | #include <linux/bitfield.h> | ||||||
|  | #include <linux/firmware.h> | ||||||
|  | #include <linux/module.h> | ||||||
|  | #include <linux/nvmem-consumer.h> | ||||||
|  | #include <linux/of_address.h> | ||||||
|  | #include <linux/of_platform.h> | ||||||
|  | #include <linux/pinctrl/consumer.h> | ||||||
|  | #include <linux/phy.h> | ||||||
|  |  | ||||||
|  | #define MEDAITEK_2P5GE_PHY_DMB_FW "mediatek/mediatek-2p5ge-phy-dmb.bin" | ||||||
|  | #define MEDIATEK_2P5GE_PHY_PMB_FW "mediatek/mediatek-2p5ge-phy-pmb.bin" | ||||||
|  |  | ||||||
|  | #define MD32_EN_CFG			0x18 | ||||||
|  | #define   MD32_EN			BIT(0) | ||||||
|  |  | ||||||
|  | #define BASE100T_STATUS_EXTEND		0x10 | ||||||
|  | #define BASE1000T_STATUS_EXTEND		0x11 | ||||||
|  | #define EXTEND_CTRL_AND_STATUS		0x16 | ||||||
|  |  | ||||||
|  | #define PHY_AUX_CTRL_STATUS		0x1d | ||||||
|  | #define   PHY_AUX_DPX_MASK		GENMASK(5, 5) | ||||||
|  | #define   PHY_AUX_SPEED_MASK		GENMASK(4, 2) | ||||||
|  |  | ||||||
|  | /* Registers on MDIO_MMD_VEND1 */ | ||||||
|  | #define MTK_PHY_LINK_STATUS_MISC	0xa2 | ||||||
|  | #define   MTK_PHY_FDX_ENABLE		BIT(5) | ||||||
|  |  | ||||||
|  | /* Registers on MDIO_MMD_VEND2 */ | ||||||
|  | #define MTK_PHY_LED0_ON_CTRL		0x24 | ||||||
|  | #define   MTK_PHY_LED0_ON_LINK1000	BIT(0) | ||||||
|  | #define   MTK_PHY_LED0_ON_LINK100	BIT(1) | ||||||
|  | #define   MTK_PHY_LED0_ON_LINK10	BIT(2) | ||||||
|  | #define   MTK_PHY_LED0_ON_LINK2500	BIT(7) | ||||||
|  | #define   MTK_PHY_LED0_POLARITY		BIT(14) | ||||||
|  |  | ||||||
|  | #define MTK_PHY_LED1_ON_CTRL		0x26 | ||||||
|  | #define   MTK_PHY_LED1_ON_FDX		BIT(4) | ||||||
|  | #define   MTK_PHY_LED1_ON_HDX		BIT(5) | ||||||
|  | #define   MTK_PHY_LED1_POLARITY		BIT(14) | ||||||
|  |  | ||||||
|  | enum { | ||||||
|  | 	PHY_AUX_SPD_10 = 0, | ||||||
|  | 	PHY_AUX_SPD_100, | ||||||
|  | 	PHY_AUX_SPD_1000, | ||||||
|  | 	PHY_AUX_SPD_2500, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static int mt798x_2p5ge_phy_config_init(struct phy_device *phydev) | ||||||
|  | { | ||||||
|  | 	int ret; | ||||||
|  | 	int i; | ||||||
|  | 	const struct firmware *fw; | ||||||
|  | 	struct device *dev = &phydev->mdio.dev; | ||||||
|  | 	struct device_node *np; | ||||||
|  | 	void __iomem *dmb_addr; | ||||||
|  | 	void __iomem *pmb_addr; | ||||||
|  | 	void __iomem *mcucsr_base; | ||||||
|  | 	u16 reg; | ||||||
|  | 	struct pinctrl *pinctrl; | ||||||
|  |  | ||||||
|  | 	np = of_find_compatible_node(NULL, NULL, "mediatek,2p5gphy-fw"); | ||||||
|  | 	if (!np) | ||||||
|  | 		return -ENOENT; | ||||||
|  |  | ||||||
|  | 	dmb_addr = of_iomap(np, 0); | ||||||
|  | 	if (!dmb_addr) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 	pmb_addr = of_iomap(np, 1); | ||||||
|  | 	if (!pmb_addr) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 	mcucsr_base = of_iomap(np, 2); | ||||||
|  | 	if (!mcucsr_base) | ||||||
|  | 		return -ENOMEM; | ||||||
|  |  | ||||||
|  | 	ret = request_firmware(&fw, MEDAITEK_2P5GE_PHY_DMB_FW, dev); | ||||||
|  | 	if (ret) { | ||||||
|  | 		dev_err(dev, "failed to load firmware: %s, ret: %d\n", | ||||||
|  | 			MEDAITEK_2P5GE_PHY_DMB_FW, ret); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  | 	for (i = 0; i < fw->size - 1; i += 4) | ||||||
|  | 		writel(*((uint32_t *)(fw->data + i)), dmb_addr + i); | ||||||
|  | 	release_firmware(fw); | ||||||
|  |  | ||||||
|  | 	ret = request_firmware(&fw, MEDIATEK_2P5GE_PHY_PMB_FW, dev); | ||||||
|  | 	if (ret) { | ||||||
|  | 		dev_err(dev, "failed to load firmware: %s, ret: %d\n", | ||||||
|  | 			MEDIATEK_2P5GE_PHY_PMB_FW, ret); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  | 	for (i = 0; i < fw->size - 1; i += 4) | ||||||
|  | 		writel(*((uint32_t *)(fw->data + i)), pmb_addr + i); | ||||||
|  | 	release_firmware(fw); | ||||||
|  |  | ||||||
|  | 	reg = readw(mcucsr_base + MD32_EN_CFG); | ||||||
|  | 	writew(reg | MD32_EN, mcucsr_base + MD32_EN_CFG); | ||||||
|  | 	dev_dbg(dev, "Firmware loading/trigger ok.\n"); | ||||||
|  |  | ||||||
|  | 	/* Setup LED */ | ||||||
|  | 	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED0_ON_CTRL, | ||||||
|  | 			   MTK_PHY_LED0_POLARITY); | ||||||
|  |  | ||||||
|  | 	phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED0_ON_CTRL, | ||||||
|  | 			 MTK_PHY_LED0_ON_LINK10 | | ||||||
|  | 			 MTK_PHY_LED0_ON_LINK100 | | ||||||
|  | 			 MTK_PHY_LED0_ON_LINK1000 | | ||||||
|  | 			 MTK_PHY_LED0_ON_LINK2500); | ||||||
|  |  | ||||||
|  | 	phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED1_ON_CTRL, | ||||||
|  | 			 MTK_PHY_LED1_ON_FDX | MTK_PHY_LED1_ON_HDX); | ||||||
|  |  | ||||||
|  | 	pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "i2p5gbe-led"); | ||||||
|  | 	if (IS_ERR(pinctrl)) { | ||||||
|  | 		dev_err(&phydev->mdio.dev, "Fail to set LED pins!\n"); | ||||||
|  | 		return PTR_ERR(pinctrl); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int mt798x_2p5ge_phy_config_aneg(struct phy_device *phydev) | ||||||
|  | { | ||||||
|  | 	bool changed = false; | ||||||
|  | 	u32 adv; | ||||||
|  | 	int ret; | ||||||
|  |  | ||||||
|  | 	if (phydev->autoneg == AUTONEG_DISABLE) { | ||||||
|  | 		/* Configure half duplex with genphy_setup_forced, | ||||||
|  | 		 * because genphy_c45_pma_setup_forced does not support. | ||||||
|  | 		 */ | ||||||
|  | 		return phydev->duplex != DUPLEX_FULL | ||||||
|  | 			? genphy_setup_forced(phydev) | ||||||
|  | 			: genphy_c45_pma_setup_forced(phydev); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = genphy_c45_an_config_aneg(phydev); | ||||||
|  | 	if (ret < 0) | ||||||
|  | 		return ret; | ||||||
|  | 	if (ret > 0) | ||||||
|  | 		changed = true; | ||||||
|  |  | ||||||
|  | 	adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising); | ||||||
|  | 	ret = phy_modify_changed(phydev, MII_CTRL1000, | ||||||
|  | 				 ADVERTISE_1000FULL | ADVERTISE_1000HALF, | ||||||
|  | 				 adv); | ||||||
|  | 	if (ret < 0) | ||||||
|  | 		return ret; | ||||||
|  | 	if (ret > 0) | ||||||
|  | 		changed = true; | ||||||
|  |  | ||||||
|  | 	return genphy_c45_check_and_restart_aneg(phydev, changed); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int mt798x_2p5ge_phy_get_features(struct phy_device *phydev) | ||||||
|  | { | ||||||
|  | 	int ret; | ||||||
|  |  | ||||||
|  | 	ret = genphy_read_abilities(phydev); | ||||||
|  | 	if (ret) | ||||||
|  | 		return ret; | ||||||
|  |  | ||||||
|  | 	/* We don't support HDX at MAC layer on mt798x. | ||||||
|  | 	 * So mask phy's HDX capabilities, too. | ||||||
|  | 	 */ | ||||||
|  | 	linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, | ||||||
|  | 			 phydev->supported); | ||||||
|  | 	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, | ||||||
|  | 			 phydev->supported); | ||||||
|  | 	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, | ||||||
|  | 			 phydev->supported); | ||||||
|  | 	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, | ||||||
|  | 			 phydev->supported); | ||||||
|  | 	linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported); | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int mt798x_2p5ge_phy_read_status(struct phy_device *phydev) | ||||||
|  | { | ||||||
|  | 	int ret; | ||||||
|  |  | ||||||
|  | 	ret = genphy_update_link(phydev); | ||||||
|  | 	if (ret) | ||||||
|  | 		return ret; | ||||||
|  |  | ||||||
|  | 	phydev->speed = SPEED_UNKNOWN; | ||||||
|  | 	phydev->duplex = DUPLEX_UNKNOWN; | ||||||
|  | 	phydev->pause = 0; | ||||||
|  | 	phydev->asym_pause = 0; | ||||||
|  |  | ||||||
|  | 	if (!phydev->link) | ||||||
|  | 		return 0; | ||||||
|  |  | ||||||
|  | 	if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) { | ||||||
|  | 		ret = genphy_c45_read_lpa(phydev); | ||||||
|  | 		if (ret < 0) | ||||||
|  | 			return ret; | ||||||
|  |  | ||||||
|  | 		/* Read the link partner's 1G advertisement */ | ||||||
|  | 		ret = phy_read(phydev, MII_STAT1000); | ||||||
|  | 		if (ret < 0) | ||||||
|  | 			return ret; | ||||||
|  | 		mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, ret); | ||||||
|  | 	} else if (phydev->autoneg == AUTONEG_DISABLE) { | ||||||
|  | 		linkmode_zero(phydev->lp_advertising); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = phy_read(phydev, PHY_AUX_CTRL_STATUS); | ||||||
|  | 	if (ret < 0) | ||||||
|  | 		return ret; | ||||||
|  |  | ||||||
|  | 	switch (FIELD_GET(PHY_AUX_SPEED_MASK, ret)) { | ||||||
|  | 	case PHY_AUX_SPD_10: | ||||||
|  | 		phydev->speed = SPEED_10; | ||||||
|  | 		break; | ||||||
|  | 	case PHY_AUX_SPD_100: | ||||||
|  | 		phydev->speed = SPEED_100; | ||||||
|  | 		break; | ||||||
|  | 	case PHY_AUX_SPD_1000: | ||||||
|  | 		phydev->speed = SPEED_1000; | ||||||
|  | 		break; | ||||||
|  | 	case PHY_AUX_SPD_2500: | ||||||
|  | 		phydev->speed = SPEED_2500; | ||||||
|  | 		phydev->duplex = DUPLEX_FULL; /* 2.5G must be FDX */ | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LINK_STATUS_MISC); | ||||||
|  | 	if (ret < 0) | ||||||
|  | 		return ret; | ||||||
|  |  | ||||||
|  | 	phydev->duplex = (ret & MTK_PHY_FDX_ENABLE) ? DUPLEX_FULL : DUPLEX_HALF; | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static struct phy_driver mtk_gephy_driver[] = { | ||||||
|  | 	{ | ||||||
|  | 		PHY_ID_MATCH_EXACT(0x00339c11), | ||||||
|  | 		.name		= "MediaTek MT798x 2.5GbE PHY", | ||||||
|  | 		.config_init	= mt798x_2p5ge_phy_config_init, | ||||||
|  | 		.config_aneg    = mt798x_2p5ge_phy_config_aneg, | ||||||
|  | 		.get_features	= mt798x_2p5ge_phy_get_features, | ||||||
|  | 		.read_status	= mt798x_2p5ge_phy_read_status, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module_phy_driver(mtk_gephy_driver); | ||||||
|  |  | ||||||
|  | static struct mdio_device_id __maybe_unused mtk_2p5ge_phy_tbl[] = { | ||||||
|  | 	{ PHY_ID_MATCH_VENDOR(0x00339c00) }, | ||||||
|  | 	{ } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | MODULE_DESCRIPTION("MediaTek 2.5Gb Ethernet PHY driver"); | ||||||
|  | MODULE_AUTHOR("SkyLake Huang <SkyLake.Huang@mediatek.com>"); | ||||||
|  | MODULE_LICENSE("GPL"); | ||||||
|  |  | ||||||
|  | MODULE_DEVICE_TABLE(mdio, mtk_2p5ge_phy_tbl); | ||||||
|  | MODULE_FIRMWARE(MEDAITEK_2P5GE_PHY_DMB_FW); | ||||||
|  | MODULE_FIRMWARE(MEDIATEK_2P5GE_PHY_PMB_FW); | ||||||
| @@ -204,6 +204,7 @@ CONFIG_MAXLINEAR_GPHY=y | |||||||
| CONFIG_MDIO_BUS=y | CONFIG_MDIO_BUS=y | ||||||
| CONFIG_MDIO_DEVICE=y | CONFIG_MDIO_DEVICE=y | ||||||
| CONFIG_MDIO_DEVRES=y | CONFIG_MDIO_DEVRES=y | ||||||
|  | CONFIG_MEDIATEK_2P5G_PHY=y | ||||||
| CONFIG_MEDIATEK_GE_PHY=y | CONFIG_MEDIATEK_GE_PHY=y | ||||||
| CONFIG_MEDIATEK_GE_SOC_PHY=y | CONFIG_MEDIATEK_GE_SOC_PHY=y | ||||||
| CONFIG_MEDIATEK_WATCHDOG=y | CONFIG_MEDIATEK_WATCHDOG=y | ||||||
|   | |||||||
| @@ -225,6 +225,7 @@ CONFIG_MAGIC_SYSRQ=y | |||||||
| CONFIG_MDIO_BUS=y | CONFIG_MDIO_BUS=y | ||||||
| CONFIG_MDIO_DEVICE=y | CONFIG_MDIO_DEVICE=y | ||||||
| CONFIG_MDIO_DEVRES=y | CONFIG_MDIO_DEVRES=y | ||||||
|  | # CONFIG_MEDIATEK_2P5G_PHY is not set | ||||||
| CONFIG_MEDIATEK_GE_PHY=y | CONFIG_MEDIATEK_GE_PHY=y | ||||||
| # CONFIG_MEDIATEK_GE_SOC_PHY is not set | # CONFIG_MEDIATEK_GE_SOC_PHY is not set | ||||||
| CONFIG_MEDIATEK_WATCHDOG=y | CONFIG_MEDIATEK_WATCHDOG=y | ||||||
|   | |||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | From 128dc09b0af36772062142ce9e85b19c84ac789a Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Daniel Golle <daniel@makrotopia.org> | ||||||
|  | Date: Tue, 28 Feb 2023 17:53:37 +0000 | ||||||
|  | Subject: [PATCH] net: phy: add driver for MediaTek 2.5G PHY | ||||||
|  |  | ||||||
|  | Signed-off-by: Daniel Golle <daniel@makrotopia.org> | ||||||
|  | --- | ||||||
|  |  drivers/net/phy/Kconfig          |   7 ++ | ||||||
|  |  drivers/net/phy/Makefile         |   1 + | ||||||
|  |  drivers/net/phy/mediatek-2p5ge.c | 220 +++++++++++++++++++++++++++++++ | ||||||
|  |  3 files changed, 226 insertions(+) | ||||||
|  |  create mode 100644 drivers/net/phy/mediatek-2p5ge.c | ||||||
|  |  | ||||||
|  | --- a/drivers/net/phy/Kconfig | ||||||
|  | +++ b/drivers/net/phy/Kconfig | ||||||
|  | @@ -304,6 +304,13 @@ config MEDIATEK_GE_SOC_PHY | ||||||
|  |  	  present in the SoCs efuse and will dynamically calibrate VCM | ||||||
|  |  	  (common-mode voltage) during startup. | ||||||
|  |   | ||||||
|  | +config MEDIATEK_2P5G_PHY | ||||||
|  | +	tristate "MediaTek 2.5G Ethernet PHY" | ||||||
|  | +	depends on (ARCH_MEDIATEK && ARM64) || COMPILE_TEST | ||||||
|  | +	default NET_MEDIATEK_SOC | ||||||
|  | +	help | ||||||
|  | +	  Supports the MediaTek 2.5G Ethernet PHY. | ||||||
|  | + | ||||||
|  |  config MICREL_PHY | ||||||
|  |  	tristate "Micrel PHYs" | ||||||
|  |  	help | ||||||
|  | --- a/drivers/net/phy/Makefile | ||||||
|  | +++ b/drivers/net/phy/Makefile | ||||||
|  | @@ -80,6 +80,7 @@ obj-$(CONFIG_MARVELL_10G_PHY)	+= marvell | ||||||
|  |  obj-$(CONFIG_MARVELL_PHY)	+= marvell.o | ||||||
|  |  obj-$(CONFIG_MARVELL_88X2222_PHY)	+= marvell-88x2222.o | ||||||
|  |  obj-$(CONFIG_MAXLINEAR_GPHY)	+= mxl-gpy.o | ||||||
|  | +obj-$(CONFIG_MEDIATEK_2P5G_PHY)	+= mediatek-2p5ge.o | ||||||
|  |  obj-$(CONFIG_MEDIATEK_GE_PHY)	+= mediatek-ge.o | ||||||
|  |  obj-$(CONFIG_MEDIATEK_GE_SOC_PHY)	+= mediatek-ge-soc.o | ||||||
|  |  obj-$(CONFIG_MESON_GXL_PHY)	+= meson-gxl.o | ||||||
		Reference in New Issue
	
	Block a user
	 Daniel Golle
					Daniel Golle