/*
 * Copyright 2024 NXP
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <errno.h>
#include <stddef.h>
#include <string.h>

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

#include <zephyr/init.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/crc.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>

#define LOG_LEVEL CONFIG_BT_HCI_DRIVER_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_nxp_ctlr);

#include "common/bt_str.h"

#include "bt_nxp_ctlr_fw.h"

#define DT_DRV_COMPAT nxp_bt_hci_uart

#define FW_UPLOAD_CHANGE_TIMEOUT_RETRY_COUNT 6

static const struct device *uart_dev = DEVICE_DT_GET(DT_INST_GPARENT(0));

#if DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios)
struct gpio_dt_spec sdio_reset = GPIO_DT_SPEC_GET(DT_DRV_INST(0), sdio_reset_gpios);
#endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios) */
#if DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios)
struct gpio_dt_spec w_disable = GPIO_DT_SPEC_GET(DT_DRV_INST(0), w_disable_gpios);
#endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios) */

struct nxp_ctlr_dev_data {
	uint32_t primary_speed;
	bool primary_flowcontrol;
	uint32_t secondary_speed;
	bool secondary_flowcontrol;
};

static struct nxp_ctlr_dev_data uart_dev_data;

#define DI         0x07U
#define POLYNOMIAL 0x04c11db7UL

#define CRC32_LEN 4

static unsigned long crc_table[256U];
static bool made_table;

static void fw_upload_gen_crc32_table(void)
{
	int i, j;
	unsigned long crc_accum;

	for (i = 0; i < 256; i++) {
		crc_accum = ((unsigned long)i << 24);
		for (j = 0; j < 8; j++) {
			if (crc_accum & 0x80000000L) {
				crc_accum = (crc_accum << 1) ^ POLYNOMIAL;
			} else {
				crc_accum = (crc_accum << 1);
			}
		}
		crc_table[i] = crc_accum;
	}
}

static unsigned char fw_upload_crc8(unsigned char *array, unsigned char len)
{
	unsigned char crc_8 = 0xff;

	crc_8 = crc8(array, len, DI, crc_8, false);

	return crc_8;
}

static unsigned long fw_upload_update_crc32(unsigned long crc_accum, char *data_blk_ptr,
					 int data_blk_size)
{
	unsigned int i, j;

	for (j = 0; j < data_blk_size; j++) {
		i = ((unsigned int)(crc_accum >> 24) ^ *data_blk_ptr++) & 0xff;
		crc_accum = (crc_accum << 8) ^ crc_table[i];
	}
	return crc_accum;
}

#define CMD4 0x4U
#define CMD6 0x6U
#define CMD7 0x7U

#define V1_HEADER_DATA_REQ  0xa5U
#define V1_START_INDICATION 0xaaU
#define V1_REQUEST_ACK      0x5aU

#define V3_START_INDICATION 0xabU
#define V3_HEADER_DATA_REQ  0xa7U
#define V3_REQUEST_ACK      0x7aU
#define V3_TIMEOUT_ACK      0x7bU
#define V3_CRC_ERROR        0x7cU

#define REQ_HEADER_LEN    1U
#define A6REQ_PAYLOAD_LEN 8U
#define AbREQ_PAYLOAD_LEN 3U

#define CRC_ERR_BIT          BIT(0)
#define NAK_REC_BIT          BIT(1)
#define TIMEOUT_REC_ACK_BIT  BIT(2)
#define TIMEOUT_REC_HEAD_BIT BIT(3)
#define TIMEOUT_REC_DATA_BIT BIT(4)
#define INVALID_CMD_REC_BIT  BIT(5)
#define WIFI_MIC_FAIL_BIT    BIT(6)
#define BT_MIC_FAIL_BIT      BIT(7)

#define CMD_HDR_LEN 16

/* CMD5 Header to change bootloader baud rate */
static uint8_t cmd5_hdrData[CMD_HDR_LEN] = {0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
					    0x2c, 0x00, 0x00, 0x00, 0x77, 0xdb, 0xfd, 0xe0};
/* CMD7 Header to change timeout of bootloader */
static uint8_t cmd7_hdrData[CMD_HDR_LEN] = {0x07, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
					    0x00, 0x00, 0x00, 0x00, 0x5b, 0x88, 0xf8, 0xba};

enum bt_nxp_ctlr_version {
	VER1 = 1,
	VER2,
	VER3,
};

struct change_speed_config {
	uint32_t clk_div_addr;
	uint32_t clk_div_val;
	uint32_t uart_clk_div_addr;
	uint32_t uart_clk_div_val;
	uint32_t mcr_addr;
	uint32_t mcr_val;
	uint32_t reinit_addr;
	uint32_t reinit_val;
	uint32_t icr_addr;
	uint32_t icr_val;
	uint32_t fcr_addr;
	uint32_t fcr_val;
};

#define SEND_BUFFER_MAX_LENGTH  0xFFFFU /* Maximum 2 byte value */
#define RECV_RING_BUFFER_LENGTH 1024

struct nxp_ctlr_fw_upload_state {
	uint8_t version;
	uint8_t hdr_sig;

	uint8_t buffer[A6REQ_PAYLOAD_LEN + REQ_HEADER_LEN + 1];

	uint8_t send_buffer[SEND_BUFFER_MAX_LENGTH + 1];

	struct {
		uint8_t buffer[RECV_RING_BUFFER_LENGTH];
		uint32_t head;
		uint32_t tail;
		struct k_sem sem;
	} rx;

	uint16_t length;
	uint32_t offset;
	uint16_t error;
	uint8_t crc8;

	uint32_t last_offset;
	uint8_t change_speed_buffer[sizeof(struct change_speed_config) + CRC32_LEN];

	uint32_t fw_length;
	uint32_t current_length;
	const uint8_t *fw;

	uint32_t cmd7_change_timeout_len;
	uint32_t change_speed_buffer_len;

	bool wait_hdr_sig;

	bool is_hdr_data;
	bool is_error_case;
	bool is_cmd7_req;
	bool is_entry_point_req;

	uint8_t last_5bytes_buffer[6];
};

static struct nxp_ctlr_fw_upload_state fw_upload;

static int fw_upload_read_data(uint8_t *buffer, uint32_t len)
{
	int err;

	while (len > 0) {
		err = k_sem_take(&fw_upload.rx.sem,
				 K_MSEC(CONFIG_BT_H4_NXP_CTLR_WAIT_HDR_SIG_TIMEOUT));
		if (err < 0) {
			LOG_ERR("Fail to read data");
			return err;
		}
		*buffer = fw_upload.rx.buffer[fw_upload.rx.tail];
		buffer++;
		fw_upload.rx.tail++;
		fw_upload.rx.tail = fw_upload.rx.tail % sizeof(fw_upload.rx.buffer);
		len--;
	}
	return 0;
}

static void fw_upload_read_to_clear(void)
{
	uint32_t key;

	key = irq_lock();
	k_sem_reset(&fw_upload.rx.sem);
	fw_upload.rx.head = 0;
	fw_upload.rx.tail = 0;
	irq_unlock(key);
}

static int fw_upload_wait_for_hdr_sig(void)
{
	int err;
	int64_t end;
	char c;

	end = k_uptime_get() + CONFIG_BT_H4_NXP_CTLR_WAIT_HDR_SIG_TIMEOUT;
	fw_upload.hdr_sig = 0xFF;

	while (k_uptime_get() < end) {
		err = fw_upload_read_data(&c, 1);
		if (err < 0) {
			k_msleep(1);
			continue;
		}
		if ((c == V1_HEADER_DATA_REQ) || (c == V1_START_INDICATION) ||
		    (c == V3_START_INDICATION) || (c == V3_HEADER_DATA_REQ)) {
			LOG_DBG("HDR SIG found 0x%02X", c);
			fw_upload.hdr_sig = c;
			if (fw_upload.version == 0) {
				if ((c == V3_START_INDICATION) || (c == V3_HEADER_DATA_REQ)) {
					fw_upload.version = VER3;
				} else {
					fw_upload.version = VER1;
				}
			}
			return 0;
		}
	}
	LOG_ERR("HDR SIG not found");
	return -EIO;
}

static void fw_upload_write_data(const uint8_t *buffer, uint32_t len)
{
	for (int i = 0; i < len; i++) {
		uart_poll_out(uart_dev, buffer[i]);
	}
}

static int fw_upload_request_check_crc(uint8_t *buffer, uint8_t request)
{
	uint8_t crc;

	if (request == V3_HEADER_DATA_REQ) {
		crc = fw_upload_crc8(buffer, A6REQ_PAYLOAD_LEN + REQ_HEADER_LEN);
		if (crc != buffer[A6REQ_PAYLOAD_LEN + REQ_HEADER_LEN]) {
			LOG_ERR("Request %d, CRC check failed", request);
			return -EINVAL;
		}
	} else if (request == V3_START_INDICATION) {
		crc = fw_upload_crc8(buffer, AbREQ_PAYLOAD_LEN + REQ_HEADER_LEN);
		if (crc != buffer[AbREQ_PAYLOAD_LEN + REQ_HEADER_LEN]) {
			LOG_ERR("Request %d, CRC check failed", request);
			return -EINVAL;
		}
	} else {
		LOG_ERR("Invalid request %d", request);
	}

	return 0;
}

static void fw_upload_send_ack(uint8_t ack)
{
	if ((ack == V3_REQUEST_ACK) || (ack == V3_CRC_ERROR)) {
		/* prepare crc for 0x7A or 0x7C */
		fw_upload.buffer[0] = ack;
		fw_upload.buffer[1] = fw_upload_crc8(fw_upload.buffer, 1);
		fw_upload_write_data(fw_upload.buffer, 2);
		LOG_DBG("ACK = %x, CRC = %x", ack, fw_upload.buffer[1]);
	} else if (ack == V3_TIMEOUT_ACK) {
		/* prepare crc for 0x7B */
		fw_upload.buffer[0] = ack;
		sys_put_le32(fw_upload.offset, &fw_upload.buffer[1]);
		fw_upload.buffer[5] = fw_upload_crc8(fw_upload.buffer, 5);
		fw_upload_write_data(fw_upload.buffer, 6);
		LOG_DBG("ACK = %x, CRC = %x", ack, fw_upload.buffer[5]);
	} else {
		LOG_ERR("Invalid ack");
	}
}

static int fw_upload_wait_req(bool secondary_speed)
{
	int err;
	uint32_t len;
	uint8_t buffer[10];

	buffer[0] = fw_upload.hdr_sig;
	if (fw_upload.hdr_sig == V3_HEADER_DATA_REQ) {
		/* CMD LINE: 0xA7 <len><offset><error><CRC8> */
		len = A6REQ_PAYLOAD_LEN + 1;
	} else if (fw_upload.hdr_sig == V3_START_INDICATION) {
		/* CMD LINE: 0xAB <CHIP ID><SW loader REV 1 byte><CRC8> */
		len = AbREQ_PAYLOAD_LEN + 1;
	} else {
		return -EINVAL;
	}

	err = fw_upload_read_data(&buffer[1], len);
	if (err < 0) {
		LOG_ERR("Fail to read req");
		return err;
	}

	err = fw_upload_request_check_crc(buffer, fw_upload.hdr_sig);
	if (err != 0) {
		LOG_ERR("Fail to check CRC");
		fw_upload_send_ack(V3_CRC_ERROR);
		return err;
	}

	if (fw_upload.hdr_sig == V3_HEADER_DATA_REQ) {
		fw_upload.length = sys_get_le16(&buffer[1]);
		fw_upload.offset = sys_get_le32(&buffer[3]);
		fw_upload.error = sys_get_le16(&buffer[7]);
		fw_upload.crc8 = buffer[9];
		LOG_DBG("Req: %hhd, %hd, %d, %hd, %hhd", fw_upload.hdr_sig, fw_upload.length,
			fw_upload.offset, fw_upload.error, fw_upload.crc8);
	} else if (fw_upload.hdr_sig == V3_START_INDICATION) {
		uint16_t chip_id;

		fw_upload_send_ack(V3_REQUEST_ACK);
		chip_id = sys_get_le16(&buffer[1]);
		LOG_DBG("Indicate: %hhd, %hd, %hhd, %hhd", fw_upload.hdr_sig, chip_id, buffer[3],
			buffer[4]);

		if (!secondary_speed) {
			return -EINVAL;
		}
	}

	return 0;
}

static int fw_upload_change_timeout(void)
{
	int err = 0;
	bool first = true;
	uint8_t retry = FW_UPLOAD_CHANGE_TIMEOUT_RETRY_COUNT;

	LOG_DBG("");

	fw_upload_gen_crc32_table();

	while (true) {
		err = fw_upload_wait_for_hdr_sig();
		if (err) {
			continue;
		}

		if (fw_upload.version == VER1) {
			return 0;
		} else if (fw_upload.version == VER3) {
			err = fw_upload_wait_req(true);
			if (err) {
				continue;
			}

			if (fw_upload.length == 0) {
				continue;
			}

			if (fw_upload.error == 0) {
				if (first || (fw_upload.last_offset == fw_upload.offset)) {
					fw_upload_send_ack(V3_REQUEST_ACK);
					fw_upload_write_data(cmd7_hdrData,
							     fw_upload.length > CMD_HDR_LEN
								     ? CMD_HDR_LEN
								     : fw_upload.length);
					fw_upload.last_offset = fw_upload.offset;
					first = false;
				} else {
					fw_upload.cmd7_change_timeout_len = CMD_HDR_LEN;
					fw_upload.wait_hdr_sig = false;
					return 0;
				}
			} else {
				if (retry > 0) {
					retry--;
					fw_upload_send_ack(V3_TIMEOUT_ACK);
				} else {
					LOG_ERR("Fail to change timeout with response err %d",
						fw_upload.error);
					return -ENOTSUP;
				}
			}
		} else {
			LOG_ERR("Unsupported version %d", fw_upload.version);
			return -ENOTSUP;
		}
	}

	return -EINVAL;
}

typedef struct {
	uint32_t uartBaudRate;
	uint32_t uartDivisio;
	uint32_t uartClkDivisor;
} uart_baudrate_clkDiv_map_t;

static const uart_baudrate_clkDiv_map_t clk_div_map[] = {
	{115200U, 16U, 0x0075F6FDU},
	{1000000U, 2U, 0x00800000U},
	{3000000U, 1U, 0x00C00000U},
};

static int fw_upload_change_speed_config(struct change_speed_config *config, uint32_t speed)
{
	config->clk_div_addr = 0x7f00008fU;
	config->uart_clk_div_addr = 0x7f000090U;
	config->mcr_addr = 0x7f000091U;
	config->reinit_addr = 0x7f000092U;
	config->icr_addr = 0x7f000093U;
	config->fcr_addr = 0x7f000094U;

	config->mcr_val = 0x00000022U;
	config->reinit_val = 0x00000001U;
	config->icr_val = 0x000000c7U;
	config->fcr_val = 0x000000c7U;

	for (int i = 0; i < ARRAY_SIZE(clk_div_map); i++) {
		if (speed == clk_div_map[i].uartBaudRate) {
			config->clk_div_val = clk_div_map[i].uartClkDivisor;
			config->uart_clk_div_val = clk_div_map[i].uartDivisio;
			return 0;
		}
	}
	return -ENOTSUP;
}

static uint16_t fw_upload_wait_length(uint8_t flag)
{
	uint8_t buffer[4];
	uint16_t len;
	uint16_t len_comp;
	int err;
	uint8_t ack;

	err = fw_upload_read_data(buffer, sizeof(buffer));
	if (err < 0) {
		return 0;
	}

	len = sys_get_le16(buffer);
	len_comp = sys_get_le16(buffer);

	if ((len ^ len_comp) == 0xFFFF) {
		LOG_DBG("remote asks for %d bytes", len);

		/* Successful. Send back the ack. */
		if ((fw_upload.hdr_sig == V1_HEADER_DATA_REQ) ||
		    (fw_upload.hdr_sig == V1_START_INDICATION)) {
			ack = V1_REQUEST_ACK;
			fw_upload_write_data(&ack, 1);
			if (fw_upload.hdr_sig == V1_START_INDICATION) {
				/* Eliminated longjmp(resync, 1); returning restart status */
				return (uint16_t)V1_START_INDICATION;
			}
		}
	} else {
		LOG_ERR("remote asks len %d bytes", len);
		LOG_ERR("remote asks len_comp %d bytes", len_comp);
		/* Failure due to mismatch. */
		ack = 0xbf;
		fw_upload_write_data(&ack, 1);
		/* Start all over again. */
		if (flag) {
			/* Eliminated longjmp(resync, 1); returning restart status */
			return (uint16_t)V1_START_INDICATION;
		}
		len = 0;
	}
	return len;
}

static uint32_t fw_upload_get_payload_length(uint8_t *cmd)
{
	uint32_t len;

	len = sys_get_le32(&cmd[8]);

	return len;
}

static void fw_upload_get_hdr_start(uint8_t *buffer)
{
	int err;
	bool done = false;
	uint32_t count = 0;

	while (!done) {
		err = fw_upload_read_data(&fw_upload.hdr_sig, 1);
		if (err >= 0) {
			if (fw_upload.hdr_sig == V1_HEADER_DATA_REQ) {
				buffer[count++] = fw_upload.hdr_sig;
				done = true;
				LOG_DBG("Found header %x", fw_upload.hdr_sig);
			}
		} else {
			LOG_ERR("Fail to read HDR sig %d", err);
			return;
		}
	}
	err = fw_upload_read_data(&buffer[count], 4);
	if (err < 0) {
		LOG_ERR("Fail to read HDR payload %d", err);
	}
}

static int fw_upload_len_valid(uint8_t *buffer, uint16_t *length)
{
	uint16_t len;
	uint16_t len_comp;

	len = sys_get_le16(&buffer[1]);
	len_comp = sys_get_le16(&buffer[3]);

	if ((len ^ len_comp) == 0xFFFFU) {
		*length = len;
		return 0;
	} else {
		return -EINVAL;
	}
}

static int fw_upload_get_last_5bytes(uint8_t *buffer)
{
	int err;
	uint32_t payload_len;
	uint16_t len = 0;

	memset(fw_upload.last_5bytes_buffer, 0, sizeof(fw_upload.last_5bytes_buffer));

	fw_upload_get_hdr_start(fw_upload.last_5bytes_buffer);
	err = fw_upload_len_valid(fw_upload.last_5bytes_buffer, &len);
	if (err >= 0) {
		LOG_DBG("Valid len %d", len);
	} else {
		LOG_ERR("Invalid HDR");
		return err;
	}

	payload_len = fw_upload_get_payload_length(buffer);

	if ((len == CMD_HDR_LEN) || ((uint32_t)len == payload_len)) {
		LOG_DBG("Len valid");
		fw_upload.is_error_case = false;
		return 0;
	}

	LOG_DBG("Len invalid");
	fw_upload.is_error_case = true;

	return -EINVAL;
}

static void fw_upload_update_result(uint32_t payload_len, uint16_t *sending_len,
				    bool *first_chunk_sent)
{
	if (fw_upload.is_cmd7_req || fw_upload.is_entry_point_req) {
		*sending_len = CMD_HDR_LEN;
		*first_chunk_sent = true;
	} else {
		*sending_len = payload_len;
		*first_chunk_sent = false;
		if (*sending_len == CMD_HDR_LEN) {
			fw_upload.is_hdr_data = true;
		}
	}
}

static int fw_upload_write_hdr_and_payload(uint16_t len_to_send, uint8_t *buffer, bool new_speed)
{
	int err;
	uint32_t payload_len;
	bool send_done = false;
	uint16_t sending_len = CMD_HDR_LEN;
	bool first_chunk_sent = false;

	LOG_DBG("");

	payload_len = fw_upload_get_payload_length(buffer);

	while (!send_done) {
		if (sending_len == len_to_send) {
			if ((sending_len == CMD_HDR_LEN) && (!fw_upload.is_hdr_data)) {
				if ((first_chunk_sent == false) ||
				    (first_chunk_sent && fw_upload.is_error_case)) {
					LOG_DBG("Send first chunk: len %d", sending_len);
					fw_upload_write_data(buffer, sending_len);
					fw_upload_update_result(payload_len, &sending_len,
								&first_chunk_sent);
				} else {
					send_done = true;
					break;
				}
			} else {
				LOG_DBG("Send data: len %d", sending_len);
				if (sending_len) {
					fw_upload_write_data(&buffer[CMD_HDR_LEN], sending_len);
					first_chunk_sent = true;
					sending_len = CMD_HDR_LEN;
					fw_upload.is_hdr_data = false;
					if (new_speed) {
						return 0;
					}
				} else {
					LOG_DBG("Download Complete");
					return 0;
				}
			}
		} else {
			if ((len_to_send & 0x01) == 0x01) {
				if (len_to_send == (CMD_HDR_LEN + 1)) {
					LOG_DBG("Resending first chunk...");
					fw_upload_write_data(buffer, len_to_send - 1);
					sending_len = payload_len;
					first_chunk_sent = false;
				} else if (len_to_send == (payload_len + 1)) {
					LOG_DBG("Resending second chunk...");
					fw_upload_write_data(&buffer[CMD_HDR_LEN], len_to_send - 1);
					sending_len = CMD_HDR_LEN;
					first_chunk_sent = true;
				}
			} else if (len_to_send == CMD_HDR_LEN) {
				LOG_DBG("Resending send buffer...");
				fw_upload_write_data(buffer, len_to_send);
				sending_len = payload_len;
				first_chunk_sent = false;
			} else if (len_to_send == payload_len) {
				LOG_DBG("Resending second chunk...");
				fw_upload_write_data(&buffer[CMD_HDR_LEN], len_to_send);
				sending_len = CMD_HDR_LEN;
				first_chunk_sent = true;
			}
		}

		err = fw_upload_get_last_5bytes(buffer);
		if (err < 0) {
			LOG_ERR("Fail to get response");
			return err;
		}

		if (fw_upload_len_valid(fw_upload.last_5bytes_buffer, &len_to_send) == 0) {
			fw_upload_send_ack(V1_REQUEST_ACK);
			LOG_DBG("BOOT_HEADER_ACK 0x5a sent");
		}
	}
	return len_to_send;
}

static int fw_upload_uart_reconfig(uint32_t speed, bool flow_control)
{
	struct uart_config config;
	int err;

	config.baudrate = speed;
	config.data_bits = UART_CFG_DATA_BITS_8;
	config.flow_ctrl = flow_control ? UART_CFG_FLOW_CTRL_RTS_CTS : UART_CFG_FLOW_CTRL_NONE;
	config.parity = UART_CFG_PARITY_NONE;
	config.stop_bits = UART_CFG_STOP_BITS_1;

	uart_irq_rx_disable(uart_dev);
	uart_irq_tx_disable(uart_dev);
	fw_upload_read_to_clear();
	err = uart_configure(uart_dev, &config);
	uart_irq_rx_enable(uart_dev);

	return err;
}

static int fw_upload_change_speed(uint8_t hdr)
{
	int err;
	uint32_t hdr_len;
	bool load_payload = false;
	bool recovery = false;
	uint16_t len_to_send;
	uint32_t crc;

	err = fw_upload_change_speed_config(
		(struct change_speed_config *)fw_upload.change_speed_buffer,
		uart_dev_data.secondary_speed);
	if (err) {
		return err;
	}

	hdr_len = sizeof(fw_upload.change_speed_buffer);

	fw_upload_gen_crc32_table();
	crc = sys_cpu_to_le32(hdr_len);
	memcpy(cmd5_hdrData + 8, &crc, 4);
	crc = fw_upload_update_crc32(0, (char *)cmd5_hdrData, 12);
	crc = sys_cpu_to_be32(crc);
	memcpy(cmd5_hdrData + 12, &crc, CRC32_LEN);
	crc = fw_upload_update_crc32(0, (char *)fw_upload.change_speed_buffer,
				  (int)sizeof(struct change_speed_config));
	crc = sys_cpu_to_be32(crc);
	memcpy(&fw_upload.change_speed_buffer[sizeof(struct change_speed_config)], &crc, CRC32_LEN);

	while (true) {
		err = fw_upload_wait_for_hdr_sig();

		if (hdr && (err == 0)) {
			if (load_payload) {
				if (fw_upload.version == VER3) {
					fw_upload.change_speed_buffer_len =
						CMD_HDR_LEN + fw_upload.length;
				}
				return 0;
			}
		} else {
			if (recovery) {
				return -ETIME;
			}

			if (load_payload) {
				LOG_ERR("HDR cannot be received by using second speed. receovery "
					"speed");

				err = fw_upload_uart_reconfig(uart_dev_data.primary_speed,
							      uart_dev_data.primary_flowcontrol);
				if (err) {
					return err;
				}

				load_payload = false;
				recovery = true;
				continue;
			}
		}

		if (fw_upload.version == VER1) {
			len_to_send = fw_upload_wait_length(0);
			if (len_to_send == V1_START_INDICATION) {
				return -EINVAL;
			} else if (len_to_send == 0) {
				continue;
			} else if (len_to_send == CMD_HDR_LEN) {
				memcpy(fw_upload.send_buffer, cmd5_hdrData, CMD_HDR_LEN);
				memcpy(&fw_upload.send_buffer[CMD_HDR_LEN],
				       fw_upload.change_speed_buffer, hdr_len);

				err = fw_upload_write_hdr_and_payload(len_to_send,
								   fw_upload.send_buffer, true);
				if (err < 0) {
					return err;
				}

				LOG_DBG("Change speed to %d", uart_dev_data.secondary_speed);

				err = fw_upload_uart_reconfig(uart_dev_data.secondary_speed,
							      uart_dev_data.secondary_flowcontrol);
				if (err) {
					return err;
				}
				load_payload = true;
			} else {
				fw_upload_write_data(fw_upload.change_speed_buffer, hdr_len);

				LOG_DBG("Change speed to %d", uart_dev_data.secondary_speed);

				err = fw_upload_uart_reconfig(uart_dev_data.secondary_speed,
							      uart_dev_data.secondary_flowcontrol);
				if (err) {
					return err;
				}
				load_payload = true;
			}
		} else if (fw_upload.version == VER3) {
			err = fw_upload_wait_req(true);
			if (!(!hdr || (err == 0))) {
				continue;
			}
			if (fw_upload.length && (fw_upload.hdr_sig == V3_HEADER_DATA_REQ)) {
				if (fw_upload.error != 0) {
					fw_upload_send_ack(V3_TIMEOUT_ACK);
					continue;
				}

				fw_upload_send_ack(V3_REQUEST_ACK);
				hdr = true;

				if (fw_upload.length == CMD_HDR_LEN) {
					LOG_DBG("Send CMD5");
					fw_upload_write_data(cmd5_hdrData, fw_upload.length);
					fw_upload.last_offset = fw_upload.offset;
				} else {
					LOG_DBG("Send UA RT config");
					fw_upload_write_data(fw_upload.change_speed_buffer,
							     fw_upload.length);

					LOG_DBG("Change speed to %d",
						uart_dev_data.secondary_speed);

					err = fw_upload_uart_reconfig(
						uart_dev_data.secondary_speed,
						uart_dev_data.secondary_flowcontrol);
					if (err) {
						return err;
					}
					load_payload = true;
				}
			}
		}
	}

	return 0;
}

static int fw_upload_v1_send_data(uint16_t len)
{
	uint32_t cmd;
	uint32_t data_len;
	int ret_len;

	memset(fw_upload.send_buffer, 0, sizeof(fw_upload.send_buffer));

	fw_upload.is_cmd7_req = false;
	fw_upload.is_entry_point_req = false;

	if ((fw_upload.fw_length - fw_upload.current_length) < len) {
		len = fw_upload.fw_length - fw_upload.current_length;
	}

	memcpy(fw_upload.send_buffer, fw_upload.fw + fw_upload.current_length, len);
	fw_upload.current_length += len;
	cmd = sys_get_le32(fw_upload.send_buffer);
	if (cmd == CMD7) {
		fw_upload.is_cmd7_req = true;
		data_len = 0;
	} else {
		data_len = fw_upload_get_payload_length(fw_upload.send_buffer);
		if ((data_len > (sizeof(fw_upload.send_buffer) - len)) ||
		    ((data_len + fw_upload.current_length) > fw_upload.fw_length)) {
			LOG_ERR("Invalid FW at %d/%d", fw_upload.current_length,
				fw_upload.fw_length);
			return -EINVAL;
		}
		memcpy(&fw_upload.send_buffer[len], fw_upload.fw + fw_upload.current_length,
		       data_len);
		fw_upload.current_length += data_len;
		if ((fw_upload.current_length < fw_upload.fw_length) &&
		    ((cmd == CMD6) || (cmd == CMD4))) {
			fw_upload.is_entry_point_req = true;
		}
	}

	ret_len = fw_upload_write_hdr_and_payload(len, fw_upload.send_buffer, false);
	LOG_DBG("FW upload %d/%d", fw_upload.current_length, fw_upload.fw_length);

	return ret_len;
}

static int fw_upload_v3_send_data(void)
{
	uint32_t start;

	LOG_DBG("Sending offset %d", fw_upload.offset);
	if (fw_upload.offset == fw_upload.last_offset) {
		LOG_WRN("Resending offset %d ...", fw_upload.offset);
		fw_upload_write_data(fw_upload.send_buffer, fw_upload.length);
		return fw_upload.length;
	}
	memset(fw_upload.send_buffer, 0, sizeof(fw_upload.send_buffer));
	start = fw_upload.offset - fw_upload.cmd7_change_timeout_len -
		fw_upload.change_speed_buffer_len;
	if (start >= fw_upload.fw_length) {
		LOG_ERR("Invalid fw offset");
		return -EINVAL;
	}

	if ((fw_upload.length + start) > fw_upload.fw_length) {
		fw_upload.length = fw_upload.fw_length - start;
	}
	memcpy(fw_upload.send_buffer, fw_upload.fw + start, fw_upload.length);
	fw_upload.current_length = start + fw_upload.length;

	fw_upload_write_data(fw_upload.send_buffer, fw_upload.length);
	fw_upload.last_offset = fw_upload.offset;

	return fw_upload.length;
}

static int fw_uploading(const uint8_t *fw, uint32_t fw_length)
{
	int err;
	bool secondary_speed = false;
	uint16_t len_to_send;

	fw_upload.wait_hdr_sig = true;
	fw_upload.is_hdr_data = false;
	fw_upload.is_error_case = false;
	fw_upload.is_cmd7_req = false;
	fw_upload.is_entry_point_req = false;
	fw_upload.last_offset = 0xFFFFU;

	err = fw_upload_change_timeout();
	LOG_DBG("Change timeout hdr flag %d (err %d)", fw_upload.wait_hdr_sig, err);
	if (err) {
		return err;
	}

	fw_upload_read_to_clear();

	if (uart_dev_data.secondary_speed &&
	    (uart_dev_data.secondary_speed != uart_dev_data.primary_speed)) {
		LOG_DBG("Change speed to %d", uart_dev_data.secondary_speed);
		err = fw_upload_change_speed(fw_upload.wait_hdr_sig);
		if (err != 0) {
			LOG_ERR("Fail to change speed");
			return err;
		}
		secondary_speed = true;
	}

	fw_upload.fw_length = fw_length;
	fw_upload.current_length = 0;
	fw_upload.fw = fw;

	while (true) {
		err = fw_upload_wait_for_hdr_sig();
		if (secondary_speed && (err != 0)) {
			return -ETIME;
		}

		secondary_speed = false;

		if (fw_upload.version == VER1) {
			len_to_send = fw_upload_wait_length(true);

			if (len_to_send == V1_START_INDICATION) {
				continue;
			}
			while (len_to_send > 0) {
				len_to_send = fw_upload_v1_send_data(len_to_send);
			}
			if (fw_upload.current_length >= fw_upload.fw_length) {
				LOG_DBG("FW download done");
				return 0;
			}
			LOG_ERR("FW download failed");
			return len_to_send;
		} else if (fw_upload.version == VER3) {
			if (fw_upload.hdr_sig == V3_START_INDICATION) {
				fw_upload_wait_req(false);
				continue;
			}
			err = fw_upload_wait_req(false);
			if (err) {
				LOG_ERR("Fail to wait req");
				return err;
			}
			if (fw_upload.length) {
				if (fw_upload.error == 0) {
					fw_upload_send_ack(V3_REQUEST_ACK);
					err = fw_upload_v3_send_data();
					if (err < 0) {
						LOG_ERR("FW download failed");
						return err;
					}
				} else {
					LOG_DBG("Error occurs %d", fw_upload.error);
					fw_upload_send_ack(V3_TIMEOUT_ACK);
					if (fw_upload.error & BT_MIC_FAIL_BIT) {
						fw_upload.change_speed_buffer_len = 0;
						fw_upload.current_length = 0;
						fw_upload.last_offset = 0;
					}
				}
			} else {
				if (fw_upload.error == 0) {
					fw_upload_send_ack(V3_REQUEST_ACK);
					LOG_DBG("FW download done");
					return 0;
				}
				LOG_DBG("Error occurs %d", fw_upload.error);
				fw_upload_send_ack(V3_TIMEOUT_ACK);
				if (fw_upload.error & BT_MIC_FAIL_BIT) {
					fw_upload.change_speed_buffer_len = 0;
					fw_upload.current_length = 0;
					fw_upload.last_offset = 0;
				}
			}
		} else {
			return -ENOTSUP;
		}
	}
	return -EINVAL;
}

static void bt_nxp_ctlr_uart_isr(const struct device *unused, void *user_data)
{
	int err = 0;
	int count = 0;

	ARG_UNUSED(unused);
	ARG_UNUSED(user_data);

	while (uart_irq_update(uart_dev) && uart_irq_is_pending(uart_dev)) {
		err = uart_poll_in(uart_dev, &fw_upload.rx.buffer[fw_upload.rx.head]);
		if (err >= 0) {
			fw_upload.rx.head++;
			fw_upload.rx.head = fw_upload.rx.head % sizeof(fw_upload.rx.buffer);
			count++;
		}
	}

	while (count > 0) {
		k_sem_give(&fw_upload.rx.sem);
		count--;
	}
}

static int bt_nxp_ctlr_init(void)
{
	int err;
	uint32_t speed;
	bool flowcontrol_of_hci;

	if (!device_is_ready(uart_dev)) {
		return -ENODEV;
	}

	speed = DT_PROP(DT_INST_GPARENT(0), current_speed);
	uart_dev_data.primary_speed = DT_PROP_OR(DT_DRV_INST(0), fw_download_primary_speed, speed);
	uart_dev_data.secondary_speed =
		DT_PROP_OR(DT_DRV_INST(0), fw_download_secondary_speed, speed);

	flowcontrol_of_hci = (bool)DT_PROP_OR(DT_DRV_INST(0), hw_flow_control, false);
	uart_dev_data.primary_flowcontrol =
		(bool)DT_PROP_OR(DT_DRV_INST(0), fw_download_primary_flowcontrol, false);
	uart_dev_data.secondary_flowcontrol =
		(bool)DT_PROP_OR(DT_DRV_INST(0), fw_download_secondary_flowcontrol, false);

#if DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios) ||                                          \
	DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios)
#if DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios)
	/* Check BT REG_ON gpio instance */
	if (!gpio_is_ready_dt(&sdio_reset)) {
		LOG_ERR("Error: failed to configure sdio_reset %s pin %d", sdio_reset.port->name,
			sdio_reset.pin);
		return -EIO;
	}

	/* Configure sdio_reset as output  */
	err = gpio_pin_configure_dt(&sdio_reset, GPIO_OUTPUT);
	if (err) {
		LOG_ERR("Error %d: failed to configure sdio_reset %s pin %d", err,
			sdio_reset.port->name, sdio_reset.pin);
		return err;
	}
	err = gpio_pin_set_dt(&sdio_reset, 0);
	if (err) {
		return err;
	}
#endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios) */

#if DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios)
	/* Check BT REG_ON gpio instance */
	if (!gpio_is_ready_dt(&w_disable)) {
		LOG_ERR("Error: failed to configure w_disable %s pin %d", w_disable.port->name,
			w_disable.pin);
		return -EIO;
	}

	/* Configure w_disable as output  */
	err = gpio_pin_configure_dt(&w_disable, GPIO_OUTPUT);
	if (err) {
		LOG_ERR("Error %d: failed to configure w_disable %s pin %d", err,
			w_disable.port->name, w_disable.pin);
		return err;
	}
	err = gpio_pin_set_dt(&w_disable, 0);
	if (err) {
		return err;
	}
#endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios) */

	/* wait for reset done */
	k_sleep(K_MSEC(100));

#if DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios)
	err = gpio_pin_set_dt(&sdio_reset, 1);
	if (err) {
		return err;
	}
#endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios) */

#if DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios)
	err = gpio_pin_set_dt(&w_disable, 1);
	if (err) {
		return err;
	}
#endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios) */
#endif

	uart_irq_rx_disable(uart_dev);
	uart_irq_tx_disable(uart_dev);

	fw_upload.rx.head = 0;
	fw_upload.rx.tail = 0;

	k_sem_init(&fw_upload.rx.sem, 0, sizeof(fw_upload.rx.buffer));

	uart_irq_callback_set(uart_dev, bt_nxp_ctlr_uart_isr);

	made_table = false;

	err = fw_upload_uart_reconfig(uart_dev_data.primary_speed,
				      uart_dev_data.primary_flowcontrol);
	if (err) {
		LOG_ERR("Fail to config uart");
		return err;
	}

	uart_irq_rx_enable(uart_dev);

	err = fw_uploading(bt_fw_bin, bt_fw_bin_len);

	if (err) {
		LOG_ERR("Fail to upload firmware");
		return err;
	}

	(void)fw_upload_uart_reconfig(speed, flowcontrol_of_hci);

	uart_irq_rx_disable(uart_dev);
	uart_irq_tx_disable(uart_dev);

	k_sleep(K_MSEC(CONFIG_BT_H4_NXP_CTLR_WAIT_TIME_AFTER_UPLOAD));

	return 0;
}

int bt_hci_transport_setup(const struct device *dev)
{
	if (dev != uart_dev) {
		return -EINVAL;
	}

	return bt_nxp_ctlr_init();
}

#define BT_HCI_VSC_BAUDRATE_UPDATE_LENGTH 4
#define BT_HCI_VSC_BAUDRATE_UPDATE_OPCODE BT_OP(BT_OGF_VS, 0x09)

static int bt_hci_baudrate_update(const struct device *dev, uint32_t baudrate)
{
	int err;
	struct net_buf *buf;

	buf = bt_hci_cmd_create(BT_HCI_VSC_BAUDRATE_UPDATE_OPCODE,
				BT_HCI_VSC_BAUDRATE_UPDATE_LENGTH);
	if (!buf) {
		LOG_ERR("Fail to allocate buffer");
		return -ENOBUFS;
	}

	/* Add new baudrate to the buffer */
	net_buf_add_le32(buf, baudrate);

	err = bt_hci_cmd_send_sync(BT_HCI_VSC_BAUDRATE_UPDATE_OPCODE, buf, NULL);
	if (err) {
		LOG_ERR("Fail to send baudrate update cmd");
		return err;
	}

	return 0;
}

int bt_h4_vnd_setup(const struct device *dev)
{
	int err;
	uint32_t default_speed;
	uint32_t operation_speed;
	bool flowcontrol_of_hci;

	if (dev != uart_dev) {
		return -EINVAL;
	}

	if (!device_is_ready(uart_dev)) {
		return -ENODEV;
	}

	default_speed = DT_PROP(DT_INST_GPARENT(0), current_speed);
	operation_speed = DT_PROP_OR(DT_DRV_INST(0), hci_operation_speed, default_speed);
	flowcontrol_of_hci = (bool)DT_PROP_OR(DT_DRV_INST(0), hw_flow_control, false);

	if (operation_speed == default_speed) {
		return 0;
	}

	err = bt_hci_baudrate_update(dev, operation_speed);
	if (err) {
		return err;
	}

	/* BT waiting time after controller bandrate updated */
	(void)k_msleep(CONFIG_BT_H4_NXP_CTLR_WAIT_TIME_AFTER_BAUDRATE_UPDATE);

	err = fw_upload_uart_reconfig(operation_speed, flowcontrol_of_hci);
	if (err) {
		LOG_ERR("Fail to update uart bandrate");
		return err;
	}

	return 0;
}
