static int v3d_mmu_flush_all(struct v3d_dev *v3d) { int ret; /* Make sure that another flush isn't already running when we * start this one. */ ret = wait_for(!(V3D_READ(V3D_MMU_CTL) & V3D_MMU_CTL_TLB_CLEARING), 100); if (ret) dev_err(v3d->dev, "TLB clear wait idle pre-wait failed\n"); V3D_WRITE(V3D_MMU_CTL, V3D_READ(V3D_MMU_CTL) | V3D_MMU_CTL_TLB_CLEAR); V3D_WRITE(V3D_MMUC_CONTROL, V3D_MMUC_CONTROL_FLUSH | V3D_MMUC_CONTROL_ENABLE); ret = wait_for(!(V3D_READ(V3D_MMU_CTL) & V3D_MMU_CTL_TLB_CLEARING), 100); if (ret) { dev_err(v3d->dev, "TLB clear wait idle failed\n"); return ret; } ret = wait_for(!(V3D_READ(V3D_MMUC_CONTROL) & V3D_MMUC_CONTROL_FLUSHING), 100); if (ret) dev_err(v3d->dev, "MMUC flush wait idle failed\n"); return ret; }
void vc4_irq_uninstall(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); /* Disable sending interrupts for our driver's IRQs. */ V3D_WRITE(V3D_INTDIS, V3D_DRIVER_IRQS); /* Clear any pending interrupts we might have left. */ V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS); cancel_work_sync(&vc4->overflow_mem_work); }
static void vc4_overflow_mem_work(struct work_struct *work) { struct vc4_dev *vc4 = container_of(work, struct vc4_dev, overflow_mem_work); struct drm_device *dev = vc4->dev; struct vc4_bo *bo; bo = vc4_bo_create(dev, 256 * 1024, true); if (IS_ERR(bo)) { DRM_ERROR("Couldn't allocate binner overflow mem\n"); return; } /* If there's a job executing currently, then our previous * overflow allocation is getting used in that job and we need * to queue it to be released when the job is done. But if no * job is executing at all, then we can free the old overflow * object direcctly. * * No lock necessary for this pointer since we're the only * ones that update the pointer, and our workqueue won't * reenter. */ if (vc4->overflow_mem) { struct vc4_exec_info *current_exec; unsigned long irqflags; spin_lock_irqsave(&vc4->job_lock, irqflags); current_exec = vc4_first_bin_job(vc4); if (current_exec) { vc4->overflow_mem->seqno = vc4->finished_seqno + 1; list_add_tail(&vc4->overflow_mem->unref_head, ¤t_exec->unref_list); vc4->overflow_mem = NULL; } spin_unlock_irqrestore(&vc4->job_lock, irqflags); } if (vc4->overflow_mem) drm_gem_object_unreference_unlocked(&vc4->overflow_mem->base.base); vc4->overflow_mem = bo; V3D_WRITE(V3D_BPOA, bo->base.paddr); V3D_WRITE(V3D_BPOS, bo->base.base.size); V3D_WRITE(V3D_INTCTL, V3D_INT_OUTOMEM); V3D_WRITE(V3D_INTENA, V3D_INT_OUTOMEM); }
static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_v3d *v3d = NULL; int ret; v3d = devm_kzalloc(&pdev->dev, sizeof(*v3d), GFP_KERNEL); if (!v3d) return -ENOMEM; dev_set_drvdata(dev, v3d); v3d->pdev = pdev; v3d->regs = vc4_ioremap_regs(pdev, 0); if (IS_ERR(v3d->regs)) return PTR_ERR(v3d->regs); vc4->v3d = v3d; v3d->vc4 = vc4; if (V3D_READ(V3D_IDENT0) != V3D_EXPECTED_IDENT0) { DRM_ERROR("V3D_IDENT0 read 0x%08x instead of 0x%08x\n", V3D_READ(V3D_IDENT0), V3D_EXPECTED_IDENT0); return -EINVAL; } /* Reset the binner overflow address/size at setup, to be sure * we don't reuse an old one. */ V3D_WRITE(V3D_BPOA, 0); V3D_WRITE(V3D_BPOS, 0); vc4_v3d_init_hw(drm); ret = drm_irq_install(drm, platform_get_irq(pdev, 0)); if (ret) { DRM_ERROR("Failed to install IRQ handler\n"); return ret; } pm_runtime_enable(dev); return 0; }
static void vc4_overflow_mem_work(struct work_struct *work) { struct vc4_dev *vc4 = container_of(work, struct vc4_dev, overflow_mem_work); struct vc4_bo *bo = vc4->bin_bo; int bin_bo_slot; struct vc4_exec_info *exec; unsigned long irqflags; bin_bo_slot = vc4_v3d_get_bin_slot(vc4); if (bin_bo_slot < 0) { DRM_ERROR("Couldn't allocate binner overflow mem\n"); return; } spin_lock_irqsave(&vc4->job_lock, irqflags); if (vc4->bin_alloc_overflow) { /* If we had overflow memory allocated previously, * then that chunk will free when the current bin job * is done. If we don't have a bin job running, then * the chunk will be done whenever the list of render * jobs has drained. */ exec = vc4_first_bin_job(vc4); if (!exec) exec = vc4_last_render_job(vc4); if (exec) { exec->bin_slots |= vc4->bin_alloc_overflow; } else { /* There's nothing queued in the hardware, so * the old slot is free immediately. */ vc4->bin_alloc_used &= ~vc4->bin_alloc_overflow; } } vc4->bin_alloc_overflow = BIT(bin_bo_slot); V3D_WRITE(V3D_BPOA, bo->base.paddr + bin_bo_slot * vc4->bin_alloc_size); V3D_WRITE(V3D_BPOS, bo->base.base.size); V3D_WRITE(V3D_INTCTL, V3D_INT_OUTOMEM); V3D_WRITE(V3D_INTENA, V3D_INT_OUTOMEM); spin_unlock_irqrestore(&vc4->job_lock, irqflags); }
int vc4_irq_postinstall(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); /* Enable both the render done and out of memory interrupts. */ V3D_WRITE(V3D_INTENA, V3D_DRIVER_IRQS); return 0; }
static void vc4_v3d_unbind(struct device *dev, struct device *master, void *data) { struct drm_device *drm = dev_get_drvdata(master); struct vc4_dev *vc4 = to_vc4_dev(drm); pm_runtime_disable(dev); drm_irq_uninstall(drm); /* Disable the binner's overflow memory address, so the next * driver probe (if any) doesn't try to reuse our old * allocation. */ V3D_WRITE(V3D_BPOA, 0); V3D_WRITE(V3D_BPOS, 0); vc4->v3d = NULL; }
static void vc4_v3d_init_hw(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); /* Take all the memory that would have been reserved for user * QPU programs, since we don't have an interface for running * them, anyway. */ V3D_WRITE(V3D_VPMBASE, 0); }
irqreturn_t vc4_irq(int irq, void *arg) { struct drm_device *dev = arg; struct vc4_dev *vc4 = to_vc4_dev(dev); uint32_t intctl; irqreturn_t status = IRQ_NONE; barrier(); intctl = V3D_READ(V3D_INTCTL); /* Acknowledge the interrupts we're handling here. The binner * last flush / render frame done interrupt will be cleared, * while OUTOMEM will stay high until the underlying cause is * cleared. */ V3D_WRITE(V3D_INTCTL, intctl); if (intctl & V3D_INT_OUTOMEM) { /* Disable OUTOMEM until the work is done. */ V3D_WRITE(V3D_INTDIS, V3D_INT_OUTOMEM); schedule_work(&vc4->overflow_mem_work); status = IRQ_HANDLED; } if (intctl & V3D_INT_FLDONE) { spin_lock(&vc4->job_lock); vc4_irq_finish_bin_job(dev); spin_unlock(&vc4->job_lock); status = IRQ_HANDLED; } if (intctl & V3D_INT_FRDONE) { spin_lock(&vc4->job_lock); vc4_irq_finish_render_job(dev); spin_unlock(&vc4->job_lock); status = IRQ_HANDLED; } return status; }
/** Reinitializes interrupt registers when a GPU reset is performed. */ void vc4_irq_reset(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); unsigned long irqflags; /* Acknowledge any stale IRQs. */ V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS); /* * Turn all our interrupts on. Binner out of memory is the * only one we expect to trigger at this point, since we've * just come from poweron and haven't supplied any overflow * memory yet. */ V3D_WRITE(V3D_INTENA, V3D_DRIVER_IRQS); spin_lock_irqsave(&vc4->job_lock, irqflags); vc4_cancel_bin_job(dev); vc4_irq_finish_render_job(dev); spin_unlock_irqrestore(&vc4->job_lock, irqflags); }
void vc4_irq_preinstall(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); init_waitqueue_head(&vc4->job_wait_queue); INIT_WORK(&vc4->overflow_mem_work, vc4_overflow_mem_work); /* Clear any pending interrupts someone might have left around * for us. */ V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS); }
static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_v3d *v3d = NULL; int ret; v3d = devm_kzalloc(&pdev->dev, sizeof(*v3d), GFP_KERNEL); if (!v3d) return -ENOMEM; dev_set_drvdata(dev, v3d); v3d->pdev = pdev; v3d->regs = vc4_ioremap_regs(pdev, 0); if (IS_ERR(v3d->regs)) return PTR_ERR(v3d->regs); vc4->v3d = v3d; v3d->vc4 = vc4; v3d->clk = devm_clk_get(dev, NULL); if (IS_ERR(v3d->clk)) { int ret = PTR_ERR(v3d->clk); if (ret == -ENOENT) { /* bcm2835 didn't have a clock reference in the DT. */ ret = 0; v3d->clk = NULL; } else { if (ret != -EPROBE_DEFER) dev_err(dev, "Failed to get V3D clock: %d\n", ret); return ret; } } if (V3D_READ(V3D_IDENT0) != V3D_EXPECTED_IDENT0) { DRM_ERROR("V3D_IDENT0 read 0x%08x instead of 0x%08x\n", V3D_READ(V3D_IDENT0), V3D_EXPECTED_IDENT0); return -EINVAL; } ret = clk_prepare_enable(v3d->clk); if (ret != 0) return ret; ret = vc4_allocate_bin_bo(drm); if (ret) { clk_disable_unprepare(v3d->clk); return ret; } /* Reset the binner overflow address/size at setup, to be sure * we don't reuse an old one. */ V3D_WRITE(V3D_BPOA, 0); V3D_WRITE(V3D_BPOS, 0); vc4_v3d_init_hw(drm); ret = drm_irq_install(drm, platform_get_irq(pdev, 0)); if (ret) { DRM_ERROR("Failed to install IRQ handler\n"); return ret; } pm_runtime_set_active(dev); pm_runtime_use_autosuspend(dev); pm_runtime_set_autosuspend_delay(dev, 40); /* a little over 2 frames. */ pm_runtime_enable(dev); return 0; }