xref: /openbmc/linux/drivers/net/phy/mxl-gpy.c (revision 2ed84c0c)
17d901a1eSXu Liang // SPDX-License-Identifier: GPL-2.0+
27d901a1eSXu Liang /* Copyright (C) 2021 Maxlinear Corporation
37d901a1eSXu Liang  * Copyright (C) 2020 Intel Corporation
47d901a1eSXu Liang  *
57d901a1eSXu Liang  * Drivers for Maxlinear Ethernet GPY
67d901a1eSXu Liang  *
77d901a1eSXu Liang  */
87d901a1eSXu Liang 
97d901a1eSXu Liang #include <linux/module.h>
107d901a1eSXu Liang #include <linux/bitfield.h>
1109ce6b20SMichael Walle #include <linux/hwmon.h>
125f4d487dSMichael Walle #include <linux/mutex.h>
137d901a1eSXu Liang #include <linux/phy.h>
1409ce6b20SMichael Walle #include <linux/polynomial.h>
1597a89ed1SMichael Walle #include <linux/property.h>
167d901a1eSXu Liang #include <linux/netdevice.h>
177d901a1eSXu Liang 
187d901a1eSXu Liang /* PHY ID */
197d901a1eSXu Liang #define PHY_ID_GPYx15B_MASK	0xFFFFFFFC
207d901a1eSXu Liang #define PHY_ID_GPY21xB_MASK	0xFFFFFFF9
217d901a1eSXu Liang #define PHY_ID_GPY2xx		0x67C9DC00
227d901a1eSXu Liang #define PHY_ID_GPY115B		0x67C9DF00
237d901a1eSXu Liang #define PHY_ID_GPY115C		0x67C9DF10
247d901a1eSXu Liang #define PHY_ID_GPY211B		0x67C9DE08
257d901a1eSXu Liang #define PHY_ID_GPY211C		0x67C9DE10
267d901a1eSXu Liang #define PHY_ID_GPY212B		0x67C9DE09
277d901a1eSXu Liang #define PHY_ID_GPY212C		0x67C9DE20
287d901a1eSXu Liang #define PHY_ID_GPY215B		0x67C9DF04
297d901a1eSXu Liang #define PHY_ID_GPY215C		0x67C9DF20
307d901a1eSXu Liang #define PHY_ID_GPY241B		0x67C9DE40
317d901a1eSXu Liang #define PHY_ID_GPY241BM		0x67C9DE80
327d901a1eSXu Liang #define PHY_ID_GPY245B		0x67C9DEC0
337d901a1eSXu Liang 
34fd8825cdSRaju Lakkaraju #define PHY_CTL1		0x13
35fd8825cdSRaju Lakkaraju #define PHY_CTL1_MDICD		BIT(3)
36fd8825cdSRaju Lakkaraju #define PHY_CTL1_MDIAB		BIT(2)
37fd8825cdSRaju Lakkaraju #define PHY_CTL1_AMDIX		BIT(0)
387d901a1eSXu Liang #define PHY_MIISTAT		0x18	/* MII state */
397d901a1eSXu Liang #define PHY_IMASK		0x19	/* interrupt mask */
407d901a1eSXu Liang #define PHY_ISTAT		0x1A	/* interrupt status */
417d901a1eSXu Liang #define PHY_FWV			0x1E	/* firmware version */
427d901a1eSXu Liang 
437d901a1eSXu Liang #define PHY_MIISTAT_SPD_MASK	GENMASK(2, 0)
447d901a1eSXu Liang #define PHY_MIISTAT_DPX		BIT(3)
457d901a1eSXu Liang #define PHY_MIISTAT_LS		BIT(10)
467d901a1eSXu Liang 
477d901a1eSXu Liang #define PHY_MIISTAT_SPD_10	0
487d901a1eSXu Liang #define PHY_MIISTAT_SPD_100	1
497d901a1eSXu Liang #define PHY_MIISTAT_SPD_1000	2
507d901a1eSXu Liang #define PHY_MIISTAT_SPD_2500	4
517d901a1eSXu Liang 
527d901a1eSXu Liang #define PHY_IMASK_WOL		BIT(15)	/* Wake-on-LAN */
537d901a1eSXu Liang #define PHY_IMASK_ANC		BIT(10)	/* Auto-Neg complete */
547d901a1eSXu Liang #define PHY_IMASK_ADSC		BIT(5)	/* Link auto-downspeed detect */
557d901a1eSXu Liang #define PHY_IMASK_DXMC		BIT(2)	/* Duplex mode change */
567d901a1eSXu Liang #define PHY_IMASK_LSPC		BIT(1)	/* Link speed change */
577d901a1eSXu Liang #define PHY_IMASK_LSTC		BIT(0)	/* Link state change */
587d901a1eSXu Liang #define PHY_IMASK_MASK		(PHY_IMASK_LSTC | \
597d901a1eSXu Liang 				 PHY_IMASK_LSPC | \
607d901a1eSXu Liang 				 PHY_IMASK_DXMC | \
617d901a1eSXu Liang 				 PHY_IMASK_ADSC | \
627d901a1eSXu Liang 				 PHY_IMASK_ANC)
637d901a1eSXu Liang 
647d901a1eSXu Liang #define PHY_FWV_REL_MASK	BIT(15)
651e9aa7baSMichael Walle #define PHY_FWV_MAJOR_MASK	GENMASK(11, 8)
667d901a1eSXu Liang #define PHY_FWV_MINOR_MASK	GENMASK(7, 0)
677d901a1eSXu Liang 
68fd8825cdSRaju Lakkaraju #define PHY_PMA_MGBT_POLARITY	0x82
69fd8825cdSRaju Lakkaraju #define PHY_MDI_MDI_X_MASK	GENMASK(1, 0)
70fd8825cdSRaju Lakkaraju #define PHY_MDI_MDI_X_NORMAL	0x3
71fd8825cdSRaju Lakkaraju #define PHY_MDI_MDI_X_AB	0x2
72fd8825cdSRaju Lakkaraju #define PHY_MDI_MDI_X_CD	0x1
73fd8825cdSRaju Lakkaraju #define PHY_MDI_MDI_X_CROSS	0x0
74fd8825cdSRaju Lakkaraju 
757d901a1eSXu Liang /* SGMII */
767d901a1eSXu Liang #define VSPEC1_SGMII_CTRL	0x08
777d901a1eSXu Liang #define VSPEC1_SGMII_CTRL_ANEN	BIT(12)		/* Aneg enable */
787d901a1eSXu Liang #define VSPEC1_SGMII_CTRL_ANRS	BIT(9)		/* Restart Aneg */
797d901a1eSXu Liang #define VSPEC1_SGMII_ANEN_ANRS	(VSPEC1_SGMII_CTRL_ANEN | \
807d901a1eSXu Liang 				 VSPEC1_SGMII_CTRL_ANRS)
817d901a1eSXu Liang 
8209ce6b20SMichael Walle /* Temperature sensor */
83343a5d35SMichael Walle #define VSPEC1_TEMP_STA	0x0E
84343a5d35SMichael Walle #define VSPEC1_TEMP_STA_DATA	GENMASK(9, 0)
8509ce6b20SMichael Walle 
865f4d487dSMichael Walle /* Mailbox */
875f4d487dSMichael Walle #define VSPEC1_MBOX_DATA	0x5
885f4d487dSMichael Walle #define VSPEC1_MBOX_ADDRLO	0x6
895f4d487dSMichael Walle #define VSPEC1_MBOX_CMD		0x7
905f4d487dSMichael Walle #define VSPEC1_MBOX_CMD_ADDRHI	GENMASK(7, 0)
915f4d487dSMichael Walle #define VSPEC1_MBOX_CMD_RD	(0 << 8)
925f4d487dSMichael Walle #define VSPEC1_MBOX_CMD_READY	BIT(15)
935f4d487dSMichael Walle 
947d901a1eSXu Liang /* WoL */
957d901a1eSXu Liang #define VPSPEC2_WOL_CTL		0x0E06
967d901a1eSXu Liang #define VPSPEC2_WOL_AD01	0x0E08
977d901a1eSXu Liang #define VPSPEC2_WOL_AD23	0x0E09
987d901a1eSXu Liang #define VPSPEC2_WOL_AD45	0x0E0A
997d901a1eSXu Liang #define WOL_EN			BIT(0)
1007d901a1eSXu Liang 
1015f4d487dSMichael Walle /* Internal registers, access via mbox */
1025f4d487dSMichael Walle #define REG_GPIO0_OUT		0xd3ce00
1035f4d487dSMichael Walle 
1041db85870SMichael Walle struct gpy_priv {
1055f4d487dSMichael Walle 	/* serialize mailbox acesses */
1065f4d487dSMichael Walle 	struct mutex mbox_lock;
1075f4d487dSMichael Walle 
1081e9aa7baSMichael Walle 	u8 fw_major;
1091db85870SMichael Walle 	u8 fw_minor;
1100ba13995SXu Liang 
1110ba13995SXu Liang 	/* It takes 3 seconds to fully switch out of loopback mode before
1120ba13995SXu Liang 	 * it can safely re-enter loopback mode. Record the time when
1130ba13995SXu Liang 	 * loopback is disabled. Check and wait if necessary before loopback
1140ba13995SXu Liang 	 * is enabled.
1150ba13995SXu Liang 	 */
1160ba13995SXu Liang 	u64 lb_dis_to;
1171db85870SMichael Walle };
1181db85870SMichael Walle 
1197d901a1eSXu Liang static const struct {
1201e9aa7baSMichael Walle 	int major;
1217d901a1eSXu Liang 	int minor;
1227d901a1eSXu Liang } ver_need_sgmii_reaneg[] = {
1237d901a1eSXu Liang 	{7, 0x6D},
1247d901a1eSXu Liang 	{8, 0x6D},
1257d901a1eSXu Liang 	{9, 0x73},
1267d901a1eSXu Liang };
1277d901a1eSXu Liang 
12809ce6b20SMichael Walle #if IS_ENABLED(CONFIG_HWMON)
12909ce6b20SMichael Walle /* The original translation formulae of the temperature (in degrees of Celsius)
13009ce6b20SMichael Walle  * are as follows:
13109ce6b20SMichael Walle  *
13209ce6b20SMichael Walle  *   T = -2.5761e-11*(N^4) + 9.7332e-8*(N^3) + -1.9165e-4*(N^2) +
13309ce6b20SMichael Walle  *       3.0762e-1*(N^1) + -5.2156e1
13409ce6b20SMichael Walle  *
13509ce6b20SMichael Walle  * where [-52.156, 137.961]C and N = [0, 1023].
13609ce6b20SMichael Walle  *
13709ce6b20SMichael Walle  * They must be accordingly altered to be suitable for the integer arithmetics.
13809ce6b20SMichael Walle  * The technique is called 'factor redistribution', which just makes sure the
13909ce6b20SMichael Walle  * multiplications and divisions are made so to have a result of the operations
14009ce6b20SMichael Walle  * within the integer numbers limit. In addition we need to translate the
14109ce6b20SMichael Walle  * formulae to accept millidegrees of Celsius. Here what it looks like after
14209ce6b20SMichael Walle  * the alterations:
14309ce6b20SMichael Walle  *
14409ce6b20SMichael Walle  *   T = -25761e-12*(N^4) + 97332e-9*(N^3) + -191650e-6*(N^2) +
14509ce6b20SMichael Walle  *       307620e-3*(N^1) + -52156
14609ce6b20SMichael Walle  *
14709ce6b20SMichael Walle  * where T = [-52156, 137961]mC and N = [0, 1023].
14809ce6b20SMichael Walle  */
14909ce6b20SMichael Walle static const struct polynomial poly_N_to_temp = {
15009ce6b20SMichael Walle 	.terms = {
15109ce6b20SMichael Walle 		{4,  -25761, 1000, 1},
15209ce6b20SMichael Walle 		{3,   97332, 1000, 1},
15309ce6b20SMichael Walle 		{2, -191650, 1000, 1},
15409ce6b20SMichael Walle 		{1,  307620, 1000, 1},
15509ce6b20SMichael Walle 		{0,  -52156,    1, 1}
15609ce6b20SMichael Walle 	}
15709ce6b20SMichael Walle };
15809ce6b20SMichael Walle 
15909ce6b20SMichael Walle static int gpy_hwmon_read(struct device *dev,
16009ce6b20SMichael Walle 			  enum hwmon_sensor_types type,
16109ce6b20SMichael Walle 			  u32 attr, int channel, long *value)
16209ce6b20SMichael Walle {
16309ce6b20SMichael Walle 	struct phy_device *phydev = dev_get_drvdata(dev);
16409ce6b20SMichael Walle 	int ret;
16509ce6b20SMichael Walle 
166343a5d35SMichael Walle 	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_TEMP_STA);
16709ce6b20SMichael Walle 	if (ret < 0)
16809ce6b20SMichael Walle 		return ret;
16909ce6b20SMichael Walle 	if (!ret)
17009ce6b20SMichael Walle 		return -ENODATA;
17109ce6b20SMichael Walle 
17209ce6b20SMichael Walle 	*value = polynomial_calc(&poly_N_to_temp,
173343a5d35SMichael Walle 				 FIELD_GET(VSPEC1_TEMP_STA_DATA, ret));
17409ce6b20SMichael Walle 
17509ce6b20SMichael Walle 	return 0;
17609ce6b20SMichael Walle }
17709ce6b20SMichael Walle 
17809ce6b20SMichael Walle static umode_t gpy_hwmon_is_visible(const void *data,
17909ce6b20SMichael Walle 				    enum hwmon_sensor_types type,
18009ce6b20SMichael Walle 				    u32 attr, int channel)
18109ce6b20SMichael Walle {
18209ce6b20SMichael Walle 	return 0444;
18309ce6b20SMichael Walle }
18409ce6b20SMichael Walle 
185*2ed84c0cSKrzysztof Kozlowski static const struct hwmon_channel_info * const gpy_hwmon_info[] = {
18609ce6b20SMichael Walle 	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
18709ce6b20SMichael Walle 	NULL
18809ce6b20SMichael Walle };
18909ce6b20SMichael Walle 
19009ce6b20SMichael Walle static const struct hwmon_ops gpy_hwmon_hwmon_ops = {
19109ce6b20SMichael Walle 	.is_visible	= gpy_hwmon_is_visible,
19209ce6b20SMichael Walle 	.read		= gpy_hwmon_read,
19309ce6b20SMichael Walle };
19409ce6b20SMichael Walle 
19509ce6b20SMichael Walle static const struct hwmon_chip_info gpy_hwmon_chip_info = {
19609ce6b20SMichael Walle 	.ops		= &gpy_hwmon_hwmon_ops,
19709ce6b20SMichael Walle 	.info		= gpy_hwmon_info,
19809ce6b20SMichael Walle };
19909ce6b20SMichael Walle 
20009ce6b20SMichael Walle static int gpy_hwmon_register(struct phy_device *phydev)
20109ce6b20SMichael Walle {
20209ce6b20SMichael Walle 	struct device *dev = &phydev->mdio.dev;
20309ce6b20SMichael Walle 	struct device *hwmon_dev;
20409ce6b20SMichael Walle 	char *hwmon_name;
20509ce6b20SMichael Walle 
20609ce6b20SMichael Walle 	hwmon_name = devm_hwmon_sanitize_name(dev, dev_name(dev));
20709ce6b20SMichael Walle 	if (IS_ERR(hwmon_name))
20809ce6b20SMichael Walle 		return PTR_ERR(hwmon_name);
20909ce6b20SMichael Walle 
21009ce6b20SMichael Walle 	hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
21109ce6b20SMichael Walle 							 phydev,
21209ce6b20SMichael Walle 							 &gpy_hwmon_chip_info,
21309ce6b20SMichael Walle 							 NULL);
21409ce6b20SMichael Walle 
21509ce6b20SMichael Walle 	return PTR_ERR_OR_ZERO(hwmon_dev);
21609ce6b20SMichael Walle }
21709ce6b20SMichael Walle #else
21809ce6b20SMichael Walle static int gpy_hwmon_register(struct phy_device *phydev)
21909ce6b20SMichael Walle {
22009ce6b20SMichael Walle 	return 0;
22109ce6b20SMichael Walle }
22209ce6b20SMichael Walle #endif
22309ce6b20SMichael Walle 
2245f4d487dSMichael Walle static int gpy_mbox_read(struct phy_device *phydev, u32 addr)
2255f4d487dSMichael Walle {
2265f4d487dSMichael Walle 	struct gpy_priv *priv = phydev->priv;
2275f4d487dSMichael Walle 	int val, ret;
2285f4d487dSMichael Walle 	u16 cmd;
2295f4d487dSMichael Walle 
2305f4d487dSMichael Walle 	mutex_lock(&priv->mbox_lock);
2315f4d487dSMichael Walle 
2325f4d487dSMichael Walle 	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_ADDRLO,
2335f4d487dSMichael Walle 			    addr);
2345f4d487dSMichael Walle 	if (ret)
2355f4d487dSMichael Walle 		goto out;
2365f4d487dSMichael Walle 
2375f4d487dSMichael Walle 	cmd = VSPEC1_MBOX_CMD_RD;
2385f4d487dSMichael Walle 	cmd |= FIELD_PREP(VSPEC1_MBOX_CMD_ADDRHI, addr >> 16);
2395f4d487dSMichael Walle 
2405f4d487dSMichael Walle 	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_CMD, cmd);
2415f4d487dSMichael Walle 	if (ret)
2425f4d487dSMichael Walle 		goto out;
2435f4d487dSMichael Walle 
2445f4d487dSMichael Walle 	/* The mbox read is used in the interrupt workaround. It was observed
2455f4d487dSMichael Walle 	 * that a read might take up to 2.5ms. This is also the time for which
2465f4d487dSMichael Walle 	 * the interrupt line is stuck low. To be on the safe side, poll the
2475f4d487dSMichael Walle 	 * ready bit for 10ms.
2485f4d487dSMichael Walle 	 */
2495f4d487dSMichael Walle 	ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
2505f4d487dSMichael Walle 					VSPEC1_MBOX_CMD, val,
2515f4d487dSMichael Walle 					(val & VSPEC1_MBOX_CMD_READY),
2525f4d487dSMichael Walle 					500, 10000, false);
2535f4d487dSMichael Walle 	if (ret)
2545f4d487dSMichael Walle 		goto out;
2555f4d487dSMichael Walle 
2565f4d487dSMichael Walle 	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_DATA);
2575f4d487dSMichael Walle 
2585f4d487dSMichael Walle out:
2595f4d487dSMichael Walle 	mutex_unlock(&priv->mbox_lock);
2605f4d487dSMichael Walle 	return ret;
2615f4d487dSMichael Walle }
2625f4d487dSMichael Walle 
2637d901a1eSXu Liang static int gpy_config_init(struct phy_device *phydev)
2647d901a1eSXu Liang {
2657d901a1eSXu Liang 	int ret;
2667d901a1eSXu Liang 
2677d901a1eSXu Liang 	/* Mask all interrupts */
2687d901a1eSXu Liang 	ret = phy_write(phydev, PHY_IMASK, 0);
2697d901a1eSXu Liang 	if (ret)
2707d901a1eSXu Liang 		return ret;
2717d901a1eSXu Liang 
2727d901a1eSXu Liang 	/* Clear all pending interrupts */
2737d901a1eSXu Liang 	ret = phy_read(phydev, PHY_ISTAT);
2747d901a1eSXu Liang 	return ret < 0 ? ret : 0;
2757d901a1eSXu Liang }
2767d901a1eSXu Liang 
2775f4d487dSMichael Walle static bool gpy_has_broken_mdint(struct phy_device *phydev)
2785f4d487dSMichael Walle {
2795f4d487dSMichael Walle 	/* At least these PHYs are known to have broken interrupt handling */
2805f4d487dSMichael Walle 	return phydev->drv->phy_id == PHY_ID_GPY215B ||
2815f4d487dSMichael Walle 	       phydev->drv->phy_id == PHY_ID_GPY215C;
2825f4d487dSMichael Walle }
2835f4d487dSMichael Walle 
2847d901a1eSXu Liang static int gpy_probe(struct phy_device *phydev)
2857d901a1eSXu Liang {
2861db85870SMichael Walle 	struct device *dev = &phydev->mdio.dev;
2871db85870SMichael Walle 	struct gpy_priv *priv;
288fc3dd036SMichael Walle 	int fw_version;
2897d901a1eSXu Liang 	int ret;
2907d901a1eSXu Liang 
2917d901a1eSXu Liang 	if (!phydev->is_c45) {
2927d901a1eSXu Liang 		ret = phy_get_c45_ids(phydev);
2937d901a1eSXu Liang 		if (ret < 0)
2947d901a1eSXu Liang 			return ret;
2957d901a1eSXu Liang 	}
2967d901a1eSXu Liang 
2971db85870SMichael Walle 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
2981db85870SMichael Walle 	if (!priv)
2991db85870SMichael Walle 		return -ENOMEM;
3001db85870SMichael Walle 	phydev->priv = priv;
3015f4d487dSMichael Walle 	mutex_init(&priv->mbox_lock);
3021db85870SMichael Walle 
30397a89ed1SMichael Walle 	if (gpy_has_broken_mdint(phydev) &&
30497a89ed1SMichael Walle 	    !device_property_present(dev, "maxlinear,use-broken-interrupts"))
30597a89ed1SMichael Walle 		phydev->dev_flags |= PHY_F_NO_IRQ;
30697a89ed1SMichael Walle 
307fc3dd036SMichael Walle 	fw_version = phy_read(phydev, PHY_FWV);
308fc3dd036SMichael Walle 	if (fw_version < 0)
309fc3dd036SMichael Walle 		return fw_version;
3101e9aa7baSMichael Walle 	priv->fw_major = FIELD_GET(PHY_FWV_MAJOR_MASK, fw_version);
3111db85870SMichael Walle 	priv->fw_minor = FIELD_GET(PHY_FWV_MINOR_MASK, fw_version);
3127d901a1eSXu Liang 
31309ce6b20SMichael Walle 	ret = gpy_hwmon_register(phydev);
31409ce6b20SMichael Walle 	if (ret)
31509ce6b20SMichael Walle 		return ret;
31609ce6b20SMichael Walle 
3171db85870SMichael Walle 	/* Show GPY PHY FW version in dmesg */
318d523f2ebSMichael Walle 	phydev_info(phydev, "Firmware Version: %d.%d (0x%04X%s)\n",
319d523f2ebSMichael Walle 		    priv->fw_major, priv->fw_minor, fw_version,
320d523f2ebSMichael Walle 		    fw_version & PHY_FWV_REL_MASK ? "" : " test version");
3217d901a1eSXu Liang 
3227d901a1eSXu Liang 	return 0;
3237d901a1eSXu Liang }
3247d901a1eSXu Liang 
3257d901a1eSXu Liang static bool gpy_sgmii_need_reaneg(struct phy_device *phydev)
3267d901a1eSXu Liang {
3271db85870SMichael Walle 	struct gpy_priv *priv = phydev->priv;
3287d901a1eSXu Liang 	size_t i;
3297d901a1eSXu Liang 
3307d901a1eSXu Liang 	for (i = 0; i < ARRAY_SIZE(ver_need_sgmii_reaneg); i++) {
3311e9aa7baSMichael Walle 		if (priv->fw_major != ver_need_sgmii_reaneg[i].major)
3327d901a1eSXu Liang 			continue;
3331db85870SMichael Walle 		if (priv->fw_minor < ver_need_sgmii_reaneg[i].minor)
3347d901a1eSXu Liang 			return true;
3357d901a1eSXu Liang 		break;
3367d901a1eSXu Liang 	}
3377d901a1eSXu Liang 
3387d901a1eSXu Liang 	return false;
3397d901a1eSXu Liang }
3407d901a1eSXu Liang 
3417d901a1eSXu Liang static bool gpy_2500basex_chk(struct phy_device *phydev)
3427d901a1eSXu Liang {
3437d901a1eSXu Liang 	int ret;
3447d901a1eSXu Liang 
3457d901a1eSXu Liang 	ret = phy_read(phydev, PHY_MIISTAT);
3467d901a1eSXu Liang 	if (ret < 0) {
3477d901a1eSXu Liang 		phydev_err(phydev, "Error: MDIO register access failed: %d\n",
3487d901a1eSXu Liang 			   ret);
3497d901a1eSXu Liang 		return false;
3507d901a1eSXu Liang 	}
3517d901a1eSXu Liang 
3527d901a1eSXu Liang 	if (!(ret & PHY_MIISTAT_LS) ||
3537d901a1eSXu Liang 	    FIELD_GET(PHY_MIISTAT_SPD_MASK, ret) != PHY_MIISTAT_SPD_2500)
3547d901a1eSXu Liang 		return false;
3557d901a1eSXu Liang 
3567d901a1eSXu Liang 	phydev->speed = SPEED_2500;
3577d901a1eSXu Liang 	phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
3587d901a1eSXu Liang 	phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
3597d901a1eSXu Liang 		       VSPEC1_SGMII_CTRL_ANEN, 0);
3607d901a1eSXu Liang 	return true;
3617d901a1eSXu Liang }
3627d901a1eSXu Liang 
3637d901a1eSXu Liang static bool gpy_sgmii_aneg_en(struct phy_device *phydev)
3647d901a1eSXu Liang {
3657d901a1eSXu Liang 	int ret;
3667d901a1eSXu Liang 
3677d901a1eSXu Liang 	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL);
3687d901a1eSXu Liang 	if (ret < 0) {
3697d901a1eSXu Liang 		phydev_err(phydev, "Error: MMD register access failed: %d\n",
3707d901a1eSXu Liang 			   ret);
3717d901a1eSXu Liang 		return true;
3727d901a1eSXu Liang 	}
3737d901a1eSXu Liang 
3747d901a1eSXu Liang 	return (ret & VSPEC1_SGMII_CTRL_ANEN) ? true : false;
3757d901a1eSXu Liang }
3767d901a1eSXu Liang 
377fd8825cdSRaju Lakkaraju static int gpy_config_mdix(struct phy_device *phydev, u8 ctrl)
378fd8825cdSRaju Lakkaraju {
379fd8825cdSRaju Lakkaraju 	int ret;
380fd8825cdSRaju Lakkaraju 	u16 val;
381fd8825cdSRaju Lakkaraju 
382fd8825cdSRaju Lakkaraju 	switch (ctrl) {
383fd8825cdSRaju Lakkaraju 	case ETH_TP_MDI_AUTO:
384fd8825cdSRaju Lakkaraju 		val = PHY_CTL1_AMDIX;
385fd8825cdSRaju Lakkaraju 		break;
386fd8825cdSRaju Lakkaraju 	case ETH_TP_MDI_X:
387fd8825cdSRaju Lakkaraju 		val = (PHY_CTL1_MDIAB | PHY_CTL1_MDICD);
388fd8825cdSRaju Lakkaraju 		break;
389fd8825cdSRaju Lakkaraju 	case ETH_TP_MDI:
390fd8825cdSRaju Lakkaraju 		val = 0;
391fd8825cdSRaju Lakkaraju 		break;
392fd8825cdSRaju Lakkaraju 	default:
393fd8825cdSRaju Lakkaraju 		return 0;
394fd8825cdSRaju Lakkaraju 	}
395fd8825cdSRaju Lakkaraju 
396fd8825cdSRaju Lakkaraju 	ret =  phy_modify(phydev, PHY_CTL1, PHY_CTL1_AMDIX | PHY_CTL1_MDIAB |
397fd8825cdSRaju Lakkaraju 			  PHY_CTL1_MDICD, val);
398fd8825cdSRaju Lakkaraju 	if (ret < 0)
399fd8825cdSRaju Lakkaraju 		return ret;
400fd8825cdSRaju Lakkaraju 
401fd8825cdSRaju Lakkaraju 	return genphy_c45_restart_aneg(phydev);
402fd8825cdSRaju Lakkaraju }
403fd8825cdSRaju Lakkaraju 
4047d901a1eSXu Liang static int gpy_config_aneg(struct phy_device *phydev)
4057d901a1eSXu Liang {
4067d901a1eSXu Liang 	bool changed = false;
4077d901a1eSXu Liang 	u32 adv;
4087d901a1eSXu Liang 	int ret;
4097d901a1eSXu Liang 
4107d901a1eSXu Liang 	if (phydev->autoneg == AUTONEG_DISABLE) {
4117d901a1eSXu Liang 		/* Configure half duplex with genphy_setup_forced,
4127d901a1eSXu Liang 		 * because genphy_c45_pma_setup_forced does not support.
4137d901a1eSXu Liang 		 */
4147d901a1eSXu Liang 		return phydev->duplex != DUPLEX_FULL
4157d901a1eSXu Liang 			? genphy_setup_forced(phydev)
4167d901a1eSXu Liang 			: genphy_c45_pma_setup_forced(phydev);
4177d901a1eSXu Liang 	}
4187d901a1eSXu Liang 
419fd8825cdSRaju Lakkaraju 	ret = gpy_config_mdix(phydev,  phydev->mdix_ctrl);
420fd8825cdSRaju Lakkaraju 	if (ret < 0)
421fd8825cdSRaju Lakkaraju 		return ret;
422fd8825cdSRaju Lakkaraju 
4237d901a1eSXu Liang 	ret = genphy_c45_an_config_aneg(phydev);
4247d901a1eSXu Liang 	if (ret < 0)
4257d901a1eSXu Liang 		return ret;
4267d901a1eSXu Liang 	if (ret > 0)
4277d901a1eSXu Liang 		changed = true;
4287d901a1eSXu Liang 
4297d901a1eSXu Liang 	adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
4307d901a1eSXu Liang 	ret = phy_modify_changed(phydev, MII_CTRL1000,
4317d901a1eSXu Liang 				 ADVERTISE_1000FULL | ADVERTISE_1000HALF,
4327d901a1eSXu Liang 				 adv);
4337d901a1eSXu Liang 	if (ret < 0)
4347d901a1eSXu Liang 		return ret;
4357d901a1eSXu Liang 	if (ret > 0)
4367d901a1eSXu Liang 		changed = true;
4377d901a1eSXu Liang 
4387d901a1eSXu Liang 	ret = genphy_c45_check_and_restart_aneg(phydev, changed);
4397d901a1eSXu Liang 	if (ret < 0)
4407d901a1eSXu Liang 		return ret;
4417d901a1eSXu Liang 
4427d901a1eSXu Liang 	if (phydev->interface == PHY_INTERFACE_MODE_USXGMII ||
4437d901a1eSXu Liang 	    phydev->interface == PHY_INTERFACE_MODE_INTERNAL)
4447d901a1eSXu Liang 		return 0;
4457d901a1eSXu Liang 
4467d901a1eSXu Liang 	/* No need to trigger re-ANEG if link speed is 2.5G or SGMII ANEG is
4477d901a1eSXu Liang 	 * disabled.
4487d901a1eSXu Liang 	 */
4497d901a1eSXu Liang 	if (!gpy_sgmii_need_reaneg(phydev) || gpy_2500basex_chk(phydev) ||
4507d901a1eSXu Liang 	    !gpy_sgmii_aneg_en(phydev))
4517d901a1eSXu Liang 		return 0;
4527d901a1eSXu Liang 
4537d901a1eSXu Liang 	/* There is a design constraint in GPY2xx device where SGMII AN is
4547d901a1eSXu Liang 	 * only triggered when there is change of speed. If, PHY link
4557d901a1eSXu Liang 	 * partner`s speed is still same even after PHY TPI is down and up
4567d901a1eSXu Liang 	 * again, SGMII AN is not triggered and hence no new in-band message
4577d901a1eSXu Liang 	 * from GPY to MAC side SGMII.
4587d901a1eSXu Liang 	 * This could cause an issue during power up, when PHY is up prior to
4597d901a1eSXu Liang 	 * MAC. At this condition, once MAC side SGMII is up, MAC side SGMII
4607d901a1eSXu Liang 	 * wouldn`t receive new in-band message from GPY with correct link
4617d901a1eSXu Liang 	 * status, speed and duplex info.
4627d901a1eSXu Liang 	 *
4637d901a1eSXu Liang 	 * 1) If PHY is already up and TPI link status is still down (such as
4647d901a1eSXu Liang 	 *    hard reboot), TPI link status is polled for 4 seconds before
4657d901a1eSXu Liang 	 *    retriggerring SGMII AN.
4667d901a1eSXu Liang 	 * 2) If PHY is already up and TPI link status is also up (such as soft
4677d901a1eSXu Liang 	 *    reboot), polling of TPI link status is not needed and SGMII AN is
4687d901a1eSXu Liang 	 *    immediately retriggered.
4697d901a1eSXu Liang 	 * 3) Other conditions such as PHY is down, speed change etc, skip
4707d901a1eSXu Liang 	 *    retriggering SGMII AN. Note: in case of speed change, GPY FW will
4717d901a1eSXu Liang 	 *    initiate SGMII AN.
4727d901a1eSXu Liang 	 */
4737d901a1eSXu Liang 
4747d901a1eSXu Liang 	if (phydev->state != PHY_UP)
4757d901a1eSXu Liang 		return 0;
4767d901a1eSXu Liang 
4777d901a1eSXu Liang 	ret = phy_read_poll_timeout(phydev, MII_BMSR, ret, ret & BMSR_LSTATUS,
4787d901a1eSXu Liang 				    20000, 4000000, false);
4797d901a1eSXu Liang 	if (ret == -ETIMEDOUT)
4807d901a1eSXu Liang 		return 0;
4817d901a1eSXu Liang 	else if (ret < 0)
4827d901a1eSXu Liang 		return ret;
4837d901a1eSXu Liang 
4847d901a1eSXu Liang 	/* Trigger SGMII AN. */
4857d901a1eSXu Liang 	return phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
4867d901a1eSXu Liang 			      VSPEC1_SGMII_CTRL_ANRS, VSPEC1_SGMII_CTRL_ANRS);
4877d901a1eSXu Liang }
4887d901a1eSXu Liang 
489fd8825cdSRaju Lakkaraju static int gpy_update_mdix(struct phy_device *phydev)
490fd8825cdSRaju Lakkaraju {
491fd8825cdSRaju Lakkaraju 	int ret;
492fd8825cdSRaju Lakkaraju 
493fd8825cdSRaju Lakkaraju 	ret = phy_read(phydev, PHY_CTL1);
494fd8825cdSRaju Lakkaraju 	if (ret < 0)
495fd8825cdSRaju Lakkaraju 		return ret;
496fd8825cdSRaju Lakkaraju 
497fd8825cdSRaju Lakkaraju 	if (ret & PHY_CTL1_AMDIX)
498fd8825cdSRaju Lakkaraju 		phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
499fd8825cdSRaju Lakkaraju 	else
500fd8825cdSRaju Lakkaraju 		if (ret & PHY_CTL1_MDICD || ret & PHY_CTL1_MDIAB)
501fd8825cdSRaju Lakkaraju 			phydev->mdix_ctrl = ETH_TP_MDI_X;
502fd8825cdSRaju Lakkaraju 		else
503fd8825cdSRaju Lakkaraju 			phydev->mdix_ctrl = ETH_TP_MDI;
504fd8825cdSRaju Lakkaraju 
505fd8825cdSRaju Lakkaraju 	ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, PHY_PMA_MGBT_POLARITY);
506fd8825cdSRaju Lakkaraju 	if (ret < 0)
507fd8825cdSRaju Lakkaraju 		return ret;
508fd8825cdSRaju Lakkaraju 
509fd8825cdSRaju Lakkaraju 	if ((ret & PHY_MDI_MDI_X_MASK) < PHY_MDI_MDI_X_NORMAL)
510fd8825cdSRaju Lakkaraju 		phydev->mdix = ETH_TP_MDI_X;
511fd8825cdSRaju Lakkaraju 	else
512fd8825cdSRaju Lakkaraju 		phydev->mdix = ETH_TP_MDI;
513fd8825cdSRaju Lakkaraju 
514fd8825cdSRaju Lakkaraju 	return 0;
515fd8825cdSRaju Lakkaraju }
516fd8825cdSRaju Lakkaraju 
5177a495ddeSRaju Lakkaraju static int gpy_update_interface(struct phy_device *phydev)
5187d901a1eSXu Liang {
5197d901a1eSXu Liang 	int ret;
5207d901a1eSXu Liang 
5217d901a1eSXu Liang 	/* Interface mode is fixed for USXGMII and integrated PHY */
5227d901a1eSXu Liang 	if (phydev->interface == PHY_INTERFACE_MODE_USXGMII ||
5237d901a1eSXu Liang 	    phydev->interface == PHY_INTERFACE_MODE_INTERNAL)
5247a495ddeSRaju Lakkaraju 		return -EINVAL;
5257d901a1eSXu Liang 
5267d901a1eSXu Liang 	/* Automatically switch SERDES interface between SGMII and 2500-BaseX
5277d901a1eSXu Liang 	 * according to speed. Disable ANEG in 2500-BaseX mode.
5287d901a1eSXu Liang 	 */
5297d901a1eSXu Liang 	switch (phydev->speed) {
5307d901a1eSXu Liang 	case SPEED_2500:
5317d901a1eSXu Liang 		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
5327d901a1eSXu Liang 		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
5337d901a1eSXu Liang 				     VSPEC1_SGMII_CTRL_ANEN, 0);
5347a495ddeSRaju Lakkaraju 		if (ret < 0) {
5357d901a1eSXu Liang 			phydev_err(phydev,
5367d901a1eSXu Liang 				   "Error: Disable of SGMII ANEG failed: %d\n",
5377d901a1eSXu Liang 				   ret);
5387a495ddeSRaju Lakkaraju 			return ret;
5397a495ddeSRaju Lakkaraju 		}
5407d901a1eSXu Liang 		break;
5417d901a1eSXu Liang 	case SPEED_1000:
5427d901a1eSXu Liang 	case SPEED_100:
5437d901a1eSXu Liang 	case SPEED_10:
5447d901a1eSXu Liang 		phydev->interface = PHY_INTERFACE_MODE_SGMII;
5457d901a1eSXu Liang 		if (gpy_sgmii_aneg_en(phydev))
5467d901a1eSXu Liang 			break;
5477d901a1eSXu Liang 		/* Enable and restart SGMII ANEG for 10/100/1000Mbps link speed
5487d901a1eSXu Liang 		 * if ANEG is disabled (in 2500-BaseX mode).
5497d901a1eSXu Liang 		 */
5507d901a1eSXu Liang 		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
5517d901a1eSXu Liang 				     VSPEC1_SGMII_ANEN_ANRS,
5527d901a1eSXu Liang 				     VSPEC1_SGMII_ANEN_ANRS);
5537a495ddeSRaju Lakkaraju 		if (ret < 0) {
5547d901a1eSXu Liang 			phydev_err(phydev,
5557d901a1eSXu Liang 				   "Error: Enable of SGMII ANEG failed: %d\n",
5567d901a1eSXu Liang 				   ret);
5577a495ddeSRaju Lakkaraju 			return ret;
5587a495ddeSRaju Lakkaraju 		}
5597d901a1eSXu Liang 		break;
5607d901a1eSXu Liang 	}
561311abcddSRaju Lakkaraju 
5627a495ddeSRaju Lakkaraju 	if (phydev->speed == SPEED_2500 || phydev->speed == SPEED_1000) {
5637a495ddeSRaju Lakkaraju 		ret = genphy_read_master_slave(phydev);
5647a495ddeSRaju Lakkaraju 		if (ret < 0)
5657a495ddeSRaju Lakkaraju 			return ret;
5667a495ddeSRaju Lakkaraju 	}
5677a495ddeSRaju Lakkaraju 
568fd8825cdSRaju Lakkaraju 	return gpy_update_mdix(phydev);
5697d901a1eSXu Liang }
5707d901a1eSXu Liang 
5717d901a1eSXu Liang static int gpy_read_status(struct phy_device *phydev)
5727d901a1eSXu Liang {
5737d901a1eSXu Liang 	int ret;
5747d901a1eSXu Liang 
5757d901a1eSXu Liang 	ret = genphy_update_link(phydev);
5767d901a1eSXu Liang 	if (ret)
5777d901a1eSXu Liang 		return ret;
5787d901a1eSXu Liang 
5797d901a1eSXu Liang 	phydev->speed = SPEED_UNKNOWN;
5807d901a1eSXu Liang 	phydev->duplex = DUPLEX_UNKNOWN;
5817d901a1eSXu Liang 	phydev->pause = 0;
5827d901a1eSXu Liang 	phydev->asym_pause = 0;
5837d901a1eSXu Liang 
5847d901a1eSXu Liang 	if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
5857d901a1eSXu Liang 		ret = genphy_c45_read_lpa(phydev);
5867d901a1eSXu Liang 		if (ret < 0)
5877d901a1eSXu Liang 			return ret;
5887d901a1eSXu Liang 
5897d901a1eSXu Liang 		/* Read the link partner's 1G advertisement */
5907d901a1eSXu Liang 		ret = phy_read(phydev, MII_STAT1000);
5917d901a1eSXu Liang 		if (ret < 0)
5927d901a1eSXu Liang 			return ret;
5937d901a1eSXu Liang 		mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, ret);
5947d901a1eSXu Liang 	} else if (phydev->autoneg == AUTONEG_DISABLE) {
5957d901a1eSXu Liang 		linkmode_zero(phydev->lp_advertising);
5967d901a1eSXu Liang 	}
5977d901a1eSXu Liang 
5987d901a1eSXu Liang 	ret = phy_read(phydev, PHY_MIISTAT);
5997d901a1eSXu Liang 	if (ret < 0)
6007d901a1eSXu Liang 		return ret;
6017d901a1eSXu Liang 
6027d901a1eSXu Liang 	phydev->link = (ret & PHY_MIISTAT_LS) ? 1 : 0;
6037d901a1eSXu Liang 	phydev->duplex = (ret & PHY_MIISTAT_DPX) ? DUPLEX_FULL : DUPLEX_HALF;
6047d901a1eSXu Liang 	switch (FIELD_GET(PHY_MIISTAT_SPD_MASK, ret)) {
6057d901a1eSXu Liang 	case PHY_MIISTAT_SPD_10:
6067d901a1eSXu Liang 		phydev->speed = SPEED_10;
6077d901a1eSXu Liang 		break;
6087d901a1eSXu Liang 	case PHY_MIISTAT_SPD_100:
6097d901a1eSXu Liang 		phydev->speed = SPEED_100;
6107d901a1eSXu Liang 		break;
6117d901a1eSXu Liang 	case PHY_MIISTAT_SPD_1000:
6127d901a1eSXu Liang 		phydev->speed = SPEED_1000;
6137d901a1eSXu Liang 		break;
6147d901a1eSXu Liang 	case PHY_MIISTAT_SPD_2500:
6157d901a1eSXu Liang 		phydev->speed = SPEED_2500;
6167d901a1eSXu Liang 		break;
6177d901a1eSXu Liang 	}
6187d901a1eSXu Liang 
6197a495ddeSRaju Lakkaraju 	if (phydev->link) {
6207a495ddeSRaju Lakkaraju 		ret = gpy_update_interface(phydev);
6217a495ddeSRaju Lakkaraju 		if (ret < 0)
6227a495ddeSRaju Lakkaraju 			return ret;
6237a495ddeSRaju Lakkaraju 	}
6247d901a1eSXu Liang 
6257d901a1eSXu Liang 	return 0;
6267d901a1eSXu Liang }
6277d901a1eSXu Liang 
6287d901a1eSXu Liang static int gpy_config_intr(struct phy_device *phydev)
6297d901a1eSXu Liang {
6307d901a1eSXu Liang 	u16 mask = 0;
6317d901a1eSXu Liang 
6327d901a1eSXu Liang 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
6337d901a1eSXu Liang 		mask = PHY_IMASK_MASK;
6347d901a1eSXu Liang 
6357d901a1eSXu Liang 	return phy_write(phydev, PHY_IMASK, mask);
6367d901a1eSXu Liang }
6377d901a1eSXu Liang 
6387d901a1eSXu Liang static irqreturn_t gpy_handle_interrupt(struct phy_device *phydev)
6397d901a1eSXu Liang {
6407d901a1eSXu Liang 	int reg;
6417d901a1eSXu Liang 
6427d901a1eSXu Liang 	reg = phy_read(phydev, PHY_ISTAT);
6437d901a1eSXu Liang 	if (reg < 0) {
6447d901a1eSXu Liang 		phy_error(phydev);
6457d901a1eSXu Liang 		return IRQ_NONE;
6467d901a1eSXu Liang 	}
6477d901a1eSXu Liang 
6487d901a1eSXu Liang 	if (!(reg & PHY_IMASK_MASK))
6497d901a1eSXu Liang 		return IRQ_NONE;
6507d901a1eSXu Liang 
6515f4d487dSMichael Walle 	/* The PHY might leave the interrupt line asserted even after PHY_ISTAT
6525f4d487dSMichael Walle 	 * is read. To avoid interrupt storms, delay the interrupt handling as
6535f4d487dSMichael Walle 	 * long as the PHY drives the interrupt line. An internal bus read will
6545f4d487dSMichael Walle 	 * stall as long as the interrupt line is asserted, thus just read a
6555f4d487dSMichael Walle 	 * random register here.
6565f4d487dSMichael Walle 	 * Because we cannot access the internal bus at all while the interrupt
6575f4d487dSMichael Walle 	 * is driven by the PHY, there is no way to make the interrupt line
6585f4d487dSMichael Walle 	 * unstuck (e.g. by changing the pinmux to GPIO input) during that time
6595f4d487dSMichael Walle 	 * frame. Therefore, polling is the best we can do and won't do any more
6605f4d487dSMichael Walle 	 * harm.
6615f4d487dSMichael Walle 	 * It was observed that this bug happens on link state and link speed
6625f4d487dSMichael Walle 	 * changes on a GPY215B and GYP215C independent of the firmware version
6635f4d487dSMichael Walle 	 * (which doesn't mean that this list is exhaustive).
6645f4d487dSMichael Walle 	 */
6655f4d487dSMichael Walle 	if (gpy_has_broken_mdint(phydev) &&
6665f4d487dSMichael Walle 	    (reg & (PHY_IMASK_LSTC | PHY_IMASK_LSPC))) {
6675f4d487dSMichael Walle 		reg = gpy_mbox_read(phydev, REG_GPIO0_OUT);
6685f4d487dSMichael Walle 		if (reg < 0) {
6695f4d487dSMichael Walle 			phy_error(phydev);
6705f4d487dSMichael Walle 			return IRQ_NONE;
6715f4d487dSMichael Walle 		}
6725f4d487dSMichael Walle 	}
6735f4d487dSMichael Walle 
6747d901a1eSXu Liang 	phy_trigger_machine(phydev);
6757d901a1eSXu Liang 
6767d901a1eSXu Liang 	return IRQ_HANDLED;
6777d901a1eSXu Liang }
6787d901a1eSXu Liang 
6797d901a1eSXu Liang static int gpy_set_wol(struct phy_device *phydev,
6807d901a1eSXu Liang 		       struct ethtool_wolinfo *wol)
6817d901a1eSXu Liang {
6827d901a1eSXu Liang 	struct net_device *attach_dev = phydev->attached_dev;
6837d901a1eSXu Liang 	int ret;
6847d901a1eSXu Liang 
6857d901a1eSXu Liang 	if (wol->wolopts & WAKE_MAGIC) {
6867d901a1eSXu Liang 		/* MAC address - Byte0:Byte1:Byte2:Byte3:Byte4:Byte5
6877d901a1eSXu Liang 		 * VPSPEC2_WOL_AD45 = Byte0:Byte1
6887d901a1eSXu Liang 		 * VPSPEC2_WOL_AD23 = Byte2:Byte3
6897d901a1eSXu Liang 		 * VPSPEC2_WOL_AD01 = Byte4:Byte5
6907d901a1eSXu Liang 		 */
6917d901a1eSXu Liang 		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
6927d901a1eSXu Liang 				       VPSPEC2_WOL_AD45,
6937d901a1eSXu Liang 				       ((attach_dev->dev_addr[0] << 8) |
6947d901a1eSXu Liang 				       attach_dev->dev_addr[1]));
6957d901a1eSXu Liang 		if (ret < 0)
6967d901a1eSXu Liang 			return ret;
6977d901a1eSXu Liang 
6987d901a1eSXu Liang 		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
6997d901a1eSXu Liang 				       VPSPEC2_WOL_AD23,
7007d901a1eSXu Liang 				       ((attach_dev->dev_addr[2] << 8) |
7017d901a1eSXu Liang 				       attach_dev->dev_addr[3]));
7027d901a1eSXu Liang 		if (ret < 0)
7037d901a1eSXu Liang 			return ret;
7047d901a1eSXu Liang 
7057d901a1eSXu Liang 		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
7067d901a1eSXu Liang 				       VPSPEC2_WOL_AD01,
7077d901a1eSXu Liang 				       ((attach_dev->dev_addr[4] << 8) |
7087d901a1eSXu Liang 				       attach_dev->dev_addr[5]));
7097d901a1eSXu Liang 		if (ret < 0)
7107d901a1eSXu Liang 			return ret;
7117d901a1eSXu Liang 
7127d901a1eSXu Liang 		/* Enable the WOL interrupt */
7137d901a1eSXu Liang 		ret = phy_write(phydev, PHY_IMASK, PHY_IMASK_WOL);
7147d901a1eSXu Liang 		if (ret < 0)
7157d901a1eSXu Liang 			return ret;
7167d901a1eSXu Liang 
7177d901a1eSXu Liang 		/* Enable magic packet matching */
7187d901a1eSXu Liang 		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
7197d901a1eSXu Liang 				       VPSPEC2_WOL_CTL,
7207d901a1eSXu Liang 				       WOL_EN);
7217d901a1eSXu Liang 		if (ret < 0)
7227d901a1eSXu Liang 			return ret;
7237d901a1eSXu Liang 
7247d901a1eSXu Liang 		/* Clear the interrupt status register.
7257d901a1eSXu Liang 		 * Only WoL is enabled so clear all.
7267d901a1eSXu Liang 		 */
7277d901a1eSXu Liang 		ret = phy_read(phydev, PHY_ISTAT);
7287d901a1eSXu Liang 		if (ret < 0)
7297d901a1eSXu Liang 			return ret;
7307d901a1eSXu Liang 	} else {
7317d901a1eSXu Liang 		/* Disable magic packet matching */
7327d901a1eSXu Liang 		ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
7337d901a1eSXu Liang 					 VPSPEC2_WOL_CTL,
7347d901a1eSXu Liang 					 WOL_EN);
7357d901a1eSXu Liang 		if (ret < 0)
7367d901a1eSXu Liang 			return ret;
7377d901a1eSXu Liang 	}
7387d901a1eSXu Liang 
7397d901a1eSXu Liang 	if (wol->wolopts & WAKE_PHY) {
7407d901a1eSXu Liang 		/* Enable the link state change interrupt */
7417d901a1eSXu Liang 		ret = phy_set_bits(phydev, PHY_IMASK, PHY_IMASK_LSTC);
7427d901a1eSXu Liang 		if (ret < 0)
7437d901a1eSXu Liang 			return ret;
7447d901a1eSXu Liang 
7457d901a1eSXu Liang 		/* Clear the interrupt status register */
7467d901a1eSXu Liang 		ret = phy_read(phydev, PHY_ISTAT);
7477d901a1eSXu Liang 		if (ret < 0)
7487d901a1eSXu Liang 			return ret;
7497d901a1eSXu Liang 
7507d901a1eSXu Liang 		if (ret & (PHY_IMASK_MASK & ~PHY_IMASK_LSTC))
7517d901a1eSXu Liang 			phy_trigger_machine(phydev);
7527d901a1eSXu Liang 
7537d901a1eSXu Liang 		return 0;
7547d901a1eSXu Liang 	}
7557d901a1eSXu Liang 
7567d901a1eSXu Liang 	/* Disable the link state change interrupt */
7577d901a1eSXu Liang 	return phy_clear_bits(phydev, PHY_IMASK, PHY_IMASK_LSTC);
7587d901a1eSXu Liang }
7597d901a1eSXu Liang 
7607d901a1eSXu Liang static void gpy_get_wol(struct phy_device *phydev,
7617d901a1eSXu Liang 			struct ethtool_wolinfo *wol)
7627d901a1eSXu Liang {
7637d901a1eSXu Liang 	int ret;
7647d901a1eSXu Liang 
7657d901a1eSXu Liang 	wol->supported = WAKE_MAGIC | WAKE_PHY;
7667d901a1eSXu Liang 	wol->wolopts = 0;
7677d901a1eSXu Liang 
7687d901a1eSXu Liang 	ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, VPSPEC2_WOL_CTL);
7697d901a1eSXu Liang 	if (ret & WOL_EN)
7707d901a1eSXu Liang 		wol->wolopts |= WAKE_MAGIC;
7717d901a1eSXu Liang 
7727d901a1eSXu Liang 	ret = phy_read(phydev, PHY_IMASK);
7737d901a1eSXu Liang 	if (ret & PHY_IMASK_LSTC)
7747d901a1eSXu Liang 		wol->wolopts |= WAKE_PHY;
7757d901a1eSXu Liang }
7767d901a1eSXu Liang 
7777d901a1eSXu Liang static int gpy_loopback(struct phy_device *phydev, bool enable)
7787d901a1eSXu Liang {
7790ba13995SXu Liang 	struct gpy_priv *priv = phydev->priv;
7800ba13995SXu Liang 	u16 set = 0;
7817d901a1eSXu Liang 	int ret;
7827d901a1eSXu Liang 
7830ba13995SXu Liang 	if (enable) {
7840ba13995SXu Liang 		u64 now = get_jiffies_64();
7850ba13995SXu Liang 
7860ba13995SXu Liang 		/* wait until 3 seconds from last disable */
7870ba13995SXu Liang 		if (time_before64(now, priv->lb_dis_to))
7880ba13995SXu Liang 			msleep(jiffies64_to_msecs(priv->lb_dis_to - now));
7890ba13995SXu Liang 
7900ba13995SXu Liang 		set = BMCR_LOOPBACK;
7917d901a1eSXu Liang 	}
7927d901a1eSXu Liang 
7930ba13995SXu Liang 	ret = phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK, set);
7940ba13995SXu Liang 	if (ret <= 0)
7957d901a1eSXu Liang 		return ret;
7960ba13995SXu Liang 
7970ba13995SXu Liang 	if (enable) {
7980ba13995SXu Liang 		/* It takes some time for PHY device to switch into
7990ba13995SXu Liang 		 * loopback mode.
8000ba13995SXu Liang 		 */
8010ba13995SXu Liang 		msleep(100);
8020ba13995SXu Liang 	} else {
8030ba13995SXu Liang 		priv->lb_dis_to = get_jiffies_64() + HZ * 3;
8040ba13995SXu Liang 	}
8050ba13995SXu Liang 
8060ba13995SXu Liang 	return 0;
8077d901a1eSXu Liang }
8087d901a1eSXu Liang 
8093b1b6e82SXu Liang static int gpy115_loopback(struct phy_device *phydev, bool enable)
8103b1b6e82SXu Liang {
8111db85870SMichael Walle 	struct gpy_priv *priv = phydev->priv;
8123b1b6e82SXu Liang 
8133b1b6e82SXu Liang 	if (enable)
8143b1b6e82SXu Liang 		return gpy_loopback(phydev, enable);
8153b1b6e82SXu Liang 
8161db85870SMichael Walle 	if (priv->fw_minor > 0x76)
8173b1b6e82SXu Liang 		return gpy_loopback(phydev, 0);
8183b1b6e82SXu Liang 
8193b1b6e82SXu Liang 	return genphy_soft_reset(phydev);
8203b1b6e82SXu Liang }
8213b1b6e82SXu Liang 
8227d901a1eSXu Liang static struct phy_driver gpy_drivers[] = {
8237d901a1eSXu Liang 	{
8247d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx),
8257d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY2xx",
8267d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
8277d901a1eSXu Liang 		.config_init	= gpy_config_init,
8287d901a1eSXu Liang 		.probe		= gpy_probe,
8297d901a1eSXu Liang 		.suspend	= genphy_suspend,
8307d901a1eSXu Liang 		.resume		= genphy_resume,
8317d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
8327d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
8337d901a1eSXu Liang 		.read_status	= gpy_read_status,
8347d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
8357d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
8367d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
8377d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
8387d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
8397d901a1eSXu Liang 	},
8407d901a1eSXu Liang 	{
8417d901a1eSXu Liang 		.phy_id		= PHY_ID_GPY115B,
8427d901a1eSXu Liang 		.phy_id_mask	= PHY_ID_GPYx15B_MASK,
8437d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY115B",
8447d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
8457d901a1eSXu Liang 		.config_init	= gpy_config_init,
8467d901a1eSXu Liang 		.probe		= gpy_probe,
8477d901a1eSXu Liang 		.suspend	= genphy_suspend,
8487d901a1eSXu Liang 		.resume		= genphy_resume,
8497d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
8507d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
8517d901a1eSXu Liang 		.read_status	= gpy_read_status,
8527d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
8537d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
8547d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
8557d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
8563b1b6e82SXu Liang 		.set_loopback	= gpy115_loopback,
8577d901a1eSXu Liang 	},
8587d901a1eSXu Liang 	{
8597d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY115C),
8607d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY115C",
8617d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
8627d901a1eSXu Liang 		.config_init	= gpy_config_init,
8637d901a1eSXu Liang 		.probe		= gpy_probe,
8647d901a1eSXu Liang 		.suspend	= genphy_suspend,
8657d901a1eSXu Liang 		.resume		= genphy_resume,
8667d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
8677d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
8687d901a1eSXu Liang 		.read_status	= gpy_read_status,
8697d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
8707d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
8717d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
8727d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
8733b1b6e82SXu Liang 		.set_loopback	= gpy115_loopback,
8747d901a1eSXu Liang 	},
8757d901a1eSXu Liang 	{
8767d901a1eSXu Liang 		.phy_id		= PHY_ID_GPY211B,
8777d901a1eSXu Liang 		.phy_id_mask	= PHY_ID_GPY21xB_MASK,
8787d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY211B",
8797d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
8807d901a1eSXu Liang 		.config_init	= gpy_config_init,
8817d901a1eSXu Liang 		.probe		= gpy_probe,
8827d901a1eSXu Liang 		.suspend	= genphy_suspend,
8837d901a1eSXu Liang 		.resume		= genphy_resume,
8847d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
8857d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
8867d901a1eSXu Liang 		.read_status	= gpy_read_status,
8877d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
8887d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
8897d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
8907d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
8917d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
8927d901a1eSXu Liang 	},
8937d901a1eSXu Liang 	{
8947d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY211C),
8957d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY211C",
8967d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
8977d901a1eSXu Liang 		.config_init	= gpy_config_init,
8987d901a1eSXu Liang 		.probe		= gpy_probe,
8997d901a1eSXu Liang 		.suspend	= genphy_suspend,
9007d901a1eSXu Liang 		.resume		= genphy_resume,
9017d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9027d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9037d901a1eSXu Liang 		.read_status	= gpy_read_status,
9047d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
9057d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
9067d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
9077d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
9087d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9097d901a1eSXu Liang 	},
9107d901a1eSXu Liang 	{
9117d901a1eSXu Liang 		.phy_id		= PHY_ID_GPY212B,
9127d901a1eSXu Liang 		.phy_id_mask	= PHY_ID_GPY21xB_MASK,
9137d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY212B",
9147d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
9157d901a1eSXu Liang 		.config_init	= gpy_config_init,
9167d901a1eSXu Liang 		.probe		= gpy_probe,
9177d901a1eSXu Liang 		.suspend	= genphy_suspend,
9187d901a1eSXu Liang 		.resume		= genphy_resume,
9197d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9207d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9217d901a1eSXu Liang 		.read_status	= gpy_read_status,
9227d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
9237d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
9247d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
9257d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
9267d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9277d901a1eSXu Liang 	},
9287d901a1eSXu Liang 	{
9297d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY212C),
9307d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY212C",
9317d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
9327d901a1eSXu Liang 		.config_init	= gpy_config_init,
9337d901a1eSXu Liang 		.probe		= gpy_probe,
9347d901a1eSXu Liang 		.suspend	= genphy_suspend,
9357d901a1eSXu Liang 		.resume		= genphy_resume,
9367d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9377d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9387d901a1eSXu Liang 		.read_status	= gpy_read_status,
9397d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
9407d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
9417d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
9427d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
9437d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9447d901a1eSXu Liang 	},
9457d901a1eSXu Liang 	{
9467d901a1eSXu Liang 		.phy_id		= PHY_ID_GPY215B,
9477d901a1eSXu Liang 		.phy_id_mask	= PHY_ID_GPYx15B_MASK,
9487d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY215B",
9497d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
9507d901a1eSXu Liang 		.config_init	= gpy_config_init,
9517d901a1eSXu Liang 		.probe		= gpy_probe,
9527d901a1eSXu Liang 		.suspend	= genphy_suspend,
9537d901a1eSXu Liang 		.resume		= genphy_resume,
9547d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9557d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9567d901a1eSXu Liang 		.read_status	= gpy_read_status,
9577d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
9587d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
9597d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
9607d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
9617d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9627d901a1eSXu Liang 	},
9637d901a1eSXu Liang 	{
9647d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY215C),
9657d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY215C",
9667d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
9677d901a1eSXu Liang 		.config_init	= gpy_config_init,
9687d901a1eSXu Liang 		.probe		= gpy_probe,
9697d901a1eSXu Liang 		.suspend	= genphy_suspend,
9707d901a1eSXu Liang 		.resume		= genphy_resume,
9717d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9727d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9737d901a1eSXu Liang 		.read_status	= gpy_read_status,
9747d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
9757d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
9767d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
9777d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
9787d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9797d901a1eSXu Liang 	},
9807d901a1eSXu Liang 	{
9817d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY241B),
9827d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY241B",
9837d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
9847d901a1eSXu Liang 		.config_init	= gpy_config_init,
9857d901a1eSXu Liang 		.probe		= gpy_probe,
9867d901a1eSXu Liang 		.suspend	= genphy_suspend,
9877d901a1eSXu Liang 		.resume		= genphy_resume,
9887d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9897d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9907d901a1eSXu Liang 		.read_status	= gpy_read_status,
9917d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
9927d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
9937d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
9947d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
9957d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9967d901a1eSXu Liang 	},
9977d901a1eSXu Liang 	{
9987d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY241BM),
9997d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY241BM",
10007d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
10017d901a1eSXu Liang 		.config_init	= gpy_config_init,
10027d901a1eSXu Liang 		.probe		= gpy_probe,
10037d901a1eSXu Liang 		.suspend	= genphy_suspend,
10047d901a1eSXu Liang 		.resume		= genphy_resume,
10057d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
10067d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
10077d901a1eSXu Liang 		.read_status	= gpy_read_status,
10087d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
10097d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
10107d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
10117d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
10127d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
10137d901a1eSXu Liang 	},
10147d901a1eSXu Liang 	{
10157d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY245B),
10167d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY245B",
10177d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
10187d901a1eSXu Liang 		.config_init	= gpy_config_init,
10197d901a1eSXu Liang 		.probe		= gpy_probe,
10207d901a1eSXu Liang 		.suspend	= genphy_suspend,
10217d901a1eSXu Liang 		.resume		= genphy_resume,
10227d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
10237d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
10247d901a1eSXu Liang 		.read_status	= gpy_read_status,
10257d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
10267d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
10277d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
10287d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
10297d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
10307d901a1eSXu Liang 	},
10317d901a1eSXu Liang };
10327d901a1eSXu Liang module_phy_driver(gpy_drivers);
10337d901a1eSXu Liang 
10347d901a1eSXu Liang static struct mdio_device_id __maybe_unused gpy_tbl[] = {
10357d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx)},
10367d901a1eSXu Liang 	{PHY_ID_GPY115B, PHY_ID_GPYx15B_MASK},
10377d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY115C)},
10387d901a1eSXu Liang 	{PHY_ID_GPY211B, PHY_ID_GPY21xB_MASK},
10397d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY211C)},
10407d901a1eSXu Liang 	{PHY_ID_GPY212B, PHY_ID_GPY21xB_MASK},
10417d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY212C)},
10427d901a1eSXu Liang 	{PHY_ID_GPY215B, PHY_ID_GPYx15B_MASK},
10437d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY215C)},
10447d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY241B)},
10457d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY241BM)},
10467d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY245B)},
10477d901a1eSXu Liang 	{ }
10487d901a1eSXu Liang };
10497d901a1eSXu Liang MODULE_DEVICE_TABLE(mdio, gpy_tbl);
10507d901a1eSXu Liang 
10517d901a1eSXu Liang MODULE_DESCRIPTION("Maxlinear Ethernet GPY Driver");
10527d901a1eSXu Liang MODULE_AUTHOR("Xu Liang");
10537d901a1eSXu Liang MODULE_LICENSE("GPL");
1054