1/**
2 * \file
3 * \brief PC16550 low-level kernel UART driver
4 *
5 * AB's quick-and-nasty serial driver.
6 */
7
8/*
9 * Copyright (c) 2007, 2008, 2010, 2012, ETH Zurich.
10 * All rights reserved.
11 *
12 * This file is distributed under the terms in the attached LICENSE file.
13 * If you do not find this file, copies can be found by writing to:
14 * ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
15 */
16
17#include <kernel.h>
18#include <x86.h>
19#include <serial.h>
20#include <dev/pc16550d_dev.h>
21
22int serial_portbase = 0x3f8; // COM1 default, can be changed via command-line arg
23
24#define NIBBLE 4
25#define HEX2ASCII 0x30
26#define HEXLETTER 0x3a
27#define HEXCORRECTION 0x7
28
29#define NUM_PORTS 2
30unsigned serial_console_port = 0;
31unsigned serial_debug_port = 0;
32unsigned serial_num_physical_ports = NUM_PORTS;
33
34// Note: hardwired for PC hardware
35static const uint32_t portbases[NUM_PORTS] = { 0x3f8, 0x2f8 };
36static pc16550d_t ports[NUM_PORTS];
37
38/**
39 * \brief Initialise the serial driver.
40 *
41 * \param  port          Serial Port number.
42 * \param  initialize_hw Should the Hardware be initialized or just
43 * mackerel representation.
44 */
45errval_t serial_init(unsigned port, bool initialize_hw)
46{
47    if (port >= NUM_PORTS) {
48    	return SYS_ERR_SERIAL_PORT_INVALID;
49    };
50
51    // XXX Backwards compatibility!
52    if (serial_portbase != 0x3f8 && port == 0) {
53    	// We're trying to initialize the console port on a machine
54    	// which uses the second UART, but the CPU driver doesn't
55    	// understand the new serial interface yet.  Kluge this into
56    	// using the second UART, and set the console and debug ports
57    	// accordingly.
58    	serial_console_port = 1;
59    	serial_debug_port = 1;
60    	port = 1;
61    }
62
63    pc16550d_t *uart = &ports[port];
64    pc16550d_initialize(uart, portbases[port]);
65
66    if (!initialize_hw) {
67        // HW is already initialized, usually APP cores come here
68        // while the BSP core do the initialization once.
69        // If the initialization happens twice,
70        // it can manifest in the keyboard not working
71        // because we disable uart interrupts and
72        // the serial driver was started in the meantime
73        return SYS_ERR_OK;
74    }
75
76    // Initialize UART
77    // disable interrupt
78    pc16550d_ier_t ier = pc16550d_ier_default;
79    ier = pc16550d_ier_erbfi_insert(ier, 0);
80    pc16550d_ier_wr(uart, ier);
81
82    // enable FIFOs
83    pc16550d_fcr_t fcr = pc16550d_fcr_default;
84    fcr = pc16550d_fcr_fifoe_insert(fcr, 1);
85    // FIFOs hold 14 bytes
86    fcr = pc16550d_fcr_rtrigger_insert(fcr, pc16550d_bytes14);
87    pc16550d_fcr_wr(uart, fcr);
88
89    pc16550d_lcr_t lcr = pc16550d_lcr_default;
90    lcr = pc16550d_lcr_wls_insert(lcr, pc16550d_bits8); // 8 data bits
91    lcr = pc16550d_lcr_stb_insert(lcr, 1); // 1 stop bit
92    lcr = pc16550d_lcr_pen_insert(lcr, 0); // no parity
93    pc16550d_lcr_wr(uart, lcr);
94
95    // set data terminal ready
96    pc16550d_mcr_t mcr = pc16550d_mcr_default;
97    mcr = pc16550d_mcr_dtr_insert(mcr, 1);
98    mcr = pc16550d_mcr_out_insert(mcr, 2);
99    pc16550d_mcr_wr(uart, mcr);
100
101    // Set baudrate (XXX: hard-coded to 115200)
102    if (!CPU_IS_M5_SIMULATOR) {
103        pc16550d_lcr_dlab_wrf(uart, 1);
104        pc16550d_dl_wr(uart, pc16550d_baud115200);
105        pc16550d_lcr_dlab_wrf(uart, 0);
106    }
107
108    return SYS_ERR_OK;
109}
110
111errval_t serial_early_init(unsigned port)
112{
113    return SYS_ERR_OK;
114}
115
116/** \brief Prints a single character to the default serial port. */
117void serial_putchar(unsigned port, char c)
118{
119    assert(port < NUM_PORTS);
120    assert(ports[port].base != 0);
121    // Wait until FIFO can hold more characters
122    while(!pc16550d_lsr_thre_rdf(&ports[port]));
123    // Write character
124    pc16550d_thr_wr(&ports[port], c);
125}
126
127/** \brief Reads a single character from the default serial port.
128 * This function spins waiting for a character to arrive.
129 */
130char serial_getchar(unsigned port)
131{
132    assert(port < NUM_PORTS);
133    assert(ports[port].base != 0);
134
135    // Read a character from FIFO
136    while( !pc16550d_lsr_dr_rdf(&ports[port]));
137    return pc16550d_rbr_rd(&ports[port]);
138}
139