123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- 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) */
- @@ -634,6 +640,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;
- @@ -648,35 +807,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)) {
- @@ -746,81 +895,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);
-
- @@ -849,7 +938,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;
-
- @@ -889,6 +978,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)
- @@ -932,29 +1023,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.
- @@ -973,9 +1041,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[] = {
|