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