diff --git a/MAINTAINERS b/MAINTAINERS
index 878dcd23b331..03024e9589bc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13191,6 +13191,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/i2c/loongson,ls2x-i2c.yaml
 F:	drivers/i2c/busses/i2c-ls2x.c
 
+LOONGSON UART DRIVER
+M:	Haowei Zheng <zhenghaowei@loongson.cn>
+L:	linux-serial@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/serial/loongson,uart.yaml
+F:	drivers/tty/serial/8250/8250_loongson.c
+
 LOONGSON-2 SOC SERIES CLOCK DRIVER
 M:	Yinbo Zhu <zhuyinbo@loongson.cn>
 L:	linux-clk@vger.kernel.org
diff --git a/drivers/tty/serial/8250/8250_loongson.c b/drivers/tty/serial/8250/8250_loongson.c
new file mode 100644
index 000000000000..88205fed4fd3
--- /dev/null
+++ b/drivers/tty/serial/8250/8250_loongson.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020-2024 Loongson Technology Corporation Limited
+ */
+
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/reset.h>
+
+#include "8250.h"
+
+/* Flags */
+#define LOONGSON_UART_FRAC         BIT(0)
+
+/* Quirks */
+#define LOONGSON_UART_QUIRK_MCR    BIT(0)
+#define LOONGSON_UART_QUIRK_MSR    BIT(1)
+
+struct loongson_uart_config {
+	unsigned int flags;
+	unsigned int quirks;
+};
+
+struct loongson_uart_data {
+	struct reset_control *rst;
+	int line;
+	int mcr_invert;
+	int msr_invert;
+	const struct loongson_uart_config *config;
+};
+
+static const struct loongson_uart_config ls3a5000_uart_config = {
+	.flags = 0,
+	.quirks = LOONGSON_UART_QUIRK_MCR
+};
+
+static const struct loongson_uart_config ls7a_uart_config = {
+	.flags = 0,
+	.quirks = LOONGSON_UART_QUIRK_MCR | LOONGSON_UART_QUIRK_MSR
+};
+
+static const struct loongson_uart_config ls2k2000_uart_config = {
+	.flags = LOONGSON_UART_FRAC,
+	.quirks = LOONGSON_UART_QUIRK_MCR
+};
+
+static unsigned int serial_fixup(struct uart_port *p, unsigned int offset, unsigned int val)
+{
+	struct loongson_uart_data *data = p->private_data;
+
+	if (offset == UART_MCR)
+		val ^= data->mcr_invert;
+	if (offset == UART_MSR)
+		val ^= data->msr_invert;
+
+	return val;
+}
+
+static unsigned int loongson_serial_in(struct uart_port *p, int offset)
+{
+	unsigned int val, offset0 = offset;
+
+	offset = offset << p->regshift;
+	val = readb(p->membase + offset);
+
+	return serial_fixup(p, offset0, val);
+}
+
+static void loongson_serial_out(struct uart_port *p, int offset, int value)
+{
+	offset = offset << p->regshift;
+	writeb(serial_fixup(p, offset, value), p->membase + offset);
+}
+
+static unsigned int loongson_frac_get_divisor(struct uart_port *port,
+		unsigned int baud,
+		unsigned int *frac)
+{
+	unsigned int quot;
+
+	quot = DIV_ROUND_CLOSEST((port->uartclk << 4), baud);
+	*frac = quot & 0xff;
+
+	return quot >> 8;
+}
+
+static void loongson_frac_set_divisor(struct uart_port *port, unsigned int baud,
+		unsigned int quot, unsigned int quot_frac)
+{
+	struct uart_8250_port *up = up_to_u8250p(port);
+
+	serial_port_out(port, UART_LCR, up->lcr | UART_LCR_DLAB);
+
+	serial_dl_write(up, quot);
+
+	serial_port_out(port, 0x2, quot_frac);
+}
+
+static int loongson_uart_probe(struct platform_device *pdev)
+{
+	struct uart_8250_port uart = {};
+	struct loongson_uart_data *data;
+	struct uart_port *port;
+	struct resource *res;
+	int ret;
+
+	port = &uart.port;
+	spin_lock_init(&port->lock);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	port->flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_IOREMAP;
+	port->iotype = UPIO_MEM;
+	port->regshift = 0;
+	port->dev = &pdev->dev;
+	port->type = PORT_LOONGSON;
+	port->mapbase = res->start;
+	port->mapsize = resource_size(res);
+	port->serial_in = loongson_serial_in;
+	port->serial_out = loongson_serial_out;
+
+	port->irq = platform_get_irq(pdev, 0);
+	if (port->irq < 0)
+		return -EINVAL;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->config = device_get_match_data(&pdev->dev);
+
+	port->private_data = data;
+
+	if (data->config->flags & LOONGSON_UART_FRAC) {
+		port->get_divisor = loongson_frac_get_divisor;
+		port->set_divisor = loongson_frac_set_divisor;
+	}
+
+	if (data->config->quirks & LOONGSON_UART_QUIRK_MCR)
+		data->mcr_invert |= (UART_MCR_RTS | UART_MCR_DTR);
+
+	if (data->config->quirks & LOONGSON_UART_QUIRK_MSR)
+		data->msr_invert |= (UART_MSR_CTS | UART_MSR_DSR);
+
+	data->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+	if (IS_ERR(data->rst))
+		return PTR_ERR(data->rst);
+
+	device_property_read_u32(&pdev->dev, "clock-frequency", &port->uartclk);
+
+	ret = reset_control_deassert(data->rst);
+	if (ret)
+		goto err_unprepare;
+
+	ret = serial8250_register_8250_port(&uart);
+	if (ret < 0)
+		goto err_unprepare;
+
+	platform_set_drvdata(pdev, data);
+	data->line = ret;
+
+	return 0;
+
+err_unprepare:
+	reset_control_assert(data->rst);
+
+	return ret;
+}
+
+static void loongson_uart_remove(struct platform_device *pdev)
+{
+	struct loongson_uart_data *data = platform_get_drvdata(pdev);
+
+	serial8250_unregister_port(data->line);
+	reset_control_assert(data->rst);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int loongson_uart_suspend(struct device *dev)
+{
+	struct loongson_uart_data *data = dev_get_drvdata(dev);
+
+	serial8250_suspend_port(data->line);
+
+	return 0;
+}
+
+static int loongson_uart_resume(struct device *dev)
+{
+	struct loongson_uart_data *data = dev_get_drvdata(dev);
+
+	serial8250_resume_port(data->line);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops loongson_uart_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(loongson_uart_suspend, loongson_uart_resume)
+};
+
+static const struct of_device_id of_platform_serial_table[] = {
+	{.compatible = "loongson,ls7a-uart", .data = &ls7a_uart_config},
+	{.compatible = "loongson,ls3a5000-uart", .data = &ls3a5000_uart_config},
+	{.compatible = "loongson,ls2k2000-uart", .data = &ls2k2000_uart_config},
+	{},
+};
+MODULE_DEVICE_TABLE(of, of_platform_serial_table);
+
+static struct platform_driver loongson_uart_driver = {
+	.probe = loongson_uart_probe,
+	.remove = loongson_uart_remove,
+	.driver = {
+		.name = "loongson-uart",
+		.pm = &loongson_uart_pm_ops,
+		.of_match_table = of_platform_serial_table,
+	},
+};
+
+module_platform_driver(loongson_uart_driver);
+
+MODULE_DESCRIPTION("LOONGSON 8250 Driver");
+MODULE_AUTHOR("Haowei Zheng <zhenghaowei@loongson.cn>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 2786918aea98..60b72c785028 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -319,6 +319,14 @@ static const struct serial8250_config uart_config[] = {
 		.rxtrig_bytes	= {1, 8, 16, 30},
 		.flags		= UART_CAP_FIFO | UART_CAP_AFE,
 	},
+	[PORT_LOONGSON] = {
+		.name		= "Loongson",
+		.fifo_size	= 16,
+		.tx_loadsz	= 16,
+		.fcr		= UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10,
+		.rxtrig_bytes   = {1, 4, 8, 14},
+		.flags		= UART_CAP_FIFO,
+	},
 };
 
 /* Uart divisor latch read */
diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig
index 47ff50763c04..ca828e94719a 100644
--- a/drivers/tty/serial/8250/Kconfig
+++ b/drivers/tty/serial/8250/Kconfig
@@ -568,6 +568,15 @@ config SERIAL_8250_BCM7271
 	  including DMA support and high accuracy BAUD rates, say
 	  Y to this option. If unsure, say N.
 
+config SERIAL_8250_LOONGSON
+	tristate "Loongson 8250 serial port support"
+	default SERIAL_8250
+	depends on SERIAL_8250
+	depends on LOONGARCH
+	help
+	  If you have machine with Loongson and want to use this serial driver,
+	  say Y to this option. If unsure, say N.
+
 config SERIAL_OF_PLATFORM
 	tristate "Devicetree based probing for 8250 ports"
 	depends on SERIAL_8250 && OF
diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile
index 1516de629b61..e9587bf69f65 100644
--- a/drivers/tty/serial/8250/Makefile
+++ b/drivers/tty/serial/8250/Makefile
@@ -51,5 +51,6 @@ obj-$(CONFIG_SERIAL_8250_RT288X)	+= 8250_rt288x.o
 obj-$(CONFIG_SERIAL_8250_CS)		+= serial_cs.o
 obj-$(CONFIG_SERIAL_8250_UNIPHIER)	+= 8250_uniphier.o
 obj-$(CONFIG_SERIAL_8250_TEGRA)		+= 8250_tegra.o
+obj-$(CONFIG_SERIAL_8250_LOONGSON)	+= 8250_loongson.o
 
 CFLAGS_8250_ingenic.o += -I$(srctree)/scripts/dtc/libfdt
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 9c007a106330..9e316b9295e5 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -31,6 +31,7 @@
 #define PORT_ALTR_16550_F128 28 /* Altera 16550 UART with 128 FIFOs */
 #define PORT_RT2880	29	/* Ralink RT2880 internal UART */
 #define PORT_16550A_FSL64 30	/* Freescale 16550 UART with 64 FIFOs */
+#define PORT_LOONGSON   31      /* Loongson 16550 UART*/
 
 /*
  * ARM specific type numbers.  These are not currently guaranteed
