xref: /openbmc/linux/drivers/net/phy/at803x.c (revision 1ca6d1b1)
10ca7111aSMatus Ujhelyi /*
20ca7111aSMatus Ujhelyi  * drivers/net/phy/at803x.c
30ca7111aSMatus Ujhelyi  *
40ca7111aSMatus Ujhelyi  * Driver for Atheros 803x PHY
50ca7111aSMatus Ujhelyi  *
60ca7111aSMatus Ujhelyi  * Author: Matus Ujhelyi <ujhelyi.m@gmail.com>
70ca7111aSMatus Ujhelyi  *
80ca7111aSMatus Ujhelyi  * This program is free software; you can redistribute  it and/or modify it
90ca7111aSMatus Ujhelyi  * under  the terms of  the GNU General  Public License as published by the
100ca7111aSMatus Ujhelyi  * Free Software Foundation;  either version 2 of the  License, or (at your
110ca7111aSMatus Ujhelyi  * option) any later version.
120ca7111aSMatus Ujhelyi  */
130ca7111aSMatus Ujhelyi 
140ca7111aSMatus Ujhelyi #include <linux/phy.h>
150ca7111aSMatus Ujhelyi #include <linux/module.h>
160ca7111aSMatus Ujhelyi #include <linux/string.h>
170ca7111aSMatus Ujhelyi #include <linux/netdevice.h>
180ca7111aSMatus Ujhelyi #include <linux/etherdevice.h>
190ca7111aSMatus Ujhelyi 
200ca7111aSMatus Ujhelyi #define AT803X_INTR_ENABLE			0x12
210ca7111aSMatus Ujhelyi #define AT803X_INTR_STATUS			0x13
220ca7111aSMatus Ujhelyi #define AT803X_WOL_ENABLE			0x01
230ca7111aSMatus Ujhelyi #define AT803X_DEVICE_ADDR			0x03
240ca7111aSMatus Ujhelyi #define AT803X_LOC_MAC_ADDR_0_15_OFFSET		0x804C
250ca7111aSMatus Ujhelyi #define AT803X_LOC_MAC_ADDR_16_31_OFFSET	0x804B
260ca7111aSMatus Ujhelyi #define AT803X_LOC_MAC_ADDR_32_47_OFFSET	0x804A
270ca7111aSMatus Ujhelyi #define AT803X_MMD_ACCESS_CONTROL		0x0D
280ca7111aSMatus Ujhelyi #define AT803X_MMD_ACCESS_CONTROL_DATA		0x0E
290ca7111aSMatus Ujhelyi #define AT803X_FUNC_DATA			0x4003
301ca6d1b1SMugunthan V N #define AT803X_DEBUG_ADDR			0x1D
311ca6d1b1SMugunthan V N #define AT803X_DEBUG_DATA			0x1E
321ca6d1b1SMugunthan V N #define AT803X_DEBUG_SYSTEM_MODE_CTRL		0x05
331ca6d1b1SMugunthan V N #define AT803X_DEBUG_RGMII_TX_CLK_DLY		BIT(8)
340ca7111aSMatus Ujhelyi 
350ca7111aSMatus Ujhelyi MODULE_DESCRIPTION("Atheros 803x PHY driver");
360ca7111aSMatus Ujhelyi MODULE_AUTHOR("Matus Ujhelyi");
370ca7111aSMatus Ujhelyi MODULE_LICENSE("GPL");
380ca7111aSMatus Ujhelyi 
39ea13c9eeSMugunthan V N static int at803x_set_wol(struct phy_device *phydev,
40ea13c9eeSMugunthan V N 			  struct ethtool_wolinfo *wol)
410ca7111aSMatus Ujhelyi {
420ca7111aSMatus Ujhelyi 	struct net_device *ndev = phydev->attached_dev;
430ca7111aSMatus Ujhelyi 	const u8 *mac;
44ea13c9eeSMugunthan V N 	int ret;
45ea13c9eeSMugunthan V N 	u32 value;
460ca7111aSMatus Ujhelyi 	unsigned int i, offsets[] = {
470ca7111aSMatus Ujhelyi 		AT803X_LOC_MAC_ADDR_32_47_OFFSET,
480ca7111aSMatus Ujhelyi 		AT803X_LOC_MAC_ADDR_16_31_OFFSET,
490ca7111aSMatus Ujhelyi 		AT803X_LOC_MAC_ADDR_0_15_OFFSET,
500ca7111aSMatus Ujhelyi 	};
510ca7111aSMatus Ujhelyi 
520ca7111aSMatus Ujhelyi 	if (!ndev)
53ea13c9eeSMugunthan V N 		return -ENODEV;
540ca7111aSMatus Ujhelyi 
55ea13c9eeSMugunthan V N 	if (wol->wolopts & WAKE_MAGIC) {
560ca7111aSMatus Ujhelyi 		mac = (const u8 *) ndev->dev_addr;
570ca7111aSMatus Ujhelyi 
580ca7111aSMatus Ujhelyi 		if (!is_valid_ether_addr(mac))
59ea13c9eeSMugunthan V N 			return -EFAULT;
600ca7111aSMatus Ujhelyi 
610ca7111aSMatus Ujhelyi 		for (i = 0; i < 3; i++) {
620ca7111aSMatus Ujhelyi 			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
630ca7111aSMatus Ujhelyi 				  AT803X_DEVICE_ADDR);
640ca7111aSMatus Ujhelyi 			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
650ca7111aSMatus Ujhelyi 				  offsets[i]);
660ca7111aSMatus Ujhelyi 			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
670ca7111aSMatus Ujhelyi 				  AT803X_FUNC_DATA);
680ca7111aSMatus Ujhelyi 			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
690ca7111aSMatus Ujhelyi 				  mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
700ca7111aSMatus Ujhelyi 		}
71ea13c9eeSMugunthan V N 
72ea13c9eeSMugunthan V N 		value = phy_read(phydev, AT803X_INTR_ENABLE);
73ea13c9eeSMugunthan V N 		value |= AT803X_WOL_ENABLE;
74ea13c9eeSMugunthan V N 		ret = phy_write(phydev, AT803X_INTR_ENABLE, value);
75ea13c9eeSMugunthan V N 		if (ret)
76ea13c9eeSMugunthan V N 			return ret;
77ea13c9eeSMugunthan V N 		value = phy_read(phydev, AT803X_INTR_STATUS);
78ea13c9eeSMugunthan V N 	} else {
79ea13c9eeSMugunthan V N 		value = phy_read(phydev, AT803X_INTR_ENABLE);
80ea13c9eeSMugunthan V N 		value &= (~AT803X_WOL_ENABLE);
81ea13c9eeSMugunthan V N 		ret = phy_write(phydev, AT803X_INTR_ENABLE, value);
82ea13c9eeSMugunthan V N 		if (ret)
83ea13c9eeSMugunthan V N 			return ret;
84ea13c9eeSMugunthan V N 		value = phy_read(phydev, AT803X_INTR_STATUS);
85ea13c9eeSMugunthan V N 	}
86ea13c9eeSMugunthan V N 
87ea13c9eeSMugunthan V N 	return ret;
88ea13c9eeSMugunthan V N }
89ea13c9eeSMugunthan V N 
90ea13c9eeSMugunthan V N static void at803x_get_wol(struct phy_device *phydev,
91ea13c9eeSMugunthan V N 			   struct ethtool_wolinfo *wol)
92ea13c9eeSMugunthan V N {
93ea13c9eeSMugunthan V N 	u32 value;
94ea13c9eeSMugunthan V N 
95ea13c9eeSMugunthan V N 	wol->supported = WAKE_MAGIC;
96ea13c9eeSMugunthan V N 	wol->wolopts = 0;
97ea13c9eeSMugunthan V N 
98ea13c9eeSMugunthan V N 	value = phy_read(phydev, AT803X_INTR_ENABLE);
99ea13c9eeSMugunthan V N 	if (value & AT803X_WOL_ENABLE)
100ea13c9eeSMugunthan V N 		wol->wolopts |= WAKE_MAGIC;
1010ca7111aSMatus Ujhelyi }
1020ca7111aSMatus Ujhelyi 
1030ca7111aSMatus Ujhelyi static int at803x_config_init(struct phy_device *phydev)
1040ca7111aSMatus Ujhelyi {
1050ca7111aSMatus Ujhelyi 	int val;
1061ca6d1b1SMugunthan V N 	int ret;
1070ca7111aSMatus Ujhelyi 	u32 features;
1080ca7111aSMatus Ujhelyi 
1090ca7111aSMatus Ujhelyi 	features = SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_AUI |
1100ca7111aSMatus Ujhelyi 		   SUPPORTED_FIBRE | SUPPORTED_BNC;
1110ca7111aSMatus Ujhelyi 
1120ca7111aSMatus Ujhelyi 	val = phy_read(phydev, MII_BMSR);
1130ca7111aSMatus Ujhelyi 	if (val < 0)
1140ca7111aSMatus Ujhelyi 		return val;
1150ca7111aSMatus Ujhelyi 
1160ca7111aSMatus Ujhelyi 	if (val & BMSR_ANEGCAPABLE)
1170ca7111aSMatus Ujhelyi 		features |= SUPPORTED_Autoneg;
1180ca7111aSMatus Ujhelyi 	if (val & BMSR_100FULL)
1190ca7111aSMatus Ujhelyi 		features |= SUPPORTED_100baseT_Full;
1200ca7111aSMatus Ujhelyi 	if (val & BMSR_100HALF)
1210ca7111aSMatus Ujhelyi 		features |= SUPPORTED_100baseT_Half;
1220ca7111aSMatus Ujhelyi 	if (val & BMSR_10FULL)
1230ca7111aSMatus Ujhelyi 		features |= SUPPORTED_10baseT_Full;
1240ca7111aSMatus Ujhelyi 	if (val & BMSR_10HALF)
1250ca7111aSMatus Ujhelyi 		features |= SUPPORTED_10baseT_Half;
1260ca7111aSMatus Ujhelyi 
1270ca7111aSMatus Ujhelyi 	if (val & BMSR_ESTATEN) {
1280ca7111aSMatus Ujhelyi 		val = phy_read(phydev, MII_ESTATUS);
1290ca7111aSMatus Ujhelyi 		if (val < 0)
1300ca7111aSMatus Ujhelyi 			return val;
1310ca7111aSMatus Ujhelyi 
1320ca7111aSMatus Ujhelyi 		if (val & ESTATUS_1000_TFULL)
1330ca7111aSMatus Ujhelyi 			features |= SUPPORTED_1000baseT_Full;
1340ca7111aSMatus Ujhelyi 		if (val & ESTATUS_1000_THALF)
1350ca7111aSMatus Ujhelyi 			features |= SUPPORTED_1000baseT_Half;
1360ca7111aSMatus Ujhelyi 	}
1370ca7111aSMatus Ujhelyi 
1380ca7111aSMatus Ujhelyi 	phydev->supported = features;
1390ca7111aSMatus Ujhelyi 	phydev->advertising = features;
1400ca7111aSMatus Ujhelyi 
1411ca6d1b1SMugunthan V N 	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
1421ca6d1b1SMugunthan V N 		ret = phy_write(phydev, AT803X_DEBUG_ADDR,
1431ca6d1b1SMugunthan V N 				AT803X_DEBUG_SYSTEM_MODE_CTRL);
1441ca6d1b1SMugunthan V N 		if (ret)
1451ca6d1b1SMugunthan V N 			return ret;
1461ca6d1b1SMugunthan V N 		ret = phy_write(phydev, AT803X_DEBUG_DATA,
1471ca6d1b1SMugunthan V N 				AT803X_DEBUG_RGMII_TX_CLK_DLY);
1481ca6d1b1SMugunthan V N 		if (ret)
1491ca6d1b1SMugunthan V N 			return ret;
1501ca6d1b1SMugunthan V N 	}
1511ca6d1b1SMugunthan V N 
1520ca7111aSMatus Ujhelyi 	return 0;
1530ca7111aSMatus Ujhelyi }
1540ca7111aSMatus Ujhelyi 
155317420abSMugunthan V N static struct phy_driver at803x_driver[] = {
156317420abSMugunthan V N {
1570ca7111aSMatus Ujhelyi 	/* ATHEROS 8035 */
1580ca7111aSMatus Ujhelyi 	.phy_id		= 0x004dd072,
1590ca7111aSMatus Ujhelyi 	.name		= "Atheros 8035 ethernet",
1600ca7111aSMatus Ujhelyi 	.phy_id_mask	= 0xffffffef,
1610ca7111aSMatus Ujhelyi 	.config_init	= at803x_config_init,
162ea13c9eeSMugunthan V N 	.set_wol	= at803x_set_wol,
163ea13c9eeSMugunthan V N 	.get_wol	= at803x_get_wol,
1640ca7111aSMatus Ujhelyi 	.features	= PHY_GBIT_FEATURES,
1650ca7111aSMatus Ujhelyi 	.flags		= PHY_HAS_INTERRUPT,
1660ca7111aSMatus Ujhelyi 	.config_aneg	= &genphy_config_aneg,
1670ca7111aSMatus Ujhelyi 	.read_status	= &genphy_read_status,
1680ca7111aSMatus Ujhelyi 	.driver		= {
1690ca7111aSMatus Ujhelyi 		.owner = THIS_MODULE,
1700ca7111aSMatus Ujhelyi 	},
171317420abSMugunthan V N }, {
1720ca7111aSMatus Ujhelyi 	/* ATHEROS 8030 */
1730ca7111aSMatus Ujhelyi 	.phy_id		= 0x004dd076,
1740ca7111aSMatus Ujhelyi 	.name		= "Atheros 8030 ethernet",
1750ca7111aSMatus Ujhelyi 	.phy_id_mask	= 0xffffffef,
1760ca7111aSMatus Ujhelyi 	.config_init	= at803x_config_init,
177ea13c9eeSMugunthan V N 	.set_wol	= at803x_set_wol,
178ea13c9eeSMugunthan V N 	.get_wol	= at803x_get_wol,
1790ca7111aSMatus Ujhelyi 	.features	= PHY_GBIT_FEATURES,
1800ca7111aSMatus Ujhelyi 	.flags		= PHY_HAS_INTERRUPT,
1810ca7111aSMatus Ujhelyi 	.config_aneg	= &genphy_config_aneg,
1820ca7111aSMatus Ujhelyi 	.read_status	= &genphy_read_status,
1830ca7111aSMatus Ujhelyi 	.driver		= {
1840ca7111aSMatus Ujhelyi 		.owner = THIS_MODULE,
1850ca7111aSMatus Ujhelyi 	},
186317420abSMugunthan V N } };
1870ca7111aSMatus Ujhelyi 
1880ca7111aSMatus Ujhelyi static int __init atheros_init(void)
1890ca7111aSMatus Ujhelyi {
190317420abSMugunthan V N 	return phy_drivers_register(at803x_driver,
191317420abSMugunthan V N 				    ARRAY_SIZE(at803x_driver));
1920ca7111aSMatus Ujhelyi }
1930ca7111aSMatus Ujhelyi 
1940ca7111aSMatus Ujhelyi static void __exit atheros_exit(void)
1950ca7111aSMatus Ujhelyi {
196317420abSMugunthan V N 	return phy_drivers_unregister(at803x_driver,
197317420abSMugunthan V N 				      ARRAY_SIZE(at803x_driver));
1980ca7111aSMatus Ujhelyi }
1990ca7111aSMatus Ujhelyi 
2000ca7111aSMatus Ujhelyi module_init(atheros_init);
2010ca7111aSMatus Ujhelyi module_exit(atheros_exit);
2020ca7111aSMatus Ujhelyi 
2030ca7111aSMatus Ujhelyi static struct mdio_device_id __maybe_unused atheros_tbl[] = {
2040ca7111aSMatus Ujhelyi 	{ 0x004dd076, 0xffffffef },
2050ca7111aSMatus Ujhelyi 	{ 0x004dd072, 0xffffffef },
2060ca7111aSMatus Ujhelyi 	{ }
2070ca7111aSMatus Ujhelyi };
2080ca7111aSMatus Ujhelyi 
2090ca7111aSMatus Ujhelyi MODULE_DEVICE_TABLE(mdio, atheros_tbl);
210