/*
 * Copyright (c) 2022 Henrik Brix Andersen <henrik@brixandersen.dk>
 * Copyright (c) 2022 Martin Jäger <martin@libre.solar>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT espressif_esp32_twai

#include <zephyr/drivers/can/can_sja1000.h>

#include <zephyr/drivers/can.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/interrupt_controller/intc_esp32.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/logging/log.h>

#include <soc.h>

LOG_MODULE_REGISTER(can_esp32_twai, CONFIG_CAN_LOG_LEVEL);

/*
 * Newer ESP32-series MCUs like ESP32-C3 and ESP32-S2 have some slightly different registers
 * compared to the original ESP32, which is fully compatible with the SJA1000 controller.
 *
 * The names with TWAI_ prefixes from Espressif reference manuals are used for these incompatible
 * registers.
 */
#ifndef CONFIG_SOC_SERIES_ESP32

/* TWAI_BUS_TIMING_0_REG is incompatible with CAN_SJA1000_BTR0 */
#define TWAI_BUS_TIMING_0_REG           (6U)
#define TWAI_BAUD_PRESC_MASK            GENMASK(12, 0)
#define TWAI_SYNC_JUMP_WIDTH_MASK       GENMASK(15, 14)
#define TWAI_BAUD_PRESC_PREP(brp)	FIELD_PREP(TWAI_BAUD_PRESC_MASK, brp)
#define TWAI_SYNC_JUMP_WIDTH_PREP(sjw)	FIELD_PREP(TWAI_SYNC_JUMP_WIDTH_MASK, sjw)

/*
 * TWAI_BUS_TIMING_1_REG is compatible with CAN_SJA1000_BTR1, but needed here for the custom
 * set_timing() function.
 */
#define TWAI_BUS_TIMING_1_REG           (7U)
#define TWAI_TIME_SEG1_MASK             GENMASK(3, 0)
#define TWAI_TIME_SEG2_MASK             GENMASK(6, 4)
#define TWAI_TIME_SAMP                  BIT(7)
#define TWAI_TIME_SEG1_PREP(seg1)       FIELD_PREP(TWAI_TIME_SEG1_MASK, seg1)
#define TWAI_TIME_SEG2_PREP(seg2)       FIELD_PREP(TWAI_TIME_SEG2_MASK, seg2)

/* TWAI_CLOCK_DIVIDER_REG is incompatible with CAN_SJA1000_CDR */
#define TWAI_CLOCK_DIVIDER_REG          (31U)
#define TWAI_CD_MASK			GENMASK(7, 0)
#define TWAI_CLOCK_OFF			BIT(8)

/*
 * Further incompatible registers currently not used by the driver:
 * - TWAI_STATUS_REG has new bit 8: TWAI_MISS_ST
 * - TWAI_INT_RAW_REG has new bit 8: TWAI_BUS_STATE_INT_ST
 * - TWAI_INT_ENA_REG has new bit 8: TWAI_BUS_STATE_INT_ENA
 */
#else

/* Redefinitions of the SJA1000 CDR bits to simplify driver config */
#define TWAI_CD_MASK			GENMASK(2, 0)
#define TWAI_CLOCK_OFF			BIT(3)

#endif /* !CONFIG_SOC_SERIES_ESP32 */

struct can_esp32_twai_config {
	mm_reg_t base;
	const struct pinctrl_dev_config *pcfg;
	const struct device *clock_dev;
	const clock_control_subsys_t clock_subsys;
	int irq_source;
	int irq_priority;
	int irq_flags;
#ifndef CONFIG_SOC_SERIES_ESP32
	/* 32-bit variant of output clock divider register required for non-ESP32 MCUs */
	uint32_t cdr32;
#endif /* !CONFIG_SOC_SERIES_ESP32 */
};

static uint8_t can_esp32_twai_read_reg(const struct device *dev, uint8_t reg)
{
	const struct can_sja1000_config *sja1000_config = dev->config;
	const struct can_esp32_twai_config *twai_config = sja1000_config->custom;
	mm_reg_t addr = twai_config->base + reg * sizeof(uint32_t);

	return sys_read32(addr) & 0xFF;
}

static void can_esp32_twai_write_reg(const struct device *dev, uint8_t reg, uint8_t val)
{
	const struct can_sja1000_config *sja1000_config = dev->config;
	const struct can_esp32_twai_config *twai_config = sja1000_config->custom;
	mm_reg_t addr = twai_config->base + reg * sizeof(uint32_t);

	sys_write32(val & 0xFF, addr);
}

#ifndef CONFIG_SOC_SERIES_ESP32

/*
 * Required for newer ESP32-series MCUs which violate the original SJA1000 8-bit register size.
 */
static void can_esp32_twai_write_reg32(const struct device *dev, uint8_t reg, uint32_t val)
{
	const struct can_sja1000_config *sja1000_config = dev->config;
	const struct can_esp32_twai_config *twai_config = sja1000_config->custom;
	mm_reg_t addr = twai_config->base + reg * sizeof(uint32_t);

	sys_write32(val, addr);
}

/*
 * Custom implementation instead of can_sja1000_set_timing required because TWAI_BUS_TIMING_0_REG
 * is incompatible with CAN_SJA1000_BTR0.
 */
static int can_esp32_twai_set_timing(const struct device *dev, const struct can_timing *timing)
{
	struct can_sja1000_data *data = dev->data;
	uint8_t btr0;
	uint8_t btr1;

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

	k_mutex_lock(&data->mod_lock, K_FOREVER);

	btr0 = TWAI_BAUD_PRESC_PREP(timing->prescaler - 1) |
	       TWAI_SYNC_JUMP_WIDTH_PREP(timing->sjw - 1);
	btr1 = TWAI_TIME_SEG1_PREP(timing->phase_seg1 - 1) |
	       TWAI_TIME_SEG2_PREP(timing->phase_seg2 - 1);

	if ((data->common.mode & CAN_MODE_3_SAMPLES) != 0) {
		btr1 |= TWAI_TIME_SAMP;
	}

	can_esp32_twai_write_reg32(dev, TWAI_BUS_TIMING_0_REG, btr0);
	can_esp32_twai_write_reg32(dev, TWAI_BUS_TIMING_1_REG, btr1);

	k_mutex_unlock(&data->mod_lock);

	return 0;
}

#endif /* !CONFIG_SOC_SERIES_ESP32 */

static int can_esp32_twai_get_core_clock(const struct device *dev, uint32_t *rate)
{
	ARG_UNUSED(dev);

	/* The internal clock operates at half of the oscillator frequency */
	*rate = APB_CLK_FREQ / 2;

	return 0;
}

static void IRAM_ATTR can_esp32_twai_isr(void *arg)
{
	const struct device *dev = (const struct device *)arg;

	can_sja1000_isr(dev);
}

static int can_esp32_twai_init(const struct device *dev)
{
	const struct can_sja1000_config *sja1000_config = dev->config;
	const struct can_esp32_twai_config *twai_config = sja1000_config->custom;
	int err;

	if (!device_is_ready(twai_config->clock_dev)) {
		LOG_ERR("clock control device not ready");
		return -ENODEV;
	}

	err = pinctrl_apply_state(twai_config->pcfg, PINCTRL_STATE_DEFAULT);
	if (err != 0) {
		LOG_ERR("failed to configure TWAI pins (err %d)", err);
		return err;
	}

	err = clock_control_on(twai_config->clock_dev, twai_config->clock_subsys);
	if (err != 0) {
		LOG_ERR("failed to enable CAN clock (err %d)", err);
		return err;
	}

	err = can_sja1000_init(dev);
	if (err != 0) {
		LOG_ERR("failed to initialize controller (err %d)", err);
		return err;
	}

#ifndef CONFIG_SOC_SERIES_ESP32
	/*
	 * TWAI_CLOCK_DIVIDER_REG is incompatible with CAN_SJA1000_CDR for non-ESP32 MCUs
	 *   - TWAI_CD has length of 8 bits instead of 3 bits
	 *   - TWAI_CLOCK_OFF at BIT(8) instead of BIT(3)
	 *   - TWAI_EXT_MODE bit missing (always "extended" = PeliCAN mode)
	 *
	 * Overwrite with 32-bit register variant configured via devicetree.
	 */
	can_esp32_twai_write_reg32(dev, TWAI_CLOCK_DIVIDER_REG, twai_config->cdr32);
#endif /* !CONFIG_SOC_SERIES_ESP32 */

	err = esp_intr_alloc(twai_config->irq_source,
			ESP_PRIO_TO_FLAGS(twai_config->irq_priority) |
			ESP_INT_FLAGS_CHECK(twai_config->irq_flags) | ESP_INTR_FLAG_IRAM,
			can_esp32_twai_isr, (void *)dev, NULL);

	if (err != 0) {
		LOG_ERR("could not allocate interrupt (err %d)", err);
	}

	return err;
}

DEVICE_API(can, can_esp32_twai_driver_api) = {
	.get_capabilities = can_sja1000_get_capabilities,
	.start = can_sja1000_start,
	.stop = can_sja1000_stop,
	.set_mode = can_sja1000_set_mode,
#ifdef CONFIG_SOC_SERIES_ESP32
	.set_timing = can_sja1000_set_timing,
#else
	.set_timing = can_esp32_twai_set_timing,
#endif /* CONFIG_SOC_SERIES_ESP32 */
	.send = can_sja1000_send,
	.add_rx_filter = can_sja1000_add_rx_filter,
	.remove_rx_filter = can_sja1000_remove_rx_filter,
	.get_state = can_sja1000_get_state,
	.set_state_change_callback = can_sja1000_set_state_change_callback,
	.get_core_clock = can_esp32_twai_get_core_clock,
	.get_max_filters = can_sja1000_get_max_filters,
#ifdef CONFIG_CAN_MANUAL_RECOVERY_MODE
	.recover = can_sja1000_recover,
#endif /* CONFIG_CAN_MANUAL_RECOVERY_MODE */
	.timing_min = CAN_SJA1000_TIMING_MIN_INITIALIZER,
#ifdef CONFIG_SOC_SERIES_ESP32
	.timing_max = CAN_SJA1000_TIMING_MAX_INITIALIZER,
#else
	/* larger prescaler allowed for newer ESP32-series MCUs */
	.timing_max = {
		.sjw = 0x4,
		.prop_seg = 0x0,
		.phase_seg1 = 0x10,
		.phase_seg2 = 0x8,
		.prescaler = 0x2000,
	}
#endif /* CONFIG_SOC_SERIES_ESP32 */
};

#ifdef CONFIG_SOC_SERIES_ESP32
#define TWAI_CLKOUT_DIVIDER_MAX (14)
#define TWAI_CDR32_INIT(inst)
#else
#define TWAI_CLKOUT_DIVIDER_MAX (490)
#define TWAI_CDR32_INIT(inst) .cdr32 = CAN_ESP32_TWAI_DT_CDR_INST_GET(inst)
#endif /* CONFIG_SOC_SERIES_ESP32 */

#define CAN_ESP32_TWAI_ASSERT_CLKOUT_DIVIDER(inst)                                                 \
	BUILD_ASSERT(COND_CODE_0(DT_INST_NODE_HAS_PROP(inst, clkout_divider), (1),                 \
		(DT_INST_PROP(inst, clkout_divider) == 1 ||                                        \
		(DT_INST_PROP(inst, clkout_divider) % 2 == 0 &&                                    \
		DT_INST_PROP(inst, clkout_divider) / 2 <= TWAI_CLKOUT_DIVIDER_MAX))),              \
		"TWAI clkout-divider from dts invalid")

#define CAN_ESP32_TWAI_DT_CDR_INST_GET(inst)                                                       \
	COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, clkout_divider),                                   \
		    COND_CODE_1(DT_INST_PROP(inst, clkout_divider) == 1, (TWAI_CD_MASK),           \
				((DT_INST_PROP(inst, clkout_divider)) / 2 - 1)),                   \
		    (TWAI_CLOCK_OFF))

#define CAN_ESP32_TWAI_INIT(inst)                                                                  \
	PINCTRL_DT_INST_DEFINE(inst);                                                              \
                                                                                                   \
	static const struct can_esp32_twai_config can_esp32_twai_config_##inst = {                 \
		.base = DT_INST_REG_ADDR(inst),                                                    \
		.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)),                             \
		.clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(inst, offset),         \
		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),                                      \
		.irq_source = DT_INST_IRQ_BY_IDX(inst, 0, irq),                                    \
		.irq_priority = DT_INST_IRQ_BY_IDX(inst, 0, priority),                             \
		.irq_flags = DT_INST_IRQ_BY_IDX(inst, 0, flags),                                   \
		TWAI_CDR32_INIT(inst)                                                              \
	};                                                                                         \
	CAN_ESP32_TWAI_ASSERT_CLKOUT_DIVIDER(inst);                                                \
	static const struct can_sja1000_config can_sja1000_config_##inst =                         \
		CAN_SJA1000_DT_CONFIG_INST_GET(inst, &can_esp32_twai_config_##inst,                \
					can_esp32_twai_read_reg, can_esp32_twai_write_reg,         \
					CAN_SJA1000_OCR_OCMODE_BIPHASE,                            \
					COND_CODE_0(IS_ENABLED(CONFIG_SOC_SERIES_ESP32), (0),      \
					(CAN_ESP32_TWAI_DT_CDR_INST_GET(inst))), 25000);           \
                                                                                                   \
	static struct can_sja1000_data can_sja1000_data_##inst =                                   \
		CAN_SJA1000_DATA_INITIALIZER(NULL);                                                \
                                                                                                   \
	CAN_DEVICE_DT_INST_DEFINE(inst, can_esp32_twai_init, NULL, &can_sja1000_data_##inst,       \
				  &can_sja1000_config_##inst, POST_KERNEL,                         \
				  CONFIG_CAN_INIT_PRIORITY, &can_esp32_twai_driver_api);

DT_INST_FOREACH_STATUS_OKAY(CAN_ESP32_TWAI_INIT)
