/*
 * Copyright (c) 2023 Cypress Semiconductor Corporation (an Infineon company) or
 * an affiliate of Cypress Semiconductor Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @brief PSoC 6 BLE (BLESS) driver.
 */

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

#include <zephyr/arch/cpu.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/drivers/bluetooth/hci_driver.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/init.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>

#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER)
#define LOG_LEVEL      CONFIG_BT_HCI_DRIVER_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(psoc6_bless);

#include "cy_ble_stack_pvt.h"
#include "cycfg_ble.h"

#define DT_DRV_COMPAT infineon_cat1_bless_hci

#define PACKET_TYPE_HCI_COMMAND     0X1
#define PACKET_TYPE_HCI_ACL_DATA    0x2
#define PACKET_TYPE_HCI_SYNCHRONOUS 0X3
#define PACKET_TYPE_HCI_EVENT       0X4

#define BLE_LOCK_TMOUT_MS       (1000)
#define BLE_THREAD_SEM_TMOUT_MS (1000)

#define CYBLE_STACK_SIZE (CY_BLE_STACK_RAM_SIZE + 4096)

#define PSOC6_BLESS_OP_SET_PUBLIC_ADDR BT_OP(BT_OGF_VS, 0x1a0)

static K_SEM_DEFINE(psoc6_bless_rx_sem, 0, 1);
static K_SEM_DEFINE(psoc6_bless_operation_sem, 1, 1);
static K_KERNEL_STACK_DEFINE(psoc6_bless_rx_thread_stack, CONFIG_BT_RX_STACK_SIZE);
static struct k_thread psoc6_bless_rx_thread_data;
static cy_stc_ble_hci_tx_packet_info_t hci_tx_pkt;

extern void Cy_BLE_EnableLowPowerMode(void);

CY_ALIGN(sizeof(uint32_t)) CY_NOINIT uint8_t psoc6_bless_stack_memory[CYBLE_STACK_SIZE];

/** BLE Stack parameters */
static cy_stc_ble_stack_params_t psoc6_bless_stack_param = {
	.memoryHeapPtr = psoc6_bless_stack_memory,
	.totalHeapSz = CYBLE_STACK_SIZE,
	.dleMaxTxCapability = CONFIG_BT_PSOC6_BLESS_MAX_TX_PAYLOAD,
	.dleMaxRxCapability = CONFIG_BT_PSOC6_BLESS_MAX_RX_PAYLOAD,
	.featureMask = (CY_BLE_DLE_FEATURE | CY_BLE_LL_PRIVACY_FEATURE |
			CY_BLE_SECURE_CONN_FEATURE | CY_BLE_PHY_UPDATE_FEATURE |
			CY_BLE_STORE_BONDLIST_FEATURE | CY_BLE_STORE_RESOLVING_LIST_FEATURE |
			CY_BLE_STORE_WHITELIST_FEATURE | CY_BLE_TX_POWER_CALIBRATION_FEATURE),
	.maxConnCount = CY_BLE_CONN_COUNT,
	.tx5dbmModeEn = CY_BLE_ENABLE_TX_5DBM,
};

static const cy_stc_sysint_t psoc6_bless_isr_cfg = {
	.intrSrc = DT_INST_IRQN(0),
	.intrPriority = DT_INST_IRQ(0, priority),
};

static cy_stc_ble_hw_config_t psoc6_bless_hw_config = {
	.blessIsrConfig = &psoc6_bless_isr_cfg,
};

static const cy_stc_ble_config_t psoc6_bless_config = {
	.stackParam = &psoc6_bless_stack_param,
	.hw = &psoc6_bless_hw_config,
};

static void psoc6_bless_rx_thread(void *, void *, void *)
{
	while (true) {
		k_sem_take(&psoc6_bless_rx_sem, K_MSEC(BLE_THREAD_SEM_TMOUT_MS));
		Cy_BLE_ProcessEvents();
	}
}

static void psoc6_bless_isr_handler(const struct device *dev)
{
	if (Cy_BLE_HAL_BlessInterruptHandler()) {
		k_sem_give(&psoc6_bless_rx_sem);
	}
}

static void psoc6_bless_events_handler(uint32_t eventCode, void *eventParam)
{
	cy_stc_ble_hci_tx_packet_info_t *hci_rx = NULL;
	struct net_buf *buf = NULL;
	size_t buf_tailroom = 0;

	if (eventCode != CY_BLE_EVT_HCI_PKT_RCVD) {
		LOG_DBG("Other EVENT 0x%X", eventCode);
		return;
	}

	hci_rx = eventParam;

	switch (hci_rx->packetType) {
	case PACKET_TYPE_HCI_EVENT:
		buf = bt_buf_get_evt(hci_rx->data[0], 0, K_NO_WAIT);
		if (!buf) {
			LOG_ERR("Failed to allocate the buffer for RX: EVENT ");
			return;
		}

		break;
	case PACKET_TYPE_HCI_ACL_DATA:
		buf = bt_buf_get_rx(BT_BUF_ACL_IN, K_NO_WAIT);
		if (!buf) {
			LOG_ERR("Failed to allocate the buffer for RX: ACL ");
			return;
		}
		bt_buf_set_type(buf, BT_BUF_ACL_IN);

		break;

	default:
		LOG_WRN("Unsupported HCI Packet Received");
		return;
	}

	buf_tailroom = net_buf_tailroom(buf);
	if (buf_tailroom < hci_rx->dataLength) {
		LOG_WRN("Not enough space for rx data");
		return;
	}
	net_buf_add_mem(buf, hci_rx->data, hci_rx->dataLength);
	bt_recv(buf);
}

static int psoc6_bless_open(void)
{
	k_tid_t tid;

	tid = k_thread_create(&psoc6_bless_rx_thread_data, psoc6_bless_rx_thread_stack,
			      K_KERNEL_STACK_SIZEOF(psoc6_bless_rx_thread_stack),
			      psoc6_bless_rx_thread, NULL, NULL, NULL,
			      K_PRIO_COOP(CONFIG_BT_RX_PRIO), 0, K_NO_WAIT);
	k_thread_name_set(tid, "psoc6_bless_rx_thread");

	return 0;
}

static int psoc6_bless_send(struct net_buf *buf)
{
	cy_en_ble_api_result_t result;

	memset(&hci_tx_pkt, 0, sizeof(cy_stc_ble_hci_tx_packet_info_t));

	hci_tx_pkt.dataLength = buf->len;
	hci_tx_pkt.data = buf->data;

	switch (bt_buf_get_type(buf)) {
	case BT_BUF_ACL_OUT:
		hci_tx_pkt.packetType = PACKET_TYPE_HCI_ACL_DATA;
		break;
	case BT_BUF_CMD:
		hci_tx_pkt.packetType = PACKET_TYPE_HCI_COMMAND;
		break;
	default:
		net_buf_unref(buf);
		return -ENOTSUP;
	}

	if (k_sem_take(&psoc6_bless_operation_sem, K_MSEC(BLE_LOCK_TMOUT_MS)) != 0) {
		LOG_ERR("Failed to acquire BLE DRV Semaphore");
		net_buf_unref(buf);
		return -EIO;
	}

	result = Cy_BLE_SoftHciSendAppPkt(&hci_tx_pkt);
	if (result != CY_BLE_SUCCESS) {
		LOG_ERR("Error in sending packet reason %d\r\n", result);
	}

	k_sem_give(&psoc6_bless_operation_sem);

	net_buf_unref(buf);

	return 0;
}

static int psoc6_bless_setup(const struct bt_hci_setup_params *params)
{
	ARG_UNUSED(params);
	struct net_buf *buf;
	int err;
	uint8_t *addr = (uint8_t *)&SFLASH_BLE_DEVICE_ADDRESS[0];
	uint8_t hci_data[] = {
		addr[5], addr[4], addr[3], addr[2], addr[1], addr[0], BT_ADDR_LE_PUBLIC,
	};

	buf = bt_hci_cmd_create(PSOC6_BLESS_OP_SET_PUBLIC_ADDR, sizeof(hci_data));
	if (buf == NULL) {
		LOG_ERR("Unable to allocate command buffer");
		return -ENOMEM;
	}

	/* Add data part of packet */
	net_buf_add_mem(buf, hci_data, sizeof(hci_data));

	err = bt_hci_cmd_send_sync(PSOC6_BLESS_OP_SET_PUBLIC_ADDR, buf, NULL);
	if (err) {
		return err;
	}

	return 0;
}

static int psoc6_bless_hci_init(void)
{
	cy_en_ble_api_result_t result;
	static const struct bt_hci_driver drv = {
		.name = "PSoC 6 BLESS",
		.bus = BT_HCI_DRIVER_BUS_VIRTUAL,
		.quirks = BT_QUIRK_NO_RESET,
		.open = psoc6_bless_open,
		.send = psoc6_bless_send,
		.setup = psoc6_bless_setup,
	};

	/* Connect BLE interrupt to ISR */
	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), psoc6_bless_isr_handler, 0, 0);

	/* Registers the generic callback functions.  */
	Cy_BLE_RegisterEventCallback(psoc6_bless_events_handler);

	/* Initializes the PSoC 6 BLESS Controller. */
	result = Cy_BLE_InitController(&psoc6_bless_config);
	if (result != CY_BLE_SUCCESS) {
		LOG_ERR("Failed to init the BLE Controller");
		return -EIO;
	}

	/* Enables the BLESS controller in HCI only mode. */
	result = Cy_BLE_EnableHCIModeController();
	if (result != CY_BLE_SUCCESS) {
		LOG_ERR("Failed to enable the BLE Controller in hci mode");
		return -EIO;
	}

	/* Enables BLE Low-power mode (LPM)*/
	Cy_BLE_EnableLowPowerMode();

	/* Register a BLESS HCI driver to the Bluetooth stack. */
	bt_hci_driver_register(&drv);

	return 0;
}

SYS_INIT(psoc6_bless_hci_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
