stm32_fmc/
sdram.rs

1//! HAL for external SDRAM
2
3use core::cmp;
4use core::convert::TryInto;
5use core::marker::PhantomData;
6
7use embedded_hal::delay::DelayNs;
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: DelayNs,
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}