blob: 1f9d8a1368ca7f72770b734db4d601c8cf5385f4 [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 <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <string.h>
#include "intel-audio-dsp.h"
#include "intel-dsp-code-loader.h"
namespace audio {
namespace intel_hda {
namespace {
// ADSP SRAM windows
constexpr size_t SKL_ADSP_SRAM0_OFFSET = 0x8000; // Shared between Skylake and Kabylake
constexpr size_t SKL_ADSP_SRAM1_OFFSET = 0xA000;
// Mailbox offsets
constexpr size_t ADSP_MAILBOX_IN_OFFSET = 0x1000; // Section 5.5 Offset from SRAM0
constexpr const char* ADSP_FIRMWARE_PATH = "/boot/lib/firmware/dsp_fw_kbl_v3266.bin";
constexpr zx_time_t INTEL_ADSP_TIMEOUT_NSEC = ZX_MSEC( 50); // 50mS Arbitrary
constexpr zx_time_t INTEL_ADSP_POLL_NSEC = ZX_USEC(500); // 500uS Arbitrary
constexpr zx_time_t INTEL_ADSP_ROM_INIT_TIMEOUT_NSEC = ZX_SEC ( 1); // 1S Arbitrary
constexpr zx_time_t INTEL_ADSP_BASE_FW_INIT_TIMEOUT_NSEC = ZX_SEC ( 3); // 3S Arbitrary
constexpr zx_time_t INTEL_ADSP_POLL_FW_NSEC = ZX_MSEC( 1); //.1mS Arbitrary
} // anon namespace
fbl::RefPtr<IntelAudioDsp> IntelAudioDsp::Create() {
fbl::AllocChecker ac;
auto ret = fbl::AdoptRef(new (&ac) IntelAudioDsp());
if (!ac.check()) {
GLOBAL_LOG(ERROR, "Out of memory attempting to allocate IHDA DSP\n");
return nullptr;
}
return ret;
}
IntelAudioDsp::IntelAudioDsp()
: ipc_(*this) {
for (auto& id : module_ids_) { id = MODULE_ID_INVALID; }
snprintf(log_prefix_, sizeof(log_prefix_), "IHDA DSP (unknown BDF)");
}
adsp_fw_registers_t* IntelAudioDsp::fw_regs() const {
return reinterpret_cast<adsp_fw_registers_t*>(static_cast<uint8_t*>(mapped_regs_.start()) +
SKL_ADSP_SRAM0_OFFSET);
}
zx_status_t IntelAudioDsp::DriverBind(zx_device_t* hda_dev) {
// IntelHDACodecDriverBase initialization. Do first so the parent reference is set.
zx_status_t res = Bind(hda_dev, "intel-sst-dsp");
res = SetupDspDevice();
if (res != ZX_OK) {
return res;
}
res = ParseNhlt();
if (res != ZX_OK) {
return res;
}
state_ = State::INITIALIZING;
// Perform hardware initializastion in a thread.
int c11_res = thrd_create(
&init_thread_,
[](void* ctx) -> int { return static_cast<IntelAudioDsp*>(ctx)->InitThread(); },
this);
if (c11_res < 0) {
LOG(ERROR, "Failed to create init thread (res = %d)\n", c11_res);
state_ = State::ERROR;
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
zx_status_t IntelAudioDsp::SetupDspDevice() {
zx_status_t res = device_get_protocol(codec_device(), ZX_PROTOCOL_IHDA_DSP,
reinterpret_cast<void*>(&ihda_dsp_));
if (res != ZX_OK) {
LOG(ERROR, "IHDA DSP device does not support IHDA DSP protocol (err %d)\n", res);
return res;
}
zx_pcie_device_info_t hda_dev_info;
ihda_dsp_get_dev_info(&ihda_dsp_, &hda_dev_info);
snprintf(log_prefix_, sizeof(log_prefix_), "IHDA DSP %02x:%02x.%01x",
hda_dev_info.bus_id,
hda_dev_info.dev_id,
hda_dev_info.func_id);
ipc_.SetLogPrefix(log_prefix_);
// Fetch the bar which holds the Audio DSP registers.
zx::vmo bar_vmo;
size_t bar_size;
res = ihda_dsp_get_mmio(&ihda_dsp_, bar_vmo.reset_and_get_address(), &bar_size);
if (res != ZX_OK) {
LOG(ERROR, "Failed to fetch DSP register VMO (err %u)\n", res);
return res;
}
if (bar_size != sizeof(adsp_registers_t)) {
LOG(ERROR, "Bad register window size (expected 0x%zx got 0x%zx)\n",
sizeof(adsp_registers_t), bar_size);
return res;
}
// Since this VMO provides access to our registers, make sure to set the
// cache policy to UNCACHED_DEVICE
res = bar_vmo.set_cache_policy(ZX_CACHE_POLICY_UNCACHED_DEVICE);
if (res != ZX_OK) {
LOG(ERROR, "Error attempting to set cache policy for PCI registers (res %d)\n", res);
return res;
}
// Map the VMO in, make sure to put it in the same VMAR as the rest of our
// registers.
constexpr uint32_t CPU_MAP_FLAGS = ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE;
res = mapped_regs_.Map(bar_vmo, 0, bar_size, CPU_MAP_FLAGS);
if (res != ZX_OK) {
LOG(ERROR, "Error attempting to map registers (res %d)\n", res);
return res;
}
// Initialize mailboxes
uint8_t* mapped_base = static_cast<uint8_t*>(mapped_regs_.start());
mailbox_in_.Initialize(static_cast<void*>(mapped_base + SKL_ADSP_SRAM0_OFFSET +
ADSP_MAILBOX_IN_OFFSET), MAILBOX_SIZE);
mailbox_out_.Initialize(static_cast<void*>(mapped_base + SKL_ADSP_SRAM1_OFFSET),
MAILBOX_SIZE);
// Get bus transaction initiator
zx::bti bti;
res = ihda_dsp_get_bti(&ihda_dsp_, bti.reset_and_get_address());
if (res != ZX_OK) {
LOG(ERROR, "Failed to get BTI handle for IHDA DSP (res %d)\n", res);
return res;
}
hda_bti_ = RefCountedBti::Create(fbl::move(bti));
if (hda_bti_ == nullptr) {
LOG(ERROR, "Out of memory while attempting to allocate BTI wrapper for IHDA DSP\n");
return ZX_ERR_NO_MEMORY;
}
// Set IRQ handler and enable HDA interrupt.
// Interrupts are still masked at the DSP level.
res = ihda_dsp_irq_enable(&ihda_dsp_,
[](void* cookie) {
auto thiz = static_cast<IntelAudioDsp*>(cookie);
thiz->ProcessIrq();
}, this);
if (res != ZX_OK) {
LOG(ERROR, "Failed to set DSP interrupt callback (res %d)\n", res);
return res;
}
return ZX_OK;
}
zx_status_t IntelAudioDsp::ParseNhlt() {
size_t size = 0;
zx_status_t res = device_get_metadata(codec_device(), MD_KEY_NHLT,
nhlt_buf_, sizeof(nhlt_buf_), &size);
if (res != ZX_OK) {
LOG(ERROR, "Failed to fetch NHLT (res %d)\n", res);
return res;
}
nhlt_table_t* nhlt = reinterpret_cast<nhlt_table_t*>(nhlt_buf_);
// Sanity check
if (size < sizeof(*nhlt)) {
LOG(ERROR, "NHLT too small (%zu bytes)\n", size);
return ZX_ERR_INTERNAL;
}
static_assert(sizeof(nhlt->header.signature) >= ACPI_NAME_SIZE);
static_assert(sizeof(ACPI_NHLT_SIGNATURE) >= ACPI_NAME_SIZE);
if (memcmp(nhlt->header.signature, ACPI_NHLT_SIGNATURE, ACPI_NAME_SIZE)) {
LOG(ERROR, "Invalid NHLT signature\n");
return ZX_ERR_INTERNAL;
}
uint8_t count = nhlt->endpoint_desc_count;
if (count > I2S_CONFIG_MAX) {
LOG(INFO, "Too many NHLT endpoints (max %zu, got %u), "
"only the first %zu will be processed\n",
I2S_CONFIG_MAX, count, I2S_CONFIG_MAX);
count = I2S_CONFIG_MAX;
}
// Extract the PCM formats and I2S config blob
size_t i = 0;
size_t desc_offset = reinterpret_cast<uint8_t*>(nhlt->endpoints) - nhlt_buf_;
while (count--) {
auto desc = reinterpret_cast<nhlt_descriptor_t*>(nhlt_buf_ + desc_offset);
// Sanity check
if ((desc_offset + desc->length) > size) {
LOG(ERROR, "NHLT endpoint descriptor out of bounds\n");
return ZX_ERR_INTERNAL;
}
size_t length = static_cast<size_t>(desc->length);
if (length < sizeof(*desc)) {
LOG(ERROR, "Short NHLT descriptor\n");
return ZX_ERR_INTERNAL;
}
length -= sizeof(*desc);
// Only care about SSP endpoints
if (desc->link_type != NHLT_LINK_TYPE_SSP) {
continue;
}
// Make sure there is enough room for formats_configs
if (length < desc->config.capabilities_size + sizeof(formats_config_t)) {
LOG(ERROR, "NHLT endpoint descriptor too short (specific_config too long)\n");
return ZX_ERR_INTERNAL;
}
length -= desc->config.capabilities_size + sizeof(formats_config_t);
// Must have at least one format
auto formats = reinterpret_cast<const formats_config_t*>(
nhlt_buf_ + desc_offset + sizeof(*desc) + desc->config.capabilities_size
);
if (formats->format_config_count == 0) {
continue;
}
// Iterate the formats and check lengths
const format_config_t* format = formats->format_configs;
for (uint8_t j = 0; j < formats->format_config_count; j++) {
size_t format_length = sizeof(*format) + format->config.capabilities_size;
if (length < format_length) {
LOG(ERROR, "Invalid NHLT endpoint desciptor format too short\n");
return ZX_ERR_INTERNAL;
}
length -= format_length;
format = reinterpret_cast<const format_config_t*>(
reinterpret_cast<const uint8_t*>(format) + format_length
);
}
if (length != 0) {
LOG(ERROR, "Invalid NHLT endpoint descriptor length\n");
return ZX_ERR_INTERNAL;
}
i2s_configs_[i++] = { desc->virtual_bus_id, desc->direction, formats };
desc_offset += desc->length;
}
LOG(INFO, "parse success, found %zu formats\n", i);
return ZX_OK;
}
void IntelAudioDsp::DeviceShutdown() {
if (state_ == State::INITIALIZING) {
thrd_join(init_thread_, NULL);
}
// Order is important below.
// Disable Audio DSP and interrupt
ihda_dsp_irq_disable(&ihda_dsp_);
ihda_dsp_disable(&ihda_dsp_);
// Reset and power down the DSP.
ResetCore(ADSP_REG_ADSPCS_CORE0_MASK);
PowerDownCore(ADSP_REG_ADSPCS_CORE0_MASK);
// Fail all pending IPCs
ipc_.Shutdown();
state_ = State::SHUT_DOWN;
}
int IntelAudioDsp::InitThread() {
zx_status_t st = ZX_OK;
auto cleanup = fbl::MakeAutoCall([this]() {
DeviceShutdown();
});
// Enable Audio DSP
ihda_dsp_enable(&ihda_dsp_);
// The HW loads the DSP base firmware from ROM during the initialization,
// when the Tensilica Core is out of reset, but halted.
st = Boot();
if (st != ZX_OK) {
LOG(ERROR, "Error in DSP boot (err %d)\n", st);
return -1;
}
// Wait for ROM initialization done
st = WaitCondition(INTEL_ADSP_ROM_INIT_TIMEOUT_NSEC,
INTEL_ADSP_POLL_FW_NSEC,
[this]() -> bool {
return ((REG_RD(&fw_regs()->fw_status) & ADSP_FW_STATUS_STATE_MASK) ==
ADSP_FW_STATUS_STATE_INITIALIZATION_DONE);
});
if (st != ZX_OK) {
LOG(ERROR, "Error waiting for DSP ROM init (err %d)\n", st);
return -1;
}
state_ = State::OPERATING;
EnableInterrupts();
// Load DSP Firmware
st = LoadFirmware();
if (st != ZX_OK) {
LOG(ERROR, "Error loading firmware (err %d)\n", st);
return -1;
}
// DSP Firmware is now ready.
LOG(INFO, "DSP firmware ready\n");
// Setup pipelines
st = GetModulesInfo();
if (st != ZX_OK) {
LOG(ERROR, "Error getting DSP modules info\n");
return -1;
}
st = SetupPipelines();
if (st != ZX_OK) {
LOG(ERROR, "Error initializing DSP pipelines\n");
return -1;
}
// Create and publish streams.
st = CreateAndStartStreams();
if (st != ZX_OK) {
LOG(ERROR, "Error starting DSP streams\n");
return -1;
}
cleanup.cancel();
return 0;
}
zx_status_t IntelAudioDsp::Boot() {
zx_status_t st = ZX_OK;
// Put core into reset
if ((st = ResetCore(ADSP_REG_ADSPCS_CORE0_MASK)) != ZX_OK) {
LOG(ERROR, "Error attempting to enter reset on core 0 (err %d)\n", st);
return st;
}
// Power down core
if ((st = PowerDownCore(ADSP_REG_ADSPCS_CORE0_MASK)) != ZX_OK) {
LOG(ERROR, "Error attempting to power down core 0 (err %d)\n", st);
return st;
}
// Power up core
if ((st = PowerUpCore(ADSP_REG_ADSPCS_CORE0_MASK)) != ZX_OK) {
LOG(ERROR, "Error attempting to power up core 0 (err %d)\n", st);
return st;
}
// Take core out of reset
if ((st = UnResetCore(ADSP_REG_ADSPCS_CORE0_MASK)) != ZX_OK) {
LOG(ERROR, "Error attempting to take core 0 out of reset (err %d)\n", st);
return st;
}
// Run core
RunCore(ADSP_REG_ADSPCS_CORE0_MASK);
if (!IsCoreEnabled(ADSP_REG_ADSPCS_CORE0_MASK)) {
LOG(ERROR, "Failed to start core 0\n");
ResetCore(ADSP_REG_ADSPCS_CORE0_MASK);
return st;
}
LOG(TRACE, "DSP core 0 booted!\n");
return ZX_OK;
}
zx_status_t IntelAudioDsp::GetModulesInfo() {
uint8_t data[MAILBOX_SIZE];
IntelDspIpc::Txn txn(nullptr, 0, data, sizeof(data));
ipc_.LargeConfigGet(&txn, 0, 0, to_underlying(BaseFWParamType::MODULES_INFO), sizeof(data));
if (txn.success()) {
auto info = reinterpret_cast<const ModulesInfo*>(txn.rx_data);
uint32_t count = info->module_count;
ZX_DEBUG_ASSERT(txn.rx_actual >= sizeof(ModulesInfo) + (count * sizeof(ModuleEntry)));
static constexpr const char* MODULE_NAMES[] = {
[COPIER] = "COPIER",
[MIXIN] = "MIXIN",
[MIXOUT] = "MIXOUT",
};
static_assert(countof(MODULE_NAMES) == countof(module_ids_), "invalid module id count\n");
for (uint32_t i = 0; i < count; i++) {
for (size_t j = 0; j < countof(MODULE_NAMES); j++) {
if (!strncmp(reinterpret_cast<const char*>(info->module_info[i].name),
MODULE_NAMES[j], strlen(MODULE_NAMES[j]))) {
if (module_ids_[j] == MODULE_ID_INVALID) {
module_ids_[j] = info->module_info[i].module_id;
} else {
LOG(ERROR, "Found duplicate module id %hu\n",
info->module_info[i].module_id);
}
}
}
}
}
return txn.success() ? ZX_OK : ZX_ERR_INTERNAL;
}
zx_status_t IntelAudioDsp::LoadFirmware() {
IntelDspCodeLoader loader(&regs()->cldma, hda_bti_);
zx_status_t st = loader.Initialize();
if (st != ZX_OK) {
LOG(ERROR, "Error initializing firmware code loader (err %d)\n", st);
return st;
}
// Get the VMO containing the firmware.
zx::vmo fw_vmo;
size_t fw_size;
st = load_firmware(codec_device(), ADSP_FIRMWARE_PATH, fw_vmo.reset_and_get_address(),
&fw_size);
if (st != ZX_OK) {
LOG(ERROR, "Error fetching firmware (err %d)\n", st);
return st;
}
// Transfer firmware to DSP
st = loader.TransferFirmware(fw_vmo, fw_size);
if (st != ZX_OK) {
return st;
}
// Wait for firwmare boot
st = WaitCondition(INTEL_ADSP_BASE_FW_INIT_TIMEOUT_NSEC,
INTEL_ADSP_POLL_FW_NSEC,
[this]() -> bool {
return ((REG_RD(&fw_regs()->fw_status) &
ADSP_FW_STATUS_STATE_MASK) ==
ADSP_FW_STATUS_STATE_ENTER_BASE_FW);
});
if (st != ZX_OK) {
LOG(ERROR, "Error waiting for DSP base firmware entry (err %d)\n", st);
return st;
}
return ZX_OK;
}
zx_status_t IntelAudioDsp::RunPipeline(uint8_t pipeline_id) {
// Pipeline must be paused before starting
zx_status_t st = ipc_.SetPipelineState(pipeline_id, PipelineState::PAUSED, true);
if (st != ZX_OK) {
return st;
}
return ipc_.SetPipelineState(pipeline_id, PipelineState::RUNNING, true);
}
bool IntelAudioDsp::IsCoreEnabled(uint8_t core_mask) {
uint32_t val = REG_RD(&regs()->adspcs);
bool enabled = (val & ADSP_REG_ADSPCS_CPA(core_mask)) &&
(val & ADSP_REG_ADSPCS_SPA(core_mask)) &&
!(val & ADSP_REG_ADSPCS_CSTALL(core_mask)) &&
!(val & ADSP_REG_ADSPCS_CRST(core_mask));
return enabled;
}
zx_status_t IntelAudioDsp::ResetCore(uint8_t core_mask) {
// Stall cores
REG_SET_BITS(&regs()->adspcs, ADSP_REG_ADSPCS_CSTALL(core_mask));
// Put cores in reset
REG_SET_BITS(&regs()->adspcs, ADSP_REG_ADSPCS_CRST(core_mask));
// Wait for success
return WaitCondition(INTEL_ADSP_TIMEOUT_NSEC,
INTEL_ADSP_POLL_NSEC,
[this, &core_mask]() -> bool {
return (REG_RD(&regs()->adspcs) &
ADSP_REG_ADSPCS_CRST(core_mask)) != 0;
});
}
zx_status_t IntelAudioDsp::UnResetCore(uint8_t core_mask) {
REG_CLR_BITS(&regs()->adspcs, ADSP_REG_ADSPCS_CRST(core_mask));
return WaitCondition(INTEL_ADSP_TIMEOUT_NSEC,
INTEL_ADSP_POLL_NSEC,
[this, &core_mask]() -> bool {
return (REG_RD(&regs()->adspcs) &
ADSP_REG_ADSPCS_CRST(core_mask)) == 0;
});
}
zx_status_t IntelAudioDsp::PowerDownCore(uint8_t core_mask) {
REG_CLR_BITS(&regs()->adspcs, ADSP_REG_ADSPCS_SPA(core_mask));
return WaitCondition(INTEL_ADSP_TIMEOUT_NSEC,
INTEL_ADSP_POLL_NSEC,
[this, &core_mask]() -> bool {
return (REG_RD(&regs()->adspcs) &
ADSP_REG_ADSPCS_SPA(core_mask)) == 0;
});
}
zx_status_t IntelAudioDsp::PowerUpCore(uint8_t core_mask) {
REG_SET_BITS(&regs()->adspcs, ADSP_REG_ADSPCS_SPA(core_mask));
return WaitCondition(INTEL_ADSP_TIMEOUT_NSEC,
INTEL_ADSP_POLL_NSEC,
[this, &core_mask]() -> bool {
return (REG_RD(&regs()->adspcs) & ADSP_REG_ADSPCS_SPA(core_mask)) != 0;
});
}
void IntelAudioDsp::RunCore(uint8_t core_mask) {
REG_CLR_BITS(&regs()->adspcs, ADSP_REG_ADSPCS_CSTALL(core_mask));
}
void IntelAudioDsp::EnableInterrupts() {
REG_SET_BITS(&regs()->adspic, ADSP_REG_ADSPIC_IPC);
REG_SET_BITS(&regs()->hipcctl, ADSP_REG_HIPCCTL_IPCTDIE | ADSP_REG_HIPCCTL_IPCTBIE);
}
void IntelAudioDsp::ProcessIrq() {
if (state_ != State::OPERATING) {
LOG(ERROR, "Got IRQ when device is not operating (state %u)\n", to_underlying(state_));
return;
}
IpcMessage message(REG_RD(&regs()->hipct), REG_RD(&regs()->hipcte));
if (message.primary & ADSP_REG_HIPCT_BUSY) {
// Process the incoming message
ipc_.ProcessIpc(message);
// Ack the IRQ after reading mailboxes.
REG_SET_BITS(&regs()->hipct, ADSP_REG_HIPCT_BUSY);
}
// Ack the IPC target done IRQ
uint32_t val = REG_RD(&regs()->hipcie);
if (val & ADSP_REG_HIPCIE_DONE) {
REG_WR(&regs()->hipcie, val);
}
}
} // namespace intel_hda
} // namespace audio
extern "C" {
zx_status_t ihda_dsp_init_hook(void** out_ctx) {
return ZX_OK;
}
zx_status_t ihda_dsp_bind_hook(void* ctx, zx_device_t* hda_dev) {
auto dev = ::audio::intel_hda::IntelAudioDsp::Create();
if (!dev) {
return ZX_ERR_NO_MEMORY;
}
zx_status_t st = dev->DriverBind(hda_dev);
if (st == ZX_OK) {
// devmgr is now in charge of the memory for dev
void* ptr __UNUSED = dev.leak_ref();
}
return st;
}
void ihda_dsp_release_hook(void* ctx) {
}
} // extern "C"