xref: /openbmc/linux/drivers/net/phy/meson-gxl.c (revision cf127ff2)
17334b3e4SNeil Armstrong /*
27334b3e4SNeil Armstrong  * Amlogic Meson GXL Internal PHY Driver
37334b3e4SNeil Armstrong  *
47334b3e4SNeil Armstrong  * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
57334b3e4SNeil Armstrong  * Copyright (C) 2016 BayLibre, SAS. All rights reserved.
67334b3e4SNeil Armstrong  * Author: Neil Armstrong <narmstrong@baylibre.com>
77334b3e4SNeil Armstrong  *
87334b3e4SNeil Armstrong  * This program is free software; you can redistribute it and/or modify
97334b3e4SNeil Armstrong  * it under the terms of the GNU General Public License as published by
107334b3e4SNeil Armstrong  * the Free Software Foundation; either version 2 of the License, or
117334b3e4SNeil Armstrong  * (at your option) any later version.
127334b3e4SNeil Armstrong  *
137334b3e4SNeil Armstrong  * This program is distributed in the hope that it will be useful, but WITHOUT
147334b3e4SNeil Armstrong  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
157334b3e4SNeil Armstrong  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
167334b3e4SNeil Armstrong  * more details.
177334b3e4SNeil Armstrong  *
187334b3e4SNeil Armstrong  */
197334b3e4SNeil Armstrong #include <linux/kernel.h>
207334b3e4SNeil Armstrong #include <linux/module.h>
217334b3e4SNeil Armstrong #include <linux/mii.h>
227334b3e4SNeil Armstrong #include <linux/ethtool.h>
237334b3e4SNeil Armstrong #include <linux/phy.h>
247334b3e4SNeil Armstrong #include <linux/netdevice.h>
25f1e2400aSJerome Brunet #include <linux/bitfield.h>
267334b3e4SNeil Armstrong 
2700fd73ebSJerome Brunet #define TSTCNTL		20
2800fd73ebSJerome Brunet #define  TSTCNTL_READ		BIT(15)
2900fd73ebSJerome Brunet #define  TSTCNTL_WRITE		BIT(14)
3000fd73ebSJerome Brunet #define  TSTCNTL_REG_BANK_SEL	GENMASK(12, 11)
3100fd73ebSJerome Brunet #define  TSTCNTL_TEST_MODE	BIT(10)
3200fd73ebSJerome Brunet #define  TSTCNTL_READ_ADDRESS	GENMASK(9, 5)
3300fd73ebSJerome Brunet #define  TSTCNTL_WRITE_ADDRESS	GENMASK(4, 0)
3400fd73ebSJerome Brunet #define TSTREAD1	21
3500fd73ebSJerome Brunet #define TSTWRITE	23
36cf127ff2SJerome Brunet #define INTSRC_FLAG	29
37cf127ff2SJerome Brunet #define  INTSRC_ANEG_PR		BIT(1)
38cf127ff2SJerome Brunet #define  INTSRC_PARALLEL_FAULT	BIT(2)
39cf127ff2SJerome Brunet #define  INTSRC_ANEG_LP_ACK	BIT(3)
40cf127ff2SJerome Brunet #define  INTSRC_LINK_DOWN	BIT(4)
41cf127ff2SJerome Brunet #define  INTSRC_REMOTE_FAULT	BIT(5)
42cf127ff2SJerome Brunet #define  INTSRC_ANEG_COMPLETE	BIT(6)
43cf127ff2SJerome Brunet #define INTSRC_MASK	30
4400fd73ebSJerome Brunet 
4500fd73ebSJerome Brunet #define BANK_ANALOG_DSP		0
4600fd73ebSJerome Brunet #define BANK_WOL		1
4700fd73ebSJerome Brunet #define BANK_BIST		3
4800fd73ebSJerome Brunet 
4900fd73ebSJerome Brunet /* WOL Registers */
5000fd73ebSJerome Brunet #define LPI_STATUS	0xc
5100fd73ebSJerome Brunet #define  LPI_STATUS_RSV12	BIT(12)
5200fd73ebSJerome Brunet 
5300fd73ebSJerome Brunet /* BIST Registers */
5400fd73ebSJerome Brunet #define FR_PLL_CONTROL	0x1b
5500fd73ebSJerome Brunet #define FR_PLL_DIV0	0x1c
5600fd73ebSJerome Brunet #define FR_PLL_DIV1	0x1d
5700fd73ebSJerome Brunet 
58fdaa84c3SJerome Brunet static int meson_gxl_open_banks(struct phy_device *phydev)
59fdaa84c3SJerome Brunet {
60fdaa84c3SJerome Brunet 	int ret;
61fdaa84c3SJerome Brunet 
62fdaa84c3SJerome Brunet 	/* Enable Analog and DSP register Bank access by
63fdaa84c3SJerome Brunet 	 * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register
64fdaa84c3SJerome Brunet 	 */
65fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTCNTL, 0);
66fdaa84c3SJerome Brunet 	if (ret)
67fdaa84c3SJerome Brunet 		return ret;
68fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
69fdaa84c3SJerome Brunet 	if (ret)
70fdaa84c3SJerome Brunet 		return ret;
71fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTCNTL, 0);
72fdaa84c3SJerome Brunet 	if (ret)
73fdaa84c3SJerome Brunet 		return ret;
74fdaa84c3SJerome Brunet 	return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
75fdaa84c3SJerome Brunet }
76fdaa84c3SJerome Brunet 
77fdaa84c3SJerome Brunet static void meson_gxl_close_banks(struct phy_device *phydev)
78fdaa84c3SJerome Brunet {
79fdaa84c3SJerome Brunet 	phy_write(phydev, TSTCNTL, 0);
80fdaa84c3SJerome Brunet }
81fdaa84c3SJerome Brunet 
82fdaa84c3SJerome Brunet static int meson_gxl_read_reg(struct phy_device *phydev,
83fdaa84c3SJerome Brunet 			      unsigned int bank, unsigned int reg)
84fdaa84c3SJerome Brunet {
85fdaa84c3SJerome Brunet 	int ret;
86fdaa84c3SJerome Brunet 
87fdaa84c3SJerome Brunet 	ret = meson_gxl_open_banks(phydev);
88fdaa84c3SJerome Brunet 	if (ret)
89fdaa84c3SJerome Brunet 		goto out;
90fdaa84c3SJerome Brunet 
91fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ |
92fdaa84c3SJerome Brunet 			FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
93fdaa84c3SJerome Brunet 			TSTCNTL_TEST_MODE |
94fdaa84c3SJerome Brunet 			FIELD_PREP(TSTCNTL_READ_ADDRESS, reg));
95fdaa84c3SJerome Brunet 	if (ret)
96fdaa84c3SJerome Brunet 		goto out;
97fdaa84c3SJerome Brunet 
98fdaa84c3SJerome Brunet 	ret = phy_read(phydev, TSTREAD1);
99fdaa84c3SJerome Brunet out:
100fdaa84c3SJerome Brunet 	/* Close the bank access on our way out */
101fdaa84c3SJerome Brunet 	meson_gxl_close_banks(phydev);
102fdaa84c3SJerome Brunet 	return ret;
103fdaa84c3SJerome Brunet }
104fdaa84c3SJerome Brunet 
105fdaa84c3SJerome Brunet static int meson_gxl_write_reg(struct phy_device *phydev,
106fdaa84c3SJerome Brunet 			       unsigned int bank, unsigned int reg,
107fdaa84c3SJerome Brunet 			       uint16_t value)
108fdaa84c3SJerome Brunet {
109fdaa84c3SJerome Brunet 	int ret;
110fdaa84c3SJerome Brunet 
111fdaa84c3SJerome Brunet 	ret = meson_gxl_open_banks(phydev);
112fdaa84c3SJerome Brunet 	if (ret)
113fdaa84c3SJerome Brunet 		goto out;
114fdaa84c3SJerome Brunet 
115fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTWRITE, value);
116fdaa84c3SJerome Brunet 	if (ret)
117fdaa84c3SJerome Brunet 		goto out;
118fdaa84c3SJerome Brunet 
119fdaa84c3SJerome Brunet 	ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE |
120fdaa84c3SJerome Brunet 			FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
121fdaa84c3SJerome Brunet 			TSTCNTL_TEST_MODE |
122fdaa84c3SJerome Brunet 			FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg));
123fdaa84c3SJerome Brunet 
124fdaa84c3SJerome Brunet out:
125fdaa84c3SJerome Brunet 	/* Close the bank access on our way out */
126fdaa84c3SJerome Brunet 	meson_gxl_close_banks(phydev);
127fdaa84c3SJerome Brunet 	return ret;
128fdaa84c3SJerome Brunet }
129fdaa84c3SJerome Brunet 
1307334b3e4SNeil Armstrong static int meson_gxl_config_init(struct phy_device *phydev)
1317334b3e4SNeil Armstrong {
1329042b46eSJerome Brunet 	int ret;
1339042b46eSJerome Brunet 
1347334b3e4SNeil Armstrong 	/* Enable fractional PLL */
135fdaa84c3SJerome Brunet 	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5);
1369042b46eSJerome Brunet 	if (ret)
1379042b46eSJerome Brunet 		return ret;
1387334b3e4SNeil Armstrong 
1397334b3e4SNeil Armstrong 	/* Program fraction FR_PLL_DIV1 */
140fdaa84c3SJerome Brunet 	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a);
1419042b46eSJerome Brunet 	if (ret)
1429042b46eSJerome Brunet 		return ret;
1437334b3e4SNeil Armstrong 
1447334b3e4SNeil Armstrong 	/* Program fraction FR_PLL_DIV1 */
145fdaa84c3SJerome Brunet 	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa);
1469042b46eSJerome Brunet 	if (ret)
1479042b46eSJerome Brunet 		return ret;
1487334b3e4SNeil Armstrong 
149c1e53551SJerome Brunet 	return genphy_config_init(phydev);
1507334b3e4SNeil Armstrong }
1517334b3e4SNeil Armstrong 
152f1e2400aSJerome Brunet /* This function is provided to cope with the possible failures of this phy
153f1e2400aSJerome Brunet  * during aneg process. When aneg fails, the PHY reports that aneg is done
154f1e2400aSJerome Brunet  * but the value found in MII_LPA is wrong:
155f1e2400aSJerome Brunet  *  - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that
156f1e2400aSJerome Brunet  *    the link partner (LP) supports aneg but the LP never acked our base
157f1e2400aSJerome Brunet  *    code word, it is likely that we never sent it to begin with.
158f1e2400aSJerome Brunet  *  - Late failures: MII_LPA is filled with a value which seems to make sense
159f1e2400aSJerome Brunet  *    but it actually is not what the LP is advertising. It seems that we
160f1e2400aSJerome Brunet  *    can detect this using a magic bit in the WOL bank (reg 12 - bit 12).
161f1e2400aSJerome Brunet  *    If this particular bit is not set when aneg is reported being done,
162f1e2400aSJerome Brunet  *    it means MII_LPA is likely to be wrong.
163f1e2400aSJerome Brunet  *
164f1e2400aSJerome Brunet  * In both case, forcing a restart of the aneg process solve the problem.
165f1e2400aSJerome Brunet  * When this failure happens, the first retry is usually successful but,
166f1e2400aSJerome Brunet  * in some cases, it may take up to 6 retries to get a decent result
167f1e2400aSJerome Brunet  */
1683b3397e2SColin Ian King static int meson_gxl_read_status(struct phy_device *phydev)
169f1e2400aSJerome Brunet {
170f1e2400aSJerome Brunet 	int ret, wol, lpa, exp;
171f1e2400aSJerome Brunet 
172f1e2400aSJerome Brunet 	if (phydev->autoneg == AUTONEG_ENABLE) {
173f1e2400aSJerome Brunet 		ret = genphy_aneg_done(phydev);
174f1e2400aSJerome Brunet 		if (ret < 0)
175f1e2400aSJerome Brunet 			return ret;
176f1e2400aSJerome Brunet 		else if (!ret)
177f1e2400aSJerome Brunet 			goto read_status_continue;
178f1e2400aSJerome Brunet 
179fdaa84c3SJerome Brunet 		/* Aneg is done, let's check everything is fine */
180fdaa84c3SJerome Brunet 		wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS);
181f1e2400aSJerome Brunet 		if (wol < 0)
182f1e2400aSJerome Brunet 			return wol;
183f1e2400aSJerome Brunet 
184f1e2400aSJerome Brunet 		lpa = phy_read(phydev, MII_LPA);
185f1e2400aSJerome Brunet 		if (lpa < 0)
186f1e2400aSJerome Brunet 			return lpa;
187f1e2400aSJerome Brunet 
188f1e2400aSJerome Brunet 		exp = phy_read(phydev, MII_EXPANSION);
189f1e2400aSJerome Brunet 		if (exp < 0)
190f1e2400aSJerome Brunet 			return exp;
191f1e2400aSJerome Brunet 
19200fd73ebSJerome Brunet 		if (!(wol & LPI_STATUS_RSV12) ||
193f1e2400aSJerome Brunet 		    ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) {
194f1e2400aSJerome Brunet 			/* Looks like aneg failed after all */
195f1e2400aSJerome Brunet 			phydev_dbg(phydev, "LPA corruption - aneg restart\n");
196f1e2400aSJerome Brunet 			return genphy_restart_aneg(phydev);
197f1e2400aSJerome Brunet 		}
198f1e2400aSJerome Brunet 	}
199f1e2400aSJerome Brunet 
200f1e2400aSJerome Brunet read_status_continue:
201f1e2400aSJerome Brunet 	return genphy_read_status(phydev);
202f1e2400aSJerome Brunet }
203f1e2400aSJerome Brunet 
204cf127ff2SJerome Brunet static int meson_gxl_ack_interrupt(struct phy_device *phydev)
205cf127ff2SJerome Brunet {
206cf127ff2SJerome Brunet 	int ret = phy_read(phydev, INTSRC_FLAG);
207cf127ff2SJerome Brunet 
208cf127ff2SJerome Brunet 	return ret < 0 ? ret : 0;
209cf127ff2SJerome Brunet }
210cf127ff2SJerome Brunet 
211cf127ff2SJerome Brunet static int meson_gxl_config_intr(struct phy_device *phydev)
212cf127ff2SJerome Brunet {
213cf127ff2SJerome Brunet 	u16 val;
214cf127ff2SJerome Brunet 
215cf127ff2SJerome Brunet 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
216cf127ff2SJerome Brunet 		val = INTSRC_ANEG_PR
217cf127ff2SJerome Brunet 			| INTSRC_PARALLEL_FAULT
218cf127ff2SJerome Brunet 			| INTSRC_ANEG_LP_ACK
219cf127ff2SJerome Brunet 			| INTSRC_LINK_DOWN
220cf127ff2SJerome Brunet 			| INTSRC_REMOTE_FAULT
221cf127ff2SJerome Brunet 			| INTSRC_ANEG_COMPLETE;
222cf127ff2SJerome Brunet 	} else {
223cf127ff2SJerome Brunet 		val = 0;
224cf127ff2SJerome Brunet 	}
225cf127ff2SJerome Brunet 
226cf127ff2SJerome Brunet 	return phy_write(phydev, INTSRC_MASK, val);
227cf127ff2SJerome Brunet }
228cf127ff2SJerome Brunet 
2297334b3e4SNeil Armstrong static struct phy_driver meson_gxl_phy[] = {
2307334b3e4SNeil Armstrong 	{
2317334b3e4SNeil Armstrong 		.phy_id		= 0x01814400,
2327334b3e4SNeil Armstrong 		.phy_id_mask	= 0xfffffff0,
2337334b3e4SNeil Armstrong 		.name		= "Meson GXL Internal PHY",
2347334b3e4SNeil Armstrong 		.features	= PHY_BASIC_FEATURES,
235cf127ff2SJerome Brunet 		.flags		= PHY_IS_INTERNAL | PHY_HAS_INTERRUPT,
2367334b3e4SNeil Armstrong 		.config_init	= meson_gxl_config_init,
2377334b3e4SNeil Armstrong 		.aneg_done      = genphy_aneg_done,
238f1e2400aSJerome Brunet 		.read_status	= meson_gxl_read_status,
239cf127ff2SJerome Brunet 		.ack_interrupt	= meson_gxl_ack_interrupt,
240cf127ff2SJerome Brunet 		.config_intr	= meson_gxl_config_intr,
2417334b3e4SNeil Armstrong 		.suspend        = genphy_suspend,
2427334b3e4SNeil Armstrong 		.resume         = genphy_resume,
2437334b3e4SNeil Armstrong 	},
2447334b3e4SNeil Armstrong };
2457334b3e4SNeil Armstrong 
2467334b3e4SNeil Armstrong static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
2477334b3e4SNeil Armstrong 	{ 0x01814400, 0xfffffff0 },
2487334b3e4SNeil Armstrong 	{ }
2497334b3e4SNeil Armstrong };
2507334b3e4SNeil Armstrong 
2517334b3e4SNeil Armstrong module_phy_driver(meson_gxl_phy);
2527334b3e4SNeil Armstrong 
2537334b3e4SNeil Armstrong MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);
2547334b3e4SNeil Armstrong 
2557334b3e4SNeil Armstrong MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");
2567334b3e4SNeil Armstrong MODULE_AUTHOR("Baoqi wang");
2577334b3e4SNeil Armstrong MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
2587334b3e4SNeil Armstrong MODULE_LICENSE("GPL");
259