From 95a1255c3ba8ead959bdd715f2855837ad89b06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Quentin?= Date: Wed, 23 Aug 2023 19:32:13 +0200 Subject: [PATCH] PARL_IO TX driver (#733) * PARL_IO TX driver * Update CHANGELOG.md * Update esp-hal-common/src/dma/mod.rs Co-authored-by: Jesse Braham --------- Co-authored-by: Jesse Braham --- CHANGELOG.md | 1 + esp-hal-common/src/dma/gdma.rs | 2 + esp-hal-common/src/dma/mod.rs | 4 + esp-hal-common/src/lib.rs | 2 + esp-hal-common/src/parl_io.rs | 935 ++++++++++++++++++++++++++ esp-hal-common/src/soc/esp32c6/mod.rs | 2 + esp-hal-common/src/soc/esp32h2/mod.rs | 2 + esp-hal-common/src/system.rs | 10 + esp32c6-hal/examples/parl_io_tx.rs | 119 ++++ esp32h2-hal/examples/parl_io_tx.rs | 119 ++++ 10 files changed, 1196 insertions(+) create mode 100644 esp-hal-common/src/parl_io.rs create mode 100644 esp32c6-hal/examples/parl_io_tx.rs create mode 100644 esp32h2-hal/examples/parl_io_tx.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 31f51746a..c2488f3b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add GPIO (output) and delay functionality to `esp32c6-lp-hal` (#715) - Implement RTCIO pullup, pulldown and hold control for Xtensa MCUs (#684) - Add GPIO input support and implement additional `embedded-hal` output traits for the C6's LP core [#720] +- Add PARL_IO TX driver for ESP32-C6 / ESP32-H2 (#733) - Implement `ufmt_write::uWrite` trait for USB Serial JTAG (#751) ### Changed diff --git a/esp-hal-common/src/dma/gdma.rs b/esp-hal-common/src/dma/gdma.rs index aa8dac7f2..07e7c0662 100644 --- a/esp-hal-common/src/dma/gdma.rs +++ b/esp-hal-common/src/dma/gdma.rs @@ -479,6 +479,8 @@ macro_rules! impl_channel { impl I2sPeripheral for [] {} impl I2s0Peripheral for [] {} impl I2s1Peripheral for [] {} + #[cfg(parl_io)] + impl ParlIoPeripheral for [] {} } }; } diff --git a/esp-hal-common/src/dma/mod.rs b/esp-hal-common/src/dma/mod.rs index 20b42ab3c..5ce233793 100644 --- a/esp-hal-common/src/dma/mod.rs +++ b/esp-hal-common/src/dma/mod.rs @@ -140,6 +140,8 @@ pub enum DmaPeripheral { Adc = 8, #[cfg(esp32s3)] Rmt = 9, + #[cfg(parl_io)] + ParlIo = 9, } #[derive(PartialEq, PartialOrd)] @@ -253,6 +255,8 @@ pub trait I2s0Peripheral: I2sPeripheral + PeripheralMarker {} /// Marks channels as useable for I2S1 pub trait I2s1Peripheral: I2sPeripheral + PeripheralMarker {} +pub trait ParlIoPeripheral: PeripheralMarker {} + /// DMA Rx pub trait Rx: RxPrivate {} diff --git a/esp-hal-common/src/lib.rs b/esp-hal-common/src/lib.rs index 2b7a274b3..3fb99dc7b 100644 --- a/esp-hal-common/src/lib.rs +++ b/esp-hal-common/src/lib.rs @@ -105,6 +105,8 @@ pub mod ledc; pub mod mcpwm; #[cfg(usb0)] pub mod otg_fs; +#[cfg(parl_io)] +pub mod parl_io; #[cfg(pcnt)] pub mod pcnt; pub mod peripheral; diff --git a/esp-hal-common/src/parl_io.rs b/esp-hal-common/src/parl_io.rs new file mode 100644 index 000000000..efc43ae42 --- /dev/null +++ b/esp-hal-common/src/parl_io.rs @@ -0,0 +1,935 @@ +//! # Parallel IO +//! +//! ## Overview +//! The Parallel IO peripheral is a general purpose parallel interface that can +//! be used to connect to external devices such as LED matrix, LCD display, +//! Printer and Camera. The peripheral has independent TX and RX units. Each +//! unit can have up to 8 or 16 data signals (depending on your target hardware) +//! plus 1 or 2 clock signals. +//! +//! At the moment, the Parallel IO driver only supports TX mode. The RX feature +//! is still working in progress. +//! +//! The driver uses DMA (Direct Memory Access) for efficient data transfer. +//! +//! ## Examples +//! +//! ### Initialization +//! ```no_run +//! // configure the data pins to use +//! let tx_pins = TxFourBits::new(io.pins.gpio1, io.pins.gpio2, io.pins.gpio3, io.pins.gpio4); +//! +//! // configure the valid pin which will be driven high during a TX transfer +//! let pin_conf = TxPinConfigWithValidPin::new(tx_pins, io.pins.gpio5); +//! +//! let mut parl_io = ParlIoTxOnly::new( +//! peripherals.PARL_IO, +//! dma_channel.configure( +//! false, +//! &mut tx_descriptors, +//! &mut rx_descriptors, +//! DmaPriority::Priority0, +//! ), +//! 1u32.MHz(), +//! &mut system.peripheral_clock_control, +//! &clocks, +//! ) +//! .unwrap(); +//! +//! // configure a pin for the clock signal +//! let cp = ClkOutPin::new(io.pins.gpio6); +//! +//! let mut parl_io_tx = +//! parl_io +//! .tx +//! .with_config(pin_conf, cp, 0, SampleEdge::Normal, BitPackOrder::Msb); +//! ``` + +use core::mem; + +use embedded_dma::ReadBuffer; +use fugit::HertzU32; +use peripheral::PeripheralRef; +use private::*; + +use crate::{ + clock::Clocks, + dma::{Channel, ChannelTypes, DmaError, DmaPeripheral, ParlIoPeripheral, TxPrivate}, + gpio::{InputPin, OutputPin}, + peripheral::{self, Peripheral}, + peripherals, + system::PeripheralClockControl, +}; + +#[allow(unused)] +const MAX_DMA_SIZE: usize = 32736; + +/// Parallel IO errors +#[derive(Debug, Clone, Copy)] +pub enum Error { + /// General DMA error + DmaError(DmaError), + /// Maximum transfer size (32736) exceeded + MaxDmaTransferSizeExceeded, + /// Trying to use an impossible clock rate + UnreachableClockRate, +} + +impl From for Error { + fn from(value: DmaError) -> Self { + Error::DmaError(value) + } +} + +/// Parallel IO sample edge +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum SampleEdge { + /// Positive edge + Normal = 0, + /// Negative edge + Invert = 1, +} + +/// Parallel IO bit packing order +#[derive(Debug, Clone, Copy)] +pub enum BitPackOrder { + /// Bit pack order: MSB + Msb = 0, + /// Bit pack order: LSB + Lsb = 1, +} + +/// Used to configure no pin as clock output +pub struct NoClkPin; +impl TxClkPin for NoClkPin { + fn configure(&mut self) { + // nothing + } +} + +/// Wraps a GPIO pin which will be used as the clock output signal +pub struct ClkOutPin<'d, P> +where + P: OutputPin, +{ + pin: PeripheralRef<'d, P>, +} +impl<'d, P> ClkOutPin<'d, P> +where + P: OutputPin, +{ + pub fn new(pin: impl Peripheral

+ 'd) -> Self { + crate::into_ref!(pin); + Self { pin } + } +} +impl<'d, P> TxClkPin for ClkOutPin<'d, P> +where + P: OutputPin, +{ + fn configure(&mut self) { + self.pin + .set_to_push_pull_output() + .connect_peripheral_to_output(crate::gpio::OutputSignal::PARL_TX_CLK); + } +} + +/// Wraps a GPIO pin which will be used as the clock input signal +pub struct ClkInPin<'d, P> +where + P: InputPin, +{ + pin: PeripheralRef<'d, P>, +} +impl<'d, P> ClkInPin<'d, P> +where + P: InputPin, +{ + pub fn new(pin: impl Peripheral

+ 'd) -> Self { + crate::into_ref!(pin); + Self { pin } + } +} +impl<'d, P> TxClkPin for ClkInPin<'d, P> +where + P: InputPin, +{ + fn configure(&mut self) { + let pcr = unsafe { &*crate::peripherals::PCR::PTR }; + pcr.parl_clk_tx_conf.modify(|_, w| { + w.parl_clk_tx_sel() + .variant(3) + .parl_clk_tx_div_num() + .variant(0) + }); // PAD_CLK_TX, no divider + + self.pin + .set_to_input() + .connect_input_to_peripheral(crate::gpio::InputSignal::PARL_TX_CLK); + } +} + +/// Pin configuration with an additional pin for the valid signal. +pub struct TxPinConfigWithValidPin<'d, P, VP> +where + P: NotContainsValidSignalPin + TxPins + ConfigurePins, + VP: OutputPin, +{ + tx_pins: P, + valid_pin: PeripheralRef<'d, VP>, +} + +impl<'d, P, VP> TxPinConfigWithValidPin<'d, P, VP> +where + P: NotContainsValidSignalPin + TxPins + ConfigurePins, + VP: OutputPin, +{ + pub fn new(tx_pins: P, valid_pin: impl Peripheral

+ 'd) -> Self { + crate::into_ref!(valid_pin); + Self { tx_pins, valid_pin } + } +} + +impl<'d, P, VP> TxPins for TxPinConfigWithValidPin<'d, P, VP> +where + P: NotContainsValidSignalPin + TxPins + ConfigurePins, + VP: OutputPin, +{ +} + +impl<'d, P, VP> ConfigurePins for TxPinConfigWithValidPin<'d, P, VP> +where + P: NotContainsValidSignalPin + TxPins + ConfigurePins, + VP: OutputPin, +{ + fn configure(&mut self) { + self.tx_pins.configure(); + self.valid_pin + .set_to_push_pull_output() + .connect_peripheral_to_output(Instance::tx_valid_pin_signal()); + Instance::set_tx_hw_valid_en(true); + } +} + +/// Pin configuration where the pin for the valid signal is the MSB pin. +pub struct TxPinConfigIncludingValidPin

+where + P: ContainsValidSignalPin + TxPins + ConfigurePins, +{ + tx_pins: P, +} + +impl

TxPinConfigIncludingValidPin

+where + P: ContainsValidSignalPin + TxPins + ConfigurePins, +{ + pub fn new(tx_pins: P) -> Self { + Self { tx_pins } + } +} + +impl

TxPins for TxPinConfigIncludingValidPin

where + P: ContainsValidSignalPin + TxPins + ConfigurePins +{ +} + +impl

ConfigurePins for TxPinConfigIncludingValidPin

+where + P: ContainsValidSignalPin + TxPins + ConfigurePins, +{ + fn configure(&mut self) { + self.tx_pins.configure(); + Instance::set_tx_hw_valid_en(true); + } +} + +macro_rules! tx_pins { + ($name:ident, $width:literal, $($pin:ident = $signal:ident),+ ) => { + paste::paste! { + #[doc = "Data pin configuration for "] + #[doc = stringify!($width)] + #[doc = "bit output mode"] + pub struct $name<'d, $($pin),+> { + $( + [< pin_ $pin:lower >] : PeripheralRef<'d, $pin>, + )+ + } + + impl<'d, $($pin),+> $name<'d, $($pin),+> + where + $($pin: OutputPin),+ + { + pub fn new( + $( + [< pin_ $pin:lower >] : impl Peripheral

+ 'd, + )+ + ) -> Self { + crate::into_ref!($( [< pin_ $pin:lower >] ),+); + Self { $( [< pin_ $pin:lower >] ),+ } + } + } + + impl<'d, $($pin),+> ConfigurePins for $name<'d, $($pin),+> + where + $($pin: OutputPin),+ + { + fn configure(&mut self) { + $( + self.[< pin_ $pin:lower >] + .set_to_push_pull_output() + .connect_peripheral_to_output(crate::gpio::OutputSignal::$signal); + )+ + + private::Instance::set_tx_bit_width( private::WidSel::[< Bits $width >]); + } + } + + impl<'d, $($pin),+> TxPins for $name<'d, $($pin),+> {} + } + }; +} + +tx_pins!(TxOneBit, 1, P0 = PARL_TX_DATA0); +tx_pins!(TxTwoBits, 2, P0 = PARL_TX_DATA0, P1 = PARL_TX_DATA1); +tx_pins!( + TxFourBits, + 4, + P0 = PARL_TX_DATA0, + P1 = PARL_TX_DATA1, + P2 = PARL_TX_DATA2, + P3 = PARL_TX_DATA3 +); +tx_pins!( + TxEightBits, + 8, + P0 = PARL_TX_DATA0, + P1 = PARL_TX_DATA1, + P2 = PARL_TX_DATA2, + P3 = PARL_TX_DATA3, + P4 = PARL_TX_DATA4, + P5 = PARL_TX_DATA5, + P6 = PARL_TX_DATA6, + P7 = PARL_TX_DATA7 +); +#[cfg(esp32c6)] +tx_pins!( + TxSixteenBits, + 16, + P0 = PARL_TX_DATA0, + P1 = PARL_TX_DATA1, + P2 = PARL_TX_DATA2, + P3 = PARL_TX_DATA3, + P4 = PARL_TX_DATA4, + P5 = PARL_TX_DATA5, + P6 = PARL_TX_DATA6, + P7 = PARL_TX_DATA7, + P8 = PARL_TX_DATA8, + P9 = PARL_TX_DATA9, + P10 = PARL_TX_DATA10, + P11 = PARL_TX_DATA11, + P12 = PARL_TX_DATA12, + P13 = PARL_TX_DATA13, + P14 = PARL_TX_DATA14, + P15 = PARL_TX_DATA15 +); + +impl<'d, P0> FullDuplex for TxOneBit<'d, P0> {} + +impl<'d, P0, P1> FullDuplex for TxTwoBits<'d, P0, P1> {} + +impl<'d, P0, P1, P2, P3> FullDuplex for TxFourBits<'d, P0, P1, P2, P3> {} + +impl<'d, P0, P1, P2, P3, P4, P5, P6, P7> FullDuplex + for TxEightBits<'d, P0, P1, P2, P3, P4, P5, P6, P7> +{ +} + +impl<'d, P0> NotContainsValidSignalPin for TxOneBit<'d, P0> {} + +impl<'d, P0, P1> NotContainsValidSignalPin for TxTwoBits<'d, P0, P1> {} + +impl<'d, P0, P1, P2, P3> NotContainsValidSignalPin for TxFourBits<'d, P0, P1, P2, P3> {} + +#[cfg(esp32c6)] +impl<'d, P0, P1, P2, P3, P4, P5, P6, P7> NotContainsValidSignalPin + for TxEightBits<'d, P0, P1, P2, P3, P4, P5, P6, P7> +{ +} + +#[cfg(esp32h2)] +impl<'d, P0, P1, P2, P3, P4, P5, P6, P7> ContainsValidSignalPin + for TxEightBits<'d, P0, P1, P2, P3, P4, P5, P6, P7> +{ +} + +#[cfg(esp32c6)] +impl<'d, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15> + ContainsValidSignalPin + for TxSixteenBits<'d, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15> +{ +} + +trait RegisterAccess {} + +impl<'d, CH> TxCreatorFullDuplex<'d, CH> +where + CH: ChannelTypes, +{ + pub fn with_config( + self, + mut tx_pins: P, + mut clk_pin: CP, + idle_value: u16, + sample_edge: SampleEdge, + bit_order: BitPackOrder, + ) -> ParlIoTx<'d, CH, P, CP> + where + P: FullDuplex + TxPins + ConfigurePins, + CP: TxClkPin, + { + tx_pins.configure(); + clk_pin.configure(); + + Instance::set_tx_idle_value(idle_value); + Instance::set_tx_sample_edge(sample_edge); + Instance::set_tx_bit_order(bit_order); + + ParlIoTx { + tx_channel: self.tx_channel, + _pins: tx_pins, + _clk_pin: clk_pin, + } + } +} + +impl<'d, CH> TxCreator<'d, CH> +where + CH: ChannelTypes, +{ + pub fn with_config( + self, + mut tx_pins: P, + mut clk_pin: CP, + idle_value: u16, + sample_edge: SampleEdge, + bit_order: BitPackOrder, + ) -> ParlIoTx<'d, CH, P, CP> + where + P: TxPins + ConfigurePins, + CP: TxClkPin, + { + tx_pins.configure(); + clk_pin.configure(); + + Instance::set_tx_idle_value(idle_value); + Instance::set_tx_sample_edge(sample_edge); + Instance::set_tx_bit_order(bit_order); + + ParlIoTx { + tx_channel: self.tx_channel, + _pins: tx_pins, + _clk_pin: clk_pin, + } + } +} + +/// Parallel IO TX channel +pub struct ParlIoTx<'d, CH, P, CP> +where + CH: ChannelTypes, + P: TxPins + ConfigurePins, + CP: TxClkPin, +{ + tx_channel: CH::Tx<'d>, + _pins: P, + _clk_pin: CP, +} + +impl<'d, CH, P, CP> core::fmt::Debug for ParlIoTx<'d, CH, P, CP> +where + CH: ChannelTypes, + P: TxPins + ConfigurePins, + CP: TxClkPin, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ParlIoTx").finish() + } +} + +/// Parallel IO in full duplex mode +/// +/// Full duplex mode might limit the maximum possible bit width. +pub struct ParlIoFullDuplex<'d, CH> +where + CH: ChannelTypes, + CH::P: ParlIoPeripheral, +{ + _parl_io: PeripheralRef<'d, peripherals::PARL_IO>, + pub tx: TxCreatorFullDuplex<'d, CH>, + pub rx: RxCreatorFullDuplex<'d, CH>, +} + +impl<'d, CH> ParlIoFullDuplex<'d, CH> +where + CH: ChannelTypes, + CH::P: ParlIoPeripheral, +{ + pub fn new( + parl_io: impl Peripheral

+ 'd, + mut dma_channel: Channel<'d, CH>, + frequency: HertzU32, + peripheral_clock_control: &mut PeripheralClockControl, + _clocks: &Clocks, + ) -> Result { + crate::into_ref!(parl_io); + internal_init(&mut dma_channel, frequency, peripheral_clock_control)?; + + Ok(Self { + _parl_io: parl_io, + tx: TxCreatorFullDuplex { + tx_channel: dma_channel.tx, + }, + rx: RxCreatorFullDuplex { + rx_channel: dma_channel.rx, + }, + }) + } +} + +/// Parallel IO in half duplex / TX only mode +pub struct ParlIoTxOnly<'d, CH> +where + CH: ChannelTypes, + CH::P: ParlIoPeripheral, +{ + _parl_io: PeripheralRef<'d, peripherals::PARL_IO>, + pub tx: TxCreator<'d, CH>, +} + +impl<'d, CH> ParlIoTxOnly<'d, CH> +where + CH: ChannelTypes, + CH::P: ParlIoPeripheral, +{ + pub fn new( + parl_io: impl Peripheral

+ 'd, + mut dma_channel: Channel<'d, CH>, + frequency: HertzU32, + peripheral_clock_control: &mut PeripheralClockControl, + _clocks: &Clocks, + ) -> Result { + crate::into_ref!(parl_io); + internal_init(&mut dma_channel, frequency, peripheral_clock_control)?; + + Ok(Self { + _parl_io: parl_io, + tx: TxCreator { + tx_channel: dma_channel.tx, + }, + }) + } +} + +fn internal_init<'d, CH>( + dma_channel: &mut Channel<'d, CH>, + frequency: HertzU32, + peripheral_clock_control: &mut PeripheralClockControl, +) -> Result<(), Error> +where + CH: ChannelTypes, + CH::P: ParlIoPeripheral, +{ + if frequency.raw() > 40_000_000 { + return Err(Error::UnreachableClockRate); + } + + peripheral_clock_control.enable(crate::system::Peripheral::ParlIo); + + let pcr = unsafe { &*crate::peripherals::PCR::PTR }; + + let divider = crate::soc::constants::PARL_IO_SCLK / frequency.raw(); + if divider > 0xffff { + return Err(Error::UnreachableClockRate); + } + let divider = divider as u16; + + pcr.parl_clk_tx_conf.modify(|_, w| { + w.parl_clk_tx_en() + .set_bit() + .parl_clk_tx_sel() + .variant(1) // PLL + .parl_clk_tx_div_num() + .variant(divider) + }); + + dma_channel.tx.init_channel(); + Ok(()) +} + +impl<'d, CH, P, CP> ParlIoTx<'d, CH, P, CP> +where + CH: ChannelTypes, + CH::P: ParlIoPeripheral, + P: TxPins + ConfigurePins, + CP: TxClkPin, +{ + /// Perform a DMA write. + /// + /// This will return a [DmaTransfer] owning the buffer(s) and the driver + /// instance. The maximum amount of data to be sent is 32736 + /// bytes. + pub fn write_dma( + mut self, + words: TXBUF, + ) -> Result, Error> + where + TXBUF: ReadBuffer, + { + let (ptr, len) = unsafe { words.read_buffer() }; + + if len > MAX_DMA_SIZE { + return Err(Error::MaxDmaTransferSizeExceeded); + } + + self.start_write_bytes_dma(ptr, len)?; + + Ok(DmaTransfer { + instance: self, + buffer: words, + }) + } + + fn start_write_bytes_dma<'w>(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> { + let pcr = unsafe { &*crate::peripherals::PCR::PTR }; + pcr.parl_clk_tx_conf + .modify(|_, w| w.parl_tx_rst_en().set_bit()); + pcr.parl_clk_tx_conf + .modify(|_, w| w.parl_tx_rst_en().clear_bit()); + + Instance::clear_tx_interrupts(); + Instance::set_tx_bytes(len as u16); + + self.tx_channel.is_done(); + + self.tx_channel + .prepare_transfer(DmaPeripheral::ParlIo, false, ptr, len)?; + + loop { + if Instance::is_tx_ready() { + break; + } + } + + Instance::set_tx_start(true); + return Ok(()); + } +} + +/// An in-progress DMA transfer. +pub struct DmaTransfer<'d, C, BUFFER, P, CP> +where + C: ChannelTypes, + C::P: ParlIoPeripheral, + P: TxPins + ConfigurePins, + CP: TxClkPin, +{ + instance: ParlIoTx<'d, C, P, CP>, + buffer: BUFFER, +} + +impl<'d, C, BUFFER, P, CP> DmaTransfer<'d, C, BUFFER, P, CP> +where + C: ChannelTypes, + C::P: ParlIoPeripheral, + P: TxPins + ConfigurePins, + CP: TxClkPin, +{ + /// Wait for the DMA transfer to complete and return the buffers and the + /// SPI instance. + pub fn wait( + self, + ) -> Result<(BUFFER, ParlIoTx<'d, C, P, CP>), (DmaError, BUFFER, ParlIoTx<'d, C, P, CP>)> { + loop { + if Instance::is_tx_eof() { + break; + } + } + + Instance::set_tx_start(false); + + // `DmaTransfer` needs to have a `Drop` implementation, because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read` + // and `mem::forget`. + // + // NOTE(unsafe) There is no panic branch between getting the resources + // and forgetting `self`. + unsafe { + let buffer = core::ptr::read(&self.buffer); + let payload = core::ptr::read(&self.instance); + let err = (&self).instance.tx_channel.has_error(); + mem::forget(self); + if err { + Err((DmaError::DescriptorError, buffer, payload)) + } else { + Ok((buffer, payload)) + } + } + } + + /// Check if the DMA transfer is complete + pub fn is_done(&self) -> bool { + let ch = &self.instance.tx_channel; + ch.is_done() + } +} + +mod private { + use super::{BitPackOrder, SampleEdge}; + use crate::dma::ChannelTypes; + + pub trait FullDuplex {} + + pub trait NotContainsValidSignalPin {} + + pub trait ContainsValidSignalPin {} + + pub trait ValidSignalPin {} + + pub trait TxPins {} + + pub trait TxClkPin { + fn configure(&mut self); + } + + pub trait ConfigurePins { + fn configure(&mut self); + } + + pub struct TxCreator<'d, CH> + where + CH: ChannelTypes, + { + pub tx_channel: CH::Tx<'d>, + } + + pub struct TxCreatorFullDuplex<'d, CH> + where + CH: ChannelTypes, + { + pub tx_channel: CH::Tx<'d>, + } + + pub struct RxCreatorFullDuplex<'d, CH> + where + CH: ChannelTypes, + { + pub rx_channel: CH::Rx<'d>, + } + + #[cfg(esp32c6)] + pub(super) enum WidSel { + Bits16 = 0, + Bits8 = 1, + Bits4 = 2, + Bits2 = 3, + Bits1 = 4, + } + + #[cfg(esp32h2)] + pub(super) enum WidSel { + Bits8 = 3, + Bits4 = 2, + Bits2 = 1, + Bits1 = 0, + } + + pub(super) struct Instance; + + #[cfg(esp32c6)] + impl Instance { + pub fn set_tx_bit_width(width: WidSel) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block + .tx_cfg0 + .modify(|_, w| w.tx_bus_wid_sel().variant(width as u8)); + } + + pub fn set_tx_idle_value(value: u16) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + reg_block + .tx_cfg1 + .modify(|_, w| w.tx_idle_value().variant(value)); + } + + pub fn set_tx_sample_edge(value: SampleEdge) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + reg_block + .tx_cfg0 + .modify(|_, w| w.tx_smp_edge_sel().variant(value as u8 == 1)); + } + + pub fn set_tx_bit_order(value: BitPackOrder) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + reg_block + .tx_cfg0 + .modify(|_, w| w.tx_bit_unpack_order().variant(value as u8 == 1)); + } + + pub fn clear_tx_interrupts() { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block.int_clr.write(|w| { + w.tx_fifo_rempty_int_clr() + .set_bit() + .tx_eof_int_clr() + .set_bit() + }); + } + + pub fn set_tx_bytes(len: u16) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block + .tx_cfg0 + .modify(|_, w| w.tx_bytelen().variant(len as u16)); + } + + pub fn is_tx_ready() -> bool { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block.st.read().tx_ready().bit_is_set() + } + + pub fn set_tx_start(value: bool) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block.tx_cfg0.modify(|_, w| w.tx_start().bit(value)); + } + + pub fn is_tx_eof() -> bool { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block.int_raw.read().tx_eof_int_raw().bit_is_set() + } + + pub fn tx_valid_pin_signal() -> crate::gpio::OutputSignal { + crate::gpio::OutputSignal::PARL_TX_DATA15 + } + + pub fn set_tx_hw_valid_en(value: bool) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block + .tx_cfg0 + .modify(|_, w| w.tx_hw_valid_en().bit(value)); + } + } + + #[cfg(esp32h2)] + impl Instance { + pub fn set_tx_bit_width(width: WidSel) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block + .tx_data_cfg + .modify(|_, w| w.tx_bus_wid_sel().variant(width as u8)); + } + + pub fn set_tx_idle_value(value: u16) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + reg_block + .tx_genrl_cfg + .modify(|_, w| w.tx_idle_value().variant(value)); + } + + pub fn set_tx_sample_edge(value: SampleEdge) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + reg_block.tx_clk_cfg.modify(|_, w| { + w.tx_clk_i_inv() + .bit(value == SampleEdge::Invert) + .tx_clk_o_inv() + .bit(value == SampleEdge::Invert) + }); + } + + pub fn set_tx_bit_order(value: BitPackOrder) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + reg_block + .tx_data_cfg + .modify(|_, w| w.tx_data_order_inv().variant(value as u8 == 1)); + } + + pub fn clear_tx_interrupts() { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block.int_clr.write(|w| { + w.tx_fifo_rempty_int_clr() + .set_bit() + .tx_eof_int_clr() + .set_bit() + }); + } + + pub fn set_tx_bytes(len: u16) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block + .tx_data_cfg + .modify(|_, w| w.tx_bitlen().variant((len as u32) * 8)); + } + + pub fn is_tx_ready() -> bool { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block.st.read().tx_ready().bit_is_set() + } + + pub fn set_tx_start(value: bool) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block + .tx_start_cfg + .modify(|_, w| w.tx_start().bit(value)); + } + + pub fn is_tx_eof() -> bool { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block.int_raw.read().tx_eof_int_raw().bit_is_set() + } + + pub fn tx_valid_pin_signal() -> crate::gpio::OutputSignal { + crate::gpio::OutputSignal::PARL_TX_DATA7 + } + + pub fn set_tx_hw_valid_en(value: bool) { + let reg_block: crate::peripherals::PARL_IO = + unsafe { crate::peripherals::PARL_IO::steal() }; + + reg_block + .tx_genrl_cfg + .modify(|_, w| w.tx_valid_output_en().bit(value)); + } + } +} diff --git a/esp-hal-common/src/soc/esp32c6/mod.rs b/esp-hal-common/src/soc/esp32c6/mod.rs index 94e08b82b..8ad33831e 100644 --- a/esp-hal-common/src/soc/esp32c6/mod.rs +++ b/esp-hal-common/src/soc/esp32c6/mod.rs @@ -31,6 +31,8 @@ pub(crate) mod constants { pub const RMT_CLOCK_SRC: u8 = 1; pub const RMT_CLOCK_SRC_FREQ: fugit::HertzU32 = fugit::HertzU32::MHz(80); + pub const PARL_IO_SCLK: u32 = 240_000_000; + pub const SOC_DRAM_LOW: u32 = 0x4080_0000; pub const SOC_DRAM_HIGH: u32 = 0x4088_0000; } diff --git a/esp-hal-common/src/soc/esp32h2/mod.rs b/esp-hal-common/src/soc/esp32h2/mod.rs index f832d29c1..8cfa25ed5 100644 --- a/esp-hal-common/src/soc/esp32h2/mod.rs +++ b/esp-hal-common/src/soc/esp32h2/mod.rs @@ -30,6 +30,8 @@ pub(crate) mod constants { pub const RMT_CLOCK_SRC: bool = false; pub const RMT_CLOCK_SRC_FREQ: fugit::HertzU32 = fugit::HertzU32::MHz(32); + pub const PARL_IO_SCLK: u32 = 96_000_000; + pub const SOC_DRAM_LOW: u32 = 0x4080_0000; pub const SOC_DRAM_HIGH: u32 = 0x4085_0000; } diff --git a/esp-hal-common/src/system.rs b/esp-hal-common/src/system.rs index 2427429ba..c844d6963 100755 --- a/esp-hal-common/src/system.rs +++ b/esp-hal-common/src/system.rs @@ -102,6 +102,8 @@ pub enum Peripheral { Uart2, #[cfg(rsa)] Rsa, + #[cfg(parl_io)] + ParlIo, } pub struct SoftwareInterruptControl { @@ -519,6 +521,14 @@ impl PeripheralClockControl { system.rsa_conf.modify(|_, w| w.rsa_rst_en().clear_bit()); system.rsa_pd_ctrl.modify(|_, w| w.rsa_mem_pd().clear_bit()); } + #[cfg(parl_io)] + Peripheral::ParlIo => { + system.parl_io_conf.modify(|_, w| w.parl_clk_en().set_bit()); + system.parl_io_conf.modify(|_, w| w.parl_rst_en().set_bit()); + system + .parl_io_conf + .modify(|_, w| w.parl_rst_en().clear_bit()); + } } } } diff --git a/esp32c6-hal/examples/parl_io_tx.rs b/esp32c6-hal/examples/parl_io_tx.rs new file mode 100644 index 000000000..ea1cf8140 --- /dev/null +++ b/esp32c6-hal/examples/parl_io_tx.rs @@ -0,0 +1,119 @@ +//! This shows using Parallel IO to output 4 bit parallel data at 1MHz clock +//! rate. +//! +//! Uses GPIO 1, 2, 3 and 4 as the data pins. +//! GPIO 5 as the "valid pin" (driven high during an active transfer) and GPIO +//! 6 as the clock signal output. +//! +//! You can use a logic analyzer to see how the pins are used. + +#![no_std] +#![no_main] + +use esp32c6_hal::{ + clock::ClockControl, + dma::DmaPriority, + gdma::Gdma, + gpio::IO, + parl_io::{ + BitPackOrder, + ClkOutPin, + ParlIoTxOnly, + SampleEdge, + TxFourBits, + TxPinConfigWithValidPin, + }, + peripherals::Peripherals, + prelude::*, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let mut system = peripherals.PCR.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. + let mut rtc = Rtc::new(peripherals.LP_CLKRST); + let timer_group0 = TimerGroup::new( + peripherals.TIMG0, + &clocks, + &mut system.peripheral_clock_control, + ); + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new( + peripherals.TIMG1, + &clocks, + &mut system.peripheral_clock_control, + ); + let mut wdt1 = timer_group1.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + let mut tx_descriptors = [0u32; 8 * 3]; + let mut rx_descriptors = [0u32; 8 * 3]; + + let dma = Gdma::new(peripherals.DMA, &mut system.peripheral_clock_control); + let dma_channel = dma.channel0; + + let tx_pins = TxFourBits::new(io.pins.gpio1, io.pins.gpio2, io.pins.gpio3, io.pins.gpio4); + + let pin_conf = TxPinConfigWithValidPin::new(tx_pins, io.pins.gpio5); + + let parl_io = ParlIoTxOnly::new( + peripherals.PARL_IO, + dma_channel.configure( + false, + &mut tx_descriptors, + &mut rx_descriptors, + DmaPriority::Priority0, + ), + 1u32.MHz(), + &mut system.peripheral_clock_control, + &clocks, + ) + .unwrap(); + + let clock_pin = ClkOutPin::new(io.pins.gpio6); + + let mut parl_io_tx = parl_io.tx.with_config( + pin_conf, + clock_pin, + 0, + SampleEdge::Normal, + BitPackOrder::Msb, + ); + + let mut buffer = dma_buffer(); + for i in 0..buffer.len() { + buffer[i] = (i % 255) as u8; + } + + let mut delay = Delay::new(&clocks); + + loop { + let transfer = parl_io_tx.write_dma(buffer).unwrap(); + + // the buffer and driver is moved into the transfer and we can get it back via + // `wait` + (buffer, parl_io_tx) = transfer.wait().unwrap(); + println!("Transferred {} bytes", buffer.len()); + + delay.delay_ms(500u32); + } +} + +fn dma_buffer() -> &'static mut [u8; 4092 * 4] { + static mut BUFFER: [u8; 4092 * 4] = [0u8; 4092 * 4]; + unsafe { &mut BUFFER } +} diff --git a/esp32h2-hal/examples/parl_io_tx.rs b/esp32h2-hal/examples/parl_io_tx.rs new file mode 100644 index 000000000..4adbf244d --- /dev/null +++ b/esp32h2-hal/examples/parl_io_tx.rs @@ -0,0 +1,119 @@ +//! This shows using Parallel IO to output 4 bit parallel data at 1MHz clock +//! rate. +//! +//! Uses GPIO 1, 2, 3 and 4 as the data pins. +//! GPIO 5 as the "valid pin" (driven high during an active transfer) and GPIO +//! 10 as the clock signal output. +//! +//! You can use a logic analyzer to see how the pins are used. + +#![no_std] +#![no_main] + +use esp32h2_hal::{ + clock::ClockControl, + dma::DmaPriority, + gdma::Gdma, + gpio::IO, + parl_io::{ + BitPackOrder, + ClkOutPin, + ParlIoTxOnly, + SampleEdge, + TxFourBits, + TxPinConfigWithValidPin, + }, + peripherals::Peripherals, + prelude::*, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let mut system = peripherals.PCR.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. + let mut rtc = Rtc::new(peripherals.LP_CLKRST); + let timer_group0 = TimerGroup::new( + peripherals.TIMG0, + &clocks, + &mut system.peripheral_clock_control, + ); + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new( + peripherals.TIMG1, + &clocks, + &mut system.peripheral_clock_control, + ); + let mut wdt1 = timer_group1.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + let mut tx_descriptors = [0u32; 8 * 3]; + let mut rx_descriptors = [0u32; 8 * 3]; + + let dma = Gdma::new(peripherals.DMA, &mut system.peripheral_clock_control); + let dma_channel = dma.channel0; + + let tx_pins = TxFourBits::new(io.pins.gpio1, io.pins.gpio2, io.pins.gpio3, io.pins.gpio4); + + let pin_conf = TxPinConfigWithValidPin::new(tx_pins, io.pins.gpio5); + + let parl_io = ParlIoTxOnly::new( + peripherals.PARL_IO, + dma_channel.configure( + false, + &mut tx_descriptors, + &mut rx_descriptors, + DmaPriority::Priority0, + ), + 1u32.MHz(), + &mut system.peripheral_clock_control, + &clocks, + ) + .unwrap(); + + let clock_pin = ClkOutPin::new(io.pins.gpio10); + + let mut parl_io_tx = parl_io.tx.with_config( + pin_conf, + clock_pin, + 0, + SampleEdge::Normal, + BitPackOrder::Msb, + ); + + let mut buffer = dma_buffer(); + for i in 0..buffer.len() { + buffer[i] = (i % 255) as u8; + } + + let mut delay = Delay::new(&clocks); + + loop { + let transfer = parl_io_tx.write_dma(buffer).unwrap(); + + // the buffer and driver is moved into the transfer and we can get it back via + // `wait` + (buffer, parl_io_tx) = transfer.wait().unwrap(); + println!("Transferred {} bytes", buffer.len()); + + delay.delay_ms(500u32); + } +} + +fn dma_buffer() -> &'static mut [u8; 4092 * 4] { + static mut BUFFER: [u8; 4092 * 4] = [0u8; 4092 * 4]; + unsafe { &mut BUFFER } +}