Another update with my latest findings. I'm finally grasping the HDMI code in the Linux kernel, but unfortunately am yet to address the lack of throttling in the consumption of SPDIF frames, or to get any audio. It is possible that I am not activating something that I should, but I'm not yet sure what it could be. Right now I'm setting up the registers in a way that makes sense to me and is close to what Linux is doing, but the results still differ.
Not all registers are in the "hd" block defined in the device tree source, so I was attempting to read some registers in the wrong location. The register block to which every register belongs is defined in drivers/gpu/drm/vc4/vc4_hdmi_regs.h in the Linux kernel source. The hint is in the name of the macro used in the initialization of vc5_hdmi_hdmi0_fields. Attempting to read from the specified offsets in the related register blocks on Linux yields expected results.
Right now I'm accessing the following registers:
The bit fields for each of those registers can be found in drivers/gpu/drm/vc4/vc4_regs.h in the Linux kernel source. I'm not copying them here since it's a lot of information, but generally searching for a register's name should reveal the macros that define its bitfields.
Info frames are generated in drivers/video/hdmi.c in the Linux kernel source, and the values come from include/linux/hdmi.h also in the Linux kernel source. Although the generic HDMI code in the kernel computes the info frame's checksum, the HDMI code for the VC4/5/6 driver actually resets it to zero. Info frames are 9 32-bit registers long each, and all the unused space must be set to zero. Info frame 0 doesn't seem to be used, but is presumable populated with values either by the firmware or the hardware itself. I tried populating it with the values that I got from Linux but it made no difference.
Some questions I have:
1. Why is the DMA channel for HDMI1 audio set to 17 in arch/arm/boot/dts/bcm2711.dtsi in the Linux kernel source? I thought that DMA channels were all in the 0-15 range.
2. Does anyone know where to find the DMA DREQ device ID for HDMI0 and HDMI1 in the Linux kernel source?
I believe that I'm getting closer to a working HDMI audio driver, but something is still escaping me. Below is my current failed attempt at a draft polled driver for the HDMI audio hardware using all the information that I've gathered so far:
Not all registers are in the "hd" block defined in the device tree source, so I was attempting to read some registers in the wrong location. The register block to which every register belongs is defined in drivers/gpu/drm/vc4/vc4_hdmi_regs.h in the Linux kernel source. The hint is in the name of the macro used in the initialization of vc5_hdmi_hdmi0_fields. Attempting to read from the specified offsets in the related register blocks on Linux yields expected results.
Right now I'm accessing the following registers:
Code:
VC4_HD_REG(HDMI_MAI_CTL, 0x0010), // Audio control.VC4_HD_REG(HDMI_MAI_THR, 0x0014), // Audio DMA DREQ thresholds.VC4_HD_REG(HDMI_MAI_FMT, 0x0018), // Audio format.VC4_HD_REG(HDMI_MAI_DATA, 0x001c), // Audio SPDIF FIFO.VC4_HD_REG(HDMI_MAI_SMP, 0x0020), // CPRMAN ticks to audio sample rate fraction.VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x0b8), // Audio frame configuration.VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x0bc), // Info frame configuration.VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x0c4), // Info frame status.VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x09c), // Audio channel map.VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a0), // Audio configuration.VC5_RAM_REG(HDMI_RAM_PACKET_START, 0x000), // 128x info frame storage registers.
Info frames are generated in drivers/video/hdmi.c in the Linux kernel source, and the values come from include/linux/hdmi.h also in the Linux kernel source. Although the generic HDMI code in the kernel computes the info frame's checksum, the HDMI code for the VC4/5/6 driver actually resets it to zero. Info frames are 9 32-bit registers long each, and all the unused space must be set to zero. Info frame 0 doesn't seem to be used, but is presumable populated with values either by the firmware or the hardware itself. I tried populating it with the values that I got from Linux but it made no difference.
Some questions I have:
1. Why is the DMA channel for HDMI1 audio set to 17 in arch/arm/boot/dts/bcm2711.dtsi in the Linux kernel source? I thought that DMA channels were all in the 0-15 range.
2. Does anyone know where to find the DMA DREQ device ID for HDMI0 and HDMI1 in the Linux kernel source?
I believe that I'm getting closer to a working HDMI audio driver, but something is still escaping me. Below is my current failed attempt at a draft polled driver for the HDMI audio hardware using all the information that I've gathered so far:
Code:
use core::hint::spin_loop;use crate::PERRY_RANGE;/// Core register block base.const BASE: usize = PERRY_RANGE.start + 0x2f00700;/// Audio channel map.const AU_CHMAP: *mut u32 = (BASE + 0x9c) as _;/// Audio configuration register.const AU_CFG: *mut u32 = (BASE + 0xa0) as _;/// Packet configuration register.const AU_PKTCFG: *mut u32 = (BASE + 0xb8) as _;/// Info frame configuration register.const IF_CFG: *mut u32 = (BASE + 0xbc) as _;/// Info frame status register.const IF_STATUS: *mut u32 = (BASE + 0xc4) as _;/// Info frame packet register block base.const IF_BASE: usize = PERRY_RANGE.start + 0x2f01b00;/// First register of the info frame packet block.const IF_START: *mut u32 = IF_BASE as _;/// HD register block base.const HD_BASE: usize = PERRY_RANGE.start + 0x2f20000;/// HD audio control register.const HD_AU_CTL: *mut u32 = (HD_BASE + 0x10) as _;/// HD audio DMA DREQ thresholds configuration register.const HD_AU_THR: *mut u32 = (HD_BASE + 0x14) as _;/// HD audio format register.const HD_AU_FMT: *mut u32 = (HD_BASE + 0x18) as _;/// HD audio data register.const HD_AU_DATA: *mut u32 = (HD_BASE + 0x1c) as _;/// HD audio clock division register.const HD_AU_SMP: *mut u32 = (HD_BASE + 0x20) as _;pub fn init() { unsafe { HD_AU_CTL.write_volatile(0xb32f); let ifcfg = IF_CFG.read_volatile(); IF_CFG.write_volatile(ifcfg & !0x10); while IF_STATUS.read_volatile() & 0x10 != 0 { spin_loop(); } let offset = 4 * 9; IF_START.add(offset).write_volatile(0xa0184); IF_START.add(offset + 1).write_volatile(0x3000d11); for idx in 2 .. 9 { IF_START.add(offset + idx).write_volatile(0); } IF_CFG.write_volatile(ifcfg | 0x10010); AU_CFG.write_volatile(0xc000003); AU_PKTCFG.write_volatile(0x21083c03); AU_CHMAP.write_volatile(0x10); HD_AU_FMT.write_volatile(0x20900); HD_AU_THR.write_volatile(0x10101c1c); HD_AU_SMP.write_volatile((54000000 / 48000 * 2) << 8); let mut cs = [0; 24]; cs[0 .. 5].copy_from_slice(&[0x4, 0x50, 0x0, 0x22, 0xd2]); for frame in 0 .. { if frame % 4800000 == 0 { crate::debug!("Frame {frame}"); } let halfperiod = if frame & 0x1 == 0 {120} else {80}; let mut val = if (frame / halfperiod) & 0x1 == 1 {0x3fff << 12} else {0xc000 << 12}; let frame = frame % 384; if frame == 0 { // Send the synchronization marker configured earlier. val |= 0xf; } let byte = frame >> 4; let bit = (frame >> 1) & 0x7; let cs = (cs[byte] >> bit) & 0x1; val |= cs << 30; if frame == 33 || frame == 41 { // Set the channel bits for the second channel. val |= 1 << 30; } // Add the parity bit. for bit in 4 .. 31 { val ^= ((val >> bit) & 0x1) << 31; } // Write the subframe once the FIFO is not full. while HD_AU_CTL.read_volatile() & 0x800 != 0 { spin_loop(); } HD_AU_DATA.write_volatile(val); } }}
Statistics: Posted by Fridux — Sun Apr 28, 2024 1:51 pm