// SPDX-License-Identifier: GPL-2.0
/* Texas Instruments ICSSG Ethernet Driver
 *
 * Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
 *
 */

#include <linux/etherdevice.h>
#include <linux/types.h>
#include <linux/regmap.h>

#include "icssg_prueth.h"

#define ICSSG_NUM_CLASSIFIERS	16
#define ICSSG_NUM_FT1_SLOTS	8
#define ICSSG_NUM_FT3_SLOTS	16

#define ICSSG_NUM_CLASSIFIERS_IN_USE	5

/* Filter 1 - FT1 */
#define FT1_NUM_SLOTS	8
#define FT1_SLOT_SIZE	0x10	/* bytes */

/* offsets from FT1 slot base i.e. slot 1 start */
#define FT1_DA0		0x0
#define FT1_DA1		0x4
#define FT1_DA0_MASK	0x8
#define FT1_DA1_MASK	0xc

#define FT1_N_REG(slize, n, reg)	\
	(offs[slice].ft1_slot_base + FT1_SLOT_SIZE * (n) + (reg))

#define FT1_LEN_MASK		GENMASK(19, 16)
#define FT1_LEN_SHIFT		16
#define FT1_LEN(len)		(((len) << FT1_LEN_SHIFT) & FT1_LEN_MASK)
#define FT1_START_MASK		GENMASK(14, 0)
#define FT1_START(start)	((start) & FT1_START_MASK)
#define FT1_MATCH_SLOT(n)	(GENMASK(23, 16) & (BIT(n) << 16))

/* FT1 config type */
enum ft1_cfg_type {
	FT1_CFG_TYPE_DISABLED = 0,
	FT1_CFG_TYPE_EQ,
	FT1_CFG_TYPE_GT,
	FT1_CFG_TYPE_LT,
};

#define FT1_CFG_SHIFT(n)	(2 * (n))
#define FT1_CFG_MASK(n)		(0x3 << FT1_CFG_SHIFT((n)))

/* Filter 3 -  FT3 */
#define FT3_NUM_SLOTS	16
#define FT3_SLOT_SIZE	0x20	/* bytes */

/* offsets from FT3 slot n's base */
#define FT3_START		0
#define FT3_START_AUTO		0x4
#define FT3_START_OFFSET	0x8
#define FT3_JUMP_OFFSET		0xc
#define FT3_LEN			0x10
#define FT3_CFG			0x14
#define FT3_T			0x18
#define FT3_T_MASK		0x1c

#define FT3_N_REG(slize, n, reg)	\
	(offs[slice].ft3_slot_base + FT3_SLOT_SIZE * (n) + (reg))

/* offsets from rx_class n's base */
#define RX_CLASS_AND_EN		0
#define RX_CLASS_OR_EN		0x4
#define RX_CLASS_NUM_SLOTS	16
#define RX_CLASS_EN_SIZE	0x8	/* bytes */

#define RX_CLASS_N_REG(slice, n, reg)	\
	(offs[slice].rx_class_base + RX_CLASS_EN_SIZE * (n) + (reg))

/* RX Class Gates */
#define RX_CLASS_GATES_SIZE	0x4	/* bytes */

#define RX_CLASS_GATES_N_REG(slice, n)	\
	(offs[slice].rx_class_gates_base + RX_CLASS_GATES_SIZE * (n))

#define RX_CLASS_GATES_ALLOW_MASK	BIT(6)
#define RX_CLASS_GATES_RAW_MASK		BIT(5)
#define RX_CLASS_GATES_PHASE_MASK	BIT(4)

/* RX Class traffic data matching bits */
#define RX_CLASS_FT_UC				BIT(31)
#define RX_CLASS_FT_MC			BIT(30)
#define RX_CLASS_FT_BC			BIT(29)
#define RX_CLASS_FT_FW			BIT(28)
#define RX_CLASS_FT_RCV			BIT(27)
#define RX_CLASS_FT_VLAN		BIT(26)
#define RX_CLASS_FT_DA_P		BIT(25)
#define RX_CLASS_FT_DA_I		BIT(24)
#define RX_CLASS_FT_FT1_MATCH_MASK	GENMASK(23, 16)
#define RX_CLASS_FT_FT1_MATCH_SHIFT	16
#define RX_CLASS_FT_FT3_MATCH_MASK	GENMASK(15, 0)
#define RX_CLASS_FT_FT3_MATCH_SHIFT	0

#define RX_CLASS_FT_FT1_MATCH(slot)	\
	((BIT(slot) << RX_CLASS_FT_FT1_MATCH_SHIFT) & \
	RX_CLASS_FT_FT1_MATCH_MASK)

/* RX class type */
enum rx_class_sel_type {
	RX_CLASS_SEL_TYPE_OR = 0,
	RX_CLASS_SEL_TYPE_AND = 1,
	RX_CLASS_SEL_TYPE_OR_AND_AND = 2,
	RX_CLASS_SEL_TYPE_OR_OR_AND = 3,
};

#define FT1_CFG_SHIFT(n)	(2 * (n))
#define FT1_CFG_MASK(n)		(0x3 << FT1_CFG_SHIFT((n)))

#define RX_CLASS_SEL_SHIFT(n)	(2 * (n))
#define RX_CLASS_SEL_MASK(n)	(0x3 << RX_CLASS_SEL_SHIFT((n)))

#define ICSSG_CFG_OFFSET	0
#define MAC_INTERFACE_0		0x18
#define MAC_INTERFACE_1		0x1c

#define ICSSG_CFG_RX_L2_G_EN	BIT(2)

/* These are register offsets per PRU */
struct miig_rt_offsets {
	u32 mac0;
	u32 mac1;
	u32 ft1_start_len;
	u32 ft1_cfg;
	u32 ft1_slot_base;
	u32 ft3_slot_base;
	u32 ft3_p_base;
	u32 ft_rx_ptr;
	u32 rx_class_base;
	u32 rx_class_cfg1;
	u32 rx_class_cfg2;
	u32 rx_class_gates_base;
	u32 rx_green;
	u32 rx_rate_cfg_base;
	u32 rx_rate_src_sel0;
	u32 rx_rate_src_sel1;
	u32 tx_rate_cfg_base;
	u32 stat_base;
	u32 tx_hsr_tag;
	u32 tx_hsr_seq;
	u32 tx_vlan_type;
	u32 tx_vlan_ins;
};

/* These are the offset values for miig_rt_offsets registers */
static const struct miig_rt_offsets offs[] = {
	/* PRU0 */
	{
		0x8,
		0xc,
		0x80,
		0x84,
		0x88,
		0x108,
		0x308,
		0x408,
		0x40c,
		0x48c,
		0x490,
		0x494,
		0x4d4,
		0x4e4,
		0x504,
		0x508,
		0x50c,
		0x54c,
		0x63c,
		0x640,
		0x644,
		0x648,
	},
	/* PRU1 */
	{
		0x10,
		0x14,
		0x64c,
		0x650,
		0x654,
		0x6d4,
		0x8d4,
		0x9d4,
		0x9d8,
		0xa58,
		0xa5c,
		0xa60,
		0xaa0,
		0xab0,
		0xad0,
		0xad4,
		0xad8,
		0xb18,
		0xc08,
		0xc0c,
		0xc10,
		0xc14,
	},
};

static void rx_class_ft1_set_start_len(struct regmap *miig_rt, int slice,
				       u16 start, u8 len)
{
	u32 offset, val;

	offset = offs[slice].ft1_start_len;
	val = FT1_LEN(len) | FT1_START(start);
	regmap_write(miig_rt, offset, val);
}

static void rx_class_ft1_set_da(struct regmap *miig_rt, int slice,
				int n, const u8 *addr)
{
	u32 offset;

	offset = FT1_N_REG(slice, n, FT1_DA0);
	regmap_write(miig_rt, offset, (u32)(addr[0] | addr[1] << 8 |
		     addr[2] << 16 | addr[3] << 24));
	offset = FT1_N_REG(slice, n, FT1_DA1);
	regmap_write(miig_rt, offset, (u32)(addr[4] | addr[5] << 8));
}

static void rx_class_ft1_set_da_mask(struct regmap *miig_rt, int slice,
				     int n, const u8 *addr)
{
	u32 offset;

	offset = FT1_N_REG(slice, n, FT1_DA0_MASK);
	regmap_write(miig_rt, offset, (u32)(addr[0] | addr[1] << 8 |
		     addr[2] << 16 | addr[3] << 24));
	offset = FT1_N_REG(slice, n, FT1_DA1_MASK);
	regmap_write(miig_rt, offset, (u32)(addr[4] | addr[5] << 8));
}

static void rx_class_ft1_cfg_set_type(struct regmap *miig_rt, int slice, int n,
				      enum ft1_cfg_type type)
{
	u32 offset;

	offset = offs[slice].ft1_cfg;
	regmap_update_bits(miig_rt, offset, FT1_CFG_MASK(n),
			   type << FT1_CFG_SHIFT(n));
}

static void rx_class_sel_set_type(struct regmap *miig_rt, int slice, int n,
				  enum rx_class_sel_type type)
{
	u32 offset;

	offset = offs[slice].rx_class_cfg1;
	regmap_update_bits(miig_rt, offset, RX_CLASS_SEL_MASK(n),
			   type << RX_CLASS_SEL_SHIFT(n));
}

static void rx_class_set_and(struct regmap *miig_rt, int slice, int n,
			     u32 data)
{
	u32 offset;

	offset = RX_CLASS_N_REG(slice, n, RX_CLASS_AND_EN);
	regmap_write(miig_rt, offset, data);
}

static void rx_class_set_or(struct regmap *miig_rt, int slice, int n,
			    u32 data)
{
	u32 offset;

	offset = RX_CLASS_N_REG(slice, n, RX_CLASS_OR_EN);
	regmap_write(miig_rt, offset, data);
}

static u32 rx_class_get_or(struct regmap *miig_rt, int slice, int n)
{
	u32 offset, val;

	offset = RX_CLASS_N_REG(slice, n, RX_CLASS_OR_EN);
	regmap_read(miig_rt, offset, &val);

	return val;
}

void icssg_class_set_host_mac_addr(struct regmap *miig_rt, const u8 *mac)
{
	regmap_write(miig_rt, MAC_INTERFACE_0, (u32)(mac[0] | mac[1] << 8 |
		     mac[2] << 16 | mac[3] << 24));
	regmap_write(miig_rt, MAC_INTERFACE_1, (u32)(mac[4] | mac[5] << 8));
}

void icssg_class_set_mac_addr(struct regmap *miig_rt, int slice, u8 *mac)
{
	regmap_write(miig_rt, offs[slice].mac0, (u32)(mac[0] | mac[1] << 8 |
		     mac[2] << 16 | mac[3] << 24));
	regmap_write(miig_rt, offs[slice].mac1, (u32)(mac[4] | mac[5] << 8));
}
EXPORT_SYMBOL_GPL(icssg_class_set_mac_addr);

static void icssg_class_ft1_add_mcast(struct regmap *miig_rt, int slice,
				      int slot, const u8 *addr, const u8 *mask)
{
	u32 val;
	int i;

	WARN(slot >= FT1_NUM_SLOTS, "invalid slot: %d\n", slot);

	rx_class_ft1_set_da(miig_rt, slice, slot, addr);
	rx_class_ft1_set_da_mask(miig_rt, slice, slot, mask);
	rx_class_ft1_cfg_set_type(miig_rt, slice, slot, FT1_CFG_TYPE_EQ);

	/* Enable the FT1 slot in OR enable for all classifiers */
	for (i = 0; i < ICSSG_NUM_CLASSIFIERS_IN_USE; i++) {
		val = rx_class_get_or(miig_rt, slice, i);
		val |= RX_CLASS_FT_FT1_MATCH(slot);
		rx_class_set_or(miig_rt, slice, i, val);
	}
}

/* disable all RX traffic */
void icssg_class_disable(struct regmap *miig_rt, int slice)
{
	u32 data, offset;
	int n;

	/* Enable RX_L2_G */
	regmap_update_bits(miig_rt, ICSSG_CFG_OFFSET, ICSSG_CFG_RX_L2_G_EN,
			   ICSSG_CFG_RX_L2_G_EN);

	for (n = 0; n < ICSSG_NUM_CLASSIFIERS; n++) {
		/* AND_EN = 0 */
		rx_class_set_and(miig_rt, slice, n, 0);
		/* OR_EN = 0 */
		rx_class_set_or(miig_rt, slice, n, 0);

		/* set CFG1 to OR */
		rx_class_sel_set_type(miig_rt, slice, n, RX_CLASS_SEL_TYPE_OR);

		/* configure gate */
		offset = RX_CLASS_GATES_N_REG(slice, n);
		regmap_read(miig_rt, offset, &data);
		/* clear class_raw so we go through filters */
		data &= ~RX_CLASS_GATES_RAW_MASK;
		/* set allow and phase mask */
		data |= RX_CLASS_GATES_ALLOW_MASK | RX_CLASS_GATES_PHASE_MASK;
		regmap_write(miig_rt, offset, data);
	}

	/* FT1 Disabled */
	for (n = 0; n < ICSSG_NUM_FT1_SLOTS; n++) {
		const u8 addr[] = { 0, 0, 0, 0, 0, 0, };

		rx_class_ft1_cfg_set_type(miig_rt, slice, n,
					  FT1_CFG_TYPE_DISABLED);
		rx_class_ft1_set_da(miig_rt, slice, n, addr);
		rx_class_ft1_set_da_mask(miig_rt, slice, n, addr);
	}

	/* clear CFG2 */
	regmap_write(miig_rt, offs[slice].rx_class_cfg2, 0);
}
EXPORT_SYMBOL_GPL(icssg_class_disable);

void icssg_class_default(struct regmap *miig_rt, int slice, bool allmulti,
			 bool is_sr1)
{
	int num_classifiers = is_sr1 ? ICSSG_NUM_CLASSIFIERS_IN_USE : 1;
	u32 data;
	int n;

	/* defaults */
	icssg_class_disable(miig_rt, slice);

	/* Setup Classifier */
	for (n = 0; n < num_classifiers; n++) {
		/* match on Broadcast or MAC_PRU address */
		data = RX_CLASS_FT_BC | RX_CLASS_FT_DA_P;

		/* multicast */
		if (allmulti)
			data |= RX_CLASS_FT_MC;

		rx_class_set_or(miig_rt, slice, n, data);

		/* set CFG1 for OR_OR_AND for classifier */
		rx_class_sel_set_type(miig_rt, slice, n,
				      RX_CLASS_SEL_TYPE_OR_OR_AND);
	}

	/* clear CFG2 */
	regmap_write(miig_rt, offs[slice].rx_class_cfg2, 0);
}
EXPORT_SYMBOL_GPL(icssg_class_default);

void icssg_class_promiscuous_sr1(struct regmap *miig_rt, int slice)
{
	u32 data, offset;
	int n;

	/* defaults */
	icssg_class_disable(miig_rt, slice);

	/* Setup Classifier */
	for (n = 0; n < ICSSG_NUM_CLASSIFIERS_IN_USE; n++) {
		/* set RAW_MASK to bypass filters */
		offset = RX_CLASS_GATES_N_REG(slice, n);
		regmap_read(miig_rt, offset, &data);
		data |= RX_CLASS_GATES_RAW_MASK;
		regmap_write(miig_rt, offset, data);
	}
}
EXPORT_SYMBOL_GPL(icssg_class_promiscuous_sr1);

void icssg_class_add_mcast_sr1(struct regmap *miig_rt, int slice,
			       struct net_device *ndev)
{
	u8 mask_addr[6] = { 0, 0, 0, 0, 0, 0xff };
	struct netdev_hw_addr *ha;
	int slot = 2;

	rx_class_ft1_set_start_len(miig_rt, slice, 0, 6);
	/* reserve first 2 slots for
	 *	1) 01-80-C2-00-00-XX Known Service Ethernet Multicast addresses
	 *	2) 01-00-5e-00-00-XX Local Network Control Block
	 *			      (224.0.0.0 - 224.0.0.255  (224.0.0/24))
	 */
	icssg_class_ft1_add_mcast(miig_rt, slice, 0,
				  eth_reserved_addr_base, mask_addr);
	icssg_class_ft1_add_mcast(miig_rt, slice, 1,
				  eth_ipv4_mcast_addr_base, mask_addr);
	mask_addr[5] = 0;
	netdev_for_each_mc_addr(ha, ndev) {
		/* skip addresses matching reserved slots */
		if (!memcmp(eth_reserved_addr_base, ha->addr, 5) ||
		    !memcmp(eth_ipv4_mcast_addr_base, ha->addr, 5)) {
			netdev_dbg(ndev, "mcast skip %pM\n", ha->addr);
			continue;
		}

		if (slot >= FT1_NUM_SLOTS) {
			netdev_dbg(ndev,
				   "can't add more than %d MC addresses, enabling allmulti\n",
				   FT1_NUM_SLOTS);
			icssg_class_default(miig_rt, slice, 1, true);
			break;
		}

		netdev_dbg(ndev, "mcast add %pM\n", ha->addr);
		icssg_class_ft1_add_mcast(miig_rt, slice, slot,
					  ha->addr, mask_addr);
		slot++;
	}
}
EXPORT_SYMBOL_GPL(icssg_class_add_mcast_sr1);

/* required for SAV check */
void icssg_ft1_set_mac_addr(struct regmap *miig_rt, int slice, u8 *mac_addr)
{
	const u8 mask_addr[] = { 0, 0, 0, 0, 0, 0, };

	rx_class_ft1_set_start_len(miig_rt, slice, ETH_ALEN, ETH_ALEN);
	rx_class_ft1_set_da(miig_rt, slice, 0, mac_addr);
	rx_class_ft1_set_da_mask(miig_rt, slice, 0, mask_addr);
	rx_class_ft1_cfg_set_type(miig_rt, slice, 0, FT1_CFG_TYPE_EQ);
}
EXPORT_SYMBOL_GPL(icssg_ft1_set_mac_addr);
