diff --git a/arch/arm64/src/imx9/Kconfig b/arch/arm64/src/imx9/Kconfig
index 9368c961c160f..7673dc6356259 100644
--- a/arch/arm64/src/imx9/Kconfig
+++ b/arch/arm64/src/imx9/Kconfig
@@ -28,10 +28,89 @@ config IMX9_FLEXIO_PWM
 	default n
 
 menu "i.MX9 Peripheral Selection"
-config IMX9_UART1
-	bool "UART1"
+
+menu "LPUART"
+
+config IMX9_LPUART
+	bool
+	default n
+	select ARCH_HAVE_SERIAL_TERMIOS
+
+config IMX9_LPUART1
+	bool "LPUART1"
+	default n
+	select IMX9_LPUART
+	select LPUART1_SERIALDRIVER
+
+config IMX9_LPUART2
+	bool "LPUART2"
+	default n
+	select IMX9_LPUART
+	select LPUART2_SERIALDRIVER
+
+config IMX9_LPUART3
+	bool "LPUART3"
+	default n
+	select IMX9_LPUART
+	select LPUART3_SERIALDRIVER
+
+config IMX9_LPUART4
+	bool "LPUART4"
+	default n
+	select IMX9_LPUART
+	select LPUART4_SERIALDRIVER
+
+config IMX9_LPUART5
+	bool "LPUART5"
+	default n
+	select IMX9_LPUART
+	select LPUART5_SERIALDRIVER
+
+config IMX9_LPUART6
+	bool "LPUART6"
 	default n
-	select UART1_SERIALDRIVER
+	select IMX9_LPUART
+	select LPUART6_SERIALDRIVER
+
+config IMX9_LPUART7
+	bool "LPUART7"
+	default n
+	select IMX9_LPUART
+	select LPUART7_SERIALDRIVER
+
+config IMX9_LPUART8
+	bool "LPUART8"
+	default n
+	select IMX9_LPUART
+	select LPUART8_SERIALDRIVER
+
+menu "LPUART Configuration"
+	depends on IMX9_LPUART
+
+config IMX9_LPUART_INVERT
+	bool "Signal Invert Support"
+	default n
+
+config IMX9_LPUART_SINGLEWIRE
+	bool "Signal Wire Support"
+	default n
+
+config IMX9_SERIAL_RXDMA_BUFFER_SIZE
+	int "RX DMA buffer size"
+	default 64
+	depends on LPUART1_RXDMA || LPUART2_RXDMA || LPUART3_RXDMA || LPUART4_RXDMA || \
+	           LPUART5_RXDMA || LPUART6_RXDMA || LPUART7_RXDMA || LPUART8_RXDMA
+	---help---
+		The DMA buffer size when using RX DMA to emulate a FIFO.
+
+		When streaming data, the generic serial layer will be called
+		every time the FIFO receives half this number of bytes.
+
+		Value given here will be rounded up to next multiple of 64 bytes.
+
+endmenu # LPUART Configuration
+
+endmenu # LPUART
 
 config IMX9_FLEXIO1_PWM
 	depends on PWM
diff --git a/arch/arm64/src/imx9/Make.defs b/arch/arm64/src/imx9/Make.defs
index 27a19cb357b97..5e11a38b59c76 100644
--- a/arch/arm64/src/imx9/Make.defs
+++ b/arch/arm64/src/imx9/Make.defs
@@ -25,10 +25,7 @@ include common/Make.defs
 CHIP_CSRCS = imx9_boot.c imx9_ccm.c imx9_clockconfig.c imx9_gpio.c imx9_iomuxc.c
 
 ifeq ($(CONFIG_ARCH_CHIP_IMX93),y)
-  CHIP_CSRCS += imx9_lpuart.c
-  ifeq ($(CONFIG_ARCH_EARLY_PRINT),y)
-    CHIP_ASRCS = imx93_lowputc.S
-  endif
+  CHIP_CSRCS += imx9_lpuart.c imx9_lowputc.c
 endif
 
 ifeq ($(CONFIG_IMX9_GPIO_IRQ),y)
diff --git a/arch/arm64/src/imx9/hardware/imx9_lpuart.h b/arch/arm64/src/imx9/hardware/imx9_lpuart.h
index 3925c51bc606f..c688d5966a14e 100644
--- a/arch/arm64/src/imx9/hardware/imx9_lpuart.h
+++ b/arch/arm64/src/imx9/hardware/imx9_lpuart.h
@@ -25,134 +25,289 @@
  * Included Files
  ****************************************************************************/
 
-#include <nuttx/config.h>
+#include <hardware/imx9_memorymap.h>
 
 /****************************************************************************
  * Pre-processor Definitions
  ****************************************************************************/
 
-/* 32-bit register definition */
-
-#define UARTVERID                   0x0000  /* Version ID Register */
-#define UARTPARAM                   0x0004  /* Parameter Register */
-#define UARTGLOBAL                  0x0008  /* LPUART Global Register */
-#define UARTPINCFG                  0x000c  /* LPUART Pin Configuration Register */
-#define UARTBAUD                    0x0010  /* LPUART Baud Rate Register */
-#define UARTSTAT                    0x0014  /* LPUART Status Register */
-#define UARTCTRL                    0x0018  /* LPUART Control Register */
-#define UARTDATA                    0x001c  /* LPUART Data Register */
-#define UARTMATCH                   0x0020  /* LPUART Match Address Register */
-#define UARTMODIR                   0x0024  /* LPUART Modem IrDA Register */
-#define UARTFIFO                    0x0028  /* LPUART FIFO Register */
-#define UARTWATER                   0x002c  /* LPUART Watermark Register */
-
-#define UARTBAUD_MAEN1              0x80000000
-#define UARTBAUD_MAEN2              0x40000000
-#define UARTBAUD_M10                0x20000000
-#define UARTBAUD_TDMAE              0x00800000
-#define UARTBAUD_RDMAE              0x00200000
-#define UARTBAUD_RIDMAE             0x00100000
-#define UARTBAUD_MATCFG             0x00400000
-#define UARTBAUD_BOTHEDGE           0x00020000
-#define UARTBAUD_RESYNCDIS          0x00010000
-#define UARTBAUD_LBKDIE             0x00008000
-#define UARTBAUD_RXEDGIE            0x00004000
-#define UARTBAUD_SBNS               0x00002000
-#define UARTBAUD_SBR                0x00000000
-#define UARTBAUD_SBR_MASK           0x1fff
-#define UARTBAUD_OSR_MASK           0x1f
-#define UARTBAUD_OSR_SHIFT          24
-
-#define UARTSTAT_LBKDIF             0x80000000
-#define UARTSTAT_RXEDGIF            0x40000000
-#define UARTSTAT_MSBF               0x20000000
-#define UARTSTAT_RXINV              0x10000000
-#define UARTSTAT_RWUID              0x08000000
-#define UARTSTAT_BRK13              0x04000000
-#define UARTSTAT_LBKDE              0x02000000
-#define UARTSTAT_RAF                0x01000000
-#define UARTSTAT_TDRE               0x00800000
-#define UARTSTAT_TC                 0x00400000
-#define UARTSTAT_RDRF               0x00200000
-#define UARTSTAT_IDLE               0x00100000
-#define UARTSTAT_OR                 0x00080000
-#define UARTSTAT_NF                 0x00040000
-#define UARTSTAT_FE                 0x00020000
-#define UARTSTAT_PE                 0x00010000
-#define UARTSTAT_MA1F               0x00008000
-#define UARTSTAT_M21F               0x00004000
-
-#define UARTCTRL_R8T9               0x80000000
-#define UARTCTRL_R9T8               0x40000000
-#define UARTCTRL_TXDIR              0x20000000
-#define UARTCTRL_TXINV              0x10000000
-#define UARTCTRL_ORIE               0x08000000
-#define UARTCTRL_NEIE               0x04000000
-#define UARTCTRL_FEIE               0x02000000
-#define UARTCTRL_PEIE               0x01000000
-#define UARTCTRL_TIE                0x00800000
-#define UARTCTRL_TCIE               0x00400000
-#define UARTCTRL_RIE                0x00200000
-#define UARTCTRL_ILIE               0x00100000
-#define UARTCTRL_TE                 0x00080000
-#define UARTCTRL_RE                 0x00040000
-#define UARTCTRL_RWU                0x00020000
-#define UARTCTRL_SBK                0x00010000
-#define UARTCTRL_MA1IE              0x00008000
-#define UARTCTRL_MA2IE              0x00004000
-#define UARTCTRL_IDLECFG_OFF        0x8
-#define UARTCTRL_LOOPS              0x00000080
-#define UARTCTRL_DOZEEN             0x00000040
-#define UARTCTRL_RSRC               0x00000020
-#define UARTCTRL_M                  0x00000010
-#define UARTCTRL_WAKE               0x00000008
-#define UARTCTRL_ILT                0x00000004
-#define UARTCTRL_PE                 0x00000002
-#define UARTCTRL_PT                 0x00000001
-
-#define UARTDATA_NOISY              0x00008000
-#define UARTDATA_PARITYE            0x00004000
-#define UARTDATA_FRETSC             0x00002000
-#define UARTDATA_RXEMPT             0x00001000
-#define UARTDATA_IDLINE             0x00000800
-#define UARTDATA_MASK               0x3ff
-
-#define UARTMODIR_IREN              0x00020000
-#define UARTMODIR_RTSWATER_S        0x8
-#define UARTMODIR_TXCTSSRC          0x00000020
-#define UARTMODIR_TXCTSC            0x00000010
-#define UARTMODIR_RXRTSE            0x00000008
-#define UARTMODIR_TXRTSPOL          0x00000004
-#define UARTMODIR_TXRTSE            0x00000002
-#define UARTMODIR_TXCTSE            0x00000001
-
-#define UARTFIFO_TXEMPT             0x00800000
-#define UARTFIFO_RXEMPT             0x00400000
-#define UARTFIFO_TXOF               0x00020000
-#define UARTFIFO_RXUF               0x00010000
-#define UARTFIFO_TXFLUSH            0x00008000
-#define UARTFIFO_RXFLUSH            0x00004000
-#define UARTFIFO_RXIDEN_MASK        0x7
-#define UARTFIFO_RXIDEN_OFF         10
-#define UARTFIFO_TXOFE              0x00000200
-#define UARTFIFO_RXUFE              0x00000100
-#define UARTFIFO_TXFE               0x00000080
-#define UARTFIFO_FIFOSIZE_MASK      0x7
-#define UARTFIFO_TXSIZE_OFF         4
-#define UARTFIFO_RXFE               0x00000008
-#define UARTFIFO_RXSIZE_OFF         0
-#define UARTFIFO_DEPTH(x)           (0x1 << ((x) ? ((x) + 1) : 0))
-
-#define UARTWATER_COUNT_MASK        0xff
-#define UARTWATER_TXCNT_OFF         8
-#define UARTWATER_RXCNT_OFF         24
-#define UARTWATER_WATER_MASK        0xff
-#define UARTWATER_TXWATER_OFF       0
-#define UARTWATER_RXWATER_OFF       16
-
-#define UARTGLOBAL_RST              0x2
-
-#define UARTFIFO_RXIDEN_RDRF        0x3
-#define UARTCTRL_IDLECFG            0x7
+/* LPUART Register Offsets **************************************************/
+
+#define IMX9_LPUART_VERID_OFFSET    (0x00) /* Version ID Register (VERID) */
+#define IMX9_LPUART_PARAM_OFFSET    (0x04) /* Parameter Register (PARAM) */
+#define IMX9_LPUART_GLOBAL_OFFSET   (0x08) /* LPUART Global Register (GLOBAL) */
+#define IMX9_LPUART_PINCFG_OFFSET   (0x0c) /* LPUART Pin Configuration Register (PINCFG) */
+#define IMX9_LPUART_BAUD_OFFSET     (0x10) /* LPUART Baud Rate Register (BAUD) */
+#define IMX9_LPUART_STAT_OFFSET     (0x14) /* LPUART Status Register (STAT) */
+#define IMX9_LPUART_CTRL_OFFSET     (0x18) /* LPUART Control Register (CTRL) */
+#define IMX9_LPUART_DATA_OFFSET     (0x1c) /* LPUART Data Register (DATA) */
+#define IMX9_LPUART_MATCH_OFFSET    (0x20) /* LPUART Match Address Register (MATCH) */
+#define IMX9_LPUART_MODIR_OFFSET    (0x24) /* LPUART Modem IrDA Register (MODIR) */
+#define IMX9_LPUART_FIFO_OFFSET     (0x28) /* LPUART FIFO Register (FIFO) */
+#define IMX9_LPUART_WATER_OFFSET    (0x2c) /* LPUART Watermark Register (WATER) */
+#define IMX9_LPUART_DATARO_OFFSET   (0x30) /* Data read-only Register (DATARO) */
+
+/* Register bit definitions *************************************************/
+
+/* Version ID Register (VERID) */
+
+#define LPUART_VERID_FEATURE_SHIFT     (0)       /* Bits 0-15: Feature Identification Number (FEATURE) */
+#define LPUART_VERID_FEATURE_MASK      (0xffff << LPUART_VERID_FEATURE_SHIFT)
+#  define LPUART_VERID_FEATURE_STD     (1 << LPUART_VERID_FEATURE_SHIFT) /* Standard feature set */
+#  define LPUART_VERID_FEATURE_MODEM   (3 << LPUART_VERID_FEATURE_SHIFT) /* MODEM/IrDA support */
+
+#define LPUART_VERID_MINOR_SHIFT       (16)      /* Bits 16-23: Minor Version Number (MINOR) */
+#define LPUART_VERID_MINOR_MASK        (0xff << LPUART_VERID_MINOR_SHIFT)
+#define LPUART_VERID_MAJOR_SHIFT       (24)      /* Bits 24-31: Major Version Number (MAJOR) */
+#define LPUART_VERID_MAJOR_MASK        (0xff << LPUART_VERID_MAJOR_SHIFT)
+
+/* Parameter Register (PARAM) */
+
+#define LPUART_PARAM_TXFIFO_SHIFT      (0)       /* Bits 0-7: Transmit FIFO Size (TXFIFO) */
+#define LPUART_PARAM_TXFIFO_MASK       (0xff << LPUART_PARAM_TXFIFO_SHIFT)
+#define LPUART_PARAM_RXFIFO_SHIFT      (8)       /* Bits 8-15: Receive FIFO Size (RXFIFO) */
+#define LPUART_PARAM_RXFIFO_MASK       (0xff << LPUART_PARAM_RXFIFO_SHIFT)
+                                                 /* Bits 16-31: Reserved */
+
+/* LPUART Global Register (GLOBAL) */
+
+                                                 /* Bit 0: Reserved */
+#define LPUART_GLOBAL_RST              (1 << 1)  /* Bit 1: Software Reset (RST) */
+                                                 /* Bits 2-31: Reserved */
+
+/* LPUART Pin Configuration Register (PINCFG) */
+
+#define LPUART_PINCFG_TRGSEL_SHIFT     (0)       /* Bits 0-1: Trigger Select (TRGSEL) */
+#define LPUART_PINCFG_TRGSEL_MASK      (0x03 << LPUART_PINCFG_TRGSEL_SHIFT)
+#  define LPUART_PINCFG_TRGSEL_DISABLE (0 << LPUART_PINCFG_TRGSEL_SHIFT) /* Trigger disabled */
+#  define LPUART_PINCFG_TRGSEL_RXD     (1 << LPUART_PINCFG_TRGSEL_SHIFT) /* Trigger used instead of RXD pin */
+#  define LPUART_PINCFG_TRGSEL_CTSB    (2 << LPUART_PINCFG_TRGSEL_SHIFT) /* Trigger used instead of CTS_B pin */
+#  define LPUART_PINCFG_TRGSEL_TXDMOD  (3 << LPUART_PINCFG_TRGSEL_SHIFT) /* Trigger used to modulate the TXD output */
+
+                                                 /* Bits 2-31: Reserved */
+
+/* LPUART Baud Rate Register (BAUD) */
+
+#define LPUART_BAUD_SBR_SHIFT          (0)       /* Bits 0-12: Baud Rate Modulo Divisor (SBR) */
+#define LPUART_BAUD_SBR_MASK           (0x1fff << LPUART_BAUD_SBR_SHIFT)
+#  define LPUART_BAUD_SBR(n)           ((n) << LPUART_BAUD_SBR_SHIFT)
+#define LPUART_BAUD_SBNS               (1 << 13) /* Bit 13: Stop Bit Number Select (SBNS) */
+#define LPUART_BAUD_RXEDGIE            (1 << 14) /* Bit 14: RX Input Active Edge Interrupt Enable (RXEDGIE) */
+#define LPUART_BAUD_LBKDIE             (1 << 15) /* Bit 15: LIN Break Detect Interrupt Enable (LBKDIE) */
+#define LPUART_BAUD_RESYNCDIS          (1 << 16) /* Bit 16: Resynchronization Disable (RESYNCDIS) */
+#define LPUART_BAUD_BOTHEDGE           (1 << 17) /* Bit 17: Both Edge Sampling (BOTHEDGE) */
+#define LPUART_BAUD_MATCFG_SHIFT       (18)      /* Bits 18-19: Match Configuration (MATCFG) */
+#define LPUART_BAUD_MATCFG_MASK        (0x03 << LPUART_BAUD_MATCFG_SHIFT)
+#  define LPUART_BAUD_MATCFG_ADDR      (0 << LPUART_BAUD_MATCFG_SHIFT) /* Address Match Wakeup */
+#  define LPUART_BAUD_MATCFG_IDLE      (1 << LPUART_BAUD_MATCFG_SHIFT) /* Idle Match Wakeup */
+#  define LPUART_BAUD_MATCFG_ONOFF     (2 << LPUART_BAUD_MATCFG_SHIFT) /* Match On and Match Off */
+#  define LPUART_BAUD_MATCFG_RWUENAB   (3 << LPUART_BAUD_MATCFG_SHIFT) /* Enables RWU on Data Match and Match On/Off for transmitter CTS input */
+
+                                                 /* Bit 20: Reserved */
+#define LPUART_BAUD_RDMAE              (1 << 21) /* Bit 21: Receiver Full DMA Enable (RDMAE) */
+                                                 /* Bit 22: Reserved */
+#define LPUART_BAUD_TDMAE              (1 << 23) /* Bit 23: Transmitter DMA Enable (TDMAE) */
+#define LPUART_BAUD_OSR_SHIFT          (24)      /* Bits 24-29: Oversampling Ratio (OSR) */
+#define LPUART_BAUD_OSR_MASK           (0x1f << LPUART_BAUD_OSR_SHIFT)
+#  define LPUART_BAUD_OSR(n)           (((n) - 1) << LPUART_BAUD_OSR_SHIFT) /* n=4..32 */
+
+#define LPUART_BAUD_M10                (1 << 29) /* Bit 29: 10-bit Mode Select (M10) */
+#define LPUART_BAUD_MAEN2              (1 << 30) /* Bit 30: Match Address Mode Enable 2 (MAEN2) */
+#define LPUART_BAUD_MAEN1              (1 << 31) /* Bit 31: Match Address Mode Enable 1 (MAEN1) */
+
+/* LPUART Status Register (STAT) */
+
+#define LPUART_STAT_LBKFE              (1 << 0)  /* Bit 0: LIN Break Flag Enable (LBKFE) */
+#define LPUART_STAT_AME                (1 << 1)  /* Bit 1: Address Mark Enable (AME) */
+                                                 /* Bits 2-13: Reserved */
+#define LPUART_STAT_MA2F               (1 << 14) /* Bit 14: Match 2 Flag (MA2F) */
+#define LPUART_STAT_MA1F               (1 << 15) /* Bit 15: Match 1 Flag (MA1F) */
+#define LPUART_STAT_PF                 (1 << 16) /* Bit 16: Parity Error Flag (PF) */
+#define LPUART_STAT_FE                 (1 << 17) /* Bit 17: Framing Error Flag (FE) */
+#define LPUART_STAT_NF                 (1 << 18) /* Bit 18: Noise Flag (NF) */
+#define LPUART_STAT_OR                 (1 << 19) /* Bit 19: Receiver Overrun Flag (OR) */
+#define LPUART_STAT_IDLE               (1 << 20) /* Bit 20: Idle Line Flag (IDLE) */
+#define LPUART_STAT_RDRF               (1 << 21) /* Bit 21: Receive Data Register Full Flag (RDRF) */
+#define LPUART_STAT_TC                 (1 << 22) /* Bit 22: Transmission Complete Flag (TC) */
+#define LPUART_STAT_TDRE               (1 << 23) /* Bit 23: Transmit Data Register Empty Flag (TDRE) */
+#define LPUART_STAT_RAF                (1 << 24) /* Bit 24: Receiver Active Flag (RAF) */
+#define LPUART_STAT_LBKDE              (1 << 25) /* Bit 25: LIN Break Detection Enable (LBKDE) */
+#define LPUART_STAT_BRK13              (1 << 26) /* Bit 26: Break Character Generation Length (BRK13) */
+#define LPUART_STAT_RWUID              (1 << 27) /* Bit 27: Receive Wake Up Idle Detect (RWUID) */
+#define LPUART_STAT_RXINV              (1 << 28) /* Bit 28: Receive Data Inversion (RXINV) */
+#define LPUART_STAT_MSBF               (1 << 29) /* Bit 29: MSB First (MSBF) */
+#define LPUART_STAT_RXEDGIF            (1 << 30) /* Bit 30: RXD Pin Active Edge Interrupt Flag (RXEDGIF) */
+#define LPUART_STAT_LBKDIF             (1 << 31) /* Bit 31: LIN Break Detect Interrupt Flag (LBKDIF) */
+
+/* LPUART Control Register (CTRL) */
+
+#define LPUART_CTRL_PT                 (1 << 0)  /* Bit 0: Parity Type */
+#  define LPUART_CTRL_PT_EVEN          (0 << 0)  /*        Even parity */
+#  define LPUART_CTRL_PT_ODD           (1 << 0)  /*        Odd parity */
+#define LPUART_CTRL_PE                 (1 << 1)  /* Bit 1: Parity Enable */
+#define LPUART_CTRL_ILT                (1 << 2)  /* Bit 2: Idle Line Type Select */
+#define LPUART_CTRL_WAKE               (1 << 3)  /* Bit 3: Receiver Wakeup Method Select */
+#define LPUART_CTRL_M                  (1 << 4)  /* Bit 4: 9-Bit or 8-Bit Mode Select */
+#define LPUART_CTRL_RSRC               (1 << 5)  /* Bit 5: Receiver Source Select */
+#define LPUART_CTRL_DOZEEN             (1 << 6)  /* Bit 6: Doze Enable */
+#define LPUART_CTRL_LOOPS              (1 << 7)  /* Bit 7: Loop Mode Select */
+#define LPUART_CTRL_IDLECFG_SHIFT      (8)       /* Bits 8-10: Idle Configuration */
+#define LPUART_CTRL_IDLECFG_MASK       (0x07 << LPUART_CTRL_IDLECFG_SHIFT)
+#  define LPUART_CTRL_IDLECFG_1        (0 << LPUART_CTRL_IDLECFG_SHIFT) /* 1 idle character */
+#  define LPUART_CTRL_IDLECFG_2        (1 << LPUART_CTRL_IDLECFG_SHIFT) /* 2 idle characters */
+#  define LPUART_CTRL_IDLECFG_4        (2 << LPUART_CTRL_IDLECFG_SHIFT) /* 4 idle characters */
+#  define LPUART_CTRL_IDLECFG_8        (3 << LPUART_CTRL_IDLECFG_SHIFT) /* 8 idle characters */
+#  define LPUART_CTRL_IDLECFG_16       (4 << LPUART_CTRL_IDLECFG_SHIFT) /* 6 idle characters */
+#  define LPUART_CTRL_IDLECFG_32       (5 << LPUART_CTRL_IDLECFG_SHIFT) /* 32 idle characters */
+#  define LPUART_CTRL_IDLECFG_64       (6 << LPUART_CTRL_IDLECFG_SHIFT) /* 64 idle characters */
+#  define LPUART_CTRL_IDLECFG_128      (7 << LPUART_CTRL_IDLECFG_SHIFT) /* 128 idle characters */
+
+#define LPUART_CTRL_M7                 (1 << 11) /* Bit 11: 7-Bit Mode Select (M7) */
+                                                 /* Bits 12-13: Reserved */
+#define LPUART_CTRL_MA2IE              (1 << 14) /* Bit 14: Match 2 Interrupt Enable (MA2IE) */
+#define LPUART_CTRL_MA1IE              (1 << 15) /* Bit 15: Match 1 Interrupt Enable (MA1IE) */
+#define LPUART_CTRL_SBK                (1 << 16) /* Bit 16: Send Break (SBK) */
+#define LPUART_CTRL_RWU                (1 << 17) /* Bit 17: Receiver Wakeup Control (RWU) */
+#define LPUART_CTRL_RE                 (1 << 18) /* Bit 18: Receiver Enable (RE) */
+#define LPUART_CTRL_TE                 (1 << 19) /* Bit 19: Transmitter Enable (TE) */
+#define LPUART_CTRL_ILIE               (1 << 20) /* Bit 20: Idle Line Interrupt Enable (ILIE) */
+#define LPUART_CTRL_RIE                (1 << 21) /* Bit 21: Receiver Interrupt Enable (RIE) */
+#define LPUART_CTRL_TCIE               (1 << 22) /* Bit 22: Transmission Complete Interrupt Enable (TCIE) */
+#define LPUART_CTRL_TIE                (1 << 23) /* Bit 23: Transmit Interrupt Enable (TIE) */
+#define LPUART_CTRL_PEIE               (1 << 24) /* Bit 24: Parity Error Interrupt Enable (PEIE) */
+#define LPUART_CTRL_FEIE               (1 << 25) /* Bit 25: Framing Error Interrupt Enable (FEIE) */
+#define LPUART_CTRL_NEIE               (1 << 26) /* Bit 26: Noise Error Interrupt Enable (NEIE) */
+#define LPUART_CTRL_ORIE               (1 << 27) /* Bit 27: Overrun Interrupt Enable (ORIE) */
+#define LPUART_CTRL_TXINV              (1 << 28) /* Bit 28: Transmit Data Inversion (TXINV) */
+#define LPUART_CTRL_TXDIR              (1 << 29) /* Bit 29: TXD Pin Direction in Single-Wire Mode (TXDIR) */
+#define LPUART_CTRL_R9T8               (1 << 30) /* Bit 30: Receive Bit 9 / Transmit Bit 8 (R9T8) */
+#define LPUART_CTRL_R8T9               (1 << 31) /* Bit 31: Receive Bit 8 / Transmit Bit 9 (R8T9) */
+
+#define LPUART_ALL_INTS (LPUART_CTRL_ORIE | LPUART_CTRL_NEIE | LPUART_CTRL_FEIE |  \
+                         LPUART_CTRL_PEIE | LPUART_CTRL_TIE  | LPUART_CTRL_TCIE |  \
+                         LPUART_CTRL_RIE  | LPUART_CTRL_ILIE | LPUART_CTRL_MA1IE | \
+                         LPUART_CTRL_MA2IE)
+
+/* LPUART Data Register (DATA) */
+
+#define LPUART_DATA_SHIFT              (0)       /* Bits 0-9: Data bits 0-9 (DATA)*/
+#define LPUART_DATA_MASK               (0x03ff << LPUART_DATA_SHIFT)
+#define LPUART_DATA_LINBRK             (1 << 10) /* Bit 10: LIN Break (LINBRK) */
+#define LPUART_DATA_STATUS_SHIFT       (11)      /* Bits 11-15: Status */
+#define LPUART_DATA_IDLINE             (1 << 11) /* Bit 11: Idle Line (IDLINE) */
+#define LPUART_DATA_RXEMPT             (1 << 12) /* Bit 12: Receive Buffer Empty (RXEMPT) */
+#define LPUART_DATA_FRETSC             (1 << 13) /* Bit 13: Frame Error / Transmit Special Character (FRETSC) */
+#define LPUART_DATA_PARITYE            (1 << 14) /* Bit 14: Parity Error (PARITYE) */
+#define LPUART_DATA_NOISY              (1 << 15) /* Bit 15: Noisy Data Received (NOISY) */
+                                                 /* Bits 16-31: Reserved */
+
+/* LPUART Match Address Register (MATCH) */
+
+#define LPUART_MATCH_MA1_SHIFT         (0)       /* Bits 0-9: Match Address 1 (MA1) */
+#define LPUART_MATCH_MA1_MASK          (0x03ff << LPUART_MATCH_MA1_SHIFT)
+#  define LPUART_MATCH_MA1(n)          ((n) << LPUART_MATCH_MA1_SHIFT)
+                                                 /* Bits 10-15: Reserved */
+#define LPUART_MATCH_MA2_SHIFT         (16)      /* Bits 16-25: Match Address 2 (MA2) */
+#define LPUART_MATCH_MA2_MASK          (0x03ff << LPUART_MATCH_MA2_SHIFT)
+#  define LPUART_MATCH_MA2(n)          ((n) << LPUART_MATCH_MA2_SHIFT)
+                                                 /* Bits 26-31: Reserved */
+
+/* LPUART Modem IrDA Register (MODIR) */
+
+#define LPUART_MODIR_TXCTSE            (1 << 0)  /* Bit 0: Transmitter clear-to-send enable (TXCTSE) */
+#define LPUART_MODIR_TXRTSE            (1 << 1)  /* Bit 1: Transmitter request-to-send enable (TXRTSE) */
+#define LPUART_MODIR_TXRTSPOL          (1 << 2)  /* Bit 2: Transmitter request-to-send polarity (TXRTSPOL) */
+#define LPUART_MODIR_RXRTSE            (1 << 3)  /* Bit 3: Receiver request-to-send enable (RXRTSE) */
+#define LPUART_MODIR_TXCTSC            (1 << 4)  /* Bit 4: Transmit CTS Configuration (TXCTSC) */
+#  define LPUART_MODIR_TXCTSC_START    (0 << 4)  /*        CTS sampled at start of character */
+#  define LPUART_MODIR_TXCTSC_IDLE     (1 << 4)  /*        CTS sampled when transmitter idle */
+#define LPUART_MODIR_TXCTSSRC          (1 << 5)  /* Bit 5: Transmit CTS Source (TXCTSSRC) */
+#  define LPUART_MODIR_TXCTSSRC_CTSB   (0 << 5)  /*        CTS input is CTS_B pin */
+#  define LPUART_MODIR_TXCTSSRC_RXMAT  (1 << 5)  /*        CTS input is receiver address match result */
+                                                 /* Bits 6-7: Reserved */
+#define LPUART_MODIR_RTSWATER_SHIFT    (8)       /* Bits 8-9: Receive RTS Configuration (RTSWATER) */
+#define LPUART_MODIR_RTSWATER_MASK     (0x03 << LPUART_MODIR_RTSWATER_SHIFT)
+#  define LPUART_MODIR_RTSWATER(n)     ((n) << LPUART_MODIR_RTSWATER_SHIFT)
+                                                 /* Bits 10-15: Reserved */
+#define LPUART_MODIR_TNP_SHIFT         (16)      /* Bits 16-17: Transmitter narrow pulse (TNP) */
+#define LPUART_MODIR_TNP_MASK          (0x03 << LPUART_MODIR_TNP_SHIFT)
+#  define LPUART_MODIR_TNP(n)          (((n) - 1) << LPUART_MODIR_TNP_SHIFT) /* n/OSR */
+
+#define LPUART_MODIR_IREN              (1 << 18) /* Bit 18: Infrared enable (IREN) */
+                                                 /* Bits 19-31: Reserved */
+
+/* LPUART FIFO Register (FIFO) */
+
+#define LPUART_FIFO_RXFIFOSIZE_SHIFT   (0)       /* Bits 0-2: Receive FIFO Buffer Depth (RXFIFOSIZE) */
+#define LPUART_FIFO_RXFIFOSIZE_MASK    (0x07 << LPUART_FIFO_RXFIFOSIZE_SHIFT)
+#  define LPUART_FIFO_RXFIFOSIZE_1     (0 << LPUART_FIFO_RXFIFOSIZE_SHIFT) /* 1 dataword */
+#  define LPUART_FIFO_RXFIFOSIZE_4     (1 << LPUART_FIFO_RXFIFOSIZE_SHIFT) /* 4 datawords */
+#  define LPUART_FIFO_RXFIFOSIZE_8     (2 << LPUART_FIFO_RXFIFOSIZE_SHIFT) /* 8 datawords */
+#  define LPUART_FIFO_RXFIFOSIZE_16    (3 << LPUART_FIFO_RXFIFOSIZE_SHIFT) /* 16 datawords */
+#  define LPUART_FIFO_RXFIFOSIZE_32    (4 << LPUART_FIFO_RXFIFOSIZE_SHIFT) /* 32 datawords */
+#  define LPUART_FIFO_RXFIFOSIZE_64    (5 << LPUART_FIFO_RXFIFOSIZE_SHIFT) /* 64 datawords */
+#  define LPUART_FIFO_RXFIFOSIZE_128   (6 << LPUART_FIFO_RXFIFOSIZE_SHIFT) /* 128 datawords */
+#  define LPUART_FIFO_RXFIFOSIZE_256   (7 << LPUART_FIFO_RXFIFOSIZE_SHIFT) /* 256 datawords */
+
+#define LPUART_FIFO_RXFE               (1 << 3)  /* Bit 3: Receive FIFO Enable (RXFE) */
+#define LPUART_FIFO_TXFIFOSIZE_SHIFT   (4)       /* Bits 4-6: Transmit FIFO Buffer Depth (TXFIFOSIZE) */
+#define LPUART_FIFO_TXFIFOSIZE_MASK    (0x07 << LPUART_FIFO_TXFIFOSIZE_SHIFT)
+#  define LPUART_FIFO_TXFIFOSIZE_1     (0 << LPUART_FIFO_TXFIFOSIZE_SHIFT) /* 1 dataword */
+#  define LPUART_FIFO_TXFIFOSIZE_4     (1 << LPUART_FIFO_TXFIFOSIZE_SHIFT) /* 4 datawords */
+#  define LPUART_FIFO_TXFIFOSIZE_8     (2 << LPUART_FIFO_TXFIFOSIZE_SHIFT) /* 8 datawords */
+#  define LPUART_FIFO_TXFIFOSIZE_16    (3 << LPUART_FIFO_TXFIFOSIZE_SHIFT) /* 16 datawords */
+#  define LPUART_FIFO_TXFIFOSIZE_32    (4 << LPUART_FIFO_TXFIFOSIZE_SHIFT) /* 32 datawords */
+#  define LPUART_FIFO_TXFIFOSIZE_64    (5 << LPUART_FIFO_TXFIFOSIZE_SHIFT) /* 64 datawords */
+#  define LPUART_FIFO_TXFIFOSIZE_128   (6 << LPUART_FIFO_TXFIFOSIZE_SHIFT) /* 128 datawords */
+#  define LPUART_FIFO_TXFIFOSIZE_256   (7 << LPUART_FIFO_TXFIFOSIZE_SHIFT) /* 256 datawords */
+
+#define LPUART_FIFO_TXFE               (1 << 7)  /* Bit 7: Transmit FIFO Enable (TXFE) */
+#define LPUART_FIFO_RXUFE              (1 << 8)  /* Bit 8: Receive FIFO Underflow Interrupt Enable (RXUFE) */
+#define LPUART_FIFO_TXOFE              (1 << 9)  /* Bit 9: Transmit FIFO Overflow Interrupt Enable (TXOFE) */
+#define LPUART_FIFO_RXIDEN_SHIFT       (10)      /* Bits 10-12: Receiver Idle Empty Enable (RXIDEN) */
+#define LPUART_FIFO_RXIDEN_MASK        (0x07 << LPUART_FIFO_RXIDEN_SHIFT)
+#  define LPUART_FIFO_RXIDEN_DISABLE   (0 << LPUART_FIFO_RXIDEN_SHIFT) /* Disable RDRF assertion when receiver is idle */
+#  define LPUART_FIFO_RXIDEN_1         (1 << LPUART_FIFO_RXIDEN_SHIFT) /* Enable RDRF assertion when receiver idle for 1 character */
+#  define LPUART_FIFO_RXIDEN_2         (2 << LPUART_FIFO_RXIDEN_SHIFT) /* Enable RDRF assertion when receiver idle for 2 characters */
+#  define LPUART_FIFO_RXIDEN_4         (3 << LPUART_FIFO_RXIDEN_SHIFT) /* Enable RDRF assertion when receiver idle for 4 characters */
+#  define LPUART_FIFO_RXIDEN_8         (4 << LPUART_FIFO_RXIDEN_SHIFT) /* Enable RDRF assertion when receiver idle for 8 characters */
+#  define LPUART_FIFO_RXIDEN_16        (5 << LPUART_FIFO_RXIDEN_SHIFT) /* Enable RDRF assertion when receiver idle for 16 characters */
+#  define LPUART_FIFO_RXIDEN_32        (6 << LPUART_FIFO_RXIDEN_SHIFT) /* Enable RDRF assertion when receiver idle for 32 characters */
+#  define LPUART_FIFO_RXIDEN_64        (7 << LPUART_FIFO_RXIDEN_SHIFT) /* Enable RDRF assertion when receiver idle for 64 characters */
+
+                                                 /* Bit 13: Reserved */
+#define LPUART_FIFO_RXFLUSH            (1 << 14) /* Bit 14: Receive FIFO Flush (RXFLUSH) */
+#define LPUART_FIFO_TXFLUSH            (1 << 15) /* Bit 15: Transmit FIFO Flush (TXFLUSH) */
+#define LPUART_FIFO_RXUF               (1 << 16) /* Bit 16: Receiver FIFO Underflow Flag (RXUF) */
+#define LPUART_FIFO_TXOF               (1 << 17) /* Bit 17: Transmitter FIFO Overflow Flag (TXOF) */
+                                                 /* Bits 18-21: Reserved */
+#define LPUART_FIFO_RXEMPT             (1 << 22) /* Bit 22: Receive Buffer/FIFO Empty (RXEMPT) */
+#define LPUART_FIFO_TXEMPT             (1 << 23) /* Bit 23: Transmit Buffer/FIFO Empty (TXEMPT) */
+                                                 /* Bits 24-31: Reserved */
+
+/* LPUART Watermark Register (WATER) */
+
+#define LPUART_WATER_TXWATER_SHIFT     (0)       /* Bits 0-1: Transmit Watermark (TXWATER) */
+#define LPUART_WATER_TXWATER_MASK      (0x03 << LPUART_WATER_TXWATER_SHIFT)
+#  define LPUART_WATER_TXWATER(n)      ((n) << LPUART_WATER_TXWATER_SHIFT)
+                                                 /* Bits 2-7: Reserved */
+#define LPUART_WATER_TXCOUNT_SHIFT     (8)       /* Bits 8-10: Transmit Counter (TXCOUNT) */
+#define LPUART_WATER_TXCOUNT_MASK      (0x07 << LPUART_WATER_TXCOUNT_SHIFT)
+#  define LPUART_WATER_TXCOUNT(n)      ((n) << LPUART_WATER_TXCOUNT_SHIFT)
+                                                 /* Bits 11-15: Reserved */
+#define LPUART_WATER_RXWATER_SHIFT     (16)      /* Bits 16-17: Receive Watermark (RXWATER) */
+#define LPUART_WATER_RXWATER_MASK      (0x03 << LPUART_WATER_RXWATER_SHIFT)
+#  define LPUART_WATER_RXWATER(n)      ((n) << LPUART_WATER_RXWATER_SHIFT)
+                                                 /* Bits 18-23: Reserved */
+#define LPUART_WATER_RXCOUNT_SHIFT     (24)      /* Bits 24-26: Receive Counter (RXCOUNT) */
+#define LPUART_WATER_RXCOUNT_MASK      (0x07 << LPUART_WATER_RXCOUNT_SHIFT)
+#  define LPUART_WATER_RXCOUNT(n)      ((n) << LPUART_WATER_RXCOUNT_SHIFT)
+                                                 /* Bits 27-31: Reserved */
+
+/* Data read-only Register (DATARO) */
+
+#define LPUART_DATARO_DATA_SHIFT       (0)       /* Bits 0-15: Receive Data (DATA) */
+#define LPUART_DATARO_DATA_MASK        (0xffff << LPUART_DATARO_DATA_SHIFT)
+                                                 /* Bits 16-31: Reserved */
 
 #endif /* __ARCH_ARM64_SRC_IMX9_HARDWARE_IMX9_LPUART_H */
diff --git a/arch/arm64/src/imx9/imx9_boot.c b/arch/arm64/src/imx9/imx9_boot.c
index 16f19de2c6b54..260e67997428f 100644
--- a/arch/arm64/src/imx9/imx9_boot.c
+++ b/arch/arm64/src/imx9/imx9_boot.c
@@ -39,6 +39,7 @@
 #include "arm64_mmu.h"
 #include "imx9_boot.h"
 #include "imx9_serial.h"
+#include "imx9_lowputc.h"
 
 /****************************************************************************
  * Private Data
@@ -96,6 +97,12 @@ void arm64_chip_boot(void)
 
   arm64_mmu_init(true);
 
+  /* Do UART early initialization & pin muxing */
+
+#ifdef CONFIG_IMX9_LPUART
+  imx9_lowsetup();
+#endif
+
   /* Perform board-specific device initialization. This would include
    * configuration of board specific resources such as GPIOs, LEDs, etc.
    */
diff --git a/arch/arm64/src/imx9/imx9_lowputc.c b/arch/arm64/src/imx9/imx9_lowputc.c
new file mode 100644
index 0000000000000..c41f6f43eed12
--- /dev/null
+++ b/arch/arm64/src/imx9/imx9_lowputc.c
@@ -0,0 +1,532 @@
+/****************************************************************************
+ * arch/arm64/src/imx9/imx9_lowputc.c
+ *
+ * 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 <nuttx/config.h>
+
+#include <stdint.h>
+#include <fixedmath.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "hardware/imx9_pinmux.h"
+#include "hardware/imx9_lpuart.h"
+
+#include "arm64_internal.h"
+
+#include "imx9_lowputc.h"
+#include "imx9_ccm.h"
+#include "imx9_iomuxc.h"
+#include "hardware/imx9_ccm.h"
+#include "hardware/imx9_pinmux.h"
+
+#include <arch/board/board.h> /* Include last:  has dependencies */
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Configuration ************************************************************/
+
+#if defined(CONFIG_LPUART1_SERIAL_CONSOLE)
+#  define IMX9_CONSOLE_DEVOFF   0
+#  define IMX9_CONSOLE_BASE     IMX9_LPUART1_BASE
+#  define IMX9_CONSOLE_BAUD     CONFIG_LPUART1_BAUD
+#  define IMX9_CONSOLE_BITS     CONFIG_LPUART1_BITS
+#  define IMX9_CONSOLE_PARITY   CONFIG_LPUART1_PARITY
+#  define IMX9_CONSOLE_2STOP    CONFIG_LPUART1_2STOP
+#elif defined(CONFIG_LPUART2_SERIAL_CONSOLE)
+#  define IMX9_CONSOLE_DEVNUM   1
+#  define IMX9_CONSOLE_BASE     IMX9_LPUART2_BASE
+#  define IMX9_CONSOLE_BAUD     CONFIG_LPUART2_BAUD
+#  define IMX9_CONSOLE_BITS     CONFIG_LPUART2_BITS
+#  define IMX9_CONSOLE_PARITY   CONFIG_LPUART2_PARITY
+#  define IMX9_CONSOLE_2STOP    CONFIG_LPUART2_2STOP
+#elif defined(CONFIG_LPUART3_SERIAL_CONSOLE)
+#  define IMX9_CONSOLE_DEVNUM   2
+#  define IMX9_CONSOLE_BASE     IMX9_LPUART3_BASE
+#  define IMX9_CONSOLE_BAUD     CONFIG_LPUART3_BAUD
+#  define IMX9_CONSOLE_BITS     CONFIG_LPUART3_BITS
+#  define IMX9_CONSOLE_PARITY   CONFIG_LPUART3_PARITY
+#  define IMX9_CONSOLE_2STOP    CONFIG_LPUART3_2STOP
+#elif defined(CONFIG_LPUART4_SERIAL_CONSOLE)
+#  define IMX9_CONSOLE_DEVNUM   3
+#  define IMX9_CONSOLE_BASE     IMX9_LPUART4_BASE
+#  define IMX9_CONSOLE_BAUD     CONFIG_LPUART4_BAUD
+#  define IMX9_CONSOLE_BITS     CONFIG_LPUART4_BITS
+#  define IMX9_CONSOLE_PARITY   CONFIG_LPUART4_PARITY
+#  define IMX9_CONSOLE_2STOP    CONFIG_LPUART4_2STOP
+#elif defined(CONFIG_LPUART5_SERIAL_CONSOLE)
+#  define IMX9_CONSOLE_DEVNUM   4
+#  define IMX9_CONSOLE_BASE     IMX9_LPUART5_BASE
+#  define IMX9_CONSOLE_BAUD     CONFIG_LPUART5_BAUD
+#  define IMX9_CONSOLE_BITS     CONFIG_LPUART5_BITS
+#  define IMX9_CONSOLE_PARITY   CONFIG_LPUART5_PARITY
+#  define IMX9_CONSOLE_2STOP    CONFIG_LPUART5_2STOP
+#elif defined(CONFIG_LPUART6_SERIAL_CONSOLE)
+#  define IMX9_CONSOLE_DEVNUM   5
+#  define IMX9_CONSOLE_BASE     IMX9_LPUART6_BASE
+#  define IMX9_CONSOLE_BAUD     CONFIG_LPUART6_BAUD
+#  define IMX9_CONSOLE_BITS     CONFIG_LPUART6_BITS
+#  define IMX9_CONSOLE_PARITY   CONFIG_LPUART6_PARITY
+#  define IMX9_CONSOLE_2STOP    CONFIG_LPUART6_2STOP
+#elif defined(CONFIG_LPUART7_SERIAL_CONSOLE)
+#  define IMX9_CONSOLE_DEVNUM   6
+#  define IMX9_CONSOLE_BASE     IMX9_LPUART7_BASE
+#  define IMX9_CONSOLE_BAUD     CONFIG_LPUART7_BAUD
+#  define IMX9_CONSOLE_BITS     CONFIG_LPUART7_BITS
+#  define IMX9_CONSOLE_PARITY   CONFIG_LPUART7_PARITY
+#  define IMX9_CONSOLE_2STOP    CONFIG_LPUART7_2STOP
+#elif defined(CONFIG_LPUART8_SERIAL_CONSOLE)
+#  define IMX9_CONSOLE_DEVNUM   7
+#  define IMX9_CONSOLE_BASE     IMX9_LPUART8_BASE
+#  define IMX9_CONSOLE_BAUD     CONFIG_LPUART8_BAUD
+#  define IMX9_CONSOLE_BITS     CONFIG_LPUART8_BITS
+#  define IMX9_CONSOLE_PARITY   CONFIG_LPUART8_PARITY
+#  define IMX9_CONSOLE_2STOP    CONFIG_LPUART8_2STOP
+#endif
+
+#define ABS(n)     (((n) < 0) ? -(n) : (n))
+
+/* Clocking *****************************************************************/
+
+/* Functional clocking is provided via the  PCC.  The PCC clocking must
+ * be configured by board-specific logic prior to using the LPUART.
+ */
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+#ifdef IMX9_CONSOLE_BASE
+static const struct uart_config_s g_console_config =
+{
+  .baud      = IMX9_CONSOLE_BAUD,    /* Configured baud */
+  .parity    = IMX9_CONSOLE_PARITY,  /* 0=none, 1=odd, 2=even */
+  .bits      = IMX9_CONSOLE_BITS,    /* Number of bits (5-9) */
+  .stopbits2 = IMX9_CONSOLE_2STOP,   /* true: Configure with 2 stop bits instead of 1 */
+};
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: imx9_lowsetup
+ *
+ * Description:
+ *   Called at the very beginning of _start.  Performs low level
+ *   initialization including setup of the console UART.  This UART done
+ *   early so that the serial console is available for debugging very early
+ *   in the boot sequence.
+ *
+ ****************************************************************************/
+
+void imx9_lowsetup(void)
+{
+#ifndef CONFIG_SUPPRESS_LPUART_CONFIG
+
+#ifdef CONFIG_IMX9_LPUART1
+  /* Configure LPUART1 pins: RXD and TXD.  Also configure RTS and CTS if flow
+   * control is enabled.
+   */
+
+  imx9_iomux_configure(MUX_LPUART1_RX);
+  imx9_iomux_configure(MUX_LPUART1_TX);
+#ifdef CONFIG_LPUART1_OFLOWCONTROL
+  imx9_iomux_configure(MUX_LPUART1_CTS);
+#endif
+#if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART1_RS485RTSCONTROL)) || \
+     (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART1_IFLOWCONTROL)))
+  imx9_iomux_configure(MUX_LPUART1_RTS);
+#endif
+#endif
+
+#ifdef CONFIG_IMX9_LPUART2
+
+  /* Configure LPUART2 pins: RXD and TXD.  Also configure RTS and CTS if flow
+   * control is enabled.
+   */
+
+  imx9_iomux_configure(MUX_LPUART2_RX);
+  imx9_iomux_configure(MUX_LPUART2_TX);
+#ifdef CONFIG_LPUART2_OFLOWCONTROL
+  imx9_iomux_configure(MUX_LPUART2_CTS);
+#endif
+#if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART2_RS485RTSCONTROL)) || \
+     (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART2_IFLOWCONTROL)))
+  imx9_iomux_configure(MUX_LPUART2_RTS);
+#endif
+#endif
+
+#ifdef CONFIG_IMX9_LPUART3
+
+  /* Configure LPUART3 pins: RXD and TXD.  Also configure RTS and CTS if flow
+   * control is enabled.
+   */
+
+  imx9_iomux_configure(MUX_LPUART3_RX);
+  imx9_iomux_configure(MUX_LPUART3_TX);
+#ifdef CONFIG_LPUART3_OFLOWCONTROL
+  imx9_iomux_configure(MUX_LPUART3_CTS);
+#endif
+#if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART3_RS485RTSCONTROL)) || \
+     (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART3_IFLOWCONTROL)))
+  imx9_iomux_configure(MUX_LPUART3_RTS);
+#endif
+#endif
+
+#ifdef CONFIG_IMX9_LPUART4
+
+  /* Configure LPUART4 pins: RXD and TXD.  Also configure RTS and CTS if flow
+   * control is enabled.
+   */
+
+  imx9_iomux_configure(LPUART4_RX);
+  imx9_iomux_configure(LPUART4_TX);
+#ifdef CONFIG_LPUART4_OFLOWCONTROL
+  imx9_iomux_configure(LPUART4_CTS);
+#endif
+#if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART4_RS485RTSCONTROL)) || \
+     (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART4_IFLOWCONTROL)))
+  imx9_iomux_configure(LPUART4_RTS);
+#endif
+#endif
+
+#ifdef CONFIG_IMX9_LPUART5
+
+  /* Configure LPUART5 pins: RXD and TXD.  Also configure RTS and CTS if flow
+   * control is enabled.
+   */
+
+  imx9_iomux_configure(MUX_LPUART5_RX);
+  imx9_iomux_configure(MUX_LPUART5_TX);
+#ifdef CONFIG_LPUART5_OFLOWCONTROL
+  imx9_iomux_configure(MUX_LPUART5_CTS);
+#endif
+#if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART5_RS485RTSCONTROL)) || \
+     (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART5_IFLOWCONTROL)))
+  imx9_iomux_configure(MUX_LPUART5_RTS);
+#endif
+#endif
+
+#ifdef CONFIG_IMX9_LPUART6
+
+  /* Configure LPUART6 pins: RXD and TXD.  Also configure RTS and CTS if flow
+   * control is enabled.
+   */
+
+  imx9_iomux_configure(MUX_LPUART6_RX);
+  imx9_iomux_configure(MUX_LPUART6_TX);
+#ifdef CONFIG_LPUART6_OFLOWCONTROL
+  imx9_iomux_configure(MUX_LPUART6_CTS);
+#endif
+#if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART6_RS485RTSCONTROL)) || \
+     (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART6_IFLOWCONTROL)))
+  imx9_iomux_configure(MUX_LPUART6_RTS);
+#endif
+#endif
+
+#ifdef CONFIG_IMX9_LPUART7
+
+  /* Configure LPUART7 pins: RXD and TXD.  Also configure RTS and CTS if flow
+   * control is enabled.
+   */
+
+  imx9_iomux_configure(MUX_LPUART7_RX);
+  imx9_iomux_configure(MUX_LPUART7_TX);
+#ifdef CONFIG_LPUART7_OFLOWCONTROL
+  imx9_iomux_configure(MUX_LPUART7_CTS);
+#endif
+#if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART7_RS485RTSCONTROL)) || \
+     (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART7_IFLOWCONTROL)))
+  imx9_iomux_configure(MUX_LPUART7_RTS);
+#endif
+#endif
+
+#ifdef CONFIG_IMX9_LPUART8
+
+  /* Configure LPUART8 pins: RXD and TXD.  Also configure RTS and CTS if flow
+   * control is enabled.
+   */
+
+  imx9_iomux_configure(MUX_LPUART8_RX);
+  imx9_iomux_configure(MUX_LPUART8_TX);
+#ifdef CONFIG_LPUART0_OFLOWCONTROL
+  imx9_iomux_configure(MUX_LPUART8_CTS);
+#endif
+#if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART8_RS485RTSCONTROL)) || \
+     (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART8_IFLOWCONTROL)))
+  imx9_iomux_configure(MUX_LPUART8_RTS);
+#endif
+#endif
+
+#ifdef IMX9_CONSOLE_BASE
+  /* Configure the serial console for initial, non-interrupt driver mode */
+
+  imx9_lpuart_configure(IMX9_CONSOLE_BASE, IMX9_CONSOLE_DEVOFF,
+                        &g_console_config);
+#endif
+
+#endif /* CONFIG_SUPPRESS_LPUART_CONFIG */
+}
+
+/****************************************************************************
+ * Name: imx9_lpuart_configure
+ *
+ * Description:
+ *   Configure a UART for non-interrupt driven operation
+ *
+ ****************************************************************************/
+
+int imx9_lpuart_configure(uint32_t base, int uartnum,
+                          const struct uart_config_s *config)
+{
+  int lpuart_freq = 24000000;
+  uint16_t sbr;
+  uint16_t temp_sbr;
+  uint32_t osr;
+  uint32_t temp_osr;
+  int temp_diff;
+  int calculated_baud;
+  int baud_diff;
+  uint32_t regval;
+
+  /* Configure root clock to 24MHz OSC */
+
+  imx9_ccm_configure_root_clock(CCM_CR_LPUART1 + uartnum - 1, OSC_24M, 1);
+
+  /* Enable peripheral clock */
+
+  imx9_ccm_gate_on(CCM_LPCG_LPUART1 + uartnum - 1, true);
+
+  /* This LPUART instantiation uses a slightly different baud rate
+   * calculation.  The idea is to use the best OSR (over-sampling rate)
+   * possible.
+   *
+   * NOTE: OSR is typically hard-set to 16 in other LPUART instantiations
+   * loop to find the best OSR value possible, one that generates minimum
+   * baud_diff iterate through the rest of the supported values of OSR
+   */
+
+  baud_diff = config->baud;
+  osr       = 0;
+  sbr       = 0;
+
+  for (temp_osr = 4; temp_osr <= 32; temp_osr++)
+    {
+      /* Calculate the temporary sbr value   */
+
+      temp_sbr = (lpuart_freq / (config->baud * temp_osr));
+
+      /* Set temp_sbr to 1 if the sourceClockInHz can not satisfy the
+       * desired baud rate.
+       */
+
+      if (temp_sbr == 0)
+        {
+          temp_sbr = 1;
+        }
+
+      /* Calculate the baud rate based on the temporary OSR and SBR values */
+
+      calculated_baud = (lpuart_freq / (temp_osr * temp_sbr));
+      temp_diff       = ABS(calculated_baud - config->baud);
+
+      /* Select the better value between srb and (sbr + 1) */
+
+      calculated_baud = (lpuart_freq / (temp_osr * (temp_sbr + 1)));
+      if (temp_diff >
+          ABS(calculated_baud - config->baud))
+        {
+          temp_diff = ABS(calculated_baud - config->baud);
+          temp_sbr++;
+        }
+
+      if (temp_diff <= baud_diff)
+        {
+          baud_diff = temp_diff;
+          osr       = temp_osr;
+          sbr       = temp_sbr;
+        }
+    }
+
+  if (baud_diff > ((config->baud * 3) / 100))
+    {
+      /* Unacceptable baud rate difference of more than 3% */
+
+      return ERROR;
+    }
+
+  /* Reset all internal logic and registers, except the Global Register */
+
+  regval  = getreg32(base + IMX9_LPUART_GLOBAL_OFFSET);
+  regval |= LPUART_GLOBAL_RST;
+  putreg32(regval, base + IMX9_LPUART_GLOBAL_OFFSET);
+
+  regval &= ~LPUART_GLOBAL_RST;
+  putreg32(regval, base + IMX9_LPUART_GLOBAL_OFFSET);
+
+  /* Construct MODIR register */
+
+  regval = 0;
+
+  if (config->userts)
+    {
+      regval |= LPUART_MODIR_RXRTSE;
+    }
+  else if (config->users485)
+    {
+      /* Both TX and RX side can't control RTS, so this gives
+       * the RX side precedence. This should have been filtered
+       * in layers above anyway, but it's just a precaution.
+       */
+
+      regval |= LPUART_MODIR_TXRTSE;
+    }
+
+  if (config->usects)
+    {
+      regval |= LPUART_MODIR_TXCTSE;
+    }
+
+  if (config->invrts)
+    {
+      regval |= LPUART_MODIR_TXRTSPOL;
+    }
+
+  putreg32(regval, base + IMX9_LPUART_MODIR_OFFSET);
+
+  regval = 0;
+
+  if ((osr > 3) && (osr < 8))
+    {
+      regval |= LPUART_BAUD_BOTHEDGE;
+    }
+
+  if (config->stopbits2)
+    {
+      regval |= LPUART_BAUD_SBNS;
+    }
+
+  regval |= LPUART_BAUD_OSR(osr) | LPUART_BAUD_SBR(sbr);
+  putreg32(regval, base + IMX9_LPUART_BAUD_OFFSET);
+
+  regval = 0;
+  if (config->parity == 1)
+    {
+      regval |= LPUART_CTRL_PE | LPUART_CTRL_PT_ODD;
+    }
+  else if (config->parity == 2)
+    {
+      regval |= LPUART_CTRL_PE | LPUART_CTRL_PT_EVEN;
+    }
+
+  if (config->bits == 9 || (config->bits == 8 && config->parity != 0))
+    {
+      regval |= LPUART_CTRL_M;
+    }
+  else if ((config->bits == 8))
+    {
+      regval &= ~LPUART_CTRL_M;
+    }
+  else
+    {
+      /* REVISIT: Here should be added support of other bit modes. */
+
+      return -ENOSYS;
+    }
+
+  regval |= LPUART_CTRL_RE | LPUART_CTRL_TE;
+  putreg32(regval, base + IMX9_LPUART_CTRL_OFFSET);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: arm64_earlyprintinit
+ *
+ * Description:
+ *   Configure LPUART1 for non-interrupt driven operation
+ *
+ ****************************************************************************/
+
+void arm64_earlyprintinit(char ch)
+{
+  /* Assume bootloader has already set up the LPUART1 */
+}
+
+/****************************************************************************
+ * Name: arm64_lowputc
+ *
+ * Description:
+ *   Output a byte with as few system dependencies as possible.  This will
+ *   even work BEFORE the console is initialized if we are booting from U-
+ *   Boot (and the same UART is used for the console, of course.)
+ *
+ ****************************************************************************/
+
+void arm64_lowputc(char ch)
+{
+#ifdef IMX9_CONSOLE_BASE
+  while ((getreg32(IMX9_CONSOLE_BASE + IMX9_LPUART_STAT_OFFSET) &
+         LPUART_STAT_TDRE) == 0)
+    {
+    }
+
+  /* If the character to output is a newline,
+   * then pre-pend a carriage return
+   */
+
+  if (ch == '\n')
+    {
+      /* Send the carriage return by writing it into the UART_TXD register. */
+
+      putreg32((uint32_t)'\r',
+                IMX9_CONSOLE_BASE + IMX9_LPUART_DATA_OFFSET);
+
+      /* Wait for the transmit register to be emptied. When the TXFE bit is
+       * non-zero, the TX Buffer FIFO is empty.
+       */
+
+      while ((getreg32(IMX9_CONSOLE_BASE + IMX9_LPUART_STAT_OFFSET) &
+             LPUART_STAT_TDRE) == 0)
+        {
+        }
+    }
+
+  /* Send the character by writing it into the UART_TXD register. */
+
+  putreg32((uint32_t)ch, IMX9_CONSOLE_BASE + IMX9_LPUART_DATA_OFFSET);
+
+  /* Wait for the transmit register to be emptied. When the TXFE bit is
+   * non-zero, the TX Buffer FIFO is empty.
+   */
+
+  while ((getreg32(IMX9_CONSOLE_BASE + IMX9_LPUART_STAT_OFFSET) &
+         LPUART_STAT_TDRE) == 0)
+    {
+    }
+#endif
+}
diff --git a/arch/arm64/src/imx9/imx93_lowputc.S b/arch/arm64/src/imx9/imx9_lowputc.h
similarity index 50%
rename from arch/arm64/src/imx9/imx93_lowputc.S
rename to arch/arm64/src/imx9/imx9_lowputc.h
index f3311810e05dd..30aceed681c28 100644
--- a/arch/arm64/src/imx9/imx93_lowputc.S
+++ b/arch/arm64/src/imx9/imx9_lowputc.h
@@ -1,5 +1,5 @@
 /****************************************************************************
- * arch/arm64/src/imx9/imx93_lowputs.S
+ * arch/arm64/src/imx9/imx9_lowputc.h
  *
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements.  See the NOTICE file distributed with
@@ -16,70 +16,69 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  *
- ****************************************************************************
- *
- *    DESCRIPTION
- *       Wrapper for early printk
- *
- ***************************************************************************/
+ ****************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_IMX9_IMX9_LOWPUTC_H
+#define __ARCH_ARM_SRC_IMX9_IMX9_LOWPUTC_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
 
 #include <nuttx/config.h>
+#include <nuttx/compiler.h>
 
-#include "arm64_macro.inc"
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdbool.h>
 
-#include "hardware/imx9_lpuart.h"
-#include "hardware/imx93/imx93_memorymap.h"
+#include "arm64_internal.h"
 
 /****************************************************************************
- * Public Symbols
+ * Public Types
  ****************************************************************************/
 
-    .file    "imx93_lowputc.S"
+/* This structure describes the configuration of an UART */
 
-/****************************************************************************
- * Assembly Macros
- ****************************************************************************/
+struct uart_config_s
+{
+  uint32_t baud;          /* Configured baud */
+  uint8_t  parity;        /* 0=none, 1=odd, 2=even */
+  uint8_t  bits;          /* Number of bits (5-9) */
+  bool     stopbits2;     /* true: Configure with 2 stop bits instead of 1 */
+  bool     userts;        /* True: Assert RTS when there are data to be sent */
+  bool     invrts;        /* True: Invert sense of RTS pin (true=active high) */
+  bool     usects;        /* True: Condition transmission on CTS asserted */
+  bool     users485;      /* True: Assert RTS while transmission progresses */
+};
 
 /****************************************************************************
- * Private Functions
+ * Public Function Prototypes
  ****************************************************************************/
 
 /****************************************************************************
- * Public Functions
+ * Name: imx9_lowsetup
+ *
+ * Description:
+ *   Called at the very beginning of _start.  Performs low level
+ *   initialization including setup of the console UART.  This UART done
+ *   early so that the serial console is available for debugging very early
+ *   in the boot sequence.
+ *
  ****************************************************************************/
 
-/* PL011 UART initialization */
-
-GTEXT(arm64_earlyprintinit)
-SECTION_FUNC(text, arm64_earlyprintinit)
-    /* TODO: Assumes u-boot has set us up, assumption is fine for now */
-    ret
+void imx9_lowsetup(void);
 
-/* i.MX93 wait LPUART to be ready to transmit
- * rb: register which contains the UART base address
- * rc: scratch register
- */
-.macro early_uart_ready rb, rc
-1:
-    ldr   \rc, [\rb, #UARTSTAT]   /* <- Flag register */
-    tst   \rc, #UARTSTAT_TDRE     /* Check FIFO EMPTY bit */
-    beq   1b                      /* Wait for the UART to be ready */
-.endm
+/****************************************************************************
+ * Name: imx9_lpuart_configure
+ *
+ * Description:
+ *   Configure a UART for non-interrupt driven operation
+ *
+ ****************************************************************************/
 
-/* i.MX93 LPUART transmit character
- * rb: register which contains the UART base address
- * rt: register which contains the character to transmit */
-.macro early_uart_transmit rb, rt
-    str   \rt, [\rb, #UARTDATA]  /* -> Data Register */
-.endm
+int imx9_lpuart_configure(uint32_t base,
+                          int uartnum,
+                          const struct uart_config_s *config);
 
-/*
- * Print a character on the UART - this function is called by C
- * w0: character to print
- */
-GTEXT(arm64_lowputc)
-SECTION_FUNC(text, arm64_lowputc)
-    ldr   x15, =IMX9_LPUART1_BASE
-    early_uart_ready x15, w2
-    early_uart_transmit x15, w0
-    ret
+#endif /* __ARCH_ARM_SRC_IMX9_IMX9_LOWPUTC_H */
diff --git a/arch/arm64/src/imx9/imx9_lpuart.c b/arch/arm64/src/imx9/imx9_lpuart.c
index 2fa4307f4c259..6743d32ae3944 100644
--- a/arch/arm64/src/imx9/imx9_lpuart.c
+++ b/arch/arm64/src/imx9/imx9_lpuart.c
@@ -23,6 +23,7 @@
  ****************************************************************************/
 
 #include <nuttx/config.h>
+
 #include <sys/types.h>
 #include <stdint.h>
 #include <stdbool.h>
@@ -33,838 +34,2663 @@
 #include <debug.h>
 
 #ifdef CONFIG_SERIAL_TERMIOS
-#  include <termios.h>
-#endif
+#  include <termios.h>
+#endif
+
+#include <nuttx/irq.h>
+#include <nuttx/arch.h>
+#include <nuttx/spinlock.h>
+#include <nuttx/init.h>
+#include <nuttx/power/pm.h>
+#include <nuttx/fs/ioctl.h>
+#include <nuttx/serial/serial.h>
+
+#include <arch/board/board.h>
+
+#include "arm64_internal.h"
+#include "hardware/imx9_lpuart.h"
+#include "hardware/imx9_pinmux.h"
+#include "imx9_lowputc.h"
+#include "imx9_serial.h"
+
+#if defined(SERIAL_HAVE_TXDMA) || defined(SERIAL_HAVE_RXDMA)
+#  include "chip.h"
+#  include "imx9_edma.h"
+#  include "hardware/imx9_dmamux.h"
+#endif
+
+#ifdef USE_SERIALDRIVER
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* The DMA buffer size when using RX DMA to emulate a FIFO.
+ *
+ * When streaming data, the generic serial layer will be called every time
+ * the FIFO receives half this number of bytes.
+ *
+ * This buffer size should be an even multiple of the Cortex-A55 D-Cache line
+ * size, ARMV8M_DCACHE_LINESIZE, so that it can be individually invalidated.
+ */
+
+#  if !defined(ARMV8M_DCACHE_LINESIZE) || ARMV8M_DCACHE_LINESIZE == 0
+#    undef ARMV8M_DCACHE_LINESIZE
+#    define ARMV8M_DCACHE_LINESIZE 64
+#  endif
+
+#  if !defined(CONFIG_IMX9_SERIAL_RXDMA_BUFFER_SIZE) || \
+      (CONFIG_IMX9_SERIAL_RXDMA_BUFFER_SIZE < ARMV8M_DCACHE_LINESIZE)
+#    undef CONFIG_IMX9_SERIAL_RXDMA_BUFFER_SIZE
+#    define CONFIG_IMX9_SERIAL_RXDMA_BUFFER_SIZE ARMV8M_DCACHE_LINESIZE
+#  endif
+
+#  define RXDMA_BUFFER_MASK   (ARMV8M_DCACHE_LINESIZE - 1)
+#  define RXDMA_BUFFER_SIZE   ((CONFIG_IMX9_SERIAL_RXDMA_BUFFER_SIZE \
+                                + RXDMA_BUFFER_MASK) & ~RXDMA_BUFFER_MASK)
+
+/* The DMA buffer size when using TX DMA.
+ *
+ * This TX buffer size should be an even multiple of the Cortex-A55 D-Cache
+ * line size, ARMV8M_DCACHE_LINESIZE, so that it can be individually
+ * invalidated.
+ */
+
+#define TXDMA_BUFFER_MASK   (ARMV8M_DCACHE_LINESIZE - 1)
+#define TXDMA_BUFFER_SIZE   ((CONFIG_IMX9_SERIAL_RXDMA_BUFFER_SIZE \
+                              + RXDMA_BUFFER_MASK) & ~RXDMA_BUFFER_MASK)
+
+/* Buffers need to be aligned and multiples of ARMV8M_DCACHE_LINESIZE */
+
+#if defined(CONFIG_ARM64_DCACHE_DISABLE)
+#  define TXDMA_BUF_SIZE(b)  (b)
+#  define TXDMA_BUF_ALIGN
+#else
+#  define TXDMA_BUF_SIZE(b) (((b) + TXDMA_BUFFER_MASK) & ~TXDMA_BUFFER_MASK)
+#  define TXDMA_BUF_ALIGN   aligned_data(ARMV8M_DCACHE_LINESIZE);
+#endif
+
+#if !defined(CONFIG_LPUART1_TXDMA)
+#  define LPUART1_TXBUFSIZE_ADJUSTED  CONFIG_LPUART1_TXBUFSIZE
+#  define LPUART1_TXBUFSIZE_ALGN
+#else
+#  define LPUART1_TXBUFSIZE_ADJUSTED TXDMA_BUF_SIZE(CONFIG_LPUART1_TXBUFSIZE)
+#  define LPUART1_TXBUFSIZE_ALGN TXDMA_BUF_ALIGN
+#endif
+
+#if !defined(CONFIG_LPUART2_TXDMA)
+#  define LPUART2_TXBUFSIZE_ADJUSTED  CONFIG_LPUART2_TXBUFSIZE
+#  define LPUART2_TXBUFSIZE_ALGN
+#else
+#  define LPUART2_TXBUFSIZE_ADJUSTED TXDMA_BUF_SIZE(CONFIG_LPUART2_TXBUFSIZE)
+#  define LPUART2_TXBUFSIZE_ALGN TXDMA_BUF_ALIGN
+#endif
+
+#if !defined(CONFIG_LPUART3_TXDMA)
+#  define LPUART3_TXBUFSIZE_ADJUSTED  CONFIG_LPUART3_TXBUFSIZE
+#  define LPUART3_TXBUFSIZE_ALGN
+#else
+#  define LPUART3_TXBUFSIZE_ADJUSTED TXDMA_BUF_SIZE(CONFIG_LPUART3_TXBUFSIZE)
+#  define LPUART3_TXBUFSIZE_ALGN TXDMA_BUF_ALIGN
+#endif
+
+#if !defined(CONFIG_LPUART4_TXDMA)
+#  define LPUART4_TXBUFSIZE_ADJUSTED  CONFIG_LPUART4_TXBUFSIZE
+#  define LPUART4_TXBUFSIZE_ALGN
+#else
+#  define LPUART4_TXBUFSIZE_ADJUSTED TXDMA_BUF_SIZE(CONFIG_LPUART4_TXBUFSIZE)
+#  define LPUART4_TXBUFSIZE_ALGN TXDMA_BUF_ALIGN
+#endif
+
+#if !defined(CONFIG_LPUART5_TXDMA)
+#  define LPUART5_TXBUFSIZE_ADJUSTED  CONFIG_LPUART5_TXBUFSIZE
+#  define LPUART5_TXBUFSIZE_ALGN
+#else
+#  define LPUART5_TXBUFSIZE_ADJUSTED TXDMA_BUF_SIZE(CONFIG_LPUART5_TXBUFSIZE)
+#  define LPUART5_TXBUFSIZE_ALGN TXDMA_BUF_ALIGN
+#endif
+
+#if !defined(CONFIG_LPUART6_TXDMA)
+#  define LPUART6_TXBUFSIZE_ADJUSTED  CONFIG_LPUART6_TXBUFSIZE
+#  define LPUART6_TXBUFSIZE_ALGN
+#else
+#  define LPUART6_TXBUFSIZE_ADJUSTED TXDMA_BUF_SIZE(CONFIG_LPUART6_TXBUFSIZE)
+#  define LPUART6_TXBUFSIZE_ALGN TXDMA_BUF_ALIGN
+#endif
+
+#if !defined(CONFIG_LPUART7_TXDMA)
+#  define LPUART7_TXBUFSIZE_ADJUSTED  CONFIG_LPUART7_TXBUFSIZE
+#  define LPUART7_TXBUFSIZE_ALGN
+#else
+#  define LPUART7_TXBUFSIZE_ADJUSTED TXDMA_BUF_SIZE(CONFIG_LPUART7_TXBUFSIZE)
+#  define LPUART7_TXBUFSIZE_ALGN TXDMA_BUF_ALIGN
+#endif
+
+#if !defined(CONFIG_LPUART8_TXDMA)
+#  define LPUART8_TXBUFSIZE_ADJUSTED  CONFIG_LPUART8_TXBUFSIZE
+#  define LPUART8_TXBUFSIZE_ALGN
+#else
+#  define LPUART8_TXBUFSIZE_ADJUSTED TXDMA_BUF_SIZE(CONFIG_LPUART8_TXBUFSIZE)
+#  define LPUART8_TXBUFSIZE_ALGN TXDMA_BUF_ALIGN
+#endif
+
+/* Which LPUART with be console? */
+
+#if defined(CONFIG_LPUART1_SERIAL_CONSOLE)
+#  define CONSOLE_DEV         g_lpuart1priv /* LPUART1 is console */
+#  if defined(CONFIG_LPUART1_RXDMA)
+#    define SERIAL_HAVE_CONSOLE_RXDMA 1
+#  endif
+#  if defined(CONFIG_LPUART1_TXDMA)
+#    define SERIAL_HAVE_CONSOLE_TXDMA 1
+#  endif
+#elif defined(CONFIG_LPUART2_SERIAL_CONSOLE)
+#  define CONSOLE_DEV         g_lpuart2priv /* LPUART2 is console */
+#  if defined(CONFIG_LPUART2_RXDMA)
+#    define SERIAL_HAVE_CONSOLE_RXDMA 1
+#  endif
+#  if defined(CONFIG_LPUART2_TXDMA)
+#    define SERIAL_HAVE_CONSOLE_TXDMA 1
+#  endif
+#elif defined(CONFIG_LPUART3_SERIAL_CONSOLE)
+#  define CONSOLE_DEV         g_lpuart3priv /* LPUART3 is console */
+#  if defined(CONFIG_LPUART3_RXDMA)
+#    define SERIAL_HAVE_CONSOLE_RXDMA 1
+#  endif
+#  if defined(CONFIG_LPUART3_TXDMA)
+#    define SERIAL_HAVE_CONSOLE_TXDMA 1
+#  endif
+#elif defined(CONFIG_LPUART4_SERIAL_CONSOLE)
+#  define CONSOLE_DEV         g_lpuart4priv /* LPUART4 is console */
+#  if defined(CONFIG_LPUART4_RXDMA)
+#    define SERIAL_HAVE_CONSOLE_RXDMA 1
+#  endif
+#  if defined(CONFIG_LPUART4_TXDMA)
+#    define SERIAL_HAVE_CONSOLE_TXDMA 1
+#  endif
+#elif defined(CONFIG_LPUART5_SERIAL_CONSOLE)
+#  define CONSOLE_DEV         g_lpuart5priv /* LPUART5 is console */
+#  if defined(CONFIG_LPUART5_RXDMA)
+#    define SERIAL_HAVE_CONSOLE_RXDMA 1
+#  endif
+#  if defined(CONFIG_LPUART5_TXDMA)
+#    define SERIAL_HAVE_CONSOLE_TXDMA 1
+#  endif
+#elif defined(CONFIG_LPUART6_SERIAL_CONSOLE)
+#  define CONSOLE_DEV         g_lpuart6priv /* LPUART6 is console */
+#  if defined(CONFIG_LPUART6_RXDMA)
+#    define SERIAL_HAVE_CONSOLE_RXDMA 1
+#  endif
+#  if defined(CONFIG_LPUART6_TXDMA)
+#    define SERIAL_HAVE_CONSOLE_TXDMA 1
+#  endif
+#elif defined(CONFIG_LPUART7_SERIAL_CONSOLE)
+#  define CONSOLE_DEV         g_lpuart7priv /* LPUART7 is console */
+#  if defined(CONFIG_LPUART7_RXDMA)
+#    define SERIAL_HAVE_CONSOLE_RXDMA 1
+#  endif
+#  if defined(CONFIG_LPUART7_TXDMA)
+#    define SERIAL_HAVE_CONSOLE_TXDMA 1
+#  endif
+#elif defined(CONFIG_LPUART8_SERIAL_CONSOLE)
+#  define CONSOLE_DEV         g_lpuart8priv /* LPUART8 is console */
+#  if defined(CONFIG_LPUART8_RXDMA)
+#    define SERIAL_HAVE_CONSOLE_RXDMA 1
+#  endif
+#  if defined(CONFIG_LPUART8_TXDMA)
+#    define SERIAL_HAVE_CONSOLE_TXDMA 1
+#  endif
+#endif
+
+#if defined(SERIAL_HAVE_CONSOLE_RXDMA) || defined(SERIAL_HAVE_CONSOLE_TXDMA)
+#  define SERIAL_HAVE_CONSOLE_DMA
+#endif
+
+#ifdef CONFIG_IMX9_LPUART1
+#define TTYS0_DEV           g_lpuart1priv /* LPUART1 is ttyS0 */
+#endif
+
+#ifdef CONFIG_IMX9_LPUART2
+#define TTYS1_DEV           g_lpuart2priv /* LPUART2 is ttyS1 */
+#endif
+
+#ifdef CONFIG_IMX9_LPUART3
+#define TTYS2_DEV           g_lpuart3priv /* LPUART3 is ttyS2 */
+#endif
+
+#ifdef CONFIG_IMX9_LPUART4
+#define TTYS3_DEV           g_lpuart4priv /* LPUART4 is ttyS3 */
+#endif
+
+#ifdef CONFIG_IMX9_LPUART5
+#define TTYS4_DEV           g_lpuart5priv /* LPUART5 is ttyS4 */
+#endif
+
+#ifdef CONFIG_IMX9_LPUART6
+#define TTYS5_DEV           g_lpuart6priv /* LPUART6 is ttyS5 */
+#endif
+
+#ifdef CONFIG_IMX9_LPUART7
+#define TTYS6_DEV           g_lpuart7priv /* LPUART7 is ttyS6 */
+#endif
+
+#ifdef CONFIG_IMX9_LPUART8
+#define TTYS7_DEV           g_lpuart8priv /* LPUART8 is ttyS7 */
+#endif
+
+/* Power management definitions */
+
+#if defined(CONFIG_PM) && !defined(CONFIG_IMX9_PM_SERIAL_ACTIVITY)
+#  define CONFIG_IMX9_PM_SERIAL_ACTIVITY 10
+#endif
+
+#if defined(CONFIG_PM)
+#  define PM_IDLE_DOMAIN      0 /* Revisit */
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct imx9_uart_s
+{
+  struct uart_dev_s dev;    /* Generic UART device */
+  const uint32_t uartbase;  /* Base address of UART registers */
+  const int uartnum;        /* LPUART number 1-8 */
+  uint32_t baud;            /* Configured baud */
+  uint32_t ie;              /* Saved enabled interrupts */
+  uint8_t  irq;             /* IRQ associated with this UART */
+  uint8_t  parity;          /* 0=none, 1=odd, 2=even */
+  uint8_t  bits;            /* Number of bits (7 or 8) */
+#if defined(CONFIG_SERIAL_RS485CONTROL) || defined(CONFIG_SERIAL_IFLOWCONTROL)
+  uint8_t  inviflow:1;      /* Invert RTS sense */
+  const uint32_t rts_gpio;  /* LPUART RTS GPIO pin configuration */
+#endif
+#ifdef CONFIG_SERIAL_OFLOWCONTROL
+  const uint32_t cts_gpio;  /* LPUART CTS GPIO pin configuration */
+#endif
+  uint8_t  stopbits2:1;     /* 1: Configure with 2 stop bits vs 1 */
+#ifdef CONFIG_SERIAL_IFLOWCONTROL
+  uint8_t  iflow:1;         /* input flow control (RTS) enabled */
+#endif
+#ifdef CONFIG_SERIAL_OFLOWCONTROL
+  uint8_t  oflow:1;         /* output flow control (CTS) enabled */
+#endif
+#ifdef CONFIG_SERIAL_RS485CONTROL
+  uint8_t rs485mode:1;      /* We are in RS485 (RTS on TX) mode */
+#endif
+  /* TX DMA state */
+
+#ifdef SERIAL_HAVE_TXDMA
+  const unsigned int txch;  /* DMAMUX source of TX DMA request */
+  DMACH_HANDLE       txdma; /* currently-open transmit DMA stream */
+#endif
+
+  /* RX DMA state */
+
+#ifdef SERIAL_HAVE_RXDMA
+  const unsigned int rxch;          /* DMAMUX source of RX DMA request */
+  DMACH_HANDLE       rxdma;         /* currently-open receive DMA stream */
+  bool               rxenable;      /* DMA-based reception en/disable */
+  uint32_t           rxdmanext;     /* Next byte in the DMA buffer to be read */
+#ifndef CONFIG_ARM64_DCACHE_DISABLE
+  uint32_t           rxdmaavail;    /* Number of bytes available without need to
+                                     * to invalidate the data cache */
+#endif
+  char *const        rxfifo;        /* Receive DMA buffer */
+#endif
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static inline uint32_t imx9_serialin(struct imx9_uart_s *priv,
+                                      uint32_t offset);
+static inline void imx9_serialout(struct imx9_uart_s *priv,
+                                   uint32_t offset, uint32_t value);
+static inline void imx9_disableuartint(struct imx9_uart_s *priv,
+                                        uint32_t *ie);
+static inline void imx9_restoreuartint(struct imx9_uart_s *priv,
+                                        uint32_t ie);
+
+static int  imx9_setup(struct uart_dev_s *dev);
+static void imx9_shutdown(struct uart_dev_s *dev);
+static int  imx9_attach(struct uart_dev_s *dev);
+static void imx9_detach(struct uart_dev_s *dev);
+static int  imx9_interrupt(int irq, void *context, void *arg);
+static int  imx9_ioctl(struct file *filep, int cmd, unsigned long arg);
+#if !defined(SERIAL_HAVE_ONLY_RXDMA)
+static int  imx9_receive(struct uart_dev_s *dev, unsigned int *status);
+static void imx9_rxint(struct uart_dev_s *dev, bool enable);
+static bool imx9_rxavailable(struct uart_dev_s *dev);
+#endif
+#if !defined(SERIAL_HAVE_ONLY_TXDMA)
+static void imx9_txint(struct uart_dev_s *dev, bool enable);
+#endif
+
+#ifdef CONFIG_SERIAL_IFLOWCONTROL
+static bool imx9_rxflowcontrol(struct uart_dev_s *dev,
+                                  unsigned int nbuffered, bool upper);
+#endif
+static void imx9_send(struct uart_dev_s *dev, int ch);
+
+static bool imx9_txready(struct uart_dev_s *dev);
+
+#ifdef SERIAL_HAVE_TXDMA
+static void imx9_dma_send(struct uart_dev_s *dev);
+static void imx9_dma_txint(struct uart_dev_s *dev, bool enable);
+static void imx9_dma_txavailable(struct uart_dev_s *dev);
+static void imx9_dma_txcallback(DMACH_HANDLE handle, void *arg, bool done,
+                                   int result);
+#endif
+
+#if defined(SERIAL_HAVE_RXDMA) || defined(SERIAL_HAVE_TXDMA)
+static int  imx9_dma_setup(struct uart_dev_s *dev);
+static void imx9_dma_shutdown(struct uart_dev_s *dev);
+#endif
+
+#ifdef SERIAL_HAVE_RXDMA
+static int  imx9_dma_receive(struct uart_dev_s *dev,
+                                unsigned int *status);
+#ifdef CONFIG_PM
+static void imx9_dma_reenable(struct imx9_uart_s *priv);
+#endif
+static void imx9_dma_rxint(struct uart_dev_s *dev, bool enable);
+static bool imx9_dma_rxavailable(struct uart_dev_s *dev);
+
+static void imx9_dma_rxcallback(DMACH_HANDLE handle, void *arg, bool done,
+                                   int result);
+#endif
+
+static bool imx9_txempty(struct uart_dev_s *dev);
+
+#ifdef CONFIG_PM
+static void up_pm_notify(struct pm_callback_s *cb, int dowmin,
+                         enum pm_state_e pmstate);
+static int  up_pm_prepare(struct pm_callback_s *cb, int domain,
+                          enum pm_state_e pmstate);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* Serial driver UART operations */
+
+#if !defined(SERIAL_HAVE_ONLY_TXDMA) && !defined(SERIAL_HAVE_ONLY_RXDMA)
+static const struct uart_ops_s g_lpuart_ops =
+{
+  .setup          = imx9_setup,
+  .shutdown       = imx9_shutdown,
+  .attach         = imx9_attach,
+  .detach         = imx9_detach,
+  .ioctl          = imx9_ioctl,
+  .receive        = imx9_receive,
+  .rxint          = imx9_rxint,
+  .rxavailable    = imx9_rxavailable,
+#ifdef CONFIG_SERIAL_IFLOWCONTROL
+  .rxflowcontrol  = imx9_rxflowcontrol,
+#endif
+  .send           = imx9_send,
+  .txint          = imx9_txint,
+  .txready        = imx9_txready,
+  .txempty        = imx9_txempty,
+};
+#endif
+
+#if defined(SERIAL_HAVE_RXDMA) && defined(SERIAL_HAVE_TXDMA)
+static const struct uart_ops_s g_lpuart_rxtxdma_ops =
+{
+  .setup          = imx9_dma_setup,
+  .shutdown       = imx9_dma_shutdown,
+  .attach         = imx9_attach,
+  .detach         = imx9_detach,
+  .ioctl          = imx9_ioctl,
+  .receive        = imx9_dma_receive,
+  .rxint          = imx9_dma_rxint,
+  .rxavailable    = imx9_dma_rxavailable,
+#ifdef CONFIG_SERIAL_IFLOWCONTROL
+  .rxflowcontrol  = imx9_rxflowcontrol,
+#endif
+  .send           = imx9_send,
+  .txint          = imx9_dma_txint,
+  .txready        = imx9_txready,
+  .txempty        = imx9_txempty,
+  .dmatxavail     = imx9_dma_txavailable,
+  .dmasend        = imx9_dma_send,
+};
+#endif
+
+#if !defined(SERIAL_HAVE_ONLY_DMA) && defined(SERIAL_HAVE_RXDMA)
+static const struct uart_ops_s g_lpuart_rxdma_ops =
+{
+  .setup          = imx9_dma_setup,
+  .shutdown       = imx9_dma_shutdown,
+  .attach         = imx9_attach,
+  .detach         = imx9_detach,
+  .ioctl          = imx9_ioctl,
+  .receive        = imx9_dma_receive,
+  .rxint          = imx9_dma_rxint,
+  .rxavailable    = imx9_dma_rxavailable,
+#ifdef CONFIG_SERIAL_IFLOWCONTROL
+  .rxflowcontrol  = imx9_rxflowcontrol,
+#endif
+  .send           = imx9_send,
+  .txint          = imx9_txint,
+  .txready        = imx9_txready,
+  .txempty        = imx9_txempty,
+};
+#endif
+
+#if !defined(SERIAL_HAVE_ONLY_DMA) && defined(SERIAL_HAVE_TXDMA)
+static const struct uart_ops_s g_lpuart_txdma_ops =
+{
+    .setup          = imx9_dma_setup,
+    .shutdown       = imx9_dma_shutdown,
+    .attach         = imx9_attach,
+    .detach         = imx9_detach,
+    .ioctl          = imx9_ioctl,
+    .receive        = imx9_receive,
+    .rxint          = imx9_rxint,
+    .rxavailable    = imx9_rxavailable,
+  #ifdef CONFIG_SERIAL_IFLOWCONTROL
+    .rxflowcontrol  = imx9_rxflowcontrol,
+  #endif
+    .send           = imx9_send,
+    .txint          = imx9_dma_txint,
+    .txready        = imx9_txready,
+    .txempty        = imx9_txempty,
+    .dmatxavail     = imx9_dma_txavailable,
+    .dmasend        = imx9_dma_send,
+};
+#endif
+
+/* Avoid unused warning */
+#if !defined(SERIAL_HAVE_ONLY_DMA) && defined(SERIAL_HAVE_RXDMA)
+const struct uart_ops_s *g_o0 = &g_lpuart_rxdma_ops;
+#endif
+#if !defined(SERIAL_HAVE_ONLY_DMA) && defined(SERIAL_HAVE_TXDMA)
+const struct uart_ops_s *g_o1 = &g_lpuart_txdma_ops;
+#endif
+
+/* I/O buffers */
+
+#ifdef CONFIG_LPUART1_RXDMA
+static char g_lpuart1rxfifo[RXDMA_BUFFER_SIZE]
+  aligned_data(ARMV8M_DCACHE_LINESIZE);
+#endif
+
+# ifdef CONFIG_LPUART2_RXDMA
+static char g_lpuart2rxfifo[RXDMA_BUFFER_SIZE]
+  aligned_data(ARMV8M_DCACHE_LINESIZE);
+#endif
+
+#ifdef CONFIG_LPUART3_RXDMA
+static char g_lpuart3rxfifo[RXDMA_BUFFER_SIZE]
+  aligned_data(ARMV8M_DCACHE_LINESIZE);
+#endif
+
+#ifdef CONFIG_LPUART4_RXDMA
+static char g_lpuart4rxfifo[RXDMA_BUFFER_SIZE]
+  aligned_data(ARMV8M_DCACHE_LINESIZE);
+#endif
+
+#ifdef CONFIG_LPUART5_RXDMA
+static char g_lpuart5rxfifo[RXDMA_BUFFER_SIZE]
+  aligned_data(ARMV8M_DCACHE_LINESIZE);
+#endif
+
+#ifdef CONFIG_LPUART6_RXDMA
+static char g_lpuart6rxfifo[RXDMA_BUFFER_SIZE]
+  aligned_data(ARMV8M_DCACHE_LINESIZE);
+#endif
+
+#ifdef CONFIG_LPUART7_RXDMA
+static char g_lpuart7rxfifo[RXDMA_BUFFER_SIZE]
+  aligned_data(ARMV8M_DCACHE_LINESIZE);
+#endif
+
+#ifdef CONFIG_LPUART8_RXDMA
+static char g_lpuart8rxfifo[RXDMA_BUFFER_SIZE]
+  aligned_data(ARMV8M_DCACHE_LINESIZE);
+#endif
+
+#ifdef CONFIG_IMX9_LPUART1
+static char g_lpuart1rxbuffer[CONFIG_LPUART1_RXBUFSIZE];
+static char g_lpuart1txbuffer[LPUART1_TXBUFSIZE_ADJUSTED]
+  LPUART1_TXBUFSIZE_ALGN;
+#endif
+
+#ifdef CONFIG_IMX9_LPUART2
+static char g_lpuart2rxbuffer[CONFIG_LPUART2_RXBUFSIZE];
+static char g_lpuart2txbuffer[LPUART2_TXBUFSIZE_ADJUSTED]
+  LPUART2_TXBUFSIZE_ALGN;
+#endif
+
+#ifdef CONFIG_IMX9_LPUART3
+static char g_lpuart3rxbuffer[CONFIG_LPUART3_RXBUFSIZE];
+static char g_lpuart3txbuffer[LPUART3_TXBUFSIZE_ADJUSTED]
+  LPUART3_TXBUFSIZE_ALGN;
+#endif
+
+#ifdef CONFIG_IMX9_LPUART4
+static char g_lpuart4rxbuffer[CONFIG_LPUART4_RXBUFSIZE];
+static char g_lpuart4txbuffer[LPUART4_TXBUFSIZE_ADJUSTED]
+  LPUART4_TXBUFSIZE_ALGN;
+#endif
+
+#ifdef CONFIG_IMX9_LPUART5
+static char g_lpuart5rxbuffer[CONFIG_LPUART5_RXBUFSIZE];
+static char g_lpuart5txbuffer[LPUART5_TXBUFSIZE_ADJUSTED]
+  LPUART5_TXBUFSIZE_ALGN;
+#endif
+
+#ifdef CONFIG_IMX9_LPUART6
+static char g_lpuart6rxbuffer[CONFIG_LPUART6_RXBUFSIZE];
+static char g_lpuart6txbuffer[LPUART6_TXBUFSIZE_ADJUSTED]
+  LPUART6_TXBUFSIZE_ALGN;
+#endif
+
+#ifdef CONFIG_IMX9_LPUART7
+static char g_lpuart7rxbuffer[CONFIG_LPUART7_RXBUFSIZE];
+static char g_lpuart7txbuffer[LPUART7_TXBUFSIZE_ADJUSTED]
+  LPUART7_TXBUFSIZE_ALGN;
+#endif
+
+#ifdef CONFIG_IMX9_LPUART8
+static char g_lpuart8rxbuffer[CONFIG_LPUART8_RXBUFSIZE];
+static char g_lpuart8txbuffer[LPUART8_TXBUFSIZE_ADJUSTED] \
+  LPUART8_TXBUFSIZE_ALGN;
+#endif
+
+#ifdef CONFIG_IMX9_LPUART1
+static struct imx9_uart_s g_lpuart1priv =
+{
+  .dev =
+    {
+      .recv     =
+      {
+        .size   = CONFIG_LPUART1_RXBUFSIZE,
+        .buffer = g_lpuart1rxbuffer,
+      },
+      .xmit     =
+      {
+        .size   = CONFIG_LPUART1_TXBUFSIZE,
+        .buffer = g_lpuart1txbuffer,
+      },
+#  if defined(CONFIG_LPUART1_RXDMA) && defined(CONFIG_LPUART1_TXDMA)
+        .ops    = &g_lpuart_rxtxdma_ops,
+#  elif defined(CONFIG_LPUART1_RXDMA) && !defined(CONFIG_LPUART1_TXDMA)
+        .ops    = &g_lpuart_rxdma_ops,
+#  elif !defined(CONFIG_LPUART1_RXDMA) && defined(CONFIG_LPUART1_TXDMA)
+        .ops    = &g_lpuart_txdma_ops,
+#  else
+        .ops    = &g_lpuart_ops,
+#  endif
+    },
+
+  .uartbase     = IMX9_LPUART1_BASE,
+  .uartnum      = 1,
+  .baud         = CONFIG_LPUART1_BAUD,
+  .irq          = IMX9_IRQ_LPUART1,
+  .parity       = CONFIG_LPUART1_PARITY,
+  .bits         = CONFIG_LPUART1_BITS,
+  .stopbits2    = CONFIG_LPUART1_2STOP,
+#  if defined(CONFIG_SERIAL_OFLOWCONTROL) && defined(CONFIG_LPUART1_OFLOWCONTROL)
+  .oflow        = 1,
+  .cts_gpio     = GPIO_LPUART1_CTS,
+#  endif
+#  if defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART1_IFLOWCONTROL)
+  .iflow        = 1,
+#  endif
+#  if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART1_RS485RTSCONTROL)) || \
+      (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART1_IFLOWCONTROL)))
+  .rts_gpio     = GPIO_LPUART1_RTS,
+#  endif
+
+#  if (defined(CONFIG_SERIAL_RS485CONTROL) || defined(CONFIG_SERIAL_IFLOWCONTROL)) && \
+      defined(CONFIG_LPUART1_INVERTIFLOWCONTROL)
+  .inviflow     = 1,
+#  endif
+
+#  if defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART1_RS485RTSCONTROL)
+  .rs485mode    = 1,
+#  endif
+
+#  ifdef CONFIG_LPUART1_TXDMA
+  .txch = DMA_REQUEST_MUXLPUART1TX,
+#  endif
+#  ifdef CONFIG_LPUART1_RXDMA
+  .rxch = DMA_REQUEST_MUXLPUART1RX,
+  .rxfifo        = g_lpuart1rxfifo,
+#  endif
+};
+#endif
+
+#ifdef CONFIG_IMX9_LPUART2
+static struct imx9_uart_s g_lpuart2priv =
+{
+  .dev =
+    {
+      .recv     =
+      {
+        .size   = CONFIG_LPUART2_RXBUFSIZE,
+        .buffer = g_lpuart2rxbuffer,
+      },
+      .xmit     =
+      {
+        .size   = CONFIG_LPUART2_TXBUFSIZE,
+        .buffer = g_lpuart2txbuffer,
+      },
+#  if defined(CONFIG_LPUART2_RXDMA) && defined(CONFIG_LPUART2_TXDMA)
+        .ops    = &g_lpuart_rxtxdma_ops,
+#  elif defined(CONFIG_LPUART2_RXDMA) && !defined(CONFIG_LPUART2_TXDMA)
+        .ops    = &g_lpuart_rxdma_ops,
+#  elif !defined(CONFIG_LPUART2_RXDMA) && defined(CONFIG_LPUART2_TXDMA)
+        .ops    = &g_lpuart_txdma_ops,
+#  else
+        .ops    = &g_lpuart_ops,
+#  endif
+    },
+
+  .uartbase     = IMX9_LPUART2_BASE,
+  .uartnum      = 2,
+  .baud         = CONFIG_LPUART2_BAUD,
+  .irq          = IMX9_IRQ_LPUART2,
+  .parity       = CONFIG_LPUART2_PARITY,
+  .bits         = CONFIG_LPUART2_BITS,
+  .stopbits2    = CONFIG_LPUART2_2STOP,
+#  if defined(CONFIG_SERIAL_OFLOWCONTROL) && defined(CONFIG_LPUART2_OFLOWCONTROL)
+  .oflow        = 1,
+  .cts_gpio     = GPIO_LPUART2_CTS,
+#  endif
+#  if defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART2_IFLOWCONTROL)
+  .iflow        = 1,
+#  endif
+#  if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART2_RS485RTSCONTROL)) || \
+      (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART2_IFLOWCONTROL)))
+  .rts_gpio     = GPIO_LPUART2_RTS,
+#  endif
+
+#  if (defined(CONFIG_SERIAL_RS485CONTROL) || defined(CONFIG_SERIAL_IFLOWCONTROL)) && \
+      defined(CONFIG_LPUART2_INVERTIFLOWCONTROL)
+  .inviflow     = 1,
+#  endif
+
+#  if defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART2_RS485RTSCONTROL)
+  .rs485mode    = 1,
+#  endif
+
+#  ifdef CONFIG_LPUART2_TXDMA
+  .txch = DMA_REQUEST_MUXLPUART2TX,
+#  endif
+#  ifdef CONFIG_LPUART2_RXDMA
+  .rxch = DMA_REQUEST_MUXLPUART2RX,
+  .rxfifo       = g_lpuart2rxfifo,
+#  endif
+};
+#endif
+
+#ifdef CONFIG_IMX9_LPUART3
+static struct imx9_uart_s g_lpuart3priv =
+{
+  .dev =
+    {
+      .recv     =
+      {
+        .size   = CONFIG_LPUART3_RXBUFSIZE,
+        .buffer = g_lpuart3rxbuffer,
+      },
+      .xmit     =
+      {
+        .size   = CONFIG_LPUART3_TXBUFSIZE,
+        .buffer = g_lpuart3txbuffer,
+      },
+#  if defined(CONFIG_LPUART3_RXDMA) && defined(CONFIG_LPUART3_TXDMA)
+        .ops    = &g_lpuart_rxtxdma_ops,
+#  elif defined(CONFIG_LPUART3_RXDMA) && !defined(CONFIG_LPUART3_TXDMA)
+        .ops    = &g_lpuart_rxdma_ops,
+#  elif !defined(CONFIG_LPUART3_RXDMA) && defined(CONFIG_LPUART3_TXDMA)
+        .ops    = &g_lpuart_txdma_ops,
+#  else
+        .ops    = &g_lpuart_ops,
+#  endif
+    },
+
+  .uartbase     = IMX9_LPUART3_BASE,
+  .uartnum      = 3,
+  .baud         = CONFIG_LPUART3_BAUD,
+  .irq          = IMX9_IRQ_LPUART3,
+  .parity       = CONFIG_LPUART3_PARITY,
+  .bits         = CONFIG_LPUART3_BITS,
+  .stopbits2    = CONFIG_LPUART3_2STOP,
+#  if defined(CONFIG_SERIAL_OFLOWCONTROL) && defined(CONFIG_LPUART3_OFLOWCONTROL)
+  .oflow        = 1,
+  .cts_gpio     = GPIO_LPUART3_CTS,
+#  endif
+#  if defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART3_IFLOWCONTROL)
+  .iflow        = 1,
+#  endif
+#  if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART3_RS485RTSCONTROL)) || \
+      (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART3_IFLOWCONTROL)))
+  .rts_gpio     = GPIO_LPUART3_RTS,
+#  endif
+
+#  if (defined(CONFIG_SERIAL_RS485CONTROL) || defined(CONFIG_SERIAL_IFLOWCONTROL)) && \
+      defined(CONFIG_LPUART3_INVERTIFLOWCONTROL)
+  .inviflow     = 1,
+#  endif
+
+#  if defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART3_RS485RTSCONTROL)
+  .rs485mode    = 1,
+#  endif
+
+#  ifdef CONFIG_LPUART3_TXDMA
+  .txch = DMA_REQUEST_MUXLPUART3TX,
+#  endif
+#  ifdef CONFIG_LPUART3_RXDMA
+  .rxch = DMA_REQUEST_MUXLPUART3RX,
+  .rxfifo       = g_lpuart3rxfifo,
+#  endif
+};
+#endif
+
+#ifdef CONFIG_IMX9_LPUART4
+static struct imx9_uart_s g_lpuart4priv =
+{
+  .dev =
+    {
+      .recv     =
+      {
+        .size   = CONFIG_LPUART4_RXBUFSIZE,
+        .buffer = g_lpuart4rxbuffer,
+      },
+      .xmit     =
+      {
+        .size   = CONFIG_LPUART4_TXBUFSIZE,
+        .buffer = g_lpuart4txbuffer,
+      },
+#  if defined(CONFIG_LPUART4_RXDMA) && defined(CONFIG_LPUART4_TXDMA)
+        .ops    = &g_lpuart_rxtxdma_ops,
+#  elif defined(CONFIG_LPUART4_RXDMA) && !defined(CONFIG_LPUART4_TXDMA)
+        .ops    = &g_lpuart_rxdma_ops,
+#  elif !defined(CONFIG_LPUART4_RXDMA) && defined(CONFIG_LPUART4_TXDMA)
+        .ops    = &g_lpuart_txdma_ops,
+#  else
+        .ops    = &g_lpuart_ops,
+#  endif
+    },
+
+  .uartbase     = IMX9_LPUART4_BASE,
+  .uartnum      = 4,
+  .baud         = CONFIG_LPUART4_BAUD,
+  .irq          = IMX9_IRQ_LPUART4,
+  .parity       = CONFIG_LPUART4_PARITY,
+  .bits         = CONFIG_LPUART4_BITS,
+  .stopbits2    = CONFIG_LPUART4_2STOP,
+#  if defined(CONFIG_SERIAL_OFLOWCONTROL) && defined(CONFIG_LPUART4_OFLOWCONTROL)
+  .oflow        = 1,
+  .cts_gpio     = GPIO_LPUART4_CTS,
+#  endif
+#  if defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART4_IFLOWCONTROL)
+  .iflow        = 1,
+#  endif
+#  if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART4_RS485RTSCONTROL)) || \
+      (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART4_IFLOWCONTROL)))
+  .rts_gpio     = GPIO_LPUART4_RTS,
+#  endif
+
+#  if (defined(CONFIG_SERIAL_RS485CONTROL) || defined(CONFIG_SERIAL_IFLOWCONTROL)) && \
+      defined(CONFIG_LPUART4_INVERTIFLOWCONTROL)
+  .inviflow     = 1,
+#  endif
+
+#  if defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART4_RS485RTSCONTROL)
+  .rs485mode    = 1,
+#  endif
+
+#  ifdef CONFIG_LPUART4_TXDMA
+  .txch = DMA_REQUEST_MUXLPUART4TX,
+#  endif
+#  ifdef CONFIG_LPUART4_RXDMA
+  .rxch = DMA_REQUEST_MUXLPUART4RX,
+  .rxfifo       = g_lpuart4rxfifo,
+#  endif
+};
+#endif
+
+#ifdef CONFIG_IMX9_LPUART5
+static struct imx9_uart_s g_lpuart5priv =
+{
+  .dev =
+    {
+      .recv     =
+      {
+        .size   = CONFIG_LPUART5_RXBUFSIZE,
+        .buffer = g_lpuart5rxbuffer,
+      },
+      .xmit     =
+      {
+        .size   = CONFIG_LPUART5_TXBUFSIZE,
+        .buffer = g_lpuart5txbuffer,
+      },
+#  if defined(CONFIG_LPUART5_RXDMA) && defined(CONFIG_LPUART5_TXDMA)
+        .ops    = &g_lpuart_rxtxdma_ops,
+#  elif defined(CONFIG_LPUART5_RXDMA) && !defined(CONFIG_LPUART5_TXDMA)
+        .ops    = &g_lpuart_rxdma_ops,
+#  elif !defined(CONFIG_LPUART5_RXDMA) && defined(CONFIG_LPUART5_TXDMA)
+        .ops    = &g_lpuart_txdma_ops,
+#  else
+        .ops    = &g_lpuart_ops,
+#  endif
+    },
+
+  .uartbase     = IMX9_LPUART5_BASE,
+  .uartnum      = 5,
+  .baud         = CONFIG_LPUART5_BAUD,
+  .irq          = IMX9_IRQ_LPUART5,
+  .parity       = CONFIG_LPUART5_PARITY,
+  .bits         = CONFIG_LPUART5_BITS,
+  .stopbits2    = CONFIG_LPUART5_2STOP,
+#  if defined(CONFIG_SERIAL_OFLOWCONTROL) && defined(CONFIG_LPUART5_OFLOWCONTROL)
+  .oflow        = 1,
+  .cts_gpio     = GPIO_LPUART5_CTS,
+#  endif
+#  if defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART5_IFLOWCONTROL)
+  .iflow        = 1,
+#  endif
+#  if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART5_RS485RTSCONTROL)) || \
+      (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART5_IFLOWCONTROL)))
+  .rts_gpio     = GPIO_LPUART5_RTS,
+#  endif
+
+#  if (defined(CONFIG_SERIAL_RS485CONTROL) || defined(CONFIG_SERIAL_IFLOWCONTROL)) && \
+      defined(CONFIG_LPUART5_INVERTIFLOWCONTROL)
+  .inviflow     = 1,
+#  endif
+
+#  if defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART5_RS485RTSCONTROL)
+  .rs485mode    = 1,
+#  endif
+
+#  ifdef CONFIG_LPUART5_TXDMA
+  .txch = DMA_REQUEST_MUXLPUART5TX,
+#  endif
+#  ifdef CONFIG_LPUART5_RXDMA
+  .rxch = DMA_REQUEST_MUXLPUART5RX,
+  .rxfifo       = g_lpuart5rxfifo,
+#  endif
+};
+#endif
+
+#ifdef CONFIG_IMX9_LPUART6
+static struct imx9_uart_s g_lpuart6priv =
+{
+  .dev =
+    {
+      .recv     =
+      {
+        .size   = CONFIG_LPUART6_RXBUFSIZE,
+        .buffer = g_lpuart6rxbuffer,
+      },
+      .xmit     =
+      {
+        .size   = CONFIG_LPUART6_TXBUFSIZE,
+        .buffer = g_lpuart6txbuffer,
+      },
+#  if defined(CONFIG_LPUART6_RXDMA) && defined(CONFIG_LPUART6_TXDMA)
+        .ops    = &g_lpuart_rxtxdma_ops,
+#  elif defined(CONFIG_LPUART6_RXDMA) && !defined(CONFIG_LPUART6_TXDMA)
+        .ops    = &g_lpuart_rxdma_ops,
+#  elif !defined(CONFIG_LPUART6_RXDMA) && defined(CONFIG_LPUART6_TXDMA)
+        .ops    = &g_lpuart_txdma_ops,
+#  else
+        .ops    = &g_lpuart_ops,
+#  endif
+    },
+
+  .uartbase     = IMX9_LPUART6_BASE,
+  .uartnum      = 6,
+  .baud         = CONFIG_LPUART6_BAUD,
+  .irq          = IMX9_IRQ_LPUART6,
+  .parity       = CONFIG_LPUART6_PARITY,
+  .bits         = CONFIG_LPUART6_BITS,
+  .stopbits2    = CONFIG_LPUART6_2STOP,
+#  if defined(CONFIG_SERIAL_OFLOWCONTROL) && defined(CONFIG_LPUART6_OFLOWCONTROL)
+  .oflow        = 1,
+  .cts_gpio     = GPIO_LPUART6_CTS,
+#  endif
+#  if defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART6_IFLOWCONTROL)
+  .iflow        = 1,
+#  endif
+#  if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART6_RS485RTSCONTROL)) || \
+      (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART6_IFLOWCONTROL)))
+  .rts_gpio     = GPIO_LPUART6_RTS,
+#  endif
+
+#  if (defined(CONFIG_SERIAL_RS485CONTROL) || defined(CONFIG_SERIAL_IFLOWCONTROL)) && \
+      defined(CONFIG_LPUART6_INVERTIFLOWCONTROL)
+  .inviflow     = 1,
+#  endif
+
+#  if defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART6_RS485RTSCONTROL)
+  .rs485mode    = 1,
+#  endif
+
+#  ifdef CONFIG_LPUART6_TXDMA
+  .txch = DMA_REQUEST_MUXLPUART6TX,
+#  endif
+#  ifdef CONFIG_LPUART6_RXDMA
+  .rxch = DMA_REQUEST_MUXLPUART6RX,
+  .rxfifo       = g_lpuart6rxfifo,
+#  endif
+};
+#endif
+
+#ifdef CONFIG_IMX9_LPUART7
+static struct imx9_uart_s g_lpuart7priv =
+{
+  .dev =
+    {
+      .recv     =
+      {
+        .size   = CONFIG_LPUART7_RXBUFSIZE,
+        .buffer = g_lpuart7rxbuffer,
+      },
+      .xmit     =
+      {
+        .size   = CONFIG_LPUART7_TXBUFSIZE,
+        .buffer = g_lpuart7txbuffer,
+      },
+#  if defined(CONFIG_LPUART7_RXDMA) && defined(CONFIG_LPUART7_TXDMA)
+        .ops    = &g_lpuart_rxtxdma_ops,
+#  elif defined(CONFIG_LPUART7_RXDMA) && !defined(CONFIG_LPUART7_TXDMA)
+        .ops    = &g_lpuart_rxdma_ops,
+#  elif !defined(CONFIG_LPUART7_RXDMA) && defined(CONFIG_LPUART7_TXDMA)
+        .ops    = &g_lpuart_txdma_ops,
+#  else
+        .ops    = &g_lpuart_ops,
+#  endif
+    },
+
+  .uartbase     = IMX9_LPUART7_BASE,
+  .uartnum      = 7,
+  .baud         = CONFIG_LPUART7_BAUD,
+  .irq          = IMX9_IRQ_LPUART7,
+  .parity       = CONFIG_LPUART7_PARITY,
+  .bits         = CONFIG_LPUART7_BITS,
+  .stopbits2    = CONFIG_LPUART7_2STOP,
+#  if defined(CONFIG_SERIAL_OFLOWCONTROL) && defined(CONFIG_LPUART7_OFLOWCONTROL)
+  .oflow        = 1,
+  .cts_gpio     = GPIO_LPUART7_CTS,
+#  endif
+#  if defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART7_IFLOWCONTROL)
+  .iflow        = 1,
+#  endif
+#  if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART7_RS485RTSCONTROL)) || \
+      (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART7_IFLOWCONTROL)))
+  .rts_gpio     = GPIO_LPUART7_RTS,
+#  endif
+
+#  if (defined(CONFIG_SERIAL_RS485CONTROL) || defined(CONFIG_SERIAL_IFLOWCONTROL)) && \
+      defined(CONFIG_LPUART7_INVERTIFLOWCONTROL)
+  .inviflow     = 1,
+#  endif
+
+#  if defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART7_RS485RTSCONTROL)
+  .rs485mode    = 1,
+#  endif
+
+#  ifdef CONFIG_LPUART7_TXDMA
+  .txch = DMA_REQUEST_MUXLPUART7TX,
+#  endif
+#  ifdef CONFIG_LPUART7_RXDMA
+  .rxch = DMA_REQUEST_MUXLPUART7RX,
+  .rxfifo       = g_lpuart7rxfifo,
+#  endif
+};
+#endif
+
+#ifdef CONFIG_IMX9_LPUART8
+static struct imx9_uart_s g_lpuart8priv =
+{
+  .dev =
+    {
+      .recv     =
+      {
+        .size   = CONFIG_LPUART8_RXBUFSIZE,
+        .buffer = g_lpuart8rxbuffer,
+      },
+      .xmit     =
+      {
+        .size   = CONFIG_LPUART8_TXBUFSIZE,
+        .buffer = g_lpuart8txbuffer,
+      },
+    #if defined(CONFIG_LPUART8_RXDMA) && defined(CONFIG_LPUART8_TXDMA)
+        .ops    = &g_lpuart_rxtxdma_ops,
+    #elif defined(CONFIG_LPUART8_RXDMA) && !defined(CONFIG_LPUART8_TXDMA)
+        .ops    = &g_lpuart_rxdma_ops,
+    #elif !defined(CONFIG_LPUART8_RXDMA) && defined(CONFIG_LPUART8_TXDMA)
+        .ops    = &g_lpuart_txdma_ops,
+    #else
+        .ops    = &g_lpuart_ops,
+    #endif
+    },
+
+  .uartbase     = IMX9_LPUART8_BASE,
+  .uartnum      = 8,
+  .baud         = CONFIG_LPUART8_BAUD,
+  .irq          = IMX9_IRQ_LPUART8,
+  .parity       = CONFIG_LPUART8_PARITY,
+  .bits         = CONFIG_LPUART8_BITS,
+  .stopbits2    = CONFIG_LPUART8_2STOP,
+#  if defined(CONFIG_SERIAL_OFLOWCONTROL) && defined(CONFIG_LPUART8_OFLOWCONTROL)
+  .oflow        = 1,
+  .cts_gpio     = GPIO_LPUART8_CTS,
+#  endif
+#  if defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART8_IFLOWCONTROL)
+  .iflow        = 1,
+#  endif
+#  if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART8_RS485RTSCONTROL)) || \
+      (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART8_IFLOWCONTROL)))
+  .rts_gpio     = GPIO_LPUART8_RTS,
+#  endif
+
+#  if (defined(CONFIG_SERIAL_RS485CONTROL) || defined(CONFIG_SERIAL_IFLOWCONTROL)) && \
+      defined(CONFIG_LPUART8_INVERTIFLOWCONTROL)
+  .inviflow     = 1,
+#  endif
+
+#  if defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART8_RS485RTSCONTROL)
+  .rs485mode    = 1,
+#  endif
+
+#  ifdef CONFIG_LPUART8_TXDMA
+  .txch = DMA_REQUEST_MUXLPUART8TX,
+#  endif
+#  ifdef CONFIG_LPUART8_RXDMA
+  .rxch = DMA_REQUEST_MUXLPUART8RX,
+  .rxfifo       = g_lpuart8rxfifo,
+#  endif
+};
+#endif
+
+#ifdef CONFIG_PM
+static  struct pm_callback_s g_serial_pmcb =
+{
+  .notify       = up_pm_notify,
+  .prepare      = up_pm_prepare,
+};
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: imx9_serialin
+ ****************************************************************************/
+
+static inline uint32_t imx9_serialin(struct imx9_uart_s *priv,
+                                      uint32_t offset)
+{
+  return getreg32(priv->uartbase + offset);
+}
+
+/****************************************************************************
+ * Name: imx9_serialout
+ ****************************************************************************/
+
+static inline void imx9_serialout(struct imx9_uart_s *priv,
+                                     uint32_t offset, uint32_t value)
+{
+  putreg32(value, priv->uartbase + offset);
+}
+
+/****************************************************************************
+ * Name: imx9_dma_nextrx
+ *
+ * Description:
+ *   Returns the index into the RX FIFO where the DMA will place the next
+ *   byte that it receives.
+ *
+ ****************************************************************************/
+
+#ifdef SERIAL_HAVE_RXDMA
+static int imx9_dma_nextrx(struct imx9_uart_s *priv)
+{
+  int dmaresidual = imx9_dmach_getcount(priv->rxdma);
+  DEBUGASSERT(dmaresidual <= RXDMA_BUFFER_SIZE);
+
+  return (RXDMA_BUFFER_SIZE - dmaresidual) % RXDMA_BUFFER_SIZE;
+}
+#endif
+
+/****************************************************************************
+ * Name: imx9_disableuartint
+ ****************************************************************************/
+
+static inline void imx9_disableuartint(struct imx9_uart_s *priv,
+                                          uint32_t *ie)
+{
+  irqstate_t flags;
+  uint32_t regval;
+
+  flags  = spin_lock_irqsave(NULL);
+  regval = imx9_serialin(priv, IMX9_LPUART_CTRL_OFFSET);
+
+  /* Return the current Rx and Tx interrupt state */
+
+  if (ie != NULL)
+    {
+      *ie = regval & LPUART_ALL_INTS;
+    }
+
+  regval &= ~LPUART_ALL_INTS;
+  imx9_serialout(priv, IMX9_LPUART_CTRL_OFFSET, regval);
+  spin_unlock_irqrestore(NULL, flags);
+}
+
+/****************************************************************************
+ * Name: imx9_restoreuartint
+ ****************************************************************************/
+
+static inline void imx9_restoreuartint(struct imx9_uart_s *priv,
+                                        uint32_t ie)
+{
+  irqstate_t flags;
+  uint32_t regval;
+
+  /* Enable/disable any interrupts that are currently disabled but should be
+   * enabled/disabled.
+   */
+
+  flags   = spin_lock_irqsave(NULL);
+  regval  = imx9_serialin(priv, IMX9_LPUART_CTRL_OFFSET);
+  regval &= ~LPUART_ALL_INTS;
+  regval |= ie;
+  imx9_serialout(priv, IMX9_LPUART_CTRL_OFFSET, regval);
+  spin_unlock_irqrestore(NULL, flags);
+}
+
+/****************************************************************************
+ * Name: imx9_dma_setup
+ *
+ * Description:
+ *   Configure the LPUART baud, bits, parity, etc. This method is called the
+ *   first time that the serial port is opened.
+ *
+ ****************************************************************************/
+
+#if defined(SERIAL_HAVE_RXDMA) || defined(SERIAL_HAVE_TXDMA)
+static int imx9_dma_setup(struct uart_dev_s *dev)
+{
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+#if defined(SERIAL_HAVE_RXDMA)
+  struct imx9_edma_xfrconfig_s config;
+#endif
+  int result;
+
+  /* Do the basic UART setup first, unless we are the console */
+
+  if (!dev->isconsole)
+    {
+      result = imx9_setup(dev);
+      if (result != OK)
+        {
+          return result;
+        }
+    }
+
+#if defined(SERIAL_HAVE_TXDMA)
+  /* Acquire the Tx DMA channel.  This should always succeed. */
+
+  if (priv->txch != 0)
+    {
+      if (priv->txdma == NULL)
+        {
+          priv->txdma = imx9_dmach_alloc(priv->txch, 0);
+          if (priv->txdma == NULL)
+            {
+              return -EBUSY;
+            }
+        }
+
+      /* Enable Tx DMA for the UART */
+
+      modifyreg32(priv->uartbase + IMX9_LPUART_BAUD_OFFSET,
+                  0, LPUART_BAUD_TDMAE);
+    }
+#endif
+
+#if defined(SERIAL_HAVE_RXDMA)
+  /* Acquire the Rx DMA channel.  This should always succeed. */
+
+  if (priv->rxch != 0)
+    {
+      if (priv->rxdma == NULL)
+        {
+          priv->rxdma = imx9_dmach_alloc(priv->rxch, 0);
+
+          if (priv->rxdma == NULL)
+            {
+              return -EBUSY;
+            }
+        }
+      else
+        {
+          imx9_dmach_stop(priv->rxdma);
+        }
+
+      /* Configure for circular DMA reception into the RX FIFO */
+
+      config.saddr  = priv->uartbase + IMX9_LPUART_DATA_OFFSET;
+      config.daddr  = (uintptr_t)priv->rxfifo;
+      config.soff   = 0;
+      config.doff   = 1;
+      config.iter   = RXDMA_BUFFER_SIZE;
+      config.flags  = EDMA_CONFIG_LINKTYPE_LINKNONE |
+                      EDMA_CONFIG_LOOPDEST |
+                      EDMA_CONFIG_INTHALF  |
+                      EDMA_CONFIG_INTMAJOR;
+      config.ssize  = EDMA_8BIT;
+      config.dsize  = EDMA_8BIT;
+      config.nbytes = 1;
+#ifdef CONFIG_IMX9_EDMA_ELINK
+      config.linkch = 0;
+#endif
+
+      imx9_dmach_xfrsetup(priv->rxdma , &config);
+
+      /* Reset our DMA shadow pointer and Rx data availability count to
+       * match the address just programmed above.
+       */
+
+      priv->rxdmanext = 0;
+
+#ifndef CONFIG_ARM64_DCACHE_DISABLE
+
+      /* Make sure the rx buffer area is all invalid or clean */
+
+      up_invalidate_dcache((uintptr_t)priv->rxfifo,
+                           (uintptr_t)priv->rxfifo + RXDMA_BUFFER_SIZE);
+      priv->rxdmaavail = 0;
+#endif
+
+      /* Enable receive Rx DMA for the UART */
+
+      modifyreg32(priv->uartbase + IMX9_LPUART_BAUD_OFFSET,
+                  0, LPUART_BAUD_RDMAE);
+
+      /* Enable interrupt on idle and erros */
+
+      modifyreg32(priv->uartbase + IMX9_LPUART_CTRL_OFFSET, 0,
+                  LPUART_CTRL_PEIE       |
+                  LPUART_CTRL_FEIE       |
+                  LPUART_CTRL_NEIE       |
+                  LPUART_CTRL_ILIE);
+
+      /* Start the DMA channel, and arrange for callbacks at the half and
+       * full points in the FIFO.  This ensures that we have half a FIFO
+       * worth of time to claim bytes before they are overwritten.
+       */
+
+      imx9_dmach_start(priv->rxdma, imx9_dma_rxcallback, (void *)priv);
+    }
+#endif
+
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: imx9_setup
+ *
+ * Description:
+ *   Configure the UART baud, bits, parity, fifos, etc. This
+ *   method is called the first time that the serial priv is
+ *   opened.
+ *
+ ****************************************************************************/
+
+static int imx9_setup(struct uart_dev_s *dev)
+{
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+#ifndef CONFIG_SUPPRESS_LPUART_CONFIG
+  struct uart_config_s config =
+    {
+      0
+    };
+
+  int ret;
+
+  /* Configure the UART */
+
+  config.baud       = priv->baud;       /* Configured baud */
+  config.parity     = priv->parity;     /* 0=none, 1=odd, 2=even */
+  config.bits       = priv->bits;       /* Number of bits (5-9) */
+  config.stopbits2  = priv->stopbits2;  /* true: Configure with 2 stop bits instead of 1 */
+#ifdef CONFIG_SERIAL_OFLOWCONTROL
+  config.usects     = priv->oflow;      /* Flow control on outbound side */
+#endif
+#ifdef CONFIG_SERIAL_IFLOWCONTROL
+  /* Flow control on outbound side if not GPIO based */
+
+  if (priv->rts_gpio == 0)
+    {
+      config.userts = priv->iflow;
+    }
+
+#endif
+#ifdef CONFIG_SERIAL_RS485CONTROL
+  config.users485   = priv->rs485mode;  /* Switch into RS485 mode */
+#endif
+#if defined(CONFIG_SERIAL_RS485CONTROL) || defined(CONFIG_SERIAL_IFLOWCONTROL)
+  config.invrts     = priv->inviflow;   /* Inversion of outbound flow control */
+#endif
+
+  ret = imx9_lpuart_configure(priv->uartbase, priv->uartnum, &config);
+
+  priv->ie = imx9_serialin(priv, IMX9_LPUART_CTRL_OFFSET) & \
+             LPUART_ALL_INTS;
+  return ret;
+
+#else
+  priv->ie = imx9_serialin(priv, IMX9_LPUART_CTRL_OFFSET) & \
+             LPUART_ALL_INTS;
+  return OK;
+#endif
+}
+
+/****************************************************************************
+ * Name: imx9_shutdown
+ *
+ * Description:
+ *   Disable the UART.  This method is called when the serial
+ *   priv is closed
+ *
+ ****************************************************************************/
+
+static void imx9_shutdown(struct uart_dev_s *dev)
+{
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+
+  /* Disable the UART */
+
+  imx9_serialout(priv, IMX9_LPUART_GLOBAL_OFFSET, LPUART_GLOBAL_RST);
+}
+
+/****************************************************************************
+ * Name: imx9_dma_shutdown
+ *
+ * Description:
+ *   Disable the LPUART.  This method is called when the serial
+ *   port is closed
+ *
+ ****************************************************************************/
+
+#if defined(SERIAL_HAVE_RXDMA) || defined(SERIAL_HAVE_TXDMA)
+static void imx9_dma_shutdown(struct uart_dev_s *dev)
+{
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+
+  /* Perform the normal UART shutdown */
+
+  imx9_shutdown(dev);
+
+#if defined(SERIAL_HAVE_RXDMA)
+  /* Stop the RX DMA channel */
+
+  if (priv->rxch != 0)
+    {
+      imx9_dmach_stop(priv->rxdma);
+
+      /* Release the RX DMA channel */
+
+      imx9_dmach_free(priv->rxdma);
+      priv->rxdma = NULL;
+    }
+#endif
+
+#if defined(SERIAL_HAVE_TXDMA)
+  /* Stop the TX DMA channel */
+
+  if (priv->txch != 0)
+    {
+      imx9_dmach_stop(priv->txdma);
+
+      /* Release the TX DMA channel */
+
+      imx9_dmach_free(priv->txdma);
+      priv->txdma = NULL;
+    }
+#endif
+}
+#endif
+
+/****************************************************************************
+ * Name: imx9_attach
+ *
+ * Description:
+ *   Configure the UART to operation in interrupt driven mode.  This method
+ *   is called when the serial priv is opened.  Normally, this is just after
+ *   the setup() method is called, however, the serial console may operate
+ *   in a non-interrupt driven mode during the boot phase.
+ *
+ *   RX and TX interrupts are not enabled when by the attach method (unless
+ *   the hardware supprivs multiple levels of interrupt enabling).  The RX
+ *   and TX interrupts are not enabled until the txint() and rxint() methods
+ *   are called.
+ *
+ ****************************************************************************/
+
+static int imx9_attach(struct uart_dev_s *dev)
+{
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+  int ret;
+
+  /* Attach and enable the IRQ */
+
+  ret = irq_attach(priv->irq, imx9_interrupt, dev);
+  if (ret == OK)
+    {
+      /* Enable the interrupt (RX and TX interrupts are still disabled
+       * in the UART
+       */
+
+      up_enable_irq(priv->irq);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: imx9_detach
+ *
+ * Description:
+ *   Detach UART interrupts.  This method is called when the serial priv is
+ *   closed normally just before the shutdown method is called.  The
+ *   exception is the serial console which is never shutdown.
+ *
+ ****************************************************************************/
+
+static void imx9_detach(struct uart_dev_s *dev)
+{
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+
+  up_disable_irq(priv->irq);
+  irq_detach(priv->irq);
+}
+
+/****************************************************************************
+ * Name: imx9_interrupt (and front-ends)
+ *
+ * Description:
+ *   This is the common UART interrupt handler.  It will be invoked when an
+ *   interrupt is received on the 'irq'.  It should call uart_xmitchars or
+ *   uart_recvchars to perform the appropriate data transfers.  The
+ *   interrupt handling logic must be able to map the 'arg' to the
+ *   appropriate uart_dev_s structure in order to call these functions.
+ *
+ ****************************************************************************/
+
+static int imx9_interrupt(int irq, void *context, void *arg)
+{
+  struct uart_dev_s *dev = (struct uart_dev_s *)arg;
+  struct imx9_uart_s *priv;
+  uint32_t usr;
+  uint32_t lsr;
+  int passes = 0;
+  bool handled;
+
+  DEBUGASSERT(dev != NULL && dev != NULL);
+  priv = (struct imx9_uart_s *)dev;
+
+#if defined(CONFIG_PM) && CONFIG_IMX9_PM_SERIAL_ACTIVITY > 0
+  /* Repriv serial activity to the power management logic */
+
+  pm_activity(PM_IDLE_DOMAIN, CONFIG_IMX9_PM_SERIAL_ACTIVITY);
+#endif
+
+  /* Loop until there are no characters to be transferred or,
+   * until we have been looping for a long time.
+   */
+
+  handled = true;
+  for (passes = 0; passes < 256 && handled; passes++)
+    {
+      handled = false;
+
+      /* Get the current UART status and check for loop
+       * termination conditions
+       */
+
+      usr  = imx9_serialin(priv, IMX9_LPUART_STAT_OFFSET);
+
+      /* Removed all W1C from the last sr */
+
+      lsr  = usr & ~(LPUART_STAT_LBKDIF | LPUART_STAT_RXEDGIF |
+                     LPUART_STAT_IDLE   | LPUART_STAT_OR      |
+                     LPUART_STAT_NF     | LPUART_STAT_FE      |
+                     LPUART_STAT_PF     | LPUART_STAT_MA1F    |
+                     LPUART_STAT_MA2F);
+
+      /* Keep what we will service */
+
+      usr &= (LPUART_STAT_RDRF | LPUART_STAT_TDRE | LPUART_STAT_OR |
+              LPUART_STAT_FE | LPUART_STAT_NF | LPUART_STAT_PF |
+              LPUART_STAT_IDLE);
+
+      /* Clear serial overrun, parity and framing errors */
+
+      if ((usr & LPUART_STAT_OR) != 0)
+        {
+          imx9_serialout(priv, IMX9_LPUART_STAT_OFFSET,
+                            LPUART_STAT_OR | lsr);
+        }
+
+      if ((usr & LPUART_STAT_NF) != 0)
+        {
+          imx9_serialout(priv, IMX9_LPUART_STAT_OFFSET,
+                            LPUART_STAT_NF | lsr);
+        }
+
+      if ((usr & LPUART_STAT_PF) != 0)
+        {
+          imx9_serialout(priv, IMX9_LPUART_STAT_OFFSET,
+                            LPUART_STAT_PF | lsr);
+        }
+
+      if ((usr & LPUART_STAT_FE) != 0)
+        {
+          imx9_serialout(priv, IMX9_LPUART_STAT_OFFSET,
+                            LPUART_STAT_FE | lsr);
+        }
+
+      if ((usr & (LPUART_STAT_FE | LPUART_STAT_PF | LPUART_STAT_NF)) != 0)
+        {
+          /* Discard data */
+
+          imx9_serialin(priv, IMX9_LPUART_DATA_OFFSET);
+        }
+
+#ifdef SERIAL_HAVE_RXDMA
+      /* The line going to idle, deliver any fractions of RX data */
+
+      if ((usr & LPUART_STAT_IDLE) != 0)
+        {
+          imx9_serialout(priv, IMX9_LPUART_STAT_OFFSET,
+                            LPUART_STAT_IDLE | lsr);
+          imx9_dma_rxcallback(priv->rxdma, priv, false, LPUART_STAT_IDLE);
+        }
+#endif
+
+      /* Handle incoming, receive bytes */
+
+      if ((usr & LPUART_STAT_RDRF) != 0 &&
+          (priv->ie & LPUART_CTRL_RIE) != 0)
+        {
+          uart_recvchars(dev);
+          handled = true;
+        }
+
+      /* Handle outgoing, transmit bytes */
+
+      if ((usr & LPUART_STAT_TDRE) != 0 &&
+          (priv->ie & LPUART_CTRL_TIE) != 0)
+        {
+          uart_xmitchars(dev);
+          handled = true;
+        }
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: imx9_ioctl
+ *
+ * Description:
+ *   All ioctl calls will be routed through this method
+ *
+ ****************************************************************************/
+
+static int imx9_ioctl(struct file *filep, int cmd, unsigned long arg)
+{
+#if defined(CONFIG_SERIAL_TIOCSERGSTRUCT) || defined(CONFIG_SERIAL_TERMIOS)
+  struct inode *inode = filep->f_inode;
+  struct uart_dev_s *dev   = inode->i_private;
+  irqstate_t flags;
+#endif
+  int ret   = OK;
+
+  switch (cmd)
+    {
+#ifdef CONFIG_SERIAL_TIOCSERGSTRUCT
+    case TIOCSERGSTRUCT:
+      {
+         struct imx9_uart_s *user = (struct imx9_uart_s *)arg;
+         if (!user)
+           {
+             ret = -EINVAL;
+           }
+         else
+           {
+             memcpy(user, dev, sizeof(struct imx9_uart_s));
+           }
+       }
+       break;
+#endif
+
+#ifdef CONFIG_SERIAL_TERMIOS
+    case TCGETS:
+      {
+        struct termios  *termiosp = (struct termios *)arg;
+        struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
 
-#include <nuttx/irq.h>
-#include <nuttx/arch.h>
-#include <nuttx/spinlock.h>
-#include <nuttx/init.h>
-#include <nuttx/fs/ioctl.h>
-#include <nuttx/semaphore.h>
-#include <nuttx/serial/serial.h>
+        if (!termiosp)
+          {
+            ret = -EINVAL;
+            break;
+          }
 
-#include "chip.h"
-#include "arm64_arch.h"
-#include "arm64_internal.h"
-#include "arm64_gic.h"
+        /* Return parity */
 
-#include "imx9_serial.h"
-#include "imx9_boot.h"
+        termiosp->c_cflag = ((priv->parity != 0) ? PARENB : 0) |
+                            ((priv->parity == 1) ? PARODD : 0);
 
-#include "hardware/imx9_lpuart.h"
-#include "hardware/imx9_memorymap.h"
+        /* Return stop bits */
 
-#ifdef USE_SERIALDRIVER
+        termiosp->c_cflag |= (priv->stopbits2) ? CSTOPB : 0;
 
-/****************************************************************************
- * Pre-processor Definitions
- ****************************************************************************/
+        /* Return flow control */
 
-/* Which UART with be tty0/console and which tty1-4?  The console will
- * always be ttyS0.  If there is no console then will use the lowest
- * numbered UART.
- */
+#ifdef CONFIG_SERIAL_OFLOWCONTROL
+        termiosp->c_cflag |= ((priv->oflow) ? CCTS_OFLOW : 0);
+#endif
+#ifdef CONFIG_SERIAL_IFLOWCONTROL
+        termiosp->c_cflag |= ((priv->iflow) ? CRTS_IFLOW : 0);
+#endif
+        /* Return baud */
 
-/* First pick the console and ttys0.  This could be any of UART1-5 */
+        cfsetispeed(termiosp, priv->baud);
+
+        /* Return number of bits */
+
+        switch (priv->bits)
+          {
+          case 5:
+            termiosp->c_cflag |= CS5;
+            break;
+
+          case 6:
+            termiosp->c_cflag |= CS6;
+            break;
+
+          case 7:
+            termiosp->c_cflag |= CS7;
+            break;
+
+          default:
+          case 8:
+            termiosp->c_cflag |= CS8;
+            break;
+
+#if defined(CS9)
+          case 9:
+            termiosp->c_cflag |= CS9;
+            break;
+#endif
+          }
+      }
+      break;
 
-#if defined(CONFIG_UART1_SERIAL_CONSOLE)
-#  define CONSOLE_DEV             g_uart1port /* UART1 is console */
-#  define TTYS0_DEV               g_uart1port /* UART1 is ttyS0 */
-#  define UART1_ASSIGNED          1
+    case TCSETS:
+      {
+        struct termios  *termiosp = (struct termios *)arg;
+        struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+        uint32_t baud;
+        uint32_t ie;
+        uint8_t parity;
+        uint8_t nbits;
+        bool stop2;
+
+        if ((!termiosp)
+#ifdef CONFIG_SERIAL_OFLOWCONTROL
+            || ((termiosp->c_cflag & CCTS_OFLOW) && (priv->cts_gpio == 0))
+#endif
+#ifdef CONFIG_SERIAL_IFLOWCONTROL
+            || ((termiosp->c_cflag & CRTS_IFLOW) && (priv->rts_gpio == 0))
 #endif
+           )
+          {
+            ret = -EINVAL;
+            break;
+          }
 
-/* Rx DMA timeout in ms, which is used to calculate Rx ring buffer size */
-#define DMA_RX_TIMEOUT              (10)
-#define UART_AUTOSUSPEND_TIMEOUT    3000
+        /* Decode baud. */
 
-/****************************************************************************
- * Private Types
- ****************************************************************************/
+        ret = OK;
+        baud = cfgetispeed(termiosp);
 
-struct imx9_uart_port_s
-{
-  unsigned long iobase;
-  unsigned int  baud;         /* Configured baud */
-  unsigned int  irq_num;
-  unsigned int  txfifo_size;
-  unsigned int  rxfifo_size;
-  int           is_console;
-};
+        /* Decode number of bits */
 
-static inline uint32_t imx9_read(struct imx9_uart_port_s *port, uint32_t off)
-{
-  return getreg32(port->iobase + off);
-}
+        switch (termiosp->c_cflag & CSIZE)
+          {
+          case CS5:
+            nbits = 5;
+            break;
 
-static inline void imx9_write(struct imx9_uart_port_s *port, uint32_t val,
-                              uint32_t off)
-{
-  putreg32(val, port->iobase + off);
-}
+          case CS6:
+            nbits = 6;
+            break;
 
-static int imx9_lpuart_stop_tx(struct imx9_uart_port_s *sport)
-{
-  unsigned long temp;
+          case CS7:
+            nbits = 7;
+            break;
 
-  temp  = imx9_read(sport, UARTCTRL);
-  temp  &= ~(UARTCTRL_TIE | UARTCTRL_TCIE);
-  imx9_write(sport, temp, UARTCTRL);
+          case CS8:
+            nbits = 8;
+            break;
 
-  return 0;
-}
+#if defined(CS9)
+          case CS9:
+            nbits = 9;
+            break;
+#endif
+          default:
+            ret = -EINVAL;
+            break;
+          }
 
-static int imx9_lpuart_stop_rx(struct imx9_uart_port_s *sport)
-{
-  unsigned long temp;
+        /* Decode parity */
 
-  temp = imx9_read(sport, UARTCTRL);
-  imx9_write(sport, temp & ~UARTCTRL_RE, UARTCTRL);
+        if ((termiosp->c_cflag & PARENB) != 0)
+          {
+            parity = (termiosp->c_cflag & PARODD) ? 1 : 2;
+          }
+        else
+          {
+            parity = 0;
+          }
 
-  return 0;
-}
+        /* Decode stop bits */
 
-static int imx9_lpuart_start_tx(struct imx9_uart_port_s *sport)
-{
-  unsigned long temp;
+        stop2 = (termiosp->c_cflag & CSTOPB) != 0;
 
-  temp = imx9_read(sport, UARTCTRL);
-  imx9_write(sport, temp | UARTCTRL_TIE, UARTCTRL);
+        /* Verify that all settings are valid before committing */
 
-  return 0;
-}
+        if (ret == OK)
+          {
+            /* Commit */
 
-static int imx9_lpuart_start_rx(struct imx9_uart_port_s *sport)
-{
-  unsigned long temp;
+            priv->baud      = baud;
+            priv->parity    = parity;
+            priv->bits      = nbits;
+            priv->stopbits2 = stop2;
+#ifdef CONFIG_SERIAL_OFLOWCONTROL
+            priv->oflow     = (termiosp->c_cflag & CCTS_OFLOW) != 0;
+#endif
+#ifdef CONFIG_SERIAL_IFLOWCONTROL
+            priv->iflow     = (termiosp->c_cflag & CRTS_IFLOW) != 0;
+#endif
+            /* effect the changes immediately - note that we do not
+             * implement TCSADRAIN / TCSAFLUSH
+             */
+
+            flags  = spin_lock_irqsave(NULL);
+            imx9_disableuartint(priv, &ie);
+            ret = dev->ops->setup(dev);
+
+            /* Restore the interrupt state */
+
+            imx9_restoreuartint(priv, ie);
+            priv->ie = ie;
+            spin_unlock_irqrestore(NULL, flags);
+          }
+      }
+      break;
+#endif /* CONFIG_SERIAL_TERMIOS */
 
-  temp = imx9_read(sport, UARTCTRL);
-  imx9_write(sport, temp | UARTCTRL_RE, UARTCTRL);
+#ifdef CONFIG_IMX9_LPUART_SINGLEWIRE
+    case TIOCSSINGLEWIRE:
+      {
+        uint32_t regval;
+        irqstate_t flags;
+        struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+
+        flags  = spin_lock_irqsave(NULL);
+        regval   = imx9_serialin(priv, IMX9_LPUART_CTRL_OFFSET);
+
+        if ((arg & SER_SINGLEWIRE_ENABLED) != 0)
+          {
+            regval |= LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC;
+          }
+        else
+          {
+            regval &= ~(LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC);
+          }
+
+        imx9_serialout(priv, IMX9_LPUART_CTRL_OFFSET, regval);
+
+        spin_unlock_irqrestore(NULL, flags);
+      }
+      break;
+#endif
+
+#ifdef CONFIG_IMX9_LPUART_INVERT
+    case TIOCSINVERT:
+      {
+        uint32_t ctrl;
+        uint32_t stat;
+        uint32_t regval;
+        irqstate_t flags;
+        struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+
+        flags  = spin_lock_irqsave(NULL);
+        ctrl   = imx9_serialin(priv, IMX9_LPUART_CTRL_OFFSET);
+        stat   = imx9_serialin(priv, IMX9_LPUART_STAT_OFFSET);
+        regval = ctrl;
+
+        /* {R|T}XINV bit field can only be written when the receiver is
+        * disabled (RE=0).
+        */
+
+        regval &= ~LPUART_CTRL_RE;
+
+        imx9_serialout(priv, IMX9_LPUART_CTRL_OFFSET, regval);
+
+        /* Enable/disable signal inversion. */
+
+        if (arg & SER_INVERT_ENABLED_RX)
+          {
+            stat |= LPUART_STAT_RXINV;
+          }
+        else
+          {
+            stat &= ~LPUART_STAT_RXINV;
+          }
+
+        /* Do not invert TX when in TIOCSSINGLEWIRE */
+
+        if ((arg & SER_INVERT_ENABLED_TX) &&
+            ((ctrl & LPUART_CTRL_LOOPS) != LPUART_CTRL_LOOPS))
+          {
+            ctrl |= LPUART_CTRL_TXINV;
+          }
+        else
+          {
+            ctrl &= ~LPUART_CTRL_TXINV;
+          }
+
+        imx9_serialout(priv, IMX9_LPUART_STAT_OFFSET, stat);
+        imx9_serialout(priv, IMX9_LPUART_CTRL_OFFSET, ctrl);
+
+        spin_unlock_irqrestore(NULL, flags);
+      }
+      break;
+#endif
+
+    case TIOCSBRK:  /* BSD compatibility: Turn break on, unconditionally */
+    case TIOCCBRK:  /* BSD compatibility: Turn break off, unconditionally */
+    default:
+      ret = -ENOTTY;
+      break;
+    }
 
-  return 0;
+  return ret;
 }
 
 /****************************************************************************
- * Name: imx9_rxint
+ * Name: imx9_receive
  *
  * Description:
- *   Call to enable or disable RX interrupts
+ *   Called (usually) from the interrupt level to receive one
+ *   character from the UART.  Error bits associated with the
+ *   receipt are provided in the return 'status'.
  *
  ****************************************************************************/
 
-static void imx9_rxint(struct uart_dev_s *dev, bool enable)
+#ifndef SERIAL_HAVE_ONLY_RXDMA
+static int imx9_receive(struct uart_dev_s *dev, unsigned int *status)
 {
-  struct imx9_uart_port_s *sport = (struct imx9_uart_port_s *)dev->priv;
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+  uint32_t rxd;
 
-  if (enable)
-    {
-      imx9_lpuart_start_rx(sport);
-    }
-  else
-    {
-      imx9_lpuart_stop_rx(sport);
-    }
+  rxd     = imx9_serialin(priv, IMX9_LPUART_DATA_OFFSET);
+  *status = rxd >> LPUART_DATA_STATUS_SHIFT;
+  return (rxd & LPUART_DATA_MASK) >> LPUART_DATA_SHIFT;
 }
+#endif
 
 /****************************************************************************
- * Name: imx9_txint
+ * Name: imx9_rxint
  *
  * Description:
- *   Call to enable or disable TX interrupts
+ *   Call to enable or disable RX interrupts
  *
  ****************************************************************************/
 
-static void imx9_txint(struct uart_dev_s *dev, bool enable)
+#ifndef SERIAL_HAVE_ONLY_RXDMA
+static void imx9_rxint(struct uart_dev_s *dev, bool enable)
 {
-  struct imx9_uart_port_s *sport = (struct imx9_uart_port_s *)dev->priv;
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+  irqstate_t flags;
+  uint32_t regval;
 
+  /* Enable interrupts for data available at Rx */
+
+  flags = spin_lock_irqsave(NULL);
   if (enable)
     {
-      imx9_lpuart_start_tx(sport);
+#ifndef CONFIG_SUPPRESS_SERIAL_INTS
+      priv->ie |= LPUART_CTRL_RIE | LPUART_CTRL_FEIE | LPUART_CTRL_ORIE;
+#endif
     }
   else
     {
-      imx9_lpuart_stop_tx(sport);
+      priv->ie &= ~(LPUART_CTRL_RIE | LPUART_CTRL_FEIE | LPUART_CTRL_ORIE);
     }
+
+  regval  = imx9_serialin(priv, IMX9_LPUART_CTRL_OFFSET);
+  regval &= ~LPUART_ALL_INTS;
+  regval |= priv->ie;
+  imx9_serialout(priv, IMX9_LPUART_CTRL_OFFSET, regval);
+  spin_unlock_irqrestore(NULL, flags);
 }
+#endif
 
 /****************************************************************************
- * Name: imx9_send
+ * Name: imx9_rxavailable
  *
  * Description:
- *   This method will send one byte on the UART
+ *   Return true if the receive fifo is not empty
  *
  ****************************************************************************/
 
-static void imx9_send(struct uart_dev_s *dev, int ch)
+#ifndef SERIAL_HAVE_ONLY_RXDMA
+static bool imx9_rxavailable(struct uart_dev_s *dev)
 {
-  struct imx9_uart_port_s *sport = (struct imx9_uart_port_s *)dev->priv;
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+  uint32_t regval;
 
-  imx9_write(sport, ch, UARTDATA);
+  /* Return true is data is ready in the Rx FIFO */
+
+  regval = imx9_serialin(priv, IMX9_LPUART_STAT_OFFSET);
+  return ((regval & LPUART_STAT_RDRF) != 0);
 }
+#endif
 
 /****************************************************************************
- * Name: imx9_receive
+ * Name: imx9_rxflowcontrol
  *
  * Description:
- *   Called (usually) from the interrupt level to receive one
- *   character from the UART.  Error bits associated with the
- *   receipt are provided in the return 'status'.
+ *   Called when Rx buffer is full (or exceeds configured watermark levels
+ *   if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is defined).
+ *   Return true if UART activated RX flow control to block more incoming
+ *   data
+ *
+ * Input Parameters:
+ *   dev       - UART device instance
+ *   nbuffered - the number of characters currently buffered
+ *               (if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is
+ *               not defined the value will be 0 for an empty buffer or the
+ *               defined buffer size for a full buffer)
+ *   upper     - true indicates the upper watermark was crossed where
+ *               false indicates the lower watermark has been crossed
+ *
+ * Returned Value:
+ *   true if RX flow control activated.
  *
  ****************************************************************************/
 
-static int imx9_receive(struct uart_dev_s *dev, unsigned int *status)
+#ifdef CONFIG_SERIAL_IFLOWCONTROL
+static bool imx9_rxflowcontrol(struct uart_dev_s *dev,
+                             unsigned int nbuffered, bool upper)
 {
-  struct imx9_uart_port_s *sport = (struct imx9_uart_port_s *)dev->priv;
-  unsigned int             rx;
-  unsigned int             sr;
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
 
-  /* to clear the FE, OR, NF, FE, PE flags,
-   * read STAT then read DATA reg
-   */
+  if (priv->iflow && (priv->rts_gpio != 0))
+    {
+      /* Assert/de-assert nRTS set it high resume/stop sending */
+
+      imx9_gpiowrite(priv->rts_gpio, upper);
+
+      if (upper)
+        {
+          /* With heavy Rx traffic, RXNE might be set and data pending.
+           * Returning 'true' in such case would cause RXNE left unhandled
+           * and causing interrupt storm. Sending end might be also be slow
+           * to react on nRTS, and returning 'true' here would prevent
+           * processing that data.
+           *
+           * Therefore, return 'false' so input data is still being processed
+           * until sending end reacts on nRTS signal and stops sending more.
+           */
+
+          return false;
+        }
+
+      return upper;
+    }
+  else
+    {
+      /* Is the RX buffer full? */
+
+      if (upper)
+        {
+          /* Disable Rx interrupt to prevent more data being from
+           * peripheral.  When hardware RTS is enabled, this will
+           * prevent more data from coming in.
+           *
+           * This function is only called when UART recv buffer is full,
+           * that is: "dev->recv.head + 1 == dev->recv.tail".
+           *
+           * Logic in "uart_read" will automatically toggle Rx interrupts
+           * when buffer is read empty and thus we do not have to re-
+           * enable Rx interrupts.
+           */
+
+          uart_disablerxint(dev);
+          return true;
+        }
+
+      /* No.. The RX buffer is empty */
 
-  sr = imx9_read(sport, UARTSTAT);
-  rx = imx9_read(sport, UARTDATA);
+      else
+        {
+          /* We might leave Rx interrupt disabled if full recv buffer was
+           * read empty.  Enable Rx interrupt to make sure that more input is
+           * received.
+           */
 
-  *status = sr;
+          uart_enablerxint(dev);
+        }
+    }
 
-  return rx;
+  return false;
 }
+#endif
 
 /****************************************************************************
- * Name: imx9_rxavailable
+ * Name: imx9_dma_receive
  *
  * Description:
- *   Return true if the receive fifo is not empty
+ *   Called (usually) from the interrupt level to receive one
+ *   character from the LPUART.  Error bits associated with the
+ *   receipt are provided in the return 'status'.
  *
  ****************************************************************************/
 
-static bool imx9_rxavailable(struct uart_dev_s *dev)
+#ifdef SERIAL_HAVE_RXDMA
+static int imx9_dma_receive(struct uart_dev_s *dev, unsigned int *status)
 {
-  struct imx9_uart_port_s *sport = (struct imx9_uart_port_s *)dev->priv;
-  unsigned long            sfifo = imx9_read(sport, UARTFIFO);
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+  uint32_t nextrx             = imx9_dma_nextrx(priv);
+  int c                       = 0;
 
-  if (sfifo & UARTFIFO_RXEMPT)
-    {
-      return false;
-    }
-  else
+  /* Check if more data is available */
+
+  if (nextrx != priv->rxdmanext)
     {
-      return true;
+#ifndef CONFIG_ARM64_DCACHE_DISABLE
+      /* If the data cache is enabled, then we will also need to manage
+       * cache coherency.  Are any bytes available in the currently coherent
+       * region of the data cache?
+       */
+
+      if (priv->rxdmaavail == 0)
+        {
+          uint32_t rxdmaavail;
+          uintptr_t addr;
+
+          /* No.. then we will have to invalidate additional space in the Rx
+           * DMA buffer.
+           */
+
+          if (nextrx > priv->rxdmanext)
+            {
+              /* Number of available bytes */
+
+              rxdmaavail = nextrx - priv->rxdmanext;
+            }
+          else
+            {
+              /* Number of available bytes up to the end of RXDMA buffer */
+
+              rxdmaavail = RXDMA_BUFFER_SIZE - priv->rxdmanext;
+            }
+
+          /* Invalidate the DMA buffer range */
+
+          addr = (uintptr_t)&priv->rxfifo[priv->rxdmanext];
+          up_invalidate_dcache(addr, addr + rxdmaavail);
+
+          /* We don't need to invalidate the data cache for the next
+           * rxdmaavail number of next bytes.
+           */
+
+          priv->rxdmaavail = rxdmaavail;
+        }
+
+      priv->rxdmaavail--;
+#endif
+
+      /* Now read from the DMA buffer */
+
+      c = priv->rxfifo[priv->rxdmanext];
+
+      priv->rxdmanext++;
+
+      if (priv->rxdmanext == RXDMA_BUFFER_SIZE)
+        {
+          priv->rxdmanext = 0;
+        }
     }
+
+  /* NOTE:  If no data is available, then we would return NULL which is,
+   * of course, valid binary data.  The protocol is that the upper half
+   * driver must call imx9_dma_rxavailable prior to calling this
+   * function to assure that this never happens.
+   */
+
+  return c;
 }
+#endif
 
 /****************************************************************************
- * Name: imx9_txready
+ * Name: imx9_dma_reenable
  *
  * Description:
- *   Return true if the tranmsit fifo is not full
+ *   Call to re-enable RX DMA.
  *
  ****************************************************************************/
 
-static bool imx9_txready(struct uart_dev_s *dev)
+#if defined(SERIAL_HAVE_RXDMA) && defined(CONFIG_PM)
+static void imx9_dma_reenable(struct imx9_uart_s *priv)
 {
-  struct imx9_uart_port_s *sport = (struct imx9_uart_port_s *)dev->priv;
-  unsigned long            txcnt;
+  struct imx9_edma_xfrconfig_s config;
+
+  /* Stop an reset the RX DMA */
+
+  imx9_dmach_stop(priv->rxdma);
+
+  /* Configure for circular DMA reception into the RX FIFO */
+
+  config.saddr  = priv->uartbase + IMX9_LPUART_DATA_OFFSET;
+  config.daddr  = (uint32_t) priv->rxfifo;
+  config.soff   = 0;
+  config.doff   = 1;
+  config.iter   = RXDMA_BUFFER_SIZE;
+  config.flags  = EDMA_CONFIG_LINKTYPE_LINKNONE |
+                  EDMA_CONFIG_LOOPDEST |
+                  EDMA_CONFIG_INTHALF |
+                  EDMA_CONFIG_INTMAJOR;
+  config.ssize  = EDMA_8BIT;
+  config.dsize  = EDMA_8BIT;
+  config.nbytes = 1;
+#ifdef CONFIG_IMX9_EDMA_ELINK
+  config.linkch = 0;
+#endif
 
-  /* When TXFULL is set, there is no space in the Tx FIFO  */
+  imx9_dmach_xfrsetup(priv->rxdma, &config);
 
-  txcnt = imx9_read(sport, UARTWATER);
-  txcnt = txcnt >> UARTWATER_TXCNT_OFF;
-  txcnt &= UARTWATER_COUNT_MASK;
+  /* Reset our DMA shadow pointer and Rx data availability count to match
+   * the address just programmed above.
+   */
 
-  if (txcnt < sport->txfifo_size)
-    {
-      return true;
-    }
-  else
-    {
-      return false;
-    }
+  priv->rxdmanext = 0;
+#ifndef CONFIG_ARM64_DCACHE_DISABLE
+  priv->rxdmaavail = 0;
+#endif
+
+  /* Start the DMA channel, and arrange for callbacks at the half and
+   * full points in the FIFO.  This ensures that we have half a FIFO
+   * worth of time to claim bytes before they are overwritten.
+   */
+
+  imx9_dmach_start(priv->rxdma, imx9_dma_rxcallback, (void *)priv);
+
+  /* Clear DMA suspended flag. */
+
+  priv->rxdmasusp  = false;
 }
+#endif
 
 /****************************************************************************
- * Name: imx9_txempty
+ * Name: imx9_dma_rxint
  *
  * Description:
- *   Return true if the transmit fifo is empty
+ *   Call to enable or disable RX interrupts
  *
  ****************************************************************************/
 
-static bool imx9_txempty(struct uart_dev_s *dev)
+#ifdef SERIAL_HAVE_RXDMA
+static void imx9_dma_rxint(struct uart_dev_s *dev, bool enable)
 {
-  struct imx9_uart_port_s *sport = (struct imx9_uart_port_s *)dev->priv;
-  unsigned long            stat    = imx9_read(sport, UARTSTAT);
-  unsigned long            sfifo   = imx9_read(sport, UARTFIFO);
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+
+  /* Enable/disable DMA reception.
+   *
+   * Note that it is not safe to check for available bytes and immediately
+   * pass them to uart_recvchars as that could potentially recurse back
+   * to us again.  Instead, bytes must wait until the next up_dma_poll or
+   * DMA event.
+   */
 
-  if (stat & UARTSTAT_TC && sfifo & UARTFIFO_TXEMPT)
-    {
-      return true;
-    }
-  else
-    {
-      return false;
-    }
+  priv->rxenable = enable;
 }
+#endif
 
 /****************************************************************************
- * Name: imx9_irq_handler (and front-ends)
+ * Name: imx9_dma_rxavailable
  *
  * Description:
- *   This is the common UART interrupt handler.  It should cal
- *   uart_transmitchars or uart_receivechar to perform the appropriate data
- *   transfers.
+ *   Return true if the receive register is not empty
  *
  ****************************************************************************/
 
-static int imx9_uart_irq_handler(int irq, void *context, void *arg)
+#ifdef SERIAL_HAVE_RXDMA
+static bool imx9_dma_rxavailable(struct uart_dev_s *dev)
 {
-  struct uart_dev_s       *dev = (struct uart_dev_s *)arg;
-  struct imx9_uart_port_s *sport;
-  unsigned long            sts;
-  unsigned long            rxcount;
-
-  DEBUGASSERT(dev != NULL && dev->priv != NULL);
-  sport = (struct imx9_uart_port_s *)dev->priv;
-
-  sts     = imx9_read(sport, UARTSTAT);
-  rxcount = imx9_read(sport, UARTWATER);
-  rxcount = rxcount >> UARTWATER_RXCNT_OFF;
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
 
-  if (sts & UARTSTAT_RDRF || rxcount > 0)
-    {
-      uart_recvchars(dev);
-    }
-
-  if (sts & UARTSTAT_TDRE)
-    {
-      uart_xmitchars(dev);
-    }
+  /* Compare our receive pointer to the current DMA pointer, if they
+   * do not match, then there are bytes to be received.
+   */
 
-  imx9_write(sport, sts, UARTSTAT);
-  return OK;
+  return (imx9_dma_nextrx(priv) != priv->rxdmanext);
 }
+#endif
 
-static int imx9_lpuart_hw_reset(struct imx9_uart_port_s *sport)
-{
-  if (sport->is_console)
-    {
-      return 0;
-    }
-
-  /* Toggle the reset signal to reset and release the peripheral */
+/****************************************************************************
+ * Name: imx9_dma_txcallback
+ *
+ * Description:
+ *   This function clears dma buffer at complete of DMA transfer and wakes up
+ *   threads waiting for space in buffer.
+ *
+ ****************************************************************************/
 
-  putreg32(UARTGLOBAL_RST, sport->iobase + UARTGLOBAL);
-  putreg32(0, sport->iobase + UARTGLOBAL);
+#ifdef SERIAL_HAVE_TXDMA
+static void imx9_dma_txcallback(DMACH_HANDLE handle, void *arg, bool done,
+                                  int result)
+{
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)arg;
 
-  return 0;
-}
+  /* Update 'nbytes' indicating number of bytes actually transferred by DMA.
+   * This is important to free TX buffer space by 'uart_xmitchars_done'.
+   */
 
-static int imx9_setup(struct uart_dev_s *dev)
-{
-  irqstate_t               i_flags;
-  int                      ret;
-  struct imx9_uart_port_s *sport = (struct imx9_uart_port_s *)dev->priv;
+  priv->dev.dmatx.nbytes = priv->dev.dmatx.length;
+#if CONFIG_IMX9_EDMA_NTCD > 1
+  priv->dev.dmatx.nbytes += priv->dev.dmatx.nlength;
+#endif
 
-  i_flags = up_irq_save();
+  /* Adjust the pointers */
 
-  ret = imx9_lpuart_hw_reset(sport);
-  if (ret < 0)
-    {
-      serr("failed to reset hw: %d\n", ret);
-    }
+  uart_xmitchars_done(&priv->dev);
 
-  up_irq_restore(i_flags);
+  /* Send more data if available */
 
-  return ret;
+  imx9_dma_txavailable(&priv->dev);
 }
+#endif
 
 /****************************************************************************
- * Name: imx9_shutdown
+ * Name: imx9_dma_txavailable
  *
  * Description:
- *   Disable the UART.  This method is called when the serial
- *   port is closed
+ *        Informs DMA that Tx data is available and is ready for transfer.
  *
  ****************************************************************************/
 
-static void imx9_shutdown(struct uart_dev_s *dev)
+#ifdef SERIAL_HAVE_TXDMA
+static void imx9_dma_txavailable(struct uart_dev_s *dev)
 {
-  struct imx9_uart_port_s *sport = (struct imx9_uart_port_s *)dev->priv;
-  unsigned long            temp;
-  irqstate_t               i_flags;
-
-  i_flags = up_irq_save();
-
-  /* clear statue */
-
-  temp = imx9_read(sport, UARTSTAT);
-  imx9_write(sport, temp, UARTSTAT);
-
-  /* disable Rx/Tx DMA */
-
-  temp = imx9_read(sport, UARTBAUD);
-  temp &= ~(UARTBAUD_TDMAE | UARTBAUD_RDMAE | UARTBAUD_RIDMAE);
-  imx9_write(sport, temp, UARTBAUD);
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
 
-  /* disable Rx/Tx and interrupts */
+  /* Only send when the DMA is idle */
 
-  temp = imx9_read(sport, UARTCTRL);
-  temp &= ~(UARTCTRL_TE | UARTCTRL_RE | UARTCTRL_TIE |
-            UARTCTRL_TCIE | UARTCTRL_RIE | UARTCTRL_ILIE |
-            UARTCTRL_LOOPS);
-  imx9_write(sport, temp, UARTCTRL);
-  imx9_write(sport, 0, UARTMODIR);
-
-  up_irq_restore(i_flags);
+  if (imx9_dmach_idle(priv->txdma) == 0)
+    {
+      uart_xmitchars_dma(dev);
+    }
 }
+#endif
+
+/****************************************************************************
+ * Name: imx9_dma_send
+ *
+ * Description:
+ *   Called (usually) from the interrupt level to start DMA transfer.
+ *   (Re-)Configures DMA Stream updating buffer and buffer length.
+ *
+ ****************************************************************************/
 
-static void imx9_setup_watermark(struct imx9_uart_port_s *sport)
+#ifdef SERIAL_HAVE_TXDMA
+static void imx9_dma_send(struct uart_dev_s *dev)
 {
-  unsigned long val;
-  unsigned long ctrl;
-  unsigned long ctrl_saved;
-  unsigned long rxiden_cnt;
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+  struct imx9_edma_xfrconfig_s config;
 
-  ctrl       = imx9_read(sport, UARTCTRL);
-  ctrl_saved = ctrl;
-  ctrl       &= ~(UARTCTRL_TIE | UARTCTRL_TCIE | UARTCTRL_TE |
-                  UARTCTRL_RIE | UARTCTRL_RE);
-  imx9_write(sport, ctrl, UARTCTRL);
+  /* We need to stop DMA before reconfiguration */
 
-  /* enable FIFO mode */
+  imx9_dmach_stop(priv->txdma);
 
-  val         = imx9_read(sport, UARTFIFO);
-  val        |= UARTFIFO_TXFE | UARTFIFO_RXFE;
-  val        |= UARTFIFO_TXFLUSH | UARTFIFO_RXFLUSH;
-  val        &= ~(UARTFIFO_RXIDEN_MASK << UARTFIFO_RXIDEN_OFF);
-  rxiden_cnt = 0;
-  val        |= ((rxiden_cnt & UARTFIFO_RXIDEN_MASK) << UARTFIFO_RXIDEN_OFF);
+  /* Reset the number sent */
 
-  imx9_write(sport, val, UARTFIFO);
+  dev->dmatx.nbytes = 0;
 
-  /* set the watermark
-   * rx_watermark = 1;
+  /* Make use of setup function to update buffer and its length for next
+   * transfer
    */
 
-  val = (1 << UARTWATER_RXWATER_OFF) | (0x0 << UARTWATER_TXWATER_OFF);
-  imx9_write(sport, val, UARTWATER);
-
-  /* Restore cr2 */
-
-  imx9_write(sport, ctrl_saved, UARTCTRL);
-}
-
-static void imx9_setup_watermark_enable(struct imx9_uart_port_s *sport)
-{
-  uint32_t temp;
+  config.iter   = dev->dmatx.length;
+  config.flags  = EDMA_CONFIG_LINKTYPE_LINKNONE;
+  config.ssize  = EDMA_8BIT;
+  config.dsize  = EDMA_8BIT;
+  config.nbytes = 1;
+  config.saddr  = (uintptr_t)dev->dmatx.buffer;
+  config.daddr  = priv->uartbase + IMX9_LPUART_DATA_OFFSET;
+  config.soff   = 1;
+  config.doff   = 0;
+#ifdef CONFIG_IMX9_EDMA_ELINK
+  config.linkch  = 0;
+#endif
 
-  imx9_setup_watermark(sport);
+  /* Flush the contents of the TX buffer into physical memory */
 
-  temp = imx9_read(sport, UARTCTRL);
-  temp |= UARTCTRL_RE | UARTCTRL_TE;
-  temp |= UARTCTRL_IDLECFG << UARTCTRL_IDLECFG_OFF;
-  imx9_write(sport, temp, UARTCTRL);
-}
+  up_clean_dcache((uintptr_t)dev->dmatx.buffer,
+                  (uintptr_t)dev->dmatx.buffer + dev->dmatx.length);
 
-static void imx9_hw_disable(struct imx9_uart_port_s *sport)
-{
-  unsigned long temp;
+  /* Setup first half */
 
-  temp  = imx9_read(sport, UARTCTRL);
-  temp  &= ~(UARTCTRL_RIE | UARTCTRL_ILIE | UARTCTRL_RE |
-             UARTCTRL_TIE | UARTCTRL_TE);
-  imx9_write(sport, temp, UARTCTRL);
-}
+  imx9_dmach_xfrsetup(priv->txdma, &config);
 
-static void imx9_configure(struct imx9_uart_port_s *sport)
-{
-  unsigned long temp;
+#if CONFIG_IMX9_EDMA_NTCD > 1
+  /* Is this a split transfer? */
 
-  temp = imx9_read(sport, UARTCTRL);
-  temp |= UARTCTRL_RIE | UARTCTRL_ILIE;
-  temp |= UARTCTRL_TIE;
-  imx9_write(sport, temp, UARTCTRL);
-}
+  if (dev->dmatx.nbuffer)
+    {
+      config.iter   = priv->dev.dmatx.nlength;
+      config.saddr  = (uintptr_t)priv->dev.dmatx.nbuffer;
 
-static void imx9_hw_setup(struct imx9_uart_port_s *sport)
-{
-  irqstate_t i_flags;
+      /* Flush the contents of the next TX buffer into physical memory */
 
-  i_flags = up_irq_save();
+      up_clean_dcache((uintptr_t)dev->dmatx.nbuffer,
+                      (uintptr_t)dev->dmatx.nbuffer + dev->dmatx.nlength);
 
-  imx9_hw_disable(sport);
+      imx9_dmach_xfrsetup(priv->txdma, &config);
+    }
+#endif
 
-  imx9_setup_watermark_enable(sport);
-  imx9_configure(sport);
-  up_enable_irq(sport->irq_num);
+  /* Start transmission with the callback on DMA completion */
 
-  up_irq_restore(i_flags);
+  imx9_dmach_start(priv->txdma, imx9_dma_txcallback, (void *)priv);
 }
+#endif
 
 /****************************************************************************
- * Name: imx9_attach
+ * Name: imx9_send
  *
  * Description:
- *   Configure the UART to operation in interrupt driven mode.  This method
- *   is called when the serial port is opened.  Normally, this is just after
- *   the setup() method is called, however, the serial console may operate in
- *   a non-interrupt driven mode during the boot phase.
- *
- *   RX and TX interrupts are not enabled when by the attach method (unless
- *   the hardware supports multiple levels of interrupt enabling).  The RX
- *   and TX interrupts are not enabled until the txint() and rxint() methods
- *   are called.
+ *   This method will send one byte on the UART
  *
  ****************************************************************************/
 
-static int imx9_attach(struct uart_dev_s *dev)
+static void imx9_send(struct uart_dev_s *dev, int ch)
 {
-  struct imx9_uart_port_s *sport = (struct imx9_uart_port_s *)dev->priv;
-  int                      ret;
-  unsigned long            temp;
-
-  /* determine FIFO size */
-
-  temp = imx9_read(sport, UARTFIFO);
-
-  sport->txfifo_size = UARTFIFO_DEPTH(
-    (temp >> UARTFIFO_TXSIZE_OFF) & UARTFIFO_FIFOSIZE_MASK);
-  sport->rxfifo_size = UARTFIFO_DEPTH(
-    (temp >> UARTFIFO_RXSIZE_OFF) & UARTFIFO_FIFOSIZE_MASK);
-
-  ret = irq_attach(sport->irq_num, imx9_uart_irq_handler, dev);
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
 
-  if (ret == OK)
-    {
-      imx9_hw_setup(sport);
-    }
-  else
+#ifdef CONSOLE_DEV
+  if (dev == &CONSOLE_DEV.dev && !dev->isconsole)
     {
-      sinfo("error ret=%d\n", ret);
+      return;
     }
+#endif
 
-  return ret;
+  imx9_serialout(priv, IMX9_LPUART_DATA_OFFSET, (uint32_t)ch);
 }
 
 /****************************************************************************
- * Name: imx_detach
+ * Name: imx9_dma_txint
  *
  * Description:
- *   Detach UART interrupts.  This method is called when the serial port is
- *   closed normally just before the shutdown method is called.  The
- *   exception is the serial console which is never shutdown.
+ *   Call to enable or disable TX interrupts from the UART.
  *
  ****************************************************************************/
 
-static void imx9_detach(struct uart_dev_s *dev)
+#ifdef SERIAL_HAVE_TXDMA
+static void imx9_dma_txint(struct uart_dev_s *dev, bool enable)
 {
-  struct imx9_uart_port_s *sport = (struct imx9_uart_port_s *)dev->priv;
+  /* Nothing to do. */
 
-  up_disable_irq(sport->irq_num);
-  irq_detach(sport->irq_num);
+  /* In case of DMA transfer we do not want to make use of UART interrupts.
+   * Instead, we use DMA interrupts that are activated once during boot
+   * sequence. Furthermore we can use imx9_dma_txcallback() to handle
+   * stuff at half DMA transfer or after transfer completion (depending
+   * on the configuration).
+   */
 }
+#endif
 
 /****************************************************************************
- * Name: imx9_ioctl
+ * Name: imx9_txint
  *
  * Description:
- *   All ioctl calls will be routed through this method
- *   for current imx9 configure.
+ *   Call to enable or disable TX interrupts
  *
  ****************************************************************************/
 
-static int imx9_ioctl(struct file *filep, int cmd, unsigned long arg)
+#if !defined(SERIAL_HAVE_ONLY_TXDMA)
+static void imx9_txint(struct uart_dev_s *dev, bool enable)
 {
-#if defined(CONFIG_SERIAL_TIOCSERGSTRUCT) || defined(CONFIG_SERIAL_TERMIOS)
-  struct inode      *inode = filep->f_inode;
-  struct uart_dev_s *dev   = inode->i_private;
-  irqstate_t        flags;
-#endif
-  int               ret = OK;
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+  irqstate_t flags;
+  uint32_t regval;
 
-  switch (cmd)
-    {
-#ifdef CONFIG_SERIAL_TIOCSERGSTRUCT
-    case TIOCSERGSTRUCT:
+  /* Enable interrupt for TX complete */
+
+  flags = spin_lock_irqsave(NULL);
+  if (enable)
     {
-      struct imx_uart_s *user = (struct imx_uart_s *)arg;
-      if (!user)
-        {
-          ret = -EINVAL;
-        }
-      else
-        {
-          memcpy(user, dev, sizeof(struct imx_uart_s));
-        }
-    }
-    break;
+#ifndef CONFIG_SUPPRESS_SERIAL_INTS
+      priv->ie |= LPUART_CTRL_TIE;
 #endif
-
-#ifdef CONFIG_SERIAL_TERMIOS
-    case TCGETS:
+    }
+  else
     {
-      struct termios    *termiosp = (struct termios *)arg;
-      struct imx_uart_s *priv     = (struct imx_uart_s *)dev->priv;
-
-      if (!termiosp)
-        {
-          ret = -EINVAL;
-          break;
-        }
-
-      /* Return parity */
-
-      termiosp->c_cflag = ((priv->parity != 0) ? PARENB : 0) |
-                         ((priv->parity == 1) ? PARODD : 0);
-
-      /* Return stop bits */
-
-      termiosp->c_cflag |= (priv->stopbits2) ? CSTOPB : 0;
-
-      /* Return flow control */
+      priv->ie &= ~LPUART_CTRL_TIE;
+    }
 
-#ifdef CONFIG_SERIAL_OFLOWCONTROL
-      termiosp->c_cflag |= ((priv->oflow) ? CCTS_OFLOW : 0);
-#endif
-#ifdef CONFIG_SERIAL_IFLOWCONTROL
-      termiosp->c_cflag |= ((priv->iflow) ? CRTS_IFLOW : 0);
+  regval  = imx9_serialin(priv, IMX9_LPUART_CTRL_OFFSET);
+  regval &= ~LPUART_ALL_INTS;
+  regval |= priv->ie;
+  imx9_serialout(priv, IMX9_LPUART_CTRL_OFFSET, regval);
+  spin_unlock_irqrestore(NULL, flags);
+}
 #endif
 
-      /* Return baud */
+/****************************************************************************
+ * Name: imx9_txready
+ *
+ * Description:
+ *   Return true if the transmit register is available to be written to
+ *
+ ****************************************************************************/
 
-      cfsetispeed(termiosp, priv->baud);
+static bool imx9_txready(struct uart_dev_s *dev)
+{
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+  uint32_t regval;
 
-      /* Return number of bits */
+  regval = imx9_serialin(priv, IMX9_LPUART_STAT_OFFSET);
+  return ((regval & LPUART_STAT_TDRE) != 0);
+}
 
-      switch (priv->bits)
-        {
-        case 5:
-        {
-          termiosp->c_cflag |= CS5;
-          break;
-        }
+/****************************************************************************
+ * Name: imx9_txempty
+ *
+ * Description:
+ *   Return true if the transmit reg is empty
+ *
+ ****************************************************************************/
 
-        case 6:
-        {
-          termiosp->c_cflag |= CS6;
-          break;
-        }
+static bool imx9_txempty(struct uart_dev_s *dev)
+{
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)dev;
+  uint32_t regval;
 
-        case 7:
-        {
-          termiosp->c_cflag |= CS7;
-          break;
-        }
+  regval = imx9_serialin(priv, IMX9_LPUART_STAT_OFFSET);
+  return ((regval & LPUART_STAT_TDRE) != 0);
+}
 
-        default:
-        case 8:
-        {
-          termiosp->c_cflag |= CS8;
-          break;
-        }
+/****************************************************************************
+ * Name: imx9_dma_rxcallback
+ *
+ * Description:
+ *   This function checks the current DMA state and calls the generic
+ *   serial stack when bytes appear to be available.
+ *
+ ****************************************************************************/
 
-#if defined(CS9)
-        case 9:
-        {
-          termiosp->c_cflag |= CS9;
-          break;
-        }
-#endif
-        }
-    }
-    break;
+#ifdef SERIAL_HAVE_RXDMA
+static void imx9_dma_rxcallback(DMACH_HANDLE handle, void *arg, bool done,
+                                  int result)
+{
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)arg;
+  uint32_t sr;
 
-    case TCSETS:
+  if (priv->rxenable && imx9_dma_rxavailable(&priv->dev))
     {
-      struct termios    *termiosp = (struct termios *)arg;
-      struct imx_uart_s *priv     = (struct imx_uart_s *)dev->priv;
-      uint32_t           baud;
-      uint32_t           ie;
-      uint8_t            parity;
-      uint8_t            nbits;
-      bool               stop2;
-
-      if ((!termiosp)
-#ifdef CONFIG_SERIAL_OFLOWCONTROL
-          || ((termiosp->c_cflag & CCTS_OFLOW) && (priv->cts_gpio == 0))
-#endif
-#ifdef CONFIG_SERIAL_IFLOWCONTROL
-          || ((termiosp->c_cflag & CRTS_IFLOW) && (priv->rts_gpio == 0))
-#endif
-          )
-        {
-          ret = -EINVAL;
-          break;
-        }
-
-      /* Decode baud. */
-
-      ret  = OK;
-      baud = cfgetispeed(termiosp);
+      uart_recvchars(&priv->dev);
+    }
 
-      /* Decode number of bits */
+  /* Get the masked LPUART status word to check and clear error flags.
+   *
+   * When wake-up from low power mode was not fast enough, UART is resumed
+   * too late and sometimes exactly when character was coming over UART,
+   * resulting to frame error.
+   * If error flag is not cleared, Rx DMA will be stuck. Clearing errors
+   * will release Rx DMA.
+   */
 
-      switch (termiosp->c_cflag & CSIZE)
-        {
-        case CS5:
-        {
-          nbits = 5;
-          break;
-        }
+  sr = imx9_serialin(priv, IMX9_LPUART_STAT_OFFSET);
 
-        case CS6:
-        {
-          nbits = 6;
-          break;
-        }
+  if ((sr & (LPUART_STAT_OR | LPUART_STAT_NF | LPUART_STAT_FE)) != 0)
+    {
+      imx9_serialout(priv, IMX9_LPUART_STAT_OFFSET,
+                      sr & (LPUART_STAT_OR |
+                            LPUART_STAT_NF |
+                            LPUART_STAT_FE));
+    }
+}
+#endif
 
-        case CS7:
-        {
-          nbits = 7;
-          break;
-        }
+/****************************************************************************
+ * Name: up_pm_notify
+ *
+ * Description:
+ *   Notify the driver of new power state. This callback is  called after
+ *   all drivers have had the opprivunity to prepare for the new power state.
+ *
+ * Input Parameters:
+ *
+ *    cb - Returned to the driver. The driver version of the callback
+ *         structure may include additional, driver-specific state data at
+ *         the end of the structure.
+ *
+ *    pmstate - Identifies the new PM state
+ *
+ * Returned Value:
+ *   None - The driver already agreed to transition to the low power
+ *   consumption state when when it returned OK to the prepare() call.
+ *
+ ****************************************************************************/
 
-        case CS8:
+#ifdef CONFIG_PM
+static void up_pm_notify(struct pm_callback_s *cb, int domain,
+                         enum pm_state_e pmstate)
+{
+  switch (pmstate)
+    {
+      case(PM_NORMAL):
         {
-          nbits = 8;
-          break;
+          /* Logic for PM_NORMAL goes here */
         }
+        break;
 
-#if defined(CS9)
-        case CS9:
+      case(PM_IDLE):
         {
-          nbits = 9;
-          break;
+          /* Logic for PM_IDLE goes here */
         }
+        break;
 
-#endif
-        default:
+      case(PM_STANDBY):
         {
-          ret = -EINVAL;
-          break;
+          /* Logic for PM_STANDBY goes here */
         }
-        }
-
-      /* Decode parity */
+        break;
 
-      if ((termiosp->c_cflag & PARENB) != 0)
+      case(PM_SLEEP):
         {
-          parity = (termiosp->c_cflag & PARODD) ? 1 : 2;
+          /* Logic for PM_SLEEP goes here */
         }
-      else
-        {
-          parity = 0;
-        }
-
-      /* Decode stop bits */
-
-      stop2 = (termiosp->c_cflag & CSTOPB) != 0;
-
-      /* Verify that all settings are valid before committing */
-
-      if (ret == OK)
-        {
-          /* Commit */
+        break;
 
-          priv->baud      = baud;
-          priv->parity    = parity;
-          priv->bits      = nbits;
-          priv->stopbits2 = stop2;
-#ifdef CONFIG_SERIAL_OFLOWCONTROL
-          priv->oflow = (termiosp->c_cflag & CCTS_OFLOW) != 0;
-#endif
-#ifdef CONFIG_SERIAL_IFLOWCONTROL
-          priv->iflow = (termiosp->c_cflag & CRTS_IFLOW) != 0;
-#endif
-
-          /* effect the changes immediately - note that we do not
-           * implement TCSADRAIN / TCSAFLUSH
-           */
-
-          flags = spin_lock_irqsave(NULL);
-          imx9_disableuartint(priv, &ie);
-          ret = imx9_setup(dev);
-
-          /* Restore the interrupt state */
+      default:
 
-          imx_restoreuartint(priv, ie);
-          priv->ie = ie;
-          spin_unlock_irqrestore(NULL, flags);
-        }
-    }
-    break;
-#endif /* CONFIG_SERIAL_TERMIOS */
+        /* Should not get here */
 
-    case TIOCSBRK:  /* BSD compatibility: Turn break on, unconditionally */
-    case TIOCCBRK:  /* BSD compatibility: Turn break off, unconditionally */
-    default:
-    {
-      ret = -ENOTTY;
-      break;
-    }
+        break;
     }
-
-  return ret;
 }
+#endif
 
 /****************************************************************************
- * Private Data
+ * Name: up_pm_prepare
+ *
+ * Description:
+ *   Request the driver to prepare for a new power state. This is a warning
+ *   that the system is about to enter into a new power state. The driver
+ *   should begin whatever operations that may be required to enter power
+ *   state. The driver may abort the state change mode by returning a
+ *   non-zero value from the callback function.
+ *
+ * Input Parameters:
+ *
+ *    cb - Returned to the driver. The driver version of the callback
+ *         structure may include additional, driver-specific state data at
+ *         the end of the structure.
+ *
+ *    pmstate - Identifies the new PM state
+ *
+ * Returned Value:
+ *   Zero - (OK) means the event was successfully processed and that the
+ *          driver is prepared for the PM state change.
+ *
+ *   Non-zero - means that the driver is not prepared to perform the tasks
+ *              needed achieve this power setting and will cause the state
+ *              change to be aborted. NOTE: The prepare() method will also
+ *              be called when reverting from lower back to higher power
+ *              consumption modes (say because another driver refused a
+ *              lower power state change). Drivers are not permitted to
+ *              return non-zero values when reverting back to higher power
+ *              consumption modes!
+ *
+ *
  ****************************************************************************/
 
-/* Serial driver UART operations */
-
-static const struct uart_ops_s g_uart_ops =
-{
-  .setup       = imx9_setup,
-  .shutdown    = imx9_shutdown,
-  .attach      = imx9_attach,
-  .detach      = imx9_detach,
-  .ioctl       = imx9_ioctl,
-  .receive     = imx9_receive,
-  .rxint       = imx9_rxint,
-  .rxavailable = imx9_rxavailable,
-#ifdef CONFIG_SERIAL_IFLOWCONTROL
-  .rxflowcontrol = NULL,
-#endif
-  .send        = imx9_send,
-  .txint       = imx9_txint,
-  .txready     = imx9_txready,
-  .txempty     = imx9_txempty,
-};
-
-/* I/O buffers */
-
-#ifdef CONFIG_IMX9_UART1
-static char g_uart1rxbuffer[CONFIG_UART1_RXBUFSIZE];
-static char g_uart1txbuffer[CONFIG_UART1_TXBUFSIZE];
-
-/* This describes the state of the IMX uart1 port. */
-
-static struct imx9_uart_port_s g_uart1priv =
+#ifdef CONFIG_PM
+static int up_pm_prepare(struct pm_callback_s *cb, int domain,
+                         enum pm_state_e pmstate)
 {
-  .iobase     = IMX9_LPUART1_BASE,
-  .baud       = CONFIG_UART1_BAUD,
-  .irq_num    = IMX9_IRQ_LPUART1,
-  .is_console = 1,
-};
-
-static struct uart_dev_s g_uart1port =
-{
-  .recv =
-  {
-    .size   = CONFIG_UART1_RXBUFSIZE,
-    .buffer = g_uart1rxbuffer,
-  },
-  .xmit =
-  {
-    .size   = CONFIG_UART1_TXBUFSIZE,
-    .buffer = g_uart1txbuffer,
-  },
-  .ops  = &g_uart_ops,
-  .priv = &g_uart1priv,
-};
+  /* Logic to prepare for a reduced power state goes here. */
 
+  return OK;
+}
 #endif
 
 /****************************************************************************
@@ -875,9 +2701,9 @@ static struct uart_dev_s g_uart1port =
  * Name: arm64_earlyserialinit
  *
  * Description:
- *   Performs the low level UART initialization early in
- *   debug so that the serial console will be available
- *   during bootup.  This must be called before arm64_serialinit.
+ *   Performs the low level UART initialization early in debug so that the
+ *   serial console will be available during bootup.  This must be called
+ *   before arm64_serialinit.
  *
  ****************************************************************************/
 
@@ -891,60 +2717,105 @@ void arm64_earlyserialinit(void)
   /* Enable the console UART.  The other UARTs will be initialized if and
    * when they are first opened.
    */
+
 #ifdef CONSOLE_DEV
-  CONSOLE_DEV.isconsole = true;
-  imx9_setup(&CONSOLE_DEV);
+  CONSOLE_DEV.dev.isconsole = true;
+  imx9_setup(&CONSOLE_DEV.dev);
 #endif
 }
 
 /****************************************************************************
- * Name: up_putc
+ * Name: arm64_serialinit
  *
  * Description:
- *   Provide priority, low-level access to support OS debug
- *   writes
+ *   Register serial console and serial privs.  This assumes
+ *   that imx9_earlyserialinit was called previously.
  *
  ****************************************************************************/
 
-int up_putc(int ch)
+void arm64_serialinit(void)
 {
-  /* Check for LF */
+#ifdef CONFIG_PM
+  int ret;
 
-  if (ch == '\n')
-    {
-      /* Add CR */
+  /* Register to receive power management callbacks */
 
-      arm64_lowputc('\r');
-    }
+  ret = pm_register(&g_serial_pmcb);
+  DEBUGASSERT(ret == OK);
+  UNUSED(ret);
+#endif
 
-  arm64_lowputc((uint8_t)ch);
-  return ch;
+#ifdef CONSOLE_DEV
+  uart_register("/dev/console", &CONSOLE_DEV.dev);
+#if defined(SERIAL_HAVE_CONSOLE_DMA)
+  imx9_dma_setup(&CONSOLE_DEV.dev);
+#endif
+#endif
+
+  /* Register all UARTs */
+
+#ifdef TTYS0_DEV
+  uart_register("/dev/ttyS0", &TTYS0_DEV.dev);
+#endif
+#ifdef TTYS1_DEV
+  uart_register("/dev/ttyS1", &TTYS1_DEV.dev);
+#endif
+#ifdef TTYS2_DEV
+  uart_register("/dev/ttyS2", &TTYS2_DEV.dev);
+#endif
+#ifdef TTYS3_DEV
+  uart_register("/dev/ttyS3", &TTYS3_DEV.dev);
+#endif
+#ifdef TTYS4_DEV
+  uart_register("/dev/ttyS4", &TTYS4_DEV.dev);
+#endif
+#ifdef TTYS5_DEV
+  uart_register("/dev/ttyS5", &TTYS5_DEV.dev);
+#endif
+#ifdef TTYS6_DEV
+  uart_register("/dev/ttyS6", &TTYS6_DEV.dev);
+#endif
+#ifdef TTYS7_DEV
+  uart_register("/dev/ttyS7", &TTYS7_DEV.dev);
+#endif
 }
 
+#endif /* USE_SERIALDRIVER */
+
 /****************************************************************************
- * Name: arm64_serialinit
+ * Name: up_putc
  *
  * Description:
- *   Register serial console and serial ports.  This assumes
- *   that imx_earlyserialinit was called previously.
+ *   Provide priority, low-level access to suppriv OS debug  writes
  *
  ****************************************************************************/
 
-void arm64_serialinit(void)
+int up_putc(int ch)
 {
-  int ret;
+#ifdef CONSOLE_DEV
+  struct imx9_uart_s *priv = (struct imx9_uart_s *)&CONSOLE_DEV;
+  uint32_t ie;
 
-  ret = uart_register("/dev/console", &CONSOLE_DEV);
-  if (ret < 0)
+  if (!CONSOLE_DEV.dev.isconsole)
     {
-      sinfo("error at register dev/console, ret =%d\n", ret);
+      return ch;
     }
 
-  ret = uart_register("/dev/ttyS0", &TTYS0_DEV);
-  if (ret < 0)
+  imx9_disableuartint(priv, &ie);
+#endif
+
+  /* Check for LF */
+
+  if (ch == '\n')
     {
-      sinfo("error at register dev/ttyS0, ret =%d\n", ret);
+      /* Add CR */
+
+      arm64_lowputc('\r');
     }
-}
 
-#endif /* USE_SERIALDRIVER */
+  arm64_lowputc(ch);
+#ifdef CONSOLE_DEV
+  imx9_restoreuartint(priv, ie);
+#endif
+  return ch;
+}
diff --git a/arch/arm64/src/imx9/imx9_serial.h b/arch/arm64/src/imx9/imx9_serial.h
index 854c6367aee77..e9c175a8ce14d 100644
--- a/arch/arm64/src/imx9/imx9_serial.h
+++ b/arch/arm64/src/imx9/imx9_serial.h
@@ -27,70 +27,216 @@
 
 #include <nuttx/config.h>
 
-#include "arm64_internal.h"
-
 /****************************************************************************
  * Pre-processor Definitions
  ****************************************************************************/
 
-/****************************************************************************
- * Public Types
- ****************************************************************************/
+#if defined(CONFIG_IMX9_LPUART1)  || defined(CONFIG_IMX9_LPUART2)  || \
+    defined(CONFIG_IMX9_LPUART3)  || defined(CONFIG_IMX9_LPUART4)  || \
+    defined(CONFIG_IMX9_LPUART5)  || defined(CONFIG_IMX9_LPUART6)  || \
+    defined(CONFIG_IMX9_LPUART7)  || defined(CONFIG_IMX9_LPUART8)
+#  define HAVE_UART 1
+#endif
 
-/****************************************************************************
- * Inline Functions
- ****************************************************************************/
+/* Assume DMA is not used on the console UART */
+
+#undef SERIAL_HAVE_CONSOLE_RXDMA
+#undef SERIAL_HAVE_CONSOLE_TXDMA
+
+#if !defined(HAVE_UART) || !defined(CONFIG_ARCH_DMA)
+#  undef CONFIG_LPUART1_RXDMA
+#  undef CONFIG_LPUART1_TXDMA
+#  undef CONFIG_LPUART2_RXDMA
+#  undef CONFIG_LPUART2_TXDMA
+#  undef CONFIG_LPUART3_RXDMA
+#  undef CONFIG_LPUART3_TXDMA
+#  undef CONFIG_LPUART4_RXDMA
+#  undef CONFIG_LPUART4_TXDMA
+#  undef CONFIG_LPUART5_RXDMA
+#  undef CONFIG_LPUART5_TXDMA
+#  undef CONFIG_LPUART6_RXDMA
+#  undef CONFIG_LPUART6_TXDMA
+#  undef CONFIG_LPUART7_RXDMA
+#  undef CONFIG_LPUART7_TXDMA
+#  undef CONFIG_LPUART8_RXDMA
+#  undef CONFIG_LPUART8_TXDMA
+#endif
 
-#ifndef __ASSEMBLY__
+/* Disable the DMA configuration on all unused LPUARTs */
 
-/****************************************************************************
- * Public Data
- ****************************************************************************/
+#ifndef CONFIG_IMX9_LPUART1
+#  undef CONFIG_LPUART1_RXDMA
+#  undef CONFIG_LPUART1_TXDMA
+#endif
+
+#ifndef CONFIG_IMX9_LPUART2
+#  undef CONFIG_LPUART2_RXDMA
+#  undef CONFIG_LPUART2_TXDMA
+#endif
+
+#ifndef CONFIG_IMX9_LPUART3
+#  undef CONFIG_LPUART3_RXDMA
+#  undef CONFIG_LPUART3_TXDMA
+#endif
+
+#ifndef CONFIG_IMX9_LPUART4
+#  undef CONFIG_LPUART4_RXDMA
+#  undef CONFIG_LPUART4_TXDMA
+#endif
+
+#ifndef CONFIG_IMX9_LPUART5
+#  undef CONFIG_LPUART5_RXDMA
+#  undef CONFIG_LPUART5_TXDMA
+#endif
+
+#ifndef CONFIG_IMX9_LPUART6
+#  undef CONFIG_LPUART6_RXDMA
+#  undef CONFIG_LPUART6_TXDMA
+#endif
+
+#ifndef CONFIG_IMX9_LPUART8
+#  undef CONFIG_LPUART7_RXDMA
+#  undef CONFIG_LPUART7_TXDMA
+#endif
+
+/* Is RX DMA available on any (enabled) LPUART? */
+
+#undef SERIAL_HAVE_RXDMA
+#if defined(CONFIG_LPUART1_RXDMA)  || defined(CONFIG_LPUART2_RXDMA)  || \
+    defined(CONFIG_LPUART3_RXDMA)  || defined(CONFIG_LPUART4_RXDMA)  || \
+    defined(CONFIG_LPUART5_RXDMA)  || defined(CONFIG_LPUART6_RXDMA)  || \
+    defined(CONFIG_LPUART7_RXDMA)  || defined(CONFIG_LPUART8_RXDMA)
+#  define SERIAL_HAVE_RXDMA 1
+#endif
+
+/* Is TX DMA available on any (enabled) LPUART? */
+#undef SERIAL_HAVE_TXDMA
+#if defined(CONFIG_LPUART1_TXDMA)  || defined(CONFIG_LPUART2_TXDMA)  || \
+    defined(CONFIG_LPUART3_TXDMA)  || defined(CONFIG_LPUART4_TXDMA)  || \
+    defined(CONFIG_LPUART5_TXDMA)  || defined(CONFIG_LPUART6_TXDMA)  || \
+    defined(CONFIG_LPUART7_TXDMA)  || defined(CONFIG_LPUART8_TXDMA)
+#  define SERIAL_HAVE_TXDMA 1
+#endif
+
+/* Is RX DMA used on all (enabled) LPUARTs */
+
+#define SERIAL_HAVE_ONLY_RXDMA 1
+#if defined(CONFIG_IMX9_LPUART1) && !defined(CONFIG_LPUART1_RXDMA)
+#  undef SERIAL_HAVE_ONLY_RXDMA
+#elif defined(CONFIG_IMX9_LPUART2) && !defined(CONFIG_LPUART2_RXDMA)
+#  undef SERIAL_HAVE_ONLY_RXDMA
+#elif defined(CONFIG_IMX9_LPUART3) && !defined(CONFIG_LPUART3_RXDMA)
+#  undef SERIAL_HAVE_ONLY_RXDMA
+#elif defined(CONFIG_IMX9_LPUART4) && !defined(CONFIG_LPUART4_RXDMA)
+#  undef SERIAL_HAVE_ONLY_RXDMA
+#elif defined(CONFIG_IMX9_LPUART5) && !defined(CONFIG_LPUART5_RXDMA)
+#  undef SERIAL_HAVE_ONLY_RXDMA
+#elif defined(CONFIG_IMX9_LPUART6) && !defined(CONFIG_LPUART6_RXDMA)
+#  undef SERIAL_HAVE_ONLY_RXDMA
+#elif defined(CONFIG_IMX9_LPUART7) && !defined(CONFIG_LPUART7_RXDMA)
+#  undef SERIAL_HAVE_ONLY_RXDMA
+#elif defined(CONFIG_IMX9_LPUART8) && !defined(CONFIG_LPUART8_RXDMA)
+#  undef SERIAL_HAVE_ONLY_RXDMA
+#endif
+
+/* Is TX DMA used on all (enabled) LPUARTs */
+
+#define SERIAL_HAVE_ONLY_TXDMA 1
+#if defined(CONFIG_IMX9_LPUART1) && !defined(CONFIG_LPUART1_TXDMA)
+#  undef SERIAL_HAVE_ONLY_TXDMA
+#elif defined(CONFIG_IMX9_LPUART2) && !defined(CONFIG_LPUART2_TXDMA)
+#  undef SERIAL_HAVE_ONLY_TXDMA
+#elif defined(CONFIG_IMX9_LPUART3) && !defined(CONFIG_LPUART3_TXDMA)
+#  undef SERIAL_HAVE_ONLY_TXDMA
+#elif defined(CONFIG_IMX9_LPUART4) && !defined(CONFIG_LPUART4_TXDMA)
+#  undef SERIAL_HAVE_ONLY_TXDMA
+#elif defined(CONFIG_IMX9_LPUART5) && !defined(CONFIG_LPUART5_TXDMA)
+#  undef SERIAL_HAVE_ONLY_TXDMA
+#elif defined(CONFIG_IMX9_LPUART6) && !defined(CONFIG_LPUART6_TXDMA)
+#  undef SERIAL_HAVE_ONLY_TXDMA
+#elif defined(CONFIG_IMX9_LPUART7) && !defined(CONFIG_LPUART7_TXDMA)
+#  undef SERIAL_HAVE_ONLY_TXDMA
+#elif defined(CONFIG_IMX9_LPUART8) && !defined(CONFIG_LPUART8_TXDMA)
+#  undef SERIAL_HAVE_ONLY_TXDMA
+#endif
+
+#undef SERIAL_HAVE_ONLY_DMA
+#if defined(SERIAL_HAVE_ONLY_RXDMA) && defined(SERIAL_HAVE_ONLY_TXDMA)
+#define SERIAL_HAVE_ONLY_DMA
+#endif
+
+/* Verify that DMA has been enabled and the DMA channel has been defined.
+ */
+
+#if defined(SERIAL_HAVE_TXDMA) || defined(SERIAL_HAVE_RXDMA)
+#  ifndef CONFIG_IMX9_EDMA
+#    error IMXRT LPUART receive or transmit DMA requires CONFIG_IMX9_EDMA
+#  endif
+#endif
+
+#if defined(SERIAL_HAVE_RXDMA)
+/* Currently RS-485 support cannot be enabled when RXDMA is in use due to
+ * lack of testing.
+ */
+
+#  if (defined(CONFIG_LPUART1_RXDMA) && defined(CONFIG_LPUART1_RS485)) || \
+      (defined(CONFIG_LPUART2_RXDMA) && defined(CONFIG_LPUART2_RS485)) || \
+      (defined(CONFIG_LPUART3_RXDMA) && defined(CONFIG_LPUART3_RS485)) || \
+      (defined(CONFIG_LPUART4_RXDMA) && defined(CONFIG_LPUART4_RS485)) || \
+      (defined(CONFIG_LPUART5_RXDMA) && defined(CONFIG_LPUART5_RS485)) || \
+      (defined(CONFIG_LPUART6_RXDMA) && defined(CONFIG_LPUART6_RS485)) || \
+      (defined(CONFIG_LPUART7_RXDMA) && defined(CONFIG_LPUART7_RS485)) || \
+      (defined(CONFIG_LPUART8_RXDMA) && defined(CONFIG_LPUART8_RS485))
+#    error "RXDMA and RS-485 cannot be enabled at the same time for the same LPUART"
+#  endif
+#endif /* SERIAL_HAVE_RXDMA */
+
+/* Currently RS-485 support cannot be enabled when TXDMA is in use due to
+ * lack of testing.
+ */
+
+#  if (defined(CONFIG_LPUART1_TXDMA) && defined(CONFIG_LPUART1_RS485)) || \
+      (defined(CONFIG_LPUART2_TXDMA) && defined(CONFIG_LPUART2_RS485)) || \
+      (defined(CONFIG_LPUART3_TXDMA) && defined(CONFIG_LPUART3_RS485)) || \
+      (defined(CONFIG_LPUART4_TXDMA) && defined(CONFIG_LPUART4_RS485)) || \
+      (defined(CONFIG_LPUART5_TXDMA) && defined(CONFIG_LPUART5_RS485)) || \
+      (defined(CONFIG_LPUART6_TXDMA) && defined(CONFIG_LPUART6_RS485)) || \
+      (defined(CONFIG_LPUART7_TXDMA) && defined(CONFIG_LPUART7_RS485)) || \
+      (defined(CONFIG_LPUART8_TXDMA) && defined(CONFIG_LPUART8_RS485))
+#  error "TXDMA and RS-485 cannot be enabled at the same time for the same LPUART"
+#endif
 
 /****************************************************************************
- * Public Function Prototypes
+ * Public Types
  ****************************************************************************/
 
 /****************************************************************************
- * Name: imx_earlyserialinit
- *
- * Description:
- *   Performs the low level UART initialization early in debug so that the
- *   serial console will be available during bootup.  This must be called
- *   before arm_serialinit.
- *
+ * Inline Functions
  ****************************************************************************/
 
-#ifdef USE_EARLYSERIALINIT
-void imx9_earlyserialinit(void);
-#endif
+#ifndef __ASSEMBLY__
 
 /****************************************************************************
- * Name: uart_earlyserialinit
- *
- * Description:
- *   Performs the low level UART initialization early in debug so that the
- *   serial console will be available during bootup.  This must be called
- *   before arm_serialinit.
- *
+ * Public Data
  ****************************************************************************/
 
-#if defined(USE_EARLYSERIALINIT) && defined(IMX9_HAVE_UART)
-void uart_earlyserialinit(void);
+#undef EXTERN
+#if defined(__cplusplus)
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
 #endif
 
 /****************************************************************************
- * Name: uart_serialinit
- *
- * Description:
- *   Register the UART serial console and serial ports.  This assumes that
- *   uart_earlyserialinit was called previously.
- *
+ * Public Function Prototypes
  ****************************************************************************/
 
-#ifdef IMX9_HAVE_UART
-void uart_serialinit(void);
+#undef EXTERN
+#if defined(__cplusplus)
+}
 #endif
 
 #endif /* __ASSEMBLY__ */
-#endif /* __ARCH_ARM_SRC_IMX9_IMX_SERIAL_H */
+#endif /* __ARCH_ARM64_SRC_IMX9_IMX9_SERIAL_H */
diff --git a/boards/arm64/imx9/imx93-evk/configs/nsh/defconfig b/boards/arm64/imx9/imx93-evk/configs/nsh/defconfig
index 64c7f53357551..b4ae81e0a023c 100644
--- a/boards/arm64/imx9/imx93-evk/configs/nsh/defconfig
+++ b/boards/arm64/imx9/imx93-evk/configs/nsh/defconfig
@@ -36,12 +36,13 @@ CONFIG_IMX9_GPIO_IRQ=y
 CONFIG_IMX9_LPI2C1=y
 CONFIG_IMX9_LPI2C_DYNTIMEO=y
 CONFIG_IMX9_LPI2C_DYNTIMEO_STARTSTOP=10
+CONFIG_IMX9_LPUART1=y
 CONFIG_IMX9_TPM3_PWM=y
 CONFIG_IMX9_TPM3_PWM_CHMUX=0x00000003
-CONFIG_IMX9_UART1=y
 CONFIG_IMX9_USBDEV_USBC1=y
 CONFIG_INIT_ENTRYPOINT="nsh_main"
 CONFIG_INTELHEX_BINARY=y
+CONFIG_LPUART1_SERIAL_CONSOLE=y
 CONFIG_NSH_ARCHINIT=y
 CONFIG_NSH_BUILTIN_APPS=y
 CONFIG_NSH_FILEIOSIZE=512
@@ -71,4 +72,3 @@ CONFIG_SYSTEM_SYSTEM=y
 CONFIG_SYSTEM_TIME64=y
 CONFIG_TESTING_GETPRIME=y
 CONFIG_TESTING_OSTEST=y
-CONFIG_UART1_SERIAL_CONSOLE=y
diff --git a/boards/arm64/imx9/imx93-evk/include/board.h b/boards/arm64/imx9/imx93-evk/include/board.h
index 6cd4c26e56143..37d46536eca7b 100644
--- a/boards/arm64/imx9/imx93-evk/include/board.h
+++ b/boards/arm64/imx9/imx93-evk/include/board.h
@@ -33,6 +33,11 @@
 
 #define IOMUX_LPI2C_DEFAULT  (IOMUXC_PAD_OD_ENABLE | IOMUXC_PAD_FSEL_SFAST | IOMUXC_PAD_DSE_X6)
 
+/* UART pin muxings */
+
+#define MUX_LPUART1_RX       IOMUX_CFG(IOMUXC_PAD_UART1_RXD_LPUART1_RX, 0, IOMUXC_MUX_SION_ON)
+#define MUX_LPUART1_TX       IOMUX_CFG(IOMUXC_PAD_UART1_TXD_LPUART1_TX, IOMUXC_PAD_FSEL_SLOW | IOMUXC_PAD_DSE_X4, 0)
+
 /* FLEXIO to PWM pin muxings */
 
 /* EVK signals
@@ -47,6 +52,8 @@
 #define FLEXIO1_PWM2_MUX IOMUX_CFG(IOMUXC_PAD_GPIO_IO06_FLEXIO1_FLEXIO06, IOMUXC_PAD_FSEL_SFAST | IOMUXC_PAD_DSE_X6, 0)
 #define FLEXIO1_PWM3_MUX IOMUX_CFG(IOMUXC_PAD_GPIO_IO07_FLEXIO1_FLEXIO07, IOMUXC_PAD_FSEL_SFAST | IOMUXC_PAD_DSE_X6, 0)
 
+/* LPI2Cs */
+
 /* TPM3 ch3 to PWM pin GPIO_IO24 muxing */
 
 #define TPM3_PWM3_MUX IOMUX_CFG(IOMUXC_PAD_GPIO_IO24_TPM3_CH3, IOMUXC_PAD_FSEL_SFAST | IOMUXC_PAD_DSE_X6, 0)