Commit e3cbf478 authored by John Crispin's avatar John Crispin Committed by Greg Kroah-Hartman

staging: mt7621-eth: add the drivers core files

Original comment:

This patch adds the main chunk of the driver. The ethernet core is used in
all of the Mediatek/Ralink Wireless SoCs. Over the years we have seen
various changes to
* the register layout
* the type of ports (single/dual gbit, internal FE/Gbit switch)
* dma engine (PDMA/QDMA)

and new offloading features were added, such as
* checksum
* VLAN TX/RX
* TSO
* LRO

The core functionality has however remained the same allowing us to use
the same code for all SoCs.

The abstraction for the various SoCs uses the typical ops struct pattern
which allows us to extend or override the core functionality depending on
which SoC we are on. The code to bring up the switches and external ports
has also been split into separate files.

There are 2 types of DMA engine, PDMA and the newer QDMA. PDMA uses a
typical ring buffer while QDMA uses a linked list. Unfortunatley we have
the MT7621 which has a few silicon issues. Due to these issues we need to
PDMA for RX and QDMA for TX. All SoCs newer than the MT7621 can can run on
QDMA exclusively.

Most of the SoCs have a switch frontend. Older silicon has a so called ESW
(Ethernet Switch) while newer cores have a GSW (Gigabit switch).
Additionally there is a MDIO bus that can be used to talk to PHYs. In these
cases one switch port get changed into a normal MAC port.

Some SoCs have a dual MAC, we currently only support this on MT7623.

NeilBrown:
 - removed everything not closely related to mt7621, as that is all I
   can test
 - converted ethtool.c to new ethtool_link_ksettings interfaces.
   Doesn't work yet.
 - updated some phydev interface use: e.g. dev_name() -> phydev_name()
 - updated mdio to use mdiobus_get_phy()
 - added some missing export_symbols
 - updated get_stats64 interface
 - TX_DMA_FPORT and TX_DMA_TSO to tx dma descriptor
 - range checked RX_DMA_FPORT in rx dma descriptor
 - tell hardware what mac address was chosen
 - fixed MT7620_GDMA1_FWD_CFG which was using wrong value
Signed-off-by: default avatarJohn Crispin <blogic@openwrt.org>
Signed-off-by: default avatarFelix Fietkau <nbd@openwrt.org>
Signed-off-by: default avatarMichael Lee <igvtee@gmail.com>
Signed-off-by: default avatarNeilBrown <neil@brown.name>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent eb4afe30
- verify devicetree documentation is consistent with code
- fix ethtool - currently doesn't return valid data.
- general code review and clean up
- add support for second MAC on mt7621
Cc: NeilBrown <neil@brown.name>
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Copyright (C) 2009-2016 John Crispin <blogic@openwrt.org>
* Copyright (C) 2009-2016 Felix Fietkau <nbd@openwrt.org>
* Copyright (C) 2013-2016 Michael Lee <igvtee@gmail.com>
*/
#include "mtk_eth_soc.h"
static const char mtk_gdma_str[][ETH_GSTRING_LEN] = {
#define _FE(x...) # x,
MTK_STAT_REG_DECLARE
#undef _FE
};
static int mtk_get_link_ksettings(struct net_device *dev,
struct ethtool_link_ksettings *cmd)
{
struct mtk_mac *mac = netdev_priv(dev);
int err;
if (!mac->phy_dev)
return -ENODEV;
if (mac->phy_flags == MTK_PHY_FLAG_ATTACH) {
err = phy_read_status(mac->phy_dev);
if (err)
return -ENODEV;
}
phy_ethtool_ksettings_get(mac->phy_dev, cmd);
return 0;
}
static int mtk_set_link_ksettings(struct net_device *dev,
const struct ethtool_link_ksettings *cmd)
{
struct mtk_mac *mac = netdev_priv(dev);
if (!mac->phy_dev)
return -ENODEV;
if (cmd->base.phy_address != mac->phy_dev->mdio.addr) {
if (mac->hw->phy->phy_node[cmd->base.phy_address]) {
mac->phy_dev = mac->hw->phy->phy[cmd->base.phy_address];
mac->phy_flags = MTK_PHY_FLAG_PORT;
} else if (mac->hw->mii_bus) {
mac->phy_dev = mdiobus_get_phy(mac->hw->mii_bus, cmd->base.phy_address);
if (!mac->phy_dev)
return -ENODEV;
mac->phy_flags = MTK_PHY_FLAG_ATTACH;
} else {
return -ENODEV;
}
}
return phy_ethtool_ksettings_set(mac->phy_dev, cmd);
}
static void mtk_get_drvinfo(struct net_device *dev,
struct ethtool_drvinfo *info)
{
struct mtk_mac *mac = netdev_priv(dev);
struct mtk_soc_data *soc = mac->hw->soc;
strlcpy(info->driver, mac->hw->dev->driver->name, sizeof(info->driver));
strlcpy(info->bus_info, dev_name(mac->hw->dev), sizeof(info->bus_info));
if (soc->reg_table[MTK_REG_MTK_COUNTER_BASE])
info->n_stats = ARRAY_SIZE(mtk_gdma_str);
}
static u32 mtk_get_msglevel(struct net_device *dev)
{
struct mtk_mac *mac = netdev_priv(dev);
return mac->hw->msg_enable;
}
static void mtk_set_msglevel(struct net_device *dev, u32 value)
{
struct mtk_mac *mac = netdev_priv(dev);
mac->hw->msg_enable = value;
}
static int mtk_nway_reset(struct net_device *dev)
{
struct mtk_mac *mac = netdev_priv(dev);
if (!mac->phy_dev)
return -EOPNOTSUPP;
return genphy_restart_aneg(mac->phy_dev);
}
static u32 mtk_get_link(struct net_device *dev)
{
struct mtk_mac *mac = netdev_priv(dev);
int err;
if (!mac->phy_dev)
goto out_get_link;
if (mac->phy_flags == MTK_PHY_FLAG_ATTACH) {
err = genphy_update_link(mac->phy_dev);
if (err)
goto out_get_link;
}
return mac->phy_dev->link;
out_get_link:
return ethtool_op_get_link(dev);
}
static int mtk_set_ringparam(struct net_device *dev,
struct ethtool_ringparam *ring)
{
struct mtk_mac *mac = netdev_priv(dev);
if ((ring->tx_pending < 2) ||
(ring->rx_pending < 2) ||
(ring->rx_pending > mac->hw->soc->dma_ring_size) ||
(ring->tx_pending > mac->hw->soc->dma_ring_size))
return -EINVAL;
dev->netdev_ops->ndo_stop(dev);
mac->hw->tx_ring.tx_ring_size = BIT(fls(ring->tx_pending) - 1);
mac->hw->rx_ring[0].rx_ring_size = BIT(fls(ring->rx_pending) - 1);
return dev->netdev_ops->ndo_open(dev);
}
static void mtk_get_ringparam(struct net_device *dev,
struct ethtool_ringparam *ring)
{
struct mtk_mac *mac = netdev_priv(dev);
ring->rx_max_pending = mac->hw->soc->dma_ring_size;
ring->tx_max_pending = mac->hw->soc->dma_ring_size;
ring->rx_pending = mac->hw->rx_ring[0].rx_ring_size;
ring->tx_pending = mac->hw->tx_ring.tx_ring_size;
}
static void mtk_get_strings(struct net_device *dev, u32 stringset, u8 *data)
{
switch (stringset) {
case ETH_SS_STATS:
memcpy(data, *mtk_gdma_str, sizeof(mtk_gdma_str));
break;
}
}
static int mtk_get_sset_count(struct net_device *dev, int sset)
{
switch (sset) {
case ETH_SS_STATS:
return ARRAY_SIZE(mtk_gdma_str);
default:
return -EOPNOTSUPP;
}
}
static void mtk_get_ethtool_stats(struct net_device *dev,
struct ethtool_stats *stats, u64 *data)
{
struct mtk_mac *mac = netdev_priv(dev);
struct mtk_hw_stats *hwstats = mac->hw_stats;
u64 *data_src, *data_dst;
unsigned int start;
int i;
if (netif_running(dev) && netif_device_present(dev)) {
if (spin_trylock(&hwstats->stats_lock)) {
mtk_stats_update_mac(mac);
spin_unlock(&hwstats->stats_lock);
}
}
do {
data_src = &hwstats->tx_bytes;
data_dst = data;
start = u64_stats_fetch_begin_irq(&hwstats->syncp);
for (i = 0; i < ARRAY_SIZE(mtk_gdma_str); i++)
*data_dst++ = *data_src++;
} while (u64_stats_fetch_retry_irq(&hwstats->syncp, start));
}
static struct ethtool_ops mtk_ethtool_ops = {
.get_link_ksettings = mtk_get_link_ksettings,
.set_link_ksettings = mtk_set_link_ksettings,
.get_drvinfo = mtk_get_drvinfo,
.get_msglevel = mtk_get_msglevel,
.set_msglevel = mtk_set_msglevel,
.nway_reset = mtk_nway_reset,
.get_link = mtk_get_link,
.set_ringparam = mtk_set_ringparam,
.get_ringparam = mtk_get_ringparam,
};
void mtk_set_ethtool_ops(struct net_device *netdev)
{
struct mtk_mac *mac = netdev_priv(netdev);
struct mtk_soc_data *soc = mac->hw->soc;
if (soc->reg_table[MTK_REG_MTK_COUNTER_BASE]) {
mtk_ethtool_ops.get_strings = mtk_get_strings;
mtk_ethtool_ops.get_sset_count = mtk_get_sset_count;
mtk_ethtool_ops.get_ethtool_stats = mtk_get_ethtool_stats;
}
netdev->ethtool_ops = &mtk_ethtool_ops;
}
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Copyright (C) 2009-2016 John Crispin <blogic@openwrt.org>
* Copyright (C) 2009-2016 Felix Fietkau <nbd@openwrt.org>
* Copyright (C) 2013-2016 Michael Lee <igvtee@gmail.com>
*/
#ifndef MTK_ETHTOOL_H
#define MTK_ETHTOOL_H
#include <linux/ethtool.h>
void mtk_set_ethtool_ops(struct net_device *netdev);
#endif /* MTK_ETHTOOL_H */
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License
*
* Copyright (C) 2009-2016 John Crispin <blogic@openwrt.org>
* Copyright (C) 2009-2016 Felix Fietkau <nbd@openwrt.org>
* Copyright (C) 2013-2016 Michael Lee <igvtee@gmail.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/phy.h>
#include <linux/of_net.h>
#include <linux/of_mdio.h>
#include "mtk_eth_soc.h"
#include "mdio.h"
static int mtk_mdio_reset(struct mii_bus *bus)
{
/* TODO */
return 0;
}
static void mtk_phy_link_adjust(struct net_device *dev)
{
struct mtk_eth *eth = netdev_priv(dev);
unsigned long flags;
int i;
spin_lock_irqsave(&eth->phy->lock, flags);
for (i = 0; i < 8; i++) {
if (eth->phy->phy_node[i]) {
struct phy_device *phydev = eth->phy->phy[i];
int status_change = 0;
if (phydev->link)
if (eth->phy->duplex[i] != phydev->duplex ||
eth->phy->speed[i] != phydev->speed)
status_change = 1;
if (phydev->link != eth->link[i])
status_change = 1;
switch (phydev->speed) {
case SPEED_1000:
case SPEED_100:
case SPEED_10:
eth->link[i] = phydev->link;
eth->phy->duplex[i] = phydev->duplex;
eth->phy->speed[i] = phydev->speed;
if (status_change &&
eth->soc->mdio_adjust_link)
eth->soc->mdio_adjust_link(eth, i);
break;
}
}
}
}
int mtk_connect_phy_node(struct mtk_eth *eth, struct mtk_mac *mac,
struct device_node *phy_node)
{
const __be32 *_port = NULL;
struct phy_device *phydev;
int phy_mode, port;
_port = of_get_property(phy_node, "reg", NULL);
if (!_port || (be32_to_cpu(*_port) >= 0x20)) {
pr_err("%s: invalid port id\n", phy_node->name);
return -EINVAL;
}
port = be32_to_cpu(*_port);
phy_mode = of_get_phy_mode(phy_node);
if (phy_mode < 0) {
dev_err(eth->dev, "incorrect phy-mode %d\n", phy_mode);
eth->phy->phy_node[port] = NULL;
return -EINVAL;
}
phydev = of_phy_connect(eth->netdev[mac->id], phy_node,
mtk_phy_link_adjust, 0, phy_mode);
if (IS_ERR(phydev)) {
dev_err(eth->dev, "could not connect to PHY\n");
eth->phy->phy_node[port] = NULL;
return PTR_ERR(phydev);
}
phydev->supported &= PHY_GBIT_FEATURES;
phydev->advertising = phydev->supported;
dev_info(eth->dev,
"connected port %d to PHY at %s [uid=%08x, driver=%s]\n",
port, phydev_name(phydev), phydev->phy_id,
phydev->drv->name);
eth->phy->phy[port] = phydev;
eth->link[port] = 0;
return 0;
}
static void phy_init(struct mtk_eth *eth, struct mtk_mac *mac,
struct phy_device *phy)
{
phy_attach(eth->netdev[mac->id], phydev_name(phy),
PHY_INTERFACE_MODE_MII);
phy->autoneg = AUTONEG_ENABLE;
phy->speed = 0;
phy->duplex = 0;
phy->supported &= PHY_BASIC_FEATURES;
phy->advertising = phy->supported | ADVERTISED_Autoneg;
phy_start_aneg(phy);
}
static int mtk_phy_connect(struct mtk_mac *mac)
{
struct mtk_eth *eth = mac->hw;
int i;
for (i = 0; i < 8; i++) {
if (eth->phy->phy_node[i]) {
if (!mac->phy_dev) {
mac->phy_dev = eth->phy->phy[i];
mac->phy_flags = MTK_PHY_FLAG_PORT;
}
} else if (eth->mii_bus) {
struct phy_device *phy;
phy = mdiobus_get_phy(eth->mii_bus, i);
if (phy) {
phy_init(eth, mac, phy);
if (!mac->phy_dev) {
mac->phy_dev = phy;
mac->phy_flags = MTK_PHY_FLAG_ATTACH;
}
}
}
}
return 0;
}
static void mtk_phy_disconnect(struct mtk_mac *mac)
{
struct mtk_eth *eth = mac->hw;
unsigned long flags;
int i;
for (i = 0; i < 8; i++)
if (eth->phy->phy_fixed[i]) {
spin_lock_irqsave(&eth->phy->lock, flags);
eth->link[i] = 0;
if (eth->soc->mdio_adjust_link)
eth->soc->mdio_adjust_link(eth, i);
spin_unlock_irqrestore(&eth->phy->lock, flags);
} else if (eth->phy->phy[i]) {
phy_disconnect(eth->phy->phy[i]);
} else if (eth->mii_bus) {
struct phy_device *phy = mdiobus_get_phy(eth->mii_bus, i);
if (phy)
phy_detach(phy);
}
}
static void mtk_phy_start(struct mtk_mac *mac)
{
struct mtk_eth *eth = mac->hw;
unsigned long flags;
int i;
for (i = 0; i < 8; i++) {
if (eth->phy->phy_fixed[i]) {
spin_lock_irqsave(&eth->phy->lock, flags);
eth->link[i] = 1;
if (eth->soc->mdio_adjust_link)
eth->soc->mdio_adjust_link(eth, i);
spin_unlock_irqrestore(&eth->phy->lock, flags);
} else if (eth->phy->phy[i]) {
phy_start(eth->phy->phy[i]);
}
}
}
static void mtk_phy_stop(struct mtk_mac *mac)
{
struct mtk_eth *eth = mac->hw;
unsigned long flags;
int i;
for (i = 0; i < 8; i++)
if (eth->phy->phy_fixed[i]) {
spin_lock_irqsave(&eth->phy->lock, flags);
eth->link[i] = 0;
if (eth->soc->mdio_adjust_link)
eth->soc->mdio_adjust_link(eth, i);
spin_unlock_irqrestore(&eth->phy->lock, flags);
} else if (eth->phy->phy[i]) {
phy_stop(eth->phy->phy[i]);
}
}
static struct mtk_phy phy_ralink = {
.connect = mtk_phy_connect,
.disconnect = mtk_phy_disconnect,
.start = mtk_phy_start,
.stop = mtk_phy_stop,
};
int mtk_mdio_init(struct mtk_eth *eth)
{
struct device_node *mii_np;
int err;
if (!eth->soc->mdio_read || !eth->soc->mdio_write)
return 0;
spin_lock_init(&phy_ralink.lock);
eth->phy = &phy_ralink;
mii_np = of_get_child_by_name(eth->dev->of_node, "mdio-bus");
if (!mii_np) {
dev_err(eth->dev, "no %s child node found", "mdio-bus");
return -ENODEV;
}
if (!of_device_is_available(mii_np)) {
err = 0;
goto err_put_node;
}
eth->mii_bus = mdiobus_alloc();
if (!eth->mii_bus) {
err = -ENOMEM;
goto err_put_node;
}
eth->mii_bus->name = "mdio";
eth->mii_bus->read = eth->soc->mdio_read;
eth->mii_bus->write = eth->soc->mdio_write;
eth->mii_bus->reset = mtk_mdio_reset;
eth->mii_bus->priv = eth;
eth->mii_bus->parent = eth->dev;
snprintf(eth->mii_bus->id, MII_BUS_ID_SIZE, "%s", mii_np->name);
err = of_mdiobus_register(eth->mii_bus, mii_np);
if (err)
goto err_free_bus;
return 0;
err_free_bus:
kfree(eth->mii_bus);
err_put_node:
of_node_put(mii_np);
eth->mii_bus = NULL;
return err;
}
void mtk_mdio_cleanup(struct mtk_eth *eth)
{
if (!eth->mii_bus)
return;
mdiobus_unregister(eth->mii_bus);
of_node_put(eth->mii_bus->dev.of_node);
kfree(eth->mii_bus);
}
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Copyright (C) 2009-2016 John Crispin <blogic@openwrt.org>
* Copyright (C) 2009-2016 Felix Fietkau <nbd@openwrt.org>
* Copyright (C) 2013-2016 Michael Lee <igvtee@gmail.com>
*/
#ifndef _RALINK_MDIO_H__
#define _RALINK_MDIO_H__
#ifdef CONFIG_NET_MEDIATEK_MDIO
int mtk_mdio_init(struct mtk_eth *eth);
void mtk_mdio_cleanup(struct mtk_eth *eth);
int mtk_connect_phy_node(struct mtk_eth *eth, struct mtk_mac *mac,
struct device_node *phy_node);
#else
static inline int mtk_mdio_init(struct mtk_eth *eth) { return 0; }
static inline void mtk_mdio_cleanup(struct mtk_eth *eth) {}
#endif
#endif
This diff is collapsed.
This diff is collapsed.
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