18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* Qualcomm IPQ8064 MDIO interface driver 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2019 Christian Lamparter <chunkeey@gmail.com> 58c2ecf20Sopenharmony_ci * Copyright (C) 2020 Ansuel Smith <ansuelsmth@gmail.com> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/delay.h> 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/of_mdio.h> 138c2ecf20Sopenharmony_ci#include <linux/of_address.h> 148c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 158c2ecf20Sopenharmony_ci#include <linux/regmap.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci/* MII address register definitions */ 188c2ecf20Sopenharmony_ci#define MII_ADDR_REG_ADDR 0x10 198c2ecf20Sopenharmony_ci#define MII_BUSY BIT(0) 208c2ecf20Sopenharmony_ci#define MII_WRITE BIT(1) 218c2ecf20Sopenharmony_ci#define MII_CLKRANGE_60_100M (0 << 2) 228c2ecf20Sopenharmony_ci#define MII_CLKRANGE_100_150M (1 << 2) 238c2ecf20Sopenharmony_ci#define MII_CLKRANGE_20_35M (2 << 2) 248c2ecf20Sopenharmony_ci#define MII_CLKRANGE_35_60M (3 << 2) 258c2ecf20Sopenharmony_ci#define MII_CLKRANGE_150_250M (4 << 2) 268c2ecf20Sopenharmony_ci#define MII_CLKRANGE_250_300M (5 << 2) 278c2ecf20Sopenharmony_ci#define MII_CLKRANGE_MASK GENMASK(4, 2) 288c2ecf20Sopenharmony_ci#define MII_REG_SHIFT 6 298c2ecf20Sopenharmony_ci#define MII_REG_MASK GENMASK(10, 6) 308c2ecf20Sopenharmony_ci#define MII_ADDR_SHIFT 11 318c2ecf20Sopenharmony_ci#define MII_ADDR_MASK GENMASK(15, 11) 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define MII_DATA_REG_ADDR 0x14 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#define MII_MDIO_DELAY_USEC (1000) 368c2ecf20Sopenharmony_ci#define MII_MDIO_RETRY_MSEC (10) 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistruct ipq8064_mdio { 398c2ecf20Sopenharmony_ci struct regmap *base; /* NSS_GMAC0_BASE */ 408c2ecf20Sopenharmony_ci}; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic int 438c2ecf20Sopenharmony_ciipq8064_mdio_wait_busy(struct ipq8064_mdio *priv) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci u32 busy; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci return regmap_read_poll_timeout(priv->base, MII_ADDR_REG_ADDR, busy, 488c2ecf20Sopenharmony_ci !(busy & MII_BUSY), MII_MDIO_DELAY_USEC, 498c2ecf20Sopenharmony_ci MII_MDIO_RETRY_MSEC * USEC_PER_MSEC); 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic int 538c2ecf20Sopenharmony_ciipq8064_mdio_read(struct mii_bus *bus, int phy_addr, int reg_offset) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci u32 miiaddr = MII_BUSY | MII_CLKRANGE_250_300M; 568c2ecf20Sopenharmony_ci struct ipq8064_mdio *priv = bus->priv; 578c2ecf20Sopenharmony_ci u32 ret_val; 588c2ecf20Sopenharmony_ci int err; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci /* Reject clause 45 */ 618c2ecf20Sopenharmony_ci if (reg_offset & MII_ADDR_C45) 628c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci miiaddr |= ((phy_addr << MII_ADDR_SHIFT) & MII_ADDR_MASK) | 658c2ecf20Sopenharmony_ci ((reg_offset << MII_REG_SHIFT) & MII_REG_MASK); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci regmap_write(priv->base, MII_ADDR_REG_ADDR, miiaddr); 688c2ecf20Sopenharmony_ci usleep_range(8, 10); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci err = ipq8064_mdio_wait_busy(priv); 718c2ecf20Sopenharmony_ci if (err) 728c2ecf20Sopenharmony_ci return err; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci regmap_read(priv->base, MII_DATA_REG_ADDR, &ret_val); 758c2ecf20Sopenharmony_ci return (int)ret_val; 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic int 798c2ecf20Sopenharmony_ciipq8064_mdio_write(struct mii_bus *bus, int phy_addr, int reg_offset, u16 data) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci u32 miiaddr = MII_WRITE | MII_BUSY | MII_CLKRANGE_250_300M; 828c2ecf20Sopenharmony_ci struct ipq8064_mdio *priv = bus->priv; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci /* Reject clause 45 */ 858c2ecf20Sopenharmony_ci if (reg_offset & MII_ADDR_C45) 868c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci regmap_write(priv->base, MII_DATA_REG_ADDR, data); 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci miiaddr |= ((phy_addr << MII_ADDR_SHIFT) & MII_ADDR_MASK) | 918c2ecf20Sopenharmony_ci ((reg_offset << MII_REG_SHIFT) & MII_REG_MASK); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci regmap_write(priv->base, MII_ADDR_REG_ADDR, miiaddr); 948c2ecf20Sopenharmony_ci usleep_range(8, 10); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci return ipq8064_mdio_wait_busy(priv); 978c2ecf20Sopenharmony_ci} 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic const struct regmap_config ipq8064_mdio_regmap_config = { 1008c2ecf20Sopenharmony_ci .reg_bits = 32, 1018c2ecf20Sopenharmony_ci .reg_stride = 4, 1028c2ecf20Sopenharmony_ci .val_bits = 32, 1038c2ecf20Sopenharmony_ci .can_multi_write = false, 1048c2ecf20Sopenharmony_ci /* the mdio lock is used by any user of this mdio driver */ 1058c2ecf20Sopenharmony_ci .disable_locking = true, 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci .cache_type = REGCACHE_NONE, 1088c2ecf20Sopenharmony_ci}; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic int 1118c2ecf20Sopenharmony_ciipq8064_mdio_probe(struct platform_device *pdev) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 1148c2ecf20Sopenharmony_ci struct ipq8064_mdio *priv; 1158c2ecf20Sopenharmony_ci struct resource res; 1168c2ecf20Sopenharmony_ci struct mii_bus *bus; 1178c2ecf20Sopenharmony_ci void __iomem *base; 1188c2ecf20Sopenharmony_ci int ret; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci if (of_address_to_resource(np, 0, &res)) 1218c2ecf20Sopenharmony_ci return -ENOMEM; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci base = ioremap(res.start, resource_size(&res)); 1248c2ecf20Sopenharmony_ci if (!base) 1258c2ecf20Sopenharmony_ci return -ENOMEM; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*priv)); 1288c2ecf20Sopenharmony_ci if (!bus) 1298c2ecf20Sopenharmony_ci return -ENOMEM; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci bus->name = "ipq8064_mdio_bus"; 1328c2ecf20Sopenharmony_ci bus->read = ipq8064_mdio_read; 1338c2ecf20Sopenharmony_ci bus->write = ipq8064_mdio_write; 1348c2ecf20Sopenharmony_ci snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev)); 1358c2ecf20Sopenharmony_ci bus->parent = &pdev->dev; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci priv = bus->priv; 1388c2ecf20Sopenharmony_ci priv->base = devm_regmap_init_mmio(&pdev->dev, base, 1398c2ecf20Sopenharmony_ci &ipq8064_mdio_regmap_config); 1408c2ecf20Sopenharmony_ci if (IS_ERR(priv->base)) 1418c2ecf20Sopenharmony_ci return PTR_ERR(priv->base); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci ret = of_mdiobus_register(bus, np); 1448c2ecf20Sopenharmony_ci if (ret) 1458c2ecf20Sopenharmony_ci return ret; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, bus); 1488c2ecf20Sopenharmony_ci return 0; 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_cistatic int 1528c2ecf20Sopenharmony_ciipq8064_mdio_remove(struct platform_device *pdev) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci struct mii_bus *bus = platform_get_drvdata(pdev); 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci mdiobus_unregister(bus); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci return 0; 1598c2ecf20Sopenharmony_ci} 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_cistatic const struct of_device_id ipq8064_mdio_dt_ids[] = { 1628c2ecf20Sopenharmony_ci { .compatible = "qcom,ipq8064-mdio" }, 1638c2ecf20Sopenharmony_ci { } 1648c2ecf20Sopenharmony_ci}; 1658c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ipq8064_mdio_dt_ids); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_cistatic struct platform_driver ipq8064_mdio_driver = { 1688c2ecf20Sopenharmony_ci .probe = ipq8064_mdio_probe, 1698c2ecf20Sopenharmony_ci .remove = ipq8064_mdio_remove, 1708c2ecf20Sopenharmony_ci .driver = { 1718c2ecf20Sopenharmony_ci .name = "ipq8064-mdio", 1728c2ecf20Sopenharmony_ci .of_match_table = ipq8064_mdio_dt_ids, 1738c2ecf20Sopenharmony_ci }, 1748c2ecf20Sopenharmony_ci}; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cimodule_platform_driver(ipq8064_mdio_driver); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm IPQ8064 MDIO interface driver"); 1798c2ecf20Sopenharmony_ciMODULE_AUTHOR("Christian Lamparter <chunkeey@gmail.com>"); 1808c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ansuel Smith <ansuelsmth@gmail.com>"); 1818c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 182