/*
 * Copyright (c) 2024 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/** @file
 * @brief Coexistence functions
 */

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

#include "coex.h"
#include "coex_struct.h"
#include "fmac_main.h"
#include "fmac_api.h"

LOG_MODULE_DECLARE(wifi_nrf, CONFIG_WIFI_NRF70_LOG_LEVEL);

extern struct nrf_wifi_drv_priv_zep rpu_drv_priv_zep;
static struct nrf_wifi_ctx_zep *rpu_ctx = &rpu_drv_priv_zep.rpu_ctx_zep;

#define CH_BASE_ADDRESS ABS_EXT_SYS_WLANSYSCOEX_CH_CONTROL
#define COEX_CONFIG_FIXED_PARAMS 4
#define COEX_REG_CFG_OFFSET_SHIFT 24

/* copied from uccp530_77_registers_ext_sys_bus.h of UCCP toolkit */
#define EXT_SYS_WLANSYSCOEX_CH_CONTROL 0x0000
#define ABS_EXT_SYS_WLANSYSCOEX_CH_CONTROL 0xA401BC00UL
#define EXT_SYS_WLANSYSCOEX_CH_TIME_REFERENCE 0x0004
#define EXT_SYS_WLANSYSCOEX_CH_SR_INFO_STATUS 0x0040
#define EXT_SYS_WLANSYSCOEX_CH_NO_WINDOW_LOOKUP_0 0x008C
#define EXT_SYS_WLANSYSCOEX_CH_NO_WINDOW_LOOKUP_44 0x013C
#define EXT_SYS_WLANSYSCOEX_RESET_SHIFT 0

/* copied from uccp530_77_registers.h of UCCP toolkit */
#define ABS_PMB_WLAN_MAC_CTRL_PULSED_SOFTWARE_RESET 0xA5009A00UL

#ifdef CONFIG_NRF70_SR_COEX_RF_SWITCH
	#define NRF_RADIO_COEX_NODE DT_NODELABEL(nrf70)
	static const struct gpio_dt_spec sr_rf_switch_spec =
	GPIO_DT_SPEC_GET(NRF_RADIO_COEX_NODE, srrf_switch_gpios);
#endif /* CONFIG_NRF70_SR_COEX_RF_SWITCH */

/* PTA registers configuration of Coexistence Hardware */
/* Separate antenna configuration, WLAN in 2.4GHz. For BLE protocol. */
const uint16_t config_buffer_SEA_ble[] = {
	0x0019, 0x00F6, 0x0008, 0x0062, 0x00F5,
	0x00F5, 0x0019, 0x0019, 0x0074, 0x0074,
	0x0008, 0x01E2, 0x00D5, 0x00D5, 0x01F6,
	0x01F6, 0x0061, 0x0061, 0x01E2, 0x0008,
	0x0004, 0x0004, 0x0019, 0x0019, 0x0008,
	0x0008, 0x00F5, 0x00F5, 0x00D5, 0x00D5,
	0x0008, 0x01E2, 0x0051, 0x0051, 0x0074,
	0x0074, 0x00F6, 0x0019, 0x0062, 0x0019,
	0x00F6, 0x0008, 0x0062, 0x0008, 0x001A
};

/* Separate antenna configuration, WLAN in 2.4GHz. For non BLE protocol */
const uint16_t config_buffer_SEA_non_ble[] = {
	0x0019, 0x00F6, 0x0008, 0x0062, 0x00F5,
	0x00F5, 0x0061, 0x0061, 0x0074, 0x0074,
	0x01E2, 0x01E2, 0x00D5, 0x00D5, 0x01F6,
	0x01F6, 0x0061, 0x0061, 0x01E2, 0x01E2,
	0x00C4, 0x00C4, 0x0061, 0x0061, 0x0008,
	0x0008, 0x00F5, 0x00F5, 0x00D5, 0x00D5,
	0x0162, 0x0162, 0x0019, 0x0019, 0x01F6,
	0x01F6, 0x00F6, 0x0019, 0x0062, 0x0019,
	0x00F6, 0x0008, 0x0062, 0x0008, 0x001A
};

/* Shared antenna configuration, WLAN in 2.4GHz. */
const uint16_t config_buffer_SHA[] = {
	0x0019, 0x00F6, 0x0008, 0x00E2, 0x0015,
	0x00F5, 0x0019, 0x0019, 0x0004, 0x01F6,
	0x0008, 0x01E2, 0x00F5, 0x00F5, 0x01F6,
	0x01F6, 0x00E1, 0x00E1, 0x01E2, 0x0008,
	0x0004, 0x0004, 0x0019, 0x0019, 0x0008,
	0x0008, 0x0015, 0x00F5, 0x00F5, 0x00F5,
	0x0008, 0x01E2, 0x00E1, 0x00E1, 0x0004,
	0x01F6, 0x00F6, 0x0019, 0x00E2, 0x0019,
	0x00F6, 0x0008, 0x00E2, 0x0008, 0x001A
};

/* Shared/separate antennas, WLAN in 5GHz */
const uint16_t config_buffer_5G[] = {
	0x0039, 0x0076, 0x0028, 0x0062, 0x0075,
	0x0075, 0x0061, 0x0061, 0x0074, 0x0074,
	0x0060, 0x0060, 0x0075, 0x0075, 0x0064,
	0x0064, 0x0071, 0x0071, 0x0060, 0x0060,
	0x0064, 0x0064, 0x0061, 0x0061, 0x0060,
	0x0060, 0x0075, 0x0075, 0x0075, 0x0075,
	0x0060, 0x0060, 0x0071, 0x0071, 0x0074,
	0x0074, 0x0076, 0x0039, 0x0062, 0x0039,
	0x0076, 0x0028, 0x0062, 0x0028, 0x003A
};

/* non-PTA register configuration of coexistence hardware */
/* Shared antenna */
const uint32_t ch_config_sha[] = {
	0x00000028, 0x00000000, 0x001e1023, 0x00000000, 0x00000000,
	0x00000000, 0x00000021, 0x000002ca, 0x00000050, 0x00000000,
	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
	0x00000000
};

/* Separate antennas. For BLE protocol. */
const uint32_t ch_config_sep_ble[] = {
	0x00000028, 0x00000000, 0x001e1023, 0x00000000, 0x00000000,
	0x00000000, 0x00000021, 0x000002ca, 0x00000055, 0x00000000,
	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
	0x00000000
};

/* Separate antennas. For non BLE protocol. */
const uint32_t ch_config_sep_non_ble[] = {
	0x00000028, 0x00000000, 0x001e1023, 0x00000000, 0x00000000,
	0x00000000, 0x00000021, 0x000002ca, 0x00000055, 0x00000000,
	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
	0x00000000
};

int nrf_wifi_coex_config_non_pta(bool separate_antennas, bool is_sr_protocol_ble)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	struct coex_ch_configuration params  = { 0 };
	const uint32_t *config_buffer_ptr = NULL;
	uint32_t start_offset = 0;
	uint32_t num_reg_to_config = 1;
	uint32_t cmd_len, index;

	/* Offset from the base address of CH */
	start_offset = ((EXT_SYS_WLANSYSCOEX_CH_TIME_REFERENCE -
	EXT_SYS_WLANSYSCOEX_CH_CONTROL) >> 2);
	/* Number of registers to be configured */
	num_reg_to_config = ((EXT_SYS_WLANSYSCOEX_CH_SR_INFO_STATUS -
	EXT_SYS_WLANSYSCOEX_CH_TIME_REFERENCE) >> 2) + 1;

	if (separate_antennas) {
		if (is_sr_protocol_ble) {
			config_buffer_ptr = ch_config_sep_ble;
		} else {
			config_buffer_ptr = ch_config_sep_non_ble;
		}
	} else {
		config_buffer_ptr = ch_config_sha;
	}

	params.message_id = HW_CONFIGURATION;
	params.num_reg_to_config = num_reg_to_config;
	params.hw_to_config = COEX_HARDWARE;
	params.hw_block_base_addr = CH_BASE_ADDRESS;

	for (index = 0; index < num_reg_to_config; index++) {
		params.configbuf[index] = (start_offset << COEX_REG_CFG_OFFSET_SHIFT) |
			(*(config_buffer_ptr + index));
		start_offset++;
	}

	cmd_len = (COEX_CONFIG_FIXED_PARAMS + num_reg_to_config) * sizeof(uint32_t);
	status = nrf_wifi_fmac_conf_srcoex(rpu_ctx->rpu_ctx,
					   (void *)(&params), cmd_len);
	if (status != NRF_WIFI_STATUS_SUCCESS) {
		return -1;
	}

	return 0;
}

int nrf_wifi_coex_config_pta(enum nrf_wifi_pta_wlan_op_band wlan_band, bool separate_antennas,
	bool is_sr_protocol_ble)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	struct coex_ch_configuration params  = { 0 };
	const uint16_t *config_buffer_ptr = NULL;
	uint32_t start_offset = 0;
	uint32_t num_reg_to_config = 1;
	uint32_t cmd_len, index;

	/* common for both SHA and SEA */
	/* Indicates offset from the base address of CH */
	start_offset = ((EXT_SYS_WLANSYSCOEX_CH_NO_WINDOW_LOOKUP_0 -
	EXT_SYS_WLANSYSCOEX_CH_CONTROL) >> 2);
	/* Number of contiguous registers to be configured starting from base+offset */
	num_reg_to_config = ((EXT_SYS_WLANSYSCOEX_CH_NO_WINDOW_LOOKUP_44 -
	EXT_SYS_WLANSYSCOEX_CH_NO_WINDOW_LOOKUP_0) >> 2) + 1;

	if (wlan_band == NRF_WIFI_PTA_WLAN_OP_BAND_2_4_GHZ) {
		/* WLAN operating in 2.4GHz */
		if (separate_antennas) {
			/* separate antennas configuration */
			if (is_sr_protocol_ble) {
				config_buffer_ptr = config_buffer_SEA_ble;
			} else {
				config_buffer_ptr = config_buffer_SEA_non_ble;
			}
		} else {
			/* Shared antenna configuration */
			config_buffer_ptr = config_buffer_SHA;
		}
	} else if (wlan_band == NRF_WIFI_PTA_WLAN_OP_BAND_5_GHZ) {
		/* WLAN operating in 5GHz */
		config_buffer_ptr = config_buffer_5G;
	} else {
		return -EINVAL;
	}

	params.message_id = HW_CONFIGURATION;
	params.num_reg_to_config = num_reg_to_config;
	params.hw_to_config = COEX_HARDWARE;
	params.hw_block_base_addr = CH_BASE_ADDRESS;

	for (index = 0; index < num_reg_to_config; index++) {
		params.configbuf[index] = (start_offset << COEX_REG_CFG_OFFSET_SHIFT) |
			(*(config_buffer_ptr+index));
		start_offset++;
	}

	cmd_len = (COEX_CONFIG_FIXED_PARAMS + num_reg_to_config) * sizeof(uint32_t);
	status = nrf_wifi_fmac_conf_srcoex(rpu_ctx->rpu_ctx,
					   (void *)(&params), cmd_len);
	if (status != NRF_WIFI_STATUS_SUCCESS) {
		return -1;
	}

	return 0;
}
#ifdef CONFIG_NRF70_SR_COEX_RF_SWITCH
int nrf_wifi_config_sr_switch(bool separate_antennas)
{
	int ret;

	if (!device_is_ready(sr_rf_switch_spec.port)) {
		LOG_ERR("Unable to open GPIO device");
		return -ENODEV;
	}

	ret = gpio_pin_configure_dt(&sr_rf_switch_spec, GPIO_OUTPUT);
	if (ret < 0) {
		LOG_ERR("Unable to configure GPIO device");
		return -1;
	}

	if (separate_antennas) {
		gpio_pin_set_dt(&sr_rf_switch_spec, 0x0);
		LOG_INF("GPIO P1.10 set to 0");
	} else {
		gpio_pin_set_dt(&sr_rf_switch_spec, 0x1);
		LOG_INF("GPIO P1.10 set to 1");
	}

	return 0;
}
#endif /* CONFIG_NRF70_SR_COEX_RF_SWITCH */

int nrf_wifi_coex_hw_reset(void)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	struct coex_ch_configuration params  = { 0 };
	uint32_t num_reg_to_config = 1;
	uint32_t start_offset = 0;
	uint32_t index = 0;
	uint32_t coex_hw_reset = 1;
	uint32_t cmd_len;

	/* reset CH */
	params.message_id = HW_CONFIGURATION;
	params.num_reg_to_config = num_reg_to_config;
	params.hw_to_config = COEX_HARDWARE;
	params.hw_block_base_addr = CH_BASE_ADDRESS;

	start_offset = ((EXT_SYS_WLANSYSCOEX_CH_CONTROL - EXT_SYS_WLANSYSCOEX_CH_CONTROL) >> 2);
	params.configbuf[index] = (start_offset << COEX_REG_CFG_OFFSET_SHIFT) |
	(coex_hw_reset << EXT_SYS_WLANSYSCOEX_RESET_SHIFT);

	cmd_len = (COEX_CONFIG_FIXED_PARAMS + num_reg_to_config) * sizeof(uint32_t);

	status = nrf_wifi_fmac_conf_srcoex(rpu_ctx->rpu_ctx,
				(void *)(&params), cmd_len);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		LOG_ERR("CH reset configuration failed");
		return -1;
	}

	return 0;
}

#ifdef CONFIG_NRF70_SR_COEX_RF_SWITCH
int sr_gpio_config(void)
{
	int ret;

	if (!device_is_ready(sr_rf_switch_spec.port)) {
		return -ENODEV;
	}

	ret = gpio_pin_configure_dt(&sr_rf_switch_spec, GPIO_OUTPUT);
	if (ret) {
		LOG_ERR("SR GPIO configuration failed %d", ret);
	}

	return ret;
}

int sr_gpio_remove(void)
{
	int ret;

	ret = gpio_pin_configure_dt(&sr_rf_switch_spec, GPIO_DISCONNECTED);
	if (ret) {
		LOG_ERR("SR GPIO remove failed %d", ret);
	}

	return ret;
}

int sr_ant_switch(unsigned int ant_switch)
{
	int ret;

	ret = gpio_pin_set_dt(&sr_rf_switch_spec, ant_switch & 0x1);
	if (ret) {
		LOG_ERR("SR GPIO set failed %d", ret);
	}

	return ret;
}
#endif /* CONFIG_NRF70_SR_COEX_RF_SWITCH */
