From d0722186aff03f0c21b944ba4824a365ee861444 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 17 Jul 2026 08:03:19 +0000 Subject: [PATCH 01/14] gd32f470vit6: fix flash pagesize and uClibc++ build errors - Add FMC_PROGMEM_PAGESIZE 4096 for CONFIG_GD32F4_FLASH_CONFIG_DEFAULT - Fix uClibc++ Make.defs workaround Co-authored-by: openhands --- arch/arm/src/gd32f4/gd32f4xx_progmem.c | 1 + libs/libxx/uClibc++/Make.defs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm/src/gd32f4/gd32f4xx_progmem.c b/arch/arm/src/gd32f4/gd32f4xx_progmem.c index e1ceed1767602..cd29b954016bc 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_progmem.c +++ b/arch/arm/src/gd32f4/gd32f4xx_progmem.c @@ -47,6 +47,7 @@ # define FMC_PROGMEM_SECTOR_SIZES {_K(128), _K(128)} # define FMC_PROGMEM_SECTOR_NUM (2) +# define FMC_PROGMEM_PAGESIZE 4096 # define FMC_PROGMEM_SECTOR_SADDR (0x08040000) # define FMC_PROGMEM_SECTOR_EADDR (0x0807FFFF) diff --git a/libs/libxx/uClibc++/Make.defs b/libs/libxx/uClibc++/Make.defs index 1f13294a3f8d0..f28b59c04eb2f 100644 --- a/libs/libxx/uClibc++/Make.defs +++ b/libs/libxx/uClibc++/Make.defs @@ -31,8 +31,8 @@ uClibc++/uClibc++: uClibc++-$(UCLIBCXX_VERSION).tar.bz2 $(Q) tar -xf $< $(Q) $(DELFILE) $< $(Q) mv uClibc++-$(UCLIBCXX_VERSION) $@ - $(Q) patch -p0 < uClibc++/0001-uClibcxx-basic_definitions-fix-GCC-specific-definiti.patch -d uClibc++ - $(Q) patch -p0 < uClibc++/0001-uclibxx-use-overload-constructor-of-filebuf-ostream.patch -d uClibc++ + $(Q) patch -p1 < uClibc++/0001-uClibcxx-basic_definitions-fix-GCC-specific-definiti.patch -d uClibc++ + $(Q) patch -p1 < uClibc++/0001-uclibxx-use-overload-constructor-of-filebuf-ostream.patch -d uClibc++ endif $(TOPDIR)/include/uClibc++: uClibc++/uClibc++ From e432ff85b0cbd5c0a124436e76b96631fcaa84eb Mon Sep 17 00:00:00 2001 From: Jackson Qin Date: Mon, 20 Jul 2026 16:40:13 +0800 Subject: [PATCH 02/14] port gd32f470vit6 board support --- arch/arm/src/gd32f4/hardware/gd32f450_pinmap.h | 7 +++++-- boards/arm/gd32f4/drivers/Kconfig | 0 boards/arm/gd32f4/gd32f470vit6-eval | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 boards/arm/gd32f4/drivers/Kconfig create mode 120000 boards/arm/gd32f4/gd32f470vit6-eval diff --git a/arch/arm/src/gd32f4/hardware/gd32f450_pinmap.h b/arch/arm/src/gd32f4/hardware/gd32f450_pinmap.h index 53bfbeafebff3..8ec27228f5788 100644 --- a/arch/arm/src/gd32f4/hardware/gd32f450_pinmap.h +++ b/arch/arm/src/gd32f4/hardware/gd32f450_pinmap.h @@ -807,8 +807,11 @@ #define GPIO_UART3_TX_1 (GPIO_CFG_MODE_AF|GPIO_CFG_PUPD_PULLUP|GPIO_CFG_PP|GPIO_CFG_SPEED_50MHZ|GPIO_CFG_AF_8|GPIO_CFG_PORT_A|GPIO_CFG_PIN_0) #define GPIO_UART3_TX_2 (GPIO_CFG_MODE_AF|GPIO_CFG_PUPD_PULLUP|GPIO_CFG_PP|GPIO_CFG_SPEED_50MHZ|GPIO_CFG_AF_8|GPIO_CFG_PORT_C|GPIO_CFG_PIN_10) -#define GPIO_UART4_RX (GPIO_CFG_MODE_AF|GPIO_CFG_PUPD_PULLUP|GPIO_CFG_PP|GPIO_CFG_SPEED_50MHZ|GPIO_CFG_AF_8|GPIO_CFG_PORT_D|GPIO_CFG_PIN_2) -#define GPIO_UART4_TX (GPIO_CFG_MODE_AF|GPIO_CFG_PUPD_PULLUP|GPIO_CFG_PP|GPIO_CFG_SPEED_50MHZ|GPIO_CFG_AF_8|GPIO_CFG_PORT_C|GPIO_CFG_PIN_12) +#define GPIO_UART4_RX_1 (GPIO_CFG_MODE_AF|GPIO_CFG_PUPD_PULLUP|GPIO_CFG_PP|GPIO_CFG_SPEED_50MHZ|GPIO_CFG_AF_8|GPIO_CFG_PORT_D|GPIO_CFG_PIN_2) +#define GPIO_UART4_TX_1 (GPIO_CFG_MODE_AF|GPIO_CFG_PUPD_PULLUP|GPIO_CFG_PP|GPIO_CFG_SPEED_50MHZ|GPIO_CFG_AF_8|GPIO_CFG_PORT_C|GPIO_CFG_PIN_12) + +#define GPIO_UART4_RX_2 (GPIO_CFG_MODE_AF|GPIO_CFG_PUPD_PULLUP|GPIO_CFG_PP|GPIO_CFG_SPEED_50MHZ|GPIO_CFG_AF_11|GPIO_CFG_PORT_C|GPIO_CFG_PIN_11) +#define GPIO_UART4_TX_2 (GPIO_CFG_MODE_AF|GPIO_CFG_PUPD_PULLUP|GPIO_CFG_PP|GPIO_CFG_SPEED_50MHZ|GPIO_CFG_AF_11|GPIO_CFG_PORT_C|GPIO_CFG_PIN_10) #define GPIO_UART6_RX_1 (GPIO_CFG_MODE_AF|GPIO_CFG_PUPD_PULLUP|GPIO_CFG_PP|GPIO_CFG_SPEED_50MHZ|GPIO_CFG_AF_8|GPIO_CFG_PORT_E|GPIO_CFG_PIN_7) #define GPIO_UART6_RX_2 (GPIO_CFG_MODE_AF|GPIO_CFG_PUPD_PULLUP|GPIO_CFG_PP|GPIO_CFG_SPEED_50MHZ|GPIO_CFG_AF_8|GPIO_CFG_PORT_F|GPIO_CFG_PIN_6) diff --git a/boards/arm/gd32f4/drivers/Kconfig b/boards/arm/gd32f4/drivers/Kconfig new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/boards/arm/gd32f4/gd32f470vit6-eval b/boards/arm/gd32f4/gd32f470vit6-eval new file mode 120000 index 0000000000000..be1271adc8974 --- /dev/null +++ b/boards/arm/gd32f4/gd32f470vit6-eval @@ -0,0 +1 @@ +/home/edy_jackson/workspace/project/nuttx_pro/nuttx_mcu/mcu_plat/platform/board_gd32f470vit6 \ No newline at end of file From 9788fb8d7234345b9b3248530d2cdb5031cc1244 Mon Sep 17 00:00:00 2001 From: Jackson Qin Date: Fri, 24 Jul 2026 18:58:11 +0800 Subject: [PATCH 03/14] gd32f4: add MCUboot application image and OTA partition Kconfig Enable GD32F4_APP_FORMAT_MCUBOOT and PROGMEM OTA slot options needed by the board mcuboot-loader/app configurations. Co-authored-by: Cursor --- arch/arm/src/gd32f4/Kconfig | 73 +++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/arch/arm/src/gd32f4/Kconfig b/arch/arm/src/gd32f4/Kconfig index c2d60e0c31122..43312cc39f502 100644 --- a/arch/arm/src/gd32f4/Kconfig +++ b/arch/arm/src/gd32f4/Kconfig @@ -192,6 +192,79 @@ config GD32F4_DFU Configure and position code for use with the GD MCU DFU bootloader. Do not select this option if you will load code using JTAG/SWM. +config GD32F4_HAVE_OTA_PARTITION + bool + default n + +choice + prompt "Application Image Format" + depends on ARCH_CHIP_GD32F4 + default GD32F4_APP_FORMAT_LEGACY + +config GD32F4_APP_FORMAT_LEGACY + bool "Legacy format" + +config GD32F4_APP_FORMAT_MCUBOOT + bool "MCUboot-bootable format" + select GD32F4_HAVE_OTA_PARTITION + depends on EXPERIMENTAL + +endchoice # Application Image Format + +if GD32F4_HAVE_OTA_PARTITION + +config GD32F4_MCUBOOT_HEADER_SIZE + hex "MCUboot image header size" + default 0x200 + depends on GD32F4_APP_FORMAT_MCUBOOT + +menuconfig GD32F4_PROGMEM_OTA_PARTITION + bool "MTD driver for OTA partition" + default n + depends on GD32F4_APP_FORMAT_MCUBOOT + select BCH + select MTD + select MTD_BYTE_WRITE + select MTD_PARTITION + select MTD_PROGMEM + select GD32F4_PROGMEM + +if GD32F4_PROGMEM_OTA_PARTITION + +config GD32F4_OTA_PRIMARY_SLOT_DEVPATH + string "Primary slot device path" + default "/dev/ota0" + +config GD32F4_OTA_SECONDARY_SLOT_DEVPATH + string "Secondary slot device path" + default "/dev/ota1" + +config GD32F4_OTA_SCRATCH_DEVPATH + string "Scratch partition device path" + default "/dev/otascratch" + +config GD32F4_OTA_PRIMARY_SLOT_OFFSET + hex "Primary slot offset" + default 0x20000 + +config GD32F4_OTA_SECONDARY_SLOT_OFFSET + hex "Secondary slot offset" + default 0x100000 + +config GD32F4_OTA_SCRATCH_OFFSET + hex "Scratch partition offset" + default 0x1e0000 + +config GD32F4_OTA_SLOT_SIZE + hex "Application image slot size (in bytes)" + default 0xe0000 + +config GD32F4_OTA_SCRATCH_SIZE + hex "Scratch partition size (in bytes)" + default 0x20000 + +endif # GD32F4_PROGMEM_OTA_PARTITION +endif # GD32F4_HAVE_OTA_PARTITION menu "GD32 Peripheral Support" From 113a0374998d573ea875dc3a57b0f4ef856c74b7 Mon Sep 17 00:00:00 2001 From: jacksonGreenStone <13117870426@163.com> Date: Sun, 26 Jul 2026 15:00:10 +0800 Subject: [PATCH 04/14] gd32f4: fix progmem OTA helpers and register gd32f470vit6-eval board. Point the out-of-tree board symlink at the sibling nuttx_mcu package path. --- arch/arm/src/gd32f4/gd32f4xx_progmem.c | 22 +++++++++++++++++----- boards/Kconfig | 19 ++++++++++++++++--- boards/arm/gd32f4/gd32f470vit6-eval | 2 +- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/arch/arm/src/gd32f4/gd32f4xx_progmem.c b/arch/arm/src/gd32f4/gd32f4xx_progmem.c index cd29b954016bc..594edef413fb4 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_progmem.c +++ b/arch/arm/src/gd32f4/gd32f4xx_progmem.c @@ -68,11 +68,23 @@ # define FMC_PROGMEM_SECTOR_EADDR (0x080FFFFF) # elif defined(CONFIG_GD32F4_FLASH_CONFIG_I) - -# define FMC_PROGMEM_SECTOR_SIZES {_K(16), _K(16), _K(16), _K(16)} -# define FMC_PROGMEM_SECTOR_NUM (4) -# define FMC_PROGMEM_SECTOR_SADDR (0x08100000) -# define FMC_PROGMEM_SECTOR_EADDR (0x0810FFFF) +# if defined(CONFIG_GD32F4_GD32F470) +/* GD32F470xI: 2048 KiB main flash @ 0x08000000, 4 KiB page erase. + * Expose full array so MCUboot OTA slots (loader/primary/secondary/scratch) + * can be partitioned via MTD progmem. + */ + +# define FMC_PROGMEM_SECTOR_SIZES {_K(256), _K(256), _K(256), _K(256)} +# define FMC_PROGMEM_SECTOR_NUM (512) +# define FMC_PROGMEM_SECTOR_SADDR (0x08000000) +# define FMC_PROGMEM_SECTOR_EADDR (0x081FFFFF) +# define FMC_PROGMEM_PAGESIZE 4096 +# else +# define FMC_PROGMEM_SECTOR_SIZES {_K(16), _K(16), _K(16), _K(16)} +# define FMC_PROGMEM_SECTOR_NUM (4) +# define FMC_PROGMEM_SECTOR_SADDR (0x08100000) +# define FMC_PROGMEM_SECTOR_EADDR (0x0810FFFF) +# endif # elif defined(CONFIG_GD32F4_FLASH_CONFIG_K) #if defined(CONFIG_GD32F4_GD32F450) diff --git a/boards/Kconfig b/boards/Kconfig index 5196f18cbc205..f3178ec02340d 100644 --- a/boards/Kconfig +++ b/boards/Kconfig @@ -559,9 +559,9 @@ config ARCH_BOARD_ESP32S3_BOX select ARCH_HAVE_IRQBUTTONS if ESPRESSIF_GPIO_IRQ ---help--- The ESP-BOX is an advanced AIoT, Edge AI, and IIoT applications development platform - released by Espressif Systems. The board is built on Espressif’s powerful ESP32-S3 + released by Espressif Systems. The board is built on Espressif¡¯s powerful ESP32-S3 Wi-Fi + Bluetooth 5 (LE) SoC and feature compact and beautiful enclosures. - Leveraging the multi-functional accessories and reliable examples from the repo’s, + Leveraging the multi-functional accessories and reliable examples from the repo¡¯s, these development boards cater to both prototyping new projects and building complex IoT systems, providing a perfect balance of form and function. @@ -845,6 +845,15 @@ config ARCH_BOARD_GD32F470IK_EVAL ---help--- GD32 MCU GD32F470IK-EVAL board based on the GD32F4 GD32F470IKT6 MCU. +config ARCH_BOARD_GD32F470VIT6_EVAL + bool "GD32 MCU GD32F470VIT6" + depends on ARCH_CHIP_GD32F470ZK + select ARCH_HAVE_LEDS + select ARCH_HAVE_BUTTONS + select ARCH_HAVE_IRQBUTTONS + ---help--- + GD32 MCU GD32F470VIT6-EVAL board based on the GD32F4 GD32F470VIT6 MCU. + config ARCH_BOARD_HIFIVE1_REVB bool "HiFive1 Rev B board" depends on ARCH_CHIP_FE310 @@ -3726,6 +3735,7 @@ config ARCH_BOARD default "gd32f470zk-eval" if ARCH_BOARD_GD32F470ZK_EVAL default "gd32f470zk-aiotbox" if ARCH_BOARD_GD32F470ZK_AIOTBOX default "gd32f470ik-eval" if ARCH_BOARD_GD32F470IK_EVAL + default "gd32f470vit6-eval" if ARCH_BOARD_GD32F470VIT6_EVAL default "hifive1-revb" if ARCH_BOARD_HIFIVE1_REVB default "hymini-stm32v" if ARCH_BOARD_HYMINI_STM32V default "imxrt1020-evk" if ARCH_BOARD_IMXRT1020_EVK @@ -4246,6 +4256,9 @@ endif if ARCH_BOARD_GD32F470IK_EVAL source "boards/arm/gd32f4/gd32f470ik-eval/Kconfig" endif +if ARCH_BOARD_GD32F470VIT6_EVAL +source "boards/arm/gd32f4/gd32f470vit6-eval/Kconfig" +endif if ARCH_BOARD_TEENSY_LC source "boards/arm/kl/teensy-lc/Kconfig" endif @@ -5555,7 +5568,7 @@ config BOARD_ETC_ROMFS_PASSWD_ENABLE The password is hashed at build time by the host tool tools/mkpasswd (compiled from tools/mkpasswd.c) using the Tiny - Encryption Algorithm (TEA) — the same algorithm used at runtime + Encryption Algorithm (TEA) ?? the same algorithm used at runtime in libs/libc/misc/lib_tea_encrypt.c. The plaintext password is never stored in the firmware image. diff --git a/boards/arm/gd32f4/gd32f470vit6-eval b/boards/arm/gd32f4/gd32f470vit6-eval index be1271adc8974..498fdee2cfad6 120000 --- a/boards/arm/gd32f4/gd32f470vit6-eval +++ b/boards/arm/gd32f4/gd32f470vit6-eval @@ -1 +1 @@ -/home/edy_jackson/workspace/project/nuttx_pro/nuttx_mcu/mcu_plat/platform/board_gd32f470vit6 \ No newline at end of file +../../../../nuttx_mcu/mcu_plat/platform/board_gd32f470vit6 \ No newline at end of file From 9729e8ab744f13e1d7b3bce4019a9675b4090afb Mon Sep 17 00:00:00 2001 From: jacksonGreenStone <13117870426@163.com> Date: Sun, 26 Jul 2026 16:27:21 +0800 Subject: [PATCH 05/14] syslog/rtt: enable CRLF conversion for Windows RTT viewers. The RTT syslog channel previously disabled CRLF, so LF-only lines staircased in MobaXterm. Leave sc_state default so CONFIG_SYSLOG_CRLF applies. --- drivers/syslog/syslog_channel.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/syslog/syslog_channel.c b/drivers/syslog/syslog_channel.c index 1abb3097fced5..6b9d7f5af8784 100644 --- a/drivers/syslog/syslog_channel.c +++ b/drivers/syslog/syslog_channel.c @@ -139,9 +139,9 @@ static syslog_channel_t g_rtt_channel = # ifdef CONFIG_SYSLOG_IOCTL , "rtt" # endif -# ifdef CONFIG_SYSLOG_CRLF - , SYSLOG_CHANNEL_DISABLE_CRLF -# endif + /* sc_state defaults to 0: CRLF conversion ON when CONFIG_SYSLOG_CRLF=y. + * Needed for MobaXterm / Windows RTT (LF-only causes staircase wrap). + */ }; #endif From c5aca3b77fbad06e383f8e8632058173a68e9482 Mon Sep 17 00:00:00 2001 From: jacksonGreenStone <13117870426@163.com> Date: Sun, 26 Jul 2026 23:06:45 +0800 Subject: [PATCH 06/14] gd32f4: add CAN0 driver and fix USART isconsole under RTT. Port bxCAN-style CAN0 support for Board1 and correct CONSOLE_UART ifdef handling so USART5 registers as /dev/ttyS3 when RTT is console. Co-authored-by: Cursor --- arch/arm/src/gd32f4/CMakeLists.txt | 4 + arch/arm/src/gd32f4/Kconfig | 42 +- arch/arm/src/gd32f4/Make.defs | 4 + arch/arm/src/gd32f4/gd32f4xx.h | 1 + arch/arm/src/gd32f4/gd32f4xx_can.c | 2546 +++++++++++++++++ arch/arm/src/gd32f4/gd32f4xx_can.h | 136 + arch/arm/src/gd32f4/gd32f4xx_serial.c | 20 +- .../src/gd32f4/hardware/gd32f450_memorymap.h | 4 +- arch/arm/src/gd32f4/hardware/gd32f4xx_can.h | 470 +++ 9 files changed, 3216 insertions(+), 11 deletions(-) create mode 100644 arch/arm/src/gd32f4/gd32f4xx_can.c create mode 100644 arch/arm/src/gd32f4/gd32f4xx_can.h create mode 100644 arch/arm/src/gd32f4/hardware/gd32f4xx_can.h diff --git a/arch/arm/src/gd32f4/CMakeLists.txt b/arch/arm/src/gd32f4/CMakeLists.txt index 674ecf5880ab8..a818d9a749020 100644 --- a/arch/arm/src/gd32f4/CMakeLists.txt +++ b/arch/arm/src/gd32f4/CMakeLists.txt @@ -66,4 +66,8 @@ if(CONFIG_GD32F4_SDIO) list(APPEND SRCS gd32f4xx_sdio.c) endif() +if(CONFIG_GD32F4_CAN) + list(APPEND SRCS gd32f4xx_can.c) +endif() + target_sources(arch PRIVATE ${SRCS}) diff --git a/arch/arm/src/gd32f4/Kconfig b/arch/arm/src/gd32f4/Kconfig index 43312cc39f502..fb2d6f1110744 100644 --- a/arch/arm/src/gd32f4/Kconfig +++ b/arch/arm/src/gd32f4/Kconfig @@ -343,12 +343,14 @@ config GD32F4_BKPSRAM config GD32F4_CAN0 bool "CAN0" select CAN + select ARCH_HAVE_CAN_ERRORS select GD32F4_CAN default n config GD32F4_CAN1 bool "CAN1" select CAN + select ARCH_HAVE_CAN_ERRORS select GD32F4_CAN default n @@ -1893,7 +1895,7 @@ config USART5_RS485_DIR_POLARITY int "Configure USART5 RS-485 DIR pin polarity" default 1 range 0 1 - depends on USART1_RS485 + depends on USART5_RS485 ---help--- Polarity of GPIO_USART5_RS485_DIR pin for RS-485 on USART5. The state on DIR pin: 0 - receive , 1 - transmit. @@ -2655,6 +2657,44 @@ config GD32F4_USB_ITRMP menu "CAN driver configuration" depends on GD32F4_CAN +config GD32F4_CAN0_BAUD + int "CAN0 BAUD" + depends on GD32F4_CAN0 + default 500000 + ---help--- + CAN0 BAUD rate. Required if CONFIG_GD32F4_CAN0 is defined. + Default 500000 for Board1 Power CAN MVP. + +config GD32F4_CAN1_BAUD + int "CAN1 BAUD" + depends on GD32F4_CAN1 + default 500000 + ---help--- + CAN1 BAUD rate. Required if CONFIG_GD32F4_CAN1 is defined. + +config GD32F4_CAN_TSEG1 + int "TSEG1 quanta" + default 6 + ---help--- + The number of CAN time quanta in segment 1. Default: 6 + (bit time = TSEG1 + TSEG2 + 1 = 14 quanta with default TSEG2). + +config GD32F4_CAN_TSEG2 + int "TSEG2 quanta" + default 7 + ---help--- + The number of CAN time quanta in segment 2. Default: 7 + +config GD32F4_CAN_REGDEBUG + bool "CAN Register level debug" + depends on DEBUG_CAN_INFO + default n + ---help--- + Output detailed register-level CAN device debug information. + Requires also CONFIG_DEBUG_CAN_INFO. + +comment "Loopback mode uses Device Drivers -> CAN Driver Support -> CONFIG_CAN_LOOPBACK" + endmenu # "CAN driver configuration" diff --git a/arch/arm/src/gd32f4/Make.defs b/arch/arm/src/gd32f4/Make.defs index 989cee6c94f56..f1be4e8b6623d 100644 --- a/arch/arm/src/gd32f4/Make.defs +++ b/arch/arm/src/gd32f4/Make.defs @@ -64,3 +64,7 @@ endif ifeq ($(CONFIG_GD32F4_SDIO),y) CHIP_CSRCS += gd32f4xx_sdio.c endif + +ifeq ($(CONFIG_GD32F4_CAN),y) +CHIP_CSRCS += gd32f4xx_can.c +endif diff --git a/arch/arm/src/gd32f4/gd32f4xx.h b/arch/arm/src/gd32f4/gd32f4xx.h index fb743e82d555c..9b2a0c3015869 100644 --- a/arch/arm/src/gd32f4/gd32f4xx.h +++ b/arch/arm/src/gd32f4/gd32f4xx.h @@ -50,5 +50,6 @@ #include "gd32f4xx_i2c.h" #include "gd32f4xx_syscfg.h" #include "gd32f4xx_sdio.h" +#include "gd32f4xx_can.h" #endif /* __ARCH_ARM_SRC_GD32F4_GD32F4XX_H */ diff --git a/arch/arm/src/gd32f4/gd32f4xx_can.c b/arch/arm/src/gd32f4/gd32f4xx_can.c new file mode 100644 index 0000000000000..a134800341bfc --- /dev/null +++ b/arch/arm/src/gd32f4/gd32f4xx_can.c @@ -0,0 +1,2546 @@ +/**************************************************************************** + * arch/arm/src/gd32f4/gd32f4xx_can.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "arm_internal.h" +#include "chip.h" +#include "gd32f4xx.h" +#include "gd32f4xx_rcu.h" +#include "gd32f4xx_can.h" +#include "gd32f4xx_gpio.h" + +#if defined(CONFIG_CAN) && \ + (defined(CONFIG_GD32F4_CAN0) || defined(CONFIG_GD32F4_CAN1)) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Delays *******************************************************************/ + +/* Time out for INAK bit */ + +#define INAK_TIMEOUT 65535 + +/* Bit timing ***************************************************************/ + +#define CAN_BIT_QUANTA (CONFIG_GD32F4_CAN_TSEG1 + CONFIG_GD32F4_CAN_TSEG2 + 1) + +#ifndef CONFIG_DEBUG_CAN_INFO +# undef CONFIG_GD32F4_CAN_REGDEBUG +#endif + +/* CAN error interrupts */ + +#ifdef CONFIG_CAN_ERRORS +# define GD32_CAN_ERRINT (CAN_IER_LECIE | CAN_IER_ERRIE | \ + CAN_IER_BOFIE | CAN_IER_EPVIE | \ + CAN_IER_EWGIE) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct gd32_can_s +{ + uint8_t port; /* CAN port number (1 or 2) */ + uint8_t canrx[2]; /* CAN RX FIFO 0/1 IRQ number */ + uint8_t cantx; /* CAN TX IRQ number */ +#ifdef CONFIG_CAN_ERRORS + uint8_t cansce; /* CAN SCE IRQ number */ +#endif + uint8_t filter; /* Filter number */ + uint32_t base; /* Base address of the CAN control registers */ + uint32_t fbase; /* Base address of the CAN filter registers */ + uint32_t baud; /* Configured baud */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* CAN Register access */ + +static uint32_t gd32can_getreg(struct gd32_can_s *priv, + int offset); +static uint32_t gd32can_getfreg(struct gd32_can_s *priv, + int offset); +static void gd32can_putreg(struct gd32_can_s *priv, int offset, + uint32_t value); +static void gd32can_putfreg(struct gd32_can_s *priv, int offset, + uint32_t value); +#ifdef CONFIG_GD32F4_CAN_REGDEBUG +static void gd32can_dumpctrlregs(struct gd32_can_s *priv, + const char *msg); +static void gd32can_dumpmbregs(struct gd32_can_s *priv, + const char *msg); +static void gd32can_dumpfiltregs(struct gd32_can_s *priv, + const char *msg); +#else +# define gd32can_dumpctrlregs(priv,msg) +# define gd32can_dumpmbregs(priv,msg) +# define gd32can_dumpfiltregs(priv,msg) +#endif + +/* Filtering (todo) */ + +#ifdef CONFIG_CAN_EXTID +static int gd32can_addextfilter(struct gd32_can_s *priv, + struct canioc_extfilter_s *arg); +static int gd32can_delextfilter(struct gd32_can_s *priv, + int arg); +#endif +static int gd32can_addstdfilter(struct gd32_can_s *priv, + struct canioc_stdfilter_s *arg); +static int gd32can_delstdfilter(struct gd32_can_s *priv, + int arg); + +/* CAN driver methods */ + +static void gd32can_reset(struct can_dev_s *dev); +static int gd32can_setup(struct can_dev_s *dev); +static void gd32can_shutdown(struct can_dev_s *dev); +static void gd32can_rxint(struct can_dev_s *dev, bool enable); +static void gd32can_txint(struct can_dev_s *dev, bool enable); +static int gd32can_ioctl(struct can_dev_s *dev, int cmd, + unsigned long arg); +static int gd32can_remoterequest(struct can_dev_s *dev, + uint16_t id); +static int gd32can_send(struct can_dev_s *dev, + struct can_msg_s *msg); +static bool gd32can_txready(struct can_dev_s *dev); +static bool gd32can_txempty(struct can_dev_s *dev); + +#ifdef CONFIG_CAN_ERRORS +static void gd32can_errint(struct can_dev_s *dev, bool enable); +#endif + +/* CAN interrupt handling */ + +static int gd32can_rxinterrupt(struct can_dev_s *dev, int rxmb); +static int gd32can_rx0interrupt(int irq, void *context, void *arg); +static int gd32can_rx1interrupt(int irq, void *context, void *arg); +static int gd32can_txinterrupt(int irq, void *context, void *arg); +#ifdef CONFIG_CAN_ERRORS +static int gd32can_sceinterrupt(int irq, void *context, void *arg); +#endif + +/* Initialization */ + +static int gd32can_enterinitmode(struct gd32_can_s *priv); +static int gd32can_exitinitmode(struct gd32_can_s *priv); +static int gd32can_bittiming(struct gd32_can_s *priv); +static int gd32can_cellinit(struct gd32_can_s *priv); +static int gd32can_filterinit(struct gd32_can_s *priv); + +/* TX mailbox status */ + +static bool gd32can_txmb0empty(uint32_t tsr_regval); +static bool gd32can_txmb1empty(uint32_t tsr_regval); +static bool gd32can_txmb2empty(uint32_t tsr_regval); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct can_ops_s g_canops = +{ + .co_reset = gd32can_reset, + .co_setup = gd32can_setup, + .co_shutdown = gd32can_shutdown, + .co_rxint = gd32can_rxint, + .co_txint = gd32can_txint, + .co_ioctl = gd32can_ioctl, + .co_remoterequest = gd32can_remoterequest, + .co_send = gd32can_send, + .co_txready = gd32can_txready, + .co_txempty = gd32can_txempty, +}; + +#ifdef CONFIG_GD32F4_CAN0 +static struct gd32_can_s g_can0priv = +{ + .port = 0, + .canrx = + { + GD32_IRQ_CAN0_RX0, + GD32_IRQ_CAN0_RX1, + }, + .cantx = GD32_IRQ_CAN0_TX, +#ifdef CONFIG_CAN_ERRORS + .cansce = GD32_IRQ_CAN0_EWMC, +#endif + .filter = 0, + .base = GD32_CAN0_BASE, + .fbase = GD32_CAN0_BASE, + .baud = CONFIG_GD32F4_CAN0_BAUD, +}; + +static struct can_dev_s g_can0dev = +{ + .cd_ops = &g_canops, + .cd_priv = &g_can0priv, +}; +#endif + +#ifdef CONFIG_GD32F4_CAN1 +static struct gd32_can_s g_can1priv = +{ + .port = 1, + .canrx = + { + GD32_IRQ_CAN1_RX0, + GD32_IRQ_CAN1_RX1, + }, + .cantx = GD32_IRQ_CAN1_TX, +#ifdef CONFIG_CAN_ERRORS + .cansce = GD32_IRQ_CAN1_EWMC, +#endif + .filter = CAN_NFILTERS / 2, + .base = GD32_CAN1_BASE, + .fbase = GD32_CAN0_BASE, + .baud = CONFIG_GD32F4_CAN1_BAUD, +}; + +static struct can_dev_s g_can1dev = +{ + .cd_ops = &g_canops, + .cd_priv = &g_can1priv, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: gd32can_getreg + * Name: gd32can_getfreg + * + * Description: + * Read the value of a CAN register or filter block register. + * + * Input Parameters: + * priv - A reference to the CAN block status + * offset - The offset to the register to read + * + * Returned Value: + * + ****************************************************************************/ + +#ifdef CONFIG_GD32F4_CAN_REGDEBUG +static uint32_t gd32can_vgetreg(uint32_t addr) +{ + static uint32_t prevaddr = 0; + static uint32_t preval = 0; + static uint32_t count = 0; + + /* Read the value from the register */ + + uint32_t val = getreg32(addr); + + /* Is this the same value that we read from the same register last time? + * Are we polling the register? If so, suppress some of the output. + */ + + if (addr == prevaddr && val == preval) + { + if (count == 0xffffffff || ++count > 3) + { + if (count == 4) + { + caninfo("...\n"); + } + + return val; + } + } + + /* No this is a new address or value */ + + else + { + /* Did we print "..." for the previous value? */ + + if (count > 3) + { + /* Yes.. then show how many times the value repeated */ + + caninfo("[repeats %" PRIu32 " more times]\n", count - 3); + } + + /* Save the new address, value, and count */ + + prevaddr = addr; + preval = val; + count = 1; + } + + /* Show the register value read */ + + caninfo("%08" PRIx32 "->%08" PRIx32 "\n", addr, val); + return val; +} + +static uint32_t gd32can_getreg(struct gd32_can_s *priv, int offset) +{ + return gd32can_vgetreg(priv->base + offset); +} + +static uint32_t gd32can_getfreg(struct gd32_can_s *priv, int offset) +{ + return gd32can_vgetreg(priv->fbase + offset); +} + +#else +static uint32_t gd32can_getreg(struct gd32_can_s *priv, int offset) +{ + return getreg32(priv->base + offset); +} + +static uint32_t gd32can_getfreg(struct gd32_can_s *priv, int offset) +{ + return getreg32(priv->fbase + offset); +} + +#endif + +/**************************************************************************** + * Name: gd32can_putreg + * Name: gd32can_putfreg + * + * Description: + * Set the value of a CAN register or filter block register. + * + * Input Parameters: + * priv - A reference to the CAN block status + * offset - The offset to the register to write + * value - The value to write to the register + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_GD32F4_CAN_REGDEBUG +static void gd32can_vputreg(uint32_t addr, uint32_t value) +{ + /* Show the register value being written */ + + caninfo("%08" PRIx32 "->%08" PRIx32 "\n", addr, value); + + /* Write the value */ + + putreg32(value, addr); +} + +static void gd32can_putreg(struct gd32_can_s *priv, int offset, + uint32_t value) +{ + gd32can_vputreg(priv->base + offset, value); +} + +static void gd32can_putfreg(struct gd32_can_s *priv, int offset, + uint32_t value) +{ + gd32can_vputreg(priv->fbase + offset, value); +} + +#else +static void gd32can_putreg(struct gd32_can_s *priv, int offset, + uint32_t value) +{ + putreg32(value, priv->base + offset); +} + +static void gd32can_putfreg(struct gd32_can_s *priv, int offset, + uint32_t value) +{ + putreg32(value, priv->fbase + offset); +} +#endif + +/**************************************************************************** + * Name: gd32can_dumpctrlregs + * + * Description: + * Dump the contents of all CAN control registers + * + * Input Parameters: + * priv - A reference to the CAN block status + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_GD32F4_CAN_REGDEBUG +static void gd32can_dumpctrlregs(struct gd32_can_s *priv, + const char *msg) +{ + if (msg) + { + caninfo("Control Registers: %s\n", msg); + } + else + { + caninfo("Control Registers:\n"); + } + + /* CAN control and status registers */ + + caninfo(" MCR: %08" PRIx32 " MSR: %08" PRIx32 " TSR: %08" PRIx32 "\n", + getreg32(priv->base + GD32_CAN_MCR_OFFSET), + getreg32(priv->base + GD32_CAN_MSR_OFFSET), + getreg32(priv->base + GD32_CAN_TSR_OFFSET)); + + caninfo(" RF0R: %08" PRIx32 " RF1R: %08" PRIx32 "\n", + getreg32(priv->base + GD32_CAN_RF0R_OFFSET), + getreg32(priv->base + GD32_CAN_RF1R_OFFSET)); + + caninfo(" IER: %08" PRIx32 " ESR: %08" PRIx32 " BTR: %08" PRIx32 "\n", + getreg32(priv->base + GD32_CAN_IER_OFFSET), + getreg32(priv->base + GD32_CAN_ESR_OFFSET), + getreg32(priv->base + GD32_CAN_BTR_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: gd32can_dumpmbregs + * + * Description: + * Dump the contents of all CAN mailbox registers + * + * Input Parameters: + * priv - A reference to the CAN block status + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_GD32F4_CAN_REGDEBUG +static void gd32can_dumpmbregs(struct gd32_can_s *priv, + const char *msg) +{ + if (msg) + { + caninfo("Mailbox Registers: %s\n", msg); + } + else + { + caninfo("Mailbox Registers:\n"); + } + + /* CAN mailbox registers (3 TX and 2 RX) */ + + caninfo(" TI0R: %08" PRIx32 " TDT0R: %08" PRIx32 " TDL0R: %08" + PRIx32 " TDH0R: %08" PRIx32 "\n", + getreg32(priv->base + GD32_CAN_TI0R_OFFSET), + getreg32(priv->base + GD32_CAN_TDT0R_OFFSET), + getreg32(priv->base + GD32_CAN_TDL0R_OFFSET), + getreg32(priv->base + GD32_CAN_TDH0R_OFFSET)); + + caninfo(" TI1R: %08" PRIx32 " TDT1R: %08" PRIx32 " TDL1R: %08" + PRIx32 " TDH1R: %08" PRIx32 "\n", + getreg32(priv->base + GD32_CAN_TI1R_OFFSET), + getreg32(priv->base + GD32_CAN_TDT1R_OFFSET), + getreg32(priv->base + GD32_CAN_TDL1R_OFFSET), + getreg32(priv->base + GD32_CAN_TDH1R_OFFSET)); + + caninfo(" TI2R: %08" PRIx32 " TDT2R: %08" PRIx32 " TDL2R: %08" + PRIx32 " TDH2R: %08" PRIx32 "\n", + getreg32(priv->base + GD32_CAN_TI2R_OFFSET), + getreg32(priv->base + GD32_CAN_TDT2R_OFFSET), + getreg32(priv->base + GD32_CAN_TDL2R_OFFSET), + getreg32(priv->base + GD32_CAN_TDH2R_OFFSET)); + + caninfo(" RI0R: %08" PRIx32 " RDT0R: %08" PRIx32 " RDL0R: %08" + PRIx32 " RDH0R: %08" PRIx32 "\n", + getreg32(priv->base + GD32_CAN_RI0R_OFFSET), + getreg32(priv->base + GD32_CAN_RDT0R_OFFSET), + getreg32(priv->base + GD32_CAN_RDL0R_OFFSET), + getreg32(priv->base + GD32_CAN_RDH0R_OFFSET)); + + caninfo(" RI1R: %08" PRIx32 " RDT1R: %08" PRIx32 " RDL1R: %08" + PRIx32 " RDH1R: %08" PRIx32 "\n", + getreg32(priv->base + GD32_CAN_RI1R_OFFSET), + getreg32(priv->base + GD32_CAN_RDT1R_OFFSET), + getreg32(priv->base + GD32_CAN_RDL1R_OFFSET), + getreg32(priv->base + GD32_CAN_RDH1R_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: gd32can_dumpfiltregs + * + * Description: + * Dump the contents of all CAN filter registers + * + * Input Parameters: + * priv - A reference to the CAN block status + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_GD32F4_CAN_REGDEBUG +static void gd32can_dumpfiltregs(struct gd32_can_s *priv, + const char *msg) +{ + int i; + + if (msg) + { + caninfo("Filter Registers: %s\n", msg); + } + else + { + caninfo("Filter Registers:\n"); + } + + caninfo(" FMR: %08" PRIx32 " FM1R: %08" PRIx32 " FS1R: %08" + PRIx32 " FFA1R: %08" PRIx32 " FA1R: %08" PRIx32 "\n", + getreg32(priv->base + GD32_CAN_FMR_OFFSET), + getreg32(priv->base + GD32_CAN_FM1R_OFFSET), + getreg32(priv->base + GD32_CAN_FS1R_OFFSET), + getreg32(priv->base + GD32_CAN_FFA1R_OFFSET), + getreg32(priv->base + GD32_CAN_FA1R_OFFSET)); + + for (i = 0; i < CAN_NFILTERS; i++) + { + caninfo(" F%dR1: %08" PRIx32 " F%dR2: %08" PRIx32 "\n", + i, getreg32(priv->base + GD32_CAN_FIR_OFFSET(i, 1)), + i, getreg32(priv->base + GD32_CAN_FIR_OFFSET(i, 2))); + } +} +#endif + +/**************************************************************************** + * Name: gd32can_reset + * + * Description: + * Reset the CAN device. Called early to initialize the hardware. This + * function is called, before gd32can_setup() and on error conditions. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void gd32can_reset(struct can_dev_s *dev) +{ + struct gd32_can_s *priv = dev->cd_priv; + uint32_t regval; + uint32_t regbit = 0; + irqstate_t flags; + + caninfo("CAN%" PRIu8 "\n", priv->port); + + /* Get the bits in the AHB1RSTR register needed to reset this CAN device */ + +#ifdef CONFIG_GD32F4_CAN0 + if (priv->port == 0) + { + regbit = RCU_APB1RST_CAN0RST; + } + else +#endif +#ifdef CONFIG_GD32F4_CAN1 + if (priv->port == 1) + { + regbit = RCU_APB1RST_CAN1RST; + } + else +#endif + { + canerr("ERROR: Unsupported port %d\n", priv->port); + return; + } + + /* Disable interrupts momentarily to stop any ongoing CAN event processing + * and to prevent any concurrent access to the AHB1RSTR register. + */ + + flags = enter_critical_section(); + + /* Reset the CAN */ + + regval = getreg32(GD32_RCU_APB1RST); + regval |= regbit; + putreg32(regval, GD32_RCU_APB1RST); + + regval &= ~regbit; + putreg32(regval, GD32_RCU_APB1RST); + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: gd32can_setup + * + * Description: + * Configure the CAN. This method is called the first time that the CAN + * device is opened. This will occur when the port is first opened. + * This setup includes configuring and attaching CAN interrupts. + * All CAN interrupts are disabled upon return. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int gd32can_setup(struct can_dev_s *dev) +{ + struct gd32_can_s *priv = dev->cd_priv; + int ret; + +#ifdef CONFIG_CAN_ERRORS + ninfo("CAN%" PRIu8 " RX0 irq: %" PRIu8 " RX1 irq: %" PRIu8 + " TX irq: %" PRIu8 " SCE irq: %" PRIu8 "\n", + priv->port, priv->canrx[0], priv->canrx[1], priv->cantx, + priv->cansce); +#else + ninfo("CAN%" PRIu8 " RX0 irq: %" PRIu8 " RX1 irq: %" PRIu8 + " TX irq: %" PRIu8 "\n", + priv->port, priv->canrx[0], priv->canrx[1], priv->cantx); +#endif + + /* CAN cell initialization */ + + ret = gd32can_cellinit(priv); + if (ret < 0) + { + canerr("ERROR: CAN%" PRId8 " cell initialization failed: %d\n", + priv->port, ret); + return ret; + } + + gd32can_dumpctrlregs(priv, "After cell initialization"); + gd32can_dumpmbregs(priv, NULL); + + /* CAN filter initialization */ + + ret = gd32can_filterinit(priv); + if (ret < 0) + { + canerr("ERROR: CAN%" PRIu8 " filter initialization failed: %d\n", + priv->port, ret); + return ret; + } + + gd32can_dumpfiltregs(priv, "After filter initialization"); + + /* Attach the CAN RX FIFO 0/1 interrupts and TX interrupts. + * The others are not used. + */ + + ret = irq_attach(priv->canrx[0], gd32can_rx0interrupt, dev); + if (ret < 0) + { + canerr("ERROR: Failed to attach CAN%" PRIu8 " RX0 IRQ (%" PRIu8 ")", + priv->port, priv->canrx[0]); + return ret; + } + + ret = irq_attach(priv->canrx[1], gd32can_rx1interrupt, dev); + if (ret < 0) + { + canerr("ERROR: Failed to attach CAN%" PRIu8 " RX1 IRQ (%" PRIu8 ")", + priv->port, priv->canrx[1]); + return ret; + } + + ret = irq_attach(priv->cantx, gd32can_txinterrupt, dev); + if (ret < 0) + { + canerr("ERROR: Failed to attach CAN%" PRIu8 " TX IRQ (%" PRIu8 ")", + priv->port, priv->cantx); + return ret; + } + +#ifdef CONFIG_CAN_ERRORS + ret = irq_attach(priv->cansce, gd32can_sceinterrupt, dev); + if (ret < 0) + { + nerr("ERROR: Failed to attach CAN%" PRIu8 " SCE IRQ (%" PRIu8 ")", + priv->port, priv->cansce); + return ret; + } + + /* Enable CAN error interrupts */ + + gd32can_errint(dev, true); +#endif + + /* Enable the interrupts at the NVIC. Interrupts are still disabled in + * the CAN module. Since we coming out of reset here, there should be + * no pending interrupts. + */ + + up_enable_irq(priv->canrx[0]); + up_enable_irq(priv->canrx[1]); + up_enable_irq(priv->cantx); +#ifdef CONFIG_CAN_ERRORS + up_enable_irq(priv->cansce); +#endif + return OK; +} + +/**************************************************************************** + * Name: gd32can_shutdown + * + * Description: + * Disable the CAN. This method is called when the CAN device is closed. + * This method reverses the operation the setup method. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void gd32can_shutdown(struct can_dev_s *dev) +{ + struct gd32_can_s *priv = dev->cd_priv; + + caninfo("CAN%" PRIu8 "\n", priv->port); + + /* Disable the RX FIFO 0/1, TX and SCE interrupts */ + + up_disable_irq(priv->canrx[0]); + up_disable_irq(priv->canrx[1]); + up_disable_irq(priv->cantx); +#ifdef CONFIG_CAN_ERRORS + up_disable_irq(priv->cansce); +#endif + + /* Detach the RX FIFO 0/1, TX and SCE interrupts */ + + irq_detach(priv->canrx[0]); + irq_detach(priv->canrx[1]); + irq_detach(priv->cantx); +#ifdef CONFIG_CAN_ERRORS + irq_detach(priv->cansce); +#endif + + /* And reset the hardware */ + + gd32can_reset(dev); +} + +/**************************************************************************** + * Name: gd32can_rxint + * + * Description: + * Call to enable or disable RX interrupts. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void gd32can_rxint(struct can_dev_s *dev, bool enable) +{ + struct gd32_can_s *priv = dev->cd_priv; + uint32_t regval; + + caninfo("CAN%" PRIu8 " rxint enable: %d\n", priv->port, enable); + + /* Enable/disable the FIFO 0/1 message pending interrupt */ + + regval = gd32can_getreg(priv, GD32_CAN_IER_OFFSET); + if (enable) + { + regval |= CAN_IER_FMPIE0 | CAN_IER_FMPIE1; + } + else + { + regval &= ~(CAN_IER_FMPIE0 | CAN_IER_FMPIE1); + } + + gd32can_putreg(priv, GD32_CAN_IER_OFFSET, regval); +} + +/**************************************************************************** + * Name: gd32can_txint + * + * Description: + * Call to enable or disable TX interrupts. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void gd32can_txint(struct can_dev_s *dev, bool enable) +{ + struct gd32_can_s *priv = dev->cd_priv; + uint32_t regval; + + caninfo("CAN%" PRIu8 " txint enable: %d\n", priv->port, enable); + + /* Support only disabling the transmit mailbox interrupt */ + + if (!enable) + { + regval = gd32can_getreg(priv, GD32_CAN_IER_OFFSET); + regval &= ~CAN_IER_TMEIE; + gd32can_putreg(priv, GD32_CAN_IER_OFFSET, regval); + } +} + +#ifdef CONFIG_CAN_ERRORS +/**************************************************************************** + * Name: gd32can_errint + * + * Description: + * Call to enable or disable CAN error interrupts. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void gd32can_errint(struct can_dev_s *dev, bool enable) +{ + struct gd32_can_s *priv = dev->cd_priv; + uint32_t regval = 0; + + caninfo("CAN%" PRIu8 " errint enable: %d\n", priv->port, enable); + + /* Enable/disable the transmit mailbox interrupt */ + + regval = gd32can_getreg(priv, GD32_CAN_IER_OFFSET); + if (enable) + { + regval |= GD32_CAN_ERRINT; + } + else + { + regval &= ~GD32_CAN_ERRINT; + } + + gd32can_putreg(priv, GD32_CAN_IER_OFFSET, regval); +} +#endif + +/**************************************************************************** + * Name: gd32can_ioctl + * + * Description: + * All ioctl calls will be routed through this method + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int gd32can_ioctl(struct can_dev_s *dev, int cmd, + unsigned long arg) +{ + struct gd32_can_s *priv; + int ret = -ENOTTY; + + caninfo("cmd=%04x arg=%lu\n", cmd, arg); + + DEBUGASSERT(dev && dev->cd_priv); + priv = dev->cd_priv; + + /* Handle the command */ + + switch (cmd) + { + /* CANIOC_GET_BITTIMING: + * Description: Return the current bit timing settings + * Argument: A pointer to a write-able instance of struct + * canioc_bittiming_s in which current bit timing + * values will be returned. + * Returned Value: Zero (OK) is returned on success. Otherwise -1 + * (ERROR) is returned with the errno variable set + * to indicate the nature of the error. + * Dependencies: None + */ + + case CANIOC_GET_BITTIMING: + { + struct canioc_bittiming_s *bt = + (struct canioc_bittiming_s *)arg; + uint32_t regval; + uint32_t brp; + + DEBUGASSERT(bt != NULL); + regval = gd32can_getreg(priv, GD32_CAN_BTR_OFFSET); + bt->bt_sjw = ((regval & CAN_BTR_SJW_MASK) >> + CAN_BTR_SJW_SHIFT) + 1; + bt->bt_tseg1 = ((regval & CAN_BTR_TS1_MASK) >> + CAN_BTR_TS1_SHIFT) + 1; + bt->bt_tseg2 = ((regval & CAN_BTR_TS2_MASK) >> + CAN_BTR_TS2_SHIFT) + 1; + + brp = ((regval & CAN_BTR_BRP_MASK) >> + CAN_BTR_BRP_SHIFT) + 1; + bt->bt_baud = GD32_PCLK1_FREQUENCY / + (brp * (bt->bt_tseg1 + bt->bt_tseg2 + 1)); + ret = OK; + } + break; + + /* CANIOC_SET_BITTIMING: + * Description: Set new current bit timing values + * Argument: A pointer to a read-able instance of struct + * canioc_bittiming_s in which the new bit timing + * values are provided. + * Returned Value: Zero (OK) is returned on success. Otherwise -1 + * (ERROR)is returned with the errno variable set + * to indicate thenature of the error. + * Dependencies: None + * + * REVISIT: There is probably a limitation here: If there are + * multiple threads trying to send CAN packets, when one of these + * threads reconfigures the bitrate, the MCAN hardware will be reset + * and the context of operation will be lost. Hence, this IOCTL can + * only safely be executed in quiescent time periods. + */ + + case CANIOC_SET_BITTIMING: + { + const struct canioc_bittiming_s *bt = + (const struct canioc_bittiming_s *)arg; + uint32_t brp; + uint32_t can_bit_quanta; + uint32_t tmp; + uint32_t regval; + + DEBUGASSERT(bt != NULL); + DEBUGASSERT(bt->bt_baud < GD32_PCLK1_FREQUENCY); + DEBUGASSERT(bt->bt_sjw > 0 && bt->bt_sjw <= 4); + DEBUGASSERT(bt->bt_tseg1 > 0 && bt->bt_tseg1 <= 16); + DEBUGASSERT(bt->bt_tseg2 > 0 && bt->bt_tseg2 <= 8); + + regval = gd32can_getreg(priv, GD32_CAN_BTR_OFFSET); + + /* Extract bit timing data + * tmp is in clocks per bit time + */ + + tmp = GD32_PCLK1_FREQUENCY / bt->bt_baud; + + /* This value is dynamic as requested by user */ + + can_bit_quanta = bt->bt_tseg1 + bt->bt_tseg2 + 1; + + if (tmp < can_bit_quanta) + { + /* This timing is not possible */ + + ret = -EINVAL; + break; + } + + /* Otherwise, nquanta is can_bit_quanta, ts1 and ts2 are + * provided by the user and we calculate brp to achieve + * can_bit_quanta quanta in the bit times + */ + + else + { + brp = (tmp + (can_bit_quanta / 2)) / can_bit_quanta; + DEBUGASSERT(brp >= 1 && brp <= CAN_BTR_BRP_MAX); + } + + caninfo("TS1: %"PRIu8 " TS2: %" PRIu8 " BRP: %" PRIu32 "\n", + bt->bt_tseg1, bt->bt_tseg2, brp); + + /* Configure bit timing. */ + + regval &= ~(CAN_BTR_BRP_MASK | CAN_BTR_TS1_MASK | + CAN_BTR_TS2_MASK | CAN_BTR_SJW_MASK); + regval |= ((brp - 1) << CAN_BTR_BRP_SHIFT) | + ((bt->bt_tseg1 - 1) << CAN_BTR_TS1_SHIFT) | + ((bt->bt_tseg2 - 1) << CAN_BTR_TS2_SHIFT) | + ((bt->bt_sjw - 1) << CAN_BTR_SJW_SHIFT); + + /* Bit timing can only be configured in init mode. */ + + ret = gd32can_enterinitmode(priv); + if (ret < 0) + { + break; + } + + gd32can_putreg(priv, GD32_CAN_BTR_OFFSET, regval); + + ret = gd32can_exitinitmode(priv); + if (ret >= 0) + { + priv->baud = GD32_PCLK1_FREQUENCY / + (brp * (bt->bt_tseg1 + bt->bt_tseg2 + 1)); + } + } + break; + + /* CANIOC_GET_CONNMODES: + * Description: Get the current bus connection modes + * Argument: A pointer to a write-able instance of struct + * canioc_connmodes_s in which the new bus modes will + * be returned. + * Returned Value: Zero (OK) is returned on success. Otherwise -1 + * (ERROR)is returned with the errno variable set + * to indicate the nature of the error. + * Dependencies: None + */ + + case CANIOC_GET_CONNMODES: + { + struct canioc_connmodes_s *bm = + (struct canioc_connmodes_s *)arg; + uint32_t regval; + + DEBUGASSERT(bm != NULL); + + regval = gd32can_getreg(priv, GD32_CAN_BTR_OFFSET); + + bm->bm_loopback = ((regval & CAN_BTR_LBKM) == CAN_BTR_LBKM); + bm->bm_silent = ((regval & CAN_BTR_SILM) == CAN_BTR_SILM); + ret = OK; + break; + } + + /* CANIOC_SET_CONNMODES: + * Description: Set new bus connection modes values + * Argument: A pointer to a read-able instance of struct + * canioc_connmodes_s in which the new bus modes + * are provided. + * Returned Value: Zero (OK) is returned on success. Otherwise -1 + * (ERROR) is returned with the errno variable set + * to indicate the nature of the error. + * Dependencies: None + */ + + case CANIOC_SET_CONNMODES: + { + struct canioc_connmodes_s *bm = + (struct canioc_connmodes_s *)arg; + uint32_t regval; + + DEBUGASSERT(bm != NULL); + + regval = gd32can_getreg(priv, GD32_CAN_BTR_OFFSET); + + if (bm->bm_loopback) + { + regval |= CAN_BTR_LBKM; + } + else + { + regval &= ~CAN_BTR_LBKM; + } + + if (bm->bm_silent) + { + regval |= CAN_BTR_SILM; + } + else + { + regval &= ~CAN_BTR_SILM; + } + + /* This register can only be configured in init mode. */ + + ret = gd32can_enterinitmode(priv); + if (ret < 0) + { + break; + } + + gd32can_putreg(priv, GD32_CAN_BTR_OFFSET, regval); + + ret = gd32can_exitinitmode(priv); + } + break; + +#ifdef CONFIG_CAN_EXTID + /* CANIOC_ADD_EXTFILTER: + * Description: Add an address filter for a extended 29 bit + * address. + * Argument: A reference to struct canioc_extfilter_s + * Returned Value: A non-negative filter ID is returned on success. + * Otherwise -1 (ERROR) is returned with the errno + * variable set to indicate the nature of the error. + */ + + case CANIOC_ADD_EXTFILTER: + { + DEBUGASSERT(arg != 0); + ret = gd32can_addextfilter(priv, + (struct canioc_extfilter_s *)arg); + } + break; + + /* CANIOC_DEL_EXTFILTER: + * Description: Remove an address filter for a standard 29 bit + * address. + * Argument: The filter index previously returned by the + * CANIOC_ADD_EXTFILTER command + * Returned Value: Zero (OK) is returned on success. Otherwise -1 + * (ERROR)is returned with the errno variable set + * to indicate the nature of the error. + */ + + case CANIOC_DEL_EXTFILTER: + { +#if 0 /* Unimplemented */ + DEBUGASSERT(arg <= priv->config->nextfilters); +#endif + ret = gd32can_delextfilter(priv, (int)arg); + } + break; +#endif + + /* CANIOC_ADD_STDFILTER: + * Description: Add an address filter for a standard 11 bit + * address. + * Argument: A reference to struct canioc_stdfilter_s + * Returned Value: A non-negative filter ID is returned on success. + * Otherwise -1 (ERROR) is returned with the errno + * variable set to indicate the nature of the error. + */ + + case CANIOC_ADD_STDFILTER: + { + DEBUGASSERT(arg != 0); + ret = gd32can_addstdfilter(priv, + (struct canioc_stdfilter_s *)arg); + } + break; + + /* CANIOC_DEL_STDFILTER: + * Description: Remove an address filter for a standard 11 bit + * address. + * Argument: The filter index previously returned by the + * CANIOC_ADD_STDFILTER command + * Returned Value: Zero (OK) is returned on success. Otherwise -1 + * (ERROR) is returned with the errno variable set + * to indicate the nature of the error. + */ + + case CANIOC_DEL_STDFILTER: + { +#if 0 /* Unimplemented */ + DEBUGASSERT(arg <= priv->config->nstdfilters); +#endif + ret = gd32can_delstdfilter(priv, (int)arg); + } + break; + + case CANIOC_SET_NART: + { + uint32_t regval; + + ret = gd32can_enterinitmode(priv); + if (ret != 0) + { + return ret; + } + + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + if (arg == 1) + { + regval |= CAN_MCR_NART; + } + else + { + regval &= ~CAN_MCR_NART; + } + + gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); + return gd32can_exitinitmode(priv); + } + break; + + case CANIOC_SET_ABOM: + { + uint32_t regval; + + ret = gd32can_enterinitmode(priv); + if (ret != 0) + { + return ret; + } + + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + if (arg == 1) + { + regval |= CAN_MCR_ABOM; + } + else + { + regval &= ~CAN_MCR_ABOM; + } + + gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); + return gd32can_exitinitmode(priv); + } + break; + + /* Unsupported/unrecognized command */ + + default: + canerr("ERROR: Unrecognized command: %04x\n", cmd); + break; + } + + return ret; +} + +/**************************************************************************** + * Name: gd32can_remoterequest + * + * Description: + * Send a remote request + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int gd32can_remoterequest(struct can_dev_s *dev, uint16_t id) +{ +#warning "Remote request not implemented" + return -ENOSYS; +} + +/**************************************************************************** + * Name: gd32can_send + * + * Description: + * Send one can message. + * + * One CAN-message consists of a maximum of 10 bytes. A message is + * composed of at least the first 2 bytes (when there are no data bytes). + * + * Byte 0: Bits 0-7: Bits 3-10 of the 11-bit CAN identifier + * Byte 1: Bits 5-7: Bits 0-2 of the 11-bit CAN identifier + * Bit 4: Remote Transmission Request (RTR) + * Bits 0-3: Data Length Code (DLC) + * Bytes 2-10: CAN data + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int gd32can_send(struct can_dev_s *dev, + struct can_msg_s *msg) +{ + struct gd32_can_s *priv = dev->cd_priv; + uint8_t *ptr; + uint32_t regval; + uint32_t tmp; + int dlc; + int txmb; + + caninfo("CAN%" PRIu8 " ID: %" PRIu32 " DLC: %" PRIu8 "\n", + priv->port, (uint32_t)msg->cm_hdr.ch_id, msg->cm_hdr.ch_dlc); + + /* Select one empty transmit mailbox */ + + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + if (gd32can_txmb0empty(regval)) + { + txmb = 0; + } + else if (gd32can_txmb1empty(regval)) + { + txmb = 1; + } + else if (gd32can_txmb2empty(regval)) + { + txmb = 2; + } + else + { + canerr("ERROR: No available mailbox\n"); + return -EBUSY; + } + + /* Clear TXRQ, RTR, IDE, EXID, and STID fields */ + + regval = gd32can_getreg(priv, GD32_CAN_TIR_OFFSET(txmb)); + regval &= ~(CAN_TIR_TXRQ | CAN_TIR_RTR | CAN_TIR_IDE | + CAN_TIR_EXID_MASK | CAN_TIR_STID_MASK); + gd32can_putreg(priv, GD32_CAN_TIR_OFFSET(txmb), regval); + + /* Set up the ID, standard 11-bit or extended 29-bit. */ + +#ifdef CONFIG_CAN_EXTID + regval &= ~CAN_TIR_EXID_MASK; + if (msg->cm_hdr.ch_extid) + { + DEBUGASSERT(msg->cm_hdr.ch_id < (1 << 29)); + regval |= (msg->cm_hdr.ch_id << CAN_TIR_EXID_SHIFT) | CAN_TIR_IDE; + } + else + { + DEBUGASSERT(msg->cm_hdr.ch_id < (1 << 11)); + regval |= msg->cm_hdr.ch_id << CAN_TIR_STID_SHIFT; + } + +#else + regval |= (((uint32_t) msg->cm_hdr.ch_id << CAN_TIR_STID_SHIFT) & + CAN_TIR_STID_MASK); + +#endif + +#ifdef CONFIG_CAN_USE_RTR + regval |= (msg->cm_hdr.ch_rtr ? CAN_TIR_RTR : 0); +#endif + + gd32can_putreg(priv, GD32_CAN_TIR_OFFSET(txmb), regval); + + /* Set up the DLC */ + + dlc = msg->cm_hdr.ch_dlc; + regval = gd32can_getreg(priv, GD32_CAN_TDTR_OFFSET(txmb)); + regval &= ~(CAN_TDTR_DLC_MASK | CAN_TDTR_TGT); + regval |= (uint32_t)dlc << CAN_TDTR_DLC_SHIFT; + gd32can_putreg(priv, GD32_CAN_TDTR_OFFSET(txmb), regval); + + /* Set up the data fields */ + + ptr = msg->cm_data; + regval = 0; + + if (dlc > 0) + { + tmp = (uint32_t)*ptr++; + regval = tmp << CAN_TDLR_DATA0_SHIFT; + + if (dlc > 1) + { + tmp = (uint32_t)*ptr++; + regval |= tmp << CAN_TDLR_DATA1_SHIFT; + + if (dlc > 2) + { + tmp = (uint32_t)*ptr++; + regval |= tmp << CAN_TDLR_DATA2_SHIFT; + + if (dlc > 3) + { + tmp = (uint32_t)*ptr++; + regval |= tmp << CAN_TDLR_DATA3_SHIFT; + } + } + } + } + + gd32can_putreg(priv, GD32_CAN_TDLR_OFFSET(txmb), regval); + + regval = 0; + if (dlc > 4) + { + tmp = (uint32_t)*ptr++; + regval = tmp << CAN_TDHR_DATA4_SHIFT; + + if (dlc > 5) + { + tmp = (uint32_t)*ptr++; + regval |= tmp << CAN_TDHR_DATA5_SHIFT; + + if (dlc > 6) + { + tmp = (uint32_t)*ptr++; + regval |= tmp << CAN_TDHR_DATA6_SHIFT; + + if (dlc > 7) + { + tmp = (uint32_t)*ptr++; + regval |= tmp << CAN_TDHR_DATA7_SHIFT; + } + } + } + } + + gd32can_putreg(priv, GD32_CAN_TDHR_OFFSET(txmb), regval); + + /* Enable the transmit mailbox empty interrupt (may already be enabled) */ + + regval = gd32can_getreg(priv, GD32_CAN_IER_OFFSET); + regval |= CAN_IER_TMEIE; + gd32can_putreg(priv, GD32_CAN_IER_OFFSET, regval); + + /* Request transmission */ + + regval = gd32can_getreg(priv, GD32_CAN_TIR_OFFSET(txmb)); + regval |= CAN_TIR_TXRQ; /* Transmit Mailbox Request */ + gd32can_putreg(priv, GD32_CAN_TIR_OFFSET(txmb), regval); + + gd32can_dumpmbregs(priv, "After send"); + return OK; +} + +/**************************************************************************** + * Name: gd32can_txready + * + * Description: + * Return true if the CAN hardware can accept another TX message. + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * True if the CAN hardware is ready to accept another TX message. + * + ****************************************************************************/ + +static bool gd32can_txready(struct can_dev_s *dev) +{ + struct gd32_can_s *priv = dev->cd_priv; + uint32_t regval; + + /* Return true if any mailbox is available */ + + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + caninfo("CAN%" PRIu8 " TSR: %08" PRIx32 "\n", priv->port, regval); + + return gd32can_txmb0empty(regval) || gd32can_txmb1empty(regval) || + gd32can_txmb2empty(regval); +} + +/**************************************************************************** + * Name: gd32can_txempty + * + * Description: + * Return true if all message have been sent. If for example, the CAN + * hardware implements FIFOs, then this would mean the transmit FIFO is + * empty. This method is called when the driver needs to make sure that + * all characters are "drained" from the TX hardware before calling + * co_shutdown(). + * + * Input Parameters: + * dev - An instance of the "upper half" can driver state structure. + * + * Returned Value: + * True if there are no pending TX transfers in the CAN hardware. + * + ****************************************************************************/ + +static bool gd32can_txempty(struct can_dev_s *dev) +{ + struct gd32_can_s *priv = dev->cd_priv; + uint32_t regval; + + /* Return true if all mailboxes are available */ + + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + caninfo("CAN%" PRIu8 " TSR: %08" PRIx32 "\n", priv->port, regval); + + return gd32can_txmb0empty(regval) && gd32can_txmb1empty(regval) && + gd32can_txmb2empty(regval); +} + +/**************************************************************************** + * Name: gd32can_rxinterrupt + * + * Description: + * CAN RX FIFO 0/1 interrupt handler + * + * Input Parameters: + * irq - The IRQ number of the interrupt. + * context - The register state save array at the time of the interrupt. + * rxmb - The RX mailbox number. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int gd32can_rxinterrupt(struct can_dev_s *dev, int rxmb) +{ + struct gd32_can_s *priv; + struct can_hdr_s hdr; + uint8_t data[CAN_MAXDATALEN]; + uint32_t regval; + int npending; + int ret; + + DEBUGASSERT(dev != NULL && dev->cd_priv != NULL); + priv = dev->cd_priv; + + /* Verify that a message is pending in the FIFO */ + + regval = gd32can_getreg(priv, GD32_CAN_RFR_OFFSET(rxmb)); + npending = (regval & CAN_RFR_FMP_MASK) >> CAN_RFR_FMP_SHIFT; + if (npending < 1) + { + canwarn("WARNING: No messages pending\n"); + return OK; + } + + if (rxmb == 0) + { + gd32can_dumpmbregs(priv, "RX0 interrupt"); + } + else + { + gd32can_dumpmbregs(priv, "RX1 interrupt"); + } + + /* Get the CAN identifier. */ + + regval = gd32can_getreg(priv, GD32_CAN_RIR_OFFSET(rxmb)); + +#ifdef CONFIG_CAN_EXTID + if ((regval & CAN_RIR_IDE) != 0) + { + hdr.ch_id = (regval & CAN_RIR_EXID_MASK) >> CAN_RIR_EXID_SHIFT; + hdr.ch_extid = true; + } + else + { + hdr.ch_id = (regval & CAN_RIR_STID_MASK) >> CAN_RIR_STID_SHIFT; + hdr.ch_extid = false; + } +#else + if ((regval & CAN_RIR_IDE) != 0) + { + canerr("ERROR: Received message with extended identifier. Dropped\n"); + ret = -ENOSYS; + goto errout; + } + + hdr.ch_id = (regval & CAN_RIR_STID_MASK) >> CAN_RIR_STID_SHIFT; +#endif + + /* Clear the error indication and unused bits */ + +#ifdef CONFIG_CAN_ERRORS + hdr.ch_error = 0; /* Error reporting not supported */ +#endif + hdr.ch_tcf = 0; + + /* Extract the RTR bit */ + + hdr.ch_rtr = (regval & CAN_RIR_RTR) != 0; + + /* Get the DLC */ + + regval = gd32can_getreg(priv, GD32_CAN_RDTR_OFFSET(rxmb)); + hdr.ch_dlc = (regval & CAN_RDTR_DLC_MASK) >> CAN_RDTR_DLC_SHIFT; + + /* Save the message data */ + + regval = gd32can_getreg(priv, GD32_CAN_RDLR_OFFSET(rxmb)); + data[0] = (regval & CAN_RDLR_DATA0_MASK) >> CAN_RDLR_DATA0_SHIFT; + data[1] = (regval & CAN_RDLR_DATA1_MASK) >> CAN_RDLR_DATA1_SHIFT; + data[2] = (regval & CAN_RDLR_DATA2_MASK) >> CAN_RDLR_DATA2_SHIFT; + data[3] = (regval & CAN_RDLR_DATA3_MASK) >> CAN_RDLR_DATA3_SHIFT; + + regval = gd32can_getreg(priv, GD32_CAN_RDHR_OFFSET(rxmb)); + data[4] = (regval & CAN_RDHR_DATA4_MASK) >> CAN_RDHR_DATA4_SHIFT; + data[5] = (regval & CAN_RDHR_DATA5_MASK) >> CAN_RDHR_DATA5_SHIFT; + data[6] = (regval & CAN_RDHR_DATA6_MASK) >> CAN_RDHR_DATA6_SHIFT; + data[7] = (regval & CAN_RDHR_DATA7_MASK) >> CAN_RDHR_DATA7_SHIFT; + + /* Provide the data to the upper half driver */ + + ret = can_receive(dev, &hdr, data); + + /* Release the FIFO */ + +#ifndef CONFIG_CAN_EXTID +errout: +#endif + regval = gd32can_getreg(priv, GD32_CAN_RFR_OFFSET(rxmb)); + regval |= CAN_RFR_RFOM; + gd32can_putreg(priv, GD32_CAN_RFR_OFFSET(rxmb), regval); + return ret; +} + +/**************************************************************************** + * Name: gd32can_rx0interrupt + * + * Description: + * CAN RX FIFO 0 interrupt handler + * + * Input Parameters: + * irq - The IRQ number of the interrupt. + * context - The register state save array at the time of the interrupt. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int gd32can_rx0interrupt(int irq, void *context, void *arg) +{ + struct can_dev_s *dev = (struct can_dev_s *)arg; + return gd32can_rxinterrupt(dev, 0); +} + +/**************************************************************************** + * Name: gd32can_rx1interrupt + * + * Description: + * CAN RX FIFO 1 interrupt handler + * + * Input Parameters: + * irq - The IRQ number of the interrupt. + * context - The register state save array at the time of the interrupt. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int gd32can_rx1interrupt(int irq, void *context, void *arg) +{ + struct can_dev_s *dev = (struct can_dev_s *)arg; + return gd32can_rxinterrupt(dev, 1); +} + +/**************************************************************************** + * Name: gd32can_txinterrupt + * + * Description: + * CAN TX mailbox complete interrupt handler + * + * Input Parameters: + * irq - The IRQ number of the interrupt. + * context - The register state save array at the time of the interrupt. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int gd32can_txinterrupt(int irq, void *context, void *arg) +{ + struct can_dev_s *dev = (struct can_dev_s *)arg; + struct gd32_can_s *priv; + uint32_t regval; + + DEBUGASSERT(dev != NULL && dev->cd_priv != NULL); + priv = dev->cd_priv; + + /* Get the transmit status */ + + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + + /* Check for RQCP0: Request completed mailbox 0 */ + + if ((regval & CAN_TSR_RQCP0) != 0) + { + /* Writing '1' to RCP0 clears RCP0 and all the status bits (TXOK0, + * ALST0 and TERR0) for Mailbox 0. + */ + + gd32can_putreg(priv, GD32_CAN_TSR_OFFSET, CAN_TSR_RQCP0); + + /* Tell the upper half that the transfer is finished. */ + + can_txdone(dev); + } + + /* Check for RQCP1: Request completed mailbox 1 */ + + if ((regval & CAN_TSR_RQCP1) != 0) + { + /* Writing '1' to RCP1 clears RCP1 and all the status bits (TXOK1, + * ALST1 and TERR1) for Mailbox 1. + */ + + gd32can_putreg(priv, GD32_CAN_TSR_OFFSET, CAN_TSR_RQCP1); + + /* Tell the upper half that the transfer is finished. */ + + can_txdone(dev); + } + + /* Check for RQCP2: Request completed mailbox 2 */ + + if ((regval & CAN_TSR_RQCP2) != 0) + { + /* Writing '1' to RCP2 clears RCP2 and all the status bits (TXOK2, + * ALST2 and TERR2) for Mailbox 2. + */ + + gd32can_putreg(priv, GD32_CAN_TSR_OFFSET, CAN_TSR_RQCP2); + + /* Tell the upper half that the transfer is finished. */ + + can_txdone(dev); + } + + return OK; +} + +#ifdef CONFIG_CAN_ERRORS +/**************************************************************************** + * Name: gd32can_sceinterrupt + * + * Description: + * CAN status change interrupt handler + * + * Input Parameters: + * irq - The IRQ number of the interrupt. + * context - The register state save array at the time of the interrupt. + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int gd32can_sceinterrupt(int irq, void *context, void *arg) +{ + struct can_dev_s *dev = (struct can_dev_s *)arg; + struct gd32_can_s *priv = NULL; + struct can_hdr_s hdr; + uint32_t regval = 0; + uint16_t errbits = 0; + uint8_t data[CAN_ERROR_DLC]; + int ret = OK; + + DEBUGASSERT(dev != NULL && dev->cd_priv != NULL); + priv = dev->cd_priv; + + /* Check Error Interrupt flag */ + + regval = gd32can_getreg(priv, GD32_CAN_MSR_OFFSET); + if (regval & CAN_MSR_ERRI) + { + /* Encode error bits */ + + errbits = 0; + memset(data, 0, sizeof(data)); + + /* Get Error statur register */ + + regval = gd32can_getreg(priv, GD32_CAN_ESR_OFFSET); + + if (regval & CAN_ESR_EWGF) + { + /* Error warning flag */ + + data[1] |= (CAN_ERROR1_RXWARNING | CAN_ERROR1_TXWARNING); + errbits |= CAN_ERROR_CONTROLLER; + } + + if (regval & CAN_ESR_EPVF) + { + /* Error passive flag */ + + data[1] |= (CAN_ERROR1_RXPASSIVE | CAN_ERROR1_TXPASSIVE); + errbits |= CAN_ERROR_CONTROLLER; + } + + if (regval & CAN_ESR_BOFF) + { + /* Bus-off flag */ + + errbits |= CAN_ERROR_BUSOFF; + } + + /* Last error code */ + + if (regval & CAN_ESR_LEC_MASK) + { + if (regval & CAN_ESR_STUFFERROR) + { + /* Stuff Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_STUFF; + } + else if (regval & CAN_ESR_FORMERROR) + { + /* Format Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_FORM; + } + else if (regval & CAN_ESR_ACKERROR) + { + /* Acknowledge Error */ + + errbits |= CAN_ERROR_NOACK; + } + else if (regval & CAN_ESR_BRECERROR) + { + /* Bit recessive Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_BIT1; + } + else if (regval & CAN_ESR_BDOMERROR) + { + /* Bit dominant Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[2] |= CAN_ERROR2_BIT0; + } + else if (regval & CAN_ESR_CRCERRPR) + { + /* Receive CRC Error */ + + errbits |= CAN_ERROR_PROTOCOL; + data[3] |= CAN_ERROR3_CRCSEQ; + } + } + + /* Get transmit status register */ + + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + + if (regval & CAN_TSR_ALST0 || regval & CAN_TSR_ALST1 || + regval & CAN_TSR_ALST2) + { + /* Lost arbitration Error */ + + errbits |= CAN_ERROR_LOSTARB; + } + + /* Clear TSR register */ + + gd32can_putreg(priv, GD32_CAN_TSR_OFFSET, regval); + + /* Clear ERRI flag */ + + gd32can_putreg(priv, GD32_CAN_MSR_OFFSET, CAN_MSR_ERRI); + } + + /* TODO: RX overflow and TX overflow */ + + /* Report a CAN error */ + + if (errbits != 0) + { + canerr("ERROR: errbits = %08" PRIx16 "\n", errbits); + + /* Format the CAN header for the error report. */ + + hdr.ch_id = errbits; + hdr.ch_dlc = CAN_ERROR_DLC; + hdr.ch_rtr = 0; + hdr.ch_error = 1; +#ifdef CONFIG_CAN_EXTID + hdr.ch_extid = 0; +#endif + hdr.ch_tcf = 0; + + /* And provide the error report to the upper half logic */ + + ret = can_receive(dev, &hdr, data); + if (ret < 0) + { + canerr("ERROR: can_receive failed: %d\n", ret); + } + } + + return ret; +} +#endif + +/**************************************************************************** + * Name: gd32can_bittiming + * + * Description: + * Set the CAN bit timing register (BTR) based on the configured BAUD. + * + * "The bit timing logic monitors the serial bus-line and performs sampling + * and adjustment of the sample point by synchronizing on the start-bit edge + * and resynchronizing on the following edges. + * + * "Its operation may be explained simply by splitting nominal bit time into + * three segments as follows: + * + * 1. "Synchronization segment (SYNC_SEG): a bit change is expected to occur + * within this time segment. It has a fixed length of one time quantum + * (1 x tCAN). + * 2. "Bit segment 1 (BS1): defines the location of the sample point. It + * includes the PROP_SEG and PHASE_SEG1 of the CAN standard. Its duration + * is programmable between 1 and 16 time quanta but may be automatically + * lengthened to compensate for positive phase drifts due to differences + * in the frequency of the various nodes of the network. + * 3. "Bit segment 2 (BS2): defines the location of the transmit point. It + * represents the PHASE_SEG2 of the CAN standard. Its duration is + * programmable between 1 and 8 time quanta but may also be automatically + * shortened to compensate for negative phase drifts." + * + * Pictorially: + * + * |<----------------- NOMINAL BIT TIME ----------------->| + * |<- SYNC_SEG ->|<------ BS1 ------>|<------ BS2 ------>| + * |<---- Tq ---->|<----- Tbs1 ------>|<----- Tbs2 ------>| + * + * Where + * Tbs1 is the duration of the BS1 segment + * Tbs2 is the duration of the BS2 segment + * Tq is the "Time Quantum" + * + * Relationships: + * + * baud = 1 / bit_time + * bit_time = Tq + Tbs1 + Tbs2 + * Tbs1 = Tq * ts1 + * Tbs2 = Tq * ts2 + * Tq = brp * Tpclk1 + * baud = Fpclk1 / (brp * (1 + ts1 + ts2)) + * + * Where: + * Tpclk1 is the period of the APB1 clock (PCLK1). + * + * Input Parameters: + * priv - A reference to the CAN block status + * + * Returned Value: + * Zero on success; a negated errno on failure + * + ****************************************************************************/ + +static int gd32can_bittiming(struct gd32_can_s *priv) +{ + uint32_t tmp; + uint32_t brp; + uint32_t ts1; + uint32_t ts2; + + caninfo("CAN%" PRIu8 " PCLK1: %lu baud: %" PRIu32 "\n", + priv->port, (unsigned long) GD32_PCLK1_FREQUENCY, priv->baud); + + /* Try to get CAN_BIT_QUANTA quanta in one bit_time. + * + * bit_time = Tq*(ts1 + ts2 + 1) + * nquanta = bit_time / Tq + * nquanta = (ts1 + ts2 + 1) + * + * bit_time = brp * Tpclk1 * (ts1 + ts2 + 1) + * nquanta = bit_time / brp / Tpclk1 + * = PCLK1 / baud / brp + * brp = PCLK1 / baud / nquanta; + * + * Example: + * PCLK1 = 42,000,000 baud = 1,000,000 nquanta = 14 : brp = 3 + * PCLK1 = 42,000,000 baud = 700,000 nquanta = 14 : brp = 4 + */ + + tmp = GD32_PCLK1_FREQUENCY / priv->baud; + if (tmp < CAN_BIT_QUANTA) + { + /* At the smallest brp value (1), there are already too few bit times + * (PCLCK1 / baud) to meet our goal. brp must be one and we need + * make some reasonable guesses about ts1 and ts2. + */ + + brp = 1; + + /* In this case, we have to guess a good value for ts1 and ts2 */ + + ts1 = (tmp - 1) >> 1; + ts2 = tmp - ts1 - 1; + if (ts1 == ts2 && ts1 > 1 && ts2 < CAN_BTR_TSEG2_MAX) + { + ts1--; + ts2++; + } + } + + /* Otherwise, nquanta is CAN_BIT_QUANTA, ts1 is CONFIG_GD32F4_CAN_TSEG1, + * ts2 is CONFIG_GD32F4_CAN_TSEG2 and we calculate brp to achieve + * CAN_BIT_QUANTA quanta in the bit time + */ + + else + { + ts1 = CONFIG_GD32F4_CAN_TSEG1; + ts2 = CONFIG_GD32F4_CAN_TSEG2; + brp = (tmp + (CAN_BIT_QUANTA / 2)) / CAN_BIT_QUANTA; + DEBUGASSERT(brp >= 1 && brp <= CAN_BTR_BRP_MAX); + } + + caninfo("TS1: %" PRIu32 " TS2: %" PRIu32 " BRP: %" PRIu32 "\n", + ts1, ts2, brp); + + /* Configure bit timing. This also does the following, less obvious + * things. Unless loopback mode is enabled, it: + * + * - Disables silent mode. + * - Disables loopback mode. + * + * NOTE that for the time being, SJW is set to 1 just because I don't + * know any better. + */ + + tmp = ((brp - 1) << CAN_BTR_BRP_SHIFT) | ((ts1 - 1) << CAN_BTR_TS1_SHIFT) | + ((ts2 - 1) << CAN_BTR_TS2_SHIFT) | ((1 - 1) << CAN_BTR_SJW_SHIFT); +#ifdef CONFIG_CAN_LOOPBACK + /* tmp |= (CAN_BTR_LBKM | CAN_BTR_SILM); */ + + tmp |= CAN_BTR_LBKM; +#endif + + gd32can_putreg(priv, GD32_CAN_BTR_OFFSET, tmp); + return OK; +} + +/**************************************************************************** + * Name: gd32can_enterinitmode + * + * Description: + * Put the CAN cell in Initialization mode. This only disconnects the CAN + * peripheral, no registers are changed. The initialization mode is + * required to change the baud rate. + * + * Input Parameters: + * priv - A pointer to the private data structure for this CAN block + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int gd32can_enterinitmode(struct gd32_can_s *priv) +{ + uint32_t regval; + volatile uint32_t timeout; + + caninfo("CAN%" PRIu8 "\n", priv->port); + + /* Enter initialization mode */ + + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + regval |= CAN_MCR_INRQ; + gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); + + /* Wait until initialization mode is acknowledged */ + + for (timeout = INAK_TIMEOUT; timeout > 0; timeout--) + { + regval = gd32can_getreg(priv, GD32_CAN_MSR_OFFSET); + if ((regval & CAN_MSR_INAK) != 0) + { + /* We are in initialization mode */ + + break; + } + } + + /* Check for a timeout */ + + if (timeout < 1) + { + canerr("ERROR: Timed out waiting to enter initialization mode\n"); + return -ETIMEDOUT; + } + + return OK; +} + +/**************************************************************************** + * Name: gd32can_exitinitmode + * + * Description: + * Put the CAN cell out of the Initialization mode (to Normal mode) + * + * Input Parameters: + * priv - A pointer to the private data structure for this CAN block + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int gd32can_exitinitmode(struct gd32_can_s *priv) +{ + uint32_t regval; + volatile uint32_t timeout; + + /* Exit Initialization mode, enter Normal mode */ + + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + regval &= ~CAN_MCR_INRQ; + gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); + + /* Wait until the initialization mode exit is acknowledged */ + + for (timeout = INAK_TIMEOUT; timeout > 0; timeout--) + { + regval = gd32can_getreg(priv, GD32_CAN_MSR_OFFSET); + if ((regval & CAN_MSR_INAK) == 0) + { + /* We are out of initialization mode */ + + break; + } + } + + /* Check for a timeout */ + + if (timeout < 1) + { + canerr("ERROR: Timed out waiting to exit initialization mode: %08" + PRIx32 "\n", regval); + return -ETIMEDOUT; + } + + return OK; +} + +/**************************************************************************** + * Name: gd32can_cellinit + * + * Description: + * CAN cell initialization + * + * Input Parameters: + * priv - A pointer to the private data structure for this CAN block + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int gd32can_cellinit(struct gd32_can_s *priv) +{ + uint32_t regval; + int ret; + + caninfo("CAN%" PRIu8 "\n", priv->port); + + /* Exit from sleep mode */ + + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + regval &= ~CAN_MCR_SLEEP; + gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); + + ret = gd32can_enterinitmode(priv); + if (ret != 0) + { + return ret; + } + + /* Disable the following modes: + * + * - Time triggered communication mode + * - Automatic bus-off management + * - Automatic wake-up mode + * - No automatic retransmission + * - Receive FIFO locked mode + * + * Enable: + * + * - Transmit FIFO priority + */ + + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + regval &= ~(CAN_MCR_RFLM | CAN_MCR_NART | CAN_MCR_AWUM | + CAN_MCR_ABOM | CAN_MCR_TTCM); + regval |= CAN_MCR_TXFP; + gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); + + /* Configure bit timing. */ + + ret = gd32can_bittiming(priv); + if (ret < 0) + { + canerr("ERROR: Failed to set bit timing: %d\n", ret); + return ret; + } + + return gd32can_exitinitmode(priv); +} + +/**************************************************************************** + * Name: gd32can_filterinit + * + * Description: + * CAN filter initialization. CAN filters are not currently used by this + * driver. The CAN filters can be configured in a different way: + * + * 1. As a match of specific IDs in a list (IdList mode), or as + * 2. And ID and a mask (IdMask mode). + * + * Filters can also be configured as: + * + * 3. 16- or 32-bit. The advantage of 16-bit filters is that you get + * more filters; The advantage of 32-bit filters is that you get + * finer control of the filtering. + * + * One filter is set up for each CAN. The filter resources are shared + * between the two CAN modules: CAN0 uses only filter 0 (but reserves + * 0 through CAN_NFILTERS/2-1); CAN1 uses only filter CAN_NFILTERS/2 + * (but reserves CAN_NFILTERS/2 through CAN_NFILTERS-1). + * + * 32-bit IdMask mode is configured. However, both the ID and the MASK + * are set to zero thus suppressing all filtering because anything masked + * with zero matches zero. + * + * Input Parameters: + * priv - A pointer to the private data structure for this CAN block + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int gd32can_filterinit(struct gd32_can_s *priv) +{ + uint32_t regval; + uint32_t bitmask; + + caninfo("CAN%" PRIu8 " filter: %" PRIu8 "\n", priv->port, priv->filter); + + /* Get the bitmask associated with the filter used by this CAN block */ + + bitmask = (uint32_t)1 << priv->filter; + + /* Enter filter initialization mode */ + + regval = gd32can_getfreg(priv, GD32_CAN_FMR_OFFSET); + regval |= CAN_FMR_FINIT; + gd32can_putfreg(priv, GD32_CAN_FMR_OFFSET, regval); + + /* Assign half the filters to CAN0, half to CAN1 (28 banks on GD32F4). */ + + regval = gd32can_getfreg(priv, GD32_CAN_FMR_OFFSET); + regval &= ~CAN_FMR_CAN1SB_MASK; + regval |= (CAN_NFILTERS / 2) << CAN_FMR_CAN1SB_SHIFT; + gd32can_putfreg(priv, GD32_CAN_FMR_OFFSET, regval); + + /* Disable the filter */ + + regval = gd32can_getfreg(priv, GD32_CAN_FA1R_OFFSET); + regval &= ~bitmask; + gd32can_putfreg(priv, GD32_CAN_FA1R_OFFSET, regval); + + /* Select the 32-bit scale for the filter */ + + regval = gd32can_getfreg(priv, GD32_CAN_FS1R_OFFSET); + regval |= bitmask; + gd32can_putfreg(priv, GD32_CAN_FS1R_OFFSET, regval); + + /* There are 14 or 28 filter banks (depending) on the device. + * Each filter bank is composed of two 32-bit registers, CAN_FiR: + */ + + gd32can_putfreg(priv, GD32_CAN_FIR_OFFSET(priv->filter, 1), 0); + gd32can_putfreg(priv, GD32_CAN_FIR_OFFSET(priv->filter, 2), 0); + + /* Set Id/Mask mode for the filter */ + + regval = gd32can_getfreg(priv, GD32_CAN_FM1R_OFFSET); + regval &= ~bitmask; + gd32can_putfreg(priv, GD32_CAN_FM1R_OFFSET, regval); + + /* Assign FIFO 0 for the filter */ + + regval = gd32can_getfreg(priv, GD32_CAN_FFA1R_OFFSET); + regval &= ~bitmask; + gd32can_putfreg(priv, GD32_CAN_FFA1R_OFFSET, regval); + + /* Enable the filter */ + + regval = gd32can_getfreg(priv, GD32_CAN_FA1R_OFFSET); + regval |= bitmask; + gd32can_putfreg(priv, GD32_CAN_FA1R_OFFSET, regval); + + /* Exit filter initialization mode */ + + regval = gd32can_getfreg(priv, GD32_CAN_FMR_OFFSET); + regval &= ~CAN_FMR_FINIT; + gd32can_putfreg(priv, GD32_CAN_FMR_OFFSET, regval); + return OK; +} + +/**************************************************************************** + * Name: gd32can_addextfilter + * + * Description: + * Add a filter for extended CAN IDs + * + * Input Parameters: + * priv - A pointer to the private data structure for this CAN block + * arg - A pointer to a structure describing the filter + * + * Returned Value: + * A non-negative filter ID is returned on success. + * Otherwise -1 (ERROR) is returned with the errno + * set to indicate the nature of the error. + * + ****************************************************************************/ + +#ifdef CONFIG_CAN_EXTID +static int gd32can_addextfilter(struct gd32_can_s *priv, + struct canioc_extfilter_s *arg) +{ + return -ENOTTY; +} +#endif + +/**************************************************************************** + * Name: gd32can_delextfilter + * + * Description: + * Remove a filter for extended CAN IDs + * + * Input Parameters: + * priv - A pointer to the private data structure for this CAN block + * arg - The filter index previously returned by the + * CANIOC_ADD_EXTFILTER command + * + * Returned Value: + * Zero (OK) is returned on success. Otherwise -1 (ERROR) + * returned with the errno variable set to indicate the + * of the error. + * + ****************************************************************************/ + +#ifdef CONFIG_CAN_EXTID +static int gd32can_delextfilter(struct gd32_can_s *priv, int arg) +{ + return -ENOTTY; +} +#endif + +/**************************************************************************** + * Name: gd32can_addstdfilter + * + * Description: + * Add a filter for standard CAN IDs + * + * Input Parameters: + * priv - A pointer to the private data structure for this CAN block + * arg - A pointer to a structure describing the filter + * + * Returned Value: + * A non-negative filter ID is returned on success. + * Otherwise -1 (ERROR) is returned with the errno + * set to indicate the nature of the error. + * + ****************************************************************************/ + +static int gd32can_addstdfilter(struct gd32_can_s *priv, + struct canioc_stdfilter_s *arg) +{ + return -ENOTTY; +} + +/**************************************************************************** + * Name: gd32can_delstdfilter + * + * Description: + * Remove a filter for standard CAN IDs + * + * Input Parameters: + * priv - A pointer to the private data structure for this CAN block + * arg - The filter index previously returned by the + * CANIOC_ADD_STDFILTER command + * + * Returned Value: + * Zero (OK) is returned on success. Otherwise -1 (ERROR) + * returned with the errno variable set to indicate the + * of the error. + * + ****************************************************************************/ + +static int gd32can_delstdfilter(struct gd32_can_s *priv, int arg) +{ + return -ENOTTY; +} + +/**************************************************************************** + * Name: gd32can_txmb0empty + * + * Input Parameters: + * tsr_regval - value of CAN transmit status register + * + * Returned Value: + * Returns true if mailbox 0 is empty and can be used for sending. + * + ****************************************************************************/ + +static bool gd32can_txmb0empty(uint32_t tsr_regval) +{ + return (tsr_regval & CAN_TSR_TME0) != 0 && + (tsr_regval & CAN_TSR_RQCP0) == 0; +} + +/**************************************************************************** + * Name: gd32can_txmb1empty + * + * Input Parameters: + * tsr_regval - value of CAN transmit status register + * + * Returned Value: + * Returns true if mailbox 1 is empty and can be used for sending. + * + ****************************************************************************/ + +static bool gd32can_txmb1empty(uint32_t tsr_regval) +{ + return (tsr_regval & CAN_TSR_TME1) != 0 && + (tsr_regval & CAN_TSR_RQCP1) == 0; +} + +/**************************************************************************** + * Name: gd32can_txmb2empty + * + * Input Parameters: + * tsr_regval - value of CAN transmit status register + * + * Returned Value: + * Returns true if mailbox 2 is empty and can be used for sending. + * + ****************************************************************************/ + +static bool gd32can_txmb2empty(uint32_t tsr_regval) +{ + return (tsr_regval & CAN_TSR_TME2) != 0 && + (tsr_regval & CAN_TSR_RQCP2) == 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: gd32_caninitialize + * + * Description: + * Initialize the selected CAN port + * + * Input Parameters: + * Port number (for hardware that has multiple CAN interfaces) + * + * Returned Value: + * Valid CAN device structure reference on success; a NULL on failure + * + ****************************************************************************/ + +struct can_dev_s *gd32_caninitialize(int port) +{ + struct can_dev_s *dev = NULL; + + caninfo("CAN%" PRIu8 "\n", port); + + /* NOTE: Peripheral clocking for CAN is enabled here. gd32_clockconfig() + * does not currently enable CAN clocks in RCU. + */ + +#ifdef CONFIG_GD32F4_CAN0 + if (port == 0) + { + /* Select the CAN0 device structure */ + + dev = &g_can0dev; + + /* Enable CAN0 clock (filters live in CAN0 block). */ + + modifyreg32(GD32_RCU_APB1EN, 0, RCU_APB1EN_CAN0EN); + + /* Configure CAN0 pins. Ambiguous pinmap entries must be + * disambiguated in the board.h file. + */ + + gd32_gpio_config(GPIO_CAN0_RX); + gd32_gpio_config(GPIO_CAN0_TX); + } + else +#endif +#ifdef CONFIG_GD32F4_CAN1 + if (port == 1) + { + /* Select the CAN1 device structure */ + + dev = &g_can1dev; + + /* CAN1 shares filter registers with CAN0; enable both clocks. */ + + modifyreg32(GD32_RCU_APB1EN, 0, RCU_APB1EN_CAN0EN); + modifyreg32(GD32_RCU_APB1EN, 0, RCU_APB1EN_CAN1EN); + + /* Configure CAN1 pins. Ambiguous pinmap entries must be + * disambiguated in the board.h file. + */ + + gd32_gpio_config(GPIO_CAN1_RX); + gd32_gpio_config(GPIO_CAN1_TX); + } + else +#endif + { + canerr("ERROR: Unsupported port %d\n", port); + return NULL; + } + + return dev; +} + +#endif /* CONFIG_CAN && (CONFIG_GD32F4_CAN0 || CONFIG_GD32F4_CAN1) */ diff --git a/arch/arm/src/gd32f4/gd32f4xx_can.h b/arch/arm/src/gd32f4/gd32f4xx_can.h new file mode 100644 index 0000000000000..8471f55e6b1f8 --- /dev/null +++ b/arch/arm/src/gd32f4/gd32f4xx_can.h @@ -0,0 +1,136 @@ +/**************************************************************************** + * arch/arm/src/gd32f4/gd32f4xx_can.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_GD32F4_GD32F4XX_CAN_H +#define __ARCH_ARM_SRC_GD32F4_GD32F4XX_CAN_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "chip.h" +#include "hardware/gd32f4xx_can.h" + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* Up to 2 CAN interfaces are supported */ + +#if GD32_NCAN < 2 +# undef CONFIG_GD32F4_CAN1 +#endif + +#if GD32_NCAN < 1 +# undef CONFIG_GD32F4_CAN0 +#endif + +/* CAN BAUD */ + +#if defined(CONFIG_GD32F4_CAN0) && !defined(CONFIG_GD32F4_CAN0_BAUD) +# error "CONFIG_GD32F4_CAN0_BAUD is not defined" +#endif + +#if defined(CONFIG_GD32F4_CAN1) && !defined(CONFIG_GD32F4_CAN1_BAUD) +# error "CONFIG_GD32F4_CAN1_BAUD is not defined" +#endif + +/* User-defined TSEG1 and TSEG2 settings may be used. + * + * CONFIG_GD32F4_CAN_TSEG1 = the number of CAN time quanta in segment 1 + * CONFIG_GD32F4_CAN_TSEG2 = the number of CAN time quanta in segment 2 + * CAN_BIT_QUANTA = The number of CAN time quanta in one bit time + */ + +#ifndef CONFIG_GD32F4_CAN_TSEG1 +# define CONFIG_GD32F4_CAN_TSEG1 6 +#endif + +#if CONFIG_GD32F4_CAN_TSEG1 < 1 || CONFIG_GD32F4_CAN_TSEG1 > CAN_BTR_TSEG1_MAX +# error "CONFIG_GD32F4_CAN_TSEG1 is out of range" +#endif + +#ifndef CONFIG_GD32F4_CAN_TSEG2 +# define CONFIG_GD32F4_CAN_TSEG2 7 +#endif + +#if CONFIG_GD32F4_CAN_TSEG2 < 1 || CONFIG_GD32F4_CAN_TSEG2 > CAN_BTR_TSEG2_MAX +# error "CONFIG_GD32F4_CAN_TSEG2 is out of range" +#endif + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#if defined(CONFIG_GD32F4_CAN0) || defined(CONFIG_GD32F4_CAN1) + +/**************************************************************************** + * Name: gd32_caninitialize + * + * Description: + * Initialize the selected CAN port as character device + * + * Input Parameters: + * Port number (0 = CAN0, 1 = CAN1) + * + * Returned Value: + * Valid CAN device structure reference on success; a NULL on failure + * + ****************************************************************************/ + +struct can_dev_s; +struct can_dev_s *gd32_caninitialize(int port); + +#endif /* CONFIG_GD32F4_CAN0 || CONFIG_GD32F4_CAN1 */ + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* __ARCH_ARM_SRC_GD32F4_GD32F4XX_CAN_H */ diff --git a/arch/arm/src/gd32f4/gd32f4xx_serial.c b/arch/arm/src/gd32f4/gd32f4xx_serial.c index 5ced798a320d8..52e96497b27b2 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_serial.c +++ b/arch/arm/src/gd32f4/gd32f4xx_serial.c @@ -158,7 +158,6 @@ struct up_dev_s const uint8_t stop_2bits; /* True: Configure with 2 stop bits instead of 1 */ const uint32_t tx_gpio; /* USART TX GPIO pin configuration */ const uint32_t rx_gpio; /* USART RX GPIO pin configuration */ - spinlock_t lock; /* Spinlock */ # ifdef CONFIG_SERIAL_IFLOWCONTROL const uint32_t rts_gpio; /* UART RTS GPIO pin configuration */ @@ -169,6 +168,7 @@ struct up_dev_s #endif /* CONFIG_SERIAL_TERMIOS */ + spinlock_t lock; /* Spinlock */ uint32_t ie; /* USART enabled interrupts */ uint16_t sr; /* Save USART status */ @@ -467,7 +467,7 @@ static struct up_dev_s g_usart0priv = static struct uart_dev_s g_usart0port = { -#if CONSOLE_UART == 0 +#if defined(CONSOLE_UART) && CONSOLE_UART == 0 .isconsole = true, #endif .recv = @@ -540,7 +540,7 @@ static struct up_dev_s g_usart1priv = static struct uart_dev_s g_usart1port = { -#if CONSOLE_UART == 1 +#if defined(CONSOLE_UART) && CONSOLE_UART == 1 .isconsole = true, #endif .recv = @@ -613,7 +613,7 @@ static struct up_dev_s g_usart2priv = static struct uart_dev_s g_usart2port = { -#if CONSOLE_UART == 2 +#if defined(CONSOLE_UART) && CONSOLE_UART == 2 .isconsole = true, #endif .recv = @@ -686,7 +686,7 @@ static struct up_dev_s g_usart5priv = static struct uart_dev_s g_usart5port = { -#if CONSOLE_UART == 5 +#if defined(CONSOLE_UART) && CONSOLE_UART == 5 .isconsole = true, #endif .recv = @@ -759,7 +759,7 @@ static struct up_dev_s g_uart3priv = static struct uart_dev_s g_uart3port = { -#if CONSOLE_UART == 3 +#if defined(CONSOLE_UART) && CONSOLE_UART == 3 .isconsole = true, #endif .recv = @@ -832,7 +832,7 @@ static struct up_dev_s g_uart4priv = static struct uart_dev_s g_uart4port = { -#if CONSOLE_UART == 3 +#if defined(CONSOLE_UART) && CONSOLE_UART == 4 .isconsole = true, #endif .recv = @@ -905,7 +905,7 @@ static struct up_dev_s g_uart6priv = static struct uart_dev_s g_uart6port = { -#if CONSOLE_UART == 3 +#if defined(CONSOLE_UART) && CONSOLE_UART == 6 .isconsole = true, #endif .recv = @@ -978,7 +978,7 @@ static struct up_dev_s g_uart7priv = static struct uart_dev_s g_uart7port = { -#if CONSOLE_UART == 3 +#if defined(CONSOLE_UART) && CONSOLE_UART == 7 .isconsole = true, #endif .recv = @@ -2793,7 +2793,9 @@ void arm_earlyserialinit(void) /* Configure whichever one is the console */ +#ifdef CONSOLE_UART up_setup(g_uart_devs[CONSOLE_UART]); +#endif #endif /* HAVE UART */ } #endif diff --git a/arch/arm/src/gd32f4/hardware/gd32f450_memorymap.h b/arch/arm/src/gd32f4/hardware/gd32f450_memorymap.h index 5a263a29c913e..139d307fc3f5a 100644 --- a/arch/arm/src/gd32f4/hardware/gd32f450_memorymap.h +++ b/arch/arm/src/gd32f4/hardware/gd32f450_memorymap.h @@ -101,7 +101,9 @@ #define GD32_SPI_BASE (GD32_APB1_BUS_BASE + 0x00003800U) /* SPI base address */ #define GD32_USART_BASE (GD32_APB1_BUS_BASE + 0x00004400U) /* USART base address */ #define GD32_I2C_BASE (GD32_APB1_BUS_BASE + 0x00005400U) /* I2C base address */ -#define GD32_CAN_BASE (GD32_APB1_BUS_BASE + 0x00006400U) /* CAN base address */ +#define GD32_CAN0_BASE (GD32_APB1_BUS_BASE + 0x00006400U) /* CAN0 base address */ +#define GD32_CAN1_BASE (GD32_APB1_BUS_BASE + 0x00006800U) /* CAN1 base address */ +#define GD32_CAN_BASE GD32_CAN0_BASE /* Alias: CAN0 */ #define GD32_CTC_BASE (GD32_APB1_BUS_BASE + 0x00006C00U) /* CTC base address */ #define GD32_PMU_BASE (GD32_APB1_BUS_BASE + 0x00007000U) /* PMU base address */ #define GD32_DAC_BASE (GD32_APB1_BUS_BASE + 0x00007400U) /* DAC base address */ diff --git a/arch/arm/src/gd32f4/hardware/gd32f4xx_can.h b/arch/arm/src/gd32f4/hardware/gd32f4xx_can.h new file mode 100644 index 0000000000000..5a22e30b8a958 --- /dev/null +++ b/arch/arm/src/gd32f4/hardware/gd32f4xx_can.h @@ -0,0 +1,470 @@ +/**************************************************************************** + * arch/arm/src/gd32f4/hardware/gd32f4xx_can.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_GD32F4_HARDWARE_GD32F4XX_CAN_H +#define __ARCH_ARM_SRC_GD32F4_HARDWARE_GD32F4XX_CAN_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "chip.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* 3 TX mailboxes */ + +#define CAN_TXMBOX1 0 +#define CAN_TXMBOX2 1 +#define CAN_TXMBOX3 2 + +/* 2 RX mailboxes */ + +#define CAN_RXMBOX1 0 +#define CAN_RXMBOX2 1 + +/* GD32F4 bxCAN has 28 filter banks (shared by CAN0/CAN1) */ + +#define CAN_NFILTERS 28 + +/* Register Offsets *********************************************************/ + +/* CAN control and status registers */ + +#define GD32_CAN_MCR_OFFSET 0x0000 /* CAN master control register */ +#define GD32_CAN_MSR_OFFSET 0x0004 /* CAN master status register */ +#define GD32_CAN_TSR_OFFSET 0x0008 /* CAN transmit status register */ + +#define GD32_CAN_RFR_OFFSET(m) (0x000c+((m)<<2)) +#define GD32_CAN_RF0R_OFFSET 0x000c /* CAN receive FIFO 0 register */ +#define GD32_CAN_RF1R_OFFSET 0x0010 /* CAN receive FIFO 1 register */ + +#define GD32_CAN_IER_OFFSET 0x0014 /* CAN interrupt enable register */ +#define GD32_CAN_ESR_OFFSET 0x0018 /* CAN error status register */ +#define GD32_CAN_BTR_OFFSET 0x001c /* CAN bit timing register */ + +/* CAN mailbox registers (3 TX and 2 RX) */ + +#define GD32_CAN_TIR_OFFSET(m) (0x0180+((m)<<4)) +#define GD32_CAN_TI0R_OFFSET 0x0180 /* TX mailbox identifier register 0 */ +#define GD32_CAN_TI1R_OFFSET 0x0190 /* TX mailbox identifier register 1 */ +#define GD32_CAN_TI2R_OFFSET 0x01a0 /* TX mailbox identifier register 2 */ + +#define GD32_CAN_TDTR_OFFSET(m) (0x0184+((m)<<4)) +#define GD32_CAN_TDT0R_OFFSET 0x0184 /* Mailbox data length control and time stamp register 0 */ +#define GD32_CAN_TDT1R_OFFSET 0x0194 /* Mailbox data length control and time stamp register 1 */ +#define GD32_CAN_TDT2R_OFFSET 0x01a4 /* Mailbox data length control and time stamp register 2 */ + +#define GD32_CAN_TDLR_OFFSET(m) (0x0188+((m)<<4)) +#define GD32_CAN_TDL0R_OFFSET 0x0188 /* Mailbox data low register 0 */ +#define GD32_CAN_TDL1R_OFFSET 0x0198 /* Mailbox data low register 1 */ +#define GD32_CAN_TDL2R_OFFSET 0x01a8 /* Mailbox data low register 2 */ + +#define GD32_CAN_TDHR_OFFSET(m) (0x018c+((m)<<4)) +#define GD32_CAN_TDH0R_OFFSET 0x018c /* Mailbox data high register 0 */ +#define GD32_CAN_TDH1R_OFFSET 0x019c /* Mailbox data high register 1 */ +#define GD32_CAN_TDH2R_OFFSET 0x01ac /* Mailbox data high register 2 */ + +#define GD32_CAN_RIR_OFFSET(m) (0x01b0+((m)<<4)) +#define GD32_CAN_RI0R_OFFSET 0x01b0 /* Rx FIFO mailbox identifier register 0 */ +#define GD32_CAN_RI1R_OFFSET 0x01c0 /* Rx FIFO mailbox identifier register 1 */ + +#define GD32_CAN_RDTR_OFFSET(m) (0x01b4+((m)<<4)) +#define GD32_CAN_RDT0R_OFFSET 0x01b4 /* Rx FIFO mailbox data length control and time stamp register 0 */ +#define GD32_CAN_RDT1R_OFFSET 0x01c4 /* Rx FIFO mailbox data length control and time stamp register 1 */ + +#define GD32_CAN_RDLR_OFFSET(m) (0x01b8+((m)<<4)) +#define GD32_CAN_RDL0R_OFFSET 0x01b8 /* Receive FIFO mailbox data low register 0 */ +#define GD32_CAN_RDL1R_OFFSET 0x01c8 /* Receive FIFO mailbox data low register 1 */ + +#define GD32_CAN_RDHR_OFFSET(m) (0x01bc+((m)<<4)) +#define GD32_CAN_RDH0R_OFFSET 0x01bc /* Receive FIFO mailbox data high register 0 */ +#define GD32_CAN_RDH1R_OFFSET 0x01cc /* Receive FIFO mailbox data high register 1 */ + +/* CAN filter registers */ + +#define GD32_CAN_FMR_OFFSET 0x0200 /* CAN filter master register */ +#define GD32_CAN_FM1R_OFFSET 0x0204 /* CAN filter mode register */ +#define GD32_CAN_FS1R_OFFSET 0x020c /* CAN filter scale register */ +#define GD32_CAN_FFA1R_OFFSET 0x0214 /* CAN filter FIFO assignment register */ +#define GD32_CAN_FA1R_OFFSET 0x021c /* CAN filter activation register */ + +/* There are 14 or 28 filter banks (depending) on the device. + * Each filter bank is composed of two 32-bit registers, CAN_FiR: + * F0R1 Offset 0x240 + * F0R2 Offset 0x244 + * F1R1 Offset 0x248 + * F1R2 Offset 0x24c + * ... + */ + +#define GD32_CAN_FIR_OFFSET(f,i) (0x240+((f)<<3)+(((i)-1)<<2)) + +/* Register Addresses *******************************************************/ + +#if GD32_NCAN > 0 +# define GD32_CAN0_MCR (GD32_CAN0_BASE+GD32_CAN_MCR_OFFSET) +# define GD32_CAN0_MSR (GD32_CAN0_BASE+GD32_CAN_MSR_OFFSET) +# define GD32_CAN0_TSR (GD32_CAN0_BASE+GD32_CAN_TSR_OFFSET) +# define GD32_CAN0_RF0R (GD32_CAN0_BASE+GD32_CAN_RF0R_OFFSET) +# define GD32_CAN0_RF1R (GD32_CAN0_BASE+GD32_CAN_RF1R_OFFSET) +# define GD32_CAN0_IER (GD32_CAN0_BASE+GD32_CAN_IER_OFFSET) +# define GD32_CAN0_ESR (GD32_CAN0_BASE+GD32_CAN_ESR_OFFSET) +# define GD32_CAN0_BTR (GD32_CAN0_BASE+GD32_CAN_BTR_OFFSET) + +# define GD32_CAN0_TIR(m) (GD32_CAN0_BASE+GD32_CAN_TIR_OFFSET(m)) +# define GD32_CAN0_TI0R (GD32_CAN0_BASE+GD32_CAN_TI0R_OFFSET) +# define GD32_CAN0_TI1R (GD32_CAN0_BASE+GD32_CAN_TI1R_OFFSET) +# define GD32_CAN0_TI2R (GD32_CAN0_BASE+GD32_CAN_TI2R_OFFSET) + +# define GD32_CAN0_TDTR(m) (GD32_CAN0_BASE+GD32_CAN_TDTR_OFFSET(m)) +# define GD32_CAN0_TDT0R (GD32_CAN0_BASE+GD32_CAN_TDT0R_OFFSET) +# define GD32_CAN0_TDT1R (GD32_CAN0_BASE+GD32_CAN_TDT1R_OFFSET) +# define GD32_CAN0_TDT2R (GD32_CAN0_BASE+GD32_CAN_TDT2R_OFFSET) + +# define GD32_CAN0_TDLR(m) (GD32_CAN0_BASE+GD32_CAN_TDLR_OFFSET(m)) +# define GD32_CAN0_TDL0R (GD32_CAN0_BASE+GD32_CAN_TDL0R_OFFSET) +# define GD32_CAN0_TDL1R (GD32_CAN0_BASE+GD32_CAN_TDL1R_OFFSET) +# define GD32_CAN0_TDL2R (GD32_CAN0_BASE+GD32_CAN_TDL2R_OFFSET) + +# define GD32_CAN0_TDHR(m) (GD32_CAN0_BASE+GD32_CAN_TDHR_OFFSET(m)) +# define GD32_CAN0_TDH0R (GD32_CAN0_BASE+GD32_CAN_TDH0R_OFFSET) +# define GD32_CAN0_TDH1R (GD32_CAN0_BASE+GD32_CAN_TDH1R_OFFSET) +# define GD32_CAN0_TDH2R (GD32_CAN0_BASE+GD32_CAN_TDH2R_OFFSET) + +# define GD32_CAN0_RIR(m) (GD32_CAN0_BASE+GD32_CAN_RIR_OFFSET(m)) +# define GD32_CAN0_RI0R (GD32_CAN0_BASE+GD32_CAN_RI0R_OFFSET) +# define GD32_CAN0_RI1R (GD32_CAN0_BASE+GD32_CAN_RI1R_OFFSET) + +# define GD32_CAN0_RDTR(m) (GD32_CAN0_BASE+GD32_CAN_RDTR_OFFSET(m)) +# define GD32_CAN0_RDT0R (GD32_CAN0_BASE+GD32_CAN_RDT0R_OFFSET) +# define GD32_CAN0_RDT1R (GD32_CAN0_BASE+GD32_CAN_RDT1R_OFFSET) + +# define GD32_CAN0_RDLR(m) (GD32_CAN0_BASE+GD32_CAN_RDLR_OFFSET(m)) +# define GD32_CAN0_RDL0R (GD32_CAN0_BASE+GD32_CAN_RDL0R_OFFSET) +# define GD32_CAN0_RDL1R (GD32_CAN0_BASE+GD32_CAN_RDL1R_OFFSET) + +# define GD32_CAN0_RDHR(m) (GD32_CAN0_BASE+GD32_CAN_RDHR_OFFSET(m)) +# define GD32_CAN0_RDH0R (GD32_CAN0_BASE+GD32_CAN_RDH0R_OFFSET) +# define GD32_CAN0_RDH1R (GD32_CAN0_BASE+GD32_CAN_RDH1R_OFFSET) + +# define GD32_CAN0_FMR (GD32_CAN0_BASE+GD32_CAN_FMR_OFFSET) +# define GD32_CAN0_FM1R (GD32_CAN0_BASE+GD32_CAN_FM1R_OFFSET) +# define GD32_CAN0_FS1R (GD32_CAN0_BASE+GD32_CAN_FS1R_OFFSET) +# define GD32_CAN0_FFA1R (GD32_CAN0_BASE+GD32_CAN_FFA1R_OFFSET) +# define GD32_CAN0_FA1R (GD32_CAN0_BASE+GD32_CAN_FA1R_OFFSET) +# define GD32_CAN0_FIR(b,i) (GD32_CAN0_BASE+GD32_CAN_FIR_OFFSET(b,i)) +#endif + +#if GD32_NCAN > 1 +# define GD32_CAN1_MCR (GD32_CAN1_BASE+GD32_CAN_MCR_OFFSET) +# define GD32_CAN1_MSR (GD32_CAN1_BASE+GD32_CAN_MSR_OFFSET) +# define GD32_CAN1_TSR (GD32_CAN1_BASE+GD32_CAN_TSR_OFFSET) +# define GD32_CAN1_RF0R (GD32_CAN1_BASE+GD32_CAN_RF0R_OFFSET) +# define GD32_CAN1_RF1R (GD32_CAN1_BASE+GD32_CAN_RF1R_OFFSET) +# define GD32_CAN1_IER (GD32_CAN1_BASE+GD32_CAN_IER_OFFSET) +# define GD32_CAN1_ESR (GD32_CAN1_BASE+GD32_CAN_ESR_OFFSET) +# define GD32_CAN1_BTR (GD32_CAN1_BASE+GD32_CAN_BTR_OFFSET) + +# define GD32_CAN1_TIR(m) (GD32_CAN1_BASE+GD32_CAN_TIR_OFFSET(m)) +# define GD32_CAN1_TI0R (GD32_CAN1_BASE+GD32_CAN_TI0R_OFFSET) +# define GD32_CAN1_TI1R (GD32_CAN1_BASE+GD32_CAN_TI1R_OFFSET) +# define GD32_CAN1_TI2R (GD32_CAN1_BASE+GD32_CAN_TI2R_OFFSET) + +# define GD32_CAN1_TDTR(m) (GD32_CAN1_BASE+GD32_CAN_TDTR_OFFSET(m)) +# define GD32_CAN1_TDT0R (GD32_CAN1_BASE+GD32_CAN_TDT0R_OFFSET) +# define GD32_CAN1_TDT1R (GD32_CAN1_BASE+GD32_CAN_TDT1R_OFFSET) +# define GD32_CAN1_TDT2R (GD32_CAN1_BASE+GD32_CAN_TDT2R_OFFSET) + +# define GD32_CAN1_TDLR(m) (GD32_CAN1_BASE+GD32_CAN_TDLR_OFFSET(m)) +# define GD32_CAN1_TDL0R (GD32_CAN1_BASE+GD32_CAN_TDL0R_OFFSET) +# define GD32_CAN1_TDL1R (GD32_CAN1_BASE+GD32_CAN_TDL1R_OFFSET) +# define GD32_CAN1_TDL2R (GD32_CAN1_BASE+GD32_CAN_TDL2R_OFFSET) + +# define GD32_CAN1_TDHR(m) (GD32_CAN1_BASE+GD32_CAN_TDHR_OFFSET(m)) +# define GD32_CAN1_TDH0R (GD32_CAN1_BASE+GD32_CAN_TDH0R_OFFSET) +# define GD32_CAN1_TDH1R (GD32_CAN1_BASE+GD32_CAN_TDH1R_OFFSET) +# define GD32_CAN1_TDH2R (GD32_CAN1_BASE+GD32_CAN_TDH2R_OFFSET) + +# define GD32_CAN1_RIR(m) (GD32_CAN1_BASE+GD32_CAN_RIR_OFFSET(m)) +# define GD32_CAN1_RI0R (GD32_CAN1_BASE+GD32_CAN_RI0R_OFFSET) +# define GD32_CAN1_RI1R (GD32_CAN1_BASE+GD32_CAN_RI1R_OFFSET) + +# define GD32_CAN1_RDTR(m) (GD32_CAN1_BASE+GD32_CAN_RDTR_OFFSET(m)) +# define GD32_CAN1_RDT0R (GD32_CAN1_BASE+GD32_CAN_RDT0R_OFFSET) +# define GD32_CAN1_RDT1R (GD32_CAN1_BASE+GD32_CAN_RDT1R_OFFSET) + +# define GD32_CAN1_RDLR(m) (GD32_CAN1_BASE+GD32_CAN_RDLR_OFFSET(m)) +# define GD32_CAN1_RDL0R (GD32_CAN1_BASE+GD32_CAN_RDL0R_OFFSET) +# define GD32_CAN1_RDL1R (GD32_CAN1_BASE+GD32_CAN_RDL1R_OFFSET) + +# define GD32_CAN1_RDHR(m) (GD32_CAN1_BASE+GD32_CAN_RDHR_OFFSET(m)) +# define GD32_CAN1_RDH0R (GD32_CAN1_BASE+GD32_CAN_RDH0R_OFFSET) +# define GD32_CAN1_RDH1R (GD32_CAN1_BASE+GD32_CAN_RDH1R_OFFSET) + +# define GD32_CAN1_FMR (GD32_CAN1_BASE+GD32_CAN_FMR_OFFSET) +# define GD32_CAN1_FM1R (GD32_CAN1_BASE+GD32_CAN_FM1R_OFFSET) +# define GD32_CAN1_FS1R (GD32_CAN1_BASE+GD32_CAN_FS1R_OFFSET) +# define GD32_CAN1_FFA1R (GD32_CAN1_BASE+GD32_CAN_FFA1R_OFFSET) +# define GD32_CAN1_FA1R (GD32_CAN1_BASE+GD32_CAN_FA1R_OFFSET) +# define GD32_CAN1_FIR(b,i) (GD32_CAN1_BASE+GD32_CAN_FIR_OFFSET(b,i)) +#endif + +/* Register Bitfield Definitions ********************************************/ + +/* CAN master control register */ + +#define CAN_MCR_INRQ (1 << 0) /* Bit 0: Initialization Request */ +#define CAN_MCR_SLEEP (1 << 1) /* Bit 1: Sleep Mode Request */ +#define CAN_MCR_TXFP (1 << 2) /* Bit 2: Transmit FIFO Priority */ +#define CAN_MCR_RFLM (1 << 3) /* Bit 3: Receive FIFO Locked Mode */ +#define CAN_MCR_NART (1 << 4) /* Bit 4: No Automatic Retransmission */ +#define CAN_MCR_AWUM (1 << 5) /* Bit 5: Automatic Wakeup Mode */ +#define CAN_MCR_ABOM (1 << 6) /* Bit 6: Automatic Bus-Off Management */ +#define CAN_MCR_TTCM (1 << 7) /* Bit 7: Time Triggered Communication Mode Enable */ +#define CAN_MCR_RESET (1 << 15) /* Bit 15: bxCAN software master reset */ +#define CAN_MCR_DBF (1 << 16) /* Bit 16: Debug freeze */ + +/* CAN master status register */ + +#define CAN_MSR_INAK (1 << 0) /* Bit 0: Initialization Acknowledge */ +#define CAN_MSR_SLAK (1 << 1) /* Bit 1: Sleep Acknowledge */ +#define CAN_MSR_ERRI (1 << 2) /* Bit 2: Error Interrupt */ +#define CAN_MSR_WKUI (1 << 3) /* Bit 3: Wakeup Interrupt */ +#define CAN_MSR_SLAKI (1 << 4) /* Bit 4: Sleep acknowledge interrupt */ +#define CAN_MSR_TXM (1 << 8) /* Bit 8: Transmit Mode */ +#define CAN_MSR_RXM (1 << 9) /* Bit 9: Receive Mode */ +#define CAN_MSR_SAMP (1 << 10) /* Bit 10: Last Sample Point */ +#define CAN_MSR_RX (1 << 11) /* Bit 11: CAN Rx Signal */ + +/* CAN transmit status register */ + +#define CAN_TSR_RQCP0 (1 << 0) /* Bit 0: Request Completed Mailbox 0 */ +#define CAN_TSR_TXOK0 (1 << 1) /* Bit 1 : Transmission OK of Mailbox 0 */ +#define CAN_TSR_ALST0 (1 << 2) /* Bit 2 : Arbitration Lost for Mailbox 0 */ +#define CAN_TSR_TERR0 (1 << 3) /* Bit 3 : Transmission Error of Mailbox 0 */ +#define CAN_TSR_ABRQ0 (1 << 7) /* Bit 7 : Abort Request for Mailbox 0 */ +#define CAN_TSR_RQCP1 (1 << 8) /* Bit 8 : Request Completed Mailbox 1 */ +#define CAN_TSR_TXOK1 (1 << 9) /* Bit 9 : Transmission OK of Mailbox 1 */ +#define CAN_TSR_ALST1 (1 << 10) /* Bit 10 : Arbitration Lost for Mailbox 1 */ +#define CAN_TSR_TERR1 (1 << 11) /* Bit 11 : Transmission Error of Mailbox 1 */ +#define CAN_TSR_ABRQ1 (1 << 15) /* Bit 15 : Abort Request for Mailbox 1 */ +#define CAN_TSR_RQCP2 (1 << 16) /* Bit 16 : Request Completed Mailbox 2 */ +#define CAN_TSR_TXOK2 (1 << 17) /* Bit 17 : Transmission OK of Mailbox 2 */ +#define CAN_TSR_ALST2 (1 << 18) /* Bit 18: Arbitration Lost for Mailbox 2 */ +#define CAN_TSR_TERR2 (1 << 19) /* Bit 19: Transmission Error of Mailbox 2 */ +#define CAN_TSR_ABRQ2 (1 << 23) /* Bit 23: Abort Request for Mailbox 2 */ +#define CAN_TSR_CODE_SHIFT (24) /* Bits 25-24: Mailbox Code */ +#define CAN_TSR_CODE_MASK (3 << CAN_TSR_CODE_SHIFT) +#define CAN_TSR_TME0 (1 << 26) /* Bit 26: Transmit Mailbox 0 Empty */ +#define CAN_TSR_TME1 (1 << 27) /* Bit 27: Transmit Mailbox 1 Empty */ +#define CAN_TSR_TME2 (1 << 28) /* Bit 28: Transmit Mailbox 2 Empty */ +#define CAN_TSR_LOW0 (1 << 29) /* Bit 29: Lowest Priority Flag for Mailbox 0 */ +#define CAN_TSR_LOW1 (1 << 30) /* Bit 30: Lowest Priority Flag for Mailbox 1 */ +#define CAN_TSR_LOW2 (1 << 31) /* Bit 31: Lowest Priority Flag for Mailbox 2 */ + +/* CAN receive FIFO 0/1 registers */ + +#define CAN_RFR_FMP_SHIFT (0) /* Bits 1-0: FIFO Message Pending */ +#define CAN_RFR_FMP_MASK (3 << CAN_RFR_FMP_SHIFT) +#define CAN_RFR_FULL (1 << 3) /* Bit 3: FIFO 0 Full */ +#define CAN_RFR_FOVR (1 << 4) /* Bit 4: FIFO 0 Overrun */ +#define CAN_RFR_RFOM (1 << 5) /* Bit 5: Release FIFO 0 Output Mailbox */ + +/* CAN interrupt enable register */ + +#define CAN_IER_TMEIE (1 << 0) /* Bit 0: Transmit Mailbox Empty Interrupt Enable */ +#define CAN_IER_FMPIE0 (1 << 1) /* Bit 1: FIFO Message Pending Interrupt Enable */ +#define CAN_IER_FFIE0 (1 << 2) /* Bit 2: FIFO Full Interrupt Enable */ +#define CAN_IER_FOVIE0 (1 << 3) /* Bit 3: FIFO Overrun Interrupt Enable */ +#define CAN_IER_FMPIE1 (1 << 4) /* Bit 4: FIFO Message Pending Interrupt Enable */ +#define CAN_IER_FFIE1 (1 << 5) /* Bit 5: FIFO Full Interrupt Enable */ +#define CAN_IER_FOVIE1 (1 << 6) /* Bit 6: FIFO Overrun Interrupt Enable */ +#define CAN_IER_EWGIE (1 << 8) /* Bit 8: Error Warning Interrupt Enable */ +#define CAN_IER_EPVIE (1 << 9) /* Bit 9: Error Passive Interrupt Enable */ +#define CAN_IER_BOFIE (1 << 10) /* Bit 10: Bus-Off Interrupt Enable */ +#define CAN_IER_LECIE (1 << 11) /* Bit 11: Last Error Code Interrupt Enable */ +#define CAN_IER_ERRIE (1 << 15) /* Bit 15: Error Interrupt Enable */ +#define CAN_IER_WKUIE (1 << 16) /* Bit 16: Wakeup Interrupt Enable */ +#define CAN_IER_SLKIE (1 << 17) /* Bit 17: Sleep Interrupt Enable */ + +/* CAN error status register */ + +#define CAN_ESR_EWGF (1 << 0) /* Bit 0: Error Warning Flag */ +#define CAN_ESR_EPVF (1 << 1) /* Bit 1: Error Passive Flag */ +#define CAN_ESR_BOFF (1 << 2) /* Bit 2: Bus-Off Flag */ +#define CAN_ESR_LEC_SHIFT (4) /* Bits 6-4: Last Error Code */ +#define CAN_ESR_LEC_MASK (7 << CAN_ESR_LEC_SHIFT) +# define CAN_ESR_NOERROR (0 << CAN_ESR_LEC_SHIFT) /* 000: No Error */ +# define CAN_ESR_STUFFERROR (1 << CAN_ESR_LEC_SHIFT) /* 001: Stuff Error */ +# define CAN_ESR_FORMERROR (2 << CAN_ESR_LEC_SHIFT) /* 010: Form Error */ +# define CAN_ESR_ACKERROR (3 << CAN_ESR_LEC_SHIFT) /* 011: Acknowledgment Error */ +# define CAN_ESR_BRECERROR (4 << CAN_ESR_LEC_SHIFT) /* 100: Bit recessive Error */ +# define CAN_ESR_BDOMERROR (5 << CAN_ESR_LEC_SHIFT) /* 101: Bit dominant Error */ +# define CAN_ESR_CRCERRPR (6 << CAN_ESR_LEC_SHIFT) /* 110: CRC Error */ +# define CAN_ESR_SWERROR (7 << CAN_ESR_LEC_SHIFT) /* 111: Set by software */ + +#define CAN_ESR_TEC_SHIFT (16) /* Bits 23-16: LS byte of the 9-bit Transmit Error Counter */ +#define CAN_ESR_TEC_MASK (0xff << CAN_ESR_TEC_SHIFT) +#define CAN_ESR_REC_SHIFT (24) /* Bits 31-24: Receive Error Counter */ +#define CAN_ESR_REC_MASK (0xff << CAN_ESR_REC_SHIFT) + +/* CAN bit timing register */ + +#define CAN_BTR_BRP_SHIFT (0) /* Bits 9-0: Baud Rate Prescaler */ +#define CAN_BTR_BRP_MASK (0x03ff << CAN_BTR_BRP_SHIFT) +#define CAN_BTR_TS1_SHIFT (16) /* Bits 19-16: Time Segment 1 */ +#define CAN_BTR_TS1_MASK (0x0f << CAN_BTR_TS1_SHIFT) +#define CAN_BTR_TS2_SHIFT (20) /* Bits 22-20: Time Segment 2 */ +#define CAN_BTR_TS2_MASK (7 << CAN_BTR_TS2_SHIFT) +#define CAN_BTR_SJW_SHIFT (24) /* Bits 25-24: Resynchronization Jump Width */ +#define CAN_BTR_SJW_MASK (3 << CAN_BTR_SJW_SHIFT) +#define CAN_BTR_LBKM (1 << 30) /* Bit 30: Loop Back Mode (Debug) */ +#define CAN_BTR_SILM (1ul << 31) /* Bit 31: Silent Mode (Debug) */ + +#define CAN_BTR_BRP_MAX (1024) /* Maximum BTR value (without decrement) */ +#define CAN_BTR_TSEG1_MAX (16) /* Maximum TSEG1 value (without decrement) */ +#define CAN_BTR_TSEG2_MAX (8) /* Maximum TSEG2 value (without decrement) */ + +/* TX mailbox identifier register */ + +#define CAN_TIR_TXRQ (1 << 0) /* Bit 0: Transmit Mailbox Request */ +#define CAN_TIR_RTR (1 << 1) /* Bit 1: Remote Transmission Request */ +#define CAN_TIR_IDE (1 << 2) /* Bit 2: Identifier Extension */ +#define CAN_TIR_EXID_SHIFT (3) /* Bit 3-31: Extended Identifier */ +#define CAN_TIR_EXID_MASK (0x1fffffff << CAN_TIR_EXID_SHIFT) +#define CAN_TIR_STID_SHIFT (21) /* Bits 21-31: Standard Identifier */ +#define CAN_TIR_STID_MASK (0x07ff << CAN_TIR_STID_SHIFT) + +/* Mailbox data length control and time stamp register */ + +#define CAN_TDTR_DLC_SHIFT (0) /* Bits 3:0: Data Length Code */ +#define CAN_TDTR_DLC_MASK (0x0f << CAN_TDTR_DLC_SHIFT) +#define CAN_TDTR_TGT (1 << 8) /* Bit 8: Transmit Global Time */ +#define CAN_TDTR_TIME_SHIFT (16) /* Bits 31:16: Message Time Stamp */ +#define CAN_TDTR_TIME_MASK (0xffff << CAN_TDTR_TIME_SHIFT) + +/* Mailbox data low register */ + +#define CAN_TDLR_DATA0_SHIFT (0) /* Bits 7-0: Data Byte 0 */ +#define CAN_TDLR_DATA0_MASK (0xff << CAN_TDLR_DATA0_SHIFT) +#define CAN_TDLR_DATA1_SHIFT (8) /* Bits 15-8: Data Byte 1 */ +#define CAN_TDLR_DATA1_MASK (0xff << CAN_TDLR_DATA1_SHIFT) +#define CAN_TDLR_DATA2_SHIFT (16) /* Bits 23-16: Data Byte 2 */ +#define CAN_TDLR_DATA2_MASK (0xff << CAN_TDLR_DATA2_SHIFT) +#define CAN_TDLR_DATA3_SHIFT (24) /* Bits 31-24: Data Byte 3 */ +#define CAN_TDLR_DATA3_MASK (0xff << CAN_TDLR_DATA3_SHIFT) + +/* Mailbox data high register */ + +#define CAN_TDHR_DATA4_SHIFT (0) /* Bits 7-0: Data Byte 4 */ +#define CAN_TDHR_DATA4_MASK (0xff << CAN_TDHR_DATA4_SHIFT) +#define CAN_TDHR_DATA5_SHIFT (8) /* Bits 15-8: Data Byte 5 */ +#define CAN_TDHR_DATA5_MASK (0xff << CAN_TDHR_DATA5_SHIFT) +#define CAN_TDHR_DATA6_SHIFT (16) /* Bits 23-16: Data Byte 6 */ +#define CAN_TDHR_DATA6_MASK (0xff << CAN_TDHR_DATA6_SHIFT) +#define CAN_TDHR_DATA7_SHIFT (24) /* Bits 31-24: Data Byte 7 */ +#define CAN_TDHR_DATA7_MASK (0xff << CAN_TDHR_DATA7_SHIFT) + +/* Rx FIFO mailbox identifier register */ + +#define CAN_RIR_RTR (1 << 1) /* Bit 1: Remote Transmission Request */ +#define CAN_RIR_IDE (1 << 2) /* Bit 2: Identifier Extension */ +#define CAN_RIR_EXID_SHIFT (3) /* Bit 3-31: Extended Identifier */ +#define CAN_RIR_EXID_MASK (0x1fffffff << CAN_RIR_EXID_SHIFT) +#define CAN_RIR_STID_SHIFT (21) /* Bits 21-31: Standard Identifier */ +#define CAN_RIR_STID_MASK (0x07ff << CAN_RIR_STID_SHIFT) + +/* Receive FIFO mailbox data length control and time stamp register */ + +#define CAN_RDTR_DLC_SHIFT (0) /* Bits 3:0: Data Length Code */ +#define CAN_RDTR_DLC_MASK (0x0f << CAN_RDTR_DLC_SHIFT) +#define CAN_RDTR_FM_SHIFT (8) /* Bits 15-8: Filter Match Index */ +#define CAN_RDTR_FM_MASK (0xff << CAN_RDTR_FM_SHIFT) +#define CAN_RDTR_TIME_SHIFT (16) /* Bits 31:16: Message Time Stamp */ +#define CAN_RDTR_TIME_MASK (0xffff << CAN_RDTR_TIME_SHIFT) + +/* Receive FIFO mailbox data low register */ + +#define CAN_RDLR_DATA0_SHIFT (0) /* Bits 7-0: Data Byte 0 */ +#define CAN_RDLR_DATA0_MASK (0xff << CAN_RDLR_DATA0_SHIFT) +#define CAN_RDLR_DATA1_SHIFT (8) /* Bits 15-8: Data Byte 1 */ +#define CAN_RDLR_DATA1_MASK (0xff << CAN_RDLR_DATA1_SHIFT) +#define CAN_RDLR_DATA2_SHIFT (16) /* Bits 23-16: Data Byte 2 */ +#define CAN_RDLR_DATA2_MASK (0xff << CAN_RDLR_DATA2_SHIFT) +#define CAN_RDLR_DATA3_SHIFT (24) /* Bits 31-24: Data Byte 3 */ +#define CAN_RDLR_DATA3_MASK (0xff << CAN_RDLR_DATA3_SHIFT) + +/* Receive FIFO mailbox data high register */ + +#define CAN_RDHR_DATA4_SHIFT (0) /* Bits 7-0: Data Byte 4 */ +#define CAN_RDHR_DATA4_MASK (0xff << CAN_RDHR_DATA4_SHIFT) +#define CAN_RDHR_DATA5_SHIFT (8) /* Bits 15-8: Data Byte 5 */ +#define CAN_RDHR_DATA5_MASK (0xff << CAN_RDHR_DATA5_SHIFT) +#define CAN_RDHR_DATA6_SHIFT (16) /* Bits 23-16: Data Byte 6 */ +#define CAN_RDHR_DATA6_MASK (0xff << CAN_RDHR_DATA6_SHIFT) +#define CAN_RDHR_DATA7_SHIFT (24) /* Bits 31-24: Data Byte 7 */ +#define CAN_RDHR_DATA7_MASK (0xff << CAN_RDHR_DATA7_SHIFT) + +/* CAN filter master register */ + +#define CAN_FMR_FINIT (1 << 0) /* Bit 0: Filter Init Mode */ +#define CAN_FMR_CAN1SB_SHIFT (8) /* Bits 13-8: CAN1 start bank */ +#define CAN_FMR_CAN1SB_MASK (0x3f << CAN_FMR_CAN1SB_SHIFT) + +/* CAN filter mode register */ + +#define CAN_FM1R_FBM_SHIFT (0) /* Bits 27:0: Filter Mode */ +#define CAN_FM1R_FBM_MASK (0x0fffffff << CAN_FM1R_FBM_SHIFT) + +/* CAN filter scale register */ + +#define CAN_FS1R_FSC_SHIFT (0) /* Bits 27:0: Filter Scale Configuration */ +#define CAN_FS1R_FSC_MASK (0x0fffffff << CAN_FS1R_FSC_SHIFT) + +/* CAN filter FIFO assignment register */ + +#define CAN_FFA1R_FFA_SHIFT (0) /* Bits 27:0: Filter FIFO Assignment */ +#define CAN_FFA1R_FFA_MASK (0x0fffffff << CAN_FFA1R_FFA_SHIFT) + +/* CAN filter activation register */ + +#define CAN_FA1R_FACT_SHIFT (0) /* Bits 27:0: Filter Active */ +#define CAN_FA1R_FACT_MASK (0x0fffffff << CAN_FA1R_FACT_SHIFT) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Public Functions Prototypes + ****************************************************************************/ + +#endif /* __ARCH_ARM_SRC_GD32F4_HARDWARE_GD32F4XX_CAN_H */ From 84e0fcd983ffe5f22c0352cc9f655a69e0637768 Mon Sep 17 00:00:00 2001 From: jacksonGreenStone <13117870426@163.com> Date: Sun, 2 Aug 2026 15:55:37 +0800 Subject: [PATCH 07/14] gd32f4: add PHY_SWITCH path for KSZ8863 RMII Ethernet bring-up. Skip MIIM copper reset when the MAC attaches to a switch port, keep PA2/PC1 free for soft I2C, and harden TX/RX poll plus ARP diagnostics. Co-authored-by: Cursor --- arch/arm/src/gd32f4/Kconfig | 13 + arch/arm/src/gd32f4/gd32f4xx_enet.c | 461 +++++++++++++++++++++++++--- arch/arm/src/gd32f4/gd32f4xx_enet.h | 19 ++ net/arp/arp_send.c | 45 ++- 4 files changed, 490 insertions(+), 48 deletions(-) diff --git a/arch/arm/src/gd32f4/Kconfig b/arch/arm/src/gd32f4/Kconfig index fb2d6f1110744..7b54341b7a9cf 100644 --- a/arch/arm/src/gd32f4/Kconfig +++ b/arch/arm/src/gd32f4/Kconfig @@ -398,6 +398,7 @@ config GD32F4_ENETMAC depends on GD32F4_HAVE_ENETMAC select NETDEVICES select ARCH_HAVE_PHY + select ARCH_HAVE_NETDEV_STATISTICS config GD32F4_EXMC bool "EXMC" @@ -2438,6 +2439,18 @@ config GD32F4_PHY_INIT provide the function of phyinitialize(); Before first uses the PHY, the GD32F4 Ethernet driver will call this function one time . +config GD32F4_PHY_SWITCH + bool "Ethernet PHY is an unmanaged switch (e.g. KSZ8863)" + default n + depends on GD32F4_PHY_INIT + ---help--- + Select when the RMII MAC attaches to a switch MAC port (e.g. KSZ8863 + Port3), not a copper PHY. Board logic hard-resets the switch; Port1/2 + copper keep strap autoneg. Skip MIIM soft-reset and MCR force at + GD32F4_PHY_ADDR (those target copper Port1 and can disable AN). MAC + speed/duplex still follow GD32F4_ENET_MODE_* / SPEEDMODE_* when + autoneg is disabled. + config GD32F4_MII bool "Use the media-independent interface (MII) interface" default n diff --git a/arch/arm/src/gd32f4/gd32f4xx_enet.c b/arch/arm/src/gd32f4/gd32f4xx_enet.c index 7a4b5dc329ebe..41ca8c7e10e4b 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_enet.c +++ b/arch/arm/src/gd32f4/gd32f4xx_enet.c @@ -50,6 +50,7 @@ #include #include #include +#include #include #if defined(CONFIG_NET_PKT) @@ -216,12 +217,13 @@ #endif /* This driver does not use enhanced descriptors. Enhanced descriptors must - * be used, however, if time stamping or and/or IPv4 checksum offload is - * supported. + * be used if time stamping and/or IPv4 checksum offload is supported. + * Forcing HWCHECKSUM without enhanced descriptors breaks RX (TX can still + * increment MSC); keep offload disabled until enhanced desc are implemented. */ #undef CONFIG_GD32F4_ENET_ENHANCEDDESC -#define CONFIG_GD32_ENET_HWCHECKSUM +#undef CONFIG_GD32_ENET_HWCHECKSUM /* Add 4 to the configured buffer size to account for the 2 byte checksum * memory needed at the end of the maximum size packet. Buffer sizes must @@ -424,8 +426,14 @@ * ENET_MAC_FRMF_FAR receive all frames 0 (disabled) */ -#ifdef CONFIG_NET_PROMISCUOUS - #define ENET_MAC_FRMF_SET_BITS (ENET_PCFRM_PREVENT_PAUSEFRAME | ENET_MAC_FRMF_PM) +/* PHY_SWITCH bring-up: FAR so unicast ARP replies are not dropped if + * ADDR0/filter is wrong; still count MSC rx + CRC to see wire RX. + */ + +#if defined(CONFIG_NET_PROMISCUOUS) || defined(CONFIG_GD32F4_PHY_SWITCH) + #define ENET_MAC_FRMF_SET_BITS (ENET_PCFRM_PREVENT_PAUSEFRAME | \ + ENET_MAC_FRMF_PM | \ + ENET_MAC_FRMF_FAR) #else #define ENET_MAC_FRMF_SET_BITS (ENET_PCFRM_PREVENT_PAUSEFRAME) #endif @@ -537,9 +545,12 @@ ENET_DMA_CTL_TSFD | \ ENET_DMA_CTL_RSFD) #else - #define DMAOMR_SET_MASK (ENET_RX_THRESHOLD_64BYTES | \ - ENET_TX_THRESHOLD_64BYTES | \ - ENET_DMA_CTL_DTCERFD) + /* Store-and-forward RX still helps with short/ARP frames; no IPCO. */ + + #define ENET_DMA_CTL_SET_MASK (ENET_RX_THRESHOLD_64BYTES | \ + ENET_TX_THRESHOLD_64BYTES | \ + ENET_DMA_CTL_RSFD | \ + ENET_DMA_CTL_FUF) #endif /* Clear the DMA_BCTL bits that will be setup during MAC initialization (or @@ -741,6 +752,10 @@ static void gd32_free_segment(struct gd32_enet_mac_s *priv, int segments); static int gd32_receive_frame(struct gd32_enet_mac_s *priv); static void gd32_receive(struct gd32_enet_mac_s *priv); +#ifdef CONFIG_GD32F4_PHY_SWITCH +static int gd32_rxdesc_cpu_count(struct gd32_enet_mac_s *priv); +static void gd32_mac_loopback_selftest(struct gd32_enet_mac_s *priv); +#endif static void gd32_freeframe(struct gd32_enet_mac_s *priv); static void gd32_tx_done(struct gd32_enet_mac_s *priv); @@ -784,12 +799,15 @@ static void gd32_rxdes_chain_init(struct gd32_enet_mac_s *priv); #if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT) static int gd32_phy_interrupt_enable(struct gd32_enet_mac_s *priv); #endif -#if defined(CONFIG_GD32F4_AUTO_NEGOTIATION) || defined(CONFIG_NETDEV_PHY_IOCTL) +#ifndef CONFIG_GD32F4_PHY_SWITCH static int gd32_phy_read(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t *value); -#endif static int gd32_phy_write(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t value); +#endif +#ifdef CONFIG_GD32F4_PHY_SWITCH +void gd32_enet_diag_snapshot(FAR const char *tag); +#endif static int gd32_phy_init(struct gd32_enet_mac_s *priv); /* MAC/DMA Initialization */ @@ -1113,6 +1131,8 @@ static int gd32_transmit(struct gd32_enet_mac_s *priv) priv->txhead = txdesc; + NETDEV_TXPACKETS(&priv->dev); + /* Detach the buffer from priv->dev structure. That buffer is now * "in-flight". */ @@ -1147,19 +1167,18 @@ static int gd32_transmit(struct gd32_enet_mac_s *priv) gd32_interrupt_disable(priv, ENET_DMA_INTEN_RIE); } - /* Check if the TX Buffer unavailable flag is set */ + /* Clear TX buffer unavailable and always poke transmit poll demand. + * After STE starts with empty TX ring, DMA often suspends (TBU); without + * TPEN the descriptor OWN/DAV bit stays set forever and MSC never moves. + */ - if ((gd32_reg_read(GD32_ENET_DMA_STAT) & ENET_DMA_INTEN_TBUIE) != 0) + if ((gd32_reg_read(GD32_ENET_DMA_STAT) & ENET_DMA_STAT_TBU) != 0) { - /* Clear TX Buffer unavailable flag */ - - gd32_reg_write(ENET_DMA_INTEN_TBUIE, GD32_ENET_DMA_STAT); - - /* Resume DMA transmission */ - - gd32_reg_write(1, GD32_ENET_DMA_TPEN); + gd32_reg_write(ENET_DMA_STAT_TBU, GD32_ENET_DMA_STAT); } + gd32_reg_write(1, GD32_ENET_DMA_TPEN); + /* Enable TX interrupts */ gd32_interrupt_enable(priv, ENET_DMA_INTEN_TIE); @@ -1168,6 +1187,30 @@ static int gd32_transmit(struct gd32_enet_mac_s *priv) wd_start(&priv->txtimeout, GD32_TXTIMEOUT, gd32_tx_timeout_expiry, (wdparm_t)priv); + + /* Bring-up: after TPEN, check DMA consumed descriptor (first 2 frames). */ + + { + static int s_txlog; + uint32_t msc_before; + uint16_t txlen; + + if (s_txlog < 2) + { + txlen = (uint16_t)txfirst->tdes1; /* buffer size programmed above */ + msc_before = gd32_reg_read(GD32_ENET_MSC_TGFCNT); + up_mdelay(2); + _warn("ENET TX#%d len=%u DAV=%d MSC %lu->%lu DMA_STAT=0x%08" + PRIx32 "\n", + s_txlog, (unsigned)txlen, + (txfirst->tdes0 & ENET_TDES0_DAV) != 0, + (unsigned long)msc_before, + (unsigned long)gd32_reg_read(GD32_ENET_MSC_TGFCNT), + gd32_reg_read(GD32_ENET_DMA_STAT)); + s_txlog++; + } + } + return OK; } @@ -1314,6 +1357,29 @@ static void gd32_do_poll(struct gd32_enet_mac_s *priv) dev->d_buf = NULL; } } + else + { + static bool s_nobuf; + + if (!s_nobuf) + { + s_nobuf = true; + _warn("ENET: TX poll skipped (no free buffers, inflight=%d)\n", + priv->inflight); + } + } + } + else + { + static bool s_busy; + + if (!s_busy) + { + s_busy = true; + _warn("ENET: TX poll skipped (desc busy tdes0=0x%08" PRIx32 + " inflight=%d)\n", + priv->txhead->tdes0, priv->inflight); + } } } @@ -1426,18 +1492,14 @@ static void gd32_free_segment(struct gd32_enet_mac_s *priv, priv->rxcurr = NULL; priv->segments = 0; - /* Check if the RX Buffer unavailable flag is set */ + /* Clear RBU if set and always poke receive poll demand (same as TX/TPEN). */ - if ((gd32_reg_read(GD32_ENET_DMA_STAT) & ENET_DMA_INTEN_RBUIE) != 0) + if ((gd32_reg_read(GD32_ENET_DMA_STAT) & ENET_DMA_STAT_RBU) != 0) { - /* Clear RBUS Ethernet DMA flag */ - - gd32_reg_write(ENET_DMA_INTEN_RBUIE, GD32_ENET_DMA_STAT); - - /* Resume DMA reception */ - - gd32_reg_write(1, GD32_ENET_DMA_RPEN); + gd32_reg_write(ENET_DMA_STAT_RBU, GD32_ENET_DMA_STAT); } + + gd32_reg_write(1, GD32_ENET_DMA_RPEN); } /**************************************************************************** @@ -1650,6 +1712,24 @@ static void gd32_receive(struct gd32_enet_mac_s *priv) while (gd32_receive_frame(priv) == OK) { +#ifdef CONFIG_GD32F4_PHY_SWITCH + { + static int s_rxlog; + + if (s_rxlog < 4 && dev->d_buf && dev->d_len >= 14) + { + FAR struct eth_hdr_s *eth = (FAR struct eth_hdr_s *)dev->d_buf; + + _warn("ENET RX#%d len=%u type=0x%04x " + "dst=%02x:%02x:%02x:%02x:%02x:%02x\n", + s_rxlog, (unsigned)dev->d_len, ntohs(eth->type), + eth->dest[0], eth->dest[1], eth->dest[2], + eth->dest[3], eth->dest[4], eth->dest[5]); + s_rxlog++; + } + } +#endif + #ifdef CONFIG_NET_PKT /* When packet sockets are enabled, feed the frame into the tap */ @@ -1683,6 +1763,9 @@ static void gd32_receive(struct gd32_enet_mac_s *priv) { ninfo("IPv4 frame\n"); + NETDEV_RXPACKETS(dev); + NETDEV_RXIPV4(dev); + /* Receive an IPv4 packet from the network device */ ipv4_input(&priv->dev); @@ -1706,6 +1789,9 @@ static void gd32_receive(struct gd32_enet_mac_s *priv) { ninfo("ARP frame\n"); + NETDEV_RXPACKETS(dev); + NETDEV_RXARP(dev); + /* Handle ARP packet */ arp_input(&priv->dev); @@ -1805,6 +1891,7 @@ static void gd32_freeframe(struct gd32_enet_mac_s *priv) /* Yes.. Decrement the number of frames "in-flight". */ priv->inflight--; + NETDEV_TXDONE(&priv->dev); /* If all of the TX descriptors were in-flight, * then RX interrupts may have been disabled... @@ -2359,6 +2446,16 @@ static void gd32_txavail_work(void *arg) gd32_do_poll(priv); } + else + { + static bool s_down; + + if (!s_down) + { + s_down = true; + _warn("ENET: txavail while ifup=0 (TX dropped)\n"); + } + } net_unlock(); } @@ -2882,7 +2979,7 @@ static int gd32_phy_interrupt_enable(struct gd32_enet_mac_s *priv) * ****************************************************************************/ -#if defined(CONFIG_GD32F4_AUTO_NEGOTIATION) || defined(CONFIG_NETDEV_PHY_IOCTL) +#ifndef CONFIG_GD32F4_PHY_SWITCH static int gd32_phy_read(uint16_t phydevaddr, uint16_t phyregaddr, uint16_t *value) { @@ -2924,7 +3021,6 @@ static int gd32_phy_read(uint16_t phydevaddr, return -ETIMEDOUT; } -#endif /**************************************************************************** * Function: gd32_phy_write @@ -2990,6 +3086,191 @@ static int gd32_phy_write(uint16_t phydevaddr, return -ETIMEDOUT; } +#endif /* !CONFIG_GD32F4_PHY_SWITCH */ + +#ifdef CONFIG_GD32F4_PHY_SWITCH +/**************************************************************************** + * Name: gd32_rxdesc_cpu_count + * + * Description: + * Count RX descriptors owned by CPU (DAV clear). MSC rx_ucast only counts + * unicast ¡ª ARP requests are broadcast and would not move RGUFCNT. + * + ****************************************************************************/ + +static int gd32_rxdesc_cpu_count(struct gd32_enet_mac_s *priv) +{ + int n = 0; + int i; + + for (i = 0; i < CONFIG_GD32F4_ENET_NRXDESC; i++) + { + if ((priv->rxtable[i].rdes0 & ENET_RDES0_DAV) == 0) + { + n++; + } + } + + return n; +} + +/**************************************************************************** + * Name: gd32_mac_loopback_selftest + * + * Description: + * Brief MAC internal loopback: if this PASSes, GD32 RX DMA/filter is OK + * and silence on the wire is Port3 RMII / KSZ / PCB. If FAIL, RX path is + * still broken inside the MCU MAC/DMA. + * + ****************************************************************************/ + +static void gd32_mac_loopback_selftest(struct gd32_enet_mac_s *priv) +{ + uint32_t cfg; + uint8_t *buf; + FAR struct eth_hdr_s *eth; + int before; + int after = 0; + int i; + + before = gd32_rxdesc_cpu_count(priv); + + cfg = gd32_reg_read(GD32_ENET_MAC_CFG); + gd32_reg_write(cfg | ENET_MAC_CFG_LBM, GD32_ENET_MAC_CFG); + + buf = gd32_buf_alloc(priv); + if (buf == NULL) + { + gd32_reg_write(cfg, GD32_ENET_MAC_CFG); + _warn("ENET LB: no free buffer\n"); + return; + } + + memset(buf, 0, 64); + eth = (FAR struct eth_hdr_s *)buf; + memcpy(eth->dest, priv->dev.d_mac.ether.ether_addr_octet, 6); + memcpy(eth->src, priv->dev.d_mac.ether.ether_addr_octet, 6); + eth->type = HTONS(0x88b5); + + priv->dev.d_buf = buf; + priv->dev.d_len = 64; + if (gd32_transmit(priv) < 0) + { + gd32_buf_free(priv, buf); + priv->dev.d_buf = NULL; + priv->dev.d_len = 0; + gd32_reg_write(cfg, GD32_ENET_MAC_CFG); + _warn("ENET LB: TX submit failed\n"); + return; + } + + for (i = 0; i < 30; i++) + { + up_mdelay(1); + + /* TX complete may need a poll before IRQ is enabled at ifup */ + + gd32_freeframe(priv); + gd32_receive(priv); + + after = gd32_rxdesc_cpu_count(priv); + if (after > before) + { + break; + } + } + + gd32_reg_write(cfg, GD32_ENET_MAC_CFG); + + /* Drain leftover loopback frames (unknown ethertype is dropped) */ + + gd32_freeframe(priv); + gd32_receive(priv); + + _err("ENET LB: %s (cpu-rxdesc %d->%d) ¡ª " + "PASS=MCU RX DMA ok (check Port3/PCB RX); " + "FAIL=MCU MAC/DMA RX broken\n", + (after > before) ? "PASS" : "FAIL", + before, after); + + /* Reset MSC so later arp-fail rx_u is not polluted by loopback. */ + + gd32_reg_write(ENET_MSC_CTL_CTR, GD32_ENET_MSC_CTL); + gd32_reg_write(0, GD32_ENET_MSC_CTL); +} + +/**************************************************************************** + * Name: gd32_enet_diag_snapshot + * + * Description: + * Dump KSZ8863 via soft-I2C (chip id, reg198, port status) + MAC MSC. + * + ****************************************************************************/ + +void gd32_enet_diag_snapshot(FAR const char *tag) +{ + uint8_t id0 = 0; + uint8_t id1 = 0; + uint8_t reg198 = 0; + uint8_t p1st = 0; + uint8_t p2st = 0; + int r0; + int r1; + int rr; + int rp1; + int rp2; + bool crs; + uint32_t dma_stat; + uint32_t rp; + struct gd32_enet_mac_s *priv = &g_enet_mac[0]; + + r0 = gd32_ksz8863_reg_read(0x00, &id0); + r1 = gd32_ksz8863_reg_read(0x01, &id1); + rr = gd32_ksz8863_reg_read(0xc6, ®198); + rp1 = gd32_ksz8863_reg_read(0x1e, &p1st); /* Port1 Status 0 */ + rp2 = gd32_ksz8863_reg_read(0x2e, &p2st); /* Port2 Status 0 */ + + _warn("KSZ8863 %s I2C chipid=%02x:%02x reg198=0x%02x " + "i2c_ok=%d intclk=%d\n", + tag, id0, id1, reg198, + (r0 == OK && r1 == OK && id0 == 0x88) ? 1 : 0, + (rr == OK && (reg198 & 0x08)) ? 1 : 0); + + _warn("KSZ8863 %s P1stat=0x%02x link=%d P2stat=0x%02x link=%d " + "(status bit5=link)\n", + tag, + p1st, (rp1 == OK) ? ((p1st >> 5) & 1) : -1, + p2st, (rp2 == OK) ? ((p2st >> 5) & 1) : -1); + + if (r0 < 0 || id0 != 0x88) + { + _warn("KSZ8863 %s I2C fail ¡ª check PA2=SDA PC1=SCL soft-I2C " + "(not ETH AF), addr 0x5f, PHY_RST PC2\n", tag); + } + + crs = gd32_gpio_read(GPIO_ENET_RMII_CRS_DV); + dma_stat = gd32_reg_read(GD32_ENET_DMA_STAT); + rp = (dma_stat & ENET_DMA_STAT_RP_MASK) >> ENET_DMA_STAT_RP_SHIFT; + + _warn("KSZ8863 %s MSC tx=%" PRIu32 " rx_u=%" PRIu32 + " rx_crc=%" PRIu32 " rx_align=%" PRIu32 " CRS_DV=%d\n", + tag, + gd32_reg_read(GD32_ENET_MSC_TGFCNT), + gd32_reg_read(GD32_ENET_MSC_RGUFCNT), + gd32_reg_read(GD32_ENET_MSC_RFCECNT), + gd32_reg_read(GD32_ENET_MSC_RFAECNT), + crs ? 1 : 0); + _warn("KSZ8863 %s FRMF=0x%08" PRIx32 " ADDR0L=0x%08" PRIx32 + " DMA_STAT=0x%08" PRIx32 " RPstate=%" PRIu32 + " rdes0=0x%08" PRIx32 " cpu_rx=%d\n", + tag, + gd32_reg_read(GD32_ENET_MAC_FRMF), + gd32_reg_read(GD32_ENET_MAC_ADDR0L), + dma_stat, rp, + priv->rxhead ? priv->rxhead->rdes0 : 0, + gd32_rxdesc_cpu_count(priv)); +} +#endif /* CONFIG_GD32F4_PHY_SWITCH */ /**************************************************************************** * Function: gd32_phy_init @@ -3031,18 +3312,29 @@ static int gd32_phy_init(struct gd32_enet_mac_s *priv) regval |= ENET_MAC_PHY_CTL_CLR; gd32_reg_write(regval, GD32_ENET_MAC_PHY_CTL); - /* Put the PHY in reset mode */ + /* Board hard-reset (CONFIG_GD32F4_PHY_INIT) is done in gd32_enet_config() + * before DMA software reset so RMII REF_CLK is present. + */ - ret = gd32_phy_write(CONFIG_GD32F4_PHY_ADDR, MII_MCR, MII_MCR_RESET); - if (ret < 0) - { - nerr("ERROR: Failed to reset the PHY: %d\n", ret); - return ret; - } +#ifdef CONFIG_GD32F4_PHY_SWITCH + /* KSZ8863: management is soft-I2C (board phyinit). Port3 MCU RMII is + * forced 100FD; copper Port1/2 keep strap AN. Do not use ENET MIIM. + */ - up_mdelay(PHY_RESET_DELAY); +#ifdef CONFIG_GD32F4_ENET_MODE_FULLDUPLEX + priv->fduplex = 1; +#endif +#ifdef CONFIG_GD32F4_ENET_SPEEDMODE_100M + priv->mbps100 = 1; +#endif + + up_mdelay(100); + gd32_enet_diag_snapshot("post-init"); - /* Perform any necessary, board-specific PHY initialization */ + UNUSED(phyval); + UNUSED(ret); + +#else /* !CONFIG_GD32F4_PHY_SWITCH */ #ifdef CONFIG_GD32F4_PHY_INIT ret = gd32_phy_boardinitialize(0); @@ -3053,6 +3345,17 @@ static int gd32_phy_init(struct gd32_enet_mac_s *priv) } #endif + /* Put the PHY in reset mode */ + + ret = gd32_phy_write(CONFIG_GD32F4_PHY_ADDR, MII_MCR, MII_MCR_RESET); + if (ret < 0) + { + nerr("ERROR: Failed to reset the PHY: %d\n", ret); + return ret; + } + + up_mdelay(PHY_RESET_DELAY); + /* Perform auto-negotiation if so configured */ #ifdef CONFIG_GD32F4_AUTO_NEGOTIATION @@ -3199,7 +3502,9 @@ static int gd32_phy_init(struct gd32_enet_mac_s *priv) #ifdef CONFIG_GD32F4_ENET_SPEEDMODE_100M priv->mbps100 = 1; #endif -#endif +#endif /* CONFIG_GD32F4_AUTO_NEGOTIATION */ + +#endif /* CONFIG_GD32F4_PHY_SWITCH */ ninfo("Duplex: %s Speed: %d MBps\n", priv->fduplex ? "FULL" : "HALF", @@ -3275,10 +3580,16 @@ static inline void gd32_enet_gpio_config(struct gd32_enet_mac_s *priv) #if defined(CONFIG_GD32F4_MII) || defined(CONFIG_GD32F4_RMII) - /* MDC and MDIO are common to both modes */ + /* MDC/MDIO: standard PHY boards use ETH AF11 on PC1/PA2. + * Board1 KSZ8863 (PHY_SWITCH) uses those pins as soft-I2C SCL/SDA ¡ª + * must NOT remux to Ethernet AF or I2C management dies (id=ffff). + */ +#ifndef CONFIG_GD32F4_PHY_SWITCH gd32_gpio_config(GPIO_ENET_MDC); - gd32_gpio_config(GPIO_ENET_MDIO); + gd32_gpio_config((GPIO_ENET_MDIO & ~GPIO_CFG_PUPD_MASK) | + GPIO_CFG_PUPD_PULLUP); +#endif /* Set up the MII interface */ @@ -3569,10 +3880,11 @@ static void gd32_mac_address(struct gd32_enet_mac_s *priv) dev->d_mac.ether.ether_addr_octet[4], dev->d_mac.ether.ether_addr_octet[5]); - /* Set the MAC address high register */ + /* Set the MAC address high register (MO bit must stay set on GD32). */ regval = ((uint32_t)dev->d_mac.ether.ether_addr_octet[5] << 8) | - (uint32_t)dev->d_mac.ether.ether_addr_octet[4]; + (uint32_t)dev->d_mac.ether.ether_addr_octet[4] | + ENET_MAC_ADDR0H_MO; gd32_reg_write(regval, GD32_ENET_MAC_ADDR0H); /* Set the MAC address low register */ @@ -3640,6 +3952,10 @@ static int gd32_mac_enable(struct gd32_enet_mac_s *priv) regval |= ENET_DMA_CTL_SRE; gd32_reg_write(regval, GD32_ENET_DMA_CTL); + /* Kick RX DMA poll ¡ª after SRE the receiver may sit suspended until RPEN. */ + + gd32_reg_write(1, GD32_ENET_DMA_RPEN); + /* Enable ENET DMA interrupts. * * The gd32 hardware supports two interrupts: (1) one dedicated to normal @@ -3662,6 +3978,12 @@ static int gd32_mac_enable(struct gd32_enet_mac_s *priv) gd32_reg_write(ENET_DMA_INTEN_RECV_ENABLE | ENET_DMA_INTEN_ERROR_ENABLE, GD32_ENET_DMA_INTEN); + + /* Enable MAC management counters (TX/RX good frame counts). */ + + gd32_reg_write(ENET_MSC_CTL_CTR, GD32_ENET_MSC_CTL); /* reset counters */ + gd32_reg_write(0, GD32_ENET_MSC_CTL); /* run */ + return OK; } @@ -3693,6 +4015,20 @@ static int gd32_enet_config(struct gd32_enet_mac_s *priv) gd32_enet_clock_enable(); +#ifdef CONFIG_GD32F4_PHY_INIT + /* KSZ8863 (and similar RMII_EXTCLK boards): hard-reset the switch so + * REF_CLK is running BEFORE DMA software reset. SWR may hang/fail if + * PA1 has no 50MHz clock yet. + */ + + ret = gd32_phy_boardinitialize(0); + if (ret < 0) + { + nerr("ERROR: Failed to initialize the PHY: %d\n", ret); + return ret; + } +#endif + /* Reset the ENET block */ ninfo("Reset the ENET block\n"); @@ -3736,7 +4072,31 @@ static int gd32_enet_config(struct gd32_enet_mac_s *priv) /* Enable normal MAC operation */ ninfo("Enable normal operation\n"); - return gd32_mac_enable(priv); + ret = gd32_mac_enable(priv); + if (ret < 0) + { + return ret; + } + + _warn("ENET MAC_CFG=0x%08" PRIx32 " DMA_CTL=0x%08" PRIx32 + " FRMF=0x%08" PRIx32 " duplex=%d speed=%d MAC=%02x:%02x:%02x:%02x:%02x:%02x\n", + gd32_reg_read(GD32_ENET_MAC_CFG), + gd32_reg_read(GD32_ENET_DMA_CTL), + gd32_reg_read(GD32_ENET_MAC_FRMF), + priv->fduplex, priv->mbps100 ? 100 : 10, + priv->dev.d_mac.ether.ether_addr_octet[0], + priv->dev.d_mac.ether.ether_addr_octet[1], + priv->dev.d_mac.ether.ether_addr_octet[2], + priv->dev.d_mac.ether.ether_addr_octet[3], + priv->dev.d_mac.ether.ether_addr_octet[4], + priv->dev.d_mac.ether.ether_addr_octet[5]); + +#ifdef CONFIG_GD32F4_PHY_SWITCH + gd32_mac_loopback_selftest(priv); + gd32_enet_diag_snapshot("ifup"); +#endif + + return OK; } /**************************************************************************** @@ -3797,6 +4157,17 @@ int gd32_enet_init(int intf) gd32_enet_gpio_config(priv); +#ifdef CONFIG_GD32F4_PHY_INIT + /* Ensure switch REF_CLK before the early ifdown DMA software reset. */ + + ret = gd32_phy_boardinitialize(0); + if (ret < 0) + { + nerr("ERROR: PHY board init failed: %d\n", ret); + return ret; + } +#endif + /* Attach the IRQ to the driver */ if (irq_attach(GD32_IRQ_ENET, gd32_interrupt, NULL)) diff --git a/arch/arm/src/gd32f4/gd32f4xx_enet.h b/arch/arm/src/gd32f4/gd32f4xx_enet.h index b67453ab523f7..30673f0b72de3 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_enet.h +++ b/arch/arm/src/gd32f4/gd32f4xx_enet.h @@ -29,6 +29,8 @@ #include +#include + #include "chip.h" #if GD32_NETHERNET > 0 @@ -62,6 +64,23 @@ int gd32_phy_boardinitialize(int intf); #endif +/**************************************************************************** + * Name: gd32_ksz8863_reg_read / gd32_ksz8863_reg_write + * + * Description: + * Board1 KSZ8863 management via software I2C (PA2=SDA, PC1=SCL), + * 7-bit address 0x5F. Not ETH MDIO. + * + ****************************************************************************/ + +#ifdef CONFIG_GD32F4_PHY_SWITCH +int gd32_ksz8863_reg_read(uint8_t reg, uint8_t *value); +int gd32_ksz8863_reg_write(uint8_t reg, uint8_t value); +void gd32_enet_diag_snapshot(FAR const char *tag); +#else +# define gd32_enet_diag_snapshot(tag) ((void)(tag)) +#endif + #endif /* __ASSEMBLY__ */ #endif /* GD32_NETHERNET > 0 */ #endif /* __ARCH_ARM_SRC_GD32F4_HARDWARE_GD32F4XX_ENET_H */ diff --git a/net/arp/arp_send.c b/net/arp/arp_send.c index 5f97a9807b058..75ede7e69b7d9 100644 --- a/net/arp/arp_send.c +++ b/net/arp/arp_send.c @@ -43,6 +43,10 @@ #include "route/route.h" #include "arp/arp.h" +#ifdef CONFIG_GD32F4_PHY_SWITCH +void gd32_enet_diag_snapshot(FAR const char *tag); +#endif + #ifdef CONFIG_NET_ARP_SEND /**************************************************************************** @@ -134,6 +138,15 @@ static uint32_t arp_send_eventhandler(FAR struct net_driver_s *dev, arp_format(dev, state->snd_ipaddr); + if (dev->d_len == 0) + { + _err("ARP: format failed for %u.%u.%u.%u\n", + ip4_addr1(state->snd_ipaddr), ip4_addr2(state->snd_ipaddr), + ip4_addr3(state->snd_ipaddr), ip4_addr4(state->snd_ipaddr)); + arp_send_terminate(dev, state, -ENOMEM); + return flags; + } + /* Don't allow any further call backs. */ arp_send_terminate(dev, state, OK); @@ -320,6 +333,21 @@ int arp_send(in_addr_t ipaddr) break; } + /* One link/MSC snapshot at the start of resolve (not every try). */ + + if (state.snd_retries == 0) + { +#ifdef CONFIG_GD32F4_PHY_SWITCH + gd32_enet_diag_snapshot("arp-begin"); +#endif + _warn("ARP: resolving %u.%u.%u.%u on %s " + "(max %u tries, %u ms)\n", + ip4_addr1(ipaddr), ip4_addr2(ipaddr), + ip4_addr3(ipaddr), ip4_addr4(ipaddr), + (FAR const char *)dev->d_ifname, + CONFIG_ARP_SEND_MAXTRIES, CONFIG_ARP_SEND_DELAYMSEC); + } + /* Set up the ARP response wait BEFORE we send the ARP request */ arp_wait_setup(ipaddr, ¬ify); @@ -424,13 +452,24 @@ int arp_send(in_addr_t ipaddr) /* Increment the retry count */ state.snd_retries++; - nerr("ERROR: arp_wait failed: %d, ipaddr: %u.%u.%u.%u\n", ret, - ip4_addr1(ipaddr), ip4_addr2(ipaddr), - ip4_addr3(ipaddr), ip4_addr4(ipaddr)); + ninfo("arp_wait failed: %d try %u\n", ret, state.snd_retries); } nxsem_destroy(&state.snd_sem); arp_callback_free(dev, state.snd_cb); + +#ifdef CONFIG_GD32F4_PHY_SWITCH + gd32_enet_diag_snapshot(ret >= 0 ? "arp-ok" : "arp-fail"); +#endif + + if (ret < 0) + { + _warn("ARP: %u.%u.%u.%u failed ret=%d " + "(ETIMEDOUT=-110 no reply; ENETUNREACH=-101)\n", + ip4_addr1(ipaddr), ip4_addr2(ipaddr), + ip4_addr3(ipaddr), ip4_addr4(ipaddr), ret); + } + return ret; } From 0d170344036f2e759ef0cdc37f4a0f8299644135 Mon Sep 17 00:00:00 2001 From: jacksonGreenStone <13117870426@163.com> Date: Wed, 12 Aug 2026 23:16:38 +0800 Subject: [PATCH 08/14] gd32f4 can: harden alone-on-bus TX with NART/ABOM and RQCP harvest. Enable NART/ABOM without fragile init-mode toggles, complete TX mailboxes on RQCP, and keep upper-half TX from stalling under O_NONBLOCK on Board1 CAN0. Co-authored-by: Cursor --- arch/arm/src/gd32f4/gd32f4xx_can.c | 491 +++++++++++++++++++++++------ drivers/can/can.c | 19 +- 2 files changed, 411 insertions(+), 99 deletions(-) diff --git a/arch/arm/src/gd32f4/gd32f4xx_can.c b/arch/arm/src/gd32f4/gd32f4xx_can.c index a134800341bfc..db41ee5c53f79 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_can.c +++ b/arch/arm/src/gd32f4/gd32f4xx_can.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -56,9 +57,13 @@ /* Delays *******************************************************************/ -/* Time out for INAK bit */ +/* Time out for INAK / SLAK (bxCAN). A bare 65535 busy-loop is only a few + * hundred us at GD32F470 clocks and races sleep?¨²init; ST HAL uses ~10 ms. + * open(/dev/can0) ?¨² cellinit returns -ETIMEDOUT (errno 110) when this is + * too short or SLEEP leave is not waited. + */ -#define INAK_TIMEOUT 65535 +#define CAN_INIT_TIMEOUT_US 100000u /* 100 ms */ /* Bit timing ***************************************************************/ @@ -89,6 +94,7 @@ struct gd32_can_s uint8_t cansce; /* CAN SCE IRQ number */ #endif uint8_t filter; /* Filter number */ + uint8_t harvesting; /* Nest guard for RQCP poll harvest */ uint32_t base; /* Base address of the CAN control registers */ uint32_t fbase; /* Base address of the CAN filter registers */ uint32_t baud; /* Configured baud */ @@ -160,6 +166,7 @@ static int gd32can_rxinterrupt(struct can_dev_s *dev, int rxmb); static int gd32can_rx0interrupt(int irq, void *context, void *arg); static int gd32can_rx1interrupt(int irq, void *context, void *arg); static int gd32can_txinterrupt(int irq, void *context, void *arg); +static void gd32can_harvest_txdone(struct can_dev_s *dev); #ifdef CONFIG_CAN_ERRORS static int gd32can_sceinterrupt(int irq, void *context, void *arg); #endif @@ -833,14 +840,22 @@ static void gd32can_txint(struct can_dev_s *dev, bool enable) caninfo("CAN%" PRIu8 " txint enable: %d\n", priv->port, enable); - /* Support only disabling the transmit mailbox interrupt */ + /* Enable/disable transmit-mailbox-empty (RQCP) interrupts. Alone-on-bus + * NART completions set RQCP+TERR; without TMEIE the upper-half TX FIFO + * never drains and O_NONBLOCK write() returns EAGAIN forever. + */ - if (!enable) + regval = gd32can_getreg(priv, GD32_CAN_IER_OFFSET); + if (enable) + { + regval |= CAN_IER_TMEIE; + } + else { - regval = gd32can_getreg(priv, GD32_CAN_IER_OFFSET); regval &= ~CAN_IER_TMEIE; - gd32can_putreg(priv, GD32_CAN_IER_OFFSET, regval); } + + gd32can_putreg(priv, GD32_CAN_IER_OFFSET, regval); } #ifdef CONFIG_CAN_ERRORS @@ -1196,14 +1211,21 @@ static int gd32can_ioctl(struct can_dev_s *dev, int cmd, case CANIOC_SET_NART: { uint32_t regval; + bool enabled; - ret = gd32can_enterinitmode(priv); - if (ret != 0) + /* NART is an MCR soft bit (bxCAN/GD32): may be written in normal + * mode. Do NOT enter/exit init here -- INAK wait often returns + * ETIMEDOUT on a noisy/stuck bus even when NART is already set + * at cellinit, which falsely breaks alone-on-bus HIL TX. + */ + + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + enabled = (regval & CAN_MCR_NART) != 0; + if ((arg == 1 && enabled) || (arg != 1 && !enabled)) { - return ret; + return OK; } - regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); if (arg == 1) { regval |= CAN_MCR_NART; @@ -1214,21 +1236,36 @@ static int gd32can_ioctl(struct can_dev_s *dev, int cmd, } gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); - return gd32can_exitinitmode(priv); + + /* Read-back: alone-on-bus TX depends on this bit sticking. */ + + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + if (arg == 1 && (regval & CAN_MCR_NART) == 0) + { + canerr("ERROR: CAN%" PRIu8 " NART write did not stick " + "(MCR=%08" PRIx32 ")\n", + priv->port, regval); + return -EIO; + } + + return OK; } break; case CANIOC_SET_ABOM: { uint32_t regval; + bool enabled; - ret = gd32can_enterinitmode(priv); - if (ret != 0) + /* Same as NART: ABOM is an MCR soft bit; skip init-mode dance. */ + + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + enabled = (regval & CAN_MCR_ABOM) != 0; + if ((arg == 1 && enabled) || (arg != 1 && !enabled)) { - return ret; + return OK; } - regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); if (arg == 1) { regval |= CAN_MCR_ABOM; @@ -1239,7 +1276,7 @@ static int gd32can_ioctl(struct can_dev_s *dev, int cmd, } gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); - return gd32can_exitinitmode(priv); + return OK; } break; @@ -1309,9 +1346,78 @@ static int gd32can_send(struct can_dev_s *dev, caninfo("CAN%" PRIu8 " ID: %" PRIu32 " DLC: %" PRIu8 "\n", priv->port, (uint32_t)msg->cm_hdr.ch_id, msg->cm_hdr.ch_dlc); - /* Select one empty transmit mailbox */ + /* Select one empty transmit mailbox. RQCP may still be set after a + * NART TERR completion if the TX IRQ has not run (common while the + * upper half holds a critical section). Hardware sets RQCP without + * needing the IRQ ¡ª harvest / short spin reclaim the mailbox. + */ regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + if (!gd32can_txmb0empty(regval) && !gd32can_txmb1empty(regval) && + !gd32can_txmb2empty(regval)) + { + int waited; + + gd32can_harvest_txdone(dev); + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + + /* ~3 ms @125 kbit is enough for one EXT frame (+NART TERR). Do + * not spin in IRQ context ¡ª the handler already harvested. + */ + + if (!up_interrupt_context() && + !gd32can_txmb0empty(regval) && !gd32can_txmb1empty(regval) && + !gd32can_txmb2empty(regval)) + { + for (waited = 0; waited < 3000; waited++) + { + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + if ((regval & (CAN_TSR_RQCP0 | CAN_TSR_RQCP1 | + CAN_TSR_RQCP2)) != 0) + { + gd32can_harvest_txdone(dev); + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + } + + if (gd32can_txmb0empty(regval) || gd32can_txmb1empty(regval) || + gd32can_txmb2empty(regval)) + { + break; + } + + up_udelay(1); + } + } + + if (!gd32can_txmb0empty(regval) && !gd32can_txmb1empty(regval) && + !gd32can_txmb2empty(regval)) + { + /* Still occupied (no NART / stuck dominant). Abort so RQCP + * asserts; harvest frees TME for the upper half. + */ + + gd32can_putreg(priv, GD32_CAN_TSR_OFFSET, + CAN_TSR_ABRQ0 | CAN_TSR_ABRQ1 | CAN_TSR_ABRQ2); + if (!up_interrupt_context()) + { + for (waited = 0; waited < 1000; waited++) + { + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + if ((regval & (CAN_TSR_RQCP0 | CAN_TSR_RQCP1 | + CAN_TSR_RQCP2)) != 0) + { + break; + } + + up_udelay(1); + } + } + + gd32can_harvest_txdone(dev); + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + } + } + if (gd32can_txmb0empty(regval)) { txmb = 0; @@ -1326,7 +1432,7 @@ static int gd32can_send(struct can_dev_s *dev, } else { - canerr("ERROR: No available mailbox\n"); + canerr("ERROR: No available mailbox TSR=%08" PRIx32 "\n", regval); return -EBUSY; } @@ -1465,9 +1571,43 @@ static bool gd32can_txready(struct can_dev_s *dev) struct gd32_can_s *priv = dev->cd_priv; uint32_t regval; - /* Return true if any mailbox is available */ + /* Drain any RQCP completions before judging emptiness. txmb*empty() + * requires RQCP clear; with NART, TERR+RQCP can sit until IRQ runs. + * POLLOUT / O_NONBLOCK write() depend on this path when TMEIE is late. + */ + + gd32can_harvest_txdone(dev); regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + if (!gd32can_txmb0empty(regval) && !gd32can_txmb1empty(regval) && + !gd32can_txmb2empty(regval) && !up_interrupt_context()) + { + /* One frame time: allow in-flight NART TERR to set RQCP while the + * caller holds the CAN critical section (TX IRQ masked). + */ + + int waited; + + for (waited = 0; waited < 1500; waited++) + { + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + if ((regval & (CAN_TSR_RQCP0 | CAN_TSR_RQCP1 | CAN_TSR_RQCP2)) != 0) + { + gd32can_harvest_txdone(dev); + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + break; + } + + if (gd32can_txmb0empty(regval) || gd32can_txmb1empty(regval) || + gd32can_txmb2empty(regval)) + { + break; + } + + up_udelay(1); + } + } + caninfo("CAN%" PRIu8 " TSR: %08" PRIx32 "\n", priv->port, regval); return gd32can_txmb0empty(regval) || gd32can_txmb1empty(regval) || @@ -1497,6 +1637,8 @@ static bool gd32can_txempty(struct can_dev_s *dev) struct gd32_can_s *priv = dev->cd_priv; uint32_t regval; + gd32can_harvest_txdone(dev); + /* Return true if all mailboxes are available */ regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); @@ -1684,62 +1826,122 @@ static int gd32can_rx1interrupt(int irq, void *context, void *arg) static int gd32can_txinterrupt(int irq, void *context, void *arg) { struct can_dev_s *dev = (struct can_dev_s *)arg; - struct gd32_can_s *priv; - uint32_t regval; - - DEBUGASSERT(dev != NULL && dev->cd_priv != NULL); - priv = dev->cd_priv; - /* Get the transmit status */ + (void)irq; + (void)context; - regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + gd32can_harvest_txdone(dev); + return OK; +} - /* Check for RQCP0: Request completed mailbox 0 */ +/**************************************************************************** + * Name: gd32can_harvest_txdone + * + * Description: + * Clear RQCP* and notify the upper half for each completed mailbox. + * Safe to call from the TX IRQ or from the poll path (txready/send) when + * NART completions have set RQCP but TMEIE/IRQ has not yet run. Nested + * calls (can_txdone -> can_xmit -> txready) are ignored via harvesting. + * + ****************************************************************************/ - if ((regval & CAN_TSR_RQCP0) != 0) - { - /* Writing '1' to RCP0 clears RCP0 and all the status bits (TXOK0, - * ALST0 and TERR0) for Mailbox 0. - */ +/* Rate-limited proof that mailboxes actually complete (TXOK or TERR). */ - gd32can_putreg(priv, GD32_CAN_TSR_OFFSET, CAN_TSR_RQCP0); +static uint32_t g_gd32can_txdone_n; - /* Tell the upper half that the transfer is finished. */ +static void gd32can_log_txdone(struct gd32_can_s *priv, int mb, + uint32_t tsr) +{ + uint32_t esr; + uint32_t mcr; + uint32_t shift = (uint32_t)mb * 8u; + int txok = (tsr & (CAN_TSR_TXOK0 << shift)) != 0; + int terr = (tsr & (CAN_TSR_TERR0 << shift)) != 0; + int alst = (tsr & (CAN_TSR_ALST0 << shift)) != 0; + + g_gd32can_txdone_n++; + + /* Never syslog from IRQ: RTT/printf paths can delay RQCP reclaim and + * starve the upper-half TX FIFO (EAGAIN storm). Thread/poll harvest + * may log; IRQ only bumps the counter. + */ - can_txdone(dev); + if (up_interrupt_context()) + { + return; } - /* Check for RQCP1: Request completed mailbox 1 */ - - if ((regval & CAN_TSR_RQCP1) != 0) + if (g_gd32can_txdone_n > 8 && (g_gd32can_txdone_n % 200) != 0) { - /* Writing '1' to RCP1 clears RCP1 and all the status bits (TXOK1, - * ALST1 and TERR1) for Mailbox 1. - */ + return; + } - gd32can_putreg(priv, GD32_CAN_TSR_OFFSET, CAN_TSR_RQCP1); + esr = gd32can_getreg(priv, GD32_CAN_ESR_OFFSET); + mcr = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + syslog(LOG_INFO, + "gd32can: TX done n=%lu mb=%d TXOK=%d TERR=%d ALST=%d " + "ESR=0x%08" PRIx32 " TEC=%lu REC=%lu NART=%d " + "(alone-on-bus+NART => TXOK=0 TERR=1 is OK; " + "TXOK=1 => ACK/loopback)\n", + (unsigned long)g_gd32can_txdone_n, mb, txok, terr, alst, + esr, + (unsigned long)((esr >> 16) & 0xff), + (unsigned long)((esr >> 24) & 0xff), + (mcr & CAN_MCR_NART) != 0); +} + +static void gd32can_harvest_txdone(struct can_dev_s *dev) +{ + struct gd32_can_s *priv; + uint32_t regval; + uint32_t rqcp; + int mb; + int pass; - /* Tell the upper half that the transfer is finished. */ + DEBUGASSERT(dev != NULL && dev->cd_priv != NULL); + priv = dev->cd_priv; - can_txdone(dev); + if (priv->harvesting) + { + return; } - /* Check for RQCP2: Request completed mailbox 2 */ + priv->harvesting = 1; - if ((regval & CAN_TSR_RQCP2) != 0) + /* Clear every RQCP and notify upper half. Multiple passes cover: + * (1) all three mailboxes completing together + * (2) can_txdone -> can_xmit queuing a new frame that finishes early + * RQCP must be cleared even on TERR (alone-on-bus + NART) or TME stays + * masked by our txmb*empty() check and the S/W FIFO never drains. + */ + + for (pass = 0; pass < 8; pass++) { - /* Writing '1' to RCP2 clears RCP2 and all the status bits (TXOK2, - * ALST2 and TERR2) for Mailbox 2. - */ + regval = gd32can_getreg(priv, GD32_CAN_TSR_OFFSET); + if ((regval & (CAN_TSR_RQCP0 | CAN_TSR_RQCP1 | CAN_TSR_RQCP2)) == 0) + { + break; + } - gd32can_putreg(priv, GD32_CAN_TSR_OFFSET, CAN_TSR_RQCP2); + for (mb = 0; mb < 3; mb++) + { + rqcp = (uint32_t)CAN_TSR_RQCP0 << (mb * 8); + if ((regval & rqcp) == 0) + { + continue; + } - /* Tell the upper half that the transfer is finished. */ + /* Clear RQCP first (frees TME for txmb*empty), then log/notify. + * Status bits are taken from the pre-clear snapshot. + */ - can_txdone(dev); + gd32can_putreg(priv, GD32_CAN_TSR_OFFSET, rqcp); + gd32can_log_txdone(priv, mb, regval); + can_txdone(dev); + } } - return OK; + priv->harvesting = 0; } #ifdef CONFIG_CAN_ERRORS @@ -2050,6 +2252,50 @@ static int gd32can_bittiming(struct gd32_can_s *priv) return OK; } +/**************************************************************************** + * Name: gd32can_wait_msr + * + * Description: + * Poll CAN_MSR until (msr & mask) is set or clear, with a real-time budget. + * + ****************************************************************************/ + +static int gd32can_wait_msr(struct gd32_can_s *priv, uint32_t mask, + bool want_set, uint32_t timeout_us) +{ + uint32_t waited = 0; + uint32_t regval; + + while (waited < timeout_us) + { + regval = gd32can_getreg(priv, GD32_CAN_MSR_OFFSET); + if (want_set) + { + if ((regval & mask) != 0) + { + return OK; + } + } + else if ((regval & mask) == 0) + { + return OK; + } + + up_udelay(10); + waited += 10; + } + + syslog(LOG_ERR, + "gd32can: CAN%" PRIu8 " wait MSR mask=0x%" PRIx32 " want=%d " + "timeout MCR=0x%08" PRIx32 " MSR=0x%08" PRIx32 + " ESR=0x%08" PRIx32 "\n", + priv->port, mask, want_set ? 1 : 0, + gd32can_getreg(priv, GD32_CAN_MCR_OFFSET), + gd32can_getreg(priv, GD32_CAN_MSR_OFFSET), + gd32can_getreg(priv, GD32_CAN_ESR_OFFSET)); + return -ETIMEDOUT; +} + /**************************************************************************** * Name: gd32can_enterinitmode * @@ -2069,38 +2315,31 @@ static int gd32can_bittiming(struct gd32_can_s *priv) static int gd32can_enterinitmode(struct gd32_can_s *priv) { uint32_t regval; - volatile uint32_t timeout; + int ret; caninfo("CAN%" PRIu8 "\n", priv->port); + /* Already in init? */ + + regval = gd32can_getreg(priv, GD32_CAN_MSR_OFFSET); + if ((regval & CAN_MSR_INAK) != 0) + { + return OK; + } + /* Enter initialization mode */ regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); regval |= CAN_MCR_INRQ; gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); - /* Wait until initialization mode is acknowledged */ - - for (timeout = INAK_TIMEOUT; timeout > 0; timeout--) - { - regval = gd32can_getreg(priv, GD32_CAN_MSR_OFFSET); - if ((regval & CAN_MSR_INAK) != 0) - { - /* We are in initialization mode */ - - break; - } - } - - /* Check for a timeout */ - - if (timeout < 1) + ret = gd32can_wait_msr(priv, CAN_MSR_INAK, true, CAN_INIT_TIMEOUT_US); + if (ret < 0) { canerr("ERROR: Timed out waiting to enter initialization mode\n"); - return -ETIMEDOUT; } - return OK; + return ret; } /**************************************************************************** @@ -2120,7 +2359,7 @@ static int gd32can_enterinitmode(struct gd32_can_s *priv) static int gd32can_exitinitmode(struct gd32_can_s *priv) { uint32_t regval; - volatile uint32_t timeout; + int ret; /* Exit Initialization mode, enter Normal mode */ @@ -2128,29 +2367,45 @@ static int gd32can_exitinitmode(struct gd32_can_s *priv) regval &= ~CAN_MCR_INRQ; gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); - /* Wait until the initialization mode exit is acknowledged */ - - for (timeout = INAK_TIMEOUT; timeout > 0; timeout--) + ret = gd32can_wait_msr(priv, CAN_MSR_INAK, false, CAN_INIT_TIMEOUT_US); + if (ret == OK) { - regval = gd32can_getreg(priv, GD32_CAN_MSR_OFFSET); - if ((regval & CAN_MSR_INAK) == 0) - { - /* We are out of initialization mode */ - - break; - } + return OK; } - /* Check for a timeout */ + /* Leaving init needs 11 recessive bits on RX. Stuck-dominant / no + * transceiver blocks that. Enable LBKM while still in init so the cell + * can sync; bxCAN still drives CANTX (HIL analyzer can see TX). + */ + + canerr("ERROR: exit init timed out; retry with LBKM\n"); - if (timeout < 1) + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + regval |= CAN_MCR_INRQ; + gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); + if (gd32can_wait_msr(priv, CAN_MSR_INAK, true, CAN_INIT_TIMEOUT_US) < 0) { - canerr("ERROR: Timed out waiting to exit initialization mode: %08" - PRIx32 "\n", regval); return -ETIMEDOUT; } - return OK; + regval = gd32can_getreg(priv, GD32_CAN_BTR_OFFSET); + regval |= CAN_BTR_LBKM; + gd32can_putreg(priv, GD32_CAN_BTR_OFFSET, regval); + + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + regval &= ~CAN_MCR_INRQ; + gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); + + ret = gd32can_wait_msr(priv, CAN_MSR_INAK, false, CAN_INIT_TIMEOUT_US); + if (ret == OK) + { + syslog(LOG_WARNING, + "gd32can: CAN%" PRIu8 " forced LBKM after exit-init timeout " + "(check CAN transceiver/bus idle; CANTX still driven)\n", + priv->port); + } + + return ret; } /**************************************************************************** @@ -2174,12 +2429,22 @@ static int gd32can_cellinit(struct gd32_can_s *priv) caninfo("CAN%" PRIu8 "\n", priv->port); - /* Exit from sleep mode */ + /* After RCU reset (can_register), the cell boots asleep. Clear SLEEP and + * wait for SLAK=0 before INRQ. Skipping the SLAK wait makes enter-init + * miss INAK within a short busy-loop ? open() errno=110 (ETIMEDOUT). + */ regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); regval &= ~CAN_MCR_SLEEP; gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); + ret = gd32can_wait_msr(priv, CAN_MSR_SLAK, false, CAN_INIT_TIMEOUT_US); + if (ret < 0) + { + canerr("ERROR: Timed out leaving sleep mode (SLAK)\n"); + return ret; + } + ret = gd32can_enterinitmode(priv); if (ret != 0) { @@ -2189,20 +2454,21 @@ static int gd32can_cellinit(struct gd32_can_s *priv) /* Disable the following modes: * * - Time triggered communication mode - * - Automatic bus-off management * - Automatic wake-up mode - * - No automatic retransmission * - Receive FIFO locked mode * * Enable: * * - Transmit FIFO priority + * - NART (no automatic retransmission): without a peer ACK the bxCAN + * mailboxes never free and the upper-half TX queue sticks full. + * HIL / alone-on-bus needs NART so each TX completes with TERR+RQCP. + * - ABOM: recover from error-passive / bus-off after no-ACK TEC climb. */ regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); - regval &= ~(CAN_MCR_RFLM | CAN_MCR_NART | CAN_MCR_AWUM | - CAN_MCR_ABOM | CAN_MCR_TTCM); - regval |= CAN_MCR_TXFP; + regval &= ~(CAN_MCR_RFLM | CAN_MCR_AWUM | CAN_MCR_TTCM); + regval |= CAN_MCR_TXFP | CAN_MCR_NART | CAN_MCR_ABOM; gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); /* Configure bit timing. */ @@ -2214,7 +2480,26 @@ static int gd32can_cellinit(struct gd32_can_s *priv) return ret; } - return gd32can_exitinitmode(priv); + ret = gd32can_exitinitmode(priv); + if (ret != 0) + { + return ret; + } + + /* Soft bits are writable in normal mode; re-assert after leaving init so + * a bit-timing/BTR write path cannot leave NART cleared by accident. + */ + + regval = gd32can_getreg(priv, GD32_CAN_MCR_OFFSET); + regval |= CAN_MCR_NART | CAN_MCR_ABOM | CAN_MCR_TXFP; + gd32can_putreg(priv, GD32_CAN_MCR_OFFSET, regval); + + caninfo("CAN%" PRIu8 " MCR after cellinit: %08" PRIx32 + " (NART=%d ABOM=%d)\n", + priv->port, regval, + (regval & CAN_MCR_NART) != 0, + (regval & CAN_MCR_ABOM) != 0); + return OK; } /**************************************************************************** @@ -2511,6 +2796,22 @@ struct can_dev_s *gd32_caninitialize(int port) gd32_gpio_config(GPIO_CAN0_RX); gd32_gpio_config(GPIO_CAN0_TX); + + /* Prove PA11/PA12 are AF9 (CAN0), not USB/GPIO. */ + + { + uint32_t ctl = getreg32(GD32_GPIO_CTL(GD32_GPIOA)); + uint32_t af1 = getreg32(GD32_GPIO_AFSEL1(GD32_GPIOA)); + unsigned m11 = (ctl >> GPIO_MODE_SHIFT(11)) & 3u; + unsigned m12 = (ctl >> GPIO_MODE_SHIFT(12)) & 3u; + unsigned a11 = (af1 >> 12) & 0xfu; /* pin11 nibble in AFSEL1 */ + unsigned a12 = (af1 >> 16) & 0xfu; /* pin12 */ + + syslog(LOG_INFO, + "gd32can: CAN0 pins PA11/PA12 CTL mode=%u/%u " + "(want AF=2) AFSEL=%u/%u (want 9)\n", + m11, m12, a11, a12); + } } else #endif diff --git a/drivers/can/can.c b/drivers/can/can.c index db1278e0b3027..7d367b66cf545 100644 --- a/drivers/can/can.c +++ b/drivers/can/can.c @@ -736,16 +736,27 @@ static ssize_t can_write(FAR struct file *filep, FAR const char *buffer, /* The transmit sender is full. In order to resolve the Lower half * interrupt exception, attempt to release invalid unconfirm * messages and trigger can_xmit. + * + * IMPORTANT: lower-half txempty()/txready() may harvest RQCP and + * call can_txdone() as a side effect (alone-on-bus NART: TERR+RQCP). + * That frees S/W FIFO space even when H/W is not fully empty. + * Always re-check TX_FULL before returning -EAGAIN. */ if (dev_txempty(dev)) { can_send_done(sender); + } + else + { + /* Kick RQCP harvest via txready while some mailboxes busy. */ - if (!TX_FULL(sender)) - { - break; - } + (void)dev_txready(dev); + } + + if (!TX_FULL(sender)) + { + break; } /* The transmit sender is full -- non-blocking mode selected? */ From 497a69c1c57e4d0962336e12f8bcbc3309cc3a4e Mon Sep 17 00:00:00 2001 From: jacksonGreenStone <13117870426@163.com> Date: Mon, 17 Aug 2026 17:14:41 +0800 Subject: [PATCH 09/14] Add GD32F470ZET6 Board2 eval board entry and relative BSP symlink. Co-authored-by: Cursor --- arch/arm/src/gd32f4/gd32f4xx_progmem.c | 1 + boards/Kconfig | 16 ++++++++++++++-- boards/arm/gd32f4/gd32f470zet6-eval | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) create mode 120000 boards/arm/gd32f4/gd32f470zet6-eval diff --git a/arch/arm/src/gd32f4/gd32f4xx_progmem.c b/arch/arm/src/gd32f4/gd32f4xx_progmem.c index 594edef413fb4..f459105ed571c 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_progmem.c +++ b/arch/arm/src/gd32f4/gd32f4xx_progmem.c @@ -59,6 +59,7 @@ # define FMC_PROGMEM_SECTOR_NUM (2) # define FMC_PROGMEM_SECTOR_SADDR (0x08040000) # define FMC_PROGMEM_SECTOR_EADDR (0x0807FFFF) +# define FMC_PROGMEM_PAGESIZE 4096 # elif defined(CONFIG_GD32F4_FLASH_CONFIG_G) diff --git a/boards/Kconfig b/boards/Kconfig index f3178ec02340d..5731a2b4ae627 100644 --- a/boards/Kconfig +++ b/boards/Kconfig @@ -559,9 +559,9 @@ config ARCH_BOARD_ESP32S3_BOX select ARCH_HAVE_IRQBUTTONS if ESPRESSIF_GPIO_IRQ ---help--- The ESP-BOX is an advanced AIoT, Edge AI, and IIoT applications development platform - released by Espressif Systems. The board is built on Espressif¡¯s powerful ESP32-S3 + released by Espressif Systems. The board is built on Espressif��s powerful ESP32-S3 Wi-Fi + Bluetooth 5 (LE) SoC and feature compact and beautiful enclosures. - Leveraging the multi-functional accessories and reliable examples from the repo¡¯s, + Leveraging the multi-functional accessories and reliable examples from the repo��s, these development boards cater to both prototyping new projects and building complex IoT systems, providing a perfect balance of form and function. @@ -854,6 +854,14 @@ config ARCH_BOARD_GD32F470VIT6_EVAL ---help--- GD32 MCU GD32F470VIT6-EVAL board based on the GD32F4 GD32F470VIT6 MCU. + +config ARCH_BOARD_GD32F470ZET6_EVAL + bool "GD32 MCU GD32F470ZET6" + depends on ARCH_CHIP_GD32F470ZK + select ARCH_HAVE_LEDS + ---help--- + GD32 MCU GD32F470ZET6 board (mcu_plat out-of-tree BSP). + config ARCH_BOARD_HIFIVE1_REVB bool "HiFive1 Rev B board" depends on ARCH_CHIP_FE310 @@ -3736,6 +3744,7 @@ config ARCH_BOARD default "gd32f470zk-aiotbox" if ARCH_BOARD_GD32F470ZK_AIOTBOX default "gd32f470ik-eval" if ARCH_BOARD_GD32F470IK_EVAL default "gd32f470vit6-eval" if ARCH_BOARD_GD32F470VIT6_EVAL + default "gd32f470zet6-eval" if ARCH_BOARD_GD32F470ZET6_EVAL default "hifive1-revb" if ARCH_BOARD_HIFIVE1_REVB default "hymini-stm32v" if ARCH_BOARD_HYMINI_STM32V default "imxrt1020-evk" if ARCH_BOARD_IMXRT1020_EVK @@ -4259,6 +4268,9 @@ endif if ARCH_BOARD_GD32F470VIT6_EVAL source "boards/arm/gd32f4/gd32f470vit6-eval/Kconfig" endif +if ARCH_BOARD_GD32F470ZET6_EVAL +source "boards/arm/gd32f4/gd32f470zet6-eval/Kconfig" +endif if ARCH_BOARD_TEENSY_LC source "boards/arm/kl/teensy-lc/Kconfig" endif diff --git a/boards/arm/gd32f4/gd32f470zet6-eval b/boards/arm/gd32f4/gd32f470zet6-eval new file mode 120000 index 0000000000000..a3e2597478920 --- /dev/null +++ b/boards/arm/gd32f4/gd32f470zet6-eval @@ -0,0 +1 @@ +../../../../nuttx_mcu/mcu_plat/platform/board_gd32f470zet6 \ No newline at end of file From 4ca22db0a3b25b97183ab0b61d9986375d701ac9 Mon Sep 17 00:00:00 2001 From: jacksonGreenStone <13117870426@163.com> Date: Thu, 20 Aug 2026 19:11:14 +0800 Subject: [PATCH 10/14] Board3 support: DM9000 RX/MAC/IRQ fix, RTT dedicated buffers, WarShip board symlink. Unmask ARP/multicast RX on DM9000, keep RTT console interactive, and register stm32f103zet6-warship for Board3 HIL. Co-authored-by: Cursor --- arch/arm/src/gd32f4/gd32f4xx_spi.c | 2 + boards/Board.mk | 39 +++++- boards/Kconfig | 11 ++ boards/arm/gd32f4/gd32f470vit6-eval | 2 +- boards/arm/gd32f4/gd32f470zet6-eval | 2 +- boards/arm/stm32/stm32f103zet6-warship | 1 + drivers/net/Kconfig | 9 ++ drivers/net/dm90x0.c | 182 ++++++++++++++++--------- drivers/net/w5500.c | 21 ++- drivers/segger/serial_rtt.c | 13 +- 10 files changed, 209 insertions(+), 73 deletions(-) create mode 120000 boards/arm/stm32/stm32f103zet6-warship diff --git a/arch/arm/src/gd32f4/gd32f4xx_spi.c b/arch/arm/src/gd32f4/gd32f4xx_spi.c index 035caf8b3ec83..d0deebf7f174f 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_spi.c +++ b/arch/arm/src/gd32f4/gd32f4xx_spi.c @@ -2220,6 +2220,8 @@ struct spi_dev_s *gd32_spibus_initialize(int port) if (port == 2) { /* Select SPI2 */ + + priv = &g_spi2dev; } else #endif diff --git a/boards/Board.mk b/boards/Board.mk index 580a18caf73a7..f3cd0971e0e04 100644 --- a/boards/Board.mk +++ b/boards/Board.mk @@ -58,13 +58,27 @@ endif $(Q) rm romfs.img endif +# Optional out-of-tree object directory. Empty = compile next to sources +# (in-tree NuttX boards). Out-of-tree BSP sets BOARD_OBJDIR before include. +BOARD_OBJDIR ?= + ifneq ($(ZDSVERSION),) -AOBJS = $(ASRCS:.S=$(OBJEXT)) +AOBJS_LOCAL = $(ASRCS:.S=$(OBJEXT)) +else +AOBJS_LOCAL = $(ASRCS:$(ASMEXT)=$(OBJEXT)) +endif +COBJS_LOCAL = $(CSRCS:.c=$(OBJEXT)) +CXXOBJS_LOCAL = $(CXXSRCS:.cxx=$(OBJEXT)) + +ifneq ($(BOARD_OBJDIR),) +AOBJS = $(addprefix $(BOARD_OBJDIR)/,$(AOBJS_LOCAL)) +COBJS = $(addprefix $(BOARD_OBJDIR)/,$(COBJS_LOCAL)) +CXXOBJS = $(addprefix $(BOARD_OBJDIR)/,$(CXXOBJS_LOCAL)) else -AOBJS = $(ASRCS:$(ASMEXT)=$(OBJEXT)) +AOBJS = $(AOBJS_LOCAL) +COBJS = $(COBJS_LOCAL) +CXXOBJS = $(CXXOBJS_LOCAL) endif -COBJS = $(CSRCS:.c=$(OBJEXT)) -CXXOBJS = $(CXXSRCS:.cxx=$(OBJEXT)) SRCS = $(ASRCS) $(CSRCS) OBJS = $(AOBJS) $(COBJS) @@ -93,6 +107,7 @@ $(ASRCS) $(HEAD_ASRC): %$(ASMEXT): %.S $(Q) rm $@.tmp endif +ifeq ($(BOARD_OBJDIR),) $(AOBJS): %$(OBJEXT): %$(ASMEXT) $(call ASSEMBLE, $<, $@) @@ -101,6 +116,19 @@ $(COBJS) $(LINKOBJS): %$(OBJEXT): %.c $(CXXOBJS) $(LINKOBJS): %$(OBJEXT): %.cxx $(call COMPILEXX, $<, $@) +else +$(BOARD_OBJDIR)/%$(OBJEXT): %$(ASMEXT) + $(Q) mkdir -p $(dir $@) + $(call ASSEMBLE, $<, $@) + +$(BOARD_OBJDIR)/%$(OBJEXT): %.c + $(Q) mkdir -p $(dir $@) + $(call COMPILE, $<, $@) + +$(BOARD_OBJDIR)/%$(OBJEXT): %.cxx + $(Q) mkdir -p $(dir $@) + $(call COMPILEXX, $<, $@) +endif libboard$(LIBEXT): $(OBJS) $(CXXOBJS) $(call ARCHIVE, $@, $(OBJS) $(CXXOBJS)) @@ -127,6 +155,9 @@ clean:: $(call DELFILE, libboard$(LIBEXT)) $(call DELFILE, $(ETCSRC)) $(call DELDIR, $(ETCDIR)) +ifneq ($(BOARD_OBJDIR),) + $(Q) rm -f $(AOBJS) $(COBJS) $(CXXOBJS) +endif $(call CLEAN) distclean:: clean diff --git a/boards/Kconfig b/boards/Kconfig index 5731a2b4ae627..74ef78ceadc61 100644 --- a/boards/Kconfig +++ b/boards/Kconfig @@ -862,6 +862,13 @@ config ARCH_BOARD_GD32F470ZET6_EVAL ---help--- GD32 MCU GD32F470ZET6 board (mcu_plat out-of-tree BSP). + +config ARCH_BOARD_STM32F103ZET6_WARSHIP + bool "MCU platform STM32F103ZET6 WarShip V3" + depends on ARCH_CHIP_STM32F103ZE + ---help--- + STM32F103ZET6 WarShip V3 board (mcu_plat out-of-tree BSP). + config ARCH_BOARD_HIFIVE1_REVB bool "HiFive1 Rev B board" depends on ARCH_CHIP_FE310 @@ -3745,6 +3752,7 @@ config ARCH_BOARD default "gd32f470ik-eval" if ARCH_BOARD_GD32F470IK_EVAL default "gd32f470vit6-eval" if ARCH_BOARD_GD32F470VIT6_EVAL default "gd32f470zet6-eval" if ARCH_BOARD_GD32F470ZET6_EVAL + default "stm32f103zet6-warship" if ARCH_BOARD_STM32F103ZET6_WARSHIP default "hifive1-revb" if ARCH_BOARD_HIFIVE1_REVB default "hymini-stm32v" if ARCH_BOARD_HYMINI_STM32V default "imxrt1020-evk" if ARCH_BOARD_IMXRT1020_EVK @@ -4271,6 +4279,9 @@ endif if ARCH_BOARD_GD32F470ZET6_EVAL source "boards/arm/gd32f4/gd32f470zet6-eval/Kconfig" endif +if ARCH_BOARD_STM32F103ZET6_WARSHIP +source "boards/arm/stm32/stm32f103zet6-warship/Kconfig" +endif if ARCH_BOARD_TEENSY_LC source "boards/arm/kl/teensy-lc/Kconfig" endif diff --git a/boards/arm/gd32f4/gd32f470vit6-eval b/boards/arm/gd32f4/gd32f470vit6-eval index 498fdee2cfad6..62aa28fa0507e 120000 --- a/boards/arm/gd32f4/gd32f470vit6-eval +++ b/boards/arm/gd32f4/gd32f470vit6-eval @@ -1 +1 @@ -../../../../nuttx_mcu/mcu_plat/platform/board_gd32f470vit6 \ No newline at end of file +/home/jackson/workspace/project/mcu_plat/nuttx_mcu/mcu_plat/platform/board_gd32f470vit6 \ No newline at end of file diff --git a/boards/arm/gd32f4/gd32f470zet6-eval b/boards/arm/gd32f4/gd32f470zet6-eval index a3e2597478920..fa6b69ac22cef 120000 --- a/boards/arm/gd32f4/gd32f470zet6-eval +++ b/boards/arm/gd32f4/gd32f470zet6-eval @@ -1 +1 @@ -../../../../nuttx_mcu/mcu_plat/platform/board_gd32f470zet6 \ No newline at end of file +/home/jackson/workspace/project/mcu_plat/nuttx_mcu/mcu_plat/platform/board_gd32f470zet6 \ No newline at end of file diff --git a/boards/arm/stm32/stm32f103zet6-warship b/boards/arm/stm32/stm32f103zet6-warship new file mode 120000 index 0000000000000..fc15fd44cf0af --- /dev/null +++ b/boards/arm/stm32/stm32f103zet6-warship @@ -0,0 +1 @@ +/home/jackson/workspace/project/mcu_plat/nuttx_mcu/mcu_plat/platform/board_stm32f103zet6 \ No newline at end of file diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 7a792b5370264..b85d7e9e60dfc 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -154,6 +154,15 @@ config DM9X_BASE hex "DM90x0 base address" default 0x0 +config DM9X_DATA_OFFSET + hex "DM90x0 data register offset from base" + default 0x2 + ---help--- + Offset of the data port from CONFIG_DM9X_BASE. Default 2 matches + boards where CMD is wired to address bit A0 (byte) / A1 (16-bit). + ALIENTEK WarShip V3 wires CMD to FSMC_A7; with a 16-bit bus that + is CPU address bit 8 → use 0x100. + config DM9X_IRQ int "DM90x0 IRQ number" default 0 diff --git a/drivers/net/dm90x0.c b/drivers/net/dm90x0.c index b3120800124a2..e7e62b350acec 100644 --- a/drivers/net/dm90x0.c +++ b/drivers/net/dm90x0.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -250,15 +251,23 @@ * according to user supplied base address and bus width. */ +/* DATA port offset: default +2 (CMD on A0/A1). WarShip V3 CMD on FSMC_A7 + * with 16-bit bus → CONFIG_DM9X_DATA_OFFSET=0x100. + */ + +#ifndef CONFIG_DM9X_DATA_OFFSET +# define CONFIG_DM9X_DATA_OFFSET 2 +#endif + #if defined(CONFIG_DM9X_BUSWIDTH8) # define DM9X_INDEX *(volatile uint8_t*)(CONFIG_DM9X_BASE) -# define DM9X_DATA *(volatile uint8_t*)(CONFIG_DM9X_BASE + 2) +# define DM9X_DATA *(volatile uint8_t*)(CONFIG_DM9X_BASE + CONFIG_DM9X_DATA_OFFSET) #elif defined(CONFIG_DM9X_BUSWIDTH16) # define DM9X_INDEX *(volatile uint16_t*)(CONFIG_DM9X_BASE) -# define DM9X_DATA *(volatile uint16_t*)(CONFIG_DM9X_BASE + 2) +# define DM9X_DATA *(volatile uint16_t*)(CONFIG_DM9X_BASE + CONFIG_DM9X_DATA_OFFSET) #elif defined(CONFIG_DM9X_BUSWIDTH32) # define DM9X_INDEX *(volatile uint32_t*)(CONFIG_DM9X_BASE) -# define DM9X_DATA *(volatile uint32_t*)(CONFIG_DM9X_BASE + 2) +# define DM9X_DATA *(volatile uint32_t*)(CONFIG_DM9X_BASE + CONFIG_DM9X_DATA_OFFSET) #endif /* Phy operating mode. Default is AUTO, but this setting can be overridden @@ -717,7 +726,7 @@ static int dm9x_transmit(FAR struct dm9x_driver_s *priv) /* Increment count of packets transmitted */ priv->dm_ntxpending++; - NETDEV_TXPACKETS(&dm9x0->dm_dev); + NETDEV_TXPACKETS(&priv->dm_dev); /* Disable all DM90x0 interrupts */ @@ -743,9 +752,9 @@ static int dm9x_transmit(FAR struct dm9x_driver_s *priv) priv->ncrxpackets = 0; - /* Re-enable DM90x0 interrupts */ - - putreg(DM9X_IMR, DM9X_IMRENABLE); + /* Leave IMR masked — caller (interrupt_work / txavail_work) re-enables + * after draining. Re-enabling here races with TX-done while work runs. + */ /* Setup the TX timeout watchdog (perhaps restarting the timer) */ @@ -853,9 +862,13 @@ static void dm9x_receive(FAR struct dm9x_driver_s *priv) priv->dm_read((FAR uint8_t *)&rx, 4); - /* Check if any errors were reported by the hardware */ + /* Check if any errors were reported by the hardware. + * RSR bit6 (MF) = multicast/broadcast — not an error. Using 0xbf + * drops ARP (dst FF:FF:FF:FF:FF:FF) and breaks IPv4 bring-up. + * Match Linux: FOE|CE|AE|PLE|RWTO|LCS|RF = 0x9f. + */ - if (rx.desc.rx_status & 0xbf) + if (rx.desc.rx_status & 0x9f) { /* Bad RX packet... update statistics */ @@ -1061,7 +1074,7 @@ static void dm9x_interrupt_work(FAR void *arg) FAR struct dm9x_driver_s *priv = (FAR struct dm9x_driver_s *)arg; uint8_t isr; uint8_t save; - int i; + int pass; /* Process pending Ethernet interrupts */ @@ -1071,66 +1084,63 @@ static void dm9x_interrupt_work(FAR void *arg) save = (uint8_t)DM9X_INDEX; - /* Disable all DM90x0 interrupts */ - - putreg(DM9X_IMR, DM9X_IMRDISABLE); + /* Drain chip ISR while INT may still be asserted (TX done during RX). */ - /* Get and clear the DM90x0 interrupt status bits */ + for (pass = 0; pass < 8; pass++) + { + putreg(DM9X_IMR, DM9X_IMRDISABLE); - isr = getreg(DM9X_ISR); - putreg(DM9X_ISR, isr); - ninfo("Interrupt status: %02x\n", isr); + isr = getreg(DM9X_ISR); + putreg(DM9X_ISR, isr); + ninfo("Interrupt status: %02x\n", isr); - /* Check for link status change */ + if ((isr & (DM9X_INT_PR | DM9X_INT_PT | DM9X_INT_LNKCHG)) == 0 && + priv->dm_ntxpending == 0) + { + break; + } - if (isr & DM9X_INT_LNKCHG) - { - /* Wait up to 0.5s for link OK */ + /* Check for link status change */ - for (i = 0; i < 500; i++) + if (isr & DM9X_INT_LNKCHG) { - dm9x_phyread(priv, 0x1); - if (dm9x_phyread(priv, 0x1) & 0x4) /* Link OK */ + if (dm9x_phyread(priv, 0x1) & 0x4) { - /* Wait to get detected speed */ - - for (i = 0; i < 200; i++) - { - up_mdelay(1); - } + priv->dm_b100m = (dm9x_phyread(priv, 0) & 0x2000) != 0; + } + else + { + priv->dm_b100m = false; + } - /* Set the new network speed */ + ninfo("Link change: %s\n", priv->dm_b100m ? "100M" : "down/10M"); + } - if (dm9x_phyread(priv, 0) & 0x2000) - { - priv->dm_b100m = true; - } - else - { - priv->dm_b100m = false; - } - break; - } + /* Check if we received an incoming packet */ - up_mdelay(1); + if (isr & DM9X_INT_PR) + { + dm9x_receive(priv); } - nerr("ERROR: delay: %dmS speed: %s\n", - i, priv->dm_b100m ? "100M" : "10M"); - } + /* Check if we are able to transmit a packet */ - /* Check if we received an incoming packet */ + if (isr & DM9X_INT_PT) + { + dm9x_txdone(priv); + } - if (isr & DM9X_INT_PR) - { - dm9x_receive(priv); - } + /* TX may have completed during RX-driven transmit — poll NSR. */ - /* Check if we are able to transmit a packet */ + if (priv->dm_ntxpending > 0) + { + uint8_t nsr = getreg(DM9X_NETS); - if (isr & DM9X_INT_PT) - { - dm9x_txdone(priv); + if (nsr & (DM9X_NETS_TX1END | DM9X_NETS_TX2END)) + { + dm9x_txdone(priv); + } + } } /* If the number of consecutive receive packets exceeds a threshold, @@ -1154,10 +1164,6 @@ static void dm9x_interrupt_work(FAR void *arg) DM9X_INDEX = save; net_unlock(); - - /* Re-enable Ethernet interrupts */ - - up_enable_irq(CONFIG_DM9X_IRQ); } /**************************************************************************** @@ -1177,6 +1183,12 @@ static void dm9x_interrupt_work(FAR void *arg) * ****************************************************************************/ +/* Boards may clear platform EXTI pending bits here (e.g. STM32 EXTI_PR). */ + +void weak_function board_dm9x_irq_ack(void) +{ +} + static int dm9x_interrupt(int irq, FAR void *context, FAR void *arg) { #if CONFIG_DM9X_NINTERFACES == 1 @@ -1186,12 +1198,14 @@ static int dm9x_interrupt(int irq, FAR void *context, FAR void *arg) #endif uint8_t isr; - /* Disable further Ethernet interrupts. Because Ethernet interrupts are - * also disabled if the TX timeout event occurs, there can be no race - * condition here. + board_dm9x_irq_ack(); + + /* Mask chip IRQs so INT deasserts (level) without leaving NVIC disabled + * across deferred work — disabling NVIC here permanently broke RX when + * LPWORK lagged or LNKCHG work ran long delays under net_lock. */ - up_disable_irq(CONFIG_DM9X_IRQ); + putreg(DM9X_IMR, DM9X_IMRDISABLE); /* Determine if a TX transfer just completed */ @@ -1206,7 +1220,11 @@ static int dm9x_interrupt(int irq, FAR void *context, FAR void *arg) wd_cancel(&priv->dm_txtimeout); } - /* Schedule to perform the interrupt processing on the worker thread. */ + /* Schedule to perform the interrupt processing on the worker thread. + * If work is already queued, leave IMR masked — the running work must + * drain ISR before re-enabling (re-enabling here loses EXTI edges while + * INT stays low). + */ work_queue(ETHWORK, &priv->dm_irqwork, dm9x_interrupt_work, priv, 0); return OK; @@ -1238,7 +1256,7 @@ static void dm9x_txtimeout_work(FAR void *arg) /* Increment statistics and dump debug info */ net_lock(); - NETDEV_TXTIMEOUTS(priv->dm_dev); + NETDEV_TXTIMEOUTS(&priv->dm_dev); ninfo(" TX packet count: %d\n", priv->dm_ntxpending); ninfo(" TX read pointer address: 0x%02x:%02x\n", @@ -1491,6 +1509,8 @@ static void dm9x_txavail_work(FAR void *arg) devif_poll(&priv->dm_dev, dm9x_txpoll); } + + putreg(DM9X_IMR, DM9X_IMRENABLE); } net_unlock(); @@ -1637,6 +1657,19 @@ static void dm9x_bringup(FAR struct dm9x_driver_s *priv) /* Configure I/O mode */ +#if defined(CONFIG_DM9X_BUSWIDTH16) + priv->dm_read = read16; + priv->dm_write = write16; + priv->dm_discard = discard16; +#elif defined(CONFIG_DM9X_BUSWIDTH8) + priv->dm_read = read8; + priv->dm_write = write8; + priv->dm_discard = discard8; +#elif defined(CONFIG_DM9X_BUSWIDTH32) + priv->dm_read = read32; + priv->dm_write = write32; + priv->dm_discard = discard32; +#else switch (getreg(DM9X_ISR) & DM9X_ISR_IOMODEM) { case DM9X_ISR_IOMODE8: @@ -1660,6 +1693,7 @@ static void dm9x_bringup(FAR struct dm9x_driver_s *priv) default: break; } +#endif /* Program PHY operating mode */ @@ -1684,6 +1718,26 @@ static void dm9x_bringup(FAR struct dm9x_driver_s *priv) putreg(DM9X_ETXCSR, 0x83); #endif + /* Program unicast MAC into PAB0-5 (netinit may have set d_mac already). */ + + { + FAR uint8_t *mptr = priv->dm_dev.d_mac.ether.ether_addr_octet; + int mi; + int mj; + + for (mi = 0, mj = DM9X_PAB0; mi < ETHER_ADDR_LEN; mi++, mj++) + { + putreg(mj, mptr[mi]); + } + + /* Accept all multicast/broadcast via hash table (U-Boot style). */ + + for (mj = DM9X_MAB0; mj <= DM9X_MAB7; mj++) + { + putreg(mj, 0xff); + } + } + /* Initialize statistics */ priv->ncrxpackets = 0; /* Number of continuous RX packets */ diff --git a/drivers/net/w5500.c b/drivers/net/w5500.c index 48ffd70e310ab..fb7a305efdff6 100644 --- a/drivers/net/w5500.c +++ b/drivers/net/w5500.c @@ -36,7 +36,9 @@ #include #include #include +#include #include +#include #include #include @@ -961,6 +963,15 @@ static int w5500_unfence(FAR struct w5500_driver_s *self) w5500_reset(self, false); /* Reset sequence and keep reset de-asserted */ + value = w5500_read8(self, W5500_BSB_COMMON_REGS, W5500_VERSIONR); + syslog(LOG_INFO, "w5500: VERSIONR=0x%02" PRIx8 " (expect 0x04)\n", value); + if (value != 0x04) + { + syslog(LOG_ERR, + "w5500: SPI/chip not responding (VERSIONR). Check WD_PWR/SPI.\n"); + goto error; + } + /* Set the Ethernet interface's MAC address */ w5500_write(self, @@ -1034,6 +1045,7 @@ static int w5500_unfence(FAR struct w5500_driver_s *self) if (value != SN_SR_SOCK_MACRAW) { + syslog(LOG_ERR, "w5500: SN_SR=0x%02" PRIx8 " (not MACRAW)\n", value); nerr("Unexpected status: %02" PRIx8 "\n", value); goto error; } @@ -1059,12 +1071,19 @@ static int w5500_unfence(FAR struct w5500_driver_s *self) if (value & PHYCFGR_LNK) { + syslog(LOG_INFO, "w5500: link UP %dMbps %s PHYCFGR=0x%02" PRIx8 "\n", + (value & PHYCFGR_SPD) ? 100 : 10, + (value & PHYCFGR_DPX) ? "full" : "half", + value); ninfo("Link up (%d Mbps / %s duplex)\n", (value & PHYCFGR_SPD) ? 100 : 10, (value & PHYCFGR_DPX) ? "full" : "half"); } else { + syslog(LOG_ERR, "w5500: link DOWN PHYCFGR=0x%02" PRIx8 + " (cable/PHY). ifup aborted; ping will fail.\n", + value); nwarn("Link still down. Cable plugged?\n"); goto error; } @@ -1348,7 +1367,7 @@ static void w5500_receive(FAR struct w5500_driver_s *self) { nerr("Bad packet size dropped (%"PRIu16")\n", self->w_dev.d_len); self->w_dev.d_len = 0; - NETDEV_RXERRORS(&priv->dev); + NETDEV_RXERRORS(&self->w_dev); continue; } diff --git a/drivers/segger/serial_rtt.c b/drivers/segger/serial_rtt.c index 6128c92c79ecd..33e5618095c3d 100644 --- a/drivers/segger/serial_rtt.c +++ b/drivers/segger/serial_rtt.c @@ -114,6 +114,15 @@ static const struct uart_ops_s g_serial_rtt_ops = static char g_rtt0_xmit_buffer[CONFIG_SEGGER_RTT_BUFFER_SIZE_UP]; static char g_rtt0_recv_buffer[CONFIG_SEGGER_RTT_BUFFER_SIZE_DOWN]; +/* Dedicated SEGGER ring buffers (do not pass NULL — host "Sent 0 of N" + * happens when down SizeOfBuffer stays 0 / default CB not usable). + */ + +static char SERIAL_RTT_BUFFER_SECTION +g_rtt0_up_buffer[CONFIG_SEGGER_RTT_BUFFER_SIZE_UP]; +static char SERIAL_RTT_BUFFER_SECTION +g_rtt0_down_buffer[CONFIG_SEGGER_RTT_BUFFER_SIZE_DOWN]; + static struct serial_rtt_s g_serial_rtt0 = { .uart = @@ -135,8 +144,8 @@ static struct serial_rtt_s g_serial_rtt0 = .priv = &g_serial_rtt0, }, .channel = 0, - .up_buffer = NULL, - .down_buffer = NULL, + .up_buffer = g_rtt0_up_buffer, + .down_buffer = g_rtt0_down_buffer, }; #endif From 73d199998d93919e65e9ad0e53aa4cf7c608f7d0 Mon Sep 17 00:00:00 2001 From: jacksonGreenStone <13117870426@163.com> Date: Mon, 7 Sep 2026 14:38:57 +0800 Subject: [PATCH 11/14] docs: rename mcu_plat references to energyos_mcu in board Kconfig help. Co-authored-by: Cursor --- boards/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boards/Kconfig b/boards/Kconfig index 74ef78ceadc61..0e7d280762e34 100644 --- a/boards/Kconfig +++ b/boards/Kconfig @@ -860,14 +860,14 @@ config ARCH_BOARD_GD32F470ZET6_EVAL depends on ARCH_CHIP_GD32F470ZK select ARCH_HAVE_LEDS ---help--- - GD32 MCU GD32F470ZET6 board (mcu_plat out-of-tree BSP). + GD32 MCU GD32F470ZET6 board (energyos_mcu out-of-tree BSP). config ARCH_BOARD_STM32F103ZET6_WARSHIP bool "MCU platform STM32F103ZET6 WarShip V3" depends on ARCH_CHIP_STM32F103ZE ---help--- - STM32F103ZET6 WarShip V3 board (mcu_plat out-of-tree BSP). + STM32F103ZET6 WarShip V3 board (energyos_mcu out-of-tree BSP). config ARCH_BOARD_HIFIVE1_REVB bool "HiFive1 Rev B board" From be014d3d84e4a1df378e87f6a127dd4e12f0b816 Mon Sep 17 00:00:00 2001 From: jacksonGreenStone <13117870426@163.com> Date: Tue, 8 Sep 2026 19:19:11 +0800 Subject: [PATCH 12/14] gd32f4: expand OTA partition Kconfig for EnergyOS 2MiB flash map. Add boot/factory/security/config/state regions, shrink A/B slots to 768KiB defaults, and retarget board symlinks to energyos_mcu paths. Co-authored-by: Cursor --- arch/arm/src/gd32f4/Kconfig | 82 +++++++++++++++++++++++++- boards/arm/gd32f4/gd32f470vit6-eval | 2 +- boards/arm/gd32f4/gd32f470zet6-eval | 2 +- boards/arm/stm32/stm32f103zet6-warship | 2 +- 4 files changed, 82 insertions(+), 6 deletions(-) diff --git a/arch/arm/src/gd32f4/Kconfig b/arch/arm/src/gd32f4/Kconfig index 7b54341b7a9cf..2588bd258cd6d 100644 --- a/arch/arm/src/gd32f4/Kconfig +++ b/arch/arm/src/gd32f4/Kconfig @@ -231,6 +231,14 @@ menuconfig GD32F4_PROGMEM_OTA_PARTITION if GD32F4_PROGMEM_OTA_PARTITION +# EnergyOS 2 MiB map (see docs/eos_mcu_flash/eos_mcu_flash.png): +# loader 128K | primary 768K | secondary 768K | scratch 128K | +# factory 32K | security 64K | sysconfig 64K | userconfig 64K | ota-state 32K + +config GD32F4_OTA_BOOT_DEVPATH + string "Bootloader (loader) device path" + default "/dev/otaboot" + config GD32F4_OTA_PRIMARY_SLOT_DEVPATH string "Primary slot device path" default "/dev/ota0" @@ -243,26 +251,94 @@ config GD32F4_OTA_SCRATCH_DEVPATH string "Scratch partition device path" default "/dev/otascratch" +config GD32F4_OTA_FACTORY_DEVPATH + string "Factory data device path" + default "/dev/factory" + +config GD32F4_OTA_SECURITY_DEVPATH + string "Security data device path" + default "/dev/security" + +config GD32F4_OTA_SYSCONFIG_DEVPATH + string "System config device path" + default "/dev/sysconfig" + +config GD32F4_OTA_USERCONFIG_DEVPATH + string "User config device path" + default "/dev/userconfig" + +config GD32F4_OTA_STATE_DEVPATH + string "OTA / boot state device path" + default "/dev/ota-state" + +config GD32F4_OTA_BOOT_OFFSET + hex "Bootloader (loader) offset" + default 0x0 + config GD32F4_OTA_PRIMARY_SLOT_OFFSET hex "Primary slot offset" default 0x20000 config GD32F4_OTA_SECONDARY_SLOT_OFFSET hex "Secondary slot offset" - default 0x100000 + default 0xe0000 config GD32F4_OTA_SCRATCH_OFFSET hex "Scratch partition offset" - default 0x1e0000 + default 0x1a0000 + +config GD32F4_OTA_FACTORY_OFFSET + hex "Factory data offset" + default 0x1c0000 + +config GD32F4_OTA_SECURITY_OFFSET + hex "Security data offset" + default 0x1c8000 + +config GD32F4_OTA_SYSCONFIG_OFFSET + hex "System config offset" + default 0x1d8000 + +config GD32F4_OTA_USERCONFIG_OFFSET + hex "User config offset" + default 0x1e8000 + +config GD32F4_OTA_STATE_OFFSET + hex "OTA / boot state offset" + default 0x1f8000 + +config GD32F4_OTA_BOOT_SIZE + hex "Bootloader (loader) size (in bytes)" + default 0x20000 config GD32F4_OTA_SLOT_SIZE hex "Application image slot size (in bytes)" - default 0xe0000 + default 0xc0000 config GD32F4_OTA_SCRATCH_SIZE hex "Scratch partition size (in bytes)" default 0x20000 +config GD32F4_OTA_FACTORY_SIZE + hex "Factory data size (in bytes)" + default 0x8000 + +config GD32F4_OTA_SECURITY_SIZE + hex "Security data size (in bytes)" + default 0x10000 + +config GD32F4_OTA_SYSCONFIG_SIZE + hex "System config size (in bytes)" + default 0x10000 + +config GD32F4_OTA_USERCONFIG_SIZE + hex "User config size (in bytes)" + default 0x10000 + +config GD32F4_OTA_STATE_SIZE + hex "OTA / boot state size (in bytes)" + default 0x8000 + endif # GD32F4_PROGMEM_OTA_PARTITION endif # GD32F4_HAVE_OTA_PARTITION diff --git a/boards/arm/gd32f4/gd32f470vit6-eval b/boards/arm/gd32f4/gd32f470vit6-eval index 62aa28fa0507e..46e9ec68b7f9d 120000 --- a/boards/arm/gd32f4/gd32f470vit6-eval +++ b/boards/arm/gd32f4/gd32f470vit6-eval @@ -1 +1 @@ -/home/jackson/workspace/project/mcu_plat/nuttx_mcu/mcu_plat/platform/board_gd32f470vit6 \ No newline at end of file +/home/jackson/workspace/project/energyos_mcu/nuttx_mcu/energyos_mcu/platform/board_gd32f470vit6 \ No newline at end of file diff --git a/boards/arm/gd32f4/gd32f470zet6-eval b/boards/arm/gd32f4/gd32f470zet6-eval index fa6b69ac22cef..62bdf469d8edd 120000 --- a/boards/arm/gd32f4/gd32f470zet6-eval +++ b/boards/arm/gd32f4/gd32f470zet6-eval @@ -1 +1 @@ -/home/jackson/workspace/project/mcu_plat/nuttx_mcu/mcu_plat/platform/board_gd32f470zet6 \ No newline at end of file +/home/jackson/workspace/project/energyos_mcu/nuttx_mcu/energyos_mcu/platform/board_gd32f470zet6 \ No newline at end of file diff --git a/boards/arm/stm32/stm32f103zet6-warship b/boards/arm/stm32/stm32f103zet6-warship index fc15fd44cf0af..75681e22b3adc 120000 --- a/boards/arm/stm32/stm32f103zet6-warship +++ b/boards/arm/stm32/stm32f103zet6-warship @@ -1 +1 @@ -/home/jackson/workspace/project/mcu_plat/nuttx_mcu/mcu_plat/platform/board_stm32f103zet6 \ No newline at end of file +/home/jackson/workspace/project/energyos_mcu/nuttx_mcu/energyos_mcu/platform/board_stm32f103zet6 \ No newline at end of file From 367db45db6ac59b8f43874836e360b807e4258ec Mon Sep 17 00:00:00 2001 From: jacksonGreenStone <13117870426@163.com> Date: Fri, 11 Sep 2026 18:34:35 +0800 Subject: [PATCH 13/14] =?UTF-8?q?GD32F470=EF=BC=9AFlash=20progmem/FMC=20RA?= =?UTF-8?q?MFUNC=20=E4=B8=8E=20BUSY=20=E5=AE=88=E5=8D=AB=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=92=91=20MCUboot=20OVERWRITE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit å�Œ bank 擦写路径失败快返回;é…�å�ˆ flash_area_get_sector 相对å��移修å¤�。Tag: ota-overwrite-sectorfix-20260911。 Co-authored-by: Cursor --- arch/arm/Kconfig | 1 + arch/arm/src/gd32f4/Kconfig | 49 ++-- arch/arm/src/gd32f4/gd32f4xx_fmc.c | 295 ++++++++++++++++---- arch/arm/src/gd32f4/gd32f4xx_fmc.h | 25 ++ arch/arm/src/gd32f4/gd32f4xx_progmem.c | 289 +++++++++++++++---- arch/arm/src/gd32f4/hardware/gd32f4xx_fmc.h | 6 +- 6 files changed, 544 insertions(+), 121 deletions(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 603dab1a96506..02e1e9a4ac575 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -160,6 +160,7 @@ config ARCH_CHIP_GD32F4 select ARCH_HAVE_I2CRESET select ARCH_HAVE_HEAPCHECK select ARCH_HAVE_PROGMEM + select ARCH_HAVE_RAMFUNCS select ARCH_HAVE_SPI_BITORDER select ARCH_HAVE_TICKLESS select ARM_HAVE_MPU_UNIFIED diff --git a/arch/arm/src/gd32f4/Kconfig b/arch/arm/src/gd32f4/Kconfig index 2588bd258cd6d..2c368ca60d2a0 100644 --- a/arch/arm/src/gd32f4/Kconfig +++ b/arch/arm/src/gd32f4/Kconfig @@ -231,9 +231,9 @@ menuconfig GD32F4_PROGMEM_OTA_PARTITION if GD32F4_PROGMEM_OTA_PARTITION -# EnergyOS 2 MiB map (see docs/eos_mcu_flash/eos_mcu_flash.png): -# loader 128K | primary 768K | secondary 768K | scratch 128K | -# factory 32K | security 64K | sysconfig 64K | userconfig 64K | ota-state 32K +# EnergyOS 2 MiB Layout v2 (Code 512K + Data 1536K @ 0x08080000): +# loader 128K | code-reserve 384K (factory/security/sys/user/ota-state) | +# primary 768K @0x80000 | secondary 768K @0x140000 (no scratch; OVERWRITE) config GD32F4_OTA_BOOT_DEVPATH string "Bootloader (loader) device path" @@ -248,7 +248,7 @@ config GD32F4_OTA_SECONDARY_SLOT_DEVPATH default "/dev/ota1" config GD32F4_OTA_SCRATCH_DEVPATH - string "Scratch partition device path" + string "Scratch partition device path (unused in OVERWRITE)" default "/dev/otascratch" config GD32F4_OTA_FACTORY_DEVPATH @@ -277,35 +277,35 @@ config GD32F4_OTA_BOOT_OFFSET config GD32F4_OTA_PRIMARY_SLOT_OFFSET hex "Primary slot offset" - default 0x20000 + default 0x80000 config GD32F4_OTA_SECONDARY_SLOT_OFFSET hex "Secondary slot offset" - default 0xe0000 + default 0x140000 config GD32F4_OTA_SCRATCH_OFFSET - hex "Scratch partition offset" - default 0x1a0000 + hex "Scratch partition offset (unused when size 0)" + default 0x0 config GD32F4_OTA_FACTORY_OFFSET - hex "Factory data offset" - default 0x1c0000 + hex "Factory data offset (in code-reserve)" + default 0x20000 config GD32F4_OTA_SECURITY_OFFSET hex "Security data offset" - default 0x1c8000 + default 0x28000 config GD32F4_OTA_SYSCONFIG_OFFSET hex "System config offset" - default 0x1d8000 + default 0x38000 config GD32F4_OTA_USERCONFIG_OFFSET hex "User config offset" - default 0x1e8000 + default 0x48000 config GD32F4_OTA_STATE_OFFSET hex "OTA / boot state offset" - default 0x1f8000 + default 0x58000 config GD32F4_OTA_BOOT_SIZE hex "Bootloader (loader) size (in bytes)" @@ -316,8 +316,8 @@ config GD32F4_OTA_SLOT_SIZE default 0xc0000 config GD32F4_OTA_SCRATCH_SIZE - hex "Scratch partition size (in bytes)" - default 0x20000 + hex "Scratch partition size (in bytes; 0 = disabled)" + default 0x0 config GD32F4_OTA_FACTORY_SIZE hex "Factory data size (in bytes)" @@ -496,6 +496,23 @@ config GD32F4_PROGMEM Use a part of GD32F4xx's internal flash memory as a Memory-Technology-Device (MTD). +config GD32F4_FLASH_HAL_SELFTEST + bool "GD32 Flash HAL self-test before MCUboot boot_go" + default n + depends on GD32F4_PROGMEM && GD32F4_GD32F470 + ---help--- + Run Secondary→SRAM→Primary Flash HAL matrix (R1/E1/W1/C1/C2/L1) + before boot_go. Intended for OVERWRITE bring-up only; disable for + production loader. Results are printed via syslog after FMC idle. + +config GD32F4_FLASH_HAL_SELFTEST_FULL + bool "Include full-slot Secondary→Primary copy (C3)" + default n + depends on GD32F4_FLASH_HAL_SELFTEST + ---help--- + Also copy the entire 768 KiB slot (C3). Destroys Primary contents; + Secondary must hold a valid image. + config GD32F4_MTD bool "MTD driver" default n diff --git a/arch/arm/src/gd32f4/gd32f4xx_fmc.c b/arch/arm/src/gd32f4/gd32f4xx_fmc.c index f4abf09868d0f..0b4b8618b5090 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_fmc.c +++ b/arch/arm/src/gd32f4/gd32f4xx_fmc.c @@ -26,6 +26,8 @@ #include #include +#include +#include #include #include @@ -39,6 +41,40 @@ # warning "Default Flash Configuration Used - See Override Flash Size Designator" #endif +/* Erase/program busy-waits MUST execute from SRAM: while FMC is busy the + * same flash bank cannot fetch instructions, so a flash-resident wait loop + * deadlocks (debugger still sees PC near gd32_fmc_page_erase / STAT idle). + */ + +#ifdef CONFIG_ARCH_RAMFUNCS +# include "arm_internal.h" +# define FMC_RAMFUNC __ramfunc__ +#else +# define FMC_RAMFUNC locate_code(".ramfunc") farcall_function noinline_function +#endif + +/* Inlined register helpers for .ramfunc paths — must not call flash-resident + * modifyreg32/putreg32 while FMC is busy (instruction fetch from same bank stalls). + */ + +# define FMC_REG_GET(a) (*(volatile uint32_t *)(a)) +# define FMC_REG_PUT(a, v) do { *(volatile uint32_t *)(a) = (v); } while (0) +# define FMC_REG_CLRSET(a, c, s) \ + do { uint32_t _v = FMC_REG_GET(a); _v &= ~(c); _v |= (s); FMC_REG_PUT(a, _v); } while (0) + +static FMC_RAMFUNC inline irqstate_t fmc_irq_save(void) +{ + uint32_t primask; + + __asm__ volatile ("mrs %0, primask\n cpsid i" : "=r" (primask) :: "memory"); + return (irqstate_t)primask; +} + +static FMC_RAMFUNC inline void fmc_irq_restore(irqstate_t flags) +{ + __asm__ volatile ("msr primask, %0" :: "r" (flags) : "memory"); +} + /**************************************************************************** * Private Data ****************************************************************************/ @@ -60,31 +96,32 @@ static mutex_t g_gd32_fmc_lock = NXMUTEX_INITIALIZER; * ****************************************************************************/ -static gd32_fmc_state_enum gd32_fmc_state_get(void) +static FMC_RAMFUNC gd32_fmc_state_enum gd32_fmc_state_get(void) { + uint32_t stat = FMC_REG_GET(GD32_FMC_STAT); gd32_fmc_state_enum fmc_state = FMC_READY; - if (getreg32(GD32_FMC_STAT) & FMC_STAT_BUSY) + if (stat & FMC_STAT_BUSY) { fmc_state = FMC_BUSY; } - else if (getreg32(GD32_FMC_STAT) & FMC_STAT_RDDERR) + else if (stat & FMC_STAT_RDDERR) { fmc_state = FMC_RDDERR; } - else if (getreg32(GD32_FMC_STAT) & FMC_STAT_PGSERR) + else if (stat & FMC_STAT_PGSERR) { fmc_state = FMC_PGSERR; } - else if (getreg32(GD32_FMC_STAT) & FMC_STAT_PGMERR) + else if (stat & FMC_STAT_PGMERR) { fmc_state = FMC_PGMERR; } - else if (getreg32(GD32_FMC_STAT) & FMC_STAT_WPERR) + else if (stat & FMC_STAT_WPERR) { fmc_state = FMC_WPERR; } - else if (getreg32(GD32_FMC_STAT) & FMC_STAT_OPERR) + else if (stat & FMC_STAT_OPERR) { fmc_state = FMC_OPERR; } @@ -109,7 +146,7 @@ static gd32_fmc_state_enum gd32_fmc_state_get(void) * ****************************************************************************/ -static gd32_fmc_state_enum gd32_fmc_ready_wait(uint32_t timeout) +static FMC_RAMFUNC gd32_fmc_state_enum gd32_fmc_ready_wait(uint32_t timeout) { gd32_fmc_state_enum fmc_state = FMC_BUSY; @@ -218,6 +255,49 @@ int gd32_fmc_lock(void) return ret; } +/**************************************************************************** + * Name: gd32_fmc_unlock_ram + * + * Description: + * Unlock FMC from SRAM (no mutex / no flash-resident helpers). + * + ****************************************************************************/ + +FMC_RAMFUNC void gd32_fmc_unlock_ram(void) +{ + if (FMC_REG_GET(GD32_FMC_CTL) & FMC_CTL_LK) + { + FMC_REG_PUT(GD32_FMC_KEY, FMC_UNLOCK_KEY0); + FMC_REG_PUT(GD32_FMC_KEY, FMC_UNLOCK_KEY1); + } +} + +/**************************************************************************** + * Name: gd32_fmc_lock_ram + * + * Description: + * Lock FMC from SRAM. + * + ****************************************************************************/ + +FMC_RAMFUNC void gd32_fmc_lock_ram(void) +{ + FMC_REG_CLRSET(GD32_FMC_CTL, 0, FMC_CTL_LK); +} + +/**************************************************************************** + * Name: gd32_fmc_flag_clear_ram + * + * Description: + * Clear FMC status flags from SRAM (W1C). + * + ****************************************************************************/ + +FMC_RAMFUNC void gd32_fmc_flag_clear_ram(uint32_t fmc_flag) +{ + FMC_REG_PUT(GD32_FMC_STAT, fmc_flag); +} + #if defined(CONFIG_GD32F4_GD32F470) /**************************************************************************** @@ -234,44 +314,116 @@ int gd32_fmc_lock(void) * ****************************************************************************/ -gd32_fmc_state_enum gd32_fmc_page_erase(uint32_t fmc_page) +FMC_RAMFUNC gd32_fmc_state_enum gd32_fmc_page_erase(uint32_t fmc_page) { gd32_fmc_state_enum fmc_state = FMC_READY; uint32_t regval; + irqstate_t flags; - /* Wait for the FMC ready */ + /* Align to 4 KiB; refuse odd addresses (manual: misaligned page → hang). */ + + if ((fmc_page & 0xfffu) != 0) + { + return FMC_PGSERR; + } + + flags = fmc_irq_save(); + + /* Abort any prior stuck START/SER/PE_EN before starting a new erase. */ + + FMC_REG_CLRSET(GD32_FMC_CTL, FMC_CTL_SER | FMC_CTL_START | FMC_CTL_PG, 0); + FMC_REG_PUT(GD32_FMC_PECFG, 0); + FMC_REG_PUT(GD32_FMC_STAT, FMC_STAT_END | FMC_STAT_OPERR | FMC_STAT_WPERR | + FMC_STAT_PGMERR | FMC_STAT_PGSERR | FMC_STAT_RDDERR); fmc_state = gd32_fmc_ready_wait(FMC_TIMEOUT_COUNT); if (FMC_READY == fmc_state) { - /* unlock page erase operation */ + FMC_REG_PUT(GD32_FMC_PEKEY, FMC_UNLOCK_PE_KEY); - putreg32(FMC_UNLOCK_PE_KEY, GD32_FMC_PEKEY); + /* Manual 2.3.4: PE_EN + PE_ADDR, SN=0, SER, START */ - /* start page erase */ + regval = FMC_PE_EN | (fmc_page & 0x1fffffffu); + FMC_REG_PUT(GD32_FMC_PECFG, regval); + FMC_REG_CLRSET(GD32_FMC_CTL, FMC_CTL_SN_MASK, FMC_CTL_SER); + FMC_REG_CLRSET(GD32_FMC_CTL, 0, FMC_CTL_START); + __asm__ volatile ("dsb" ::: "memory"); + __asm__ volatile ("isb" ::: "memory"); - regval = FMC_PE_EN | fmc_page; - putreg32(regval, GD32_FMC_PECFG); - modifyreg32(GD32_FMC_CTL, FMC_CTL_SN_MASK, 0); - modifyreg32(GD32_FMC_CTL, 0, FMC_CTL_SER); - modifyreg32(GD32_FMC_CTL, 0, FMC_CTL_START); + fmc_state = gd32_fmc_ready_wait(FMC_TIMEOUT_COUNT); - /* Wait for the FMC ready */ + /* Always drop command bits after the wait (success or timeout). */ - fmc_state = gd32_fmc_ready_wait(FMC_TIMEOUT_COUNT); + FMC_REG_PUT(GD32_FMC_PECFG, 0); + FMC_REG_CLRSET(GD32_FMC_CTL, FMC_CTL_SER | FMC_CTL_START, 0); - modifyreg32(GD32_FMC_PECFG, FMC_PE_EN, 0); - modifyreg32(GD32_FMC_CTL, FMC_CTL_SER, 0); - } + /* On success clear the status flags. On timeout/failure keep them (and + * any stuck BUSY) so the caller can fail fast and diagnostics can read + * the cause instead of silently continuing into a flash-read deadlock. + */ - /* return the FMC state */ + if (FMC_READY == fmc_state) + { + FMC_REG_PUT(GD32_FMC_STAT, FMC_STAT_END | FMC_STAT_OPERR | + FMC_STAT_WPERR | FMC_STAT_PGMERR | FMC_STAT_PGSERR | + FMC_STAT_RDDERR); + } + } + fmc_irq_restore(flags); return fmc_state; } -#endif +/**************************************************************************** + * Name: gd32_fmc_erase_range_and_reset + * + * Description: + * Erase [start, end) with 4 KiB page erase from RAM, then system reset. + * Used before MCUboot overwrite so the loader only programs into already + * erased primary flash (same-bank erase from flash-resident code hangs). + * + ****************************************************************************/ + +FMC_RAMFUNC void gd32_fmc_erase_range_and_reset(uint32_t start, uint32_t end) +{ + uint32_t addr; + uint32_t i; + irqstate_t flags; + + flags = fmc_irq_save(); + + if (FMC_REG_GET(GD32_FMC_CTL) & FMC_CTL_LK) + { + FMC_REG_PUT(GD32_FMC_KEY, FMC_UNLOCK_KEY0); + FMC_REG_PUT(GD32_FMC_KEY, FMC_UNLOCK_KEY1); + } + + start &= ~0xfffu; + + for (addr = start; addr < end; addr += 4096u) + { + for (i = 0; i < 4096u; i += 4u) + { + if (*(volatile uint32_t *)(addr + i) != 0xffffffffu) + { + (void)gd32_fmc_page_erase(addr); + break; + } + } + } + + /* AIRCR SYSRESETREQ (key 0x05FA). Never return. */ + + FMC_REG_PUT(0xe000ed0c, 0x05fa0004); + for (; ; ) + { + } + + fmc_irq_restore(flags); /* unreachable; keeps compiler quiet if any */ +} +#endif /* page erase / erase-range: CONFIG_GD32F4_GD32F470 */ /**************************************************************************** * Name: gd32_fmc_sector_erase * @@ -286,9 +438,20 @@ gd32_fmc_state_enum gd32_fmc_page_erase(uint32_t fmc_page) * ****************************************************************************/ -gd32_fmc_state_enum gd32_fmc_sector_erase(uint32_t fmc_sector) +FMC_RAMFUNC gd32_fmc_state_enum gd32_fmc_sector_erase(uint32_t fmc_sector) { gd32_fmc_state_enum fmc_state = FMC_READY; + irqstate_t flags; + + flags = fmc_irq_save(); + + /* Abort any prior stuck command bits; drop page-erase config too. */ + + FMC_REG_PUT(GD32_FMC_PECFG, 0); + FMC_REG_CLRSET(GD32_FMC_CTL, FMC_CTL_SER | FMC_CTL_START | FMC_CTL_PG | + FMC_CTL_SN_MASK, 0); + FMC_REG_PUT(GD32_FMC_STAT, FMC_STAT_END | FMC_STAT_OPERR | FMC_STAT_WPERR | + FMC_STAT_PGMERR | FMC_STAT_PGSERR | FMC_STAT_RDDERR); /* Wait for the FMC ready */ @@ -296,24 +459,27 @@ gd32_fmc_state_enum gd32_fmc_sector_erase(uint32_t fmc_sector) if (FMC_READY == fmc_state) { - /* Start sector erase */ + /* Manual 2.3.5: SER, SN, START (SN encodes sector number). */ - modifyreg32(GD32_FMC_CTL, 0, FMC_CTL_SER); - modifyreg32(GD32_FMC_CTL, FMC_CTL_SN_MASK, fmc_sector); - modifyreg32(GD32_FMC_CTL, 0, FMC_CTL_START); + FMC_REG_CLRSET(GD32_FMC_CTL, FMC_CTL_SN_MASK, FMC_CTL_SER | fmc_sector); + FMC_REG_CLRSET(GD32_FMC_CTL, 0, FMC_CTL_START); + __asm__ volatile ("dsb" ::: "memory"); + __asm__ volatile ("isb" ::: "memory"); - /* Wait for the FMC ready */ + /* Wait for the FMC ready (must run from RAM). */ fmc_state = gd32_fmc_ready_wait(FMC_TIMEOUT_COUNT); - /* Reset the SER bit */ + /* Reset the SER / SN / START bits (always, including timeout). */ - modifyreg32(GD32_FMC_CTL, FMC_CTL_SER, 0); - modifyreg32(GD32_FMC_CTL, FMC_CTL_SN_MASK, 0); + FMC_REG_CLRSET(GD32_FMC_CTL, FMC_CTL_SER | FMC_CTL_START | FMC_CTL_SN_MASK, + 0); + FMC_REG_PUT(GD32_FMC_PECFG, 0); + FMC_REG_PUT(GD32_FMC_STAT, FMC_STAT_END | FMC_STAT_OPERR | FMC_STAT_WPERR | + FMC_STAT_PGMERR | FMC_STAT_PGSERR | FMC_STAT_RDDERR); } - /* return the FMC state */ - + fmc_irq_restore(flags); return fmc_state; } @@ -445,9 +611,15 @@ gd32_fmc_state_enum gd32_fmc_bank1_erase(void) * ****************************************************************************/ -gd32_fmc_state_enum gd32_fmc_word_program(uint32_t address, uint32_t data) +FMC_RAMFUNC gd32_fmc_state_enum gd32_fmc_word_program(uint32_t address, + uint32_t data) { gd32_fmc_state_enum fmc_state = FMC_READY; + irqstate_t flags; + + /* IRQs off: flash-resident ISRs hang if they fetch while FMC BUSY. */ + + flags = fmc_irq_save(); /* Wait for the FMC ready */ @@ -457,20 +629,30 @@ gd32_fmc_state_enum gd32_fmc_word_program(uint32_t address, uint32_t data) { /* Set the PG bit to start program */ - modifyreg32(GD32_FMC_CTL, FMC_CTL_PSZ_MASK, FMC_CTL_PSZ_WORD); - modifyreg32(GD32_FMC_CTL, 0, FMC_CTL_PG); + FMC_REG_CLRSET(GD32_FMC_CTL, FMC_CTL_PSZ_MASK, FMC_CTL_PSZ_WORD); + FMC_REG_CLRSET(GD32_FMC_CTL, 0, FMC_CTL_PG); - putreg32(data, address); + FMC_REG_PUT(address, data); + __asm__ volatile ("dsb" ::: "memory"); - /* Wait for the FMC ready */ + /* Wait for the FMC ready (must run from RAM; see FMC_RAMFUNC). */ fmc_state = gd32_fmc_ready_wait(FMC_TIMEOUT_COUNT); /* Reset the PG bit */ - modifyreg32(GD32_FMC_CTL, FMC_CTL_PG, 0); + FMC_REG_CLRSET(GD32_FMC_CTL, FMC_CTL_PG, 0); + + /* Verify while still in RAM (same-bank flash fetch during BUSY hangs). */ + + if (FMC_READY == fmc_state && FMC_REG_GET(address) != data) + { + fmc_state = FMC_PGMERR; + } } + fmc_irq_restore(flags); + /* Return the FMC state */ return fmc_state; @@ -538,9 +720,13 @@ gd32_fmc_state_enum gd32_fmc_halfword_program(uint32_t address, * ****************************************************************************/ -gd32_fmc_state_enum gd32_fmc_byte_program(uint32_t address, uint8_t data) +FMC_RAMFUNC gd32_fmc_state_enum gd32_fmc_byte_program(uint32_t address, + uint8_t data) { gd32_fmc_state_enum fmc_state = FMC_READY; + irqstate_t flags; + + flags = fmc_irq_save(); /* Wait for the FMC ready */ @@ -550,20 +736,29 @@ gd32_fmc_state_enum gd32_fmc_byte_program(uint32_t address, uint8_t data) { /* Set the PG bit to start program */ - modifyreg32(GD32_FMC_CTL, FMC_CTL_PSZ_MASK, FMC_CTL_PSZ_BYTE); - modifyreg32(GD32_FMC_CTL, 0, FMC_CTL_PG); + FMC_REG_CLRSET(GD32_FMC_CTL, FMC_CTL_PSZ_MASK, FMC_CTL_PSZ_BYTE); + FMC_REG_CLRSET(GD32_FMC_CTL, 0, FMC_CTL_PG); - putreg8(data, address); + *(volatile uint8_t *)address = data; + __asm__ volatile ("dsb" ::: "memory"); - /* Wait for the FMC ready */ + /* Wait for the FMC ready (must run from RAM). */ fmc_state = gd32_fmc_ready_wait(FMC_TIMEOUT_COUNT); /* Reset the PG bit */ - modifyreg32(GD32_FMC_CTL, FMC_CTL_PG, 0); + FMC_REG_CLRSET(GD32_FMC_CTL, FMC_CTL_PG, 0); + + if (FMC_READY == fmc_state && + *(volatile uint8_t *)address != data) + { + fmc_state = FMC_PGMERR; + } } + fmc_irq_restore(flags); + /* Return the FMC state */ return fmc_state; @@ -719,7 +914,7 @@ int gd32_ob_write_protection_disable(uint32_t ob_wp) void gd32_fmc_flag_clear(uint32_t fmc_flag) { - /* Clear the flags */ + /* Status flags are write-1-to-clear (see GD32F4xx FMC_STAT). */ - modifyreg32(GD32_FMC_STAT, fmc_flag, 0); + putreg32(fmc_flag, GD32_FMC_STAT); } diff --git a/arch/arm/src/gd32f4/gd32f4xx_fmc.h b/arch/arm/src/gd32f4/gd32f4xx_fmc.h index 85ee764a474ba..b89e674e54a54 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_fmc.h +++ b/arch/arm/src/gd32f4/gd32f4xx_fmc.h @@ -85,6 +85,19 @@ int gd32_fmc_unlock(void); int gd32_fmc_lock(void); +/**************************************************************************** + * Name: gd32_fmc_unlock_ram / gd32_fmc_lock_ram / gd32_fmc_flag_clear_ram + * + * Description: + * SRAM-resident FMC helpers for use around erase/program busy windows. + * No mutex, no flash-resident putreg32 — safe when called from .ramfunc. + * + ****************************************************************************/ + +void gd32_fmc_unlock_ram(void); +void gd32_fmc_lock_ram(void); +void gd32_fmc_flag_clear_ram(uint32_t fmc_flag); + #if defined(CONFIG_GD32F4_GD32F470) /**************************************************************************** @@ -103,6 +116,18 @@ int gd32_fmc_lock(void); gd32_fmc_state_enum gd32_fmc_page_erase(uint32_t fmc_page); +/**************************************************************************** + * Name: gd32_fmc_erase_range_and_reset + * + * Description: + * RAM-resident: erase [start,end) with 4 KiB pages (skip already 0xFF), + * then trigger a system reset. Does not return. + * + ****************************************************************************/ + +void noreturn_function gd32_fmc_erase_range_and_reset(uint32_t start, + uint32_t end); + #endif /**************************************************************************** * Name: gd32_fmc_sector_erase diff --git a/arch/arm/src/gd32f4/gd32f4xx_progmem.c b/arch/arm/src/gd32f4/gd32f4xx_progmem.c index f459105ed571c..e2210b227cc80 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_progmem.c +++ b/arch/arm/src/gd32f4/gd32f4xx_progmem.c @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -43,6 +44,20 @@ * Pre-processor Definitions ****************************************************************************/ +/* Progmem helpers used around FMC program/erase must not fetch from the + * same flash bank while FMC is busy. Match gd32f4xx_fmc.c RAMFUNC style. + */ + +#ifdef CONFIG_ARCH_RAMFUNCS +# ifdef __clang__ +# define PROGMEM_RAMFUNC __ramfunc__ +# else +# define PROGMEM_RAMFUNC locate_code(".ramfunc") farcall_function noinline_function +# endif +#else +# define PROGMEM_RAMFUNC +#endif + #if defined(CONFIG_GD32F4_FLASH_CONFIG_DEFAULT) # define FMC_PROGMEM_SECTOR_SIZES {_K(128), _K(128)} @@ -70,9 +85,10 @@ # elif defined(CONFIG_GD32F4_FLASH_CONFIG_I) # if defined(CONFIG_GD32F4_GD32F470) -/* GD32F470xI: 2048 KiB main flash @ 0x08000000, 4 KiB page erase. - * Expose full array so MCUboot OTA slots (loader/primary/secondary/scratch) - * can be partitioned via MTD progmem. +/* GD32F470xI: 2048 KiB @ 0x08000000. Expose uniform 4 KiB blocks to MTD + * (partition math needs uniform pagesize). Hardware erase uses sector erase + * inside up_progmem_eraseblock() — page-erase BUSY can stick forever when the + * loader overwrites primary in the same flash bank. */ # define FMC_PROGMEM_SECTOR_SIZES {_K(256), _K(256), _K(256), _K(256)} @@ -80,6 +96,7 @@ # define FMC_PROGMEM_SECTOR_SADDR (0x08000000) # define FMC_PROGMEM_SECTOR_EADDR (0x081FFFFF) # define FMC_PROGMEM_PAGESIZE 4096 +# define FMC_PROGMEM_USE_HW_SECTOR_ERASE 1 # else # define FMC_PROGMEM_SECTOR_SIZES {_K(16), _K(16), _K(16), _K(16)} # define FMC_PROGMEM_SECTOR_NUM (4) @@ -146,10 +163,8 @@ typedef struct uint32_t sector_end_addr; /* the end address of the sector */ } fmc_sector_info_struct; -#if !defined(CONFIG_GD32F4_GD32F470) static const size_t sector_sizes[FMC_PROGMEM_SECTOR_NUM] = FMC_PROGMEM_SECTOR_SIZES; -#endif static mutex_t g_gd32_progmem_lock = NXMUTEX_INITIALIZER; @@ -317,14 +332,13 @@ size_t up_progmem_erasesize(size_t block) { return 0; } - else - { - #ifdef CONFIG_GD32F4_GD32F470 - return FMC_PROGMEM_PAGESIZE; - #else - return sector_sizes[block]; - #endif - } + +#ifdef FMC_PROGMEM_PAGESIZE + UNUSED(sector_sizes); + return FMC_PROGMEM_PAGESIZE; +#else + return sector_sizes[block]; +#endif } /**************************************************************************** @@ -436,54 +450,131 @@ size_t up_progmem_getaddress(size_t page) * ****************************************************************************/ -ssize_t up_progmem_eraseblock(size_t block) +/* Spot-check first / mid / last word of a 4 KiB page (RAM-safe). */ + +static PROGMEM_RAMFUNC bool progmem_page_erased_spot(size_t addr) +{ + if (*(volatile uint32_t *)(addr) != 0xffffffffu) + { + return false; + } + + if (*(volatile uint32_t *)(addr + 2048u) != 0xffffffffu) + { + return false; + } + + if (*(volatile uint32_t *)(addr + 4092u) != 0xffffffffu) + { + return false; + } + + return true; +} + +ssize_t PROGMEM_RAMFUNC up_progmem_eraseblock(size_t block) { size_t addr; +#ifndef FMC_PROGMEM_USE_HW_SECTOR_ERASE fmc_sector_info_struct sector_info; +#endif if (block >= FMC_PROGMEM_SECTOR_NUM) { return -EFAULT; } + /* Inline address math — do not call Flash-resident up_progmem_getaddress + * or gd32_fmc_sector_info_get from this .ramfunc path. + */ + +#ifdef FMC_PROGMEM_PAGESIZE + addr = FMC_PROGMEM_SECTOR_SADDR + block * FMC_PROGMEM_PAGESIZE; +#else addr = up_progmem_getaddress(block); - sector_info = gd32_fmc_sector_info_get(addr); - if (sector_info.sector_name == FMC_WRONG_SECTOR_NAME) +#endif + +#if defined(CONFIG_GD32F4_GD32F470) || defined(FMC_PROGMEM_USE_HW_SECTOR_ERASE) + /* GD32F470: 4 KiB page erase. Sector erase from on-chip loader has been + * observed to leave FMC BUSY stuck. Erasing an already-blank page can also + * stick — skip FMC when the logical page is already 0xFF (word scan). + */ + + /* FMC must be idle before touching flash. If a prior operation left BUSY + * set, any flash read stalls the AHB bus forever (observed hang in + * up_progmem_ispageerased). Fail fast instead of deadlocking so the caller + * can abort the upgrade cleanly. + */ + + if ((getreg32(GD32_FMC_STAT) & FMC_STAT_BUSY) != 0) { - return -EFAULT; + return -EBUSY; } - /* Get flash ready and begin erasing single block */ + if (up_progmem_ispageerased(block) == 0) + { + return (ssize_t)FMC_PROGMEM_PAGESIZE; + } - gd32_fmc_unlock(); - gd32_fmc_flag_clear(FMC_STAT_PERR); + gd32_fmc_unlock_ram(); + gd32_fmc_flag_clear_ram(FMC_STAT_END | FMC_STAT_OPERR | FMC_STAT_WPERR | + FMC_STAT_PGMERR | FMC_STAT_PGSERR | FMC_STAT_RDDERR); -#ifdef CONFIG_GD32F4_GD32F470 - if (gd32_fmc_page_erase(addr)) + if (gd32_fmc_page_erase((uint32_t)addr) != FMC_READY) { - return -EFAULT; + gd32_fmc_lock_ram(); + return -EIO; + } + + /* FMC could have been left busy by a failed erase; never read flash then. */ + + if ((getreg32(GD32_FMC_STAT) & FMC_STAT_BUSY) != 0) + { + gd32_fmc_lock_ram(); + return -EBUSY; } + + /* Spot-check only (full word rescan is unnecessary for OVERWRITE). */ + + if (!progmem_page_erased_spot(addr)) + { + gd32_fmc_lock_ram(); + return -EIO; + } + + gd32_fmc_lock_ram(); + return (ssize_t)FMC_PROGMEM_PAGESIZE; #else - if (gd32_fmc_sector_erase(sector_info.sector_num)) + sector_info = gd32_fmc_sector_info_get(addr); + if (sector_info.sector_name == FMC_WRONG_SECTOR_NAME) { return -EFAULT; } -#endif - - /* Verify */ if (up_progmem_ispageerased(block) == 0) { - /* success */ - - return up_progmem_pagesize(block); + return (ssize_t)up_progmem_pagesize(block); } - else + + gd32_fmc_unlock_ram(); + gd32_fmc_flag_clear_ram(FMC_STAT_END | FMC_STAT_OPERR | FMC_STAT_WPERR | + FMC_STAT_PGMERR | FMC_STAT_PGSERR | FMC_STAT_RDDERR); + + if (gd32_fmc_sector_erase(sector_info.sector_num)) { - /* failure */ + gd32_fmc_lock_ram(); + return -EFAULT; + } - return -EIO; + if (up_progmem_ispageerased(block) == 0) + { + gd32_fmc_lock_ram(); + return up_progmem_pagesize(block); } + + gd32_fmc_lock_ram(); + return -EIO; +#endif } /**************************************************************************** @@ -504,29 +595,116 @@ ssize_t up_progmem_eraseblock(size_t block) * ****************************************************************************/ -ssize_t up_progmem_ispageerased(size_t page) +ssize_t PROGMEM_RAMFUNC up_progmem_ispageerased(size_t page) { size_t page_size; size_t addr; - int i; + size_t i; if (page >= FMC_PROGMEM_SECTOR_NUM) { return -EFAULT; } +#ifdef FMC_PROGMEM_PAGESIZE + page_size = FMC_PROGMEM_PAGESIZE; + addr = FMC_PROGMEM_SECTOR_SADDR + page * FMC_PROGMEM_PAGESIZE; +#else page_size = up_progmem_pagesize(page); addr = up_progmem_getaddress(page); +#endif - for (i = 0; i < page_size; i++) + for (i = 0; i < page_size; i += 4) { - if (getreg8(addr) != 0xffu) + if (*(volatile uint32_t *)(addr + i) != 0xffffffffu) { - break; + return (ssize_t)(page_size - i); } } - return (ssize_t)(page_size - i); + return 0; +} + +/**************************************************************************** + * Name: up_progmem_write + * + * Description: + * Program data at given address + * + * Note: this function is not limited to single page and nor it requires + * the address be aligned inside the page boundaries. + * + * Input Parameters: + * addr - Address with or without flash offset + * (absolute or aligned to page0) + * buf - Pointer to buffer + * count - Number of bytes to write + * + * Returned Value: + * Bytes written or negative value on error. The following errors are + * reported (errno is not set!) + * + * EINVAL: If count is not aligned with the flash boundaries (i.e. + * some MCU's require per half-word or even word access) + * EFAULT: On invalid address + * EIO: On unsuccessful write + * EROFS: On access to write protected area + * EACCES: Insufficient permissions (read/write protected) + * EPERM: If operation is not permitted due to some other constraints + * (i.e. some internal block is not running etc.) + * + ****************************************************************************/ + +/**************************************************************************** + * Name: progmem_write_ram + * + * Description: + * Program loop that must execute from RAM on GD32F470 (same flash bank as + * the loader/app). Flash-resident loops hang if they fetch while FMC BUSY. + * + ****************************************************************************/ + +static PROGMEM_RAMFUNC int progmem_write_ram(size_t addr, FAR const uint8_t *byte, + size_t remaining) +{ + while (remaining > 0) + { + if ((addr & 3) == 0 && remaining >= 4) + { + uint32_t word = + ((uint32_t)byte[0]) | + ((uint32_t)byte[1] << 8) | + ((uint32_t)byte[2] << 16) | + ((uint32_t)byte[3] << 24); + + if (word != 0xffffffffu) + { + if (gd32_fmc_word_program(addr, word) != FMC_READY) + { + return -EIO; + } + } + + addr += 4; + byte += 4; + remaining -= 4; + continue; + } + + if (*byte != 0xff) + { + if (gd32_fmc_byte_program(addr, *byte) != FMC_READY) + { + return -EIO; + } + } + + addr++; + byte++; + remaining--; + } + + return OK; } /**************************************************************************** @@ -561,8 +739,6 @@ ssize_t up_progmem_ispageerased(size_t page) ssize_t up_progmem_write(size_t addr, const void *buf, size_t count) { - uint8_t *byte = (uint8_t *)buf; - uint32_t i; int ret; /* Check for valid address range */ @@ -583,29 +759,34 @@ ssize_t up_progmem_write(size_t addr, const void *buf, size_t count) return -EFAULT; } - /* Get flash ready and begin flashing */ + /* Unlock from SRAM before entering the FMC-busy program loop. */ - gd32_fmc_unlock(); + gd32_fmc_unlock_ram(); - for (i = 0; i < count; i++) + ret = progmem_write_ram(addr, (FAR const uint8_t *)buf, count); + + /* Fail closed: programmed bytes must be visible on the flash bus. */ + + if (ret >= 0) { - gd32_fmc_byte_program(addr, *byte); + FAR const uint8_t *src = (FAR const uint8_t *)buf; + size_t i; + size_t vlen = count < 16 ? count : 16; - if (getreg8(addr) != *byte) + for (i = 0; i < vlen; i++) { - gd32_fmc_lock(); - nxmutex_unlock(&g_gd32_progmem_lock); - return -EIO; + if (*(volatile uint8_t *)(addr + i) != src[i]) + { + ret = -EIO; + break; + } } - - addr++; - byte++; } - gd32_fmc_lock(); + gd32_fmc_lock_ram(); nxmutex_unlock(&g_gd32_progmem_lock); - return count; + return (ret < 0) ? (ssize_t)ret : (ssize_t)count; } /**************************************************************************** diff --git a/arch/arm/src/gd32f4/hardware/gd32f4xx_fmc.h b/arch/arm/src/gd32f4/hardware/gd32f4xx_fmc.h index 6782a01766e63..34f8e05c80c81 100644 --- a/arch/arm/src/gd32f4/hardware/gd32f4xx_fmc.h +++ b/arch/arm/src/gd32f4/hardware/gd32f4xx_fmc.h @@ -302,6 +302,10 @@ #define FMC_OB_DRP_ALL (0x0FFF0FFF) /* D-bus read protection protection of all sectors */ /* FMC time out */ -#define FMC_TIMEOUT_COUNT (0x4FFFFFFF) /* count to judge of FMC timeout */ +/* ~few seconds of polling at typical core clocks; old 0x4FFFFFFF could + * spin ~10+ minutes per stuck page erase and miss EMS reboot windows. + */ + +#define FMC_TIMEOUT_COUNT (0x000FFFFF) #endif /* __ARCH_ARM_SRC_GD32F4_HARDWARE_GD32F4XX_FMC_H */ From 64777d1b1831981ff013e8fd1b6447ca9e7d702e Mon Sep 17 00:00:00 2001 From: jacksonGreenStone <13117870426@163.com> Date: Fri, 11 Sep 2026 18:46:07 +0800 Subject: [PATCH 14/14] =?UTF-8?q?GD32F470=EF=BC=9A=E8=A1=A5=E5=85=85=20bri?= =?UTF-8?q?ngup/RTT=20=E7=9B=B8=E5=85=B3=E6=94=B9=E5=8A=A8=E5=B9=B6?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E5=88=B0=20jackson=20=E5=88=86=E6=94=AF?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 与 OVERWRITE å�Œåˆ†æ”¯å‰©ä½™æœ¬åœ°ä¿®æ”¹ä¸€å¹¶æ��交。 Co-authored-by: Cursor --- arch/arm/src/gd32f4/gd32f4xx_allocateheap.c | 13 +++++++++++-- arch/arm/src/gd32f4/gd32f4xx_idle.c | 8 +++++++- arch/arm/src/gd32f4/gd32f4xx_start.c | 11 +++++++++++ drivers/segger/config/SEGGER_RTT_Conf.h | 19 +++++++------------ drivers/segger/syslog_rtt.c | 10 ++++++++-- 5 files changed, 44 insertions(+), 17 deletions(-) diff --git a/arch/arm/src/gd32f4/gd32f4xx_allocateheap.c b/arch/arm/src/gd32f4/gd32f4xx_allocateheap.c index 7ad82170663af..fe1205a058aec 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_allocateheap.c +++ b/arch/arm/src/gd32f4/gd32f4xx_allocateheap.c @@ -84,9 +84,18 @@ #if defined(CONFIG_GD32F4_GD32F4XX) -/* Set the end of system SRAM */ +/* Set the end of system SRAM. + * + * GD32F470 has up to 512KB contiguous System SRAM (0x20000000..). + * Older NuttX defaults only covered the smaller F4xx window + * (SRAM_END=0x20020000 → ~40KB heap after BSS), which OOMs when + * Board1 bringup starts webui + pylon_bms + sim_sdk + sim_pcs. + * Prefer CONFIG_RAM_END from the board defconfig. + */ -# if defined(CONFIG_GD32F4_GD32F450) +# if defined(CONFIG_GD32F4_GD32F470) +# define SRAM_END CONFIG_RAM_END +# elif defined(CONFIG_GD32F4_GD32F450) # if defined(CONFIG_GD32F4_GD32F450XI) # define SRAM_END 0x20070000 # else diff --git a/arch/arm/src/gd32f4/gd32f4xx_idle.c b/arch/arm/src/gd32f4/gd32f4xx_idle.c index 2018cfa0353ac..fbca802f9bec0 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_idle.c +++ b/arch/arm/src/gd32f4/gd32f4xx_idle.c @@ -166,7 +166,13 @@ void up_idle(void) up_idlepm(); -#if !defined(CONFIG_DEBUG_SYMBOLS) + /* Skip WFI when debugging OR when board asks to keep peripherals awake. + * GD32 UART RX during WFI has dropped OTA/gs_raw frames on Board1 HIL + * unless a debugger session was attached. + */ + +#if !defined(CONFIG_DEBUG_SYMBOLS) && \ + !defined(CONFIG_GD32F4_DISABLE_IDLE_SLEEP_DURING_DEBUG) BEGIN_IDLE(); asm("WFI"); END_IDLE(); diff --git a/arch/arm/src/gd32f4/gd32f4xx_start.c b/arch/arm/src/gd32f4/gd32f4xx_start.c index c895d31938134..d7e6f80a05c2a 100644 --- a/arch/arm/src/gd32f4/gd32f4xx_start.c +++ b/arch/arm/src/gd32f4/gd32f4xx_start.c @@ -240,6 +240,17 @@ void __start(void) *dest++ = *src++; } +#ifdef CONFIG_ARCH_RAMFUNCS + /* Copy .ramfunc (FMC erase/program waiters) from FLASH load address to SRAM */ + + for (src = (const uint32_t *)_framfuncs, + dest = (uint32_t *)_sramfuncs; dest < (uint32_t *)_eramfuncs; + ) + { + *dest++ = *src++; + } +#endif + #ifdef CONFIG_ARMV7M_STACKCHECK arm_stack_check_init(); #endif diff --git a/drivers/segger/config/SEGGER_RTT_Conf.h b/drivers/segger/config/SEGGER_RTT_Conf.h index d1a0e56ae4611..e4d9611363217 100644 --- a/drivers/segger/config/SEGGER_RTT_Conf.h +++ b/drivers/segger/config/SEGGER_RTT_Conf.h @@ -115,20 +115,15 @@ extern ptrdiff_t g_segger_offset; #define SEGGER_RTT_IS_CONNECTED(ch) (SEGGER_RTT_RDOFF_UP(ch) != 0) #define SEGGER_RTT_IS_FIFO_MODE(ch) (SEGGER_RTT_FLAG_UP(ch) == SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL) -/* Determine whether JLink is connected, and use FIFO mode - * after connection to ensure that data is not lost. - */ +/* Never promote RTT to BLOCK_IF_FIFO_FULL. A brief J-Link/RTT attach sets + * RdOff != 0 ("connected"); the old helper then switched the up-buffer to + * blocking mode, and after the probe detached the MCU hung forever in + * _WriteBlocking once the 4KB buffer filled (OTA DATA ACK silence). + * Keep the configured non-blocking mode (NO_BLOCK_TRIM/SKIP). + */ #define SEGGER_RTT_BLOCK_IF_FIFO_FULL(ch) \ - do \ - { \ - if (!SEGGER_RTT_IS_FIFO_MODE(ch) && SEGGER_RTT_IS_CONNECTED(ch)) \ - { \ - SEGGER_RTT_SetFlagsUpBuffer(ch, SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL); \ - SEGGER_RTT_SetFlagsDownBuffer(ch, SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL); \ - } \ - } \ - while (0) + do { (void)(ch); } while (0) #define SEGGER_SYSVIEW_PRINTF_IMPLICIT_FORMAT 1 diff --git a/drivers/segger/syslog_rtt.c b/drivers/segger/syslog_rtt.c index 684d210d3caf7..ec5013915fb9e 100644 --- a/drivers/segger/syslog_rtt.c +++ b/drivers/segger/syslog_rtt.c @@ -34,7 +34,13 @@ int syslog_rtt_putc(FAR syslog_channel_t *channel, int ch) { - SEGGER_RTT_BLOCK_IF_FIFO_FULL(CONFIG_SYSLOG_RTT_CHANNEL); + /* Never block: OTA/RS485 paths call syslog while the bus thread must + * still ACK. With no RTT host, BLOCK_IF_FIFO_FULL hangs forever once + * the up-buffer is full (seen as DATA ACK silence after a few KB). + * Buffer mode is CONFIG_SEGGER_RTT_MODE_NO_BLOCK_TRIM — drop/trim. + */ + + (void)channel; SEGGER_RTT_PutChar(CONFIG_SYSLOG_RTT_CHANNEL, ch); return ch; } @@ -42,6 +48,6 @@ int syslog_rtt_putc(FAR syslog_channel_t *channel, int ch) ssize_t syslog_rtt_write(FAR syslog_channel_t *channel, FAR const char *buffer, size_t buflen) { - SEGGER_RTT_BLOCK_IF_FIFO_FULL(CONFIG_SYSLOG_RTT_CHANNEL); + (void)channel; return SEGGER_RTT_Write(CONFIG_SYSLOG_RTT_CHANNEL, buffer, buflen); }