diff --git a/drivers/can/CMakeLists.txt b/drivers/can/CMakeLists.txt index 630ace7a658..c5a39471ea9 100644 --- a/drivers/can/CMakeLists.txt +++ b/drivers/can/CMakeLists.txt @@ -10,6 +10,7 @@ zephyr_library_sources_ifdef(CONFIG_CAN_MCAN can_mcan.c) zephyr_library_sources_ifdef(CONFIG_CAN_MCP2515 can_mcp2515.c) zephyr_library_sources_ifdef(CONFIG_CAN_MCUX_FLEXCAN can_mcux_flexcan.c) zephyr_library_sources_ifdef(CONFIG_CAN_SAM can_sam.c) +zephyr_library_sources_ifdef(CONFIG_CAN_SAM0 can_sam0.c) zephyr_library_sources_ifdef(CONFIG_CAN_STM32 can_stm32.c) zephyr_library_sources_ifdef(CONFIG_CAN_STM32FD can_stm32fd.c) zephyr_library_sources_ifdef(CONFIG_CAN_STM32H7 can_stm32h7.c) diff --git a/drivers/can/Kconfig b/drivers/can/Kconfig index 7c37744a3c8..c006d468ec0 100644 --- a/drivers/can/Kconfig +++ b/drivers/can/Kconfig @@ -86,6 +86,7 @@ config CAN_QEMU_IFACE_NAME configured before starting QEMU. source "drivers/can/Kconfig.sam" +source "drivers/can/Kconfig.sam0" source "drivers/can/Kconfig.stm32" source "drivers/can/Kconfig.stm32fd" source "drivers/can/Kconfig.stm32h7" diff --git a/drivers/can/Kconfig.sam0 b/drivers/can/Kconfig.sam0 new file mode 100644 index 00000000000..aca2befcca8 --- /dev/null +++ b/drivers/can/Kconfig.sam0 @@ -0,0 +1,9 @@ +# SAM CAN configuration options +# Copyright (c) 2022 Kamil Serwus +# SPDX-License-Identifier: Apache-2.0 + +config CAN_SAM0 + bool "Atmel SAM0 CAN driver" + default y + depends on DT_HAS_ATMEL_SAM0_CAN_ENABLED + select CAN_MCAN diff --git a/drivers/can/can_sam0.c b/drivers/can/can_sam0.c new file mode 100644 index 00000000000..2f9a390ecc8 --- /dev/null +++ b/drivers/can/can_sam0.c @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2022 Vestas Wind Systems A/S + * Copyright (c) 2021 Alexander Wachter + * Copyright (c) 2022 Kamil Serwus + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include "can_mcan.h" + +LOG_MODULE_REGISTER(can_sam0, CONFIG_CAN_LOG_LEVEL); + +#define DT_DRV_COMPAT atmel_sam0_can + +struct can_sam0_config { + mm_reg_t base; + void (*config_irq)(void); + const struct pinctrl_dev_config *pcfg; + volatile uint32_t *mclk; + uint32_t mclk_mask; + uint16_t gclk_core_id; + int divider; +}; + +struct can_sam0_data { + struct can_mcan_msg_sram msg_ram; +}; + +static int can_sam0_read_reg(const struct device *dev, uint16_t reg, uint32_t *val) +{ + const struct can_mcan_config *mcan_config = dev->config; + const struct can_sam0_config *sam_config = mcan_config->custom; + + return can_mcan_sys_read_reg(sam_config->base, reg, val); +} + +static int can_sam0_write_reg(const struct device *dev, uint16_t reg, uint32_t val) +{ + const struct can_mcan_config *mcan_config = dev->config; + const struct can_sam0_config *sam_config = mcan_config->custom; + uint32_t bits = 0U; + + switch (reg) { + case CAN_MCAN_ILS: + /* All interrupts are assigned to MCAN_INT0 */ + val = 0; + break; + case CAN_MCAN_ILE: + /* SAM0 has only one line to handle interrupts */ + val = CAN_MCAN_ILE_EINT0; + break; + default: + /* No field remap needed */ + bits = val; + break; + }; + + return can_mcan_sys_write_reg(sam_config->base, reg, val); +} + +void can_sam0_line_x_isr(const struct device *dev) +{ + can_mcan_line_0_isr(dev); + can_mcan_line_1_isr(dev); +} + +static int can_sam0_get_core_clock(const struct device *dev, uint32_t *rate) +{ + const struct can_mcan_config *mcan_cfg = dev->config; + const struct can_sam0_config *sam_cfg = mcan_cfg->custom; + + *rate = SOC_ATMEL_SAM0_OSC48M_FREQ_HZ / (sam_cfg->divider); + + return 0; +} + +static void can_sam0_clock_enable(const struct can_sam0_config *cfg) +{ + /* Enable the GLCK7 with DIV*/ + GCLK->GENCTRL[7].reg = GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_OSC48M) + | GCLK_GENCTRL_DIV(cfg->divider) + | GCLK_GENCTRL_GENEN; + + /* Route channel */ + GCLK->PCHCTRL[cfg->gclk_core_id].reg = GCLK_PCHCTRL_GEN_GCLK7 + | GCLK_PCHCTRL_CHEN; + + /* Enable CAN clock in MCLK */ + *cfg->mclk |= cfg->mclk_mask; +} + +static int can_sam0_init(const struct device *dev) +{ + const struct can_mcan_config *mcan_cfg = dev->config; + const struct can_sam0_config *sam_cfg = mcan_cfg->custom; + int ret; + + can_sam0_clock_enable(sam_cfg); + + ret = pinctrl_apply_state(sam_cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + LOG_ERR("failed to apply pinctrl"); + return ret; + } + + ret = can_mcan_configure_message_ram(dev, 0U); + if (ret != 0) { + LOG_ERR("failed to configure message ram"); + return ret; + } + + ret = can_mcan_init(dev); + if (ret != 0) { + LOG_ERR("failed to mcan init"); + return ret; + } + + sam_cfg->config_irq(); + + return ret; +} + +static const struct can_driver_api can_sam0_driver_api = { + .get_capabilities = can_mcan_get_capabilities, + .start = can_mcan_start, + .stop = can_mcan_stop, + .set_mode = can_mcan_set_mode, + .set_timing = can_mcan_set_timing, + .send = can_mcan_send, + .add_rx_filter = can_mcan_add_rx_filter, + .remove_rx_filter = can_mcan_remove_rx_filter, + .get_state = can_mcan_get_state, +#ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY + .recover = can_mcan_recover, +#endif /* CONFIG_CAN_AUTO_BUS_OFF_RECOVERY */ + .get_core_clock = can_sam0_get_core_clock, + .get_max_filters = can_mcan_get_max_filters, + .get_max_bitrate = can_mcan_get_max_bitrate, + .set_state_change_callback = can_mcan_set_state_change_callback, + .timing_min = { + .sjw = 0x1, + .prop_seg = 0x00, + .phase_seg1 = 0x01, + .phase_seg2 = 0x01, + .prescaler = 0x01 + }, + .timing_max = { + .sjw = 0x7f, + .prop_seg = 0x00, + .phase_seg1 = 0x100, + .phase_seg2 = 0x80, + .prescaler = 0x200 + }, +#ifdef CONFIG_CAN_FD_MODE + .set_timing_data = can_mcan_set_timing_data, + .timing_data_min = { + .sjw = 0x01, + .prop_seg = 0x00, + .phase_seg1 = 0x01, + .phase_seg2 = 0x01, + .prescaler = 0x01 + }, + .timing_data_max = { + .sjw = 0x10, + .prop_seg = 0x00, + .phase_seg1 = 0x20, + .phase_seg2 = 0x10, + .prescaler = 0x20 + } +#endif /* CONFIG_CAN_FD_MODE */ +}; + +#define CAN_SAM0_IRQ_CFG_FUNCTION(inst) \ +static void config_can_##inst##_irq(void) \ +{ \ + LOG_DBG("Enable CAN##inst## IRQ"); \ + IRQ_CONNECT(DT_INST_IRQ_BY_NAME(inst, line_0, irq), \ + DT_INST_IRQ_BY_NAME(inst, line_0, priority), can_sam0_line_x_isr, \ + DEVICE_DT_INST_GET(inst), 0); \ + irq_enable(DT_INST_IRQ_BY_NAME(inst, line_0, irq)); \ +} + +#define CAN_SAM0_CFG_INST(inst) \ + static const struct can_sam0_config can_sam0_cfg_##inst = { \ + .base = (mm_reg_t)DT_INST_REG_ADDR(inst), \ + .mclk = (volatile uint32_t *)MCLK_MASK_DT_INT_REG_ADDR(inst), \ + .mclk_mask = BIT(DT_INST_CLOCKS_CELL_BY_NAME(inst, mclk, bit)), \ + .gclk_core_id = DT_INST_CLOCKS_CELL_BY_NAME(inst, gclk, periph_ch), \ + .divider = DT_INST_PROP(inst, divider), \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ + .config_irq = config_can_##inst##_irq, \ + }; \ + \ + static const struct can_mcan_config can_mcan_cfg_##inst = \ + CAN_MCAN_DT_CONFIG_INST_GET(inst, &can_sam0_cfg_##inst, \ + can_sam0_read_reg, \ + can_sam0_write_reg); + +#define CAN_SAM0_DATA_INST(inst) \ + static struct can_sam0_data can_sam0_data_##inst; \ + \ + static struct can_mcan_data can_mcan_data_##inst = \ + CAN_MCAN_DATA_INITIALIZER(&can_sam0_data_##inst.msg_ram, \ + &can_sam0_data_##inst); \ + +#define CAN_SAM0_DEVICE_INST(inst) \ + DEVICE_DT_INST_DEFINE(inst, &can_sam0_init, NULL, \ + &can_mcan_data_##inst, \ + &can_mcan_cfg_##inst, \ + POST_KERNEL, CONFIG_CAN_INIT_PRIORITY, \ + &can_sam0_driver_api); + +#define CAN_SAM0_INST(inst) \ + PINCTRL_DT_INST_DEFINE(inst); \ + CAN_SAM0_IRQ_CFG_FUNCTION(inst) \ + CAN_SAM0_CFG_INST(inst) \ + CAN_SAM0_DATA_INST(inst) \ + CAN_SAM0_DEVICE_INST(inst) + +DT_INST_FOREACH_STATUS_OKAY(CAN_SAM0_INST) diff --git a/dts/bindings/can/atmel,sam0-can.yaml b/dts/bindings/can/atmel,sam0-can.yaml new file mode 100644 index 00000000000..a555d8bf485 --- /dev/null +++ b/dts/bindings/can/atmel,sam0-can.yaml @@ -0,0 +1,25 @@ +description: Specialization of Bosch m_can CAN-FD controller for Atmel SAM0 + +compatible: "atmel,sam0-can" + +include: + - name: can-fd-controller.yaml + - name: pinctrl-device.yaml + +properties: + reg: + required: true + + interrupts: + required: true + + clocks: + required: true + + clock-names: + required: true + + divider: + type: int + required: true + description: Clock divider of GLCK7 used by CAN as clock source diff --git a/soc/arm/atmel_sam0/samc20/soc.h b/soc/arm/atmel_sam0/samc20/soc.h index cca05ab6521..270f7557521 100644 --- a/soc/arm/atmel_sam0/samc20/soc.h +++ b/soc/arm/atmel_sam0/samc20/soc.h @@ -57,6 +57,7 @@ #include "../common/atmel_sam0_dt.h" #define SOC_ATMEL_SAM0_OSC32K_FREQ_HZ 32768 +#define SOC_ATMEL_SAM0_OSC48M_FREQ_HZ 48000000 /** Processor Clock (HCLK) Frequency */ #define SOC_ATMEL_SAM0_HCLK_FREQ_HZ CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC diff --git a/soc/arm/atmel_sam0/samc21/soc.h b/soc/arm/atmel_sam0/samc21/soc.h index 3f258b2eace..9f245e8eb07 100644 --- a/soc/arm/atmel_sam0/samc21/soc.h +++ b/soc/arm/atmel_sam0/samc21/soc.h @@ -57,6 +57,7 @@ #include "../common/atmel_sam0_dt.h" #define SOC_ATMEL_SAM0_OSC32K_FREQ_HZ 32768 +#define SOC_ATMEL_SAM0_OSC48M_FREQ_HZ 48000000 /** Processor Clock (HCLK) Frequency */ #define SOC_ATMEL_SAM0_HCLK_FREQ_HZ CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC