Skip to content

Instantly share code, notes, and snippets.

@ktemkin
Created May 8, 2018 15:34
Show Gist options
  • Save ktemkin/5426fc5464d690d782d27544b9c61f86 to your computer and use it in GitHub Desktop.
Save ktemkin/5426fc5464d690d782d27544b9c61f86 to your computer and use it in GitHub Desktop.
#include <stdint.h>
#include "registers.h"
#define UART_PROVIDES_PUTS_PUTC
#define CAR_BASE 0x60006000UL
#define PINMUX_BASE 0x70003000UL
#define HEX_CHAR(x) ((((x) + '0') > '9') ? ((x) + '7') : ((x) + '0'))
typedef struct {
/* uart itself */
char * name;
uint64_t base;
/* clock and reset */
uint32_t car_mask;
uint32_t car_clock_offset;
uint32_t car_reset_offset;
uint32_t car_source_offset;
/* pinmux */
uint32_t pinmux_tx_offset;
/* tx offset is always +1 rx_offset */
/* inversion settings */
// the switch inverts these when communciating with its joycons; we may want to as well
int inversion_settings;
} uart_t;
static const uart_t uarts[] = {
/* UARTs */
{ .name = "UART-A", .base = 0x70006000ULL, .car_clock_offset = 0x10, .car_reset_offset = 0x04,
.car_mask = (1 << 6), .car_source_offset = 0x178, .pinmux_tx_offset = 0x0e4},
{ .name = "UART-B", .base = 0x70006040ULL, .car_clock_offset = 0x10, .car_reset_offset = 0x04,
.car_mask = (1 << 7), .car_source_offset = 0x17c, .pinmux_tx_offset = 0x0f4},
{ .name = "UART-C", .base = 0x70006200ULL, .car_clock_offset = 0x14, .car_reset_offset = 0x08,
.car_mask = (1 << 23), .car_source_offset = 0x1a0, .pinmux_tx_offset = 0x104, .inversion_settings = 0b11},
{ .name = "UART-D", .base = 0x70006300ULL, .car_clock_offset = 0x18, .car_reset_offset = 0x0c,
.car_mask = (1 << 1), .car_source_offset = 0x1c0, .pinmux_tx_offset = 0x114},
};
static const uart_t *uart;
enum {
UART_THR_DLAB = 0x00,
UART_IER_DLAB = 0x04,
UART_IIR_FCR = 0x08,
UART_LCR = 0x0C,
UART_LSR = 0x14,
UART_IRDA_CSR = 0x20
};
enum {
UART_RATE_115200 = (408000000/115200/16), /* based on 408000000 PLLP */
FCR_TX_CLR = 0x4, /* bit 2 of FCR : clear TX FIFO */
FCR_RX_CLR = 0x2, /* bit 1 of FCR : clear RX FIFO */
FCR_EN_FIFO = 0x1, /* bit 0 of FCR : enable TX & RX FIFO */
LCR_DLAB = 0x80, /* bit 7 of LCR : Divisor Latch Access Bit */
LCR_WD_SIZE_8 = 0x3, /* bit 1:0 of LCR : word length of 8 */
};
static void reset_using_pmc()
{
uint32_t *reset;
reset = (uint32_t *)0x7000e400;
*reset |= (1 << 4);
}
static inline unsigned long div_round_up(unsigned int n, unsigned int d)
{
return (n + d - 1) / d;
}
/**
* Enables a given UART, and selects it for use in the UART functions.
*/
void enable_uart(int uart_number)
{
/* Set the active uart. */
uart = &uarts[uart_number];
/* Enable UART clock */
reg_set(CAR_BASE, uart->car_clock_offset, uart->car_mask);
/* Reset and unreset UART */
reg_set( CAR_BASE, uart->car_reset_offset, uart->car_mask);
reg_clear(CAR_BASE, uart->car_reset_offset, uart->car_mask);
/* Program UART clock source: PLLP (408000000) */
reg_write(CAR_BASE, uart->car_source_offset, 0);
/* Program 115200n8 to the uart port */
/* baud-rate of 115200 */
reg_set( uart->base, UART_LCR, LCR_DLAB);
reg_write(uart->base, UART_THR_DLAB, (UART_RATE_115200 & 0xff));
reg_write(uart->base, UART_IER_DLAB, (UART_RATE_115200 >> 8));
reg_clear(uart->base, UART_LCR, LCR_DLAB);
/* 8-bit and no */
reg_write(uart->base, UART_LCR, LCR_WD_SIZE_8);
reg_write(uart->base, UART_IRDA_CSR, uart->inversion_settings);
/* Handle pin multiplexing. */
reg_write(PINMUX_BASE, uart->pinmux_tx_offset, 0b0001000);
reg_write(PINMUX_BASE, uart->pinmux_tx_offset + 4, 0b1001000);
/* ensure the TX fifos are up */
reg_write(uart->base, UART_IIR_FCR, 0);
}
/**
* Returns true iff the UART buffer is ready to transmit.
*/
static int uart_buffer_available()
{
uint32_t holding_reg = reg_read(uart->base, UART_LSR);
return ((holding_reg >> 5) & 0x01);
}
/**
* Prints a single character (synchronously) via serial.
*
* @param c The character to be printed
*/
void uart_putc(char c)
{
// If we're about to send a newline, prefix it with a carriage return.
// This makes our putc behave like a normal console putc.
if(c == '\n')
uart_putc('\r');
// Wait for the holding register to become available.
while(!uart_buffer_available());
// Stick data in the holding register...
reg_write(uart->base, UART_THR_DLAB, c);
}
/**
* Prints a string (synchronously) via serial.
*
* @param s The string to be printed; must be null terminated.
*/
int uart_puts(const char * s)
{
while(*s) {
uart_putc(*s);
++s;
}
return 0;
}
/* send an 8 bit byte as two HEX characters to the console */
void dump_byte(uint8_t b)
{
uart_putc(HEX_CHAR((b >> 4) & 0xf));
uart_putc(HEX_CHAR(b & 0xf));
}
void dump_word(uint32_t w)
{
dump_byte(w >> 8);
dump_byte(w & 0xff);
}
void dump_dword(uint32_t d)
{
dump_word(d >> 16);
dump_word(d & 0xffff);
}
#pragma once
enum {
UART_A = 0,
UART_B = 1,
UART_C = 2,
UART_D = 3,
};
/**
* Enables a given UART, and selects it for use in the UART functions.
*/
void enable_uart(int uart);
/**
* Prints a string (synchronously) via serial.
*
* @param s The string to be printed; must be null terminated.
*/
int uart_puts(const char * s);
/**
* Prints a single character (synchronously) via serial.
*
* @param c The character to be printed
*/
void uart_putc(char c);
/* send an 8 bit byte as two HEX characters to the console */
void dump_byte(uint8_t b);
void dump_word(uint32_t w);
void dump_dword(uint32_t d);
#ifdef UART_PROVIDES_PUTS_PUTC
static inline int puts(const char * s)
{
return uart_puts(s);
}
static inline void putc(char c)
{
uart_putc(c);
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment