/* LAN9250 Stand-alone Ethernet Controller with SPI
 *
 * Copyright (c) 2024 Mario Paja
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#define DT_DRV_COMPAT microchip_lan9250

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <string.h>
#include <errno.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/ethernet.h>
#include <ethernet/eth_stats.h>

#include "eth_lan9250_priv.h"

LOG_MODULE_REGISTER(eth_lan9250, CONFIG_ETHERNET_LOG_LEVEL);

static int lan9250_write_sys_reg(const struct device *dev, uint16_t address, uint32_t data)
{
	const struct lan9250_config *config = dev->config;
	uint8_t cmd[1] = {LAN9250_SPI_INSTR_WRITE};
	uint8_t addr[2];
	uint8_t instr[4];
	struct spi_buf tx_buf[3];
	const struct spi_buf_set tx = {.buffers = tx_buf, .count = 3};

	sys_put_be16(address, addr);
	sys_put_le32(data, instr);

	tx_buf[0].buf = &cmd;
	tx_buf[0].len = ARRAY_SIZE(cmd);
	tx_buf[1].buf = addr;
	tx_buf[1].len = ARRAY_SIZE(addr);
	tx_buf[2].buf = instr;
	tx_buf[2].len = ARRAY_SIZE(instr);

	return spi_write_dt(&config->spi, &tx);
}

static int lan9250_read_sys_reg(const struct device *dev, uint16_t address, uint32_t *value)
{
	const struct lan9250_config *config = dev->config;
	uint8_t cmd[1] = {LAN9250_SPI_INSTR_READ};
	uint8_t addr[2];
	struct spi_buf tx_buf[3];
	struct spi_buf rx_buf[3];
	const struct spi_buf_set tx = {.buffers = tx_buf, .count = 3};
	const struct spi_buf_set rx = {.buffers = rx_buf, .count = 3};

	sys_put_be16(address, addr);

	tx_buf[0].buf = &cmd;
	tx_buf[0].len = ARRAY_SIZE(cmd);
	tx_buf[1].buf = addr;
	tx_buf[1].len = ARRAY_SIZE(addr);
	tx_buf[2].buf = NULL;
	tx_buf[2].len = sizeof(uint32_t);

	rx_buf[0].buf = NULL;
	rx_buf[0].len = 1;
	rx_buf[1].buf = NULL;
	rx_buf[1].len = 2;
	rx_buf[2].buf = value;
	rx_buf[2].len = sizeof(uint32_t);

	return spi_transceive_dt(&config->spi, &tx, &rx);
}

static int lan9250_wait_ready(const struct device *dev, uint16_t address, uint32_t mask,
			      uint32_t expected, uint32_t m_second)
{
	int ret;
	uint32_t tmp;
	k_timepoint_t end = sys_timepoint_calc(K_MSEC(m_second));

	while (true) {
		ret = lan9250_read_sys_reg(dev, address, &tmp);
		if ((ret == 0) && ((tmp & mask) == expected)) {
			return 0;
		}
		if (sys_timepoint_expired(end)) {
			return -EIO;
		}
		k_busy_wait(USEC_PER_MSEC * 1U);
	}
}

static int lan9250_read_mac_reg(const struct device *dev, uint8_t address, uint32_t *value)
{
	uint32_t tmp;

	/* Wait for MAC to be ready and send writing register command and data */
	lan9250_wait_ready(dev, LAN9250_MAC_CSR_CMD, LAN9250_MAC_CSR_CMD_BUSY, 0,
			   LAN9250_MAC_TIMEOUT);
	lan9250_write_sys_reg(dev, LAN9250_MAC_CSR_CMD,
			      address | LAN9250_MAC_CSR_CMD_BUSY | LAN9250_MAC_CSR_CMD_READ);

	/* Wait for MAC to be ready and send writing register command and data */
	lan9250_wait_ready(dev, LAN9250_MAC_CSR_CMD, LAN9250_MAC_CSR_CMD_BUSY, 0,
			   LAN9250_MAC_TIMEOUT);
	lan9250_read_sys_reg(dev, LAN9250_MAC_CSR_DATA, &tmp);

	*value = tmp;
	return 0;
}

static int lan9250_write_mac_reg(const struct device *dev, uint8_t address, uint32_t data)
{
	/* Wait for MAC to be ready and send writing register command and data */
	lan9250_wait_ready(dev, LAN9250_MAC_CSR_CMD, LAN9250_MAC_CSR_CMD_BUSY, 0,
			   LAN9250_MAC_TIMEOUT);
	lan9250_write_sys_reg(dev, LAN9250_MAC_CSR_DATA, data);
	lan9250_write_sys_reg(dev, LAN9250_MAC_CSR_CMD, address | LAN9250_MAC_CSR_CMD_BUSY);

	/* Wait until writing MAC is done */
	lan9250_wait_ready(dev, LAN9250_MAC_CSR_CMD, LAN9250_MAC_CSR_CMD_BUSY, 0,
			   LAN9250_MAC_TIMEOUT);

	return 0;
}

static int lan9250_wait_mac_ready(const struct device *dev, uint8_t address, uint32_t mask,
				  uint32_t expected, uint32_t m_second)
{
	int ret;
	uint32_t tmp;
	k_timepoint_t end = sys_timepoint_calc(K_MSEC(m_second));

	while (true) {
		ret = lan9250_read_mac_reg(dev, address, &tmp);
		if ((ret == 0) && ((tmp & mask) == expected)) {
			return 0;
		}
		if (sys_timepoint_expired(end)) {
			return -EIO;
		}
		k_msleep(1);
	}
}

static int lan9250_read_phy_reg(const struct device *dev, uint8_t address, uint16_t *value)
{
	uint32_t tmp;

	/* Wait PHY to be ready and send reading register command */
	lan9250_wait_mac_ready(dev, LAN9250_HMAC_MII_ACC, LAN9250_HMAC_MII_ACC_MIIBZY, 0,
			       LAN9250_PHY_TIMEOUT);

	/* Reference: Microchip Ethernet LAN9250
	 * https://github.com/microchip-pic-avr-solutions/ethernet-lan9250/
	 *
	 * Datasheet:
	 * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/00001913A.pdf
	 *
	 * 12.2.18 PHY REGISTERS
	 * The PHY registers are indirectly accessed through the Host MAC MII Access Register
	 * (HMAC_MII_ACC) and Host MAC MII Data Register (HMAC_MII_DATA).
	 *
	 * Write 32bit value to the indirect MAC registers
	 * Where phy_add = 0b00001 & index = address
	 * Data = ((phy_add & 0x1F) << 11) | ((index & 0x1F) << 6)
	 */
	lan9250_write_mac_reg(dev, LAN9250_HMAC_MII_ACC, (1 << 11) | ((address & 0x1F) << 6));

	/* Wait PHY to be ready and send reading register command */
	lan9250_wait_mac_ready(dev, LAN9250_HMAC_MII_ACC, LAN9250_HMAC_MII_ACC_MIIBZY, 0,
			       LAN9250_PHY_TIMEOUT);

	/* Read 32bit value from the indirect MAC registers */
	lan9250_read_mac_reg(dev, LAN9250_HMAC_MII_DATA, &tmp);
	*value = tmp;

	return 0;
}

static int lan9250_write_phy_reg(const struct device *dev, uint8_t address, uint16_t data)
{
	/* Wait PHY to be ready and send reading register command */
	lan9250_wait_mac_ready(dev, LAN9250_HMAC_MII_ACC, LAN9250_HMAC_MII_ACC_MIIBZY, 0,
			       LAN9250_PHY_TIMEOUT);
	lan9250_write_mac_reg(dev, LAN9250_HMAC_MII_DATA, data);

	/* Reference: Microchip Ethernet LAN9250
	 * https://github.com/microchip-pic-avr-solutions/ethernet-lan9250/
	 *
	 * Datasheet:
	 * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/00001913A.pdf
	 *
	 * 12.2.18 PHY REGISTERS
	 * The PHY registers are indirectly accessed through the Host MAC MII Access Register
	 * (HMAC_MII_ACC) and Host MAC MII Data Register (HMAC_MII_DATA).
	 *
	 * Write 32bit value to the indirect MAC registers
	 * Where phy_add = 0b00001 & index = address
	 * Data = ((phy_add & 0x1F) << 11) | ((index & 0x1F)<< 6) | MIIWnR
	 */
	lan9250_write_mac_reg(dev, LAN9250_HMAC_MII_ACC,
			      (1 << 11) | ((address & 0x1F) << 6) | LAN9250_HMAC_MII_ACC_MIIW_R);

	/* Wait PHY to be ready and send reading register command */
	lan9250_wait_mac_ready(dev, LAN9250_HMAC_MII_ACC, LAN9250_HMAC_MII_ACC_MIIBZY, 0,
			       LAN9250_PHY_TIMEOUT);

	return 0;
}

static int lan9250_set_macaddr(const struct device *dev)
{
	struct lan9250_runtime *ctx = dev->data;

	lan9250_write_mac_reg(dev, LAN9250_HMAC_ADDRL,
			      ctx->mac_address[0] | (ctx->mac_address[1] << 8) |
			      (ctx->mac_address[2] << 16) | (ctx->mac_address[3] << 24));
	lan9250_write_mac_reg(dev, LAN9250_HMAC_ADDRH,
			      ctx->mac_address[4] | (ctx->mac_address[5] << 8));

	return 0;
}

static int lan9250_hw_cfg_check(const struct device *dev)
{
	uint32_t tmp;

	do {
		lan9250_read_sys_reg(dev, LAN9250_HW_CFG, &tmp);
		k_busy_wait(USEC_PER_MSEC * 1U);
	} while ((tmp & LAN9250_HW_CFG_DEVICE_READY) == 0);

	return 0;
}

static int lan9250_sw_reset(const struct device *dev)
{
	int ret;

	ret = lan9250_write_sys_reg(dev, LAN9250_RESET_CTL,
				    LAN9250_RESET_CTL_HMAC_RST |
				    LAN9250_RESET_CTL_PHY_RST |
				    LAN9250_RESET_CTL_DIGITAL_RST);
	if (ret < 0) {
		return ret;
	}

	/* Wait until LAN9250 SPI bus is ready */
	return lan9250_wait_ready(dev, LAN9250_BYTE_TEST, BOTR_MASK,
				  LAN9250_BYTE_TEST_DEFAULT, LAN9250_RESET_TIMEOUT);
}

static int lan9250_configure(const struct device *dev)
{
	uint32_t tmp;

	lan9250_hw_cfg_check(dev);

	/* Read LAN9250 hardware ID */
	lan9250_read_sys_reg(dev, LAN9250_ID_REV, &tmp);
	if ((tmp & LAN9250_ID_REV_CHIP_ID) != LAN9250_ID_REV_CHIP_ID_DEFAULT) {
		LOG_ERR("ERROR: Bad Rev ID: %08x\n", tmp);
		return -ENODEV;
	}

	/* Configure TX FIFO size mode to be 8:
	 *
	 *   - TX data FIFO size:   7680
	 *   - RX data FIFO size:   7680
	 *   - TX status FIFO size: 512
	 *   - RX status FIFO size: 512
	 */
	lan9250_write_sys_reg(dev, LAN9250_HW_CFG,
			      LAN9250_HW_CFG_MBO | LAN9250_HW_CFG_TX_FIF_SZ_8KB);

	/* Configure MAC automatic flow control:
	 *
	 *  Reference: Microchip Ethernet LAN9250
	 *  https://github.com/microchip-pic-avr-solutions/ethernet-lan9250/
	 *  LAN_Regwrite32(AFC_CFG, 0x006E3741);
	 *
	 */
	lan9250_write_sys_reg(dev, LAN9250_AFC_CFG, 0x006e3741);

	/* Configure interrupt:
	 *
	 *   - Interrupt De-assertion interval: 100
	 *   - Interrupt output to pin
	 *   - Interrupt pin active output low
	 *   - Interrupt pin push-pull driver
	 */
	lan9250_write_sys_reg(dev, LAN9250_IRQ_CFG,
			      LAN9250_IRQ_CFG_INT_DEAS_100US | LAN9250_IRQ_CFG_IRQ_EN |
			      LAN9250_IRQ_CFG_IRQ_TYPE_PP);

	/* Configure interrupt trigger source, please refer to macro
	 * LAN9250_INT_SOURCE.
	 */
	lan9250_write_sys_reg(dev, LAN9250_INT_EN,
			      LAN9250_INT_EN_PHY_INT_EN | LAN9250_INT_EN_RSFL_EN);

	/* Disable TX data FIFO available interrupt */
	lan9250_write_sys_reg(dev, LAN9250_FIFO_INT,
			      LAN9250_FIFO_INT_TX_DATA_AVAILABLE_LEVEL |
			      LAN9250_FIFO_INT_TX_STATUS_LEVEL);

	/* Configure RX:
	 *
	 *   - RX DMA counter: Ethernet maximum packet size
	 *   - RX data offset: 4, so that need read dummy before reading data
	 */
	lan9250_write_sys_reg(dev, LAN9250_RX_CFG, 0x06000000 | 0x00000400);

	/* Configure remote power management:
	 *
	 *   - Auto wakeup
	 *   - Disable 1588 clock
	 *   - Disable 1588 timestamp unit clock
	 *   - Energy-detect
	 *   - Wake on
	 *   - Clear wakeon
	 */
	lan9250_write_sys_reg(dev, LAN9250_PMT_CTRL,
			      LAN9250_PMT_CTRL_PM_WAKE | LAN9250_PMT_CTRL_1588_DIS |
			      LAN9250_PMT_CTRL_1588_TSU_DIS | LAN9250_PMT_CTRL_WOL_EN |
			      LAN9250_PMT_CTRL_WOL_STS);

	/* Configure PHY basic control:
	 *
	 *   - Auto-Negotiation for 10/100 Mbits and Half/Full Duplex
	 */
	lan9250_write_phy_reg(dev, LAN9250_PHY_BASIC_CONTROL,
			      LAN9250_PHY_BASIC_CONTROL_PHY_AN |
			      LAN9250_PHY_BASIC_CONTROL_PHY_SPEED_SEL_LSB |
			      LAN9250_PHY_BASIC_CONTROL_PHY_DUPLEX);

	/* Configure PHY auto-negotiation advertisement capability:
	 *
	 *   - Asymmetric pause
	 *   - Symmetric pause
	 *   - 100Base-X half/full duplex
	 *   - 10Base-X half/full duplex
	 *   - Select IEEE802.3
	 */
	lan9250_write_phy_reg(dev, LAN9250_PHY_AN_ADV,
			      LAN9250_PHY_AN_ADV_ASYM_PAUSE | LAN9250_PHY_AN_ADV_SYM_PAUSE |
			      LAN9250_PHY_AN_ADV_100BTX_HD | LAN9250_PHY_AN_ADV_100BTX_FD |
			      LAN9250_PHY_AN_ADV_10BT_HD | LAN9250_PHY_AN_ADV_10BT_FD |
			      LAN9250_PHY_AN_ADV_SELECTOR_DEFAULT);

	/* Configure PHY special mode:
	 *
	 *   - PHY mode = 111b, enable all capable and auto-nagotiation
	 *   - PHY address = 1, default value is fixed to 1 by manufacturer
	 */
	lan9250_write_phy_reg(dev, LAN9250_PHY_SPECIAL_MODES, 0x00E0 | 1);

	/* Configure PHY special control or status indication:
	 *
	 *   - Port auto-MDIX determined by bits 14 and 13
	 *   - Auto-MDIX
	 *   - Disable SQE tests
	 */
	lan9250_write_phy_reg(dev, LAN9250_PHY_SPECIAL_CONTROL_STAT_IND,
			      LAN9250_PHY_SPECIAL_CONTROL_STAT_IND_AMDIXCTRL |
			      LAN9250_PHY_SPECIAL_CONTROL_STAT_IND_AMDIXEN |
			      LAN9250_PHY_SPECIAL_CONTROL_STAT_IND_SQEOFF);

	/* Configure PHY interrupt source:
	 *
	 *   - Link up
	 *   - Link down
	 */
	lan9250_write_phy_reg(dev, LAN9250_PHY_INTERRUPT_MASK,
			      LAN9250_PHY_INTERRUPT_SOURCE_LINK_UP |
			      LAN9250_PHY_INTERRUPT_SOURCE_LINK_DOWN);

	/* Configure special control or status:
	 *
	 *   - Fixed to write 0000010b to reserved filed
	 */
	lan9250_write_phy_reg(dev, LAN9250_PHY_SPECIAL_CONTROL_STATUS,
			      LAN9250_PHY_MODE_CONTROL_STATUS_ALTINT);

	/* Clear interrupt status */
	lan9250_write_sys_reg(dev, LAN9250_INT_STS, 0xFFFFFFFF);

	/* Configure HMAC control:
	 *
	 *   - Automatically strip the pad field on incoming packets
	 *   - TX enable
	 *   - RX enable
	 *   - Full duplex
	 *   - Promiscuous disabled
	 */
	lan9250_write_mac_reg(dev, LAN9250_HMAC_CR,
			      LAN9250_HMAC_CR_PADSTR | LAN9250_HMAC_CR_TXEN | LAN9250_HMAC_CR_RXEN |
			      LAN9250_HMAC_CR_FDPX);

	/* Configure TX:
	 *
	 *   - TX enable
	 */
	lan9250_write_sys_reg(dev, LAN9250_TX_CFG, LAN9250_TX_CFG_TX_ON);

	return 0;
}

static int lan9250_write_buf(const struct device *dev, uint8_t *data_buffer, uint16_t buf_len)
{
	const struct lan9250_config *config = dev->config;
	uint8_t cmd[1] = {LAN9250_SPI_INSTR_WRITE};
	uint8_t instr[2] = {(LAN9250_TX_DATA_FIFO >> 8) & 0xFF, (LAN9250_TX_DATA_FIFO & 0xFF)};
	struct spi_buf tx_buf[3];
	const struct spi_buf_set tx = {.buffers = tx_buf, .count = 3};

	tx_buf[0].buf = &cmd;
	tx_buf[0].len = ARRAY_SIZE(cmd);
	tx_buf[1].buf = &instr;
	tx_buf[1].len = ARRAY_SIZE(instr);
	tx_buf[2].buf = data_buffer;
	tx_buf[2].len = buf_len;

	return spi_transceive_dt(&config->spi, &tx, NULL);
}

static int lan9250_read_buf(const struct device *dev, uint8_t *data_buffer, uint16_t buf_len)
{
	const struct lan9250_config *config = dev->config;
	uint8_t cmd[1] = {LAN9250_SPI_INSTR_READ};
	uint8_t instr[2] = {(LAN9250_RX_DATA_FIFO >> 8) & 0xFF, (LAN9250_RX_DATA_FIFO & 0xFF)};
	struct spi_buf tx_buf[3];
	struct spi_buf rx_buf[3];
	const struct spi_buf_set tx = {.buffers = tx_buf, .count = 3};
	const struct spi_buf_set rx = {.buffers = rx_buf, .count = 3};

	tx_buf[0].buf = &cmd;
	tx_buf[0].len = ARRAY_SIZE(cmd);
	tx_buf[1].buf = &instr;
	tx_buf[1].len = ARRAY_SIZE(instr);
	tx_buf[2].buf = NULL;
	tx_buf[2].len = buf_len;

	rx_buf[0].buf = NULL;
	rx_buf[0].len = 1;
	rx_buf[1].buf = NULL;
	rx_buf[1].len = 2;
	rx_buf[2].buf = data_buffer;
	rx_buf[2].len = buf_len;

	return spi_transceive_dt(&config->spi, &tx, &rx);
}

static int lan9250_rx(const struct device *dev)
{
	struct lan9250_runtime *ctx = dev->data;
	const uint16_t buf_rx_size = CONFIG_NET_BUF_DATA_SIZE;
	struct net_pkt *pkt;
	struct net_buf *pkt_buf;
	uint16_t pkt_len;
	uint8_t pktcnt;
	uint32_t tmp;

	/* Check valid packet count */
	lan9250_read_sys_reg(dev, LAN9250_RX_FIFO_INF, &tmp);
	pktcnt = (tmp & 0x00ff0000) >> 16;

	/* Check packet length */
	lan9250_read_sys_reg(dev, LAN9250_RX_STATUS_FIFO, &tmp);
	pkt_len = (tmp & LAN9250_RX_STS_PACKET_LEN) >> 16;

	if (pktcnt == 0 || pkt_len == 0) {
		return 0;
	}

	/* Read dummy  data */
	lan9250_read_sys_reg(dev, LAN9250_RX_DATA_FIFO, &tmp);
	pkt_len -= 4;

	if (pkt_len > NET_ETH_MAX_FRAME_SIZE) {
		LOG_ERR("Maximum frame length exceeded, it should be: %d", NET_ETH_MAX_FRAME_SIZE);
		eth_stats_update_errors_rx(ctx->iface);
	}

	/* Get the frame from the buffer */
	pkt = net_pkt_rx_alloc_with_buffer(ctx->iface, pkt_len, NET_AF_UNSPEC, 0,
					   K_MSEC(CONFIG_ETH_LAN9250_BUF_ALLOC_TIMEOUT));
	if (!pkt) {
		LOG_ERR("%s: Could not allocate rx buffer", dev->name);
		eth_stats_update_errors_rx(ctx->iface);
		return 0;
	}

	pkt_buf = pkt->buffer;

	do {
		uint8_t *data_ptr = pkt_buf->data;
		uint16_t data_len;

		if (pkt_len > buf_rx_size) {
			data_len = buf_rx_size;
		} else {
			data_len = pkt_len;
		}
		pkt_len -= data_len;

		lan9250_read_buf(dev, data_ptr, data_len);
		net_buf_add(pkt_buf, data_len);
		pkt_buf = pkt_buf->frags;
	} while (pkt_len > 0);

	lan9250_read_sys_reg(dev, LAN9250_RX_DATA_FIFO, &tmp);
	net_pkt_set_iface(pkt, ctx->iface);

	/* Feed buffer frame to IP stack */
	if (net_recv_data(net_pkt_iface(pkt), pkt) < 0) {
		net_pkt_unref(pkt);
	}

	k_sem_give(&ctx->tx_rx_sem);

	return 0;
}

static int lan9250_tx(const struct device *dev, struct net_pkt *pkt)
{
	struct lan9250_runtime *ctx = dev->data;
	size_t len = net_pkt_get_len(pkt);
	uint32_t regval;
	uint16_t free_size;
	uint8_t status_size;
	uint32_t tmp;

	lan9250_read_sys_reg(dev, LAN9250_TX_FIFO_INF, &regval);
	status_size = (regval & LAN9250_TX_FIFO_INF_TXSUSED) >> 16;
	free_size = regval & LAN9250_TX_FIFO_INF_TXFREE;

	k_sem_take(&ctx->tx_rx_sem, K_FOREVER);

	/* TX command 'A' */
	lan9250_write_sys_reg(dev, LAN9250_TX_DATA_FIFO,
			      LAN9250_TX_CMD_A_INT_ON_COMP | LAN9250_TX_CMD_A_BUFFER_ALIGN_4B |
			      LAN9250_TX_CMD_A_START_OFFSET_0B |
			      LAN9250_TX_CMD_A_FIRST_SEG | LAN9250_TX_CMD_A_LAST_SEG | len);

	/* TX command 'B' */
	lan9250_write_sys_reg(dev, LAN9250_TX_DATA_FIFO, LAN9250_TX_CMD_B_PACKET_TAG | len);

	if (net_pkt_read(pkt, ctx->buf, len)) {
		return -EIO;
	}

	lan9250_write_buf(dev, ctx->buf, LAN9250_ALIGN(len));

	for (int i = 0; i < status_size; i++) {
		lan9250_read_sys_reg(dev, LAN9250_TX_STATUS_FIFO, &tmp);
	}

	k_sem_give(&ctx->tx_rx_sem);

	return 0;
}

static void lan9250_gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
	struct lan9250_runtime *context = CONTAINER_OF(cb, struct lan9250_runtime, gpio_cb);

	k_sem_give(&context->int_sem);
}

static void lan9250_thread(void *p1, void *p2, void *p3)
{
	ARG_UNUSED(p2);
	ARG_UNUSED(p3);

	struct lan9250_runtime *context = p1;
	uint32_t int_sts;
	uint16_t tmp;
	uint32_t ier;

	while (true) {
		k_sem_take(&context->int_sem, K_FOREVER);

		/* Save interrupt enable register value */
		lan9250_read_sys_reg(context->dev, LAN9250_INT_EN, &ier);

		/* Disable interrupts to release the interrupt line */
		lan9250_write_sys_reg(context->dev, LAN9250_INT_EN, 0);

		/* Read interrupt status register */
		lan9250_read_sys_reg(context->dev, LAN9250_INT_STS, &int_sts);

		if ((int_sts & LAN9250_INT_STS_PHY_INT) != 0) {

			/* Read PHY interrupt source register */
			lan9250_read_phy_reg(context->dev, LAN9250_PHY_INTERRUPT_SOURCE, &tmp);
			if (tmp & LAN9250_PHY_INTERRUPT_SOURCE_LINK_UP) {
				LOG_DBG("LINK UP");
				net_eth_carrier_on(context->iface);
			} else if (tmp & LAN9250_PHY_INTERRUPT_SOURCE_LINK_DOWN) {
				LOG_DBG("LINK DOWN");
				net_eth_carrier_off(context->iface);
			}
		}

		if ((int_sts & LAN9250_INT_STS_RSFL) != 0) {
			lan9250_write_sys_reg(context->dev, LAN9250_INT_STS, LAN9250_INT_STS_RSFL);
			lan9250_rx(context->dev);
		}

		/* Re-enable interrupts */
		lan9250_write_sys_reg(context->dev, LAN9250_INT_EN, ier);
	}
}

static enum ethernet_hw_caps lan9250_get_capabilities(const struct device *dev)
{
	ARG_UNUSED(dev);

	return ETHERNET_LINK_10BASE | ETHERNET_LINK_100BASE;
}

static void lan9250_iface_init(struct net_if *iface)
{
	const struct device *dev = net_if_get_device(iface);
	struct lan9250_runtime *context = dev->data;

	net_if_set_link_addr(iface, context->mac_address, sizeof(context->mac_address),
			     NET_LINK_ETHERNET);
	context->iface = iface;
	ethernet_init(iface);

	net_if_carrier_off(iface);
}

static int lan9250_set_config(const struct device *dev, enum ethernet_config_type type,
			      const struct ethernet_config *config)
{
	struct lan9250_runtime *ctx = dev->data;

	if (type == ETHERNET_CONFIG_TYPE_MAC_ADDRESS) {
		memcpy(ctx->mac_address, config->mac_address.addr, sizeof(ctx->mac_address));
		lan9250_set_macaddr(dev);
		return net_if_set_link_addr(ctx->iface, ctx->mac_address, sizeof(ctx->mac_address),
					    NET_LINK_ETHERNET);
	}

	return -ENOTSUP;
}

static const struct ethernet_api api_funcs = {
	.iface_api.init = lan9250_iface_init,
	.get_capabilities = lan9250_get_capabilities,
	.set_config = lan9250_set_config,
	.send = lan9250_tx,
};

static int lan9250_init(const struct device *dev)
{
	int ret;
	const struct lan9250_config *config = dev->config;
	struct lan9250_runtime *context = dev->data;

	context->dev = dev;

	/* SPI config */
	if (!spi_is_ready_dt(&config->spi)) {
		LOG_ERR("SPI master port %s not ready", config->spi.bus->name);
		return -EINVAL;
	}

	/* Initialize GPIO */
	if (!gpio_is_ready_dt(&config->interrupt)) {
		LOG_ERR("GPIO port %s not ready", config->interrupt.port->name);
		return -EINVAL;
	}

	ret = gpio_pin_configure_dt(&config->interrupt, GPIO_INPUT);
	if (ret < 0) {
		LOG_ERR("Unable to configure GPIO pin %u", config->interrupt.pin);
		return ret;
	}

	gpio_init_callback(&(context->gpio_cb), lan9250_gpio_callback,
			   BIT(config->interrupt.pin));
	ret = gpio_add_callback(config->interrupt.port, &context->gpio_cb);
	if (ret < 0) {
		LOG_ERR("Unable to add GPIO callback %u", config->interrupt.pin);
		return ret;
	}

	ret = gpio_pin_interrupt_configure_dt(&config->interrupt,
					      GPIO_INT_EDGE_TO_ACTIVE);
	if (ret < 0) {
		LOG_ERR("Unable to enable GPIO INT %u", config->interrupt.pin);
		return ret;
	}

	if (config->reset.port != NULL) {
		if (!gpio_is_ready_dt(&config->reset)) {
			LOG_ERR("GPIO port %s not ready", config->reset.port->name);
			return -EINVAL;
		}

		ret = gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT_INACTIVE);
		if (ret < 0) {
			LOG_ERR("Unable to configure GPIO pin %u", config->reset.pin);
			return ret;
		}

		/* See Section 19.6.3 from the LAN9250 Data Sheet
		 *
		 * trstia is 200 microseconds min (use 250 us)
		 * tcfg is 15 milliseconds min (use 20 ms for after reset)
		 */
		gpio_pin_set_dt(&config->reset, 1);
		k_usleep(250);
		gpio_pin_set_dt(&config->reset, 0);
		k_msleep(20);
	}

	/* Reset and wait for ready on the LAN9250 SPI device */
	ret = lan9250_sw_reset(dev);
	if (ret < 0) {
		LOG_ERR("Reset failed");
		return ret;
	}
	lan9250_configure(dev);

	(void)net_eth_mac_load(&config->mac_cfg, context->mac_address);
	lan9250_set_macaddr(dev);

	k_thread_create(&context->thread, context->thread_stack,
			CONFIG_ETH_LAN9250_RX_THREAD_STACK_SIZE, lan9250_thread, context, NULL,
			NULL, K_PRIO_COOP(CONFIG_ETH_LAN9250_RX_THREAD_PRIO), 0, K_NO_WAIT);

	LOG_INF("LAN9250 Initialized");

	return 0;
}

#define LAN9250_DEFINE(inst)                                                                       \
	static struct lan9250_runtime lan9250_##inst##_runtime = {                                 \
		.tx_rx_sem = Z_SEM_INITIALIZER(lan9250_##inst##_runtime.tx_rx_sem, 1, UINT_MAX),   \
		.int_sem = Z_SEM_INITIALIZER(lan9250_##inst##_runtime.int_sem, 0, UINT_MAX),       \
	};                                                                                         \
                                                                                                   \
	static const struct lan9250_config lan9250_##inst##_config = {                             \
		.spi = SPI_DT_SPEC_INST_GET(inst, SPI_WORD_SET(8)),                                \
		.interrupt = GPIO_DT_SPEC_INST_GET(inst, int_gpios),                               \
		.reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {0}),                         \
		.mac_cfg = NET_ETH_MAC_DT_INST_CONFIG_INIT(inst),                                  \
	};                                                                                         \
                                                                                                   \
	ETH_NET_DEVICE_DT_INST_DEFINE(inst, lan9250_init, NULL, &lan9250_##inst##_runtime,         \
				      &lan9250_##inst##_config, CONFIG_ETH_INIT_PRIORITY,          \
				      &api_funcs, NET_ETH_MTU);
DT_INST_FOREACH_STATUS_OKAY(LAN9250_DEFINE);
