/*
 * Copyright (c) 2023 Arm Limited (or its affiliates). All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/net/ethernet.h>
#include <zephyr/net/phy.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/sys_clock.h>
#include <zephyr/drivers/mdio.h>
#include <zephyr/logging/log.h>
#include "eth_smsc91x_priv.h"

#define DT_DRV_COMPAT smsc_lan91c111

LOG_MODULE_REGISTER(eth_smsc91x, CONFIG_ETHERNET_LOG_LEVEL);

#define SMSC_LOCK(sc)	   k_mutex_lock(&(sc)->lock, K_FOREVER)
#define SMSC_UNLOCK(sc)	   k_mutex_unlock(&(sc)->lock)
#define HW_CYCLE_PER_US	   (sys_clock_hw_cycles_per_sec() / 1000000UL)
#define TX_ALLOC_WAIT_TIME 100
#define MAX_IRQ_LOOPS	   8

/*
 * MII
 */
#define MDO	 MGMT_MDO
#define MDI	 MGMT_MDI
#define MDC	 MGMT_MCLK
#define MDIRPHY	 MGMT_MDOE
#define MDIRHOST 0

#define MII_IDLE_DETECT_CYCLES 32

#define MII_COMMAND_START 0x01
#define MII_COMMAND_READ  0x02
#define MII_COMMAND_WRITE 0x01
#define MII_COMMAND_ACK	  0x02

static const char *smsc_chip_ids[16] = {
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	/* 9 */ "SMSC LAN91C11",
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
};

struct smsc_data {
	mm_reg_t smsc_reg;
	unsigned int irq;
	unsigned int smsc_chip;
	unsigned int smsc_rev;
	unsigned int smsc_mask;
	uint8_t mac[6];
	struct k_mutex lock;
	struct k_work isr_work;
};

struct eth_config {
	DEVICE_MMIO_ROM;
	const struct device *phy_dev;
};

struct eth_context {
	DEVICE_MMIO_RAM;
	struct net_if *iface;
	struct smsc_data sc;
};

static uint8_t tx_buffer[NET_ETH_MAX_FRAME_SIZE];
static uint8_t rx_buffer[NET_ETH_MAX_FRAME_SIZE];

static ALWAYS_INLINE void delay(int us)
{
	k_busy_wait(us);
}

static ALWAYS_INLINE void smsc_select_bank(struct smsc_data *sc, uint16_t bank)
{
	sys_write16(bank & BSR_BANK_MASK, sc->smsc_reg + BSR);
}

static ALWAYS_INLINE unsigned int smsc_current_bank(struct smsc_data *sc)
{
	return FIELD_GET(BSR_BANK_MASK, sys_read16(sc->smsc_reg + BSR));
}

static void smsc_mmu_wait(struct smsc_data *sc)
{
	__ASSERT((smsc_current_bank(sc) == 2), "%s called when not in bank 2", __func__);
	while (sys_read16(sc->smsc_reg + MMUCR) & MMUCR_BUSY) {
		;
	}
}

static ALWAYS_INLINE uint8_t smsc_read_1(struct smsc_data *sc, int offset)
{
	return sys_read8(sc->smsc_reg + offset);
}

static ALWAYS_INLINE uint16_t smsc_read_2(struct smsc_data *sc, int offset)
{
	return sys_read16(sc->smsc_reg + offset);
}

static ALWAYS_INLINE void smsc_read_multi_2(struct smsc_data *sc, int offset, uint16_t *datap,
					    uint16_t count)
{
	while (count--) {
		*datap++ = sys_read16(sc->smsc_reg + offset);
	}
}

static ALWAYS_INLINE void smsc_write_1(struct smsc_data *sc, int offset, uint8_t val)
{
	sys_write8(val, sc->smsc_reg + offset);
}

static ALWAYS_INLINE void smsc_write_2(struct smsc_data *sc, int offset, uint16_t val)
{
	sys_write16(val, sc->smsc_reg + offset);
}

static ALWAYS_INLINE void smsc_write_multi_2(struct smsc_data *sc, int offset, uint16_t *datap,
					     uint16_t count)
{
	while (count--) {
		sys_write16(*datap++, sc->smsc_reg + offset);
	}
}

static uint32_t smsc_mii_bitbang_read(struct smsc_data *sc)
{
	uint16_t val;

	__ASSERT(FIELD_GET(BSR_BANK_MASK, smsc_read_2(sc, BSR)) == 3,
		 "%s called with bank %d (!=3)", __func__,
		 FIELD_GET(BSR_BANK_MASK, smsc_read_2(sc, BSR)));

	val = smsc_read_2(sc, MGMT);
	delay(1); /* Simulate a timing sequence */

	return val;
}

static void smsc_mii_bitbang_write(struct smsc_data *sc, uint16_t val)
{
	__ASSERT(FIELD_GET(BSR_BANK_MASK, smsc_read_2(sc, BSR)) == 3,
		 "%s called with bank %d (!=3)", __func__,
		 FIELD_GET(BSR_BANK_MASK, smsc_read_2(sc, BSR)));

	smsc_write_2(sc, MGMT, val);
	delay(1); /* Simulate a timing sequence */
}

static void smsc_miibus_sync(struct smsc_data *sc)
{
	int i;
	uint32_t v;

	v = MDIRPHY | MDO;

	smsc_mii_bitbang_write(sc, v);
	for (i = 0; i < MII_IDLE_DETECT_CYCLES; i++) {
		smsc_mii_bitbang_write(sc, v | MDC);
		smsc_mii_bitbang_write(sc, v);
	}
}

static void smsc_miibus_sendbits(struct smsc_data *sc, uint32_t data, int nbits)
{
	int i;
	uint32_t v;

	v = MDIRPHY;
	smsc_mii_bitbang_write(sc, v);

	for (i = 1 << (nbits - 1); i != 0; i >>= 1) {
		if (data & i) {
			v |= MDO;
		} else {
			v &= ~MDO;
		}

		smsc_mii_bitbang_write(sc, v);
		smsc_mii_bitbang_write(sc, v | MDC);
		smsc_mii_bitbang_write(sc, v);
	}
}

static int smsc_miibus_readreg(struct smsc_data *sc, int phy, int reg)
{
	int i, err, val;

	irq_disable(sc->irq);
	SMSC_LOCK(sc);

	smsc_select_bank(sc, 3);

	smsc_miibus_sync(sc);

	smsc_miibus_sendbits(sc, MII_COMMAND_START, 2);
	smsc_miibus_sendbits(sc, MII_COMMAND_READ, 2);
	smsc_miibus_sendbits(sc, phy, 5);
	smsc_miibus_sendbits(sc, reg, 5);

	/* Switch direction to PHY -> host */
	smsc_mii_bitbang_write(sc, MDIRHOST);
	smsc_mii_bitbang_write(sc, MDIRHOST | MDC);
	smsc_mii_bitbang_write(sc, MDIRHOST);

	/* Check for error. */
	err = smsc_mii_bitbang_read(sc) & MDI;

	/* Idle clock. */
	smsc_mii_bitbang_write(sc, MDIRHOST | MDC);
	smsc_mii_bitbang_write(sc, MDIRHOST);

	val = 0;
	for (i = 0; i < 16; i++) {
		val <<= 1;
		/* Read data prior to clock low-high transition. */
		if (err == 0 && (smsc_mii_bitbang_read(sc) & MDI) != 0) {
			val |= 1;
		}

		smsc_mii_bitbang_write(sc, MDIRHOST | MDC);
		smsc_mii_bitbang_write(sc, MDIRHOST);
	}

	/* Set direction to host -> PHY, without a clock transition. */
	smsc_mii_bitbang_write(sc, MDIRPHY);

	SMSC_UNLOCK(sc);
	irq_enable(sc->irq);

	return (err == 0 ? val : 0);
}

static void smsc_miibus_writereg(struct smsc_data *sc, int phy, int reg, uint16_t val)
{
	irq_disable(sc->irq);
	SMSC_LOCK(sc);

	smsc_select_bank(sc, 3);

	smsc_miibus_sync(sc);

	smsc_miibus_sendbits(sc, MII_COMMAND_START, 2);
	smsc_miibus_sendbits(sc, MII_COMMAND_WRITE, 2);
	smsc_miibus_sendbits(sc, phy, 5);
	smsc_miibus_sendbits(sc, reg, 5);
	smsc_miibus_sendbits(sc, MII_COMMAND_ACK, 2);
	smsc_miibus_sendbits(sc, val, 16);

	smsc_mii_bitbang_write(sc, MDIRPHY);

	SMSC_UNLOCK(sc);
	irq_enable(sc->irq);
}

static void smsc_reset(struct smsc_data *sc)
{
	uint16_t ctr;

	/*
	 * Mask all interrupts
	 */
	smsc_select_bank(sc, 2);
	smsc_write_1(sc, MSK, 0);

	/*
	 * Tell the device to reset
	 */
	smsc_select_bank(sc, 0);
	smsc_write_2(sc, RCR, RCR_SOFT_RST);

	/*
	 * Set up the configuration register
	 */
	smsc_select_bank(sc, 1);
	smsc_write_2(sc, CR, CR_EPH_POWER_EN);
	delay(1);

	/*
	 * Turn off transmit and receive.
	 */
	smsc_select_bank(sc, 0);
	smsc_write_2(sc, TCR, 0);
	smsc_write_2(sc, RCR, 0);

	/*
	 * Set up the control register
	 */
	smsc_select_bank(sc, 1);
	ctr = smsc_read_2(sc, CTR);
	ctr |= CTR_LE_ENABLE | CTR_AUTO_RELEASE;
	smsc_write_2(sc, CTR, ctr);

	/*
	 * Reset the MMU
	 */
	smsc_select_bank(sc, 2);
	smsc_mmu_wait(sc);
	smsc_write_2(sc, MMUCR, FIELD_PREP(MMUCR_CMD_MASK, MMUCR_CMD_MMU_RESET));
	smsc_mmu_wait(sc);
}

static void smsc_enable(struct smsc_data *sc)
{
	/*
	 * Set up the receive/PHY control register.
	 */
	smsc_select_bank(sc, 0);
	smsc_write_2(sc, RPCR,
		     RPCR_ANEG | RPCR_DPLX | RPCR_SPEED |
		     FIELD_PREP(RPCR_LSA_MASK, RPCR_LED_LINK_ANY) |
		     FIELD_PREP(RPCR_LSB_MASK, RPCR_LED_ACT_ANY));

	/*
	 * Set up the transmit and receive control registers.
	 */
	smsc_write_2(sc, TCR, TCR_TXENA | TCR_PAD_EN);
	smsc_write_2(sc, RCR, RCR_RXEN | RCR_STRIP_CRC);

	/*
	 * Clear all interrupt status
	 */
	smsc_select_bank(sc, 2);
	smsc_write_1(sc, ACK, 0);

	/*
	 * Set up the interrupt mask
	 */
	smsc_select_bank(sc, 2);
	sc->smsc_mask = RCV_INT;
	smsc_write_1(sc, MSK, sc->smsc_mask);
}

static int smsc_check(struct smsc_data *sc)
{
	uint16_t val;

	val = smsc_read_2(sc, BSR);
	if (FIELD_GET(BSR_IDENTIFY_MASK, val) != BSR_IDENTIFY) {
		LOG_ERR("Identification value not in BSR");
		return -ENODEV;
	}

	smsc_write_2(sc, BSR, 0);
	val = smsc_read_2(sc, BSR);
	if (FIELD_GET(BSR_IDENTIFY_MASK, val) != BSR_IDENTIFY) {
		LOG_ERR("Identification value not in BSR after write");
		return -ENODEV;
	}

	smsc_select_bank(sc, 3);
	val = smsc_read_2(sc, REV);
	val = FIELD_GET(REV_CHIP_MASK, val);
	if (smsc_chip_ids[val] == NULL) {
		LOG_ERR("Unknown chip revision: %d", val);
		return -ENODEV;
	}

	return 0;
}

static void smsc_recv_pkt(struct eth_context *data)
{
	struct net_pkt *pkt;
	unsigned int packet, status, len;
	struct smsc_data *sc = &data->sc;
	uint16_t val16;
	int ret;

	smsc_select_bank(sc, 2);
	packet = smsc_read_1(sc, FIFO_RX);
	while ((packet & FIFO_EMPTY) == 0) {
		/*
		 * Point to the start of the packet.
		 */
		smsc_select_bank(sc, 2);
		smsc_write_1(sc, PNR, packet);
		smsc_write_2(sc, PTR, PTR_READ | PTR_RCV | PTR_AUTO_INCR);

		/*
		 * Grab status and packet length.
		 */
		status = smsc_read_2(sc, DATA0);
		val16 = smsc_read_2(sc, DATA0);
		len = FIELD_GET(RX_LEN_MASK, val16);
		if (len < PKT_CTRL_DATA_LEN) {
			LOG_WRN("rxlen(%d) too short", len);
		} else {
			len -= PKT_CTRL_DATA_LEN;
			if (status & RX_ODDFRM) {
				len += 1;
			}

			if (len > NET_ETH_MAX_FRAME_SIZE) {
				LOG_WRN("rxlen(%d) too large", len);
				goto _mmu_release;
			}

			/*
			 * Check for errors.
			 */
			if (status & (RX_TOOSHORT | RX_TOOLNG | RX_BADCRC | RX_ALIGNERR)) {
				LOG_WRN("status word (0x%04x) indicate some error", status);
				goto _mmu_release;
			}

			/*
			 * Pull the packet out of the device.
			 */
			smsc_select_bank(sc, 2);
			smsc_write_1(sc, PNR, packet);

			/*
			 * Pointer start from 4 because we have already read status and len from
			 * RX_FIFO
			 */
			smsc_write_2(sc, PTR, 4 | PTR_READ | PTR_RCV | PTR_AUTO_INCR);
			smsc_read_multi_2(sc, DATA0, (uint16_t *)rx_buffer, len / 2);
			if (len & 1) {
				rx_buffer[len - 1] = smsc_read_1(sc, DATA0);
			}

			pkt = net_pkt_rx_alloc_with_buffer(data->iface, len, AF_UNSPEC, 0,
							   K_NO_WAIT);
			if (!pkt) {
				LOG_ERR("Failed to obtain RX buffer");
				goto _mmu_release;
			}

			ret = net_pkt_write(pkt, rx_buffer, len);
			if (ret) {
				net_pkt_unref(pkt);
				LOG_WRN("net_pkt_write return %d", ret);
				goto _mmu_release;
			}

			ret = net_recv_data(data->iface, pkt);
			if (ret) {
				LOG_WRN("net_recv_data return %d", ret);
				net_pkt_unref(pkt);
			}
		}

_mmu_release:
		/*
		 * Tell the device we're done
		 */
		smsc_mmu_wait(sc);
		smsc_write_2(sc, MMUCR, FIELD_PREP(MMUCR_CMD_MASK, MMUCR_CMD_RELEASE));
		smsc_mmu_wait(sc);

		packet = smsc_read_1(sc, FIFO_RX);
	}

	sc->smsc_mask |= RCV_INT;
	smsc_write_1(sc, MSK, sc->smsc_mask);
}

static int smsc_send_pkt(struct smsc_data *sc, uint8_t *buf, uint16_t len)
{
	unsigned int polling_count;
	uint8_t packet;

	SMSC_LOCK(sc);
	/*
	 * Request memory
	 */
	smsc_select_bank(sc, 2);
	smsc_mmu_wait(sc);
	smsc_write_2(sc, MMUCR, FIELD_PREP(MMUCR_CMD_MASK, MMUCR_CMD_TX_ALLOC));

	/*
	 * Polling if the allocation succeeds.
	 */
	for (polling_count = TX_ALLOC_WAIT_TIME; polling_count > 0; polling_count--) {
		if (smsc_read_1(sc, IST) & ALLOC_INT) {
			break;
		}

		delay(1);
	}

	if (polling_count == 0) {
		SMSC_UNLOCK(sc);
		LOG_WRN("Alloc TX mem timeout");
		return -1;
	}

	packet = smsc_read_1(sc, ARR);
	if (packet & ARR_FAILED) {
		SMSC_UNLOCK(sc);
		LOG_WRN("Alloc TX mem failed");
		return -1;
	}

	/*
	 * Tell the device to write to our packet number.
	 */
	smsc_write_1(sc, PNR, packet);
	smsc_write_2(sc, PTR, PTR_AUTO_INCR);

	/*
	 * Tell the device how long the packet is (include control data).
	 */
	smsc_write_2(sc, DATA0, 0);
	smsc_write_2(sc, DATA0, len + PKT_CTRL_DATA_LEN);
	smsc_write_multi_2(sc, DATA0, (uint16_t *)buf, len / 2);

	/* Push out the control byte and the odd byte if needed. */
	if (len & 1) {
		smsc_write_2(sc, DATA0, (CTRL_ODD << 8) | buf[len - 1]);
	} else {
		smsc_write_2(sc, DATA0, 0);
	}

	/*
	 * Enqueue the packet.
	 */
	smsc_mmu_wait(sc);
	smsc_write_2(sc, MMUCR, FIELD_PREP(MMUCR_CMD_MASK, MMUCR_CMD_ENQUEUE));

	/*
	 * Unmask the TX empty interrupt
	 */
	sc->smsc_mask |= (TX_EMPTY_INT | TX_INT);
	smsc_write_1(sc, MSK, sc->smsc_mask);

	SMSC_UNLOCK(sc);

	/*
	 * Finish up
	 */
	return 0;
}

static void smsc_isr_task(struct k_work *item)
{
	struct smsc_data *sc = CONTAINER_OF(item, struct smsc_data, isr_work);
	struct eth_context *data = CONTAINER_OF(sc, struct eth_context, sc);
	uint8_t status;
	unsigned int mem_info, ephsr, packet, tcr;

	SMSC_LOCK(sc);

	for (int loop_count = 0; loop_count < MAX_IRQ_LOOPS; loop_count++) {
		smsc_select_bank(sc, 0);
		mem_info = smsc_read_2(sc, MIR);

		smsc_select_bank(sc, 2);
		status = smsc_read_1(sc, IST);
		LOG_DBG("INT 0x%02x MASK 0x%02x MEM 0x%04x FIFO 0x%04x", status,
			smsc_read_1(sc, MSK), mem_info, smsc_read_2(sc, FIFO));

		status &= sc->smsc_mask;
		if (!status) {
			break;
		}

		/*
		 * Transmit error
		 */
		if (status & TX_INT) {
			/*
			 * Kill off the packet if there is one.
			 */
			packet = smsc_read_1(sc, FIFO_TX);
			if ((packet & FIFO_EMPTY) == 0) {
				smsc_select_bank(sc, 2);
				smsc_write_1(sc, PNR, packet);
				smsc_write_2(sc, PTR, PTR_READ | PTR_AUTO_INCR);

				smsc_select_bank(sc, 0);
				ephsr = smsc_read_2(sc, EPHSR);

				if ((ephsr & EPHSR_TX_SUC) == 0) {
					LOG_WRN("bad packet, EPHSR: 0x%04x", ephsr);
				}

				smsc_select_bank(sc, 2);
				smsc_mmu_wait(sc);
				smsc_write_2(sc, MMUCR,
					     FIELD_PREP(MMUCR_CMD_MASK, MMUCR_CMD_RELEASE_PKT));

				smsc_select_bank(sc, 0);
				tcr = smsc_read_2(sc, TCR);
				tcr |= TCR_TXENA | TCR_PAD_EN;
				smsc_write_2(sc, TCR, tcr);
			}

			/*
			 * Ack the interrupt
			 */
			smsc_select_bank(sc, 2);
			smsc_write_1(sc, ACK, TX_INT);
		}

		/*
		 * Receive
		 */
		if (status & RCV_INT) {
			smsc_write_1(sc, ACK, RCV_INT);
			smsc_recv_pkt(data);
		}

		/*
		 * Transmit empty
		 */
		if (status & TX_EMPTY_INT) {
			smsc_write_1(sc, ACK, TX_EMPTY_INT);
			sc->smsc_mask &= ~TX_EMPTY_INT;
		}
	}

	smsc_select_bank(sc, 2);
	smsc_write_1(sc, MSK, sc->smsc_mask);

	SMSC_UNLOCK(sc);
}

static int smsc_init(struct smsc_data *sc)
{
	int ret;
	unsigned int val;

	ret = smsc_check(sc);
	if (ret) {
		return ret;
	}

	SMSC_LOCK(sc);
	smsc_reset(sc);
	SMSC_UNLOCK(sc);

	smsc_select_bank(sc, 3);
	val = smsc_read_2(sc, REV);
	sc->smsc_chip = FIELD_GET(REV_CHIP_MASK, val);
	sc->smsc_rev = FIELD_GET(REV_REV_MASK, val);

	smsc_select_bank(sc, 1);
	sc->mac[0] = smsc_read_1(sc, IAR0);
	sc->mac[1] = smsc_read_1(sc, IAR1);
	sc->mac[2] = smsc_read_1(sc, IAR2);
	sc->mac[3] = smsc_read_1(sc, IAR3);
	sc->mac[4] = smsc_read_1(sc, IAR4);
	sc->mac[5] = smsc_read_1(sc, IAR5);

	return 0;
}

static const struct device *eth_get_phy(const struct device *dev)
{
	const struct eth_config *cfg = dev->config;

	return cfg->phy_dev;
}

static void phy_link_state_changed(const struct device *phy_dev, struct phy_link_state *state,
				   void *user_data)
{
	const struct device *dev = user_data;
	struct eth_context *data = dev->data;

	if (state->is_up) {
		net_eth_carrier_on(data->iface);
	} else {
		net_eth_carrier_off(data->iface);
	}
}

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

	return (ETHERNET_LINK_10BASE
		| ETHERNET_LINK_100BASE
#if defined(CONFIG_NET_PROMISCUOUS_MODE)
		| ETHERNET_PROMISC_MODE
#endif
	);
}

static int eth_tx(const struct device *dev, struct net_pkt *pkt)
{
	struct eth_context *data = dev->data;
	struct smsc_data *sc = &data->sc;
	uint16_t len;

	len = net_pkt_get_len(pkt);
	if (net_pkt_read(pkt, tx_buffer, len)) {
		LOG_WRN("read pkt failed");
		return -1;
	}

	return smsc_send_pkt(sc, tx_buffer, len);
}

static int eth_smsc_set_config(const struct device *dev,
			       enum ethernet_config_type type,
			       const struct ethernet_config *config)
{
	int ret = 0;

	switch (type) {
#if defined(CONFIG_NET_PROMISCUOUS_MODE)
	case ETHERNET_CONFIG_TYPE_PROMISC_MODE:
		struct eth_context *data = dev->data;
		struct smsc_data *sc = &data->sc;
		uint8_t reg_val;

		SMSC_LOCK(sc);
		smsc_select_bank(sc, 0);
		reg_val = smsc_read_1(sc, RCR);
		if (config->promisc_mode && !(reg_val & RCR_PRMS)) {
			smsc_write_1(sc, RCR, reg_val | RCR_PRMS);
		} else if (!config->promisc_mode && (reg_val & RCR_PRMS)) {
			smsc_write_1(sc, RCR, reg_val & ~RCR_PRMS);
		} else {
			ret = -EALREADY;
		}
		SMSC_UNLOCK(sc);
		break;
#endif

	default:
		ret = -ENOTSUP;
		break;
	}

	return ret;
}

static void eth_initialize(struct net_if *iface)
{
	const struct device *dev = net_if_get_device(iface);
	struct eth_context *data = dev->data;
	const struct eth_config *cfg = dev->config;
	const struct device *phy_dev = cfg->phy_dev;
	struct smsc_data *sc = &data->sc;

	ethernet_init(iface);

	net_if_carrier_off(iface);

	smsc_reset(sc);
	smsc_enable(sc);

	LOG_INF("MAC %02x:%02x:%02x:%02x:%02x:%02x", sc->mac[0], sc->mac[1], sc->mac[2], sc->mac[3],
		sc->mac[4], sc->mac[5]);

	net_if_set_link_addr(iface, sc->mac, sizeof(sc->mac), NET_LINK_ETHERNET);
	data->iface = iface;

	if (device_is_ready(phy_dev)) {
		phy_link_callback_set(phy_dev, phy_link_state_changed, (void *)dev);
	} else {
		LOG_ERR("PHY device not ready");
	}
}

static const struct ethernet_api api_funcs = {
	.iface_api.init   = eth_initialize,
	.get_capabilities = eth_smsc_get_caps,
	.get_phy          = eth_get_phy,
	.set_config       = eth_smsc_set_config,
	.send             = eth_tx,
};

static void eth_smsc_isr(const struct device *dev)
{
	struct eth_context *data = dev->data;
	struct smsc_data *sc = &data->sc;
	uint32_t curbank;

	curbank = smsc_current_bank(sc);

	/*
	 * Block interrupts in order to let smsc91x_isr_task to kick in
	 */
	smsc_select_bank(sc, 2);
	smsc_write_1(sc, MSK, 0);

	smsc_select_bank(sc, curbank);
	k_work_submit(&(sc->isr_work));
}

int eth_init(const struct device *dev)
{
	struct eth_context *data = (struct eth_context *)dev->data;
	struct smsc_data *sc = &data->sc;
	int ret;

	ret = k_mutex_init(&sc->lock);
	if (ret) {
		return ret;
	}

	k_work_init(&sc->isr_work, smsc_isr_task);

	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), eth_smsc_isr, DEVICE_DT_INST_GET(0),
		    0);

	DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
	sc->smsc_reg = DEVICE_MMIO_GET(dev);
	sc->irq = DT_INST_IRQN(0);

	smsc_init(sc);

	irq_enable(DT_INST_IRQN(0));

	return 0;
}

static struct eth_context eth_0_context;

static struct eth_config eth_0_config = {
	DEVICE_MMIO_ROM_INIT(DT_PARENT(DT_DRV_INST(0))),
	.phy_dev = DEVICE_DT_GET(DT_INST_PHANDLE(0, phy_handle)),
};

ETH_NET_DEVICE_DT_INST_DEFINE(0,
	eth_init, NULL, &eth_0_context,
	&eth_0_config, CONFIG_ETH_INIT_PRIORITY,
	&api_funcs, NET_ETH_MTU);

#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT smsc_lan91c111_mdio

struct mdio_smsc_config {
	const struct device *eth_dev;
};

static int mdio_smsc_read(const struct device *dev, uint8_t prtad, uint8_t devad, uint16_t *data)
{
	const struct mdio_smsc_config *cfg = dev->config;
	const struct device *eth_dev = cfg->eth_dev;
	struct eth_context *eth_data = eth_dev->data;
	struct smsc_data *sc = &eth_data->sc;

	*data = smsc_miibus_readreg(sc, prtad, devad);

	return 0;
}

static int mdio_smsc_write(const struct device *dev, uint8_t prtad, uint8_t devad, uint16_t data)
{
	const struct mdio_smsc_config *cfg = dev->config;
	const struct device *eth_dev = cfg->eth_dev;
	struct eth_context *eth_data = eth_dev->data;
	struct smsc_data *sc = &eth_data->sc;

	smsc_miibus_writereg(sc, prtad, devad, data);

	return 0;
}

static DEVICE_API(mdio, mdio_smsc_api) = {
	.read = mdio_smsc_read,
	.write = mdio_smsc_write,
};

const struct mdio_smsc_config mdio_smsc_config_0 = {
	.eth_dev = DEVICE_DT_GET(DT_CHILD(DT_INST_PARENT(0), ethernet)),
};

DEVICE_DT_INST_DEFINE(0, NULL, NULL, NULL, &mdio_smsc_config_0, POST_KERNEL,
		      CONFIG_MDIO_INIT_PRIORITY, &mdio_smsc_api);
