Skip to content

Commit 83b6beb

Browse files
Haowei ZhengKexyBiscuit
authored andcommitted
FROMLIST: tty: serial: 8250: Add loongson uart driver support
Due to certain hardware design challenges, we have opted to utilize a dedicated UART driver to probe the UART interface. Signed-off-by: Haowei Zheng <[email protected]> Link: https://lore.kernel.org/all/[email protected]/ Signed-off-by: Kexy Biscuit <[email protected]> [Mingcong Bai: Resolved minor conflicts in MAINTAINERS, and drivers/tty/serial/8250/Kconfig.] Signed-off-by: Mingcong Bai <[email protected]>
1 parent b083171 commit 83b6beb

File tree

6 files changed

+254
-0
lines changed

6 files changed

+254
-0
lines changed

MAINTAINERS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14155,6 +14155,13 @@ S: Maintained
1415514155
F: Documentation/devicetree/bindings/pwm/loongson,ls7a-pwm.yaml
1415614156
F: drivers/pwm/pwm-loongson.c
1415714157

14158+
LOONGSON UART DRIVER
14159+
M: Haowei Zheng <[email protected]>
14160+
14161+
S: Maintained
14162+
F: Documentation/devicetree/bindings/serial/loongson,uart.yaml
14163+
F: drivers/tty/serial/8250/8250_loongson.c
14164+
1415814165
LOONGSON-2 SOC SERIES CLOCK DRIVER
1415914166
M: Yinbo Zhu <[email protected]>
1416014167
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (C) 2020-2024 Loongson Technology Corporation Limited
4+
*/
5+
6+
#include <linux/console.h>
7+
#include <linux/delay.h>
8+
#include <linux/io.h>
9+
#include <linux/module.h>
10+
#include <linux/reset.h>
11+
12+
#include "8250.h"
13+
14+
/* Flags */
15+
#define LOONGSON_UART_FRAC BIT(0)
16+
17+
/* Quirks */
18+
#define LOONGSON_UART_QUIRK_MCR BIT(0)
19+
#define LOONGSON_UART_QUIRK_MSR BIT(1)
20+
21+
struct loongson_uart_config {
22+
unsigned int flags;
23+
unsigned int quirks;
24+
};
25+
26+
struct loongson_uart_data {
27+
struct reset_control *rst;
28+
int line;
29+
int mcr_invert;
30+
int msr_invert;
31+
const struct loongson_uart_config *config;
32+
};
33+
34+
static const struct loongson_uart_config ls3a5000_uart_config = {
35+
.flags = 0,
36+
.quirks = LOONGSON_UART_QUIRK_MCR
37+
};
38+
39+
static const struct loongson_uart_config ls7a_uart_config = {
40+
.flags = 0,
41+
.quirks = LOONGSON_UART_QUIRK_MCR | LOONGSON_UART_QUIRK_MSR
42+
};
43+
44+
static const struct loongson_uart_config ls2k2000_uart_config = {
45+
.flags = LOONGSON_UART_FRAC,
46+
.quirks = LOONGSON_UART_QUIRK_MCR
47+
};
48+
49+
static unsigned int serial_fixup(struct uart_port *p, unsigned int offset, unsigned int val)
50+
{
51+
struct loongson_uart_data *data = p->private_data;
52+
53+
if (offset == UART_MCR)
54+
val ^= data->mcr_invert;
55+
if (offset == UART_MSR)
56+
val ^= data->msr_invert;
57+
58+
return val;
59+
}
60+
61+
static unsigned int loongson_serial_in(struct uart_port *p, int offset)
62+
{
63+
unsigned int val, offset0 = offset;
64+
65+
offset = offset << p->regshift;
66+
val = readb(p->membase + offset);
67+
68+
return serial_fixup(p, offset0, val);
69+
}
70+
71+
static void loongson_serial_out(struct uart_port *p, int offset, int value)
72+
{
73+
offset = offset << p->regshift;
74+
writeb(serial_fixup(p, offset, value), p->membase + offset);
75+
}
76+
77+
static unsigned int loongson_frac_get_divisor(struct uart_port *port,
78+
unsigned int baud,
79+
unsigned int *frac)
80+
{
81+
unsigned int quot;
82+
83+
quot = DIV_ROUND_CLOSEST((port->uartclk << 4), baud);
84+
*frac = quot & 0xff;
85+
86+
return quot >> 8;
87+
}
88+
89+
static void loongson_frac_set_divisor(struct uart_port *port, unsigned int baud,
90+
unsigned int quot, unsigned int quot_frac)
91+
{
92+
struct uart_8250_port *up = up_to_u8250p(port);
93+
94+
serial_port_out(port, UART_LCR, up->lcr | UART_LCR_DLAB);
95+
96+
serial_dl_write(up, quot);
97+
98+
serial_port_out(port, 0x2, quot_frac);
99+
}
100+
101+
static int loongson_uart_probe(struct platform_device *pdev)
102+
{
103+
struct uart_8250_port uart = {};
104+
struct loongson_uart_data *data;
105+
struct uart_port *port;
106+
struct resource *res;
107+
int ret;
108+
109+
port = &uart.port;
110+
spin_lock_init(&port->lock);
111+
112+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
113+
if (!res)
114+
return -ENODEV;
115+
116+
port->flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_IOREMAP;
117+
port->iotype = UPIO_MEM;
118+
port->regshift = 0;
119+
port->dev = &pdev->dev;
120+
port->type = PORT_LOONGSON;
121+
port->mapbase = res->start;
122+
port->mapsize = resource_size(res);
123+
port->serial_in = loongson_serial_in;
124+
port->serial_out = loongson_serial_out;
125+
126+
port->irq = platform_get_irq(pdev, 0);
127+
if (port->irq < 0)
128+
return -EINVAL;
129+
130+
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
131+
if (!data)
132+
return -ENOMEM;
133+
134+
data->config = device_get_match_data(&pdev->dev);
135+
136+
port->private_data = data;
137+
138+
if (data->config->flags & LOONGSON_UART_FRAC) {
139+
port->get_divisor = loongson_frac_get_divisor;
140+
port->set_divisor = loongson_frac_set_divisor;
141+
}
142+
143+
if (data->config->quirks & LOONGSON_UART_QUIRK_MCR)
144+
data->mcr_invert |= (UART_MCR_RTS | UART_MCR_DTR);
145+
146+
if (data->config->quirks & LOONGSON_UART_QUIRK_MSR)
147+
data->msr_invert |= (UART_MSR_CTS | UART_MSR_DSR);
148+
149+
data->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
150+
if (IS_ERR(data->rst))
151+
return PTR_ERR(data->rst);
152+
153+
device_property_read_u32(&pdev->dev, "clock-frequency", &port->uartclk);
154+
155+
ret = reset_control_deassert(data->rst);
156+
if (ret)
157+
goto err_unprepare;
158+
159+
ret = serial8250_register_8250_port(&uart);
160+
if (ret < 0)
161+
goto err_unprepare;
162+
163+
platform_set_drvdata(pdev, data);
164+
data->line = ret;
165+
166+
return 0;
167+
168+
err_unprepare:
169+
reset_control_assert(data->rst);
170+
171+
return ret;
172+
}
173+
174+
static void loongson_uart_remove(struct platform_device *pdev)
175+
{
176+
struct loongson_uart_data *data = platform_get_drvdata(pdev);
177+
178+
serial8250_unregister_port(data->line);
179+
reset_control_assert(data->rst);
180+
}
181+
182+
#ifdef CONFIG_PM_SLEEP
183+
static int loongson_uart_suspend(struct device *dev)
184+
{
185+
struct loongson_uart_data *data = dev_get_drvdata(dev);
186+
187+
serial8250_suspend_port(data->line);
188+
189+
return 0;
190+
}
191+
192+
static int loongson_uart_resume(struct device *dev)
193+
{
194+
struct loongson_uart_data *data = dev_get_drvdata(dev);
195+
196+
serial8250_resume_port(data->line);
197+
198+
return 0;
199+
}
200+
#endif
201+
202+
static const struct dev_pm_ops loongson_uart_pm_ops = {
203+
SET_SYSTEM_SLEEP_PM_OPS(loongson_uart_suspend, loongson_uart_resume)
204+
};
205+
206+
static const struct of_device_id of_platform_serial_table[] = {
207+
{.compatible = "loongson,ls7a-uart", .data = &ls7a_uart_config},
208+
{.compatible = "loongson,ls3a5000-uart", .data = &ls3a5000_uart_config},
209+
{.compatible = "loongson,ls2k2000-uart", .data = &ls2k2000_uart_config},
210+
{},
211+
};
212+
MODULE_DEVICE_TABLE(of, of_platform_serial_table);
213+
214+
static struct platform_driver loongson_uart_driver = {
215+
.probe = loongson_uart_probe,
216+
.remove = loongson_uart_remove,
217+
.driver = {
218+
.name = "loongson-uart",
219+
.pm = &loongson_uart_pm_ops,
220+
.of_match_table = of_platform_serial_table,
221+
},
222+
};
223+
224+
module_platform_driver(loongson_uart_driver);
225+
226+
MODULE_DESCRIPTION("LOONGSON 8250 Driver");
227+
MODULE_AUTHOR("Haowei Zheng <[email protected]>");
228+
MODULE_LICENSE("GPL");

drivers/tty/serial/8250/8250_port.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,14 @@ static const struct serial8250_config uart_config[] = {
319319
.rxtrig_bytes = {1, 8, 16, 30},
320320
.flags = UART_CAP_FIFO | UART_CAP_AFE,
321321
},
322+
[PORT_LOONGSON] = {
323+
.name = "Loongson",
324+
.fifo_size = 16,
325+
.tx_loadsz = 16,
326+
.fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10,
327+
.rxtrig_bytes = {1, 4, 8, 14},
328+
.flags = UART_CAP_FIFO,
329+
},
322330
};
323331

324332
/* Uart divisor latch read */

drivers/tty/serial/8250/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,15 @@ config SERIAL_8250_BCM7271
569569
including DMA support and high accuracy BAUD rates, say
570570
Y to this option. If unsure, say N.
571571

572+
config SERIAL_8250_LOONGSON
573+
tristate "Loongson 8250 serial port support"
574+
default SERIAL_8250
575+
depends on SERIAL_8250
576+
depends on LOONGARCH
577+
help
578+
If you have machine with Loongson and want to use this serial driver,
579+
say Y to this option. If unsure, say N.
580+
572581
config SERIAL_8250_NI
573582
tristate "NI 16550 based serial port"
574583
depends on SERIAL_8250

drivers/tty/serial/8250/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,6 @@ obj-$(CONFIG_SERIAL_8250_RT288X) += 8250_rt288x.o
5252
obj-$(CONFIG_SERIAL_8250_CS) += serial_cs.o
5353
obj-$(CONFIG_SERIAL_8250_UNIPHIER) += 8250_uniphier.o
5454
obj-$(CONFIG_SERIAL_8250_TEGRA) += 8250_tegra.o
55+
obj-$(CONFIG_SERIAL_8250_LOONGSON) += 8250_loongson.o
5556

5657
CFLAGS_8250_ingenic.o += -I$(srctree)/scripts/dtc/libfdt

include/uapi/linux/serial_core.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#define PORT_ALTR_16550_F128 28 /* Altera 16550 UART with 128 FIFOs */
3232
#define PORT_RT2880 29 /* Ralink RT2880 internal UART */
3333
#define PORT_16550A_FSL64 30 /* Freescale 16550 UART with 64 FIFOs */
34+
#define PORT_LOONGSON 31 /* Loongson 16550 UART*/
3435

3536
/*
3637
* ARM specific type numbers. These are not currently guaranteed

0 commit comments

Comments
 (0)