Merge branch 'evk'
This commit is contained in:
@@ -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<T> {
|
||||
i2c: T,
|
||||
pins: CodecPins,
|
||||
mclk: u32,
|
||||
}
|
||||
|
||||
impl<T> Wm8904Dac<T>
|
||||
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<T> Dac<T> for Wm8904Dac<T>
|
||||
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);
|
||||
}
|
||||
}
|
||||
+73
-1
@@ -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);
|
||||
}
|
||||
|
||||
+46
-5
@@ -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;
|
||||
|
||||
@@ -611,11 +620,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()
|
||||
@@ -623,6 +627,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());
|
||||
|
||||
@@ -646,10 +677,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),
|
||||
@@ -691,6 +729,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
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
import struct
|
||||
from time import sleep
|
||||
from typing import Self
|
||||
import hid
|
||||
|
||||
VID = 0x1209
|
||||
PID = 0xCC1D
|
||||
INTERVAL = 0.1
|
||||
|
||||
|
||||
@dataclass
|
||||
class AudioTelemetry:
|
||||
STRUCT = "<LLLLL"
|
||||
LEN = struct.calcsize(STRUCT)
|
||||
|
||||
average_buffer_fill: int
|
||||
frame_count: int
|
||||
dac_underflow_count: int
|
||||
usb_underflow_count: int
|
||||
dac_overflow_count: int
|
||||
|
||||
def from_bytes(b: bytes) -> Self:
|
||||
if len(b) != AudioTelemetry.LEN:
|
||||
raise ValueError(f"wrong size report ({len(b)} != {AudioTelemetry.LEN})")
|
||||
fields = struct.unpack(AudioTelemetry.STRUCT, b)
|
||||
return AudioTelemetry(*fields)
|
||||
|
||||
|
||||
def main():
|
||||
with hid.Device(VID, PID) as h:
|
||||
while True:
|
||||
report = AudioTelemetry.from_bytes(h.read(AudioTelemetry.LEN))
|
||||
print(f"{report}")
|
||||
sleep(INTERVAL)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,10 +0,0 @@
|
||||
[project]
|
||||
name = "guac-scripts"
|
||||
version = "0.1.0"
|
||||
description = "Scripts to work with GUAC devices"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"hid>=1.0.9",
|
||||
"rich-click>=1.9.7",
|
||||
]
|
||||
Reference in New Issue
Block a user