esp_hal/lcd_cam/lcd/
i8080.rs

1//! # LCD - I8080/MOTO6800 Mode.
2//!
3//! ## Overview
4//!
5//! The LCD_CAM peripheral I8080 driver provides support for the I8080
6//! format/timing. The driver mandates DMA (Direct Memory Access) for
7//! efficient data transfer.
8//!
9//! ## Examples
10//!
11//! ### MIPI-DSI Display
12//!
13//! The following example shows how to send a command to a MIPI-DSI display over
14//! the I8080 protocol.
15//!
16//! ```rust, no_run
17#![doc = crate::before_snippet!()]
18//! # use esp_hal::lcd_cam::{LcdCam, lcd::i8080::{Config, I8080, TxEightBits}};
19//! # use esp_hal::dma_tx_buffer;
20//! # use esp_hal::dma::DmaTxBuf;
21//!
22//! # let mut dma_buf = dma_tx_buffer!(32678)?;
23//!
24//! let tx_pins = TxEightBits::new(
25//!     peripherals.GPIO9,
26//!     peripherals.GPIO46,
27//!     peripherals.GPIO3,
28//!     peripherals.GPIO8,
29//!     peripherals.GPIO18,
30//!     peripherals.GPIO17,
31//!     peripherals.GPIO16,
32//!     peripherals.GPIO15,
33//! );
34//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
35//!
36//! let config = Config::default().with_frequency(Rate::from_mhz(20));
37//!
38//! let mut i8080 = I8080::new(
39//!     lcd_cam.lcd,
40//!     peripherals.DMA_CH0,
41//!     tx_pins,
42//!     config,
43//! )?
44//! .with_ctrl_pins(peripherals.GPIO0, peripherals.GPIO47);
45//!
46//! dma_buf.fill(&[0x55]);
47//! let transfer = i8080.send(0x3Au8, 0, dma_buf)?; // RGB565
48//! transfer.wait();
49//! # Ok(())
50//! # }
51//! ```
52
53use core::{
54    fmt::Formatter,
55    marker::PhantomData,
56    mem::{ManuallyDrop, size_of},
57    ops::{Deref, DerefMut},
58};
59
60use crate::{
61    Blocking,
62    DriverMode,
63    clock::Clocks,
64    dma::{ChannelTx, DmaError, DmaPeripheral, DmaTxBuffer, PeripheralTxChannel, TxChannelFor},
65    gpio::{
66        OutputConfig,
67        OutputSignal,
68        interconnect::{self, PeripheralOutput},
69    },
70    lcd_cam::{
71        BitOrder,
72        ByteOrder,
73        ClockError,
74        Instance,
75        LCD_DONE_WAKER,
76        Lcd,
77        calculate_clkm,
78        lcd::{ClockMode, DelayMode, Phase, Polarity},
79    },
80    pac,
81    peripherals::LCD_CAM,
82    system::{self, GenericPeripheralGuard},
83    time::Rate,
84};
85
86/// A configuration error.
87#[derive(Debug, Clone, Copy, PartialEq)]
88#[cfg_attr(feature = "defmt", derive(defmt::Format))]
89pub enum ConfigError {
90    /// Clock configuration error.
91    Clock(ClockError),
92}
93
94/// Represents the I8080 LCD interface.
95pub struct I8080<'d, Dm: DriverMode> {
96    lcd_cam: LCD_CAM<'d>,
97    tx_channel: ChannelTx<Blocking, PeripheralTxChannel<LCD_CAM<'d>>>,
98    _guard: GenericPeripheralGuard<{ system::Peripheral::LcdCam as u8 }>,
99    _mode: PhantomData<Dm>,
100}
101
102impl<'d, Dm> I8080<'d, Dm>
103where
104    Dm: DriverMode,
105{
106    /// Creates a new instance of the I8080 LCD interface.
107    pub fn new(
108        lcd: Lcd<'d, Dm>,
109        channel: impl TxChannelFor<LCD_CAM<'d>>,
110        mut pins: impl TxPins,
111        config: Config,
112    ) -> Result<Self, ConfigError> {
113        let tx_channel = ChannelTx::new(channel.degrade());
114
115        let mut this = Self {
116            lcd_cam: lcd.lcd_cam,
117            tx_channel,
118            _guard: lcd._guard,
119            _mode: PhantomData,
120        };
121
122        this.apply_config(&config)?;
123        pins.configure();
124
125        Ok(this)
126    }
127
128    fn regs(&self) -> &pac::lcd_cam::RegisterBlock {
129        self.lcd_cam.register_block()
130    }
131
132    /// Applies configuration.
133    ///
134    /// # Errors
135    ///
136    /// [`ConfigError::Clock`] variant will be returned if the frequency passed
137    /// in `Config` is too low.
138    pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> {
139        let clocks = Clocks::get();
140        // Due to https://www.espressif.com/sites/default/files/documentation/esp32-s3_errata_en.pdf
141        // the LCD_PCLK divider must be at least 2. To make up for this the user
142        // provided frequency is doubled to match.
143        let (i, divider) = calculate_clkm(
144            (config.frequency.as_hz() * 2) as _,
145            &[
146                clocks.xtal_clock.as_hz() as _,
147                clocks.cpu_clock.as_hz() as _,
148                clocks.crypto_pwm_clock.as_hz() as _,
149            ],
150        )
151        .map_err(ConfigError::Clock)?;
152
153        self.regs().lcd_clock().write(|w| unsafe {
154            // Force enable the clock for all configuration registers.
155            w.clk_en().set_bit();
156            w.lcd_clk_sel().bits((i + 1) as _);
157            w.lcd_clkm_div_num().bits(divider.div_num as _);
158            w.lcd_clkm_div_b().bits(divider.div_b as _);
159            w.lcd_clkm_div_a().bits(divider.div_a as _); // LCD_PCLK = LCD_CLK / 2
160            w.lcd_clk_equ_sysclk().clear_bit();
161            w.lcd_clkcnt_n().bits(2 - 1); // Must not be 0.
162            w.lcd_ck_idle_edge()
163                .bit(config.clock_mode.polarity == Polarity::IdleHigh);
164            w.lcd_ck_out_edge()
165                .bit(config.clock_mode.phase == Phase::ShiftHigh)
166        });
167
168        self.regs()
169            .lcd_ctrl()
170            .write(|w| w.lcd_rgb_mode_en().clear_bit());
171        self.regs()
172            .lcd_rgb_yuv()
173            .write(|w| w.lcd_conv_bypass().clear_bit());
174
175        self.regs().lcd_user().modify(|_, w| {
176            w.lcd_8bits_order().bit(false);
177            w.lcd_bit_order().bit(false);
178            w.lcd_byte_order().bit(false);
179            w.lcd_2byte_en().bit(false)
180        });
181        self.regs().lcd_misc().write(|w| unsafe {
182            // Set the threshold for Async Tx FIFO full event. (5 bits)
183            w.lcd_afifo_threshold_num().bits(0);
184            // Configure the setup cycles in LCD non-RGB mode. Setup cycles
185            // expected = this value + 1. (6 bit)
186            w.lcd_vfk_cyclelen()
187                .bits(config.setup_cycles.saturating_sub(1) as _);
188            // Configure the hold time cycles in LCD non-RGB mode. Hold
189            // cycles expected = this value + 1.
190            w.lcd_vbk_cyclelen()
191                .bits(config.hold_cycles.saturating_sub(1) as _);
192            // 1: Send the next frame data when the current frame is sent out.
193            // 0: LCD stops when the current frame is sent out.
194            w.lcd_next_frame_en().clear_bit();
195            // Enable blank region when LCD sends data out.
196            w.lcd_bk_en().set_bit();
197            // 1: LCD_CD = !LCD_CAM_LCD_CD_IDLE_EDGE when LCD is in DOUT phase.
198            // 0: LCD_CD = LCD_CAM_LCD_CD_IDLE_EDGE.
199            w.lcd_cd_data_set()
200                .bit(config.cd_data_edge != config.cd_idle_edge);
201            // 1: LCD_CD = !LCD_CAM_LCD_CD_IDLE_EDGE when LCD is in DUMMY phase.
202            // 0: LCD_CD = LCD_CAM_LCD_CD_IDLE_EDGE.
203            w.lcd_cd_dummy_set()
204                .bit(config.cd_dummy_edge != config.cd_idle_edge);
205            // 1: LCD_CD = !LCD_CAM_LCD_CD_IDLE_EDGE when LCD is in CMD phase.
206            // 0: LCD_CD = LCD_CAM_LCD_CD_IDLE_EDGE.
207            w.lcd_cd_cmd_set()
208                .bit(config.cd_cmd_edge != config.cd_idle_edge);
209            // The default value of LCD_CD
210            w.lcd_cd_idle_edge().bit(config.cd_idle_edge)
211        });
212        self.regs()
213            .lcd_dly_mode()
214            .write(|w| unsafe { w.lcd_cd_mode().bits(config.cd_mode as u8) });
215        self.regs().lcd_data_dout_mode().write(|w| unsafe {
216            w.dout0_mode().bits(config.output_bit_mode as u8);
217            w.dout1_mode().bits(config.output_bit_mode as u8);
218            w.dout2_mode().bits(config.output_bit_mode as u8);
219            w.dout3_mode().bits(config.output_bit_mode as u8);
220            w.dout4_mode().bits(config.output_bit_mode as u8);
221            w.dout5_mode().bits(config.output_bit_mode as u8);
222            w.dout6_mode().bits(config.output_bit_mode as u8);
223            w.dout7_mode().bits(config.output_bit_mode as u8);
224            w.dout8_mode().bits(config.output_bit_mode as u8);
225            w.dout9_mode().bits(config.output_bit_mode as u8);
226            w.dout10_mode().bits(config.output_bit_mode as u8);
227            w.dout11_mode().bits(config.output_bit_mode as u8);
228            w.dout12_mode().bits(config.output_bit_mode as u8);
229            w.dout13_mode().bits(config.output_bit_mode as u8);
230            w.dout14_mode().bits(config.output_bit_mode as u8);
231            w.dout15_mode().bits(config.output_bit_mode as u8)
232        });
233
234        self.regs()
235            .lcd_user()
236            .modify(|_, w| w.lcd_update().set_bit());
237
238        Ok(())
239    }
240
241    /// Configures the byte order for data transmission in 16-bit mode.
242    /// This must be set to [ByteOrder::default()] when transmitting in 8-bit
243    /// mode.
244    pub fn set_byte_order(&mut self, byte_order: ByteOrder) -> &mut Self {
245        let is_inverted = byte_order != ByteOrder::default();
246        self.regs()
247            .lcd_user()
248            .modify(|_, w| w.lcd_byte_order().bit(is_inverted));
249        self
250    }
251
252    /// Configures the byte order for data transmission in 8-bit mode.
253    /// This must be set to [ByteOrder::default()] when transmitting in 16-bit
254    /// mode.
255    pub fn set_8bits_order(&mut self, byte_order: ByteOrder) -> &mut Self {
256        let is_inverted = byte_order != ByteOrder::default();
257        self.regs()
258            .lcd_user()
259            .modify(|_, w| w.lcd_8bits_order().bit(is_inverted));
260        self
261    }
262
263    /// Configures the bit order for data transmission.
264    pub fn set_bit_order(&mut self, bit_order: BitOrder) -> &mut Self {
265        self.regs()
266            .lcd_user()
267            .modify(|_, w| w.lcd_bit_order().bit(bit_order != BitOrder::default()));
268        self
269    }
270
271    /// Associates a CS pin with the I8080 interface.
272    pub fn with_cs(self, cs: impl PeripheralOutput<'d>) -> Self {
273        let cs = cs.into();
274
275        cs.apply_output_config(&OutputConfig::default());
276        cs.set_output_enable(true);
277
278        OutputSignal::LCD_CS.connect_to(&cs);
279
280        self
281    }
282
283    /// Configures the control pins for the I8080 interface.
284    pub fn with_ctrl_pins(
285        self,
286        dc: impl PeripheralOutput<'d>,
287        wrx: impl PeripheralOutput<'d>,
288    ) -> Self {
289        let dc = dc.into();
290        let wrx = wrx.into();
291
292        dc.apply_output_config(&OutputConfig::default());
293        dc.set_output_enable(true);
294        OutputSignal::LCD_DC.connect_to(&dc);
295
296        wrx.apply_output_config(&OutputConfig::default());
297        wrx.set_output_enable(true);
298        OutputSignal::LCD_PCLK.connect_to(&wrx);
299
300        self
301    }
302
303    /// Sends a command and data to the LCD using DMA.
304    ///
305    /// Passing a `Command<u8>` will make this an 8-bit transfer and a
306    /// `Command<u16>` will make this a 16-bit transfer.
307    ///
308    /// Note: A 16-bit transfer on an 8-bit bus will silently truncate the 2nd
309    /// byte and an 8-bit transfer on a 16-bit bus will silently pad each
310    /// byte to 2 bytes.
311    pub fn send<W: Into<u16> + Copy, BUF: DmaTxBuffer>(
312        mut self,
313        cmd: impl Into<Command<W>>,
314        dummy: u8,
315        mut data: BUF,
316    ) -> Result<I8080Transfer<'d, BUF, Dm>, (DmaError, Self, BUF)> {
317        let cmd = cmd.into();
318
319        // Reset LCD control unit and Async Tx FIFO
320        self.regs()
321            .lcd_user()
322            .modify(|_, w| w.lcd_reset().set_bit());
323        self.regs()
324            .lcd_misc()
325            .modify(|_, w| w.lcd_afifo_reset().set_bit());
326
327        // Set cmd value
328        match cmd {
329            Command::None => {
330                self.regs()
331                    .lcd_user()
332                    .modify(|_, w| w.lcd_cmd().clear_bit());
333            }
334            Command::One(value) => {
335                self.regs().lcd_user().modify(|_, w| {
336                    w.lcd_cmd().set_bit();
337                    w.lcd_cmd_2_cycle_en().clear_bit()
338                });
339                self.regs()
340                    .lcd_cmd_val()
341                    .write(|w| unsafe { w.lcd_cmd_value().bits(value.into() as _) });
342            }
343            Command::Two(first, second) => {
344                self.regs().lcd_user().modify(|_, w| {
345                    w.lcd_cmd().set_bit();
346                    w.lcd_cmd_2_cycle_en().set_bit()
347                });
348                let cmd = first.into() as u32 | ((second.into() as u32) << 16);
349                self.regs()
350                    .lcd_cmd_val()
351                    .write(|w| unsafe { w.lcd_cmd_value().bits(cmd) });
352            }
353        }
354
355        let is_2byte_mode = size_of::<W>() == 2;
356        self.regs().lcd_user().modify(|_, w| unsafe {
357            // Set dummy length
358            if dummy > 0 {
359                // Enable DUMMY phase in LCD sequence when LCD starts.
360                w.lcd_dummy()
361                    .set_bit()
362                    // Configure DUMMY cycles. DUMMY cycles = this value + 1. (2 bits)
363                    .lcd_dummy_cyclelen()
364                    .bits((dummy - 1) as _)
365            } else {
366                w.lcd_dummy().clear_bit()
367            }
368            .lcd_2byte_en()
369            .bit(is_2byte_mode)
370        });
371
372        // Use continous mode for DMA. FROM the S3 TRM:
373        // > In a continuous output, LCD module keeps sending data till:
374        // > i. LCD_CAM_LCD_START is cleared;
375        // > ii. or LCD_CAM_LCD_RESET is set;
376        // > iii. or all the data in GDMA is sent out.
377        self.regs()
378            .lcd_user()
379            .modify(|_, w| w.lcd_always_out_en().set_bit().lcd_dout().set_bit());
380
381        let result = unsafe {
382            self.tx_channel
383                .prepare_transfer(DmaPeripheral::LcdCam, &mut data)
384        }
385        .and_then(|_| self.tx_channel.start_transfer());
386        if let Err(err) = result {
387            return Err((err, self, data));
388        }
389
390        // Setup interrupts.
391        self.regs()
392            .lc_dma_int_clr()
393            .write(|w| w.lcd_trans_done_int_clr().set_bit());
394
395        self.regs().lcd_user().modify(|_, w| {
396            w.lcd_update().set_bit();
397            w.lcd_start().set_bit()
398        });
399
400        Ok(I8080Transfer {
401            i8080: ManuallyDrop::new(self),
402            buf_view: ManuallyDrop::new(data.into_view()),
403        })
404    }
405}
406
407impl<Dm: DriverMode> core::fmt::Debug for I8080<'_, Dm> {
408    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
409        f.debug_struct("I8080").finish()
410    }
411}
412
413/// Represents an ongoing (or potentially finished) transfer using the I8080 LCD
414/// interface
415pub struct I8080Transfer<'d, BUF: DmaTxBuffer, Dm: DriverMode> {
416    i8080: ManuallyDrop<I8080<'d, Dm>>,
417    buf_view: ManuallyDrop<BUF::View>,
418}
419
420impl<'d, BUF: DmaTxBuffer, Dm: DriverMode> I8080Transfer<'d, BUF, Dm> {
421    /// Returns true when [Self::wait] will not block.
422    pub fn is_done(&self) -> bool {
423        self.i8080
424            .regs()
425            .lcd_user()
426            .read()
427            .lcd_start()
428            .bit_is_clear()
429    }
430
431    /// Stops this transfer on the spot and returns the peripheral and buffer.
432    pub fn cancel(mut self) -> (I8080<'d, Dm>, BUF) {
433        self.stop_peripherals();
434        let (_, i8080, buf) = self.wait();
435        (i8080, buf)
436    }
437
438    /// Waits for the transfer to finish and returns the peripheral and buffer.
439    ///
440    /// Note: This also clears the transfer interrupt so it can be used in
441    /// interrupt handlers to "handle" the interrupt.
442    pub fn wait(mut self) -> (Result<(), DmaError>, I8080<'d, Dm>, BUF) {
443        while !self.is_done() {}
444
445        // Clear "done" interrupt.
446        self.i8080
447            .regs()
448            .lc_dma_int_clr()
449            .write(|w| w.lcd_trans_done_int_clr().set_bit());
450
451        // SAFETY: Since forget is called on self, we know that self.i8080 and
452        // self.buf_view won't be touched again.
453        let (i8080, view) = unsafe {
454            let i8080 = ManuallyDrop::take(&mut self.i8080);
455            let view = ManuallyDrop::take(&mut self.buf_view);
456            core::mem::forget(self);
457            (i8080, view)
458        };
459
460        let result = if i8080.tx_channel.has_error() {
461            Err(DmaError::DescriptorError)
462        } else {
463            Ok(())
464        };
465
466        (result, i8080, BUF::from_view(view))
467    }
468
469    fn stop_peripherals(&mut self) {
470        // Stop the LCD_CAM peripheral.
471        self.i8080
472            .regs()
473            .lcd_user()
474            .modify(|_, w| w.lcd_start().clear_bit());
475
476        // Stop the DMA
477        self.i8080.tx_channel.stop_transfer();
478    }
479}
480
481impl<BUF: DmaTxBuffer, Dm: DriverMode> Deref for I8080Transfer<'_, BUF, Dm> {
482    type Target = BUF::View;
483
484    fn deref(&self) -> &Self::Target {
485        &self.buf_view
486    }
487}
488
489impl<BUF: DmaTxBuffer, Dm: DriverMode> DerefMut for I8080Transfer<'_, BUF, Dm> {
490    fn deref_mut(&mut self) -> &mut Self::Target {
491        &mut self.buf_view
492    }
493}
494
495impl<BUF: DmaTxBuffer> I8080Transfer<'_, BUF, crate::Async> {
496    /// Waits for [Self::is_done] to return true.
497    pub async fn wait_for_done(&mut self) {
498        use core::{
499            future::Future,
500            pin::Pin,
501            task::{Context, Poll},
502        };
503
504        struct LcdDoneFuture {}
505
506        impl Future for LcdDoneFuture {
507            type Output = ();
508
509            fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
510                if Instance::is_lcd_done_set() {
511                    // Interrupt bit will be cleared in Self::wait.
512                    // This allows `wait_for_done` to be called more than once.
513                    //
514                    // Instance::clear_lcd_done();
515                    Poll::Ready(())
516                } else {
517                    LCD_DONE_WAKER.register(cx.waker());
518                    Instance::listen_lcd_done();
519                    Poll::Pending
520                }
521            }
522        }
523
524        impl Drop for LcdDoneFuture {
525            fn drop(&mut self) {
526                Instance::unlisten_lcd_done();
527            }
528        }
529
530        LcdDoneFuture {}.await
531    }
532}
533
534impl<BUF: DmaTxBuffer, Dm: DriverMode> Drop for I8080Transfer<'_, BUF, Dm> {
535    fn drop(&mut self) {
536        self.stop_peripherals();
537
538        // SAFETY: This is Drop, we know that self.i8080 and self.buf_view
539        // won't be touched again.
540        let view = unsafe {
541            ManuallyDrop::drop(&mut self.i8080);
542            ManuallyDrop::take(&mut self.buf_view)
543        };
544        let _ = BUF::from_view(view);
545    }
546}
547
548#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)]
549#[cfg_attr(feature = "defmt", derive(defmt::Format))]
550/// Configuration settings for the I8080 interface.
551pub struct Config {
552    /// Specifies the clock mode, including polarity and phase settings.
553    clock_mode: ClockMode,
554
555    /// The frequency of the pixel clock.
556    frequency: Rate,
557
558    /// Setup cycles expected, must be at least 1. (6 bits)
559    setup_cycles: usize,
560
561    /// Hold cycles expected, must be at least 1. (13 bits)
562    hold_cycles: usize,
563
564    /// The default value of LCD_CD.
565    cd_idle_edge: bool,
566    /// The value of LCD_CD during CMD phase.
567    cd_cmd_edge: bool,
568    /// The value of LCD_CD during dummy phase.
569    cd_dummy_edge: bool,
570    /// The value of LCD_CD during data phase.
571    cd_data_edge: bool,
572
573    /// The output LCD_CD is delayed by module clock LCD_CLK.
574    cd_mode: DelayMode,
575    /// The output data bits are delayed by module clock LCD_CLK.
576    output_bit_mode: DelayMode,
577}
578
579impl Default for Config {
580    fn default() -> Self {
581        Self {
582            clock_mode: Default::default(),
583            frequency: Rate::from_mhz(20),
584            setup_cycles: 1,
585            hold_cycles: 1,
586            cd_idle_edge: false,
587            cd_cmd_edge: false,
588            cd_dummy_edge: false,
589            cd_data_edge: true,
590            cd_mode: Default::default(),
591            output_bit_mode: Default::default(),
592        }
593    }
594}
595
596/// LCD_CAM I8080 command.
597///
598/// Can be [Command::None] if command phase should be suppressed.
599#[derive(Debug, Clone, Copy, PartialEq)]
600#[cfg_attr(feature = "defmt", derive(defmt::Format))]
601pub enum Command<T> {
602    /// Suppresses the command phase. No command is sent.
603    None,
604    /// Sends a single-word command.
605    One(T),
606    /// Sends a two-word command.
607    Two(T, T),
608}
609
610impl From<u8> for Command<u8> {
611    fn from(value: u8) -> Self {
612        Command::One(value)
613    }
614}
615
616impl From<u16> for Command<u16> {
617    fn from(value: u16) -> Self {
618        Command::One(value)
619    }
620}
621
622/// Represents a group of 8 output pins configured for 8-bit parallel data
623/// transmission.
624pub struct TxEightBits<'d> {
625    pins: [interconnect::OutputSignal<'d>; 8],
626}
627
628impl<'d> TxEightBits<'d> {
629    #[allow(clippy::too_many_arguments)]
630    /// Creates a new `TxEightBits` instance with the provided output pins.
631    pub fn new(
632        pin_0: impl PeripheralOutput<'d>,
633        pin_1: impl PeripheralOutput<'d>,
634        pin_2: impl PeripheralOutput<'d>,
635        pin_3: impl PeripheralOutput<'d>,
636        pin_4: impl PeripheralOutput<'d>,
637        pin_5: impl PeripheralOutput<'d>,
638        pin_6: impl PeripheralOutput<'d>,
639        pin_7: impl PeripheralOutput<'d>,
640    ) -> Self {
641        Self {
642            pins: [
643                pin_0.into(),
644                pin_1.into(),
645                pin_2.into(),
646                pin_3.into(),
647                pin_4.into(),
648                pin_5.into(),
649                pin_6.into(),
650                pin_7.into(),
651            ],
652        }
653    }
654}
655
656impl TxPins for TxEightBits<'_> {
657    fn configure(&mut self) {
658        const SIGNALS: [OutputSignal; 8] = [
659            OutputSignal::LCD_DATA_0,
660            OutputSignal::LCD_DATA_1,
661            OutputSignal::LCD_DATA_2,
662            OutputSignal::LCD_DATA_3,
663            OutputSignal::LCD_DATA_4,
664            OutputSignal::LCD_DATA_5,
665            OutputSignal::LCD_DATA_6,
666            OutputSignal::LCD_DATA_7,
667        ];
668
669        for (pin, signal) in self.pins.iter().zip(SIGNALS.into_iter()) {
670            pin.apply_output_config(&OutputConfig::default());
671            pin.set_output_enable(true);
672            signal.connect_to(pin);
673        }
674    }
675}
676
677/// Represents a group of 16 output pins configured for 16-bit parallel data
678/// transmission.
679pub struct TxSixteenBits<'d> {
680    pins: [interconnect::OutputSignal<'d>; 16],
681}
682
683impl<'d> TxSixteenBits<'d> {
684    #[allow(clippy::too_many_arguments)]
685    /// Creates a new `TxSixteenBits` instance with the provided output pins.
686    pub fn new(
687        pin_0: impl PeripheralOutput<'d>,
688        pin_1: impl PeripheralOutput<'d>,
689        pin_2: impl PeripheralOutput<'d>,
690        pin_3: impl PeripheralOutput<'d>,
691        pin_4: impl PeripheralOutput<'d>,
692        pin_5: impl PeripheralOutput<'d>,
693        pin_6: impl PeripheralOutput<'d>,
694        pin_7: impl PeripheralOutput<'d>,
695        pin_8: impl PeripheralOutput<'d>,
696        pin_9: impl PeripheralOutput<'d>,
697        pin_10: impl PeripheralOutput<'d>,
698        pin_11: impl PeripheralOutput<'d>,
699        pin_12: impl PeripheralOutput<'d>,
700        pin_13: impl PeripheralOutput<'d>,
701        pin_14: impl PeripheralOutput<'d>,
702        pin_15: impl PeripheralOutput<'d>,
703    ) -> Self {
704        Self {
705            pins: [
706                pin_0.into(),
707                pin_1.into(),
708                pin_2.into(),
709                pin_3.into(),
710                pin_4.into(),
711                pin_5.into(),
712                pin_6.into(),
713                pin_7.into(),
714                pin_8.into(),
715                pin_9.into(),
716                pin_10.into(),
717                pin_11.into(),
718                pin_12.into(),
719                pin_13.into(),
720                pin_14.into(),
721                pin_15.into(),
722            ],
723        }
724    }
725}
726
727impl TxPins for TxSixteenBits<'_> {
728    fn configure(&mut self) {
729        const SIGNALS: [OutputSignal; 16] = [
730            OutputSignal::LCD_DATA_0,
731            OutputSignal::LCD_DATA_1,
732            OutputSignal::LCD_DATA_2,
733            OutputSignal::LCD_DATA_3,
734            OutputSignal::LCD_DATA_4,
735            OutputSignal::LCD_DATA_5,
736            OutputSignal::LCD_DATA_6,
737            OutputSignal::LCD_DATA_7,
738            OutputSignal::LCD_DATA_8,
739            OutputSignal::LCD_DATA_9,
740            OutputSignal::LCD_DATA_10,
741            OutputSignal::LCD_DATA_11,
742            OutputSignal::LCD_DATA_12,
743            OutputSignal::LCD_DATA_13,
744            OutputSignal::LCD_DATA_14,
745            OutputSignal::LCD_DATA_15,
746        ];
747
748        for (pin, signal) in self.pins.iter_mut().zip(SIGNALS.into_iter()) {
749            pin.apply_output_config(&OutputConfig::default());
750            pin.set_output_enable(true);
751            signal.connect_to(pin);
752        }
753    }
754}
755
756#[doc(hidden)]
757pub trait TxPins {
758    fn configure(&mut self);
759}