/*
 * Copyright (c) 2021 Telink Semiconductor
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT telink_b91_zb

#include "rf.h"
#include "stimer.h"

#define LOG_MODULE_NAME ieee802154_b91
#if defined(CONFIG_IEEE802154_DRIVER_LOG_LEVEL)
#define LOG_LEVEL CONFIG_IEEE802154_DRIVER_LOG_LEVEL
#else
#define LOG_LEVEL LOG_LEVEL_NONE
#endif

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

#include <zephyr/random/random.h>
#include <zephyr/net/ieee802154_radio.h>
#include <zephyr/irq.h>
#if defined(CONFIG_NET_L2_OPENTHREAD)
#include <zephyr/net/openthread.h>
#endif

#include <zephyr/drivers/interrupt_controller/riscv_plic.h>

#include "ieee802154_b91.h"


/* B91 data structure */
static struct b91_data data;

/* Set filter PAN ID */
static int b91_set_pan_id(uint16_t pan_id)
{
	uint8_t pan_id_le[B91_PAN_ID_SIZE];

	sys_put_le16(pan_id, pan_id_le);
	memcpy(data.filter_pan_id, pan_id_le, B91_PAN_ID_SIZE);

	return 0;
}

/* Set filter short address */
static int b91_set_short_addr(uint16_t short_addr)
{
	uint8_t short_addr_le[B91_SHORT_ADDRESS_SIZE];

	sys_put_le16(short_addr, short_addr_le);
	memcpy(data.filter_short_addr, short_addr_le, B91_SHORT_ADDRESS_SIZE);

	return 0;
}

/* Set filter IEEE address */
static int b91_set_ieee_addr(const uint8_t *ieee_addr)
{
	memcpy(data.filter_ieee_addr, ieee_addr, B91_IEEE_ADDRESS_SIZE);

	return 0;
}

/* Filter PAN ID, short address and IEEE address */
static bool b91_run_filter(uint8_t *rx_buffer)
{
	/* Check destination PAN Id */
	if (memcmp(&rx_buffer[B91_PAN_ID_OFFSET], data.filter_pan_id,
		   B91_PAN_ID_SIZE) != 0 &&
	    memcmp(&rx_buffer[B91_PAN_ID_OFFSET], B91_BROADCAST_ADDRESS,
		   B91_PAN_ID_SIZE) != 0) {
		return false;
	}

	/* Check destination address */
	switch (rx_buffer[B91_DEST_ADDR_TYPE_OFFSET] & B91_DEST_ADDR_TYPE_MASK) {
	case B91_DEST_ADDR_TYPE_SHORT:
		/* First check if the destination is broadcast */
		/* If not broadcast, check if length and address matches */
		if (memcmp(&rx_buffer[B91_DEST_ADDR_OFFSET], B91_BROADCAST_ADDRESS,
			   B91_SHORT_ADDRESS_SIZE) != 0 &&
		    memcmp(&rx_buffer[B91_DEST_ADDR_OFFSET], data.filter_short_addr,
			   B91_SHORT_ADDRESS_SIZE) != 0) {
			return false;
		}
		break;

	case B91_DEST_ADDR_TYPE_IEEE:
		/* If not broadcast, check if length and address matches */
		if ((net_if_get_link_addr(data.iface)->len != B91_IEEE_ADDRESS_SIZE) ||
		    memcmp(&rx_buffer[B91_DEST_ADDR_OFFSET], data.filter_ieee_addr,
			   B91_IEEE_ADDRESS_SIZE) != 0) {
			return false;
		}
		break;

	default:
		return false;
	}

	return true;
}

/* Get MAC address */
static inline uint8_t *b91_get_mac(const struct device *dev)
{
	struct b91_data *b91 = dev->data;

#if defined(CONFIG_IEEE802154_B91_RANDOM_MAC)
	sys_rand_get(b91->mac_addr, sizeof(b91->mac_addr));

	/*
	 * Clear bit 0 to ensure it isn't a multicast address and set
	 * bit 1 to indicate address is locally administered and may
	 * not be globally unique.
	 */
	b91->mac_addr[0] = (b91->mac_addr[0] & ~0x01) | 0x02;
#else
	/* Vendor Unique Identifier */
	b91->mac_addr[0] = 0xC4;
	b91->mac_addr[1] = 0x19;
	b91->mac_addr[2] = 0xD1;
	b91->mac_addr[3] = 0x00;

	/* Extended Unique Identifier */
	b91->mac_addr[4] = CONFIG_IEEE802154_B91_MAC4;
	b91->mac_addr[5] = CONFIG_IEEE802154_B91_MAC5;
	b91->mac_addr[6] = CONFIG_IEEE802154_B91_MAC6;
	b91->mac_addr[7] = CONFIG_IEEE802154_B91_MAC7;
#endif

	return b91->mac_addr;
}

/* Convert RSSI to LQI */
static uint8_t b91_convert_rssi_to_lqi(int8_t rssi)
{
	uint32_t lqi32 = 0;

	/* check for MIN value */
	if (rssi < B91_RSSI_TO_LQI_MIN) {
		return 0;
	}

	/* convert RSSI to LQI */
	lqi32 = B91_RSSI_TO_LQI_SCALE * (rssi - B91_RSSI_TO_LQI_MIN);

	/* check for MAX value */
	if (lqi32 > 0xFF) {
		lqi32 = 0xFF;
	}

	return (uint8_t)lqi32;
}

/* Update RSSI and LQI parameters */
static void b91_update_rssi_and_lqi(struct net_pkt *pkt)
{
	int8_t rssi;
	uint8_t lqi;

	rssi = ((signed char)(data.rx_buffer
			      [data.rx_buffer[B91_LENGTH_OFFSET] + B91_RSSI_OFFSET])) - 110;
	lqi = b91_convert_rssi_to_lqi(rssi);

	net_pkt_set_ieee802154_lqi(pkt, lqi);
	net_pkt_set_ieee802154_rssi_dbm(pkt, rssi);
}

/* Prepare TX buffer */
static int b91_set_tx_payload(uint8_t *payload, uint8_t payload_len)
{
	unsigned char rf_data_len;
	unsigned int rf_tx_dma_len;

	/* See Telink SDK Dev Handbook, AN-21010600, section 21.5.2.2. */
	if (payload_len > (B91_TRX_LENGTH - B91_PAYLOAD_OFFSET - IEEE802154_FCS_LENGTH)) {
		return -EINVAL;
	}

	rf_data_len = payload_len + 1;
	rf_tx_dma_len = rf_tx_packet_dma_len(rf_data_len);
	data.tx_buffer[0] = rf_tx_dma_len & 0xff;
	data.tx_buffer[1] = (rf_tx_dma_len >> 8) & 0xff;
	data.tx_buffer[2] = (rf_tx_dma_len >> 16) & 0xff;
	data.tx_buffer[3] = (rf_tx_dma_len >> 24) & 0xff;
	data.tx_buffer[4] = payload_len + IEEE802154_FCS_LENGTH;
	memcpy(data.tx_buffer + B91_PAYLOAD_OFFSET, payload, payload_len);

	return 0;
}

/* Enable ack handler */
static void b91_handle_ack_en(void)
{
	data.ack_handler_en = true;
}

/* Disable ack handler */
static void b91_handle_ack_dis(void)
{
	data.ack_handler_en = false;
}

/* Handle acknowledge packet */
static void b91_handle_ack(void)
{
	struct net_pkt *ack_pkt;

	/* allocate ack packet */
	ack_pkt = net_pkt_rx_alloc_with_buffer(data.iface, B91_ACK_FRAME_LEN,
					       AF_UNSPEC, 0, K_NO_WAIT);
	if (!ack_pkt) {
		LOG_ERR("No free packet available.");
		return;
	}

	/* update packet data */
	if (net_pkt_write(ack_pkt, data.rx_buffer + B91_PAYLOAD_OFFSET,
			  B91_ACK_FRAME_LEN) < 0) {
		LOG_ERR("Failed to write to a packet.");
		goto out;
	}

	/* update RSSI and LQI */
	b91_update_rssi_and_lqi(ack_pkt);

	/* init net cursor */
	net_pkt_cursor_init(ack_pkt);

	/* handle ack */
	if (ieee802154_handle_ack(data.iface, ack_pkt) != NET_OK) {
		LOG_INF("ACK packet not handled - releasing.");
	}

	/* release ack_wait semaphore */
	k_sem_give(&data.ack_wait);

out:
	net_pkt_unref(ack_pkt);
}

/* Send acknowledge packet */
static void b91_send_ack(uint8_t seq_num)
{
	uint8_t ack_buf[] = { B91_ACK_TYPE, 0, seq_num };

	if (b91_set_tx_payload(ack_buf, sizeof(ack_buf))) {
		return;
	}

	rf_set_txmode();
	delay_us(CONFIG_IEEE802154_B91_SET_TXRX_DELAY_US);
	rf_tx_pkt(data.tx_buffer);
}

/* RX IRQ handler */
static void b91_rf_rx_isr(void)
{
	int status;
	uint8_t length;
	uint8_t *payload;
	struct net_pkt *pkt;

	/* disable DMA and clear IRQ flag */
	dma_chn_dis(DMA1);
	rf_clr_irq_status(FLD_RF_IRQ_RX);

	/* check CRC */
	if (rf_zigbee_packet_crc_ok(data.rx_buffer)) {
		/* get payload length */
		if (IS_ENABLED(CONFIG_IEEE802154_L2_PKT_INCL_FCS)) {
			length = data.rx_buffer[B91_LENGTH_OFFSET];
		} else {
			length = data.rx_buffer[B91_LENGTH_OFFSET] - B91_FCS_LENGTH;
		}

		/* check length */
		if ((length < B91_PAYLOAD_MIN) || (length > B91_PAYLOAD_MAX)) {
			LOG_ERR("Invalid length\n");
			goto exit;
		}

		/* get payload */
		payload = (uint8_t *)(data.rx_buffer + B91_PAYLOAD_OFFSET);

		/* handle acknowledge packet if enabled */
		if ((length == (B91_ACK_FRAME_LEN + B91_FCS_LENGTH)) &&
		    ((payload[B91_FRAME_TYPE_OFFSET] & B91_FRAME_TYPE_MASK) == B91_ACK_TYPE)) {
			if (data.ack_handler_en) {
				b91_handle_ack();
			}
			goto exit;
		}

		/* run filter (check PAN ID and destination address) */
		if (b91_run_filter(payload) == false) {
			LOG_DBG("Packet received is not addressed to me");
			goto exit;
		}

		/* send ack if requested */
		if (payload[B91_FRAME_TYPE_OFFSET] & B91_ACK_REQUEST) {
			b91_send_ack(payload[B91_DSN_OFFSET]);
		}

		/* get packet pointer from NET stack */
		pkt = net_pkt_rx_alloc_with_buffer(data.iface, length, AF_UNSPEC, 0, K_NO_WAIT);
		if (!pkt) {
			LOG_ERR("No pkt available");
			goto exit;
		}

		/* update packet data */
		if (net_pkt_write(pkt, payload, length)) {
			LOG_ERR("Failed to write to a packet.");
			net_pkt_unref(pkt);
			goto exit;
		}

		/* update RSSI and LQI parameters */
		b91_update_rssi_and_lqi(pkt);

		/* transfer data to NET stack */
		status = net_recv_data(data.iface, pkt);
		if (status < 0) {
			LOG_ERR("RCV Packet dropped by NET stack: %d", status);
			net_pkt_unref(pkt);
		}
	}

exit:
	dma_chn_en(DMA1);
}

/* TX IRQ handler */
static void b91_rf_tx_isr(void)
{
	/* clear irq status */
	rf_clr_irq_status(FLD_RF_IRQ_TX);

	/* release tx semaphore */
	k_sem_give(&data.tx_wait);

	/* set to rx mode */
	rf_set_rxmode();
}

/* IRQ handler */
static void b91_rf_isr(void)
{
	if (rf_get_irq_status(FLD_RF_IRQ_RX)) {
		b91_rf_rx_isr();
	} else if (rf_get_irq_status(FLD_RF_IRQ_TX)) {
		b91_rf_tx_isr();
	} else {
		rf_clr_irq_status(FLD_RF_IRQ_ALL);
	}
}

/* Driver initialization */
static int b91_init(const struct device *dev)
{
	struct b91_data *b91 = dev->data;

	/* init semaphores */
	k_sem_init(&b91->tx_wait, 0, 1);
	k_sem_init(&b91->ack_wait, 0, 1);

	/* init rf module */
	rf_mode_init();
	rf_set_zigbee_250K_mode();
	rf_set_tx_dma(2, B91_TRX_LENGTH);
	rf_set_rx_dma(data.rx_buffer, 3, B91_TRX_LENGTH);
	rf_set_rxmode();

	/* init IRQs */
	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), b91_rf_isr, 0, 0);
	riscv_plic_irq_enable(DT_INST_IRQN(0));
	riscv_plic_set_priority(DT_INST_IRQN(0), DT_INST_IRQ(0, priority));
	rf_set_irq_mask(FLD_RF_IRQ_RX | FLD_RF_IRQ_TX);

	/* init data variables */
	data.is_started = true;
	data.ack_handler_en = false;
	data.current_channel = 0;

	return 0;
}

/* API implementation: iface_init */
static void b91_iface_init(struct net_if *iface)
{
	const struct device *dev = net_if_get_device(iface);
	struct b91_data *b91 = dev->data;
	uint8_t *mac = b91_get_mac(dev);

	net_if_set_link_addr(iface, mac, B91_IEEE_ADDRESS_SIZE, NET_LINK_IEEE802154);

	b91->iface = iface;

	ieee802154_init(iface);
}

/* API implementation: get_capabilities */
static enum ieee802154_hw_caps b91_get_capabilities(const struct device *dev)
{
	ARG_UNUSED(dev);

	return IEEE802154_HW_FCS | IEEE802154_HW_FILTER |
	       IEEE802154_HW_TX_RX_ACK | IEEE802154_HW_RX_TX_ACK;
}

/* API implementation: cca */
static int b91_cca(const struct device *dev)
{
	ARG_UNUSED(dev);

	unsigned int t1 = stimer_get_tick();

	while (!clock_time_exceed(t1, B91_CCA_TIME_MAX_US)) {
		if (rf_get_rssi() < CONFIG_IEEE802154_B91_CCA_RSSI_THRESHOLD) {
			return 0;
		}
	}

	return -EBUSY;
}

/* API implementation: set_channel */
static int b91_set_channel(const struct device *dev, uint16_t channel)
{
	ARG_UNUSED(dev);

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

	if (channel < 11) {
		return -ENOTSUP;
	}

	if (data.current_channel != channel) {
		data.current_channel = channel;
		rf_set_chn(B91_LOGIC_CHANNEL_TO_PHYSICAL(channel));
		rf_set_rxmode();
	}

	return 0;
}

/* API implementation: filter */
static int b91_filter(const struct device *dev,
		      bool set,
		      enum ieee802154_filter_type type,
		      const struct ieee802154_filter *filter)
{
	if (!set) {
		return -ENOTSUP;
	}

	if (type == IEEE802154_FILTER_TYPE_IEEE_ADDR) {
		return b91_set_ieee_addr(filter->ieee_addr);
	} else if (type == IEEE802154_FILTER_TYPE_SHORT_ADDR) {
		return b91_set_short_addr(filter->short_addr);
	} else if (type == IEEE802154_FILTER_TYPE_PAN_ID) {
		return b91_set_pan_id(filter->pan_id);
	}

	return -ENOTSUP;
}

/* API implementation: set_txpower */
static int b91_set_txpower(const struct device *dev, int16_t dbm)
{
	ARG_UNUSED(dev);

	/* check for supported Min/Max range */
	if (dbm < B91_TX_POWER_MIN) {
		dbm = B91_TX_POWER_MIN;
	} else if (dbm > B91_TX_POWER_MAX) {
		dbm = B91_TX_POWER_MAX;
	}

	/* set TX power */
	rf_set_power_level(b91_tx_pwr_lt[dbm - B91_TX_POWER_MIN]);

	return 0;
}

/* API implementation: start */
static int b91_start(const struct device *dev)
{
	ARG_UNUSED(dev);

	/* check if RF is already started */
	if (!data.is_started) {
		rf_set_rxmode();
		delay_us(CONFIG_IEEE802154_B91_SET_TXRX_DELAY_US);
		riscv_plic_irq_enable(DT_INST_IRQN(0));
		data.is_started = true;
	}

	return 0;
}

/* API implementation: stop */
static int b91_stop(const struct device *dev)
{
	ARG_UNUSED(dev);

	/* check if RF is already stopped */
	if (data.is_started) {
		riscv_plic_irq_disable(DT_INST_IRQN(0));
		rf_set_tx_rx_off();
		delay_us(CONFIG_IEEE802154_B91_SET_TXRX_DELAY_US);
		data.is_started = false;
	}

	return 0;
}

/* API implementation: tx */
static int b91_tx(const struct device *dev,
		  enum ieee802154_tx_mode mode,
		  struct net_pkt *pkt,
		  struct net_buf *frag)
{
	ARG_UNUSED(pkt);

	int status;
	struct b91_data *b91 = dev->data;

	/* check for supported mode */
	if (mode != IEEE802154_TX_MODE_DIRECT) {
		LOG_DBG("TX mode %d not supported", mode);
		return -ENOTSUP;
	}

	/* prepare tx buffer */
	status = b91_set_tx_payload(frag->data, frag->len);
	if (status) {
		return status;
	}

	/* reset semaphores */
	k_sem_reset(&b91->tx_wait);
	k_sem_reset(&b91->ack_wait);

	/* start transmission */
	rf_set_txmode();
	delay_us(CONFIG_IEEE802154_B91_SET_TXRX_DELAY_US);
	rf_tx_pkt(data.tx_buffer);

	/* wait for tx done */
	status = k_sem_take(&b91->tx_wait, K_MSEC(B91_TX_WAIT_TIME_MS));
	if (status != 0) {
		rf_set_rxmode();
		return -EIO;
	}

	/* wait for ACK if requested */
	if (frag->data[B91_FRAME_TYPE_OFFSET] & B91_ACK_REQUEST) {
		b91_handle_ack_en();
		status = k_sem_take(&b91->ack_wait, K_MSEC(B91_ACK_WAIT_TIME_MS));
		b91_handle_ack_dis();
	}

	return status;
}

/* API implementation: ed_scan */
static int b91_ed_scan(const struct device *dev, uint16_t duration,
		       energy_scan_done_cb_t done_cb)
{
	ARG_UNUSED(dev);
	ARG_UNUSED(duration);
	ARG_UNUSED(done_cb);

	/* ed_scan not supported */

	return -ENOTSUP;
}

/* API implementation: configure */
static int b91_configure(const struct device *dev,
			 enum ieee802154_config_type type,
			 const struct ieee802154_config *config)
{
	ARG_UNUSED(dev);
	ARG_UNUSED(type);
	ARG_UNUSED(config);

	/* configure not supported */

	return -ENOTSUP;
}

/* driver-allocated attribute memory - constant across all driver instances */
IEEE802154_DEFINE_PHY_SUPPORTED_CHANNELS(drv_attr, 11, 26);

/* API implementation: attr_get */
static int b91_attr_get(const struct device *dev, enum ieee802154_attr attr,
			struct ieee802154_attr_value *value)
{
	ARG_UNUSED(dev);

	return ieee802154_attr_get_channel_page_and_range(
		attr, IEEE802154_ATTR_PHY_CHANNEL_PAGE_ZERO_OQPSK_2450_BPSK_868_915,
		&drv_attr.phy_supported_channels, value);
}

/* IEEE802154 driver APIs structure */
static const struct ieee802154_radio_api b91_radio_api = {
	.iface_api.init = b91_iface_init,
	.get_capabilities = b91_get_capabilities,
	.cca = b91_cca,
	.set_channel = b91_set_channel,
	.filter = b91_filter,
	.set_txpower = b91_set_txpower,
	.start = b91_start,
	.stop = b91_stop,
	.tx = b91_tx,
	.ed_scan = b91_ed_scan,
	.configure = b91_configure,
	.attr_get = b91_attr_get,
};


#if defined(CONFIG_NET_L2_IEEE802154)
#define L2 IEEE802154_L2
#define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(IEEE802154_L2)
#define MTU 125
#elif defined(CONFIG_NET_L2_OPENTHREAD)
#define L2 OPENTHREAD_L2
#define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(OPENTHREAD_L2)
#define MTU 1280
#endif


/* IEEE802154 driver registration */
#if defined(CONFIG_NET_L2_IEEE802154) || defined(CONFIG_NET_L2_OPENTHREAD)
NET_DEVICE_DT_INST_DEFINE(0, b91_init, NULL, &data, NULL,
			  CONFIG_IEEE802154_B91_INIT_PRIO,
			  &b91_radio_api, L2, L2_CTX_TYPE, MTU);
#else
DEVICE_DT_INST_DEFINE(0, b91_init, NULL, &data, NULL,
		      POST_KERNEL, CONFIG_IEEE802154_B91_INIT_PRIO,
		      &b91_radio_api);
#endif
