diff options
| author | elagil <[email protected]> | 2024-11-18 20:51:22 +0100 |
|---|---|---|
| committer | elagil <[email protected]> | 2024-11-18 20:51:22 +0100 |
| commit | 62dbdcd45adfa7f3b74b377f0b4ef7eafaef78fd (patch) | |
| tree | c8417e738a1eb6eb46a7409ba5d8a15a69847a78 /embassy-stm32/src/spdifrx | |
| parent | 050d3d1a092eca69b14bd07cd5ca8496a0f09f2d (diff) | |
feat: add SPDIFRX driver
Diffstat (limited to 'embassy-stm32/src/spdifrx')
| -rw-r--r-- | embassy-stm32/src/spdifrx/mod.rs | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/embassy-stm32/src/spdifrx/mod.rs b/embassy-stm32/src/spdifrx/mod.rs new file mode 100644 index 000000000..bfc8bff29 --- /dev/null +++ b/embassy-stm32/src/spdifrx/mod.rs | |||
| @@ -0,0 +1,336 @@ | |||
| 1 | //! S/PDIF receiver | ||
| 2 | #![macro_use] | ||
| 3 | #![cfg_attr(gpdma, allow(unused))] | ||
| 4 | |||
| 5 | use core::marker::PhantomData; | ||
| 6 | |||
| 7 | use embassy_hal_internal::{into_ref, PeripheralRef}; | ||
| 8 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 9 | |||
| 10 | use crate::dma::ringbuffer::Error as RingbufferError; | ||
| 11 | pub use crate::dma::word; | ||
| 12 | #[cfg(not(gpdma))] | ||
| 13 | use crate::dma::ReadableRingBuffer; | ||
| 14 | use crate::dma::{Channel, TransferOptions}; | ||
| 15 | use crate::gpio::{AfType, AnyPin, Pull, SealedPin as _}; | ||
| 16 | use crate::interrupt::typelevel::Interrupt; | ||
| 17 | use crate::pac::spdifrx::Spdifrx as Regs; | ||
| 18 | use crate::rcc::{RccInfo, SealedRccPeripheral}; | ||
| 19 | use crate::{interrupt, peripherals, Peripheral}; | ||
| 20 | |||
| 21 | /// Possible S/PDIF preamble types. | ||
| 22 | #[allow(dead_code)] | ||
| 23 | #[repr(u8)] | ||
| 24 | enum PreambleType { | ||
| 25 | Unused = 0x00, | ||
| 26 | /// The preamble changes to preamble “B” once every 192 frames to identify the start of the block structure used to | ||
| 27 | /// organize the channel status and user information. | ||
| 28 | B = 0x01, | ||
| 29 | /// The first sub-frame (left or “A” channel in stereophonic operation and primary channel in monophonic operation) | ||
| 30 | /// normally starts with preamble “M” | ||
| 31 | M = 0x02, | ||
| 32 | /// The second sub-frame (right or “B” channel in stereophonic operation and secondary channel in monophonic | ||
| 33 | /// operation) always starts with preamble “W”. | ||
| 34 | W = 0x03, | ||
| 35 | } | ||
| 36 | |||
| 37 | macro_rules! new_spdifrx_pin { | ||
| 38 | ($name:ident, $af_type:expr) => {{ | ||
| 39 | let pin = $name.into_ref(); | ||
| 40 | let input_sel = pin.input_sel(); | ||
| 41 | pin.set_as_af(pin.af_num(), $af_type); | ||
| 42 | (Some(pin.map_into()), input_sel) | ||
| 43 | }}; | ||
| 44 | } | ||
| 45 | |||
| 46 | macro_rules! impl_spdifrx_pin { | ||
| 47 | ($inst:ident, $pin:ident, $af:expr, $sel:expr) => { | ||
| 48 | impl crate::spdifrx::InPin<peripherals::$inst> for crate::peripherals::$pin { | ||
| 49 | fn af_num(&self) -> u8 { | ||
| 50 | $af | ||
| 51 | } | ||
| 52 | fn input_sel(&self) -> u8 { | ||
| 53 | $sel | ||
| 54 | } | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Ring-buffered SPDIFRX driver. | ||
| 60 | /// | ||
| 61 | /// Data is read by DMAs and stored in a ring buffer. | ||
| 62 | #[cfg(not(gpdma))] | ||
| 63 | pub struct Spdifrx<'d, T: Instance> { | ||
| 64 | _peri: PeripheralRef<'d, T>, | ||
| 65 | spdifrx_in: Option<PeripheralRef<'d, AnyPin>>, | ||
| 66 | data_ring_buffer: ReadableRingBuffer<'d, u32>, | ||
| 67 | } | ||
| 68 | |||
| 69 | /// Gives the address of the data register. | ||
| 70 | fn dr_address(r: Regs) -> *mut u32 { | ||
| 71 | #[cfg(spdifrx_v1)] | ||
| 72 | let address = r.dr().as_ptr() as _; | ||
| 73 | #[cfg(spdifrx_h7)] | ||
| 74 | let address = r.fmt0_dr().as_ptr() as _; // All fmtx_dr() implementations have the same address. | ||
| 75 | |||
| 76 | return address; | ||
| 77 | } | ||
| 78 | |||
| 79 | /// Gives the address of the channel status register. | ||
| 80 | #[allow(unused)] | ||
| 81 | fn csr_address(r: Regs) -> *mut u32 { | ||
| 82 | r.csr().as_ptr() as _ | ||
| 83 | } | ||
| 84 | |||
| 85 | /// Select the channel for capturing control information. | ||
| 86 | pub enum ControlChannelSelection { | ||
| 87 | /// Capture control info from channel A. | ||
| 88 | A, | ||
| 89 | /// Capture control info from channel B. | ||
| 90 | B, | ||
| 91 | } | ||
| 92 | |||
| 93 | /// Configuration options for the SPDIFRX driver. | ||
| 94 | pub struct Config { | ||
| 95 | /// Select the channel for capturing control information. | ||
| 96 | pub control_channel_selection: ControlChannelSelection, | ||
| 97 | } | ||
| 98 | |||
| 99 | /// S/PDIF errors. | ||
| 100 | #[derive(Debug)] | ||
| 101 | pub enum Error { | ||
| 102 | /// DMA overrun error. | ||
| 103 | RingbufferError(RingbufferError), | ||
| 104 | /// Left/right channel synchronization error. | ||
| 105 | ChannelSyncError, | ||
| 106 | } | ||
| 107 | |||
| 108 | impl From<RingbufferError> for Error { | ||
| 109 | fn from(error: RingbufferError) -> Self { | ||
| 110 | Self::RingbufferError(error) | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | impl Default for Config { | ||
| 115 | fn default() -> Self { | ||
| 116 | Self { | ||
| 117 | control_channel_selection: ControlChannelSelection::A, | ||
| 118 | } | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | #[cfg(not(gpdma))] | ||
| 123 | impl<'d, T: Instance> Spdifrx<'d, T> { | ||
| 124 | fn dma_opts() -> TransferOptions { | ||
| 125 | TransferOptions { | ||
| 126 | half_transfer_ir: true, | ||
| 127 | // new_write() and new_read() always use circular mode | ||
| 128 | ..Default::default() | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | /// Create a new `Spdifrx` instance. | ||
| 133 | pub fn new( | ||
| 134 | peri: impl Peripheral<P = T> + 'd, | ||
| 135 | _irq: impl interrupt::typelevel::Binding<T::GlobalInterrupt, GlobalInterruptHandler<T>> + 'd, | ||
| 136 | config: Config, | ||
| 137 | spdifrx_in: impl Peripheral<P = impl InPin<T>> + 'd, | ||
| 138 | data_dma: impl Peripheral<P = impl Channel + Dma<T>> + 'd, | ||
| 139 | data_dma_buf: &'d mut [u32], | ||
| 140 | ) -> Self { | ||
| 141 | let (spdifrx_in, input_sel) = new_spdifrx_pin!(spdifrx_in, AfType::input(Pull::None)); | ||
| 142 | Self::setup(config, input_sel); | ||
| 143 | |||
| 144 | into_ref!(peri, data_dma); | ||
| 145 | |||
| 146 | let regs = T::info().regs; | ||
| 147 | let dr_request = data_dma.request(); | ||
| 148 | let dr_ring_buffer = | ||
| 149 | unsafe { ReadableRingBuffer::new(data_dma, dr_request, dr_address(regs), data_dma_buf, Self::dma_opts()) }; | ||
| 150 | |||
| 151 | Self { | ||
| 152 | _peri: peri, | ||
| 153 | spdifrx_in, | ||
| 154 | data_ring_buffer: dr_ring_buffer, | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | fn setup(config: Config, input_sel: u8) { | ||
| 159 | T::info().rcc.enable_and_reset(); | ||
| 160 | T::GlobalInterrupt::unpend(); | ||
| 161 | unsafe { T::GlobalInterrupt::enable() }; | ||
| 162 | |||
| 163 | let regs = T::info().regs; | ||
| 164 | |||
| 165 | regs.imr().write(|imr| { | ||
| 166 | imr.set_ifeie(true); // Enables interrupts for TERR, SERR, FERR. | ||
| 167 | imr.set_syncdie(true); // Enables SYNCD interrupt. | ||
| 168 | }); | ||
| 169 | |||
| 170 | regs.cr().write(|cr| { | ||
| 171 | cr.set_spdifen(0x00); // Disable SPDIF receiver synchronization. | ||
| 172 | cr.set_rxdmaen(true); // Use RX DMA for data. Enabled on `read`. | ||
| 173 | cr.set_cbdmaen(false); // Do not capture channel info. | ||
| 174 | cr.set_rxsteo(true); // Operate in stereo mode. | ||
| 175 | cr.set_drfmt(0x01); // Data is left-aligned (MSB). | ||
| 176 | |||
| 177 | // Disable all status fields in the data register. | ||
| 178 | // Status can be obtained directly with the status register DMA. | ||
| 179 | cr.set_pmsk(false); // Write parity bit to the data register. FIXME: Add parity check. | ||
| 180 | cr.set_vmsk(false); // Write validity to the data register. | ||
| 181 | cr.set_cumsk(true); // Do not write C and U bits to the data register. | ||
| 182 | cr.set_ptmsk(false); // Write preamble bits to the data register. | ||
| 183 | |||
| 184 | cr.set_chsel(match config.control_channel_selection { | ||
| 185 | ControlChannelSelection::A => false, | ||
| 186 | ControlChannelSelection::B => true, | ||
| 187 | }); // Select channel status source. | ||
| 188 | |||
| 189 | cr.set_nbtr(0x02); // 16 attempts are allowed. | ||
| 190 | cr.set_wfa(true); // Wait for activity before going to synchronization phase. | ||
| 191 | cr.set_insel(input_sel); // Input pin selection. | ||
| 192 | |||
| 193 | #[cfg(stm32h7)] | ||
| 194 | cr.set_cksen(true); // Generate a symbol clock. | ||
| 195 | |||
| 196 | #[cfg(stm32h7)] | ||
| 197 | cr.set_cksbkpen(false); // Do not generate a backup symbol clock. | ||
| 198 | }); | ||
| 199 | } | ||
| 200 | |||
| 201 | /// Start the SPDIFRX driver. | ||
| 202 | pub fn start(&mut self) { | ||
| 203 | self.data_ring_buffer.start(); | ||
| 204 | |||
| 205 | T::info().regs.cr().modify(|cr| { | ||
| 206 | cr.set_spdifen(0x03); // Enable S/PDIF receiver. | ||
| 207 | }); | ||
| 208 | } | ||
| 209 | |||
| 210 | /// Read from the SPDIFRX data ring buffer. | ||
| 211 | /// | ||
| 212 | /// SPDIFRX is always receiving data in the background. This function pops already-received | ||
| 213 | /// data from the buffer. | ||
| 214 | /// | ||
| 215 | /// If there's less than `data.len()` data in the buffer, this waits until there is. | ||
| 216 | pub async fn read(&mut self, data: &mut [u32]) -> Result<(), Error> { | ||
| 217 | self.data_ring_buffer.read_exact(data).await?; | ||
| 218 | |||
| 219 | let first_preamble = (data[0] >> 4) & 0b11_u32; | ||
| 220 | if (first_preamble as u8) == (PreambleType::W as u8) { | ||
| 221 | trace!("S/PDIF left/right mismatch"); | ||
| 222 | |||
| 223 | // Resynchronize until the first sample is for the left channel. | ||
| 224 | self.data_ring_buffer.clear(); | ||
| 225 | return Err(Error::ChannelSyncError); | ||
| 226 | }; | ||
| 227 | |||
| 228 | for sample in data.as_mut() { | ||
| 229 | if (*sample & (0x0002_u32)) == 0x0001 { | ||
| 230 | // Discard invalid samples, setting them to mute level. | ||
| 231 | *sample = 0; | ||
| 232 | } else { | ||
| 233 | // Discard status information in the lowest byte. | ||
| 234 | *sample &= 0xFFFFFF00; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | Ok(()) | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | #[cfg(not(gpdma))] | ||
| 243 | impl<'d, T: Instance> Drop for Spdifrx<'d, T> { | ||
| 244 | fn drop(&mut self) { | ||
| 245 | T::info().regs.cr().modify(|cr| cr.set_spdifen(0x00)); | ||
| 246 | self.spdifrx_in.as_ref().map(|x| x.set_as_disconnected()); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | struct State { | ||
| 251 | #[allow(unused)] | ||
| 252 | waker: AtomicWaker, | ||
| 253 | } | ||
| 254 | |||
| 255 | impl State { | ||
| 256 | const fn new() -> Self { | ||
| 257 | Self { | ||
| 258 | waker: AtomicWaker::new(), | ||
| 259 | } | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | struct Info { | ||
| 264 | regs: crate::pac::spdifrx::Spdifrx, | ||
| 265 | rcc: RccInfo, | ||
| 266 | } | ||
| 267 | |||
| 268 | peri_trait!( | ||
| 269 | irqs: [GlobalInterrupt], | ||
| 270 | ); | ||
| 271 | |||
| 272 | /// SPIDFRX pin trait | ||
| 273 | pub trait InPin<T: Instance>: crate::gpio::Pin { | ||
| 274 | /// Get the GPIO AF number needed to use this pin. | ||
| 275 | fn af_num(&self) -> u8; | ||
| 276 | /// Get the SPIDFRX INSEL number needed to use this pin. | ||
| 277 | fn input_sel(&self) -> u8; | ||
| 278 | } | ||
| 279 | |||
| 280 | dma_trait!(Dma, Instance); | ||
| 281 | |||
| 282 | /// Global interrupt handler. | ||
| 283 | pub struct GlobalInterruptHandler<T: Instance> { | ||
| 284 | _phantom: PhantomData<T>, | ||
| 285 | } | ||
| 286 | |||
| 287 | impl<T: Instance> interrupt::typelevel::Handler<T::GlobalInterrupt> for GlobalInterruptHandler<T> { | ||
| 288 | unsafe fn on_interrupt() { | ||
| 289 | T::state().waker.wake(); | ||
| 290 | |||
| 291 | let regs = T::info().regs; | ||
| 292 | let sr = regs.sr().read(); | ||
| 293 | |||
| 294 | if sr.serr() || sr.terr() || sr.ferr() { | ||
| 295 | trace!("SPDIFRX error, resync"); | ||
| 296 | |||
| 297 | // Clear errors by disabling SPDIFRX, then reenable. | ||
| 298 | regs.cr().modify(|cr| cr.set_spdifen(0x00)); | ||
| 299 | regs.cr().modify(|cr| cr.set_spdifen(0x03)); | ||
| 300 | } else if sr.syncd() { | ||
| 301 | // Synchronization was successful. | ||
| 302 | trace!("SPDIFRX sync success"); | ||
| 303 | } | ||
| 304 | |||
| 305 | // Clear interrupt flags. | ||
| 306 | regs.ifcr().write(|ifcr| { | ||
| 307 | ifcr.set_perrcf(true); // Clears parity error flag. | ||
| 308 | ifcr.set_ovrcf(true); // Clears overrun error flag. | ||
| 309 | ifcr.set_sbdcf(true); // Clears synchronization block detected flag. | ||
| 310 | ifcr.set_syncdcf(true); // Clears SYNCD from SR (was read above). | ||
| 311 | }); | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | foreach_peripheral!( | ||
| 316 | (spdifrx, $inst:ident) => { | ||
| 317 | #[allow(private_interfaces)] | ||
| 318 | impl SealedInstance for peripherals::$inst { | ||
| 319 | fn info() -> &'static Info { | ||
| 320 | static INFO: Info = Info{ | ||
| 321 | regs: crate::pac::$inst, | ||
| 322 | rcc: crate::peripherals::$inst::RCC_INFO, | ||
| 323 | }; | ||
| 324 | &INFO | ||
| 325 | } | ||
| 326 | fn state() -> &'static State { | ||
| 327 | static STATE: State = State::new(); | ||
| 328 | &STATE | ||
| 329 | } | ||
| 330 | } | ||
| 331 | |||
| 332 | impl Instance for peripherals::$inst { | ||
| 333 | type GlobalInterrupt = crate::_generated::peripheral_interrupts::$inst::GLOBAL; | ||
| 334 | } | ||
| 335 | }; | ||
| 336 | ); | ||
