/* Copyright © 2019 Michal Schulz https://github.com/michalsc This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include "devicetree.h" #include "support_rpi.h" #include "mmu.h" #include "tlsf.h" #ifdef PISTORM #include "ps_protocol.h" #endif static int serial_up = 0; #define ARM_PERIIOBASE ((intptr_t)io_base) #ifdef PISTORM uint8_t *q_buffer; volatile uint64_t q_head; volatile uint64_t q_tail; #define Q_SIZE (16*1024*1024) void q_push(uint8_t data) { while(q_tail + Q_SIZE <= q_head) asm volatile("yield"); q_buffer[q_head & (Q_SIZE - 1)] = data; __sync_add_and_fetch(&q_head, 1); asm volatile("sev"); } uint8_t q_pop() { while (q_tail == q_head) { asm volatile("wfe"); } uint8_t data = q_buffer[q_tail & (Q_SIZE - 1)]; __sync_add_and_fetch(&q_tail, 1); return data; } int redirect = 0; int fast_serial = 0; void putByte(void *io_base, char chr) { (void)io_base; if (redirect) { if (chr == '\n') q_push('\r'); q_push(chr); } else { if (fast_serial) { if (chr == '\n') fastSerial_putByte('\r'); fastSerial_putByte(chr); } else { if (chr == '\n') bitbang_putByte('\r'); bitbang_putByte(chr); } } } void serial_writer() { redirect = 1; if (fast_serial) { while(1) { fastSerial_putByte(q_pop()); } } else { while(1) { bitbang_putByte(q_pop()); } } } #else static inline void waitSerOUT(void *io_base) { while(1) { if ((rd32le(PL011_0_BASE + PL011_FR) & PL011_FR_TXFF) == 0) break; } } static inline void putByte(void *io_base, char chr) { if (serial_up) { waitSerOUT(io_base); if (chr == '\n') { wr32le(PL011_0_BASE + PL011_DR, '\r'); waitSerOUT(io_base); } wr32le(PL011_0_BASE + PL011_DR, (uint8_t)chr); waitSerOUT(io_base); } } #endif #undef ARM_PERIIOBASE #define ARM_PERIIOBASE 0xf2000000 volatile unsigned char print_lock = 0; void kprintf(const char * restrict format, ...) { va_list v; va_start(v, format); while(__atomic_test_and_set(&print_lock, __ATOMIC_ACQUIRE)) asm volatile("yield"); vkprintf_pc(putByte, (void*)ARM_PERIIOBASE, format, v); __atomic_clear(&print_lock, __ATOMIC_RELEASE); va_end(v); } void vkprintf(const char * restrict format, va_list args) { while(__atomic_test_and_set(&print_lock, __ATOMIC_ACQUIRE)) asm volatile("yield"); vkprintf_pc(putByte, (void*)ARM_PERIIOBASE, format, args); __atomic_clear(&print_lock, __ATOMIC_RELEASE); } /* status register flags */ #define MBOX_TX_FULL (1UL << 31) #define MBOX_RX_EMPTY (1UL << 30) #define MBOX_CHANMASK 0xF /* VideoCore tags used. */ #define VCTAG_GET_ARM_MEMORY 0x00010005 #define VCTAG_GET_CLOCK_RATE 0x00030002 #define VCCLOCK_PIXEL 9 /*----------------------------------------------------------------------------*/ static uint32_t mbox_recv(uint32_t channel) { volatile uint32_t *mbox_read = (uint32_t*)(ARM_PERIIOBASE + 0xB880); volatile uint32_t *mbox_status = (uint32_t*)(ARM_PERIIOBASE + 0xB898); uint32_t response, status; do { do { status = LE32(*mbox_status); dsb(); } while (status & MBOX_RX_EMPTY); dmb(); response = LE32(*mbox_read); dmb(); } while ((response & MBOX_CHANMASK) != channel); return (response & ~MBOX_CHANMASK); } /*----------------------------------------------------------------------------*/ static void mbox_send(uint32_t channel, uint32_t data) { volatile uint32_t *mbox_write = (uint32_t*)(ARM_PERIIOBASE + 0xB8A0); volatile uint32_t *mbox_status = (uint32_t*)(ARM_PERIIOBASE + 0xB898); uint32_t status; data &= ~MBOX_CHANMASK; data |= channel & MBOX_CHANMASK; do { status = LE32(*mbox_status); dsb(); } while (status & MBOX_TX_FULL); dmb(); *mbox_write = LE32(data); } //uint32_t FBReq[128] __attribute__((aligned(16))); uint32_t *FBReq = (uint32_t *)0xffffff9000001000; uint32_t get_clock_rate(uint32_t clock_id) { FBReq[0] = LE32(4*8); // Length FBReq[1] = 0; // Request FBReq[2] = LE32(0x00030002);// GetClockRate FBReq[3] = LE32(8); FBReq[4] = 0; FBReq[5] = LE32(clock_id); FBReq[6] = 0; FBReq[7] = 0; arm_flush_cache((intptr_t)FBReq, 32); mbox_send(8, mmu_virt2phys((intptr_t)FBReq)); mbox_recv(8); return LE32(FBReq[6]); } uint32_t get_max_clock_rate(uint32_t clock_id) { FBReq[0] = LE32(4*8); // Length FBReq[1] = 0; // Request FBReq[2] = LE32(0x00030004);// GetMaxClockRate FBReq[3] = LE32(8); FBReq[4] = 0; FBReq[5] = LE32(clock_id); FBReq[6] = 0; FBReq[7] = 0; arm_flush_cache((intptr_t)FBReq, 32); mbox_send(8, mmu_virt2phys((intptr_t)FBReq)); mbox_recv(8); return LE32(FBReq[6]); } uint32_t get_min_clock_rate(uint32_t clock_id) { FBReq[0] = LE32(4*8); // Length FBReq[1] = 0; // Request FBReq[2] = LE32(0x00030007);// GetClockRate FBReq[3] = LE32(8); FBReq[4] = 0; FBReq[5] = LE32(clock_id); FBReq[6] = 0; FBReq[7] = 0; arm_flush_cache((intptr_t)FBReq, 32); mbox_send(8, mmu_virt2phys((intptr_t)FBReq)); mbox_recv(8); return LE32(FBReq[6]); } uint32_t set_clock_rate(uint32_t clock_id, uint32_t speed) { FBReq[0] = LE32(4*9); // Length FBReq[1] = 0; // Request FBReq[2] = LE32(0x00038002);// SetClockRate FBReq[3] = LE32(12); FBReq[4] = 0; FBReq[5] = LE32(clock_id); FBReq[6] = LE32(speed); FBReq[7] = 0; FBReq[8] = 0; arm_flush_cache((intptr_t)FBReq, 36); mbox_send(8, mmu_virt2phys((intptr_t)FBReq)); mbox_recv(8); return LE32(FBReq[6]); } void get_vc_memory(void **base, uint32_t *size) { FBReq[0] = LE32(4*8); FBReq[1] = 0; FBReq[2] = LE32(0x00010006); FBReq[3] = LE32(8); FBReq[4] = 0; FBReq[5] = 0; FBReq[6] = 0; FBReq[7] = 0; arm_flush_cache((intptr_t)FBReq, 32); mbox_send(8, mmu_virt2phys((intptr_t)FBReq)); mbox_recv(8); if (base) { *base = (void *)(intptr_t)LE32(FBReq[5]); } if (size) { *size = LE32(FBReq[6]); } } struct Size get_display_size() { struct Size sz; FBReq[0] = LE32(4*8); FBReq[1] = 0; FBReq[2] = LE32(0x00040003); FBReq[3] = LE32(8); FBReq[4] = 0; FBReq[5] = 0; FBReq[6] = 0; FBReq[7] = 0; arm_flush_cache((intptr_t)FBReq, 32); mbox_send(8, mmu_virt2phys((intptr_t)FBReq)); mbox_recv(8); sz.width = LE32(FBReq[5]); sz.height = LE32(FBReq[6]); return sz; } void init_display(struct Size dimensions, void **framebuffer, uint32_t *pitch) { int c = 1; int pos_buffer_base = 0; int pos_buffer_pitch = 0; FBReq[c++] = 0; // Request FBReq[c++] = LE32(0x48003); // SET_RESOLUTION FBReq[c++] = LE32(8); FBReq[c++] = 0; FBReq[c++] = LE32(dimensions.width); FBReq[c++] = LE32(dimensions.height); FBReq[c++] = LE32(0x48004); // Virtual resolution: duplicate physical size... FBReq[c++] = LE32(8); FBReq[c++] = 0; FBReq[c++] = LE32(dimensions.width); FBReq[c++] = LE32(dimensions.height); FBReq[c++] = LE32(0x48005); // Set depth FBReq[c++] = LE32(4); FBReq[c++] = LE32(0); FBReq[c++] = LE32(16); FBReq[c++] = LE32(0x40001); // Allocate buffer FBReq[c++] = LE32(8); FBReq[c++] = LE32(0); pos_buffer_base = c; FBReq[c++] = LE32(64); FBReq[c++] = LE32(0); FBReq[c++] = LE32(0x40008); // Get pitch FBReq[c++] = LE32(4); FBReq[c++] = LE32(0); pos_buffer_pitch = c; FBReq[c++] = LE32(0); FBReq[c++] = 0; FBReq[0] = LE32(c << 2); arm_flush_cache((intptr_t)FBReq, c * 4); mbox_send(8, mmu_virt2phys((intptr_t)FBReq)); mbox_recv(8); arm_dcache_invalidate((intptr_t)FBReq, c*4); uint32_t _base = LE32(FBReq[pos_buffer_base]); uint32_t _pitch = LE32(FBReq[pos_buffer_pitch]); if ((_base & 0xc0000000) == 0x40000000) { // Cached buffer } else if ((_base & 0xc0000000) == 0xc0000000) { // Uncached buffer } _base &= ~0xc0000000; if (framebuffer) *framebuffer = (void*)(intptr_t)_base; if (pitch) *pitch = _pitch; } #define PL011_ICR_FLAGS (PL011_ICR_RXIC|PL011_ICR_TXIC|PL011_ICR_RTIC|PL011_ICR_FEIC|PL011_ICR_PEIC|PL011_ICR_BEIC|PL011_ICR_OEIC|PL011_ICR_RIMIC|PL011_ICR_CTSMIC|PL011_ICR_DSRMIC|PL011_ICR_DCDMIC) #define DEF_BAUD 115200 #define PL011_DIVCLOCK(baud, clock) ((clock * 4) / baud) #define PL011_BAUDINT(baud, clock) ((PL011_DIVCLOCK(baud, clock) & 0xFFFFFFC0) >> 6) #define PL011_BAUDFRAC(baud, clock) ((PL011_DIVCLOCK(baud, clock) & 0x0000003F) >> 0) #define GPIO_BASE (ARM_PERIIOBASE + 0x200000) #define GPFSEL1 (GPIO_BASE + 0x4) #define GPPUD (GPIO_BASE + 0x94) #define GPPUDCLK0 (GPIO_BASE + 0x98) #ifdef PISTORM void setup_serial() { of_node_t *e = NULL; serial_up = 1; e = dt_find_node("/chosen"); if (e) { of_property_t * prop = dt_find_property(e, "bootargs"); if (prop) { if (strstr(prop->op_value, "fast_serial")) { fast_serial = 1; fastSerial_init(); } else fast_serial = 0; } } q_buffer = tlsf_malloc(tlsf, Q_SIZE); q_head = 0; q_tail = 0; } #else void setup_serial() { unsigned int uartvar; unsigned const int uartbaud = 115200; unsigned int uartclock; unsigned int uartdivint; unsigned int uartdivfrac; uartclock = get_clock_rate(2); wr32le(PL011_0_BASE + PL011_CR, 0); uartvar = rd32le(GPFSEL1); uartvar &= ~(7<<12); // TX on GPIO14 uartvar |= 4<<12; // alt0 uartvar &= ~(7<<15); // RX on GPIO15 uartvar |= 4<<15; // alt0 wr32le(GPFSEL1, uartvar); /* Disable pull-ups and pull-downs on rs232 lines */ wr32le(GPPUD, 0); for (uartvar = 0; uartvar < 150; uartvar++) asm volatile ("nop"); wr32le(GPPUDCLK0, (1 << 14)|(1 << 15)); for (uartvar = 0; uartvar < 150; uartvar++) asm volatile ("nop"); wr32le(GPPUDCLK0, 0); wr32le(PL011_0_BASE + PL011_ICR, PL011_ICR_FLAGS); uartdivint = PL011_BAUDINT(uartbaud, uartclock); wr32le(PL011_0_BASE + PL011_IBRD, uartdivint); uartdivfrac = PL011_BAUDFRAC(uartbaud, uartclock); wr32le(PL011_0_BASE + PL011_FBRD, uartdivfrac); wr32le(PL011_0_BASE + PL011_LCRH, PL011_LCRH_WLEN8|PL011_LCRH_FEN); // 8N1, Fifo enabled wr32le(PL011_0_BASE + PL011_CR, PL011_CR_UARTEN|PL011_CR_TXE|PL011_CR_RXE); // enable the uart, tx and rx for (uartvar = 0; uartvar < 150; uartvar++) asm volatile ("nop"); serial_up = 1; } #endif