1//! HAL for external SDRAM
2
3use core::cmp;
4use core::convert::TryInto;
5use core::marker::PhantomData;
6
7use embedded_hal::blocking::delay::DelayUs;
8
9use crate::fmc::{AddressPinSet, FmcBank, FmcRegisters};
10use crate::FmcPeripheral;
11
12use crate::ral::{fmc, modify_reg, write_reg};
13
14/// FMC SDRAM Configuration Structure definition
15///
16#[cfg_attr(feature = "defmt", derive(defmt::Format))]
17#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct SdramConfiguration {
19 /// Number of bits of column address
20 pub column_bits: u8,
21 /// Number of bits of column address
22 pub row_bits: u8,
23 /// Memory device width
24 pub memory_data_width: u8,
25 /// Number of the device's internal banks
26 pub internal_banks: u8,
27 /// SDRAM CAS latency in number of memory clock cycles
28 pub cas_latency: u8,
29 /// Enables the SDRAM device to be accessed in write mode
30 pub write_protection: bool,
31 /// This bit enable the SDRAM controller to anticipate the next read
32 pub read_burst: bool,
33 /// Delay in system clock cycles on read data path
34 pub read_pipe_delay_cycles: u8,
35}
36
37/// FMC SDRAM Timing parameters structure definition
38#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39#[derive(Clone, Copy, Debug, PartialEq)]
40pub struct SdramTiming {
41 /// Time between applying a valid clock and any command other than
42 /// COMMAND INHIBIT or NOP
43 pub startup_delay_ns: u32,
44 /// Maximum SD clock frequency to make timing
45 pub max_sd_clock_hz: u32,
46 /// Period between refresh cycles in nanoseconds
47 pub refresh_period_ns: u32,
48 /// Delay between a LOAD MODE register command and an ACTIVATE command
49 pub mode_register_to_active: u32,
50 /// Delay from releasing self refresh to next command
51 pub exit_self_refresh: u32,
52 /// Delay between an ACTIVATE and a PRECHARGE command
53 pub active_to_precharge: u32,
54 /// Auto refresh command duration
55 pub row_cycle: u32,
56 /// Delay between a PRECHARGE command and another command
57 pub row_precharge: u32,
58 /// Delay between an ACTIVATE command and READ/WRITE command
59 pub row_to_column: u32,
60}
61
62/// Respresents a model of SDRAM chip
63pub trait SdramChip {
64 /// Value of the mode register
65 const MODE_REGISTER: u16;
66
67 /// SDRAM controller configuration
68 const CONFIG: SdramConfiguration;
69
70 /// Timing parameters
71 const TIMING: SdramTiming;
72}
73
74/// SDRAM Controller
75#[allow(missing_debug_implementations)]
76pub struct Sdram<FMC, IC> {
77 /// SDRAM bank
78 target_bank: SdramTargetBank,
79 /// FMC memory bank to use
80 fmc_bank: FmcBank,
81 /// Parameters for the SDRAM IC
82 _chip: PhantomData<IC>,
83 /// FMC peripheral
84 fmc: FMC,
85 /// Register access
86 regs: FmcRegisters,
87}
88
89/// SDRAM Commands
90#[cfg_attr(feature = "defmt", derive(defmt::Format))]
91#[derive(Clone, Copy, Debug, PartialEq)]
92#[allow(unused)]
93enum SdramCommand {
94 NormalMode,
95 ClkEnable,
96 Pall,
97 Autorefresh(u8),
98 LoadMode(u16),
99 Selfrefresh,
100 Powerdown,
101}
102/// Target bank for SDRAM commands
103#[derive(Clone, Copy, Debug, PartialEq)]
104#[cfg_attr(feature = "defmt", derive(defmt::Format))]
105#[allow(unused)]
106pub enum SdramTargetBank {
107 /// Targeting the 1st SDRAM bank
108 Bank1,
109 /// Targeting the 2nd SDRAM bank
110 Bank2,
111 /// Targeting both SDRAM banks
112 Both,
113}
114impl From<u32> for SdramTargetBank {
115 fn from(n: u32) -> Self {
116 match n {
117 1 => SdramTargetBank::Bank1,
118 2 => SdramTargetBank::Bank2,
119 _ => unimplemented!(),
120 }
121 }
122}
123
124/// SDRAM target bank and corresponding FMC Bank
125pub trait SdramPinSet {
126 /// External SDRAM bank
127 const TARGET: SdramTargetBank;
128 /// Corresponding FMC bank to map this to
129 const FMC: FmcBank;
130}
131
132/// Type to mark SDRAM on Bank 1 of FMC controller
133#[derive(Clone, Copy, Debug)]
134#[cfg_attr(feature = "defmt", derive(defmt::Format))]
135pub struct SdramBank1;
136impl SdramPinSet for SdramBank1 {
137 const TARGET: SdramTargetBank = SdramTargetBank::Bank1;
138 const FMC: FmcBank = FmcBank::Bank5;
139}
140
141/// Type to mark SDRAM on Bank 2 of FMC controller
142#[derive(Clone, Copy, Debug)]
143#[cfg_attr(feature = "defmt", derive(defmt::Format))]
144pub struct SdramBank2;
145impl SdramPinSet for SdramBank2 {
146 const TARGET: SdramTargetBank = SdramTargetBank::Bank2;
147 const FMC: FmcBank = FmcBank::Bank6;
148}
149
150/// Set of pins for an SDRAM, that corresponds to a specific bank
151pub trait PinsSdram<Bank: SdramPinSet, Address: AddressPinSet> {
152 /// The number of SDRAM banks addressable with this set of pins
153 const NUMBER_INTERNAL_BANKS: u8;
154}
155
156/// Like `modfiy_reg`, but applies to bank 1 or 2 based on a varaiable
157macro_rules! modify_reg_banked {
158 ( $periph:path, $instance:expr, $bank:expr, $reg1:ident, $reg2:ident, $( $field:ident : $value:expr ),+ ) => {{
159 use SdramTargetBank::*;
160
161 match $bank {
162 Bank1 => modify_reg!( $periph, $instance, $reg1, $( $field : $value ),*),
163 Bank2 => modify_reg!( $periph, $instance, $reg2, $( $field : $value ),*),
164 _ => panic!(),
165 }
166 }};
167}
168
169impl<IC: SdramChip, FMC: FmcPeripheral> Sdram<FMC, IC> {
170 /// New SDRAM instance
171 ///
172 /// `_pins` must be a set of pins connecting to an SDRAM on the FMC
173 /// controller
174 ///
175 /// # Panics
176 ///
177 /// * Panics if there are not enough address lines in `PINS` to access the
178 /// whole SDRAM
179 ///
180 /// * Panics if there are not enough bank address lines in `PINS` to access
181 /// the whole SDRAM
182 pub fn new<PINS, BANK, ADDR>(fmc: FMC, _pins: PINS, _chip: IC) -> Self
183 where
184 PINS: PinsSdram<BANK, ADDR>,
185 ADDR: AddressPinSet,
186 BANK: SdramPinSet,
187 {
188 assert!(
189 ADDR::ADDRESS_PINS >= IC::CONFIG.row_bits,
190 "Not enough address pins to access all SDRAM rows"
191 );
192 assert!(
193 ADDR::ADDRESS_PINS >= IC::CONFIG.column_bits,
194 "Not enough address pins to access all SDRAM colums"
195 );
196 assert!(
197 PINS::NUMBER_INTERNAL_BANKS >= IC::CONFIG.internal_banks,
198 "Not enough bank address pins to access all internal banks"
199 );
200
201 fmc_trace!("Bank selected via pins: {}.", BANK::TARGET);
202
203 Sdram {
204 target_bank: BANK::TARGET,
205 fmc_bank: BANK::FMC,
206 _chip: PhantomData,
207 fmc,
208 regs: FmcRegisters::new::<FMC>(),
209 }
210 }
211
212 /// New SDRAM instance
213 ///
214 /// `bank` denotes which SDRAM bank to target. This can be either bank 1 or
215 /// bank 2.
216 ///
217 /// # Safety
218 ///
219 /// The pins are not checked against the requirements for the SDRAM chip. So
220 /// you may be able to initialise a SDRAM without enough pins to access the
221 /// whole memory
222 pub fn new_unchecked(
223 fmc: FMC,
224 bank: impl Into<SdramTargetBank>,
225 _chip: IC,
226 ) -> Self {
227 // Select default bank mapping
228 let target_bank = bank.into();
229 let fmc_bank = match target_bank {
230 SdramTargetBank::Bank1 => FmcBank::Bank5,
231 SdramTargetBank::Bank2 => FmcBank::Bank6,
232 _ => unimplemented!(),
233 };
234
235 Sdram {
236 target_bank,
237 fmc_bank,
238 _chip: PhantomData,
239 fmc,
240 regs: FmcRegisters::new::<FMC>(),
241 }
242 }
243
244 /// Initialise SDRAM instance. Delay is used to wait the SDRAM powerup
245 /// delay
246 ///
247 /// Returns a raw pointer to the memory-mapped SDRAM block
248 ///
249 /// # Panics
250 ///
251 /// * Panics if any setting in `IC::CONFIG` cannot be achieved
252 ///
253 /// * Panics if the FMC source clock is too fast for
254 /// maximum SD clock in `IC::TIMING`
255 pub fn init<D>(&mut self, delay: &mut D) -> *mut u32
256 where
257 D: DelayUs<u8>,
258 {
259 use SdramCommand::*;
260
261 // Select bank
262 let bank = self.target_bank;
263
264 // Calcuate SD clock
265 let (sd_clock_hz, divide) = {
266 let fmc_source_ck_hz = self.fmc.source_clock_hz();
267 let sd_clock_wanted = IC::TIMING.max_sd_clock_hz;
268
269 // Divider, round up. At least 2
270 let divide: u32 = cmp::max(
271 (fmc_source_ck_hz + sd_clock_wanted - 1) / sd_clock_wanted,
272 2,
273 );
274
275 // Max 3
276 assert!(divide <= 3,
277 "Source clock too fast for required SD_CLOCK. The maximum division ratio is 3");
278
279 let sd_clock_hz = fmc_source_ck_hz / divide;
280 (sd_clock_hz, divide)
281 };
282
283 fmc_trace!(
284 "FMC clock {:?} (/{}, Max {:?})",
285 sd_clock_hz,
286 divide,
287 IC::TIMING.max_sd_clock_hz
288 );
289
290 unsafe {
291 // Enable memory controller AHB register access
292 self.fmc.enable();
293
294 // Program device features and timing
295 self.set_features_timings(IC::CONFIG, IC::TIMING, divide);
296
297 // Enable memory controller
298 self.fmc.memory_controller_enable();
299
300 // Step 1: Send a clock configuration enable command
301 self.send_command(ClkEnable, bank);
302
303 // Step 2: SDRAM powerup delay
304 let startup_delay_us = (IC::TIMING.startup_delay_ns + 999) / 1000;
305 fmc_trace!("Startup delay: {} us", startup_delay_us);
306
307 delay.delay_us(startup_delay_us.try_into().unwrap());
308
309 // Step 3: Send a PALL (precharge all) command
310 self.send_command(Pall, bank);
311
312 // Step 4: Send eight auto refresh commands
313 self.send_command(Autorefresh(8), bank);
314
315 // Step 5: Program the SDRAM's mode register
316 self.send_command(LoadMode(IC::MODE_REGISTER), bank);
317
318 // Step 6: Set the refresh rate counter
319 // period (ns) * frequency (hz) / 10^9 = count
320 let refresh_counter_top = ((IC::TIMING.refresh_period_ns as u64
321 * sd_clock_hz as u64)
322 / 1_000_000_000)
323 - 20;
324 assert!(
325 refresh_counter_top >= 41 && refresh_counter_top < (1 << 13),
326 "Impossible configuration for H7 FMC Controller"
327 );
328
329 fmc_trace!("SDRTR: count {}", refresh_counter_top);
330
331 modify_reg!(
332 fmc,
333 self.regs.global(),
334 SDRTR,
335 COUNT: refresh_counter_top as u32
336 );
337 }
338
339 #[cfg(feature = "trace-register-values")]
340 {
341 use crate::read_reg;
342 fmc_trace!(
343 "BCR1: 0x{:x}",
344 read_reg!(fmc, self.regs.global(), BCR1)
345 );
346 fmc_trace!(
347 "BTR1: 0x{:x}",
348 read_reg!(fmc, self.regs.global(), BTR1)
349 );
350 fmc_trace!(
351 "BCR2: 0x{:x}",
352 read_reg!(fmc, self.regs.global(), BCR2)
353 );
354 fmc_trace!(
355 "BTR2: 0x{:x}",
356 read_reg!(fmc, self.regs.global(), BTR2)
357 );
358 fmc_trace!(
359 "BCR3: 0x{:x}",
360 read_reg!(fmc, self.regs.global(), BCR3)
361 );
362 fmc_trace!(
363 "BTR3: 0x{:x}",
364 read_reg!(fmc, self.regs.global(), BTR3)
365 );
366 fmc_trace!(
367 "BCR4: 0x{:x}",
368 read_reg!(fmc, self.regs.global(), BCR4)
369 );
370 fmc_trace!(
371 "BTR4: 0x{:x}",
372 read_reg!(fmc, self.regs.global(), BTR4)
373 );
374 fmc_trace!(
375 "SDCR1: 0x{:x}",
376 read_reg!(fmc, self.regs.global(), SDCR1)
377 );
378 fmc_trace!(
379 "SDCR2: 0x{:x}",
380 read_reg!(fmc, self.regs.global(), SDCR2)
381 );
382 fmc_trace!(
383 "SDTR1: 0x{:x}",
384 read_reg!(fmc, self.regs.global(), SDTR1)
385 );
386 fmc_trace!(
387 "SDTR2: 0x{:x}",
388 read_reg!(fmc, self.regs.global(), SDTR2)
389 );
390 fmc_trace!(
391 "SDCMR: 0x{:x}",
392 read_reg!(fmc, self.regs.global(), SDCMR)
393 );
394 fmc_trace!(
395 "SDRTR: 0x{:x}",
396 read_reg!(fmc, self.regs.global(), SDRTR)
397 );
398 }
399
400 // Memory now initialised. Return base address
401 self.fmc_bank.ptr()
402 }
403
404 /// Program memory device features and timings
405 ///
406 /// # Safety
407 ///
408 /// Some settings are common between both banks. Calling this function
409 /// mutliple times with different banks and different configurations is
410 /// unsafe.
411 ///
412 /// For example, see RM0433 rev 7 Section 22.9.3
413 unsafe fn set_features_timings(
414 &mut self,
415 config: SdramConfiguration,
416 timing: SdramTiming,
417 sd_clock_divide: u32,
418 ) {
419 // Features ---- SDCR REGISTER
420
421 // CAS latency 1 ~ 3 cycles
422 assert!(
423 config.cas_latency >= 1 && config.cas_latency <= 3,
424 "Impossible configuration for FMC Controller"
425 );
426
427 // Row Bits: 11 ~ 13
428 assert!(
429 config.row_bits >= 11 && config.row_bits <= 13,
430 "Impossible configuration for FMC Controller"
431 );
432
433 // Column bits: 8 ~ 11
434 assert!(
435 config.column_bits >= 8 && config.column_bits <= 11,
436 "Impossible configuration for FMC Controller"
437 );
438
439 // Read Pipe Delay Cycles 0 ~ 2
440 assert!(
441 config.read_pipe_delay_cycles <= 2,
442 "Impossible configuration for FMC Controller"
443 );
444
445 // Common settings written to SDCR1 only
446 modify_reg!(fmc, self.regs.global(), SDCR1,
447 RPIPE: config.read_pipe_delay_cycles as u32,
448 RBURST: config.read_burst as u32,
449 SDCLK: sd_clock_divide);
450
451 modify_reg_banked!(fmc, self.regs.global(),
452 self.target_bank, SDCR1, SDCR2,
453 // fields
454 WP: config.write_protection as u32,
455 CAS: config.cas_latency as u32,
456 NB:
457 match config.internal_banks {
458 2 => 0,
459 4 => 1,
460 _ => {
461 panic!("Impossible configuration for FMC Controller")
462 }
463 },
464 MWID:
465 match config.memory_data_width {
466 8 => 0,
467 16 => 1,
468 32 => 2,
469 _ => {
470 panic!("Impossible configuration for FMC Controller")
471 }
472 },
473 NR: config.row_bits as u32 - 11,
474 NC: config.column_bits as u32 - 8);
475
476 // Timing ---- SDTR REGISTER
477
478 // Self refresh >= ACTIVE to PRECHARGE
479 let minimum_self_refresh = timing.active_to_precharge;
480
481 // Write recovery - Self refresh
482 let write_recovery_self_refresh =
483 minimum_self_refresh - timing.row_to_column;
484 // Write recovery - WRITE command to PRECHARGE command
485 let write_recovery_row_cycle =
486 timing.row_cycle - timing.row_to_column - timing.row_precharge;
487 let write_recovery =
488 cmp::max(write_recovery_self_refresh, write_recovery_row_cycle);
489
490 // Common seting written to SDTR1 only
491 modify_reg!(fmc, self.regs.global(), SDTR1,
492 TRC: timing.row_cycle - 1,
493 TRP: timing.row_precharge - 1
494 );
495
496 modify_reg_banked!(fmc, self.regs.global(),
497 self.target_bank, SDTR1, SDTR2,
498 // fields
499 TRCD: timing.row_to_column - 1,
500 TWR: write_recovery - 1,
501 TRAS: minimum_self_refresh - 1,
502 TXSR: timing.exit_self_refresh - 1,
503 TMRD: timing.mode_register_to_active - 1
504 );
505 }
506
507 /// Send command to SDRAM
508 unsafe fn send_command(
509 &mut self,
510 mode: SdramCommand,
511 target: SdramTargetBank,
512 ) {
513 use SdramCommand::*;
514 use SdramTargetBank::*;
515
516 // Command
517 let (cmd, number_refresh, mode_reg) = match mode {
518 NormalMode => (0x00, 1, 0),
519 ClkEnable => (0x01, 1, 0),
520 Pall => (0x02, 1, 0),
521 Autorefresh(a) => (0x03, a, 0), // Autorefresh
522 LoadMode(mr) => (0x04, 1, mr), // Mode register
523 Selfrefresh => (0x05, 1, 0),
524 Powerdown => (0x06, 1, 0),
525 };
526 // Bank for issuing command
527 let (b1, b2) = match target {
528 Bank1 => (1, 0),
529 Bank2 => (0, 1),
530 Both => (1, 1),
531 };
532
533 // Write to SDCMR
534 write_reg!(
535 fmc,
536 self.regs.global(),
537 SDCMR,
538 MRD: mode_reg as u32,
539 NRFS: number_refresh as u32,
540 CTB1: b1,
541 CTB2: b2,
542 MODE: cmd
543 );
544
545 #[cfg(feature = "trace-register-values")]
546 fmc_trace!(
547 "Modifying SDCMR: mrd {}, nrfs {}, ctb1 {}, ctb2 {}, mode {}",
548 mode_reg,
549 number_refresh,
550 b1,
551 b2,
552 cmd
553 );
554 }
555}
556