| // 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 "eisa_vid_lut.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 }, |
| }; |
| |
| static uint32_t DecompressEisaVid(const uint8_t* vpid) { |
| uint32_t data = (static_cast<uint32_t>(vpid[0]) << 8) | vpid[1]; |
| uint32_t a = (data >> 10) & 0x1F; |
| uint32_t b = (data >> 5) & 0x1F; |
| uint32_t c = (data >> 0) & 0x1F; |
| |
| if (!a || (a > 26) || !b || (b > 26) || !c || (c > 26)) { |
| return 0; |
| } |
| |
| return EISA_ID(a + 'A' - 1 , b + 'A' - 1, c + 'A' - 1); |
| } |
| } // 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_ || !regs_->valid()) { |
| 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; |
| } |
| |
| // In order to have gotten this far, we must have an EDID, and it must be at |
| // least 256 bytes long. Without these pre-requisites, there should be no |
| // way to signal audio support in the sink, and we should not even be here. |
| ZX_DEBUG_ASSERT((display_->edid_buf != nullptr) && (display_->edid_length >= 256)); |
| |
| // 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)); |
| |
| // Add in the VPID block. Extract the mfr EISA ID at the same time. Note |
| // that the 3 character EISA ID is actually stored in "compressed ascii" |
| // format so that it takes only two bytes. This must be expanded before |
| // passing it to the EISA LUT; |
| const uint8_t* vpid = display_->edid_buf + 0x08; |
| const char* mfr_name = lookup_eisa_vid(DecompressEisaVid(vpid)); |
| sha.Update(vpid, 10); |
| snprintf(mfr_name_, sizeof(mfr_name_), "%s", mfr_name ? mfr_name : "<unknown>"); |
| |
| // Now go looking for the first monitor descriptor. See section A.2.10.13 |
| // of CTA-861-G for details. |
| constexpr uint32_t DTD_LEN = 18; |
| const uint8_t* vesa_desc_block = display_->edid_buf + 0x36; |
| static const uint8_t MONITOR_NAME_TAG[] = { 0x00, 0x00, 0x00, 0xFC, 0x00 }; |
| |
| snprintf(prod_name_, sizeof(prod_name_), "Generic HDMI"); |
| |
| for (uint32_t i = 0; i < 4; ++i, vesa_desc_block += DTD_LEN) { |
| if (memcmp(vesa_desc_block, MONITOR_NAME_TAG, sizeof(MONITOR_NAME_TAG)) == 0) { |
| // Found a monitor name. Stuff the block into our SHA. |
| sha.Update(vesa_desc_block, DTD_LEN); |
| |
| // Then compute the name's str len and use it to populate the |
| // product name field. |
| uint32_t len; |
| for (len = 0; (len < 13) && vesa_desc_block[5 + len] != 0x0A; ++len) |
| ; // note: deliberate lack of for-loop body |
| |
| snprintf(prod_name_, fbl::min<uint32_t>(len, sizeof(prod_name_)), |
| "%s", reinterpret_cast<const char*>(vesa_desc_block + 5)); |
| |
| break; |
| } |
| } |
| |
| // Finally, go looking for a monitor serial number block in the DTD section |
| // of the CEA/CTA extension |
| const uint8_t* cea_block = display_->edid_buf + 128; |
| static const uint8_t MONITOR_SERIAL_NUM_TAG[] = { 0x00, 0x00, 0x00, 0xFF, 0x00 }; |
| for (uint32_t i = cea_block[2]; (i + DTD_LEN) <= 128; i += DTD_LEN) { |
| vesa_desc_block = cea_block + i; |
| if (memcmp(vesa_desc_block, MONITOR_SERIAL_NUM_TAG, sizeof(MONITOR_SERIAL_NUM_TAG)) == 0) { |
| sha.Update(vesa_desc_block, DTD_LEN); |
| break; |
| } |
| } |
| |
| // 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) { |
| ZX_DEBUG_ASSERT(regs.valid()); |
| |
| regs[AIU_958_DCU_FF_CTRL] = 0; // Disable the FIFO |
| regs.ClrBits(AIU_MEM_IEC958_CONTROL, |
| AIU_958_MCTRL_FILL_ENB | AIU_958_MCTRL_EMPTY_ENB); // Disable the DMA |
| regs[AIU_RST_SOFT] = AIU_RS_958_FAST_DOMAIN; // 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. |
| // |
| // So, start by looking for the SADs in the CEA/CTA EDID block and build |
| // the list by filtering each of these based on the capabilities of the S912 |
| // audio units. If there are no SADs present, then just list the basic |
| // audio formats, but without the 44.1k frequency. |
| // |
| ZX_DEBUG_ASSERT((display_->edid_buf != nullptr) && (display_->edid_length >= 256)); |
| const uint8_t* cea_block = display_->edid_buf + 128; |
| const uint8_t* sads = nullptr; |
| uint32_t sad_cnt = 0; |
| uint32_t cea_db_end = fbl::min<uint32_t>(cea_block[2], 128); |
| |
| // Look for the SAD block. Each CEA/CTA data block header requires just a |
| // single byte, and the data block section starts at byte 4 into the cea |
| // block. |
| for (uint32_t off = 4; off < cea_db_end; off += (1 + (cea_block[off] & 0x1F))) { |
| // The audio data block ID is 0x1; block IDs are bits [5, 7] of the header. |
| if ((cea_block[off] >> 5) == 0x01) { |
| sads = cea_block + off + 1; |
| sad_cnt = fbl::min<uint32_t>(cea_db_end - off - 1, cea_block[off] & 0x1F) / 3; |
| break; |
| } |
| } |
| |
| bool has_audio_block = (sad_cnt && (sads != nullptr)); |
| { |
| fbl::AllocChecker ac; |
| supported_formats_.reserve(1, &ac); |
| if (!ac.check()) { |
| zxlogf(ERROR, "Out of memory attempting to construct supported format list.\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| } |
| |
| // Add the range for basic audio support. |
| audio_stream_format_range_t range; |
| |
| range.min_channels = 2; |
| range.max_channels = 2; |
| range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT; |
| range.min_frames_per_second = 32000; |
| range.max_frames_per_second = 48000; |
| range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY; |
| |
| supported_formats_.push_back(range); |
| |
| // No short audio descriptors? If not, basic audio only. |
| if (!has_audio_block) { |
| return ZX_OK; |
| } |
| |
| // Go over the list of SADs and extract the formats we support. |
| for (uint32_t i = 0; i < sad_cnt; ++i) { |
| const uint8_t* sad = &sads[i * 3]; |
| |
| // If this is not an LPCM format according to the format code in the |
| // first byte of the SAD, skip it. |
| if (((sad[0] >> 3) & 0xF) != 0x1) { |
| continue; |
| } |
| |
| // If this is not a stereo format, skip it. |
| if ((sad[0] & 0x7) != 0x1) { |
| continue; |
| } |
| |
| // Extract only the rates that we support. If this leaves this entry |
| // with nothing, skip it. |
| constexpr uint32_t SUPPORTED_RATES = SAD_RATE_32000 | |
| SAD_RATE_48000 | |
| SAD_RATE_96000 | |
| SAD_RATE_192000; |
| uint32_t rates = sad[1] & SUPPORTED_RATES; |
| if (!rates) { |
| continue; |
| } |
| |
| uint32_t fmts = |
| (sad[2] & SAD_BPS_16 ? static_cast<uint32_t>(AUDIO_SAMPLE_FORMAT_16BIT) : 0) | |
| (sad[2] & SAD_BPS_20 ? static_cast<uint32_t>(AUDIO_SAMPLE_FORMAT_20BIT_IN32) : 0) | |
| (sad[2] & SAD_BPS_24 ? static_cast<uint32_t>(AUDIO_SAMPLE_FORMAT_24BIT_PACKED | |
| AUDIO_SAMPLE_FORMAT_24BIT_IN32) : 0); |
| |
| // If this entry applies to both 32k and 48k audio rates, then merge its |
| // bits-per-samples in with the basic audio entry. |
| constexpr uint32_t R32_48 = SAD_RATE_32000 | SAD_RATE_48000; |
| if ((rates & R32_48) == R32_48) { |
| auto& r = supported_formats_[0]; |
| r.sample_formats = static_cast<audio_sample_format_t>(r.sample_formats | fmts); |
| rates &= ~R32_48; |
| } |
| |
| // Now build continuous ranges of sample rates in the 48k family from |
| // what is left and add them to the set. |
| static const struct { |
| uint32_t flag, val; |
| } RATE_LUT[] = { |
| { SAD_RATE_32000, 32000 }, |
| { SAD_RATE_48000, 48000 }, |
| { SAD_RATE_96000, 96000 }, |
| { SAD_RATE_192000, 192000 }, |
| }; |
| |
| for (uint32_t j = 0; j < countof(RATE_LUT); ++j) { |
| const auto& start = RATE_LUT[j]; |
| if (!(rates & start.flag)) { |
| continue; |
| } |
| |
| // We found the start of a range. At this point, we are guaranteed |
| // to add at least one new entry into the set of format ranges. |
| // Find the end of this range. |
| uint32_t k; |
| for (k = j + 1; k < countof(RATE_LUT); ++k) { |
| if (!(rates & RATE_LUT[k].flag)) { |
| break; |
| } |
| } |
| |
| const auto& end = RATE_LUT[k - 1]; |
| j = k - 1; |
| |
| // Now, add the range to our set. |
| range.sample_formats = static_cast<audio_sample_format_t>(fmts); |
| range.min_frames_per_second = start.val; |
| range.max_frames_per_second = end.val; |
| |
| { |
| 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() { |
| ZX_DEBUG_ASSERT((regs_ != nullptr) && regs_->valid()); |
| const auto& regs = *regs_; |
| |
| regs[AIU_RST_SOFT] = AIU_RS_958_FAST_DOMAIN; // 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[AIU_958_FORCE_LEFT] = 0x00; |
| |
| regs.SetBits(AIU_MEM_IEC958_CONTROL, |
| AIU_958_MCTRL_FILL_ENB | AIU_958_MCTRL_EMPTY_ENB); // Enable the DMA |
| regs.SetBits(AIU_958_DCU_FF_CTRL, AIU_958_DCU_FF_CTRL_ENB); // Enable the fifo |
| } |
| |
| void Vim2SpdifAudioStream::SetupBuffer() { |
| ZX_DEBUG_ASSERT((regs_ != nullptr) && regs_->valid()); |
| 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[AIU_MEM_IEC958_START_PTR] = static_cast<uint32_t>(r.phys_addr); |
| regs[AIU_MEM_IEC958_RD_PTR] = static_cast<uint32_t>(r.phys_addr); |
| regs[AIU_MEM_IEC958_END_PTR] = static_cast<uint32_t>(r.phys_addr + usable_buffer_size_ - 8); |
| |
| // 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[AIU_MEM_IEC958_MASKS] = 0xFFFF; |
| |
| // 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.SetBits(AIU_MEM_IEC958_CONTROL, AIU_958_MCTRL_INIT); |
| regs.ClrBits(AIU_MEM_IEC958_CONTROL, AIU_958_MCTRL_INIT); |
| regs[AIU_MEM_IEC958_BUF_CNTL] = 1; |
| regs[AIU_MEM_IEC958_BUF_CNTL] = 0; |
| } |
| |
| void Vim2SpdifAudioStream::SetMode(uint32_t frame_rate, audio_sample_format_t fmt) { |
| ZX_DEBUG_ASSERT((regs_ != nullptr) && regs_->valid()); |
| 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 < countof(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 >= countof(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 < countof(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.ModBits(AIU_CLK_CTRL, DIV_MASK, RATE.div_bits); |
| |
| // 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[AIU_958_VALID_CTRL] = AIU_958_VCTRL_SEND_VBIT; |
| |
| // 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[AIU_958_BPF] = AIU_958_BYTES_PER_FRAME; |
| |
| // 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[AIU_958_CHSTAT_L0] = (ch_status & 0xFFFF); |
| regs[AIU_958_CHSTAT_R0] = (ch_status & 0xFFFF); |
| regs[AIU_958_CHSTAT_L1] = (ch_status >> 16); |
| regs[AIU_958_CHSTAT_R1] = (ch_status >> 16); |
| regs[AIU_958_MISC] = misc; |
| regs[AIU_MEM_IEC958_CONTROL] = mctrl; |
| |
| // Set the "level hold" to zero. I have no idea why. |
| regs.ClrBits(AIU_MEM_IEC958_BUF_CNTL, SHIFTED_MASK(AIU_958_BCTRL_LEVEL_HOLD)); |
| } |
| |
| 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[AIU_958_CTRL] = muted ? MUTE_BITS : 0u; |
| } |
| |
| } // namespace vim2 |
| } // namespace audio |