From b59810650ddf361cc0d4aaafe0282a55ec459b7a Mon Sep 17 00:00:00 2001 From: cyliang tw Date: Tue, 24 Oct 2023 19:01:32 +0800 Subject: [PATCH] drivers: can: support for Nuvoton numaker series Add Nuvoton numaker series can-fd controller based on mcan. Signed-off-by: cyliang tw --- .../numaker_pfm_m467-pinctrl.dtsi | 8 + .../arm/numaker_pfm_m467/numaker_pfm_m467.dts | 9 + drivers/can/CMakeLists.txt | 1 + drivers/can/Kconfig | 1 + drivers/can/Kconfig.numaker | 13 + drivers/can/can_numaker.c | 299 ++++++++++++++++++ dts/arm/nuvoton/m46x.dtsi | 64 ++++ dts/bindings/can/nuvoton,numaker-canfd.yaml | 21 ++ 8 files changed, 416 insertions(+) create mode 100644 drivers/can/Kconfig.numaker create mode 100644 drivers/can/can_numaker.c create mode 100644 dts/bindings/can/nuvoton,numaker-canfd.yaml diff --git a/boards/arm/numaker_pfm_m467/numaker_pfm_m467-pinctrl.dtsi b/boards/arm/numaker_pfm_m467/numaker_pfm_m467-pinctrl.dtsi index 8aff684b1a9..190e4262cad 100644 --- a/boards/arm/numaker_pfm_m467/numaker_pfm_m467-pinctrl.dtsi +++ b/boards/arm/numaker_pfm_m467/numaker_pfm_m467-pinctrl.dtsi @@ -24,4 +24,12 @@ ; }; }; + + /* CAN TX/RX --> PJ10/PJ11 */ + canfd0_default: canfd0_default { + group0 { + pinmux = , + ; + }; + }; }; diff --git a/boards/arm/numaker_pfm_m467/numaker_pfm_m467.dts b/boards/arm/numaker_pfm_m467/numaker_pfm_m467.dts index 860ca7579f1..f6e7a4eb799 100644 --- a/boards/arm/numaker_pfm_m467/numaker_pfm_m467.dts +++ b/boards/arm/numaker_pfm_m467/numaker_pfm_m467.dts @@ -29,6 +29,7 @@ zephyr,sram = &sram0; zephyr,flash = &flash0; zephyr,code-partition = &slot0_partition; + zephyr,canbus = &canfd0; }; @@ -108,3 +109,11 @@ pinctrl-names = "default"; status = "okay"; }; + +&canfd0 { + bus-speed = <125000>; + bus-speed-data = <1000000>; + pinctrl-0 = <&canfd0_default>; + pinctrl-names = "default"; + status = "okay"; +}; diff --git a/drivers/can/CMakeLists.txt b/drivers/can/CMakeLists.txt index e0420efe784..c7d9de93f2e 100644 --- a/drivers/can/CMakeLists.txt +++ b/drivers/can/CMakeLists.txt @@ -19,6 +19,7 @@ zephyr_library_sources_ifdef(CONFIG_CAN_STM32_FDCAN can_stm32_fdcan.c) zephyr_library_sources_ifdef(CONFIG_CAN_STM32H7_FDCAN can_stm32h7_fdcan.c) zephyr_library_sources_ifdef(CONFIG_CAN_TCAN4X5X can_tcan4x5x.c) zephyr_library_sources_ifdef(CONFIG_CAN_RCAR can_rcar.c) +zephyr_library_sources_ifdef(CONFIG_CAN_NUMAKER can_numaker.c) if(CONFIG_CAN_NATIVE_POSIX_LINUX) if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL Linux) diff --git a/drivers/can/Kconfig b/drivers/can/Kconfig index 30381b09780..37d9a93f170 100644 --- a/drivers/can/Kconfig +++ b/drivers/can/Kconfig @@ -92,6 +92,7 @@ source "drivers/can/Kconfig.mcux" source "drivers/can/Kconfig.mcp2515" source "drivers/can/Kconfig.mcan" source "drivers/can/Kconfig.rcar" +source "drivers/can/Kconfig.numaker" source "drivers/can/Kconfig.loopback" source "drivers/can/Kconfig.native_posix_linux" source "drivers/can/Kconfig.sja1000" diff --git a/drivers/can/Kconfig.numaker b/drivers/can/Kconfig.numaker new file mode 100644 index 00000000000..c700853c554 --- /dev/null +++ b/drivers/can/Kconfig.numaker @@ -0,0 +1,13 @@ +# NuMaker CAN(-FD) driver configuration options + +# Copyright (c) 2023 Nuvoton Technology Corporation +# SPDX-License-Identifier: Apache-2.0 + +config CAN_NUMAKER + bool "Nuvoton NuMaker CAN-FD driver" + default y + select CAN_MCAN + depends on DT_HAS_NUVOTON_NUMAKER_CANFD_ENABLED + depends on SOC_SERIES_M46X + help + Enables Nuvoton NuMaker CAN-FD driver, using Bosch M_CAN diff --git a/drivers/can/can_numaker.c b/drivers/can/can_numaker.c new file mode 100644 index 00000000000..0bb47d78a04 --- /dev/null +++ b/drivers/can/can_numaker.c @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2023 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nuvoton_numaker_canfd + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(can_numaker, CONFIG_CAN_LOG_LEVEL); + +/* CANFD Clock Source Selection */ +#define NUMAKER_CANFD_CLKSEL_HXT 0 +#define NUMAKER_CANFD_CLKSEL_PLL_DIV2 1 +#define NUMAKER_CANFD_CLKSEL_HCLK 2 +#define NUMAKER_CANFD_CLKSEL_HIRC 3 + +/* Implementation notes + * 1. Use Bosch M_CAN driver (m_can) as backend + * 2. Need to modify can_numaker_get_core_clock() for new SOC support + */ + +struct can_numaker_config { + mm_reg_t canfd_base; + mem_addr_t mrba; + mem_addr_t mram; + const struct reset_dt_spec reset; + uint32_t clk_modidx; + uint32_t clk_src; + uint32_t clk_div; + const struct device *clk_dev; + void (*irq_config_func)(const struct device *dev); + const struct pinctrl_dev_config *pincfg; +}; + +static int can_numaker_get_core_clock(const struct device *dev, uint32_t *rate) +{ + const struct can_mcan_config *mcan_config = dev->config; + const struct can_numaker_config *config = mcan_config->custom; + uint32_t clksrc_rate_idx; + uint32_t clkdiv_divider; + + /* Module clock source rate */ + clksrc_rate_idx = CLK_GetModuleClockSource(config->clk_modidx); + /* Module clock divider */ + clkdiv_divider = CLK_GetModuleClockDivider(config->clk_modidx) + 1; + + switch (clksrc_rate_idx) { + case NUMAKER_CANFD_CLKSEL_HXT: + *rate = __HXT / clkdiv_divider; + break; + case NUMAKER_CANFD_CLKSEL_PLL_DIV2: + *rate = (CLK_GetPLLClockFreq() / 2) / clkdiv_divider; + break; + case NUMAKER_CANFD_CLKSEL_HCLK: + *rate = CLK_GetHCLKFreq() / clkdiv_divider; + break; + case NUMAKER_CANFD_CLKSEL_HIRC: + *rate = __HIRC / clkdiv_divider; + break; + default: + LOG_ERR("Invalid clock source rate index"); + return -EIO; + } + + LOG_DBG("Clock rate index/divider: %d/%d", clksrc_rate_idx, clkdiv_divider); + + return 0; +} + +static inline int can_numaker_init_unlocked(const struct device *dev) +{ + const struct can_mcan_config *mcan_config = dev->config; + const struct can_numaker_config *config = mcan_config->custom; + struct numaker_scc_subsys scc_subsys; + int rc; + + memset(&scc_subsys, 0x00, sizeof(scc_subsys)); + scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC; + scc_subsys.pcc.clk_modidx = config->clk_modidx; + scc_subsys.pcc.clk_src = config->clk_src; + scc_subsys.pcc.clk_div = config->clk_div; + + /* To enable clock */ + rc = clock_control_on(config->clk_dev, (clock_control_subsys_t) &scc_subsys); + if (rc < 0) { + return rc; + } + /* To set module clock */ + rc = clock_control_configure(config->clk_dev, (clock_control_subsys_t)&scc_subsys, NULL); + if (rc < 0) { + return rc; + } + + /* Configure pinmux (NuMaker's SYS MFP) */ + rc = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); + if (rc < 0) { + return rc; + } + + /* Reset CAN to default state, same as BSP's SYS_ResetModule(id_rst) */ + reset_line_toggle_dt(&config->reset); + + config->irq_config_func(dev); + + rc = can_mcan_configure_mram(dev, config->mrba, config->mram); + if (rc != 0) { + return rc; + } + + rc = can_mcan_init(dev); + if (rc < 0) { + LOG_ERR("Failed to initialize mcan: %d", rc); + return rc; + } + +#if CONFIG_CAN_LOG_LEVEL >= LOG_LEVEL_DBG + uint32_t rate; + + rc = can_numaker_get_core_clock(dev, &rate); + if (rc < 0) { + return rc; + } + + LOG_DBG("CAN core clock: %d", rate); +#endif + + return rc; +} + +static int can_numaker_init(const struct device *dev) +{ + const struct can_mcan_config *mcan_config = dev->config; + const struct can_numaker_config *config = mcan_config->custom; + int rc; + + if (!device_is_ready(config->reset.dev)) { + LOG_ERR("reset controller not ready"); + return -ENODEV; + } + + if (!device_is_ready(config->clk_dev)) { + LOG_ERR("clock controller not ready"); + return -ENODEV; + } + + SYS_UnlockReg(); + rc = can_numaker_init_unlocked(dev); + SYS_LockReg(); + + return rc; +} + +static const struct can_driver_api can_numaker_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, +#ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY + .recover = can_mcan_recover, +#endif /* CONFIG_CAN_AUTO_BUS_OFF_RECOVERY */ + .get_state = can_mcan_get_state, + .set_state_change_callback = can_mcan_set_state_change_callback, + .get_core_clock = can_numaker_get_core_clock, + .get_max_filters = can_mcan_get_max_filters, + .get_max_bitrate = can_mcan_get_max_bitrate, + .timing_min = CAN_MCAN_TIMING_MIN_INITIALIZER, + .timing_max = CAN_MCAN_TIMING_MAX_INITIALIZER, +#ifdef CONFIG_CAN_FD_MODE + .set_timing_data = can_mcan_set_timing_data, + .timing_data_min = CAN_MCAN_TIMING_DATA_MIN_INITIALIZER, + .timing_data_max = CAN_MCAN_TIMING_DATA_MAX_INITIALIZER, +#endif /* CONFIG_CAN_FD_MODE */ +}; + +static int can_numaker_read_reg(const struct device *dev, uint16_t reg, uint32_t *val) +{ + const struct can_mcan_config *mcan_cfg = dev->config; + const struct can_numaker_config *numaker_cfg = mcan_cfg->custom; + + return can_mcan_sys_read_reg(numaker_cfg->canfd_base, reg, val); +} + +static int can_numaker_write_reg(const struct device *dev, uint16_t reg, uint32_t val) +{ + const struct can_mcan_config *mcan_cfg = dev->config; + const struct can_numaker_config *numaker_cfg = mcan_cfg->custom; + + return can_mcan_sys_write_reg(numaker_cfg->canfd_base, reg, val); +} + +static int can_numaker_read_mram(const struct device *dev, uint16_t offset, void *dst, size_t len) +{ + const struct can_mcan_config *mcan_cfg = dev->config; + const struct can_numaker_config *numaker_cfg = mcan_cfg->custom; + + return can_mcan_sys_read_mram(numaker_cfg->mram, offset, dst, len); +} + +static int can_numaker_write_mram(const struct device *dev, uint16_t offset, const void *src, + size_t len) +{ + const struct can_mcan_config *mcan_cfg = dev->config; + const struct can_numaker_config *numaker_cfg = mcan_cfg->custom; + + return can_mcan_sys_write_mram(numaker_cfg->mram, offset, src, len); +} + +static int can_numaker_clear_mram(const struct device *dev, uint16_t offset, size_t len) +{ + const struct can_mcan_config *mcan_cfg = dev->config; + const struct can_numaker_config *numaker_cfg = mcan_cfg->custom; + + return can_mcan_sys_clear_mram(numaker_cfg->mram, offset, len); +} + +static const struct can_mcan_ops can_numaker_ops = { + .read_reg = can_numaker_read_reg, + .write_reg = can_numaker_write_reg, + .read_mram = can_numaker_read_mram, + .write_mram = can_numaker_write_mram, + .clear_mram = can_numaker_clear_mram, +}; + +#define NUMAKER_CLKCTRL_DEV_INIT(inst) \ + .clk_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(inst))), + +#define NUMAKER_PINCTRL_DEFINE(inst) \ + PINCTRL_DT_INST_DEFINE(inst); +#define NUMAKER_PINCTRL_INIT(inst) \ + .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), + +#define CAN_NUMAKER_INIT(inst) \ + NUMAKER_PINCTRL_DEFINE(inst); \ + CAN_MCAN_DT_INST_CALLBACKS_DEFINE(inst, can_numaker_cbs_##inst); \ + \ + static void can_numaker_irq_config_func_##inst(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, 0, irq), \ + DT_INST_IRQ_BY_IDX(inst, 0, priority), \ + can_mcan_line_0_isr, \ + DEVICE_DT_INST_GET(inst), \ + 0); \ + irq_enable(DT_INST_IRQ_BY_IDX(inst, 0, irq)); \ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, 1, irq), \ + DT_INST_IRQ_BY_IDX(inst, 1, priority), \ + can_mcan_line_1_isr, \ + DEVICE_DT_INST_GET(inst), \ + 0); \ + irq_enable(DT_INST_IRQ_BY_IDX(inst, 1, irq)); \ + } \ + \ + static const struct can_numaker_config can_numaker_config_##inst = { \ + .canfd_base = CAN_MCAN_DT_INST_MCAN_ADDR(inst), \ + .mrba = CAN_MCAN_DT_INST_MRBA(inst), \ + .mram = CAN_MCAN_DT_INST_MRAM_ADDR(inst), \ + .reset = RESET_DT_SPEC_INST_GET(inst), \ + .clk_modidx = DT_INST_CLOCKS_CELL(inst, clock_module_index), \ + .clk_src = DT_INST_CLOCKS_CELL(inst, clock_source), \ + .clk_div = DT_INST_CLOCKS_CELL(inst, clock_divider), \ + NUMAKER_CLKCTRL_DEV_INIT(inst) \ + .irq_config_func = can_numaker_irq_config_func_##inst, \ + NUMAKER_PINCTRL_INIT(inst) \ + }; \ + \ + static const struct can_mcan_config can_mcan_config_##inst = \ + CAN_MCAN_DT_CONFIG_INST_GET(inst, \ + &can_numaker_config_##inst, \ + &can_numaker_ops, \ + &can_numaker_cbs_##inst); \ + \ + static uint32_t can_numaker_data_##inst; \ + \ + static struct can_mcan_data can_mcan_data_##inst = \ + CAN_MCAN_DATA_INITIALIZER(&can_numaker_data_ ## inst); \ + \ + CAN_DEVICE_DT_INST_DEFINE(inst, \ + &can_numaker_init, \ + NULL, \ + &can_mcan_data_##inst, \ + &can_mcan_config_##inst, \ + POST_KERNEL, \ + CONFIG_CAN_INIT_PRIORITY, \ + &can_numaker_driver_api); \ + +DT_INST_FOREACH_STATUS_OKAY(CAN_NUMAKER_INIT); diff --git a/dts/arm/nuvoton/m46x.dtsi b/dts/arm/nuvoton/m46x.dtsi index 2355a6fd82a..ea0db8bac45 100644 --- a/dts/arm/nuvoton/m46x.dtsi +++ b/dts/arm/nuvoton/m46x.dtsi @@ -428,6 +428,70 @@ #pwm-cells = <3>; status = "disabled"; }; + + canfd0: canfd@40020000 { + compatible = "nuvoton,numaker-canfd"; + reg = <0x40020000 0x200>, <0x40020200 0x1800>; + reg-names = "m_can", "message_ram"; + interrupts = <112 0>, <113 0>; + interrupt-names = "LINE_0", "LINE_1"; + resets = <&rst NUMAKER_CANFD0_RST>; + clocks = <&pcc NUMAKER_CANFD0_MODULE + NUMAKER_CLK_CLKSEL0_CANFD0SEL_HCLK + NUMAKER_CLK_CLKDIV5_CANFD0(1)>; + bosch,mram-cfg = <0x0 12 10 3 3 3 3 3>; + status = "disabled"; + sample-point = <875>; + sample-point-data = <875>; + }; + + canfd1: canfd@40024000 { + compatible = "nuvoton,numaker-canfd"; + reg = <0x40024000 0x200>, <0x40024200 0x1800>; + reg-names = "m_can", "message_ram"; + interrupts = <114 0>, <115 0>; + interrupt-names = "LINE_0", "LINE_1"; + resets = <&rst NUMAKER_CANFD1_RST>; + clocks = <&pcc NUMAKER_CANFD1_MODULE + NUMAKER_CLK_CLKSEL0_CANFD1SEL_HCLK + NUMAKER_CLK_CLKDIV5_CANFD1(1)>; + bosch,mram-cfg = <0x0 12 10 3 3 3 3 3>; + status = "disabled"; + sample-point = <875>; + sample-point-data = <875>; + }; + + canfd2: canfd@40028000 { + compatible = "nuvoton,numaker-canfd"; + reg = <0x40028000 0x200>, <0x40028200 0x1800>; + reg-names = "m_can", "message_ram"; + interrupts = <120 0>, <121 0>; + interrupt-names = "LINE_0", "LINE_1"; + resets = <&rst NUMAKER_CANFD2_RST>; + clocks = <&pcc NUMAKER_CANFD2_MODULE + NUMAKER_CLK_CLKSEL0_CANFD2SEL_HCLK + NUMAKER_CLK_CLKDIV5_CANFD2(1)>; + bosch,mram-cfg = <0x0 12 10 3 3 3 3 3>; + status = "disabled"; + sample-point = <875>; + sample-point-data = <875>; + }; + + canfd3: canfd@4002c000 { + compatible = "nuvoton,numaker-canfd"; + reg = <0x4002c000 0x200>, <0x4002c200 0x1800>; + reg-names = "m_can", "message_ram"; + interrupts = <122 0>, <123 0>; + interrupt-names = "LINE_0", "LINE_1"; + resets = <&rst NUMAKER_CANFD3_RST>; + clocks = <&pcc NUMAKER_CANFD3_MODULE + NUMAKER_CLK_CLKSEL0_CANFD3SEL_HCLK + NUMAKER_CLK_CLKDIV5_CANFD3(1)>; + bosch,mram-cfg = <0x0 12 10 3 3 3 3 3>; + status = "disabled"; + sample-point = <875>; + sample-point-data = <875>; + }; }; }; diff --git a/dts/bindings/can/nuvoton,numaker-canfd.yaml b/dts/bindings/can/nuvoton,numaker-canfd.yaml new file mode 100644 index 00000000000..dbfa39259c0 --- /dev/null +++ b/dts/bindings/can/nuvoton,numaker-canfd.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2023 Nuvoton Technology Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: Nuvoton NuMaker CAN-FD controller, using Bosch M_CAN IP + +compatible: "nuvoton,numaker-canfd" + +include: ["bosch,m_can-base.yaml", reset-device.yaml, pinctrl-device.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + resets: + required: true + + clocks: + required: true