diff --git a/example-colorlight-i5.py b/example-colorlight-i5.py index 30f1e8f..32fdf7e 100755 --- a/example-colorlight-i5.py +++ b/example-colorlight-i5.py @@ -25,6 +25,8 @@ from rtl.pca9635_master import * +from spi_dma import Wishbone2SPIDMA + _io_eurolut_proto1 = [ ("eurorack_pmod_p3b", 0, Subsignal("mclk", Pins("A3")), @@ -175,6 +177,10 @@ def add_oled(soc): spi_master.add_clk_divider() soc.submodules.oled_ctl = GPIOOut(soc.platform.request("oled_ctl")) + # SPI DMA for framebuffer dumping + soc.submodules.spi_dma = Wishbone2SPIDMA() + soc.bus.add_master(master=soc.spi_dma.bus) + def add_pca9635_master(soc): pca9635_master = PCA9635Master(soc.platform, soc.platform.request("pca9635")) soc.add_module("pca9635", pca9635_master) diff --git a/firmware/litex-fw/src/main.rs b/firmware/litex-fw/src/main.rs index 72eeccb..3dec4bc 100644 --- a/firmware/litex-fw/src/main.rs +++ b/firmware/litex-fw/src/main.rs @@ -10,6 +10,7 @@ use riscv_rt::entry; use litex_hal::hal::digital::v2::OutputPin; use heapless::String; use embedded_midi::MidiIn; +use core::arch::asm; use embedded_graphics::{ pixelcolor::{Gray4, GrayColor}, @@ -254,6 +255,15 @@ fn main() -> ! { let mut modif: bool = false; let mut btn_held_ms: u32 = 0; + unsafe { + peripherals.SPI_DMA.spi_control_reg_address.write( + |w| w.bits(litex_pac::OLED_SPI::PTR as u32)); + peripherals.SPI_DMA.spi_status_reg_address.write( + |w| w.bits(litex_pac::OLED_SPI::PTR as u32 + 0x04)); + peripherals.SPI_DMA.spi_mosi_reg_address.write( + |w| w.bits(litex_pac::OLED_SPI::PTR as u32 + 0x08)); + } + loop { Text::with_alignment( @@ -388,13 +398,22 @@ fn main() -> ! { .draw(&mut disp).ok(); } - disp.swap_clear(); + let fb = disp.swap_clear(); + unsafe { + while peripherals.SPI_DMA.done.read().bits() == 0 { + // Don't start to DMA a new framebuffer if we're still + // pushing through the last one. + } + peripherals.SPI_DMA.read_base.write(|w| w.bits(fb.as_ptr() as u32)); + peripherals.SPI_DMA.read_length.write(|w| w.bits(fb.len() as u32)); + peripherals.SPI_DMA.start.write(|w| w.start().bit(true)); + peripherals.SPI_DMA.start.write(|w| w.start().bit(false)); + } let cycle_cnt_now = timer.uptime(); let cycle_cnt_last = cycle_cnt; cycle_cnt = cycle_cnt_now; let delta = (cycle_cnt_now - cycle_cnt_last) as u32; td_us = Some(delta / (SYSTEM_CLOCK_FREQUENCY / 1_000_000u32)); - } } diff --git a/firmware/ssd1322 b/firmware/ssd1322 index e0bab79..8ff05f6 160000 --- a/firmware/ssd1322 +++ b/firmware/ssd1322 @@ -1 +1 @@ -Subproject commit e0bab79aa072254dd40752f0bb15382a91e87f09 +Subproject commit 8ff05f65ceb9bec498f6915534bfdf9e1245c058 diff --git a/spi_dma.py b/spi_dma.py new file mode 100755 index 0000000..d32d371 --- /dev/null +++ b/spi_dma.py @@ -0,0 +1,120 @@ +#!/bin/python3 + +from migen import * + +from litex.soc.interconnect.csr import * +from litex.soc.interconnect import wishbone + +SPI_START = ((8<<8) | (1<<0)) +SPI_LENGTH = (1<<8) +SPI_DONE = (1<<0) + +class Wishbone2SPIDMA(Module, AutoCSR): + def __init__(self): + # Wishbone + self.bus = bus = wishbone.Interface() + + # Control + self.start = CSR() + self.done = CSRStatus() + + # Read parameters: base and length of DMA + self.read_base = CSRStorage(32) + self.read_length = CSRStorage(32) + + # SPI parameters: address of control/status/mosi registers + self.spi_control_reg_address = CSRStorage(32) + self.spi_status_reg_address = CSRStorage(32) + self.spi_mosi_reg_address = CSRStorage(32) + + # # # + + # Shorten CSR's names + start = self.start.re + done = self.done.status + + read_base = self.read_base.storage[2:] + read_length = self.read_length.storage + + spi_mosi_reg_address = self.spi_mosi_reg_address.storage[2:] + spi_control_reg_address = self.spi_control_reg_address.storage[2:] + spi_status_reg_address = self.spi_status_reg_address.storage[2:] + + # internals + word_offset = Signal(32) + byte_offset = Signal(3) + byte_count = Signal(32) + data = Signal(32) + + # fsm + self.submodules.fsm = fsm = FSM() + fsm.act("IDLE", + If(start, + NextValue(word_offset, 0), + NextValue(byte_offset, 0), + NextValue(byte_count, 0), + NextState("WISHBONE-READ-DATA") + ).Else( + done.eq(1), + ) + ) + fsm.act("WISHBONE-READ-DATA", + bus.stb.eq(1), + bus.cyc.eq(1), + bus.sel.eq(2**(bus.data_width//8)-1), + bus.adr.eq(read_base + word_offset), + If(bus.ack, + NextValue(data, bus.dat_r), + NextState("SPI-WRITE-DATA") + ) + ) + fsm.act("SPI-WRITE-DATA", + bus.stb.eq(1), + bus.cyc.eq(1), + bus.we.eq(1), + bus.sel.eq(2**(bus.data_width//8)-1), + bus.adr.eq(spi_mosi_reg_address), + bus.dat_w.eq(data), + If(bus.ack, + NextState("SPI-WRITE-START") + ) + ) + fsm.act("SPI-WRITE-START", + bus.stb.eq(1), + bus.cyc.eq(1), + bus.we.eq(1), + bus.sel.eq(2**(bus.data_width//8)-1), + bus.adr.eq(spi_control_reg_address), + bus.dat_w.eq(SPI_START), + If(bus.ack, + NextState("SPI-WAIT-DONE") + ) + ) + fsm.act("SPI-WAIT-DONE", + bus.stb.eq(1), + bus.cyc.eq(1), + bus.sel.eq(2**(bus.data_width//8)-1), + bus.adr.eq(spi_status_reg_address), + If(bus.ack, + If(bus.dat_r & SPI_DONE, + NextValue(byte_count, byte_count + 1), + NextValue(byte_offset, byte_offset + 1), + NextState("SHIFT-BYTE") + ) + ) + ) + fsm.act("SHIFT-BYTE", + If(byte_count >= read_length, + NextState("IDLE") + ).Elif(byte_offset >= 4, + NextValue(byte_offset, 0), + NextState("INC-WORD-OFFSET") + ).Else( + NextValue(data, data >> 8), + NextState("SPI-WRITE-DATA") + ) + ) + fsm.act("INC-WORD-OFFSET", + NextValue(word_offset, word_offset + 1), + NextState("WISHBONE-READ-DATA") + )