From 4b77f29c1b20d5fdac577c557699e0b20f78d799 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 30 Sep 2022 18:03:21 -0400 Subject: [PATCH] atsamd: Add support CANbus on ATSAME5x chips Signed-off-by: Kevin O'Connor --- docs/CANBUS.md | 4 +- src/atsamd/Kconfig | 45 ++++++ src/atsamd/Makefile | 8 +- src/atsamd/chipid.c | 10 +- src/atsamd/clock.c | 8 +- src/atsamd/fdcan.c | 293 ++++++++++++++++++++++++++++++++++++++ src/atsamd/internal.h | 2 +- src/atsamd/samd51_clock.c | 8 +- 8 files changed, 365 insertions(+), 13 deletions(-) create mode 100755 src/atsamd/fdcan.c diff --git a/docs/CANBUS.md b/docs/CANBUS.md index f8d64676..c71273d5 100644 --- a/docs/CANBUS.md +++ b/docs/CANBUS.md @@ -4,8 +4,8 @@ This document describes Klipper's CAN bus support. ## Device Hardware -Klipper currently supports CAN on stm32 and rp2040 chips. In addition, -the micro-controller chip must be on a board that has a CAN +Klipper currently supports CAN on stm32, same5x, and rp2040 chips. In +addition, the micro-controller chip must be on a board that has a CAN transceiver. To compile for CAN, run `make menuconfig` and select "CAN bus" as the diff --git a/src/atsamd/Kconfig b/src/atsamd/Kconfig index 47af178e..0fa4c5d2 100644 --- a/src/atsamd/Kconfig +++ b/src/atsamd/Kconfig @@ -68,6 +68,9 @@ config MACH_SAME51 config MACH_SAME54 bool select MACH_SAMX5 +config HAVE_SAMD_CANBUS + bool + default y if MACH_SAME51 || MACH_SAME54 config MCU string @@ -159,6 +162,48 @@ choice config ATSAMD_SERIAL bool "Serial" select SERIAL + config ATSAMD_MMENU_CANBUS_PA23_PA22 + bool "CAN bus (on PA23/PA22)" + depends on HAVE_SAMD_CANBUS + select CANSERIAL + config ATSAMD_MMENU_CANBUS_PA25_PA24 + bool "CAN bus (on PA25/PA24)" + depends on HAVE_SAMD_CANBUS + select CANSERIAL + config ATSAMD_MMENU_CANBUS_PB13_PB12 + bool "CAN bus (on PB13/PB12)" + depends on HAVE_SAMD_CANBUS + select CANSERIAL + config ATSAMD_MMENU_CANBUS_PB15_PB14 + bool "CAN bus (on PB15/PB14)" + depends on HAVE_SAMD_CANBUS + select CANSERIAL + config ATSAMD_USBCANBUS + bool "USB to CAN bus bridge" + depends on HAVE_SAMD_CANBUS + select USBCANBUS +endchoice +choice + prompt "CAN bus interface" if USBCANBUS + config ATSAMD_CMENU_CANBUS_PA23_PA22 + bool "CAN bus (on PA23/PA22)" + config ATSAMD_CMENU_CANBUS_PB13_PB12 + bool "CAN bus (on PB13/PB12)" + config ATSAMD_CMENU_CANBUS_PB15_PB14 + bool "CAN bus (on PB15/PB14)" endchoice +config ATSAMD_CANBUS_PA23_PA22 + bool + default y if ATSAMD_MMENU_CANBUS_PA23_PA22 || ATSAMD_CMENU_CANBUS_PA23_PA22 +config ATSAMD_CANBUS_PA25_PA24 + bool + default y if ATSAMD_MMENU_CANBUS_PA25_PA24 +config ATSAMD_CANBUS_PB13_PB12 + bool + default y if ATSAMD_MMENU_CANBUS_PB13_PB12 || ATSAMD_CMENU_CANBUS_PB13_PB12 +config ATSAMD_CANBUS_PB15_PB14 + bool + default y if ATSAMD_MMENU_CANBUS_PB15_PB14 || ATSAMD_CMENU_CANBUS_PB15_PB14 + endif diff --git a/src/atsamd/Makefile b/src/atsamd/Makefile index 0d5d9698..9c94b812 100644 --- a/src/atsamd/Makefile +++ b/src/atsamd/Makefile @@ -3,7 +3,7 @@ # Setup the toolchain CROSS_PREFIX=arm-none-eabi- -dirs-y += src/atsamd src/generic +dirs-y += src/atsamd src/generic lib/fast-hash MCU := $(shell echo $(CONFIG_MCU) | tr a-z A-Z) @@ -11,7 +11,7 @@ CFLAGS-$(CONFIG_MACH_SAMD21) += -mcpu=cortex-m0plus -Ilib/samd21/samd21a/include CFLAGS-$(CONFIG_MACH_SAMD51) += -Ilib/samd51/samd51a/include CFLAGS-$(CONFIG_MACH_SAME51) += -Ilib/same51/include CFLAGS-$(CONFIG_MACH_SAMX5) += -mcpu=cortex-m4 -Ilib/same54/include -CFLAGS += $(CFLAGS-y) -D__$(MCU)__ -mthumb -Ilib/cmsis-core +CFLAGS += $(CFLAGS-y) -D__$(MCU)__ -mthumb -Ilib/cmsis-core -Ilib/fast-hash CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld @@ -22,6 +22,10 @@ src-y += atsamd/main.c atsamd/gpio.c generic/crc16_ccitt.c src-y += generic/armcm_boot.c generic/armcm_irq.c generic/armcm_reset.c src-$(CONFIG_USBSERIAL) += atsamd/usbserial.c atsamd/chipid.c generic/usb_cdc.c src-$(CONFIG_SERIAL) += atsamd/serial.c generic/serial_irq.c +canbus-src-y := generic/canserial.c ../lib/fast-hash/fasthash.c +canbus-src-y += atsamd/fdcan.c atsamd/chipid.c +src-$(CONFIG_USBCANBUS) += $(canbus-src-y) atsamd/usbserial.c generic/usb_canbus.c +src-$(CONFIG_CANSERIAL) += $(canbus-src-y) generic/canbus.c src-$(CONFIG_HAVE_GPIO_ADC) += atsamd/adc.c src-$(CONFIG_HAVE_GPIO_I2C) += atsamd/i2c.c src-$(CONFIG_HAVE_GPIO_SPI) += atsamd/spi.c diff --git a/src/atsamd/chipid.c b/src/atsamd/chipid.c index a4e378bc..95aaf9be 100644 --- a/src/atsamd/chipid.c +++ b/src/atsamd/chipid.c @@ -5,6 +5,7 @@ // This file may be distributed under the terms of the GNU GPLv3 license. #include "autoconf.h" // CONFIG_USB_SERIAL_NUMBER_CHIPID +#include "generic/canserial.h" // canserial_set_uuid #include "generic/usb_cdc.h" // usb_fill_serial #include "generic/usbstd.h" // usb_string_descriptor #include "sched.h" // DECL_INIT @@ -25,7 +26,7 @@ usbserial_get_serialid(void) void chipid_init(void) { - if (!CONFIG_USB_SERIAL_NUMBER_CHIPID) + if (!CONFIG_USB_SERIAL_NUMBER_CHIPID && !CONFIG_CANBUS) return; uint32_t id[4]; @@ -40,6 +41,11 @@ chipid_init(void) id[2] = *(uint32_t*)0x00806014; id[3] = *(uint32_t*)0x00806018; } - usb_fill_serial(&cdc_chipid.desc, ARRAY_SIZE(cdc_chipid.data), id); + + if (CONFIG_USB_SERIAL_NUMBER_CHIPID) + usb_fill_serial(&cdc_chipid.desc, ARRAY_SIZE(cdc_chipid.data), id); + + if (CONFIG_CANBUS) + canserial_set_uuid((void*)id, CHIP_UID_LEN); } DECL_INIT(chipid_init); diff --git a/src/atsamd/clock.c b/src/atsamd/clock.c index 496ae56e..661c80ba 100644 --- a/src/atsamd/clock.c +++ b/src/atsamd/clock.c @@ -35,11 +35,13 @@ route_pclock(uint32_t pclk_id, uint32_t clkgen_id) // Enable a peripheral clock and power to that peripheral void -enable_pclock(uint32_t pclk_id, uint32_t pm_id) +enable_pclock(uint32_t pclk_id, int32_t pm_id) { route_pclock(pclk_id, CLKGEN_MAIN); - uint32_t pm_port = pm_id / 32, pm_bit = 1 << (pm_id % 32); - (&PM->APBAMASK.reg)[pm_port] |= pm_bit; + if (pm_id >= 0) { + uint32_t pm_port = pm_id / 32, pm_bit = 1 << (pm_id % 32); + (&PM->APBAMASK.reg)[pm_port] |= pm_bit; + } } // Return the frequency of the given peripheral clock diff --git a/src/atsamd/fdcan.c b/src/atsamd/fdcan.c new file mode 100755 index 00000000..c12d91db --- /dev/null +++ b/src/atsamd/fdcan.c @@ -0,0 +1,293 @@ +// CANbus support on atsame51 chips +// +// Copyright (C) 2021-2022 Kevin O'Connor +// Copyright (C) 2019 Eug Krashtan +// Copyright (C) 2020 Pontus Borg +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#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 "internal.h" // enable_pclock +#include "sched.h" // DECL_INIT + + +/**************************************************************** + * Pin configuration + ****************************************************************/ + +#if CONFIG_ATSAMD_CANBUS_PA23_PA22 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PA23,PA22"); + #define GPIO_Rx GPIO('A', 23) + #define GPIO_Tx GPIO('A', 22) + #define CANx_GCLK_ID CAN0_GCLK_ID +#elif CONFIG_ATSAMD_CANBUS_PA25_PA24 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PA25,PA24"); + #define GPIO_Rx GPIO('A', 25) + #define GPIO_Tx GPIO('A', 24) + #define CANx_GCLK_ID CAN0_GCLK_ID +#elif CONFIG_ATSAMD_CANBUS_PB13_PB12 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB13,PB12"); + #define GPIO_Rx GPIO('B', 13) + #define GPIO_Tx GPIO('B', 12) + #define CANx_GCLK_ID CAN1_GCLK_ID +#elif CONFIG_ATSAMD_CANBUS_PB15_PB14 + DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB15,PB14"); + #define GPIO_Rx GPIO('B', 15) + #define GPIO_Tx GPIO('B', 14) + #define CANx_GCLK_ID CAN1_GCLK_ID +#endif + +#if CANx_GCLK_ID == CAN0_GCLK_ID + #define CAN_FUNCTION 'I' + #define CANx CAN0 + #define CANx_IRQn CAN0_IRQn +#else + #define CAN_FUNCTION 'H' + #define CANx CAN1 + #define CANx_IRQn CAN1_IRQn +#endif + + +/**************************************************************** + * Message ram layout + ****************************************************************/ + +struct fdcan_fifo { + uint32_t id_section; + uint32_t dlc_section; + uint32_t data[64 / 4]; +}; + +#define FDCAN_XTD (1<<30) +#define FDCAN_RTR (1<<29) + +struct fdcan_msg_ram { + uint32_t FLS[28]; // Filter list standard + uint32_t FLE[16]; // Filter list extended + struct fdcan_fifo RXF0[3]; + struct fdcan_fifo RXF1[3]; + uint32_t TEF[6]; // Tx event FIFO + struct fdcan_fifo TXFIFO[3]; +}; + +// Message ram is in regular memory +static struct fdcan_msg_ram MSG_RAM; + + +/**************************************************************** + * CANbus code + ****************************************************************/ + +#define FDCAN_IE_TC (CAN_IE_TCE | CAN_IE_TCFE | CAN_IE_TFEE) + +// Transmit a packet +int +canbus_send(struct canbus_msg *msg) +{ + uint32_t txfqs = CANx->TXFQS.reg; + if (txfqs & CAN_TXFQS_TFQF) + // No space in transmit fifo - wait for irq + return -1; + + uint32_t w_index = ((txfqs & CAN_TXFQS_TFQPI_Msk) >> CAN_TXFQS_TFQPI_Pos); + struct fdcan_fifo *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]; + barrier(); + CANx->TXBAR.reg = ((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 */ + CANx->CCCR.reg |= CAN_CCCR_INIT; + /* Wait the acknowledge */ + while (!(CANx->CCCR.reg & CAN_CCCR_INIT)) + ; + /* Enable configuration change */ + CANx->CCCR.reg |= CAN_CCCR_CCE; + + // Load filter + can_filter(0, CANBUS_ID_ADMIN); + can_filter(1, id); + can_filter(2, id + 1); + + uint32_t flssa = (uint32_t)MSG_RAM.FLS - CAN0_MSG_RAM_ADDR; + CANx->SIDFC.reg = flssa | ((id ? 3 : 1) << CAN_SIDFC_LSS_Pos); + CANx->GFC.reg = 0x02 << CAN_GFC_ANFS_Pos; + + /* Leave the initialisation mode for the filter */ + barrier(); + CANx->CCCR.reg &= ~CAN_CCCR_CCE; + CANx->CCCR.reg &= ~CAN_CCCR_INIT; +} + +// This function handles CAN global interrupts +void +CAN_IRQHandler(void) +{ + uint32_t ir = CANx->IR.reg; + + if (ir & CAN_IE_RF0NE) { + CANx->IR.reg = CAN_IE_RF0NE; + + uint32_t rxf0s = CANx->RXF0S.reg; + if (rxf0s & CAN_RXF0S_F0FL_Msk) { + // Read and ack data packet + uint32_t idx = (rxf0s & CAN_RXF0S_F0GI_Msk) >> CAN_RXF0S_F0GI_Pos; + struct fdcan_fifo *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]; + barrier(); + CANx->RXF0A.reg = idx; + + // Process packet + canbus_process_data(&msg); + } + } + if (ir & FDCAN_IE_TC) { + // Tx + CANx->IR.reg = 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)) << CAN_NBTP_NSJW_Pos + | ((uint32_t)(time_seg1-1)) << CAN_NBTP_NTSEG1_Pos + | ((uint32_t)(time_seg2-1)) << CAN_NBTP_NTSEG2_Pos + | ((uint32_t)(brp - 1)) << CAN_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) +{ + if (!CONFIG_USB) { + // The FDCAN peripheral only seems to run if at least one + // other peripheral is also enabled. + enable_pclock(USB_GCLK_ID, ID_USB); + USB->DEVICE.CTRLA.reg = USB_CTRLA_ENABLE; + } + + enable_pclock(CANx_GCLK_ID, -1); + + gpio_peripheral(GPIO_Rx, CAN_FUNCTION, 1); + gpio_peripheral(GPIO_Tx, CAN_FUNCTION, 0); + + uint32_t pclock = get_pclock_frequency(CANx_GCLK_ID); + + uint32_t btr = compute_btr(pclock, CONFIG_CANBUS_FREQUENCY); + + /*##-1- Configure the CAN #######################################*/ + + /* Exit from sleep mode */ + CANx->CCCR.reg &= ~CAN_CCCR_CSR; + /* Wait the acknowledge */ + while (CANx->CCCR.reg & CAN_CCCR_CSA) + ; + /* Request initialisation */ + CANx->CCCR.reg |= CAN_CCCR_INIT; + /* Wait the acknowledge */ + while (!(CANx->CCCR.reg & CAN_CCCR_INIT)) + ; + /* Enable configuration change */ + CANx->CCCR.reg |= CAN_CCCR_CCE; + + /* Disable protocol exception handling */ + CANx->CCCR.reg |= CAN_CCCR_PXHD; + + CANx->NBTP.reg = btr; + + /* Setup message RAM addresses */ + uint32_t f0sa = (uint32_t)MSG_RAM.RXF0 - CAN0_MSG_RAM_ADDR; + CANx->RXF0C.reg = f0sa | (ARRAY_SIZE(MSG_RAM.RXF0) << CAN_RXF0C_F0S_Pos); + CANx->RXESC.reg = (7 << CAN_RXESC_F1DS_Pos) | (7 << CAN_RXESC_F0DS_Pos); + uint32_t tbsa = (uint32_t)MSG_RAM.TXFIFO - CAN0_MSG_RAM_ADDR; + CANx->TXBC.reg = tbsa | (ARRAY_SIZE(MSG_RAM.TXFIFO) << CAN_TXBC_TFQS_Pos); + CANx->TXESC.reg = 7 << CAN_TXESC_TBDS_Pos; + + /* Leave the initialisation mode */ + CANx->CCCR.reg &= ~CAN_CCCR_CCE; + CANx->CCCR.reg &= ~CAN_CCCR_INIT; + + /*##-2- Configure the CAN Filter #######################################*/ + canbus_set_filter(0); + + /*##-3- Configure Interrupts #################################*/ + armcm_enable_irq(CAN_IRQHandler, CANx_IRQn, 1); + CANx->ILE.reg = CAN_ILE_EINT0; + CANx->IE.reg = CAN_IE_RF0NE | FDCAN_IE_TC; +} +DECL_INIT(can_init); diff --git a/src/atsamd/internal.h b/src/atsamd/internal.h index 310102c5..967833c9 100644 --- a/src/atsamd/internal.h +++ b/src/atsamd/internal.h @@ -22,7 +22,7 @@ #define GET_FUSE(REG) \ ((*((uint32_t*)(REG##_ADDR)) & (REG##_Msk)) >> (REG##_Pos)) -void enable_pclock(uint32_t pclk_id, uint32_t pm_id); +void enable_pclock(uint32_t pclk_id, int32_t pm_id); uint32_t get_pclock_frequency(uint32_t pclk_id); void gpio_peripheral(uint32_t gpio, char ptype, int32_t pull_up); diff --git a/src/atsamd/samd51_clock.c b/src/atsamd/samd51_clock.c index 2f769435..3497bccf 100644 --- a/src/atsamd/samd51_clock.c +++ b/src/atsamd/samd51_clock.c @@ -39,11 +39,13 @@ route_pclock(uint32_t pclk_id, uint32_t clkgen_id) // Enable a peripheral clock and power to that peripheral void -enable_pclock(uint32_t pclk_id, uint32_t pm_id) +enable_pclock(uint32_t pclk_id, int32_t pm_id) { route_pclock(pclk_id, CLKGEN_48M); - uint32_t pm_port = pm_id / 32, pm_bit = 1 << (pm_id % 32); - (&MCLK->APBAMASK.reg)[pm_port] |= pm_bit; + if (pm_id >= 0) { + uint32_t pm_port = pm_id / 32, pm_bit = 1 << (pm_id % 32); + (&MCLK->APBAMASK.reg)[pm_port] |= pm_bit; + } } // Return the frequency of the given peripheral clock