Watch
1
Fork
You've already forked kernel_a52xq
0
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:
Jeykumar Sankaran 2018-06-27 15:26:09 -04:00 • committed by Sean Paul
commit 25fdd5933e
64 changed files with 31913 additions and 15 deletions

View file

@ -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

View 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(&register_irq_cb->list);
list_add_tail(&register_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(&register_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;
}

View 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__ */

View 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;
}

View 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_ */

File diff suppressed because it is too large Load diff

View 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_ */

File diff suppressed because it is too large Load diff

View 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_ */

File diff suppressed because it is too large Load diff

View 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__ */

View 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__ */

View 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);
}

View 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);
}

File diff suppressed because it is too large Load diff

View 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 */

View 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);
}

View 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 */

View 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);
}

View 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