162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* Copyright Sunplus Technology Co., Ltd. 362306a36Sopenharmony_ci * All rights reserved. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/platform_device.h> 762306a36Sopenharmony_ci#include <linux/netdevice.h> 862306a36Sopenharmony_ci#include <linux/bitfield.h> 962306a36Sopenharmony_ci#include <linux/of_mdio.h> 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include "spl2sw_register.h" 1262306a36Sopenharmony_ci#include "spl2sw_define.h" 1362306a36Sopenharmony_ci#include "spl2sw_mdio.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#define SPL2SW_MDIO_READ_CMD 0x02 1662306a36Sopenharmony_ci#define SPL2SW_MDIO_WRITE_CMD 0x01 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistatic int spl2sw_mdio_access(struct spl2sw_common *comm, u8 cmd, u8 addr, u8 regnum, u16 wdata) 1962306a36Sopenharmony_ci{ 2062306a36Sopenharmony_ci u32 reg, reg2; 2162306a36Sopenharmony_ci u32 val; 2262306a36Sopenharmony_ci int ret; 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci /* Note that addr (of phy) should match either ext_phy0_addr 2562306a36Sopenharmony_ci * or ext_phy1_addr, or mdio commands won't be sent out. 2662306a36Sopenharmony_ci */ 2762306a36Sopenharmony_ci reg = readl(comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE); 2862306a36Sopenharmony_ci reg &= ~MAC_EXT_PHY0_ADDR; 2962306a36Sopenharmony_ci reg |= FIELD_PREP(MAC_EXT_PHY0_ADDR, addr); 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci reg2 = FIELD_PREP(MAC_CPU_PHY_WT_DATA, wdata) | FIELD_PREP(MAC_CPU_PHY_CMD, cmd) | 3262306a36Sopenharmony_ci FIELD_PREP(MAC_CPU_PHY_REG_ADDR, regnum) | FIELD_PREP(MAC_CPU_PHY_ADDR, addr); 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci /* Set ext_phy0_addr and then issue mdio command. 3562306a36Sopenharmony_ci * No interrupt is allowed in between. 3662306a36Sopenharmony_ci */ 3762306a36Sopenharmony_ci spin_lock_irq(&comm->mdio_lock); 3862306a36Sopenharmony_ci writel(reg, comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE); 3962306a36Sopenharmony_ci writel(reg2, comm->l2sw_reg_base + L2SW_PHY_CNTL_REG0); 4062306a36Sopenharmony_ci spin_unlock_irq(&comm->mdio_lock); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci ret = read_poll_timeout(readl, val, val & cmd, 1, 1000, true, 4362306a36Sopenharmony_ci comm->l2sw_reg_base + L2SW_PHY_CNTL_REG1); 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci /* Set ext_phy0_addr back to 31 to prevent 4662306a36Sopenharmony_ci * from sending mdio command to phy by 4762306a36Sopenharmony_ci * hardware auto-mdio function. 4862306a36Sopenharmony_ci */ 4962306a36Sopenharmony_ci reg = readl(comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE); 5062306a36Sopenharmony_ci reg &= ~MAC_EXT_PHY0_ADDR; 5162306a36Sopenharmony_ci reg |= FIELD_PREP(MAC_EXT_PHY0_ADDR, 31); 5262306a36Sopenharmony_ci writel(reg, comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci if (ret == 0) 5562306a36Sopenharmony_ci return val >> 16; 5662306a36Sopenharmony_ci else 5762306a36Sopenharmony_ci return ret; 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic int spl2sw_mii_read(struct mii_bus *bus, int addr, int regnum) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci struct spl2sw_common *comm = bus->priv; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci return spl2sw_mdio_access(comm, SPL2SW_MDIO_READ_CMD, addr, regnum, 0); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic int spl2sw_mii_write(struct mii_bus *bus, int addr, int regnum, u16 val) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci struct spl2sw_common *comm = bus->priv; 7062306a36Sopenharmony_ci int ret; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci ret = spl2sw_mdio_access(comm, SPL2SW_MDIO_WRITE_CMD, addr, regnum, val); 7362306a36Sopenharmony_ci if (ret < 0) 7462306a36Sopenharmony_ci return ret; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci return 0; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ciu32 spl2sw_mdio_init(struct spl2sw_common *comm) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci struct device_node *mdio_np; 8262306a36Sopenharmony_ci struct mii_bus *mii_bus; 8362306a36Sopenharmony_ci int ret; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci /* Get mdio child node. */ 8662306a36Sopenharmony_ci mdio_np = of_get_child_by_name(comm->pdev->dev.of_node, "mdio"); 8762306a36Sopenharmony_ci if (!mdio_np) { 8862306a36Sopenharmony_ci dev_err(&comm->pdev->dev, "No mdio child node found!\n"); 8962306a36Sopenharmony_ci return -ENODEV; 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci /* Allocate and register mdio bus. */ 9362306a36Sopenharmony_ci mii_bus = devm_mdiobus_alloc(&comm->pdev->dev); 9462306a36Sopenharmony_ci if (!mii_bus) { 9562306a36Sopenharmony_ci ret = -ENOMEM; 9662306a36Sopenharmony_ci goto out; 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci mii_bus->name = "sunplus_mii_bus"; 10062306a36Sopenharmony_ci mii_bus->parent = &comm->pdev->dev; 10162306a36Sopenharmony_ci mii_bus->priv = comm; 10262306a36Sopenharmony_ci mii_bus->read = spl2sw_mii_read; 10362306a36Sopenharmony_ci mii_bus->write = spl2sw_mii_write; 10462306a36Sopenharmony_ci snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&comm->pdev->dev)); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci ret = of_mdiobus_register(mii_bus, mdio_np); 10762306a36Sopenharmony_ci if (ret) { 10862306a36Sopenharmony_ci dev_err(&comm->pdev->dev, "Failed to register mdiobus!\n"); 10962306a36Sopenharmony_ci goto out; 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci comm->mii_bus = mii_bus; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ciout: 11562306a36Sopenharmony_ci of_node_put(mdio_np); 11662306a36Sopenharmony_ci return ret; 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_civoid spl2sw_mdio_remove(struct spl2sw_common *comm) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci if (comm->mii_bus) { 12262306a36Sopenharmony_ci mdiobus_unregister(comm->mii_bus); 12362306a36Sopenharmony_ci comm->mii_bus = NULL; 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci} 126