net: phy: mdio-bcm-unimac: Allow configuring MDIO clock divider
authorFlorian Fainelli <f.fainelli@gmail.com>
Fri, 21 Sep 2018 00:05:40 +0000 (17:05 -0700)
committerDavid S. Miller <davem@davemloft.net>
Sat, 22 Sep 2018 02:08:24 +0000 (19:08 -0700)
Allow the configuration of the MDIO clock divider when the Device Tree
contains 'clock-frequency' property (similar to I2C and SPI buses).
Because the hardware may have lost its state during suspend/resume,
re-apply the MDIO clock divider upon resumption.

Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Documentation/devicetree/bindings/net/brcm,unimac-mdio.txt
drivers/net/phy/mdio-bcm-unimac.c

index 4648948f7c3b8f26391292b06aa01743100e8796..e15589f47787633b8d5abeb44ccfa78e01994583 100644 (file)
@@ -19,6 +19,9 @@ Optional properties:
 - interrupt-names: must be "mdio_done_error" when there is a share interrupt fed
   to this hardware block, or must be "mdio_done" for the first interrupt and
   "mdio_error" for the second when there are separate interrupts
+- clocks: A reference to the clock supplying the MDIO bus controller
+- clock-frequency: the MDIO bus clock that must be output by the MDIO bus
+  hardware, if absent, the default hardware values are used
 
 Child nodes of this MDIO bus controller node are standard Ethernet PHY device
 nodes as described in Documentation/devicetree/bindings/net/phy.txt
index 8d370667fa1b3e5ada10b299ee35e35294bca798..80b9583eaa952a7fc16a0e00d57e5a423cc04163 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/module.h>
 #include <linux/io.h>
 #include <linux/delay.h>
+#include <linux/clk.h>
 
 #include <linux/of.h>
 #include <linux/of_platform.h>
@@ -45,6 +46,8 @@ struct unimac_mdio_priv {
        void __iomem            *base;
        int (*wait_func)        (void *wait_func_data);
        void                    *wait_func_data;
+       struct clk              *clk;
+       u32                     clk_freq;
 };
 
 static inline u32 unimac_mdio_readl(struct unimac_mdio_priv *priv, u32 offset)
@@ -189,6 +192,35 @@ static int unimac_mdio_reset(struct mii_bus *bus)
        return 0;
 }
 
+static void unimac_mdio_clk_set(struct unimac_mdio_priv *priv)
+{
+       unsigned long rate;
+       u32 reg, div;
+
+       /* Keep the hardware default values */
+       if (!priv->clk_freq)
+               return;
+
+       if (!priv->clk)
+               rate = 250000000;
+       else
+               rate = clk_get_rate(priv->clk);
+
+       div = (rate / (2 * priv->clk_freq)) - 1;
+       if (div & ~MDIO_CLK_DIV_MASK) {
+               pr_warn("Incorrect MDIO clock frequency, ignoring\n");
+               return;
+       }
+
+       /* The MDIO clock is the reference clock (typicaly 250Mhz) divided by
+        * 2 x (MDIO_CLK_DIV + 1)
+        */
+       reg = unimac_mdio_readl(priv, MDIO_CFG);
+       reg &= ~(MDIO_CLK_DIV_MASK << MDIO_CLK_DIV_SHIFT);
+       reg |= div << MDIO_CLK_DIV_SHIFT;
+       unimac_mdio_writel(priv, reg, MDIO_CFG);
+}
+
 static int unimac_mdio_probe(struct platform_device *pdev)
 {
        struct unimac_mdio_pdata *pdata = pdev->dev.platform_data;
@@ -217,9 +249,26 @@ static int unimac_mdio_probe(struct platform_device *pdev)
                return -ENOMEM;
        }
 
+       priv->clk = devm_clk_get(&pdev->dev, NULL);
+       if (PTR_ERR(priv->clk) == -EPROBE_DEFER)
+               return PTR_ERR(priv->clk);
+       else
+               priv->clk = NULL;
+
+       ret = clk_prepare_enable(priv->clk);
+       if (ret)
+               return ret;
+
+       if (of_property_read_u32(np, "clock-frequency", &priv->clk_freq))
+               priv->clk_freq = 0;
+
+       unimac_mdio_clk_set(priv);
+
        priv->mii_bus = mdiobus_alloc();
-       if (!priv->mii_bus)
-               return -ENOMEM;
+       if (!priv->mii_bus) {
+               ret = -ENOMEM;
+               goto out_clk_disable;
+       }
 
        bus = priv->mii_bus;
        bus->priv = priv;
@@ -253,6 +302,8 @@ static int unimac_mdio_probe(struct platform_device *pdev)
 
 out_mdio_free:
        mdiobus_free(bus);
+out_clk_disable:
+       clk_disable_unprepare(priv->clk);
        return ret;
 }
 
@@ -262,10 +313,37 @@ static int unimac_mdio_remove(struct platform_device *pdev)
 
        mdiobus_unregister(priv->mii_bus);
        mdiobus_free(priv->mii_bus);
+       clk_disable_unprepare(priv->clk);
+
+       return 0;
+}
+
+static int unimac_mdio_suspend(struct device *d)
+{
+       struct unimac_mdio_priv *priv = dev_get_drvdata(d);
+
+       clk_disable_unprepare(priv->clk);
+
+       return 0;
+}
+
+static int unimac_mdio_resume(struct device *d)
+{
+       struct unimac_mdio_priv *priv = dev_get_drvdata(d);
+       int ret;
+
+       ret = clk_prepare_enable(priv->clk);
+       if (ret)
+               return ret;
+
+       unimac_mdio_clk_set(priv);
 
        return 0;
 }
 
+static SIMPLE_DEV_PM_OPS(unimac_mdio_pm_ops,
+                        unimac_mdio_suspend, unimac_mdio_resume);
+
 static const struct of_device_id unimac_mdio_ids[] = {
        { .compatible = "brcm,genet-mdio-v5", },
        { .compatible = "brcm,genet-mdio-v4", },
@@ -281,6 +359,7 @@ static struct platform_driver unimac_mdio_driver = {
        .driver = {
                .name = UNIMAC_MDIO_DRV_NAME,
                .of_match_table = unimac_mdio_ids,
+               .pm = &unimac_mdio_pm_ops,
        },
        .probe  = unimac_mdio_probe,
        .remove = unimac_mdio_remove,