linux-stable/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
Michael Walle e6fa930f73 net: lan966x: hardcode the number of external ports
Instead of counting the child nodes in the device tree, hardcode the
number of ports in the driver itself.  The counting won't work at all
if an ethernet port is marked as disabled, e.g. because it is not
connected on the board at all.

It turns out that the LAN9662 and LAN9668 use the same switching IP
with the same synthesis parameters. The only difference is that the
output ports are not connected. Thus, we can just hardcode the
number of physical ports to 8.

Fixes: db8bcaad53 ("net: lan966x: add the basic lan966x driver")
Signed-off-by: Michael Walle <michael@walle.cc>
Reviewed-by: Horatiu Vultur <horatiu.vultur@microchip.com>
Link: https://lore.kernel.org/r/20220704153654.1167886-1-michael@walle.cc
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2022-07-05 17:52:08 -07:00

1219 lines
31 KiB
C

// SPDX-License-Identifier: GPL-2.0+
#include <linux/module.h>
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
#include <linux/iopoll.h>
#include <linux/ip.h>
#include <linux/of_platform.h>
#include <linux/of_net.h>
#include <linux/packing.h>
#include <linux/phy/phy.h>
#include <linux/reset.h>
#include <net/addrconf.h>
#include "lan966x_main.h"
#define XTR_EOF_0 0x00000080U
#define XTR_EOF_1 0x01000080U
#define XTR_EOF_2 0x02000080U
#define XTR_EOF_3 0x03000080U
#define XTR_PRUNED 0x04000080U
#define XTR_ABORT 0x05000080U
#define XTR_ESCAPE 0x06000080U
#define XTR_NOT_READY 0x07000080U
#define XTR_VALID_BYTES(x) (4 - (((x) >> 24) & 3))
#define IO_RANGES 2
static const struct of_device_id lan966x_match[] = {
{ .compatible = "microchip,lan966x-switch" },
{ }
};
MODULE_DEVICE_TABLE(of, lan966x_match);
struct lan966x_main_io_resource {
enum lan966x_target id;
phys_addr_t offset;
int range;
};
static const struct lan966x_main_io_resource lan966x_main_iomap[] = {
{ TARGET_CPU, 0xc0000, 0 }, /* 0xe00c0000 */
{ TARGET_FDMA, 0xc0400, 0 }, /* 0xe00c0400 */
{ TARGET_ORG, 0, 1 }, /* 0xe2000000 */
{ TARGET_GCB, 0x4000, 1 }, /* 0xe2004000 */
{ TARGET_QS, 0x8000, 1 }, /* 0xe2008000 */
{ TARGET_PTP, 0xc000, 1 }, /* 0xe200c000 */
{ TARGET_CHIP_TOP, 0x10000, 1 }, /* 0xe2010000 */
{ TARGET_REW, 0x14000, 1 }, /* 0xe2014000 */
{ TARGET_SYS, 0x28000, 1 }, /* 0xe2028000 */
{ TARGET_DEV, 0x34000, 1 }, /* 0xe2034000 */
{ TARGET_DEV + 1, 0x38000, 1 }, /* 0xe2038000 */
{ TARGET_DEV + 2, 0x3c000, 1 }, /* 0xe203c000 */
{ TARGET_DEV + 3, 0x40000, 1 }, /* 0xe2040000 */
{ TARGET_DEV + 4, 0x44000, 1 }, /* 0xe2044000 */
{ TARGET_DEV + 5, 0x48000, 1 }, /* 0xe2048000 */
{ TARGET_DEV + 6, 0x4c000, 1 }, /* 0xe204c000 */
{ TARGET_DEV + 7, 0x50000, 1 }, /* 0xe2050000 */
{ TARGET_QSYS, 0x100000, 1 }, /* 0xe2100000 */
{ TARGET_AFI, 0x120000, 1 }, /* 0xe2120000 */
{ TARGET_ANA, 0x140000, 1 }, /* 0xe2140000 */
};
static int lan966x_create_targets(struct platform_device *pdev,
struct lan966x *lan966x)
{
struct resource *iores[IO_RANGES];
void __iomem *begin[IO_RANGES];
int idx;
/* Initially map the entire range and after that update each target to
* point inside the region at the correct offset. It is possible that
* other devices access the same region so don't add any checks about
* this.
*/
for (idx = 0; idx < IO_RANGES; idx++) {
iores[idx] = platform_get_resource(pdev, IORESOURCE_MEM,
idx);
if (!iores[idx]) {
dev_err(&pdev->dev, "Invalid resource\n");
return -EINVAL;
}
begin[idx] = devm_ioremap(&pdev->dev,
iores[idx]->start,
resource_size(iores[idx]));
if (!begin[idx]) {
dev_err(&pdev->dev, "Unable to get registers: %s\n",
iores[idx]->name);
return -ENOMEM;
}
}
for (idx = 0; idx < ARRAY_SIZE(lan966x_main_iomap); idx++) {
const struct lan966x_main_io_resource *iomap =
&lan966x_main_iomap[idx];
lan966x->regs[iomap->id] = begin[iomap->range] + iomap->offset;
}
return 0;
}
static bool lan966x_port_unique_address(struct net_device *dev)
{
struct lan966x_port *port = netdev_priv(dev);
struct lan966x *lan966x = port->lan966x;
int p;
for (p = 0; p < lan966x->num_phys_ports; ++p) {
port = lan966x->ports[p];
if (!port || port->dev == dev)
continue;
if (ether_addr_equal(dev->dev_addr, port->dev->dev_addr))
return false;
}
return true;
}
static int lan966x_port_set_mac_address(struct net_device *dev, void *p)
{
struct lan966x_port *port = netdev_priv(dev);
struct lan966x *lan966x = port->lan966x;
const struct sockaddr *addr = p;
int ret;
if (ether_addr_equal(addr->sa_data, dev->dev_addr))
return 0;
/* Learn the new net device MAC address in the mac table. */
ret = lan966x_mac_cpu_learn(lan966x, addr->sa_data, HOST_PVID);
if (ret)
return ret;
/* If there is another port with the same address as the dev, then don't
* delete it from the MAC table
*/
if (!lan966x_port_unique_address(dev))
goto out;
/* Then forget the previous one. */
ret = lan966x_mac_cpu_forget(lan966x, dev->dev_addr, HOST_PVID);
if (ret)
return ret;
out:
eth_hw_addr_set(dev, addr->sa_data);
return ret;
}
static int lan966x_port_get_phys_port_name(struct net_device *dev,
char *buf, size_t len)
{
struct lan966x_port *port = netdev_priv(dev);
int ret;
ret = snprintf(buf, len, "p%d", port->chip_port);
if (ret >= len)
return -EINVAL;
return 0;
}
static int lan966x_port_open(struct net_device *dev)
{
struct lan966x_port *port = netdev_priv(dev);
struct lan966x *lan966x = port->lan966x;
int err;
/* Enable receiving frames on the port, and activate auto-learning of
* MAC addresses.
*/
lan_rmw(ANA_PORT_CFG_LEARNAUTO_SET(1) |
ANA_PORT_CFG_RECV_ENA_SET(1) |
ANA_PORT_CFG_PORTID_VAL_SET(port->chip_port),
ANA_PORT_CFG_LEARNAUTO |
ANA_PORT_CFG_RECV_ENA |
ANA_PORT_CFG_PORTID_VAL,
lan966x, ANA_PORT_CFG(port->chip_port));
err = phylink_fwnode_phy_connect(port->phylink, port->fwnode, 0);
if (err) {
netdev_err(dev, "Could not attach to PHY\n");
return err;
}
phylink_start(port->phylink);
return 0;
}
static int lan966x_port_stop(struct net_device *dev)
{
struct lan966x_port *port = netdev_priv(dev);
lan966x_port_config_down(port);
phylink_stop(port->phylink);
phylink_disconnect_phy(port->phylink);
return 0;
}
static int lan966x_port_inj_status(struct lan966x *lan966x)
{
return lan_rd(lan966x, QS_INJ_STATUS);
}
static int lan966x_port_inj_ready(struct lan966x *lan966x, u8 grp)
{
u32 val;
if (lan_rd(lan966x, QS_INJ_STATUS) & QS_INJ_STATUS_FIFO_RDY_SET(BIT(grp)))
return 0;
return readx_poll_timeout_atomic(lan966x_port_inj_status, lan966x, val,
QS_INJ_STATUS_FIFO_RDY_GET(val) & BIT(grp),
READL_SLEEP_US, READL_TIMEOUT_US);
}
static int lan966x_port_ifh_xmit(struct sk_buff *skb,
__be32 *ifh,
struct net_device *dev)
{
struct lan966x_port *port = netdev_priv(dev);
struct lan966x *lan966x = port->lan966x;
u32 i, count, last;
u8 grp = 0;
u32 val;
int err;
val = lan_rd(lan966x, QS_INJ_STATUS);
if (!(QS_INJ_STATUS_FIFO_RDY_GET(val) & BIT(grp)) ||
(QS_INJ_STATUS_WMARK_REACHED_GET(val) & BIT(grp)))
goto err;
/* Write start of frame */
lan_wr(QS_INJ_CTRL_GAP_SIZE_SET(1) |
QS_INJ_CTRL_SOF_SET(1),
lan966x, QS_INJ_CTRL(grp));
/* Write IFH header */
for (i = 0; i < IFH_LEN; ++i) {
/* Wait until the fifo is ready */
err = lan966x_port_inj_ready(lan966x, grp);
if (err)
goto err;
lan_wr((__force u32)ifh[i], lan966x, QS_INJ_WR(grp));
}
/* Write frame */
count = DIV_ROUND_UP(skb->len, 4);
last = skb->len % 4;
for (i = 0; i < count; ++i) {
/* Wait until the fifo is ready */
err = lan966x_port_inj_ready(lan966x, grp);
if (err)
goto err;
lan_wr(((u32 *)skb->data)[i], lan966x, QS_INJ_WR(grp));
}
/* Add padding */
while (i < (LAN966X_BUFFER_MIN_SZ / 4)) {
/* Wait until the fifo is ready */
err = lan966x_port_inj_ready(lan966x, grp);
if (err)
goto err;
lan_wr(0, lan966x, QS_INJ_WR(grp));
++i;
}
/* Inidcate EOF and valid bytes in the last word */
lan_wr(QS_INJ_CTRL_GAP_SIZE_SET(1) |
QS_INJ_CTRL_VLD_BYTES_SET(skb->len < LAN966X_BUFFER_MIN_SZ ?
0 : last) |
QS_INJ_CTRL_EOF_SET(1),
lan966x, QS_INJ_CTRL(grp));
/* Add dummy CRC */
lan_wr(0, lan966x, QS_INJ_WR(grp));
skb_tx_timestamp(skb);
dev->stats.tx_packets++;
dev->stats.tx_bytes += skb->len;
if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP &&
LAN966X_SKB_CB(skb)->rew_op == IFH_REW_OP_TWO_STEP_PTP)
return NETDEV_TX_OK;
dev_consume_skb_any(skb);
return NETDEV_TX_OK;
err:
if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP &&
LAN966X_SKB_CB(skb)->rew_op == IFH_REW_OP_TWO_STEP_PTP)
lan966x_ptp_txtstamp_release(port, skb);
return NETDEV_TX_BUSY;
}
static void lan966x_ifh_set_bypass(void *ifh, u64 bypass)
{
packing(ifh, &bypass, IFH_POS_BYPASS + IFH_WID_BYPASS - 1,
IFH_POS_BYPASS, IFH_LEN * 4, PACK, 0);
}
static void lan966x_ifh_set_port(void *ifh, u64 bypass)
{
packing(ifh, &bypass, IFH_POS_DSTS + IFH_WID_DSTS - 1,
IFH_POS_DSTS, IFH_LEN * 4, PACK, 0);
}
static void lan966x_ifh_set_qos_class(void *ifh, u64 bypass)
{
packing(ifh, &bypass, IFH_POS_QOS_CLASS + IFH_WID_QOS_CLASS - 1,
IFH_POS_QOS_CLASS, IFH_LEN * 4, PACK, 0);
}
static void lan966x_ifh_set_ipv(void *ifh, u64 bypass)
{
packing(ifh, &bypass, IFH_POS_IPV + IFH_WID_IPV - 1,
IFH_POS_IPV, IFH_LEN * 4, PACK, 0);
}
static void lan966x_ifh_set_vid(void *ifh, u64 vid)
{
packing(ifh, &vid, IFH_POS_TCI + IFH_WID_TCI - 1,
IFH_POS_TCI, IFH_LEN * 4, PACK, 0);
}
static void lan966x_ifh_set_rew_op(void *ifh, u64 rew_op)
{
packing(ifh, &rew_op, IFH_POS_REW_CMD + IFH_WID_REW_CMD - 1,
IFH_POS_REW_CMD, IFH_LEN * 4, PACK, 0);
}
static void lan966x_ifh_set_timestamp(void *ifh, u64 timestamp)
{
packing(ifh, &timestamp, IFH_POS_TIMESTAMP + IFH_WID_TIMESTAMP - 1,
IFH_POS_TIMESTAMP, IFH_LEN * 4, PACK, 0);
}
static int lan966x_port_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct lan966x_port *port = netdev_priv(dev);
struct lan966x *lan966x = port->lan966x;
__be32 ifh[IFH_LEN];
int err;
memset(ifh, 0x0, sizeof(__be32) * IFH_LEN);
lan966x_ifh_set_bypass(ifh, 1);
lan966x_ifh_set_port(ifh, BIT_ULL(port->chip_port));
lan966x_ifh_set_qos_class(ifh, skb->priority >= 7 ? 0x7 : skb->priority);
lan966x_ifh_set_ipv(ifh, skb->priority >= 7 ? 0x7 : skb->priority);
lan966x_ifh_set_vid(ifh, skb_vlan_tag_get(skb));
if (port->lan966x->ptp && skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
err = lan966x_ptp_txtstamp_request(port, skb);
if (err)
return err;
lan966x_ifh_set_rew_op(ifh, LAN966X_SKB_CB(skb)->rew_op);
lan966x_ifh_set_timestamp(ifh, LAN966X_SKB_CB(skb)->ts_id);
}
spin_lock(&lan966x->tx_lock);
if (port->lan966x->fdma)
err = lan966x_fdma_xmit(skb, ifh, dev);
else
err = lan966x_port_ifh_xmit(skb, ifh, dev);
spin_unlock(&lan966x->tx_lock);
return err;
}
static int lan966x_port_change_mtu(struct net_device *dev, int new_mtu)
{
struct lan966x_port *port = netdev_priv(dev);
struct lan966x *lan966x = port->lan966x;
int old_mtu = dev->mtu;
int err;
lan_wr(DEV_MAC_MAXLEN_CFG_MAX_LEN_SET(new_mtu),
lan966x, DEV_MAC_MAXLEN_CFG(port->chip_port));
dev->mtu = new_mtu;
if (!lan966x->fdma)
return 0;
err = lan966x_fdma_change_mtu(lan966x);
if (err) {
lan_wr(DEV_MAC_MAXLEN_CFG_MAX_LEN_SET(old_mtu),
lan966x, DEV_MAC_MAXLEN_CFG(port->chip_port));
dev->mtu = old_mtu;
}
return err;
}
static int lan966x_mc_unsync(struct net_device *dev, const unsigned char *addr)
{
struct lan966x_port *port = netdev_priv(dev);
struct lan966x *lan966x = port->lan966x;
return lan966x_mac_forget(lan966x, addr, HOST_PVID, ENTRYTYPE_LOCKED);
}
static int lan966x_mc_sync(struct net_device *dev, const unsigned char *addr)
{
struct lan966x_port *port = netdev_priv(dev);
struct lan966x *lan966x = port->lan966x;
return lan966x_mac_cpu_learn(lan966x, addr, HOST_PVID);
}
static void lan966x_port_set_rx_mode(struct net_device *dev)
{
__dev_mc_sync(dev, lan966x_mc_sync, lan966x_mc_unsync);
}
static int lan966x_port_get_parent_id(struct net_device *dev,
struct netdev_phys_item_id *ppid)
{
struct lan966x_port *port = netdev_priv(dev);
struct lan966x *lan966x = port->lan966x;
ppid->id_len = sizeof(lan966x->base_mac);
memcpy(&ppid->id, &lan966x->base_mac, ppid->id_len);
return 0;
}
static int lan966x_port_ioctl(struct net_device *dev, struct ifreq *ifr,
int cmd)
{
struct lan966x_port *port = netdev_priv(dev);
if (!phy_has_hwtstamp(dev->phydev) && port->lan966x->ptp) {
switch (cmd) {
case SIOCSHWTSTAMP:
return lan966x_ptp_hwtstamp_set(port, ifr);
case SIOCGHWTSTAMP:
return lan966x_ptp_hwtstamp_get(port, ifr);
}
}
if (!dev->phydev)
return -ENODEV;
return phy_mii_ioctl(dev->phydev, ifr, cmd);
}
static const struct net_device_ops lan966x_port_netdev_ops = {
.ndo_open = lan966x_port_open,
.ndo_stop = lan966x_port_stop,
.ndo_start_xmit = lan966x_port_xmit,
.ndo_change_mtu = lan966x_port_change_mtu,
.ndo_set_rx_mode = lan966x_port_set_rx_mode,
.ndo_get_phys_port_name = lan966x_port_get_phys_port_name,
.ndo_get_stats64 = lan966x_stats_get,
.ndo_set_mac_address = lan966x_port_set_mac_address,
.ndo_get_port_parent_id = lan966x_port_get_parent_id,
.ndo_eth_ioctl = lan966x_port_ioctl,
};
bool lan966x_netdevice_check(const struct net_device *dev)
{
return dev->netdev_ops == &lan966x_port_netdev_ops;
}
bool lan966x_hw_offload(struct lan966x *lan966x, u32 port, struct sk_buff *skb)
{
u32 val;
/* The IGMP and MLD frames are not forward by the HW if
* multicast snooping is enabled, therefor don't mark as
* offload to allow the SW to forward the frames accordingly.
*/
val = lan_rd(lan966x, ANA_CPU_FWD_CFG(port));
if (!(val & (ANA_CPU_FWD_CFG_IGMP_REDIR_ENA |
ANA_CPU_FWD_CFG_MLD_REDIR_ENA)))
return true;
if (eth_type_vlan(skb->protocol)) {
skb = skb_vlan_untag(skb);
if (unlikely(!skb))
return false;
}
if (skb->protocol == htons(ETH_P_IP) &&
ip_hdr(skb)->protocol == IPPROTO_IGMP)
return false;
if (IS_ENABLED(CONFIG_IPV6) &&
skb->protocol == htons(ETH_P_IPV6) &&
ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr) &&
!ipv6_mc_check_mld(skb))
return false;
return true;
}
static int lan966x_port_xtr_status(struct lan966x *lan966x, u8 grp)
{
return lan_rd(lan966x, QS_XTR_RD(grp));
}
static int lan966x_port_xtr_ready(struct lan966x *lan966x, u8 grp)
{
u32 val;
return read_poll_timeout(lan966x_port_xtr_status, val,
val != XTR_NOT_READY,
READL_SLEEP_US, READL_TIMEOUT_US, false,
lan966x, grp);
}
static int lan966x_rx_frame_word(struct lan966x *lan966x, u8 grp, u32 *rval)
{
u32 bytes_valid;
u32 val;
int err;
val = lan_rd(lan966x, QS_XTR_RD(grp));
if (val == XTR_NOT_READY) {
err = lan966x_port_xtr_ready(lan966x, grp);
if (err)
return -EIO;
}
switch (val) {
case XTR_ABORT:
return -EIO;
case XTR_EOF_0:
case XTR_EOF_1:
case XTR_EOF_2:
case XTR_EOF_3:
case XTR_PRUNED:
bytes_valid = XTR_VALID_BYTES(val);
val = lan_rd(lan966x, QS_XTR_RD(grp));
if (val == XTR_ESCAPE)
*rval = lan_rd(lan966x, QS_XTR_RD(grp));
else
*rval = val;
return bytes_valid;
case XTR_ESCAPE:
*rval = lan_rd(lan966x, QS_XTR_RD(grp));
return 4;
default:
*rval = val;
return 4;
}
}
void lan966x_ifh_get_src_port(void *ifh, u64 *src_port)
{
packing(ifh, src_port, IFH_POS_SRCPORT + IFH_WID_SRCPORT - 1,
IFH_POS_SRCPORT, IFH_LEN * 4, UNPACK, 0);
}
static void lan966x_ifh_get_len(void *ifh, u64 *len)
{
packing(ifh, len, IFH_POS_LEN + IFH_WID_LEN - 1,
IFH_POS_LEN, IFH_LEN * 4, UNPACK, 0);
}
void lan966x_ifh_get_timestamp(void *ifh, u64 *timestamp)
{
packing(ifh, timestamp, IFH_POS_TIMESTAMP + IFH_WID_TIMESTAMP - 1,
IFH_POS_TIMESTAMP, IFH_LEN * 4, UNPACK, 0);
}
static irqreturn_t lan966x_xtr_irq_handler(int irq, void *args)
{
struct lan966x *lan966x = args;
int i, grp = 0, err = 0;
if (!(lan_rd(lan966x, QS_XTR_DATA_PRESENT) & BIT(grp)))
return IRQ_NONE;
do {
u64 src_port, len, timestamp;
struct net_device *dev;
struct sk_buff *skb;
int sz = 0, buf_len;
u32 ifh[IFH_LEN];
u32 *buf;
u32 val;
for (i = 0; i < IFH_LEN; i++) {
err = lan966x_rx_frame_word(lan966x, grp, &ifh[i]);
if (err != 4)
goto recover;
}
err = 0;
lan966x_ifh_get_src_port(ifh, &src_port);
lan966x_ifh_get_len(ifh, &len);
lan966x_ifh_get_timestamp(ifh, &timestamp);
WARN_ON(src_port >= lan966x->num_phys_ports);
dev = lan966x->ports[src_port]->dev;
skb = netdev_alloc_skb(dev, len);
if (unlikely(!skb)) {
netdev_err(dev, "Unable to allocate sk_buff\n");
err = -ENOMEM;
break;
}
buf_len = len - ETH_FCS_LEN;
buf = (u32 *)skb_put(skb, buf_len);
len = 0;
do {
sz = lan966x_rx_frame_word(lan966x, grp, &val);
if (sz < 0) {
kfree_skb(skb);
goto recover;
}
*buf++ = val;
len += sz;
} while (len < buf_len);
/* Read the FCS */
sz = lan966x_rx_frame_word(lan966x, grp, &val);
if (sz < 0) {
kfree_skb(skb);
goto recover;
}
/* Update the statistics if part of the FCS was read before */
len -= ETH_FCS_LEN - sz;
if (unlikely(dev->features & NETIF_F_RXFCS)) {
buf = (u32 *)skb_put(skb, ETH_FCS_LEN);
*buf = val;
}
lan966x_ptp_rxtstamp(lan966x, skb, timestamp);
skb->protocol = eth_type_trans(skb, dev);
if (lan966x->bridge_mask & BIT(src_port)) {
skb->offload_fwd_mark = 1;
skb_reset_network_header(skb);
if (!lan966x_hw_offload(lan966x, src_port, skb))
skb->offload_fwd_mark = 0;
}
if (!skb_defer_rx_timestamp(skb))
netif_rx(skb);
dev->stats.rx_bytes += len;
dev->stats.rx_packets++;
recover:
if (sz < 0 || err)
lan_rd(lan966x, QS_XTR_RD(grp));
} while (lan_rd(lan966x, QS_XTR_DATA_PRESENT) & BIT(grp));
return IRQ_HANDLED;
}
static irqreturn_t lan966x_ana_irq_handler(int irq, void *args)
{
struct lan966x *lan966x = args;
return lan966x_mac_irq_handler(lan966x);
}
static void lan966x_cleanup_ports(struct lan966x *lan966x)
{
struct lan966x_port *port;
int p;
for (p = 0; p < lan966x->num_phys_ports; p++) {
port = lan966x->ports[p];
if (!port)
continue;
if (port->dev)
unregister_netdev(port->dev);
if (lan966x->fdma && lan966x->fdma_ndev == port->dev)
lan966x_fdma_netdev_deinit(lan966x, port->dev);
if (port->phylink) {
rtnl_lock();
lan966x_port_stop(port->dev);
rtnl_unlock();
phylink_destroy(port->phylink);
port->phylink = NULL;
}
if (port->fwnode)
fwnode_handle_put(port->fwnode);
}
disable_irq(lan966x->xtr_irq);
lan966x->xtr_irq = -ENXIO;
if (lan966x->ana_irq) {
disable_irq(lan966x->ana_irq);
lan966x->ana_irq = -ENXIO;
}
if (lan966x->fdma)
devm_free_irq(lan966x->dev, lan966x->fdma_irq, lan966x);
if (lan966x->ptp_irq)
devm_free_irq(lan966x->dev, lan966x->ptp_irq, lan966x);
if (lan966x->ptp_ext_irq)
devm_free_irq(lan966x->dev, lan966x->ptp_ext_irq, lan966x);
}
static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
phy_interface_t phy_mode,
struct fwnode_handle *portnp)
{
struct lan966x_port *port;
struct phylink *phylink;
struct net_device *dev;
int err;
if (p >= lan966x->num_phys_ports)
return -EINVAL;
dev = devm_alloc_etherdev_mqs(lan966x->dev,
sizeof(struct lan966x_port), 8, 1);
if (!dev)
return -ENOMEM;
SET_NETDEV_DEV(dev, lan966x->dev);
port = netdev_priv(dev);
port->dev = dev;
port->lan966x = lan966x;
port->chip_port = p;
lan966x->ports[p] = port;
dev->max_mtu = ETH_MAX_MTU;
dev->netdev_ops = &lan966x_port_netdev_ops;
dev->ethtool_ops = &lan966x_ethtool_ops;
dev->features |= NETIF_F_HW_VLAN_CTAG_TX |
NETIF_F_HW_VLAN_STAG_TX;
dev->needed_headroom = IFH_LEN * sizeof(u32);
eth_hw_addr_gen(dev, lan966x->base_mac, p + 1);
lan966x_mac_learn(lan966x, PGID_CPU, dev->dev_addr, HOST_PVID,
ENTRYTYPE_LOCKED);
port->phylink_config.dev = &port->dev->dev;
port->phylink_config.type = PHYLINK_NETDEV;
port->phylink_pcs.poll = true;
port->phylink_pcs.ops = &lan966x_phylink_pcs_ops;
port->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
MAC_10 | MAC_100 | MAC_1000FD | MAC_2500FD;
__set_bit(PHY_INTERFACE_MODE_MII,
port->phylink_config.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_GMII,
port->phylink_config.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_SGMII,
port->phylink_config.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_QSGMII,
port->phylink_config.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX,
port->phylink_config.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_2500BASEX,
port->phylink_config.supported_interfaces);
phylink = phylink_create(&port->phylink_config,
portnp,
phy_mode,
&lan966x_phylink_mac_ops);
if (IS_ERR(phylink)) {
port->dev = NULL;
return PTR_ERR(phylink);
}
port->phylink = phylink;
err = register_netdev(dev);
if (err) {
dev_err(lan966x->dev, "register_netdev failed\n");
return err;
}
lan966x_vlan_port_set_vlan_aware(port, 0);
lan966x_vlan_port_set_vid(port, HOST_PVID, false, false);
lan966x_vlan_port_apply(port);
return 0;
}
static void lan966x_init(struct lan966x *lan966x)
{
u32 p, i;
/* MAC table initialization */
lan966x_mac_init(lan966x);
lan966x_vlan_init(lan966x);
/* Flush queues */
lan_wr(lan_rd(lan966x, QS_XTR_FLUSH) |
GENMASK(1, 0),
lan966x, QS_XTR_FLUSH);
/* Allow to drain */
mdelay(1);
/* All Queues normal */
lan_wr(lan_rd(lan966x, QS_XTR_FLUSH) &
~(GENMASK(1, 0)),
lan966x, QS_XTR_FLUSH);
/* Set MAC age time to default value, the entry is aged after
* 2 * AGE_PERIOD
*/
lan_wr(ANA_AUTOAGE_AGE_PERIOD_SET(BR_DEFAULT_AGEING_TIME / 2 / HZ),
lan966x, ANA_AUTOAGE);
/* Disable learning for frames discarded by VLAN ingress filtering */
lan_rmw(ANA_ADVLEARN_VLAN_CHK_SET(1),
ANA_ADVLEARN_VLAN_CHK,
lan966x, ANA_ADVLEARN);
/* Setup frame ageing - "2 sec" - The unit is 6.5 us on lan966x */
lan_wr(SYS_FRM_AGING_AGE_TX_ENA_SET(1) |
(20000000 / 65),
lan966x, SYS_FRM_AGING);
/* Map the 8 CPU extraction queues to CPU port */
lan_wr(0, lan966x, QSYS_CPU_GROUP_MAP);
/* Do byte-swap and expect status after last data word
* Extraction: Mode: manual extraction) | Byte_swap
*/
lan_wr(QS_XTR_GRP_CFG_MODE_SET(lan966x->fdma ? 2 : 1) |
QS_XTR_GRP_CFG_BYTE_SWAP_SET(1),
lan966x, QS_XTR_GRP_CFG(0));
/* Injection: Mode: manual injection | Byte_swap */
lan_wr(QS_INJ_GRP_CFG_MODE_SET(lan966x->fdma ? 2 : 1) |
QS_INJ_GRP_CFG_BYTE_SWAP_SET(1),
lan966x, QS_INJ_GRP_CFG(0));
lan_rmw(QS_INJ_CTRL_GAP_SIZE_SET(0),
QS_INJ_CTRL_GAP_SIZE,
lan966x, QS_INJ_CTRL(0));
/* Enable IFH insertion/parsing on CPU ports */
lan_wr(SYS_PORT_MODE_INCL_INJ_HDR_SET(1) |
SYS_PORT_MODE_INCL_XTR_HDR_SET(1),
lan966x, SYS_PORT_MODE(CPU_PORT));
/* Setup flooding PGIDs */
lan_wr(ANA_FLOODING_IPMC_FLD_MC4_DATA_SET(PGID_MCIPV4) |
ANA_FLOODING_IPMC_FLD_MC4_CTRL_SET(PGID_MC) |
ANA_FLOODING_IPMC_FLD_MC6_DATA_SET(PGID_MCIPV6) |
ANA_FLOODING_IPMC_FLD_MC6_CTRL_SET(PGID_MC),
lan966x, ANA_FLOODING_IPMC);
/* There are 8 priorities */
for (i = 0; i < 8; ++i)
lan_rmw(ANA_FLOODING_FLD_MULTICAST_SET(PGID_MC) |
ANA_FLOODING_FLD_UNICAST_SET(PGID_UC) |
ANA_FLOODING_FLD_BROADCAST_SET(PGID_BC),
ANA_FLOODING_FLD_MULTICAST |
ANA_FLOODING_FLD_UNICAST |
ANA_FLOODING_FLD_BROADCAST,
lan966x, ANA_FLOODING(i));
for (i = 0; i < PGID_ENTRIES; ++i)
/* Set all the entries to obey VLAN_VLAN */
lan_rmw(ANA_PGID_CFG_OBEY_VLAN_SET(1),
ANA_PGID_CFG_OBEY_VLAN,
lan966x, ANA_PGID_CFG(i));
for (p = 0; p < lan966x->num_phys_ports; p++) {
/* Disable bridging by default */
lan_rmw(ANA_PGID_PGID_SET(0x0),
ANA_PGID_PGID,
lan966x, ANA_PGID(p + PGID_SRC));
/* Do not forward BPDU frames to the front ports and copy them
* to CPU
*/
lan_wr(0xffff, lan966x, ANA_CPU_FWD_BPDU_CFG(p));
}
/* Set source buffer size for each priority and each port to 1500 bytes */
for (i = 0; i <= QSYS_Q_RSRV; ++i) {
lan_wr(1500 / 64, lan966x, QSYS_RES_CFG(i));
lan_wr(1500 / 64, lan966x, QSYS_RES_CFG(512 + i));
}
/* Enable switching to/from cpu port */
lan_wr(QSYS_SW_PORT_MODE_PORT_ENA_SET(1) |
QSYS_SW_PORT_MODE_SCH_NEXT_CFG_SET(1) |
QSYS_SW_PORT_MODE_INGRESS_DROP_MODE_SET(1),
lan966x, QSYS_SW_PORT_MODE(CPU_PORT));
/* Configure and enable the CPU port */
lan_rmw(ANA_PGID_PGID_SET(0),
ANA_PGID_PGID,
lan966x, ANA_PGID(CPU_PORT));
lan_rmw(ANA_PGID_PGID_SET(BIT(CPU_PORT)),
ANA_PGID_PGID,
lan966x, ANA_PGID(PGID_CPU));
/* Multicast to all other ports */
lan_rmw(GENMASK(lan966x->num_phys_ports - 1, 0),
ANA_PGID_PGID,
lan966x, ANA_PGID(PGID_MC));
/* This will be controlled by mrouter ports */
lan_rmw(GENMASK(lan966x->num_phys_ports - 1, 0),
ANA_PGID_PGID,
lan966x, ANA_PGID(PGID_MCIPV4));
lan_rmw(GENMASK(lan966x->num_phys_ports - 1, 0),
ANA_PGID_PGID,
lan966x, ANA_PGID(PGID_MCIPV6));
/* Unicast to all other ports */
lan_rmw(GENMASK(lan966x->num_phys_ports - 1, 0),
ANA_PGID_PGID,
lan966x, ANA_PGID(PGID_UC));
/* Broadcast to the CPU port and to other ports */
lan_rmw(ANA_PGID_PGID_SET(BIT(CPU_PORT) | GENMASK(lan966x->num_phys_ports - 1, 0)),
ANA_PGID_PGID,
lan966x, ANA_PGID(PGID_BC));
lan_wr(REW_PORT_CFG_NO_REWRITE_SET(1),
lan966x, REW_PORT_CFG(CPU_PORT));
lan_rmw(ANA_ANAINTR_INTR_ENA_SET(1),
ANA_ANAINTR_INTR_ENA,
lan966x, ANA_ANAINTR);
spin_lock_init(&lan966x->tx_lock);
}
static int lan966x_ram_init(struct lan966x *lan966x)
{
return lan_rd(lan966x, SYS_RAM_INIT);
}
static int lan966x_reset_switch(struct lan966x *lan966x)
{
struct reset_control *switch_reset;
int val = 0;
int ret;
switch_reset = devm_reset_control_get_shared(lan966x->dev, "switch");
if (IS_ERR(switch_reset))
return dev_err_probe(lan966x->dev, PTR_ERR(switch_reset),
"Could not obtain switch reset");
reset_control_reset(switch_reset);
lan_wr(SYS_RESET_CFG_CORE_ENA_SET(0), lan966x, SYS_RESET_CFG);
lan_wr(SYS_RAM_INIT_RAM_INIT_SET(1), lan966x, SYS_RAM_INIT);
ret = readx_poll_timeout(lan966x_ram_init, lan966x,
val, (val & BIT(1)) == 0, READL_SLEEP_US,
READL_TIMEOUT_US);
if (ret)
return ret;
lan_wr(SYS_RESET_CFG_CORE_ENA_SET(1), lan966x, SYS_RESET_CFG);
return 0;
}
static int lan966x_probe(struct platform_device *pdev)
{
struct fwnode_handle *ports, *portnp;
struct lan966x *lan966x;
u8 mac_addr[ETH_ALEN];
int err;
lan966x = devm_kzalloc(&pdev->dev, sizeof(*lan966x), GFP_KERNEL);
if (!lan966x)
return -ENOMEM;
platform_set_drvdata(pdev, lan966x);
lan966x->dev = &pdev->dev;
if (!device_get_mac_address(&pdev->dev, mac_addr)) {
ether_addr_copy(lan966x->base_mac, mac_addr);
} else {
pr_info("MAC addr was not set, use random MAC\n");
eth_random_addr(lan966x->base_mac);
lan966x->base_mac[5] &= 0xf0;
}
ports = device_get_named_child_node(&pdev->dev, "ethernet-ports");
if (!ports)
return dev_err_probe(&pdev->dev, -ENODEV,
"no ethernet-ports child found\n");
err = lan966x_create_targets(pdev, lan966x);
if (err)
return dev_err_probe(&pdev->dev, err,
"Failed to create targets");
err = lan966x_reset_switch(lan966x);
if (err)
return dev_err_probe(&pdev->dev, err, "Reset failed");
lan966x->num_phys_ports = NUM_PHYS_PORTS;
lan966x->ports = devm_kcalloc(&pdev->dev, lan966x->num_phys_ports,
sizeof(struct lan966x_port *),
GFP_KERNEL);
if (!lan966x->ports)
return -ENOMEM;
/* There QS system has 32KB of memory */
lan966x->shared_queue_sz = LAN966X_BUFFER_MEMORY;
/* set irq */
lan966x->xtr_irq = platform_get_irq_byname(pdev, "xtr");
if (lan966x->xtr_irq <= 0)
return -EINVAL;
err = devm_request_threaded_irq(&pdev->dev, lan966x->xtr_irq, NULL,
lan966x_xtr_irq_handler, IRQF_ONESHOT,
"frame extraction", lan966x);
if (err) {
pr_err("Unable to use xtr irq");
return -ENODEV;
}
lan966x->ana_irq = platform_get_irq_byname(pdev, "ana");
if (lan966x->ana_irq) {
err = devm_request_threaded_irq(&pdev->dev, lan966x->ana_irq, NULL,
lan966x_ana_irq_handler, IRQF_ONESHOT,
"ana irq", lan966x);
if (err)
return dev_err_probe(&pdev->dev, err, "Unable to use ana irq");
}
lan966x->ptp_irq = platform_get_irq_byname(pdev, "ptp");
if (lan966x->ptp_irq > 0) {
err = devm_request_threaded_irq(&pdev->dev, lan966x->ptp_irq, NULL,
lan966x_ptp_irq_handler, IRQF_ONESHOT,
"ptp irq", lan966x);
if (err)
return dev_err_probe(&pdev->dev, err, "Unable to use ptp irq");
lan966x->ptp = 1;
}
lan966x->fdma_irq = platform_get_irq_byname(pdev, "fdma");
if (lan966x->fdma_irq > 0) {
err = devm_request_irq(&pdev->dev, lan966x->fdma_irq,
lan966x_fdma_irq_handler, 0,
"fdma irq", lan966x);
if (err)
return dev_err_probe(&pdev->dev, err, "Unable to use fdma irq");
lan966x->fdma = true;
}
if (lan966x->ptp) {
lan966x->ptp_ext_irq = platform_get_irq_byname(pdev, "ptp-ext");
if (lan966x->ptp_ext_irq > 0) {
err = devm_request_threaded_irq(&pdev->dev,
lan966x->ptp_ext_irq, NULL,
lan966x_ptp_ext_irq_handler,
IRQF_ONESHOT,
"ptp-ext irq", lan966x);
if (err)
return dev_err_probe(&pdev->dev, err,
"Unable to use ptp-ext irq");
}
}
/* init switch */
lan966x_init(lan966x);
lan966x_stats_init(lan966x);
/* go over the child nodes */
fwnode_for_each_available_child_node(ports, portnp) {
phy_interface_t phy_mode;
struct phy *serdes;
u32 p;
if (fwnode_property_read_u32(portnp, "reg", &p))
continue;
phy_mode = fwnode_get_phy_mode(portnp);
err = lan966x_probe_port(lan966x, p, phy_mode, portnp);
if (err)
goto cleanup_ports;
/* Read needed configuration */
lan966x->ports[p]->config.portmode = phy_mode;
lan966x->ports[p]->fwnode = fwnode_handle_get(portnp);
serdes = devm_of_phy_get(lan966x->dev, to_of_node(portnp), NULL);
if (PTR_ERR(serdes) == -ENODEV)
serdes = NULL;
if (IS_ERR(serdes)) {
err = PTR_ERR(serdes);
goto cleanup_ports;
}
lan966x->ports[p]->serdes = serdes;
lan966x_port_init(lan966x->ports[p]);
}
lan966x_mdb_init(lan966x);
err = lan966x_fdb_init(lan966x);
if (err)
goto cleanup_ports;
err = lan966x_ptp_init(lan966x);
if (err)
goto cleanup_fdb;
err = lan966x_fdma_init(lan966x);
if (err)
goto cleanup_ptp;
return 0;
cleanup_ptp:
lan966x_ptp_deinit(lan966x);
cleanup_fdb:
lan966x_fdb_deinit(lan966x);
cleanup_ports:
fwnode_handle_put(portnp);
lan966x_cleanup_ports(lan966x);
cancel_delayed_work_sync(&lan966x->stats_work);
destroy_workqueue(lan966x->stats_queue);
mutex_destroy(&lan966x->stats_lock);
return err;
}
static int lan966x_remove(struct platform_device *pdev)
{
struct lan966x *lan966x = platform_get_drvdata(pdev);
lan966x_fdma_deinit(lan966x);
lan966x_cleanup_ports(lan966x);
cancel_delayed_work_sync(&lan966x->stats_work);
destroy_workqueue(lan966x->stats_queue);
mutex_destroy(&lan966x->stats_lock);
lan966x_mac_purge_entries(lan966x);
lan966x_mdb_deinit(lan966x);
lan966x_fdb_deinit(lan966x);
lan966x_ptp_deinit(lan966x);
return 0;
}
static struct platform_driver lan966x_driver = {
.probe = lan966x_probe,
.remove = lan966x_remove,
.driver = {
.name = "lan966x-switch",
.of_match_table = lan966x_match,
},
};
static int __init lan966x_switch_driver_init(void)
{
int ret;
lan966x_register_notifier_blocks();
ret = platform_driver_register(&lan966x_driver);
if (ret)
goto err;
return 0;
err:
lan966x_unregister_notifier_blocks();
return ret;
}
static void __exit lan966x_switch_driver_exit(void)
{
platform_driver_unregister(&lan966x_driver);
lan966x_unregister_notifier_blocks();
}
module_init(lan966x_switch_driver_init);
module_exit(lan966x_switch_driver_exit);
MODULE_DESCRIPTION("Microchip LAN966X switch driver");
MODULE_AUTHOR("Horatiu Vultur <horatiu.vultur@microchip.com>");
MODULE_LICENSE("Dual MIT/GPL");