1use core::ops::{Deref, DerefMut};
7
8use arbitrary_int::{prelude::*, u2, u3, u6};
9pub use zynq7000::qspi::LinearQspiConfig;
10use zynq7000::{
11 qspi::{
12 BaudRateDivisor, Config, InstructionCode, InterruptStatus, LoopbackMasterClockDelay,
13 SpiEnable,
14 },
15 slcr::{clocks::SingleCommonPeriphIoClockControl, mio::Speed, reset::QspiResetControl},
16};
17
18pub use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3, Mode};
19pub use zynq7000::slcr::clocks::SrcSelIo;
20pub use zynq7000::slcr::mio::IoType;
21
22use crate::{
23 PeriphSelect,
24 clocks::Clocks,
25 enable_amba_peripheral_clock,
26 gpio::{
27 IoPeriphPin,
28 mio::{
29 Mio0, Mio1, Mio2, Mio3, Mio4, Mio5, Mio6, Mio8, Mio9, Mio10, Mio11, Mio12, Mio13,
30 MioPin, MuxConfig, Pin,
31 },
32 },
33 slcr::Slcr,
34 spi_mode_const_to_cpol_cpha,
35 time::Hertz,
36};
37
38pub(crate) mod lqspi_configs;
39
40pub const QSPI_MUX_CONFIG: MuxConfig = MuxConfig::new_with_l0();
41pub const FIFO_DEPTH: usize = 63;
42pub const QSPI_START_ADDRESS: usize = 0xFC00_0000;
44
45#[derive(Debug, thiserror::Error)]
46pub enum ClockCalculationError {
47 #[error("violated clock ratio restriction")]
48 RefClockSmallerThanCpu1xClock,
49 #[error("reference divisor out of range")]
50 RefDivOutOfRange,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum BaudRateConfig {
55 WithLoopback,
56 WithoutLoopback(BaudRateDivisor),
57}
58
59impl BaudRateConfig {
60 #[inline]
61 pub const fn baud_rate_divisor(&self) -> BaudRateDivisor {
62 match self {
63 BaudRateConfig::WithLoopback => BaudRateDivisor::_2,
64 BaudRateConfig::WithoutLoopback(divisor) => *divisor,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct ClockConfig {
71 pub src_sel: SrcSelIo,
72 pub ref_clk_div: u6,
73 pub baud_rate_config: BaudRateConfig,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum QspiVendor {
78 WinbondAndSpansion,
79 Micron,
80}
81
82pub type OperatingMode = InstructionCode;
83
84pub trait Qspi0ChipSelectPin: MioPin {}
85pub trait Qspi0Io0Pin: MioPin {}
86pub trait Qspi0Io1Pin: MioPin {}
87pub trait Qspi0Io2Pin: MioPin {}
88pub trait Qspi0Io3Pin: MioPin {}
89pub trait Qspi0ClockPin: MioPin {}
90
91impl Qspi0ChipSelectPin for Pin<Mio1> {}
92impl Qspi0Io0Pin for Pin<Mio2> {}
93impl Qspi0Io1Pin for Pin<Mio3> {}
94impl Qspi0Io2Pin for Pin<Mio4> {}
95impl Qspi0Io3Pin for Pin<Mio5> {}
96impl Qspi0ClockPin for Pin<Mio6> {}
97
98pub trait Qspi1ChipSelectPin: MioPin {}
99pub trait Qspi1Io0Pin: MioPin {}
100pub trait Qspi1Io1Pin: MioPin {}
101pub trait Qspi1Io2Pin: MioPin {}
102pub trait Qspi1Io3Pin: MioPin {}
103pub trait Qspi1ClockPin: MioPin {}
104
105impl Qspi1ChipSelectPin for Pin<Mio0> {}
106impl Qspi1Io0Pin for Pin<Mio10> {}
107impl Qspi1Io1Pin for Pin<Mio11> {}
108impl Qspi1Io2Pin for Pin<Mio12> {}
109impl Qspi1Io3Pin for Pin<Mio13> {}
110impl Qspi1ClockPin for Pin<Mio9> {}
111
112pub trait FeedbackClockPin: MioPin {}
113
114impl FeedbackClockPin for Pin<Mio8> {}
115
116pub struct QspiDeviceCombination {
117 pub vendor: QspiVendor,
118 pub operating_mode: OperatingMode,
119 pub two_devices: bool,
120}
121
122impl From<QspiDeviceCombination> for LinearQspiConfig {
123 fn from(value: QspiDeviceCombination) -> Self {
124 linear_mode_config_for_common_devices(value)
125 }
126}
127
128pub const fn linear_mode_config_for_common_devices(
129 dev_combination: QspiDeviceCombination,
130) -> LinearQspiConfig {
131 match dev_combination.operating_mode {
132 InstructionCode::Read => {
133 if dev_combination.two_devices {
134 lqspi_configs::RD_TWO
135 } else {
136 lqspi_configs::RD_ONE
137 }
138 }
139 InstructionCode::FastRead => {
140 if dev_combination.two_devices {
141 lqspi_configs::FAST_RD_TWO
142 } else {
143 lqspi_configs::FAST_RD_ONE
144 }
145 }
146 InstructionCode::FastReadDualOutput => {
147 if dev_combination.two_devices {
148 lqspi_configs::DUAL_OUT_FAST_RD_TWO
149 } else {
150 lqspi_configs::DUAL_OUT_FAST_RD_ONE
151 }
152 }
153 InstructionCode::FastReadQuadOutput => {
154 if dev_combination.two_devices {
155 lqspi_configs::QUAD_OUT_FAST_RD_TWO
156 } else {
157 lqspi_configs::QUAD_OUT_FAST_RD_ONE
158 }
159 }
160 InstructionCode::FastReadDualIo => {
161 match (dev_combination.vendor, dev_combination.two_devices) {
162 (QspiVendor::WinbondAndSpansion, false) => {
163 lqspi_configs::winbond_spansion::DUAL_IO_FAST_RD_ONE
164 }
165 (QspiVendor::WinbondAndSpansion, true) => {
166 lqspi_configs::winbond_spansion::DUAL_IO_FAST_RD_TWO
167 }
168 (QspiVendor::Micron, false) => lqspi_configs::micron::DUAL_IO_FAST_RD_ONE,
169 (QspiVendor::Micron, true) => lqspi_configs::micron::DUAL_IO_FAST_RD_TWO,
170 }
171 }
172 InstructionCode::FastReadQuadIo => {
173 match (dev_combination.vendor, dev_combination.two_devices) {
174 (QspiVendor::WinbondAndSpansion, false) => {
175 lqspi_configs::winbond_spansion::QUAD_IO_FAST_RD_ONE
176 }
177 (QspiVendor::WinbondAndSpansion, true) => {
178 lqspi_configs::winbond_spansion::QUAD_IO_FAST_RD_TWO
179 }
180 (QspiVendor::Micron, false) => lqspi_configs::micron::QUAD_IO_FAST_RD_ONE,
181 (QspiVendor::Micron, true) => lqspi_configs::micron::QUAD_IO_FAST_RD_TWO,
182 }
183 }
184 }
185}
186
187impl ClockConfig {
188 pub fn new(src_sel: SrcSelIo, ref_clk_div: u6, baud_rate_config: BaudRateConfig) -> Self {
189 Self {
190 src_sel,
191 ref_clk_div,
192 baud_rate_config,
193 }
194 }
195
196 pub fn calculate_with_loopback(
202 src_sel: SrcSelIo,
203 clocks: &Clocks,
204 target_qspi_interface_clock: Hertz,
205 ) -> Result<Self, ClockCalculationError> {
206 let target_ref_clock = target_qspi_interface_clock * 2;
208 let ref_clk = match src_sel {
209 SrcSelIo::IoPll | SrcSelIo::IoPllAlt => clocks.io_clocks().ref_clk(),
210 SrcSelIo::ArmPll => clocks.arm_clocks().ref_clk(),
211 SrcSelIo::DdrPll => clocks.ddr_clocks().ref_clk(),
212 };
213 let ref_clk_div = ref_clk.raw().div_ceil(target_ref_clock.raw());
214 if ref_clk_div > u6::MAX.as_u32() {
215 return Err(ClockCalculationError::RefDivOutOfRange);
216 }
217 Ok(Self {
218 src_sel,
219 ref_clk_div: u6::new(ref_clk_div as u8),
220 baud_rate_config: BaudRateConfig::WithLoopback,
221 })
222 }
223
224 pub fn calculate(
230 src_sel: SrcSelIo,
231 clocks: &Clocks,
232 target_qspi_ref_clock: Hertz,
233 target_qspi_interface_clock: Hertz,
234 ) -> Result<Self, ClockCalculationError> {
235 let (ref_clk_div, ref_clk) = match src_sel {
236 SrcSelIo::IoPll | SrcSelIo::IoPllAlt => (
237 clocks
238 .io_clocks()
239 .ref_clk()
240 .raw()
241 .div_ceil(target_qspi_ref_clock.raw()),
242 clocks.io_clocks().ref_clk(),
243 ),
244 SrcSelIo::ArmPll => (
245 clocks
246 .arm_clocks()
247 .ref_clk()
248 .raw()
249 .div_ceil(target_qspi_ref_clock.raw()),
250 clocks.arm_clocks().ref_clk(),
251 ),
252 SrcSelIo::DdrPll => (
253 clocks
254 .ddr_clocks()
255 .ref_clk()
256 .raw()
257 .div_ceil(target_qspi_ref_clock.raw()),
258 clocks.ddr_clocks().ref_clk(),
259 ),
260 };
261 if ref_clk_div > u6::MAX.as_u32() {
262 return Err(ClockCalculationError::RefDivOutOfRange);
263 }
264 let qspi_ref_clk = ref_clk / ref_clk_div;
265 if qspi_ref_clk < clocks.arm_clocks().cpu_1x_clk() {
266 return Err(ClockCalculationError::RefClockSmallerThanCpu1xClock);
267 }
268 let qspi_baud_rate_div = qspi_ref_clk / target_qspi_interface_clock;
269 let baud_rate_div = match qspi_baud_rate_div {
270 0..=2 => BaudRateDivisor::_2,
271 3..=4 => BaudRateDivisor::_4,
272 5..=8 => BaudRateDivisor::_8,
273 9..=16 => BaudRateDivisor::_16,
274 17..=32 => BaudRateDivisor::_32,
275 65..=128 => BaudRateDivisor::_64,
276 129..=256 => BaudRateDivisor::_128,
277 _ => BaudRateDivisor::_256,
278 };
279 Ok(Self {
280 src_sel,
281 ref_clk_div: u6::new(ref_clk_div as u8),
282 baud_rate_config: BaudRateConfig::WithoutLoopback(baud_rate_div),
283 })
284 }
285}
286
287pub struct QspiLowLevel(zynq7000::qspi::MmioQspi<'static>);
288
289impl QspiLowLevel {
290 #[inline]
291 pub fn new(regs: zynq7000::qspi::MmioQspi<'static>) -> Self {
292 Self(regs)
293 }
294
295 pub fn regs(&mut self) -> &mut zynq7000::qspi::MmioQspi<'static> {
296 &mut self.0
297 }
298
299 pub fn initialize(&mut self, clock_config: ClockConfig, mode: embedded_hal::spi::Mode) {
300 enable_amba_peripheral_clock(PeriphSelect::Lqspi);
301 reset();
302 let (cpol, cpha) = spi_mode_const_to_cpol_cpha(mode);
303 unsafe {
304 Slcr::with(|slcr| {
305 slcr.clk_ctrl().write_lqspi_clk_ctrl(
306 SingleCommonPeriphIoClockControl::builder()
307 .with_divisor(clock_config.ref_clk_div)
308 .with_srcsel(clock_config.src_sel)
309 .with_clk_act(true)
310 .build(),
311 );
312 })
313 }
314 let baudrate_config = clock_config.baud_rate_config;
315 self.0.write_config(
316 Config::builder()
317 .with_interface_mode(zynq7000::qspi::InterfaceMode::FlashMemoryInterface)
318 .with_edianness(zynq7000::qspi::Endianness::Little)
319 .with_holdb_dr(true)
320 .with_manual_start_command(false)
321 .with_manual_start_enable(false)
322 .with_manual_cs(false)
323 .with_peripheral_chip_select(false)
324 .with_fifo_width(u2::new(0b11))
325 .with_baud_rate_div(baudrate_config.baud_rate_divisor())
326 .with_clock_phase(cpha)
327 .with_clock_polarity(cpol)
328 .with_mode_select(true)
329 .build(),
330 );
331 if baudrate_config == BaudRateConfig::WithLoopback {
332 self.0.write_loopback_master_clock_delay(
333 LoopbackMasterClockDelay::builder()
334 .with_use_loopback(true)
335 .with_delay_1(u2::new(0x0))
336 .with_delay_0(u3::new(0x0))
337 .build(),
338 );
339 }
340 }
341
342 pub fn enable_linear_addressing(&mut self, config: LinearQspiConfig) {
343 self.0
344 .write_spi_enable(SpiEnable::builder().with_enable(false).build());
345 self.0.modify_config(|mut val| {
346 val.set_manual_start_enable(false);
348 val.set_manual_cs(false);
349 val.set_peripheral_chip_select(false);
350 val
351 });
352 self.0.write_linear_qspi_config(config);
353 }
354
355 pub fn enable_io_mode(&mut self, dual_flash: bool) {
356 self.0.modify_config(|mut val| {
357 val.set_manual_start_enable(true);
358 val.set_manual_cs(true);
359 val
360 });
361 self.0.write_rx_fifo_threshold(0x1);
362 self.0.write_tx_fifo_threshold(0x1);
363 self.0.write_linear_qspi_config(
364 LinearQspiConfig::builder()
365 .with_enable_linear_mode(false)
366 .with_both_memories(dual_flash)
367 .with_separate_memory_bus(dual_flash)
368 .with_upper_memory_page(false)
369 .with_mode_enable(false)
370 .with_mode_on(true)
371 .with_mode_bits(0xA0)
373 .with_num_dummy_bytes(u3::new(0x2))
374 .with_instruction_code(InstructionCode::FastReadQuadIo)
375 .build(),
376 );
377 }
378
379 pub fn disable(&mut self) {
380 self.0
381 .write_spi_enable(SpiEnable::builder().with_enable(false).build());
382 self.0.modify_config(|mut val| {
383 val.set_peripheral_chip_select(true);
384 val
385 });
386 }
387}
388
389pub struct Qspi {
390 ll: QspiLowLevel,
391}
392
393impl Qspi {
394 pub fn new_single_qspi<
395 ChipSelect: Qspi0ChipSelectPin,
396 Io0: Qspi0Io0Pin,
397 Io1: Qspi0Io1Pin,
398 Io2: Qspi0Io2Pin,
399 Io3: Qspi0Io3Pin,
400 Clock: Qspi0ClockPin,
401 >(
402 regs: zynq7000::qspi::MmioQspi<'static>,
403 clock_config: ClockConfig,
404 mode: embedded_hal::spi::Mode,
405 voltage: IoType,
406 cs: ChipSelect,
407 ios: (Io0, Io1, Io2, Io3),
408 clock: Clock,
409 ) -> Self {
410 IoPeriphPin::new_with_full_config(
411 cs,
412 zynq7000::slcr::mio::Config::builder()
413 .with_disable_hstl_rcvr(false)
414 .with_pullup(true)
415 .with_io_type(voltage)
416 .with_speed(Speed::SlowCmosEdge)
417 .with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
418 .with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
419 .with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
420 .with_l0_sel(QSPI_MUX_CONFIG.l0_sel())
421 .with_tri_enable(false)
422 .build(),
423 );
424 let io_and_clock_config = zynq7000::slcr::mio::Config::builder()
425 .with_disable_hstl_rcvr(false)
426 .with_pullup(false)
427 .with_io_type(voltage)
428 .with_speed(Speed::SlowCmosEdge)
429 .with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
430 .with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
431 .with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
432 .with_l0_sel(QSPI_MUX_CONFIG.l0_sel())
433 .with_tri_enable(false)
434 .build();
435 IoPeriphPin::new_with_full_config(ios.0, io_and_clock_config);
436 IoPeriphPin::new_with_full_config(ios.1, io_and_clock_config);
437 IoPeriphPin::new_with_full_config(ios.2, io_and_clock_config);
438 IoPeriphPin::new_with_full_config(ios.3, io_and_clock_config);
439 IoPeriphPin::new_with_full_config(clock, io_and_clock_config);
440 let mut ll = QspiLowLevel::new(regs);
441 ll.initialize(clock_config, mode);
442 Self { ll }
443 }
444
445 #[allow(clippy::too_many_arguments)]
446 pub fn new_single_qspi_with_feedback<
447 ChipSelect: Qspi0ChipSelectPin,
448 Io0: Qspi0Io0Pin,
449 Io1: Qspi0Io1Pin,
450 Io2: Qspi0Io2Pin,
451 Io3: Qspi0Io3Pin,
452 Clock: Qspi0ClockPin,
453 Feedback: FeedbackClockPin,
454 >(
455 regs: zynq7000::qspi::MmioQspi<'static>,
456 clock_config: ClockConfig,
457 mode: embedded_hal::spi::Mode,
458 voltage: IoType,
459 cs: ChipSelect,
460 io: (Io0, Io1, Io2, Io3),
461 clock: Clock,
462 feedback: Feedback,
463 ) -> Self {
464 IoPeriphPin::new_with_full_config(
465 feedback,
466 zynq7000::slcr::mio::Config::builder()
467 .with_disable_hstl_rcvr(false)
468 .with_pullup(false)
469 .with_io_type(voltage)
470 .with_speed(Speed::SlowCmosEdge)
471 .with_l3_sel(QSPI_MUX_CONFIG.l3_sel())
472 .with_l2_sel(QSPI_MUX_CONFIG.l2_sel())
473 .with_l1_sel(QSPI_MUX_CONFIG.l1_sel())
474 .with_l0_sel(QSPI_MUX_CONFIG.l0_sel())
475 .with_tri_enable(false)
476 .build(),
477 );
478 Self::new_single_qspi(regs, clock_config, mode, voltage, cs, io, clock)
479 }
480
481 #[inline]
482 pub fn regs(&mut self) -> &mut zynq7000::qspi::MmioQspi<'static> {
483 &mut self.ll.0
484 }
485
486 pub fn into_linear_addressed(mut self, config: LinearQspiConfig) -> QspiLinearAddressing {
487 self.ll.enable_linear_addressing(config);
488 QspiLinearAddressing { ll: self.ll }
489 }
490
491 pub fn into_io_mode(mut self, dual_flash: bool) -> QspiIoMode {
492 self.ll.enable_io_mode(dual_flash);
493 QspiIoMode { ll: self.ll }
494 }
495}
496
497pub struct QspiIoMode {
498 ll: QspiLowLevel,
499}
500
501impl QspiIoMode {
502 #[inline]
503 pub fn regs(&mut self) -> &mut zynq7000::qspi::MmioQspi<'static> {
504 &mut self.ll.0
505 }
506
507 pub fn transfer_guard(&mut self) -> QspiIoTransferGuard<'_> {
508 QspiIoTransferGuard::new(self)
509 }
510
511 pub fn into_qspi(mut self, ll: QspiLowLevel) -> Qspi {
512 self.ll.disable();
513 Qspi { ll }
514 }
515
516 #[inline]
518 pub fn write_word_txd_00(&mut self, word: u32) {
519 self.regs().write_tx_data_00(word);
520 }
521
522 #[inline]
524 pub fn write_word_txd_01(&mut self, word: u32) {
525 self.regs().write_tx_data_01(word);
526 }
527
528 #[inline]
530 pub fn write_word_txd_10(&mut self, word: u32) {
531 self.regs().write_tx_data_10(word);
532 }
533
534 #[inline]
536 pub fn write_word_txd_11(&mut self, word: u32) {
537 self.regs().write_tx_data_11(word);
538 }
539
540 #[inline]
541 pub fn read_rx_data(&mut self) -> u32 {
542 self.regs().read_rx_data()
543 }
544
545 pub fn transfer_init(&mut self) {
546 self.regs().modify_config(|mut val| {
547 val.set_peripheral_chip_select(false);
548 val
549 });
550 self.regs()
551 .write_spi_enable(SpiEnable::builder().with_enable(true).build());
552 }
553
554 pub fn transfer_start(&mut self) {
555 self.regs().modify_config(|mut val| {
556 val.set_manual_start_command(true);
557 val
558 });
559 }
560
561 pub fn transfer_done(&mut self) {
562 self.regs().modify_config(|mut val| {
563 val.set_peripheral_chip_select(true);
564 val
565 });
566 self.regs()
567 .write_spi_enable(SpiEnable::builder().with_enable(false).build());
568 }
569
570 pub fn read_status(&mut self) -> InterruptStatus {
571 self.regs().read_interrupt_status()
572 }
573
574 pub fn clear_rx_fifo(&mut self) {
575 while self.read_status().rx_above_threshold() {
576 self.read_rx_data();
577 }
578 }
579
580 pub fn into_linear_addressed(mut self, config: LinearQspiConfig) -> QspiLinearAddressing {
581 self.ll.enable_linear_addressing(config);
582 QspiLinearAddressing { ll: self.ll }
583 }
584}
585
586pub struct QspiIoTransferGuard<'a>(&'a mut QspiIoMode);
589
590impl<'a> QspiIoTransferGuard<'a> {
591 pub fn new(qspi: &'a mut QspiIoMode) -> Self {
592 qspi.clear_rx_fifo();
593 qspi.transfer_init();
594 Self(qspi)
595 }
596}
597
598impl QspiIoTransferGuard<'_> {
599 pub fn start(&mut self) {
600 self.0.transfer_start();
601 }
602}
603
604impl Deref for QspiIoTransferGuard<'_> {
605 type Target = QspiIoMode;
606
607 fn deref(&self) -> &Self::Target {
608 self.0
609 }
610}
611
612impl DerefMut for QspiIoTransferGuard<'_> {
613 fn deref_mut(&mut self) -> &mut Self::Target {
614 self.0
615 }
616}
617
618impl<'a> Drop for QspiIoTransferGuard<'a> {
619 fn drop(&mut self) {
620 self.0.transfer_done();
621 }
622}
623
624pub struct QspiLinearAddressing {
625 ll: QspiLowLevel,
626}
627
628impl QspiLinearAddressing {
629 pub const BASE_ADDRESS: usize = QSPI_START_ADDRESS;
631
632 pub fn into_io_mode(mut self, dual_flash: bool) -> QspiIoMode {
633 self.ll.enable_io_mode(dual_flash);
634 QspiIoMode { ll: self.ll }
635 }
636
637 pub fn read_guard(&mut self) -> QspiLinearReadGuard<'_> {
638 QspiLinearReadGuard::new(self)
639 }
640}
641
642pub struct QspiLinearReadGuard<'a>(&'a mut QspiLinearAddressing);
643
644impl QspiLinearReadGuard<'_> {
645 pub const BASE_ADDRESS: usize = QSPI_START_ADDRESS;
647
648 pub fn new(qspi: &mut QspiLinearAddressing) -> QspiLinearReadGuard<'_> {
649 qspi.ll
650 .0
651 .write_spi_enable(SpiEnable::builder().with_enable(true).build());
652 QspiLinearReadGuard(qspi)
653 }
654}
655
656impl Drop for QspiLinearReadGuard<'_> {
657 fn drop(&mut self) {
658 self.0
659 .ll
660 .0
661 .write_spi_enable(SpiEnable::builder().with_enable(false).build());
662 }
663}
664
665#[inline]
670pub fn reset() {
671 unsafe {
672 Slcr::with(|regs| {
673 regs.reset_ctrl().write_lqspi(
674 QspiResetControl::builder()
675 .with_qspi_ref_reset(true)
676 .with_cpu_1x_reset(true)
677 .build(),
678 );
679 for _ in 0..3 {
681 cortex_ar::asm::nop();
682 }
683 regs.reset_ctrl().write_lqspi(QspiResetControl::DEFAULT);
684 });
685 }
686}