// SPDX-License-Identifier: GPL-2.0+ // Copyright (c) 2024 Hisilicon Limited. #include #include "hbg_common.h" #include "hbg_hw.h" #include "hbg_mdio.h" #include "hbg_reg.h" #define HBG_MAC_GET_PRIV(mac) ((struct hbg_priv *)(mac)->mdio_bus->priv) #define HBG_MII_BUS_GET_MAC(bus) (&((struct hbg_priv *)(bus)->priv)->mac) #define HBG_MDIO_C22_MODE 0x1 #define HBG_MDIO_C22_REG_WRITE 0x1 #define HBG_MDIO_C22_REG_READ 0x2 #define HBG_MDIO_OP_TIMEOUT_US (1 * 1000 * 1000) #define HBG_MDIO_OP_INTERVAL_US (5 * 1000) static void hbg_mdio_set_command(struct hbg_mac *mac, u32 cmd) { hbg_reg_write(HBG_MAC_GET_PRIV(mac), HBG_REG_MDIO_COMMAND_ADDR, cmd); } static void hbg_mdio_get_command(struct hbg_mac *mac, u32 *cmd) { *cmd = hbg_reg_read(HBG_MAC_GET_PRIV(mac), HBG_REG_MDIO_COMMAND_ADDR); } static void hbg_mdio_set_wdata_reg(struct hbg_mac *mac, u16 wdata_value) { hbg_reg_write_field(HBG_MAC_GET_PRIV(mac), HBG_REG_MDIO_WDATA_ADDR, HBG_REG_MDIO_WDATA_M, wdata_value); } static u32 hbg_mdio_get_rdata_reg(struct hbg_mac *mac) { return hbg_reg_read_field(HBG_MAC_GET_PRIV(mac), HBG_REG_MDIO_RDATA_ADDR, HBG_REG_MDIO_WDATA_M); } static int hbg_mdio_wait_ready(struct hbg_mac *mac) { struct hbg_priv *priv = HBG_MAC_GET_PRIV(mac); u32 cmd = 0; int ret; ret = readl_poll_timeout(priv->io_base + HBG_REG_MDIO_COMMAND_ADDR, cmd, !FIELD_GET(HBG_REG_MDIO_COMMAND_START_B, cmd), HBG_MDIO_OP_INTERVAL_US, HBG_MDIO_OP_TIMEOUT_US); return ret ? -ETIMEDOUT : 0; } static int hbg_mdio_cmd_send(struct hbg_mac *mac, u32 prt_addr, u32 dev_addr, u32 type, u32 op_code) { u32 cmd = 0; hbg_mdio_get_command(mac, &cmd); hbg_field_modify(cmd, HBG_REG_MDIO_COMMAND_ST_M, type); hbg_field_modify(cmd, HBG_REG_MDIO_COMMAND_OP_M, op_code); hbg_field_modify(cmd, HBG_REG_MDIO_COMMAND_PRTAD_M, prt_addr); hbg_field_modify(cmd, HBG_REG_MDIO_COMMAND_DEVAD_M, dev_addr); /* if auto scan enabled, this value need fix to 0 */ hbg_field_modify(cmd, HBG_REG_MDIO_COMMAND_START_B, 0x1); hbg_mdio_set_command(mac, cmd); /* wait operation complete and check the result */ return hbg_mdio_wait_ready(mac); } static int hbg_mdio_read22(struct mii_bus *bus, int phy_addr, int regnum) { struct hbg_mac *mac = HBG_MII_BUS_GET_MAC(bus); int ret; ret = hbg_mdio_cmd_send(mac, phy_addr, regnum, HBG_MDIO_C22_MODE, HBG_MDIO_C22_REG_READ); if (ret) return ret; return hbg_mdio_get_rdata_reg(mac); } static int hbg_mdio_write22(struct mii_bus *bus, int phy_addr, int regnum, u16 val) { struct hbg_mac *mac = HBG_MII_BUS_GET_MAC(bus); hbg_mdio_set_wdata_reg(mac, val); return hbg_mdio_cmd_send(mac, phy_addr, regnum, HBG_MDIO_C22_MODE, HBG_MDIO_C22_REG_WRITE); } static void hbg_mdio_init_hw(struct hbg_priv *priv) { u32 freq = priv->dev_specs.mdio_frequency; struct hbg_mac *mac = &priv->mac; u32 cmd = 0; cmd |= FIELD_PREP(HBG_REG_MDIO_COMMAND_ST_M, HBG_MDIO_C22_MODE); cmd |= FIELD_PREP(HBG_REG_MDIO_COMMAND_AUTO_SCAN_B, HBG_STATUS_DISABLE); /* freq use two bits, which are stored in clk_sel and clk_sel_exp */ cmd |= FIELD_PREP(HBG_REG_MDIO_COMMAND_CLK_SEL_B, freq & 0x1); cmd |= FIELD_PREP(HBG_REG_MDIO_COMMAND_CLK_SEL_EXP_B, (freq >> 1) & 0x1); hbg_mdio_set_command(mac, cmd); } static void hbg_phy_adjust_link(struct net_device *netdev) { struct hbg_priv *priv = netdev_priv(netdev); struct phy_device *phydev = netdev->phydev; u32 speed; if (phydev->link != priv->mac.link_status) { if (phydev->link) { switch (phydev->speed) { case SPEED_10: speed = HBG_PORT_MODE_SGMII_10M; break; case SPEED_100: speed = HBG_PORT_MODE_SGMII_100M; break; case SPEED_1000: speed = HBG_PORT_MODE_SGMII_1000M; break; default: return; } priv->mac.speed = speed; priv->mac.duplex = phydev->duplex; priv->mac.autoneg = phydev->autoneg; hbg_hw_adjust_link(priv, speed, phydev->duplex); } priv->mac.link_status = phydev->link; phy_print_status(phydev); } } static void hbg_phy_disconnect(void *data) { phy_disconnect((struct phy_device *)data); } static int hbg_phy_connect(struct hbg_priv *priv) { struct phy_device *phydev = priv->mac.phydev; struct device *dev = &priv->pdev->dev; int ret; ret = phy_connect_direct(priv->netdev, phydev, hbg_phy_adjust_link, PHY_INTERFACE_MODE_SGMII); if (ret) return dev_err_probe(dev, ret, "failed to connect phy\n"); ret = devm_add_action_or_reset(dev, hbg_phy_disconnect, phydev); if (ret) return ret; phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Half_BIT); phy_attached_info(phydev); return 0; } void hbg_phy_start(struct hbg_priv *priv) { phy_start(priv->mac.phydev); } void hbg_phy_stop(struct hbg_priv *priv) { phy_stop(priv->mac.phydev); } int hbg_mdio_init(struct hbg_priv *priv) { struct device *dev = &priv->pdev->dev; struct hbg_mac *mac = &priv->mac; struct phy_device *phydev; struct mii_bus *mdio_bus; int ret; mac->phy_addr = priv->dev_specs.phy_addr; mdio_bus = devm_mdiobus_alloc(dev); if (!mdio_bus) return dev_err_probe(dev, -ENOMEM, "failed to alloc MDIO bus\n"); mdio_bus->parent = dev; mdio_bus->priv = priv; mdio_bus->phy_mask = ~(1 << mac->phy_addr); mdio_bus->name = "hibmcge mii bus"; mac->mdio_bus = mdio_bus; mdio_bus->read = hbg_mdio_read22; mdio_bus->write = hbg_mdio_write22; snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s-%s", "mii", dev_name(dev)); ret = devm_mdiobus_register(dev, mdio_bus); if (ret) return dev_err_probe(dev, ret, "failed to register MDIO bus\n"); phydev = mdiobus_get_phy(mdio_bus, mac->phy_addr); if (!phydev) return dev_err_probe(dev, -ENODEV, "failed to get phy device\n"); mac->phydev = phydev; hbg_mdio_init_hw(priv); return hbg_phy_connect(priv); }