/*
 * Copyright (c) 2018 Karsten Koenig
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT microchip_mcp2515

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/can/transceiver.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(can_mcp2515, CONFIG_CAN_LOG_LEVEL);

#include "can_mcp2515.h"

#define SP_IS_SET(inst) DT_INST_NODE_HAS_PROP(inst, sample_point) ||

/* Macro to exclude the sample point algorithm from compilation if not used
 * Without the macro, the algorithm would always waste ROM
 */
#define USE_SP_ALGO (DT_INST_FOREACH_STATUS_OKAY(SP_IS_SET) 0)

#define SP_AND_TIMING_NOT_SET(inst) \
	(!DT_INST_NODE_HAS_PROP(inst, sample_point) && \
	!(DT_INST_NODE_HAS_PROP(inst, prop_seg) && \
	DT_INST_NODE_HAS_PROP(inst, phase_seg1) && \
	DT_INST_NODE_HAS_PROP(inst, phase_seg2))) ||

#if DT_INST_FOREACH_STATUS_OKAY(SP_AND_TIMING_NOT_SET) 0
#error You must either set a sampling-point or timings (phase-seg* and prop-seg)
#endif

/* Timeout for changing mode */
#define MCP2515_MODE_CHANGE_TIMEOUT_USEC 1000
#define MCP2515_MODE_CHANGE_RETRIES      100
#define MCP2515_MODE_CHANGE_DELAY	    \
	K_USEC(MCP2515_MODE_CHANGE_TIMEOUT_USEC / MCP2515_MODE_CHANGE_RETRIES)

static int mcp2515_cmd_soft_reset(const struct device *dev)
{
	const struct mcp2515_config *dev_cfg = dev->config;

	uint8_t cmd_buf[] = { MCP2515_OPCODE_RESET };

	const struct spi_buf tx_buf = {
		.buf = cmd_buf, .len = sizeof(cmd_buf),
	};
	const struct spi_buf_set tx = {
		.buffers = &tx_buf, .count = 1U
	};

	return spi_write_dt(&dev_cfg->bus, &tx);
}

static int mcp2515_cmd_bit_modify(const struct device *dev, uint8_t reg_addr,
				  uint8_t mask,
				  uint8_t data)
{
	const struct mcp2515_config *dev_cfg = dev->config;

	uint8_t cmd_buf[] = { MCP2515_OPCODE_BIT_MODIFY, reg_addr, mask, data };

	const struct spi_buf tx_buf = {
		.buf = cmd_buf, .len = sizeof(cmd_buf),
	};
	const struct spi_buf_set tx = {
		.buffers = &tx_buf, .count = 1U
	};

	return spi_write_dt(&dev_cfg->bus, &tx);
}

static int mcp2515_cmd_write_reg(const struct device *dev, uint8_t reg_addr,
				 uint8_t *buf_data, uint8_t buf_len)
{
	const struct mcp2515_config *dev_cfg = dev->config;

	uint8_t cmd_buf[] = { MCP2515_OPCODE_WRITE, reg_addr };

	struct spi_buf tx_buf[] = {
		{ .buf = cmd_buf, .len = sizeof(cmd_buf) },
		{ .buf = buf_data, .len = buf_len }
	};
	const struct spi_buf_set tx = {
		.buffers = tx_buf, .count = ARRAY_SIZE(tx_buf)
	};

	return spi_write_dt(&dev_cfg->bus, &tx);
}

/*
 * Load TX buffer instruction
 *
 * When loading a transmit buffer, reduces the overhead of a normal WRITE
 * command by placing the Address Pointer at one of six locations, as
 * selected by parameter abc.
 *
 *   0: TX Buffer 0, Start at TXB0SIDH (0x31)
 *   1: TX Buffer 0, Start at TXB0D0 (0x36)
 *   2: TX Buffer 1, Start at TXB1SIDH (0x41)
 *   3: TX Buffer 1, Start at TXB1D0 (0x46)
 *   4: TX Buffer 2, Start at TXB2SIDH (0x51)
 *   5: TX Buffer 2, Start at TXB2D0 (0x56)
 */
static int mcp2515_cmd_load_tx_buffer(const struct device *dev, uint8_t abc,
				      uint8_t *buf_data, uint8_t buf_len)
{
	const struct mcp2515_config *dev_cfg = dev->config;

	__ASSERT(abc <= 5, "abc <= 5");

	uint8_t cmd_buf[] = { MCP2515_OPCODE_LOAD_TX_BUFFER | abc };

	struct spi_buf tx_buf[] = {
		{ .buf = cmd_buf, .len = sizeof(cmd_buf) },
		{ .buf = buf_data, .len = buf_len }
	};
	const struct spi_buf_set tx = {
		.buffers = tx_buf, .count = ARRAY_SIZE(tx_buf)
	};

	return spi_write_dt(&dev_cfg->bus, &tx);
}

/*
 * Request-to-Send Instruction
 *
 * Parameter nnn is the combination of bits at positions 0, 1 and 2 in the RTS
 * opcode that respectively initiate transmission for buffers TXB0, TXB1 and
 * TXB2.
 */
static int mcp2515_cmd_rts(const struct device *dev, uint8_t nnn)
{
	const struct mcp2515_config *dev_cfg = dev->config;

	__ASSERT(nnn < BIT(MCP2515_TX_CNT), "nnn < BIT(MCP2515_TX_CNT)");

	uint8_t cmd_buf[] = { MCP2515_OPCODE_RTS | nnn };

	struct spi_buf tx_buf[] = {
		{ .buf = cmd_buf, .len = sizeof(cmd_buf) }
	};
	const struct spi_buf_set tx = {
		.buffers = tx_buf, .count = ARRAY_SIZE(tx_buf)
	};

	return spi_write_dt(&dev_cfg->bus, &tx);
}

static int mcp2515_cmd_read_reg(const struct device *dev, uint8_t reg_addr,
				uint8_t *buf_data, uint8_t buf_len)
{
	const struct mcp2515_config *dev_cfg = dev->config;

	uint8_t cmd_buf[] = { MCP2515_OPCODE_READ, reg_addr };

	struct spi_buf tx_buf[] = {
		{ .buf = cmd_buf, .len = sizeof(cmd_buf) },
		{ .buf = NULL, .len = buf_len }
	};
	const struct spi_buf_set tx = {
		.buffers = tx_buf, .count = ARRAY_SIZE(tx_buf)
	};
	struct spi_buf rx_buf[] = {
		{ .buf = NULL, .len = sizeof(cmd_buf) },
		{ .buf = buf_data, .len = buf_len }
	};
	const struct spi_buf_set rx = {
		.buffers = rx_buf, .count = ARRAY_SIZE(rx_buf)
	};

	return spi_transceive_dt(&dev_cfg->bus, &tx, &rx);
}

/*
 * Read RX Buffer instruction
 *
 * When reading a receive buffer, reduces the overhead of a normal READ
 * command by placing the Address Pointer at one of four locations selected by
 * parameter nm:
 *   0: Receive Buffer 0, Start at RXB0SIDH (0x61)
 *   1: Receive Buffer 0, Start at RXB0D0 (0x66)
 *   2: Receive Buffer 1, Start at RXB1SIDH (0x71)
 *   3: Receive Buffer 1, Start at RXB1D0 (0x76)
 */
static int mcp2515_cmd_read_rx_buffer(const struct device *dev, uint8_t nm,
				      uint8_t *buf_data, uint8_t buf_len)
{
	const struct mcp2515_config *dev_cfg = dev->config;

	__ASSERT(nm <= 0x03, "nm <= 0x03");

	uint8_t cmd_buf[] = { MCP2515_OPCODE_READ_RX_BUFFER | (nm << 1) };

	struct spi_buf tx_buf[] = {
		{ .buf = cmd_buf, .len = sizeof(cmd_buf) },
		{ .buf = NULL, .len = buf_len }
	};
	const struct spi_buf_set tx = {
		.buffers = tx_buf, .count = ARRAY_SIZE(tx_buf)
	};
	struct spi_buf rx_buf[] = {
		{ .buf = NULL, .len = sizeof(cmd_buf) },
		{ .buf = buf_data, .len = buf_len }
	};
	const struct spi_buf_set rx = {
		.buffers = rx_buf, .count = ARRAY_SIZE(rx_buf)
	};

	return spi_transceive_dt(&dev_cfg->bus, &tx, &rx);
}

static void mcp2515_convert_canframe_to_mcp2515frame(const struct can_frame
						     *source, uint8_t *target)
{
	uint8_t rtr;
	uint8_t dlc;
	uint8_t data_idx = 0U;

	if ((source->flags & CAN_FRAME_IDE) != 0) {
		target[MCP2515_FRAME_OFFSET_SIDH] = source->id >> 21;
		target[MCP2515_FRAME_OFFSET_SIDL] =
			(((source->id >> 18) & 0x07) << 5) | (BIT(3)) |
			((source->id >> 16) & 0x03);
		target[MCP2515_FRAME_OFFSET_EID8] = source->id >> 8;
		target[MCP2515_FRAME_OFFSET_EID0] = source->id;
	} else {
		target[MCP2515_FRAME_OFFSET_SIDH] = source->id >> 3;
		target[MCP2515_FRAME_OFFSET_SIDL] =
			(source->id & 0x07) << 5;
	}

	rtr = (source->flags & CAN_FRAME_RTR) != 0 ? BIT(6) : 0;
	dlc = (source->dlc) & 0x0F;

	target[MCP2515_FRAME_OFFSET_DLC] = rtr | dlc;

	for (; data_idx < CAN_MAX_DLC; data_idx++) {
		target[MCP2515_FRAME_OFFSET_D0 + data_idx] =
			source->data[data_idx];
	}
}

static void mcp2515_convert_mcp2515frame_to_canframe(const uint8_t *source,
						     struct can_frame *target)
{
	uint8_t data_idx = 0U;

	memset(target, 0, sizeof(*target));

	if (source[MCP2515_FRAME_OFFSET_SIDL] & BIT(3)) {
		target->flags |= CAN_FRAME_IDE;
		target->id =
			(source[MCP2515_FRAME_OFFSET_SIDH] << 21) |
			((source[MCP2515_FRAME_OFFSET_SIDL] >> 5) << 18) |
			((source[MCP2515_FRAME_OFFSET_SIDL] & 0x03) << 16) |
			(source[MCP2515_FRAME_OFFSET_EID8] << 8) |
			source[MCP2515_FRAME_OFFSET_EID0];
	} else {
		target->id = (source[MCP2515_FRAME_OFFSET_SIDH] << 3) |
				 (source[MCP2515_FRAME_OFFSET_SIDL] >> 5);
	}

	target->dlc = source[MCP2515_FRAME_OFFSET_DLC] & 0x0F;

	if ((source[MCP2515_FRAME_OFFSET_DLC] & BIT(6)) != 0) {
		target->flags |= CAN_FRAME_RTR;
	}

	for (; data_idx < CAN_MAX_DLC; data_idx++) {
		target->data[data_idx] = source[MCP2515_FRAME_OFFSET_D0 +
						data_idx];
	}
}

const int mcp2515_set_mode_int(const struct device *dev, uint8_t mcp2515_mode)
{
	int retries = MCP2515_MODE_CHANGE_RETRIES;
	uint8_t canstat;

	mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_CANCTRL,
			       MCP2515_CANCTRL_MODE_MASK,
			       mcp2515_mode << MCP2515_CANCTRL_MODE_POS);
	mcp2515_cmd_read_reg(dev, MCP2515_ADDR_CANSTAT, &canstat, 1);

	while (((canstat & MCP2515_CANSTAT_MODE_MASK) >> MCP2515_CANSTAT_MODE_POS)
		!= mcp2515_mode) {
		if (--retries < 0) {
			LOG_ERR("Timeout trying to set MCP2515 operation mode");
			return -EIO;
		}

		k_sleep(MCP2515_MODE_CHANGE_DELAY);
		mcp2515_cmd_read_reg(dev, MCP2515_ADDR_CANSTAT, &canstat, 1);
	}

	return 0;
}

static void mcp2515_tx_done(const struct device *dev, uint8_t tx_idx, int status)
{
	struct mcp2515_data *dev_data = dev->data;
	can_tx_callback_t callback = dev_data->tx_cb[tx_idx].cb;

	if (callback != NULL) {
		callback(dev, status, dev_data->tx_cb[tx_idx].cb_arg);
		dev_data->tx_cb[tx_idx].cb = NULL;

		k_mutex_lock(&dev_data->mutex, K_FOREVER);
		dev_data->tx_busy_map &= ~BIT(tx_idx);
		k_mutex_unlock(&dev_data->mutex);
		k_sem_give(&dev_data->tx_sem);
	}
}

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

	*rate = dev_cfg->osc_freq / 2;
	return 0;
}

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

	return CONFIG_CAN_MAX_FILTER;
}

static int mcp2515_get_max_bitrate(const struct device *dev, uint32_t *max_bitrate)
{
	const struct mcp2515_config *dev_cfg = dev->config;

	*max_bitrate = dev_cfg->max_bitrate;

	return 0;
}

static int mcp2515_set_timing(const struct device *dev,
			      const struct can_timing *timing)
{
	struct mcp2515_data *dev_data = dev->data;
	int ret;

	if (!timing) {
		return -EINVAL;
	}

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

	/* CNF3, CNF2, CNF1, CANINTE */
	uint8_t config_buf[4];

	/* CNF1; SJW<7:6> | BRP<5:0> */
	__ASSERT(timing->prescaler > 0, "Prescaler should be bigger than zero");
	uint8_t brp = timing->prescaler - 1;
	if (timing->sjw != CAN_SJW_NO_CHANGE) {
		dev_data->sjw = (timing->sjw - 1) << 6;
	}

	uint8_t cnf1 = dev_data->sjw | brp;

	/* CNF2; BTLMODE<7>|SAM<6>|PHSEG1<5:3>|PRSEG<2:0> */
	const uint8_t btlmode = 1 << 7;
	const uint8_t sam = 0 << 6;
	const uint8_t phseg1 = (timing->phase_seg1 - 1) << 3;
	const uint8_t prseg = (timing->prop_seg - 1);

	const uint8_t cnf2 = btlmode | sam | phseg1 | prseg;

	/* CNF3; SOF<7>|WAKFIL<6>|UND<5:3>|PHSEG2<2:0> */
	const uint8_t sof = 0 << 7;
	const uint8_t wakfil = 0 << 6;
	const uint8_t und = 0 << 3;
	const uint8_t phseg2 = (timing->phase_seg2 - 1);

	const uint8_t cnf3 = sof | wakfil | und | phseg2;

	const uint8_t caninte = MCP2515_INTE_RX0IE | MCP2515_INTE_RX1IE |
			     MCP2515_INTE_TX0IE | MCP2515_INTE_TX1IE |
			     MCP2515_INTE_TX2IE | MCP2515_INTE_ERRIE;

	/* Receive everything, filtering done in driver, RXB0 roll over into
	 * RXB1 */
	const uint8_t rx0_ctrl = BIT(6) | BIT(5) | BIT(2);
	const uint8_t rx1_ctrl = BIT(6) | BIT(5);

	__ASSERT(timing->sjw <= 4, "1 <= SJW <= 4");
	__ASSERT((timing->prop_seg >= 1) && (timing->prop_seg <= 8),
		 "1 <= PROP <= 8");
	__ASSERT((timing->phase_seg1 >= 1) && (timing->phase_seg1 <= 8),
		 "1 <= BS1 <= 8");
	__ASSERT((timing->phase_seg2 >= 2) && (timing->phase_seg2 <= 8),
		 "2 <= BS2 <= 8");
	__ASSERT(timing->prop_seg + timing->phase_seg1 >= timing->phase_seg2,
		 "PROP + BS1 >= BS2");
	__ASSERT(timing->phase_seg2 > timing->sjw, "BS2 > SJW");

	config_buf[0] = cnf3;
	config_buf[1] = cnf2;
	config_buf[2] = cnf1;
	config_buf[3] = caninte;

	k_mutex_lock(&dev_data->mutex, K_FOREVER);

	ret = mcp2515_cmd_write_reg(dev, MCP2515_ADDR_CNF3, config_buf,
				    sizeof(config_buf));
	if (ret < 0) {
		LOG_ERR("Failed to write the configuration [%d]", ret);
		goto done;
	}

	ret = mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_RXB0CTRL, rx0_ctrl,
				     rx0_ctrl);
	if (ret < 0) {
		LOG_ERR("Failed to write RXB0CTRL [%d]", ret);
		goto done;
	}

	ret = mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_RXB1CTRL, rx1_ctrl,
				     rx1_ctrl);
	if (ret < 0) {
		LOG_ERR("Failed to write RXB1CTRL [%d]", ret);
		goto done;
	}

done:
	k_mutex_unlock(&dev_data->mutex);
	return ret;
}

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

	*cap = CAN_MODE_NORMAL | CAN_MODE_LISTENONLY | CAN_MODE_LOOPBACK;

	return 0;
}

static int mcp2515_start(const struct device *dev)
{
	const struct mcp2515_config *dev_cfg = dev->config;
	struct mcp2515_data *dev_data = dev->data;
	int ret;

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

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

	k_mutex_lock(&dev_data->mutex, K_FOREVER);

	ret = mcp2515_set_mode_int(dev, dev_data->mcp2515_mode);
	if (ret < 0) {
		LOG_ERR("Failed to set the mode [%d]", ret);

		if (dev_cfg->phy != NULL) {
			/* Attempt to disable the CAN transceiver in case of error */
			(void)can_transceiver_disable(dev_cfg->phy);
		}
	} else {
		dev_data->started = true;
	}

	k_mutex_unlock(&dev_data->mutex);

	return ret;
}

static int mcp2515_stop(const struct device *dev)
{
	const struct mcp2515_config *dev_cfg = dev->config;
	struct mcp2515_data *dev_data = dev->data;
	int ret;
	int i;

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

	k_mutex_lock(&dev_data->mutex, K_FOREVER);

	/* Abort any pending transmissions before entering configuration mode */
	mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_TXB0CTRL,
			       MCP2515_TXBNCTRL_TXREQ_MASK, 0);
#if MCP2515_TX_CNT == 2
	mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_TXB1CTRL,
			       MCP2515_TXBNCTRL_TXREQ_MASK, 0);
#endif /*  MCP2515_TX_CNT == 2 */
#if MCP2515_TX_CNT == 3
	mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_TXB2CTRL,
			       MCP2515_TXBNCTRL_TXREQ_MASK, 0);
#endif /* MCP2515_TX_CNT == 3 */

	ret = mcp2515_set_mode_int(dev, MCP2515_MODE_CONFIGURATION);
	if (ret < 0) {
		LOG_ERR("Failed to enter configuration mode [%d]", ret);
		k_mutex_unlock(&dev_data->mutex);
		return ret;
	}

	dev_data->started = false;

	k_mutex_unlock(&dev_data->mutex);

	for (i = 0; i < MCP2515_TX_CNT; i++) {
		mcp2515_tx_done(dev, i, -ENETDOWN);
	}

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

	return 0;
}

static int mcp2515_set_mode(const struct device *dev, can_mode_t mode)
{
	struct mcp2515_data *dev_data = dev->data;

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

	switch (mode) {
	case CAN_MODE_NORMAL:
		dev_data->mcp2515_mode = MCP2515_MODE_NORMAL;
		break;
	case CAN_MODE_LISTENONLY:
		dev_data->mcp2515_mode = MCP2515_MODE_SILENT;
		break;
	case CAN_MODE_LOOPBACK:
		dev_data->mcp2515_mode = MCP2515_MODE_LOOPBACK;
		break;
	default:
		LOG_ERR("Unsupported CAN Mode %u", mode);
		return -ENOTSUP;
	}

	return 0;
}

static int mcp2515_send(const struct device *dev,
			const struct can_frame *frame,
			k_timeout_t timeout, can_tx_callback_t callback,
			void *user_data)
{
	struct mcp2515_data *dev_data = dev->data;
	uint8_t tx_idx = 0U;
	uint8_t abc;
	uint8_t nnn;
	uint8_t len;
	uint8_t tx_frame[MCP2515_FRAME_LEN];

	__ASSERT_NO_MSG(callback != NULL);

	if (frame->dlc > CAN_MAX_DLC) {
		LOG_ERR("DLC of %d exceeds maximum (%d)",
			frame->dlc, CAN_MAX_DLC);
		return -EINVAL;
	}

	if ((frame->flags & ~(CAN_FRAME_IDE | CAN_FRAME_RTR)) != 0) {
		LOG_ERR("unsupported CAN frame flags 0x%02x", frame->flags);
		return -ENOTSUP;
	}

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

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

	k_mutex_lock(&dev_data->mutex, K_FOREVER);

	/* find a free tx slot */
	for (; tx_idx < MCP2515_TX_CNT; tx_idx++) {
		if ((BIT(tx_idx) & dev_data->tx_busy_map) == 0) {
			dev_data->tx_busy_map |= BIT(tx_idx);
			break;
		}
	}

	k_mutex_unlock(&dev_data->mutex);

	if (tx_idx == MCP2515_TX_CNT) {
		LOG_WRN("no free tx slot available");
		return -EIO;
	}

	dev_data->tx_cb[tx_idx].cb = callback;
	dev_data->tx_cb[tx_idx].cb_arg = user_data;

	mcp2515_convert_canframe_to_mcp2515frame(frame, tx_frame);

	/* Address Pointer selection */
	abc = 2 * tx_idx;

	/* Calculate minimum length to transfer */
	len = sizeof(tx_frame) - CAN_MAX_DLC + frame->dlc;

	mcp2515_cmd_load_tx_buffer(dev, abc, tx_frame, len);

	/* request tx slot transmission */
	nnn = BIT(tx_idx);
	mcp2515_cmd_rts(dev, nnn);

	return 0;
}

static int mcp2515_add_rx_filter(const struct device *dev,
				 can_rx_callback_t rx_cb,
				 void *cb_arg,
				 const struct can_filter *filter)
{
	struct mcp2515_data *dev_data = dev->data;
	int filter_id = 0;

	__ASSERT(rx_cb != NULL, "response_ptr can not be null");

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

	k_mutex_lock(&dev_data->mutex, K_FOREVER);

	/* find free filter */
	while ((BIT(filter_id) & dev_data->filter_usage)
	       && (filter_id < CONFIG_CAN_MAX_FILTER)) {
		filter_id++;
	}

	/* setup filter */
	if (filter_id < CONFIG_CAN_MAX_FILTER) {
		dev_data->filter_usage |= BIT(filter_id);

		dev_data->filter[filter_id] = *filter;
		dev_data->rx_cb[filter_id] = rx_cb;
		dev_data->cb_arg[filter_id] = cb_arg;

	} else {
		filter_id = -ENOSPC;
	}

	k_mutex_unlock(&dev_data->mutex);

	return filter_id;
}

static void mcp2515_remove_rx_filter(const struct device *dev, int filter_id)
{
	struct mcp2515_data *dev_data = dev->data;

	k_mutex_lock(&dev_data->mutex, K_FOREVER);
	dev_data->filter_usage &= ~BIT(filter_id);
	k_mutex_unlock(&dev_data->mutex);
}

static void mcp2515_set_state_change_callback(const struct device *dev,
					      can_state_change_callback_t cb,
					      void *user_data)
{
	struct mcp2515_data *dev_data = dev->data;

	dev_data->state_change_cb = cb;
	dev_data->state_change_cb_data = user_data;
}

static void mcp2515_rx_filter(const struct device *dev,
			      struct can_frame *frame)
{
	struct mcp2515_data *dev_data = dev->data;
	uint8_t filter_id = 0U;
	can_rx_callback_t callback;
	struct can_frame tmp_frame;

	k_mutex_lock(&dev_data->mutex, K_FOREVER);

	for (; filter_id < CONFIG_CAN_MAX_FILTER; filter_id++) {
		if (!(BIT(filter_id) & dev_data->filter_usage)) {
			continue; /* filter slot empty */
		}

		if (!can_frame_matches_filter(frame, &dev_data->filter[filter_id])) {
			continue; /* filter did not match */
		}

		callback = dev_data->rx_cb[filter_id];
		/*Make a temporary copy in case the user modifies the message*/
		tmp_frame = *frame;

		callback(dev, &tmp_frame, dev_data->cb_arg[filter_id]);
	}

	k_mutex_unlock(&dev_data->mutex);
}

static void mcp2515_rx(const struct device *dev, uint8_t rx_idx)
{
	__ASSERT(rx_idx < MCP2515_RX_CNT, "rx_idx < MCP2515_RX_CNT");

	struct can_frame frame;
	uint8_t rx_frame[MCP2515_FRAME_LEN];
	uint8_t nm;

	/* Address Pointer selection */
	nm = 2 * rx_idx;

	/* Fetch rx buffer */
	mcp2515_cmd_read_rx_buffer(dev, nm, rx_frame, sizeof(rx_frame));
	mcp2515_convert_mcp2515frame_to_canframe(rx_frame, &frame);
	mcp2515_rx_filter(dev, &frame);
}

static int mcp2515_get_state(const struct device *dev, enum can_state *state,
			     struct can_bus_err_cnt *err_cnt)
{
	struct mcp2515_data *dev_data = dev->data;
	uint8_t eflg;
	uint8_t err_cnt_buf[2];
	int ret;

	ret = mcp2515_cmd_read_reg(dev, MCP2515_ADDR_EFLG, &eflg, sizeof(eflg));
	if (ret < 0) {
		LOG_ERR("Failed to read error register [%d]", ret);
		return -EIO;
	}

	if (state != NULL) {
		if (!dev_data->started) {
			*state = CAN_STATE_STOPPED;
		} else if (eflg & MCP2515_EFLG_TXBO) {
			*state = CAN_STATE_BUS_OFF;
		} else if ((eflg & MCP2515_EFLG_RXEP) || (eflg & MCP2515_EFLG_TXEP)) {
			*state = CAN_STATE_ERROR_PASSIVE;
		} else if (eflg & MCP2515_EFLG_EWARN) {
			*state = CAN_STATE_ERROR_WARNING;
		} else {
			*state = CAN_STATE_ERROR_ACTIVE;
		}
	}

	if (err_cnt != NULL) {
		ret = mcp2515_cmd_read_reg(dev, MCP2515_ADDR_TEC, err_cnt_buf,
					   sizeof(err_cnt_buf));
		if (ret < 0) {
			LOG_ERR("Failed to read error counters [%d]", ret);
			return -EIO;
		}

		err_cnt->tx_err_cnt = err_cnt_buf[0];
		err_cnt->rx_err_cnt = err_cnt_buf[1];
	}

	return 0;
}

static void mcp2515_handle_errors(const struct device *dev)
{
	struct mcp2515_data *dev_data = dev->data;
	can_state_change_callback_t state_change_cb = dev_data->state_change_cb;
	void *state_change_cb_data = dev_data->state_change_cb_data;
	enum can_state state;
	struct can_bus_err_cnt err_cnt;
	int err;

	err = mcp2515_get_state(dev, &state, state_change_cb ? &err_cnt : NULL);
	if (err != 0) {
		LOG_ERR("Failed to get CAN controller state [%d]", err);
		return;
	}

	if (state_change_cb && dev_data->old_state != state) {
		dev_data->old_state = state;
		state_change_cb(dev, state, err_cnt, state_change_cb_data);
	}
}

#ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY
static int mcp2515_recover(const struct device *dev, k_timeout_t timeout)
{
	struct mcp2515_data *dev_data = dev->data;

	ARG_UNUSED(timeout);

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

	return -ENOTSUP;
}
#endif

static void mcp2515_handle_interrupts(const struct device *dev)
{
	const struct mcp2515_config *dev_cfg = dev->config;
	int ret;
	uint8_t canintf;

	/* Loop until INT pin is inactive (all interrupt flags handled) */
	while (1) {
		ret = mcp2515_cmd_read_reg(dev, MCP2515_ADDR_CANINTF,
				&canintf, 1);
		if (ret != 0) {
			LOG_ERR("Couldn't read INTF register %d", ret);
			continue;
		}

		if (canintf == 0) {
			/* No interrupt flags set */
			break;
		}

		if (canintf & MCP2515_CANINTF_RX0IF) {
			mcp2515_rx(dev, 0);

			/* RX0IF flag cleared automatically during read */
			canintf &= ~MCP2515_CANINTF_RX0IF;
		}

		if (canintf & MCP2515_CANINTF_RX1IF) {
			mcp2515_rx(dev, 1);

			/* RX1IF flag cleared automatically during read */
			canintf &= ~MCP2515_CANINTF_RX1IF;
		}

		if (canintf & MCP2515_CANINTF_TX0IF) {
			mcp2515_tx_done(dev, 0, 0);
		}

		if (canintf & MCP2515_CANINTF_TX1IF) {
			mcp2515_tx_done(dev, 1, 0);
		}

		if (canintf & MCP2515_CANINTF_TX2IF) {
			mcp2515_tx_done(dev, 2, 0);
		}

		if (canintf & MCP2515_CANINTF_ERRIF) {
			mcp2515_handle_errors(dev);
		}

		if (canintf != 0) {
			/* Clear remaining flags */
			mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_CANINTF,
					canintf, ~canintf);
		}

		/* Break from loop if INT pin is inactive */
		ret = gpio_pin_get_dt(&dev_cfg->int_gpio);
		if (ret < 0) {
			LOG_ERR("Couldn't read INT pin");
		} else if (ret == 0) {
			/* All interrupt flags handled */
			break;
		}
	}
}

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

	while (1) {
		k_sem_take(&dev_data->int_sem, K_FOREVER);
		mcp2515_handle_interrupts(dev);
	}
}

static void mcp2515_int_gpio_callback(const struct device *dev,
				      struct gpio_callback *cb, uint32_t pins)
{
	struct mcp2515_data *dev_data =
		CONTAINER_OF(cb, struct mcp2515_data, int_gpio_cb);

	k_sem_give(&dev_data->int_sem);
}

static const struct can_driver_api can_api_funcs = {
	.get_capabilities = mcp2515_get_capabilities,
	.set_timing = mcp2515_set_timing,
	.start = mcp2515_start,
	.stop = mcp2515_stop,
	.set_mode = mcp2515_set_mode,
	.send = mcp2515_send,
	.add_rx_filter = mcp2515_add_rx_filter,
	.remove_rx_filter = mcp2515_remove_rx_filter,
	.get_state = mcp2515_get_state,
#ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY
	.recover = mcp2515_recover,
#endif
	.set_state_change_callback = mcp2515_set_state_change_callback,
	.get_core_clock = mcp2515_get_core_clock,
	.get_max_filters = mcp2515_get_max_filters,
	.get_max_bitrate = mcp2515_get_max_bitrate,
	.timing_min = {
		.sjw = 0x1,
		.prop_seg = 0x01,
		.phase_seg1 = 0x01,
		.phase_seg2 = 0x02,
		.prescaler = 0x01
	},
	.timing_max = {
		.sjw = 0x04,
		.prop_seg = 0x08,
		.phase_seg1 = 0x08,
		.phase_seg2 = 0x08,
		.prescaler = 0x40
	}
};


static int mcp2515_init(const struct device *dev)
{
	const struct mcp2515_config *dev_cfg = dev->config;
	struct mcp2515_data *dev_data = dev->data;
	struct can_timing timing;
	k_tid_t tid;
	int ret;

	k_sem_init(&dev_data->int_sem, 0, 1);
	k_mutex_init(&dev_data->mutex);
	k_sem_init(&dev_data->tx_sem, MCP2515_TX_CNT, MCP2515_TX_CNT);

	if (dev_cfg->phy != NULL) {
		if (!device_is_ready(dev_cfg->phy)) {
			LOG_ERR("CAN transceiver not ready");
			return -ENODEV;
		}
	}

	if (!spi_is_ready_dt(&dev_cfg->bus)) {
		LOG_ERR("SPI bus %s not ready", dev_cfg->bus.bus->name);
		return -ENODEV;
	}

	/* Reset MCP2515 */
	if (mcp2515_cmd_soft_reset(dev)) {
		LOG_ERR("Soft-reset failed");
		return -EIO;
	}

	/* Initialize interrupt handling  */
	if (!gpio_is_ready_dt(&dev_cfg->int_gpio)) {
		LOG_ERR("Interrupt GPIO port not ready");
		return -ENODEV;
	}

	if (gpio_pin_configure_dt(&dev_cfg->int_gpio, GPIO_INPUT)) {
		LOG_ERR("Unable to configure interrupt GPIO");
		return -EINVAL;
	}

	gpio_init_callback(&(dev_data->int_gpio_cb), mcp2515_int_gpio_callback,
			   BIT(dev_cfg->int_gpio.pin));

	if (gpio_add_callback(dev_cfg->int_gpio.port,
			      &(dev_data->int_gpio_cb))) {
		return -EINVAL;
	}

	if (gpio_pin_interrupt_configure_dt(&dev_cfg->int_gpio,
					    GPIO_INT_EDGE_TO_ACTIVE)) {
		return -EINVAL;
	}

	tid = k_thread_create(&dev_data->int_thread, dev_data->int_thread_stack,
			      dev_cfg->int_thread_stack_size,
			      (k_thread_entry_t) mcp2515_int_thread, (void *)dev,
			      NULL, NULL, K_PRIO_COOP(dev_cfg->int_thread_priority),
			      0, K_NO_WAIT);
	(void)k_thread_name_set(tid, "mcp2515");

	(void)memset(dev_data->rx_cb, 0, sizeof(dev_data->rx_cb));
	(void)memset(dev_data->filter, 0, sizeof(dev_data->filter));
	dev_data->old_state = CAN_STATE_ERROR_ACTIVE;

	timing.sjw = dev_cfg->tq_sjw;
	if (dev_cfg->sample_point && USE_SP_ALGO) {
		ret = can_calc_timing(dev, &timing, dev_cfg->bus_speed,
				      dev_cfg->sample_point);
		if (ret == -EINVAL) {
			LOG_ERR("Can't find timing for given param");
			return -EIO;
		}
		LOG_DBG("Presc: %d, BS1: %d, BS2: %d",
			timing.prescaler, timing.phase_seg1, timing.phase_seg2);
		LOG_DBG("Sample-point err : %d", ret);
	} else {
		timing.prop_seg = dev_cfg->tq_prop;
		timing.phase_seg1 = dev_cfg->tq_bs1;
		timing.phase_seg2 = dev_cfg->tq_bs2;
		ret = can_calc_prescaler(dev, &timing, dev_cfg->bus_speed);
		if (ret) {
			LOG_WRN("Bitrate error: %d", ret);
		}
	}

	k_usleep(MCP2515_OSC_STARTUP_US);

	ret = can_set_timing(dev, &timing);
	if (ret) {
		return ret;
	}

	ret = can_set_mode(dev, CAN_MODE_NORMAL);

	return ret;
}

#define MCP2515_INIT(inst)                                                                         \
	static K_KERNEL_STACK_DEFINE(mcp2515_int_thread_stack_##inst,                              \
				     CONFIG_CAN_MCP2515_INT_THREAD_STACK_SIZE);                    \
                                                                                                   \
	static struct mcp2515_data mcp2515_data_##inst = {                                         \
		.int_thread_stack = mcp2515_int_thread_stack_##inst,                               \
		.tx_busy_map = 0U,                                                                 \
		.filter_usage = 0U,                                                                \
	};                                                                                         \
                                                                                                   \
	static const struct mcp2515_config mcp2515_config_##inst = {                               \
		.bus = SPI_DT_SPEC_INST_GET(inst, SPI_WORD_SET(8), 0),                             \
		.int_gpio = GPIO_DT_SPEC_INST_GET(inst, int_gpios),                                \
		.int_thread_stack_size = CONFIG_CAN_MCP2515_INT_THREAD_STACK_SIZE,                 \
		.int_thread_priority = CONFIG_CAN_MCP2515_INT_THREAD_PRIO,                         \
		.tq_sjw = DT_INST_PROP(inst, sjw),                                                 \
		.tq_prop = DT_INST_PROP_OR(inst, prop_seg, 0),                                     \
		.tq_bs1 = DT_INST_PROP_OR(inst, phase_seg1, 0),                                    \
		.tq_bs2 = DT_INST_PROP_OR(inst, phase_seg2, 0),                                    \
		.bus_speed = DT_INST_PROP(inst, bus_speed),                                        \
		.osc_freq = DT_INST_PROP(inst, osc_freq),                                          \
		.sample_point = DT_INST_PROP_OR(inst, sample_point, 0),                            \
		.phy = DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(inst, phys)),                         \
		.max_bitrate = DT_INST_CAN_TRANSCEIVER_MAX_BITRATE(inst, 1000000),                 \
	};                                                                                         \
                                                                                                   \
	DEVICE_DT_INST_DEFINE(inst, &mcp2515_init, NULL, &mcp2515_data_##inst,                     \
			      &mcp2515_config_##inst, POST_KERNEL, CONFIG_CAN_INIT_PRIORITY,       \
			      &can_api_funcs);

DT_INST_FOREACH_STATUS_OKAY(MCP2515_INIT)
