Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DMA functionality #46

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ description = "HAL for the bl602 microcontroller"
bl602-pac = { git = "https://github.com/sipeed/bl602-pac", branch = "main" }
embedded-hal = "=1.0.0-alpha.5"
embedded-time = "0.12.0"
embedded-dma = "0.2.0"
riscv = "0.10.1"
nb = "1.0"
paste = "1.0"
Expand Down
64 changes: 64 additions & 0 deletions examples/serial_dma.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#![no_std]
#![no_main]

use bl602_hal as hal;
use embedded_hal::delay::blocking::DelayMs;
use hal::{
clock::{Strict, SysclkFreq, UART_PLL_FREQ},
dma::single_buffer,
dma::{DMAExt, DMA},
pac,
prelude::*,
serial::*,
};

use panic_halt as _;

#[riscv_rt::entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let mut parts = dp.GLB.split();

// Set up all the clocks we need
let clocks = Strict::new()
.use_pll(40_000_000u32.Hz())
.sys_clk(SysclkFreq::Pll160Mhz)
.uart_clk(UART_PLL_FREQ.Hz())
.freeze(&mut parts.clk_cfg);

// Set up uart output. Since this microcontroller has a pin matrix,
// we need to set up both the pins and the muxs
let pin16 = parts.pin16.into_uart_sig0();
let pin7 = parts.pin7.into_uart_sig7();
let mux0 = parts.uart_mux0.into_uart0_tx();
let mux7 = parts.uart_mux7.into_uart0_rx();

// Configure our UART to 115200Baud, and use the pins we configured above
let mut serial = Serial::new(
dp.UART0,
Config::default().baudrate(115_200.Bd()),
((pin16, mux0), (pin7, mux7)),
clocks,
);
serial.link_dma(false, true);

let dma = DMA::new(dp.DMA);
let channels = dma.split();
let mut channel = channels.ch0;

let mut tx_buf = include_bytes!("serial_dma.txt");

// Create a blocking delay function based on the current cpu frequency
let mut d = bl602_hal::delay::McycleDelay::new(clocks.sysclk().0);

loop {
let dma_config = single_buffer::Config::new(channel, tx_buf, serial);
let dma_transfer = dma_config.start();

// Blocking wait, this can also be done by listening for the *transfer complete* interrupt.
(channel, tx_buf, serial) = dma_transfer.wait();

d.delay_ms(1000).unwrap();
}
}

9 changes: 9 additions & 0 deletions examples/serial_dma.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Sit amet porttitor eget dolor. Et egestas quis ipsum suspendisse ultrices gravida dictum fusce.
Etiam tempor orci eu lobortis elementum nibh tellus molestie.
Hac habitasse platea dictumst vestibulum rhoncus est pellentesque elit.
Tristique sollicitudin nibh sit amet commodo nulla facilisi nullam vehicula.
Iaculis eu non diam phasellus. Odio facilisis mauris sit amet massa.
Nunc congue nisi vitae suscipit tellus mauris a diam maecenas.
Faucibus nisl tincidunt eget nullam non nisi est sit. Mi sit amet mauris commodo.
Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Sapien eget mi proin sed.
6 changes: 6 additions & 0 deletions hal_defaults.x
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@ PROVIDE(Gpio = DefaultHandler);
PROVIDE(TimerCh0 = DefaultHandler);
PROVIDE(TimerCh1 = DefaultHandler);
PROVIDE(Watchdog = DefaultHandler);
PROVIDE(Dma = DefaultHandler);
PROVIDE(Spi = DefaultHandler);
PROVIDE(Uart0 = DefaultHandler);
PROVIDE(Uart1 = DefaultHandler);
PROVIDE(I2c = DefaultHandler);
PROVIDE(Pwm = DefaultHandler);
202 changes: 202 additions & 0 deletions src/dma/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
//! Direct Memory Access
//!
//! Abstraction layer for configuring and using the DMA controller to move data
//! without intervention from the CPU core.
//! The BL602 support 4 independent channels that can transfer data to/from
//! memory and peripherals in various configurations.
//!
//! The current structure of this module has been taken from
//! [rp-hal](https://github.com/rp-rs/rp-hal) and could be subject to change in the future
//! if it needs to be tailored for BL602 specifics (e.g., when implementing the linked list mode).
use crate::typelevel::Sealed;
use core::marker::PhantomData;
use embedded_dma::{ReadBuffer, WriteBuffer};

pub mod single_buffer;
pub mod single_channel;

/// DMA unit.
pub trait DMAExt: Sealed {
/// Splits the DMA unit into its individual channels.
fn split(self) -> Channels;
}

pub struct DMA {
dma: crate::pac::DMA,
}

impl DMA {
/// Enable the DMA engine and construct a new instance
pub fn new(dma: crate::pac::DMA) -> Self {
dma.dma_top_config.modify(|_, w| w.e().set_bit());
Self { dma }
}

/// Disable the DMA engine and free the underlying object
pub fn free(self) -> crate::pac::DMA {
self.dma.dma_top_config.modify(|_, w| w.e().clear_bit());
self.dma
}
}

impl Sealed for DMA {}

/// DMA channel.
pub struct Channel<CH: ChannelIndex> {
_phantom: PhantomData<CH>,
}

/// DMA channel identifier.
pub trait ChannelIndex: Sealed {
/// Numerical index of the DMA channel (0..3).
fn id() -> u8;
}

trait ChannelRegs {
unsafe fn ptr() -> *const crate::pac::dma::CH;
fn regs(&self) -> &crate::pac::dma::CH;
}

macro_rules! channels {
(
$($CHX:ident: ($chX:ident, $x:expr),)+
) => {
impl DMAExt for DMA {
fn split(self) -> Channels {
Channels {
$(
$chX: Channel {
_phantom: PhantomData,
},
)+
}
}
}

/// Set of DMA channels.
pub struct Channels {
$(
/// DMA channel.
pub $chX: Channel<$CHX>,
)+
}
$(
/// DMA channel identifier.
pub struct $CHX;
impl ChannelIndex for $CHX {
fn id() -> u8 {
$x
}
}

impl Sealed for $CHX {}

impl ChannelRegs for Channel<$CHX> {
unsafe fn ptr() -> *const crate::pac::dma::CH {
&(*crate::pac::DMA::ptr()).$chX as *const _
}

fn regs(&self) -> &crate::pac::dma::CH {
unsafe { &*Self::ptr() }
}
}
)+
}
}

channels! {
CH0: (ch0, 0),
CH1: (ch1, 1),
CH2: (ch2, 2),
CH3: (ch3, 3),
}

/// Trait which is implemented by anything that can be read via DMA.
pub trait ReadTarget {
/// Type which is transferred in a single DMA transfer.
type ReceivedWord;

/// Returns the SRCPH number for this data source (`None` for memory buffers).
fn rx_treq() -> Option<u8>;

/// Returns the address and the maximum number of words that can be transferred from this data
/// source in a single DMA operation.
///
/// For peripherals, the count should likely be u32::MAX. If a data source implements
/// EndlessReadTarget, it is suitable for infinite transfers from or to ring buffers. Note that
/// ring buffers designated for endless transfers, but with a finite buffer size, should return
/// the size of their individual buffers here.
///
/// # Safety
///
/// This function has the same safety guarantees as `ReadBuffer::read_buffer`.
fn rx_address_count(&self) -> (u32, u32);

/// Returns whether the address shall be incremented after each transfer.
fn rx_increment(&self) -> bool;
}

/// Marker which signals that `rx_address_count()` can be called multiple times.
///
/// The DMA code will never call `rx_address_count()` to request more than two buffers to configure
/// two DMA channels. In the case of peripherals, the function can always return the same values.
pub trait EndlessReadTarget: ReadTarget {}

impl<B: ReadBuffer> ReadTarget for B {
type ReceivedWord = <B as ReadBuffer>::Word;

fn rx_treq() -> Option<u8> {
None
}

fn rx_address_count(&self) -> (u32, u32) {
let (ptr, len) = unsafe { self.read_buffer() };
(ptr as u32, len as u32)
}

fn rx_increment(&self) -> bool {
true
}
}

/// Trait which is implemented by anything that can be written via DMA.
pub trait WriteTarget {
/// Type which is transferred in a single DMA transfer.
type TransmittedWord;

/// Returns the DSTPH number for this data sink (`None` for memory buffers).
fn tx_treq() -> Option<u8>;

/// Returns the address and the maximum number of words that can be transferred from this data
/// source in a single DMA operation.
///
/// See `ReadTarget::rx_address_count` for a complete description of the semantics of this
/// function.
fn tx_address_count(&mut self) -> (u32, u32);

/// Returns whether the address shall be incremented after each transfer.
fn tx_increment(&self) -> bool;
}

/// Marker which signals that `tx_address_count()` can be called multiple times.
///
/// The DMA code will never call `tx_address_count()` to request more than two buffers to configure
/// two DMA channels. In the case of peripherals, the function can always return the same values.
pub trait EndlessWriteTarget: WriteTarget {}

impl<B: WriteBuffer> WriteTarget for B {
type TransmittedWord = <B as WriteBuffer>::Word;

fn tx_treq() -> Option<u8> {
None
}

fn tx_address_count(&mut self) -> (u32, u32) {
let (ptr, len) = unsafe { self.write_buffer() };
(ptr as u32, len as u32)
}

fn tx_increment(&self) -> bool {
true
}
}
87 changes: 87 additions & 0 deletions src/dma/single_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! Single-buffered or peripheral-peripheral DMA Transfers

use core::sync::atomic::{compiler_fence, Ordering};

use super::{ReadTarget, WriteTarget};
use super::single_channel::{ChannelConfig, SingleChannel};

/// Configuration for single-buffered DMA transfer
pub struct Config<CH: SingleChannel, FROM: ReadTarget, TO: WriteTarget> {
ch: CH,
from: FROM,
to: TO,
}

impl<CH, FROM, TO, WORD> Config<CH, FROM, TO>
where
CH: SingleChannel,
FROM: ReadTarget<ReceivedWord = WORD>,
TO: WriteTarget<TransmittedWord = WORD>,
{
/// Create a new configuration for single-buffered DMA transfer
pub fn new(ch: CH, from: FROM, to: TO) -> Config<CH, FROM, TO> {
Config { ch, from, to }
}

/// Start the DMA transfer
pub fn start(mut self) -> Transfer<CH, FROM, TO> {
// TODO: Do we want to call any callbacks to configure source/sink?

// Make sure that memory contents reflect what the user intended.
// TODO: How much of the following is necessary?
compiler_fence(Ordering::SeqCst);

// Configure the DMA channel and start it.
self.ch.config(&self.from, &mut self.to);
self.ch.start();

Transfer {
ch: self.ch,
from: self.from,
to: self.to,
}
}
}

// TODO: Drop for most of these structs
/// Instance of a single-buffered DMA transfer
pub struct Transfer<CH: SingleChannel, FROM: ReadTarget, TO: WriteTarget> {
ch: CH,
from: FROM,
to: TO,
}

impl<CH, FROM, TO, WORD> Transfer<CH, FROM, TO>
where
CH: SingleChannel,
FROM: ReadTarget<ReceivedWord = WORD>,
TO: WriteTarget<TransmittedWord = WORD>,
{
/// Check if an interrupt is pending for this channel
/// and clear the corresponding pending bit
pub fn check_tc_irq(&mut self) -> bool {
self.ch.check_tc_irq()
}

/// Check if an interrupt is pending for this channel
/// and clear the corresponding pending bit
pub fn check_err_irq(&mut self) -> bool {
self.ch.check_err_irq()
}

pub fn is_done(&self) -> bool {
!self.ch.is_enabled()
// let status = unsafe { (*crate::pac::DMA::ptr()).dma_int_tcstatus.read().bits() };
// status != 0
}

/// Block until the transfer is complete, returning the channel and targets
pub fn wait(self) -> (CH, FROM, TO) {
while !self.is_done() {}

// Make sure that memory contents reflect what the user intended.
compiler_fence(Ordering::SeqCst);

(self.ch, self.from, self.to)
}
}
Loading