162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved. 362306a36Sopenharmony_ci */ 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci/* Qualcomm Technologies, Inc. EMAC PHY Controller driver. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/of_mdio.h> 962306a36Sopenharmony_ci#include <linux/phy.h> 1062306a36Sopenharmony_ci#include <linux/iopoll.h> 1162306a36Sopenharmony_ci#include <linux/acpi.h> 1262306a36Sopenharmony_ci#include "emac.h" 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci/* EMAC base register offsets */ 1562306a36Sopenharmony_ci#define EMAC_MDIO_CTRL 0x001414 1662306a36Sopenharmony_ci#define EMAC_PHY_STS 0x001418 1762306a36Sopenharmony_ci#define EMAC_MDIO_EX_CTRL 0x001440 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* EMAC_MDIO_CTRL */ 2062306a36Sopenharmony_ci#define MDIO_MODE BIT(30) 2162306a36Sopenharmony_ci#define MDIO_PR BIT(29) 2262306a36Sopenharmony_ci#define MDIO_AP_EN BIT(28) 2362306a36Sopenharmony_ci#define MDIO_BUSY BIT(27) 2462306a36Sopenharmony_ci#define MDIO_CLK_SEL_BMSK 0x7000000 2562306a36Sopenharmony_ci#define MDIO_CLK_SEL_SHFT 24 2662306a36Sopenharmony_ci#define MDIO_START BIT(23) 2762306a36Sopenharmony_ci#define SUP_PREAMBLE BIT(22) 2862306a36Sopenharmony_ci#define MDIO_RD_NWR BIT(21) 2962306a36Sopenharmony_ci#define MDIO_REG_ADDR_BMSK 0x1f0000 3062306a36Sopenharmony_ci#define MDIO_REG_ADDR_SHFT 16 3162306a36Sopenharmony_ci#define MDIO_DATA_BMSK 0xffff 3262306a36Sopenharmony_ci#define MDIO_DATA_SHFT 0 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* EMAC_PHY_STS */ 3562306a36Sopenharmony_ci#define PHY_ADDR_BMSK 0x1f0000 3662306a36Sopenharmony_ci#define PHY_ADDR_SHFT 16 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define MDIO_CLK_25_4 0 3962306a36Sopenharmony_ci#define MDIO_CLK_25_28 7 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci#define MDIO_WAIT_TIMES 1000 4262306a36Sopenharmony_ci#define MDIO_STATUS_DELAY_TIME 1 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic int emac_mdio_read(struct mii_bus *bus, int addr, int regnum) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci struct emac_adapter *adpt = bus->priv; 4762306a36Sopenharmony_ci u32 reg; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci emac_reg_update32(adpt->base + EMAC_PHY_STS, PHY_ADDR_BMSK, 5062306a36Sopenharmony_ci (addr << PHY_ADDR_SHFT)); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci reg = SUP_PREAMBLE | 5362306a36Sopenharmony_ci ((MDIO_CLK_25_4 << MDIO_CLK_SEL_SHFT) & MDIO_CLK_SEL_BMSK) | 5462306a36Sopenharmony_ci ((regnum << MDIO_REG_ADDR_SHFT) & MDIO_REG_ADDR_BMSK) | 5562306a36Sopenharmony_ci MDIO_START | MDIO_RD_NWR; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci writel(reg, adpt->base + EMAC_MDIO_CTRL); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci if (readl_poll_timeout(adpt->base + EMAC_MDIO_CTRL, reg, 6062306a36Sopenharmony_ci !(reg & (MDIO_START | MDIO_BUSY)), 6162306a36Sopenharmony_ci MDIO_STATUS_DELAY_TIME, MDIO_WAIT_TIMES * 100)) 6262306a36Sopenharmony_ci return -EIO; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci return (reg >> MDIO_DATA_SHFT) & MDIO_DATA_BMSK; 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic int emac_mdio_write(struct mii_bus *bus, int addr, int regnum, u16 val) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci struct emac_adapter *adpt = bus->priv; 7062306a36Sopenharmony_ci u32 reg; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci emac_reg_update32(adpt->base + EMAC_PHY_STS, PHY_ADDR_BMSK, 7362306a36Sopenharmony_ci (addr << PHY_ADDR_SHFT)); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci reg = SUP_PREAMBLE | 7662306a36Sopenharmony_ci ((MDIO_CLK_25_4 << MDIO_CLK_SEL_SHFT) & MDIO_CLK_SEL_BMSK) | 7762306a36Sopenharmony_ci ((regnum << MDIO_REG_ADDR_SHFT) & MDIO_REG_ADDR_BMSK) | 7862306a36Sopenharmony_ci ((val << MDIO_DATA_SHFT) & MDIO_DATA_BMSK) | 7962306a36Sopenharmony_ci MDIO_START; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci writel(reg, adpt->base + EMAC_MDIO_CTRL); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci if (readl_poll_timeout(adpt->base + EMAC_MDIO_CTRL, reg, 8462306a36Sopenharmony_ci !(reg & (MDIO_START | MDIO_BUSY)), 8562306a36Sopenharmony_ci MDIO_STATUS_DELAY_TIME, MDIO_WAIT_TIMES * 100)) 8662306a36Sopenharmony_ci return -EIO; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci return 0; 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci/* Configure the MDIO bus and connect the external PHY */ 9262306a36Sopenharmony_ciint emac_phy_config(struct platform_device *pdev, struct emac_adapter *adpt) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 9562306a36Sopenharmony_ci struct mii_bus *mii_bus; 9662306a36Sopenharmony_ci int ret; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci /* Create the mii_bus object for talking to the MDIO bus */ 9962306a36Sopenharmony_ci adpt->mii_bus = mii_bus = devm_mdiobus_alloc(&pdev->dev); 10062306a36Sopenharmony_ci if (!mii_bus) 10162306a36Sopenharmony_ci return -ENOMEM; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci mii_bus->name = "emac-mdio"; 10462306a36Sopenharmony_ci snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s", pdev->name); 10562306a36Sopenharmony_ci mii_bus->read = emac_mdio_read; 10662306a36Sopenharmony_ci mii_bus->write = emac_mdio_write; 10762306a36Sopenharmony_ci mii_bus->parent = &pdev->dev; 10862306a36Sopenharmony_ci mii_bus->priv = adpt; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if (has_acpi_companion(&pdev->dev)) { 11162306a36Sopenharmony_ci u32 phy_addr; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci ret = mdiobus_register(mii_bus); 11462306a36Sopenharmony_ci if (ret) { 11562306a36Sopenharmony_ci dev_err(&pdev->dev, "could not register mdio bus\n"); 11662306a36Sopenharmony_ci return ret; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci ret = device_property_read_u32(&pdev->dev, "phy-channel", 11962306a36Sopenharmony_ci &phy_addr); 12062306a36Sopenharmony_ci if (ret) 12162306a36Sopenharmony_ci /* If we can't read a valid phy address, then assume 12262306a36Sopenharmony_ci * that there is only one phy on this mdio bus. 12362306a36Sopenharmony_ci */ 12462306a36Sopenharmony_ci adpt->phydev = phy_find_first(mii_bus); 12562306a36Sopenharmony_ci else 12662306a36Sopenharmony_ci adpt->phydev = mdiobus_get_phy(mii_bus, phy_addr); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci /* of_phy_find_device() claims a reference to the phydev, 12962306a36Sopenharmony_ci * so we do that here manually as well. When the driver 13062306a36Sopenharmony_ci * later unloads, it can unilaterally drop the reference 13162306a36Sopenharmony_ci * without worrying about ACPI vs DT. 13262306a36Sopenharmony_ci */ 13362306a36Sopenharmony_ci if (adpt->phydev) 13462306a36Sopenharmony_ci get_device(&adpt->phydev->mdio.dev); 13562306a36Sopenharmony_ci } else { 13662306a36Sopenharmony_ci struct device_node *phy_np; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci ret = of_mdiobus_register(mii_bus, np); 13962306a36Sopenharmony_ci if (ret) { 14062306a36Sopenharmony_ci dev_err(&pdev->dev, "could not register mdio bus\n"); 14162306a36Sopenharmony_ci return ret; 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci phy_np = of_parse_phandle(np, "phy-handle", 0); 14562306a36Sopenharmony_ci adpt->phydev = of_phy_find_device(phy_np); 14662306a36Sopenharmony_ci of_node_put(phy_np); 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci if (!adpt->phydev) { 15062306a36Sopenharmony_ci dev_err(&pdev->dev, "could not find external phy\n"); 15162306a36Sopenharmony_ci mdiobus_unregister(mii_bus); 15262306a36Sopenharmony_ci return -ENODEV; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci return 0; 15662306a36Sopenharmony_ci} 157