507 lines
15 KiB
Diff
507 lines
15 KiB
Diff
From 634db83b82658f4641d8026e340c6027cf74a6bb Mon Sep 17 00:00:00 2001
|
|
From: Corentin Labbe <clabbe.montjoie@gmail.com>
|
|
Date: Tue, 24 Oct 2017 19:57:13 +0200
|
|
Subject: [PATCH] net: stmmac: dwmac-sun8i: Handle integrated/external MDIOs
|
|
|
|
The Allwinner H3 SoC have two distinct MDIO bus, only one could be
|
|
active at the same time.
|
|
The selection of the active MDIO bus are done via some bits in the EMAC
|
|
register of the system controller.
|
|
|
|
This patch implement this MDIO switch via a custom MDIO-mux.
|
|
|
|
Signed-off-by: Corentin Labbe <clabbe.montjoie@gmail.com>
|
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
|
Signed-off-by: David S. Miller <davem@davemloft.net>
|
|
---
|
|
drivers/net/ethernet/stmicro/stmmac/Kconfig | 1 +
|
|
drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c | 353 ++++++++++++++--------
|
|
2 files changed, 224 insertions(+), 130 deletions(-)
|
|
|
|
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
|
|
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
|
|
@@ -159,6 +159,7 @@ config DWMAC_SUN8I
|
|
tristate "Allwinner sun8i GMAC support"
|
|
default ARCH_SUNXI
|
|
depends on OF && (ARCH_SUNXI || COMPILE_TEST)
|
|
+ select MDIO_BUS_MUX
|
|
---help---
|
|
Support for Allwinner H3 A83T A64 EMAC ethernet controllers.
|
|
|
|
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
|
|
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
|
|
@@ -17,6 +17,7 @@
|
|
#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/iopoll.h>
|
|
+#include <linux/mdio-mux.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
@@ -41,14 +42,14 @@
|
|
* This value is used for disabling properly EMAC
|
|
* and used as a good starting value in case of the
|
|
* boot process(uboot) leave some stuff.
|
|
- * @internal_phy: Does the MAC embed an internal PHY
|
|
+ * @soc_has_internal_phy: Does the MAC embed an internal PHY
|
|
* @support_mii: Does the MAC handle MII
|
|
* @support_rmii: Does the MAC handle RMII
|
|
* @support_rgmii: Does the MAC handle RGMII
|
|
*/
|
|
struct emac_variant {
|
|
u32 default_syscon_value;
|
|
- int internal_phy;
|
|
+ bool soc_has_internal_phy;
|
|
bool support_mii;
|
|
bool support_rmii;
|
|
bool support_rgmii;
|
|
@@ -61,7 +62,8 @@ struct emac_variant {
|
|
* @rst_ephy: reference to the optional EPHY reset for the internal PHY
|
|
* @variant: reference to the current board variant
|
|
* @regmap: regmap for using the syscon
|
|
- * @use_internal_phy: Does the current PHY choice imply using the internal PHY
|
|
+ * @internal_phy_powered: Does the internal PHY is enabled
|
|
+ * @mux_handle: Internal pointer used by mdio-mux lib
|
|
*/
|
|
struct sunxi_priv_data {
|
|
struct clk *tx_clk;
|
|
@@ -70,12 +72,13 @@ struct sunxi_priv_data {
|
|
struct reset_control *rst_ephy;
|
|
const struct emac_variant *variant;
|
|
struct regmap *regmap;
|
|
- bool use_internal_phy;
|
|
+ bool internal_phy_powered;
|
|
+ void *mux_handle;
|
|
};
|
|
|
|
static const struct emac_variant emac_variant_h3 = {
|
|
.default_syscon_value = 0x58000,
|
|
- .internal_phy = PHY_INTERFACE_MODE_MII,
|
|
+ .soc_has_internal_phy = true,
|
|
.support_mii = true,
|
|
.support_rmii = true,
|
|
.support_rgmii = true
|
|
@@ -83,20 +86,20 @@ static const struct emac_variant emac_va
|
|
|
|
static const struct emac_variant emac_variant_v3s = {
|
|
.default_syscon_value = 0x38000,
|
|
- .internal_phy = PHY_INTERFACE_MODE_MII,
|
|
+ .soc_has_internal_phy = true,
|
|
.support_mii = true
|
|
};
|
|
|
|
static const struct emac_variant emac_variant_a83t = {
|
|
.default_syscon_value = 0,
|
|
- .internal_phy = 0,
|
|
+ .soc_has_internal_phy = false,
|
|
.support_mii = true,
|
|
.support_rgmii = true
|
|
};
|
|
|
|
static const struct emac_variant emac_variant_a64 = {
|
|
.default_syscon_value = 0,
|
|
- .internal_phy = 0,
|
|
+ .soc_has_internal_phy = false,
|
|
.support_mii = true,
|
|
.support_rmii = true,
|
|
.support_rgmii = true
|
|
@@ -195,6 +198,9 @@ static const struct emac_variant emac_va
|
|
#define H3_EPHY_LED_POL BIT(17) /* 1: active low, 0: active high */
|
|
#define H3_EPHY_SHUTDOWN BIT(16) /* 1: shutdown, 0: power up */
|
|
#define H3_EPHY_SELECT BIT(15) /* 1: internal PHY, 0: external PHY */
|
|
+#define H3_EPHY_MUX_MASK (H3_EPHY_SHUTDOWN | H3_EPHY_SELECT)
|
|
+#define DWMAC_SUN8I_MDIO_MUX_INTERNAL_ID 1
|
|
+#define DWMAC_SUN8I_MDIO_MUX_EXTERNAL_ID 2
|
|
|
|
/* H3/A64 specific bits */
|
|
#define SYSCON_RMII_EN BIT(13) /* 1: enable RMII (overrides EPIT) */
|
|
@@ -635,6 +641,159 @@ static int sun8i_dwmac_reset(struct stmm
|
|
return 0;
|
|
}
|
|
|
|
+/* Search in mdio-mux node for internal PHY node and get its clk/reset */
|
|
+static int get_ephy_nodes(struct stmmac_priv *priv)
|
|
+{
|
|
+ struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
|
|
+ struct device_node *mdio_mux, *iphynode;
|
|
+ struct device_node *mdio_internal;
|
|
+ int ret;
|
|
+
|
|
+ mdio_mux = of_get_child_by_name(priv->device->of_node, "mdio-mux");
|
|
+ if (!mdio_mux) {
|
|
+ dev_err(priv->device, "Cannot get mdio-mux node\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ mdio_internal = of_find_compatible_node(mdio_mux, NULL,
|
|
+ "allwinner,sun8i-h3-mdio-internal");
|
|
+ if (!mdio_internal) {
|
|
+ dev_err(priv->device, "Cannot get internal_mdio node\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ /* Seek for internal PHY */
|
|
+ for_each_child_of_node(mdio_internal, iphynode) {
|
|
+ gmac->ephy_clk = of_clk_get(iphynode, 0);
|
|
+ if (IS_ERR(gmac->ephy_clk))
|
|
+ continue;
|
|
+ gmac->rst_ephy = of_reset_control_get_exclusive(iphynode, NULL);
|
|
+ if (IS_ERR(gmac->rst_ephy)) {
|
|
+ ret = PTR_ERR(gmac->rst_ephy);
|
|
+ if (ret == -EPROBE_DEFER)
|
|
+ return ret;
|
|
+ continue;
|
|
+ }
|
|
+ dev_info(priv->device, "Found internal PHY node\n");
|
|
+ return 0;
|
|
+ }
|
|
+ return -ENODEV;
|
|
+}
|
|
+
|
|
+static int sun8i_dwmac_power_internal_phy(struct stmmac_priv *priv)
|
|
+{
|
|
+ struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
|
|
+ int ret;
|
|
+
|
|
+ if (gmac->internal_phy_powered) {
|
|
+ dev_warn(priv->device, "Internal PHY already powered\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ dev_info(priv->device, "Powering internal PHY\n");
|
|
+ ret = clk_prepare_enable(gmac->ephy_clk);
|
|
+ if (ret) {
|
|
+ dev_err(priv->device, "Cannot enable internal PHY\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Make sure the EPHY is properly reseted, as U-Boot may leave
|
|
+ * it at deasserted state, and thus it may fail to reset EMAC.
|
|
+ */
|
|
+ reset_control_assert(gmac->rst_ephy);
|
|
+
|
|
+ ret = reset_control_deassert(gmac->rst_ephy);
|
|
+ if (ret) {
|
|
+ dev_err(priv->device, "Cannot deassert internal phy\n");
|
|
+ clk_disable_unprepare(gmac->ephy_clk);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ gmac->internal_phy_powered = true;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sun8i_dwmac_unpower_internal_phy(struct sunxi_priv_data *gmac)
|
|
+{
|
|
+ if (!gmac->internal_phy_powered)
|
|
+ return 0;
|
|
+
|
|
+ clk_disable_unprepare(gmac->ephy_clk);
|
|
+ reset_control_assert(gmac->rst_ephy);
|
|
+ gmac->internal_phy_powered = false;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* MDIO multiplexing switch function
|
|
+ * This function is called by the mdio-mux layer when it thinks the mdio bus
|
|
+ * multiplexer needs to switch.
|
|
+ * 'current_child' is the current value of the mux register
|
|
+ * 'desired_child' is the value of the 'reg' property of the target child MDIO
|
|
+ * node.
|
|
+ * The first time this function is called, current_child == -1.
|
|
+ * If current_child == desired_child, then the mux is already set to the
|
|
+ * correct bus.
|
|
+ */
|
|
+static int mdio_mux_syscon_switch_fn(int current_child, int desired_child,
|
|
+ void *data)
|
|
+{
|
|
+ struct stmmac_priv *priv = data;
|
|
+ struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
|
|
+ u32 reg, val;
|
|
+ int ret = 0;
|
|
+ bool need_power_ephy = false;
|
|
+
|
|
+ if (current_child ^ desired_child) {
|
|
+ regmap_read(gmac->regmap, SYSCON_EMAC_REG, ®);
|
|
+ switch (desired_child) {
|
|
+ case DWMAC_SUN8I_MDIO_MUX_INTERNAL_ID:
|
|
+ dev_info(priv->device, "Switch mux to internal PHY");
|
|
+ val = (reg & ~H3_EPHY_MUX_MASK) | H3_EPHY_SELECT;
|
|
+
|
|
+ need_power_ephy = true;
|
|
+ break;
|
|
+ case DWMAC_SUN8I_MDIO_MUX_EXTERNAL_ID:
|
|
+ dev_info(priv->device, "Switch mux to external PHY");
|
|
+ val = (reg & ~H3_EPHY_MUX_MASK) | H3_EPHY_SHUTDOWN;
|
|
+ need_power_ephy = false;
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(priv->device, "Invalid child ID %x\n",
|
|
+ desired_child);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ regmap_write(gmac->regmap, SYSCON_EMAC_REG, val);
|
|
+ if (need_power_ephy) {
|
|
+ ret = sun8i_dwmac_power_internal_phy(priv);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ } else {
|
|
+ sun8i_dwmac_unpower_internal_phy(gmac);
|
|
+ }
|
|
+ /* After changing syscon value, the MAC need reset or it will
|
|
+ * use the last value (and so the last PHY set).
|
|
+ */
|
|
+ ret = sun8i_dwmac_reset(priv);
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sun8i_dwmac_register_mdio_mux(struct stmmac_priv *priv)
|
|
+{
|
|
+ int ret;
|
|
+ struct device_node *mdio_mux;
|
|
+ struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
|
|
+
|
|
+ mdio_mux = of_get_child_by_name(priv->device->of_node, "mdio-mux");
|
|
+ if (!mdio_mux)
|
|
+ return -ENODEV;
|
|
+
|
|
+ ret = mdio_mux_init(priv->device, mdio_mux, mdio_mux_syscon_switch_fn,
|
|
+ &gmac->mux_handle, priv, priv->mii);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
static int sun8i_dwmac_set_syscon(struct stmmac_priv *priv)
|
|
{
|
|
struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
|
|
@@ -649,35 +808,25 @@ static int sun8i_dwmac_set_syscon(struct
|
|
"Current syscon value is not the default %x (expect %x)\n",
|
|
val, reg);
|
|
|
|
- if (gmac->variant->internal_phy) {
|
|
- if (!gmac->use_internal_phy) {
|
|
- /* switch to external PHY interface */
|
|
- reg &= ~H3_EPHY_SELECT;
|
|
- } else {
|
|
- reg |= H3_EPHY_SELECT;
|
|
- reg &= ~H3_EPHY_SHUTDOWN;
|
|
- dev_dbg(priv->device, "Select internal_phy %x\n", reg);
|
|
-
|
|
- if (of_property_read_bool(priv->plat->phy_node,
|
|
- "allwinner,leds-active-low"))
|
|
- reg |= H3_EPHY_LED_POL;
|
|
- else
|
|
- reg &= ~H3_EPHY_LED_POL;
|
|
-
|
|
- /* Force EPHY xtal frequency to 24MHz. */
|
|
- reg |= H3_EPHY_CLK_SEL;
|
|
-
|
|
- ret = of_mdio_parse_addr(priv->device,
|
|
- priv->plat->phy_node);
|
|
- if (ret < 0) {
|
|
- dev_err(priv->device, "Could not parse MDIO addr\n");
|
|
- return ret;
|
|
- }
|
|
- /* of_mdio_parse_addr returns a valid (0 ~ 31) PHY
|
|
- * address. No need to mask it again.
|
|
- */
|
|
- reg |= ret << H3_EPHY_ADDR_SHIFT;
|
|
+ if (gmac->variant->soc_has_internal_phy) {
|
|
+ if (of_property_read_bool(priv->plat->phy_node,
|
|
+ "allwinner,leds-active-low"))
|
|
+ reg |= H3_EPHY_LED_POL;
|
|
+ else
|
|
+ reg &= ~H3_EPHY_LED_POL;
|
|
+
|
|
+ /* Force EPHY xtal frequency to 24MHz. */
|
|
+ reg |= H3_EPHY_CLK_SEL;
|
|
+
|
|
+ ret = of_mdio_parse_addr(priv->device, priv->plat->phy_node);
|
|
+ if (ret < 0) {
|
|
+ dev_err(priv->device, "Could not parse MDIO addr\n");
|
|
+ return ret;
|
|
}
|
|
+ /* of_mdio_parse_addr returns a valid (0 ~ 31) PHY
|
|
+ * address. No need to mask it again.
|
|
+ */
|
|
+ reg |= 1 << H3_EPHY_ADDR_SHIFT;
|
|
}
|
|
|
|
if (!of_property_read_u32(node, "allwinner,tx-delay-ps", &val)) {
|
|
@@ -747,81 +896,21 @@ static void sun8i_dwmac_unset_syscon(str
|
|
regmap_write(gmac->regmap, SYSCON_EMAC_REG, reg);
|
|
}
|
|
|
|
-static int sun8i_dwmac_power_internal_phy(struct stmmac_priv *priv)
|
|
+static void sun8i_dwmac_exit(struct platform_device *pdev, void *priv)
|
|
{
|
|
- struct sunxi_priv_data *gmac = priv->plat->bsp_priv;
|
|
- int ret;
|
|
-
|
|
- if (!gmac->use_internal_phy)
|
|
- return 0;
|
|
+ struct sunxi_priv_data *gmac = priv;
|
|
|
|
- ret = clk_prepare_enable(gmac->ephy_clk);
|
|
- if (ret) {
|
|
- dev_err(priv->device, "Cannot enable ephy\n");
|
|
- return ret;
|
|
+ if (gmac->variant->soc_has_internal_phy) {
|
|
+ /* sun8i_dwmac_exit could be called with mdiomux uninit */
|
|
+ if (gmac->mux_handle)
|
|
+ mdio_mux_uninit(gmac->mux_handle);
|
|
+ if (gmac->internal_phy_powered)
|
|
+ sun8i_dwmac_unpower_internal_phy(gmac);
|
|
}
|
|
|
|
- /* Make sure the EPHY is properly reseted, as U-Boot may leave
|
|
- * it at deasserted state, and thus it may fail to reset EMAC.
|
|
- */
|
|
- reset_control_assert(gmac->rst_ephy);
|
|
-
|
|
- ret = reset_control_deassert(gmac->rst_ephy);
|
|
- if (ret) {
|
|
- dev_err(priv->device, "Cannot deassert ephy\n");
|
|
- clk_disable_unprepare(gmac->ephy_clk);
|
|
- return ret;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int sun8i_dwmac_unpower_internal_phy(struct sunxi_priv_data *gmac)
|
|
-{
|
|
- if (!gmac->use_internal_phy)
|
|
- return 0;
|
|
-
|
|
- clk_disable_unprepare(gmac->ephy_clk);
|
|
- reset_control_assert(gmac->rst_ephy);
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/* sun8i_power_phy() - Activate the PHY:
|
|
- * In case of error, no need to call sun8i_unpower_phy(),
|
|
- * it will be called anyway by sun8i_dwmac_exit()
|
|
- */
|
|
-static int sun8i_power_phy(struct stmmac_priv *priv)
|
|
-{
|
|
- int ret;
|
|
-
|
|
- ret = sun8i_dwmac_power_internal_phy(priv);
|
|
- if (ret)
|
|
- return ret;
|
|
-
|
|
- ret = sun8i_dwmac_set_syscon(priv);
|
|
- if (ret)
|
|
- return ret;
|
|
-
|
|
- /* After changing syscon value, the MAC need reset or it will use
|
|
- * the last value (and so the last PHY set.
|
|
- */
|
|
- ret = sun8i_dwmac_reset(priv);
|
|
- if (ret)
|
|
- return ret;
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static void sun8i_unpower_phy(struct sunxi_priv_data *gmac)
|
|
-{
|
|
sun8i_dwmac_unset_syscon(gmac);
|
|
- sun8i_dwmac_unpower_internal_phy(gmac);
|
|
-}
|
|
-
|
|
-static void sun8i_dwmac_exit(struct platform_device *pdev, void *priv)
|
|
-{
|
|
- struct sunxi_priv_data *gmac = priv;
|
|
|
|
- sun8i_unpower_phy(gmac);
|
|
+ reset_control_put(gmac->rst_ephy);
|
|
|
|
clk_disable_unprepare(gmac->tx_clk);
|
|
|
|
@@ -850,7 +939,7 @@ static struct mac_device_info *sun8i_dwm
|
|
if (!mac)
|
|
return NULL;
|
|
|
|
- ret = sun8i_power_phy(priv);
|
|
+ ret = sun8i_dwmac_set_syscon(priv);
|
|
if (ret)
|
|
return NULL;
|
|
|
|
@@ -892,6 +981,8 @@ static int sun8i_dwmac_probe(struct plat
|
|
struct sunxi_priv_data *gmac;
|
|
struct device *dev = &pdev->dev;
|
|
int ret;
|
|
+ struct stmmac_priv *priv;
|
|
+ struct net_device *ndev;
|
|
|
|
ret = stmmac_get_platform_resources(pdev, &stmmac_res);
|
|
if (ret)
|
|
@@ -935,29 +1026,6 @@ static int sun8i_dwmac_probe(struct plat
|
|
}
|
|
|
|
plat_dat->interface = of_get_phy_mode(dev->of_node);
|
|
- if (plat_dat->interface == gmac->variant->internal_phy) {
|
|
- dev_info(&pdev->dev, "Will use internal PHY\n");
|
|
- gmac->use_internal_phy = true;
|
|
- gmac->ephy_clk = of_clk_get(plat_dat->phy_node, 0);
|
|
- if (IS_ERR(gmac->ephy_clk)) {
|
|
- ret = PTR_ERR(gmac->ephy_clk);
|
|
- dev_err(&pdev->dev, "Cannot get EPHY clock: %d\n", ret);
|
|
- return -EINVAL;
|
|
- }
|
|
-
|
|
- gmac->rst_ephy = of_reset_control_get(plat_dat->phy_node, NULL);
|
|
- if (IS_ERR(gmac->rst_ephy)) {
|
|
- ret = PTR_ERR(gmac->rst_ephy);
|
|
- if (ret == -EPROBE_DEFER)
|
|
- return ret;
|
|
- dev_err(&pdev->dev, "No EPHY reset control found %d\n",
|
|
- ret);
|
|
- return -EINVAL;
|
|
- }
|
|
- } else {
|
|
- dev_info(&pdev->dev, "Will use external PHY\n");
|
|
- gmac->use_internal_phy = false;
|
|
- }
|
|
|
|
/* platform data specifying hardware features and callbacks.
|
|
* hardware features were copied from Allwinner drivers.
|
|
@@ -976,9 +1044,34 @@ static int sun8i_dwmac_probe(struct plat
|
|
|
|
ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
|
|
if (ret)
|
|
- sun8i_dwmac_exit(pdev, plat_dat->bsp_priv);
|
|
+ goto dwmac_exit;
|
|
+
|
|
+ ndev = dev_get_drvdata(&pdev->dev);
|
|
+ priv = netdev_priv(ndev);
|
|
+ /* The mux must be registered after parent MDIO
|
|
+ * so after stmmac_dvr_probe()
|
|
+ */
|
|
+ if (gmac->variant->soc_has_internal_phy) {
|
|
+ ret = get_ephy_nodes(priv);
|
|
+ if (ret)
|
|
+ goto dwmac_exit;
|
|
+ ret = sun8i_dwmac_register_mdio_mux(priv);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Failed to register mux\n");
|
|
+ goto dwmac_mux;
|
|
+ }
|
|
+ } else {
|
|
+ ret = sun8i_dwmac_reset(priv);
|
|
+ if (ret)
|
|
+ goto dwmac_exit;
|
|
+ }
|
|
|
|
return ret;
|
|
+dwmac_mux:
|
|
+ sun8i_dwmac_unset_syscon(gmac);
|
|
+dwmac_exit:
|
|
+ sun8i_dwmac_exit(pdev, plat_dat->bsp_priv);
|
|
+return ret;
|
|
}
|
|
|
|
static const struct of_device_id sun8i_dwmac_match[] = {
|