blob: 32ecff77b03ff926156d9d08e70569f7becbb858 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <digest/digest.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/limits.h>
#include <soc/aml-s912/s912-audio.h>
#include "hdmitx.h"
#include "vim-display.h"
#include "vim-spdif-audio-stream.h"
#define SHIFTED_MASK(_name) ((_name##_MASK) << (_name##_SHIFT))
#define SHIFTED_VAL(_name, _val) ((_val & _name##_MASK) << _name##_SHIFT)
#define MOD_FIELD(_name, _val) SHFTED_MASK(_name), SHIFTED_VAL(_name, _val)
namespace audio {
namespace vim2 {
namespace {
// 128 bytes per frame. Why? I have no idea. This is clearly not an audio
// frame, nor is it a SPDIF block. I suspect that it may be the amount of
// data which the DMA engine tries to fetch each time it jumps on the bus,
// but I don't really know for certain.
constexpr uint32_t AIU_958_BYTES_PER_FRAME = 128;
static const struct {
uint32_t rate;
uint32_t N;
} STANDARD_FRAME_RATE_N_LUT[] = {
{ .rate = 32000, .N = 4096 },
{ .rate = 48000, .N = 6144 },
{ .rate = 96000, .N = 12288 },
{ .rate = 192000, .N = 25467 },
{ .rate = 44100, .N = 6272 },
{ .rate = 88200, .N = 12544 },
{ .rate = 176400, .N = 28028 },
};
} // anon namespace
Vim2SpdifAudioStream::Vim2SpdifAudioStream(const vim2_display* display,
fbl::RefPtr<Registers> regs,
fbl::RefPtr<RefCountedVmo> ring_buffer_vmo,
fzl::PinnedVmo pinned_ring_buffer,
uint64_t display_id)
: SimpleAudioStream(display->parent, false),
display_(display),
display_id_(display_id),
regs_(fbl::move(regs)),
ring_buffer_vmo_(fbl::move(ring_buffer_vmo)),
pinned_ring_buffer_(fbl::move(pinned_ring_buffer)) {
}
void Vim2SpdifAudioStream::ShutdownHook() {
vim2_display_disable_audio(display_);
Disable(*regs_);
}
void Vim2SpdifAudioStream::RingBufferShutdown() {
vim2_display_disable_audio(display_);
}
zx_status_t Vim2SpdifAudioStream::ChangeFormat(const audio_proto::StreamSetFmtReq& req) {
// Figure out the maximum number of audio frames we can fit into our ring
// buffer while still guaranteeing...
//
// 1) The buffer is a multiple of audio frame size
// 2) The buffer is a multiple of AIU frame size
//
ZX_DEBUG_ASSERT(frame_size_ > 0);
usable_buffer_size_ = fbl::round_down(static_cast<uint32_t>(pinned_ring_buffer_.region(0).size),
fbl::lcm(AIU_958_BYTES_PER_FRAME, frame_size_));
// TODO(johngro): figure out the proper value for this
fifo_depth_ = 512;
// TODO(johngro): fill this out based on the estimate given by EDID (if any)
external_delay_nsec_ = 0;
// Figure out the proper values for N and CTS based on this audio mode and
// pixel clock.
// Start by going through our table of standard audio modes for standard
// audio clocks. If we cannot find the answer in the LUT, then fall back on
// computing the answer on the fly using the recommended N as a starting
// point to compute CTS.
//
// See section 7.2 (Audio Sample Clock Capture and Regeneration) of the HDMI
// 1.3a spec (or later) for details.
uint32_t N = 0;
for (const auto& entry : STANDARD_FRAME_RATE_N_LUT) {
if (entry.rate == req.frames_per_second) {
N = entry.N;
break;
}
}
// This should never happen (As we are not advertising any frame rates which
// are not in the LUT), but JiC.
if (!N) {
zxlogf(ERROR, "Failed to find starting N value for audio frame rate (%u).\n",
req.frames_per_second);
return ZX_ERR_NOT_SUPPORTED;
}
// Given our suggested starting value for N, CTS should be computed as...
//
// CTS = pixel_clock * N / (128 * audio_frame_rate)
//
// Since our pixel clock is already expressed in KHz, this becomes
// CTS = pkhz * N * 1000 / (128 * audio_frame_rate)
// = pkhz * N * 125 / (16 * audio_frame_rate)
//
// If our numerator is not divisible by 16 * frame_rate, then we would (in
// theory) need to dither the N/CTS values being sent, which is something we
// currently do not support. For now, if this happens, return an error
// instead.
uint64_t numer = static_cast<uint64_t>(display_->p->timings.pfreq) * N * 125;
uint32_t denom = req.frames_per_second << 4;
if (numer % denom) {
zxlogf(ERROR, "Failed to find CTS value (pclk %u, N %u, frame_rate %u)\n",
display_->p->timings.pfreq, N, req.frames_per_second);
return ZX_ERR_NOT_SUPPORTED;
}
uint32_t CTS = static_cast<uint32_t>(numer / denom);
uint32_t bits_per_sample;
switch (req.sample_format) {
case AUDIO_SAMPLE_FORMAT_16BIT: bits_per_sample = 16; break;
case AUDIO_SAMPLE_FORMAT_24BIT_PACKED: __FALLTHROUGH;
case AUDIO_SAMPLE_FORMAT_24BIT_IN32: bits_per_sample = 24; break;
default:
zxlogf(ERROR, "Unsupported requested sample format (0x%08x)!\n", req.sample_format);
return ZX_ERR_NOT_SUPPORTED;
}
// Set up the registers to match our format choice.
SetMode(req.frames_per_second, req.sample_format);
// Tell the HDMI driver about the mode we just configured.
zx_status_t res;
res = vim2_display_configure_audio_mode(display_,
N, CTS,
req.frames_per_second, bits_per_sample);
if (res != ZX_OK) {
zxlogf(ERROR, "Failed to configure VIM2 HDMI TX audio parameters! (res %d)\n", res);
return res;
}
return ZX_OK;
}
zx_status_t Vim2SpdifAudioStream::GetBuffer(const audio_proto::RingBufGetBufferReq& req,
uint32_t* out_num_rb_frames,
zx::vmo* out_buffer) {
uint32_t rb_frames = usable_buffer_size_ / frame_size_;
if (req.min_ring_buffer_frames > rb_frames) {
return ZX_ERR_OUT_OF_RANGE;
}
constexpr uint32_t rights = ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER;
zx_status_t res = ring_buffer_vmo_->vmo().duplicate(rights, out_buffer);
if (res != ZX_OK) {
return res;
}
*out_num_rb_frames = rb_frames;
SetupBuffer();
return ZX_OK;
}
zx_status_t Vim2SpdifAudioStream::Start(uint64_t* out_start_time) {
uint64_t a, b;
Mute(cur_gain_state_.cur_mute);
a = zx_clock_get(ZX_CLOCK_MONOTONIC);
Enable();
b = zx_clock_get(ZX_CLOCK_MONOTONIC);
*out_start_time = ((b - a) >> 1) + a;
return ZX_OK;
}
zx_status_t Vim2SpdifAudioStream::Stop() {
Disable(*regs_);
Mute(false);
return ZX_OK;
}
zx_status_t Vim2SpdifAudioStream::SetGain(const audio_proto::SetGainReq& req) {
if (req.flags & AUDIO_SGF_MUTE_VALID) {
cur_gain_state_.cur_mute = ((req.flags & AUDIO_SGF_MUTE) != 0);
Mute(cur_gain_state_.cur_mute);
}
return ZX_OK;
}
zx_status_t Vim2SpdifAudioStream::Init() {
zx_status_t res;
if (!regs_) {
zxlogf(ERROR, "null or invalid registers in %s\n", __PRETTY_FUNCTION__);
return ZX_ERR_INVALID_ARGS;
}
Disable(*regs_);
if (!ring_buffer_vmo_ || !ring_buffer_vmo_->vmo().is_valid()) {
zxlogf(ERROR, "Bad ring buffer VMO passed to %s\n", __PRETTY_FUNCTION__);
return ZX_ERR_INVALID_ARGS;
}
// Set up the DMA addresses.
if ((pinned_ring_buffer_.region_count() != 1) ||
(pinned_ring_buffer_.region(0).size < PAGE_SIZE) ||
((pinned_ring_buffer_.region(0).phys_addr + pinned_ring_buffer_.region(0).size)
>= fbl::numeric_limits<uint32_t>::max())) {
zxlogf(ERROR, "Bad ring buffer scatter/gather list passed to %s\n", __PRETTY_FUNCTION__);
return ZX_ERR_INVALID_ARGS;
}
res = CreateFormatList();
if (res != ZX_OK) {
return res;
}
// Set our gain capabilities.
cur_gain_state_.cur_gain = 0.0;
cur_gain_state_.cur_mute = false;
cur_gain_state_.cur_agc = false;
cur_gain_state_.min_gain = 0.0;
cur_gain_state_.max_gain = 0.0;
cur_gain_state_.gain_step = 0.0;
cur_gain_state_.can_mute = true;
cur_gain_state_.can_agc = false;
// Set our device node name.
snprintf(device_name_, sizeof(device_name_), "vim2-spdif-out");
// Create our unique ID by hashing portions of the EDID we get from our
// display. In particular, look for and hash...
//
// 1) The vendor/product ID.
// 2) The first monitor descriptor, if present.
// 3) The monitor serial number, if present.
//
// We deliberately do not simply hash contents the entire EDID. Timing
// and other configuration information can change, esp. when a device is
// connected to an AV receiver and changes are made to the processing
// configuration of the AVR. We want to focus on attempting to identify the
// device we are connected to, and not the mode that it is operating in.
//
// While we are parsing this information, also extract the manufacturer name
// (from the vendor/product ID section), and the device name (from the first
// monitor descriptor, if present).
//
// TODO(johngro): Someday, when this gets split into separate DAI/Codec
// drivers, this code belongs in the HDMI codec section of things.
digest::Digest sha;
res = sha.Init();
if (res != ZX_OK) {
zxlogf(WARN, "Failed to initialize digest while computing unique ID. (res %d)\n", res);
return res;
}
// Seed our SHA with a constant number taken from 'uuidgen'.
static const uint8_t SEED[] = { 0xd8, 0x27, 0x52, 0xb7, 0x60, 0x9a, 0x46, 0xd4,
0xa6, 0xc4, 0xdc, 0x32, 0xf5, 0xce, 0x1b, 0x7d };
sha.Update(SEED, sizeof(SEED));
snprintf(mfr_name_, sizeof(mfr_name_), "%s",
strlen(display_->manufacturer_name) ? display_->manufacturer_name : "<unknown>");
snprintf(prod_name_, sizeof(prod_name_), "%s",
strlen(display_->monitor_name) ? display_->monitor_name : "Generic HDMI");
sha.Update(mfr_name_, strnlen(mfr_name_, sizeof(mfr_name_)));
sha.Update(prod_name_, strnlen(prod_name_, sizeof(prod_name_)));
sha.Update(display_->monitor_serial,
strnlen(display_->monitor_serial, sizeof(display_->monitor_serial)));
// Finish the SHA and attempt to copy as much of the results to our internal
// cached representation as we can.
uint8_t digest_out[digest::Digest::kLength];
sha.Final();
res = sha.CopyTo(digest_out, sizeof(digest_out));
if (res != ZX_OK) {
zxlogf(ERROR, "Failed to copy digest while computing unique ID. (res %d)", res);
return res;
}
::memset(unique_id_.data, 0, sizeof(unique_id_.data));
::memcpy(unique_id_.data, digest_out, fbl::min(sizeof(digest_out), sizeof(unique_id_.data)));
return ZX_OK;
}
void Vim2SpdifAudioStream::Disable(const Registers& regs) {
regs.Write32(0, AIU_958_DCU_FF_CTRL); // Disable the FIFO
regs.ClearBits32(AIU_958_MCTRL_FILL_ENB | AIU_958_MCTRL_EMPTY_ENB,
AIU_MEM_IEC958_CONTROL); // Disable the DMA
regs.Write32(AIU_RS_958_FAST_DOMAIN, AIU_RST_SOFT); // reset the unit
}
zx_status_t Vim2SpdifAudioStream::CreateFormatList() {
// Compute the list of audio formats that we support. To do this, we need
// to intersect the capabilities of the display sink we are connect to, with
// the capabilities of the S912 audio hardware.
//
// The DesignWare HDMI transmitter which is integrated into the S912 can be
// fed a couple of different ways; either from one or more I2S units acting
// in parallel, or one or more SPDIF units acting in parallel. Each unit
// can carry up to 2 channels of audio. The DesignWare block also has
// options to synthesize its own independent DMA engine (which would have
// been super convenient), but these features were not enabled when the S912
// was synthesized.
//
// The S912 has only 1 SPDIF unit (as well as only one I2S unit), which
// limits our maximum number of channels to 2.
//
// In addition, the way that the clocks are being set up on VIM2, there is
// no factor of 7 in the clock feeding the audio units. Because of this, we
// cannot generate any of the 44.1k family of audio rates. We can, however,
// generate clock rates up to 192KHz, and can generate 16, 20, and 24 bit audio.
for (unsigned i = 0; i < display_->audio_format_count; i++) {
audio_stream_format_range_t range;
zx_status_t status = display_->dc_cb->get_audio_format(
display_->dc_cb_ctx, display_->display_id, i, &range);
ZX_ASSERT(status == ZX_OK);
constexpr uint32_t SUPPORTED_FORMATS = AUDIO_SAMPLE_FORMAT_16BIT |
AUDIO_SAMPLE_FORMAT_24BIT_PACKED | AUDIO_SAMPLE_FORMAT_24BIT_IN32;
range.sample_formats =
static_cast<audio_sample_format_t>(range.sample_formats & SUPPORTED_FORMATS);
if (range.sample_formats == 0) {
continue;
}
// Require stereo
if (range.max_channels < 2) {
continue;
}
range.max_channels = fbl::min<uint8_t>(range.max_channels, 2);
constexpr uint32_t MIN_SUPPORTED_RATE = 32000;
constexpr uint32_t MAX_SUPPORTED_RATE = 192000;
range.flags &= ASF_RANGE_FLAG_FPS_48000_FAMILY;
if (range.flags == 0
|| range.max_frames_per_second < MIN_SUPPORTED_RATE
|| range.min_frames_per_second > MAX_SUPPORTED_RATE) {
continue;
}
range.max_frames_per_second = fbl::min(MAX_SUPPORTED_RATE, range.max_frames_per_second);
range.min_frames_per_second = fbl::max(MIN_SUPPORTED_RATE, range.min_frames_per_second);
fbl::AllocChecker ac;
supported_formats_.push_back(range, &ac);
if (!ac.check()) {
zxlogf(ERROR, "Out of memory attempting to construct supported format list.\n");
return ZX_ERR_NO_MEMORY;
}
}
return ZX_OK;
}
void Vim2SpdifAudioStream::Enable() {
const auto& regs = *regs_;
regs.Write32(AIU_RS_958_FAST_DOMAIN, AIU_RST_SOFT); // reset
// Force the next sample fetched from the FIFO to be the start of a
// frame by writing *any* value to the FORCE_LEFT register.
//
// Note: In the AmLogic documentation I have access to, this register is
// actually missing from the documentation (but mentioned briefly in the
// discussion of bit 13 of AIU_958_MISC). Notes left by the AM Logic driver
// author in other codebases seem to say that when the SPDIF serializer has
// been reset, that whether or not the next payload is supposed to be a left
// or right sample does not actually get reset. In order to get a proper
// sequence of marker bits transmitted, we are supposed to use the
// FORCE_LEFT register to reset this state as well any time we reset the
// SPDIF TX unit.
regs.Write32(0x00, AIU_958_FORCE_LEFT);
regs.SetBits32(AIU_958_MCTRL_FILL_ENB | AIU_958_MCTRL_EMPTY_ENB,
AIU_MEM_IEC958_CONTROL); // Enable the DMA
regs.SetBits32(AIU_958_DCU_FF_CTRL_ENB, AIU_958_DCU_FF_CTRL); // Enable the fifo
}
void Vim2SpdifAudioStream::SetupBuffer() {
ZX_DEBUG_ASSERT((regs_ != nullptr));
const auto& regs = *regs_;
// Set up the DMA addresses.
ZX_DEBUG_ASSERT(pinned_ring_buffer_.region_count() == 1);
ZX_DEBUG_ASSERT(pinned_ring_buffer_.region(0).size >= 8);
ZX_DEBUG_ASSERT((pinned_ring_buffer_.region(0).phys_addr +
pinned_ring_buffer_.region(0).size - 1)
<= fbl::numeric_limits<uint32_t>::max());
const auto& r = pinned_ring_buffer_.region(0);
ZX_DEBUG_ASSERT(usable_buffer_size_ >= AIU_958_BYTES_PER_FRAME);
ZX_DEBUG_ASSERT(usable_buffer_size_ <= r.size);
regs.Write32(static_cast<uint32_t>(r.phys_addr), AIU_MEM_IEC958_START_PTR);
regs.Write32(static_cast<uint32_t>(r.phys_addr), AIU_MEM_IEC958_RD_PTR);
regs.Write32(static_cast<uint32_t>(r.phys_addr + usable_buffer_size_ - 8), AIU_MEM_IEC958_END_PTR);
// Set the masks register to all channels present, and to read from all
// channels. Apparently, this is the thing to do when we are operating in
// "split mode"
regs.Write32(0xFFFF, AIU_MEM_IEC958_MASKS);
// Now that the buffer has been set up, perform some register writes to the
// CONTROL and BUF_CONTROL registers in order complete the setup.
//
// Exactly what this is accomplishing is something of a mystery.
// Documentation for bit 0 of the MEM_CONTROL register consists of "bit 0:
// cntl_init". Documentation for the low 16 bits of the BUF_CNTL register
// consists of "bits [0:15]: level_hold". Why we need to follow this
// sequence, or what it is accomplishing, is not documented.
//
// This sequence is here right now because it is done by the driver written
// by AmLogic's engineer(s) in other code bases. They provide no
// real explanation for what is going on here either; so for now, this
// remains nothing but cargo-cult garbage.
regs.SetBits32(AIU_958_MCTRL_INIT, AIU_MEM_IEC958_CONTROL);
regs.ClearBits32(AIU_958_MCTRL_INIT, AIU_MEM_IEC958_CONTROL);
regs.Write32(1, AIU_MEM_IEC958_BUF_CNTL);
regs.Write32(0, AIU_MEM_IEC958_BUF_CNTL);
}
void Vim2SpdifAudioStream::SetMode(uint32_t frame_rate, audio_sample_format_t fmt) {
ZX_DEBUG_ASSERT((regs_ != nullptr));
const auto& regs = *regs_;
// Look up our frame rate to figure out our clock divider and channel status
// bit. Note: clock divider values are based on a reference frame rate of
// 192KHz
static const struct {
uint32_t frame_rate;
uint32_t div_bits;
uint32_t ch_status_bits;
} RATE_LUT[] = {
{ .frame_rate = 32000,
.div_bits = SHIFTED_VAL(AIU_CLK_CTRL_958_DIV, 2u) | AIU_CLK_CTRL_958_DIV_MORE,
.ch_status_bits = SPDIF_CS_SAMP_FREQ_32K
},
{ .frame_rate = 48000,
.div_bits = SHIFTED_VAL(AIU_CLK_CTRL_958_DIV, 3u),
.ch_status_bits = SPDIF_CS_SAMP_FREQ_48K
},
{ .frame_rate = 96000,
.div_bits = SHIFTED_VAL(AIU_CLK_CTRL_958_DIV, 1u),
.ch_status_bits = SPDIF_CS_SAMP_FREQ_96K
},
{ .frame_rate = 192000,
.div_bits = SHIFTED_VAL(AIU_CLK_CTRL_958_DIV, 0u),
.ch_status_bits = SPDIF_CS_SAMP_FREQ_192K
},
};
uint32_t rate_ndx;
for (rate_ndx = 0; rate_ndx < fbl::count_of(RATE_LUT); ++rate_ndx) {
if (RATE_LUT[rate_ndx].frame_rate == frame_rate) {
break;
}
}
// The requested frame rate should already have been validated by the code
// before us. If something has gone terribly wrong, log a warning and
// default to 48K.
if (rate_ndx >= fbl::count_of(RATE_LUT)) {
constexpr uint32_t DEFAULT_RATE_NDX = 1;
zxlogf(WARN, "Failed to find requested frame rate (%u) in LUT! Defaulting to 48000\n",
frame_rate);
static_assert(DEFAULT_RATE_NDX < fbl::count_of(RATE_LUT), "Invalid default rate index!");
rate_ndx = DEFAULT_RATE_NDX;
}
const auto& RATE = RATE_LUT[rate_ndx];
// Now go ahead and set up the clock divider.
constexpr uint32_t DIV_MASK = SHIFTED_MASK(AIU_CLK_CTRL_958_DIV) | AIU_CLK_CTRL_958_DIV_MORE;
regs.ModifyBits32(RATE.div_bits, DIV_MASK, AIU_CLK_CTRL);
// Send a 0 for the V bit in each frame. This indicates that the audio is
// "valid", at least from a PCM perspective. When packing compressed audio
// into a SPDIF transport, apparently the thing to do is set the V bit to 1
// in order to prevent older SPDIF receivers from treating the data like PCM
// and breaking your ears.
regs.Write32(AIU_958_VCTRL_SEND_VBIT, AIU_958_VALID_CTRL);
// TODO(johngro): Should the bytes per frame vary based on the size of an
// audio frame? In particular, should the bytes per frame be an integer
// multiple of the audio frame size?
regs.Write32(AIU_958_BYTES_PER_FRAME, AIU_958_BPF);
// TODO(johngro): Provide some way to change the category code. Shipping
// products should not be sending "experimental" as their category code.
constexpr uint32_t CH_STATUS_BASE = SPDIF_CS_SPDIF_CONSUMER
| SPDIF_CS_AUD_DATA_PCM
| SPDIF_CS_COPY_PERMITTED
| SPDIF_CS_NO_PRE_EMPHASIS
| SPDIF_CS_CCODE_EXPERIMENTAL
| SPDIF_CS_CLK_ACC_100PPM;
constexpr uint32_t MISC_BASE = AIU_958_MISC_FORCE_LR;
constexpr uint32_t MCTRL_BASE = AIU_958_MCTRL_LINEAR_RAW
| SHIFTED_VAL(AIU_958_MCTRL_ENDIAN, 0u);
uint32_t ch_status = CH_STATUS_BASE | RATE.ch_status_bits;
uint32_t misc = MISC_BASE;
uint32_t mctrl = MCTRL_BASE;
// TODO(johngro): Figure out how to get to bits >= 32 in the channel status
// word. In theory, we can use bits [32, 35] to signal the number of
// significant bits in the encoding, as well as to indicate that the
// auxiliary bits are carrying audio data instead of aux signalling.
switch (fmt) {
case AUDIO_SAMPLE_FORMAT_24BIT_PACKED:
break;
// Notes about the 32bit shift field.
// The 958_MISC register has a 3-bit field in it whose documentation reads...
//
// "shift number for 32 bit mode"
//
// Experimentally, it has been determined that the SPDIF encoder expects
// audio to be right justified when sending data from 32 bit containers.
// IOW, if a user puts 24 bit samples into a 32 bit container, the SPDIF
// encoder expects the samples to be in bits [0, 23].
//
// If audio is left justified instead (think 32 bit samples with the low
// bits zeroed out), the "shift number" bits can be used. The 32 bit words
// will be right shifted by this number of bits for values [0, 6], or 8 bits
// to the left when set to the 7.
//
// TL;DR? When sending left justified audio in a 32 bit container, set this
// field to 7.
case AUDIO_SAMPLE_FORMAT_24BIT_IN32:
misc |= AIU_958_MISC_32BIT_MODE | SHIFTED_VAL(AIU_958_MISC_32BIT_SHIFT, 7u);
break;
default:
zxlogf(WARN, "Unsupported format (0x%08x), defaulting to PCM16\n", fmt);
__FALLTHROUGH;
case AUDIO_SAMPLE_FORMAT_16BIT:
mctrl |= AIU_958_MCTRL_16BIT_MODE;
misc |= AIU_958_MISC_16BIT | SHIFTED_VAL(AIU_958_MISC_16BIT_ALIGN,
AIU_958_MISC_16BIT_ALIGN_LEFT);
break;
}
regs.Write32((ch_status & 0xFFFF), AIU_958_CHSTAT_L0);
regs.Write32((ch_status & 0xFFFF), AIU_958_CHSTAT_R0);
regs.Write32((ch_status >> 16), AIU_958_CHSTAT_L1);
regs.Write32((ch_status >> 16), AIU_958_CHSTAT_R1);
regs.Write32(misc, AIU_958_MISC);
regs.Write32(mctrl, AIU_MEM_IEC958_CONTROL);
// Set the "level hold" to zero. I have no idea why.
regs.ClearBits32(SHIFTED_MASK(AIU_958_BCTRL_LEVEL_HOLD), AIU_MEM_IEC958_BUF_CNTL);
}
void Vim2SpdifAudioStream::Mute(bool muted) {
constexpr uint32_t MUTE_BITS = AIU_958_CTRL_MUTE_LEFT
| AIU_958_CTRL_MUTE_RIGHT
| AIU_958_CTRL_FUB_ZERO;
const auto& regs = *regs_;
regs.Write32(muted ? MUTE_BITS : 0u, AIU_958_CTRL);
}
} // namespace vim2
} // namespace audio