/*
 * Copyright 2022-2023 NXP
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT nxp_s32_netc_psi

#define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(nxp_s32_eth_psi);

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/mbox.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/net/ethernet.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/phy.h>
#include <ethernet/eth_stats.h>

#include <soc.h>
#include <Netc_Eth_Ip.h>
#include <Netc_Eth_Ip_Irq.h>
#include <Netc_EthSwt_Ip.h>

#include "eth.h"
#include "eth_nxp_s32_netc_priv.h"

#define TX_RING_IDX	1
#define RX_RING_IDX	0

static void nxp_s32_eth_configure_port(uint8_t port_idx, enum phy_link_speed speed)
{
	EthTrcv_BaudRateType baudrate;
	Netc_EthSwt_Ip_PortDuplexType duplex;
	Std_ReturnType status;

	(void)Netc_EthSwt_Ip_SetPortMode(NETC_SWITCH_IDX, port_idx, false);

	baudrate = PHY_TO_NETC_SPEED(speed);
	status = Netc_EthSwt_Ip_SetPortSpeed(NETC_SWITCH_IDX, port_idx, baudrate);
	if (status != E_OK) {
		LOG_ERR("Failed to set port %d speed: %d", port_idx, status);
		return;
	}

	duplex = PHY_TO_NETC_DUPLEX_MODE(speed);
	status = Netc_EthSwt_Ip_SetPortMacLayerDuplexMode(NETC_SWITCH_IDX, port_idx, duplex);
	if (status != E_OK) {
		LOG_ERR("Failed to set port %d duplex mode: %d", port_idx, status);
		return;
	}

	(void)Netc_EthSwt_Ip_SetPortMode(NETC_SWITCH_IDX, port_idx, true);
}

static void phy_link_state_changed(const struct device *pdev,
				   struct phy_link_state *state,
				   void *user_data)
{
	const struct device *dev = (struct device *)user_data;
	const struct nxp_s32_eth_config *cfg = dev->config;
	const struct nxp_s32_eth_data *ctx = dev->data;

	ARG_UNUSED(pdev);

	if (state->is_up) {
		LOG_DBG("Link up");
		nxp_s32_eth_configure_port(cfg->port_idx, state->speed);
		net_eth_carrier_on(ctx->iface);
	} else {
		LOG_DBG("Link down");
		net_eth_carrier_off(ctx->iface);
	}
}

/* Configure ETHx_EXT_RX_CLK @ 125 MHz as source of ETH_x_RGMII_RX_CLK */
static int nxp_s32_eth_configure_cgm(uint8_t port_idx)
{
	uint32_t tout = 0xFFFFFFFF;

	if (port_idx == 0) {
		IP_MC_CGM_1->MUX_7_CSC = (IP_MC_CGM_1->MUX_7_CSC & ~MC_CGM_MUX_7_CSC_SELCTL_MASK)
					| MC_CGM_MUX_7_CSC_SELCTL(NETC_ETH_0_RX_CLK_IDX);
		IP_MC_CGM_1->MUX_7_CSC = (IP_MC_CGM_1->MUX_7_CSC & ~MC_CGM_MUX_7_CSC_CLK_SW_MASK)
					| MC_CGM_MUX_7_CSC_CLK_SW(1);

		while (((IP_MC_CGM_1->MUX_7_CSS & MC_CGM_MUX_7_CSS_CLK_SW_MASK) == 0)
				&& (tout > 0)) {
			tout--;
		}
		while (((IP_MC_CGM_1->MUX_7_CSS & MC_CGM_MUX_7_CSS_SWIP_MASK) != 0)
				&& (tout > 0)) {
			tout--;
		}
		while (((IP_MC_CGM_1->MUX_7_CSS & MC_CGM_MUX_7_CSS_SWTRG_MASK)
				>> MC_CGM_MUX_7_CSS_SWTRG_SHIFT != 1) && (tout > 0)) {
			tout--;
		}

		__ASSERT_NO_MSG(((IP_MC_CGM_1->MUX_7_CSS & MC_CGM_MUX_7_CSS_SELSTAT_MASK)
				 >> MC_CGM_MUX_7_CSS_SELSTAT_SHIFT) == NETC_ETH_0_RX_CLK_IDX);

	} else if (port_idx == 1) {
		IP_MC_CGM_1->MUX_9_CSC = (IP_MC_CGM_1->MUX_9_CSC & ~MC_CGM_MUX_9_CSC_SELCTL_MASK)
					| MC_CGM_MUX_9_CSC_SELCTL(NETC_ETH_1_RX_CLK_IDX);
		IP_MC_CGM_1->MUX_9_CSC = (IP_MC_CGM_1->MUX_9_CSC & ~MC_CGM_MUX_9_CSC_CLK_SW_MASK)
					| MC_CGM_MUX_9_CSC_CLK_SW(1);

		while (((IP_MC_CGM_1->MUX_9_CSS & MC_CGM_MUX_9_CSS_CLK_SW_MASK) == 0)
				&& (tout > 0)) {
			tout--;
		}
		while (((IP_MC_CGM_1->MUX_9_CSS & MC_CGM_MUX_9_CSS_SWIP_MASK) != 0)
				&& (tout > 0)) {
			tout--;
		}
		while (((IP_MC_CGM_1->MUX_9_CSS & MC_CGM_MUX_9_CSS_SWTRG_MASK)
				>> MC_CGM_MUX_9_CSS_SWTRG_SHIFT != 1) && (tout > 0)) {
			tout--;
		}

		__ASSERT_NO_MSG(((IP_MC_CGM_1->MUX_9_CSS & MC_CGM_MUX_9_CSS_SELSTAT_MASK)
				 >> MC_CGM_MUX_9_CSS_SELSTAT_SHIFT) == NETC_ETH_1_RX_CLK_IDX);
	} else {
		return -EINVAL;
	}

	return 0;
}

static int nxp_s32_eth_initialize(const struct device *dev)
{
	const struct nxp_s32_eth_config *cfg = dev->config;
	int err;

	err = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT);
	if (err != 0) {
		return err;
	}

	err = nxp_s32_eth_configure_cgm(cfg->port_idx);
	if (err != 0) {
		LOG_ERR("Failed to configure NETC Switch CGM");
		return -EIO;
	}

	return nxp_s32_eth_initialize_common(dev);
}

static void nxp_s32_eth_iface_init(struct net_if *iface)
{
	const struct device *dev = net_if_get_device(iface);
	struct nxp_s32_eth_data *ctx = dev->data;
	const struct nxp_s32_eth_config *cfg = dev->config;
	const struct nxp_s32_eth_msix *msix;

	/*
	 * For VLAN, this value is only used to get the correct L2 driver.
	 * The iface pointer in context should contain the main interface
	 * if the VLANs are enabled.
	 */
	if (ctx->iface == NULL) {
		ctx->iface = iface;
	}

	Netc_Eth_Ip_SetMacAddr(cfg->si_idx, (const uint8_t *)ctx->mac_addr);
	net_if_set_link_addr(iface, ctx->mac_addr, sizeof(ctx->mac_addr), NET_LINK_ETHERNET);

	LOG_INF("SI%d MAC: %02x:%02x:%02x:%02x:%02x:%02x", cfg->si_idx,
		ctx->mac_addr[0], ctx->mac_addr[1], ctx->mac_addr[2],
		ctx->mac_addr[3], ctx->mac_addr[4], ctx->mac_addr[5]);

	ethernet_init(iface);

	/*
	 * PSI controls the PHY. If PHY is configured either as fixed
	 * link or autoneg, the callback is executed at least once
	 * immediately after setting it.
	 */
	if (!device_is_ready(cfg->phy_dev)) {
		LOG_ERR("PHY device (%p) is not ready, cannot init iface",
			cfg->phy_dev);
		return;
	}
	phy_link_callback_set(cfg->phy_dev, &phy_link_state_changed, (void *)dev);

	/* Do not start the interface until PHY link is up */
	net_if_carrier_off(iface);

	for (int i = 0; i < NETC_MSIX_EVENTS_COUNT; i++) {
		msix = &cfg->msix[i];
		if (mbox_is_ready_dt(&msix->mbox_spec)) {
			if (mbox_set_enabled_dt(&msix->mbox_spec, true)) {
				LOG_ERR("Failed to enable MRU channel %u",
					msix->mbox_spec.channel_id);
			}
		}
	}
}

static const struct ethernet_api nxp_s32_eth_api = {
	.iface_api.init = nxp_s32_eth_iface_init,
	.get_capabilities = nxp_s32_eth_get_capabilities,
	.set_config = nxp_s32_eth_set_config,
	.send = nxp_s32_eth_tx
};

BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(nxp_s32_netc_psi) == 1, "Only one PSI enabled supported");

#define NETC_VSI_GENERAL_CFG(node, prop, idx)					\
	[DT_PROP_BY_IDX(node, prop, idx)] = {					\
		.siId = DT_PROP_BY_IDX(node, prop, idx),			\
		.enableSi = true,						\
		.NumberOfRxBDR = 1,						\
		.NumberOfTxBDR = 1,						\
		.SIVlanControl = (NETC_F3_PSICFGR0_SIVC_CVLAN_BIT		\
				| NETC_F3_PSICFGR0_SIVC_SVLAN_BIT),		\
		.changeMACAllowed = true,					\
		.hashFilterUpdateAllowed = true,				\
		IF_ENABLED(CONFIG_NET_PROMISCUOUS_MODE,				\
			(.multicastPromiscuousChangeAllowed = true,))		\
	}

#define NETC_VSI_RX_MSG_BUF(node, prop, idx, n)							\
	BUILD_ASSERT((DT_PROP_BY_IDX(node, prop, idx) > NETC_ETH_IP_PSI_INDEX)			\
		&& (DT_PROP_BY_IDX(node, prop, idx) <= FEATURE_NETC_ETH_NUM_OF_VIRTUAL_CTRLS),	\
		"Invalid VSI index");								\
	static Netc_Eth_Ip_VsiToPsiMsgType							\
	_CONCAT3(nxp_s32_eth##n##_vsi, DT_PROP_BY_IDX(node, prop, idx), _rx_msg_buf)		\
		__aligned(FEATURE_NETC_ETH_VSI_MSG_ALIGNMENT)

#define NETC_VSI_RX_MSG_BUF_ARRAY(node, prop, idx, n)						\
	[DT_PROP_BY_IDX(node, prop, idx) - 1] =							\
		&_CONCAT3(nxp_s32_eth##n##_vsi, DT_PROP_BY_IDX(node, prop, idx), _rx_msg_buf)

#define NETC_SWITCH_PORT_CFG(_, n)						\
	{									\
		.ePort = &nxp_s32_eth##n##_switch_port_egress_cfg,		\
		.iPort = &nxp_s32_eth##n##_switch_port_ingress_cfg,		\
		.EthSwtPortMacLayerPortEnable = true,				\
		.EthSwtPortMacLayerSpeed = ETHTRCV_BAUD_RATE_1000MBIT,		\
		.EthSwtPortMacLayerDuplexMode = NETC_ETHSWT_PORT_FULL_DUPLEX,	\
		.EthSwtPortPhysicalLayerType = NETC_ETHSWT_RGMII_MODE,		\
		.EthSwtPortPruningEnable = true,				\
	}

#define PHY_NODE(n)	DT_INST_PHANDLE(n, phy_handle)
#define INIT_VSIS(n)	DT_INST_NODE_HAS_PROP(n, vsis)

#define NETC_PSI_INSTANCE_DEFINE(n)							\
void nxp_s32_eth_psi##n##_rx_event(uint8_t chan, const uint32_t *buf, uint8_t buf_size)	\
{											\
	ARG_UNUSED(chan);								\
	ARG_UNUSED(buf);								\
	ARG_UNUSED(buf_size);								\
											\
	Netc_Eth_Ip_MSIX_Rx(NETC_SI_NXP_S32_HW_INSTANCE(n));				\
}											\
											\
static void nxp_s32_eth##n##_rx_callback(const uint8_t unused, const uint8_t ring)	\
{											\
	const struct device *dev = DEVICE_DT_INST_GET(n);				\
	const struct nxp_s32_eth_config *cfg = dev->config;				\
	struct nxp_s32_eth_data *ctx = dev->data;					\
											\
	ARG_UNUSED(unused);								\
											\
	if (ring == cfg->rx_ring_idx) {							\
		k_sem_give(&ctx->rx_sem);						\
	}										\
}											\
											\
static Netc_Eth_Ip_StateType nxp_s32_eth##n##_state;					\
static Netc_Eth_Ip_MACFilterHashTableEntryType						\
nxp_s32_eth##n##_mac_filter_hash_table[CONFIG_ETH_NXP_S32_MAC_FILTER_TABLE_SIZE];	\
											\
NETC_TX_RING(n, 0, NETC_MIN_RING_LEN, NETC_MIN_RING_BUF_SIZE);				\
NETC_TX_RING(n, TX_RING_IDX,								\
	CONFIG_ETH_NXP_S32_TX_RING_LEN, CONFIG_ETH_NXP_S32_TX_RING_BUF_SIZE);		\
NETC_RX_RING(n, RX_RING_IDX,								\
	CONFIG_ETH_NXP_S32_RX_RING_LEN, CONFIG_ETH_NXP_S32_RX_RING_BUF_SIZE);		\
											\
static const Netc_Eth_Ip_RxRingConfigType nxp_s32_eth##n##_rxring_cfg[1] = {		\
	{										\
		.RingDesc = nxp_s32_eth##n##_rxring0_desc,				\
		.Buffer = nxp_s32_eth##n##_rxring0_buf,					\
		.ringSize = CONFIG_ETH_NXP_S32_RX_RING_LEN,				\
		.maxRingSize = CONFIG_ETH_NXP_S32_RX_RING_LEN,				\
		.bufferLen = CONFIG_ETH_NXP_S32_TX_RING_BUF_SIZE,			\
		.maxBuffLen = CONFIG_ETH_NXP_S32_TX_RING_BUF_SIZE,			\
		.TimerThreshold = CONFIG_ETH_NXP_S32_RX_IRQ_TIMER_THRESHOLD,		\
		.PacketsThreshold = CONFIG_ETH_NXP_S32_RX_IRQ_PACKET_THRESHOLD,		\
		.Callback = nxp_s32_eth##n##_rx_callback,				\
	}										\
};											\
											\
static const Netc_Eth_Ip_TxRingConfigType nxp_s32_eth##n##_txring_cfg[2] = {		\
	{										\
		.RingDesc = nxp_s32_eth##n##_txring0_desc,				\
		.Buffer = nxp_s32_eth##n##_txring0_buf,					\
		.ringSize = NETC_MIN_RING_LEN,						\
		.maxRingSize = NETC_MIN_RING_LEN,					\
		.bufferLen = NETC_MIN_RING_BUF_SIZE,					\
		.maxBuffLen = NETC_MIN_RING_BUF_SIZE,					\
	},										\
	{										\
		.RingDesc = nxp_s32_eth##n##_txring1_desc,				\
		.Buffer = nxp_s32_eth##n##_txring1_buf,					\
		.ringSize = CONFIG_ETH_NXP_S32_TX_RING_LEN,				\
		.maxRingSize = CONFIG_ETH_NXP_S32_TX_RING_LEN,				\
		.bufferLen = CONFIG_ETH_NXP_S32_TX_RING_BUF_SIZE,			\
		.maxBuffLen = CONFIG_ETH_NXP_S32_TX_RING_BUF_SIZE,			\
	}										\
};											\
											\
static const Netc_Eth_Ip_GeneralSIConfigType						\
nxp_s32_eth##n##_psi_cfg[FEATURE_NETC_ETH_NUMBER_OF_CTRLS] = {				\
	[NETC_SI_NXP_S32_HW_INSTANCE(n)] = {						\
		.siId = NETC_SI_NXP_S32_HW_INSTANCE(n),					\
		.enableSi = true,							\
		.NumberOfRxBDR = 1,							\
		.NumberOfTxBDR = 2,							\
		.SIVlanControl = (NETC_F3_PSICFGR0_SIVC_CVLAN_BIT			\
				| NETC_F3_PSICFGR0_SIVC_SVLAN_BIT),			\
		.changeMACAllowed = true,						\
		.hashFilterUpdateAllowed = true,					\
		IF_ENABLED(CONFIG_NET_PROMISCUOUS_MODE,					\
			(.multicastPromiscuousChangeAllowed = true,))			\
	},										\
	COND_CODE_1(INIT_VSIS(n),							\
		(DT_INST_FOREACH_PROP_ELEM_SEP(n, vsis, NETC_VSI_GENERAL_CFG, (,))),	\
		(EMPTY))								\
};											\
											\
COND_CODE_1(INIT_VSIS(n),								\
	(DT_INST_FOREACH_PROP_ELEM_SEP_VARGS(n, vsis, NETC_VSI_RX_MSG_BUF, (;), n)),	\
	(EMPTY));									\
											\
static const Netc_Eth_Ip_EnetcGeneralConfigType nxp_s32_eth##n##_enetc_general_cfg = {	\
	.numberOfConfiguredSis = FEATURE_NETC_ETH_NUMBER_OF_CTRLS,			\
	.stationInterfaceGeneralConfig = &nxp_s32_eth##n##_psi_cfg,			\
	IF_ENABLED(CONFIG_NET_PROMISCUOUS_MODE,						\
		(.maskMACPromiscuousMulticastEnable = (uint16_t)true,			\
		.maskMACPromiscuousUnicastEnable = (uint16_t)true,))			\
	.RxVsiMsgCmdToPsi = {								\
		COND_CODE_1(INIT_VSIS(n),						\
			(DT_INST_FOREACH_PROP_ELEM_SEP_VARGS(n, vsis,			\
				NETC_VSI_RX_MSG_BUF_ARRAY, (,), n)),			\
			(EMPTY))							\
	},										\
};											\
											\
static const Netc_Eth_Ip_StationInterfaceConfigType nxp_s32_eth##n##_si_cfg = {		\
	.NumberOfRxBDR = 1,								\
	.NumberOfTxBDR = 2,								\
	.txMruMailboxAddr = NULL,							\
	.rxMruMailboxAddr = (uint32_t *)MRU_MBOX_ADDR(DT_DRV_INST(n), rx),		\
	.siMsgMruMailboxAddr = COND_CODE_1(INIT_VSIS(n),				\
		((uint32_t *)MRU_MBOX_ADDR(DT_DRV_INST(n), vsi_msg)), (NULL)),		\
	.RxInterrupts = (uint32_t)true,							\
	.TxInterrupts = (uint32_t)false,						\
	.MACFilterTableMaxNumOfEntries = CONFIG_ETH_NXP_S32_MAC_FILTER_TABLE_SIZE,	\
};											\
											\
static uint8_t nxp_s32_eth##n##_switch_vlandr2dei_cfg[NETC_ETHSWT_NUMBER_OF_DR];	\
static Netc_EthSwt_Ip_PortIngressType nxp_s32_eth##n##_switch_port_ingress_cfg;		\
static Netc_EthSwt_Ip_PortEgressType nxp_s32_eth##n##_switch_port_egress_cfg = {	\
	.vlanDrToDei = &nxp_s32_eth##n##_switch_vlandr2dei_cfg,				\
};											\
static Netc_EthSwt_Ip_PortType nxp_s32_eth##n##_switch_ports_cfg[NETC_ETHSWT_NUMBER_OF_PORTS] = {\
	LISTIFY(NETC_ETHSWT_NUMBER_OF_PORTS, NETC_SWITCH_PORT_CFG, (,), n)		\
};											\
											\
static const Netc_EthSwt_Ip_ConfigType nxp_s32_eth##n##_switch_cfg = {			\
	.port = &nxp_s32_eth##n##_switch_ports_cfg,					\
	.EthSwtArlTableEntryTimeout = NETC_SWITCH_PORT_AGING,				\
	.netcClockFrequency = DT_INST_PROP(n, clock_frequency),				\
	.MacLearningOption = ETHSWT_MACLEARNINGOPTION_HWDISABLED,			\
	.MacForwardingOption = ETHSWT_NO_FDB_LOOKUP_FLOOD_FRAME,			\
	.Timer1588ClkSrc = ETHSWT_REFERENCE_CLOCK_DISABLED,				\
};											\
											\
PINCTRL_DT_INST_DEFINE(n);								\
											\
NETC_GENERATE_MAC_ADDRESS(n)								\
											\
static const struct nxp_s32_eth_config nxp_s32_eth##n##_config = {			\
	.netc_cfg = {									\
		.SiType = NETC_ETH_IP_PHYSICAL_SI,					\
		.siConfig = &nxp_s32_eth##n##_si_cfg,					\
		.generalConfig = &nxp_s32_eth##n##_enetc_general_cfg,			\
		.stateStructure = &nxp_s32_eth##n##_state,				\
		.paCtrlRxRingConfig = &nxp_s32_eth##n##_rxring_cfg,			\
		.paCtrlTxRingConfig = &nxp_s32_eth##n##_txring_cfg,			\
	},										\
	.si_idx = NETC_SI_NXP_S32_HW_INSTANCE(n),					\
	.port_idx = NETC_SWITCH_PORT_IDX,						\
	.tx_ring_idx = TX_RING_IDX,							\
	.rx_ring_idx = RX_RING_IDX,							\
	.msix = {									\
		NETC_MSIX(DT_DRV_INST(n), rx, nxp_s32_eth_psi##n##_rx_event),		\
		COND_CODE_1(INIT_VSIS(n),						\
			(NETC_MSIX(DT_DRV_INST(n), vsi_msg, Netc_Eth_Ip_MSIX_SIMsgEvent)),\
			(EMPTY))							\
	},										\
	.mac_filter_hash_table = &nxp_s32_eth##n##_mac_filter_hash_table[0],		\
	.generate_mac = nxp_s32_eth##n##_generate_mac,					\
	.phy_dev = DEVICE_DT_GET(PHY_NODE(n)),						\
	.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),					\
};											\
											\
static struct nxp_s32_eth_data nxp_s32_eth##n##_data = {				\
	.mac_addr = DT_INST_PROP_OR(n, local_mac_address, {0}),				\
};											\
											\
ETH_NET_DEVICE_DT_INST_DEFINE(n,							\
			nxp_s32_eth_initialize,						\
			NULL,								\
			&nxp_s32_eth##n##_data,						\
			&nxp_s32_eth##n##_config,					\
			CONFIG_ETH_INIT_PRIORITY,					\
			&nxp_s32_eth_api,						\
			NET_ETH_MTU);							\

DT_INST_FOREACH_STATUS_OKAY(NETC_PSI_INSTANCE_DEFINE)

static int nxp_s32_eth_switch_init(void)
{
	Std_ReturnType swt_status;

	swt_status = Netc_EthSwt_Ip_Init(NETC_SWITCH_IDX, &nxp_s32_eth0_switch_cfg);
	if (swt_status != E_OK) {
		LOG_ERR("Failed to initialize NETC Switch %d (%d)",
			NETC_SWITCH_IDX, swt_status);
		return -EIO;
	}

	return 0;
}

/*
 * NETC Switch driver must be initialized before any other NETC component.
 * This is because Netc_EthSwt_Ip_Init() will not only initialize the Switch,
 * but also perform global initialization, enable the PCIe functions for MDIO
 * and ENETC, and initialize MDIO with a fixed configuration.
 */
SYS_INIT(nxp_s32_eth_switch_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
