|
| 1 | +/* |
| 2 | + * Copyright (c) 2023 Nuvoton Technology Corporation. |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + */ |
| 6 | + |
| 7 | +#define DT_DRV_COMPAT nuvoton_numaker_canfd |
| 8 | + |
| 9 | +#include <zephyr/drivers/reset.h> |
| 10 | +#include <zephyr/drivers/pinctrl.h> |
| 11 | +#include <zephyr/drivers/can.h> |
| 12 | +#include <zephyr/drivers/can/can_mcan.h> |
| 13 | +#include <zephyr/drivers/clock_control.h> |
| 14 | +#include <zephyr/drivers/clock_control/clock_control_numaker.h> |
| 15 | +#include <zephyr/logging/log.h> |
| 16 | +#include <soc.h> |
| 17 | +#include <NuMicro.h> |
| 18 | + |
| 19 | +LOG_MODULE_REGISTER(can_numaker, CONFIG_CAN_LOG_LEVEL); |
| 20 | + |
| 21 | +/* CANFD Clock Source Selection */ |
| 22 | +#define NUMAKER_CANFD_CLKSEL_HXT 0 |
| 23 | +#define NUMAKER_CANFD_CLKSEL_PLL_DIV2 1 |
| 24 | +#define NUMAKER_CANFD_CLKSEL_HCLK 2 |
| 25 | +#define NUMAKER_CANFD_CLKSEL_HIRC 3 |
| 26 | + |
| 27 | +/* Implementation notes |
| 28 | + * 1. Use Bosch M_CAN driver (m_can) as backend |
| 29 | + * 2. Need to modify can_numaker_get_core_clock() for new SOC support |
| 30 | + */ |
| 31 | + |
| 32 | +struct can_numaker_config { |
| 33 | + mm_reg_t canfd_base; |
| 34 | + mem_addr_t mrba; |
| 35 | + mem_addr_t mram; |
| 36 | + const struct reset_dt_spec reset; |
| 37 | + uint32_t clk_modidx; |
| 38 | + uint32_t clk_src; |
| 39 | + uint32_t clk_div; |
| 40 | + const struct device *clk_dev; |
| 41 | + void (*irq_config_func)(const struct device *dev); |
| 42 | + const struct pinctrl_dev_config *pincfg; |
| 43 | +}; |
| 44 | + |
| 45 | +static int can_numaker_get_core_clock(const struct device *dev, uint32_t *rate) |
| 46 | +{ |
| 47 | + const struct can_mcan_config *mcan_config = dev->config; |
| 48 | + const struct can_numaker_config *config = mcan_config->custom; |
| 49 | + uint32_t clksrc_rate_idx; |
| 50 | + uint32_t clkdiv_divider; |
| 51 | + |
| 52 | + /* Module clock source rate */ |
| 53 | + clksrc_rate_idx = CLK_GetModuleClockSource(config->clk_modidx); |
| 54 | + /* Module clock divider */ |
| 55 | + clkdiv_divider = CLK_GetModuleClockDivider(config->clk_modidx) + 1; |
| 56 | + |
| 57 | + switch (clksrc_rate_idx) { |
| 58 | + case NUMAKER_CANFD_CLKSEL_HXT: |
| 59 | + *rate = __HXT / clkdiv_divider; |
| 60 | + break; |
| 61 | + case NUMAKER_CANFD_CLKSEL_PLL_DIV2: |
| 62 | + *rate = (CLK_GetPLLClockFreq() / 2) / clkdiv_divider; |
| 63 | + break; |
| 64 | + case NUMAKER_CANFD_CLKSEL_HCLK: |
| 65 | + *rate = CLK_GetHCLKFreq() / clkdiv_divider; |
| 66 | + break; |
| 67 | + case NUMAKER_CANFD_CLKSEL_HIRC: |
| 68 | + *rate = __HIRC / clkdiv_divider; |
| 69 | + break; |
| 70 | + default: |
| 71 | + LOG_ERR("Invalid clock source rate index"); |
| 72 | + return -EIO; |
| 73 | + } |
| 74 | + |
| 75 | + LOG_DBG("Clock rate index/divider: %d/%d", clksrc_rate_idx, clkdiv_divider); |
| 76 | + |
| 77 | + return 0; |
| 78 | +} |
| 79 | + |
| 80 | +static inline int can_numaker_init_unlocked(const struct device *dev) |
| 81 | +{ |
| 82 | + const struct can_mcan_config *mcan_config = dev->config; |
| 83 | + const struct can_numaker_config *config = mcan_config->custom; |
| 84 | + struct numaker_scc_subsys scc_subsys; |
| 85 | + int rc; |
| 86 | + |
| 87 | + memset(&scc_subsys, 0x00, sizeof(scc_subsys)); |
| 88 | + scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC; |
| 89 | + scc_subsys.pcc.clk_modidx = config->clk_modidx; |
| 90 | + scc_subsys.pcc.clk_src = config->clk_src; |
| 91 | + scc_subsys.pcc.clk_div = config->clk_div; |
| 92 | + |
| 93 | + /* To enable clock */ |
| 94 | + rc = clock_control_on(config->clk_dev, (clock_control_subsys_t) &scc_subsys); |
| 95 | + if (rc < 0) { |
| 96 | + return rc; |
| 97 | + } |
| 98 | + /* To set module clock */ |
| 99 | + rc = clock_control_configure(config->clk_dev, (clock_control_subsys_t)&scc_subsys, NULL); |
| 100 | + if (rc < 0) { |
| 101 | + return rc; |
| 102 | + } |
| 103 | + |
| 104 | + /* Configure pinmux (NuMaker's SYS MFP) */ |
| 105 | + rc = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); |
| 106 | + if (rc < 0) { |
| 107 | + return rc; |
| 108 | + } |
| 109 | + |
| 110 | + /* Reset CAN to default state, same as BSP's SYS_ResetModule(id_rst) */ |
| 111 | + reset_line_toggle_dt(&config->reset); |
| 112 | + |
| 113 | + config->irq_config_func(dev); |
| 114 | + |
| 115 | + rc = can_mcan_configure_mram(dev, config->mrba, config->mram); |
| 116 | + if (rc != 0) { |
| 117 | + return rc; |
| 118 | + } |
| 119 | + |
| 120 | + rc = can_mcan_init(dev); |
| 121 | + if (rc < 0) { |
| 122 | + LOG_ERR("Failed to initialize mcan: %d", rc); |
| 123 | + return rc; |
| 124 | + } |
| 125 | + |
| 126 | +#if CONFIG_CAN_LOG_LEVEL >= LOG_LEVEL_DBG |
| 127 | + uint32_t rate; |
| 128 | + |
| 129 | + rc = can_numaker_get_core_clock(dev, &rate); |
| 130 | + if (rc < 0) { |
| 131 | + return rc; |
| 132 | + } |
| 133 | + |
| 134 | + LOG_DBG("CAN core clock: %d", rate); |
| 135 | +#endif |
| 136 | + |
| 137 | + return rc; |
| 138 | +} |
| 139 | + |
| 140 | +static int can_numaker_init(const struct device *dev) |
| 141 | +{ |
| 142 | + const struct can_mcan_config *mcan_config = dev->config; |
| 143 | + const struct can_numaker_config *config = mcan_config->custom; |
| 144 | + int rc; |
| 145 | + |
| 146 | + if (!device_is_ready(config->reset.dev)) { |
| 147 | + LOG_ERR("reset controller not ready"); |
| 148 | + return -ENODEV; |
| 149 | + } |
| 150 | + |
| 151 | + if (!device_is_ready(config->clk_dev)) { |
| 152 | + LOG_ERR("clock controller not ready"); |
| 153 | + return -ENODEV; |
| 154 | + } |
| 155 | + |
| 156 | + SYS_UnlockReg(); |
| 157 | + rc = can_numaker_init_unlocked(dev); |
| 158 | + SYS_LockReg(); |
| 159 | + |
| 160 | + return rc; |
| 161 | +} |
| 162 | + |
| 163 | +static const struct can_driver_api can_numaker_driver_api = { |
| 164 | + .get_capabilities = can_mcan_get_capabilities, |
| 165 | + .start = can_mcan_start, |
| 166 | + .stop = can_mcan_stop, |
| 167 | + .set_mode = can_mcan_set_mode, |
| 168 | + .set_timing = can_mcan_set_timing, |
| 169 | + .send = can_mcan_send, |
| 170 | + .add_rx_filter = can_mcan_add_rx_filter, |
| 171 | + .remove_rx_filter = can_mcan_remove_rx_filter, |
| 172 | +#ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY |
| 173 | + .recover = can_mcan_recover, |
| 174 | +#endif /* CONFIG_CAN_AUTO_BUS_OFF_RECOVERY */ |
| 175 | + .get_state = can_mcan_get_state, |
| 176 | + .set_state_change_callback = can_mcan_set_state_change_callback, |
| 177 | + .get_core_clock = can_numaker_get_core_clock, |
| 178 | + .get_max_filters = can_mcan_get_max_filters, |
| 179 | + .get_max_bitrate = can_mcan_get_max_bitrate, |
| 180 | + .timing_min = CAN_MCAN_TIMING_MIN_INITIALIZER, |
| 181 | + .timing_max = CAN_MCAN_TIMING_MAX_INITIALIZER, |
| 182 | +#ifdef CONFIG_CAN_FD_MODE |
| 183 | + .set_timing_data = can_mcan_set_timing_data, |
| 184 | + .timing_data_min = CAN_MCAN_TIMING_DATA_MIN_INITIALIZER, |
| 185 | + .timing_data_max = CAN_MCAN_TIMING_DATA_MAX_INITIALIZER, |
| 186 | +#endif /* CONFIG_CAN_FD_MODE */ |
| 187 | +}; |
| 188 | + |
| 189 | +static int can_numaker_read_reg(const struct device *dev, uint16_t reg, uint32_t *val) |
| 190 | +{ |
| 191 | + const struct can_mcan_config *mcan_cfg = dev->config; |
| 192 | + const struct can_numaker_config *numaker_cfg = mcan_cfg->custom; |
| 193 | + |
| 194 | + return can_mcan_sys_read_reg(numaker_cfg->canfd_base, reg, val); |
| 195 | +} |
| 196 | + |
| 197 | +static int can_numaker_write_reg(const struct device *dev, uint16_t reg, uint32_t val) |
| 198 | +{ |
| 199 | + const struct can_mcan_config *mcan_cfg = dev->config; |
| 200 | + const struct can_numaker_config *numaker_cfg = mcan_cfg->custom; |
| 201 | + |
| 202 | + return can_mcan_sys_write_reg(numaker_cfg->canfd_base, reg, val); |
| 203 | +} |
| 204 | + |
| 205 | +static int can_numaker_read_mram(const struct device *dev, uint16_t offset, void *dst, size_t len) |
| 206 | +{ |
| 207 | + const struct can_mcan_config *mcan_cfg = dev->config; |
| 208 | + const struct can_numaker_config *numaker_cfg = mcan_cfg->custom; |
| 209 | + |
| 210 | + return can_mcan_sys_read_mram(numaker_cfg->mram, offset, dst, len); |
| 211 | +} |
| 212 | + |
| 213 | +static int can_numaker_write_mram(const struct device *dev, uint16_t offset, const void *src, |
| 214 | + size_t len) |
| 215 | +{ |
| 216 | + const struct can_mcan_config *mcan_cfg = dev->config; |
| 217 | + const struct can_numaker_config *numaker_cfg = mcan_cfg->custom; |
| 218 | + |
| 219 | + return can_mcan_sys_write_mram(numaker_cfg->mram, offset, src, len); |
| 220 | +} |
| 221 | + |
| 222 | +static int can_numaker_clear_mram(const struct device *dev, uint16_t offset, size_t len) |
| 223 | +{ |
| 224 | + const struct can_mcan_config *mcan_cfg = dev->config; |
| 225 | + const struct can_numaker_config *numaker_cfg = mcan_cfg->custom; |
| 226 | + |
| 227 | + return can_mcan_sys_clear_mram(numaker_cfg->mram, offset, len); |
| 228 | +} |
| 229 | + |
| 230 | +static const struct can_mcan_ops can_numaker_ops = { |
| 231 | + .read_reg = can_numaker_read_reg, |
| 232 | + .write_reg = can_numaker_write_reg, |
| 233 | + .read_mram = can_numaker_read_mram, |
| 234 | + .write_mram = can_numaker_write_mram, |
| 235 | + .clear_mram = can_numaker_clear_mram, |
| 236 | +}; |
| 237 | + |
| 238 | +#define NUMAKER_CLKCTRL_DEV_INIT(inst) \ |
| 239 | + .clk_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(inst))), |
| 240 | + |
| 241 | +#define NUMAKER_PINCTRL_DEFINE(inst) \ |
| 242 | + PINCTRL_DT_INST_DEFINE(inst); |
| 243 | +#define NUMAKER_PINCTRL_INIT(inst) \ |
| 244 | + .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), |
| 245 | + |
| 246 | +#define CAN_NUMAKER_INIT(inst) \ |
| 247 | + NUMAKER_PINCTRL_DEFINE(inst); \ |
| 248 | + CAN_MCAN_DT_INST_CALLBACKS_DEFINE(inst, can_numaker_cbs_##inst); \ |
| 249 | + \ |
| 250 | + static void can_numaker_irq_config_func_##inst(const struct device *dev) \ |
| 251 | + { \ |
| 252 | + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, 0, irq), \ |
| 253 | + DT_INST_IRQ_BY_IDX(inst, 0, priority), \ |
| 254 | + can_mcan_line_0_isr, \ |
| 255 | + DEVICE_DT_INST_GET(inst), \ |
| 256 | + 0); \ |
| 257 | + irq_enable(DT_INST_IRQ_BY_IDX(inst, 0, irq)); \ |
| 258 | + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, 1, irq), \ |
| 259 | + DT_INST_IRQ_BY_IDX(inst, 1, priority), \ |
| 260 | + can_mcan_line_1_isr, \ |
| 261 | + DEVICE_DT_INST_GET(inst), \ |
| 262 | + 0); \ |
| 263 | + irq_enable(DT_INST_IRQ_BY_IDX(inst, 1, irq)); \ |
| 264 | + } \ |
| 265 | + \ |
| 266 | + static const struct can_numaker_config can_numaker_config_##inst = { \ |
| 267 | + .canfd_base = CAN_MCAN_DT_INST_MCAN_ADDR(inst), \ |
| 268 | + .mrba = CAN_MCAN_DT_INST_MRBA(inst), \ |
| 269 | + .mram = CAN_MCAN_DT_INST_MRAM_ADDR(inst), \ |
| 270 | + .reset = RESET_DT_SPEC_INST_GET(inst), \ |
| 271 | + .clk_modidx = DT_INST_CLOCKS_CELL(inst, clock_module_index), \ |
| 272 | + .clk_src = DT_INST_CLOCKS_CELL(inst, clock_source), \ |
| 273 | + .clk_div = DT_INST_CLOCKS_CELL(inst, clock_divider), \ |
| 274 | + NUMAKER_CLKCTRL_DEV_INIT(inst) \ |
| 275 | + .irq_config_func = can_numaker_irq_config_func_##inst, \ |
| 276 | + NUMAKER_PINCTRL_INIT(inst) \ |
| 277 | + }; \ |
| 278 | + \ |
| 279 | + static const struct can_mcan_config can_mcan_config_##inst = \ |
| 280 | + CAN_MCAN_DT_CONFIG_INST_GET(inst, \ |
| 281 | + &can_numaker_config_##inst, \ |
| 282 | + &can_numaker_ops, \ |
| 283 | + &can_numaker_cbs_##inst); \ |
| 284 | + \ |
| 285 | + static uint32_t can_numaker_data_##inst; \ |
| 286 | + \ |
| 287 | + static struct can_mcan_data can_mcan_data_##inst = \ |
| 288 | + CAN_MCAN_DATA_INITIALIZER(&can_numaker_data_ ## inst); \ |
| 289 | + \ |
| 290 | + CAN_DEVICE_DT_INST_DEFINE(inst, \ |
| 291 | + &can_numaker_init, \ |
| 292 | + NULL, \ |
| 293 | + &can_mcan_data_##inst, \ |
| 294 | + &can_mcan_config_##inst, \ |
| 295 | + POST_KERNEL, \ |
| 296 | + CONFIG_CAN_INIT_PRIORITY, \ |
| 297 | + &can_numaker_driver_api); \ |
| 298 | + |
| 299 | +DT_INST_FOREACH_STATUS_OKAY(CAN_NUMAKER_INIT); |
0 commit comments