| // Copyright 2020 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 "aml-tdm-config-device.h" |
| |
| #include <utility> |
| |
| #include <ddk/debug.h> |
| #include <fbl/auto_call.h> |
| |
| namespace audio::aml_g12 { |
| |
| AmlTdmConfigDevice::AmlTdmConfigDevice(const metadata::AmlConfig& metadata, ddk::MmioBuffer mmio) { |
| if (metadata.is_input) { |
| aml_tdm_in_t tdm = {}; |
| aml_toddr_t ddr = {}; |
| aml_tdm_mclk_t mclk = {}; |
| switch (metadata.bus) { |
| case metadata::AmlBus::TDM_A: |
| tdm = TDM_IN_A; |
| ddr = TODDR_A; |
| mclk = MCLK_A; |
| break; |
| case metadata::AmlBus::TDM_B: |
| tdm = TDM_IN_B; |
| ddr = TODDR_B; |
| mclk = MCLK_B; |
| break; |
| case metadata::AmlBus::TDM_C: |
| tdm = TDM_IN_C; |
| ddr = TODDR_C; |
| mclk = MCLK_C; |
| break; |
| } |
| device_ = AmlTdmInDevice::Create(std::move(mmio), HIFI_PLL, tdm, ddr, mclk, metadata.version); |
| } else { |
| aml_tdm_out_t tdm = {}; |
| aml_frddr_t ddr = {}; |
| aml_tdm_mclk_t mclk = {}; |
| switch (metadata.bus) { |
| case metadata::AmlBus::TDM_A: |
| tdm = TDM_OUT_A; |
| ddr = FRDDR_A; |
| mclk = MCLK_A; |
| break; |
| case metadata::AmlBus::TDM_B: |
| tdm = TDM_OUT_B; |
| ddr = FRDDR_B; |
| mclk = MCLK_B; |
| break; |
| case metadata::AmlBus::TDM_C: |
| tdm = TDM_OUT_C; |
| ddr = FRDDR_C; |
| mclk = MCLK_C; |
| break; |
| } |
| device_ = AmlTdmOutDevice::Create(std::move(mmio), HIFI_PLL, tdm, ddr, mclk, metadata.version); |
| } |
| ZX_ASSERT(device_ != nullptr); |
| } |
| |
| zx_status_t AmlTdmConfigDevice::InitHW(const metadata::AmlConfig& metadata, |
| uint64_t channels_to_use, uint32_t frame_rate) { |
| zx_status_t status; |
| |
| // Shut down the SoC audio peripherals (tdm/dma) |
| device_->Shutdown(); |
| |
| auto on_error = fbl::MakeAutoCall([this]() { device_->Shutdown(); }); |
| |
| device_->Initialize(); |
| |
| // Setup TDM. |
| constexpr uint32_t kMaxLanes = metadata::kMaxNumberOfLanes; |
| uint32_t lanes_mutes[kMaxLanes] = {}; |
| // bitoffset defines samples start relative to the edge of fsync. |
| uint8_t bitoffset = metadata.is_input ? 4 : 3; |
| if (metadata.dai.type == metadata::DaiType::I2s) { |
| bitoffset--; |
| } |
| if (metadata.dai.sclk_on_raising) { |
| bitoffset--; |
| } |
| |
| // Configure lanes mute masks based on channels_to_use and lane enable mask. |
| if (channels_to_use != AUDIO_SET_FORMAT_REQ_BITMASK_DISABLED) { |
| uint32_t channel = 0; |
| size_t lane_start = 0; |
| for (size_t i = 0; i < kMaxLanes; ++i) { |
| for (size_t j = 0; j < 64; ++j) { |
| if (metadata.lanes_enable_mask[i] & (static_cast<uint64_t>(1) << j)) { |
| if (~channels_to_use & (1 << channel)) { |
| lanes_mutes[i] |= ((1 << channel) >> lane_start); |
| } |
| channel++; |
| } |
| } |
| lane_start = channel; |
| } |
| } |
| device_->ConfigTdmSlot(bitoffset, static_cast<uint8_t>(metadata.dai.number_of_channels - 1), |
| metadata.dai.bits_per_slot - 1, metadata.dai.bits_per_sample - 1, |
| metadata.mix_mask, metadata.dai.type == metadata::DaiType::I2s); |
| device_->ConfigTdmSwaps(metadata.swaps); |
| for (size_t i = 0; i < kMaxLanes; ++i) { |
| status = device_->ConfigTdmLane(i, metadata.lanes_enable_mask[i], lanes_mutes[i]); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s could not configure TDM lane %d", __FILE__, status); |
| return status; |
| } |
| } |
| |
| if (metadata.mClockDivFactor) { |
| // PLL sourcing audio clock tree should be running at 768MHz |
| // Note: Audio clock tree input should always be < 1GHz |
| // mclk rate for 96kHz = 768MHz/5 = 153.6MHz |
| // mclk rate for 48kHz = 768MHz/10 = 76.8MHz |
| // Note: absmax mclk frequency is 500MHz per AmLogic |
| ZX_ASSERT(!(metadata.mClockDivFactor % 2)); // mClock div factor must be divisable by 2. |
| ZX_ASSERT(frame_rate == 48'000 || frame_rate == 96'000); |
| static_assert(countof(AmlTdmConfigDevice::kSupportedFrameRates) == 2); |
| static_assert(AmlTdmConfigDevice::kSupportedFrameRates[0] == 48'000); |
| static_assert(AmlTdmConfigDevice::kSupportedFrameRates[1] == 96'000); |
| uint32_t mdiv = metadata.mClockDivFactor / ((frame_rate == 96'000) ? 2 : 1); |
| status = device_->SetMclkDiv(mdiv - 1); // register val is div - 1; |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s could not configure MCLK %d", __FILE__, status); |
| return status; |
| } |
| device_->SetMClkPad(MCLK_PAD_0); |
| } |
| if (metadata.sClockDivFactor) { |
| uint32_t frame_sync_clks = 0; |
| switch (metadata.dai.type) { |
| case metadata::DaiType::I2s: |
| case metadata::DaiType::StereoLeftJustified: |
| // For I2S and Stereo Left Justified we have a 50% duty cycle, hence the frame sync clocks |
| // is set to the size of one slot. |
| frame_sync_clks = metadata.dai.bits_per_slot; |
| break; |
| case metadata::DaiType::Tdm1: |
| frame_sync_clks = 1; |
| break; |
| } |
| status = device_->SetSclkDiv(metadata.sClockDivFactor - 1, frame_sync_clks - 1, |
| (metadata.dai.bits_per_slot * metadata.dai.number_of_channels) - 1, |
| !metadata.dai.sclk_on_raising); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s could not configure SCLK %d", __FILE__, status); |
| return status; |
| } |
| } |
| |
| // Allow clock divider changes to stabilize |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(1))); |
| |
| device_->Sync(); |
| |
| on_error.cancel(); |
| // At this point the SoC audio peripherals are ready to start, but no |
| // clocks are active. The codec is also in software shutdown and will |
| // need to be started after the audio clocks are activated. |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlTdmConfigDevice::Normalize(metadata::AmlConfig& metadata) { |
| if (metadata.ring_buffer.bytes_per_sample == 0) { |
| metadata.ring_buffer.bytes_per_sample = 2; |
| } |
| // Only 16 bits samples supported. |
| if (metadata.ring_buffer.bytes_per_sample != 2) { |
| zxlogf(ERROR, "%s metadata unsupported bytes per sample %u", __FILE__, |
| metadata.ring_buffer.bytes_per_sample); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| // Only the PCM signed sample format is supported. |
| if (metadata.dai.sample_format != metadata::SampleFormat::PcmSigned) { |
| zxlogf(ERROR, "%s metadata unsupported sample type %d", __FILE__, |
| static_cast<int>(metadata.dai.sample_format)); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| if (metadata.dai.type == metadata::DaiType::I2s || |
| metadata.dai.type == metadata::DaiType::StereoLeftJustified) { |
| metadata.dai.number_of_channels = 2; |
| } |
| if (metadata.dai.bits_per_sample == 0) { |
| metadata.dai.bits_per_sample = 16; |
| } |
| if (metadata.dai.bits_per_slot == 0) { |
| metadata.dai.bits_per_slot = 32; |
| } |
| if (metadata.dai.bits_per_slot != 32 && metadata.dai.bits_per_slot != 16) { |
| zxlogf(ERROR, "%s metadata unsupported bits per slot %d", __FILE__, metadata.dai.bits_per_slot); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| if (metadata.dai.bits_per_sample != 32 && metadata.dai.bits_per_sample != 16) { |
| zxlogf(ERROR, "%s metadata unsupported bits per sample %d", __FILE__, |
| metadata.dai.bits_per_sample); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| if (metadata.dai.bits_per_sample > metadata.dai.bits_per_slot) { |
| zxlogf(ERROR, "%s metadata unsupported bits per sample bits per slot combination %u/%u", |
| __FILE__, metadata.dai.bits_per_sample, metadata.dai.bits_per_slot); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return ZX_OK; |
| } |
| |
| } // namespace audio::aml_g12 |