1/* 2 * Driver for Aquantia PHY 3 * 4 * Author: Shaohui Xie <Shaohui.Xie@freescale.com> 5 * 6 * Copyright 2015 Freescale Semiconductor, Inc. 7 * 8 * This file is licensed under the terms of the GNU General Public License 9 * version 2. This program is licensed "as is" without any warranty of any 10 * kind, whether express or implied. 11 */ 12 13#include <linux/kernel.h> 14#include <linux/module.h> 15#include <linux/delay.h> 16#include <linux/mii.h> 17#include <linux/ethtool.h> 18#include <linux/phy.h> 19#include <linux/mdio.h> 20 21#define PHY_ID_AQ1202 0x03a1b445 22#define PHY_ID_AQ2104 0x03a1b460 23#define PHY_ID_AQR105 0x03a1b4a2 24#define PHY_ID_AQR405 0x03a1b4b0 25 26#define PHY_AQUANTIA_FEATURES (SUPPORTED_10000baseT_Full | \ 27 SUPPORTED_1000baseT_Full | \ 28 SUPPORTED_100baseT_Full | \ 29 PHY_DEFAULT_FEATURES) 30 31static int aquantia_config_aneg(struct phy_device *phydev) 32{ 33 phydev->supported = PHY_AQUANTIA_FEATURES; 34 phydev->advertising = phydev->supported; 35 36 return 0; 37} 38 39static int aquantia_aneg_done(struct phy_device *phydev) 40{ 41 int reg; 42 43 reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); 44 return (reg < 0) ? reg : (reg & BMSR_ANEGCOMPLETE); 45} 46 47static int aquantia_config_intr(struct phy_device *phydev) 48{ 49 int err; 50 51 if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 52 err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 1); 53 if (err < 0) 54 return err; 55 56 err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 1); 57 if (err < 0) 58 return err; 59 60 err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0x1001); 61 } else { 62 err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 0); 63 if (err < 0) 64 return err; 65 66 err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 0); 67 if (err < 0) 68 return err; 69 70 err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0); 71 } 72 73 return err; 74} 75 76static int aquantia_ack_interrupt(struct phy_device *phydev) 77{ 78 int reg; 79 80 reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xcc01); 81 return (reg < 0) ? reg : 0; 82} 83 84static int aquantia_read_status(struct phy_device *phydev) 85{ 86 int reg; 87 88 reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); 89 reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); 90 if (reg & MDIO_STAT1_LSTATUS) 91 phydev->link = 1; 92 else 93 phydev->link = 0; 94 95 reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800); 96 mdelay(10); 97 reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800); 98 99 switch (reg) { 100 case 0x9: 101 phydev->speed = SPEED_2500; 102 break; 103 case 0x5: 104 phydev->speed = SPEED_1000; 105 break; 106 case 0x3: 107 phydev->speed = SPEED_100; 108 break; 109 case 0x7: 110 default: 111 phydev->speed = SPEED_10000; 112 break; 113 } 114 phydev->duplex = DUPLEX_FULL; 115 116 return 0; 117} 118 119static struct phy_driver aquantia_driver[] = { 120{ 121 .phy_id = PHY_ID_AQ1202, 122 .phy_id_mask = 0xfffffff0, 123 .name = "Aquantia AQ1202", 124 .features = PHY_AQUANTIA_FEATURES, 125 .flags = PHY_HAS_INTERRUPT, 126 .aneg_done = aquantia_aneg_done, 127 .config_aneg = aquantia_config_aneg, 128 .config_intr = aquantia_config_intr, 129 .ack_interrupt = aquantia_ack_interrupt, 130 .read_status = aquantia_read_status, 131 .driver = { .owner = THIS_MODULE,}, 132}, 133{ 134 .phy_id = PHY_ID_AQ2104, 135 .phy_id_mask = 0xfffffff0, 136 .name = "Aquantia AQ2104", 137 .features = PHY_AQUANTIA_FEATURES, 138 .flags = PHY_HAS_INTERRUPT, 139 .aneg_done = aquantia_aneg_done, 140 .config_aneg = aquantia_config_aneg, 141 .config_intr = aquantia_config_intr, 142 .ack_interrupt = aquantia_ack_interrupt, 143 .read_status = aquantia_read_status, 144 .driver = { .owner = THIS_MODULE,}, 145}, 146{ 147 .phy_id = PHY_ID_AQR105, 148 .phy_id_mask = 0xfffffff0, 149 .name = "Aquantia AQR105", 150 .features = PHY_AQUANTIA_FEATURES, 151 .flags = PHY_HAS_INTERRUPT, 152 .aneg_done = aquantia_aneg_done, 153 .config_aneg = aquantia_config_aneg, 154 .config_intr = aquantia_config_intr, 155 .ack_interrupt = aquantia_ack_interrupt, 156 .read_status = aquantia_read_status, 157 .driver = { .owner = THIS_MODULE,}, 158}, 159{ 160 .phy_id = PHY_ID_AQR405, 161 .phy_id_mask = 0xfffffff0, 162 .name = "Aquantia AQR405", 163 .features = PHY_AQUANTIA_FEATURES, 164 .flags = PHY_HAS_INTERRUPT, 165 .aneg_done = aquantia_aneg_done, 166 .config_aneg = aquantia_config_aneg, 167 .config_intr = aquantia_config_intr, 168 .ack_interrupt = aquantia_ack_interrupt, 169 .read_status = aquantia_read_status, 170 .driver = { .owner = THIS_MODULE,}, 171}, 172}; 173 174module_phy_driver(aquantia_driver); 175 176static struct mdio_device_id __maybe_unused aquantia_tbl[] = { 177 { PHY_ID_AQ1202, 0xfffffff0 }, 178 { PHY_ID_AQ2104, 0xfffffff0 }, 179 { PHY_ID_AQR105, 0xfffffff0 }, 180 { PHY_ID_AQR405, 0xfffffff0 }, 181 { } 182}; 183 184MODULE_DEVICE_TABLE(mdio, aquantia_tbl); 185 186MODULE_DESCRIPTION("Aquantia PHY driver"); 187MODULE_AUTHOR("Shaohui Xie <Shaohui.Xie@freescale.com>"); 188MODULE_LICENSE("GPL v2"); 189