danborkmann@iogearbox.net | 8a3b7a2 | 2012-01-19 00:39:31 +0000 | [diff] [blame] | 1 | /* |
| 2 | * MDIO bus driver for the Xilinx Axi Ethernet device |
| 3 | * |
| 4 | * Copyright (c) 2009 Secret Lab Technologies, Ltd. |
| 5 | * Copyright (c) 2010 Xilinx, Inc. All rights reserved. |
| 6 | * Copyright (c) 2012 Daniel Borkmann, <daniel.borkmann@tik.ee.ethz.ch> |
| 7 | * Copyright (c) 2012 Ariane Keller, <ariane.keller@tik.ee.ethz.ch> |
| 8 | */ |
| 9 | |
| 10 | #include <linux/of_address.h> |
| 11 | #include <linux/of_mdio.h> |
| 12 | #include <linux/jiffies.h> |
| 13 | |
| 14 | #include "xilinx_axienet.h" |
| 15 | |
| 16 | #define MAX_MDIO_FREQ 2500000 /* 2.5 MHz */ |
| 17 | #define DEFAULT_CLOCK_DIVISOR XAE_MDIO_DIV_DFT |
| 18 | |
| 19 | /* Wait till MDIO interface is ready to accept a new transaction.*/ |
| 20 | int axienet_mdio_wait_until_ready(struct axienet_local *lp) |
| 21 | { |
| 22 | long end = jiffies + 2; |
| 23 | while (!(axienet_ior(lp, XAE_MDIO_MCR_OFFSET) & |
| 24 | XAE_MDIO_MCR_READY_MASK)) { |
| 25 | if (end - jiffies <= 0) { |
| 26 | WARN_ON(1); |
| 27 | return -ETIMEDOUT; |
| 28 | } |
| 29 | udelay(1); |
| 30 | } |
| 31 | return 0; |
| 32 | } |
| 33 | |
| 34 | /** |
| 35 | * axienet_mdio_read - MDIO interface read function |
| 36 | * @bus: Pointer to mii bus structure |
| 37 | * @phy_id: Address of the PHY device |
| 38 | * @reg: PHY register to read |
| 39 | * |
| 40 | * returns: The register contents on success, -ETIMEDOUT on a timeout |
| 41 | * |
| 42 | * Reads the contents of the requested register from the requested PHY |
| 43 | * address by first writing the details into MCR register. After a while |
| 44 | * the register MRD is read to obtain the PHY register content. |
| 45 | */ |
| 46 | static int axienet_mdio_read(struct mii_bus *bus, int phy_id, int reg) |
| 47 | { |
| 48 | u32 rc; |
| 49 | int ret; |
| 50 | struct axienet_local *lp = bus->priv; |
| 51 | |
| 52 | ret = axienet_mdio_wait_until_ready(lp); |
| 53 | if (ret < 0) |
| 54 | return ret; |
| 55 | |
| 56 | axienet_iow(lp, XAE_MDIO_MCR_OFFSET, |
| 57 | (((phy_id << XAE_MDIO_MCR_PHYAD_SHIFT) & |
| 58 | XAE_MDIO_MCR_PHYAD_MASK) | |
| 59 | ((reg << XAE_MDIO_MCR_REGAD_SHIFT) & |
| 60 | XAE_MDIO_MCR_REGAD_MASK) | |
| 61 | XAE_MDIO_MCR_INITIATE_MASK | |
| 62 | XAE_MDIO_MCR_OP_READ_MASK)); |
| 63 | |
| 64 | ret = axienet_mdio_wait_until_ready(lp); |
| 65 | if (ret < 0) |
| 66 | return ret; |
| 67 | |
| 68 | rc = axienet_ior(lp, XAE_MDIO_MRD_OFFSET) & 0x0000FFFF; |
| 69 | |
| 70 | dev_dbg(lp->dev, "axienet_mdio_read(phy_id=%i, reg=%x) == %x\n", |
| 71 | phy_id, reg, rc); |
| 72 | |
| 73 | return rc; |
| 74 | } |
| 75 | |
| 76 | /** |
| 77 | * axienet_mdio_write - MDIO interface write function |
| 78 | * @bus: Pointer to mii bus structure |
| 79 | * @phy_id: Address of the PHY device |
| 80 | * @reg: PHY register to write to |
| 81 | * @val: Value to be written into the register |
| 82 | * |
| 83 | * returns: 0 on success, -ETIMEDOUT on a timeout |
| 84 | * |
| 85 | * Writes the value to the requested register by first writing the value |
| 86 | * into MWD register. The the MCR register is then appropriately setup |
| 87 | * to finish the write operation. |
| 88 | */ |
| 89 | static int axienet_mdio_write(struct mii_bus *bus, int phy_id, int reg, |
| 90 | u16 val) |
| 91 | { |
| 92 | int ret; |
| 93 | struct axienet_local *lp = bus->priv; |
| 94 | |
| 95 | dev_dbg(lp->dev, "axienet_mdio_write(phy_id=%i, reg=%x, val=%x)\n", |
| 96 | phy_id, reg, val); |
| 97 | |
| 98 | ret = axienet_mdio_wait_until_ready(lp); |
| 99 | if (ret < 0) |
| 100 | return ret; |
| 101 | |
| 102 | axienet_iow(lp, XAE_MDIO_MWD_OFFSET, (u32) val); |
| 103 | axienet_iow(lp, XAE_MDIO_MCR_OFFSET, |
| 104 | (((phy_id << XAE_MDIO_MCR_PHYAD_SHIFT) & |
| 105 | XAE_MDIO_MCR_PHYAD_MASK) | |
| 106 | ((reg << XAE_MDIO_MCR_REGAD_SHIFT) & |
| 107 | XAE_MDIO_MCR_REGAD_MASK) | |
| 108 | XAE_MDIO_MCR_INITIATE_MASK | |
| 109 | XAE_MDIO_MCR_OP_WRITE_MASK)); |
| 110 | |
| 111 | ret = axienet_mdio_wait_until_ready(lp); |
| 112 | if (ret < 0) |
| 113 | return ret; |
| 114 | return 0; |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * axienet_mdio_setup - MDIO setup function |
| 119 | * @lp: Pointer to axienet local data structure. |
| 120 | * @np: Pointer to device node |
| 121 | * |
| 122 | * returns: 0 on success, -ETIMEDOUT on a timeout, -ENOMEM when |
| 123 | * mdiobus_alloc (to allocate memory for mii bus structure) fails. |
| 124 | * |
| 125 | * Sets up the MDIO interface by initializing the MDIO clock and enabling the |
| 126 | * MDIO interface in hardware. Register the MDIO interface. |
| 127 | **/ |
| 128 | int axienet_mdio_setup(struct axienet_local *lp, struct device_node *np) |
| 129 | { |
| 130 | int ret; |
| 131 | u32 clk_div, host_clock; |
| 132 | u32 *property_p; |
| 133 | struct mii_bus *bus; |
| 134 | struct resource res; |
| 135 | struct device_node *np1; |
| 136 | |
| 137 | /* clk_div can be calculated by deriving it from the equation: |
| 138 | * fMDIO = fHOST / ((1 + clk_div) * 2) |
| 139 | * |
| 140 | * Where fMDIO <= 2500000, so we get: |
| 141 | * fHOST / ((1 + clk_div) * 2) <= 2500000 |
| 142 | * |
| 143 | * Then we get: |
| 144 | * 1 / ((1 + clk_div) * 2) <= (2500000 / fHOST) |
| 145 | * |
| 146 | * Then we get: |
| 147 | * 1 / (1 + clk_div) <= ((2500000 * 2) / fHOST) |
| 148 | * |
| 149 | * Then we get: |
| 150 | * 1 / (1 + clk_div) <= (5000000 / fHOST) |
| 151 | * |
| 152 | * So: |
| 153 | * (1 + clk_div) >= (fHOST / 5000000) |
| 154 | * |
| 155 | * And finally: |
| 156 | * clk_div >= (fHOST / 5000000) - 1 |
| 157 | * |
| 158 | * fHOST can be read from the flattened device tree as property |
| 159 | * "clock-frequency" from the CPU |
| 160 | */ |
| 161 | |
| 162 | np1 = of_find_node_by_name(NULL, "cpu"); |
| 163 | if (!np1) { |
| 164 | printk(KERN_WARNING "%s(): Could not find CPU device node.", |
| 165 | __func__); |
| 166 | printk(KERN_WARNING "Setting MDIO clock divisor to " |
| 167 | "default %d\n", DEFAULT_CLOCK_DIVISOR); |
| 168 | clk_div = DEFAULT_CLOCK_DIVISOR; |
| 169 | goto issue; |
| 170 | } |
| 171 | property_p = (u32 *) of_get_property(np1, "clock-frequency", NULL); |
| 172 | if (!property_p) { |
| 173 | printk(KERN_WARNING "%s(): Could not find CPU property: " |
| 174 | "clock-frequency.", __func__); |
| 175 | printk(KERN_WARNING "Setting MDIO clock divisor to " |
| 176 | "default %d\n", DEFAULT_CLOCK_DIVISOR); |
| 177 | clk_div = DEFAULT_CLOCK_DIVISOR; |
| 178 | goto issue; |
| 179 | } |
| 180 | |
| 181 | host_clock = be32_to_cpup(property_p); |
| 182 | clk_div = (host_clock / (MAX_MDIO_FREQ * 2)) - 1; |
| 183 | /* If there is any remainder from the division of |
| 184 | * fHOST / (MAX_MDIO_FREQ * 2), then we need to add |
| 185 | * 1 to the clock divisor or we will surely be above 2.5 MHz */ |
| 186 | if (host_clock % (MAX_MDIO_FREQ * 2)) |
| 187 | clk_div++; |
| 188 | |
| 189 | printk(KERN_DEBUG "%s(): Setting MDIO clock divisor to %u based " |
| 190 | "on %u Hz host clock.\n", __func__, clk_div, host_clock); |
| 191 | |
| 192 | of_node_put(np1); |
| 193 | issue: |
| 194 | axienet_iow(lp, XAE_MDIO_MC_OFFSET, |
| 195 | (((u32) clk_div) | XAE_MDIO_MC_MDIOEN_MASK)); |
| 196 | |
| 197 | ret = axienet_mdio_wait_until_ready(lp); |
| 198 | if (ret < 0) |
| 199 | return ret; |
| 200 | |
| 201 | bus = mdiobus_alloc(); |
| 202 | if (!bus) |
| 203 | return -ENOMEM; |
| 204 | |
| 205 | np1 = of_get_parent(lp->phy_node); |
| 206 | of_address_to_resource(np1, 0, &res); |
| 207 | snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx", |
| 208 | (unsigned long long) res.start); |
| 209 | |
| 210 | bus->priv = lp; |
| 211 | bus->name = "Xilinx Axi Ethernet MDIO"; |
| 212 | bus->read = axienet_mdio_read; |
| 213 | bus->write = axienet_mdio_write; |
| 214 | bus->parent = lp->dev; |
| 215 | bus->irq = lp->mdio_irqs; /* preallocated IRQ table */ |
| 216 | lp->mii_bus = bus; |
| 217 | |
| 218 | ret = of_mdiobus_register(bus, np1); |
| 219 | if (ret) { |
| 220 | mdiobus_free(bus); |
| 221 | return ret; |
| 222 | } |
| 223 | return 0; |
| 224 | } |
| 225 | |
| 226 | /** |
| 227 | * axienet_mdio_teardown - MDIO remove function |
| 228 | * @lp: Pointer to axienet local data structure. |
| 229 | * |
| 230 | * Unregisters the MDIO and frees any associate memory for mii bus. |
| 231 | */ |
| 232 | void axienet_mdio_teardown(struct axienet_local *lp) |
| 233 | { |
| 234 | mdiobus_unregister(lp->mii_bus); |
| 235 | kfree(lp->mii_bus->irq); |
| 236 | mdiobus_free(lp->mii_bus); |
| 237 | lp->mii_bus = NULL; |
| 238 | } |