summaryrefslogtreecommitdiffstats
path: root/src/soc/nvidia/tegra124/uart.c
blob: d016cc26180e1d10e2fbca504bd0c48a3a90290d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* SPDX-License-Identifier: GPL-2.0-only */

#include <device/mmio.h>
#include <boot/coreboot_tables.h>
#include <console/uart.h>
#include <drivers/uart/uart8250reg.h>


struct tegra124_uart {
	union {
		uint32_t thr; // Transmit holding register.
		uint32_t rbr; // Receive buffer register.
		uint32_t dll; // Divisor latch lsb.
	};
	union {
		uint32_t ier; // Interrupt enable register.
		uint32_t dlm; // Divisor latch msb.
	};
	union {
		uint32_t iir; // Interrupt identification register.
		uint32_t fcr; // FIFO control register.
	};
	uint32_t lcr; // Line control register.
	uint32_t mcr; // Modem control register.
	uint32_t lsr; // Line status register.
	uint32_t msr; // Modem status register.
} __packed;

static void tegra124_uart_tx_flush(struct tegra124_uart *uart_ptr);
static int tegra124_uart_tst_byte(struct tegra124_uart *uart_ptr);

static void tegra124_uart_init(struct tegra124_uart *uart_ptr)
{
	// Use a hardcoded divisor for now.
	const unsigned int divisor = 221;
	const uint8_t line_config = UART8250_LCR_WLS_8; // 8n1

	tegra124_uart_tx_flush(uart_ptr);

	// Disable interrupts.
	write8(&uart_ptr->ier, 0);
	// Force DTR and RTS to high.
	write8(&uart_ptr->mcr, UART8250_MCR_DTR | UART8250_MCR_RTS);
	// Set line configuration, access divisor latches.
	write8(&uart_ptr->lcr, UART8250_LCR_DLAB | line_config);
	// Set the divisor.
	write8(&uart_ptr->dll, divisor & 0xff);
	write8(&uart_ptr->dlm, (divisor >> 8) & 0xff);
	// Hide the divisor latches.
	write8(&uart_ptr->lcr, line_config);
	// Enable FIFOs, and clear receive and transmit.
	write8(&uart_ptr->fcr, UART8250_FCR_FIFO_EN |
	       UART8250_FCR_CLEAR_RCVR | UART8250_FCR_CLEAR_XMIT);
}

static unsigned char tegra124_uart_rx_byte(struct tegra124_uart *uart_ptr)
{
	if (!tegra124_uart_tst_byte(uart_ptr))
		return 0;
	return read8(&uart_ptr->rbr);
}

static void tegra124_uart_tx_byte(struct tegra124_uart *uart_ptr, unsigned char data)
{
	while (!(read8(&uart_ptr->lsr) & UART8250_LSR_THRE));
	write8(&uart_ptr->thr, data);
}

static void tegra124_uart_tx_flush(struct tegra124_uart *uart_ptr)
{
	while (!(read8(&uart_ptr->lsr) & UART8250_LSR_TEMT));
}

static int tegra124_uart_tst_byte(struct tegra124_uart *uart_ptr)
{
	return (read8(&uart_ptr->lsr) & UART8250_LSR_DR) == UART8250_LSR_DR;
}

uintptr_t uart_platform_base(int idx)
{
	//Default to UART A
	unsigned int base = 0x70006000;
	//UARTs A - E are mapped as index 0 - 4
	if ((idx < 5) && (idx >= 0)) {
		if (idx != 1) { //not UART B
			base += idx * 0x100;
		} else {
			base += 0x40;
		}
	}
	return base;
}

void uart_init(int idx)
{
	struct tegra124_uart *uart_ptr = uart_platform_baseptr(idx);
	tegra124_uart_init(uart_ptr);
}

unsigned char uart_rx_byte(int idx)
{
	struct tegra124_uart *uart_ptr = uart_platform_baseptr(idx);
	return tegra124_uart_rx_byte(uart_ptr);
}

void uart_tx_byte(int idx, unsigned char data)
{
	struct tegra124_uart *uart_ptr = uart_platform_baseptr(idx);
	tegra124_uart_tx_byte(uart_ptr, data);
}

void uart_tx_flush(int idx)
{
	struct tegra124_uart *uart_ptr = uart_platform_baseptr(idx);
	tegra124_uart_tx_flush(uart_ptr);
}

void uart_fill_lb(void *data)
{
	struct lb_serial serial;
	serial.type = LB_SERIAL_TYPE_MEMORY_MAPPED;
	serial.baseaddr = uart_platform_base(CONFIG_UART_FOR_CONSOLE);
	serial.baud = get_uart_baudrate();
	serial.regwidth = 4;
	serial.input_hertz = uart_platform_refclk();
	serial.uart_pci_addr = CONFIG_UART_PCI_ADDR;
	lb_add_serial(&serial, data);

	lb_add_console(LB_TAG_CONSOLE_SERIAL8250MEM, data);
}