// 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 #include #include #include #include #include #include #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(®s_->stream.ctl_sts.w)); LOG(INFO, " LPIB=0x%08x\n", REG_RD(®s_->stream.lpib)); LOG(INFO, " CBL=0x%08x\n", REG_RD(®s_->stream.cbl)); LOG(INFO, " LVI=0x%04x\n", REG_RD(®s_->stream.lvi)); LOG(INFO, " FIFOD=0x%04x\n", REG_RD(®s_->stream.fifod)); LOG(INFO, " FMT=0x%04x\n", REG_RD(®s_->stream.fmt)); LOG(INFO, " BDPL=0x%08x\n", REG_RD(®s_->stream.bdpl)); LOG(INFO, " BDPU=0x%08x\n", REG_RD(®s_->stream.bdpu)); LOG(INFO, " SPBFCH=0x%08x\n", REG_RD(®s_->spbfch)); LOG(INFO, "SPBFCTL=0x%08x\n", REG_RD(®s_->spbfctl)); LOG(INFO, " SPIB=0x%08x\n", REG_RD(®s_->spib)); } IntelDspCodeLoader::IntelDspCodeLoader(adsp_code_loader_registers_t* regs, const fbl::RefPtr& 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(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(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 > fbl::numeric_limits::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(r.size) : static_cast(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(®s_->stream.ctl_sts.w, ctl_val); REG_WR(®s_->stream.bdpl, static_cast(bdl_phys & 0xFFFFFFFFu)); REG_WR(®s_->stream.bdpu, static_cast((bdl_phys >> 32) & 0xFFFFFFFFu)); REG_WR(®s_->stream.cbl, fw_size); REG_WR(®s_->stream.lvi, region_count - 1); REG_WR(®s_->spbfctl, ADSP_REG_CL_SPBFCTL_SPIBE); REG_WR(®s_->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(®s_->stream.ctl_sts.w, SET); hw_wmb(); return ZX_OK; } void IntelDspCodeLoader::StopTransfer() { REG_CLR_BITS(®s_->stream.ctl_sts.w, HDA_SD_REG_CTRL_RUN); } } // namespace intel_hda } // namespace audio