/*
 * Copyright (C) 2007 - 2022 Xilinx, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 *
 */

#include "lwipopts.h"
#include "xlwipconfig.h"
#include "xemac_ieee_reg.h"

#if !NO_SYS
#endif

#include "lwip/mem.h"
#include "lwip/stats.h"
#include "lwip/sys.h"
#include "lwip/ip.h"
#include "lwip/tcp.h"
#include "lwip/udp.h"
#include "lwip/priv/tcp_priv.h"

#include "netif/etharp.h"
#include "netif/xadapter.h"

#ifdef XLWIP_CONFIG_INCLUDE_EMACLITE
#include "netif/xemacliteif.h"
#endif

#ifdef XLWIP_CONFIG_INCLUDE_AXI_ETHERNET
#include "netif/xaxiemacif.h"
#endif

#ifdef XLWIP_CONFIG_INCLUDE_GEM
#include "netif/xemacpsif.h"
#endif

#if !NO_SYS
#include "lwip/tcpip.h"

#define THREAD_STACKSIZE 256
#define LINK_DETECT_THREAD_INTERVAL 1000 /* one second */

void link_detect_thread(void *p);
#endif

/* global lwip debug variable used for debugging */
int lwip_runtime_debug = 0;

enum ethernet_link_status eth_link_status = ETH_LINK_UNDEFINED;
u32_t phyaddrforemac;

void
lwip_raw_init()
{
	ip_init();	/* Doesn't do much, it should be called to handle future changes. */
#if LWIP_UDP
	udp_init();	/* Clears the UDP PCB list. */
#endif
#if LWIP_TCP
	tcp_init();	/* Clears the TCP PCB list and clears some internal TCP timers. */
			/* Note: you must call tcp_fasttmr() and tcp_slowtmr() at the */
			/* predefined regular intervals after this initialization. */
#endif
}

static enum xemac_types
find_mac_type(unsigned base)
{
	int i;

	for (i = 0; i < xtopology_n_emacs; i++) {
		if (xtopology[i].emac_baseaddr == base)
			return xtopology[i].emac_type;
	}

	return xemac_type_unknown;
}

int
xtopology_find_index(unsigned base)
{
	int i;

	for (i = 0; i < xtopology_n_emacs; i++) {
		if (xtopology[i].emac_baseaddr == base)
			return i;
	}

	return -1;
}

/*
 * xemac_add: this is a wrapper around lwIP's netif_add function.
 * The objective is to provide portability between the different Xilinx MAC's
 * This function can be used to add both xps_ethernetlite and xps_ll_temac
 * based interfaces
 */
struct netif *
xemac_add(struct netif *netif,
	ip_addr_t *ipaddr, ip_addr_t *netmask, ip_addr_t *gw,
	unsigned char *mac_ethernet_address,
	UINTPTR mac_baseaddr)
{
	int i;
	struct netif * nif = NULL;

	/* set mac address */
	netif->hwaddr_len = 6;
	for (i = 0; i < 6; i++)
		netif->hwaddr[i] = mac_ethernet_address[i];

	/* initialize based on MAC type */
		switch (find_mac_type(mac_baseaddr)) {
			case xemac_type_xps_emaclite:
#ifdef XLWIP_CONFIG_INCLUDE_EMACLITE
				nif = netif_add(netif, ipaddr, netmask, gw,
					(void*)(UINTPTR)mac_baseaddr,
					xemacliteif_init,
#if NO_SYS
					ethernet_input
#else
					tcpip_input
#endif
					);
#else
				nif = NULL;
#endif
			case xemac_type_axi_ethernet:
#ifdef XLWIP_CONFIG_INCLUDE_AXI_ETHERNET
					nif = netif_add(netif, ipaddr, netmask, gw,
					(void*)(UINTPTR)mac_baseaddr,
					xaxiemacif_init,
#if NO_SYS
					ethernet_input
#else
					tcpip_input
#endif
					);
#else
				nif = NULL;
#endif
#if defined (__arm__) || defined (__aarch64__)
			case xemac_type_emacps:
#ifdef XLWIP_CONFIG_INCLUDE_GEM
                nif = netif_add(netif, ipaddr, netmask, gw,
						(void*)(UINTPTR)mac_baseaddr,
						xemacpsif_init,
#if NO_SYS
						ethernet_input
#else
						tcpip_input
#endif

						);
#endif
#endif
			default:
				xil_printf("unable to determine type of EMAC with baseaddress 0x%08x\r\n",
						mac_baseaddr);
	}

	#ifdef OS_IS_FREERTOS
		/* Start thread to detect link periodically for Hot Plug autodetect */
		sys_thread_new("link_detect_thread", link_detect_thread, netif,
				THREAD_STACKSIZE, tskIDLE_PRIORITY);
	#endif

	return nif;
}

#if !NO_SYS
/*
 * The input thread calls lwIP to process any received packets.
 * This thread waits until a packet is received (sem_rx_data_available),
 * and then calls xemacif_input which processes 1 packet at a time.
 */
void
xemacif_input_thread(struct netif *netif)
{
	struct xemac_s *emac = (struct xemac_s *)netif->state;
	while (1) {
		/* sleep until there are packets to process
		 * This semaphore is set by the packet receive interrupt
		 * routine.
		 */
		sys_sem_wait(&emac->sem_rx_data_available);

		/* move all received packets to lwIP */
		xemacif_input(netif);
	}
}
#endif

int
xemacif_input(struct netif *netif)
{
	struct xemac_s *emac = (struct xemac_s *)netif->state;

	int n_packets = 0;

	switch (emac->type) {
		case xemac_type_xps_emaclite:
#ifdef XLWIP_CONFIG_INCLUDE_EMACLITE
			n_packets = xemacliteif_input(netif);
			break;
#else
			xil_printf("incorrect configuration: xps_ethernetlite drivers not present?");
			while(1);
			return 0;
#endif
		case xemac_type_axi_ethernet:
#ifdef XLWIP_CONFIG_INCLUDE_AXI_ETHERNET
			n_packets = xaxiemacif_input(netif);
			break;
#else
			xil_printf("incorrect configuration: axi_ethernet drivers not present?");
			while(1);
			return 0;
#endif
#if defined (__arm__) || defined (__aarch64__)
		case xemac_type_emacps:
#ifdef XLWIP_CONFIG_INCLUDE_GEM
			n_packets = xemacpsif_input(netif);
			break;
#else
			xil_printf("incorrect configuration: ps7_ethernet drivers not present?\r\n");
			while(1);
			return 0;
#endif
#endif
		default:
			xil_printf("incorrect configuration: unknown temac type");
			while(1);
			return 0;
	}

	return n_packets;
}

#if defined(XLWIP_CONFIG_INCLUDE_GEM)
static u32_t phy_link_detect(XEmacPs *xemacp, u32_t phy_addr)
{
	u16_t status;

	/* Read Phy Status register twice to get the confirmation of the current
	 * link status.
	 */
	XEmacPs_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
	XEmacPs_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);

	if (status & IEEE_STAT_LINK_STATUS)
		return 1;
	return 0;
}
#elif defined(XLWIP_CONFIG_INCLUDE_AXI_ETHERNET)
static u32_t phy_link_detect(XAxiEthernet *xemacp, u32_t phy_addr)
{
	u16_t status;

	/* Read Phy Status register twice to get the confirmation of the current
	 * link status.
	 */
	XAxiEthernet_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
	XAxiEthernet_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);

	if (status & IEEE_STAT_LINK_STATUS)
		return 1;
	return 0;
}
#elif defined(XLWIP_CONFIG_INCLUDE_EMACLITE)
static u32_t phy_link_detect(XEmacLite *xemacp, u32_t phy_addr)
{
	u16_t status;

	/* Read Phy Status register twice to get the confirmation of the current
	 * link status.
	 */
	XEmacLite_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
	XEmacLite_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);

	if (status & IEEE_STAT_LINK_STATUS)
		return 1;
	return 0;
}
#endif

#if defined(XLWIP_CONFIG_INCLUDE_GEM)
static u32_t phy_autoneg_status(XEmacPs *xemacp, u32_t phy_addr)
{
	u16_t status;

	/* Read Phy Status register twice to get the confirmation of the current
	 * link status.
	 */
	XEmacPs_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
	XEmacPs_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);

	if (status & IEEE_STAT_AUTONEGOTIATE_COMPLETE)
		return 1;
	return 0;
}
#elif defined(XLWIP_CONFIG_INCLUDE_AXI_ETHERNET)
static u32_t phy_autoneg_status(XAxiEthernet *xemacp, u32_t phy_addr)
{
	u16_t status;

	/* Read Phy Status register twice to get the confirmation of the current
	 * link status.
	 */
	XAxiEthernet_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
	XAxiEthernet_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);

	if (status & IEEE_STAT_AUTONEGOTIATE_COMPLETE)
		return 1;
	return 0;
}
#elif defined(XLWIP_CONFIG_INCLUDE_EMACLITE)
static u32_t phy_autoneg_status(XEmacLite *xemacp, u32_t phy_addr)
{
	u16_t status;

	/* Read Phy Status register twice to get the confirmation of the current
	 * link status.
	 */
	XEmacLite_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
	XEmacLite_PhyRead(xemacp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);

	if (status & IEEE_STAT_AUTONEGOTIATE_COMPLETE)
		return 1;
	return 0;
}
#endif

void eth_link_detect(struct netif *netif)
{
	u32_t link_speed, phy_link_status;
	struct xemac_s *xemac = (struct xemac_s *)(netif->state);

#if defined(XLWIP_CONFIG_INCLUDE_GEM)
	xemacpsif_s *xemacs = (xemacpsif_s *)(xemac->state);
	XEmacPs *xemacp = &xemacs->emacps;
#elif defined(XLWIP_CONFIG_INCLUDE_AXI_ETHERNET)
	xaxiemacif_s *xemacs = (xaxiemacif_s *)(xemac->state);
	XAxiEthernet *xemacp = &xemacs->axi_ethernet;
#elif defined(XLWIP_CONFIG_INCLUDE_EMACLITE)
	xemacliteif_s *xemacs = (xemacliteif_s *)(xemac->state);
	XEmacLite *xemacp = xemacs->instance;
#endif

	if ((xemacp->IsReady != (u32)XIL_COMPONENT_IS_READY) ||
			(eth_link_status == ETH_LINK_UNDEFINED))
		return;

	phy_link_status = phy_link_detect(xemacp, phyaddrforemac);

	if ((eth_link_status == ETH_LINK_UP) && (!phy_link_status))
		eth_link_status = ETH_LINK_DOWN;

	switch (eth_link_status) {
		case ETH_LINK_UNDEFINED:
		case ETH_LINK_UP:
			return;
		case ETH_LINK_DOWN:
			netif_set_link_down(netif);
			eth_link_status = ETH_LINK_NEGOTIATING;
			xil_printf("Ethernet Link down\r\n");
			break;
		case ETH_LINK_NEGOTIATING:
			if (phy_link_status &&
				phy_autoneg_status(xemacp, phyaddrforemac)) {

				/* Initiate Phy setup to get link speed */
#if defined(XLWIP_CONFIG_INCLUDE_GEM)
				link_speed = phy_setup_emacps(xemacp,
								phyaddrforemac);
				XEmacPs_SetOperatingSpeed(xemacp, link_speed);
#elif defined(XLWIP_CONFIG_INCLUDE_AXI_ETHERNET)
				link_speed = phy_setup_axiemac(xemacp);
				XAxiEthernet_SetOperatingSpeed(xemacp,
							       link_speed);
#endif
				netif_set_link_up(netif);
				eth_link_status = ETH_LINK_UP;
				xil_printf("Ethernet Link up\r\n");
			}
			break;
	}
}

#if !NO_SYS
void link_detect_thread(void *p)
{
	struct netif *netif = (struct netif *) p;

	while (1) {
		/* Call eth_link_detect() every second to detect Ethernet link
		 * change.
		 */
		eth_link_detect(netif);
		vTaskDelay(LINK_DETECT_THREAD_INTERVAL / portTICK_RATE_MS);
	}
}
#endif
