2018-05-14 22:04:57 +02:00
|
|
|
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
|
|
|
|
/*
|
|
|
|
* Microsemi Ocelot Switch driver
|
|
|
|
*
|
|
|
|
* Copyright (c) 2017 Microsemi Corporation
|
|
|
|
*/
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
|
|
|
|
#include "ocelot.h"
|
|
|
|
|
2023-04-12 15:47:30 +03:00
|
|
|
int __ocelot_bulk_read_ix(struct ocelot *ocelot, enum ocelot_reg reg,
|
|
|
|
u32 offset, void *buf, int count)
|
2022-02-13 11:12:53 -08:00
|
|
|
{
|
2023-04-12 15:47:31 +03:00
|
|
|
enum ocelot_target target;
|
|
|
|
u32 addr;
|
2022-02-13 11:12:53 -08:00
|
|
|
|
2023-04-12 15:47:31 +03:00
|
|
|
ocelot_reg_to_target_addr(ocelot, reg, &target, &addr);
|
2022-02-13 11:12:53 -08:00
|
|
|
WARN_ON(!target);
|
|
|
|
|
2023-04-12 15:47:31 +03:00
|
|
|
return regmap_bulk_read(ocelot->targets[target], addr + offset,
|
2022-02-13 11:12:53 -08:00
|
|
|
buf, count);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(__ocelot_bulk_read_ix);
|
|
|
|
|
2023-04-12 15:47:30 +03:00
|
|
|
u32 __ocelot_read_ix(struct ocelot *ocelot, enum ocelot_reg reg, u32 offset)
|
2018-05-14 22:04:57 +02:00
|
|
|
{
|
2023-04-12 15:47:31 +03:00
|
|
|
enum ocelot_target target;
|
|
|
|
u32 addr, val;
|
2018-05-14 22:04:57 +02:00
|
|
|
|
2023-04-12 15:47:31 +03:00
|
|
|
ocelot_reg_to_target_addr(ocelot, reg, &target, &addr);
|
2018-05-14 22:04:57 +02:00
|
|
|
WARN_ON(!target);
|
|
|
|
|
2023-04-12 15:47:31 +03:00
|
|
|
regmap_read(ocelot->targets[target], addr + offset, &val);
|
2018-05-14 22:04:57 +02:00
|
|
|
return val;
|
|
|
|
}
|
2021-08-10 13:37:48 +01:00
|
|
|
EXPORT_SYMBOL_GPL(__ocelot_read_ix);
|
2018-05-14 22:04:57 +02:00
|
|
|
|
2023-04-12 15:47:30 +03:00
|
|
|
void __ocelot_write_ix(struct ocelot *ocelot, u32 val, enum ocelot_reg reg,
|
|
|
|
u32 offset)
|
2018-05-14 22:04:57 +02:00
|
|
|
{
|
2023-04-12 15:47:31 +03:00
|
|
|
enum ocelot_target target;
|
|
|
|
u32 addr;
|
2018-05-14 22:04:57 +02:00
|
|
|
|
2023-04-12 15:47:31 +03:00
|
|
|
ocelot_reg_to_target_addr(ocelot, reg, &target, &addr);
|
2018-05-14 22:04:57 +02:00
|
|
|
WARN_ON(!target);
|
|
|
|
|
2023-04-12 15:47:31 +03:00
|
|
|
regmap_write(ocelot->targets[target], addr + offset, val);
|
2018-05-14 22:04:57 +02:00
|
|
|
}
|
2021-08-10 13:37:48 +01:00
|
|
|
EXPORT_SYMBOL_GPL(__ocelot_write_ix);
|
2018-05-14 22:04:57 +02:00
|
|
|
|
2023-04-12 15:47:30 +03:00
|
|
|
void __ocelot_rmw_ix(struct ocelot *ocelot, u32 val, u32 mask,
|
|
|
|
enum ocelot_reg reg, u32 offset)
|
2018-05-14 22:04:57 +02:00
|
|
|
{
|
2023-04-12 15:47:31 +03:00
|
|
|
enum ocelot_target target;
|
|
|
|
u32 addr;
|
2018-05-14 22:04:57 +02:00
|
|
|
|
2023-04-12 15:47:31 +03:00
|
|
|
ocelot_reg_to_target_addr(ocelot, reg, &target, &addr);
|
2018-05-14 22:04:57 +02:00
|
|
|
WARN_ON(!target);
|
|
|
|
|
2023-04-12 15:47:31 +03:00
|
|
|
regmap_update_bits(ocelot->targets[target], addr + offset, mask, val);
|
2018-05-14 22:04:57 +02:00
|
|
|
}
|
2021-08-10 13:37:48 +01:00
|
|
|
EXPORT_SYMBOL_GPL(__ocelot_rmw_ix);
|
2018-05-14 22:04:57 +02:00
|
|
|
|
2023-04-12 15:47:30 +03:00
|
|
|
u32 ocelot_port_readl(struct ocelot_port *port, enum ocelot_reg reg)
|
2018-05-14 22:04:57 +02:00
|
|
|
{
|
2020-07-13 19:57:01 +03:00
|
|
|
struct ocelot *ocelot = port->ocelot;
|
|
|
|
u16 target = reg >> TARGET_OFFSET;
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
WARN_ON(!target);
|
|
|
|
|
|
|
|
regmap_read(port->target, ocelot->map[target][reg & REG_MASK], &val);
|
|
|
|
return val;
|
2018-05-14 22:04:57 +02:00
|
|
|
}
|
2021-08-10 13:37:48 +01:00
|
|
|
EXPORT_SYMBOL_GPL(ocelot_port_readl);
|
2018-05-14 22:04:57 +02:00
|
|
|
|
2023-04-12 15:47:30 +03:00
|
|
|
void ocelot_port_writel(struct ocelot_port *port, u32 val, enum ocelot_reg reg)
|
2018-05-14 22:04:57 +02:00
|
|
|
{
|
2020-07-13 19:57:01 +03:00
|
|
|
struct ocelot *ocelot = port->ocelot;
|
|
|
|
u16 target = reg >> TARGET_OFFSET;
|
|
|
|
|
|
|
|
WARN_ON(!target);
|
|
|
|
|
|
|
|
regmap_write(port->target, ocelot->map[target][reg & REG_MASK], val);
|
2018-05-14 22:04:57 +02:00
|
|
|
}
|
2021-08-10 13:37:48 +01:00
|
|
|
EXPORT_SYMBOL_GPL(ocelot_port_writel);
|
2018-05-14 22:04:57 +02:00
|
|
|
|
2023-04-12 15:47:30 +03:00
|
|
|
void ocelot_port_rmwl(struct ocelot_port *port, u32 val, u32 mask,
|
|
|
|
enum ocelot_reg reg)
|
net: dsa: felix: implement port flushing on .phylink_mac_link_down
There are several issues which may be seen when the link goes down while
forwarding traffic, all of which can be attributed to the fact that the
port flushing procedure from the reference manual was not closely
followed.
With flow control enabled on both the ingress port and the egress port,
it may happen when a link goes down that Ethernet packets are in flight.
In flow control mode, frames are held back and not dropped. When there
is enough traffic in flight (example: iperf3 TCP), then the ingress port
might enter congestion and never exit that state. This is a problem,
because it is the egress port's link that went down, and that has caused
the inability of the ingress port to send packets to any other port.
This is solved by flushing the egress port's queues when it goes down.
There is also a problem when performing stream splitting for
IEEE 802.1CB traffic (not yet upstream, but a sort of multicast,
basically). There, if one port from the destination ports mask goes
down, splitting the stream towards the other destinations will no longer
be performed. This can be traced down to this line:
ocelot_port_writel(ocelot_port, 0, DEV_MAC_ENA_CFG);
which should have been instead, as per the reference manual:
ocelot_port_rmwl(ocelot_port, 0, DEV_MAC_ENA_CFG_RX_ENA,
DEV_MAC_ENA_CFG);
Basically only DEV_MAC_ENA_CFG_RX_ENA should be disabled, but not
DEV_MAC_ENA_CFG_TX_ENA - I don't have further insight into why that is
the case, but apparently multicasting to several ports will cause issues
if at least one of them doesn't have DEV_MAC_ENA_CFG_TX_ENA set.
I am not sure what the state of the Ocelot VSC7514 driver is, but
probably not as bad as Felix/Seville, since VSC7514 uses phylib and has
the following in ocelot_adjust_link:
if (!phydev->link)
return;
therefore the port is not really put down when the link is lost, unlike
the DSA drivers which use .phylink_mac_link_down for that.
Nonetheless, I put ocelot_port_flush() in the common ocelot.c because it
needs to access some registers from drivers/net/ethernet/mscc/ocelot_rew.h
which are not exported in include/soc/mscc/ and a bugfix patch should
probably not move headers around.
Fixes: bdeced75b13f ("net: dsa: felix: Add PCS operations for PHYLINK")
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-02-08 19:36:27 +02:00
|
|
|
{
|
|
|
|
u32 cur = ocelot_port_readl(port, reg);
|
|
|
|
|
|
|
|
ocelot_port_writel(port, (cur & (~mask)) | val, reg);
|
|
|
|
}
|
2021-08-10 13:37:48 +01:00
|
|
|
EXPORT_SYMBOL_GPL(ocelot_port_rmwl);
|
net: dsa: felix: implement port flushing on .phylink_mac_link_down
There are several issues which may be seen when the link goes down while
forwarding traffic, all of which can be attributed to the fact that the
port flushing procedure from the reference manual was not closely
followed.
With flow control enabled on both the ingress port and the egress port,
it may happen when a link goes down that Ethernet packets are in flight.
In flow control mode, frames are held back and not dropped. When there
is enough traffic in flight (example: iperf3 TCP), then the ingress port
might enter congestion and never exit that state. This is a problem,
because it is the egress port's link that went down, and that has caused
the inability of the ingress port to send packets to any other port.
This is solved by flushing the egress port's queues when it goes down.
There is also a problem when performing stream splitting for
IEEE 802.1CB traffic (not yet upstream, but a sort of multicast,
basically). There, if one port from the destination ports mask goes
down, splitting the stream towards the other destinations will no longer
be performed. This can be traced down to this line:
ocelot_port_writel(ocelot_port, 0, DEV_MAC_ENA_CFG);
which should have been instead, as per the reference manual:
ocelot_port_rmwl(ocelot_port, 0, DEV_MAC_ENA_CFG_RX_ENA,
DEV_MAC_ENA_CFG);
Basically only DEV_MAC_ENA_CFG_RX_ENA should be disabled, but not
DEV_MAC_ENA_CFG_TX_ENA - I don't have further insight into why that is
the case, but apparently multicasting to several ports will cause issues
if at least one of them doesn't have DEV_MAC_ENA_CFG_TX_ENA set.
I am not sure what the state of the Ocelot VSC7514 driver is, but
probably not as bad as Felix/Seville, since VSC7514 uses phylib and has
the following in ocelot_adjust_link:
if (!phydev->link)
return;
therefore the port is not really put down when the link is lost, unlike
the DSA drivers which use .phylink_mac_link_down for that.
Nonetheless, I put ocelot_port_flush() in the common ocelot.c because it
needs to access some registers from drivers/net/ethernet/mscc/ocelot_rew.h
which are not exported in include/soc/mscc/ and a bugfix patch should
probably not move headers around.
Fixes: bdeced75b13f ("net: dsa: felix: Add PCS operations for PHYLINK")
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-02-08 19:36:27 +02:00
|
|
|
|
net: mscc: ocelot: introduce a new ocelot_target_{read,write} API
There are some targets (register blocks) in the Ocelot switch that are
instantiated more than once. For example, the VCAP IS1, IS2 and ES0
blocks all share the same register layout for interacting with the cache
for the TCAM and the action RAM.
For the VCAPs, the procedure for servicing them is actually common. We
just need an API specifying which VCAP we are talking to, and we do that
via these raw ocelot_target_read and ocelot_target_write accessors.
In plain ocelot_read, the target is encoded into the register enum
itself:
u16 target = reg >> TARGET_OFFSET;
For the VCAPs, the registers are currently defined like this:
enum ocelot_reg {
[...]
S2_CORE_UPDATE_CTRL = S2 << TARGET_OFFSET,
S2_CORE_MV_CFG,
S2_CACHE_ENTRY_DAT,
S2_CACHE_MASK_DAT,
S2_CACHE_ACTION_DAT,
S2_CACHE_CNT_DAT,
S2_CACHE_TG_DAT,
[...]
};
which is precisely what we want to avoid, because we'd have to duplicate
the same register map for S1 and for S0, and then figure out how to pass
VCAP instance-specific registers to the ocelot_read calls (basically
another lookup table that undoes the effect of shifting with
TARGET_OFFSET).
So for some targets, propose a more raw API, similar to what is
currently done with ocelot_port_readl and ocelot_port_writel. Those
targets can only be accessed with ocelot_target_{read,write} and not
with ocelot_{read,write} after the conversion, which is fine.
The VCAP registers are not actually modified to use this new API as of
this patch. They will be modified in the next one.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-09-30 01:27:21 +03:00
|
|
|
u32 __ocelot_target_read_ix(struct ocelot *ocelot, enum ocelot_target target,
|
|
|
|
u32 reg, u32 offset)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
regmap_read(ocelot->targets[target],
|
|
|
|
ocelot->map[target][reg] + offset, &val);
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
void __ocelot_target_write_ix(struct ocelot *ocelot, enum ocelot_target target,
|
|
|
|
u32 val, u32 reg, u32 offset)
|
|
|
|
{
|
|
|
|
regmap_write(ocelot->targets[target],
|
|
|
|
ocelot->map[target][reg] + offset, val);
|
|
|
|
}
|
|
|
|
|
2018-05-14 22:04:57 +02:00
|
|
|
int ocelot_regfields_init(struct ocelot *ocelot,
|
|
|
|
const struct reg_field *const regfields)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
u16 target;
|
|
|
|
|
|
|
|
for (i = 0; i < REGFIELD_MAX; i++) {
|
|
|
|
struct reg_field regfield = {};
|
|
|
|
u32 reg = regfields[i].reg;
|
|
|
|
|
|
|
|
if (!reg)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
target = regfields[i].reg >> TARGET_OFFSET;
|
|
|
|
|
|
|
|
regfield.reg = ocelot->map[target][reg & REG_MASK];
|
|
|
|
regfield.lsb = regfields[i].lsb;
|
|
|
|
regfield.msb = regfields[i].msb;
|
net: mscc: ocelot: convert QSYS_SWITCH_PORT_MODE and SYS_PORT_MODE to regfields
Currently Felix and Ocelot share the same bit layout in these per-port
registers, but Seville does not. So we need reg_fields for that.
Actually since these are per-port registers, we need to also specify the
number of ports, and register size per port, and use the regmap API for
multiple ports.
There's a more subtle point to be made about the other 2 register
fields:
- QSYS_SWITCH_PORT_MODE_SCH_NEXT_CFG
- QSYS_SWITCH_PORT_MODE_INGRESS_DROP_MODE
which we are not writing any longer, for 2 reasons:
- Using the previous API (ocelot_write_rix), we were only writing 1 for
Felix and Ocelot, which was their hardware-default value, and which
there wasn't any intention in changing.
- In the case of SCH_NEXT_CFG, in fact Seville does not have this
register field at all, and therefore, if we want to have common code
we would be required to not write to it.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2020-07-13 19:57:03 +03:00
|
|
|
regfield.id_size = regfields[i].id_size;
|
|
|
|
regfield.id_offset = regfields[i].id_offset;
|
2018-05-14 22:04:57 +02:00
|
|
|
|
|
|
|
ocelot->regfields[i] =
|
|
|
|
devm_regmap_field_alloc(ocelot->dev,
|
|
|
|
ocelot->targets[target],
|
|
|
|
regfield);
|
|
|
|
|
|
|
|
if (IS_ERR(ocelot->regfields[i]))
|
|
|
|
return PTR_ERR(ocelot->regfields[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2021-08-10 13:37:48 +01:00
|
|
|
EXPORT_SYMBOL_GPL(ocelot_regfields_init);
|
2018-05-14 22:04:57 +02:00
|
|
|
|
|
|
|
static struct regmap_config ocelot_regmap_config = {
|
|
|
|
.reg_bits = 32,
|
|
|
|
.val_bits = 32,
|
|
|
|
.reg_stride = 4,
|
|
|
|
};
|
|
|
|
|
2019-11-14 17:03:20 +02:00
|
|
|
struct regmap *ocelot_regmap_init(struct ocelot *ocelot, struct resource *res)
|
2018-05-14 22:04:57 +02:00
|
|
|
{
|
|
|
|
void __iomem *regs;
|
|
|
|
|
|
|
|
regs = devm_ioremap_resource(ocelot->dev, res);
|
|
|
|
if (IS_ERR(regs))
|
|
|
|
return ERR_CAST(regs);
|
|
|
|
|
2019-11-14 17:03:20 +02:00
|
|
|
ocelot_regmap_config.name = res->name;
|
|
|
|
|
|
|
|
return devm_regmap_init_mmio(ocelot->dev, regs, &ocelot_regmap_config);
|
2018-05-14 22:04:57 +02:00
|
|
|
}
|
2021-08-10 13:37:48 +01:00
|
|
|
EXPORT_SYMBOL_GPL(ocelot_regmap_init);
|