xref: /openbmc/linux/drivers/net/phy/at803x.c (revision bd8ca17f)
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
3077a99394SZhao Qiang #define AT803X_INER				0x0012
3177a99394SZhao Qiang #define AT803X_INER_INIT			0xec00
3277a99394SZhao Qiang #define AT803X_INSR				0x0013
331ca6d1b1SMugunthan V N #define AT803X_DEBUG_ADDR			0x1D
341ca6d1b1SMugunthan V N #define AT803X_DEBUG_DATA			0x1E
351ca6d1b1SMugunthan V N #define AT803X_DEBUG_SYSTEM_MODE_CTRL		0x05
361ca6d1b1SMugunthan V N #define AT803X_DEBUG_RGMII_TX_CLK_DLY		BIT(8)
370ca7111aSMatus Ujhelyi 
38bd8ca17fSDaniel Mack #define ATH8030_PHY_ID 0x004dd076
39bd8ca17fSDaniel Mack #define ATH8031_PHY_ID 0x004dd074
40bd8ca17fSDaniel Mack #define ATH8035_PHY_ID 0x004dd072
41bd8ca17fSDaniel Mack 
420ca7111aSMatus Ujhelyi MODULE_DESCRIPTION("Atheros 803x PHY driver");
430ca7111aSMatus Ujhelyi MODULE_AUTHOR("Matus Ujhelyi");
440ca7111aSMatus Ujhelyi MODULE_LICENSE("GPL");
450ca7111aSMatus Ujhelyi 
46ea13c9eeSMugunthan V N static int at803x_set_wol(struct phy_device *phydev,
47ea13c9eeSMugunthan V N 			  struct ethtool_wolinfo *wol)
480ca7111aSMatus Ujhelyi {
490ca7111aSMatus Ujhelyi 	struct net_device *ndev = phydev->attached_dev;
500ca7111aSMatus Ujhelyi 	const u8 *mac;
51ea13c9eeSMugunthan V N 	int ret;
52ea13c9eeSMugunthan V N 	u32 value;
530ca7111aSMatus Ujhelyi 	unsigned int i, offsets[] = {
540ca7111aSMatus Ujhelyi 		AT803X_LOC_MAC_ADDR_32_47_OFFSET,
550ca7111aSMatus Ujhelyi 		AT803X_LOC_MAC_ADDR_16_31_OFFSET,
560ca7111aSMatus Ujhelyi 		AT803X_LOC_MAC_ADDR_0_15_OFFSET,
570ca7111aSMatus Ujhelyi 	};
580ca7111aSMatus Ujhelyi 
590ca7111aSMatus Ujhelyi 	if (!ndev)
60ea13c9eeSMugunthan V N 		return -ENODEV;
610ca7111aSMatus Ujhelyi 
62ea13c9eeSMugunthan V N 	if (wol->wolopts & WAKE_MAGIC) {
630ca7111aSMatus Ujhelyi 		mac = (const u8 *) ndev->dev_addr;
640ca7111aSMatus Ujhelyi 
650ca7111aSMatus Ujhelyi 		if (!is_valid_ether_addr(mac))
66ea13c9eeSMugunthan V N 			return -EFAULT;
670ca7111aSMatus Ujhelyi 
680ca7111aSMatus Ujhelyi 		for (i = 0; i < 3; i++) {
690ca7111aSMatus Ujhelyi 			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
700ca7111aSMatus Ujhelyi 				  AT803X_DEVICE_ADDR);
710ca7111aSMatus Ujhelyi 			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
720ca7111aSMatus Ujhelyi 				  offsets[i]);
730ca7111aSMatus Ujhelyi 			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
740ca7111aSMatus Ujhelyi 				  AT803X_FUNC_DATA);
750ca7111aSMatus Ujhelyi 			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
760ca7111aSMatus Ujhelyi 				  mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
770ca7111aSMatus Ujhelyi 		}
78ea13c9eeSMugunthan V N 
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 	} else {
86ea13c9eeSMugunthan V N 		value = phy_read(phydev, AT803X_INTR_ENABLE);
87ea13c9eeSMugunthan V N 		value &= (~AT803X_WOL_ENABLE);
88ea13c9eeSMugunthan V N 		ret = phy_write(phydev, AT803X_INTR_ENABLE, value);
89ea13c9eeSMugunthan V N 		if (ret)
90ea13c9eeSMugunthan V N 			return ret;
91ea13c9eeSMugunthan V N 		value = phy_read(phydev, AT803X_INTR_STATUS);
92ea13c9eeSMugunthan V N 	}
93ea13c9eeSMugunthan V N 
94ea13c9eeSMugunthan V N 	return ret;
95ea13c9eeSMugunthan V N }
96ea13c9eeSMugunthan V N 
97ea13c9eeSMugunthan V N static void at803x_get_wol(struct phy_device *phydev,
98ea13c9eeSMugunthan V N 			   struct ethtool_wolinfo *wol)
99ea13c9eeSMugunthan V N {
100ea13c9eeSMugunthan V N 	u32 value;
101ea13c9eeSMugunthan V N 
102ea13c9eeSMugunthan V N 	wol->supported = WAKE_MAGIC;
103ea13c9eeSMugunthan V N 	wol->wolopts = 0;
104ea13c9eeSMugunthan V N 
105ea13c9eeSMugunthan V N 	value = phy_read(phydev, AT803X_INTR_ENABLE);
106ea13c9eeSMugunthan V N 	if (value & AT803X_WOL_ENABLE)
107ea13c9eeSMugunthan V N 		wol->wolopts |= WAKE_MAGIC;
1080ca7111aSMatus Ujhelyi }
1090ca7111aSMatus Ujhelyi 
1106229ed1fSDaniel Mack static int at803x_suspend(struct phy_device *phydev)
1116229ed1fSDaniel Mack {
1126229ed1fSDaniel Mack 	int value;
1136229ed1fSDaniel Mack 	int wol_enabled;
1146229ed1fSDaniel Mack 
1156229ed1fSDaniel Mack 	mutex_lock(&phydev->lock);
1166229ed1fSDaniel Mack 
1176229ed1fSDaniel Mack 	value = phy_read(phydev, AT803X_INTR_ENABLE);
1186229ed1fSDaniel Mack 	wol_enabled = value & AT803X_WOL_ENABLE;
1196229ed1fSDaniel Mack 
1206229ed1fSDaniel Mack 	value = phy_read(phydev, MII_BMCR);
1216229ed1fSDaniel Mack 
1226229ed1fSDaniel Mack 	if (wol_enabled)
1236229ed1fSDaniel Mack 		value |= BMCR_ISOLATE;
1246229ed1fSDaniel Mack 	else
1256229ed1fSDaniel Mack 		value |= BMCR_PDOWN;
1266229ed1fSDaniel Mack 
1276229ed1fSDaniel Mack 	phy_write(phydev, MII_BMCR, value);
1286229ed1fSDaniel Mack 
1296229ed1fSDaniel Mack 	mutex_unlock(&phydev->lock);
1306229ed1fSDaniel Mack 
1316229ed1fSDaniel Mack 	return 0;
1326229ed1fSDaniel Mack }
1336229ed1fSDaniel Mack 
1346229ed1fSDaniel Mack static int at803x_resume(struct phy_device *phydev)
1356229ed1fSDaniel Mack {
1366229ed1fSDaniel Mack 	int value;
1376229ed1fSDaniel Mack 
1386229ed1fSDaniel Mack 	mutex_lock(&phydev->lock);
1396229ed1fSDaniel Mack 
1406229ed1fSDaniel Mack 	value = phy_read(phydev, MII_BMCR);
1416229ed1fSDaniel Mack 	value &= ~(BMCR_PDOWN | BMCR_ISOLATE);
1426229ed1fSDaniel Mack 	phy_write(phydev, MII_BMCR, value);
1436229ed1fSDaniel Mack 
1446229ed1fSDaniel Mack 	mutex_unlock(&phydev->lock);
1456229ed1fSDaniel Mack 
1466229ed1fSDaniel Mack 	return 0;
1476229ed1fSDaniel Mack }
1486229ed1fSDaniel Mack 
1490ca7111aSMatus Ujhelyi static int at803x_config_init(struct phy_device *phydev)
1500ca7111aSMatus Ujhelyi {
1511ca6d1b1SMugunthan V N 	int ret;
1520ca7111aSMatus Ujhelyi 
1536ff01dbbSDaniel Mack 	ret = genphy_config_init(phydev);
1546ff01dbbSDaniel Mack 	if (ret < 0)
1556ff01dbbSDaniel Mack 		return ret;
1560ca7111aSMatus Ujhelyi 
1571ca6d1b1SMugunthan V N 	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
1581ca6d1b1SMugunthan V N 		ret = phy_write(phydev, AT803X_DEBUG_ADDR,
1591ca6d1b1SMugunthan V N 				AT803X_DEBUG_SYSTEM_MODE_CTRL);
1601ca6d1b1SMugunthan V N 		if (ret)
1611ca6d1b1SMugunthan V N 			return ret;
1621ca6d1b1SMugunthan V N 		ret = phy_write(phydev, AT803X_DEBUG_DATA,
1631ca6d1b1SMugunthan V N 				AT803X_DEBUG_RGMII_TX_CLK_DLY);
1641ca6d1b1SMugunthan V N 		if (ret)
1651ca6d1b1SMugunthan V N 			return ret;
1661ca6d1b1SMugunthan V N 	}
1671ca6d1b1SMugunthan V N 
1680ca7111aSMatus Ujhelyi 	return 0;
1690ca7111aSMatus Ujhelyi }
1700ca7111aSMatus Ujhelyi 
17177a99394SZhao Qiang static int at803x_ack_interrupt(struct phy_device *phydev)
17277a99394SZhao Qiang {
17377a99394SZhao Qiang 	int err;
17477a99394SZhao Qiang 
17577a99394SZhao Qiang 	err = phy_read(phydev, AT803X_INSR);
17677a99394SZhao Qiang 
17777a99394SZhao Qiang 	return (err < 0) ? err : 0;
17877a99394SZhao Qiang }
17977a99394SZhao Qiang 
18077a99394SZhao Qiang static int at803x_config_intr(struct phy_device *phydev)
18177a99394SZhao Qiang {
18277a99394SZhao Qiang 	int err;
18377a99394SZhao Qiang 	int value;
18477a99394SZhao Qiang 
18577a99394SZhao Qiang 	value = phy_read(phydev, AT803X_INER);
18677a99394SZhao Qiang 
18777a99394SZhao Qiang 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
18877a99394SZhao Qiang 		err = phy_write(phydev, AT803X_INER,
18977a99394SZhao Qiang 				value | AT803X_INER_INIT);
19077a99394SZhao Qiang 	else
19177a99394SZhao Qiang 		err = phy_write(phydev, AT803X_INER, 0);
19277a99394SZhao Qiang 
19377a99394SZhao Qiang 	return err;
19477a99394SZhao Qiang }
19577a99394SZhao Qiang 
196317420abSMugunthan V N static struct phy_driver at803x_driver[] = {
197317420abSMugunthan V N {
1980ca7111aSMatus Ujhelyi 	/* ATHEROS 8035 */
199bd8ca17fSDaniel Mack 	.phy_id		= ATH8035_PHY_ID,
2000ca7111aSMatus Ujhelyi 	.name		= "Atheros 8035 ethernet",
2010ca7111aSMatus Ujhelyi 	.phy_id_mask	= 0xffffffef,
2020ca7111aSMatus Ujhelyi 	.config_init	= at803x_config_init,
203ea13c9eeSMugunthan V N 	.set_wol	= at803x_set_wol,
204ea13c9eeSMugunthan V N 	.get_wol	= at803x_get_wol,
2056229ed1fSDaniel Mack 	.suspend	= at803x_suspend,
2066229ed1fSDaniel Mack 	.resume		= at803x_resume,
2070ca7111aSMatus Ujhelyi 	.features	= PHY_GBIT_FEATURES,
2080ca7111aSMatus Ujhelyi 	.flags		= PHY_HAS_INTERRUPT,
2090197ffedSDaniel Mack 	.config_aneg	= genphy_config_aneg,
2100197ffedSDaniel Mack 	.read_status	= genphy_read_status,
2110ca7111aSMatus Ujhelyi 	.driver		= {
2120ca7111aSMatus Ujhelyi 		.owner = THIS_MODULE,
2130ca7111aSMatus Ujhelyi 	},
214317420abSMugunthan V N }, {
2150ca7111aSMatus Ujhelyi 	/* ATHEROS 8030 */
216bd8ca17fSDaniel Mack 	.phy_id		= ATH8030_PHY_ID,
2170ca7111aSMatus Ujhelyi 	.name		= "Atheros 8030 ethernet",
2180ca7111aSMatus Ujhelyi 	.phy_id_mask	= 0xffffffef,
2190ca7111aSMatus Ujhelyi 	.config_init	= at803x_config_init,
220ea13c9eeSMugunthan V N 	.set_wol	= at803x_set_wol,
221ea13c9eeSMugunthan V N 	.get_wol	= at803x_get_wol,
2226229ed1fSDaniel Mack 	.suspend	= at803x_suspend,
2236229ed1fSDaniel Mack 	.resume		= at803x_resume,
2240ca7111aSMatus Ujhelyi 	.features	= PHY_GBIT_FEATURES,
2250ca7111aSMatus Ujhelyi 	.flags		= PHY_HAS_INTERRUPT,
2260197ffedSDaniel Mack 	.config_aneg	= genphy_config_aneg,
2270197ffedSDaniel Mack 	.read_status	= genphy_read_status,
2280ca7111aSMatus Ujhelyi 	.driver		= {
2290ca7111aSMatus Ujhelyi 		.owner = THIS_MODULE,
2300ca7111aSMatus Ujhelyi 	},
23105d7cce8SMugunthan V N }, {
23205d7cce8SMugunthan V N 	/* ATHEROS 8031 */
233bd8ca17fSDaniel Mack 	.phy_id		= ATH8031_PHY_ID,
23405d7cce8SMugunthan V N 	.name		= "Atheros 8031 ethernet",
23505d7cce8SMugunthan V N 	.phy_id_mask	= 0xffffffef,
23605d7cce8SMugunthan V N 	.config_init	= at803x_config_init,
23705d7cce8SMugunthan V N 	.set_wol	= at803x_set_wol,
23805d7cce8SMugunthan V N 	.get_wol	= at803x_get_wol,
2396229ed1fSDaniel Mack 	.suspend	= at803x_suspend,
2406229ed1fSDaniel Mack 	.resume		= at803x_resume,
24105d7cce8SMugunthan V N 	.features	= PHY_GBIT_FEATURES,
24205d7cce8SMugunthan V N 	.flags		= PHY_HAS_INTERRUPT,
2430197ffedSDaniel Mack 	.config_aneg	= genphy_config_aneg,
2440197ffedSDaniel Mack 	.read_status	= genphy_read_status,
24577a99394SZhao Qiang 	.ack_interrupt  = &at803x_ack_interrupt,
24677a99394SZhao Qiang 	.config_intr    = &at803x_config_intr,
24705d7cce8SMugunthan V N 	.driver		= {
24805d7cce8SMugunthan V N 		.owner = THIS_MODULE,
24905d7cce8SMugunthan V N 	},
250317420abSMugunthan V N } };
2510ca7111aSMatus Ujhelyi 
2520ca7111aSMatus Ujhelyi static int __init atheros_init(void)
2530ca7111aSMatus Ujhelyi {
254317420abSMugunthan V N 	return phy_drivers_register(at803x_driver,
255317420abSMugunthan V N 				    ARRAY_SIZE(at803x_driver));
2560ca7111aSMatus Ujhelyi }
2570ca7111aSMatus Ujhelyi 
2580ca7111aSMatus Ujhelyi static void __exit atheros_exit(void)
2590ca7111aSMatus Ujhelyi {
2602ebb1582SShruti Kanetkar 	phy_drivers_unregister(at803x_driver, ARRAY_SIZE(at803x_driver));
2610ca7111aSMatus Ujhelyi }
2620ca7111aSMatus Ujhelyi 
2630ca7111aSMatus Ujhelyi module_init(atheros_init);
2640ca7111aSMatus Ujhelyi module_exit(atheros_exit);
2650ca7111aSMatus Ujhelyi 
2660ca7111aSMatus Ujhelyi static struct mdio_device_id __maybe_unused atheros_tbl[] = {
267bd8ca17fSDaniel Mack 	{ ATH8030_PHY_ID, 0xffffffef },
268bd8ca17fSDaniel Mack 	{ ATH8031_PHY_ID, 0xffffffef },
269bd8ca17fSDaniel Mack 	{ ATH8035_PHY_ID, 0xffffffef },
2700ca7111aSMatus Ujhelyi 	{ }
2710ca7111aSMatus Ujhelyi };
2720ca7111aSMatus Ujhelyi 
2730ca7111aSMatus Ujhelyi MODULE_DEVICE_TABLE(mdio, atheros_tbl);
274