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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
|
title: Raspberry 5 baremetal UART example
keywords:raspi5,asm,c,kernel,arm64,uart,tty,serial
# Raspberry 5 bare metal UART example
## Intro
As base can check the [/writeup/raspberry5_baremetal_helloworld.md](/writeup/raspberry5_baremetal_helloworld.md)
Getting basic communication for Raspberry 5 UART bare metal.
## Enabling UART
To find out MMIO base for UART, first step to set flag so
PCIe bus doesn't reset UART,
```
enable_rp1_uart=1
```
if flag is set then UART will output string
```
RP1_UART 0000001c00030000
```
where MMIO base is
```c
#define MMIO_BASE 0x1c00000000UL
```
and UART base
```c
#define PL011_REG_OFFSET MMIO_BASE+0x30000
```
## Configure UART
Disable UART before configure it and then set desired settings.
This case just to set to 8 bit mode. After configuration done
enable RX/TX and UART.
```c
//disable UART
pl011_write32(PL011_CR,0x0);
//config uart
pl011_write32(PL011_LCRH, PL011_LCRH_WLEN_8BIT);
//enable UART
pl011_write32(
PL011_CR,
PL011_CR_UARTEN|PL011_CR_TXE|PL011_CR_RXE
);
```
## Sending first characters
Sending characters is done by writing 8 bits to register DR
```c
#define PL011_DR 0x00 /* Data read or written from the interface. */
```
if just write then raspi5 will do it faster then UART can process and
characters will be lost in basic case only first 2 characters where out.
So before writing to DR check that TX FIFO is not full and wait if its full
```c
void pl011_putc(const uint8_t c) {
while (pl011_read32(PL011_FR)&PL011_FR_TXFF);
pl011_write32(PL011_DR,c);
}
```
## Receiving characters
to receive characters read register DR and to check if RX is empty
check registers FR RX FIFO Empty flag.
```c
uint8_t pl011_getc() {
return pl011_read8(PL011_DR);
}
```
## Basic command line
Here is basic echo terminal loop that echo's back characters sent over UART.
Loop checks FR register to see if RX FIFO isn't empty, and read character.
Extra processing set for Enter to output "\r\n" and move to new line
```c
while (1) {
uint32_t rx_status = pl011_read32(PL011_FR);
//empty? if not printout
if (!(rx_status&PL011_FR_RXFE)) {
uint8_t c = pl011_read8(PL011_DR);
if (c == 0xD) {
pl011_puts("\r\n");
} else {
pl011_write8(PL011_DR,c);
}
}
}
```
## Source code
For base setup check hello world example [/writeup/raspberry5_baremetal_helloworld.md](/writeup/raspberry5_baremetal_helloworld.md)
### pl011.h
```c
#ifndef __PL011_H
#define __PL011_H
//#include <stdint.h>
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
//static for now for raspi5
#define MMIO_BASE 0x1c00000000UL
#define PL011_REG_OFFSET MMIO_BASE+0x30000
#define PL011_DR 0x00 /* Data read or written from the interface. */
#define PL011_FR 0x18 /* Flag register (Read only). */
#define PL011_IBRD 0x24 /* Integer baud rate divisor register. */
#define PL011_FBRD 0x28 /* Fractional baud rate divisor register. */
#define PL011_LCRH 0x2c /* Line control register. */
#define PL011_CR 0x30 /* Control register. */
#define PL011_IFLS 0x34 /* Interrupt fifo level select. */
#define PL011_IMSC 0x38 /* Interrupt mask. */
#define PL011_RIS 0x3c /* Raw interrupt status. */
#define PL011_MIS 0x40 /* Masked interrupt status. */
#define PL011_ICR 0x44 /* Interrupt clear register. */
#define PL011_DMACR 0x48 /* DMA control register. */
#define PL011_FR_RXFE (1 << 4)/* RX FIFO Empty */
#define PL011_FR_TXFF (1 << 5)/* TX FIFO full */
#define PL011_LCRH_WLEN_8BIT (3 << 5) /* 8bit */
#define PL011_CR_CTSEN (1 << 15) /* CTS hardware flow control enable */
#define PL011_CR_RTSEN (1 << 14) /* RTS hardware flow control enable */
#define PL011_CR_RTS (1 << 11) /* Request to send */
#define PL011_CR_DTR (1 << 10) /* Data transmit ready. */
#define PL011_CR_RXE (1 << 9) /* Receive enable */
#define PL011_CR_TXE (1 << 8) /* Transmit enable */
#define PL011_CR_LBE (1 << 7) /* Loopback enable */
#define PL011_CR_UARTEN (1 << 0) /* UART Enable */
#define pl011_read8(offset) (*(volatile uint8_t *)(PL011_REG_OFFSET+offset))
#define pl011_write8(offset,val) (*(volatile uint8_t *)(PL011_REG_OFFSET+offset)) = val
#define pl011_read32(offset) (*(volatile uint32_t *)(PL011_REG_OFFSET+offset))
#define pl011_write32(offset,val) (*(volatile uint32_t *)(PL011_REG_OFFSET+offset)) = val
void pl011_init();
void pl011_putc(const uint8_t c);
void pl011_puts(const char *str);
uint8_t pl011_getc();
```
### pl011.c
```c
#include "pl011.h"
void pl011_init() {
//disable UART
pl011_write32(PL011_CR,0x0);
//config uart
pl011_write32(PL011_LCRH, PL011_LCRH_WLEN_8BIT);
//enable UART
pl011_write32(
PL011_CR,
PL011_CR_UARTEN|PL011_CR_TXE|PL011_CR_RXE
);
}
void pl011_putc(const uint8_t c) {
while (pl011_read32(PL011_FR)&PL011_FR_TXFF);
pl011_write32(PL011_DR,c);
}
void pl011_puts(const char *str) {
uint16_t cnt = 0;
while (cnt<256) {
if (str[cnt] != 0) {
pl011_putc(str[cnt]);
} else {
break;
}
cnt++;
}
}
uint8_t pl011_getc() {
return pl011_read8(PL011_DR);
}
```
### kernel.c
```c
#include <pl011.h>
void main()
{
pl011_init();
pl011_puts("Raspberry Pi5 UART demo?!\r\n");
while (1) {
uint32_t rx_status = pl011_read32(PL011_FR);
//empty? if not printout
if (!(rx_status&PL011_FR_RXFE)) {
uint8_t c = pl011_read8(PL011_DR);
if (c == 0xD) {
pl011_puts("\r\n");
} else {
pl011_write8(PL011_DR,c);
}
}
}
}
```
Creating Makefile and linker script is left as exercise
## Links
https://github.com/BarrelfishOS/barrelfish/blob/master/kernel/arch/arm/pl011.c
https://github.com/DSERIOUSGUY/HobOS/blob/main/uart.c
https://github.com/rsta2/circle/blob/master/lib/serial.cpp
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/tty/serial/amba-pl011.c?h=v6.16-rc4
https://developer.arm.com/documentation/ddi0183/latest/
[/writeup/raspberry5_baremetal_helloworld.md](/writeup/raspberry5_baremetal_helloworld.md)
https://wiki.osdev.org/Detecting_Raspberry_Pi_Board
https://github.com/dwelch67/raspberrypi/tree/master/uart01
https://github.com/bztsrc/raspi3-tutorial/blob/master/03_uart1/uart.c
https://www.rpi4os.com/part3-helloworld/
https://www.valvers.com/open-software/raspberry-pi/bare-metal-programming-in-c-part-5/#aux-peripheral
https://github.com/valvers/arm-tutorial-rpi/tree/master/part-5
https://jsandler18.github.io/tutorial/boot.html
https://github.com/ARM-software/arm-trusted-firmware/blob/master/plat/rpi/rpi5/include/rpi_hw.h
https://github.com/ARM-software/arm-trusted-firmware/blob/master/drivers/arm/pl011/aarch64/pl011_console.S
https://elixir.bootlin.com/linux/v6.15.5/source/include/linux/amba/serial.h#L39
https://github.com/ARM-software/arm-trusted-firmware/blob/master/include/drivers/arm/pl011.h
[/writeup/raspberry5_baremetal_helloworld.md](/writeup/raspberry5_baremetal_helloworld.md)
https://krinkinmu.github.io/2020/11/29/PL011.html
https://forums.raspberrypi.com/viewtopic.php?t=34112
https://datasheets.raspberrypi.com/bcm2711/bcm2711-peripherals.pdf
https://datasheets.raspberrypi.com/rp1/rp1-peripherals.pdf
https://github.com/raspberrypi/linux/blob/rpi-6.12.y/arch/arm64/boot/dts/broadcom/bcm2712.dtsi
https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
|