net/fsl_pq_mdio: streamline probing of MDIO nodes
authorTimur Tabi <timur@freescale.com>
Wed, 29 Aug 2012 08:08:01 +0000 (08:08 +0000)
committerDavid S. Miller <davem@davemloft.net>
Thu, 30 Aug 2012 17:29:32 +0000 (13:29 -0400)
Make the device tree probe function more data-driven, so that it no longer
searches the 'compatible' property more than once.  The of_device_id[] array
allows for per-entry private data, so we use that to store details about each
type of node that the driver supports.  This removes the need to check the
'compatible' property inside the probe function.

The driver supports four types on MDIO devices:

1) Gianfar MDIO nodes that only map the MII registers
2) Gianfar MDIO nodes that map the full MDIO register set
3) eTSEC2 MDIO nodes (which map the full MDIO register set)
4) QE MDIO nodes (which map only the MII registers)

Gianfar, eTSEC2, and QE have different mappings for the TBIPA register, which
is needed to initialize the TBI PHY.  In addition, the QE needs a special
hack because of the way the device tree is ordered.

All of this information is encapsulated in the fsl_pq_mdio_data structure,
so when an MDIO node is probed, per-device data and functions are used
to determine how to initialize the device.

Signed-off-by: Timur Tabi <timur@freescale.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/freescale/fsl_pq_mdio.c

index 44f00a4..7eef1bb 100644 (file)
 #include <linux/init.h>
 #include <linux/delay.h>
 #include <linux/module.h>
-#include <linux/platform_device.h>
 #include <linux/mii.h>
 #include <linux/of_address.h>
 #include <linux/of_mdio.h>
-#include <linux/of_platform.h>
+#include <linux/of_device.h>
 
 #include <asm/io.h>
 #include <asm/ucc.h>   /* for ucc_set_qe_mux_mii_mng() */
 
 #define MII_READ_COMMAND       0x00000001
 
+struct fsl_pq_mii {
+       u32 miimcfg;    /* MII management configuration reg */
+       u32 miimcom;    /* MII management command reg */
+       u32 miimadd;    /* MII management address reg */
+       u32 miimcon;    /* MII management control reg */
+       u32 miimstat;   /* MII management status reg */
+       u32 miimind;    /* MII management indication reg */
+};
+
 struct fsl_pq_mdio {
        u8 res1[16];
        u32 ieventm;    /* MDIO Interrupt event register (for etsec2)*/
@@ -48,12 +56,7 @@ struct fsl_pq_mdio {
        u8 res2[4];
        u32 emapm;      /* MDIO Event mapping register (for etsec2)*/
        u8 res3[1280];
-       u32 miimcfg;    /* MII management configuration reg */
-       u32 miimcom;    /* MII management command reg */
-       u32 miimadd;    /* MII management address reg */
-       u32 miimcon;    /* MII management control reg */
-       u32 miimstat;   /* MII management status reg */
-       u32 miimind;    /* MII management indication reg */
+       struct fsl_pq_mii mii;
        u8 res4[28];
        u32 utbipar;    /* TBI phy address reg (only on UCC) */
        u8 res5[2728];
@@ -64,7 +67,25 @@ struct fsl_pq_mdio {
 
 struct fsl_pq_mdio_priv {
        void __iomem *map;
-       struct fsl_pq_mdio __iomem *regs;
+       struct fsl_pq_mii __iomem *regs;
+};
+
+/*
+ * Per-device-type data.  Each type of device tree node that we support gets
+ * one of these.
+ *
+ * @mii_offset: the offset of the MII registers within the memory map of the
+ * node.  Some nodes define only the MII registers, and some define the whole
+ * MAC (which includes the MII registers).
+ *
+ * @get_tbipa: determines the address of the TBIPA register
+ *
+ * @ucc_configure: a special function for extra QE configuration
+ */
+struct fsl_pq_mdio_data {
+       unsigned int mii_offset;        /* offset of the MII registers */
+       uint32_t __iomem * (*get_tbipa)(void __iomem *p);
+       void (*ucc_configure)(phys_addr_t start, phys_addr_t end);
 };
 
 /*
@@ -80,7 +101,7 @@ static int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
                u16 value)
 {
        struct fsl_pq_mdio_priv *priv = bus->priv;
-       struct fsl_pq_mdio __iomem *regs = priv->regs;
+       struct fsl_pq_mii __iomem *regs = priv->regs;
        u32 status;
 
        /* Set the PHY address and the register address we want to write */
@@ -109,7 +130,7 @@ static int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
 static int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
 {
        struct fsl_pq_mdio_priv *priv = bus->priv;
-       struct fsl_pq_mdio __iomem *regs = priv->regs;
+       struct fsl_pq_mii __iomem *regs = priv->regs;
        u32 status;
        u16 value;
 
@@ -130,6 +151,7 @@ static int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
        /* Grab the value of the register from miimstat */
        value = in_be32(&regs->miimstat);
 
+       dev_dbg(&bus->dev, "read %04x from address %x/%x\n", value, mii_id, regnum);
        return value;
 }
 
@@ -137,7 +159,7 @@ static int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
 static int fsl_pq_mdio_reset(struct mii_bus *bus)
 {
        struct fsl_pq_mdio_priv *priv = bus->priv;
-       struct fsl_pq_mdio __iomem *regs = priv->regs;
+       struct fsl_pq_mii __iomem *regs = priv->regs;
        u32 status;
 
        mutex_lock(&bus->mdio_lock);
@@ -162,84 +184,181 @@ static int fsl_pq_mdio_reset(struct mii_bus *bus)
        return 0;
 }
 
-static u32 __iomem *get_gfar_tbipa(struct fsl_pq_mdio __iomem *regs, struct device_node *np)
-{
 #if defined(CONFIG_GIANFAR) || defined(CONFIG_GIANFAR_MODULE)
-       struct gfar __iomem *enet_regs;
+/*
+ * This is mildly evil, but so is our hardware for doing this.
+ * Also, we have to cast back to struct gfar because of
+ * definition weirdness done in gianfar.h.
+ */
+static uint32_t __iomem *get_gfar_tbipa(void __iomem *p)
+{
+       struct gfar __iomem *enet_regs = p;
 
-       /*
-        * This is mildly evil, but so is our hardware for doing this.
-        * Also, we have to cast back to struct gfar because of
-        * definition weirdness done in gianfar.h.
-        */
-       if (of_device_is_compatible(np, "fsl,gianfar-mdio") ||
-               of_device_is_compatible(np, "fsl,gianfar-tbi") ||
-               of_device_is_compatible(np, "gianfar")) {
-               enet_regs = (struct gfar __iomem *)regs;
-               return &enet_regs->tbipa;
-       } else if (of_device_is_compatible(np, "fsl,etsec2-mdio") ||
-                       of_device_is_compatible(np, "fsl,etsec2-tbi")) {
-               return of_iomap(np, 1);
-       }
-#endif
-       return NULL;
+       return &enet_regs->tbipa;
 }
 
-
-static int get_ucc_id_for_range(u64 start, u64 end, u32 *ucc_id)
+/*
+ * Return the TBIPAR address for an eTSEC2 node
+ */
+static uint32_t __iomem *get_etsec_tbipa(void __iomem *p)
 {
+       return p;
+}
+#endif
+
 #if defined(CONFIG_UCC_GETH) || defined(CONFIG_UCC_GETH_MODULE)
+/*
+ * Return the TBIPAR address for a QE MDIO node
+ */
+static uint32_t __iomem *get_ucc_tbipa(void __iomem *p)
+{
+       struct fsl_pq_mdio __iomem *mdio = p;
+
+       return &mdio->utbipar;
+}
+
+/*
+ * Find the UCC node that controls the given MDIO node
+ *
+ * For some reason, the QE MDIO nodes are not children of the UCC devices
+ * that control them.  Therefore, we need to scan all UCC nodes looking for
+ * the one that encompases the given MDIO node.  We do this by comparing
+ * physical addresses.  The 'start' and 'end' addresses of the MDIO node are
+ * passed, and the correct UCC node will cover the entire address range.
+ *
+ * This assumes that there is only one QE MDIO node in the entire device tree.
+ */
+static void ucc_configure(phys_addr_t start, phys_addr_t end)
+{
+       static bool found_mii_master;
        struct device_node *np = NULL;
-       int err = 0;
 
-       for_each_compatible_node(np, NULL, "ucc_geth") {
-               struct resource tempres;
+       if (found_mii_master)
+               return;
 
-               err = of_address_to_resource(np, 0, &tempres);
-               if (err)
+       for_each_compatible_node(np, NULL, "ucc_geth") {
+               struct resource res;
+               const uint32_t *iprop;
+               uint32_t id;
+               int ret;
+
+               ret = of_address_to_resource(np, 0, &res);
+               if (ret < 0) {
+                       pr_debug("fsl-pq-mdio: no address range in node %s\n",
+                                np->full_name);
                        continue;
+               }
 
                /* if our mdio regs fall within this UCC regs range */
-               if ((start >= tempres.start) && (end <= tempres.end)) {
-                       /* Find the id of the UCC */
-                       const u32 *id;
-
-                       id = of_get_property(np, "cell-index", NULL);
-                       if (!id) {
-                               id = of_get_property(np, "device-id", NULL);
-                               if (!id)
-                                       continue;
+               if ((start < res.start) || (end > res.end))
+                       continue;
+
+               iprop = of_get_property(np, "cell-index", NULL);
+               if (!iprop) {
+                       iprop = of_get_property(np, "device-id", NULL);
+                       if (!iprop) {
+                               pr_debug("fsl-pq-mdio: no UCC ID in node %s\n",
+                                        np->full_name);
+                               continue;
                        }
+               }
 
-                       *ucc_id = *id;
+               id = be32_to_cpup(iprop);
 
-                       return 0;
+               /*
+                * cell-index and device-id for QE nodes are
+                * numbered from 1, not 0.
+                */
+               if (ucc_set_qe_mux_mii_mng(id - 1) < 0) {
+                       pr_debug("fsl-pq-mdio: invalid UCC ID in node %s\n",
+                                np->full_name);
+                       continue;
                }
+
+               pr_debug("fsl-pq-mdio: setting node UCC%u to MII master\n", id);
+               found_mii_master = true;
        }
+}
 
-       if (err)
-               return err;
-       else
-               return -EINVAL;
-#else
-       return -ENODEV;
 #endif
-}
+
+static struct of_device_id fsl_pq_mdio_match[] = {
+#if defined(CONFIG_GIANFAR) || defined(CONFIG_GIANFAR_MODULE)
+       {
+               .compatible = "fsl,gianfar-tbi",
+               .data = &(struct fsl_pq_mdio_data) {
+                       .mii_offset = 0,
+                       .get_tbipa = get_gfar_tbipa,
+               },
+       },
+       {
+               .compatible = "fsl,gianfar-mdio",
+               .data = &(struct fsl_pq_mdio_data) {
+                       .mii_offset = 0,
+                       .get_tbipa = get_gfar_tbipa,
+               },
+       },
+       {
+               .type = "mdio",
+               .compatible = "gianfar",
+               .data = &(struct fsl_pq_mdio_data) {
+                       .mii_offset = offsetof(struct fsl_pq_mdio, mii),
+                       .get_tbipa = get_gfar_tbipa,
+               },
+       },
+       {
+               .compatible = "fsl,etsec2-tbi",
+               .data = &(struct fsl_pq_mdio_data) {
+                       .mii_offset = offsetof(struct fsl_pq_mdio, mii),
+                       .get_tbipa = get_etsec_tbipa,
+               },
+       },
+       {
+               .compatible = "fsl,etsec2-mdio",
+               .data = &(struct fsl_pq_mdio_data) {
+                       .mii_offset = offsetof(struct fsl_pq_mdio, mii),
+                       .get_tbipa = get_etsec_tbipa,
+               },
+       },
+#endif
+#if defined(CONFIG_UCC_GETH) || defined(CONFIG_UCC_GETH_MODULE)
+       {
+               .compatible = "fsl,ucc-mdio",
+               .data = &(struct fsl_pq_mdio_data) {
+                       .mii_offset = 0,
+                       .get_tbipa = get_ucc_tbipa,
+                       .ucc_configure = ucc_configure,
+               },
+       },
+       {
+               /* Legacy UCC MDIO node */
+               .type = "mdio",
+               .compatible = "ucc_geth_phy",
+               .data = &(struct fsl_pq_mdio_data) {
+                       .mii_offset = 0,
+                       .get_tbipa = get_ucc_tbipa,
+                       .ucc_configure = ucc_configure,
+               },
+       },
+#endif
+       {},
+};
+MODULE_DEVICE_TABLE(of, fsl_pq_mdio_match);
 
 static int fsl_pq_mdio_probe(struct platform_device *pdev)
 {
+       const struct of_device_id *id =
+               of_match_device(fsl_pq_mdio_match, &pdev->dev);
+       const struct fsl_pq_mdio_data *data = id->data;
        struct device_node *np = pdev->dev.of_node;
+       struct resource res;
        struct device_node *tbi;
        struct fsl_pq_mdio_priv *priv;
-       struct fsl_pq_mdio __iomem *regs = NULL;
-       void __iomem *map;
-       u32 __iomem *tbipa;
        struct mii_bus *new_bus;
-       int tbiaddr = -1;
-       const u32 *addrp;
-       u64 addr = 0, size = 0;
        int err;
 
+       dev_dbg(&pdev->dev, "found %s compatible node\n", id->compatible);
+
        priv = kzalloc(sizeof(*priv), GFP_KERNEL);
        if (!priv)
                return -ENOMEM;
@@ -256,39 +375,35 @@ static int fsl_pq_mdio_probe(struct platform_device *pdev)
        new_bus->reset = &fsl_pq_mdio_reset;
        new_bus->priv = priv;
 
-       addrp = of_get_address(np, 0, &size, NULL);
-       if (!addrp) {
-               err = -EINVAL;
-               goto err_free_bus;
-       }
-
-       /* Set the PHY base address */
-       addr = of_translate_address(np, addrp);
-       if (addr == OF_BAD_ADDR) {
-               err = -EINVAL;
+       err = of_address_to_resource(np, 0, &res);
+       if (err < 0) {
+               dev_err(&pdev->dev, "could not obtain address information\n");
                goto err_free_bus;
        }
 
        snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s@%llx", np->name,
-               (unsigned long long)addr);
+               (unsigned long long)res.start);
 
-       map = ioremap(addr, size);
-       if (!map) {
+       priv->map = of_iomap(np, 0);
+       if (!priv->map) {
                err = -ENOMEM;
                goto err_free_bus;
        }
-       priv->map = map;
 
-       if (of_device_is_compatible(np, "fsl,gianfar-mdio") ||
-                       of_device_is_compatible(np, "fsl,gianfar-tbi") ||
-                       of_device_is_compatible(np, "fsl,ucc-mdio") ||
-                       of_device_is_compatible(np, "ucc_geth_phy"))
-               map -= offsetof(struct fsl_pq_mdio, miimcfg);
-       regs = map;
-       priv->regs = regs;
+       /*
+        * Some device tree nodes represent only the MII registers, and
+        * others represent the MAC and MII registers.  The 'mii_offset' field
+        * contains the offset of the MII registers inside the mapped register
+        * space.
+        */
+       if (data->mii_offset > resource_size(&res)) {
+               dev_err(&pdev->dev, "invalid register map\n");
+               err = -EINVAL;
+               goto err_unmap_regs;
+       }
+       priv->regs = priv->map + data->mii_offset;
 
        new_bus->irq = kcalloc(PHY_MAX_ADDR, sizeof(int), GFP_KERNEL);
-
        if (NULL == new_bus->irq) {
                err = -ENOMEM;
                goto err_unmap_regs;
@@ -297,54 +412,36 @@ static int fsl_pq_mdio_probe(struct platform_device *pdev)
        new_bus->parent = &pdev->dev;
        dev_set_drvdata(&pdev->dev, new_bus);
 
-       if (of_device_is_compatible(np, "fsl,gianfar-mdio") ||
-                       of_device_is_compatible(np, "fsl,gianfar-tbi") ||
-                       of_device_is_compatible(np, "fsl,etsec2-mdio") ||
-                       of_device_is_compatible(np, "fsl,etsec2-tbi") ||
-                       of_device_is_compatible(np, "gianfar")) {
-               tbipa = get_gfar_tbipa(regs, np);
-               if (!tbipa) {
-                       err = -EINVAL;
-                       goto err_free_irqs;
-               }
-       } else if (of_device_is_compatible(np, "fsl,ucc-mdio") ||
-                       of_device_is_compatible(np, "ucc_geth_phy")) {
-               u32 id;
-               static u32 mii_mng_master;
-
-               tbipa = &regs->utbipar;
-
-               if ((err = get_ucc_id_for_range(addr, addr + size, &id)))
-                       goto err_free_irqs;
-
-               if (!mii_mng_master) {
-                       mii_mng_master = id;
-                       ucc_set_qe_mux_mii_mng(id - 1);
+       if (data->get_tbipa) {
+               for_each_child_of_node(np, tbi) {
+                       if (strcmp(tbi->type, "tbi-phy") == 0) {
+                               dev_dbg(&pdev->dev, "found TBI PHY node %s\n",
+                                       strrchr(tbi->full_name, '/') + 1);
+                               break;
+                       }
                }
-       } else {
-               err = -ENODEV;
-               goto err_free_irqs;
-       }
 
-       for_each_child_of_node(np, tbi) {
-               if (!strncmp(tbi->type, "tbi-phy", 8))
-                       break;
-       }
+               if (tbi) {
+                       const u32 *prop = of_get_property(tbi, "reg", NULL);
+                       uint32_t __iomem *tbipa;
 
-       if (tbi) {
-               const u32 *prop = of_get_property(tbi, "reg", NULL);
+                       if (!prop) {
+                               dev_err(&pdev->dev,
+                                       "missing 'reg' property in node %s\n",
+                                       tbi->full_name);
+                               err = -EBUSY;
+                               goto error;
+                       }
 
-               if (prop)
-                       tbiaddr = *prop;
+                       tbipa = data->get_tbipa(priv->map);
 
-               if (tbiaddr == -1) {
-                       err = -EBUSY;
-                       goto err_free_irqs;
-               } else {
-                       out_be32(tbipa, tbiaddr);
+                       out_be32(tbipa, be32_to_cpup(prop));
                }
        }
 
+       if (data->ucc_configure)
+               data->ucc_configure(res.start, res.end);
+
        err = of_mdiobus_register(new_bus, np);
        if (err) {
                dev_err(&pdev->dev, "cannot register %s as MDIO bus\n",
@@ -384,34 +481,6 @@ static int fsl_pq_mdio_remove(struct platform_device *pdev)
        return 0;
 }
 
-static struct of_device_id fsl_pq_mdio_match[] = {
-       {
-               .type = "mdio",
-               .compatible = "ucc_geth_phy",
-       },
-       {
-               .type = "mdio",
-               .compatible = "gianfar",
-       },
-       {
-               .compatible = "fsl,ucc-mdio",
-       },
-       {
-               .compatible = "fsl,gianfar-tbi",
-       },
-       {
-               .compatible = "fsl,gianfar-mdio",
-       },
-       {
-               .compatible = "fsl,etsec2-tbi",
-       },
-       {
-               .compatible = "fsl,etsec2-mdio",
-       },
-       {},
-};
-MODULE_DEVICE_TABLE(of, fsl_pq_mdio_match);
-
 static struct platform_driver fsl_pq_mdio_driver = {
        .driver = {
                .name = "fsl-pq_mdio",