/* ieee802154_cc2520.c - TI CC2520 driver */

#define DT_DRV_COMPAT ti_cc2520

/*
 * Copyright (c) 2016 Intel Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define LOG_MODULE_NAME ieee802154_cc2520
#define LOG_LEVEL CONFIG_IEEE802154_DRIVER_LOG_LEVEL

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME);

#include <errno.h>

#include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/debug/stack.h>

#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_pkt.h>

#include <zephyr/sys/byteorder.h>
#include <string.h>
#include <zephyr/random/rand32.h>

#include <zephyr/drivers/gpio.h>

#ifdef CONFIG_IEEE802154_CC2520_CRYPTO

#include <zephyr/crypto/crypto.h>
#include <zephyr/crypto/cipher.h>

#endif /* CONFIG_IEEE802154_CC2520_CRYPTO */

#include <zephyr/net/ieee802154_radio.h>

#include "ieee802154_cc2520.h"

/**
 * Content is split as follows:
 * 1 - Debug related functions
 * 2 - Generic helper functions (for any parts)
 * 3 - GPIO related functions
 * 4 - TX related helper functions
 * 5 - RX related helper functions
 * 6 - Radio device API functions
 * 7 - Legacy radio device API functions
 * 8 - Initialization
 */


#define CC2520_AUTOMATISM		(FRMCTRL0_AUTOCRC | FRMCTRL0_AUTOACK)
#define CC2520_TX_THRESHOLD		(0x7F)
#define CC2520_FCS_LENGTH		(2)

/*********
 * DEBUG *
 ********/
#if LOG_LEVEL == LOG_LEVEL_DBG
static inline void cc2520_print_gpio_config(const struct device *dev)
{
	struct cc2520_context *cc2520 = dev->data;

	LOG_DBG("GPIOCTRL0/1/2/3/4/5 = 0x%x/0x%x/0x%x/0x%x/0x%x/0x%x",
		read_reg_gpioctrl0(cc2520),
		read_reg_gpioctrl1(cc2520),
		read_reg_gpioctrl2(cc2520),
		read_reg_gpioctrl3(cc2520),
		read_reg_gpioctrl4(cc2520),
		read_reg_gpioctrl5(cc2520));
	LOG_DBG("GPIOPOLARITY: 0x%x",
		read_reg_gpiopolarity(cc2520));
	LOG_DBG("GPIOCTRL: 0x%x",
		read_reg_gpioctrl(cc2520));
}

static inline void cc2520_print_exceptions(struct cc2520_context *cc2520)
{
	uint8_t flag = read_reg_excflag0(cc2520);

	LOG_DBG("EXCFLAG0:");

	if (flag & EXCFLAG0_RF_IDLE) {
		LOG_DBG(" RF_IDLE");
	}

	if (flag & EXCFLAG0_TX_FRM_DONE) {
		LOG_DBG(" TX_FRM_DONE");
	}

	if (flag & EXCFLAG0_TX_ACK_DONE) {
		LOG_DBG(" TX_ACK_DONE");
	}

	if (flag & EXCFLAG0_TX_UNDERFLOW) {
		LOG_DBG(" TX_UNDERFLOW");
	}

	if (flag & EXCFLAG0_TX_OVERFLOW) {
		LOG_DBG(" TX_OVERFLOW");
	}

	if (flag & EXCFLAG0_RX_UNDERFLOW) {
		LOG_DBG(" RX_UNDERFLOW");
	}

	if (flag & EXCFLAG0_RX_OVERFLOW) {
		LOG_DBG(" RX_OVERFLOW");
	}

	if (flag & EXCFLAG0_RXENABLE_ZERO) {
		LOG_DBG(" RXENABLE_ZERO");
	}

	flag = read_reg_excflag1(cc2520);

	LOG_DBG("EXCFLAG1:");

	if (flag & EXCFLAG1_RX_FRM_DONE) {
		LOG_DBG(" RX_FRM_DONE");
	}

	if (flag & EXCFLAG1_RX_FRM_ACCEPTED) {
		LOG_DBG(" RX_FRM_ACCEPTED");
	}

	if (flag & EXCFLAG1_SRC_MATCH_DONE) {
		LOG_DBG(" SRC_MATCH_DONE");
	}

	if (flag & EXCFLAG1_SRC_MATCH_FOUND) {
		LOG_DBG(" SRC_MATCH_FOUND");
	}

	if (flag & EXCFLAG1_FIFOP) {
		LOG_DBG(" FIFOP");
	}

	if (flag & EXCFLAG1_SFD) {
		LOG_DBG(" SFD");
	}

	if (flag & EXCFLAG1_DPU_DONE_L) {
		LOG_DBG(" DPU_DONE_L");
	}

	if (flag & EXCFLAG1_DPU_DONE_H) {
		LOG_DBG(" DPU_DONE_H");
	}
}

static inline void cc2520_print_errors(struct cc2520_context *cc2520)
{
	uint8_t flag = read_reg_excflag2(cc2520);

	LOG_DBG("EXCFLAG2:");

	if (flag & EXCFLAG2_MEMADDR_ERROR) {
		LOG_DBG(" MEMADDR_ERROR");
	}

	if (flag & EXCFLAG2_USAGE_ERROR) {
		LOG_DBG(" USAGE_ERROR");
	}

	if (flag & EXCFLAG2_OPERAND_ERROR) {
		LOG_DBG(" OPERAND_ERROR");
	}

	if (flag & EXCFLAG2_SPI_ERROR) {
		LOG_DBG(" SPI_ERROR");
	}

	if (flag & EXCFLAG2_RF_NO_LOCK) {
		LOG_DBG(" RF_NO_LOCK");
	}

	if (flag & EXCFLAG2_RX_FRM_ABORTED) {
		LOG_DBG(" RX_FRM_ABORTED");
	}

	if (flag & EXCFLAG2_RFBUFMOV_TIMEOUT) {
		LOG_DBG(" RFBUFMOV_TIMEOUT");
	}
}
#else
#define cc2520_print_gpio_config(...)
#define cc2520_print_exceptions(...)
#define cc2520_print_errors(...)
#endif /* LOG_LEVEL == LOG_LEVEL_DBG */


/*********************
 * Generic functions *
 ********************/
#define z_usleep(usec) k_busy_wait(usec)

bool z_cc2520_access(const struct device *dev, bool read, uint8_t ins,
		     uint16_t addr, void *data, size_t length)
{
	const struct cc2520_config *cfg = dev->config;
	uint8_t cmd_buf[2];
	struct spi_buf buf[2] = {
		{
			.buf = cmd_buf,
			.len = 1,
		},
		{
			.buf = data,
			.len = length,

		}
	};
	struct spi_buf_set tx = {
		.buffers = buf,
	};


	cmd_buf[0] = ins;

	if (ins == CC2520_INS_MEMRD || ins == CC2520_INS_MEMWR) {
		buf[0].len = 2;
		cmd_buf[0] |= (uint8_t)(addr >> 8);
		cmd_buf[1] = (uint8_t)(addr & 0xff);
	} else if (ins == CC2520_INS_REGRD || ins == CC2520_INS_REGWR) {
		cmd_buf[0] |= (uint8_t)(addr & 0xff);
	}

	if (read) {
		const struct spi_buf_set rx = {
			.buffers = buf,
			.count = 2
		};

		tx.count = 1;

		return (spi_transceive_dt(&cfg->bus, &tx, &rx) == 0);
	}

	tx.count = data ? 2 : 1;

	return (spi_write_dt(&cfg->bus, &tx) == 0);
}

static inline uint8_t cc2520_status(const struct device *dev)
{
	uint8_t status;

	if (z_cc2520_access(dev, true, CC2520_INS_SNOP, 0, &status, 1)) {
		return status;
	}

	return 0;
}

static bool verify_osc_stabilization(const struct device *dev)
{
	uint8_t timeout = 100U;
	uint8_t status;

	do {
		status = cc2520_status(dev);
		z_usleep(1);
		timeout--;
	} while (!(status & CC2520_STATUS_XOSC_STABLE_N_RUNNING) && timeout);

	return !!(status & CC2520_STATUS_XOSC_STABLE_N_RUNNING);
}


static inline uint8_t *get_mac(const struct device *dev)
{
	struct cc2520_context *cc2520 = dev->data;

#if defined(CONFIG_IEEE802154_CC2520_RANDOM_MAC)
	uint32_t *ptr = (uint32_t *)(cc2520->mac_addr + 4);

	UNALIGNED_PUT(sys_rand32_get(), ptr);

	cc2520->mac_addr[7] = (cc2520->mac_addr[7] & ~0x01) | 0x02;
#else
	cc2520->mac_addr[4] = CONFIG_IEEE802154_CC2520_MAC4;
	cc2520->mac_addr[5] = CONFIG_IEEE802154_CC2520_MAC5;
	cc2520->mac_addr[6] = CONFIG_IEEE802154_CC2520_MAC6;
	cc2520->mac_addr[7] = CONFIG_IEEE802154_CC2520_MAC7;
#endif

	cc2520->mac_addr[0] = 0x00;
	cc2520->mac_addr[1] = 0x12;
	cc2520->mac_addr[2] = 0x4b;
	cc2520->mac_addr[3] = 0x00;

	return cc2520->mac_addr;
}

static int cc2520_set_pan_id(const struct device *dev, uint16_t pan_id)
{
	LOG_DBG("0x%x", pan_id);

	pan_id = sys_le16_to_cpu(pan_id);

	if (!write_mem_pan_id(dev, (uint8_t *) &pan_id)) {
		LOG_ERR("Failed");
		return -EIO;
	}

	return 0;
}

static int cc2520_set_short_addr(const struct device *dev,
				 uint16_t short_addr)
{
	LOG_DBG("0x%x", short_addr);

	short_addr = sys_le16_to_cpu(short_addr);

	if (!write_mem_short_addr(dev, (uint8_t *) &short_addr)) {
		LOG_ERR("Failed");
		return -EIO;
	}

	return 0;
}

static int cc2520_set_ieee_addr(const struct device *dev,
				const uint8_t *ieee_addr)
{
	if (!write_mem_ext_addr(dev, (void *)ieee_addr)) {
		LOG_ERR("Failed");
		return -EIO;
	}

	LOG_DBG("IEEE address %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
		ieee_addr[7], ieee_addr[6], ieee_addr[5], ieee_addr[4],
		ieee_addr[3], ieee_addr[2], ieee_addr[1], ieee_addr[0]);

	return 0;
}

/******************
 * GPIO functions *
 *****************/
static inline void set_reset(const struct device *dev, uint32_t value)
{
	const struct cc2520_config *cfg = dev->config;

	gpio_pin_set_raw(cfg->reset.port, cfg->reset.pin, value);
}

static inline void set_vreg_en(const struct device *dev, uint32_t value)
{
	const struct cc2520_config *cfg = dev->config;

	gpio_pin_set_raw(cfg->vreg_en.port, cfg->vreg_en.pin, value);
}

static inline uint32_t get_fifo(const struct device *dev)
{
	const struct cc2520_config *cfg = dev->config;

	return gpio_pin_get_raw(cfg->fifo.port, cfg->fifo.pin);
}

static inline uint32_t get_fifop(const struct device *dev)
{
	const struct cc2520_config *cfg = dev->config;

	return gpio_pin_get_raw(cfg->fifop.port, cfg->fifop.pin);
}

static inline uint32_t get_cca(const struct device *dev)
{
	const struct cc2520_config *cfg = dev->config;

	return gpio_pin_get_raw(cfg->cca.port, cfg->cca.pin);
}

static inline void sfd_int_handler(const struct device *port,
				   struct gpio_callback *cb, uint32_t pins)
{
	struct cc2520_context *cc2520 =
		CONTAINER_OF(cb, struct cc2520_context, sfd_cb);

	if (atomic_get(&cc2520->tx) == 1) {
		atomic_set(&cc2520->tx, 0);
		k_sem_give(&cc2520->tx_sync);
	}
}

static inline void fifop_int_handler(const struct device *port,
				     struct gpio_callback *cb, uint32_t pins)
{
	struct cc2520_context *cc2520 =
		CONTAINER_OF(cb, struct cc2520_context, fifop_cb);

	/* Note: Errata document - 1.2 */
	if (!get_fifop(cc2520->dev) && !get_fifop(cc2520->dev)) {
		return;
	}

	if (!get_fifo(cc2520->dev)) {
		cc2520->overflow = true;
	}

	k_sem_give(&cc2520->rx_lock);
}

static void enable_fifop_interrupt(const struct device *dev,
				   bool enable)
{
	const struct cc2520_config *cfg = dev->config;
	gpio_flags_t mode = enable ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE;

	gpio_pin_interrupt_configure_dt(&cfg->fifop, mode);
}

static void enable_sfd_interrupt(const struct device *dev,
				 bool enable)
{
	const struct cc2520_config *cfg = dev->config;
	gpio_flags_t mode = enable ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE;

	gpio_pin_interrupt_configure_dt(&cfg->sfd, mode);
}

static inline int setup_gpio_callbacks(const struct device *dev)
{
	const struct cc2520_config *cfg = dev->config;
	struct cc2520_context *cc2520 = dev->data;

	gpio_init_callback(&cc2520->sfd_cb, sfd_int_handler, BIT(cfg->sfd.pin));
	if (gpio_add_callback(cfg->sfd.port, &cc2520->sfd_cb) != 0) {
		return -EIO;
	}


	gpio_init_callback(&cc2520->fifop_cb, fifop_int_handler, BIT(cfg->fifop.pin));
	if (gpio_add_callback(cfg->fifop.port, &cc2520->fifop_cb) != 0) {
		return -EIO;
	}

	return 0;
}


/****************
 * TX functions *
 ***************/
static inline bool write_txfifo_length(const struct device *dev, uint8_t len)
{
	uint8_t length = len + CC2520_FCS_LENGTH;

	return z_cc2520_access(dev, false, CC2520_INS_TXBUF, 0, &length, 1);
}

static inline bool write_txfifo_content(const struct device *dev,
					uint8_t *frame, uint8_t len)
{
	return z_cc2520_access(dev, false, CC2520_INS_TXBUF, 0, frame, len);
}

static inline bool verify_txfifo_status(const struct device *dev,
					uint8_t len)
{
	if (read_reg_txfifocnt(dev) < len ||
	    (read_reg_excflag0(dev) & EXCFLAG0_TX_UNDERFLOW)) {
		return false;
	}

	return true;
}

static inline bool verify_tx_done(const struct device *dev)
{
	uint8_t timeout = 10U;
	uint8_t status;

	do {
		z_usleep(1);
		timeout--;
		status = read_reg_excflag0(dev);
	} while (!(status & EXCFLAG0_TX_FRM_DONE) && timeout);

	return !!(status & EXCFLAG0_TX_FRM_DONE);
}

/****************
 * RX functions *
 ***************/

static inline void flush_rxfifo(const struct device *dev)
{
	/* Note: Errata document - 1.1 */
	enable_fifop_interrupt(dev, false);

	instruct_sflushrx(dev);
	instruct_sflushrx(dev);

	enable_fifop_interrupt(dev, true);

	write_reg_excflag0(dev, EXCFLAG0_RESET_RX_FLAGS);
}

static inline uint8_t read_rxfifo_length(const struct device *dev)
{
	uint8_t len;


	if (z_cc2520_access(dev, true, CC2520_INS_RXBUF, 0, &len, 1)) {
		return len;
	}

	return 0;
}

static inline bool read_rxfifo_content(const struct device *dev,
				       struct net_buf *buf, uint8_t len)
{
	if (!z_cc2520_access(dev, true, CC2520_INS_RXBUF, 0, buf->data, len)) {
		return false;
	}

	if (read_reg_excflag0(dev) & EXCFLAG0_RX_UNDERFLOW) {
		LOG_ERR("RX underflow!");
		return false;
	}

	net_buf_add(buf, len);

	return true;
}

static inline void insert_radio_noise_details(struct net_pkt *pkt, uint8_t *status)
{
	uint8_t lqi;

	net_pkt_set_ieee802154_rssi_dbm(pkt, (int8_t) status[0]);

	/**
	 * CC2520 does not provide an LQI but a correlation factor.
	 * See Section 20.6
	 * Such calculation can be loosely used to transform it to lqi:
	 * corr <= 50 ? lqi = 0
	 * or:
	 * corr >= 110 ? lqi = 255
	 * else:
	 * lqi = (lqi - 50) * 4
	 */
	lqi = status[1] & CC2520_FCS_CORRELATION;
	if (lqi <= 50U) {
		lqi = 0U;
	} else if (lqi >= 110U) {
		lqi = 255U;
	} else {
		lqi = (lqi - 50U) << 2;
	}

	net_pkt_set_ieee802154_lqi(pkt, lqi);
}

static inline bool verify_crc(const struct device *dev, struct net_pkt *pkt)
{
	uint8_t status[2];

	if (!z_cc2520_access(dev, true, CC2520_INS_RXBUF, 0, &status, 2)) {
		return false;
	}

	if (!(status[1] & CC2520_FCS_CRC_OK)) {
		return false;
	}

	insert_radio_noise_details(pkt, status);

	return true;
}

static inline bool verify_rxfifo_validity(const struct device *dev,
					  uint8_t pkt_len)
{
	if (pkt_len < 2 || read_reg_rxfifocnt(dev) != pkt_len) {
		return false;
	}

	return true;
}

static void cc2520_rx(void *arg)
{
	const struct device *dev = arg;
	struct cc2520_context *cc2520 = dev->data;
	struct net_pkt *pkt;
	uint8_t pkt_len;

	while (1) {
		pkt = NULL;

		k_sem_take(&cc2520->rx_lock, K_FOREVER);

		if (cc2520->overflow) {
			LOG_ERR("RX overflow!");
			cc2520->overflow = false;

			goto flush;
		}

		pkt_len = read_rxfifo_length(dev) & 0x7f;
		if (!verify_rxfifo_validity(dev, pkt_len)) {
			LOG_ERR("Invalid content");
			goto flush;
		}

		pkt = net_pkt_rx_alloc_with_buffer(cc2520->iface, pkt_len,
						   AF_UNSPEC, 0, K_NO_WAIT);
		if (!pkt) {
			LOG_ERR("No pkt available");
			goto flush;
		}

		if (!IS_ENABLED(CONFIG_IEEE802154_RAW_MODE)) {
			pkt_len -= 2U;
		}

		if (!read_rxfifo_content(dev, pkt->buffer, pkt_len)) {
			LOG_ERR("No content read");
			goto flush;
		}

		if (!verify_crc(dev, pkt)) {
			LOG_ERR("Bad packet CRC");
			goto out;
		}

		if (ieee802154_handle_ack(cc2520->iface, pkt) == NET_OK) {
			LOG_DBG("ACK packet handled");
			goto out;
		}

		LOG_DBG("Caught a packet (%u)", pkt_len);

		if (net_recv_data(cc2520->iface, pkt) < 0) {
			LOG_DBG("Packet dropped by NET stack");
			goto out;
		}

		log_stack_usage(&cc2520->cc2520_rx_thread);
		continue;
flush:
		cc2520_print_exceptions(cc2520);
		cc2520_print_errors(cc2520);
		flush_rxfifo(dev);
out:
		if (pkt) {
			net_pkt_unref(pkt);
		}
	}
}

/********************
 * Radio device API *
 *******************/
static enum ieee802154_hw_caps cc2520_get_capabilities(const struct device *dev)
{
	/* TODO: Add support for IEEE802154_HW_PROMISC */
	return IEEE802154_HW_FCS | IEEE802154_HW_2_4_GHZ | IEEE802154_HW_FILTER |
	       IEEE802154_HW_RX_TX_ACK;
}

static int cc2520_cca(const struct device *dev)
{
	if (!get_cca(dev)) {
		LOG_WRN("Busy");
		return -EBUSY;
	}

	return 0;
}

static int cc2520_set_channel(const struct device *dev, uint16_t channel)
{
	LOG_DBG("%u", channel);

	if (channel < 11 || channel > 26) {
		return -EINVAL;
	}

	/* See chapter 16 */
	channel = 11 + (channel - 11) * 5U;

	if (!write_reg_freqctrl(dev, FREQCTRL_FREQ(channel))) {
		LOG_ERR("Failed");
		return -EIO;
	}

	return 0;
}

static int cc2520_filter(const struct device *dev,
			 bool set,
			 enum ieee802154_filter_type type,
			 const struct ieee802154_filter *filter)
{
	LOG_DBG("Applying filter %u", type);

	if (!set) {
		return -ENOTSUP;
	}

	if (type == IEEE802154_FILTER_TYPE_IEEE_ADDR) {
		return cc2520_set_ieee_addr(dev, filter->ieee_addr);
	} else if (type == IEEE802154_FILTER_TYPE_SHORT_ADDR) {
		return cc2520_set_short_addr(dev, filter->short_addr);
	} else if (type == IEEE802154_FILTER_TYPE_PAN_ID) {
		return cc2520_set_pan_id(dev, filter->pan_id);
	}

	return -ENOTSUP;
}

static int cc2520_set_txpower(const struct device *dev, int16_t dbm)
{
	uint8_t pwr;

	LOG_DBG("%d", dbm);

	/* See chapter 19 part 8 */
	switch (dbm) {
	case 5:
		pwr = 0xF7;
		break;
	case 3:
		pwr = 0xF2;
		break;
	case 2:
		pwr = 0xAB;
		break;
	case 1:
		pwr = 0x13;
		break;
	case 0:
		pwr = 0x32;
		break;
	case -2:
		pwr = 0x81;
		break;
	case -4:
		pwr = 0x88;
		break;
	case -7:
		pwr = 0x2C;
		break;
	case -18:
		pwr = 0x03;
		break;
	default:
		goto error;
	}

	if (!write_reg_txpower(dev, pwr)) {
		goto error;
	}

	return 0;
error:
	LOG_ERR("Failed");
	return -EIO;
}

static int cc2520_tx(const struct device *dev,
		     enum ieee802154_tx_mode mode,
		     struct net_pkt *pkt,
		     struct net_buf *frag)
{
	uint8_t *frame = frag->data;
	uint8_t len = frag->len;
	struct cc2520_context *cc2520 = dev->data;
	uint8_t retry = 2U;
	bool status;

	if (mode != IEEE802154_TX_MODE_DIRECT) {
		NET_ERR("TX mode %d not supported", mode);
		return -ENOTSUP;
	}

	LOG_DBG("%p (%u)", frag, len);

	if (!write_reg_excflag0(dev, EXCFLAG0_RESET_TX_FLAGS) ||
	    !write_txfifo_length(dev, len) ||
	    !write_txfifo_content(dev, frame, len)) {
		LOG_ERR("Cannot feed in TX fifo");
		goto error;
	}

	if (!verify_txfifo_status(dev, len)) {
		LOG_ERR("Did not write properly into TX FIFO");
		goto error;
	}

#ifdef CONFIG_IEEE802154_CC2520_CRYPTO
	k_sem_take(&cc2520->access_lock, K_FOREVER);
#endif

	/* 1 retry is allowed here */
	do {
		atomic_set(&cc2520->tx, 1);
		k_sem_init(&cc2520->tx_sync, 0, K_SEM_MAX_LIMIT);

		if (!instruct_stxoncca(dev)) {
			LOG_ERR("Cannot start transmission");
			goto error;
		}

		/* TODO: Implement standard conforming CSMA/CA or use the soft MAC's default. */
		k_sem_take(&cc2520->tx_sync, K_MSEC(10));

		retry--;
		status = verify_tx_done(dev);
	} while (!status && retry);

#ifdef CONFIG_IEEE802154_CC2520_CRYPTO
	k_sem_give(&cc2520->access_lock);
#endif

	if (status) {
		return 0;
	}

error:
#ifdef CONFIG_IEEE802154_CC2520_CRYPTO
	k_sem_give(&cc2520->access_lock);
#endif
	LOG_ERR("No TX_FRM_DONE");
	cc2520_print_exceptions(cc2520);
	cc2520_print_errors(cc2520);

	atomic_set(&cc2520->tx, 0);
	instruct_sflushtx(dev);

	return -EIO;
}

static int cc2520_start(const struct device *dev)
{
	if (!instruct_sxoscon(dev) ||
	    !instruct_srxon(dev) ||
	    !verify_osc_stabilization(dev)) {
		LOG_ERR("Error starting CC2520");
		return -EIO;
	}

	flush_rxfifo(dev);

	enable_fifop_interrupt(dev, true);
	enable_sfd_interrupt(dev, true);

	return 0;
}

static int cc2520_stop(const struct device *dev)
{
	flush_rxfifo(dev);

	enable_fifop_interrupt(dev, false);
	enable_sfd_interrupt(dev, false);

	if (!instruct_srfoff(dev) ||
	    !instruct_sxoscoff(dev)) {
		LOG_ERR("Error stopping CC2520");
		return -EIO;
	}

	return 0;
}

/******************
 * Initialization *
 *****************/
static int power_on_and_setup(const struct device *dev)
{
	/* Switching to LPM2 mode */
	set_reset(dev, 0);
	z_usleep(150);

	set_vreg_en(dev, 0);
	z_usleep(250);

	/* Then to ACTIVE mode */
	set_vreg_en(dev, 1);
	z_usleep(250);

	set_reset(dev, 1);
	z_usleep(150);

	if (!verify_osc_stabilization(dev)) {
		return -EIO;
	}

	/* Default settings to always write (see chapter 28 part 1) */
	if (!write_reg_txpower(dev, CC2520_TXPOWER_DEFAULT) ||
	    !write_reg_ccactrl0(dev, CC2520_CCACTRL0_DEFAULT) ||
	    !write_reg_mdmctrl0(dev, CC2520_MDMCTRL0_DEFAULT) ||
	    !write_reg_mdmctrl1(dev, CC2520_MDMCTRL1_DEFAULT) ||
	    !write_reg_rxctrl(dev, CC2520_RXCTRL_DEFAULT) ||
	    !write_reg_fsctrl(dev, CC2520_FSCTRL_DEFAULT) ||
	    !write_reg_fscal1(dev, CC2520_FSCAL1_DEFAULT) ||
	    !write_reg_agcctrl1(dev, CC2520_AGCCTRL1_DEFAULT) ||
	    !write_reg_adctest0(dev, CC2520_ADCTEST0_DEFAULT) ||
	    !write_reg_adctest1(dev, CC2520_ADCTEST1_DEFAULT) ||
	    !write_reg_adctest2(dev, CC2520_ADCTEST2_DEFAULT)) {
		return -EIO;
	}

	/* EXTCLOCK0: Disabling external clock
	 * FRMCTRL0: AUTOACK and AUTOCRC enabled
	 * FRMCTRL1: SET_RXENMASK_ON_TX and IGNORE_TX_UNDERF
	 * FRMFILT0: Frame filtering (setting CC2520_FRAME_FILTERING)
	 * FIFOPCTRL: Set TX threshold (setting CC2520_TX_THRESHOLD)
	 */
	if (!write_reg_extclock(dev, 0) ||
	    !write_reg_frmctrl0(dev, CC2520_AUTOMATISM) ||
	    !write_reg_frmctrl1(dev, FRMCTRL1_IGNORE_TX_UNDERF |
				FRMCTRL1_SET_RXENMASK_ON_TX) ||
	    !write_reg_frmfilt0(dev, FRMFILT0_FRAME_FILTER_EN |
				FRMFILT0_MAX_FRAME_VERSION(3)) ||
	    !write_reg_frmfilt1(dev, FRMFILT1_ACCEPT_ALL) ||
	    !write_reg_srcmatch(dev, SRCMATCH_DEFAULTS) ||
	    !write_reg_fifopctrl(dev,
				 FIFOPCTRL_FIFOP_THR(CC2520_TX_THRESHOLD))) {
		return -EIO;
	}

	/* Cleaning up TX fifo */
	instruct_sflushtx(dev);

	if (setup_gpio_callbacks(dev) != 0) {
		return -EIO;
	}

	cc2520_print_gpio_config(dev);

	return 0;
}

static int configure_gpios(const struct device *dev)
{
	const struct cc2520_config *cfg = dev->config;

	if (!gpio_is_ready_dt(&cfg->vreg_en) ||
	    !gpio_is_ready_dt(&cfg->reset) ||
	    !gpio_is_ready_dt(&cfg->fifo) ||
	    !gpio_is_ready_dt(&cfg->cca) ||
	    !gpio_is_ready_dt(&cfg->sfd) ||
	    !gpio_is_ready_dt(&cfg->fifop)) {
		return -ENODEV;
	}

	gpio_pin_configure_dt(&cfg->vreg_en, GPIO_OUTPUT_LOW);
	gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_LOW);
	gpio_pin_configure_dt(&cfg->fifo, GPIO_INPUT);
	gpio_pin_configure_dt(&cfg->cca, GPIO_INPUT);
	gpio_pin_configure_dt(&cfg->sfd, GPIO_INPUT);
	gpio_pin_configure_dt(&cfg->fifop, GPIO_INPUT);

	return 0;
}

static int cc2520_init(const struct device *dev)
{
	const struct cc2520_config *cfg = dev->config;
	struct cc2520_context *cc2520 = dev->data;

	cc2520->dev = dev;

	atomic_set(&cc2520->tx, 0);
	k_sem_init(&cc2520->rx_lock, 0, K_SEM_MAX_LIMIT);

#ifdef CONFIG_IEEE802154_CC2520_CRYPTO
	k_sem_init(&cc2520->access_lock, 1, 1);
#endif

	if (configure_gpios(dev) != 0) {
		LOG_ERR("Configuring GPIOS failed");
		return -EIO;
	}

	if (!spi_is_ready_dt(&cfg->bus)) {
		LOG_ERR("SPI bus %s not ready", cfg->bus.bus->name);
		return -EIO;
	}

	LOG_DBG("GPIO and SPI configured");

	if (power_on_and_setup(dev) != 0) {
		LOG_ERR("Configuring CC2520 failed");
		return -EIO;
	}

	k_thread_create(&cc2520->cc2520_rx_thread, cc2520->cc2520_rx_stack,
			CONFIG_IEEE802154_CC2520_RX_STACK_SIZE,
			(k_thread_entry_t)cc2520_rx,
			(void *)dev, NULL, NULL, K_PRIO_COOP(2), 0, K_NO_WAIT);
	k_thread_name_set(&cc2520->cc2520_rx_thread, "cc2520_rx");

	LOG_INF("CC2520 initialized");

	return 0;
}

static void cc2520_iface_init(struct net_if *iface)
{
	const struct device *dev = net_if_get_device(iface);
	struct cc2520_context *cc2520 = dev->data;
	uint8_t *mac = get_mac(dev);

	net_if_set_link_addr(iface, mac, 8, NET_LINK_IEEE802154);

	cc2520->iface = iface;

	ieee802154_init(iface);
}

static const struct cc2520_config cc2520_config = {
	.bus = SPI_DT_SPEC_INST_GET(0, SPI_WORD_SET(8), 0),
	.vreg_en = GPIO_DT_SPEC_INST_GET(0, vreg_en_gpios),
	.reset = GPIO_DT_SPEC_INST_GET(0, reset_gpios),
	.fifo = GPIO_DT_SPEC_INST_GET(0, fifo_gpios),
	.cca = GPIO_DT_SPEC_INST_GET(0, cca_gpios),
	.sfd = GPIO_DT_SPEC_INST_GET(0, sfd_gpios),
	.fifop = GPIO_DT_SPEC_INST_GET(0, fifop_gpios)
};

static struct cc2520_context cc2520_context_data;

static struct ieee802154_radio_api cc2520_radio_api = {
	.iface_api.init	= cc2520_iface_init,

	.get_capabilities	= cc2520_get_capabilities,
	.cca			= cc2520_cca,
	.set_channel		= cc2520_set_channel,
	.filter			= cc2520_filter,
	.set_txpower		= cc2520_set_txpower,
	.start			= cc2520_start,
	.stop			= cc2520_stop,
	.tx			= cc2520_tx,
};

#if defined(CONFIG_IEEE802154_RAW_MODE)
DEVICE_DT_INST_DEFINE(0, cc2520_init, NULL, &cc2520_context_data, NULL,
		      POST_KERNEL, CONFIG_IEEE802154_CC2520_INIT_PRIO,
		      &cc2520_radio_api);
#else
NET_DEVICE_DT_INST_DEFINE(0, cc2520_init, NULL, &cc2520_context_data,
			  &cc2520_config, CONFIG_IEEE802154_CC2520_INIT_PRIO,
			  &cc2520_radio_api, IEEE802154_L2,
			  NET_L2_GET_CTX_TYPE(IEEE802154_L2), 125);
#endif


#ifdef CONFIG_IEEE802154_CC2520_CRYPTO

static inline bool cc2520_read_ram(const struct device *dev, uint16_t addr,
				   uint8_t *data_buf, uint8_t len)
{
	return z_cc2520_access(dev, true, CC2520_INS_MEMRD,
			       addr, data_buf, len);
}

static inline bool cc2520_write_ram(const struct device *dev, uint16_t addr,
				    uint8_t *data_buf, uint8_t len)
{
	return z_cc2520_access(dev, false, CC2520_INS_MEMWR,
			       addr, data_buf, len);
}

static inline bool instruct_uccm_ccm(const struct device *dev,
				     bool uccm,
				     uint8_t key_addr,
				     uint8_t auth_crypt,
				     uint8_t nonce_addr,
				     uint16_t input_addr,
				     uint16_t output_addr,
				     uint8_t in_len,
				     uint8_t m)
{
	const struct cc2520_config *cfg = dev->config;
	struct cc2520_context *ctx = dev->data;
	uint8_t cmd[9];
	const struct spi_buf buf[1] = {
		{
			.buf = cmd,
			.len = 9,
		},
	};
	const struct spi_buf_set tx = {
		.buffers = buf,
		.count = 1
	};

	int ret;

	LOG_DBG("%sCCM(P={01} K={%02x} C={%02x} N={%02x}"
		    " A={%03x} E={%03x} F{%02x} M={%02x})",
		    uccm ? "U" : "", key_addr, auth_crypt, nonce_addr,
		    input_addr, output_addr, in_len, m);

	cmd[0] = uccm ? CC2520_INS_UCCM | 1 : CC2520_INS_CCM | 1;
	cmd[1] = key_addr;
	cmd[2] = (auth_crypt & 0x7f);
	cmd[3] = nonce_addr;
	cmd[4] = (uint8_t)(((input_addr & 0x0f00) >> 4) |
			   ((output_addr & 0x0f00) >> 8));
	cmd[5] = (uint8_t)(input_addr & 0x00ff);
	cmd[6] = (uint8_t)(output_addr & 0x00ff);
	cmd[7] = (in_len & 0x7f);
	cmd[8] = (m & 0x03);

	k_sem_take(&ctx->access_lock, K_FOREVER);

	ret = spi_write_dt(&cfg->bus, &tx);

	k_sem_give(&ctx->access_lock);

	if (ret) {
		LOG_ERR("%sCCM Failed", uccm ? "U" : "");
		return false;
	}

	return true;
}

static inline void generate_nonce(uint8_t *ccm_nonce, uint8_t *nonce,
				  struct cipher_aead_pkt *apkt, uint8_t m)
{
	nonce[0] = 0 | (apkt->ad_len ? 0x40 : 0) | (m << 3) | 1;

	memcpy(&nonce[1], ccm_nonce, 13);

	nonce[14] = (uint8_t)(apkt->pkt->in_len >> 8);
	nonce[15] = (uint8_t)(apkt->pkt->in_len);

	/* See section 26.8.1 */
	sys_mem_swap(nonce, 16);
}

static int insert_crypto_parameters(struct cipher_ctx *ctx,
				    struct cipher_aead_pkt *apkt,
				    uint8_t *ccm_nonce, uint8_t *auth_crypt)
{
	const struct device *cc2520 = ctx->device;
	uint8_t data[128];
	uint8_t *in_buf;
	uint8_t in_len;
	uint8_t m = 0U;

	if (!apkt->pkt->out_buf || !apkt->pkt->out_buf_max) {
		LOG_ERR("Out buffer needs to be set");
		return -EINVAL;
	}

	if (!ctx->key.bit_stream || !ctx->keylen) {
		LOG_ERR("No key installed");
		return -EINVAL;
	}

	if (!(ctx->flags & CAP_INPLACE_OPS)) {
		LOG_ERR("It supports only in-place operation");
		return -EINVAL;
	}

	if (!apkt->ad || !apkt->ad_len) {
		LOG_ERR("CCM needs associated data");
		return -EINVAL;
	}

	if (apkt->pkt->in_buf && apkt->pkt->in_buf - apkt->ad_len != apkt->ad) {
		LOG_ERR("In-place needs ad and input in same memory");
		return -EINVAL;
	}

	if (!apkt->pkt->in_buf) {
		if (!ctx->mode_params.ccm_info.tag_len) {
			LOG_ERR("Auth only needs a tag length");
			return -EINVAL;
		}

		in_buf = apkt->ad;
		in_len = apkt->ad_len;

		*auth_crypt = 0U;
	} else {
		in_buf = data;

		memcpy(in_buf, apkt->ad, apkt->ad_len);
		memcpy(in_buf + apkt->ad_len,
		       apkt->pkt->in_buf, apkt->pkt->in_len);
		in_len = apkt->ad_len + apkt->pkt->in_len;

		*auth_crypt = !apkt->tag ? apkt->pkt->in_len :
			apkt->pkt->in_len - ctx->mode_params.ccm_info.tag_len;
	}

	if (ctx->mode_params.ccm_info.tag_len) {
		if ((ctx->mode_params.ccm_info.tag_len >> 2) > 3) {
			m = 3U;
		} else {
			m = ctx->mode_params.ccm_info.tag_len >> 2;
		}
	}

	/* Writing the frame in RAM */
	if (!cc2520_write_ram(cc2520, CC2520_MEM_DATA, in_buf, in_len)) {
		LOG_ERR("Cannot write the frame in RAM");
		return -EIO;
	}

	/* See section 26.8.1 */
	sys_memcpy_swap(data, ctx->key.bit_stream, ctx->keylen);

	/* Writing the key in RAM */
	if (!cc2520_write_ram(cc2520, CC2520_MEM_KEY, data, 16)) {
		LOG_ERR("Cannot write the key in RAM");
		return -EIO;
	}

	generate_nonce(ccm_nonce, data, apkt, m);

	/* Writing the nonce in RAM */
	if (!cc2520_write_ram(cc2520, CC2520_MEM_NONCE, data, 16)) {
		LOG_ERR("Cannot write the nonce in RAM");
		return -EIO;
	}

	return m;
}

static int cc2520_crypto_ccm(struct cipher_ctx *ctx,
			      struct cipher_aead_pkt *apkt,
			      uint8_t *ccm_nonce)
{
	const struct device *cc2520 = ctx->device;
	uint8_t auth_crypt;
	int m;

	if (!apkt || !apkt->pkt) {
		LOG_ERR("Invalid crypto packet to operate with");
		return -EINVAL;
	}

	m = insert_crypto_parameters(ctx, apkt, ccm_nonce, &auth_crypt);
	if (m < 0) {
		LOG_ERR("Inserting crypto parameters failed");
		return m;
	}

	apkt->pkt->out_len = apkt->pkt->in_len + apkt->ad_len +
		(m ? ctx->mode_params.ccm_info.tag_len : 0);

	if (apkt->pkt->out_len > apkt->pkt->out_buf_max) {
		LOG_ERR("Result will not fit into out buffer %u vs %u",
			    apkt->pkt->out_len, apkt->pkt->out_buf_max);
		return -ENOBUFS;
	}

	if (!instruct_uccm_ccm(cc2520, false, CC2520_MEM_KEY >> 4, auth_crypt,
			       CC2520_MEM_NONCE >> 4, CC2520_MEM_DATA,
			       0x000, apkt->ad_len, m) ||
	    !cc2520_read_ram(cc2520, CC2520_MEM_DATA,
			      apkt->pkt->out_buf, apkt->pkt->out_len)) {
		LOG_ERR("CCM or reading result from RAM failed");
		return -EIO;
	}

	if (apkt->tag) {
		memcpy(apkt->tag, apkt->pkt->out_buf + apkt->pkt->in_len,
					ctx->mode_params.ccm_info.tag_len);
	}

	return 0;
}

static int cc2520_crypto_uccm(struct cipher_ctx *ctx,
			       struct cipher_aead_pkt *apkt,
			       uint8_t *ccm_nonce)
{
	const struct device *cc2520 = ctx->device;
	uint8_t auth_crypt;
	int m;

	if (!apkt || !apkt->pkt) {
		LOG_ERR("Invalid crypto packet to operate with");
		return -EINVAL;
	}

	if (ctx->mode_params.ccm_info.tag_len && !apkt->tag) {
		LOG_ERR("In case of MIC you need to provide a tag");
		return -EINVAL;
	}

	m = insert_crypto_parameters(ctx, apkt, ccm_nonce, &auth_crypt);
	if (m < 0) {
		return m;
	}

	apkt->pkt->out_len = apkt->pkt->in_len + apkt->ad_len;

	if (!instruct_uccm_ccm(cc2520, true, CC2520_MEM_KEY >> 4, auth_crypt,
			       CC2520_MEM_NONCE >> 4, CC2520_MEM_DATA,
			       0x000, apkt->ad_len, m) ||
	    !cc2520_read_ram(cc2520, CC2520_MEM_DATA,
			      apkt->pkt->out_buf, apkt->pkt->out_len)) {
		LOG_ERR("UCCM or reading result from RAM failed");
		return -EIO;
	}

	if (m && (!(read_reg_dpustat(cc2520) & DPUSTAT_AUTHSTAT_H))) {
		LOG_ERR("Authentication of the frame failed");
		return -EBADMSG;
	}

	return 0;
}

static int cc2520_crypto_hw_caps(const struct device *dev)
{
	return CAP_RAW_KEY | CAP_INPLACE_OPS | CAP_SYNC_OPS;
}

static int cc2520_crypto_begin_session(const struct device *dev,
				       struct cipher_ctx *ctx,
				       enum cipher_algo algo,
				       enum cipher_mode mode,
				       enum cipher_op op_type)
{
	if (algo != CRYPTO_CIPHER_ALGO_AES || mode != CRYPTO_CIPHER_MODE_CCM) {
		LOG_ERR("Wrong algo (%u) or mode (%u)", algo, mode);
		return -EINVAL;
	}

	if (ctx->mode_params.ccm_info.nonce_len != 13U) {
		LOG_ERR("Nonce length erroneous (%u)",
			    ctx->mode_params.ccm_info.nonce_len);
		return -EINVAL;
	}

	if (op_type == CRYPTO_CIPHER_OP_ENCRYPT) {
		ctx->ops.ccm_crypt_hndlr = cc2520_crypto_ccm;
	} else {
		ctx->ops.ccm_crypt_hndlr = cc2520_crypto_uccm;
	}

	ctx->ops.cipher_mode = mode;
	ctx->device = dev;

	return 0;
}

static int cc2520_crypto_free_session(const struct device *dev,
				      struct cipher_ctx *ctx)
{
	ARG_UNUSED(dev);

	ctx->ops.ccm_crypt_hndlr = NULL;
	ctx->device = NULL;

	return 0;
}

static int cc2520_crypto_init(const struct device *dev)
{
	LOG_INF("CC2520 crypto part initialized");

	return 0;
}

struct crypto_driver_api cc2520_crypto_api = {
	.query_hw_caps			= cc2520_crypto_hw_caps,
	.cipher_begin_session			= cc2520_crypto_begin_session,
	.cipher_free_session			= cc2520_crypto_free_session,
	.cipher_async_callback_set	= NULL
};

DEVICE_DEFINE(cc2520_crypto, "cc2520_crypto",
		cc2520_crypto_init, NULL,
		&cc2520_context_data, NULL, POST_KERNEL,
		CONFIG_IEEE802154_CC2520_CRYPTO_INIT_PRIO, &cc2520_crypto_api);

#endif /* CONFIG_IEEE802154_CC2520_CRYPTO */
