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

#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 <S32Z2.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 PSI_NODE	DT_COMPAT_GET_ANY_STATUS_OKAY(nxp_s32_netc_psi)
#define PHY_NODE	DT_PHANDLE(PSI_NODE, phy_handle)
#define INIT_VSIS	DT_NODE_HAS_PROP(PSI_NODE, vsis)
#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;
#if defined(CONFIG_NET_IPV6)
	static struct net_if_mcast_monitor mon;

	net_if_mcast_mon_register(&mon, iface, nxp_s32_eth_mcast_cb);
#endif /* CONFIG_NET_IPV6 */

	/*
	 * 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 (msix->mbox_channel.dev != NULL) {
			if (mbox_set_enabled(&msix->mbox_channel, true)) {
				LOG_ERR("Failed to enable MRU channel %u", msix->mbox_channel.id);
			}
		}
	}
}

static void nxp_s32_eth0_rx_callback(const uint8_t unused, const uint8_t ring)
{
	const struct device *dev = DEVICE_DT_GET(PSI_NODE);
	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 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),		\
	}

#define NETC_VSI_RX_MSG_BUF(node, prop, idx)							\
	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_eth0_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)						\
	[DT_PROP_BY_IDX(node, prop, idx) - 1] =							\
		&_CONCAT3(nxp_s32_eth0_vsi, DT_PROP_BY_IDX(node, prop, idx), _rx_msg_buf)

#define NETC_SWITCH_PORT_CFG(_, __)						\
	{									\
		.ePort = &nxp_s32_eth0_switch_port_egress_cfg,			\
		.iPort = &nxp_s32_eth0_switch_port_ingress_cfg,			\
		.EthSwtPortMacLayerPortEnable = true,				\
		.EthSwtPortMacLayerSpeed = ETHTRCV_BAUD_RATE_1000MBIT,		\
		.EthSwtPortMacLayerDuplexMode = NETC_ETHSWT_PORT_FULL_DUPLEX,	\
		.EthSwtPortPhysicalLayerType = NETC_ETHSWT_RGMII_MODE,		\
		.EthSwtPortPruningEnable = true,				\
	}

static Netc_Eth_Ip_StateType nxp_s32_eth0_state;

static Netc_Eth_Ip_MACFilterHashTableEntryType
nxp_s32_eth0_mac_filter_hash_table[CONFIG_ETH_NXP_S32_MAC_FILTER_TABLE_SIZE];

NETC_TX_RING(0, 0, NETC_MIN_RING_LEN, NETC_MIN_RING_BUF_SIZE);
NETC_TX_RING(0, TX_RING_IDX,
	     CONFIG_ETH_NXP_S32_TX_RING_LEN, CONFIG_ETH_NXP_S32_TX_RING_BUF_SIZE);
NETC_RX_RING(0, 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_eth0_rxring_cfg[1] = {
	{
		.RingDesc = nxp_s32_eth0_rxring0_desc,
		.Buffer = nxp_s32_eth0_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_eth0_rx_callback,
	}
};

static const Netc_Eth_Ip_TxRingConfigType nxp_s32_eth0_txring_cfg[2] = {
	{
		.RingDesc = nxp_s32_eth0_txring0_desc,
		.Buffer = nxp_s32_eth0_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_eth0_txring1_desc,
		.Buffer = nxp_s32_eth0_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_eth0_psi_cfg[FEATURE_NETC_ETH_NUMBER_OF_CTRLS] = {
	[NETC_ETH_IP_PSI_INDEX] = {
		.siId = NETC_ETH_IP_PSI_INDEX,
		.enableSi = true,
		.NumberOfRxBDR = 1,
		.NumberOfTxBDR = 2,
		.SIVlanControl = (NETC_F3_PSICFGR0_SIVC_CVLAN_BIT
				| NETC_F3_PSICFGR0_SIVC_SVLAN_BIT),
	},
	COND_CODE_1(INIT_VSIS,
		(DT_FOREACH_PROP_ELEM_SEP(PSI_NODE, vsis, NETC_VSI_GENERAL_CFG, (,))),
		(EMPTY))
};

COND_CODE_1(INIT_VSIS,
	(DT_FOREACH_PROP_ELEM_SEP(PSI_NODE, vsis, NETC_VSI_RX_MSG_BUF, (;))),
	(EMPTY));

static const Netc_Eth_Ip_EnetcGeneralConfigType nxp_s32_eth0_enetc_general_cfg = {
	.numberOfConfiguredSis = FEATURE_NETC_ETH_NUMBER_OF_CTRLS,
	.stationInterfaceGeneralConfig = &nxp_s32_eth0_psi_cfg,
#if defined(CONFIG_NET_PROMISCUOUS_MODE)
	.maskMACPromiscuousMulticastEnable = (uint16_t)true,
	.maskMACPromiscuousUnicastEnable = (uint16_t)true,
#endif
	.RxVsiMsgCmdToPsi = {
		COND_CODE_1(INIT_VSIS,
			(DT_FOREACH_PROP_ELEM_SEP(PSI_NODE, vsis,
				NETC_VSI_RX_MSG_BUF_ARRAY, (,))),
			(EMPTY))
	},
};

static const Netc_Eth_Ip_StationInterfaceConfigType nxp_s32_eth0_si_cfg = {
	.NumberOfRxBDR = 1,
	.NumberOfTxBDR = 2,
	.txMruMailboxAddr = NULL,
	.rxMruMailboxAddr = (uint32_t *)MRU_MBOX_ADDR(PSI_NODE, rx),
	.siMsgMruMailboxAddr = COND_CODE_1(INIT_VSIS,
		((uint32_t *)MRU_MBOX_ADDR(PSI_NODE, 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_eth0_switch_vlandr2dei_cfg[NETC_ETHSWT_NUMBER_OF_DR];
static Netc_EthSwt_Ip_PortIngressType nxp_s32_eth0_switch_port_ingress_cfg;
static Netc_EthSwt_Ip_PortEgressType nxp_s32_eth0_switch_port_egress_cfg = {
	.vlanDrToDei = &nxp_s32_eth0_switch_vlandr2dei_cfg,
};
static Netc_EthSwt_Ip_PortType nxp_s32_eth0_switch_ports_cfg[NETC_ETHSWT_NUMBER_OF_PORTS] = {
	LISTIFY(NETC_ETHSWT_NUMBER_OF_PORTS, NETC_SWITCH_PORT_CFG, (,))
};

static const Netc_EthSwt_Ip_ConfigType nxp_s32_eth0_switch_cfg = {
	.port = &nxp_s32_eth0_switch_ports_cfg,
	.EthSwtArlTableEntryTimeout = NETC_SWITCH_PORT_AGING,
	.netcClockFrequency = DT_PROP(PSI_NODE, clock_frequency),
	.MacLearningOption = ETHSWT_MACLEARNINGOPTION_HWDISABLED,
	.MacForwardingOption = ETHSWT_NO_FDB_LOOKUP_FLOOD_FRAME,
	.Timer1588ClkSrc = ETHSWT_REFERENCE_CLOCK_DISABLED,
};

PINCTRL_DT_DEFINE(PSI_NODE);

NETC_GENERATE_MAC_ADDRESS(PSI_NODE, 0)

static const struct nxp_s32_eth_config nxp_s32_eth0_config = {
	.netc_cfg = {
		.SiType = NETC_ETH_IP_PHYSICAL_SI,
		.siConfig = &nxp_s32_eth0_si_cfg,
		.generalConfig = &nxp_s32_eth0_enetc_general_cfg,
		.stateStructure = &nxp_s32_eth0_state,
		.paCtrlRxRingConfig = &nxp_s32_eth0_rxring_cfg,
		.paCtrlTxRingConfig = &nxp_s32_eth0_txring_cfg,
	},
	.si_idx = NETC_ETH_IP_PSI_INDEX,
	.port_idx = NETC_SWITCH_PORT_IDX,
	.tx_ring_idx = TX_RING_IDX,
	.rx_ring_idx = RX_RING_IDX,
	.msix = {
		NETC_MSIX(PSI_NODE, rx, Netc_Eth_Ip_0_MSIX_RxEvent),
		COND_CODE_1(INIT_VSIS,
			(NETC_MSIX(PSI_NODE, vsi_msg, Netc_Eth_Ip_MSIX_SIMsgEvent)),
			(EMPTY))
	},
	.mac_filter_hash_table = &nxp_s32_eth0_mac_filter_hash_table[0],
	.generate_mac = nxp_s32_eth0_generate_mac,
	.phy_dev = DEVICE_DT_GET(PHY_NODE),
	.pincfg = PINCTRL_DT_DEV_CONFIG_GET(PSI_NODE),
};

static struct nxp_s32_eth_data nxp_s32_eth0_data = {
	.mac_addr = DT_PROP_OR(PSI_NODE, local_mac_address, {0}),
};

ETH_NET_DEVICE_DT_DEFINE(PSI_NODE,
			nxp_s32_eth_initialize,
			NULL,
			&nxp_s32_eth0_data,
			&nxp_s32_eth0_config,
			CONFIG_ETH_INIT_PRIORITY,
			&nxp_s32_eth_api,
			NET_ETH_MTU);

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);
