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 }, 18605d7cce8SMugunthan V N }, { 18705d7cce8SMugunthan V N /* ATHEROS 8031 */ 18805d7cce8SMugunthan V N .phy_id = 0x004dd074, 18905d7cce8SMugunthan V N .name = "Atheros 8031 ethernet", 19005d7cce8SMugunthan V N .phy_id_mask = 0xffffffef, 19105d7cce8SMugunthan V N .config_init = at803x_config_init, 19205d7cce8SMugunthan V N .set_wol = at803x_set_wol, 19305d7cce8SMugunthan V N .get_wol = at803x_get_wol, 19405d7cce8SMugunthan V N .features = PHY_GBIT_FEATURES, 19505d7cce8SMugunthan V N .flags = PHY_HAS_INTERRUPT, 19605d7cce8SMugunthan V N .config_aneg = &genphy_config_aneg, 19705d7cce8SMugunthan V N .read_status = &genphy_read_status, 19805d7cce8SMugunthan V N .driver = { 19905d7cce8SMugunthan V N .owner = THIS_MODULE, 20005d7cce8SMugunthan V N }, 201317420abSMugunthan V N } }; 2020ca7111aSMatus Ujhelyi 2030ca7111aSMatus Ujhelyi static int __init atheros_init(void) 2040ca7111aSMatus Ujhelyi { 205317420abSMugunthan V N return phy_drivers_register(at803x_driver, 206317420abSMugunthan V N ARRAY_SIZE(at803x_driver)); 2070ca7111aSMatus Ujhelyi } 2080ca7111aSMatus Ujhelyi 2090ca7111aSMatus Ujhelyi static void __exit atheros_exit(void) 2100ca7111aSMatus Ujhelyi { 211317420abSMugunthan V N return phy_drivers_unregister(at803x_driver, 212317420abSMugunthan V N ARRAY_SIZE(at803x_driver)); 2130ca7111aSMatus Ujhelyi } 2140ca7111aSMatus Ujhelyi 2150ca7111aSMatus Ujhelyi module_init(atheros_init); 2160ca7111aSMatus Ujhelyi module_exit(atheros_exit); 2170ca7111aSMatus Ujhelyi 2180ca7111aSMatus Ujhelyi static struct mdio_device_id __maybe_unused atheros_tbl[] = { 2190ca7111aSMatus Ujhelyi { 0x004dd076, 0xffffffef }, 2200ca7111aSMatus Ujhelyi { 0x004dd072, 0xffffffef }, 2210ca7111aSMatus Ujhelyi { } 2220ca7111aSMatus Ujhelyi }; 2230ca7111aSMatus Ujhelyi 2240ca7111aSMatus Ujhelyi MODULE_DEVICE_TABLE(mdio, atheros_tbl); 225