1 | //! HAL for external SDRAM |
2 | |
3 | use core::cmp; |
4 | use core::convert::TryInto; |
5 | use core::marker::PhantomData; |
6 | |
7 | use embedded_hal::blocking::delay::DelayUs; |
8 | |
9 | use crate::fmc::{AddressPinSet, FmcBank, FmcRegisters}; |
10 | use crate::FmcPeripheral; |
11 | |
12 | use 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)] |
18 | pub 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)] |
40 | pub 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 |
63 | pub 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)] |
76 | pub 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)] |
93 | enum 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)] |
106 | pub 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 | } |
114 | impl 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 |
125 | pub 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))] |
135 | pub struct SdramBank1; |
136 | impl 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))] |
144 | pub struct SdramBank2; |
145 | impl 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 |
151 | pub 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 |
157 | macro_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 | |
169 | impl<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 | |