1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <zircon/syscalls.h>
6 
7 #include "intel-i915.h"
8 #include "interrupts.h"
9 #include "macros.h"
10 #include "registers.h"
11 
12 namespace {
irq_handler(void * arg)13 static int irq_handler(void* arg) {
14     return static_cast<i915::Interrupts*>(arg)->IrqLoop();
15 }
16 } // namespace
17 
18 namespace i915 {
19 
Interrupts()20 Interrupts::Interrupts() { }
21 
~Interrupts()22 Interrupts::~Interrupts() {
23     ZX_ASSERT(irq_ == ZX_HANDLE_INVALID);
24 }
25 
Destroy()26 void Interrupts::Destroy() {
27     if (irq_ != ZX_HANDLE_INVALID) {
28         zx_interrupt_destroy(irq_.get());
29         thrd_join(irq_thread_, nullptr);
30 
31         irq_.reset();
32     }
33 }
34 
IrqLoop()35 int Interrupts::IrqLoop() {
36     for (;;) {
37         zx_time_t timestamp;
38         if (zx_interrupt_wait(irq_.get(), &timestamp) != ZX_OK) {
39             LOG_INFO("interrupt wait failed\n");
40             break;
41         }
42         auto interrupt_ctrl =
43                 registers::MasterInterruptControl::Get().ReadFrom(controller_->mmio_space());
44         interrupt_ctrl.set_enable_mask(0);
45         interrupt_ctrl.WriteTo(controller_->mmio_space());
46 
47         if (interrupt_ctrl.sde_int_pending()) {
48             auto sde_int_identity = registers::SdeInterruptBase::Get(registers
49                     ::SdeInterruptBase::kSdeIntIdentity).ReadFrom(controller_->mmio_space());
50             auto hp_ctrl1 = registers::HotplugCtrl
51                     ::Get(registers::DDI_A).ReadFrom(controller_->mmio_space());
52             auto hp_ctrl2 = registers::HotplugCtrl
53                     ::Get(registers::DDI_E).ReadFrom(controller_->mmio_space());
54             for (uint32_t i = 0; i < registers::kDdiCount; i++) {
55                 registers::Ddi ddi = registers::kDdis[i];
56                 auto hp_ctrl = ddi < registers::DDI_E ? hp_ctrl1 : hp_ctrl2;
57                 bool hp_detected = sde_int_identity.ddi_bit(ddi).get()
58                         & (hp_ctrl.hpd_long_pulse(ddi).get() || hp_ctrl.hpd_short_pulse(ddi).get());
59                 if (hp_detected) {
60                     controller_->HandleHotplug(ddi, hp_ctrl.hpd_long_pulse(ddi).get());
61                 }
62             }
63             // Write back the register to clear the bits
64             hp_ctrl1.WriteTo(controller_->mmio_space());
65             hp_ctrl2.WriteTo(controller_->mmio_space());
66             sde_int_identity.WriteTo(controller_->mmio_space());
67         }
68 
69         if (interrupt_ctrl.de_pipe_c_int_pending()) {
70             HandlePipeInterrupt(registers::PIPE_C, timestamp);
71         } else if (interrupt_ctrl.de_pipe_b_int_pending()) {
72             HandlePipeInterrupt(registers::PIPE_B, timestamp);
73         } else if (interrupt_ctrl.de_pipe_a_int_pending()) {
74             HandlePipeInterrupt(registers::PIPE_A, timestamp);
75         }
76 
77         {
78             fbl::AutoLock lock(&lock_);
79             if (interrupt_ctrl.reg_value() & interrupt_mask_) {
80                 interrupt_cb_.callback(interrupt_cb_.ctx, interrupt_ctrl.reg_value());
81             }
82         }
83 
84         interrupt_ctrl.set_enable_mask(1);
85         interrupt_ctrl.WriteTo(controller_->mmio_space());
86     }
87     return 0;
88 }
89 
HandlePipeInterrupt(registers::Pipe pipe,zx_time_t timestamp)90 void Interrupts::HandlePipeInterrupt(registers::Pipe pipe, zx_time_t timestamp) {
91     registers::PipeRegs regs(pipe);
92     auto identity = regs.PipeDeInterrupt(regs.kIdentityReg).ReadFrom(controller_->mmio_space());
93     identity.WriteTo(controller_->mmio_space());
94 
95     if (identity.vsync()) {
96         controller_->HandlePipeVsync(pipe, timestamp);
97     }
98 }
99 
EnablePipeVsync(registers::Pipe pipe,bool enable)100 void Interrupts::EnablePipeVsync(registers::Pipe pipe, bool enable) {
101     registers::PipeRegs regs(pipe);
102     auto mask_reg = regs.PipeDeInterrupt(regs.kMaskReg).FromValue(0);
103     mask_reg.set_vsync(!enable);
104     mask_reg.WriteTo(controller_->mmio_space());
105 
106     auto enable_reg = regs.PipeDeInterrupt(regs.kEnableReg).FromValue(0);
107     enable_reg.set_vsync(enable);
108     enable_reg.WriteTo(controller_->mmio_space());
109 }
110 
EnableHotplugInterrupts()111 void Interrupts::EnableHotplugInterrupts() {
112     auto sfuse_strap = registers::SouthFuseStrap::Get().ReadFrom(controller_->mmio_space());
113     for (uint32_t i = 0; i < registers::kDdiCount; i++) {
114         registers::Ddi ddi = registers::kDdis[i];
115         bool enabled = (ddi == registers::DDI_A) || (ddi == registers::DDI_E)
116                 || (ddi == registers::DDI_B && sfuse_strap.port_b_present())
117                 || (ddi == registers::DDI_C && sfuse_strap.port_c_present())
118                 || (ddi == registers::DDI_D && sfuse_strap.port_d_present());
119 
120         auto hp_ctrl = registers::HotplugCtrl::Get(ddi).ReadFrom(controller_->mmio_space());
121         hp_ctrl.hpd_enable(ddi).set(enabled);
122         hp_ctrl.WriteTo(controller_->mmio_space());
123 
124         auto mask = registers::SdeInterruptBase::Get(
125                         registers::SdeInterruptBase::kSdeIntMask)
126                         .ReadFrom(controller_->mmio_space());
127         mask.ddi_bit(ddi).set(!enabled);
128         mask.WriteTo(controller_->mmio_space());
129 
130         auto enable = registers::SdeInterruptBase::Get(
131                           registers::SdeInterruptBase::kSdeIntEnable)
132                           .ReadFrom(controller_->mmio_space());
133         enable.ddi_bit(ddi).set(enabled);
134         enable.WriteTo(controller_->mmio_space());
135     }
136 }
137 
SetInterruptCallback(const zx_intel_gpu_core_interrupt_t * callback,uint32_t interrupt_mask)138 zx_status_t Interrupts::SetInterruptCallback(const zx_intel_gpu_core_interrupt_t* callback,
139                                              uint32_t interrupt_mask) {
140     fbl::AutoLock lock(&lock_);
141     if (callback->callback != nullptr && interrupt_cb_.callback != nullptr) {
142         return ZX_ERR_ALREADY_BOUND;
143     }
144     interrupt_cb_ = *callback;
145     interrupt_mask_ = interrupt_mask;
146     return ZX_OK;
147 }
148 
Init(Controller * controller)149 zx_status_t Interrupts::Init(Controller* controller) {
150     controller_ = controller;
151     ddk::MmioBuffer* mmio_space = controller_->mmio_space();
152 
153     mtx_init(&lock_, mtx_plain);
154 
155     // Disable interrupts here, re-enable them in ::FinishInit()
156     auto interrupt_ctrl = registers::MasterInterruptControl::Get().ReadFrom(mmio_space);
157     interrupt_ctrl.set_enable_mask(0);
158     interrupt_ctrl.WriteTo(mmio_space);
159 
160     uint32_t irq_cnt = 0;
161     zx_status_t status = pci_query_irq_mode(controller_->pci(), ZX_PCIE_IRQ_MODE_LEGACY, &irq_cnt);
162     if (status != ZX_OK || !irq_cnt) {
163         LOG_ERROR("Failed to find interrupts (%d %d)\n", status, irq_cnt);
164         return ZX_ERR_INTERNAL;
165     }
166 
167     if ((status = pci_set_irq_mode(controller_->pci(), ZX_PCIE_IRQ_MODE_LEGACY, 1)) != ZX_OK) {
168         LOG_ERROR("Failed to set irq mode (%d)\n", status);
169         return status;
170     }
171 
172     if ((status = pci_map_interrupt(controller_->pci(), 0, irq_.reset_and_get_address())
173             != ZX_OK)) {
174         LOG_ERROR("Failed to map interrupt (%d)\n", status);
175         return status;
176     }
177 
178     status = thrd_create_with_name(&irq_thread_, irq_handler, this, "i915-irq-thread");
179     if (status != ZX_OK) {
180         LOG_ERROR("Failed to create irq thread\n");
181         return status;
182     }
183 
184     Resume();
185     return ZX_OK;
186 }
187 
FinishInit()188 void Interrupts::FinishInit() {
189     auto ctrl = registers::MasterInterruptControl::Get().ReadFrom(controller_->mmio_space());
190     ctrl.set_enable_mask(1);
191     ctrl.WriteTo(controller_->mmio_space());
192 }
193 
Resume()194 void Interrupts::Resume() {
195     EnableHotplugInterrupts();
196 }
197 
198 } // namespace i915
199