aboutsummaryrefslogtreecommitdiff
#include <stddef.h>
#include <stdint.h>
#include "uart.h"
#include "global.h"

void uart_init(uint32_t baud, uint32_t config)
{
  // PL011 UART must be disabled before configuring
  wr32(PL011_UART_CR, 0);

  // GPIO pins used for UART should have pull up/down disabled
  // Procedure as described in BCM2835 ARM Peripherals
  wr32(GPPUD, 0);

  for (int i = 0; i < 150; i++) // delay for at least 150 cycles
    asm volatile("nop");

  wr32(GPPUDCLK0, (1 << 14) | (1 << 15));

  for (int i = 0; i < 150; i++)
    asm volatile("nop");

  wr32(GPPUDCLK0, 0);
  
  wr32(GPPUD, 0);  

  // Setting clock rate
  // As described in UART (PL011) Technical Reference Manual
  uint32_t int_part = DEFAULT_UART_CLOCK_RATE / (16 * baud);
  uint32_t rest = DEFAULT_UART_CLOCK_RATE % (16 * baud);
  uint32_t fract_part = (rest * 64 * 2 + 1) / (2 * 16 * baud);
  
  wr32(PL011_UART_IBRD, int_part);
  wr32(PL011_UART_FBRD, fract_part);

  // Set data transmission specified by caller
  // Don't enable FIFO to be able to receive interrupt every received
  // char, not every 2 chars
  wr32(PL011_UART_LCRH, config);

  // Set interrupt to come when transmit FIFO becomes ≤ 1/8 full
  // or receive FIFO becomes ≥ 1/8 full
  // (not really matters, since we disabled FIFOs)
  wr32(PL011_UART_IFLS, 0);

  // Enable UART receiving and transmitting, as well as UART itself
  wr32(PL011_UART_CR, (1 << 9) | (1 << 8) | 1);

  // At first, it's probably safer to disable interrupts :)
  uart_irq_disable();

  // The above disables the entire uart irq;
  // Also disable single sources within it
  wr32(PL011_UART_IMSC, 0);
}

inline static _Bool can_transmit(void)
{
  return !(rd32(PL011_UART_FR) & (1 << 5));
}

inline static _Bool can_receive(void)
{
  return !(rd32(PL011_UART_FR) & (1 << 4));
}

void putchar(char c)
{
  while (!can_transmit());
  
  wr32(PL011_UART_DR, c);
}

char getchar(void)
{
  while (!can_receive());
  
  return rd32(PL011_UART_DR);
}

_Bool putchar_non_blocking(char c)
{
  if (can_transmit())
    {
      wr32(PL011_UART_DR, c);
      return 0;
    }

  return 1;
}

int getchar_non_blocking(void)
{
  if (can_receive())
    return rd32(PL011_UART_DR);

  return -1;
}