brcm47xx: b44: fix error handling for dummy phy creation
[openwrt.git] / target / linux / brcm47xx / patches-3.10 / 205-b44-add-phylib-support.patch
1 From 31963d998d2984079dc4f4b36b7df170d85f6d66 Mon Sep 17 00:00:00 2001
2 From: Hauke Mehrtens <hauke@hauke-m.de>
3 Date: Thu, 3 Oct 2013 22:07:11 +0200
4 Subject: [PATCH 6/9] b44: add phylib support
5
6 Most of the older home routers based on the Broadcom BCM47XX SoC series
7 are using a MAC that is supported by b44. On most of these routers not
8 the internal PHY of this MAC core is used, but a switch sometimes on an
9 external chip or integrated into the same SoC as the Ethernet core.
10 For this switch a special PHY driver is needed which should not be
11 integrated into b44 as the same switches are also used by other
12 Broadcom home networking SoCs which are using different Ethernet MAC
13 drivers. This was tested with the b53 switch driver which is currently
14 on its way to mainline.
15
16 With this patch we scan the mdio bus when the sprom or nvram says that
17 the PHY address is 30, if a PHY was found at this address b44 uses it.
18
19 This was tested with a BCM4704, BCM4712 and BCM5354.
20
21 Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
22 ---
23  drivers/net/ethernet/broadcom/Kconfig |    1 +
24  drivers/net/ethernet/broadcom/b44.c   |  181 ++++++++++++++++++++++++++++++++-
25  drivers/net/ethernet/broadcom/b44.h   |    4 +
26  3 files changed, 183 insertions(+), 3 deletions(-)
27
28 --- a/drivers/net/ethernet/broadcom/Kconfig
29 +++ b/drivers/net/ethernet/broadcom/Kconfig
30 @@ -24,6 +24,7 @@ config B44
31         select SSB
32         select NET_CORE
33         select MII
34 +       select PHYLIB
35         ---help---
36           If you have a network (Ethernet) controller of this type, say Y
37           or M and read the Ethernet-HOWTO, available from
38 --- a/drivers/net/ethernet/broadcom/b44.c
39 +++ b/drivers/net/ethernet/broadcom/b44.c
40 @@ -6,6 +6,7 @@
41   * Copyright (C) 2006 Felix Fietkau (nbd@openwrt.org)
42   * Copyright (C) 2006 Broadcom Corporation.
43   * Copyright (C) 2007 Michael Buesch <m@bues.ch>
44 + * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
45   *
46   * Distribute under GPL.
47   */
48 @@ -29,6 +30,7 @@
49  #include <linux/dma-mapping.h>
50  #include <linux/ssb/ssb.h>
51  #include <linux/slab.h>
52 +#include <linux/phy.h>
53  
54  #include <asm/uaccess.h>
55  #include <asm/io.h>
56 @@ -316,6 +318,23 @@ static void b44_mdio_write_mii(struct ne
57         __b44_writephy(bp, phy_id, location, val);
58  }
59  
60 +static int b44_mdio_read_phylib(struct mii_bus *bus, int phy_id, int location)
61 +{
62 +       u32 val;
63 +       struct b44 *bp = bus->priv;
64 +       int rc = __b44_readphy(bp, phy_id, location, &val);
65 +       if (rc)
66 +               return 0xffffffff;
67 +       return val;
68 +}
69 +
70 +static int b44_mdio_write_phylib(struct mii_bus *bus, int phy_id, int location,
71 +                                u16 val)
72 +{
73 +       struct b44 *bp = bus->priv;
74 +       return __b44_writephy(bp, phy_id, location, val);
75 +}
76 +
77  static int b44_phy_reset(struct b44 *bp)
78  {
79         u32 val;
80 @@ -1805,6 +1824,11 @@ static int b44_get_settings(struct net_d
81  {
82         struct b44 *bp = netdev_priv(dev);
83  
84 +       if (bp->flags & B44_FLAG_EXTERNAL_PHY) {
85 +               BUG_ON(!bp->phydev);
86 +               return phy_ethtool_gset(bp->phydev, cmd);
87 +       }
88 +
89         cmd->supported = (SUPPORTED_Autoneg);
90         cmd->supported |= (SUPPORTED_100baseT_Half |
91                           SUPPORTED_100baseT_Full |
92 @@ -1846,7 +1870,23 @@ static int b44_get_settings(struct net_d
93  static int b44_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
94  {
95         struct b44 *bp = netdev_priv(dev);
96 -       u32 speed = ethtool_cmd_speed(cmd);
97 +       u32 speed;
98 +       int ret;
99 +
100 +       if (bp->flags & B44_FLAG_EXTERNAL_PHY) {
101 +               BUG_ON(!bp->phydev);
102 +               spin_lock_irq(&bp->lock);
103 +               if (netif_running(dev))
104 +                       b44_setup_phy(bp);
105 +
106 +               ret = phy_ethtool_sset(bp->phydev, cmd);
107 +
108 +               spin_unlock_irq(&bp->lock);
109 +
110 +               return ret;
111 +       }
112 +
113 +       speed = ethtool_cmd_speed(cmd);
114  
115         /* We do not support gigabit. */
116         if (cmd->autoneg == AUTONEG_ENABLE) {
117 @@ -2076,7 +2116,6 @@ static const struct ethtool_ops b44_etht
118  
119  static int b44_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
120  {
121 -       struct mii_ioctl_data *data = if_mii(ifr);
122         struct b44 *bp = netdev_priv(dev);
123         int err = -EINVAL;
124  
125 @@ -2084,7 +2123,12 @@ static int b44_ioctl(struct net_device *
126                 goto out;
127  
128         spin_lock_irq(&bp->lock);
129 -       err = generic_mii_ioctl(&bp->mii_if, data, cmd, NULL);
130 +       if (bp->flags & B44_FLAG_EXTERNAL_PHY) {
131 +               BUG_ON(bp->phydev);
132 +               err = phy_mii_ioctl(bp->phydev, ifr, cmd);
133 +       } else {
134 +               err = generic_mii_ioctl(&bp->mii_if, if_mii(ifr), cmd, NULL);
135 +       }
136         spin_unlock_irq(&bp->lock);
137  out:
138         return err;
139 @@ -2146,6 +2190,124 @@ static const struct net_device_ops b44_n
140  #endif
141  };
142  
143 +static void b44_adjust_link(struct net_device *dev)
144 +{
145 +       struct b44 *bp = netdev_priv(dev);
146 +       struct phy_device *phydev = bp->phydev;
147 +       bool status_changed = 0;
148 +
149 +       BUG_ON(!phydev);
150 +
151 +       if (bp->old_link != phydev->link) {
152 +               status_changed = 1;
153 +               bp->old_link = phydev->link;
154 +       }
155 +
156 +       /* reflect duplex change */
157 +       if (phydev->link && (bp->old_duplex != phydev->duplex)) {
158 +               status_changed = 1;
159 +               bp->old_duplex = phydev->duplex;
160 +       }
161 +
162 +       if (status_changed)
163 +               phy_print_status(phydev);
164 +}
165 +
166 +static int b44_register_phy_one(struct b44 *bp)
167 +{
168 +       struct mii_bus *mii_bus;
169 +       struct ssb_device *sdev = bp->sdev;
170 +       struct phy_device *phydev;
171 +       int err;
172 +
173 +       mii_bus = mdiobus_alloc();
174 +       if (!mii_bus) {
175 +               dev_err(sdev->dev, "mdiobus_alloc() failed\n");
176 +               err = -ENOMEM;
177 +               goto err_out;
178 +       }
179 +
180 +       mii_bus->priv = bp;
181 +       mii_bus->read = b44_mdio_read_phylib;
182 +       mii_bus->write = b44_mdio_write_phylib;
183 +       mii_bus->name = "b44_eth_mii";
184 +       mii_bus->parent = sdev->dev;
185 +       mii_bus->phy_mask = ~(1 << bp->phy_addr);
186 +       snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%x", instance);
187 +       mii_bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL);
188 +       if (!mii_bus->irq) {
189 +               dev_err(sdev->dev, "mii_bus irq allocation failed\n");
190 +               err = -ENOMEM;
191 +               goto err_out_mdiobus;
192 +       }
193 +
194 +       memset(mii_bus->irq, PHY_POLL, sizeof(int) * PHY_MAX_ADDR);
195 +
196 +       bp->mii_bus = mii_bus;
197 +
198 +       err = mdiobus_register(mii_bus);
199 +       if (err) {
200 +               dev_err(sdev->dev, "failed to register MII bus\n");
201 +               goto err_out_mdiobus_irq;
202 +       }
203 +
204 +       phydev = bp->mii_bus->phy_map[bp->phy_addr];
205 +       if (!phydev) {
206 +               dev_err(sdev->dev, "could not find PHY at %i\n", bp->phy_addr);
207 +               err = -ENODEV;
208 +               goto err_out_mdiobus_unregister;
209 +       }
210 +
211 +       err = phy_connect_direct(bp->dev, phydev, &b44_adjust_link,
212 +                                PHY_INTERFACE_MODE_MII);
213 +       if (err < 0) {
214 +               dev_err(sdev->dev, "could not attach PHY at %i\n",
215 +                       bp->phy_addr);
216 +               goto err_out_mdiobus_unregister;
217 +       }
218 +
219 +       /* mask with MAC supported features */
220 +       phydev->supported &= (SUPPORTED_10baseT_Half |
221 +                             SUPPORTED_10baseT_Full |
222 +                             SUPPORTED_100baseT_Half |
223 +                             SUPPORTED_100baseT_Full |
224 +                             SUPPORTED_Autoneg |
225 +                             SUPPORTED_MII);
226 +       phydev->advertising = phydev->supported;
227 +
228 +       bp->phydev = phydev;
229 +       bp->old_link = 0;
230 +       bp->old_duplex = -1;
231 +       bp->phy_addr = phydev->addr;
232 +
233 +       dev_info(sdev->dev, "attached PHY driver [%s] (mii_bus:phy_addr=%s)\n",
234 +                phydev->drv->name, dev_name(&phydev->dev));
235 +
236 +       return 0;
237 +
238 +err_out_mdiobus_unregister:
239 +       mdiobus_unregister(mii_bus);
240 +
241 +err_out_mdiobus_irq:
242 +       kfree(mii_bus->irq);
243 +
244 +err_out_mdiobus:
245 +       mdiobus_free(mii_bus);
246 +
247 +err_out:
248 +       return err;
249 +}
250 +
251 +static void b44_unregister_phy_one(struct b44 *bp)
252 +{
253 +       struct mii_bus *mii_bus = bp->mii_bus;
254 +
255 +       phy_disconnect(bp->phydev);
256 +       mdiobus_unregister(mii_bus);
257 +       kfree(mii_bus->irq);
258 +       mdiobus_free(mii_bus);
259 +}
260 +
261  static int b44_init_one(struct ssb_device *sdev,
262                         const struct ssb_device_id *ent)
263  {
264 @@ -2246,10 +2408,20 @@ static int b44_init_one(struct ssb_devic
265         if (b44_phy_reset(bp) < 0)
266                 bp->phy_addr = B44_PHY_ADDR_NO_LOACL_PHY;
267  
268 +       if (bp->flags & B44_FLAG_EXTERNAL_PHY) {
269 +               err = b44_register_phy_one(bp);
270 +               if (err) {
271 +                       dev_err(sdev->dev, "Cannot register PHY, aborting\n");
272 +                       goto err_out_unregister_netdev;
273 +               }
274 +       }
275 +
276         netdev_info(dev, "%s %pM\n", DRV_DESCRIPTION, dev->dev_addr);
277  
278         return 0;
279  
280 +err_out_unregister_netdev:
281 +       unregister_netdev(dev);
282  err_out_powerdown:
283         ssb_bus_may_powerdown(sdev->bus);
284  
285 @@ -2263,8 +2435,11 @@ out:
286  static void b44_remove_one(struct ssb_device *sdev)
287  {
288         struct net_device *dev = ssb_get_drvdata(sdev);
289 +       struct b44 *bp = netdev_priv(dev);
290  
291         unregister_netdev(dev);
292 +       if (bp->flags & B44_FLAG_EXTERNAL_PHY)
293 +               b44_unregister_phy_one(bp);
294         ssb_device_disable(sdev, 0);
295         ssb_bus_may_powerdown(sdev->bus);
296         free_netdev(dev);
297 --- a/drivers/net/ethernet/broadcom/b44.h
298 +++ b/drivers/net/ethernet/broadcom/b44.h
299 @@ -397,6 +397,10 @@ struct b44 {
300         u32                     tx_pending;
301         u8                      phy_addr;
302         u8                      force_copybreak;
303 +       struct phy_device       *phydev;
304 +       struct mii_bus          *mii_bus;
305 +       int                     old_link;
306 +       int                     old_duplex;
307         struct mii_if_info      mii_if;
308  };
309