// Copyright 2017 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 "binding.h" #include "debug-logging.h" #include "intel-hda-controller.h" #include "intel-hda-dsp.h" #include "intel-hda-stream.h" #include "utils.h" namespace audio { namespace intel_hda { namespace { static constexpr zx_duration_t INTEL_HDA_RESET_HOLD_TIME_NSEC = ZX_USEC(100); // Section 5.5.1.2 static constexpr zx_duration_t INTEL_HDA_RESET_TIMEOUT_NSEC = ZX_MSEC(1); // 1mS Arbitrary static constexpr zx_duration_t INTEL_HDA_RING_BUF_RESET_TIMEOUT_NSEC = ZX_MSEC(1); // 1mS Arbitrary static constexpr zx_duration_t INTEL_HDA_RESET_POLL_TIMEOUT_NSEC = ZX_USEC(10); // 10uS Arbitrary static constexpr zx_duration_t INTEL_HDA_CODEC_DISCOVERY_WAIT_NSEC = ZX_USEC(521); // Section 4.3 static constexpr unsigned int MAX_CAPS = 10; // Arbitrary number of capabilities to check } // anon namespace zx_status_t IntelHDAController::ResetControllerHW() { zx_status_t res; // Are we currently being held in reset? If not, try to make sure that all // of our DMA streams are stopped and have been reset (but are not being // held in reset) before cycling the controller. Anecdotally, holding a // stream in reset while attempting to reset the controller on some Skylake // hardware has caused some pretty profound hardware lockups which require // fully removing power (warm reboot == not good enough) to recover from. if (REG_RD(®s()->gctl) & HDA_REG_GCTL_HWINIT) { // Explicitly disable all top level interrupt sources. REG_WR(®s()->intctl, 0u); hw_mb(); // Count the number of streams present in the hardware and // unconditionally stop and reset all of them. uint16_t gcap = REG_RD(®s()->gcap); unsigned int total_stream_cnt = HDA_REG_GCAP_ISS(gcap) + HDA_REG_GCAP_OSS(gcap) + HDA_REG_GCAP_BSS(gcap); if (total_stream_cnt > countof(regs()->stream_desc)) { LOG(ERROR, "Fatal error during reset! Controller reports more streams (%u) " "than should be possible for IHDA hardware. (GCAP = 0x%04hx)\n", total_stream_cnt, gcap); return ZX_ERR_INTERNAL; } hda_stream_desc_regs_t* sregs = regs()->stream_desc; for (uint32_t i = 0; i < total_stream_cnt; ++i) { IntelHDAStream::Reset(sregs + i); } // Explicitly shut down any CORB/RIRB DMA REG_WR(®s()->corbctl, 0u); REG_WR(®s()->rirbctl, 0u); } // Assert the reset signal and wait for the controller to ack. REG_CLR_BITS(®s()->gctl, HDA_REG_GCTL_HWINIT); hw_mb(); res = WaitCondition(INTEL_HDA_RESET_TIMEOUT_NSEC, INTEL_HDA_RESET_POLL_TIMEOUT_NSEC, [this]() -> bool { return (REG_RD(®s()->gctl) & HDA_REG_GCTL_HWINIT) == 0; }); if (res != ZX_OK) { LOG(ERROR, "Error attempting to enter reset! (res %d)\n", res); return res; } // Wait the spec mandated hold time. zx_nanosleep(zx_deadline_after(INTEL_HDA_RESET_HOLD_TIME_NSEC)); // Deassert the reset signal and wait for the controller to ack. REG_SET_BITS(®s()->gctl, HDA_REG_GCTL_HWINIT); hw_mb(); res = WaitCondition(INTEL_HDA_RESET_TIMEOUT_NSEC, INTEL_HDA_RESET_POLL_TIMEOUT_NSEC, [this]() -> bool { return (REG_RD(®s()->gctl) & HDA_REG_GCTL_HWINIT) != 0; }); if (res != ZX_OK) { LOG(ERROR, "Error attempting to leave reset! (res %d)\n", res); return res; } // Wait the spec mandated discovery time. zx_nanosleep(zx_deadline_after(INTEL_HDA_CODEC_DISCOVERY_WAIT_NSEC)); return res; } zx_status_t IntelHDAController::ResetCORBRdPtrLocked() { zx_status_t res; /* Set the reset bit, then wait for ack from the HW. See Section 3.3.21 */ REG_WR(®s()->corbrp, HDA_REG_CORBRP_RST); hw_mb(); if ((res = WaitCondition(INTEL_HDA_RING_BUF_RESET_TIMEOUT_NSEC, INTEL_HDA_RESET_POLL_TIMEOUT_NSEC, [this]() -> bool { return (REG_RD(®s()->corbrp) & HDA_REG_CORBRP_RST) != 0; })) != ZX_OK) { return res; } /* Clear the reset bit, then wait for ack */ REG_WR(®s()->corbrp, 0u); hw_mb(); if ((res = WaitCondition(INTEL_HDA_RING_BUF_RESET_TIMEOUT_NSEC, INTEL_HDA_RESET_POLL_TIMEOUT_NSEC, [this]() -> bool { return (REG_RD(®s()->corbrp) & HDA_REG_CORBRP_RST) == 0; })) != ZX_OK) { return res; } return ZX_OK; } zx_status_t IntelHDAController::SetupPCIDevice(zx_device_t* pci_dev) { zx_status_t res; if (pci_dev == nullptr) return ZX_ERR_INVALID_ARGS; // Have we already been set up? if (pci_dev_ != nullptr) { LOG(ERROR, "Device already initialized!\n"); return ZX_ERR_BAD_STATE; } ZX_DEBUG_ASSERT(irq_ != nullptr); ZX_DEBUG_ASSERT(mapped_regs_.start() == nullptr); ZX_DEBUG_ASSERT(pci_.ops == nullptr); pci_dev_ = pci_dev; // The device had better be a PCI device, or we are very confused. res = device_get_protocol(pci_dev_, ZX_PROTOCOL_PCI, reinterpret_cast(&pci_)); if (res != ZX_OK) { LOG(ERROR, "PCI device does not support PCI protocol! (res %d)\n", res); return res; } // Fetch our device info and use it to re-generate our debug tag once we // know our BDF address. ZX_DEBUG_ASSERT(pci_.ops != nullptr); res = pci_get_device_info(&pci_, &pci_dev_info_); if (res != ZX_OK) { LOG(ERROR, "Failed to fetch basic PCI device info! (res %d)\n", res); return res; } snprintf(log_prefix_, sizeof(log_prefix_), "IHDA Controller %02x:%02x.%01x", pci_dev_info_.bus_id, pci_dev_info_.dev_id, pci_dev_info_.func_id); // Fetch a handle to our bus transaction initiator and stash it in a ref // counted object (so we can manage the lifecycle as we share the handle // with various pinned VMOs we need to grant the controller BTI access to). zx::bti pci_bti; res = pci_get_bti(&pci_, 0, pci_bti.reset_and_get_address()); if (res != ZX_OK) { LOG(ERROR, "Failed to get BTI handle for IHDA Controller (res %d)\n", res); return res; } pci_bti_ = RefCountedBti::Create(fbl::move(pci_bti)); if (pci_bti_ == nullptr) { LOG(ERROR, "Out of memory while attempting to allocate BTI wrapper for IHDA Controller\n"); return ZX_ERR_NO_MEMORY; } // Fetch the BAR which holds our main registers, then sanity check the type // and size. zx_pci_bar_t bar_info; res = pci_get_bar(&pci_, 0u, &bar_info); if (res != ZX_OK) { LOG(ERROR, "Error attempting to fetch registers from PCI (res %d)\n", res); return res; } if (bar_info.type != ZX_PCI_BAR_TYPE_MMIO) { LOG(ERROR, "Bad register window type (expected %u got %u)\n", ZX_PCI_BAR_TYPE_MMIO, bar_info.type); return ZX_ERR_INTERNAL; } // We should have a valid handle now, make sure we don't leak it. zx::vmo bar_vmo(bar_info.handle); if (bar_info.size != sizeof(hda_all_registers_t)) { LOG(ERROR, "Bad register window size (expected 0x%zx got 0x%zx)\n", sizeof(hda_all_registers_t), bar_info.size); return ZX_ERR_INTERNAL; } // 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_PERM_READ | ZX_VM_PERM_WRITE; res = mapped_regs_.Map(bar_vmo, 0, bar_info.size, CPU_MAP_FLAGS, DriverVmars::registers()); if (res != ZX_OK) { LOG(ERROR, "Error attempting to map registers (res %d)\n", res); return res; } return ZX_OK; } zx_status_t IntelHDAController::SetupPCIInterrupts() { ZX_DEBUG_ASSERT(pci_dev_ != nullptr); // Make absolutely sure that IRQs are disabled at the controller level // before proceeding. REG_WR(®s()->intctl, 0u); // Configure our IRQ mode and map our IRQ handle. Try to use MSI, but if // that fails, fall back on legacy IRQs. zx_status_t res = pci_set_irq_mode(&pci_, ZX_PCIE_IRQ_MODE_MSI, 1); if (res != ZX_OK) { res = pci_set_irq_mode(&pci_, ZX_PCIE_IRQ_MODE_LEGACY, 1); if (res != ZX_OK) { LOG(ERROR, "Failed to set IRQ mode (%d)!\n", res); return res; } else { LOG(ERROR, "Falling back on legacy IRQ mode!\n"); } } // Retrieve our PCI interrupt, then use it to activate our IRQ dispatcher. zx::interrupt irq; res = pci_map_interrupt(&pci_, 0, irq.reset_and_get_address()); if (res != ZX_OK) { LOG(ERROR, "Failed to map IRQ! (res %d)\n", res); return res; } ZX_DEBUG_ASSERT(irq_ != nullptr); auto irq_handler = [controller = fbl::WrapRefPtr(this)] (const dispatcher::Interrupt* irq, zx_time_t timestamp) -> zx_status_t { OBTAIN_EXECUTION_DOMAIN_TOKEN(t, controller->default_domain_); LOG_EX(SPEW, *controller, "Hard IRQ (ts = %lu)\n", timestamp); return controller->HandleIrq(); }; res = irq_->Activate(default_domain_, fbl::move(irq), fbl::move(irq_handler)); if (res != ZX_OK) { LOG(ERROR, "Failed to activate IRQ dispatcher! (res %d)\n", res); return res; } // Enable Bus Mastering so we can DMA data and receive MSIs res = pci_enable_bus_master(&pci_, true); if (res != ZX_OK) { LOG(ERROR, "Failed to enable PCI bus mastering!\n"); return res; } return ZX_OK; } zx_status_t IntelHDAController::SetupStreamDescriptors() { fbl::AutoLock stream_pool_lock(&stream_pool_lock_); // Sanity check our stream counts. uint16_t gcap; unsigned int input_stream_cnt, output_stream_cnt, bidir_stream_cnt, total_stream_cnt; gcap = REG_RD(®s()->gcap); input_stream_cnt = HDA_REG_GCAP_ISS(gcap); output_stream_cnt = HDA_REG_GCAP_OSS(gcap); bidir_stream_cnt = HDA_REG_GCAP_BSS(gcap); total_stream_cnt = input_stream_cnt + output_stream_cnt + bidir_stream_cnt; static_assert(MAX_STREAMS_PER_CONTROLLER == countof(regs()->stream_desc), "Max stream count mismatch!"); if (!total_stream_cnt || (total_stream_cnt > countof(regs()->stream_desc))) { LOG(ERROR, "Invalid stream counts in GCAP register (In %u Out %u Bidir %u; Max %zu)\n", input_stream_cnt, output_stream_cnt, bidir_stream_cnt, countof(regs()->stream_desc)); return ZX_ERR_INTERNAL; } // Allocate our stream descriptors and populate our free lists. for (uint32_t i = 0; i < total_stream_cnt; ++i) { uint16_t stream_id = static_cast(i + 1); auto type = (i < input_stream_cnt) ? IntelHDAStream::Type::INPUT : ((i < input_stream_cnt + output_stream_cnt) ? IntelHDAStream::Type::OUTPUT : IntelHDAStream::Type::BIDIR); auto stream = IntelHDAStream::Create(type, stream_id, ®s()->stream_desc[i], pci_bti_); if (stream == nullptr) { LOG(ERROR, "Failed to create HDA stream context %u/%u\n", i, total_stream_cnt); return ZX_ERR_NO_MEMORY; } ZX_DEBUG_ASSERT(i < countof(all_streams_)); ZX_DEBUG_ASSERT(all_streams_[i] == nullptr); all_streams_[i] = stream; ReturnStreamLocked(fbl::move(stream)); } return ZX_OK; } zx_status_t IntelHDAController::SetupCommandBufferSize(uint8_t* size_reg, unsigned int* entry_count) { // Note: this method takes advantage of the fact that the TX and RX ring // buffer size register bitfield definitions are identical. uint8_t tmp = REG_RD(size_reg); uint8_t cmd; if (tmp & HDA_REG_CORBSIZE_CAP_256ENT) { *entry_count = 256; cmd = HDA_REG_CORBSIZE_CFG_256ENT; } else if (tmp & HDA_REG_CORBSIZE_CAP_16ENT) { *entry_count = 16; cmd = HDA_REG_CORBSIZE_CFG_16ENT; } else if (tmp & HDA_REG_CORBSIZE_CAP_2ENT) { *entry_count = 2; cmd = HDA_REG_CORBSIZE_CFG_2ENT; } else { LOG(ERROR, "Invalid ring buffer capabilities! (0x%02x)\n", tmp); return ZX_ERR_BAD_STATE; } REG_WR(size_reg, cmd); return ZX_OK; } zx_status_t IntelHDAController::SetupCommandBuffer() { fbl::AutoLock corb_lock(&corb_lock_); fbl::AutoLock rirb_lock(&rirb_lock_); zx_status_t res; // Allocate our command buffer memory and map it into our address space. // Even the largest buffers permissible should fit within a single 4k page. zx::vmo cmd_buf_vmo; constexpr uint32_t CPU_MAP_FLAGS = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE; static_assert(PAGE_SIZE >= (HDA_CORB_MAX_BYTES + HDA_RIRB_MAX_BYTES), "PAGE_SIZE to small to hold CORB and RIRB buffers!"); res = cmd_buf_cpu_mem_.CreateAndMap(PAGE_SIZE, CPU_MAP_FLAGS, DriverVmars::registers(), &cmd_buf_vmo, ZX_RIGHT_SAME_RIGHTS, ZX_CACHE_POLICY_UNCACHED_DEVICE); if (res != ZX_OK) { LOG(ERROR, "Failed to create and map %u bytes for CORB/RIRB command buffers! (res %d)\n", PAGE_SIZE, res); return res; } // Pin this VMO and grant the controller access to it. The controller will // need read/write access as this page contains both the command and // response buffers. // // TODO(johngro): If we (someday) decide that we need more isolation, we // should split this allocation so that there is a dedicated page for the // command buffer separate from the response buffer. The controller should // never have a reason it needs to write to the command buffer, but it would // need its own page if we wanted to control the access at an IOMMU level. constexpr uint32_t HDA_MAP_FLAGS = ZX_BTI_PERM_READ | ZX_BTI_PERM_WRITE; res = cmd_buf_hda_mem_.Pin(cmd_buf_vmo, pci_bti_->initiator(), HDA_MAP_FLAGS); if (res != ZX_OK) { LOG(ERROR, "Failed to pin pages for CORB/RIRB command buffers! (res %d)\n", res); return res; } // Start by making sure that the output and response ring buffers are being // held in the stopped state REG_WR(®s()->corbctl, 0u); REG_WR(®s()->rirbctl, 0u); // Reset the read and write pointers for both ring buffers REG_WR(®s()->corbwp, 0u); res = ResetCORBRdPtrLocked(); if (res != ZX_OK) return res; // Note; the HW does not expose a Response Input Ring Buffer Read Pointer, // we have to maintain our own. rirb_rd_ptr_ = 0; REG_WR(®s()->rirbwp, HDA_REG_RIRBWP_RST); // Physical memory for the CORB/RIRB should already have been allocated at // this point ZX_DEBUG_ASSERT(cmd_buf_cpu_mem_.start() != 0); // Determine the ring buffer sizes. If there are options, make them as // large as possible. res = SetupCommandBufferSize(®s()->corbsize, &corb_entry_count_); if (res != ZX_OK) return res; res = SetupCommandBufferSize(®s()->rirbsize, &rirb_entry_count_); if (res != ZX_OK) return res; // Stash these so we don't have to constantly recalculate then corb_mask_ = corb_entry_count_ - 1u; rirb_mask_ = rirb_entry_count_ - 1u; corb_max_in_flight_ = rirb_mask_ > RIRB_RESERVED_RESPONSE_SLOTS ? rirb_mask_ - RIRB_RESERVED_RESPONSE_SLOTS : 1; corb_max_in_flight_ = fbl::min(corb_max_in_flight_, corb_mask_); // Program the base address registers for the TX/RX ring buffers, and set up // the virtual pointers to the ring buffer entries. const auto& region = cmd_buf_hda_mem_.region(0); uint64_t cmd_buf_paddr64 = static_cast(region.phys_addr); // TODO(johngro) : If the controller does not support 64 bit phys // addressing, we need to make sure to get a page from low memory to use for // our command buffers. bool gcap_64bit_ok = HDA_REG_GCAP_64OK(REG_RD(®s()->gcap)); if ((cmd_buf_paddr64 >> 32) && !gcap_64bit_ok) { LOG(ERROR, "Intel HDA controller does not support 64-bit physical addressing!\n"); return ZX_ERR_NOT_SUPPORTED; } // Section 4.4.1.1; corb ring buffer base address must be 128 byte aligned. ZX_DEBUG_ASSERT(!(cmd_buf_paddr64 & 0x7F)); auto cmd_buf_start = reinterpret_cast(cmd_buf_cpu_mem_.start()); REG_WR(®s()->corblbase, ((uint32_t)(cmd_buf_paddr64 & 0xFFFFFFFF))); REG_WR(®s()->corbubase, ((uint32_t)(cmd_buf_paddr64 >> 32))); corb_ = reinterpret_cast(cmd_buf_start); cmd_buf_paddr64 += HDA_CORB_MAX_BYTES; // Section 4.4.2.2; rirb ring buffer base address must be 128 byte aligned. ZX_DEBUG_ASSERT(!(cmd_buf_paddr64 & 0x7F)); REG_WR(®s()->rirblbase, ((uint32_t)(cmd_buf_paddr64 & 0xFFFFFFFF))); REG_WR(®s()->rirbubase, ((uint32_t)(cmd_buf_paddr64 >> 32))); rirb_ = reinterpret_cast(cmd_buf_start + HDA_CORB_MAX_BYTES); // Make sure our current view of the space available in the CORB is up-to-date. ComputeCORBSpaceLocked(); // Set the response interrupt count threshold. The RIRB IRQ will fire any // time all of the SDATA_IN lines stop having codec responses to transmit, // or when RINTCNT responses have been received, whichever happens // first. We would like to batch up responses to minimize IRQ load, but we // also need to make sure to... // 1) Not configure the threshold to be larger than the available space in // the ring buffer. // 2) Reserve some space (if we can) at the end of the ring buffer so the // hardware has space to write while we are servicing our IRQ. If we // reserve no space, then the ring buffer is going to fill up and // potentially overflow before we can get in there and process responses. unsigned int thresh = rirb_entry_count_ - 1u; if (thresh > RIRB_RESERVED_RESPONSE_SLOTS) thresh -= RIRB_RESERVED_RESPONSE_SLOTS; ZX_DEBUG_ASSERT(thresh); REG_WR(®s()->rintcnt, thresh); // Clear out any lingering interrupt status REG_WR(®s()->corbsts, HDA_REG_CORBSTS_MEI); REG_WR(®s()->rirbsts, OR(HDA_REG_RIRBSTS_INTFL, HDA_REG_RIRBSTS_OIS)); // Enable the TX/RX IRQs and DMA engines. REG_WR(®s()->corbctl, OR(HDA_REG_CORBCTL_MEIE, HDA_REG_CORBCTL_DMA_EN)); REG_WR(®s()->rirbctl, OR(OR(HDA_REG_RIRBCTL_INTCTL, HDA_REG_RIRBCTL_DMA_EN), HDA_REG_RIRBCTL_OIC)); return ZX_OK; } void IntelHDAController::ProbeAudioDSP() { // This driver only supports the Audio DSP on Kabylake. if ((pci_dev_info_.vendor_id != INTEL_HDA_PCI_VID) || (pci_dev_info_.device_id != INTEL_HDA_PCI_DID_KABYLAKE)) { LOG(TRACE, "Audio DSP is not supported for device 0x%04x:0x%04x\n", pci_dev_info_.vendor_id, pci_dev_info_.device_id); return; } // Look for the processing pipe capability structure. Existence of this // structure means the Audio DSP is supported by the HW. uint32_t offset = REG_RD(®s()->llch); if ((offset == 0) || (offset >= mapped_regs_.size())) { LOG(TRACE, "Invalid LLCH offset to capability structures: 0x%08x\n", offset); return; } hda_pp_registers_t* pp_regs = nullptr; hda_pp_registers_t* found_regs = nullptr; uint8_t* regs_ptr = nullptr; unsigned int count = 0; uint32_t cap; do { regs_ptr = reinterpret_cast(regs()) + offset; pp_regs = reinterpret_cast(regs_ptr); cap = REG_RD(&pp_regs->ppch); if ((cap & HDA_CAP_ID_MASK) == HDA_CAP_PP_ID) { found_regs = pp_regs; break; } offset = cap & HDA_CAP_PTR_MASK; count += 1; } while ((count < MAX_CAPS) && (offset != 0)); if (found_regs == nullptr) { LOG(TRACE, "Pipe processing capability structure not found\n"); return; } dsp_ = IntelHDADSP::Create(*this, pp_regs, pci_bti_); } zx_status_t IntelHDAController::InitInternal(zx_device_t* pci_dev) { // TODO(johngro): see ZX-940; remove this priority boost when we can, and // when there is a better way of handling real time requirements. // // Right now, the interrupt handler runs in the same execution domain as all // of the other event sources managed by the HDA controller. If it is // configured to run and send DMA ring buffer notifications to the higher // level, the IRQ needs to be running at a boosted priority in order to have // a chance of meeting its real time deadlines. // // There is currently no terribly good way to control this dynamically, or // to apply this priority only to the interrupt event source and not others. // If it ever becomes a serious issue that the channel event handlers in // this system are running at boosted priority, we can come back here and // split the IRQ handler to run its own dedicated exeuction domain instead // of using the default domain. default_domain_ = dispatcher::ExecutionDomain::Create(24 /* HIGH_PRIORITY in LK */); if (default_domain_ == nullptr) { return ZX_ERR_NO_MEMORY; } irq_ = dispatcher::Interrupt::Create(); if (irq_ == nullptr) { return ZX_ERR_NO_MEMORY; } irq_wakeup_event_ = dispatcher::WakeupEvent::Create(); if (irq_wakeup_event_ == nullptr) { return ZX_ERR_NO_MEMORY; } zx_status_t res; res = irq_wakeup_event_->Activate( default_domain_, [controller = fbl::WrapRefPtr(this)](const dispatcher::WakeupEvent* evt) -> zx_status_t { OBTAIN_EXECUTION_DOMAIN_TOKEN(t, controller->default_domain_); LOG_EX(SPEW, *controller, "SW IRQ Wakeup\n"); return controller->HandleIrq(); }); if (res != ZX_OK) { return res; } res = SetupPCIDevice(pci_dev); if (res != ZX_OK) { return res; } // Check our hardware version uint8_t major, minor; major = REG_RD(®s()->vmaj); minor = REG_RD(®s()->vmin); if ((1 != major) || (0 != minor)) { LOG(ERROR, "Unexpected HW revision %d.%d!\n", major, minor); return ZX_ERR_NOT_SUPPORTED; } // Completely reset the hardware res = ResetControllerHW(); if (res != ZX_OK) return res; // Setup interrupts and enable bus mastering. res = SetupPCIInterrupts(); if (res != ZX_OK) return res; // Allocate and set up our stream descriptors. res = SetupStreamDescriptors(); if (res != ZX_OK) return res; // Allocate and set up the codec communication ring buffers (CORB/RIRB) res = SetupCommandBuffer(); if (res != ZX_OK) return res; // Generate a device name, initialize our device structure, and attempt to // publish our device. char dev_name[ZX_DEVICE_NAME_MAX] = { 0 }; snprintf(dev_name, sizeof(dev_name), "intel-hda-%03u", id()); device_add_args_t args = {}; args.version = DEVICE_ADD_ARGS_VERSION; args.name = dev_name; args.ctx = this; args.ops = &CONTROLLER_DEVICE_THUNKS; args.proto_id = ZX_PROTOCOL_IHDA; // Manually add a reference to this object. If we succeeded in publishing, // the DDK will be holding an unmanaged reference to us in our device's ctx // pointer. We will re-claim the reference when the DDK eventually calls // our Release hook. this->AddRef(); res = device_add(pci_dev_, &args, &dev_node_); if (res != ZX_OK) { // We failed to publish our device. Release the manual reference we // just added. __UNUSED bool should_destruct; should_destruct = this->Release(); ZX_DEBUG_ASSERT(!should_destruct); } else { // Flag the fact that we have entered the operating state. SetState(State::OPERATING); // Make sure that interrupts are completely disabled before proceeding. // If we have a unmasked, pending IRQ, we need to make sure that it // generates and interrupt once we have finished this interrupt // configuration. REG_WR(®s()->intctl, 0u); // Clear our STATESTS shadow, setup the WAKEEN register to wake us // up if there is any change to the codec enumeration status. This will // kick off the process of codec enumeration. REG_SET_BITS(®s()->wakeen, HDA_REG_STATESTS_MASK); // Allow unsolicited codec responses REG_SET_BITS(®s()->gctl, HDA_REG_GCTL_UNSOL); // Compute the set of interrupts we may be interested in during // operation, then enable those interrupts. uint32_t interesting_irqs = HDA_REG_INTCTL_GIE | HDA_REG_INTCTL_CIE; for (uint32_t i = 0; i < countof(all_streams_); ++i) { if (all_streams_[i] != nullptr) interesting_irqs |= HDA_REG_INTCTL_SIE(i); } REG_WR(®s()->intctl, interesting_irqs); // Probe for the Audio DSP. This is done after adding the HDA controller // device because the Audio DSP will be added a child to the HDA // controller and ddktl requires the parent device node to be initialized // at construction time. // No need to check for return value because the absence of the Audio // DSP is not a failure. // TODO(yky) Come up with a way to warn for the absence of Audio DSP // on platforms that require it. ProbeAudioDSP(); } return res; } zx_status_t IntelHDAController::Init(zx_device_t* pci_dev) { zx_status_t res = InitInternal(pci_dev); if (res != ZX_OK) { DeviceShutdown(); } return res; } } // namespace intel_hda } // namespace audio