drivers: can: mcp2515: Add driver for MCP2515 CAN controller
The MCP2515 is a CAN controller that can be connected via SPI to an host MCU. This driver adds support for the MCP2515 as a new driver in the CAN subsystem. As it is a SPI peripheral it uses a thread for its interrupt handling and the received message filtering is done inside this interrupt thread, as the MCP2515 filter capabilities are not sufficient for the Zephyr CAN interface. The driver was validated with an external CAN logger and the adjusted CAN sample application. Signed-off-by: Karsten Koenig <karsten.koenig.030@gmail.com>
This commit is contained in:
parent
b8774ae8f6
commit
35b9308488
7 changed files with 933 additions and 2 deletions
|
@ -106,6 +106,7 @@
|
|||
/drivers/adc/ @anangl
|
||||
/drivers/bluetooth/ @sjanc @jhedberg @Vudentz
|
||||
/drivers/can/ @alexanderwachter
|
||||
/drivers/can/*mcp2515* @karstenkoenig
|
||||
/drivers/clock_control/*stm32f4* @rsalveti @idlethread
|
||||
/drivers/counter/ @nordic-krch
|
||||
/drivers/display/ @vanwinkeljan
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_sources_ifdef(CONFIG_CAN_STM32 stm32_can.c)
|
||||
zephyr_sources_ifdef(CONFIG_USERSPACE can_handlers.c)
|
||||
zephyr_sources_ifdef(CONFIG_CAN_STM32 stm32_can.c)
|
||||
zephyr_sources_ifdef(CONFIG_CAN_MCP2515 mcp2515.c)
|
||||
zephyr_sources_ifdef(CONFIG_USERSPACE can_handlers.c)
|
||||
|
|
|
@ -36,5 +36,6 @@ config CAN_1
|
|||
Enable CAN controller 1
|
||||
|
||||
source "drivers/can/Kconfig.stm32"
|
||||
source "drivers/can/Kconfig.mcp2515"
|
||||
|
||||
endif # CAN
|
||||
|
|
138
drivers/can/Kconfig.mcp2515
Normal file
138
drivers/can/Kconfig.mcp2515
Normal file
|
@ -0,0 +1,138 @@
|
|||
# Kconfig.mcp2515 - MCP2515 CAN configuration options
|
||||
|
||||
#
|
||||
# Copyright (c) 2018 Karsten Koenig
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
config CAN_MCP2515
|
||||
bool "MCP2515 CAN Driver"
|
||||
depends on SPI
|
||||
help
|
||||
Enable MCP2515 CAN Driver
|
||||
|
||||
if CAN_MCP2515
|
||||
|
||||
config CAN_MCP2515_NAME
|
||||
string "Driver name"
|
||||
default "MCP2515"
|
||||
|
||||
config CAN_MCP2515_OSC_FREQ
|
||||
int "Oscillator frequency"
|
||||
default 8000000
|
||||
help
|
||||
Specify the frequency of the oscillator connected to the MCP2515.
|
||||
|
||||
config CAN_PROP_SEG
|
||||
int "Prop_Seg"
|
||||
default 2
|
||||
range 1 8
|
||||
help
|
||||
Time quantums of propagation segment (ISO 11898-1)
|
||||
|
||||
config CAN_PHASE_SEG1
|
||||
int "Phase_Seg1"
|
||||
default 7
|
||||
range 1 8
|
||||
help
|
||||
Time quantums of phase buffer 1 segment (ISO 11898-1)
|
||||
|
||||
config CAN_PHASE_SEG2
|
||||
int "Phase_Seg2"
|
||||
default 6
|
||||
range 2 8
|
||||
help
|
||||
Time quantums of phase buffer 2 segment (ISO 11898-1)
|
||||
|
||||
config CAN_SJW
|
||||
int "SJW"
|
||||
default 1
|
||||
range 1 4
|
||||
help
|
||||
Resynchronization jump width (ISO 11898-1)
|
||||
|
||||
config CAN_MCP2515_SPI_PORT_NAME
|
||||
string "SPI device where MCP2515 is connected"
|
||||
default "SPI_1"
|
||||
help
|
||||
Specify the device name of the SPI device to which MCP2515 is
|
||||
connected.
|
||||
|
||||
config CAN_MCP2515_SPI_SLAVE
|
||||
int "SPI Slave Select where MCP2515 is connected"
|
||||
default 1
|
||||
help
|
||||
Specify the slave select pin of the SPI to which MCP2515 is
|
||||
connected.
|
||||
|
||||
config CAN_MCP2515_SPI_FREQ
|
||||
int "SPI frequency to use with MCP2515"
|
||||
default 1000000
|
||||
help
|
||||
SPI frequency to use with MCP2515
|
||||
|
||||
config CAN_MCP2515_INT_PORT_NAME
|
||||
string "INT GPIO controller port name"
|
||||
default "GPIO_0"
|
||||
help
|
||||
GPIO port where INT is connected.
|
||||
|
||||
config CAN_MCP2515_INT_PIN
|
||||
int "INT GPIO pin"
|
||||
default 19
|
||||
help
|
||||
GPIO pin where INT is connected.
|
||||
|
||||
config CAN_MCP2515_INT_THREAD_STACK_SIZE
|
||||
int "Stack size for interrupt handler"
|
||||
default 512
|
||||
help
|
||||
Size of the stack used for internal thread which is ran for
|
||||
interrupt handling and incoming packets.
|
||||
|
||||
config CAN_MCP2515_INT_THREAD_PRIO
|
||||
int "Priority for interrupt handler"
|
||||
default 2
|
||||
help
|
||||
Priority level of the internal thread which is ran for
|
||||
interrupt handling and incoming packets.
|
||||
|
||||
config CAN_MCP2515_GPIO_SPI_CS
|
||||
bool "Manage SPI CS through a GPIO pin"
|
||||
help
|
||||
This option is useful if one needs to manage SPI CS through a GPIO
|
||||
pin to by-pass the SPI controller's CS logic.
|
||||
|
||||
if CAN_MCP2515_GPIO_SPI_CS
|
||||
config CAN_MCP2515_SPI_CS_PORT_NAME
|
||||
string "GPIO driver's name to use to drive SPI CS through"
|
||||
help
|
||||
This option is mandatory to set which GPIO controller to use in
|
||||
order to actually emulate the SPI CS.
|
||||
|
||||
config CAN_MCP2515_SPI_CS_PIN
|
||||
int "GPIO PIN to use to drive SPI CS through"
|
||||
default 0
|
||||
help
|
||||
This option is mandatory to set which GPIO pin to use in order
|
||||
to actually emulate the SPI CS.
|
||||
|
||||
endif #CAN_MCP2515_GPIO_SPI_CS
|
||||
|
||||
config CAN_MCP2515_MAX_FILTER
|
||||
int "Maximum number of concurrent active filters"
|
||||
default 5
|
||||
range 1 32
|
||||
help
|
||||
Defines the array size of the callback/msgq pointers.
|
||||
Must be at least the size of concurrent reads.
|
||||
|
||||
|
||||
config CAN_MCP2515_INIT_PRIORITY
|
||||
int "Init priority"
|
||||
default 80
|
||||
help
|
||||
MCP2515 driver initialization priority, must be higher than SPI.
|
||||
|
||||
endif # CAN_MCP2515
|
639
drivers/can/mcp2515.c
Normal file
639
drivers/can/mcp2515.c
Normal file
|
@ -0,0 +1,639 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Karsten Koenig
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <kernel.h>
|
||||
#include <device.h>
|
||||
#include <spi.h>
|
||||
#include <gpio.h>
|
||||
|
||||
#define LOG_LEVEL CONFIG_CAN_LOG_LEVEL
|
||||
#include <logging/log.h>
|
||||
LOG_MODULE_REGISTER(mcp2515_can);
|
||||
|
||||
#include "mcp2515.h"
|
||||
|
||||
static int mcp2515_cmd_soft_reset(struct device *dev)
|
||||
{
|
||||
u8_t cmd_buf[] = { MCP2515_OPCODE_RESET };
|
||||
|
||||
const struct spi_buf tx_buf = {
|
||||
.buf = cmd_buf, .len = sizeof(cmd_buf),
|
||||
};
|
||||
const struct spi_buf_set tx = {
|
||||
.buffers = &tx_buf, .count = 1U
|
||||
};
|
||||
|
||||
return spi_write(DEV_DATA(dev)->spi, &DEV_DATA(dev)->spi_cfg, &tx);
|
||||
}
|
||||
|
||||
static int mcp2515_cmd_bit_modify(struct device *dev, u8_t reg_addr, u8_t mask,
|
||||
u8_t data)
|
||||
{
|
||||
u8_t cmd_buf[] = { MCP2515_OPCODE_BIT_MODIFY, reg_addr, mask, data };
|
||||
|
||||
const struct spi_buf tx_buf = {
|
||||
.buf = cmd_buf, .len = sizeof(cmd_buf),
|
||||
};
|
||||
const struct spi_buf_set tx = {
|
||||
.buffers = &tx_buf, .count = 1U
|
||||
};
|
||||
|
||||
return spi_write(DEV_DATA(dev)->spi, &DEV_DATA(dev)->spi_cfg, &tx);
|
||||
}
|
||||
|
||||
static int mcp2515_cmd_write_reg(struct device *dev, u8_t reg_addr,
|
||||
u8_t *buf_data, u8_t buf_len)
|
||||
{
|
||||
u8_t cmd_buf[] = { MCP2515_OPCODE_WRITE, reg_addr };
|
||||
|
||||
struct spi_buf tx_buf[] = {
|
||||
{ .buf = cmd_buf, .len = sizeof(cmd_buf) },
|
||||
{ .buf = buf_data, .len = buf_len }
|
||||
};
|
||||
const struct spi_buf_set tx = {
|
||||
.buffers = tx_buf, .count = ARRAY_SIZE(tx_buf)
|
||||
};
|
||||
|
||||
return spi_write(DEV_DATA(dev)->spi, &DEV_DATA(dev)->spi_cfg, &tx);
|
||||
}
|
||||
|
||||
static int mcp2515_cmd_read_reg(struct device *dev, u8_t reg_addr,
|
||||
u8_t *buf_data, u8_t buf_len)
|
||||
{
|
||||
u8_t cmd_buf[] = { MCP2515_OPCODE_READ, reg_addr };
|
||||
|
||||
struct spi_buf tx_buf[] = {
|
||||
{ .buf = cmd_buf, .len = sizeof(cmd_buf) },
|
||||
{ .buf = NULL, .len = buf_len }
|
||||
};
|
||||
const struct spi_buf_set tx = {
|
||||
.buffers = tx_buf, .count = ARRAY_SIZE(tx_buf)
|
||||
};
|
||||
struct spi_buf rx_buf[] = {
|
||||
{ .buf = NULL, .len = sizeof(cmd_buf) },
|
||||
{ .buf = buf_data, .len = buf_len }
|
||||
};
|
||||
const struct spi_buf_set rx = {
|
||||
.buffers = rx_buf, .count = ARRAY_SIZE(rx_buf)
|
||||
};
|
||||
|
||||
return spi_transceive(DEV_DATA(dev)->spi, &DEV_DATA(dev)->spi_cfg,
|
||||
&tx, &rx);
|
||||
}
|
||||
|
||||
static u8_t mcp2515_convert_canmode_to_mcp2515mode(enum can_mode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case CAN_NORMAL_MODE:
|
||||
return MCP2515_MODE_NORMAL;
|
||||
case CAN_SILENT_MODE:
|
||||
return MCP2515_MODE_SILENT;
|
||||
case CAN_LOOPBACK_MODE:
|
||||
return MCP2515_MODE_LOOPBACK;
|
||||
default:
|
||||
LOG_ERR("Unsupported CAN Mode %u", mode);
|
||||
return MCP2515_MODE_SILENT;
|
||||
}
|
||||
}
|
||||
|
||||
static void mcp2515_convert_zcanframe_to_mcp2515frame(const struct zcan_frame
|
||||
*source, u8_t *target)
|
||||
{
|
||||
u8_t rtr;
|
||||
u8_t dlc;
|
||||
u8_t data_idx = 0U;
|
||||
|
||||
if (source->id_type == CAN_STANDARD_IDENTIFIER) {
|
||||
target[MCP2515_FRAME_OFFSET_SIDH] = source->std_id >> 3;
|
||||
target[MCP2515_FRAME_OFFSET_SIDL] =
|
||||
(source->std_id & 0x07) << 5;
|
||||
} else {
|
||||
target[MCP2515_FRAME_OFFSET_SIDH] = source->ext_id >> 21;
|
||||
target[MCP2515_FRAME_OFFSET_SIDL] =
|
||||
(((source->ext_id >> 18) & 0x07) << 5) | (BIT(3)) |
|
||||
((source->ext_id >> 16) & 0x03);
|
||||
target[MCP2515_FRAME_OFFSET_EID8] = source->ext_id >> 8;
|
||||
target[MCP2515_FRAME_OFFSET_EID0] = source->ext_id;
|
||||
}
|
||||
|
||||
rtr = (source->rtr == CAN_REMOTEREQUEST) ? BIT(6) : 0;
|
||||
dlc = (source->dlc) & 0x0F;
|
||||
|
||||
target[MCP2515_FRAME_OFFSET_DLC] = rtr | dlc;
|
||||
|
||||
for (; data_idx < 8; data_idx++) {
|
||||
target[MCP2515_FRAME_OFFSET_D0 + data_idx] =
|
||||
source->data[data_idx];
|
||||
}
|
||||
}
|
||||
|
||||
static void mcp2515_convert_mcp2515frame_to_zcanframe(const u8_t *source,
|
||||
struct zcan_frame *target)
|
||||
{
|
||||
u8_t data_idx = 0U;
|
||||
|
||||
if (source[MCP2515_FRAME_OFFSET_SIDL] & BIT(3)) {
|
||||
target->id_type = CAN_EXTENDED_IDENTIFIER;
|
||||
target->ext_id =
|
||||
(source[MCP2515_FRAME_OFFSET_SIDH] << 21) |
|
||||
((source[MCP2515_FRAME_OFFSET_SIDL] >> 5) << 18) |
|
||||
((source[MCP2515_FRAME_OFFSET_SIDL] & 0x03) << 16) |
|
||||
(source[MCP2515_FRAME_OFFSET_EID8] << 8) |
|
||||
source[MCP2515_FRAME_OFFSET_EID0];
|
||||
} else {
|
||||
target->id_type = CAN_STANDARD_IDENTIFIER;
|
||||
target->std_id = (source[MCP2515_FRAME_OFFSET_SIDH] << 3) |
|
||||
(source[MCP2515_FRAME_OFFSET_SIDL] >> 5);
|
||||
}
|
||||
|
||||
target->dlc = source[MCP2515_FRAME_OFFSET_DLC] & 0x0F;
|
||||
target->rtr = source[MCP2515_FRAME_OFFSET_DLC] & BIT(6) ?
|
||||
CAN_REMOTEREQUEST : CAN_DATAFRAME;
|
||||
|
||||
for (; data_idx < 8; data_idx++) {
|
||||
target->data[data_idx] = source[MCP2515_FRAME_OFFSET_D0 +
|
||||
data_idx];
|
||||
}
|
||||
}
|
||||
|
||||
const int mcp2515_set_mode(struct device *dev, u8_t mcp2515_mode)
|
||||
{
|
||||
u8_t canstat;
|
||||
|
||||
mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_CANCTRL,
|
||||
MCP2515_CANCTRL_MODE_MASK,
|
||||
mcp2515_mode << MCP2515_CANCTRL_MODE_POS);
|
||||
mcp2515_cmd_read_reg(dev, MCP2515_ADDR_CANSTAT, &canstat, 1);
|
||||
|
||||
if (((canstat & MCP2515_CANSTAT_MODE_MASK) >> MCP2515_CANSTAT_MODE_POS)
|
||||
!= mcp2515_mode) {
|
||||
LOG_ERR("Failed to set MCP2515 operation mode");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mcp2515_attach(struct device *dev, const struct zcan_filter *filter,
|
||||
void *response_ptr, u8_t is_type_msgq)
|
||||
{
|
||||
struct mcp2515_data *dev_data = DEV_DATA(dev);
|
||||
int filter_idx = 0;
|
||||
|
||||
__ASSERT(response_ptr != NULL, "response_ptr can not be null");
|
||||
|
||||
k_mutex_lock(&dev_data->filter_mutex, K_FOREVER);
|
||||
|
||||
/* find free filter */
|
||||
while ((BIT(filter_idx) & dev_data->filter_usage)
|
||||
&& (filter_idx < CONFIG_CAN_MCP2515_MAX_FILTER)) {
|
||||
filter_idx++;
|
||||
}
|
||||
|
||||
/* setup filter */
|
||||
if (filter_idx < CONFIG_CAN_MCP2515_MAX_FILTER) {
|
||||
dev_data->filter_usage |= BIT(filter_idx);
|
||||
if (is_type_msgq) {
|
||||
dev_data->filter_response_type |= BIT(filter_idx);
|
||||
} else {
|
||||
dev_data->filter_response_type &= ~BIT(filter_idx);
|
||||
}
|
||||
dev_data->filter[filter_idx] = *filter;
|
||||
dev_data->filter_response[filter_idx] = response_ptr;
|
||||
} else {
|
||||
filter_idx = CAN_NO_FREE_FILTER;
|
||||
}
|
||||
|
||||
k_mutex_unlock(&dev_data->filter_mutex);
|
||||
|
||||
return filter_idx;
|
||||
}
|
||||
|
||||
static int mcp2515_configure(struct device *dev, enum can_mode mode,
|
||||
u32_t bitrate)
|
||||
{
|
||||
const struct mcp2515_config *dev_cfg = DEV_CFG(dev);
|
||||
|
||||
/* CNF3, CNF2, CNF1, CANINTE */
|
||||
u8_t config_buf[4];
|
||||
|
||||
const u8_t bit_length = 1 + dev_cfg->tq_prop + dev_cfg->tq_bs1 +
|
||||
dev_cfg->tq_bs2;
|
||||
|
||||
/* CNF1; SJW<7:6> | BRP<5:0> */
|
||||
u8_t brp =
|
||||
(CONFIG_CAN_MCP2515_OSC_FREQ / (bit_length * bitrate * 2)) - 1;
|
||||
const u8_t sjw = (dev_cfg->tq_sjw - 1) << 6;
|
||||
u8_t cnf1 = sjw | brp;
|
||||
|
||||
/* CNF2; BTLMODE<7>|SAM<6>|PHSEG1<5:3>|PRSEG<2:0> */
|
||||
const u8_t btlmode = 1 << 7;
|
||||
const u8_t sam = 0 << 6;
|
||||
const u8_t phseg1 = (dev_cfg->tq_bs1 - 1) << 3;
|
||||
const u8_t prseg = (dev_cfg->tq_prop - 1);
|
||||
|
||||
const u8_t cnf2 = btlmode | sam | phseg1 | prseg;
|
||||
|
||||
/* CNF3; SOF<7>|WAKFIL<6>|UND<5:3>|PHSEG2<2:0> */
|
||||
const u8_t sof = 0 << 7;
|
||||
const u8_t wakfil = 0 << 6;
|
||||
const u8_t und = 0 << 3;
|
||||
const u8_t phseg2 = (dev_cfg->tq_bs2 - 1);
|
||||
|
||||
const u8_t cnf3 = sof | wakfil | und | phseg2;
|
||||
|
||||
/* CANINTE
|
||||
* MERRE<7>:WAKIE<6>:ERRIE<5>:TX2IE<4>:TX1IE<3>:TX0IE<2>:RX1IE<1>:
|
||||
* RX0IE<0>
|
||||
* all TX and RX buffer interrupts enabled
|
||||
*/
|
||||
const u8_t caninte = BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0);
|
||||
|
||||
/* Receive everything, filtering done in driver, RXB0 roll over into
|
||||
* RXB1 */
|
||||
const u8_t rx0_ctrl = BIT(6) | BIT(5) | BIT(2);
|
||||
const u8_t rx1_ctrl = BIT(6) | BIT(5);
|
||||
|
||||
__ASSERT((cfg->tq_sjw >= 1) && (cfg->tq_sjw <= 4), "1 <= SJW <= 4");
|
||||
__ASSERT((cfg->tq_prop >= 1) && (cfg->tq_prop <= 8), "1 <= PROP <= 8");
|
||||
__ASSERT((cfg->tq_bs1 >= 1) && (cfg->tq_bs1 <= 8), "1 <= BS1 <= 8");
|
||||
__ASSERT((cfg->tq_bs2 >= 2) && (cfg->tq_bs2 <= 8), "2 <= BS2 <= 8");
|
||||
__ASSERT(cfg->tq_prop + cfg->tq_bs1 >= cfg->tq_bs2,
|
||||
"PROP + BS1 >= BS2");
|
||||
__ASSERT(cfg->tq_bs2 > cfg->tq_sjw, "BS2 > SJW");
|
||||
|
||||
if (CONFIG_CAN_MCP2515_OSC_FREQ % (bit_length * bitrate * 2)) {
|
||||
LOG_ERR("Prescaler is not a natural number! "
|
||||
"prescaler = osc_rate / ((PROP + SEG1 + SEG2 + 1) "
|
||||
"* bitrate * 2)\n"
|
||||
"prescaler = %d / ((%d + %d + %d + 1) * %d * 2)",
|
||||
CONFIG_CAN_MCP2515_OSC_FREQ, dev_cfg->tq_prop,
|
||||
dev_cfg->tq_bs1, dev_cfg->tq_bs2, bitrate);
|
||||
}
|
||||
|
||||
config_buf[0] = cnf3;
|
||||
config_buf[1] = cnf2;
|
||||
config_buf[2] = cnf1;
|
||||
config_buf[3] = caninte;
|
||||
|
||||
/* will enter configuration mode automatically */
|
||||
mcp2515_cmd_soft_reset(dev);
|
||||
|
||||
mcp2515_cmd_write_reg(dev, MCP2515_ADDR_CNF3, config_buf,
|
||||
sizeof(config_buf));
|
||||
|
||||
mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_RXB0CTRL, rx0_ctrl, rx0_ctrl);
|
||||
mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_RXB1CTRL, rx1_ctrl, rx1_ctrl);
|
||||
|
||||
return mcp2515_set_mode(dev,
|
||||
mcp2515_convert_canmode_to_mcp2515mode(mode));
|
||||
}
|
||||
|
||||
int mcp2515_send(struct device *dev, struct zcan_frame *msg, s32_t timeout,
|
||||
can_tx_callback_t callback)
|
||||
{
|
||||
struct mcp2515_data *dev_data = DEV_DATA(dev);
|
||||
u8_t tx_idx = 0U;
|
||||
u8_t addr_tx_ctrl;
|
||||
u8_t tx_frame[MCP2515_FRAME_LEN];
|
||||
|
||||
if (k_sem_take(&dev_data->tx_sem, timeout) != 0) {
|
||||
return CAN_TIMEOUT;
|
||||
}
|
||||
|
||||
k_mutex_lock(&dev_data->tx_mutex, K_FOREVER);
|
||||
|
||||
/* find a free tx slot */
|
||||
for (; tx_idx < MCP2515_TX_CNT; tx_idx++) {
|
||||
if ((BIT(tx_idx) & dev_data->tx_busy_map) == 0) {
|
||||
dev_data->tx_busy_map |= BIT(tx_idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
k_mutex_unlock(&dev_data->tx_mutex);
|
||||
|
||||
if (tx_idx == MCP2515_TX_CNT) {
|
||||
LOG_WRN("no free tx slot available");
|
||||
return CAN_TX_ERR;
|
||||
}
|
||||
|
||||
dev_data->tx_cb[tx_idx].cb = callback;
|
||||
|
||||
addr_tx_ctrl = MCP2515_ADDR_TXB0CTRL +
|
||||
(tx_idx * MCP2515_ADDR_OFFSET_FRAME2FRAME);
|
||||
|
||||
mcp2515_convert_zcanframe_to_mcp2515frame(msg, tx_frame);
|
||||
mcp2515_cmd_write_reg(dev,
|
||||
addr_tx_ctrl + MCP2515_ADDR_OFFSET_CTRL2FRAME,
|
||||
tx_frame, sizeof(tx_frame));
|
||||
/* request tx slot transmission */
|
||||
mcp2515_cmd_bit_modify(dev, addr_tx_ctrl, MCP2515_TXCTRL_TXREQ,
|
||||
MCP2515_TXCTRL_TXREQ);
|
||||
|
||||
if (callback == NULL) {
|
||||
k_sem_take(&dev_data->tx_cb[tx_idx].sem, K_FOREVER);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mcp2515_attach_msgq(struct device *dev, struct k_msgq *msgq,
|
||||
const struct zcan_filter *filter)
|
||||
{
|
||||
return mcp2515_attach(dev, filter, (void *) msgq, 1);
|
||||
}
|
||||
|
||||
int mcp2515_attach_isr(struct device *dev, can_rx_callback_t isr,
|
||||
const struct zcan_filter *filter)
|
||||
{
|
||||
return mcp2515_attach(dev, filter, (void *) isr, 0);
|
||||
}
|
||||
|
||||
void mcp2515_detach(struct device *dev, int filter_nr)
|
||||
{
|
||||
struct mcp2515_data *dev_data = DEV_DATA(dev);
|
||||
|
||||
k_mutex_lock(&dev_data->filter_mutex, K_FOREVER);
|
||||
dev_data->filter_usage &= ~BIT(filter_nr);
|
||||
k_mutex_unlock(&dev_data->filter_mutex);
|
||||
}
|
||||
|
||||
static u8_t mcp2515_filter_match(struct zcan_frame *msg,
|
||||
struct zcan_filter *filter)
|
||||
{
|
||||
if (msg->id_type != filter->id_type) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((msg->rtr ^ filter->rtr) & filter->rtr_mask) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (msg->id_type == CAN_STANDARD_IDENTIFIER) {
|
||||
if ((msg->std_id ^ filter->std_id) & filter->std_id_mask) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if ((msg->ext_id ^ filter->ext_id) & filter->ext_id_mask) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void mcp2515_rx_filter(struct device *dev, struct zcan_frame *msg)
|
||||
{
|
||||
struct mcp2515_data *dev_data = DEV_DATA(dev);
|
||||
u8_t filter_idx = 0U;
|
||||
|
||||
k_mutex_lock(&dev_data->filter_mutex, K_FOREVER);
|
||||
|
||||
for (; filter_idx < CONFIG_CAN_MCP2515_MAX_FILTER; filter_idx++) {
|
||||
if (!(BIT(filter_idx) & dev_data->filter_usage)) {
|
||||
continue; /* filter slot empty */
|
||||
}
|
||||
|
||||
if (!mcp2515_filter_match(msg,
|
||||
&dev_data->filter[filter_idx])) {
|
||||
continue; /* filter did not match */
|
||||
}
|
||||
|
||||
if (dev_data->filter_response_type & BIT(filter_idx)) {
|
||||
struct k_msgq *msg_q =
|
||||
dev_data->filter_response[filter_idx];
|
||||
|
||||
k_msgq_put(msg_q, msg, K_NO_WAIT);
|
||||
} else {
|
||||
can_rx_callback_t callback =
|
||||
dev_data->filter_response[filter_idx];
|
||||
|
||||
callback(msg);
|
||||
}
|
||||
}
|
||||
|
||||
k_mutex_unlock(&dev_data->filter_mutex);
|
||||
}
|
||||
|
||||
static void mcp2515_rx(struct device *dev, u8_t rx_idx)
|
||||
{
|
||||
struct zcan_frame msg;
|
||||
u8_t rx_frame[MCP2515_FRAME_LEN];
|
||||
u8_t addr_rx_ctrl = MCP2515_ADDR_RXB0CTRL +
|
||||
(rx_idx * MCP2515_ADDR_OFFSET_FRAME2FRAME);
|
||||
|
||||
/* Fetch rx buffer */
|
||||
mcp2515_cmd_read_reg(dev,
|
||||
addr_rx_ctrl + MCP2515_ADDR_OFFSET_CTRL2FRAME,
|
||||
rx_frame, sizeof(rx_frame));
|
||||
mcp2515_convert_mcp2515frame_to_zcanframe(rx_frame, &msg);
|
||||
mcp2515_rx_filter(dev, &msg);
|
||||
}
|
||||
|
||||
static void mcp2515_tx_done(struct device *dev, u8_t tx_idx)
|
||||
{
|
||||
struct mcp2515_data *dev_data = DEV_DATA(dev);
|
||||
|
||||
if (dev_data->tx_cb[tx_idx].cb == NULL) {
|
||||
k_sem_give(&dev_data->tx_cb[tx_idx].sem);
|
||||
} else {
|
||||
dev_data->tx_cb[tx_idx].cb(0);
|
||||
}
|
||||
|
||||
k_mutex_lock(&dev_data->tx_mutex, K_FOREVER);
|
||||
dev_data->tx_busy_map &= ~BIT(tx_idx);
|
||||
k_mutex_unlock(&dev_data->tx_mutex);
|
||||
k_sem_give(&dev_data->tx_sem);
|
||||
}
|
||||
|
||||
static void mcp2515_handle_interrupts(struct device *dev)
|
||||
{
|
||||
u8_t canintf;
|
||||
|
||||
mcp2515_cmd_read_reg(dev, MCP2515_ADDR_CANINTF, &canintf, 1);
|
||||
|
||||
while (canintf != 0) {
|
||||
if (canintf & MCP2515_CANINTF_RX0IF) {
|
||||
mcp2515_rx(dev, 0);
|
||||
}
|
||||
|
||||
if (canintf & MCP2515_CANINTF_RX1IF) {
|
||||
mcp2515_rx(dev, 1);
|
||||
}
|
||||
|
||||
if (canintf & MCP2515_CANINTF_TX0IF) {
|
||||
mcp2515_tx_done(dev, 0);
|
||||
}
|
||||
|
||||
if (canintf & MCP2515_CANINTF_TX0IF) {
|
||||
mcp2515_tx_done(dev, 1);
|
||||
}
|
||||
|
||||
if (canintf & MCP2515_CANINTF_TX0IF) {
|
||||
mcp2515_tx_done(dev, 2);
|
||||
}
|
||||
|
||||
/* clear the flags we handled */
|
||||
mcp2515_cmd_bit_modify(dev, MCP2515_ADDR_CANINTF, canintf,
|
||||
~canintf);
|
||||
|
||||
/* check that no new interrupts happened while clearing known
|
||||
* ones */
|
||||
mcp2515_cmd_read_reg(dev, MCP2515_ADDR_CANINTF, &canintf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void mcp2515_int_thread(struct device *dev)
|
||||
{
|
||||
struct mcp2515_data *dev_data = DEV_DATA(dev);
|
||||
|
||||
while (1) {
|
||||
k_sem_take(&dev_data->int_sem, K_FOREVER);
|
||||
mcp2515_handle_interrupts(dev);
|
||||
}
|
||||
}
|
||||
|
||||
static void mcp2515_int_gpio_callback(struct device *dev,
|
||||
struct gpio_callback *cb, u32_t pins)
|
||||
{
|
||||
struct mcp2515_data *dev_data =
|
||||
CONTAINER_OF(cb, struct mcp2515_data, int_gpio_cb);
|
||||
|
||||
k_sem_give(&dev_data->int_sem);
|
||||
}
|
||||
|
||||
static const struct can_driver_api can_api_funcs = {
|
||||
.configure = mcp2515_configure,
|
||||
.send = mcp2515_send,
|
||||
.attach_msgq = mcp2515_attach_msgq,
|
||||
.attach_isr = mcp2515_attach_isr,
|
||||
.detach = mcp2515_detach
|
||||
};
|
||||
|
||||
|
||||
static int mcp2515_init(struct device *dev)
|
||||
{
|
||||
const struct mcp2515_config *dev_cfg = DEV_CFG(dev);
|
||||
struct mcp2515_data *dev_data = DEV_DATA(dev);
|
||||
|
||||
k_sem_init(&dev_data->int_sem, 0, UINT_MAX);
|
||||
k_mutex_init(&dev_data->tx_mutex);
|
||||
k_sem_init(&dev_data->tx_sem, 3, 3);
|
||||
k_sem_init(&dev_data->tx_cb[0].sem, 0, 1);
|
||||
k_sem_init(&dev_data->tx_cb[1].sem, 0, 1);
|
||||
k_sem_init(&dev_data->tx_cb[2].sem, 0, 1);
|
||||
k_mutex_init(&dev_data->filter_mutex);
|
||||
|
||||
/* SPI config */
|
||||
dev_data->spi_cfg.operation = SPI_WORD_SET(8);
|
||||
dev_data->spi_cfg.frequency = dev_cfg->spi_freq;
|
||||
dev_data->spi_cfg.slave = dev_cfg->spi_slave;
|
||||
|
||||
dev_data->spi = device_get_binding(dev_cfg->spi_port);
|
||||
if (!dev_data->spi) {
|
||||
LOG_ERR("SPI master port %s not found", dev_cfg->spi_port);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CAN_MCP2515_GPIO_SPI_CS
|
||||
dev_data->spi_cs_ctrl.gpio_dev =
|
||||
device_get_binding(dev_cfg->spi_cs_port);
|
||||
if (!dev_data->spi_cs_ctrl.gpio_dev) {
|
||||
LOG_ERR("Unable to get GPIO SPI CS device");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev_data->spi_cs_ctrl.gpio_pin = dev_cfg->spi_cs_pin;
|
||||
dev_data->spi_cs_ctrl.delay = 0U;
|
||||
|
||||
dev_data->spi_cfg.cs = &dev_data->spi_cs_ctrl;
|
||||
#else
|
||||
dev_data->spi_cfg.cs = NULL;
|
||||
#endif /* CAN_MCP2515_GPIO_SPI_CS */
|
||||
|
||||
/* Reset MCP2515 */
|
||||
if (mcp2515_cmd_soft_reset(dev)) {
|
||||
LOG_ERR("Soft-reset failed");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Initialize interrupt handling */
|
||||
dev_data->int_gpio = device_get_binding(dev_cfg->int_port);
|
||||
if (dev_data->int_gpio == NULL) {
|
||||
LOG_ERR("GPIO port %s not found", dev_cfg->int_port);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (gpio_pin_configure(dev_data->int_gpio, dev_cfg->int_pin,
|
||||
(GPIO_DIR_IN | GPIO_INT | GPIO_INT_EDGE
|
||||
| GPIO_INT_ACTIVE_LOW | GPIO_INT_DEBOUNCE))) {
|
||||
LOG_ERR("Unable to configure GPIO pin %u", dev_cfg->int_pin);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
gpio_init_callback(&(dev_data->int_gpio_cb), mcp2515_int_gpio_callback,
|
||||
BIT(dev_cfg->int_pin));
|
||||
|
||||
if (gpio_add_callback(dev_data->int_gpio, &(dev_data->int_gpio_cb))) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (gpio_pin_enable_callback(dev_data->int_gpio, dev_cfg->int_pin)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
k_thread_create(&dev_data->int_thread, dev_data->int_thread_stack,
|
||||
dev_cfg->int_thread_stack_size,
|
||||
(k_thread_entry_t) mcp2515_int_thread, (void *)dev,
|
||||
NULL, NULL, K_PRIO_COOP(dev_cfg->int_thread_priority),
|
||||
0, K_NO_WAIT);
|
||||
|
||||
(void)memset(dev_data->filter_response, 0,
|
||||
sizeof(dev_data->filter_response));
|
||||
(void)memset(dev_data->filter, 0, sizeof(dev_data->filter));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CAN_1
|
||||
|
||||
static K_THREAD_STACK_DEFINE(mcp2515_int_thread_stack,
|
||||
CONFIG_CAN_MCP2515_INT_THREAD_STACK_SIZE);
|
||||
|
||||
static struct mcp2515_data mcp2515_data_1 = {
|
||||
.int_thread_stack = mcp2515_int_thread_stack,
|
||||
.tx_cb[0].cb = NULL,
|
||||
.tx_cb[1].cb = NULL,
|
||||
.tx_cb[2].cb = NULL,
|
||||
.tx_busy_map = 0U,
|
||||
.filter_usage = 0U,
|
||||
.filter_response_type = 0U,
|
||||
};
|
||||
|
||||
static const struct mcp2515_config mcp2515_config_1 = {
|
||||
.spi_port = CONFIG_CAN_MCP2515_SPI_PORT_NAME,
|
||||
.spi_freq = CONFIG_CAN_MCP2515_SPI_FREQ,
|
||||
.spi_slave = CONFIG_CAN_MCP2515_SPI_SLAVE,
|
||||
.int_pin = CONFIG_CAN_MCP2515_INT_PIN,
|
||||
.int_port = CONFIG_CAN_MCP2515_INT_PORT_NAME,
|
||||
.int_thread_stack_size = CONFIG_CAN_MCP2515_INT_THREAD_STACK_SIZE,
|
||||
.int_thread_priority = CONFIG_CAN_MCP2515_INT_THREAD_PRIO,
|
||||
#ifdef CONFIG_CAN_MCP2515_GPIO_SPI_CS
|
||||
.spi_cs_pin = CONFIG_CAN_MCP2515_SPI_CS_PIN,
|
||||
.spi_cs_port = CONFIG_CAN_MCP2515_SPI_CS_PORT_NAME,
|
||||
#endif /* CAN_MCP2515_GPIO_SPI_CS */
|
||||
.tq_sjw = CONFIG_CAN_SJW,
|
||||
.tq_prop = CONFIG_CAN_PROP_SEG,
|
||||
.tq_bs1 = CONFIG_CAN_PHASE_SEG1,
|
||||
.tq_bs2 = CONFIG_CAN_PHASE_SEG2,
|
||||
};
|
||||
|
||||
DEVICE_AND_API_INIT(can_mcp2515_1, CONFIG_CAN_MCP2515_NAME, &mcp2515_init,
|
||||
&mcp2515_data_1, &mcp2515_config_1, POST_KERNEL,
|
||||
CONFIG_CAN_MCP2515_INIT_PRIORITY, &can_api_funcs);
|
||||
|
||||
#endif /* CONFIG_CAN_1 */
|
131
drivers/can/mcp2515.h
Normal file
131
drivers/can/mcp2515.h
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Karsten Koenig
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _MCP2515_H_
|
||||
#define _MCP2515_H_
|
||||
|
||||
#include <can.h>
|
||||
|
||||
#define MCP2515_TX_CNT 3
|
||||
#define MCP2515_FRAME_LEN 13
|
||||
|
||||
#define DEV_CFG(dev) \
|
||||
((const struct mcp2515_config *const)(dev)->config->config_info)
|
||||
#define DEV_DATA(dev) ((struct mcp2515_data *const)(dev)->driver_data)
|
||||
|
||||
struct mcp2515_tx_cb {
|
||||
struct k_sem sem;
|
||||
can_tx_callback_t cb;
|
||||
};
|
||||
|
||||
struct mcp2515_data {
|
||||
/* spi device data */
|
||||
struct device *spi;
|
||||
struct spi_config spi_cfg;
|
||||
#ifdef CONFIG_CAN_MCP2515_GPIO_SPI_CS
|
||||
struct spi_cs_control spi_cs_ctrl;
|
||||
#endif
|
||||
|
||||
/* interrupt data */
|
||||
struct device *int_gpio;
|
||||
struct gpio_callback int_gpio_cb;
|
||||
struct k_thread int_thread;
|
||||
k_thread_stack_t *int_thread_stack;
|
||||
struct k_sem int_sem;
|
||||
|
||||
/* tx data */
|
||||
struct k_sem tx_sem;
|
||||
struct k_mutex tx_mutex;
|
||||
struct mcp2515_tx_cb tx_cb[MCP2515_TX_CNT];
|
||||
u8_t tx_busy_map;
|
||||
|
||||
/* filter data */
|
||||
struct k_mutex filter_mutex;
|
||||
u32_t filter_usage;
|
||||
u32_t filter_response_type;
|
||||
void *filter_response[CONFIG_CAN_MCP2515_MAX_FILTER];
|
||||
struct zcan_filter filter[CONFIG_CAN_MCP2515_MAX_FILTER];
|
||||
};
|
||||
|
||||
struct mcp2515_config {
|
||||
/* spi configuration */
|
||||
const char *spi_port;
|
||||
u8_t spi_cs_pin;
|
||||
const char *spi_cs_port;
|
||||
u32_t spi_freq;
|
||||
u8_t spi_slave;
|
||||
|
||||
/* interrupt configuration */
|
||||
u8_t int_pin;
|
||||
const char *int_port;
|
||||
size_t int_thread_stack_size;
|
||||
int int_thread_priority;
|
||||
|
||||
/* CAN timing */
|
||||
u8_t tq_sjw;
|
||||
u8_t tq_prop;
|
||||
u8_t tq_bs1;
|
||||
u8_t tq_bs2;
|
||||
};
|
||||
|
||||
/* MCP2515 Opcodes */
|
||||
#define MCP2515_OPCODE_WRITE 0x02
|
||||
#define MCP2515_OPCODE_READ 0x03
|
||||
#define MCP2515_OPCODE_BIT_MODIFY 0x05
|
||||
#define MCP2515_OPCODE_READ_STATUS 0xA0
|
||||
#define MCP2515_OPCODE_RESET 0xC0
|
||||
|
||||
/* MCP2515 Registers */
|
||||
#define MCP2515_ADDR_CANSTAT 0x0E
|
||||
#define MCP2515_ADDR_CANCTRL 0x0F
|
||||
#define MCP2515_ADDR_CNF3 0x28
|
||||
#define MCP2515_ADDR_CNF2 0x29
|
||||
#define MCP2515_ADDR_CNF1 0x2A
|
||||
#define MCP2515_ADDR_CANINTE 0x2B
|
||||
#define MCP2515_ADDR_CANINTF 0x2C
|
||||
#define MCP2515_ADDR_TXB0CTRL 0x30
|
||||
#define MCP2515_ADDR_TXB1CTRL 0x40
|
||||
#define MCP2515_ADDR_TXB2CTRL 0x50
|
||||
#define MCP2515_ADDR_RXB0CTRL 0x60
|
||||
#define MCP2515_ADDR_RXB1CTRL 0x70
|
||||
|
||||
#define MCP2515_ADDR_OFFSET_FRAME2FRAME 0x10
|
||||
#define MCP2515_ADDR_OFFSET_CTRL2FRAME 0x01
|
||||
|
||||
/* MCP2515 Operation Modes */
|
||||
#define MCP2515_MODE_NORMAL 0x00
|
||||
#define MCP2515_MODE_LOOPBACK 0x02
|
||||
#define MCP2515_MODE_SILENT 0x03
|
||||
#define MCP2515_MODE_CONFIGURATION 0x04
|
||||
|
||||
/* MCP2515_FRAME_OFFSET */
|
||||
#define MCP2515_FRAME_OFFSET_SIDH 0
|
||||
#define MCP2515_FRAME_OFFSET_SIDL 1
|
||||
#define MCP2515_FRAME_OFFSET_EID8 2
|
||||
#define MCP2515_FRAME_OFFSET_EID0 3
|
||||
#define MCP2515_FRAME_OFFSET_DLC 4
|
||||
#define MCP2515_FRAME_OFFSET_D0 5
|
||||
|
||||
/* MCP2515_CANINTF */
|
||||
#define MCP2515_CANINTF_RX0IF BIT(0)
|
||||
#define MCP2515_CANINTF_RX1IF BIT(1)
|
||||
#define MCP2515_CANINTF_TX0IF BIT(2)
|
||||
#define MCP2515_CANINTF_TX1IF BIT(3)
|
||||
#define MCP2515_CANINTF_TX2IF BIT(4)
|
||||
#define MCP2515_CANINTF_ERRIF BIT(5)
|
||||
#define MCP2515_CANINTF_WAKIF BIT(6)
|
||||
#define MCP2515_CANINTF_MERRF BIT(7)
|
||||
|
||||
|
||||
#define MCP2515_TXCTRL_TXREQ BIT(3)
|
||||
|
||||
#define MCP2515_CANSTAT_MODE_POS 5
|
||||
#define MCP2515_CANSTAT_MODE_MASK (0x07 << MCP2515_CANSTAT_MODE_POS)
|
||||
#define MCP2515_CANCTRL_MODE_POS 5
|
||||
#define MCP2515_CANCTRL_MODE_MASK (0x07 << MCP2515_CANCTRL_MODE_POS)
|
||||
|
||||
#endif /*_MCP2515_H_*/
|
20
dts/bindings/can/mcp,mcp2515.yaml
Normal file
20
dts/bindings/can/mcp,mcp2515.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: MCP2515 CAN
|
||||
id: mcp,mcp2515-can
|
||||
version: 0.1
|
||||
|
||||
description: >
|
||||
This binding gives a base representation of the MCP2515 SPI CAN controller
|
||||
|
||||
inherits:
|
||||
!include can.yaml
|
||||
!include spi-device.yaml
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
type: string
|
||||
category: required
|
||||
description: compatible strings
|
||||
constraint: "mcp,mcp2515"
|
||||
|
||||
...
|
Loading…
Add table
Add a link
Reference in a new issue