blob: 4db12d67b01bed984379264e88e810ece0c4fab6 [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 "intel-dsp-topology.h"
#include <zircon/device/audio.h>
#include <memory>
#include <fbl/string_printf.h>
#include "intel-dsp-ipc.h"
#include "intel-dsp-modules.h"
#include "intel-dsp.h"
namespace audio {
namespace intel_hda {
namespace {
// Module config parameters extracted from kbl_i2s_chrome.conf
// To route audio from the system memory to the audio codecs, we must
// set up an appropriate _topology_ inside the DSP. Topologies consist
// of _pipelines_ and _modules_.
//
// Each module performs some operation on the audio, such as copying it
// to/from a DMA gateway; mixing the output of other modules together;
// modifying the volume of the stream; etc. Each module is given
// a unique name of the form (<module type>, <id>). For example,
// (<COPIER>, 0), (<COPIER>, 1) and (<DEMUX>, 0) are three unique names.
//
// Pipelines are used to instruct the DSP how to schedule modules. Every
// module needs to be inside a pipeline. Each pipeline can have an
// arbitrary number of modules, with the following constraints:
//
// * If a module connects to another module in the same pipeline, it must
// use output pin 0.
//
// * A pipeline can only have a single linear series of modules (i.e., no
// forking within the pipeline, but forking to another pipeline is permitted).
//
// Currently, the only type of module we use in our topology is
// a _Copier_ module. Copiers are a type of module which may be
// configured to copy audio data from:
//
// * A DMA gateway to another module
// * a module to another module
// * a module to a DMA gateway
//
// but cannot copy directly from DMA to DMA.
//
// We currently set up a default topology consisting of two pipelines,
// as follows:
//
// Playback: [host DMA gateway] -> copier -> copier -> [I2S gateway]
// Capture: [I2S gateway] -> copier -> copier -> [host DMA gateway]
constexpr uint8_t I2S0_BUS = 0;
constexpr uint8_t I2S1_BUS = 1;
// Use 48khz 16-bit stereo for host input/output.
constexpr AudioDataFormat kHostFormat = {
.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,
};
// Format used by the Eve's Max98927 speaker codecs, and onboard mic,
// which are both on the I2S-0 bus.
constexpr AudioDataFormat kFormatI2S0Bus = {
.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,
};
constexpr AudioDataFormat kFormatMax98927 = kFormatI2S0Bus;
constexpr AudioDataFormat kFormatDmic = kFormatI2S0Bus;
// Format used by the Eve's ALC5663 headphone codec.
constexpr AudioDataFormat kFormatAlc5663 = {
.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 = 24,
.sample_type = SampleType::INT_MSB,
.reserved = 0,
};
// Format used for intermediate DSP operations.
const AudioDataFormat kDspFormat = {
.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,
};
constexpr uint32_t AudioBytesPerSec(const AudioDataFormat& format) {
return static_cast<uint32_t>(format.sampling_frequency) *
(static_cast<uint32_t>(format.bit_depth) / 8) * format.number_of_channels;
}
CopierCfg CreateCopierCfg(AudioDataFormat input, AudioDataFormat output) {
CopierCfg result = {};
// Setup input/output formats.
result.base_cfg.audio_fmt = input;
result.out_fmt = output;
// Calculate input and output buffer sizes. The copier needs 1ms of data.
result.base_cfg.ibs = AudioBytesPerSec(input) / 1000;
result.base_cfg.obs = AudioBytesPerSec(output) / 1000;
// Set cycles per input frame to 100k (arbitrary).
result.base_cfg.cpc = 100'000;
return result;
}
CopierCfg CreateGatewayCopierCfg(const AudioDataFormat& input, const AudioDataFormat& output,
uint32_t gateway_node_id) {
// Create base config.
CopierCfg result = CreateCopierCfg(input, output);
result.gtw_cfg.node_id = gateway_node_id;
// Set the DMA buffer size to 2 times the input/output frame size.
result.gtw_cfg.dma_buffer_size = std::max(result.base_cfg.ibs, result.base_cfg.obs) * 2;
return result;
}
// Copy the given ranges of bytes into a new std::vector<uint8_t>.
std::vector<uint8_t> RawBytesOf(const uint8_t* object, size_t size) {
std::vector<uint8_t> result;
result.resize(size);
memcpy(result.data(), object, size);
return result;
}
// Copy the underlying bytes of the given object to a new std::vector<uint8_t>.
template <typename T>
std::vector<uint8_t> RawBytesOf(const T* object) {
return RawBytesOf(reinterpret_cast<const uint8_t*>(object), sizeof(*object));
}
zx_status_t GetI2SBlob(const Nhlt& nhlt, uint8_t bus_id, uint8_t direction,
const AudioDataFormat& format, const void** out_blob, size_t* out_size) {
for (const auto& cfg : nhlt.i2s_configs()) {
if ((cfg.bus_id != bus_id) || (cfg.direction != direction)) {
continue;
}
// TODO better matching here
for (const I2SConfig::Format& endpoint_format : cfg.formats) {
if (format.valid_bit_depth != endpoint_format.config.valid_bits_per_sample) {
continue;
}
*out_blob = endpoint_format.capabilities.data();
*out_size = endpoint_format.capabilities.size();
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
StatusOr<std::vector<uint8_t>> GetI2SModuleConfig(const Nhlt& nhlt, uint8_t i2s_instance_id,
uint8_t direction, const CopierCfg& base_cfg) {
const void* blob;
size_t blob_size;
zx_status_t st = GetI2SBlob(
nhlt, i2s_instance_id, direction,
(direction == NHLT_DIRECTION_RENDER) ? base_cfg.out_fmt : base_cfg.base_cfg.audio_fmt, &blob,
&blob_size);
if (st != ZX_OK) {
return Status(st);
}
// Copy the I2S config blob
size_t cfg_size = sizeof(base_cfg) + blob_size;
ZX_DEBUG_ASSERT(cfg_size <= UINT16_MAX);
fbl::AllocChecker ac;
std::unique_ptr<uint8_t[]> cfg_buf(new (&ac) uint8_t[cfg_size]);
if (!ac.check()) {
return Status(ZX_ERR_NO_MEMORY,
"out of memory while attempting to allocate copier config buffer");
}
memcpy(cfg_buf.get() + sizeof(base_cfg), blob, blob_size);
// Copy the copier config
memcpy(cfg_buf.get(), &base_cfg, sizeof(base_cfg));
auto copier_cfg = reinterpret_cast<CopierCfg*>(cfg_buf.get());
copier_cfg->gtw_cfg.config_length = static_cast<uint32_t>(blob_size);
return RawBytesOf(cfg_buf.get(), cfg_size);
}
// Create a pieline transferring data from the host to an I2S bus.
//
// The I2S device must be present in the given NHLT table.
StatusOr<DspPipelineId> ConnectHostToI2S(const Nhlt& nhlt, DspModuleController* controller,
uint16_t copier_module_id, uint32_t host_gateway_id,
uint32_t i2s_gateway_id, uint8_t i2s_bus,
const AudioDataFormat& i2s_format) {
CopierCfg host_out_copier = CreateGatewayCopierCfg(kHostFormat, kDspFormat, host_gateway_id);
CopierCfg i2s_out_copier = CreateGatewayCopierCfg(kDspFormat, i2s_format, i2s_gateway_id);
StatusOr<std::vector<uint8_t>> i2s_out_gateway_cfg =
GetI2SModuleConfig(nhlt, i2s_bus, NHLT_DIRECTION_RENDER, i2s_out_copier);
if (!i2s_out_gateway_cfg.ok()) {
return i2s_out_gateway_cfg.status();
}
return CreateSimplePipeline(controller,
{
// Copy from host DMA.
{copier_module_id, RawBytesOf(&host_out_copier)},
// Copy to I2S.
{copier_module_id, i2s_out_gateway_cfg.ConsumeValueOrDie()},
});
}
// Create a pieline transferring data from the I2S bus to the host.
//
// The I2S device must be present in the given NHLT table.
StatusOr<DspPipelineId> ConnectI2SToHost(const Nhlt& nhlt, DspModuleController* controller,
uint16_t copier_module_id, uint32_t i2s_gateway_id,
uint8_t i2s_bus, uint32_t host_gateway_id,
const AudioDataFormat& i2s_format) {
CopierCfg i2s_in_copier = CreateGatewayCopierCfg(i2s_format, kDspFormat, i2s_gateway_id);
CopierCfg host_in_copier = CreateGatewayCopierCfg(kDspFormat, kHostFormat, host_gateway_id);
StatusOr<std::vector<uint8_t>> i2s_in_gateway_cfg =
GetI2SModuleConfig(nhlt, i2s_bus, NHLT_DIRECTION_CAPTURE, i2s_in_copier);
if (!i2s_in_gateway_cfg.ok()) {
return i2s_in_gateway_cfg.status();
}
return CreateSimplePipeline(controller,
{
// Copy from I2S.
{copier_module_id, i2s_in_gateway_cfg.ConsumeValueOrDie()},
// Copy to host DMA.
{copier_module_id, RawBytesOf(&host_in_copier)},
});
}
// Get the module ID corresponding of the given module name.
StatusOr<uint16_t> GetModuleId(DspModuleController* controller, const char* name) {
// Read available modules.
StatusOr<std::map<fbl::String, std::unique_ptr<ModuleEntry>>> modules_or_err =
controller->ReadModuleDetails();
if (!modules_or_err.ok()) {
return modules_or_err.status();
}
auto& modules = modules_or_err.ValueOrDie();
// Fetch out the copier module.
auto copier_it = modules.find(name);
if (copier_it == modules.end()) {
return Status(ZX_ERR_NOT_FOUND,
fbl::StringPrintf("DSP doesn't have support for module '%s'", name));
}
return copier_it->second->module_id;
}
// Set up the DSP to handle the Pixelbook Eve's topology.
struct PixelbookEvePipelines {
DspPipelineId speakers;
DspPipelineId inbuilt_microphone;
DspPipelineId headphone;
};
StatusOr<PixelbookEvePipelines> SetUpPixelbookEvePipelines(const Nhlt& nhlt,
DspModuleController* controller) {
// Get the ID of the "COPIER" module.
StatusOr<uint16_t> copier_module_id_or_err = GetModuleId(controller, "COPIER");
if (!copier_module_id_or_err.ok()) {
return copier_module_id_or_err.status();
}
uint16_t copier_module_id = copier_module_id_or_err.ValueOrDie();
// Create output pipeline to MAX98927 codec.
StatusOr<DspPipelineId> speakers = ConnectHostToI2S(
nhlt, controller, copier_module_id, HDA_GATEWAY_CFG_NODE_ID(DMA_TYPE_HDA_HOST_OUTPUT, 0),
I2S_GATEWAY_CFG_NODE_ID(DMA_TYPE_I2S_LINK_OUTPUT, I2S0_BUS, 0), I2S0_BUS, kFormatMax98927);
if (!speakers.ok()) {
return PrependMessage("Could not set up route to MAX98927 codec", speakers.status());
}
// Create output pipeline to ALC5663 codec.
StatusOr<DspPipelineId> headphones = ConnectHostToI2S(
nhlt, controller, copier_module_id, HDA_GATEWAY_CFG_NODE_ID(DMA_TYPE_HDA_HOST_OUTPUT, 1),
I2S_GATEWAY_CFG_NODE_ID(DMA_TYPE_I2S_LINK_OUTPUT, I2S1_BUS, 0), I2S1_BUS, kFormatAlc5663);
if (!headphones.ok()) {
return PrependMessage("Could not set up route to ALC5663 codec", headphones.status());
}
// Create input pipeline from DMIC.
StatusOr<DspPipelineId> inbuilt_microphone =
ConnectI2SToHost(nhlt, controller, copier_module_id,
I2S_GATEWAY_CFG_NODE_ID(DMA_TYPE_I2S_LINK_INPUT, I2S0_BUS, 0), I2S0_BUS,
HDA_GATEWAY_CFG_NODE_ID(DMA_TYPE_HDA_HOST_INPUT, 0), kFormatDmic);
if (!inbuilt_microphone.ok()) {
return PrependMessage("Could not set up route from DMIC", inbuilt_microphone.status());
}
PixelbookEvePipelines result = {};
result.inbuilt_microphone = inbuilt_microphone.ValueOrDie();
result.speakers = speakers.ValueOrDie();
result.headphone = headphones.ValueOrDie();
return result;
}
} // namespace
Status IntelDsp::StartPipeline(DspPipeline pipeline) {
// Pipeline must be paused before starting.
if (Status status =
module_controller_->SetPipelineState(pipeline.id, PipelineState::PAUSED, true);
!status.ok()) {
return status;
}
// Start the pipeline.
if (Status status =
module_controller_->SetPipelineState(pipeline.id, PipelineState::RUNNING, true);
!status.ok()) {
return status;
}
return OkStatus();
}
Status IntelDsp::PausePipeline(DspPipeline pipeline) {
if (Status status =
module_controller_->SetPipelineState(pipeline.id, PipelineState::PAUSED, true);
!status.ok()) {
return status;
}
if (Status status = module_controller_->SetPipelineState(pipeline.id, PipelineState::RESET, true);
!status.ok()) {
return status;
}
return OkStatus();
}
zx_status_t IntelDsp::CreateAndStartStreams() {
zx_status_t res = ZX_OK;
// Setup the pipelines.
StatusOr<PixelbookEvePipelines> pipelines =
SetUpPixelbookEvePipelines(*nhlt_, module_controller_.get());
if (!pipelines.ok()) {
LOG(ERROR, "Failed to set up DSP pipelines: %s\n", pipelines.status().ToString().c_str());
return pipelines.status().code();
}
// Create and publish the streams we will use.
static struct {
uint32_t stream_id;
bool is_input;
DspPipeline pipeline;
audio_stream_unique_id_t uid;
fbl::String name;
} STREAMS[] = {
// Speakers
{
.stream_id = 1,
.is_input = false,
.pipeline = {pipelines.ValueOrDie().speakers},
.uid = AUDIO_STREAM_UNIQUE_ID_BUILTIN_SPEAKERS,
.name = "Builtin Speakers",
},
// DMIC
{
.stream_id = 2,
.is_input = true,
.pipeline = {pipelines.ValueOrDie().inbuilt_microphone},
.uid = AUDIO_STREAM_UNIQUE_ID_BUILTIN_MICROPHONE,
.name = "Builtin Microphone",
},
// Headphones
{
.stream_id = 3,
.is_input = false,
.pipeline = {pipelines.ValueOrDie().headphone},
.uid = AUDIO_STREAM_UNIQUE_ID_BUILTIN_HEADPHONE_JACK,
.name = "Builtin Headphone Jack",
},
};
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.name, &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