From a8c83c519c2bf72c25b600ddf8a1317f39148a47 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Wed, 29 Jun 2022 18:07:51 -0400 Subject: [PATCH] stm32g0: add fdcan support from Klipper Signed-off-by: Eric Callahan --- src/stm32/Kconfig | 11 +- src/stm32/Makefile | 1 + src/stm32/fdcan.c | 266 ++++++++++++++++++++++++++++++++++++++++++++ src/stm32/stm32g0.c | 2 + 4 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 src/stm32/fdcan.c diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index f8c64fe..95978f5 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -105,6 +105,9 @@ config HAVE_STM32_USBOTG config HAVE_STM32_CANBUS bool default y if MACH_STM32F1 || MACH_STM32F2 || MACH_STM32F4x5 || MACH_STM32F446 || MACH_STM32F0x2 +config HAVE_STM32_FDCANBUS + bool + default y if MACH_STM32G0 config MCU string @@ -304,7 +307,7 @@ choice select SERIAL config STM32_CANBUS_PA11_PA12 bool "CAN bus (on PA11/PA12)" - depends on HAVE_STM32_CANBUS + depends on HAVE_STM32_CANBUS || HAVE_STM32_FDCANBUS select CANSERIAL config STM32_CANBUS_PA11_PA12_REMAP bool "CAN bus (on PA9/PA10)" if LOW_LEVEL_OPTIONS @@ -312,7 +315,7 @@ choice select CANSERIAL config STM32_CANBUS_PB8_PB9 bool "CAN bus (on PB8/PB9)" if LOW_LEVEL_OPTIONS - depends on HAVE_STM32_CANBUS + depends on HAVE_STM32_CANBUS || HAVE_STM32_FDCANBUS select CANSERIAL config STM32_CANBUS_PI9_PH13 bool "CAN bus (on PI9/PH13)" if LOW_LEVEL_OPTIONS @@ -330,6 +333,10 @@ choice bool "CAN bus (on PD0/PD1)" if LOW_LEVEL_OPTIONS depends on HAVE_STM32_CANBUS select CANSERIAL + config STM32_CANBUS_PB0_PB1 + bool "CAN bus (on PB0/PB1)" + depends on HAVE_STM32_FDCANBUS + select CANSERIAL endchoice ###################################################################### diff --git a/src/stm32/Makefile b/src/stm32/Makefile index fc39af8..7d2fb4f 100644 --- a/src/stm32/Makefile +++ b/src/stm32/Makefile @@ -55,6 +55,7 @@ serial-src-$(CONFIG_MACH_STM32H7) := stm32/stm32h7_serial.c src-$(CONFIG_SERIAL) += $(serial-src-y) generic/serial_irq.c canbus-src-y := generic/canserial.c ../lib/fast-hash/fasthash.c canbus-src-$(CONFIG_HAVE_STM32_CANBUS) += stm32/can.c +canbus-src-$(CONFIG_HAVE_STM32_FDCANBUS) += stm32/fdcan.c src-$(CONFIG_CANSERIAL) += $(canbus-src-y) generic/canbus.c stm32/chipid.c # Binary output file rules diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c new file mode 100644 index 0000000..c142420 --- /dev/null +++ b/src/stm32/fdcan.c @@ -0,0 +1,266 @@ +// Serial over CAN emulation for STM32 boards. +// +// Copyright (C) 2019 Eug Krashtan +// Copyright (C) 2020 Pontus Borg +// Copyright (C) 2021 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // memcpy +#include "autoconf.h" // CONFIG_MACH_STM32F1 +#include "board/irq.h" // irq_disable +#include "command.h" // DECL_CONSTANT_STR +#include "generic/armcm_boot.h" // armcm_enable_irq +#include "generic/canbus.h" // canbus_notify_tx +#include "generic/canserial.h" // CANBUS_ID_ADMIN +#include "generic/serial_irq.h" // serial_rx_byte +#include "internal.h" // enable_pclock +#include "sched.h" // DECL_INIT + +typedef struct +{ + __IO uint32_t id_section; + __IO uint32_t dlc_section; + __IO uint32_t data[64 / 4]; +}FDCAN_FIFO_TypeDef; + +#define FDCAN_XTD (1<<30) +#define FDCAN_RTR (1<<29) + +typedef struct +{ + __IO uint32_t FLS[28]; // Filter list standard + __IO uint32_t FLE[16]; // Filter list extended + FDCAN_FIFO_TypeDef RXF0[3]; + FDCAN_FIFO_TypeDef RXF1[3]; + __IO uint32_t TEF[6]; // Tx event FIFO + FDCAN_FIFO_TypeDef TXFIFO[3]; +}FDCAN_MSG_RAM_TypeDef; + +typedef struct +{ + FDCAN_MSG_RAM_TypeDef fdcan1; + FDCAN_MSG_RAM_TypeDef fdcan2; +}FDCAN_RAM_TypeDef; + +FDCAN_RAM_TypeDef *fdcan_ram = (FDCAN_RAM_TypeDef *)(SRAMCAN_BASE); + +#define FDCAN_IE_TC (FDCAN_IE_TCE | FDCAN_IE_TCFE | FDCAN_IE_TFEE) + +#if CONFIG_STM32_CANBUS_PA11_PA12 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PA11,PA12"); + #define GPIO_Rx GPIO('A', 11) + #define GPIO_Tx GPIO('A', 12) +#elif CONFIG_STM32_CANBUS_PB8_PB9 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB8,PB9"); + #define GPIO_Rx GPIO('B', 8) + #define GPIO_Tx GPIO('B', 9) +#elif CONFIG_STM32_CANBUS_PB0_PB1 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB0,PB1"); + #define GPIO_Rx GPIO('B', 0) + #define GPIO_Tx GPIO('B', 1) +#endif + +#if !CONFIG_STM32_CANBUS_PB0_PB1 + #define SOC_CAN FDCAN1 + #define MSG_RAM fdcan_ram->fdcan1 +#else + #define SOC_CAN FDCAN2 + #define MSG_RAM fdcan_ram->fdcan2 +#endif + +#define CAN_IT0_IRQn TIM16_FDCAN_IT0_IRQn +#define CAN_FUNCTION GPIO_FUNCTION(3) // Alternative function mapping number + +#ifndef SOC_CAN + #error No known CAN device for configured MCU +#endif + +// Transmit a packet +int +canbus_send(struct canbus_msg *msg) +{ + uint32_t txfqs = SOC_CAN->TXFQS; + if (txfqs & FDCAN_TXFQS_TFQF) + // No space in transmit fifo - wait for irq + return -1; + + uint32_t w_index = ((txfqs & FDCAN_TXFQS_TFQPI) >> FDCAN_TXFQS_TFQPI_Pos); + FDCAN_FIFO_TypeDef *txfifo = &MSG_RAM.TXFIFO[w_index]; + uint32_t ids; + if (msg->id & CANMSG_ID_EFF) + ids = (msg->id & 0x1fffffff) | FDCAN_XTD; + else + ids = (msg->id & 0x7ff) << 18; + ids |= msg->id & CANMSG_ID_RTR ? FDCAN_RTR : 0; + txfifo->id_section = ids; + txfifo->dlc_section = (msg->dlc & 0x0f) << 16; + txfifo->data[0] = msg->data32[0]; + txfifo->data[1] = msg->data32[1]; + SOC_CAN->TXBAR = ((uint32_t)1 << w_index); + return CANMSG_DATA_LEN(msg); +} + +static void +can_filter(uint32_t index, uint32_t id) +{ + MSG_RAM.FLS[index] = ((0x2 << 30) // Classic filter + | (0x1 << 27) // Store in Rx FIFO 0 if filter matches + | (id << 16) + | 0x7FF); // mask all enabled +} + +// Setup the receive packet filter +void +canbus_set_filter(uint32_t id) +{ + if (!CONFIG_CANBUS_FILTER) + return; + /* Request initialisation */ + SOC_CAN->CCCR |= FDCAN_CCCR_INIT; + /* Wait the acknowledge */ + while (!(SOC_CAN->CCCR & FDCAN_CCCR_INIT)) + ; + /* Enable configuration change */ + SOC_CAN->CCCR |= FDCAN_CCCR_CCE; + + // Load filter + can_filter(0, CANBUS_ID_ADMIN); + can_filter(1, id); + can_filter(2, id + 1); + SOC_CAN->RXGFC = ((id ? 3 : 1) << FDCAN_RXGFC_LSS_Pos + | 0x02 << FDCAN_RXGFC_ANFS_Pos); + + /* Leave the initialisation mode for the filter */ + SOC_CAN->CCCR &= ~FDCAN_CCCR_CCE; + SOC_CAN->CCCR &= ~FDCAN_CCCR_INIT; +} + +// This function handles CAN global interrupts +void +CAN_IRQHandler(void) +{ + uint32_t ir = SOC_CAN->IR; + + if (ir & FDCAN_IE_RF0NE) { + SOC_CAN->IR = FDCAN_IE_RF0NE; + + uint32_t rxf0s = SOC_CAN->RXF0S; + if (rxf0s & FDCAN_RXF0S_F0FL) { + // Read and ack data packet + uint32_t idx = (rxf0s & FDCAN_RXF0S_F0GI) >> FDCAN_RXF0S_F0GI_Pos; + FDCAN_FIFO_TypeDef *rxf0 = &MSG_RAM.RXF0[idx]; + uint32_t ids = rxf0->id_section; + struct canbus_msg msg; + if (ids & FDCAN_XTD) + msg.id = (ids & 0x1fffffff) | CANMSG_ID_EFF; + else + msg.id = (ids >> 18) & 0x7ff; + msg.id |= ids & FDCAN_RTR ? CANMSG_ID_RTR : 0; + msg.dlc = (rxf0->dlc_section >> 16) & 0x0f; + msg.data32[0] = rxf0->data[0]; + msg.data32[1] = rxf0->data[1]; + SOC_CAN->RXF0A = idx; + + // Process packet + canbus_process_data(&msg); + } + } + if (ir & FDCAN_IE_TC) { + // Tx + SOC_CAN->IR = FDCAN_IE_TC; + canbus_notify_tx(); + } +} + +static inline const uint32_t +make_btr(uint32_t sjw, // Sync jump width, ... hmm + uint32_t time_seg1, // time segment before sample point, 1 .. 16 + uint32_t time_seg2, // time segment after sample point, 1 .. 8 + uint32_t brp) // Baud rate prescaler, 1 .. 1024 +{ + return (((uint32_t)(sjw-1)) << FDCAN_NBTP_NSJW_Pos + | ((uint32_t)(time_seg1-1)) << FDCAN_NBTP_NTSEG1_Pos + | ((uint32_t)(time_seg2-1)) << FDCAN_NBTP_NTSEG2_Pos + | ((uint32_t)(brp - 1)) << FDCAN_NBTP_NBRP_Pos); +} + +static inline const uint32_t +compute_btr(uint32_t pclock, uint32_t bitrate) +{ + /* + Some equations: + Tpclock = 1 / pclock + Tq = brp * Tpclock + Tbs1 = Tq * TS1 + Tbs2 = Tq * TS2 + NominalBitTime = Tq + Tbs1 + Tbs2 + BaudRate = 1/NominalBitTime + Bit value sample point is after Tq+Tbs1. Ideal sample point + is at 87.5% of NominalBitTime + Use the lowest brp where ts1 and ts2 are in valid range + */ + + uint32_t bit_clocks = pclock / bitrate; // clock ticks per bit + + uint32_t sjw = 2; + uint32_t qs; + // Find number of time quantas that gives us the exact wanted bit time + for (qs = 18; qs > 9; qs--) { + // check that bit_clocks / quantas is an integer + uint32_t brp_rem = bit_clocks % qs; + if (brp_rem == 0) + break; + } + uint32_t brp = bit_clocks / qs; + uint32_t time_seg2 = qs / 8; // sample at ~87.5% + uint32_t time_seg1 = qs - (1 + time_seg2); + + return make_btr(sjw, time_seg1, time_seg2, brp); +} + +void +can_init(void) +{ + enable_pclock((uint32_t)SOC_CAN); + + gpio_peripheral(GPIO_Rx, CAN_FUNCTION, 1); + gpio_peripheral(GPIO_Tx, CAN_FUNCTION, 0); + + uint32_t pclock = get_pclock_frequency((uint32_t)SOC_CAN); + + uint32_t btr = compute_btr(pclock, CONFIG_CANBUS_FREQUENCY); + + /*##-1- Configure the CAN #######################################*/ + + /* Exit from sleep mode */ + SOC_CAN->CCCR &= ~FDCAN_CCCR_CSR; + /* Wait the acknowledge */ + while (SOC_CAN->CCCR & FDCAN_CCCR_CSA) + ; + /* Request initialisation */ + SOC_CAN->CCCR |= FDCAN_CCCR_INIT; + /* Wait the acknowledge */ + while (!(SOC_CAN->CCCR & FDCAN_CCCR_INIT)) + ; + /* Enable configuration change */ + SOC_CAN->CCCR |= FDCAN_CCCR_CCE; + + /* Disable protocol exception handling */ + SOC_CAN->CCCR |= FDCAN_CCCR_PXHD; + + SOC_CAN->NBTP = btr; + + /* Leave the initialisation mode */ + SOC_CAN->CCCR &= ~FDCAN_CCCR_CCE; + SOC_CAN->CCCR &= ~FDCAN_CCCR_INIT; + + /*##-2- Configure the CAN Filter #######################################*/ + canbus_set_filter(0); + + /*##-3- Configure Interrupts #################################*/ + armcm_enable_irq(CAN_IRQHandler, CAN_IT0_IRQn, 0); + SOC_CAN->ILE = FDCAN_ILE_EINT0; + SOC_CAN->IE = FDCAN_IE_RF0NE | FDCAN_IE_TC; +} +DECL_INIT(can_init); diff --git a/src/stm32/stm32g0.c b/src/stm32/stm32g0.c index ef5dec0..967eb2a 100644 --- a/src/stm32/stm32g0.c +++ b/src/stm32/stm32g0.c @@ -30,6 +30,8 @@ lookup_clock_line(uint32_t periph_base) uint32_t bit = 1 << ((periph_base - AHBPERIPH_BASE) / 0x400); return (struct cline){.en=&RCC->AHBENR, .rst=&RCC->AHBRSTR, .bit=bit}; } + if ((periph_base == FDCAN1_BASE) || (periph_base == FDCAN2_BASE)) + return (struct cline){.en=&RCC->APBENR1,.rst=&RCC->APBRSTR1,.bit=1<<12}; if (periph_base == USB_BASE) return (struct cline){.en=&RCC->APBENR1,.rst=&RCC->APBRSTR1,.bit=1<<13}; if (periph_base == CRS_BASE)