xref: /openbmc/linux/drivers/net/phy/meson-gxl.c (revision afc2336f)
1a2443fd1SAndrew Lunn // SPDX-License-Identifier: GPL-2.0+
27334b3e4SNeil Armstrong /*
37334b3e4SNeil Armstrong  * Amlogic Meson GXL Internal PHY Driver
47334b3e4SNeil Armstrong  *
57334b3e4SNeil Armstrong  * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
67334b3e4SNeil Armstrong  * Copyright (C) 2016 BayLibre, SAS. All rights reserved.
77334b3e4SNeil Armstrong  * Author: Neil Armstrong <narmstrong@baylibre.com>
87334b3e4SNeil Armstrong  */
97334b3e4SNeil Armstrong #include <linux/kernel.h>
107334b3e4SNeil Armstrong #include <linux/module.h>
117334b3e4SNeil Armstrong #include <linux/mii.h>
127334b3e4SNeil Armstrong #include <linux/ethtool.h>
137334b3e4SNeil Armstrong #include <linux/phy.h>
147334b3e4SNeil Armstrong #include <linux/netdevice.h>
15f1e2400aSJerome Brunet #include <linux/bitfield.h>
167334b3e4SNeil Armstrong 
1700fd73ebSJerome Brunet #define TSTCNTL		20
1800fd73ebSJerome Brunet #define  TSTCNTL_READ		BIT(15)
1900fd73ebSJerome Brunet #define  TSTCNTL_WRITE		BIT(14)
2000fd73ebSJerome Brunet #define  TSTCNTL_REG_BANK_SEL	GENMASK(12, 11)
2100fd73ebSJerome Brunet #define  TSTCNTL_TEST_MODE	BIT(10)
2200fd73ebSJerome Brunet #define  TSTCNTL_READ_ADDRESS	GENMASK(9, 5)
2300fd73ebSJerome Brunet #define  TSTCNTL_WRITE_ADDRESS	GENMASK(4, 0)
2400fd73ebSJerome Brunet #define TSTREAD1	21
2500fd73ebSJerome Brunet #define TSTWRITE	23
26cf127ff2SJerome Brunet #define INTSRC_FLAG	29
27cf127ff2SJerome Brunet #define  INTSRC_ANEG_PR		BIT(1)
28cf127ff2SJerome Brunet #define  INTSRC_PARALLEL_FAULT	BIT(2)
29cf127ff2SJerome Brunet #define  INTSRC_ANEG_LP_ACK	BIT(3)
30cf127ff2SJerome Brunet #define  INTSRC_LINK_DOWN	BIT(4)
31cf127ff2SJerome Brunet #define  INTSRC_REMOTE_FAULT	BIT(5)
32cf127ff2SJerome Brunet #define  INTSRC_ANEG_COMPLETE	BIT(6)
33a502a8f0SHeiner Kallweit #define  INTSRC_ENERGY_DETECT	BIT(7)
34cf127ff2SJerome Brunet #define INTSRC_MASK	30
3500fd73ebSJerome Brunet 
36a502a8f0SHeiner Kallweit #define INT_SOURCES (INTSRC_LINK_DOWN | INTSRC_ANEG_COMPLETE | \
37a502a8f0SHeiner Kallweit 		     INTSRC_ENERGY_DETECT)
38a502a8f0SHeiner Kallweit 
3900fd73ebSJerome Brunet #define BANK_ANALOG_DSP		0
4000fd73ebSJerome Brunet #define BANK_WOL		1
4100fd73ebSJerome Brunet #define BANK_BIST		3
4200fd73ebSJerome Brunet 
4300fd73ebSJerome Brunet /* WOL Registers */
4400fd73ebSJerome Brunet #define LPI_STATUS	0xc
4500fd73ebSJerome Brunet #define  LPI_STATUS_RSV12	BIT(12)
4600fd73ebSJerome Brunet 
4700fd73ebSJerome Brunet /* BIST Registers */
4800fd73ebSJerome Brunet #define FR_PLL_CONTROL	0x1b
4900fd73ebSJerome Brunet #define FR_PLL_DIV0	0x1c
5000fd73ebSJerome Brunet #define FR_PLL_DIV1	0x1d
5100fd73ebSJerome Brunet 
52fdaa84c3SJerome Brunet static int meson_gxl_open_banks(struct phy_device *phydev)
53fdaa84c3SJerome Brunet {
54fdaa84c3SJerome Brunet 	int ret;
55fdaa84c3SJerome Brunet 
56fdaa84c3SJerome Brunet 	/* Enable Analog and DSP register Bank access by
57fdaa84c3SJerome Brunet 	 * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register
58fdaa84c3SJerome Brunet 	 */
59fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTCNTL, 0);
60fdaa84c3SJerome Brunet 	if (ret)
61fdaa84c3SJerome Brunet 		return ret;
62fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
63fdaa84c3SJerome Brunet 	if (ret)
64fdaa84c3SJerome Brunet 		return ret;
65fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTCNTL, 0);
66fdaa84c3SJerome Brunet 	if (ret)
67fdaa84c3SJerome Brunet 		return ret;
68fdaa84c3SJerome Brunet 	return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
69fdaa84c3SJerome Brunet }
70fdaa84c3SJerome Brunet 
71fdaa84c3SJerome Brunet static void meson_gxl_close_banks(struct phy_device *phydev)
72fdaa84c3SJerome Brunet {
73fdaa84c3SJerome Brunet 	phy_write(phydev, TSTCNTL, 0);
74fdaa84c3SJerome Brunet }
75fdaa84c3SJerome Brunet 
76fdaa84c3SJerome Brunet static int meson_gxl_read_reg(struct phy_device *phydev,
77fdaa84c3SJerome Brunet 			      unsigned int bank, unsigned int reg)
78fdaa84c3SJerome Brunet {
79fdaa84c3SJerome Brunet 	int ret;
80fdaa84c3SJerome Brunet 
81fdaa84c3SJerome Brunet 	ret = meson_gxl_open_banks(phydev);
82fdaa84c3SJerome Brunet 	if (ret)
83fdaa84c3SJerome Brunet 		goto out;
84fdaa84c3SJerome Brunet 
85fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ |
86fdaa84c3SJerome Brunet 			FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
87fdaa84c3SJerome Brunet 			TSTCNTL_TEST_MODE |
88fdaa84c3SJerome Brunet 			FIELD_PREP(TSTCNTL_READ_ADDRESS, reg));
89fdaa84c3SJerome Brunet 	if (ret)
90fdaa84c3SJerome Brunet 		goto out;
91fdaa84c3SJerome Brunet 
92fdaa84c3SJerome Brunet 	ret = phy_read(phydev, TSTREAD1);
93fdaa84c3SJerome Brunet out:
94fdaa84c3SJerome Brunet 	/* Close the bank access on our way out */
95fdaa84c3SJerome Brunet 	meson_gxl_close_banks(phydev);
96fdaa84c3SJerome Brunet 	return ret;
97fdaa84c3SJerome Brunet }
98fdaa84c3SJerome Brunet 
99fdaa84c3SJerome Brunet static int meson_gxl_write_reg(struct phy_device *phydev,
100fdaa84c3SJerome Brunet 			       unsigned int bank, unsigned int reg,
101fdaa84c3SJerome Brunet 			       uint16_t value)
102fdaa84c3SJerome Brunet {
103fdaa84c3SJerome Brunet 	int ret;
104fdaa84c3SJerome Brunet 
105fdaa84c3SJerome Brunet 	ret = meson_gxl_open_banks(phydev);
106fdaa84c3SJerome Brunet 	if (ret)
107fdaa84c3SJerome Brunet 		goto out;
108fdaa84c3SJerome Brunet 
109fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTWRITE, value);
110fdaa84c3SJerome Brunet 	if (ret)
111fdaa84c3SJerome Brunet 		goto out;
112fdaa84c3SJerome Brunet 
113fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE |
114fdaa84c3SJerome Brunet 			FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
115fdaa84c3SJerome Brunet 			TSTCNTL_TEST_MODE |
116fdaa84c3SJerome Brunet 			FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg));
117fdaa84c3SJerome Brunet 
118fdaa84c3SJerome Brunet out:
119fdaa84c3SJerome Brunet 	/* Close the bank access on our way out */
120fdaa84c3SJerome Brunet 	meson_gxl_close_banks(phydev);
121fdaa84c3SJerome Brunet 	return ret;
122fdaa84c3SJerome Brunet }
123fdaa84c3SJerome Brunet 
1247334b3e4SNeil Armstrong static int meson_gxl_config_init(struct phy_device *phydev)
1257334b3e4SNeil Armstrong {
1269042b46eSJerome Brunet 	int ret;
1279042b46eSJerome Brunet 
1287334b3e4SNeil Armstrong 	/* Enable fractional PLL */
129fdaa84c3SJerome Brunet 	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5);
1309042b46eSJerome Brunet 	if (ret)
1319042b46eSJerome Brunet 		return ret;
1327334b3e4SNeil Armstrong 
1337334b3e4SNeil Armstrong 	/* Program fraction FR_PLL_DIV1 */
134fdaa84c3SJerome Brunet 	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a);
1359042b46eSJerome Brunet 	if (ret)
1369042b46eSJerome Brunet 		return ret;
1377334b3e4SNeil Armstrong 
1387334b3e4SNeil Armstrong 	/* Program fraction FR_PLL_DIV1 */
139fdaa84c3SJerome Brunet 	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa);
1409042b46eSJerome Brunet 	if (ret)
1419042b46eSJerome Brunet 		return ret;
1427334b3e4SNeil Armstrong 
143c227ce44SHeiner Kallweit 	return 0;
1447334b3e4SNeil Armstrong }
1457334b3e4SNeil Armstrong 
146f1e2400aSJerome Brunet /* This function is provided to cope with the possible failures of this phy
147f1e2400aSJerome Brunet  * during aneg process. When aneg fails, the PHY reports that aneg is done
148f1e2400aSJerome Brunet  * but the value found in MII_LPA is wrong:
149f1e2400aSJerome Brunet  *  - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that
150f1e2400aSJerome Brunet  *    the link partner (LP) supports aneg but the LP never acked our base
151f1e2400aSJerome Brunet  *    code word, it is likely that we never sent it to begin with.
152f1e2400aSJerome Brunet  *  - Late failures: MII_LPA is filled with a value which seems to make sense
153f1e2400aSJerome Brunet  *    but it actually is not what the LP is advertising. It seems that we
154f1e2400aSJerome Brunet  *    can detect this using a magic bit in the WOL bank (reg 12 - bit 12).
155f1e2400aSJerome Brunet  *    If this particular bit is not set when aneg is reported being done,
156f1e2400aSJerome Brunet  *    it means MII_LPA is likely to be wrong.
157f1e2400aSJerome Brunet  *
158f1e2400aSJerome Brunet  * In both case, forcing a restart of the aneg process solve the problem.
159f1e2400aSJerome Brunet  * When this failure happens, the first retry is usually successful but,
160f1e2400aSJerome Brunet  * in some cases, it may take up to 6 retries to get a decent result
161f1e2400aSJerome Brunet  */
1623b3397e2SColin Ian King static int meson_gxl_read_status(struct phy_device *phydev)
163f1e2400aSJerome Brunet {
164f1e2400aSJerome Brunet 	int ret, wol, lpa, exp;
165f1e2400aSJerome Brunet 
166f1e2400aSJerome Brunet 	if (phydev->autoneg == AUTONEG_ENABLE) {
167f1e2400aSJerome Brunet 		ret = genphy_aneg_done(phydev);
168f1e2400aSJerome Brunet 		if (ret < 0)
169f1e2400aSJerome Brunet 			return ret;
170f1e2400aSJerome Brunet 		else if (!ret)
171f1e2400aSJerome Brunet 			goto read_status_continue;
172f1e2400aSJerome Brunet 
173fdaa84c3SJerome Brunet 		/* Aneg is done, let's check everything is fine */
174fdaa84c3SJerome Brunet 		wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS);
175f1e2400aSJerome Brunet 		if (wol < 0)
176f1e2400aSJerome Brunet 			return wol;
177f1e2400aSJerome Brunet 
178f1e2400aSJerome Brunet 		lpa = phy_read(phydev, MII_LPA);
179f1e2400aSJerome Brunet 		if (lpa < 0)
180f1e2400aSJerome Brunet 			return lpa;
181f1e2400aSJerome Brunet 
182f1e2400aSJerome Brunet 		exp = phy_read(phydev, MII_EXPANSION);
183f1e2400aSJerome Brunet 		if (exp < 0)
184f1e2400aSJerome Brunet 			return exp;
185f1e2400aSJerome Brunet 
18600fd73ebSJerome Brunet 		if (!(wol & LPI_STATUS_RSV12) ||
187f1e2400aSJerome Brunet 		    ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) {
188f1e2400aSJerome Brunet 			/* Looks like aneg failed after all */
189f1e2400aSJerome Brunet 			phydev_dbg(phydev, "LPA corruption - aneg restart\n");
190f1e2400aSJerome Brunet 			return genphy_restart_aneg(phydev);
191f1e2400aSJerome Brunet 		}
192f1e2400aSJerome Brunet 	}
193f1e2400aSJerome Brunet 
194f1e2400aSJerome Brunet read_status_continue:
195f1e2400aSJerome Brunet 	return genphy_read_status(phydev);
196f1e2400aSJerome Brunet }
197f1e2400aSJerome Brunet 
198cf127ff2SJerome Brunet static int meson_gxl_ack_interrupt(struct phy_device *phydev)
199cf127ff2SJerome Brunet {
200cf127ff2SJerome Brunet 	int ret = phy_read(phydev, INTSRC_FLAG);
201cf127ff2SJerome Brunet 
202cf127ff2SJerome Brunet 	return ret < 0 ? ret : 0;
203cf127ff2SJerome Brunet }
204cf127ff2SJerome Brunet 
205cf127ff2SJerome Brunet static int meson_gxl_config_intr(struct phy_device *phydev)
206cf127ff2SJerome Brunet {
207daa5c4d0SJerome Brunet 	int ret;
208cf127ff2SJerome Brunet 
209cf127ff2SJerome Brunet 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
21084c8f773SIoana Ciornei 		/* Ack any pending IRQ */
21184c8f773SIoana Ciornei 		ret = meson_gxl_ack_interrupt(phydev);
21284c8f773SIoana Ciornei 		if (ret)
21384c8f773SIoana Ciornei 			return ret;
21484c8f773SIoana Ciornei 
215a502a8f0SHeiner Kallweit 		ret = phy_write(phydev, INTSRC_MASK, INT_SOURCES);
216cf127ff2SJerome Brunet 	} else {
217a502a8f0SHeiner Kallweit 		ret = phy_write(phydev, INTSRC_MASK, 0);
218cf127ff2SJerome Brunet 
219daa5c4d0SJerome Brunet 		/* Ack any pending IRQ */
220daa5c4d0SJerome Brunet 		ret = meson_gxl_ack_interrupt(phydev);
22184c8f773SIoana Ciornei 	}
222daa5c4d0SJerome Brunet 
22384c8f773SIoana Ciornei 	return ret;
224cf127ff2SJerome Brunet }
225cf127ff2SJerome Brunet 
2266719e2beSIoana Ciornei static irqreturn_t meson_gxl_handle_interrupt(struct phy_device *phydev)
2276719e2beSIoana Ciornei {
2286719e2beSIoana Ciornei 	int irq_status;
2296719e2beSIoana Ciornei 
2306719e2beSIoana Ciornei 	irq_status = phy_read(phydev, INTSRC_FLAG);
2316719e2beSIoana Ciornei 	if (irq_status < 0) {
2326719e2beSIoana Ciornei 		phy_error(phydev);
2336719e2beSIoana Ciornei 		return IRQ_NONE;
2346719e2beSIoana Ciornei 	}
2356719e2beSIoana Ciornei 
236a502a8f0SHeiner Kallweit 	irq_status &= INT_SOURCES;
237a502a8f0SHeiner Kallweit 
2386719e2beSIoana Ciornei 	if (irq_status == 0)
2396719e2beSIoana Ciornei 		return IRQ_NONE;
2406719e2beSIoana Ciornei 
241a502a8f0SHeiner Kallweit 	/* Aneg-complete interrupt is used for link-up detection */
242a502a8f0SHeiner Kallweit 	if (phydev->autoneg == AUTONEG_ENABLE &&
243a502a8f0SHeiner Kallweit 	    irq_status == INTSRC_ENERGY_DETECT)
244a502a8f0SHeiner Kallweit 		return IRQ_HANDLED;
245a502a8f0SHeiner Kallweit 
2466719e2beSIoana Ciornei 	phy_trigger_machine(phydev);
2476719e2beSIoana Ciornei 
2486719e2beSIoana Ciornei 	return IRQ_HANDLED;
2496719e2beSIoana Ciornei }
2506719e2beSIoana Ciornei 
2517334b3e4SNeil Armstrong static struct phy_driver meson_gxl_phy[] = {
2527334b3e4SNeil Armstrong 	{
253fad137c4SJerome Brunet 		PHY_ID_MATCH_EXACT(0x01814400),
2547334b3e4SNeil Armstrong 		.name		= "Meson GXL Internal PHY",
255dcdecdcfSHeiner Kallweit 		/* PHY_BASIC_FEATURES */
256a4307c0eSHeiner Kallweit 		.flags		= PHY_IS_INTERNAL,
257f2f98c1dSTimotej Lazar 		.soft_reset     = genphy_soft_reset,
2587334b3e4SNeil Armstrong 		.config_init	= meson_gxl_config_init,
259f1e2400aSJerome Brunet 		.read_status	= meson_gxl_read_status,
260cf127ff2SJerome Brunet 		.config_intr	= meson_gxl_config_intr,
2616719e2beSIoana Ciornei 		.handle_interrupt = meson_gxl_handle_interrupt,
2627334b3e4SNeil Armstrong 		.suspend        = genphy_suspend,
2637334b3e4SNeil Armstrong 		.resume         = genphy_resume,
2645c3407abSJerome Brunet 	}, {
2655c3407abSJerome Brunet 		PHY_ID_MATCH_EXACT(0x01803301),
2665c3407abSJerome Brunet 		.name		= "Meson G12A Internal PHY",
267dcdecdcfSHeiner Kallweit 		/* PHY_BASIC_FEATURES */
2685c3407abSJerome Brunet 		.flags		= PHY_IS_INTERNAL,
2695c3407abSJerome Brunet 		.soft_reset     = genphy_soft_reset,
2705c3407abSJerome Brunet 		.config_intr	= meson_gxl_config_intr,
2716719e2beSIoana Ciornei 		.handle_interrupt = meson_gxl_handle_interrupt,
2725c3407abSJerome Brunet 		.suspend        = genphy_suspend,
2735c3407abSJerome Brunet 		.resume         = genphy_resume,
274*afc2336fSChris Healy 		.read_mmd	= genphy_read_mmd_unsupported,
275*afc2336fSChris Healy 		.write_mmd	= genphy_write_mmd_unsupported,
2767334b3e4SNeil Armstrong 	},
2777334b3e4SNeil Armstrong };
2787334b3e4SNeil Armstrong 
2797334b3e4SNeil Armstrong static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
280fad137c4SJerome Brunet 	{ PHY_ID_MATCH_VENDOR(0x01814400) },
2815c3407abSJerome Brunet 	{ PHY_ID_MATCH_VENDOR(0x01803301) },
2827334b3e4SNeil Armstrong 	{ }
2837334b3e4SNeil Armstrong };
2847334b3e4SNeil Armstrong 
2857334b3e4SNeil Armstrong module_phy_driver(meson_gxl_phy);
2867334b3e4SNeil Armstrong 
2877334b3e4SNeil Armstrong MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);
2887334b3e4SNeil Armstrong 
2897334b3e4SNeil Armstrong MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");
2907334b3e4SNeil Armstrong MODULE_AUTHOR("Baoqi wang");
2917334b3e4SNeil Armstrong MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
292afb4fa47SJerome Brunet MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
2937334b3e4SNeil Armstrong MODULE_LICENSE("GPL");
294