162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 10G controller driver for Samsung SoCs 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (C) 2013 Samsung Electronics Co., Ltd. 562306a36Sopenharmony_ci * http://www.samsung.com 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Siva Reddy Kallam <siva.kallam@samsung.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/io.h> 1362306a36Sopenharmony_ci#include <linux/mii.h> 1462306a36Sopenharmony_ci#include <linux/netdevice.h> 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci#include <linux/phy.h> 1762306a36Sopenharmony_ci#include <linux/slab.h> 1862306a36Sopenharmony_ci#include <linux/sxgbe_platform.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include "sxgbe_common.h" 2162306a36Sopenharmony_ci#include "sxgbe_reg.h" 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define SXGBE_SMA_WRITE_CMD 0x01 /* write command */ 2462306a36Sopenharmony_ci#define SXGBE_SMA_PREAD_CMD 0x02 /* post read increament address */ 2562306a36Sopenharmony_ci#define SXGBE_SMA_READ_CMD 0x03 /* read command */ 2662306a36Sopenharmony_ci#define SXGBE_SMA_SKIP_ADDRFRM 0x00040000 /* skip the address frame */ 2762306a36Sopenharmony_ci#define SXGBE_MII_BUSY 0x00400000 /* mii busy */ 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic int sxgbe_mdio_busy_wait(void __iomem *ioaddr, unsigned int mii_data) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci unsigned long fin_time = jiffies + 3 * HZ; /* 3 seconds */ 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci while (!time_after(jiffies, fin_time)) { 3462306a36Sopenharmony_ci if (!(readl(ioaddr + mii_data) & SXGBE_MII_BUSY)) 3562306a36Sopenharmony_ci return 0; 3662306a36Sopenharmony_ci cpu_relax(); 3762306a36Sopenharmony_ci } 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci return -EBUSY; 4062306a36Sopenharmony_ci} 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic void sxgbe_mdio_ctrl_data(struct sxgbe_priv_data *sp, u32 cmd, 4362306a36Sopenharmony_ci u16 phydata) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci u32 reg = phydata; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci reg |= (cmd << 16) | SXGBE_SMA_SKIP_ADDRFRM | 4862306a36Sopenharmony_ci ((sp->clk_csr & 0x7) << 19) | SXGBE_MII_BUSY; 4962306a36Sopenharmony_ci writel(reg, sp->ioaddr + sp->hw->mii.data); 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic void sxgbe_mdio_c45(struct sxgbe_priv_data *sp, u32 cmd, int phyaddr, 5362306a36Sopenharmony_ci int devad, int phyreg, u16 phydata) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci u32 reg; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci /* set mdio address register */ 5862306a36Sopenharmony_ci reg = (devad & 0x1f) << 21; 5962306a36Sopenharmony_ci reg |= (phyaddr << 16) | (phyreg & 0xffff); 6062306a36Sopenharmony_ci writel(reg, sp->ioaddr + sp->hw->mii.addr); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci sxgbe_mdio_ctrl_data(sp, cmd, phydata); 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic void sxgbe_mdio_c22(struct sxgbe_priv_data *sp, u32 cmd, int phyaddr, 6662306a36Sopenharmony_ci int phyreg, u16 phydata) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci u32 reg; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci writel(1 << phyaddr, sp->ioaddr + SXGBE_MDIO_CLAUSE22_PORT_REG); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci /* set mdio address register */ 7362306a36Sopenharmony_ci reg = (phyaddr << 16) | (phyreg & 0x1f); 7462306a36Sopenharmony_ci writel(reg, sp->ioaddr + sp->hw->mii.addr); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci sxgbe_mdio_ctrl_data(sp, cmd, phydata); 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic int sxgbe_mdio_access_c22(struct sxgbe_priv_data *sp, u32 cmd, 8062306a36Sopenharmony_ci int phyaddr, int phyreg, u16 phydata) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci const struct mii_regs *mii = &sp->hw->mii; 8362306a36Sopenharmony_ci int rc; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci rc = sxgbe_mdio_busy_wait(sp->ioaddr, mii->data); 8662306a36Sopenharmony_ci if (rc < 0) 8762306a36Sopenharmony_ci return rc; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci /* Ports 0-3 only support C22. */ 9062306a36Sopenharmony_ci if (phyaddr >= 4) 9162306a36Sopenharmony_ci return -ENODEV; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci sxgbe_mdio_c22(sp, cmd, phyaddr, phyreg, phydata); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci return sxgbe_mdio_busy_wait(sp->ioaddr, mii->data); 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic int sxgbe_mdio_access_c45(struct sxgbe_priv_data *sp, u32 cmd, 9962306a36Sopenharmony_ci int phyaddr, int devad, int phyreg, 10062306a36Sopenharmony_ci u16 phydata) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci const struct mii_regs *mii = &sp->hw->mii; 10362306a36Sopenharmony_ci int rc; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci rc = sxgbe_mdio_busy_wait(sp->ioaddr, mii->data); 10662306a36Sopenharmony_ci if (rc < 0) 10762306a36Sopenharmony_ci return rc; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci sxgbe_mdio_c45(sp, cmd, phyaddr, devad, phyreg, phydata); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci return sxgbe_mdio_busy_wait(sp->ioaddr, mii->data); 11262306a36Sopenharmony_ci} 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci/** 11562306a36Sopenharmony_ci * sxgbe_mdio_read_c22 11662306a36Sopenharmony_ci * @bus: points to the mii_bus structure 11762306a36Sopenharmony_ci * @phyaddr: address of phy port 11862306a36Sopenharmony_ci * @phyreg: address of register with in phy register 11962306a36Sopenharmony_ci * Description: this function used for C22 MDIO Read 12062306a36Sopenharmony_ci */ 12162306a36Sopenharmony_cistatic int sxgbe_mdio_read_c22(struct mii_bus *bus, int phyaddr, int phyreg) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci struct net_device *ndev = bus->priv; 12462306a36Sopenharmony_ci struct sxgbe_priv_data *priv = netdev_priv(ndev); 12562306a36Sopenharmony_ci int rc; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci rc = sxgbe_mdio_access_c22(priv, SXGBE_SMA_READ_CMD, phyaddr, 12862306a36Sopenharmony_ci phyreg, 0); 12962306a36Sopenharmony_ci if (rc < 0) 13062306a36Sopenharmony_ci return rc; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci return readl(priv->ioaddr + priv->hw->mii.data) & 0xffff; 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci/** 13662306a36Sopenharmony_ci * sxgbe_mdio_read_c45 13762306a36Sopenharmony_ci * @bus: points to the mii_bus structure 13862306a36Sopenharmony_ci * @phyaddr: address of phy port 13962306a36Sopenharmony_ci * @devad: device (MMD) address 14062306a36Sopenharmony_ci * @phyreg: address of register with in phy register 14162306a36Sopenharmony_ci * Description: this function used for C45 MDIO Read 14262306a36Sopenharmony_ci */ 14362306a36Sopenharmony_cistatic int sxgbe_mdio_read_c45(struct mii_bus *bus, int phyaddr, int devad, 14462306a36Sopenharmony_ci int phyreg) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci struct net_device *ndev = bus->priv; 14762306a36Sopenharmony_ci struct sxgbe_priv_data *priv = netdev_priv(ndev); 14862306a36Sopenharmony_ci int rc; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci rc = sxgbe_mdio_access_c45(priv, SXGBE_SMA_READ_CMD, phyaddr, 15162306a36Sopenharmony_ci devad, phyreg, 0); 15262306a36Sopenharmony_ci if (rc < 0) 15362306a36Sopenharmony_ci return rc; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci return readl(priv->ioaddr + priv->hw->mii.data) & 0xffff; 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci/** 15962306a36Sopenharmony_ci * sxgbe_mdio_write_c22 16062306a36Sopenharmony_ci * @bus: points to the mii_bus structure 16162306a36Sopenharmony_ci * @phyaddr: address of phy port 16262306a36Sopenharmony_ci * @phyreg: address of phy registers 16362306a36Sopenharmony_ci * @phydata: data to be written into phy register 16462306a36Sopenharmony_ci * Description: this function is used for C22 MDIO write 16562306a36Sopenharmony_ci */ 16662306a36Sopenharmony_cistatic int sxgbe_mdio_write_c22(struct mii_bus *bus, int phyaddr, int phyreg, 16762306a36Sopenharmony_ci u16 phydata) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci struct net_device *ndev = bus->priv; 17062306a36Sopenharmony_ci struct sxgbe_priv_data *priv = netdev_priv(ndev); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci return sxgbe_mdio_access_c22(priv, SXGBE_SMA_WRITE_CMD, phyaddr, phyreg, 17362306a36Sopenharmony_ci phydata); 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci/** 17762306a36Sopenharmony_ci * sxgbe_mdio_write_c45 17862306a36Sopenharmony_ci * @bus: points to the mii_bus structure 17962306a36Sopenharmony_ci * @phyaddr: address of phy port 18062306a36Sopenharmony_ci * @phyreg: address of phy registers 18162306a36Sopenharmony_ci * @devad: device (MMD) address 18262306a36Sopenharmony_ci * @phydata: data to be written into phy register 18362306a36Sopenharmony_ci * Description: this function is used for C45 MDIO write 18462306a36Sopenharmony_ci */ 18562306a36Sopenharmony_cistatic int sxgbe_mdio_write_c45(struct mii_bus *bus, int phyaddr, int devad, 18662306a36Sopenharmony_ci int phyreg, u16 phydata) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci struct net_device *ndev = bus->priv; 18962306a36Sopenharmony_ci struct sxgbe_priv_data *priv = netdev_priv(ndev); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci return sxgbe_mdio_access_c45(priv, SXGBE_SMA_WRITE_CMD, phyaddr, 19262306a36Sopenharmony_ci devad, phyreg, phydata); 19362306a36Sopenharmony_ci} 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ciint sxgbe_mdio_register(struct net_device *ndev) 19662306a36Sopenharmony_ci{ 19762306a36Sopenharmony_ci struct mii_bus *mdio_bus; 19862306a36Sopenharmony_ci struct sxgbe_priv_data *priv = netdev_priv(ndev); 19962306a36Sopenharmony_ci struct sxgbe_mdio_bus_data *mdio_data = priv->plat->mdio_bus_data; 20062306a36Sopenharmony_ci int err, phy_addr; 20162306a36Sopenharmony_ci int *irqlist; 20262306a36Sopenharmony_ci bool phy_found = false; 20362306a36Sopenharmony_ci bool act; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci /* allocate the new mdio bus */ 20662306a36Sopenharmony_ci mdio_bus = mdiobus_alloc(); 20762306a36Sopenharmony_ci if (!mdio_bus) { 20862306a36Sopenharmony_ci netdev_err(ndev, "%s: mii bus allocation failed\n", __func__); 20962306a36Sopenharmony_ci return -ENOMEM; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci if (mdio_data->irqs) 21362306a36Sopenharmony_ci irqlist = mdio_data->irqs; 21462306a36Sopenharmony_ci else 21562306a36Sopenharmony_ci irqlist = priv->mii_irq; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci /* assign mii bus fields */ 21862306a36Sopenharmony_ci mdio_bus->name = "sxgbe"; 21962306a36Sopenharmony_ci mdio_bus->read = sxgbe_mdio_read_c22; 22062306a36Sopenharmony_ci mdio_bus->write = sxgbe_mdio_write_c22; 22162306a36Sopenharmony_ci mdio_bus->read_c45 = sxgbe_mdio_read_c45; 22262306a36Sopenharmony_ci mdio_bus->write_c45 = sxgbe_mdio_write_c45; 22362306a36Sopenharmony_ci snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s-%x", 22462306a36Sopenharmony_ci mdio_bus->name, priv->plat->bus_id); 22562306a36Sopenharmony_ci mdio_bus->priv = ndev; 22662306a36Sopenharmony_ci mdio_bus->phy_mask = mdio_data->phy_mask; 22762306a36Sopenharmony_ci mdio_bus->parent = priv->device; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci /* register with kernel subsystem */ 23062306a36Sopenharmony_ci err = mdiobus_register(mdio_bus); 23162306a36Sopenharmony_ci if (err != 0) { 23262306a36Sopenharmony_ci netdev_err(ndev, "mdiobus register failed\n"); 23362306a36Sopenharmony_ci goto mdiobus_err; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) { 23762306a36Sopenharmony_ci struct phy_device *phy = mdiobus_get_phy(mdio_bus, phy_addr); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci if (phy) { 24062306a36Sopenharmony_ci char irq_num[4]; 24162306a36Sopenharmony_ci char *irq_str; 24262306a36Sopenharmony_ci /* If an IRQ was provided to be assigned after 24362306a36Sopenharmony_ci * the bus probe, do it here. 24462306a36Sopenharmony_ci */ 24562306a36Sopenharmony_ci if ((mdio_data->irqs == NULL) && 24662306a36Sopenharmony_ci (mdio_data->probed_phy_irq > 0)) { 24762306a36Sopenharmony_ci irqlist[phy_addr] = mdio_data->probed_phy_irq; 24862306a36Sopenharmony_ci phy->irq = mdio_data->probed_phy_irq; 24962306a36Sopenharmony_ci } 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci /* If we're going to bind the MAC to this PHY bus, 25262306a36Sopenharmony_ci * and no PHY number was provided to the MAC, 25362306a36Sopenharmony_ci * use the one probed here. 25462306a36Sopenharmony_ci */ 25562306a36Sopenharmony_ci if (priv->plat->phy_addr == -1) 25662306a36Sopenharmony_ci priv->plat->phy_addr = phy_addr; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci act = (priv->plat->phy_addr == phy_addr); 25962306a36Sopenharmony_ci switch (phy->irq) { 26062306a36Sopenharmony_ci case PHY_POLL: 26162306a36Sopenharmony_ci irq_str = "POLL"; 26262306a36Sopenharmony_ci break; 26362306a36Sopenharmony_ci case PHY_MAC_INTERRUPT: 26462306a36Sopenharmony_ci irq_str = "MAC"; 26562306a36Sopenharmony_ci break; 26662306a36Sopenharmony_ci default: 26762306a36Sopenharmony_ci sprintf(irq_num, "%d", phy->irq); 26862306a36Sopenharmony_ci irq_str = irq_num; 26962306a36Sopenharmony_ci break; 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci netdev_info(ndev, "PHY ID %08x at %d IRQ %s (%s)%s\n", 27262306a36Sopenharmony_ci phy->phy_id, phy_addr, irq_str, 27362306a36Sopenharmony_ci phydev_name(phy), act ? " active" : ""); 27462306a36Sopenharmony_ci phy_found = true; 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci } 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci if (!phy_found) { 27962306a36Sopenharmony_ci netdev_err(ndev, "PHY not found\n"); 28062306a36Sopenharmony_ci goto phyfound_err; 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci priv->mii = mdio_bus; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci return 0; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ciphyfound_err: 28862306a36Sopenharmony_ci err = -ENODEV; 28962306a36Sopenharmony_ci mdiobus_unregister(mdio_bus); 29062306a36Sopenharmony_cimdiobus_err: 29162306a36Sopenharmony_ci mdiobus_free(mdio_bus); 29262306a36Sopenharmony_ci return err; 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ciint sxgbe_mdio_unregister(struct net_device *ndev) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci struct sxgbe_priv_data *priv = netdev_priv(ndev); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci if (!priv->mii) 30062306a36Sopenharmony_ci return 0; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci mdiobus_unregister(priv->mii); 30362306a36Sopenharmony_ci priv->mii->priv = NULL; 30462306a36Sopenharmony_ci mdiobus_free(priv->mii); 30562306a36Sopenharmony_ci priv->mii = NULL; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci return 0; 30862306a36Sopenharmony_ci} 309