1 /*
2 * xen/drivers/char/meson-uart.c
3 *
4 * Driver for Amlogic MESON UART
5 *
6 * Copyright (c) 2019, Amit Singh Tomar <amittomer25@gmail.com>.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms and conditions of the GNU General Public
10 * License, version 2, as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public
18 * License along with this program; If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <xen/irq.h>
22 #include <xen/serial.h>
23 #include <xen/vmap.h>
24 #include <asm/io.h>
25
26 /* Register offsets */
27 #define AML_UART_WFIFO_REG 0x00
28 #define AML_UART_RFIFO_REG 0x04
29 #define AML_UART_CONTROL_REG 0x08
30 #define AML_UART_STATUS_REG 0x0c
31 #define AML_UART_MISC_REG 0x10
32
33 /* UART_CONTROL bits */
34 #define AML_UART_TX_RST BIT(22, UL)
35 #define AML_UART_RX_RST BIT(23, UL)
36 #define AML_UART_CLEAR_ERR BIT(24, UL)
37 #define AML_UART_RX_INT_EN BIT(27, UL)
38 #define AML_UART_TX_INT_EN BIT(28, UL)
39
40 /* UART_STATUS bits */
41 #define AML_UART_RX_FIFO_EMPTY BIT(20, UL)
42 #define AML_UART_TX_FIFO_FULL BIT(21, UL)
43 #define AML_UART_TX_FIFO_EMPTY BIT(22, UL)
44 #define AML_UART_TX_CNT_MASK GENMASK(14, 8)
45
46 /* AML_UART_MISC bits */
47 #define AML_UART_XMIT_IRQ(c) (((c) & 0xff) << 8)
48 #define AML_UART_RECV_IRQ(c) ((c) & 0xff)
49
50 #define TX_FIFO_SIZE 64
51
52 #define setbits(addr, set) writel((readl(addr) | (set)), (addr))
53 #define clrbits(addr, clear) writel((readl(addr) & ~(clear)), (addr))
54
55 static struct meson_uart {
56 unsigned int irq;
57 void __iomem *regs;
58 struct irqaction irqaction;
59 struct vuart_info vuart;
60 } meson_com;
61
meson_uart_interrupt(int irq,void * data,struct cpu_user_regs * regs)62 static void meson_uart_interrupt(int irq, void *data,
63 struct cpu_user_regs *regs)
64 {
65 struct serial_port *port = data;
66 struct meson_uart *uart = port->uart;
67 uint32_t st = readl(uart->regs + AML_UART_STATUS_REG);
68
69 if ( !(st & AML_UART_RX_FIFO_EMPTY) )
70 serial_rx_interrupt(port, regs);
71
72 if ( !(st & AML_UART_TX_FIFO_FULL) )
73 serial_tx_interrupt(port, regs);
74 }
75
meson_uart_init_preirq(struct serial_port * port)76 static void __init meson_uart_init_preirq(struct serial_port *port)
77 {
78 struct meson_uart *uart = port->uart;
79
80 /* Reset UART */
81 setbits(uart->regs + AML_UART_CONTROL_REG,
82 (AML_UART_RX_RST | AML_UART_TX_RST | AML_UART_CLEAR_ERR));
83
84 clrbits(uart->regs + AML_UART_CONTROL_REG,
85 (AML_UART_RX_RST | AML_UART_TX_RST | AML_UART_CLEAR_ERR));
86
87 /* Disable Rx/Tx interrupts */
88 clrbits(uart->regs + AML_UART_CONTROL_REG,
89 (AML_UART_RX_INT_EN | AML_UART_TX_INT_EN));
90 }
91
meson_uart_init_postirq(struct serial_port * port)92 static void __init meson_uart_init_postirq(struct serial_port *port)
93 {
94 struct meson_uart *uart = port->uart;
95
96 uart->irqaction.handler = meson_uart_interrupt;
97 uart->irqaction.name = "meson_uart";
98 uart->irqaction.dev_id = port;
99
100 if ( setup_irq(uart->irq, 0, &uart->irqaction) != 0 )
101 {
102 printk("Failed to allocated Meson UART IRQ %d\n", uart->irq);
103 return;
104 }
105
106 /*
107 * Configure Rx/Tx interrupts based on bytes in FIFO, these bits have
108 * taken from Linux driver
109 */
110 writel((AML_UART_RECV_IRQ(1) | AML_UART_XMIT_IRQ(TX_FIFO_SIZE / 2)),
111 uart->regs + AML_UART_MISC_REG);
112
113 /* Make sure Rx/Tx interrupts are enabled now */
114 setbits(uart->regs + AML_UART_CONTROL_REG,
115 (AML_UART_RX_INT_EN | AML_UART_TX_INT_EN));
116 }
117
meson_uart_suspend(struct serial_port * port)118 static void meson_uart_suspend(struct serial_port *port)
119 {
120 BUG();
121 }
122
meson_uart_resume(struct serial_port * port)123 static void meson_uart_resume(struct serial_port *port)
124 {
125 BUG();
126 }
127
meson_uart_putc(struct serial_port * port,char c)128 static void meson_uart_putc(struct serial_port *port, char c)
129 {
130 struct meson_uart *uart = port->uart;
131
132 writel(c, uart->regs + AML_UART_WFIFO_REG);
133 }
134
meson_uart_getc(struct serial_port * port,char * c)135 static int meson_uart_getc(struct serial_port *port, char *c)
136 {
137 struct meson_uart *uart = port->uart;
138
139 if ( (readl(uart->regs + AML_UART_STATUS_REG) & AML_UART_RX_FIFO_EMPTY) )
140 return 0;
141
142 *c = readl(uart->regs + AML_UART_RFIFO_REG) & 0xff;
143
144 return 1;
145 }
146
meson_irq(struct serial_port * port)147 static int __init meson_irq(struct serial_port *port)
148 {
149 struct meson_uart *uart = port->uart;
150
151 return uart->irq;
152 }
153
meson_vuart_info(struct serial_port * port)154 static const struct vuart_info *meson_vuart_info(struct serial_port *port)
155 {
156 struct meson_uart *uart = port->uart;
157
158 return &uart->vuart;
159 }
160
meson_uart_stop_tx(struct serial_port * port)161 static void meson_uart_stop_tx(struct serial_port *port)
162 {
163 struct meson_uart *uart = port->uart;
164
165 clrbits(uart->regs + AML_UART_CONTROL_REG, AML_UART_TX_INT_EN);
166 }
167
meson_uart_start_tx(struct serial_port * port)168 static void meson_uart_start_tx(struct serial_port *port)
169 {
170 struct meson_uart *uart = port->uart;
171
172 setbits(uart->regs + AML_UART_CONTROL_REG, AML_UART_TX_INT_EN);
173 }
174
meson_uart_tx_ready(struct serial_port * port)175 static int meson_uart_tx_ready(struct serial_port *port)
176 {
177 struct meson_uart *uart = port->uart;
178 uint32_t reg;
179
180 reg = readl(uart->regs + AML_UART_STATUS_REG);
181
182 if ( reg & AML_UART_TX_FIFO_EMPTY )
183 return TX_FIFO_SIZE;
184 if ( reg & AML_UART_TX_FIFO_FULL )
185 return 0;
186
187 return (reg & AML_UART_TX_CNT_MASK) >> 8;
188 }
189
190 static struct uart_driver __read_mostly meson_uart_driver = {
191 .init_preirq = meson_uart_init_preirq,
192 .init_postirq = meson_uart_init_postirq,
193 .endboot = NULL,
194 .suspend = meson_uart_suspend,
195 .resume = meson_uart_resume,
196 .putc = meson_uart_putc,
197 .getc = meson_uart_getc,
198 .tx_ready = meson_uart_tx_ready,
199 .stop_tx = meson_uart_stop_tx,
200 .start_tx = meson_uart_start_tx,
201 .irq = meson_irq,
202 .vuart_info = meson_vuart_info,
203 };
204
meson_uart_init(struct dt_device_node * dev,const void * data)205 static int __init meson_uart_init(struct dt_device_node *dev, const void *data)
206 {
207 const char *config = data;
208 struct meson_uart *uart;
209 int res;
210 u64 addr, size;
211
212 if ( strcmp(config, "") )
213 printk("WARNING: UART configuration is not supported\n");
214
215 uart = &meson_com;
216
217 res = dt_device_get_address(dev, 0, &addr, &size);
218 if ( res )
219 {
220 printk("meson: Unable to retrieve the base address of the UART\n");
221 return res;
222 }
223
224 res = platform_get_irq(dev, 0);
225 if ( res < 0 )
226 {
227 printk("meson: Unable to retrieve the IRQ\n");
228 return -EINVAL;
229 }
230
231 uart->irq = res;
232
233 uart->regs = ioremap_nocache(addr, size);
234 if ( !uart->regs )
235 {
236 printk("meson: Unable to map the UART\n");
237 return -ENOMEM;
238 }
239
240 uart->vuart.base_addr = addr;
241 uart->vuart.size = size;
242 uart->vuart.data_off = AML_UART_WFIFO_REG;
243 uart->vuart.status_off = AML_UART_STATUS_REG;
244 uart->vuart.status = AML_UART_RX_FIFO_EMPTY | AML_UART_TX_FIFO_EMPTY;
245
246 /* Register with generic serial driver. */
247 serial_register_uart(SERHND_DTUART, &meson_uart_driver, uart);
248
249 dt_device_set_used_by(dev, DOMID_XEN);
250
251 return 0;
252 }
253
254 static const struct dt_device_match meson_dt_match[] __initconst =
255 {
256 DT_MATCH_COMPATIBLE("amlogic,meson-uart"),
257 DT_MATCH_COMPATIBLE("amlogic,meson6-uart"),
258 DT_MATCH_COMPATIBLE("amlogic,meson8-uart"),
259 DT_MATCH_COMPATIBLE("amlogic,meson8b-uart"),
260 DT_MATCH_COMPATIBLE("amlogic,meson-gx-uart"),
261 { /* sentinel */ },
262 };
263
264 DT_DEVICE_START(meson, "Amlogic UART", DEVICE_SERIAL)
265 .dt_match = meson_dt_match,
266 .init = meson_uart_init,
267 DT_DEVICE_END
268
269 /*
270 * Local variables:
271 * mode: C
272 * c-file-style: "BSD"
273 * c-basic-offset: 4
274 * indent-tabs-mode: nil
275 * End:
276 */
277