Contributors: 9
Author Tokens Token Proportion Commits Commit Proportion
Cedric Jehasse 1120 76.76% 1 4.35%
Vivien Didelot 273 18.71% 10 43.48%
Andrew Lunn 34 2.33% 6 26.09%
Lennert Buytenhek 13 0.89% 1 4.35%
Pavana Sharma 8 0.55% 1 4.35%
Guenter Roeck 7 0.48% 1 4.35%
Brandon Streiff 2 0.14% 1 4.35%
Thomas Gleixner 1 0.07% 1 4.35%
Marek Behún 1 0.07% 1 4.35%
Total 1459 23


// 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,
};