From 6e5c8a339d39a83267ac0720b96f6e41f6c874a9 Mon Sep 17 00:00:00 2001 From: Alexander Wachter Date: Sun, 9 Jan 2022 22:25:58 +0100 Subject: [PATCH] drivers: can: Add frontend for Atmel SAM M_CAN controller This commit adds a frontend for the generic Bosch m_can driver for Atmel SAM series. Signed-off-by: Alexander Wachter --- drivers/can/CMakeLists.txt | 1 + drivers/can/Kconfig | 1 + drivers/can/Kconfig.sam | 22 +++ drivers/can/can_sam.c | 258 ++++++++++++++++++++++++++++ dts/bindings/can/atmel,sam-can.yaml | 19 ++ 5 files changed, 301 insertions(+) create mode 100644 drivers/can/Kconfig.sam create mode 100644 drivers/can/can_sam.c create mode 100644 dts/bindings/can/atmel,sam-can.yaml diff --git a/drivers/can/CMakeLists.txt b/drivers/can/CMakeLists.txt index 1553cc23ca8..ac0c6a6be1d 100644 --- a/drivers/can/CMakeLists.txt +++ b/drivers/can/CMakeLists.txt @@ -8,6 +8,7 @@ zephyr_library_sources_ifdef(CONFIG_CAN_LOOPBACK can_loopback.c) 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_STM32 can_stm32.c) zephyr_library_sources_ifdef(CONFIG_CAN_STM32FD can_stm32fd.c) zephyr_library_sources_ifdef(CONFIG_CAN_RCAR can_rcar.c) diff --git a/drivers/can/Kconfig b/drivers/can/Kconfig index 630132bc53e..7bd5cad91d1 100644 --- a/drivers/can/Kconfig +++ b/drivers/can/Kconfig @@ -93,6 +93,7 @@ config CAN_AUTO_BUS_OFF_RECOVERY recessive bits). When this option is enabled, the recovery API is not available. +source "drivers/can/Kconfig.sam" source "drivers/can/Kconfig.stm32" source "drivers/can/Kconfig.stm32fd" source "drivers/can/Kconfig.mcux" diff --git a/drivers/can/Kconfig.sam b/drivers/can/Kconfig.sam new file mode 100644 index 00000000000..919a73328d9 --- /dev/null +++ b/drivers/can/Kconfig.sam @@ -0,0 +1,22 @@ +# SAM CAN configuration options +# Copyright (c) 2021 Alexander Wachter +# SPDX-License-Identifier: Apache-2.0 + +DT_COMPAT_ATMEL_SAM_CAN := atmel,sam-can + +config CAN_SAM + bool "Atmel SAM CAN driver" + default $(dt_compat_enabled,$(DT_COMPAT_ATMEL_SAM_CAN)) + select CAN_MCAN + +if CAN_SAM + +config CAN_SAM_CKDIV + int "Clock divider" + range 0 255 + default 0 + depends on CAN_SAM + help + Clock divider for the MCAN core clock. + +endif #CAN_SAM diff --git a/drivers/can/can_sam.c b/drivers/can/can_sam.c new file mode 100644 index 00000000000..cc0a9b3ae8f --- /dev/null +++ b/drivers/can/can_sam.c @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2021 Alexander Wachter + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "can_mcan.h" + +#include +#include +#include + +#include +LOG_MODULE_DECLARE(can_driver, CONFIG_CAN_LOG_LEVEL); +#define DT_DRV_COMPAT atmel_sam_can + +struct can_sam_config { + struct can_mcan_config mcan_cfg; + void (*config_irq)(void); + struct soc_gpio_pin pin_list[2]; + uint8_t pmc_id; +}; + +struct can_sam_data { + struct can_mcan_data mcan_data; + struct can_mcan_msg_sram msg_ram; +}; + + +static int can_sam_get_core_clock(const struct device *dev, uint32_t *rate) +{ + *rate = SOC_ATMEL_SAM_MCK_FREQ_HZ / (CONFIG_CAN_SAM_CKDIV + 1); + return 0; +} + +static void can_sam_set_state_change_callback(const struct device *dev, + can_state_change_callback_t cb, + void *user_data) +{ + struct can_sam_data *data = dev->data; + + data->mcan_data.state_change_cb_data = user_data; + data->mcan_data.state_change_cb = cb; +} + +static void can_sam_clock_enable(const struct can_sam_config *cfg) +{ + REG_PMC_PCK5 = PMC_PCK_CSS_PLLA_CLK | PMC_PCK_PRES(CONFIG_CAN_SAM_CKDIV); + PMC->PMC_SCER |= PMC_SCER_PCK5; + soc_pmc_peripheral_enable(cfg->pmc_id); +} + +static int can_sam_init(const struct device *dev) +{ + const struct can_sam_config *cfg = dev->config; + const struct can_mcan_config *mcan_cfg = &cfg->mcan_cfg; + struct can_sam_data *data = dev->data; + struct can_mcan_data *mcan_data = &data->mcan_data; + struct can_mcan_msg_sram *msg_ram = &data->msg_ram; + int ret; + + can_sam_clock_enable(cfg); + soc_gpio_list_configure(cfg->pin_list, ARRAY_SIZE(cfg->pin_list)); + ret = can_mcan_init(dev, mcan_cfg, msg_ram, mcan_data); + if (ret) { + return ret; + } + + cfg->config_irq(); + + return ret; +} + +static enum can_state can_sam_get_state(const struct device *dev, struct can_bus_err_cnt *err_cnt) +{ + const struct can_sam_config *cfg = dev->config; + const struct can_mcan_config *mcan_cfg = &cfg->mcan_cfg; + + return can_mcan_get_state(mcan_cfg, err_cnt); +} + +static int can_sam_send(const struct device *dev, const struct zcan_frame *frame, + k_timeout_t timeout, can_tx_callback_t callback, void *user_data) +{ + const struct can_sam_config *cfg = dev->config; + const struct can_mcan_config *mcan_cfg = &cfg->mcan_cfg; + struct can_sam_data *data = dev->data; + struct can_mcan_data *mcan_data = &data->mcan_data; + struct can_mcan_msg_sram *msg_ram = &data->msg_ram; + + return can_mcan_send(mcan_cfg, mcan_data, msg_ram, frame, timeout, callback, user_data); +} + +static int can_sam_add_rx_filter(const struct device *dev, can_rx_callback_t cb, void *cb_arg, + const struct zcan_filter *filter) +{ + struct can_sam_data *data = dev->data; + struct can_mcan_data *mcan_data = &data->mcan_data; + struct can_mcan_msg_sram *msg_ram = &data->msg_ram; + + return can_mcan_add_rx_filter(mcan_data, msg_ram, cb, cb_arg, filter); +} + +static void can_sam_remove_rx_filter(const struct device *dev, int filter_id) +{ + struct can_sam_data *data = dev->data; + struct can_mcan_data *mcan_data = &data->mcan_data; + struct can_mcan_msg_sram *msg_ram = &data->msg_ram; + + can_mcan_remove_rx_filter(mcan_data, msg_ram, filter_id); +} + +static int can_sam_set_mode(const struct device *dev, enum can_mode mode) +{ + const struct can_sam_config *cfg = dev->config; + const struct can_mcan_config *mcan_cfg = &cfg->mcan_cfg; + + return can_mcan_set_mode(mcan_cfg, mode); +} + +static int can_sam_set_timing(const struct device *dev, const struct can_timing *timing, + const struct can_timing *timing_data) +{ + const struct can_sam_config *cfg = dev->config; + const struct can_mcan_config *mcan_cfg = &cfg->mcan_cfg; + + return can_mcan_set_timing(mcan_cfg, timing, timing_data); +} + +static void can_sam_line_0_isr(const struct device *dev) +{ + const struct can_sam_config *cfg = dev->config; + const struct can_mcan_config *mcan_cfg = &cfg->mcan_cfg; + struct can_sam_data *data = dev->data; + struct can_mcan_data *mcan_data = &data->mcan_data; + struct can_mcan_msg_sram *msg_ram = &data->msg_ram; + + can_mcan_line_0_isr(mcan_cfg, msg_ram, mcan_data); +} + +static void can_sam_line_1_isr(const struct device *dev) +{ + const struct can_sam_config *cfg = dev->config; + const struct can_mcan_config *mcan_cfg = &cfg->mcan_cfg; + struct can_sam_data *data = dev->data; + struct can_mcan_data *mcan_data = &data->mcan_data; + struct can_mcan_msg_sram *msg_ram = &data->msg_ram; + + can_mcan_line_1_isr(mcan_cfg, msg_ram, mcan_data); +} + +static const struct can_driver_api can_api_funcs = { + .set_mode = can_sam_set_mode, + .set_timing = can_sam_set_timing, + .send = can_sam_send, + .add_rx_filter = can_sam_add_rx_filter, + .remove_rx_filter = can_sam_remove_rx_filter, + .get_state = can_sam_get_state, +#ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY + .recover = can_mcan_recover, +#endif + .get_core_clock = can_sam_get_core_clock, + .set_state_change_callback = can_sam_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 + .timing_min_data = { + .sjw = 0x01, + .prop_seg = 0x00, + .phase_seg1 = 0x01, + .phase_seg2 = 0x01, + .prescaler = 0x01 + }, + .timing_max_data = { + .sjw = 0x10, + .prop_seg = 0x00, + .phase_seg1 = 0x20, + .phase_seg2 = 0x10, + .prescaler = 0x20 + } +#endif +}; + +#define CAN_SAM_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_sam_line_0_isr, \ + DEVICE_DT_INST_GET(inst), 0); \ + irq_enable(DT_INST_IRQ_BY_NAME(inst, line_0, irq)); \ + IRQ_CONNECT(DT_INST_IRQ_BY_NAME(inst, line_1, irq), \ + DT_INST_IRQ_BY_NAME(inst, line_1, priority), can_sam_line_1_isr, \ + DEVICE_DT_INST_GET(inst), 0); \ + irq_enable(DT_INST_IRQ_BY_NAME(inst, line_1, irq)); \ +} + +#ifdef CONFIG_CAN_FD_MODE +#define CAN_SAM_MCAN_CFG(inst) \ +{ \ + .can = (struct can_mcan_reg *)DT_INST_REG_ADDR(inst), \ + .bus_speed = DT_INST_PROP(inst, bus_speed), .sjw = DT_INST_PROP(inst, sjw), \ + .sample_point = DT_INST_PROP_OR(inst, sample_point, 0), \ + .prop_ts1 = DT_INST_PROP_OR(inst, prop_seg, 0) + DT_INST_PROP_OR(inst, phase_seg1, 0), \ + .ts2 = DT_INST_PROP_OR(inst, phase_seg2, 0), \ + .bus_speed_data = DT_INST_PROP(inst, bus_speed_data), \ + .sjw_data = DT_INST_PROP(inst, sjw_data), \ + .sample_point_data = DT_INST_PROP_OR(inst, sample_point_data, 0), \ + .prop_ts1_data = DT_INST_PROP_OR(inst, prop_seg_data, 0) + \ + DT_INST_PROP_OR(inst, phase_seg1_data, 0), \ + .ts2_data = DT_INST_PROP_OR(inst, phase_seg2_data, 0), \ + .tx_delay_comp_offset = DT_INST_PROP(inst, tx_delay_comp_offset) \ +} +#else /* CONFIG_CAN_FD_MODE */ +#define CAN_SAM_MCAN_CFG(inst) \ +{ \ + .can = (struct can_mcan_reg *)DT_INST_REG_ADDR(inst), \ + .bus_speed = DT_INST_PROP(inst, bus_speed), .sjw = DT_INST_PROP(inst, sjw), \ + .sample_point = DT_INST_PROP_OR(inst, sample_point, 0), \ + .prop_ts1 = DT_INST_PROP_OR(inst, prop_seg, 0) + DT_INST_PROP_OR(inst, phase_seg1, 0), \ + .ts2 = DT_INST_PROP_OR(inst, phase_seg2, 0), \ +} +#endif /* CONFIG_CAN_FD_MODE */ + +#define CAN_SAM_CFG_INST(inst) \ +static const struct can_sam_config can_sam_cfg_##inst = { \ + .pmc_id = DT_INST_PROP(inst, peripheral_id), \ + .pin_list = { ATMEL_SAM_DT_INST_PIN(inst, 0), ATMEL_SAM_DT_INST_PIN(inst, 1) }, \ + .config_irq = config_can_##inst##_irq, \ + .mcan_cfg = CAN_SAM_MCAN_CFG(inst) \ +}; + +#define CAN_SAM_DATA_INST(inst) static struct can_sam_data can_sam_dev_data_##inst; + +#define CAN_SAM_DEVICE_INST(inst) \ +DEVICE_DT_INST_DEFINE(inst, &can_sam_init, NULL, &can_sam_dev_data_##inst, \ + &can_sam_cfg_##inst, POST_KERNEL, CONFIG_CAN_INIT_PRIORITY, \ + &can_api_funcs); + +#define CAN_SAM_INST(inst) \ + CAN_SAM_IRQ_CFG_FUNCTION(inst) \ + CAN_SAM_CFG_INST(inst) \ + CAN_SAM_DATA_INST(inst) \ + CAN_SAM_DEVICE_INST(inst) + +DT_INST_FOREACH_STATUS_OKAY(CAN_SAM_INST) diff --git a/dts/bindings/can/atmel,sam-can.yaml b/dts/bindings/can/atmel,sam-can.yaml new file mode 100644 index 00000000000..b2c49c2ba3d --- /dev/null +++ b/dts/bindings/can/atmel,sam-can.yaml @@ -0,0 +1,19 @@ +description: Specialization of Bosch m_can CAN-FD controller for Atmel SAM + +compatible: "atmel,sam-can" + +include: bosch,m-can.yaml + +properties: + peripheral-id: + type: int + required: true + description: peripheral ID + pinctrl-0: + type: phandles + required: true + description: | + PIO configuration for CAN_RX and CAN_TX. We expect that the phandles will + reference pinctrl nodes. These nodes will have a nodelabel that + matches the Atmel SoC HAL defines and be of the form + pinctrl-0 = <&pb3a_can0_rx0 &pb2a_can0_tx0>;