Skip to content

Commit

Permalink
net: lan966x: add the basic lan966x driver
Browse files Browse the repository at this point in the history
This patch adds basic SwitchDev driver framework for lan966x. It
includes only the IO range mapping and probing of the switch.

Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Horatiu Vultur authored and David S. Miller committed Nov 29, 2021
1 parent 642fcf5 commit db8bcaa
Show file tree
Hide file tree
Showing 7 changed files with 1,207 additions and 0 deletions.
1 change: 1 addition & 0 deletions drivers/net/ethernet/microchip/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ config LAN743X
To compile this driver as a module, choose M here. The module will be
called lan743x.

source "drivers/net/ethernet/microchip/lan966x/Kconfig"
source "drivers/net/ethernet/microchip/sparx5/Kconfig"

endif # NET_VENDOR_MICROCHIP
1 change: 1 addition & 0 deletions drivers/net/ethernet/microchip/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ obj-$(CONFIG_LAN743X) += lan743x.o

lan743x-objs := lan743x_main.o lan743x_ethtool.o lan743x_ptp.o

obj-$(CONFIG_LAN966X_SWITCH) += lan966x/
obj-$(CONFIG_SPARX5_SWITCH) += sparx5/
7 changes: 7 additions & 0 deletions drivers/net/ethernet/microchip/lan966x/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
config LAN966X_SWITCH
tristate "Lan966x switch driver"
depends on HAS_IOMEM
depends on OF
select PHYLINK
help
This driver supports the Lan966x network switch device.
8 changes: 8 additions & 0 deletions drivers/net/ethernet/microchip/lan966x/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the Microchip Lan966x network device drivers.
#

obj-$(CONFIG_LAN966X_SWITCH) += lan966x-switch.o

lan966x-switch-objs := lan966x_main.o
357 changes: 357 additions & 0 deletions drivers/net/ethernet/microchip/lan966x/lan966x_main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
// SPDX-License-Identifier: GPL-2.0+

#include <linux/module.h>
#include <linux/if_bridge.h>
#include <linux/iopoll.h>
#include <linux/of_platform.h>
#include <linux/of_net.h>
#include <linux/reset.h>

#include "lan966x_main.h"

#define READL_SLEEP_US 10
#define READL_TIMEOUT_US 100000000

#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_ORG, 0, 1 }, /* 0xe2000000 */
{ TARGET_GCB, 0x4000, 1 }, /* 0xe2004000 */
{ TARGET_QS, 0x8000, 1 }, /* 0xe2008000 */
{ 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 (IS_ERR(begin[idx])) {
dev_err(&pdev->dev, "Unable to get registers: %s\n",
iores[idx]->name);
return PTR_ERR(begin[idx]);
}
}

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 int lan966x_probe_port(struct lan966x *lan966x, u32 p,
phy_interface_t phy_mode)
{
struct lan966x_port *port;

if (p >= lan966x->num_phys_ports)
return -EINVAL;

port = devm_kzalloc(lan966x->dev, sizeof(*port), GFP_KERNEL);
if (!port)
return -ENOMEM;

port->lan966x = lan966x;
port->chip_port = p;
port->pvid = PORT_PVID;
lan966x->ports[p] = port;

return 0;
}

static void lan966x_init(struct lan966x *lan966x)
{
u32 p, i;

/* 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(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(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_MC) |
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_BROADCAST_SET(PGID_BC),
ANA_FLOODING_FLD_MULTICAST |
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));

/* 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);
}

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, *phy_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");

phy_reset = devm_reset_control_get_shared(lan966x->dev, "phy");
if (IS_ERR(phy_reset))
return dev_err_probe(lan966x->dev, PTR_ERR(phy_reset),
"Could not obtain phy reset\n");

reset_control_reset(switch_reset);
reset_control_reset(phy_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;
int err, i;

lan966x = devm_kzalloc(&pdev->dev, sizeof(*lan966x), GFP_KERNEL);
if (!lan966x)
return -ENOMEM;

platform_set_drvdata(pdev, lan966x);
lan966x->dev = &pdev->dev;

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");

i = 0;
fwnode_for_each_available_child_node(ports, portnp)
++i;

lan966x->num_phys_ports = i;
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;

/* init switch */
lan966x_init(lan966x);

/* go over the child nodes */
fwnode_for_each_available_child_node(ports, portnp) {
phy_interface_t phy_mode;
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);
if (err)
goto cleanup_ports;
}

return 0;

cleanup_ports:
fwnode_handle_put(portnp);

return err;
}

static struct platform_driver lan966x_driver = {
.probe = lan966x_probe,
.driver = {
.name = "lan966x-switch",
.of_match_table = lan966x_match,
},
};
module_platform_driver(lan966x_driver);

MODULE_DESCRIPTION("Microchip LAN966X switch driver");
MODULE_AUTHOR("Horatiu Vultur <horatiu.vultur@microchip.com>");
MODULE_LICENSE("Dual MIT/GPL");
Loading

0 comments on commit db8bcaa

Please sign in to comment.