xref: /openbmc/linux/drivers/net/phy/mxl-gpy.c (revision 123eaaf6)
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;
110*123eaaf6SRaju Lakkaraju 	u32 wolopts;
1110ba13995SXu Liang 
1120ba13995SXu Liang 	/* It takes 3 seconds to fully switch out of loopback mode before
1130ba13995SXu Liang 	 * it can safely re-enter loopback mode. Record the time when
1140ba13995SXu Liang 	 * loopback is disabled. Check and wait if necessary before loopback
1150ba13995SXu Liang 	 * is enabled.
1160ba13995SXu Liang 	 */
1170ba13995SXu Liang 	u64 lb_dis_to;
1181db85870SMichael Walle };
1191db85870SMichael Walle 
1207d901a1eSXu Liang static const struct {
1211e9aa7baSMichael Walle 	int major;
1227d901a1eSXu Liang 	int minor;
1237d901a1eSXu Liang } ver_need_sgmii_reaneg[] = {
1247d901a1eSXu Liang 	{7, 0x6D},
1257d901a1eSXu Liang 	{8, 0x6D},
1267d901a1eSXu Liang 	{9, 0x73},
1277d901a1eSXu Liang };
1287d901a1eSXu Liang 
12909ce6b20SMichael Walle #if IS_ENABLED(CONFIG_HWMON)
13009ce6b20SMichael Walle /* The original translation formulae of the temperature (in degrees of Celsius)
13109ce6b20SMichael Walle  * are as follows:
13209ce6b20SMichael Walle  *
13309ce6b20SMichael Walle  *   T = -2.5761e-11*(N^4) + 9.7332e-8*(N^3) + -1.9165e-4*(N^2) +
13409ce6b20SMichael Walle  *       3.0762e-1*(N^1) + -5.2156e1
13509ce6b20SMichael Walle  *
13609ce6b20SMichael Walle  * where [-52.156, 137.961]C and N = [0, 1023].
13709ce6b20SMichael Walle  *
13809ce6b20SMichael Walle  * They must be accordingly altered to be suitable for the integer arithmetics.
13909ce6b20SMichael Walle  * The technique is called 'factor redistribution', which just makes sure the
14009ce6b20SMichael Walle  * multiplications and divisions are made so to have a result of the operations
14109ce6b20SMichael Walle  * within the integer numbers limit. In addition we need to translate the
14209ce6b20SMichael Walle  * formulae to accept millidegrees of Celsius. Here what it looks like after
14309ce6b20SMichael Walle  * the alterations:
14409ce6b20SMichael Walle  *
14509ce6b20SMichael Walle  *   T = -25761e-12*(N^4) + 97332e-9*(N^3) + -191650e-6*(N^2) +
14609ce6b20SMichael Walle  *       307620e-3*(N^1) + -52156
14709ce6b20SMichael Walle  *
14809ce6b20SMichael Walle  * where T = [-52156, 137961]mC and N = [0, 1023].
14909ce6b20SMichael Walle  */
15009ce6b20SMichael Walle static const struct polynomial poly_N_to_temp = {
15109ce6b20SMichael Walle 	.terms = {
15209ce6b20SMichael Walle 		{4,  -25761, 1000, 1},
15309ce6b20SMichael Walle 		{3,   97332, 1000, 1},
15409ce6b20SMichael Walle 		{2, -191650, 1000, 1},
15509ce6b20SMichael Walle 		{1,  307620, 1000, 1},
15609ce6b20SMichael Walle 		{0,  -52156,    1, 1}
15709ce6b20SMichael Walle 	}
15809ce6b20SMichael Walle };
15909ce6b20SMichael Walle 
gpy_hwmon_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * value)16009ce6b20SMichael Walle static int gpy_hwmon_read(struct device *dev,
16109ce6b20SMichael Walle 			  enum hwmon_sensor_types type,
16209ce6b20SMichael Walle 			  u32 attr, int channel, long *value)
16309ce6b20SMichael Walle {
16409ce6b20SMichael Walle 	struct phy_device *phydev = dev_get_drvdata(dev);
16509ce6b20SMichael Walle 	int ret;
16609ce6b20SMichael Walle 
167343a5d35SMichael Walle 	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_TEMP_STA);
16809ce6b20SMichael Walle 	if (ret < 0)
16909ce6b20SMichael Walle 		return ret;
17009ce6b20SMichael Walle 	if (!ret)
17109ce6b20SMichael Walle 		return -ENODATA;
17209ce6b20SMichael Walle 
17309ce6b20SMichael Walle 	*value = polynomial_calc(&poly_N_to_temp,
174343a5d35SMichael Walle 				 FIELD_GET(VSPEC1_TEMP_STA_DATA, ret));
17509ce6b20SMichael Walle 
17609ce6b20SMichael Walle 	return 0;
17709ce6b20SMichael Walle }
17809ce6b20SMichael Walle 
gpy_hwmon_is_visible(const void * data,enum hwmon_sensor_types type,u32 attr,int channel)17909ce6b20SMichael Walle static umode_t gpy_hwmon_is_visible(const void *data,
18009ce6b20SMichael Walle 				    enum hwmon_sensor_types type,
18109ce6b20SMichael Walle 				    u32 attr, int channel)
18209ce6b20SMichael Walle {
18309ce6b20SMichael Walle 	return 0444;
18409ce6b20SMichael Walle }
18509ce6b20SMichael Walle 
1862ed84c0cSKrzysztof Kozlowski static const struct hwmon_channel_info * const gpy_hwmon_info[] = {
18709ce6b20SMichael Walle 	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
18809ce6b20SMichael Walle 	NULL
18909ce6b20SMichael Walle };
19009ce6b20SMichael Walle 
19109ce6b20SMichael Walle static const struct hwmon_ops gpy_hwmon_hwmon_ops = {
19209ce6b20SMichael Walle 	.is_visible	= gpy_hwmon_is_visible,
19309ce6b20SMichael Walle 	.read		= gpy_hwmon_read,
19409ce6b20SMichael Walle };
19509ce6b20SMichael Walle 
19609ce6b20SMichael Walle static const struct hwmon_chip_info gpy_hwmon_chip_info = {
19709ce6b20SMichael Walle 	.ops		= &gpy_hwmon_hwmon_ops,
19809ce6b20SMichael Walle 	.info		= gpy_hwmon_info,
19909ce6b20SMichael Walle };
20009ce6b20SMichael Walle 
gpy_hwmon_register(struct phy_device * phydev)20109ce6b20SMichael Walle static int gpy_hwmon_register(struct phy_device *phydev)
20209ce6b20SMichael Walle {
20309ce6b20SMichael Walle 	struct device *dev = &phydev->mdio.dev;
20409ce6b20SMichael Walle 	struct device *hwmon_dev;
20509ce6b20SMichael Walle 	char *hwmon_name;
20609ce6b20SMichael Walle 
20709ce6b20SMichael Walle 	hwmon_name = devm_hwmon_sanitize_name(dev, dev_name(dev));
20809ce6b20SMichael Walle 	if (IS_ERR(hwmon_name))
20909ce6b20SMichael Walle 		return PTR_ERR(hwmon_name);
21009ce6b20SMichael Walle 
21109ce6b20SMichael Walle 	hwmon_dev = devm_hwmon_device_register_with_info(dev, hwmon_name,
21209ce6b20SMichael Walle 							 phydev,
21309ce6b20SMichael Walle 							 &gpy_hwmon_chip_info,
21409ce6b20SMichael Walle 							 NULL);
21509ce6b20SMichael Walle 
21609ce6b20SMichael Walle 	return PTR_ERR_OR_ZERO(hwmon_dev);
21709ce6b20SMichael Walle }
21809ce6b20SMichael Walle #else
gpy_hwmon_register(struct phy_device * phydev)21909ce6b20SMichael Walle static int gpy_hwmon_register(struct phy_device *phydev)
22009ce6b20SMichael Walle {
22109ce6b20SMichael Walle 	return 0;
22209ce6b20SMichael Walle }
22309ce6b20SMichael Walle #endif
22409ce6b20SMichael Walle 
gpy_ack_interrupt(struct phy_device * phydev)225*123eaaf6SRaju Lakkaraju static int gpy_ack_interrupt(struct phy_device *phydev)
226*123eaaf6SRaju Lakkaraju {
227*123eaaf6SRaju Lakkaraju 	int ret;
228*123eaaf6SRaju Lakkaraju 
229*123eaaf6SRaju Lakkaraju 	/* Clear all pending interrupts */
230*123eaaf6SRaju Lakkaraju 	ret = phy_read(phydev, PHY_ISTAT);
231*123eaaf6SRaju Lakkaraju 	return ret < 0 ? ret : 0;
232*123eaaf6SRaju Lakkaraju }
233*123eaaf6SRaju Lakkaraju 
gpy_mbox_read(struct phy_device * phydev,u32 addr)2345f4d487dSMichael Walle static int gpy_mbox_read(struct phy_device *phydev, u32 addr)
2355f4d487dSMichael Walle {
2365f4d487dSMichael Walle 	struct gpy_priv *priv = phydev->priv;
2375f4d487dSMichael Walle 	int val, ret;
2385f4d487dSMichael Walle 	u16 cmd;
2395f4d487dSMichael Walle 
2405f4d487dSMichael Walle 	mutex_lock(&priv->mbox_lock);
2415f4d487dSMichael Walle 
2425f4d487dSMichael Walle 	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_ADDRLO,
2435f4d487dSMichael Walle 			    addr);
2445f4d487dSMichael Walle 	if (ret)
2455f4d487dSMichael Walle 		goto out;
2465f4d487dSMichael Walle 
2475f4d487dSMichael Walle 	cmd = VSPEC1_MBOX_CMD_RD;
2485f4d487dSMichael Walle 	cmd |= FIELD_PREP(VSPEC1_MBOX_CMD_ADDRHI, addr >> 16);
2495f4d487dSMichael Walle 
2505f4d487dSMichael Walle 	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_CMD, cmd);
2515f4d487dSMichael Walle 	if (ret)
2525f4d487dSMichael Walle 		goto out;
2535f4d487dSMichael Walle 
2545f4d487dSMichael Walle 	/* The mbox read is used in the interrupt workaround. It was observed
2555f4d487dSMichael Walle 	 * that a read might take up to 2.5ms. This is also the time for which
2565f4d487dSMichael Walle 	 * the interrupt line is stuck low. To be on the safe side, poll the
2575f4d487dSMichael Walle 	 * ready bit for 10ms.
2585f4d487dSMichael Walle 	 */
2595f4d487dSMichael Walle 	ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
2605f4d487dSMichael Walle 					VSPEC1_MBOX_CMD, val,
2615f4d487dSMichael Walle 					(val & VSPEC1_MBOX_CMD_READY),
2625f4d487dSMichael Walle 					500, 10000, false);
2635f4d487dSMichael Walle 	if (ret)
2645f4d487dSMichael Walle 		goto out;
2655f4d487dSMichael Walle 
2665f4d487dSMichael Walle 	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_DATA);
2675f4d487dSMichael Walle 
2685f4d487dSMichael Walle out:
2695f4d487dSMichael Walle 	mutex_unlock(&priv->mbox_lock);
2705f4d487dSMichael Walle 	return ret;
2715f4d487dSMichael Walle }
2725f4d487dSMichael Walle 
gpy_config_init(struct phy_device * phydev)2737d901a1eSXu Liang static int gpy_config_init(struct phy_device *phydev)
2747d901a1eSXu Liang {
275*123eaaf6SRaju Lakkaraju 	/* Nothing to configure. Configuration Requirement Placeholder */
276*123eaaf6SRaju Lakkaraju 	return 0;
2777d901a1eSXu Liang }
2787d901a1eSXu Liang 
gpy_probe(struct phy_device * phydev)2797d901a1eSXu Liang static int gpy_probe(struct phy_device *phydev)
2807d901a1eSXu Liang {
2811db85870SMichael Walle 	struct device *dev = &phydev->mdio.dev;
2821db85870SMichael Walle 	struct gpy_priv *priv;
283fc3dd036SMichael Walle 	int fw_version;
2847d901a1eSXu Liang 	int ret;
2857d901a1eSXu Liang 
2867d901a1eSXu Liang 	if (!phydev->is_c45) {
2877d901a1eSXu Liang 		ret = phy_get_c45_ids(phydev);
2887d901a1eSXu Liang 		if (ret < 0)
2897d901a1eSXu Liang 			return ret;
2907d901a1eSXu Liang 	}
2917d901a1eSXu Liang 
2921db85870SMichael Walle 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
2931db85870SMichael Walle 	if (!priv)
2941db85870SMichael Walle 		return -ENOMEM;
2951db85870SMichael Walle 	phydev->priv = priv;
2965f4d487dSMichael Walle 	mutex_init(&priv->mbox_lock);
2971db85870SMichael Walle 
298519d6487SXu Liang 	if (!device_property_present(dev, "maxlinear,use-broken-interrupts"))
29997a89ed1SMichael Walle 		phydev->dev_flags |= PHY_F_NO_IRQ;
30097a89ed1SMichael Walle 
301fc3dd036SMichael Walle 	fw_version = phy_read(phydev, PHY_FWV);
302fc3dd036SMichael Walle 	if (fw_version < 0)
303fc3dd036SMichael Walle 		return fw_version;
3041e9aa7baSMichael Walle 	priv->fw_major = FIELD_GET(PHY_FWV_MAJOR_MASK, fw_version);
3051db85870SMichael Walle 	priv->fw_minor = FIELD_GET(PHY_FWV_MINOR_MASK, fw_version);
3067d901a1eSXu Liang 
30709ce6b20SMichael Walle 	ret = gpy_hwmon_register(phydev);
30809ce6b20SMichael Walle 	if (ret)
30909ce6b20SMichael Walle 		return ret;
31009ce6b20SMichael Walle 
3111db85870SMichael Walle 	/* Show GPY PHY FW version in dmesg */
312d523f2ebSMichael Walle 	phydev_info(phydev, "Firmware Version: %d.%d (0x%04X%s)\n",
313d523f2ebSMichael Walle 		    priv->fw_major, priv->fw_minor, fw_version,
314d523f2ebSMichael Walle 		    fw_version & PHY_FWV_REL_MASK ? "" : " test version");
3157d901a1eSXu Liang 
3167d901a1eSXu Liang 	return 0;
3177d901a1eSXu Liang }
3187d901a1eSXu Liang 
gpy_sgmii_need_reaneg(struct phy_device * phydev)3197d901a1eSXu Liang static bool gpy_sgmii_need_reaneg(struct phy_device *phydev)
3207d901a1eSXu Liang {
3211db85870SMichael Walle 	struct gpy_priv *priv = phydev->priv;
3227d901a1eSXu Liang 	size_t i;
3237d901a1eSXu Liang 
3247d901a1eSXu Liang 	for (i = 0; i < ARRAY_SIZE(ver_need_sgmii_reaneg); i++) {
3251e9aa7baSMichael Walle 		if (priv->fw_major != ver_need_sgmii_reaneg[i].major)
3267d901a1eSXu Liang 			continue;
3271db85870SMichael Walle 		if (priv->fw_minor < ver_need_sgmii_reaneg[i].minor)
3287d901a1eSXu Liang 			return true;
3297d901a1eSXu Liang 		break;
3307d901a1eSXu Liang 	}
3317d901a1eSXu Liang 
3327d901a1eSXu Liang 	return false;
3337d901a1eSXu Liang }
3347d901a1eSXu Liang 
gpy_2500basex_chk(struct phy_device * phydev)3357d901a1eSXu Liang static bool gpy_2500basex_chk(struct phy_device *phydev)
3367d901a1eSXu Liang {
3377d901a1eSXu Liang 	int ret;
3387d901a1eSXu Liang 
3397d901a1eSXu Liang 	ret = phy_read(phydev, PHY_MIISTAT);
3407d901a1eSXu Liang 	if (ret < 0) {
3417d901a1eSXu Liang 		phydev_err(phydev, "Error: MDIO register access failed: %d\n",
3427d901a1eSXu Liang 			   ret);
3437d901a1eSXu Liang 		return false;
3447d901a1eSXu Liang 	}
3457d901a1eSXu Liang 
3467d901a1eSXu Liang 	if (!(ret & PHY_MIISTAT_LS) ||
3477d901a1eSXu Liang 	    FIELD_GET(PHY_MIISTAT_SPD_MASK, ret) != PHY_MIISTAT_SPD_2500)
3487d901a1eSXu Liang 		return false;
3497d901a1eSXu Liang 
3507d901a1eSXu Liang 	phydev->speed = SPEED_2500;
3517d901a1eSXu Liang 	phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
3527d901a1eSXu Liang 	phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
3537d901a1eSXu Liang 		       VSPEC1_SGMII_CTRL_ANEN, 0);
3547d901a1eSXu Liang 	return true;
3557d901a1eSXu Liang }
3567d901a1eSXu Liang 
gpy_sgmii_aneg_en(struct phy_device * phydev)3577d901a1eSXu Liang static bool gpy_sgmii_aneg_en(struct phy_device *phydev)
3587d901a1eSXu Liang {
3597d901a1eSXu Liang 	int ret;
3607d901a1eSXu Liang 
3617d901a1eSXu Liang 	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL);
3627d901a1eSXu Liang 	if (ret < 0) {
3637d901a1eSXu Liang 		phydev_err(phydev, "Error: MMD register access failed: %d\n",
3647d901a1eSXu Liang 			   ret);
3657d901a1eSXu Liang 		return true;
3667d901a1eSXu Liang 	}
3677d901a1eSXu Liang 
3687d901a1eSXu Liang 	return (ret & VSPEC1_SGMII_CTRL_ANEN) ? true : false;
3697d901a1eSXu Liang }
3707d901a1eSXu Liang 
gpy_config_mdix(struct phy_device * phydev,u8 ctrl)371fd8825cdSRaju Lakkaraju static int gpy_config_mdix(struct phy_device *phydev, u8 ctrl)
372fd8825cdSRaju Lakkaraju {
373fd8825cdSRaju Lakkaraju 	int ret;
374fd8825cdSRaju Lakkaraju 	u16 val;
375fd8825cdSRaju Lakkaraju 
376fd8825cdSRaju Lakkaraju 	switch (ctrl) {
377fd8825cdSRaju Lakkaraju 	case ETH_TP_MDI_AUTO:
378fd8825cdSRaju Lakkaraju 		val = PHY_CTL1_AMDIX;
379fd8825cdSRaju Lakkaraju 		break;
380fd8825cdSRaju Lakkaraju 	case ETH_TP_MDI_X:
381fd8825cdSRaju Lakkaraju 		val = (PHY_CTL1_MDIAB | PHY_CTL1_MDICD);
382fd8825cdSRaju Lakkaraju 		break;
383fd8825cdSRaju Lakkaraju 	case ETH_TP_MDI:
384fd8825cdSRaju Lakkaraju 		val = 0;
385fd8825cdSRaju Lakkaraju 		break;
386fd8825cdSRaju Lakkaraju 	default:
387fd8825cdSRaju Lakkaraju 		return 0;
388fd8825cdSRaju Lakkaraju 	}
389fd8825cdSRaju Lakkaraju 
390fd8825cdSRaju Lakkaraju 	ret =  phy_modify(phydev, PHY_CTL1, PHY_CTL1_AMDIX | PHY_CTL1_MDIAB |
391fd8825cdSRaju Lakkaraju 			  PHY_CTL1_MDICD, val);
392fd8825cdSRaju Lakkaraju 	if (ret < 0)
393fd8825cdSRaju Lakkaraju 		return ret;
394fd8825cdSRaju Lakkaraju 
395fd8825cdSRaju Lakkaraju 	return genphy_c45_restart_aneg(phydev);
396fd8825cdSRaju Lakkaraju }
397fd8825cdSRaju Lakkaraju 
gpy_config_aneg(struct phy_device * phydev)3987d901a1eSXu Liang static int gpy_config_aneg(struct phy_device *phydev)
3997d901a1eSXu Liang {
4007d901a1eSXu Liang 	bool changed = false;
4017d901a1eSXu Liang 	u32 adv;
4027d901a1eSXu Liang 	int ret;
4037d901a1eSXu Liang 
4047d901a1eSXu Liang 	if (phydev->autoneg == AUTONEG_DISABLE) {
4057d901a1eSXu Liang 		/* Configure half duplex with genphy_setup_forced,
4067d901a1eSXu Liang 		 * because genphy_c45_pma_setup_forced does not support.
4077d901a1eSXu Liang 		 */
4087d901a1eSXu Liang 		return phydev->duplex != DUPLEX_FULL
4097d901a1eSXu Liang 			? genphy_setup_forced(phydev)
4107d901a1eSXu Liang 			: genphy_c45_pma_setup_forced(phydev);
4117d901a1eSXu Liang 	}
4127d901a1eSXu Liang 
413fd8825cdSRaju Lakkaraju 	ret = gpy_config_mdix(phydev,  phydev->mdix_ctrl);
414fd8825cdSRaju Lakkaraju 	if (ret < 0)
415fd8825cdSRaju Lakkaraju 		return ret;
416fd8825cdSRaju Lakkaraju 
4177d901a1eSXu Liang 	ret = genphy_c45_an_config_aneg(phydev);
4187d901a1eSXu Liang 	if (ret < 0)
4197d901a1eSXu Liang 		return ret;
4207d901a1eSXu Liang 	if (ret > 0)
4217d901a1eSXu Liang 		changed = true;
4227d901a1eSXu Liang 
4237d901a1eSXu Liang 	adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
4247d901a1eSXu Liang 	ret = phy_modify_changed(phydev, MII_CTRL1000,
4257d901a1eSXu Liang 				 ADVERTISE_1000FULL | ADVERTISE_1000HALF,
4267d901a1eSXu Liang 				 adv);
4277d901a1eSXu Liang 	if (ret < 0)
4287d901a1eSXu Liang 		return ret;
4297d901a1eSXu Liang 	if (ret > 0)
4307d901a1eSXu Liang 		changed = true;
4317d901a1eSXu Liang 
4327d901a1eSXu Liang 	ret = genphy_c45_check_and_restart_aneg(phydev, changed);
4337d901a1eSXu Liang 	if (ret < 0)
4347d901a1eSXu Liang 		return ret;
4357d901a1eSXu Liang 
4367d901a1eSXu Liang 	if (phydev->interface == PHY_INTERFACE_MODE_USXGMII ||
4377d901a1eSXu Liang 	    phydev->interface == PHY_INTERFACE_MODE_INTERNAL)
4387d901a1eSXu Liang 		return 0;
4397d901a1eSXu Liang 
4407d901a1eSXu Liang 	/* No need to trigger re-ANEG if link speed is 2.5G or SGMII ANEG is
4417d901a1eSXu Liang 	 * disabled.
4427d901a1eSXu Liang 	 */
4437d901a1eSXu Liang 	if (!gpy_sgmii_need_reaneg(phydev) || gpy_2500basex_chk(phydev) ||
4447d901a1eSXu Liang 	    !gpy_sgmii_aneg_en(phydev))
4457d901a1eSXu Liang 		return 0;
4467d901a1eSXu Liang 
4477d901a1eSXu Liang 	/* There is a design constraint in GPY2xx device where SGMII AN is
4487d901a1eSXu Liang 	 * only triggered when there is change of speed. If, PHY link
4497d901a1eSXu Liang 	 * partner`s speed is still same even after PHY TPI is down and up
4507d901a1eSXu Liang 	 * again, SGMII AN is not triggered and hence no new in-band message
4517d901a1eSXu Liang 	 * from GPY to MAC side SGMII.
4527d901a1eSXu Liang 	 * This could cause an issue during power up, when PHY is up prior to
4537d901a1eSXu Liang 	 * MAC. At this condition, once MAC side SGMII is up, MAC side SGMII
4547d901a1eSXu Liang 	 * wouldn`t receive new in-band message from GPY with correct link
4557d901a1eSXu Liang 	 * status, speed and duplex info.
4567d901a1eSXu Liang 	 *
4577d901a1eSXu Liang 	 * 1) If PHY is already up and TPI link status is still down (such as
4587d901a1eSXu Liang 	 *    hard reboot), TPI link status is polled for 4 seconds before
4597d901a1eSXu Liang 	 *    retriggerring SGMII AN.
4607d901a1eSXu Liang 	 * 2) If PHY is already up and TPI link status is also up (such as soft
4617d901a1eSXu Liang 	 *    reboot), polling of TPI link status is not needed and SGMII AN is
4627d901a1eSXu Liang 	 *    immediately retriggered.
4637d901a1eSXu Liang 	 * 3) Other conditions such as PHY is down, speed change etc, skip
4647d901a1eSXu Liang 	 *    retriggering SGMII AN. Note: in case of speed change, GPY FW will
4657d901a1eSXu Liang 	 *    initiate SGMII AN.
4667d901a1eSXu Liang 	 */
4677d901a1eSXu Liang 
4687d901a1eSXu Liang 	if (phydev->state != PHY_UP)
4697d901a1eSXu Liang 		return 0;
4707d901a1eSXu Liang 
4717d901a1eSXu Liang 	ret = phy_read_poll_timeout(phydev, MII_BMSR, ret, ret & BMSR_LSTATUS,
4727d901a1eSXu Liang 				    20000, 4000000, false);
4737d901a1eSXu Liang 	if (ret == -ETIMEDOUT)
4747d901a1eSXu Liang 		return 0;
4757d901a1eSXu Liang 	else if (ret < 0)
4767d901a1eSXu Liang 		return ret;
4777d901a1eSXu Liang 
4787d901a1eSXu Liang 	/* Trigger SGMII AN. */
4797d901a1eSXu Liang 	return phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
4807d901a1eSXu Liang 			      VSPEC1_SGMII_CTRL_ANRS, VSPEC1_SGMII_CTRL_ANRS);
4817d901a1eSXu Liang }
4827d901a1eSXu Liang 
gpy_update_mdix(struct phy_device * phydev)483fd8825cdSRaju Lakkaraju static int gpy_update_mdix(struct phy_device *phydev)
484fd8825cdSRaju Lakkaraju {
485fd8825cdSRaju Lakkaraju 	int ret;
486fd8825cdSRaju Lakkaraju 
487fd8825cdSRaju Lakkaraju 	ret = phy_read(phydev, PHY_CTL1);
488fd8825cdSRaju Lakkaraju 	if (ret < 0)
489fd8825cdSRaju Lakkaraju 		return ret;
490fd8825cdSRaju Lakkaraju 
491fd8825cdSRaju Lakkaraju 	if (ret & PHY_CTL1_AMDIX)
492fd8825cdSRaju Lakkaraju 		phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
493fd8825cdSRaju Lakkaraju 	else
494fd8825cdSRaju Lakkaraju 		if (ret & PHY_CTL1_MDICD || ret & PHY_CTL1_MDIAB)
495fd8825cdSRaju Lakkaraju 			phydev->mdix_ctrl = ETH_TP_MDI_X;
496fd8825cdSRaju Lakkaraju 		else
497fd8825cdSRaju Lakkaraju 			phydev->mdix_ctrl = ETH_TP_MDI;
498fd8825cdSRaju Lakkaraju 
499fd8825cdSRaju Lakkaraju 	ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, PHY_PMA_MGBT_POLARITY);
500fd8825cdSRaju Lakkaraju 	if (ret < 0)
501fd8825cdSRaju Lakkaraju 		return ret;
502fd8825cdSRaju Lakkaraju 
503fd8825cdSRaju Lakkaraju 	if ((ret & PHY_MDI_MDI_X_MASK) < PHY_MDI_MDI_X_NORMAL)
504fd8825cdSRaju Lakkaraju 		phydev->mdix = ETH_TP_MDI_X;
505fd8825cdSRaju Lakkaraju 	else
506fd8825cdSRaju Lakkaraju 		phydev->mdix = ETH_TP_MDI;
507fd8825cdSRaju Lakkaraju 
508fd8825cdSRaju Lakkaraju 	return 0;
509fd8825cdSRaju Lakkaraju }
510fd8825cdSRaju Lakkaraju 
gpy_update_interface(struct phy_device * phydev)5117a495ddeSRaju Lakkaraju static int gpy_update_interface(struct phy_device *phydev)
5127d901a1eSXu Liang {
5137d901a1eSXu Liang 	int ret;
5147d901a1eSXu Liang 
5157d901a1eSXu Liang 	/* Interface mode is fixed for USXGMII and integrated PHY */
5167d901a1eSXu Liang 	if (phydev->interface == PHY_INTERFACE_MODE_USXGMII ||
5177d901a1eSXu Liang 	    phydev->interface == PHY_INTERFACE_MODE_INTERNAL)
5187a495ddeSRaju Lakkaraju 		return -EINVAL;
5197d901a1eSXu Liang 
5207d901a1eSXu Liang 	/* Automatically switch SERDES interface between SGMII and 2500-BaseX
5217d901a1eSXu Liang 	 * according to speed. Disable ANEG in 2500-BaseX mode.
5227d901a1eSXu Liang 	 */
5237d901a1eSXu Liang 	switch (phydev->speed) {
5247d901a1eSXu Liang 	case SPEED_2500:
5257d901a1eSXu Liang 		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
5267d901a1eSXu Liang 		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
5277d901a1eSXu Liang 				     VSPEC1_SGMII_CTRL_ANEN, 0);
5287a495ddeSRaju Lakkaraju 		if (ret < 0) {
5297d901a1eSXu Liang 			phydev_err(phydev,
5307d901a1eSXu Liang 				   "Error: Disable of SGMII ANEG failed: %d\n",
5317d901a1eSXu Liang 				   ret);
5327a495ddeSRaju Lakkaraju 			return ret;
5337a495ddeSRaju Lakkaraju 		}
5347d901a1eSXu Liang 		break;
5357d901a1eSXu Liang 	case SPEED_1000:
5367d901a1eSXu Liang 	case SPEED_100:
5377d901a1eSXu Liang 	case SPEED_10:
5387d901a1eSXu Liang 		phydev->interface = PHY_INTERFACE_MODE_SGMII;
5397d901a1eSXu Liang 		if (gpy_sgmii_aneg_en(phydev))
5407d901a1eSXu Liang 			break;
5417d901a1eSXu Liang 		/* Enable and restart SGMII ANEG for 10/100/1000Mbps link speed
5427d901a1eSXu Liang 		 * if ANEG is disabled (in 2500-BaseX mode).
5437d901a1eSXu Liang 		 */
5447d901a1eSXu Liang 		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
5457d901a1eSXu Liang 				     VSPEC1_SGMII_ANEN_ANRS,
5467d901a1eSXu Liang 				     VSPEC1_SGMII_ANEN_ANRS);
5477a495ddeSRaju Lakkaraju 		if (ret < 0) {
5487d901a1eSXu Liang 			phydev_err(phydev,
5497d901a1eSXu Liang 				   "Error: Enable of SGMII ANEG failed: %d\n",
5507d901a1eSXu Liang 				   ret);
5517a495ddeSRaju Lakkaraju 			return ret;
5527a495ddeSRaju Lakkaraju 		}
5537d901a1eSXu Liang 		break;
5547d901a1eSXu Liang 	}
555311abcddSRaju Lakkaraju 
5567a495ddeSRaju Lakkaraju 	if (phydev->speed == SPEED_2500 || phydev->speed == SPEED_1000) {
5577a495ddeSRaju Lakkaraju 		ret = genphy_read_master_slave(phydev);
5587a495ddeSRaju Lakkaraju 		if (ret < 0)
5597a495ddeSRaju Lakkaraju 			return ret;
5607a495ddeSRaju Lakkaraju 	}
5617a495ddeSRaju Lakkaraju 
562fd8825cdSRaju Lakkaraju 	return gpy_update_mdix(phydev);
5637d901a1eSXu Liang }
5647d901a1eSXu Liang 
gpy_read_status(struct phy_device * phydev)5657d901a1eSXu Liang static int gpy_read_status(struct phy_device *phydev)
5667d901a1eSXu Liang {
5677d901a1eSXu Liang 	int ret;
5687d901a1eSXu Liang 
5697d901a1eSXu Liang 	ret = genphy_update_link(phydev);
5707d901a1eSXu Liang 	if (ret)
5717d901a1eSXu Liang 		return ret;
5727d901a1eSXu Liang 
5737d901a1eSXu Liang 	phydev->speed = SPEED_UNKNOWN;
5747d901a1eSXu Liang 	phydev->duplex = DUPLEX_UNKNOWN;
5757d901a1eSXu Liang 	phydev->pause = 0;
5767d901a1eSXu Liang 	phydev->asym_pause = 0;
5777d901a1eSXu Liang 
5787d901a1eSXu Liang 	if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
5797d901a1eSXu Liang 		ret = genphy_c45_read_lpa(phydev);
5807d901a1eSXu Liang 		if (ret < 0)
5817d901a1eSXu Liang 			return ret;
5827d901a1eSXu Liang 
5837d901a1eSXu Liang 		/* Read the link partner's 1G advertisement */
5847d901a1eSXu Liang 		ret = phy_read(phydev, MII_STAT1000);
5857d901a1eSXu Liang 		if (ret < 0)
5867d901a1eSXu Liang 			return ret;
5877d901a1eSXu Liang 		mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, ret);
5887d901a1eSXu Liang 	} else if (phydev->autoneg == AUTONEG_DISABLE) {
5897d901a1eSXu Liang 		linkmode_zero(phydev->lp_advertising);
5907d901a1eSXu Liang 	}
5917d901a1eSXu Liang 
5927d901a1eSXu Liang 	ret = phy_read(phydev, PHY_MIISTAT);
5937d901a1eSXu Liang 	if (ret < 0)
5947d901a1eSXu Liang 		return ret;
5957d901a1eSXu Liang 
5967d901a1eSXu Liang 	phydev->link = (ret & PHY_MIISTAT_LS) ? 1 : 0;
5977d901a1eSXu Liang 	phydev->duplex = (ret & PHY_MIISTAT_DPX) ? DUPLEX_FULL : DUPLEX_HALF;
5987d901a1eSXu Liang 	switch (FIELD_GET(PHY_MIISTAT_SPD_MASK, ret)) {
5997d901a1eSXu Liang 	case PHY_MIISTAT_SPD_10:
6007d901a1eSXu Liang 		phydev->speed = SPEED_10;
6017d901a1eSXu Liang 		break;
6027d901a1eSXu Liang 	case PHY_MIISTAT_SPD_100:
6037d901a1eSXu Liang 		phydev->speed = SPEED_100;
6047d901a1eSXu Liang 		break;
6057d901a1eSXu Liang 	case PHY_MIISTAT_SPD_1000:
6067d901a1eSXu Liang 		phydev->speed = SPEED_1000;
6077d901a1eSXu Liang 		break;
6087d901a1eSXu Liang 	case PHY_MIISTAT_SPD_2500:
6097d901a1eSXu Liang 		phydev->speed = SPEED_2500;
6107d901a1eSXu Liang 		break;
6117d901a1eSXu Liang 	}
6127d901a1eSXu Liang 
6137a495ddeSRaju Lakkaraju 	if (phydev->link) {
6147a495ddeSRaju Lakkaraju 		ret = gpy_update_interface(phydev);
6157a495ddeSRaju Lakkaraju 		if (ret < 0)
6167a495ddeSRaju Lakkaraju 			return ret;
6177a495ddeSRaju Lakkaraju 	}
6187d901a1eSXu Liang 
6197d901a1eSXu Liang 	return 0;
6207d901a1eSXu Liang }
6217d901a1eSXu Liang 
gpy_config_intr(struct phy_device * phydev)6227d901a1eSXu Liang static int gpy_config_intr(struct phy_device *phydev)
6237d901a1eSXu Liang {
624*123eaaf6SRaju Lakkaraju 	struct gpy_priv *priv = phydev->priv;
6257d901a1eSXu Liang 	u16 mask = 0;
626*123eaaf6SRaju Lakkaraju 	int ret;
627*123eaaf6SRaju Lakkaraju 
628*123eaaf6SRaju Lakkaraju 	ret = gpy_ack_interrupt(phydev);
629*123eaaf6SRaju Lakkaraju 	if (ret)
630*123eaaf6SRaju Lakkaraju 		return ret;
6317d901a1eSXu Liang 
6327d901a1eSXu Liang 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
6337d901a1eSXu Liang 		mask = PHY_IMASK_MASK;
6347d901a1eSXu Liang 
635*123eaaf6SRaju Lakkaraju 	if (priv->wolopts & WAKE_MAGIC)
636*123eaaf6SRaju Lakkaraju 		mask |= PHY_IMASK_WOL;
637*123eaaf6SRaju Lakkaraju 
638*123eaaf6SRaju Lakkaraju 	if (priv->wolopts & WAKE_PHY)
639*123eaaf6SRaju Lakkaraju 		mask |= PHY_IMASK_LSTC;
640*123eaaf6SRaju Lakkaraju 
6417d901a1eSXu Liang 	return phy_write(phydev, PHY_IMASK, mask);
6427d901a1eSXu Liang }
6437d901a1eSXu Liang 
gpy_handle_interrupt(struct phy_device * phydev)6447d901a1eSXu Liang static irqreturn_t gpy_handle_interrupt(struct phy_device *phydev)
6457d901a1eSXu Liang {
6467d901a1eSXu Liang 	int reg;
6477d901a1eSXu Liang 
6487d901a1eSXu Liang 	reg = phy_read(phydev, PHY_ISTAT);
6497d901a1eSXu Liang 	if (reg < 0) {
6507d901a1eSXu Liang 		phy_error(phydev);
6517d901a1eSXu Liang 		return IRQ_NONE;
6527d901a1eSXu Liang 	}
6537d901a1eSXu Liang 
6547d901a1eSXu Liang 	if (!(reg & PHY_IMASK_MASK))
6557d901a1eSXu Liang 		return IRQ_NONE;
6567d901a1eSXu Liang 
6575f4d487dSMichael Walle 	/* The PHY might leave the interrupt line asserted even after PHY_ISTAT
6585f4d487dSMichael Walle 	 * is read. To avoid interrupt storms, delay the interrupt handling as
6595f4d487dSMichael Walle 	 * long as the PHY drives the interrupt line. An internal bus read will
6605f4d487dSMichael Walle 	 * stall as long as the interrupt line is asserted, thus just read a
6615f4d487dSMichael Walle 	 * random register here.
6625f4d487dSMichael Walle 	 * Because we cannot access the internal bus at all while the interrupt
6635f4d487dSMichael Walle 	 * is driven by the PHY, there is no way to make the interrupt line
6645f4d487dSMichael Walle 	 * unstuck (e.g. by changing the pinmux to GPIO input) during that time
6655f4d487dSMichael Walle 	 * frame. Therefore, polling is the best we can do and won't do any more
6665f4d487dSMichael Walle 	 * harm.
6675f4d487dSMichael Walle 	 * It was observed that this bug happens on link state and link speed
668519d6487SXu Liang 	 * changes independent of the firmware version.
6695f4d487dSMichael Walle 	 */
670519d6487SXu Liang 	if (reg & (PHY_IMASK_LSTC | PHY_IMASK_LSPC)) {
6715f4d487dSMichael Walle 		reg = gpy_mbox_read(phydev, REG_GPIO0_OUT);
6725f4d487dSMichael Walle 		if (reg < 0) {
6735f4d487dSMichael Walle 			phy_error(phydev);
6745f4d487dSMichael Walle 			return IRQ_NONE;
6755f4d487dSMichael Walle 		}
6765f4d487dSMichael Walle 	}
6775f4d487dSMichael Walle 
6787d901a1eSXu Liang 	phy_trigger_machine(phydev);
6797d901a1eSXu Liang 
6807d901a1eSXu Liang 	return IRQ_HANDLED;
6817d901a1eSXu Liang }
6827d901a1eSXu Liang 
gpy_set_wol(struct phy_device * phydev,struct ethtool_wolinfo * wol)6837d901a1eSXu Liang static int gpy_set_wol(struct phy_device *phydev,
6847d901a1eSXu Liang 		       struct ethtool_wolinfo *wol)
6857d901a1eSXu Liang {
6867d901a1eSXu Liang 	struct net_device *attach_dev = phydev->attached_dev;
687*123eaaf6SRaju Lakkaraju 	struct gpy_priv *priv = phydev->priv;
6887d901a1eSXu Liang 	int ret;
6897d901a1eSXu Liang 
6907d901a1eSXu Liang 	if (wol->wolopts & WAKE_MAGIC) {
6917d901a1eSXu Liang 		/* MAC address - Byte0:Byte1:Byte2:Byte3:Byte4:Byte5
6927d901a1eSXu Liang 		 * VPSPEC2_WOL_AD45 = Byte0:Byte1
6937d901a1eSXu Liang 		 * VPSPEC2_WOL_AD23 = Byte2:Byte3
6947d901a1eSXu Liang 		 * VPSPEC2_WOL_AD01 = Byte4:Byte5
6957d901a1eSXu Liang 		 */
6967d901a1eSXu Liang 		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
6977d901a1eSXu Liang 				       VPSPEC2_WOL_AD45,
6987d901a1eSXu Liang 				       ((attach_dev->dev_addr[0] << 8) |
6997d901a1eSXu Liang 				       attach_dev->dev_addr[1]));
7007d901a1eSXu Liang 		if (ret < 0)
7017d901a1eSXu Liang 			return ret;
7027d901a1eSXu Liang 
7037d901a1eSXu Liang 		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
7047d901a1eSXu Liang 				       VPSPEC2_WOL_AD23,
7057d901a1eSXu Liang 				       ((attach_dev->dev_addr[2] << 8) |
7067d901a1eSXu Liang 				       attach_dev->dev_addr[3]));
7077d901a1eSXu Liang 		if (ret < 0)
7087d901a1eSXu Liang 			return ret;
7097d901a1eSXu Liang 
7107d901a1eSXu Liang 		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
7117d901a1eSXu Liang 				       VPSPEC2_WOL_AD01,
7127d901a1eSXu Liang 				       ((attach_dev->dev_addr[4] << 8) |
7137d901a1eSXu Liang 				       attach_dev->dev_addr[5]));
7147d901a1eSXu Liang 		if (ret < 0)
7157d901a1eSXu Liang 			return ret;
7167d901a1eSXu Liang 
7177d901a1eSXu Liang 		/* Enable the WOL interrupt */
7187d901a1eSXu Liang 		ret = phy_write(phydev, PHY_IMASK, PHY_IMASK_WOL);
7197d901a1eSXu Liang 		if (ret < 0)
7207d901a1eSXu Liang 			return ret;
7217d901a1eSXu Liang 
7227d901a1eSXu Liang 		/* Enable magic packet matching */
7237d901a1eSXu Liang 		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
7247d901a1eSXu Liang 				       VPSPEC2_WOL_CTL,
7257d901a1eSXu Liang 				       WOL_EN);
7267d901a1eSXu Liang 		if (ret < 0)
7277d901a1eSXu Liang 			return ret;
7287d901a1eSXu Liang 
7297d901a1eSXu Liang 		/* Clear the interrupt status register.
7307d901a1eSXu Liang 		 * Only WoL is enabled so clear all.
7317d901a1eSXu Liang 		 */
7327d901a1eSXu Liang 		ret = phy_read(phydev, PHY_ISTAT);
7337d901a1eSXu Liang 		if (ret < 0)
7347d901a1eSXu Liang 			return ret;
735*123eaaf6SRaju Lakkaraju 
736*123eaaf6SRaju Lakkaraju 		priv->wolopts |= WAKE_MAGIC;
7377d901a1eSXu Liang 	} else {
7387d901a1eSXu Liang 		/* Disable magic packet matching */
7397d901a1eSXu Liang 		ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
7407d901a1eSXu Liang 					 VPSPEC2_WOL_CTL,
7417d901a1eSXu Liang 					 WOL_EN);
7427d901a1eSXu Liang 		if (ret < 0)
7437d901a1eSXu Liang 			return ret;
744*123eaaf6SRaju Lakkaraju 
745*123eaaf6SRaju Lakkaraju 		/* Disable the WOL interrupt */
746*123eaaf6SRaju Lakkaraju 		ret = phy_clear_bits(phydev, PHY_IMASK, PHY_IMASK_WOL);
747*123eaaf6SRaju Lakkaraju 		if (ret < 0)
748*123eaaf6SRaju Lakkaraju 			return ret;
749*123eaaf6SRaju Lakkaraju 
750*123eaaf6SRaju Lakkaraju 		priv->wolopts &= ~WAKE_MAGIC;
7517d901a1eSXu Liang 	}
7527d901a1eSXu Liang 
7537d901a1eSXu Liang 	if (wol->wolopts & WAKE_PHY) {
7547d901a1eSXu Liang 		/* Enable the link state change interrupt */
7557d901a1eSXu Liang 		ret = phy_set_bits(phydev, PHY_IMASK, PHY_IMASK_LSTC);
7567d901a1eSXu Liang 		if (ret < 0)
7577d901a1eSXu Liang 			return ret;
7587d901a1eSXu Liang 
7597d901a1eSXu Liang 		/* Clear the interrupt status register */
7607d901a1eSXu Liang 		ret = phy_read(phydev, PHY_ISTAT);
7617d901a1eSXu Liang 		if (ret < 0)
7627d901a1eSXu Liang 			return ret;
7637d901a1eSXu Liang 
7647d901a1eSXu Liang 		if (ret & (PHY_IMASK_MASK & ~PHY_IMASK_LSTC))
7657d901a1eSXu Liang 			phy_trigger_machine(phydev);
7667d901a1eSXu Liang 
767*123eaaf6SRaju Lakkaraju 		priv->wolopts |= WAKE_PHY;
7687d901a1eSXu Liang 		return 0;
7697d901a1eSXu Liang 	}
7707d901a1eSXu Liang 
771*123eaaf6SRaju Lakkaraju 	priv->wolopts &= ~WAKE_PHY;
7727d901a1eSXu Liang 	/* Disable the link state change interrupt */
7737d901a1eSXu Liang 	return phy_clear_bits(phydev, PHY_IMASK, PHY_IMASK_LSTC);
7747d901a1eSXu Liang }
7757d901a1eSXu Liang 
gpy_get_wol(struct phy_device * phydev,struct ethtool_wolinfo * wol)7767d901a1eSXu Liang static void gpy_get_wol(struct phy_device *phydev,
7777d901a1eSXu Liang 			struct ethtool_wolinfo *wol)
7787d901a1eSXu Liang {
779*123eaaf6SRaju Lakkaraju 	struct gpy_priv *priv = phydev->priv;
7807d901a1eSXu Liang 
7817d901a1eSXu Liang 	wol->supported = WAKE_MAGIC | WAKE_PHY;
782*123eaaf6SRaju Lakkaraju 	wol->wolopts = priv->wolopts;
7837d901a1eSXu Liang }
7847d901a1eSXu Liang 
gpy_loopback(struct phy_device * phydev,bool enable)7857d901a1eSXu Liang static int gpy_loopback(struct phy_device *phydev, bool enable)
7867d901a1eSXu Liang {
7870ba13995SXu Liang 	struct gpy_priv *priv = phydev->priv;
7880ba13995SXu Liang 	u16 set = 0;
7897d901a1eSXu Liang 	int ret;
7907d901a1eSXu Liang 
7910ba13995SXu Liang 	if (enable) {
7920ba13995SXu Liang 		u64 now = get_jiffies_64();
7930ba13995SXu Liang 
7940ba13995SXu Liang 		/* wait until 3 seconds from last disable */
7950ba13995SXu Liang 		if (time_before64(now, priv->lb_dis_to))
7960ba13995SXu Liang 			msleep(jiffies64_to_msecs(priv->lb_dis_to - now));
7970ba13995SXu Liang 
7980ba13995SXu Liang 		set = BMCR_LOOPBACK;
7997d901a1eSXu Liang 	}
8007d901a1eSXu Liang 
8010ba13995SXu Liang 	ret = phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK, set);
8020ba13995SXu Liang 	if (ret <= 0)
8037d901a1eSXu Liang 		return ret;
8040ba13995SXu Liang 
8050ba13995SXu Liang 	if (enable) {
8060ba13995SXu Liang 		/* It takes some time for PHY device to switch into
8070ba13995SXu Liang 		 * loopback mode.
8080ba13995SXu Liang 		 */
8090ba13995SXu Liang 		msleep(100);
8100ba13995SXu Liang 	} else {
8110ba13995SXu Liang 		priv->lb_dis_to = get_jiffies_64() + HZ * 3;
8120ba13995SXu Liang 	}
8130ba13995SXu Liang 
8140ba13995SXu Liang 	return 0;
8157d901a1eSXu Liang }
8167d901a1eSXu Liang 
gpy115_loopback(struct phy_device * phydev,bool enable)8173b1b6e82SXu Liang static int gpy115_loopback(struct phy_device *phydev, bool enable)
8183b1b6e82SXu Liang {
8191db85870SMichael Walle 	struct gpy_priv *priv = phydev->priv;
8203b1b6e82SXu Liang 
8213b1b6e82SXu Liang 	if (enable)
8223b1b6e82SXu Liang 		return gpy_loopback(phydev, enable);
8233b1b6e82SXu Liang 
8241db85870SMichael Walle 	if (priv->fw_minor > 0x76)
8253b1b6e82SXu Liang 		return gpy_loopback(phydev, 0);
8263b1b6e82SXu Liang 
8273b1b6e82SXu Liang 	return genphy_soft_reset(phydev);
8283b1b6e82SXu Liang }
8293b1b6e82SXu Liang 
8307d901a1eSXu Liang static struct phy_driver gpy_drivers[] = {
8317d901a1eSXu Liang 	{
8327d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx),
8337d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY2xx",
8347d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
8357d901a1eSXu Liang 		.config_init	= gpy_config_init,
8367d901a1eSXu Liang 		.probe		= gpy_probe,
8377d901a1eSXu Liang 		.suspend	= genphy_suspend,
8387d901a1eSXu Liang 		.resume		= genphy_resume,
8397d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
8407d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
8417d901a1eSXu Liang 		.read_status	= gpy_read_status,
8427d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
8437d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
8447d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
8457d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
8467d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
8477d901a1eSXu Liang 	},
8487d901a1eSXu Liang 	{
8497d901a1eSXu Liang 		.phy_id		= PHY_ID_GPY115B,
8507d901a1eSXu Liang 		.phy_id_mask	= PHY_ID_GPYx15B_MASK,
8517d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY115B",
8527d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
8537d901a1eSXu Liang 		.config_init	= gpy_config_init,
8547d901a1eSXu Liang 		.probe		= gpy_probe,
8557d901a1eSXu Liang 		.suspend	= genphy_suspend,
8567d901a1eSXu Liang 		.resume		= genphy_resume,
8577d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
8587d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
8597d901a1eSXu Liang 		.read_status	= gpy_read_status,
8607d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
8617d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
8627d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
8637d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
8643b1b6e82SXu Liang 		.set_loopback	= gpy115_loopback,
8657d901a1eSXu Liang 	},
8667d901a1eSXu Liang 	{
8677d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY115C),
8687d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY115C",
8697d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
8707d901a1eSXu Liang 		.config_init	= gpy_config_init,
8717d901a1eSXu Liang 		.probe		= gpy_probe,
8727d901a1eSXu Liang 		.suspend	= genphy_suspend,
8737d901a1eSXu Liang 		.resume		= genphy_resume,
8747d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
8757d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
8767d901a1eSXu Liang 		.read_status	= gpy_read_status,
8777d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
8787d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
8797d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
8807d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
8813b1b6e82SXu Liang 		.set_loopback	= gpy115_loopback,
8827d901a1eSXu Liang 	},
8837d901a1eSXu Liang 	{
8847d901a1eSXu Liang 		.phy_id		= PHY_ID_GPY211B,
8857d901a1eSXu Liang 		.phy_id_mask	= PHY_ID_GPY21xB_MASK,
8867d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY211B",
8877d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
8887d901a1eSXu Liang 		.config_init	= gpy_config_init,
8897d901a1eSXu Liang 		.probe		= gpy_probe,
8907d901a1eSXu Liang 		.suspend	= genphy_suspend,
8917d901a1eSXu Liang 		.resume		= genphy_resume,
8927d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
8937d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
8947d901a1eSXu Liang 		.read_status	= gpy_read_status,
8957d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
8967d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
8977d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
8987d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
8997d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9007d901a1eSXu Liang 	},
9017d901a1eSXu Liang 	{
9027d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY211C),
9037d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY211C",
9047d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
9057d901a1eSXu Liang 		.config_init	= gpy_config_init,
9067d901a1eSXu Liang 		.probe		= gpy_probe,
9077d901a1eSXu Liang 		.suspend	= genphy_suspend,
9087d901a1eSXu Liang 		.resume		= genphy_resume,
9097d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9107d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9117d901a1eSXu Liang 		.read_status	= gpy_read_status,
9127d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
9137d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
9147d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
9157d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
9167d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9177d901a1eSXu Liang 	},
9187d901a1eSXu Liang 	{
9197d901a1eSXu Liang 		.phy_id		= PHY_ID_GPY212B,
9207d901a1eSXu Liang 		.phy_id_mask	= PHY_ID_GPY21xB_MASK,
9217d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY212B",
9227d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
9237d901a1eSXu Liang 		.config_init	= gpy_config_init,
9247d901a1eSXu Liang 		.probe		= gpy_probe,
9257d901a1eSXu Liang 		.suspend	= genphy_suspend,
9267d901a1eSXu Liang 		.resume		= genphy_resume,
9277d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9287d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9297d901a1eSXu Liang 		.read_status	= gpy_read_status,
9307d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
9317d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
9327d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
9337d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
9347d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9357d901a1eSXu Liang 	},
9367d901a1eSXu Liang 	{
9377d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY212C),
9387d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY212C",
9397d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
9407d901a1eSXu Liang 		.config_init	= gpy_config_init,
9417d901a1eSXu Liang 		.probe		= gpy_probe,
9427d901a1eSXu Liang 		.suspend	= genphy_suspend,
9437d901a1eSXu Liang 		.resume		= genphy_resume,
9447d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9457d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9467d901a1eSXu Liang 		.read_status	= gpy_read_status,
9477d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
9487d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
9497d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
9507d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
9517d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9527d901a1eSXu Liang 	},
9537d901a1eSXu Liang 	{
9547d901a1eSXu Liang 		.phy_id		= PHY_ID_GPY215B,
9557d901a1eSXu Liang 		.phy_id_mask	= PHY_ID_GPYx15B_MASK,
9567d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY215B",
9577d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
9587d901a1eSXu Liang 		.config_init	= gpy_config_init,
9597d901a1eSXu Liang 		.probe		= gpy_probe,
9607d901a1eSXu Liang 		.suspend	= genphy_suspend,
9617d901a1eSXu Liang 		.resume		= genphy_resume,
9627d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9637d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9647d901a1eSXu Liang 		.read_status	= gpy_read_status,
9657d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
9667d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
9677d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
9687d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
9697d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9707d901a1eSXu Liang 	},
9717d901a1eSXu Liang 	{
9727d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY215C),
9737d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY215C",
9747d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
9757d901a1eSXu Liang 		.config_init	= gpy_config_init,
9767d901a1eSXu Liang 		.probe		= gpy_probe,
9777d901a1eSXu Liang 		.suspend	= genphy_suspend,
9787d901a1eSXu Liang 		.resume		= genphy_resume,
9797d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9807d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9817d901a1eSXu Liang 		.read_status	= gpy_read_status,
9827d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
9837d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
9847d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
9857d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
9867d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
9877d901a1eSXu Liang 	},
9887d901a1eSXu Liang 	{
9897d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY241B),
9907d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY241B",
9917d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
9927d901a1eSXu Liang 		.config_init	= gpy_config_init,
9937d901a1eSXu Liang 		.probe		= gpy_probe,
9947d901a1eSXu Liang 		.suspend	= genphy_suspend,
9957d901a1eSXu Liang 		.resume		= genphy_resume,
9967d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
9977d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
9987d901a1eSXu Liang 		.read_status	= gpy_read_status,
9997d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
10007d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
10017d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
10027d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
10037d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
10047d901a1eSXu Liang 	},
10057d901a1eSXu Liang 	{
10067d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY241BM),
10077d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY241BM",
10087d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
10097d901a1eSXu Liang 		.config_init	= gpy_config_init,
10107d901a1eSXu Liang 		.probe		= gpy_probe,
10117d901a1eSXu Liang 		.suspend	= genphy_suspend,
10127d901a1eSXu Liang 		.resume		= genphy_resume,
10137d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
10147d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
10157d901a1eSXu Liang 		.read_status	= gpy_read_status,
10167d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
10177d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
10187d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
10197d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
10207d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
10217d901a1eSXu Liang 	},
10227d901a1eSXu Liang 	{
10237d901a1eSXu Liang 		PHY_ID_MATCH_MODEL(PHY_ID_GPY245B),
10247d901a1eSXu Liang 		.name		= "Maxlinear Ethernet GPY245B",
10257d901a1eSXu Liang 		.get_features	= genphy_c45_pma_read_abilities,
10267d901a1eSXu Liang 		.config_init	= gpy_config_init,
10277d901a1eSXu Liang 		.probe		= gpy_probe,
10287d901a1eSXu Liang 		.suspend	= genphy_suspend,
10297d901a1eSXu Liang 		.resume		= genphy_resume,
10307d901a1eSXu Liang 		.config_aneg	= gpy_config_aneg,
10317d901a1eSXu Liang 		.aneg_done	= genphy_c45_aneg_done,
10327d901a1eSXu Liang 		.read_status	= gpy_read_status,
10337d901a1eSXu Liang 		.config_intr	= gpy_config_intr,
10347d901a1eSXu Liang 		.handle_interrupt = gpy_handle_interrupt,
10357d901a1eSXu Liang 		.set_wol	= gpy_set_wol,
10367d901a1eSXu Liang 		.get_wol	= gpy_get_wol,
10377d901a1eSXu Liang 		.set_loopback	= gpy_loopback,
10387d901a1eSXu Liang 	},
10397d901a1eSXu Liang };
10407d901a1eSXu Liang module_phy_driver(gpy_drivers);
10417d901a1eSXu Liang 
10427d901a1eSXu Liang static struct mdio_device_id __maybe_unused gpy_tbl[] = {
10437d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx)},
10447d901a1eSXu Liang 	{PHY_ID_GPY115B, PHY_ID_GPYx15B_MASK},
10457d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY115C)},
10467d901a1eSXu Liang 	{PHY_ID_GPY211B, PHY_ID_GPY21xB_MASK},
10477d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY211C)},
10487d901a1eSXu Liang 	{PHY_ID_GPY212B, PHY_ID_GPY21xB_MASK},
10497d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY212C)},
10507d901a1eSXu Liang 	{PHY_ID_GPY215B, PHY_ID_GPYx15B_MASK},
10517d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY215C)},
10527d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY241B)},
10537d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY241BM)},
10547d901a1eSXu Liang 	{PHY_ID_MATCH_MODEL(PHY_ID_GPY245B)},
10557d901a1eSXu Liang 	{ }
10567d901a1eSXu Liang };
10577d901a1eSXu Liang MODULE_DEVICE_TABLE(mdio, gpy_tbl);
10587d901a1eSXu Liang 
10597d901a1eSXu Liang MODULE_DESCRIPTION("Maxlinear Ethernet GPY Driver");
10607d901a1eSXu Liang MODULE_AUTHOR("Xu Liang");
10617d901a1eSXu Liang MODULE_LICENSE("GPL");
1062