source: mainline/uspace/drv/char/pl011/pl011.c

Last change on this file was 7348c4b, checked in by Jakub Jermář <jakub@…>, 6 years ago

arm64: Add PL011 uspace driver

  • Property mode set to 100644
File size: 10.2 KB
Line 
1/*
2 * Copyright (c) 2016 Petr Pavlu
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @file ARM PrimeCell PL011 UART driver.
30 */
31
32#include <async.h>
33#include <ddf/driver.h>
34#include <ddf/log.h>
35#include <ddi.h>
36#include <errno.h>
37#include <io/chardev_srv.h>
38#include <macros.h>
39
40#include "pl011.h"
41
42/** PL011 register map. */
43typedef struct {
44 /** UART data register. */
45 ioport32_t data;
46 union {
47 /** Receive status register (same values that are in upper bits
48 * of data register).
49 */
50 const ioport32_t status;
51 /** Error clear register (writing anything clears all errors).
52 */
53 ioport32_t error_clear;
54 };
55 /** Reserved. */
56 PADD32(4);
57 /** Flag register. */
58 const ioport32_t flag;
59 /** Transmit FIFO full. */
60#define PL011_UART_FLAG_TXFF_FLAG (1 << 5)
61
62 /** Reserved. */
63 PADD32(1);
64 /** IrDA low-power counter register. */
65 ioport32_t irda_low_power;
66 /** Integer baud rate register. */
67 ioport32_t int_baud_divisor;
68 /** Fractional baud rate register. */
69 ioport32_t fract_baud_divisor;
70 /** Line control register. */
71 ioport32_t line_control_high;
72 /** Control register. */
73 ioport32_t control;
74 /** Interrupt FIFO level select register. */
75 ioport32_t interrupt_fifo;
76 /** Interrupt mask set/clear register. */
77 ioport32_t interrupt_mask;
78 /** Raw interrupt status register (pending interrupts before applying
79 * the mask).
80 */
81 const ioport32_t raw_interrupt_status;
82 /** Masked interrupt status register (pending interrupts after applying
83 * the mask).
84 */
85 const ioport32_t masked_interrupt_status;
86 /** Interrupt clear register (write 1s to clear pending interrupts). */
87 ioport32_t interrupt_clear;
88
89 /** Interrupt indicating a change in the nUARTRI modem status. */
90#define PL011_UART_INTERRUPT_RIM_FLAG (1 << 0)
91 /** Interrupt indicating a change in the nUARTCTS modem status. */
92#define PL011_UART_INTERRUPT_CTSM_FLAG (1 << 1)
93 /** Interrupt indicating a change in the nUARTDCD modem status. */
94#define PL011_UART_INTERRUPT_DCDM_FLAG (1 << 2)
95 /** Interrupt indicating a change in the nUARTDSR modem status. */
96#define PL011_UART_INTERRUPT_DSRM_FLAG (1 << 3)
97 /** The receive interrupt. */
98#define PL011_UART_INTERRUPT_RX_FLAG (1 << 4)
99 /** The transmit interrupt. */
100#define PL011_UART_INTERRUPT_TX_FLAG (1 << 5)
101 /** The receive timeout interrupt. */
102#define PL011_UART_INTERRUPT_RT_FLAG (1 << 6)
103 /** Interrupt indicating an overrun error. */
104#define PL011_UART_INTERRUPT_FE_FLAG (1 << 7)
105 /** Interrupt indicating a break in the reception. */
106#define PL011_UART_INTERRUPT_PE_FLAG (1 << 8)
107 /** Interrupt indicating a parity error in the received character. */
108#define PL011_UART_INTERRUPT_BE_FLAG (1 << 9)
109 /** Interrupt indicating a framing error in the received character. */
110#define PL011_UART_INTERRUPT_OE_FLAG (1 << 10)
111 /** All interrupt mask. */
112#define PL011_UART_INTERRUPT_ALL 0x3ff
113
114 /** DMA control register. */
115 ioport32_t dma_control;
116 /** Reserved. */
117 PADD32(13);
118 /** Reserved for test purposes. */
119 PADD32(4);
120 /** Reserved. */
121 PADD32(976);
122 /** Reserved for future ID expansion. */
123 PADD32(4);
124 /** UARTPeriphID0 register. */
125 const ioport32_t periph_id0;
126 /** UARTPeriphID1 register. */
127 const ioport32_t periph_id1;
128 /** UARTPeriphID2 register. */
129 const ioport32_t periph_id2;
130 /** UARTPeriphID3 register. */
131 const ioport32_t periph_id3;
132 /** UARTPCellID0 register. */
133 const ioport32_t cell_id0;
134 /** UARTPCellID1 register. */
135 const ioport32_t cell_id1;
136 /** UARTPCellID2 register. */
137 const ioport32_t cell_id2;
138 /** UARTPCellID3 register. */
139 const ioport32_t cell_id3;
140} pl011_uart_regs_t;
141
142static void pl011_connection(ipc_call_t *, void *);
143
144static errno_t pl011_read(chardev_srv_t *, void *, size_t, size_t *,
145 chardev_flags_t);
146static errno_t pl011_write(chardev_srv_t *, const void *, size_t, size_t *);
147
148static chardev_ops_t pl011_chardev_ops = {
149 .read = pl011_read,
150 .write = pl011_write
151};
152
153/** Address range accessed by the PL011 interrupt pseudo-code. */
154static const irq_pio_range_t pl011_ranges_proto[] = {
155 {
156 .base = 0,
157 .size = sizeof(pl011_uart_regs_t)
158 }
159};
160
161/** PL011 interrupt pseudo-code instructions. */
162static const irq_cmd_t pl011_cmds_proto[] = {
163 {
164 /* Read masked_interrupt_status. */
165 .cmd = CMD_PIO_READ_32,
166 .addr = NULL,
167 .dstarg = 1
168 },
169 {
170 .cmd = CMD_AND,
171 .value = PL011_UART_INTERRUPT_RX_FLAG |
172 PL011_UART_INTERRUPT_RT_FLAG,
173 .srcarg = 1,
174 .dstarg = 3
175 },
176 {
177 .cmd = CMD_PREDICATE,
178 .value = 1,
179 .srcarg = 3
180 },
181 {
182 /* Read data. */
183 .cmd = CMD_PIO_READ_32,
184 .addr = NULL,
185 .dstarg = 2
186 },
187 {
188 .cmd = CMD_ACCEPT
189 }
190};
191
192/** Process an interrupt from a PL011 device. */
193static void pl011_irq_handler(ipc_call_t *call, void *arg)
194{
195 pl011_t *pl011 = (pl011_t *) arg;
196 uint32_t intrs = ipc_get_arg1(call);
197 uint8_t c = ipc_get_arg2(call);
198 errno_t rc;
199
200 if ((intrs & (PL011_UART_INTERRUPT_RX_FLAG |
201 PL011_UART_INTERRUPT_RT_FLAG)) == 0) {
202 /* TODO */
203 return;
204 }
205
206 fibril_mutex_lock(&pl011->buf_lock);
207
208 rc = circ_buf_push(&pl011->cbuf, &c);
209 if (rc != EOK)
210 ddf_msg(LVL_ERROR, "Buffer overrun");
211
212 fibril_mutex_unlock(&pl011->buf_lock);
213 fibril_condvar_broadcast(&pl011->buf_cv);
214}
215
216/** Add a PL011 device. */
217errno_t pl011_add(pl011_t *pl011, pl011_res_t *res)
218{
219 ddf_fun_t *fun = NULL;
220 irq_pio_range_t *pl011_ranges = NULL;
221 irq_cmd_t *pl011_cmds = NULL;
222 errno_t rc;
223
224 circ_buf_init(&pl011->cbuf, pl011->buf, pl011_buf_size, 1);
225 fibril_mutex_initialize(&pl011->buf_lock);
226 fibril_condvar_initialize(&pl011->buf_cv);
227
228 pl011->irq_handle = CAP_NIL;
229
230 pl011_ranges = malloc(sizeof(pl011_ranges_proto));
231 if (pl011_ranges == NULL) {
232 rc = ENOMEM;
233 goto error;
234 }
235 pl011_cmds = malloc(sizeof(pl011_cmds_proto));
236 if (pl011_cmds == NULL) {
237 rc = ENOMEM;
238 goto error;
239 }
240
241 pl011->res = *res;
242
243 fun = ddf_fun_create(pl011->dev, fun_exposed, "a");
244 if (fun == NULL) {
245 ddf_msg(LVL_ERROR, "Error creating function 'a'.");
246 rc = ENOMEM;
247 goto error;
248 }
249
250 rc = pio_enable(
251 (void *) res->base, sizeof(pl011_uart_regs_t), &pl011->regs);
252 if (rc != EOK)
253 goto error;
254
255 ddf_fun_set_conn_handler(fun, pl011_connection);
256
257 memcpy(pl011_ranges, pl011_ranges_proto, sizeof(pl011_ranges_proto));
258 memcpy(pl011_cmds, pl011_cmds_proto, sizeof(pl011_cmds_proto));
259 pl011_ranges[0].base = res->base;
260 pl011_uart_regs_t *regsphys = (pl011_uart_regs_t *) res->base;
261 pl011_cmds[0].addr = (void *) &regsphys->masked_interrupt_status;
262 pl011_cmds[3].addr = (void *) &regsphys->data;
263
264 pl011->irq_code.rangecount =
265 sizeof(pl011_ranges_proto) / sizeof(irq_pio_range_t);
266 pl011->irq_code.ranges = pl011_ranges;
267 pl011->irq_code.cmdcount = sizeof(pl011_cmds_proto) / sizeof(irq_cmd_t);
268 pl011->irq_code.cmds = pl011_cmds;
269
270 rc = async_irq_subscribe(res->irq, pl011_irq_handler, pl011,
271 &pl011->irq_code, &pl011->irq_handle);
272 if (rc != EOK) {
273 ddf_msg(LVL_ERROR, "Error registering IRQ code.");
274 goto error;
275 }
276
277 chardev_srvs_init(&pl011->cds);
278 pl011->cds.ops = &pl011_chardev_ops;
279 pl011->cds.sarg = pl011;
280
281 rc = ddf_fun_bind(fun);
282 if (rc != EOK) {
283 ddf_msg(LVL_ERROR, "Error binding function 'a'.");
284 goto error;
285 }
286
287 ddf_fun_add_to_category(fun, "console");
288
289 return EOK;
290error:
291 if (cap_handle_valid(pl011->irq_handle))
292 async_irq_unsubscribe(pl011->irq_handle);
293 if (fun != NULL)
294 ddf_fun_destroy(fun);
295 free(pl011_ranges);
296 free(pl011_cmds);
297
298 return rc;
299}
300
301/** Remove a PL011 device. */
302errno_t pl011_remove(pl011_t *pl011)
303{
304 return ENOTSUP;
305}
306
307/** A PL011 device gone. */
308errno_t pl011_gone(pl011_t *pl011)
309{
310 return ENOTSUP;
311}
312
313/** Send a character to a PL011 device.
314 *
315 * @param c Character to be printed.
316 */
317static void pl011_putchar(pl011_t *pl011, uint8_t ch)
318{
319 pl011_uart_regs_t *regs = (pl011_uart_regs_t *) pl011->regs;
320
321 /* Wait for space to become available in the TX FIFO. */
322 while (pio_read_32(&regs->flag) & PL011_UART_FLAG_TXFF_FLAG)
323 ;
324
325 pio_write_32(&regs->data, ch);
326}
327
328/** Read from a PL011 device. */
329static errno_t pl011_read(chardev_srv_t *srv, void *buf, size_t size,
330 size_t *nread, chardev_flags_t flags)
331{
332 pl011_t *pl011 = (pl011_t *) srv->srvs->sarg;
333 size_t p;
334 uint8_t *bp = (uint8_t *) buf;
335 errno_t rc;
336
337 fibril_mutex_lock(&pl011->buf_lock);
338
339 while ((flags & chardev_f_nonblock) == 0 &&
340 circ_buf_nused(&pl011->cbuf) == 0)
341 fibril_condvar_wait(&pl011->buf_cv, &pl011->buf_lock);
342
343 p = 0;
344 while (p < size) {
345 rc = circ_buf_pop(&pl011->cbuf, &bp[p]);
346 if (rc != EOK)
347 break;
348 ++p;
349 }
350
351 fibril_mutex_unlock(&pl011->buf_lock);
352
353 *nread = p;
354 return EOK;
355}
356
357/** Write to a PL011 device. */
358static errno_t pl011_write(chardev_srv_t *srv, const void *data, size_t size,
359 size_t *nwr)
360{
361 pl011_t *pl011 = (pl011_t *) srv->srvs->sarg;
362 size_t i;
363 uint8_t *dp = (uint8_t *) data;
364
365 for (i = 0; i < size; i++)
366 pl011_putchar(pl011, dp[i]);
367
368 *nwr = size;
369 return EOK;
370}
371
372/** Character device connection handler. */
373static void pl011_connection(ipc_call_t *icall, void *arg)
374{
375 pl011_t *pl011 = (pl011_t *) ddf_dev_data_get(
376 ddf_fun_get_dev((ddf_fun_t *) arg));
377
378 chardev_conn(icall, &pl011->cds);
379}
380
381/** @}
382 */
Note: See TracBrowser for help on using the repository browser.