use core::cmp;
use core::convert::TryInto;
use core::marker::PhantomData;
use embedded_hal::blocking::delay::DelayUs;
use crate::fmc::{FmcBank, FmcRegisters};
use crate::FmcPeripheral;
use crate::ral::{fmc, modify_reg, write_reg};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FmcSdramConfiguration {
pub column_bits: u8,
pub row_bits: u8,
pub memory_data_width: u8,
pub internal_banks: u8,
pub cas_latency: u8,
pub write_protection: bool,
pub read_burst: bool,
pub read_pipe_delay_cycles: u8,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FmcSdramTiming {
pub startup_delay_ns: u32,
pub max_sd_clock_hz: u32,
pub refresh_period_ns: u32,
pub mode_register_to_active: u32,
pub exit_self_refresh: u32,
pub active_to_precharge: u32,
pub row_cycle: u32,
pub row_precharge: u32,
pub row_to_column: u32,
}
pub trait SdramChip {
const MODE_REGISTER: u16;
const CONFIG: FmcSdramConfiguration;
const TIMING: FmcSdramTiming;
}
#[allow(missing_debug_implementations)]
pub struct Sdram<FMC, IC> {
target_bank: SdramTargetBank,
fmc_bank: FmcBank,
_chip: PhantomData<IC>,
fmc: FMC,
regs: FmcRegisters,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(unused)]
enum SdramCommand {
NormalMode,
ClkEnable,
Pall,
Autorefresh(u8),
LoadMode(u16),
Selfrefresh,
Powerdown,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(unused)]
pub enum SdramTargetBank {
Bank1,
Bank2,
Both,
}
impl From<u32> for SdramTargetBank {
fn from(n: u32) -> Self {
match n {
1 => SdramTargetBank::Bank1,
2 => SdramTargetBank::Bank2,
_ => unimplemented!(),
}
}
}
pub trait SdramPinSet {
const TARGET: SdramTargetBank;
const FMC: FmcBank;
}
#[derive(Clone, Copy, Debug)]
pub struct SdramBank1;
impl SdramPinSet for SdramBank1 {
const TARGET: SdramTargetBank = SdramTargetBank::Bank1;
const FMC: FmcBank = FmcBank::Bank5;
}
#[derive(Clone, Copy, Debug)]
pub struct SdramBank2;
impl SdramPinSet for SdramBank2 {
const TARGET: SdramTargetBank = SdramTargetBank::Bank2;
const FMC: FmcBank = FmcBank::Bank6;
}
pub trait PinsSdram<Bank: SdramPinSet> {
const ADDRESS_LINES: u8;
const NUMBER_INTERNAL_BANKS: u8;
}
macro_rules! modify_reg_banked {
( $periph:path, $instance:expr, $bank:expr, $reg1:ident, $reg2:ident, $( $field:ident : $value:expr ),+ ) => {{
use SdramTargetBank::*;
match $bank {
Bank1 => modify_reg!( $periph, $instance, $reg1, $( $field : $value ),*),
Bank2 => modify_reg!( $periph, $instance, $reg2, $( $field : $value ),*),
_ => panic!(),
}
}};
}
impl<IC: SdramChip, FMC: FmcPeripheral> Sdram<FMC, IC> {
pub fn new<PINS, BANK>(fmc: FMC, _pins: PINS, _chip: IC) -> Self
where
PINS: PinsSdram<BANK>,
BANK: SdramPinSet,
{
assert!(
PINS::ADDRESS_LINES >= IC::CONFIG.row_bits,
"Not enough address pins to access all SDRAM rows"
);
assert!(
PINS::ADDRESS_LINES >= IC::CONFIG.column_bits,
"Not enough address pins to access all SDRAM colums"
);
assert!(
PINS::NUMBER_INTERNAL_BANKS >= IC::CONFIG.internal_banks,
"Not enough bank address pins to access all internal banks"
);
Sdram {
target_bank: BANK::TARGET,
fmc_bank: BANK::FMC,
_chip: PhantomData,
fmc,
regs: FmcRegisters::new::<FMC>(),
}
}
pub fn new_unchecked(
fmc: FMC,
bank: impl Into<SdramTargetBank>,
_chip: IC,
) -> Self {
let target_bank = bank.into();
let fmc_bank = match target_bank {
SdramTargetBank::Bank1 => FmcBank::Bank5,
SdramTargetBank::Bank2 => FmcBank::Bank6,
_ => unimplemented!(),
};
Sdram {
target_bank,
fmc_bank,
_chip: PhantomData,
fmc,
regs: FmcRegisters::new::<FMC>(),
}
}
pub fn init<D>(&mut self, delay: &mut D) -> *mut u32
where
D: DelayUs<u8>,
{
use SdramCommand::*;
let bank = self.target_bank;
let (sd_clock_hz, divide) = {
let fmc_source_ck_hz = self.fmc.source_clock_hz();
let sd_clock_wanted = IC::TIMING.max_sd_clock_hz;
let divide: u32 = cmp::max(
(fmc_source_ck_hz + sd_clock_wanted - 1) / sd_clock_wanted,
2,
);
assert!(divide <= 3,
"Source clock too fast for required SD_CLOCK. The maximum division ratio is 3");
let sd_clock_hz = fmc_source_ck_hz / divide;
(sd_clock_hz, divide)
};
fmc_trace!(
"FMC clock {:?} (/{}, Max {:?})",
sd_clock_hz,
divide,
IC::TIMING.max_sd_clock_hz
);
unsafe {
self.fmc.enable();
self.set_features_timings(IC::CONFIG, IC::TIMING, divide);
self.fmc.memory_controller_enable();
self.send_command(ClkEnable, bank);
let startup_delay_us = (IC::TIMING.startup_delay_ns + 999) / 1000;
delay.delay_us(startup_delay_us.try_into().unwrap());
self.send_command(Pall, bank);
self.send_command(Autorefresh(8), bank);
self.send_command(LoadMode(IC::MODE_REGISTER), bank);
let refresh_counter_top = ((IC::TIMING.refresh_period_ns as u64
* sd_clock_hz as u64)
/ 1_000_000_000)
- 20;
assert!(
refresh_counter_top >= 41 && refresh_counter_top < (1 << 13),
"Impossible configuration for H7 FMC Controller"
);
modify_reg!(
fmc,
self.regs.global(),
SDRTR,
COUNT: refresh_counter_top as u32
);
}
self.fmc_bank.ptr()
}
unsafe fn set_features_timings(
&mut self,
config: FmcSdramConfiguration,
timing: FmcSdramTiming,
sd_clock_divide: u32,
) {
assert!(
config.cas_latency >= 1 && config.cas_latency <= 3,
"Impossible configuration for FMC Controller"
);
assert!(
config.row_bits >= 11 && config.row_bits <= 13,
"Impossible configuration for FMC Controller"
);
assert!(
config.column_bits >= 8 && config.column_bits <= 11,
"Impossible configuration for FMC Controller"
);
assert!(
config.read_pipe_delay_cycles <= 2,
"Impossible configuration for FMC Controller"
);
modify_reg!(fmc, self.regs.global(), SDCR1,
RPIPE: config.read_pipe_delay_cycles as u32,
RBURST: config.read_burst as u32,
SDCLK: sd_clock_divide);
modify_reg_banked!(fmc, self.regs.global(),
self.target_bank, SDCR1, SDCR2,
WP: config.write_protection as u32,
CAS: config.cas_latency as u32,
NB:
match config.internal_banks {
2 => 0,
4 => 1,
_ => {
panic!("Impossible configuration for FMC Controller")
}
},
MWID:
match config.memory_data_width {
8 => 0,
16 => 1,
32 => 2,
_ => {
panic!("Impossible configuration for FMC Controller")
}
},
NR: config.row_bits as u32 - 11,
NC: config.column_bits as u32 - 8);
let minimum_self_refresh = timing.active_to_precharge;
let write_recovery_self_refresh =
minimum_self_refresh - timing.row_to_column;
let write_recovery_row_cycle =
timing.row_cycle - timing.row_to_column - timing.row_precharge;
let write_recovery =
cmp::max(write_recovery_self_refresh, write_recovery_row_cycle);
modify_reg!(fmc, self.regs.global(), SDTR1,
TRC: timing.row_cycle - 1,
TRP: timing.row_precharge - 1
);
modify_reg_banked!(fmc, self.regs.global(),
self.target_bank, SDTR1, SDTR2,
TRCD: timing.row_to_column - 1,
TWR: write_recovery - 1,
TRAS: minimum_self_refresh - 1,
TXSR: timing.exit_self_refresh - 1,
TMRD: timing.mode_register_to_active - 1
);
}
unsafe fn send_command(
&mut self,
mode: SdramCommand,
target: SdramTargetBank,
) {
use SdramCommand::*;
use SdramTargetBank::*;
let (cmd, number_refresh, mode_reg) = match mode {
NormalMode => (0x00, 1, 0),
ClkEnable => (0x01, 1, 0),
Pall => (0x02, 1, 0),
Autorefresh(a) => (0x03, a, 0),
LoadMode(mr) => (0x04, 1, mr),
Selfrefresh => (0x05, 1, 0),
Powerdown => (0x06, 1, 0),
};
let (b1, b2) = match target {
Bank1 => (1, 0),
Bank2 => (0, 1),
Both => (1, 1),
};
write_reg!(
fmc,
self.regs.global(),
SDCMR,
MRD: mode_reg as u32,
NRFS: number_refresh as u32,
CTB1: b1,
CTB2: b2,
MODE: cmd
);
}
}