aboutsummaryrefslogtreecommitdiff
path: root/embassy-rp/src/psram.rs
blob: 22eb1ed255a7d5d215dca0dda06fb5840e36cd0f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
//! PSRAM driver for APS6404L and compatible devices
//!
//! This driver provides support for PSRAM (Pseudo-Static RAM) devices connected via QMI CS1.
//! It handles device verification, initialization, and memory-mapped access configuration.
//!
//! This driver is only available on RP235x chips as it requires the QMI CS1 peripheral.

// Credit: Initially based on https://github.com/Altaflux/gb-rp2350 (also licensed Apache 2.0 + MIT).
// Copyright (c) Altaflux

#![cfg(feature = "_rp235x")]

use critical_section::{acquire, release, CriticalSection, RestoreState};

use crate::pac;
use crate::qmi_cs1::QmiCs1;

/// PSRAM errors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
    /// PSRAM device is not detected or not supported
    DeviceNotFound,
    /// Invalid configuration
    InvalidConfig,
    /// Detected PSRAM size does not match the expected size
    SizeMismatch,
}

/// PSRAM device verification type.
#[derive(Clone, Copy)]
pub enum VerificationType {
    /// Skip device verification
    None,
    /// Verify as APS6404L device
    Aps6404l,
}

/// Memory configuration.
#[derive(Clone)]
pub struct Config {
    /// System clock frequency in Hz
    pub clock_hz: u32,
    /// Maximum memory operating frequency in Hz
    pub max_mem_freq: u32,
    /// Maximum CS assert time in microseconds (must be <= 8 us)
    pub max_select_us: u32,
    /// Minimum CS deassert time in nanoseconds (must be >= 18 ns)
    pub min_deselect_ns: u32,
    /// Cooldown period between operations (in SCLK cycles)
    pub cooldown: u8,
    /// Page break size for memory operations
    pub page_break: PageBreak,
    /// Clock divisor for direct mode operations during initialization
    pub init_clkdiv: u8,
    /// Enter Quad Mode command
    pub enter_quad_cmd: Option<u8>,
    /// Quad Read command (fast read with 4-bit data)
    pub quad_read_cmd: u8,
    /// Quad Write command (page program with 4-bit data)
    pub quad_write_cmd: Option<u8>,
    /// Number of dummy cycles for quad read operations
    pub dummy_cycles: u8,
    /// Read format configuration
    pub read_format: FormatConfig,
    /// Write format configuration
    pub write_format: Option<FormatConfig>,
    /// Expected memory size in bytes
    pub mem_size: usize,
    /// Device verification type
    pub verification_type: VerificationType,
    /// Whether the memory is writable via XIP (e.g., PSRAM vs. read-only flash)
    pub xip_writable: bool,
}

/// Page break configuration for memory window operations.
#[derive(Clone, Copy)]
pub enum PageBreak {
    /// No page breaks
    None,
    /// Break at 256-byte boundaries
    _256,
    /// Break at 1024-byte boundaries
    _1024,
    /// Break at 4096-byte boundaries
    _4096,
}

/// Format configuration for read/write operations.
#[derive(Clone)]
pub struct FormatConfig {
    /// Width of command prefix phase
    pub prefix_width: Width,
    /// Width of address phase
    pub addr_width: Width,
    /// Width of command suffix phase
    pub suffix_width: Width,
    /// Width of dummy/turnaround phase
    pub dummy_width: Width,
    /// Width of data phase
    pub data_width: Width,
    /// Length of prefix (None or 8 bits)
    pub prefix_len: bool,
    /// Length of suffix (None or 8 bits)
    pub suffix_len: bool,
}

/// Interface width for different phases of SPI transfer.
#[derive(Clone, Copy)]
pub enum Width {
    /// Single-bit (standard SPI)
    Single,
    /// Dual-bit (2 data lines)
    Dual,
    /// Quad-bit (4 data lines)
    Quad,
}

impl Default for Config {
    fn default() -> Self {
        Self::aps6404l()
    }
}

impl Config {
    /// Create configuration for APS6404L PSRAM.
    pub fn aps6404l() -> Self {
        Self {
            clock_hz: 125_000_000,        // Default to 125MHz
            max_mem_freq: 133_000_000,    // APS6404L max frequency
            max_select_us: 8,             // 8 microseconds max CS assert
            min_deselect_ns: 18,          // 18 nanoseconds min CS deassert
            cooldown: 1,                  // 1 SCLK cycle cooldown
            page_break: PageBreak::_1024, // 1024-byte page boundaries
            init_clkdiv: 10,              // Medium clock for initialization
            enter_quad_cmd: Some(0x35),   // Enter Quad Mode
            quad_read_cmd: 0xEB,          // Fast Quad Read
            quad_write_cmd: Some(0x38),   // Quad Page Program
            dummy_cycles: 24,             // 24 dummy cycles for quad read
            read_format: FormatConfig {
                prefix_width: Width::Quad,
                addr_width: Width::Quad,
                suffix_width: Width::Quad,
                dummy_width: Width::Quad,
                data_width: Width::Quad,
                prefix_len: true,  // 8-bit prefix
                suffix_len: false, // No suffix
            },
            write_format: Some(FormatConfig {
                prefix_width: Width::Quad,
                addr_width: Width::Quad,
                suffix_width: Width::Quad,
                dummy_width: Width::Quad,
                data_width: Width::Quad,
                prefix_len: true,  // 8-bit prefix
                suffix_len: false, // No suffix
            }),
            mem_size: 8 * 1024 * 1024, // 8MB for APS6404L
            verification_type: VerificationType::Aps6404l,
            xip_writable: true, // PSRAM is writable
        }
    }

    /// Create a custom memory configuration.
    pub fn custom(
        clock_hz: u32,
        max_mem_freq: u32,
        max_select_us: u32,
        min_deselect_ns: u32,
        cooldown: u8,
        page_break: PageBreak,
        init_clkdiv: u8,
        enter_quad_cmd: Option<u8>,
        quad_read_cmd: u8,
        quad_write_cmd: Option<u8>,
        dummy_cycles: u8,
        read_format: FormatConfig,
        write_format: Option<FormatConfig>,
        mem_size: usize,
        verification_type: VerificationType,
        xip_writable: bool,
    ) -> Self {
        Self {
            clock_hz,
            max_mem_freq,
            max_select_us,
            min_deselect_ns,
            cooldown,
            page_break,
            init_clkdiv,
            enter_quad_cmd,
            quad_read_cmd,
            quad_write_cmd,
            dummy_cycles,
            read_format,
            write_format,
            mem_size,
            verification_type,
            xip_writable,
        }
    }
}

/// PSRAM driver.
pub struct Psram<'d> {
    #[allow(dead_code)]
    qmi_cs1: QmiCs1<'d>,
    size: usize,
}

impl<'d> Psram<'d> {
    /// Create a new PSRAM driver instance.
    ///
    /// This will detect the PSRAM device and configure it for memory-mapped access.
    pub fn new(qmi_cs1: QmiCs1<'d>, config: Config) -> Result<Self, Error> {
        let qmi = pac::QMI;
        let xip = pac::XIP_CTRL;

        // Verify PSRAM device if requested
        match config.verification_type {
            VerificationType::Aps6404l => {
                Self::verify_aps6404l(&qmi, config.mem_size)?;
                debug!("APS6404L PSRAM verified, size: {:#x}", config.mem_size);
            }
            VerificationType::None => {
                debug!("Skipping PSRAM verification, assuming size: {:#x}", config.mem_size);
            }
        }

        // Initialize PSRAM with proper timing
        Self::init_psram(&qmi, &xip, &config)?;

        Ok(Self {
            qmi_cs1,
            size: config.mem_size,
        })
    }

    /// Get the detected PSRAM size in bytes.
    pub fn size(&self) -> usize {
        self.size
    }

    /// Get the base address for memory-mapped access.
    ///
    /// After initialization, PSRAM can be accessed directly through memory mapping.
    /// The base address for CS1 is typically 0x11000000.
    pub fn base_address(&self) -> *mut u8 {
        0x1100_0000 as *mut u8
    }

    /// Verify APS6404L PSRAM device matches expected configuration.
    #[unsafe(link_section = ".data.ram_func")]
    #[inline(never)]
    fn verify_aps6404l(qmi: &pac::qmi::Qmi, expected_size: usize) -> Result<(), Error> {
        // APS6404L-specific constants
        const EXPECTED_KGD: u8 = 0x5D;
        crate::multicore::pause_core1();
        core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);

        {
            // Helper for making sure `release` is called even if `f` panics.
            struct Guard {
                state: RestoreState,
            }

            impl Drop for Guard {
                #[inline(always)]
                fn drop(&mut self) {
                    unsafe { release(self.state) }
                }
            }

            let state = unsafe { acquire() };
            let _guard = Guard { state };

            let _cs = unsafe { CriticalSection::new() };

            let (kgd, eid) = unsafe { Self::read_aps6404l_kgd_eid(qmi) };

            let mut detected_size: u32 = 0;
            if kgd == EXPECTED_KGD as u32 {
                detected_size = 1024 * 1024;
                let size_id = eid >> 5;
                if eid == 0x26 || size_id == 2 {
                    // APS6404L-3SQR-SN or 8MB variants
                    detected_size *= 8;
                } else if size_id == 0 {
                    detected_size *= 2;
                } else if size_id == 1 {
                    detected_size *= 4;
                }
            }

            // Verify the detected size matches the expected size
            if detected_size as usize != expected_size {
                return Err(Error::SizeMismatch);
            }

            Ok(())
        }?;

        crate::multicore::resume_core1();

        Ok(())
    }

    #[unsafe(link_section = ".data.ram_func")]
    #[inline(never)]
    unsafe fn read_aps6404l_kgd_eid(qmi: &pac::qmi::Qmi) -> (u32, u32) {
        const RESET_ENABLE_CMD: u8 = 0xf5;
        const READ_ID_CMD: u8 = 0x9f;

        #[allow(unused_assignments)]
        let mut kgd: u32 = 0;
        #[allow(unused_assignments)]
        let mut eid: u32 = 0;

        let qmi_base = qmi.as_ptr() as usize;

        #[cfg(target_arch = "arm")]
        core::arch::asm!(
        // Configure DIRECT_CSR: shift clkdiv (30) to bits 29:22 and set EN (bit 0)
        "movs {temp}, #30",
        "lsls {temp}, {temp}, #22",
        "orr {temp}, {temp}, #1",        // Set EN bit
        "str {temp}, [{qmi_base}]",

        // Poll for BUSY to clear before first operation
        "1:",
        "ldr {temp}, [{qmi_base}]",
        "lsls {temp}, {temp}, #30",      // Shift BUSY bit to sign position
        "bmi 1b",                        // Branch if negative (BUSY = 1)

        // Assert CS1N (bit 3)
        "ldr {temp}, [{qmi_base}]",
        "orr {temp}, {temp}, #8",        // Set ASSERT_CS1N bit (bit 3)
        "str {temp}, [{qmi_base}]",

        // Transmit RESET_ENABLE_CMD as quad
        // DIRECT_TX: OE=1 (bit 19), IWIDTH=2 (bits 17:16), DATA=RESET_ENABLE_CMD
        "movs {temp}, {reset_enable_cmd}",
        "orr {temp}, {temp}, #0x80000",  // Set OE (bit 19)
        "orr {temp}, {temp}, #0x20000",  // Set IWIDTH=2 (quad, bits 17:16)
        "str {temp}, [{qmi_base}, #4]",  // Store to DIRECT_TX

        // Wait for BUSY to clear
        "2:",
        "ldr {temp}, [{qmi_base}]",
        "lsls {temp}, {temp}, #30",
        "bmi 2b",

        // Read and discard RX data
        "ldr {temp}, [{qmi_base}, #8]",

        // Deassert CS1N
        "ldr {temp}, [{qmi_base}]",
        "bic {temp}, {temp}, #8",        // Clear ASSERT_CS1N bit
        "str {temp}, [{qmi_base}]",

        // Assert CS1N again
        "ldr {temp}, [{qmi_base}]",
        "orr {temp}, {temp}, #8",        // Set ASSERT_CS1N bit
        "str {temp}, [{qmi_base}]",

        // Read ID loop (7 iterations)
        "movs {counter}, #0",            // Initialize counter

        "3:",                            // Loop start
        "cmp {counter}, #0",
        "bne 4f",                        // If not first iteration, send 0xFF

        // First iteration: send READ_ID_CMD
        "movs {temp}, {read_id_cmd}",
        "b 5f",
        "4:",                            // Other iterations: send 0xFF
        "movs {temp}, #0xFF",
        "5:",
        "str {temp}, [{qmi_base}, #4]",  // Store to DIRECT_TX

        // Wait for TXEMPTY
        "6:",
        "ldr {temp}, [{qmi_base}]",
        "lsls {temp}, {temp}, #20",      // Shift TXEMPTY (bit 11) to bit 31
        "bpl 6b",                        // Branch if positive (TXEMPTY = 0)

        // Wait for BUSY to clear
        "7:",
        "ldr {temp}, [{qmi_base}]",
        "lsls {temp}, {temp}, #30",      // Shift BUSY bit to sign position
        "bmi 7b",                        // Branch if negative (BUSY = 1)

        // Read RX data
        "ldr {temp}, [{qmi_base}, #8]",
        "uxth {temp}, {temp}",           // Extract lower 16 bits

        // Store KGD or EID based on iteration
        "cmp {counter}, #5",
        "bne 8f",
        "mov {kgd}, {temp}",             // Store KGD
        "b 9f",
        "8:",
        "cmp {counter}, #6",
        "bne 9f",
        "mov {eid}, {temp}",             // Store EID

        "9:",
        "adds {counter}, #1",
        "cmp {counter}, #7",
        "blt 3b",                        // Continue loop if counter < 7

        // Disable direct mode: clear EN and ASSERT_CS1N
        "movs {temp}, #0",
        "str {temp}, [{qmi_base}]",

        // Memory barriers
        "dmb",
        "dsb",
        "isb",
        qmi_base = in(reg) qmi_base,
        temp = out(reg) _,
        counter = out(reg) _,
        kgd = out(reg) kgd,
        eid = out(reg) eid,
        reset_enable_cmd = const RESET_ENABLE_CMD as u32,
        read_id_cmd = const READ_ID_CMD as u32,
        options(nostack),
        );

        #[cfg(target_arch = "riscv32")]
        unimplemented!("APS6404L PSRAM verification not implemented for RISC-V");

        (kgd, eid)
    }

    /// Initialize PSRAM with proper timing.
    #[unsafe(link_section = ".data.ram_func")]
    #[inline(never)]
    fn init_psram(qmi: &pac::qmi::Qmi, xip_ctrl: &pac::xip_ctrl::XipCtrl, config: &Config) -> Result<(), Error> {
        // Set PSRAM timing for APS6404
        //
        // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133 MHz.
        // So: don't allow running at divisor 1 above 100 MHz (because delay of 2 would be too late),
        // and add an extra 1 to the rxdelay if the divided clock is > 100 MHz (i.e., sys clock > 200 MHz).
        let clock_hz = config.clock_hz;
        let max_psram_freq = config.max_mem_freq;

        let mut divisor: u32 = (clock_hz + max_psram_freq - 1) / max_psram_freq;
        if divisor == 1 && clock_hz > 100_000_000 {
            divisor = 2;
        }
        let mut rxdelay: u32 = divisor;
        if clock_hz / divisor > 100_000_000 {
            rxdelay += 1;
        }

        // - Max select must be <= 8 us. The value is given in multiples of 64 system clocks.
        // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2).
        let clock_period_fs: u64 = 1_000_000_000_000_000_u64 / u64::from(clock_hz);
        let max_select: u8 = ((config.max_select_us as u64 * 1_000_000) / clock_period_fs) as u8;
        let min_deselect: u32 = ((config.min_deselect_ns as u64 * 1_000_000 + (clock_period_fs - 1)) / clock_period_fs
            - u64::from(divisor + 1) / 2) as u32;

        crate::multicore::pause_core1();
        core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);

        if let Some(enter_quad_cmd) = config.enter_quad_cmd {
            // Helper for making sure `release` is called even if `f` panics.
            struct Guard {
                state: RestoreState,
            }

            impl Drop for Guard {
                #[inline(always)]
                fn drop(&mut self) {
                    unsafe { release(self.state) }
                }
            }

            let state = unsafe { acquire() };
            let _guard = Guard { state };

            let _cs = unsafe { CriticalSection::new() };

            unsafe { Self::direct_csr_send_init_command(config, enter_quad_cmd) };

            qmi.mem(1).timing().write(|w| {
                w.set_cooldown(config.cooldown);
                w.set_pagebreak(match config.page_break {
                    PageBreak::None => pac::qmi::vals::Pagebreak::NONE,
                    PageBreak::_256 => pac::qmi::vals::Pagebreak::_256,
                    PageBreak::_1024 => pac::qmi::vals::Pagebreak::_1024,
                    PageBreak::_4096 => pac::qmi::vals::Pagebreak::_4096,
                });
                w.set_max_select(max_select);
                w.set_min_deselect(min_deselect as u8);
                w.set_rxdelay(rxdelay as u8);
                w.set_clkdiv(divisor as u8);
            });

            // Set PSRAM commands and formats
            qmi.mem(1).rfmt().write(|w| {
                let width_to_pac = |w: Width| match w {
                    Width::Single => pac::qmi::vals::PrefixWidth::S,
                    Width::Dual => pac::qmi::vals::PrefixWidth::D,
                    Width::Quad => pac::qmi::vals::PrefixWidth::Q,
                };

                w.set_prefix_width(width_to_pac(config.read_format.prefix_width));
                w.set_addr_width(match config.read_format.addr_width {
                    Width::Single => pac::qmi::vals::AddrWidth::S,
                    Width::Dual => pac::qmi::vals::AddrWidth::D,
                    Width::Quad => pac::qmi::vals::AddrWidth::Q,
                });
                w.set_suffix_width(match config.read_format.suffix_width {
                    Width::Single => pac::qmi::vals::SuffixWidth::S,
                    Width::Dual => pac::qmi::vals::SuffixWidth::D,
                    Width::Quad => pac::qmi::vals::SuffixWidth::Q,
                });
                w.set_dummy_width(match config.read_format.dummy_width {
                    Width::Single => pac::qmi::vals::DummyWidth::S,
                    Width::Dual => pac::qmi::vals::DummyWidth::D,
                    Width::Quad => pac::qmi::vals::DummyWidth::Q,
                });
                w.set_data_width(match config.read_format.data_width {
                    Width::Single => pac::qmi::vals::DataWidth::S,
                    Width::Dual => pac::qmi::vals::DataWidth::D,
                    Width::Quad => pac::qmi::vals::DataWidth::Q,
                });
                w.set_prefix_len(if config.read_format.prefix_len {
                    pac::qmi::vals::PrefixLen::_8
                } else {
                    pac::qmi::vals::PrefixLen::NONE
                });
                w.set_suffix_len(if config.read_format.suffix_len {
                    pac::qmi::vals::SuffixLen::_8
                } else {
                    pac::qmi::vals::SuffixLen::NONE
                });
                w.set_dummy_len(match config.dummy_cycles {
                    0 => pac::qmi::vals::DummyLen::NONE,
                    4 => pac::qmi::vals::DummyLen::_4,
                    8 => pac::qmi::vals::DummyLen::_8,
                    12 => pac::qmi::vals::DummyLen::_12,
                    16 => pac::qmi::vals::DummyLen::_16,
                    20 => pac::qmi::vals::DummyLen::_20,
                    24 => pac::qmi::vals::DummyLen::_24,
                    28 => pac::qmi::vals::DummyLen::_28,
                    _ => pac::qmi::vals::DummyLen::_24, // Default to 24
                });
            });

            qmi.mem(1).rcmd().write(|w| w.set_prefix(config.quad_read_cmd));

            if let Some(ref write_format) = config.write_format {
                qmi.mem(1).wfmt().write(|w| {
                    w.set_prefix_width(match write_format.prefix_width {
                        Width::Single => pac::qmi::vals::PrefixWidth::S,
                        Width::Dual => pac::qmi::vals::PrefixWidth::D,
                        Width::Quad => pac::qmi::vals::PrefixWidth::Q,
                    });
                    w.set_addr_width(match write_format.addr_width {
                        Width::Single => pac::qmi::vals::AddrWidth::S,
                        Width::Dual => pac::qmi::vals::AddrWidth::D,
                        Width::Quad => pac::qmi::vals::AddrWidth::Q,
                    });
                    w.set_suffix_width(match write_format.suffix_width {
                        Width::Single => pac::qmi::vals::SuffixWidth::S,
                        Width::Dual => pac::qmi::vals::SuffixWidth::D,
                        Width::Quad => pac::qmi::vals::SuffixWidth::Q,
                    });
                    w.set_dummy_width(match write_format.dummy_width {
                        Width::Single => pac::qmi::vals::DummyWidth::S,
                        Width::Dual => pac::qmi::vals::DummyWidth::D,
                        Width::Quad => pac::qmi::vals::DummyWidth::Q,
                    });
                    w.set_data_width(match write_format.data_width {
                        Width::Single => pac::qmi::vals::DataWidth::S,
                        Width::Dual => pac::qmi::vals::DataWidth::D,
                        Width::Quad => pac::qmi::vals::DataWidth::Q,
                    });
                    w.set_prefix_len(if write_format.prefix_len {
                        pac::qmi::vals::PrefixLen::_8
                    } else {
                        pac::qmi::vals::PrefixLen::NONE
                    });
                    w.set_suffix_len(if write_format.suffix_len {
                        pac::qmi::vals::SuffixLen::_8
                    } else {
                        pac::qmi::vals::SuffixLen::NONE
                    });
                });
            }

            if let Some(quad_write_cmd) = config.quad_write_cmd {
                qmi.mem(1).wcmd().write(|w| w.set_prefix(quad_write_cmd));
            }

            if config.xip_writable {
                // Enable XIP writable mode for PSRAM
                xip_ctrl.ctrl().modify(|w| w.set_writable_m1(true));
            } else {
                // Disable XIP writable mode
                xip_ctrl.ctrl().modify(|w| w.set_writable_m1(false));
            }
        }
        crate::multicore::resume_core1();

        Ok(())
    }

    #[unsafe(link_section = ".data.ram_func")]
    #[inline(never)]
    unsafe fn direct_csr_send_init_command(config: &Config, init_cmd: u8) {
        #[cfg(target_arch = "arm")]
        core::arch::asm!(
        // Full memory barrier
        "dmb",
        "dsb",
        "isb",

        // Configure QMI Direct CSR register
        // Load base address of QMI (0x400D0000)
        "movw {base}, #0x0000",
        "movt {base}, #0x400D",

        // Load init_clkdiv and shift to bits 29:22
        "lsl {temp}, {clkdiv}, #22",

        // OR with EN (bit 0) and AUTO_CS1N (bit 7)
        "orr {temp}, {temp}, #0x81",

        // Store to DIRECT_CSR register
        "str {temp}, [{base}, #0]",

        // Memory barrier
        "dmb",

        // First busy wait loop
        "1:",
        "ldr {temp}, [{base}, #0]",      // Load DIRECT_CSR
        "tst {temp}, #0x2",              // Test BUSY bit (bit 1)
        "bne 1b",                        // Branch if busy

        // Write to Direct TX register
        "mov {temp}, {enter_quad_cmd}",

        // OR with NOPUSH (bit 20)
        "orr {temp}, {temp}, #0x100000",

        // Store to DIRECT_TX register (offset 0x4)
        "str {temp}, [{base}, #4]",

        // Memory barrier
        "dmb",

        // Second busy wait loop
        "2:",
        "ldr {temp}, [{base}, #0]",      // Load DIRECT_CSR
        "tst {temp}, #0x2",              // Test BUSY bit (bit 1)
        "bne 2b",                        // Branch if busy

        // Disable Direct CSR
        "mov {temp}, #0",
        "str {temp}, [{base}, #0]",      // Clear DIRECT_CSR register

        // Full memory barrier to ensure no prefetching
        "dmb",
        "dsb",
        "isb",
        base = out(reg) _,
        temp = out(reg) _,
        clkdiv = in(reg) config.init_clkdiv as u32,
        enter_quad_cmd = in(reg) u32::from(init_cmd),
        options(nostack),
        );

        #[cfg(target_arch = "riscv32")]
        unimplemented!("Direct CSR command sending is not implemented for RISC-V yet");
    }
}