drm/msm/sde: add virtual writeback device and connector
Enable virtual writeback connector by allowing a property on the
connector to specify output frame buffer id, which takes in a
framebuffer object added via ADDFB2 ioctl. A new writeback
configure ioctl is added to configure connection state and
display modes of writeback connector.
Change-Id: Ifce411a3f0798e3af7dd7f19da27d67cdd849bfb
Signed-off-by: Adrian Salido-Moreno <adrianm@codeaurora.org>
Signed-off-by: Alan Kwong <akwong@codeaurora.org>
Signed-off-by: Clarence Ip <cip@codeaurora.org>
Signed-off-by: Dhaval Patel <pdhaval@codeaurora.org>
Signed-off-by: Narendra Muppalla <narendram@codeaurora.org>
diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig
index b03bcdd..a9c5aa6 100644
--- a/drivers/gpu/drm/msm/Kconfig
+++ b/drivers/gpu/drm/msm/Kconfig
@@ -96,3 +96,11 @@
default n
help
Chose this option if HDCP supported is needed in DRM/KMS driver.
+
+config DRM_SDE_WB
+ bool "Enable Writeback support in SDE DRM"
+ depends on DRM_MSM
+ default y
+ help
+ Choose this option for writeback connector support.
+
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 366da8a..280a7dc 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -131,3 +131,6 @@
sde/sde_formats.o
obj-$(CONFIG_DRM_MSM) += display-manager/display_manager.o
+
+obj-$(CONFIG_DRM_SDE_WB) += sde/sde_wb.o \
+ sde/sde_encoder_phys_wb.o
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index a3822ee..9830b2f 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -21,6 +21,7 @@
#include "msm_gpu.h"
#include "msm_kms.h"
#include "display_manager.h"
+#include "sde_wb.h"
/*
@@ -782,6 +783,7 @@
DRM_IOCTL_DEF_DRV(MSM_GEM_SUBMIT, msm_ioctl_gem_submit, DRM_AUTH|DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(MSM_WAIT_FENCE, msm_ioctl_wait_fence, DRM_AUTH|DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(MSM_GEM_MADVISE, msm_ioctl_gem_madvise, DRM_AUTH|DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(SDE_WB_CONFIG, sde_wb_config, DRM_UNLOCKED|DRM_AUTH),
};
static const struct vm_operations_struct vm_ops = {
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index 0c2a552..69db252 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -116,6 +116,10 @@
/* range properties */
CONNECTOR_PROP_OUT_FB = CONNECTOR_PROP_BLOBCOUNT,
CONNECTOR_PROP_RETIRE_FENCE,
+ CONNECTOR_PROP_DST_X,
+ CONNECTOR_PROP_DST_Y,
+ CONNECTOR_PROP_DST_W,
+ CONNECTOR_PROP_DST_H,
/* enum/bitmask properties */
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c
index 0e1e829..d7e8dcc 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.c
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.c
@@ -120,6 +120,34 @@
}
}
+ /*
+ * Get default LMs if specified in platform config,
+ * otherwise acquire the free LMs.
+ */
+ for (i = WB_0; i < WB_MAX; i++) {
+ if (enc_hw_res.wbs[i]) {
+ struct sde_crtc_mixer *mixer =
+ &sde_crtc->mixer[sde_crtc->num_mixers];
+ plat_hw_res_map = sde_rm_get_res_map(sde_kms,
+ SDE_NONE, i);
+
+ lm_idx = plat_hw_res_map->lm;
+ if (!lm_idx && unused_lm_count)
+ lm_idx = unused_lm_id[--unused_lm_count];
+
+ DBG("Acquiring LM %d", lm_idx);
+ mixer->hw_lm = sde_rm_acquire_mixer(sde_kms, lm_idx);
+ if (IS_ERR_OR_NULL(mixer->hw_lm)) {
+ DRM_ERROR("Invalid mixer\n");
+ return -EACCES;
+ }
+ /* interface info */
+ mixer->wb_idx = i;
+ mixer->mode = enc_hw_res.wbs[i];
+ sde_crtc->num_mixers++;
+ }
+ }
+
DBG("control paths %d, num_mixers %d, lm[0] %d, ctl[0] %d ",
sde_crtc->num_ctls, sde_crtc->num_mixers,
sde_crtc->mixer[0].hw_lm->idx,
@@ -413,8 +441,23 @@
for (i = 0; i < sde_crtc->num_ctls; i++) {
mixer = &sde_crtc->mixer[i];
ctl = mixer->hw_ctl;
- ctl->ops.get_bitmask_intf(ctl, &mixer->flush_mask,
- mixer->intf_idx);
+
+ switch (mixer->mode) {
+ case INTF_MODE_CMD:
+ case INTF_MODE_VIDEO:
+ ctl->ops.get_bitmask_intf(ctl, &mixer->flush_mask,
+ mixer->intf_idx);
+ break;
+ case INTF_MODE_WB_LINE:
+ ctl->ops.get_bitmask_wb(ctl, &mixer->flush_mask,
+ mixer->wb_idx);
+ break;
+ default:
+ DBG("Invalid ctl %d interface mode %d", ctl->idx,
+ mixer->mode);
+ return -EINVAL;
+ }
+
ctl->ops.update_pending_flush(ctl, mixer->flush_mask);
DBG("added CTL_ID %d mask 0x%x to pending flush", ctl->idx,
mixer->flush_mask);
@@ -1116,10 +1159,11 @@
} else if (!m->hw_ctl) {
seq_printf(s, "Mixer[%d] has no CTL\n", i);
} else {
- seq_printf(s, "LM_%d/CTL_%d -> INTF_%d\n",
+ seq_printf(s, "LM_%d/CTL_%d -> INTF_%d, WB_%d\n",
m->hw_lm->idx - LM_0,
m->hw_ctl->idx - CTL_0,
- m->intf_idx - INTF_0);
+ m->intf_idx - INTF_0,
+ m->wb_idx - WB_0);
}
}
seq_printf(s, "Border: %d\n", sde_crtc->stage_cfg.border_enable);
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.h b/drivers/gpu/drm/msm/sde/sde_crtc.h
index 417e54b..c34be7f4 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.h
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.h
@@ -31,6 +31,7 @@
* @hw_lm : LM HW Driver context
* @hw_ctl : CTL Path HW driver context
* @intf_idx : Interface idx
+ * @wb_idx : Writeback idx
* @mode : Interface mode Active/CMD
* @flush_mask : Flush mask value for this commit
*/
@@ -39,6 +40,7 @@
struct sde_hw_mixer *hw_lm;
struct sde_hw_ctl *hw_ctl;
enum sde_intf intf_idx;
+ enum sde_wb wb_idx;
enum sde_intf_mode mode;
u32 flush_mask;
};
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.c b/drivers/gpu/drm/msm/sde/sde_encoder.c
index 5d4d6ce..5194679 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder.c
@@ -256,6 +256,57 @@
return ret;
}
+static int sde_encoder_virt_atomic_check(struct drm_encoder *drm_enc,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct sde_encoder_virt *sde_enc = NULL;
+ const struct drm_display_mode *mode;
+ struct drm_display_mode *adj_mode;
+ int i = 0;
+ int ret = 0;
+
+ DBG("");
+
+ if (!drm_enc || !crtc_state || !conn_state) {
+ DRM_ERROR("Invalid pointer");
+ return -EINVAL;
+ }
+
+ sde_enc = to_sde_encoder_virt(drm_enc);
+ mode = &crtc_state->mode;
+ adj_mode = &crtc_state->adjusted_mode;
+ MSM_EVT(drm_enc->dev, 0, 0);
+
+ /* perform atomic check on the first physical encoder (master) */
+ for (i = 0; i < sde_enc->num_phys_encs; i++) {
+ struct sde_encoder_phys *phys = sde_enc->phys_encs[i];
+
+ if (phys && phys->ops.atomic_check) {
+ ret = phys->ops.atomic_check(phys, crtc_state,
+ conn_state);
+ if (ret)
+ DRM_ERROR("Mode unsupported, phys_enc %d.%d\n",
+ drm_enc->base.id, i);
+ break;
+ } else if (phys && phys->ops.mode_fixup) {
+ if (!phys->ops.mode_fixup(phys, mode, adj_mode)) {
+ DRM_ERROR("Mode unsupported, phys_enc %d.%d\n",
+ drm_enc->base.id, i);
+ ret = -EINVAL;
+ }
+ break;
+ }
+ }
+
+ /* Call to populate mode->crtc* information required by framework */
+ drm_mode_set_crtcinfo(adj_mode, 0);
+
+ MSM_EVT(drm_enc->dev, adj_mode->flags, adj_mode->private_flags);
+
+ return ret;
+}
+
static void sde_encoder_virt_mode_set(struct drm_encoder *drm_enc,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
@@ -351,6 +402,7 @@
.mode_set = sde_encoder_virt_mode_set,
.disable = sde_encoder_virt_disable,
.enable = sde_encoder_virt_enable,
+ .atomic_check = sde_encoder_virt_atomic_check,
};
static const struct drm_encoder_funcs sde_encoder_funcs = {
@@ -374,6 +426,15 @@
return INTF_MAX;
}
+static enum sde_wb sde_encoder_get_wb(struct sde_mdss_cfg *catalog,
+ enum sde_intf_type type, u32 controller_id)
+{
+ if (controller_id < catalog->wb_count)
+ return catalog->wb[controller_id].id;
+
+ return WB_MAX;
+}
+
static void sde_encoder_vblank_callback(struct drm_encoder *drm_enc)
{
struct sde_encoder_virt *sde_enc = NULL;
@@ -567,6 +628,45 @@
return 0;
}
+static int sde_encoder_virt_add_phys_enc_wb(
+ struct sde_encoder_virt *sde_enc,
+ struct sde_kms *sde_kms,
+ enum sde_wb wb_idx,
+ enum sde_ctl ctl_idx,
+ enum sde_cdm cdm_idx,
+ enum sde_enc_split_role split_role)
+{
+ struct sde_encoder_phys *enc = NULL;
+ struct sde_encoder_virt_ops parent_ops = {
+ sde_encoder_vblank_callback,
+ sde_encoder_handle_phys_enc_ready_for_kickoff
+ };
+
+ DBG("");
+
+ if (sde_enc->num_phys_encs + 1 >=
+ ARRAY_SIZE(sde_enc->phys_encs)) {
+ DRM_ERROR("Too many physical encoders %d, unable to add\n",
+ sde_enc->num_phys_encs);
+ return -EINVAL;
+ }
+
+ enc = sde_encoder_phys_wb_init(sde_kms, wb_idx,
+ ctl_idx, cdm_idx, split_role, &sde_enc->base,
+ parent_ops);
+
+ if (IS_ERR_OR_NULL(enc)) {
+ DRM_ERROR("Failed to initialize phys wb enc: %ld\n",
+ PTR_ERR(enc));
+ return enc == 0 ? -EINVAL : PTR_ERR(enc);
+ }
+
+ sde_enc->phys_encs[sde_enc->num_phys_encs] = enc;
+ ++sde_enc->num_phys_encs;
+
+ return 0;
+}
+
static int sde_encoder_setup_display(struct sde_encoder_virt *sde_enc,
struct sde_kms *sde_kms,
struct msm_display_info *disp_info,
@@ -584,6 +684,9 @@
} else if (disp_info->intf_type == DRM_MODE_CONNECTOR_HDMIA) {
*drm_enc_mode = DRM_MODE_ENCODER_TMDS;
intf_type = INTF_HDMI;
+ } else if (disp_info->intf_type == DRM_MODE_CONNECTOR_VIRTUAL) {
+ *drm_enc_mode = DRM_MODE_ENCODER_VIRTUAL;
+ intf_type = INTF_WB;
} else {
DRM_ERROR("Unsupported display interface type");
return -EINVAL;
@@ -602,7 +705,9 @@
const struct sde_hw_res_map *hw_res_map = NULL;
enum sde_intf intf_idx = INTF_MAX;
enum sde_pingpong pp_idx = PINGPONG_MAX;
+ enum sde_wb wb_idx = WB_MAX;
enum sde_ctl ctl_idx = CTL_MAX;
+ enum sde_cdm cdm_idx = SDE_NONE;
u32 controller_id = disp_info->h_tile_instance[i];
enum sde_enc_split_role split_role = ENC_ROLE_SOLO;
@@ -616,26 +721,45 @@
DBG("h_tile_instance %d = %d, split_role %d",
i, controller_id, split_role);
- intf_idx = sde_encoder_get_intf(sde_kms->catalog,
- intf_type, controller_id);
- if (intf_idx == INTF_MAX) {
- DRM_ERROR("Error: could not get the interface id\n");
- ret = -EINVAL;
+ if (intf_type == INTF_WB) {
+ wb_idx = sde_encoder_get_wb(sde_kms->catalog,
+ intf_type, controller_id);
+ if (wb_idx == WB_MAX) {
+ DRM_ERROR(
+ "Error: could not get the writeback id\n");
+ ret = -EINVAL;
+ }
+ intf_idx = SDE_NONE;
+ } else {
+ intf_idx = sde_encoder_get_intf(sde_kms->catalog,
+ intf_type, controller_id);
+ if (intf_idx == INTF_MAX) {
+ DRM_ERROR(
+ "Error: could not get the interface id\n");
+ ret = -EINVAL;
+ }
+ wb_idx = SDE_NONE;
}
- hw_res_map = sde_rm_get_res_map(sde_kms, intf_idx, SDE_NONE);
+ hw_res_map = sde_rm_get_res_map(sde_kms, intf_idx, wb_idx);
if (IS_ERR_OR_NULL(hw_res_map)) {
ret = -EINVAL;
} else {
pp_idx = hw_res_map->pp;
ctl_idx = hw_res_map->ctl;
+ cdm_idx = hw_res_map->cdm;
}
if (!ret) {
- ret = sde_encoder_virt_add_phys_encs(
- disp_info->capabilities,
- sde_enc, sde_kms, intf_idx, pp_idx,
- ctl_idx, split_role);
+ if (intf_type == INTF_WB)
+ ret = sde_encoder_virt_add_phys_enc_wb(
+ sde_enc, sde_kms, wb_idx,
+ ctl_idx, cdm_idx, split_role);
+ else
+ ret = sde_encoder_virt_add_phys_encs(
+ disp_info->capabilities,
+ sde_enc, sde_kms, intf_idx,
+ pp_idx, ctl_idx, split_role);
if (ret)
DRM_ERROR("Failed to add phys encs\n");
}
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
index cc9fd9f..45114d4 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
@@ -20,6 +20,10 @@
#include "sde_hw_pingpong.h"
#include "sde_hw_ctl.h"
#include "sde_hw_top.h"
+#include "sde_hw_wb.h"
+#include "sde_hw_cdm.h"
+
+#define SDE_ENCODER_NAME_MAX 16
/**
* enum sde_enc_split_role - Role this physical encoder will play in a
@@ -62,6 +66,7 @@
* 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.
@@ -86,6 +91,9 @@
struct drm_display_mode *adjusted_mode);
void (*enable)(struct sde_encoder_phys *encoder);
void (*disable)(struct sde_encoder_phys *encoder);
+ int (*atomic_check)(struct sde_encoder_phys *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state);
void (*destroy)(struct sde_encoder_phys *encoder);
void (*get_hw_resources)(struct sde_encoder_phys *encoder,
struct sde_encoder_hw_resources *hw_res);
@@ -118,6 +126,8 @@
* @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
* @sde_kms: Pointer to the sde_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
@@ -132,6 +142,8 @@
struct sde_encoder_virt_ops parent_ops;
struct sde_hw_mdp *hw_mdptop;
struct sde_hw_ctl *hw_ctl;
+ struct sde_hw_cdm *hw_cdm;
+ struct sde_hw_cdm_cfg cdm_cfg;
struct sde_kms *sde_kms;
struct drm_display_mode cached_mode;
enum sde_enc_split_role split_role;
@@ -185,6 +197,51 @@
};
/**
+ * struct sde_encoder_phys_wb - sub-class of sde_encoder_phys to handle
+ * writeback specific operations
+ * @base: Baseclass physical encoder structure
+ * @hw_wb: Hardware interface to the wb registers
+ * @irq_idx: IRQ interface lookup index
+ * @wbdone_timeout: Timeout value for writeback done in msec
+ * @bypass_irqreg: Bypass irq register/unregister if non-zero
+ * @wbdone_complete: for wbdone irq synchronization
+ * @wb_cfg: Writeback hardware configuration
+ * @intf_cfg: Interface hardware configuration
+ * @wb_roi: Writeback region-of-interest
+ * @wb_fmt: Writeback pixel format
+ * @frame_count: Counter of completed writeback operations
+ * @kickoff_count: Counter of issued writeback operations
+ * @mmu_id: mmu identifier for non-secure/secure domain
+ * @wb_dev: Pointer to writeback device
+ * @start_time: Start time of writeback latest request
+ * @end_time: End time of writeback latest request
+ * @wb_name: Name of this writeback device
+ * @debugfs_root: Root entry of writeback debugfs
+ */
+struct sde_encoder_phys_wb {
+ struct sde_encoder_phys base;
+ struct sde_hw_wb *hw_wb;
+ int irq_idx;
+ u32 wbdone_timeout;
+ u32 bypass_irqreg;
+ struct completion wbdone_complete;
+ struct sde_hw_wb_cfg wb_cfg;
+ struct sde_hw_intf_cfg intf_cfg;
+ struct sde_rect wb_roi;
+ const struct sde_format *wb_fmt;
+ u32 frame_count;
+ u32 kickoff_count;
+ int mmu_id[SDE_IOMMU_DOMAIN_MAX];
+ struct sde_wb_device *wb_dev;
+ ktime_t start_time;
+ ktime_t end_time;
+#ifdef CONFIG_DEBUG_FS
+ char wb_name[SDE_ENCODER_NAME_MAX];
+ struct dentry *debugfs_root;
+#endif
+};
+
+/**
* sde_encoder_phys_vid_init - Construct a new video mode physical encoder
* @sde_kms: Pointer to the sde_kms top level
* @intf_idx: Interface index this phys_enc will control
@@ -222,6 +279,44 @@
struct drm_encoder *parent,
struct sde_encoder_virt_ops parent_ops);
+/**
+ * sde_encoder_phys_wb_init - Construct a new writeback physical encoder
+ * @sde_kms: Pointer to the sde_kms top level
+ * @wb_idx: Writeback index this phys_enc will control
+ * @ctl_idx: Control index this phys_enc requires
+ * @cdm_idx: Chromadown index this phys_enc requires
+ * @split_role: Role to play in a split-panel configuration
+ * @parent: Pointer to the containing virtual encoder
+ * @parent_ops: Callbacks exposed by the parent to the phys_enc
+ *
+ * Return: Error code or newly allocated encoder
+ */
+#ifdef CONFIG_DRM_SDE_WB
+struct sde_encoder_phys *sde_encoder_phys_wb_init(
+ struct sde_kms *sde_kms,
+ enum sde_wb wb_idx,
+ enum sde_ctl ctl_idx,
+ enum sde_cdm cdm_idx,
+ enum sde_enc_split_role split_role,
+ struct drm_encoder *parent,
+ struct sde_encoder_virt_ops parent_ops);
+#else
+static inline
+struct sde_encoder_phys *sde_encoder_phys_wb_init(
+ struct sde_kms *sde_kms,
+ enum sde_wb wb_idx,
+ enum sde_ctl ctl_idx,
+ enum sde_cdm cdm_idx,
+ enum sde_enc_split_role split_role,
+ struct drm_encoder *parent,
+ struct sde_encoder_virt_ops parent_ops)
+{
+ return NULL;
+}
+#endif
+void sde_encoder_phys_setup_cdm(struct sde_encoder_phys *phys_enc,
+ struct drm_framebuffer *fb, const struct sde_format *format,
+ struct sde_rect *wb_roi);
#endif /* __sde_encoder_phys_H__ */
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c
new file mode 100644
index 0000000..1f09470
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c
@@ -0,0 +1,1028 @@
+/*
+ * Copyright (c) 2015-2016 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) "sde-wb:[%s] " fmt, __func__
+
+#include <linux/jiffies.h>
+#include <linux/debugfs.h>
+
+#include "sde_encoder_phys.h"
+#include "sde_formats.h"
+#include "sde_hw_top.h"
+#include "sde_hw_interrupts.h"
+#include "sde_wb.h"
+
+/* wait for at most 2 vsync for lowest refresh rate (24hz) */
+#define WAIT_TIMEOUT_MSEC 84
+
+#define to_sde_encoder_phys_wb(x) \
+ container_of(x, struct sde_encoder_phys_wb, base)
+
+#define DEV(phy_enc) (phy_enc->parent->dev)
+
+/**
+ * sde_encoder_phys_wb_get_intr_type - get interrupt type based on block mode
+ * @hw_wb: Pointer to h/w writeback driver
+ */
+static enum sde_intr_type sde_encoder_phys_wb_get_intr_type(
+ struct sde_hw_wb *hw_wb)
+{
+ return (hw_wb->caps->features & BIT(SDE_WB_BLOCK_MODE)) ?
+ SDE_IRQ_TYPE_WB_ROT_COMP : SDE_IRQ_TYPE_WB_WFD_COMP;
+}
+
+/**
+ * sde_encoder_phys_wb_set_traffic_shaper - set traffic shaper for writeback
+ * @phys_enc: Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_set_traffic_shaper(
+ struct sde_encoder_phys *phys_enc)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb_cfg *wb_cfg = &wb_enc->wb_cfg;
+
+ /* traffic shaper is only enabled for rotator */
+ wb_cfg->ts_cfg.en = false;
+}
+
+/**
+ * sde_encoder_phys_setup_cdm - setup chroma down block
+ * @phys_enc: Pointer to physical encoder
+ * @fb: Pointer to output framebuffer
+ * @format: Output format
+ */
+void sde_encoder_phys_setup_cdm(struct sde_encoder_phys *phys_enc,
+ struct drm_framebuffer *fb, const struct sde_format *format,
+ struct sde_rect *wb_roi)
+{
+ struct sde_hw_cdm *hw_cdm = phys_enc->hw_cdm;
+ struct sde_hw_cdm_cfg *cdm_cfg = &phys_enc->cdm_cfg;
+ int ret;
+
+ if (!SDE_FORMAT_IS_YUV(format)) {
+ SDE_DEBUG("[cdm_disable fmt:%x]\n",
+ format->base.pixel_format);
+
+ if (hw_cdm && hw_cdm->ops.disable)
+ hw_cdm->ops.disable(hw_cdm);
+
+ return;
+ }
+
+ memset(cdm_cfg, 0, sizeof(struct sde_hw_cdm_cfg));
+
+ cdm_cfg->output_width = wb_roi->w;
+ cdm_cfg->output_height = wb_roi->h;
+ cdm_cfg->output_fmt = format;
+ cdm_cfg->output_type = CDM_CDWN_OUTPUT_WB;
+ cdm_cfg->output_bit_depth = CDM_CDWN_OUTPUT_8BIT;
+
+ /* enable 10 bit logic */
+ switch (cdm_cfg->output_fmt->chroma_sample) {
+ case SDE_CHROMA_RGB:
+ cdm_cfg->h_cdwn_type = CDM_CDWN_DISABLE;
+ cdm_cfg->v_cdwn_type = CDM_CDWN_DISABLE;
+ break;
+ case SDE_CHROMA_H2V1:
+ cdm_cfg->h_cdwn_type = CDM_CDWN_COSITE;
+ cdm_cfg->v_cdwn_type = CDM_CDWN_DISABLE;
+ break;
+ case SDE_CHROMA_420:
+ cdm_cfg->h_cdwn_type = CDM_CDWN_COSITE;
+ cdm_cfg->v_cdwn_type = CDM_CDWN_OFFSITE;
+ break;
+ case SDE_CHROMA_H1V2:
+ default:
+ SDE_ERROR("unsupported chroma sampling type\n");
+ cdm_cfg->h_cdwn_type = CDM_CDWN_DISABLE;
+ cdm_cfg->v_cdwn_type = CDM_CDWN_DISABLE;
+ break;
+ }
+
+ SDE_DEBUG("[cdm_enable:%d,%d,%X,%d,%d,%d,%d]\n",
+ cdm_cfg->output_width,
+ cdm_cfg->output_height,
+ cdm_cfg->output_fmt->base.pixel_format,
+ cdm_cfg->output_type,
+ cdm_cfg->output_bit_depth,
+ cdm_cfg->h_cdwn_type,
+ cdm_cfg->v_cdwn_type);
+
+ if (hw_cdm && hw_cdm->ops.setup_cdwn) {
+ ret = hw_cdm->ops.setup_cdwn(hw_cdm, cdm_cfg);
+ if (ret < 0) {
+ SDE_ERROR("failed to setup CDM %d\n", ret);
+ return;
+ }
+ }
+
+ if (hw_cdm && hw_cdm->ops.enable) {
+ ret = hw_cdm->ops.enable(hw_cdm, cdm_cfg);
+ if (ret < 0) {
+ SDE_ERROR("failed to enable CDM %d\n", ret);
+ return;
+ }
+ }
+}
+
+/**
+ * sde_encoder_phys_wb_setup_fb - setup output framebuffer
+ * @phys_enc: Pointer to physical encoder
+ * @fb: Pointer to output framebuffer
+ * @wb_roi: Pointer to output region of interest
+ */
+static void sde_encoder_phys_wb_setup_fb(struct sde_encoder_phys *phys_enc,
+ struct drm_framebuffer *fb, struct sde_rect *wb_roi)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+ struct sde_hw_wb_cfg *wb_cfg = &wb_enc->wb_cfg;
+ const struct msm_format *format;
+ int ret, mmu_id;
+
+ memset(wb_cfg, 0, sizeof(struct sde_hw_wb_cfg));
+
+ wb_cfg->intf_mode = INTF_MODE_WB_LINE;
+ wb_cfg->is_secure = (fb->flags & DRM_MODE_FB_SECURE) ? true : false;
+ mmu_id = (wb_cfg->is_secure) ?
+ wb_enc->mmu_id[SDE_IOMMU_DOMAIN_SECURE] :
+ wb_enc->mmu_id[SDE_IOMMU_DOMAIN_UNSECURE];
+
+ SDE_DEBUG("[fb_secure:%d]\n", wb_cfg->is_secure);
+
+ format = msm_framebuffer_format(fb);
+ wb_cfg->dest.format = sde_get_sde_format_ext(
+ format->pixel_format,
+ fb->modifier,
+ drm_format_num_planes(fb->pixel_format));
+ if (!wb_cfg->dest.format) {
+ /* this error should be detected during atomic_check */
+ SDE_ERROR("failed to get format %x\n", format->pixel_format);
+ return;
+ }
+
+ ret = sde_format_populate_layout_with_roi(mmu_id, fb, wb_roi,
+ &wb_cfg->dest);
+ if (ret) {
+ /* this error should be detected during atomic_check */
+ SDE_DEBUG("failed to populate layout %d\n", ret);
+ return;
+ }
+
+ if ((wb_cfg->dest.format->fetch_planes == SDE_PLANE_PLANAR) &&
+ (wb_cfg->dest.format->element[0] == C1_B_Cb))
+ swap(wb_cfg->dest.plane_addr[1], wb_cfg->dest.plane_addr[2]);
+
+ SDE_DEBUG("[fb_offset:%8.8x,%8.8x,%8.8x,%8.8x]\n",
+ wb_cfg->dest.plane_addr[0],
+ wb_cfg->dest.plane_addr[1],
+ wb_cfg->dest.plane_addr[2],
+ wb_cfg->dest.plane_addr[3]);
+ SDE_DEBUG("[fb_stride:%8.8x,%8.8x,%8.8x,%8.8x]\n",
+ wb_cfg->dest.plane_pitch[0],
+ wb_cfg->dest.plane_pitch[1],
+ wb_cfg->dest.plane_pitch[2],
+ wb_cfg->dest.plane_pitch[3]);
+
+ if (hw_wb->ops.setup_outformat)
+ hw_wb->ops.setup_outformat(hw_wb, wb_cfg);
+
+ if (hw_wb->ops.setup_outaddress)
+ hw_wb->ops.setup_outaddress(hw_wb, wb_cfg);
+}
+
+/**
+ * sde_encoder_phys_wb_setup_cdp - setup chroma down prefetch block
+ * @phys_enc: Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_setup_cdp(struct sde_encoder_phys *phys_enc)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+ struct sde_hw_intf_cfg *intf_cfg = &wb_enc->intf_cfg;
+
+ memset(intf_cfg, 0, sizeof(struct sde_hw_intf_cfg));
+
+ intf_cfg->intf = SDE_NONE;
+ intf_cfg->wb = hw_wb->idx;
+
+ if (phys_enc->hw_ctl->ops.setup_intf_cfg)
+ phys_enc->hw_ctl->ops.setup_intf_cfg(phys_enc->hw_ctl,
+ intf_cfg);
+}
+
+/**
+ * sde_encoder_phys_wb_atomic_check - verify and fixup given atomic states
+ * @phys_enc: Pointer to physical encoder
+ * @crtc_state: Pointer to CRTC atomic state
+ * @conn_state: Pointer to connector atomic state
+ */
+static int sde_encoder_phys_wb_atomic_check(
+ struct sde_encoder_phys *phys_enc,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+ const struct sde_wb_cfg *wb_cfg = hw_wb->caps;
+ struct drm_framebuffer *fb;
+ const struct sde_format *fmt;
+ struct sde_rect wb_roi;
+ const struct drm_display_mode *mode = &crtc_state->mode;
+ int rc;
+
+ SDE_DEBUG("[atomic_check:%d,%d,\"%s\",%d,%d]\n",
+ hw_wb->idx - WB_0, mode->base.id, mode->name,
+ mode->hdisplay, mode->vdisplay);
+
+ memset(&wb_roi, 0, sizeof(struct sde_rect));
+
+ rc = sde_wb_connector_state_get_output_roi(conn_state, &wb_roi);
+ if (rc) {
+ SDE_ERROR("failed to get roi %d\n", rc);
+ return rc;
+ }
+
+ SDE_DEBUG("[roi:%u,%u,%u,%u]\n", wb_roi.x, wb_roi.y,
+ wb_roi.w, wb_roi.h);
+
+ fb = sde_wb_connector_state_get_output_fb(conn_state);
+ if (!fb) {
+ SDE_ERROR("no output framebuffer\n");
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("[fb_id:%u][fb:%u,%u]\n", fb->base.id,
+ fb->width, fb->height);
+
+ fmt = sde_get_sde_format_ext(fb->pixel_format, fb->modifier,
+ drm_format_num_planes(fb->pixel_format));
+ if (!fmt) {
+ SDE_ERROR("unsupported output pixel format:%d\n",
+ fb->pixel_format);
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("[fb_fmt:%x,%llx]\n", fb->pixel_format,
+ fb->modifier[0]);
+
+ if (SDE_FORMAT_IS_YUV(fmt) &&
+ !(wb_cfg->features & BIT(SDE_WB_YUV_CONFIG))) {
+ SDE_ERROR("invalid output format %x\n", fmt->base.pixel_format);
+ return -EINVAL;
+ }
+
+ if (SDE_FORMAT_IS_UBWC(fmt) &&
+ !(wb_cfg->features & BIT(SDE_WB_UBWC_1_0))) {
+ SDE_ERROR("invalid output format %x\n", fmt->base.pixel_format);
+ return -EINVAL;
+ }
+
+ if (wb_roi.w && wb_roi.h) {
+ if (wb_roi.w != mode->hdisplay) {
+ SDE_ERROR("invalid roi w=%d, mode w=%d\n", wb_roi.w,
+ mode->hdisplay);
+ return -EINVAL;
+ } else if (wb_roi.h != mode->vdisplay) {
+ SDE_ERROR("invalid roi h=%d, mode h=%d\n", wb_roi.h,
+ mode->vdisplay);
+ return -EINVAL;
+ } else if (wb_roi.x + wb_roi.w > fb->width) {
+ SDE_ERROR("invalid roi x=%d, w=%d, fb w=%d\n",
+ wb_roi.x, wb_roi.w, fb->width);
+ return -EINVAL;
+ } else if (wb_roi.y + wb_roi.h > fb->height) {
+ SDE_ERROR("invalid roi y=%d, h=%d, fb h=%d\n",
+ wb_roi.y, wb_roi.h, fb->height);
+ return -EINVAL;
+ } else if (wb_roi.w > wb_cfg->sblk->maxlinewidth) {
+ SDE_ERROR("invalid roi w=%d, maxlinewidth=%u\n",
+ wb_roi.w, wb_cfg->sblk->maxlinewidth);
+ return -EINVAL;
+ }
+ } else {
+ if (wb_roi.x || wb_roi.y) {
+ SDE_ERROR("invalid roi x=%d, y=%d\n",
+ wb_roi.x, wb_roi.y);
+ return -EINVAL;
+ } else if (fb->width != mode->hdisplay) {
+ SDE_ERROR("invalid fb w=%d, mode w=%d\n", fb->width,
+ mode->hdisplay);
+ return -EINVAL;
+ } else if (fb->height != mode->vdisplay) {
+ SDE_ERROR("invalid fb h=%d, mode h=%d\n", fb->height,
+ mode->vdisplay);
+ return -EINVAL;
+ } else if (fb->width > wb_cfg->sblk->maxlinewidth) {
+ SDE_ERROR("invalid fb w=%d, maxlinewidth=%u\n",
+ fb->width, wb_cfg->sblk->maxlinewidth);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * sde_encoder_phys_wb_flush - flush hardware update
+ * @phys_enc: Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_flush(struct sde_encoder_phys *phys_enc)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+ struct sde_hw_ctl *hw_ctl = phys_enc->hw_ctl;
+ struct sde_hw_cdm *hw_cdm = phys_enc->hw_cdm;
+ u32 flush_mask = 0;
+
+ SDE_DEBUG("[wb:%d]\n", hw_wb->idx - WB_0);
+
+ if (hw_ctl->ops.get_bitmask_wb)
+ hw_ctl->ops.get_bitmask_wb(hw_ctl, &flush_mask, hw_wb->idx);
+
+ if (hw_ctl->ops.get_bitmask_cdm && hw_cdm)
+ hw_ctl->ops.get_bitmask_cdm(hw_ctl, &flush_mask, hw_cdm->idx);
+
+ if (hw_ctl->ops.update_pending_flush)
+ hw_ctl->ops.update_pending_flush(hw_ctl, flush_mask);
+
+ SDE_DEBUG("Flushing CTL_ID %d, flush_mask %x, WB %d\n",
+ hw_ctl->idx - CTL_0, flush_mask, hw_wb->idx - WB_0);
+}
+
+/**
+ * sde_encoder_phys_wb_setup - setup writeback encoder
+ * @phys_enc: Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_setup(
+ struct sde_encoder_phys *phys_enc)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+ struct drm_display_mode mode = phys_enc->cached_mode;
+ struct drm_framebuffer *fb;
+ struct sde_rect *wb_roi = &wb_enc->wb_roi;
+
+ SDE_DEBUG("[mode_set:%d,%d,\"%s\",%d,%d]\n",
+ hw_wb->idx - WB_0, mode.base.id, mode.name,
+ mode.hdisplay, mode.vdisplay);
+
+ memset(wb_roi, 0, sizeof(struct sde_rect));
+
+ fb = sde_wb_get_output_fb(wb_enc->wb_dev);
+ if (!fb) {
+ SDE_DEBUG("no output framebuffer\n");
+ return;
+ }
+
+ SDE_DEBUG("[fb_id:%u][fb:%u,%u]\n", fb->base.id,
+ fb->width, fb->height);
+
+ sde_wb_get_output_roi(wb_enc->wb_dev, wb_roi);
+ if (wb_roi->w == 0 || wb_roi->h == 0) {
+ wb_roi->x = 0;
+ wb_roi->y = 0;
+ wb_roi->w = fb->width;
+ wb_roi->h = fb->height;
+ }
+
+ SDE_DEBUG("[roi:%u,%u,%u,%u]\n", wb_roi->x, wb_roi->y,
+ wb_roi->w, wb_roi->h);
+
+ wb_enc->wb_fmt = sde_get_sde_format_ext(fb->pixel_format, fb->modifier,
+ drm_format_num_planes(fb->pixel_format));
+ if (!wb_enc->wb_fmt) {
+ SDE_ERROR("unsupported output pixel format: %d\n",
+ fb->pixel_format);
+ return;
+ }
+
+ SDE_DEBUG("[fb_fmt:%x,%llx]\n", fb->pixel_format,
+ fb->modifier[0]);
+
+ sde_encoder_phys_wb_set_traffic_shaper(phys_enc);
+
+ sde_encoder_phys_setup_cdm(phys_enc, fb, wb_enc->wb_fmt, wb_roi);
+
+ sde_encoder_phys_wb_setup_fb(phys_enc, fb, wb_roi);
+
+ sde_encoder_phys_wb_setup_cdp(phys_enc);
+}
+
+/**
+ * sde_encoder_phys_wb_unregister_irq - unregister writeback interrupt handler
+ * @phys_enc: Pointer to physical encoder
+ */
+static int sde_encoder_phys_wb_unregister_irq(
+ struct sde_encoder_phys *phys_enc)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+
+ if (wb_enc->bypass_irqreg)
+ return 0;
+
+ sde_disable_irq(phys_enc->sde_kms, &wb_enc->irq_idx, 1);
+ sde_register_irq_callback(phys_enc->sde_kms, wb_enc->irq_idx, NULL);
+
+ SDE_DEBUG("un-register IRQ for wb %d, irq_idx=%d\n",
+ hw_wb->idx - WB_0,
+ wb_enc->irq_idx);
+
+ return 0;
+}
+
+/**
+ * sde_encoder_phys_wb_done_irq - writeback interrupt handler
+ * @arg: Pointer to writeback encoder
+ * @irq_idx: interrupt index
+ */
+static void sde_encoder_phys_wb_done_irq(void *arg, int irq_idx)
+{
+ struct sde_encoder_phys_wb *wb_enc = arg;
+ struct sde_encoder_phys *phys_enc = &wb_enc->base;
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+
+ SDE_DEBUG("[wb:%d,%u]\n", hw_wb->idx - WB_0,
+ wb_enc->frame_count);
+
+ complete_all(&wb_enc->wbdone_complete);
+
+ phys_enc->parent_ops.handle_vblank_virt(phys_enc->parent);
+}
+
+/**
+ * sde_encoder_phys_wb_register_irq - register writeback interrupt handler
+ * @phys_enc: Pointer to physical encoder
+ */
+static int sde_encoder_phys_wb_register_irq(struct sde_encoder_phys *phys_enc)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+ struct sde_irq_callback irq_cb;
+ enum sde_intr_type intr_type;
+ int ret = 0;
+
+ if (wb_enc->bypass_irqreg)
+ return 0;
+
+ intr_type = sde_encoder_phys_wb_get_intr_type(hw_wb);
+ wb_enc->irq_idx = sde_irq_idx_lookup(phys_enc->sde_kms,
+ intr_type, hw_wb->idx);
+ if (wb_enc->irq_idx < 0) {
+ SDE_ERROR(
+ "failed to lookup IRQ index for WB_DONE with wb=%d\n",
+ hw_wb->idx - WB_0);
+ return -EINVAL;
+ }
+
+ irq_cb.func = sde_encoder_phys_wb_done_irq;
+ irq_cb.arg = wb_enc;
+ ret = sde_register_irq_callback(phys_enc->sde_kms, wb_enc->irq_idx,
+ &irq_cb);
+ if (ret) {
+ SDE_ERROR("failed to register IRQ callback WB_DONE\n");
+ return ret;
+ }
+
+ ret = sde_enable_irq(phys_enc->sde_kms, &wb_enc->irq_idx, 1);
+ if (ret) {
+ SDE_ERROR(
+ "failed to enable IRQ for WB_DONE, wb %d, irq_idx=%d\n",
+ hw_wb->idx - WB_0,
+ wb_enc->irq_idx);
+ wb_enc->irq_idx = -EINVAL;
+
+ /* Unregister callback on IRQ enable failure */
+ sde_register_irq_callback(phys_enc->sde_kms, wb_enc->irq_idx,
+ NULL);
+ return ret;
+ }
+
+ SDE_DEBUG("registered IRQ for wb %d, irq_idx=%d\n",
+ hw_wb->idx - WB_0,
+ wb_enc->irq_idx);
+
+ return ret;
+}
+
+/**
+ * sde_encoder_phys_wb_mode_set - set display mode
+ * @phys_enc: Pointer to physical encoder
+ * @mode: Pointer to requested display mode
+ * @adj_mode: Pointer to adjusted display mode
+ */
+static void sde_encoder_phys_wb_mode_set(
+ struct sde_encoder_phys *phys_enc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+
+ phys_enc->cached_mode = *adj_mode;
+
+ SDE_DEBUG("[mode_set_cache:%d,%d,\"%s\",%d,%d]\n",
+ hw_wb->idx - WB_0, mode->base.id,
+ mode->name, mode->hdisplay, mode->vdisplay);
+}
+
+/**
+ * sde_encoder_phys_wb_control_vblank_irq - Control vblank interrupt
+ * @phys_enc: Pointer to physical encoder
+ * @enable: Enable interrupt
+ */
+static int sde_encoder_phys_wb_control_vblank_irq(
+ struct sde_encoder_phys *phys_enc,
+ bool enable)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+ int ret = 0;
+
+ SDE_DEBUG("[wb:%d,%d]\n", hw_wb->idx - WB_0, enable);
+
+ if (enable)
+ ret = sde_encoder_phys_wb_register_irq(phys_enc);
+ else
+ ret = sde_encoder_phys_wb_unregister_irq(phys_enc);
+
+ if (ret)
+ SDE_ERROR("control vblank irq error %d, enable %d\n", ret,
+ enable);
+
+ return ret;
+}
+
+/**
+ * sde_encoder_phys_wb_wait_for_commit_done - wait until request is committed
+ * @phys_enc: Pointer to physical encoder
+ */
+static int sde_encoder_phys_wb_wait_for_commit_done(
+ struct sde_encoder_phys *phys_enc)
+{
+ unsigned long ret;
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ u32 irq_status;
+ u64 wb_time = 0;
+ int rc = 0;
+
+ /* Return EWOULDBLOCK since we know the wait isn't necessary */
+ if (WARN_ON(phys_enc->enable_state != SDE_ENC_ENABLED))
+ return -EWOULDBLOCK;
+
+ MSM_EVT(DEV(phys_enc), wb_enc->frame_count, 0);
+
+ ret = wait_for_completion_timeout(&wb_enc->wbdone_complete,
+ msecs_to_jiffies(wb_enc->wbdone_timeout));
+
+ if (!ret) {
+ MSM_EVT(DEV(phys_enc), wb_enc->frame_count, 0);
+
+ irq_status = sde_read_irq(phys_enc->sde_kms,
+ wb_enc->irq_idx, true);
+ if (irq_status) {
+ SDE_DEBUG("wb:%d done but irq not triggered\n",
+ wb_enc->wb_dev->wb_idx - WB_0);
+ sde_encoder_phys_wb_done_irq(wb_enc, wb_enc->irq_idx);
+ } else {
+ SDE_ERROR("wb:%d kickoff timed out\n",
+ wb_enc->wb_dev->wb_idx - WB_0);
+ rc = -ETIMEDOUT;
+ }
+ }
+
+ sde_encoder_phys_wb_unregister_irq(phys_enc);
+
+ if (!rc)
+ wb_enc->end_time = ktime_get();
+
+ /* once operation is done, disable traffic shaper */
+ if (wb_enc->wb_cfg.ts_cfg.en && wb_enc->hw_wb &&
+ wb_enc->hw_wb->ops.setup_trafficshaper) {
+ wb_enc->wb_cfg.ts_cfg.en = false;
+ wb_enc->hw_wb->ops.setup_trafficshaper(
+ wb_enc->hw_wb, &wb_enc->wb_cfg);
+ }
+
+ /* remove vote for iommu/clk/bus */
+ wb_enc->frame_count++;
+
+ if (!rc) {
+ wb_time = (u64)ktime_to_us(wb_enc->end_time) -
+ (u64)ktime_to_us(wb_enc->start_time);
+ SDE_DEBUG("wb:%d took %llu us\n",
+ wb_enc->wb_dev->wb_idx - WB_0, wb_time);
+ }
+
+ MSM_EVT(DEV(phys_enc), wb_enc->frame_count, wb_time);
+
+ return rc;
+}
+
+/**
+ * sde_encoder_phys_wb_prepare_for_kickoff - pre-kickoff processing
+ * @phys_enc: Pointer to physical encoder
+ * @need_to_wait: Wait for next submission
+ */
+static void sde_encoder_phys_wb_prepare_for_kickoff(
+ struct sde_encoder_phys *phys_enc,
+ bool *need_to_wait)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ int ret;
+
+ SDE_DEBUG("[wb:%d,%u]\n", wb_enc->hw_wb->idx - WB_0,
+ wb_enc->kickoff_count);
+
+ *need_to_wait = false;
+
+ reinit_completion(&wb_enc->wbdone_complete);
+
+ ret = sde_encoder_phys_wb_register_irq(phys_enc);
+ if (ret) {
+ SDE_ERROR("failed to register irq %d\n", ret);
+ return;
+ }
+
+ wb_enc->kickoff_count++;
+
+ /* set OT limit & enable traffic shaper */
+ sde_encoder_phys_wb_setup(phys_enc);
+
+ sde_encoder_phys_wb_flush(phys_enc);
+
+ /* vote for iommu/clk/bus */
+ wb_enc->start_time = ktime_get();
+
+ MSM_EVT(DEV(phys_enc), *need_to_wait, wb_enc->kickoff_count);
+}
+
+/**
+ * sde_encoder_phys_wb_handle_post_kickoff - post-kickoff processing
+ * @phys_enc: Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_handle_post_kickoff(
+ struct sde_encoder_phys *phys_enc)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+
+ SDE_DEBUG("[wb:%d]\n", wb_enc->hw_wb->idx - WB_0);
+
+ MSM_EVT(DEV(phys_enc), 0, 0);
+}
+
+/**
+ * sde_encoder_phys_wb_enable - enable writeback encoder
+ * @phys_enc: Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_enable(struct sde_encoder_phys *phys_enc)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+ struct drm_connector *connector;
+
+ SDE_DEBUG("[wb:%d]\n", hw_wb->idx - WB_0);
+
+ /* find associated writeback connector */
+ drm_for_each_connector(connector, phys_enc->parent->dev) {
+ if (connector->encoder == phys_enc->parent)
+ break;
+ }
+ if (!connector || connector->encoder != phys_enc->parent) {
+ SDE_ERROR("failed to find writeback connector\n");
+ return;
+ }
+ wb_enc->wb_dev = sde_wb_connector_get_wb(connector);
+
+ phys_enc->enable_state = SDE_ENC_ENABLED;
+}
+
+/**
+ * sde_encoder_phys_wb_disable - disable writeback encoder
+ * @phys_enc: Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_disable(struct sde_encoder_phys *phys_enc)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+
+ SDE_DEBUG("[wb:%d]\n", hw_wb->idx - WB_0);
+
+ if (phys_enc->enable_state == SDE_ENC_DISABLED) {
+ SDE_ERROR("encoder is already disabled\n");
+ return;
+ }
+
+ if (wb_enc->frame_count != wb_enc->kickoff_count) {
+ SDE_DEBUG("[wait_for_done: wb:%d, frame:%u, kickoff:%u]\n",
+ hw_wb->idx - WB_0, wb_enc->frame_count,
+ wb_enc->kickoff_count);
+ sde_encoder_phys_wb_wait_for_commit_done(phys_enc);
+ }
+
+ phys_enc->enable_state = SDE_ENC_DISABLED;
+}
+
+/**
+ * sde_encoder_phys_wb_get_hw_resources - get hardware resources
+ * @phys_enc: Pointer to physical encoder
+ * @hw_res: Pointer to encoder resources
+ */
+static void sde_encoder_phys_wb_get_hw_resources(
+ struct sde_encoder_phys *phys_enc,
+ struct sde_encoder_hw_resources *hw_res)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+ const struct sde_hw_res_map *hw_res_map;
+
+ SDE_DEBUG("[wb:%d]\n", hw_wb->idx - WB_0);
+
+ hw_res->wbs[hw_wb->idx] = INTF_MODE_WB_LINE;
+
+ /*
+ * Validate if we want to use the default map
+ * defaults should not be in use,
+ * otherwise signal/return failure
+ */
+ hw_res_map = sde_rm_get_res_map(phys_enc->sde_kms,
+ SDE_NONE, hw_wb->idx);
+ if (IS_ERR_OR_NULL(hw_res_map)) {
+ SDE_ERROR("failed to get hw_res_map: %ld\n",
+ PTR_ERR(hw_res_map));
+ return;
+ }
+
+ /*
+ * cached ctl_idx at init time, shouldn't we use that?
+ */
+ hw_res->ctls[hw_res_map->ctl] = true;
+}
+
+#ifdef CONFIG_DEBUG_FS
+/**
+ * sde_encoder_phys_wb_init_debugfs - initialize writeback encoder debugfs
+ * @phys_enc: Pointer to physical encoder
+ * @sde_kms: Pointer to SDE KMS object
+ */
+static int sde_encoder_phys_wb_init_debugfs(
+ struct sde_encoder_phys *phys_enc, struct sde_kms *kms)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+
+ if (!phys_enc || !kms || !wb_enc->hw_wb)
+ return -EINVAL;
+
+ snprintf(wb_enc->wb_name, ARRAY_SIZE(wb_enc->wb_name), "encoder_wb%d",
+ wb_enc->hw_wb->idx - WB_0);
+
+ wb_enc->debugfs_root =
+ debugfs_create_dir(wb_enc->wb_name,
+ sde_debugfs_get_root(kms));
+ if (!wb_enc->debugfs_root) {
+ SDE_ERROR("failed to create debugfs\n");
+ return -ENOMEM;
+ }
+
+ if (!debugfs_create_u32("wbdone_timeout", 0644,
+ wb_enc->debugfs_root, &wb_enc->wbdone_timeout)) {
+ SDE_ERROR("failed to create debugfs/wbdone_timeout\n");
+ return -ENOMEM;
+ }
+
+ if (!debugfs_create_u32("bypass_irqreg", 0644,
+ wb_enc->debugfs_root, &wb_enc->bypass_irqreg)) {
+ SDE_ERROR("failed to create debugfs/bypass_irqreg\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/**
+ * sde_encoder_phys_wb_destroy_debugfs - destroy writeback encoder debugfs
+ * @phys_enc: Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_destroy_debugfs(
+ struct sde_encoder_phys *phys_enc)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+
+ if (!phys_enc)
+ return;
+
+ debugfs_remove_recursive(wb_enc->debugfs_root);
+}
+#else
+static void sde_encoder_phys_wb_init_debugfs(
+ struct sde_encoder_phys *phys_enc, struct sde_kms *kms)
+{
+}
+static void sde_encoder_phys_wb_destroy_debugfs(
+ struct sde_encoder_phys *phys_enc)
+{
+}
+#endif
+
+/**
+ * sde_encoder_phys_wb_destroy - destroy writeback encoder
+ * @phys_enc: Pointer to physical encoder
+ */
+static void sde_encoder_phys_wb_destroy(struct sde_encoder_phys *phys_enc)
+{
+ struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
+ struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
+
+ SDE_DEBUG("[wb:%d]\n", hw_wb->idx - WB_0);
+
+ if (!phys_enc)
+ return;
+
+ sde_encoder_phys_wb_destroy_debugfs(phys_enc);
+
+ if (phys_enc->hw_ctl)
+ sde_rm_release_ctl_path(phys_enc->sde_kms,
+ phys_enc->hw_ctl->idx);
+ if (phys_enc->hw_cdm)
+ sde_rm_release_cdm_path(phys_enc->sde_kms,
+ phys_enc->hw_cdm->idx);
+ if (hw_wb)
+ sde_hw_wb_destroy(hw_wb);
+ if (phys_enc->hw_mdptop)
+ sde_hw_mdp_destroy(phys_enc->hw_mdptop);
+
+ kfree(wb_enc);
+}
+
+/**
+ * sde_encoder_phys_wb_init_ops - initialize writeback operations
+ * @ops: Pointer to encoder operation table
+ */
+static void sde_encoder_phys_wb_init_ops(struct sde_encoder_phys_ops *ops)
+{
+ ops->mode_set = sde_encoder_phys_wb_mode_set;
+ ops->enable = sde_encoder_phys_wb_enable;
+ ops->disable = sde_encoder_phys_wb_disable;
+ ops->destroy = sde_encoder_phys_wb_destroy;
+ ops->atomic_check = sde_encoder_phys_wb_atomic_check;
+ ops->get_hw_resources = sde_encoder_phys_wb_get_hw_resources;
+ ops->control_vblank_irq = sde_encoder_phys_wb_control_vblank_irq;
+ ops->wait_for_commit_done = sde_encoder_phys_wb_wait_for_commit_done;
+ ops->prepare_for_kickoff = sde_encoder_phys_wb_prepare_for_kickoff;
+ ops->handle_post_kickoff = sde_encoder_phys_wb_handle_post_kickoff;
+}
+
+/**
+ * sde_encoder_phys_wb_init - initialize writeback encoder
+ * @sde_kms: Pointer to SDE KMS object
+ * @wb_idx: Writeback index
+ * @ctl_idx: Control index
+ * @cdm_idx: Chroma down index
+ * @split_role: Master/slave mode
+ * @parent: Pointer to virtual encoder
+ * @parent_ops: Pointer to virtual encoder operations
+ */
+struct sde_encoder_phys *sde_encoder_phys_wb_init(
+ struct sde_kms *sde_kms,
+ enum sde_wb wb_idx,
+ enum sde_ctl ctl_idx,
+ enum sde_cdm cdm_idx,
+ enum sde_enc_split_role split_role,
+ struct drm_encoder *parent,
+ struct sde_encoder_virt_ops parent_ops)
+{
+ struct sde_encoder_phys *phys_enc;
+ struct sde_encoder_phys_wb *wb_enc;
+ struct sde_hw_mdp *hw_mdp;
+ int ret = 0;
+
+ SDE_DEBUG("\n");
+
+ wb_enc = kzalloc(sizeof(*wb_enc), GFP_KERNEL);
+ if (!wb_enc) {
+ ret = -ENOMEM;
+ goto fail_alloc;
+ }
+ wb_enc->irq_idx = -EINVAL;
+ wb_enc->wbdone_timeout = WAIT_TIMEOUT_MSEC;
+ init_completion(&wb_enc->wbdone_complete);
+
+ phys_enc = &wb_enc->base;
+
+ if (sde_kms->vbif[VBIF_NRT]) {
+ wb_enc->mmu_id[SDE_IOMMU_DOMAIN_UNSECURE] =
+ sde_kms->mmu_id[MSM_SMMU_DOMAIN_NRT_UNSECURE];
+ wb_enc->mmu_id[SDE_IOMMU_DOMAIN_SECURE] =
+ sde_kms->mmu_id[MSM_SMMU_DOMAIN_NRT_SECURE];
+ } else {
+ wb_enc->mmu_id[SDE_IOMMU_DOMAIN_UNSECURE] =
+ sde_kms->mmu_id[MSM_SMMU_DOMAIN_UNSECURE];
+ wb_enc->mmu_id[SDE_IOMMU_DOMAIN_SECURE] =
+ sde_kms->mmu_id[MSM_SMMU_DOMAIN_SECURE];
+ }
+
+ hw_mdp = sde_hw_mdptop_init(MDP_TOP, sde_kms->mmio, sde_kms->catalog);
+ if (IS_ERR_OR_NULL(hw_mdp)) {
+ ret = PTR_ERR(hw_mdp);
+ SDE_ERROR("failed to init hw_top: %d\n", ret);
+ goto fail_mdp_init;
+ }
+ phys_enc->hw_mdptop = hw_mdp;
+
+ if (wb_idx != SDE_NONE) {
+ struct sde_hw_wb *hw_wb;
+
+ hw_wb = sde_hw_wb_init(wb_idx, sde_kms->mmio,
+ sde_kms->catalog, phys_enc->hw_mdptop);
+ if (IS_ERR_OR_NULL(hw_wb)) {
+ ret = PTR_ERR(hw_wb);
+ SDE_ERROR("failed to init hw_wb%d: %d\n",
+ wb_idx - WB_0, ret);
+ goto fail_wb_init;
+ }
+ wb_enc->hw_wb = hw_wb;
+ } else {
+ ret = -EINVAL;
+ SDE_ERROR("invalid wb_idx\n");
+ goto fail_wb_check;
+ }
+
+ if (cdm_idx != SDE_NONE) {
+ struct sde_hw_cdm *hw_cdm;
+
+ SDE_DEBUG("Acquiring CDM %d\n", cdm_idx - CDM_0);
+ hw_cdm = sde_rm_acquire_cdm_path(sde_kms, cdm_idx,
+ phys_enc->hw_mdptop);
+ if (IS_ERR_OR_NULL(hw_cdm)) {
+ ret = PTR_ERR(hw_cdm);
+ SDE_ERROR("failed to init hw_cdm%d: %d\n",
+ cdm_idx - CDM_0, ret);
+ goto fail_cdm_init;
+ }
+ phys_enc->hw_cdm = hw_cdm;
+ }
+
+ if (ctl_idx != SDE_NONE) {
+ struct sde_hw_ctl *hw_ctl;
+
+ SDE_DEBUG("Acquiring CTL %d\n", ctl_idx - CTL_0);
+ hw_ctl = sde_rm_acquire_ctl_path(sde_kms, ctl_idx);
+ if (IS_ERR_OR_NULL(hw_ctl)) {
+ ret = PTR_ERR(hw_ctl);
+ SDE_ERROR("failed to init hw_ctl%d: %d\n",
+ ctl_idx - CTL_0, ret);
+ goto fail_ctl_init;
+ }
+ phys_enc->hw_ctl = hw_ctl;
+ } else {
+ ret = -EINVAL;
+ SDE_ERROR("invalid ctl_idx\n");
+ goto fail_ctl_check;
+ }
+
+ sde_encoder_phys_wb_init_ops(&phys_enc->ops);
+ phys_enc->parent = parent;
+ phys_enc->parent_ops = parent_ops;
+ phys_enc->sde_kms = sde_kms;
+ phys_enc->split_role = split_role;
+ spin_lock_init(&phys_enc->spin_lock);
+
+ ret = sde_encoder_phys_wb_init_debugfs(phys_enc, sde_kms);
+ if (ret) {
+ SDE_ERROR("failed to init debugfs %d\n", ret);
+ goto fail_debugfs_init;
+ }
+
+ SDE_DEBUG("Created sde_encoder_phys_wb for wb %d\n",
+ wb_enc->hw_wb->idx - WB_0);
+
+ return phys_enc;
+
+fail_debugfs_init:
+ sde_rm_release_ctl_path(sde_kms, ctl_idx);
+fail_ctl_init:
+fail_ctl_check:
+ sde_rm_release_cdm_path(sde_kms, cdm_idx);
+fail_cdm_init:
+ sde_hw_wb_destroy(wb_enc->hw_wb);
+fail_wb_init:
+fail_wb_check:
+ sde_hw_mdp_destroy(phys_enc->hw_mdptop);
+fail_mdp_init:
+ kfree(wb_enc);
+fail_alloc:
+ return ERR_PTR(ret);
+}
+
diff --git a/drivers/gpu/drm/msm/sde/sde_formats.c b/drivers/gpu/drm/msm/sde/sde_formats.c
index 5655a8a..6d9c574 100644
--- a/drivers/gpu/drm/msm/sde/sde_formats.c
+++ b/drivers/gpu/drm/msm/sde/sde_formats.c
@@ -363,6 +363,9 @@
uint32_t *v_sample,
uint32_t *h_sample)
{
+ if (!v_sample || !h_sample)
+ return;
+
switch (chroma_sample) {
case SDE_CHROMA_H2V1:
*v_sample = 1;
@@ -702,6 +705,69 @@
return ret;
}
+static void _sde_format_calc_offset_linear(struct sde_hw_fmt_layout *source,
+ u32 x, u32 y)
+{
+ if ((x == 0) && (y == 0))
+ return;
+
+ source->plane_addr[0] += y * source->plane_pitch[0];
+
+ if (source->num_planes == 1) {
+ source->plane_addr[0] += x * source->format->bpp;
+ } else {
+ uint32_t xoff, yoff;
+ uint32_t v_subsample = 1;
+ uint32_t h_subsample = 1;
+
+ _sde_get_v_h_subsample_rate(source->format->chroma_sample,
+ &v_subsample, &h_subsample);
+
+ xoff = x / h_subsample;
+ yoff = y / v_subsample;
+
+ source->plane_addr[0] += x;
+ source->plane_addr[1] += xoff +
+ (yoff * source->plane_pitch[1]);
+ if (source->num_planes == 2) /* pseudo planar */
+ source->plane_addr[1] += xoff;
+ else /* planar */
+ source->plane_addr[2] += xoff +
+ (yoff * source->plane_pitch[2]);
+ }
+}
+
+int sde_format_populate_layout_with_roi(
+ int mmu_id,
+ struct drm_framebuffer *fb,
+ struct sde_rect *roi,
+ struct sde_hw_fmt_layout *layout)
+{
+ int ret;
+
+ ret = sde_format_populate_layout(mmu_id, fb, layout);
+ if (ret || !roi)
+ return ret;
+
+ if (!roi->w || !roi->h || (roi->x + roi->w > fb->width) ||
+ (roi->y + roi->h > fb->height)) {
+ DRM_ERROR("invalid roi=[%d,%d,%d,%d], fb=[%u,%u]\n",
+ roi->x, roi->y, roi->w, roi->h,
+ fb->width, fb->height);
+ ret = -EINVAL;
+ } else if (SDE_FORMAT_IS_LINEAR(layout->format)) {
+ _sde_format_calc_offset_linear(layout, roi->x, roi->y);
+ layout->width = roi->w;
+ layout->height = roi->h;
+ } else if (roi->x || roi->y || (roi->w != fb->width) ||
+ (roi->h != fb->height)) {
+ DRM_ERROR("non-linear layout with roi not supported\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
int sde_format_check_modified_format(
const struct msm_kms *kms,
const struct msm_format *msm_fmt,
diff --git a/drivers/gpu/drm/msm/sde/sde_formats.h b/drivers/gpu/drm/msm/sde/sde_formats.h
index c890484..db2a4d3 100644
--- a/drivers/gpu/drm/msm/sde/sde_formats.h
+++ b/drivers/gpu/drm/msm/sde/sde_formats.h
@@ -87,4 +87,20 @@
struct drm_framebuffer *fb,
struct sde_hw_fmt_layout *fmtl);
+/**
+ * sde_format_populate_layout_with_roi - populate the given format layout
+ * based on mmu, fb, roi, and format found in the fb
+ * @mmu_id: mmu id handle
+ * @fb: framebuffer pointer
+ * @roi: region of interest (optional)
+ * @fmtl: format layout structure to populate
+ *
+ * Return: error code on failure, 0 on success
+ */
+int sde_format_populate_layout_with_roi(
+ int mmu_id,
+ struct drm_framebuffer *fb,
+ struct sde_rect *roi,
+ struct sde_hw_fmt_layout *fmtl);
+
#endif /*_SDE_FORMATS_H */
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_catalog.h b/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
index a7b660a..e41aa5f 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
@@ -408,10 +408,13 @@
* @id enum identifying this block
* @base register offset of this block
* @features bit mask identifying sub-blocks/features
+ * @sblk sub-block information
+ * @format_list: Pointer to list of supported formats
*/
struct sde_wb_cfg {
SDE_HW_BLK_INFO;
- struct sde_wb_sub_blocks *sblk;
+ const struct sde_wb_sub_blocks *sblk;
+ const struct sde_format_extended *format_list;
};
/**
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c b/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c
index 2f8bcc0..f071437 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_catalog_8996.c
@@ -140,6 +140,89 @@
{0, 0},
};
+static const struct sde_format_extended wb0_formats[] = {
+ {DRM_FORMAT_RGB565, 0},
+ {DRM_FORMAT_RGB888, 0},
+ {DRM_FORMAT_ARGB8888, 0},
+ {DRM_FORMAT_RGBA8888, 0},
+ {DRM_FORMAT_XRGB8888, 0},
+ {DRM_FORMAT_RGBX8888, 0},
+ {DRM_FORMAT_ARGB1555, 0},
+ {DRM_FORMAT_RGBA5551, 0},
+ {DRM_FORMAT_XRGB1555, 0},
+ {DRM_FORMAT_RGBX5551, 0},
+ {DRM_FORMAT_ARGB4444, 0},
+ {DRM_FORMAT_RGBA4444, 0},
+ {DRM_FORMAT_RGBX4444, 0},
+ {DRM_FORMAT_XRGB4444, 0},
+
+ {DRM_FORMAT_BGR565, 0},
+ {DRM_FORMAT_BGR888, 0},
+ {DRM_FORMAT_ABGR8888, 0},
+ {DRM_FORMAT_BGRA8888, 0},
+ {DRM_FORMAT_BGRX8888, 0},
+ {DRM_FORMAT_ABGR1555, 0},
+ {DRM_FORMAT_BGRA5551, 0},
+ {DRM_FORMAT_XBGR1555, 0},
+ {DRM_FORMAT_BGRX5551, 0},
+ {DRM_FORMAT_ABGR4444, 0},
+ {DRM_FORMAT_BGRA4444, 0},
+ {DRM_FORMAT_BGRX4444, 0},
+ {DRM_FORMAT_XBGR4444, 0},
+
+ {DRM_FORMAT_RGBX8888, DRM_FORMAT_MOD_QCOM_COMPRESSED},
+ {DRM_FORMAT_RGBA8888, DRM_FORMAT_MOD_QCOM_COMPRESSED},
+ {DRM_FORMAT_RGB565, DRM_FORMAT_MOD_QCOM_COMPRESSED},
+
+ {DRM_FORMAT_YUV420, 0},
+ {DRM_FORMAT_NV12, 0},
+ {DRM_FORMAT_NV16, 0},
+ {DRM_FORMAT_YUYV, 0},
+
+ {DRM_FORMAT_NV12, DRM_FORMAT_MOD_QCOM_COMPRESSED},
+ {DRM_FORMAT_AYUV, DRM_FORMAT_MOD_QCOM_COMPRESSED},
+
+ {0, 0},
+};
+
+static const struct sde_format_extended wb2_formats[] = {
+ {DRM_FORMAT_RGB565, 0},
+ {DRM_FORMAT_RGB888, 0},
+ {DRM_FORMAT_ARGB8888, 0},
+ {DRM_FORMAT_RGBA8888, 0},
+ {DRM_FORMAT_XRGB8888, 0},
+ {DRM_FORMAT_RGBX8888, 0},
+ {DRM_FORMAT_ARGB1555, 0},
+ {DRM_FORMAT_RGBA5551, 0},
+ {DRM_FORMAT_XRGB1555, 0},
+ {DRM_FORMAT_RGBX5551, 0},
+ {DRM_FORMAT_ARGB4444, 0},
+ {DRM_FORMAT_RGBA4444, 0},
+ {DRM_FORMAT_RGBX4444, 0},
+ {DRM_FORMAT_XRGB4444, 0},
+
+ {DRM_FORMAT_BGR565, 0},
+ {DRM_FORMAT_BGR888, 0},
+ {DRM_FORMAT_ABGR8888, 0},
+ {DRM_FORMAT_BGRA8888, 0},
+ {DRM_FORMAT_BGRX8888, 0},
+ {DRM_FORMAT_ABGR1555, 0},
+ {DRM_FORMAT_BGRA5551, 0},
+ {DRM_FORMAT_XBGR1555, 0},
+ {DRM_FORMAT_BGRX5551, 0},
+ {DRM_FORMAT_ABGR4444, 0},
+ {DRM_FORMAT_BGRA4444, 0},
+ {DRM_FORMAT_BGRX4444, 0},
+ {DRM_FORMAT_XBGR4444, 0},
+
+ {DRM_FORMAT_YUV420, 0},
+ {DRM_FORMAT_NV12, 0},
+ {DRM_FORMAT_NV16, 0},
+ {DRM_FORMAT_YUYV, 0},
+
+ {0, 0},
+};
+
/**
* set_cfg_1xx_init(): populate sde sub-blocks reg offsets and instance counts
*/
@@ -257,6 +340,16 @@
.version = 0x1},
};
+ /* Writeback 0/1 capability */
+ static const struct sde_wb_sub_blocks wb0 = {
+ .maxlinewidth = 2048,
+ };
+
+ /* Writeback 2 capability */
+ static const struct sde_wb_sub_blocks wb2 = {
+ .maxlinewidth = 4096,
+ };
+
/* Setup Register maps and defaults */
*cfg = (struct sde_mdss_cfg){
.mdss_count = 1,
@@ -376,11 +469,17 @@
.wb_count = 3,
.wb = {
{.id = WB_0, .base = 0x00065000,
- .features = WB01_17X_MASK},
+ .features = WB01_17X_MASK,
+ .sblk = &wb0,
+ .format_list = wb0_formats},
{.id = WB_1, .base = 0x00065800,
- .features = WB01_17X_MASK},
+ .features = WB01_17X_MASK,
+ .sblk = &wb0,
+ .format_list = wb0_formats},
{.id = WB_2, .base = 0x00066000,
- .features = WB2_17X_MASK},
+ .features = WB2_17X_MASK,
+ .sblk = &wb2,
+ .format_list = wb2_formats},
},
.ad_count = 2,
.ad = {
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_mdss.h b/drivers/gpu/drm/msm/sde/sde_hw_mdss.h
index 73cecbf..a4f5656 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_mdss.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_mdss.h
@@ -148,7 +148,10 @@
INTF_HDMI = 0x3,
INTF_LCDC = 0x5,
INTF_EDP = 0x9,
- INTF_TYPE_MAX
+ INTF_TYPE_MAX,
+
+ /* virtual interfaces */
+ INTF_WB = 0x100,
};
enum sde_intf_mode {
diff --git a/drivers/gpu/drm/msm/sde/sde_wb.c b/drivers/gpu/drm/msm/sde/sde_wb.c
new file mode 100644
index 0000000..2d2f7cf
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde/sde_wb.c
@@ -0,0 +1,692 @@
+/*
+ * Copyright (c) 2015-2016, 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) "sde-wb:[%s] " fmt, __func__
+
+#include "msm_kms.h"
+#include "sde_kms.h"
+#include "sde_wb.h"
+#include "sde_formats.h"
+
+/* maximum display mode resolution if not available from catalog */
+#define SDE_WB_MODE_MAX_WIDTH 4096
+#define SDE_WB_MODE_MAX_HEIGHT 4096
+
+/* Serialization lock for sde_wb_list */
+static DEFINE_MUTEX(sde_wb_list_lock);
+
+/* List of all writeback devices installed */
+static LIST_HEAD(sde_wb_list);
+
+/**
+ * sde_wb_is_format_valid - check if given format/modifier is supported
+ * @wb_dev: Pointer to writeback device
+ * @pixel_format: Fourcc pixel format
+ * @format_modifier: Format modifier
+ * Returns: true if valid; false otherwise
+ */
+static int sde_wb_is_format_valid(struct sde_wb_device *wb_dev,
+ u32 pixel_format, u64 format_modifier)
+{
+ const struct sde_format_extended *fmts = wb_dev->wb_cfg->format_list;
+ int i;
+
+ if (!fmts)
+ return false;
+
+ for (i = 0; fmts[i].fourcc_format; i++)
+ if ((fmts[i].modifier == format_modifier) &&
+ (fmts[i].fourcc_format == pixel_format))
+ return true;
+
+ return false;
+}
+
+enum drm_connector_status
+sde_wb_connector_detect(struct drm_connector *connector,
+ bool force,
+ void *display)
+{
+ enum drm_connector_status rc = connector_status_unknown;
+
+ SDE_DEBUG("\n");
+
+ if (display)
+ rc = ((struct sde_wb_device *)display)->detect_status;
+
+ return rc;
+}
+
+int sde_wb_connector_get_modes(struct drm_connector *connector, void *display)
+{
+ struct sde_wb_device *wb_dev;
+ int num_modes = 0;
+
+ if (!connector || !display)
+ return 0;
+
+ wb_dev = display;
+
+ SDE_DEBUG("\n");
+
+ mutex_lock(&wb_dev->wb_lock);
+ if (wb_dev->count_modes && wb_dev->modes) {
+ struct drm_display_mode *mode;
+ int i, ret;
+
+ for (i = 0; i < wb_dev->count_modes; i++) {
+ mode = drm_mode_create(connector->dev);
+ if (!mode) {
+ SDE_ERROR("failed to create mode\n");
+ break;
+ }
+ ret = drm_mode_convert_umode(mode,
+ &wb_dev->modes[i]);
+ if (ret) {
+ SDE_ERROR("failed to convert mode %d\n", ret);
+ break;
+ }
+
+ drm_mode_probed_add(connector, mode);
+ num_modes++;
+ }
+ } else {
+ u32 max_width = (wb_dev->wb_cfg && wb_dev->wb_cfg->sblk) ?
+ wb_dev->wb_cfg->sblk->maxlinewidth :
+ SDE_WB_MODE_MAX_WIDTH;
+
+ num_modes = drm_add_modes_noedid(connector, max_width,
+ SDE_WB_MODE_MAX_HEIGHT);
+ }
+ mutex_unlock(&wb_dev->wb_lock);
+ return num_modes;
+}
+
+struct drm_framebuffer *
+sde_wb_connector_state_get_output_fb(struct drm_connector_state *state)
+{
+ if (!state || !state->connector ||
+ (state->connector->connector_type !=
+ DRM_MODE_CONNECTOR_VIRTUAL)) {
+ SDE_ERROR("invalid params\n");
+ return NULL;
+ }
+
+ SDE_DEBUG("\n");
+
+ return sde_connector_get_out_fb(state);
+}
+
+int sde_wb_connector_state_get_output_roi(struct drm_connector_state *state,
+ struct sde_rect *roi)
+{
+ if (!state || !roi || !state->connector ||
+ (state->connector->connector_type !=
+ DRM_MODE_CONNECTOR_VIRTUAL)) {
+ SDE_ERROR("invalid params\n");
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("\n");
+
+ roi->x = sde_connector_get_property(state, CONNECTOR_PROP_DST_X);
+ roi->y = sde_connector_get_property(state, CONNECTOR_PROP_DST_Y);
+ roi->w = sde_connector_get_property(state, CONNECTOR_PROP_DST_W);
+ roi->h = sde_connector_get_property(state, CONNECTOR_PROP_DST_H);
+
+ return 0;
+}
+
+/**
+ * sde_wb_connector_set_modes - set writeback modes and connection status
+ * @wb_dev: Pointer to write back device
+ * @count_modes: Count of modes
+ * @modes: Pointer to writeback mode requested
+ * @connected: Connection status requested
+ * Returns: 0 if success; error code otherwise
+ */
+static
+int sde_wb_connector_set_modes(struct sde_wb_device *wb_dev,
+ u32 count_modes, struct drm_mode_modeinfo __user *modes,
+ bool connected)
+{
+ int ret = 0;
+
+ if (!wb_dev || !wb_dev->connector ||
+ (wb_dev->connector->connector_type !=
+ DRM_MODE_CONNECTOR_VIRTUAL)) {
+ SDE_ERROR("invalid params\n");
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("\n");
+
+ if (connected) {
+ SDE_DEBUG("connect\n");
+
+ if (wb_dev->modes) {
+ wb_dev->count_modes = 0;
+
+ kfree(wb_dev->modes);
+ wb_dev->modes = NULL;
+ }
+
+ if (count_modes && modes) {
+ wb_dev->modes = kcalloc(count_modes,
+ sizeof(struct drm_mode_modeinfo),
+ GFP_KERNEL);
+ if (!wb_dev->modes) {
+ SDE_ERROR("invalid params\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ if (copy_from_user(wb_dev->modes, modes,
+ count_modes *
+ sizeof(struct drm_mode_modeinfo))) {
+ SDE_ERROR("failed to copy modes\n");
+ kfree(wb_dev->modes);
+ wb_dev->modes = NULL;
+ ret = -EFAULT;
+ goto error;
+ }
+
+ wb_dev->count_modes = count_modes;
+ }
+
+ wb_dev->detect_status = connector_status_connected;
+ } else {
+ SDE_DEBUG("disconnect\n");
+
+ if (wb_dev->modes) {
+ wb_dev->count_modes = 0;
+
+ kfree(wb_dev->modes);
+ wb_dev->modes = NULL;
+ }
+
+ wb_dev->detect_status = connector_status_disconnected;
+ }
+
+error:
+ return ret;
+}
+
+int sde_wb_connector_set_property(struct drm_connector *connector,
+ struct drm_connector_state *state,
+ int property_index,
+ uint64_t value,
+ void *display)
+{
+ struct sde_wb_device *wb_dev = display;
+ struct drm_framebuffer *out_fb;
+ int rc = 0;
+
+ SDE_DEBUG("\n");
+
+ if (state && (property_index == CONNECTOR_PROP_OUT_FB)) {
+ const struct sde_format *sde_format;
+
+ out_fb = sde_connector_get_out_fb(state);
+ if (!out_fb)
+ goto done;
+
+ sde_format = sde_get_sde_format_ext(out_fb->pixel_format,
+ out_fb->modifier,
+ drm_format_num_planes(out_fb->pixel_format));
+ if (!sde_format) {
+ SDE_ERROR("failed to get sde format\n");
+ rc = -EINVAL;
+ goto done;
+ }
+
+ if (!sde_wb_is_format_valid(wb_dev, out_fb->pixel_format,
+ out_fb->modifier[0])) {
+ SDE_ERROR("unsupported writeback format 0x%x/0x%llx\n",
+ out_fb->pixel_format,
+ out_fb->modifier[0]);
+ rc = -EINVAL;
+ goto done;
+ }
+ }
+
+done:
+ return rc;
+}
+
+int sde_wb_get_info(struct msm_display_info *info, void *display)
+{
+ struct sde_wb_device *wb_dev = display;
+
+ if (!info || !wb_dev) {
+ pr_err("invalid params\n");
+ return -EINVAL;
+ }
+
+ info->intf_type = DRM_MODE_CONNECTOR_VIRTUAL;
+ info->num_of_h_tiles = 1;
+ info->h_tile_instance[0] = sde_wb_get_index(display);
+ info->is_connected = true;
+ info->capabilities = MSM_DISPLAY_CAP_HOT_PLUG | MSM_DISPLAY_CAP_EDID;
+ info->max_width = (wb_dev->wb_cfg && wb_dev->wb_cfg->sblk) ?
+ wb_dev->wb_cfg->sblk->maxlinewidth :
+ SDE_WB_MODE_MAX_WIDTH;
+ info->max_height = SDE_WB_MODE_MAX_HEIGHT;
+ info->compression = MSM_DISPLAY_COMPRESS_NONE;
+ return 0;
+}
+
+int sde_wb_connector_post_init(struct drm_connector *connector,
+ void *info,
+ void *display)
+{
+ struct sde_connector *c_conn;
+ struct sde_wb_device *wb_dev = display;
+ const struct sde_format_extended *format_list;
+
+ if (!connector || !info || !display || !wb_dev->wb_cfg) {
+ SDE_ERROR("invalid params\n");
+ return -EINVAL;
+ }
+
+ c_conn = to_sde_connector(connector);
+ wb_dev->connector = connector;
+ wb_dev->detect_status = connector_status_connected;
+ format_list = wb_dev->wb_cfg->format_list;
+
+ /*
+ * Add extra connector properties
+ */
+ msm_property_install_range(&c_conn->property_info, "FB_ID",
+ 0, ~0, ~0, CONNECTOR_PROP_OUT_FB);
+ msm_property_install_range(&c_conn->property_info, "DST_X",
+ 0, UINT_MAX, 0, CONNECTOR_PROP_DST_X);
+ msm_property_install_range(&c_conn->property_info, "DST_Y",
+ 0, UINT_MAX, 0, CONNECTOR_PROP_DST_Y);
+ msm_property_install_range(&c_conn->property_info, "DST_W",
+ 0, UINT_MAX, 0, CONNECTOR_PROP_DST_W);
+ msm_property_install_range(&c_conn->property_info, "DST_H",
+ 0, UINT_MAX, 0, CONNECTOR_PROP_DST_H);
+
+ /*
+ * Populate info buffer
+ */
+ if (format_list) {
+ sde_kms_info_start(info, "pixel_formats");
+ while (format_list->fourcc_format) {
+ sde_kms_info_append_format(info,
+ format_list->fourcc_format,
+ format_list->modifier);
+ ++format_list;
+ }
+ sde_kms_info_stop(info);
+ }
+
+ sde_kms_info_add_keyint(info,
+ "wb_intf_index",
+ wb_dev->wb_idx - WB_0);
+
+ sde_kms_info_add_keyint(info,
+ "maxlinewidth",
+ wb_dev->wb_cfg->sblk->maxlinewidth);
+
+ sde_kms_info_start(info, "features");
+ if (wb_dev->wb_cfg && (wb_dev->wb_cfg->features & SDE_WB_UBWC_1_0))
+ sde_kms_info_append(info, "wb_ubwc");
+ sde_kms_info_stop(info);
+
+ return 0;
+}
+
+struct drm_framebuffer *sde_wb_get_output_fb(struct sde_wb_device *wb_dev)
+{
+ struct drm_framebuffer *fb;
+
+ if (!wb_dev || !wb_dev->connector) {
+ SDE_ERROR("invalid params\n");
+ return NULL;
+ }
+
+ SDE_DEBUG("\n");
+
+ mutex_lock(&wb_dev->wb_lock);
+ fb = sde_wb_connector_state_get_output_fb(wb_dev->connector->state);
+ mutex_unlock(&wb_dev->wb_lock);
+
+ return fb;
+}
+
+int sde_wb_get_output_roi(struct sde_wb_device *wb_dev, struct sde_rect *roi)
+{
+ int rc;
+
+ if (!wb_dev || !wb_dev->connector || !roi) {
+ SDE_ERROR("invalid params\n");
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("\n");
+
+ mutex_lock(&wb_dev->wb_lock);
+ rc = sde_wb_connector_state_get_output_roi(
+ wb_dev->connector->state, roi);
+ mutex_unlock(&wb_dev->wb_lock);
+
+ return rc;
+}
+
+u32 sde_wb_get_num_of_displays(void)
+{
+ u32 count = 0;
+ struct sde_wb_device *wb_dev;
+
+ SDE_DEBUG("\n");
+
+ mutex_lock(&sde_wb_list_lock);
+ list_for_each_entry(wb_dev, &sde_wb_list, wb_list) {
+ count++;
+ }
+ mutex_unlock(&sde_wb_list_lock);
+
+ return count;
+}
+
+int wb_display_get_displays(void **display_array, u32 max_display_count)
+{
+ struct sde_wb_device *curr;
+ int i = 0;
+
+ SDE_DEBUG("\n");
+
+ if (!display_array || !max_display_count) {
+ if (!display_array)
+ SDE_ERROR("invalid param\n");
+ return 0;
+ }
+
+ mutex_lock(&sde_wb_list_lock);
+ list_for_each_entry(curr, &sde_wb_list, wb_list) {
+ if (i >= max_display_count)
+ break;
+ display_array[i++] = curr;
+ }
+ mutex_unlock(&sde_wb_list_lock);
+
+ return i;
+}
+
+int sde_wb_config(struct drm_device *drm_dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct sde_drm_wb_cfg *config = data;
+ struct msm_drm_private *priv;
+ struct sde_wb_device *wb_dev = NULL;
+ struct sde_wb_device *curr;
+ struct drm_connector *connector;
+ uint32_t flags;
+ uint32_t connector_id;
+ uint32_t count_modes;
+ uint64_t modes;
+ int rc;
+
+ if (!drm_dev || !data) {
+ SDE_ERROR("invalid params\n");
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("\n");
+
+ flags = config->flags;
+ connector_id = config->connector_id;
+ count_modes = config->count_modes;
+ modes = config->modes;
+
+ priv = drm_dev->dev_private;
+
+ connector = drm_connector_find(drm_dev, connector_id);
+ if (!connector) {
+ SDE_ERROR("failed to find connector\n");
+ rc = -ENOENT;
+ goto fail;
+ }
+
+ mutex_lock(&sde_wb_list_lock);
+ list_for_each_entry(curr, &sde_wb_list, wb_list) {
+ if (curr->connector == connector) {
+ wb_dev = curr;
+ break;
+ }
+ }
+ mutex_unlock(&sde_wb_list_lock);
+
+ if (!wb_dev) {
+ SDE_ERROR("failed to find wb device\n");
+ rc = -ENOENT;
+ goto fail;
+ }
+
+ mutex_lock(&wb_dev->wb_lock);
+
+ rc = sde_wb_connector_set_modes(wb_dev, count_modes,
+ (struct drm_mode_modeinfo __user *) (uintptr_t) modes,
+ (flags & SDE_DRM_WB_CFG_FLAGS_CONNECTED) ? true : false);
+
+ mutex_unlock(&wb_dev->wb_lock);
+ drm_helper_hpd_irq_event(drm_dev);
+fail:
+ return rc;
+}
+
+int sde_wb_dev_init(struct sde_wb_device *wb_dev)
+{
+ int rc = 0;
+
+ if (!wb_dev) {
+ SDE_ERROR("invalid params\n");
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("\n");
+
+ return rc;
+}
+
+int sde_wb_dev_deinit(struct sde_wb_device *wb_dev)
+{
+ int rc = 0;
+
+ if (!wb_dev) {
+ SDE_ERROR("invalid params\n");
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("\n");
+
+ return rc;
+}
+
+int sde_wb_bind(struct sde_wb_device *wb_dev, struct drm_device *drm_dev)
+{
+ int rc = 0;
+
+ if (!wb_dev || !drm_dev) {
+ SDE_ERROR("invalid params\n");
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("\n");
+
+ mutex_lock(&wb_dev->wb_lock);
+ wb_dev->drm_dev = drm_dev;
+ mutex_unlock(&wb_dev->wb_lock);
+
+ return rc;
+}
+
+int sde_wb_unbind(struct sde_wb_device *wb_dev)
+{
+ int rc = 0;
+
+ if (!wb_dev) {
+ SDE_ERROR("invalid params\n");
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("\n");
+
+ mutex_lock(&wb_dev->wb_lock);
+ wb_dev->drm_dev = NULL;
+ mutex_unlock(&wb_dev->wb_lock);
+
+ return rc;
+}
+
+int sde_wb_drm_init(struct sde_wb_device *wb_dev, struct drm_encoder *encoder)
+{
+ int rc = 0;
+
+ if (!wb_dev || !encoder) {
+ SDE_ERROR("invalid params\n");
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("\n");
+
+ mutex_lock(&wb_dev->wb_lock);
+
+ if (wb_dev->drm_dev->dev_private) {
+ struct msm_drm_private *priv = wb_dev->drm_dev->dev_private;
+ struct sde_kms *sde_kms = to_sde_kms(priv->kms);
+
+ if (wb_dev->index < sde_kms->catalog->wb_count) {
+ wb_dev->wb_idx = sde_kms->catalog->wb[wb_dev->index].id;
+ wb_dev->wb_cfg = &sde_kms->catalog->wb[wb_dev->index];
+ }
+ }
+
+ wb_dev->drm_dev = encoder->dev;
+ wb_dev->encoder = encoder;
+ mutex_unlock(&wb_dev->wb_lock);
+ return rc;
+}
+
+int sde_wb_drm_deinit(struct sde_wb_device *wb_dev)
+{
+ int rc = 0;
+
+ if (!wb_dev) {
+ SDE_ERROR("invalid params\n");
+ return -EINVAL;
+ }
+
+ SDE_DEBUG("\n");
+
+ return rc;
+}
+
+/**
+ * sde_wb_probe - load writeback module
+ * @pdev: Pointer to platform device
+ */
+static int sde_wb_probe(struct platform_device *pdev)
+{
+ struct sde_wb_device *wb_dev;
+ int ret;
+
+ wb_dev = devm_kzalloc(&pdev->dev, sizeof(*wb_dev), GFP_KERNEL);
+ if (!wb_dev)
+ return -ENOMEM;
+
+ SDE_DEBUG("\n");
+
+ ret = of_property_read_u32(pdev->dev.of_node, "cell-index",
+ &wb_dev->index);
+ if (ret) {
+ SDE_DEBUG("cell index not set, default to 0\n");
+ wb_dev->index = 0;
+ }
+
+ wb_dev->name = of_get_property(pdev->dev.of_node, "label", NULL);
+ if (!wb_dev->name) {
+ SDE_DEBUG("label not set, default to unknown\n");
+ wb_dev->name = "unknown";
+ }
+
+ wb_dev->wb_idx = SDE_NONE;
+
+ mutex_init(&wb_dev->wb_lock);
+ platform_set_drvdata(pdev, wb_dev);
+
+ mutex_lock(&sde_wb_list_lock);
+ list_add(&wb_dev->wb_list, &sde_wb_list);
+ mutex_unlock(&sde_wb_list_lock);
+
+ return 0;
+}
+
+/**
+ * sde_wb_remove - unload writeback module
+ * @pdev: Pointer to platform device
+ */
+static int sde_wb_remove(struct platform_device *pdev)
+{
+ struct sde_wb_device *wb_dev;
+ struct sde_wb_device *curr, *next;
+
+ wb_dev = platform_get_drvdata(pdev);
+ if (!wb_dev)
+ return 0;
+
+ SDE_DEBUG("\n");
+
+ mutex_lock(&sde_wb_list_lock);
+ list_for_each_entry_safe(curr, next, &sde_wb_list, wb_list) {
+ if (curr == wb_dev) {
+ list_del(&wb_dev->wb_list);
+ break;
+ }
+ }
+ mutex_unlock(&sde_wb_list_lock);
+
+ kfree(wb_dev->modes);
+ mutex_destroy(&wb_dev->wb_lock);
+
+ platform_set_drvdata(pdev, NULL);
+ devm_kfree(&pdev->dev, wb_dev);
+
+ return 0;
+}
+
+static const struct of_device_id dt_match[] = {
+ { .compatible = "qcom,wb-display"},
+ {}
+};
+
+static struct platform_driver sde_wb_driver = {
+ .probe = sde_wb_probe,
+ .remove = sde_wb_remove,
+ .driver = {
+ .name = "sde_wb",
+ .of_match_table = dt_match,
+ },
+};
+
+void sde_wb_register(void)
+{
+ platform_driver_register(&sde_wb_driver);
+}
+
+void sde_wb_unregister(void)
+{
+ platform_driver_unregister(&sde_wb_driver);
+}
diff --git a/drivers/gpu/drm/msm/sde/sde_wb.h b/drivers/gpu/drm/msm/sde/sde_wb.h
new file mode 100644
index 0000000..7905620
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde/sde_wb.h
@@ -0,0 +1,376 @@
+/* Copyright (c) 2015-2016, 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 __SDE_WB_H__
+#define __SDE_WB_H__
+
+#include <linux/platform_device.h>
+
+#include "msm_kms.h"
+#include "sde_kms.h"
+
+/**
+ * struct sde_wb_device - Writeback device context
+ * @drm_dev: Pointer to controlling DRM device
+ * @index: Index of hardware instance from device tree
+ * @wb_idx: Writeback identifier of enum sde_wb
+ * @wb_cfg: Writeback configuration catalog
+ * @name: Name of writeback device from device tree
+ * @display_type: Display type from device tree
+ * @wb_list List of all writeback devices
+ * @wb_lock Serialization lock for writeback context structure
+ * @connector: Connector associated with writeback device
+ * @encoder: Encoder associated with writeback device
+ * @count_modes: Length of writeback connector modes array
+ * @modes: Writeback connector modes array
+ */
+struct sde_wb_device {
+ struct drm_device *drm_dev;
+
+ u32 index;
+ u32 wb_idx;
+ struct sde_wb_cfg *wb_cfg;
+ const char *name;
+
+ struct list_head wb_list;
+ struct mutex wb_lock;
+
+ struct drm_connector *connector;
+ struct drm_encoder *encoder;
+
+ enum drm_connector_status detect_status;
+ u32 count_modes;
+ struct drm_mode_modeinfo *modes;
+};
+
+/**
+ * sde_wb_get_index - get device index of the given writeback device
+ * @wb_dev: Pointer to writeback device
+ * Returns: Index of hardware instance
+ */
+static inline
+int sde_wb_get_index(struct sde_wb_device *wb_dev)
+{
+ return wb_dev ? wb_dev->index : -1;
+}
+
+#ifdef CONFIG_DRM_SDE_WB
+/**
+ * sde_wb_get_output_fb - get framebuffer in current atomic state
+ * @wb_dev: Pointer to writeback device
+ * Returns: Pointer to framebuffer
+ */
+struct drm_framebuffer *sde_wb_get_output_fb(struct sde_wb_device *wb_dev);
+
+/**
+ * sde_wb_get_output_roi - get region-of-interest in current atomic state
+ * @wb_dev: Pointer to writeback device
+ * @roi: Pointer to region of interest
+ * Returns: 0 if success; error code otherwise
+ */
+int sde_wb_get_output_roi(struct sde_wb_device *wb_dev, struct sde_rect *roi);
+
+/**
+ * sde_wb_get_num_of_displays - get total number of writeback devices
+ * Returns: Number of writeback devices
+ */
+u32 sde_wb_get_num_of_displays(void);
+
+/**
+ * wb_display_get_displays - returns pointers for supported display devices
+ * @display_array: Pointer to display array to be filled
+ * @max_display_count: Size of display_array
+ * @Returns: Number of display entries filled
+ */
+int wb_display_get_displays(void **display_array, u32 max_display_count);
+
+/**
+ * wb_display_get_displays - returns pointers for supported display devices
+ * @display_array: Pointer to display array to be filled
+ * @max_display_count: Size of display_array
+ * @Returns: Number of display entries filled
+ */
+int sde_wb_dev_init(struct sde_wb_device *wb_dev);
+
+/**
+ * sde_wb_dev_deinit - perform device de-initialization
+ * @wb_dev: Pointer to writeback device
+ * Returns: 0 if success; error code otherwise
+ */
+int sde_wb_dev_deinit(struct sde_wb_device *wb_dev);
+
+/**
+ * sde_wb_bind - bind writeback device with controlling device
+ * @wb_dev: Pointer to writeback device
+ * @drm_dev: Pointer to controlling DRM device
+ * Returns: 0 if success; error code otherwise
+ */
+int sde_wb_bind(struct sde_wb_device *wb_dev, struct drm_device *drm_dev);
+
+/**
+ * sde_wb_unbind - unbind writeback from controlling device
+ * @wb_dev: Pointer to writeback device
+ * Returns: 0 if success; error code otherwise
+ */
+int sde_wb_unbind(struct sde_wb_device *wb_dev);
+
+/**
+ * sde_wb_drm_init - perform DRM initialization
+ * @wb_dev: Pointer to writeback device
+ * @encoder: Pointer to associated encoder
+ * Returns: 0 if success; error code otherwise
+ */
+int sde_wb_drm_init(struct sde_wb_device *wb_dev, struct drm_encoder *encoder);
+
+/**
+ * sde_wb_drm_deinit - perform DRM de-initialization
+ * @wb_dev: Pointer to writeback device
+ * Returns: 0 if success; error code otherwise
+ */
+int sde_wb_drm_deinit(struct sde_wb_device *wb_dev);
+
+/**
+ * sde_wb_register - register writeback module
+ */
+void sde_wb_register(void);
+
+/**
+ * sde_wb_unregister - unregister writeback module
+ */
+void sde_wb_unregister(void);
+
+/**
+ * sde_wb_config - setup connection status and available drm modes of the
+ * given writeback connector
+ * @drm_dev: Pointer to DRM device
+ * @data: Pointer to writeback configuration
+ * @file_priv: Pointer file private data
+ * Returns: 0 if success; error code otherwise
+ *
+ * This function will initiate hot-plug detection event.
+ */
+int sde_wb_config(struct drm_device *drm_dev, void *data,
+ struct drm_file *file_priv);
+
+/**
+ * sde_wb_connector_post_init - perform writeback specific initialization
+ * @connector: Pointer to drm connector structure
+ * @info: Pointer to connector info
+ * @display: Pointer to private display structure
+ * Returns: Zero on success
+ */
+int sde_wb_connector_post_init(struct drm_connector *connector,
+ void *info,
+ void *display);
+
+/**
+ * sde_wb_connector_detect - perform writeback connection status detection
+ * @connector: Pointer to connector
+ * @force: Indicate force detection
+ * @display: Pointer to writeback device
+ * Returns: connector status
+ */
+enum drm_connector_status
+sde_wb_connector_detect(struct drm_connector *connector,
+ bool force,
+ void *display);
+
+/**
+ * sde_wb_connector_get_modes - get display modes of connector
+ * @connector: Pointer to connector
+ * @display: Pointer to writeback device
+ * Returns: Number of modes
+ *
+ * If display modes are not specified in writeback configuration IOCTL, this
+ * function will install default EDID modes up to maximum resolution support.
+ */
+int sde_wb_connector_get_modes(struct drm_connector *connector, void *display);
+
+/**
+ * sde_wb_connector_set_property - set atomic connector property
+ * @connector: Pointer to drm connector structure
+ * @state: Pointer to drm connector state structure
+ * @property_index: DRM property index
+ * @value: Incoming property value
+ * @display: Pointer to private display structure
+ * Returns: Zero on success
+ */
+int sde_wb_connector_set_property(struct drm_connector *connector,
+ struct drm_connector_state *state,
+ int property_index,
+ uint64_t value,
+ void *display);
+
+/**
+ * sde_wb_get_info - retrieve writeback 'display' information
+ * @info: Pointer to display info structure
+ * @display: Pointer to private display structure
+ * Returns: Zero on success
+ */
+int sde_wb_get_info(struct msm_display_info *info, void *display);
+
+/**
+ * sde_wb_connector_get_wb - retrieve writeback device of the given connector
+ * @connector: Pointer to drm connector
+ * Returns: Pointer to writeback device on success; NULL otherwise
+ */
+static inline
+struct sde_wb_device *sde_wb_connector_get_wb(struct drm_connector *connector)
+{
+ if (!connector ||
+ (connector->connector_type != DRM_MODE_CONNECTOR_VIRTUAL)) {
+ SDE_ERROR("invalid params\n");
+ return NULL;
+ }
+
+ return sde_connector_get_display(connector);
+}
+
+/**
+ * sde_wb_connector_state_get_output_fb - get framebuffer of given state
+ * @state: Pointer to connector state
+ * Returns: Pointer to framebuffer
+ */
+struct drm_framebuffer *
+sde_wb_connector_state_get_output_fb(struct drm_connector_state *state);
+
+/**
+ * sde_wb_connector_state_get_output_roi - get roi from given atomic state
+ * @state: Pointer to atomic state
+ * @roi: Pointer to region of interest
+ * Returns: 0 if success; error code otherwise
+ */
+int sde_wb_connector_state_get_output_roi(struct drm_connector_state *state,
+ struct sde_rect *roi);
+
+#else
+static inline
+struct drm_framebuffer *sde_wb_get_output_fb(struct sde_wb_device *wb_dev)
+{
+ return NULL;
+}
+static inline
+int sde_wb_get_output_roi(struct sde_wb_device *wb_dev, struct sde_rect *roi)
+{
+ return 0;
+}
+static inline
+u32 sde_wb_get_num_of_displays(void)
+{
+ return 0;
+}
+static inline
+int wb_display_get_displays(void **display_array, u32 max_display_count)
+{
+ return 0;
+}
+static inline
+int sde_wb_dev_init(struct sde_wb_device *wb_dev)
+{
+ return 0;
+}
+static inline
+int sde_wb_dev_deinit(struct sde_wb_device *wb_dev)
+{
+ return 0;
+}
+static inline
+int sde_wb_bind(struct sde_wb_device *wb_dev, struct drm_device *drm_dev)
+{
+ return 0;
+}
+static inline
+int sde_wb_unbind(struct sde_wb_device *wb_dev)
+{
+ return 0;
+}
+static inline
+int sde_wb_drm_init(struct sde_wb_device *wb_dev, struct drm_encoder *encoder)
+{
+ return 0;
+}
+static inline
+int sde_wb_drm_deinit(struct sde_wb_device *wb_dev)
+{
+ return 0;
+}
+static inline
+void sde_wb_register(void)
+{
+}
+static inline
+void sde_wb_unregister(void)
+{
+}
+static inline
+int sde_wb_config(struct drm_device *drm_dev, void *data,
+ struct drm_file *file_priv)
+{
+ return 0;
+}
+static inline
+int sde_wb_connector_post_init(struct drm_connector *connector,
+ void *info,
+ void *display)
+{
+ return 0;
+}
+static inline
+enum drm_connector_status
+sde_wb_connector_detect(struct drm_connector *connector,
+ bool force,
+ void *display)
+{
+ return connector_status_disconnected;
+}
+static inline
+int sde_wb_connector_get_modes(struct drm_connector *connector, void *display)
+{
+ return -EINVAL;
+}
+static inline
+int sde_wb_connector_set_property(struct drm_connector *connector,
+ struct drm_connector_state *state,
+ int property_index,
+ uint64_t value,
+ void *display)
+{
+ return 0;
+}
+static inline
+int sde_wb_get_info(struct msm_display_info *info, void *display)
+{
+ return 0;
+}
+static inline
+struct sde_wb_device *sde_wb_connector_get_wb(struct drm_connector *connector)
+{
+ return NULL;
+}
+
+static inline
+struct drm_framebuffer *
+sde_wb_connector_state_get_output_fb(struct drm_connector_state *state)
+{
+ return NULL;
+}
+
+static inline
+int sde_wb_connector_state_get_output_roi(struct drm_connector_state *state,
+ struct sde_rect *roi)
+{
+ return 0;
+}
+
+#endif
+#endif /* __SDE_WB_H__ */
+