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:

committed by
Sean Paul

parent
036bfeb33b
commit
25fdd5933e
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
@@ -15,6 +16,8 @@
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/kthread.h>
|
||||
#include <uapi/linux/sched/types.h>
|
||||
#include <drm/drm_of.h>
|
||||
|
||||
#include "msm_drv.h"
|
||||
@@ -149,7 +152,7 @@ struct vblank_event {
|
||||
bool enable;
|
||||
};
|
||||
|
||||
static void vblank_ctrl_worker(struct work_struct *work)
|
||||
static void vblank_ctrl_worker(struct kthread_work *work)
|
||||
{
|
||||
struct msm_vblank_ctrl *vbl_ctrl = container_of(work,
|
||||
struct msm_vblank_ctrl, work);
|
||||
@@ -197,7 +200,8 @@ static int vblank_ctrl_queue_work(struct msm_drm_private *priv,
|
||||
list_add_tail(&vbl_ev->node, &vbl_ctrl->event_list);
|
||||
spin_unlock_irqrestore(&vbl_ctrl->lock, flags);
|
||||
|
||||
queue_work(priv->wq, &vbl_ctrl->work);
|
||||
kthread_queue_work(&priv->disp_thread[crtc_id].worker,
|
||||
&vbl_ctrl->work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -211,17 +215,33 @@ static int msm_drm_uninit(struct device *dev)
|
||||
struct msm_mdss *mdss = priv->mdss;
|
||||
struct msm_vblank_ctrl *vbl_ctrl = &priv->vblank_ctrl;
|
||||
struct vblank_event *vbl_ev, *tmp;
|
||||
int i;
|
||||
|
||||
/* We must cancel and cleanup any pending vblank enable/disable
|
||||
* work before drm_irq_uninstall() to avoid work re-enabling an
|
||||
* irq after uninstall has disabled it.
|
||||
*/
|
||||
cancel_work_sync(&vbl_ctrl->work);
|
||||
kthread_flush_work(&vbl_ctrl->work);
|
||||
list_for_each_entry_safe(vbl_ev, tmp, &vbl_ctrl->event_list, node) {
|
||||
list_del(&vbl_ev->node);
|
||||
kfree(vbl_ev);
|
||||
}
|
||||
|
||||
/* clean up display commit/event worker threads */
|
||||
for (i = 0; i < priv->num_crtcs; i++) {
|
||||
if (priv->disp_thread[i].thread) {
|
||||
kthread_flush_worker(&priv->disp_thread[i].worker);
|
||||
kthread_stop(priv->disp_thread[i].thread);
|
||||
priv->disp_thread[i].thread = NULL;
|
||||
}
|
||||
|
||||
if (priv->event_thread[i].thread) {
|
||||
kthread_flush_worker(&priv->event_thread[i].worker);
|
||||
kthread_stop(priv->event_thread[i].thread);
|
||||
priv->event_thread[i].thread = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
msm_gem_shrinker_cleanup(ddev);
|
||||
|
||||
drm_kms_helper_poll_fini(ddev);
|
||||
@@ -269,6 +289,7 @@ static int msm_drm_uninit(struct device *dev)
|
||||
|
||||
#define KMS_MDP4 4
|
||||
#define KMS_MDP5 5
|
||||
#define KMS_DPU 3
|
||||
|
||||
static int get_mdp_ver(struct platform_device *pdev)
|
||||
{
|
||||
@@ -360,7 +381,8 @@ static int msm_drm_init(struct device *dev, struct drm_driver *drv)
|
||||
struct msm_drm_private *priv;
|
||||
struct msm_kms *kms;
|
||||
struct msm_mdss *mdss;
|
||||
int ret;
|
||||
int ret, i;
|
||||
struct sched_param param;
|
||||
|
||||
ddev = drm_dev_alloc(drv, dev);
|
||||
if (IS_ERR(ddev)) {
|
||||
@@ -379,7 +401,17 @@ static int msm_drm_init(struct device *dev, struct drm_driver *drv)
|
||||
ddev->dev_private = priv;
|
||||
priv->dev = ddev;
|
||||
|
||||
ret = mdp5_mdss_init(ddev);
|
||||
switch (get_mdp_ver(pdev)) {
|
||||
case KMS_MDP5:
|
||||
ret = mdp5_mdss_init(ddev);
|
||||
break;
|
||||
case KMS_DPU:
|
||||
ret = dpu_mdss_init(ddev);
|
||||
break;
|
||||
default:
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
if (ret)
|
||||
goto err_free_priv;
|
||||
|
||||
@@ -389,7 +421,7 @@ static int msm_drm_init(struct device *dev, struct drm_driver *drv)
|
||||
|
||||
INIT_LIST_HEAD(&priv->inactive_list);
|
||||
INIT_LIST_HEAD(&priv->vblank_ctrl.event_list);
|
||||
INIT_WORK(&priv->vblank_ctrl.work, vblank_ctrl_worker);
|
||||
kthread_init_work(&priv->vblank_ctrl.work, vblank_ctrl_worker);
|
||||
spin_lock_init(&priv->vblank_ctrl.lock);
|
||||
|
||||
drm_mode_config_init(ddev);
|
||||
@@ -413,6 +445,10 @@ static int msm_drm_init(struct device *dev, struct drm_driver *drv)
|
||||
case KMS_MDP5:
|
||||
kms = mdp5_kms_init(ddev);
|
||||
break;
|
||||
case KMS_DPU:
|
||||
kms = dpu_kms_init(ddev);
|
||||
priv->kms = kms;
|
||||
break;
|
||||
default:
|
||||
kms = ERR_PTR(-ENODEV);
|
||||
break;
|
||||
@@ -444,6 +480,79 @@ static int msm_drm_init(struct device *dev, struct drm_driver *drv)
|
||||
ddev->mode_config.funcs = &mode_config_funcs;
|
||||
ddev->mode_config.helper_private = &mode_config_helper_funcs;
|
||||
|
||||
/**
|
||||
* this priority was found during empiric testing to have appropriate
|
||||
* realtime scheduling to process display updates and interact with
|
||||
* other real time and normal priority task
|
||||
*/
|
||||
param.sched_priority = 16;
|
||||
for (i = 0; i < priv->num_crtcs; i++) {
|
||||
|
||||
/* initialize display thread */
|
||||
priv->disp_thread[i].crtc_id = priv->crtcs[i]->base.id;
|
||||
kthread_init_worker(&priv->disp_thread[i].worker);
|
||||
priv->disp_thread[i].dev = ddev;
|
||||
priv->disp_thread[i].thread =
|
||||
kthread_run(kthread_worker_fn,
|
||||
&priv->disp_thread[i].worker,
|
||||
"crtc_commit:%d", priv->disp_thread[i].crtc_id);
|
||||
ret = sched_setscheduler(priv->disp_thread[i].thread,
|
||||
SCHED_FIFO, ¶m);
|
||||
if (ret)
|
||||
pr_warn("display thread priority update failed: %d\n",
|
||||
ret);
|
||||
|
||||
if (IS_ERR(priv->disp_thread[i].thread)) {
|
||||
dev_err(dev, "failed to create crtc_commit kthread\n");
|
||||
priv->disp_thread[i].thread = NULL;
|
||||
}
|
||||
|
||||
/* initialize event thread */
|
||||
priv->event_thread[i].crtc_id = priv->crtcs[i]->base.id;
|
||||
kthread_init_worker(&priv->event_thread[i].worker);
|
||||
priv->event_thread[i].dev = ddev;
|
||||
priv->event_thread[i].thread =
|
||||
kthread_run(kthread_worker_fn,
|
||||
&priv->event_thread[i].worker,
|
||||
"crtc_event:%d", priv->event_thread[i].crtc_id);
|
||||
/**
|
||||
* event thread should also run at same priority as disp_thread
|
||||
* because it is handling frame_done events. A lower priority
|
||||
* event thread and higher priority disp_thread can causes
|
||||
* frame_pending counters beyond 2. This can lead to commit
|
||||
* failure at crtc commit level.
|
||||
*/
|
||||
ret = sched_setscheduler(priv->event_thread[i].thread,
|
||||
SCHED_FIFO, ¶m);
|
||||
if (ret)
|
||||
pr_warn("display event thread priority update failed: %d\n",
|
||||
ret);
|
||||
|
||||
if (IS_ERR(priv->event_thread[i].thread)) {
|
||||
dev_err(dev, "failed to create crtc_event kthread\n");
|
||||
priv->event_thread[i].thread = NULL;
|
||||
}
|
||||
|
||||
if ((!priv->disp_thread[i].thread) ||
|
||||
!priv->event_thread[i].thread) {
|
||||
/* clean up previously created threads if any */
|
||||
for ( ; i >= 0; i--) {
|
||||
if (priv->disp_thread[i].thread) {
|
||||
kthread_stop(
|
||||
priv->disp_thread[i].thread);
|
||||
priv->disp_thread[i].thread = NULL;
|
||||
}
|
||||
|
||||
if (priv->event_thread[i].thread) {
|
||||
kthread_stop(
|
||||
priv->event_thread[i].thread);
|
||||
priv->event_thread[i].thread = NULL;
|
||||
}
|
||||
}
|
||||
goto err_msm_uninit;
|
||||
}
|
||||
}
|
||||
|
||||
ret = drm_vblank_init(ddev, priv->num_crtcs);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to initialize vblank\n");
|
||||
@@ -1060,12 +1169,13 @@ static int add_display_components(struct device *dev,
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* MDP5 based devices don't have a flat hierarchy. There is a top level
|
||||
* parent: MDSS, and children: MDP5, DSI, HDMI, eDP etc. Populate the
|
||||
* children devices, find the MDP5 node, and then add the interfaces
|
||||
* to our components list.
|
||||
* MDP5/DPU based devices don't have a flat hierarchy. There is a top
|
||||
* level parent: MDSS, and children: MDP5/DPU, DSI, HDMI, eDP etc.
|
||||
* Populate the children devices, find the MDP5/DPU node, and then add
|
||||
* the interfaces to our components list.
|
||||
*/
|
||||
if (of_device_is_compatible(dev->of_node, "qcom,mdss")) {
|
||||
if (of_device_is_compatible(dev->of_node, "qcom,mdss") ||
|
||||
of_device_is_compatible(dev->of_node, "qcom,sdm845-mdss")) {
|
||||
ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to populate children devices\n");
|
||||
@@ -1177,6 +1287,7 @@ static int msm_pdev_remove(struct platform_device *pdev)
|
||||
static const struct of_device_id dt_match[] = {
|
||||
{ .compatible = "qcom,mdp4", .data = (void *)KMS_MDP4 },
|
||||
{ .compatible = "qcom,mdss", .data = (void *)KMS_MDP5 },
|
||||
{ .compatible = "qcom,sdm845-mdss", .data = (void *)KMS_DPU },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, dt_match);
|
||||
@@ -1198,6 +1309,7 @@ static int __init msm_drm_register(void)
|
||||
|
||||
DBG("init");
|
||||
msm_mdp_register();
|
||||
msm_dpu_register();
|
||||
msm_dsi_register();
|
||||
msm_edp_register();
|
||||
msm_hdmi_register();
|
||||
@@ -1214,6 +1326,7 @@ static void __exit msm_drm_unregister(void)
|
||||
msm_edp_unregister();
|
||||
msm_dsi_unregister();
|
||||
msm_mdp_unregister();
|
||||
msm_dpu_unregister();
|
||||
}
|
||||
|
||||
module_init(msm_drm_register);
|
||||
|
Reference in New Issue
Block a user