CS107e library source files
/*
* Functions to manage D1 clocks
*
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
* Sat Sep 7 11:08:33 PDT 2024
*/
#include "ccu.h"
#include "assert.h"
#include <stddef.h>
#include "printf.h"
#include "timer.h"
/*
* Models the D-1 clock tree as diagrammed on p.39 of D-1 manual
* (with some simplifications to only consider clocks we are using)
* The implementation borrows design elements from linux clock tree
* (e.g. clock parent)
*/
struct debug_info {
long (*fn)(uint32_t);
uint32_t reg_id;
const char *name;
ccu_parent_id_t parents[4];
uint32_t ncount, mcount;
};
static struct debug_info *info_for_id(uint32_t id);
static long debug_rate_pll(ccu_pll_id_t id);
static long debug_rate_clk(ccu_module_id_t id);
static long debug_rate_bgr(ccu_bgr_id_t id);
static long debug_rate_parent(ccu_parent_id_t id);
static int get_parent_src_index(ccu_module_id_t id, ccu_parent_id_t parent);
static void validate_pll(ccu_pll_id_t id);
static void validate_module_clk(ccu_module_id_t id);
static void validate_bgr(ccu_bgr_id_t id);
typedef union {
struct {
uint32_t factor_m0 : 1;
uint32_t factor_m1 : 1;
uint32_t : 6;
uint32_t factor_n : 8;
uint32_t factor_p : 6;
uint32_t : 5;
uint32_t output_ena : 1;
uint32_t locked : 1;
uint32_t lock_ena : 1;
uint32_t ldo_ena : 1;
uint32_t ena : 1;
};
uint32_t bits;
} pll_reg_t;
typedef union {
struct {
uint32_t factor_m : 5;
uint32_t : 3;
uint32_t factor_n : 2;
uint32_t : 14;
uint32_t src : 3;
uint32_t : 4;
uint32_t ena : 1;
};
uint32_t bits;
} module_clk_reg_t;
typedef struct {
union {
uint8_t raw[0xD10];
uint32_t regs[1];
} ;
} ccu_t;
#define CCU_BASE ((ccu_t *)0x02001000)
_Static_assert(&CCU_BASE->regs[0] == (uint32_t *)0x02001000, "CCU pll cpu reg must be at address 0x02001000");
_Static_assert(&CCU_BASE->raw[CCU_APB0_CLK_REG] == (uint8_t *)0x02001520, "CCU apb0 reg must be at address 0x02001520");
static volatile ccu_t *const module = CCU_BASE;
static volatile uint32_t *reg_for_id(uint32_t raw_offset) {
assert(raw_offset % 4 == 0); // reject if not on 4-byte boundary
assert(raw_offset < sizeof(module->raw)); // reject if not in range
return (uint32_t *)(module->raw + raw_offset);
}
// Converting PLL rate into appropriate set of dividers on the fly
// is messy. Rather, take from manual the recommended dividers for
// commonly used PLL rates and list in this table for easy access.
// if you need an additional rate config, simply add to this table
static const struct pll_config_t {
ccu_pll_id_t pll_id;
uint32_t rate;
struct { uint8_t P; uint8_t N; uint8_t M1; uint8_t M0; };
} pll_table[] = {
{CCU_PLL_VIDEO0_CTRL_REG, 297000000, { .N= 99, .M1= 2 }}, // recommended dividers 99,2 p.43 D-1 manual
{CCU_PLL_VIDEO0_CTRL_REG, 120000000, { .N= 20, .M1= 1 }},
{CCU_PLL_AUDIO0_CTRL_REG, 22545454, {.P= 33, .N= 124, .M1= 1, .M0= 1 }}, // base rate for audio 22.1Khz
{CCU_PLL_AUDIO1_CTRL_REG, 3072000000, { .N= 128, .M1= 1 }}, // base rate for audio 24Khz
{0}
};
static const struct pll_config_t *get_pll_config_for_rate(ccu_pll_id_t id, long rate) {
for (const struct pll_config_t *cfg = pll_table; cfg->rate != 0; cfg++) {
if (cfg->pll_id == id && cfg->rate == rate) return cfg;
}
return NULL;
}
#define ASSERT_IN_RANGE(val, lo, hi) assert(val >= lo && val <= hi)
#define BITS_N_M1(n, m1) ((pll_reg_t){ .factor_n=n, .factor_m1=m1 }).bits
#define BITS_P_N_M1_M0(p, n, m1, m0) ((pll_reg_t){ .factor_p=p, .factor_n=n, .factor_m1=m1, .factor_m0=m0 }).bits;
static void get_pll_bits(ccu_pll_id_t id, long rate, uint32_t *factor_mask, uint32_t *new_factors) {
uint32_t out_mhz;
const struct pll_config_t *cfg = get_pll_config_for_rate(id, rate);
if (!cfg) error("No matching pll config found in rate table.");
switch(cfg->pll_id) {
case CCU_PLL_VIDEO0_CTRL_REG: // N, M1
case CCU_PLL_VIDEO1_CTRL_REG:
ASSERT_IN_RANGE(cfg->N, 13, 255);
ASSERT_IN_RANGE(cfg->M1, 1, 2);
*factor_mask = BITS_N_M1(-1,-1);
*new_factors = BITS_N_M1(cfg->N-1,cfg->M1-1);
return;
case CCU_PLL_AUDIO0_CTRL_REG: // P, N, M1, M0
ASSERT_IN_RANGE(cfg->P, 1, 64);
ASSERT_IN_RANGE(cfg->N, 13, 255);
ASSERT_IN_RANGE(cfg->M1, 1, 2);
ASSERT_IN_RANGE(cfg->M0, 1, 2);
out_mhz = (cfg->N/cfg->M0/cfg->M1)*24;
ASSERT_IN_RANGE(out_mhz, 180, 3000); // valid output freq range is 180M-3G
*factor_mask = BITS_P_N_M1_M0(-1,-1,-1,-1);
*new_factors = BITS_P_N_M1_M0(cfg->P-1,cfg->N-1,cfg->M1-1,cfg->M0-1);
return;
case CCU_PLL_AUDIO1_CTRL_REG: // N, M1
ASSERT_IN_RANGE(cfg->N, 13, 255);
ASSERT_IN_RANGE(cfg->M1, 1, 2);
out_mhz = (cfg->N/cfg->M1)*24;
ASSERT_IN_RANGE(out_mhz, 180, 3500); // valid output freq range is 180M-3.5G
*factor_mask = BITS_N_M1(-1,-1);
*new_factors = BITS_N_M1(cfg->N-1,cfg->M1-1);
return;
case CCU_PLL_PERI_CTRL_REG:
case CCU_PLL_CPU_CTRL_REG:
case CCU_PLL_DDR_CTRL_REG:
case CCU_PLL_VE_CTRL_REG:
error("Attempt to change PLL that should not be modified.");
}
}
// Procedure to update PLL from p46 of D-1 user manual
// NOTE: code explicitly sets and clears bits using bitwise ops!
// This ensures it acts exactly as required in spec
// (not using bitfields, gcc-generated code can vary)
static void update_pll_bits(volatile uint32_t *reg, uint32_t factor_mask, uint32_t factor_bits) {
const uint32_t PLL_ENA = (1 << 31);
const uint32_t LOCK_ENA = (1 << 29);
const uint32_t LOCKED = (1 << 28);
const uint32_t OUT_ENA = (1 << 27);
*reg |= PLL_ENA; // enable PLL
*reg &= ~OUT_ENA; // disable output while changing
*reg = (*reg & ~factor_mask) | factor_bits; // clear previous factors & apply new
*reg |= LOCK_ENA; // lock enable
while (! (*reg & LOCKED)) // wait until lock
;
timer_delay_us(50); // short delay to stabilize
*reg |= OUT_ENA; // re-enable output
}
long ccu_config_pll_rate(ccu_pll_id_t id, long rate) {
validate_pll(id);
uint32_t factor_mask, new_factors;
get_pll_bits(id, rate, &factor_mask, &new_factors);
update_pll_bits(reg_for_id(id), factor_mask, new_factors);
long set_rate = debug_rate_pll(id);
assert(rate == set_rate);
return set_rate;
}
static uint32_t get_module_clk_bits(ccu_module_id_t id, ccu_parent_id_t parent, long rate) {
int src = get_parent_src_index(id, parent);
if (src == -1) error("Parent id is not valid for module clock")
long parent_rate = debug_rate_parent(parent);
module_clk_reg_t new_settings = { .src= src, .factor_n= 0, .factor_m= 0 };
if (parent_rate == rate) { // no dividers needed, src parent at desired rate
return new_settings.bits;
}
struct debug_info *info = info_for_id(id);
int n_exp_max = (1 << info->ncount) - 1;
int m_max = (1 << info->mcount);
int divisor_max = m_max * (1 << n_exp_max);
assert(parent_rate >= rate);
assert(parent_rate % rate == 0);
ASSERT_IN_RANGE(parent_rate/rate, 1, divisor_max);
int divider_needed = parent_rate/rate;
for (int exp = n_exp_max; exp >= 0; exp--) {
int power_of_two = 1 << exp;
if (divider_needed % power_of_two == 0 && divider_needed/power_of_two <= m_max) {
new_settings.factor_n = exp;
new_settings.factor_m = divider_needed/power_of_two - 1;
//printf("Choosing factors n %d m %d to reach rate %ld\n", new_settings.factor_n,new_settings.factor_m, rate);
return new_settings.bits;
}
}
error("No compatible factors between parent and module rate.");
}
/* From p47 D-1 user manual:
* Configure the clock source and frequency division factor first,
* and then release the clock gating (that is, set enable to 1)
*/
static void update_clock_bits(volatile uint32_t *reg, uint32_t bits) {
const uint32_t ENA = (1 << 31);
*reg &= ~ENA; // disable clock during change
*reg = bits;
*reg |= ENA; // re-enable
}
long ccu_config_module_clock_rate(ccu_module_id_t id, ccu_parent_id_t parent, long rate) {
validate_module_clk(id);
uint32_t new_bits = get_module_clk_bits(id, parent, rate);
update_clock_bits(reg_for_id(id), new_bits);
long set_rate = debug_rate_clk(id);
assert(rate == set_rate);
return set_rate;
}
/*
* From p47 D-1 user manual:
* For the Bus Gating Reset register of a module, the reset bit is de-asserted first,
* and then the clock gating bit is enabled to avoid potential problems
* caused by the asynchronous release of the reset signal.
*/
long ccu_ungate_bus_clock_bits(ccu_bgr_id_t id, uint32_t gating_bits, uint32_t reset_bits) {
validate_bgr(id);
volatile uint32_t *reg = reg_for_id(id);
*reg |= reset_bits; // de-assert reset
*reg |= gating_bits; // enable
return debug_rate_bgr(id);
}
// most bus clocks use standard bits for reset/gate
// general function above allow other use cases
long ccu_ungate_bus_clock(ccu_bgr_id_t id) {
const uint32_t standard_gating_bits = 1 << 0;
const uint32_t standard_reset_bits = 1 << 16;
return ccu_ungate_bus_clock_bits(id, standard_gating_bits, standard_reset_bits);
}
/**** DEBUG INFO from here down ***/
#define STRINGIFY(x) #x
#define INFO_PLL(x) debug_rate_pll, x, STRINGIFY(x)
#define INFO_CLK(x) debug_rate_clk, x, STRINGIFY(x)
#define INFO_BGR(x) debug_rate_bgr, x, STRINGIFY(x)
#define NOT_IN_MODEL PARENT_NONE
static struct debug_info info_table[] = {
{ .name= "PLL" },
{ INFO_PLL(CCU_PLL_CPU_CTRL_REG) },
{ INFO_PLL(CCU_PLL_DDR_CTRL_REG) },
{ INFO_PLL(CCU_PLL_PERI_CTRL_REG) },
{ INFO_PLL(CCU_PLL_VIDEO0_CTRL_REG) },
{ INFO_PLL(CCU_PLL_VIDEO1_CTRL_REG) },
{ INFO_PLL(CCU_PLL_VE_CTRL_REG) },
{ INFO_PLL(CCU_PLL_AUDIO0_CTRL_REG) },
{ INFO_PLL(CCU_PLL_AUDIO1_CTRL_REG) },
{ .name= "Module Clock" }, // parent not listed defaults to NOT_IN_MODEL
{ INFO_CLK(CCU_PSI_CLK_REG), {PARENT_HOSC, PARENT_32K, NOT_IN_MODEL, PARENT_PERI}, .ncount=2,.mcount=2 },
{ INFO_CLK(CCU_APB0_CLK_REG), {PARENT_HOSC, PARENT_32K, PARENT_PSI, PARENT_PERI}, .ncount=2,.mcount=5 },
{ INFO_CLK(CCU_APB1_CLK_REG), {PARENT_HOSC, PARENT_32K, PARENT_PSI, PARENT_PERI}, .ncount=2,.mcount=5 },
{ INFO_CLK(CCU_DRAM_CLK_REG), {PARENT_DDR, NOT_IN_MODEL, PARENT_PERI_2X}, .ncount=2,.mcount=2 },
{ INFO_CLK(CCU_DE_CLK_REG), {PARENT_PERI_2X, PARENT_VIDEO0_4X, PARENT_VIDEO1_4X}, .ncount=0,.mcount=5 },
{ INFO_CLK(CCU_TCONTV_CLK_REG), {PARENT_VIDEO0, PARENT_VIDEO0_4X}, .ncount=2,.mcount=4 },
{ INFO_CLK(CCU_HDMI_24M_CLK_REG), {PARENT_HOSC}, .ncount=0,.mcount=0 },
{ INFO_CLK(CCU_SPI0_CLK_REG), {PARENT_HOSC, PARENT_PERI, PARENT_PERI_2X}, .ncount=2,.mcount=4 },
{ INFO_CLK(CCU_SPI1_CLK_REG), {PARENT_HOSC, PARENT_PERI, PARENT_PERI_2X}, .ncount=2,.mcount=4 },
{ INFO_CLK(CCU_I2S2_CLK_REG), {PARENT_AUDIO0, NOT_IN_MODEL, NOT_IN_MODEL, PARENT_AUDIO1_DIV5}, .ncount=0,.mcount=5},
{ INFO_CLK(CCU_LEDC_CLK_REG), {PARENT_HOSC, PARENT_PERI, NOT_IN_MODEL, NOT_IN_MODEL}, .ncount=2,.mcount=4},
{ .name= "Bus Clock" },
{ INFO_BGR(CCU_DE_BGR_REG), {PARENT_AHB0} },
{ INFO_BGR(CCU_DPSS_TOP_BGR_REG), {PARENT_AHB0} },
{ INFO_BGR(CCU_HDMI_BGR_REG), {PARENT_AHB0} },
{ INFO_BGR(CCU_TCONTV_BGR_REG), {PARENT_AHB0} },
{ INFO_BGR(CCU_DMA_BGR_REG), {PARENT_AHB0} },
{ INFO_BGR(CCU_HSTIMER_BGR_REG), {PARENT_AHB0} },
{ INFO_BGR(CCU_PWM_BGR_REG), {PARENT_APB0} },
{ INFO_BGR(CCU_UART_BGR_REG), {PARENT_APB1} },
{ INFO_BGR(CCU_I2S_BGR_REG), {PARENT_APB0} },
{ INFO_BGR(CCU_TWI_BGR_REG), {PARENT_APB1} },
{ INFO_BGR(CCU_SPI_BGR_REG), {PARENT_APB1} },
{ INFO_BGR(CCU_LEDC_BGR_REG), {PARENT_APB1} },
{0},
};
static int get_parent_src_index(ccu_module_id_t id, ccu_parent_id_t parent) {
for (struct debug_info *info = info_table; info->name; info++) {
if (info->reg_id == id) {
for (int i = 0; i < sizeof(info->parents)/sizeof(*info->parents); i++) {
if (info->parents[i] == parent) return i;
}
}
}
return -1;
}
static struct debug_info *info_for_id(uint32_t id) {
for (struct debug_info *info = info_table; info->name; info++) {
if (!info->fn) continue;
if (info->reg_id == id) {
return info;
}
}
return NULL;
}
void ccu_debug_show_clocks(const char *label) {
printf("\n++++++++ CCU clock debug (%s) ++++++++\n", label);
for (struct debug_info *i = info_table; i->name; i++) {
if (!i->fn) {
printf("\n Rate %s\n", i->name);
continue;
}
long rate = i->fn(i->reg_id);
if (rate != 0) printf("%12ld %s\t raw=[%08x]\n", rate, i->name, *reg_for_id(i->reg_id));
}
}
static long debug_rate_parent(ccu_parent_id_t id) {
int mult = 1, div = 1;
switch (id) {
case NOT_IN_MODEL: return -1;
case PARENT_HOSC: return 24*1000*1000;
case PARENT_32K: return 32768;
case PARENT_DDR: return debug_rate_pll(CCU_PLL_DDR_CTRL_REG);
case PARENT_PERI_2X: mult = 2; // *** fallthrough
case PARENT_PERI: return mult*debug_rate_pll(CCU_PLL_PERI_CTRL_REG);
case PARENT_VIDEO0_4X: mult = 4; // *** fallthrough
case PARENT_VIDEO0: return mult*debug_rate_pll(CCU_PLL_VIDEO0_CTRL_REG);
case PARENT_VIDEO1_4X: mult = 4; // *** fallthrough
case PARENT_VIDEO1: return mult*debug_rate_pll(CCU_PLL_VIDEO1_CTRL_REG);
case PARENT_AUDIO0: return debug_rate_pll(CCU_PLL_AUDIO0_CTRL_REG);
case PARENT_AUDIO1_DIV5: div = 5; // *** fallthrough
case PARENT_AUDIO1: return debug_rate_pll(CCU_PLL_AUDIO1_CTRL_REG) / div;
case PARENT_APB0: return debug_rate_clk(CCU_APB0_CLK_REG);
case PARENT_APB1: return debug_rate_clk(CCU_APB1_CLK_REG);
case PARENT_AHB0: return debug_rate_clk(CCU_PSI_CLK_REG);
case PARENT_PSI: return debug_rate_clk(CCU_PSI_CLK_REG);
}
return -1;
}
static long debug_rate_pll(ccu_pll_id_t id) {
pll_reg_t pll;
pll.bits = *reg_for_id(id);
if (!pll.ena || !pll.output_ena) return 0;
int p = pll.factor_p+1, n = pll.factor_n+1, m1 = pll.factor_m1+1, m0 = pll.factor_m0+1;
long parent_rate = debug_rate_parent(PARENT_HOSC);
switch (id) {
case CCU_PLL_PERI_CTRL_REG:
case CCU_PLL_VIDEO0_CTRL_REG:
case CCU_PLL_VIDEO1_CTRL_REG:
return parent_rate*n/m1/4;
case CCU_PLL_AUDIO0_CTRL_REG:
return parent_rate*n/m1/m0/p/4;
default:
return parent_rate*n/m1/m0;
}
}
static long debug_rate_clk(ccu_module_id_t id) {
module_clk_reg_t clk;
clk.bits = *reg_for_id(id);
if (id > CCU_APB1_CLK_REG && !clk.ena) return 0; // cheezy (ena bits not applicable for psi/apb?)
int n = 1 << clk.factor_n;
int m = clk.factor_m + 1;
struct debug_info *i = info_for_id(id);
ccu_parent_id_t parent = i->parents[clk.src];
return debug_rate_parent(parent)/n/m;
}
static long debug_rate_bgr(ccu_bgr_id_t id) {
uint32_t val = *reg_for_id(id);
struct debug_info *i = info_for_id(id);
return (val & 0xff) ? debug_rate_parent(i->parents[0]) : 0;
}
static void validate_pll(ccu_pll_id_t id) {
struct debug_info *info = info_for_id(id);
if (!info || info->fn != debug_rate_pll) error("PLL id is not valid");
}
static void validate_module_clk(ccu_module_id_t id) {
struct debug_info *info = info_for_id(id);
if (!info || info->fn != debug_rate_clk) error("Module clock id is not valid");
}
static void validate_bgr(ccu_bgr_id_t id) {
struct debug_info *info = info_for_id(id);
if (!info || info->fn != debug_rate_bgr) error("Bus clock id is not valid");
}
/*
* File: cstart.c
* --------------
*
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
*/
#include "mango.h"
#include "strings.h"
#include "memmap.h"
void *sys_memset(void *s, int c, size_t n);
// The C function _cstart is called from the assembly in start.s
// _cstart zeroes out the BSS section and then calls the main function
void _cstart(void) {
// linker script memmap.ld places symbols to mark bss boundaries
sys_memset(&__bss_start, 0, &__bss_end - &__bss_start);
mango_actled(LED_ON); // turn on blue onboard LED while executing main
main();
mango_reboot(); // reset the Pi if main() completed successfully
}
/*
* Module to control DisplayEngine 2.0 peripheral on Mango Pi
*
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
* Feb 2024
*/
#include "de.h"
#include "assert.h"
#include "timer.h"
// Display Engine 2.0
typedef struct {
uint32_t width :13;
uint32_t :3;
uint32_t height :13;
uint32_t :3;
} de_size_t;
// minimal layout of DE device reigsters to init & configure mixer/blender/ui layer
typedef union {
struct {
uint32_t sclk_gate;
uint32_t hclk_gate;
uint32_t ahb_reset;
uint32_t sclk_div; // 4 bits per module
uint32_t de2tcon_mux; // @[0] swap mixer/tcon
} regs;
} de_t;
typedef union {
struct {
uint32_t glb_ctl;
uint32_t glb_sts;
uint32_t glb_dbuffer;
de_size_t glb_size;
} regs;
} de_mixer_t;
typedef union {
struct {
uint32_t pipe_ctrl;
struct {
uint32_t fill_color;
de_size_t input_size;
uint32_t offset;
uint32_t reserved;
} pipe[4];
// not sure re: # of pipes? (D1 manual says 4 in one place and 2 in another)
// we are currently only using pipe [1]
uint32_t reserved[15];
uint32_t route;
uint32_t premultiply;
uint32_t background_color;
de_size_t output_size;
} regs;
} de_blender_t;
typedef union {
struct {
struct {
uint32_t attr_ctrl;
de_size_t size;
uint32_t offset;
uint32_t pitch_nbytes;
uint32_t top_laddr;
uint32_t bot_laddr;
uint32_t fill_color;
uint32_t reserved;
} layer[4];
uint32_t top_haddr;
uint32_t bot_haddr;
de_size_t overlay_size;
} regs;
} de_ui_t;
typedef union {
struct {
uint32_t ctrl;
uint32_t status;
uint32_t field_ctrl;
uint32_t bist;
uint32_t reservedA[12];
de_size_t output_size;
uint32_t reservedB[15];
de_size_t input_size;
uint32_t reservedC;
uint32_t horiz_step;
uint32_t vert_step;
uint32_t horiz_phase;
uint32_t reservedD;
uint32_t vert_phase[2];
uint32_t reservedE[88];
uint32_t horiz_coeff[16];
} regs;
} de_scaler_t;
#define DE_BASE ((de_t *)0x5000000)
#define DE_MIXER0 ((de_mixer_t *)0x5100000)
#define DE_BLD0 ((de_blender_t *)0x5101000)
#define DE_UI_CH1 ((de_ui_t *)0x5103000)
#define DE_SCALER ((de_scaler_t *)0x5140000)
_Static_assert(&(DE_BASE->regs.de2tcon_mux) == (uint32_t *)0x5000010, "de de2tcon_mux reg must be at address 0x5000010");
_Static_assert(&(DE_MIXER0->regs.glb_dbuffer) == (uint32_t *)0x5100008, "de mixer0 glb_dbuffer reg must be at address 0x5100008");
_Static_assert(&(DE_BLD0->regs.pipe[1].offset) == (uint32_t *)0x510101c, "de blender0 pipe[1] offset reg must be at address 0x510101c");
_Static_assert(&(DE_UI_CH1->regs.top_haddr) == (uint32_t *)0x5103080, "de ui ch1 topaddr reg must be at address 0x5103080");
_Static_assert(&(DE_SCALER->regs.horiz_step) == (uint32_t *)0x5140088, "de scaler horiz step reg must be at address 0x5140088");
static struct {
volatile de_t *de;
volatile de_mixer_t * de_mixer0;
volatile de_blender_t * de_bld0;
volatile de_ui_t * de_ui_ch1;
volatile de_scaler_t * de_scaler;
} const module = {
.de = DE_BASE,
.de_mixer0 = DE_MIXER0,
.de_bld0 = DE_BLD0,
.de_ui_ch1 = DE_UI_CH1,
.de_scaler = DE_SCALER,
};
static void de_config_mixer0(de_size_t full_screen);
static void de_config_blender0(de_size_t full_screen);
static void de_config_ui_ch1(de_size_t fb_size, de_size_t full_screen);
static void de_config_ui_scaler(de_size_t fb_size, de_size_t full_screen);
// Simplest possible init of DE2 to config for dispay of single framebuffer
// Key references:
// DisplayEngine 2.0 spec https://linux-sunxi.org/images/7/7b/Allwinner_DE2.0_Spec_V1.0.pdf
// https://linux-sunxi.org/DE2_Register_Guide
void de_init(int fb_width, int fb_height, int screen_width, int screen_height) {
// top-level reset, ungate clocks
module.de->regs.ahb_reset = module.de->regs.sclk_gate = module.de->regs.hclk_gate = 1; // 1 to ungate mixer0
if (fb_width > screen_width || fb_height > screen_height)
error("de_init(): requested framebuffer size does not fit on screen");
// de_size_t registers are slightly wacky: actual width/height = (stored value + 1)
de_size_t full_screen = {.width= screen_width-1, .height= screen_height-1};
de_size_t fb_size = {.width= fb_width-1, .height= fb_height-1};
de_config_mixer0(full_screen);
de_config_blender0(full_screen);
de_config_ui_ch1(fb_size, full_screen);
}
void de_set_active_framebuffer(void *addr) {
module.de_ui_ch1->regs.layer[0].attr_ctrl &= ~(1 << 4); // disable fill
uintptr_t full_address = (uintptr_t)addr;
uint32_t low_addr = full_address & 0xffffffff;
assert((uintptr_t)low_addr == full_address); // confirm address fits in 32 bits
module.de_ui_ch1->regs.layer[0].top_laddr = low_addr;
timer_delay_ms(10); // resync delay
}
// DE Mixer block is a pipeline: framebuffer(s) -> overlay channel(s) -> (optional scaler) -> blender -> output to TCON
// Mixer-0 more full featured (1 video channel, 3 UI overlay)
// Mixer-1 only has 1 video + 1 UI
static void de_config_mixer0(de_size_t full_screen) {
module.de_mixer0->regs.glb_ctl = 1; // enable mixer 0
module.de_mixer0->regs.glb_size = full_screen;
}
// DE Blender pairwise composites 2 overlay channels together. Three separate blenders allow blending 4 channels.
// We use only blender 0 with single UI channel.
static void de_config_blender0(de_size_t full_screen) {
// #warning TODO TEMPORARY: setting blender background to magenta
// module.de_bld0->regs.background_color = 0xff00ff;
module.de_bld0->regs.output_size = full_screen;
uint32_t pipe_index = 1; // use pipe index 1, route for first ui layer (ch1)
module.de_bld0->regs.pipe_ctrl = ((1 << pipe_index) << 8); // enable pipe index 1
module.de_bld0->regs.pipe[pipe_index].input_size = full_screen;
module.de_bld0->regs.route = 0x3210; // channels 0-3, each channel routed to pipe at corresponding index
}
enum format_t
{ ARGB_8888 = 0x0, ABGR_8888 = 0x1, RGBA_8888 = 0x2, BGRA_8888 = 0x3,
XRGB_8888 = 0x4, XBGR_8888 = 0x5, RGBX_8888 = 0x6, BGRX_8888 = 0x7,
RGB_888 = 0x8, BGR_888 = 0x9, RGB_565 = 0xa, BGR_565 = 0xb,
ARGB_4444 = 0xc, ABGR_4444 = 0xd, RGBA_4444 = 0xe, BGRA_4444 = 0xf };
// DE UI Overlay represents a single framebuffer. Mixer0 has three UI overlay channels, we use only channel 1.
// An optional UI scaler can be used to up/downscale from framebuffer input on route to blender.
static void de_config_ui_ch1(de_size_t fb_size, de_size_t full_screen) {
// default alpha @[24], top-addr-only @[23] no premul @[16] format @[8] enable fill @[4] use global alpha @[1] enable @[0]
uint32_t features = (0xff << 24) | (0 << 23) | (0 << 16) | (XRGB_8888 << 8) | (1 << 4) | (1 << 1) | (1 << 0);
module.de_ui_ch1->regs.layer[0].attr_ctrl = features;
module.de_ui_ch1->regs.layer[0].size = fb_size;
module.de_ui_ch1->regs.layer[0].offset = 0; // position @ top left corner of output
module.de_ui_ch1->regs.layer[0].pitch_nbytes = (fb_size.width+1) * 4; // 4 bytes (32 bits) per pixel
module.de_ui_ch1->regs.overlay_size = fb_size;
// #warning TODO TEMPORARY: setting ui layer background to yellow
// module.de_ui_ch1->regs.layer[0].fill_color = 0xffff00;
de_config_ui_scaler(fb_size, full_screen); // will center on screen and apply scaler if necessary
}
// From DE2 docs:
// UI scaler supports 1/16x downscale to 32x upscale
// horizontal scaler is 16-phase 4-tap anti-aliasing filter
// vertical scaler is 16-phase linear filter
// return ratio in/out as count of 32nds (round up)
static int scale_factor(int in, int out) {
return ((in * 32) + out - 1)/out; // round up to nearest 32nd (minimum scale for DE2)
}
static int compute_scale_step(de_size_t fb_size, de_size_t full_screen, de_size_t *p_scaled_size, unsigned int *p_offset) {
int screen_width = full_screen.width+1;
int screen_height = full_screen.height+1;
int fb_width = fb_size.width+1;
int fb_height = fb_size.height+1;
int horiz_f = scale_factor(fb_width, screen_width);
int vert_f = scale_factor(fb_height, screen_height);
// force square pixels, use larger of two scale factors
int scale_f = (horiz_f > vert_f) ? horiz_f : vert_f; // in 32nds
// JDZ: for now, only apply scaling if at least 2x (scale to partial pixels has poor result)
// otherwise do not scale, center orig rect on screen as-is
// revisit later, consider whether to use video scaler instead
if (scale_f > 16) scale_f = 32;
int output_width = (fb_width*32)/scale_f;
int output_height = (fb_height*32)/scale_f;
*p_scaled_size = (de_size_t){.width= output_width - 1, .height= output_height - 1};
int margin_x = (screen_width - output_width)/2;
int margin_y = (screen_height - output_height)/2;
*p_offset = (margin_y << 16) | margin_x; // position in center
return scale_f << 15; // scale factor stored as x.15 fixed point (only 5 bits of fraction used tho')
}
// DE UI Scaler used to upscale a framebuffer before feeding into blender pipe
// scaler used when frame buffer size is < 1/2 of screen size
static void de_config_ui_scaler(de_size_t fb_size, de_size_t full_screen) {
de_size_t scaler_output_size;
uint32_t center_offset;
int step = compute_scale_step(fb_size, full_screen, &scaler_output_size, ¢er_offset);
module.de_bld0->regs.pipe[1].offset = center_offset; // position in center
if (step == 0x100000) {
module.de_scaler->regs.ctrl = 0; // disable scaler
module.de_bld0->regs.pipe[1].input_size = fb_size; // ui layer is direct input to blender pipe 1
} else {
module.de_scaler->regs.ctrl = 1; // enable scaler
module.de_scaler->regs.horiz_phase = 1 << 20; // correct for first pixel
module.de_scaler->regs.horiz_step = module.de_scaler->regs.vert_step = step;
module.de_scaler->regs.input_size = fb_size; // ui layer is input to scaler
module.de_scaler->regs.output_size = scaler_output_size;
module.de_bld0->regs.pipe[1].input_size = scaler_output_size; // scaler output is input to blender pipe 1
// UI scaler works line by line, array of coeff controls of 4-tap blend within line (horiz)
// below I am setting coeffs to 0,0,0,64 to replicate right pixel of each 4-tap (hard cutoff)
// note that vert is fixed linear scale of taps without configurable control (fuzzy instead of crisp...)
// if this ends being unacceptable, could switch to video scaler which has controls for both horiz and vert
for (int i = 0; i < 16; i++) module.de_scaler->regs.horiz_coeff[i] = 0x40;
module.de_scaler->regs.ctrl |= (1 << 4); // apply coefficients
}
}
/*
* Access to font pixel data stored as a bitmap.
*
* Author: Philip Levis <pal@cs.stanford.edu>
* Last modified: 3/17/16
*/
#include "font.h"
static const struct font {
unsigned char first_char, last_char;
int glyph_width, glyph_height;
uint8_t pixel_data[];
} apple_II_font;
static struct {
const struct font *font;
} const module = {
.font = &apple_II_font,
};
int font_get_glyph_height(void) {
return module.font->glyph_height;
}
int font_get_glyph_width(void) {
return module.font->glyph_width;
}
int font_get_glyph_size(void) {
return font_get_glyph_width() * font_get_glyph_height();
}
/*
* Extract glyph pixels for requested character from font bitmap.
* Read bits from font bitmap and store into array of bytes, one byte per pixel.
* Use 0xff byte for 'on' pixel, 0x0 for 'off' pixel.
*/
bool font_get_glyph(char ch, uint8_t buf[], size_t buflen) {
if ((ch != ' ' && (ch < module.font->first_char || ch > module.font->last_char)) || (buflen != font_get_glyph_size())) {
return false;
}
if (ch == ' ') { // Handle space as special case, return all-off image
for (int i = 0; i < buflen; i++) {
buf[i] = 0;
}
} else {
int index = 0;
int nbits_in_row = (module.font->last_char - module.font->first_char + 1) * font_get_glyph_width();
int x_offset = (ch - module.font->first_char);
for (int y = 0; y < font_get_glyph_height(); y++) {
for (int x = 0; x < font_get_glyph_width(); x++) {
int bit_index = y * nbits_in_row + x_offset * font_get_glyph_width() + x;
int bit_start = bit_index / 8;
int bit_offset = bit_index % 8;
// extract single bit for this pixel from bitmap
int val = module.font->pixel_data[bit_start] & (1 << (7 - bit_offset));
// use 0xff for on pixel, 0x0 for off pixel
buf[index++] = val != 0 ? 0xFF : 0x00;
}
}
}
return true;
}
/*
* Apple II font stored as a bitmap.
* Each character is 14 bits wide and 16 bits tall (long story
* about some C conversion).
*
* Generated from a screenshot of the original font, turned into
* a 32-bit color C structure with GIMP, then turned into a bitmap
* C structure with a simple C program.
*/
static const struct font apple_II_font = {
.first_char = 0x21, .last_char = 0x7F,
.glyph_width = 14, .glyph_height = 16,
.pixel_data = {
0x03, 0x00, 0x33, 0x00, 0xcc, 0x00, 0xc0, 0x3c,
0x00, 0x30, 0x00, 0x30, 0x00, 0xc0, 0x03, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0xf0, 0x03, 0x00, 0x3f, 0x03,
0xff, 0x00, 0x30, 0x3f, 0xf0, 0x0f, 0xc3, 0xff,
0x03, 0xf0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x30, 0x00, 0x00, 0x30, 0x00, 0xfc, 0x03, 0xf0,
0x03, 0x00, 0xff, 0x00, 0xfc, 0x0f, 0xf0, 0x3f,
0xf0, 0xff, 0xc0, 0xff, 0x0c, 0x0c, 0x0f, 0xc0,
0x00, 0xc3, 0x03, 0x0c, 0x00, 0x30, 0x30, 0xc0,
0xc0, 0xfc, 0x0f, 0xf0, 0x0f, 0xc0, 0xff, 0x00,
0xfc, 0x0f, 0xfc, 0x30, 0x30, 0xc0, 0xc3, 0x03,
0x0c, 0x0c, 0x30, 0x30, 0xff, 0xc3, 0xff, 0x00,
0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x03, 0x00,
0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x0c, 0x00,
0x00, 0x0f, 0x00, 0x00, 0x0c, 0x00, 0x03, 0x00,
0x03, 0x03, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00,
0xc0, 0x3f, 0x00, 0x3c, 0xc0, 0x00, 0x00, 0xc0,
0x0c, 0xc0, 0x33, 0x00, 0x30, 0x0f, 0x00, 0x0c,
0x00, 0x0c, 0x00, 0x30, 0x00, 0xc0, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xfc, 0x00, 0xc0, 0x0f, 0xc0, 0xff, 0xc0,
0x0c, 0x0f, 0xfc, 0x03, 0xf0, 0xff, 0xc0, 0xfc,
0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00,
0x00, 0x0c, 0x00, 0x3f, 0x00, 0xfc, 0x00, 0xc0,
0x3f, 0xc0, 0x3f, 0x03, 0xfc, 0x0f, 0xfc, 0x3f,
0xf0, 0x3f, 0xc3, 0x03, 0x03, 0xf0, 0x00, 0x30,
0xc0, 0xc3, 0x00, 0x0c, 0x0c, 0x30, 0x30, 0x3f,
0x03, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0x3f, 0x03,
0xff, 0x0c, 0x0c, 0x30, 0x30, 0xc0, 0xc3, 0x03,
0x0c, 0x0c, 0x3f, 0xf0, 0xff, 0xc0, 0x00, 0x0f,
0xfc, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
0x30, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
0xc0, 0x00, 0x03, 0x00, 0x00, 0xc0, 0x00, 0xc0,
0xc0, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x30, 0x0f,
0xc0, 0x0f, 0x30, 0x00, 0x00, 0x30, 0x03, 0x30,
0x0c, 0xc0, 0x3f, 0xc3, 0xc3, 0x0c, 0xc0, 0x03,
0x00, 0x30, 0x00, 0x0c, 0x0c, 0xcc, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xc0,
0xc0, 0xf0, 0x0c, 0x0c, 0x00, 0x30, 0x0f, 0x03,
0x00, 0x03, 0x00, 0x00, 0x30, 0xc0, 0xc3, 0x03,
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0xc0, 0x30, 0x30, 0xc0, 0xc0, 0xcc, 0x0c, 0x0c,
0x30, 0x30, 0xc0, 0xc3, 0x00, 0x0c, 0x00, 0x30,
0x00, 0xc0, 0xc0, 0x30, 0x00, 0x0c, 0x30, 0xc0,
0xc0, 0x03, 0xcf, 0x0c, 0x0c, 0x30, 0x30, 0xc0,
0xc3, 0x03, 0x0c, 0x0c, 0x30, 0x30, 0x0c, 0x03,
0x03, 0x0c, 0x0c, 0x30, 0x30, 0xc0, 0xc3, 0x03,
0x00, 0x0c, 0x3c, 0x00, 0xc0, 0x00, 0x0f, 0x00,
0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00,
0x00, 0x00, 0x00, 0xc0, 0x00, 0x03, 0x0c, 0x00,
0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0f, 0x00, 0x0c, 0x00, 0x3c, 0x0c,
0xf0, 0x33, 0x30, 0x0c, 0x00, 0xcc, 0x03, 0x30,
0x0f, 0xf0, 0xf0, 0xc3, 0x30, 0x00, 0xc0, 0x0c,
0x00, 0x03, 0x03, 0x33, 0x00, 0xc0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x30, 0x3c,
0x03, 0x03, 0x00, 0x0c, 0x03, 0xc0, 0xc0, 0x00,
0xc0, 0x00, 0x0c, 0x30, 0x30, 0xc0, 0xc0, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x30, 0x0c,
0x0c, 0x30, 0x30, 0x33, 0x03, 0x03, 0x0c, 0x0c,
0x30, 0x30, 0xc0, 0x03, 0x00, 0x0c, 0x00, 0x30,
0x30, 0x0c, 0x00, 0x03, 0x0c, 0x30, 0x30, 0x00,
0xf3, 0xc3, 0x03, 0x0c, 0x0c, 0x30, 0x30, 0xc0,
0xc3, 0x03, 0x0c, 0x0c, 0x03, 0x00, 0xc0, 0xc3,
0x03, 0x0c, 0x0c, 0x30, 0x30, 0xc0, 0xc0, 0x03,
0x0f, 0x00, 0x30, 0x00, 0x03, 0xc0, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x30, 0x00, 0x00, 0xc3, 0x00, 0x00, 0x30,
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0xc0, 0x03, 0x00, 0x0f, 0x03, 0x3c, 0x0c,
0xcc, 0x03, 0x00, 0x33, 0x03, 0xff, 0x0c, 0xc0,
0x00, 0xc0, 0xcc, 0x00, 0x30, 0x0c, 0x00, 0x00,
0x30, 0x3f, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0c, 0x0c, 0x3c, 0x03, 0x00, 0x00,
0xc0, 0x0c, 0x03, 0x30, 0x3f, 0xc0, 0xc0, 0x00,
0x0c, 0x0c, 0x0c, 0x30, 0x30, 0x0c, 0x00, 0x30,
0x03, 0x00, 0x3f, 0xf0, 0x03, 0x00, 0x0c, 0x0c,
0xcc, 0x30, 0x30, 0xc0, 0xc3, 0x00, 0x0c, 0x0c,
0x30, 0x00, 0xc0, 0x03, 0x00, 0x0c, 0x0c, 0x03,
0x00, 0x00, 0xc3, 0x30, 0x0c, 0x00, 0x33, 0x30,
0xf0, 0xc3, 0x03, 0x0c, 0x0c, 0x30, 0x30, 0xc0,
0xc3, 0x00, 0x00, 0xc0, 0x30, 0x30, 0xc0, 0xc3,
0x03, 0x03, 0x30, 0x0c, 0xc0, 0x03, 0x03, 0xc0,
0x03, 0x00, 0x00, 0xf0, 0x0c, 0x00, 0x00, 0x00,
0x30, 0x0f, 0xc0, 0xff, 0x00, 0xff, 0x03, 0xfc,
0x0f, 0xc0, 0x30, 0x00, 0xfc, 0x0f, 0xf0, 0x0f,
0x00, 0x0f, 0x03, 0x03, 0x00, 0xc0, 0x3c, 0xf0,
0xff, 0x00, 0xfc, 0x0f, 0xf0, 0x0f, 0xf0, 0xcf,
0xc0, 0xff, 0x0f, 0xf0, 0x30, 0x30, 0xc0, 0xc3,
0x03, 0x0c, 0x0c, 0x30, 0x30, 0xff, 0xc0, 0xf0,
0x00, 0xc0, 0x03, 0xc0, 0x00, 0x00, 0xcc, 0x00,
0xc0, 0x0c, 0xc0, 0xff, 0xc3, 0x30, 0x00, 0x30,
0x33, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x0c, 0x0f,
0xc0, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x03, 0x0f, 0x00, 0xc0, 0x00, 0x30, 0x03,
0x00, 0xcc, 0x0f, 0xf0, 0x30, 0x00, 0x03, 0x03,
0x03, 0x0c, 0x0c, 0x03, 0x00, 0x0c, 0x00, 0xc0,
0x0f, 0xfc, 0x00, 0xc0, 0x03, 0x03, 0x33, 0x0c,
0x0c, 0x30, 0x30, 0xc0, 0x03, 0x03, 0x0c, 0x00,
0x30, 0x00, 0xc0, 0x03, 0x03, 0x00, 0xc0, 0x00,
0x30, 0xcc, 0x03, 0x00, 0x0c, 0xcc, 0x3c, 0x30,
0xc0, 0xc3, 0x03, 0x0c, 0x0c, 0x30, 0x30, 0xc0,
0x00, 0x30, 0x0c, 0x0c, 0x30, 0x30, 0xc0, 0xc0,
0xcc, 0x03, 0x30, 0x00, 0xc0, 0xf0, 0x00, 0xc0,
0x00, 0x3c, 0x03, 0x00, 0x00, 0x00, 0x0c, 0x03,
0xf0, 0x3f, 0xc0, 0x3f, 0xc0, 0xff, 0x03, 0xf0,
0x0c, 0x00, 0x3f, 0x03, 0xfc, 0x03, 0xc0, 0x03,
0xc0, 0xc0, 0xc0, 0x30, 0x0f, 0x3c, 0x3f, 0xc0,
0x3f, 0x03, 0xfc, 0x03, 0xfc, 0x33, 0xf0, 0x3f,
0xc3, 0xfc, 0x0c, 0x0c, 0x30, 0x30, 0xc0, 0xc3,
0x03, 0x0c, 0x0c, 0x3f, 0xf0, 0x3c, 0x00, 0x30,
0x00, 0xf0, 0x00, 0x00, 0x33, 0x00, 0x30, 0x00,
0x00, 0x0c, 0xc0, 0x3f, 0x00, 0x30, 0x03, 0x00,
0x00, 0x00, 0xc0, 0x00, 0x03, 0x00, 0xc0, 0x3f,
0xf0, 0x00, 0x03, 0xff, 0x00, 0x00, 0x03, 0x00,
0xcc, 0xc0, 0x30, 0x00, 0xf0, 0x03, 0xc0, 0xc3,
0x00, 0x03, 0x0f, 0xf0, 0x03, 0x00, 0x3f, 0x00,
0xff, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
0x00, 0x0c, 0x03, 0x00, 0xcf, 0xc3, 0x03, 0x0f,
0xf0, 0x30, 0x00, 0xc0, 0xc3, 0xfc, 0x0f, 0xf0,
0x30, 0x00, 0xff, 0xc0, 0x30, 0x00, 0x0c, 0x3c,
0x00, 0xc0, 0x03, 0x33, 0x0c, 0xcc, 0x30, 0x30,
0xff, 0x03, 0x03, 0x0f, 0xf0, 0x0f, 0xc0, 0x0c,
0x03, 0x03, 0x0c, 0x0c, 0x33, 0x30, 0x0c, 0x00,
0x30, 0x00, 0xc0, 0x3c, 0x00, 0x0c, 0x00, 0x0f,
0x03, 0x30, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0c,
0x0c, 0x30, 0x00, 0xc0, 0xc3, 0x03, 0x0f, 0xf0,
0x30, 0x30, 0xc0, 0xc0, 0x30, 0x00, 0x30, 0x30,
0xc0, 0x0c, 0x03, 0x33, 0x0c, 0x0c, 0x30, 0x30,
0xc0, 0xc3, 0x03, 0x0f, 0x00, 0x30, 0x00, 0x30,
0x03, 0x03, 0x0c, 0x0c, 0x30, 0x30, 0x33, 0x03,
0x03, 0x00, 0x30, 0x3c, 0x00, 0x0c, 0x00, 0x0f,
0x00, 0x00, 0x33, 0x30, 0x0c, 0x00, 0x00, 0x03,
0x30, 0x0f, 0xc0, 0x0c, 0x00, 0xc0, 0x00, 0x00,
0x30, 0x00, 0x00, 0xc0, 0x30, 0x0f, 0xfc, 0x00,
0x00, 0xff, 0xc0, 0x00, 0x00, 0xc0, 0x33, 0x30,
0x0c, 0x00, 0x3c, 0x00, 0xf0, 0x30, 0xc0, 0x00,
0xc3, 0xfc, 0x00, 0xc0, 0x0f, 0xc0, 0x3f, 0xc0,
0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x03,
0x00, 0xc0, 0x33, 0xf0, 0xc0, 0xc3, 0xfc, 0x0c,
0x00, 0x30, 0x30, 0xff, 0x03, 0xfc, 0x0c, 0x00,
0x3f, 0xf0, 0x0c, 0x00, 0x03, 0x0f, 0x00, 0x30,
0x00, 0xcc, 0xc3, 0x33, 0x0c, 0x0c, 0x3f, 0xc0,
0xc0, 0xc3, 0xfc, 0x03, 0xf0, 0x03, 0x00, 0xc0,
0xc3, 0x03, 0x0c, 0xcc, 0x03, 0x00, 0x0c, 0x00,
0x30, 0x0f, 0x00, 0x03, 0x00, 0x03, 0xc0, 0xcc,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x03, 0x0c,
0x00, 0x30, 0x30, 0xc0, 0xc3, 0xfc, 0x0c, 0x0c,
0x30, 0x30, 0x0c, 0x00, 0x0c, 0x0c, 0x30, 0x03,
0x00, 0xcc, 0xc3, 0x03, 0x0c, 0x0c, 0x30, 0x30,
0xc0, 0xc3, 0xc0, 0x0c, 0x00, 0x0c, 0x00, 0xc0,
0xc3, 0x03, 0x0c, 0x0c, 0x0c, 0xc0, 0xc0, 0xc0,
0x0c, 0x0f, 0x00, 0x03, 0x00, 0x03, 0xc0, 0x00,
0x0c, 0xcc, 0x03, 0x00, 0x00, 0x03, 0xff, 0x00,
0xcc, 0x0c, 0x00, 0xcc, 0xc0, 0x00, 0x0c, 0x00,
0x00, 0x30, 0x3f, 0x00, 0x30, 0x00, 0xc0, 0x00,
0x00, 0x00, 0x00, 0xc0, 0x0f, 0x0c, 0x03, 0x00,
0x30, 0x00, 0x03, 0x0f, 0xfc, 0x00, 0x30, 0xc0,
0xc0, 0xc0, 0x0c, 0x0c, 0x00, 0x30, 0x0c, 0x00,
0x30, 0x03, 0x00, 0x3f, 0xf0, 0x03, 0x00, 0x30,
0x0c, 0xf0, 0x3f, 0xf0, 0xc0, 0xc3, 0x00, 0x0c,
0x0c, 0x30, 0x00, 0xc0, 0x03, 0x0f, 0x0c, 0x0c,
0x03, 0x00, 0x00, 0xc3, 0x30, 0x0c, 0x00, 0x30,
0x30, 0xc3, 0xc3, 0x03, 0x0c, 0x00, 0x33, 0x30,
0xcc, 0x00, 0x03, 0x00, 0xc0, 0x30, 0x30, 0xc0,
0xc3, 0x33, 0x03, 0x30, 0x03, 0x00, 0x30, 0x03,
0xc0, 0x00, 0x30, 0x00, 0xf0, 0xc0, 0xc0, 0x00,
0x00, 0x00, 0x0f, 0xf0, 0xc0, 0xc3, 0x00, 0x0c,
0x0c, 0x3f, 0xf0, 0x30, 0x03, 0x03, 0x0c, 0x0c,
0x03, 0x00, 0x03, 0x03, 0xf0, 0x00, 0xc0, 0x33,
0x30, 0xc0, 0xc3, 0x03, 0x0c, 0x0c, 0x30, 0x30,
0xc0, 0x00, 0xfc, 0x03, 0x00, 0x30, 0x30, 0xc0,
0xc3, 0x33, 0x00, 0xc0, 0x30, 0x30, 0x0c, 0x00,
0xf0, 0x00, 0xc0, 0x03, 0xc0, 0x00, 0x00, 0xcc,
0x00, 0xc0, 0x00, 0x00, 0xff, 0xc0, 0x33, 0x03,
0x00, 0x33, 0x30, 0x00, 0x03, 0x00, 0x00, 0x0c,
0x0f, 0xc0, 0x0c, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x30, 0x03, 0xc3, 0x00, 0xc0, 0x0c, 0x00,
0x00, 0xc3, 0xff, 0x00, 0x0c, 0x30, 0x30, 0x30,
0x03, 0x03, 0x00, 0x0c, 0x03, 0x00, 0x0c, 0x00,
0xc0, 0x0f, 0xfc, 0x00, 0xc0, 0x0c, 0x03, 0x3c,
0x0f, 0xfc, 0x30, 0x30, 0xc0, 0x03, 0x03, 0x0c,
0x00, 0x30, 0x00, 0xc3, 0xc3, 0x03, 0x00, 0xc0,
0x00, 0x30, 0xcc, 0x03, 0x00, 0x0c, 0x0c, 0x30,
0xf0, 0xc0, 0xc3, 0x00, 0x0c, 0xcc, 0x33, 0x00,
0x00, 0xc0, 0x30, 0x0c, 0x0c, 0x30, 0x30, 0xcc,
0xc0, 0xcc, 0x00, 0xc0, 0x0c, 0x00, 0xf0, 0x00,
0x0c, 0x00, 0x3c, 0x30, 0x30, 0x00, 0x00, 0x00,
0x03, 0xfc, 0x30, 0x30, 0xc0, 0x03, 0x03, 0x0f,
0xfc, 0x0c, 0x00, 0xc0, 0xc3, 0x03, 0x00, 0xc0,
0x00, 0xc0, 0xfc, 0x00, 0x30, 0x0c, 0xcc, 0x30,
0x30, 0xc0, 0xc3, 0x03, 0x0c, 0x0c, 0x30, 0x00,
0x3f, 0x00, 0xc0, 0x0c, 0x0c, 0x30, 0x30, 0xcc,
0xc0, 0x30, 0x0c, 0x0c, 0x03, 0x00, 0x3c, 0x00,
0x30, 0x00, 0xf0, 0x00, 0x00, 0x33, 0x00, 0x00,
0x00, 0x00, 0x0c, 0xc0, 0xff, 0x03, 0x0f, 0x0c,
0x30, 0x00, 0x00, 0x30, 0x00, 0x0c, 0x0c, 0xcc,
0x03, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x30,
0x00, 0xc0, 0xc0, 0x30, 0x0c, 0x00, 0x30, 0x30,
0x03, 0x03, 0x03, 0x0c, 0x0c, 0x0c, 0x00, 0xc0,
0xc0, 0x0c, 0x00, 0x00, 0x03, 0x00, 0x0c, 0x00,
0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x03, 0x03,
0x0c, 0x0c, 0x30, 0x30, 0xc0, 0xc3, 0x00, 0x0c,
0x00, 0x30, 0x30, 0xc0, 0xc0, 0x30, 0x0c, 0x0c,
0x30, 0xc0, 0xc0, 0x03, 0x03, 0x0c, 0x0c, 0x30,
0x30, 0xc0, 0x03, 0x0c, 0x0c, 0x30, 0x30, 0x30,
0x0c, 0x03, 0x03, 0x03, 0x30, 0x3c, 0xf0, 0xc0,
0xc0, 0x30, 0x0c, 0x00, 0x3c, 0x00, 0x00, 0xc0,
0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
0x0c, 0x0c, 0x30, 0x00, 0xc0, 0xc3, 0x00, 0x03,
0x00, 0x0f, 0xf0, 0xc0, 0xc0, 0x30, 0x00, 0x30,
0x30, 0xc0, 0x0c, 0x03, 0x33, 0x0c, 0x0c, 0x30,
0x30, 0xff, 0x00, 0xff, 0x0c, 0x00, 0x00, 0x30,
0x30, 0xc3, 0x0f, 0x03, 0x30, 0x33, 0x30, 0x33,
0x00, 0xff, 0x03, 0x00, 0x0f, 0x00, 0x0c, 0x00,
0x3c, 0x00, 0x00, 0x33, 0x30, 0x00, 0x00, 0x00,
0x03, 0x30, 0x3f, 0xc0, 0xc3, 0xc3, 0x0c, 0x00,
0x00, 0x0c, 0x00, 0x03, 0x03, 0x33, 0x00, 0xc0,
0x03, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x30,
0x30, 0x0c, 0x03, 0x00, 0x0c, 0x0c, 0x00, 0xc0,
0xc0, 0xc3, 0x03, 0x03, 0x00, 0x30, 0x30, 0x03,
0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00,
0x30, 0x00, 0x00, 0x30, 0x00, 0xc0, 0xc3, 0x03,
0x0c, 0x0c, 0x30, 0x30, 0xc0, 0x03, 0x00, 0x0c,
0x0c, 0x30, 0x30, 0x0c, 0x03, 0x03, 0x0c, 0x30,
0x30, 0x00, 0xc0, 0xc3, 0x03, 0x0c, 0x0c, 0x30,
0x00, 0xc3, 0x03, 0x0c, 0x0c, 0x0c, 0x03, 0x00,
0xc0, 0xc0, 0xcc, 0x0f, 0x3c, 0x30, 0x30, 0x0c,
0x03, 0x00, 0x0f, 0x00, 0x00, 0x30, 0x03, 0xc0,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc3, 0x03,
0x0c, 0x00, 0x30, 0x30, 0xc0, 0x00, 0xc0, 0x03,
0xfc, 0x30, 0x30, 0x0c, 0x00, 0x0c, 0x0c, 0x30,
0x03, 0x00, 0xcc, 0xc3, 0x03, 0x0c, 0x0c, 0x3f,
0xc0, 0x3f, 0xc3, 0x00, 0x00, 0x0c, 0x0c, 0x30,
0xc3, 0xc0, 0xcc, 0x0c, 0xcc, 0x0c, 0xc0, 0x3f,
0xc0, 0xc0, 0x03, 0xc0, 0x03, 0x00, 0x0f, 0x00,
0x00, 0x0c, 0xcc, 0x03, 0x00, 0x00, 0x00, 0xcc,
0x00, 0xc0, 0x00, 0xf0, 0x3c, 0xc0, 0x00, 0x00,
0xc0, 0x03, 0x00, 0x0c, 0x00, 0x00, 0x03, 0x00,
0x00, 0x00, 0x0c, 0x00, 0x00, 0x03, 0xf0, 0x0f,
0xc0, 0xff, 0xc0, 0xfc, 0x00, 0x30, 0x0f, 0xc0,
0x3f, 0x00, 0xc0, 0x03, 0xf0, 0x3f, 0x00, 0x00,
0x00, 0xc0, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00,
0x30, 0x03, 0xfc, 0x30, 0x30, 0xff, 0x00, 0xfc,
0x0f, 0xf0, 0x3f, 0xf0, 0xc0, 0x00, 0xff, 0x0c,
0x0c, 0x0f, 0xc0, 0x3f, 0x03, 0x03, 0x0f, 0xfc,
0x30, 0x30, 0xc0, 0xc0, 0xfc, 0x0c, 0x00, 0x0f,
0x30, 0xc0, 0xc0, 0xfc, 0x00, 0xc0, 0x0f, 0xc0,
0x0c, 0x03, 0x03, 0x0c, 0x0c, 0x03, 0x00, 0xff,
0xc3, 0xff, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0f, 0xf0, 0xff, 0x00, 0xff,
0x03, 0xfc, 0x0f, 0xf0, 0x30, 0x00, 0x03, 0x0c,
0x0c, 0x0f, 0xc0, 0xc3, 0x03, 0x03, 0x03, 0xf0,
0x30, 0x30, 0xc0, 0xc0, 0xfc, 0x0c, 0x00, 0x00,
0x30, 0xc0, 0x03, 0xfc, 0x00, 0xf0, 0x0f, 0x30,
0x0c, 0x03, 0xcf, 0x0c, 0x0c, 0x00, 0x30, 0xff,
0xc0, 0x3f, 0x00, 0xc0, 0x3f, 0x00, 0x00, 0x00,
0x00, 0x00, 0xc0, 0x00, 0x00, 0x33, 0x00, 0x30,
0x00, 0x3c, 0x0f, 0x30, 0x00, 0x00, 0x30, 0x00,
0xc0, 0x03, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0xfc, 0x03, 0xf0, 0x3f,
0xf0, 0x3f, 0x00, 0x0c, 0x03, 0xf0, 0x0f, 0xc0,
0x30, 0x00, 0xfc, 0x0f, 0xc0, 0x00, 0x00, 0x30,
0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00,
0xff, 0x0c, 0x0c, 0x3f, 0xc0, 0x3f, 0x03, 0xfc,
0x0f, 0xfc, 0x30, 0x00, 0x3f, 0xc3, 0x03, 0x03,
0xf0, 0x0f, 0xc0, 0xc0, 0xc3, 0xff, 0x0c, 0x0c,
0x30, 0x30, 0x3f, 0x03, 0x00, 0x03, 0xcc, 0x30,
0x30, 0x3f, 0x00, 0x30, 0x03, 0xf0, 0x03, 0x00,
0xc0, 0xc3, 0x03, 0x00, 0xc0, 0x3f, 0xf0, 0xff,
0xc0, 0x00, 0x0f, 0xfc, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xfc, 0x3f, 0xc0, 0x3f, 0xc0, 0xff,
0x03, 0xfc, 0x0c, 0x00, 0x00, 0xc3, 0x03, 0x03,
0xf0, 0x30, 0xc0, 0xc0, 0xc0, 0xfc, 0x0c, 0x0c,
0x30, 0x30, 0x3f, 0x03, 0x00, 0x00, 0x0c, 0x30,
0x00, 0xff, 0x00, 0x3c, 0x03, 0xcc, 0x03, 0x00,
0xf3, 0xc3, 0x03, 0x00, 0x0c, 0x3f, 0xf0, 0x0f,
0xc0, 0x30, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x03,
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xc0, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3f, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
}
};
/*
* GPIO interrupt handling
*
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
* Updated Thu Feb 15 12:28:12 PST 2024
*/
#include "gpio_interrupt.h"
#include "_gpio_common.h"
#include "assert.h"
#include "gpio_extra.h"
#include "interrupts.h"
#include <stddef.h>
typedef union {
struct {
uint32_t cfg[4]; // only 1-2 in use
uint32_t ctl;
uint32_t status;
uint32_t debounce;
} regs;
uint8_t padding[0x20];
} gpio_eint_t;
#define GPIO_EINT_BASE ((gpio_eint_t *)0x2000220)
_Static_assert(&(GPIO_EINT_BASE[GROUP_C].regs.cfg[0]) == (uint32_t *)0x2000240, "PC irq cfg0 reg must be at address 0x2000240");
_Static_assert(&(GPIO_EINT_BASE[GROUP_E].regs.debounce) == (uint32_t *)0x2000298, "PE irq debounce reg must be at address 0x2000298");
typedef struct {
volatile gpio_eint_t *const eint;
const int max_pin_index;
const interrupt_source_t source;
struct {
handlerfn_t fn;
void *aux_data;
} handlers[GPIO_MAX_PIN_INDEX];
} gpio_int_group_t;
static struct {
gpio_int_group_t groups[GPIO_NGROUPS];
bool initialized;
} module = {
.groups = { {.eint= GPIO_EINT_BASE + GROUP_B, .max_pin_index= GPIO_PB_LAST_INDEX, .source= INTERRUPT_SOURCE_GPIOB},
{.eint= GPIO_EINT_BASE + GROUP_C, .max_pin_index= GPIO_PC_LAST_INDEX, .source= INTERRUPT_SOURCE_GPIOC},
{.eint= GPIO_EINT_BASE + GROUP_D, .max_pin_index= GPIO_PD_LAST_INDEX, .source= INTERRUPT_SOURCE_GPIOD},
{.eint= GPIO_EINT_BASE + GROUP_E, .max_pin_index= GPIO_PE_LAST_INDEX, .source= INTERRUPT_SOURCE_GPIOE},
{.eint= GPIO_EINT_BASE + GROUP_F, .max_pin_index= GPIO_PF_LAST_INDEX, .source= INTERRUPT_SOURCE_GPIOF},
{.eint= GPIO_EINT_BASE + GROUP_G, .max_pin_index= GPIO_PG_LAST_INDEX, .source= INTERRUPT_SOURCE_GPIOG},
},
.initialized = false,
};
static void dispatch_to_pin(void *group_num);
static gpio_int_group_t *get_int_group(gpio_id_t gpio, int *p_index) {
gpio_pin_t p = get_group_and_index(gpio);
*p_index = p.pin_index;
return &module.groups[p.group];
}
// register private function dispatch_to_pin with top-level interrupts module
// as handler for all GPIO interrupt sources, use pointer to interrupt group as aux_data
void gpio_interrupt_init(void) {
for (int i = 0; i < GPIO_NGROUPS; i++) {
interrupts_register_handler(module.groups[i].source, dispatch_to_pin, &module.groups[i]);
interrupts_enable_source(module.groups[i].source);
}
module.initialized = true;
}
void gpio_interrupt_register_handler(gpio_id_t gpio, handlerfn_t fn, void *aux_data) {
if (!module.initialized) error("gpio_interrupt_init() has not been called!\n");
assert(gpio_id_is_valid(gpio));
int pin_index;
gpio_int_group_t *gp = get_int_group(gpio, &pin_index);
gp->handlers[pin_index].fn = fn;
gp->handlers[pin_index].aux_data = aux_data;
}
// dispatch_to_pin handler receives all GPIO interrupts and performs second-level dispatch to
// per-pin handlers that have been registered with this module
static void dispatch_to_pin(void *aux_data) {
gpio_int_group_t *gp = aux_data;
// find 'on' bit in status register to determine which pin had interrupt
// use clz to count number of leading zero bits before first one bit
int pin_index = 31 - __builtin_clz(gp->eint->regs.status);
gp->handlers[pin_index].fn(gp->handlers[pin_index].aux_data);
}
static void gpio_interrupt_set_enabled(gpio_id_t gpio, bool state) {
if (!module.initialized) error("gpio_interrupt_init() has not been called!\n");
assert(gpio_id_is_valid(gpio));
int pin_index;
gpio_int_group_t *gp = get_int_group(gpio, &pin_index);
unsigned int mask = (1 << pin_index);
if (state) {
gp->eint->regs.ctl |= mask; // set enable bit
} else {
gp->eint->regs.ctl &= ~mask; // clear enable bit
}
}
void gpio_interrupt_enable(gpio_id_t gpio) {
gpio_interrupt_set_enabled(gpio, true);
}
void gpio_interrupt_disable(gpio_id_t gpio) {
gpio_interrupt_set_enabled(gpio, false);
}
void gpio_interrupt_clear(gpio_id_t gpio) {
if (!module.initialized) error("gpio_interrupt_init() has not been called!\n");
assert(gpio_id_is_valid(gpio));
int pin_index;
gpio_int_group_t *gp = get_int_group(gpio, &pin_index);
unsigned int mask = (1 << pin_index);
if ((gp->eint->regs.status & mask) != 0) { // if pending bit set for this pin
gp->eint->regs.status |= mask; // write 1 to clear
}
}
void gpio_interrupt_config(gpio_id_t gpio, gpio_event_t event, bool debounce) {
if (!module.initialized) error("gpio_interrupt_init() has not been called!\n");
assert(gpio_id_is_valid(gpio) && event <= GPIO_INTERRUPT_DOUBLE_EDGE);
int pin_index;
gpio_int_group_t *gp = get_int_group(gpio, &pin_index);
int bank = pin_index / 8;
int index = pin_index % 8;
int shift = index * 4;
unsigned int mask = ((1 << 4) - 1);
gp->eint->regs.cfg[bank] = (gp->eint->regs.cfg[bank] & ~(mask << shift)) | ((event & mask) << shift);
gpio_set_function(gpio, GPIO_FN_INTERRUPT); // change pin function to interrupt
if (debounce) { // if debounce requested
// apply 32Khz clock, predivide 2^5, will filter to ~1 event per ms
gp->eint->regs.debounce = (5 << 4) | 0;
} else {
// apply 24Mhz clock, no predivide, no filter
gp->eint->regs.debounce = (0 << 4) | 1;
}
gpio_interrupt_clear(gpio); // clear any past event, start anew with updated config
}
/*
* This module configures the HDMI hardware
* Written to drive Synopsis DesignWare HDMI TX controller using in AW D1.
* Uses TCONTV peripheral of AW D1 to stream pixels to HDMI
*
* Support for classic resolutions: 1080p, 720p, SVGA
*
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
* Updated: Feb 2024
*/
#include "hdmi.h"
#include "_hdmi_private.h"
#include "assert.h"
#include "ccu.h"
#include "printf.h"
#include <stdbool.h>
#include "timer.h"
typedef union {
struct {
uint8_t invidconf; // V and H sync polarity bits 5&6, data enble input polarity bit 4, HDMI mode bit 3
uint8_t inhactv[2]; // count of horiz active pixels
uint8_t inhblank[2]; // count of horiz blank pixels
uint8_t invactv[2]; // count of vert active lines
uint8_t invblank; // count of vert blank lines
uint8_t hsyncindelay[2]; // count of pixel clock cycles from non-active edge to last valid period
uint8_t hsyncinwidth[2]; // count of pixel clock cycles
uint8_t vsyncindelay; // count of hsync pulses from non-active edge to last valid period
uint8_t vsyncinwidth; // count of hsync pulses
uint8_t infreq[3]; // these fields used for debugging
uint8_t ctrldur; // control period minimum duration (min 12 pixel clock cycles)
uint8_t exctrldur; // extended control period minimum duration (min 32 pixel clock cycles)
uint8_t exctrlspac; // extended control period maximum spacing (max 50 msec)
uint8_t chpream[3]; // bits to fill channel data lines not used to transmit the preamble
} regs;
} hdmi_frame_composer_t;
typedef union {
struct {
uint8_t sfrdiv;
uint8_t clkdis; // clock domain disable
uint8_t fswrstz;
uint8_t opctrl;
uint8_t flowctrl;
} regs;
} hdmi_main_controller_t;
typedef union {
struct {
uint32_t setup;
uint32_t reserved[6];
uint32_t port_sel;
uint32_t gate;
} regs;
} tcon_top_t;
typedef union {
struct {
uint32_t gtcl;
uint32_t reserved[15];
uint32_t src_ctl;
uint32_t reservedB[19];
uint32_t ctl;
// next 6 regs are named basic0-basic5 in doc
struct { uint32_t height:16, width:16; } dimensions[3];
struct { uint32_t bp:16, total:16; } htiming, vtiming;
struct { uint32_t vert:16, horiz:16; } sync;
} regs;
} tcon_tv_t;
struct display_timing {
hdmi_resolution_id_t id;
struct {
uint32_t pixels, front_porch, sync_pulse, back_porch;
} horiz, vert;
struct {
long rate;
ccu_pll_id_t id;
} pll;
struct {
long rate;
ccu_parent_id_t parent;
} tcon_clk, de_clk; // TCONTV and DE clocks
};
#define HDMI_FC ((hdmi_frame_composer_t *)0x5501000)
#define HDMI_MC ((hdmi_main_controller_t *)0x5504000)
#define TCON_TOP ((tcon_top_t *)0x5460000)
#define TCON_TV ((tcon_tv_t *)0x5470000)
_Static_assert(&(HDMI_FC->regs.invblank) == (uint8_t *)0x5501007, "hdmi fc invblank reg must be at address 0x5501007");
_Static_assert(&(HDMI_MC->regs.clkdis) == (uint8_t *)0x5504001, "hdmi mc clkdis reg must be at address 0x5504001");
_Static_assert(&(TCON_TOP->regs.port_sel) == (uint32_t *)0x546001c, "tcon top port_sel reg must be at address 0x546001c");
_Static_assert(&(TCON_TV->regs.src_ctl) == (uint32_t *)0x5470040, "tcon tv src_ctl eg must be at address 0x5470040");
static struct {
volatile hdmi_frame_composer_t * const hdmi_fc;
volatile hdmi_main_controller_t * const hdmi_mc;
volatile tcon_top_t * const tcon_top;
volatile tcon_tv_t * const tcon_tv;
struct display_timing config;
} module = {
.hdmi_fc = HDMI_FC,
.hdmi_mc = HDMI_MC,
.tcon_top = TCON_TOP,
.tcon_tv = TCON_TV,
.config.id = HDMI_INVALID,
};
/*
* https://electronics.stackexchange.com/questions/14828/how-do-i-calculate-needed-pixel-clock-frequency
* If I want to have a resolution of X * Y pixels, updating in frequency f.
* How do I calculate the pixel clock speed?
* Ex: 1280 x 1024 @ 85Hz usually have a pixel clock of 157.5 MHz
* Pixel clock = Horizontal_Active_Resolution X Vertical_Active_Resolution X Refresh_Rate X (1+ Blanking Period %).
*
* Sample pixel clock rates
*
* 640x480 (VGA): ~25-30 MHz
* 800x600 (SVGA): ~40-50 MHz
* 1280x720 (720p): ~74-85 MHz
* 1920x1080 (1080p @ 30Hz): ~148-165 MHz
* 1920x1080 (1080p @ 60Hz): ~296-330 MHz
*/
// support these fixed standard timings (should use edid to negotiate with monitor instead?)
static const struct display_timing avail_resolutions[] = {
// {horiz} {vert} {pll video0}, {tcon clk}, {de clk}
{HDMI_1080P, {1920, 88, 44, 148}, {1080, 4, 5, 36}, {297000000, CCU_PLL_VIDEO0_CTRL_REG}, {148500000, PARENT_VIDEO0}, {297000000, PARENT_VIDEO0_4X}},
{HDMI_HD, {1280, 110, 40, 220}, { 720, 5, 5, 20}, {297000000, CCU_PLL_VIDEO0_CTRL_REG}, { 74250000, PARENT_VIDEO0}, {297000000, PARENT_VIDEO0_4X}},
{HDMI_SVGA, { 800, 40, 128, 88}, { 600, 1, 4, 23}, {120000000, CCU_PLL_VIDEO0_CTRL_REG}, { 40000000, PARENT_VIDEO0}, {480000000, PARENT_VIDEO0_4X}},
{HDMI_INVALID, {0}}
};
static bool select_resolution(hdmi_resolution_id_t id);
static void enable_display_clocks(void);
static void hdmi_controller_init(void);
static void tcon_init(void);
static int sun20i_d1_hdmi_phy_config(void);
void hdmi_init(hdmi_resolution_id_t id) {
if (!select_resolution(id)) {
error("Unable to init hdmi, resolution id is invalid!\n");
}
enable_display_clocks();
hdmi_controller_init();
tcon_init();
// possible to call hdmi_init again to change resolution
// but must init PHY exactly once
// (does not need re-init for change in resolution
// and in fact re-init will cause problems)
static bool phy_initialized = false;
if (!phy_initialized) {
sun20i_d1_hdmi_phy_config();
phy_initialized = true;
}
}
static bool select_resolution(hdmi_resolution_id_t id) {
for (const struct display_timing *p = avail_resolutions; p->id != HDMI_INVALID; p++) {
if (p->id == id) {
module.config = *p;
return true;
}
}
return false;
}
hdmi_resolution_id_t hdmi_best_match(int width, int height) {
hdmi_resolution_id_t chosen = HDMI_INVALID;
// resolutions listed in order from largest to smallest, choose "tightest" (i.e. smallest that fits)
for (const struct display_timing *p = avail_resolutions; p->id != HDMI_INVALID; p++) {
if (width <= p->horiz.pixels && height <= p->vert.pixels) {
chosen = p->id;
}
}
return chosen;
}
int hdmi_get_screen_width(void) {
if (module.config.id == HDMI_INVALID) error("Must call hdmi_init before using hdmi_get_screen_width()");
return module.config.horiz.pixels;
}
int hdmi_get_screen_height(void) {
if (module.config.id == HDMI_INVALID) error("Must call hdmi_init before using hdmi_get_screen_height()");
return module.config.vert.pixels;
}
// enable all clocks needed for HDMI+TCON+DE2
static void enable_display_clocks(void) {
// video0 PLL
ccu_config_pll_rate(module.config.pll.id, module.config.pll.rate);
// hdmi clock, both sub and main (bits 16 and 17)
ccu_ungate_bus_clock_bits(CCU_HDMI_BGR_REG, 1 << 0, (1 << 16)|(1 << 17) );
ccu_config_module_clock_rate(CCU_HDMI_24M_CLK_REG, PARENT_HOSC, 24000000);
ccu_ungate_bus_clock(CCU_DPSS_TOP_BGR_REG); // tcon top clock
ccu_ungate_bus_clock(CCU_TCONTV_BGR_REG); // tcon tv clock
ccu_config_module_clock_rate(CCU_TCONTV_CLK_REG, module.config.tcon_clk.parent, module.config.tcon_clk.rate);
ccu_ungate_bus_clock(CCU_DE_BGR_REG); // de clock
ccu_config_module_clock_rate(CCU_DE_CLK_REG, module.config.de_clk.parent, module.config.de_clk.rate);
}
// HDMI controller registers must be written in 8-bit chunks
static void hdmi_write_short(volatile uint8_t arr[], short val) {
arr[0] = val & 0xff;
arr[1] = val >> 8;
}
#define BLANKING(d) (d.front_porch + d.sync_pulse + d.back_porch)
#define TOTAL(d) (d.pixels + BLANKING(d))
static void hdmi_controller_init(void) {
// doc Synopsys Designware @ https://people.freebsd.org/~gonzo/arm/iMX6-HDMI.pdf
// frame controller
// V and H sync polarity bits 5&6, data enble input polarity bit 4, HDMI mode bit 3
module.hdmi_fc->regs.invidconf = (1<<6) | (1<<5) | (1<<4) | (1<<3);
hdmi_write_short(module.hdmi_fc->regs.inhactv, module.config.horiz.pixels);
hdmi_write_short(module.hdmi_fc->regs.inhblank, BLANKING(module.config.horiz));
hdmi_write_short(module.hdmi_fc->regs.hsyncindelay, module.config.horiz.front_porch);
hdmi_write_short(module.hdmi_fc->regs.hsyncinwidth, module.config.horiz.sync_pulse);
hdmi_write_short(module.hdmi_fc->regs.invactv, module.config.vert.pixels);
module.hdmi_fc->regs.invblank = BLANKING(module.config.vert);
module.hdmi_fc->regs.vsyncindelay = module.config.vert.front_porch;
module.hdmi_fc->regs.vsyncinwidth = module.config.vert.sync_pulse;
module.hdmi_fc->regs.ctrldur = 12; // spacing set at minimums
module.hdmi_fc->regs.exctrldur = 32; // values from linux bridge driver
module.hdmi_fc->regs.exctrlspac = 1;
module.hdmi_fc->regs.chpream[0] = 0x0b;
module.hdmi_fc->regs.chpream[1] = 0x16;
module.hdmi_fc->regs.chpream[2] = 0x21;
// main controller
module.hdmi_mc->regs.clkdis = 0x7c; // enable pixel+tdms clock (disable others)
}
static void tcon_init(void) {
module.tcon_tv->regs.gtcl = (1 << 31); // tcon_tv global enable @[31]
// vertical video start delay is computed by excluding vertical front
// porch value from total vertical timings
// See https://lkml.iu.edu/hypermail/linux/kernel/1910.0/06574.html
uint32_t start_delay = TOTAL(module.config.vert) - (module.config.vert.pixels + module.config.vert.front_porch) - 1;
module.tcon_tv->regs.ctl = (1 << 31) | (start_delay << 4); // enable tv @[31], delay @[8-4] (@[1] set for blue test data)
// [0] input resolution, [1] upscaled resolution, [2] output resolution
for (int i = 0; i < 3; i++) {
module.tcon_tv->regs.dimensions[i].width = module.config.horiz.pixels - 1;
module.tcon_tv->regs.dimensions[i].height = module.config.vert.pixels - 1;
};
module.tcon_tv->regs.htiming.total = TOTAL(module.config.horiz) - 1;
module.tcon_tv->regs.htiming.bp = module.config.horiz.sync_pulse + module.config.horiz.back_porch - 1;
module.tcon_tv->regs.vtiming.total = 2 * TOTAL(module.config.vert);
module.tcon_tv->regs.vtiming.bp = module.config.vert.sync_pulse + module.config.vert.back_porch - 1;
module.tcon_tv->regs.sync.horiz = module.config.horiz.sync_pulse - 1;
module.tcon_tv->regs.sync.vert = module.config.vert.sync_pulse - 1;
// configure tcon_top mux and select tcon tv
// clear state, config for hdmi, enable
module.tcon_top->regs.gate &= ~((0xf << 28) | (0xf << 20)); // clear bits @[31-28], [23-20]
module.tcon_top->regs.gate |= (0x1 << 28) | (0x1 << 20); // from Linux
module.tcon_top->regs.port_sel &= ~((0x3 << 4) | (0x3 << 0)); // clear bits @[5-4], @[1-0]
module.tcon_top->regs.port_sel |= (2 << 0); // config mixer0 -> tcon_tv
//module.tcon_tv->regs.ctl |= (1 << 1); // enable blue test data
}
/*
* Code below drives the HDMI PHY used on the Allwinner D1 SoC. The
* PHY is responsible for low-level HDMI clock and timing signals.
* The PHY used for the D1 is custom design by Allwinner. Sadly there
* seems to be zero documentation for it. I got the code below from the
* BSP and linux kernel driver.
*
* juliez July 2023
*/
// Everything from here down was taken near-verbatim from linux-sunxi kernel driver
// https://github.com/smaeul/linux/blob/d1/all/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
#define AW_PHY_TIMEOUT 1000
static int sun20i_d1_hdmi_phy_enable(void) {
int i = 0, status = 0;
//enib -> enldo -> enrcal -> encalog -> enbi[3:0] -> enck -> enp2s[3:0] -> enres -> enresck -> entx[3:0]
phy_base->phy_ctl4.bits.reg_slv = 4; //low power voltage 1.08V, default is 3, set 4 as well as pll_ctl0 bit [24:26]
phy_base->phy_ctl5.bits.enib = 1;
phy_base->phy_ctl0.bits.enldo = 1;
phy_base->phy_ctl0.bits.enldo_fs = 1;
phy_base->phy_ctl5.bits.enrcal = 1;
phy_base->phy_ctl5.bits.encalog = 1;
for (i = 0; i < AW_PHY_TIMEOUT; i++) {
timer_delay_us(5);
status = phy_base->phy_pll_sts.bits.phy_rcalend2d_status;
if (status & 0x1) {
break;
}
}
if ((i == AW_PHY_TIMEOUT) && !status) {
printf("phy_rcalend2d_status TIMEOUT\n");
return -1;
}
phy_base->phy_ctl0.bits.enbi = 0xF;
for (i = 0; i < AW_PHY_TIMEOUT; i++) {
timer_delay_us(5);
status = phy_base->phy_pll_sts.bits.pll_lock_status;
if (status & 0x1) {
break;
}
}
if ((i == AW_PHY_TIMEOUT) && !status) {
printf("pll_lock_status TIMEOUT\n");
return -1;
}
phy_base->phy_ctl0.bits.enck = 1;
phy_base->phy_ctl5.bits.enp2s = 0xF;
phy_base->phy_ctl5.bits.enres = 1;
phy_base->phy_ctl5.bits.enresck = 1;
phy_base->phy_ctl0.bits.entx = 0xF;
for (i = 0; i < AW_PHY_TIMEOUT; i++) {
timer_delay_us(5);
status = phy_base->phy_pll_sts.bits.tx_ready_dly_status;
if (status & 0x1) {
break;
}
}
if ((i == AW_PHY_TIMEOUT) && !status) {
printf("tx_ready_status TIMEOUT\n");
return -1;
}
return 0;
}
static int sun20i_d1_hdmi_phy_config(void) {
int ret;
/* enable all channel */
phy_base->phy_ctl5.bits.reg_p1opt = 0xF;
// phy_reset
phy_base->phy_ctl0.bits.entx = 0;
phy_base->phy_ctl5.bits.enresck = 0;
phy_base->phy_ctl5.bits.enres = 0;
phy_base->phy_ctl5.bits.enp2s = 0;
phy_base->phy_ctl0.bits.enck = 0;
phy_base->phy_ctl0.bits.enbi = 0;
phy_base->phy_ctl5.bits.encalog = 0;
phy_base->phy_ctl5.bits.enrcal = 0;
phy_base->phy_ctl0.bits.enldo_fs = 0;
phy_base->phy_ctl0.bits.enldo = 0;
phy_base->phy_ctl5.bits.enib = 0;
phy_base->pll_ctl1.bits.reset = 1;
phy_base->pll_ctl1.bits.pwron = 0;
phy_base->pll_ctl0.bits.envbs = 0;
// phy_set_mpll
phy_base->pll_ctl0.bits.cko_sel = 0x3;
phy_base->pll_ctl0.bits.bypass_ppll = 0x1;
phy_base->pll_ctl1.bits.drv_ana = 1;
phy_base->pll_ctl1.bits.ctrl_modle_clksrc = 0x0; //0: PLL_video 1: MPLL
phy_base->pll_ctl1.bits.sdm_en = 0x0; //mpll sdm jitter is very large, not used for the time being
phy_base->pll_ctl1.bits.sckref = 0; //default value is 1
phy_base->pll_ctl0.bits.slv = 4;
phy_base->pll_ctl0.bits.prop_cntrl = 7; //default value 7
phy_base->pll_ctl0.bits.gmp_cntrl = 3; //default value 1
phy_base->pll_ctl1.bits.ref_cntrl = 0;
phy_base->pll_ctl0.bits.vcorange = 1;
// phy_set_div
phy_base->pll_ctl0.bits.div_pre = 0; //div7 = n+1
phy_base->pll_ctl1.bits.pcnt_en = 0;
phy_base->pll_ctl1.bits.pcnt_n = 1; //div6 = 1 (pcnt_en=0) [div6 = n (pcnt_en = 1) note that some multiples are problematic] 4-256
phy_base->pll_ctl1.bits.pixel_rep = 0; //div5 = n+1
phy_base->pll_ctl0.bits.bypass_clrdpth = 0;
phy_base->pll_ctl0.bits.clr_dpth = 0; //div4 = 1 (bypass_clrdpth = 0)
//00: 2 01: 2.5 10: 3 11: 4
phy_base->pll_ctl0.bits.n_cntrl = 1; //div
phy_base->pll_ctl0.bits.div2_ckbit = 0; //div1 = n+1
phy_base->pll_ctl0.bits.div2_cktmds = 0; //div2 = n+1
phy_base->pll_ctl0.bits.bcr = 0; //div3 0: [1:10] 1: [1:40]
phy_base->pll_ctl1.bits.pwron = 1;
phy_base->pll_ctl1.bits.reset = 0;
// configure phy
/* config values taken from table */
phy_base->phy_ctl1.dwval = ((phy_base->phy_ctl1.dwval & 0xFFC0FFFF) | /* config->phy_ctl1 */ 0x0);
phy_base->phy_ctl2.dwval = ((phy_base->phy_ctl2.dwval & 0xFF000000) | /* config->phy_ctl2 */ 0x0);
phy_base->phy_ctl3.dwval = ((phy_base->phy_ctl3.dwval & 0xFFFF0000) | /* config->phy_ctl3 */ 0xFFFF);
phy_base->phy_ctl4.dwval = ((phy_base->phy_ctl4.dwval & 0xE0000000) | /* config->phy_ctl4 */ 0xC0D0D0D);
//phy_base->pll_ctl0.dwval |= config->pll_ctl0;
//phy_base->pll_ctl1.dwval |= config->pll_ctl1;
// phy_set_clk
phy_base->phy_ctl6.bits.switch_clkch_data_corresponding = 0;
phy_base->phy_ctl6.bits.clk_greate0_340m = 0x3FF;
phy_base->phy_ctl6.bits.clk_greate1_340m = 0x3FF;
phy_base->phy_ctl6.bits.clk_greate2_340m = 0x0;
phy_base->phy_ctl7.bits.clk_greate3_340m = 0x0;
phy_base->phy_ctl7.bits.clk_low_340m = 0x3E0;
phy_base->phy_ctl6.bits.en_ckdat = 1; //default value is 0
// phy_base->phy_ctl2.bits.reg_resdi = 0x18;
// phy_base->phy_ctl4.bits.reg_slv = 3; //low power voltage 1.08V, default value is 3
phy_base->phy_ctl1.bits.res_scktmds = 0; //
phy_base->phy_ctl0.bits.reg_csmps = 2;
phy_base->phy_ctl0.bits.reg_ck_test_sel = 0; //?
phy_base->phy_ctl0.bits.reg_ck_sel = 1;
phy_base->phy_indbg_ctrl.bits.txdata_debugmode = 0;
// phy_enable
ret = sun20i_d1_hdmi_phy_enable();
if (ret)
return ret;
phy_base->phy_ctl0.bits.sda_en = 1;
phy_base->phy_ctl0.bits.scl_en = 1;
phy_base->phy_ctl0.bits.hpd_en = 1;
phy_base->phy_ctl0.bits.reg_den = 0xF;
phy_base->pll_ctl0.bits.envbs = 1;
return 0;
}
/*
* High-speed timer used as countdown with interrupt
*
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
*/
#include "hstimer.h"
#include <stdint.h>
#include "ccu.h"
// structs defined to match layout of hardware registers
typedef union {
struct {
uint32_t ctrl;
uint32_t intv_lo; // lower 32 bits of interval (see docs note above)
uint32_t intv_hi; // upper 32 bits of interval
uint32_t cur_lo; // lower 32 bits of cur value
uint32_t cur_hi; // upper 32 bits of cur value
} regs;
uint8_t padding[0x20];
} hstimer_t;
#define TIMER_BASE ((hstimer_t *)0x3008020)
_Static_assert(&(TIMER_BASE[0].regs.ctrl) == (uint32_t *)0x3008020, "hstimer0 ctrl reg must be at address 0x3008020");
_Static_assert(&(TIMER_BASE[1].regs.ctrl) == (uint32_t *)0x3008040, "hstimer1 ctrl reg must be at address 0x3008040");
typedef union {
struct {
uint32_t irq_en; // interrupt enable
uint32_t irq_stas; // interrupt status
} regs;
} hstimer_irq_t;
#define INTERRUPT_BASE ((hstimer_irq_t *)0x3008000)
_Static_assert(&(INTERRUPT_BASE->regs.irq_stas) == (uint32_t *)0x3008004, "hstimer irq stas reg must be at address 0x3008004");
static struct {
volatile hstimer_irq_t *interrupt;
volatile hstimer_t *timers;
} const module = {
.interrupt = INTERRUPT_BASE,
.timers = TIMER_BASE,
};
/* From docs:
HSTimer0 is a 56-bit counter. The interval value consists of two parts:
HS_TMR0_INTV_VALUE_LO acts as the bit[31:0] and
HS_TMR0_INTV_VALUE_HI acts as the bit[55:32].
To read or write the interval value, HS_TMR0_INTV_LO_REG should be done before HS_TMR0_INTV_HI_REG.
*/
void hstimer_init(hstimer_id_t index, long usecs) {
if (index != HSTIMER0 && index != HSTIMER1) return;
long rate = ccu_ungate_bus_clock(CCU_HSTIMER_BGR_REG); // clock up peripheral
module.timers[index].regs.ctrl = (0 << 7) | (0 << 4); // config mode normal periodic (not one-shot), prescale = 2^0, not enabled
uint64_t count = (usecs * rate)/(1000*1000); // calculate count based on clock frequency
module.timers[index].regs.intv_lo = count & 0xffffffff; // must set low before high (see docs note above)
module.timers[index].regs.intv_hi = count >> 32;
module.timers[index].regs.ctrl |= (1 << 1); // reload interval into cur
module.interrupt->regs.irq_en |= (1 << index); // enable interrupts
}
void hstimer_enable(hstimer_id_t index) {
if (index != HSTIMER0 && index != HSTIMER1) return;
module.timers[index].regs.ctrl |= 1; // set ctrl bit will start/resume countdown
}
void hstimer_disable(hstimer_id_t index) {
if (index != HSTIMER0 && index != HSTIMER1) return;
module.timers[index].regs.ctrl &= ~1; // clear ctrl bit will pause countdown
}
void hstimer_interrupt_clear(hstimer_id_t index) {
if (index != HSTIMER0 && index != HSTIMER1) return;
module.interrupt->regs.irq_stas = (1 << index); // write 1 to clear
}
/*
* Use of PLIC to configure and dispatch external interrupts
*
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
* June 2023
*/
#include "interrupts.h"
#include "assert.h"
#include "disassemble.h"
#include "mango.h"
#include <stddef.h>
#include "_system.h"
/* Module-private helpers defined in interrupts_asm.s */
unsigned long interrupts_get_mepc(void);
unsigned long interrupts_get_mcause(void);
unsigned long interrupts_get_mtval(void);
void interrupts_set_mtvec(void *);
#define N_SOURCES 256
// structs defined to match layout of hardware registers
typedef union {
struct {
uint32_t priority[1024]; // only first 256 indexes are used (priority per source)
uint32_t pending[1024]; // only first 8 indexes used (pending bit per source, 256/32 = 8)
uint32_t enable[1024]; // only first 8 indexes used (enable bit per source, 256/32 = 8)
} regs;
} source_t;
#define SOURCE_BASE ((source_t *)0x10000000)
_Static_assert(&(SOURCE_BASE->regs.priority[0]) == (uint32_t *)0x10000000, "priority0 reg must be at address 0x10000000");
_Static_assert(&(SOURCE_BASE->regs.pending[1]) == (uint32_t *)0x10001004, "pending1 reg must be at address 0x10001004");
typedef union {
struct {
uint32_t ctrl;
uint32_t threshhold;
uint32_t claim_complete;
} regs;
} plic_t;
#define PLIC_BASE ((plic_t *)0x101FFFFC)
_Static_assert(&(PLIC_BASE->regs.claim_complete) == (uint32_t *)0x10200004, "plic claim reg must be at address 0x10200004");
static struct {
volatile source_t *sources;
volatile plic_t *plic;
struct {
handlerfn_t fn;
void *aux_data;
} handlers[N_SOURCES];
bool initialized;
} module = {
.sources = SOURCE_BASE,
.plic = PLIC_BASE,
};
static const char *table[] = {
"Instruction address misaligned",
"Instruction access fault",
"Illegal instruction",
"Breakpoint",
"Load address misaligned",
"Load access fault",
"Store/AMO address misaligned",
"Store/AMO access fault",
"E-call from U-mode",
"E-call from S-mode",
"Reserved (10)",
"E-call from M-mode", /* external interrupt, M-mode */
"Instruction page fault",
"Load page fault",
"Reserved (14)",
"Store/AMO page fault",
};
#define DESCRIPTION(cause) (cause < sizeof(table)/sizeof(*table) ? table[cause] : "Unknown")
void _trap_handler(void);
// gcc attribute used to generate prologue/epilogue appropriate for machine interrupt
// https://gcc.gnu.org/onlinedocs/gcc/RISC-V-Function-Attributes.html
__attribute__((interrupt("machine"))) void _trap_handler(void) {
long mcause = interrupts_get_mcause();
#define EXTERNAL_INTERRUPT ((1L << 63) | 0xb)
if (mcause == EXTERNAL_INTERRUPT) { // trap is interrupt
// do not need to search pending bits to identify which source, claim reg has source number
uint32_t source = module.plic->regs.claim_complete; // read claim_complete to "claim" (atomically clears pending bit)
module.handlers[source].fn(module.handlers[source].aux_data); // dispatch to registered handler
module.plic->regs.claim_complete = source; // write claim_complete to clear
} else { // trap is exception
long mtval = interrupts_get_mtval();
void *mepc = (void *)interrupts_get_mepc();
sys_report_error("EXCEPTION: %s\n", DESCRIPTION(mcause));
if (mcause == 0 || mcause == 1) {
sys_report_error("Cannot fetch instruction at invalid address (mepc): %p\n", mepc);
} else if (mcause == 2 || mcause == 3) {
sys_report_error("Faulting instruction (mepc): [%pW] %pI at %p %pL\n", mepc, mepc, mepc, mepc);
} else if (mcause == 5 || mcause == 7) {
sys_report_error("Faulting address (mtval): 0x%lx \n", mtval);
} else {
sys_report_error("Instruction (mepc): %p %pL\n", mepc, mepc);
sys_report_error("Value (mtval): %8ld 0x%lx \n", mtval, mtval);
}
mango_abort();
}
}
void interrupts_init(void) {
if (module.initialized) error("interrupts_init() must be called only once");
interrupts_global_disable();
module.plic->regs.ctrl = 0; // machine mode only
module.plic->regs.threshhold = 0; // accept interrupts of any priority
interrupts_set_mtvec(_trap_handler); // install trap handler
for (int i = 0; i < N_SOURCES/32; i++) {
module.sources->regs.pending[i] = 0;// all sources initially disabled
module.sources->regs.enable[i] = 0;
}
for (int i = 0; i < N_SOURCES; i++) {
module.sources->regs.priority[i] = 0;
module.handlers[i].fn = NULL; // all sources initially have NULL handler
module.plic->regs.claim_complete = i; // reset any pending claim
}
module.initialized = true;
}
static bool is_valid_source(interrupt_source_t source) {
switch (source) {
case INTERRUPT_SOURCE_UART0...INTERRUPT_SOURCE_UART5:
case INTERRUPT_SOURCE_TWI0...INTERRUPT_SOURCE_TWI3:
case INTERRUPT_SOURCE_SPI0...INTERRUPT_SOURCE_SPI1:
case INTERRUPT_SOURCE_HSTIMER0...INTERRUPT_SOURCE_HSTIMER1:
case INTERRUPT_SOURCE_GPIOB...INTERRUPT_SOURCE_GPIOG:
return true;
}
return false;
}
static void set_source_enabled(interrupt_source_t source, bool enabled) {
if (!module.initialized) error("interrupts_init() has not been called!\n");
if (!is_valid_source(source)) error("request to enable/disable interrupt source that is not valid");
int bank = source / 32;
int shift = source % 32;
if (enabled) {
module.sources->regs.priority[source] = 1; // priority at 1 (0 is disable, 1 is lowest)
module.sources->regs.enable[bank] |= (1 << shift); // set enable bit
} else {
module.sources->regs.priority[source] = 0;
module.sources->regs.enable[bank] &= ~(1 << shift); // clear enable bit
}
}
void interrupts_enable_source(interrupt_source_t source) {
set_source_enabled(source, true);
}
void interrupts_disable_source(interrupt_source_t source) {
set_source_enabled(source, false);
}
void interrupts_register_handler(interrupt_source_t source, handlerfn_t fn, void *aux_data) {
if (!module.initialized) error("interrupts_init() has not been called!\n");
if (!is_valid_source(source)) error("request to register handler for interrupt source that is not valid");
module.handlers[source].fn = fn; // store handler function and
module.handlers[source].aux_data = aux_data; // aux_data pointer into array at index corresponding to source
}
/*
* Asm routines to access CSRs used by interrupts module
*/
.attribute arch, "rv64im_zicsr"
.globl interrupts_global_enable
interrupts_global_enable:
li a0,1<<11 # MEIE bit 11 (m-mode external interrupts)
csrs mie,a0 # set bit in mie register
li a0,1<<3 # MIE bit 3 (global enable m-mode interrupts)
csrs mstatus,a0 # set bit in mstatus register
ret
.globl interrupts_global_disable
interrupts_global_disable:
li a0,1<<11 # (reverse steps from global enable)
csrc mie,a0 # clear bit in mie
li a0,1<<3
csrc mstatus,a0 # clear bit in mstatus
ret
.globl interrupts_get_mcause
interrupts_get_mcause:
csrr a0,mcause # retrieve mcause
ret
.globl interrupts_get_mepc
interrupts_get_mepc:
csrr a0,mepc # retrieve mepc
ret
.globl interrupts_get_mtval
interrupts_get_mtval:
csrr a0,mtval # retrieve mtval
ret
.globl interrupts_set_mtvec
interrupts_set_mtvec:
csrw mtvec,a0 # mtvec holds addr of handler to call on trap
ret
/*
* Mango module
*
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
* Updated: Mon Jan 1 11:50:26 PST 2024
*/
#include "mango.h"
#include "timer.h"
#include "_system.h"
#include "uart.h"
// structs defined to match layout of hardware registers
typedef union {
struct {
uint32_t irq_enable;
uint32_t irq_status;
uint32_t soft_reset;
uint32_t reserved;
uint32_t control;
uint32_t config;
uint32_t mode;
uint32_t output;
} regs;
} watchdog_t;
#define WATCHDOG_BASE ((watchdog_t *)0x20500A0)
_Static_assert(&(WATCHDOG_BASE->regs.mode) == (uint32_t *)0x20500b8, "watchdog mode reg must be at address 0x20500b8");
static struct {
volatile watchdog_t *wdog;
} const module = {
.wdog = WATCHDOG_BASE
};
void mango_reboot(void) {
if (sys_running_in_simulator()) {
syscall_exit(0); // divert iff in gdb sim
}
timer_delay_ms(100); // give output time to flush (needed if using uart)
const int cycles = 1;
module.wdog->regs.config = 1; // config watchdog for whole system reset
// mode = 0x16aa in upper 16 bits allows write to lower 15, cycles in bits [7:4], enable 1 in lsb [0]
module.wdog->regs.mode = 0x16aa << 16 | (cycles << 4) | 1;
while (1) ;
}
void mango_abort(void) {
if (sys_running_in_simulator()) {
syscall_exit(1); // divert iff in gdb sim
}
uart_start_error(); // will force init uart if needed
uart_putstring("\n *** In mango_abort(), type r to reboot: ");
uart_end_error();
while (1) {
mango_actled(LED_TOGGLE);
timer_delay_ms(100);
if (uart_haschar()) {
int ch = uart_recv();
if (ch == 'r') {
uart_putstring("rebooting\n");
mango_reboot();
}
}
}
}
void mango_actled(enum led_state_t s) {
static const gpio_id_t led = GPIO_PD18;
gpio_set_output(led);
if (s == LED_ON)
gpio_write(led, 1);
else if (s == LED_OFF)
gpio_write(led, 0);
else if (s == LED_TOGGLE)
gpio_write(led, !gpio_read(led));
}
/*
* Lookup table to access key information for a PS/2 scancode.
*
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
*/
#include "ps2_keys.h"
#define UNUSED { PS2_KEY_NONE, PS2_KEY_NONE }
// For completeness, array lists all keys on the full PS/2 keyboard.
// Refer to the assign5 writeup for spec on which keys are required
// to be implemented by your keyboard driver.
ps2_key_t const ps2_keys[] = {
/* scancode */
/* 00 */ UNUSED,
/* 01 */ { PS2_KEY_F9, PS2_KEY_F9 },
/* 02 */ UNUSED,
/* 03 */ { PS2_KEY_F5, PS2_KEY_F5 },
/* 04 */ { PS2_KEY_F3, PS2_KEY_F3 },
/* 05 */ { PS2_KEY_F1, PS2_KEY_F1 },
/* 06 */ { PS2_KEY_F2, PS2_KEY_F2 },
/* 07 */ { PS2_KEY_F12, PS2_KEY_F12 },
/* 08 */ UNUSED,
/* 09 */ { PS2_KEY_F10, PS2_KEY_F10 },
/* 0A */ { PS2_KEY_F8, PS2_KEY_F8 },
/* 0B */ { PS2_KEY_F6, PS2_KEY_F6 },
/* 0C */ { PS2_KEY_F4, PS2_KEY_F4 },
/* 0D */ { '\t', '\t' },
/* 0E */ { '`', '~' },
/* 0F */ UNUSED,
/* 10 */ UNUSED,
/* 11 */ { PS2_KEY_ALT, PS2_KEY_ALT },
/* 12 */ { PS2_KEY_SHIFT, PS2_KEY_SHIFT },
/* 13 */ UNUSED,
/* 14 */ { PS2_KEY_CTRL, PS2_KEY_CTRL },
/* 15 */ { 'q', 'Q' },
/* 16 */ { '1', '!' },
/* 17 */ UNUSED,
/* 18 */ UNUSED,
/* 19 */ UNUSED,
/* 1A */ { 'z', 'Z' },
/* 1B */ { 's', 'S' },
/* 1C */ { 'a', 'A' },
/* 1D */ { 'w', 'W' },
/* 1E */ { '2', '@' },
/* 1F */ UNUSED,
/* 20 */ UNUSED,
/* 21 */ { 'c', 'C' },
/* 22 */ { 'x', 'X' },
/* 23 */ { 'd', 'D' },
/* 24 */ { 'e', 'E' },
/* 25 */ { '4', '$' },
/* 26 */ { '3', '#' },
/* 27 */ UNUSED,
/* 28 */ UNUSED,
/* 29 */ { ' ', ' ' },
/* 2A */ { 'v', 'V' },
/* 2B */ { 'f', 'F' },
/* 2C */ { 't', 'T' },
/* 2D */ { 'r', 'R' },
/* 2E */ { '5', '%' },
/* 2F */ UNUSED,
/* 30 */ UNUSED,
/* 31 */ { 'n', 'N' },
/* 32 */ { 'b', 'B' },
/* 33 */ { 'h', 'H' },
/* 34 */ { 'g', 'G' },
/* 35 */ { 'y', 'Y' },
/* 36 */ { '6', '^' },
/* 37 */ UNUSED,
/* 38 */ UNUSED,
/* 39 */ UNUSED,
/* 3A */ { 'm', 'M' },
/* 3B */ { 'j', 'J' },
/* 3C */ { 'u', 'U' },
/* 3D */ { '7', '&' },
/* 3E */ { '8', '*' },
/* 3F */ UNUSED,
/* 40 */ UNUSED,
/* 41 */ { ',', '<' },
/* 42 */ { 'k', 'K' },
/* 43 */ { 'i', 'I' },
/* 44 */ { 'o', 'O' },
/* 45 */ { '0', ')' },
/* 46 */ { '9', '(' },
/* 47 */ UNUSED,
/* 48 */ UNUSED,
/* 49 */ { '.', '>' },
/* 4A */ { '/', '?' },
/* 4B */ { 'l', 'L' },
/* 4C */ { ';', ':' },
/* 4D */ { 'p', 'P' },
/* 4E */ { '-', '_' },
/* 4F */ UNUSED,
/* 50 */ UNUSED,
/* 51 */ UNUSED,
/* 52 */ { '\'', '"' },
/* 53 */ UNUSED,
/* 54 */ { '[', '{' },
/* 55 */ { '=', '+' },
/* 56 */ UNUSED,
/* 57 */ UNUSED,
/* 58 */ { PS2_KEY_CAPS_LOCK, PS2_KEY_CAPS_LOCK },
/* 59 */ { PS2_KEY_SHIFT, PS2_KEY_SHIFT },
/* 5A */ { '\n', '\n' },
/* 5B */ { ']', '}' },
/* 5C */ UNUSED,
/* 5D */ { '\\', '|' },
/* 5E */ UNUSED,
/* 5F */ UNUSED,
/* 60 */ UNUSED,
/* 61 */ UNUSED,
/* 62 */ UNUSED,
/* 63 */ UNUSED,
/* 64 */ UNUSED,
/* 65 */ UNUSED,
/* 66 */ { '\b', '\b' }, // delete key produces backspace (= ascii 0x08 = \b)
/* 67 */ UNUSED,
/* 68 */ UNUSED,
/* 69 */ { PS2_KEY_END, '1' },
/* 6A */ UNUSED,
/* 6B */ { PS2_KEY_ARROW_LEFT, '4' },
/* 6C */ { PS2_KEY_HOME, '7' },
/* 6D */ UNUSED,
/* 6E */ UNUSED,
/* 6F */ UNUSED,
/* 70 */ { PS2_KEY_INSERT, '0' },
/* 71 */ { PS2_KEY_DELETE, '.' },
/* 72 */ { PS2_KEY_ARROW_DOWN, '2' },
/* 73 */ { '5', '5' },
/* 74 */ { PS2_KEY_ARROW_RIGHT, '6' },
/* 75 */ { PS2_KEY_ARROW_UP, '8' },
/* 76 */ { PS2_KEY_ESC, PS2_KEY_ESC },
/* 77 */ { PS2_KEY_NUM_LOCK, PS2_KEY_NUM_LOCK },
/* 78 */ { PS2_KEY_F11, PS2_KEY_F11 },
/* 79 */ { '+', '+' },
/* 7A */ { PS2_KEY_PAGE_DOWN, '3' },
/* 7B */ { '-', '-' },
/* 7C */ { '*', '*' },
/* 7D */ { PS2_KEY_PAGE_UP, '9' },
/* 7E */ { PS2_KEY_SCROLL_LOCK, 0 },
/* 7F */ UNUSED,
/* 80 */ UNUSED,
/* 81 */ UNUSED,
/* 82 */ UNUSED,
/* 83 */ { PS2_KEY_F7, PS2_KEY_F7 },
};
/*
* Pseudorandom number generator
*
* Author: Pat Hanrahan <hanrahan@cs.stanford.edu>
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
*/
#include "rand.h"
#include <stdbool.h>
#include <stdint.h>
static struct {
bool initialized;
uint32_t z1, z2, z3, z4;
} module = {
.initialized = false,
};
unsigned int rand(void) {
if (!module.initialized) {
srand(12345);
}
// http://stackoverflow.com/questions/1167253/implementation-of-rand
// LFSR113 from L'écuyer
uint32_t b;
b = ((module.z1 << 6) ^ module.z1) >> 13;
module.z1 = ((module.z1 & 4294967294U) << 18) ^ b;
b = ((module.z2 << 2) ^ module.z2) >> 27;
module.z2 = ((module.z2 & 4294967288U) << 2) ^ b;
b = ((module.z3 << 13) ^ module.z3) >> 21;
module.z3 = ((module.z3 & 4294967280U) << 7) ^ b;
b = ((module.z4 << 3) ^ module.z4) >> 12;
module.z4 = ((module.z4 & 4294967168U) << 13) ^ b;
return (module.z1 ^ module.z2 ^ module.z3 ^ module.z4);
}
void srand(unsigned int seed) {
// initial factors must be >= 2, 8, 16, and 128 respectively
// this just lifts all to at least 128 to satisfy requirement
if (seed < 128) seed += 128;
module.z1 = seed;
module.z2 = seed;
module.z3 = seed;
module.z4 = seed;
module.initialized = true;
}
/*
* Implementation of ringbuffer module.
*
* The ring buffer data structure allows lock-free concurrent
* access by one reader and one writer.
*
* Author: Philip Levis <pal@cs.stanford.edu>
* Julie Zelenski <zelenski@cs.stanford.edu>
*/
#include "ringbuffer.h"
#include "assert.h"
#include "malloc.h"
#define LENGTH 512
/*
* A ring buffer is represented using a struct containing a fixed-size array
* and head and tail fields, which are indexes into the entries[] array.
* head is the index of the frontmost element (head advances during dequeue)
* tail is the index of the next position to use (tail advances during enqueue)
* Both head and tail advance circularly, i.e. index = (index + 1) % LENGTH
* The ring buffer is empty if tail == head
* The ring buffer is full if tail + 1 == head
* (Note: one slot permanently unused to distinguish full from empty)
*/
struct ringbuffer {
int entries[LENGTH];
int head, tail;
};
rb_t *rb_new(void) {
rb_t *rb = malloc(sizeof(struct ringbuffer));
assert(rb != NULL);
rb->head = rb->tail = 0;
return rb;
}
bool rb_empty(rb_t *rb) {
assert(rb != NULL);
return rb->head == rb->tail;
}
bool rb_full(rb_t *rb) {
assert(rb != NULL);
return (rb->tail + 1) % LENGTH == rb->head;
}
/*
* Note: enqueue is called by writer. enqueue advances rb->tail,
* no changes to rb->head. This design allows safe concurrent access.
*/
bool rb_enqueue(rb_t *rb, int elem) {
assert(rb != NULL);
if (rb_full(rb)) {
return false;
}
rb->entries[rb->tail] = elem;
rb->tail = (rb->tail + 1) % LENGTH;
return true;
}
/*
* Note: dequeue is called by reader. dequeue advances rb->head,
* no changes to rb->tail. This design allows safe concurrent access.
*/
bool rb_dequeue(rb_t *rb, int *p_elem) {
assert(rb != NULL && p_elem != NULL);
if (rb_empty(rb)) {
return false;
}
*p_elem = rb->entries[rb->head];
rb->head = (rb->head + 1) % LENGTH;
return true;
}
/*
/* File: start.s
* -------------
* These asm instuctions go first in binary image, they will be
* the first to be executed in a newly loaded program.
*
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
*/
.attribute arch, "rv64im_zicsr"
# Identify this section to linker script memmap.ld
.section ".text.start"
.type _start, @function
.globl _start
.cfi_startproc
.cfi_undefined ra # tell gdb this is entry point, has no caller
_start:
csrc mstatus, 1<<3 # global disable interrupts, mstatus.mie = 0
la t0,_trap_handler
csrw mtvec,t0 # install trap handler
.globl _start_gdb
_start_gdb: # gdb entry set here to skip over csr insns that are unavail in sim
li fp,0 # init fp
la sp,__stack_top # init sp (stack grows down)
jal _cstart
hang: j hang # backstop at end of instructions
.cfi_endproc
.size _start, .-_start
/*
* Hardware abstractions for a serial port (UART).
*
* Author: Julie Zelenski <zelenski@cs.stanford.edu>
* Last updated: Wed Dec 27 16:28:18 PST 2023
*/
#include "uart.h"
#include <stddef.h>
#include "assert.h"
#include "ccu.h"
#include "gpio.h"
#include "gpio_extra.h"
#include "interrupts.h"
#include "_system.h"
#define LCR_DLAB (1 << 7)
#define USR_BUSY (1 << 0)
#define USR_TX_NOT_FULL (1 << 1)
#define USR_RX_NOT_EMPTY (1 << 3)
/*
* D1 uart has 6 UART controllers (UART0, UART1, UART2, UART3, UART4, UART5)
* Compatible with industry-standard 16450/16550
* peripheral registers similar to rpi
* TX/RX fifo, can be interrupt-driven
*/
// structs defined to match layout of hardware registers
typedef union {
struct {
union {
uint32_t rbr; // receive buffer register
uint32_t thr; // transmit holding register
uint32_t dll; // divisor latch (LSB)
};
union {
uint32_t dlh; // divisor latch (MSB)
uint32_t ier; // interrupt enable register
};
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 reserved[25];
uint32_t usr; // busy status, at offset 0x7c
uint32_t reserved2[9];
uint32_t halt; // at offset 0xa4
} regs;
uint8_t padding[0x400];
} uart_t;
#define UART_BASE ((uart_t *)0x02500000)
_Static_assert(&(UART_BASE[0].regs.lcr) == (uint32_t *)0x0250000C, "UART0 lcr reg must be at address 0x0250000C");
_Static_assert(&(UART_BASE[1].regs.dlh) == (uint32_t *)0x02500404, "UART1 dlh reg must be at address 0x02500404");
typedef struct {
int index;
gpio_id_t tx, rx;
unsigned int fn;
} uart_config_t;
static struct {
volatile uart_t *uart_base, *uart;
uart_config_t config;
bool running_in_simulator;
} module = { .uart_base = UART_BASE,
.uart = NULL, // will be set in uart_init
};
// not published for now, used to do testing of alternate uarts
void uart_reinit_custom(int, gpio_id_t, gpio_id_t, unsigned int);
void uart_reinit_custom(int uart_id, gpio_id_t tx, gpio_id_t rx, unsigned int gpio_fn) {
if (module.uart) { // shut down previous if active
uart_flush();
gpio_set_function(module.config.tx, GPIO_FN_DISABLED); // disconnect gpio
gpio_set_pullnone(module.config.tx);
gpio_set_function(module.config.rx, GPIO_FN_DISABLED);
gpio_set_pullnone(module.config.rx);
module.uart = NULL;
}
module.config.index = uart_id;
module.config.tx = tx;
module.config.rx = rx;
module.config.fn = gpio_fn;
module.uart = module.uart_base + module.config.index;
// clock up peripheral
// gating bits [0:5], reset bits [16:21]
uint32_t bit = 1 << module.config.index;
uint32_t reset = bit << 16;
long sys_clock_rate = ccu_ungate_bus_clock_bits(CCU_UART_BGR_REG, bit, reset);
// configure GPIOs
gpio_set_function(module.config.tx, module.config.fn);
gpio_set_pullup(module.config.tx);
gpio_set_function(module.config.rx, module.config.fn);
gpio_set_pullup(module.config.rx);
// configure baud rate
uint32_t baud = 115200;
module.uart->regs.fcr = 1; // enable TX/RX fifo
module.uart->regs.halt = 1; // temporarily disable TX transfer
uint32_t udiv = sys_clock_rate / (16 * baud);
module.uart->regs.lcr |= LCR_DLAB; // set DLAB = 1 to access DLL/DLH
module.uart->regs.dll = udiv & 0xff; // low byte of divisor -> DLL
module.uart->regs.dlh = (udiv >> 8) & 0xff; // hi byte of divisor -> DLH
module.uart->regs.lcr &= ~LCR_DLAB; // set DLAB = 0 to access RBR/THR
module.uart->regs.halt = 0; // re-enable TX transfer
// configure data-parity-stop (low 4 bits of LCR)
uint8_t data = 0b11; // 8 data
uint8_t parity = 0b0; // no parity
uint8_t stop = 0b0; // 1 stop
uint8_t settings = (parity << 3) | (stop << 2) | (data << 0);
// clear low 4 bits, replace with settings 8-n-1
module.uart->regs.lcr = (module.uart->regs.lcr & ~0b1111) | settings;
module.uart->regs.mcr = 0; // disable modem control
module.uart->regs.ier = 0; // disable interrupts by default
}
void uart_init(void) {
static bool initialized = false;
if (initialized) error("uart_init() should be called only once.");
initialized = true;
module.uart = NULL;
// default to UART0 on pins PB8+9
uart_reinit_custom(0, GPIO_PB8, GPIO_PB9, GPIO_FN_ALT6);
module.running_in_simulator = sys_running_in_simulator();
uart_putchar('\f'); // clear terminal window on init
}
void uart_use_interrupts(handlerfn_t handler, void *client_data) {
if (module.uart == NULL) error("uart_init() has not been called!\n");
interrupt_source_t src = INTERRUPT_SOURCE_UART0 + module.config.index;
interrupts_register_handler(src, handler, client_data); // install handler
interrupts_enable_source(src); // turn on source
module.uart->regs.ier = 1; // enable interrupts in uart peripheral
}
unsigned char uart_recv(void) {
if (module.uart == NULL) error("uart_init() has not been called!\n");
if (module.running_in_simulator) {
char byte;
syscall_read(0, &byte, 1); // divert iff under gdb sim
return byte;
} else {
while (!uart_haschar()) ; // wait for char to arrive
return module.uart->regs.rbr & 0xFF;
}
}
void uart_send(char byte) {
if (module.uart == NULL) error("uart_init() has not been called!\n");
if (module.running_in_simulator) {
syscall_write(1, &byte, 1); // divert iff under gdb sim
} else {
while ((module.uart->regs.usr & USR_TX_NOT_FULL) == 0) ;
module.uart->regs.thr = byte;
}
}
void uart_flush(void) {
if (module.uart == NULL) error("uart_init() has not been called!\n");
while ((module.uart->regs.usr & USR_BUSY) != 0) ;
}
bool uart_haschar(void) {
if (module.uart == NULL) error("uart_init() has not been called!\n");
return (module.uart->regs.usr & USR_RX_NOT_EMPTY) != 0;
}
// RE: line endings
// canonial use is '\n' newline as sole line terminator (both read/write)
// but connected terminal may expect to receive a CR-LF sequence from Pi
// and may send a CR to Pi for return/enter key. uart_getchar and uart_putchar
// internally convert chars, client can simply send/receive newline
// Use uart_send/uart_recv to send/receive raw byte, no conversion
int uart_getchar(void) {
if (module.uart == NULL) error("uart_init() has not been called!\n");
int ch = uart_recv();
if (ch == '\r') {
return '\n'; // convert CR to newline
}
return ch;
}
int uart_putchar(int ch) {
if (module.uart == NULL) error("uart_init() has not been called!\n");
if (ch == '\f' && !module.running_in_simulator) { // if formfeed on actual Pi
uart_putstring("\e[2J"); // remap to clear screen sequence
return ch;
}
// convert newline to CR LF sequence by inserting CR first
if (ch == '\n') {
uart_send('\r');
}
uart_send(ch);
return ch;
}
int uart_putstring(const char *str) {
if (module.uart == NULL) error("uart_init() has not been called!\n");
int n = 0;
while (str[n]) {
uart_putchar(str[n++]);
}
return n;
}
void uart_start_error(void) {
if (module.uart == NULL) {
// if uart_init has not been called, there is no serial connection to read/write
// All calls to uart operations are dead ends (that means no printf/assert!)
// Force a call to uart_init here to enable reporting of problem
// (otherwise lack of output is ultra mysterious)
uart_reinit_custom(0, GPIO_PB8, GPIO_PB9, GPIO_FN_ALT6);
}
uart_putstring("\e[31;1m"); // red-bold
}
void uart_end_error(void) {
uart_putstring("\e[0m"); // normal
}