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