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/io.h> 7#include <linux/iopoll.h> 8#include <linux/kernel.h> 9#include <linux/module.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_MODE_REG 0x40 16#define MDIO_ADDR_REG 0x44 17#define MDIO_DATA_WRITE_REG 0x48 18#define MDIO_DATA_READ_REG 0x4c 19#define MDIO_CMD_REG 0x50 20#define MDIO_CMD_ACCESS_BUSY BIT(16) 21#define MDIO_CMD_ACCESS_START BIT(8) 22#define MDIO_CMD_ACCESS_CODE_READ 0 23#define MDIO_CMD_ACCESS_CODE_WRITE 1 24#define MDIO_CMD_ACCESS_CODE_C45_ADDR 0 25#define MDIO_CMD_ACCESS_CODE_C45_WRITE 1 26#define MDIO_CMD_ACCESS_CODE_C45_READ 2 27 28/* 0 = Clause 22, 1 = Clause 45 */ 29#define MDIO_MODE_C45 BIT(8) 30 31#define IPQ4019_MDIO_TIMEOUT 10000 32#define IPQ4019_MDIO_SLEEP 10 33 34struct ipq4019_mdio_data { 35 void __iomem *membase; 36}; 37 38static int ipq4019_mdio_wait_busy(struct mii_bus *bus) 39{ 40 struct ipq4019_mdio_data *priv = bus->priv; 41 unsigned int busy; 42 43 return readl_poll_timeout(priv->membase + MDIO_CMD_REG, busy, 44 (busy & MDIO_CMD_ACCESS_BUSY) == 0, 45 IPQ4019_MDIO_SLEEP, IPQ4019_MDIO_TIMEOUT); 46} 47 48static int ipq4019_mdio_read(struct mii_bus *bus, int mii_id, int regnum) 49{ 50 struct ipq4019_mdio_data *priv = bus->priv; 51 unsigned int data; 52 unsigned int cmd; 53 54 if (ipq4019_mdio_wait_busy(bus)) 55 return -ETIMEDOUT; 56 57 /* Clause 45 support */ 58 if (regnum & MII_ADDR_C45) { 59 unsigned int mmd = (regnum >> 16) & 0x1F; 60 unsigned int reg = regnum & 0xFFFF; 61 62 /* Enter Clause 45 mode */ 63 data = readl(priv->membase + MDIO_MODE_REG); 64 65 data |= MDIO_MODE_C45; 66 67 writel(data, priv->membase + MDIO_MODE_REG); 68 69 /* issue the phy address and mmd */ 70 writel((mii_id << 8) | mmd, priv->membase + MDIO_ADDR_REG); 71 72 /* issue reg */ 73 writel(reg, priv->membase + MDIO_DATA_WRITE_REG); 74 75 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; 76 } else { 77 /* Enter Clause 22 mode */ 78 data = readl(priv->membase + MDIO_MODE_REG); 79 80 data &= ~MDIO_MODE_C45; 81 82 writel(data, priv->membase + MDIO_MODE_REG); 83 84 /* issue the phy address and reg */ 85 writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); 86 87 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_READ; 88 } 89 90 /* issue read command */ 91 writel(cmd, priv->membase + MDIO_CMD_REG); 92 93 /* Wait read complete */ 94 if (ipq4019_mdio_wait_busy(bus)) 95 return -ETIMEDOUT; 96 97 if (regnum & MII_ADDR_C45) { 98 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_READ; 99 100 writel(cmd, priv->membase + MDIO_CMD_REG); 101 102 if (ipq4019_mdio_wait_busy(bus)) 103 return -ETIMEDOUT; 104 } 105 106 /* Read and return data */ 107 return readl(priv->membase + MDIO_DATA_READ_REG); 108} 109 110static int ipq4019_mdio_write(struct mii_bus *bus, int mii_id, int regnum, 111 u16 value) 112{ 113 struct ipq4019_mdio_data *priv = bus->priv; 114 unsigned int data; 115 unsigned int cmd; 116 117 if (ipq4019_mdio_wait_busy(bus)) 118 return -ETIMEDOUT; 119 120 /* Clause 45 support */ 121 if (regnum & MII_ADDR_C45) { 122 unsigned int mmd = (regnum >> 16) & 0x1F; 123 unsigned int reg = regnum & 0xFFFF; 124 125 /* Enter Clause 45 mode */ 126 data = readl(priv->membase + MDIO_MODE_REG); 127 128 data |= MDIO_MODE_C45; 129 130 writel(data, priv->membase + MDIO_MODE_REG); 131 132 /* issue the phy address and mmd */ 133 writel((mii_id << 8) | mmd, priv->membase + MDIO_ADDR_REG); 134 135 /* issue reg */ 136 writel(reg, priv->membase + MDIO_DATA_WRITE_REG); 137 138 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_ADDR; 139 140 writel(cmd, priv->membase + MDIO_CMD_REG); 141 142 if (ipq4019_mdio_wait_busy(bus)) 143 return -ETIMEDOUT; 144 } else { 145 /* Enter Clause 22 mode */ 146 data = readl(priv->membase + MDIO_MODE_REG); 147 148 data &= ~MDIO_MODE_C45; 149 150 writel(data, priv->membase + MDIO_MODE_REG); 151 152 /* issue the phy address and reg */ 153 writel((mii_id << 8) | regnum, priv->membase + MDIO_ADDR_REG); 154 } 155 156 /* issue write data */ 157 writel(value, priv->membase + MDIO_DATA_WRITE_REG); 158 159 /* issue write command */ 160 if (regnum & MII_ADDR_C45) 161 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_C45_WRITE; 162 else 163 cmd = MDIO_CMD_ACCESS_START | MDIO_CMD_ACCESS_CODE_WRITE; 164 165 writel(cmd, priv->membase + MDIO_CMD_REG); 166 167 /* Wait write complete */ 168 if (ipq4019_mdio_wait_busy(bus)) 169 return -ETIMEDOUT; 170 171 return 0; 172} 173 174static int ipq4019_mdio_probe(struct platform_device *pdev) 175{ 176 struct ipq4019_mdio_data *priv; 177 struct mii_bus *bus; 178 int ret; 179 180 bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*priv)); 181 if (!bus) 182 return -ENOMEM; 183 184 priv = bus->priv; 185 186 priv->membase = devm_platform_ioremap_resource(pdev, 0); 187 if (IS_ERR(priv->membase)) 188 return PTR_ERR(priv->membase); 189 190 bus->name = "ipq4019_mdio"; 191 bus->read = ipq4019_mdio_read; 192 bus->write = ipq4019_mdio_write; 193 bus->parent = &pdev->dev; 194 snprintf(bus->id, MII_BUS_ID_SIZE, "%s%d", pdev->name, pdev->id); 195 196 ret = of_mdiobus_register(bus, pdev->dev.of_node); 197 if (ret) { 198 dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); 199 return ret; 200 } 201 202 platform_set_drvdata(pdev, bus); 203 204 return 0; 205} 206 207static int ipq4019_mdio_remove(struct platform_device *pdev) 208{ 209 struct mii_bus *bus = platform_get_drvdata(pdev); 210 211 mdiobus_unregister(bus); 212 213 return 0; 214} 215 216static const struct of_device_id ipq4019_mdio_dt_ids[] = { 217 { .compatible = "qcom,ipq4019-mdio" }, 218 { } 219}; 220MODULE_DEVICE_TABLE(of, ipq4019_mdio_dt_ids); 221 222static struct platform_driver ipq4019_mdio_driver = { 223 .probe = ipq4019_mdio_probe, 224 .remove = ipq4019_mdio_remove, 225 .driver = { 226 .name = "ipq4019-mdio", 227 .of_match_table = ipq4019_mdio_dt_ids, 228 }, 229}; 230 231module_platform_driver(ipq4019_mdio_driver); 232 233MODULE_DESCRIPTION("ipq4019 MDIO interface driver"); 234MODULE_AUTHOR("Qualcomm Atheros"); 235MODULE_LICENSE("Dual BSD/GPL"); 236