Commit | Line | Data |
---|---|---|
92744989 GL |
1 | /* |
2 | * MDIO bus driver for the Xilinx TEMAC device | |
3 | * | |
4 | * Copyright (c) 2009 Secret Lab Technologies, Ltd. | |
5 | */ | |
6 | ||
7 | #include <linux/io.h> | |
8 | #include <linux/netdevice.h> | |
9 | #include <linux/mutex.h> | |
10 | #include <linux/phy.h> | |
11 | #include <linux/of.h> | |
12 | #include <linux/of_device.h> | |
9f1a1fca | 13 | #include <linux/of_address.h> |
5a0e3ad6 | 14 | #include <linux/slab.h> |
92744989 GL |
15 | #include <linux/of_mdio.h> |
16 | ||
17 | #include "ll_temac.h" | |
18 | ||
19 | /* --------------------------------------------------------------------- | |
20 | * MDIO Bus functions | |
21 | */ | |
22 | static int temac_mdio_read(struct mii_bus *bus, int phy_id, int reg) | |
23 | { | |
24 | struct temac_local *lp = bus->priv; | |
25 | u32 rc; | |
26 | ||
27 | /* Write the PHY address to the MIIM Access Initiator register. | |
28 | * When the transfer completes, the PHY register value will appear | |
29 | * in the LSW0 register */ | |
30 | mutex_lock(&lp->indirect_mutex); | |
31 | temac_iow(lp, XTE_LSW0_OFFSET, (phy_id << 5) | reg); | |
32 | rc = temac_indirect_in32(lp, XTE_MIIMAI_OFFSET); | |
33 | mutex_unlock(&lp->indirect_mutex); | |
34 | ||
35 | dev_dbg(lp->dev, "temac_mdio_read(phy_id=%i, reg=%x) == %x\n", | |
36 | phy_id, reg, rc); | |
37 | ||
38 | return rc; | |
39 | } | |
40 | ||
41 | static int temac_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val) | |
42 | { | |
43 | struct temac_local *lp = bus->priv; | |
44 | ||
45 | dev_dbg(lp->dev, "temac_mdio_write(phy_id=%i, reg=%x, val=%x)\n", | |
46 | phy_id, reg, val); | |
47 | ||
48 | /* First write the desired value into the write data register | |
49 | * and then write the address into the access initiator register | |
50 | */ | |
51 | mutex_lock(&lp->indirect_mutex); | |
52 | temac_indirect_out32(lp, XTE_MGTDR_OFFSET, val); | |
53 | temac_indirect_out32(lp, XTE_MIIMAI_OFFSET, (phy_id << 5) | reg); | |
54 | mutex_unlock(&lp->indirect_mutex); | |
55 | ||
56 | return 0; | |
57 | } | |
58 | ||
59 | int temac_mdio_setup(struct temac_local *lp, struct device_node *np) | |
60 | { | |
61 | struct mii_bus *bus; | |
62 | const u32 *bus_hz; | |
63 | int clk_div; | |
64 | int rc, size; | |
65 | struct resource res; | |
66 | ||
67 | /* Calculate a reasonable divisor for the clock rate */ | |
68 | clk_div = 0x3f; /* worst-case default setting */ | |
69 | bus_hz = of_get_property(np, "clock-frequency", &size); | |
70 | if (bus_hz && size >= sizeof(*bus_hz)) { | |
71 | clk_div = (*bus_hz) / (2500 * 1000 * 2) - 1; | |
72 | if (clk_div < 1) | |
73 | clk_div = 1; | |
74 | if (clk_div > 0x3f) | |
75 | clk_div = 0x3f; | |
76 | } | |
77 | ||
78 | /* Enable the MDIO bus by asserting the enable bit and writing | |
79 | * in the clock config */ | |
80 | mutex_lock(&lp->indirect_mutex); | |
81 | temac_indirect_out32(lp, XTE_MC_OFFSET, 1 << 6 | clk_div); | |
82 | mutex_unlock(&lp->indirect_mutex); | |
83 | ||
84 | bus = mdiobus_alloc(); | |
85 | if (!bus) | |
86 | return -ENOMEM; | |
87 | ||
88 | of_address_to_resource(np, 0, &res); | |
89 | snprintf(bus->id, MII_BUS_ID_SIZE, "%.8llx", | |
90 | (unsigned long long)res.start); | |
91 | bus->priv = lp; | |
92 | bus->name = "Xilinx TEMAC MDIO"; | |
93 | bus->read = temac_mdio_read; | |
94 | bus->write = temac_mdio_write; | |
95 | bus->parent = lp->dev; | |
96 | bus->irq = lp->mdio_irqs; /* preallocated IRQ table */ | |
97 | ||
98 | lp->mii_bus = bus; | |
99 | ||
100 | rc = of_mdiobus_register(bus, np); | |
101 | if (rc) | |
102 | goto err_register; | |
103 | ||
104 | mutex_lock(&lp->indirect_mutex); | |
105 | dev_dbg(lp->dev, "MDIO bus registered; MC:%x\n", | |
106 | temac_indirect_in32(lp, XTE_MC_OFFSET)); | |
107 | mutex_unlock(&lp->indirect_mutex); | |
108 | return 0; | |
109 | ||
110 | err_register: | |
111 | mdiobus_free(bus); | |
112 | return rc; | |
113 | } | |
114 | ||
115 | void temac_mdio_teardown(struct temac_local *lp) | |
116 | { | |
117 | mdiobus_unregister(lp->mii_bus); | |
118 | kfree(lp->mii_bus->irq); | |
119 | mdiobus_free(lp->mii_bus); | |
120 | lp->mii_bus = NULL; | |
121 | } | |
122 |