diff --git a/.gitignore b/.gitignore index c2ede3414..8573d747a 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,5 @@ Mkfile.old dkms.conf .*/ + +exosphere/bpmpfw/out/* \ No newline at end of file diff --git a/exosphere/bpmpfw/Makefile b/exosphere/bpmpfw/Makefile new file mode 100644 index 000000000..ffee95e1f --- /dev/null +++ b/exosphere/bpmpfw/Makefile @@ -0,0 +1,66 @@ +rwildcard = $(foreach d, $(wildcard $1*), $(filter $(subst *, %, $2), $d) $(call rwildcard, $d/, $2)) + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/base_tools + +name := $(shell basename $(CURDIR)) + +dir_source := src +dir_build := build +dir_out := out + +ASFLAGS := + +CFLAGS = \ + -Iinclude \ + -Iinclude/compat \ + -march=armv8-a \ + -mlittle-endian \ + -fno-stack-protector \ + -fno-common \ + -fno-builtin \ + -fno-inline \ + -ffreestanding \ + -std=gnu99 \ + -Werror \ + -Wall \ + -Wno-error=unused-variable + +LDFLAGS := -nostartfiles -Wl,--nmagic + +objects = $(patsubst $(dir_source)/%.s, $(dir_build)/%.o, \ + $(patsubst $(dir_source)/%.c, $(dir_build)/%.o, \ + $(call rwildcard, $(dir_source), *.s *.c))) + +define bin2o + bin2s $< | $(AS) -o $(@) +endef + +.PHONY: all +all: $(dir_out)/$(name).bin + +.PHONY: clean +clean: + @rm -rf $(dir_build) + @rm -rf $(dir_out) + +$(dir_out)/$(name).bin: $(dir_build)/$(name).elf + @mkdir -p "$(@D)" + $(OBJCOPY) -S -O binary $< $@ + +$(dir_build)/$(name).elf: $(bundled) $(objects) + $(LINK.o) -T linker.ld $(OUTPUT_OPTION) $^ + +$(dir_build)/%.bin.o: $(dir_build)/%.bin + @$(bin2o) + +$(dir_build)/%.o: $(dir_source)/%.c + @mkdir -p "$(@D)" + $(COMPILE.c) $(OUTPUT_OPTION) $< + +$(dir_build)/%.o: $(dir_source)/%.s + @mkdir -p "$(@D)" + $(COMPILE.s) $(OUTPUT_OPTION) $< diff --git a/exosphere/bpmpfw/linker.ld b/exosphere/bpmpfw/linker.ld new file mode 100644 index 000000000..fd4272593 --- /dev/null +++ b/exosphere/bpmpfw/linker.ld @@ -0,0 +1,21 @@ +OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") +OUTPUT_ARCH(arm) + +ENTRY(_start) +SECTIONS +{ + . = 0x40003000; + + __start__ = ABSOLUTE(.); + + .text : ALIGN(4) { *(.text.start) *(.text*); . = ALIGN(4); } + .rodata : ALIGN(4) { *(.rodata*); . = ALIGN(4); } + .bss : ALIGN(8) { __bss_start__ = .; *(.bss* COMMON); . = ALIGN(8); __bss_end__ = .; } + + . = ALIGN(4); + + __end__ = ABSOLUTE(.); + + __stack_top__ = 0x40005000; + __stack_bottom__ = 0x40004000; +} \ No newline at end of file diff --git a/exosphere/bpmpfw/src/emc.c b/exosphere/bpmpfw/src/emc.c new file mode 100644 index 000000000..3bb220c0a --- /dev/null +++ b/exosphere/bpmpfw/src/emc.c @@ -0,0 +1,58 @@ +#include + +#include "lp0.h" +#include "emc.h" +#include "pmc.h" +#include "timer.h" + +void emc_trigger_timing_update(void) { + EMC_TIMING_CONTROL_0 = 1; + while (EMC_EMC_STATUS_0 & 0x800000) { + /* Wait until TIMING_UPDATE_STALLED is unset. */ + } +} + +/* Puts DRAM into self refresh mode. */ +void emc_put_dram_in_self_refresh_mode(void) { + /* Verify CH1_ENABLE [PMC]. */ + if (!(EMC_FBIO_CFG7_0 & 4)) { + reboot(); + } + + /* Clear config. */ + EMC_CFG_0 = 0; + emc_trigger_timing_update(); + timer_wait(5); + + /* Set calibration intervals. */ + EMC_ZCAL_INTERVAL_0 = 0; + EMC_AUTO_CAL_CONFIG_0 = 0x600; /* AUTO_CAL_MEASURE_STALL | AUTO_CAL_UPDATE_STALL */ + + /* If EMC0 mirror is set, clear digital DLL. */ + if (EMC0_CFG_DIG_DLL_0 & 1) { + EMC_CFG_DIG_DLL_0 &= 0xFFFFFFFE; + emc_trigger_timing_update(); + while (EMC0_CFG_DIG_DLL_0 & 1) { /* Wait for EMC0 to clear. */ } + while (EMC1_CFG_DIG_DLL_0 & 1) { /* Wait for EMC1 to clear. */ } + } else { + emc_trigger_timing_update(); + } + + /* Stall all transactions to DRAM. */ + EMC_REQ_CTRL_0 = 3; /* STALL_ALL_WRITES | STALL_ALL_READS. */ + while (!(EMC0_EMC_STATUS_0 & 4)) { /* Wait for NO_OUTSTANDING_TRANSACTIONS for EMC0. */ } + while (!(EMC1_EMC_STATUS_0 & 4)) { /* Wait for NO_OUTSTANDING_TRANSACTIONS for EMC1. */ } + + /* Enable Self-Refresh Mode. */ + EMC_SELF_REF_0 |= 1; + + + /* Wait until we see the right devices in self refresh mode. */ + uint32_t num_populated_devices = 1; + if (EMC_ADR_CFG_0) { + num_populated_devices = 3; + } + + while (((EMC0_EMC_STATUS_0 >> 8) & 3) != num_populated_devices) { /* Wait for EMC0 DRAM_IN_SELF_REFRESH to be correct. */ } + while (((EMC1_EMC_STATUS_0 >> 8) & 3) != num_populated_devices) { /* Wait for EMC1 DRAM_IN_SELF_REFRESH to be correct. */ } +} \ No newline at end of file diff --git a/exosphere/bpmpfw/src/emc.h b/exosphere/bpmpfw/src/emc.h new file mode 100644 index 000000000..5cd5f07b7 --- /dev/null +++ b/exosphere/bpmpfw/src/emc.h @@ -0,0 +1,53 @@ +#ifndef EXOSPHERE_BPMPFW_EMC_H +#define EXOSPHERE_BPMPFW_EMC_H + +#include + +#define EMC_BASE (0x7001B000) + +#define EMC0_BASE (0x7001E000) +#define EMC1_BASE (0x7001F000) + + +#define MAKE_EMC_REG(ofs) ((*((volatile uint32_t *)(EMC_BASE + ofs)))) + +#define MAKE_EMC0_REG(ofs) ((*((volatile uint32_t *)(EMC0_BASE + ofs)))) +#define MAKE_EMC1_REG(ofs) ((*((volatile uint32_t *)(EMC1_BASE + ofs)))) + +#define EMC_CFG_0 MAKE_EMC_REG(0x00C) + +#define EMC_ADR_CFG_0 MAKE_EMC_REG(0x10) + +#define EMC_TIMING_CONTROL_0 MAKE_EMC_REG(0x028) + +#define EMC_SELF_REF_0 MAKE_EMC_REG(0x0E0) + +#define EMC_MRW_0 MAKE_EMC_REG(0x0E8) + +#define EMC_FBIO_CFG5_0 MAKE_EMC_REG(0x104) + +#define EMC_MRW3_0 MAKE_EMC_REG(0x138) + +#define EMC_AUTO_CAL_CONFIG_0 MAKE_EMC_REG(0x2A4) + +#define EMC_REQ_CTRL_0 MAKE_EMC_REG(0x2B0) + +#define EMC_EMC_STATUS_0 MAKE_EMC_REG(0x2B4) +#define EMC0_EMC_STATUS_0 MAKE_EMC0_REG(0x2B4) +#define EMC1_EMC_STATUS_0 MAKE_EMC1_REG(0x2B4) + +#define EMC_CFG_DIG_DLL_0 MAKE_EMC_REG(0x2BC) +#define EMC0_CFG_DIG_DLL_0 MAKE_EMC0_REG(0x2BC) +#define EMC1_CFG_DIG_DLL_0 MAKE_EMC1_REG(0x2BC) + +#define EMC_ZCAL_INTERVAL_0 MAKE_EMC_REG(0x2E0) + +#define EMC_PMC_SCRATCH3_0 MAKE_EMC_REG(0x448) + +#define EMC_FBIO_CFG7_0 MAKE_EMC_REG(0x584) + + + +void emc_put_dram_in_self_refresh_mode(void); + +#endif \ No newline at end of file diff --git a/exosphere/bpmpfw/src/i2c.c b/exosphere/bpmpfw/src/i2c.c new file mode 100644 index 000000000..75dc76b31 --- /dev/null +++ b/exosphere/bpmpfw/src/i2c.c @@ -0,0 +1,83 @@ +#include "i2c.h" +#include "timer.h" + +/* Load hardware config for I2C4. */ +void i2c_load_config(void) { + /* Set MSTR_CONFIG_LOAD, TIMEOUT_CONFIG_LOAD, undocumented bit. */ + I2C_I2C_CONFIG_LOAD_0 = 0x25; + + /* Wait a bit for master config to be loaded. */ + for (unsigned int i = 0; i < 20; i++) { + timer_wait(1); + if (!(I2C_I2C_CONFIG_LOAD_0 & 1)) { + break; + } + } +} + +/* Initialize I2C4. */ +void i2c_init(void) { + /* Setup divisor, and clear the bus. */ + I2C_I2C_CLK_DIVISOR_REGISTER_0 = 0x50001; + I2C_I2C_BUS_CLEAR_CONFIG_0 = 0x90003; + + /* Load hardware configuration. */ + i2c_load_config(); + + /* Wait a while until BUS_CLEAR_DONE is set. */ + for (unsigned int i = 0; i < 10; i++) { + timer_wait(20000); + if (I2C_INTERRUPT_STATUS_REGISTER_0 & 0x800) { + break; + } + } + + /* Read the BUS_CLEAR_STATUS. Result doesn't matter. */ + uint32_t unused_clear_status = I2C_I2C_BUS_CLEAR_STATUS_0; + + /* Read and set the Interrupt Status. */ + uint32_t int_status = I2C_INTERRUPT_STATUS_REGISTER_0; + I2C_INTERRUPT_STATUS_REGISTER_0 = int_status; +} + +/* Writes a value to an i2c device. */ +int i2c_write(unsigned int device, uint32_t val, unsigned int num_bytes) { + if (num_bytes > 4) { + return 0; + } + + /* Set device for 7-bit mode. */ + I2C_I2C_CMD_ADDR0_0 = device << 1; + + /* Load in data to write. */ + I2C_I2C_CMD_DATA1_0 = val; + + /* Set config with LENGTH = num_bytes, NEW_MASTER_FSM, DEBOUNCE_CNT = 4T. */ + I2C_I2C_CNFG_0 = ((num_bytes << 1) - 2) | 0x2800; + + i2c_load_config(); + + /* Config |= SEND; */ + I2C_I2C_CNFG_0 |= 0x200; + + + while (I2C_I2C_STATUS_0 & 0x100) { + /* Wait until not busy. */ + } + + /* Return CMD1_STAT == SL1_XFER_SUCCESSFUL. */ + return (I2C_I2C_STATUS_0 & 7) == 0; +} + +/* Writes a byte val to reg for given device. */ +int i2c_send_byte_command(unsigned int device, unsigned char reg, unsigned char b) { + uint32_t val = (reg) | (b << 8); + /* Write 1 byte (reg) + 1 byte (value) */ + return i2c_write(device, val, 2); +} + +/* Actually reset device 27. This might turn off the screen? */ +int i2c_send_reset_cmd(void) { + /* Write 00 to Device 27 Reg 00. */ + return i2c_send_byte_command(27, 0, 0); +} \ No newline at end of file diff --git a/exosphere/bpmpfw/src/i2c.h b/exosphere/bpmpfw/src/i2c.h new file mode 100644 index 000000000..be656ee1e --- /dev/null +++ b/exosphere/bpmpfw/src/i2c.h @@ -0,0 +1,34 @@ +#ifndef EXOSPHERE_BPMPFW_I2C_H +#define EXOSPHERE_BPMPFW_I2C_H + +#include + +/* I2C_BASE = I2C4. */ +#define I2C_BASE (0x7000D000) + +#define MAKE_I2C_REG(ofs) ((*((volatile uint32_t *)(I2C_BASE + ofs)))) + +#define I2C_I2C_CNFG_0 MAKE_I2C_REG(0x000) + +#define I2C_I2C_CMD_ADDR0_0 MAKE_I2C_REG(0x004) + +#define I2C_I2C_CMD_DATA1_0 MAKE_I2C_REG(0x00C) + +#define I2C_I2C_STATUS_0 MAKE_I2C_REG(0x01C) + +#define I2C_INTERRUPT_STATUS_REGISTER_0 MAKE_I2C_REG(0x068) + +#define I2C_I2C_CLK_DIVISOR_REGISTER_0 MAKE_I2C_REG(0x06C) + +#define I2C_I2C_BUS_CLEAR_CONFIG_0 MAKE_I2C_REG(0x084) + +#define I2C_I2C_BUS_CLEAR_STATUS_0 MAKE_I2C_REG(0x088) + + +#define I2C_I2C_CONFIG_LOAD_0 MAKE_I2C_REG(0x08C) + +void i2c_init(void); + +int i2c_send_reset_cmd(void); + +#endif \ No newline at end of file diff --git a/exosphere/bpmpfw/src/lp0.c b/exosphere/bpmpfw/src/lp0.c new file mode 100644 index 000000000..adaad0fff --- /dev/null +++ b/exosphere/bpmpfw/src/lp0.c @@ -0,0 +1,105 @@ +#include + +#include "lp0.h" +#include "i2c.h" +#include "pmc.h" +#include "emc.h" +#include "timer.h" + +#define CACHE_CTRL (*((volatile uint32_t *)0x50040000)) + +#define PRI_ICTLR_COP_IER_CLR_0 (*((volatile uint32_t *)0x60004038)) +#define SEC_ICTLR_COP_IER_CLR_0 (*((volatile uint32_t *)0x60004138)) +#define TRI_ICTLR_COP_IER_CLR_0 (*((volatile uint32_t *)0x60004238)) +#define QUAD_ICTLR_COP_IER_CLR_0 (*((volatile uint32_t *)0x60004338)) +#define PENTA_ICTLR_COP_IER_CLR_0 (*((volatile uint32_t *)0x60004438)) +#define HEXA_ICTLR_COP_IER_CLR_0 (*((volatile uint32_t *)0x60004538)) + +void reboot(void) { + /* Write MAIN_RST */ + APBDEV_PMC_CNTRL_0 = 0x10; + while (1) { + /* Wait for reboot. */ + } +} + + +void set_pmc_dpd_io_pads(void) { + /* Read val from EMC_PMC scratch, configure accordingly. */ + uint32_t emc_pmc_val = EMC_PMC_SCRATCH3_0; + APBDEV_PMC_DDR_CNTRL_0 = emc_pmc_val & 0x7FFFF; + if (emc_pmc_val & 0x40000000) { + APBDEV_PMC_WEAK_BIAS_0 = 0x7FFF0000; + } + /* Request to put pads in Deep Power Down. */ + APBDEV_PMC_IO_DPD3_REQ_0 = 0x8FFFFFFF; + while (APBDEV_PMC_IO_DPD3_STATUS_0 != 0xFFFFFFF) { /* Wait a while. */ } + spinlock_wait(32); + APBDEV_PMC_IO_DPD4_REQ_0 = 0x8FFFFFFF; + while (APBDEV_PMC_IO_DPD4_STATUS_0 != 0xFFF1FFF) { /* Wait a while. */ } + spinlock_wait(32); +} + +void lp0_entry_main(void) { + /* Disable the BPMP Cache. */ + CACHE_CTRL |= 0xC00; + + /* Wait until the CPU Rail is turned off. */ + while (APBDEV_PMC_PWRGATE_STATUS_0 & 1) { /* Wait for TrustZone to finish. */ } + + /* Clamp the CPU Rail. */ + APBDEV_PMC_SET_SW_CLAMP_0 |= 0x1; + while (!(APBDEV_PMC_CLAMP_STATUS_0 & 1)) { /* Wait for CPU Rail to be clamped. */ } + + /* Waste some time. */ + spinlock_wait(10); + + /* Reset device 27 over I2C, then wait a while. */ + i2c_init(); + i2c_send_reset_cmd(); + timer_wait(700); + + /* Clear Interrupt Enable for BPMP in all ICTLRs. */ + PRI_ICTLR_COP_IER_CLR_0 = 0xFFFFFFFF; + SEC_ICTLR_COP_IER_CLR_0 = 0xFFFFFFFF; + TRI_ICTLR_COP_IER_CLR_0 = 0xFFFFFFFF; + QUAD_ICTLR_COP_IER_CLR_0 = 0xFFFFFFFF; + PENTA_ICTLR_COP_IER_CLR_0 = 0xFFFFFFFF; + HEXA_ICTLR_COP_IER_CLR_0 = 0xFFFFFFFF; + + /* Write EMC's DRAM op into PMC scratch. */ + if ((EMC_FBIO_CFG5_0 & 3) != 1) { + /* If DRAM_TYPE != LPDDR4, something's gone wrong. Reboot. */ + reboot(); + } + /* Write MRW3_OP into scratch. */ + APBDEV_PMC_SCRATCH18_0 = (APBDEV_PMC_SCRATCH18_0 & 0xFFFFFF3F) | (EMC_MRW3_0 & 0xC0); + uint32_t mrw3_op = ((EMC_MRW3_0 & 0xC0) << 8) | (EMC_MRW3_0 & 0xC0); + APBDEV_PMC_SCRATCH12_0 = (APBDEV_PMC_SCRATCH12_0 & 0xFFFF3F3F) | mrw3_op; + APBDEV_PMC_SCRATCH13_0 = (APBDEV_PMC_SCRATCH13_0 & 0xFFFF3F3F) | mrw3_op; + + /* Ready DRAM for deep sleep. */ + emc_put_dram_in_self_refresh_mode(); + + /* Setup LPDDR MRW based on device config. */ + EMC_MRW_0 = 0x88110000; + if (EMC_ADR_CFG_0 & 1) { + EMC_MRW_0 = 0x48110000; + } + + /* Put IO pads in Deep Power Down. */ + set_pmc_dpd_io_pads(); + + /* Enable pad sampling during deep sleep. */ + APBDEV_PMC_DPD_SAMPLE_0 |= 1; + + /* Waste some more time. */ + spinlock_wait(0x128); + + /* Enter deep sleep. */ + APBDEV_PMC_DPD_ENABLE_0 |= 1; + + while (1) { /* Wait until we're asleep. */ } +} + + diff --git a/exosphere/bpmpfw/src/lp0.h b/exosphere/bpmpfw/src/lp0.h new file mode 100644 index 000000000..66018d217 --- /dev/null +++ b/exosphere/bpmpfw/src/lp0.h @@ -0,0 +1,8 @@ +#ifndef EXOSPHERE_BPMPFW_LP0_H +#define EXOSPHERE_BPMPFW_LP0_H + +void lp0_entry_main(void); + +void reboot(void); + +#endif \ No newline at end of file diff --git a/exosphere/bpmpfw/src/pmc.h b/exosphere/bpmpfw/src/pmc.h new file mode 100644 index 000000000..efb0a2c69 --- /dev/null +++ b/exosphere/bpmpfw/src/pmc.h @@ -0,0 +1,37 @@ +#ifndef EXOSPHERE_BPMPFW_PMC_H +#define EXOSPHERE_BPMPFW_PMC_H + +#include + +#define PMC_BASE (0x7000E400) + +#define MAKE_PMC_REG(ofs) ((*((volatile uint32_t *)(PMC_BASE + ofs)))) + +#define APBDEV_PMC_CNTRL_0 MAKE_PMC_REG(0x000) + +#define APBDEV_PMC_DPD_SAMPLE_0 MAKE_PMC_REG(0x020) + +#define APBDEV_PMC_DPD_ENABLE_0 MAKE_PMC_REG(0x024) + +#define APBDEV_PMC_CLAMP_STATUS_0 MAKE_PMC_REG(0x02C) + +#define APBDEV_PMC_PWRGATE_STATUS_0 MAKE_PMC_REG(0x038) + +#define APBDEV_PMC_SCRATCH12_0 MAKE_PMC_REG(0x080) +#define APBDEV_PMC_SCRATCH13_0 MAKE_PMC_REG(0x084) +#define APBDEV_PMC_SCRATCH18_0 MAKE_PMC_REG(0x098) + + +#define APBDEV_PMC_WEAK_BIAS_0 MAKE_PMC_REG(0x2C8) + +#define APBDEV_PMC_IO_DPD3_REQ_0 MAKE_PMC_REG(0x45C) +#define APBDEV_PMC_IO_DPD3_STATUS_0 MAKE_PMC_REG(0x460) + +#define APBDEV_PMC_IO_DPD4_REQ_0 MAKE_PMC_REG(0x464) +#define APBDEV_PMC_IO_DPD4_STATUS_0 MAKE_PMC_REG(0x468) + +#define APBDEV_PMC_SET_SW_CLAMP_0 MAKE_PMC_REG(0x47C) + +#define APBDEV_PMC_DDR_CNTRL_0 MAKE_PMC_REG(0x4E4) + +#endif \ No newline at end of file diff --git a/exosphere/bpmpfw/src/start.s b/exosphere/bpmpfw/src/start.s new file mode 100644 index 000000000..afad59f54 --- /dev/null +++ b/exosphere/bpmpfw/src/start.s @@ -0,0 +1,28 @@ +.section .text.start +.align 4 +.global _start +_start: + b crt0 +.global _reboot + b reboot + +.global crt0 +.type crt0, %function +crt0: + @ setup to call lp0_entry_main + msr cpsr_f, #0xC0 + msr cpsr_cf, #0xD3 + ldr sp, =__stack_top__ + ldr lr, =reboot + bl lp0_entry_main + infloop: + b infloop + + +.global spinlock_wait +.type spinlock_wait, %function +spinlock_wait: + sub r0, r0, #1 + cmp r0, #0 + bgt spinlock_wait + bx lr \ No newline at end of file diff --git a/exosphere/bpmpfw/src/timer.h b/exosphere/bpmpfw/src/timer.h new file mode 100644 index 000000000..abb92c434 --- /dev/null +++ b/exosphere/bpmpfw/src/timer.h @@ -0,0 +1,15 @@ +#ifndef EXOSPHERE_BPMPFW_TIMER_H +#define EXOSPHERE_BPMPFW_TIMER_H + +#define TIMERUS_CNTR_1US_0 (*((volatile uint32_t *)(0x60005010))) + +static inline void timer_wait(uint32_t microseconds) { + uint32_t old_time = TIMERUS_CNTR_1US_0; + while (TIMERUS_CNTR_1US_0 - old_time <= microseconds) { + /* Spin-lock. */ + } +} + +void spinlock_wait(uint32_t count); + +#endif \ No newline at end of file