// SPDX-License-Identifier: GPL-2.0
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
/* Copyright 2019 Collabora ltd. */
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/dma-resv.h>
#include <drm/gpu_scheduler.h>
#include <drm/panfrost_drm.h>
#include "panfrost_device.h"
#include "panfrost_devfreq.h"
#include "panfrost_job.h"
#include "panfrost_features.h"
#include "panfrost_issues.h"
#include "panfrost_gem.h"
#include "panfrost_regs.h"
#include "panfrost_gpu.h"
#include "panfrost_mmu.h"
#include "panfrost_dump.h"
#define JOB_TIMEOUT_MS 500
#define job_write(dev, reg, data) writel(data, dev->iomem + (reg))
#define job_read(dev, reg) readl(dev->iomem + (reg))
struct panfrost_queue_state {
struct drm_gpu_scheduler sched;
u64 fence_context;
u64 emit_seqno;
};
struct panfrost_job_slot {
struct panfrost_queue_state queue[NUM_JOB_SLOTS];
spinlock_t job_lock;
int irq;
};
static struct panfrost_job *
to_panfrost_job(struct drm_sched_job *sched_job)
{
return container_of(sched_job, struct panfrost_job, base);
}
struct panfrost_fence {
struct dma_fence base;
struct drm_device *dev;
/* panfrost seqno for signaled() test */
u64 seqno;
int queue;
};
static inline struct panfrost_fence *
to_panfrost_fence(struct dma_fence *fence)
{
return (struct panfrost_fence *)fence;
}
static const char *panfrost_fence_get_driver_name(struct dma_fence *fence)
{
return "panfrost";
}
static const char *panfrost_fence_get_timeline_name(struct dma_fence *fence)
{
struct panfrost_fence *f = to_panfrost_fence(fence);
switch (f->queue) {
case 0:
return "panfrost-js-0";
case 1:
return "panfrost-js-1";
case 2:
return "panfrost-js-2";
default:
return NULL;
}
}
static const struct dma_fence_ops panfrost_fence_ops = {
.get_driver_name = panfrost_fence_get_driver_name,
.get_timeline_name = panfrost_fence_get_timeline_name,
};
static struct dma_fence *panfrost_fence_create(struct panfrost_device *pfdev, int js_num)
{
struct panfrost_fence *fence;
struct panfrost_job_slot *js = pfdev->js;
fence = kzalloc(sizeof(*fence), GFP_KERNEL);
if (!fence)
return ERR_PTR(-ENOMEM);
fence->dev = pfdev->ddev;
fence->queue = js_num;
fence->seqno = ++js->queue[js_num].emit_seqno;
dma_fence_init(&fence->base, &panfrost_fence_ops, &js->job_lock,
js->queue[js_num].fence_context, fence->seqno);
return &fence->base;
}
int panfrost_job_get_slot(struct panfrost_job *job)
{
/* JS0: fragment jobs.
* JS1: vertex/tiler jobs
* JS2: compute jobs
*/
if (job->requirements & PANFROST_JD_REQ_FS)
return 0;
/* Not exposed to userspace yet */
#if 0
if (job->requirements & PANFROST_JD_REQ_ONLY_COMPUTE) {
if ((job->requirements & PANFROST_JD_REQ_CORE_GRP_MASK) &&
(job->pfdev->features.nr_core_groups == 2))
return 2;
if (panfrost_has_hw_issue(job->pfdev, HW_ISSUE_8987))
return 2;
}
#endif
return 1;
}
static void panfrost_job_write_affinity(struct panfrost_device *pfdev,
u32 requirements,
int js)
{
u64 affinity;
/*
* Use all cores for now.
* Eventually we may need to support tiler only jobs and h/w with
* multiple (2) coherent core groups
*/
affinity = pfdev->features.shader_present;
job_write(pfdev, JS_AFFINITY_NEXT_LO(js), lower_32_bits(affinity));
job_write(pfdev, JS_AFFINITY_NEXT_HI(js), upper_32_bits(affinity));
}
static u32
panfrost_get_job_chain_flag(const struct panfrost_job *job)
{
struct panfrost_fence *f = to_panfrost_fence(job->done_fence);
if (!panfrost_has_hw_feature(job->pfdev, HW_FEATURE_JOBCHAIN_DISAMBIGUATION))
return 0;
return (f->seqno & 1) ? JS_CONFIG_JOB_CHAIN_FLAG : 0;
}
static struct panfrost_job *
panfrost_dequeue_job(struct panfrost_device *pfdev, int slot)
{
struct panfrost_job *job = pfdev->jobs[slot][0];
WARN_ON(!job);
if (job->is_profiled) {
if (job->engine_usage) {
job->engine_usage->elapsed_ns[slot] +=
ktime_to_ns(ktime_sub(ktime_get(), job->start_time));
job->engine_usage->cycles[slot] +=
panfrost_cycle_counter_read(pfdev) - job->start_cycles;
}
panfrost_cycle_counter_put(job->pfdev);
}
pfdev->jobs[slot][0] = pfdev->jobs[slot][1];
pfdev->jobs[slot][1] = NULL;
return job;
}
static unsigned int
panfrost_enqueue_job(struct panfrost_device *pfdev, int slot,
struct panfrost_job *job)
{
if (WARN_ON(!job))
return 0;
if (!pfdev->jobs[slot][0]) {
pfdev->jobs[slot][0] = job;
return 0;
}
WARN_ON(pfdev->jobs[slot][1]);
pfdev->jobs[slot][1] = job;
WARN_ON(panfrost_get_job_chain_flag(job) ==
panfrost_get_job_chain_flag(pfdev->jobs[slot][0]));
return 1;
}
static void panfrost_job_hw_submit(struct panfrost_job *job, int js)
{
struct panfrost_device *pfdev = job->pfdev;
unsigned int subslot;
u32 cfg;
u64 jc_head = job->jc