| // 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(®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<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(®s_->stream.ctl_sts.w, ctl_val); |
| REG_WR(®s_->stream.bdpl, static_cast<uint32_t>(bdl_phys & 0xFFFFFFFFu)); |
| REG_WR(®s_->stream.bdpu, static_cast<uint32_t>((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 |