diff --git a/CODEOWNERS b/CODEOWNERS index 53fa793106b..c39c9e19b4f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/drivers/can/CMakeLists.txt b/drivers/can/CMakeLists.txt index 3a0866a79bf..7f598a0e272 100644 --- a/drivers/can/CMakeLists.txt +++ b/drivers/can/CMakeLists.txt @@ -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) diff --git a/drivers/can/Kconfig b/drivers/can/Kconfig index 8bb2a99eb44..73155db7851 100644 --- a/drivers/can/Kconfig +++ b/drivers/can/Kconfig @@ -36,5 +36,6 @@ config CAN_1 Enable CAN controller 1 source "drivers/can/Kconfig.stm32" +source "drivers/can/Kconfig.mcp2515" endif # CAN diff --git a/drivers/can/Kconfig.mcp2515 b/drivers/can/Kconfig.mcp2515 new file mode 100644 index 00000000000..671eaeb0092 --- /dev/null +++ b/drivers/can/Kconfig.mcp2515 @@ -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 diff --git a/drivers/can/mcp2515.c b/drivers/can/mcp2515.c new file mode 100644 index 00000000000..4b4a9d9c7a8 --- /dev/null +++ b/drivers/can/mcp2515.c @@ -0,0 +1,639 @@ +/* + * Copyright (c) 2018 Karsten Koenig + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_CAN_LOG_LEVEL +#include +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 */ diff --git a/drivers/can/mcp2515.h b/drivers/can/mcp2515.h new file mode 100644 index 00000000000..092c42649ee --- /dev/null +++ b/drivers/can/mcp2515.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2018 Karsten Koenig + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#ifndef _MCP2515_H_ +#define _MCP2515_H_ + +#include + +#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_*/ diff --git a/dts/bindings/can/mcp,mcp2515.yaml b/dts/bindings/can/mcp,mcp2515.yaml new file mode 100644 index 00000000000..0df2948c85d --- /dev/null +++ b/dts/bindings/can/mcp,mcp2515.yaml @@ -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" + +...