/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Semtech LR11xx LoRa driver for Zephyr RTOS.
 *
 * Implements Zephyr's lora_driver_api for LoRa packet mode operation.
 * Supports: LR1110, LR1120.
 *
 * The LR11xx SPI protocol uses 2-byte opcodes and requires BUSY polling
 * between transactions. See lr11xx.h for opcode definitions.
 *
 * Only LoRa packet mode is implemented. LoRaWAN and GNSS functions
 * of the LR11xx are not used by the KISS modem application.
 */

#define DT_DRV_COMPAT semtech_lr11xx

#include "lr11xx.h"

#include <zephyr/drivers/lora.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(lr11xx, CONFIG_LORA_LOG_LEVEL);

/* ── Helpers to map Zephyr lora_modem_config to LR11xx codes ─────────────── */

static uint8_t zephyr_sf_to_lr11xx(enum lora_datarate dr)
{
	switch (dr) {
	case SF_5:  return LR11XX_LORA_SF5;
	case SF_6:  return LR11XX_LORA_SF6;
	case SF_7:  return LR11XX_LORA_SF7;
	case SF_8:  return LR11XX_LORA_SF8;
	case SF_9:  return LR11XX_LORA_SF9;
	case SF_10: return LR11XX_LORA_SF10;
	case SF_11: return LR11XX_LORA_SF11;
	case SF_12: /* fall through */
	default:    return LR11XX_LORA_SF12;
	}
}

static uint8_t zephyr_bw_to_lr11xx(enum lora_signal_bandwidth bw)
{
	switch (bw) {
	case BW_500_KHZ: return LR11XX_LORA_BW_500;
	case BW_250_KHZ: return LR11XX_LORA_BW_250;
	case BW_125_KHZ: /* fall through */
	default:         return LR11XX_LORA_BW_125;
	}
}

static uint8_t zephyr_cr_to_lr11xx(enum lora_coding_rate cr)
{
	switch (cr) {
	case CR_4_5: return LR11XX_LORA_CR_4_5;
	case CR_4_6: return LR11XX_LORA_CR_4_6;
	case CR_4_7: return LR11XX_LORA_CR_4_7;
	case CR_4_8: /* fall through */
	default:     return LR11XX_LORA_CR_4_8;
	}
}

/* ── Radio configuration ─────────────────────────────────────────────────── */

static int lr11xx_configure(const struct device *dev,
			    struct lora_modem_config *cfg)
{
	int ret;
	uint8_t params[8];

	/* Enter standby (RC oscillator) */
	params[0] = LR11XX_STANDBY_RC;
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_STANDBY, params, 1);
	if (ret < 0) {
		return ret;
	}

	/* Set packet type to LoRa */
	params[0] = LR11XX_PKT_TYPE_LORA;
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_PACKET_TYPE, params, 1);
	if (ret < 0) {
		return ret;
	}

	/* Set RF frequency */
	uint32_t freq = (uint32_t)cfg->frequency;

	params[0] = (freq >> 24) & 0xFF;
	params[1] = (freq >> 16) & 0xFF;
	params[2] = (freq >>  8) & 0xFF;
	params[3] = (freq >>  0) & 0xFF;
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_RF_FREQUENCY, params, 4);
	if (ret < 0) {
		return ret;
	}

	/* Set TX power and ramp time (ramp 40 us = 0x04) */
	params[0] = (uint8_t)cfg->tx_power;
	params[1] = 0x04; /* Ramp time */
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_TX_PARAMS, params, 2);
	if (ret < 0) {
		return ret;
	}

	/*
	 * Set modulation parameters (LoRa):
	 *   [0] SF, [1] BW, [2] CR, [3] LDRO (low data rate optimize)
	 */
	params[0] = zephyr_sf_to_lr11xx(cfg->datarate);
	params[1] = zephyr_bw_to_lr11xx(cfg->bandwidth);
	params[2] = zephyr_cr_to_lr11xx(cfg->coding_rate);
	params[3] = 0x00; /* LDRO: auto would require SF/BW calculation */
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_MODULATION_PARAMS,
				   params, 4);
	if (ret < 0) {
		return ret;
	}

	/*
	 * Set packet parameters (LoRa):
	 *   [0:1] preamble length, [2] header type (0=explicit),
	 *   [3]   payload length,  [4] CRC mode (1=on),
	 *   [5]   IQ inversion (0=standard)
	 */
	uint16_t preamble = (uint16_t)cfg->preamble_len;

	params[0] = (preamble >> 8) & 0xFF;
	params[1] = (preamble >> 0) & 0xFF;
	params[2] = 0x00;  /* explicit header */
	params[3] = 0xFF;  /* max payload (will be overridden per-packet) */
	params[4] = 0x01;  /* CRC on */
	params[5] = 0x00;  /* standard IQ */
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_PACKET_PARAMS,
				   params, 6);
	if (ret < 0) {
		return ret;
	}

	/* Set buffer base addresses (TX=0, RX=0) */
	params[0] = 0x00;
	params[1] = 0x00;
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_BUFFER_BASE_ADDR,
				   params, 2);
	if (ret < 0) {
		return ret;
	}

	/* Configure DIO IRQ routing to EVENT/DIO9 */
	uint32_t irq_mask = LR11XX_DIO9_MASK;

	params[0] = (irq_mask >> 24) & 0xFF;
	params[1] = (irq_mask >> 16) & 0xFF;
	params[2] = (irq_mask >>  8) & 0xFF;
	params[3] = (irq_mask >>  0) & 0xFF;
	/* DIO9 enable mask = same as IRQ mask */
	params[4] = params[0];
	params[5] = params[1];
	params[6] = params[2];
	params[7] = params[3];
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_DIO_IRQ_PARAMS,
				   params, 8);

	return ret;
}

/* ── Send ────────────────────────────────────────────────────────────────── */

static int lr11xx_send(const struct device *dev,
		       uint8_t *data, uint32_t data_len)
{
	struct lr11xx_data *drv = dev->data;
	int ret;
	uint8_t params[3];

	/* Clear pending IRQ flags */
	uint32_t clr = LR11XX_IRQ_ALL;

	params[0] = (clr >> 24) & 0xFF;
	params[1] = (clr >> 16) & 0xFF;
	params[2] = (clr >>  8) & 0xFF;
	params[3] = (clr >>  0) & 0xFF;
	lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4);

	/* Write payload to TX buffer at offset 0 */
	uint8_t *tx_buf = k_malloc(data_len + 1);

	if (!tx_buf) {
		return -ENOMEM;
	}
	tx_buf[0] = 0x00; /* buffer offset */
	memcpy(&tx_buf[1], data, data_len);

	/* Use opcode 0x010A (WriteBuffer8) to write payload */
	ret = lr11xx_hal_write_cmd(dev, 0x010Au, tx_buf, data_len + 1);
	k_free(tx_buf);
	if (ret < 0) {
		return ret;
	}

	/* Update packet params with actual payload length */
	uint8_t pkt_params[6];

	pkt_params[0] = 0x00;
	pkt_params[1] = 0x08;  /* preamble length high byte */
	pkt_params[2] = 0x00;  /* explicit header */
	pkt_params[3] = (uint8_t)data_len;
	pkt_params[4] = 0x01;  /* CRC on */
	pkt_params[5] = 0x00;  /* standard IQ */
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_PACKET_PARAMS,
				   pkt_params, 6);
	if (ret < 0) {
		return ret;
	}

	/* Reset the event semaphore and trigger TX */
	k_sem_reset(&drv->event_sem);

	params[0] = 0x00;  /* TX timeout = 0 (no timeout) */
	params[1] = 0x00;
	params[2] = 0x00;
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_TX, params, 3);
	if (ret < 0) {
		return ret;
	}

	/* Wait for TX_DONE IRQ */
	ret = k_sem_take(&drv->event_sem, K_MSEC(5000));
	if (ret < 0) {
		LOG_WRN("TX timeout");
		return -ETIMEDOUT;
	}

	/* Read and check IRQ status */
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_IRQ_STATUS, NULL, 0);
	if (ret < 0) {
		return ret;
	}

	uint8_t irq_buf[4];

	ret = lr11xx_hal_read_resp(dev, irq_buf, sizeof(irq_buf));
	if (ret < 0) {
		return ret;
	}

	uint32_t irq = ((uint32_t)irq_buf[0] << 24) |
		       ((uint32_t)irq_buf[1] << 16) |
		       ((uint32_t)irq_buf[2] <<  8) |
		       ((uint32_t)irq_buf[3]);

	if (!(irq & LR11XX_IRQ_TX_DONE)) {
		LOG_WRN("TX: unexpected IRQ 0x%08x", irq);
		return -EIO;
	}

	/* Clear IRQ flags */
	params[0] = (irq >> 24) & 0xFF;
	params[1] = (irq >> 16) & 0xFF;
	params[2] = (irq >>  8) & 0xFF;
	params[3] = (irq >>  0) & 0xFF;
	lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4);

	return 0;
}

/* ── Receive ─────────────────────────────────────────────────────────────── */

static int lr11xx_recv(const struct device *dev,
		       uint8_t *data, uint8_t size,
		       k_timeout_t timeout,
		       int16_t *rssi, int8_t *snr)
{
	struct lr11xx_data *drv = dev->data;
	int ret;
	uint8_t params[3];

	/* Clear pending IRQs */
	uint32_t clr = LR11XX_IRQ_ALL;

	params[0] = (clr >> 24) & 0xFF;
	params[1] = (clr >> 16) & 0xFF;
	params[2] = (clr >>  8) & 0xFF;
	params[3] = (clr >>  0) & 0xFF;
	lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4);

	/* Enter continuous RX */
	k_sem_reset(&drv->event_sem);

	params[0] = 0xFF;  /* RX timeout: 0xFFFFFF = single with preamble timeout */
	params[1] = 0xFF;
	params[2] = 0xFF;
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_SET_RX, params, 3);
	if (ret < 0) {
		return ret;
	}

	/* Wait for RX_DONE (or timeout) */
	ret = k_sem_take(&drv->event_sem, timeout);
	if (ret < 0) {
		return -ETIMEDOUT;
	}

	/* Read IRQ status */
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_IRQ_STATUS, NULL, 0);
	if (ret < 0) {
		return ret;
	}

	uint8_t irq_buf[4];

	ret = lr11xx_hal_read_resp(dev, irq_buf, sizeof(irq_buf));
	if (ret < 0) {
		return ret;
	}

	uint32_t irq = ((uint32_t)irq_buf[0] << 24) |
		       ((uint32_t)irq_buf[1] << 16) |
		       ((uint32_t)irq_buf[2] <<  8) |
		       ((uint32_t)irq_buf[3]);

	/* Clear IRQ flags */
	params[0] = (irq >> 24) & 0xFF;
	params[1] = (irq >> 16) & 0xFF;
	params[2] = (irq >>  8) & 0xFF;
	params[3] = (irq >>  0) & 0xFF;
	lr11xx_hal_write_cmd(dev, LR11XX_CMD_CLEAR_IRQ_STATUS, params, 4);

	if (irq & (LR11XX_IRQ_TIMEOUT | LR11XX_IRQ_CRC_ERROR)) {
		LOG_DBG("RX error IRQ=0x%08x", irq);
		return -EIO;
	}

	if (!(irq & LR11XX_IRQ_RX_DONE)) {
		return -EIO;
	}

	/* Get RX buffer status: payload_len, rx_start_ptr */
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_RX_BUFFER_STATUS, NULL, 0);
	if (ret < 0) {
		return ret;
	}

	uint8_t rxbuf_status[2];

	ret = lr11xx_hal_read_resp(dev, rxbuf_status, sizeof(rxbuf_status));
	if (ret < 0) {
		return ret;
	}

	uint8_t payload_len = rxbuf_status[0];
	uint8_t offset      = rxbuf_status[1];

	if (payload_len > size) {
		payload_len = size;
	}

	/* Read payload from RX buffer */
	uint8_t read_params[2] = { offset, payload_len };

	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_READ_BUFFER,
				   read_params, sizeof(read_params));
	if (ret < 0) {
		return ret;
	}

	ret = lr11xx_hal_read_resp(dev, data, payload_len);
	if (ret < 0) {
		return ret;
	}

	/* Get packet status (RSSI, SNR) */
	ret = lr11xx_hal_write_cmd(dev, LR11XX_CMD_GET_PACKET_STATUS, NULL, 0);
	if (ret < 0) {
		goto skip_status;
	}

	uint8_t pkt_status[3];

	ret = lr11xx_hal_read_resp(dev, pkt_status, sizeof(pkt_status));
	if (ret == 0) {
		/* rssi_pkt = -pkt_status[0] / 2 dBm */
		if (rssi) {
			*rssi = -(int16_t)pkt_status[0] / 2;
		}
		/* snr_pkt = pkt_status[1] / 4 dB (signed) */
		if (snr) {
			*snr = (int8_t)pkt_status[1] / 4;
		}
	}

skip_status:
	return (int)payload_len;
}

/* ── Initialization ──────────────────────────────────────────────────────── */

static int lr11xx_init(const struct device *dev)
{
	const struct lr11xx_config *cfg = dev->config;
	struct lr11xx_data *drv = dev->data;
	int ret;

	if (!spi_is_ready_dt(&cfg->spi)) {
		LOG_ERR("SPI bus not ready");
		return -ENODEV;
	}

	ret = gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
		return ret;
	}

	ret = gpio_pin_configure_dt(&cfg->busy, GPIO_INPUT);
	if (ret < 0) {
		return ret;
	}

	ret = gpio_pin_configure_dt(&cfg->event, GPIO_INPUT);
	if (ret < 0) {
		return ret;
	}

	k_sem_init(&drv->event_sem, 0, 1);

	/* Set up interrupt on EVENT/DIO9 pin */
	ret = gpio_pin_interrupt_configure_dt(&cfg->event,
					      GPIO_INT_EDGE_TO_ACTIVE);
	if (ret < 0) {
		return ret;
	}

	gpio_init_callback(&drv->event_cb, lr11xx_hal_event_handler,
			   BIT(cfg->event.pin));
	ret = gpio_add_callback(cfg->event.port, &drv->event_cb);
	if (ret < 0) {
		return ret;
	}

	/* Hardware reset */
	ret = lr11xx_hal_reset(dev);
	if (ret < 0) {
		LOG_ERR("Reset failed: %d", ret);
		return ret;
	}

	LOG_INF("LR11xx initialized");
	return 0;
}

/* ── Driver API ──────────────────────────────────────────────────────────── */

static const struct lora_driver_api lr11xx_api = {
	.config  = lr11xx_configure,
	.send    = lr11xx_send,
	.recv    = lr11xx_recv,
};

/* ── Device instantiation macro ─────────────────────────────────────────── */

#define LR11XX_DEFINE(n)						      \
	static struct lr11xx_data lr11xx_data_##n;			      \
									      \
	static const struct lr11xx_config lr11xx_config_##n = {	      \
		.spi   = SPI_DT_SPEC_INST_GET(n,			      \
				SPI_WORD_SET(8) | SPI_TRANSFER_MSB, 0),       \
		.reset = GPIO_DT_SPEC_INST_GET(n, reset_gpios),	      \
		.busy  = GPIO_DT_SPEC_INST_GET(n, busy_gpios),		      \
		.event = GPIO_DT_SPEC_INST_GET(n, event_gpios),	      \
	};								      \
									      \
	DEVICE_DT_INST_DEFINE(n,					      \
		lr11xx_init, NULL,					      \
		&lr11xx_data_##n, &lr11xx_config_##n,			      \
		POST_KERNEL, CONFIG_LORA_INIT_PRIORITY,			      \
		&lr11xx_api);

DT_INST_FOREACH_STATUS_OKAY(LR11XX_DEFINE)
