mv88e6xxx-objs += smi.o
mv88e6xxx-objs += switchdev.o
mv88e6xxx-objs += trace.o
+mv88e6xxx-objs += tcflower.o
+mv88e6xxx-objs += tcam.o
# for tracing framework to find trace.h
CFLAGS_trace.o := -I$(src)
#include "ptp.h"
#include "serdes.h"
#include "smi.h"
+#include "tcam.h"
+#include "tcflower.h"
static void assert_reg_lock(struct mv88e6xxx_chip *chip)
{
if (err)
return err;
}
+ if (chip->info->ops->port_enable_tcam) {
+ err = chip->info->ops->port_enable_tcam(chip, port);
+ if (err)
+ return err;
+ }
if (chip->info->ops->port_tag_remap) {
err = chip->info->ops->port_tag_remap(chip, port);
return 0;
}
+static int mv88e6xxx_tcam_setup(struct mv88e6xxx_chip *chip)
+{
+ if (!mv88e6xxx_has_tcam(chip))
+ return 0;
+
+ return chip->info->ops->tcam_ops->flush_tcam(chip);
+}
+
static void mv88e6xxx_teardown(struct dsa_switch *ds)
{
struct mv88e6xxx_chip *chip = ds->priv;
mv88e6xxx_teardown_devlink_regions_global(ds);
mv88e6xxx_hwtstamp_free(chip);
mv88e6xxx_ptp_free(chip);
+ mv88e6xxx_flower_teardown(chip);
mv88e6xxx_mdios_unregister(chip);
}
if (err)
goto unlock;
+ err = mv88e6xxx_tcam_setup(chip);
+ if (err)
+ goto unlock;
+
unlock:
mv88e6xxx_reg_unlock(chip);
.ptp_ops = &mv88e6390_ptp_ops,
.phylink_get_caps = mv88e6390_phylink_get_caps,
.pcs_ops = &mv88e6390_pcs_ops,
+ .tcam_ops = &mv88e6390_tcam_ops,
};
static const struct mv88e6xxx_ops mv88e6320_ops = {
.serdes_get_regs = mv88e6390_serdes_get_regs,
.phylink_get_caps = mv88e6390_phylink_get_caps,
.pcs_ops = &mv88e6390_pcs_ops,
+ .tcam_ops = &mv88e6390_tcam_ops,
};
static const struct mv88e6xxx_ops mv88e6390x_ops = {
.port_set_cmode = mv88e6393x_port_set_cmode,
.port_setup_message_port = mv88e6xxx_setup_message_port,
.port_set_upstream_port = mv88e6393x_port_set_upstream_port,
+ .port_enable_tcam = mv88e6xxx_port_enable_tcam,
.stats_snapshot = mv88e6390_g1_stats_snapshot,
.stats_set_histogram = mv88e6390_g1_stats_set_histogram,
.stats_get_sset_count = mv88e6320_stats_get_sset_count,
.ptp_ops = &mv88e6352_ptp_ops,
.phylink_get_caps = mv88e6393x_phylink_get_caps,
.pcs_ops = &mv88e6393x_pcs_ops,
+ .tcam_ops = &mv88e6393_tcam_ops,
};
static const struct mv88e6xxx_info mv88e6xxx_table[] = {
.num_databases = 4096,
.num_ports = 11, /* 10 + Z80 */
.num_internal_phys = 8,
+ .num_tcam_entries = 256,
.internal_phys_offset = 1,
.max_vid = 8191,
.max_sid = 63,
.phy_base_addr = 0x0,
.global1_addr = 0x1b,
.global2_addr = 0x1c,
+ .tcam_addr = 0x1f,
.age_time_coeff = 3750,
.g1_irqs = 10,
.g2_irqs = 14,
.num_ports = 11, /* 10 + Z80 */
.num_internal_phys = 9,
.num_gpio = 16,
+ .num_tcam_entries = 256,
.max_vid = 8191,
.max_sid = 63,
.port_base_addr = 0x0,
.phy_base_addr = 0x0,
.global1_addr = 0x1b,
.global2_addr = 0x1c,
+ .tcam_addr = 0x1f,
.age_time_coeff = 3750,
.g1_irqs = 9,
.g2_irqs = 14,
.num_ports = 11, /* 10 + Z80 */
.num_internal_phys = 9,
.num_gpio = 16,
+ .num_tcam_entries = 256,
.max_vid = 8191,
.max_sid = 63,
.port_base_addr = 0x0,
.phy_base_addr = 0x0,
.global1_addr = 0x1b,
.global2_addr = 0x1c,
+ .tcam_addr = 0x1f,
.age_time_coeff = 3750,
.g1_irqs = 9,
.g2_irqs = 14,
.num_databases = 4096,
.num_ports = 11, /* 10 + Z80 */
.num_internal_phys = 8,
+ .num_tcam_entries = 256,
.internal_phys_offset = 1,
.max_vid = 8191,
.max_sid = 63,
.phy_base_addr = 0x0,
.global1_addr = 0x1b,
.global2_addr = 0x1c,
+ .tcam_addr = 0x1f,
.age_time_coeff = 3750,
.g1_irqs = 10,
.g2_irqs = 14,
INIT_LIST_HEAD(&chip->mdios);
idr_init(&chip->policies);
INIT_LIST_HEAD(&chip->msts);
+ INIT_LIST_HEAD(&chip->tcam.entries);
return chip;
}
.port_hwtstamp_get = mv88e6xxx_port_hwtstamp_get,
.port_txtstamp = mv88e6xxx_port_txtstamp,
.port_rxtstamp = mv88e6xxx_port_rxtstamp,
+ .cls_flower_add = mv88e6xxx_cls_flower_add,
+ .cls_flower_del = mv88e6xxx_cls_flower_del,
.get_ts_info = mv88e6xxx_get_ts_info,
.devlink_param_get = mv88e6xxx_devlink_param_get,
.devlink_param_set = mv88e6xxx_devlink_param_set,
unsigned int num_ports;
unsigned int num_internal_phys;
unsigned int num_gpio;
+ unsigned int num_tcam_entries;
unsigned int max_vid;
unsigned int max_sid;
unsigned int port_base_addr;
unsigned int phy_base_addr;
unsigned int global1_addr;
unsigned int global2_addr;
+ unsigned int tcam_addr;
unsigned int age_time_coeff;
unsigned int g1_irqs;
unsigned int g2_irqs;
struct mv88e6xxx_ptp_ops;
struct mv88e6xxx_pcs_ops;
struct mv88e6xxx_cc_coeffs;
+struct mv88e6xxx_tcam_ops;
struct mv88e6xxx_irq {
u16 masked;
int type;
};
+struct mv88e6xxx_tcam {
+ struct list_head entries;
+};
+
struct mv88e6xxx_chip {
const struct mv88e6xxx_info *info;
/* FID map */
DECLARE_BITMAP(fid_bitmap, MV88E6XXX_N_FID);
+
+ /* TCAM entries */
+ struct mv88e6xxx_tcam tcam;
+};
+
+#define TCAM_MATCH_SIZE 96
+
+struct mv88e6xxx_tcam_key {
+ u16 spv;
+ u16 spv_mask;
+
+ u8 frame_data[TCAM_MATCH_SIZE];
+ u8 frame_mask[TCAM_MATCH_SIZE];
+};
+
+struct mv88e6xxx_tcam_action {
+ u8 dpv_mode;
+ u16 dpv;
+};
+
+struct mv88e6xxx_tcam_entry {
+ struct list_head list;
+ unsigned long cookie;
+ u32 prio;
+ u8 hw_idx;
+
+ struct mv88e6xxx_tcam_key key;
+ struct mv88e6xxx_tcam_action action;
+
};
struct mv88e6xxx_bus_ops {
/* Max Frame Size */
int (*set_max_frame_size)(struct mv88e6xxx_chip *chip, int mtu);
+
+ int (*port_enable_tcam)(struct mv88e6xxx_chip *chip, int port);
+
+ /* Ternary Content Addressable Memory operations */
+ const struct mv88e6xxx_tcam_ops *tcam_ops;
};
struct mv88e6xxx_irq_ops {
};
+struct mv88e6xxx_tcam_ops {
+ int (*entry_add)(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_tcam_entry *entry, u8 idx);
+ int (*flush_tcam)(struct mv88e6xxx_chip *chip);
+};
+
static inline bool mv88e6xxx_has_stu(struct mv88e6xxx_chip *chip)
{
return chip->info->max_sid > 0 &&
return !!chip->info->global2_addr;
}
+static inline bool mv88e6xxx_has_tcam(struct mv88e6xxx_chip *chip)
+{
+ return !!chip->info->tcam_addr;
+}
+
static inline unsigned int mv88e6xxx_num_databases(struct mv88e6xxx_chip *chip)
{
return chip->info->num_databases;
int mv88e6xxx_port_disable_pri_override(struct mv88e6xxx_chip *chip, int port)
{
- return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_PRI_OVERRIDE, 0);
+ u16 reg;
+ int err;
+
+ err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_PRI_OVERRIDE,
+ ®);
+ if (err)
+ return err;
+
+ reg &= MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_MASK;
+ return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_PRI_OVERRIDE,
+ reg);
+}
+
+int mv88e6xxx_port_enable_tcam(struct mv88e6xxx_chip *chip, int port)
+{
+ u16 reg;
+ int err;
+
+ err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_PRI_OVERRIDE,
+ ®);
+ if (err)
+ return err;
+
+ reg &= ~MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_MASK;
+ reg |= MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_96_BYTE;
+ return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_PRI_OVERRIDE,
+ reg);
}
/* Offset 0x0E: Policy & MGMT Control Register for FAMILY 6191X 6193X 6393X */
#define MV88E6XXX_PORT_ATU_CTL 0x0c
/* Offset 0x0D: Priority Override Register */
-#define MV88E6XXX_PORT_PRI_OVERRIDE 0x0d
+#define MV88E6XXX_PORT_PRI_OVERRIDE 0x0d
+#define MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_MASK 0x0003
+#define MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_48_BYTE 0x0001
+#define MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_96_BYTE 0x0002
/* Offset 0x0E: Policy Control Register */
#define MV88E6XXX_PORT_POLICY_CTL 0x0e
int mv88e6xxx_port_hidden_read(struct mv88e6xxx_chip *chip, int block, int port,
int reg, u16 *val);
+int mv88e6xxx_port_enable_tcam(struct mv88e6xxx_chip *chip, int port);
+
#endif /* _MV88E6XXX_PORT_H */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Marvell 88E6xxx Switch TCAM support
+ *
+ * Copyright (c) 2026 Luminex Network Intelligence
+ */
+
+#include "linux/list.h"
+
+#include "chip.h"
+#include "tcam.h"
+
+/* TCAM operatation register */
+#define MV88E6XXX_TCAM_OP 0x00
+#define MV88E6XXX_TCAM_OP_BUSY 0x8000
+#define MV88E6XXX_TCAM_OP_OP_MASK 0x7000
+#define MV88E6XXX_TCAM_OP_OP_FLUSH_ALL 0x1000
+#define MV88E6XXX_TCAM_OP_OP_FLUSH 0x2000
+#define MV88E6XXX_TCAM_OP_OP_LOAD 0x3000
+#define MV88E6XXX_TCAM_OP_OP_GET_NEXT 0x4000
+#define MV88E6XXX_TCAM_OP_OP_READ 0x5000
+
+/* TCAM extension register */
+#define MV88E6XXX_TCAM_EXTENSION 0x01
+
+/* TCAM keys register 1 */
+#define MV88E6XXX_TCAM_KEYS1 0x02
+#define MV88E6XXX_TCAM_KEYS1_FT_MASK 0xC000
+#define MV88E6XXX_TCAM_KEYS1_SPV_MASK 0x0007
+#define MV88E6XXX_TCAM_KEYS1_SPV_MASK_MASK 0x0700
+
+/* TCAM keys register 2 */
+#define MV88E6XXX_TCAM_KEYS2 0x03
+#define MV88E6XXX_TCAM_KEYS2_SPV_MASK 0x00ff
+#define MV88E6XXX_TCAM_KEYS2_SPV_MASK_MASK 0xff00
+
+#define MV88E6XXX_TCAM_ING_ACT3 0x04
+#define MV88E6XXX_TCAM_ING_ACT3_SF 0x0800
+#define MV88E6XXX_TCAM_ING_ACT3_DPV_MASK 0x07ff
+
+#define MV88E6XXX_TCAM_ING_ACT5 0x06
+#define MV88E6XXX_TCAM_ING_ACT5_DPV_MODE_MASK 0xc000
+
+static int mv88e6xxx_tcam_write(struct mv88e6xxx_chip *chip, int reg, u16 val)
+{
+ return mv88e6xxx_write(chip, chip->info->tcam_addr, reg, val);
+}
+
+static int mv88e6xxx_tcam_wait(struct mv88e6xxx_chip *chip)
+{
+ int bit = __bf_shf(MV88E6XXX_TCAM_OP_BUSY);
+
+ return mv88e6xxx_wait_bit(chip, chip->info->tcam_addr,
+ MV88E6XXX_TCAM_OP, bit, 0);
+}
+
+static int mv88e6xxx_tcam_read_page(struct mv88e6xxx_chip *chip, u8 page,
+ u8 entry)
+
+{
+ u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_READ |
+ (page & 0x3) << 10 | entry;
+ int err;
+
+ err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val);
+ if (err)
+ return err;
+
+ return mv88e6xxx_tcam_wait(chip);
+}
+
+static int mv88e6xxx_tcam_load_page(struct mv88e6xxx_chip *chip, u8 page,
+ u8 entry)
+{
+ u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_LOAD |
+ (page & 0x3) << 10 | entry;
+ int err;
+
+ err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val);
+ if (err)
+ return err;
+
+ return mv88e6xxx_tcam_wait(chip);
+}
+
+static int mv88e6xxx_tcam_flush_entry(struct mv88e6xxx_chip *chip, u8 entry)
+{
+ u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_FLUSH | entry;
+ int err;
+
+ err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val);
+ if (err)
+ return err;
+
+ return mv88e6xxx_tcam_wait(chip);
+}
+
+static int mv88e6xxx_tcam_flush_all(struct mv88e6xxx_chip *chip)
+{
+ u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_FLUSH_ALL;
+ int err;
+
+ err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val);
+ if (err)
+ return err;
+
+ return mv88e6xxx_tcam_wait(chip);
+}
+
+struct mv88e6xxx_tcam_entry *
+mv88e6xxx_tcam_entry_find(struct mv88e6xxx_chip *chip, unsigned long cookie)
+{
+ struct mv88e6xxx_tcam_entry *entry;
+
+ list_for_each_entry(entry, &chip->tcam.entries, list)
+ if (entry->cookie == cookie)
+ return entry;
+
+ return NULL;
+}
+
+/* insert tcam entry in ordered list and move existing entries if necessary */
+static int mv88e6xxx_tcam_insert_entry(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_tcam_entry *entry)
+{
+ struct mv88e6xxx_tcam_entry *elem;
+ struct list_head *hpos;
+ int err;
+
+ list_for_each_prev(hpos, &chip->tcam.entries) {
+ u8 move_idx;
+
+ elem = list_entry(hpos, struct mv88e6xxx_tcam_entry, list);
+ if (entry->prio >= elem->prio)
+ break;
+
+ move_idx = elem->hw_idx + 1;
+
+ err = mv88e6xxx_tcam_flush_entry(chip, move_idx);
+ if (err)
+ return err;
+
+ err = chip->info->ops->tcam_ops->entry_add(chip, elem,
+ move_idx);
+ if (err)
+ return err;
+
+ elem->hw_idx = move_idx;
+ }
+
+ if (list_is_head(hpos, &chip->tcam.entries)) {
+ entry->hw_idx = 0;
+ } else {
+ elem = list_entry(hpos, struct mv88e6xxx_tcam_entry, list);
+ entry->hw_idx = elem->hw_idx + 1;
+ }
+ list_add(&entry->list, hpos);
+ return 0;
+}
+
+int mv88e6xxx_tcam_entry_add(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_tcam_entry *entry)
+{
+ int err;
+ struct mv88e6xxx_tcam_entry *last;
+
+ last = list_last_entry_or_null(&chip->tcam.entries,
+ struct mv88e6xxx_tcam_entry, list);
+ if (last && last->hw_idx == chip->info->num_tcam_entries - 1)
+ return -ENOSPC;
+
+ err = mv88e6xxx_tcam_insert_entry(chip, entry);
+ if (err)
+ return err;
+
+ err = mv88e6xxx_tcam_flush_entry(chip, entry->hw_idx);
+ if (err)
+ goto unlink_out;
+
+ err = chip->info->ops->tcam_ops->entry_add(chip, entry, entry->hw_idx);
+ if (err)
+ goto unlink_out;
+
+ return 0;
+
+unlink_out:
+ list_del(&entry->list);
+ return err;
+}
+
+int mv88e6xxx_tcam_entry_del(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_tcam_entry *entry)
+{
+ struct mv88e6xxx_tcam_entry *elem = entry;
+ u8 move_idx = entry->hw_idx;
+ int err;
+
+ err = mv88e6xxx_tcam_flush_entry(chip, entry->hw_idx);
+ if (err)
+ return err;
+
+ /* move entries that come after the deleted entry forward */
+ list_for_each_entry_continue(elem, &chip->tcam.entries, list) {
+ u8 tmp_idx = elem->hw_idx;
+
+ err = mv88e6xxx_tcam_flush_entry(chip, move_idx);
+ if (err)
+ break;
+
+ err = chip->info->ops->tcam_ops->entry_add(chip, elem,
+ move_idx);
+ if (err)
+ break;
+
+ elem->hw_idx = move_idx;
+ move_idx = tmp_idx;
+
+ /* flush the last entry after moving entries */
+ if (list_is_last(&elem->list, &chip->tcam.entries))
+ err = mv88e6xxx_tcam_flush_entry(chip, tmp_idx);
+ }
+
+ list_del(&entry->list);
+ kfree(entry);
+ return err;
+}
+
+static int mv88e6390_tcam_entry_add(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_tcam_entry *entry, u8 idx)
+{
+ int err = 0;
+ int i;
+ u16 val, spv_mask, spv;
+
+ err = mv88e6xxx_tcam_read_page(chip, 2, idx);
+ if (err)
+ return err;
+ if (entry->action.dpv_mode != 0) {
+ val = MV88E6XXX_TCAM_ING_ACT3_SF |
+ (entry->action.dpv & MV88E6XXX_TCAM_ING_ACT3_DPV_MASK);
+
+ err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_ING_ACT3, val);
+ if (err)
+ return err;
+
+ val = entry->action.dpv_mode << 14;
+ err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_ING_ACT5, val);
+ if (err)
+ return err;
+ }
+ err = mv88e6xxx_tcam_load_page(chip, 2, idx);
+ if (err)
+ return err;
+
+ err = mv88e6xxx_tcam_read_page(chip, 1, idx);
+ if (err)
+ return err;
+
+ for (i = PAGE0_MATCH_SIZE;
+ i < PAGE0_MATCH_SIZE + PAGE1_MATCH_SIZE; i++) {
+ if (entry->key.frame_mask[i]) {
+ val = entry->key.frame_mask[i] << 8 |
+ entry->key.frame_data[i];
+
+ err = mv88e6xxx_tcam_write(chip,
+ i - PAGE0_MATCH_SIZE + 2,
+ val);
+ if (err)
+ return err;
+ }
+ }
+ err = mv88e6xxx_tcam_load_page(chip, 1, idx);
+ if (err)
+ return err;
+
+ err = mv88e6xxx_tcam_read_page(chip, 0, idx);
+ if (err)
+ return err;
+
+ for (i = 0; i < PAGE0_MATCH_SIZE; i++) {
+ if (entry->key.frame_mask[i]) {
+ val = entry->key.frame_mask[i] << 8 |
+ entry->key.frame_data[i];
+
+ err = mv88e6xxx_tcam_write(chip, i + 6, val);
+ if (err)
+ return err;
+ }
+ }
+
+ spv_mask = entry->key.spv_mask & mv88e6xxx_port_mask(chip);
+ spv = entry->key.spv & mv88e6xxx_port_mask(chip);
+ /* frame type mask bits must be set for a valid entry */
+ val = MV88E6XXX_TCAM_KEYS1_FT_MASK |
+ (spv_mask & MV88E6XXX_TCAM_KEYS1_SPV_MASK_MASK) |
+ ((spv >> 8) & MV88E6XXX_TCAM_KEYS1_SPV_MASK);
+ err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_KEYS1, val);
+ if (err)
+ return err;
+
+ val = ((spv_mask << 8) & MV88E6XXX_TCAM_KEYS2_SPV_MASK_MASK) |
+ (spv & MV88E6XXX_TCAM_KEYS2_SPV_MASK);
+ err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_KEYS2, val);
+ if (err)
+ return err;
+
+ err = mv88e6xxx_tcam_load_page(chip, 0, idx);
+ if (err)
+ return err;
+
+ entry->hw_idx = idx;
+ return 0;
+}
+
+static int mv88e6393_tcam_entry_add(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_tcam_entry *entry, u8 idx)
+{
+ int err;
+
+ /* select block 0 port 0, then adding an entry is the same as 6390 as
+ * other blocks aren't used at the moment
+ */
+ err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_EXTENSION, 0x00);
+ if (err)
+ return err;
+
+ return mv88e6390_tcam_entry_add(chip, entry, idx);
+}
+
+const struct mv88e6xxx_tcam_ops mv88e6390_tcam_ops = {
+ .entry_add = mv88e6390_tcam_entry_add,
+ .flush_tcam = mv88e6xxx_tcam_flush_all,
+};
+
+const struct mv88e6xxx_tcam_ops mv88e6393_tcam_ops = {
+ .entry_add = mv88e6393_tcam_entry_add,
+ .flush_tcam = mv88e6xxx_tcam_flush_all,
+};
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/*
+ * Copyright (c) 2026 Luminex Network Intelligence
+ */
+#ifndef _MV88E6XXX_TCAM_H_
+#define _MV88E6XXX_TCAM_H_
+
+#define PAGE0_MATCH_SIZE 22
+#define PAGE1_MATCH_SIZE 26
+
+#define DPV_MODE_NOP 0x0
+#define DPV_MODE_AND 0x1
+#define DPV_MODE_OR 0x2
+#define DPV_MODE_REPLACE 0x3
+
+int mv88e6xxx_tcam_entry_add(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_tcam_entry *entry);
+int mv88e6xxx_tcam_entry_del(struct mv88e6xxx_chip *chip,
+ struct mv88e6xxx_tcam_entry *entry);
+struct mv88e6xxx_tcam_entry *
+mv88e6xxx_tcam_entry_find(struct mv88e6xxx_chip *chip, unsigned long cookie);
+#define mv88e6xxx_tcam_match_set(key, _offset, data, mask) \
+ do { \
+ typeof(_offset) (offset) = (_offset); \
+ BUILD_BUG_ON((offset) + sizeof((data)) > TCAM_MATCH_SIZE); \
+ __mv88e6xxx_tcam_match_set(key, offset, sizeof(data), \
+ (u8 *)&(data), (u8 *)&(mask)); \
+ } while (0)
+
+static inline void __mv88e6xxx_tcam_match_set(struct mv88e6xxx_tcam_key *key,
+ unsigned int offset, size_t size,
+ u8 *data, u8 *mask)
+{
+ memcpy(&key->frame_data[offset], data, size);
+ memcpy(&key->frame_mask[offset], mask, size);
+}
+
+extern const struct mv88e6xxx_tcam_ops mv88e6390_tcam_ops;
+extern const struct mv88e6xxx_tcam_ops mv88e6393_tcam_ops;
+#endif
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Marvell 88E6xxx Switch flower support
+ *
+ * Copyright (c) 2026 Luminex Network Intelligence
+ */
+
+#include "chip.h"
+#include "tcflower.h"
+#include "tcam.h"
+
+#define MV88E6XXX_ETHTYPE_OFFSET 16
+#define MV88E6XXX_IP_PROTO_OFFSET 27
+#define MV88E6XXX_IPV4_SRC_OFFSET 30
+#define MV88E6XXX_IPV4_DST_OFFSET 34
+
+static int mv88e6xxx_flower_parse_key(struct mv88e6xxx_chip *chip,
+ struct netlink_ext_ack *extack,
+ struct flow_cls_offload *cls,
+ struct mv88e6xxx_tcam_key *key)
+{
+ struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ struct flow_dissector *dissector = rule->match.dissector;
+ u16 addr_type = 0;
+
+ if (dissector->used_keys &
+ ~(BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) |
+ BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS))) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Unsupported keys used");
+ return -EOPNOTSUPP;
+ }
+
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
+ struct flow_match_control match;
+
+ flow_rule_match_control(rule, &match);
+ addr_type = match.key->addr_type;
+
+ if (flow_rule_has_control_flags(match.mask->flags,
+ cls->common.extack))
+ return -EOPNOTSUPP;
+ }
+ if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
+ struct flow_match_basic match;
+
+ flow_rule_match_basic(rule, &match);
+ mv88e6xxx_tcam_match_set(key, MV88E6XXX_ETHTYPE_OFFSET,
+ match.key->n_proto,
+ match.mask->n_proto);
+ mv88e6xxx_tcam_match_set(key, MV88E6XXX_IP_PROTO_OFFSET,
+ match.key->ip_proto,
+ match.mask->ip_proto);
+ }
+
+ if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
+ struct flow_match_ipv4_addrs match;
+
+ flow_rule_match_ipv4_addrs(cls->rule, &match);
+ mv88e6xxx_tcam_match_set(key, MV88E6XXX_IPV4_SRC_OFFSET,
+ match.key->src,
+ match.mask->src);
+ mv88e6xxx_tcam_match_set(key, MV88E6XXX_IPV4_DST_OFFSET,
+ match.key->dst,
+ match.mask->dst);
+ }
+
+ return 0;
+}
+
+int mv88e6xxx_cls_flower_add(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress)
+{
+ struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
+ struct netlink_ext_ack *extack = cls->common.extack;
+ struct mv88e6xxx_chip *chip = ds->priv;
+ struct mv88e6xxx_tcam_key key = { 0 };
+ const struct flow_action_entry *act;
+ unsigned long cookie = cls->cookie;
+ struct mv88e6xxx_tcam_entry *entry;
+ int err, i;
+
+ if (!mv88e6xxx_has_tcam(chip)) {
+ NL_SET_ERR_MSG_MOD(extack, "hardware offload not supported");
+ return -EOPNOTSUPP;
+ }
+
+ err = mv88e6xxx_flower_parse_key(chip, extack, cls, &key);
+ if (err)
+ return err;
+
+ mv88e6xxx_reg_lock(chip);
+ entry = mv88e6xxx_tcam_entry_find(chip, cookie);
+ if (entry) {
+ err = -EEXIST;
+ goto err_unlock;
+ }
+
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry) {
+ err = -ENOMEM;
+ goto err_unlock;
+ }
+
+ entry->cookie = cookie;
+ entry->prio = cls->common.prio;
+ entry->key = key;
+
+ flow_action_for_each(i, act, &rule->action) {
+ switch (act->id) {
+ case FLOW_ACTION_TRAP: {
+ int cpu = dsa_upstream_port(ds, port);
+
+ entry->action.dpv_mode = DPV_MODE_REPLACE;
+ entry->action.dpv = BIT(cpu);
+ break;
+ }
+ default:
+ NL_SET_ERR_MSG_MOD(extack, "action not supported");
+ err = -EOPNOTSUPP;
+ goto err_free_entry;
+ }
+ }
+
+ entry->key.spv = BIT(port);
+ entry->key.spv_mask = mv88e6xxx_port_mask(chip);
+
+ err = mv88e6xxx_tcam_entry_add(chip, entry);
+ if (err)
+ goto err_free_entry;
+
+ mv88e6xxx_reg_unlock(chip);
+ return 0;
+
+err_free_entry:
+ kfree(entry);
+err_unlock:
+ mv88e6xxx_reg_unlock(chip);
+ return err;
+}
+
+int mv88e6xxx_cls_flower_del(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress)
+{
+ struct mv88e6xxx_chip *chip = ds->priv;
+ struct mv88e6xxx_tcam_entry *entry;
+ int err = 0;
+
+ mv88e6xxx_reg_lock(chip);
+ entry = mv88e6xxx_tcam_entry_find(chip, cls->cookie);
+
+ if (entry)
+ err = mv88e6xxx_tcam_entry_del(chip, entry);
+ mv88e6xxx_reg_unlock(chip);
+ return err;
+}
+
+void mv88e6xxx_flower_teardown(struct mv88e6xxx_chip *chip)
+{
+ struct mv88e6xxx_tcam_entry *pos, *n;
+
+ list_for_each_entry_safe(pos, n, &chip->tcam.entries, list) {
+ list_del(&pos->list);
+ kfree(pos);
+ }
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/*
+ * Copyright (c) 2026 Luminex Network Intelligence
+ */
+#ifndef _MV88E6XXX_TCFLOWER_H_
+#define _MV88E6XXX_TCFLOWER_H_
+
+int mv88e6xxx_cls_flower_add(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress);
+int mv88e6xxx_cls_flower_del(struct dsa_switch *ds, int port,
+ struct flow_cls_offload *cls, bool ingress);
+void mv88e6xxx_flower_teardown(struct mv88e6xxx_chip *chip);
+#endif