This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 5c3c2cbf0a91426830810b29706558794b500a23
Author: leocafonso <[email protected]>
AuthorDate: Mon Oct 5 10:54:56 2026 -0700

    arch/arm/ra8m1: Add DTC and per-instance SCI FIFO/DTC/termios support
    
    Adds the Data Transfer Controller (DTC) as a generic register-triggered
    DMA engine (ra_dtc.c/.h, hardware/ra8m1_dtc.h, CONFIG_RA_DTC, normal
    mode only), and generalizes SCI_B's 16-stage FIFO and DTC support from
    SCI9-only to every instance (SCI0-4, SCI9): CONFIG_RA_SCIn_FIFO and
    CONFIG_RA_SCIn_TXDTC/RXDTC are mutually exclusive, not combinable --
    TX DTC moves queued ring-buffer data in the background through
    CONFIG_SERIAL_TXDMA, RX DTC moves each byte as it arrives, and measured
    on hardware the FIFO's standing multi-byte buffer gives far better
    high-baud overrun margin than this one-byte-per-activation RX DTC
    design, which also had a real arming bug fixed here (up_rxint()'s old
    "first enable" check could never fire, so the DTC was never armed),
    alongside a FIFO register-clear/ordering fix.  up_ioctl() also gains
    TCGETS/TCSETS (baud rate, parity, stop bits), board.h gains SCI0's
    GPIO pins (P609/P610) to exercise it on this board, and comments
    throughout ra_serial.c/Kconfig are condensed from the narrative
    debugging form they accumulated down to the essential fact each one
    needs.  Tested on hardware: SCI0 and SCI9 simultaneously, FIFO and DTC
    independently and mutually exclusive; FIFO (TTRG=15/RTRG=0) holds up
    cleanly to 3 Mbps for transfers within the RX ring buffer, well past
    RX DTC's ceiling (see below).
    
    A separate, more serious bug was also found and fixed while hardware-
    testing the overrun path at high baud: up_erinterrupt() never cleared
    its ICU IELSRn.IR flag (RA8M1 User's Manual section 13.5.1 is explicit
    that this causes the NVIC to re-enter the handler indefinitely), which
    livelocked the whole system on any receive error, in every
    configuration, not something specific to this series.  Fixed by
    clearing it like the other two SCI interrupt handlers already did, by
    draining RDR/the FIFO on an error before clearing it (SCIn_ERI fires
    instead of SCIn_RXI for every receive error, so data already received
    was otherwise never picked up), and by having the RX DTC path also
    recognize a byte stuck via CSR.RDRF directly, not just its own
    bookkeeping, since its event can be silently dropped by the ICU while
    an earlier byte's IR is still pending.  Also added CONFIG_SERIAL_
    TIOCGICOUNT (frame/overrun/parity counters via the standard
    TIOCGICOUNT ioctl), matching stm32's precedent, since nothing in the
    generic NuttX serial core surfaces a receive error to an application
    otherwise.  Confirmed on hardware: nsh on SCI9 stays fully responsive
    through a sustained 3 Mbps overrun on SCI0 that previously froze the
    whole board.
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
    Assisted-by: Claude:claude-sonnet-5
    Signed-off-by: leocafonso <[email protected]>
---
 arch/arm/Kconfig                        |   1 +
 arch/arm/src/ra8m1/CMakeLists.txt       |   4 +
 arch/arm/src/ra8m1/Kconfig              | 452 +++++++++++++++++
 arch/arm/src/ra8m1/Make.defs            |   4 +
 arch/arm/src/ra8m1/hardware/ra8m1_dtc.h | 173 +++++++
 arch/arm/src/ra8m1/ra_dtc.c             | 191 +++++++
 arch/arm/src/ra8m1/ra_dtc.h             | 182 +++++++
 arch/arm/src/ra8m1/ra_irq.c             |  12 +
 arch/arm/src/ra8m1/ra_serial.c          | 855 +++++++++++++++++++++++++++++++-
 9 files changed, 1868 insertions(+), 6 deletions(-)

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 080486cf963..b1b7a550b05 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -386,6 +386,7 @@ config ARCH_CHIP_RA8M1
        bool "Renesas RA8M1"
        select ARCH_CORTEXM85
        select ARCH_HAVE_FPU
+       select ARCH_HAVE_SERIAL_TERMIOS
        ---help---
                Renesas RA8M1 Family (ARM Cortex-M85).
 
diff --git a/arch/arm/src/ra8m1/CMakeLists.txt 
b/arch/arm/src/ra8m1/CMakeLists.txt
index 336fad5699c..8f679252baa 100644
--- a/arch/arm/src/ra8m1/CMakeLists.txt
+++ b/arch/arm/src/ra8m1/CMakeLists.txt
@@ -34,4 +34,8 @@ if(CONFIG_RA_GPT_TIMER)
   list(APPEND SRCS ra_gpt.c)
 endif()
 
+if(CONFIG_RA_DTC)
+  list(APPEND SRCS ra_dtc.c)
+endif()
+
 target_sources(arch PRIVATE ${SRCS})
diff --git a/arch/arm/src/ra8m1/Kconfig b/arch/arm/src/ra8m1/Kconfig
index 98ccd064aad..20fd2bf1345 100644
--- a/arch/arm/src/ra8m1/Kconfig
+++ b/arch/arm/src/ra8m1/Kconfig
@@ -349,36 +349,488 @@ config RA_SCI0_UART
        depends on RA_HAVE_SCI_B0
        select SCI0_SERIALDRIVER
 
+config RA_SCI0_FIFO
+       bool "Use SCI0's 16-stage FIFO"
+       default n
+       depends on RA_SCI0_UART
+       select RA_SCI_FIFO
+       ---help---
+               Switch SCI0 to its 16-stage FIFO (CCR3.FM=1): fewer interrupts
+               on a continuous stream, and enables the receive-timeout ("idle
+               line") event (SCIn_RXI fires on fewer-than-trigger-level bytes
+               within 15 ETUs of the last stop bit).
+
+               Mutually exclusive with CONFIG_RA_SCI0_TXDTC/RXDTC (see their 
help).
+
+if RA_SCI0_FIFO
+
+config RA_SCI0_FIFO_TXTRG
+       int "Transmit FIFO trigger level (FCR.TTRG)"
+       range 0 15
+       default 0
+       ---help---
+               SCIn_TXI fires once the transmit-FIFO's occupancy drops to this
+               level or below.  0 (the Renesas FSP default) waits for the FIFO
+               to empty completely before the next interrupt, for the fewest
+               interrupts on a continuous stream.
+
+config RA_SCI0_FIFO_RXTRG
+       int "Receive FIFO trigger level (FCR.RTRG)"
+       range 0 15
+       default 15
+       ---help---
+               SCIn_RXI fires once the receive-FIFO's occupancy reaches this
+               level.  The idle-line timeout only ever fires when 0 < count <
+               RTRG, so it never fires at RTRG 0 or 1.  Lower values give more
+               overrun margin (confirmed on hardware: RTRG=0 ran clean to
+               3 Mbps); higher values mean fewer interrupts but less margin --
+               the default of 15 is the worst setting for high-baud overrun
+               resistance.
+
+endif # RA_SCI0_FIFO
+
+config RA_SCI0_TXDTC
+       bool "Transmit SCI0 through the DTC"
+       default n
+       depends on RA_SCI0_UART && RA_DTC && !RA_SCI0_FIFO
+       select SERIAL_TXDMA
+       ---help---
+               Move transmit data out of the ring buffer into TDR through the
+               DTC in the background, instead of one byte per SCIn_TXI: one
+               completion interrupt per chunk, not per byte (up_dma_send_at()
+               in ra_serial.c).
+
+               Mutually exclusive with CONFIG_RA_SCI0_FIFO (see its help).
+
+config RA_SCI0_RXDTC
+       bool "Receive SCI0 through the DTC"
+       default n
+       depends on RA_SCI0_UART && RA_DTC && !RA_SCI0_FIFO
+       ---help---
+               Move each received byte out of RDR through the DTC as it
+               arrives: one DTC activation per byte, with a CPU interrupt on
+               every one (no interrupt-count reduction like TXDTC, since byte
+               arrival isn't known in advance).
+
+               Its register move avoids NVIC latency, but only while armed --
+               the gap between a byte being consumed and up_dma_rxarm()
+               re-arming is serviced by the CPU instead.  At very high baud
+               rates that gap can matter more than FIFO's standing multi-byte
+               margin: do not assume this beats CONFIG_RA_SCI0_FIFO with a low
+               receive trigger level.
+
+               Mutually exclusive with CONFIG_RA_SCI0_FIFO (see its help).
+
 config RA_SCI1_UART
        bool "UART 1"
        default n
        depends on RA_HAVE_SCI_B1
        select SCI1_SERIALDRIVER
 
+config RA_SCI1_FIFO
+       bool "Use SCI1's 16-stage FIFO"
+       default n
+       depends on RA_SCI1_UART
+       select RA_SCI_FIFO
+       ---help---
+               Switch SCI1 to its 16-stage FIFO (CCR3.FM=1): fewer interrupts
+               on a continuous stream, and enables the receive-timeout ("idle
+               line") event (SCIn_RXI fires on fewer-than-trigger-level bytes
+               within 15 ETUs of the last stop bit).
+
+               Mutually exclusive with CONFIG_RA_SCI1_TXDTC/RXDTC (see their 
help).
+
+if RA_SCI1_FIFO
+
+config RA_SCI1_FIFO_TXTRG
+       int "Transmit FIFO trigger level (FCR.TTRG)"
+       range 0 15
+       default 0
+       ---help---
+               SCIn_TXI fires once the transmit-FIFO's occupancy drops to this
+               level or below.  0 (the Renesas FSP default) waits for the FIFO
+               to empty completely before the next interrupt, for the fewest
+               interrupts on a continuous stream.
+
+config RA_SCI1_FIFO_RXTRG
+       int "Receive FIFO trigger level (FCR.RTRG)"
+       range 0 15
+       default 15
+       ---help---
+               SCIn_RXI fires once the receive-FIFO's occupancy reaches this
+               level.  The idle-line timeout only ever fires when 0 < count <
+               RTRG, so it never fires at RTRG 0 or 1.  Lower values give more
+               overrun margin (confirmed on hardware: RTRG=0 ran clean to
+               3 Mbps); higher values mean fewer interrupts but less margin --
+               the default of 15 is the worst setting for high-baud overrun
+               resistance.
+
+endif # RA_SCI1_FIFO
+
+config RA_SCI1_TXDTC
+       bool "Transmit SCI1 through the DTC"
+       default n
+       depends on RA_SCI1_UART && RA_DTC && !RA_SCI1_FIFO
+       select SERIAL_TXDMA
+       ---help---
+               Move transmit data out of the ring buffer into TDR through the
+               DTC in the background, instead of one byte per SCIn_TXI: one
+               completion interrupt per chunk, not per byte (up_dma_send_at()
+               in ra_serial.c).
+
+               Mutually exclusive with CONFIG_RA_SCI1_FIFO (see its help).
+
+config RA_SCI1_RXDTC
+       bool "Receive SCI1 through the DTC"
+       default n
+       depends on RA_SCI1_UART && RA_DTC && !RA_SCI1_FIFO
+       ---help---
+               Move each received byte out of RDR through the DTC as it
+               arrives: one DTC activation per byte, with a CPU interrupt on
+               every one (no interrupt-count reduction like TXDTC, since byte
+               arrival isn't known in advance).
+
+               Its register move avoids NVIC latency, but only while armed --
+               the gap between a byte being consumed and up_dma_rxarm()
+               re-arming is serviced by the CPU instead.  At very high baud
+               rates that gap can matter more than FIFO's standing multi-byte
+               margin: do not assume this beats CONFIG_RA_SCI1_FIFO with a low
+               receive trigger level.
+
+               Mutually exclusive with CONFIG_RA_SCI1_FIFO (see its help).
+
 config RA_SCI2_UART
        bool "UART 2"
        default n
        depends on RA_HAVE_SCI_B2
        select SCI2_SERIALDRIVER
 
+config RA_SCI2_FIFO
+       bool "Use SCI2's 16-stage FIFO"
+       default n
+       depends on RA_SCI2_UART
+       select RA_SCI_FIFO
+       ---help---
+               Switch SCI2 to its 16-stage FIFO (CCR3.FM=1): fewer interrupts
+               on a continuous stream, and enables the receive-timeout ("idle
+               line") event (SCIn_RXI fires on fewer-than-trigger-level bytes
+               within 15 ETUs of the last stop bit).
+
+               Mutually exclusive with CONFIG_RA_SCI2_TXDTC/RXDTC (see their 
help).
+
+if RA_SCI2_FIFO
+
+config RA_SCI2_FIFO_TXTRG
+       int "Transmit FIFO trigger level (FCR.TTRG)"
+       range 0 15
+       default 0
+       ---help---
+               SCIn_TXI fires once the transmit-FIFO's occupancy drops to this
+               level or below.  0 (the Renesas FSP default) waits for the FIFO
+               to empty completely before the next interrupt, for the fewest
+               interrupts on a continuous stream.
+
+config RA_SCI2_FIFO_RXTRG
+       int "Receive FIFO trigger level (FCR.RTRG)"
+       range 0 15
+       default 15
+       ---help---
+               SCIn_RXI fires once the receive-FIFO's occupancy reaches this
+               level.  The idle-line timeout only ever fires when 0 < count <
+               RTRG, so it never fires at RTRG 0 or 1.  Lower values give more
+               overrun margin (confirmed on hardware: RTRG=0 ran clean to
+               3 Mbps); higher values mean fewer interrupts but less margin --
+               the default of 15 is the worst setting for high-baud overrun
+               resistance.
+
+endif # RA_SCI2_FIFO
+
+config RA_SCI2_TXDTC
+       bool "Transmit SCI2 through the DTC"
+       default n
+       depends on RA_SCI2_UART && RA_DTC && !RA_SCI2_FIFO
+       select SERIAL_TXDMA
+       ---help---
+               Move transmit data out of the ring buffer into TDR through the
+               DTC in the background, instead of one byte per SCIn_TXI: one
+               completion interrupt per chunk, not per byte (up_dma_send_at()
+               in ra_serial.c).
+
+               Mutually exclusive with CONFIG_RA_SCI2_FIFO (see its help).
+
+config RA_SCI2_RXDTC
+       bool "Receive SCI2 through the DTC"
+       default n
+       depends on RA_SCI2_UART && RA_DTC && !RA_SCI2_FIFO
+       ---help---
+               Move each received byte out of RDR through the DTC as it
+               arrives: one DTC activation per byte, with a CPU interrupt on
+               every one (no interrupt-count reduction like TXDTC, since byte
+               arrival isn't known in advance).
+
+               Its register move avoids NVIC latency, but only while armed --
+               the gap between a byte being consumed and up_dma_rxarm()
+               re-arming is serviced by the CPU instead.  At very high baud
+               rates that gap can matter more than FIFO's standing multi-byte
+               margin: do not assume this beats CONFIG_RA_SCI2_FIFO with a low
+               receive trigger level.
+
+               Mutually exclusive with CONFIG_RA_SCI2_FIFO (see its help).
+
 config RA_SCI3_UART
        bool "UART 3"
        default n
        depends on RA_HAVE_SCI_B3
        select SCI3_SERIALDRIVER
 
+config RA_SCI3_FIFO
+       bool "Use SCI3's 16-stage FIFO"
+       default n
+       depends on RA_SCI3_UART
+       select RA_SCI_FIFO
+       ---help---
+               Switch SCI3 to its 16-stage FIFO (CCR3.FM=1): fewer interrupts
+               on a continuous stream, and enables the receive-timeout ("idle
+               line") event (SCIn_RXI fires on fewer-than-trigger-level bytes
+               within 15 ETUs of the last stop bit).
+
+               Mutually exclusive with CONFIG_RA_SCI3_TXDTC/RXDTC (see their 
help).
+
+if RA_SCI3_FIFO
+
+config RA_SCI3_FIFO_TXTRG
+       int "Transmit FIFO trigger level (FCR.TTRG)"
+       range 0 15
+       default 0
+       ---help---
+               SCIn_TXI fires once the transmit-FIFO's occupancy drops to this
+               level or below.  0 (the Renesas FSP default) waits for the FIFO
+               to empty completely before the next interrupt, for the fewest
+               interrupts on a continuous stream.
+
+config RA_SCI3_FIFO_RXTRG
+       int "Receive FIFO trigger level (FCR.RTRG)"
+       range 0 15
+       default 15
+       ---help---
+               SCIn_RXI fires once the receive-FIFO's occupancy reaches this
+               level.  The idle-line timeout only ever fires when 0 < count <
+               RTRG, so it never fires at RTRG 0 or 1.  Lower values give more
+               overrun margin (confirmed on hardware: RTRG=0 ran clean to
+               3 Mbps); higher values mean fewer interrupts but less margin --
+               the default of 15 is the worst setting for high-baud overrun
+               resistance.
+
+endif # RA_SCI3_FIFO
+
+config RA_SCI3_TXDTC
+       bool "Transmit SCI3 through the DTC"
+       default n
+       depends on RA_SCI3_UART && RA_DTC && !RA_SCI3_FIFO
+       select SERIAL_TXDMA
+       ---help---
+               Move transmit data out of the ring buffer into TDR through the
+               DTC in the background, instead of one byte per SCIn_TXI: one
+               completion interrupt per chunk, not per byte (up_dma_send_at()
+               in ra_serial.c).
+
+               Mutually exclusive with CONFIG_RA_SCI3_FIFO (see its help).
+
+config RA_SCI3_RXDTC
+       bool "Receive SCI3 through the DTC"
+       default n
+       depends on RA_SCI3_UART && RA_DTC && !RA_SCI3_FIFO
+       ---help---
+               Move each received byte out of RDR through the DTC as it
+               arrives: one DTC activation per byte, with a CPU interrupt on
+               every one (no interrupt-count reduction like TXDTC, since byte
+               arrival isn't known in advance).
+
+               Its register move avoids NVIC latency, but only while armed --
+               the gap between a byte being consumed and up_dma_rxarm()
+               re-arming is serviced by the CPU instead.  At very high baud
+               rates that gap can matter more than FIFO's standing multi-byte
+               margin: do not assume this beats CONFIG_RA_SCI3_FIFO with a low
+               receive trigger level.
+
+               Mutually exclusive with CONFIG_RA_SCI3_FIFO (see its help).
+
 config RA_SCI4_UART
        bool "UART 4"
        default n
        depends on RA_HAVE_SCI_B4
        select SCI4_SERIALDRIVER
 
+config RA_SCI4_FIFO
+       bool "Use SCI4's 16-stage FIFO"
+       default n
+       depends on RA_SCI4_UART
+       select RA_SCI_FIFO
+       ---help---
+               Switch SCI4 to its 16-stage FIFO (CCR3.FM=1): fewer interrupts
+               on a continuous stream, and enables the receive-timeout ("idle
+               line") event (SCIn_RXI fires on fewer-than-trigger-level bytes
+               within 15 ETUs of the last stop bit).
+
+               Mutually exclusive with CONFIG_RA_SCI4_TXDTC/RXDTC (see their 
help).
+
+if RA_SCI4_FIFO
+
+config RA_SCI4_FIFO_TXTRG
+       int "Transmit FIFO trigger level (FCR.TTRG)"
+       range 0 15
+       default 0
+       ---help---
+               SCIn_TXI fires once the transmit-FIFO's occupancy drops to this
+               level or below.  0 (the Renesas FSP default) waits for the FIFO
+               to empty completely before the next interrupt, for the fewest
+               interrupts on a continuous stream.
+
+config RA_SCI4_FIFO_RXTRG
+       int "Receive FIFO trigger level (FCR.RTRG)"
+       range 0 15
+       default 15
+       ---help---
+               SCIn_RXI fires once the receive-FIFO's occupancy reaches this
+               level.  The idle-line timeout only ever fires when 0 < count <
+               RTRG, so it never fires at RTRG 0 or 1.  Lower values give more
+               overrun margin (confirmed on hardware: RTRG=0 ran clean to
+               3 Mbps); higher values mean fewer interrupts but less margin --
+               the default of 15 is the worst setting for high-baud overrun
+               resistance.
+
+endif # RA_SCI4_FIFO
+
+config RA_SCI4_TXDTC
+       bool "Transmit SCI4 through the DTC"
+       default n
+       depends on RA_SCI4_UART && RA_DTC && !RA_SCI4_FIFO
+       select SERIAL_TXDMA
+       ---help---
+               Move transmit data out of the ring buffer into TDR through the
+               DTC in the background, instead of one byte per SCIn_TXI: one
+               completion interrupt per chunk, not per byte (up_dma_send_at()
+               in ra_serial.c).
+
+               Mutually exclusive with CONFIG_RA_SCI4_FIFO (see its help).
+
+config RA_SCI4_RXDTC
+       bool "Receive SCI4 through the DTC"
+       default n
+       depends on RA_SCI4_UART && RA_DTC && !RA_SCI4_FIFO
+       ---help---
+               Move each received byte out of RDR through the DTC as it
+               arrives: one DTC activation per byte, with a CPU interrupt on
+               every one (no interrupt-count reduction like TXDTC, since byte
+               arrival isn't known in advance).
+
+               Its register move avoids NVIC latency, but only while armed --
+               the gap between a byte being consumed and up_dma_rxarm()
+               re-arming is serviced by the CPU instead.  At very high baud
+               rates that gap can matter more than FIFO's standing multi-byte
+               margin: do not assume this beats CONFIG_RA_SCI4_FIFO with a low
+               receive trigger level.
+
+               Mutually exclusive with CONFIG_RA_SCI4_FIFO (see its help).
+
 config RA_SCI9_UART
        bool "UART 9"
        default n
        depends on RA_HAVE_SCI_B9
        select SCI9_SERIALDRIVER
 
+config RA_SCI_FIFO
+       bool
+       default n
+       ---help---
+               Hidden aggregate selected by any CONFIG_RA_SCIn_FIFO: lets
+               ra_serial.c's shared FIFO code use one compile-time guard
+               instead of checking every instance's own option.
+
+config RA_SCI9_FIFO
+       bool "Use SCI9's 16-stage FIFO"
+       default n
+       depends on RA_SCI9_UART
+       select RA_SCI_FIFO
+       ---help---
+               Switch SCI9 to its 16-stage FIFO (CCR3.FM=1): fewer interrupts
+               on a continuous stream, and enables the receive-timeout ("idle
+               line") event (SCIn_RXI fires on fewer-than-trigger-level bytes
+               within 15 ETUs of the last stop bit).
+
+               Mutually exclusive with CONFIG_RA_SCI9_TXDTC/RXDTC (see their 
help).
+
+if RA_SCI9_FIFO
+
+config RA_SCI9_FIFO_TXTRG
+       int "Transmit FIFO trigger level (FCR.TTRG)"
+       range 0 15
+       default 0
+       ---help---
+               SCIn_TXI fires once the transmit-FIFO's occupancy drops to this
+               level or below.  0 (the Renesas FSP default) waits for the FIFO
+               to empty completely before the next interrupt, for the fewest
+               interrupts on a continuous stream.
+
+config RA_SCI9_FIFO_RXTRG
+       int "Receive FIFO trigger level (FCR.RTRG)"
+       range 0 15
+       default 15
+       ---help---
+               SCIn_RXI fires once the receive-FIFO's occupancy reaches this
+               level.  The idle-line timeout only ever fires when 0 < count <
+               RTRG, so it never fires at RTRG 0 or 1.  Lower values give more
+               overrun margin (confirmed on hardware: RTRG=0 ran clean to
+               3 Mbps); higher values mean fewer interrupts but less margin --
+               the default of 15 is the worst setting for high-baud overrun
+               resistance.
+
+endif # RA_SCI9_FIFO
+
+config RA_SCI9_TXDTC
+       bool "Transmit SCI9 through the DTC"
+       default n
+       depends on RA_SCI9_UART && RA_DTC && !RA_SCI9_FIFO
+       select SERIAL_TXDMA
+       ---help---
+               Move transmit data out of the ring buffer into TDR through the
+               DTC in the background, instead of one byte per SCIn_TXI: one
+               completion interrupt per chunk, not per byte (up_dma_send_at()
+               in ra_serial.c).
+
+               Mutually exclusive with CONFIG_RA_SCI9_FIFO (see its help).
+
+config RA_SCI9_RXDTC
+       bool "Receive SCI9 through the DTC"
+       default n
+       depends on RA_SCI9_UART && RA_DTC && !RA_SCI9_FIFO
+       ---help---
+               Move each received byte out of RDR through the DTC as it
+               arrives: one DTC activation per byte, with a CPU interrupt on
+               every one (no interrupt-count reduction like TXDTC, since byte
+               arrival isn't known in advance).
+
+               Its register move avoids NVIC latency, but only while armed --
+               the gap between a byte being consumed and up_dma_rxarm()
+               re-arming is serviced by the CPU instead.  At very high baud
+               rates that gap can matter more than FIFO's standing multi-byte
+               margin: do not assume this beats CONFIG_RA_SCI9_FIFO with a low
+               receive trigger level.
+
+               Mutually exclusive with CONFIG_RA_SCI9_FIFO (see its help).
+
+config RA_DTC
+       bool "Data Transfer Controller (DTC)"
+       default n
+       ---help---
+               Enable the RA8M1 Data Transfer Controller.  The DTC offloads
+               repetitive, register-triggered data transfers (e.g. UART TX/RX)
+               from the CPU: the same ICU event that would otherwise interrupt
+               the CPU instead activates the DTC, which performs one transfer
+               and, once its transfer count is exhausted, still interrupts the
+               CPU normally.  Peripheral drivers that support it expose their
+               own per-channel options (e.g. RA_SCI9_TXDTC, RA_SCI9_RXDTC).
+
 config RA_GPT_TIMER
        bool "GPT timer (/dev/timerN)"
        default n
diff --git a/arch/arm/src/ra8m1/Make.defs b/arch/arm/src/ra8m1/Make.defs
index 20ee65eb375..26d3345e8d9 100644
--- a/arch/arm/src/ra8m1/Make.defs
+++ b/arch/arm/src/ra8m1/Make.defs
@@ -40,3 +40,7 @@ CHIP_CSRCS += ra_timerisr.c
 ifeq ($(CONFIG_RA_GPT_TIMER),y)
 CHIP_CSRCS += ra_gpt.c
 endif
+
+ifeq ($(CONFIG_RA_DTC),y)
+CHIP_CSRCS += ra_dtc.c
+endif
diff --git a/arch/arm/src/ra8m1/hardware/ra8m1_dtc.h 
b/arch/arm/src/ra8m1/hardware/ra8m1_dtc.h
new file mode 100644
index 00000000000..c7e888fc4a2
--- /dev/null
+++ b/arch/arm/src/ra8m1/hardware/ra8m1_dtc.h
@@ -0,0 +1,173 @@
+/****************************************************************************
+ * arch/arm/src/ra8m1/hardware/ra8m1_dtc.h
+ *
+ * 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_RA8M1_HARDWARE_RA8M1_DTC_H
+#define __ARCH_ARM_SRC_RA8M1_HARDWARE_RA8M1_DTC_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include "chip.h"
+#include "hardware/ra8m1_memorymap.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Register Offsets *********************************************************/
+
+#define R_DTC_DTCCR_OFFSET                  0x0000  /* DTC Control Register 
(8-bits) */
+#define R_DTC_DTCCR_NS_OFFSET               0x0000  /* DTC Control Register 
for Non Secure Region (8-bits) */
+#define R_DTC_DTCVBR_OFFSET                 0x0004  /* DTC Vector Base 
Register (32-bits) */
+#define R_DTC_DTCVBR_NS_OFFSET              0x0004  /* DTC Vector Base 
Register for Non Secure Region (32-bits) */
+#define R_DTC_DTCST_OFFSET                  0x000c  /* DTC Module Start 
Register (8-bits) */
+#define R_DTC_DTCSTS_OFFSET                 0x000e  /* DTC Status Register 
(16-bits) */
+#define R_DTC_DTCCR_S_OFFSET                0x0010  /* DTC Control Register 
for Secure Region (8-bits) */
+#define R_DTC_DTCCR_SEC_OFFSET              0x0010  /* DTC Control Register 
for Secure Region (8-bits) */
+#define R_DTC_DTCVBR_S_OFFSET               0x0014  /* DTC Vector Base 
Register for secure Region (32-bits) */
+#define R_DTC_DTCVBR_SEC_OFFSET             0x0014  /* DTC Vector Base 
Register for secure Region (32-bits) */
+#define R_DTC_DTEVR_OFFSET                  0x0020  /* DTC Error Vector 
Register (32-bits) */
+
+/* Register Addresses *******************************************************/
+
+/* DTC Registers */
+
+#define R_DTC_DTCCR                        (R_DTC_BASE + R_DTC_DTCCR_OFFSET)
+#define R_DTC_DTCCR_NS                     (R_DTC_BASE + R_DTC_DTCCR_NS_OFFSET)
+#define R_DTC_DTCVBR                       (R_DTC_BASE + R_DTC_DTCVBR_OFFSET)
+#define R_DTC_DTCVBR_NS                    (R_DTC_BASE + 
R_DTC_DTCVBR_NS_OFFSET)
+#define R_DTC_DTCST                        (R_DTC_BASE + R_DTC_DTCST_OFFSET)
+#define R_DTC_DTCSTS                       (R_DTC_BASE + R_DTC_DTCSTS_OFFSET)
+#define R_DTC_DTCCR_S                      (R_DTC_BASE + R_DTC_DTCCR_S_OFFSET)
+#define R_DTC_DTCCR_SEC                    (R_DTC_BASE + 
R_DTC_DTCCR_SEC_OFFSET)
+#define R_DTC_DTCVBR_S                     (R_DTC_BASE + R_DTC_DTCVBR_S_OFFSET)
+#define R_DTC_DTCVBR_SEC                   (R_DTC_BASE + 
R_DTC_DTCVBR_SEC_OFFSET)
+#define R_DTC_DTEVR                        (R_DTC_BASE + R_DTC_DTEVR_OFFSET)
+
+/* Register Bitfield Definitions ********************************************/
+
+/* DTC Control Register for Non Secure Region (8-bits) **********************/
+
+#define R_DTC_DTCCR_NS_RRS (1 <<  4)  /* 10: DTC Transfer Information Read 
Skip Enable. */
+
+/* DTC Vector Base Register for Non Secure Region (32-bits) *****************/
+
+#define R_DTC_DTCVBR_NS_DTCVBR_SHIFT (10)
+#define R_DTC_DTCVBR_NS_DTCVBR_MASK (0x3fffff)
+
+/* DTC Module Start Register (8-bits) ***************************************/
+
+#define R_DTC_DTCST_DTCST (1 <<  0)  /* 01: DTC Module Start */
+
+/* DTC Status Register (16-bits) ********************************************/
+
+#define R_DTC_DTCSTS_VECN_SHIFT (0)
+#define R_DTC_DTCSTS_VECN_MASK (0xff)
+#define R_DTC_DTCSTS_ACT (1 << 15)  /* 8000: DTC Active Flag */
+
+/* DTC Control Register for Secure Region (8-bits) **************************/
+
+#define R_DTC_DTCCR_S_RRSS (1 <<  4)  /* 10: DTC Transfer Information Read 
Skip Enable for secure */
+
+/* DTC Vector Base Register for secure Region (32-bits) *********************/
+
+#define R_DTC_DTCVBR_S_DTCVBRS_SHIFT (10)
+#define R_DTC_DTCVBR_S_DTCVBRS_MASK (0x3fffff)
+
+/* DTC Error Vector Register (32-bits) **************************************/
+
+#define R_DTC_DTEVR_DTEV_SHIFT (0)
+#define R_DTC_DTEVR_DTEV_MASK (0xff)
+#define R_DTC_DTEVR_DTEVSAM (1 <<  8)  /* 100: DTC Error Vector Number SA 
Monitor */
+#define R_DTC_DTEVR_DTESTA (1 << 16)   /* 10000: DTC Error Status Flag */
+
+/* DTC Vector Table Entry (32-bits) *****************************************
+ *
+ * One entry per IELSR vector number, at DTCVBR(_SEC) + 0x4 * vector.  Not a
+ * peripheral register: it is a plain 32-bit word the DTC reads out of SRAM
+ * (manual figure 17.3).  Bits 31:2 are the transfer information's start
+ * address (so it must be word-aligned); bit 1 is reserved; bit 0 selects
+ * privileged/unprivileged access for that vector's transfer.
+ */
+
+#define R_DTC_VECTBL_ADDR_MASK        (0xfffffffc)
+#define R_DTC_VECTBL_UNPRIVILEGED     (1 << 0)  /* 0: privileged, 1: 
unprivileged */
+
+/* The vector table must start on a 1024-byte boundary (manual 17.3.1); this
+ * is also the alignment DTCVBR(_SEC) itself is shifted for
+ * (R_DTC_DTCVBR_S_DTCVBRS_SHIFT above).
+ */
+
+#define R_DTC_VECTBL_ALIGN            (1024)
+
+/* DTC Transfer Information (16 bytes) **************************************
+ *
+ * Not peripheral registers either: MRA, MRB, SAR, DAR, CRA and CRB are the
+ * byte/halfword/word layout of one 16-byte block in SRAM that a vector
+ * table entry above points to (manual figure 17.4, and the "Base address:
+ * DTCVBR(_SEC) / Offset address: 0x03 + 0x4 x Vector number (Inaccessible
+ * directly from the CPU...)" wording under each of section 17.2.2-17.2.7 is
+ * this same block, described per-vector).  The CPU accesses these directly
+ * as normal SRAM; the DTC copies them to/from its internal registers.
+ */
+
+#define R_DTC_TRANSFER_INFO_MRB_OFFSET  0x02  /* DTC Mode Register B (8-bits) 
*/
+#define R_DTC_TRANSFER_INFO_MRA_OFFSET  0x03  /* DTC Mode Register A (8-bits) 
*/
+#define R_DTC_TRANSFER_INFO_SAR_OFFSET  0x04  /* Transfer Source Register 
(32-bits) */
+#define R_DTC_TRANSFER_INFO_DAR_OFFSET  0x08  /* Transfer Destination Register 
(32-bits) */
+#define R_DTC_TRANSFER_INFO_CRB_OFFSET  0x0c  /* Transfer Count Register B 
(16-bits) */
+#define R_DTC_TRANSFER_INFO_CRA_OFFSET  0x0e  /* Transfer Count Register A 
(16-bits) */
+
+#define R_DTC_TRANSFER_INFO_SIZE        0x10  /* One block per activation 
source */
+
+/* MRA (DTC Mode Register A) ************************************************/
+
+#define R_DTC_MRA_SM_SHIFT (2)
+#define R_DTC_MRA_SM_MASK (0x3)
+#  define R_DTC_MRA_SM_FIXED        (0 << R_DTC_MRA_SM_SHIFT)  /* SAR fixed 
(write-back skipped) */
+#  define R_DTC_MRA_SM_INCREMENT    (2 << R_DTC_MRA_SM_SHIFT)  /* SAR 
incremented after transfer */
+#  define R_DTC_MRA_SM_DECREMENT    (3 << R_DTC_MRA_SM_SHIFT)  /* SAR 
decremented after transfer */
+#define R_DTC_MRA_SZ_SHIFT (4)
+#define R_DTC_MRA_SZ_MASK (0x3)
+#  define R_DTC_MRA_SZ_BYTE         (0 << R_DTC_MRA_SZ_SHIFT)  /* 8-bit 
transfer */
+#  define R_DTC_MRA_SZ_HALFWORD     (1 << R_DTC_MRA_SZ_SHIFT)  /* 16-bit 
transfer */
+#  define R_DTC_MRA_SZ_WORD         (2 << R_DTC_MRA_SZ_SHIFT)  /* 32-bit 
transfer */
+#define R_DTC_MRA_MD_SHIFT (6)
+#define R_DTC_MRA_MD_MASK (0x3)
+#  define R_DTC_MRA_MD_NORMAL       (0 << R_DTC_MRA_MD_SHIFT)  /* Normal 
transfer mode */
+#  define R_DTC_MRA_MD_REPEAT       (1 << R_DTC_MRA_MD_SHIFT)  /* Repeat 
transfer mode */
+#  define R_DTC_MRA_MD_BLOCK        (2 << R_DTC_MRA_MD_SHIFT)  /* Block 
transfer mode */
+
+/* MRB (DTC Mode Register B) ************************************************/
+
+#define R_DTC_MRB_DM_SHIFT (2)
+#define R_DTC_MRB_DM_MASK (0x3)
+#  define R_DTC_MRB_DM_FIXED        (0 << R_DTC_MRB_DM_SHIFT)  /* DAR fixed 
(write-back skipped) */
+#  define R_DTC_MRB_DM_INCREMENT    (2 << R_DTC_MRB_DM_SHIFT)  /* DAR 
incremented after transfer */
+#  define R_DTC_MRB_DM_DECREMENT    (3 << R_DTC_MRB_DM_SHIFT)  /* DAR 
decremented after transfer */
+#define R_DTC_MRB_DTS   (1 << 4)                               /* 10: 
0=destination is repeat/block area, 1=source is */
+#define R_DTC_MRB_DISEL (1 << 5)                               /* 20: 
0=interrupt after count completes, 1=every transfer */
+#define R_DTC_MRB_CHNS  (1 << 6)                               /* 40: Chain 
transfer select */
+#define R_DTC_MRB_CHNE  (1 << 7)                               /* 80: Chain 
transfer enable */
+
+#endif /* __ARCH_ARM_SRC_RA8M1_HARDWARE_RA8M1_DTC_H */
diff --git a/arch/arm/src/ra8m1/ra_dtc.c b/arch/arm/src/ra8m1/ra_dtc.c
new file mode 100644
index 00000000000..10b2a14e0b7
--- /dev/null
+++ b/arch/arm/src/ra8m1/ra_dtc.c
@@ -0,0 +1,191 @@
+/****************************************************************************
+ * arch/arm/src/ra8m1/ra_dtc.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.
+ *
+ ****************************************************************************/
+
+/* Data Transfer Controller (DTC).
+ *
+ * The vector table is a plain array of 32-bit words in SRAM (RA8M1 User's
+ * Manual, figure 17.3), one per IELSR slot (0-95), pointed to by DTCVBR
+ * (and, since this port is a flat, no-TrustZone image that always uses the
+ * secure register/memory aliases, DTCVBR_SEC too -- both are programmed
+ * with the same address as a belt-and-braces measure: which one a given
+ * vector's DTC activation actually reads depends on that IELSR slot's
+ * security attribution, in ICU.ICUSARG/H/I, which this port does not
+ * currently touch, so its reset state is what decides).  Each entry's bits
+ * 31:2 are the start address of that vector's transfer information block
+ * (struct dtc_transfer_info_s, see ra_dtc.h); bit 0 selects
+ * privileged/unprivileged access -- always privileged here.
+ *
+ * DTC and DMAC share one module-stop bit, MSTPCRA.MSTPA22 (RA8M1 User's
+ * Manual 16.8/17.10; its own SVD-derived name in ra8m1_mstp.h even says
+ * "DMA Controller/Data Transfer Controller unit0 Module Stop").
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <arch/barriers.h>
+
+#include <nuttx/cache.h>
+#include <nuttx/irq.h>
+
+#include "arm_internal.h"
+#include "hardware/ra8m1_dtc.h"
+#include "hardware/ra8m1_icu.h"
+#include "hardware/ra8m1_mstp.h"
+#include "ra_dtc.h"
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* One 32-bit entry per IELSR slot.  1024-byte aligned, per manual 17.3.1;
+ * this is also the alignment DTCVBR(_SEC) itself is shifted for.
+ */
+
+static uint32_t g_dtc_vectbl[RA_IRQ_NEXTINT]
+  aligned_data(R_DTC_VECTBL_ALIGN);
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ra8m1_dtc_vectbl_entry
+ ****************************************************************************/
+
+uint32_t *ra8m1_dtc_vectbl_entry(uint32_t vectbl_base, uint8_t vector)
+{
+  return (uint32_t *)(vectbl_base + ((uint32_t)vector << 2));
+}
+
+/****************************************************************************
+ * Name: ra8m1_dtc_vectbl_set
+ ****************************************************************************/
+
+void ra8m1_dtc_vectbl_set(uint32_t vectbl_base, uint8_t vector,
+                          FAR struct dtc_transfer_info_s *info)
+{
+  DEBUGASSERT(((uintptr_t)info & 3) == 0);
+
+  *ra8m1_dtc_vectbl_entry(vectbl_base, vector) =
+    ((uint32_t)info) & R_DTC_VECTBL_ADDR_MASK;
+}
+
+/****************************************************************************
+ * Name: ra8m1_dtc_transfer_info
+ ****************************************************************************/
+
+FAR struct dtc_transfer_info_s *
+ra8m1_dtc_transfer_info(uint32_t vectbl_base, uint8_t vector)
+{
+  uint32_t entry = *ra8m1_dtc_vectbl_entry(vectbl_base, vector);
+
+  return (FAR struct dtc_transfer_info_s *)(entry & R_DTC_VECTBL_ADDR_MASK);
+}
+
+/****************************************************************************
+ * Name: ra_dtc_initialize
+ ****************************************************************************/
+
+void ra_dtc_initialize(void)
+{
+  uint32_t vectbl_addr = (uint32_t)g_dtc_vectbl;
+
+  DEBUGASSERT((vectbl_addr & (R_DTC_VECTBL_ALIGN - 1)) == 0);
+
+  /* Release the shared DMAC/DTC module-stop bit */
+
+  modifyreg32(R_MSTP_MSTPCRA, R_MSTP_MSTPCRA_MSTPA22, 0);
+  getreg32(R_MSTP_MSTPCRA);
+
+  /* Every entry starts unset (0): no vector activates the DTC until
+   * ra_dtc_configure() points it at a real transfer information block.
+   */
+
+  memset(g_dtc_vectbl, 0, sizeof(g_dtc_vectbl));
+  up_clean_dcache(vectbl_addr, vectbl_addr + sizeof(g_dtc_vectbl));
+
+  /* Program the vector table base.  See the file header comment on why
+   * both DTCVBR and DTCVBR_SEC get the same address.
+   */
+
+  putreg32(vectbl_addr, R_DTC_DTCVBR);
+  putreg32(vectbl_addr, R_DTC_DTCVBR_SEC);
+
+  /* Leave DTCCR.RRS (read-skip) at its reset value of 0: with read-skip
+   * enabled the DTC would reuse the previous transfer information instead
+   * of re-reading what ra_dtc_configure() just wrote.
+   */
+
+  UP_DSB();
+
+  putreg8(R_DTC_DTCST_DTCST, R_DTC_DTCST);
+}
+
+/****************************************************************************
+ * Name: ra_dtc_configure
+ ****************************************************************************/
+
+void ra_dtc_configure(uint8_t vector, FAR struct dtc_transfer_info_s *info)
+{
+  uint32_t vectbl_addr = (uint32_t)g_dtc_vectbl;
+  uintptr_t info_addr = (uintptr_t)info;
+
+  DEBUGASSERT(vector < RA_IRQ_NEXTINT);
+
+  ra8m1_dtc_vectbl_set(vectbl_addr, vector, info);
+
+  up_clean_dcache(info_addr, info_addr + sizeof(*info));
+  up_clean_dcache(vectbl_addr + ((uint32_t)vector << 2),
+                  vectbl_addr + ((uint32_t)vector << 2) + sizeof(uint32_t));
+
+  UP_DSB();
+}
+
+/****************************************************************************
+ * Name: ra_dtc_enable
+ ****************************************************************************/
+
+void ra_dtc_enable(uint8_t vector)
+{
+  DEBUGASSERT(vector < RA_IRQ_NEXTINT);
+
+  modifyreg32(R_ICU_IELSR(vector), 0, R_ICU_IELSR_DTCE);
+}
+
+/****************************************************************************
+ * Name: ra_dtc_disable
+ ****************************************************************************/
+
+void ra_dtc_disable(uint8_t vector)
+{
+  DEBUGASSERT(vector < RA_IRQ_NEXTINT);
+
+  modifyreg32(R_ICU_IELSR(vector), R_ICU_IELSR_DTCE, 0);
+}
diff --git a/arch/arm/src/ra8m1/ra_dtc.h b/arch/arm/src/ra8m1/ra_dtc.h
new file mode 100644
index 00000000000..0bfbe7b1889
--- /dev/null
+++ b/arch/arm/src/ra8m1/ra_dtc.h
@@ -0,0 +1,182 @@
+/****************************************************************************
+ * arch/arm/src/ra8m1/ra_dtc.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_RA8M1_RA_DTC_H
+#define __ARCH_ARM_SRC_RA8M1_RA_DTC_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+#include <nuttx/compiler.h>
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* One 16-byte transfer-information block (RA8M1 User's Manual, figure
+ * 17.4).  MRA, MRB, SAR, DAR, CRA and CRB are not peripheral registers:
+ * they are this exact byte/halfword/word layout in ordinary SRAM, which
+ * the DTC itself reads and writes.  Allocate one of these per DTC vector
+ * actually used (word-aligned; the natural alignment of this struct is 4
+ * bytes, since it is deliberately left unpacked -- see the note below),
+ * fill it the same way section 17.2.2-17.2.7 of the manual describes each
+ * field, and point a vector table entry at it with ra8m1_dtc_vectbl_set()
+ * -- before setting that vector's ICU.IELSRn.DTCE bit.
+ *
+ * This must NOT be a packed struct.  A packed attribute drops the type's
+ * alignment to 1, so a plain "static struct dtc_transfer_info_s foo;" can
+ * land on an odd address; ra8m1_dtc_vectbl_set() below only masks off the
+ * low 2 bits of that address before handing it to the DTC, so the DTC
+ * would then read/write the wrong 16 bytes.  The (1,1,1,1,4,4,2,2) layout
+ * below has no padding either way, so leaving it unpacked costs nothing
+ * and the offset asserts still guard the layout.
+ */
+
+struct dtc_transfer_info_s
+{
+  uint8_t  reserved[2];  /* +0x00: Reserved(0), write 0 */
+  uint8_t  mrb;          /* +0x02: DTC Mode Register B */
+  uint8_t  mra;          /* +0x03: DTC Mode Register A */
+  uint32_t sar;          /* +0x04: Transfer Source Register */
+  uint32_t dar;          /* +0x08: Transfer Destination Register */
+  uint16_t crb;          /* +0x0c: Transfer Count Register B */
+  uint16_t cra;          /* +0x0e: Transfer Count Register A */
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: ra8m1_dtc_vectbl_entry
+ *
+ * Description:
+ *   Address of the 32-bit vector table entry for DTC vector "vector" (an
+ *   IELSR slot number, 0-95), given the vector table's base address (the
+ *   value programmed into DTCVBR_SEC).
+ *
+ ****************************************************************************/
+
+uint32_t *ra8m1_dtc_vectbl_entry(uint32_t vectbl_base, uint8_t vector);
+
+/****************************************************************************
+ * Name: ra8m1_dtc_vectbl_set
+ *
+ * Description:
+ *   Point DTC vector "vector" at the transfer information block "info"
+ *   (privileged access).  "info" must be word-aligned.  Do this before
+ *   setting the vector's ICU.IELSRn.DTCE bit.
+ *
+ ****************************************************************************/
+
+void ra8m1_dtc_vectbl_set(uint32_t vectbl_base, uint8_t vector,
+                          FAR struct dtc_transfer_info_s *info);
+
+/****************************************************************************
+ * Name: ra8m1_dtc_transfer_info
+ *
+ * Description:
+ *   Dereference DTC vector "vector"'s table entry and return a pointer to
+ *   its transfer information block, as currently programmed with
+ *   ra8m1_dtc_vectbl_set().  Returns NULL if the entry is unset (0).
+ *
+ ****************************************************************************/
+
+FAR struct dtc_transfer_info_s *
+ra8m1_dtc_transfer_info(uint32_t vectbl_base, uint8_t vector);
+
+/****************************************************************************
+ * Name: ra_dtc_initialize
+ *
+ * Description:
+ *   One-time DTC bring-up: release the shared DMAC/DTC module-stop bit,
+ *   program the (secure) vector table base and start the DTC (DTCST.DTCST
+ *   = 1).  Called once from up_irqinitialize(), after ra_attach_icu() has
+ *   run.  Every DTC vector's table entry is 0 (unset) until a caller uses
+ *   ra_dtc_configure() on it.
+ *
+ ****************************************************************************/
+
+void ra_dtc_initialize(void);
+
+/****************************************************************************
+ * Name: ra_dtc_configure
+ *
+ * Description:
+ *   Point DTC vector "vector" (an IELSR slot number, 0-95) at the
+ *   caller-owned transfer information block "info", which must already be
+ *   filled in (mra/mrb/sar/dar/cra/crb) and stay valid and unmodified by
+ *   software until the transfer this arms has completed.  Cleans "info"
+ *   and its vector table entry from the data cache, if enabled, so the
+ *   DTC's view of SRAM is current.  Does not touch IELSRn.DTCE -- call
+ *   ra_dtc_enable() to actually arm the vector.
+ *
+ ****************************************************************************/
+
+void ra_dtc_configure(uint8_t vector, FAR struct dtc_transfer_info_s *info);
+
+/****************************************************************************
+ * Name: ra_dtc_enable
+ *
+ * Description:
+ *   Set IELSRn.DTCE for "vector": the next matching interrupt is serviced
+ *   by the DTC instead of the CPU.  Call ra_dtc_configure() on this vector
+ *   first.  The DTC clears DTCE itself once the transfer this arms
+ *   completes (normal/block mode) or on each activation (repeat mode with
+ *   MRB.DISEL set) -- the vector's normal CPU interrupt handler then runs
+ *   as usual on the next matching event.
+ *
+ ****************************************************************************/
+
+void ra_dtc_enable(uint8_t vector);
+
+/****************************************************************************
+ * Name: ra_dtc_disable
+ *
+ * Description:
+ *   Clear IELSRn.DTCE for "vector", handing its interrupt back to the CPU
+ *   immediately instead of waiting for the DTC to finish or exhaust its
+ *   count.
+ *
+ ****************************************************************************/
+
+void ra_dtc_disable(uint8_t vector);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ARCH_ARM_SRC_RA8M1_RA_DTC_H */
diff --git a/arch/arm/src/ra8m1/ra_irq.c b/arch/arm/src/ra8m1/ra_irq.c
index 9a555585bd2..36c4ed6cb75 100644
--- a/arch/arm/src/ra8m1/ra_irq.c
+++ b/arch/arm/src/ra8m1/ra_irq.c
@@ -37,6 +37,10 @@
 #include "arm_internal.h"
 #include "ra_icu.h"
 
+#ifdef CONFIG_RA_DTC
+#  include "ra_dtc.h"
+#endif
+
 /****************************************************************************
  * Pre-processor Definitions
  ****************************************************************************/
@@ -244,6 +248,14 @@ void up_irqinitialize(void)
 
   ra_attach_icu();
 
+#ifdef CONFIG_RA_DTC
+  /* Bring up the DTC after the ICU events are routed: ra_dtc_enable()
+   * arms DTC activation on an already-routed IELSR slot.
+   */
+
+  ra_dtc_initialize();
+#endif
+
   ra_prioritize_syscall(NVIC_SYSH_SVCALL_PRIORITY);
 
   ra_dumpnvic("initial", RA_IRQ_FIRST + 32);
diff --git a/arch/arm/src/ra8m1/ra_serial.c b/arch/arm/src/ra8m1/ra_serial.c
index 4fca4ba5588..e6808f307e6 100644
--- a/arch/arm/src/ra8m1/ra_serial.c
+++ b/arch/arm/src/ra8m1/ra_serial.c
@@ -55,6 +55,13 @@
 #include "ra_icu.h"
 #include "ra_gpio.h"
 
+#ifdef CONFIG_RA_DTC
+#  include <nuttx/cache.h>
+
+#  include "hardware/ra8m1_dtc.h"
+#  include "ra_dtc.h"
+#endif
+
 /****************************************************************************
  * Pre-processor Definitions
  ****************************************************************************/
@@ -285,6 +292,14 @@
  * Private Function Prototypes
  ****************************************************************************/
 
+/* Forward-declared so up_dma_send_at()'s prototype below shares the same
+ * file-scope tag as the real definition further down, instead of GCC
+ * implicitly declaring a distinct, incompatible one scoped to the
+ * prototype's parameter list.
+ */
+
+struct up_dev_s;
+
 static int up_setup(struct uart_dev_s *dev);
 static void up_shutdown(struct uart_dev_s *dev);
 static int up_attach(struct uart_dev_s *dev);
@@ -300,6 +315,12 @@ static void up_send(struct uart_dev_s *dev, int ch);
 static void up_txint(struct uart_dev_s *dev, bool enable);
 static bool up_txready(struct uart_dev_s *dev);
 static bool up_txempty(struct uart_dev_s *dev);
+#ifdef CONFIG_SERIAL_TXDMA
+static void up_dma_send_at(struct up_dev_s *priv, uintptr_t buffer,
+                            size_t length);
+static void up_dma_send(struct uart_dev_s *dev);
+static void up_dma_txavail(struct uart_dev_s *dev);
+#endif
 
 /****************************************************************************
  * Private Data
@@ -318,8 +339,34 @@ struct up_dev_s
   uint8_t parity;           /* 0=none, 1=odd, 2=even */
   uint8_t bits;             /* Number of bits (7, 8 or 9) */
   bool stopbits2;           /* true: Configure with 2 stop bits instead of 1 */
+#ifdef CONFIG_RA_SCI_FIFO
+  bool fifo;                /* true: 16-stage FIFO mode (CCR3.FM), not 1-stage 
*/
+  uint8_t ttrg;             /* FCR.TTRG when priv->fifo (0-15) */
+  uint8_t rtrg;             /* FCR.RTRG when priv->fifo (0-15) */
+#endif
+#ifdef CONFIG_RA_DTC
+  bool txdtc;               /* true: transmit through the DTC, not per-byte */
+  bool rxdtc;               /* true: receive through the DTC, not per-byte */
+  bool rxdtc_pending;       /* true: dtcrxbyte holds an unread DTC'd byte */
+  uint8_t dtcrxbyte;        /* Scratch destination for the RX DTC transfer */
+
+  /* This port's DTC transfer info: one for TX, one for RX -- both may be
+   * armed at once, on different IELSR vectors, so they cannot share one
+   * block.
+   */
+
+  struct dtc_transfer_info_s dtcinfo;
+  struct dtc_transfer_info_s dtcrxinfo;
+#endif
+#ifdef CONFIG_SERIAL_TIOCGICOUNT
+  struct serial_icounter_s icount;  /* Frame/overrun/parity error counts */
+#endif
 };
 
+#ifdef CONFIG_RA_DTC
+static void up_dma_rxarm(struct up_dev_s *priv);
+#endif
+
 static const struct uart_ops_s g_uart_ops =
 {
   .setup        = up_setup,
@@ -334,6 +381,10 @@ static const struct uart_ops_s g_uart_ops =
   .txint        = up_txint,
   .txready      = up_txready,
   .txempty      = up_txempty,
+#ifdef CONFIG_SERIAL_TXDMA
+  .dmasend      = up_dma_send,
+  .dmatxavail   = up_dma_txavail,
+#endif
 };
 
 /* I/O buffers */
@@ -376,6 +427,17 @@ static struct up_dev_s  g_uart0priv =
   .parity       = CONFIG_SCI0_PARITY,
   .bits         = CONFIG_SCI0_BITS,
   .stopbits2    = CONFIG_SCI0_2STOP,
+#ifdef CONFIG_RA_SCI0_FIFO
+  .fifo         = true,
+  .ttrg         = CONFIG_RA_SCI0_FIFO_TXTRG,
+  .rtrg         = CONFIG_RA_SCI0_FIFO_RXTRG,
+#endif
+#ifdef CONFIG_RA_SCI0_TXDTC
+  .txdtc        = true,
+#endif
+#ifdef CONFIG_RA_SCI0_RXDTC
+  .rxdtc        = true,
+#endif
 };
 
 static uart_dev_t g_uart0port =
@@ -408,6 +470,17 @@ static struct up_dev_s  g_uart1priv =
   .parity       = CONFIG_SCI1_PARITY,
   .bits         = CONFIG_SCI1_BITS,
   .stopbits2    = CONFIG_SCI1_2STOP,
+#ifdef CONFIG_RA_SCI1_FIFO
+  .fifo         = true,
+  .ttrg         = CONFIG_RA_SCI1_FIFO_TXTRG,
+  .rtrg         = CONFIG_RA_SCI1_FIFO_RXTRG,
+#endif
+#ifdef CONFIG_RA_SCI1_TXDTC
+  .txdtc        = true,
+#endif
+#ifdef CONFIG_RA_SCI1_RXDTC
+  .rxdtc        = true,
+#endif
 };
 
 static uart_dev_t  g_uart1port =
@@ -440,6 +513,17 @@ static struct up_dev_s  g_uart2priv =
   .parity       = CONFIG_SCI2_PARITY,
   .bits         = CONFIG_SCI2_BITS,
   .stopbits2    = CONFIG_SCI2_2STOP,
+#ifdef CONFIG_RA_SCI2_FIFO
+  .fifo         = true,
+  .ttrg         = CONFIG_RA_SCI2_FIFO_TXTRG,
+  .rtrg         = CONFIG_RA_SCI2_FIFO_RXTRG,
+#endif
+#ifdef CONFIG_RA_SCI2_TXDTC
+  .txdtc        = true,
+#endif
+#ifdef CONFIG_RA_SCI2_RXDTC
+  .rxdtc        = true,
+#endif
 };
 
 static uart_dev_t  g_uart2port =
@@ -472,6 +556,17 @@ static struct up_dev_s  g_uart3priv =
   .parity       = CONFIG_SCI3_PARITY,
   .bits         = CONFIG_SCI3_BITS,
   .stopbits2    = CONFIG_SCI3_2STOP,
+#ifdef CONFIG_RA_SCI3_FIFO
+  .fifo         = true,
+  .ttrg         = CONFIG_RA_SCI3_FIFO_TXTRG,
+  .rtrg         = CONFIG_RA_SCI3_FIFO_RXTRG,
+#endif
+#ifdef CONFIG_RA_SCI3_TXDTC
+  .txdtc        = true,
+#endif
+#ifdef CONFIG_RA_SCI3_RXDTC
+  .rxdtc        = true,
+#endif
 };
 
 static uart_dev_t  g_uart3port =
@@ -504,6 +599,17 @@ static struct up_dev_s  g_uart4priv =
   .parity       = CONFIG_SCI4_PARITY,
   .bits         = CONFIG_SCI4_BITS,
   .stopbits2    = CONFIG_SCI4_2STOP,
+#ifdef CONFIG_RA_SCI4_FIFO
+  .fifo         = true,
+  .ttrg         = CONFIG_RA_SCI4_FIFO_TXTRG,
+  .rtrg         = CONFIG_RA_SCI4_FIFO_RXTRG,
+#endif
+#ifdef CONFIG_RA_SCI4_TXDTC
+  .txdtc        = true,
+#endif
+#ifdef CONFIG_RA_SCI4_RXDTC
+  .rxdtc        = true,
+#endif
 };
 
 static uart_dev_t  g_uart4port =
@@ -536,6 +642,17 @@ static struct up_dev_s  g_uart9priv =
   .parity       = CONFIG_SCI9_PARITY,
   .bits         = CONFIG_SCI9_BITS,
   .stopbits2    = CONFIG_SCI9_2STOP,
+#ifdef CONFIG_RA_SCI9_FIFO
+  .fifo         = true,
+  .ttrg         = CONFIG_RA_SCI9_FIFO_TXTRG,
+  .rtrg         = CONFIG_RA_SCI9_FIFO_RXTRG,
+#endif
+#ifdef CONFIG_RA_SCI9_TXDTC
+  .txdtc        = true,
+#endif
+#ifdef CONFIG_RA_SCI9_RXDTC
+  .rxdtc        = true,
+#endif
 };
 
 static uart_dev_t  g_uart9port =
@@ -655,6 +772,18 @@ static void up_sci_config(struct up_dev_s *priv)
 
   regval &= ~(R_SCI_B_CCR3_MOD_MASK << R_SCI_B_CCR3_MOD_SHIFT);
   regval |= R_SCI_B_CCR3_MOD_ASYNCHRONOUS;
+
+#ifdef CONFIG_RA_SCI_FIFO
+  if (priv->fifo)
+    {
+      regval |= R_SCI_B_CCR3_FM;
+    }
+  else
+#endif
+    {
+      regval &= ~R_SCI_B_CCR3_FM;
+    }
+
   up_serialout(priv, R_SCI_B_CCR3_OFFSET, regval);
 
   /* Parity lives in CCR1.PE/CCR1.PM */
@@ -686,6 +815,42 @@ static void up_sci_config(struct up_dev_s *priv)
   regval |= ra_sci_baud_ccr2(RA_SCICLK_FREQUENCY, priv->baud);
   up_serialout(priv, R_SCI_B_CCR2_OFFSET, regval);
 
+#ifdef CONFIG_RA_SCI_FIFO
+  if (priv->fifo)
+    {
+      /* RA8M1 User's Manual Table 31.30, step 7: empty both FIFOs
+       * (TFRST/RFRST are write-1, self-clearing) and set the trigger
+       * levels in the same write.  DRES=0 routes the receive-timeout
+       * (idle-line) event to SCIn_RXI, same as a threshold hit, so
+       * up_rxinterrupt() needs only one code path.  RSTRG is left at 0
+       * (no hardware flow control).
+       *
+       * Must come after the CCR3 write that sets CCR3.FM: FCR silently
+       * fails to latch RTRG/TTRG if written too soon after CCR3 (needs
+       * the settling time the CCR2/CCR1 writes above provide anyway --
+       * matches Renesas FSP's r_sci_b_uart_config_set() ordering).
+       */
+
+      regval = R_SCI_B_FCR_TFRST | R_SCI_B_FCR_RFRST |
+               ((uint32_t)priv->ttrg << R_SCI_B_FCR_TTRG_SHIFT) |
+               ((uint32_t)priv->rtrg << R_SCI_B_FCR_RTRG_SHIFT);
+      up_serialout(priv, R_SCI_B_FCR_OFFSET, regval);
+
+      /* Table 31.30, step 11: drop any stale DR/BRK flags. */
+
+      up_serialout(priv, R_SCI_B_FFCLR_OFFSET, R_SCI_B_FFCLR_DRC);
+
+      /* CSR.TDRE/RDRF are the real SCIn_TXI/SCIn_RXI sources in FIFO
+       * mode and do not auto-clear; clear them through CFCLR here so
+       * the stale TDRE=1 left by TFRST above doesn't swallow the first
+       * 0->1 edge once up_send()'s initial fake-kick burst drains.
+       */
+
+      up_serialout(priv, R_SCI_B_CFCLR_OFFSET,
+                   R_SCI_B_CFCLR_TDREC | R_SCI_B_CFCLR_RDRFC);
+    }
+#endif
+
   /* Re-enable the channel: transmit/receive plus their interrupts */
 
   regval = (R_SCI_B_CCR0_TE | R_SCI_B_CCR0_RE | R_SCI_B_CCR0_TIE |
@@ -835,6 +1000,21 @@ static int up_attach(struct uart_dev_s *dev)
   up_enable_irq(priv->txirq);
   up_enable_irq(priv->erirq);
 
+#ifdef CONFIG_RA_DTC
+  if (priv->rxdtc)
+    {
+      /* One-time initial arm, before anything can be received.
+       * ra_dtc_initialize() has already run by now (from
+       * up_irqinitialize(), before any port's up_attach()), so the
+       * vector table this points into is valid.  up_rxint() never
+       * arms it (see there); up_rxdrain() does on every later drain.
+       */
+
+      priv->rxdtc_pending = false;
+      up_dma_rxarm(priv);
+    }
+#endif
+
   return ret;
 }
 
@@ -850,6 +1030,77 @@ static void up_detach(struct uart_dev_s *dev)
   irq_detach(priv->erirq);
 }
 
+/****************************************************************************
+ * Name: up_rxdrain
+ *
+ * Description:
+ *   Deliver whatever is currently available to the upper half and leave
+ *   the receive path ready for more.  Shared by up_rxinterrupt() (a
+ *   genuine SCIn_RXI) and up_erinterrupt() (SCIn_ERI, which fires instead
+ *   of SCIn_RXI for every receive error -- RA8M1 User's Manual section
+ *   31.3.9 -- so without this, data already sitting in RDR/the FIFO when
+ *   an error occurs is never picked up at all).
+ *
+ ****************************************************************************/
+
+static void up_rxdrain(struct up_dev_s *priv, struct uart_dev_s *dev)
+{
+#ifdef CONFIG_RA_DTC
+  if (priv->rxdtc && !priv->rxdtc_pending)
+    {
+      uint8_t vector = priv->rxirq - RA_IRQ_FIRST;
+
+      if ((getreg32(R_ICU_IELSR(vector)) & R_ICU_IELSR_DTCE) == 0)
+        {
+          /* DTCE clear means the DTC already moved a byte into
+           * dtcrxbyte, whether this drain was triggered by that
+           * byte's own SCIn_RXI or by a later SCIn_ERI that happened
+           * to be serviced first.  Claimed exactly once, here, so the
+           * other caller can't also claim (and so double-deliver) it.
+           */
+
+          priv->rxdtc_pending = true;
+        }
+    }
+#endif
+
+  uart_recvchars(dev);
+
+#ifdef CONFIG_RA_DTC
+  if (priv->rxdtc)
+    {
+      /* Safe to call unconditionally: up_dma_rxarm() no-ops if DTCE is
+       * already 1.
+       */
+
+      up_dma_rxarm(priv);
+    }
+#endif
+
+#ifdef CONFIG_RA_SCI_FIFO
+  if (priv->fifo)
+    {
+      /* uart_recvchars() above just drained the FIFO to FRSR.R == 0.
+       * Clearing FRSR.DR unconditionally here is safe (it stays set
+       * until both the drain and this write happen -- RA8M1 User's
+       * Manual section 31.2.19) and harmless if DR wasn't the cause.
+       */
+
+      up_serialout(priv, R_SCI_B_FFCLR_OFFSET, R_SCI_B_FFCLR_DRC);
+
+      /* CSR.RDRF is the real SCIn_RXI source in FIFO mode too (FRSR.R
+       * is just a byte counter) and reading RDR doesn't clear it the
+       * way it does in non-FIFO mode -- only CFCLR does (section
+       * 31.2.9; matches Renesas FSP's sci_b_uart_rxi_isr()).  Leaving
+       * it set would latch out the next 0->1 edge and SCIn_RXI would
+       * never fire again.
+       */
+
+      up_serialout(priv, R_SCI_B_CFCLR_OFFSET, R_SCI_B_CFCLR_RDRFC);
+    }
+#endif
+}
+
 /****************************************************************************
  * Name: up_rxinterrupt
  *
@@ -860,10 +1111,13 @@ static void up_detach(struct uart_dev_s *dev)
 
 static int up_rxinterrupt(int irq, void *context, void *arg)
 {
-  struct uart_dev_s *dev = (struct uart_dev_s *)arg;
+  struct uart_dev_s *dev  = (struct uart_dev_s *)arg;
+  struct up_dev_s   *priv = (struct up_dev_s *)dev->priv;
 
   ra_clear_ir(irq);
-  uart_recvchars(dev);
+
+  up_rxdrain(priv, dev);
+
   return OK;
 }
 
@@ -878,9 +1132,121 @@ static int up_rxinterrupt(int irq, void *context, void 
*arg)
 static int up_txinterrupt(int irq, void *context, void *arg)
 {
   struct uart_dev_s *dev = (struct uart_dev_s *)arg;
+#if defined(CONFIG_SERIAL_TXDMA) || defined(CONFIG_RA_SCI_FIFO)
+  struct up_dev_s *priv = (struct up_dev_s *)dev->priv;
+#endif
 
   ra_clear_ir(irq);
-  uart_xmitchars(dev);
+
+#ifdef CONFIG_RA_SCI_FIFO
+  if (priv->fifo)
+    {
+      /* CSR.TDRE is the real SCIn_TXI source in FIFO mode too (FTSR.T is
+       * just a byte counter) and doesn't auto-clear -- only CFCLR does
+       * (section 31.2.9).  Must run BEFORE the fill below: clearing
+       * after could erase an edge the fill itself just caused (e.g. at
+       * TTRG=15, one byte refills the FIFO straight back past the
+       * threshold).  Clearing first loses nothing, since any later drop
+       * below TTRG still latches TDRE fresh.
+       */
+
+      up_serialout(priv, R_SCI_B_CFCLR_OFFSET, R_SCI_B_CFCLR_TDREC);
+    }
+#endif
+
+#ifdef CONFIG_SERIAL_TXDMA
+  if (priv->txdtc)
+    {
+      /* dev->dmatx.length is 0 the first time TXI fires after enabling
+       * TIE (up_txint()/up_dma_txavail() already tried
+       * uart_xmitchars_dma() directly), and the length last armed
+       * otherwise -- DTC normal mode has moved every byte of it by now.
+       */
+
+      bool chained = false;
+
+      if (dev->dmatx.length != 0)
+        {
+          dev->dmatx.nbytes += dev->dmatx.length;
+
+          if (dev->dmatx.nlength != 0)
+            {
+              /* uart_xmitchars_dma() split a wrapped ring buffer into
+               * this (now done) segment and a second one at the start
+               * of the buffer (dmatx.nbuffer/nlength).  Chain into it
+               * here, exactly once -- without this, a wrapping transfer
+               * silently drops everything past the wrap.
+               *
+               * dev->dmatx.buffer is deliberately left pointing at this
+               * segment, not the second one: uart_xmitchars_done() below
+               * compares it against dev->xmit.tail to decide whether to
+               * advance it (serial_dma.c), and that must stay true for
+               * BOTH segments until done() finally runs for them
+               * together.  dmatx.length is updated so the next
+               * completion's nbytes accounting matches this segment,
+               * not the first one again.
+               */
+
+              FAR char *nbuffer = dev->dmatx.nbuffer;
+              size_t nlength = dev->dmatx.nlength;
+
+              dev->dmatx.length  = nlength;
+              dev->dmatx.nbuffer = NULL;
+              dev->dmatx.nlength = 0;
+
+              up_dma_send_at(priv, (uintptr_t)nbuffer, nlength);
+              chained = true;
+            }
+          else
+            {
+              uart_xmitchars_done(dev);
+            }
+        }
+
+      if (!chained)
+        {
+          uart_xmitchars_dma(dev);
+
+          if (dev->dmatx.length == 0)
+            {
+              /* Nothing armed: the ring buffer was empty.  up_txint()
+               * never touches CCR0.TIE for a txdtc port (see there), so
+               * this is the only place that clears it once the last
+               * transfer drains; up_dma_send_at() is the only place
+               * that sets it again.
+               */
+
+              uint32_t regval = up_serialin(priv, R_SCI_B_CCR0_OFFSET);
+
+              regval &= ~R_SCI_B_CCR0_TIE;
+              up_serialout(priv, R_SCI_B_CCR0_OFFSET, regval);
+            }
+        }
+    }
+  else
+#endif
+    {
+      uart_xmitchars(dev);
+    }
+
+#ifdef CONFIG_RA_SCI_FIFO
+  if (priv->fifo)
+    {
+      if (dev->xmit.head == dev->xmit.tail)
+        {
+          /* Nothing left to send, so TIE serves no purpose: disable
+           * it here rather than leave it armed for no reason.
+           * up_txint(true) re-enables it the next time there's
+           * something to send.
+           */
+
+          uint32_t regval = up_serialin(priv, R_SCI_B_CCR0_OFFSET);
+
+          regval &= ~R_SCI_B_CCR0_TIE;
+          up_serialout(priv, R_SCI_B_CCR0_OFFSET, regval);
+        }
+    }
+#endif
 
   return OK;
 }
@@ -902,10 +1268,43 @@ static int up_erinterrupt(int irq, void *context, void 
*arg)
   DEBUGASSERT(dev != NULL && dev->priv != NULL);
   priv = (struct up_dev_s *)dev->priv;
 
+  /* IELSRn.IR must be cleared by software or the NVIC re-enters this
+   * handler indefinitely (RA8M1 User's Manual section 13.5.1).
+   */
+
+  ra_clear_ir(irq);
+
   /* Save for error reporting */
 
   priv->sr = up_serialin(priv, R_SCI_B_CSR_OFFSET) & SCI_UART_ERR_BITS;
 
+#ifdef CONFIG_SERIAL_TIOCGICOUNT
+  if (priv->sr & R_SCI_B_CSR_ORER)
+    {
+      priv->icount.overrun++;
+    }
+
+  if (priv->sr & R_SCI_B_CSR_PER)
+    {
+      priv->icount.parity++;
+    }
+
+  if (priv->sr & R_SCI_B_CSR_FER)
+    {
+      priv->icount.frame++;
+    }
+#endif
+
+  /* Receive errors report only through SCIn_ERI, never SCIn_RXI (RA8M1
+   * User's Manual section 31.3.9): drain whatever is already in RDR/the
+   * FIFO now, through the same path up_rxinterrupt() uses, or it is
+   * never picked up and blocks all further reception -- reception
+   * cannot resume while CSR.ORER stays set, and clearing it below does
+   * not by itself free up a byte still sitting unread in RDR/the FIFO.
+   */
+
+  up_rxdrain(priv, dev);
+
   /* Errors are cleared through the dedicated CFCLR register, not by
    * writing CSR directly.
    */
@@ -929,6 +1328,111 @@ static int up_ioctl(struct file *filep, int cmd, 
unsigned long arg)
 {
   int ret = -ENOTTY;
 
+#if defined(CONFIG_SERIAL_TERMIOS) || defined(CONFIG_SERIAL_TIOCGICOUNT)
+  struct inode      *inode = filep->f_inode;
+  struct uart_dev_s *dev   = inode->i_private;
+  struct up_dev_s   *priv  = (struct up_dev_s *)dev->priv;
+#endif
+
+  switch (cmd)
+    {
+#ifdef CONFIG_SERIAL_TERMIOS
+      case TCGETS:
+        {
+          struct termios *termiosp = (struct termios *)(uintptr_t)arg;
+
+          if (!termiosp)
+            {
+              ret = -EINVAL;
+              break;
+            }
+
+          /* Word length (priv->bits) is fixed by CONFIG_SCIn_BITS;
+           * TCSETS rejects changing it.  9-bit has no POSIX CSIZE
+           * encoding, so report CS8 for it rather than leave this
+           * meaningless.
+           */
+
+          termiosp->c_cflag = (priv->bits == 7 ? CS7 : CS8) |
+                               (priv->parity != 0 ? PARENB : 0) |
+                               (priv->parity == 1 ? PARODD : 0) |
+                               (priv->stopbits2 ? CSTOPB : 0);
+
+          cfsetispeed(termiosp, priv->baud);
+
+          ret = OK;
+        }
+        break;
+
+      case TCSETS:
+        {
+          struct termios *termiosp = (struct termios *)(uintptr_t)arg;
+          uint8_t         want_bits;
+
+          if (!termiosp)
+            {
+              ret = -EINVAL;
+              break;
+            }
+
+          /* Reject a word-length change; FIFO/DTC setup is fixed at
+           * build time and not revisited here either.
+           */
+
+          want_bits = (termiosp->c_cflag & CSIZE) == CS7 ? 7 : 8;
+          if (want_bits != priv->bits)
+            {
+              ret = -EINVAL;
+              break;
+            }
+
+          if (termiosp->c_cflag & PARENB)
+            {
+              priv->parity = (termiosp->c_cflag & PARODD) ? 1 : 2;
+            }
+          else
+            {
+              priv->parity = 0;
+            }
+
+          priv->stopbits2 = (termiosp->c_cflag & CSTOPB) != 0;
+          priv->baud      = cfgetispeed(termiosp);
+
+          /* Apply immediately (TCSADRAIN/TCSAFLUSH not implemented,
+           * same as every other NuttX serial driver's TCSETS); this
+           * briefly disables CCR0.TE/RE, so a transfer in flight can
+           * be truncated.
+           */
+
+          up_sci_config(priv);
+
+          ret = OK;
+        }
+        break;
+#endif
+
+#ifdef CONFIG_SERIAL_TIOCGICOUNT
+      case TIOCGICOUNT:
+        {
+          struct serial_icounter_s *icount =
+            (struct serial_icounter_s *)(uintptr_t)arg;
+
+          if (!icount)
+            {
+              ret = -EINVAL;
+              break;
+            }
+
+          memcpy(icount, &priv->icount, sizeof(struct serial_icounter_s));
+          ret = OK;
+        }
+        break;
+#endif
+
+      default:
+        break;
+    }
+
   return ret;
 }
 
@@ -951,6 +1455,37 @@ static int up_receive(struct uart_dev_s *dev, unsigned 
int *status)
   *status   = priv->sr;
   priv->sr  = 0;
 
+#ifdef CONFIG_RA_DTC
+  if (priv->rxdtc)
+    {
+      if (!priv->rxdtc_pending)
+        {
+          /* Reached only via the CSR.RDRF fallback in up_rxavailable():
+           * the DTC never actually moved this byte.  Read it directly.
+           */
+
+          return (int)(up_serialin(priv, R_SCI_B_RDR_OFFSET) &
+                       R_SCI_B_RDR_RDAT_MASK & 0xff);
+        }
+
+      /* The DTC writes this byte directly to SRAM, bypassing the CPU
+       * cache: invalidate before reading it back.  Only safe as-is
+       * while CONFIG_ARMV8M_DCACHE is unset (true for every config
+       * tested so far) -- dtcrxbyte shares a cache line with CPU-
+       * written fields (e.g. rxdtc_pending), so invalidating it would
+       * also discard those writes (TODO: give dtcrxbyte its own cache
+       * line before enabling dcache here).
+       */
+
+      uintptr_t byteaddr = (uintptr_t)&priv->dtcrxbyte;
+
+      up_invalidate_dcache(byteaddr, byteaddr + sizeof(priv->dtcrxbyte));
+      priv->rxdtc_pending = false;
+
+      return (int)priv->dtcrxbyte;
+    }
+#endif
+
   /* Then return the actual received byte.  RDR.RDAT is 9 bits wide, but
    * only the low 8 matter for byte-oriented (7/8-bit) transfers.
    */
@@ -975,6 +1510,26 @@ static void up_rxint(struct uart_dev_s *dev, bool enable)
   irqstate_t flags;
 
   flags = enter_critical_section();
+
+#ifdef CONFIG_RA_DTC
+  if (priv->rxdtc)
+    {
+      /* uart_readv()'s blocking-read path toggles this on every read
+       * that finds the ring buffer empty, purely to make that check
+       * atomic -- not because it wants reception actually stopped.  A
+       * complete no-op here: the DTC stays armed, and RIE/DTCE/
+       * rxdtc_pending are left exactly as up_attach()/up_rxdrain()
+       * maintain them.  (Previously this disarmed/
+       * re-armed the DTC on every toggle, which could drop a byte
+       * in flight mid-toggle -- confirmed on hardware as pasted input
+       * getting stuck after one byte.)
+       */
+
+      leave_critical_section(flags);
+      return;
+    }
+#endif
+
   if (enable)
     {
 #ifndef CONFIG_SUPPRESS_SERIAL_INTS
@@ -983,7 +1538,6 @@ static void up_rxint(struct uart_dev_s *dev, bool enable)
       regval  = up_serialin(priv, R_SCI_B_CCR0_OFFSET);
       regval |= (R_SCI_B_CCR0_RIE);
       up_serialout(priv, R_SCI_B_CCR0_OFFSET, regval);
-
 #endif
     }
   else
@@ -1010,6 +1564,35 @@ static bool up_rxavailable(struct uart_dev_s *dev)
 {
   struct up_dev_s *priv = (struct up_dev_s *)dev->priv;
 
+#ifdef CONFIG_RA_DTC
+  if (priv->rxdtc)
+    {
+      /* rxdtc_pending covers the normal case (DTC already moved the
+       * byte).  CSR.RDRF covers a byte the DTC never touched at all,
+       * e.g. one whose own event was ignored because IR was already 1
+       * from the byte before it (RA8M1 User's Manual Table 13.5, note 2).
+       */
+
+      return priv->rxdtc_pending ||
+             (up_serialin(priv, R_SCI_B_CSR_OFFSET) & R_SCI_B_CSR_RDRF) != 0;
+    }
+#endif
+
+#ifdef CONFIG_RA_SCI_FIFO
+  if (priv->fifo)
+    {
+      /* CSR.RDRF does not apply in FIFO mode; FRSR.R is the receive-FIFO
+       * byte count.  This also covers the receive-timeout ("idle line")
+       * case: FRSR.DR only ever sets when FRSR.R is already nonzero (the
+       * whole point of the timeout is that fewer bytes than the trigger
+       * level arrived), so checking R here answers both causes at once.
+       */
+
+      return ((up_serialin(priv, R_SCI_B_FRSR_OFFSET) >>
+               R_SCI_B_FRSR_R_SHIFT) & R_SCI_B_FRSR_R_MASK) != 0;
+    }
+#endif
+
   return (up_serialin(priv,
                          R_SCI_B_CSR_OFFSET) & R_SCI_B_CSR_RDRF) ==
          R_SCI_B_CSR_RDRF;
@@ -1046,6 +1629,33 @@ static void up_txint(struct uart_dev_s *dev, bool enable)
 
   irqstate_t flags;
 
+#ifdef CONFIG_SERIAL_TXDMA
+  if (priv->txdtc)
+    {
+      /* CCR0.TIE is not a generic interrupt mask for a txdtc port: in
+       * non-FIFO mode, TIE=0 stops the SCI from generating SCIn_TXI at
+       * all (section 31.12.2(1)), and the DTC's activation (IELSRn.
+       * DTCE) feeds off that same event.  The generic serial core calls
+       * uart_disabletxint() assuming disabling is always harmless, but
+       * here it can permanently silence a DTC transfer still waiting on
+       * a future TDR->TSR edge (confirmed on hardware: hung nsh's
+       * multi-write output partway through).
+       *
+       * So enable only attempts a DMA kick (up_dma_txavail() no-ops if
+       * a transfer is already in flight) and disable does nothing.
+       * up_dma_send_at() is the only place that sets TIE; the idle
+       * branch of up_txinterrupt() is the only place that clears it.
+       */
+
+      if (enable)
+        {
+          up_dma_txavail(dev);
+        }
+
+      return;
+    }
+#endif
+
   flags = enter_critical_section();
   if (enable)
     {
@@ -1060,6 +1670,18 @@ static void up_txint(struct uart_dev_s *dev, bool enable)
        * interrupts disabled (note this may recurse).
        */
 
+#ifdef CONFIG_RA_SCI_FIFO
+      if (priv->fifo)
+        {
+          /* Clear any stale TDRE latched while TIE was off before this
+           * fake kick fills the FIFO, not after -- same reasoning as
+           * up_txinterrupt()'s TDREC clear.
+           */
+
+          up_serialout(priv, R_SCI_B_CFCLR_OFFSET, R_SCI_B_CFCLR_TDREC);
+        }
+
+#endif
       uart_xmitchars(dev);
 
 #endif
@@ -1087,8 +1709,24 @@ static void up_txint(struct uart_dev_s *dev, bool enable)
 static bool up_txready(struct uart_dev_s *dev)
 {
   struct up_dev_s   *priv   = (struct up_dev_s *)dev->priv;
-  bool              ret     =
-    ((up_serialin(priv,
+  bool              ret;
+
+#ifdef CONFIG_RA_SCI_FIFO
+  if (priv->fifo)
+    {
+      /* CSR.TDRE means "count at or below TTRG" in FIFO mode, a
+       * threshold, not "any room at all": use the transmit-FIFO count
+       * against its 16-entry depth instead.
+       */
+
+      uint32_t used = (up_serialin(priv, R_SCI_B_FTSR_OFFSET) >>
+                       R_SCI_B_FTSR_T_SHIFT) & R_SCI_B_FTSR_T_MASK;
+
+      return used < 16;
+    }
+#endif
+
+  ret = ((up_serialin(priv,
                      R_SCI_B_CSR_OFFSET) & R_SCI_B_CSR_TDRE) ==
      R_SCI_B_CSR_TDRE);
 
@@ -1114,6 +1752,211 @@ static bool up_txempty(struct uart_dev_s *dev)
   return ret;
 }
 
+/****************************************************************************
+ * Name: up_dma_send_at
+ *
+ * Description:
+ *   Send "length" bytes starting at "buffer", for a port with priv->txdtc
+ *   set.  Callers: up_dma_send() (fresh send) and up_txinterrupt()'s
+ *   wrap-chain case.
+ *
+ *   Takes buffer/length as plain arguments, rather than reading
+ *   dev->dmatx.buffer/length directly, so the wrap-chain case can send
+ *   dev->dmatx.nbuffer/nlength (the second half of a wrapped ring buffer)
+ *   without disturbing dev->dmatx.buffer -- uart_xmitchars_done() only
+ *   advances dev->xmit.tail while that still points at the first segment
+ *   (serial_dma.c).
+ *
+ *   Section 31.12.2(1) of the RA8M1 User's Manual: setting CCR0.TIE while
+ *   TE is already 1 does not itself raise SCIn_TXI, so arming the DTC
+ *   alone only works if a TDR->TSR edge is already pending or imminent.
+ *   This port always runs non-FIFO when txdtc is set, where CSR.TDRE is a
+ *   live level (true exactly when TDR is empty):
+ *
+ *   TDRE=1 (TDR empty, e.g. first send ever, or a fresh kick with nothing
+ *   in flight): arm the DTC for bytes[1:], THEN write byte[0] to TDR
+ *   directly -- DTCE must be set before that write, since TDR->TSR can
+ *   happen within a few bus cycles and a TXI that latches before DTCE is
+ *   set goes to the CPU instead of the DTC.
+ *
+ *   TDRE=0 (TDR holds a byte not yet at TSR -- always true when called
+ *   from up_txinterrupt(), since MRB.DISEL=0 means the CPU interrupt
+ *   fires on the DTC's final transfer, the one that just wrote that
+ *   pending byte into TDR, not the one reaching TSR -- section 13.2.14):
+ *   arm the DTC for the whole new buffer and skip the direct write; the
+ *   TDR->TSR edge already pending is what activates it.  Writing byte[0]
+ *   directly here instead would overwrite the still-pending byte.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_SERIAL_TXDMA
+static void up_dma_send_at(struct up_dev_s *priv, uintptr_t buffer,
+                            size_t length)
+{
+  struct dtc_transfer_info_s *info = &priv->dtcinfo;
+  uint8_t vector = priv->txirq - RA_IRQ_FIRST;
+  irqstate_t flags;
+  uint32_t regval;
+
+  DEBUGASSERT(priv->txdtc);
+  DEBUGASSERT(length > 0 && length <= 65536);
+
+  up_clean_dcache(buffer, buffer + length);
+
+  flags = enter_critical_section();
+
+  /* On a fresh send (uart_write() -> dmatxavail), callers reach this
+   * before uart_enabletxint(), so CCR0.TIE is still 0.  Set it here,
+   * before the TDR write below: section 31.12.2(1) gates SCIn_TXI on
+   * TIE being 1 at the moment TDRE rises, not retroactively, so setting
+   * TIE after the write would miss the edge and leave the DTC armed but
+   * never activated.  (Already 1 by the time up_txinterrupt()'s
+   * wrap-chain case calls this; the write here is then a no-op.)
+   */
+
+  regval  = up_serialin(priv, R_SCI_B_CCR0_OFFSET);
+  regval |= R_SCI_B_CCR0_TIE;
+  up_serialout(priv, R_SCI_B_CCR0_OFFSET, regval);
+
+  /* Normal mode, byte size, SAR increments through the ring buffer, DAR
+   * fixed at TDR's low byte.  DISEL=0: one CPU interrupt after the whole
+   * count, not one per byte.
+   */
+
+  info->mra = R_DTC_MRA_MD_NORMAL | R_DTC_MRA_SZ_BYTE |
+              R_DTC_MRA_SM_INCREMENT;
+  info->mrb = R_DTC_MRB_DM_FIXED;
+  info->dar = priv->scibase + R_SCI_B_TDR_BY_LL_OFFSET;
+  info->crb = 0;  /* Unused in normal mode */
+
+  if ((up_serialin(priv, R_SCI_B_CSR_OFFSET) & R_SCI_B_CSR_TDRE) != 0)
+    {
+      /* Idle (TDR empty): write byte[0] directly, and arm the DTC (if
+       * more than one byte) for the rest -- see the "idle" case above.
+       */
+
+      if (length > 1)
+        {
+          info->sar = (uint32_t)(buffer + 1);
+          info->cra = (uint16_t)(length - 1);
+
+          ra_dtc_configure(vector, info);
+          ra_dtc_enable(vector);
+        }
+
+      up_serialout(priv, R_SCI_B_TDR_OFFSET,
+                   ((uint32_t)(*(FAR uint8_t *)buffer)) &
+                   R_SCI_B_TDR_TDAT_MASK);
+    }
+  else
+    {
+      /* Not idle: TDR holds a byte that has not yet reached TSR.  Arm
+       * the DTC for the whole buffer instead, with no direct write --
+       * see the "not idle" case above.
+       */
+
+      info->sar = (uint32_t)buffer;
+      info->cra = (uint16_t)length;
+
+      ra_dtc_configure(vector, info);
+      ra_dtc_enable(vector);
+    }
+
+  leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: up_dma_send
+ *
+ * Description:
+ *   up_dma_send_at() for dev->dmatx.buffer/length -- the .dmasend callback
+ *   the generic serial core invokes for a fresh send (dmatxavail's fake
+ *   kick, via up_dma_txavail() below).  up_txinterrupt()'s wrap-chain case
+ *   calls up_dma_send_at() directly instead, see its own comment.
+ *
+ ****************************************************************************/
+
+static void up_dma_send(struct uart_dev_s *dev)
+{
+  struct up_dev_s *priv = (struct up_dev_s *)dev->priv;
+
+  up_dma_send_at(priv, (uintptr_t)dev->dmatx.buffer, dev->dmatx.length);
+}
+#endif
+
+/****************************************************************************
+ * Name: up_dma_txavail
+ *
+ * Description:
+ *   Start a new DTC transmit if this port uses one and none is already in
+ *   flight.  Called by the serial upper half whenever new data may be
+ *   available to send; a no-op for every port except one with
+ *   priv->txdtc set.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_SERIAL_TXDMA
+static void up_dma_txavail(struct uart_dev_s *dev)
+{
+  struct up_dev_s *priv = (struct up_dev_s *)dev->priv;
+  irqstate_t flags;
+
+  /* Without this, a stray TXI could land between the length==0 check and
+   * uart_xmitchars_dma() actually arming something, and be missed.
+   */
+
+  flags = enter_critical_section();
+
+  if (priv->txdtc && dev->dmatx.length == 0)
+    {
+      uart_xmitchars_dma(dev);
+    }
+
+  leave_critical_section(flags);
+}
+#endif
+
+/****************************************************************************
+ * Name: up_dma_rxarm
+ *
+ * Description:
+ *   (Re)arm this port's RX DTC vector to move exactly the next byte from
+ *   RDR to priv->dtcrxbyte: DTC normal mode, byte size, both source and
+ *   destination fixed (a single register, a single scratch variable), one
+ *   count.  Rebuilds the whole transfer info every call rather than
+ *   assuming it is still 1 from the last time: normal mode's count reads
+ *   back as 0 once exhausted, and DTC normal mode treats a stored count of
+ *   0 as 65536, not "do nothing" (RA8M1 User's Manual section 17.2.6).
+ *
+ *   No-ops if DTCE is already 1 (up_rxdrain() calls this unconditionally
+ *   on every drain): rewriting a live descriptor while DTCE is still 1
+ *   risks tearing an activation already in progress or imminent.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_RA_DTC
+static void up_dma_rxarm(struct up_dev_s *priv)
+{
+  struct dtc_transfer_info_s *info = &priv->dtcrxinfo;
+  uint8_t vector = priv->rxirq - RA_IRQ_FIRST;
+
+  if ((getreg32(R_ICU_IELSR(vector)) & R_ICU_IELSR_DTCE) != 0)
+    {
+      return;
+    }
+
+  info->mra = R_DTC_MRA_MD_NORMAL | R_DTC_MRA_SZ_BYTE | R_DTC_MRA_SM_FIXED;
+  info->mrb = R_DTC_MRB_DM_FIXED;
+  info->sar = priv->scibase + R_SCI_B_RDR_OFFSET;
+  info->dar = (uint32_t)(uintptr_t)&priv->dtcrxbyte;
+  info->cra = 1;
+  info->crb = 0;  /* Unused in normal mode */
+
+  ra_dtc_configure(vector, info);
+  ra_dtc_enable(vector);
+}
+#endif
+
 /****************************************************************************
  * Public Functions
  ****************************************************************************/

Reply via email to