18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 10G controller driver for Samsung SoCs 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2013 Samsung Electronics Co., Ltd. 58c2ecf20Sopenharmony_ci * http://www.samsung.com 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Siva Reddy Kallam <siva.kallam@samsung.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/io.h> 138c2ecf20Sopenharmony_ci#include <linux/mii.h> 148c2ecf20Sopenharmony_ci#include <linux/netdevice.h> 158c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 168c2ecf20Sopenharmony_ci#include <linux/phy.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/sxgbe_platform.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include "sxgbe_common.h" 218c2ecf20Sopenharmony_ci#include "sxgbe_reg.h" 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define SXGBE_SMA_WRITE_CMD 0x01 /* write command */ 248c2ecf20Sopenharmony_ci#define SXGBE_SMA_PREAD_CMD 0x02 /* post read increament address */ 258c2ecf20Sopenharmony_ci#define SXGBE_SMA_READ_CMD 0x03 /* read command */ 268c2ecf20Sopenharmony_ci#define SXGBE_SMA_SKIP_ADDRFRM 0x00040000 /* skip the address frame */ 278c2ecf20Sopenharmony_ci#define SXGBE_MII_BUSY 0x00400000 /* mii busy */ 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_cistatic int sxgbe_mdio_busy_wait(void __iomem *ioaddr, unsigned int mii_data) 308c2ecf20Sopenharmony_ci{ 318c2ecf20Sopenharmony_ci unsigned long fin_time = jiffies + 3 * HZ; /* 3 seconds */ 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci while (!time_after(jiffies, fin_time)) { 348c2ecf20Sopenharmony_ci if (!(readl(ioaddr + mii_data) & SXGBE_MII_BUSY)) 358c2ecf20Sopenharmony_ci return 0; 368c2ecf20Sopenharmony_ci cpu_relax(); 378c2ecf20Sopenharmony_ci } 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci return -EBUSY; 408c2ecf20Sopenharmony_ci} 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic void sxgbe_mdio_ctrl_data(struct sxgbe_priv_data *sp, u32 cmd, 438c2ecf20Sopenharmony_ci u16 phydata) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci u32 reg = phydata; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci reg |= (cmd << 16) | SXGBE_SMA_SKIP_ADDRFRM | 488c2ecf20Sopenharmony_ci ((sp->clk_csr & 0x7) << 19) | SXGBE_MII_BUSY; 498c2ecf20Sopenharmony_ci writel(reg, sp->ioaddr + sp->hw->mii.data); 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic void sxgbe_mdio_c45(struct sxgbe_priv_data *sp, u32 cmd, int phyaddr, 538c2ecf20Sopenharmony_ci int phyreg, u16 phydata) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci u32 reg; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci /* set mdio address register */ 588c2ecf20Sopenharmony_ci reg = ((phyreg >> 16) & 0x1f) << 21; 598c2ecf20Sopenharmony_ci reg |= (phyaddr << 16) | (phyreg & 0xffff); 608c2ecf20Sopenharmony_ci writel(reg, sp->ioaddr + sp->hw->mii.addr); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci sxgbe_mdio_ctrl_data(sp, cmd, phydata); 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic void sxgbe_mdio_c22(struct sxgbe_priv_data *sp, u32 cmd, int phyaddr, 668c2ecf20Sopenharmony_ci int phyreg, u16 phydata) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci u32 reg; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci writel(1 << phyaddr, sp->ioaddr + SXGBE_MDIO_CLAUSE22_PORT_REG); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci /* set mdio address register */ 738c2ecf20Sopenharmony_ci reg = (phyaddr << 16) | (phyreg & 0x1f); 748c2ecf20Sopenharmony_ci writel(reg, sp->ioaddr + sp->hw->mii.addr); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci sxgbe_mdio_ctrl_data(sp, cmd, phydata); 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic int sxgbe_mdio_access(struct sxgbe_priv_data *sp, u32 cmd, int phyaddr, 808c2ecf20Sopenharmony_ci int phyreg, u16 phydata) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci const struct mii_regs *mii = &sp->hw->mii; 838c2ecf20Sopenharmony_ci int rc; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci rc = sxgbe_mdio_busy_wait(sp->ioaddr, mii->data); 868c2ecf20Sopenharmony_ci if (rc < 0) 878c2ecf20Sopenharmony_ci return rc; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (phyreg & MII_ADDR_C45) { 908c2ecf20Sopenharmony_ci sxgbe_mdio_c45(sp, cmd, phyaddr, phyreg, phydata); 918c2ecf20Sopenharmony_ci } else { 928c2ecf20Sopenharmony_ci /* Ports 0-3 only support C22. */ 938c2ecf20Sopenharmony_ci if (phyaddr >= 4) 948c2ecf20Sopenharmony_ci return -ENODEV; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci sxgbe_mdio_c22(sp, cmd, phyaddr, phyreg, phydata); 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci return sxgbe_mdio_busy_wait(sp->ioaddr, mii->data); 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci/** 1038c2ecf20Sopenharmony_ci * sxgbe_mdio_read 1048c2ecf20Sopenharmony_ci * @bus: points to the mii_bus structure 1058c2ecf20Sopenharmony_ci * @phyaddr: address of phy port 1068c2ecf20Sopenharmony_ci * @phyreg: address of register with in phy register 1078c2ecf20Sopenharmony_ci * Description: this function used for C45 and C22 MDIO Read 1088c2ecf20Sopenharmony_ci */ 1098c2ecf20Sopenharmony_cistatic int sxgbe_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci struct net_device *ndev = bus->priv; 1128c2ecf20Sopenharmony_ci struct sxgbe_priv_data *priv = netdev_priv(ndev); 1138c2ecf20Sopenharmony_ci int rc; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci rc = sxgbe_mdio_access(priv, SXGBE_SMA_READ_CMD, phyaddr, phyreg, 0); 1168c2ecf20Sopenharmony_ci if (rc < 0) 1178c2ecf20Sopenharmony_ci return rc; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci return readl(priv->ioaddr + priv->hw->mii.data) & 0xffff; 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci/** 1238c2ecf20Sopenharmony_ci * sxgbe_mdio_write 1248c2ecf20Sopenharmony_ci * @bus: points to the mii_bus structure 1258c2ecf20Sopenharmony_ci * @phyaddr: address of phy port 1268c2ecf20Sopenharmony_ci * @phyreg: address of phy registers 1278c2ecf20Sopenharmony_ci * @phydata: data to be written into phy register 1288c2ecf20Sopenharmony_ci * Description: this function is used for C45 and C22 MDIO write 1298c2ecf20Sopenharmony_ci */ 1308c2ecf20Sopenharmony_cistatic int sxgbe_mdio_write(struct mii_bus *bus, int phyaddr, int phyreg, 1318c2ecf20Sopenharmony_ci u16 phydata) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci struct net_device *ndev = bus->priv; 1348c2ecf20Sopenharmony_ci struct sxgbe_priv_data *priv = netdev_priv(ndev); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci return sxgbe_mdio_access(priv, SXGBE_SMA_WRITE_CMD, phyaddr, phyreg, 1378c2ecf20Sopenharmony_ci phydata); 1388c2ecf20Sopenharmony_ci} 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ciint sxgbe_mdio_register(struct net_device *ndev) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci struct mii_bus *mdio_bus; 1438c2ecf20Sopenharmony_ci struct sxgbe_priv_data *priv = netdev_priv(ndev); 1448c2ecf20Sopenharmony_ci struct sxgbe_mdio_bus_data *mdio_data = priv->plat->mdio_bus_data; 1458c2ecf20Sopenharmony_ci int err, phy_addr; 1468c2ecf20Sopenharmony_ci int *irqlist; 1478c2ecf20Sopenharmony_ci bool phy_found = false; 1488c2ecf20Sopenharmony_ci bool act; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci /* allocate the new mdio bus */ 1518c2ecf20Sopenharmony_ci mdio_bus = mdiobus_alloc(); 1528c2ecf20Sopenharmony_ci if (!mdio_bus) { 1538c2ecf20Sopenharmony_ci netdev_err(ndev, "%s: mii bus allocation failed\n", __func__); 1548c2ecf20Sopenharmony_ci return -ENOMEM; 1558c2ecf20Sopenharmony_ci } 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci if (mdio_data->irqs) 1588c2ecf20Sopenharmony_ci irqlist = mdio_data->irqs; 1598c2ecf20Sopenharmony_ci else 1608c2ecf20Sopenharmony_ci irqlist = priv->mii_irq; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci /* assign mii bus fields */ 1638c2ecf20Sopenharmony_ci mdio_bus->name = "sxgbe"; 1648c2ecf20Sopenharmony_ci mdio_bus->read = &sxgbe_mdio_read; 1658c2ecf20Sopenharmony_ci mdio_bus->write = &sxgbe_mdio_write; 1668c2ecf20Sopenharmony_ci snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s-%x", 1678c2ecf20Sopenharmony_ci mdio_bus->name, priv->plat->bus_id); 1688c2ecf20Sopenharmony_ci mdio_bus->priv = ndev; 1698c2ecf20Sopenharmony_ci mdio_bus->phy_mask = mdio_data->phy_mask; 1708c2ecf20Sopenharmony_ci mdio_bus->parent = priv->device; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci /* register with kernel subsystem */ 1738c2ecf20Sopenharmony_ci err = mdiobus_register(mdio_bus); 1748c2ecf20Sopenharmony_ci if (err != 0) { 1758c2ecf20Sopenharmony_ci netdev_err(ndev, "mdiobus register failed\n"); 1768c2ecf20Sopenharmony_ci goto mdiobus_err; 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) { 1808c2ecf20Sopenharmony_ci struct phy_device *phy = mdiobus_get_phy(mdio_bus, phy_addr); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci if (phy) { 1838c2ecf20Sopenharmony_ci char irq_num[4]; 1848c2ecf20Sopenharmony_ci char *irq_str; 1858c2ecf20Sopenharmony_ci /* If an IRQ was provided to be assigned after 1868c2ecf20Sopenharmony_ci * the bus probe, do it here. 1878c2ecf20Sopenharmony_ci */ 1888c2ecf20Sopenharmony_ci if ((mdio_data->irqs == NULL) && 1898c2ecf20Sopenharmony_ci (mdio_data->probed_phy_irq > 0)) { 1908c2ecf20Sopenharmony_ci irqlist[phy_addr] = mdio_data->probed_phy_irq; 1918c2ecf20Sopenharmony_ci phy->irq = mdio_data->probed_phy_irq; 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci /* If we're going to bind the MAC to this PHY bus, 1958c2ecf20Sopenharmony_ci * and no PHY number was provided to the MAC, 1968c2ecf20Sopenharmony_ci * use the one probed here. 1978c2ecf20Sopenharmony_ci */ 1988c2ecf20Sopenharmony_ci if (priv->plat->phy_addr == -1) 1998c2ecf20Sopenharmony_ci priv->plat->phy_addr = phy_addr; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci act = (priv->plat->phy_addr == phy_addr); 2028c2ecf20Sopenharmony_ci switch (phy->irq) { 2038c2ecf20Sopenharmony_ci case PHY_POLL: 2048c2ecf20Sopenharmony_ci irq_str = "POLL"; 2058c2ecf20Sopenharmony_ci break; 2068c2ecf20Sopenharmony_ci case PHY_IGNORE_INTERRUPT: 2078c2ecf20Sopenharmony_ci irq_str = "IGNORE"; 2088c2ecf20Sopenharmony_ci break; 2098c2ecf20Sopenharmony_ci default: 2108c2ecf20Sopenharmony_ci sprintf(irq_num, "%d", phy->irq); 2118c2ecf20Sopenharmony_ci irq_str = irq_num; 2128c2ecf20Sopenharmony_ci break; 2138c2ecf20Sopenharmony_ci } 2148c2ecf20Sopenharmony_ci netdev_info(ndev, "PHY ID %08x at %d IRQ %s (%s)%s\n", 2158c2ecf20Sopenharmony_ci phy->phy_id, phy_addr, irq_str, 2168c2ecf20Sopenharmony_ci phydev_name(phy), act ? " active" : ""); 2178c2ecf20Sopenharmony_ci phy_found = true; 2188c2ecf20Sopenharmony_ci } 2198c2ecf20Sopenharmony_ci } 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci if (!phy_found) { 2228c2ecf20Sopenharmony_ci netdev_err(ndev, "PHY not found\n"); 2238c2ecf20Sopenharmony_ci goto phyfound_err; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci priv->mii = mdio_bus; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci return 0; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ciphyfound_err: 2318c2ecf20Sopenharmony_ci err = -ENODEV; 2328c2ecf20Sopenharmony_ci mdiobus_unregister(mdio_bus); 2338c2ecf20Sopenharmony_cimdiobus_err: 2348c2ecf20Sopenharmony_ci mdiobus_free(mdio_bus); 2358c2ecf20Sopenharmony_ci return err; 2368c2ecf20Sopenharmony_ci} 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ciint sxgbe_mdio_unregister(struct net_device *ndev) 2398c2ecf20Sopenharmony_ci{ 2408c2ecf20Sopenharmony_ci struct sxgbe_priv_data *priv = netdev_priv(ndev); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci if (!priv->mii) 2438c2ecf20Sopenharmony_ci return 0; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci mdiobus_unregister(priv->mii); 2468c2ecf20Sopenharmony_ci priv->mii->priv = NULL; 2478c2ecf20Sopenharmony_ci mdiobus_free(priv->mii); 2488c2ecf20Sopenharmony_ci priv->mii = NULL; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci return 0; 2518c2ecf20Sopenharmony_ci} 252