/*
 * Copyright (c) 2022 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

/**
 * @file
 *   This file implements a generic bluetooth external radio 3w coexistence
 *   interface.
 *
 */

#include <stddef.h>
#include <stdint.h>

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/devicetree.h>
#include <nrfx.h>
#include <nrfx_ppi.h>
#include <nrfx_gpiote.h>

#include <mpsl_cx_abstract_interface.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MPSL, 4);

#if DT_NODE_HAS_STATUS(DT_PHANDLE(DT_NODELABEL(radio), coex), okay)
#define COEX_NODE DT_PHANDLE(DT_NODELABEL(radio), coex)
#else
#define COEX_NODE DT_INVALID_NODE
#error No enabled coex nodes registered in DTS.
#endif

#if !(DT_NODE_HAS_COMPAT(COEX_NODE, generic_radio_coex_three_wire))
#error Selected coex node is not compatible with generic-radio-coex-three-wire.
#endif

#if !IS_ENABLED(CONFIG_SOC_SERIES_NRF52X)
#error Bluetooth 3-wire coex is only supported on the nRF52 series
#endif

#define MPSL_COEX_BT_GPIO_POLARITY_GET(dt_property)                                                \
	((GPIO_ACTIVE_LOW & DT_GPIO_FLAGS(COEX_NODE, dt_property)) ? false : true)

/**
 * @brief Expected time between request and grant signal in microseconds.
 * @note 0 means that grant signal is always meaningful.
 * @see req_grant_delay_get
 */
#define REQUEST_LEAD_TIME 0U

// get GPIOs from device tree
static const struct gpio_dt_spec req_spec = GPIO_DT_SPEC_GET(COEX_NODE, req_gpios);
static const struct gpio_dt_spec pri_spec = GPIO_DT_SPEC_GET(COEX_NODE, pri_dir_gpios);
static const struct gpio_dt_spec gra_spec = GPIO_DT_SPEC_GET(COEX_NODE, grant_gpios);

// pin number for NRFX DRIVER API
#define GRANT_PIN_PORT_NO  DT_PROP(DT_GPIO_CTLR(COEX_NODE, grant_gpios), port)
#define GRANT_PIN_PIN_NO   DT_GPIO_PIN(COEX_NODE, grant_gpios)
static uint32_t grant_abs_pin = NRF_GPIO_PIN_MAP(GRANT_PIN_PORT_NO, GRANT_PIN_PIN_NO);

// callback from BLE stack
// When PTA changes the granted radio operations list, call this function to inform the BLE stack.
static mpsl_cx_cb_t pta_changed_callback;

/**
 * @brief The BLE stack will call this function before it needs to transmit or receive.
 * 
 *  The function will set the request and priority pins based on the request parameters.
 * 
 * @see mpsl_cx_interface_t
 */
static int32_t request(const mpsl_cx_request_t *req_params)
{
	int ret;

	if (req_params == NULL) {
		return -NRF_EINVAL;
	}

    // simlpy assume priority is high if prio > 127
	bool pri_active = req_params->prio > (UINT8_MAX / 2);

    // control the priority pin
	ret = gpio_pin_set_dt(&pri_spec, pri_active);
	if (ret < 0) {
		return -NRF_EPERM;
	}

    // set request when RX and TX
	bool req_active = req_params->ops & (MPSL_CX_OP_RX | MPSL_CX_OP_TX);
    
    // control the request pin
	ret = gpio_pin_set_dt(&req_spec, req_active);
	if (ret < 0) {
		return -NRF_EPERM;
	}

	return 0;
}

/**
 * @brief The BLE stack will call this function after it no longer needs to transmit or receive.
 * 
 * set the request and priority pins to INACTIVE.
 */
static int32_t release(void)
{
	int ret;

	ret = gpio_pin_set_dt(&req_spec, 0);
	if (ret < 0) {
		return -NRF_EPERM;
	}

	ret = gpio_pin_set_dt(&pri_spec, 0);
	if (ret < 0) {
		return -NRF_EPERM;
	}

	return 0;
}

/**
 * @brief The BLE stack will call this function to get list of radio operations currently granted by PTA
 * @note PTA may change granted radio operations list at any time. It is possible that value
 *       returned by this function is outdated before the function ends its execution.
 */
static int32_t granted_ops_get(mpsl_cx_op_map_t *granted_ops)
{
	bool grant_is_asserted;
    mpsl_cx_op_map_t operations = 
        MPSL_CX_OP_IDLE_LISTEN | MPSL_CX_OP_RX; // assume we can always receive

	int ret = gpio_pin_get_dt(&gra_spec);

	if (ret < 0) {
		return ret;
	}
    grant_is_asserted = (bool)ret;

    // if grant is asserted, we can transmit
    if(grant_is_asserted) {
        operations |= MPSL_CX_OP_TX;
        LOG_DBG("Grant is asserted");
    } else {
        LOG_DBG("Grant is not asserted");
    }

	*granted_ops = operations;
	return 0;
}

/**
 * @brief Get time PTA needs to respond with grant signal to request signal.
 *
 * There are three ways to manage the grant signal by PTA:
 * 1. Keep grant signal in denied state by default and provide grant only after request.
 *    The maximum delay between request and grant signal is known.
 * 2. Keep grant signal in approved state by default and deny only if given request cannot be
 *    accepted. Radio is not allowed to start any operation for known time after the request signal
 *    is set. After this delay the grant signal can indicate denial anytime to prevent or to abort
 *    radio operation.
 * 3. Manage the granted signal constantly, even if there is no request from given radio.
 *
 * This function is to be used to verify how long the user of this interface should wait from
 * calling @ref mpsl_cx_request (return from function) to @ref mpsl_cx_granted_ops_get (call
 * to function) to prevent getting false results while PTA is responding to the last request.
 *
 * @retval >0 Minimal delay from setting the request signal to the time grant signal gets
 *            meaningful value. In microseconds, rounded up
 * @retval 0  Grant signal has always meaningful value. Can be checked before, during, or after
 *            setting the request signal.
 */
static uint32_t req_grant_delay_get(void)
{
	return REQUEST_LEAD_TIME;
}

static int32_t register_callback(mpsl_cx_cb_t cb)
{
	pta_changed_callback = cb;

	if (cb != NULL) {
        LOG_DBG("Enable grant pin interrupt");
		nrfx_gpiote_trigger_enable(grant_abs_pin, true);
	} else {
        LOG_DBG("Disable grant pin interrupt");
		nrfx_gpiote_trigger_disable(grant_abs_pin);
	}

	return 0;
}

static const mpsl_cx_interface_t m_mpsl_cx_methods = {
	.p_request             = request,
	.p_release             = release,
	.p_granted_ops_get     = granted_ops_get,
	.p_req_grant_delay_get = req_grant_delay_get,
	.p_register_callback   = register_callback,
};

/**
 * @brief GPIO event callback for GRANT pin
 */
static void gpiote_irq_handler(const struct device *gpiob, struct gpio_callback *cb, uint32_t pins)
{
	(void)gpiob;
	(void)cb;
	(void)pins;

	static mpsl_cx_op_map_t last_notified;
	int32_t ret;
	mpsl_cx_op_map_t granted_ops;
	mpsl_cx_cb_t callback_copy = pta_changed_callback ;

    LOG_DBG("Grant pin interrupt");

	if (callback_copy != NULL) {
		ret = granted_ops_get(&granted_ops);

		__ASSERT(ret == 0, "Getting grant pin state returned unexpected result: %d", ret);
		if (ret != 0) {
			/* nrfx gpio implementation cannot return failure for this call
			 * This condition is handled for fail-safe approach. It is assumed
			 * GRANT is not given, if cannot read its value
			 */
			granted_ops = MPSL_CX_OP_IDLE_LISTEN | MPSL_CX_OP_RX;
		}

		if (granted_ops != last_notified) {
			last_notified = granted_ops;
			callback_copy(granted_ops);
		}
	}
}


static int mpsl_cx_init(void)
{
    int32_t ret;
    pta_changed_callback = NULL;

    ret = mpsl_cx_interface_set(&m_mpsl_cx_methods);
	if (ret != 0) {
		return ret;
	}

    ret = gpio_pin_configure_dt(&req_spec, GPIO_OUTPUT_INACTIVE);
	if (ret != 0) {
		return ret;
	}

	ret = gpio_pin_configure_dt(&pri_spec, GPIO_OUTPUT_INACTIVE);
	if (ret != 0) {
		return ret;
	}

	ret = gpio_pin_configure_dt(&gra_spec, GPIO_INPUT | GPIO_PULL_UP);
	if (ret != 0) {
		return ret;
	}

    ret = gpio_pin_interrupt_configure_dt(&gra_spec,
		GPIO_INT_ENABLE | GPIO_INT_EDGE | GPIO_INT_EDGE_BOTH);
	if (ret != 0) {
		return ret;
	}
	grant_abs_pin = NRF_GPIO_PIN_MAP(GRANT_PIN_PORT_NO, GRANT_PIN_PIN_NO);
	nrfx_gpiote_trigger_disable(grant_abs_pin);

    static struct gpio_callback grant_cb;
	gpio_init_callback(&grant_cb, gpiote_irq_handler, BIT(gra_spec.pin));
	gpio_add_callback(gra_spec.port, &grant_cb);

	return 0;
}

SYS_INIT(mpsl_cx_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);