/*
 * Copyright (c) 2018-2019 PHYTEC Messtechnik GmbH
 * Copyright (c) 2023 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/*
 * This file is based on SW_DP.c from CMSIS-DAP Source (Revision:    V2.0.0)
 * https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/DAP/Firmware
 * Copyright (c) 2013-2017, ARM Limited, All Rights Reserved
 * SPDX-License-Identifier: Apache-2.0
 */


/* Serial Wire Debug Port interface bit-bang driver */

#define DT_DRV_COMPAT zephyr_swdp_gpio

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/swdp.h>

#include "swdp_ll_pin.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(swdp, CONFIG_DP_DRIVER_LOG_LEVEL);

#define MAX_SWJ_CLOCK(delay_cycles, port_write_cycles) \
	((CPU_CLOCK / 2U) / (port_write_cycles + delay_cycles))

/*
 * Default SWCLK frequency in Hz.
 * sw_clock can be used to overwrite this default value.
 */
#define SWDP_DEFAULT_SWCLK_FREQUENCY	1000000U

#define DELAY_FAST_CYCLES		2U
#define DELAY_SLOW_CYCLES		3U

struct sw_config {
	struct gpio_dt_spec clk;
	struct gpio_dt_spec dout;
	struct gpio_dt_spec dio;
	struct gpio_dt_spec dnoe;
	void *dout_reg;
	void *dio_reg;
	void *dnoe_reg;
	struct gpio_dt_spec noe;
	struct gpio_dt_spec reset;
	bool keep_reset_deast;
	uint32_t port_write_cycles;
	void *clk_reg;
};

struct sw_cfg_data {
	uint32_t clock_delay;
	uint8_t turnaround;
	bool data_phase;
	bool fast_clock;
};

/*
 * Move A[2:3], RnW, APnDP bits to their position,
 * add start bit, stop bit(6), park bit and parity bit.
 * For example, reading IDCODE would be APnDP=0, RnW=1, A2=0, A3=0.
 * The request would be 0xa5, which is 10100101 in binary.
 *
 * For more information, see:
 * - CMSIS-DAP Command Specification, DAP_Transfer
 * - ARM Debug Interface v5 Architecture Specification
 */
const static uint8_t sw_request_lut[16] = {
	0x81, 0xa3, 0xa5, 0x87, 0xa9, 0x8b, 0x8d, 0xaf,
	0xb1, 0x93, 0x95, 0xb7, 0x99, 0xbb, 0xbd, 0x9f
};

static ALWAYS_INLINE uint32_t sw_get32bit_parity(uint32_t data)
{
	data ^= data >> 16;
	data ^= data >> 8;
	data ^= data >> 4;
	data ^= data >> 2;
	data ^= data >> 1;

	return data & 1U;
}

/* Set SWCLK DAP hardware output pin to high level */
static ALWAYS_INLINE void pin_swclk_set(const struct device *dev)
{
	const struct sw_config *config = dev->config;

	if (FAST_BITBANG_HW_SUPPORT) {
		swdp_ll_pin_set(config->clk_reg, config->clk.pin);
	} else {
		gpio_pin_set_dt(&config->clk, 1);
	}
}

/* Set SWCLK DAP hardware output pin to low level */
static ALWAYS_INLINE void pin_swclk_clr(const struct device *dev)
{
	const struct sw_config *config = dev->config;

	if (FAST_BITBANG_HW_SUPPORT) {
		swdp_ll_pin_clr(config->clk_reg, config->clk.pin);
	} else {
		gpio_pin_set_dt(&config->clk, 0);
	}
}

/* Set the SWDIO DAP hardware output pin to high level */
static ALWAYS_INLINE void pin_swdio_set(const struct device *dev)
{
	const struct sw_config *config = dev->config;

	if (config->dout.port) {
		if (FAST_BITBANG_HW_SUPPORT) {
			swdp_ll_pin_set(config->dout_reg, config->dout.pin);
		} else {
			gpio_pin_set_dt(&config->dout, 1);
		}
	} else {
		if (FAST_BITBANG_HW_SUPPORT) {
			swdp_ll_pin_set(config->dio_reg, config->dio.pin);
		} else {
			gpio_pin_set_dt(&config->dio, 1);
		}
	}
}

/* Set the SWDIO DAP hardware output pin to low level */
static ALWAYS_INLINE void pin_swdio_clr(const struct device *dev)
{
	const struct sw_config *config = dev->config;

	if (config->dout.port) {
		if (FAST_BITBANG_HW_SUPPORT) {
			swdp_ll_pin_clr(config->dout_reg, config->dout.pin);
		} else {
			gpio_pin_set_dt(&config->dout, 0);
		}
	} else {
		if (FAST_BITBANG_HW_SUPPORT) {
			swdp_ll_pin_clr(config->dio_reg, config->dio.pin);
		} else {
			gpio_pin_set_dt(&config->dio, 0);
		}
	}
}

/* Set the SWDIO DAP hardware output pin to bit level */
static ALWAYS_INLINE void pin_swdio_out(const struct device *dev,
					const uint32_t bit)
{
	if (bit & 1U) {
		pin_swdio_set(dev);
	} else {
		pin_swdio_clr(dev);
	}
}

/* Return current level of the SWDIO DAP hardware input pin */
static ALWAYS_INLINE uint32_t pin_swdio_in(const struct device *dev)
{
	const struct sw_config *config = dev->config;

	if (FAST_BITBANG_HW_SUPPORT) {
		return swdp_ll_pin_get(config->dio_reg, config->dio.pin);
	} else {
		return gpio_pin_get_dt(&config->dio);
	}
}

/*
 * Configure the SWDIO DAP hardware to output mode.
 * This is default configuration for every transfer.
 */
static ALWAYS_INLINE void pin_swdio_out_enable(const struct device *dev)
{
	const struct sw_config *config = dev->config;

	if (config->dnoe.port) {
		if (FAST_BITBANG_HW_SUPPORT) {
			swdp_ll_pin_set(config->dnoe_reg, config->dnoe.pin);
		} else {
			gpio_pin_set_dt(&config->dnoe, 1);
		}
	} else {
		if (FAST_BITBANG_HW_SUPPORT) {
			swdp_ll_pin_output(config->dio_reg, config->dio.pin);
		} else {
			gpio_pin_configure_dt(&config->dio, GPIO_OUTPUT_ACTIVE);
		}
	}
}

/*
 * Configure the SWDIO DAP hardware to input mode.
 */
static ALWAYS_INLINE void pin_swdio_out_disable(const struct device *dev)
{
	const struct sw_config *config = dev->config;

	if (config->dnoe.port) {
		if (FAST_BITBANG_HW_SUPPORT) {
			swdp_ll_pin_clr(config->dnoe_reg, config->dnoe.pin);
		} else {
			gpio_pin_set_dt(&config->dnoe, 0);
		}
	} else {
		if (FAST_BITBANG_HW_SUPPORT) {
			swdp_ll_pin_input(config->dio_reg, config->dio.pin);
		} else {
			gpio_pin_configure_dt(&config->dio, GPIO_INPUT);
		}
	}
}

#define SW_CLOCK_CYCLE(dev, delay)			\
	do {						\
		pin_swclk_clr(dev);			\
		pin_delay_asm(delay);			\
		pin_swclk_set(dev);			\
		pin_delay_asm(delay);			\
	} while (0)

#define SW_WRITE_BIT(dev, bit, delay)			\
	do {						\
		pin_swdio_out(dev, bit);		\
		pin_swclk_clr(dev);			\
		pin_delay_asm(delay);			\
		pin_swclk_set(dev);			\
		pin_delay_asm(delay);			\
	} while (0)

#define SW_READ_BIT(dev, bit, delay)			\
	do {						\
		pin_swclk_clr(dev);			\
		pin_delay_asm(delay);			\
		bit = pin_swdio_in(dev);		\
		pin_swclk_set(dev);			\
		pin_delay_asm(delay);			\
	} while (0)

static int sw_output_sequence(const struct device *dev, uint32_t count,
			      const uint8_t *data)
{
	struct sw_cfg_data *sw_data = dev->data;
	unsigned int key;
	uint32_t val = 0; /* current byte */
	uint32_t n = 0; /* bit counter */

	LOG_DBG("writing %u bits", count);
	LOG_HEXDUMP_DBG(data, count, "sequence bit data");
	key = irq_lock();

	pin_swdio_out_enable(dev);
	while (count--) {
		if (n == 0U) {
			val = *data++;
			n = 8U;
		}
		if (val & 1U) {
			pin_swdio_set(dev);
		} else {
			pin_swdio_clr(dev);
		}
		SW_CLOCK_CYCLE(dev, sw_data->clock_delay);
		val >>= 1;
		n--;
	}

	irq_unlock(key);

	return 0;
}

static int sw_input_sequence(const struct device *dev, uint32_t count,
			     uint8_t *data)
{
	struct sw_cfg_data *sw_data = dev->data;
	unsigned int key;
	uint32_t val = 0U; /* current byte */
	uint32_t n = 8U; /* bit counter */
	uint32_t bit;

	LOG_DBG("reading %u bits", count);
	key = irq_lock();

	pin_swdio_out_disable(dev);
	while (count--) {
		if (n == 0U) {
			*data++ = val;
			val = 0;
			n = 8U;
		}
		SW_READ_BIT(dev, bit, sw_data->clock_delay);
		LOG_DBG("Read bit: %d", bit);
		val = (val << 1 | bit);
		n--;
	}

	*data = val; /* write last byte */
	irq_unlock(key);

	return 0;
}

static ALWAYS_INLINE void sw_cycle_turnaround(const struct device *dev)
{
	struct sw_cfg_data *sw_data = dev->data;
	uint32_t n;

	for (n = sw_data->turnaround; n; n--) {
		SW_CLOCK_CYCLE(dev, sw_data->clock_delay);
	}
}

static int sw_transfer(const struct device *dev,
		       const uint8_t request, uint32_t *const data,
		       const uint8_t idle_cycles, uint8_t *const response)
{
	struct sw_cfg_data *sw_data = dev->data;
	unsigned int key;
	uint32_t ack;
	uint32_t bit;
	uint32_t val;
	uint32_t parity = 0;
	uint32_t n;

	pin_swdio_out_enable(dev);

	LOG_DBG("request 0x%02x idle %u", request, idle_cycles);
	if (!(request & SWDP_REQUEST_RnW)) {
		LOG_DBG("write data 0x%08x", *data);
		parity = sw_get32bit_parity(*data);
	}

	key = irq_lock();

	val = sw_request_lut[request & 0xFU];
	for (n = 8U; n; n--) {
		SW_WRITE_BIT(dev, val, sw_data->clock_delay);
		val >>= 1;
	}

	pin_swdio_out_disable(dev);
	sw_cycle_turnaround(dev);

	/* Acknowledge response */
	SW_READ_BIT(dev, bit, sw_data->clock_delay);
	ack = bit << 0;
	SW_READ_BIT(dev, bit, sw_data->clock_delay);
	ack |= bit << 1;
	SW_READ_BIT(dev, bit, sw_data->clock_delay);
	ack |= bit << 2;

	if (ack == SWDP_ACK_OK) {
		/* Data transfer */
		if (request & SWDP_REQUEST_RnW) {
			/* Read data */
			val = 0U;
			for (n = 32U; n; n--) {
				/* Read RDATA[0:31] */
				SW_READ_BIT(dev, bit, sw_data->clock_delay);
				val >>= 1;
				val |= bit << 31;
			}

			/* Read parity bit */
			SW_READ_BIT(dev, bit, sw_data->clock_delay);
			sw_cycle_turnaround(dev);
			pin_swdio_out_enable(dev);

			if ((sw_get32bit_parity(val) ^ bit) & 1U) {
				ack = SWDP_TRANSFER_ERROR;
			}

			if (data) {
				*data = val;
			}

		} else {
			sw_cycle_turnaround(dev);

			pin_swdio_out_enable(dev);
			/* Write data */
			val = *data;
			for (n = 32U; n; n--) {
				SW_WRITE_BIT(dev, val, sw_data->clock_delay);
				val >>= 1;
			}

			/* Write parity bit */
			SW_WRITE_BIT(dev, parity, sw_data->clock_delay);
		}
		/* Idle cycles */
		n = idle_cycles;
		if (n) {
			pin_swdio_out(dev, 0U);
			for (; n; n--) {
				SW_CLOCK_CYCLE(dev, sw_data->clock_delay);
			}
		}

		pin_swdio_out(dev, 1U);
		irq_unlock(key);
		if (request & SWDP_REQUEST_RnW) {
			LOG_DBG("read data 0x%08x", *data);
		}

		if (response) {
			*response = (uint8_t)ack;
		}

		return 0;
	}

	if ((ack == SWDP_ACK_WAIT) || (ack == SWDP_ACK_FAULT)) {
		/* WAIT OR fault response */
		if (sw_data->data_phase) {
			for (n = 32U + 1U + sw_data->turnaround; n; n--) {
				/* Dummy Read RDATA[0:31] + Parity */
				SW_CLOCK_CYCLE(dev, sw_data->clock_delay);
			}
		} else {
			sw_cycle_turnaround(dev);
		}

		pin_swdio_out_enable(dev);
		pin_swdio_out(dev, 1U);
		irq_unlock(key);
		LOG_DBG("Transfer wait or fault");
		if (response) {
			*response = (uint8_t)ack;
		}

		return 0;
	}

	/* Protocol error */
	for (n = sw_data->turnaround + 32U + 1U; n; n--) {
		/* Back off data phase */
		SW_CLOCK_CYCLE(dev, sw_data->clock_delay);
	}

	pin_swdio_out_enable(dev);
	pin_swdio_out(dev, 1U);
	irq_unlock(key);
	LOG_INF("Protocol error");
	if (response) {
		*response = (uint8_t)ack;
	}

	return 0;
}

static int sw_set_pins(const struct device *dev,
		       const uint8_t pins, const uint8_t value)
{
	const struct sw_config *config = dev->config;

	LOG_DBG("pins 0x%02x value 0x%02x", pins, value);

	if (pins & BIT(SWDP_SWCLK_PIN)) {
		if (value & BIT(SWDP_SWCLK_PIN)) {
			gpio_pin_set_dt(&config->clk, 1);
		} else {
			gpio_pin_set_dt(&config->clk, 0);
		}
	}

	if (config->dout_reg != NULL) {
		if (pins & BIT(SWDP_SWDIO_PIN)) {
			if (value & BIT(SWDP_SWDIO_PIN)) {
				gpio_pin_set_dt(&config->dout, 1);
			} else {
				gpio_pin_set_dt(&config->dout, 0);
			}
		}
	} else {
		if (pins & BIT(SWDP_SWDIO_PIN)) {
			if (value & BIT(SWDP_SWDIO_PIN)) {
				gpio_pin_set_dt(&config->dio, 1);
			} else {
				gpio_pin_set_dt(&config->dio, 0);
			}
		}
	}

	if (config->reset.port) {
		if (pins & BIT(SWDP_nRESET_PIN)) {
			if (value & BIT(SWDP_nRESET_PIN)) {
				gpio_pin_set_dt(&config->reset, 1);
			} else {
				gpio_pin_set_dt(&config->reset, 0);
			}
		}
	}

	return 0;
}

static int sw_get_pins(const struct device *dev, uint8_t *const state)
{
	const struct sw_config *config = dev->config;
	uint32_t val;

	if (config->reset.port) {
		val = gpio_pin_get_dt(&config->reset);
		*state = val ? BIT(SWDP_nRESET_PIN) : 0;
	}

	val = gpio_pin_get_dt(&config->dio);
	*state |= val ? BIT(SWDP_SWDIO_PIN) : 0;

	val = gpio_pin_get_dt(&config->clk);
	*state |= val ? BIT(SWDP_SWCLK_PIN) : 0;

	LOG_DBG("pins state 0x%02x", *state);

	return 0;
}

static int sw_set_clock(const struct device *dev, const uint32_t clock)
{
	const struct sw_config *config = dev->config;
	struct sw_cfg_data *sw_data = dev->data;
	uint32_t delay;

	if (clock >= MAX_SWJ_CLOCK(DELAY_FAST_CYCLES, config->port_write_cycles)) {
		sw_data->fast_clock = true;
		delay = 1U;
	} else {
		sw_data->fast_clock = false;

		delay = ((CPU_CLOCK / 2U) + (clock - 1U)) / clock;
		if (delay > config->port_write_cycles) {
			delay -= config->port_write_cycles;
			delay = (delay + (DELAY_SLOW_CYCLES - 1U)) / DELAY_SLOW_CYCLES;
		} else {
			delay = 1U;
		}
	}

	sw_data->clock_delay = delay;

	LOG_WRN("cpu_clock %d, delay %d", CPU_CLOCK, sw_data->clock_delay);

	return 0;
}

static int sw_configure(const struct device *dev,
			const uint8_t turnaround, const bool data_phase)
{
	struct sw_cfg_data *sw_data = dev->data;

	sw_data->turnaround = turnaround;
	sw_data->data_phase = data_phase;

	LOG_INF("turnaround %d, data_phase %d",
		sw_data->turnaround, sw_data->data_phase);

	return 0;
}

static int sw_port_on(const struct device *dev)
{
	const struct sw_config *config = dev->config;
	int ret;

	if (config->dnoe.port) {
		ret = gpio_pin_configure_dt(&config->dnoe, GPIO_OUTPUT_ACTIVE);
		if (ret) {
			return ret;
		}
	}

	if (config->dout.port) {
		ret = gpio_pin_configure_dt(&config->dout, GPIO_OUTPUT_ACTIVE);
		if (ret) {
			return ret;
		}
	}

	ret = gpio_pin_configure_dt(&config->dio, GPIO_INPUT);
	if (ret) {
		return ret;
	}

	if (config->noe.port) {
		ret = gpio_pin_configure_dt(&config->noe, GPIO_OUTPUT_ACTIVE);
		if (ret) {
			return ret;
		}
	}

	ret = gpio_pin_configure_dt(&config->clk, GPIO_OUTPUT_ACTIVE);
	if (ret) {
		return ret;
	}

	if (config->reset.port) {
		ret = gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT_ACTIVE);
		if (ret) {
			return ret;
		}
	}

	return 0;
}

static int sw_port_off(const struct device *dev)
{
	const struct sw_config *config = dev->config;
	int ret;

	/* If there is a transceiver connected to IO, pins should always be driven. */
	if (config->dnoe.port) {
		ret = gpio_pin_configure_dt(&config->dnoe, GPIO_OUTPUT_INACTIVE);
		if (ret) {
			return ret;
		}

		if (config->dout.port) {
			ret = gpio_pin_configure_dt(&config->dout, GPIO_OUTPUT_ACTIVE);
			if (ret) {
				return ret;
			}
		}

		ret = gpio_pin_configure_dt(&config->dio, GPIO_INPUT);
		if (ret) {
			return ret;
		}
	} else {
		if (config->dout.port) {
			ret = gpio_pin_configure_dt(&config->dout, GPIO_DISCONNECTED);
			if (ret) {
				return ret;
			}
		}

		ret = gpio_pin_configure_dt(&config->dio, GPIO_DISCONNECTED);
		if (ret) {
			return ret;
		}
	}

	/* If there is a transceiver connected to CLK, pins should always be driven. */
	if (config->noe.port) {
		ret = gpio_pin_configure_dt(&config->noe, GPIO_OUTPUT_INACTIVE);
		if (ret) {
			return ret;
		}

		ret = gpio_pin_configure_dt(&config->clk, GPIO_OUTPUT_ACTIVE);
		if (ret) {
			return ret;
		}

	} else {
		ret = gpio_pin_configure_dt(&config->clk, GPIO_DISCONNECTED);
		if (ret) {
			return ret;
		}
	}

	if (config->reset.port) {
		ret = gpio_pin_configure_dt(&config->reset,
					    config->keep_reset_deast
						    ? GPIO_OUTPUT_ACTIVE
						    : GPIO_DISCONNECTED);
		if (ret) {
			return ret;
		}
	}

	return 0;
}

static int sw_gpio_init(const struct device *dev)
{
	struct sw_cfg_data *sw_data = dev->data;
	int ret;

	/* start with the port turned off */
	ret = sw_port_off(dev);
	if (ret) {
		return ret;
	}

	sw_data->turnaround = 1U;
	sw_data->data_phase = false;
	sw_data->fast_clock = false;
	sw_set_clock(dev, SWDP_DEFAULT_SWCLK_FREQUENCY);

	return 0;
}

static struct swdp_api swdp_bitbang_api = {
	.swdp_output_sequence = sw_output_sequence,
	.swdp_input_sequence = sw_input_sequence,
	.swdp_transfer	= sw_transfer,
	.swdp_set_pins	= sw_set_pins,
	.swdp_get_pins	= sw_get_pins,
	.swdp_set_clock	= sw_set_clock,
	.swdp_configure	= sw_configure,
	.swdp_port_on	= sw_port_on,
	.swdp_port_off	= sw_port_off,
};

#define SW_GPIOS_GET_REG(n, gpios)							\
	COND_CODE_1(DT_INST_NODE_HAS_PROP(n, gpios),					\
		    (INT_TO_POINTER(DT_REG_ADDR(DT_PHANDLE(DT_DRV_INST(n), gpios)))),	\
		    (NULL))

#define SW_DEVICE_DEFINE(n)								\
	BUILD_ASSERT((DT_INST_NODE_HAS_PROP(n, dout_gpios)) ==				\
		     (DT_INST_NODE_HAS_PROP(n, dnoe_gpios)),				\
		     "Either the dout-gpios or dnoe-gpios property is missing.");	\
											\
	static const struct sw_config sw_cfg_##n = {					\
		.clk = GPIO_DT_SPEC_INST_GET(n, clk_gpios),				\
		.clk_reg = SW_GPIOS_GET_REG(n, clk_gpios),				\
		.dio = GPIO_DT_SPEC_INST_GET(n, dio_gpios),				\
		.dio_reg = SW_GPIOS_GET_REG(n, dio_gpios),				\
		.dout = GPIO_DT_SPEC_INST_GET_OR(n, dout_gpios, {0}),			\
		.dout_reg = SW_GPIOS_GET_REG(n, dout_gpios),				\
		.dnoe = GPIO_DT_SPEC_INST_GET_OR(n, dnoe_gpios, {0}),			\
		.dnoe_reg = SW_GPIOS_GET_REG(n, dnoe_gpios),				\
		.noe = GPIO_DT_SPEC_INST_GET_OR(n, noe_gpios, {0}),			\
		.reset = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {0}),			\
		.keep_reset_deast = DT_INST_PROP(n, keep_reset_deasserted),             \
		.port_write_cycles = DT_INST_PROP(n, port_write_cycles),		\
	};										\
											\
	static struct sw_cfg_data sw_data_##n;						\
											\
	DEVICE_DT_INST_DEFINE(n, sw_gpio_init, NULL,					\
			      &sw_data_##n, &sw_cfg_##n,				\
			      POST_KERNEL, CONFIG_DP_DRIVER_INIT_PRIO,			\
			      &swdp_bitbang_api);

DT_INST_FOREACH_STATUS_OKAY(SW_DEVICE_DEFINE)
