// SPDX-License-Identifier: GPL-2.0-only
/*
 * This file is part of the CAN-CTRL IP Core driver for Linux
 *
 * Copyright (C) Fraunhofer-Institut for Photonic Microsystems (IPMS)
 * Maria-Reiche-Str. 2
 * 01109 Dresden
 *
 * This file is licensed under the terms of the GNU General Public
 * License version 2. This program is licensed "as is" without any
 * warranty of any kind, whether express or implied.
 *
 * @file can_lib.c
 * @author bog, sep, svt
 * @date 14/05/2024
 * @brief CAN-CTRL driver functions
 */

#include "can_lib.h"
#include "can_linux_hal.h"

#define CAN_WORD_SIZE (4u) /**< CAN word access size in bytes */
#define CAN_IDE_SHIFT (18u) /**< CAN ID shift between extended and basic ID */

/**
 * @brief convert length to DLC for classic and FD frame
 *
 */
static uint32_t can_dlc_code(uint32_t len)
{
	return ((len) <= (uint8_t)8  ? (uint32_t)(len) :
		(len) <= (uint8_t)12 ? (uint32_t)0b1001 :
		(len) <= (uint8_t)16 ? (uint32_t)0b1010 :
		(len) <= (uint8_t)20 ? (uint32_t)0b1011 :
		(len) <= (uint8_t)24 ? (uint32_t)0b1100 :
		(len) <= (uint8_t)32 ? (uint32_t)0b1101 :
		(len) <= (uint8_t)48 ? (uint32_t)0b1110 :
		(len) <= (uint8_t)64 ? (uint32_t)0b1111 :
				       (uint32_t)0);
}

/**
 * @brief convert DLC to length for classic and FD frame
 *
 */
static uint8_t can_dlc_decode(uint32_t len)
{
	return ((len) <= (uint32_t)0b1000 ? (uint8_t)(len) :
		(len) == (uint32_t)0b1001 ? (uint8_t)12 :
		(len) == (uint32_t)0b1010 ? (uint8_t)16 :
		(len) == (uint32_t)0b1011 ? (uint8_t)20 :
		(len) == (uint32_t)0b1100 ? (uint8_t)24 :
		(len) == (uint32_t)0b1101 ? (uint8_t)32 :
		(len) == (uint32_t)0b1110 ? (uint8_t)48 :
		(len) == (uint32_t)0b1111 ? (uint8_t)64 :
					    (uint8_t)0);
}

/**
 * @brief copy payload to transmit buffer in word alignment
 *
 * @param can   pointer to CAN core register map
 * @param data  pointer to buffer
 * @param len   copy length in bytes
 */
static void can_tbuf_payload(volatile CAN_CTRL_t *can, uint32_t *data,
			     uint32_t len)
{
	uint32_t word_len = len / CAN_WORD_SIZE;
	uint32_t remain_len = len % CAN_WORD_SIZE;

	can_linux_tbuf_payload_write(can, (uint32_t *)data, word_len, 0);

	if (remain_len != 0u) {
		uint32_t remain_data = data[word_len];

		remain_data &= ~(0xFFFFFFFFu << (remain_len * 8u));
		can_linux_tbuf_payload_write(can, &remain_data, 1, word_len);
	}
}

/**
 * @brief copy payload from receive buffer in word alignment
 *
 * @param can   pointer to CAN core register map
 * @param data  pointer to buffer
 * @param len   copy length in bytes
 * @note data and sizeof(data) must be word-aligned
 */
static void can_rbuf_payload(volatile CAN_CTRL_t *can, uint32_t *data,
			     uint32_t len)
{
	uint32_t word_len = len / CAN_WORD_SIZE;
	uint32_t remain_len = len % CAN_WORD_SIZE;

	can_linux_rbuf_payload_read(can, (uint32_t *)data, word_len, 0);

	if (remain_len != 0u) {
		uint32_t remain_data;

		can_linux_rbuf_payload_read(can, &remain_data, 1, word_len);
		remain_data &= ~(0xFFFFFFFFu << (remain_len * 8u));
		data[word_len] = remain_data;
	}
}

/**
 * @brief trigger the transmit buffer depending on buffer select
 *
 * @param can   pointer to CAN core register map
 */
static can_linux_error_code_t can_tbuf_trigger(volatile CAN_CTRL_t *can)
{
	CAN_CTRL_TCMD_t tcmd;

	can_linux_tcmd_read(can, &tcmd);

	if (tcmd.TBSEL == (uint8_t)1)
		return can_transmit_buffer_trigger(can, TBUF_TRIGGER_STB_ALL);
	else
		return can_transmit_buffer_trigger(can, TBUF_TRIGGER_PTB_ONE);
}

/**
 * @brief activate/deactivate the reset state of the CAN core
 */
can_linux_error_code_t can_reset(volatile CAN_CTRL_t *can, bool active)
{
	CAN_CTRL_CFG_STAT_t cfg_stat;

	if (can == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_cfg_stat_read(can, &cfg_stat);
	cfg_stat.RESET = active ? (uint8_t)1 : (uint8_t)0;
	can_linux_cfg_stat_write(can, &cfg_stat);

	return CAN_NO_ERROR;
}

/*!
 * @brief check if the transmit buffer can receive the next frame
 *
 * @param can pointer to CAN core register map
 * @return true buffer slots available
 * @return false no buffer slots available
 */
static bool can_check_transmitter_free(volatile CAN_CTRL_t *can)
{
	CAN_CTRL_TCMD_t tcmd;

	can_linux_tcmd_read(can, &tcmd);

	// if PTB is selected we have to check for Reset of TPE, otherwise TSNEXT
	if (tcmd.TBSEL == (uint8_t)0) {
		if (tcmd.TPE != (uint8_t)0)
			return false;

	} else {
		CAN_CTRL_TCTRL_t tctrl;

		can_linux_tctrl_read(can, &tctrl);
		if (tctrl.TSSTAT == (uint8_t)TSSTAT_STB_full)
			return false;
	}

	return true;
}

/*!
 * @brief follow up after calling a transmit function
 *
 * @param can pointer to CAN core register map
 * @param auto_action action to be performed
 * @return can_linux_error_code_t
 */
static can_linux_error_code_t
perform_transmit_autoaction(volatile CAN_CTRL_t *can,
			    can_autoaction_type_t auto_action)
{
	switch (auto_action) {
	case CAN_AUTOACTION_PRIMARY_SEND:
		return can_tbuf_trigger(can);
		break;
	case CAN_AUTOACTION_SECONDARY_NEXT:
		return can_select_next_tbuf_slot(can);
		break;
	case CAN_AUTOACTION_NONE:
		return CAN_NO_ERROR;
		break;
	default:
		break;
	}

	return CAN_ERR_PARAMETER;
}

/**
 * @brief start the transmit of classic CAN frame
 */
can_linux_error_code_t can_transmit(volatile CAN_CTRL_t *can,
				    can_frame_info_t *frame, uint32_t *data,
				    can_autoaction_type_t auto_action, uint32_t handle)
{
	CAN_CTRL_TBUF_IDENTIFIER_t identifier = { 0 };
	CAN_CTRL_TBUF_FORMAT_t tbuf_format = { 0 };
	CAN_CTRL_TBUF_TYPE_t tbuf_type;

	if (can == NULL || frame == NULL || data == NULL ||
	    frame->len > ((uint8_t)CAN_MAX_FRAME_BYTELEN)) {
		return CAN_ERR_PARAMETER;
	}

	if (can_check_transmitter_free(can) == false)
		return CAN_ERR_TBUF_FULL;

	identifier.ID = frame->use_ext_id ? frame->id :
					    frame->id << CAN_IDE_SHIFT;
	can_linux_tbuf_identifier_write(can, &identifier);

	tbuf_format.DLC = can_dlc_code(frame->len);
	if (frame->use_ext_id)
		tbuf_format.IDE = 1;

	if (frame->is_remote_frame)
		tbuf_format.RMF = 1;

	can_linux_tbuf_format_write(can, &tbuf_format);
	
	tbuf_type.HANDLE = handle;
	tbuf_type.SDT = 0;
    tbuf_type.VCID = 0;

	can_linux_tbuf_type_write(can, &tbuf_type);

	can_tbuf_payload(can, data, frame->len);

	return perform_transmit_autoaction(can, auto_action);
}

/**
 * @brief start the transmit of CAN FD frame
 */
can_linux_error_code_t can_fd_transmit(volatile CAN_CTRL_t *can,
				       can_fd_frame_info_t *frame,
				       uint32_t *data,
				       can_autoaction_type_t auto_action, 
					   uint32_t handle)
{
	CAN_CTRL_TBUF_IDENTIFIER_t identifier = { 0 };
	CAN_CTRL_TBUF_FORMAT_t tbuf_format = { 0 };
	CAN_CTRL_TBUF_TYPE_t tbuf_type  = { 0 };

	if (can == NULL || frame == NULL || data == NULL ||
	    frame->len > (CAN_MAX_FD_FRAME_BYTELEN)) {
		return CAN_ERR_PARAMETER;
	}

	if (can_check_transmitter_free(can) == false)
		return CAN_ERR_TBUF_FULL;

	identifier.ID = frame->use_ext_id ? frame->id :
					    frame->id << CAN_IDE_SHIFT;
	can_linux_tbuf_identifier_write(can, &identifier);

	tbuf_format.FDF = 1;
	tbuf_format.DLC = can_dlc_code(frame->len);
	if (frame->use_ext_id)
		tbuf_format.IDE = 1;

	if ((frame->flags & (uint8_t)CAN_FD_FLAG_BRS) == (uint8_t)1)
		tbuf_format.BRS = 1;

	can_linux_tbuf_format_write(can, &tbuf_format);

	tbuf_type.HANDLE = handle;
    tbuf_type.SDT = 0;
    tbuf_type.VCID = 0;

	can_linux_tbuf_type_write(can, &tbuf_type);

	can_tbuf_payload(can, data, frame->len);

	return perform_transmit_autoaction(can, auto_action);
}

/**
 * @brief start the transmit of CAN XL frame
 */
can_linux_error_code_t can_xl_transmit(volatile CAN_CTRL_t *can,
				       can_xl_frame_info_t *frame,
				       uint32_t *data,
				       can_autoaction_type_t auto_action,
					   uint32_t handle)
{
	CAN_CTRL_TBUF_IDENTIFIER_t identifier = { 0 };	
	CAN_CTRL_TBUF_FORMAT_t tbuf_format = { 0 };
	CAN_CTRL_TBUF_TYPE_t tbuf_type;

	if (can == NULL || frame == NULL || data == NULL ||
	    frame->len > (CAN_MAX_XL_FRAME_BYTELEN)) {
		return CAN_ERR_PARAMETER;
	}

	if (can_check_transmitter_free(can) == false)
		return CAN_ERR_TBUF_FULL;

	identifier.ID = frame->prio << CAN_IDE_SHIFT;
	can_linux_tbuf_identifier_write(can, &identifier);

	tbuf_format.FDF = 1;
	tbuf_format.XLF = 1;
	tbuf_format.DLC = frame->len - 1u;
	if (frame->sec == (uint8_t)1)
		tbuf_format.SEC = 1;

	can_linux_tbuf_format_write(can, &tbuf_format);

	tbuf_type.HANDLE = handle;
    tbuf_type.SDT = frame->sdu_type;
    tbuf_type.VCID = frame->vcid;

	can_linux_tbuf_type_write(can, &tbuf_type);

	can_linux_tbuf_acceptance_field_write(can, &frame->acceptance_field);

	can_tbuf_payload(can, data, frame->len);

	return perform_transmit_autoaction(can, auto_action);
}

/**
 * @brief manually trigger the transmit of CAN frame
 */
can_linux_error_code_t can_transmit_buffer_trigger(volatile CAN_CTRL_t *can,
						   can_tbuf_trigger_t trigger)
{
	CAN_CTRL_TCMD_t tcmd;

	if (can == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_tcmd_read(can, &tcmd);

	if (trigger == TBUF_TRIGGER_PTB_ONE)
		tcmd.TPE = 1;
	else if (trigger == TBUF_TRIGGER_STB_ONE)
		tcmd.TSONE = 1;
	else if (trigger == TBUF_TRIGGER_STB_ALL)
		tcmd.TSALL = 1;
	else
		return CAN_ERR_PARAMETER;

	can_linux_tcmd_write(can, &tcmd);

	return CAN_NO_ERROR;
}

/**
 * @brief select the transmission buffer (primary/secondary)
 */
can_linux_error_code_t can_transmit_buffer_select(volatile CAN_CTRL_t *can,
						  can_tbuf_sel_t tbsel)
{
	CAN_CTRL_TCMD_t tcmd;

	if (can == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_tcmd_read(can, &tcmd);
	tcmd.TBSEL = (tbsel == TBUF_SEL_PTB) ? (uint8_t)0 : (uint8_t)1;
	can_linux_tcmd_write(can, &tcmd);

	return CAN_NO_ERROR;
}

/**
 * @brief set the loopback mode
 */
can_linux_error_code_t can_loopback_set(volatile CAN_CTRL_t *can,
					can_loopback_mode_t mode)
{
	CAN_CTRL_CFG_STAT_t cfg_stat;
	bool is_valid_loopbackmode;

	if (can == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_cfg_stat_read(can, &cfg_stat);
	cfg_stat.LBMI = 0;
	cfg_stat.LBME = 0;

	switch (mode) {
	case CAN_LOOPBACK_EXTERNAL: {
		cfg_stat.LBME = 1;
		is_valid_loopbackmode = true;
		break;
	}
	case CAN_LOOPBACK_INTERNAL: {
		cfg_stat.LBMI = 1;
		is_valid_loopbackmode = true;
		break;
	}
	case CAN_LOOPBACK_DISABLE: {
		cfg_stat.LBME = 0;
		cfg_stat.LBMI = 0;
		is_valid_loopbackmode = true;
		break;
	}
	default: {
		is_valid_loopbackmode = false;
		break;
	}
	}

	if (!is_valid_loopbackmode)
		return CAN_ERR_PARAMETER;


	can_linux_cfg_stat_write(can, &cfg_stat);

	return CAN_NO_ERROR;
}

/**
 * @brief read the current interrupt flags
 */
can_linux_error_code_t can_interrupt_get(volatile CAN_CTRL_t *can,
					 uint32_t *interrupt_flags)
{
	CAN_CTRL_INTF_t intf;

	if (can == NULL || interrupt_flags == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_intf_read(can, &intf);
	*interrupt_flags = intf.VALUE & (uint32_t)CAN_INTERRUPT_MASK;

	return CAN_NO_ERROR;
}

/**
 * @brief clear interrupt flags
 */
can_linux_error_code_t can_interrupt_clear(volatile CAN_CTRL_t *can,
					   uint32_t mask)
{
	CAN_CTRL_INTF_t intf;

	if (can == NULL || (mask & (uint32_t)~CAN_INTERRUPT_MASK) != 0u)
		return CAN_ERR_PARAMETER;

	intf.VALUE = mask & (uint32_t)CAN_INTERRUPT_MASK;
	can_linux_intf_write(can, &intf);

	return CAN_NO_ERROR;
}

/**
 * @brief enable interrupt
 */
can_linux_error_code_t can_interrupt_enable(volatile CAN_CTRL_t *can,
					    uint32_t mask)
{
	CAN_CTRL_INTE_t inte;

	if (can == NULL || (mask & (uint32_t)~CAN_INTERRUPT_SET_MASK) != 0u)
		return CAN_ERR_PARAMETER;

	can_linux_inte_read(can, &inte);
	inte.VALUE |= mask;
	can_linux_inte_write(can, &inte);

	return CAN_NO_ERROR;
}

/**
 * @brief disable interrupt
 */
can_linux_error_code_t can_interrupt_disable(volatile CAN_CTRL_t *can,
					     uint32_t mask)
{
	CAN_CTRL_INTE_t inte;

	if (can == NULL || (mask & (uint32_t)~CAN_INTERRUPT_SET_MASK) != 0u)
		return CAN_ERR_PARAMETER;

	can_linux_inte_read(can, &inte);
	inte.VALUE &= ~mask;
	can_linux_inte_write(can, &inte);

	return CAN_NO_ERROR;
}

/**
 * @brief determine frame type from receive buffer
 */
can_linux_error_code_t can_determine_rbuf_frame_type(volatile CAN_CTRL_t *can,
						     can_frame_type_t *type)
{
	CAN_CTRL_RCTRL_t rctrl;
	CAN_CTRL_RBUF_FORMAT_t format;

	if (can == NULL || type == NULL)
		return CAN_ERR_PARAMETER;

	*type = CAN_FRAME_CLASSIC;
	
	can_linux_rctrl_read(can, &rctrl);
	if (rctrl.RSTAT == (uint8_t)0)
		return CAN_ERR_NO_DATA;

	can_linux_rbuf_format_read(can, &format);

	if (format.FDF == (uint32_t)1) {
		if (format.XLF == (uint32_t)1)
			*type = CAN_FRAME_XL;
		else
			*type = CAN_FRAME_FD;
	}

	return CAN_NO_ERROR;
}

/**
 * @brief receive classic CAN frame
 */
can_linux_error_code_t can_receive_data(volatile CAN_CTRL_t *can,
					can_frame_info_t *frame, uint32_t *data,
					uint8_t buf_len)
{
	CAN_CTRL_RCTRL_t rctrl;
	CAN_CTRL_RBUF_IDENTIFIER_t identifier;
	CAN_CTRL_RBUF_FORMAT_t format;

	if (can == NULL || frame == NULL || data == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_rctrl_read(can, &rctrl);
	if (rctrl.RSTAT == 0u)
		return CAN_ERR_NO_DATA;

	can_linux_rbuf_identifier_read(can, &identifier);
	can_linux_rbuf_format_read(can, &format);

	frame->id = (format.IDE != 0u) ? identifier.ID :
					 identifier.ID >> CAN_IDE_SHIFT;
	frame->use_ext_id = (format.IDE != 0u);
	frame->is_remote_frame = (format.RMF != 0u);
	frame->len = can_dlc_decode(format.DLC);

	if (frame->len > buf_len) {
		frame->len = buf_len;
		can_rbuf_payload(can, data, frame->len);
		return CAN_ERR_MEM_LIMIT;
	}

	can_rbuf_payload(can, data, frame->len);

	return CAN_NO_ERROR;
}

/**
 * @brief receive CAN FD frame
 */
can_linux_error_code_t can_fd_receive_data(volatile CAN_CTRL_t *can,
					   can_fd_frame_info_t *frame,
					   uint32_t *data, uint8_t buf_len)
{
	CAN_CTRL_RCTRL_t rctrl;
	CAN_CTRL_RBUF_IDENTIFIER_t identifier;
	CAN_CTRL_RBUF_FORMAT_t format;

	if (can == NULL || frame == NULL || data == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_rctrl_read(can, &rctrl);
	if (rctrl.RSTAT == 0u)
		return CAN_ERR_NO_DATA;

	can_linux_rbuf_identifier_read(can, &identifier);
	can_linux_rbuf_format_read(can, &format);

	frame->id = (format.IDE != 0u) ? identifier.ID :
					 identifier.ID >> CAN_IDE_SHIFT;
	frame->use_ext_id = (format.IDE == 1u);
	frame->len = can_dlc_decode(format.DLC);

	frame->flags = 0u;
	if (format.BRS == 1u)
		frame->flags |= (uint8_t)CAN_FD_FLAG_BRS;

	if (frame->len > buf_len) {
		frame->len = buf_len;
		can_rbuf_payload(can, data, frame->len);
		return CAN_ERR_MEM_LIMIT;
	}

	can_rbuf_payload(can, data, frame->len);

	return CAN_NO_ERROR;
}

/**
 * @brief receive CAN XL frame
 */
can_linux_error_code_t can_xl_receive_data(volatile CAN_CTRL_t *can,
					   can_xl_frame_info_t *frame,
					   uint32_t *data, uint32_t buf_len)
{
	CAN_CTRL_RCTRL_t rctrl;
	CAN_CTRL_RBUF_IDENTIFIER_t identifier;
	CAN_CTRL_RBUF_FORMAT_t format;

	if (can == NULL || frame == NULL || data == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_rctrl_read(can, &rctrl);
	if (rctrl.RSTAT == (uint8_t)0)
		return CAN_ERR_NO_DATA;

	can_linux_rbuf_identifier_read(can, &identifier);
	can_linux_rbuf_format_read(can, &format);

	frame->prio = identifier.ID >> (uint32_t)CAN_IDE_SHIFT;
	frame->sec = (uint8_t)format.SEC;
	frame->len = format.DLC + (uint32_t)1;

	can_linux_rbuf_acceptance_field_read(can, &frame->acceptance_field);

	if (frame->len > buf_len) {
		frame->len = buf_len;
		can_rbuf_payload(can, data, frame->len);
		return CAN_ERR_MEM_LIMIT;
	}

	can_rbuf_payload(can, data, frame->len);

	return CAN_NO_ERROR;
}

/**
 * @brief finish the receive frame, switch internally to next receive buffer slot
 */
void can_receive_finish(volatile CAN_CTRL_t *can)
{
	CAN_CTRL_RCTRL_t rctrl;

	if (can == NULL)
		return;

	can_linux_rctrl_read(can, &rctrl);
	rctrl.RREL = 1;
	can_linux_rctrl_write(can, &rctrl);
}

/**
 * @brief set acceptance filter settings
 */
can_linux_error_code_t can_acceptance_filter_set(volatile CAN_CTRL_t *can,
						 uint8_t idx,
						 can_acceptance_filter_t *code,
						 can_acceptance_filter_t *mask)
{
	CAN_CTRL_CFG_STAT_t cfg_stat;
	CAN_CTRL_ACFCTRL_t acfctrl = { 0 };
	CAN_CTRL_ACFC_IDENTIFIER_t acfc_identifier;
	CAN_CTRL_ACFC_FORMAT_t acfc_format;
	CAN_CTRL_ACFM_IDENTIFIER_t acfm_identifier;
	CAN_CTRL_ACFM_FORMAT_t acfm_format;
	CAN_CTRL_ACFM_TYPE_t acfm_type;
	uint32_t acfm_acceptance_field;

	if (can == NULL || code == NULL || mask == NULL || idx > (uint8_t)15)
		return CAN_ERR_PARAMETER;

	can_linux_cfg_stat_read(can, &cfg_stat);
	if (cfg_stat.RESET == (uint8_t)0)
		return CAN_ERR_RESETSTATE;

	acfctrl.ACFADR = idx;
	can_linux_acfctrl_write(can, &acfctrl);
	can_linux_acfctrl_read(can, &acfctrl);
	if (acfctrl.ACFADR != idx)
		return CAN_ERR_HW_LIMIT;

	can_linux_acfc_identifier_read(can, &acfc_identifier);
	can_linux_acfc_format_read(can, &acfc_format);

	acfc_identifier.ID = code->use_ext_id ? code->id :
						code->id << CAN_IDE_SHIFT;
	acfc_format.IDE = code->use_ext_id ? 1u : 0u;

	can_linux_acfc_identifier_write(can, &acfc_identifier);
	can_linux_acfc_format_write(can, &acfc_format);

	can_linux_acfm_identifier_read(can, &acfm_identifier);

	acfm_identifier.ID =
		~(code->use_ext_id ? mask->id : mask->id << CAN_IDE_SHIFT);

	acfm_format.VALUE = 0xFFFFFFFFu;
	acfm_format.IDE = mask->use_ext_id ? 1u : 0u;
	acfm_type.VALUE = 0xFFFFFFFFu;
	acfm_acceptance_field = 0xFFFFFFFFu;

	can_linux_acfm_identifier_write(can, &acfm_identifier);
	can_linux_acfm_format_write(can, &acfm_format);
	can_linux_acfm_type_write(can, &acfm_type);
	can_linux_acfm_acceptance_field_write(can, &acfm_acceptance_field);

	return CAN_NO_ERROR;
}

/**
 * @brief enable/disable acceptance filter
 */
can_linux_error_code_t can_acceptance_filter_enable(volatile CAN_CTRL_t *can,
						    uint8_t idx, bool enable)
{
	if (can == NULL || idx > (uint8_t)15)
		return CAN_ERR_PARAMETER;

	if (idx < (uint8_t)8) {
		CAN_CTRL_ACF_EN_0_t en0;

		can_linux_acf_en_0_read(can, &en0);
		if (enable)
			en0.VALUE |= ((uint8_t)1 << idx);
		else
			en0.VALUE &= ~((uint8_t)1 << idx);

		can_linux_acf_en_0_write(can, &en0);
	} else {
		CAN_CTRL_ACF_EN_1_t en1;

		can_linux_acf_en_1_read(can, &en1);
		if (enable)
			en1.VALUE |= ((uint8_t)1 << (idx - (uint8_t)8));
		else
			en1.VALUE &= ~((uint8_t)1 << (idx - (uint8_t)8));

		can_linux_acf_en_1_write(can, &en1);
	}

	return CAN_NO_ERROR;
}

/**
 * @brief enable/disable transceiver mode switching for CAN XL transceiver
 */
can_linux_error_code_t
can_transceiver_mode_switching_enable(volatile CAN_CTRL_t *can, bool enable)
{
	CAN_CTRL_CFG_STAT_t cfg_stat;
	CAN_CTRL_NODECFG_t nodecfg;

	if (can == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_cfg_stat_read(can, &cfg_stat);
	if (cfg_stat.RESET == (uint8_t)0)
		return CAN_ERR_RESETSTATE;

	can_linux_nodecfg_read(can, &nodecfg);
	nodecfg.TMSE = enable ? (uint8_t)1 : (uint8_t)0;
	if (nodecfg.TMSE == 1u)
		nodecfg.CES = 0;

	can_linux_nodecfg_write(can, &nodecfg);

	return CAN_NO_ERROR;
}

/**
 * @brief enable/disable transceiver error signaling for CAN XL transceiver
 */
can_linux_error_code_t
can_transceiver_error_signaling_enable(volatile CAN_CTRL_t *can, bool enable)
{
	CAN_CTRL_CFG_STAT_t cfg_stat;
	CAN_CTRL_NODECFG_t nodecfg;

	if (can == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_cfg_stat_read(can, &cfg_stat);
	if (cfg_stat.RESET == (uint8_t)0)
		return CAN_ERR_RESETSTATE;

	can_linux_nodecfg_read(can, &nodecfg);
	if (nodecfg.TMSE == 0u)
		nodecfg.CES = enable ? (uint8_t)1 : (uint8_t)0;

	can_linux_nodecfg_write(can, &nodecfg);

	return CAN_NO_ERROR;
}
/**
 * @brief configure CAN PWM settings
 * @param can   pointer to CAN core register map
 * @param pwm   pointer to pwm settings
 *
 * @return can_linux_error_code_t
 */
static can_linux_error_code_t can_pwm_config(volatile CAN_CTRL_t *can,
					     can_pwm_t *pwm)
{
	CAN_CTRL_CFG_STAT_t cfg_stat;
	CAN_CTRL_PWM_CFG_t pwm_cfg = { 0 };

	can_linux_cfg_stat_read(can, &cfg_stat);
	if (cfg_stat.RESET == (uint8_t)0)
		return CAN_ERR_RESETSTATE;

	pwm_cfg.PWMO = pwm->PWMO;
	pwm_cfg.PWMS = pwm->PWMS - 1u;
	pwm_cfg.PWML = pwm->PWML - 1u;

	can_linux_pwm_cfg_write(can, &pwm_cfg);

	return CAN_NO_ERROR;
}

/*!
 * @brief get the rolling counter for transmitted CAN frames
 *
 * @return uint8_t counter value
 */
can_linux_error_code_t can_get_HANDLEcnt(volatile CAN_CTRL_t *can,
					 uint8_t *LLC_Counter)
{
	CAN_CTRL_TBUF_TYPE_t tbuf_type;

	if (can == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_tbuf_type_read(can, &tbuf_type);
	*LLC_Counter = (uint8_t)tbuf_type.HANDLE;

	return CAN_NO_ERROR;
}

/*!
 * @brief calculates and sets fitting pwm config based on current bittimings
 *
 * @param can    pointer to CAN core register map
 * @return          can_linux_error_code_t
 */
static can_linux_error_code_t determine_pwm_values(volatile CAN_CTRL_t *can,
						   uint32_t cclk)
{
	const uint32_t max_pwm_symbol_length_ns = 200;

	CAN_CTRL_AC_TIME_t ac_time;
	CAN_CTRL_XL_TIME_t xl_time;
	CAN_CTRL_PRESC_t presc;
	u64 tmp;
	bool found;
	uint16_t prescaler;
	uint32_t ac_tq;
	uint32_t xl_tq;
	uint32_t mtq_per_arbitration_bit;
	uint32_t mtq_per_databit;
	uint32_t databit_len_ns;
	uint32_t pwm_sym;

	can_linux_ac_time_read(can, &ac_time);
	can_linux_xl_time_read(can, &xl_time);
	can_linux_presc_read(can, &presc);

	prescaler = ((uint16_t)presc.VALUE + 1u);

	if (prescaler == 0u)
		return CAN_ERR_PARAMETER;

	// register representation and sync seg must be taken into account, so we add 3 to get the actual quanta
	ac_tq = (ac_time.AC_SEG_1 + ac_time.AC_SEG_2 + 3u);
	xl_tq = xl_time.XL_SEG_1 + xl_time.XL_SEG_2 + 3u;
	mtq_per_arbitration_bit = ac_tq / prescaler;
	mtq_per_databit = xl_tq / prescaler;

	// prevent 32Bit-Overflow in databit_len_ns
	if (cclk < 120u)
		return CAN_ERR_PARAMETER;

	tmp = (u64)1000000000ULL * xl_tq;
	do_div(tmp, (u32)cclk);
	databit_len_ns = (u32)tmp;
	pwm_sym = ((databit_len_ns + (max_pwm_symbol_length_ns / 2u)) /
			    max_pwm_symbol_length_ns);

	if ((pwm_sym == 0u) || (mtq_per_databit == 0u))
		return CAN_ERR_PARAMETER;

	found = false;

	do {
		if (mtq_per_databit % pwm_sym != 0u)
			pwm_sym++;
		else
			found = true;

	} while (pwm_sym < mtq_per_databit && !found);

	if (found && (pwm_sym != 0u)) {
		uint32_t mtq_per_pwm_symbol = mtq_per_databit / pwm_sym;
		can_pwm_t pwm;

		pwm.PWMS = (uint32_t)((mtq_per_pwm_symbol + 3u) / 4u);
		pwm.PWML = mtq_per_pwm_symbol - pwm.PWMS;
		pwm.PWMO = mtq_per_arbitration_bit % mtq_per_databit;
		return can_pwm_config(can, &pwm);
	}

	return CAN_ERR_PARAMETER;
}

/**
 * @brief set the bittiming for CAN protocol
 */
can_linux_error_code_t can_bittiming_set(volatile CAN_CTRL_t *can,
					 can_bittiming_type_t type,
					 can_bittiming_t *timing)
{
	CAN_CTRL_CFG_STAT_t cfg_stat;
	CAN_CTRL_PRESC_t prescaler;
	uint8_t tdc = 0;

	if (can == NULL || timing == NULL)
		return CAN_ERR_PARAMETER;

	can_linux_cfg_stat_read(can, &cfg_stat);
	if (cfg_stat.RESET == (uint8_t)0)
		return CAN_ERR_RESETSTATE;

	if (timing->tdc_enable == true) {
		tdc = (timing->tdc != (uint8_t)0) ?
			      timing->tdc :
			      (uint8_t)(((1u + timing->prop_seg +
					  timing->phase_seg1) *
						 timing->prescaler +
					 1u) /
					timing->prescaler);
	}

	if (type == CAN_BITTIMING_AC) {
		CAN_CTRL_AC_TIME_t ac_time = { 0 };

		ac_time.AC_SEG_1 = timing->phase_seg1 + timing->prop_seg - 1u;
		ac_time.AC_SEG_2 = timing->phase_seg2 - 1u;
		ac_time.AC_SJW = timing->sjw - 1u;

		can_linux_ac_time_write(can, &ac_time);

		prescaler.PRESC = (uint8_t)(timing->prescaler - 1u);
		can_linux_presc_write(can, &prescaler);
	} else if (type == CAN_BITTIMING_FD) {
		CAN_CTRL_FD_TIME_t fd_time = { 0 };

		fd_time.FD_SEG_1 = timing->phase_seg1 + timing->prop_seg - 1u;
		fd_time.FD_SEG_2 = timing->phase_seg2 - 1u;
		fd_time.FD_SJW = timing->sjw - 1u;

		can_linux_fd_time_write(can, &fd_time);
		can_linux_fd_tdc_write(can, &tdc);
	} else if (type == CAN_BITTIMING_XL) {
		CAN_CTRL_XL_TIME_t xl_time = { 0 };

		xl_time.XL_SEG_1 = timing->phase_seg1 + timing->prop_seg - 1u;
		xl_time.XL_SEG_2 = timing->phase_seg2 - 1u;
		xl_time.XL_SJW = timing->sjw - 1u;

		can_linux_xl_time_write(can, &xl_time);
		can_linux_xl_tdc_write(can, &tdc);
	} else {
		return CAN_ERR_PARAMETER;
	}

	if (type != CAN_BITTIMING_XL)
		return CAN_NO_ERROR;

	return determine_pwm_values(can, timing->cclk);
}

/*!
 * @brief select the next slot in the transmit buffer
 */
can_linux_error_code_t can_select_next_tbuf_slot(volatile CAN_CTRL_t *can)
{
	CAN_CTRL_TCTRL_t tctrl;

	if (!can)
		return CAN_ERR_PARAMETER;

	can_linux_tctrl_read(can, &tctrl);
	tctrl.TSNEXT = 1;
	can_linux_tctrl_write(can, &tctrl);
	return CAN_NO_ERROR;
}