162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 262306a36Sopenharmony_ci/* Copyright (c) 2015, The Linux Foundation. All rights reserved. */ 362306a36Sopenharmony_ci/* Copyright (c) 2020 Sartura Ltd. */ 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci#include <linux/delay.h> 662306a36Sopenharmony_ci#include <linux/io.h> 762306a36Sopenharmony_ci#include <linux/iopoll.h> 862306a36Sopenharmony_ci#include <linux/kernel.h> 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/of_address.h> 1162306a36Sopenharmony_ci#include <linux/of_mdio.h> 1262306a36Sopenharmony_ci#include <linux/phy.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci#include <linux/clk.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define MDIO_MODE_REG 0x40 1762306a36Sopenharmony_ci#define MDIO_ADDR_REG 0x44 1862306a36Sopenharmony_ci#define MDIO_DATA_WRITE_REG 0x48 1962306a36Sopenharmony_ci#define MDIO_DATA_READ_REG 0x4c 2062306a36Sopenharmony_ci#define MDIO_CMD_REG 0x50 2162306a36Sopenharmony_ci#define MDIO_CMD_ACCESS_BUSY BIT(16) 2262306a36Sopenharmony_ci#define MDIO_CMD_ACCESS_START BIT(8) 2362306a36Sopenharmony_ci#define MDIO_CMD_ACCESS_CODE_READ 0 2462306a36Sopenharmony_ci#define MDIO_CMD_ACCESS_CODE_WRITE 1 2562306a36Sopenharmony_ci#define MDIO_CMD_ACCESS_CODE_C45_ADDR 0 2662306a36Sopenharmony_ci#define MDIO_CMD_ACCESS_CODE_C45_WRITE 1 2762306a36Sopenharmony_ci#define MDIO_CMD_ACCESS_CODE_C45_READ 2 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* 0 = Clause 22, 1 = Clause 45 */ 3062306a36Sopenharmony_ci#define MDIO_MODE_C45 BIT(8) 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define IPQ4019_MDIO_TIMEOUT 10000 3362306a36Sopenharmony_ci#define IPQ4019_MDIO_SLEEP 10 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/* MDIO clock source frequency is fixed to 100M */ 3662306a36Sopenharmony_ci#define IPQ_MDIO_CLK_RATE 100000000 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define IPQ_PHY_SET_DELAY_US 100000 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistruct ipq4019_mdio_data { 4162306a36Sopenharmony_ci void __iomem *membase; 4262306a36Sopenharmony_ci void __iomem *eth_ldo_rdy; 4362306a36Sopenharmony_ci struct clk *mdio_clk; 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic int ipq4019_mdio_wait_busy(struct mii_bus *bus) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci struct ipq4019_mdio_data *priv = bus->priv; 4962306a36Sopenharmony_ci unsigned int busy; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci return readl_poll_timeout(priv->membase + MDIO_CMD_REG, busy, 5262306a36Sopenharmony_ci (busy & MDIO_CMD_ACCESS_BUSY) == 0, 5362306a36Sopenharmony_ci IPQ4019_MDIO_SLEEP, IPQ4019_MDIO_TIMEOUT); 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic int ipq4019_mdio_read_c45(struct mii_bus *bus, int mii_id, int mmd, 5762306a36Sopenharmony_ci int reg) 5862306a36Sopenharmony_ci{ 5962306a36Sopenharmony_ci struct ipq4019_mdio_data *priv = bus->priv; 6062306a36Sopenharmony_ci unsigned int data; 6162306a36Sopenharmony_ci unsigned int cmd; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (ipq4019_mdio_wait_busy(bus)) 6462306a36Sopenharmony_ci return -ETIMEDOUT; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci data = readl(priv->membase + MDIO_MODE_REG); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci data |= MDIO_MODE_C45; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci writel(data, priv->membase + MDIO_MODE_REG); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci /* issue the phy address and mmd */ 7362306a36Sopenharmony_ci writel((mii_id << 8) | mmd, priv->membase + MDIO_ADDR_REG); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci /* issue reg */ 7662306a36Sopenharmony_ci writel(reg, priv->membase + MDIO_DATA_WRITE_REG); 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci /* issue read command */ 8162306a36Sopenharmony_ci writel(cmd, priv->membase + MDIO_CMD_REG); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci /* Wait read complete */ 8462306a36Sopenharmony_ci if (ipq4019_mdio_wait_busy(bus)) 8562306a36Sopenharmony_ci return -ETIMEDOUT; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_READ; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci writel(cmd, priv->membase + MDIO_CMD_REG); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci if (ipq4019_mdio_wait_busy(bus)) 9262306a36Sopenharmony_ci return -ETIMEDOUT; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci /* Read and return data */ 9562306a36Sopenharmony_ci return readl(priv->membase + MDIO_DATA_READ_REG); 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic int ipq4019_mdio_read_c22(struct mii_bus *bus, int mii_id, int regnum) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci struct ipq4019_mdio_data *priv = bus->priv; 10162306a36Sopenharmony_ci unsigned int data; 10262306a36Sopenharmony_ci unsigned int cmd; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (ipq4019_mdio_wait_busy(bus)) 10562306a36Sopenharmony_ci return -ETIMEDOUT; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci data = readl(priv->membase + MDIO_MODE_REG); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci data &= ~MDIO_MODE_C45; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci writel(data, priv->membase + MDIO_MODE_REG); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci /* issue the phy address and reg */ 11462306a36Sopenharmony_ci writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_READ; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci /* issue read command */ 11962306a36Sopenharmony_ci writel(cmd, priv->membase + MDIO_CMD_REG); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci /* Wait read complete */ 12262306a36Sopenharmony_ci if (ipq4019_mdio_wait_busy(bus)) 12362306a36Sopenharmony_ci return -ETIMEDOUT; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci /* Read and return data */ 12662306a36Sopenharmony_ci return readl(priv->membase + MDIO_DATA_READ_REG); 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic int ipq4019_mdio_write_c45(struct mii_bus *bus, int mii_id, int mmd, 13062306a36Sopenharmony_ci int reg, u16 value) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci struct ipq4019_mdio_data *priv = bus->priv; 13362306a36Sopenharmony_ci unsigned int data; 13462306a36Sopenharmony_ci unsigned int cmd; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (ipq4019_mdio_wait_busy(bus)) 13762306a36Sopenharmony_ci return -ETIMEDOUT; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci data = readl(priv->membase + MDIO_MODE_REG); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci data |= MDIO_MODE_C45; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci writel(data, priv->membase + MDIO_MODE_REG); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci /* issue the phy address and mmd */ 14662306a36Sopenharmony_ci writel((mii_id << 8) | mmd, priv->membase + MDIO_ADDR_REG); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci /* issue reg */ 14962306a36Sopenharmony_ci writel(reg, priv->membase + MDIO_DATA_WRITE_REG); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci writel(cmd, priv->membase + MDIO_CMD_REG); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (ipq4019_mdio_wait_busy(bus)) 15662306a36Sopenharmony_ci return -ETIMEDOUT; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci /* issue write data */ 15962306a36Sopenharmony_ci writel(value, priv->membase + MDIO_DATA_WRITE_REG); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_WRITE; 16262306a36Sopenharmony_ci writel(cmd, priv->membase + MDIO_CMD_REG); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci /* Wait write complete */ 16562306a36Sopenharmony_ci if (ipq4019_mdio_wait_busy(bus)) 16662306a36Sopenharmony_ci return -ETIMEDOUT; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci return 0; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic int ipq4019_mdio_write_c22(struct mii_bus *bus, int mii_id, int regnum, 17262306a36Sopenharmony_ci u16 value) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci struct ipq4019_mdio_data *priv = bus->priv; 17562306a36Sopenharmony_ci unsigned int data; 17662306a36Sopenharmony_ci unsigned int cmd; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci if (ipq4019_mdio_wait_busy(bus)) 17962306a36Sopenharmony_ci return -ETIMEDOUT; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci /* Enter Clause 22 mode */ 18262306a36Sopenharmony_ci data = readl(priv->membase + MDIO_MODE_REG); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci data &= ~MDIO_MODE_C45; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci writel(data, priv->membase + MDIO_MODE_REG); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci /* issue the phy address and reg */ 18962306a36Sopenharmony_ci writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci /* issue write data */ 19262306a36Sopenharmony_ci writel(value, priv->membase + MDIO_DATA_WRITE_REG); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci /* issue write command */ 19562306a36Sopenharmony_ci cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_WRITE; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci writel(cmd, priv->membase + MDIO_CMD_REG); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci /* Wait write complete */ 20062306a36Sopenharmony_ci if (ipq4019_mdio_wait_busy(bus)) 20162306a36Sopenharmony_ci return -ETIMEDOUT; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci return 0; 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic int ipq_mdio_reset(struct mii_bus *bus) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci struct ipq4019_mdio_data *priv = bus->priv; 20962306a36Sopenharmony_ci u32 val; 21062306a36Sopenharmony_ci int ret; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci /* To indicate CMN_PLL that ethernet_ldo has been ready if platform resource 1 21362306a36Sopenharmony_ci * is specified in the device tree. 21462306a36Sopenharmony_ci */ 21562306a36Sopenharmony_ci if (priv->eth_ldo_rdy) { 21662306a36Sopenharmony_ci val = readl(priv->eth_ldo_rdy); 21762306a36Sopenharmony_ci val |= BIT(0); 21862306a36Sopenharmony_ci writel(val, priv->eth_ldo_rdy); 21962306a36Sopenharmony_ci fsleep(IPQ_PHY_SET_DELAY_US); 22062306a36Sopenharmony_ci } 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci /* Configure MDIO clock source frequency if clock is specified in the device tree */ 22362306a36Sopenharmony_ci ret = clk_set_rate(priv->mdio_clk, IPQ_MDIO_CLK_RATE); 22462306a36Sopenharmony_ci if (ret) 22562306a36Sopenharmony_ci return ret; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci ret = clk_prepare_enable(priv->mdio_clk); 22862306a36Sopenharmony_ci if (ret == 0) 22962306a36Sopenharmony_ci mdelay(10); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci return ret; 23262306a36Sopenharmony_ci} 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_cistatic int ipq4019_mdio_probe(struct platform_device *pdev) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci struct ipq4019_mdio_data *priv; 23762306a36Sopenharmony_ci struct mii_bus *bus; 23862306a36Sopenharmony_ci struct resource *res; 23962306a36Sopenharmony_ci int ret; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*priv)); 24262306a36Sopenharmony_ci if (!bus) 24362306a36Sopenharmony_ci return -ENOMEM; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci priv = bus->priv; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci priv->membase = devm_platform_ioremap_resource(pdev, 0); 24862306a36Sopenharmony_ci if (IS_ERR(priv->membase)) 24962306a36Sopenharmony_ci return PTR_ERR(priv->membase); 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci priv->mdio_clk = devm_clk_get_optional(&pdev->dev, "gcc_mdio_ahb_clk"); 25262306a36Sopenharmony_ci if (IS_ERR(priv->mdio_clk)) 25362306a36Sopenharmony_ci return PTR_ERR(priv->mdio_clk); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci /* The platform resource is provided on the chipset IPQ5018 */ 25662306a36Sopenharmony_ci /* This resource is optional */ 25762306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 1); 25862306a36Sopenharmony_ci if (res) 25962306a36Sopenharmony_ci priv->eth_ldo_rdy = devm_ioremap_resource(&pdev->dev, res); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci bus->name = "ipq4019_mdio"; 26262306a36Sopenharmony_ci bus->read = ipq4019_mdio_read_c22; 26362306a36Sopenharmony_ci bus->write = ipq4019_mdio_write_c22; 26462306a36Sopenharmony_ci bus->read_c45 = ipq4019_mdio_read_c45; 26562306a36Sopenharmony_ci bus->write_c45 = ipq4019_mdio_write_c45; 26662306a36Sopenharmony_ci bus->reset = ipq_mdio_reset; 26762306a36Sopenharmony_ci bus->parent = &pdev->dev; 26862306a36Sopenharmony_ci snprintf(bus->id, MII_BUS_ID_SIZE, "%s%d", pdev->name, pdev->id); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci ret = of_mdiobus_register(bus, pdev->dev.of_node); 27162306a36Sopenharmony_ci if (ret) { 27262306a36Sopenharmony_ci dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); 27362306a36Sopenharmony_ci return ret; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci platform_set_drvdata(pdev, bus); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci return 0; 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic int ipq4019_mdio_remove(struct platform_device *pdev) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci struct mii_bus *bus = platform_get_drvdata(pdev); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci mdiobus_unregister(bus); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci return 0; 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cistatic const struct of_device_id ipq4019_mdio_dt_ids[] = { 29162306a36Sopenharmony_ci { .compatible = "qcom,ipq4019-mdio" }, 29262306a36Sopenharmony_ci { .compatible = "qcom,ipq5018-mdio" }, 29362306a36Sopenharmony_ci { } 29462306a36Sopenharmony_ci}; 29562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ipq4019_mdio_dt_ids); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_cistatic struct platform_driver ipq4019_mdio_driver = { 29862306a36Sopenharmony_ci .probe = ipq4019_mdio_probe, 29962306a36Sopenharmony_ci .remove = ipq4019_mdio_remove, 30062306a36Sopenharmony_ci .driver = { 30162306a36Sopenharmony_ci .name = "ipq4019-mdio", 30262306a36Sopenharmony_ci .of_match_table = ipq4019_mdio_dt_ids, 30362306a36Sopenharmony_ci }, 30462306a36Sopenharmony_ci}; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_cimodule_platform_driver(ipq4019_mdio_driver); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ciMODULE_DESCRIPTION("ipq4019 MDIO interface driver"); 30962306a36Sopenharmony_ciMODULE_AUTHOR("Qualcomm Atheros"); 31062306a36Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 311