Merge branch 'ftgmac100-batch5-features'

Benjamin Herrenschmidt says:

====================
ftgmac100: Rework batch 5 - Features

This is the third spin of the fifth and last batch of
updates to the ftgmac100 driver.

This contains a few additional "features" such as:

 - Support for ethtool n-way reset
 - Multicast filtering & promisc support
 - Vlan offload
 - netpoll

And a couple of misc bits. This also adds the device-tree binding
documentation.

v2. - Addresses review comments and adds a new patch fixing a
      theorical ordering issue in my new NAPI poll implementation
    - Add a bug fix (Patch 8/9) for a potential ordering issue
      in the new NAPI poll code.

v3. - Rebase on net-next (fix conflict with an unrelated #include
      change series)
    - Update DT bindings better describing accepted phy-mode values
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2017-04-18 14:11:10 -04:00
Родитель f4820661a9 88a286f7e9
Коммит 4116c97689
3 изменённых файлов: 298 добавлений и 12 удалений

Просмотреть файл

@ -0,0 +1,35 @@
* Faraday Technology FTGMAC100 gigabit ethernet controller
Required properties:
- compatible: "faraday,ftgmac100"
Must also contain one of these if used as part of an Aspeed AST2400
or 2500 family SoC as they have some subtle tweaks to the
implementation:
- "aspeed,ast2400-mac"
- "aspeed,ast2500-mac"
- reg: Address and length of the register set for the device
- interrupts: Should contain ethernet controller interrupt
Optional properties:
- phy-mode: See ethernet.txt file in the same directory. If the property is
absent, "rgmii" is assumed. Supported values are "rgmii*" and "rmii" for
aspeed parts. Other (unknown) parts will accept any value.
- use-ncsi: Use the NC-SI stack instead of an MDIO PHY. Currently assumes
rmii (100bT) but kept as a separate property in case NC-SI grows support
for a gigabit link.
- no-hw-checksum: Used to disable HW checksum support. Here for backward
compatibility as the driver now should have correct defaults based on
the SoC.
Example:
mac0: ethernet@1e660000 {
compatible = "aspeed,ast2500-mac", "faraday,ftgmac100";
reg = <0x1e660000 0x180>;
interrupts = <2>;
status = "okay";
use-ncsi;
};

Просмотреть файл

@ -32,6 +32,9 @@
#include <linux/phy.h> #include <linux/phy.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/property.h> #include <linux/property.h>
#include <linux/crc32.h>
#include <linux/if_vlan.h>
#include <linux/of_net.h>
#include <net/ip.h> #include <net/ip.h>
#include <net/ncsi.h> #include <net/ncsi.h>
@ -99,6 +102,15 @@ struct ftgmac100 {
int cur_duplex; int cur_duplex;
bool use_ncsi; bool use_ncsi;
/* Multicast filter settings */
u32 maht0;
u32 maht1;
/* Flow control settings */
bool tx_pause;
bool rx_pause;
bool aneg_pause;
/* Misc */ /* Misc */
bool need_mac_restart; bool need_mac_restart;
bool is_aspeed; bool is_aspeed;
@ -219,6 +231,23 @@ static int ftgmac100_set_mac_addr(struct net_device *dev, void *p)
return 0; return 0;
} }
static void ftgmac100_config_pause(struct ftgmac100 *priv)
{
u32 fcr = FTGMAC100_FCR_PAUSE_TIME(16);
/* Throttle tx queue when receiving pause frames */
if (priv->rx_pause)
fcr |= FTGMAC100_FCR_FC_EN;
/* Enables sending pause frames when the RX queue is past a
* certain threshold.
*/
if (priv->tx_pause)
fcr |= FTGMAC100_FCR_FCTHR_EN;
iowrite32(fcr, priv->base + FTGMAC100_OFFSET_FCR);
}
static void ftgmac100_init_hw(struct ftgmac100 *priv) static void ftgmac100_init_hw(struct ftgmac100 *priv)
{ {
u32 reg, rfifo_sz, tfifo_sz; u32 reg, rfifo_sz, tfifo_sz;
@ -244,6 +273,10 @@ static void ftgmac100_init_hw(struct ftgmac100 *priv)
/* Write MAC address */ /* Write MAC address */
ftgmac100_write_mac_addr(priv, priv->netdev->dev_addr); ftgmac100_write_mac_addr(priv, priv->netdev->dev_addr);
/* Write multicast filter */
iowrite32(priv->maht0, priv->base + FTGMAC100_OFFSET_MAHT0);
iowrite32(priv->maht1, priv->base + FTGMAC100_OFFSET_MAHT1);
/* Configure descriptor sizes and increase burst sizes according /* Configure descriptor sizes and increase burst sizes according
* to values in Aspeed SDK. The FIFO arbitration is enabled and * to values in Aspeed SDK. The FIFO arbitration is enabled and
* the thresholds set based on the recommended values in the * the thresholds set based on the recommended values in the
@ -297,6 +330,16 @@ static void ftgmac100_start_hw(struct ftgmac100 *priv)
/* Add other bits as needed */ /* Add other bits as needed */
if (priv->cur_duplex == DUPLEX_FULL) if (priv->cur_duplex == DUPLEX_FULL)
maccr |= FTGMAC100_MACCR_FULLDUP; maccr |= FTGMAC100_MACCR_FULLDUP;
if (priv->netdev->flags & IFF_PROMISC)
maccr |= FTGMAC100_MACCR_RX_ALL;
if (priv->netdev->flags & IFF_ALLMULTI)
maccr |= FTGMAC100_MACCR_RX_MULTIPKT;
else if (netdev_mc_count(priv->netdev))
maccr |= FTGMAC100_MACCR_HT_MULTI_EN;
/* Vlan filtering enabled */
if (priv->netdev->features & NETIF_F_HW_VLAN_CTAG_RX)
maccr |= FTGMAC100_MACCR_RM_VLAN;
/* Hit the HW */ /* Hit the HW */
iowrite32(maccr, priv->base + FTGMAC100_OFFSET_MACCR); iowrite32(maccr, priv->base + FTGMAC100_OFFSET_MACCR);
@ -307,6 +350,42 @@ static void ftgmac100_stop_hw(struct ftgmac100 *priv)
iowrite32(0, priv->base + FTGMAC100_OFFSET_MACCR); iowrite32(0, priv->base + FTGMAC100_OFFSET_MACCR);
} }
static void ftgmac100_calc_mc_hash(struct ftgmac100 *priv)
{
struct netdev_hw_addr *ha;
priv->maht1 = 0;
priv->maht0 = 0;
netdev_for_each_mc_addr(ha, priv->netdev) {
u32 crc_val = ether_crc_le(ETH_ALEN, ha->addr);
crc_val = (~(crc_val >> 2)) & 0x3f;
if (crc_val >= 32)
priv->maht1 |= 1ul << (crc_val - 32);
else
priv->maht0 |= 1ul << (crc_val);
}
}
static void ftgmac100_set_rx_mode(struct net_device *netdev)
{
struct ftgmac100 *priv = netdev_priv(netdev);
/* Setup the hash filter */
ftgmac100_calc_mc_hash(priv);
/* Interface down ? that's all there is to do */
if (!netif_running(netdev))
return;
/* Update the HW */
iowrite32(priv->maht0, priv->base + FTGMAC100_OFFSET_MAHT0);
iowrite32(priv->maht1, priv->base + FTGMAC100_OFFSET_MAHT1);
/* Reconfigure MACCR */
ftgmac100_start_hw(priv);
}
static int ftgmac100_alloc_rx_buf(struct ftgmac100 *priv, unsigned int entry, static int ftgmac100_alloc_rx_buf(struct ftgmac100 *priv, unsigned int entry,
struct ftgmac100_rxdes *rxdes, gfp_t gfp) struct ftgmac100_rxdes *rxdes, gfp_t gfp)
{ {
@ -457,6 +536,12 @@ static bool ftgmac100_rx_packet(struct ftgmac100 *priv, int *processed)
/* Transfer received size to skb */ /* Transfer received size to skb */
skb_put(skb, size); skb_put(skb, size);
/* Extract vlan tag */
if ((netdev->features & NETIF_F_HW_VLAN_CTAG_RX) &&
(csum_vlan & FTGMAC100_RXDES1_VLANTAG_AVAIL))
__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
csum_vlan & 0xffff);
/* Tear down DMA mapping, do necessary cache management */ /* Tear down DMA mapping, do necessary cache management */
map = le32_to_cpu(rxdes->rxdes3); map = le32_to_cpu(rxdes->rxdes3);
@ -681,6 +766,13 @@ static int ftgmac100_hard_start_xmit(struct sk_buff *skb,
if (skb->ip_summed == CHECKSUM_PARTIAL && if (skb->ip_summed == CHECKSUM_PARTIAL &&
!ftgmac100_prep_tx_csum(skb, &csum_vlan)) !ftgmac100_prep_tx_csum(skb, &csum_vlan))
goto drop; goto drop;
/* Add VLAN tag */
if (skb_vlan_tag_present(skb)) {
csum_vlan |= FTGMAC100_TXDES1_INS_VLANTAG;
csum_vlan |= skb_vlan_tag_get(skb) & 0xffff;
}
txdes->txdes1 = cpu_to_le32(csum_vlan); txdes->txdes1 = cpu_to_le32(csum_vlan);
/* Next descriptor */ /* Next descriptor */
@ -912,6 +1004,7 @@ static void ftgmac100_adjust_link(struct net_device *netdev)
{ {
struct ftgmac100 *priv = netdev_priv(netdev); struct ftgmac100 *priv = netdev_priv(netdev);
struct phy_device *phydev = netdev->phydev; struct phy_device *phydev = netdev->phydev;
bool tx_pause, rx_pause;
int new_speed; int new_speed;
/* We store "no link" as speed 0 */ /* We store "no link" as speed 0 */
@ -920,8 +1013,21 @@ static void ftgmac100_adjust_link(struct net_device *netdev)
else else
new_speed = phydev->speed; new_speed = phydev->speed;
/* Grab pause settings from PHY if configured to do so */
if (priv->aneg_pause) {
rx_pause = tx_pause = phydev->pause;
if (phydev->asym_pause)
tx_pause = !rx_pause;
} else {
rx_pause = priv->rx_pause;
tx_pause = priv->tx_pause;
}
/* Link hasn't changed, do nothing */
if (phydev->speed == priv->cur_speed && if (phydev->speed == priv->cur_speed &&
phydev->duplex == priv->cur_duplex) phydev->duplex == priv->cur_duplex &&
rx_pause == priv->rx_pause &&
tx_pause == priv->tx_pause)
return; return;
/* Print status if we have a link or we had one and just lost it, /* Print status if we have a link or we had one and just lost it,
@ -932,6 +1038,8 @@ static void ftgmac100_adjust_link(struct net_device *netdev)
priv->cur_speed = new_speed; priv->cur_speed = new_speed;
priv->cur_duplex = phydev->duplex; priv->cur_duplex = phydev->duplex;
priv->rx_pause = rx_pause;
priv->tx_pause = tx_pause;
/* Link is down, do nothing else */ /* Link is down, do nothing else */
if (!new_speed) if (!new_speed)
@ -944,7 +1052,7 @@ static void ftgmac100_adjust_link(struct net_device *netdev)
schedule_work(&priv->reset_task); schedule_work(&priv->reset_task);
} }
static int ftgmac100_mii_probe(struct ftgmac100 *priv) static int ftgmac100_mii_probe(struct ftgmac100 *priv, phy_interface_t intf)
{ {
struct net_device *netdev = priv->netdev; struct net_device *netdev = priv->netdev;
struct phy_device *phydev; struct phy_device *phydev;
@ -956,13 +1064,22 @@ static int ftgmac100_mii_probe(struct ftgmac100 *priv)
} }
phydev = phy_connect(netdev, phydev_name(phydev), phydev = phy_connect(netdev, phydev_name(phydev),
&ftgmac100_adjust_link, PHY_INTERFACE_MODE_GMII); &ftgmac100_adjust_link, intf);
if (IS_ERR(phydev)) { if (IS_ERR(phydev)) {
netdev_err(netdev, "%s: Could not attach to PHY\n", netdev->name); netdev_err(netdev, "%s: Could not attach to PHY\n", netdev->name);
return PTR_ERR(phydev); return PTR_ERR(phydev);
} }
/* Indicate that we support PAUSE frames (see comment in
* Documentation/networking/phy.txt)
*/
phydev->supported |= SUPPORTED_Pause | SUPPORTED_Asym_Pause;
phydev->advertising = phydev->supported;
/* Display what we found */
phy_attached_info(phydev);
return 0; return 0;
} }
@ -1045,13 +1162,6 @@ static void ftgmac100_get_drvinfo(struct net_device *netdev,
strlcpy(info->bus_info, dev_name(&netdev->dev), sizeof(info->bus_info)); strlcpy(info->bus_info, dev_name(&netdev->dev), sizeof(info->bus_info));
} }
static int ftgmac100_nway_reset(struct net_device *ndev)
{
if (!ndev->phydev)
return -ENXIO;
return phy_start_aneg(ndev->phydev);
}
static void ftgmac100_get_ringparam(struct net_device *netdev, static void ftgmac100_get_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ering) struct ethtool_ringparam *ering)
{ {
@ -1085,13 +1195,58 @@ static int ftgmac100_set_ringparam(struct net_device *netdev,
return 0; return 0;
} }
static void ftgmac100_get_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct ftgmac100 *priv = netdev_priv(netdev);
pause->autoneg = priv->aneg_pause;
pause->tx_pause = priv->tx_pause;
pause->rx_pause = priv->rx_pause;
}
static int ftgmac100_set_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct ftgmac100 *priv = netdev_priv(netdev);
struct phy_device *phydev = netdev->phydev;
priv->aneg_pause = pause->autoneg;
priv->tx_pause = pause->tx_pause;
priv->rx_pause = pause->rx_pause;
if (phydev) {
phydev->advertising &= ~ADVERTISED_Pause;
phydev->advertising &= ~ADVERTISED_Asym_Pause;
if (pause->rx_pause) {
phydev->advertising |= ADVERTISED_Pause;
phydev->advertising |= ADVERTISED_Asym_Pause;
}
if (pause->tx_pause)
phydev->advertising ^= ADVERTISED_Asym_Pause;
}
if (netif_running(netdev)) {
if (phydev && priv->aneg_pause)
phy_start_aneg(phydev);
else
ftgmac100_config_pause(priv);
}
return 0;
}
static const struct ethtool_ops ftgmac100_ethtool_ops = { static const struct ethtool_ops ftgmac100_ethtool_ops = {
.get_drvinfo = ftgmac100_get_drvinfo, .get_drvinfo = ftgmac100_get_drvinfo,
.get_link = ethtool_op_get_link, .get_link = ethtool_op_get_link,
.get_link_ksettings = phy_ethtool_get_link_ksettings, .get_link_ksettings = phy_ethtool_get_link_ksettings,
.set_link_ksettings = phy_ethtool_set_link_ksettings, .set_link_ksettings = phy_ethtool_set_link_ksettings,
.nway_reset = phy_ethtool_nway_reset,
.get_ringparam = ftgmac100_get_ringparam, .get_ringparam = ftgmac100_get_ringparam,
.set_ringparam = ftgmac100_set_ringparam, .set_ringparam = ftgmac100_set_ringparam,
.get_pauseparam = ftgmac100_get_pauseparam,
.set_pauseparam = ftgmac100_set_pauseparam,
}; };
static irqreturn_t ftgmac100_interrupt(int irq, void *dev_id) static irqreturn_t ftgmac100_interrupt(int irq, void *dev_id)
@ -1194,6 +1349,13 @@ static int ftgmac100_poll(struct napi_struct *napi, int budget)
*/ */
iowrite32(FTGMAC100_INT_RXTX, iowrite32(FTGMAC100_INT_RXTX,
priv->base + FTGMAC100_OFFSET_ISR); priv->base + FTGMAC100_OFFSET_ISR);
/* Push the above (and provides a barrier vs. subsequent
* reads of the descriptor).
*/
ioread32(priv->base + FTGMAC100_OFFSET_ISR);
/* Check RX and TX descriptors for more work to do */
if (ftgmac100_check_rx(priv) || if (ftgmac100_check_rx(priv) ||
ftgmac100_tx_buf_cleanable(priv)) ftgmac100_tx_buf_cleanable(priv))
return budget; return budget;
@ -1223,6 +1385,7 @@ static int ftgmac100_init_all(struct ftgmac100 *priv, bool ignore_alloc_err)
/* Reinit and restart HW */ /* Reinit and restart HW */
ftgmac100_init_hw(priv); ftgmac100_init_hw(priv);
ftgmac100_config_pause(priv);
ftgmac100_start_hw(priv); ftgmac100_start_hw(priv);
/* Re-enable the device */ /* Re-enable the device */
@ -1412,6 +1575,41 @@ static void ftgmac100_tx_timeout(struct net_device *netdev)
schedule_work(&priv->reset_task); schedule_work(&priv->reset_task);
} }
static int ftgmac100_set_features(struct net_device *netdev,
netdev_features_t features)
{
struct ftgmac100 *priv = netdev_priv(netdev);
netdev_features_t changed = netdev->features ^ features;
if (!netif_running(netdev))
return 0;
/* Update the vlan filtering bit */
if (changed & NETIF_F_HW_VLAN_CTAG_RX) {
u32 maccr;
maccr = ioread32(priv->base + FTGMAC100_OFFSET_MACCR);
if (priv->netdev->features & NETIF_F_HW_VLAN_CTAG_RX)
maccr |= FTGMAC100_MACCR_RM_VLAN;
else
maccr &= ~FTGMAC100_MACCR_RM_VLAN;
iowrite32(maccr, priv->base + FTGMAC100_OFFSET_MACCR);
}
return 0;
}
#ifdef CONFIG_NET_POLL_CONTROLLER
static void ftgmac100_poll_controller(struct net_device *netdev)
{
unsigned long flags;
local_irq_save(flags);
ftgmac100_interrupt(netdev->irq, netdev);
local_irq_restore(flags);
}
#endif
static const struct net_device_ops ftgmac100_netdev_ops = { static const struct net_device_ops ftgmac100_netdev_ops = {
.ndo_open = ftgmac100_open, .ndo_open = ftgmac100_open,
.ndo_stop = ftgmac100_stop, .ndo_stop = ftgmac100_stop,
@ -1420,12 +1618,19 @@ static const struct net_device_ops ftgmac100_netdev_ops = {
.ndo_validate_addr = eth_validate_addr, .ndo_validate_addr = eth_validate_addr,
.ndo_do_ioctl = ftgmac100_do_ioctl, .ndo_do_ioctl = ftgmac100_do_ioctl,
.ndo_tx_timeout = ftgmac100_tx_timeout, .ndo_tx_timeout = ftgmac100_tx_timeout,
.ndo_set_rx_mode = ftgmac100_set_rx_mode,
.ndo_set_features = ftgmac100_set_features,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = ftgmac100_poll_controller,
#endif
}; };
static int ftgmac100_setup_mdio(struct net_device *netdev) static int ftgmac100_setup_mdio(struct net_device *netdev)
{ {
struct ftgmac100 *priv = netdev_priv(netdev); struct ftgmac100 *priv = netdev_priv(netdev);
struct platform_device *pdev = to_platform_device(priv->dev); struct platform_device *pdev = to_platform_device(priv->dev);
int phy_intf = PHY_INTERFACE_MODE_RGMII;
struct device_node *np = pdev->dev.of_node;
int i, err = 0; int i, err = 0;
u32 reg; u32 reg;
@ -1441,6 +1646,39 @@ static int ftgmac100_setup_mdio(struct net_device *netdev)
iowrite32(reg, priv->base + FTGMAC100_OFFSET_REVR); iowrite32(reg, priv->base + FTGMAC100_OFFSET_REVR);
}; };
/* Get PHY mode from device-tree */
if (np) {
/* Default to RGMII. It's a gigabit part after all */
phy_intf = of_get_phy_mode(np);
if (phy_intf < 0)
phy_intf = PHY_INTERFACE_MODE_RGMII;
/* Aspeed only supports these. I don't know about other IP
* block vendors so I'm going to just let them through for
* now. Note that this is only a warning if for some obscure
* reason the DT really means to lie about it or it's a newer
* part we don't know about.
*
* On the Aspeed SoC there are additionally straps and SCU
* control bits that could tell us what the interface is
* (or allow us to configure it while the IP block is held
* in reset). For now I chose to keep this driver away from
* those SoC specific bits and assume the device-tree is
* right and the SCU has been configured properly by pinmux
* or the firmware.
*/
if (priv->is_aspeed &&
phy_intf != PHY_INTERFACE_MODE_RMII &&
phy_intf != PHY_INTERFACE_MODE_RGMII &&
phy_intf != PHY_INTERFACE_MODE_RGMII_ID &&
phy_intf != PHY_INTERFACE_MODE_RGMII_RXID &&
phy_intf != PHY_INTERFACE_MODE_RGMII_TXID) {
netdev_warn(netdev,
"Unsupported PHY mode %s !\n",
phy_modes(phy_intf));
}
}
priv->mii_bus->name = "ftgmac100_mdio"; priv->mii_bus->name = "ftgmac100_mdio";
snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "%s-%d", snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "%s-%d",
pdev->name, pdev->id); pdev->name, pdev->id);
@ -1457,7 +1695,7 @@ static int ftgmac100_setup_mdio(struct net_device *netdev)
goto err_register_mdiobus; goto err_register_mdiobus;
} }
err = ftgmac100_mii_probe(priv); err = ftgmac100_mii_probe(priv, phy_intf);
if (err) { if (err) {
dev_err(priv->dev, "MII Probe failed!\n"); dev_err(priv->dev, "MII Probe failed!\n");
goto err_mii_probe; goto err_mii_probe;
@ -1552,6 +1790,11 @@ static int ftgmac100_probe(struct platform_device *pdev)
netdev->irq = irq; netdev->irq = irq;
/* Enable pause */
priv->tx_pause = true;
priv->rx_pause = true;
priv->aneg_pause = true;
/* MAC address from chip or random one */ /* MAC address from chip or random one */
ftgmac100_initial_mac(priv); ftgmac100_initial_mac(priv);
@ -1590,7 +1833,8 @@ static int ftgmac100_probe(struct platform_device *pdev)
/* Base feature set */ /* Base feature set */
netdev->hw_features = NETIF_F_RXCSUM | NETIF_F_HW_CSUM | netdev->hw_features = NETIF_F_RXCSUM | NETIF_F_HW_CSUM |
NETIF_F_GRO | NETIF_F_SG; NETIF_F_GRO | NETIF_F_SG | NETIF_F_HW_VLAN_CTAG_RX |
NETIF_F_HW_VLAN_CTAG_TX;
/* AST2400 doesn't have working HW checksum generation */ /* AST2400 doesn't have working HW checksum generation */
if (np && (of_device_is_compatible(np, "aspeed,ast2400-mac"))) if (np && (of_device_is_compatible(np, "aspeed,ast2400-mac")))

Просмотреть файл

@ -198,6 +198,13 @@
#define FTGMAC100_PHYDATA_MIIWDATA(x) ((x) & 0xffff) #define FTGMAC100_PHYDATA_MIIWDATA(x) ((x) & 0xffff)
#define FTGMAC100_PHYDATA_MIIRDATA(phydata) (((phydata) >> 16) & 0xffff) #define FTGMAC100_PHYDATA_MIIRDATA(phydata) (((phydata) >> 16) & 0xffff)
/*
* Flow control register
*/
#define FTGMAC100_FCR_FC_EN (1 << 0)
#define FTGMAC100_FCR_FCTHR_EN (1 << 2)
#define FTGMAC100_FCR_PAUSE_TIME(x) (((x) & 0xffff) << 16)
/* /*
* Transmit descriptor, aligned to 16 bytes * Transmit descriptor, aligned to 16 bytes
*/ */