blob: 88ac9163feeabf54b8f8f97aa5828e6b883d5833 [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 <zircon/device/audio.h>
#include "intel-audio-dsp.h"
#include "intel-dsp-topology.h"
namespace audio {
namespace intel_hda {
namespace {
// Module config parameters extracted from kbl_i2s_chrome.conf
// Set up 2 pipelines for system playback:
// 1. copier[host DMA]->mixin
// 2. mixout->copier[I2S DMA]
// 2 Pipelines are needed because only one instance of a module can exist in a pipeline.
constexpr uint8_t PIPELINE0_ID = 0;
constexpr uint8_t PIPELINE1_ID = 1;
// Set up 2 pipelines for system capture:
// 2. copier[I2S DMA]->mixin
// 3. mixout->copier[host DMA]
// 2 Pipelines are needed because only one instance of a module can exist in a pipeline.
constexpr uint8_t PIPELINE2_ID = 2;
constexpr uint8_t PIPELINE3_ID = 3;
// Module instance IDs.
constexpr uint8_t HOST_OUT_COPIER_ID = 0;
constexpr uint8_t I2S0_OUT_COPIER_ID = 1;
constexpr uint8_t I2S0_IN_COPIER_ID = 2;
constexpr uint8_t HOST_IN_COPIER_ID = 3;
constexpr uint8_t HOST_OUT_MIXIN_ID = 0;
constexpr uint8_t I2S0_IN_MIXIN_ID = 1;
constexpr uint8_t I2S0_OUT_MIXOUT_ID = 0;
constexpr uint8_t HOST_IN_MIXOUT_ID = 1;
const struct PipelineConfig {
uint8_t id;
uint8_t priority;
uint8_t mem_pages;
bool lp;
} PIPELINE_CFG[] = {
{
.id = PIPELINE0_ID,
.priority = 0,
.mem_pages = 2,
.lp = true, // false in config, keep running in low power mode for dev
},
{
.id = PIPELINE1_ID,
.priority = 0,
.mem_pages = 4,
.lp = true,
},
{
.id = PIPELINE2_ID,
.priority = 0,
.mem_pages = 2,
.lp = true,
},
{
.id = PIPELINE3_ID,
.priority = 0,
.mem_pages = 2,
.lp = true,
},
};
// Use 48khz 16-bit stereo throughout
const AudioDataFormat FMT_HOST = {
.sampling_frequency = SamplingFrequency::FS_48000HZ,
.bit_depth = BitDepth::DEPTH_16BIT,
.channel_map = 0xFFFFFF10,
.channel_config = ChannelConfig::CONFIG_STEREO,
.interleaving_style = InterleavingStyle::PER_CHANNEL,
.number_of_channels = 2,
.valid_bit_depth = 16,
.sample_type = SampleType::INT_MSB,
.reserved = 0,
};
const AudioDataFormat FMT_I2S = {
.sampling_frequency = SamplingFrequency::FS_48000HZ,
.bit_depth = BitDepth::DEPTH_32BIT,
.channel_map = 0xFFFFFF10,
.channel_config = ChannelConfig::CONFIG_STEREO,
.interleaving_style = InterleavingStyle::PER_CHANNEL,
.number_of_channels = 2,
.valid_bit_depth = 16,
.sample_type = SampleType::INT_MSB,
.reserved = 0,
};
// Mixer modules only operate on 32-bits
const AudioDataFormat FMT_MIXER = {
.sampling_frequency = SamplingFrequency::FS_48000HZ,
.bit_depth = BitDepth::DEPTH_32BIT,
.channel_map = 0xFFFFFF10,
.channel_config = ChannelConfig::CONFIG_STEREO,
.interleaving_style = InterleavingStyle::PER_CHANNEL,
.number_of_channels = 2,
.valid_bit_depth = 32,
.sample_type = SampleType::INT_MSB,
.reserved = 0,
};
const CopierCfg HOST_OUT_COPIER_CFG = {
.base_cfg = {
.cpc = 100000,
.ibs = 384,
.obs = 384,
.is_pages = 0,
.audio_fmt = {
.sampling_frequency = FMT_HOST.sampling_frequency,
.bit_depth = FMT_HOST.bit_depth,
.channel_map = FMT_HOST.channel_map,
.channel_config = FMT_HOST.channel_config,
.interleaving_style = FMT_HOST.interleaving_style,
.number_of_channels = FMT_HOST.number_of_channels,
.valid_bit_depth = FMT_HOST.valid_bit_depth,
.sample_type = FMT_HOST.sample_type,
.reserved = 0,
},
},
.out_fmt = {
.sampling_frequency = FMT_MIXER.sampling_frequency,
.bit_depth = FMT_MIXER.bit_depth,
.channel_map = FMT_MIXER.channel_map,
.channel_config = FMT_MIXER.channel_config,
.interleaving_style = FMT_MIXER.interleaving_style,
.number_of_channels = FMT_MIXER.number_of_channels,
.valid_bit_depth = FMT_MIXER.valid_bit_depth,
.sample_type = FMT_MIXER.sample_type,
.reserved = 0,
},
.copier_feature_mask = 0,
.gtw_cfg = {
.node_id = HDA_GATEWAY_CFG_NODE_ID(DMA_TYPE_HDA_HOST_OUTPUT, 0),
.dma_buffer_size = 2 * 384,
.config_length = 0,
},
};
const CopierCfg HOST_IN_COPIER_CFG = {
.base_cfg = {
.cpc = 100000,
.ibs = 384,
.obs = 384,
.is_pages = 0,
.audio_fmt = {
.sampling_frequency = FMT_MIXER.sampling_frequency,
.bit_depth = FMT_MIXER.bit_depth,
.channel_map = FMT_MIXER.channel_map,
.channel_config = FMT_MIXER.channel_config,
.interleaving_style = FMT_MIXER.interleaving_style,
.number_of_channels = FMT_MIXER.number_of_channels,
.valid_bit_depth = FMT_MIXER.valid_bit_depth,
.sample_type = FMT_MIXER.sample_type,
.reserved = 0,
},
},
.out_fmt = {
.sampling_frequency = FMT_HOST.sampling_frequency,
.bit_depth = FMT_HOST.bit_depth,
.channel_map = FMT_HOST.channel_map,
.channel_config = FMT_HOST.channel_config,
.interleaving_style = FMT_HOST.interleaving_style,
.number_of_channels = FMT_HOST.number_of_channels,
.valid_bit_depth = FMT_HOST.valid_bit_depth,
.sample_type = FMT_HOST.sample_type,
.reserved = 0,
},
.copier_feature_mask = 0,
.gtw_cfg = {
.node_id = HDA_GATEWAY_CFG_NODE_ID(DMA_TYPE_HDA_HOST_INPUT, 0),
.dma_buffer_size = 2 * 384,
.config_length = 0,
},
};
constexpr uint8_t I2S_OUT_INSTANCE_ID = 0;
const CopierCfg I2S_OUT_COPIER_CFG = {
.base_cfg = {
.cpc = 100000,
.ibs = 384,
.obs = 384,
.is_pages = 0,
.audio_fmt = {
.sampling_frequency = FMT_MIXER.sampling_frequency,
.bit_depth = FMT_MIXER.bit_depth,
.channel_map = FMT_MIXER.channel_map,
.channel_config = FMT_MIXER.channel_config,
.interleaving_style = FMT_MIXER.interleaving_style,
.number_of_channels = FMT_MIXER.number_of_channels,
.valid_bit_depth = FMT_MIXER.valid_bit_depth,
.sample_type = FMT_MIXER.sample_type,
.reserved = 0,
},
},
.out_fmt = {
.sampling_frequency = FMT_I2S.sampling_frequency,
.bit_depth = FMT_I2S.bit_depth,
.channel_map = FMT_I2S.channel_map,
.channel_config = FMT_I2S.channel_config,
.interleaving_style = FMT_I2S.interleaving_style,
.number_of_channels = FMT_I2S.number_of_channels,
.valid_bit_depth = FMT_I2S.valid_bit_depth,
.sample_type = FMT_I2S.sample_type,
.reserved = 0,
},
.copier_feature_mask = 0,
.gtw_cfg = {
.node_id = I2S_GATEWAY_CFG_NODE_ID(DMA_TYPE_I2S_LINK_OUTPUT, I2S_OUT_INSTANCE_ID, 0),
.dma_buffer_size = 2 * 384,
.config_length = 0,
},
};
constexpr uint8_t I2S_IN_INSTANCE_ID = 0;
const CopierCfg I2S_IN_COPIER_CFG = {
.base_cfg = {
.cpc = 100000,
.ibs = 384,
.obs = 384,
.is_pages = 0,
.audio_fmt = {
.sampling_frequency = FMT_I2S.sampling_frequency,
.bit_depth = FMT_I2S.bit_depth,
.channel_map = FMT_I2S.channel_map,
.channel_config = FMT_I2S.channel_config,
.interleaving_style = FMT_I2S.interleaving_style,
.number_of_channels = FMT_I2S.number_of_channels,
.valid_bit_depth = FMT_I2S.valid_bit_depth,
.sample_type = FMT_I2S.sample_type,
.reserved = 0,
},
},
.out_fmt = {
.sampling_frequency = FMT_MIXER.sampling_frequency,
.bit_depth = FMT_MIXER.bit_depth,
.channel_map = FMT_MIXER.channel_map,
.channel_config = FMT_MIXER.channel_config,
.interleaving_style = FMT_MIXER.interleaving_style,
.number_of_channels = FMT_MIXER.number_of_channels,
.valid_bit_depth = FMT_MIXER.valid_bit_depth,
.sample_type = FMT_MIXER.sample_type,
.reserved = 0,
},
.copier_feature_mask = 0,
.gtw_cfg = {
.node_id = I2S_GATEWAY_CFG_NODE_ID(DMA_TYPE_I2S_LINK_INPUT, I2S_IN_INSTANCE_ID, 0),
.dma_buffer_size = 2 * 384,
.config_length = 0,
},
};
const BaseModuleCfg MIXER_CFG = {
.cpc = 100000,
.ibs = 384,
.obs = 384,
.is_pages = 0,
.audio_fmt = {
.sampling_frequency = FMT_MIXER.sampling_frequency,
.bit_depth = FMT_MIXER.bit_depth,
.channel_map = FMT_MIXER.channel_map,
.channel_config = FMT_MIXER.channel_config,
.interleaving_style = FMT_MIXER.interleaving_style,
.number_of_channels = FMT_MIXER.number_of_channels,
.valid_bit_depth = FMT_MIXER.valid_bit_depth,
.sample_type = FMT_MIXER.sample_type,
.reserved = 0,
},
};
} // anon namespace
zx_status_t IntelAudioDsp::GetI2SBlob(uint8_t bus_id, uint8_t direction,
const AudioDataFormat& format,
const void** out_blob, size_t* out_size) {
zx_status_t st = ZX_ERR_NOT_FOUND;
for (const auto& cfg : i2s_configs_) {
if (!cfg.valid) {
break;
}
if ((cfg.bus_id != bus_id) || (cfg.direction != direction)) {
continue;
}
// TODO better matching here
const formats_config_t* formats = cfg.formats;
const format_config_t* f = &formats->format_configs[0];
for (size_t j = 0; j < formats->format_config_count; j++) {
if (format.valid_bit_depth != f->valid_bits_per_sample) {
f = reinterpret_cast<const format_config_t*>(
reinterpret_cast<const uint8_t*>(f) +
sizeof(*f) +
f->config.capabilities_size
);
continue;
}
*out_blob = reinterpret_cast<const void*>(f->config.capabilities);
*out_size = static_cast<size_t>(f->config.capabilities_size);
st = ZX_OK;
break;
}
}
return st;
}
zx_status_t IntelAudioDsp::CreateHostDmaModule(uint8_t instance_id, uint8_t pipeline_id,
const CopierCfg& cfg) {
return ipc_.InitInstance(module_ids_[Module::COPIER],
instance_id,
ProcDomain::LOW_LATENCY,
0,
pipeline_id,
sizeof(cfg),
&cfg);
}
zx_status_t IntelAudioDsp::CreateI2SModule(uint8_t instance_id, uint8_t pipeline_id,
uint8_t i2s_instance_id, uint8_t direction,
const CopierCfg& cfg) {
const void* blob;
size_t blob_size;
zx_status_t st = GetI2SBlob(i2s_instance_id, direction,
(direction == NHLT_DIRECTION_RENDER) ? cfg.out_fmt :
cfg.base_cfg.audio_fmt,
&blob, &blob_size);
if (st != ZX_OK) {
LOG(ERROR, "I2S config (instance %u direction %u) not found\n",
i2s_instance_id, direction);
return st;
}
// Copy the I2S config blob
size_t cfg_size = sizeof(cfg) + blob_size;
ZX_DEBUG_ASSERT(cfg_size <= UINT16_MAX);
fbl::AllocChecker ac;
fbl::unique_ptr<uint8_t[]> cfg_buf(new (&ac) uint8_t[cfg_size]);
if (!ac.check()) {
LOG(ERROR, "out of memory while attempting to allocate copier config buffer\n");
return ZX_ERR_NO_MEMORY;
}
memcpy(cfg_buf.get() + sizeof(cfg), blob, blob_size);
// Copy the copier config
memcpy(cfg_buf.get(), &cfg, sizeof(cfg));
auto copier_cfg = reinterpret_cast<CopierCfg*>(cfg_buf.get());
copier_cfg->gtw_cfg.config_length = static_cast<uint32_t>(blob_size);
return ipc_.InitInstance(module_ids_[Module::COPIER],
instance_id,
ProcDomain::LOW_LATENCY,
0,
pipeline_id,
static_cast<uint16_t>(cfg_size),
cfg_buf.get());
}
zx_status_t IntelAudioDsp::CreateMixinModule(uint8_t instance_id, uint8_t pipeline_id,
const BaseModuleCfg& cfg) {
return ipc_.InitInstance(module_ids_[Module::MIXIN],
instance_id,
ProcDomain::LOW_LATENCY,
0,
pipeline_id,
sizeof(cfg),
&cfg);
}
zx_status_t IntelAudioDsp::CreateMixoutModule(uint8_t instance_id, uint8_t pipeline_id,
const BaseModuleCfg& cfg) {
return ipc_.InitInstance(module_ids_[Module::MIXOUT],
instance_id,
ProcDomain::LOW_LATENCY,
0,
pipeline_id,
sizeof(cfg),
&cfg);
}
zx_status_t IntelAudioDsp::SetupPipelines() {
ZX_DEBUG_ASSERT(module_ids_[Module::COPIER] != 0);
ZX_DEBUG_ASSERT(module_ids_[Module::MIXIN] != 0);
ZX_DEBUG_ASSERT(module_ids_[Module::MIXOUT] != 0);
zx_status_t st = ZX_OK;
// Create pipelines
for (const auto& cfg : PIPELINE_CFG) {
st = ipc_.CreatePipeline(cfg.id, cfg.priority, cfg.mem_pages, cfg.lp);
if (st != ZX_OK) {
return st;
}
}
// Create pipeline 0 modules. Host DMA -> mixin
// Modules must be created in order of source -> sink
st = CreateHostDmaModule(HOST_OUT_COPIER_ID, PIPELINE0_ID, HOST_OUT_COPIER_CFG);
if (st != ZX_OK) {
return st;
}
st = CreateMixinModule(HOST_OUT_MIXIN_ID, PIPELINE0_ID, MIXER_CFG);
if (st != ZX_OK) {
return st;
}
// Bind pipeline 0
st = ipc_.Bind(module_ids_[Module::COPIER], HOST_OUT_COPIER_ID, 0,
module_ids_[Module::MIXIN], HOST_OUT_MIXIN_ID, 0);
if (st != ZX_OK) {
return st;
}
// Create pipeline 1 modules. mixout -> I2S DMA
st = CreateMixoutModule(I2S0_OUT_MIXOUT_ID, PIPELINE1_ID, MIXER_CFG);
if (st != ZX_OK) {
return st;
}
st = CreateI2SModule(I2S0_OUT_COPIER_ID, PIPELINE1_ID,
I2S_OUT_INSTANCE_ID, NHLT_DIRECTION_RENDER,
I2S_OUT_COPIER_CFG);
if (st != ZX_OK) {
return st;
}
// Bind pipeline 1
st = ipc_.Bind(module_ids_[Module::MIXOUT], I2S0_OUT_MIXOUT_ID, 0,
module_ids_[Module::COPIER], I2S0_OUT_COPIER_ID, 0);
if (st != ZX_OK) {
return st;
}
// Create pipeline 2 modules. I2S DMA -> mixin
st = CreateI2SModule(I2S0_IN_COPIER_ID, PIPELINE2_ID,
I2S_IN_INSTANCE_ID, NHLT_DIRECTION_CAPTURE,
I2S_IN_COPIER_CFG);
if (st != ZX_OK) {
return st;
}
st = CreateMixinModule(I2S0_IN_MIXIN_ID, PIPELINE2_ID, MIXER_CFG);
if (st != ZX_OK) {
return st;
}
// Bind pipeline 2
st = ipc_.Bind(module_ids_[Module::COPIER], I2S0_IN_COPIER_ID, 0,
module_ids_[Module::MIXIN], I2S0_IN_MIXIN_ID, 0);
if (st != ZX_OK) {
return st;
}
// Create pipeline 3 modules. mixout -> Host DMA
st = CreateMixoutModule(HOST_IN_MIXOUT_ID, PIPELINE3_ID, MIXER_CFG);
if (st != ZX_OK) {
return st;
}
st = CreateHostDmaModule(HOST_IN_COPIER_ID, PIPELINE3_ID, HOST_IN_COPIER_CFG);
if (st != ZX_OK) {
return st;
}
// Bind pipeline 2
st = ipc_.Bind(module_ids_[Module::MIXOUT], HOST_IN_MIXOUT_ID, 0,
module_ids_[Module::COPIER], HOST_IN_COPIER_ID, 0);
if (st != ZX_OK) {
return st;
}
// Bind playback pipeline
st = ipc_.Bind(module_ids_[Module::MIXIN], HOST_OUT_MIXIN_ID, 0,
module_ids_[Module::MIXOUT], I2S0_OUT_MIXOUT_ID, 0);
if (st != ZX_OK) {
return st;
}
// Bind capture pipeline
st = ipc_.Bind(module_ids_[Module::MIXIN], I2S0_IN_MIXIN_ID, 0,
module_ids_[Module::MIXOUT], HOST_IN_MIXOUT_ID, 0);
if (st != ZX_OK) {
return st;
}
return ZX_OK;
}
zx_status_t IntelAudioDsp::StartPipeline(const DspPipeline& pipeline) {
// Sink first and then source
zx_status_t st = RunPipeline(pipeline.pl_sink);
if (st != ZX_OK) {
return st;
}
return RunPipeline(pipeline.pl_source);
// TODO Error recovery
}
zx_status_t IntelAudioDsp::PausePipeline(const DspPipeline& pipeline) {
zx_status_t st = ipc_.SetPipelineState(pipeline.pl_source, PipelineState::PAUSED, true);
if (st != ZX_OK) {
return st;
}
st = ipc_.SetPipelineState(pipeline.pl_sink, PipelineState::PAUSED, true);
if (st != ZX_OK) {
return st;
}
// Reset DSP DMA
st = ipc_.SetPipelineState(pipeline.pl_source, PipelineState::RESET, true);
if (st != ZX_OK) {
return st;
}
return ipc_.SetPipelineState(pipeline.pl_sink, PipelineState::RESET, true);
// TODO Error recovery
}
zx_status_t IntelAudioDsp::CreateAndStartStreams() {
zx_status_t res = ZX_OK;
// Create and publish the streams we will use.
static struct {
uint32_t stream_id;
bool is_input;
struct DspPipeline pipeline;
audio_stream_unique_id_t uid;
} STREAMS[] = {
// Speakers
{
.stream_id = 1,
.is_input = false,
.pipeline = {
.pl_source = PIPELINE0_ID,
.pl_sink = PIPELINE1_ID,
},
.uid = AUDIO_STREAM_UNIQUE_ID_BUILTIN_SPEAKERS,
},
};
for (const auto& stream_def : STREAMS) {
auto stream = fbl::AdoptRef(new IntelDspStream(stream_def.stream_id,
stream_def.is_input,
stream_def.pipeline,
&stream_def.uid));
res = ActivateStream(stream);
if (res != ZX_OK) {
LOG(ERROR, "Failed to activate %s stream id #%u (res %d)!",
stream_def.is_input ? "input" : "output", stream_def.stream_id, res);
return res;
}
}
return ZX_OK;
}
} // namespace intel_hda
} // namespace audio