162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/* Driver for Asix PHYs
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Author: Michael Schmitz <schmitzmic@gmail.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include <linux/kernel.h>
762306a36Sopenharmony_ci#include <linux/errno.h>
862306a36Sopenharmony_ci#include <linux/init.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/mii.h>
1162306a36Sopenharmony_ci#include <linux/phy.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define PHY_ID_ASIX_AX88772A		0x003b1861
1462306a36Sopenharmony_ci#define PHY_ID_ASIX_AX88772C		0x003b1881
1562306a36Sopenharmony_ci#define PHY_ID_ASIX_AX88796B		0x003b1841
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ciMODULE_DESCRIPTION("Asix PHY driver");
1862306a36Sopenharmony_ciMODULE_AUTHOR("Michael Schmitz <schmitzmic@gmail.com>");
1962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/**
2262306a36Sopenharmony_ci * asix_soft_reset - software reset the PHY via BMCR_RESET bit
2362306a36Sopenharmony_ci * @phydev: target phy_device struct
2462306a36Sopenharmony_ci *
2562306a36Sopenharmony_ci * Description: Perform a software PHY reset using the standard
2662306a36Sopenharmony_ci * BMCR_RESET bit and poll for the reset bit to be cleared.
2762306a36Sopenharmony_ci * Toggle BMCR_RESET bit off to accommodate broken AX8796B PHY implementation
2862306a36Sopenharmony_ci * such as used on the Individual Computers' X-Surf 100 Zorro card.
2962306a36Sopenharmony_ci *
3062306a36Sopenharmony_ci * Returns: 0 on success, < 0 on failure
3162306a36Sopenharmony_ci */
3262306a36Sopenharmony_cistatic int asix_soft_reset(struct phy_device *phydev)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	int ret;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	/* Asix PHY won't reset unless reset bit toggles */
3762306a36Sopenharmony_ci	ret = phy_write(phydev, MII_BMCR, 0);
3862306a36Sopenharmony_ci	if (ret < 0)
3962306a36Sopenharmony_ci		return ret;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	return genphy_soft_reset(phydev);
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci/* AX88772A is not working properly with some old switches (NETGEAR EN 108TP):
4562306a36Sopenharmony_ci * after autoneg is done and the link status is reported as active, the MII_LPA
4662306a36Sopenharmony_ci * register is 0. This issue is not reproducible on AX88772C.
4762306a36Sopenharmony_ci */
4862306a36Sopenharmony_cistatic int asix_ax88772a_read_status(struct phy_device *phydev)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	int ret, val;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	ret = genphy_update_link(phydev);
5362306a36Sopenharmony_ci	if (ret)
5462306a36Sopenharmony_ci		return ret;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	if (!phydev->link)
5762306a36Sopenharmony_ci		return 0;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	/* If MII_LPA is 0, phy_resolve_aneg_linkmode() will fail to resolve
6062306a36Sopenharmony_ci	 * linkmode so use MII_BMCR as default values.
6162306a36Sopenharmony_ci	 */
6262306a36Sopenharmony_ci	val = phy_read(phydev, MII_BMCR);
6362306a36Sopenharmony_ci	if (val < 0)
6462306a36Sopenharmony_ci		return val;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	if (val & BMCR_SPEED100)
6762306a36Sopenharmony_ci		phydev->speed = SPEED_100;
6862306a36Sopenharmony_ci	else
6962306a36Sopenharmony_ci		phydev->speed = SPEED_10;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	if (val & BMCR_FULLDPLX)
7262306a36Sopenharmony_ci		phydev->duplex = DUPLEX_FULL;
7362306a36Sopenharmony_ci	else
7462306a36Sopenharmony_ci		phydev->duplex = DUPLEX_HALF;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	ret = genphy_read_lpa(phydev);
7762306a36Sopenharmony_ci	if (ret < 0)
7862306a36Sopenharmony_ci		return ret;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)
8162306a36Sopenharmony_ci		phy_resolve_aneg_linkmode(phydev);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	return 0;
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic void asix_ax88772a_link_change_notify(struct phy_device *phydev)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	/* Reset PHY, otherwise MII_LPA will provide outdated information.
8962306a36Sopenharmony_ci	 * This issue is reproducible only with some link partner PHYs
9062306a36Sopenharmony_ci	 */
9162306a36Sopenharmony_ci	if (phydev->state == PHY_NOLINK) {
9262306a36Sopenharmony_ci		phy_init_hw(phydev);
9362306a36Sopenharmony_ci		phy_start_aneg(phydev);
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic struct phy_driver asix_driver[] = {
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A),
10062306a36Sopenharmony_ci	.name		= "Asix Electronics AX88772A",
10162306a36Sopenharmony_ci	.flags		= PHY_IS_INTERNAL,
10262306a36Sopenharmony_ci	.read_status	= asix_ax88772a_read_status,
10362306a36Sopenharmony_ci	.suspend	= genphy_suspend,
10462306a36Sopenharmony_ci	.resume		= genphy_resume,
10562306a36Sopenharmony_ci	.soft_reset	= asix_soft_reset,
10662306a36Sopenharmony_ci	.link_change_notify	= asix_ax88772a_link_change_notify,
10762306a36Sopenharmony_ci}, {
10862306a36Sopenharmony_ci	PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772C),
10962306a36Sopenharmony_ci	.name		= "Asix Electronics AX88772C",
11062306a36Sopenharmony_ci	.flags		= PHY_IS_INTERNAL,
11162306a36Sopenharmony_ci	.suspend	= genphy_suspend,
11262306a36Sopenharmony_ci	.resume		= genphy_resume,
11362306a36Sopenharmony_ci	.soft_reset	= asix_soft_reset,
11462306a36Sopenharmony_ci}, {
11562306a36Sopenharmony_ci	.phy_id		= PHY_ID_ASIX_AX88796B,
11662306a36Sopenharmony_ci	.name		= "Asix Electronics AX88796B",
11762306a36Sopenharmony_ci	.phy_id_mask	= 0xfffffff0,
11862306a36Sopenharmony_ci	/* PHY_BASIC_FEATURES */
11962306a36Sopenharmony_ci	.soft_reset	= asix_soft_reset,
12062306a36Sopenharmony_ci} };
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cimodule_phy_driver(asix_driver);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic struct mdio_device_id __maybe_unused asix_tbl[] = {
12562306a36Sopenharmony_ci	{ PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A) },
12662306a36Sopenharmony_ci	{ PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772C) },
12762306a36Sopenharmony_ci	{ PHY_ID_ASIX_AX88796B, 0xfffffff0 },
12862306a36Sopenharmony_ci	{ }
12962306a36Sopenharmony_ci};
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(mdio, asix_tbl);
132