diff --git a/Cargo.toml b/Cargo.toml index e79b6e6..cad9cb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,12 @@ version = "0.1.0" edition = "2024" [features] -default = ["nodac", "hid"] +default = ["evk", "hid"] ak4490 = [] cs4398 = [] +wm8904 = [] nodac = [] +evk = ["wm8904"] hid = [ "dep:usbd-hid"] [dependencies] diff --git a/src/dac/wm8904.rs b/src/dac/wm8904.rs new file mode 100644 index 0000000..1c11a6b --- /dev/null +++ b/src/dac/wm8904.rs @@ -0,0 +1,253 @@ +use crate::{CodecPins, traits::Dac}; +use embedded_hal::prelude::{ + _embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead, +}; +const WM8904_I2C_ADDRESS: u8 = 0b0011010; +const MCLK: u32 = 24576000 / 2; // assume EVK TODO: better + +#[repr(u8)] +#[derive(Clone, Copy)] +#[allow(dead_code)] +enum RegisterAddress { + SwResetId = 0x00, + BiasControl0 = 0x04, + VmidControl = 0x05, + MicBiasControl0 = 0x06, + MicBiasControl1 = 0x07, + AnaAdc0 = 0x0a, + PowerMgmt0 = 0x0c, + PowerMgmt2 = 0x0e, + PowerMgmt3 = 0x0f, + PowerMgmt6 = 0x12, + ClockRates0 = 0x14, + ClockRates1 = 0x15, + ClockRates2 = 0x16, + AudioInterface0 = 0x18, + AudioInterface1 = 0x19, + AudioInterface2 = 0x1a, + AudioInterface3 = 0x1b, + DacDigiVolLeft = 0x1e, + DacDigiVolRight = 0x1f, + DacDigital0 = 0x20, + DacDigi1 = 0x21, + AdcDigiVolLeft = 0x24, + AdcDigiVolRight = 0x25, + AdcDigital0 = 0x26, + DigiMic0 = 0x27, + Drc0 = 0x28, + Drc1 = 0x29, + Drc2 = 0x2a, + Drc3 = 0x2b, + AnaLeftIn0 = 0x2c, + AnaRightIn0 = 0x2d, + AnaLeftIn1 = 0x2e, + AnaRightIn1 = 0x2f, + AnaOut1Left = 0x39, + AnaOut1Right = 0x3a, + AnaOut2Left = 0x3b, + AnaOut2Right = 0x3c, + AnaOut12Zc = 0x3d, + DcServo0 = 0x43, + DcServo1 = 0x44, + DcServo2 = 0x45, + DcServo4 = 0x47, + DcServo5 = 0x48, + DcServo6 = 0x49, + DcServo7 = 0x4a, + DcServo8 = 0x4b, + DcServo9 = 0x4c, + DcServoRb0 = 0x4d, + AnaHp0 = 0x5a, + AnaLineOut0 = 0x5e, + ChargePump0 = 0x62, + ClassW = 0x68, + WriteSeq0 = 0x6c, + WriteSeq1 = 0x6d, + WriteSeq2 = 0x6e, + WriteSeq3 = 0x6f, + WriteSeq4 = 0x70, + FllControl1 = 0x74, + FllControl2 = 0x75, + FllControl3 = 0x76, + FllControl4 = 0x77, + FllControl5 = 0x78, + GpioControl1 = 0x79, + GpioControl2 = 0x7a, + GpioControl3 = 0x7b, + GpioControl4 = 0x7c, + DigiPulls = 0x7e, + IntStatus = 0x7f, + IntStatusMask = 0x80, + IntPriority = 0x81, + IntDebounce = 0x82, + Eq1 = 0x86, + Eq2 = 0x87, + Eq3 = 0x88, + Eq4 = 0x89, + Eq5 = 0x8a, + Eq6 = 0x8b, + Eq7 = 0x8c, + Eq8 = 0x8d, + Eq9 = 0x8e, + Eq10 = 0x8f, + Eq11 = 0x90, + Eq12 = 0x91, + Eq13 = 0x92, + Eq14 = 0x93, + Eq15 = 0x94, + Eq16 = 0x95, + Eq17 = 0x96, + Eq18 = 0x97, + Eq19 = 0x98, + Eq20 = 0x99, + Eq21 = 0x9a, + Eq22 = 0x9b, + Eq23 = 0x9c, + Eq24 = 0x9d, + AdcTest0 = 0xc6, + FllNcoTest0 = 0xf7, + FllNcoTest1 = 0xf8, +} + +pub struct Wm8904Dac { + i2c: T, + pins: CodecPins, + mclk: u32, +} + +impl Wm8904Dac +where + T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Write, +{ + #[inline] + fn write_reg(&mut self, reg: RegisterAddress, val: u16) { + let b = val.to_be_bytes(); + defmt::info!("i2c w [{:?}]", &[reg as u8, b[0], b[1]]); + self.i2c + .write(WM8904_I2C_ADDRESS, &[reg as u8, b[0], b[1]]) + .ok(); + } + fn cr1_for_rate(&self, rate: u32) -> u16 { + let fs_ratio = self.mclk / rate; + if !self.mclk.is_multiple_of(rate) { + defmt::warn!("sample rate should be a multiple of mclk"); + } + let clk_sys_rate: u16 = match fs_ratio { + 64 => 0, + 128 => 1, + 192 => 2, + 256 => 3, + 384 => 4, + 512 => 5, + 768 => 6, + 1024 => 7, + 1408 => 8, + 1536 => 9, + _ => { + defmt::warn!("unsupport ratio {}", fs_ratio); + 0 + } + }; + let sample_rate: u16 = match rate { + r if r < 11025 => 0, // 0-11024 + r if r < 16000 => 1, // 11025 - 15999 + r if r < 22050 => 2, // 16000 - 22049 + r if r < 32000 => 3, // 22050 - 31999 + r if r < 44100 => 4, // 32000 - 44099 + _ => 5, // 44100+ + }; + (clk_sys_rate << 10) | sample_rate + } + fn blck_div_for_rate(&self, rate: u32) -> u16 { + let bits_per_frame = 64; + let bits_per_second = bits_per_frame * rate; + (self.mclk / bits_per_second) as u16 + } +} + +impl Dac for Wm8904Dac +where + T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Write, +{ + fn new(i2c: T, pins: CodecPins) -> Self { + Self { + i2c, + pins, + mclk: MCLK, + } + } + fn init(&mut self) { + let mut buf = [0u8; 2]; + match self.i2c.write_read(WM8904_I2C_ADDRESS, &[0], &mut buf) { + Ok(_) => { + let chip_id = ((buf[0] as u16) << 8) | buf[1] as u16; + defmt::info!("Read chip ID: {:x}", chip_id) + } + Err(_) => defmt::error!("Error reading I2C"), + } + + self.write_reg(RegisterAddress::ClockRates2, 0x000f); // OPCLK_ENA | CLK_SYS_ENA | CLK_DSP_ENA | TOCLK_ENA + self.write_reg(RegisterAddress::WriteSeq0, 0x0100); // write sequencer 0 ENA + self.write_reg(RegisterAddress::WriteSeq3, 0x0100); // write sequencer 3 START, INDEX=0 + // wait on write sequencer + defmt::info!("[codec] waiting on write seq"); + loop { + let mut buf = [0; 2]; + self.i2c + .write_read(WM8904_I2C_ADDRESS, &[0x70], &mut buf) + .ok(); + if buf[1] & 1 == 0 { + break; + } + } + defmt::debug!("[codec] write seq done"); + self.write_reg(RegisterAddress::ClockRates0, 0); + self.write_reg(RegisterAddress::PowerMgmt0, 0); // IN PGAs disabled + self.write_reg(RegisterAddress::PowerMgmt2, 0x0003); // HPL_PGA_ENA | HPR_PGA_ENA + self.write_reg(RegisterAddress::PowerMgmt3, 0); //line outs disabled + + self.write_reg(RegisterAddress::PowerMgmt6, 0x000c); // power management 6 = DACL_ENA | DACR_ENA + self.write_reg(RegisterAddress::AudioInterface0, 0x0050); // audio if 0 = AIFADCR_SRC | AIFDACR_SRC + self.write_reg(RegisterAddress::DacDigi1, 0x0040); // dac digital 1 = DAC_OSR128 + self.write_reg(RegisterAddress::AnaLeftIn0, 0x0005); + self.write_reg(RegisterAddress::AnaRightIn0, 0x0005); + self.write_reg(RegisterAddress::AnaOut1Left, 0x0039); // analog out1 left = vol=0dB + self.write_reg(RegisterAddress::AnaOut1Right, 0x0039); // analog out1 right = vol=0dB + self.write_reg(RegisterAddress::AnaOut2Left, 0x0039); // analog out2 left = vol=0dB + self.write_reg(RegisterAddress::AnaOut2Right, 0x0039); // analog out2 right = vol=0dB + self.write_reg(RegisterAddress::DcServo0, 0x0003); // dc servo 0 = HPOUTL_ENA | HPOUTR_ENA + self.write_reg(RegisterAddress::AnaHp0, 0x00ff); // analog hp 0 = remove all shorts etc + self.write_reg(RegisterAddress::AnaLineOut0, 0x00ff); // analog lineout 0 = remove all shorts etc + self.write_reg(RegisterAddress::ClassW, 0x0001); // enable class w charge pump + self.write_reg(RegisterAddress::ChargePump0, 0x0001); // enable charge pump + self.write_reg(RegisterAddress::AudioInterface1, (3 << 2) | 2); // audio if 1 = i2s, 32 bit per sample + + self.write_reg(RegisterAddress::ClockRates1, self.cr1_for_rate(96000)); // Set up for 48k, impl will change if needed + self.write_reg(RegisterAddress::ClockRates2, 0x000f); // clock rates 2 = CLK_SYS_ENA + + self.write_reg( + RegisterAddress::AudioInterface2, + self.blck_div_for_rate(96000), + ); + + self.write_reg(RegisterAddress::AudioInterface3, 0); // audio interface 3 = input lrclock + self.write_reg(RegisterAddress::AnaOut12Zc, 0); // analog out12 zc = play source = dac + self.write_reg(RegisterAddress::DacDigiVolLeft, 0x01ff); // dac vol left = update left/right = 0dB + } + fn change_rate(&mut self, new_rate: u32) { + // TODO: mute, stop clocks etc. + defmt::info!("dac rate -> {}", new_rate); + self.write_reg(RegisterAddress::ClockRates1, self.cr1_for_rate(new_rate)); + self.write_reg( + RegisterAddress::AudioInterface2, + self.blck_div_for_rate(new_rate), + ); + } + fn mute(&mut self) { + // self.write_reg(RegisterAddress::DacDigiVolLeft, 0x0100); + } + fn unmute(&mut self) { + // TODO: restore previous volume + // self.write_reg(RegisterAddress::DacDigiVolLeft, 0x01ff); + } +} diff --git a/src/hw.rs b/src/hw.rs index e103576..68fcfe8 100644 --- a/src/hw.rs +++ b/src/hw.rs @@ -1,5 +1,5 @@ use crate::pac; -use defmt::debug; +use defmt::{debug, info}; pub(crate) struct PllConstants { pub m: u16, // 1-65535 @@ -112,3 +112,75 @@ pub(crate) fn init_sys_pll1() { syscon.fmccr.modify(|_, w| w.flashtim().flashtim11()); syscon.mainclkselb.modify(|_, w| w.sel().enum_0x2()); // pll1 } + +// Fo = M/(N*2*P) * Fin +// Fo = 3072/(125*2*8) * 16MHz = 24.576MHz +const AUDIO_PLL: PllConstants = PllConstants::new(125, 3072, 8); + +// Set PLL0 to 24.576MHz, start, and wait for lock +// This is not exposed by lpc55-hal, unfortunately. Copy their implementation here. +pub(crate) fn init_audio_pll() { + let syscon = unsafe { &*pac::SYSCON::ptr() }; + let pmc = unsafe { &*pac::PMC::ptr() }; + let anactrl = unsafe { &*pac::ANACTRL::ptr() }; + + debug!("start clk_in"); + pmc.pdruncfg0 + .modify(|_, w| w.pden_xtal32m().poweredon().pden_ldoxo32m().poweredon()); + syscon.clock_ctrl.modify(|_, w| w.clkin_ena().enable()); + anactrl + .xo32m_ctrl + .modify(|_, w| w.enable_system_clk_out().enable()); + + debug!("init pll0: {}", AUDIO_PLL); + pmc.pdruncfg0 + .modify(|_, w| w.pden_pll0().poweredoff().pden_pll0_sscg().poweredoff()); + syscon.pll0clksel.write(|w| w.sel().enum_0x1()); // clk_in + syscon.pll0ctrl.write(|w| unsafe { + w.clken() + .enable() + .seli() + .bits(AUDIO_PLL.seli) + .selp() + .bits(AUDIO_PLL.selp) + }); + + syscon + .pll0ndec + .write(|w| unsafe { w.ndiv().bits(AUDIO_PLL.n) }); + syscon.pll0ndec.write(|w| unsafe { + w.ndiv().bits(AUDIO_PLL.n).nreq().set_bit() // latch + }); + + syscon + .pll0pdec + .write(|w| unsafe { w.pdiv().bits(AUDIO_PLL.p) }); + syscon.pll0pdec.write(|w| unsafe { + w.pdiv().bits(AUDIO_PLL.p).preq().set_bit() // latch + }); + + syscon.pll0sscg0.write(|w| unsafe { w.md_lbs().bits(0) }); + + syscon + .pll0sscg1 + .write(|w| unsafe { w.mdiv_ext().bits(AUDIO_PLL.m).sel_ext().set_bit() }); + syscon.pll0sscg1.write(|w| unsafe { + w.mdiv_ext() + .bits(AUDIO_PLL.m) + .sel_ext() + .set_bit() + .mreq() + .set_bit() // latch + .md_req() + .set_bit() // latch + }); + + pmc.pdruncfg0 + .modify(|_, w| w.pden_pll0().poweredon().pden_pll0_sscg().poweredon()); + info!("pll0 wait for lock"); + let mut i = 0usize; + while syscon.pll0stat.read().lock().bit_is_clear() { + i += 1; + } + info!("pll0 locked after {} loops", i); +} diff --git a/src/main.rs b/src/main.rs index b86ed0d..d63780e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,6 +57,11 @@ pub mod dac { mod noop; pub use self::noop::NoopDac as DacImpl; } +#[cfg(feature = "wm8904")] +pub mod dac { + mod wm8904; + pub use self::wm8904::Wm8904Dac as DacImpl; +} mod dma; #[cfg(feature = "hid")] @@ -76,7 +81,11 @@ const USB_FRAME_RATE: u32 = 8000; // microframe rate: 8000 for HS, 1000 for FS const QUEUE_RUNNING_UP: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 4) / 10; // 40% const QUEUE_RUNNING_DOWN: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 2) / 10; // 20% const NODATA_TIMEOUT_FRAMES: usize = SAMPLE_RATE as usize / 100; // ~100ms +#[cfg(not(feature = "evk"))] const MCLK_FREQ: u32 = 24576000; +#[cfg(feature = "evk")] +const MCLK_FREQ: u32 = 24576000 / 2; + const SAMPLE_RATE: u32 = 192000; const HID_INTERVAL_MS: u8 = 100; @@ -594,11 +603,6 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) - .od() .normal() }); - pac::SYSCON::ptr() - .as_ref() - .unwrap() - .mclkio - .modify(|_, w| w.mclkio().input()); pac::SYSCON::ptr() .as_ref() .unwrap() @@ -606,6 +610,33 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) - .modify(|_, w| w.sel().enum_0x5()); // MCLK }; + #[cfg(not(feature = "evk"))] + unsafe { + pac::SYSCON::ptr() + .as_ref() + .unwrap() + .mclkio + .modify(|_, w| w.mclkio().input()); + } + #[cfg(feature = "evk")] + unsafe { + pac::SYSCON::ptr() + .as_ref() + .unwrap() + .mclkclksel + .modify(|_, w| w.sel().enum_0x1()); // PLL0 + pac::SYSCON::ptr() + .as_ref() + .unwrap() + .mclkdiv + .modify(|_, w| w.div().bits(1).halt().run().reset().released()); // div by 2 = PLL0 fout / 2 = 12.288MHz, max for WM8904 @ 96k + pac::SYSCON::ptr() + .as_ref() + .unwrap() + .mclkio + .modify(|_, w| w.mclkio().output()); + } + // Select I2S TX function fc7.pselid.write(|w| w.persel().i2s_transmit()); @@ -629,10 +660,17 @@ fn main() -> ! { let usb0_vbus_pin = pins::Pio0_22::take() .unwrap() .into_usb0_vbus_pin(&mut iocon); + + #[cfg(not(feature = "evk"))] let codec_i2c_pins = ( pins::Pio0_16::take().unwrap().into_i2c4_scl_pin(&mut iocon), pins::Pio0_5::take().unwrap().into_i2c4_sda_pin(&mut iocon), ); + #[cfg(feature = "evk")] + let codec_i2c_pins = ( + pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon), + pins::Pio1_21::take().unwrap().into_i2c4_sda_pin(&mut iocon), + ); let codec_i2s_pins = ( pins::Pio0_21::take().unwrap().into_spi7_sck_pin(&mut iocon), pins::Pio0_20::take().unwrap().into_i2s7_sda_pin(&mut iocon), @@ -674,6 +712,9 @@ fn main() -> ! { .configure(&mut anactrl, &mut pmc, &mut syscon) .unwrap(); hw::init_sys_pll1(); + #[cfg(feature = "evk")] + hw::init_audio_pll(); + let mut delay_timer = Timer::new( hal.ctimer .0