/* * Driver for Aquantia PHY * * Author: Shaohui Xie * * Copyright 2015 Freescale Semiconductor, Inc. * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. */ #include #include #include #include #include #include #include #define PHY_ID_AQ1202 0x03a1b445 #define PHY_ID_AQ2104 0x03a1b460 #define PHY_ID_AQR105 0x03a1b4a2 #define PHY_ID_AQR107 0x03a1b4e0 #define PHY_ID_AQR405 0x03a1b4b0 #define AQ_MDIO_AN_VENDOR_1 0xC400 #define AQ_MDIO_AN_VENDOR_1_ADV1G 0x8000 #define PHY_AQUANTIA_FEATURES (SUPPORTED_10000baseT_Full | \ SUPPORTED_1000baseT_Full | \ SUPPORTED_100baseT_Full | \ PHY_DEFAULT_FEATURES) static int aquantia_c45_restart_aneg(struct phy_device *phydev) { int val; val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); if (val < 0) return val; val |= MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART; return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, val); } static int aquantia_config_advert(struct phy_device *phydev) { int oldadv, adv; int err, changed = 0; /* Setup standard advertisement */ adv = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE); if (adv < 0) return adv; oldadv = adv; adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4 | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); adv |= ethtool_adv_to_mii_adv_t(phydev->advertising); if (adv != oldadv) { err = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE, adv); if (err < 0) return err; changed = 1; } /* Configure gigabit if it's supported */ adv = phy_read_mmd(phydev, MDIO_MMD_AN, AQ_MDIO_AN_VENDOR_1); if (adv < 0) return adv; oldadv = adv; if (phydev->advertising & SUPPORTED_1000baseT_Full) adv |= AQ_MDIO_AN_VENDOR_1_ADV1G; else adv &= ~AQ_MDIO_AN_VENDOR_1_ADV1G; if (adv != oldadv) { changed = 1; err = phy_write_mmd(phydev, MDIO_MMD_AN, AQ_MDIO_AN_VENDOR_1, adv); if (err < 0) return err; } /* Configure 10Gigabit */ adv = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL); if (adv < 0) return adv; oldadv = adv; if (phydev->advertising & SUPPORTED_10000baseT_Full) adv |= MDIO_AN_10GBT_CTRL_ADV10G; else adv &= ~MDIO_AN_10GBT_CTRL_ADV10G; if (adv != oldadv) { changed = 1; err = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL, adv); if (err < 0) return err; } return changed; } static int aquantia_config_aneg(struct phy_device *phydev) { int err; phydev->supported = PHY_AQUANTIA_FEATURES; if (phydev->autoneg != AUTONEG_ENABLE) { pr_err("%s - Autoneg-off is not supported\n", __func__); /* revert advertising mode to default */ phydev->advertising = phydev->supported; return -EINVAL; } err = aquantia_config_advert(phydev); if (err > 0) err = aquantia_c45_restart_aneg(phydev); return err; } static int aquantia_aneg_done(struct phy_device *phydev) { int reg; reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); return (reg < 0) ? reg : (reg & BMSR_ANEGCOMPLETE); } static int aquantia_config_intr(struct phy_device *phydev) { int err; if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 1); if (err < 0) return err; err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 1); if (err < 0) return err; err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0x1001); } else { err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 0); if (err < 0) return err; err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 0); if (err < 0) return err; err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0); } return err; } static int aquantia_ack_interrupt(struct phy_device *phydev) { int reg; reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xcc01); return (reg < 0) ? reg : 0; } static int aquantia_read_status(struct phy_device *phydev) { int reg; reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); if (reg & MDIO_STAT1_LSTATUS) phydev->link = 1; else phydev->link = 0; reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800); mdelay(10); reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800); switch (reg) { case 0x9: phydev->speed = SPEED_2500; break; case 0x5: phydev->speed = SPEED_1000; break; case 0x3: phydev->speed = SPEED_100; break; case 0x7: default: phydev->speed = SPEED_10000; break; } phydev->duplex = DUPLEX_FULL; return 0; } static struct phy_driver aquantia_driver[] = { { .phy_id = PHY_ID_AQ1202, .phy_id_mask = 0xfffffff0, .name = "Aquantia AQ1202", .features = PHY_AQUANTIA_FEATURES, .flags = PHY_HAS_INTERRUPT, .aneg_done = aquantia_aneg_done, .config_aneg = aquantia_config_aneg, .config_intr = aquantia_config_intr, .ack_interrupt = aquantia_ack_interrupt, .read_status = aquantia_read_status, }, { .phy_id = PHY_ID_AQ2104, .phy_id_mask = 0xfffffff0, .name = "Aquantia AQ2104", .features = PHY_AQUANTIA_FEATURES, .flags = PHY_HAS_INTERRUPT, .aneg_done = aquantia_aneg_done, .config_aneg = aquantia_config_aneg, .config_intr = aquantia_config_intr, .ack_interrupt = aquantia_ack_interrupt, .read_status = aquantia_read_status, }, { .phy_id = PHY_ID_AQR105, .phy_id_mask = 0xfffffff0, .name = "Aquantia AQR105", .features = PHY_AQUANTIA_FEATURES, .flags = PHY_HAS_INTERRUPT, .aneg_done = aquantia_aneg_done, .config_aneg = aquantia_config_aneg, .config_intr = aquantia_config_intr, .ack_interrupt = aquantia_ack_interrupt, .read_status = aquantia_read_status, }, { .phy_id = PHY_ID_AQR107, .phy_id_mask = 0xfffffff0, .name = "Aquantia AQR107", .features = PHY_AQUANTIA_FEATURES, .flags = PHY_HAS_INTERRUPT, .aneg_done = aquantia_aneg_done, .config_aneg = aquantia_config_aneg, .config_intr = aquantia_config_intr, .ack_interrupt = aquantia_ack_interrupt, .read_status = aquantia_read_status, }, { .phy_id = PHY_ID_AQR405, .phy_id_mask = 0xfffffff0, .name = "Aquantia AQR405", .features = PHY_AQUANTIA_FEATURES, .flags = PHY_HAS_INTERRUPT, .aneg_done = aquantia_aneg_done, .config_aneg = aquantia_config_aneg, .config_intr = aquantia_config_intr, .ack_interrupt = aquantia_ack_interrupt, .read_status = aquantia_read_status, }, }; module_phy_driver(aquantia_driver); static struct mdio_device_id __maybe_unused aquantia_tbl[] = { { PHY_ID_AQ1202, 0xfffffff0 }, { PHY_ID_AQ2104, 0xfffffff0 }, { PHY_ID_AQR105, 0xfffffff0 }, { PHY_ID_AQR107, 0xfffffff0 }, { PHY_ID_AQR405, 0xfffffff0 }, {} }; MODULE_DEVICE_TABLE(mdio, aquantia_tbl); MODULE_DESCRIPTION("Aquantia PHY driver"); MODULE_AUTHOR("Shaohui Xie "); MODULE_LICENSE("GPL v2");