Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,39 @@
# SPDX-License-Identifier: (GPL-2.0 OR MIT)
config NET_VENDOR_MICROSEMI
bool "Microsemi devices"
default y
help
If you have a network (Ethernet) card belonging to this class, say Y.
Note that the answer to this question doesn't directly affect the
kernel: saying N will just cause the configurator to skip all
the questions about Microsemi devices.
if NET_VENDOR_MICROSEMI
# Users should depend on NET_SWITCHDEV, HAS_IOMEM, BRIDGE
config MSCC_OCELOT_SWITCH_LIB
depends on PTP_1588_CLOCK_OPTIONAL
select NET_DEVLINK
select REGMAP_MMIO
select PACKING
select PHYLINK
tristate
help
This is a hardware support library for Ocelot network switches. It is
used by switchdev as well as by DSA drivers.
config MSCC_OCELOT_SWITCH
tristate "Ocelot switch driver"
depends on PTP_1588_CLOCK_OPTIONAL
depends on BRIDGE || BRIDGE=n
depends on NET_SWITCHDEV
depends on HAS_IOMEM
depends on OF
select MSCC_OCELOT_SWITCH_LIB
select GENERIC_PHY
help
This driver supports the Ocelot network switch device as present on
the Ocelot SoCs (VSC7514).
endif # NET_VENDOR_MICROSEMI
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: (GPL-2.0 OR MIT)
obj-$(CONFIG_MSCC_OCELOT_SWITCH_LIB) += mscc_ocelot_switch_lib.o
mscc_ocelot_switch_lib-y := \
ocelot.o \
ocelot_devlink.o \
ocelot_flower.o \
ocelot_io.o \
ocelot_mm.o \
ocelot_police.o \
ocelot_ptp.o \
ocelot_stats.o \
ocelot_vcap.o \
vsc7514_regs.o
mscc_ocelot_switch_lib-$(CONFIG_BRIDGE_MRP) += ocelot_mrp.o
obj-$(CONFIG_MSCC_OCELOT_SWITCH) += mscc_ocelot.o
mscc_ocelot-y := \
ocelot_fdma.o \
ocelot_net.o \
ocelot_vsc7514.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,126 @@
/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
/*
* Microsemi Ocelot Switch driver
*
* Copyright (c) 2017 Microsemi Corporation
*/
#ifndef _MSCC_OCELOT_H_
#define _MSCC_OCELOT_H_
#include <linux/bitops.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/net_tstamp.h>
#include <linux/phylink.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <soc/mscc/ocelot_qsys.h>
#include <soc/mscc/ocelot_sys.h>
#include <soc/mscc/ocelot_dev.h>
#include <soc/mscc/ocelot_ana.h>
#include <soc/mscc/ocelot_ptp.h>
#include <soc/mscc/ocelot_vcap.h>
#include <soc/mscc/ocelot.h>
#include "ocelot_rew.h"
#include "ocelot_qs.h"
#define OCELOT_STANDALONE_PVID 0
#define OCELOT_BUFFER_CELL_SZ 60
#define OCELOT_STATS_CHECK_DELAY (2 * HZ)
#define OCELOT_PTP_QUEUE_SZ 128
#define OCELOT_JUMBO_MTU 9000
struct ocelot_port_tc {
bool block_shared;
unsigned long offload_cnt;
unsigned long ingress_mirred_id;
unsigned long egress_mirred_id;
unsigned long police_id;
};
struct ocelot_port_private {
struct ocelot_port port;
struct net_device *dev;
struct phylink *phylink;
struct phylink_config phylink_config;
struct ocelot_port_tc tc;
};
/* A (PGID) port mask structure, encoding the 2^ocelot->num_phys_ports
* possibilities of egress port masks for L2 multicast traffic.
* For a switch with 9 user ports, there are 512 possible port masks, but the
* hardware only has 46 individual PGIDs that it can forward multicast traffic
* to. So we need a structure that maps the limited PGID indices to the port
* destinations requested by the user for L2 multicast.
*/
struct ocelot_pgid {
unsigned long ports;
int index;
refcount_t refcount;
struct list_head list;
};
struct ocelot_multicast {
struct list_head list;
enum macaccess_entry_type entry_type;
unsigned char addr[ETH_ALEN];
u16 vid;
u16 ports;
struct ocelot_pgid *pgid;
};
static inline void ocelot_reg_to_target_addr(struct ocelot *ocelot,
enum ocelot_reg reg,
enum ocelot_target *target,
u32 *addr)
{
*target = reg >> TARGET_OFFSET;
*addr = ocelot->map[*target][reg & REG_MASK];
}
int ocelot_bridge_num_find(struct ocelot *ocelot,
const struct net_device *bridge);
int ocelot_mact_learn(struct ocelot *ocelot, int port,
const unsigned char mac[ETH_ALEN],
unsigned int vid, enum macaccess_entry_type type);
int ocelot_mact_forget(struct ocelot *ocelot,
const unsigned char mac[ETH_ALEN], unsigned int vid);
struct net_device *ocelot_port_to_netdev(struct ocelot *ocelot, int port);
int ocelot_netdev_to_port(struct ocelot *ocelot, struct net_device *dev);
int ocelot_probe_port(struct ocelot *ocelot, int port, struct regmap *target,
struct device_node *portnp);
void ocelot_release_port(struct ocelot_port *ocelot_port);
int ocelot_port_devlink_init(struct ocelot *ocelot, int port,
enum devlink_port_flavour flavour);
void ocelot_port_devlink_teardown(struct ocelot *ocelot, int port);
int ocelot_trap_add(struct ocelot *ocelot, int port,
unsigned long cookie, bool take_ts,
void (*populate)(struct ocelot_vcap_filter *f));
int ocelot_trap_del(struct ocelot *ocelot, int port, unsigned long cookie);
struct ocelot_mirror *ocelot_mirror_get(struct ocelot *ocelot, int to,
struct netlink_ext_ack *extack);
void ocelot_mirror_put(struct ocelot *ocelot);
int ocelot_stats_init(struct ocelot *ocelot);
void ocelot_stats_deinit(struct ocelot *ocelot);
int ocelot_mm_init(struct ocelot *ocelot);
void ocelot_port_change_fp(struct ocelot *ocelot, int port,
unsigned long preemptible_tcs);
void ocelot_port_update_active_preemptible_tcs(struct ocelot *ocelot, int port);
extern struct notifier_block ocelot_netdevice_nb;
extern struct notifier_block ocelot_switchdev_nb;
extern struct notifier_block ocelot_switchdev_blocking_nb;
extern const struct devlink_ops ocelot_devlink_ops;
#endif
@@ -0,0 +1,916 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/* Copyright 2020-2021 NXP
*/
#include <net/devlink.h>
#include "ocelot.h"
/* The queue system tracks four resource consumptions:
* Resource 0: Memory tracked per source port
* Resource 1: Frame references tracked per source port
* Resource 2: Memory tracked per destination port
* Resource 3: Frame references tracked per destination port
*/
#define OCELOT_RESOURCE_SZ 256
#define OCELOT_NUM_RESOURCES 4
#define BUF_xxxx_I (0 * OCELOT_RESOURCE_SZ)
#define REF_xxxx_I (1 * OCELOT_RESOURCE_SZ)
#define BUF_xxxx_E (2 * OCELOT_RESOURCE_SZ)
#define REF_xxxx_E (3 * OCELOT_RESOURCE_SZ)
/* For each resource type there are 4 types of watermarks:
* Q_RSRV: reservation per QoS class per port
* PRIO_SHR: sharing watermark per QoS class across all ports
* P_RSRV: reservation per port
* COL_SHR: sharing watermark per color (drop precedence) across all ports
*/
#define xxx_Q_RSRV_x 0
#define xxx_PRIO_SHR_x 216
#define xxx_P_RSRV_x 224
#define xxx_COL_SHR_x 254
/* Reservation Watermarks
* ----------------------
*
* For setting up the reserved areas, egress watermarks exist per port and per
* QoS class for both ingress and egress.
*/
/* Amount of packet buffer
* | per QoS class
* | | reserved
* | | | per egress port
* | | | |
* V V v v
* BUF_Q_RSRV_E
*/
#define BUF_Q_RSRV_E(port, prio) \
(BUF_xxxx_E + xxx_Q_RSRV_x + OCELOT_NUM_TC * (port) + (prio))
/* Amount of packet buffer
* | for all port's traffic classes
* | | reserved
* | | | per egress port
* | | | |
* V V v v
* BUF_P_RSRV_E
*/
#define BUF_P_RSRV_E(port) \
(BUF_xxxx_E + xxx_P_RSRV_x + (port))
/* Amount of packet buffer
* | per QoS class
* | | reserved
* | | | per ingress port
* | | | |
* V V v v
* BUF_Q_RSRV_I
*/
#define BUF_Q_RSRV_I(port, prio) \
(BUF_xxxx_I + xxx_Q_RSRV_x + OCELOT_NUM_TC * (port) + (prio))
/* Amount of packet buffer
* | for all port's traffic classes
* | | reserved
* | | | per ingress port
* | | | |
* V V v v
* BUF_P_RSRV_I
*/
#define BUF_P_RSRV_I(port) \
(BUF_xxxx_I + xxx_P_RSRV_x + (port))
/* Amount of frame references
* | per QoS class
* | | reserved
* | | | per egress port
* | | | |
* V V v v
* REF_Q_RSRV_E
*/
#define REF_Q_RSRV_E(port, prio) \
(REF_xxxx_E + xxx_Q_RSRV_x + OCELOT_NUM_TC * (port) + (prio))
/* Amount of frame references
* | for all port's traffic classes
* | | reserved
* | | | per egress port
* | | | |
* V V v v
* REF_P_RSRV_E
*/
#define REF_P_RSRV_E(port) \
(REF_xxxx_E + xxx_P_RSRV_x + (port))
/* Amount of frame references
* | per QoS class
* | | reserved
* | | | per ingress port
* | | | |
* V V v v
* REF_Q_RSRV_I
*/
#define REF_Q_RSRV_I(port, prio) \
(REF_xxxx_I + xxx_Q_RSRV_x + OCELOT_NUM_TC * (port) + (prio))
/* Amount of frame references
* | for all port's traffic classes
* | | reserved
* | | | per ingress port
* | | | |
* V V v v
* REF_P_RSRV_I
*/
#define REF_P_RSRV_I(port) \
(REF_xxxx_I + xxx_P_RSRV_x + (port))
/* Sharing Watermarks
* ------------------
*
* The shared memory area is shared between all ports.
*/
/* Amount of buffer
* | per QoS class
* | | from the shared memory area
* | | | for egress traffic
* | | | |
* V V v v
* BUF_PRIO_SHR_E
*/
#define BUF_PRIO_SHR_E(prio) \
(BUF_xxxx_E + xxx_PRIO_SHR_x + (prio))
/* Amount of buffer
* | per color (drop precedence level)
* | | from the shared memory area
* | | | for egress traffic
* | | | |
* V V v v
* BUF_COL_SHR_E
*/
#define BUF_COL_SHR_E(dp) \
(BUF_xxxx_E + xxx_COL_SHR_x + (1 - (dp)))
/* Amount of buffer
* | per QoS class
* | | from the shared memory area
* | | | for ingress traffic
* | | | |
* V V v v
* BUF_PRIO_SHR_I
*/
#define BUF_PRIO_SHR_I(prio) \
(BUF_xxxx_I + xxx_PRIO_SHR_x + (prio))
/* Amount of buffer
* | per color (drop precedence level)
* | | from the shared memory area
* | | | for ingress traffic
* | | | |
* V V v v
* BUF_COL_SHR_I
*/
#define BUF_COL_SHR_I(dp) \
(BUF_xxxx_I + xxx_COL_SHR_x + (1 - (dp)))
/* Amount of frame references
* | per QoS class
* | | from the shared area
* | | | for egress traffic
* | | | |
* V V v v
* REF_PRIO_SHR_E
*/
#define REF_PRIO_SHR_E(prio) \
(REF_xxxx_E + xxx_PRIO_SHR_x + (prio))
/* Amount of frame references
* | per color (drop precedence level)
* | | from the shared area
* | | | for egress traffic
* | | | |
* V V v v
* REF_COL_SHR_E
*/
#define REF_COL_SHR_E(dp) \
(REF_xxxx_E + xxx_COL_SHR_x + (1 - (dp)))
/* Amount of frame references
* | per QoS class
* | | from the shared area
* | | | for ingress traffic
* | | | |
* V V v v
* REF_PRIO_SHR_I
*/
#define REF_PRIO_SHR_I(prio) \
(REF_xxxx_I + xxx_PRIO_SHR_x + (prio))
/* Amount of frame references
* | per color (drop precedence level)
* | | from the shared area
* | | | for ingress traffic
* | | | |
* V V v v
* REF_COL_SHR_I
*/
#define REF_COL_SHR_I(dp) \
(REF_xxxx_I + xxx_COL_SHR_x + (1 - (dp)))
static u32 ocelot_wm_read(struct ocelot *ocelot, int index)
{
int wm = ocelot_read_gix(ocelot, QSYS_RES_CFG, index);
return ocelot->ops->wm_dec(wm);
}
static void ocelot_wm_write(struct ocelot *ocelot, int index, u32 val)
{
u32 wm = ocelot->ops->wm_enc(val);
ocelot_write_gix(ocelot, wm, QSYS_RES_CFG, index);
}
static void ocelot_wm_status(struct ocelot *ocelot, int index, u32 *inuse,
u32 *maxuse)
{
int res_stat = ocelot_read_gix(ocelot, QSYS_RES_STAT, index);
return ocelot->ops->wm_stat(res_stat, inuse, maxuse);
}
/* The hardware comes out of reset with strange defaults: the sum of all
* reservations for frame memory is larger than the total buffer size.
* One has to wonder how can the reservation watermarks still guarantee
* anything under congestion.
* Bring some sense into the hardware by changing the defaults to disable all
* reservations and rely only on the sharing watermark for frames with drop
* precedence 0. The user can still explicitly request reservations per port
* and per port-tc through devlink-sb.
*/
static void ocelot_disable_reservation_watermarks(struct ocelot *ocelot,
int port)
{
int prio;
for (prio = 0; prio < OCELOT_NUM_TC; prio++) {
ocelot_wm_write(ocelot, BUF_Q_RSRV_I(port, prio), 0);
ocelot_wm_write(ocelot, BUF_Q_RSRV_E(port, prio), 0);
ocelot_wm_write(ocelot, REF_Q_RSRV_I(port, prio), 0);
ocelot_wm_write(ocelot, REF_Q_RSRV_E(port, prio), 0);
}
ocelot_wm_write(ocelot, BUF_P_RSRV_I(port), 0);
ocelot_wm_write(ocelot, BUF_P_RSRV_E(port), 0);
ocelot_wm_write(ocelot, REF_P_RSRV_I(port), 0);
ocelot_wm_write(ocelot, REF_P_RSRV_E(port), 0);
}
/* We want the sharing watermarks to consume all nonreserved resources, for
* efficient resource utilization (a single traffic flow should be able to use
* up the entire buffer space and frame resources as long as there's no
* interference).
* The switch has 10 sharing watermarks per lookup: 8 per traffic class and 2
* per color (drop precedence).
* The trouble with configuring these sharing watermarks is that:
* (1) There's a risk that we overcommit the resources if we configure
* (a) all 8 per-TC sharing watermarks to the max
* (b) all 2 per-color sharing watermarks to the max
* (2) There's a risk that we undercommit the resources if we configure
* (a) all 8 per-TC sharing watermarks to "max / 8"
* (b) all 2 per-color sharing watermarks to "max / 2"
* So for Linux, let's just disable the sharing watermarks per traffic class
* (setting them to 0 will make them always exceeded), and rely only on the
* sharing watermark for drop priority 0. So frames with drop priority set to 1
* by QoS classification or policing will still be allowed, but only as long as
* the port and port-TC reservations are not exceeded.
*/
static void ocelot_disable_tc_sharing_watermarks(struct ocelot *ocelot)
{
int prio;
for (prio = 0; prio < OCELOT_NUM_TC; prio++) {
ocelot_wm_write(ocelot, BUF_PRIO_SHR_I(prio), 0);
ocelot_wm_write(ocelot, BUF_PRIO_SHR_E(prio), 0);
ocelot_wm_write(ocelot, REF_PRIO_SHR_I(prio), 0);
ocelot_wm_write(ocelot, REF_PRIO_SHR_E(prio), 0);
}
}
static void ocelot_get_buf_rsrv(struct ocelot *ocelot, u32 *buf_rsrv_i,
u32 *buf_rsrv_e)
{
int port, prio;
*buf_rsrv_i = 0;
*buf_rsrv_e = 0;
for (port = 0; port <= ocelot->num_phys_ports; port++) {
for (prio = 0; prio < OCELOT_NUM_TC; prio++) {
*buf_rsrv_i += ocelot_wm_read(ocelot,
BUF_Q_RSRV_I(port, prio));
*buf_rsrv_e += ocelot_wm_read(ocelot,
BUF_Q_RSRV_E(port, prio));
}
*buf_rsrv_i += ocelot_wm_read(ocelot, BUF_P_RSRV_I(port));
*buf_rsrv_e += ocelot_wm_read(ocelot, BUF_P_RSRV_E(port));
}
*buf_rsrv_i *= OCELOT_BUFFER_CELL_SZ;
*buf_rsrv_e *= OCELOT_BUFFER_CELL_SZ;
}
static void ocelot_get_ref_rsrv(struct ocelot *ocelot, u32 *ref_rsrv_i,
u32 *ref_rsrv_e)
{
int port, prio;
*ref_rsrv_i = 0;
*ref_rsrv_e = 0;
for (port = 0; port <= ocelot->num_phys_ports; port++) {
for (prio = 0; prio < OCELOT_NUM_TC; prio++) {
*ref_rsrv_i += ocelot_wm_read(ocelot,
REF_Q_RSRV_I(port, prio));
*ref_rsrv_e += ocelot_wm_read(ocelot,
REF_Q_RSRV_E(port, prio));
}
*ref_rsrv_i += ocelot_wm_read(ocelot, REF_P_RSRV_I(port));
*ref_rsrv_e += ocelot_wm_read(ocelot, REF_P_RSRV_E(port));
}
}
/* Calculate all reservations, then set up the sharing watermark for DP=0 to
* consume the remaining resources up to the pool's configured size.
*/
static void ocelot_setup_sharing_watermarks(struct ocelot *ocelot)
{
u32 buf_rsrv_i, buf_rsrv_e;
u32 ref_rsrv_i, ref_rsrv_e;
u32 buf_shr_i, buf_shr_e;
u32 ref_shr_i, ref_shr_e;
ocelot_get_buf_rsrv(ocelot, &buf_rsrv_i, &buf_rsrv_e);
ocelot_get_ref_rsrv(ocelot, &ref_rsrv_i, &ref_rsrv_e);
buf_shr_i = ocelot->pool_size[OCELOT_SB_BUF][OCELOT_SB_POOL_ING] -
buf_rsrv_i;
buf_shr_e = ocelot->pool_size[OCELOT_SB_BUF][OCELOT_SB_POOL_EGR] -
buf_rsrv_e;
ref_shr_i = ocelot->pool_size[OCELOT_SB_REF][OCELOT_SB_POOL_ING] -
ref_rsrv_i;
ref_shr_e = ocelot->pool_size[OCELOT_SB_REF][OCELOT_SB_POOL_EGR] -
ref_rsrv_e;
buf_shr_i /= OCELOT_BUFFER_CELL_SZ;
buf_shr_e /= OCELOT_BUFFER_CELL_SZ;
ocelot_wm_write(ocelot, BUF_COL_SHR_I(0), buf_shr_i);
ocelot_wm_write(ocelot, BUF_COL_SHR_E(0), buf_shr_e);
ocelot_wm_write(ocelot, REF_COL_SHR_E(0), ref_shr_e);
ocelot_wm_write(ocelot, REF_COL_SHR_I(0), ref_shr_i);
ocelot_wm_write(ocelot, BUF_COL_SHR_I(1), 0);
ocelot_wm_write(ocelot, BUF_COL_SHR_E(1), 0);
ocelot_wm_write(ocelot, REF_COL_SHR_E(1), 0);
ocelot_wm_write(ocelot, REF_COL_SHR_I(1), 0);
}
/* Ensure that all reservations can be enforced */
static int ocelot_watermark_validate(struct ocelot *ocelot,
struct netlink_ext_ack *extack)
{
u32 buf_rsrv_i, buf_rsrv_e;
u32 ref_rsrv_i, ref_rsrv_e;
ocelot_get_buf_rsrv(ocelot, &buf_rsrv_i, &buf_rsrv_e);
ocelot_get_ref_rsrv(ocelot, &ref_rsrv_i, &ref_rsrv_e);
if (buf_rsrv_i > ocelot->pool_size[OCELOT_SB_BUF][OCELOT_SB_POOL_ING]) {
NL_SET_ERR_MSG_MOD(extack,
"Ingress frame reservations exceed pool size");
return -ERANGE;
}
if (buf_rsrv_e > ocelot->pool_size[OCELOT_SB_BUF][OCELOT_SB_POOL_EGR]) {
NL_SET_ERR_MSG_MOD(extack,
"Egress frame reservations exceed pool size");
return -ERANGE;
}
if (ref_rsrv_i > ocelot->pool_size[OCELOT_SB_REF][OCELOT_SB_POOL_ING]) {
NL_SET_ERR_MSG_MOD(extack,
"Ingress reference reservations exceed pool size");
return -ERANGE;
}
if (ref_rsrv_e > ocelot->pool_size[OCELOT_SB_REF][OCELOT_SB_POOL_EGR]) {
NL_SET_ERR_MSG_MOD(extack,
"Egress reference reservations exceed pool size");
return -ERANGE;
}
return 0;
}
/* The hardware works like this:
*
* Frame forwarding decision taken
* |
* v
* +--------------------+--------------------+--------------------+
* | | | |
* v v v v
* Ingress memory Egress memory Ingress frame Egress frame
* check check reference check reference check
* | | | |
* v v v v
* BUF_Q_RSRV_I ok BUF_Q_RSRV_E ok REF_Q_RSRV_I ok REF_Q_RSRV_E ok
*(src port, prio) -+ (dst port, prio) -+ (src port, prio) -+ (dst port, prio) -+
* | | | | | | | |
* |exceeded | |exceeded | |exceeded | |exceeded |
* v | v | v | v |
* BUF_P_RSRV_I ok| BUF_P_RSRV_E ok| REF_P_RSRV_I ok| REF_P_RSRV_E ok|
* (src port) ----+ (dst port) ----+ (src port) ----+ (dst port) -----+
* | | | | | | | |
* |exceeded | |exceeded | |exceeded | |exceeded |
* v | v | v | v |
* BUF_PRIO_SHR_I ok| BUF_PRIO_SHR_E ok| REF_PRIO_SHR_I ok| REF_PRIO_SHR_E ok|
* (prio) ------+ (prio) ------+ (prio) ------+ (prio) -------+
* | | | | | | | |
* |exceeded | |exceeded | |exceeded | |exceeded |
* v | v | v | v |
* BUF_COL_SHR_I ok| BUF_COL_SHR_E ok| REF_COL_SHR_I ok| REF_COL_SHR_E ok|
* (dp) -------+ (dp) -------+ (dp) -------+ (dp) --------+
* | | | | | | | |
* |exceeded | |exceeded | |exceeded | |exceeded |
* v v v v v v v v
* fail success fail success fail success fail success
* | | | | | | | |
* v v v v v v v v
* +-----+----+ +-----+----+ +-----+----+ +-----+-----+
* | | | |
* +-------> OR <-------+ +-------> OR <-------+
* | |
* v v
* +----------------> AND <-----------------+
* |
* v
* FIFO drop / accept
*
* We are modeling each of the 4 parallel lookups as a devlink-sb pool.
* At least one (ingress or egress) memory pool and one (ingress or egress)
* frame reference pool need to have resources for frame acceptance to succeed.
*
* The following watermarks are controlled explicitly through devlink-sb:
* BUF_Q_RSRV_I, BUF_Q_RSRV_E, REF_Q_RSRV_I, REF_Q_RSRV_E
* BUF_P_RSRV_I, BUF_P_RSRV_E, REF_P_RSRV_I, REF_P_RSRV_E
* The following watermarks are controlled implicitly through devlink-sb:
* BUF_COL_SHR_I, BUF_COL_SHR_E, REF_COL_SHR_I, REF_COL_SHR_E
* The following watermarks are unused and disabled:
* BUF_PRIO_SHR_I, BUF_PRIO_SHR_E, REF_PRIO_SHR_I, REF_PRIO_SHR_E
*
* This function overrides the hardware defaults with more sane ones (no
* reservations by default, let sharing use all resources) and disables the
* unused watermarks.
*/
static void ocelot_watermark_init(struct ocelot *ocelot)
{
int all_tcs = GENMASK(OCELOT_NUM_TC - 1, 0);
int port;
ocelot_write(ocelot, all_tcs, QSYS_RES_QOS_MODE);
for (port = 0; port <= ocelot->num_phys_ports; port++)
ocelot_disable_reservation_watermarks(ocelot, port);
ocelot_disable_tc_sharing_watermarks(ocelot);
ocelot_setup_sharing_watermarks(ocelot);
}
/* Watermark encode
* Bit 8: Unit; 0:1, 1:16
* Bit 7-0: Value to be multiplied with unit
*/
u16 ocelot_wm_enc(u16 value)
{
WARN_ON(value >= 16 * BIT(8));
if (value >= BIT(8))
return BIT(8) | (value / 16);
return value;
}
EXPORT_SYMBOL(ocelot_wm_enc);
u16 ocelot_wm_dec(u16 wm)
{
if (wm & BIT(8))
return (wm & GENMASK(7, 0)) * 16;
return wm;
}
EXPORT_SYMBOL(ocelot_wm_dec);
void ocelot_wm_stat(u32 val, u32 *inuse, u32 *maxuse)
{
*inuse = (val & GENMASK(23, 12)) >> 12;
*maxuse = val & GENMASK(11, 0);
}
EXPORT_SYMBOL(ocelot_wm_stat);
/* Pool size and type are fixed up at runtime. Keeping this structure to
* look up the cell size multipliers.
*/
static const struct devlink_sb_pool_info ocelot_sb_pool[] = {
[OCELOT_SB_BUF] = {
.cell_size = OCELOT_BUFFER_CELL_SZ,
.threshold_type = DEVLINK_SB_THRESHOLD_TYPE_STATIC,
},
[OCELOT_SB_REF] = {
.cell_size = 1,
.threshold_type = DEVLINK_SB_THRESHOLD_TYPE_STATIC,
},
};
/* Returns the pool size configured through ocelot_sb_pool_set */
int ocelot_sb_pool_get(struct ocelot *ocelot, unsigned int sb_index,
u16 pool_index,
struct devlink_sb_pool_info *pool_info)
{
if (sb_index >= OCELOT_SB_NUM)
return -ENODEV;
if (pool_index >= OCELOT_SB_POOL_NUM)
return -ENODEV;
*pool_info = ocelot_sb_pool[sb_index];
pool_info->size = ocelot->pool_size[sb_index][pool_index];
if (pool_index)
pool_info->pool_type = DEVLINK_SB_POOL_TYPE_INGRESS;
else
pool_info->pool_type = DEVLINK_SB_POOL_TYPE_EGRESS;
return 0;
}
EXPORT_SYMBOL(ocelot_sb_pool_get);
/* The pool size received here configures the total amount of resources used on
* ingress (or on egress, depending upon the pool index). The pool size, minus
* the values for the port and port-tc reservations, is written into the
* COL_SHR(dp=0) sharing watermark.
*/
int ocelot_sb_pool_set(struct ocelot *ocelot, unsigned int sb_index,
u16 pool_index, u32 size,
enum devlink_sb_threshold_type threshold_type,
struct netlink_ext_ack *extack)
{
u32 old_pool_size;
int err;
if (sb_index >= OCELOT_SB_NUM) {
NL_SET_ERR_MSG_MOD(extack,
"Invalid sb, use 0 for buffers and 1 for frame references");
return -ENODEV;
}
if (pool_index >= OCELOT_SB_POOL_NUM) {
NL_SET_ERR_MSG_MOD(extack,
"Invalid pool, use 0 for ingress and 1 for egress");
return -ENODEV;
}
if (threshold_type != DEVLINK_SB_THRESHOLD_TYPE_STATIC) {
NL_SET_ERR_MSG_MOD(extack,
"Only static threshold supported");
return -EOPNOTSUPP;
}
old_pool_size = ocelot->pool_size[sb_index][pool_index];
ocelot->pool_size[sb_index][pool_index] = size;
err = ocelot_watermark_validate(ocelot, extack);
if (err) {
ocelot->pool_size[sb_index][pool_index] = old_pool_size;
return err;
}
ocelot_setup_sharing_watermarks(ocelot);
return 0;
}
EXPORT_SYMBOL(ocelot_sb_pool_set);
/* This retrieves the configuration made with ocelot_sb_port_pool_set */
int ocelot_sb_port_pool_get(struct ocelot *ocelot, int port,
unsigned int sb_index, u16 pool_index,
u32 *p_threshold)
{
int wm_index;
switch (sb_index) {
case OCELOT_SB_BUF:
if (pool_index == OCELOT_SB_POOL_ING)
wm_index = BUF_P_RSRV_I(port);
else
wm_index = BUF_P_RSRV_E(port);
break;
case OCELOT_SB_REF:
if (pool_index == OCELOT_SB_POOL_ING)
wm_index = REF_P_RSRV_I(port);
else
wm_index = REF_P_RSRV_E(port);
break;
default:
return -ENODEV;
}
*p_threshold = ocelot_wm_read(ocelot, wm_index);
*p_threshold *= ocelot_sb_pool[sb_index].cell_size;
return 0;
}
EXPORT_SYMBOL(ocelot_sb_port_pool_get);
/* This configures the P_RSRV per-port reserved resource watermark */
int ocelot_sb_port_pool_set(struct ocelot *ocelot, int port,
unsigned int sb_index, u16 pool_index,
u32 threshold, struct netlink_ext_ack *extack)
{
int wm_index, err;
u32 old_thr;
switch (sb_index) {
case OCELOT_SB_BUF:
if (pool_index == OCELOT_SB_POOL_ING)
wm_index = BUF_P_RSRV_I(port);
else
wm_index = BUF_P_RSRV_E(port);
break;
case OCELOT_SB_REF:
if (pool_index == OCELOT_SB_POOL_ING)
wm_index = REF_P_RSRV_I(port);
else
wm_index = REF_P_RSRV_E(port);
break;
default:
NL_SET_ERR_MSG_MOD(extack, "Invalid shared buffer");
return -ENODEV;
}
threshold /= ocelot_sb_pool[sb_index].cell_size;
old_thr = ocelot_wm_read(ocelot, wm_index);
ocelot_wm_write(ocelot, wm_index, threshold);
err = ocelot_watermark_validate(ocelot, extack);
if (err) {
ocelot_wm_write(ocelot, wm_index, old_thr);
return err;
}
ocelot_setup_sharing_watermarks(ocelot);
return 0;
}
EXPORT_SYMBOL(ocelot_sb_port_pool_set);
/* This retrieves the configuration done by ocelot_sb_tc_pool_bind_set */
int ocelot_sb_tc_pool_bind_get(struct ocelot *ocelot, int port,
unsigned int sb_index, u16 tc_index,
enum devlink_sb_pool_type pool_type,
u16 *p_pool_index, u32 *p_threshold)
{
int wm_index;
switch (sb_index) {
case OCELOT_SB_BUF:
if (pool_type == DEVLINK_SB_POOL_TYPE_INGRESS)
wm_index = BUF_Q_RSRV_I(port, tc_index);
else
wm_index = BUF_Q_RSRV_E(port, tc_index);
break;
case OCELOT_SB_REF:
if (pool_type == DEVLINK_SB_POOL_TYPE_INGRESS)
wm_index = REF_Q_RSRV_I(port, tc_index);
else
wm_index = REF_Q_RSRV_E(port, tc_index);
break;
default:
return -ENODEV;
}
*p_threshold = ocelot_wm_read(ocelot, wm_index);
*p_threshold *= ocelot_sb_pool[sb_index].cell_size;
if (pool_type == DEVLINK_SB_POOL_TYPE_INGRESS)
*p_pool_index = 0;
else
*p_pool_index = 1;
return 0;
}
EXPORT_SYMBOL(ocelot_sb_tc_pool_bind_get);
/* This configures the Q_RSRV per-port-tc reserved resource watermark */
int ocelot_sb_tc_pool_bind_set(struct ocelot *ocelot, int port,
unsigned int sb_index, u16 tc_index,
enum devlink_sb_pool_type pool_type,
u16 pool_index, u32 threshold,
struct netlink_ext_ack *extack)
{
int wm_index, err;
u32 old_thr;
/* Paranoid check? */
if (pool_index == OCELOT_SB_POOL_ING &&
pool_type != DEVLINK_SB_POOL_TYPE_INGRESS)
return -EINVAL;
if (pool_index == OCELOT_SB_POOL_EGR &&
pool_type != DEVLINK_SB_POOL_TYPE_EGRESS)
return -EINVAL;
switch (sb_index) {
case OCELOT_SB_BUF:
if (pool_type == DEVLINK_SB_POOL_TYPE_INGRESS)
wm_index = BUF_Q_RSRV_I(port, tc_index);
else
wm_index = BUF_Q_RSRV_E(port, tc_index);
break;
case OCELOT_SB_REF:
if (pool_type == DEVLINK_SB_POOL_TYPE_INGRESS)
wm_index = REF_Q_RSRV_I(port, tc_index);
else
wm_index = REF_Q_RSRV_E(port, tc_index);
break;
default:
NL_SET_ERR_MSG_MOD(extack, "Invalid shared buffer");
return -ENODEV;
}
threshold /= ocelot_sb_pool[sb_index].cell_size;
old_thr = ocelot_wm_read(ocelot, wm_index);
ocelot_wm_write(ocelot, wm_index, threshold);
err = ocelot_watermark_validate(ocelot, extack);
if (err) {
ocelot_wm_write(ocelot, wm_index, old_thr);
return err;
}
ocelot_setup_sharing_watermarks(ocelot);
return 0;
}
EXPORT_SYMBOL(ocelot_sb_tc_pool_bind_set);
/* The hardware does not support atomic snapshots, we'll read out the
* occupancy registers individually and have this as just a stub.
*/
int ocelot_sb_occ_snapshot(struct ocelot *ocelot, unsigned int sb_index)
{
return 0;
}
EXPORT_SYMBOL(ocelot_sb_occ_snapshot);
/* The watermark occupancy registers are cleared upon read,
* so let's read them.
*/
int ocelot_sb_occ_max_clear(struct ocelot *ocelot, unsigned int sb_index)
{
u32 inuse, maxuse;
int port, prio;
switch (sb_index) {
case OCELOT_SB_BUF:
for (port = 0; port <= ocelot->num_phys_ports; port++) {
for (prio = 0; prio < OCELOT_NUM_TC; prio++) {
ocelot_wm_status(ocelot, BUF_Q_RSRV_I(port, prio),
&inuse, &maxuse);
ocelot_wm_status(ocelot, BUF_Q_RSRV_E(port, prio),
&inuse, &maxuse);
}
ocelot_wm_status(ocelot, BUF_P_RSRV_I(port),
&inuse, &maxuse);
ocelot_wm_status(ocelot, BUF_P_RSRV_E(port),
&inuse, &maxuse);
}
break;
case OCELOT_SB_REF:
for (port = 0; port <= ocelot->num_phys_ports; port++) {
for (prio = 0; prio < OCELOT_NUM_TC; prio++) {
ocelot_wm_status(ocelot, REF_Q_RSRV_I(port, prio),
&inuse, &maxuse);
ocelot_wm_status(ocelot, REF_Q_RSRV_E(port, prio),
&inuse, &maxuse);
}
ocelot_wm_status(ocelot, REF_P_RSRV_I(port),
&inuse, &maxuse);
ocelot_wm_status(ocelot, REF_P_RSRV_E(port),
&inuse, &maxuse);
}
break;
default:
return -ENODEV;
}
return 0;
}
EXPORT_SYMBOL(ocelot_sb_occ_max_clear);
/* This retrieves the watermark occupancy for per-port P_RSRV watermarks */
int ocelot_sb_occ_port_pool_get(struct ocelot *ocelot, int port,
unsigned int sb_index, u16 pool_index,
u32 *p_cur, u32 *p_max)
{
int wm_index;
switch (sb_index) {
case OCELOT_SB_BUF:
if (pool_index == OCELOT_SB_POOL_ING)
wm_index = BUF_P_RSRV_I(port);
else
wm_index = BUF_P_RSRV_E(port);
break;
case OCELOT_SB_REF:
if (pool_index == OCELOT_SB_POOL_ING)
wm_index = REF_P_RSRV_I(port);
else
wm_index = REF_P_RSRV_E(port);
break;
default:
return -ENODEV;
}
ocelot_wm_status(ocelot, wm_index, p_cur, p_max);
*p_cur *= ocelot_sb_pool[sb_index].cell_size;
*p_max *= ocelot_sb_pool[sb_index].cell_size;
return 0;
}
EXPORT_SYMBOL(ocelot_sb_occ_port_pool_get);
/* This retrieves the watermark occupancy for per-port-tc Q_RSRV watermarks */
int ocelot_sb_occ_tc_port_bind_get(struct ocelot *ocelot, int port,
unsigned int sb_index, u16 tc_index,
enum devlink_sb_pool_type pool_type,
u32 *p_cur, u32 *p_max)
{
int wm_index;
switch (sb_index) {
case OCELOT_SB_BUF:
if (pool_type == DEVLINK_SB_POOL_TYPE_INGRESS)
wm_index = BUF_Q_RSRV_I(port, tc_index);
else
wm_index = BUF_Q_RSRV_E(port, tc_index);
break;
case OCELOT_SB_REF:
if (pool_type == DEVLINK_SB_POOL_TYPE_INGRESS)
wm_index = REF_Q_RSRV_I(port, tc_index);
else
wm_index = REF_Q_RSRV_E(port, tc_index);
break;
default:
return -ENODEV;
}
ocelot_wm_status(ocelot, wm_index, p_cur, p_max);
*p_cur *= ocelot_sb_pool[sb_index].cell_size;
*p_max *= ocelot_sb_pool[sb_index].cell_size;
return 0;
}
EXPORT_SYMBOL(ocelot_sb_occ_tc_port_bind_get);
int ocelot_devlink_sb_register(struct ocelot *ocelot)
{
int err;
err = devlink_sb_register(ocelot->devlink, OCELOT_SB_BUF,
ocelot->packet_buffer_size, 1, 1,
OCELOT_NUM_TC, OCELOT_NUM_TC);
if (err)
return err;
err = devlink_sb_register(ocelot->devlink, OCELOT_SB_REF,
ocelot->num_frame_refs, 1, 1,
OCELOT_NUM_TC, OCELOT_NUM_TC);
if (err) {
devlink_sb_unregister(ocelot->devlink, OCELOT_SB_BUF);
return err;
}
ocelot->pool_size[OCELOT_SB_BUF][OCELOT_SB_POOL_ING] = ocelot->packet_buffer_size;
ocelot->pool_size[OCELOT_SB_BUF][OCELOT_SB_POOL_EGR] = ocelot->packet_buffer_size;
ocelot->pool_size[OCELOT_SB_REF][OCELOT_SB_POOL_ING] = ocelot->num_frame_refs;
ocelot->pool_size[OCELOT_SB_REF][OCELOT_SB_POOL_EGR] = ocelot->num_frame_refs;
ocelot_watermark_init(ocelot);
return 0;
}
EXPORT_SYMBOL(ocelot_devlink_sb_register);
void ocelot_devlink_sb_unregister(struct ocelot *ocelot)
{
devlink_sb_unregister(ocelot->devlink, OCELOT_SB_BUF);
devlink_sb_unregister(ocelot->devlink, OCELOT_SB_REF);
}
EXPORT_SYMBOL(ocelot_devlink_sb_unregister);
@@ -0,0 +1,892 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
* Microsemi SoCs FDMA driver
*
* Copyright (c) 2021 Microchip
*
* Page recycling code is mostly taken from gianfar driver.
*/
#include <linux/align.h>
#include <linux/bitops.h>
#include <linux/dmapool.h>
#include <linux/dsa/ocelot.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include "ocelot_fdma.h"
#include "ocelot_qs.h"
DEFINE_STATIC_KEY_FALSE(ocelot_fdma_enabled);
static void ocelot_fdma_writel(struct ocelot *ocelot, u32 reg, u32 data)
{
regmap_write(ocelot->targets[FDMA], reg, data);
}
static u32 ocelot_fdma_readl(struct ocelot *ocelot, u32 reg)
{
u32 retval;
regmap_read(ocelot->targets[FDMA], reg, &retval);
return retval;
}
static dma_addr_t ocelot_fdma_idx_dma(dma_addr_t base, u16 idx)
{
return base + idx * sizeof(struct ocelot_fdma_dcb);
}
static u16 ocelot_fdma_dma_idx(dma_addr_t base, dma_addr_t dma)
{
return (dma - base) / sizeof(struct ocelot_fdma_dcb);
}
static u16 ocelot_fdma_idx_next(u16 idx, u16 ring_sz)
{
return unlikely(idx == ring_sz - 1) ? 0 : idx + 1;
}
static u16 ocelot_fdma_idx_prev(u16 idx, u16 ring_sz)
{
return unlikely(idx == 0) ? ring_sz - 1 : idx - 1;
}
static int ocelot_fdma_rx_ring_free(struct ocelot_fdma *fdma)
{
struct ocelot_fdma_rx_ring *rx_ring = &fdma->rx_ring;
if (rx_ring->next_to_use >= rx_ring->next_to_clean)
return OCELOT_FDMA_RX_RING_SIZE -
(rx_ring->next_to_use - rx_ring->next_to_clean) - 1;
else
return rx_ring->next_to_clean - rx_ring->next_to_use - 1;
}
static int ocelot_fdma_tx_ring_free(struct ocelot_fdma *fdma)
{
struct ocelot_fdma_tx_ring *tx_ring = &fdma->tx_ring;
if (tx_ring->next_to_use >= tx_ring->next_to_clean)
return OCELOT_FDMA_TX_RING_SIZE -
(tx_ring->next_to_use - tx_ring->next_to_clean) - 1;
else
return tx_ring->next_to_clean - tx_ring->next_to_use - 1;
}
static bool ocelot_fdma_tx_ring_empty(struct ocelot_fdma *fdma)
{
struct ocelot_fdma_tx_ring *tx_ring = &fdma->tx_ring;
return tx_ring->next_to_clean == tx_ring->next_to_use;
}
static void ocelot_fdma_activate_chan(struct ocelot *ocelot, dma_addr_t dma,
int chan)
{
ocelot_fdma_writel(ocelot, MSCC_FDMA_DCB_LLP(chan), dma);
/* Barrier to force memory writes to DCB to be completed before starting
* the channel.
*/
wmb();
ocelot_fdma_writel(ocelot, MSCC_FDMA_CH_ACTIVATE, BIT(chan));
}
static u32 ocelot_fdma_read_ch_safe(struct ocelot *ocelot)
{
return ocelot_fdma_readl(ocelot, MSCC_FDMA_CH_SAFE);
}
static int ocelot_fdma_wait_chan_safe(struct ocelot *ocelot, int chan)
{
u32 safe;
return readx_poll_timeout_atomic(ocelot_fdma_read_ch_safe, ocelot, safe,
safe & BIT(chan), 0,
OCELOT_FDMA_CH_SAFE_TIMEOUT_US);
}
static void ocelot_fdma_dcb_set_data(struct ocelot_fdma_dcb *dcb,
dma_addr_t dma_addr,
size_t size)
{
u32 offset = dma_addr & 0x3;
dcb->llp = 0;
dcb->datap = ALIGN_DOWN(dma_addr, 4);
dcb->datal = ALIGN_DOWN(size, 4);
dcb->stat = MSCC_FDMA_DCB_STAT_BLOCKO(offset);
}
static bool ocelot_fdma_rx_alloc_page(struct ocelot *ocelot,
struct ocelot_fdma_rx_buf *rxb)
{
dma_addr_t mapping;
struct page *page;
page = dev_alloc_page();
if (unlikely(!page))
return false;
mapping = dma_map_page(ocelot->dev, page, 0, PAGE_SIZE,
DMA_FROM_DEVICE);
if (unlikely(dma_mapping_error(ocelot->dev, mapping))) {
__free_page(page);
return false;
}
rxb->page = page;
rxb->page_offset = 0;
rxb->dma_addr = mapping;
return true;
}
static int ocelot_fdma_alloc_rx_buffs(struct ocelot *ocelot, u16 alloc_cnt)
{
struct ocelot_fdma *fdma = ocelot->fdma;
struct ocelot_fdma_rx_ring *rx_ring;
struct ocelot_fdma_rx_buf *rxb;
struct ocelot_fdma_dcb *dcb;
dma_addr_t dma_addr;
int ret = 0;
u16 idx;
rx_ring = &fdma->rx_ring;
idx = rx_ring->next_to_use;
while (alloc_cnt--) {
rxb = &rx_ring->bufs[idx];
/* try reuse page */
if (unlikely(!rxb->page)) {
if (unlikely(!ocelot_fdma_rx_alloc_page(ocelot, rxb))) {
dev_err_ratelimited(ocelot->dev,
"Failed to allocate rx\n");
ret = -ENOMEM;
break;
}
}
dcb = &rx_ring->dcbs[idx];
dma_addr = rxb->dma_addr + rxb->page_offset;
ocelot_fdma_dcb_set_data(dcb, dma_addr, OCELOT_FDMA_RXB_SIZE);
idx = ocelot_fdma_idx_next(idx, OCELOT_FDMA_RX_RING_SIZE);
/* Chain the DCB to the next one */
dcb->llp = ocelot_fdma_idx_dma(rx_ring->dcbs_dma, idx);
}
rx_ring->next_to_use = idx;
rx_ring->next_to_alloc = idx;
return ret;
}
static bool ocelot_fdma_tx_dcb_set_skb(struct ocelot *ocelot,
struct ocelot_fdma_tx_buf *tx_buf,
struct ocelot_fdma_dcb *dcb,
struct sk_buff *skb)
{
dma_addr_t mapping;
mapping = dma_map_single(ocelot->dev, skb->data, skb->len,
DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(ocelot->dev, mapping)))
return false;
dma_unmap_addr_set(tx_buf, dma_addr, mapping);
ocelot_fdma_dcb_set_data(dcb, mapping, OCELOT_FDMA_RX_SIZE);
tx_buf->skb = skb;
dcb->stat |= MSCC_FDMA_DCB_STAT_BLOCKL(skb->len);
dcb->stat |= MSCC_FDMA_DCB_STAT_SOF | MSCC_FDMA_DCB_STAT_EOF;
return true;
}
static bool ocelot_fdma_check_stop_rx(struct ocelot *ocelot)
{
u32 llp;
/* Check if the FDMA hits the DCB with LLP == NULL */
llp = ocelot_fdma_readl(ocelot, MSCC_FDMA_DCB_LLP(MSCC_FDMA_XTR_CHAN));
if (unlikely(llp))
return false;
ocelot_fdma_writel(ocelot, MSCC_FDMA_CH_DISABLE,
BIT(MSCC_FDMA_XTR_CHAN));
return true;
}
static void ocelot_fdma_rx_set_llp(struct ocelot_fdma_rx_ring *rx_ring)
{
struct ocelot_fdma_dcb *dcb;
unsigned int idx;
idx = ocelot_fdma_idx_prev(rx_ring->next_to_use,
OCELOT_FDMA_RX_RING_SIZE);
dcb = &rx_ring->dcbs[idx];
dcb->llp = 0;
}
static void ocelot_fdma_rx_restart(struct ocelot *ocelot)
{
struct ocelot_fdma *fdma = ocelot->fdma;
struct ocelot_fdma_rx_ring *rx_ring;
const u8 chan = MSCC_FDMA_XTR_CHAN;
dma_addr_t new_llp, dma_base;
unsigned int idx;
u32 llp_prev;
int ret;
rx_ring = &fdma->rx_ring;
ret = ocelot_fdma_wait_chan_safe(ocelot, chan);
if (ret) {
dev_err_ratelimited(ocelot->dev,
"Unable to stop RX channel\n");
return;
}
ocelot_fdma_rx_set_llp(rx_ring);
/* FDMA stopped on the last DCB that contained a NULL LLP, since
* we processed some DCBs in RX, there is free space, and we must set
* DCB_LLP to point to the next DCB
*/
llp_prev = ocelot_fdma_readl(ocelot, MSCC_FDMA_DCB_LLP_PREV(chan));
dma_base = rx_ring->dcbs_dma;
/* Get the next DMA addr located after LLP == NULL DCB */
idx = ocelot_fdma_dma_idx(dma_base, llp_prev);
idx = ocelot_fdma_idx_next(idx, OCELOT_FDMA_RX_RING_SIZE);
new_llp = ocelot_fdma_idx_dma(dma_base, idx);
/* Finally reactivate the channel */
ocelot_fdma_activate_chan(ocelot, new_llp, chan);
}
static bool ocelot_fdma_add_rx_frag(struct ocelot_fdma_rx_buf *rxb, u32 stat,
struct sk_buff *skb, bool first)
{
int size = MSCC_FDMA_DCB_STAT_BLOCKL(stat);
struct page *page = rxb->page;
if (likely(first)) {
skb_put(skb, size);
} else {
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page,
rxb->page_offset, size, OCELOT_FDMA_RX_SIZE);
}
/* Try to reuse page */
if (unlikely(page_ref_count(page) != 1 || page_is_pfmemalloc(page)))
return false;
/* Change offset to the other half */
rxb->page_offset ^= OCELOT_FDMA_RX_SIZE;
page_ref_inc(page);
return true;
}
static void ocelot_fdma_reuse_rx_page(struct ocelot *ocelot,
struct ocelot_fdma_rx_buf *old_rxb)
{
struct ocelot_fdma_rx_ring *rx_ring = &ocelot->fdma->rx_ring;
struct ocelot_fdma_rx_buf *new_rxb;
new_rxb = &rx_ring->bufs[rx_ring->next_to_alloc];
rx_ring->next_to_alloc = ocelot_fdma_idx_next(rx_ring->next_to_alloc,
OCELOT_FDMA_RX_RING_SIZE);
/* Copy page reference */
*new_rxb = *old_rxb;
/* Sync for use by the device */
dma_sync_single_range_for_device(ocelot->dev, old_rxb->dma_addr,
old_rxb->page_offset,
OCELOT_FDMA_RX_SIZE, DMA_FROM_DEVICE);
}
static struct sk_buff *ocelot_fdma_get_skb(struct ocelot *ocelot, u32 stat,
struct ocelot_fdma_rx_buf *rxb,
struct sk_buff *skb)
{
bool first = false;
/* Allocate skb head and data */
if (likely(!skb)) {
void *buff_addr = page_address(rxb->page) +
rxb->page_offset;
skb = build_skb(buff_addr, OCELOT_FDMA_SKBFRAG_SIZE);
if (unlikely(!skb)) {
dev_err_ratelimited(ocelot->dev,
"build_skb failed !\n");
return NULL;
}
first = true;
}
dma_sync_single_range_for_cpu(ocelot->dev, rxb->dma_addr,
rxb->page_offset, OCELOT_FDMA_RX_SIZE,
DMA_FROM_DEVICE);
if (ocelot_fdma_add_rx_frag(rxb, stat, skb, first)) {
/* Reuse the free half of the page for the next_to_alloc DCB*/
ocelot_fdma_reuse_rx_page(ocelot, rxb);
} else {
/* page cannot be reused, unmap it */
dma_unmap_page(ocelot->dev, rxb->dma_addr, PAGE_SIZE,
DMA_FROM_DEVICE);
}
/* clear rx buff content */
rxb->page = NULL;
return skb;
}
static bool ocelot_fdma_receive_skb(struct ocelot *ocelot, struct sk_buff *skb)
{
struct net_device *ndev;
void *xfh = skb->data;
u64 timestamp;
u64 src_port;
skb_pull(skb, OCELOT_TAG_LEN);
ocelot_xfh_get_src_port(xfh, &src_port);
if (unlikely(src_port >= ocelot->num_phys_ports))
return false;
ndev = ocelot_port_to_netdev(ocelot, src_port);
if (unlikely(!ndev))
return false;
if (pskb_trim(skb, skb->len - ETH_FCS_LEN))
return false;
skb->dev = ndev;
skb->protocol = eth_type_trans(skb, skb->dev);
skb->dev->stats.rx_bytes += skb->len;
skb->dev->stats.rx_packets++;
if (ocelot->ptp) {
ocelot_xfh_get_rew_val(xfh, &timestamp);
ocelot_ptp_rx_timestamp(ocelot, skb, timestamp);
}
if (likely(!skb_defer_rx_timestamp(skb)))
netif_receive_skb(skb);
return true;
}
static int ocelot_fdma_rx_get(struct ocelot *ocelot, int budget)
{
struct ocelot_fdma *fdma = ocelot->fdma;
struct ocelot_fdma_rx_ring *rx_ring;
struct ocelot_fdma_rx_buf *rxb;
struct ocelot_fdma_dcb *dcb;
struct sk_buff *skb;
int work_done = 0;
int cleaned_cnt;
u32 stat;
u16 idx;
cleaned_cnt = ocelot_fdma_rx_ring_free(fdma);
rx_ring = &fdma->rx_ring;
skb = rx_ring->skb;
while (budget--) {
idx = rx_ring->next_to_clean;
dcb = &rx_ring->dcbs[idx];
stat = dcb->stat;
if (MSCC_FDMA_DCB_STAT_BLOCKL(stat) == 0)
break;
/* New packet is a start of frame but we already got a skb set,
* we probably lost an EOF packet, free skb
*/
if (unlikely(skb && (stat & MSCC_FDMA_DCB_STAT_SOF))) {
dev_kfree_skb(skb);
skb = NULL;
}
rxb = &rx_ring->bufs[idx];
/* Fetch next to clean buffer from the rx_ring */
skb = ocelot_fdma_get_skb(ocelot, stat, rxb, skb);
if (unlikely(!skb))
break;
work_done++;
cleaned_cnt++;
idx = ocelot_fdma_idx_next(idx, OCELOT_FDMA_RX_RING_SIZE);
rx_ring->next_to_clean = idx;
if (unlikely(stat & MSCC_FDMA_DCB_STAT_ABORT ||
stat & MSCC_FDMA_DCB_STAT_PD)) {
dev_err_ratelimited(ocelot->dev,
"DCB aborted or pruned\n");
dev_kfree_skb(skb);
skb = NULL;
continue;
}
/* We still need to process the other fragment of the packet
* before delivering it to the network stack
*/
if (!(stat & MSCC_FDMA_DCB_STAT_EOF))
continue;
if (unlikely(!ocelot_fdma_receive_skb(ocelot, skb)))
dev_kfree_skb(skb);
skb = NULL;
}
rx_ring->skb = skb;
if (cleaned_cnt)
ocelot_fdma_alloc_rx_buffs(ocelot, cleaned_cnt);
return work_done;
}
static void ocelot_fdma_wakeup_netdev(struct ocelot *ocelot)
{
struct ocelot_port_private *priv;
struct ocelot_port *ocelot_port;
struct net_device *dev;
int port;
for (port = 0; port < ocelot->num_phys_ports; port++) {
ocelot_port = ocelot->ports[port];
if (!ocelot_port)
continue;
priv = container_of(ocelot_port, struct ocelot_port_private,
port);
dev = priv->dev;
if (unlikely(netif_queue_stopped(dev)))
netif_wake_queue(dev);
}
}
static void ocelot_fdma_tx_cleanup(struct ocelot *ocelot, int budget)
{
struct ocelot_fdma *fdma = ocelot->fdma;
struct ocelot_fdma_tx_ring *tx_ring;
struct ocelot_fdma_tx_buf *buf;
unsigned int new_null_llp_idx;
struct ocelot_fdma_dcb *dcb;
bool end_of_list = false;
struct sk_buff *skb;
dma_addr_t dma;
u32 dcb_llp;
u16 ntc;
int ret;
tx_ring = &fdma->tx_ring;
/* Purge the TX packets that have been sent up to the NULL llp or the
* end of done list.
*/
while (!ocelot_fdma_tx_ring_empty(fdma)) {
ntc = tx_ring->next_to_clean;
dcb = &tx_ring->dcbs[ntc];
if (!(dcb->stat & MSCC_FDMA_DCB_STAT_PD))
break;
buf = &tx_ring->bufs[ntc];
skb = buf->skb;
dma_unmap_single(ocelot->dev, dma_unmap_addr(buf, dma_addr),
skb->len, DMA_TO_DEVICE);
napi_consume_skb(skb, budget);
dcb_llp = dcb->llp;
/* Only update after accessing all dcb fields */
tx_ring->next_to_clean = ocelot_fdma_idx_next(ntc,
OCELOT_FDMA_TX_RING_SIZE);
/* If we hit the NULL LLP, stop, we might need to reload FDMA */
if (dcb_llp == 0) {
end_of_list = true;
break;
}
}
/* No need to try to wake if there were no TX cleaned_cnt up. */
if (ocelot_fdma_tx_ring_free(fdma))
ocelot_fdma_wakeup_netdev(ocelot);
/* If there is still some DCBs to be processed by the FDMA or if the
* pending list is empty, there is no need to restart the FDMA.
*/
if (!end_of_list || ocelot_fdma_tx_ring_empty(fdma))
return;
ret = ocelot_fdma_wait_chan_safe(ocelot, MSCC_FDMA_INJ_CHAN);
if (ret) {
dev_warn(ocelot->dev,
"Failed to wait for TX channel to stop\n");
return;
}
/* Set NULL LLP to be the last DCB used */
new_null_llp_idx = ocelot_fdma_idx_prev(tx_ring->next_to_use,
OCELOT_FDMA_TX_RING_SIZE);
dcb = &tx_ring->dcbs[new_null_llp_idx];
dcb->llp = 0;
dma = ocelot_fdma_idx_dma(tx_ring->dcbs_dma, tx_ring->next_to_clean);
ocelot_fdma_activate_chan(ocelot, dma, MSCC_FDMA_INJ_CHAN);
}
static int ocelot_fdma_napi_poll(struct napi_struct *napi, int budget)
{
struct ocelot_fdma *fdma = container_of(napi, struct ocelot_fdma, napi);
struct ocelot *ocelot = fdma->ocelot;
int work_done = 0;
bool rx_stopped;
ocelot_fdma_tx_cleanup(ocelot, budget);
rx_stopped = ocelot_fdma_check_stop_rx(ocelot);
work_done = ocelot_fdma_rx_get(ocelot, budget);
if (rx_stopped)
ocelot_fdma_rx_restart(ocelot);
if (work_done < budget) {
napi_complete_done(&fdma->napi, work_done);
ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_ENA,
BIT(MSCC_FDMA_INJ_CHAN) |
BIT(MSCC_FDMA_XTR_CHAN));
}
return work_done;
}
static irqreturn_t ocelot_fdma_interrupt(int irq, void *dev_id)
{
u32 ident, llp, frm, err, err_code;
struct ocelot *ocelot = dev_id;
ident = ocelot_fdma_readl(ocelot, MSCC_FDMA_INTR_IDENT);
frm = ocelot_fdma_readl(ocelot, MSCC_FDMA_INTR_FRM);
llp = ocelot_fdma_readl(ocelot, MSCC_FDMA_INTR_LLP);
ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_LLP, llp & ident);
ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_FRM, frm & ident);
if (frm || llp) {
ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_ENA, 0);
napi_schedule(&ocelot->fdma->napi);
}
err = ocelot_fdma_readl(ocelot, MSCC_FDMA_EVT_ERR);
if (unlikely(err)) {
err_code = ocelot_fdma_readl(ocelot, MSCC_FDMA_EVT_ERR_CODE);
dev_err_ratelimited(ocelot->dev,
"Error ! chans mask: %#x, code: %#x\n",
err, err_code);
ocelot_fdma_writel(ocelot, MSCC_FDMA_EVT_ERR, err);
ocelot_fdma_writel(ocelot, MSCC_FDMA_EVT_ERR_CODE, err_code);
}
return IRQ_HANDLED;
}
static void ocelot_fdma_send_skb(struct ocelot *ocelot,
struct ocelot_fdma *fdma, struct sk_buff *skb)
{
struct ocelot_fdma_tx_ring *tx_ring = &fdma->tx_ring;
struct ocelot_fdma_tx_buf *tx_buf;
struct ocelot_fdma_dcb *dcb;
dma_addr_t dma;
u16 next_idx;
dcb = &tx_ring->dcbs[tx_ring->next_to_use];
tx_buf = &tx_ring->bufs[tx_ring->next_to_use];
if (!ocelot_fdma_tx_dcb_set_skb(ocelot, tx_buf, dcb, skb)) {
dev_kfree_skb_any(skb);
return;
}
next_idx = ocelot_fdma_idx_next(tx_ring->next_to_use,
OCELOT_FDMA_TX_RING_SIZE);
skb_tx_timestamp(skb);
/* If the FDMA TX chan is empty, then enqueue the DCB directly */
if (ocelot_fdma_tx_ring_empty(fdma)) {
dma = ocelot_fdma_idx_dma(tx_ring->dcbs_dma,
tx_ring->next_to_use);
ocelot_fdma_activate_chan(ocelot, dma, MSCC_FDMA_INJ_CHAN);
} else {
/* Chain the DCBs */
dcb->llp = ocelot_fdma_idx_dma(tx_ring->dcbs_dma, next_idx);
}
tx_ring->next_to_use = next_idx;
}
static int ocelot_fdma_prepare_skb(struct ocelot *ocelot, int port, u32 rew_op,
struct sk_buff *skb, struct net_device *dev)
{
int needed_headroom = max_t(int, OCELOT_TAG_LEN - skb_headroom(skb), 0);
int needed_tailroom = max_t(int, ETH_FCS_LEN - skb_tailroom(skb), 0);
void *ifh;
int err;
if (unlikely(needed_headroom || needed_tailroom ||
skb_header_cloned(skb))) {
err = pskb_expand_head(skb, needed_headroom, needed_tailroom,
GFP_ATOMIC);
if (unlikely(err)) {
dev_kfree_skb_any(skb);
return 1;
}
}
err = skb_linearize(skb);
if (err) {
net_err_ratelimited("%s: skb_linearize error (%d)!\n",
dev->name, err);
dev_kfree_skb_any(skb);
return 1;
}
ifh = skb_push(skb, OCELOT_TAG_LEN);
skb_put(skb, ETH_FCS_LEN);
ocelot_ifh_set_basic(ifh, ocelot, port, rew_op, skb);
return 0;
}
int ocelot_fdma_inject_frame(struct ocelot *ocelot, int port, u32 rew_op,
struct sk_buff *skb, struct net_device *dev)
{
struct ocelot_fdma *fdma = ocelot->fdma;
int ret = NETDEV_TX_OK;
spin_lock(&fdma->tx_ring.xmit_lock);
if (ocelot_fdma_tx_ring_free(fdma) == 0) {
netif_stop_queue(dev);
ret = NETDEV_TX_BUSY;
goto out;
}
if (ocelot_fdma_prepare_skb(ocelot, port, rew_op, skb, dev))
goto out;
ocelot_fdma_send_skb(ocelot, fdma, skb);
out:
spin_unlock(&fdma->tx_ring.xmit_lock);
return ret;
}
static void ocelot_fdma_free_rx_ring(struct ocelot *ocelot)
{
struct ocelot_fdma *fdma = ocelot->fdma;
struct ocelot_fdma_rx_ring *rx_ring;
struct ocelot_fdma_rx_buf *rxb;
u16 idx;
rx_ring = &fdma->rx_ring;
idx = rx_ring->next_to_clean;
/* Free the pages held in the RX ring */
while (idx != rx_ring->next_to_use) {
rxb = &rx_ring->bufs[idx];
dma_unmap_page(ocelot->dev, rxb->dma_addr, PAGE_SIZE,
DMA_FROM_DEVICE);
__free_page(rxb->page);
idx = ocelot_fdma_idx_next(idx, OCELOT_FDMA_RX_RING_SIZE);
}
if (fdma->rx_ring.skb)
dev_kfree_skb_any(fdma->rx_ring.skb);
}
static void ocelot_fdma_free_tx_ring(struct ocelot *ocelot)
{
struct ocelot_fdma *fdma = ocelot->fdma;
struct ocelot_fdma_tx_ring *tx_ring;
struct ocelot_fdma_tx_buf *txb;
struct sk_buff *skb;
u16 idx;
tx_ring = &fdma->tx_ring;
idx = tx_ring->next_to_clean;
while (idx != tx_ring->next_to_use) {
txb = &tx_ring->bufs[idx];
skb = txb->skb;
dma_unmap_single(ocelot->dev, dma_unmap_addr(txb, dma_addr),
skb->len, DMA_TO_DEVICE);
dev_kfree_skb_any(skb);
idx = ocelot_fdma_idx_next(idx, OCELOT_FDMA_TX_RING_SIZE);
}
}
static int ocelot_fdma_rings_alloc(struct ocelot *ocelot)
{
struct ocelot_fdma *fdma = ocelot->fdma;
struct ocelot_fdma_dcb *dcbs;
unsigned int adjust;
dma_addr_t dcbs_dma;
int ret;
/* Create a pool of consistent memory blocks for hardware descriptors */
fdma->dcbs_base = dmam_alloc_coherent(ocelot->dev,
OCELOT_DCBS_HW_ALLOC_SIZE,
&fdma->dcbs_dma_base, GFP_KERNEL);
if (!fdma->dcbs_base)
return -ENOMEM;
/* DCBs must be aligned on a 32bit boundary */
dcbs = fdma->dcbs_base;
dcbs_dma = fdma->dcbs_dma_base;
if (!IS_ALIGNED(dcbs_dma, 4)) {
adjust = dcbs_dma & 0x3;
dcbs_dma = ALIGN(dcbs_dma, 4);
dcbs = (void *)dcbs + adjust;
}
/* TX queue */
fdma->tx_ring.dcbs = dcbs;
fdma->tx_ring.dcbs_dma = dcbs_dma;
spin_lock_init(&fdma->tx_ring.xmit_lock);
/* RX queue */
fdma->rx_ring.dcbs = dcbs + OCELOT_FDMA_TX_RING_SIZE;
fdma->rx_ring.dcbs_dma = dcbs_dma + OCELOT_FDMA_TX_DCB_SIZE;
ret = ocelot_fdma_alloc_rx_buffs(ocelot,
ocelot_fdma_tx_ring_free(fdma));
if (ret) {
ocelot_fdma_free_rx_ring(ocelot);
return ret;
}
/* Set the last DCB LLP as NULL, this is normally done when restarting
* the RX chan, but this is for the first run
*/
ocelot_fdma_rx_set_llp(&fdma->rx_ring);
return 0;
}
void ocelot_fdma_netdev_init(struct ocelot *ocelot, struct net_device *dev)
{
struct ocelot_fdma *fdma = ocelot->fdma;
dev->needed_headroom = OCELOT_TAG_LEN;
dev->needed_tailroom = ETH_FCS_LEN;
if (fdma->ndev)
return;
fdma->ndev = dev;
netif_napi_add_weight(dev, &fdma->napi, ocelot_fdma_napi_poll,
OCELOT_FDMA_WEIGHT);
}
void ocelot_fdma_netdev_deinit(struct ocelot *ocelot, struct net_device *dev)
{
struct ocelot_fdma *fdma = ocelot->fdma;
if (fdma->ndev == dev) {
netif_napi_del(&fdma->napi);
fdma->ndev = NULL;
}
}
void ocelot_fdma_init(struct platform_device *pdev, struct ocelot *ocelot)
{
struct device *dev = ocelot->dev;
struct ocelot_fdma *fdma;
int ret;
fdma = devm_kzalloc(dev, sizeof(*fdma), GFP_KERNEL);
if (!fdma)
return;
ocelot->fdma = fdma;
ocelot->dev->coherent_dma_mask = DMA_BIT_MASK(32);
ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_ENA, 0);
fdma->ocelot = ocelot;
fdma->irq = platform_get_irq_byname(pdev, "fdma");
ret = devm_request_irq(dev, fdma->irq, ocelot_fdma_interrupt, 0,
dev_name(dev), ocelot);
if (ret)
goto err_free_fdma;
ret = ocelot_fdma_rings_alloc(ocelot);
if (ret)
goto err_free_irq;
static_branch_enable(&ocelot_fdma_enabled);
return;
err_free_irq:
devm_free_irq(dev, fdma->irq, fdma);
err_free_fdma:
devm_kfree(dev, fdma);
ocelot->fdma = NULL;
}
void ocelot_fdma_start(struct ocelot *ocelot)
{
struct ocelot_fdma *fdma = ocelot->fdma;
/* Reconfigure for extraction and injection using DMA */
ocelot_write_rix(ocelot, QS_INJ_GRP_CFG_MODE(2), QS_INJ_GRP_CFG, 0);
ocelot_write_rix(ocelot, QS_INJ_CTRL_GAP_SIZE(0), QS_INJ_CTRL, 0);
ocelot_write_rix(ocelot, QS_XTR_GRP_CFG_MODE(2), QS_XTR_GRP_CFG, 0);
ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_LLP, 0xffffffff);
ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_FRM, 0xffffffff);
ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_LLP_ENA,
BIT(MSCC_FDMA_INJ_CHAN) | BIT(MSCC_FDMA_XTR_CHAN));
ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_FRM_ENA,
BIT(MSCC_FDMA_XTR_CHAN));
ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_ENA,
BIT(MSCC_FDMA_INJ_CHAN) | BIT(MSCC_FDMA_XTR_CHAN));
napi_enable(&fdma->napi);
ocelot_fdma_activate_chan(ocelot, ocelot->fdma->rx_ring.dcbs_dma,
MSCC_FDMA_XTR_CHAN);
}
void ocelot_fdma_deinit(struct ocelot *ocelot)
{
struct ocelot_fdma *fdma = ocelot->fdma;
ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_ENA, 0);
ocelot_fdma_writel(ocelot, MSCC_FDMA_CH_FORCEDIS,
BIT(MSCC_FDMA_XTR_CHAN));
ocelot_fdma_writel(ocelot, MSCC_FDMA_CH_FORCEDIS,
BIT(MSCC_FDMA_INJ_CHAN));
napi_synchronize(&fdma->napi);
napi_disable(&fdma->napi);
ocelot_fdma_free_rx_ring(ocelot);
ocelot_fdma_free_tx_ring(ocelot);
}
@@ -0,0 +1,166 @@
/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
/*
* Microsemi SoCs FDMA driver
*
* Copyright (c) 2021 Microchip
*/
#ifndef _MSCC_OCELOT_FDMA_H_
#define _MSCC_OCELOT_FDMA_H_
#include "ocelot.h"
#define MSCC_FDMA_DCB_STAT_BLOCKO(x) (((x) << 20) & GENMASK(31, 20))
#define MSCC_FDMA_DCB_STAT_BLOCKO_M GENMASK(31, 20)
#define MSCC_FDMA_DCB_STAT_BLOCKO_X(x) (((x) & GENMASK(31, 20)) >> 20)
#define MSCC_FDMA_DCB_STAT_PD BIT(19)
#define MSCC_FDMA_DCB_STAT_ABORT BIT(18)
#define MSCC_FDMA_DCB_STAT_EOF BIT(17)
#define MSCC_FDMA_DCB_STAT_SOF BIT(16)
#define MSCC_FDMA_DCB_STAT_BLOCKL_M GENMASK(15, 0)
#define MSCC_FDMA_DCB_STAT_BLOCKL(x) ((x) & GENMASK(15, 0))
#define MSCC_FDMA_DCB_LLP(x) ((x) * 4 + 0x0)
#define MSCC_FDMA_DCB_LLP_PREV(x) ((x) * 4 + 0xA0)
#define MSCC_FDMA_CH_SAFE 0xcc
#define MSCC_FDMA_CH_ACTIVATE 0xd0
#define MSCC_FDMA_CH_DISABLE 0xd4
#define MSCC_FDMA_CH_FORCEDIS 0xd8
#define MSCC_FDMA_EVT_ERR 0x164
#define MSCC_FDMA_EVT_ERR_CODE 0x168
#define MSCC_FDMA_INTR_LLP 0x16c
#define MSCC_FDMA_INTR_LLP_ENA 0x170
#define MSCC_FDMA_INTR_FRM 0x174
#define MSCC_FDMA_INTR_FRM_ENA 0x178
#define MSCC_FDMA_INTR_ENA 0x184
#define MSCC_FDMA_INTR_IDENT 0x188
#define MSCC_FDMA_INJ_CHAN 2
#define MSCC_FDMA_XTR_CHAN 0
#define OCELOT_FDMA_WEIGHT 32
#define OCELOT_FDMA_CH_SAFE_TIMEOUT_US 10
#define OCELOT_FDMA_RX_RING_SIZE 512
#define OCELOT_FDMA_TX_RING_SIZE 128
#define OCELOT_FDMA_RX_DCB_SIZE (OCELOT_FDMA_RX_RING_SIZE * \
sizeof(struct ocelot_fdma_dcb))
#define OCELOT_FDMA_TX_DCB_SIZE (OCELOT_FDMA_TX_RING_SIZE * \
sizeof(struct ocelot_fdma_dcb))
/* +4 allows for word alignment after allocation */
#define OCELOT_DCBS_HW_ALLOC_SIZE (OCELOT_FDMA_RX_DCB_SIZE + \
OCELOT_FDMA_TX_DCB_SIZE + \
4)
#define OCELOT_FDMA_RX_SIZE (PAGE_SIZE / 2)
#define OCELOT_FDMA_SKBFRAG_OVR (4 + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
#define OCELOT_FDMA_RXB_SIZE ALIGN_DOWN(OCELOT_FDMA_RX_SIZE - OCELOT_FDMA_SKBFRAG_OVR, 4)
#define OCELOT_FDMA_SKBFRAG_SIZE (OCELOT_FDMA_RXB_SIZE + OCELOT_FDMA_SKBFRAG_OVR)
DECLARE_STATIC_KEY_FALSE(ocelot_fdma_enabled);
struct ocelot_fdma_dcb {
u32 llp;
u32 datap;
u32 datal;
u32 stat;
} __packed;
/**
* struct ocelot_fdma_tx_buf - TX buffer structure
* @skb: SKB currently used in the corresponding DCB.
* @dma_addr: SKB DMA mapped address.
*/
struct ocelot_fdma_tx_buf {
struct sk_buff *skb;
DEFINE_DMA_UNMAP_ADDR(dma_addr);
};
/**
* struct ocelot_fdma_tx_ring - TX ring description of DCBs
*
* @dcbs: DCBs allocated for the ring
* @dcbs_dma: DMA base address of the DCBs
* @bufs: List of TX buffer associated to the DCBs
* @xmit_lock: lock for concurrent xmit access
* @next_to_clean: Next DCB to be cleaned in tx_cleanup
* @next_to_use: Next available DCB to send SKB
*/
struct ocelot_fdma_tx_ring {
struct ocelot_fdma_dcb *dcbs;
dma_addr_t dcbs_dma;
struct ocelot_fdma_tx_buf bufs[OCELOT_FDMA_TX_RING_SIZE];
/* Protect concurrent xmit calls */
spinlock_t xmit_lock;
u16 next_to_clean;
u16 next_to_use;
};
/**
* struct ocelot_fdma_rx_buf - RX buffer structure
* @page: Struct page used in this buffer
* @page_offset: Current page offset (either 0 or PAGE_SIZE/2)
* @dma_addr: DMA address of the page
*/
struct ocelot_fdma_rx_buf {
struct page *page;
u32 page_offset;
dma_addr_t dma_addr;
};
/**
* struct ocelot_fdma_rx_ring - TX ring description of DCBs
*
* @dcbs: DCBs allocated for the ring
* @dcbs_dma: DMA base address of the DCBs
* @bufs: List of RX buffer associated to the DCBs
* @skb: SKB currently received by the netdev
* @next_to_clean: Next DCB to be cleaned NAPI polling
* @next_to_use: Next available DCB to send SKB
* @next_to_alloc: Next buffer that needs to be allocated (page reuse or alloc)
*/
struct ocelot_fdma_rx_ring {
struct ocelot_fdma_dcb *dcbs;
dma_addr_t dcbs_dma;
struct ocelot_fdma_rx_buf bufs[OCELOT_FDMA_RX_RING_SIZE];
struct sk_buff *skb;
u16 next_to_clean;
u16 next_to_use;
u16 next_to_alloc;
};
/**
* struct ocelot_fdma - FDMA context
*
* @irq: FDMA interrupt
* @ndev: Net device used to initialize NAPI
* @dcbs_base: Memory coherent DCBs
* @dcbs_dma_base: DMA base address of memory coherent DCBs
* @tx_ring: Injection ring
* @rx_ring: Extraction ring
* @napi: NAPI context
* @ocelot: Back-pointer to ocelot struct
*/
struct ocelot_fdma {
int irq;
struct net_device *ndev;
struct ocelot_fdma_dcb *dcbs_base;
dma_addr_t dcbs_dma_base;
struct ocelot_fdma_tx_ring tx_ring;
struct ocelot_fdma_rx_ring rx_ring;
struct napi_struct napi;
struct ocelot *ocelot;
};
void ocelot_fdma_init(struct platform_device *pdev, struct ocelot *ocelot);
void ocelot_fdma_start(struct ocelot *ocelot);
void ocelot_fdma_deinit(struct ocelot *ocelot);
int ocelot_fdma_inject_frame(struct ocelot *fdma, int port, u32 rew_op,
struct sk_buff *skb, struct net_device *dev);
void ocelot_fdma_netdev_init(struct ocelot *ocelot, struct net_device *dev);
void ocelot_fdma_netdev_deinit(struct ocelot *ocelot,
struct net_device *dev);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,168 @@
// 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"
int __ocelot_bulk_read_ix(struct ocelot *ocelot, enum ocelot_reg reg,
u32 offset, void *buf, int count)
{
enum ocelot_target target;
u32 addr;
ocelot_reg_to_target_addr(ocelot, reg, &target, &addr);
WARN_ON(!target);
return regmap_bulk_read(ocelot->targets[target], addr + offset,
buf, count);
}
EXPORT_SYMBOL_GPL(__ocelot_bulk_read_ix);
u32 __ocelot_read_ix(struct ocelot *ocelot, enum ocelot_reg reg, u32 offset)
{
enum ocelot_target target;
u32 addr, val;
ocelot_reg_to_target_addr(ocelot, reg, &target, &addr);
WARN_ON(!target);
regmap_read(ocelot->targets[target], addr + offset, &val);
return val;
}
EXPORT_SYMBOL_GPL(__ocelot_read_ix);
void __ocelot_write_ix(struct ocelot *ocelot, u32 val, enum ocelot_reg reg,
u32 offset)
{
enum ocelot_target target;
u32 addr;
ocelot_reg_to_target_addr(ocelot, reg, &target, &addr);
WARN_ON(!target);
regmap_write(ocelot->targets[target], addr + offset, val);
}
EXPORT_SYMBOL_GPL(__ocelot_write_ix);
void __ocelot_rmw_ix(struct ocelot *ocelot, u32 val, u32 mask,
enum ocelot_reg reg, u32 offset)
{
enum ocelot_target target;
u32 addr;
ocelot_reg_to_target_addr(ocelot, reg, &target, &addr);
WARN_ON(!target);
regmap_update_bits(ocelot->targets[target], addr + offset, mask, val);
}
EXPORT_SYMBOL_GPL(__ocelot_rmw_ix);
u32 ocelot_port_readl(struct ocelot_port *port, enum ocelot_reg reg)
{
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;
}
EXPORT_SYMBOL_GPL(ocelot_port_readl);
void ocelot_port_writel(struct ocelot_port *port, u32 val, enum ocelot_reg reg)
{
struct ocelot *ocelot = port->ocelot;
u16 target = reg >> TARGET_OFFSET;
WARN_ON(!target);
regmap_write(port->target, ocelot->map[target][reg & REG_MASK], val);
}
EXPORT_SYMBOL_GPL(ocelot_port_writel);
void ocelot_port_rmwl(struct ocelot_port *port, u32 val, u32 mask,
enum ocelot_reg reg)
{
u32 cur = ocelot_port_readl(port, reg);
ocelot_port_writel(port, (cur & (~mask)) | val, reg);
}
EXPORT_SYMBOL_GPL(ocelot_port_rmwl);
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);
}
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;
regfield.id_size = regfields[i].id_size;
regfield.id_offset = regfields[i].id_offset;
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;
}
EXPORT_SYMBOL_GPL(ocelot_regfields_init);
static struct regmap_config ocelot_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
};
struct regmap *ocelot_regmap_init(struct ocelot *ocelot, struct resource *res)
{
void __iomem *regs;
regs = devm_ioremap_resource(ocelot->dev, res);
if (IS_ERR(regs))
return ERR_CAST(regs);
ocelot_regmap_config.name = res->name;
return devm_regmap_init_mmio(ocelot->dev, regs, &ocelot_regmap_config);
}
EXPORT_SYMBOL_GPL(ocelot_regmap_init);
@@ -0,0 +1,300 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
* Hardware library for MAC Merge Layer and Frame Preemption on TSN-capable
* switches (VSC9959)
*
* Copyright 2022-2023 NXP
*/
#include <linux/ethtool.h>
#include <soc/mscc/ocelot.h>
#include <soc/mscc/ocelot_dev.h>
#include <soc/mscc/ocelot_qsys.h>
#include "ocelot.h"
static const char *
mm_verify_state_to_string(enum ethtool_mm_verify_status state)
{
switch (state) {
case ETHTOOL_MM_VERIFY_STATUS_INITIAL:
return "INITIAL";
case ETHTOOL_MM_VERIFY_STATUS_VERIFYING:
return "VERIFYING";
case ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED:
return "SUCCEEDED";
case ETHTOOL_MM_VERIFY_STATUS_FAILED:
return "FAILED";
case ETHTOOL_MM_VERIFY_STATUS_DISABLED:
return "DISABLED";
default:
return "UNKNOWN";
}
}
static enum ethtool_mm_verify_status ocelot_mm_verify_status(u32 val)
{
switch (DEV_MM_STAT_MM_STATUS_PRMPT_VERIFY_STATE_X(val)) {
case 0:
return ETHTOOL_MM_VERIFY_STATUS_INITIAL;
case 1:
return ETHTOOL_MM_VERIFY_STATUS_VERIFYING;
case 2:
return ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED;
case 3:
return ETHTOOL_MM_VERIFY_STATUS_FAILED;
case 4:
return ETHTOOL_MM_VERIFY_STATUS_DISABLED;
default:
return ETHTOOL_MM_VERIFY_STATUS_UNKNOWN;
}
}
void ocelot_port_update_active_preemptible_tcs(struct ocelot *ocelot, int port)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
struct ocelot_mm_state *mm = &ocelot->mm[port];
u32 val = 0;
lockdep_assert_held(&ocelot->fwd_domain_lock);
/* Only commit preemptible TCs when MAC Merge is active.
* On NXP LS1028A, when using QSGMII, the port hangs if transmitting
* preemptible frames at any other link speed than gigabit, so avoid
* preemption at lower speeds in this PHY mode.
*/
if ((ocelot_port->phy_mode != PHY_INTERFACE_MODE_QSGMII ||
ocelot_port->speed == SPEED_1000) && mm->tx_active)
val = mm->preemptible_tcs;
/* Cut through switching doesn't work for preemptible priorities,
* so first make sure it is disabled. Also, changing the preemptible
* TCs affects the oversized frame dropping logic, so that needs to be
* re-triggered. And since tas_guard_bands_update() also implicitly
* calls cut_through_fwd(), we don't need to explicitly call it.
*/
mm->active_preemptible_tcs = val;
ocelot->ops->tas_guard_bands_update(ocelot, port);
dev_dbg(ocelot->dev,
"port %d %s/%s, MM TX %s, preemptible TCs 0x%x, active 0x%x\n",
port, phy_modes(ocelot_port->phy_mode),
phy_speed_to_str(ocelot_port->speed),
mm->tx_active ? "active" : "inactive", mm->preemptible_tcs,
mm->active_preemptible_tcs);
ocelot_rmw_rix(ocelot, QSYS_PREEMPTION_CFG_P_QUEUES(val),
QSYS_PREEMPTION_CFG_P_QUEUES_M,
QSYS_PREEMPTION_CFG, port);
}
void ocelot_port_change_fp(struct ocelot *ocelot, int port,
unsigned long preemptible_tcs)
{
struct ocelot_mm_state *mm = &ocelot->mm[port];
lockdep_assert_held(&ocelot->fwd_domain_lock);
if (mm->preemptible_tcs == preemptible_tcs)
return;
mm->preemptible_tcs = preemptible_tcs;
ocelot_port_update_active_preemptible_tcs(ocelot, port);
}
static void ocelot_mm_update_port_status(struct ocelot *ocelot, int port)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
struct ocelot_mm_state *mm = &ocelot->mm[port];
enum ethtool_mm_verify_status verify_status;
u32 val, ack = 0;
if (!mm->tx_enabled)
return;
val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS);
verify_status = ocelot_mm_verify_status(val);
if (mm->verify_status != verify_status) {
dev_dbg(ocelot->dev,
"Port %d MAC Merge verification state %s\n",
port, mm_verify_state_to_string(verify_status));
mm->verify_status = verify_status;
}
if (val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY) {
mm->tx_active = !!(val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STATUS);
dev_dbg(ocelot->dev, "Port %d TX preemption %s\n",
port, mm->tx_active ? "active" : "inactive");
ocelot_port_update_active_preemptible_tcs(ocelot, port);
ack |= DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY;
}
if (val & DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY) {
dev_err(ocelot->dev,
"Unexpected P-frame received on port %d while verification was unsuccessful or not yet verified\n",
port);
ack |= DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY;
}
if (val & DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY) {
dev_err(ocelot->dev,
"Unexpected P-frame requested to be transmitted on port %d while verification was unsuccessful or not yet verified, or MM_TX_ENA=0\n",
port);
ack |= DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY;
}
if (ack)
ocelot_port_writel(ocelot_port, ack, DEV_MM_STATUS);
}
void ocelot_mm_irq(struct ocelot *ocelot)
{
int port;
mutex_lock(&ocelot->fwd_domain_lock);
for (port = 0; port < ocelot->num_phys_ports; port++)
ocelot_mm_update_port_status(ocelot, port);
mutex_unlock(&ocelot->fwd_domain_lock);
}
EXPORT_SYMBOL_GPL(ocelot_mm_irq);
int ocelot_port_set_mm(struct ocelot *ocelot, int port,
struct ethtool_mm_cfg *cfg,
struct netlink_ext_ack *extack)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
u32 mm_enable = 0, verify_disable = 0, add_frag_size;
struct ocelot_mm_state *mm;
int err;
if (!ocelot->mm_supported)
return -EOPNOTSUPP;
mm = &ocelot->mm[port];
err = ethtool_mm_frag_size_min_to_add(cfg->tx_min_frag_size,
&add_frag_size, extack);
if (err)
return err;
if (cfg->pmac_enabled)
mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA;
if (cfg->tx_enabled)
mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA;
if (!cfg->verify_enabled)
verify_disable = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS;
mutex_lock(&ocelot->fwd_domain_lock);
ocelot_port_rmwl(ocelot_port, mm_enable,
DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA |
DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA,
DEV_MM_ENABLE_CONFIG);
ocelot_port_rmwl(ocelot_port, verify_disable |
DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME(cfg->verify_time),
DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS |
DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_M,
DEV_MM_VERIF_CONFIG);
ocelot_rmw_rix(ocelot,
QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE(add_frag_size),
QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_M,
QSYS_PREEMPTION_CFG,
port);
/* The switch will emit an IRQ when TX is disabled, to notify that it
* has become inactive. We optimize ocelot_mm_update_port_status() to
* not bother processing MM IRQs at all for ports with TX disabled,
* but we need to ACK this IRQ now, while mm->tx_enabled is still set,
* otherwise we get an IRQ storm.
*/
if (mm->tx_enabled && !cfg->tx_enabled) {
ocelot_mm_update_port_status(ocelot, port);
WARN_ON(mm->tx_active);
}
mm->tx_enabled = cfg->tx_enabled;
mutex_unlock(&ocelot->fwd_domain_lock);
return 0;
}
EXPORT_SYMBOL_GPL(ocelot_port_set_mm);
int ocelot_port_get_mm(struct ocelot *ocelot, int port,
struct ethtool_mm_state *state)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
struct ocelot_mm_state *mm;
u32 val, add_frag_size;
if (!ocelot->mm_supported)
return -EOPNOTSUPP;
mm = &ocelot->mm[port];
mutex_lock(&ocelot->fwd_domain_lock);
val = ocelot_port_readl(ocelot_port, DEV_MM_ENABLE_CONFIG);
state->pmac_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA);
state->tx_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA);
val = ocelot_port_readl(ocelot_port, DEV_MM_VERIF_CONFIG);
state->verify_enabled = !(val & DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS);
state->verify_time = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_X(val);
state->max_verify_time = 128;
val = ocelot_read_rix(ocelot, QSYS_PREEMPTION_CFG, port);
add_frag_size = QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_X(val);
state->tx_min_frag_size = ethtool_mm_frag_size_add_to_min(add_frag_size);
state->rx_min_frag_size = ETH_ZLEN;
ocelot_mm_update_port_status(ocelot, port);
state->verify_status = mm->verify_status;
state->tx_active = mm->tx_active;
mutex_unlock(&ocelot->fwd_domain_lock);
return 0;
}
EXPORT_SYMBOL_GPL(ocelot_port_get_mm);
int ocelot_mm_init(struct ocelot *ocelot)
{
struct ocelot_port *ocelot_port;
struct ocelot_mm_state *mm;
int port;
if (!ocelot->mm_supported)
return 0;
ocelot->mm = devm_kcalloc(ocelot->dev, ocelot->num_phys_ports,
sizeof(*ocelot->mm), GFP_KERNEL);
if (!ocelot->mm)
return -ENOMEM;
for (port = 0; port < ocelot->num_phys_ports; port++) {
u32 val;
mm = &ocelot->mm[port];
ocelot_port = ocelot->ports[port];
/* Update initial status variable for the
* verification state machine
*/
val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS);
mm->verify_status = ocelot_mm_verify_status(val);
}
return 0;
}
@@ -0,0 +1,236 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/* Microsemi Ocelot Switch driver
*
* Copyright (c) 2017, 2019 Microsemi Corporation
* Copyright 2020-2021 NXP
*/
#include <linux/if_bridge.h>
#include <linux/mrp_bridge.h>
#include <soc/mscc/ocelot_vcap.h>
#include <uapi/linux/mrp_bridge.h>
#include "ocelot.h"
#include "ocelot_vcap.h"
static const u8 mrp_test_dmac[] = { 0x01, 0x15, 0x4e, 0x00, 0x00, 0x01 };
static const u8 mrp_control_dmac[] = { 0x01, 0x15, 0x4e, 0x00, 0x00, 0x02 };
static int ocelot_mrp_find_partner_port(struct ocelot *ocelot,
struct ocelot_port *p)
{
int i;
for (i = 0; i < ocelot->num_phys_ports; ++i) {
struct ocelot_port *ocelot_port = ocelot->ports[i];
if (!ocelot_port || p == ocelot_port)
continue;
if (ocelot_port->mrp_ring_id == p->mrp_ring_id)
return i;
}
return -1;
}
static int ocelot_mrp_del_vcap(struct ocelot *ocelot, int id)
{
struct ocelot_vcap_block *block_vcap_is2;
struct ocelot_vcap_filter *filter;
block_vcap_is2 = &ocelot->block[VCAP_IS2];
filter = ocelot_vcap_block_find_filter_by_id(block_vcap_is2, id,
false);
if (!filter)
return 0;
return ocelot_vcap_filter_del(ocelot, filter);
}
static int ocelot_mrp_redirect_add_vcap(struct ocelot *ocelot, int src_port,
int dst_port)
{
const u8 mrp_test_mask[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
struct ocelot_vcap_filter *filter;
int err;
filter = kzalloc_obj(*filter);
if (!filter)
return -ENOMEM;
filter->key_type = OCELOT_VCAP_KEY_ETYPE;
filter->prio = 1;
filter->id.cookie = OCELOT_VCAP_IS2_MRP_REDIRECT(ocelot, src_port);
filter->id.tc_offload = false;
filter->block_id = VCAP_IS2;
filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
filter->ingress_port_mask = BIT(src_port);
ether_addr_copy(filter->key.etype.dmac.value, mrp_test_dmac);
ether_addr_copy(filter->key.etype.dmac.mask, mrp_test_mask);
filter->action.mask_mode = OCELOT_MASK_MODE_REDIRECT;
filter->action.port_mask = BIT(dst_port);
err = ocelot_vcap_filter_add(ocelot, filter, NULL);
if (err)
kfree(filter);
return err;
}
static void ocelot_populate_mrp_trap_key(struct ocelot_vcap_filter *filter)
{
const u8 mrp_mask[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
/* Here is possible to use control or test dmac because the mask
* doesn't cover the LSB
*/
ether_addr_copy(filter->key.etype.dmac.value, mrp_test_dmac);
ether_addr_copy(filter->key.etype.dmac.mask, mrp_mask);
}
static int ocelot_mrp_trap_add(struct ocelot *ocelot, int port)
{
unsigned long cookie = OCELOT_VCAP_IS2_MRP_TRAP(ocelot);
return ocelot_trap_add(ocelot, port, cookie, false,
ocelot_populate_mrp_trap_key);
}
static int ocelot_mrp_trap_del(struct ocelot *ocelot, int port)
{
unsigned long cookie = OCELOT_VCAP_IS2_MRP_TRAP(ocelot);
return ocelot_trap_del(ocelot, port, cookie);
}
static void ocelot_mrp_save_mac(struct ocelot *ocelot,
struct ocelot_port *port)
{
ocelot_mact_learn(ocelot, PGID_BLACKHOLE, mrp_test_dmac,
OCELOT_STANDALONE_PVID, ENTRYTYPE_LOCKED);
ocelot_mact_learn(ocelot, PGID_BLACKHOLE, mrp_control_dmac,
OCELOT_STANDALONE_PVID, ENTRYTYPE_LOCKED);
}
static void ocelot_mrp_del_mac(struct ocelot *ocelot,
struct ocelot_port *port)
{
ocelot_mact_forget(ocelot, mrp_test_dmac, OCELOT_STANDALONE_PVID);
ocelot_mact_forget(ocelot, mrp_control_dmac, OCELOT_STANDALONE_PVID);
}
int ocelot_mrp_add(struct ocelot *ocelot, int port,
const struct switchdev_obj_mrp *mrp)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
struct ocelot_port_private *priv;
struct net_device *dev;
if (!ocelot_port)
return -EOPNOTSUPP;
priv = container_of(ocelot_port, struct ocelot_port_private, port);
dev = priv->dev;
if (mrp->p_port != dev && mrp->s_port != dev)
return 0;
ocelot_port->mrp_ring_id = mrp->ring_id;
return 0;
}
EXPORT_SYMBOL(ocelot_mrp_add);
int ocelot_mrp_del(struct ocelot *ocelot, int port,
const struct switchdev_obj_mrp *mrp)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
if (!ocelot_port)
return -EOPNOTSUPP;
if (ocelot_port->mrp_ring_id != mrp->ring_id)
return 0;
ocelot_port->mrp_ring_id = 0;
return 0;
}
EXPORT_SYMBOL(ocelot_mrp_del);
int ocelot_mrp_add_ring_role(struct ocelot *ocelot, int port,
const struct switchdev_obj_ring_role_mrp *mrp)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
int dst_port;
int err;
if (!ocelot_port)
return -EOPNOTSUPP;
if (mrp->ring_role != BR_MRP_RING_ROLE_MRC && !mrp->sw_backup)
return -EOPNOTSUPP;
if (ocelot_port->mrp_ring_id != mrp->ring_id)
return 0;
ocelot_mrp_save_mac(ocelot, ocelot_port);
if (mrp->ring_role != BR_MRP_RING_ROLE_MRC)
return ocelot_mrp_trap_add(ocelot, port);
dst_port = ocelot_mrp_find_partner_port(ocelot, ocelot_port);
if (dst_port == -1)
return -EINVAL;
err = ocelot_mrp_redirect_add_vcap(ocelot, port, dst_port);
if (err)
return err;
err = ocelot_mrp_trap_add(ocelot, port);
if (err) {
ocelot_mrp_del_vcap(ocelot,
OCELOT_VCAP_IS2_MRP_REDIRECT(ocelot, port));
return err;
}
return 0;
}
EXPORT_SYMBOL(ocelot_mrp_add_ring_role);
int ocelot_mrp_del_ring_role(struct ocelot *ocelot, int port,
const struct switchdev_obj_ring_role_mrp *mrp)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
int err, i;
if (!ocelot_port)
return -EOPNOTSUPP;
if (mrp->ring_role != BR_MRP_RING_ROLE_MRC && !mrp->sw_backup)
return -EOPNOTSUPP;
if (ocelot_port->mrp_ring_id != mrp->ring_id)
return 0;
err = ocelot_mrp_trap_del(ocelot, port);
if (err)
return err;
ocelot_mrp_del_vcap(ocelot, OCELOT_VCAP_IS2_MRP_REDIRECT(ocelot, port));
for (i = 0; i < ocelot->num_phys_ports; ++i) {
ocelot_port = ocelot->ports[i];
if (!ocelot_port)
continue;
if (ocelot_port->mrp_ring_id != 0)
goto out;
}
ocelot_mrp_del_mac(ocelot, ocelot->ports[port]);
out:
return 0;
}
EXPORT_SYMBOL(ocelot_mrp_del_ring_role);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/* Microsemi Ocelot Switch driver
*
* Copyright (c) 2019 Microsemi Corporation
*/
#include <soc/mscc/ocelot.h>
#include "ocelot_police.h"
/* Types for ANA:POL[0-192]:POL_MODE_CFG.FRM_MODE */
#define POL_MODE_LINERATE 0 /* Incl IPG. Unit: 33 1/3 kbps, 4096 bytes */
#define POL_MODE_DATARATE 1 /* Excl IPG. Unit: 33 1/3 kbps, 4096 bytes */
#define POL_MODE_FRMRATE_HI 2 /* Unit: 33 1/3 fps, 32.8 frames */
#define POL_MODE_FRMRATE_LO 3 /* Unit: 1/3 fps, 0.3 frames */
/* Policer indexes */
#define POL_IX_PORT 0 /* 0-11 : Port policers */
#define POL_IX_QUEUE 32 /* 32-127 : Queue policers */
/* Default policer order */
#define POL_ORDER 0x1d3 /* Ocelot policer order: Serial (QoS -> Port -> VCAP) */
int qos_policer_conf_set(struct ocelot *ocelot, u32 pol_ix,
struct qos_policer_conf *conf)
{
u32 cf = 0, cir_ena = 0, frm_mode = POL_MODE_LINERATE;
u32 cir = 0, cbs = 0, pir = 0, pbs = 0;
bool cir_discard = 0, pir_discard = 0;
u32 pbs_max = 0, cbs_max = 0;
u8 ipg = 20;
u32 value;
pir = conf->pir;
pbs = conf->pbs;
switch (conf->mode) {
case MSCC_QOS_RATE_MODE_LINE:
case MSCC_QOS_RATE_MODE_DATA:
if (conf->mode == MSCC_QOS_RATE_MODE_LINE) {
frm_mode = POL_MODE_LINERATE;
ipg = min_t(u8, GENMASK(4, 0), conf->ipg);
} else {
frm_mode = POL_MODE_DATARATE;
}
if (conf->dlb) {
cir_ena = 1;
cir = conf->cir;
cbs = conf->cbs;
if (cir == 0 && cbs == 0) {
/* Discard cir frames */
cir_discard = 1;
} else {
cir = DIV_ROUND_UP(cir, 100);
cir *= 3; /* 33 1/3 kbps */
cbs = DIV_ROUND_UP(cbs, 4096);
cbs = (cbs ? cbs : 1); /* No zero burst size */
cbs_max = 60; /* Limit burst size */
cf = conf->cf;
if (cf)
pir += conf->cir;
}
}
if (pir == 0 && pbs == 0) {
/* Discard PIR frames */
pir_discard = 1;
} else {
pir = DIV_ROUND_UP(pir, 100);
pir *= 3; /* 33 1/3 kbps */
pbs = DIV_ROUND_UP(pbs, 4096);
pbs = (pbs ? pbs : 1); /* No zero burst size */
pbs_max = 60; /* Limit burst size */
}
break;
case MSCC_QOS_RATE_MODE_FRAME:
if (pir >= 100) {
frm_mode = POL_MODE_FRMRATE_HI;
pir = DIV_ROUND_UP(pir, 100);
pir *= 3; /* 33 1/3 fps */
pbs = (pbs * 10) / 328; /* 32.8 frames */
pbs = (pbs ? pbs : 1); /* No zero burst size */
pbs_max = GENMASK(6, 0); /* Limit burst size */
} else {
frm_mode = POL_MODE_FRMRATE_LO;
if (pir == 0 && pbs == 0) {
/* Discard all frames */
pir_discard = 1;
cir_discard = 1;
} else {
pir *= 3; /* 1/3 fps */
pbs = (pbs * 10) / 3; /* 0.3 frames */
pbs = (pbs ? pbs : 1); /* No zero burst size */
pbs_max = 61; /* Limit burst size */
}
}
break;
default: /* MSCC_QOS_RATE_MODE_DISABLED */
/* Disable policer using maximum rate and zero burst */
pir = GENMASK(15, 0);
pbs = 0;
break;
}
/* Check limits */
if (pir > GENMASK(15, 0)) {
dev_err(ocelot->dev,
"Invalid pir for policer %u: %u (max %lu)\n",
pol_ix, pir, GENMASK(15, 0));
return -EINVAL;
}
if (cir > GENMASK(15, 0)) {
dev_err(ocelot->dev,
"Invalid cir for policer %u: %u (max %lu)\n",
pol_ix, cir, GENMASK(15, 0));
return -EINVAL;
}
if (pbs > pbs_max) {
dev_err(ocelot->dev,
"Invalid pbs for policer %u: %u (max %u)\n",
pol_ix, pbs, pbs_max);
return -EINVAL;
}
if (cbs > cbs_max) {
dev_err(ocelot->dev,
"Invalid cbs for policer %u: %u (max %u)\n",
pol_ix, cbs, cbs_max);
return -EINVAL;
}
value = (ANA_POL_MODE_CFG_IPG_SIZE(ipg) |
ANA_POL_MODE_CFG_FRM_MODE(frm_mode) |
(cf ? ANA_POL_MODE_CFG_DLB_COUPLED : 0) |
(cir_ena ? ANA_POL_MODE_CFG_CIR_ENA : 0) |
ANA_POL_MODE_CFG_OVERSHOOT_ENA);
ocelot_write_gix(ocelot, value, ANA_POL_MODE_CFG, pol_ix);
ocelot_write_gix(ocelot,
ANA_POL_PIR_CFG_PIR_RATE(pir) |
ANA_POL_PIR_CFG_PIR_BURST(pbs),
ANA_POL_PIR_CFG, pol_ix);
ocelot_write_gix(ocelot,
(pir_discard ? GENMASK(22, 0) : 0),
ANA_POL_PIR_STATE, pol_ix);
ocelot_write_gix(ocelot,
ANA_POL_CIR_CFG_CIR_RATE(cir) |
ANA_POL_CIR_CFG_CIR_BURST(cbs),
ANA_POL_CIR_CFG, pol_ix);
ocelot_write_gix(ocelot,
(cir_discard ? GENMASK(22, 0) : 0),
ANA_POL_CIR_STATE, pol_ix);
return 0;
}
int ocelot_policer_validate(const struct flow_action *action,
const struct flow_action_entry *a,
struct netlink_ext_ack *extack)
{
if (a->police.exceed.act_id != FLOW_ACTION_DROP) {
NL_SET_ERR_MSG_MOD(extack,
"Offload not supported when exceed action is not drop");
return -EOPNOTSUPP;
}
if (a->police.notexceed.act_id != FLOW_ACTION_PIPE &&
a->police.notexceed.act_id != FLOW_ACTION_ACCEPT) {
NL_SET_ERR_MSG_MOD(extack,
"Offload not supported when conform action is not pipe or ok");
return -EOPNOTSUPP;
}
if (a->police.notexceed.act_id == FLOW_ACTION_ACCEPT &&
!flow_action_is_last_entry(action, a)) {
NL_SET_ERR_MSG_MOD(extack,
"Offload not supported when conform action is ok, but police action is not last");
return -EOPNOTSUPP;
}
if (a->police.peakrate_bytes_ps ||
a->police.avrate || a->police.overhead) {
NL_SET_ERR_MSG_MOD(extack,
"Offload not supported when peakrate/avrate/overhead is configured");
return -EOPNOTSUPP;
}
if (a->police.rate_pkt_ps) {
NL_SET_ERR_MSG_MOD(extack,
"Offload does not support packets per second");
return -EOPNOTSUPP;
}
return 0;
}
EXPORT_SYMBOL(ocelot_policer_validate);
int ocelot_port_policer_add(struct ocelot *ocelot, int port,
struct ocelot_policer *pol)
{
struct qos_policer_conf pp = { 0 };
int err;
if (!pol)
return -EINVAL;
pp.mode = MSCC_QOS_RATE_MODE_DATA;
pp.pir = pol->rate;
pp.pbs = pol->burst;
dev_dbg(ocelot->dev, "%s: port %u pir %u kbps, pbs %u bytes\n",
__func__, port, pp.pir, pp.pbs);
err = qos_policer_conf_set(ocelot, POL_IX_PORT + port, &pp);
if (err)
return err;
ocelot_rmw_gix(ocelot,
ANA_PORT_POL_CFG_PORT_POL_ENA |
ANA_PORT_POL_CFG_POL_ORDER(POL_ORDER),
ANA_PORT_POL_CFG_PORT_POL_ENA |
ANA_PORT_POL_CFG_POL_ORDER_M,
ANA_PORT_POL_CFG, port);
return 0;
}
EXPORT_SYMBOL(ocelot_port_policer_add);
int ocelot_port_policer_del(struct ocelot *ocelot, int port)
{
struct qos_policer_conf pp = { 0 };
int err;
dev_dbg(ocelot->dev, "%s: port %u\n", __func__, port);
pp.mode = MSCC_QOS_RATE_MODE_DISABLED;
err = qos_policer_conf_set(ocelot, POL_IX_PORT + port, &pp);
if (err)
return err;
ocelot_rmw_gix(ocelot,
ANA_PORT_POL_CFG_POL_ORDER(POL_ORDER),
ANA_PORT_POL_CFG_PORT_POL_ENA |
ANA_PORT_POL_CFG_POL_ORDER_M,
ANA_PORT_POL_CFG, port);
return 0;
}
EXPORT_SYMBOL(ocelot_port_policer_del);
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
/* Microsemi Ocelot Switch driver
*
* Copyright (c) 2019 Microsemi Corporation
*/
#ifndef _MSCC_OCELOT_POLICE_H_
#define _MSCC_OCELOT_POLICE_H_
#include "ocelot.h"
#include <net/flow_offload.h>
enum mscc_qos_rate_mode {
MSCC_QOS_RATE_MODE_DISABLED, /* Policer/shaper disabled */
MSCC_QOS_RATE_MODE_LINE, /* Measure line rate in kbps incl. IPG */
MSCC_QOS_RATE_MODE_DATA, /* Measures data rate in kbps excl. IPG */
MSCC_QOS_RATE_MODE_FRAME, /* Measures frame rate in fps */
__MSCC_QOS_RATE_MODE_END,
NUM_MSCC_QOS_RATE_MODE = __MSCC_QOS_RATE_MODE_END,
MSCC_QOS_RATE_MODE_MAX = __MSCC_QOS_RATE_MODE_END - 1,
};
struct qos_policer_conf {
enum mscc_qos_rate_mode mode;
bool dlb; /* Enable DLB (dual leaky bucket mode */
bool cf; /* Coupling flag (ignored in SLB mode) */
u32 cir; /* CIR in kbps/fps (ignored in SLB mode) */
u32 cbs; /* CBS in bytes/frames (ignored in SLB mode) */
u32 pir; /* PIR in kbps/fps */
u32 pbs; /* PBS in bytes/frames */
u8 ipg; /* Size of IPG when MSCC_QOS_RATE_MODE_LINE is chosen */
};
int qos_policer_conf_set(struct ocelot *ocelot, u32 pol_ix,
struct qos_policer_conf *conf);
int ocelot_policer_validate(const struct flow_action *action,
const struct flow_action_entry *a,
struct netlink_ext_ack *extack);
#endif /* _MSCC_OCELOT_POLICE_H_ */
@@ -0,0 +1,912 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/* Microsemi Ocelot PTP clock driver
*
* Copyright (c) 2017 Microsemi Corporation
* Copyright 2020 NXP
*/
#include <linux/time64.h>
#include <linux/dsa/ocelot.h>
#include <linux/ptp_classify.h>
#include <soc/mscc/ocelot_ptp.h>
#include <soc/mscc/ocelot_sys.h>
#include <soc/mscc/ocelot_vcap.h>
#include <soc/mscc/ocelot.h>
#include "ocelot.h"
#define OCELOT_PTP_TX_TSTAMP_TIMEOUT (5 * HZ)
int ocelot_ptp_gettime64(struct ptp_clock_info *ptp, struct timespec64 *ts)
{
struct ocelot *ocelot = container_of(ptp, struct ocelot, ptp_info);
unsigned long flags;
time64_t s;
u32 val;
s64 ns;
spin_lock_irqsave(&ocelot->ptp_clock_lock, flags);
val = ocelot_read_rix(ocelot, PTP_PIN_CFG, TOD_ACC_PIN);
val &= ~(PTP_PIN_CFG_SYNC | PTP_PIN_CFG_ACTION_MASK | PTP_PIN_CFG_DOM);
val |= PTP_PIN_CFG_ACTION(PTP_PIN_ACTION_SAVE);
ocelot_write_rix(ocelot, val, PTP_PIN_CFG, TOD_ACC_PIN);
s = ocelot_read_rix(ocelot, PTP_PIN_TOD_SEC_MSB, TOD_ACC_PIN) & 0xffff;
s <<= 32;
s += ocelot_read_rix(ocelot, PTP_PIN_TOD_SEC_LSB, TOD_ACC_PIN);
ns = ocelot_read_rix(ocelot, PTP_PIN_TOD_NSEC, TOD_ACC_PIN);
spin_unlock_irqrestore(&ocelot->ptp_clock_lock, flags);
/* Deal with negative values */
if (ns >= 0x3ffffff0 && ns <= 0x3fffffff) {
s--;
ns &= 0xf;
ns += 999999984;
}
set_normalized_timespec64(ts, s, ns);
return 0;
}
EXPORT_SYMBOL(ocelot_ptp_gettime64);
int ocelot_ptp_settime64(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct ocelot *ocelot = container_of(ptp, struct ocelot, ptp_info);
unsigned long flags;
u32 val;
spin_lock_irqsave(&ocelot->ptp_clock_lock, flags);
val = ocelot_read_rix(ocelot, PTP_PIN_CFG, TOD_ACC_PIN);
val &= ~(PTP_PIN_CFG_SYNC | PTP_PIN_CFG_ACTION_MASK | PTP_PIN_CFG_DOM);
val |= PTP_PIN_CFG_ACTION(PTP_PIN_ACTION_IDLE);
ocelot_write_rix(ocelot, val, PTP_PIN_CFG, TOD_ACC_PIN);
ocelot_write_rix(ocelot, lower_32_bits(ts->tv_sec), PTP_PIN_TOD_SEC_LSB,
TOD_ACC_PIN);
ocelot_write_rix(ocelot, upper_32_bits(ts->tv_sec), PTP_PIN_TOD_SEC_MSB,
TOD_ACC_PIN);
ocelot_write_rix(ocelot, ts->tv_nsec, PTP_PIN_TOD_NSEC, TOD_ACC_PIN);
val = ocelot_read_rix(ocelot, PTP_PIN_CFG, TOD_ACC_PIN);
val &= ~(PTP_PIN_CFG_SYNC | PTP_PIN_CFG_ACTION_MASK | PTP_PIN_CFG_DOM);
val |= PTP_PIN_CFG_ACTION(PTP_PIN_ACTION_LOAD);
ocelot_write_rix(ocelot, val, PTP_PIN_CFG, TOD_ACC_PIN);
spin_unlock_irqrestore(&ocelot->ptp_clock_lock, flags);
if (ocelot->ops->tas_clock_adjust)
ocelot->ops->tas_clock_adjust(ocelot);
return 0;
}
EXPORT_SYMBOL(ocelot_ptp_settime64);
int ocelot_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
if (delta > -(NSEC_PER_SEC / 2) && delta < (NSEC_PER_SEC / 2)) {
struct ocelot *ocelot = container_of(ptp, struct ocelot,
ptp_info);
unsigned long flags;
u32 val;
spin_lock_irqsave(&ocelot->ptp_clock_lock, flags);
val = ocelot_read_rix(ocelot, PTP_PIN_CFG, TOD_ACC_PIN);
val &= ~(PTP_PIN_CFG_SYNC | PTP_PIN_CFG_ACTION_MASK |
PTP_PIN_CFG_DOM);
val |= PTP_PIN_CFG_ACTION(PTP_PIN_ACTION_IDLE);
ocelot_write_rix(ocelot, val, PTP_PIN_CFG, TOD_ACC_PIN);
ocelot_write_rix(ocelot, 0, PTP_PIN_TOD_SEC_LSB, TOD_ACC_PIN);
ocelot_write_rix(ocelot, 0, PTP_PIN_TOD_SEC_MSB, TOD_ACC_PIN);
ocelot_write_rix(ocelot, delta, PTP_PIN_TOD_NSEC, TOD_ACC_PIN);
val = ocelot_read_rix(ocelot, PTP_PIN_CFG, TOD_ACC_PIN);
val &= ~(PTP_PIN_CFG_SYNC | PTP_PIN_CFG_ACTION_MASK |
PTP_PIN_CFG_DOM);
val |= PTP_PIN_CFG_ACTION(PTP_PIN_ACTION_DELTA);
ocelot_write_rix(ocelot, val, PTP_PIN_CFG, TOD_ACC_PIN);
spin_unlock_irqrestore(&ocelot->ptp_clock_lock, flags);
if (ocelot->ops->tas_clock_adjust)
ocelot->ops->tas_clock_adjust(ocelot);
} else {
/* Fall back using ocelot_ptp_settime64 which is not exact. */
struct timespec64 ts;
u64 now;
ocelot_ptp_gettime64(ptp, &ts);
now = ktime_to_ns(timespec64_to_ktime(ts));
ts = ns_to_timespec64(now + delta);
ocelot_ptp_settime64(ptp, &ts);
}
return 0;
}
EXPORT_SYMBOL(ocelot_ptp_adjtime);
int ocelot_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct ocelot *ocelot = container_of(ptp, struct ocelot, ptp_info);
u32 unit = 0, direction = 0;
unsigned long flags;
u64 adj = 0;
spin_lock_irqsave(&ocelot->ptp_clock_lock, flags);
if (!scaled_ppm)
goto disable_adj;
if (scaled_ppm < 0) {
direction = PTP_CFG_CLK_ADJ_CFG_DIR;
scaled_ppm = -scaled_ppm;
}
adj = PSEC_PER_SEC << 16;
do_div(adj, scaled_ppm);
do_div(adj, 1000);
/* If the adjustment value is too large, use ns instead */
if (adj >= (1L << 30)) {
unit = PTP_CFG_CLK_ADJ_FREQ_NS;
do_div(adj, 1000);
}
/* Still too big */
if (adj >= (1L << 30))
goto disable_adj;
ocelot_write(ocelot, unit | adj, PTP_CLK_CFG_ADJ_FREQ);
ocelot_write(ocelot, PTP_CFG_CLK_ADJ_CFG_ENA | direction,
PTP_CLK_CFG_ADJ_CFG);
spin_unlock_irqrestore(&ocelot->ptp_clock_lock, flags);
return 0;
disable_adj:
ocelot_write(ocelot, 0, PTP_CLK_CFG_ADJ_CFG);
spin_unlock_irqrestore(&ocelot->ptp_clock_lock, flags);
return 0;
}
EXPORT_SYMBOL(ocelot_ptp_adjfine);
int ocelot_ptp_verify(struct ptp_clock_info *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan)
{
switch (func) {
case PTP_PF_NONE:
case PTP_PF_PEROUT:
break;
case PTP_PF_EXTTS:
case PTP_PF_PHYSYNC:
return -1;
}
return 0;
}
EXPORT_SYMBOL(ocelot_ptp_verify);
int ocelot_ptp_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
struct ocelot *ocelot = container_of(ptp, struct ocelot, ptp_info);
struct timespec64 ts_phase, ts_period;
enum ocelot_ptp_pins ptp_pin;
unsigned long flags;
bool pps = false;
int pin = -1;
s64 wf_high;
s64 wf_low;
u32 val;
switch (rq->type) {
case PTP_CLK_REQ_PEROUT:
pin = ptp_find_pin(ocelot->ptp_clock, PTP_PF_PEROUT,
rq->perout.index);
if (pin == 0)
ptp_pin = PTP_PIN_0;
else if (pin == 1)
ptp_pin = PTP_PIN_1;
else if (pin == 2)
ptp_pin = PTP_PIN_2;
else if (pin == 3)
ptp_pin = PTP_PIN_3;
else
return -EBUSY;
ts_period.tv_sec = rq->perout.period.sec;
ts_period.tv_nsec = rq->perout.period.nsec;
if (ts_period.tv_sec == 1 && ts_period.tv_nsec == 0)
pps = true;
/* Handle turning off */
if (!on) {
spin_lock_irqsave(&ocelot->ptp_clock_lock, flags);
val = PTP_PIN_CFG_ACTION(PTP_PIN_ACTION_IDLE);
ocelot_write_rix(ocelot, val, PTP_PIN_CFG, ptp_pin);
spin_unlock_irqrestore(&ocelot->ptp_clock_lock, flags);
break;
}
if (rq->perout.flags & PTP_PEROUT_PHASE) {
ts_phase.tv_sec = rq->perout.phase.sec;
ts_phase.tv_nsec = rq->perout.phase.nsec;
} else {
/* Compatibility */
ts_phase.tv_sec = rq->perout.start.sec;
ts_phase.tv_nsec = rq->perout.start.nsec;
}
if (ts_phase.tv_sec || (ts_phase.tv_nsec && !pps)) {
dev_warn(ocelot->dev,
"Absolute start time not supported!\n");
dev_warn(ocelot->dev,
"Accept nsec for PPS phase adjustment, otherwise start time should be 0 0.\n");
return -EINVAL;
}
/* Calculate waveform high and low times */
if (rq->perout.flags & PTP_PEROUT_DUTY_CYCLE) {
struct timespec64 ts_on;
ts_on.tv_sec = rq->perout.on.sec;
ts_on.tv_nsec = rq->perout.on.nsec;
wf_high = timespec64_to_ns(&ts_on);
} else {
if (pps) {
wf_high = 1000;
} else {
wf_high = timespec64_to_ns(&ts_period);
wf_high = div_s64(wf_high, 2);
}
}
wf_low = timespec64_to_ns(&ts_period);
wf_low -= wf_high;
/* Handle PPS request */
if (pps) {
spin_lock_irqsave(&ocelot->ptp_clock_lock, flags);
ocelot_write_rix(ocelot, ts_phase.tv_nsec,
PTP_PIN_WF_LOW_PERIOD, ptp_pin);
ocelot_write_rix(ocelot, wf_high,
PTP_PIN_WF_HIGH_PERIOD, ptp_pin);
val = PTP_PIN_CFG_ACTION(PTP_PIN_ACTION_CLOCK);
val |= PTP_PIN_CFG_SYNC;
ocelot_write_rix(ocelot, val, PTP_PIN_CFG, ptp_pin);
spin_unlock_irqrestore(&ocelot->ptp_clock_lock, flags);
break;
}
/* Handle periodic clock */
if (wf_high > 0x3fffffff || wf_high <= 0x6)
return -EINVAL;
if (wf_low > 0x3fffffff || wf_low <= 0x6)
return -EINVAL;
spin_lock_irqsave(&ocelot->ptp_clock_lock, flags);
ocelot_write_rix(ocelot, wf_low, PTP_PIN_WF_LOW_PERIOD,
ptp_pin);
ocelot_write_rix(ocelot, wf_high, PTP_PIN_WF_HIGH_PERIOD,
ptp_pin);
val = PTP_PIN_CFG_ACTION(PTP_PIN_ACTION_CLOCK);
ocelot_write_rix(ocelot, val, PTP_PIN_CFG, ptp_pin);
spin_unlock_irqrestore(&ocelot->ptp_clock_lock, flags);
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
EXPORT_SYMBOL(ocelot_ptp_enable);
static void ocelot_populate_l2_ptp_trap_key(struct ocelot_vcap_filter *trap)
{
trap->key_type = OCELOT_VCAP_KEY_ETYPE;
*(__be16 *)trap->key.etype.etype.value = htons(ETH_P_1588);
*(__be16 *)trap->key.etype.etype.mask = htons(0xffff);
}
static void
ocelot_populate_ipv4_ptp_event_trap_key(struct ocelot_vcap_filter *trap)
{
trap->key_type = OCELOT_VCAP_KEY_IPV4;
trap->key.ipv4.proto.value[0] = IPPROTO_UDP;
trap->key.ipv4.proto.mask[0] = 0xff;
trap->key.ipv4.dport.value = PTP_EV_PORT;
trap->key.ipv4.dport.mask = 0xffff;
}
static void
ocelot_populate_ipv6_ptp_event_trap_key(struct ocelot_vcap_filter *trap)
{
trap->key_type = OCELOT_VCAP_KEY_IPV6;
trap->key.ipv6.proto.value[0] = IPPROTO_UDP;
trap->key.ipv6.proto.mask[0] = 0xff;
trap->key.ipv6.dport.value = PTP_EV_PORT;
trap->key.ipv6.dport.mask = 0xffff;
}
static void
ocelot_populate_ipv4_ptp_general_trap_key(struct ocelot_vcap_filter *trap)
{
trap->key_type = OCELOT_VCAP_KEY_IPV4;
trap->key.ipv4.proto.value[0] = IPPROTO_UDP;
trap->key.ipv4.proto.mask[0] = 0xff;
trap->key.ipv4.dport.value = PTP_GEN_PORT;
trap->key.ipv4.dport.mask = 0xffff;
}
static void
ocelot_populate_ipv6_ptp_general_trap_key(struct ocelot_vcap_filter *trap)
{
trap->key_type = OCELOT_VCAP_KEY_IPV6;
trap->key.ipv6.proto.value[0] = IPPROTO_UDP;
trap->key.ipv6.proto.mask[0] = 0xff;
trap->key.ipv6.dport.value = PTP_GEN_PORT;
trap->key.ipv6.dport.mask = 0xffff;
}
static int ocelot_l2_ptp_trap_add(struct ocelot *ocelot, int port)
{
unsigned long l2_cookie = OCELOT_VCAP_IS2_L2_PTP_TRAP(ocelot);
return ocelot_trap_add(ocelot, port, l2_cookie, true,
ocelot_populate_l2_ptp_trap_key);
}
static int ocelot_l2_ptp_trap_del(struct ocelot *ocelot, int port)
{
unsigned long l2_cookie = OCELOT_VCAP_IS2_L2_PTP_TRAP(ocelot);
return ocelot_trap_del(ocelot, port, l2_cookie);
}
static int ocelot_ipv4_ptp_trap_add(struct ocelot *ocelot, int port)
{
unsigned long ipv4_gen_cookie = OCELOT_VCAP_IS2_IPV4_GEN_PTP_TRAP(ocelot);
unsigned long ipv4_ev_cookie = OCELOT_VCAP_IS2_IPV4_EV_PTP_TRAP(ocelot);
int err;
err = ocelot_trap_add(ocelot, port, ipv4_ev_cookie, true,
ocelot_populate_ipv4_ptp_event_trap_key);
if (err)
return err;
err = ocelot_trap_add(ocelot, port, ipv4_gen_cookie, false,
ocelot_populate_ipv4_ptp_general_trap_key);
if (err)
ocelot_trap_del(ocelot, port, ipv4_ev_cookie);
return err;
}
static int ocelot_ipv4_ptp_trap_del(struct ocelot *ocelot, int port)
{
unsigned long ipv4_gen_cookie = OCELOT_VCAP_IS2_IPV4_GEN_PTP_TRAP(ocelot);
unsigned long ipv4_ev_cookie = OCELOT_VCAP_IS2_IPV4_EV_PTP_TRAP(ocelot);
int err;
err = ocelot_trap_del(ocelot, port, ipv4_ev_cookie);
err |= ocelot_trap_del(ocelot, port, ipv4_gen_cookie);
return err;
}
static int ocelot_ipv6_ptp_trap_add(struct ocelot *ocelot, int port)
{
unsigned long ipv6_gen_cookie = OCELOT_VCAP_IS2_IPV6_GEN_PTP_TRAP(ocelot);
unsigned long ipv6_ev_cookie = OCELOT_VCAP_IS2_IPV6_EV_PTP_TRAP(ocelot);
int err;
err = ocelot_trap_add(ocelot, port, ipv6_ev_cookie, true,
ocelot_populate_ipv6_ptp_event_trap_key);
if (err)
return err;
err = ocelot_trap_add(ocelot, port, ipv6_gen_cookie, false,
ocelot_populate_ipv6_ptp_general_trap_key);
if (err)
ocelot_trap_del(ocelot, port, ipv6_ev_cookie);
return err;
}
static int ocelot_ipv6_ptp_trap_del(struct ocelot *ocelot, int port)
{
unsigned long ipv6_gen_cookie = OCELOT_VCAP_IS2_IPV6_GEN_PTP_TRAP(ocelot);
unsigned long ipv6_ev_cookie = OCELOT_VCAP_IS2_IPV6_EV_PTP_TRAP(ocelot);
int err;
err = ocelot_trap_del(ocelot, port, ipv6_ev_cookie);
err |= ocelot_trap_del(ocelot, port, ipv6_gen_cookie);
return err;
}
static int ocelot_setup_ptp_traps(struct ocelot *ocelot, int port,
bool l2, bool l4)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
int err;
ocelot_port->trap_proto &= ~(OCELOT_PROTO_PTP_L2 |
OCELOT_PROTO_PTP_L4);
if (l2)
err = ocelot_l2_ptp_trap_add(ocelot, port);
else
err = ocelot_l2_ptp_trap_del(ocelot, port);
if (err)
return err;
if (l4) {
err = ocelot_ipv4_ptp_trap_add(ocelot, port);
if (err)
goto err_ipv4;
err = ocelot_ipv6_ptp_trap_add(ocelot, port);
if (err)
goto err_ipv6;
} else {
err = ocelot_ipv4_ptp_trap_del(ocelot, port);
err |= ocelot_ipv6_ptp_trap_del(ocelot, port);
}
if (err)
return err;
if (l2)
ocelot_port->trap_proto |= OCELOT_PROTO_PTP_L2;
if (l4)
ocelot_port->trap_proto |= OCELOT_PROTO_PTP_L4;
return 0;
err_ipv6:
ocelot_ipv4_ptp_trap_del(ocelot, port);
err_ipv4:
if (l2)
ocelot_l2_ptp_trap_del(ocelot, port);
return err;
}
static int ocelot_traps_to_ptp_rx_filter(unsigned int proto)
{
if ((proto & OCELOT_PROTO_PTP_L2) && (proto & OCELOT_PROTO_PTP_L4))
return HWTSTAMP_FILTER_PTP_V2_EVENT;
else if (proto & OCELOT_PROTO_PTP_L2)
return HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
else if (proto & OCELOT_PROTO_PTP_L4)
return HWTSTAMP_FILTER_PTP_V2_L4_EVENT;
return HWTSTAMP_FILTER_NONE;
}
static int ocelot_ptp_tx_type_to_cmd(int tx_type, int *ptp_cmd)
{
switch (tx_type) {
case HWTSTAMP_TX_ON:
*ptp_cmd = IFH_REW_OP_TWO_STEP_PTP;
break;
case HWTSTAMP_TX_ONESTEP_SYNC:
/* IFH_REW_OP_ONE_STEP_PTP updates the correctionField,
* what we need to update is the originTimestamp.
*/
*ptp_cmd = IFH_REW_OP_ORIGIN_PTP;
break;
case HWTSTAMP_TX_OFF:
*ptp_cmd = 0;
break;
default:
return -ERANGE;
}
return 0;
}
void ocelot_hwstamp_get(struct ocelot *ocelot, int port,
struct kernel_hwtstamp_config *cfg)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
switch (ocelot_port->ptp_cmd) {
case IFH_REW_OP_TWO_STEP_PTP:
cfg->tx_type = HWTSTAMP_TX_ON;
break;
case IFH_REW_OP_ORIGIN_PTP:
cfg->tx_type = HWTSTAMP_TX_ONESTEP_SYNC;
break;
default:
cfg->tx_type = HWTSTAMP_TX_OFF;
break;
}
cfg->rx_filter = ocelot_traps_to_ptp_rx_filter(ocelot_port->trap_proto);
}
EXPORT_SYMBOL(ocelot_hwstamp_get);
int ocelot_hwstamp_set(struct ocelot *ocelot, int port,
struct kernel_hwtstamp_config *cfg,
struct netlink_ext_ack *extack)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
bool l2 = false, l4 = false;
int ptp_cmd;
int err;
/* Tx type sanity check */
err = ocelot_ptp_tx_type_to_cmd(cfg->tx_type, &ptp_cmd);
if (err)
return err;
switch (cfg->rx_filter) {
case HWTSTAMP_FILTER_NONE:
break;
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
l4 = true;
break;
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
l2 = true;
break;
case HWTSTAMP_FILTER_PTP_V2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
l2 = true;
l4 = true;
break;
default:
return -ERANGE;
}
err = ocelot_setup_ptp_traps(ocelot, port, l2, l4);
if (err)
return err;
ocelot_port->ptp_cmd = ptp_cmd;
cfg->rx_filter = ocelot_traps_to_ptp_rx_filter(ocelot_port->trap_proto);
return 0;
}
EXPORT_SYMBOL(ocelot_hwstamp_set);
int ocelot_get_ts_info(struct ocelot *ocelot, int port,
struct kernel_ethtool_ts_info *info)
{
if (ocelot->ptp_clock) {
info->phc_index = ptp_clock_index(ocelot->ptp_clock);
} else {
info->so_timestamping |= SOF_TIMESTAMPING_TX_SOFTWARE;
return 0;
}
info->so_timestamping |= SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_TX_HARDWARE |
SOF_TIMESTAMPING_RX_HARDWARE |
SOF_TIMESTAMPING_RAW_HARDWARE;
info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON) |
BIT(HWTSTAMP_TX_ONESTEP_SYNC);
info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
BIT(HWTSTAMP_FILTER_PTP_V2_EVENT) |
BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT);
return 0;
}
EXPORT_SYMBOL(ocelot_get_ts_info);
static struct sk_buff *ocelot_port_dequeue_ptp_tx_skb(struct ocelot *ocelot,
int port, u8 ts_id,
u32 seqid)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
struct sk_buff *skb, *skb_tmp, *skb_match = NULL;
struct ptp_header *hdr;
spin_lock(&ocelot->ts_id_lock);
skb_queue_walk_safe(&ocelot_port->tx_skbs, skb, skb_tmp) {
if (OCELOT_SKB_CB(skb)->ts_id != ts_id)
continue;
/* Check that the timestamp ID is for the expected PTP
* sequenceId. We don't have to test ptp_parse_header() against
* NULL, because we've pre-validated the packet's ptp_class.
*/
hdr = ptp_parse_header(skb, OCELOT_SKB_CB(skb)->ptp_class);
if (seqid != ntohs(hdr->sequence_id))
continue;
__skb_unlink(skb, &ocelot_port->tx_skbs);
ocelot->ptp_skbs_in_flight--;
skb_match = skb;
break;
}
spin_unlock(&ocelot->ts_id_lock);
return skb_match;
}
static int ocelot_port_queue_ptp_tx_skb(struct ocelot *ocelot, int port,
struct sk_buff *clone)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
DECLARE_BITMAP(ts_id_in_flight, OCELOT_MAX_PTP_ID);
struct sk_buff *skb, *skb_tmp;
unsigned long n;
spin_lock(&ocelot->ts_id_lock);
/* To get a better chance of acquiring a timestamp ID, first flush the
* stale packets still waiting in the TX timestamping queue. They are
* probably lost.
*/
skb_queue_walk_safe(&ocelot_port->tx_skbs, skb, skb_tmp) {
if (time_before(OCELOT_SKB_CB(skb)->ptp_tx_time +
OCELOT_PTP_TX_TSTAMP_TIMEOUT, jiffies)) {
u64_stats_update_begin(&ocelot_port->ts_stats->syncp);
ocelot_port->ts_stats->lost++;
u64_stats_update_end(&ocelot_port->ts_stats->syncp);
dev_dbg_ratelimited(ocelot->dev,
"port %d invalidating stale timestamp ID %u which seems lost\n",
port, OCELOT_SKB_CB(skb)->ts_id);
__skb_unlink(skb, &ocelot_port->tx_skbs);
kfree_skb(skb);
ocelot->ptp_skbs_in_flight--;
} else {
__set_bit(OCELOT_SKB_CB(skb)->ts_id, ts_id_in_flight);
}
}
if (ocelot->ptp_skbs_in_flight == OCELOT_PTP_FIFO_SIZE) {
spin_unlock(&ocelot->ts_id_lock);
return -EBUSY;
}
n = find_first_zero_bit(ts_id_in_flight, OCELOT_MAX_PTP_ID);
if (n == OCELOT_MAX_PTP_ID) {
spin_unlock(&ocelot->ts_id_lock);
return -EBUSY;
}
/* Found an available timestamp ID, use it */
OCELOT_SKB_CB(clone)->ts_id = n;
OCELOT_SKB_CB(clone)->ptp_tx_time = jiffies;
ocelot->ptp_skbs_in_flight++;
__skb_queue_tail(&ocelot_port->tx_skbs, clone);
spin_unlock(&ocelot->ts_id_lock);
dev_dbg_ratelimited(ocelot->dev, "port %d timestamp id %lu\n", port, n);
return 0;
}
static bool ocelot_ptp_is_onestep_sync(struct sk_buff *skb,
unsigned int ptp_class)
{
struct ptp_header *hdr;
u8 msgtype, twostep;
hdr = ptp_parse_header(skb, ptp_class);
if (!hdr)
return false;
msgtype = ptp_get_msgtype(hdr, ptp_class);
twostep = hdr->flag_field[0] & 0x2;
if (msgtype == PTP_MSGTYPE_SYNC && twostep == 0)
return true;
return false;
}
int ocelot_port_txtstamp_request(struct ocelot *ocelot, int port,
struct sk_buff *skb,
struct sk_buff **clone)
{
struct ocelot_port *ocelot_port = ocelot->ports[port];
u8 ptp_cmd = ocelot_port->ptp_cmd;
unsigned int ptp_class;
int err;
/* Don't do anything if PTP timestamping not enabled */
if (!ptp_cmd)
return 0;
ptp_class = ptp_classify_raw(skb);
if (ptp_class == PTP_CLASS_NONE) {
err = -EINVAL;
goto error;
}
/* Store ptp_cmd in OCELOT_SKB_CB(skb)->ptp_cmd */
if (ptp_cmd == IFH_REW_OP_ORIGIN_PTP) {
if (ocelot_ptp_is_onestep_sync(skb, ptp_class)) {
OCELOT_SKB_CB(skb)->ptp_cmd = ptp_cmd;
u64_stats_update_begin(&ocelot_port->ts_stats->syncp);
ocelot_port->ts_stats->onestep_pkts_unconfirmed++;
u64_stats_update_end(&ocelot_port->ts_stats->syncp);
return 0;
}
/* Fall back to two-step timestamping */
ptp_cmd = IFH_REW_OP_TWO_STEP_PTP;
}
if (ptp_cmd == IFH_REW_OP_TWO_STEP_PTP) {
*clone = skb_clone_sk(skb);
if (!(*clone)) {
err = -ENOMEM;
goto error;
}
/* Store timestamp ID in OCELOT_SKB_CB(clone)->ts_id */
err = ocelot_port_queue_ptp_tx_skb(ocelot, port, *clone);
if (err) {
kfree_skb(*clone);
goto error;
}
skb_shinfo(*clone)->tx_flags |= SKBTX_IN_PROGRESS;
OCELOT_SKB_CB(skb)->ptp_cmd = ptp_cmd;
OCELOT_SKB_CB(*clone)->ptp_class = ptp_class;
}
return 0;
error:
u64_stats_update_begin(&ocelot_port->ts_stats->syncp);
ocelot_port->ts_stats->err++;
u64_stats_update_end(&ocelot_port->ts_stats->syncp);
return err;
}
EXPORT_SYMBOL(ocelot_port_txtstamp_request);
static void ocelot_get_hwtimestamp(struct ocelot *ocelot,
struct timespec64 *ts)
{
unsigned long flags;
u32 val;
spin_lock_irqsave(&ocelot->ptp_clock_lock, flags);
/* Read current PTP time to get seconds */
val = ocelot_read_rix(ocelot, PTP_PIN_CFG, TOD_ACC_PIN);
val &= ~(PTP_PIN_CFG_SYNC | PTP_PIN_CFG_ACTION_MASK | PTP_PIN_CFG_DOM);
val |= PTP_PIN_CFG_ACTION(PTP_PIN_ACTION_SAVE);
ocelot_write_rix(ocelot, val, PTP_PIN_CFG, TOD_ACC_PIN);
ts->tv_sec = ocelot_read_rix(ocelot, PTP_PIN_TOD_SEC_LSB, TOD_ACC_PIN);
/* Read packet HW timestamp from FIFO */
val = ocelot_read(ocelot, SYS_PTP_TXSTAMP);
ts->tv_nsec = SYS_PTP_TXSTAMP_PTP_TXSTAMP(val);
/* Sec has incremented since the ts was registered */
if ((ts->tv_sec & 0x1) != !!(val & SYS_PTP_TXSTAMP_PTP_TXSTAMP_SEC))
ts->tv_sec--;
spin_unlock_irqrestore(&ocelot->ptp_clock_lock, flags);
}
void ocelot_get_txtstamp(struct ocelot *ocelot)
{
int budget = OCELOT_PTP_QUEUE_SZ;
while (budget--) {
struct skb_shared_hwtstamps shhwtstamps;
struct ocelot_port *ocelot_port;
u32 val, id, seqid, txport;
struct sk_buff *skb_match;
struct timespec64 ts;
val = ocelot_read(ocelot, SYS_PTP_STATUS);
/* Check if a timestamp can be retrieved */
if (!(val & SYS_PTP_STATUS_PTP_MESS_VLD))
break;
WARN_ON(val & SYS_PTP_STATUS_PTP_OVFL);
/* Retrieve the ts ID and Tx port */
id = SYS_PTP_STATUS_PTP_MESS_ID_X(val);
txport = SYS_PTP_STATUS_PTP_MESS_TXPORT_X(val);
seqid = SYS_PTP_STATUS_PTP_MESS_SEQ_ID(val);
ocelot_port = ocelot->ports[txport];
/* Retrieve its associated skb */
skb_match = ocelot_port_dequeue_ptp_tx_skb(ocelot, txport, id,
seqid);
if (!skb_match) {
u64_stats_update_begin(&ocelot_port->ts_stats->syncp);
ocelot_port->ts_stats->err++;
u64_stats_update_end(&ocelot_port->ts_stats->syncp);
dev_dbg_ratelimited(ocelot->dev,
"port %d received TX timestamp (seqid %d, ts id %u) for packet previously declared stale\n",
txport, seqid, id);
goto next_ts;
}
u64_stats_update_begin(&ocelot_port->ts_stats->syncp);
ocelot_port->ts_stats->pkts++;
u64_stats_update_end(&ocelot_port->ts_stats->syncp);
/* Get the h/w timestamp */
ocelot_get_hwtimestamp(ocelot, &ts);
/* Set the timestamp into the skb */
memset(&shhwtstamps, 0, sizeof(shhwtstamps));
shhwtstamps.hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
skb_complete_tx_timestamp(skb_match, &shhwtstamps);
next_ts:
ocelot_write(ocelot, SYS_PTP_NXT_PTP_NXT, SYS_PTP_NXT);
}
}
EXPORT_SYMBOL(ocelot_get_txtstamp);
int ocelot_init_timestamp(struct ocelot *ocelot,
const struct ptp_clock_info *info)
{
struct ptp_clock *ptp_clock;
int i;
ocelot->ptp_info = *info;
for (i = 0; i < OCELOT_PTP_PINS_NUM; i++) {
struct ptp_pin_desc *p = &ocelot->ptp_pins[i];
snprintf(p->name, sizeof(p->name), "switch_1588_dat%d", i);
p->index = i;
p->func = PTP_PF_NONE;
}
ocelot->ptp_info.pin_config = &ocelot->ptp_pins[0];
ptp_clock = ptp_clock_register(&ocelot->ptp_info, ocelot->dev);
if (IS_ERR(ptp_clock))
return PTR_ERR(ptp_clock);
/* Check if PHC support is missing at the configuration level */
if (!ptp_clock)
return 0;
ocelot->ptp_clock = ptp_clock;
ocelot_write(ocelot, SYS_PTP_CFG_PTP_STAMP_WID(30), SYS_PTP_CFG);
ocelot_write(ocelot, 0xffffffff, ANA_TABLES_PTP_ID_LOW);
ocelot_write(ocelot, 0xffffffff, ANA_TABLES_PTP_ID_HIGH);
ocelot_write(ocelot, PTP_CFG_MISC_PTP_EN, PTP_CFG_MISC);
return 0;
}
EXPORT_SYMBOL(ocelot_init_timestamp);
int ocelot_deinit_timestamp(struct ocelot *ocelot)
{
if (ocelot->ptp_clock)
ptp_clock_unregister(ocelot->ptp_clock);
return 0;
}
EXPORT_SYMBOL(ocelot_deinit_timestamp);
@@ -0,0 +1,78 @@
/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
/*
* Microsemi Ocelot Switch driver
*
* Copyright (c) 2017 Microsemi Corporation
*/
#ifndef _MSCC_OCELOT_QS_H_
#define _MSCC_OCELOT_QS_H_
/* TODO handle BE */
#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 QS_XTR_GRP_CFG_RSZ 0x4
#define QS_XTR_GRP_CFG_MODE(x) (((x) << 2) & GENMASK(3, 2))
#define QS_XTR_GRP_CFG_MODE_M GENMASK(3, 2)
#define QS_XTR_GRP_CFG_MODE_X(x) (((x) & GENMASK(3, 2)) >> 2)
#define QS_XTR_GRP_CFG_STATUS_WORD_POS BIT(1)
#define QS_XTR_GRP_CFG_BYTE_SWAP BIT(0)
#define QS_XTR_RD_RSZ 0x4
#define QS_XTR_FRM_PRUNING_RSZ 0x4
#define QS_XTR_CFG_DP_WM(x) (((x) << 5) & GENMASK(7, 5))
#define QS_XTR_CFG_DP_WM_M GENMASK(7, 5)
#define QS_XTR_CFG_DP_WM_X(x) (((x) & GENMASK(7, 5)) >> 5)
#define QS_XTR_CFG_SCH_WM(x) (((x) << 2) & GENMASK(4, 2))
#define QS_XTR_CFG_SCH_WM_M GENMASK(4, 2)
#define QS_XTR_CFG_SCH_WM_X(x) (((x) & GENMASK(4, 2)) >> 2)
#define QS_XTR_CFG_OFLW_ERR_STICKY(x) ((x) & GENMASK(1, 0))
#define QS_XTR_CFG_OFLW_ERR_STICKY_M GENMASK(1, 0)
#define QS_INJ_GRP_CFG_RSZ 0x4
#define QS_INJ_GRP_CFG_MODE(x) (((x) << 2) & GENMASK(3, 2))
#define QS_INJ_GRP_CFG_MODE_M GENMASK(3, 2)
#define QS_INJ_GRP_CFG_MODE_X(x) (((x) & GENMASK(3, 2)) >> 2)
#define QS_INJ_GRP_CFG_BYTE_SWAP BIT(0)
#define QS_INJ_WR_RSZ 0x4
#define QS_INJ_CTRL_RSZ 0x4
#define QS_INJ_CTRL_GAP_SIZE(x) (((x) << 21) & GENMASK(24, 21))
#define QS_INJ_CTRL_GAP_SIZE_M GENMASK(24, 21)
#define QS_INJ_CTRL_GAP_SIZE_X(x) (((x) & GENMASK(24, 21)) >> 21)
#define QS_INJ_CTRL_ABORT BIT(20)
#define QS_INJ_CTRL_EOF BIT(19)
#define QS_INJ_CTRL_SOF BIT(18)
#define QS_INJ_CTRL_VLD_BYTES(x) (((x) << 16) & GENMASK(17, 16))
#define QS_INJ_CTRL_VLD_BYTES_M GENMASK(17, 16)
#define QS_INJ_CTRL_VLD_BYTES_X(x) (((x) & GENMASK(17, 16)) >> 16)
#define QS_INJ_STATUS_WMARK_REACHED(x) (((x) << 4) & GENMASK(5, 4))
#define QS_INJ_STATUS_WMARK_REACHED_M GENMASK(5, 4)
#define QS_INJ_STATUS_WMARK_REACHED_X(x) (((x) & GENMASK(5, 4)) >> 4)
#define QS_INJ_STATUS_FIFO_RDY(x) (((x) << 2) & GENMASK(3, 2))
#define QS_INJ_STATUS_FIFO_RDY_M GENMASK(3, 2)
#define QS_INJ_STATUS_FIFO_RDY_X(x) (((x) & GENMASK(3, 2)) >> 2)
#define QS_INJ_STATUS_INJ_IN_PROGRESS(x) ((x) & GENMASK(1, 0))
#define QS_INJ_STATUS_INJ_IN_PROGRESS_M GENMASK(1, 0)
#define QS_INJ_ERR_RSZ 0x4
#define QS_INJ_ERR_ABORT_ERR_STICKY BIT(1)
#define QS_INJ_ERR_WR_ERR_STICKY BIT(0)
#endif
@@ -0,0 +1,81 @@
/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
/*
* Microsemi Ocelot Switch driver
*
* Copyright (c) 2017 Microsemi Corporation
*/
#ifndef _MSCC_OCELOT_REW_H_
#define _MSCC_OCELOT_REW_H_
#define REW_PORT_VLAN_CFG_GSZ 0x80
#define REW_PORT_VLAN_CFG_PORT_TPID(x) (((x) << 16) & GENMASK(31, 16))
#define REW_PORT_VLAN_CFG_PORT_TPID_M GENMASK(31, 16)
#define REW_PORT_VLAN_CFG_PORT_TPID_X(x) (((x) & GENMASK(31, 16)) >> 16)
#define REW_PORT_VLAN_CFG_PORT_DEI BIT(15)
#define REW_PORT_VLAN_CFG_PORT_PCP(x) (((x) << 12) & GENMASK(14, 12))
#define REW_PORT_VLAN_CFG_PORT_PCP_M GENMASK(14, 12)
#define REW_PORT_VLAN_CFG_PORT_PCP_X(x) (((x) & GENMASK(14, 12)) >> 12)
#define REW_PORT_VLAN_CFG_PORT_VID(x) ((x) & GENMASK(11, 0))
#define REW_PORT_VLAN_CFG_PORT_VID_M GENMASK(11, 0)
#define REW_TAG_CFG_GSZ 0x80
#define REW_TAG_CFG_TAG_CFG(x) (((x) << 7) & GENMASK(8, 7))
#define REW_TAG_CFG_TAG_CFG_M GENMASK(8, 7)
#define REW_TAG_CFG_TAG_CFG_X(x) (((x) & GENMASK(8, 7)) >> 7)
#define REW_TAG_CFG_TAG_TPID_CFG(x) (((x) << 5) & GENMASK(6, 5))
#define REW_TAG_CFG_TAG_TPID_CFG_M GENMASK(6, 5)
#define REW_TAG_CFG_TAG_TPID_CFG_X(x) (((x) & GENMASK(6, 5)) >> 5)
#define REW_TAG_CFG_TAG_VID_CFG BIT(4)
#define REW_TAG_CFG_TAG_PCP_CFG(x) (((x) << 2) & GENMASK(3, 2))
#define REW_TAG_CFG_TAG_PCP_CFG_M GENMASK(3, 2)
#define REW_TAG_CFG_TAG_PCP_CFG_X(x) (((x) & GENMASK(3, 2)) >> 2)
#define REW_TAG_CFG_TAG_DEI_CFG(x) ((x) & GENMASK(1, 0))
#define REW_TAG_CFG_TAG_DEI_CFG_M GENMASK(1, 0)
#define REW_PORT_CFG_GSZ 0x80
#define REW_PORT_CFG_ES0_EN BIT(5)
#define REW_PORT_CFG_FCS_UPDATE_NONCPU_CFG(x) (((x) << 3) & GENMASK(4, 3))
#define REW_PORT_CFG_FCS_UPDATE_NONCPU_CFG_M GENMASK(4, 3)
#define REW_PORT_CFG_FCS_UPDATE_NONCPU_CFG_X(x) (((x) & GENMASK(4, 3)) >> 3)
#define REW_PORT_CFG_FCS_UPDATE_CPU_ENA BIT(2)
#define REW_PORT_CFG_FLUSH_ENA BIT(1)
#define REW_PORT_CFG_AGE_DIS BIT(0)
#define REW_DSCP_CFG_GSZ 0x80
#define REW_PCP_DEI_QOS_MAP_CFG_GSZ 0x80
#define REW_PCP_DEI_QOS_MAP_CFG_RSZ 0x4
#define REW_PCP_DEI_QOS_MAP_CFG_DEI_QOS_VAL BIT(3)
#define REW_PCP_DEI_QOS_MAP_CFG_PCP_QOS_VAL(x) ((x) & GENMASK(2, 0))
#define REW_PCP_DEI_QOS_MAP_CFG_PCP_QOS_VAL_M GENMASK(2, 0)
#define REW_PTP_CFG_GSZ 0x80
#define REW_PTP_CFG_PTP_BACKPLANE_MODE BIT(7)
#define REW_PTP_CFG_GP_CFG_UNUSED(x) (((x) << 3) & GENMASK(6, 3))
#define REW_PTP_CFG_GP_CFG_UNUSED_M GENMASK(6, 3)
#define REW_PTP_CFG_GP_CFG_UNUSED_X(x) (((x) & GENMASK(6, 3)) >> 3)
#define REW_PTP_CFG_PTP_1STEP_DIS BIT(2)
#define REW_PTP_CFG_PTP_2STEP_DIS BIT(1)
#define REW_PTP_CFG_PTP_UDP_KEEP BIT(0)
#define REW_PTP_DLY1_CFG_GSZ 0x80
#define REW_RED_TAG_CFG_GSZ 0x80
#define REW_RED_TAG_CFG_RED_TAG_CFG BIT(0)
#define REW_DSCP_REMAP_DP1_CFG_RSZ 0x4
#define REW_DSCP_REMAP_CFG_RSZ 0x4
#define REW_REW_STICKY_ES0_TAGB_PUSH_FAILED BIT(0)
#define REW_PPT_RSZ 0x4
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
/* Microsemi Ocelot Switch driver
* Copyright (c) 2019 Microsemi Corporation
*/
#ifndef _MSCC_OCELOT_VCAP_H_
#define _MSCC_OCELOT_VCAP_H_
#include "ocelot.h"
#include <soc/mscc/ocelot_vcap.h>
#include <net/flow_offload.h>
#define OCELOT_POLICER_DISCARD 0x17f
int ocelot_vcap_filter_stats_update(struct ocelot *ocelot,
struct ocelot_vcap_filter *rule);
int ocelot_vcap_init(struct ocelot *ocelot);
int ocelot_setup_tc_cls_flower(struct ocelot_port_private *priv,
struct flow_cls_offload *f,
bool ingress);
#endif /* _MSCC_OCELOT_VCAP_H_ */
@@ -0,0 +1,432 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
* Microsemi Ocelot Switch driver
*
* Copyright (c) 2017 Microsemi Corporation
*/
#include <linux/dsa/ocelot.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of_net.h>
#include <linux/netdevice.h>
#include <linux/phylink.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/platform_device.h>
#include <linux/mfd/syscon.h>
#include <linux/skbuff.h>
#include <net/switchdev.h>
#include <soc/mscc/ocelot.h>
#include <soc/mscc/ocelot_vcap.h>
#include <soc/mscc/vsc7514_regs.h>
#include "ocelot_fdma.h"
#include "ocelot.h"
#define VSC7514_VCAP_POLICER_BASE 128
#define VSC7514_VCAP_POLICER_MAX 191
static int ocelot_chip_init(struct ocelot *ocelot, const struct ocelot_ops *ops)
{
int ret;
ocelot->map = vsc7514_regmap;
ocelot->num_mact_rows = 1024;
ocelot->ops = ops;
ret = ocelot_regfields_init(ocelot, vsc7514_regfields);
if (ret)
return ret;
ocelot_pll5_init(ocelot);
eth_random_addr(ocelot->base_mac);
ocelot->base_mac[5] &= 0xf0;
return 0;
}
static irqreturn_t ocelot_xtr_irq_handler(int irq, void *arg)
{
struct ocelot *ocelot = arg;
int grp = 0, err;
ocelot_lock_xtr_grp(ocelot, grp);
while (ocelot_read(ocelot, QS_XTR_DATA_PRESENT) & BIT(grp)) {
struct sk_buff *skb;
err = ocelot_xtr_poll_frame(ocelot, grp, &skb);
if (err)
goto out;
skb->dev->stats.rx_bytes += skb->len;
skb->dev->stats.rx_packets++;
if (!skb_defer_rx_timestamp(skb))
netif_rx(skb);
}
out:
if (err < 0)
ocelot_drain_cpu_queue(ocelot, 0);
ocelot_unlock_xtr_grp(ocelot, grp);
return IRQ_HANDLED;
}
static irqreturn_t ocelot_ptp_rdy_irq_handler(int irq, void *arg)
{
struct ocelot *ocelot = arg;
ocelot_get_txtstamp(ocelot);
return IRQ_HANDLED;
}
static const struct of_device_id mscc_ocelot_match[] = {
{ .compatible = "mscc,vsc7514-switch" },
{ }
};
MODULE_DEVICE_TABLE(of, mscc_ocelot_match);
static const struct ocelot_ops ocelot_ops = {
.reset = ocelot_reset,
.wm_enc = ocelot_wm_enc,
.wm_dec = ocelot_wm_dec,
.wm_stat = ocelot_wm_stat,
.port_to_netdev = ocelot_port_to_netdev,
.netdev_to_port = ocelot_netdev_to_port,
};
static struct ptp_clock_info ocelot_ptp_clock_info = {
.owner = THIS_MODULE,
.name = "ocelot ptp",
.max_adj = 0x7fffffff,
.n_alarm = 0,
.n_ext_ts = 0,
.n_per_out = OCELOT_PTP_PINS_NUM,
.n_pins = OCELOT_PTP_PINS_NUM,
.supported_perout_flags = PTP_PEROUT_DUTY_CYCLE |
PTP_PEROUT_PHASE,
.pps = 0,
.gettime64 = ocelot_ptp_gettime64,
.settime64 = ocelot_ptp_settime64,
.adjtime = ocelot_ptp_adjtime,
.adjfine = ocelot_ptp_adjfine,
.verify = ocelot_ptp_verify,
.enable = ocelot_ptp_enable,
};
static void mscc_ocelot_teardown_devlink_ports(struct ocelot *ocelot)
{
int port;
for (port = 0; port < ocelot->num_phys_ports; port++)
ocelot_port_devlink_teardown(ocelot, port);
}
static void mscc_ocelot_release_ports(struct ocelot *ocelot)
{
int port;
for (port = 0; port < ocelot->num_phys_ports; port++) {
struct ocelot_port *ocelot_port;
ocelot_port = ocelot->ports[port];
if (!ocelot_port)
continue;
ocelot_deinit_port(ocelot, port);
ocelot_release_port(ocelot_port);
}
}
static int mscc_ocelot_init_ports(struct platform_device *pdev,
struct device_node *ports)
{
struct ocelot *ocelot = platform_get_drvdata(pdev);
u32 devlink_ports_registered = 0;
struct device_node *portnp;
int port, err;
u32 reg;
ocelot->ports = devm_kcalloc(ocelot->dev, ocelot->num_phys_ports,
sizeof(struct ocelot_port *), GFP_KERNEL);
if (!ocelot->ports)
return -ENOMEM;
ocelot->devlink_ports = devm_kcalloc(ocelot->dev,
ocelot->num_phys_ports,
sizeof(*ocelot->devlink_ports),
GFP_KERNEL);
if (!ocelot->devlink_ports)
return -ENOMEM;
for_each_available_child_of_node(ports, portnp) {
struct regmap *target;
struct resource *res;
char res_name[8];
if (of_property_read_u32(portnp, "reg", &reg))
continue;
port = reg;
if (port < 0 || port >= ocelot->num_phys_ports) {
dev_err(ocelot->dev,
"invalid port number: %d >= %d\n", port,
ocelot->num_phys_ports);
continue;
}
snprintf(res_name, sizeof(res_name), "port%d", port);
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
res_name);
target = ocelot_regmap_init(ocelot, res);
if (IS_ERR(target)) {
err = PTR_ERR(target);
of_node_put(portnp);
goto out_teardown;
}
err = ocelot_port_devlink_init(ocelot, port,
DEVLINK_PORT_FLAVOUR_PHYSICAL);
if (err) {
of_node_put(portnp);
goto out_teardown;
}
err = ocelot_probe_port(ocelot, port, target, portnp);
if (err) {
ocelot_port_devlink_teardown(ocelot, port);
continue;
}
devlink_ports_registered |= BIT(port);
}
/* Initialize unused devlink ports at the end */
for (port = 0; port < ocelot->num_phys_ports; port++) {
if (devlink_ports_registered & BIT(port))
continue;
err = ocelot_port_devlink_init(ocelot, port,
DEVLINK_PORT_FLAVOUR_UNUSED);
if (err)
goto out_teardown;
devlink_ports_registered |= BIT(port);
}
return 0;
out_teardown:
/* Unregister the network interfaces */
mscc_ocelot_release_ports(ocelot);
/* Tear down devlink ports for the registered network interfaces */
for (port = 0; port < ocelot->num_phys_ports; port++) {
if (devlink_ports_registered & BIT(port))
ocelot_port_devlink_teardown(ocelot, port);
}
return err;
}
static int mscc_ocelot_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int err, irq_xtr, irq_ptp_rdy;
struct device_node *ports;
struct devlink *devlink;
struct ocelot *ocelot;
struct regmap *hsio;
unsigned int i;
struct {
enum ocelot_target id;
char *name;
u8 optional:1;
} io_target[] = {
{ SYS, "sys" },
{ REW, "rew" },
{ QSYS, "qsys" },
{ ANA, "ana" },
{ QS, "qs" },
{ S0, "s0" },
{ S1, "s1" },
{ S2, "s2" },
{ PTP, "ptp", 1 },
{ FDMA, "fdma", 1 },
};
if (!np && !pdev->dev.platform_data)
return -ENODEV;
devlink =
devlink_alloc(&ocelot_devlink_ops, sizeof(*ocelot), &pdev->dev);
if (!devlink)
return -ENOMEM;
ocelot = devlink_priv(devlink);
ocelot->devlink = priv_to_devlink(ocelot);
platform_set_drvdata(pdev, ocelot);
ocelot->dev = &pdev->dev;
for (i = 0; i < ARRAY_SIZE(io_target); i++) {
struct regmap *target;
struct resource *res;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
io_target[i].name);
target = ocelot_regmap_init(ocelot, res);
if (IS_ERR(target)) {
if (io_target[i].optional) {
ocelot->targets[io_target[i].id] = NULL;
continue;
}
err = PTR_ERR(target);
goto out_free_devlink;
}
ocelot->targets[io_target[i].id] = target;
}
if (ocelot->targets[FDMA])
ocelot_fdma_init(pdev, ocelot);
hsio = syscon_regmap_lookup_by_compatible("mscc,ocelot-hsio");
if (IS_ERR(hsio)) {
dev_err(&pdev->dev, "missing hsio syscon\n");
err = PTR_ERR(hsio);
goto out_free_devlink;
}
ocelot->targets[HSIO] = hsio;
err = ocelot_chip_init(ocelot, &ocelot_ops);
if (err)
goto out_free_devlink;
irq_xtr = platform_get_irq_byname(pdev, "xtr");
if (irq_xtr < 0) {
err = irq_xtr;
goto out_free_devlink;
}
err = devm_request_threaded_irq(&pdev->dev, irq_xtr, NULL,
ocelot_xtr_irq_handler, IRQF_ONESHOT,
"frame extraction", ocelot);
if (err)
goto out_free_devlink;
irq_ptp_rdy = platform_get_irq_byname(pdev, "ptp_rdy");
if (irq_ptp_rdy > 0 && ocelot->targets[PTP]) {
err = devm_request_threaded_irq(&pdev->dev, irq_ptp_rdy, NULL,
ocelot_ptp_rdy_irq_handler,
IRQF_ONESHOT, "ptp ready",
ocelot);
if (err)
goto out_free_devlink;
/* Both the PTP interrupt and the PTP bank are available */
ocelot->ptp = 1;
}
ports = of_get_child_by_name(np, "ethernet-ports");
if (!ports) {
dev_err(ocelot->dev, "no ethernet-ports child node found\n");
err = -ENODEV;
goto out_free_devlink;
}
ocelot->num_phys_ports = of_get_child_count(ports);
ocelot->num_flooding_pgids = 1;
ocelot->vcap = vsc7514_vcap_props;
ocelot->vcap_pol.base = VSC7514_VCAP_POLICER_BASE;
ocelot->vcap_pol.max = VSC7514_VCAP_POLICER_MAX;
ocelot->npi = -1;
err = ocelot_init(ocelot);
if (err)
goto out_put_ports;
err = mscc_ocelot_init_ports(pdev, ports);
if (err)
goto out_ocelot_devlink_unregister;
if (ocelot->fdma)
ocelot_fdma_start(ocelot);
err = ocelot_devlink_sb_register(ocelot);
if (err)
goto out_ocelot_release_ports;
if (ocelot->ptp) {
err = ocelot_init_timestamp(ocelot, &ocelot_ptp_clock_info);
if (err) {
dev_err(ocelot->dev,
"Timestamp initialization failed\n");
ocelot->ptp = 0;
}
}
register_netdevice_notifier(&ocelot_netdevice_nb);
register_switchdev_notifier(&ocelot_switchdev_nb);
register_switchdev_blocking_notifier(&ocelot_switchdev_blocking_nb);
of_node_put(ports);
devlink_register(devlink);
dev_info(&pdev->dev, "Ocelot switch probed\n");
return 0;
out_ocelot_release_ports:
mscc_ocelot_release_ports(ocelot);
mscc_ocelot_teardown_devlink_ports(ocelot);
out_ocelot_devlink_unregister:
ocelot_deinit(ocelot);
out_put_ports:
of_node_put(ports);
out_free_devlink:
devlink_free(devlink);
return err;
}
static void mscc_ocelot_remove(struct platform_device *pdev)
{
struct ocelot *ocelot = platform_get_drvdata(pdev);
if (ocelot->fdma)
ocelot_fdma_deinit(ocelot);
devlink_unregister(ocelot->devlink);
ocelot_deinit_timestamp(ocelot);
ocelot_devlink_sb_unregister(ocelot);
mscc_ocelot_release_ports(ocelot);
mscc_ocelot_teardown_devlink_ports(ocelot);
ocelot_deinit(ocelot);
unregister_switchdev_blocking_notifier(&ocelot_switchdev_blocking_nb);
unregister_switchdev_notifier(&ocelot_switchdev_nb);
unregister_netdevice_notifier(&ocelot_netdevice_nb);
devlink_free(ocelot->devlink);
}
static struct platform_driver mscc_ocelot_driver = {
.probe = mscc_ocelot_probe,
.remove = mscc_ocelot_remove,
.driver = {
.name = "ocelot-switch",
.of_match_table = mscc_ocelot_match,
},
};
module_platform_driver(mscc_ocelot_driver);
MODULE_DESCRIPTION("Microsemi Ocelot switch driver");
MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@bootlin.com>");
MODULE_LICENSE("Dual MIT/GPL");
@@ -0,0 +1,685 @@
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
* Microsemi Ocelot Switch driver
*
* Copyright (c) 2017 Microsemi Corporation
* Copyright (c) 2021 Innovative Advantage
*/
#include <soc/mscc/ocelot_vcap.h>
#include <soc/mscc/vsc7514_regs.h>
#include "ocelot.h"
const struct reg_field vsc7514_regfields[REGFIELD_MAX] = {
[ANA_ADVLEARN_VLAN_CHK] = REG_FIELD(ANA_ADVLEARN, 11, 11),
[ANA_ADVLEARN_LEARN_MIRROR] = REG_FIELD(ANA_ADVLEARN, 0, 10),
[ANA_ANEVENTS_MSTI_DROP] = REG_FIELD(ANA_ANEVENTS, 27, 27),
[ANA_ANEVENTS_ACLKILL] = REG_FIELD(ANA_ANEVENTS, 26, 26),
[ANA_ANEVENTS_ACLUSED] = REG_FIELD(ANA_ANEVENTS, 25, 25),
[ANA_ANEVENTS_AUTOAGE] = REG_FIELD(ANA_ANEVENTS, 24, 24),
[ANA_ANEVENTS_VS2TTL1] = REG_FIELD(ANA_ANEVENTS, 23, 23),
[ANA_ANEVENTS_STORM_DROP] = REG_FIELD(ANA_ANEVENTS, 22, 22),
[ANA_ANEVENTS_LEARN_DROP] = REG_FIELD(ANA_ANEVENTS, 21, 21),
[ANA_ANEVENTS_AGED_ENTRY] = REG_FIELD(ANA_ANEVENTS, 20, 20),
[ANA_ANEVENTS_CPU_LEARN_FAILED] = REG_FIELD(ANA_ANEVENTS, 19, 19),
[ANA_ANEVENTS_AUTO_LEARN_FAILED] = REG_FIELD(ANA_ANEVENTS, 18, 18),
[ANA_ANEVENTS_LEARN_REMOVE] = REG_FIELD(ANA_ANEVENTS, 17, 17),
[ANA_ANEVENTS_AUTO_LEARNED] = REG_FIELD(ANA_ANEVENTS, 16, 16),
[ANA_ANEVENTS_AUTO_MOVED] = REG_FIELD(ANA_ANEVENTS, 15, 15),
[ANA_ANEVENTS_DROPPED] = REG_FIELD(ANA_ANEVENTS, 14, 14),
[ANA_ANEVENTS_CLASSIFIED_DROP] = REG_FIELD(ANA_ANEVENTS, 13, 13),
[ANA_ANEVENTS_CLASSIFIED_COPY] = REG_FIELD(ANA_ANEVENTS, 12, 12),
[ANA_ANEVENTS_VLAN_DISCARD] = REG_FIELD(ANA_ANEVENTS, 11, 11),
[ANA_ANEVENTS_FWD_DISCARD] = REG_FIELD(ANA_ANEVENTS, 10, 10),
[ANA_ANEVENTS_MULTICAST_FLOOD] = REG_FIELD(ANA_ANEVENTS, 9, 9),
[ANA_ANEVENTS_UNICAST_FLOOD] = REG_FIELD(ANA_ANEVENTS, 8, 8),
[ANA_ANEVENTS_DEST_KNOWN] = REG_FIELD(ANA_ANEVENTS, 7, 7),
[ANA_ANEVENTS_BUCKET3_MATCH] = REG_FIELD(ANA_ANEVENTS, 6, 6),
[ANA_ANEVENTS_BUCKET2_MATCH] = REG_FIELD(ANA_ANEVENTS, 5, 5),
[ANA_ANEVENTS_BUCKET1_MATCH] = REG_FIELD(ANA_ANEVENTS, 4, 4),
[ANA_ANEVENTS_BUCKET0_MATCH] = REG_FIELD(ANA_ANEVENTS, 3, 3),
[ANA_ANEVENTS_CPU_OPERATION] = REG_FIELD(ANA_ANEVENTS, 2, 2),
[ANA_ANEVENTS_DMAC_LOOKUP] = REG_FIELD(ANA_ANEVENTS, 1, 1),
[ANA_ANEVENTS_SMAC_LOOKUP] = REG_FIELD(ANA_ANEVENTS, 0, 0),
[ANA_TABLES_MACACCESS_B_DOM] = REG_FIELD(ANA_TABLES_MACACCESS, 18, 18),
[ANA_TABLES_MACTINDX_BUCKET] = REG_FIELD(ANA_TABLES_MACTINDX, 10, 11),
[ANA_TABLES_MACTINDX_M_INDEX] = REG_FIELD(ANA_TABLES_MACTINDX, 0, 9),
[QSYS_TIMED_FRAME_ENTRY_TFRM_VLD] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 20, 20),
[QSYS_TIMED_FRAME_ENTRY_TFRM_FP] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 8, 19),
[QSYS_TIMED_FRAME_ENTRY_TFRM_PORTNO] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 4, 7),
[QSYS_TIMED_FRAME_ENTRY_TFRM_TM_SEL] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 1, 3),
[QSYS_TIMED_FRAME_ENTRY_TFRM_TM_T] = REG_FIELD(QSYS_TIMED_FRAME_ENTRY, 0, 0),
[SYS_RESET_CFG_CORE_ENA] = REG_FIELD(SYS_RESET_CFG, 2, 2),
[SYS_RESET_CFG_MEM_ENA] = REG_FIELD(SYS_RESET_CFG, 1, 1),
[SYS_RESET_CFG_MEM_INIT] = REG_FIELD(SYS_RESET_CFG, 0, 0),
/* Replicated per number of ports (12), register size 4 per port */
[QSYS_SWITCH_PORT_MODE_PORT_ENA] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 14, 14, 12, 4),
[QSYS_SWITCH_PORT_MODE_SCH_NEXT_CFG] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 11, 13, 12, 4),
[QSYS_SWITCH_PORT_MODE_YEL_RSRVD] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 10, 10, 12, 4),
[QSYS_SWITCH_PORT_MODE_INGRESS_DROP_MODE] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 9, 9, 12, 4),
[QSYS_SWITCH_PORT_MODE_TX_PFC_ENA] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 1, 8, 12, 4),
[QSYS_SWITCH_PORT_MODE_TX_PFC_MODE] = REG_FIELD_ID(QSYS_SWITCH_PORT_MODE, 0, 0, 12, 4),
[SYS_PORT_MODE_DATA_WO_TS] = REG_FIELD_ID(SYS_PORT_MODE, 5, 6, 12, 4),
[SYS_PORT_MODE_INCL_INJ_HDR] = REG_FIELD_ID(SYS_PORT_MODE, 3, 4, 12, 4),
[SYS_PORT_MODE_INCL_XTR_HDR] = REG_FIELD_ID(SYS_PORT_MODE, 1, 2, 12, 4),
[SYS_PORT_MODE_INCL_HDR_ERR] = REG_FIELD_ID(SYS_PORT_MODE, 0, 0, 12, 4),
[SYS_PAUSE_CFG_PAUSE_START] = REG_FIELD_ID(SYS_PAUSE_CFG, 10, 18, 12, 4),
[SYS_PAUSE_CFG_PAUSE_STOP] = REG_FIELD_ID(SYS_PAUSE_CFG, 1, 9, 12, 4),
[SYS_PAUSE_CFG_PAUSE_ENA] = REG_FIELD_ID(SYS_PAUSE_CFG, 0, 1, 12, 4),
};
EXPORT_SYMBOL(vsc7514_regfields);
static const u32 vsc7514_ana_regmap[] = {
REG(ANA_ADVLEARN, 0x009000),
REG(ANA_VLANMASK, 0x009004),
REG(ANA_PORT_B_DOMAIN, 0x009008),
REG(ANA_ANAGEFIL, 0x00900c),
REG(ANA_ANEVENTS, 0x009010),
REG(ANA_STORMLIMIT_BURST, 0x009014),
REG(ANA_STORMLIMIT_CFG, 0x009018),
REG(ANA_ISOLATED_PORTS, 0x009028),
REG(ANA_COMMUNITY_PORTS, 0x00902c),
REG(ANA_AUTOAGE, 0x009030),
REG(ANA_MACTOPTIONS, 0x009034),
REG(ANA_LEARNDISC, 0x009038),
REG(ANA_AGENCTRL, 0x00903c),
REG(ANA_MIRRORPORTS, 0x009040),
REG(ANA_EMIRRORPORTS, 0x009044),
REG(ANA_FLOODING, 0x009048),
REG(ANA_FLOODING_IPMC, 0x00904c),
REG(ANA_SFLOW_CFG, 0x009050),
REG(ANA_PORT_MODE, 0x009080),
REG(ANA_PGID_PGID, 0x008c00),
REG(ANA_TABLES_ANMOVED, 0x008b30),
REG(ANA_TABLES_MACHDATA, 0x008b34),
REG(ANA_TABLES_MACLDATA, 0x008b38),
REG(ANA_TABLES_MACACCESS, 0x008b3c),
REG(ANA_TABLES_MACTINDX, 0x008b40),
REG(ANA_TABLES_VLANACCESS, 0x008b44),
REG(ANA_TABLES_VLANTIDX, 0x008b48),
REG(ANA_TABLES_ISDXACCESS, 0x008b4c),
REG(ANA_TABLES_ISDXTIDX, 0x008b50),
REG(ANA_TABLES_ENTRYLIM, 0x008b00),
REG(ANA_TABLES_PTP_ID_HIGH, 0x008b54),
REG(ANA_TABLES_PTP_ID_LOW, 0x008b58),
REG(ANA_MSTI_STATE, 0x008e00),
REG(ANA_PORT_VLAN_CFG, 0x007000),
REG(ANA_PORT_DROP_CFG, 0x007004),
REG(ANA_PORT_QOS_CFG, 0x007008),
REG(ANA_PORT_VCAP_CFG, 0x00700c),
REG(ANA_PORT_VCAP_S1_KEY_CFG, 0x007010),
REG(ANA_PORT_VCAP_S2_CFG, 0x00701c),
REG(ANA_PORT_PCP_DEI_MAP, 0x007020),
REG(ANA_PORT_CPU_FWD_CFG, 0x007060),
REG(ANA_PORT_CPU_FWD_BPDU_CFG, 0x007064),
REG(ANA_PORT_CPU_FWD_GARP_CFG, 0x007068),
REG(ANA_PORT_CPU_FWD_CCM_CFG, 0x00706c),
REG(ANA_PORT_PORT_CFG, 0x007070),
REG(ANA_PORT_POL_CFG, 0x007074),
REG(ANA_PORT_PTP_CFG, 0x007078),
REG(ANA_PORT_PTP_DLY1_CFG, 0x00707c),
REG(ANA_OAM_UPM_LM_CNT, 0x007c00),
REG(ANA_PORT_PTP_DLY2_CFG, 0x007080),
REG(ANA_PFC_PFC_CFG, 0x008800),
REG(ANA_PFC_PFC_TIMER, 0x008804),
REG(ANA_IPT_OAM_MEP_CFG, 0x008000),
REG(ANA_IPT_IPT, 0x008004),
REG(ANA_PPT_PPT, 0x008ac0),
REG(ANA_FID_MAP_FID_MAP, 0x000000),
REG(ANA_AGGR_CFG, 0x0090b4),
REG(ANA_CPUQ_CFG, 0x0090b8),
REG(ANA_CPUQ_CFG2, 0x0090bc),
REG(ANA_CPUQ_8021_CFG, 0x0090c0),
REG(ANA_DSCP_CFG, 0x009100),
REG(ANA_DSCP_REWR_CFG, 0x009200),
REG(ANA_VCAP_RNG_TYPE_CFG, 0x009240),
REG(ANA_VCAP_RNG_VAL_CFG, 0x009260),
REG(ANA_VRAP_CFG, 0x009280),
REG(ANA_VRAP_HDR_DATA, 0x009284),
REG(ANA_VRAP_HDR_MASK, 0x009288),
REG(ANA_DISCARD_CFG, 0x00928c),
REG(ANA_FID_CFG, 0x009290),
REG(ANA_POL_PIR_CFG, 0x004000),
REG(ANA_POL_CIR_CFG, 0x004004),
REG(ANA_POL_MODE_CFG, 0x004008),
REG(ANA_POL_PIR_STATE, 0x00400c),
REG(ANA_POL_CIR_STATE, 0x004010),
REG(ANA_POL_STATE, 0x004014),
REG(ANA_POL_FLOWC, 0x008b80),
REG(ANA_POL_HYST, 0x008bec),
REG(ANA_POL_MISC_CFG, 0x008bf0),
};
static const u32 vsc7514_qs_regmap[] = {
REG(QS_XTR_GRP_CFG, 0x000000),
REG(QS_XTR_RD, 0x000008),
REG(QS_XTR_FRM_PRUNING, 0x000010),
REG(QS_XTR_FLUSH, 0x000018),
REG(QS_XTR_DATA_PRESENT, 0x00001c),
REG(QS_XTR_CFG, 0x000020),
REG(QS_INJ_GRP_CFG, 0x000024),
REG(QS_INJ_WR, 0x00002c),
REG(QS_INJ_CTRL, 0x000034),
REG(QS_INJ_STATUS, 0x00003c),
REG(QS_INJ_ERR, 0x000040),
REG(QS_INH_DBG, 0x000048),
};
static const u32 vsc7514_qsys_regmap[] = {
REG(QSYS_PORT_MODE, 0x011200),
REG(QSYS_SWITCH_PORT_MODE, 0x011234),
REG(QSYS_STAT_CNT_CFG, 0x011264),
REG(QSYS_EEE_CFG, 0x011268),
REG(QSYS_EEE_THRES, 0x011294),
REG(QSYS_IGR_NO_SHARING, 0x011298),
REG(QSYS_EGR_NO_SHARING, 0x01129c),
REG(QSYS_SW_STATUS, 0x0112a0),
REG(QSYS_EXT_CPU_CFG, 0x0112d0),
REG(QSYS_PAD_CFG, 0x0112d4),
REG(QSYS_CPU_GROUP_MAP, 0x0112d8),
REG(QSYS_QMAP, 0x0112dc),
REG(QSYS_ISDX_SGRP, 0x011400),
REG(QSYS_TIMED_FRAME_ENTRY, 0x014000),
REG(QSYS_TFRM_MISC, 0x011310),
REG(QSYS_TFRM_PORT_DLY, 0x011314),
REG(QSYS_TFRM_TIMER_CFG_1, 0x011318),
REG(QSYS_TFRM_TIMER_CFG_2, 0x01131c),
REG(QSYS_TFRM_TIMER_CFG_3, 0x011320),
REG(QSYS_TFRM_TIMER_CFG_4, 0x011324),
REG(QSYS_TFRM_TIMER_CFG_5, 0x011328),
REG(QSYS_TFRM_TIMER_CFG_6, 0x01132c),
REG(QSYS_TFRM_TIMER_CFG_7, 0x011330),
REG(QSYS_TFRM_TIMER_CFG_8, 0x011334),
REG(QSYS_RED_PROFILE, 0x011338),
REG(QSYS_RES_QOS_MODE, 0x011378),
REG(QSYS_RES_CFG, 0x012000),
REG(QSYS_RES_STAT, 0x012004),
REG(QSYS_EGR_DROP_MODE, 0x01137c),
REG(QSYS_EQ_CTRL, 0x011380),
REG(QSYS_EVENTS_CORE, 0x011384),
REG(QSYS_CIR_CFG, 0x000000),
REG(QSYS_EIR_CFG, 0x000004),
REG(QSYS_SE_CFG, 0x000008),
REG(QSYS_SE_DWRR_CFG, 0x00000c),
REG(QSYS_SE_CONNECT, 0x00003c),
REG(QSYS_SE_DLB_SENSE, 0x000040),
REG(QSYS_CIR_STATE, 0x000044),
REG(QSYS_EIR_STATE, 0x000048),
REG(QSYS_SE_STATE, 0x00004c),
REG(QSYS_HSCH_MISC_CFG, 0x011388),
};
static const u32 vsc7514_rew_regmap[] = {
REG(REW_PORT_VLAN_CFG, 0x000000),
REG(REW_TAG_CFG, 0x000004),
REG(REW_PORT_CFG, 0x000008),
REG(REW_DSCP_CFG, 0x00000c),
REG(REW_PCP_DEI_QOS_MAP_CFG, 0x000010),
REG(REW_PTP_CFG, 0x000050),
REG(REW_PTP_DLY1_CFG, 0x000054),
REG(REW_DSCP_REMAP_DP1_CFG, 0x000690),
REG(REW_DSCP_REMAP_CFG, 0x000790),
REG(REW_STAT_CFG, 0x000890),
REG(REW_PPT, 0x000680),
};
static const u32 vsc7514_sys_regmap[] = {
REG(SYS_COUNT_RX_OCTETS, 0x000000),
REG(SYS_COUNT_RX_UNICAST, 0x000004),
REG(SYS_COUNT_RX_MULTICAST, 0x000008),
REG(SYS_COUNT_RX_BROADCAST, 0x00000c),
REG(SYS_COUNT_RX_SHORTS, 0x000010),
REG(SYS_COUNT_RX_FRAGMENTS, 0x000014),
REG(SYS_COUNT_RX_JABBERS, 0x000018),
REG(SYS_COUNT_RX_CRC_ALIGN_ERRS, 0x00001c),
REG(SYS_COUNT_RX_SYM_ERRS, 0x000020),
REG(SYS_COUNT_RX_64, 0x000024),
REG(SYS_COUNT_RX_65_127, 0x000028),
REG(SYS_COUNT_RX_128_255, 0x00002c),
REG(SYS_COUNT_RX_256_511, 0x000030),
REG(SYS_COUNT_RX_512_1023, 0x000034),
REG(SYS_COUNT_RX_1024_1526, 0x000038),
REG(SYS_COUNT_RX_1527_MAX, 0x00003c),
REG(SYS_COUNT_RX_PAUSE, 0x000040),
REG(SYS_COUNT_RX_CONTROL, 0x000044),
REG(SYS_COUNT_RX_LONGS, 0x000048),
REG(SYS_COUNT_RX_CLASSIFIED_DROPS, 0x00004c),
REG(SYS_COUNT_RX_RED_PRIO_0, 0x000050),
REG(SYS_COUNT_RX_RED_PRIO_1, 0x000054),
REG(SYS_COUNT_RX_RED_PRIO_2, 0x000058),
REG(SYS_COUNT_RX_RED_PRIO_3, 0x00005c),
REG(SYS_COUNT_RX_RED_PRIO_4, 0x000060),
REG(SYS_COUNT_RX_RED_PRIO_5, 0x000064),
REG(SYS_COUNT_RX_RED_PRIO_6, 0x000068),
REG(SYS_COUNT_RX_RED_PRIO_7, 0x00006c),
REG(SYS_COUNT_RX_YELLOW_PRIO_0, 0x000070),
REG(SYS_COUNT_RX_YELLOW_PRIO_1, 0x000074),
REG(SYS_COUNT_RX_YELLOW_PRIO_2, 0x000078),
REG(SYS_COUNT_RX_YELLOW_PRIO_3, 0x00007c),
REG(SYS_COUNT_RX_YELLOW_PRIO_4, 0x000080),
REG(SYS_COUNT_RX_YELLOW_PRIO_5, 0x000084),
REG(SYS_COUNT_RX_YELLOW_PRIO_6, 0x000088),
REG(SYS_COUNT_RX_YELLOW_PRIO_7, 0x00008c),
REG(SYS_COUNT_RX_GREEN_PRIO_0, 0x000090),
REG(SYS_COUNT_RX_GREEN_PRIO_1, 0x000094),
REG(SYS_COUNT_RX_GREEN_PRIO_2, 0x000098),
REG(SYS_COUNT_RX_GREEN_PRIO_3, 0x00009c),
REG(SYS_COUNT_RX_GREEN_PRIO_4, 0x0000a0),
REG(SYS_COUNT_RX_GREEN_PRIO_5, 0x0000a4),
REG(SYS_COUNT_RX_GREEN_PRIO_6, 0x0000a8),
REG(SYS_COUNT_RX_GREEN_PRIO_7, 0x0000ac),
REG(SYS_COUNT_TX_OCTETS, 0x000100),
REG(SYS_COUNT_TX_UNICAST, 0x000104),
REG(SYS_COUNT_TX_MULTICAST, 0x000108),
REG(SYS_COUNT_TX_BROADCAST, 0x00010c),
REG(SYS_COUNT_TX_COLLISION, 0x000110),
REG(SYS_COUNT_TX_DROPS, 0x000114),
REG(SYS_COUNT_TX_PAUSE, 0x000118),
REG(SYS_COUNT_TX_64, 0x00011c),
REG(SYS_COUNT_TX_65_127, 0x000120),
REG(SYS_COUNT_TX_128_255, 0x000124),
REG(SYS_COUNT_TX_256_511, 0x000128),
REG(SYS_COUNT_TX_512_1023, 0x00012c),
REG(SYS_COUNT_TX_1024_1526, 0x000130),
REG(SYS_COUNT_TX_1527_MAX, 0x000134),
REG(SYS_COUNT_TX_YELLOW_PRIO_0, 0x000138),
REG(SYS_COUNT_TX_YELLOW_PRIO_1, 0x00013c),
REG(SYS_COUNT_TX_YELLOW_PRIO_2, 0x000140),
REG(SYS_COUNT_TX_YELLOW_PRIO_3, 0x000144),
REG(SYS_COUNT_TX_YELLOW_PRIO_4, 0x000148),
REG(SYS_COUNT_TX_YELLOW_PRIO_5, 0x00014c),
REG(SYS_COUNT_TX_YELLOW_PRIO_6, 0x000150),
REG(SYS_COUNT_TX_YELLOW_PRIO_7, 0x000154),
REG(SYS_COUNT_TX_GREEN_PRIO_0, 0x000158),
REG(SYS_COUNT_TX_GREEN_PRIO_1, 0x00015c),
REG(SYS_COUNT_TX_GREEN_PRIO_2, 0x000160),
REG(SYS_COUNT_TX_GREEN_PRIO_3, 0x000164),
REG(SYS_COUNT_TX_GREEN_PRIO_4, 0x000168),
REG(SYS_COUNT_TX_GREEN_PRIO_5, 0x00016c),
REG(SYS_COUNT_TX_GREEN_PRIO_6, 0x000170),
REG(SYS_COUNT_TX_GREEN_PRIO_7, 0x000174),
REG(SYS_COUNT_TX_AGED, 0x000178),
REG(SYS_COUNT_DROP_LOCAL, 0x000200),
REG(SYS_COUNT_DROP_TAIL, 0x000204),
REG(SYS_COUNT_DROP_YELLOW_PRIO_0, 0x000208),
REG(SYS_COUNT_DROP_YELLOW_PRIO_1, 0x00020c),
REG(SYS_COUNT_DROP_YELLOW_PRIO_2, 0x000210),
REG(SYS_COUNT_DROP_YELLOW_PRIO_3, 0x000214),
REG(SYS_COUNT_DROP_YELLOW_PRIO_4, 0x000218),
REG(SYS_COUNT_DROP_YELLOW_PRIO_5, 0x00021c),
REG(SYS_COUNT_DROP_YELLOW_PRIO_6, 0x000220),
REG(SYS_COUNT_DROP_YELLOW_PRIO_7, 0x000224),
REG(SYS_COUNT_DROP_GREEN_PRIO_0, 0x000228),
REG(SYS_COUNT_DROP_GREEN_PRIO_1, 0x00022c),
REG(SYS_COUNT_DROP_GREEN_PRIO_2, 0x000230),
REG(SYS_COUNT_DROP_GREEN_PRIO_3, 0x000234),
REG(SYS_COUNT_DROP_GREEN_PRIO_4, 0x000238),
REG(SYS_COUNT_DROP_GREEN_PRIO_5, 0x00023c),
REG(SYS_COUNT_DROP_GREEN_PRIO_6, 0x000240),
REG(SYS_COUNT_DROP_GREEN_PRIO_7, 0x000244),
REG(SYS_RESET_CFG, 0x000508),
REG(SYS_CMID, 0x00050c),
REG(SYS_VLAN_ETYPE_CFG, 0x000510),
REG(SYS_PORT_MODE, 0x000514),
REG(SYS_FRONT_PORT_MODE, 0x000548),
REG(SYS_FRM_AGING, 0x000574),
REG(SYS_STAT_CFG, 0x000578),
REG(SYS_SW_STATUS, 0x00057c),
REG(SYS_MISC_CFG, 0x0005ac),
REG(SYS_REW_MAC_HIGH_CFG, 0x0005b0),
REG(SYS_REW_MAC_LOW_CFG, 0x0005dc),
REG(SYS_CM_ADDR, 0x000500),
REG(SYS_CM_DATA, 0x000504),
REG(SYS_PAUSE_CFG, 0x000608),
REG(SYS_PAUSE_TOT_CFG, 0x000638),
REG(SYS_ATOP, 0x00063c),
REG(SYS_ATOP_TOT_CFG, 0x00066c),
REG(SYS_MAC_FC_CFG, 0x000670),
REG(SYS_MMGT, 0x00069c),
REG(SYS_MMGT_FAST, 0x0006a0),
REG(SYS_EVENTS_DIF, 0x0006a4),
REG(SYS_EVENTS_CORE, 0x0006b4),
REG(SYS_PTP_STATUS, 0x0006b8),
REG(SYS_PTP_TXSTAMP, 0x0006bc),
REG(SYS_PTP_NXT, 0x0006c0),
REG(SYS_PTP_CFG, 0x0006c4),
};
static const u32 vsc7514_vcap_regmap[] = {
/* VCAP_CORE_CFG */
REG(VCAP_CORE_UPDATE_CTRL, 0x000000),
REG(VCAP_CORE_MV_CFG, 0x000004),
/* VCAP_CORE_CACHE */
REG(VCAP_CACHE_ENTRY_DAT, 0x000008),
REG(VCAP_CACHE_MASK_DAT, 0x000108),
REG(VCAP_CACHE_ACTION_DAT, 0x000208),
REG(VCAP_CACHE_CNT_DAT, 0x000308),
REG(VCAP_CACHE_TG_DAT, 0x000388),
/* VCAP_CONST */
REG(VCAP_CONST_VCAP_VER, 0x000398),
REG(VCAP_CONST_ENTRY_WIDTH, 0x00039c),
REG(VCAP_CONST_ENTRY_CNT, 0x0003a0),
REG(VCAP_CONST_ENTRY_SWCNT, 0x0003a4),
REG(VCAP_CONST_ENTRY_TG_WIDTH, 0x0003a8),
REG(VCAP_CONST_ACTION_DEF_CNT, 0x0003ac),
REG(VCAP_CONST_ACTION_WIDTH, 0x0003b0),
REG(VCAP_CONST_CNT_WIDTH, 0x0003b4),
REG(VCAP_CONST_CORE_CNT, 0x0003b8),
REG(VCAP_CONST_IF_CNT, 0x0003bc),
};
static const u32 vsc7514_ptp_regmap[] = {
REG(PTP_PIN_CFG, 0x000000),
REG(PTP_PIN_TOD_SEC_MSB, 0x000004),
REG(PTP_PIN_TOD_SEC_LSB, 0x000008),
REG(PTP_PIN_TOD_NSEC, 0x00000c),
REG(PTP_PIN_WF_HIGH_PERIOD, 0x000014),
REG(PTP_PIN_WF_LOW_PERIOD, 0x000018),
REG(PTP_CFG_MISC, 0x0000a0),
REG(PTP_CLK_CFG_ADJ_CFG, 0x0000a4),
REG(PTP_CLK_CFG_ADJ_FREQ, 0x0000a8),
};
static const u32 vsc7514_dev_gmii_regmap[] = {
REG(DEV_CLOCK_CFG, 0x0),
REG(DEV_PORT_MISC, 0x4),
REG(DEV_EVENTS, 0x8),
REG(DEV_EEE_CFG, 0xc),
REG(DEV_RX_PATH_DELAY, 0x10),
REG(DEV_TX_PATH_DELAY, 0x14),
REG(DEV_PTP_PREDICT_CFG, 0x18),
REG(DEV_MAC_ENA_CFG, 0x1c),
REG(DEV_MAC_MODE_CFG, 0x20),
REG(DEV_MAC_MAXLEN_CFG, 0x24),
REG(DEV_MAC_TAGS_CFG, 0x28),
REG(DEV_MAC_ADV_CHK_CFG, 0x2c),
REG(DEV_MAC_IFG_CFG, 0x30),
REG(DEV_MAC_HDX_CFG, 0x34),
REG(DEV_MAC_DBG_CFG, 0x38),
REG(DEV_MAC_FC_MAC_LOW_CFG, 0x3c),
REG(DEV_MAC_FC_MAC_HIGH_CFG, 0x40),
REG(DEV_MAC_STICKY, 0x44),
REG(PCS1G_CFG, 0x48),
REG(PCS1G_MODE_CFG, 0x4c),
REG(PCS1G_SD_CFG, 0x50),
REG(PCS1G_ANEG_CFG, 0x54),
REG(PCS1G_ANEG_NP_CFG, 0x58),
REG(PCS1G_LB_CFG, 0x5c),
REG(PCS1G_DBG_CFG, 0x60),
REG(PCS1G_CDET_CFG, 0x64),
REG(PCS1G_ANEG_STATUS, 0x68),
REG(PCS1G_ANEG_NP_STATUS, 0x6c),
REG(PCS1G_LINK_STATUS, 0x70),
REG(PCS1G_LINK_DOWN_CNT, 0x74),
REG(PCS1G_STICKY, 0x78),
REG(PCS1G_DEBUG_STATUS, 0x7c),
REG(PCS1G_LPI_CFG, 0x80),
REG(PCS1G_LPI_WAKE_ERROR_CNT, 0x84),
REG(PCS1G_LPI_STATUS, 0x88),
REG(PCS1G_TSTPAT_MODE_CFG, 0x8c),
REG(PCS1G_TSTPAT_STATUS, 0x90),
REG(DEV_PCS_FX100_CFG, 0x94),
REG(DEV_PCS_FX100_STATUS, 0x98),
};
const u32 *vsc7514_regmap[TARGET_MAX] = {
[ANA] = vsc7514_ana_regmap,
[QS] = vsc7514_qs_regmap,
[QSYS] = vsc7514_qsys_regmap,
[REW] = vsc7514_rew_regmap,
[SYS] = vsc7514_sys_regmap,
[S0] = vsc7514_vcap_regmap,
[S1] = vsc7514_vcap_regmap,
[S2] = vsc7514_vcap_regmap,
[PTP] = vsc7514_ptp_regmap,
[DEV_GMII] = vsc7514_dev_gmii_regmap,
};
EXPORT_SYMBOL(vsc7514_regmap);
static const struct vcap_field vsc7514_vcap_es0_keys[] = {
[VCAP_ES0_EGR_PORT] = { 0, 4 },
[VCAP_ES0_IGR_PORT] = { 4, 4 },
[VCAP_ES0_RSV] = { 8, 2 },
[VCAP_ES0_L2_MC] = { 10, 1 },
[VCAP_ES0_L2_BC] = { 11, 1 },
[VCAP_ES0_VID] = { 12, 12 },
[VCAP_ES0_DP] = { 24, 1 },
[VCAP_ES0_PCP] = { 25, 3 },
};
static const struct vcap_field vsc7514_vcap_es0_actions[] = {
[VCAP_ES0_ACT_PUSH_OUTER_TAG] = { 0, 2 },
[VCAP_ES0_ACT_PUSH_INNER_TAG] = { 2, 1 },
[VCAP_ES0_ACT_TAG_A_TPID_SEL] = { 3, 2 },
[VCAP_ES0_ACT_TAG_A_VID_SEL] = { 5, 1 },
[VCAP_ES0_ACT_TAG_A_PCP_SEL] = { 6, 2 },
[VCAP_ES0_ACT_TAG_A_DEI_SEL] = { 8, 2 },
[VCAP_ES0_ACT_TAG_B_TPID_SEL] = { 10, 2 },
[VCAP_ES0_ACT_TAG_B_VID_SEL] = { 12, 1 },
[VCAP_ES0_ACT_TAG_B_PCP_SEL] = { 13, 2 },
[VCAP_ES0_ACT_TAG_B_DEI_SEL] = { 15, 2 },
[VCAP_ES0_ACT_VID_A_VAL] = { 17, 12 },
[VCAP_ES0_ACT_PCP_A_VAL] = { 29, 3 },
[VCAP_ES0_ACT_DEI_A_VAL] = { 32, 1 },
[VCAP_ES0_ACT_VID_B_VAL] = { 33, 12 },
[VCAP_ES0_ACT_PCP_B_VAL] = { 45, 3 },
[VCAP_ES0_ACT_DEI_B_VAL] = { 48, 1 },
[VCAP_ES0_ACT_RSV] = { 49, 24 },
[VCAP_ES0_ACT_HIT_STICKY] = { 73, 1 },
};
static const struct vcap_field vsc7514_vcap_is1_keys[] = {
[VCAP_IS1_HK_TYPE] = { 0, 1 },
[VCAP_IS1_HK_LOOKUP] = { 1, 2 },
[VCAP_IS1_HK_IGR_PORT_MASK] = { 3, 12 },
[VCAP_IS1_HK_RSV] = { 15, 9 },
[VCAP_IS1_HK_OAM_Y1731] = { 24, 1 },
[VCAP_IS1_HK_L2_MC] = { 25, 1 },
[VCAP_IS1_HK_L2_BC] = { 26, 1 },
[VCAP_IS1_HK_IP_MC] = { 27, 1 },
[VCAP_IS1_HK_VLAN_TAGGED] = { 28, 1 },
[VCAP_IS1_HK_VLAN_DBL_TAGGED] = { 29, 1 },
[VCAP_IS1_HK_TPID] = { 30, 1 },
[VCAP_IS1_HK_VID] = { 31, 12 },
[VCAP_IS1_HK_DEI] = { 43, 1 },
[VCAP_IS1_HK_PCP] = { 44, 3 },
/* Specific Fields for IS1 Half Key S1_NORMAL */
[VCAP_IS1_HK_L2_SMAC] = { 47, 48 },
[VCAP_IS1_HK_ETYPE_LEN] = { 95, 1 },
[VCAP_IS1_HK_ETYPE] = { 96, 16 },
[VCAP_IS1_HK_IP_SNAP] = { 112, 1 },
[VCAP_IS1_HK_IP4] = { 113, 1 },
/* Layer-3 Information */
[VCAP_IS1_HK_L3_FRAGMENT] = { 114, 1 },
[VCAP_IS1_HK_L3_FRAG_OFS_GT0] = { 115, 1 },
[VCAP_IS1_HK_L3_OPTIONS] = { 116, 1 },
[VCAP_IS1_HK_L3_DSCP] = { 117, 6 },
[VCAP_IS1_HK_L3_IP4_SIP] = { 123, 32 },
/* Layer-4 Information */
[VCAP_IS1_HK_TCP_UDP] = { 155, 1 },
[VCAP_IS1_HK_TCP] = { 156, 1 },
[VCAP_IS1_HK_L4_SPORT] = { 157, 16 },
[VCAP_IS1_HK_L4_RNG] = { 173, 8 },
/* Specific Fields for IS1 Half Key S1_5TUPLE_IP4 */
[VCAP_IS1_HK_IP4_INNER_TPID] = { 47, 1 },
[VCAP_IS1_HK_IP4_INNER_VID] = { 48, 12 },
[VCAP_IS1_HK_IP4_INNER_DEI] = { 60, 1 },
[VCAP_IS1_HK_IP4_INNER_PCP] = { 61, 3 },
[VCAP_IS1_HK_IP4_IP4] = { 64, 1 },
[VCAP_IS1_HK_IP4_L3_FRAGMENT] = { 65, 1 },
[VCAP_IS1_HK_IP4_L3_FRAG_OFS_GT0] = { 66, 1 },
[VCAP_IS1_HK_IP4_L3_OPTIONS] = { 67, 1 },
[VCAP_IS1_HK_IP4_L3_DSCP] = { 68, 6 },
[VCAP_IS1_HK_IP4_L3_IP4_DIP] = { 74, 32 },
[VCAP_IS1_HK_IP4_L3_IP4_SIP] = { 106, 32 },
[VCAP_IS1_HK_IP4_L3_PROTO] = { 138, 8 },
[VCAP_IS1_HK_IP4_TCP_UDP] = { 146, 1 },
[VCAP_IS1_HK_IP4_TCP] = { 147, 1 },
[VCAP_IS1_HK_IP4_L4_RNG] = { 148, 8 },
[VCAP_IS1_HK_IP4_IP_PAYLOAD_S1_5TUPLE] = { 156, 32 },
};
static const struct vcap_field vsc7514_vcap_is1_actions[] = {
[VCAP_IS1_ACT_DSCP_ENA] = { 0, 1 },
[VCAP_IS1_ACT_DSCP_VAL] = { 1, 6 },
[VCAP_IS1_ACT_QOS_ENA] = { 7, 1 },
[VCAP_IS1_ACT_QOS_VAL] = { 8, 3 },
[VCAP_IS1_ACT_DP_ENA] = { 11, 1 },
[VCAP_IS1_ACT_DP_VAL] = { 12, 1 },
[VCAP_IS1_ACT_PAG_OVERRIDE_MASK] = { 13, 8 },
[VCAP_IS1_ACT_PAG_VAL] = { 21, 8 },
[VCAP_IS1_ACT_RSV] = { 29, 9 },
/* The fields below are incorrectly shifted by 2 in the manual */
[VCAP_IS1_ACT_VID_REPLACE_ENA] = { 38, 1 },
[VCAP_IS1_ACT_VID_ADD_VAL] = { 39, 12 },
[VCAP_IS1_ACT_FID_SEL] = { 51, 2 },
[VCAP_IS1_ACT_FID_VAL] = { 53, 13 },
[VCAP_IS1_ACT_PCP_DEI_ENA] = { 66, 1 },
[VCAP_IS1_ACT_PCP_VAL] = { 67, 3 },
[VCAP_IS1_ACT_DEI_VAL] = { 70, 1 },
[VCAP_IS1_ACT_VLAN_POP_CNT_ENA] = { 71, 1 },
[VCAP_IS1_ACT_VLAN_POP_CNT] = { 72, 2 },
[VCAP_IS1_ACT_CUSTOM_ACE_TYPE_ENA] = { 74, 4 },
[VCAP_IS1_ACT_HIT_STICKY] = { 78, 1 },
};
static const struct vcap_field vsc7514_vcap_is2_keys[] = {
/* Common: 46 bits */
[VCAP_IS2_TYPE] = { 0, 4 },
[VCAP_IS2_HK_FIRST] = { 4, 1 },
[VCAP_IS2_HK_PAG] = { 5, 8 },
[VCAP_IS2_HK_IGR_PORT_MASK] = { 13, 12 },
[VCAP_IS2_HK_RSV2] = { 25, 1 },
[VCAP_IS2_HK_HOST_MATCH] = { 26, 1 },
[VCAP_IS2_HK_L2_MC] = { 27, 1 },
[VCAP_IS2_HK_L2_BC] = { 28, 1 },
[VCAP_IS2_HK_VLAN_TAGGED] = { 29, 1 },
[VCAP_IS2_HK_VID] = { 30, 12 },
[VCAP_IS2_HK_DEI] = { 42, 1 },
[VCAP_IS2_HK_PCP] = { 43, 3 },
/* MAC_ETYPE / MAC_LLC / MAC_SNAP / OAM common */
[VCAP_IS2_HK_L2_DMAC] = { 46, 48 },
[VCAP_IS2_HK_L2_SMAC] = { 94, 48 },
/* MAC_ETYPE (TYPE=000) */
[VCAP_IS2_HK_MAC_ETYPE_ETYPE] = { 142, 16 },
[VCAP_IS2_HK_MAC_ETYPE_L2_PAYLOAD0] = { 158, 16 },
[VCAP_IS2_HK_MAC_ETYPE_L2_PAYLOAD1] = { 174, 8 },
[VCAP_IS2_HK_MAC_ETYPE_L2_PAYLOAD2] = { 182, 3 },
/* MAC_LLC (TYPE=001) */
[VCAP_IS2_HK_MAC_LLC_L2_LLC] = { 142, 40 },
/* MAC_SNAP (TYPE=010) */
[VCAP_IS2_HK_MAC_SNAP_L2_SNAP] = { 142, 40 },
/* MAC_ARP (TYPE=011) */
[VCAP_IS2_HK_MAC_ARP_SMAC] = { 46, 48 },
[VCAP_IS2_HK_MAC_ARP_ADDR_SPACE_OK] = { 94, 1 },
[VCAP_IS2_HK_MAC_ARP_PROTO_SPACE_OK] = { 95, 1 },
[VCAP_IS2_HK_MAC_ARP_LEN_OK] = { 96, 1 },
[VCAP_IS2_HK_MAC_ARP_TARGET_MATCH] = { 97, 1 },
[VCAP_IS2_HK_MAC_ARP_SENDER_MATCH] = { 98, 1 },
[VCAP_IS2_HK_MAC_ARP_OPCODE_UNKNOWN] = { 99, 1 },
[VCAP_IS2_HK_MAC_ARP_OPCODE] = { 100, 2 },
[VCAP_IS2_HK_MAC_ARP_L3_IP4_DIP] = { 102, 32 },
[VCAP_IS2_HK_MAC_ARP_L3_IP4_SIP] = { 134, 32 },
[VCAP_IS2_HK_MAC_ARP_DIP_EQ_SIP] = { 166, 1 },
/* IP4_TCP_UDP / IP4_OTHER common */
[VCAP_IS2_HK_IP4] = { 46, 1 },
[VCAP_IS2_HK_L3_FRAGMENT] = { 47, 1 },
[VCAP_IS2_HK_L3_FRAG_OFS_GT0] = { 48, 1 },
[VCAP_IS2_HK_L3_OPTIONS] = { 49, 1 },
[VCAP_IS2_HK_IP4_L3_TTL_GT0] = { 50, 1 },
[VCAP_IS2_HK_L3_TOS] = { 51, 8 },
[VCAP_IS2_HK_L3_IP4_DIP] = { 59, 32 },
[VCAP_IS2_HK_L3_IP4_SIP] = { 91, 32 },
[VCAP_IS2_HK_DIP_EQ_SIP] = { 123, 1 },
/* IP4_TCP_UDP (TYPE=100) */
[VCAP_IS2_HK_TCP] = { 124, 1 },
[VCAP_IS2_HK_L4_DPORT] = { 125, 16 },
[VCAP_IS2_HK_L4_SPORT] = { 141, 16 },
[VCAP_IS2_HK_L4_RNG] = { 157, 8 },
[VCAP_IS2_HK_L4_SPORT_EQ_DPORT] = { 165, 1 },
[VCAP_IS2_HK_L4_SEQUENCE_EQ0] = { 166, 1 },
[VCAP_IS2_HK_L4_FIN] = { 167, 1 },
[VCAP_IS2_HK_L4_SYN] = { 168, 1 },
[VCAP_IS2_HK_L4_RST] = { 169, 1 },
[VCAP_IS2_HK_L4_PSH] = { 170, 1 },
[VCAP_IS2_HK_L4_ACK] = { 171, 1 },
[VCAP_IS2_HK_L4_URG] = { 172, 1 },
[VCAP_IS2_HK_L4_1588_DOM] = { 173, 8 },
[VCAP_IS2_HK_L4_1588_VER] = { 181, 4 },
/* IP4_OTHER (TYPE=101) */
[VCAP_IS2_HK_IP4_L3_PROTO] = { 124, 8 },
[VCAP_IS2_HK_L3_PAYLOAD] = { 132, 56 },
/* IP6_STD (TYPE=110) */
[VCAP_IS2_HK_IP6_L3_TTL_GT0] = { 46, 1 },
[VCAP_IS2_HK_L3_IP6_SIP] = { 47, 128 },
[VCAP_IS2_HK_IP6_L3_PROTO] = { 175, 8 },
/* OAM (TYPE=111) */
[VCAP_IS2_HK_OAM_MEL_FLAGS] = { 142, 7 },
[VCAP_IS2_HK_OAM_VER] = { 149, 5 },
[VCAP_IS2_HK_OAM_OPCODE] = { 154, 8 },
[VCAP_IS2_HK_OAM_FLAGS] = { 162, 8 },
[VCAP_IS2_HK_OAM_MEPID] = { 170, 16 },
[VCAP_IS2_HK_OAM_CCM_CNTS_EQ0] = { 186, 1 },
[VCAP_IS2_HK_OAM_IS_Y1731] = { 187, 1 },
};
static const struct vcap_field vsc7514_vcap_is2_actions[] = {
[VCAP_IS2_ACT_HIT_ME_ONCE] = { 0, 1 },
[VCAP_IS2_ACT_CPU_COPY_ENA] = { 1, 1 },
[VCAP_IS2_ACT_CPU_QU_NUM] = { 2, 3 },
[VCAP_IS2_ACT_MASK_MODE] = { 5, 2 },
[VCAP_IS2_ACT_MIRROR_ENA] = { 7, 1 },
[VCAP_IS2_ACT_LRN_DIS] = { 8, 1 },
[VCAP_IS2_ACT_POLICE_ENA] = { 9, 1 },
[VCAP_IS2_ACT_POLICE_IDX] = { 10, 9 },
[VCAP_IS2_ACT_POLICE_VCAP_ONLY] = { 19, 1 },
[VCAP_IS2_ACT_PORT_MASK] = { 20, 11 },
[VCAP_IS2_ACT_REW_OP] = { 31, 9 },
[VCAP_IS2_ACT_SMAC_REPLACE_ENA] = { 40, 1 },
[VCAP_IS2_ACT_RSV] = { 41, 2 },
[VCAP_IS2_ACT_ACL_ID] = { 43, 6 },
[VCAP_IS2_ACT_HIT_CNT] = { 49, 32 },
};
struct vcap_props vsc7514_vcap_props[] = {
[VCAP_ES0] = {
.action_type_width = 0,
.action_table = {
[ES0_ACTION_TYPE_NORMAL] = {
.width = 73, /* HIT_STICKY not included */
.count = 1,
},
},
.target = S0,
.keys = vsc7514_vcap_es0_keys,
.actions = vsc7514_vcap_es0_actions,
},
[VCAP_IS1] = {
.action_type_width = 0,
.action_table = {
[IS1_ACTION_TYPE_NORMAL] = {
.width = 78, /* HIT_STICKY not included */
.count = 4,
},
},
.target = S1,
.keys = vsc7514_vcap_is1_keys,
.actions = vsc7514_vcap_is1_actions,
},
[VCAP_IS2] = {
.action_type_width = 1,
.action_table = {
[IS2_ACTION_TYPE_NORMAL] = {
.width = 49,
.count = 2
},
[IS2_ACTION_TYPE_SMAC_SIP] = {
.width = 6,
.count = 4
},
},
.target = S2,
.keys = vsc7514_vcap_is2_keys,
.actions = vsc7514_vcap_is2_actions,
},
};
EXPORT_SYMBOL(vsc7514_vcap_props);