1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
// SPDX-License-Identifier: GPL-2.0
/* Copyright Sunplus Technology Co., Ltd.
* All rights reserved.
*/
#include <linux/platform_device.h>
#include <linux/netdevice.h>
#include <linux/bitfield.h>
#include <linux/of_mdio.h>
#include "spl2sw_register.h"
#include "spl2sw_define.h"
#include "spl2sw_mdio.h"
#define SPL2SW_MDIO_READ_CMD 0x02
#define SPL2SW_MDIO_WRITE_CMD 0x01
static int spl2sw_mdio_access(struct spl2sw_common *comm, u8 cmd, u8 addr, u8 regnum, u16 wdata)
{
u32 reg, reg2;
u32 val;
int ret;
/* Note that addr (of phy) should match either ext_phy0_addr
* or ext_phy1_addr, or mdio commands won't be sent out.
*/
reg = readl(comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE);
reg &= ~MAC_EXT_PHY0_ADDR;
reg |= FIELD_PREP(MAC_EXT_PHY0_ADDR, addr);
reg2 = FIELD_PREP(MAC_CPU_PHY_WT_DATA, wdata) | FIELD_PREP(MAC_CPU_PHY_CMD, cmd) |
FIELD_PREP(MAC_CPU_PHY_REG_ADDR, regnum) | FIELD_PREP(MAC_CPU_PHY_ADDR, addr);
/* Set ext_phy0_addr and then issue mdio command.
* No interrupt is allowed in between.
*/
spin_lock_irq(&comm->mdio_lock);
writel(reg, comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE);
writel(reg2, comm->l2sw_reg_base + L2SW_PHY_CNTL_REG0);
spin_unlock_irq(&comm->mdio_lock);
ret = read_poll_timeout(readl, val, val & cmd, 1, 1000, true,
comm->l2sw_reg_base + L2SW_PHY_CNTL_REG1);
/* Set ext_phy0_addr back to 31 to prevent
* from sending mdio command to phy by
* hardware auto-mdio function.
*/
reg = readl(comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE);
reg &= ~MAC_EXT_PHY0_ADDR;
reg |= FIELD_PREP(MAC_EXT_PHY0_ADDR, 31);
writel(reg, comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE);
if (ret == 0)
return val >> 16;
else
return ret;
}
static int spl2sw_mii_read(struct mii_bus *bus, int addr, int regnum)
{
struct spl2sw_common *comm = bus->priv;
if (regnum & MII_ADDR_C45)
return -EOPNOTSUPP;
return spl2sw_mdio_access(comm, SPL2SW_MDIO_READ_CMD, addr, regnum, 0);
}
static int spl2sw_mii_write(struct mii_bus *bus, int addr, int regnum, u16 val)
{
struct spl2sw_common *comm = bus->priv;
int ret;
if (regnum & MII_ADDR_C45)
return -EOPNOTSUPP;
ret = spl2sw_mdio_access(comm, SPL2SW_MDIO_WRITE_CMD, addr, regnum, val);
if (ret < 0)
return ret;
return 0;
}
u32 spl2sw_mdio_init(struct spl2sw_common *comm)
{
struct device_node *mdio_np;
struct mii_bus *mii_bus;
int ret;
/* Get mdio child node. */
mdio_np = of_get_child_by_name(comm->pdev->dev.of_node, "mdio");
if (!mdio_np) {
dev_err(&comm->pdev->dev, "No mdio child node found!\n");
return -ENODEV;
}
/* Allocate and register mdio bus. */
mii_bus = devm_mdiobus_alloc(&comm->pdev->dev);
if (!mii_bus) {
ret = -ENOMEM;
goto out;
}
mii_bus->name = "sunplus_mii_bus";
mii_bus->parent = &comm->pdev->dev;
mii_bus->priv = comm;
mii_bus->read = spl2sw_mii_read;
mii_bus->write = spl2sw_mii_write;
snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&comm->pdev->dev));
ret = of_mdiobus_register(mii_bus, mdio_np);
if (ret) {
dev_err(&comm->pdev->dev, "Failed to register mdiobus!\n");
goto out;
}
comm->mii_bus = mii_bus;
out:
of_node_put(mdio_np);
return ret;
}
void spl2sw_mdio_remove(struct spl2sw_common *comm)
{
if (comm->mii_bus) {
mdiobus_unregister(comm->mii_bus);
comm->mii_bus = NULL;
}
}
|