-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
net: lan966x: add the basic lan966x driver
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
Showing
7 changed files
with
1,207 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/* SPDX-License-Identifier: GPL-2.0+ */ | ||
|
||
#ifndef __LAN966X_MAIN_H__ | ||
#define __LAN966X_MAIN_H__ | ||
|
||
#include "lan966x_regs.h" | ||
|
||
#define LAN966X_BUFFER_CELL_SZ 64 | ||
#define LAN966X_BUFFER_MEMORY (160 * 1024) | ||
#define LAN966X_BUFFER_MIN_SZ 60 | ||
|
||
#define PGID_AGGR 64 | ||
#define PGID_SRC 80 | ||
#define PGID_ENTRIES 89 | ||
|
||
#define PORT_PVID 0 | ||
|
||
/* Reserved amount for (SRC, PRIO) at index 8*SRC + PRIO */ | ||
#define QSYS_Q_RSRV 95 | ||
|
||
/* Reserved PGIDs */ | ||
#define PGID_CPU (PGID_AGGR - 6) | ||
#define PGID_UC (PGID_AGGR - 5) | ||
#define PGID_BC (PGID_AGGR - 4) | ||
#define PGID_MC (PGID_AGGR - 3) | ||
#define PGID_MCIPV4 (PGID_AGGR - 2) | ||
#define PGID_MCIPV6 (PGID_AGGR - 1) | ||
|
||
#define LAN966X_SPEED_NONE 0 | ||
#define LAN966X_SPEED_1000 1 | ||
#define LAN966X_SPEED_100 2 | ||
#define LAN966X_SPEED_10 3 | ||
|
||
#define CPU_PORT 8 | ||
|
||
struct lan966x_port; | ||
|
||
struct lan966x { | ||
struct device *dev; | ||
|
||
u8 num_phys_ports; | ||
struct lan966x_port **ports; | ||
|
||
void __iomem *regs[NUM_TARGETS]; | ||
|
||
int shared_queue_sz; | ||
}; | ||
|
||
struct lan966x_port { | ||
struct lan966x *lan966x; | ||
|
||
u8 chip_port; | ||
u16 pvid; | ||
}; | ||
|
||
static inline void __iomem *lan_addr(void __iomem *base[], | ||
int id, int tinst, int tcnt, | ||
int gbase, int ginst, | ||
int gcnt, int gwidth, | ||
int raddr, int rinst, | ||
int rcnt, int rwidth) | ||
{ | ||
WARN_ON((tinst) >= tcnt); | ||
WARN_ON((ginst) >= gcnt); | ||
WARN_ON((rinst) >= rcnt); | ||
return base[id + (tinst)] + | ||
gbase + ((ginst) * gwidth) + | ||
raddr + ((rinst) * rwidth); | ||
} | ||
|
||
static inline u32 lan_rd(struct lan966x *lan966x, int id, int tinst, int tcnt, | ||
int gbase, int ginst, int gcnt, int gwidth, | ||
int raddr, int rinst, int rcnt, int rwidth) | ||
{ | ||
return readl(lan_addr(lan966x->regs, id, tinst, tcnt, gbase, ginst, | ||
gcnt, gwidth, raddr, rinst, rcnt, rwidth)); | ||
} | ||
|
||
static inline void lan_wr(u32 val, struct lan966x *lan966x, | ||
int id, int tinst, int tcnt, | ||
int gbase, int ginst, int gcnt, int gwidth, | ||
int raddr, int rinst, int rcnt, int rwidth) | ||
{ | ||
writel(val, lan_addr(lan966x->regs, id, tinst, tcnt, | ||
gbase, ginst, gcnt, gwidth, | ||
raddr, rinst, rcnt, rwidth)); | ||
} | ||
|
||
static inline void lan_rmw(u32 val, u32 mask, struct lan966x *lan966x, | ||
int id, int tinst, int tcnt, | ||
int gbase, int ginst, int gcnt, int gwidth, | ||
int raddr, int rinst, int rcnt, int rwidth) | ||
{ | ||
u32 nval; | ||
|
||
nval = readl(lan_addr(lan966x->regs, id, tinst, tcnt, gbase, ginst, | ||
gcnt, gwidth, raddr, rinst, rcnt, rwidth)); | ||
nval = (nval & ~mask) | (val & mask); | ||
writel(nval, lan_addr(lan966x->regs, id, tinst, tcnt, gbase, ginst, | ||
gcnt, gwidth, raddr, rinst, rcnt, rwidth)); | ||
} | ||
|
||
#endif /* __LAN966X_MAIN_H__ */ |
Large diffs are not rendered by default.
Oops, something went wrong.