sam: can: CAN driver for SAM0 socs

Driver was based on can_sam. SAMC21 has only 1 interrupt for one
can "output", so can interrupt has to executes two lines of
interrupts.
CAN is configured to use OSC48M clock via GLCK7. GLCK7 is set
by divider configured from dts.

Signed-off-by: Kamil Serwus <kserwus@gmail.com>
This commit is contained in:
Kamil Serwus 2022-11-16 20:27:33 +01:00 committed by Fabio Baltieri
parent d01780fc94
commit 632704e04b
7 changed files with 265 additions and 0 deletions

View file

@ -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)

View file

@ -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"

9
drivers/can/Kconfig.sam0 Normal file
View file

@ -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

227
drivers/can/can_sam0.c Normal file
View file

@ -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 <zephyr/drivers/can.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/irq.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <soc.h>
#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)

View file

@ -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

View file

@ -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

View file

@ -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