/**
 * Copyright (c) 2018 Linaro
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT inventek_eswifi
#include "eswifi_log.h"
LOG_MODULE_DECLARE(LOG_MODULE_NAME);

#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 "eswifi.h"

#define ESWIFI_SPI_THREAD_STACK_SIZE 1024
K_KERNEL_STACK_MEMBER(eswifi_spi_poll_stack, ESWIFI_SPI_THREAD_STACK_SIZE);

#define SPI_READ_CHUNK_SIZE 32

struct eswifi_spi_config {
	struct gpio_dt_spec dr;
	struct spi_dt_spec bus;
};

struct eswifi_spi_data {
	const struct eswifi_spi_config *cfg;
	struct k_thread poll_thread;
};

static const struct eswifi_spi_config eswifi_config_spi0 = {
	.dr = GPIO_DT_SPEC_INST_GET(0, data_gpios),
	.bus = SPI_DT_SPEC_INST_GET(0, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB |
				    SPI_WORD_SET(16) | SPI_HOLD_ON_CS |
				    SPI_LOCK_ON, 1000U),
};

static struct eswifi_spi_data eswifi_spi0;

static bool eswifi_spi_cmddata_ready(struct eswifi_spi_data *spi)
{
	return gpio_pin_get_dt(&spi->cfg->dr) > 0;
}

static int eswifi_spi_wait_cmddata_ready(struct eswifi_spi_data *spi)
{
	unsigned int max_retries = 60 * 1000; /* 1 minute */

	do {
		/* allow other threads to be scheduled */
		k_sleep(K_MSEC(1));
	} while (!eswifi_spi_cmddata_ready(spi) && --max_retries);

	return max_retries ? 0 : -ETIMEDOUT;
}

static int eswifi_spi_write(struct eswifi_dev *eswifi, char *data, size_t dlen)
{
	struct eswifi_spi_data *spi = eswifi->bus_data;
	struct spi_buf spi_tx_buf[1];
	struct spi_buf_set spi_tx;
	int status;

	spi_tx_buf[0].buf = data;
	spi_tx_buf[0].len = dlen;
	spi_tx.buffers = spi_tx_buf;
	spi_tx.count = ARRAY_SIZE(spi_tx_buf);

	status = spi_write_dt(&spi->cfg->bus, &spi_tx);
	if (status) {
		LOG_ERR("SPI write error %d", status);
	} else {
		status = dlen;
	}

	return status;
}

static int eswifi_spi_read(struct eswifi_dev *eswifi, char *data, size_t dlen)
{
	struct eswifi_spi_data *spi = eswifi->bus_data;
	struct spi_buf spi_rx_buf[1];
	struct spi_buf_set spi_rx;
	int status;

	spi_rx_buf[0].buf = data;
	spi_rx_buf[0].len = dlen;
	spi_rx.buffers = spi_rx_buf;
	spi_rx.count = ARRAY_SIZE(spi_rx_buf);

	status = spi_read_dt(&spi->cfg->bus, &spi_rx);
	if (status) {
		LOG_ERR("SPI read error %d", status);
	} else {
		status = dlen;
	}

	return status;
}

static int eswifi_spi_request(struct eswifi_dev *eswifi, char *cmd, size_t clen,
			      char *rsp, size_t rlen)
{
	struct eswifi_spi_data *spi = eswifi->bus_data;
	unsigned int offset = 0U, to_read = SPI_READ_CHUNK_SIZE;
	char tmp[2];
	int err;

	LOG_DBG("cmd=%p (%u byte), rsp=%p (%u byte)", cmd, clen, rsp, rlen);

	/*
	 * CMD/DATA protocol:
	 * 1. Module raises data-ready when ready for **command phase**
	 * 2. Host announces command start by lowering chip-select
	 * 3. Host write the command (possibly several spi transfers)
	 * 4. Host announces end of command by raising chip-select
	 * 5. Module lowers data-ready signal
	 * 6. Module raises data-ready to signal start of the **data phase**
	 * 7. Host lowers chip-select
	 * 8. Host fetch data as long as data-ready pin is up
	 * 9. Module lowers data-ready to signal the end of the data Phase
	 * 10. Host raises chip-select
	 *
	 * Note:
	 * All commands to the eS-WiFi module must be post-padded with
	 * 0x0A (Line Feed) to an even number of bytes.
	 * All data from eS-WiFi module are post-padded with 0x15(NAK) to an
	 * even number of bytes.
	 */

	if (!cmd) {
		goto data;
	}

	/* CMD/DATA READY signals the Command Phase */
	err = eswifi_spi_wait_cmddata_ready(spi);
	if (err) {
		LOG_ERR("CMD ready timeout\n");
		return err;
	}

	if (clen % 2) { /* Add post-padding if necessary */
		/* cmd is a string so cmd[clen] is 0x00 */
		cmd[clen] = 0x0a;
		clen++;
	}

	eswifi_spi_write(eswifi, cmd, clen);

	/* Our device is flagged with SPI_HOLD_ON_CS|SPI_LOCK_ON, release */
	spi_release_dt(&spi->cfg->bus);

data:
	/* CMD/DATA READY signals the Data Phase */
	err = eswifi_spi_wait_cmddata_ready(spi);
	if (err) {
		LOG_ERR("DATA ready timeout\n");
		return err;
	}

	while (eswifi_spi_cmddata_ready(spi) && to_read) {
		to_read = MIN(rlen - offset, to_read);
		memset(rsp + offset, 0, to_read);
		eswifi_spi_read(eswifi, rsp + offset, to_read);
		offset += to_read;
		k_yield();
	}

	/* Flush remaining data if receiving buffer not large enough */
	while (eswifi_spi_cmddata_ready(spi)) {
		eswifi_spi_read(eswifi, tmp, 2);
		k_sleep(K_MSEC(1));
	}

	/* Our device is flagged with SPI_HOLD_ON_CS|SPI_LOCK_ON, release */
	spi_release_dt(&spi->cfg->bus);

	LOG_DBG("success");

	return offset;
}

static void eswifi_spi_read_msg(struct eswifi_dev *eswifi)
{
	const char startstr[] = "[SOMA]";
	const char endstr[] = "[EOMA]";
	char cmd[] = "MR\r";
	size_t msg_len;
	char *rsp;
	int ret;

	LOG_DBG("");

	eswifi_lock(eswifi);

	ret = eswifi_at_cmd_rsp(eswifi, cmd, &rsp);
	if (ret < 0) {
		LOG_ERR("Unable to read msg %d", ret);
		eswifi_unlock(eswifi);
		return;
	}

	if (strncmp(rsp, startstr, sizeof(endstr) - 1)) {
		LOG_ERR("Malformed async msg");
		eswifi_unlock(eswifi);
		return;
	}

	/* \r\n[SOMA]...[EOMA]\r\nOK\r\n> */
	msg_len = ret - (sizeof(startstr) - 1) - (sizeof(endstr) - 1);
	if (msg_len > 0) {
		eswifi_async_msg(eswifi, rsp + sizeof(endstr) - 1, msg_len);
	}

	eswifi_unlock(eswifi);
}

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

	struct eswifi_dev *eswifi = p1;

	while (1) {
		k_sleep(K_MSEC(1000));
		eswifi_spi_read_msg(eswifi);
	}
}

int eswifi_spi_init(struct eswifi_dev *eswifi)
{
	struct eswifi_spi_data *spi = &eswifi_spi0; /* Static instance */
	const struct eswifi_spi_config *cfg = &eswifi_config_spi0; /* Static instance */

	/* SPI DATA READY PIN */
	if (!gpio_is_ready_dt(&cfg->dr)) {
		LOG_ERR("device %s is not ready", cfg->dr.port->name);
		return -ENODEV;
	}
	gpio_pin_configure_dt(&cfg->dr, GPIO_INPUT);

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

	spi->cfg = cfg;

	eswifi->bus_data = spi;

	LOG_DBG("success");

	k_thread_create(&spi->poll_thread, eswifi_spi_poll_stack,
			ESWIFI_SPI_THREAD_STACK_SIZE,
			eswifi_spi_poll_thread, eswifi, NULL,
			NULL, K_PRIO_COOP(CONFIG_WIFI_ESWIFI_THREAD_PRIO), 0,
			K_NO_WAIT);

	return 0;
}

static struct eswifi_bus_ops eswifi_bus_ops_spi = {
	.init = eswifi_spi_init,
	.request = eswifi_spi_request,
};

struct eswifi_bus_ops *eswifi_get_bus(void)
{
	return &eswifi_bus_ops_spi;
}
