/*
 * Copyright (c) 2023 Andriy Gelman
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT infineon_xmc4xxx_can_node

#include <zephyr/device.h>
#include <zephyr/drivers/can.h>
#include <zephyr/drivers/can/transceiver.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/bitarray.h>

#include <soc.h>
#include <xmc_can.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(can_xmc4xxx, CONFIG_CAN_LOG_LEVEL);

#define CAN_XMC4XXX_MULTICAN_NODE DT_INST(0, infineon_xmc4xxx_can)

#define CAN_XMC4XXX_NUM_MESSAGE_OBJECTS DT_PROP(CAN_XMC4XXX_MULTICAN_NODE, message_objects)
#define CAN_XMC4XXX_CLOCK_PRESCALER DT_PROP(CAN_XMC4XXX_MULTICAN_NODE, clock_prescaler)

static CAN_GLOBAL_TypeDef *const can_xmc4xxx_global_reg =
	(CAN_GLOBAL_TypeDef *)DT_REG_ADDR(CAN_XMC4XXX_MULTICAN_NODE);

static bool can_xmc4xxx_global_init;
static uint32_t can_xmc4xxx_clock_frequency;

SYS_BITARRAY_DEFINE_STATIC(mo_usage_bitarray, CAN_XMC4XXX_NUM_MESSAGE_OBJECTS);
static int can_xmc4xxx_num_free_mo = CAN_XMC4XXX_NUM_MESSAGE_OBJECTS;

#define CAN_XMC4XXX_IRQ_MIN 76
#define CAN_XMC4XXX_MAX_DLC 8

#define CAN_XMC4XXX_REG_TO_NODE_IND(reg) (((uint32_t)(reg) - (uint32_t)CAN_NODE0_BASE) / 0x100)

struct can_xmc4xxx_tx_callback {
	can_tx_callback_t function;
	void *user_data;
};

struct can_xmc4xxx_rx_callback {
	can_rx_callback_t function;
	void *user_data;
};

struct can_xmc4xxx_rx_fifo {
	CAN_MO_TypeDef *base;
	CAN_MO_TypeDef *top;
	CAN_MO_TypeDef *tail;
	CAN_MO_TypeDef *head;
};

struct can_xmc4xxx_data {
	struct can_driver_data common;

	enum can_state state;
	struct k_mutex mutex;

	struct k_sem tx_sem;
	struct can_xmc4xxx_tx_callback tx_callbacks[CONFIG_CAN_XMC4XXX_MAX_TX_QUEUE];

	uint32_t filter_usage;
	struct can_xmc4xxx_rx_callback rx_callbacks[CONFIG_CAN_XMC4XXX_MAX_FILTERS];
	struct can_xmc4xxx_rx_fifo rx_fifos[CONFIG_CAN_XMC4XXX_MAX_FILTERS];
#if defined(CONFIG_CAN_ACCEPT_RTR)
	struct can_xmc4xxx_rx_fifo rtr_fifos[CONFIG_CAN_XMC4XXX_MAX_FILTERS];
#endif

	CAN_MO_TypeDef *tx_mo[CONFIG_CAN_XMC4XXX_MAX_TX_QUEUE];
};

struct can_xmc4xxx_config {
	struct can_driver_config common;

	CAN_NODE_TypeDef *can;
	bool clock_div8;

	uint8_t service_request;
	void (*irq_config_func)(void);

	uint8_t input_src;
	const struct pinctrl_dev_config *pcfg;
};

static int can_xmc4xxx_set_mode(const struct device *dev, can_mode_t mode)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	const struct can_xmc4xxx_config *dev_cfg = dev->config;

	if (dev_data->common.started) {
		return -EBUSY;
	}

	if ((mode & (CAN_MODE_3_SAMPLES | CAN_MODE_ONE_SHOT |
		     CAN_MODE_LOOPBACK | CAN_MODE_FD)) != 0) {
		return -ENOTSUP;
	}

	if ((mode & CAN_MODE_LISTENONLY) != 0) {
		XMC_CAN_NODE_SetAnalyzerMode(dev_cfg->can);
	} else {
		XMC_CAN_NODE_ReSetAnalyzerMode(dev_cfg->can);
	}

	dev_data->common.mode = mode;

	return 0;
}

static int can_xmc4xxx_set_timing(const struct device *dev, const struct can_timing *timing)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	const struct can_xmc4xxx_config *dev_cfg = dev->config;
	uint32_t reg;

	if (!timing) {
		return -EINVAL;
	}

	if (dev_data->common.started) {
		return -EBUSY;
	}

	k_mutex_lock(&dev_data->mutex, K_FOREVER);

	reg = FIELD_PREP(CAN_NODE_NBTR_DIV8_Msk, dev_cfg->clock_div8);
	reg |= FIELD_PREP(CAN_NODE_NBTR_BRP_Msk, timing->prescaler - 1);
	reg |= FIELD_PREP(CAN_NODE_NBTR_TSEG1_Msk, timing->prop_seg + timing->phase_seg1 - 1);
	reg |= FIELD_PREP(CAN_NODE_NBTR_TSEG2_Msk, timing->phase_seg2 - 1);
	reg |= FIELD_PREP(CAN_NODE_NBTR_SJW_Msk, timing->sjw - 1);

	dev_cfg->can->NBTR = reg;

	k_mutex_unlock(&dev_data->mutex);

	return 0;
}

static int can_xmc4xxx_send(const struct device *dev, const struct can_frame *msg,
			    k_timeout_t timeout, can_tx_callback_t callback, void *callback_arg)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	uint8_t mailbox_idx;
	struct can_xmc4xxx_tx_callback *callbacks = &dev_data->tx_callbacks[0];
	CAN_MO_TypeDef *mo;
	unsigned int key;

	LOG_DBG("Sending %d bytes. Id: 0x%x, ID type: %s %s %s %s", can_dlc_to_bytes(msg->dlc),
		msg->id, msg->flags & CAN_FRAME_IDE ? "extended" : "standard",
		msg->flags & CAN_FRAME_RTR ? "RTR" : "",
		msg->flags & CAN_FRAME_FDF ? "FD frame" : "",
		msg->flags & CAN_FRAME_BRS ? "BRS" : "");

	if (msg->dlc > CAN_XMC4XXX_MAX_DLC) {
		return -EINVAL;
	}

	if (!dev_data->common.started) {
		return -ENETDOWN;
	}

	if (dev_data->state == CAN_STATE_BUS_OFF) {
		return -ENETUNREACH;
	}

	if ((msg->flags & (CAN_FRAME_FDF | CAN_FRAME_BRS)) != 0) {
		return -ENOTSUP;
	}

	if (k_sem_take(&dev_data->tx_sem, timeout) != 0) {
		return -EAGAIN;
	}

	k_mutex_lock(&dev_data->mutex, K_FOREVER);

	for (mailbox_idx = 0; mailbox_idx < CONFIG_CAN_XMC4XXX_MAX_TX_QUEUE; mailbox_idx++) {
		if (callbacks[mailbox_idx].function == NULL) {
			break;
		}
	}

	__ASSERT_NO_MSG(mailbox_idx < CONFIG_CAN_XMC4XXX_MAX_TX_QUEUE);

	key = irq_lock();
	/* critical section in case can_xmc4xxx_reset_tx_fifos() called in isr  */
	/* so that callback function and callback_arg are consistent */
	callbacks[mailbox_idx].function = callback;
	callbacks[mailbox_idx].user_data = callback_arg;
	irq_unlock(key);

	mo = dev_data->tx_mo[mailbox_idx];
	mo->MOCTR = CAN_MO_MOCTR_RESMSGVAL_Msk;

	if ((msg->flags & CAN_FRAME_IDE) != 0) {
		/* MOAR - message object arbitration register */
		mo->MOAR = FIELD_PREP(CAN_MO_MOAR_PRI_Msk, 1) |
			   FIELD_PREP(CAN_MO_MOAR_ID_Msk, msg->id) | CAN_MO_MOAR_IDE_Msk;
	} else {
		mo->MOAR = FIELD_PREP(CAN_MO_MOAR_PRI_Msk, 1) |
			   FIELD_PREP(XMC_CAN_MO_MOAR_STDID_Msk, msg->id);
	}

	mo->MOFCR &= ~CAN_MO_MOFCR_DLC_Msk;
	mo->MOFCR |= FIELD_PREP(CAN_MO_MOFCR_DLC_Msk, msg->dlc);

	if ((msg->flags & CAN_FRAME_RTR) != 0) {
		mo->MOCTR = CAN_MO_MOCTR_RESDIR_Msk;
	} else {
		mo->MOCTR = CAN_MO_MOCTR_SETDIR_Msk;
		memcpy((void *)&mo->MODATAL, &msg->data[0], sizeof(uint32_t));
		memcpy((void *)&mo->MODATAH, &msg->data[4], sizeof(uint32_t));
	}

	mo->MOCTR = CAN_MO_MOCTR_SETTXEN0_Msk | CAN_MO_MOCTR_SETTXEN1_Msk |
		    CAN_MO_MOCTR_SETMSGVAL_Msk | CAN_MO_MOCTR_RESRXEN_Msk |
		    CAN_MO_MOCTR_RESRTSEL_Msk;
	mo->MOCTR = CAN_MO_MOCTR_SETTXRQ_Msk;

	k_mutex_unlock(&dev_data->mutex);
	return 0;
}

static CAN_MO_TypeDef *can_xmc4xxx_get_mo(uint8_t *mo_index)
{
	int i;

	for (i = 0; i < CAN_XMC4XXX_NUM_MESSAGE_OBJECTS; i++) {
		int prev_val;

		sys_bitarray_test_and_set_bit(&mo_usage_bitarray, i, &prev_val);
		if (prev_val == 0) {
			*mo_index = i;
			can_xmc4xxx_num_free_mo--;
			return &CAN_MO->MO[i];
		}
	}

	return NULL;
}

static void can_xmc4xxx_deinit_fifo(const struct device *dev, struct can_xmc4xxx_rx_fifo *fifo)
{
	CAN_MO_TypeDef *mo = fifo->base;

	while (mo != NULL) {
		int next_index;
		int index;

		/* invalidate message */
		mo->MOCTR = CAN_MO_MOCTR_RESMSGVAL_Msk;

		next_index = FIELD_GET(CAN_MO_MOSTAT_PNEXT_Msk, mo->MOSTAT);
		index = ((uint32_t)mo - (uint32_t)&CAN_MO->MO[0]) / sizeof(*mo);

		if ((uint32_t)mo == (uint32_t)fifo->top) {
			mo = NULL;
		} else {
			mo = &CAN_MO->MO[next_index];
		}

		/* we need to move the node back to the list of unallocated message objects, */
		/* which is list index = 0. 255 gets rolled over to 0 in the function below */
		XMC_CAN_AllocateMOtoNodeList(can_xmc4xxx_global_reg, 255, index);

		sys_bitarray_clear_bit(&mo_usage_bitarray, index);
		can_xmc4xxx_num_free_mo++;
	}
}

static int can_xmc4xxx_init_fifo(const struct device *dev, const struct can_filter *filter,
				 struct can_xmc4xxx_rx_fifo *fifo, bool is_rtr)
{
	const struct can_xmc4xxx_config *dev_cfg = dev->config;
	CAN_MO_TypeDef *mo;
	uint32_t reg;
	uint8_t mo_index = 0, base_index;

	if (can_xmc4xxx_num_free_mo < CONFIG_CAN_XMC4XXX_RX_FIFO_ITEMS) {
		return -ENOMEM;
	}

	mo = can_xmc4xxx_get_mo(&mo_index);
	__ASSERT_NO_MSG(mo != NULL);

	base_index = mo_index;
	fifo->base = mo;
	fifo->tail = mo;

	XMC_CAN_AllocateMOtoNodeList(can_xmc4xxx_global_reg,
				     CAN_XMC4XXX_REG_TO_NODE_IND(dev_cfg->can), mo_index);

	/* setup the base object - this controls the filtering for the fifo */
	mo->MOCTR = CAN_MO_MOCTR_RESMSGVAL_Msk;
	mo->MOAMR &= ~(CAN_MO_MOAMR_AM_Msk | CAN_MO_MOAMR_MIDE_Msk);
	mo->MOAR = 0;

	if ((filter->flags & CAN_FILTER_IDE) != 0) {
		mo->MOAMR |= FIELD_PREP(CAN_MO_MOAMR_AM_Msk, filter->mask) | CAN_MO_MOAMR_MIDE_Msk;
		mo->MOAR |= FIELD_PREP(CAN_MO_MOAR_ID_Msk, filter->id) | CAN_MO_MOAR_IDE_Msk;
	} else {
		mo->MOAMR |= FIELD_PREP(XMC_CAN_MO_MOAR_STDID_Msk, filter->mask);
		mo->MOAR |= FIELD_PREP(XMC_CAN_MO_MOAR_STDID_Msk, filter->id);
	}

	mo->MOFCR = FIELD_PREP(CAN_MO_MOFCR_MMC_Msk, 1) | CAN_MO_MOFCR_RXIE_Msk;
	if (is_rtr) {
		mo->MOFCR |= CAN_MO_MOFCR_RMM_Msk;
		mo->MOCTR = CAN_MO_MOCTR_SETDIR_Msk;
	} else {
		mo->MOCTR = CAN_MO_MOCTR_RESDIR_Msk;
	}

	/* Writing to MOCTR sets or resets message object properties */
	mo->MOCTR = CAN_MO_MOCTR_RESTXEN0_Msk | CAN_MO_MOCTR_RESTXEN1_Msk |
		    CAN_MO_MOCTR_SETMSGVAL_Msk | CAN_MO_MOCTR_SETRXEN_Msk |
		    CAN_MO_MOCTR_RESRTSEL_Msk;

	mo->MOIPR = FIELD_PREP(CAN_MO_MOIPR_RXINP_Msk, dev_cfg->service_request);

	/* setup the remaining message objects in the fifo */
	for (int i = 1; i < CONFIG_CAN_XMC4XXX_RX_FIFO_ITEMS; i++) {
		mo = can_xmc4xxx_get_mo(&mo_index);
		__ASSERT_NO_MSG(mo != NULL);

		XMC_CAN_AllocateMOtoNodeList(can_xmc4xxx_global_reg,
					     CAN_XMC4XXX_REG_TO_NODE_IND(dev_cfg->can), mo_index);

		mo->MOCTR = CAN_MO_MOCTR_RESMSGVAL_Msk;
		mo->MOCTR = CAN_MO_MOCTR_SETMSGVAL_Msk | CAN_MO_MOCTR_RESRXEN_Msk;

		/* all the other message objects in the fifo must point to the base object */
		mo->MOFGPR = FIELD_PREP(CAN_MO_MOFGPR_CUR_Msk, base_index);
	}

	reg = 0;
	reg |= FIELD_PREP(CAN_MO_MOFGPR_CUR_Msk, base_index);
	reg |= FIELD_PREP(CAN_MO_MOFGPR_TOP_Msk, mo_index);
	reg |= FIELD_PREP(CAN_MO_MOFGPR_BOT_Msk, base_index);
	reg |= FIELD_PREP(CAN_MO_MOFGPR_SEL_Msk, base_index);

	fifo->base->MOFGPR = reg;
	fifo->top = mo;

	return 0;
}

static int can_xmc4xxx_add_rx_filter(const struct device *dev, can_rx_callback_t callback,
				     void *user_data, const struct can_filter *filter)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	int filter_idx;

	if ((filter->flags & ~CAN_FILTER_IDE) != 0) {
		LOG_ERR("Unsupported CAN filter flags 0x%02x", filter->flags);
		return -ENOTSUP;
	}

	k_mutex_lock(&dev_data->mutex, K_FOREVER);

	for (filter_idx = 0; filter_idx < CONFIG_CAN_XMC4XXX_MAX_FILTERS; filter_idx++) {
		if ((BIT(filter_idx) & dev_data->filter_usage) == 0) {
			break;
		}
	}

	if (filter_idx >= CONFIG_CAN_XMC4XXX_MAX_FILTERS) {
		filter_idx = -ENOSPC;
	} else {
		unsigned int key = irq_lock();
		int ret;

		ret = can_xmc4xxx_init_fifo(dev, filter, &dev_data->rx_fifos[filter_idx], false);
		if (ret < 0) {
			irq_unlock(key);
			k_mutex_unlock(&dev_data->mutex);
			return ret;
		}

#if defined(CONFIG_CAN_ACCEPT_RTR)
		ret = can_xmc4xxx_init_fifo(dev, filter, &dev_data->rtr_fifos[filter_idx], true);
		if (ret < 0) {
			can_xmc4xxx_deinit_fifo(dev, &dev_data->rx_fifos[filter_idx]);
			irq_unlock(key);
			k_mutex_unlock(&dev_data->mutex);
			return ret;
		}
#endif

		dev_data->filter_usage |= BIT(filter_idx);
		dev_data->rx_callbacks[filter_idx].function = callback;
		dev_data->rx_callbacks[filter_idx].user_data = user_data;

		irq_unlock(key);
	}

	k_mutex_unlock(&dev_data->mutex);

	return filter_idx;
}

static void can_xmc4xxx_remove_rx_filter(const struct device *dev, int filter_idx)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	unsigned int key;

	if (filter_idx < 0 || filter_idx >= CONFIG_CAN_XMC4XXX_MAX_FILTERS) {
		LOG_ERR("Filter ID %d out of bounds", filter_idx);
		return;
	}

	k_mutex_lock(&dev_data->mutex, K_FOREVER);

	if ((dev_data->filter_usage & BIT(filter_idx)) == 0) {
		k_mutex_unlock(&dev_data->mutex);
		return;
	}

	key = irq_lock();
	can_xmc4xxx_deinit_fifo(dev, &dev_data->rx_fifos[filter_idx]);
#if defined(CONFIG_CAN_ACCEPT_RTR)
	can_xmc4xxx_deinit_fifo(dev, &dev_data->rtr_fifos[filter_idx]);
#endif

	dev_data->filter_usage &= ~BIT(filter_idx);
	dev_data->rx_callbacks[filter_idx].function = NULL;
	dev_data->rx_callbacks[filter_idx].user_data = NULL;
	irq_unlock(key);

	k_mutex_unlock(&dev_data->mutex);
}

static void can_xmc4xxx_set_state_change_callback(const struct device *dev,
						  can_state_change_callback_t cb, void *user_data)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	unsigned int key;

	key = irq_lock();
	/* critical section so that state_change_cb and state_change_cb_data are consistent */
	dev_data->common.state_change_cb = cb;
	dev_data->common.state_change_cb_user_data = user_data;
	irq_unlock(key);
}

static void can_xmc4xxx_get_state_from_status(const struct device *dev, enum can_state *state,
					      struct can_bus_err_cnt *err_cnt, uint32_t *status)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	const struct can_xmc4xxx_config *dev_cfg = dev->config;
	uint8_t tec = XMC_CAN_NODE_GetTransmitErrorCounter(dev_cfg->can);
	uint8_t rec = XMC_CAN_NODE_GetTransmitErrorCounter(dev_cfg->can);

	if (err_cnt != NULL) {
		err_cnt->tx_err_cnt = tec;
		err_cnt->rx_err_cnt = rec;
	}

	if (state == NULL) {
		return;
	}

	if (!dev_data->common.started) {
		*state = CAN_STATE_STOPPED;
		return;
	}

	if ((*status & XMC_CAN_NODE_STATUS_BUS_OFF) != 0) {
		*state = CAN_STATE_BUS_OFF;
	} else if (tec >= 128 || rec >= 128) {
		*state = CAN_STATE_ERROR_PASSIVE;
	} else if ((*status & XMC_CAN_NODE_STATUS_ERROR_WARNING_STATUS) != 0) {
		*state = CAN_STATE_ERROR_WARNING;
	} else {
		*state = CAN_STATE_ERROR_ACTIVE;
	}
}

static int can_xmc4xxx_get_state(const struct device *dev, enum can_state *state,
				 struct can_bus_err_cnt *err_cnt)
{
	const struct can_xmc4xxx_config *dev_cfg = dev->config;
	uint32_t status;

	status = XMC_CAN_NODE_GetStatus(dev_cfg->can);

	can_xmc4xxx_get_state_from_status(dev, state, err_cnt, &status);

	return 0;
}

static int can_xmc4xxx_get_core_clock(const struct device *dev, uint32_t *rate)
{
	const struct can_xmc4xxx_config *dev_cfg = dev->config;

	*rate = can_xmc4xxx_clock_frequency;
	if (dev_cfg->clock_div8) {
		*rate /= 8;
	}

	return 0;
}

static int can_xmc4xxx_get_max_filters(const struct device *dev, bool ide)
{
	ARG_UNUSED(ide);

	return CONFIG_CAN_XMC4XXX_MAX_FILTERS;
}

static void can_xmc4xxx_reset_tx_fifos(const struct device *dev, int status)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	struct can_xmc4xxx_tx_callback *tx_callbacks = &dev_data->tx_callbacks[0];

	LOG_DBG("All Tx message objects reset");
	for (int i = 0; i < CONFIG_CAN_XMC4XXX_MAX_TX_QUEUE; i++) {
		can_tx_callback_t callback;
		void *user_data;

		callback = tx_callbacks[i].function;
		user_data = tx_callbacks[i].user_data;

		tx_callbacks[i].function = NULL;

		if (callback) {
			dev_data->tx_mo[i]->MOCTR = CAN_MO_MOCTR_RESMSGVAL_Msk;
			callback(dev, status, user_data);
			k_sem_give(&dev_data->tx_sem);
		}
	}
}

static void can_xmc4xxx_tx_handler(const struct device *dev)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	struct can_xmc4xxx_tx_callback *tx_callbacks = &dev_data->tx_callbacks[0];

	for (int i = 0; i < CONFIG_CAN_XMC4XXX_MAX_TX_QUEUE; i++) {
		CAN_MO_TypeDef *mo = dev_data->tx_mo[i];

		if ((mo->MOSTAT & XMC_CAN_MO_STATUS_TX_PENDING) != 0) {
			can_tx_callback_t callback;
			void *user_data;

			mo->MOCTR = XMC_CAN_MO_RESET_STATUS_TX_PENDING;

			callback = tx_callbacks[i].function;
			user_data = tx_callbacks[i].user_data;

			tx_callbacks[i].function = NULL;

			if (callback) {
				callback(dev, 0, user_data);
				k_sem_give(&dev_data->tx_sem);
			}
		}
	}
}

static inline void can_xmc4xxx_increment_fifo_tail(struct can_xmc4xxx_rx_fifo *fifo)
{
	uint8_t next_index;

	if ((uint32_t)fifo->tail == (uint32_t)fifo->top) {
		fifo->tail = fifo->base;
		return;
	}

	next_index = FIELD_GET(CAN_MO_MOSTAT_PNEXT_Msk, fifo->tail->MOSTAT);
	fifo->tail = &CAN_MO->MO[next_index];
}

static inline bool can_xmc4xxx_is_fifo_empty(struct can_xmc4xxx_rx_fifo *fifo)
{
	if (fifo->tail->MOSTAT & XMC_CAN_MO_STATUS_RX_PENDING) {
		return false;
	}

	return true;
}

static inline void can_xmc4xxx_update_fifo_head(struct can_xmc4xxx_rx_fifo *fifo)
{
	uint32_t reg = fifo->base->MOFGPR;
	uint8_t top_index, bot_index, cur_index;
	uint8_t head_index = FIELD_GET(CAN_MO_MOFGPR_CUR_Msk, reg);

	fifo->head = &CAN_MO->MO[head_index];
	top_index = FIELD_GET(CAN_MO_MOFGPR_TOP_Msk, reg);
	bot_index = FIELD_GET(CAN_MO_MOFGPR_BOT_Msk, reg);
	cur_index = FIELD_GET(CAN_MO_MOFGPR_CUR_Msk, reg);

	LOG_DBG("Fifo: top %d, bot %d, cur %d", top_index, bot_index, cur_index);
}

static void can_xmc4xxx_rx_fifo_handler(const struct device *dev, struct can_xmc4xxx_rx_fifo *fifo,
					struct can_xmc4xxx_rx_callback *rx_callback)
{
	bool is_rtr = (fifo->base->MOSTAT & CAN_MO_MOSTAT_DIR_Msk) != 0;

	while (!can_xmc4xxx_is_fifo_empty(fifo)) {
		struct can_frame frame;
		CAN_MO_TypeDef *mo_tail = fifo->tail;

		memset(&frame, 0, sizeof(frame));

		if ((mo_tail->MOAR & CAN_MO_MOAR_IDE_Msk) != 0) {
			frame.flags |= CAN_FRAME_IDE;
			frame.id = FIELD_GET(CAN_MO_MOAR_ID_Msk, mo_tail->MOAR);
		} else {
			frame.id = FIELD_GET(XMC_CAN_MO_MOAR_STDID_Msk, mo_tail->MOAR);
		}

		frame.dlc = FIELD_GET(CAN_MO_MOFCR_DLC_Msk, mo_tail->MOFCR);

		if (!is_rtr) {
			memcpy(&frame.data[0], (void *)&mo_tail->MODATAL, sizeof(uint32_t));
			memcpy(&frame.data[4], (void *)&mo_tail->MODATAH, sizeof(uint32_t));
		} else {
			frame.flags |= CAN_FRAME_RTR;
			memset(&frame.data[0], 0, CAN_MAX_DLEN);
		}

		if (rx_callback->function != NULL) {
			rx_callback->function(dev, &frame, rx_callback->user_data);
		}

		/* reset the rx pending bit on the tail */
		mo_tail->MOCTR = XMC_CAN_MO_RESET_STATUS_RX_PENDING;
		can_xmc4xxx_increment_fifo_tail(fifo);
	}
}

static void can_xmc4xxx_rx_handler(const struct device *dev)
{
	struct can_xmc4xxx_data *dev_data = dev->data;

	for (int i = 0; i < CONFIG_CAN_XMC4XXX_MAX_FILTERS; i++) {
		if ((BIT(i) & dev_data->filter_usage) == 0) {
			continue;
		}

		can_xmc4xxx_update_fifo_head(&dev_data->rx_fifos[i]);
		can_xmc4xxx_rx_fifo_handler(dev, &dev_data->rx_fifos[i],
					    &dev_data->rx_callbacks[i]);
#if defined(CONFIG_CAN_ACCEPT_RTR)
		can_xmc4xxx_update_fifo_head(&dev_data->rtr_fifos[i]);
		can_xmc4xxx_rx_fifo_handler(dev, &dev_data->rtr_fifos[i],
					    &dev_data->rx_callbacks[i]);
#endif
	}
}

static void can_xmc4xxx_state_change_handler(const struct device *dev, uint32_t status)
{
	const struct can_xmc4xxx_config *dev_cfg = dev->config;
	struct can_xmc4xxx_data *dev_data = dev->data;
	enum can_state new_state;
	struct can_bus_err_cnt err_cnt;

	can_xmc4xxx_get_state_from_status(dev, &new_state, &err_cnt, &status);
	if (dev_data->state != new_state) {
		if (dev_data->common.state_change_cb) {
			dev_data->common.state_change_cb(
				dev, new_state, err_cnt,
				dev_data->common.state_change_cb_user_data);
		}

		if (dev_data->state != CAN_STATE_STOPPED && new_state == CAN_STATE_BUS_OFF) {
			/* re-enable the node after auto bus-off recovery completes */
			XMC_CAN_NODE_ResetInitBit(dev_cfg->can);
		}

		dev_data->state = new_state;

		if (dev_data->state == CAN_STATE_BUS_OFF) {
			can_xmc4xxx_reset_tx_fifos(dev, -ENETDOWN);
		}
	}
}

static void can_xmc4xxx_isr(const struct device *dev)
{
	const struct can_xmc4xxx_config *dev_cfg = dev->config;
	uint32_t status;

	status = XMC_CAN_NODE_GetStatus(dev_cfg->can);
	XMC_CAN_NODE_ClearStatus(dev_cfg->can, status);

	if ((status & XMC_CAN_NODE_STATUS_TX_OK) != 0) {
		can_xmc4xxx_tx_handler(dev);
	}

	if ((status & XMC_CAN_NODE_STATUS_RX_OK) != 0) {
		can_xmc4xxx_rx_handler(dev);
	}

	if ((status & XMC_CAN_NODE_STATUS_ALERT_WARNING) != 0) {
		/* change of bit NSRx.BOFF */
		/* change of bit NSRx.EWRN */
		can_xmc4xxx_state_change_handler(dev, status);
	}
}

static int can_xmc4xxx_get_capabilities(const struct device *dev, can_mode_t *cap)
{
	ARG_UNUSED(dev);

	*cap = CAN_MODE_NORMAL | CAN_MODE_LISTENONLY;

	return 0;
}

static int can_xmc4xxx_start(const struct device *dev)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	const struct can_xmc4xxx_config *dev_cfg = dev->config;
	int ret = 0;
	unsigned int key;

	if (dev_data->common.started) {
		return -EALREADY;
	}

	key = irq_lock();
	can_xmc4xxx_reset_tx_fifos(dev, -ENETDOWN);
	irq_unlock(key);

	if (dev_cfg->common.phy != NULL) {
		ret = can_transceiver_enable(dev_cfg->common.phy, dev_data->common.mode);
		if (ret < 0) {
			LOG_ERR("Failed to enable CAN transceiver [%d]", ret);
			return ret;
		}
	}

	k_mutex_lock(&dev_data->mutex, K_FOREVER);

	XMC_CAN_NODE_DisableConfigurationChange(dev_cfg->can);

	dev_data->common.started = true;
	XMC_CAN_NODE_ResetInitBit(dev_cfg->can);

	k_mutex_unlock(&dev_data->mutex);

	return ret;
}

static int can_xmc4xxx_stop(const struct device *dev)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	const struct can_xmc4xxx_config *dev_cfg = dev->config;
	int ret = 0;
	unsigned int key;

	if (!dev_data->common.started) {
		return -EALREADY;
	}

	key = irq_lock();
	XMC_CAN_NODE_SetInitBit(dev_cfg->can);

	XMC_CAN_NODE_EnableConfigurationChange(dev_cfg->can);

	can_xmc4xxx_reset_tx_fifos(dev, -ENETDOWN);
	dev_data->common.started = false;
	irq_unlock(key);

	if (dev_cfg->common.phy != NULL) {
		ret = can_transceiver_disable(dev_cfg->common.phy);
		if (ret < 0) {
			LOG_ERR("Failed to disable CAN transceiver [%d]", ret);
			return ret;
		}
	}

	return 0;
}

static int can_xmc4xxx_init(const struct device *dev)
{
	struct can_xmc4xxx_data *dev_data = dev->data;
	const struct can_xmc4xxx_config *dev_cfg = dev->config;
	int ret;
	struct can_timing timing = {0};
	CAN_MO_TypeDef *mo;
	uint8_t mo_index = 0;

	k_sem_init(&dev_data->tx_sem, CONFIG_CAN_XMC4XXX_MAX_TX_QUEUE,
		   CONFIG_CAN_XMC4XXX_MAX_TX_QUEUE);
	k_mutex_init(&dev_data->mutex);

	if (!can_xmc4xxx_global_init) {
		uint32_t fdr_step;
		uint32_t clk_module;

		XMC_CAN_Enable(can_xmc4xxx_global_reg);
		XMC_CAN_SetBaudrateClockSource(can_xmc4xxx_global_reg, XMC_CAN_CANCLKSRC_FPERI);

		clk_module = XMC_CAN_GetBaudrateClockFrequency(can_xmc4xxx_global_reg);
		fdr_step = 1024 - CAN_XMC4XXX_CLOCK_PRESCALER;
		can_xmc4xxx_clock_frequency = clk_module / CAN_XMC4XXX_CLOCK_PRESCALER;

		LOG_DBG("Clock frequency %dHz\n", can_xmc4xxx_clock_frequency);

		can_xmc4xxx_global_reg->FDR &= ~(CAN_FDR_DM_Msk | CAN_FDR_STEP_Msk);
		can_xmc4xxx_global_reg->FDR |= FIELD_PREP(CAN_FDR_DM_Msk, XMC_CAN_DM_NORMAL) |
					       FIELD_PREP(CAN_FDR_STEP_Msk, fdr_step);

		can_xmc4xxx_global_init = true;
	}

	XMC_CAN_NODE_EnableConfigurationChange(dev_cfg->can);

	XMC_CAN_NODE_SetReceiveInput(dev_cfg->can, dev_cfg->input_src);

	XMC_CAN_NODE_SetInitBit(dev_cfg->can);

	XMC_CAN_NODE_SetEventNodePointer(dev_cfg->can, XMC_CAN_NODE_POINTER_EVENT_ALERT,
					 dev_cfg->service_request);

	XMC_CAN_NODE_SetEventNodePointer(dev_cfg->can, XMC_CAN_NODE_POINTER_EVENT_LEC,
					 dev_cfg->service_request);

	XMC_CAN_NODE_SetEventNodePointer(dev_cfg->can, XMC_CAN_NODE_POINTER_EVENT_TRANSFER_OK,
					 dev_cfg->service_request);

	XMC_CAN_NODE_SetEventNodePointer(dev_cfg->can, XMC_CAN_NODE_POINTER_EVENT_FRAME_COUNTER,
					 dev_cfg->service_request);

	XMC_CAN_NODE_EnableEvent(dev_cfg->can, XMC_CAN_NODE_EVENT_TX_INT |
					       XMC_CAN_NODE_EVENT_ALERT);

	/* set up tx messages */
	for (int i = 0; i < CONFIG_CAN_XMC4XXX_MAX_TX_QUEUE; i++) {
		mo = can_xmc4xxx_get_mo(&mo_index);
		if (mo == NULL) {
			return -ENOMEM;
		}

		dev_data->tx_mo[i] = mo;

		XMC_CAN_AllocateMOtoNodeList(can_xmc4xxx_global_reg,
					     CAN_XMC4XXX_REG_TO_NODE_IND(dev_cfg->can), mo_index);

		mo->MOIPR = FIELD_PREP(CAN_MO_MOIPR_TXINP_Msk, dev_cfg->service_request);
		mo->MOFCR = FIELD_PREP(CAN_MO_MOFCR_MMC_Msk, 0) | CAN_MO_MOFCR_TXIE_Msk;
	}

#ifdef CONFIG_CAN_XMC4XXX_INTERNAL_BUS_MODE
	/* The name of this function is misleading. It doesn't actually enable */
	/* loopback on a single node, but connects all CAN devices to an internal bus. */
	XMC_CAN_NODE_EnableLoopBack(dev_cfg->can);
#endif

	dev_cfg->irq_config_func();

	dev_data->state = CAN_STATE_STOPPED;

#ifndef CONFIG_CAN_XMC4XXX_INTERNAL_BUS_MODE
	ret = pinctrl_apply_state(dev_cfg->pcfg, PINCTRL_STATE_DEFAULT);
	if (ret < 0) {
		return ret;
	}
#endif

	ret = can_calc_timing(dev, &timing, dev_cfg->common.bitrate,
			      dev_cfg->common.sample_point);
	if (ret < 0) {
		return ret;
	}

	LOG_DBG("Presc: %d, BS1: %d, BS2: %d", timing.prescaler, timing.phase_seg1,
		timing.phase_seg2);
	LOG_DBG("Sample-point err : %d", ret);

	return can_set_timing(dev, &timing);
}

static DEVICE_API(can, can_xmc4xxx_api_funcs) = {
	.get_capabilities = can_xmc4xxx_get_capabilities,
	.set_mode = can_xmc4xxx_set_mode,
	.set_timing = can_xmc4xxx_set_timing,
	.start = can_xmc4xxx_start,
	.stop = can_xmc4xxx_stop,
	.send = can_xmc4xxx_send,
	.add_rx_filter = can_xmc4xxx_add_rx_filter,
	.remove_rx_filter = can_xmc4xxx_remove_rx_filter,
	.get_state = can_xmc4xxx_get_state,
	.set_state_change_callback = can_xmc4xxx_set_state_change_callback,
	.get_core_clock = can_xmc4xxx_get_core_clock,
	.get_max_filters = can_xmc4xxx_get_max_filters,
	.timing_min = {
		.sjw = 1,
		.prop_seg = 0,
		.phase_seg1 = 3,
		.phase_seg2 = 2,
		.prescaler = 1,
	},
	.timing_max = {
		.sjw = 4,
		.prop_seg = 0,
		.phase_seg1 = 16,
		.phase_seg2 = 8,
		.prescaler = 64,
	},
};

#define CAN_XMC4XXX_INIT(inst)                                                                     \
	static void can_xmc4xxx_irq_config_##inst(void)                                            \
	{                                                                                          \
		IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), can_xmc4xxx_isr,      \
			    DEVICE_DT_INST_GET(inst), 0);                                          \
		irq_enable(DT_INST_IRQN(inst));                                                    \
	}                                                                                          \
                                                                                                   \
	PINCTRL_DT_INST_DEFINE(inst);                                                              \
                                                                                                   \
	static struct can_xmc4xxx_data can_xmc4xxx_data_##inst;                                    \
	static const struct can_xmc4xxx_config can_xmc4xxx_config_##inst = {                       \
		.common = CAN_DT_DRIVER_CONFIG_INST_GET(inst, 0, 1000000),                         \
		.can = (CAN_NODE_TypeDef *)DT_INST_REG_ADDR(inst),                                 \
		.clock_div8 = DT_INST_PROP(inst, clock_div8),                                      \
		.irq_config_func = can_xmc4xxx_irq_config_##inst,                                  \
		.service_request = DT_INST_IRQN(inst) - CAN_XMC4XXX_IRQ_MIN,                       \
		.input_src = DT_INST_ENUM_IDX(inst, input_src),                                    \
		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),                                      \
	};                                                                                         \
                                                                                                   \
	CAN_DEVICE_DT_INST_DEFINE(inst, can_xmc4xxx_init, NULL, &can_xmc4xxx_data_##inst,          \
				  &can_xmc4xxx_config_##inst, POST_KERNEL,                         \
				  CONFIG_CAN_INIT_PRIORITY, &can_xmc4xxx_api_funcs);

DT_INST_FOREACH_STATUS_OKAY(CAN_XMC4XXX_INIT)
