1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (c) 2021 Nuvoton Technology Corp.
4 */
5
6#include <common.h>
7#include <clk.h>
8#include <dm.h>
9#include <serial.h>
10
11struct npcm_uart {
12	union {
13		u32	rbr;	/* Receive Buffer Register */
14		u32	thr;	/* Transmit Holding Register */
15		u32	dll;	/* Divisor Latch (Low Byte) Register */
16	};
17	union {
18		u32	ier;	/* Interrupt Enable Register */
19		u32	dlm;	/* Divisor Latch (Low Byte) Register */
20	};
21	union {
22		u32	iir;	/* Interrupt Identification Register */
23		u32	fcr;	/* FIFO Control Register */
24	};
25	u32	lcr;		/* Line Control Register */
26	u32	mcr;		/* Modem Control Register */
27	u32	lsr;		/* Line Status Control Register */
28	u32	msr;		/* Modem Status Register */
29	u32	tor;		/* Timeout Register */
30};
31
32#define	LCR_WLS_8BITS	3	/* 8-bit word length select */
33#define	FCR_TFR		BIT(2)	/* TxFIFO reset */
34#define	FCR_RFR		BIT(1)	/* RxFIFO reset */
35#define	FCR_FME		BIT(0)	/* FIFO mode enable */
36#define	LSR_THRE	BIT(5)	/* Status of TxFIFO empty */
37#define	LSR_RFDR	BIT(0)	/* Status of RxFIFO data ready */
38#define	LCR_DLAB	BIT(7)	/* Divisor latch access bit */
39
40struct npcm_serial_plat {
41	struct npcm_uart *reg;
42	u32 uart_clk;		/* frequency of uart clock source */
43};
44
45static int npcm_serial_pending(struct udevice *dev, bool input)
46{
47	struct npcm_serial_plat *plat = dev_get_plat(dev);
48	struct npcm_uart *uart = plat->reg;
49
50	if (input)
51		return readb(&uart->lsr) & LSR_RFDR ? 1 : 0;
52	else
53		return readb(&uart->lsr) & LSR_THRE ? 0 : 1;
54}
55
56static int npcm_serial_putc(struct udevice *dev, const char ch)
57{
58	struct npcm_serial_plat *plat = dev_get_plat(dev);
59	struct npcm_uart *uart = plat->reg;
60
61	if (!(readb(&uart->lsr) & LSR_THRE))
62		return -EAGAIN;
63
64	writeb(ch, &uart->thr);
65
66	return 0;
67}
68
69static int npcm_serial_getc(struct udevice *dev)
70{
71	struct npcm_serial_plat *plat = dev_get_plat(dev);
72	struct npcm_uart *uart = plat->reg;
73
74	if (!(readb(&uart->lsr) & LSR_RFDR))
75		return -EAGAIN;
76
77	return readb(&uart->rbr);
78}
79
80static int npcm_serial_setbrg(struct udevice *dev, int baudrate)
81{
82	struct npcm_serial_plat *plat = dev_get_plat(dev);
83	struct npcm_uart *uart = plat->reg;
84	u16 divisor;
85
86	if (IS_ENABLED(CONFIG_SYS_SKIP_UART_INIT))
87		return 0;
88
89	/* BaudOut = UART Clock / (16 * [Divisor + 2]) */
90	divisor = DIV_ROUND_CLOSEST(plat->uart_clk, 16 * baudrate) - 2;
91
92	setbits_8(&uart->lcr, LCR_DLAB);
93	writeb(divisor & 0xff, &uart->dll);
94	writeb(divisor >> 8, &uart->dlm);
95	clrbits_8(&uart->lcr, LCR_DLAB);
96
97	return 0;
98}
99
100static int npcm_serial_probe(struct udevice *dev)
101{
102	struct npcm_serial_plat *plat = dev_get_plat(dev);
103	struct npcm_uart *uart;
104	struct clk clk, parent;
105	u32 freq;
106	int ret;
107
108	plat->reg = dev_read_addr_ptr(dev);
109	uart = plat->reg;
110
111	if (!IS_ENABLED(CONFIG_SYS_SKIP_UART_INIT)) {
112		freq = dev_read_u32_default(dev, "clock-frequency", 24000000);
113
114		ret = clk_get_by_index(dev, 0, &clk);
115		if (ret < 0)
116			return ret;
117
118		ret = clk_get_by_index(dev, 1, &parent);
119		if (!ret) {
120			ret = clk_set_parent(&clk, &parent);
121			if (ret)
122				return ret;
123		}
124
125		if (freq) {
126			ret = clk_set_rate(&clk, freq);
127			if (ret < 0)
128				return ret;
129		}
130		plat->uart_clk = clk_get_rate(&clk);
131	}
132
133	/* Disable all interrupt */
134	writeb(0, &uart->ier);
135
136	/* Set 8 bit, 1 stop, no parity */
137	writeb(LCR_WLS_8BITS, &uart->lcr);
138
139	/* Reset RX/TX FIFO */
140	writeb(FCR_FME | FCR_RFR | FCR_TFR, &uart->fcr);
141
142	return 0;
143}
144
145static const struct dm_serial_ops npcm_serial_ops = {
146	.getc = npcm_serial_getc,
147	.setbrg = npcm_serial_setbrg,
148	.putc = npcm_serial_putc,
149	.pending = npcm_serial_pending,
150};
151
152static const struct udevice_id npcm_serial_ids[] = {
153	{ .compatible = "nuvoton,npcm750-uart" },
154	{ .compatible = "nuvoton,npcm845-uart" },
155	{ }
156};
157
158U_BOOT_DRIVER(serial_npcm) = {
159	.name	= "serial_npcm",
160	.id	= UCLASS_SERIAL,
161	.of_match = npcm_serial_ids,
162	.plat_auto  = sizeof(struct npcm_serial_plat),
163	.probe = npcm_serial_probe,
164	.ops	= &npcm_serial_ops,
165	.flags = DM_FLAG_PRE_RELOC,
166};
167