summaryrefslogtreecommitdiff
path: root/src/uart.c
blob: bb2a0ea3b608c4567e10d0d7c439940319382466 (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
/*
References:
  https://wiki.osdev.org/Raspberry_Pi_Bare_Bones
*/
#include <uart.h>

#include <gpio.h>
#include <mmio.h>

enum
{
    // The base address for UART.
    // For raspi4 0xFE201000, raspi2 & 3 0x3F201000, and 0x20201000 for raspi1.
    UART0_BASE   = (GPIO_BASE + 0x1000),
    // The offsets for reach register for the UART.
    UART0_DR     = (UART0_BASE + 0x00),
    UART0_RSRECR = (UART0_BASE + 0x04),
    UART0_FR     = (UART0_BASE + 0x18),
    UART0_ILPR   = (UART0_BASE + 0x20),
    UART0_IBRD   = (UART0_BASE + 0x24),
    UART0_FBRD   = (UART0_BASE + 0x28),
    UART0_LCRH   = (UART0_BASE + 0x2C),
    UART0_CR     = (UART0_BASE + 0x30),
    UART0_IFLS   = (UART0_BASE + 0x34),
    UART0_IMSC   = (UART0_BASE + 0x38),
    UART0_RIS    = (UART0_BASE + 0x3C),
    UART0_MIS    = (UART0_BASE + 0x40),
    UART0_ICR    = (UART0_BASE + 0x44),
    UART0_DMACR  = (UART0_BASE + 0x48),
    UART0_ITCR   = (UART0_BASE + 0x80),
    UART0_ITIP   = (UART0_BASE + 0x84),
    UART0_ITOP   = (UART0_BASE + 0x88),
    UART0_TDR    = (UART0_BASE + 0x8C),
    // The offsets for Mailbox registers.
    MBOX_BASE    = 0xB880,
    MBOX_READ    = (MBOX_BASE + 0x00),
    MBOX_STATUS  = (MBOX_BASE + 0x18),
    MBOX_WRITE   = (MBOX_BASE + 0x20)
};

// A mailbox message with set clock rate of PL011 to 3MHz tag.
static volatile unsigned int  __attribute__((aligned(16))) mbox[9] = {
  9*4, 0, 0x38002, 12, 8, 2, 3000000, 0, 0
};

// Loop <delay> times in a way that the compiler won't optimize away.
static inline void delay(int32_t count) {
  asm volatile("__delay_%=: subs %[count], %[count], #1; bne __delay_%=\n"
    : "=r"(count): [count]"0"(count) : "cc");
}

void uart_init(int raspi) {
  // Disable UART0.
  mmio_write(UART0_CR, 0x00000000);
  
  // Set up GPIO pins 14 and 15.

  // Disable pull up/down for all GPIO pins.
  mmio_write(GPPUD, 0x00000000);
  delay(150);

  // Disable pull up/down for pins 14-15.
  mmio_write(GPPUDCLK0, (1 << 14) | (1 << 15));
  delay(150);

  // Write 0 to GPPUDCLK0 to make it take effect.
  mmio_write(GPPUDCLK0, 0x00000000);

  // Clear pending interrupts.
  mmio_write(UART0_ICR, 0x7FF);

  // Set integer & fractional part of baud rate.
  // Divider = UART_CLOCK/(16 * Baud)
  // Fraction part register = (Fractional part * 64) + 0.5
  // Baud = 115200.

  // For Raspi3 and 4 the UART_CLOCK is system-clock dependent by default.
  // Set it to 3Mhz so that we can consistently set the baud rate
  if (raspi >= 3) {
    // UART_CLOCK = 30000000;
    unsigned int r = (unsigned int) (((uint64_t)(&mbox) & ~0xF) | 8);
    // Wait until we can talk to the VC.
    while (mmio_read(MBOX_STATUS) & 0x80000000);
    // Send our message to property channel and wait for the response.
    mmio_write(MBOX_WRITE, r);
    while ((mmio_read(MBOX_STATUS) & 0x40000000) ||
           (mmio_read(MBOX_READ) != r));
  }

  // Divider = 3000000 / (16 * 115200) = 1.627 = ~1.
  mmio_write(UART0_IBRD, 1);
  // Fractional part register = (.627 * 64) + 0.5 = 40.6 = ~40.
  mmio_write(UART0_FBRD, 40);

  // Enable FIFO & 8 bit data transmission (1 stop bit, no parity).
  mmio_write(UART0_LCRH, (1 << 4) | (1 << 5) | (1 << 6));

  // Disable all interrupts.
  mmio_write(UART0_IMSC, (1 << 1) | (1 << 4) | (1 << 5) | (1 << 6) |
                         (1 << 7) | (1 << 8) | (1 << 9) | (1 << 10));

  // Enable UART0, receive & transfer part of UART.
  mmio_write(UART0_CR, (1 << 0) | (1 << 8) | (1 << 9));
}

void uart_putc(unsigned char c) {
  // Wait for UART to become ready to transmit.
  while (mmio_read(UART0_FR) & (1 << 5));
  mmio_write(UART0_DR, c);
}

unsigned char uart_getc() {
  // Wait for UART to have received something.
  while (mmio_read(UART0_FR) & (1 << 4));
  return mmio_read(UART0_DR);
}

void uart_write(const void* buffer, size_t length) {
  const uint8_t* bytes = buffer;
  for (size_t i = 0; i < length; ++i) {
    uart_putc(*bytes++);
  }
}

void uart_print(const char* string) {
  while (*string) {
    uart_putc(*string++);
  }
}