1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2 /* Copyright (c) 2015, The Linux Foundation. All rights reserved. */ 3 /* Copyright (c) 2020 Sartura Ltd. */ 4 5 #include <linux/delay.h> 6 #include <linux/kernel.h> 7 #include <linux/module.h> 8 #include <linux/io.h> 9 #include <linux/iopoll.h> 10 #include <linux/of_address.h> 11 #include <linux/of_mdio.h> 12 #include <linux/phy.h> 13 #include <linux/platform_device.h> 14 15 #define MDIO_ADDR_REG 0x44 16 #define MDIO_DATA_WRITE_REG 0x48 17 #define MDIO_DATA_READ_REG 0x4c 18 #define MDIO_CMD_REG 0x50 19 #define MDIO_CMD_ACCESS_BUSY BIT(16) 20 #define MDIO_CMD_ACCESS_START BIT(8) 21 #define MDIO_CMD_ACCESS_CODE_READ 0 22 #define MDIO_CMD_ACCESS_CODE_WRITE 1 23 24 #define ipq4019_MDIO_TIMEOUT 10000 25 #define ipq4019_MDIO_SLEEP 10 26 27 struct ipq4019_mdio_data { 28 void __iomem *membase; 29 }; 30 31 static int ipq4019_mdio_wait_busy(struct mii_bus *bus) 32 { 33 struct ipq4019_mdio_data *priv = bus->priv; 34 unsigned int busy; 35 36 return readl_poll_timeout(priv->membase + MDIO_CMD_REG, busy, 37 (busy & MDIO_CMD_ACCESS_BUSY) == 0, 38 ipq4019_MDIO_SLEEP, ipq4019_MDIO_TIMEOUT); 39 } 40 41 static int ipq4019_mdio_read(struct mii_bus *bus, int mii_id, int regnum) 42 { 43 struct ipq4019_mdio_data *priv = bus->priv; 44 unsigned int cmd; 45 46 /* Reject clause 45 */ 47 if (regnum & MII_ADDR_C45) 48 return -EOPNOTSUPP; 49 50 if (ipq4019_mdio_wait_busy(bus)) 51 return -ETIMEDOUT; 52 53 /* issue the phy address and reg */ 54 writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); 55 56 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_READ; 57 58 /* issue read command */ 59 writel(cmd, priv->membase + MDIO_CMD_REG); 60 61 /* Wait read complete */ 62 if (ipq4019_mdio_wait_busy(bus)) 63 return -ETIMEDOUT; 64 65 /* Read and return data */ 66 return readl(priv->membase + MDIO_DATA_READ_REG); 67 } 68 69 static int ipq4019_mdio_write(struct mii_bus *bus, int mii_id, int regnum, 70 u16 value) 71 { 72 struct ipq4019_mdio_data *priv = bus->priv; 73 unsigned int cmd; 74 75 /* Reject clause 45 */ 76 if (regnum & MII_ADDR_C45) 77 return -EOPNOTSUPP; 78 79 if (ipq4019_mdio_wait_busy(bus)) 80 return -ETIMEDOUT; 81 82 /* issue the phy address and reg */ 83 writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); 84 85 /* issue write data */ 86 writel(value, priv->membase + MDIO_DATA_WRITE_REG); 87 88 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_WRITE; 89 /* issue write command */ 90 writel(cmd, priv->membase + MDIO_CMD_REG); 91 92 /* Wait write complete */ 93 if (ipq4019_mdio_wait_busy(bus)) 94 return -ETIMEDOUT; 95 96 return 0; 97 } 98 99 static int ipq4019_mdio_probe(struct platform_device *pdev) 100 { 101 struct ipq4019_mdio_data *priv; 102 struct mii_bus *bus; 103 int ret; 104 105 bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*priv)); 106 if (!bus) 107 return -ENOMEM; 108 109 priv = bus->priv; 110 111 priv->membase = devm_platform_ioremap_resource(pdev, 0); 112 if (IS_ERR(priv->membase)) 113 return PTR_ERR(priv->membase); 114 115 bus->name = "ipq4019_mdio"; 116 bus->read = ipq4019_mdio_read; 117 bus->write = ipq4019_mdio_write; 118 bus->parent = &pdev->dev; 119 snprintf(bus->id, MII_BUS_ID_SIZE, "%s%d", pdev->name, pdev->id); 120 121 ret = of_mdiobus_register(bus, pdev->dev.of_node); 122 if (ret) { 123 dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); 124 return ret; 125 } 126 127 platform_set_drvdata(pdev, bus); 128 129 return 0; 130 } 131 132 static int ipq4019_mdio_remove(struct platform_device *pdev) 133 { 134 struct mii_bus *bus = platform_get_drvdata(pdev); 135 136 mdiobus_unregister(bus); 137 138 return 0; 139 } 140 141 static const struct of_device_id ipq4019_mdio_dt_ids[] = { 142 { .compatible = "qcom,ipq4019-mdio" }, 143 { } 144 }; 145 MODULE_DEVICE_TABLE(of, ipq4019_mdio_dt_ids); 146 147 static struct platform_driver ipq4019_mdio_driver = { 148 .probe = ipq4019_mdio_probe, 149 .remove = ipq4019_mdio_remove, 150 .driver = { 151 .name = "ipq4019-mdio", 152 .of_match_table = ipq4019_mdio_dt_ids, 153 }, 154 }; 155 156 module_platform_driver(ipq4019_mdio_driver); 157 158 MODULE_DESCRIPTION("ipq4019 MDIO interface driver"); 159 MODULE_AUTHOR("Qualcomm Atheros"); 160 MODULE_LICENSE("Dual BSD/GPL"); 161