diff --git a/drivers/net/ethernet/altera/altera_tse.h b/drivers/net/ethernet/altera/altera_tse.h index db5eed06e92dfa..f4e3fddb639a38 100644 --- a/drivers/net/ethernet/altera/altera_tse.h +++ b/drivers/net/ethernet/altera/altera_tse.h @@ -476,7 +476,7 @@ struct altera_tse_private { struct phylink *phylink; struct phylink_config phylink_config; - struct phylink_pcs *pcs; + struct altera_tse_pcs *pcs; }; /* Function prototypes diff --git a/drivers/net/ethernet/altera/altera_tse_main.c b/drivers/net/ethernet/altera/altera_tse_main.c index 66e3af73ec41ac..109b7ed90c6e50 100644 --- a/drivers/net/ethernet/altera/altera_tse_main.c +++ b/drivers/net/ethernet/altera/altera_tse_main.c @@ -87,6 +87,36 @@ static inline u32 tse_tx_avail(struct altera_tse_private *priv) return priv->tx_cons + priv->tx_ring_size - priv->tx_prod - 1; } +static int altera_tse_pcs_read(void *priv, int regnum) +{ + struct altera_tse_private *tse = priv; + + if (tse->pcs_base) + return readw(tse->pcs_base + (regnum * 2)); + else + return readl(tse->mac_dev + tse_csroffs(mdio_phy0) + + (regnum * 4)); + return 0; +} + +static int altera_tse_pcs_write(void *priv, int regnum, u16 value) +{ + struct altera_tse_private *tse = priv; + + if (tse->pcs_base) + writew(value, tse->pcs_base + (regnum * 2)); + else + writel(value, tse->mac_dev + tse_csroffs(mdio_phy0) + + (regnum * 4)); + + return 0; +} + +static struct altera_tse_pcs_ops tse_ops = { + .read = altera_tse_pcs_read, + .write = altera_tse_pcs_write, +}; + /* MDIO specific functions */ static int altera_tse_mdio_read(struct mii_bus *bus, int mii_id, int regnum) @@ -1090,7 +1120,7 @@ static struct phylink_pcs *alt_tse_select_pcs(struct phylink_config *config, if (interface == PHY_INTERFACE_MODE_SGMII || interface == PHY_INTERFACE_MODE_1000BASEX) - return priv->pcs; + return altera_tse_pcs_to_phylink_pcs(priv->pcs); else return NULL; } @@ -1143,7 +1173,6 @@ static int altera_tse_probe(struct platform_device *pdev) struct resource *pcs_res; struct net_device *ndev; void __iomem *descmap; - int pcs_reg_width = 2; int ret = -ENODEV; ndev = alloc_etherdev(sizeof(struct altera_tse_private)); @@ -1262,10 +1291,6 @@ static int altera_tse_probe(struct platform_device *pdev) */ ret = request_and_map(pdev, "pcs", &pcs_res, &priv->pcs_base); - if (ret) { - priv->pcs_base = priv->mac_dev + tse_csroffs(mdio_phy0); - pcs_reg_width = 4; - } /* Rx IRQ */ priv->rx_irq = platform_get_irq_byname(pdev, "rx_irq"); @@ -1389,7 +1414,11 @@ static int altera_tse_probe(struct platform_device *pdev) (unsigned long) control_port->start, priv->rx_irq, priv->tx_irq); - priv->pcs = alt_tse_pcs_create(ndev, priv->pcs_base, pcs_reg_width); + priv->pcs = alt_tse_pcs_create(ndev, &tse_ops, priv); + if (!priv->pcs) { + ret = -ENODEV; + goto err_init_phy; + } priv->phylink_config.dev = &ndev->dev; priv->phylink_config.type = PHYLINK_NETDEV; @@ -1412,11 +1441,12 @@ static int altera_tse_probe(struct platform_device *pdev) if (IS_ERR(priv->phylink)) { dev_err(&pdev->dev, "failed to create phylink\n"); ret = PTR_ERR(priv->phylink); - goto err_init_phy; + goto err_pcs; } return 0; - +err_pcs: + alt_tse_pcs_destroy(priv->pcs); err_init_phy: unregister_netdev(ndev); err_register_netdev: @@ -1438,6 +1468,8 @@ static int altera_tse_remove(struct platform_device *pdev) altera_tse_mdio_destroy(ndev); unregister_netdev(ndev); phylink_destroy(priv->phylink); + alt_tse_pcs_destroy(priv->pcs); + free_netdev(ndev); return 0; diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig index 6e7e6c346a3ec5..768e8cefe17cce 100644 --- a/drivers/net/pcs/Kconfig +++ b/drivers/net/pcs/Kconfig @@ -28,8 +28,12 @@ config PCS_RZN1_MIIC config PCS_ALTERA_TSE tristate + select PCS_LYNX help This module provides helper functions for the Altera Triple Speed Ethernet SGMII PCS, that can be found on the Intel Socfpga family. + This PCS appears to be a Lynx PCS exposed over mmio instead of a + mdio device, so the core logic from Lynx PCS is re-used and wrapped + around a virtual mii bus and mdio device. endmenu diff --git a/drivers/net/pcs/pcs-altera-tse.c b/drivers/net/pcs/pcs-altera-tse.c index d616749761f411..3adf6b1c0823fe 100644 --- a/drivers/net/pcs/pcs-altera-tse.c +++ b/drivers/net/pcs/pcs-altera-tse.c @@ -9,151 +9,109 @@ #include #include #include +#include -/* SGMII PCS register addresses - */ -#define SGMII_PCS_LINK_TIMER_0 0x12 -#define SGMII_PCS_LINK_TIMER_1 0x13 -#define SGMII_PCS_IF_MODE 0x14 -#define PCS_IF_MODE_SGMII_ENA BIT(0) -#define PCS_IF_MODE_USE_SGMII_AN BIT(1) -#define PCS_IF_MODE_SGMI_HALF_DUPLEX BIT(4) -#define PCS_IF_MODE_SGMI_PHY_AN BIT(5) -#define SGMII_PCS_SW_RESET_TIMEOUT 100 /* usecs */ - -struct altera_tse_pcs { - struct phylink_pcs pcs; - void __iomem *base; - int reg_width; -}; - -static struct altera_tse_pcs *phylink_pcs_to_tse_pcs(struct phylink_pcs *pcs) +static int altera_tse_pcs_mdio_write(struct mii_bus *bus, int mii_id, int regnum, + u16 value) { - return container_of(pcs, struct altera_tse_pcs, pcs); -} + struct altera_tse_pcs *tse_pcs = bus->priv; -static u16 tse_pcs_read(struct altera_tse_pcs *tse_pcs, int regnum) -{ - if (tse_pcs->reg_width == 4) - return readl(tse_pcs->base + regnum * 4); - else - return readw(tse_pcs->base + regnum * 2); + return tse_pcs->ops->write(tse_pcs->priv, regnum, value); } -static void tse_pcs_write(struct altera_tse_pcs *tse_pcs, int regnum, - u16 value) +static int altera_tse_pcs_mdio_read(struct mii_bus *bus, int mii_id, int regnum) { - if (tse_pcs->reg_width == 4) - writel(value, tse_pcs->base + regnum * 4); - else - writew(value, tse_pcs->base + regnum * 2); -} + struct altera_tse_pcs *tse_pcs = bus->priv; -static int tse_pcs_reset(struct altera_tse_pcs *tse_pcs) -{ - u16 bmcr; - - /* Reset PCS block */ - bmcr = tse_pcs_read(tse_pcs, MII_BMCR); - bmcr |= BMCR_RESET; - tse_pcs_write(tse_pcs, MII_BMCR, bmcr); + if (mii_id != 0) + return 0; - return read_poll_timeout(tse_pcs_read, bmcr, (bmcr & BMCR_RESET), - 10, SGMII_PCS_SW_RESET_TIMEOUT, 1, - tse_pcs, MII_BMCR); + return tse_pcs->ops->read(tse_pcs->priv, regnum); } -static int alt_tse_pcs_validate(struct phylink_pcs *pcs, - unsigned long *supported, - const struct phylink_link_state *state) +static struct altera_tse_pcs * +altera_tse_pcs_mdio_create(struct net_device *dev, + struct altera_tse_pcs_ops *ops, + void *priv) { - if (state->interface == PHY_INTERFACE_MODE_SGMII || - state->interface == PHY_INTERFACE_MODE_1000BASEX) - return 1; - - return -EINVAL; -} - -static int alt_tse_pcs_config(struct phylink_pcs *pcs, unsigned int mode, - phy_interface_t interface, - const unsigned long *advertising, - bool permit_pause_to_mac) -{ - struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs); - u32 ctrl, if_mode; - - ctrl = tse_pcs_read(tse_pcs, MII_BMCR); - if_mode = tse_pcs_read(tse_pcs, SGMII_PCS_IF_MODE); - - /* Set link timer to 1.6ms, as per the MegaCore Function User Guide */ - tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_0, 0x0D40); - tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_1, 0x03); - - if (interface == PHY_INTERFACE_MODE_SGMII) { - if_mode |= PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA; - } else if (interface == PHY_INTERFACE_MODE_1000BASEX) { - if_mode &= ~(PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA); + struct altera_tse_pcs *tse_pcs; + struct mii_bus *mii_bus; + int ret; + + tse_pcs = kzalloc(sizeof(*tse_pcs), GFP_KERNEL); + if (IS_ERR(tse_pcs)) + return NULL; + + tse_pcs->ops = ops; + tse_pcs->priv = priv; + + mii_bus = mdiobus_alloc(); + if (!mii_bus) + goto out_free_pcs; + + mii_bus->name = "Altera TSE PCS MDIO"; + mii_bus->read = &altera_tse_pcs_mdio_read; + mii_bus->write = &altera_tse_pcs_mdio_write; + snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s-%s", mii_bus->name, + dev->name); + + mii_bus->priv = tse_pcs; + mii_bus->parent = &dev->dev; + + ret = mdiobus_register(mii_bus); + if (ret) + goto out_free_mdio; + + tse_pcs->mii_bus = mii_bus; + tse_pcs->mdiodev = mdio_device_create(mii_bus, 0); + if (IS_ERR(tse_pcs->mdiodev)) { + ret = PTR_ERR(tse_pcs->mdiodev); + goto out_unregister_mdio; } - ctrl |= (BMCR_SPEED1000 | BMCR_FULLDPLX | BMCR_ANENABLE); - - tse_pcs_write(tse_pcs, MII_BMCR, ctrl); - tse_pcs_write(tse_pcs, SGMII_PCS_IF_MODE, if_mode); + return tse_pcs; - return tse_pcs_reset(tse_pcs); +out_unregister_mdio: + mdiobus_unregister(mii_bus); +out_free_mdio: + mdiobus_free(mii_bus); +out_free_pcs: + kfree(tse_pcs); + return NULL; } -static void alt_tse_pcs_get_state(struct phylink_pcs *pcs, - struct phylink_link_state *state) +void alt_tse_pcs_destroy(struct altera_tse_pcs *tse_pcs) { - struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs); - u16 bmsr, lpa; - - bmsr = tse_pcs_read(tse_pcs, MII_BMSR); - lpa = tse_pcs_read(tse_pcs, MII_LPA); - - phylink_mii_c22_pcs_decode_state(state, bmsr, lpa); + mdio_device_free(tse_pcs->mdiodev); + mdiobus_unregister(tse_pcs->mii_bus); + mdiobus_free(tse_pcs->mii_bus); + kfree(tse_pcs); } -static void alt_tse_pcs_an_restart(struct phylink_pcs *pcs) +struct altera_tse_pcs *alt_tse_pcs_create(struct net_device *dev, + struct altera_tse_pcs_ops *ops, + void *priv) { - struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs); - u16 bmcr; - - bmcr = tse_pcs_read(tse_pcs, MII_BMCR); - bmcr |= BMCR_ANRESTART; - tse_pcs_write(tse_pcs, MII_BMCR, bmcr); - - /* This PCS seems to require a soft reset to re-sync the AN logic */ - tse_pcs_reset(tse_pcs); -} + struct altera_tse_pcs *tse_pcs; + struct phylink_pcs *pcs; -static const struct phylink_pcs_ops alt_tse_pcs_ops = { - .pcs_validate = alt_tse_pcs_validate, - .pcs_get_state = alt_tse_pcs_get_state, - .pcs_config = alt_tse_pcs_config, - .pcs_an_restart = alt_tse_pcs_an_restart, -}; + tse_pcs = altera_tse_pcs_mdio_create(dev, ops, priv); + if (!tse_pcs) + return NULL; -struct phylink_pcs *alt_tse_pcs_create(struct net_device *ndev, - void __iomem *pcs_base, int reg_width) -{ - struct altera_tse_pcs *tse_pcs; + pcs = lynx_pcs_create(tse_pcs->mdiodev); + if (!pcs) + goto out_free_mdio; - if (reg_width != 4 && reg_width != 2) - return ERR_PTR(-EINVAL); + tse_pcs->pcs = pcs; - tse_pcs = devm_kzalloc(&ndev->dev, sizeof(*tse_pcs), GFP_KERNEL); - if (!tse_pcs) - return ERR_PTR(-ENOMEM); + return tse_pcs; - tse_pcs->pcs.ops = &alt_tse_pcs_ops; - tse_pcs->base = pcs_base; - tse_pcs->reg_width = reg_width; +out_free_mdio: + alt_tse_pcs_destroy(tse_pcs); - return &tse_pcs->pcs; + return NULL; } -EXPORT_SYMBOL_GPL(alt_tse_pcs_create); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Altera TSE PCS driver"); diff --git a/include/linux/pcs-altera-tse.h b/include/linux/pcs-altera-tse.h index 92ab9f08e8359b..67be242a468ee4 100644 --- a/include/linux/pcs-altera-tse.h +++ b/include/linux/pcs-altera-tse.h @@ -11,7 +11,25 @@ struct phylink_pcs; struct net_device; -struct phylink_pcs *alt_tse_pcs_create(struct net_device *ndev, - void __iomem *pcs_base, int reg_width); +struct altera_tse_pcs_ops { + int (*read)(void *priv, int regnum); + int (*write)(void *priv, int regnum, u16 value); +}; + +struct altera_tse_pcs { + struct phylink_pcs *pcs; + struct altera_tse_pcs_ops *ops; + struct mii_bus *mii_bus; + struct mdio_device *mdiodev; + void *priv; +}; + +#define altera_tse_pcs_to_phylink_pcs(tse_pcs) ((tse_pcs)->pcs) + +struct altera_tse_pcs *alt_tse_pcs_create(struct net_device *ndev, + struct altera_tse_pcs_ops *ops, + void *priv); + +void alt_tse_pcs_destroy(struct altera_tse_pcs *tse_pcs); #endif /* __LINUX_PCS_ALTERA_TSE_H */