/*
 * Copyright (c) 2023 DENX Software Engineering GmbH
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "oa_tc6.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(oa_tc6, CONFIG_ETHERNET_LOG_LEVEL);

int oa_tc6_reg_read(struct oa_tc6 *tc6, const uint32_t reg, uint32_t *val)
{
	uint8_t buf[OA_TC6_HDR_SIZE + 12] = { 0 };
	struct spi_buf tx_buf = { .buf = buf, .len = sizeof(buf) };
	const struct spi_buf_set tx = {	.buffers = &tx_buf, .count = 1 };
	struct spi_buf rx_buf = { .buf = buf, .len = sizeof(buf) };
	const struct spi_buf_set rx = {	.buffers = &rx_buf, .count = 1 };
	uint32_t rv, rvn, hdr_bkp, *hdr = (uint32_t *) &buf[0];
	int ret = 0;

	/*
	 * Buffers are allocated for protected (larger) case (by 4 bytes).
	 * When non-protected case - we need to decrase them
	 */
	if (!tc6->protected) {
		tx_buf.len -= sizeof(rvn);
		rx_buf.len -= sizeof(rvn);
	}

	*hdr = FIELD_PREP(OA_CTRL_HDR_DNC, 0) |
		FIELD_PREP(OA_CTRL_HDR_WNR, 0) |
		FIELD_PREP(OA_CTRL_HDR_AID, 0) |
		FIELD_PREP(OA_CTRL_HDR_MMS, reg >> 16) |
		FIELD_PREP(OA_CTRL_HDR_ADDR, reg) |
		FIELD_PREP(OA_CTRL_HDR_LEN, 0); /* To read single register len = 0 */
	*hdr |= FIELD_PREP(OA_CTRL_HDR_P, oa_tc6_get_parity(*hdr));
	hdr_bkp = *hdr;
	*hdr = sys_cpu_to_be32(*hdr);

	ret = spi_transceive_dt(tc6->spi, &tx, &rx);
	if (ret < 0) {
		return ret;
	}

	/* Check if echoed control command header is correct */
	rv = sys_be32_to_cpu(*(uint32_t *)&buf[4]);
	if (hdr_bkp != rv) {
		LOG_ERR("Header transmission error!");
		return -1;
	}

	rv = sys_be32_to_cpu(*(uint32_t *)&buf[8]);

	/* In protected mode read data is followed by its compliment value */
	if (tc6->protected) {
		rvn = sys_be32_to_cpu(*(uint32_t *)&buf[12]);
		if (rv != ~rvn) {
			LOG_ERR("Protected mode transmission error!");
			return -1;
		}
	}

	*val = rv;

	return ret;
}

int oa_tc6_reg_write(struct oa_tc6 *tc6, const uint32_t reg, uint32_t val)
{
	uint8_t buf_tx[OA_TC6_HDR_SIZE + 12] = { 0 };
	uint8_t buf_rx[OA_TC6_HDR_SIZE + 12] = { 0 };
	struct spi_buf tx_buf = { .buf = buf_tx, .len = sizeof(buf_tx) };
	const struct spi_buf_set tx = {	.buffers = &tx_buf, .count = 1 };
	struct spi_buf rx_buf = { .buf = buf_rx, .len = sizeof(buf_rx) };
	const struct spi_buf_set rx = {	.buffers = &rx_buf, .count = 1	};
	uint32_t rv, rvn, hdr_bkp, *hdr = (uint32_t *) &buf_tx[0];
	int ret;

	/*
	 * Buffers are allocated for protected (larger) case (by 4 bytes).
	 * When non-protected case - we need to decrase them
	 */
	if (!tc6->protected) {
		tx_buf.len -= sizeof(rvn);
		rx_buf.len -= sizeof(rvn);
	}

	*hdr = FIELD_PREP(OA_CTRL_HDR_DNC, 0) |
		FIELD_PREP(OA_CTRL_HDR_WNR, 1) |
		FIELD_PREP(OA_CTRL_HDR_AID, 0) |
		FIELD_PREP(OA_CTRL_HDR_MMS, reg >> 16) |
		FIELD_PREP(OA_CTRL_HDR_ADDR, reg) |
		FIELD_PREP(OA_CTRL_HDR_LEN, 0); /* To read single register len = 0 */
	*hdr |= FIELD_PREP(OA_CTRL_HDR_P, oa_tc6_get_parity(*hdr));
	hdr_bkp = *hdr;
	*hdr = sys_cpu_to_be32(*hdr);

	*(uint32_t *)&buf_tx[4] = sys_cpu_to_be32(val);
	if (tc6->protected) {
		*(uint32_t *)&buf_tx[8] = sys_be32_to_cpu(~val);
	}

	ret = spi_transceive_dt(tc6->spi, &tx, &rx);
	if (ret < 0) {
		return ret;
	}

	/* Check if echoed control command header is correct */
	rv = sys_be32_to_cpu(*(uint32_t *)&buf_rx[4]);
	if (hdr_bkp != rv) {
		LOG_ERR("Header transmission error!");
		return -1;
	}

	/* Check if echoed value is correct */
	rv = sys_be32_to_cpu(*(uint32_t *)&buf_rx[8]);
	if (val != rv) {
		LOG_ERR("Header transmission error!");
		return -1;
	}

	/*
	 * In protected mode check if read value is followed by its
	 * compliment value
	 */
	if (tc6->protected) {
		rvn = sys_be32_to_cpu(*(uint32_t *)&buf_rx[12]);
		if (val != ~rvn) {
			LOG_ERR("Protected mode transmission error!");
			return -1;
		}
	}

	return ret;
}

int oa_tc6_reg_rmw(struct oa_tc6 *tc6, const uint32_t reg,
		   uint32_t mask, uint32_t val)
{
	uint32_t tmp;
	int ret;

	ret = oa_tc6_reg_read(tc6, reg, &tmp);
	if (ret < 0) {
		return ret;
	}

	tmp &= ~mask;

	if (val) {
		tmp |= val;
	}

	return oa_tc6_reg_write(tc6, reg, tmp);
}

int oa_tc6_set_protected_ctrl(struct oa_tc6 *tc6, bool prote)
{
	int ret = oa_tc6_reg_rmw(tc6, OA_CONFIG0, OA_CONFIG0_PROTE,
				 prote ? OA_CONFIG0_PROTE : 0);
	if (ret < 0) {
		return ret;
	}

	tc6->protected = prote;
	return 0;
}

int oa_tc6_send_chunks(struct oa_tc6 *tc6, struct net_pkt *pkt)
{
	uint16_t len = net_pkt_get_len(pkt);
	uint8_t oa_tx[tc6->cps];
	uint32_t hdr, ftr;
	uint8_t chunks, i;
	int ret;

	if (len == 0) {
		return -ENODATA;
	}

	chunks = len / tc6->cps;
	if (len % tc6->cps) {
		chunks++;
	}

	/* Check if LAN865x has any free internal buffer space */
	if (chunks > tc6->txc) {
		return -EIO;
	}

	/* Transform struct net_pkt content into chunks */
	for (i = 1; i <= chunks; i++) {
		hdr = FIELD_PREP(OA_DATA_HDR_DNC, 1) |
			FIELD_PREP(OA_DATA_HDR_DV, 1) |
			FIELD_PREP(OA_DATA_HDR_NORX, 1) |
			FIELD_PREP(OA_DATA_HDR_SWO, 0);

		if (i == 1) {
			hdr |= FIELD_PREP(OA_DATA_HDR_SV, 1);
		}

		if (i == chunks) {
			hdr |= FIELD_PREP(OA_DATA_HDR_EBO, len - 1) |
				FIELD_PREP(OA_DATA_HDR_EV, 1);
		}

		hdr |= FIELD_PREP(OA_DATA_HDR_P, oa_tc6_get_parity(hdr));

		ret = net_pkt_read(pkt, oa_tx, len > tc6->cps ? tc6->cps : len);
		if (ret < 0) {
			return ret;
		}

		ret = oa_tc6_chunk_spi_transfer(tc6, NULL, oa_tx, hdr, &ftr);
		if (ret < 0) {
			return ret;
		}

		len -= tc6->cps;
	}

	return 0;
}

int oa_tc6_check_status(struct oa_tc6 *tc6)
{
	uint32_t sts;

	if (!tc6->sync) {
		LOG_ERR("SYNC: Configuration lost, reset IC!");
		return -EIO;
	}

	if (tc6->exst) {
		/*
		 * Just clear any pending interrupts.
		 * The RESETC is handled separately as it requires per
		 * device configuration.
		 */
		oa_tc6_reg_read(tc6, OA_STATUS0, &sts);
		if (sts != 0) {
			oa_tc6_reg_write(tc6, OA_STATUS0, sts);
			LOG_WRN("EXST: OA_STATUS0: 0x%x", sts);
		}

		oa_tc6_reg_read(tc6, OA_STATUS1, &sts);
		if (sts != 0) {
			oa_tc6_reg_write(tc6, OA_STATUS1, sts);
			LOG_WRN("EXST: OA_STATUS1: 0x%x", sts);
		}
	}

	return 0;
}

static int oa_tc6_update_status(struct oa_tc6 *tc6, uint32_t ftr)
{
	if (oa_tc6_get_parity(ftr)) {
		LOG_DBG("OA Status Update: Footer parity error!");
		return -EIO;
	}

	tc6->exst = FIELD_GET(OA_DATA_FTR_EXST, ftr);
	tc6->sync = FIELD_GET(OA_DATA_FTR_SYNC, ftr);
	tc6->rca = FIELD_GET(OA_DATA_FTR_RCA, ftr);
	tc6->txc = FIELD_GET(OA_DATA_FTR_TXC, ftr);

	return 0;
}

int oa_tc6_chunk_spi_transfer(struct oa_tc6 *tc6, uint8_t *buf_rx, uint8_t *buf_tx,
				     uint32_t hdr, uint32_t *ftr)
{
	struct spi_buf tx_buf[2];
	struct spi_buf rx_buf[2];
	struct spi_buf_set tx;
	struct spi_buf_set rx;
	int ret;

	hdr = sys_cpu_to_be32(hdr);
	tx_buf[0].buf = &hdr;
	tx_buf[0].len = sizeof(hdr);

	tx_buf[1].buf = buf_tx;
	tx_buf[1].len = tc6->cps;

	tx.buffers = tx_buf;
	tx.count = ARRAY_SIZE(tx_buf);

	rx_buf[0].buf = buf_rx;
	rx_buf[0].len = tc6->cps;

	rx_buf[1].buf = ftr;
	rx_buf[1].len = sizeof(*ftr);

	rx.buffers = rx_buf;
	rx.count = ARRAY_SIZE(rx_buf);

	ret = spi_transceive_dt(tc6->spi, &tx, &rx);
	if (ret < 0) {
		return ret;
	}
	*ftr = sys_be32_to_cpu(*ftr);

	return oa_tc6_update_status(tc6, *ftr);
}

int oa_tc6_read_status(struct oa_tc6 *tc6, uint32_t *ftr)
{
	uint32_t hdr;

	hdr = FIELD_PREP(OA_DATA_HDR_DNC, 1) |
		FIELD_PREP(OA_DATA_HDR_DV, 0) |
		FIELD_PREP(OA_DATA_HDR_NORX, 1);
	hdr |= FIELD_PREP(OA_DATA_HDR_P, oa_tc6_get_parity(hdr));

	return oa_tc6_chunk_spi_transfer(tc6, NULL, NULL, hdr, ftr);
}

int oa_tc6_read_chunks(struct oa_tc6 *tc6, struct net_pkt *pkt)
{
	struct net_buf *buf_rx = NULL;
	uint32_t hdr, ftr;
	uint8_t sbo, ebo;
	int ret;

	/*
	 * Special case - append already received data (extracted from previous
	 * chunk) to new packet.
	 */
	if (tc6->concat_buf) {
		net_pkt_append_buffer(pkt, tc6->concat_buf);
		tc6->concat_buf = NULL;
	}

	do {
		buf_rx = net_pkt_get_frag(pkt, tc6->cps, OA_TC6_BUF_ALLOC_TIMEOUT);
		if (!buf_rx) {
			LOG_ERR("OA RX: Can't allocate RX buffer fordata!");
			return -ENOMEM;
		}

		hdr = FIELD_PREP(OA_DATA_HDR_DNC, 1);
		hdr |= FIELD_PREP(OA_DATA_HDR_P, oa_tc6_get_parity(hdr));

		ret = oa_tc6_chunk_spi_transfer(tc6, buf_rx->data, NULL, hdr, &ftr);
		if (ret < 0) {
			LOG_ERR("OA RX: transmission error: %d!", ret);
			goto unref_buf;
		}

		ret = -EIO;
		if (oa_tc6_get_parity(ftr)) {
			LOG_ERR("OA RX: Footer parity error!");
			goto unref_buf;
		}

		if (!FIELD_GET(OA_DATA_FTR_SYNC, ftr)) {
			LOG_ERR("OA RX: Configuration not SYNC'ed!");
			goto unref_buf;
		}

		if (!FIELD_GET(OA_DATA_FTR_DV, ftr)) {
			LOG_DBG("OA RX: Data chunk not valid, skip!");
			goto unref_buf;
		}

		sbo = FIELD_GET(OA_DATA_FTR_SWO, ftr) * sizeof(uint32_t);
		ebo = FIELD_GET(OA_DATA_FTR_EBO, ftr) + 1;

		if (FIELD_GET(OA_DATA_FTR_SV, ftr)) {
			/*
			 * Adjust beginning of the buffer with SWO only when
			 * we DO NOT have two frames concatenated together
			 * in one chunk.
			 */
			if (!(FIELD_GET(OA_DATA_FTR_EV, ftr) && (ebo <= sbo))) {
				if (sbo) {
					net_buf_pull(buf_rx, sbo);
				}
			}
		}

		net_pkt_append_buffer(pkt, buf_rx);
		buf_rx->len = tc6->cps;

		if (FIELD_GET(OA_DATA_FTR_EV, ftr)) {
			/*
			 * Check if received frame shall be dropped - i.e. MAC has
			 * detected error condition, which shall result in frame drop
			 * by the SPI host.
			 */
			if (FIELD_GET(OA_DATA_FTR_FD, ftr)) {
				ret = -EIO;
				goto unref_buf;
			}

			/*
			 * Concatenation of frames in a single chunk - one frame ends
			 * and second one starts just afterwards (ebo == sbo).
			 */
			if (FIELD_GET(OA_DATA_FTR_SV, ftr) && (ebo <= sbo)) {
				tc6->concat_buf = net_buf_clone(buf_rx, OA_TC6_BUF_ALLOC_TIMEOUT);
				if (!tc6->concat_buf) {
					LOG_ERR("OA RX: Can't allocate RX buffer for data!");
					ret = -ENOMEM;
					goto unref_buf;
				}
				net_buf_pull(tc6->concat_buf, sbo);
			}

			/* Set final size of the buffer */
			buf_rx->len = ebo;
			/*
			 * Exit when complete packet is read and added to
			 * struct net_pkt
			 */
			break;
		}
	} while (tc6->rca > 0);

	return 0;

 unref_buf:
	net_buf_unref(buf_rx);
	return ret;
}
