1/* 2 * Broadcom UniMAC MDIO bus controller driver 3 * 4 * Copyright (C) 2014, Broadcom Corporation 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 */ 11 12#include <linux/kernel.h> 13#include <linux/phy.h> 14#include <linux/platform_device.h> 15#include <linux/sched.h> 16#include <linux/module.h> 17#include <linux/io.h> 18#include <linux/delay.h> 19 20#include <linux/of.h> 21#include <linux/of_platform.h> 22#include <linux/of_mdio.h> 23 24#define MDIO_CMD 0x00 25#define MDIO_START_BUSY (1 << 29) 26#define MDIO_READ_FAIL (1 << 28) 27#define MDIO_RD (2 << 26) 28#define MDIO_WR (1 << 26) 29#define MDIO_PMD_SHIFT 21 30#define MDIO_PMD_MASK 0x1F 31#define MDIO_REG_SHIFT 16 32#define MDIO_REG_MASK 0x1F 33 34#define MDIO_CFG 0x04 35#define MDIO_C22 (1 << 0) 36#define MDIO_C45 0 37#define MDIO_CLK_DIV_SHIFT 4 38#define MDIO_CLK_DIV_MASK 0x3F 39#define MDIO_SUPP_PREAMBLE (1 << 12) 40 41struct unimac_mdio_priv { 42 struct mii_bus *mii_bus; 43 void __iomem *base; 44}; 45 46static inline void unimac_mdio_start(struct unimac_mdio_priv *priv) 47{ 48 u32 reg; 49 50 reg = __raw_readl(priv->base + MDIO_CMD); 51 reg |= MDIO_START_BUSY; 52 __raw_writel(reg, priv->base + MDIO_CMD); 53} 54 55static inline unsigned int unimac_mdio_busy(struct unimac_mdio_priv *priv) 56{ 57 return __raw_readl(priv->base + MDIO_CMD) & MDIO_START_BUSY; 58} 59 60static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg) 61{ 62 struct unimac_mdio_priv *priv = bus->priv; 63 unsigned int timeout = 1000; 64 u32 cmd; 65 66 /* Prepare the read operation */ 67 cmd = MDIO_RD | (phy_id << MDIO_PMD_SHIFT) | (reg << MDIO_REG_SHIFT); 68 __raw_writel(cmd, priv->base + MDIO_CMD); 69 70 /* Start MDIO transaction */ 71 unimac_mdio_start(priv); 72 73 do { 74 if (!unimac_mdio_busy(priv)) 75 break; 76 77 usleep_range(1000, 2000); 78 } while (timeout--); 79 80 if (!timeout) 81 return -ETIMEDOUT; 82 83 cmd = __raw_readl(priv->base + MDIO_CMD); 84 if (cmd & MDIO_READ_FAIL) 85 return -EIO; 86 87 return cmd & 0xffff; 88} 89 90static int unimac_mdio_write(struct mii_bus *bus, int phy_id, 91 int reg, u16 val) 92{ 93 struct unimac_mdio_priv *priv = bus->priv; 94 unsigned int timeout = 1000; 95 u32 cmd; 96 97 /* Prepare the write operation */ 98 cmd = MDIO_WR | (phy_id << MDIO_PMD_SHIFT) | 99 (reg << MDIO_REG_SHIFT) | (0xffff & val); 100 __raw_writel(cmd, priv->base + MDIO_CMD); 101 102 unimac_mdio_start(priv); 103 104 do { 105 if (!unimac_mdio_busy(priv)) 106 break; 107 108 usleep_range(1000, 2000); 109 } while (timeout--); 110 111 if (!timeout) 112 return -ETIMEDOUT; 113 114 return 0; 115} 116 117static int unimac_mdio_probe(struct platform_device *pdev) 118{ 119 struct unimac_mdio_priv *priv; 120 struct device_node *np; 121 struct mii_bus *bus; 122 struct resource *r; 123 int ret; 124 125 np = pdev->dev.of_node; 126 127 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 128 if (!priv) 129 return -ENOMEM; 130 131 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 132 133 /* Just ioremap, as this MDIO block is usually integrated into an 134 * Ethernet MAC controller register range 135 */ 136 priv->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); 137 if (!priv->base) { 138 dev_err(&pdev->dev, "failed to remap register\n"); 139 return -ENOMEM; 140 } 141 142 priv->mii_bus = mdiobus_alloc(); 143 if (!priv->mii_bus) 144 return -ENOMEM; 145 146 bus = priv->mii_bus; 147 bus->priv = priv; 148 bus->name = "unimac MII bus"; 149 bus->parent = &pdev->dev; 150 bus->read = unimac_mdio_read; 151 bus->write = unimac_mdio_write; 152 snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name); 153 154 bus->irq = kcalloc(PHY_MAX_ADDR, sizeof(int), GFP_KERNEL); 155 if (!bus->irq) { 156 ret = -ENOMEM; 157 goto out_mdio_free; 158 } 159 160 ret = of_mdiobus_register(bus, np); 161 if (ret) { 162 dev_err(&pdev->dev, "MDIO bus registration failed\n"); 163 goto out_mdio_irq; 164 } 165 166 platform_set_drvdata(pdev, priv); 167 168 dev_info(&pdev->dev, "Broadcom UniMAC MDIO bus at 0x%p\n", priv->base); 169 170 return 0; 171 172out_mdio_irq: 173 kfree(bus->irq); 174out_mdio_free: 175 mdiobus_free(bus); 176 return ret; 177} 178 179static int unimac_mdio_remove(struct platform_device *pdev) 180{ 181 struct unimac_mdio_priv *priv = platform_get_drvdata(pdev); 182 183 mdiobus_unregister(priv->mii_bus); 184 kfree(priv->mii_bus->irq); 185 mdiobus_free(priv->mii_bus); 186 187 return 0; 188} 189 190static const struct of_device_id unimac_mdio_ids[] = { 191 { .compatible = "brcm,genet-mdio-v4", }, 192 { .compatible = "brcm,genet-mdio-v3", }, 193 { .compatible = "brcm,genet-mdio-v2", }, 194 { .compatible = "brcm,genet-mdio-v1", }, 195 { .compatible = "brcm,unimac-mdio", }, 196 { /* sentinel */ }, 197}; 198 199static struct platform_driver unimac_mdio_driver = { 200 .driver = { 201 .name = "unimac-mdio", 202 .of_match_table = unimac_mdio_ids, 203 }, 204 .probe = unimac_mdio_probe, 205 .remove = unimac_mdio_remove, 206}; 207module_platform_driver(unimac_mdio_driver); 208 209MODULE_AUTHOR("Broadcom Corporation"); 210MODULE_DESCRIPTION("Broadcom UniMAC MDIO bus controller"); 211MODULE_LICENSE("GPL"); 212MODULE_ALIAS("platform:unimac-mdio"); 213