Commit f91e32de authored by David S. Miller's avatar David S. Miller

Merge branch 'regmap-TSE-PCS'

Maxime Chevallier says:

====================
net: add a regmap-based mdio driver and drop TSE PCS

This is the V4 of a series that follows-up on the work [1] aiming to drop the
altera TSE PCS driver, as it turns out to be a version of the Lynx PCS exposed
as a memory-mapped block, instead of living on an MDIO bus.

One step of this removal involved creating a regmap-based mdio driver
that translates MDIO accesses into the actual underlying bus that
exposes the register. The register layout must of course match the
standard MDIO layout, but we can now account for differences in stride
with recent work on the regmap subsystem [2].

Sorry for repeating this, but I didn't hear anything on this matter in previous
iterations, Mark, Net maintainers, this series depends on the patch
e12ff287 that was recently merged into the regmap tree [3].

For this series to be usable in net-next, this patch must be applied
beforehand. Should Mark create a tag that would then be merged into
net-next ? Or should we just wait for the next release to merge this
into net-next ?

This series introduces a new MDIO driver, and uses it to convert Altera
TSE from the actual TSE PCS driver to Lynx PCS.

Since it turns out dwmac_socfpga also uses a TSE PCS block, port that
driver to Lynx as well.

Changes in V4 :
 - Use new pcs_lynx_create/destroy helpers added by Russell
 - Rework the cleanup sequence to avoid leaking data
 - Rework a bit KConfig to properly select dependencies
 - Fix a few hiccups with misplaced hunks in 2 commits

Changes in V3 :
 - Use a dedicated struct for the mii bus's priv data, to avoid
   duplicating the whole struct mdio_regmap_config, from which 2 fields
   only are necessary after init, as suggested by Russell
 - Use ~0 instead of ~0UL for the no-scan bitmask, following Simon's
   review.

Changes in V2 :
 - Use phy_mask to avoid unnecessarily scanning the whole mdio bus
 - Go one step further and completely disable scanning if users
   set the .autoscan flag to false, in case the mdiodevice isn't an
   actual PHY (a PCS for example).
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents f69de8aa 5d1f3fe7
......@@ -909,13 +909,6 @@ L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/ethernet/altera/
ALTERA TSE PCS
M: Maxime Chevallier <maxime.chevallier@bootlin.com>
L: netdev@vger.kernel.org
S: Supported
F: drivers/net/pcs/pcs-altera-tse.c
F: include/linux/pcs-altera-tse.h
ALTERA UART/JTAG UART SERIAL DRIVERS
M: Tobias Klauser <tklauser@distanz.ch>
L: linux-serial@vger.kernel.org
......@@ -12844,6 +12837,13 @@ F: Documentation/devicetree/bindings/net/ieee802154/mcr20a.txt
F: drivers/net/ieee802154/mcr20a.c
F: drivers/net/ieee802154/mcr20a.h
MDIO REGMAP DRIVER
M: Maxime Chevallier <maxime.chevallier@bootlin.com>
L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/mdio/mdio-regmap.c
F: include/linux/mdio/mdio-regmap.h
MEASUREMENT COMPUTING CIO-DAC IIO DRIVER
M: William Breathitt Gray <william.gray@linaro.org>
L: linux-iio@vger.kernel.org
......
......@@ -5,6 +5,8 @@ config ALTERA_TSE
select PHYLIB
select PHYLINK
select PCS_ALTERA_TSE
select MDIO_REGMAP
select REGMAP_MMIO
help
This driver supports the Altera Triple-Speed (TSE) Ethernet MAC.
......
......@@ -27,14 +27,16 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mii.h>
#include <linux/mdio/mdio-regmap.h>
#include <linux/netdevice.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
#include <linux/of_net.h>
#include <linux/of_platform.h>
#include <linux/pcs-altera-tse.h>
#include <linux/pcs-lynx.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/skbuff.h>
#include <asm/cacheflush.h>
......@@ -1132,13 +1134,16 @@ static int request_and_map(struct platform_device *pdev, const char *name,
static int altera_tse_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id = NULL;
struct regmap_config pcs_regmap_cfg;
struct altera_tse_private *priv;
struct mdio_regmap_config mrc;
struct resource *control_port;
struct regmap *pcs_regmap;
struct resource *dma_res;
struct resource *pcs_res;
struct mii_bus *pcs_bus;
struct net_device *ndev;
void __iomem *descmap;
int pcs_reg_width = 2;
int ret = -ENODEV;
ndev = alloc_etherdev(sizeof(struct altera_tse_private));
......@@ -1255,12 +1260,32 @@ static int altera_tse_probe(struct platform_device *pdev)
* address space, but if it's not the case, we fallback to the mdiophy0
* from the MAC's address space
*/
ret = request_and_map(pdev, "pcs", &pcs_res,
&priv->pcs_base);
ret = request_and_map(pdev, "pcs", &pcs_res, &priv->pcs_base);
if (ret) {
/* If we can't find a dedicated resource for the PCS, fallback
* to the internal PCS, that has a different address stride
*/
priv->pcs_base = priv->mac_dev + tse_csroffs(mdio_phy0);
pcs_reg_width = 4;
pcs_regmap_cfg.reg_bits = 32;
/* Values are MDIO-like values, on 16 bits */
pcs_regmap_cfg.val_bits = 16;
pcs_regmap_cfg.reg_shift = REGMAP_UPSHIFT(2);
} else {
pcs_regmap_cfg.reg_bits = 16;
pcs_regmap_cfg.val_bits = 16;
pcs_regmap_cfg.reg_shift = REGMAP_UPSHIFT(1);
}
/* Create a regmap for the PCS so that it can be used by the PCS driver */
pcs_regmap = devm_regmap_init_mmio(&pdev->dev, priv->pcs_base,
&pcs_regmap_cfg);
if (IS_ERR(pcs_regmap)) {
ret = PTR_ERR(pcs_regmap);
goto err_free_netdev;
}
mrc.regmap = pcs_regmap;
mrc.parent = &pdev->dev;
mrc.valid_addr = 0x0;
/* Rx IRQ */
priv->rx_irq = platform_get_irq_byname(pdev, "rx_irq");
......@@ -1384,7 +1409,18 @@ 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);
snprintf(mrc.name, MII_BUS_ID_SIZE, "%s-pcs-mii", ndev->name);
pcs_bus = devm_mdio_regmap_register(&pdev->dev, &mrc);
if (IS_ERR(pcs_bus)) {
ret = PTR_ERR(pcs_bus);
goto err_init_pcs;
}
priv->pcs = lynx_pcs_create_mdiodev(pcs_bus, 0);
if (IS_ERR(priv->pcs)) {
ret = PTR_ERR(priv->pcs);
goto err_init_pcs;
}
priv->phylink_config.dev = &ndev->dev;
priv->phylink_config.type = PHYLINK_NETDEV;
......@@ -1407,12 +1443,13 @@ 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_init_phylink;
}
return 0;
err_init_phy:
err_init_phylink:
lynx_pcs_destroy(priv->pcs);
err_init_pcs:
unregister_netdev(ndev);
err_register_netdev:
netif_napi_del(&priv->napi);
......@@ -1433,6 +1470,8 @@ static int altera_tse_remove(struct platform_device *pdev)
altera_tse_mdio_destroy(ndev);
unregister_netdev(ndev);
phylink_destroy(priv->phylink);
lynx_pcs_destroy(priv->pcs);
free_netdev(ndev);
return 0;
......
......@@ -158,6 +158,9 @@ config DWMAC_SOCFPGA
default ARCH_INTEL_SOCFPGA
depends on OF && (ARCH_INTEL_SOCFPGA || COMPILE_TEST)
select MFD_SYSCON
select MDIO_REGMAP
select REGMAP_MMIO
select PCS_LYNX
help
Support for ethernet controller on Altera SOCFPGA
......
......@@ -35,7 +35,7 @@ obj-$(CONFIG_DWMAC_IMX8) += dwmac-imx.o
obj-$(CONFIG_DWMAC_TEGRA) += dwmac-tegra.o
obj-$(CONFIG_DWMAC_VISCONTI) += dwmac-visconti.o
stmmac-platform-objs:= stmmac_platform.o
dwmac-altr-socfpga-objs := altr_tse_pcs.o dwmac-socfpga.o
dwmac-altr-socfpga-objs := dwmac-socfpga.o
obj-$(CONFIG_STMMAC_PCI) += stmmac-pci.o
obj-$(CONFIG_DWMAC_INTEL) += dwmac-intel.o
......
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright Altera Corporation (C) 2016. All rights reserved.
*
* Author: Tien Hock Loh <thloh@altera.com>
*/
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_net.h>
#include <linux/phy.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/stmmac.h>
#include "stmmac.h"
#include "stmmac_platform.h"
#include "altr_tse_pcs.h"
#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_GMII_MII 0
#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RGMII BIT(1)
#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RMII BIT(2)
#define SYSMGR_EMACGRP_CTRL_PHYSEL_WIDTH 2
#define SYSMGR_EMACGRP_CTRL_PHYSEL_MASK GENMASK(1, 0)
#define TSE_PCS_CONTROL_AN_EN_MASK BIT(12)
#define TSE_PCS_CONTROL_REG 0x00
#define TSE_PCS_CONTROL_RESTART_AN_MASK BIT(9)
#define TSE_PCS_CTRL_AUTONEG_SGMII 0x1140
#define TSE_PCS_IF_MODE_REG 0x28
#define TSE_PCS_LINK_TIMER_0_REG 0x24
#define TSE_PCS_LINK_TIMER_1_REG 0x26
#define TSE_PCS_SIZE 0x40
#define TSE_PCS_STATUS_AN_COMPLETED_MASK BIT(5)
#define TSE_PCS_STATUS_LINK_MASK 0x0004
#define TSE_PCS_STATUS_REG 0x02
#define TSE_PCS_SGMII_SPEED_1000 BIT(3)
#define TSE_PCS_SGMII_SPEED_100 BIT(2)
#define TSE_PCS_SGMII_SPEED_10 0x0
#define TSE_PCS_SW_RST_MASK 0x8000
#define TSE_PCS_PARTNER_ABILITY_REG 0x0A
#define TSE_PCS_PARTNER_DUPLEX_FULL 0x1000
#define TSE_PCS_PARTNER_DUPLEX_HALF 0x0000
#define TSE_PCS_PARTNER_DUPLEX_MASK 0x1000
#define TSE_PCS_PARTNER_SPEED_MASK GENMASK(11, 10)
#define TSE_PCS_PARTNER_SPEED_1000 BIT(11)
#define TSE_PCS_PARTNER_SPEED_100 BIT(10)
#define TSE_PCS_PARTNER_SPEED_10 0x0000
#define TSE_PCS_PARTNER_SPEED_1000 BIT(11)
#define TSE_PCS_PARTNER_SPEED_100 BIT(10)
#define TSE_PCS_PARTNER_SPEED_10 0x0000
#define TSE_PCS_SGMII_SPEED_MASK GENMASK(3, 2)
#define TSE_PCS_SGMII_LINK_TIMER_0 0x0D40
#define TSE_PCS_SGMII_LINK_TIMER_1 0x0003
#define TSE_PCS_SW_RESET_TIMEOUT 100
#define TSE_PCS_USE_SGMII_AN_MASK BIT(1)
#define TSE_PCS_USE_SGMII_ENA BIT(0)
#define TSE_PCS_IF_USE_SGMII 0x03
#define AUTONEGO_LINK_TIMER 20
static int tse_pcs_reset(void __iomem *base, struct tse_pcs *pcs)
{
int counter = 0;
u16 val;
val = readw(base + TSE_PCS_CONTROL_REG);
val |= TSE_PCS_SW_RST_MASK;
writew(val, base + TSE_PCS_CONTROL_REG);
while (counter < TSE_PCS_SW_RESET_TIMEOUT) {
val = readw(base + TSE_PCS_CONTROL_REG);
val &= TSE_PCS_SW_RST_MASK;
if (val == 0)
break;
counter++;
udelay(1);
}
if (counter >= TSE_PCS_SW_RESET_TIMEOUT) {
dev_err(pcs->dev, "PCS could not get out of sw reset\n");
return -ETIMEDOUT;
}
return 0;
}
int tse_pcs_init(void __iomem *base, struct tse_pcs *pcs)
{
int ret = 0;
writew(TSE_PCS_IF_USE_SGMII, base + TSE_PCS_IF_MODE_REG);
writew(TSE_PCS_CTRL_AUTONEG_SGMII, base + TSE_PCS_CONTROL_REG);
writew(TSE_PCS_SGMII_LINK_TIMER_0, base + TSE_PCS_LINK_TIMER_0_REG);
writew(TSE_PCS_SGMII_LINK_TIMER_1, base + TSE_PCS_LINK_TIMER_1_REG);
ret = tse_pcs_reset(base, pcs);
if (ret == 0)
writew(SGMII_ADAPTER_ENABLE,
pcs->sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG);
return ret;
}
static void pcs_link_timer_callback(struct tse_pcs *pcs)
{
u16 val = 0;
void __iomem *tse_pcs_base = pcs->tse_pcs_base;
void __iomem *sgmii_adapter_base = pcs->sgmii_adapter_base;
val = readw(tse_pcs_base + TSE_PCS_STATUS_REG);
val &= TSE_PCS_STATUS_LINK_MASK;
if (val != 0) {
dev_dbg(pcs->dev, "Adapter: Link is established\n");
writew(SGMII_ADAPTER_ENABLE,
sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG);
} else {
mod_timer(&pcs->aneg_link_timer, jiffies +
msecs_to_jiffies(AUTONEGO_LINK_TIMER));
}
}
static void auto_nego_timer_callback(struct tse_pcs *pcs)
{
u16 val = 0;
u16 speed = 0;
u16 duplex = 0;
void __iomem *tse_pcs_base = pcs->tse_pcs_base;
void __iomem *sgmii_adapter_base = pcs->sgmii_adapter_base;
val = readw(tse_pcs_base + TSE_PCS_STATUS_REG);
val &= TSE_PCS_STATUS_AN_COMPLETED_MASK;
if (val != 0) {
dev_dbg(pcs->dev, "Adapter: Auto Negotiation is completed\n");
val = readw(tse_pcs_base + TSE_PCS_PARTNER_ABILITY_REG);
speed = val & TSE_PCS_PARTNER_SPEED_MASK;
duplex = val & TSE_PCS_PARTNER_DUPLEX_MASK;
if (speed == TSE_PCS_PARTNER_SPEED_10 &&
duplex == TSE_PCS_PARTNER_DUPLEX_FULL)
dev_dbg(pcs->dev,
"Adapter: Link Partner is Up - 10/Full\n");
else if (speed == TSE_PCS_PARTNER_SPEED_100 &&
duplex == TSE_PCS_PARTNER_DUPLEX_FULL)
dev_dbg(pcs->dev,
"Adapter: Link Partner is Up - 100/Full\n");
else if (speed == TSE_PCS_PARTNER_SPEED_1000 &&
duplex == TSE_PCS_PARTNER_DUPLEX_FULL)
dev_dbg(pcs->dev,
"Adapter: Link Partner is Up - 1000/Full\n");
else if (speed == TSE_PCS_PARTNER_SPEED_10 &&
duplex == TSE_PCS_PARTNER_DUPLEX_HALF)
dev_err(pcs->dev,
"Adapter does not support Half Duplex\n");
else if (speed == TSE_PCS_PARTNER_SPEED_100 &&
duplex == TSE_PCS_PARTNER_DUPLEX_HALF)
dev_err(pcs->dev,
"Adapter does not support Half Duplex\n");
else if (speed == TSE_PCS_PARTNER_SPEED_1000 &&
duplex == TSE_PCS_PARTNER_DUPLEX_HALF)
dev_err(pcs->dev,
"Adapter does not support Half Duplex\n");
else
dev_err(pcs->dev,
"Adapter: Invalid Partner Speed and Duplex\n");
if (duplex == TSE_PCS_PARTNER_DUPLEX_FULL &&
(speed == TSE_PCS_PARTNER_SPEED_10 ||
speed == TSE_PCS_PARTNER_SPEED_100 ||
speed == TSE_PCS_PARTNER_SPEED_1000))
writew(SGMII_ADAPTER_ENABLE,
sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG);
} else {
val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG);
val |= TSE_PCS_CONTROL_RESTART_AN_MASK;
writew(val, tse_pcs_base + TSE_PCS_CONTROL_REG);
tse_pcs_reset(tse_pcs_base, pcs);
mod_timer(&pcs->aneg_link_timer, jiffies +
msecs_to_jiffies(AUTONEGO_LINK_TIMER));
}
}
static void aneg_link_timer_callback(struct timer_list *t)
{
struct tse_pcs *pcs = from_timer(pcs, t, aneg_link_timer);
if (pcs->autoneg == AUTONEG_ENABLE)
auto_nego_timer_callback(pcs);
else if (pcs->autoneg == AUTONEG_DISABLE)
pcs_link_timer_callback(pcs);
}
void tse_pcs_fix_mac_speed(struct tse_pcs *pcs, struct phy_device *phy_dev,
unsigned int speed)
{
void __iomem *tse_pcs_base = pcs->tse_pcs_base;
u32 val;
pcs->autoneg = phy_dev->autoneg;
if (phy_dev->autoneg == AUTONEG_ENABLE) {
val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG);
val |= TSE_PCS_CONTROL_AN_EN_MASK;
writew(val, tse_pcs_base + TSE_PCS_CONTROL_REG);
val = readw(tse_pcs_base + TSE_PCS_IF_MODE_REG);
val |= TSE_PCS_USE_SGMII_AN_MASK;
writew(val, tse_pcs_base + TSE_PCS_IF_MODE_REG);
val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG);
val |= TSE_PCS_CONTROL_RESTART_AN_MASK;
tse_pcs_reset(tse_pcs_base, pcs);
timer_setup(&pcs->aneg_link_timer, aneg_link_timer_callback,
0);
mod_timer(&pcs->aneg_link_timer, jiffies +
msecs_to_jiffies(AUTONEGO_LINK_TIMER));
} else if (phy_dev->autoneg == AUTONEG_DISABLE) {
val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG);
val &= ~TSE_PCS_CONTROL_AN_EN_MASK;
writew(val, tse_pcs_base + TSE_PCS_CONTROL_REG);
val = readw(tse_pcs_base + TSE_PCS_IF_MODE_REG);
val &= ~TSE_PCS_USE_SGMII_AN_MASK;
writew(val, tse_pcs_base + TSE_PCS_IF_MODE_REG);
val = readw(tse_pcs_base + TSE_PCS_IF_MODE_REG);
val &= ~TSE_PCS_SGMII_SPEED_MASK;
switch (speed) {
case 1000:
val |= TSE_PCS_SGMII_SPEED_1000;
break;
case 100:
val |= TSE_PCS_SGMII_SPEED_100;
break;
case 10:
val |= TSE_PCS_SGMII_SPEED_10;
break;
default:
return;
}
writew(val, tse_pcs_base + TSE_PCS_IF_MODE_REG);
tse_pcs_reset(tse_pcs_base, pcs);
timer_setup(&pcs->aneg_link_timer, aneg_link_timer_callback,
0);
mod_timer(&pcs->aneg_link_timer, jiffies +
msecs_to_jiffies(AUTONEGO_LINK_TIMER));
}
}
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright Altera Corporation (C) 2016. All rights reserved.
*
* Author: Tien Hock Loh <thloh@altera.com>
*/
#ifndef __TSE_PCS_H__
#define __TSE_PCS_H__
#include <linux/phy.h>
#include <linux/timer.h>
#define SGMII_ADAPTER_CTRL_REG 0x00
#define SGMII_ADAPTER_ENABLE 0x0000
#define SGMII_ADAPTER_DISABLE 0x0001
struct tse_pcs {
struct device *dev;
void __iomem *tse_pcs_base;
void __iomem *sgmii_adapter_base;
struct timer_list aneg_link_timer;
int autoneg;
};
int tse_pcs_init(void __iomem *base, struct tse_pcs *pcs);
void tse_pcs_fix_mac_speed(struct tse_pcs *pcs, struct phy_device *phy_dev,
unsigned int speed);
#endif /* __TSE_PCS_H__ */
......@@ -16,6 +16,7 @@
#include <linux/stmmac.h>
#include <linux/phy.h>
#include <linux/pcs/pcs-xpcs.h>
#include <linux/pcs-lynx.h>
#include <linux/module.h>
#if IS_ENABLED(CONFIG_VLAN_8021Q)
#define STMMAC_VLAN_TAG_USED
......@@ -519,6 +520,7 @@ struct mac_device_info {
const struct stmmac_tc_ops *tc;
const struct stmmac_mmc_ops *mmc;
struct dw_xpcs *xpcs;
struct phylink_pcs *lynx_pcs; /* Lynx external PCS */
struct mii_regs mii; /* MII register Addresses */
struct mac_link link;
void __iomem *pcsr; /* vpointer to device CSRs */
......
......@@ -10,14 +10,13 @@
#include <linux/of_net.h>
#include <linux/phy.h>
#include <linux/regmap.h>
#include <linux/mdio/mdio-regmap.h>
#include <linux/reset.h>
#include <linux/stmmac.h>
#include "stmmac.h"
#include "stmmac_platform.h"
#include "altr_tse_pcs.h"
#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_GMII_MII 0x0
#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RGMII 0x1
#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RMII 0x2
......@@ -37,6 +36,10 @@
#define EMAC_SPLITTER_CTRL_SPEED_100 0x3
#define EMAC_SPLITTER_CTRL_SPEED_1000 0x0
#define SGMII_ADAPTER_CTRL_REG 0x00
#define SGMII_ADAPTER_ENABLE 0x0000
#define SGMII_ADAPTER_DISABLE 0x0001
struct socfpga_dwmac;
struct socfpga_dwmac_ops {
int (*set_phy_mode)(struct socfpga_dwmac *dwmac_priv);
......@@ -50,16 +53,18 @@ struct socfpga_dwmac {
struct reset_control *stmmac_rst;
struct reset_control *stmmac_ocp_rst;
void __iomem *splitter_base;
void __iomem *tse_pcs_base;
void __iomem *sgmii_adapter_base;
bool f2h_ptp_ref_clk;
struct tse_pcs pcs;
const struct socfpga_dwmac_ops *ops;
struct mdio_device *pcs_mdiodev;
};
static void socfpga_dwmac_fix_mac_speed(void *priv, unsigned int speed)
{
struct socfpga_dwmac *dwmac = (struct socfpga_dwmac *)priv;
void __iomem *splitter_base = dwmac->splitter_base;
void __iomem *sgmii_adapter_base = dwmac->pcs.sgmii_adapter_base;
void __iomem *sgmii_adapter_base = dwmac->sgmii_adapter_base;
struct device *dev = dwmac->dev;
struct net_device *ndev = dev_get_drvdata(dev);
struct phy_device *phy_dev = ndev->phydev;
......@@ -89,11 +94,9 @@ static void socfpga_dwmac_fix_mac_speed(void *priv, unsigned int speed)
writel(val, splitter_base + EMAC_SPLITTER_CTRL_REG);
}
if (phy_dev && sgmii_adapter_base) {
if (phy_dev && sgmii_adapter_base)
writew(SGMII_ADAPTER_ENABLE,
sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG);
tse_pcs_fix_mac_speed(&dwmac->pcs, phy_dev, speed);
}
}
static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device *dev)
......@@ -183,11 +186,11 @@ static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device *
goto err_node_put;
}
dwmac->pcs.sgmii_adapter_base =
dwmac->sgmii_adapter_base =
devm_ioremap_resource(dev, &res_sgmii_adapter);
if (IS_ERR(dwmac->pcs.sgmii_adapter_base)) {
ret = PTR_ERR(dwmac->pcs.sgmii_adapter_base);
if (IS_ERR(dwmac->sgmii_adapter_base)) {
ret = PTR_ERR(dwmac->sgmii_adapter_base);
goto err_node_put;
}
}
......@@ -205,11 +208,11 @@ static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device *
goto err_node_put;
}
dwmac->pcs.tse_pcs_base =
dwmac->tse_pcs_base =
devm_ioremap_resource(dev, &res_tse_pcs);
if (IS_ERR(dwmac->pcs.tse_pcs_base)) {
ret = PTR_ERR(dwmac->pcs.tse_pcs_base);
if (IS_ERR(dwmac->tse_pcs_base)) {
ret = PTR_ERR(dwmac->tse_pcs_base);
goto err_node_put;
}
}
......@@ -235,6 +238,13 @@ static int socfpga_get_plat_phymode(struct socfpga_dwmac *dwmac)
return priv->plat->interface;
}
static void socfpga_sgmii_config(struct socfpga_dwmac *dwmac, bool enable)
{
u16 val = enable ? SGMII_ADAPTER_ENABLE : SGMII_ADAPTER_DISABLE;
writew(val, dwmac->sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG);
}
static int socfpga_set_phy_mode_common(int phymode, u32 *val)
{
switch (phymode) {
......@@ -310,12 +320,8 @@ static int socfpga_gen5_set_phy_mode(struct socfpga_dwmac *dwmac)
*/
reset_control_deassert(dwmac->stmmac_ocp_rst);
reset_control_deassert(dwmac->stmmac_rst);
if (phymode == PHY_INTERFACE_MODE_SGMII) {
if (tse_pcs_init(dwmac->pcs.tse_pcs_base, &dwmac->pcs) != 0) {
dev_err(dwmac->dev, "Unable to initialize TSE PCS");
return -EINVAL;
}
}
if (phymode == PHY_INTERFACE_MODE_SGMII)
socfpga_sgmii_config(dwmac, true);
return 0;
}
......@@ -367,12 +373,8 @@ static int socfpga_gen10_set_phy_mode(struct socfpga_dwmac *dwmac)
*/
reset_control_deassert(dwmac->stmmac_ocp_rst);
reset_control_deassert(dwmac->stmmac_rst);
if (phymode == PHY_INTERFACE_MODE_SGMII) {
if (tse_pcs_init(dwmac->pcs.tse_pcs_base, &dwmac->pcs) != 0) {
dev_err(dwmac->dev, "Unable to initialize TSE PCS");
return -EINVAL;
}
}
if (phymode == PHY_INTERFACE_MODE_SGMII)
socfpga_sgmii_config(dwmac, true);
return 0;
}
......@@ -386,6 +388,7 @@ static int socfpga_dwmac_probe(struct platform_device *pdev)
struct net_device *ndev;
struct stmmac_priv *stpriv;
const struct socfpga_dwmac_ops *ops;
struct regmap_config pcs_regmap_cfg;
ops = device_get_match_data(&pdev->dev);
if (!ops) {
......@@ -443,6 +446,44 @@ static int socfpga_dwmac_probe(struct platform_device *pdev)
if (ret)
goto err_dvr_remove;
memset(&pcs_regmap_cfg, 0, sizeof(pcs_regmap_cfg));
pcs_regmap_cfg.reg_bits = 16;
pcs_regmap_cfg.val_bits = 16;
pcs_regmap_cfg.reg_shift = REGMAP_UPSHIFT(1);
/* Create a regmap for the PCS so that it can be used by the PCS driver,
* if we have such a PCS
*/
if (dwmac->tse_pcs_base) {
struct mdio_regmap_config mrc;
struct regmap *pcs_regmap;
struct mii_bus *pcs_bus;
pcs_regmap = devm_regmap_init_mmio(&pdev->dev, dwmac->tse_pcs_base,
&pcs_regmap_cfg);
if (IS_ERR(pcs_regmap)) {
ret = PTR_ERR(pcs_regmap);
goto err_dvr_remove;
}
mrc.regmap = pcs_regmap;
mrc.parent = &pdev->dev;
mrc.valid_addr = 0x0;
snprintf(mrc.name, MII_BUS_ID_SIZE, "%s-pcs-mii", ndev->name);
pcs_bus = devm_mdio_regmap_register(&pdev->dev, &mrc);
if (IS_ERR(pcs_bus)) {
ret = PTR_ERR(pcs_bus);
goto err_dvr_remove;
}
stpriv->hw->lynx_pcs = lynx_pcs_create_mdiodev(pcs_bus, 0);
if (IS_ERR(stpriv->hw->lynx_pcs)) {
ret = PTR_ERR(stpriv->hw->lynx_pcs);
goto err_dvr_remove;
}
}
return 0;
err_dvr_remove:
......
......@@ -937,10 +937,13 @@ static struct phylink_pcs *stmmac_mac_select_pcs(struct phylink_config *config,
{
struct stmmac_priv *priv = netdev_priv(to_net_dev(config->dev));
if (!priv->hw->xpcs)
return NULL;
if (priv->hw->xpcs)
return &priv->hw->xpcs->pcs;
if (priv->hw->lynx_pcs)
return priv->hw->lynx_pcs;
return &priv->hw->xpcs->pcs;
return NULL;
}
static void stmmac_mac_config(struct phylink_config *config, unsigned int mode,
......@@ -3813,7 +3816,8 @@ static int __stmmac_open(struct net_device *dev,
if (priv->hw->pcs != STMMAC_PCS_TBI &&
priv->hw->pcs != STMMAC_PCS_RTBI &&
(!priv->hw->xpcs ||
xpcs_get_an_mode(priv->hw->xpcs, mode) != DW_AN_C73)) {
xpcs_get_an_mode(priv->hw->xpcs, mode) != DW_AN_C73) &&
!priv->hw->lynx_pcs) {
ret = stmmac_init_phy(dev);
if (ret) {
netdev_err(priv->dev,
......
......@@ -665,6 +665,9 @@ int stmmac_mdio_unregister(struct net_device *ndev)
if (priv->hw->xpcs)
xpcs_destroy(priv->hw->xpcs);
if (priv->hw->lynx_pcs)
lynx_pcs_destroy(priv->hw->lynx_pcs);
mdiobus_unregister(priv->mii);
priv->mii->priv = NULL;
mdiobus_free(priv->mii);
......
......@@ -185,6 +185,17 @@ config MDIO_IPQ8064
This driver supports the MDIO interface found in the network
interface units of the IPQ8064 SoC
config MDIO_REGMAP
tristate
help
This driver allows using MDIO devices that are not sitting on a
regular MDIO bus, but still exposes the standard 802.3 register
layout. It's regmap-based so that it can be used on integrated,
memory-mapped PHYs, SPI PHYs and so on. A new virtual MDIO bus is
created, and its read/write operations are mapped to the underlying
regmap. Users willing to use this driver must explicitly select
REGMAP.
config MDIO_THUNDER
tristate "ThunderX SOCs MDIO buses"
depends on 64BIT
......
......@@ -19,6 +19,7 @@ obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o
obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-mscc-miim.o
obj-$(CONFIG_MDIO_MVUSB) += mdio-mvusb.o
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
obj-$(CONFIG_MDIO_REGMAP) += mdio-regmap.o
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o
obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o
......
// SPDX-License-Identifier: GPL-2.0-or-later
/* Driver for MMIO-Mapped MDIO devices. Some IPs expose internal PHYs or PCS
* within the MMIO-mapped area
*
* Copyright (C) 2023 Maxime Chevallier <maxime.chevallier@bootlin.com>
*/
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/mdio/mdio-regmap.h>
#define DRV_NAME "mdio-regmap"
struct mdio_regmap_priv {
struct regmap *regmap;
u8 valid_addr;
};
static int mdio_regmap_read_c22(struct mii_bus *bus, int addr, int regnum)
{
struct mdio_regmap_priv *ctx = bus->priv;
unsigned int val;
int ret;
if (ctx->valid_addr != addr)
return -ENODEV;
ret = regmap_read(ctx->regmap, regnum, &val);
if (ret < 0)
return ret;
return val;
}
static int mdio_regmap_write_c22(struct mii_bus *bus, int addr, int regnum,
u16 val)
{
struct mdio_regmap_priv *ctx = bus->priv;
if (ctx->valid_addr != addr)
return -ENODEV;
return regmap_write(ctx->regmap, regnum, val);
}
struct mii_bus *devm_mdio_regmap_register(struct device *dev,
const struct mdio_regmap_config *config)
{
struct mdio_regmap_priv *mr;
struct mii_bus *mii;
int rc;
if (!config->parent)
return ERR_PTR(-EINVAL);
mii = devm_mdiobus_alloc_size(config->parent, sizeof(*mr));
if (!mii)
return ERR_PTR(-ENOMEM);
mr = mii->priv;
mr->regmap = config->regmap;
mr->valid_addr = config->valid_addr;
mii->name = DRV_NAME;
strscpy(mii->id, config->name, MII_BUS_ID_SIZE);
mii->parent = config->parent;
mii->read = mdio_regmap_read_c22;
mii->write = mdio_regmap_write_c22;
if (config->autoscan)
mii->phy_mask = ~BIT(config->valid_addr);
else
mii->phy_mask = ~0;
rc = devm_mdiobus_register(dev, mii);
if (rc) {
dev_err(config->parent, "Cannot register MDIO bus![%s] (%d)\n", mii->id, rc);
return ERR_PTR(rc);
}
return mii;
}
EXPORT_SYMBOL_GPL(devm_mdio_regmap_register);
MODULE_DESCRIPTION("MDIO API over regmap");
MODULE_AUTHOR("Maxime Chevallier <maxime.chevallier@bootlin.com>");
MODULE_LICENSE("GPL");
......@@ -33,10 +33,4 @@ config PCS_RZN1_MIIC
on RZ/N1 SoCs. This PCS converts MII to RMII/RGMII or can be set in
pass-through mode for MII.
config PCS_ALTERA_TSE
tristate
help
This module provides helper functions for the Altera Triple Speed
Ethernet SGMII PCS, that can be found on the Intel Socfpga family.
endmenu
......@@ -7,4 +7,3 @@ obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
obj-$(CONFIG_PCS_ALTERA_TSE) += pcs-altera-tse.o
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Bootlin
*
* Maxime Chevallier <maxime.chevallier@bootlin.com>
*/
#include <linux/netdevice.h>
#include <linux/phy.h>
#include <linux/phylink.h>
#include <linux/pcs-altera-tse.h>
/* 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)
{
return container_of(pcs, struct altera_tse_pcs, pcs);
}
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);
}
static void tse_pcs_write(struct altera_tse_pcs *tse_pcs, int regnum,
u16 value)
{
if (tse_pcs->reg_width == 4)
writel(value, tse_pcs->base + regnum * 4);
else
writew(value, tse_pcs->base + regnum * 2);
}
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);
return read_poll_timeout(tse_pcs_read, bmcr, (bmcr & BMCR_RESET),
10, SGMII_PCS_SW_RESET_TIMEOUT, 1,
tse_pcs, MII_BMCR);
}
static int alt_tse_pcs_validate(struct phylink_pcs *pcs,
unsigned long *supported,
const struct phylink_link_state *state)
{
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);
}
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_reset(tse_pcs);
}
static void alt_tse_pcs_get_state(struct phylink_pcs *pcs,
struct phylink_link_state *state)
{
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);
}
static void alt_tse_pcs_an_restart(struct phylink_pcs *pcs)
{
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);
}
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,
};
struct phylink_pcs *alt_tse_pcs_create(struct net_device *ndev,
void __iomem *pcs_base, int reg_width)
{
struct altera_tse_pcs *tse_pcs;
if (reg_width != 4 && reg_width != 2)
return ERR_PTR(-EINVAL);
tse_pcs = devm_kzalloc(&ndev->dev, sizeof(*tse_pcs), GFP_KERNEL);
if (!tse_pcs)
return ERR_PTR(-ENOMEM);
tse_pcs->pcs.ops = &alt_tse_pcs_ops;
tse_pcs->base = pcs_base;
tse_pcs->reg_width = reg_width;
return &tse_pcs->pcs;
}
EXPORT_SYMBOL_GPL(alt_tse_pcs_create);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Altera TSE PCS driver");
MODULE_AUTHOR("Maxime Chevallier <maxime.chevallier@bootlin.com>");
/* SPDX-License-Identifier: GPL-2.0 */
/* Driver for MMIO-Mapped MDIO devices. Some IPs expose internal PHYs or PCS
* within the MMIO-mapped area
*
* Copyright (C) 2023 Maxime Chevallier <maxime.chevallier@bootlin.com>
*/
#ifndef MDIO_REGMAP_H
#define MDIO_REGMAP_H
#include <linux/phy.h>
struct device;
struct regmap;
struct mdio_regmap_config {
struct device *parent;
struct regmap *regmap;
char name[MII_BUS_ID_SIZE];
u8 valid_addr;
bool autoscan;
};
struct mii_bus *devm_mdio_regmap_register(struct device *dev,
const struct mdio_regmap_config *config);
#endif
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022 Bootlin
*
* Maxime Chevallier <maxime.chevallier@bootlin.com>
*/
#ifndef __LINUX_PCS_ALTERA_TSE_H
#define __LINUX_PCS_ALTERA_TSE_H
struct phylink_pcs;
struct net_device;
struct phylink_pcs *alt_tse_pcs_create(struct net_device *ndev,
void __iomem *pcs_base, int reg_width);
#endif /* __LINUX_PCS_ALTERA_TSE_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment