mirror of
https://github.com/a52xq-dev/kernel.git
synced 2026-10-19 06:08:12 +07:00
drm/msm: Add SDM845 DPU support
SDM845 SoC includes the Mobile Display Sub System (MDSS) which is a
top level wrapper consisting of Display Processing Unit (DPU) and
display peripheral modules such as Display Serial Interface (DSI)
and DisplayPort (DP).
MDSS functions essentially as a back-end composition engine. It blends
video and graphic images stored in the frame buffers and scans out the
composed image to a display sink (over DSI/DP).
The following diagram represents hardware blocks for a simple pipeline
(two planes are present on a given crtc which is connected to a DSI
connector):
MDSS
+---------------------------------+
| +-----------------------------+ |
| | DPU | |
| | +--------+ +--------+ | |
| | | SSPP | | SSPP | | |
| | +----+---+ +----+---+ | |
| | | | | |
| | +----v-----------v---+ | |
| | | Layer Mixer (LM) | | |
| | +--------------------+ | |
| | +--------------------+ | |
| | | PingPong (PP) | | |
| | +--------------------+ | |
| | +--------------------+ | |
| | | INTERFACE (VIDEO) | | |
| | +---+----------------+ | |
| +------|----------------------+ |
| | |
| +------|---------------------+ |
| | | DISPLAY PERIPHERALS | |
| | +---v-+ +-----+ | |
| | | DSI | | DP | | |
| | +-----+ +-----+ | |
| +----------------------------+ |
+---------------------------------+
The number of DPU sub-blocks (i.e. SSPPs, LMs, PP blocks and INTFs)
depends on SoC capabilities.
Overview of DPU sub-blocks:
---------------------------
* Source Surface Processor (SSPP):
Refers to any of hardware pipes like ViG, DMA etc. Only ViG pipes are
capable of performing format conversion, scaling and quality improvement
for source surfaces.
* Layer Mixer (LM):
Blend source surfaces together (in requested zorder)
* PingPong (PP):
This block controls frame done interrupt output, EOL and EOF generation,
overflow/underflow control.
* Display interface (INTF):
Timing generator and interface connecting the display peripherals.
DRM components mapping to DPU architecture:
------------------------------------------
PLANEs maps to SSPPs
CRTC maps to LMs
Encoder maps to PPs, INTFs
Data flow setup:
---------------
MDSS hardware can support various data flows (e.g.):
- Dual pipe: Output from two LMs combined to single display.
- Split display: Output from two LMs connected to two separate
interfaces.
The hardware capabilities determine the number of concurrent data paths
possible. Any control path (i.e. pipeline w/i DPU) can be routed to any
of the hardware data paths. A given control path can be triggered,
flushed and controlled independently.
Changes in v3:
- Move msm_media_info.h from uapi to dpu/ subdir
- Remove preclose callback dpu (it's handled in core)
- Fix kbuild warnings with parent_ops
- Remove unused functions from dpu_core_irq
- Rename mdss_phys to mdss
- Rename mdp_phys address space to mdp
- Drop _phys from vbif and regdma binding names
Signed-off-by: Abhinav Kumar <abhinavk@codeaurora.org>
Signed-off-by: Archit Taneja <architt@codeaurora.org>
Signed-off-by: Chandan Uddaraju <chandanu@codeaurora.org>
Signed-off-by: Jeykumar Sankaran <jsanka@codeaurora.org>
Signed-off-by: Jordan Crouse <jcrouse@codeaurora.org>
Signed-off-by: Rajesh Yadav <ryadav@codeaurora.org>
Signed-off-by: Sravanthi Kollukuduru <skolluku@codeaurora.org>
Signed-off-by: Sean Paul <seanpaul@chromium.org>
[robclark minor rebase]
Signed-off-by: Rob Clark <robdclark@gmail.com>
This commit is contained in:
parent
036bfeb33b
commit
25fdd5933e
64 changed files with 31913 additions and 15 deletions
|
|
@ -1,5 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
ccflags-y := -Idrivers/gpu/drm/msm
|
||||
ccflags-y += -Idrivers/gpu/drm/msm/disp/dpu1
|
||||
ccflags-$(CONFIG_DRM_MSM_DSI) += -Idrivers/gpu/drm/msm/dsi
|
||||
|
||||
msm-y := \
|
||||
|
|
@ -45,6 +46,34 @@ msm-y := \
|
|||
disp/mdp5/mdp5_mixer.o \
|
||||
disp/mdp5/mdp5_plane.o \
|
||||
disp/mdp5/mdp5_smp.o \
|
||||
disp/dpu1/dpu_core_irq.o \
|
||||
disp/dpu1/dpu_core_perf.o \
|
||||
disp/dpu1/dpu_crtc.o \
|
||||
disp/dpu1/dpu_encoder.o \
|
||||
disp/dpu1/dpu_encoder_phys_cmd.o \
|
||||
disp/dpu1/dpu_encoder_phys_vid.o \
|
||||
disp/dpu1/dpu_formats.o \
|
||||
disp/dpu1/dpu_hw_blk.o \
|
||||
disp/dpu1/dpu_hw_catalog.o \
|
||||
disp/dpu1/dpu_hw_cdm.o \
|
||||
disp/dpu1/dpu_hw_ctl.o \
|
||||
disp/dpu1/dpu_hw_interrupts.o \
|
||||
disp/dpu1/dpu_hw_intf.o \
|
||||
disp/dpu1/dpu_hw_lm.o \
|
||||
disp/dpu1/dpu_hw_pingpong.o \
|
||||
disp/dpu1/dpu_hw_sspp.o \
|
||||
disp/dpu1/dpu_hw_top.o \
|
||||
disp/dpu1/dpu_hw_util.o \
|
||||
disp/dpu1/dpu_hw_vbif.o \
|
||||
disp/dpu1/dpu_io_util.o \
|
||||
disp/dpu1/dpu_irq.o \
|
||||
disp/dpu1/dpu_kms.o \
|
||||
disp/dpu1/dpu_kms_utils.o \
|
||||
disp/dpu1/dpu_mdss.o \
|
||||
disp/dpu1/dpu_plane.o \
|
||||
disp/dpu1/dpu_power_handle.o \
|
||||
disp/dpu1/dpu_rm.o \
|
||||
disp/dpu1/dpu_vbif.o \
|
||||
msm_atomic.o \
|
||||
msm_debugfs.o \
|
||||
msm_drv.o \
|
||||
|
|
@ -62,7 +91,8 @@ msm-y := \
|
|||
msm_ringbuffer.o \
|
||||
msm_submitqueue.o
|
||||
|
||||
msm-$(CONFIG_DEBUG_FS) += adreno/a5xx_debugfs.o
|
||||
msm-$(CONFIG_DEBUG_FS) += adreno/a5xx_debugfs.o \
|
||||
disp/dpu1/dpu_dbg.o
|
||||
|
||||
msm-$(CONFIG_DRM_FBDEV_EMULATION) += msm_fbdev.o
|
||||
msm-$(CONFIG_COMMON_CLK) += disp/mdp4/mdp4_lvds_pll.o
|
||||
|
|
|
|||
479
drivers/gpu/drm/msm/disp/dpu1/dpu_core_irq.c
Normal file
479
drivers/gpu/drm/msm/disp/dpu1/dpu_core_irq.c
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/kthread.h>
|
||||
|
||||
#include "dpu_core_irq.h"
|
||||
#include "dpu_trace.h"
|
||||
|
||||
/**
|
||||
* dpu_core_irq_callback_handler - dispatch core interrupts
|
||||
* @arg: private data of callback handler
|
||||
* @irq_idx: interrupt index
|
||||
*/
|
||||
static void dpu_core_irq_callback_handler(void *arg, int irq_idx)
|
||||
{
|
||||
struct dpu_kms *dpu_kms = arg;
|
||||
struct dpu_irq *irq_obj = &dpu_kms->irq_obj;
|
||||
struct dpu_irq_callback *cb;
|
||||
unsigned long irq_flags;
|
||||
|
||||
pr_debug("irq_idx=%d\n", irq_idx);
|
||||
|
||||
if (list_empty(&irq_obj->irq_cb_tbl[irq_idx])) {
|
||||
DRM_ERROR("no registered cb, idx:%d enable_count:%d\n", irq_idx,
|
||||
atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idx]));
|
||||
}
|
||||
|
||||
atomic_inc(&irq_obj->irq_counts[irq_idx]);
|
||||
|
||||
/*
|
||||
* Perform registered function callback
|
||||
*/
|
||||
spin_lock_irqsave(&dpu_kms->irq_obj.cb_lock, irq_flags);
|
||||
list_for_each_entry(cb, &irq_obj->irq_cb_tbl[irq_idx], list)
|
||||
if (cb->func)
|
||||
cb->func(cb->arg, irq_idx);
|
||||
spin_unlock_irqrestore(&dpu_kms->irq_obj.cb_lock, irq_flags);
|
||||
|
||||
/*
|
||||
* Clear pending interrupt status in HW.
|
||||
* NOTE: dpu_core_irq_callback_handler is protected by top-level
|
||||
* spinlock, so it is safe to clear any interrupt status here.
|
||||
*/
|
||||
dpu_kms->hw_intr->ops.clear_intr_status_nolock(
|
||||
dpu_kms->hw_intr,
|
||||
irq_idx);
|
||||
}
|
||||
|
||||
int dpu_core_irq_idx_lookup(struct dpu_kms *dpu_kms,
|
||||
enum dpu_intr_type intr_type, u32 instance_idx)
|
||||
{
|
||||
if (!dpu_kms || !dpu_kms->hw_intr ||
|
||||
!dpu_kms->hw_intr->ops.irq_idx_lookup)
|
||||
return -EINVAL;
|
||||
|
||||
return dpu_kms->hw_intr->ops.irq_idx_lookup(intr_type,
|
||||
instance_idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* _dpu_core_irq_enable - enable core interrupt given by the index
|
||||
* @dpu_kms: Pointer to dpu kms context
|
||||
* @irq_idx: interrupt index
|
||||
*/
|
||||
static int _dpu_core_irq_enable(struct dpu_kms *dpu_kms, int irq_idx)
|
||||
{
|
||||
unsigned long irq_flags;
|
||||
int ret = 0, enable_count;
|
||||
|
||||
if (!dpu_kms || !dpu_kms->hw_intr ||
|
||||
!dpu_kms->irq_obj.enable_counts ||
|
||||
!dpu_kms->irq_obj.irq_counts) {
|
||||
DPU_ERROR("invalid params\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (irq_idx < 0 || irq_idx >= dpu_kms->hw_intr->irq_idx_tbl_size) {
|
||||
DPU_ERROR("invalid IRQ index: [%d]\n", irq_idx);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
enable_count = atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idx]);
|
||||
DRM_DEBUG_KMS("irq_idx=%d enable_count=%d\n", irq_idx, enable_count);
|
||||
trace_dpu_core_irq_enable_idx(irq_idx, enable_count);
|
||||
|
||||
if (atomic_inc_return(&dpu_kms->irq_obj.enable_counts[irq_idx]) == 1) {
|
||||
ret = dpu_kms->hw_intr->ops.enable_irq(
|
||||
dpu_kms->hw_intr,
|
||||
irq_idx);
|
||||
if (ret)
|
||||
DPU_ERROR("Fail to enable IRQ for irq_idx:%d\n",
|
||||
irq_idx);
|
||||
|
||||
DPU_DEBUG("irq_idx=%d ret=%d\n", irq_idx, ret);
|
||||
|
||||
spin_lock_irqsave(&dpu_kms->irq_obj.cb_lock, irq_flags);
|
||||
/* empty callback list but interrupt is enabled */
|
||||
if (list_empty(&dpu_kms->irq_obj.irq_cb_tbl[irq_idx]))
|
||||
DPU_ERROR("irq_idx=%d enabled with no callback\n",
|
||||
irq_idx);
|
||||
spin_unlock_irqrestore(&dpu_kms->irq_obj.cb_lock, irq_flags);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int dpu_core_irq_enable(struct dpu_kms *dpu_kms, int *irq_idxs, u32 irq_count)
|
||||
{
|
||||
int i, ret = 0, counts;
|
||||
|
||||
if (!dpu_kms || !irq_idxs || !irq_count) {
|
||||
DPU_ERROR("invalid params\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
counts = atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idxs[0]]);
|
||||
if (counts)
|
||||
DRM_ERROR("irq_idx=%d enable_count=%d\n", irq_idxs[0], counts);
|
||||
|
||||
for (i = 0; (i < irq_count) && !ret; i++)
|
||||
ret = _dpu_core_irq_enable(dpu_kms, irq_idxs[i]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* _dpu_core_irq_disable - disable core interrupt given by the index
|
||||
* @dpu_kms: Pointer to dpu kms context
|
||||
* @irq_idx: interrupt index
|
||||
*/
|
||||
static int _dpu_core_irq_disable(struct dpu_kms *dpu_kms, int irq_idx)
|
||||
{
|
||||
int ret = 0, enable_count;
|
||||
|
||||
if (!dpu_kms || !dpu_kms->hw_intr || !dpu_kms->irq_obj.enable_counts) {
|
||||
DPU_ERROR("invalid params\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (irq_idx < 0 || irq_idx >= dpu_kms->hw_intr->irq_idx_tbl_size) {
|
||||
DPU_ERROR("invalid IRQ index: [%d]\n", irq_idx);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
enable_count = atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idx]);
|
||||
DRM_DEBUG_KMS("irq_idx=%d enable_count=%d\n", irq_idx, enable_count);
|
||||
trace_dpu_core_irq_disable_idx(irq_idx, enable_count);
|
||||
|
||||
if (atomic_dec_return(&dpu_kms->irq_obj.enable_counts[irq_idx]) == 0) {
|
||||
ret = dpu_kms->hw_intr->ops.disable_irq(
|
||||
dpu_kms->hw_intr,
|
||||
irq_idx);
|
||||
if (ret)
|
||||
DPU_ERROR("Fail to disable IRQ for irq_idx:%d\n",
|
||||
irq_idx);
|
||||
DPU_DEBUG("irq_idx=%d ret=%d\n", irq_idx, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int dpu_core_irq_disable(struct dpu_kms *dpu_kms, int *irq_idxs, u32 irq_count)
|
||||
{
|
||||
int i, ret = 0, counts;
|
||||
|
||||
if (!dpu_kms || !irq_idxs || !irq_count) {
|
||||
DPU_ERROR("invalid params\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
counts = atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idxs[0]]);
|
||||
if (counts == 2)
|
||||
DRM_ERROR("irq_idx=%d enable_count=%d\n", irq_idxs[0], counts);
|
||||
|
||||
for (i = 0; (i < irq_count) && !ret; i++)
|
||||
ret = _dpu_core_irq_disable(dpu_kms, irq_idxs[i]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 dpu_core_irq_read(struct dpu_kms *dpu_kms, int irq_idx, bool clear)
|
||||
{
|
||||
if (!dpu_kms || !dpu_kms->hw_intr ||
|
||||
!dpu_kms->hw_intr->ops.get_interrupt_status)
|
||||
return 0;
|
||||
|
||||
if (irq_idx < 0) {
|
||||
DPU_ERROR("[%pS] invalid irq_idx=%d\n",
|
||||
__builtin_return_address(0), irq_idx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return dpu_kms->hw_intr->ops.get_interrupt_status(dpu_kms->hw_intr,
|
||||
irq_idx, clear);
|
||||
}
|
||||
|
||||
int dpu_core_irq_register_callback(struct dpu_kms *dpu_kms, int irq_idx,
|
||||
struct dpu_irq_callback *register_irq_cb)
|
||||
{
|
||||
unsigned long irq_flags;
|
||||
|
||||
if (!dpu_kms || !dpu_kms->irq_obj.irq_cb_tbl) {
|
||||
DPU_ERROR("invalid params\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!register_irq_cb || !register_irq_cb->func) {
|
||||
DPU_ERROR("invalid irq_cb:%d func:%d\n",
|
||||
register_irq_cb != NULL,
|
||||
register_irq_cb ?
|
||||
register_irq_cb->func != NULL : -1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (irq_idx < 0 || irq_idx >= dpu_kms->hw_intr->irq_idx_tbl_size) {
|
||||
DPU_ERROR("invalid IRQ index: [%d]\n", irq_idx);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
DPU_DEBUG("[%pS] irq_idx=%d\n", __builtin_return_address(0), irq_idx);
|
||||
|
||||
spin_lock_irqsave(&dpu_kms->irq_obj.cb_lock, irq_flags);
|
||||
trace_dpu_core_irq_register_callback(irq_idx, register_irq_cb);
|
||||
list_del_init(®ister_irq_cb->list);
|
||||
list_add_tail(®ister_irq_cb->list,
|
||||
&dpu_kms->irq_obj.irq_cb_tbl[irq_idx]);
|
||||
spin_unlock_irqrestore(&dpu_kms->irq_obj.cb_lock, irq_flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dpu_core_irq_unregister_callback(struct dpu_kms *dpu_kms, int irq_idx,
|
||||
struct dpu_irq_callback *register_irq_cb)
|
||||
{
|
||||
unsigned long irq_flags;
|
||||
|
||||
if (!dpu_kms || !dpu_kms->irq_obj.irq_cb_tbl) {
|
||||
DPU_ERROR("invalid params\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!register_irq_cb || !register_irq_cb->func) {
|
||||
DPU_ERROR("invalid irq_cb:%d func:%d\n",
|
||||
register_irq_cb != NULL,
|
||||
register_irq_cb ?
|
||||
register_irq_cb->func != NULL : -1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (irq_idx < 0 || irq_idx >= dpu_kms->hw_intr->irq_idx_tbl_size) {
|
||||
DPU_ERROR("invalid IRQ index: [%d]\n", irq_idx);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
DPU_DEBUG("[%pS] irq_idx=%d\n", __builtin_return_address(0), irq_idx);
|
||||
|
||||
spin_lock_irqsave(&dpu_kms->irq_obj.cb_lock, irq_flags);
|
||||
trace_dpu_core_irq_unregister_callback(irq_idx, register_irq_cb);
|
||||
list_del_init(®ister_irq_cb->list);
|
||||
/* empty callback list but interrupt is still enabled */
|
||||
if (list_empty(&dpu_kms->irq_obj.irq_cb_tbl[irq_idx]) &&
|
||||
atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idx]))
|
||||
DPU_ERROR("irq_idx=%d enabled with no callback\n", irq_idx);
|
||||
spin_unlock_irqrestore(&dpu_kms->irq_obj.cb_lock, irq_flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dpu_clear_all_irqs(struct dpu_kms *dpu_kms)
|
||||
{
|
||||
if (!dpu_kms || !dpu_kms->hw_intr ||
|
||||
!dpu_kms->hw_intr->ops.clear_all_irqs)
|
||||
return;
|
||||
|
||||
dpu_kms->hw_intr->ops.clear_all_irqs(dpu_kms->hw_intr);
|
||||
}
|
||||
|
||||
static void dpu_disable_all_irqs(struct dpu_kms *dpu_kms)
|
||||
{
|
||||
if (!dpu_kms || !dpu_kms->hw_intr ||
|
||||
!dpu_kms->hw_intr->ops.disable_all_irqs)
|
||||
return;
|
||||
|
||||
dpu_kms->hw_intr->ops.disable_all_irqs(dpu_kms->hw_intr);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
#define DEFINE_DPU_DEBUGFS_SEQ_FOPS(__prefix) \
|
||||
static int __prefix ## _open(struct inode *inode, struct file *file) \
|
||||
{ \
|
||||
return single_open(file, __prefix ## _show, inode->i_private); \
|
||||
} \
|
||||
static const struct file_operations __prefix ## _fops = { \
|
||||
.owner = THIS_MODULE, \
|
||||
.open = __prefix ## _open, \
|
||||
.release = single_release, \
|
||||
.read = seq_read, \
|
||||
.llseek = seq_lseek, \
|
||||
}
|
||||
|
||||
static int dpu_debugfs_core_irq_show(struct seq_file *s, void *v)
|
||||
{
|
||||
struct dpu_irq *irq_obj = s->private;
|
||||
struct dpu_irq_callback *cb;
|
||||
unsigned long irq_flags;
|
||||
int i, irq_count, enable_count, cb_count;
|
||||
|
||||
if (!irq_obj || !irq_obj->enable_counts || !irq_obj->irq_cb_tbl) {
|
||||
DPU_ERROR("invalid parameters\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < irq_obj->total_irqs; i++) {
|
||||
spin_lock_irqsave(&irq_obj->cb_lock, irq_flags);
|
||||
cb_count = 0;
|
||||
irq_count = atomic_read(&irq_obj->irq_counts[i]);
|
||||
enable_count = atomic_read(&irq_obj->enable_counts[i]);
|
||||
list_for_each_entry(cb, &irq_obj->irq_cb_tbl[i], list)
|
||||
cb_count++;
|
||||
spin_unlock_irqrestore(&irq_obj->cb_lock, irq_flags);
|
||||
|
||||
if (irq_count || enable_count || cb_count)
|
||||
seq_printf(s, "idx:%d irq:%d enable:%d cb:%d\n",
|
||||
i, irq_count, enable_count, cb_count);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_DPU_DEBUGFS_SEQ_FOPS(dpu_debugfs_core_irq);
|
||||
|
||||
int dpu_debugfs_core_irq_init(struct dpu_kms *dpu_kms,
|
||||
struct dentry *parent)
|
||||
{
|
||||
dpu_kms->irq_obj.debugfs_file = debugfs_create_file("core_irq", 0600,
|
||||
parent, &dpu_kms->irq_obj,
|
||||
&dpu_debugfs_core_irq_fops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dpu_debugfs_core_irq_destroy(struct dpu_kms *dpu_kms)
|
||||
{
|
||||
debugfs_remove(dpu_kms->irq_obj.debugfs_file);
|
||||
dpu_kms->irq_obj.debugfs_file = NULL;
|
||||
}
|
||||
|
||||
#else
|
||||
int dpu_debugfs_core_irq_init(struct dpu_kms *dpu_kms,
|
||||
struct dentry *parent)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dpu_debugfs_core_irq_destroy(struct dpu_kms *dpu_kms)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
void dpu_core_irq_preinstall(struct dpu_kms *dpu_kms)
|
||||
{
|
||||
struct msm_drm_private *priv;
|
||||
int i;
|
||||
|
||||
if (!dpu_kms) {
|
||||
DPU_ERROR("invalid dpu_kms\n");
|
||||
return;
|
||||
} else if (!dpu_kms->dev) {
|
||||
DPU_ERROR("invalid drm device\n");
|
||||
return;
|
||||
} else if (!dpu_kms->dev->dev_private) {
|
||||
DPU_ERROR("invalid device private\n");
|
||||
return;
|
||||
}
|
||||
priv = dpu_kms->dev->dev_private;
|
||||
|
||||
pm_runtime_get_sync(&dpu_kms->pdev->dev);
|
||||
dpu_clear_all_irqs(dpu_kms);
|
||||
dpu_disable_all_irqs(dpu_kms);
|
||||
pm_runtime_put_sync(&dpu_kms->pdev->dev);
|
||||
|
||||
spin_lock_init(&dpu_kms->irq_obj.cb_lock);
|
||||
|
||||
/* Create irq callbacks for all possible irq_idx */
|
||||
dpu_kms->irq_obj.total_irqs = dpu_kms->hw_intr->irq_idx_tbl_size;
|
||||
dpu_kms->irq_obj.irq_cb_tbl = kcalloc(dpu_kms->irq_obj.total_irqs,
|
||||
sizeof(struct list_head), GFP_KERNEL);
|
||||
dpu_kms->irq_obj.enable_counts = kcalloc(dpu_kms->irq_obj.total_irqs,
|
||||
sizeof(atomic_t), GFP_KERNEL);
|
||||
dpu_kms->irq_obj.irq_counts = kcalloc(dpu_kms->irq_obj.total_irqs,
|
||||
sizeof(atomic_t), GFP_KERNEL);
|
||||
for (i = 0; i < dpu_kms->irq_obj.total_irqs; i++) {
|
||||
INIT_LIST_HEAD(&dpu_kms->irq_obj.irq_cb_tbl[i]);
|
||||
atomic_set(&dpu_kms->irq_obj.enable_counts[i], 0);
|
||||
atomic_set(&dpu_kms->irq_obj.irq_counts[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
int dpu_core_irq_postinstall(struct dpu_kms *dpu_kms)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dpu_core_irq_uninstall(struct dpu_kms *dpu_kms)
|
||||
{
|
||||
struct msm_drm_private *priv;
|
||||
int i;
|
||||
|
||||
if (!dpu_kms) {
|
||||
DPU_ERROR("invalid dpu_kms\n");
|
||||
return;
|
||||
} else if (!dpu_kms->dev) {
|
||||
DPU_ERROR("invalid drm device\n");
|
||||
return;
|
||||
} else if (!dpu_kms->dev->dev_private) {
|
||||
DPU_ERROR("invalid device private\n");
|
||||
return;
|
||||
}
|
||||
priv = dpu_kms->dev->dev_private;
|
||||
|
||||
pm_runtime_get_sync(&dpu_kms->pdev->dev);
|
||||
for (i = 0; i < dpu_kms->irq_obj.total_irqs; i++)
|
||||
if (atomic_read(&dpu_kms->irq_obj.enable_counts[i]) ||
|
||||
!list_empty(&dpu_kms->irq_obj.irq_cb_tbl[i]))
|
||||
DPU_ERROR("irq_idx=%d still enabled/registered\n", i);
|
||||
|
||||
dpu_clear_all_irqs(dpu_kms);
|
||||
dpu_disable_all_irqs(dpu_kms);
|
||||
pm_runtime_put_sync(&dpu_kms->pdev->dev);
|
||||
|
||||
kfree(dpu_kms->irq_obj.irq_cb_tbl);
|
||||
kfree(dpu_kms->irq_obj.enable_counts);
|
||||
kfree(dpu_kms->irq_obj.irq_counts);
|
||||
dpu_kms->irq_obj.irq_cb_tbl = NULL;
|
||||
dpu_kms->irq_obj.enable_counts = NULL;
|
||||
dpu_kms->irq_obj.irq_counts = NULL;
|
||||
dpu_kms->irq_obj.total_irqs = 0;
|
||||
}
|
||||
|
||||
irqreturn_t dpu_core_irq(struct dpu_kms *dpu_kms)
|
||||
{
|
||||
/*
|
||||
* Read interrupt status from all sources. Interrupt status are
|
||||
* stored within hw_intr.
|
||||
* Function will also clear the interrupt status after reading.
|
||||
* Individual interrupt status bit will only get stored if it
|
||||
* is enabled.
|
||||
*/
|
||||
dpu_kms->hw_intr->ops.get_interrupt_statuses(dpu_kms->hw_intr);
|
||||
|
||||
/*
|
||||
* Dispatch to HW driver to handle interrupt lookup that is being
|
||||
* fired. When matching interrupt is located, HW driver will call to
|
||||
* dpu_core_irq_callback_handler with the irq_idx from the lookup table.
|
||||
* dpu_core_irq_callback_handler will perform the registered function
|
||||
* callback, and do the interrupt status clearing once the registered
|
||||
* callback is finished.
|
||||
*/
|
||||
dpu_kms->hw_intr->ops.dispatch_irqs(
|
||||
dpu_kms->hw_intr,
|
||||
dpu_core_irq_callback_handler,
|
||||
dpu_kms);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
153
drivers/gpu/drm/msm/disp/dpu1/dpu_core_irq.h
Normal file
153
drivers/gpu/drm/msm/disp/dpu1/dpu_core_irq.h
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef __DPU_CORE_IRQ_H__
|
||||
#define __DPU_CORE_IRQ_H__
|
||||
|
||||
#include "dpu_kms.h"
|
||||
#include "dpu_hw_interrupts.h"
|
||||
|
||||
/**
|
||||
* dpu_core_irq_preinstall - perform pre-installation of core IRQ handler
|
||||
* @dpu_kms: DPU handle
|
||||
* @return: none
|
||||
*/
|
||||
void dpu_core_irq_preinstall(struct dpu_kms *dpu_kms);
|
||||
|
||||
/**
|
||||
* dpu_core_irq_postinstall - perform post-installation of core IRQ handler
|
||||
* @dpu_kms: DPU handle
|
||||
* @return: 0 if success; error code otherwise
|
||||
*/
|
||||
int dpu_core_irq_postinstall(struct dpu_kms *dpu_kms);
|
||||
|
||||
/**
|
||||
* dpu_core_irq_uninstall - uninstall core IRQ handler
|
||||
* @dpu_kms: DPU handle
|
||||
* @return: none
|
||||
*/
|
||||
void dpu_core_irq_uninstall(struct dpu_kms *dpu_kms);
|
||||
|
||||
/**
|
||||
* dpu_core_irq - core IRQ handler
|
||||
* @dpu_kms: DPU handle
|
||||
* @return: interrupt handling status
|
||||
*/
|
||||
irqreturn_t dpu_core_irq(struct dpu_kms *dpu_kms);
|
||||
|
||||
/**
|
||||
* dpu_core_irq_idx_lookup - IRQ helper function for lookup irq_idx from HW
|
||||
* interrupt mapping table.
|
||||
* @dpu_kms: DPU handle
|
||||
* @intr_type: DPU HW interrupt type for lookup
|
||||
* @instance_idx: DPU HW block instance defined in dpu_hw_mdss.h
|
||||
* @return: irq_idx or -EINVAL when fail to lookup
|
||||
*/
|
||||
int dpu_core_irq_idx_lookup(
|
||||
struct dpu_kms *dpu_kms,
|
||||
enum dpu_intr_type intr_type,
|
||||
uint32_t instance_idx);
|
||||
|
||||
/**
|
||||
* dpu_core_irq_enable - IRQ helper function for enabling one or more IRQs
|
||||
* @dpu_kms: DPU handle
|
||||
* @irq_idxs: Array of irq index
|
||||
* @irq_count: Number of irq_idx provided in the array
|
||||
* @return: 0 for success enabling IRQ, otherwise failure
|
||||
*
|
||||
* This function increments count on each enable and decrements on each
|
||||
* disable. Interrupts is enabled if count is 0 before increment.
|
||||
*/
|
||||
int dpu_core_irq_enable(
|
||||
struct dpu_kms *dpu_kms,
|
||||
int *irq_idxs,
|
||||
uint32_t irq_count);
|
||||
|
||||
/**
|
||||
* dpu_core_irq_disable - IRQ helper function for disabling one of more IRQs
|
||||
* @dpu_kms: DPU handle
|
||||
* @irq_idxs: Array of irq index
|
||||
* @irq_count: Number of irq_idx provided in the array
|
||||
* @return: 0 for success disabling IRQ, otherwise failure
|
||||
*
|
||||
* This function increments count on each enable and decrements on each
|
||||
* disable. Interrupts is disabled if count is 0 after decrement.
|
||||
*/
|
||||
int dpu_core_irq_disable(
|
||||
struct dpu_kms *dpu_kms,
|
||||
int *irq_idxs,
|
||||
uint32_t irq_count);
|
||||
|
||||
/**
|
||||
* dpu_core_irq_read - IRQ helper function for reading IRQ status
|
||||
* @dpu_kms: DPU handle
|
||||
* @irq_idx: irq index
|
||||
* @clear: True to clear the irq after read
|
||||
* @return: non-zero if irq detected; otherwise no irq detected
|
||||
*/
|
||||
u32 dpu_core_irq_read(
|
||||
struct dpu_kms *dpu_kms,
|
||||
int irq_idx,
|
||||
bool clear);
|
||||
|
||||
/**
|
||||
* dpu_core_irq_register_callback - For registering callback function on IRQ
|
||||
* interrupt
|
||||
* @dpu_kms: DPU handle
|
||||
* @irq_idx: irq index
|
||||
* @irq_cb: IRQ callback structure, containing callback function
|
||||
* and argument. Passing NULL for irq_cb will unregister
|
||||
* the callback for the given irq_idx
|
||||
* This must exist until un-registration.
|
||||
* @return: 0 for success registering callback, otherwise failure
|
||||
*
|
||||
* This function supports registration of multiple callbacks for each interrupt.
|
||||
*/
|
||||
int dpu_core_irq_register_callback(
|
||||
struct dpu_kms *dpu_kms,
|
||||
int irq_idx,
|
||||
struct dpu_irq_callback *irq_cb);
|
||||
|
||||
/**
|
||||
* dpu_core_irq_unregister_callback - For unregistering callback function on IRQ
|
||||
* interrupt
|
||||
* @dpu_kms: DPU handle
|
||||
* @irq_idx: irq index
|
||||
* @irq_cb: IRQ callback structure, containing callback function
|
||||
* and argument. Passing NULL for irq_cb will unregister
|
||||
* the callback for the given irq_idx
|
||||
* This must match with registration.
|
||||
* @return: 0 for success registering callback, otherwise failure
|
||||
*
|
||||
* This function supports registration of multiple callbacks for each interrupt.
|
||||
*/
|
||||
int dpu_core_irq_unregister_callback(
|
||||
struct dpu_kms *dpu_kms,
|
||||
int irq_idx,
|
||||
struct dpu_irq_callback *irq_cb);
|
||||
|
||||
/**
|
||||
* dpu_debugfs_core_irq_init - register core irq debugfs
|
||||
* @dpu_kms: pointer to kms
|
||||
* @parent: debugfs directory root
|
||||
* @Return: 0 on success
|
||||
*/
|
||||
int dpu_debugfs_core_irq_init(struct dpu_kms *dpu_kms,
|
||||
struct dentry *parent);
|
||||
|
||||
/**
|
||||
* dpu_debugfs_core_irq_destroy - deregister core irq debugfs
|
||||
* @dpu_kms: pointer to kms
|
||||
*/
|
||||
void dpu_debugfs_core_irq_destroy(struct dpu_kms *dpu_kms);
|
||||
|
||||
#endif /* __DPU_CORE_IRQ_H__ */
|
||||
637
drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c
Normal file
637
drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c
Normal file
|
|
@ -0,0 +1,637 @@
|
|||
/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sort.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/bitmap.h>
|
||||
|
||||
#include "dpu_kms.h"
|
||||
#include "dpu_trace.h"
|
||||
#include "dpu_crtc.h"
|
||||
#include "dpu_core_perf.h"
|
||||
|
||||
#define DPU_PERF_MODE_STRING_SIZE 128
|
||||
|
||||
/**
|
||||
* enum dpu_perf_mode - performance tuning mode
|
||||
* @DPU_PERF_MODE_NORMAL: performance controlled by user mode client
|
||||
* @DPU_PERF_MODE_MINIMUM: performance bounded by minimum setting
|
||||
* @DPU_PERF_MODE_FIXED: performance bounded by fixed setting
|
||||
*/
|
||||
enum dpu_perf_mode {
|
||||
DPU_PERF_MODE_NORMAL,
|
||||
DPU_PERF_MODE_MINIMUM,
|
||||
DPU_PERF_MODE_FIXED,
|
||||
DPU_PERF_MODE_MAX
|
||||
};
|
||||
|
||||
static struct dpu_kms *_dpu_crtc_get_kms(struct drm_crtc *crtc)
|
||||
{
|
||||
struct msm_drm_private *priv;
|
||||
|
||||
if (!crtc->dev || !crtc->dev->dev_private) {
|
||||
DPU_ERROR("invalid device\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
priv = crtc->dev->dev_private;
|
||||
if (!priv || !priv->kms) {
|
||||
DPU_ERROR("invalid kms\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return to_dpu_kms(priv->kms);
|
||||
}
|
||||
|
||||
static bool _dpu_core_perf_crtc_is_power_on(struct drm_crtc *crtc)
|
||||
{
|
||||
return dpu_crtc_is_enabled(crtc);
|
||||
}
|
||||
|
||||
static bool _dpu_core_video_mode_intf_connected(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_crtc *tmp_crtc;
|
||||
bool intf_connected = false;
|
||||
|
||||
if (!crtc)
|
||||
goto end;
|
||||
|
||||
drm_for_each_crtc(tmp_crtc, crtc->dev) {
|
||||
if ((dpu_crtc_get_intf_mode(tmp_crtc) == INTF_MODE_VIDEO) &&
|
||||
_dpu_core_perf_crtc_is_power_on(tmp_crtc)) {
|
||||
DPU_DEBUG("video interface connected crtc:%d\n",
|
||||
tmp_crtc->base.id);
|
||||
intf_connected = true;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
return intf_connected;
|
||||
}
|
||||
|
||||
static void _dpu_core_perf_calc_crtc(struct dpu_kms *kms,
|
||||
struct drm_crtc *crtc,
|
||||
struct drm_crtc_state *state,
|
||||
struct dpu_core_perf_params *perf)
|
||||
{
|
||||
struct dpu_crtc_state *dpu_cstate;
|
||||
int i;
|
||||
|
||||
if (!kms || !kms->catalog || !crtc || !state || !perf) {
|
||||
DPU_ERROR("invalid parameters\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dpu_cstate = to_dpu_crtc_state(state);
|
||||
memset(perf, 0, sizeof(struct dpu_core_perf_params));
|
||||
|
||||
if (!dpu_cstate->bw_control) {
|
||||
for (i = 0; i < DPU_POWER_HANDLE_DBUS_ID_MAX; i++) {
|
||||
perf->bw_ctl[i] = kms->catalog->perf.max_bw_high *
|
||||
1000ULL;
|
||||
perf->max_per_pipe_ib[i] = perf->bw_ctl[i];
|
||||
}
|
||||
perf->core_clk_rate = kms->perf.max_core_clk_rate;
|
||||
} else if (kms->perf.perf_tune.mode == DPU_PERF_MODE_MINIMUM) {
|
||||
for (i = 0; i < DPU_POWER_HANDLE_DBUS_ID_MAX; i++) {
|
||||
perf->bw_ctl[i] = 0;
|
||||
perf->max_per_pipe_ib[i] = 0;
|
||||
}
|
||||
perf->core_clk_rate = 0;
|
||||
} else if (kms->perf.perf_tune.mode == DPU_PERF_MODE_FIXED) {
|
||||
for (i = 0; i < DPU_POWER_HANDLE_DBUS_ID_MAX; i++) {
|
||||
perf->bw_ctl[i] = kms->perf.fix_core_ab_vote;
|
||||
perf->max_per_pipe_ib[i] = kms->perf.fix_core_ib_vote;
|
||||
}
|
||||
perf->core_clk_rate = kms->perf.fix_core_clk_rate;
|
||||
}
|
||||
|
||||
DPU_DEBUG(
|
||||
"crtc=%d clk_rate=%llu core_ib=%llu core_ab=%llu llcc_ib=%llu llcc_ab=%llu mem_ib=%llu mem_ab=%llu\n",
|
||||
crtc->base.id, perf->core_clk_rate,
|
||||
perf->max_per_pipe_ib[DPU_POWER_HANDLE_DBUS_ID_MNOC],
|
||||
perf->bw_ctl[DPU_POWER_HANDLE_DBUS_ID_MNOC],
|
||||
perf->max_per_pipe_ib[DPU_POWER_HANDLE_DBUS_ID_LLCC],
|
||||
perf->bw_ctl[DPU_POWER_HANDLE_DBUS_ID_LLCC],
|
||||
perf->max_per_pipe_ib[DPU_POWER_HANDLE_DBUS_ID_EBI],
|
||||
perf->bw_ctl[DPU_POWER_HANDLE_DBUS_ID_EBI]);
|
||||
}
|
||||
|
||||
int dpu_core_perf_crtc_check(struct drm_crtc *crtc,
|
||||
struct drm_crtc_state *state)
|
||||
{
|
||||
u32 bw, threshold;
|
||||
u64 bw_sum_of_intfs = 0;
|
||||
enum dpu_crtc_client_type curr_client_type;
|
||||
bool is_video_mode;
|
||||
struct dpu_crtc_state *dpu_cstate;
|
||||
struct drm_crtc *tmp_crtc;
|
||||
struct dpu_kms *kms;
|
||||
int i;
|
||||
|
||||
if (!crtc || !state) {
|
||||
DPU_ERROR("invalid crtc\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
kms = _dpu_crtc_get_kms(crtc);
|
||||
if (!kms || !kms->catalog) {
|
||||
DPU_ERROR("invalid parameters\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* we only need bandwidth check on real-time clients (interfaces) */
|
||||
if (dpu_crtc_get_client_type(crtc) == NRT_CLIENT)
|
||||
return 0;
|
||||
|
||||
dpu_cstate = to_dpu_crtc_state(state);
|
||||
|
||||
/* obtain new values */
|
||||
_dpu_core_perf_calc_crtc(kms, crtc, state, &dpu_cstate->new_perf);
|
||||
|
||||
for (i = DPU_POWER_HANDLE_DBUS_ID_MNOC;
|
||||
i < DPU_POWER_HANDLE_DBUS_ID_MAX; i++) {
|
||||
bw_sum_of_intfs = dpu_cstate->new_perf.bw_ctl[i];
|
||||
curr_client_type = dpu_crtc_get_client_type(crtc);
|
||||
|
||||
drm_for_each_crtc(tmp_crtc, crtc->dev) {
|
||||
if (_dpu_core_perf_crtc_is_power_on(tmp_crtc) &&
|
||||
(dpu_crtc_get_client_type(tmp_crtc) ==
|
||||
curr_client_type) &&
|
||||
(tmp_crtc != crtc)) {
|
||||
struct dpu_crtc_state *tmp_cstate =
|
||||
to_dpu_crtc_state(tmp_crtc->state);
|
||||
|
||||
DPU_DEBUG("crtc:%d bw:%llu ctrl:%d\n",
|
||||
tmp_crtc->base.id,
|
||||
tmp_cstate->new_perf.bw_ctl[i],
|
||||
tmp_cstate->bw_control);
|
||||
/*
|
||||
* For bw check only use the bw if the
|
||||
* atomic property has been already set
|
||||
*/
|
||||
if (tmp_cstate->bw_control)
|
||||
bw_sum_of_intfs +=
|
||||
tmp_cstate->new_perf.bw_ctl[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* convert bandwidth to kb */
|
||||
bw = DIV_ROUND_UP_ULL(bw_sum_of_intfs, 1000);
|
||||
DPU_DEBUG("calculated bandwidth=%uk\n", bw);
|
||||
|
||||
is_video_mode = dpu_crtc_get_intf_mode(crtc) == INTF_MODE_VIDEO;
|
||||
threshold = (is_video_mode ||
|
||||
_dpu_core_video_mode_intf_connected(crtc)) ?
|
||||
kms->catalog->perf.max_bw_low :
|
||||
kms->catalog->perf.max_bw_high;
|
||||
|
||||
DPU_DEBUG("final threshold bw limit = %d\n", threshold);
|
||||
|
||||
if (!dpu_cstate->bw_control) {
|
||||
DPU_DEBUG("bypass bandwidth check\n");
|
||||
} else if (!threshold) {
|
||||
DPU_ERROR("no bandwidth limits specified\n");
|
||||
return -E2BIG;
|
||||
} else if (bw > threshold) {
|
||||
DPU_ERROR("exceeds bandwidth: %ukb > %ukb\n", bw,
|
||||
threshold);
|
||||
return -E2BIG;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _dpu_core_perf_crtc_update_bus(struct dpu_kms *kms,
|
||||
struct drm_crtc *crtc, u32 bus_id)
|
||||
{
|
||||
struct dpu_core_perf_params perf = { { 0 } };
|
||||
enum dpu_crtc_client_type curr_client_type
|
||||
= dpu_crtc_get_client_type(crtc);
|
||||
struct drm_crtc *tmp_crtc;
|
||||
struct dpu_crtc_state *dpu_cstate;
|
||||
int ret = 0;
|
||||
|
||||
drm_for_each_crtc(tmp_crtc, crtc->dev) {
|
||||
if (_dpu_core_perf_crtc_is_power_on(tmp_crtc) &&
|
||||
curr_client_type ==
|
||||
dpu_crtc_get_client_type(tmp_crtc)) {
|
||||
dpu_cstate = to_dpu_crtc_state(tmp_crtc->state);
|
||||
|
||||
perf.max_per_pipe_ib[bus_id] =
|
||||
max(perf.max_per_pipe_ib[bus_id],
|
||||
dpu_cstate->new_perf.max_per_pipe_ib[bus_id]);
|
||||
|
||||
DPU_DEBUG("crtc=%d bus_id=%d bw=%llu\n",
|
||||
tmp_crtc->base.id, bus_id,
|
||||
dpu_cstate->new_perf.bw_ctl[bus_id]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dpu_core_perf_crtc_release_bw() - request zero bandwidth
|
||||
* @crtc - pointer to a crtc
|
||||
*
|
||||
* Function checks a state variable for the crtc, if all pending commit
|
||||
* requests are done, meaning no more bandwidth is needed, release
|
||||
* bandwidth request.
|
||||
*/
|
||||
void dpu_core_perf_crtc_release_bw(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_crtc *tmp_crtc;
|
||||
struct dpu_crtc *dpu_crtc;
|
||||
struct dpu_crtc_state *dpu_cstate;
|
||||
struct dpu_kms *kms;
|
||||
int i;
|
||||
|
||||
if (!crtc) {
|
||||
DPU_ERROR("invalid crtc\n");
|
||||
return;
|
||||
}
|
||||
|
||||
kms = _dpu_crtc_get_kms(crtc);
|
||||
if (!kms || !kms->catalog) {
|
||||
DPU_ERROR("invalid kms\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dpu_crtc = to_dpu_crtc(crtc);
|
||||
dpu_cstate = to_dpu_crtc_state(crtc->state);
|
||||
|
||||
/* only do this for command mode rt client */
|
||||
if (dpu_crtc_get_intf_mode(crtc) != INTF_MODE_CMD)
|
||||
return;
|
||||
|
||||
/*
|
||||
* If video interface present, cmd panel bandwidth cannot be
|
||||
* released.
|
||||
*/
|
||||
if (dpu_crtc_get_intf_mode(crtc) == INTF_MODE_CMD)
|
||||
drm_for_each_crtc(tmp_crtc, crtc->dev) {
|
||||
if (_dpu_core_perf_crtc_is_power_on(tmp_crtc) &&
|
||||
dpu_crtc_get_intf_mode(tmp_crtc) ==
|
||||
INTF_MODE_VIDEO)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Release the bandwidth */
|
||||
if (kms->perf.enable_bw_release) {
|
||||
trace_dpu_cmd_release_bw(crtc->base.id);
|
||||
DPU_DEBUG("Release BW crtc=%d\n", crtc->base.id);
|
||||
for (i = 0; i < DPU_POWER_HANDLE_DBUS_ID_MAX; i++) {
|
||||
dpu_crtc->cur_perf.bw_ctl[i] = 0;
|
||||
_dpu_core_perf_crtc_update_bus(kms, crtc, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int _dpu_core_perf_set_core_clk_rate(struct dpu_kms *kms, u64 rate)
|
||||
{
|
||||
struct dss_clk *core_clk = kms->perf.core_clk;
|
||||
|
||||
if (core_clk->max_rate && (rate > core_clk->max_rate))
|
||||
rate = core_clk->max_rate;
|
||||
|
||||
core_clk->rate = rate;
|
||||
return msm_dss_clk_set_rate(core_clk, 1);
|
||||
}
|
||||
|
||||
static u64 _dpu_core_perf_get_core_clk_rate(struct dpu_kms *kms)
|
||||
{
|
||||
u64 clk_rate = kms->perf.perf_tune.min_core_clk;
|
||||
struct drm_crtc *crtc;
|
||||
struct dpu_crtc_state *dpu_cstate;
|
||||
|
||||
drm_for_each_crtc(crtc, kms->dev) {
|
||||
if (_dpu_core_perf_crtc_is_power_on(crtc)) {
|
||||
dpu_cstate = to_dpu_crtc_state(crtc->state);
|
||||
clk_rate = max(dpu_cstate->new_perf.core_clk_rate,
|
||||
clk_rate);
|
||||
clk_rate = clk_round_rate(kms->perf.core_clk->clk,
|
||||
clk_rate);
|
||||
}
|
||||
}
|
||||
|
||||
if (kms->perf.perf_tune.mode == DPU_PERF_MODE_FIXED)
|
||||
clk_rate = kms->perf.fix_core_clk_rate;
|
||||
|
||||
DPU_DEBUG("clk:%llu\n", clk_rate);
|
||||
|
||||
return clk_rate;
|
||||
}
|
||||
|
||||
int dpu_core_perf_crtc_update(struct drm_crtc *crtc,
|
||||
int params_changed, bool stop_req)
|
||||
{
|
||||
struct dpu_core_perf_params *new, *old;
|
||||
int update_bus = 0, update_clk = 0;
|
||||
u64 clk_rate = 0;
|
||||
struct dpu_crtc *dpu_crtc;
|
||||
struct dpu_crtc_state *dpu_cstate;
|
||||
int i;
|
||||
struct msm_drm_private *priv;
|
||||
struct dpu_kms *kms;
|
||||
int ret;
|
||||
|
||||
if (!crtc) {
|
||||
DPU_ERROR("invalid crtc\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
kms = _dpu_crtc_get_kms(crtc);
|
||||
if (!kms || !kms->catalog) {
|
||||
DPU_ERROR("invalid kms\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
priv = kms->dev->dev_private;
|
||||
|
||||
dpu_crtc = to_dpu_crtc(crtc);
|
||||
dpu_cstate = to_dpu_crtc_state(crtc->state);
|
||||
|
||||
DPU_DEBUG("crtc:%d stop_req:%d core_clk:%llu\n",
|
||||
crtc->base.id, stop_req, kms->perf.core_clk_rate);
|
||||
|
||||
old = &dpu_crtc->cur_perf;
|
||||
new = &dpu_cstate->new_perf;
|
||||
|
||||
if (_dpu_core_perf_crtc_is_power_on(crtc) && !stop_req) {
|
||||
for (i = 0; i < DPU_POWER_HANDLE_DBUS_ID_MAX; i++) {
|
||||
/*
|
||||
* cases for bus bandwidth update.
|
||||
* 1. new bandwidth vote - "ab or ib vote" is higher
|
||||
* than current vote for update request.
|
||||
* 2. new bandwidth vote - "ab or ib vote" is lower
|
||||
* than current vote at end of commit or stop.
|
||||
*/
|
||||
if ((params_changed && ((new->bw_ctl[i] >
|
||||
old->bw_ctl[i]) ||
|
||||
(new->max_per_pipe_ib[i] >
|
||||
old->max_per_pipe_ib[i]))) ||
|
||||
(!params_changed && ((new->bw_ctl[i] <
|
||||
old->bw_ctl[i]) ||
|
||||
(new->max_per_pipe_ib[i] <
|
||||
old->max_per_pipe_ib[i])))) {
|
||||
DPU_DEBUG(
|
||||
"crtc=%d p=%d new_bw=%llu,old_bw=%llu\n",
|
||||
crtc->base.id, params_changed,
|
||||
new->bw_ctl[i], old->bw_ctl[i]);
|
||||
old->bw_ctl[i] = new->bw_ctl[i];
|
||||
old->max_per_pipe_ib[i] =
|
||||
new->max_per_pipe_ib[i];
|
||||
update_bus |= BIT(i);
|
||||
}
|
||||
}
|
||||
|
||||
if ((params_changed &&
|
||||
(new->core_clk_rate > old->core_clk_rate)) ||
|
||||
(!params_changed &&
|
||||
(new->core_clk_rate < old->core_clk_rate))) {
|
||||
old->core_clk_rate = new->core_clk_rate;
|
||||
update_clk = 1;
|
||||
}
|
||||
} else {
|
||||
DPU_DEBUG("crtc=%d disable\n", crtc->base.id);
|
||||
memset(old, 0, sizeof(*old));
|
||||
memset(new, 0, sizeof(*new));
|
||||
update_bus = ~0;
|
||||
update_clk = 1;
|
||||
}
|
||||
trace_dpu_perf_crtc_update(crtc->base.id,
|
||||
new->bw_ctl[DPU_POWER_HANDLE_DBUS_ID_MNOC],
|
||||
new->bw_ctl[DPU_POWER_HANDLE_DBUS_ID_LLCC],
|
||||
new->bw_ctl[DPU_POWER_HANDLE_DBUS_ID_EBI],
|
||||
new->core_clk_rate, stop_req,
|
||||
update_bus, update_clk);
|
||||
|
||||
for (i = 0; i < DPU_POWER_HANDLE_DBUS_ID_MAX; i++) {
|
||||
if (update_bus & BIT(i)) {
|
||||
ret = _dpu_core_perf_crtc_update_bus(kms, crtc, i);
|
||||
if (ret) {
|
||||
DPU_ERROR("crtc-%d: failed to update bw vote for bus-%d\n",
|
||||
crtc->base.id, i);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the clock after bandwidth vote to ensure
|
||||
* bandwidth is available before clock rate is increased.
|
||||
*/
|
||||
if (update_clk) {
|
||||
clk_rate = _dpu_core_perf_get_core_clk_rate(kms);
|
||||
|
||||
trace_dpu_core_perf_update_clk(kms->dev, stop_req, clk_rate);
|
||||
|
||||
ret = _dpu_core_perf_set_core_clk_rate(kms, clk_rate);
|
||||
if (ret) {
|
||||
DPU_ERROR("failed to set %s clock rate %llu\n",
|
||||
kms->perf.core_clk->clk_name, clk_rate);
|
||||
return ret;
|
||||
}
|
||||
|
||||
kms->perf.core_clk_rate = clk_rate;
|
||||
DPU_DEBUG("update clk rate = %lld HZ\n", clk_rate);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
||||
static ssize_t _dpu_core_perf_mode_write(struct file *file,
|
||||
const char __user *user_buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct dpu_core_perf *perf = file->private_data;
|
||||
struct dpu_perf_cfg *cfg = &perf->catalog->perf;
|
||||
u32 perf_mode = 0;
|
||||
char buf[10];
|
||||
|
||||
if (!perf)
|
||||
return -ENODEV;
|
||||
|
||||
if (count >= sizeof(buf))
|
||||
return -EFAULT;
|
||||
|
||||
if (copy_from_user(buf, user_buf, count))
|
||||
return -EFAULT;
|
||||
|
||||
buf[count] = 0; /* end of string */
|
||||
|
||||
if (kstrtouint(buf, 0, &perf_mode))
|
||||
return -EFAULT;
|
||||
|
||||
if (perf_mode >= DPU_PERF_MODE_MAX)
|
||||
return -EFAULT;
|
||||
|
||||
if (perf_mode == DPU_PERF_MODE_FIXED) {
|
||||
DRM_INFO("fix performance mode\n");
|
||||
} else if (perf_mode == DPU_PERF_MODE_MINIMUM) {
|
||||
/* run the driver with max clk and BW vote */
|
||||
perf->perf_tune.min_core_clk = perf->max_core_clk_rate;
|
||||
perf->perf_tune.min_bus_vote =
|
||||
(u64) cfg->max_bw_high * 1000;
|
||||
DRM_INFO("minimum performance mode\n");
|
||||
} else if (perf_mode == DPU_PERF_MODE_NORMAL) {
|
||||
/* reset the perf tune params to 0 */
|
||||
perf->perf_tune.min_core_clk = 0;
|
||||
perf->perf_tune.min_bus_vote = 0;
|
||||
DRM_INFO("normal performance mode\n");
|
||||
}
|
||||
perf->perf_tune.mode = perf_mode;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t _dpu_core_perf_mode_read(struct file *file,
|
||||
char __user *buff, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct dpu_core_perf *perf = file->private_data;
|
||||
int len = 0;
|
||||
char buf[DPU_PERF_MODE_STRING_SIZE] = {'\0'};
|
||||
|
||||
if (!perf)
|
||||
return -ENODEV;
|
||||
|
||||
if (*ppos)
|
||||
return 0; /* the end */
|
||||
|
||||
len = snprintf(buf, sizeof(buf),
|
||||
"mode %d min_mdp_clk %llu min_bus_vote %llu\n",
|
||||
perf->perf_tune.mode,
|
||||
perf->perf_tune.min_core_clk,
|
||||
perf->perf_tune.min_bus_vote);
|
||||
if (len < 0 || len >= sizeof(buf))
|
||||
return 0;
|
||||
|
||||
if ((count < sizeof(buf)) || copy_to_user(buff, buf, len))
|
||||
return -EFAULT;
|
||||
|
||||
*ppos += len; /* increase offset */
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static const struct file_operations dpu_core_perf_mode_fops = {
|
||||
.open = simple_open,
|
||||
.read = _dpu_core_perf_mode_read,
|
||||
.write = _dpu_core_perf_mode_write,
|
||||
};
|
||||
|
||||
static void dpu_core_perf_debugfs_destroy(struct dpu_core_perf *perf)
|
||||
{
|
||||
debugfs_remove_recursive(perf->debugfs_root);
|
||||
perf->debugfs_root = NULL;
|
||||
}
|
||||
|
||||
int dpu_core_perf_debugfs_init(struct dpu_core_perf *perf,
|
||||
struct dentry *parent)
|
||||
{
|
||||
struct dpu_mdss_cfg *catalog = perf->catalog;
|
||||
struct msm_drm_private *priv;
|
||||
struct dpu_kms *dpu_kms;
|
||||
|
||||
priv = perf->dev->dev_private;
|
||||
if (!priv || !priv->kms) {
|
||||
DPU_ERROR("invalid KMS reference\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dpu_kms = to_dpu_kms(priv->kms);
|
||||
|
||||
perf->debugfs_root = debugfs_create_dir("core_perf", parent);
|
||||
if (!perf->debugfs_root) {
|
||||
DPU_ERROR("failed to create core perf debugfs\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
debugfs_create_u64("max_core_clk_rate", 0600, perf->debugfs_root,
|
||||
&perf->max_core_clk_rate);
|
||||
debugfs_create_u64("core_clk_rate", 0600, perf->debugfs_root,
|
||||
&perf->core_clk_rate);
|
||||
debugfs_create_u32("enable_bw_release", 0600, perf->debugfs_root,
|
||||
(u32 *)&perf->enable_bw_release);
|
||||
debugfs_create_u32("threshold_low", 0600, perf->debugfs_root,
|
||||
(u32 *)&catalog->perf.max_bw_low);
|
||||
debugfs_create_u32("threshold_high", 0600, perf->debugfs_root,
|
||||
(u32 *)&catalog->perf.max_bw_high);
|
||||
debugfs_create_u32("min_core_ib", 0600, perf->debugfs_root,
|
||||
(u32 *)&catalog->perf.min_core_ib);
|
||||
debugfs_create_u32("min_llcc_ib", 0600, perf->debugfs_root,
|
||||
(u32 *)&catalog->perf.min_llcc_ib);
|
||||
debugfs_create_u32("min_dram_ib", 0600, perf->debugfs_root,
|
||||
(u32 *)&catalog->perf.min_dram_ib);
|
||||
debugfs_create_file("perf_mode", 0600, perf->debugfs_root,
|
||||
(u32 *)perf, &dpu_core_perf_mode_fops);
|
||||
debugfs_create_u64("fix_core_clk_rate", 0600, perf->debugfs_root,
|
||||
&perf->fix_core_clk_rate);
|
||||
debugfs_create_u64("fix_core_ib_vote", 0600, perf->debugfs_root,
|
||||
&perf->fix_core_ib_vote);
|
||||
debugfs_create_u64("fix_core_ab_vote", 0600, perf->debugfs_root,
|
||||
&perf->fix_core_ab_vote);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static void dpu_core_perf_debugfs_destroy(struct dpu_core_perf *perf)
|
||||
{
|
||||
}
|
||||
|
||||
int dpu_core_perf_debugfs_init(struct dpu_core_perf *perf,
|
||||
struct dentry *parent)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void dpu_core_perf_destroy(struct dpu_core_perf *perf)
|
||||
{
|
||||
if (!perf) {
|
||||
DPU_ERROR("invalid parameters\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dpu_core_perf_debugfs_destroy(perf);
|
||||
perf->max_core_clk_rate = 0;
|
||||
perf->core_clk = NULL;
|
||||
perf->phandle = NULL;
|
||||
perf->catalog = NULL;
|
||||
perf->dev = NULL;
|
||||
}
|
||||
|
||||
int dpu_core_perf_init(struct dpu_core_perf *perf,
|
||||
struct drm_device *dev,
|
||||
struct dpu_mdss_cfg *catalog,
|
||||
struct dpu_power_handle *phandle,
|
||||
struct dss_clk *core_clk)
|
||||
{
|
||||
perf->dev = dev;
|
||||
perf->catalog = catalog;
|
||||
perf->phandle = phandle;
|
||||
perf->core_clk = core_clk;
|
||||
|
||||
perf->max_core_clk_rate = core_clk->max_rate;
|
||||
if (!perf->max_core_clk_rate) {
|
||||
DPU_DEBUG("optional max core clk rate, use default\n");
|
||||
perf->max_core_clk_rate = DPU_PERF_DEFAULT_MAX_CORE_CLK_RATE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
133
drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.h
Normal file
133
drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.h
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _DPU_CORE_PERF_H_
|
||||
#define _DPU_CORE_PERF_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
|
||||
#include "dpu_hw_catalog.h"
|
||||
#include "dpu_power_handle.h"
|
||||
|
||||
#define DPU_PERF_DEFAULT_MAX_CORE_CLK_RATE 412500000
|
||||
|
||||
/**
|
||||
* struct dpu_core_perf_params - definition of performance parameters
|
||||
* @max_per_pipe_ib: maximum instantaneous bandwidth request
|
||||
* @bw_ctl: arbitrated bandwidth request
|
||||
* @core_clk_rate: core clock rate request
|
||||
*/
|
||||
struct dpu_core_perf_params {
|
||||
u64 max_per_pipe_ib[DPU_POWER_HANDLE_DBUS_ID_MAX];
|
||||
u64 bw_ctl[DPU_POWER_HANDLE_DBUS_ID_MAX];
|
||||
u64 core_clk_rate;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_core_perf_tune - definition of performance tuning control
|
||||
* @mode: performance mode
|
||||
* @min_core_clk: minimum core clock
|
||||
* @min_bus_vote: minimum bus vote
|
||||
*/
|
||||
struct dpu_core_perf_tune {
|
||||
u32 mode;
|
||||
u64 min_core_clk;
|
||||
u64 min_bus_vote;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_core_perf - definition of core performance context
|
||||
* @dev: Pointer to drm device
|
||||
* @debugfs_root: top level debug folder
|
||||
* @catalog: Pointer to catalog configuration
|
||||
* @phandle: Pointer to power handler
|
||||
* @core_clk: Pointer to core clock structure
|
||||
* @core_clk_rate: current core clock rate
|
||||
* @max_core_clk_rate: maximum allowable core clock rate
|
||||
* @perf_tune: debug control for performance tuning
|
||||
* @enable_bw_release: debug control for bandwidth release
|
||||
* @fix_core_clk_rate: fixed core clock request in Hz used in mode 2
|
||||
* @fix_core_ib_vote: fixed core ib vote in bps used in mode 2
|
||||
* @fix_core_ab_vote: fixed core ab vote in bps used in mode 2
|
||||
*/
|
||||
struct dpu_core_perf {
|
||||
struct drm_device *dev;
|
||||
struct dentry *debugfs_root;
|
||||
struct dpu_mdss_cfg *catalog;
|
||||
struct dpu_power_handle *phandle;
|
||||
struct dss_clk *core_clk;
|
||||
u64 core_clk_rate;
|
||||
u64 max_core_clk_rate;
|
||||
struct dpu_core_perf_tune perf_tune;
|
||||
u32 enable_bw_release;
|
||||
u64 fix_core_clk_rate;
|
||||
u64 fix_core_ib_vote;
|
||||
u64 fix_core_ab_vote;
|
||||
};
|
||||
|
||||
/**
|
||||
* dpu_core_perf_crtc_check - validate performance of the given crtc state
|
||||
* @crtc: Pointer to crtc
|
||||
* @state: Pointer to new crtc state
|
||||
* return: zero if success, or error code otherwise
|
||||
*/
|
||||
int dpu_core_perf_crtc_check(struct drm_crtc *crtc,
|
||||
struct drm_crtc_state *state);
|
||||
|
||||
/**
|
||||
* dpu_core_perf_crtc_update - update performance of the given crtc
|
||||
* @crtc: Pointer to crtc
|
||||
* @params_changed: true if crtc parameters are modified
|
||||
* @stop_req: true if this is a stop request
|
||||
* return: zero if success, or error code otherwise
|
||||
*/
|
||||
int dpu_core_perf_crtc_update(struct drm_crtc *crtc,
|
||||
int params_changed, bool stop_req);
|
||||
|
||||
/**
|
||||
* dpu_core_perf_crtc_release_bw - release bandwidth of the given crtc
|
||||
* @crtc: Pointer to crtc
|
||||
*/
|
||||
void dpu_core_perf_crtc_release_bw(struct drm_crtc *crtc);
|
||||
|
||||
/**
|
||||
* dpu_core_perf_destroy - destroy the given core performance context
|
||||
* @perf: Pointer to core performance context
|
||||
*/
|
||||
void dpu_core_perf_destroy(struct dpu_core_perf *perf);
|
||||
|
||||
/**
|
||||
* dpu_core_perf_init - initialize the given core performance context
|
||||
* @perf: Pointer to core performance context
|
||||
* @dev: Pointer to drm device
|
||||
* @catalog: Pointer to catalog
|
||||
* @phandle: Pointer to power handle
|
||||
* @core_clk: pointer to core clock
|
||||
*/
|
||||
int dpu_core_perf_init(struct dpu_core_perf *perf,
|
||||
struct drm_device *dev,
|
||||
struct dpu_mdss_cfg *catalog,
|
||||
struct dpu_power_handle *phandle,
|
||||
struct dss_clk *core_clk);
|
||||
|
||||
/**
|
||||
* dpu_core_perf_debugfs_init - initialize debugfs for core performance context
|
||||
* @perf: Pointer to core performance context
|
||||
* @debugfs_parent: Pointer to parent debugfs
|
||||
*/
|
||||
int dpu_core_perf_debugfs_init(struct dpu_core_perf *perf,
|
||||
struct dentry *parent);
|
||||
|
||||
#endif /* _DPU_CORE_PERF_H_ */
|
||||
2504
drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
Normal file
2504
drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
Normal file
File diff suppressed because it is too large
Load diff
484
drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.h
Normal file
484
drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.h
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2018 The Linux Foundation. All rights reserved.
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _DPU_CRTC_H_
|
||||
#define _DPU_CRTC_H_
|
||||
|
||||
#include <linux/kthread.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include "dpu_kms.h"
|
||||
#include "dpu_core_perf.h"
|
||||
#include "dpu_hw_blk.h"
|
||||
|
||||
#define DPU_CRTC_NAME_SIZE 12
|
||||
|
||||
/* define the maximum number of in-flight frame events */
|
||||
#define DPU_CRTC_FRAME_EVENT_SIZE 4
|
||||
|
||||
/**
|
||||
* enum dpu_crtc_client_type: crtc client type
|
||||
* @RT_CLIENT: RealTime client like video/cmd mode display
|
||||
* voting through apps rsc
|
||||
* @NRT_CLIENT: Non-RealTime client like WB display
|
||||
* voting through apps rsc
|
||||
*/
|
||||
enum dpu_crtc_client_type {
|
||||
RT_CLIENT,
|
||||
NRT_CLIENT,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum dpu_crtc_smmu_state: smmu state
|
||||
* @ATTACHED: all the context banks are attached.
|
||||
* @DETACHED: all the context banks are detached.
|
||||
* @ATTACH_ALL_REQ: transient state of attaching context banks.
|
||||
* @DETACH_ALL_REQ: transient state of detaching context banks.
|
||||
*/
|
||||
enum dpu_crtc_smmu_state {
|
||||
ATTACHED = 0,
|
||||
DETACHED,
|
||||
ATTACH_ALL_REQ,
|
||||
DETACH_ALL_REQ,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum dpu_crtc_smmu_state_transition_type: state transition type
|
||||
* @NONE: no pending state transitions
|
||||
* @PRE_COMMIT: state transitions should be done before processing the commit
|
||||
* @POST_COMMIT: state transitions to be done after processing the commit.
|
||||
*/
|
||||
enum dpu_crtc_smmu_state_transition_type {
|
||||
NONE,
|
||||
PRE_COMMIT,
|
||||
POST_COMMIT
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_crtc_smmu_state_data: stores the smmu state and transition type
|
||||
* @state: current state of smmu context banks
|
||||
* @transition_type: transition request type
|
||||
* @transition_error: whether there is error while transitioning the state
|
||||
*/
|
||||
struct dpu_crtc_smmu_state_data {
|
||||
uint32_t state;
|
||||
uint32_t transition_type;
|
||||
uint32_t transition_error;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_crtc_mixer: stores the map for each virtual pipeline in the CRTC
|
||||
* @hw_lm: LM HW Driver context
|
||||
* @hw_ctl: CTL Path HW driver context
|
||||
* @encoder: Encoder attached to this lm & ctl
|
||||
* @mixer_op_mode: mixer blending operation mode
|
||||
* @flush_mask: mixer flush mask for ctl, mixer and pipe
|
||||
*/
|
||||
struct dpu_crtc_mixer {
|
||||
struct dpu_hw_mixer *hw_lm;
|
||||
struct dpu_hw_ctl *hw_ctl;
|
||||
struct drm_encoder *encoder;
|
||||
u32 mixer_op_mode;
|
||||
u32 flush_mask;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_crtc_frame_event: stores crtc frame event for crtc processing
|
||||
* @work: base work structure
|
||||
* @crtc: Pointer to crtc handling this event
|
||||
* @list: event list
|
||||
* @ts: timestamp at queue entry
|
||||
* @event: event identifier
|
||||
*/
|
||||
struct dpu_crtc_frame_event {
|
||||
struct kthread_work work;
|
||||
struct drm_crtc *crtc;
|
||||
struct list_head list;
|
||||
ktime_t ts;
|
||||
u32 event;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_crtc_event - event callback tracking structure
|
||||
* @list: Linked list tracking node
|
||||
* @kt_work: Kthread worker structure
|
||||
* @dpu_crtc: Pointer to associated dpu_crtc structure
|
||||
* @cb_func: Pointer to callback function
|
||||
* @usr: Pointer to user data to be provided to the callback
|
||||
*/
|
||||
struct dpu_crtc_event {
|
||||
struct list_head list;
|
||||
struct kthread_work kt_work;
|
||||
void *dpu_crtc;
|
||||
|
||||
void (*cb_func)(struct drm_crtc *crtc, void *usr);
|
||||
void *usr;
|
||||
};
|
||||
|
||||
/*
|
||||
* Maximum number of free event structures to cache
|
||||
*/
|
||||
#define DPU_CRTC_MAX_EVENT_COUNT 16
|
||||
|
||||
/**
|
||||
* struct dpu_crtc - virtualized CRTC data structure
|
||||
* @base : Base drm crtc structure
|
||||
* @name : ASCII description of this crtc
|
||||
* @num_ctls : Number of ctl paths in use
|
||||
* @num_mixers : Number of mixers in use
|
||||
* @mixers_swapped: Whether the mixers have been swapped for left/right update
|
||||
* especially in the case of DSC Merge.
|
||||
* @mixers : List of active mixers
|
||||
* @event : Pointer to last received drm vblank event. If there is a
|
||||
* pending vblank event, this will be non-null.
|
||||
* @vsync_count : Running count of received vsync events
|
||||
* @drm_requested_vblank : Whether vblanks have been enabled in the encoder
|
||||
* @property_info : Opaque structure for generic property support
|
||||
* @property_defaults : Array of default values for generic property support
|
||||
* @stage_cfg : H/w mixer stage configuration
|
||||
* @debugfs_root : Parent of debugfs node
|
||||
* @vblank_cb_count : count of vblank callback since last reset
|
||||
* @play_count : frame count between crtc enable and disable
|
||||
* @vblank_cb_time : ktime at vblank count reset
|
||||
* @vblank_requested : whether the user has requested vblank events
|
||||
* @suspend : whether or not a suspend operation is in progress
|
||||
* @enabled : whether the DPU CRTC is currently enabled. updated in the
|
||||
* commit-thread, not state-swap time which is earlier, so
|
||||
* safe to make decisions on during VBLANK on/off work
|
||||
* @feature_list : list of color processing features supported on a crtc
|
||||
* @active_list : list of color processing features are active
|
||||
* @dirty_list : list of color processing features are dirty
|
||||
* @ad_dirty: list containing ad properties that are dirty
|
||||
* @ad_active: list containing ad properties that are active
|
||||
* @crtc_lock : crtc lock around create, destroy and access.
|
||||
* @frame_pending : Whether or not an update is pending
|
||||
* @frame_events : static allocation of in-flight frame events
|
||||
* @frame_event_list : available frame event list
|
||||
* @spin_lock : spin lock for frame event, transaction status, etc...
|
||||
* @frame_done_comp : for frame_event_done synchronization
|
||||
* @event_thread : Pointer to event handler thread
|
||||
* @event_worker : Event worker queue
|
||||
* @event_cache : Local cache of event worker structures
|
||||
* @event_free_list : List of available event structures
|
||||
* @event_lock : Spinlock around event handling code
|
||||
* @misr_enable : boolean entry indicates misr enable/disable status.
|
||||
* @misr_frame_count : misr frame count provided by client
|
||||
* @misr_data : store misr data before turning off the clocks.
|
||||
* @phandle: Pointer to power handler
|
||||
* @power_event : registered power event handle
|
||||
* @cur_perf : current performance committed to clock/bandwidth driver
|
||||
* @rp_lock : serialization lock for resource pool
|
||||
* @rp_head : list of active resource pool
|
||||
* @scl3_cfg_lut : qseed3 lut config
|
||||
*/
|
||||
struct dpu_crtc {
|
||||
struct drm_crtc base;
|
||||
char name[DPU_CRTC_NAME_SIZE];
|
||||
|
||||
/* HW Resources reserved for the crtc */
|
||||
u32 num_ctls;
|
||||
u32 num_mixers;
|
||||
bool mixers_swapped;
|
||||
struct dpu_crtc_mixer mixers[CRTC_DUAL_MIXERS];
|
||||
struct dpu_hw_scaler3_lut_cfg *scl3_lut_cfg;
|
||||
|
||||
struct drm_pending_vblank_event *event;
|
||||
u32 vsync_count;
|
||||
|
||||
struct dpu_hw_stage_cfg stage_cfg;
|
||||
struct dentry *debugfs_root;
|
||||
|
||||
u32 vblank_cb_count;
|
||||
u64 play_count;
|
||||
ktime_t vblank_cb_time;
|
||||
bool vblank_requested;
|
||||
bool suspend;
|
||||
bool enabled;
|
||||
|
||||
struct list_head feature_list;
|
||||
struct list_head active_list;
|
||||
struct list_head dirty_list;
|
||||
struct list_head ad_dirty;
|
||||
struct list_head ad_active;
|
||||
|
||||
struct mutex crtc_lock;
|
||||
|
||||
atomic_t frame_pending;
|
||||
struct dpu_crtc_frame_event frame_events[DPU_CRTC_FRAME_EVENT_SIZE];
|
||||
struct list_head frame_event_list;
|
||||
spinlock_t spin_lock;
|
||||
struct completion frame_done_comp;
|
||||
|
||||
/* for handling internal event thread */
|
||||
struct dpu_crtc_event event_cache[DPU_CRTC_MAX_EVENT_COUNT];
|
||||
struct list_head event_free_list;
|
||||
spinlock_t event_lock;
|
||||
bool misr_enable;
|
||||
u32 misr_frame_count;
|
||||
u32 misr_data[CRTC_DUAL_MIXERS];
|
||||
|
||||
struct dpu_power_handle *phandle;
|
||||
struct dpu_power_event *power_event;
|
||||
|
||||
struct dpu_core_perf_params cur_perf;
|
||||
|
||||
struct mutex rp_lock;
|
||||
struct list_head rp_head;
|
||||
|
||||
struct dpu_crtc_smmu_state_data smmu_state;
|
||||
};
|
||||
|
||||
#define to_dpu_crtc(x) container_of(x, struct dpu_crtc, base)
|
||||
|
||||
/**
|
||||
* struct dpu_crtc_res_ops - common operations for crtc resources
|
||||
* @get: get given resource
|
||||
* @put: put given resource
|
||||
*/
|
||||
struct dpu_crtc_res_ops {
|
||||
void *(*get)(void *val, u32 type, u64 tag);
|
||||
void (*put)(void *val);
|
||||
};
|
||||
|
||||
#define DPU_CRTC_RES_FLAG_FREE BIT(0)
|
||||
|
||||
/**
|
||||
* struct dpu_crtc_res - definition of crtc resources
|
||||
* @list: list of crtc resource
|
||||
* @type: crtc resource type
|
||||
* @tag: unique identifier per type
|
||||
* @refcount: reference/usage count
|
||||
* @ops: callback operations
|
||||
* @val: resource handle associated with type/tag
|
||||
* @flags: customization flags
|
||||
*/
|
||||
struct dpu_crtc_res {
|
||||
struct list_head list;
|
||||
u32 type;
|
||||
u64 tag;
|
||||
atomic_t refcount;
|
||||
struct dpu_crtc_res_ops ops;
|
||||
void *val;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
/**
|
||||
* dpu_crtc_respool - crtc resource pool
|
||||
* @rp_lock: pointer to serialization lock
|
||||
* @rp_head: pointer to head of active resource pools of this crtc
|
||||
* @rp_list: list of crtc resource pool
|
||||
* @sequence_id: sequence identifier, incremented per state duplication
|
||||
* @res_list: list of resource managed by this resource pool
|
||||
* @ops: resource operations for parent resource pool
|
||||
*/
|
||||
struct dpu_crtc_respool {
|
||||
struct mutex *rp_lock;
|
||||
struct list_head *rp_head;
|
||||
struct list_head rp_list;
|
||||
u32 sequence_id;
|
||||
struct list_head res_list;
|
||||
struct dpu_crtc_res_ops ops;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_crtc_state - dpu container for atomic crtc state
|
||||
* @base: Base drm crtc state structure
|
||||
* @is_ppsplit : Whether current topology requires PPSplit special handling
|
||||
* @bw_control : true if bw/clk controlled by core bw/clk properties
|
||||
* @bw_split_vote : true if bw controlled by llcc/dram bw properties
|
||||
* @lm_bounds : LM boundaries based on current mode full resolution, no ROI.
|
||||
* Origin top left of CRTC.
|
||||
* @property_state: Local storage for msm_prop properties
|
||||
* @property_values: Current crtc property values
|
||||
* @input_fence_timeout_ns : Cached input fence timeout, in ns
|
||||
* @new_perf: new performance state being requested
|
||||
*/
|
||||
struct dpu_crtc_state {
|
||||
struct drm_crtc_state base;
|
||||
|
||||
bool bw_control;
|
||||
bool bw_split_vote;
|
||||
|
||||
bool is_ppsplit;
|
||||
struct drm_rect lm_bounds[CRTC_DUAL_MIXERS];
|
||||
|
||||
uint64_t input_fence_timeout_ns;
|
||||
|
||||
struct dpu_core_perf_params new_perf;
|
||||
struct dpu_crtc_respool rp;
|
||||
};
|
||||
|
||||
#define to_dpu_crtc_state(x) \
|
||||
container_of(x, struct dpu_crtc_state, base)
|
||||
|
||||
/**
|
||||
* dpu_crtc_get_mixer_width - get the mixer width
|
||||
* Mixer width will be same as panel width(/2 for split)
|
||||
*/
|
||||
static inline int dpu_crtc_get_mixer_width(struct dpu_crtc *dpu_crtc,
|
||||
struct dpu_crtc_state *cstate, struct drm_display_mode *mode)
|
||||
{
|
||||
u32 mixer_width;
|
||||
|
||||
if (!dpu_crtc || !cstate || !mode)
|
||||
return 0;
|
||||
|
||||
mixer_width = (dpu_crtc->num_mixers == CRTC_DUAL_MIXERS ?
|
||||
mode->hdisplay / CRTC_DUAL_MIXERS : mode->hdisplay);
|
||||
|
||||
return mixer_width;
|
||||
}
|
||||
|
||||
/**
|
||||
* dpu_crtc_get_mixer_height - get the mixer height
|
||||
* Mixer height will be same as panel height
|
||||
*/
|
||||
static inline int dpu_crtc_get_mixer_height(struct dpu_crtc *dpu_crtc,
|
||||
struct dpu_crtc_state *cstate, struct drm_display_mode *mode)
|
||||
{
|
||||
if (!dpu_crtc || !cstate || !mode)
|
||||
return 0;
|
||||
|
||||
return mode->vdisplay;
|
||||
}
|
||||
|
||||
/**
|
||||
* dpu_crtc_frame_pending - retun the number of pending frames
|
||||
* @crtc: Pointer to drm crtc object
|
||||
*/
|
||||
static inline int dpu_crtc_frame_pending(struct drm_crtc *crtc)
|
||||
{
|
||||
struct dpu_crtc *dpu_crtc;
|
||||
|
||||
if (!crtc)
|
||||
return -EINVAL;
|
||||
|
||||
dpu_crtc = to_dpu_crtc(crtc);
|
||||
return atomic_read(&dpu_crtc->frame_pending);
|
||||
}
|
||||
|
||||
/**
|
||||
* dpu_crtc_vblank - enable or disable vblanks for this crtc
|
||||
* @crtc: Pointer to drm crtc object
|
||||
* @en: true to enable vblanks, false to disable
|
||||
*/
|
||||
int dpu_crtc_vblank(struct drm_crtc *crtc, bool en);
|
||||
|
||||
/**
|
||||
* dpu_crtc_commit_kickoff - trigger kickoff of the commit for this crtc
|
||||
* @crtc: Pointer to drm crtc object
|
||||
*/
|
||||
void dpu_crtc_commit_kickoff(struct drm_crtc *crtc);
|
||||
|
||||
/**
|
||||
* dpu_crtc_complete_commit - callback signalling completion of current commit
|
||||
* @crtc: Pointer to drm crtc object
|
||||
* @old_state: Pointer to drm crtc old state object
|
||||
*/
|
||||
void dpu_crtc_complete_commit(struct drm_crtc *crtc,
|
||||
struct drm_crtc_state *old_state);
|
||||
|
||||
/**
|
||||
* dpu_crtc_init - create a new crtc object
|
||||
* @dev: dpu device
|
||||
* @plane: base plane
|
||||
* @Return: new crtc object or error
|
||||
*/
|
||||
struct drm_crtc *dpu_crtc_init(struct drm_device *dev, struct drm_plane *plane);
|
||||
|
||||
/**
|
||||
* dpu_crtc_register_custom_event - api for enabling/disabling crtc event
|
||||
* @kms: Pointer to dpu_kms
|
||||
* @crtc_drm: Pointer to crtc object
|
||||
* @event: Event that client is interested
|
||||
* @en: Flag to enable/disable the event
|
||||
*/
|
||||
int dpu_crtc_register_custom_event(struct dpu_kms *kms,
|
||||
struct drm_crtc *crtc_drm, u32 event, bool en);
|
||||
|
||||
/**
|
||||
* dpu_crtc_get_intf_mode - get interface mode of the given crtc
|
||||
* @crtc: Pointert to crtc
|
||||
*/
|
||||
enum dpu_intf_mode dpu_crtc_get_intf_mode(struct drm_crtc *crtc);
|
||||
|
||||
/**
|
||||
* dpu_crtc_get_client_type - check the crtc type- rt, nrt etc.
|
||||
* @crtc: Pointer to crtc
|
||||
*/
|
||||
static inline enum dpu_crtc_client_type dpu_crtc_get_client_type(
|
||||
struct drm_crtc *crtc)
|
||||
{
|
||||
struct dpu_crtc_state *cstate =
|
||||
crtc ? to_dpu_crtc_state(crtc->state) : NULL;
|
||||
|
||||
if (!cstate)
|
||||
return NRT_CLIENT;
|
||||
|
||||
return RT_CLIENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* dpu_crtc_is_enabled - check if dpu crtc is enabled or not
|
||||
* @crtc: Pointer to crtc
|
||||
*/
|
||||
static inline bool dpu_crtc_is_enabled(struct drm_crtc *crtc)
|
||||
{
|
||||
return crtc ? crtc->enabled : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* dpu_crtc_event_queue - request event callback
|
||||
* @crtc: Pointer to drm crtc structure
|
||||
* @func: Pointer to callback function
|
||||
* @usr: Pointer to user data to be passed to callback
|
||||
* Returns: Zero on success
|
||||
*/
|
||||
int dpu_crtc_event_queue(struct drm_crtc *crtc,
|
||||
void (*func)(struct drm_crtc *crtc, void *usr), void *usr);
|
||||
|
||||
/**
|
||||
* dpu_crtc_res_add - add given resource to resource pool in crtc state
|
||||
* @state: Pointer to drm crtc state
|
||||
* @type: Resource type
|
||||
* @tag: Search tag for given resource
|
||||
* @val: Resource handle
|
||||
* @ops: Resource callback operations
|
||||
* return: 0 if success; error code otherwise
|
||||
*/
|
||||
int dpu_crtc_res_add(struct drm_crtc_state *state, u32 type, u64 tag,
|
||||
void *val, struct dpu_crtc_res_ops *ops);
|
||||
|
||||
/**
|
||||
* dpu_crtc_res_get - get given resource from resource pool in crtc state
|
||||
* @state: Pointer to drm crtc state
|
||||
* @type: Resource type
|
||||
* @tag: Search tag for given resource
|
||||
* return: Resource handle if success; pointer error or null otherwise
|
||||
*/
|
||||
void *dpu_crtc_res_get(struct drm_crtc_state *state, u32 type, u64 tag);
|
||||
|
||||
/**
|
||||
* dpu_crtc_res_put - return given resource to resource pool in crtc state
|
||||
* @state: Pointer to drm crtc state
|
||||
* @type: Resource type
|
||||
* @tag: Search tag for given resource
|
||||
* return: None
|
||||
*/
|
||||
void dpu_crtc_res_put(struct drm_crtc_state *state, u32 type, u64 tag);
|
||||
|
||||
#endif /* _DPU_CRTC_H_ */
|
||||
2393
drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c
Normal file
2393
drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c
Normal file
File diff suppressed because it is too large
Load diff
103
drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h
Normal file
103
drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef DPU_DBG_H_
|
||||
#define DPU_DBG_H_
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
enum dpu_dbg_dump_flag {
|
||||
DPU_DBG_DUMP_IN_LOG = BIT(0),
|
||||
DPU_DBG_DUMP_IN_MEM = BIT(1),
|
||||
};
|
||||
|
||||
#if defined(CONFIG_DEBUG_FS)
|
||||
|
||||
/**
|
||||
* dpu_dbg_init_dbg_buses - initialize debug bus dumping support for the chipset
|
||||
* @hwversion: Chipset revision
|
||||
*/
|
||||
void dpu_dbg_init_dbg_buses(u32 hwversion);
|
||||
|
||||
/**
|
||||
* dpu_dbg_init - initialize global dpu debug facilities: regdump
|
||||
* @dev: device handle
|
||||
* Returns: 0 or -ERROR
|
||||
*/
|
||||
int dpu_dbg_init(struct device *dev);
|
||||
|
||||
/**
|
||||
* dpu_dbg_debugfs_register - register entries at the given debugfs dir
|
||||
* @debugfs_root: debugfs root in which to create dpu debug entries
|
||||
* Returns: 0 or -ERROR
|
||||
*/
|
||||
int dpu_dbg_debugfs_register(struct dentry *debugfs_root);
|
||||
|
||||
/**
|
||||
* dpu_dbg_destroy - destroy the global dpu debug facilities
|
||||
* Returns: none
|
||||
*/
|
||||
void dpu_dbg_destroy(void);
|
||||
|
||||
/**
|
||||
* dpu_dbg_dump - trigger dumping of all dpu_dbg facilities
|
||||
* @queue_work: whether to queue the dumping work to the work_struct
|
||||
* @name: string indicating origin of dump
|
||||
* @dump_dbgbus: dump the dpu debug bus
|
||||
* @dump_vbif_rt: dump the vbif rt bus
|
||||
* Returns: none
|
||||
*/
|
||||
void dpu_dbg_dump(bool queue_work, const char *name, bool dump_dbgbus_dpu,
|
||||
bool dump_dbgbus_vbif_rt);
|
||||
|
||||
/**
|
||||
* dpu_dbg_set_dpu_top_offset - set the target specific offset from mdss base
|
||||
* address of the top registers. Used for accessing debug bus controls.
|
||||
* @blk_off: offset from mdss base of the top block
|
||||
*/
|
||||
void dpu_dbg_set_dpu_top_offset(u32 blk_off);
|
||||
|
||||
#else
|
||||
|
||||
static inline void dpu_dbg_init_dbg_buses(u32 hwversion)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int dpu_dbg_init(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int dpu_dbg_debugfs_register(struct dentry *debugfs_root)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void dpu_dbg_destroy(void)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void dpu_dbg_dump(bool queue_work, const char *name,
|
||||
bool dump_dbgbus_dpu, bool dump_dbgbus_vbif_rt)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void dpu_dbg_set_dpu_top_offset(u32 blk_off)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* defined(CONFIG_DEBUG_FS) */
|
||||
|
||||
|
||||
#endif /* DPU_DBG_H_ */
|
||||
2575
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
Normal file
2575
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
Normal file
File diff suppressed because it is too large
Load diff
191
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h
Normal file
191
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __DPU_ENCODER_H__
|
||||
#define __DPU_ENCODER_H__
|
||||
|
||||
#include <drm/drm_crtc.h>
|
||||
#include "dpu_hw_mdss.h"
|
||||
|
||||
#define DPU_ENCODER_FRAME_EVENT_DONE BIT(0)
|
||||
#define DPU_ENCODER_FRAME_EVENT_ERROR BIT(1)
|
||||
#define DPU_ENCODER_FRAME_EVENT_PANEL_DEAD BIT(2)
|
||||
#define DPU_ENCODER_FRAME_EVENT_IDLE BIT(3)
|
||||
|
||||
#define IDLE_TIMEOUT (66 - 16/2)
|
||||
|
||||
/**
|
||||
* Encoder functions and data types
|
||||
* @intfs: Interfaces this encoder is using, INTF_MODE_NONE if unused
|
||||
* @needs_cdm: Encoder requests a CDM based on pixel format conversion needs
|
||||
* @display_num_of_h_tiles: Number of horizontal tiles in case of split
|
||||
* interface
|
||||
* @topology: Topology of the display
|
||||
*/
|
||||
struct dpu_encoder_hw_resources {
|
||||
enum dpu_intf_mode intfs[INTF_MAX];
|
||||
bool needs_cdm;
|
||||
u32 display_num_of_h_tiles;
|
||||
};
|
||||
|
||||
/**
|
||||
* dpu_encoder_kickoff_params - info encoder requires at kickoff
|
||||
* @affected_displays: bitmask, bit set means the ROI of the commit lies within
|
||||
* the bounds of the physical display at the bit index
|
||||
*/
|
||||
struct dpu_encoder_kickoff_params {
|
||||
unsigned long affected_displays;
|
||||
};
|
||||
|
||||
/**
|
||||
* dpu_encoder_get_hw_resources - Populate table of required hardware resources
|
||||
* @encoder: encoder pointer
|
||||
* @hw_res: resource table to populate with encoder required resources
|
||||
* @conn_state: report hw reqs based on this proposed connector state
|
||||
*/
|
||||
void dpu_encoder_get_hw_resources(struct drm_encoder *encoder,
|
||||
struct dpu_encoder_hw_resources *hw_res,
|
||||
struct drm_connector_state *conn_state);
|
||||
|
||||
/**
|
||||
* dpu_encoder_register_vblank_callback - provide callback to encoder that
|
||||
* will be called on the next vblank.
|
||||
* @encoder: encoder pointer
|
||||
* @cb: callback pointer, provide NULL to deregister and disable IRQs
|
||||
* @data: user data provided to callback
|
||||
*/
|
||||
void dpu_encoder_register_vblank_callback(struct drm_encoder *encoder,
|
||||
void (*cb)(void *), void *data);
|
||||
|
||||
/**
|
||||
* dpu_encoder_register_frame_event_callback - provide callback to encoder that
|
||||
* will be called after the request is complete, or other events.
|
||||
* @encoder: encoder pointer
|
||||
* @cb: callback pointer, provide NULL to deregister
|
||||
* @data: user data provided to callback
|
||||
*/
|
||||
void dpu_encoder_register_frame_event_callback(struct drm_encoder *encoder,
|
||||
void (*cb)(void *, u32), void *data);
|
||||
|
||||
/**
|
||||
* dpu_encoder_prepare_for_kickoff - schedule double buffer flip of the ctl
|
||||
* path (i.e. ctl flush and start) at next appropriate time.
|
||||
* Immediately: if no previous commit is outstanding.
|
||||
* Delayed: Block until next trigger can be issued.
|
||||
* @encoder: encoder pointer
|
||||
* @params: kickoff time parameters
|
||||
*/
|
||||
void dpu_encoder_prepare_for_kickoff(struct drm_encoder *encoder,
|
||||
struct dpu_encoder_kickoff_params *params);
|
||||
|
||||
/**
|
||||
* dpu_encoder_trigger_kickoff_pending - Clear the flush bits from previous
|
||||
* kickoff and trigger the ctl prepare progress for command mode display.
|
||||
* @encoder: encoder pointer
|
||||
*/
|
||||
void dpu_encoder_trigger_kickoff_pending(struct drm_encoder *encoder);
|
||||
|
||||
/**
|
||||
* dpu_encoder_kickoff - trigger a double buffer flip of the ctl path
|
||||
* (i.e. ctl flush and start) immediately.
|
||||
* @encoder: encoder pointer
|
||||
*/
|
||||
void dpu_encoder_kickoff(struct drm_encoder *encoder);
|
||||
|
||||
/**
|
||||
* dpu_encoder_wait_for_event - Waits for encoder events
|
||||
* @encoder: encoder pointer
|
||||
* @event: event to wait for
|
||||
* MSM_ENC_COMMIT_DONE - Wait for hardware to have flushed the current pending
|
||||
* frames to hardware at a vblank or ctl_start
|
||||
* Encoders will map this differently depending on the
|
||||
* panel type.
|
||||
* vid mode -> vsync_irq
|
||||
* cmd mode -> ctl_start
|
||||
* MSM_ENC_TX_COMPLETE - Wait for the hardware to transfer all the pixels to
|
||||
* the panel. Encoders will map this differently
|
||||
* depending on the panel type.
|
||||
* vid mode -> vsync_irq
|
||||
* cmd mode -> pp_done
|
||||
* Returns: 0 on success, -EWOULDBLOCK if already signaled, error otherwise
|
||||
*/
|
||||
int dpu_encoder_wait_for_event(struct drm_encoder *drm_encoder,
|
||||
enum msm_event_wait event);
|
||||
|
||||
/*
|
||||
* dpu_encoder_get_intf_mode - get interface mode of the given encoder
|
||||
* @encoder: Pointer to drm encoder object
|
||||
*/
|
||||
enum dpu_intf_mode dpu_encoder_get_intf_mode(struct drm_encoder *encoder);
|
||||
|
||||
/**
|
||||
* dpu_encoder_virt_restore - restore the encoder configs
|
||||
* @encoder: encoder pointer
|
||||
*/
|
||||
void dpu_encoder_virt_restore(struct drm_encoder *encoder);
|
||||
|
||||
/**
|
||||
* dpu_encoder_check_mode - check if given mode is supported or not
|
||||
* @drm_enc: Pointer to drm encoder object
|
||||
* @mode: Mode to be checked
|
||||
* @Return: true if it is cmd mode
|
||||
*/
|
||||
bool dpu_encoder_check_mode(struct drm_encoder *drm_enc, u32 mode);
|
||||
|
||||
/**
|
||||
* dpu_encoder_init - initialize virtual encoder object
|
||||
* @dev: Pointer to drm device structure
|
||||
* @disp_info: Pointer to display information structure
|
||||
* Returns: Pointer to newly created drm encoder
|
||||
*/
|
||||
struct drm_encoder *dpu_encoder_init(
|
||||
struct drm_device *dev,
|
||||
int drm_enc_mode);
|
||||
|
||||
/**
|
||||
* dpu_encoder_setup - setup dpu_encoder for the display probed
|
||||
* @dev: Pointer to drm device structure
|
||||
* @enc: Pointer to the drm_encoder
|
||||
* @disp_info: Pointer to the display info
|
||||
*/
|
||||
int dpu_encoder_setup(struct drm_device *dev, struct drm_encoder *enc,
|
||||
struct msm_display_info *disp_info);
|
||||
|
||||
/**
|
||||
* dpu_encoder_destroy - destroy previously initialized virtual encoder
|
||||
* @drm_enc: Pointer to previously created drm encoder structure
|
||||
*/
|
||||
void dpu_encoder_destroy(struct drm_encoder *drm_enc);
|
||||
|
||||
/**
|
||||
* dpu_encoder_prepare_commit - prepare encoder at the very beginning of an
|
||||
* atomic commit, before any registers are written
|
||||
* @drm_enc: Pointer to previously created drm encoder structure
|
||||
*/
|
||||
void dpu_encoder_prepare_commit(struct drm_encoder *drm_enc);
|
||||
|
||||
/**
|
||||
* dpu_encoder_set_idle_timeout - set the idle timeout for video
|
||||
* and command mode encoders.
|
||||
* @drm_enc: Pointer to previously created drm encoder structure
|
||||
* @idle_timeout: idle timeout duration in milliseconds
|
||||
*/
|
||||
void dpu_encoder_set_idle_timeout(struct drm_encoder *drm_enc,
|
||||
u32 idle_timeout);
|
||||
|
||||
#endif /* __DPU_ENCODER_H__ */
|
||||
453
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys.h
Normal file
453
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys.h
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2018 The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __DPU_ENCODER_PHYS_H__
|
||||
#define __DPU_ENCODER_PHYS_H__
|
||||
|
||||
#include <linux/jiffies.h>
|
||||
|
||||
#include "dpu_kms.h"
|
||||
#include "dpu_hw_intf.h"
|
||||
#include "dpu_hw_pingpong.h"
|
||||
#include "dpu_hw_ctl.h"
|
||||
#include "dpu_hw_top.h"
|
||||
#include "dpu_hw_cdm.h"
|
||||
#include "dpu_encoder.h"
|
||||
|
||||
#define DPU_ENCODER_NAME_MAX 16
|
||||
|
||||
/* wait for at most 2 vsync for lowest refresh rate (24hz) */
|
||||
#define KICKOFF_TIMEOUT_MS 84
|
||||
#define KICKOFF_TIMEOUT_JIFFIES msecs_to_jiffies(KICKOFF_TIMEOUT_MS)
|
||||
|
||||
/**
|
||||
* enum dpu_enc_split_role - Role this physical encoder will play in a
|
||||
* split-panel configuration, where one panel is master, and others slaves.
|
||||
* Masters have extra responsibilities, like managing the VBLANK IRQ.
|
||||
* @ENC_ROLE_SOLO: This is the one and only panel. This encoder is master.
|
||||
* @ENC_ROLE_MASTER: This encoder is the master of a split panel config.
|
||||
* @ENC_ROLE_SLAVE: This encoder is not the master of a split panel config.
|
||||
*/
|
||||
enum dpu_enc_split_role {
|
||||
ENC_ROLE_SOLO,
|
||||
ENC_ROLE_MASTER,
|
||||
ENC_ROLE_SLAVE,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum dpu_enc_enable_state - current enabled state of the physical encoder
|
||||
* @DPU_ENC_DISABLING: Encoder transitioning to disable state
|
||||
* Events bounding transition are encoder type specific
|
||||
* @DPU_ENC_DISABLED: Encoder is disabled
|
||||
* @DPU_ENC_ENABLING: Encoder transitioning to enabled
|
||||
* Events bounding transition are encoder type specific
|
||||
* @DPU_ENC_ENABLED: Encoder is enabled
|
||||
* @DPU_ENC_ERR_NEEDS_HW_RESET: Encoder is enabled, but requires a hw_reset
|
||||
* to recover from a previous error
|
||||
*/
|
||||
enum dpu_enc_enable_state {
|
||||
DPU_ENC_DISABLING,
|
||||
DPU_ENC_DISABLED,
|
||||
DPU_ENC_ENABLING,
|
||||
DPU_ENC_ENABLED,
|
||||
DPU_ENC_ERR_NEEDS_HW_RESET
|
||||
};
|
||||
|
||||
struct dpu_encoder_phys;
|
||||
|
||||
/**
|
||||
* struct dpu_encoder_virt_ops - Interface the containing virtual encoder
|
||||
* provides for the physical encoders to use to callback.
|
||||
* @handle_vblank_virt: Notify virtual encoder of vblank IRQ reception
|
||||
* Note: This is called from IRQ handler context.
|
||||
* @handle_underrun_virt: Notify virtual encoder of underrun IRQ reception
|
||||
* Note: This is called from IRQ handler context.
|
||||
* @handle_frame_done: Notify virtual encoder that this phys encoder
|
||||
* completes last request frame.
|
||||
*/
|
||||
struct dpu_encoder_virt_ops {
|
||||
void (*handle_vblank_virt)(struct drm_encoder *,
|
||||
struct dpu_encoder_phys *phys);
|
||||
void (*handle_underrun_virt)(struct drm_encoder *,
|
||||
struct dpu_encoder_phys *phys);
|
||||
void (*handle_frame_done)(struct drm_encoder *,
|
||||
struct dpu_encoder_phys *phys, u32 event);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_encoder_phys_ops - Interface the physical encoders provide to
|
||||
* the containing virtual encoder.
|
||||
* @late_register: DRM Call. Add Userspace interfaces, debugfs.
|
||||
* @prepare_commit: MSM Atomic Call, start of atomic commit sequence
|
||||
* @is_master: Whether this phys_enc is the current master
|
||||
* encoder. Can be switched at enable time. Based
|
||||
* on split_role and current mode (CMD/VID).
|
||||
* @mode_fixup: DRM Call. Fixup a DRM mode.
|
||||
* @mode_set: DRM Call. Set a DRM mode.
|
||||
* This likely caches the mode, for use at enable.
|
||||
* @enable: DRM Call. Enable a DRM mode.
|
||||
* @disable: DRM Call. Disable mode.
|
||||
* @atomic_check: DRM Call. Atomic check new DRM state.
|
||||
* @destroy: DRM Call. Destroy and release resources.
|
||||
* @get_hw_resources: Populate the structure with the hardware
|
||||
* resources that this phys_enc is using.
|
||||
* Expect no overlap between phys_encs.
|
||||
* @control_vblank_irq Register/Deregister for VBLANK IRQ
|
||||
* @wait_for_commit_done: Wait for hardware to have flushed the
|
||||
* current pending frames to hardware
|
||||
* @wait_for_tx_complete: Wait for hardware to transfer the pixels
|
||||
* to the panel
|
||||
* @wait_for_vblank: Wait for VBLANK, for sub-driver internal use
|
||||
* @prepare_for_kickoff: Do any work necessary prior to a kickoff
|
||||
* For CMD encoder, may wait for previous tx done
|
||||
* @handle_post_kickoff: Do any work necessary post-kickoff work
|
||||
* @trigger_start: Process start event on physical encoder
|
||||
* @needs_single_flush: Whether encoder slaves need to be flushed
|
||||
* @setup_misr: Sets up MISR, enable and disables based on sysfs
|
||||
* @collect_misr: Collects MISR data on frame update
|
||||
* @hw_reset: Issue HW recovery such as CTL reset and clear
|
||||
* DPU_ENC_ERR_NEEDS_HW_RESET state
|
||||
* @irq_control: Handler to enable/disable all the encoder IRQs
|
||||
* @prepare_idle_pc: phys encoder can update the vsync_enable status
|
||||
* on idle power collapse prepare
|
||||
* @restore: Restore all the encoder configs.
|
||||
* @get_line_count: Obtain current vertical line count
|
||||
*/
|
||||
|
||||
struct dpu_encoder_phys_ops {
|
||||
int (*late_register)(struct dpu_encoder_phys *encoder,
|
||||
struct dentry *debugfs_root);
|
||||
void (*prepare_commit)(struct dpu_encoder_phys *encoder);
|
||||
bool (*is_master)(struct dpu_encoder_phys *encoder);
|
||||
bool (*mode_fixup)(struct dpu_encoder_phys *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode);
|
||||
void (*mode_set)(struct dpu_encoder_phys *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode);
|
||||
void (*enable)(struct dpu_encoder_phys *encoder);
|
||||
void (*disable)(struct dpu_encoder_phys *encoder);
|
||||
int (*atomic_check)(struct dpu_encoder_phys *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state);
|
||||
void (*destroy)(struct dpu_encoder_phys *encoder);
|
||||
void (*get_hw_resources)(struct dpu_encoder_phys *encoder,
|
||||
struct dpu_encoder_hw_resources *hw_res,
|
||||
struct drm_connector_state *conn_state);
|
||||
int (*control_vblank_irq)(struct dpu_encoder_phys *enc, bool enable);
|
||||
int (*wait_for_commit_done)(struct dpu_encoder_phys *phys_enc);
|
||||
int (*wait_for_tx_complete)(struct dpu_encoder_phys *phys_enc);
|
||||
int (*wait_for_vblank)(struct dpu_encoder_phys *phys_enc);
|
||||
void (*prepare_for_kickoff)(struct dpu_encoder_phys *phys_enc,
|
||||
struct dpu_encoder_kickoff_params *params);
|
||||
void (*handle_post_kickoff)(struct dpu_encoder_phys *phys_enc);
|
||||
void (*trigger_start)(struct dpu_encoder_phys *phys_enc);
|
||||
bool (*needs_single_flush)(struct dpu_encoder_phys *phys_enc);
|
||||
|
||||
void (*setup_misr)(struct dpu_encoder_phys *phys_encs,
|
||||
bool enable, u32 frame_count);
|
||||
u32 (*collect_misr)(struct dpu_encoder_phys *phys_enc);
|
||||
void (*hw_reset)(struct dpu_encoder_phys *phys_enc);
|
||||
void (*irq_control)(struct dpu_encoder_phys *phys, bool enable);
|
||||
void (*prepare_idle_pc)(struct dpu_encoder_phys *phys_enc);
|
||||
void (*restore)(struct dpu_encoder_phys *phys);
|
||||
int (*get_line_count)(struct dpu_encoder_phys *phys);
|
||||
};
|
||||
|
||||
/**
|
||||
* enum dpu_intr_idx - dpu encoder interrupt index
|
||||
* @INTR_IDX_VSYNC: Vsync interrupt for video mode panel
|
||||
* @INTR_IDX_PINGPONG: Pingpong done unterrupt for cmd mode panel
|
||||
* @INTR_IDX_UNDERRUN: Underrun unterrupt for video and cmd mode panel
|
||||
* @INTR_IDX_RDPTR: Readpointer done unterrupt for cmd mode panel
|
||||
*/
|
||||
enum dpu_intr_idx {
|
||||
INTR_IDX_VSYNC,
|
||||
INTR_IDX_PINGPONG,
|
||||
INTR_IDX_UNDERRUN,
|
||||
INTR_IDX_CTL_START,
|
||||
INTR_IDX_RDPTR,
|
||||
INTR_IDX_MAX,
|
||||
};
|
||||
|
||||
/**
|
||||
* dpu_encoder_irq - tracking structure for interrupts
|
||||
* @name: string name of interrupt
|
||||
* @intr_type: Encoder interrupt type
|
||||
* @intr_idx: Encoder interrupt enumeration
|
||||
* @hw_idx: HW Block ID
|
||||
* @irq_idx: IRQ interface lookup index from DPU IRQ framework
|
||||
* will be -EINVAL if IRQ is not registered
|
||||
* @irq_cb: interrupt callback
|
||||
*/
|
||||
struct dpu_encoder_irq {
|
||||
const char *name;
|
||||
enum dpu_intr_type intr_type;
|
||||
enum dpu_intr_idx intr_idx;
|
||||
int hw_idx;
|
||||
int irq_idx;
|
||||
struct dpu_irq_callback cb;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_encoder_phys - physical encoder that drives a single INTF block
|
||||
* tied to a specific panel / sub-panel. Abstract type, sub-classed by
|
||||
* phys_vid or phys_cmd for video mode or command mode encs respectively.
|
||||
* @parent: Pointer to the containing virtual encoder
|
||||
* @connector: If a mode is set, cached pointer to the active connector
|
||||
* @ops: Operations exposed to the virtual encoder
|
||||
* @parent_ops: Callbacks exposed by the parent to the phys_enc
|
||||
* @hw_mdptop: Hardware interface to the top registers
|
||||
* @hw_ctl: Hardware interface to the ctl registers
|
||||
* @hw_cdm: Hardware interface to the cdm registers
|
||||
* @cdm_cfg: Chroma-down hardware configuration
|
||||
* @hw_pp: Hardware interface to the ping pong registers
|
||||
* @dpu_kms: Pointer to the dpu_kms top level
|
||||
* @cached_mode: DRM mode cached at mode_set time, acted on in enable
|
||||
* @enabled: Whether the encoder has enabled and running a mode
|
||||
* @split_role: Role to play in a split-panel configuration
|
||||
* @intf_mode: Interface mode
|
||||
* @intf_idx: Interface index on dpu hardware
|
||||
* @topology_name: topology selected for the display
|
||||
* @enc_spinlock: Virtual-Encoder-Wide Spin Lock for IRQ purposes
|
||||
* @enable_state: Enable state tracking
|
||||
* @vblank_refcount: Reference count of vblank request
|
||||
* @vsync_cnt: Vsync count for the physical encoder
|
||||
* @underrun_cnt: Underrun count for the physical encoder
|
||||
* @pending_kickoff_cnt: Atomic counter tracking the number of kickoffs
|
||||
* vs. the number of done/vblank irqs. Should hover
|
||||
* between 0-2 Incremented when a new kickoff is
|
||||
* scheduled. Decremented in irq handler
|
||||
* @pending_ctlstart_cnt: Atomic counter tracking the number of ctl start
|
||||
* pending.
|
||||
* @pending_kickoff_wq: Wait queue for blocking until kickoff completes
|
||||
* @irq: IRQ tracking structures
|
||||
*/
|
||||
struct dpu_encoder_phys {
|
||||
struct drm_encoder *parent;
|
||||
struct drm_connector *connector;
|
||||
struct dpu_encoder_phys_ops ops;
|
||||
const struct dpu_encoder_virt_ops *parent_ops;
|
||||
struct dpu_hw_mdp *hw_mdptop;
|
||||
struct dpu_hw_ctl *hw_ctl;
|
||||
struct dpu_hw_cdm *hw_cdm;
|
||||
struct dpu_hw_cdm_cfg cdm_cfg;
|
||||
struct dpu_hw_pingpong *hw_pp;
|
||||
struct dpu_kms *dpu_kms;
|
||||
struct drm_display_mode cached_mode;
|
||||
enum dpu_enc_split_role split_role;
|
||||
enum dpu_intf_mode intf_mode;
|
||||
enum dpu_intf intf_idx;
|
||||
enum dpu_rm_topology_name topology_name;
|
||||
spinlock_t *enc_spinlock;
|
||||
enum dpu_enc_enable_state enable_state;
|
||||
atomic_t vblank_refcount;
|
||||
atomic_t vsync_cnt;
|
||||
atomic_t underrun_cnt;
|
||||
atomic_t pending_ctlstart_cnt;
|
||||
atomic_t pending_kickoff_cnt;
|
||||
wait_queue_head_t pending_kickoff_wq;
|
||||
struct dpu_encoder_irq irq[INTR_IDX_MAX];
|
||||
};
|
||||
|
||||
static inline int dpu_encoder_phys_inc_pending(struct dpu_encoder_phys *phys)
|
||||
{
|
||||
atomic_inc_return(&phys->pending_ctlstart_cnt);
|
||||
return atomic_inc_return(&phys->pending_kickoff_cnt);
|
||||
}
|
||||
|
||||
/**
|
||||
* struct dpu_encoder_phys_vid - sub-class of dpu_encoder_phys to handle video
|
||||
* mode specific operations
|
||||
* @base: Baseclass physical encoder structure
|
||||
* @hw_intf: Hardware interface to the intf registers
|
||||
* @timing_params: Current timing parameter
|
||||
*/
|
||||
struct dpu_encoder_phys_vid {
|
||||
struct dpu_encoder_phys base;
|
||||
struct dpu_hw_intf *hw_intf;
|
||||
struct intf_timing_params timing_params;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_encoder_phys_cmd - sub-class of dpu_encoder_phys to handle command
|
||||
* mode specific operations
|
||||
* @base: Baseclass physical encoder structure
|
||||
* @intf_idx: Intf Block index used by this phys encoder
|
||||
* @stream_sel: Stream selection for multi-stream interfaces
|
||||
* @serialize_wait4pp: serialize wait4pp feature waits for pp_done interrupt
|
||||
* after ctl_start instead of before next frame kickoff
|
||||
* @pp_timeout_report_cnt: number of pingpong done irq timeout errors
|
||||
* @pending_vblank_cnt: Atomic counter tracking pending wait for VBLANK
|
||||
* @pending_vblank_wq: Wait queue for blocking until VBLANK received
|
||||
*/
|
||||
struct dpu_encoder_phys_cmd {
|
||||
struct dpu_encoder_phys base;
|
||||
int stream_sel;
|
||||
bool serialize_wait4pp;
|
||||
int pp_timeout_report_cnt;
|
||||
atomic_t pending_vblank_cnt;
|
||||
wait_queue_head_t pending_vblank_wq;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_enc_phys_init_params - initialization parameters for phys encs
|
||||
* @dpu_kms: Pointer to the dpu_kms top level
|
||||
* @parent: Pointer to the containing virtual encoder
|
||||
* @parent_ops: Callbacks exposed by the parent to the phys_enc
|
||||
* @split_role: Role to play in a split-panel configuration
|
||||
* @intf_idx: Interface index this phys_enc will control
|
||||
* @enc_spinlock: Virtual-Encoder-Wide Spin Lock for IRQ purposes
|
||||
*/
|
||||
struct dpu_enc_phys_init_params {
|
||||
struct dpu_kms *dpu_kms;
|
||||
struct drm_encoder *parent;
|
||||
const struct dpu_encoder_virt_ops *parent_ops;
|
||||
enum dpu_enc_split_role split_role;
|
||||
enum dpu_intf intf_idx;
|
||||
spinlock_t *enc_spinlock;
|
||||
};
|
||||
|
||||
/**
|
||||
* dpu_encoder_wait_info - container for passing arguments to irq wait functions
|
||||
* @wq: wait queue structure
|
||||
* @atomic_cnt: wait until atomic_cnt equals zero
|
||||
* @timeout_ms: timeout value in milliseconds
|
||||
*/
|
||||
struct dpu_encoder_wait_info {
|
||||
wait_queue_head_t *wq;
|
||||
atomic_t *atomic_cnt;
|
||||
s64 timeout_ms;
|
||||
};
|
||||
|
||||
/**
|
||||
* dpu_encoder_phys_vid_init - Construct a new video mode physical encoder
|
||||
* @p: Pointer to init params structure
|
||||
* Return: Error code or newly allocated encoder
|
||||
*/
|
||||
struct dpu_encoder_phys *dpu_encoder_phys_vid_init(
|
||||
struct dpu_enc_phys_init_params *p);
|
||||
|
||||
/**
|
||||
* dpu_encoder_phys_cmd_init - Construct a new command mode physical encoder
|
||||
* @p: Pointer to init params structure
|
||||
* Return: Error code or newly allocated encoder
|
||||
*/
|
||||
struct dpu_encoder_phys *dpu_encoder_phys_cmd_init(
|
||||
struct dpu_enc_phys_init_params *p);
|
||||
|
||||
/**
|
||||
* dpu_encoder_helper_trigger_start - control start helper function
|
||||
* This helper function may be optionally specified by physical
|
||||
* encoders if they require ctl_start triggering.
|
||||
* @phys_enc: Pointer to physical encoder structure
|
||||
*/
|
||||
void dpu_encoder_helper_trigger_start(struct dpu_encoder_phys *phys_enc);
|
||||
|
||||
/**
|
||||
* dpu_encoder_helper_wait_event_timeout - wait for event with timeout
|
||||
* taking into account that jiffies may jump between reads leading to
|
||||
* incorrectly detected timeouts. Prevent failure in this scenario by
|
||||
* making sure that elapsed time during wait is valid.
|
||||
* @drm_id: drm object id for logging
|
||||
* @hw_id: hw instance id for logging
|
||||
* @info: wait info structure
|
||||
*/
|
||||
int dpu_encoder_helper_wait_event_timeout(
|
||||
int32_t drm_id,
|
||||
int32_t hw_id,
|
||||
struct dpu_encoder_wait_info *info);
|
||||
|
||||
/**
|
||||
* dpu_encoder_helper_hw_reset - issue ctl hw reset
|
||||
* This helper function may be optionally specified by physical
|
||||
* encoders if they require ctl hw reset. If state is currently
|
||||
* DPU_ENC_ERR_NEEDS_HW_RESET, it is set back to DPU_ENC_ENABLED.
|
||||
* @phys_enc: Pointer to physical encoder structure
|
||||
*/
|
||||
void dpu_encoder_helper_hw_reset(struct dpu_encoder_phys *phys_enc);
|
||||
|
||||
static inline enum dpu_3d_blend_mode dpu_encoder_helper_get_3d_blend_mode(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
if (!phys_enc || phys_enc->enable_state == DPU_ENC_DISABLING)
|
||||
return BLEND_3D_NONE;
|
||||
|
||||
if (phys_enc->split_role == ENC_ROLE_SOLO &&
|
||||
phys_enc->topology_name == DPU_RM_TOPOLOGY_DUALPIPE_3DMERGE)
|
||||
return BLEND_3D_H_ROW_INT;
|
||||
|
||||
return BLEND_3D_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* dpu_encoder_helper_split_config - split display configuration helper function
|
||||
* This helper function may be used by physical encoders to configure
|
||||
* the split display related registers.
|
||||
* @phys_enc: Pointer to physical encoder structure
|
||||
* @interface: enum dpu_intf setting
|
||||
*/
|
||||
void dpu_encoder_helper_split_config(
|
||||
struct dpu_encoder_phys *phys_enc,
|
||||
enum dpu_intf interface);
|
||||
|
||||
/**
|
||||
* dpu_encoder_helper_hw_release - prepare for h/w reset during disable
|
||||
* @phys_enc: Pointer to physical encoder structure
|
||||
* @fb: Optional fb for specifying new mixer output resolution, may be NULL
|
||||
* Return: Zero on success
|
||||
*/
|
||||
int dpu_encoder_helper_hw_release(struct dpu_encoder_phys *phys_enc,
|
||||
struct drm_framebuffer *fb);
|
||||
|
||||
/**
|
||||
* dpu_encoder_helper_report_irq_timeout - utility to report error that irq has
|
||||
* timed out, including reporting frame error event to crtc and debug dump
|
||||
* @phys_enc: Pointer to physical encoder structure
|
||||
* @intr_idx: Failing interrupt index
|
||||
*/
|
||||
void dpu_encoder_helper_report_irq_timeout(struct dpu_encoder_phys *phys_enc,
|
||||
enum dpu_intr_idx intr_idx);
|
||||
|
||||
/**
|
||||
* dpu_encoder_helper_wait_for_irq - utility to wait on an irq.
|
||||
* note: will call dpu_encoder_helper_wait_for_irq on timeout
|
||||
* @phys_enc: Pointer to physical encoder structure
|
||||
* @intr_idx: encoder interrupt index
|
||||
* @wait_info: wait info struct
|
||||
* @Return: 0 or -ERROR
|
||||
*/
|
||||
int dpu_encoder_helper_wait_for_irq(struct dpu_encoder_phys *phys_enc,
|
||||
enum dpu_intr_idx intr_idx,
|
||||
struct dpu_encoder_wait_info *wait_info);
|
||||
|
||||
/**
|
||||
* dpu_encoder_helper_register_irq - register and enable an irq
|
||||
* @phys_enc: Pointer to physical encoder structure
|
||||
* @intr_idx: encoder interrupt index
|
||||
* @Return: 0 or -ERROR
|
||||
*/
|
||||
int dpu_encoder_helper_register_irq(struct dpu_encoder_phys *phys_enc,
|
||||
enum dpu_intr_idx intr_idx);
|
||||
|
||||
/**
|
||||
* dpu_encoder_helper_unregister_irq - unregister and disable an irq
|
||||
* @phys_enc: Pointer to physical encoder structure
|
||||
* @intr_idx: encoder interrupt index
|
||||
* @Return: 0 or -ERROR
|
||||
*/
|
||||
int dpu_encoder_helper_unregister_irq(struct dpu_encoder_phys *phys_enc,
|
||||
enum dpu_intr_idx intr_idx);
|
||||
|
||||
#endif /* __dpu_encoder_phys_H__ */
|
||||
905
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c
Normal file
905
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c
Normal file
|
|
@ -0,0 +1,905 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2018 The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__
|
||||
#include "dpu_encoder_phys.h"
|
||||
#include "dpu_hw_interrupts.h"
|
||||
#include "dpu_core_irq.h"
|
||||
#include "dpu_formats.h"
|
||||
#include "dpu_trace.h"
|
||||
|
||||
#define DPU_DEBUG_CMDENC(e, fmt, ...) DPU_DEBUG("enc%d intf%d " fmt, \
|
||||
(e) && (e)->base.parent ? \
|
||||
(e)->base.parent->base.id : -1, \
|
||||
(e) ? (e)->base.intf_idx - INTF_0 : -1, ##__VA_ARGS__)
|
||||
|
||||
#define DPU_ERROR_CMDENC(e, fmt, ...) DPU_ERROR("enc%d intf%d " fmt, \
|
||||
(e) && (e)->base.parent ? \
|
||||
(e)->base.parent->base.id : -1, \
|
||||
(e) ? (e)->base.intf_idx - INTF_0 : -1, ##__VA_ARGS__)
|
||||
|
||||
#define to_dpu_encoder_phys_cmd(x) \
|
||||
container_of(x, struct dpu_encoder_phys_cmd, base)
|
||||
|
||||
#define PP_TIMEOUT_MAX_TRIALS 10
|
||||
|
||||
/*
|
||||
* Tearcheck sync start and continue thresholds are empirically found
|
||||
* based on common panels In the future, may want to allow panels to override
|
||||
* these default values
|
||||
*/
|
||||
#define DEFAULT_TEARCHECK_SYNC_THRESH_START 4
|
||||
#define DEFAULT_TEARCHECK_SYNC_THRESH_CONTINUE 4
|
||||
|
||||
#define DPU_ENC_WR_PTR_START_TIMEOUT_US 20000
|
||||
|
||||
static inline int _dpu_encoder_phys_cmd_get_idle_timeout(
|
||||
struct dpu_encoder_phys_cmd *cmd_enc)
|
||||
{
|
||||
return KICKOFF_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
static inline bool dpu_encoder_phys_cmd_is_master(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
return (phys_enc->split_role != ENC_ROLE_SLAVE) ? true : false;
|
||||
}
|
||||
|
||||
static bool dpu_encoder_phys_cmd_mode_fixup(
|
||||
struct dpu_encoder_phys *phys_enc,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adj_mode)
|
||||
{
|
||||
if (phys_enc)
|
||||
DPU_DEBUG_CMDENC(to_dpu_encoder_phys_cmd(phys_enc), "\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _dpu_encoder_phys_cmd_update_intf_cfg(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
struct dpu_hw_ctl *ctl;
|
||||
struct dpu_hw_intf_cfg intf_cfg = { 0 };
|
||||
|
||||
if (!phys_enc)
|
||||
return;
|
||||
|
||||
ctl = phys_enc->hw_ctl;
|
||||
if (!ctl || !ctl->ops.setup_intf_cfg)
|
||||
return;
|
||||
|
||||
intf_cfg.intf = phys_enc->intf_idx;
|
||||
intf_cfg.intf_mode_sel = DPU_CTL_MODE_SEL_CMD;
|
||||
intf_cfg.stream_sel = cmd_enc->stream_sel;
|
||||
intf_cfg.mode_3d = dpu_encoder_helper_get_3d_blend_mode(phys_enc);
|
||||
ctl->ops.setup_intf_cfg(ctl, &intf_cfg);
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_pp_tx_done_irq(void *arg, int irq_idx)
|
||||
{
|
||||
struct dpu_encoder_phys *phys_enc = arg;
|
||||
unsigned long lock_flags;
|
||||
int new_cnt;
|
||||
u32 event = DPU_ENCODER_FRAME_EVENT_DONE;
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_pp)
|
||||
return;
|
||||
|
||||
DPU_ATRACE_BEGIN("pp_done_irq");
|
||||
/* notify all synchronous clients first, then asynchronous clients */
|
||||
if (phys_enc->parent_ops->handle_frame_done)
|
||||
phys_enc->parent_ops->handle_frame_done(phys_enc->parent,
|
||||
phys_enc, event);
|
||||
|
||||
spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags);
|
||||
new_cnt = atomic_add_unless(&phys_enc->pending_kickoff_cnt, -1, 0);
|
||||
spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags);
|
||||
|
||||
trace_dpu_enc_phys_cmd_pp_tx_done(DRMID(phys_enc->parent),
|
||||
phys_enc->hw_pp->idx - PINGPONG_0,
|
||||
new_cnt, event);
|
||||
|
||||
/* Signal any waiting atomic commit thread */
|
||||
wake_up_all(&phys_enc->pending_kickoff_wq);
|
||||
DPU_ATRACE_END("pp_done_irq");
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_pp_rd_ptr_irq(void *arg, int irq_idx)
|
||||
{
|
||||
struct dpu_encoder_phys *phys_enc = arg;
|
||||
struct dpu_encoder_phys_cmd *cmd_enc;
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_pp)
|
||||
return;
|
||||
|
||||
DPU_ATRACE_BEGIN("rd_ptr_irq");
|
||||
cmd_enc = to_dpu_encoder_phys_cmd(phys_enc);
|
||||
|
||||
if (phys_enc->parent_ops->handle_vblank_virt)
|
||||
phys_enc->parent_ops->handle_vblank_virt(phys_enc->parent,
|
||||
phys_enc);
|
||||
|
||||
atomic_add_unless(&cmd_enc->pending_vblank_cnt, -1, 0);
|
||||
wake_up_all(&cmd_enc->pending_vblank_wq);
|
||||
DPU_ATRACE_END("rd_ptr_irq");
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_ctl_start_irq(void *arg, int irq_idx)
|
||||
{
|
||||
struct dpu_encoder_phys *phys_enc = arg;
|
||||
struct dpu_encoder_phys_cmd *cmd_enc;
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_ctl)
|
||||
return;
|
||||
|
||||
DPU_ATRACE_BEGIN("ctl_start_irq");
|
||||
cmd_enc = to_dpu_encoder_phys_cmd(phys_enc);
|
||||
|
||||
atomic_add_unless(&phys_enc->pending_ctlstart_cnt, -1, 0);
|
||||
|
||||
/* Signal any waiting ctl start interrupt */
|
||||
wake_up_all(&phys_enc->pending_kickoff_wq);
|
||||
DPU_ATRACE_END("ctl_start_irq");
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_underrun_irq(void *arg, int irq_idx)
|
||||
{
|
||||
struct dpu_encoder_phys *phys_enc = arg;
|
||||
|
||||
if (!phys_enc)
|
||||
return;
|
||||
|
||||
if (phys_enc->parent_ops->handle_underrun_virt)
|
||||
phys_enc->parent_ops->handle_underrun_virt(phys_enc->parent,
|
||||
phys_enc);
|
||||
}
|
||||
|
||||
static void _dpu_encoder_phys_cmd_setup_irq_hw_idx(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_irq *irq;
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_CTL_START];
|
||||
irq->hw_idx = phys_enc->hw_ctl->idx;
|
||||
irq->irq_idx = -EINVAL;
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_PINGPONG];
|
||||
irq->hw_idx = phys_enc->hw_pp->idx;
|
||||
irq->irq_idx = -EINVAL;
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_RDPTR];
|
||||
irq->hw_idx = phys_enc->hw_pp->idx;
|
||||
irq->irq_idx = -EINVAL;
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_UNDERRUN];
|
||||
irq->hw_idx = phys_enc->intf_idx;
|
||||
irq->irq_idx = -EINVAL;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_mode_set(
|
||||
struct dpu_encoder_phys *phys_enc,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adj_mode)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
struct dpu_rm *rm = &phys_enc->dpu_kms->rm;
|
||||
struct dpu_rm_hw_iter iter;
|
||||
int i, instance;
|
||||
|
||||
if (!phys_enc || !mode || !adj_mode) {
|
||||
DPU_ERROR("invalid args\n");
|
||||
return;
|
||||
}
|
||||
phys_enc->cached_mode = *adj_mode;
|
||||
DPU_DEBUG_CMDENC(cmd_enc, "caching mode:\n");
|
||||
drm_mode_debug_printmodeline(adj_mode);
|
||||
|
||||
instance = phys_enc->split_role == ENC_ROLE_SLAVE ? 1 : 0;
|
||||
|
||||
/* Retrieve previously allocated HW Resources. Shouldn't fail */
|
||||
dpu_rm_init_hw_iter(&iter, phys_enc->parent->base.id, DPU_HW_BLK_CTL);
|
||||
for (i = 0; i <= instance; i++) {
|
||||
if (dpu_rm_get_hw(rm, &iter))
|
||||
phys_enc->hw_ctl = (struct dpu_hw_ctl *)iter.hw;
|
||||
}
|
||||
|
||||
if (IS_ERR_OR_NULL(phys_enc->hw_ctl)) {
|
||||
DPU_ERROR_CMDENC(cmd_enc, "failed to init ctl: %ld\n",
|
||||
PTR_ERR(phys_enc->hw_ctl));
|
||||
phys_enc->hw_ctl = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
_dpu_encoder_phys_cmd_setup_irq_hw_idx(phys_enc);
|
||||
}
|
||||
|
||||
static int _dpu_encoder_phys_cmd_handle_ppdone_timeout(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
u32 frame_event = DPU_ENCODER_FRAME_EVENT_ERROR;
|
||||
bool do_log = false;
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_pp || !phys_enc->hw_ctl)
|
||||
return -EINVAL;
|
||||
|
||||
cmd_enc->pp_timeout_report_cnt++;
|
||||
if (cmd_enc->pp_timeout_report_cnt == PP_TIMEOUT_MAX_TRIALS) {
|
||||
frame_event |= DPU_ENCODER_FRAME_EVENT_PANEL_DEAD;
|
||||
do_log = true;
|
||||
} else if (cmd_enc->pp_timeout_report_cnt == 1) {
|
||||
do_log = true;
|
||||
}
|
||||
|
||||
trace_dpu_enc_phys_cmd_pdone_timeout(DRMID(phys_enc->parent),
|
||||
phys_enc->hw_pp->idx - PINGPONG_0,
|
||||
cmd_enc->pp_timeout_report_cnt,
|
||||
atomic_read(&phys_enc->pending_kickoff_cnt),
|
||||
frame_event);
|
||||
|
||||
/* to avoid flooding, only log first time, and "dead" time */
|
||||
if (do_log) {
|
||||
DRM_ERROR("id:%d pp:%d kickoff timeout %d cnt %d koff_cnt %d\n",
|
||||
DRMID(phys_enc->parent),
|
||||
phys_enc->hw_pp->idx - PINGPONG_0,
|
||||
phys_enc->hw_ctl->idx - CTL_0,
|
||||
cmd_enc->pp_timeout_report_cnt,
|
||||
atomic_read(&phys_enc->pending_kickoff_cnt));
|
||||
|
||||
dpu_encoder_helper_unregister_irq(phys_enc, INTR_IDX_RDPTR);
|
||||
dpu_dbg_dump(false, __func__, true, true);
|
||||
}
|
||||
|
||||
atomic_add_unless(&phys_enc->pending_kickoff_cnt, -1, 0);
|
||||
|
||||
/* request a ctl reset before the next kickoff */
|
||||
phys_enc->enable_state = DPU_ENC_ERR_NEEDS_HW_RESET;
|
||||
|
||||
if (phys_enc->parent_ops->handle_frame_done)
|
||||
phys_enc->parent_ops->handle_frame_done(
|
||||
phys_enc->parent, phys_enc, frame_event);
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int _dpu_encoder_phys_cmd_wait_for_idle(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
struct dpu_encoder_wait_info wait_info;
|
||||
int ret;
|
||||
|
||||
if (!phys_enc) {
|
||||
DPU_ERROR("invalid encoder\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
wait_info.wq = &phys_enc->pending_kickoff_wq;
|
||||
wait_info.atomic_cnt = &phys_enc->pending_kickoff_cnt;
|
||||
wait_info.timeout_ms = KICKOFF_TIMEOUT_MS;
|
||||
|
||||
ret = dpu_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_PINGPONG,
|
||||
&wait_info);
|
||||
if (ret == -ETIMEDOUT)
|
||||
_dpu_encoder_phys_cmd_handle_ppdone_timeout(phys_enc);
|
||||
else if (!ret)
|
||||
cmd_enc->pp_timeout_report_cnt = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dpu_encoder_phys_cmd_control_vblank_irq(
|
||||
struct dpu_encoder_phys *phys_enc,
|
||||
bool enable)
|
||||
{
|
||||
int ret = 0;
|
||||
int refcount;
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_pp) {
|
||||
DPU_ERROR("invalid encoder\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
refcount = atomic_read(&phys_enc->vblank_refcount);
|
||||
|
||||
/* Slave encoders don't report vblank */
|
||||
if (!dpu_encoder_phys_cmd_is_master(phys_enc))
|
||||
goto end;
|
||||
|
||||
/* protect against negative */
|
||||
if (!enable && refcount == 0) {
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
DRM_DEBUG_KMS("id:%u pp:%d enable=%s/%d\n", DRMID(phys_enc->parent),
|
||||
phys_enc->hw_pp->idx - PINGPONG_0,
|
||||
enable ? "true" : "false", refcount);
|
||||
|
||||
if (enable && atomic_inc_return(&phys_enc->vblank_refcount) == 1)
|
||||
ret = dpu_encoder_helper_register_irq(phys_enc, INTR_IDX_RDPTR);
|
||||
else if (!enable && atomic_dec_return(&phys_enc->vblank_refcount) == 0)
|
||||
ret = dpu_encoder_helper_unregister_irq(phys_enc,
|
||||
INTR_IDX_RDPTR);
|
||||
|
||||
end:
|
||||
if (ret) {
|
||||
DRM_ERROR("vblank irq err id:%u pp:%d ret:%d, enable %s/%d\n",
|
||||
DRMID(phys_enc->parent),
|
||||
phys_enc->hw_pp->idx - PINGPONG_0, ret,
|
||||
enable ? "true" : "false", refcount);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dpu_encoder_phys_cmd_irq_control(struct dpu_encoder_phys *phys_enc,
|
||||
bool enable)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc;
|
||||
|
||||
if (!phys_enc)
|
||||
return;
|
||||
|
||||
cmd_enc = to_dpu_encoder_phys_cmd(phys_enc);
|
||||
|
||||
trace_dpu_enc_phys_cmd_irq_ctrl(DRMID(phys_enc->parent),
|
||||
phys_enc->hw_pp->idx - PINGPONG_0,
|
||||
enable, atomic_read(&phys_enc->vblank_refcount));
|
||||
|
||||
if (enable) {
|
||||
dpu_encoder_helper_register_irq(phys_enc, INTR_IDX_PINGPONG);
|
||||
dpu_encoder_helper_register_irq(phys_enc, INTR_IDX_UNDERRUN);
|
||||
dpu_encoder_phys_cmd_control_vblank_irq(phys_enc, true);
|
||||
|
||||
if (dpu_encoder_phys_cmd_is_master(phys_enc))
|
||||
dpu_encoder_helper_register_irq(phys_enc,
|
||||
INTR_IDX_CTL_START);
|
||||
} else {
|
||||
if (dpu_encoder_phys_cmd_is_master(phys_enc))
|
||||
dpu_encoder_helper_unregister_irq(phys_enc,
|
||||
INTR_IDX_CTL_START);
|
||||
|
||||
dpu_encoder_helper_unregister_irq(phys_enc, INTR_IDX_UNDERRUN);
|
||||
dpu_encoder_phys_cmd_control_vblank_irq(phys_enc, false);
|
||||
dpu_encoder_helper_unregister_irq(phys_enc, INTR_IDX_PINGPONG);
|
||||
}
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_tearcheck_config(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
struct dpu_hw_tear_check tc_cfg = { 0 };
|
||||
struct drm_display_mode *mode;
|
||||
bool tc_enable = true;
|
||||
u32 vsync_hz;
|
||||
struct msm_drm_private *priv;
|
||||
struct dpu_kms *dpu_kms;
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_pp) {
|
||||
DPU_ERROR("invalid encoder\n");
|
||||
return;
|
||||
}
|
||||
mode = &phys_enc->cached_mode;
|
||||
|
||||
DPU_DEBUG_CMDENC(cmd_enc, "pp %d\n", phys_enc->hw_pp->idx - PINGPONG_0);
|
||||
|
||||
if (!phys_enc->hw_pp->ops.setup_tearcheck ||
|
||||
!phys_enc->hw_pp->ops.enable_tearcheck) {
|
||||
DPU_DEBUG_CMDENC(cmd_enc, "tearcheck not supported\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dpu_kms = phys_enc->dpu_kms;
|
||||
if (!dpu_kms || !dpu_kms->dev || !dpu_kms->dev->dev_private) {
|
||||
DPU_ERROR("invalid device\n");
|
||||
return;
|
||||
}
|
||||
priv = dpu_kms->dev->dev_private;
|
||||
|
||||
/*
|
||||
* TE default: dsi byte clock calculated base on 70 fps;
|
||||
* around 14 ms to complete a kickoff cycle if te disabled;
|
||||
* vclk_line base on 60 fps; write is faster than read;
|
||||
* init == start == rdptr;
|
||||
*
|
||||
* vsync_count is ratio of MDP VSYNC clock frequency to LCD panel
|
||||
* frequency divided by the no. of rows (lines) in the LCDpanel.
|
||||
*/
|
||||
vsync_hz = dpu_kms_get_clk_rate(dpu_kms, "vsync_clk");
|
||||
if (vsync_hz <= 0) {
|
||||
DPU_DEBUG_CMDENC(cmd_enc, "invalid - vsync_hz %u\n",
|
||||
vsync_hz);
|
||||
return;
|
||||
}
|
||||
|
||||
tc_cfg.vsync_count = vsync_hz / (mode->vtotal * mode->vrefresh);
|
||||
|
||||
/* enable external TE after kickoff to avoid premature autorefresh */
|
||||
tc_cfg.hw_vsync_mode = 0;
|
||||
|
||||
/*
|
||||
* By setting sync_cfg_height to near max register value, we essentially
|
||||
* disable dpu hw generated TE signal, since hw TE will arrive first.
|
||||
* Only caveat is if due to error, we hit wrap-around.
|
||||
*/
|
||||
tc_cfg.sync_cfg_height = 0xFFF0;
|
||||
tc_cfg.vsync_init_val = mode->vdisplay;
|
||||
tc_cfg.sync_threshold_start = DEFAULT_TEARCHECK_SYNC_THRESH_START;
|
||||
tc_cfg.sync_threshold_continue = DEFAULT_TEARCHECK_SYNC_THRESH_CONTINUE;
|
||||
tc_cfg.start_pos = mode->vdisplay;
|
||||
tc_cfg.rd_ptr_irq = mode->vdisplay + 1;
|
||||
|
||||
DPU_DEBUG_CMDENC(cmd_enc,
|
||||
"tc %d vsync_clk_speed_hz %u vtotal %u vrefresh %u\n",
|
||||
phys_enc->hw_pp->idx - PINGPONG_0, vsync_hz,
|
||||
mode->vtotal, mode->vrefresh);
|
||||
DPU_DEBUG_CMDENC(cmd_enc,
|
||||
"tc %d enable %u start_pos %u rd_ptr_irq %u\n",
|
||||
phys_enc->hw_pp->idx - PINGPONG_0, tc_enable, tc_cfg.start_pos,
|
||||
tc_cfg.rd_ptr_irq);
|
||||
DPU_DEBUG_CMDENC(cmd_enc,
|
||||
"tc %d hw_vsync_mode %u vsync_count %u vsync_init_val %u\n",
|
||||
phys_enc->hw_pp->idx - PINGPONG_0, tc_cfg.hw_vsync_mode,
|
||||
tc_cfg.vsync_count, tc_cfg.vsync_init_val);
|
||||
DPU_DEBUG_CMDENC(cmd_enc,
|
||||
"tc %d cfgheight %u thresh_start %u thresh_cont %u\n",
|
||||
phys_enc->hw_pp->idx - PINGPONG_0, tc_cfg.sync_cfg_height,
|
||||
tc_cfg.sync_threshold_start, tc_cfg.sync_threshold_continue);
|
||||
|
||||
phys_enc->hw_pp->ops.setup_tearcheck(phys_enc->hw_pp, &tc_cfg);
|
||||
phys_enc->hw_pp->ops.enable_tearcheck(phys_enc->hw_pp, tc_enable);
|
||||
}
|
||||
|
||||
static void _dpu_encoder_phys_cmd_pingpong_config(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_ctl || !phys_enc->hw_pp
|
||||
|| !phys_enc->hw_ctl->ops.setup_intf_cfg) {
|
||||
DPU_ERROR("invalid arg(s), enc %d\n", phys_enc != 0);
|
||||
return;
|
||||
}
|
||||
|
||||
DPU_DEBUG_CMDENC(cmd_enc, "pp %d, enabling mode:\n",
|
||||
phys_enc->hw_pp->idx - PINGPONG_0);
|
||||
drm_mode_debug_printmodeline(&phys_enc->cached_mode);
|
||||
|
||||
_dpu_encoder_phys_cmd_update_intf_cfg(phys_enc);
|
||||
dpu_encoder_phys_cmd_tearcheck_config(phys_enc);
|
||||
}
|
||||
|
||||
static bool dpu_encoder_phys_cmd_needs_single_flush(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
/**
|
||||
* we do separate flush for each CTL and let
|
||||
* CTL_START synchronize them
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_enable_helper(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_hw_ctl *ctl;
|
||||
u32 flush_mask = 0;
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_ctl || !phys_enc->hw_pp) {
|
||||
DPU_ERROR("invalid arg(s), encoder %d\n", phys_enc != 0);
|
||||
return;
|
||||
}
|
||||
|
||||
dpu_encoder_helper_split_config(phys_enc, phys_enc->intf_idx);
|
||||
|
||||
_dpu_encoder_phys_cmd_pingpong_config(phys_enc);
|
||||
|
||||
if (!dpu_encoder_phys_cmd_is_master(phys_enc))
|
||||
goto skip_flush;
|
||||
|
||||
ctl = phys_enc->hw_ctl;
|
||||
ctl->ops.get_bitmask_intf(ctl, &flush_mask, phys_enc->intf_idx);
|
||||
ctl->ops.update_pending_flush(ctl, flush_mask);
|
||||
|
||||
skip_flush:
|
||||
return;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_enable(struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_pp) {
|
||||
DPU_ERROR("invalid phys encoder\n");
|
||||
return;
|
||||
}
|
||||
|
||||
DPU_DEBUG_CMDENC(cmd_enc, "pp %d\n", phys_enc->hw_pp->idx - PINGPONG_0);
|
||||
|
||||
if (phys_enc->enable_state == DPU_ENC_ENABLED) {
|
||||
DPU_ERROR("already enabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dpu_encoder_phys_cmd_enable_helper(phys_enc);
|
||||
phys_enc->enable_state = DPU_ENC_ENABLED;
|
||||
}
|
||||
|
||||
static void _dpu_encoder_phys_cmd_connect_te(
|
||||
struct dpu_encoder_phys *phys_enc, bool enable)
|
||||
{
|
||||
if (!phys_enc || !phys_enc->hw_pp ||
|
||||
!phys_enc->hw_pp->ops.connect_external_te)
|
||||
return;
|
||||
|
||||
trace_dpu_enc_phys_cmd_connect_te(DRMID(phys_enc->parent), enable);
|
||||
phys_enc->hw_pp->ops.connect_external_te(phys_enc->hw_pp, enable);
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_prepare_idle_pc(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
_dpu_encoder_phys_cmd_connect_te(phys_enc, false);
|
||||
}
|
||||
|
||||
static int dpu_encoder_phys_cmd_get_line_count(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_hw_pingpong *hw_pp;
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_pp)
|
||||
return -EINVAL;
|
||||
|
||||
if (!dpu_encoder_phys_cmd_is_master(phys_enc))
|
||||
return -EINVAL;
|
||||
|
||||
hw_pp = phys_enc->hw_pp;
|
||||
if (!hw_pp->ops.get_line_count)
|
||||
return -EINVAL;
|
||||
|
||||
return hw_pp->ops.get_line_count(hw_pp);
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_disable(struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_pp) {
|
||||
DPU_ERROR("invalid encoder\n");
|
||||
return;
|
||||
}
|
||||
DRM_DEBUG_KMS("id:%u pp:%d state:%d\n", DRMID(phys_enc->parent),
|
||||
phys_enc->hw_pp->idx - PINGPONG_0,
|
||||
phys_enc->enable_state);
|
||||
|
||||
if (phys_enc->enable_state == DPU_ENC_DISABLED) {
|
||||
DPU_ERROR_CMDENC(cmd_enc, "already disabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (phys_enc->hw_pp->ops.enable_tearcheck)
|
||||
phys_enc->hw_pp->ops.enable_tearcheck(phys_enc->hw_pp, false);
|
||||
phys_enc->enable_state = DPU_ENC_DISABLED;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_destroy(struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
|
||||
if (!phys_enc) {
|
||||
DPU_ERROR("invalid encoder\n");
|
||||
return;
|
||||
}
|
||||
kfree(cmd_enc);
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_get_hw_resources(
|
||||
struct dpu_encoder_phys *phys_enc,
|
||||
struct dpu_encoder_hw_resources *hw_res,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
|
||||
if (!phys_enc) {
|
||||
DPU_ERROR("invalid encoder\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((phys_enc->intf_idx - INTF_0) >= INTF_MAX) {
|
||||
DPU_ERROR("invalid intf idx:%d\n", phys_enc->intf_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
DPU_DEBUG_CMDENC(cmd_enc, "\n");
|
||||
hw_res->intfs[phys_enc->intf_idx - INTF_0] = INTF_MODE_CMD;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_prepare_for_kickoff(
|
||||
struct dpu_encoder_phys *phys_enc,
|
||||
struct dpu_encoder_kickoff_params *params)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
int ret;
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_pp) {
|
||||
DPU_ERROR("invalid encoder\n");
|
||||
return;
|
||||
}
|
||||
DRM_DEBUG_KMS("id:%u pp:%d pending_cnt:%d\n", DRMID(phys_enc->parent),
|
||||
phys_enc->hw_pp->idx - PINGPONG_0,
|
||||
atomic_read(&phys_enc->pending_kickoff_cnt));
|
||||
|
||||
/*
|
||||
* Mark kickoff request as outstanding. If there are more than one,
|
||||
* outstanding, then we have to wait for the previous one to complete
|
||||
*/
|
||||
ret = _dpu_encoder_phys_cmd_wait_for_idle(phys_enc);
|
||||
if (ret) {
|
||||
/* force pending_kickoff_cnt 0 to discard failed kickoff */
|
||||
atomic_set(&phys_enc->pending_kickoff_cnt, 0);
|
||||
DRM_ERROR("failed wait_for_idle: id:%u ret:%d pp:%d\n",
|
||||
DRMID(phys_enc->parent), ret,
|
||||
phys_enc->hw_pp->idx - PINGPONG_0);
|
||||
}
|
||||
|
||||
DPU_DEBUG_CMDENC(cmd_enc, "pp:%d pending_cnt %d\n",
|
||||
phys_enc->hw_pp->idx - PINGPONG_0,
|
||||
atomic_read(&phys_enc->pending_kickoff_cnt));
|
||||
}
|
||||
|
||||
static int _dpu_encoder_phys_cmd_wait_for_ctl_start(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_cmd *cmd_enc =
|
||||
to_dpu_encoder_phys_cmd(phys_enc);
|
||||
struct dpu_encoder_wait_info wait_info;
|
||||
int ret;
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_ctl) {
|
||||
DPU_ERROR("invalid argument(s)\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
wait_info.wq = &phys_enc->pending_kickoff_wq;
|
||||
wait_info.atomic_cnt = &phys_enc->pending_ctlstart_cnt;
|
||||
wait_info.timeout_ms = KICKOFF_TIMEOUT_MS;
|
||||
|
||||
ret = dpu_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_CTL_START,
|
||||
&wait_info);
|
||||
if (ret == -ETIMEDOUT) {
|
||||
DPU_ERROR_CMDENC(cmd_enc, "ctl start interrupt wait failed\n");
|
||||
ret = -EINVAL;
|
||||
} else if (!ret)
|
||||
ret = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dpu_encoder_phys_cmd_wait_for_tx_complete(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
int rc;
|
||||
struct dpu_encoder_phys_cmd *cmd_enc;
|
||||
|
||||
if (!phys_enc)
|
||||
return -EINVAL;
|
||||
|
||||
cmd_enc = to_dpu_encoder_phys_cmd(phys_enc);
|
||||
|
||||
rc = _dpu_encoder_phys_cmd_wait_for_idle(phys_enc);
|
||||
if (rc) {
|
||||
DRM_ERROR("failed wait_for_idle: id:%u ret:%d intf:%d\n",
|
||||
DRMID(phys_enc->parent), rc,
|
||||
phys_enc->intf_idx - INTF_0);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int dpu_encoder_phys_cmd_wait_for_commit_done(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
int rc = 0;
|
||||
struct dpu_encoder_phys_cmd *cmd_enc;
|
||||
|
||||
if (!phys_enc)
|
||||
return -EINVAL;
|
||||
|
||||
cmd_enc = to_dpu_encoder_phys_cmd(phys_enc);
|
||||
|
||||
/* only required for master controller */
|
||||
if (dpu_encoder_phys_cmd_is_master(phys_enc))
|
||||
rc = _dpu_encoder_phys_cmd_wait_for_ctl_start(phys_enc);
|
||||
|
||||
/* required for both controllers */
|
||||
if (!rc && cmd_enc->serialize_wait4pp)
|
||||
dpu_encoder_phys_cmd_prepare_for_kickoff(phys_enc, NULL);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int dpu_encoder_phys_cmd_wait_for_vblank(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
int rc = 0;
|
||||
struct dpu_encoder_phys_cmd *cmd_enc;
|
||||
struct dpu_encoder_wait_info wait_info;
|
||||
|
||||
if (!phys_enc)
|
||||
return -EINVAL;
|
||||
|
||||
cmd_enc = to_dpu_encoder_phys_cmd(phys_enc);
|
||||
|
||||
/* only required for master controller */
|
||||
if (!dpu_encoder_phys_cmd_is_master(phys_enc))
|
||||
return rc;
|
||||
|
||||
wait_info.wq = &cmd_enc->pending_vblank_wq;
|
||||
wait_info.atomic_cnt = &cmd_enc->pending_vblank_cnt;
|
||||
wait_info.timeout_ms = _dpu_encoder_phys_cmd_get_idle_timeout(cmd_enc);
|
||||
|
||||
atomic_inc(&cmd_enc->pending_vblank_cnt);
|
||||
|
||||
rc = dpu_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_RDPTR,
|
||||
&wait_info);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_handle_post_kickoff(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
if (!phys_enc)
|
||||
return;
|
||||
|
||||
/**
|
||||
* re-enable external TE, either for the first time after enabling
|
||||
* or if disabled for Autorefresh
|
||||
*/
|
||||
_dpu_encoder_phys_cmd_connect_te(phys_enc, true);
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_trigger_start(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
if (!phys_enc)
|
||||
return;
|
||||
|
||||
dpu_encoder_helper_trigger_start(phys_enc);
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_cmd_init_ops(
|
||||
struct dpu_encoder_phys_ops *ops)
|
||||
{
|
||||
ops->is_master = dpu_encoder_phys_cmd_is_master;
|
||||
ops->mode_set = dpu_encoder_phys_cmd_mode_set;
|
||||
ops->mode_fixup = dpu_encoder_phys_cmd_mode_fixup;
|
||||
ops->enable = dpu_encoder_phys_cmd_enable;
|
||||
ops->disable = dpu_encoder_phys_cmd_disable;
|
||||
ops->destroy = dpu_encoder_phys_cmd_destroy;
|
||||
ops->get_hw_resources = dpu_encoder_phys_cmd_get_hw_resources;
|
||||
ops->control_vblank_irq = dpu_encoder_phys_cmd_control_vblank_irq;
|
||||
ops->wait_for_commit_done = dpu_encoder_phys_cmd_wait_for_commit_done;
|
||||
ops->prepare_for_kickoff = dpu_encoder_phys_cmd_prepare_for_kickoff;
|
||||
ops->wait_for_tx_complete = dpu_encoder_phys_cmd_wait_for_tx_complete;
|
||||
ops->wait_for_vblank = dpu_encoder_phys_cmd_wait_for_vblank;
|
||||
ops->trigger_start = dpu_encoder_phys_cmd_trigger_start;
|
||||
ops->needs_single_flush = dpu_encoder_phys_cmd_needs_single_flush;
|
||||
ops->hw_reset = dpu_encoder_helper_hw_reset;
|
||||
ops->irq_control = dpu_encoder_phys_cmd_irq_control;
|
||||
ops->restore = dpu_encoder_phys_cmd_enable_helper;
|
||||
ops->prepare_idle_pc = dpu_encoder_phys_cmd_prepare_idle_pc;
|
||||
ops->handle_post_kickoff = dpu_encoder_phys_cmd_handle_post_kickoff;
|
||||
ops->get_line_count = dpu_encoder_phys_cmd_get_line_count;
|
||||
}
|
||||
|
||||
struct dpu_encoder_phys *dpu_encoder_phys_cmd_init(
|
||||
struct dpu_enc_phys_init_params *p)
|
||||
{
|
||||
struct dpu_encoder_phys *phys_enc = NULL;
|
||||
struct dpu_encoder_phys_cmd *cmd_enc = NULL;
|
||||
struct dpu_hw_mdp *hw_mdp;
|
||||
struct dpu_encoder_irq *irq;
|
||||
int i, ret = 0;
|
||||
|
||||
DPU_DEBUG("intf %d\n", p->intf_idx - INTF_0);
|
||||
|
||||
cmd_enc = kzalloc(sizeof(*cmd_enc), GFP_KERNEL);
|
||||
if (!cmd_enc) {
|
||||
ret = -ENOMEM;
|
||||
DPU_ERROR("failed to allocate\n");
|
||||
goto fail;
|
||||
}
|
||||
phys_enc = &cmd_enc->base;
|
||||
|
||||
hw_mdp = dpu_rm_get_mdp(&p->dpu_kms->rm);
|
||||
if (IS_ERR_OR_NULL(hw_mdp)) {
|
||||
ret = PTR_ERR(hw_mdp);
|
||||
DPU_ERROR("failed to get mdptop\n");
|
||||
goto fail_mdp_init;
|
||||
}
|
||||
phys_enc->hw_mdptop = hw_mdp;
|
||||
phys_enc->intf_idx = p->intf_idx;
|
||||
|
||||
dpu_encoder_phys_cmd_init_ops(&phys_enc->ops);
|
||||
phys_enc->parent = p->parent;
|
||||
phys_enc->parent_ops = p->parent_ops;
|
||||
phys_enc->dpu_kms = p->dpu_kms;
|
||||
phys_enc->split_role = p->split_role;
|
||||
phys_enc->intf_mode = INTF_MODE_CMD;
|
||||
phys_enc->enc_spinlock = p->enc_spinlock;
|
||||
cmd_enc->stream_sel = 0;
|
||||
phys_enc->enable_state = DPU_ENC_DISABLED;
|
||||
for (i = 0; i < INTR_IDX_MAX; i++) {
|
||||
irq = &phys_enc->irq[i];
|
||||
INIT_LIST_HEAD(&irq->cb.list);
|
||||
irq->irq_idx = -EINVAL;
|
||||
irq->hw_idx = -EINVAL;
|
||||
irq->cb.arg = phys_enc;
|
||||
}
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_CTL_START];
|
||||
irq->name = "ctl_start";
|
||||
irq->intr_type = DPU_IRQ_TYPE_CTL_START;
|
||||
irq->intr_idx = INTR_IDX_CTL_START;
|
||||
irq->cb.func = dpu_encoder_phys_cmd_ctl_start_irq;
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_PINGPONG];
|
||||
irq->name = "pp_done";
|
||||
irq->intr_type = DPU_IRQ_TYPE_PING_PONG_COMP;
|
||||
irq->intr_idx = INTR_IDX_PINGPONG;
|
||||
irq->cb.func = dpu_encoder_phys_cmd_pp_tx_done_irq;
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_RDPTR];
|
||||
irq->name = "pp_rd_ptr";
|
||||
irq->intr_type = DPU_IRQ_TYPE_PING_PONG_RD_PTR;
|
||||
irq->intr_idx = INTR_IDX_RDPTR;
|
||||
irq->cb.func = dpu_encoder_phys_cmd_pp_rd_ptr_irq;
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_UNDERRUN];
|
||||
irq->name = "underrun";
|
||||
irq->intr_type = DPU_IRQ_TYPE_INTF_UNDER_RUN;
|
||||
irq->intr_idx = INTR_IDX_UNDERRUN;
|
||||
irq->cb.func = dpu_encoder_phys_cmd_underrun_irq;
|
||||
|
||||
atomic_set(&phys_enc->vblank_refcount, 0);
|
||||
atomic_set(&phys_enc->pending_kickoff_cnt, 0);
|
||||
atomic_set(&phys_enc->pending_ctlstart_cnt, 0);
|
||||
atomic_set(&cmd_enc->pending_vblank_cnt, 0);
|
||||
init_waitqueue_head(&phys_enc->pending_kickoff_wq);
|
||||
init_waitqueue_head(&cmd_enc->pending_vblank_wq);
|
||||
|
||||
DPU_DEBUG_CMDENC(cmd_enc, "created\n");
|
||||
|
||||
return phys_enc;
|
||||
|
||||
fail_mdp_init:
|
||||
kfree(cmd_enc);
|
||||
fail:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
922
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_vid.c
Normal file
922
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_vid.c
Normal file
|
|
@ -0,0 +1,922 @@
|
|||
/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__
|
||||
#include "dpu_encoder_phys.h"
|
||||
#include "dpu_hw_interrupts.h"
|
||||
#include "dpu_core_irq.h"
|
||||
#include "dpu_formats.h"
|
||||
#include "dpu_trace.h"
|
||||
|
||||
#define DPU_DEBUG_VIDENC(e, fmt, ...) DPU_DEBUG("enc%d intf%d " fmt, \
|
||||
(e) && (e)->base.parent ? \
|
||||
(e)->base.parent->base.id : -1, \
|
||||
(e) && (e)->hw_intf ? \
|
||||
(e)->hw_intf->idx - INTF_0 : -1, ##__VA_ARGS__)
|
||||
|
||||
#define DPU_ERROR_VIDENC(e, fmt, ...) DPU_ERROR("enc%d intf%d " fmt, \
|
||||
(e) && (e)->base.parent ? \
|
||||
(e)->base.parent->base.id : -1, \
|
||||
(e) && (e)->hw_intf ? \
|
||||
(e)->hw_intf->idx - INTF_0 : -1, ##__VA_ARGS__)
|
||||
|
||||
#define to_dpu_encoder_phys_vid(x) \
|
||||
container_of(x, struct dpu_encoder_phys_vid, base)
|
||||
|
||||
static bool dpu_encoder_phys_vid_is_master(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (phys_enc->split_role != ENC_ROLE_SLAVE)
|
||||
ret = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void drm_mode_to_intf_timing_params(
|
||||
const struct dpu_encoder_phys_vid *vid_enc,
|
||||
const struct drm_display_mode *mode,
|
||||
struct intf_timing_params *timing)
|
||||
{
|
||||
memset(timing, 0, sizeof(*timing));
|
||||
|
||||
if ((mode->htotal < mode->hsync_end)
|
||||
|| (mode->hsync_start < mode->hdisplay)
|
||||
|| (mode->vtotal < mode->vsync_end)
|
||||
|| (mode->vsync_start < mode->vdisplay)
|
||||
|| (mode->hsync_end < mode->hsync_start)
|
||||
|| (mode->vsync_end < mode->vsync_start)) {
|
||||
DPU_ERROR(
|
||||
"invalid params - hstart:%d,hend:%d,htot:%d,hdisplay:%d\n",
|
||||
mode->hsync_start, mode->hsync_end,
|
||||
mode->htotal, mode->hdisplay);
|
||||
DPU_ERROR("vstart:%d,vend:%d,vtot:%d,vdisplay:%d\n",
|
||||
mode->vsync_start, mode->vsync_end,
|
||||
mode->vtotal, mode->vdisplay);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* https://www.kernel.org/doc/htmldocs/drm/ch02s05.html
|
||||
* Active Region Front Porch Sync Back Porch
|
||||
* <-----------------><------------><-----><----------->
|
||||
* <- [hv]display --->
|
||||
* <--------- [hv]sync_start ------>
|
||||
* <----------------- [hv]sync_end ------->
|
||||
* <---------------------------- [hv]total ------------->
|
||||
*/
|
||||
timing->width = mode->hdisplay; /* active width */
|
||||
timing->height = mode->vdisplay; /* active height */
|
||||
timing->xres = timing->width;
|
||||
timing->yres = timing->height;
|
||||
timing->h_back_porch = mode->htotal - mode->hsync_end;
|
||||
timing->h_front_porch = mode->hsync_start - mode->hdisplay;
|
||||
timing->v_back_porch = mode->vtotal - mode->vsync_end;
|
||||
timing->v_front_porch = mode->vsync_start - mode->vdisplay;
|
||||
timing->hsync_pulse_width = mode->hsync_end - mode->hsync_start;
|
||||
timing->vsync_pulse_width = mode->vsync_end - mode->vsync_start;
|
||||
timing->hsync_polarity = (mode->flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0;
|
||||
timing->vsync_polarity = (mode->flags & DRM_MODE_FLAG_NVSYNC) ? 1 : 0;
|
||||
timing->border_clr = 0;
|
||||
timing->underflow_clr = 0xff;
|
||||
timing->hsync_skew = mode->hskew;
|
||||
|
||||
/* DSI controller cannot handle active-low sync signals. */
|
||||
if (vid_enc->hw_intf->cap->type == INTF_DSI) {
|
||||
timing->hsync_polarity = 0;
|
||||
timing->vsync_polarity = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* For edp only:
|
||||
* DISPLAY_V_START = (VBP * HCYCLE) + HBP
|
||||
* DISPLAY_V_END = (VBP + VACTIVE) * HCYCLE - 1 - HFP
|
||||
*/
|
||||
/*
|
||||
* if (vid_enc->hw->cap->type == INTF_EDP) {
|
||||
* display_v_start += mode->htotal - mode->hsync_start;
|
||||
* display_v_end -= mode->hsync_start - mode->hdisplay;
|
||||
* }
|
||||
*/
|
||||
}
|
||||
|
||||
static inline u32 get_horizontal_total(const struct intf_timing_params *timing)
|
||||
{
|
||||
u32 active = timing->xres;
|
||||
u32 inactive =
|
||||
timing->h_back_porch + timing->h_front_porch +
|
||||
timing->hsync_pulse_width;
|
||||
return active + inactive;
|
||||
}
|
||||
|
||||
static inline u32 get_vertical_total(const struct intf_timing_params *timing)
|
||||
{
|
||||
u32 active = timing->yres;
|
||||
u32 inactive =
|
||||
timing->v_back_porch + timing->v_front_porch +
|
||||
timing->vsync_pulse_width;
|
||||
return active + inactive;
|
||||
}
|
||||
|
||||
/*
|
||||
* programmable_fetch_get_num_lines:
|
||||
* Number of fetch lines in vertical front porch
|
||||
* @timing: Pointer to the intf timing information for the requested mode
|
||||
*
|
||||
* Returns the number of fetch lines in vertical front porch at which mdp
|
||||
* can start fetching the next frame.
|
||||
*
|
||||
* Number of needed prefetch lines is anything that cannot be absorbed in the
|
||||
* start of frame time (back porch + vsync pulse width).
|
||||
*
|
||||
* Some panels have very large VFP, however we only need a total number of
|
||||
* lines based on the chip worst case latencies.
|
||||
*/
|
||||
static u32 programmable_fetch_get_num_lines(
|
||||
struct dpu_encoder_phys_vid *vid_enc,
|
||||
const struct intf_timing_params *timing)
|
||||
{
|
||||
u32 worst_case_needed_lines =
|
||||
vid_enc->hw_intf->cap->prog_fetch_lines_worst_case;
|
||||
u32 start_of_frame_lines =
|
||||
timing->v_back_porch + timing->vsync_pulse_width;
|
||||
u32 needed_vfp_lines = worst_case_needed_lines - start_of_frame_lines;
|
||||
u32 actual_vfp_lines = 0;
|
||||
|
||||
/* Fetch must be outside active lines, otherwise undefined. */
|
||||
if (start_of_frame_lines >= worst_case_needed_lines) {
|
||||
DPU_DEBUG_VIDENC(vid_enc,
|
||||
"prog fetch is not needed, large vbp+vsw\n");
|
||||
actual_vfp_lines = 0;
|
||||
} else if (timing->v_front_porch < needed_vfp_lines) {
|
||||
/* Warn fetch needed, but not enough porch in panel config */
|
||||
pr_warn_once
|
||||
("low vbp+vfp may lead to perf issues in some cases\n");
|
||||
DPU_DEBUG_VIDENC(vid_enc,
|
||||
"less vfp than fetch req, using entire vfp\n");
|
||||
actual_vfp_lines = timing->v_front_porch;
|
||||
} else {
|
||||
DPU_DEBUG_VIDENC(vid_enc, "room in vfp for needed prefetch\n");
|
||||
actual_vfp_lines = needed_vfp_lines;
|
||||
}
|
||||
|
||||
DPU_DEBUG_VIDENC(vid_enc,
|
||||
"v_front_porch %u v_back_porch %u vsync_pulse_width %u\n",
|
||||
timing->v_front_porch, timing->v_back_porch,
|
||||
timing->vsync_pulse_width);
|
||||
DPU_DEBUG_VIDENC(vid_enc,
|
||||
"wc_lines %u needed_vfp_lines %u actual_vfp_lines %u\n",
|
||||
worst_case_needed_lines, needed_vfp_lines, actual_vfp_lines);
|
||||
|
||||
return actual_vfp_lines;
|
||||
}
|
||||
|
||||
/*
|
||||
* programmable_fetch_config: Programs HW to prefetch lines by offsetting
|
||||
* the start of fetch into the vertical front porch for cases where the
|
||||
* vsync pulse width and vertical back porch time is insufficient
|
||||
*
|
||||
* Gets # of lines to pre-fetch, then calculate VSYNC counter value.
|
||||
* HW layer requires VSYNC counter of first pixel of tgt VFP line.
|
||||
*
|
||||
* @timing: Pointer to the intf timing information for the requested mode
|
||||
*/
|
||||
static void programmable_fetch_config(struct dpu_encoder_phys *phys_enc,
|
||||
const struct intf_timing_params *timing)
|
||||
{
|
||||
struct dpu_encoder_phys_vid *vid_enc =
|
||||
to_dpu_encoder_phys_vid(phys_enc);
|
||||
struct intf_prog_fetch f = { 0 };
|
||||
u32 vfp_fetch_lines = 0;
|
||||
u32 horiz_total = 0;
|
||||
u32 vert_total = 0;
|
||||
u32 vfp_fetch_start_vsync_counter = 0;
|
||||
unsigned long lock_flags;
|
||||
|
||||
if (WARN_ON_ONCE(!vid_enc->hw_intf->ops.setup_prg_fetch))
|
||||
return;
|
||||
|
||||
vfp_fetch_lines = programmable_fetch_get_num_lines(vid_enc, timing);
|
||||
if (vfp_fetch_lines) {
|
||||
vert_total = get_vertical_total(timing);
|
||||
horiz_total = get_horizontal_total(timing);
|
||||
vfp_fetch_start_vsync_counter =
|
||||
(vert_total - vfp_fetch_lines) * horiz_total + 1;
|
||||
f.enable = 1;
|
||||
f.fetch_start = vfp_fetch_start_vsync_counter;
|
||||
}
|
||||
|
||||
DPU_DEBUG_VIDENC(vid_enc,
|
||||
"vfp_fetch_lines %u vfp_fetch_start_vsync_counter %u\n",
|
||||
vfp_fetch_lines, vfp_fetch_start_vsync_counter);
|
||||
|
||||
spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags);
|
||||
vid_enc->hw_intf->ops.setup_prg_fetch(vid_enc->hw_intf, &f);
|
||||
spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags);
|
||||
}
|
||||
|
||||
static bool dpu_encoder_phys_vid_mode_fixup(
|
||||
struct dpu_encoder_phys *phys_enc,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adj_mode)
|
||||
{
|
||||
if (phys_enc)
|
||||
DPU_DEBUG_VIDENC(to_dpu_encoder_phys_vid(phys_enc), "\n");
|
||||
|
||||
/*
|
||||
* Modifying mode has consequences when the mode comes back to us
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_setup_timing_engine(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
struct drm_display_mode mode;
|
||||
struct intf_timing_params timing_params = { 0 };
|
||||
const struct dpu_format *fmt = NULL;
|
||||
u32 fmt_fourcc = DRM_FORMAT_RGB888;
|
||||
unsigned long lock_flags;
|
||||
struct dpu_hw_intf_cfg intf_cfg = { 0 };
|
||||
|
||||
if (!phys_enc || !phys_enc->hw_ctl->ops.setup_intf_cfg) {
|
||||
DPU_ERROR("invalid encoder %d\n", phys_enc != 0);
|
||||
return;
|
||||
}
|
||||
|
||||
mode = phys_enc->cached_mode;
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
if (!vid_enc->hw_intf->ops.setup_timing_gen) {
|
||||
DPU_ERROR("timing engine setup is not supported\n");
|
||||
return;
|
||||
}
|
||||
|
||||
DPU_DEBUG_VIDENC(vid_enc, "enabling mode:\n");
|
||||
drm_mode_debug_printmodeline(&mode);
|
||||
|
||||
if (phys_enc->split_role != ENC_ROLE_SOLO) {
|
||||
mode.hdisplay >>= 1;
|
||||
mode.htotal >>= 1;
|
||||
mode.hsync_start >>= 1;
|
||||
mode.hsync_end >>= 1;
|
||||
|
||||
DPU_DEBUG_VIDENC(vid_enc,
|
||||
"split_role %d, halve horizontal %d %d %d %d\n",
|
||||
phys_enc->split_role,
|
||||
mode.hdisplay, mode.htotal,
|
||||
mode.hsync_start, mode.hsync_end);
|
||||
}
|
||||
|
||||
drm_mode_to_intf_timing_params(vid_enc, &mode, &timing_params);
|
||||
|
||||
fmt = dpu_get_dpu_format(fmt_fourcc);
|
||||
DPU_DEBUG_VIDENC(vid_enc, "fmt_fourcc 0x%X\n", fmt_fourcc);
|
||||
|
||||
intf_cfg.intf = vid_enc->hw_intf->idx;
|
||||
intf_cfg.intf_mode_sel = DPU_CTL_MODE_SEL_VID;
|
||||
intf_cfg.stream_sel = 0; /* Don't care value for video mode */
|
||||
intf_cfg.mode_3d = dpu_encoder_helper_get_3d_blend_mode(phys_enc);
|
||||
|
||||
spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags);
|
||||
vid_enc->hw_intf->ops.setup_timing_gen(vid_enc->hw_intf,
|
||||
&timing_params, fmt);
|
||||
phys_enc->hw_ctl->ops.setup_intf_cfg(phys_enc->hw_ctl, &intf_cfg);
|
||||
spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags);
|
||||
|
||||
programmable_fetch_config(phys_enc, &timing_params);
|
||||
|
||||
vid_enc->timing_params = timing_params;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_vblank_irq(void *arg, int irq_idx)
|
||||
{
|
||||
struct dpu_encoder_phys *phys_enc = arg;
|
||||
struct dpu_hw_ctl *hw_ctl;
|
||||
unsigned long lock_flags;
|
||||
u32 flush_register = 0;
|
||||
int new_cnt = -1, old_cnt = -1;
|
||||
|
||||
if (!phys_enc)
|
||||
return;
|
||||
|
||||
hw_ctl = phys_enc->hw_ctl;
|
||||
if (!hw_ctl)
|
||||
return;
|
||||
|
||||
DPU_ATRACE_BEGIN("vblank_irq");
|
||||
|
||||
if (phys_enc->parent_ops->handle_vblank_virt)
|
||||
phys_enc->parent_ops->handle_vblank_virt(phys_enc->parent,
|
||||
phys_enc);
|
||||
|
||||
old_cnt = atomic_read(&phys_enc->pending_kickoff_cnt);
|
||||
|
||||
/*
|
||||
* only decrement the pending flush count if we've actually flushed
|
||||
* hardware. due to sw irq latency, vblank may have already happened
|
||||
* so we need to double-check with hw that it accepted the flush bits
|
||||
*/
|
||||
spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags);
|
||||
if (hw_ctl && hw_ctl->ops.get_flush_register)
|
||||
flush_register = hw_ctl->ops.get_flush_register(hw_ctl);
|
||||
|
||||
if (flush_register == 0)
|
||||
new_cnt = atomic_add_unless(&phys_enc->pending_kickoff_cnt,
|
||||
-1, 0);
|
||||
spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags);
|
||||
|
||||
/* Signal any waiting atomic commit thread */
|
||||
wake_up_all(&phys_enc->pending_kickoff_wq);
|
||||
DPU_ATRACE_END("vblank_irq");
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_underrun_irq(void *arg, int irq_idx)
|
||||
{
|
||||
struct dpu_encoder_phys *phys_enc = arg;
|
||||
|
||||
if (!phys_enc)
|
||||
return;
|
||||
|
||||
if (phys_enc->parent_ops->handle_underrun_virt)
|
||||
phys_enc->parent_ops->handle_underrun_virt(phys_enc->parent,
|
||||
phys_enc);
|
||||
}
|
||||
|
||||
static bool _dpu_encoder_phys_is_dual_ctl(struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
if (!phys_enc)
|
||||
return false;
|
||||
|
||||
if (phys_enc->topology_name == DPU_RM_TOPOLOGY_DUALPIPE)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool dpu_encoder_phys_vid_needs_single_flush(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
return (phys_enc && _dpu_encoder_phys_is_dual_ctl(phys_enc));
|
||||
}
|
||||
|
||||
static void _dpu_encoder_phys_vid_setup_irq_hw_idx(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_irq *irq;
|
||||
|
||||
/*
|
||||
* Initialize irq->hw_idx only when irq is not registered.
|
||||
* Prevent invalidating irq->irq_idx as modeset may be
|
||||
* called many times during dfps.
|
||||
*/
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_VSYNC];
|
||||
if (irq->irq_idx < 0)
|
||||
irq->hw_idx = phys_enc->intf_idx;
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_UNDERRUN];
|
||||
if (irq->irq_idx < 0)
|
||||
irq->hw_idx = phys_enc->intf_idx;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_mode_set(
|
||||
struct dpu_encoder_phys *phys_enc,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adj_mode)
|
||||
{
|
||||
struct dpu_rm *rm;
|
||||
struct dpu_rm_hw_iter iter;
|
||||
int i, instance;
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
|
||||
if (!phys_enc || !phys_enc->dpu_kms) {
|
||||
DPU_ERROR("invalid encoder/kms\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rm = &phys_enc->dpu_kms->rm;
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
|
||||
if (adj_mode) {
|
||||
phys_enc->cached_mode = *adj_mode;
|
||||
drm_mode_debug_printmodeline(adj_mode);
|
||||
DPU_DEBUG_VIDENC(vid_enc, "caching mode:\n");
|
||||
}
|
||||
|
||||
instance = phys_enc->split_role == ENC_ROLE_SLAVE ? 1 : 0;
|
||||
|
||||
/* Retrieve previously allocated HW Resources. Shouldn't fail */
|
||||
dpu_rm_init_hw_iter(&iter, phys_enc->parent->base.id, DPU_HW_BLK_CTL);
|
||||
for (i = 0; i <= instance; i++) {
|
||||
if (dpu_rm_get_hw(rm, &iter))
|
||||
phys_enc->hw_ctl = (struct dpu_hw_ctl *)iter.hw;
|
||||
}
|
||||
if (IS_ERR_OR_NULL(phys_enc->hw_ctl)) {
|
||||
DPU_ERROR_VIDENC(vid_enc, "failed to init ctl, %ld\n",
|
||||
PTR_ERR(phys_enc->hw_ctl));
|
||||
phys_enc->hw_ctl = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
_dpu_encoder_phys_vid_setup_irq_hw_idx(phys_enc);
|
||||
}
|
||||
|
||||
static int dpu_encoder_phys_vid_control_vblank_irq(
|
||||
struct dpu_encoder_phys *phys_enc,
|
||||
bool enable)
|
||||
{
|
||||
int ret = 0;
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
int refcount;
|
||||
|
||||
if (!phys_enc) {
|
||||
DPU_ERROR("invalid encoder\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
refcount = atomic_read(&phys_enc->vblank_refcount);
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
|
||||
/* Slave encoders don't report vblank */
|
||||
if (!dpu_encoder_phys_vid_is_master(phys_enc))
|
||||
goto end;
|
||||
|
||||
/* protect against negative */
|
||||
if (!enable && refcount == 0) {
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
DRM_DEBUG_KMS("id:%u enable=%d/%d\n", DRMID(phys_enc->parent), enable,
|
||||
atomic_read(&phys_enc->vblank_refcount));
|
||||
|
||||
if (enable && atomic_inc_return(&phys_enc->vblank_refcount) == 1)
|
||||
ret = dpu_encoder_helper_register_irq(phys_enc, INTR_IDX_VSYNC);
|
||||
else if (!enable && atomic_dec_return(&phys_enc->vblank_refcount) == 0)
|
||||
ret = dpu_encoder_helper_unregister_irq(phys_enc,
|
||||
INTR_IDX_VSYNC);
|
||||
|
||||
end:
|
||||
if (ret) {
|
||||
DRM_ERROR("failed: id:%u intf:%d ret:%d enable:%d refcnt:%d\n",
|
||||
DRMID(phys_enc->parent),
|
||||
vid_enc->hw_intf->idx - INTF_0, ret, enable,
|
||||
refcount);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_enable(struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct msm_drm_private *priv;
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
struct dpu_hw_intf *intf;
|
||||
struct dpu_hw_ctl *ctl;
|
||||
u32 flush_mask = 0;
|
||||
|
||||
if (!phys_enc || !phys_enc->parent || !phys_enc->parent->dev ||
|
||||
!phys_enc->parent->dev->dev_private) {
|
||||
DPU_ERROR("invalid encoder/device\n");
|
||||
return;
|
||||
}
|
||||
priv = phys_enc->parent->dev->dev_private;
|
||||
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
intf = vid_enc->hw_intf;
|
||||
ctl = phys_enc->hw_ctl;
|
||||
if (!vid_enc->hw_intf || !phys_enc->hw_ctl) {
|
||||
DPU_ERROR("invalid hw_intf %d hw_ctl %d\n",
|
||||
vid_enc->hw_intf != 0, phys_enc->hw_ctl != 0);
|
||||
return;
|
||||
}
|
||||
|
||||
DPU_DEBUG_VIDENC(vid_enc, "\n");
|
||||
|
||||
if (WARN_ON(!vid_enc->hw_intf->ops.enable_timing))
|
||||
return;
|
||||
|
||||
dpu_encoder_helper_split_config(phys_enc, vid_enc->hw_intf->idx);
|
||||
|
||||
dpu_encoder_phys_vid_setup_timing_engine(phys_enc);
|
||||
|
||||
/*
|
||||
* For single flush cases (dual-ctl or pp-split), skip setting the
|
||||
* flush bit for the slave intf, since both intfs use same ctl
|
||||
* and HW will only flush the master.
|
||||
*/
|
||||
if (dpu_encoder_phys_vid_needs_single_flush(phys_enc) &&
|
||||
!dpu_encoder_phys_vid_is_master(phys_enc))
|
||||
goto skip_flush;
|
||||
|
||||
ctl->ops.get_bitmask_intf(ctl, &flush_mask, intf->idx);
|
||||
ctl->ops.update_pending_flush(ctl, flush_mask);
|
||||
|
||||
skip_flush:
|
||||
DPU_DEBUG_VIDENC(vid_enc, "update pending flush ctl %d flush_mask %x\n",
|
||||
ctl->idx - CTL_0, flush_mask);
|
||||
|
||||
/* ctl_flush & timing engine enable will be triggered by framework */
|
||||
if (phys_enc->enable_state == DPU_ENC_DISABLED)
|
||||
phys_enc->enable_state = DPU_ENC_ENABLING;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_destroy(struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
|
||||
if (!phys_enc) {
|
||||
DPU_ERROR("invalid encoder\n");
|
||||
return;
|
||||
}
|
||||
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
DPU_DEBUG_VIDENC(vid_enc, "\n");
|
||||
kfree(vid_enc);
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_get_hw_resources(
|
||||
struct dpu_encoder_phys *phys_enc,
|
||||
struct dpu_encoder_hw_resources *hw_res,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
|
||||
if (!phys_enc || !hw_res) {
|
||||
DPU_ERROR("invalid arg(s), enc %d hw_res %d conn_state %d\n",
|
||||
phys_enc != 0, hw_res != 0, conn_state != 0);
|
||||
return;
|
||||
}
|
||||
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
if (!vid_enc->hw_intf) {
|
||||
DPU_ERROR("invalid arg(s), hw_intf\n");
|
||||
return;
|
||||
}
|
||||
|
||||
DPU_DEBUG_VIDENC(vid_enc, "\n");
|
||||
hw_res->intfs[vid_enc->hw_intf->idx - INTF_0] = INTF_MODE_VIDEO;
|
||||
}
|
||||
|
||||
static int _dpu_encoder_phys_vid_wait_for_vblank(
|
||||
struct dpu_encoder_phys *phys_enc, bool notify)
|
||||
{
|
||||
struct dpu_encoder_wait_info wait_info;
|
||||
int ret;
|
||||
|
||||
if (!phys_enc) {
|
||||
pr_err("invalid encoder\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
wait_info.wq = &phys_enc->pending_kickoff_wq;
|
||||
wait_info.atomic_cnt = &phys_enc->pending_kickoff_cnt;
|
||||
wait_info.timeout_ms = KICKOFF_TIMEOUT_MS;
|
||||
|
||||
if (!dpu_encoder_phys_vid_is_master(phys_enc)) {
|
||||
if (notify && phys_enc->parent_ops->handle_frame_done)
|
||||
phys_enc->parent_ops->handle_frame_done(
|
||||
phys_enc->parent, phys_enc,
|
||||
DPU_ENCODER_FRAME_EVENT_DONE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Wait for kickoff to complete */
|
||||
ret = dpu_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_VSYNC,
|
||||
&wait_info);
|
||||
|
||||
if (ret == -ETIMEDOUT) {
|
||||
dpu_encoder_helper_report_irq_timeout(phys_enc, INTR_IDX_VSYNC);
|
||||
} else if (!ret && notify && phys_enc->parent_ops->handle_frame_done)
|
||||
phys_enc->parent_ops->handle_frame_done(
|
||||
phys_enc->parent, phys_enc,
|
||||
DPU_ENCODER_FRAME_EVENT_DONE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dpu_encoder_phys_vid_wait_for_vblank(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
return _dpu_encoder_phys_vid_wait_for_vblank(phys_enc, true);
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_prepare_for_kickoff(
|
||||
struct dpu_encoder_phys *phys_enc,
|
||||
struct dpu_encoder_kickoff_params *params)
|
||||
{
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
struct dpu_hw_ctl *ctl;
|
||||
int rc;
|
||||
|
||||
if (!phys_enc || !params) {
|
||||
DPU_ERROR("invalid encoder/parameters\n");
|
||||
return;
|
||||
}
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
|
||||
ctl = phys_enc->hw_ctl;
|
||||
if (!ctl || !ctl->ops.wait_reset_status)
|
||||
return;
|
||||
|
||||
/*
|
||||
* hw supports hardware initiated ctl reset, so before we kickoff a new
|
||||
* frame, need to check and wait for hw initiated ctl reset completion
|
||||
*/
|
||||
rc = ctl->ops.wait_reset_status(ctl);
|
||||
if (rc) {
|
||||
DPU_ERROR_VIDENC(vid_enc, "ctl %d reset failure: %d\n",
|
||||
ctl->idx, rc);
|
||||
dpu_encoder_helper_unregister_irq(phys_enc, INTR_IDX_VSYNC);
|
||||
dpu_dbg_dump(false, __func__, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_disable(struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct msm_drm_private *priv;
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
unsigned long lock_flags;
|
||||
int ret;
|
||||
|
||||
if (!phys_enc || !phys_enc->parent || !phys_enc->parent->dev ||
|
||||
!phys_enc->parent->dev->dev_private) {
|
||||
DPU_ERROR("invalid encoder/device\n");
|
||||
return;
|
||||
}
|
||||
priv = phys_enc->parent->dev->dev_private;
|
||||
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
if (!vid_enc->hw_intf || !phys_enc->hw_ctl) {
|
||||
DPU_ERROR("invalid hw_intf %d hw_ctl %d\n",
|
||||
vid_enc->hw_intf != 0, phys_enc->hw_ctl != 0);
|
||||
return;
|
||||
}
|
||||
|
||||
DPU_DEBUG_VIDENC(vid_enc, "\n");
|
||||
|
||||
if (WARN_ON(!vid_enc->hw_intf->ops.enable_timing))
|
||||
return;
|
||||
|
||||
if (phys_enc->enable_state == DPU_ENC_DISABLED) {
|
||||
DPU_ERROR("already disabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags);
|
||||
vid_enc->hw_intf->ops.enable_timing(vid_enc->hw_intf, 0);
|
||||
if (dpu_encoder_phys_vid_is_master(phys_enc))
|
||||
dpu_encoder_phys_inc_pending(phys_enc);
|
||||
spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags);
|
||||
|
||||
/*
|
||||
* Wait for a vsync so we know the ENABLE=0 latched before
|
||||
* the (connector) source of the vsync's gets disabled,
|
||||
* otherwise we end up in a funny state if we re-enable
|
||||
* before the disable latches, which results that some of
|
||||
* the settings changes for the new modeset (like new
|
||||
* scanout buffer) don't latch properly..
|
||||
*/
|
||||
if (dpu_encoder_phys_vid_is_master(phys_enc)) {
|
||||
ret = _dpu_encoder_phys_vid_wait_for_vblank(phys_enc, false);
|
||||
if (ret) {
|
||||
atomic_set(&phys_enc->pending_kickoff_cnt, 0);
|
||||
DRM_ERROR("wait disable failed: id:%u intf:%d ret:%d\n",
|
||||
DRMID(phys_enc->parent),
|
||||
vid_enc->hw_intf->idx - INTF_0, ret);
|
||||
}
|
||||
}
|
||||
|
||||
phys_enc->enable_state = DPU_ENC_DISABLED;
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_handle_post_kickoff(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
unsigned long lock_flags;
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
|
||||
if (!phys_enc) {
|
||||
DPU_ERROR("invalid encoder\n");
|
||||
return;
|
||||
}
|
||||
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
DPU_DEBUG_VIDENC(vid_enc, "enable_state %d\n", phys_enc->enable_state);
|
||||
|
||||
/*
|
||||
* Video mode must flush CTL before enabling timing engine
|
||||
* Video encoders need to turn on their interfaces now
|
||||
*/
|
||||
if (phys_enc->enable_state == DPU_ENC_ENABLING) {
|
||||
trace_dpu_enc_phys_vid_post_kickoff(DRMID(phys_enc->parent),
|
||||
vid_enc->hw_intf->idx - INTF_0);
|
||||
spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags);
|
||||
vid_enc->hw_intf->ops.enable_timing(vid_enc->hw_intf, 1);
|
||||
spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags);
|
||||
phys_enc->enable_state = DPU_ENC_ENABLED;
|
||||
}
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_irq_control(struct dpu_encoder_phys *phys_enc,
|
||||
bool enable)
|
||||
{
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
int ret;
|
||||
|
||||
if (!phys_enc)
|
||||
return;
|
||||
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
|
||||
trace_dpu_enc_phys_vid_irq_ctrl(DRMID(phys_enc->parent),
|
||||
vid_enc->hw_intf->idx - INTF_0,
|
||||
enable,
|
||||
atomic_read(&phys_enc->vblank_refcount));
|
||||
|
||||
if (enable) {
|
||||
ret = dpu_encoder_phys_vid_control_vblank_irq(phys_enc, true);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
dpu_encoder_helper_register_irq(phys_enc, INTR_IDX_UNDERRUN);
|
||||
} else {
|
||||
dpu_encoder_phys_vid_control_vblank_irq(phys_enc, false);
|
||||
dpu_encoder_helper_unregister_irq(phys_enc, INTR_IDX_UNDERRUN);
|
||||
}
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_setup_misr(struct dpu_encoder_phys *phys_enc,
|
||||
bool enable, u32 frame_count)
|
||||
{
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
|
||||
if (!phys_enc)
|
||||
return;
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
|
||||
if (vid_enc->hw_intf && vid_enc->hw_intf->ops.setup_misr)
|
||||
vid_enc->hw_intf->ops.setup_misr(vid_enc->hw_intf,
|
||||
enable, frame_count);
|
||||
}
|
||||
|
||||
static u32 dpu_encoder_phys_vid_collect_misr(struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
|
||||
if (!phys_enc)
|
||||
return 0;
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
|
||||
return vid_enc->hw_intf && vid_enc->hw_intf->ops.collect_misr ?
|
||||
vid_enc->hw_intf->ops.collect_misr(vid_enc->hw_intf) : 0;
|
||||
}
|
||||
|
||||
static int dpu_encoder_phys_vid_get_line_count(
|
||||
struct dpu_encoder_phys *phys_enc)
|
||||
{
|
||||
struct dpu_encoder_phys_vid *vid_enc;
|
||||
|
||||
if (!phys_enc)
|
||||
return -EINVAL;
|
||||
|
||||
if (!dpu_encoder_phys_vid_is_master(phys_enc))
|
||||
return -EINVAL;
|
||||
|
||||
vid_enc = to_dpu_encoder_phys_vid(phys_enc);
|
||||
if (!vid_enc->hw_intf || !vid_enc->hw_intf->ops.get_line_count)
|
||||
return -EINVAL;
|
||||
|
||||
return vid_enc->hw_intf->ops.get_line_count(vid_enc->hw_intf);
|
||||
}
|
||||
|
||||
static void dpu_encoder_phys_vid_init_ops(struct dpu_encoder_phys_ops *ops)
|
||||
{
|
||||
ops->is_master = dpu_encoder_phys_vid_is_master;
|
||||
ops->mode_set = dpu_encoder_phys_vid_mode_set;
|
||||
ops->mode_fixup = dpu_encoder_phys_vid_mode_fixup;
|
||||
ops->enable = dpu_encoder_phys_vid_enable;
|
||||
ops->disable = dpu_encoder_phys_vid_disable;
|
||||
ops->destroy = dpu_encoder_phys_vid_destroy;
|
||||
ops->get_hw_resources = dpu_encoder_phys_vid_get_hw_resources;
|
||||
ops->control_vblank_irq = dpu_encoder_phys_vid_control_vblank_irq;
|
||||
ops->wait_for_commit_done = dpu_encoder_phys_vid_wait_for_vblank;
|
||||
ops->wait_for_vblank = dpu_encoder_phys_vid_wait_for_vblank;
|
||||
ops->wait_for_tx_complete = dpu_encoder_phys_vid_wait_for_vblank;
|
||||
ops->irq_control = dpu_encoder_phys_vid_irq_control;
|
||||
ops->prepare_for_kickoff = dpu_encoder_phys_vid_prepare_for_kickoff;
|
||||
ops->handle_post_kickoff = dpu_encoder_phys_vid_handle_post_kickoff;
|
||||
ops->needs_single_flush = dpu_encoder_phys_vid_needs_single_flush;
|
||||
ops->setup_misr = dpu_encoder_phys_vid_setup_misr;
|
||||
ops->collect_misr = dpu_encoder_phys_vid_collect_misr;
|
||||
ops->hw_reset = dpu_encoder_helper_hw_reset;
|
||||
ops->get_line_count = dpu_encoder_phys_vid_get_line_count;
|
||||
}
|
||||
|
||||
struct dpu_encoder_phys *dpu_encoder_phys_vid_init(
|
||||
struct dpu_enc_phys_init_params *p)
|
||||
{
|
||||
struct dpu_encoder_phys *phys_enc = NULL;
|
||||
struct dpu_encoder_phys_vid *vid_enc = NULL;
|
||||
struct dpu_rm_hw_iter iter;
|
||||
struct dpu_hw_mdp *hw_mdp;
|
||||
struct dpu_encoder_irq *irq;
|
||||
int i, ret = 0;
|
||||
|
||||
if (!p) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vid_enc = kzalloc(sizeof(*vid_enc), GFP_KERNEL);
|
||||
if (!vid_enc) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
phys_enc = &vid_enc->base;
|
||||
|
||||
hw_mdp = dpu_rm_get_mdp(&p->dpu_kms->rm);
|
||||
if (IS_ERR_OR_NULL(hw_mdp)) {
|
||||
ret = PTR_ERR(hw_mdp);
|
||||
DPU_ERROR("failed to get mdptop\n");
|
||||
goto fail;
|
||||
}
|
||||
phys_enc->hw_mdptop = hw_mdp;
|
||||
phys_enc->intf_idx = p->intf_idx;
|
||||
|
||||
/**
|
||||
* hw_intf resource permanently assigned to this encoder
|
||||
* Other resources allocated at atomic commit time by use case
|
||||
*/
|
||||
dpu_rm_init_hw_iter(&iter, 0, DPU_HW_BLK_INTF);
|
||||
while (dpu_rm_get_hw(&p->dpu_kms->rm, &iter)) {
|
||||
struct dpu_hw_intf *hw_intf = (struct dpu_hw_intf *)iter.hw;
|
||||
|
||||
if (hw_intf->idx == p->intf_idx) {
|
||||
vid_enc->hw_intf = hw_intf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vid_enc->hw_intf) {
|
||||
ret = -EINVAL;
|
||||
DPU_ERROR("failed to get hw_intf\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
DPU_DEBUG_VIDENC(vid_enc, "\n");
|
||||
|
||||
dpu_encoder_phys_vid_init_ops(&phys_enc->ops);
|
||||
phys_enc->parent = p->parent;
|
||||
phys_enc->parent_ops = p->parent_ops;
|
||||
phys_enc->dpu_kms = p->dpu_kms;
|
||||
phys_enc->split_role = p->split_role;
|
||||
phys_enc->intf_mode = INTF_MODE_VIDEO;
|
||||
phys_enc->enc_spinlock = p->enc_spinlock;
|
||||
for (i = 0; i < INTR_IDX_MAX; i++) {
|
||||
irq = &phys_enc->irq[i];
|
||||
INIT_LIST_HEAD(&irq->cb.list);
|
||||
irq->irq_idx = -EINVAL;
|
||||
irq->hw_idx = -EINVAL;
|
||||
irq->cb.arg = phys_enc;
|
||||
}
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_VSYNC];
|
||||
irq->name = "vsync_irq";
|
||||
irq->intr_type = DPU_IRQ_TYPE_INTF_VSYNC;
|
||||
irq->intr_idx = INTR_IDX_VSYNC;
|
||||
irq->cb.func = dpu_encoder_phys_vid_vblank_irq;
|
||||
|
||||
irq = &phys_enc->irq[INTR_IDX_UNDERRUN];
|
||||
irq->name = "underrun";
|
||||
irq->intr_type = DPU_IRQ_TYPE_INTF_UNDER_RUN;
|
||||
irq->intr_idx = INTR_IDX_UNDERRUN;
|
||||
irq->cb.func = dpu_encoder_phys_vid_underrun_irq;
|
||||
|
||||
atomic_set(&phys_enc->vblank_refcount, 0);
|
||||
atomic_set(&phys_enc->pending_kickoff_cnt, 0);
|
||||
init_waitqueue_head(&phys_enc->pending_kickoff_wq);
|
||||
phys_enc->enable_state = DPU_ENC_DISABLED;
|
||||
|
||||
DPU_DEBUG_VIDENC(vid_enc, "created intf idx:%d\n", p->intf_idx);
|
||||
|
||||
return phys_enc;
|
||||
|
||||
fail:
|
||||
DPU_ERROR("failed to create encoder\n");
|
||||
if (vid_enc)
|
||||
dpu_encoder_phys_vid_destroy(phys_enc);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
1214
drivers/gpu/drm/msm/disp/dpu1/dpu_formats.c
Normal file
1214
drivers/gpu/drm/msm/disp/dpu1/dpu_formats.c
Normal file
File diff suppressed because it is too large
Load diff
136
drivers/gpu/drm/msm/disp/dpu1/dpu_formats.h
Normal file
136
drivers/gpu/drm/msm/disp/dpu1/dpu_formats.h
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _DPU_FORMATS_H
|
||||
#define _DPU_FORMATS_H
|
||||
|
||||
#include <drm/drm_fourcc.h>
|
||||
#include "msm_gem.h"
|
||||
#include "dpu_hw_mdss.h"
|
||||
|
||||
/**
|
||||
* dpu_get_dpu_format_ext() - Returns dpu format structure pointer.
|
||||
* @format: DRM FourCC Code
|
||||
* @modifiers: format modifier array from client, one per plane
|
||||
*/
|
||||
const struct dpu_format *dpu_get_dpu_format_ext(
|
||||
const uint32_t format,
|
||||
const uint64_t modifier);
|
||||
|
||||
#define dpu_get_dpu_format(f) dpu_get_dpu_format_ext(f, 0)
|
||||
|
||||
/**
|
||||
* dpu_get_msm_format - get an dpu_format by its msm_format base
|
||||
* callback function registers with the msm_kms layer
|
||||
* @kms: kms driver
|
||||
* @format: DRM FourCC Code
|
||||
* @modifiers: data layout modifier
|
||||
*/
|
||||
const struct msm_format *dpu_get_msm_format(
|
||||
struct msm_kms *kms,
|
||||
const uint32_t format,
|
||||
const uint64_t modifiers);
|
||||
|
||||
/**
|
||||
* dpu_populate_formats - populate the given array with fourcc codes supported
|
||||
* @format_list: pointer to list of possible formats
|
||||
* @pixel_formats: array to populate with fourcc codes
|
||||
* @pixel_modifiers: array to populate with drm modifiers, can be NULL
|
||||
* @pixel_formats_max: length of pixel formats array
|
||||
* Return: number of elements populated
|
||||
*/
|
||||
uint32_t dpu_populate_formats(
|
||||
const struct dpu_format_extended *format_list,
|
||||
uint32_t *pixel_formats,
|
||||
uint64_t *pixel_modifiers,
|
||||
uint32_t pixel_formats_max);
|
||||
|
||||
/**
|
||||
* dpu_format_get_plane_sizes - calculate size and layout of given buffer format
|
||||
* @fmt: pointer to dpu_format
|
||||
* @w: width of the buffer
|
||||
* @h: height of the buffer
|
||||
* @layout: layout of the buffer
|
||||
* @pitches: array of size [DPU_MAX_PLANES] to populate
|
||||
* pitch for each plane
|
||||
*
|
||||
* Return: size of the buffer
|
||||
*/
|
||||
int dpu_format_get_plane_sizes(
|
||||
const struct dpu_format *fmt,
|
||||
const uint32_t w,
|
||||
const uint32_t h,
|
||||
struct dpu_hw_fmt_layout *layout,
|
||||
const uint32_t *pitches);
|
||||
|
||||
/**
|
||||
* dpu_format_get_block_size - get block size of given format when
|
||||
* operating in block mode
|
||||
* @fmt: pointer to dpu_format
|
||||
* @w: pointer to width of the block
|
||||
* @h: pointer to height of the block
|
||||
*
|
||||
* Return: 0 if success; error oode otherwise
|
||||
*/
|
||||
int dpu_format_get_block_size(const struct dpu_format *fmt,
|
||||
uint32_t *w, uint32_t *h);
|
||||
|
||||
/**
|
||||
* dpu_format_check_modified_format - validate format and buffers for
|
||||
* dpu non-standard, i.e. modified format
|
||||
* @kms: kms driver
|
||||
* @msm_fmt: pointer to the msm_fmt base pointer of an dpu_format
|
||||
* @cmd: fb_cmd2 structure user request
|
||||
* @bos: gem buffer object list
|
||||
*
|
||||
* Return: error code on failure, 0 on success
|
||||
*/
|
||||
int dpu_format_check_modified_format(
|
||||
const struct msm_kms *kms,
|
||||
const struct msm_format *msm_fmt,
|
||||
const struct drm_mode_fb_cmd2 *cmd,
|
||||
struct drm_gem_object **bos);
|
||||
|
||||
/**
|
||||
* dpu_format_populate_layout - populate the given format layout based on
|
||||
* mmu, fb, and format found in the fb
|
||||
* @aspace: address space pointer
|
||||
* @fb: framebuffer pointer
|
||||
* @fmtl: format layout structure to populate
|
||||
*
|
||||
* Return: error code on failure, -EAGAIN if success but the addresses
|
||||
* are the same as before or 0 if new addresses were populated
|
||||
*/
|
||||
int dpu_format_populate_layout(
|
||||
struct msm_gem_address_space *aspace,
|
||||
struct drm_framebuffer *fb,
|
||||
struct dpu_hw_fmt_layout *fmtl);
|
||||
|
||||
/**
|
||||
* dpu_format_get_framebuffer_size - get framebuffer memory size
|
||||
* @format: DRM pixel format
|
||||
* @width: pixel width
|
||||
* @height: pixel height
|
||||
* @pitches: array of size [DPU_MAX_PLANES] to populate
|
||||
* pitch for each plane
|
||||
* @modifiers: drm modifier
|
||||
*
|
||||
* Return: memory size required for frame buffer
|
||||
*/
|
||||
uint32_t dpu_format_get_framebuffer_size(
|
||||
const uint32_t format,
|
||||
const uint32_t width,
|
||||
const uint32_t height,
|
||||
const uint32_t *pitches,
|
||||
const uint64_t modifiers);
|
||||
|
||||
#endif /*_DPU_FORMATS_H */
|
||||
155
drivers/gpu/drm/msm/disp/dpu1/dpu_hw_blk.c
Normal file
155
drivers/gpu/drm/msm/disp/dpu1/dpu_hw_blk.c
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__
|
||||
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "dpu_hw_mdss.h"
|
||||
#include "dpu_hw_blk.h"
|
||||
|
||||
/* Serialization lock for dpu_hw_blk_list */
|
||||
static DEFINE_MUTEX(dpu_hw_blk_lock);
|
||||
|
||||
/* List of all hw block objects */
|
||||
static LIST_HEAD(dpu_hw_blk_list);
|
||||
|
||||
/**
|
||||
* dpu_hw_blk_init - initialize hw block object
|
||||
* @type: hw block type - enum dpu_hw_blk_type
|
||||
* @id: instance id of the hw block
|
||||
* @ops: Pointer to block operations
|
||||
* return: 0 if success; error code otherwise
|
||||
*/
|
||||
int dpu_hw_blk_init(struct dpu_hw_blk *hw_blk, u32 type, int id,
|
||||
struct dpu_hw_blk_ops *ops)
|
||||
{
|
||||
if (!hw_blk) {
|
||||
pr_err("invalid parameters\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&hw_blk->list);
|
||||
hw_blk->type = type;
|
||||
hw_blk->id = id;
|
||||
atomic_set(&hw_blk->refcount, 0);
|
||||
|
||||
if (ops)
|
||||
hw_blk->ops = *ops;
|
||||
|
||||
mutex_lock(&dpu_hw_blk_lock);
|
||||
list_add(&hw_blk->list, &dpu_hw_blk_list);
|
||||
mutex_unlock(&dpu_hw_blk_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* dpu_hw_blk_destroy - destroy hw block object.
|
||||
* @hw_blk: pointer to hw block object
|
||||
* return: none
|
||||
*/
|
||||
void dpu_hw_blk_destroy(struct dpu_hw_blk *hw_blk)
|
||||
{
|
||||
if (!hw_blk) {
|
||||
pr_err("invalid parameters\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (atomic_read(&hw_blk->refcount))
|
||||
pr_err("hw_blk:%d.%d invalid refcount\n", hw_blk->type,
|
||||
hw_blk->id);
|
||||
|
||||
mutex_lock(&dpu_hw_blk_lock);
|
||||
list_del(&hw_blk->list);
|
||||
mutex_unlock(&dpu_hw_blk_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* dpu_hw_blk_get - get hw_blk from free pool
|
||||
* @hw_blk: if specified, increment reference count only
|
||||
* @type: if hw_blk is not specified, allocate the next available of this type
|
||||
* @id: if specified (>= 0), allocate the given instance of the above type
|
||||
* return: pointer to hw block object
|
||||
*/
|
||||
struct dpu_hw_blk *dpu_hw_blk_get(struct dpu_hw_blk *hw_blk, u32 type, int id)
|
||||
{
|
||||
struct dpu_hw_blk *curr;
|
||||
int rc, refcount;
|
||||
|
||||
if (!hw_blk) {
|
||||
mutex_lock(&dpu_hw_blk_lock);
|
||||
list_for_each_entry(curr, &dpu_hw_blk_list, list) {
|
||||
if ((curr->type != type) ||
|
||||
(id >= 0 && curr->id != id) ||
|
||||
(id < 0 &&
|
||||
atomic_read(&curr->refcount)))
|
||||
continue;
|
||||
|
||||
hw_blk = curr;
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&dpu_hw_blk_lock);
|
||||
}
|
||||
|
||||
if (!hw_blk) {
|
||||
pr_debug("no hw_blk:%d\n", type);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
refcount = atomic_inc_return(&hw_blk->refcount);
|
||||
|
||||
if (refcount == 1 && hw_blk->ops.start) {
|
||||
rc = hw_blk->ops.start(hw_blk);
|
||||
if (rc) {
|
||||
pr_err("failed to start hw_blk:%d rc:%d\n", type, rc);
|
||||
goto error_start;
|
||||
}
|
||||
}
|
||||
|
||||
pr_debug("hw_blk:%d.%d refcount:%d\n", hw_blk->type,
|
||||
hw_blk->id, refcount);
|
||||
return hw_blk;
|
||||
|
||||
error_start:
|
||||
dpu_hw_blk_put(hw_blk);
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* dpu_hw_blk_put - put hw_blk to free pool if decremented refcount is zero
|
||||
* @hw_blk: hw block to be freed
|
||||
* @free_blk: function to be called when reference count goes to zero
|
||||
*/
|
||||
void dpu_hw_blk_put(struct dpu_hw_blk *hw_blk)
|
||||
{
|
||||
if (!hw_blk) {
|
||||
pr_err("invalid parameters\n");
|
||||
return;
|
||||
}
|
||||
|
||||
pr_debug("hw_blk:%d.%d refcount:%d\n", hw_blk->type, hw_blk->id,
|
||||
atomic_read(&hw_blk->refcount));
|
||||
|
||||
if (!atomic_read(&hw_blk->refcount)) {
|
||||
pr_err("hw_blk:%d.%d invalid put\n", hw_blk->type, hw_blk->id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (atomic_dec_return(&hw_blk->refcount))
|
||||
return;
|
||||
|
||||
if (hw_blk->ops.stop)
|
||||
hw_blk->ops.stop(hw_blk);
|
||||
}
|
||||
53
drivers/gpu/drm/msm/disp/dpu1/dpu_hw_blk.h
Normal file
53
drivers/gpu/drm/msm/disp/dpu1/dpu_hw_blk.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _DPU_HW_BLK_H
|
||||
#define _DPU_HW_BLK_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/atomic.h>
|
||||
|
||||
struct dpu_hw_blk;
|
||||
|
||||
/**
|
||||
* struct dpu_hw_blk_ops - common hardware block operations
|
||||
* @start: start operation on first get
|
||||
* @stop: stop operation on last put
|
||||
*/
|
||||
struct dpu_hw_blk_ops {
|
||||
int (*start)(struct dpu_hw_blk *);
|
||||
void (*stop)(struct dpu_hw_blk *);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_hw_blk - definition of hardware block object
|
||||
* @list: list of hardware blocks
|
||||
* @type: hardware block type
|
||||
* @id: instance id
|
||||
* @refcount: reference/usage count
|
||||
*/
|
||||
struct dpu_hw_blk {
|
||||
struct list_head list;
|
||||
u32 type;
|
||||
int id;
|
||||
atomic_t refcount;
|
||||
struct dpu_hw_blk_ops ops;
|
||||
};
|
||||
|
||||
int dpu_hw_blk_init(struct dpu_hw_blk *hw_blk, u32 type, int id,
|
||||
struct dpu_hw_blk_ops *ops);
|
||||
void dpu_hw_blk_destroy(struct dpu_hw_blk *hw_blk);
|
||||
|
||||
struct dpu_hw_blk *dpu_hw_blk_get(struct dpu_hw_blk *hw_blk, u32 type, int id);
|
||||
void dpu_hw_blk_put(struct dpu_hw_blk *hw_blk);
|
||||
#endif /*_DPU_HW_BLK_H */
|
||||
511
drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c
Normal file
511
drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c
Normal file
|
|
@ -0,0 +1,511 @@
|
|||
/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include "dpu_hw_mdss.h"
|
||||
#include "dpu_hw_catalog.h"
|
||||
#include "dpu_hw_catalog_format.h"
|
||||
#include "dpu_kms.h"
|
||||
|
||||
#define VIG_SDM845_MASK \
|
||||
(BIT(DPU_SSPP_SRC) | BIT(DPU_SSPP_SCALER_QSEED3) | BIT(DPU_SSPP_QOS) |\
|
||||
BIT(DPU_SSPP_CSC_10BIT) | BIT(DPU_SSPP_CDP) | BIT(DPU_SSPP_QOS_8LVL) |\
|
||||
BIT(DPU_SSPP_TS_PREFILL) | BIT(DPU_SSPP_EXCL_RECT))
|
||||
|
||||
#define DMA_SDM845_MASK \
|
||||
(BIT(DPU_SSPP_SRC) | BIT(DPU_SSPP_QOS) | BIT(DPU_SSPP_QOS_8LVL) |\
|
||||
BIT(DPU_SSPP_TS_PREFILL) | BIT(DPU_SSPP_TS_PREFILL_REC1) |\
|
||||
BIT(DPU_SSPP_CDP) | BIT(DPU_SSPP_EXCL_RECT))
|
||||
|
||||
#define MIXER_SDM845_MASK \
|
||||
(BIT(DPU_MIXER_SOURCESPLIT) | BIT(DPU_DIM_LAYER))
|
||||
|
||||
#define PINGPONG_SDM845_MASK BIT(DPU_PINGPONG_DITHER)
|
||||
|
||||
#define PINGPONG_SDM845_SPLIT_MASK \
|
||||
(PINGPONG_SDM845_MASK | BIT(DPU_PINGPONG_TE2))
|
||||
|
||||
#define DEFAULT_PIXEL_RAM_SIZE (50 * 1024)
|
||||
#define DEFAULT_DPU_LINE_WIDTH 2048
|
||||
#define DEFAULT_DPU_OUTPUT_LINE_WIDTH 2560
|
||||
|
||||
#define MAX_HORZ_DECIMATION 4
|
||||
#define MAX_VERT_DECIMATION 4
|
||||
|
||||
#define MAX_UPSCALE_RATIO 20
|
||||
#define MAX_DOWNSCALE_RATIO 4
|
||||
#define SSPP_UNITY_SCALE 1
|
||||
|
||||
#define STRCAT(X, Y) (X Y)
|
||||
|
||||
/*************************************************************
|
||||
* DPU sub blocks config
|
||||
*************************************************************/
|
||||
/* DPU top level caps */
|
||||
static const struct dpu_caps sdm845_dpu_caps = {
|
||||
.max_mixer_width = DEFAULT_DPU_OUTPUT_LINE_WIDTH,
|
||||
.max_mixer_blendstages = 0xb,
|
||||
.qseed_type = DPU_SSPP_SCALER_QSEED3,
|
||||
.smart_dma_rev = DPU_SSPP_SMART_DMA_V2,
|
||||
.ubwc_version = DPU_HW_UBWC_VER_20,
|
||||
.has_src_split = true,
|
||||
.has_dim_layer = true,
|
||||
.has_idle_pc = true,
|
||||
};
|
||||
|
||||
static struct dpu_mdp_cfg sdm845_mdp[] = {
|
||||
{
|
||||
.name = "top_0", .id = MDP_TOP,
|
||||
.base = 0x0, .len = 0x45C,
|
||||
.features = 0,
|
||||
.highest_bank_bit = 0x2,
|
||||
.has_dest_scaler = true,
|
||||
.clk_ctrls[DPU_CLK_CTRL_VIG0] = {
|
||||
.reg_off = 0x2AC, .bit_off = 0},
|
||||
.clk_ctrls[DPU_CLK_CTRL_VIG1] = {
|
||||
.reg_off = 0x2B4, .bit_off = 0},
|
||||
.clk_ctrls[DPU_CLK_CTRL_VIG2] = {
|
||||
.reg_off = 0x2BC, .bit_off = 0},
|
||||
.clk_ctrls[DPU_CLK_CTRL_VIG3] = {
|
||||
.reg_off = 0x2C4, .bit_off = 0},
|
||||
.clk_ctrls[DPU_CLK_CTRL_DMA0] = {
|
||||
.reg_off = 0x2AC, .bit_off = 8},
|
||||
.clk_ctrls[DPU_CLK_CTRL_DMA1] = {
|
||||
.reg_off = 0x2B4, .bit_off = 8},
|
||||
.clk_ctrls[DPU_CLK_CTRL_CURSOR0] = {
|
||||
.reg_off = 0x2BC, .bit_off = 8},
|
||||
.clk_ctrls[DPU_CLK_CTRL_CURSOR1] = {
|
||||
.reg_off = 0x2C4, .bit_off = 8},
|
||||
},
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* CTL sub blocks config
|
||||
*************************************************************/
|
||||
static struct dpu_ctl_cfg sdm845_ctl[] = {
|
||||
{
|
||||
.name = "ctl_0", .id = CTL_0,
|
||||
.base = 0x1000, .len = 0xE4,
|
||||
.features = BIT(DPU_CTL_SPLIT_DISPLAY)
|
||||
},
|
||||
{
|
||||
.name = "ctl_1", .id = CTL_1,
|
||||
.base = 0x1200, .len = 0xE4,
|
||||
.features = BIT(DPU_CTL_SPLIT_DISPLAY)
|
||||
},
|
||||
{
|
||||
.name = "ctl_2", .id = CTL_2,
|
||||
.base = 0x1400, .len = 0xE4,
|
||||
.features = 0
|
||||
},
|
||||
{
|
||||
.name = "ctl_3", .id = CTL_3,
|
||||
.base = 0x1600, .len = 0xE4,
|
||||
.features = 0
|
||||
},
|
||||
{
|
||||
.name = "ctl_4", .id = CTL_4,
|
||||
.base = 0x1800, .len = 0xE4,
|
||||
.features = 0
|
||||
},
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* SSPP sub blocks config
|
||||
*************************************************************/
|
||||
|
||||
/* SSPP common configuration */
|
||||
static const struct dpu_sspp_blks_common sdm845_sspp_common = {
|
||||
.maxlinewidth = DEFAULT_DPU_OUTPUT_LINE_WIDTH,
|
||||
.pixel_ram_size = DEFAULT_PIXEL_RAM_SIZE,
|
||||
.maxhdeciexp = MAX_HORZ_DECIMATION,
|
||||
.maxvdeciexp = MAX_VERT_DECIMATION,
|
||||
};
|
||||
|
||||
#define _VIG_SBLK(num, sdma_pri) \
|
||||
{ \
|
||||
.common = &sdm845_sspp_common, \
|
||||
.maxdwnscale = MAX_DOWNSCALE_RATIO, \
|
||||
.maxupscale = MAX_UPSCALE_RATIO, \
|
||||
.smart_dma_priority = sdma_pri, \
|
||||
.src_blk = {.name = STRCAT("sspp_src_", num), \
|
||||
.id = DPU_SSPP_SRC, .base = 0x00, .len = 0x150,}, \
|
||||
.scaler_blk = {.name = STRCAT("sspp_scaler", num), \
|
||||
.id = DPU_SSPP_SCALER_QSEED3, \
|
||||
.base = 0xa00, .len = 0xa0,}, \
|
||||
.csc_blk = {.name = STRCAT("sspp_csc", num), \
|
||||
.id = DPU_SSPP_CSC_10BIT, \
|
||||
.base = 0x1a00, .len = 0x100,}, \
|
||||
.format_list = plane_formats_yuv, \
|
||||
.virt_format_list = plane_formats, \
|
||||
}
|
||||
|
||||
#define _DMA_SBLK(num, sdma_pri) \
|
||||
{ \
|
||||
.common = &sdm845_sspp_common, \
|
||||
.maxdwnscale = SSPP_UNITY_SCALE, \
|
||||
.maxupscale = SSPP_UNITY_SCALE, \
|
||||
.smart_dma_priority = sdma_pri, \
|
||||
.src_blk = {.name = STRCAT("sspp_src_", num), \
|
||||
.id = DPU_SSPP_SRC, .base = 0x00, .len = 0x150,}, \
|
||||
.format_list = plane_formats, \
|
||||
.virt_format_list = plane_formats, \
|
||||
}
|
||||
|
||||
static const struct dpu_sspp_sub_blks sdm845_vig_sblk_0 = _VIG_SBLK("0", 5);
|
||||
static const struct dpu_sspp_sub_blks sdm845_vig_sblk_1 = _VIG_SBLK("1", 6);
|
||||
static const struct dpu_sspp_sub_blks sdm845_vig_sblk_2 = _VIG_SBLK("2", 7);
|
||||
static const struct dpu_sspp_sub_blks sdm845_vig_sblk_3 = _VIG_SBLK("3", 8);
|
||||
|
||||
static const struct dpu_sspp_sub_blks sdm845_dma_sblk_0 = _DMA_SBLK("8", 1);
|
||||
static const struct dpu_sspp_sub_blks sdm845_dma_sblk_1 = _DMA_SBLK("9", 2);
|
||||
static const struct dpu_sspp_sub_blks sdm845_dma_sblk_2 = _DMA_SBLK("10", 3);
|
||||
static const struct dpu_sspp_sub_blks sdm845_dma_sblk_3 = _DMA_SBLK("11", 4);
|
||||
|
||||
#define SSPP_VIG_BLK(_name, _id, _base, _sblk, _xinid, _clkctrl) \
|
||||
{ \
|
||||
.name = _name, .id = _id, \
|
||||
.base = _base, .len = 0x1c8, \
|
||||
.features = VIG_SDM845_MASK, \
|
||||
.sblk = &_sblk, \
|
||||
.xin_id = _xinid, \
|
||||
.type = SSPP_TYPE_VIG, \
|
||||
.clk_ctrl = _clkctrl \
|
||||
}
|
||||
|
||||
#define SSPP_DMA_BLK(_name, _id, _base, _sblk, _xinid, _clkctrl) \
|
||||
{ \
|
||||
.name = _name, .id = _id, \
|
||||
.base = _base, .len = 0x1c8, \
|
||||
.features = DMA_SDM845_MASK, \
|
||||
.sblk = &_sblk, \
|
||||
.xin_id = _xinid, \
|
||||
.type = SSPP_TYPE_DMA, \
|
||||
.clk_ctrl = _clkctrl \
|
||||
}
|
||||
|
||||
static struct dpu_sspp_cfg sdm845_sspp[] = {
|
||||
SSPP_VIG_BLK("sspp_0", SSPP_VIG0, 0x4000,
|
||||
sdm845_vig_sblk_0, 0, DPU_CLK_CTRL_VIG0),
|
||||
SSPP_VIG_BLK("sspp_1", SSPP_VIG1, 0x6000,
|
||||
sdm845_vig_sblk_1, 4, DPU_CLK_CTRL_VIG1),
|
||||
SSPP_VIG_BLK("sspp_2", SSPP_VIG2, 0x8000,
|
||||
sdm845_vig_sblk_2, 8, DPU_CLK_CTRL_VIG2),
|
||||
SSPP_VIG_BLK("sspp_3", SSPP_VIG3, 0xa000,
|
||||
sdm845_vig_sblk_3, 12, DPU_CLK_CTRL_VIG3),
|
||||
SSPP_DMA_BLK("sspp_8", SSPP_DMA0, 0x24000,
|
||||
sdm845_dma_sblk_0, 1, DPU_CLK_CTRL_DMA0),
|
||||
SSPP_DMA_BLK("sspp_9", SSPP_DMA1, 0x26000,
|
||||
sdm845_dma_sblk_1, 5, DPU_CLK_CTRL_DMA1),
|
||||
SSPP_DMA_BLK("sspp_10", SSPP_DMA2, 0x28000,
|
||||
sdm845_dma_sblk_2, 9, DPU_CLK_CTRL_CURSOR0),
|
||||
SSPP_DMA_BLK("sspp_11", SSPP_DMA3, 0x2a000,
|
||||
sdm845_dma_sblk_3, 13, DPU_CLK_CTRL_CURSOR1),
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* MIXER sub blocks config
|
||||
*************************************************************/
|
||||
static const struct dpu_lm_sub_blks sdm845_lm_sblk = {
|
||||
.maxwidth = DEFAULT_DPU_OUTPUT_LINE_WIDTH,
|
||||
.maxblendstages = 11, /* excluding base layer */
|
||||
.blendstage_base = { /* offsets relative to mixer base */
|
||||
0x20, 0x38, 0x50, 0x68, 0x80, 0x98,
|
||||
0xb0, 0xc8, 0xe0, 0xf8, 0x110
|
||||
},
|
||||
};
|
||||
|
||||
#define LM_BLK(_name, _id, _base, _ds, _pp, _lmpair) \
|
||||
{ \
|
||||
.name = _name, .id = _id, \
|
||||
.base = _base, .len = 0x320, \
|
||||
.features = MIXER_SDM845_MASK, \
|
||||
.sblk = &sdm845_lm_sblk, \
|
||||
.ds = _ds, \
|
||||
.pingpong = _pp, \
|
||||
.lm_pair_mask = (1 << _lmpair) \
|
||||
}
|
||||
|
||||
static struct dpu_lm_cfg sdm845_lm[] = {
|
||||
LM_BLK("lm_0", LM_0, 0x44000, DS_0, PINGPONG_0, LM_1),
|
||||
LM_BLK("lm_1", LM_1, 0x45000, DS_1, PINGPONG_1, LM_0),
|
||||
LM_BLK("lm_2", LM_2, 0x46000, DS_MAX, PINGPONG_2, LM_5),
|
||||
LM_BLK("lm_3", LM_3, 0x0, DS_MAX, PINGPONG_MAX, 0),
|
||||
LM_BLK("lm_4", LM_4, 0x0, DS_MAX, PINGPONG_MAX, 0),
|
||||
LM_BLK("lm_5", LM_5, 0x49000, DS_MAX, PINGPONG_3, LM_2),
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* DS sub blocks config
|
||||
*************************************************************/
|
||||
static const struct dpu_ds_top_cfg sdm845_ds_top = {
|
||||
.name = "ds_top_0", .id = DS_TOP,
|
||||
.base = 0x60000, .len = 0xc,
|
||||
.maxinputwidth = DEFAULT_DPU_LINE_WIDTH,
|
||||
.maxoutputwidth = DEFAULT_DPU_OUTPUT_LINE_WIDTH,
|
||||
.maxupscale = MAX_UPSCALE_RATIO,
|
||||
};
|
||||
|
||||
#define DS_BLK(_name, _id, _base) \
|
||||
{\
|
||||
.name = _name, .id = _id, \
|
||||
.base = _base, .len = 0x800, \
|
||||
.features = DPU_SSPP_SCALER_QSEED3, \
|
||||
.top = &sdm845_ds_top \
|
||||
}
|
||||
|
||||
static struct dpu_ds_cfg sdm845_ds[] = {
|
||||
DS_BLK("ds_0", DS_0, 0x800),
|
||||
DS_BLK("ds_1", DS_1, 0x1000),
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* PINGPONG sub blocks config
|
||||
*************************************************************/
|
||||
static const struct dpu_pingpong_sub_blks sdm845_pp_sblk_te = {
|
||||
.te2 = {.id = DPU_PINGPONG_TE2, .base = 0x2000, .len = 0x0,
|
||||
.version = 0x1},
|
||||
.dither = {.id = DPU_PINGPONG_DITHER, .base = 0x30e0,
|
||||
.len = 0x20, .version = 0x10000},
|
||||
};
|
||||
|
||||
static const struct dpu_pingpong_sub_blks sdm845_pp_sblk = {
|
||||
.dither = {.id = DPU_PINGPONG_DITHER, .base = 0x30e0,
|
||||
.len = 0x20, .version = 0x10000},
|
||||
};
|
||||
|
||||
#define PP_BLK_TE(_name, _id, _base) \
|
||||
{\
|
||||
.name = _name, .id = _id, \
|
||||
.base = _base, .len = 0xd4, \
|
||||
.features = PINGPONG_SDM845_SPLIT_MASK, \
|
||||
.sblk = &sdm845_pp_sblk_te \
|
||||
}
|
||||
#define PP_BLK(_name, _id, _base) \
|
||||
{\
|
||||
.name = _name, .id = _id, \
|
||||
.base = _base, .len = 0xd4, \
|
||||
.features = PINGPONG_SDM845_MASK, \
|
||||
.sblk = &sdm845_pp_sblk \
|
||||
}
|
||||
|
||||
static struct dpu_pingpong_cfg sdm845_pp[] = {
|
||||
PP_BLK_TE("pingpong_0", PINGPONG_0, 0x70000),
|
||||
PP_BLK_TE("pingpong_1", PINGPONG_1, 0x70800),
|
||||
PP_BLK("pingpong_2", PINGPONG_2, 0x71000),
|
||||
PP_BLK("pingpong_3", PINGPONG_3, 0x71800),
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* INTF sub blocks config
|
||||
*************************************************************/
|
||||
#define INTF_BLK(_name, _id, _base, _type, _ctrl_id) \
|
||||
{\
|
||||
.name = _name, .id = _id, \
|
||||
.base = _base, .len = 0x280, \
|
||||
.type = _type, \
|
||||
.controller_id = _ctrl_id, \
|
||||
.prog_fetch_lines_worst_case = 24 \
|
||||
}
|
||||
|
||||
static struct dpu_intf_cfg sdm845_intf[] = {
|
||||
INTF_BLK("intf_0", INTF_0, 0x6A000, INTF_DP, 0),
|
||||
INTF_BLK("intf_1", INTF_1, 0x6A800, INTF_DSI, 0),
|
||||
INTF_BLK("intf_2", INTF_2, 0x6B000, INTF_DSI, 1),
|
||||
INTF_BLK("intf_3", INTF_3, 0x6B800, INTF_DP, 1),
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* CDM sub blocks config
|
||||
*************************************************************/
|
||||
static struct dpu_cdm_cfg sdm845_cdm[] = {
|
||||
{
|
||||
.name = "cdm_0", .id = CDM_0,
|
||||
.base = 0x79200, .len = 0x224,
|
||||
.features = 0,
|
||||
.intf_connect = BIT(INTF_3),
|
||||
},
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* VBIF sub blocks config
|
||||
*************************************************************/
|
||||
/* VBIF QOS remap */
|
||||
static u32 sdm845_rt_pri_lvl[] = {3, 3, 4, 4, 5, 5, 6, 6};
|
||||
static u32 sdm845_nrt_pri_lvl[] = {3, 3, 3, 3, 3, 3, 3, 3};
|
||||
|
||||
static struct dpu_vbif_cfg sdm845_vbif[] = {
|
||||
{
|
||||
.name = "vbif_0", .id = VBIF_0,
|
||||
.base = 0, .len = 0x1040,
|
||||
.features = BIT(DPU_VBIF_QOS_REMAP),
|
||||
.xin_halt_timeout = 0x4000,
|
||||
.qos_rt_tbl = {
|
||||
.npriority_lvl = ARRAY_SIZE(sdm845_rt_pri_lvl),
|
||||
.priority_lvl = sdm845_rt_pri_lvl,
|
||||
},
|
||||
.qos_nrt_tbl = {
|
||||
.npriority_lvl = ARRAY_SIZE(sdm845_nrt_pri_lvl),
|
||||
.priority_lvl = sdm845_nrt_pri_lvl,
|
||||
},
|
||||
.memtype_count = 14,
|
||||
.memtype = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
|
||||
},
|
||||
};
|
||||
|
||||
static struct dpu_reg_dma_cfg sdm845_regdma = {
|
||||
.base = 0x0, .version = 0x1, .trigger_sel_off = 0x119c
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* PERF data config
|
||||
*************************************************************/
|
||||
|
||||
/* SSPP QOS LUTs */
|
||||
static struct dpu_qos_lut_entry sdm845_qos_linear[] = {
|
||||
{.fl = 4, .lut = 0x357},
|
||||
{.fl = 5, .lut = 0x3357},
|
||||
{.fl = 6, .lut = 0x23357},
|
||||
{.fl = 7, .lut = 0x223357},
|
||||
{.fl = 8, .lut = 0x2223357},
|
||||
{.fl = 9, .lut = 0x22223357},
|
||||
{.fl = 10, .lut = 0x222223357},
|
||||
{.fl = 11, .lut = 0x2222223357},
|
||||
{.fl = 12, .lut = 0x22222223357},
|
||||
{.fl = 13, .lut = 0x222222223357},
|
||||
{.fl = 14, .lut = 0x1222222223357},
|
||||
{.fl = 0, .lut = 0x11222222223357}
|
||||
};
|
||||
|
||||
static struct dpu_qos_lut_entry sdm845_qos_macrotile[] = {
|
||||
{.fl = 10, .lut = 0x344556677},
|
||||
{.fl = 11, .lut = 0x3344556677},
|
||||
{.fl = 12, .lut = 0x23344556677},
|
||||
{.fl = 13, .lut = 0x223344556677},
|
||||
{.fl = 14, .lut = 0x1223344556677},
|
||||
{.fl = 0, .lut = 0x112233344556677},
|
||||
};
|
||||
|
||||
static struct dpu_qos_lut_entry sdm845_qos_nrt[] = {
|
||||
{.fl = 0, .lut = 0x0},
|
||||
};
|
||||
|
||||
static struct dpu_perf_cfg sdm845_perf_data = {
|
||||
.max_bw_low = 6800000,
|
||||
.max_bw_high = 6800000,
|
||||
.min_core_ib = 2400000,
|
||||
.min_llcc_ib = 800000,
|
||||
.min_dram_ib = 800000,
|
||||
.core_ib_ff = "6.0",
|
||||
.core_clk_ff = "1.0",
|
||||
.comp_ratio_rt =
|
||||
"NV12/5/1/1.23 AB24/5/1/1.23 XB24/5/1/1.23",
|
||||
.comp_ratio_nrt =
|
||||
"NV12/5/1/1.25 AB24/5/1/1.25 XB24/5/1/1.25",
|
||||
.undersized_prefill_lines = 2,
|
||||
.xtra_prefill_lines = 2,
|
||||
.dest_scale_prefill_lines = 3,
|
||||
.macrotile_prefill_lines = 4,
|
||||
.yuv_nv12_prefill_lines = 8,
|
||||
.linear_prefill_lines = 1,
|
||||
.downscaling_prefill_lines = 1,
|
||||
.amortizable_threshold = 25,
|
||||
.min_prefill_lines = 24,
|
||||
.danger_lut_tbl = {0xf, 0xffff, 0x0},
|
||||
.qos_lut_tbl = {
|
||||
{.nentry = ARRAY_SIZE(sdm845_qos_linear),
|
||||
.entries = sdm845_qos_linear
|
||||
},
|
||||
{.nentry = ARRAY_SIZE(sdm845_qos_macrotile),
|
||||
.entries = sdm845_qos_macrotile
|
||||
},
|
||||
{.nentry = ARRAY_SIZE(sdm845_qos_nrt),
|
||||
.entries = sdm845_qos_nrt
|
||||
},
|
||||
},
|
||||
.cdp_cfg = {
|
||||
{.rd_enable = 1, .wr_enable = 1},
|
||||
{.rd_enable = 1, .wr_enable = 0}
|
||||
},
|
||||
};
|
||||
|
||||
/*************************************************************
|
||||
* Hardware catalog init
|
||||
*************************************************************/
|
||||
|
||||
/*
|
||||
* sdm845_cfg_init(): populate sdm845 dpu sub-blocks reg offsets
|
||||
* and instance counts.
|
||||
*/
|
||||
void sdm845_cfg_init(struct dpu_mdss_cfg *dpu_cfg)
|
||||
{
|
||||
*dpu_cfg = (struct dpu_mdss_cfg){
|
||||
.caps = &sdm845_dpu_caps,
|
||||
.mdp_count = ARRAY_SIZE(sdm845_mdp),
|
||||
.mdp = sdm845_mdp,
|
||||
.ctl_count = ARRAY_SIZE(sdm845_ctl),
|
||||
.ctl = sdm845_ctl,
|
||||
.sspp_count = ARRAY_SIZE(sdm845_sspp),
|
||||
.sspp = sdm845_sspp,
|
||||
.mixer_count = ARRAY_SIZE(sdm845_lm),
|
||||
.mixer = sdm845_lm,
|
||||
.ds_count = ARRAY_SIZE(sdm845_ds),
|
||||
.ds = sdm845_ds,
|
||||
.pingpong_count = ARRAY_SIZE(sdm845_pp),
|
||||
.pingpong = sdm845_pp,
|
||||
.cdm_count = ARRAY_SIZE(sdm845_cdm),
|
||||
.cdm = sdm845_cdm,
|
||||
.intf_count = ARRAY_SIZE(sdm845_intf),
|
||||
.intf = sdm845_intf,
|
||||
.vbif_count = ARRAY_SIZE(sdm845_vbif),
|
||||
.vbif = sdm845_vbif,
|
||||
.reg_dma_count = 1,
|
||||
.dma_cfg = sdm845_regdma,
|
||||
.perf = sdm845_perf_data,
|
||||
};
|
||||
}
|
||||
|
||||
static struct dpu_mdss_hw_cfg_handler cfg_handler[] = {
|
||||
{ .hw_rev = DPU_HW_VER_400, .cfg_init = sdm845_cfg_init},
|
||||
{ .hw_rev = DPU_HW_VER_401, .cfg_init = sdm845_cfg_init},
|
||||
};
|
||||
|
||||
void dpu_hw_catalog_deinit(struct dpu_mdss_cfg *dpu_cfg)
|
||||
{
|
||||
kfree(dpu_cfg);
|
||||
}
|
||||
|
||||
struct dpu_mdss_cfg *dpu_hw_catalog_init(u32 hw_rev)
|
||||
{
|
||||
int i;
|
||||
struct dpu_mdss_cfg *dpu_cfg;
|
||||
|
||||
dpu_cfg = kzalloc(sizeof(*dpu_cfg), GFP_KERNEL);
|
||||
if (!dpu_cfg)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(cfg_handler); i++) {
|
||||
if (cfg_handler[i].hw_rev == hw_rev) {
|
||||
cfg_handler[i].cfg_init(dpu_cfg);
|
||||
dpu_cfg->hwversion = hw_rev;
|
||||
return dpu_cfg;
|
||||
}
|
||||
}
|
||||
|
||||
DPU_ERROR("unsupported chipset id:%X\n", hw_rev);
|
||||
dpu_hw_catalog_deinit(dpu_cfg);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
804
drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
Normal file
804
drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
Normal file
|
|
@ -0,0 +1,804 @@
|
|||
/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _DPU_HW_CATALOG_H
|
||||
#define _DPU_HW_CATALOG_H
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/err.h>
|
||||
#include <drm/drmP.h>
|
||||
|
||||
/**
|
||||
* Max hardware block count: For ex: max 12 SSPP pipes or
|
||||
* 5 ctl paths. In all cases, it can have max 12 hardware blocks
|
||||
* based on current design
|
||||
*/
|
||||
#define MAX_BLOCKS 12
|
||||
|
||||
#define DPU_HW_VER(MAJOR, MINOR, STEP) (((MAJOR & 0xF) << 28) |\
|
||||
((MINOR & 0xFFF) << 16) |\
|
||||
(STEP & 0xFFFF))
|
||||
|
||||
#define DPU_HW_MAJOR(rev) ((rev) >> 28)
|
||||
#define DPU_HW_MINOR(rev) (((rev) >> 16) & 0xFFF)
|
||||
#define DPU_HW_STEP(rev) ((rev) & 0xFFFF)
|
||||
#define DPU_HW_MAJOR_MINOR(rev) ((rev) >> 16)
|
||||
|
||||
#define IS_DPU_MAJOR_MINOR_SAME(rev1, rev2) \
|
||||
(DPU_HW_MAJOR_MINOR((rev1)) == DPU_HW_MAJOR_MINOR((rev2)))
|
||||
|
||||
#define DPU_HW_VER_170 DPU_HW_VER(1, 7, 0) /* 8996 v1.0 */
|
||||
#define DPU_HW_VER_171 DPU_HW_VER(1, 7, 1) /* 8996 v2.0 */
|
||||
#define DPU_HW_VER_172 DPU_HW_VER(1, 7, 2) /* 8996 v3.0 */
|
||||
#define DPU_HW_VER_300 DPU_HW_VER(3, 0, 0) /* 8998 v1.0 */
|
||||
#define DPU_HW_VER_301 DPU_HW_VER(3, 0, 1) /* 8998 v1.1 */
|
||||
#define DPU_HW_VER_400 DPU_HW_VER(4, 0, 0) /* sdm845 v1.0 */
|
||||
#define DPU_HW_VER_401 DPU_HW_VER(4, 0, 1) /* sdm845 v2.0 */
|
||||
#define DPU_HW_VER_410 DPU_HW_VER(4, 1, 0) /* sdm670 v1.0 */
|
||||
#define DPU_HW_VER_500 DPU_HW_VER(5, 0, 0) /* sdm855 v1.0 */
|
||||
|
||||
|
||||
#define IS_MSM8996_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_170)
|
||||
#define IS_MSM8998_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_300)
|
||||
#define IS_SDM845_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_400)
|
||||
#define IS_SDM670_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_410)
|
||||
#define IS_SDM855_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_500)
|
||||
|
||||
|
||||
#define DPU_HW_BLK_NAME_LEN 16
|
||||
|
||||
#define MAX_IMG_WIDTH 0x3fff
|
||||
#define MAX_IMG_HEIGHT 0x3fff
|
||||
|
||||
#define CRTC_DUAL_MIXERS 2
|
||||
|
||||
#define MAX_XIN_COUNT 16
|
||||
|
||||
/**
|
||||
* Supported UBWC feature versions
|
||||
*/
|
||||
enum {
|
||||
DPU_HW_UBWC_VER_10 = 0x100,
|
||||
DPU_HW_UBWC_VER_20 = 0x200,
|
||||
DPU_HW_UBWC_VER_30 = 0x300,
|
||||
};
|
||||
|
||||
#define IS_UBWC_20_SUPPORTED(rev) ((rev) >= DPU_HW_UBWC_VER_20)
|
||||
|
||||
/**
|
||||
* MDP TOP BLOCK features
|
||||
* @DPU_MDP_PANIC_PER_PIPE Panic configuration needs to be be done per pipe
|
||||
* @DPU_MDP_10BIT_SUPPORT, Chipset supports 10 bit pixel formats
|
||||
* @DPU_MDP_BWC, MDSS HW supports Bandwidth compression.
|
||||
* @DPU_MDP_UBWC_1_0, This chipsets supports Universal Bandwidth
|
||||
* compression initial revision
|
||||
* @DPU_MDP_UBWC_1_5, Universal Bandwidth compression version 1.5
|
||||
* @DPU_MDP_MAX Maximum value
|
||||
|
||||
*/
|
||||
enum {
|
||||
DPU_MDP_PANIC_PER_PIPE = 0x1,
|
||||
DPU_MDP_10BIT_SUPPORT,
|
||||
DPU_MDP_BWC,
|
||||
DPU_MDP_UBWC_1_0,
|
||||
DPU_MDP_UBWC_1_5,
|
||||
DPU_MDP_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* SSPP sub-blocks/features
|
||||
* @DPU_SSPP_SRC Src and fetch part of the pipes,
|
||||
* @DPU_SSPP_SCALER_QSEED2, QSEED2 algorithm support
|
||||
* @DPU_SSPP_SCALER_QSEED3, QSEED3 alogorithm support
|
||||
* @DPU_SSPP_SCALER_RGB, RGB Scaler, supported by RGB pipes
|
||||
* @DPU_SSPP_CSC, Support of Color space converion
|
||||
* @DPU_SSPP_CSC_10BIT, Support of 10-bit Color space conversion
|
||||
* @DPU_SSPP_CURSOR, SSPP can be used as a cursor layer
|
||||
* @DPU_SSPP_QOS, SSPP support QoS control, danger/safe/creq
|
||||
* @DPU_SSPP_QOS_8LVL, SSPP support 8-level QoS control
|
||||
* @DPU_SSPP_EXCL_RECT, SSPP supports exclusion rect
|
||||
* @DPU_SSPP_SMART_DMA_V1, SmartDMA 1.0 support
|
||||
* @DPU_SSPP_SMART_DMA_V2, SmartDMA 2.0 support
|
||||
* @DPU_SSPP_TS_PREFILL Supports prefill with traffic shaper
|
||||
* @DPU_SSPP_TS_PREFILL_REC1 Supports prefill with traffic shaper multirec
|
||||
* @DPU_SSPP_CDP Supports client driven prefetch
|
||||
* @DPU_SSPP_MAX maximum value
|
||||
*/
|
||||
enum {
|
||||
DPU_SSPP_SRC = 0x1,
|
||||
DPU_SSPP_SCALER_QSEED2,
|
||||
DPU_SSPP_SCALER_QSEED3,
|
||||
DPU_SSPP_SCALER_RGB,
|
||||
DPU_SSPP_CSC,
|
||||
DPU_SSPP_CSC_10BIT,
|
||||
DPU_SSPP_CURSOR,
|
||||
DPU_SSPP_QOS,
|
||||
DPU_SSPP_QOS_8LVL,
|
||||
DPU_SSPP_EXCL_RECT,
|
||||
DPU_SSPP_SMART_DMA_V1,
|
||||
DPU_SSPP_SMART_DMA_V2,
|
||||
DPU_SSPP_TS_PREFILL,
|
||||
DPU_SSPP_TS_PREFILL_REC1,
|
||||
DPU_SSPP_CDP,
|
||||
DPU_SSPP_MAX
|
||||
};
|
||||
|
||||
/*
|
||||
* MIXER sub-blocks/features
|
||||
* @DPU_MIXER_LAYER Layer mixer layer blend configuration,
|
||||
* @DPU_MIXER_SOURCESPLIT Layer mixer supports source-split configuration
|
||||
* @DPU_MIXER_GC Gamma correction block
|
||||
* @DPU_DIM_LAYER Layer mixer supports dim layer
|
||||
* @DPU_MIXER_MAX maximum value
|
||||
*/
|
||||
enum {
|
||||
DPU_MIXER_LAYER = 0x1,
|
||||
DPU_MIXER_SOURCESPLIT,
|
||||
DPU_MIXER_GC,
|
||||
DPU_DIM_LAYER,
|
||||
DPU_MIXER_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* PINGPONG sub-blocks
|
||||
* @DPU_PINGPONG_TE Tear check block
|
||||
* @DPU_PINGPONG_TE2 Additional tear check block for split pipes
|
||||
* @DPU_PINGPONG_SPLIT PP block supports split fifo
|
||||
* @DPU_PINGPONG_SLAVE PP block is a suitable slave for split fifo
|
||||
* @DPU_PINGPONG_DITHER, Dither blocks
|
||||
* @DPU_PINGPONG_MAX
|
||||
*/
|
||||
enum {
|
||||
DPU_PINGPONG_TE = 0x1,
|
||||
DPU_PINGPONG_TE2,
|
||||
DPU_PINGPONG_SPLIT,
|
||||
DPU_PINGPONG_SLAVE,
|
||||
DPU_PINGPONG_DITHER,
|
||||
DPU_PINGPONG_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* CTL sub-blocks
|
||||
* @DPU_CTL_SPLIT_DISPLAY CTL supports video mode split display
|
||||
* @DPU_CTL_MAX
|
||||
*/
|
||||
enum {
|
||||
DPU_CTL_SPLIT_DISPLAY = 0x1,
|
||||
DPU_CTL_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* VBIF sub-blocks and features
|
||||
* @DPU_VBIF_QOS_OTLIM VBIF supports OT Limit
|
||||
* @DPU_VBIF_QOS_REMAP VBIF supports QoS priority remap
|
||||
* @DPU_VBIF_MAX maximum value
|
||||
*/
|
||||
enum {
|
||||
DPU_VBIF_QOS_OTLIM = 0x1,
|
||||
DPU_VBIF_QOS_REMAP,
|
||||
DPU_VBIF_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* MACRO DPU_HW_BLK_INFO - information of HW blocks inside DPU
|
||||
* @name: string name for debug purposes
|
||||
* @id: enum identifying this block
|
||||
* @base: register base offset to mdss
|
||||
* @len: length of hardware block
|
||||
* @features bit mask identifying sub-blocks/features
|
||||
*/
|
||||
#define DPU_HW_BLK_INFO \
|
||||
char name[DPU_HW_BLK_NAME_LEN]; \
|
||||
u32 id; \
|
||||
u32 base; \
|
||||
u32 len; \
|
||||
unsigned long features
|
||||
|
||||
/**
|
||||
* MACRO DPU_HW_SUBBLK_INFO - information of HW sub-block inside DPU
|
||||
* @name: string name for debug purposes
|
||||
* @id: enum identifying this sub-block
|
||||
* @base: offset of this sub-block relative to the block
|
||||
* offset
|
||||
* @len register block length of this sub-block
|
||||
*/
|
||||
#define DPU_HW_SUBBLK_INFO \
|
||||
char name[DPU_HW_BLK_NAME_LEN]; \
|
||||
u32 id; \
|
||||
u32 base; \
|
||||
u32 len
|
||||
|
||||
/**
|
||||
* struct dpu_src_blk: SSPP part of the source pipes
|
||||
* @info: HW register and features supported by this sub-blk
|
||||
*/
|
||||
struct dpu_src_blk {
|
||||
DPU_HW_SUBBLK_INFO;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_scaler_blk: Scaler information
|
||||
* @info: HW register and features supported by this sub-blk
|
||||
* @version: qseed block revision
|
||||
*/
|
||||
struct dpu_scaler_blk {
|
||||
DPU_HW_SUBBLK_INFO;
|
||||
u32 version;
|
||||
};
|
||||
|
||||
struct dpu_csc_blk {
|
||||
DPU_HW_SUBBLK_INFO;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_pp_blk : Pixel processing sub-blk information
|
||||
* @info: HW register and features supported by this sub-blk
|
||||
* @version: HW Algorithm version
|
||||
*/
|
||||
struct dpu_pp_blk {
|
||||
DPU_HW_SUBBLK_INFO;
|
||||
u32 version;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_format_extended - define dpu specific pixel format+modifier
|
||||
* @fourcc_format: Base FOURCC pixel format code
|
||||
* @modifier: 64-bit drm format modifier, same modifier must be applied to all
|
||||
* framebuffer planes
|
||||
*/
|
||||
struct dpu_format_extended {
|
||||
uint32_t fourcc_format;
|
||||
uint64_t modifier;
|
||||
};
|
||||
|
||||
/**
|
||||
* enum dpu_qos_lut_usage - define QoS LUT use cases
|
||||
*/
|
||||
enum dpu_qos_lut_usage {
|
||||
DPU_QOS_LUT_USAGE_LINEAR,
|
||||
DPU_QOS_LUT_USAGE_MACROTILE,
|
||||
DPU_QOS_LUT_USAGE_NRT,
|
||||
DPU_QOS_LUT_USAGE_MAX,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_qos_lut_entry - define QoS LUT table entry
|
||||
* @fl: fill level, or zero on last entry to indicate default lut
|
||||
* @lut: lut to use if equal to or less than fill level
|
||||
*/
|
||||
struct dpu_qos_lut_entry {
|
||||
u32 fl;
|
||||
u64 lut;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_qos_lut_tbl - define QoS LUT table
|
||||
* @nentry: number of entry in this table
|
||||
* @entries: Pointer to table entries
|
||||
*/
|
||||
struct dpu_qos_lut_tbl {
|
||||
u32 nentry;
|
||||
struct dpu_qos_lut_entry *entries;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_caps - define DPU capabilities
|
||||
* @max_mixer_width max layer mixer line width support.
|
||||
* @max_mixer_blendstages max layer mixer blend stages or
|
||||
* supported z order
|
||||
* @qseed_type qseed2 or qseed3 support.
|
||||
* @smart_dma_rev Supported version of SmartDMA feature.
|
||||
* @ubwc_version UBWC feature version (0x0 for not supported)
|
||||
* @has_src_split source split feature status
|
||||
* @has_dim_layer dim layer feature status
|
||||
* @has_idle_pc indicate if idle power collapse feature is supported
|
||||
*/
|
||||
struct dpu_caps {
|
||||
u32 max_mixer_width;
|
||||
u32 max_mixer_blendstages;
|
||||
u32 qseed_type;
|
||||
u32 smart_dma_rev;
|
||||
u32 ubwc_version;
|
||||
bool has_src_split;
|
||||
bool has_dim_layer;
|
||||
bool has_idle_pc;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_sspp_blks_common : SSPP sub-blocks common configuration
|
||||
* @maxwidth: max pixelwidth supported by this pipe
|
||||
* @pixel_ram_size: size of latency hiding and de-tiling buffer in bytes
|
||||
* @maxhdeciexp: max horizontal decimation supported by this pipe
|
||||
* (max is 2^value)
|
||||
* @maxvdeciexp: max vertical decimation supported by this pipe
|
||||
* (max is 2^value)
|
||||
*/
|
||||
struct dpu_sspp_blks_common {
|
||||
u32 maxlinewidth;
|
||||
u32 pixel_ram_size;
|
||||
u32 maxhdeciexp;
|
||||
u32 maxvdeciexp;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_sspp_sub_blks : SSPP sub-blocks
|
||||
* common: Pointer to common configurations shared by sub blocks
|
||||
* @creq_vblank: creq priority during vertical blanking
|
||||
* @danger_vblank: danger priority during vertical blanking
|
||||
* @maxdwnscale: max downscale ratio supported(without DECIMATION)
|
||||
* @maxupscale: maxupscale ratio supported
|
||||
* @smart_dma_priority: hw priority of rect1 of multirect pipe
|
||||
* @max_per_pipe_bw: maximum allowable bandwidth of this pipe in kBps
|
||||
* @src_blk:
|
||||
* @scaler_blk:
|
||||
* @csc_blk:
|
||||
* @hsic:
|
||||
* @memcolor:
|
||||
* @pcc_blk:
|
||||
* @igc_blk:
|
||||
* @format_list: Pointer to list of supported formats
|
||||
* @virt_format_list: Pointer to list of supported formats for virtual planes
|
||||
*/
|
||||
struct dpu_sspp_sub_blks {
|
||||
const struct dpu_sspp_blks_common *common;
|
||||
u32 creq_vblank;
|
||||
u32 danger_vblank;
|
||||
u32 maxdwnscale;
|
||||
u32 maxupscale;
|
||||
u32 smart_dma_priority;
|
||||
u32 max_per_pipe_bw;
|
||||
struct dpu_src_blk src_blk;
|
||||
struct dpu_scaler_blk scaler_blk;
|
||||
struct dpu_pp_blk csc_blk;
|
||||
struct dpu_pp_blk hsic_blk;
|
||||
struct dpu_pp_blk memcolor_blk;
|
||||
struct dpu_pp_blk pcc_blk;
|
||||
struct dpu_pp_blk igc_blk;
|
||||
|
||||
const struct dpu_format_extended *format_list;
|
||||
const struct dpu_format_extended *virt_format_list;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_lm_sub_blks: information of mixer block
|
||||
* @maxwidth: Max pixel width supported by this mixer
|
||||
* @maxblendstages: Max number of blend-stages supported
|
||||
* @blendstage_base: Blend-stage register base offset
|
||||
* @gc: gamma correction block
|
||||
*/
|
||||
struct dpu_lm_sub_blks {
|
||||
u32 maxwidth;
|
||||
u32 maxblendstages;
|
||||
u32 blendstage_base[MAX_BLOCKS];
|
||||
struct dpu_pp_blk gc;
|
||||
};
|
||||
|
||||
struct dpu_pingpong_sub_blks {
|
||||
struct dpu_pp_blk te;
|
||||
struct dpu_pp_blk te2;
|
||||
struct dpu_pp_blk dither;
|
||||
};
|
||||
|
||||
/**
|
||||
* dpu_clk_ctrl_type - Defines top level clock control signals
|
||||
*/
|
||||
enum dpu_clk_ctrl_type {
|
||||
DPU_CLK_CTRL_NONE,
|
||||
DPU_CLK_CTRL_VIG0,
|
||||
DPU_CLK_CTRL_VIG1,
|
||||
DPU_CLK_CTRL_VIG2,
|
||||
DPU_CLK_CTRL_VIG3,
|
||||
DPU_CLK_CTRL_VIG4,
|
||||
DPU_CLK_CTRL_RGB0,
|
||||
DPU_CLK_CTRL_RGB1,
|
||||
DPU_CLK_CTRL_RGB2,
|
||||
DPU_CLK_CTRL_RGB3,
|
||||
DPU_CLK_CTRL_DMA0,
|
||||
DPU_CLK_CTRL_DMA1,
|
||||
DPU_CLK_CTRL_CURSOR0,
|
||||
DPU_CLK_CTRL_CURSOR1,
|
||||
DPU_CLK_CTRL_INLINE_ROT0_SSPP,
|
||||
DPU_CLK_CTRL_MAX,
|
||||
};
|
||||
|
||||
/* struct dpu_clk_ctrl_reg : Clock control register
|
||||
* @reg_off: register offset
|
||||
* @bit_off: bit offset
|
||||
*/
|
||||
struct dpu_clk_ctrl_reg {
|
||||
u32 reg_off;
|
||||
u32 bit_off;
|
||||
};
|
||||
|
||||
/* struct dpu_mdp_cfg : MDP TOP-BLK instance info
|
||||
* @id: index identifying this block
|
||||
* @base: register base offset to mdss
|
||||
* @features bit mask identifying sub-blocks/features
|
||||
* @highest_bank_bit: UBWC parameter
|
||||
* @ubwc_static: ubwc static configuration
|
||||
* @ubwc_swizzle: ubwc default swizzle setting
|
||||
* @has_dest_scaler: indicates support of destination scaler
|
||||
* @clk_ctrls clock control register definition
|
||||
*/
|
||||
struct dpu_mdp_cfg {
|
||||
DPU_HW_BLK_INFO;
|
||||
u32 highest_bank_bit;
|
||||
u32 ubwc_static;
|
||||
u32 ubwc_swizzle;
|
||||
bool has_dest_scaler;
|
||||
struct dpu_clk_ctrl_reg clk_ctrls[DPU_CLK_CTRL_MAX];
|
||||
};
|
||||
|
||||
/* struct dpu_mdp_cfg : MDP TOP-BLK instance info
|
||||
* @id: index identifying this block
|
||||
* @base: register base offset to mdss
|
||||
* @features bit mask identifying sub-blocks/features
|
||||
*/
|
||||
struct dpu_ctl_cfg {
|
||||
DPU_HW_BLK_INFO;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_sspp_cfg - information of source pipes
|
||||
* @id: index identifying this block
|
||||
* @base register offset of this block
|
||||
* @features bit mask identifying sub-blocks/features
|
||||
* @sblk: SSPP sub-blocks information
|
||||
* @xin_id: bus client identifier
|
||||
* @clk_ctrl clock control identifier
|
||||
* @type sspp type identifier
|
||||
*/
|
||||
struct dpu_sspp_cfg {
|
||||
DPU_HW_BLK_INFO;
|
||||
const struct dpu_sspp_sub_blks *sblk;
|
||||
u32 xin_id;
|
||||
enum dpu_clk_ctrl_type clk_ctrl;
|
||||
u32 type;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_lm_cfg - information of layer mixer blocks
|
||||
* @id: index identifying this block
|
||||
* @base register offset of this block
|
||||
* @features bit mask identifying sub-blocks/features
|
||||
* @sblk: LM Sub-blocks information
|
||||
* @pingpong: ID of connected PingPong, PINGPONG_MAX if unsupported
|
||||
* @ds: ID of connected DS, DS_MAX if unsupported
|
||||
* @lm_pair_mask: Bitmask of LMs that can be controlled by same CTL
|
||||
*/
|
||||
struct dpu_lm_cfg {
|
||||
DPU_HW_BLK_INFO;
|
||||
const struct dpu_lm_sub_blks *sblk;
|
||||
u32 pingpong;
|
||||
u32 ds;
|
||||
unsigned long lm_pair_mask;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_ds_top_cfg - information of dest scaler top
|
||||
* @id enum identifying this block
|
||||
* @base register offset of this block
|
||||
* @features bit mask identifying features
|
||||
* @version hw version of dest scaler
|
||||
* @maxinputwidth maximum input line width
|
||||
* @maxoutputwidth maximum output line width
|
||||
* @maxupscale maximum upscale ratio
|
||||
*/
|
||||
struct dpu_ds_top_cfg {
|
||||
DPU_HW_BLK_INFO;
|
||||
u32 version;
|
||||
u32 maxinputwidth;
|
||||
u32 maxoutputwidth;
|
||||
u32 maxupscale;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_ds_cfg - information of dest scaler blocks
|
||||
* @id enum identifying this block
|
||||
* @base register offset wrt DS top offset
|
||||
* @features bit mask identifying features
|
||||
* @version hw version of the qseed block
|
||||
* @top DS top information
|
||||
*/
|
||||
struct dpu_ds_cfg {
|
||||
DPU_HW_BLK_INFO;
|
||||
u32 version;
|
||||
const struct dpu_ds_top_cfg *top;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_pingpong_cfg - information of PING-PONG blocks
|
||||
* @id enum identifying this block
|
||||
* @base register offset of this block
|
||||
* @features bit mask identifying sub-blocks/features
|
||||
* @sblk sub-blocks information
|
||||
*/
|
||||
struct dpu_pingpong_cfg {
|
||||
DPU_HW_BLK_INFO;
|
||||
const struct dpu_pingpong_sub_blks *sblk;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_cdm_cfg - information of chroma down blocks
|
||||
* @id enum identifying this block
|
||||
* @base register offset of this block
|
||||
* @features bit mask identifying sub-blocks/features
|
||||
* @intf_connect Bitmask of INTF IDs this CDM can connect to
|
||||
*/
|
||||
struct dpu_cdm_cfg {
|
||||
DPU_HW_BLK_INFO;
|
||||
unsigned long intf_connect;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_intf_cfg - information of timing engine blocks
|
||||
* @id enum identifying this block
|
||||
* @base register offset of this block
|
||||
* @features bit mask identifying sub-blocks/features
|
||||
* @type: Interface type(DSI, DP, HDMI)
|
||||
* @controller_id: Controller Instance ID in case of multiple of intf type
|
||||
* @prog_fetch_lines_worst_case Worst case latency num lines needed to prefetch
|
||||
*/
|
||||
struct dpu_intf_cfg {
|
||||
DPU_HW_BLK_INFO;
|
||||
u32 type; /* interface type*/
|
||||
u32 controller_id;
|
||||
u32 prog_fetch_lines_worst_case;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_vbif_dynamic_ot_cfg - dynamic OT setting
|
||||
* @pps pixel per seconds
|
||||
* @ot_limit OT limit to use up to specified pixel per second
|
||||
*/
|
||||
struct dpu_vbif_dynamic_ot_cfg {
|
||||
u64 pps;
|
||||
u32 ot_limit;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_vbif_dynamic_ot_tbl - dynamic OT setting table
|
||||
* @count length of cfg
|
||||
* @cfg pointer to array of configuration settings with
|
||||
* ascending requirements
|
||||
*/
|
||||
struct dpu_vbif_dynamic_ot_tbl {
|
||||
u32 count;
|
||||
struct dpu_vbif_dynamic_ot_cfg *cfg;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_vbif_qos_tbl - QoS priority table
|
||||
* @npriority_lvl num of priority level
|
||||
* @priority_lvl pointer to array of priority level in ascending order
|
||||
*/
|
||||
struct dpu_vbif_qos_tbl {
|
||||
u32 npriority_lvl;
|
||||
u32 *priority_lvl;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_vbif_cfg - information of VBIF blocks
|
||||
* @id enum identifying this block
|
||||
* @base register offset of this block
|
||||
* @features bit mask identifying sub-blocks/features
|
||||
* @ot_rd_limit default OT read limit
|
||||
* @ot_wr_limit default OT write limit
|
||||
* @xin_halt_timeout maximum time (in usec) for xin to halt
|
||||
* @dynamic_ot_rd_tbl dynamic OT read configuration table
|
||||
* @dynamic_ot_wr_tbl dynamic OT write configuration table
|
||||
* @qos_rt_tbl real-time QoS priority table
|
||||
* @qos_nrt_tbl non-real-time QoS priority table
|
||||
* @memtype_count number of defined memtypes
|
||||
* @memtype array of xin memtype definitions
|
||||
*/
|
||||
struct dpu_vbif_cfg {
|
||||
DPU_HW_BLK_INFO;
|
||||
u32 default_ot_rd_limit;
|
||||
u32 default_ot_wr_limit;
|
||||
u32 xin_halt_timeout;
|
||||
struct dpu_vbif_dynamic_ot_tbl dynamic_ot_rd_tbl;
|
||||
struct dpu_vbif_dynamic_ot_tbl dynamic_ot_wr_tbl;
|
||||
struct dpu_vbif_qos_tbl qos_rt_tbl;
|
||||
struct dpu_vbif_qos_tbl qos_nrt_tbl;
|
||||
u32 memtype_count;
|
||||
u32 memtype[MAX_XIN_COUNT];
|
||||
};
|
||||
/**
|
||||
* struct dpu_reg_dma_cfg - information of lut dma blocks
|
||||
* @id enum identifying this block
|
||||
* @base register offset of this block
|
||||
* @features bit mask identifying sub-blocks/features
|
||||
* @version version of lutdma hw block
|
||||
* @trigger_sel_off offset to trigger select registers of lutdma
|
||||
*/
|
||||
struct dpu_reg_dma_cfg {
|
||||
DPU_HW_BLK_INFO;
|
||||
u32 version;
|
||||
u32 trigger_sel_off;
|
||||
};
|
||||
|
||||
/**
|
||||
* Define CDP use cases
|
||||
* @DPU_PERF_CDP_UDAGE_RT: real-time use cases
|
||||
* @DPU_PERF_CDP_USAGE_NRT: non real-time use cases such as WFD
|
||||
*/
|
||||
enum {
|
||||
DPU_PERF_CDP_USAGE_RT,
|
||||
DPU_PERF_CDP_USAGE_NRT,
|
||||
DPU_PERF_CDP_USAGE_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_perf_cdp_cfg - define CDP use case configuration
|
||||
* @rd_enable: true if read pipe CDP is enabled
|
||||
* @wr_enable: true if write pipe CDP is enabled
|
||||
*/
|
||||
struct dpu_perf_cdp_cfg {
|
||||
bool rd_enable;
|
||||
bool wr_enable;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_perf_cfg - performance control settings
|
||||
* @max_bw_low low threshold of maximum bandwidth (kbps)
|
||||
* @max_bw_high high threshold of maximum bandwidth (kbps)
|
||||
* @min_core_ib minimum bandwidth for core (kbps)
|
||||
* @min_core_ib minimum mnoc ib vote in kbps
|
||||
* @min_llcc_ib minimum llcc ib vote in kbps
|
||||
* @min_dram_ib minimum dram ib vote in kbps
|
||||
* @core_ib_ff core instantaneous bandwidth fudge factor
|
||||
* @core_clk_ff core clock fudge factor
|
||||
* @comp_ratio_rt string of 0 or more of <fourcc>/<ven>/<mod>/<comp ratio>
|
||||
* @comp_ratio_nrt string of 0 or more of <fourcc>/<ven>/<mod>/<comp ratio>
|
||||
* @undersized_prefill_lines undersized prefill in lines
|
||||
* @xtra_prefill_lines extra prefill latency in lines
|
||||
* @dest_scale_prefill_lines destination scaler latency in lines
|
||||
* @macrotile_perfill_lines macrotile latency in lines
|
||||
* @yuv_nv12_prefill_lines yuv_nv12 latency in lines
|
||||
* @linear_prefill_lines linear latency in lines
|
||||
* @downscaling_prefill_lines downscaling latency in lines
|
||||
* @amortizable_theshold minimum y position for traffic shaping prefill
|
||||
* @min_prefill_lines minimum pipeline latency in lines
|
||||
* @safe_lut_tbl: LUT tables for safe signals
|
||||
* @danger_lut_tbl: LUT tables for danger signals
|
||||
* @qos_lut_tbl: LUT tables for QoS signals
|
||||
* @cdp_cfg cdp use case configurations
|
||||
*/
|
||||
struct dpu_perf_cfg {
|
||||
u32 max_bw_low;
|
||||
u32 max_bw_high;
|
||||
u32 min_core_ib;
|
||||
u32 min_llcc_ib;
|
||||
u32 min_dram_ib;
|
||||
const char *core_ib_ff;
|
||||
const char *core_clk_ff;
|
||||
const char *comp_ratio_rt;
|
||||
const char *comp_ratio_nrt;
|
||||
u32 undersized_prefill_lines;
|
||||
u32 xtra_prefill_lines;
|
||||
u32 dest_scale_prefill_lines;
|
||||
u32 macrotile_prefill_lines;
|
||||
u32 yuv_nv12_prefill_lines;
|
||||
u32 linear_prefill_lines;
|
||||
u32 downscaling_prefill_lines;
|
||||
u32 amortizable_threshold;
|
||||
u32 min_prefill_lines;
|
||||
u32 safe_lut_tbl[DPU_QOS_LUT_USAGE_MAX];
|
||||
u32 danger_lut_tbl[DPU_QOS_LUT_USAGE_MAX];
|
||||
struct dpu_qos_lut_tbl qos_lut_tbl[DPU_QOS_LUT_USAGE_MAX];
|
||||
struct dpu_perf_cdp_cfg cdp_cfg[DPU_PERF_CDP_USAGE_MAX];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct dpu_mdss_cfg - information of MDSS HW
|
||||
* This is the main catalog data structure representing
|
||||
* this HW version. Contains number of instances,
|
||||
* register offsets, capabilities of the all MDSS HW sub-blocks.
|
||||
*
|
||||
* @dma_formats Supported formats for dma pipe
|
||||
* @cursor_formats Supported formats for cursor pipe
|
||||
* @vig_formats Supported formats for vig pipe
|
||||
*/
|
||||
struct dpu_mdss_cfg {
|
||||
u32 hwversion;
|
||||
|
||||
const struct dpu_caps *caps;
|
||||
|
||||
u32 mdp_count;
|
||||
struct dpu_mdp_cfg *mdp;
|
||||
|
||||
u32 ctl_count;
|
||||
struct dpu_ctl_cfg *ctl;
|
||||
|
||||
u32 sspp_count;
|
||||
struct dpu_sspp_cfg *sspp;
|
||||
|
||||
u32 mixer_count;
|
||||
struct dpu_lm_cfg *mixer;
|
||||
|
||||
u32 ds_count;
|
||||
struct dpu_ds_cfg *ds;
|
||||
|
||||
u32 pingpong_count;
|
||||
struct dpu_pingpong_cfg *pingpong;
|
||||
|
||||
u32 cdm_count;
|
||||
struct dpu_cdm_cfg *cdm;
|
||||
|
||||
u32 intf_count;
|
||||
struct dpu_intf_cfg *intf;
|
||||
|
||||
u32 vbif_count;
|
||||
struct dpu_vbif_cfg *vbif;
|
||||
|
||||
u32 reg_dma_count;
|
||||
struct dpu_reg_dma_cfg dma_cfg;
|
||||
|
||||
u32 ad_count;
|
||||
|
||||
/* Add additional block data structures here */
|
||||
|
||||
struct dpu_perf_cfg perf;
|
||||
struct dpu_format_extended *dma_formats;
|
||||
struct dpu_format_extended *cursor_formats;
|
||||
struct dpu_format_extended *vig_formats;
|
||||
};
|
||||
|
||||
struct dpu_mdss_hw_cfg_handler {
|
||||
u32 hw_rev;
|
||||
void (*cfg_init)(struct dpu_mdss_cfg *dpu_cfg);
|
||||
};
|
||||
|
||||
/*
|
||||
* Access Macros
|
||||
*/
|
||||
#define BLK_MDP(s) ((s)->mdp)
|
||||
#define BLK_CTL(s) ((s)->ctl)
|
||||
#define BLK_VIG(s) ((s)->vig)
|
||||
#define BLK_RGB(s) ((s)->rgb)
|
||||
#define BLK_DMA(s) ((s)->dma)
|
||||
#define BLK_CURSOR(s) ((s)->cursor)
|
||||
#define BLK_MIXER(s) ((s)->mixer)
|
||||
#define BLK_DS(s) ((s)->ds)
|
||||
#define BLK_PINGPONG(s) ((s)->pingpong)
|
||||
#define BLK_CDM(s) ((s)->cdm)
|
||||
#define BLK_INTF(s) ((s)->intf)
|
||||
#define BLK_AD(s) ((s)->ad)
|
||||
|
||||
/**
|
||||
* dpu_hw_catalog_init - dpu hardware catalog init API retrieves
|
||||
* hardcoded target specific catalog information in config structure
|
||||
* @hw_rev: caller needs provide the hardware revision.
|
||||
*
|
||||
* Return: dpu config structure
|
||||
*/
|
||||
struct dpu_mdss_cfg *dpu_hw_catalog_init(u32 hw_rev);
|
||||
|
||||
/**
|
||||
* dpu_hw_catalog_deinit - dpu hardware catalog cleanup
|
||||
* @dpu_cfg: pointer returned from init function
|
||||
*/
|
||||
void dpu_hw_catalog_deinit(struct dpu_mdss_cfg *dpu_cfg);
|
||||
|
||||
/**
|
||||
* dpu_hw_sspp_multirect_enabled - check multirect enabled for the sspp
|
||||
* @cfg: pointer to sspp cfg
|
||||
*/
|
||||
static inline bool dpu_hw_sspp_multirect_enabled(const struct dpu_sspp_cfg *cfg)
|
||||
{
|
||||
return test_bit(DPU_SSPP_SMART_DMA_V1, &cfg->features) ||
|
||||
test_bit(DPU_SSPP_SMART_DMA_V2, &cfg->features);
|
||||
}
|
||||
#endif /* _DPU_HW_CATALOG_H */
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue