diff --git a/boards/arm/atsamc21n_xpro/atsamc21n_xpro-pinctrl.dtsi b/boards/arm/atsamc21n_xpro/atsamc21n_xpro-pinctrl.dtsi index 735b1b0ddd450..bc79bf08f8b7e 100644 --- a/boards/arm/atsamc21n_xpro/atsamc21n_xpro-pinctrl.dtsi +++ b/boards/arm/atsamc21n_xpro/atsamc21n_xpro-pinctrl.dtsi @@ -25,6 +25,20 @@ }; }; + can0_default: can0_default { + group1 { + pinmux = , + ; + }; + }; + + can1_default: can1_default { + group1 { + pinmux = , + ; + }; + }; + sercom0_uart_default: sercom0_uart_default { group1 { pinmux = , diff --git a/boards/arm/atsamc21n_xpro/atsamc21n_xpro.dts b/boards/arm/atsamc21n_xpro/atsamc21n_xpro.dts index 14d0dd22d5fbe..98ab04162a04b 100644 --- a/boards/arm/atsamc21n_xpro/atsamc21n_xpro.dts +++ b/boards/arm/atsamc21n_xpro/atsamc21n_xpro.dts @@ -18,6 +18,7 @@ zephyr,shell-uart = &sercom4; zephyr,sram = &sram0; zephyr,flash = &flash0; + zephyr,canbus = &can0; }; /* These aliases are provided for compatibility with samples */ @@ -151,3 +152,29 @@ }; }; }; + +&can0 { + status = "okay"; + + pinctrl-0 = <&can0_default>; + pinctrl-names = "default"; + + bus-speed = <125000>; + bus-speed-data = <1000000>; + + can-transceiver { + max-bitrate = <5000000>; + }; +}; + +&can1 { + pinctrl-0 = <&can1_default>; + pinctrl-names = "default"; + + bus-speed = <125000>; + bus-speed-data = <1000000>; + + can-transceiver { + max-bitrate = <5000000>; + }; +}; diff --git a/boards/arm/atsamc21n_xpro/atsamc21n_xpro.yaml b/boards/arm/atsamc21n_xpro/atsamc21n_xpro.yaml index 0d8fbeb75e110..554804a6f94dc 100644 --- a/boards/arm/atsamc21n_xpro/atsamc21n_xpro.yaml +++ b/boards/arm/atsamc21n_xpro/atsamc21n_xpro.yaml @@ -17,3 +17,4 @@ supported: - i2c - pwm - spi + - can diff --git a/boards/arm/atsamc21n_xpro/doc/index.rst b/boards/arm/atsamc21n_xpro/doc/index.rst index 07d12d6299e62..bb060fe32a46c 100644 --- a/boards/arm/atsamc21n_xpro/doc/index.rst +++ b/boards/arm/atsamc21n_xpro/doc/index.rst @@ -71,6 +71,9 @@ features: * - SPI - on-chip - Serial Peripheral Interface ports + * - CAN + - on-chip + - CAN ports Other hardware features are not currently supported by Zephyr. @@ -90,6 +93,10 @@ Default Zephyr Peripheral Mapping: ---------------------------------- - ADC0 : PB09 - ADC1 : PA08 +- CAN0 TX : PA24 +- CAN0 RX : PA25 +- CAN1 TX : PB14 +- CAN1 RX : PB15 - SERCOM0 USART TX : PB24 - SERCOM0 USART RX : PB25 - SERCOM1 I2C SDA : PA16 diff --git a/drivers/can/CMakeLists.txt b/drivers/can/CMakeLists.txt index 630ace7a6581d..c5a39471ea9c6 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 7c37744a3c895..c006d468ec0d6 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 0000000000000..aca2befcca814 --- /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_mcan.c b/drivers/can/can_mcan.c index f8091b3224c7d..ca252e914ec41 100644 --- a/drivers/can/can_mcan.c +++ b/drivers/can/can_mcan.c @@ -1207,6 +1207,18 @@ int can_mcan_configure_message_ram(const struct device *dev, uintptr_t mrba) uint32_t reg; int err; + err = can_mcan_exit_sleep_mode(dev); + if (err != 0) { + LOG_ERR("Failed to exit sleep mode"); + return -EIO; + } + + err = can_mcan_enter_init_mode(dev, K_MSEC(CAN_INIT_TIMEOUT_MS)); + if (err != 0) { + LOG_ERR("Failed to enter init mode"); + return -EIO; + } + can_mcan_enable_configuration_change(dev); reg = ((POINTER_TO_UINT(msg_ram->std_filt) - mrba) & CAN_MCAN_SIDFC_FLSSA) | diff --git a/drivers/can/can_sam0.c b/drivers/can/can_sam0.c new file mode 100644 index 0000000000000..2f9a390ecc8f3 --- /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/arm/atmel/samc21.dtsi b/dts/arm/atmel/samc21.dtsi index f7874487d0dc2..0fe0d9565a0e1 100644 --- a/dts/arm/atmel/samc21.dtsi +++ b/dts/arm/atmel/samc21.dtsi @@ -43,5 +43,47 @@ clock-names = "GCLK", "MCLK"; status = "disabled"; }; + + can { + compatible = "bosch,m_can-base"; + #address-cells = <1>; + #size-cells = <1>; + std-filter-elements = <28>; + ext-filter-elements = <8>; + rx-fifo0-elements = <3>; + rx-fifo1-elements = <3>; + rx-buffer-elements = <0>; + tx-buffer-elements = <1>; + + can0: can@42001c00 { + compatible = "atmel,sam0-can"; + reg = <0x42001c00 0x100>; + interrupts = <15 0>; + interrupt-names = "LINE_0"; + clocks = <&gclk 26>, <&mclk 0x10 8>; + clock-names = "GCLK", "MCLK"; + divider = <12>; + sjw = <1>; + sample-point = <875>; + sjw-data = <1>; + sample-point-data = <875>; + status = "disabled"; + }; + + can1: can@42002000 { + compatible = "atmel,sam0-can"; + reg = <0x42002000 0x100>; + interrupts = <16 0>; + interrupt-names = "LINE_0"; + clocks = <&gclk 27>, <&mclk 0x10 9>; + clock-names = "GCLK", "MCLK"; + divider = <12>; + sjw = <1>; + sample-point = <875>; + sjw-data = <1>; + sample-point-data = <875>; + status = "disabled"; + }; + }; }; }; diff --git a/dts/bindings/can/atmel,sam0-can.yaml b/dts/bindings/can/atmel,sam0-can.yaml new file mode 100644 index 0000000000000..a555d8bf485ae --- /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 cca05ab6521ad..270f7557521c1 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 3f258b2eace7b..9f245e8eb0795 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