blob: eb177c65dc9a064df72735370b0590dae80f3dbf [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 <climits>
#include <limits>
#include <stdio.h>
#include <string.h>
#include <zircon/assert.h>
#include <hw/arch_ops.h>
#include <zircon/process.h>
#include "debug-logging.h"
#include "intel-dsp-code-loader.h"
namespace audio {
namespace intel_hda {
namespace {
constexpr uint32_t ADSP_CLDMA_STREAM_TAG = 1;
constexpr uint32_t DMA_ALIGN = 128;
constexpr uint32_t DMA_ALIGN_MASK = DMA_ALIGN - 1;
} // anon namespace
void IntelDspCodeLoader::DumpRegisters() {
LOG(INFO, "CTL_STS=0x%08x\n", REG_RD(&regs_->stream.ctl_sts.w));
LOG(INFO, " LPIB=0x%08x\n", REG_RD(&regs_->stream.lpib));
LOG(INFO, " CBL=0x%08x\n", REG_RD(&regs_->stream.cbl));
LOG(INFO, " LVI=0x%04x\n", REG_RD(&regs_->stream.lvi));
LOG(INFO, " FIFOD=0x%04x\n", REG_RD(&regs_->stream.fifod));
LOG(INFO, " FMT=0x%04x\n", REG_RD(&regs_->stream.fmt));
LOG(INFO, " BDPL=0x%08x\n", REG_RD(&regs_->stream.bdpl));
LOG(INFO, " BDPU=0x%08x\n", REG_RD(&regs_->stream.bdpu));
LOG(INFO, " SPBFCH=0x%08x\n", REG_RD(&regs_->spbfch));
LOG(INFO, "SPBFCTL=0x%08x\n", REG_RD(&regs_->spbfctl));
LOG(INFO, " SPIB=0x%08x\n", REG_RD(&regs_->spib));
}
IntelDspCodeLoader::IntelDspCodeLoader(adsp_code_loader_registers_t* regs,
const fbl::RefPtr<RefCountedBti>& pci_bti)
: regs_(regs),
pci_bti_(pci_bti) {
snprintf(log_prefix_, sizeof(log_prefix_), "IHDA DSP Code Loader");
}
IntelDspCodeLoader::~IntelDspCodeLoader() {
}
zx_status_t IntelDspCodeLoader::Initialize() {
// BDL entries should be 16 bytes long, meaning that we should be able to
// fit 256 of them perfectly into a single 4k page.
constexpr size_t MAX_BDL_BYTES = sizeof(IntelHDABDLEntry) * MAX_BDL_LENGTH;
static_assert(MAX_BDL_BYTES <= PAGE_SIZE, "A max length BDL must fit inside a single page!");
// Create a VMO made of a single page and map it for read/write so the CPU
// has access to it.
constexpr uint32_t CPU_MAP_FLAGS = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
zx::vmo bdl_vmo;
zx_status_t res;
res = bdl_cpu_mem_.CreateAndMap(PAGE_SIZE,
CPU_MAP_FLAGS,
NULL,
&bdl_vmo,
ZX_RIGHT_SAME_RIGHTS,
ZX_CACHE_POLICY_UNCACHED_DEVICE);
if (res != ZX_OK) {
LOG(ERROR, "Failed to create and map %u bytes for code loader BDL (res %d)\n",
PAGE_SIZE, res);
return res;
}
// Pin this VMO and grant the controller access to it. The controller
// should only need read access to buffer descriptor lists.
constexpr uint32_t DSP_MAP_FLAGS = ZX_BTI_PERM_READ;
ZX_DEBUG_ASSERT(pci_bti_ != nullptr);
res = bdl_dsp_mem_.Pin(bdl_vmo, pci_bti_->initiator(), DSP_MAP_FLAGS);
if (res != ZX_OK) {
LOG(ERROR, "Failed to pin pages for code loader BDL (res %d)\n", res);
return res;
}
// Sanity checks. At this point, everything should be allocated, mapped,
// and should obey the alignment restrictions imposed by the HDA spec.
ZX_DEBUG_ASSERT(bdl_cpu_mem_.start() != 0);
ZX_DEBUG_ASSERT(!(reinterpret_cast<uintptr_t>(bdl_cpu_mem_.start()) & DMA_ALIGN_MASK));
ZX_DEBUG_ASSERT(bdl_dsp_mem_.region_count() == 1);
ZX_DEBUG_ASSERT(!(bdl_dsp_mem_.region(0).phys_addr & DMA_ALIGN_MASK));
return ZX_OK;
}
zx_status_t IntelDspCodeLoader::TransferFirmware(const fzl::PinnedVmo& pinned_fw,
size_t fw_size) {
uint32_t region_count = pinned_fw.region_count();
// Sanity checks that the firmware fits in the BDL.
ZX_DEBUG_ASSERT(region_count < MAX_BDL_LENGTH);
// Build BDL to transfer the firmware
IntelHDABDLEntry* bdl = reinterpret_cast<IntelHDABDLEntry*>(bdl_cpu_mem_.start());
size_t remaining = fw_size;
uint32_t num_region;
for (num_region = 0; (num_region < region_count) && (remaining > 0); num_region++) {
const auto& r = pinned_fw.region(num_region);
if (r.size > std::numeric_limits<uint32_t>::max()) {
LOG(ERROR, "VMO region too large (%" PRIu64 " bytes)\n", r.size);
return ZX_ERR_INTERNAL;
}
bdl[num_region].address = r.phys_addr;
bdl[num_region].length = (remaining > r.size) ? static_cast<uint32_t>(r.size) :
static_cast<uint32_t>(remaining);
remaining -= bdl[num_region].length;
}
// Interrupt on the last BDL entry
bdl[num_region - 1].flags = IntelHDABDLEntry::IOC_FLAG;
// Program DMA
const auto bdl_phys = bdl_dsp_mem_.region(0).phys_addr;
uint32_t ctl_val = HDA_SD_REG_CTRL_STRM_TAG(ADSP_CLDMA_STREAM_TAG) |
HDA_SD_REG_CTRL_STRIPE1;
REG_WR(&regs_->stream.ctl_sts.w, ctl_val);
REG_WR(&regs_->stream.bdpl, static_cast<uint32_t>(bdl_phys & 0xFFFFFFFFu));
REG_WR(&regs_->stream.bdpu, static_cast<uint32_t>((bdl_phys >> 32) & 0xFFFFFFFFu));
REG_WR(&regs_->stream.cbl, fw_size);
REG_WR(&regs_->stream.lvi, region_count - 1);
REG_WR(&regs_->spbfctl, ADSP_REG_CL_SPBFCTL_SPIBE);
REG_WR(&regs_->spib, fw_size);
hw_wmb();
// Start DMA
constexpr uint32_t SET = HDA_SD_REG_CTRL_RUN |
HDA_SD_REG_CTRL_IOCE |
HDA_SD_REG_CTRL_FEIE |
HDA_SD_REG_CTRL_DEIE |
HDA_SD_REG_STS32_ACK;
REG_SET_BITS(&regs_->stream.ctl_sts.w, SET);
hw_wmb();
return ZX_OK;
}
void IntelDspCodeLoader::StopTransfer() {
REG_CLR_BITS(&regs_->stream.ctl_sts.w, HDA_SD_REG_CTRL_RUN);
}
} // namespace intel_hda
} // namespace audio